// shortcut: 静态检查覆盖直接绑定;动态文案和接口映射仍须行为测试与人工验收。 const fs = require('node:fs'); const path = require('node:path'); const assert = require('node:assert/strict'); const root = path.resolve(__dirname, '..'); const internal = { test: value => (value.match(/\b(?:id|\w*Id)\b/g) || []) .some(field => !/displayId$/i.test(field)) }; function scan(source) { const findings = []; const add = (index, kind, value) => findings.push({ line: source.slice(0, index).split('\n').length, kind, value }); // 脚本和属性保留行数,但不把路由、事件参数、条件判断当可见文本。 const markup = source.replace(//g, s => s.replace(/[^\n]/g, ' ')); const visible = markup.replace(/<[^>]*>/g, s => s.replace(/[^\n]/g, ' ')); for (const match of visible.matchAll(/\{\{([\s\S]*?)\}\}/g)) { if (/^\s*(?:categoryLabel\(row\.categoryId\)|orgMap\[row\.organizationId\](?:\s*\|\|\s*'-')?)\s*$/.test(match[1])) continue; if (internal.test(match[1])) add(match.index, '可见插值需审查', match[1].trim()); } for (const match of markup.matchAll(/]*\bprop=["']([^"']+)["'][^>]*>/g)) { if (match[1] === 'organizationId' && !match[0].endsWith('/>')) { const end = markup.indexOf('', match.index); if (end >= 0 && /orgMap\[row\.organizationId\]/.test(markup.slice(match.index, end))) continue; } if (internal.test(match[1])) add(match.index, '表格字段需审查', match[1]); } for (const match of markup.matchAll(/]*\bv-model=["']([^"']+)["'][^>]*>/g)) { if (internal.test(match[1])) add(match.index, '编号输入需解析', match[1]); } return findings; } function files(dir) { return fs.readdirSync(dir, { withFileTypes: true }).flatMap(entry => { const target = path.join(dir, entry.name); return entry.isDirectory() ? files(target) : /\.(vue|wxml)$/.test(entry.name) ? [target] : []; }); } if (process.argv.includes('--self-check')) { assert.equal(scan('{{item.userId}}').length, 1); assert.equal(scan('{{item.displayId}}').length, 0); assert.equal(scan('姓名').length, 0); assert.equal(scan('').length, 1); assert.equal(scan('').length, 1); assert.equal(scan('').length, 0); assert.equal(scan('{{orgMap[row.organizationId]}}').length, 0); assert.equal(scan('{{otherMap[row.userId]}}').length, 1); assert.equal(scan('').length, 1); console.log('PASS: 9 项检查器自检(仅允许已审查的名称映射)。'); } else { const targets = ['admin-web/src', 'miniapp/pages', 'miniapp/components'].flatMap(dir => files(path.join(root, dir))); let count = 0; for (const file of targets) { for (const finding of scan(fs.readFileSync(file, 'utf8'))) { console.log(`${path.relative(root, file).replaceAll('\\', '/')}:${finding.line} ${finding.kind}: ${finding.value}`); count++; } } console.log(`${count ? 'FAIL' : 'PASS'}: ${targets.length} 个模板,${count} 个待审查位置。静态结果不代表端到端验收。`); process.exitCode = count ? 1 : 0; }