fix: clamp negative line numbers when reading files (#2337)

Improve handling of AI-generated line numbers:
- Clamp negative startLine values to 0 instead of throwing errors
- Convert non-integer line numbers to integers automatically
- Only reject when line numbers aren't numbers at all

These changes prevent unnecessary model retries by handling imperfect
AI-generated line numbers gracefully, making the system more robust
when interacting with AI models.

Signed-off-by: Eric Wheeler <roo-code@z.ewheeler.org>
Co-authored-by: Eric Wheeler <roo-code@z.ewheeler.org>
This commit is contained in:
KJ7LNW 2025-04-05 19:10:11 -07:00 • committed by GitHub
parent 7261efbdec
commit 5e0c9a71e4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 26 additions and 21 deletions

View file

@ -33,23 +33,21 @@ describe("nthline", () => {
it("should throw error for negative to_line", async () => {
await expect(readLines(testFile, -3)).rejects.toThrow(
"Invalid endLine: -3. Line numbers must be non-negative integers.",
"startLine (0) must be less than or equal to endLine (-3)",
)
})
it("should throw error for negative from_line", async () => {
await expect(readLines(testFile, 3, -1)).rejects.toThrow(
"Invalid startLine: -1. Line numbers must be non-negative integers.",
)
it("should handle negative from_line by clamping to 0", async () => {
const lines = await readLines(testFile, 3, -1)
expect(lines).toEqual(["Line 1", "Line 2", "Line 3", "Line 4"].join("\n"))
})
it("should throw error for non-integer line numbers", async () => {
await expect(readLines(testFile, 3, 1.5)).rejects.toThrow(
"Invalid startLine: 1.5. Line numbers must be non-negative integers.",
)
await expect(readLines(testFile, 3.5)).rejects.toThrow(
"Invalid endLine: 3.5. Line numbers must be non-negative integers.",
)
it("should floor non-integer line numbers", async () => {
const linesWithNonIntegerStart = await readLines(testFile, 3, 1.5)
expect(linesWithNonIntegerStart).toEqual(["Line 2", "Line 3", "Line 4"].join("\n"))
const linesWithNonIntegerEnd = await readLines(testFile, 3.5)
expect(linesWithNonIntegerEnd).toEqual(["Line 1", "Line 2", "Line 3", "Line 4"].join("\n"))
})
it("should throw error when from_line > to_line", async () => {

View file

@ -24,17 +24,24 @@ const outOfRangeError = (filepath: string, n: number) => {
*/
export function readLines(filepath: string, endLine?: number, startLine?: number): Promise<string> {
return new Promise((resolve, reject) => {
// Validate input parameters
// Check startLine validity if provided
if (startLine !== undefined && (startLine < 0 || startLine % 1 !== 0)) {
return reject(
new RangeError(`Invalid startLine: ${startLine}. Line numbers must be non-negative integers.`),
)
// Reject if startLine is defined but not a number
if (startLine !== undefined && typeof startLine !== "number") {
return reject(new RangeError(`Invalid startLine: ${startLine}. Line numbers must be numbers.`))
}
// Check endLine validity if provided
if (endLine !== undefined && (endLine < 0 || endLine % 1 !== 0)) {
return reject(new RangeError(`Invalid endLine: ${endLine}. Line numbers must be non-negative integers.`))
// Force startLine to be an integer and clamp to 0 if negative
if (startLine !== undefined) {
startLine = Math.max(0, Math.floor(startLine))
}
// Reject if endLine is defined but not a number
if (endLine !== undefined && typeof endLine !== "number") {
return reject(new RangeError(`Invalid endLine: ${endLine}. Line numbers must be numbers.`))
}
// Force endLine to be an integer
if (endLine !== undefined) {
endLine = Math.floor(endLine)
}
const effectiveStartLine = startLine === undefined ? 0 : startLine