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
11 changes: 6 additions & 5 deletions plugins/axiomcode/skills/axiomcode/scripts/axiomcode-impact
Original file line number Diff line number Diff line change
Expand Up @@ -394,9 +394,8 @@ class Impact:
mfl = re.match(r'^(.+?):(\d+)(?::\d+)?$', s)
if mfl and ('/' in mfl.group(1) or '.' in mfl.group(1)) and kind in (None, 'field', 'type'):
f, ln = self.g.repo_file(mfl.group(1))[0], int(mfl.group(2))
files = sorted({x['file'] for x in self.g.q("SELECT DISTINCT file FROM symbols WHERE file = ? OR file LIKE ?", f, f"%/{f}")
if x['file'] == f or (x['file'] or '').endswith('/' + f)})
if f in files: files = [f]
files, scoped = self.g.line_files(f) # --in chooses among a partial path's files (#1666)
if scoped and len(files) == 1: s = f"{files[0]}:{ln}"
d = self.g.decl_at_line(files[0], ln) if len(files) == 1 else None
if d and d[0] == 'field' and kind in (None, 'field'):
row = self.fields.get(d[1]['rowid']) or d[1]
Expand Down Expand Up @@ -568,8 +567,10 @@ class Impact:
# the one declaration a file:line could not target, and its bare name answered for every const so named
fl = re.fullmatch(r'(.+\.\w+):(\d+)', s)
if fl:
p, n = fl.group(1), int(fl.group(2))
return [f for f in self.fields.values() if f['line'] == n and (f['file'] == p or (f['file'] or '').endswith('/' + p))]
# a partial path names ONE file, as for a callable (#1599, #1666): matched against every file it ends, it took
# the field at that line in whichever of them had one, so `test.js:6` in 17 test.js answered for one of them
files, _ = self.g.line_files(self.g.repo_file(fl.group(1))[0]); n = int(fl.group(2))
return [f for f in self.fields.values() if f['line'] == n and f['file'] == files[0]] if len(files) == 1 else []
parts = s.split('.'); name = parts[-1]; owner = '.'.join(parts[:-1])
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))]
if not rows and '.' in s: # the dotted spelling, only after the written one
Expand Down
19 changes: 15 additions & 4 deletions plugins/axiomcode/skills/axiomcode/scripts/axiomcode-path
Original file line number Diff line number Diff line change
Expand Up @@ -324,6 +324,18 @@ class G:
try:
with open(os.path.join(self.repo, f), 'rb') as h: return len(h.read().splitlines())
except OSError: return None

