fix(hashline-edit): detect overlapping ranges and prevent false unwrap of blank-line spans

- Add detectOverlappingRanges() to reject edits with overlapping pos..end ranges
  instead of crashing with undefined.match()
- Add bounds guard (?? "") in edit-operation-primitives for out-of-range line access
- Add null guard in leadingWhitespace() for undefined/empty input
- Fix restoreOldWrappedLines false unwrap: skip candidate spans containing
  blank/whitespace-only lines, preventing incorrect collapse of structural
  blank lines and indentation (the "애국가 bug")
- Improve tool description for range replace clarity
- Add tests: overlapping range detection, false unwrap prevention
This commit is contained in:
minpeter
2026-02-24 14:46:17 +09:00
parent a5aa5780e3
commit 12f44d284b
7 changed files with 84 additions and 5 deletions
@@ -15,6 +15,7 @@ export function stripMergeOperatorChars(text: string): string {
}
function leadingWhitespace(text: string): string {
if (!text) return ""
const match = text.match(/^\s*/)
return match ? match[0] : ""
}
@@ -36,7 +37,9 @@ export function restoreOldWrappedLines(originalLines: string[], replacementLines
const candidates: { start: number; len: number; replacement: string; canonical: string }[] = []
for (let start = 0; start < replacementLines.length; start += 1) {
for (let len = 2; len <= 10 && start + len <= replacementLines.length; len += 1) {
const canonicalSpan = stripAllWhitespace(replacementLines.slice(start, start + len).join(""))
const span = replacementLines.slice(start, start + len)
if (span.some((line) => line.trim().length === 0)) continue
const canonicalSpan = stripAllWhitespace(span.join(""))
const original = canonicalToOriginal.get(canonicalSpan)
if (original && original.count === 1 && canonicalSpan.length >= 6) {
candidates.push({ start, len, replacement: original.line, canonical: canonicalSpan })