Skip to content

Commit aa93655

Browse files
committed
feat(scripts): line-limits 新增末尾换行判据
1 parent f174239 commit aa93655

1 file changed

Lines changed: 19 additions & 2 deletions

File tree

‎scripts/line-limits.mjs‎

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,13 @@
66
* 副作用:`--write` 会重写 docs/standards/line-limit-exemptions.md(人工列按路径保留,唯带 `(自动摘取,待细化)` 标记的理由是生成物、每次重摘);默认模式只读。
77
* 用法:node scripts/line-limits.mjs [--write|--full]
88
* 忽略任何传入的文件参数,**永远全树扫描**(门禁因此不必依赖 glob —— 旧 glob 的命中集还大于扫描域)。
9+
*
10+
* 判据 (f)(仅 `--full`,批 8 T2):域内**非空**文件的最后一个字节必须是 `0x0A`(末尾换行存在性)。
11+
* 🔴 **只判末尾 `0x0A` 是否存在,不判行尾风格** —— 本仓 CRLF 与 LF 并用(域内 1156 文件实测:以 CRLF 结尾 581 · 以裸 LF 结尾 575),
12+
* 写成「必须 LF 行尾」会让**整仓 RED**。Why 非加它不可:末尾换行缺失时 `countLines()` 读数**纹丝不动**
13+
* (公式已把「末尾无换行」折进去)⇒ (a)–(e) 与豁免表**都看不见**它(批 7 §C51.7① 在 `commands_video.rs` 上实测)。
914
*/
10-
import { existsSync, writeFileSync } from 'node:fs';
15+
import { existsSync, readFileSync, writeFileSync } from 'node:fs';
1116
import { join, resolve } from 'node:path';
1217
import { fileURLToPath } from 'node:url';
1318
import { ROOT, SCAN_DIRS, SOURCE_EXT, TABLE_PATH, HARD_LIMIT, SOFT_LIMIT, FROZEN_OVER_LIMIT, scanTree, parseTable, parseHistory, autoReason, stripOverPrefix, OVER_PREFIX } from './lib/lineScan.mjs';
@@ -165,12 +170,24 @@ function check({ full }) {
165170
if (n !== undefined && n !== r.declared) problems.push(`(e) 行数不一致:${r.path} 声明 ${r.declared} / 实测 ${n} → 运行 --write`);
166171
}
167172
}
173+
// (f) 末尾换行存在性(仅 --full;口径见头注「判据 (f)」)。
174+
// Why 判**字节**而不判文本:`readFileSync(p, "utf8").endsWith("\n")` 在 CRLF 文件上同样为真,
175+
// 看起来够用,却把「末尾恰有一个 0x0A」与「文本层最后一个字符是换行」混谈 —— 判据要的是前者。
176+
// 空文件(`b.length === 0`)跳过:它没有「末尾」,补一行空行反而是内容改动(卡片 `:615` 逐字)。
177+
if (full) {
178+
for (const p of measured.keys()) {
179+
const b = readFileSync(join(ROOT, p));
180+
if (b.length > 0 && b[b.length - 1] !== 0x0a) problems.push(`(f) 末尾无换行:${p}`);
181+
}
182+
}
168183

169184
const over = [...measured.entries()].filter(([, n]) => n > HARD_LIMIT).length;
170185
const band = [...measured.entries()].filter(([, n]) => n > SOFT_LIMIT && n <= HARD_LIMIT).length;
171186
const mode = full ? '--full' : '默认';
187+
// 表头只在 **(f) 命中时**追加子口径名 ⇒ 既有五条判据单独命中时,表头与拆分前**逐字相同**。
188+
const eolHit = problems.some((p) => p.startsWith('(f) '));
172189
if (problems.length) {
173-
console.error(`❌ line-limits(${mode}):${problems.length} 处问题`);
190+
console.error(`❌ line-limits(${mode}${eolHit ? ' · 末尾换行' : ''}):${problems.length} 处问题`);
174191
for (const p of problems) console.error(` · ${p}`);
175192
console.error(`\n现状:>${HARD_LIMIT} 硬限 ${over}/${FROZEN_OVER_LIMIT.length}(棘轮)· ${SOFT_LIMIT+1}–${HARD_LIMIT} 档 ${band} · 登记条目 ${rows.length}`);
176193
process.exit(1);

0 commit comments

Comments
 (0)