From 87bf15449b9d8487ffc0231ceca145de69464240 Mon Sep 17 00:00:00 2001 From: swapnil <78632212+swapnilpaliwal-sd@users.noreply.github.com> Date: Tue, 29 Sep 2026 17:05:21 -0700 Subject: [PATCH] path, impact: a file:line inside a type's body but in none of its members is refused, naming the members either side A line number copied before an edit often lands on a blank or doc-comment line between two methods. Where a class body has no module node of its own (TypeScript, JavaScript, Java, C#), that line fell through to the file's module and was answered as its top-level code, with nothing saying the line held no declaration; Java said only "no callable spans". It is now refused with the enclosing type and the nearest declaration above and below it. A Python class body is a module node inside the type and answers as before. Checked: new checks in fileline-line-outside-file (JS) and fileline-dotted-basename (TS) fail before (3 of 32) and pass after (32 of 32); graph unchanged (9988 call edges on a real NestJS repo either way). --- .../skills/axiomcode/scripts/axiomcode-path | 16 +++++++++++++++ .../fileline-line-outside-file/case.json | 13 ++++++++++++ .../src/services/cart.js | 8 ++++++++ .../fileline-dotted-basename/case.json | 20 ++++++++++++++++++- .../fileline-dotted-basename/src/cart.ts | 7 +++++++ 5 files changed, 63 insertions(+), 1 deletion(-) create mode 100644 tests/cases/javascript/fileline-line-outside-file/src/services/cart.js create mode 100644 tests/cases/typescript/fileline-dotted-basename/src/cart.ts diff --git a/plugins/axiomcode/skills/axiomcode/scripts/axiomcode-path b/plugins/axiomcode/skills/axiomcode/scripts/axiomcode-path index 3fbea52f1..4f92d6121 100755 --- a/plugins/axiomcode/skills/axiomcode/scripts/axiomcode-path +++ b/plugins/axiomcode/skills/axiomcode/scripts/axiomcode-path @@ -406,6 +406,22 @@ class G: # body's), and the first row came back whatever the line, so a module-level constant below a class was # answered as that class's body r = self.q("SELECT id FROM symbols WHERE file = ? AND kind = 'module' AND method_id IS NOT NULL ORDER BY (line <= ? AND COALESCE(end_line, line) >= ?) DESC, COALESCE(end_line, line) - line LIMIT 1", f, ln, ln) + # A LINE INSIDE A TYPE'S BODY BUT IN NONE OF ITS MEMBERS is not top-level code. A blank or comment line between + # two methods — a line number copied before the file was edited — was answered as the whole file's module + # where a class body has no module node of its own (TypeScript, Java, C#), with no word that the line holds + # nothing. A Python class body IS a module node inside the type, and still answers as before. + ty = self.q("SELECT id, display, line, end_line FROM symbols WHERE file = ? AND type_id IS NOT NULL AND method_id IS NULL AND line < ? AND end_line > ? ORDER BY end_line - line LIMIT 1", f, ln, ln) + mod = self.sym.get(r[0]['id'], {}) if r else {} + if ty and not (r and ty[0]['line'] <= (mod.get('line') or 0) and (mod.get('end_line') or 0) <= ty[0]['end_line']): + t = ty[0] + mem = self.q("SELECT id, line, end_line FROM symbols WHERE file = ? AND line > ? AND end_line < ? AND kind <> 'module' AND (method_id IS NOT NULL OR kind IN ('field','const','enum_member','variable'))", f, t['line'], t['end_line']) + above = max((x for x in mem if (x['end_line'] or x['line']) < ln), key=lambda x: (x['end_line'] or x['line'], -x['line']), default=None) + below = min((x for x in mem if x['line'] > ln), key=lambda x: (x['line'], x['line'] - (x['end_line'] or x['line'])), default=None) + near = [f" {self.name(x['id'])} {f}:{x['line']}" for x in (above, below) if x] + die(f"line {ln} of {f} is inside {t['display']} ({f}:{t['line']}-{t['end_line']}) but in none of its declarations" + " (a blank, comment or separator line — often a line number from before an edit)." + + ("\n the nearest declarations:\n" + '\n'.join(near) if near else '') + + f"\n ask for one of them, by name or by its line; `{t['display']}` asks about the whole type") if r: return f"{self.disp(r[0]['id'])} (top-level code at {s})", [r[0]['id']] if outside: die(f"{m.group(1)} is outside the indexed repository {self.repo}: give the file relative to that root, or ask the graph of the repository it belongs to") die(f"no callable spans {s}") diff --git a/tests/cases/javascript/fileline-line-outside-file/case.json b/tests/cases/javascript/fileline-line-outside-file/case.json index 2dae3ef39..7e0cde15a 100644 --- a/tests/cases/javascript/fileline-line-outside-file/case.json +++ b/tests/cases/javascript/fileline-line-outside-file/case.json @@ -36,6 +36,19 @@ "run": ["path", "*", "src/services/userService.js:2"], "want": ["getUser"], "avoid": ["is not in it"]}, + {"why": "a blank line between two members of a class is refused with the members either side, not answered as the file's top-level code", + "run": ["impact", "src/services/cart.js:3"], + "expect_error": true, + "want": ["line 3 of src/services/cart.js is inside Cart", "Cart.add src/services/cart.js:5"], + "avoid": [""]}, + {"why": "control: the class header line still answers for the class", + "run": ["impact", "src/services/cart.js:1"], + "want": ["Cart"], + "avoid": ["is inside Cart", "change: cart."]}, + {"why": "control: the line after the class is still the file's top-level code", + "run": ["impact", "src/services/cart.js:7"], + "want": ["cart. (at src/services/cart.js:7)"], + "avoid": ["is inside Cart"]}, {"why": "control: a file the index does not hold is still refused as before", "run": ["impact", "zzz/userService.js:99"], "expect_error": true, diff --git a/tests/cases/javascript/fileline-line-outside-file/src/services/cart.js b/tests/cases/javascript/fileline-line-outside-file/src/services/cart.js new file mode 100644 index 000000000..159f3f1b3 --- /dev/null +++ b/tests/cases/javascript/fileline-line-outside-file/src/services/cart.js @@ -0,0 +1,8 @@ +class Cart { + constructor() { this.items = []; } + + /** adds one */ + add(x) { this.items.push(x); } +} +new Cart().add('a'); +module.exports = { Cart }; diff --git a/tests/cases/typescript/fileline-dotted-basename/case.json b/tests/cases/typescript/fileline-dotted-basename/case.json index 701c6d748..7efe90b68 100644 --- a/tests/cases/typescript/fileline-dotted-basename/case.json +++ b/tests/cases/typescript/fileline-dotted-basename/case.json @@ -23,4 +23,22 @@ {"why": "path takes a ./ file:line", "run": ["path", "*", "./src/user.service.ts:3"], "want": ["UserController.get"], - "avoid": ["no callable spans"]}]} + "avoid": ["no callable spans"]}, + {"why": "a blank line inside a class body, between two members, is refused with the members either side, not answered as the file's top-level code", + "run": ["impact", "src/cart.ts:3"], + "expect_error": true, + "want": ["line 3 of src/cart.ts is inside Cart", "Cart.add src/cart.ts:5"], + "avoid": [""]}, + {"why": "the same for a member's doc-comment line, through path", + "run": ["path", "*", "src/cart.ts:4"], + "expect_error": true, + "want": ["line 4 of src/cart.ts is inside Cart", "Cart.add src/cart.ts:5"], + "avoid": ["top-level code"]}, + {"why": "control: the member's own line still answers for it", + "run": ["impact", "src/cart.ts:5"], + "want": ["change: Cart.add (at src/cart.ts:5)"], + "avoid": ["is inside Cart"]}, + {"why": "control: a line after the class is still the file's top-level code", + "run": ["impact", "src/cart.ts:7"], + "want": ["cart. (at src/cart.ts:7)"], + "avoid": ["is inside Cart"]}]} diff --git a/tests/cases/typescript/fileline-dotted-basename/src/cart.ts b/tests/cases/typescript/fileline-dotted-basename/src/cart.ts new file mode 100644 index 000000000..8b0c3aa94 --- /dev/null +++ b/tests/cases/typescript/fileline-dotted-basename/src/cart.ts @@ -0,0 +1,7 @@ +export class Cart { + private items: string[] = []; + + /** adds one */ + add(x: string): void { this.items.push(x); } +} +new Cart().add('a');