# 判据规范(写判据前先读这份) 这份文件记的是**判据本身的写法**:什么样的判据能红、能红在对的地方、以及不会在"代码完全正确"时乱红。 不是"怎么用 node:test",而是这个仓库里踩出来的形状。 一句话版本:**判据要钉结构与行为,不要钉字面与邻接。** --- ## 1. 判结构必须配对/解析;看邻接或固定宽度都会被合法写法绕过 统一形式(这是同一个坑在本仓露头的**第三次**,所以升格成规则): > **判结构要配对/解析(括号、块、AST);看"前一个字符"或"固定宽度窗口"都会被合法写法绕过。** 三次露头: | # | 当时的写法 | 被什么合法写法绕过 | |---|---|---| | ① | 固定宽度窗口正则 | 往规则里加一行注释就把 `backdrop-filter` 挤出窗口 —— 断言变红而 CSS 完全正确(`background.test.mjs` 的注释里记着这件事) | | ② | 括号配对取代窗口 | 修掉了 ①,方向对 | | ③ | "调用点往前看一个字符是不是 `}`" | ArkUI 的**链式修饰符**:`Row(){…}.backgroundColor(x).backgroundBlurStyle(A)` 里调用点前面是 `)` 不是 `}` → 判据**静默失效**(拿到 null 就放过去了) | ③ 的修法:往回扫时**跳过成对的括号组**再找 `}`。它同时暴露了"叠"有两种形态这件事 (同一组件叠两次 / 套在另一层玻璃的子树里),所以 §2 的形状在这些规则里反复出现。 **推论**:同一条规则里出现 `[\s\S]{0,N}` 时先问一句"N 是怎么来的"。答不出数字来源的, 就是窗口式判据,换成"取出这条规则的 `{...}` 体再断言"。 **已知存量**(pi 2026-09-14 点名,**WebUI 侧归他**,别人不要顺手改): `client/electron/test/background.test.mjs` 里还有一批 `\{[\s\S]{0,80}…`、`{0,160}` 窗口。 下次碰那个文件时按本节形状改成"取规则体",不要在那里再加一条注释算了。 ## 2. 合成的清单 + 空 allow-list:条目要结构化 "清单从 SDK/参考实现生成 + allow-list 目前为空"是个好形状,但它**迟早被侵蚀**: 上游升版会往清单里加新条目 → 某天早上套件突然红,且红在与本次改动无关的代码上。 这时人的第一反应是把名字塞进 allow-list —— 而 allow-list 一旦这么用, 就不再是"研究过的例外",只是"红的止痛药"。 所以条目写成三元组,并断言后两项非空: ```js const ALLOW = [ /* { name, replacement, why } */ ]; ``` - `replacement` 必须非空:**"暂时不想改"不是放行理由**,"替代品要求的 API level 高于本机基线"才是; - **不要断言"名单必须为空"**(那会挡住合理放行);要断言的是"有名字、没替代品 → 红"。 这样侵蚀发生时红的是**放行这件事本身**,而不是某天的新 SDK。 同一个形状的实例:`cross-client-theme.test.mjs` 的 `SELF_OWNED_COLORS`(手写色登记表, 每条带理由)、`GLASS_REGISTRY`(玻璃面登记,每条带"为什么这里要玻璃")。 ## 3. 判据必须能红,而且红的地方要对 每条新判据配一次**变异验证**:把源码改成"错的样子",确认它红,并且红在那条上。 变异没红有两种可能,都要查清:一是判据没覆盖,二是**变异没真的生效** (本仓真发生过:变异脚本的锚点不匹配、缩进不对,于是"变异后依然全绿"被当成判据有效)。 顺带:变异后**不要用 `git checkout` 还原**(会连同未提交的改动一起抹掉)。 先 `cp` 到备份,再从事先的备份还原。 ## 4. 读源码断言的两种模式,别混用 - 断**代码行为**:读**剥掉注释**的源码(注释里出现的调用不是调用); - 断**"理由写清了没"**:读**原文**(理由就在注释里)。 本仓真踩过:用剥离注释的读取去断"注释里写了为什么用 Canvas",永远红。 ## 5. 取片段按行/按块,别用偏移算术两头夹 `src.indexOf('build() {')` 会撞上文件里更早的同名成员;偏移差一个字符会把最后一行拦腰截断 (现象是"这行只剩 57 个字符"这种看着像文案、其实像切片的怪事)。 取"某个成员的正文"要按行扫到下一个同级成员,并加一条自检断言切片没跨到别的成员上。 ## 6. 判据要接线,扫描范围要有下限 - 新增判据文件必须进 `test/run-all.mjs` 的 `SUITE`(run-all 的**自检 2** 会直接红); - 扫目录的判据要断言"**至少扫到 N 个文件**",并列出必须包含的子目录 (`pages/` `common/` `model/` `api/` …)—— 目录改名/只扫一个子目录会让判据变成 **空判据而依然全绿**。 - **验证要按真实入口跑**:`npm test` 走的是 `node test/run-all.mjs && vitest run`。 用 `node --test test/run-all.mjs` 去验时,runner 只报"0 个测试"、**把 runner 内部的 `process.exit(1)` 吞掉**,于是"变异后依然 exit 0"看起来像判据失效(本仓刚踩过这一次: 自检 3 其实是好的,是我用错了入口去验它)。**验判据要模拟用户/CI 真正跑的那一行。** ## 7. 判据要钉用户真正会点的那一层 (移交信里交代的头号纪律)判据通过了但用户点不到,等于没做。 所以断言尽量落在"用户会触发的那个入口/那条路径"上: 例如"点同意/拒绝后列表要变"要钉到那条链路上,而不是钉"某函数存在"。