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) {