Skip to content

Commit ebcc41d

Browse files
Merge pull request #1735 from AxiomCodeAI/loop/ja-is1666
impact / path: --in chooses among the files a partial file:line path matches
2 parents 8157a50 + 19bd139 commit ebcc41d

13 files changed

Lines changed: 111 additions & 9 deletions

File tree

‎plugins/axiomcode/skills/axiomcode/scripts/axiomcode-impact‎

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -394,9 +394,8 @@ class Impact:
394394
mfl = re.match(r'^(.+?):(\d+)(?::\d+)?$', s)
395395
if mfl and ('/' in mfl.group(1) or '.' in mfl.group(1)) and kind in (None, 'field', 'type'):
396396
f, ln = self.g.repo_file(mfl.group(1))[0], int(mfl.group(2))
397-
files = sorted({x['file'] for x in self.g.q("SELECT DISTINCT file FROM symbols WHERE file = ? OR file LIKE ?", f, f"%/{f}")
398-
if x['file'] == f or (x['file'] or '').endswith('/' + f)})
399-
if f in files: files = [f]
397+
files, scoped = self.g.line_files(f) # --in chooses among a partial path's files (#1666)
398+
if scoped and len(files) == 1: s = f"{files[0]}:{ln}"
400399
d = self.g.decl_at_line(files[0], ln) if len(files) == 1 else None
401400
if d and d[0] == 'field' and kind in (None, 'field'):
402401
row = self.fields.get(d[1]['rowid']) or d[1]
@@ -568,8 +567,10 @@ class Impact:
568567
# the one declaration a file:line could not target, and its bare name answered for every const so named
569568
fl = re.fullmatch(r'(.+\.\w+):(\d+)', s)
570569
if fl:
571-
p, n = fl.group(1), int(fl.group(2))
572-
return [f for f in self.fields.values() if f['line'] == n and (f['file'] == p or (f['file'] or '').endswith('/' + p))]
570+
# a partial path names ONE file, as for a callable (#1599, #1666): matched against every file it ends, it took
571+
# the field at that line in whichever of them had one, so `test.js:6` in 17 test.js answered for one of them
572+
files, _ = self.g.line_files(self.g.repo_file(fl.group(1))[0]); n = int(fl.group(2))
573+
return [f for f in self.fields.values() if f['line'] == n and f['file'] == files[0]] if len(files) == 1 else []
573574
parts = s.split('.'); name = parts[-1]; owner = '.'.join(parts[:-1])
574575
rows = [f for f in self.fields.values() if f['name'] == name and (not owner or (f['owner'] or '') == owner or (f['owner'] or '').endswith('.' + owner) or (f['qualified_name'] or '').endswith('.' + s))]
575576
if not rows and '.' in s: # the dotted spelling, only after the written one

