1212 * 不该误伤 —— 「注释字面量骗过整文件扫描器」本批已 3 例)。
1313 * 副作用:只读磁盘(`app/src` 全树 + `ui/tokens.css`),不修改任何文件。
1414 * 边界:① 域含**测试文件**(T5 读数 54 处里 12 处在测试里)⇒ **本文件自身也必须在域内贡献 0 命中**
15- * (正则与消息里一律不写裸串,由 ① 的最后一条判据盯着);② 只判 `--ed-dur-*` / `--ed-ease` 两族
16- * (`--ed-type-*` / `--ed-space-*` 各有自己的兜底判据,见 `style-seams.test.ts`);③ 计数**不是**
17- * 冻结常量(T12/T13 会新增消费点)⇒ 判「齐备 + 同值」,**不判**「恰好 54」。
15+ * (正则与消息里一律不写裸串,由 ① 的最后一条判据盯着);② 只判 `--ed-dur-*` / `--ed-ease*` 两族
16+ * —— `--ed-ease` 家族今天有**三名**(`ease` / `ease-instrument` / `ease-paper`,真源
17+ * `EASING_TOKENS`),`--ed-type-*` / `--ed-space-*` 各有自己的兜底判据(见 `style-seams.test.ts`);
18+ * ③ 计数**不是**冻结常量(T12/T13 会新增消费点)⇒ 判「齐备 + 同值」,**不判**「恰好 54」;
19+ * ④ 🔴 **名字模式本身也是判据**(由 **①b** 盯住,T8 实测的教训):`\b` 收尾在本族**不成立** ——
20+ * `ease` 后接 `-` 处正是词边界(`e` 是词字符、`-` 不是)⇒ `var(--ed-ease-instrument` 会被**前缀吞成**
21+ * `--ed-ease`,`fallbackAfter` 随后读到 `-instrument` 就返回 `null` ⇒ ② 把一处**写了兜底**的消费
22+ * 报成「没有兜底」(**误红**)。今天该名 0 消费点所以没触发,波 B/C 一加消费点就炸 ⇒ 名字必须
23+ * **整段吃完**(`ease(?:-[a-z-]+)?`)并以终止符前瞻收尾(`var()` 里名字后只能是空白 / 逗号 / 右括号)。
1824 */
1925import { readdirSync , readFileSync , statSync } from "node:fs" ;
2026import { dirname , join } from "node:path" ;
@@ -28,10 +34,14 @@ const SRC = join(HERE, "..", "..");
2834/** 域内必须有、且必须**真在消费**的锚文件(动态域的锚:改名 / 搬走必须被看见) */
2935const ANCHORS = [ "ui/primitives/Toast.css" , "ui/primitives/Button.css" , "ui/primitives/Modal.css" ] as const ;
3036const SELF = "ui/primitives/motionTokens.consumption.test.ts" ;
31- /** 消费点:`var(--ed-dur-<name>` / `var(--ed-ease`(`\b` 收尾 ⇒ `--ed-ease-in` 这类新名不会误配) */
32- const CONSUMER = / v a r \( - - e d - (?: d u r - [ a - z - ] + | e a s e ) \b / g;
37+ /**
38+ * 消费点:`var(--ed-dur-<name>` / `var(--ed-ease[-<name>]`。
39+ * 🔴 **不许**改回 `\b` 收尾(T8 实测):`ease` 后接 `-` 处正是词边界 ⇒ `var(--ed-ease-instrument`
40+ * 被前缀吞成 `--ed-ease`、兜底解析成 `null`、② 误报「没有兜底」。名字必须整段吃完 + 终止符前瞻。
41+ */
42+ const CONSUMER = / v a r \( - - e d - (?: d u r - [ a - z - ] + | e a s e (?: - [ a - z - ] + ) ? ) (? = [ \s , ) ] ) / g;
3343/** 带逗号的消费(与 `CONSUMER` **独立**的形态计数:兜底换了写法也不会被解析器静默漏掉) */
34- const WITH_COMMA = / v a r \( - - e d - (?: d u r - [ a - z - ] + | e a s e ) (? = \s * , ) / g;
44+ const WITH_COMMA = / v a r \( - - e d - (?: d u r - [ a - z - ] + | e a s e (?: - [ a - z - ] + ) ? ) (? = \s * , ) / g;
3545/** 真源 = **生成器导出**(不是产物)⇒「真源改值而兜底不改」必红(R13.2 的第三条变异体) */
3646const TRUTH = new Map < string , string > ( [
3747 ...DURATION_TOKENS . map ( ( t ) => [ `--ed-dur-${ t . name } ` , `${ t . ms } ms` ] as const ) ,
@@ -113,6 +123,33 @@ describe("动效 token 消费点:兜底字面量必须等于生成器真源(
113123 expect ( hitsIn ( SELF ) , "本文件不得被自己的正则 / 消息里的字面量污染计数" ) . toEqual ( [ ] ) ;
114124 } ) ;
115125
126+ it ( "①b 名字模式自证:四类 token 名必须被**整段**解析(`ease-…` 不得被前缀吞成 `--ed-ease`)" , ( ) => {
127+ // 裸串一律**拼接写**(本文件自身必须贡献 0 命中,见 ① 的最后一条)
128+ const sample = [
129+ "var" + "(--ed-dur-micro, 120ms)" ,
130+ "var" + "(--ed-ease, cubic-bezier(0.2, 0, 0, 1))" ,
131+ "var" + "(--ed-ease-instrument, cubic-bezier(0.4, 0, 0.2, 1))" ,
132+ "var" + "(--ed-ease-paper, cubic-bezier(0.215, 0.61, 0.355, 1))" ,
133+ ] . join ( "; " ) ;
134+ const hits = [ ...sample . matchAll ( CONSUMER ) ] . map ( ( m ) => ( {
135+ name : m [ 0 ] . replace ( / ^ v a r \( / , "" ) ,
136+ fallback : fallbackAfter ( sample , ( m . index ?? 0 ) + m [ 0 ] . length ) ,
137+ } ) ) ;
138+ expect ( hits . map ( ( h ) => h . name ) , "名字被前缀吞掉 ⇒ 兜底解析成 null ⇒ ② 随即误报「没有兜底」" ) . toEqual ( [
139+ "--ed-dur-micro" ,
140+ "--ed-ease" ,
141+ "--ed-ease-instrument" ,
142+ "--ed-ease-paper" ,
143+ ] ) ;
144+ expect ( hits . map ( ( h ) => h . fallback ) , "四类的兜底都必须取到(`ease-<name>` 与 `ease` 走同一条路径)" ) . toEqual ( [
145+ "120ms" ,
146+ "cubic-bezier(0.2, 0, 0, 1)" ,
147+ "cubic-bezier(0.4, 0, 0.2, 1)" ,
148+ "cubic-bezier(0.215, 0.61, 0.355, 1)" ,
149+ ] ) ;
150+ expect ( [ ...sample . matchAll ( WITH_COMMA ) ] . length , "形态计数(带逗号)必须同样认得 `ease-<name>`" ) . toBe ( 4 ) ;
151+ } ) ;
152+
116153 it ( "② 兜底齐备:每个消费点都带兜底(漏一个 ⇒ 那一处静默失去时长)" , ( ) => {
117154 const bad = NO_FALLBACK . map ( ( h ) => `${ h . file } :${ h . line } ${ h . name } ` ) ;
118155 expect ( HITS . length , "域内一处消费点都没有 ⇒ 本文件是空真" ) . toBeGreaterThan ( 0 ) ;
0 commit comments