fix(hashline-edit): widen non-numeric prefix detection and remove duplicate try-catch
- Replace regex /^([A-Za-z_]+)#.../ with indexOf-based prefix check to catch line-ref#VK and line.ref#VK style inputs that were previously giving generic errors - Extract parseLineRefWithHint helper to eliminate duplicated try-catch in validateLineRef and validateLineRefs - Restore idempotency guard in appendWriteHashlineOutput using new output format - Add tests for LINE42 extraction, line-ref hint, line.ref hint, and guard behavior Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
@@ -38,6 +38,32 @@ describe("parseLineRef", () => {
|
||||
expect(() => parseLineRef(ref)).toThrow(/not a line number/i)
|
||||
})
|
||||
|
||||
it("extracts valid line number from mixed prefix like LINE42 without throwing", () => {
|
||||
//#given — normalizeLineRef extracts 42#VK from LINE42#VK
|
||||
const ref = "LINE42#VK"
|
||||
|
||||
//#when / #then — should parse successfully as line 42
|
||||
const result = parseLineRef(ref)
|
||||
expect(result.line).toBe(42)
|
||||
expect(result.hash).toBe("VK")
|
||||
})
|
||||
|
||||
it("gives specific hint when hyphenated prefix like line-ref is used", () => {
|
||||
//#given
|
||||
const ref = "line-ref#VK"
|
||||
|
||||
//#when / #then
|
||||
expect(() => parseLineRef(ref)).toThrow(/not a line number/i)
|
||||
})
|
||||
|
||||
it("gives specific hint when prefix contains a period like line.ref", () => {
|
||||
//#given
|
||||
const ref = "line.ref#VK"
|
||||
|
||||
//#when / #then
|
||||
expect(() => parseLineRef(ref)).toThrow(/not a line number/i)
|
||||
})
|
||||
|
||||
it("accepts refs copied with markers and trailing content", () => {
|
||||
//#given
|
||||
const ref = ">>> 42#VK|const value = 1"
|
||||
|
||||
@@ -38,12 +38,16 @@ 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.`
|
||||
)
|
||||
const hashIdx = normalized.indexOf('#')
|
||||
if (hashIdx > 0) {
|
||||
const prefix = normalized.slice(0, hashIdx)
|
||||
const suffix = normalized.slice(hashIdx + 1)
|
||||
if (!/^\d+$/.test(prefix) && /^[ZPMQVRWSNKTXJBYH]{2}$/.test(suffix)) {
|
||||
throw new Error(
|
||||
`Invalid line reference: "${ref}". "${prefix}" 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_number}#{hash_id}"`
|
||||
@@ -51,17 +55,7 @@ export function parseLineRef(ref: string): LineRef {
|
||||
}
|
||||
|
||||
export function validateLineRef(lines: string[], ref: string): void {
|
||||
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
|
||||
const { line, hash } = parseLineRefWithHint(ref, lines)
|
||||
|
||||
if (line < 1 || line > lines.length) {
|
||||
throw new Error(
|
||||
@@ -145,22 +139,24 @@ function suggestLineForHash(ref: string, lines: string[]): string | null {
|
||||
}
|
||||
return null
|
||||
}
|
||||
function parseLineRefWithHint(ref: string, lines: string[]): LineRef {
|
||||
try {
|
||||
return parseLineRef(ref)
|
||||
} catch (parseError) {
|
||||
const hint = suggestLineForHash(ref, lines)
|
||||
if (hint && parseError instanceof Error) {
|
||||
throw new Error(`${parseError.message} ${hint}`)
|
||||
}
|
||||
throw parseError
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export function validateLineRefs(lines: string[], refs: string[]): void {
|
||||
const mismatches: HashMismatch[] = []
|
||||
|
||||
for (const ref of refs) {
|
||||
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
|
||||
const { line, hash } = parseLineRefWithHint(ref, lines)
|
||||
|
||||
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