aboutsummaryrefslogtreecommitdiff
path: root/scripts/lint-no-undef.py
diff options
context:
space:
mode:
authorAgando <roduccomm@gmail.com>2026-11-09 15:36:23 +0100
committerAgando <roduccomm@gmail.com>2026-11-09 15:36:23 +0100
commita065a4c30be748b0c8fc6dd0b5ee1dc4d3de4b1c (patch)
tree8a84adabfccee9994937cd71888170ab205b9361 /scripts/lint-no-undef.py
parenta46ebee30cfbbac86abb45068411bc28e1c65503 (diff)
chore(newos4): commit accumulated maintenance outputHEADv4.6.156main
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.py83
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())