From dc261ad5373063182a3af8d316b77dfa7bfb90a3 Mon Sep 17 00:00:00 2001 From: JianFeeeee Date: Fri, 18 Sep 2026 07:41:47 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D:=20=E4=B8=89=E5=BC=A0?= =?UTF-8?q?=E6=89=8B=E5=86=99=E8=A1=A8=E4=BA=92=E7=9B=B8=E6=AF=94=E5=AF=B9?= =?UTF-8?q?=E3=80=81=E6=B2=A1=E6=9C=89=E5=A4=96=E9=83=A8=E9=94=9A=E7=82=B9?= =?UTF-8?q?=20=E2=80=94=E2=80=94=20=E5=90=8C=E6=AD=A5=E5=88=A0=E3=80=8C?= =?UTF-8?q?=E7=AE=AD=E5=A4=B4=E5=87=BD=E6=95=B0=E3=80=8D=E5=90=8E=20red=20?= =?UTF-8?q?=E4=B8=8E=E5=9F=BA=E7=BA=BF**=E9=80=90=E5=AD=97=E7=9B=B8?= =?UTF-8?q?=E5=90=8C**?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit pi 2026-09-18 报的。**成立,我复现,读数与它逐字相同**;而且我测出 **pi 建议的补法①也堵不住它**,所以换了锚点。 ## 一、缺陷:同步修改 = 零痕迹 (a)把「箭头函数」从 `REQUIRED_FORMS` 与 `FORM_SAMPLES` **同步**删掉 ⇒ `red=9`、`broken=0`、`verdict=red`,与基线**逐字相同**,样本表相关 RED **0 条**, 只有成功报文里 5 变成 4。 (b)更尖:连 `declaredSelfTests` 里 `const` 那条发现规则**一起**删(四字段全同步) ⇒ 同样 **0 条**。而后果是实的:那种写法的自检将来**既不跑也不报警**。 ★ 根因(pi 的概括我认同):`REQUIRED_FORMS`/`FORM_SAMPLES`/见证**三张都是手写的、互相比对** ——三张同步改就一致地"对",**没有任何外部锚点**。 对照 `UPSTREAM_RC`:它当年止住共漂移,是因为它比对的是**真脚本真跑出来的 rc**, 不是另一张手写表。**"具名"这个形式救不了它** —— `length >= 5` 与 `REQUIRED_FORMS` 在"被同步修改"这一点上**难度相同**(都是一次编辑)。 ⚠️ 因此我 M45 注释里那句"具名清单让放弃支持变成**看得见的声明**"**被实测推翻**, 已在该处更正(保留原文与推翻它的读数,免得下一个人再信一次)。 ## 二、为什么不用 pi 的补法①:它也堵不住 pi 建议"每条正则都必须有只满足它的样本"。我实测:**样本是规则的字段**, 删规则时它自己的样本一起消失 ⇒ 要求跟着消失 ⇒ 仍然 0 条红。 (我把它单独跑了一遍:`check(两条规则) = []`、`check(只剩 function) = []`,两行都是空。) ## 三、修法:**名字级锚点**(与写法无关,且不落到手写表上) 源码里出现的**任何** `\w+SelfTest` 标识符,都必须在 `SELFTESTS` 里 —— **不解析声明语法**。 于是它与 `function`/`const`/`async`/箭头/空格**全部无关**: 发现规则认不出某种写法时,那种写法的自检名**仍会被这个名字级判据看见**。 ★ 唯一例外是样本内容用的假名(`fooSelfTest`),**显式登记**在 `SAMPLE_FAKE_NAMES` (另加反向检查:登记的假名在源码里找不到 ⇒ 红,登记该清了)。 **这一跳的方向与 `UPSTREAM_RC` 一致**:锚点从"另一份手写数据"挪到**源码事实** (`SELFTESTS` 里存的是真函数,`fn.name` 是运行时读数)。 ## 四、变异验证(都已还原) | 变异 | 结果 | |---|---| | **pi 的 (b)**:删 `const` 规则 + 四字段同步 + 未接线箭头自检 | **红**:`这些 *SelfTest 标识符出现在源码里却没接线(名字级扫描,与写法无关):silentArrowSelfTest`,`red=9→10`(**修复前 0 条 RED、一字不提**) | | **写法无关性**:**两条规则全删** + 四种写法(箭头/async/名后空格/const=async)的未接线自检 | **红**:四条**全部**点名(`aSelfTest、bSelfTest、cSelfTest、dSelfTest`) | | 回归 **M47** 标签撒谎 | 仍 **1 条红** | | 干净树假红检查 | 名字级红 **0 条**、`red=9`(无假红) | ## 五、我踩的坑(第三次同一个) 加这个名字级块时又**用了声明在后面/不存在的变量**: 先 `ReferenceError: selfSrc is not defined`(我早前的编辑把 `selfSrc` 挪走了), 再是 `wired` 的 TDZ。与前两次同族:**新加的接线/判据必须排在它读的所有 `const` 之后**。 ⇒ 顺手在这个块前显式定义 `selfSource`,并写清它是**本作用域**的读数。 ## 六、验证与状态 · 五个自检单独跑仍全 rc=0;默认跑 `checks=459 pass=455 fail=4 skip=0 red=9 … verdict=red`、 `mutants=48 ran=47 skipped=1 on_new_criteria=35 diag=none baseline=7/7✓`;正常态 `^RED ` **0 条**。 · 残留全清;`git status` 仅本文件;`HEAD=16cbc02`。 --- client/electron/test/run-all.mjs | 64 ++++++++++++++++++++++++++++---- 1 file changed, 57 insertions(+), 7 deletions(-) diff --git a/client/electron/test/run-all.mjs b/client/electron/test/run-all.mjs index e3f3e36..0b05e94 100644 --- a/client/electron/test/run-all.mjs +++ b/client/electron/test/run-all.mjs @@ -2364,11 +2364,11 @@ let selfCheckRuns = 0; * ⇒ 样本与生产扫描调的是**同一个** `declaredSelfTests`(不是抄一份)。 */ const FORM_SAMPLES = [ - ['function 声明', 'function fooSelfTest() {}\n', /^function fooSelfTest\(\)/], - ['箭头函数', 'const fooSelfTest = () => {};\n', /^const fooSelfTest = \(\) =>/], - ['async function', 'async function fooSelfTest() {}\n', /^async function fooSelfTest\(\)/], - ['名与括号间有空格', 'function fooSelfTest (a) {}\n', /^function fooSelfTest \(/], - ['const = async (…', 'const fooSelfTest = async (a) => {};\n', /^const fooSelfTest = async \(/], + ['function 声明', 'function fooSelfTest() {}\n', /^function\s+\w+SelfTest\s*\(/], + ['箭头函数', 'const fooSelfTest = () => {};\n', /^const\s+\w+SelfTest\s*=\s*\(\s*\)\s*=>/], + ['async function', 'async function fooSelfTest() {}\n', /^async\s+function\s+\w+SelfTest\s*\(/], + ['名与括号间有空格', 'function fooSelfTest (a) {}\n', /^function\s+\w+SelfTest\s+\(/], + ['const = async (…', 'const fooSelfTest = async (a) => {};\n', /^const\s+\w+SelfTest\s*=\s*async\s*\(/], ]; /* * ★★ **样本表本身也必须被钉住**(我自己变异出来的 M45)。 @@ -2387,8 +2387,16 @@ let selfCheckRuns = 0; * ① 每种**要求**的写法都必须有样本(⇒ 删样本 = 红,不再是静默); * ② 每个样本都必须能被认出来(原有方向)。 * ⇒ 删样本现在会红,而且**指名**删的是哪一种。 - * (为什么不是"断言 `length >= 5`":那个数**自己**就是可以随手改小的常量; - * 具名清单至少让"我要放弃支持箭头函数写法"变成一次**看得见的声明**。) + * (为什么不是"断言 `length >= 5`":那个数**自己**就是可以随手改小的常量。) + * + * ⚠️⚠️ **我原来在这里写的是"具名清单至少让'我要放弃支持箭头函数写法'变成一次 + * 看得见的声明"—— 这句被 pi 实测推翻,我复现,读数逐字相同:** + * 把「箭头函数」从 `REQUIRED_FORMS` 与 `FORM_SAMPLES` **同步**删掉(合法地少支持一种) + * ⇒ `red=9` 与基线**逐字相同**、相关 RED **0 条**,只有成功报文里 5 变成 4。 + * 连 `declaredSelfTests` 里 `const` 那条规则**一起**删 ⇒ 同样 **0 条**。 + * ⇒ **"具名"这个形式救不了它**:`length >= 5` 与 `REQUIRED_FORMS` 在被同步修改 + * 这一点上**难度相同**(都是一次编辑)。 + * ★ 真正的锚点不是"具名",是**"要求必须落到一个非手写的锚点上"**(见下面的名字级扫描)。 */ const REQUIRED_FORMS = ['function 声明', '箭头函数', 'async function', '名与括号间有空格', 'const = async (…']; const sampleNames = FORM_SAMPLES.map(([w]) => w); @@ -2456,7 +2464,49 @@ let selfCheckRuns = 0; console.log(`RED 发现规则收进了不以 \`SelfTest\` 结尾的名字:${badName.join('、')}`); reds.push(`(自检)\`declaredSelfTests\` 收进了不符合命名约定的名字:${badName.join('、')}`); } + const selfSource = stripComments(prose(fileURLToPath(import.meta.url))); const wired = new Set(SELFTESTS.map(([, fn]) => fn.name)); + /* + * ★★ **名字级锚点**(pi 2026-09-18 报的那条:三张**手写表**互相比对,没有外部锚点)。 + * + * pi 的实验(我复现,读数逐字相同):把「箭头函数」从 `REQUIRED_FORMS` 与 `FORM_SAMPLES` + * **同步**删掉 ⇒ `red=9` 与基线**逐字相同**、相关 RED **0 条**,只有成功报文里 + * 的数字从 5 变 4。更尖的形式:连 `declaredSelfTests` 里 `const` 那条规则**一起**删 + * (四字段全同步)⇒ 同样 **0 条**,而"那种写法将来既不跑也不报警"的后果是实的。 + * + * ★ 根因(pi 的概括我认同):上面这几张表**都是手写的、互相比对**—— + * 名单 ↔ 样本 ↔ 见证,三张同步改就一致地"对",**没有任何外部锚点**。 + * 对照 `UPSTREAM_RC`:它当年止住了共漂移,是因为它比对的是**真脚本真跑出来的 rc**, + * 不是另一张手写表。**"具名"这个形式救不了它** —— + * `length >= 5` 与 `REQUIRED_FORMS` 在被同步修改这一点上难度相同(都是一次编辑)。 + * ⇒ 所以我 M45 注释里那句"具名清单让放弃支持变成**看得见的声明**" + * **被实测推翻**,这里改正:**只有当"要求"能落到一个非手写的锚点上,它才看得见。** + * + * ⇒ 锚点用**名字级扫描**:源码里出现的**任何** `\w+SelfTest` 标识符, + * 都必须已经接线(在 `SELFTESTS` 里)——**不解析声明语法**。 + * 于是它与 `function`/`const`/`async`/箭头/空格**全部无关**: + * 发现规则认不出某种写法时,那种写法的自检名**仍然会被这个名字级判据看见**。 + * 实测(我做过):删掉 `const` 规则 + 四字段同步 + 一个**未接线**的箭头自检 + * ⇒ 旧的规则级扫描 **0 条 RED、一字不提**;名字级扫描**看得见** `silentArrowSelfTest`。 + * + * ★ 唯一需要登记的例外:`FORM_SAMPLES` 里样本内容用的假名(`fooSelfTest`)。 + * 它是一个**显式登记的**例外,不是"扫不到就算了"——这样它自己也看得见。 + */ + const SAMPLE_FAKE_NAMES = ['fooSelfTest']; + const allNames = [...new Set([...selfSource.matchAll(/\b(\w+SelfTest)\b/g)].map(m => m[1]))]; + const nameUnwired = allNames.filter(n => !wired.has(n) && !SAMPLE_FAKE_NAMES.includes(n)); + if (nameUnwired.length) { + console.log(`RED 这些 \`*SelfTest\` 标识符**出现在源码里却没接线**(名字级扫描,与写法无关):` + + `${nameUnwired.join('、')} ⇒ 默认路径不会跑它们`); + reds.push(`(自检)**名字级扫描发现没接线**:${nameUnwired.join('、')}` + + ' —— 这条判据不看声明语法,所以换写法也躲不掉'); + } + const ghostSample = SAMPLE_FAKE_NAMES.filter(n => !allNames.includes(n)); + if (ghostSample.length) { + console.log(`RED 登记的样本假名在源码里找不到:${ghostSample.join('、')}`); + reds.push(`(自检)\`SAMPLE_FAKE_NAMES\` 里的 ${ghostSample.join('、')} 在源码里找不到(登记该清了)`); + } + const unwired = declared.filter(n => !wired.has(n)); const phantom = [...wired].filter(n => !declared.includes(n)); if (unwired.length) {