fix(hashline-edit): improve error messages for invalid LINE#ID references
- Detect non-numeric prefixes (e.g., "LINE#HK", "POS#VK") and explain
that the prefix must be an actual line number, not literal text
- Add suggestLineForHash() that reverse-looks up a hash in file lines
to suggest the correct reference (e.g., Did you mean "1#HK"?)
- Unify error message format from "LINE#ID" to "{line_number}#{hash_id}"
matching the tool description convention
- Add 3 tests covering non-numeric prefix detection and hash suggestion
This commit is contained in:
@@ -19,7 +19,23 @@ describe("parseLineRef", () => {
|
||||
const ref = "42:VK"
|
||||
|
||||
//#when / #then
|
||||
expect(() => parseLineRef(ref)).toThrow("LINE#ID")
|
||||
expect(() => parseLineRef(ref)).toThrow("{line_number}#{hash_id}")
|
||||
})
|
||||
|
||||
it("gives specific hint when literal text is used instead of line number", () => {
|
||||
//#given — model sends "LINE#HK" instead of "1#HK"
|
||||
const ref = "LINE#HK"
|
||||
|
||||
//#when / #then — error should mention that LINE is not a valid number
|
||||
expect(() => parseLineRef(ref)).toThrow(/not a line number/i)
|
||||
})
|
||||
|
||||
it("gives specific hint for other non-numeric prefixes like POS#VK", () => {
|
||||
//#given
|
||||
const ref = "POS#VK"
|
||||
|
||||
//#when / #then
|
||||
expect(() => parseLineRef(ref)).toThrow(/not a line number/i)
|
||||
})
|
||||
|
||||
it("accepts refs copied with markers and trailing content", () => {
|
||||
@@ -60,4 +76,13 @@ describe("validateLineRef", () => {
|
||||
expect(() => validateLineRefs(lines, ["2#ZZ"]))
|
||||
.toThrow(/>>>\s+2#[ZPMQVRWSNKTXJBYH]{2}\|two/)
|
||||
})
|
||||
|
||||
it("suggests correct line number when hash matches a file line", () => {
|
||||
//#given — model sends LINE#XX where XX is the actual hash for line 1
|
||||
const lines = ["function hello() {", " return 42", "}"]
|
||||
const hash = computeLineHash(1, lines[0])
|
||||
|
||||
//#when / #then — error should suggest the correct reference
|
||||
expect(() => validateLineRefs(lines, [`LINE#${hash}`])).toThrow(new RegExp(`1#${hash}`))
|
||||
})
|
||||
})
|
||||
|
||||
@@ -38,13 +38,30 @@ export function parseLineRef(ref: string): LineRef {
|
||||
hash: match[2],
|
||||
}
|
||||
}
|
||||
const nonNumericMatch = ref.trim().match(/^([A-Za-z_]+)#([ZPMQVRWSNKTXJBYH]{2})$/)
|
||||
if (nonNumericMatch) {
|
||||
throw new Error(
|
||||
`Invalid line reference: "${ref}". "${nonNumericMatch[1]}" is not a line number. ` +
|
||||
`Use the actual line number from the read output.`
|
||||
)
|
||||
}
|
||||
throw new Error(
|
||||
`Invalid line reference format: "${ref}". Expected format: "LINE#ID" (e.g., "42#VK")`
|
||||
`Invalid line reference format: "${ref}". Expected format: "{line_number}#{hash_id}"`
|
||||
)
|
||||
}
|
||||
|
||||
export function validateLineRef(lines: string[], ref: string): void {
|
||||
const { line, hash } = parseLineRef(ref)
|
||||
let parsed: LineRef
|
||||
try {
|
||||
parsed = parseLineRef(ref)
|
||||
} catch (parseError) {
|
||||
const hint = suggestLineForHash(ref, lines)
|
||||
if (hint && parseError instanceof Error) {
|
||||
throw new Error(`${parseError.message} ${hint}`)
|
||||
}
|
||||
throw parseError
|
||||
}
|
||||
const { line, hash } = parsed
|
||||
|
||||
if (line < 1 || line > lines.length) {
|
||||
throw new Error(
|
||||
@@ -92,7 +109,7 @@ export class HashlineMismatchError extends Error {
|
||||
const output: string[] = []
|
||||
output.push(
|
||||
`${mismatches.length} line${mismatches.length > 1 ? "s have" : " has"} changed since last read. ` +
|
||||
"Use updated LINE#ID references below (>>> marks changed lines)."
|
||||
"Use updated {line_number}#{hash_id} references below (>>> marks changed lines)."
|
||||
)
|
||||
output.push("")
|
||||
|
||||
@@ -117,11 +134,33 @@ export class HashlineMismatchError extends Error {
|
||||
}
|
||||
}
|
||||
|
||||
function suggestLineForHash(ref: string, lines: string[]): string | null {
|
||||
const hashMatch = ref.trim().match(/#([ZPMQVRWSNKTXJBYH]{2})$/)
|
||||
if (!hashMatch) return null
|
||||
const hash = hashMatch[1]
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
if (computeLineHash(i + 1, lines[i]) === hash) {
|
||||
return `Did you mean "${i + 1}#${hash}"?`
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
export function validateLineRefs(lines: string[], refs: string[]): void {
|
||||
const mismatches: HashMismatch[] = []
|
||||
|
||||
for (const ref of refs) {
|
||||
const { line, hash } = parseLineRef(ref)
|
||||
let parsed: LineRef
|
||||
try {
|
||||
parsed = parseLineRef(ref)
|
||||
} catch (parseError) {
|
||||
const hint = suggestLineForHash(ref, lines)
|
||||
if (hint && parseError instanceof Error) {
|
||||
throw new Error(`${parseError.message} ${hint}`)
|
||||
}
|
||||
throw parseError
|
||||
}
|
||||
const { line, hash } = parsed
|
||||
|
||||
if (line < 1 || line > lines.length) {
|
||||
throw new Error(`Line number ${line} out of bounds (file has ${lines.length} lines)`)
|
||||
|
||||
Reference in New Issue
Block a user