1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
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())
|