buildReplacementLines emits the splice for a fuzzy match by per-position substitution at leading-ws, body, trailing-ws, and ending. Search and replace agreement at a position -> file's bytes win; disagreement -> replacement's bytes are spliced. Extra replace lines past the matched region reference t
(matched, searchLines []string, replace, forcedEnding string, atNoNewlineEOF bool)
| 963 | // |
| 964 | //nolint:revive // atNoNewlineEOF is a computed match property, not caller control coupling. |
| 965 | func buildReplacementLines(matched, searchLines []string, replace, forcedEnding string, atNoNewlineEOF bool) string { |
| 966 | repLines := strings.SplitAfter(replace, "\n") |
| 967 | // SplitAfter on a string ending in "\n" yields a trailing empty |
| 968 | // element. Drop it so it doesn't pair with a phantom line. |
| 969 | if len(repLines) > 0 && repLines[len(repLines)-1] == "" { |
| 970 | repLines = repLines[:len(repLines)-1] |
| 971 | } |
| 972 | prefix, suffix := alignSearchReplace(searchLines, repLines) |
| 973 | |
| 974 | // Combine search and replace so a zero-width search still |
| 975 | // informs the unit from the replacement's inserted depths. |
| 976 | // Fallback for detection failure lives in the inserted branch. |
| 977 | searchUnit, searchUnitOK := detectIndentUnit(append(append([]string(nil), searchLines...), repLines...)) |
| 978 | fileUnit, fileUnitOK := detectIndentUnit(matched) |
| 979 | var b strings.Builder |
| 980 | for i, rLine := range repLines { |
| 981 | var refIdx int |
| 982 | inserted := false |
| 983 | searchMiddleLen := len(searchLines) - prefix - suffix |
| 984 | switch { |
| 985 | case i < prefix: |
| 986 | refIdx = i |
| 987 | case i >= len(repLines)-suffix: |
| 988 | refIdx = i - (len(repLines) - len(searchLines)) |
| 989 | case i-prefix < searchMiddleLen: |
| 990 | refIdx = prefix + (i - prefix) |
| 991 | default: |
| 992 | // Pure insertion: pick the reference content line by |
| 993 | // the caller's indent signal. An inserted line whose |
| 994 | // lead matches the suffix's first rep line belongs to |
| 995 | // the suffix scope; one matching the prefix's last rep |
| 996 | // line belongs to the prefix scope. Fall back to |
| 997 | // suffix, then prefix, then i-clamped. |
| 998 | inserted = true |
| 999 | rLeadForI := leadOnly(rLine) |
| 1000 | switch { |
| 1001 | case prefix > 0 && suffix > 0: |
| 1002 | prefixRLead := leadOnly(repLines[prefix-1]) |
| 1003 | suffixRLead := leadOnly(repLines[len(repLines)-suffix]) |
| 1004 | switch { |
| 1005 | case rLeadForI == suffixRLead: |
| 1006 | refIdx = len(searchLines) - suffix |
| 1007 | case rLeadForI == prefixRLead: |
| 1008 | refIdx = prefix - 1 |
| 1009 | default: |
| 1010 | refIdx = len(searchLines) - suffix |
| 1011 | } |
| 1012 | case suffix > 0: |
| 1013 | refIdx = len(searchLines) - suffix |
| 1014 | case prefix > 0: |
| 1015 | refIdx = prefix - 1 |
| 1016 | default: |
| 1017 | refIdx = min(i, len(searchLines)-1) |
| 1018 | } |
| 1019 | } |
| 1020 | refContent := matched[refIdx] |
| 1021 | sLead, _, sTrail, sEnd := splitLineParts(searchLines[refIdx]) |
| 1022 | rLead, rMid, rTrail, rEnd := splitLineParts(rLine) |
no test coverage detected