iter(g65): verify 校验文档 props 落到实现,并补齐漏记字段
This commit is contained in:
@@ -154,6 +154,12 @@
|
||||
"n": "width",
|
||||
"t": "number",
|
||||
"d": "缩略图宽度像素,高度自适应;缺省走样式默认"
|
||||
},
|
||||
{
|
||||
"n": "fallback",
|
||||
"t": "string",
|
||||
"df": "'图片加载失败'",
|
||||
"d": "加载失败时缩略图内显示的占位文案;缺省「图片加载失败」"
|
||||
}
|
||||
],
|
||||
"example": "image({ src: '/a.png', alt: '架构图', width: 160 })",
|
||||
@@ -241,6 +247,12 @@
|
||||
"n": "suffix",
|
||||
"t": "string | HTMLElement",
|
||||
"d": "后置附加块(textField/inputNumber 在输入框内侧后、textarea 在下方);缺省不渲染"
|
||||
},
|
||||
{
|
||||
"n": "clearable",
|
||||
"t": "boolean",
|
||||
"df": "false",
|
||||
"d": "开启后有值即显示一键清空按钮;缺省 false"
|
||||
}
|
||||
],
|
||||
"example": "const name = signal(''); const el = textField({ value: name, placeholder: '输入名字…', onInput: (v) => console.log(v) })",
|
||||
@@ -349,6 +361,11 @@
|
||||
"n": "suffix",
|
||||
"t": "string | HTMLElement",
|
||||
"d": "后置附加块(textField/inputNumber 在输入框内侧后、textarea 在下方);缺省不渲染"
|
||||
},
|
||||
{
|
||||
"n": "maxCount",
|
||||
"t": "number",
|
||||
"d": "字数上限:同时设原生 maxlength,并在下方附加块显示实时计数;缺省不限"
|
||||
}
|
||||
],
|
||||
"example": "const bio = signal(''); textarea({ value: bio, rows: 4, placeholder: '一句话介绍…' })",
|
||||
@@ -1271,6 +1288,12 @@
|
||||
"k": 1,
|
||||
"d": "翻页回调"
|
||||
},
|
||||
{
|
||||
"n": "jumpable",
|
||||
"t": "boolean",
|
||||
"df": "false",
|
||||
"d": "末尾显示跳页输入框(总页数 > 1 才出现);缺省 false"
|
||||
},
|
||||
{
|
||||
"n": "pageSizes",
|
||||
"t": "number[]",
|
||||
|
||||
@@ -850,6 +850,39 @@ function checkDemoBundle(anchors) {
|
||||
return issues;
|
||||
}
|
||||
|
||||
/*** 文档字段漂移校验:props 名必须在实现源码里真实出现(改了参数名忘了改文档就会命中) ***/
|
||||
function checkPropsImpl(pkg, comps) {
|
||||
const issues = [];
|
||||
const srcDir = path.join(ROOT, pkg.dir, 'src'); // 实现源码目录
|
||||
if (!fs.existsSync(srcDir)) return issues;
|
||||
const sources = new Map(); // 文件名 → 源码文本
|
||||
for (const name of fs.readdirSync(srcDir)) {
|
||||
if (name.endsWith('.ts')) sources.set(name, fs.readFileSync(path.join(srcDir, name), 'utf8'));
|
||||
}
|
||||
const findImpl = (id) => {
|
||||
const own = sources.get(`${id}.ts`);
|
||||
if (own !== undefined) return own; // 同名文件优先
|
||||
const decl = new RegExp(`export function ${id}\\(`); // 兜底:按导出函数名反查文件
|
||||
for (const text of sources.values()) if (decl.test(text)) return text;
|
||||
return null;
|
||||
};
|
||||
for (const c of comps) {
|
||||
if (!c.id) continue; // 缺 id 由主循环报,这里跳过
|
||||
const tag = `${pkg.name}#${c.id}`;
|
||||
const impl = findImpl(c.id);
|
||||
if (impl === null) {
|
||||
issues.push(`${tag}: 在 ${pkg.dir}/src 下找不到实现(既无同名文件,也没有 export function ${c.id})`);
|
||||
continue;
|
||||
}
|
||||
for (const p of c.props ?? []) {
|
||||
const name = String(p.n ?? '');
|
||||
if (!/^[A-Za-z_$][\w$]*$/.test(name)) continue; // 说明性条目(...children、带括号注记)不参与
|
||||
if (!impl.includes(name)) issues.push(`${tag}: 文档声明了 props「${name}」,实现里找不到该名字(改名后文档未同步)`);
|
||||
}
|
||||
}
|
||||
return issues;
|
||||
}
|
||||
|
||||
/*** 清单一致性校验:组件文档块的 id / props 说明 / demo 锚点必须与代码和演示产物对齐 ***/
|
||||
function cmdVerify() {
|
||||
const issues = [];
|
||||
@@ -867,6 +900,7 @@ function cmdVerify() {
|
||||
if (pkg.type === 'skin-dom') {
|
||||
issues.push(...checkSkinCss(pkg));
|
||||
issues.push(...checkTokenRefs(pkg, tokenVars));
|
||||
issues.push(...checkPropsImpl(pkg, comps));
|
||||
}
|
||||
if (comps.length && !exports.length) issues.push(`${pkg.name}: docs.components 有 ${comps.length} 项但 api.exports 为空`);
|
||||
|
||||
@@ -906,7 +940,7 @@ function cmdVerify() {
|
||||
console.error(`${issues.length} violation(s)`);
|
||||
process.exit(1);
|
||||
}
|
||||
console.log('verify OK (manifest ↔ docs ↔ demo 一致,皮肤无硬编码颜色与未定义令牌,README 组件清单与分组同步,backlog ↔ 留痕一致)');
|
||||
console.log('verify OK (manifest ↔ docs ↔ demo 一致,文档 props 均能落到实现,皮肤无硬编码颜色与未定义令牌,README 组件清单与分组同步,backlog ↔ 留痕一致)');
|
||||
}
|
||||
|
||||
/*** 校验 git 提交信息是否符合 COMMITTING.md 规范(参数为 message 文件路径或直接 message 文本),违规退出码 1 ***/
|
||||
|
||||
Reference in New Issue
Block a user