Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion src/signals/boundary-test-generation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,11 @@ export type BoundaryTouch = {
// true-positive set). Each pattern only matches an ADDED line (a line starting with a single `+`, not `++`
// which is the `+++ b/file` patch header) so this only ever reacts to genuinely new code, never context lines
// or the file the diff is against.
const ARRAY_INDEX_BOUNDS_PATTERN = /\[\s*(?:[\w.]+\.length|[\w.]+\.length\s*-\s*1|-1)\s*\]|\.length\s*(?:-\s*1)?\s*[<>]=?/;
// The `.at(<literal>)` alternative catches the modern `arr.at(-1)` / `arr.at(0)` last-element/off-by-one
// idiom (bracket forms above miss it). Deliberately only a NUMERIC literal argument (optionally negative):
// a bare identifier like `.at(idx)` carries none of the specific boundary signal `-1`/`0` does, and matching
// it would reintroduce the false-positive noise this pattern set is kept small to avoid.
const ARRAY_INDEX_BOUNDS_PATTERN = /\[\s*(?:[\w.]+\.length|[\w.]+\.length\s*-\s*1|-1)\s*\]|\.length\s*(?:-\s*1)?\s*[<>]=?|\.at\(\s*-?\d+\s*\)/;
const NULL_OR_UNDEFINED_BRANCH_PATTERN = /(?:===?|!==?)\s*(?:null|undefined)\b|\b(?:null|undefined)\s*(?:===?|!==?)|\?\?|\?\./;
const EMPTY_COLLECTION_CHECK_PATTERN = /\.length\s*(?:===?|!==?|[<>]=?)\s*0\b|\blen\(.*\)\s*(?:===?|!==?|[<>]=?)\s*0\b|\.(?:isEmpty|is_empty)\s*\(/;

Expand Down
13 changes: 13 additions & 0 deletions test/unit/boundary-test-generation.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,19 @@ describe("detectBoundaryTouches", () => {
expect(touches[0]).toMatchObject({ path: "src/list.ts", kind: "array_index_bounds" });
});

it("detects the modern `.at(<literal>)` last-element/off-by-one idiom as array/index bounds", () => {
for (const line of ["+const last = items.at(-1);\n", "+const head = items.at(0);\n", "+const penult = rows.at(-2);\n"]) {
const touches = detectBoundaryTouches([{ path: "src/list.ts", patch: line }]);
expect(touches).toHaveLength(1);
expect(touches[0]?.kind).toBe("array_index_bounds");
}
});

it("does NOT flag `.at(<identifier>)` — a non-literal argument carries no off-by-one boundary signal", () => {
// Narrow-scope guard (#1972): only a numeric literal argument is a boundary tell; `.at(idx)` is not.
expect(detectBoundaryTouches([{ path: "src/list.ts", patch: "+const item = items.at(idx);\n" }])).toHaveLength(0);
});

it("detects a null/undefined branch pattern in an added line", () => {
const touches = detectBoundaryTouches([{ path: "src/user.ts", patch: "@@ -1,1 +1,2 @@\n+if (user === null) return defaultUser;\n" }]);
expect(touches).toHaveLength(1);
Expand Down