reverse-skills-max: initial public release (restored, redacted, hardened)

This commit is contained in:
baradok609hecl1pq6qi
2026-10-20 02:17:15 +03:00
commit da24c2bd20
2227 changed files with 443931 additions and 0 deletions

View File

@@ -0,0 +1,516 @@
#!/usr/bin/env node
'use strict';
const fs = require('fs');
const path = require('path');
function parseArgs(argv) {
const args = {
caseDir: '',
dir: '',
file: '',
maxLineLength: 180,
maxFileLines: 500,
maxFunctionLines: 90,
json: false,
markdown: false,
};
for (let i = 2; i < argv.length; i++) {
const a = argv[i];
if (a === '--case-dir' || a === '--case' || a === '-d') args.caseDir = argv[++i] || '';
else if (a === '--dir') args.dir = argv[++i] || '';
else if (a === '--file' || a === '-f') args.file = argv[++i] || '';
else if (a === '--max-line-length') args.maxLineLength = Number(argv[++i] || args.maxLineLength);
else if (a === '--max-file-lines') args.maxFileLines = Number(argv[++i] || args.maxFileLines);
else if (a === '--max-function-lines') args.maxFunctionLines = Number(argv[++i] || args.maxFunctionLines);
else if (a === '--json') args.json = true;
else if (a === '--markdown') args.markdown = true;
else if (a === '--help' || a === '-h') args.help = true;
else throw new Error(`未知参数:${a}`);
}
if (!args.json && !args.markdown) args.markdown = true;
return args;
}
function usage() {
return `用法:
node scripts/check_code_quality.js --case-dir case --markdown
node scripts/check_code_quality.js --dir case/result --json
node scripts/check_code_quality.js --file case/result/src/env/install-env.js --markdown
说明:检查最终补环境代码是否简洁、可读、模块化,并验证中文注释为 UTF-8、无乱码、无连续问号、中文注释不含问号。`;
}
function exists(p) { try { fs.accessSync(p); return true; } catch { return false; } }
function stat(p) { try { return fs.statSync(p); } catch { return null; } }
function rel(root, p) { return (path.relative(root, p) || '.').replace(/\\/g, '/'); }
function ext(p) { return path.extname(p).toLowerCase(); }
function walk(p, out = []) {
if (!exists(p)) return out;
const st = stat(p);
if (!st) return out;
if (st.isDirectory()) {
let names = [];
try { names = fs.readdirSync(p); } catch { names = []; }
for (const name of names) walk(path.join(p, name), out);
} else if (st.isFile()) out.push(p);
return out;
}
function isCodeFile(p) {
return ['.js', '.mjs', '.cjs', '.py'].includes(ext(p));
}
function shouldSkipFile(root, p) {
const r = rel(root, p).toLowerCase();
if (/(^|\/)(node_modules|dist|build|coverage|vendor|third_party|third-party)(\/|$)/.test(r)) return true;
if (/(^|\/)src\/target\/(original|vendor|bundle|bundles)(\/|$)/.test(r)) return true;
if (/(\.min\.js|bundle\.js|vendor\.js|package-lock\.json)$/i.test(r)) return true;
return false;
}
function readUtf8Strict(file) {
const buf = fs.readFileSync(file);
const hasBom = buf.length >= 3 && buf[0] === 0xef && buf[1] === 0xbb && buf[2] === 0xbf;
const text = buf.toString('utf8');
return { text: text.replace(/^\uFEFF/, ''), hasBom };
}
function hasChinese(s) {
return /[\u4e00-\u9fff]/.test(String(s || ''));
}
function extractJsComments(text) {
const comments = [];
const blockRe = /\/\*[\s\S]*?\*\//g;
let m;
while ((m = blockRe.exec(text))) {
for (const line of m[0].split(/\r?\n/)) comments.push(line.replace(/^\/\*+|\*+\/$/g, '').replace(/^\s*\*\s?/, '').trim());
}
const lineRe = /(^|\s)\/\/(.*)$/gm;
while ((m = lineRe.exec(text))) comments.push((m[2] || '').trim());
return comments.filter(Boolean);
}
function extractPyComments(text) {
const comments = [];
const lineRe = /^\s*#(.*)$/gm;
let m;
while ((m = lineRe.exec(text))) comments.push((m[1] || '').trim());
return comments.filter(Boolean);
}
function extractComments(file, text) {
return ext(file) === '.py' ? extractPyComments(text) : extractJsComments(text);
}
function firstNonEmptyLines(text, count = 8) {
return text.split(/\r?\n/).map(x => x.trim()).filter(Boolean).slice(0, count);
}
function stripJsLine(line) {
return line
.replace(/\/\*.*?\*\//g, '')
.replace(/\/\/.*$/g, '')
.trim();
}
function codeLineCount(file, lines) {
if (ext(file) === '.py') return lines.filter(line => line.trim() && !line.trim().startsWith('#')).length;
return lines.filter(line => stripJsLine(line)).length;
}
function maxJsBraceDepth(lines) {
let depth = 0;
let maxDepth = 0;
for (const raw of lines) {
const line = stripJsLine(raw);
for (const ch of line) {
if (ch === '{') {
depth += 1;
if (depth > maxDepth) maxDepth = depth;
} else if (ch === '}') {
depth = Math.max(0, depth - 1);
}
}
}
return maxDepth;
}
function maxPyIndentDepth(lines) {
let maxDepth = 0;
for (const raw of lines) {
if (!raw.trim() || raw.trim().startsWith('#')) continue;
const spaces = raw.match(/^\s*/)[0].replace(/\t/g, ' ').length;
maxDepth = Math.max(maxDepth, Math.floor(spaces / 2));
}
return maxDepth;
}
function inspectJsFunctions(lines, maxFunctionLines) {
const problems = [];
const warnings = [];
const starters = [
/\bfunction\s+([A-Za-z_$][\w$]*)?\s*\([^)]*\)\s*\{/,
/\b(?:const|let|var)\s+([A-Za-z_$][\w$]*)\s*=\s*(?:async\s*)?\([^)]*\)\s*=>\s*\{/,
/\b(?:async\s+)?([A-Za-z_$][\w$]*)\s*\([^)]*\)\s*\{/
];
for (let i = 0; i < lines.length; i++) {
const line = stripJsLine(lines[i]);
if (!line.includes('{')) continue;
if (!starters.some(re => re.test(line))) continue;
let depth = 0;
let end = i;
let seenOpen = false;
for (let j = i; j < lines.length; j++) {
const cur = stripJsLine(lines[j]);
for (const ch of cur) {
if (ch === '{') { depth += 1; seenOpen = true; }
else if (ch === '}') depth -= 1;
}
if (seenOpen && depth <= 0) { end = j; break; }
}
const size = end - i + 1;
if (size > maxFunctionLines) problems.push(`第 ${i + 1} 行附近函数过长:${size} 行,建议拆分到 ${maxFunctionLines} 行以内。`);
const prev = lines.slice(Math.max(0, i - 3), i).join('\n');
if (size > 15 && !hasChinese(prev)) warnings.push(`第 ${i + 1} 行附近函数较长但前置中文说明不足,建议补充职责、输入和输出说明。`);
}
return { problems, warnings };
}
function inspectPyFunctions(lines, maxFunctionLines) {
const problems = [];
const warnings = [];
for (let i = 0; i < lines.length; i++) {
const m = lines[i].match(/^(\s*)def\s+([A-Za-z_]\w*)\s*\(/);
if (!m) continue;
const baseIndent = m[1].replace(/\t/g, ' ').length;
let end = i;
for (let j = i + 1; j < lines.length; j++) {
if (!lines[j].trim()) { end = j; continue; }
const indent = lines[j].match(/^\s*/)[0].replace(/\t/g, ' ').length;
if (indent <= baseIndent) break;
end = j;
}
const size = end - i + 1;
if (size > maxFunctionLines) problems.push(`第 ${i + 1} 行附近函数过长:${size} 行,建议拆分到 ${maxFunctionLines} 行以内。`);
const prev = lines.slice(Math.max(0, i - 3), i).join('\n');
if (size > 15 && !hasChinese(prev)) warnings.push(`第 ${i + 1} 行附近函数较长但前置中文说明不足,建议补充职责、输入和输出说明。`);
}
return { problems, warnings };
}
function inspectEmbeddedScriptStrings(relFile, text) {
const problems = [];
const warnings = [];
const normalized = relFile.replace(/\\/g, '/');
const fileName = path.basename(normalized).toLowerCase();
const isEnvFile = /(^|\/)src\/env\//i.test(normalized) || /(^|\/)env\//i.test(normalized);
if (!/\.(?:js|mjs|cjs)$/i.test(normalized)) return { problems, warnings };
const scriptRe = /\b(?:const|let|var)\s+([A-Za-z_$][\w$]*(?:SCRIPT|SOURCE|CODE)[A-Za-z_$]*)\s*=\s*String\.raw`([\s\S]*?)`/g;
let match;
while ((match = scriptRe.exec(text))) {
const name = match[1];
const body = match[2] || '';
const bodyLines = body.split(/\r?\n/).length;
const looksLikeWebApi = /\b(?:Navigator|Document|Window|Location|History|Screen|XMLHttpRequest|HTMLCanvasElement|WebGL|navigator|document|window|xbs\.|createProtoChains|createNativeFunction|createGetter|createSetter|createNativeCollection|xbs\.dom\.createDocument)\b/.test(body);
if (isEnvFile && (bodyLines > 40 || looksLikeWebApi)) {
problems.push('发现大段 isolated-vm 补环境字符串 `' + name + ' = String.raw 模板字符串`(约 ' + bodyLines + ' 行)。补环境源码必须拆成真实 .js 文件,例如 browser-objects/navigator.js、document.js、fingerprint/canvas.js,再由 runtime.runFile/runFiles 注入 Context。');
} else if (bodyLines > 80) {
warnings.push(`发现较大的 String.raw 字符串 \`${name}\`(约 ${bodyLines} 行),请确认不是补环境 WebAPI 主实现。`);
}
}
if (isEnvFile && /^script-(?:core|browser|.*objects|.*env).*\.js$/i.test(fileName) && /String\.raw`/.test(text)) {
problems.push('发现 script-*.js 聚合字符串补环境文件。isolated-vm 交付应使用真实模块文件,不应把主要 WebAPI 放入 script-core.js 或 script-browser-objects.js。');
}
if (isEnvFile && /\b[A-Z][A-Z0-9_]*SCRIPT\b[\s\S]{0,200}\.join\(\s*['"]\\n['"]\s*\)/.test(text)) {
problems.push('发现把多个 *_SCRIPT 字符串 join 后执行的补环境聚合方式。请改为 runtime.runFiles([...]) 按真实文件注入。');
}
if (isEnvFile && /module\.exports\s*=\s*\{[\s\S]{0,300}\b[A-Z][A-Z0-9_]*SCRIPT\b/.test(text)) {
warnings.push('发现导出 *_SCRIPT 字符串。除极小 bootstrap 外,补环境实现应导出安装函数或通过真实文件加载。');
}
return { problems, warnings };
}
const WEBAPI_DOMAIN_PATTERNS = [
['window/global', /\b(?:window|globalThis|self|top|parent|frames|innerWidth|outerWidth|devicePixelRatio)\b/],
['navigator', /\b(?:navigator|Navigator|PluginArray|MimeTypeArray|permissions|sendBeacon|hardwareConcurrency|webdriver|mimeTypes|plugins)\b/],
['document/dom', /\b(?:document|Document|HTMLDocument|Element|HTMLElement|createElement|createTextNode|currentScript|DOMRect|MutationObserver|HTMLCollection|NodeList)\b/],
['location/history', /\b(?:location|Location|history|History|pushState|replaceState)\b/],
['screen', /\b(?:screen|Screen|availWidth|availHeight|colorDepth|pixelDepth)\b/],
['storage/cookie', /\b(?:localStorage|sessionStorage|Storage|cookie|Cookie|document\.cookie)\b/],
['network', /\b(?:XMLHttpRequest|fetch|Request|Response|Headers|sendBeacon|open\s*\(|setRequestHeader|readyState)\b/],
['canvas', /\b(?:HTMLCanvasElement|CanvasRenderingContext2D|toDataURL|getImageData|measureText|canvas)\b/],
['webgl', /\b(?:WebGLRenderingContext|WebGL2RenderingContext|WebGLShaderPrecisionFormat|getParameter|readPixels|getSupportedExtensions|webgl)\b/],
['audio', /\b(?:AudioContext|OfflineAudioContext|AudioBuffer|HTMLAudioElement|getChannelData|startRendering|audio)\b/],
['performance/timing', /\b(?:performance|Performance|PerformanceNavigationTiming|timeOrigin|navigationStart|requestAnimationFrame)\b/],
['events', /\b(?:EventTarget|Event|MouseEvent|KeyboardEvent|CustomEvent|addEventListener|dispatchEvent|isTrusted)\b/],
['worker/wasm/message', /\b(?:Worker|postMessage|MessageChannel|MessagePort|WebAssembly|BroadcastChannel)\b/],
];
function detectWebApiDomains(text) {
const found = [];
for (const [name, pattern] of WEBAPI_DOMAIN_PATTERNS) {
if (pattern.test(text)) found.push(name);
}
return found;
}
function isEnvModulePath(relFile) {
return /(^|\/)src\/(?:env|node-runtime\/env)\//i.test(relFile) || /(^|\/)env\//i.test(relFile);
}
function inspectWebApiModuleBoundary(relFile, text, lines, args) {
const problems = [];
const warnings = [];
const normalized = relFile.replace(/\\/g, '/');
if (!/\.(?:js|mjs|cjs)$/i.test(normalized)) return { problems, warnings };
const domains = detectWebApiDomains(text);
if (!domains.length) return { problems, warnings };
const envModule = isEnvModulePath(normalized);
const orchestrationDir = /(^|\/)src\/(?:signer|request|resources)\//i.test(normalized);
const signerFile = /(^|\/)src\/signer\//i.test(normalized);
const probeLikeFile = /(^|\/)[^/]*(?:probe|diagnostic|runtime-runner|runtime_probe|runtime-probe)[^/]*\.(?:js|mjs|cjs)$/i.test(normalized);
const codeLines = codeLineCount(normalized, lines);
const implementationLike = /\b(?:installBrowserEnv|install[A-Z][\w$]*Env|create[A-Z][\w$]*(?:Environment|Constructors?|Env)|createNativeHelpers|defineValue\s*\(|defineGetter\s*\(|defineAccessor\s*\(|Object\.defineProperty|Object\.defineProperties|nativeApi\.fn|nativeApi\.getter|HTMLCanvasElement|WebGLRenderingContext|XMLHttpRequest)\b/.test(text);
const domainText = domains.join('、');
if (signerFile && domains.length >= 3 && implementationLike) {
problems.push(`src/signer 文件承载了 ${domains.length} 类 WebAPI 补环境主体(${domainText})。signer 只允许做入口编排、调用目标 signer 和整理输出;请把 WebAPI 安装逻辑拆到 src/env/ 或 src/node-runtime/env/ 下的真实模块。`);
}
if (probeLikeFile && !envModule && domains.length >= 4 && codeLines > 180 && implementationLike) {
problems.push(`probe / runtime 入口文件承载了 ${domains.length} 类 WebAPI 补环境主体(${domainText})。probe 只能是薄入口,建议少于 150 行;请拆分为 browser-objects、fingerprint、network、bootstrap 等模块。`);
}
if (!envModule && domains.length >= 5 && codeLines > args.maxFileLines && implementationLike) {
problems.push(`单个非 env 文件同时实现 ${domains.length} 类 WebAPI 且超过 ${args.maxFileLines} 行(${domainText}),属于补环境主体堆叠。请先规划目录并拆分模块后再继续验证。`);
}
if (orchestrationDir && domains.length >= 2 && codeLines > 240 && implementationLike && !signerFile) {
warnings.push(`编排目录文件包含多类 WebAPI 实现迹象(${domainText})。请确认它只做请求、资源或摘要编排;WebAPI 主体必须放入 src/env/ 或 src/node-runtime/env/。`);
}
return { problems, warnings };
}
function inspectProjectStructure(root, files) {
const problems = [];
const warnings = [];
const codeFiles = files.filter(isCodeFile);
const fileEntries = codeFiles.map(filePath => {
let text = '';
try { text = readUtf8Strict(filePath).text; } catch { text = ''; }
return { filePath, relFile: rel(root, filePath), text };
});
const isolatedVmUsed = fileEntries.some(entry => /\b(?:isolated_vm\.node|xbs-isolated-vm|createXbsRuntime|createIsolatedVmRuntime|window\.xbs|xbs\.dom\.createDocument)\b/.test(entry.text));
if (!isolatedVmUsed) return { problems, warnings };
const envEntries = fileEntries.filter(entry => /(^|\/)src\/env\//i.test(entry.relFile));
const embeddedEnvScripts = envEntries.filter(entry => /String\.raw`/.test(entry.text) && /\b(?:Navigator|Document|Window|XMLHttpRequest|HTMLCanvasElement|WebGL|xbs\.|navigator|document|window)\b/.test(entry.text));
if (embeddedEnvScripts.length) {
problems.push('选择 isolated-vm 时发现补环境 WebAPI 仍以 String.raw 聚合字符串实现:' + embeddedEnvScripts.map(entry => entry.relFile).join('、') + '。请拆分为真实模块文件并用 runtime.runFile/runFiles 加载。');
}
const hasBrowserObjectModule = envEntries.some(entry => /(^|\/)src\/env\/browser-objects\/[^/]+\.js$/i.test(entry.relFile));
const touchesBrowserObjects = envEntries.some(entry => /\b(?:navigator|document|window|location|screen|XMLHttpRequest|history|storage)\b/i.test(entry.text));
if (touchesBrowserObjects && !hasBrowserObjectModule) {
problems.push('选择 isolated-vm 且补了浏览器对象,但未发现 src/env/browser-objects/*.js 模块。请至少按 navigator.js、document.js、window.js 等职责拆分。');
}
const hasRunFiles = fileEntries.some(entry => /\brunFiles\s*\(|\brunFile\s*\(/.test(entry.text));
if (embeddedEnvScripts.length && !hasRunFiles) {
warnings.push('未发现 runtime.runFile/runFiles 文件化加载入口。建议在 xbs runtime 中提供按文件注入 Context 的能力。');
}
return { problems, warnings };
}
function inspectFile(root, file, args) {
const relFile = rel(root, file);
const problems = [];
const warnings = [];
const { text, hasBom } = readUtf8Strict(file);
const lines = text.split(/\r?\n/);
const comments = extractComments(file, text);
const chineseComments = comments.filter(hasChinese);
const codeLines = codeLineCount(file, lines);
const embeddedScriptCheck = inspectEmbeddedScriptStrings(relFile, text);
problems.push(...embeddedScriptCheck.problems);
warnings.push(...embeddedScriptCheck.warnings);
const webApiBoundaryCheck = inspectWebApiModuleBoundary(relFile, text, lines, args);
problems.push(...webApiBoundaryCheck.problems);
warnings.push(...webApiBoundaryCheck.warnings);
if (hasBom) problems.push('文件带 UTF-8 BOM,建议使用无 BOM UTF-8。');
if (/\uFFFD/.test(text)) problems.push('文件包含替换字符,疑似 UTF-8 解码或写入异常。');
if (/\?{6,}/.test(text)) problems.push('文件包含连续问号,疑似中文编码问题。');
for (let i = 0; i < comments.length; i++) {
const c = comments[i];
if (hasChinese(c) && /[??]/.test(c)) problems.push(`中文注释包含问号:第 ${i + 1} 条注释“${c.slice(0, 60)}”。`);
if (/\uFFFD|\?{3,}/.test(c)) problems.push(`注释疑似乱码:第 ${i + 1} 条注释“${c.slice(0, 60)}”。`);
}
const header = firstNonEmptyLines(text, 8).join('\n');
if (!hasChinese(header) || !/^\s*(\/\/|\/\*|\*|#)/m.test(header)) {
problems.push('文件开头缺少中文职责注释;请在文件顶部说明模块用途、数据来源和边界。');
}
if (codeLines > 20 && chineseComments.length === 0) problems.push('代码超过 20 行但没有中文注释。');
if (codeLines > 80 && chineseComments.length < Math.ceil(codeLines / 120)) {
problems.push(`中文注释过少:代码约 ${codeLines} 行,中文注释 ${chineseComments.length} 条。`);
}
if (lines.length > args.maxFileLines) problems.push(`文件过大:${lines.length} 行,建议拆分到 ${args.maxFileLines} 行以内。`);
lines.forEach((line, idx) => {
if (line.length > args.maxLineLength) problems.push(`第 ${idx + 1} 行过长:${line.length} 字符,建议拆分到 ${args.maxLineLength} 字符以内。`);
if (line.length > 320) problems.push(`第 ${idx + 1} 行疑似压缩或堆叠代码。`);
});
const semicolonDense = lines.filter(line => (line.match(/;/g) || []).length >= 6);
if (semicolonDense.length) problems.push(`发现 ${semicolonDense.length} 行包含大量分号,疑似压缩或多语句堆叠。`);
const denseLineProblems = [];
lines.forEach((rawLine, idx) => {
const line = stripJsLine(rawLine);
if (!line) return;
const lineNo = idx + 1;
const defineCount = (line.match(/\b(?:Object\.defineProperty|Object\.defineProperties|defineValue|defineNativeValue|defineNativeGetter|defineNativeSetter|defineNativeAccessor)\s*\(/g) || []).length;
if (defineCount >= 2) {
denseLineProblems.push(`第 ${lineNo} 行同时包含 ${defineCount} 个属性定义调用,应拆成多行并补充 descriptor 说明。`);
}
if (/(?:Object\.defineProperty|Object\.defineProperties)\s*\([^;]+\{[^{}\n]*(?:value|get|set|writable|enumerable|configurable)[^{}\n]*\}[^;]*;?$/.test(line) && line.length > 110) {
denseLineProblems.push(`第 ${lineNo} 行把属性描述符压在一行,建议展开 value/get/set/writable/enumerable/configurable。`);
}
if (/\bObject\.assign\s*\([^;]*\{.*\{.*\}/.test(line)) {
denseLineProblems.push(`第 ${lineNo} 行疑似用 Object.assign 堆叠对象和方法,补环境代码应拆为 createProtoChains 与 defineProperty。`);
}
if (/\b(?:ctx|window|globalThis|globalObject|self)\s*(?:\.|\[).*=.*\{.*(?:function\b|=>|\b[A-Za-z_$][\w$]*\s*\([^)]*\)\s*\{)/.test(line)) {
denseLineProblems.push(`第 ${lineNo} 行把全局 WebAPI 对象、方法或函数堆在一行,建议拆成模块化安装函数。`);
}
if (/\b(?:if|for|while|try|catch|finally)\b[^{;\n]*\{[^{}\n]*;[^{}\n]*\}/.test(line) && line.length > 120) {
denseLineProblems.push(`第 ${lineNo} 行存在较长的单行控制流代码块,建议展开为多行。`);
}
if (/\bfunction\b[^{;\n]*\{[^{}\n]*;[^{}\n]*\}/.test(line) && line.length > 120) {
denseLineProblems.push(`第 ${lineNo} 行存在单行函数体,建议提取为具名函数并展开实现。`);
}
});
if (denseLineProblems.length) problems.push(...denseLineProblems.slice(0, 30));
if (denseLineProblems.length > 30) problems.push(`还有 ${denseLineProblems.length - 30} 个疑似单行堆叠问题未逐条展示,请先格式化源码后重新检查。`);
if (/\bdebugger\s*;/.test(text)) problems.push('存在 debugger 语句,最终代码不得保留调试断点。');
if (/TODO|FIXME|临时|随便|测试用|先这样|debug/i.test(text)) warnings.push('发现 TODO/FIXME/临时/调试类标记,交付前建议清理或改为正式说明。');
if (/\b(?:var\s+[a-z]\b|function\s+[a-z]\s*\(|const\s+[a-z]\s*=|let\s+[a-z]\s*=)/.test(text)) warnings.push('发现过短变量或函数名,建议使用表达业务含义的命名。');
const depth = ext(file) === '.py' ? maxPyIndentDepth(lines) : maxJsBraceDepth(lines);
if (depth > 8) problems.push(`嵌套层级过深:最大层级 ${depth},建议拆分函数或提前返回。`);
else if (depth > 6) warnings.push(`嵌套层级偏深:最大层级 ${depth},建议优化结构。`);
const funcs = ext(file) === '.py' ? inspectPyFunctions(lines, args.maxFunctionLines) : inspectJsFunctions(lines, args.maxFunctionLines);
problems.push(...funcs.problems);
warnings.push(...funcs.warnings);
const anonymousCount = (text.match(/\bfunction\s*\(/g) || []).length + (text.match(/=>\s*\{/g) || []).length;
if (anonymousCount > 8) warnings.push(`匿名函数较多:${anonymousCount} 个,建议提取为具名函数提升可读性。`);
return {
file: relFile,
clean: problems.length === 0,
lines: lines.length,
codeLines,
chineseCommentCount: chineseComments.length,
maxDepth: depth,
problems,
warnings,
};
}
function check(args) {
const root = args.file
? path.resolve(path.dirname(args.file))
: args.dir
? path.resolve(args.dir)
: path.join(path.resolve(args.caseDir || 'case'), 'result');
const files = args.file ? [path.resolve(args.file)] : walk(root).filter(p => isCodeFile(p) && !shouldSkipFile(root, p));
const problems = [];
const warnings = [];
if (!files.length) problems.push(`未找到可检查的最终代码文件:${root}`);
const fileResults = files.map(f => inspectFile(root, f, args));
for (const r of fileResults) {
for (const p of r.problems) problems.push(`${r.file}: ${p}`);
for (const w of r.warnings) warnings.push(`${r.file}: ${w}`);
}
const structure = inspectProjectStructure(root, files);
for (const p of structure.problems) problems.push(p);
for (const w of structure.warnings) warnings.push(w);
return {
root,
clean: problems.length === 0,
filesChecked: fileResults.length,
limits: {
maxLineLength: args.maxLineLength,
maxFileLines: args.maxFileLines,
maxFunctionLines: args.maxFunctionLines,
},
problems,
warnings,
fileResults,
};
}
function renderMarkdown(result) {
const lines = [
'# 补环境代码质量检查结果',
'',
`检查范围:${result.root}`,
`是否通过:${result.clean ? '是' : '否'}`,
`检查文件数:${result.filesChecked}`,
'',
'## 质量规则',
`- 单行长度上限:${result.limits.maxLineLength}`,
`- 单文件行数上限:${result.limits.maxFileLines}`,
`- 单函数行数上限:${result.limits.maxFunctionLines}`,
'- 必须有中文职责注释,中文注释不得包含问号、连续问号或乱码。',
'- 禁止压缩代码、过度堆叠语句、调试断点和临时测试标记。',
'- signer / probe / runtime 入口不得承载 navigator、document、canvas、webgl、performance 等多域 WebAPI 补环境主体。',
'- XHR/fetch 实现应拆入 env/network 并通过 live session bridge;浏览器对象私有状态不得以 _ / __ own property 泄露。',
'- isolated-vm 补环境不得以大段 String.raw / *_SCRIPT 字符串作为主要交付形态,必须拆成真实文件模块并通过 runFile/runFiles 注入。',
'',
];
if (result.problems.length) {
lines.push('## 问题');
for (const p of result.problems) lines.push(`- ${p}`);
lines.push('');
}
if (result.warnings.length) {
lines.push('## 提醒');
for (const w of result.warnings) lines.push(`- ${w}`);
lines.push('');
}
lines.push('## 文件摘要');
for (const f of result.fileResults) {
lines.push(`- ${f.file}:${f.clean ? '通过' : '失败'},总行数 ${f.lines},代码行 ${f.codeLines},中文注释 ${f.chineseCommentCount},最大嵌套 ${f.maxDepth}`);
}
if (!result.fileResults.length) lines.push('- 无');
return lines.join('\n') + '\n';
}
try {
const args = parseArgs(process.argv);
if (args.help) { console.log(usage()); process.exit(0); }
const result = check(args);
if (args.json) console.log(JSON.stringify(result, null, 2));
if (args.markdown) process.stdout.write(renderMarkdown(result));
process.exit(result.clean ? 0 : 1);
} catch (err) {
console.error(err.message || String(err));
console.error(usage());
process.exit(1);
}
module.exports = { check, inspectFile, extractComments, inspectEmbeddedScriptStrings, inspectWebApiModuleBoundary, inspectProjectStructure };