102 lines
4.3 KiB
Python
102 lines
4.3 KiB
Python
#!/usr/bin/env python
|
||||
|
|
# -*- coding: utf-8 -*-
|
|||
|
|
"""原型 JS 与模板字面量的**语法门禁**(③段 3b/3d 用,只读原型,⛔ 不写盘)。
|
|||
|
|
|
|||
|
|
为什么需要它(不是"为了严谨",是同一个坑踩了 **4 次**的真实事故):
|
|||
|
|
原型是单文件自包含,页面结构写在一堆 JS **模板字面量**里(反引号包起来的字符串)。
|
|||
|
|
只要在模板里的 **HTML 注释**(`<!-- ... -->`)中写出一个反引号,模板字面量就会被提前闭合,
|
|||
|
|
而后面那段被当成 JS 代码解析 —— 症状是**页面静默坏掉**(白屏 / `xxx is not defined` /
|
|||
|
|
`Unexpected identifier`),而 `node --check 整份 html` 是**报不出来**的(html 不是 js),
|
|||
|
|
于是它一路混到浏览器里才炸。
|
|||
|
|
|
|||
|
|
已经踩到的 4 次(每次都是「HTML 注释里带反引号」):
|
|||
|
|
1. P1 的 `.grid.cols2` 注释 → 页面白屏
|
|||
|
|
2. P2 的 `.home-stats` / `.stat-item` 注释 → `stats is not defined`,自检没跑完
|
|||
|
|
3. P2 的 `.grid-cards` / `font-size` 注释 → `Unexpected identifier 'style'`
|
|||
|
|
4. P1 的 `.qa-block` / `.func-card .qa{...}` 注释 → `Unexpected token '{'`
|
|||
|
|
|
|||
|
|
本脚本查两件事:
|
|||
|
|
① **语法**:把 `<script>` 段抠出来交给 `node --check`(⛔ 不是查整份 html)。
|
|||
|
|
② **模板里的反引号**:多行扫描 script 区内的每个 `<!-- ... -->` 块,块内出现反引号即报红。
|
|||
|
|
⚠️ 必须**多行**扫:反引号常在注释的**续行**上(单行 grep 抓不到)。
|
|||
|
|
|
|||
|
|
用法:
|
|||
|
|
python _tools/check_js.py # 查同目录的 mcn-workbench.html
|
|||
|
|
python _tools/check_js.py --proto X.html
|
|||
|
|
退出码:0 = 全过;1 = 有 FAIL。
|
|||
|
|
"""
|
|||
|
|
|
|||
|
|
import argparse
|
|||
|
|
import io
|
|||
|
|
import os
|
|||
|
|
import re
|
|||
|
|
import subprocess
|
|||
|
|
import sys
|
|||
|
|
import tempfile
|
|||
|
|
|
|||
|
|
HERE = os.path.dirname(os.path.abspath(__file__))
|
|||
|
|
DEFAULT_PROTO = os.path.abspath(os.path.join(HERE, '..', 'mcn-workbench.html'))
|
|||
|
|
NODE = r"E:\ProgramData\.workbuddy\binaries\node\versions\22.22.2-6\node.exe"
|
|||
|
|
|
|||
|
|
|
|||
|
|
def main():
|
|||
|
|
ap = argparse.ArgumentParser()
|
|||
|
|
ap.add_argument('--proto', default=DEFAULT_PROTO)
|
|||
|
|
a = ap.parse_args()
|
|||
|
|
|
|||
|
|
src = io.open(a.proto, encoding='utf-8').read()
|
|||
|
|
fails = []
|
|||
|
|
|
|||
|
|
i = src.index('<script>') + len('<script>')
|
|||
|
|
j = src.rindex('</script>')
|
|||
|
|
js = src[i:j]
|
|||
|
|
|
|||
|
|
# ① 语法:把 script 抠出来给 node --check
|
|||
|
|
node = NODE if os.path.exists(NODE) else 'node'
|
|||
|
|
with tempfile.NamedTemporaryFile('w', suffix='.js', delete=False, encoding='utf-8', newline='') as f:
|
|||
|
|
f.write(js)
|
|||
|
|
tmp = f.name
|
|||
|
|
try:
|
|||
|
|
r = subprocess.run([node, '--check', tmp], capture_output=True)
|
|||
|
|
if r.returncode == 0:
|
|||
|
|
print('PASS 语法:<script> 段 ' + str(js.count(chr(10))) + ' 行,node --check 通过')
|
|||
|
|
else:
|
|||
|
|
err = r.stderr.decode('utf-8', 'replace').strip().split('\n')
|
|||
|
|
# 把临时文件里的行号换算回原文件行号
|
|||
|
|
base = src[:i].count('\n') + 1
|
|||
|
|
head = [l for l in err if l.strip()][:4]
|
|||
|
|
fails.append('语法不过:' + ' / '.join(head))
|
|||
|
|
print('FAIL 语法:node --check 报错(下面的行号是 script 段内的,原文件约 +%d 行)' % base)
|
|||
|
|
for l in head:
|
|||
|
|
print(' ' + l)
|
|||
|
|
finally:
|
|||
|
|
os.unlink(tmp)
|
|||
|
|
|
|||
|
|
# ② 模板里的反引号(多行扫描每个 HTML 注释块)
|
|||
|
|
bad = []
|
|||
|
|
for m in re.finditer(r'<!--[\s\S]*?-->', js):
|
|||
|
|
blk = m.group(0)
|
|||
|
|
if '`' in blk:
|
|||
|
|
line = js[:m.start()].count('\n') + src[:i].count('\n') + 2
|
|||
|
|
first = blk.split('\n')[0][:70]
|
|||
|
|
n_tick = blk.count('`')
|
|||
|
|
bad.append('原文件第 %d 行附近:模板里的 HTML 注释含 %d 个反引号 —— 「%s」' % (line, n_tick, first))
|
|||
|
|
if bad:
|
|||
|
|
fails.extend(bad)
|
|||
|
|
print('FAIL 模板里的反引号:')
|
|||
|
|
for b in bad:
|
|||
|
|
print(' ' + b)
|
|||
|
|
else:
|
|||
|
|
print('PASS 模板里的反引号:script 区内所有 <!-- --> 块(多行扫描)都不含反引号')
|
|||
|
|
|
|||
|
|
print()
|
|||
|
|
if fails:
|
|||
|
|
print('结论:FAIL %d 项 —— 上面每一条都会让**页面在浏览器里才炸**(node --check 整份 html 看不到)。' % len(fails))
|
|||
|
|
print('修法:注释里的反引号一律去掉(用 .cls 或 「cls」代替 `.cls`)。')
|
|||
|
|
return 1
|
|||
|
|
print('结论:全过。')
|
|||
|
|
return 0
|
|||
|
|
|
|||
|
|
|
|||
|
|
if __name__ == '__main__':
|
|||
|
|
sys.exit(main())
|