diff --git a/plugins/axiomcode/skills/axiomcode/scripts/ax_fresh.py b/plugins/axiomcode/skills/axiomcode/scripts/ax_fresh.py index 808b4a3cb..4993e7ccd 100644 --- a/plugins/axiomcode/skills/axiomcode/scripts/ax_fresh.py +++ b/plugins/axiomcode/skills/axiomcode/scripts/ax_fresh.py @@ -64,6 +64,17 @@ 'python': ('pyproject.toml', 'setup.cfg', 'setup.py'), 'csharp': ('global.json', 'Directory.Build.props'), } +# EXTENSION CASE (#1771). The JavaScript parser matches an extension whatever its case (jsExtensionOf lower-cases the +# name, so Main.JS and Up.VUE are read) and the C# parser reads *.CS (discoverCsFiles); the others match it exactly. +# Matched exactly here, such a file was parsed but left out of the table: an edit to it was invisible and `index` said +# "graph up to date". These are the extensions each parser folds; every other one stays case-sensitive, as its parser is. +FOLD = {'javascript': frozenset(EXT['javascript']), 'csharp': frozenset({'.cs'})} + +def has_ext(lang, name, exts): + """True when the parser of `lang` takes file `name` as ending in one of `exts`""" + if name.endswith(exts): return True + fold = FOLD.get(lang) + return bool(fold) and name.lower().endswith(tuple(e for e in exts if e in fold)) # TOOL OUTPUT NOBODY PARSES: what the non-source scan of `impact` (axiomcode-impact) leaves out. NOT what the file table # prunes: that is SKIP below, per language. PRUNE_ALL = {'.git', '.hg', '.svn', '.axiomcode', 'node_modules', 'bower_components', 'dist', 'build', 'out', @@ -194,7 +205,7 @@ def watched(root, lang): for f in files: for l, exts, names in spec: if l in off: continue - if f.endswith(exts) or f in names or (l == 'java' and os.path.basename(d) == 'services' and 'META-INF' in d) \ + if has_ext(l, f, exts) or f in names or (l == 'java' and os.path.basename(d) == 'services' and 'META-INF' in d) \ or (l == 'python' and python_script(os.path.join(d, f), f)): yield os.path.join(d, f); break @@ -1118,7 +1129,7 @@ def main(argv): n = {l: 0 for l in SOURCE} for l in SOURCE: for p in watched(repo, l): - if p.endswith(SOURCE[l]) or (l == 'python' and python_script(p)): n[l] += 1 + if has_ext(l, os.path.basename(p), SOURCE[l]) or (l == 'python' and python_script(p)): n[l] += 1 print(n['java'], n['typescript'], n['python'], n['javascript'], n['csharp']); return 0 if cmd == 'pyscripts': # how many extensionless python scripts are under repo: added to the build's .py count when it picks a language diff --git a/tests/freshness.py b/tests/freshness.py index 97ffa425f..47242267b 100644 --- a/tests/freshness.py +++ b/tests/freshness.py @@ -131,6 +131,27 @@ def prune_checks(): write(only, 'src/App.vue', '\n') n = {l: sum(1 for p in ax_fresh.watched(only, l) if p.endswith(ax_fresh.SOURCE[l])) for l in ('javascript', 'typescript')} check("prune: a repository of .vue files alone counts no JavaScript or TypeScript source", n == {'javascript': 0, 'typescript': 0}, n) + # EXTENSION CASE (#1771): the JavaScript parser reads Main.JS and Up.VUE and the C# parser reads Calc.CS, so an edit + # to one makes the graph stale and they count as source. The control: the TypeScript, Python and Java parsers match + # an extension exactly, so Main.TS, calc.PY and Calc.JAVA stay unwatched and uncounted + upper = os.path.join(work, 'upper') + for f in ('src/lib.js', 'src/Main.JS', 'src/views/Up.VUE', 'src/Old.Mjs', 'src/Main.TS', 'src/calc.PY', + 'src/Calc.JAVA', 'src/Calc.CS'): + write(upper, f, 'x\n') + want = {'javascript': {'src/lib.js', 'src/Main.JS', 'src/views/Up.VUE', 'src/Old.Mjs'}, 'typescript': {'src/lib.js'}, + 'python': set(), 'java': set(), 'csharp': {'src/Calc.CS'}} + for lang, files in want.items(): + got = {os.path.relpath(p, upper) for p in ax_fresh.watched(upper, lang)} + check(f"prune: {lang} watches an upper-case extension exactly when its parser reads one", got == files, sorted(got)) + table = dict(lang='javascript', lang_auto=True, src='', files=ax_fresh.snapshot(upper, 'javascript', upper)) + write(upper, 'src/Main.JS', 'third();\n'); write(upper, 'src/views/Up.VUE', 'third();\n'); write(upper, 'src/Main.TS', 'y\n') + c = ax_fresh.changes(upper, table) + check("prune: javascript: an edit to Main.JS or Up.VUE makes the graph stale; one to Main.TS does not", + c == (['src/Main.JS', 'src/views/Up.VUE'], [], []), c) + n = {l: sum(1 for p in ax_fresh.watched(upper, l) if ax_fresh.has_ext(l, os.path.basename(p), ax_fresh.SOURCE[l])) + for l in ax_fresh.SOURCE} + check("prune: Main.JS and Calc.CS count as source; Main.TS, calc.PY, Calc.JAVA and Up.VUE do not", + n == {'java': 0, 'typescript': 0, 'python': 0, 'javascript': 3, 'csharp': 1}, n) finally: shutil.rmtree(work, ignore_errors=True)