diff --git a/mutannot.fsx b/mutannot.fsx index a797846..821447f 100755 --- a/mutannot.fsx +++ b/mutannot.fsx @@ -178,27 +178,40 @@ let findMutation id mutations = |> Array.tryFind (fun mutation -> mutation.Id = id) |> Option.defaultWith (fun () -> fail $"Unknown mutation id: {id}") -let lineNumberAt (text: string) index = - let mutable line = 1 - for i in 0 .. index - 1 do - if text[i] = '\n' then - line <- line + 1 - line +let lineSpan (filePath: string) (text: string) (lineNumber: int) = + if lineNumber < 1 then + fail $"Line number must be positive: {lineNumber}" -let replaceNearestOccurrence (mutation: MutationCase) (text: string) = - let rec collect fromIndex acc = - let idx = text.IndexOf(mutation.Find, fromIndex, StringComparison.Ordinal) - if idx < 0 then List.rev acc - else collect (idx + 1) (idx :: acc) + let mutable currentLine = 1 + let mutable lineStart = 0 + let mutable index = 0 - let matches = collect 0 [] - match matches with - | [] -> fail $"Could not find '{mutation.Find}' in {mutation.File}" - | _ -> - let chosen = - matches - |> List.minBy (fun idx -> abs (lineNumberAt text idx - mutation.Line)) - text.Remove(chosen, mutation.Find.Length).Insert(chosen, mutation.Replace) + while index < text.Length && currentLine < lineNumber do + if text[index] = '\n' then + currentLine <- currentLine + 1 + lineStart <- index + 1 + + index <- index + 1 + + if currentLine <> lineNumber then + fail $"Line {lineNumber} does not exist in {filePath}" + + let mutable lineEnd = lineStart + while lineEnd < text.Length && text[lineEnd] <> '\n' && text[lineEnd] <> '\r' do + lineEnd <- lineEnd + 1 + + lineStart, lineEnd + +let replaceOccurrenceOnLine (mutation: MutationCase) (text: string) = + let lineStart, lineEnd = lineSpan mutation.File text mutation.Line + let lineText = text.Substring(lineStart, lineEnd - lineStart) + let lineOffset = lineText.IndexOf(mutation.Find, StringComparison.Ordinal) + + if lineOffset < 0 then + fail $"Line {mutation.Line} in {mutation.File} does not contain '{mutation.Find}'." + + let absoluteOffset = lineStart + lineOffset + text.Remove(absoluteOffset, mutation.Find.Length).Insert(absoluteOffset, mutation.Replace) let printMutation mutation = printfn "id: %s" mutation.Id @@ -243,7 +256,7 @@ let runMutation (mutation: MutationCase) = fail $"Target file does not exist in worktree: {targetFile}" let originalText = File.ReadAllText targetFile - let mutatedText = replaceNearestOccurrence mutation originalText + let mutatedText = replaceOccurrenceOnLine mutation originalText File.WriteAllText(targetFile, mutatedText) printfn "==> %s: %s" mutation.Id mutation.TestName