diff options
| author | Agando <roduccomm@gmail.com> | 2026-11-09 15:36:23 +0100 |
|---|---|---|
| committer | Agando <roduccomm@gmail.com> | 2026-11-09 15:36:23 +0100 |
| commit | a065a4c30be748b0c8fc6dd0b5ee1dc4d3de4b1c (patch) | |
| tree | 8a84adabfccee9994937cd71888170ab205b9361 /scripts/lint-no-undef.py | |
| parent | a46ebee30cfbbac86abb45068411bc28e1c65503 (diff) | |
The daily collector and the feedback pipeline had left 57 modified files uncommitted (skills,
modules-index, CHANGELOG, feedback decisions, debug counters). The public mirror publishes git
HEAD, so without this the published repo stayed behind the tree - and it blocked the publisher
outright once uncommitted subtrees started failing loudly.
Diffstat (limited to 'scripts/lint-no-undef.py')
| -rw-r--r-- | scripts/lint-no-undef.py | 83 |
1 files changed, 83 insertions, 0 deletions
diff --git a/scripts/lint-no-undef.py b/scripts/lint-no-undef.py new file mode 100644 index 0000000..9f64931 --- /dev/null +++ b/scripts/lint-no-undef.py @@ -0,0 +1,83 @@ +#!/usr/bin/env python3 +"""Static no-undef gate for newOS4 browser scripts. + +The /agents/ leak fix and the `tc is not defined` crash are the same class of bug: +a refactor moves code and leaves a stale identifier behind. node --check can't see it +(it's a runtime ReferenceError). ESLint's no-undef can. + +Cross-file globals (classes/functions declared in *other* bundled files) are collected +from top-level declarations across all core+ext files and passed as --global, so only +genuinely-undefined identifiers are reported. + +Usage: python3 scripts/lint-no-undef.py [--changed module-aiassist.js ...] +Exit: 0 = clean, 1 = undefined identifiers found +""" +import os +import re +import subprocess +import sys + +ROOT = '/var/www/webx11.duckdns.org/spaces/newos4' +# eslint is not packaged with newOS4; reuse a local install or bootstrap one: +# mkdir -p /tmp/lint && cd /tmp/lint && npm i eslint@8 +def _eslint(): + import shutil + for cand in (os.environ.get('ESLINT_BIN'), '/tmp/lint/node_modules/.bin/eslint', shutil.which('eslint')): + if cand and os.path.exists(cand): + return cand + sys.exit('eslint not found — run: mkdir -p /tmp/lint && cd /tmp/lint && npm i eslint@8') + + +ESLINT = None # resolved in main() + +CORE = os.path.join(ROOT, 'modules/core') +EXT = os.path.join(ROOT, 'modules/ext') + +DECL = re.compile(r'^(?:class|function|async function|const|let|var)\s+([A-Za-z_$][\w$]*)', re.M) + + +def all_js(d): + return [os.path.join(d, f) for f in sorted(os.listdir(d)) if f.endswith('.js') and not f.endswith('.bak')] + + +def collect_globals(files): + names = set() + for p in files: + with open(p, encoding='utf-8', errors='replace') as fh: + src = fh.read() + names.update(DECL.findall(src)) + # window.Foo = ... / globalThis.Foo = ... + names.update(re.findall(r'(?:window|globalThis)\.([A-Za-z_$][\w$]*)\s*=', src)) + return {n for n in names if len(n) > 1} + + +def main(): + global ESLINT + ESLINT = _eslint() + files = all_js(CORE) + all_js(EXT) + args = sys.argv[1:] + if args: + want = set(args) + files = [f for f in files if os.path.basename(f) in want] + if not files: + print('no matching files'); return 1 + + globs = collect_globals(all_js(CORE) + all_js(EXT)) + cmd = [ESLINT, '--no-eslintrc', '--env', 'browser,es2022', + '--rule', '{"no-undef":"error"}', + '--global', ' '.join(sorted(globs))] + files + r = subprocess.run(cmd, capture_output=True, text=True) + out = (r.stdout + r.stderr).strip() + + findings = re.findall(r"^(.+?):(\d+):(\d+): error '(.+?)' is not defined", out, re.M) + if not findings: + print(f'✅ no-undef clean — {len(files)} file(s), {len(globs)} cross-file globals declared') + return 0 + print(f'❌ {len(findings)} undefined identifier(s):\n') + for path, line, col, name in findings: + print(f' {os.path.relpath(path, ROOT)}:{line}:{col} → {name} is not defined') + return 1 + + +if __name__ == '__main__': + sys.exit(main()) |