iter(g221): verify 校验滚动容器覆盖

This commit is contained in:
HIsheR
2026-10-26 20:02:21 +08:00
parent 0c978c9811
commit 92bc8bb78a
+54 -1
View File
@@ -923,6 +923,58 @@ function checkSkinCss(pkg) {
return issues; return issues;
} }
/*** 滚动条覆盖校验:凡是 overflow: auto|scroll 的 .ua-* 容器,都必须出现在「scrollbar-width」那条统一规则里 ***/
function checkScrollCoverage(pkg) {
const issues = [];
const dir = path.join(ROOT, pkg.dir);
if (!fs.existsSync(dir)) return issues;
for (const name of fs.readdirSync(dir)) {
if (!name.endsWith('.css')) continue; // 只查样式表
const css = fs.readFileSync(path.join(dir, name), 'utf8').replace(/\r\n/g, '\n').replace(/\/\*[\s\S]*?\*\//g, '');
const rules = []; // 括号配平扫出的「选择器 + 规则体」;正则切不了嵌套(@media 里有花括号)
let i = 0;
let sel = '';
while (i < css.length) {
if (css[i] === '{') {
const start = i + 1;
let depth = 1;
i += 1;
while (i < css.length && depth > 0) {
if (css[i] === '{') depth += 1;
else if (css[i] === '}') depth -= 1;
i += 1;
}
rules.push({ sel: sel.trim(), body: css.slice(start, i - 1) });
sel = '';
continue;
}
if (css[i] === '}') { sel = ''; i += 1; continue; } // 顶层多余的右括号:重置选择器
sel += css[i];
i += 1;
}
const containers = new Set(); // 滚得动的单类容器
for (const rule of rules) {
if (!/overflow(-[xy])?:\s*(auto|scroll)/.test(rule.body)) continue;
for (const part of rule.sel.split(',')) {
const hit = part.trim().match(/^\.(ua-[a-z0-9-]+)$/);
if (hit) containers.add(hit[1]);
}
}
if (!containers.size) continue;
const group = rules.find((rule) => rule.body.includes('scrollbar-width')); // 细条 + 令牌拇指那条统一规则
if (!group) {
issues.push(`${pkg.dir}/${name}: 未找到滚动条统一规则(scrollbar-width),新增滚动容器会退回系统原生粗条`);
continue;
}
const covered = new Set([...group.sel.matchAll(/\.(ua-[a-z0-9-]+)/g)].map((m) => m[1]));
for (const cls of containers) {
if (covered.has(cls)) continue;
issues.push(`${pkg.dir}/${name}: 滚动容器 .${cls} 未纳入滚动条统一规则(暗色下会露出原生亮条)`);
}
}
return issues;
}
/*** 重复关键帧校验:两份 @keyframes 的帧体完全一致 = 复制粘贴的动效,语义上只应存在一份(名字不同也算) ***/ /*** 重复关键帧校验:两份 @keyframes 的帧体完全一致 = 复制粘贴的动效,语义上只应存在一份(名字不同也算) ***/
function checkDuplicateKeyframes(pkg) { function checkDuplicateKeyframes(pkg) {
const issues = []; const issues = [];
@@ -1379,6 +1431,7 @@ function cmdVerify() {
if (pkg.type === 'skin-dom') { if (pkg.type === 'skin-dom') {
issues.push(...checkSkinCss(pkg)); issues.push(...checkSkinCss(pkg));
issues.push(...checkDuplicateKeyframes(pkg)); issues.push(...checkDuplicateKeyframes(pkg));
issues.push(...checkScrollCoverage(pkg));
issues.push(...checkTokenRefs(pkg, tokenVars)); issues.push(...checkTokenRefs(pkg, tokenVars));
issues.push(...checkPropsImpl(pkg, comps)); issues.push(...checkPropsImpl(pkg, comps));
issues.push(...checkPropsDoc(pkg, comps)); issues.push(...checkPropsDoc(pkg, comps));
@@ -1424,7 +1477,7 @@ function cmdVerify() {
console.error(`${issues.length} violation(s)`); console.error(`${issues.length} violation(s)`);
process.exit(1); process.exit(1);
} }
console.log('verify OK (manifest ↔ docs ↔ demo 一致,文档 props ↔ 入参接口双向一致,皮肤无硬编码颜色与未定义令牌、暗色令牌齐全、tokens.json ↔ tokens.css 名字与值双源一致、@keyframes 无复制粘贴的重复帧,README 组件清单与分组同步,backlog ↔ 留痕一致(编号严格递增、无重复 id、留痕时间线单调且带 +08:00),组合配方结构完整,MCP 工具清单三处一致,视觉回归抽查清单六组件 × 五维度齐全)'); console.log('verify OK (manifest ↔ docs ↔ demo 一致,文档 props ↔ 入参接口双向一致,皮肤无硬编码颜色与未定义令牌、暗色令牌齐全、tokens.json ↔ tokens.css 名字与值双源一致、@keyframes 无复制粘贴的重复帧、滚动容器全在细条规则内,README 组件清单与分组同步,backlog ↔ 留痕一致(编号严格递增、无重复 id、留痕时间线单调且带 +08:00),组合配方结构完整,MCP 工具清单三处一致,视觉回归抽查清单六组件 × 五维度齐全)');
} }
/*** 校验 git 提交信息是否符合 COMMITTING.md 规范(参数为 message 文件路径或直接 message 文本),违规退出码 1 ***/ /*** 校验 git 提交信息是否符合 COMMITTING.md 规范(参数为 message 文件路径或直接 message 文本),违规退出码 1 ***/