def line_files(self, f):
"""-> (the indexed files a file:line's path `f` names, the --in note when the scope chose among them). The file
written in full, else every file `f` is a suffix of. --IN CHOOSES AMONG THEM (#1666): it was applied only after
the file was settled, so `index.js:2 --in packages/a` was refused with every index.js listed. Under the scope
one file is the answer; two or more are the refusal's list; none leaves the list whole, and says so."""
files = sorted({x['file'] for x in self.q("SELECT DISTINCT file FROM symbols WHERE file = ? OR file LIKE ?", f, f"%/{f}")
if x['file'] == f or (x['file'] or '').endswith('/' + f)})
if f in files: return [f], ''
if len(files) < 2 or not self.IN: return files, ''
kept = [x for x in files if self.under_in(x)]
return (kept, f" under *{self.IN}*") if kept else (files, f", none of them under *{self.IN}*")
def _resolve(self, sel, fragment=False):
s = sel.strip().strip('`"\'')
# file:line, or file:line:col as a stack trace prints it — the column never changes which callable spans the line
Expand All @@ -334,17 +346,16 @@ class G:
# lib/ took the smallest span across BOTH files, so the answer was for whichever function at that line was
# shorter, labelled with what was typed. The path is settled first: the file written in full, else the one
# file it is a suffix of; two or more are listed, each with what spans the line there.
files = sorted({x['file'] for x in self.q("SELECT DISTINCT file FROM symbols WHERE file = ? OR file LIKE ?", f, f"%/{f}")
if x['file'] == f or (x['file'] or '').endswith('/' + f)})
if f in files: files = [f]
files, scoped = self.line_files(f)
if len(files) > 1:
rows = []
for fx in files[:12]:
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)
rows.append(f" {fx}:{ln} " + (self.disp(at[0]['id']) if at else '(top-level code)'))
if len(files) > 12: rows.append(f" … +{len(files) - 12} more")
die(f"'{m.group(1)}' matches {len(files)} indexed files — give enough of the path to name one:\n" + '\n'.join(rows))
die(f"'{m.group(1)}' matches {len(files)} indexed files{scoped} — give enough of the path to name one:\n" + '\n'.join(rows))
if files: f = files[0]
if scoped: s = f"{f}:{ln}" # the scope chose the file: the label names it
# A LINE OUTSIDE THE FILE IS REFUSED (#1601). Line 0, or a line past the end — a number copied before the file
# was edited, or from an old stack trace — spans no callable, so it fell through to the module node and was
# answered as the file's top-level code, as if that line existed. The file on disk is what the reader sees.
Expand Down
48 changes: 48 additions & 0 deletions tests/cases/javascript/fileline-partial-path-in-scope/case.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
{"lang": "javascript",
"checks": [
{"why": "a basename matching five indexed files, one of them under --in, answers for that one and names it (#1666)",
"run": ["impact", "index.js:2", "--in", "packages/a"],
"want": ["change: runA (at packages/a/src/index.js:2)", "startA"],
"avoid": ["matches 5 indexed files", "startB", "runB"]},
{"why": "the same through path, from everything into the one file under --in",
"run": ["path", "*", "index.js:2", "--in", "packages/b"],
"want": ["startB"],
"avoid": ["matches 5 indexed files", "startA"]},
{"why": "a dir/file suffix is narrowed the same way",
"run": ["impact", "src/index.js:2", "--in", "packages/b"],
"want": ["change: runB (at packages/b/src/index.js:2)", "startB"],
"avoid": ["matches 2 indexed files", "startA"]},
{"why": "control: a scope that still leaves two files is refused, listing only those two",
"run": ["impact", "index.js:2", "--in", "packages/c"],
"expect_error": true,
"want": ["matches 2 indexed files under *packages/c*", "packages/c/index.js:2", "packages/c/lib/index.js:2"],
"avoid": ["packages/a/src/index.js", "change: load"]},
{"why": "control: the same through path",
"run": ["path", "*", "index.js:2", "--in", "packages/c"],
"expect_error": true,
"want": ["matches 2 indexed files under *packages/c*"],
"avoid": ["packages/d/index.js"]},
{"why": "control: a scope holding none of the files is refused with every file listed",
"run": ["impact", "index.js:2", "--in", "shared"],
"expect_error": true,
"want": ["matches 5 indexed files", "none of them under *shared*", "packages/a/src/index.js:2", "packages/d/index.js:2"]},
{"why": "control: without --in the basename is refused as before (#1599)",
"run": ["impact", "index.js:2"],
"expect_error": true,
"want": ["matches 5 indexed files", "packages/a/src/index.js:2", "packages/c/lib/index.js:2"]},
{"why": "control: the full path with --in answers for it, labelled as typed",
"run": ["impact", "packages/a/src/index.js:2", "--in", "packages/a"],
"want": ["change: runA (at packages/a/src/index.js:2)", "startA"],
"avoid": ["startB"]},
{"why": "a const on the line of a partial path matching two files is refused, not answered for one of them",
"run": ["impact", "config.js:1"],
"expect_error": true,
"want": ["matches 2 indexed files", "packages/a/src/config.js:1", "packages/b/src/config.js:1"],
"avoid": ["change: variable tokenA", "change: const tokenA"]},
{"why": "the same const line with --in answers for the one file under the scope",
"run": ["impact", "config.js:1", "--in", "packages/b"],
"want": ["tokenB"],
"avoid": ["tokenA", "matches 2 indexed files"]},
{"why": "control: a partial path matching one file is labelled as typed, with or without --in",
"run": ["impact", "d/index.js:2", "--in", "packages"],
"want": ["change: main (at d/index.js:2)"]}]}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
const { runA } = require('./index');

function startA() {
return runA(1);
}

module.exports = { startA };
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
const tokenA = 'a';
module.exports = { tokenA };
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
function runA(x) {
return String(x);
}
module.exports = { runA };
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
const { runB } = require('./index');

function startB() {
return runB('1');
}

module.exports = { startB };
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
const tokenB = 'b';
module.exports = { tokenB };
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
function runB(x) {
return Number(x);
}
module.exports = { runB };
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
function load() {
return 1;
}
module.exports = { load };
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
function loadLib() {
return 2;
}
module.exports = { loadLib };
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
function main() {
return 3;
}
module.exports = { main };
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
function helper() {
return 4;
}
module.exports = { helper };
Loading