‎plugins/axiomcode/skills/axiomcode/scripts/axiomcode-path‎

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -324,6 +324,18 @@ class G:
324324
try:
325325
with open(os.path.join(self.repo, f), 'rb') as h: return len(h.read().splitlines())
326326
except OSError: return None
327+
328+
def line_files(self, f):
329+
"""-> (the indexed files a file:line's path `f` names, the --in note when the scope chose among them). The file
330+
written in full, else every file `f` is a suffix of. --IN CHOOSES AMONG THEM (#1666): it was applied only after
331+
the file was settled, so `index.js:2 --in packages/a` was refused with every index.js listed. Under the scope
332+
one file is the answer; two or more are the refusal's list; none leaves the list whole, and says so."""
333+
files = sorted({x['file'] for x in self.q("SELECT DISTINCT file FROM symbols WHERE file = ? OR file LIKE ?", f, f"%/{f}")
334+
if x['file'] == f or (x['file'] or '').endswith('/' + f)})
335+
if f in files: return [f], ''
336+
if len(files) < 2 or not self.IN: return files, ''
337+
kept = [x for x in files if self.under_in(x)]
338+
return (kept, f" under *{self.IN}*") if kept else (files, f", none of them under *{self.IN}*")
327339
def _resolve(self, sel, fragment=False):
328340
s = sel.strip().strip('`"\'')
329341
# file:line, or file:line:col as a stack trace prints it — the column never changes which callable spans the line
@@ -334,17 +346,16 @@ class G:
334346
# lib/ took the smallest span across BOTH files, so the answer was for whichever function at that line was
335347
# shorter, labelled with what was typed. The path is settled first: the file written in full, else the one
336348
# file it is a suffix of; two or more are listed, each with what spans the line there.
337-
files = sorted({x['file'] for x in self.q("SELECT DISTINCT file FROM symbols WHERE file = ? OR file LIKE ?", f, f"%/{f}")
338-
if x['file'] == f or (x['file'] or '').endswith('/' + f)})
339-
if f in files: files = [f]
349+
files, scoped = self.line_files(f)
340350
if len(files) > 1:
341351
rows = []
342352
for fx in files[:12]:
343353
at = self.q("SELECT id FROM symbols WHERE file = ? AND line <= ? AND end_line >= ? AND method_id IS NOT NULL AND kind <> 'module' ORDER BY end_line - line LIMIT 1", fx, ln, ln)
344354
rows.append(f" {fx}:{ln} " + (self.disp(at[0]['id']) if at else '(top-level code)'))
345355
if len(files) > 12: rows.append(f" … +{len(files) - 12} more")
346-
die(f"'{m.group(1)}' matches {len(files)} indexed files — give enough of the path to name one:\n" + '\n'.join(rows))
356+
die(f"'{m.group(1)}' matches {len(files)} indexed files{scoped} — give enough of the path to name one:\n" + '\n'.join(rows))
347357
if files: f = files[0]
358+
if scoped: s = f"{f}:{ln}" # the scope chose the file: the label names it
348359
# A LINE OUTSIDE THE FILE IS REFUSED (#1601). Line 0, or a line past the end — a number copied before the file
349360
# was edited, or from an old stack trace — spans no callable, so it fell through to the module node and was
350361
# answered as the file's top-level code, as if that line existed. The file on disk is what the reader sees.
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
{"lang": "javascript",
2+
"checks": [
3+
{"why": "a basename matching five indexed files, one of them under --in, answers for that one and names it (#1666)",
4+
"run": ["impact", "index.js:2", "--in", "packages/a"],
5+
"want": ["change: runA (at packages/a/src/index.js:2)", "startA"],
6+
"avoid": ["matches 5 indexed files", "startB", "runB"]},
7+
{"why": "the same through path, from everything into the one file under --in",
8+
"run": ["path", "*", "index.js:2", "--in", "packages/b"],
9+
"want": ["startB"],
10+
"avoid": ["matches 5 indexed files", "startA"]},
11+
{"why": "a dir/file suffix is narrowed the same way",
12+
"run": ["impact", "src/index.js:2", "--in", "packages/b"],
13+
"want": ["change: runB (at packages/b/src/index.js:2)", "startB"],
14+
"avoid": ["matches 2 indexed files", "startA"]},
15+
{"why": "control: a scope that still leaves two files is refused, listing only those two",
16+
"run": ["impact", "index.js:2", "--in", "packages/c"],
17+
"expect_error": true,
18+
"want": ["matches 2 indexed files under *packages/c*", "packages/c/index.js:2", "packages/c/lib/index.js:2"],
19+
"avoid": ["packages/a/src/index.js", "change: load"]},
20+
{"why": "control: the same through path",
21+
"run": ["path", "*", "index.js:2", "--in", "packages/c"],
22+
"expect_error": true,
23+
"want": ["matches 2 indexed files under *packages/c*"],
24+
"avoid": ["packages/d/index.js"]},
25+
{"why": "control: a scope holding none of the files is refused with every file listed",
26+
"run": ["impact", "index.js:2", "--in", "shared"],
27+
"expect_error": true,
28+
"want": ["matches 5 indexed files", "none of them under *shared*", "packages/a/src/index.js:2", "packages/d/index.js:2"]},
29+
{"why": "control: without --in the basename is refused as before (#1599)",
30+
"run": ["impact", "index.js:2"],
31+
"expect_error": true,
32+
"want": ["matches 5 indexed files", "packages/a/src/index.js:2", "packages/c/lib/index.js:2"]},
33+
{"why": "control: the full path with --in answers for it, labelled as typed",
34+
"run": ["impact", "packages/a/src/index.js:2", "--in", "packages/a"],
35+
"want": ["change: runA (at packages/a/src/index.js:2)", "startA"],
36+
"avoid": ["startB"]},
37+
{"why": "a const on the line of a partial path matching two files is refused, not answered for one of them",
38+
"run": ["impact", "config.js:1"],
39+
"expect_error": true,
40+
"want": ["matches 2 indexed files", "packages/a/src/config.js:1", "packages/b/src/config.js:1"],
41+
"avoid": ["change: variable tokenA", "change: const tokenA"]},
42+
{"why": "the same const line with --in answers for the one file under the scope",
43+
"run": ["impact", "config.js:1", "--in", "packages/b"],
44+
"want": ["tokenB"],
45+
"avoid": ["tokenA", "matches 2 indexed files"]},
46+
{"why": "control: a partial path matching one file is labelled as typed, with or without --in",
47+
"run": ["impact", "d/index.js:2", "--in", "packages"],
48+
"want": ["change: main (at d/index.js:2)"]}]}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
const { runA } = require('./index');
2+
3+
function startA() {
4+
return runA(1);
5+
}
6+
7+
module.exports = { startA };
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
const tokenA = 'a';
2+
module.exports = { tokenA };
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
function runA(x) {
2+
return String(x);
3+
}
4+
module.exports = { runA };
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
const { runB } = require('./index');
2+
3+
function startB() {
4+
return runB('1');
5+
}
6+
7+
module.exports = { startB };
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
const tokenB = 'b';
2+
module.exports = { tokenB };
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
function runB(x) {
2+
return Number(x);
3+
}
4+
module.exports = { runB };
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
function load() {
2+
return 1;
3+
}
4+
module.exports = { load };

0 commit comments

Comments
 (0)