docs: 归档 demo 工程样例与插件实测报告

- 新增三个 demo 工程(demo-eslint / demo-sqlfluff / demo-stylelint):各含 src/ 样例代码与覆盖率映射(run-coverage.mjs、coverage_map.json、stylelint_line_map.json)

- 归档各 demo 的插件实测导出报告(reports/):

  - demo-eslint:common.js / esm-demo.js / legacysyntax.cjs / typescript.ts 审查报告 + 覆盖率报告(292/292 条零偏差,126 种规则全覆盖)

  - demo-sqlfluff:11 个 SQL 样例审查报告 + 覆盖率报告(113/113 条零偏差,57 种规则全覆盖,含 JJ01 行号缺陷修复后的复测验证)

  - demo-stylelint:common.css / empty-source.css 审查报告 + 覆盖率报告(145/145 条零偏差,68 种规则全覆盖)
This commit is contained in:
范智鹏
2026-08-25 22:00:19 +08:00
parent 9c676e46e6
commit c92249a044
43 changed files with 6103 additions and 0 deletions
+166
View File
@@ -0,0 +1,166 @@
// ESLint 覆盖率测试 v3:复刻内置配置(与 ESLintAdapter.getDefaultConfig 逐字一致)
// + legacysyntax.js script 模式(对应 demo 工程配置 eslint.config.mjs
// + 以 calculateConfigForFile 计算各文件类型实际生效(severity != off)的规则集
import { ESLint } from 'eslint';
import js from '@eslint/js';
import ts from 'typescript-eslint';
import { readFileSync, readdirSync, writeFileSync } from 'fs';
import { join, dirname } from 'path';
import { fileURLToPath } from 'url';
const __dirname = dirname(fileURLToPath(import.meta.url));
const SRC = join(__dirname, 'src');
// ===== 以下与 src/rules/builtin-rules.ts 逐字一致 =====
const eslintExtraRules = {
'eqeqeq': 'error', 'no-eq-null': 'error', 'no-self-compare': 'error',
'no-promise-executor-return': 'error', 'no-shadow': 'error', 'no-unassigned-vars': 'error',
'no-useless-assignment': 'error', 'block-scoped-var': 'error', 'default-case': 'error',
'default-case-last': 'error', 'no-unmodified-loop-condition': 'error', 'no-unreachable-loop': 'error',
'no-eval': 'error', 'no-extend-native': 'error', 'no-var': 'error',
'no-await-in-loop': 'warn', 'prefer-template': 'warn', 'prefer-object-spread': 'warn',
'prefer-rest-params': 'warn', 'prefer-spread': 'warn', 'prefer-object-has-own': 'warn',
'no-useless-concat': 'warn', 'no-useless-return': 'warn', 'no-useless-computed-key': 'warn',
'no-useless-rename': 'warn', 'no-param-reassign': 'warn', 'no-return-assign': 'error',
'no-throw-literal': 'error', 'camelcase': 'warn', 'new-cap': 'warn', 'no-array-constructor': 'error',
};
const eslintExtraTsRules = {
'@typescript-eslint/no-non-null-assertion': 'error',
'@typescript-eslint/no-dynamic-delete': 'error',
'@typescript-eslint/no-useless-empty-export': 'error',
'@typescript-eslint/consistent-type-imports': 'error',
'@typescript-eslint/unified-signatures': 'error',
'@typescript-eslint/no-extraneous-class': 'warn',
'@typescript-eslint/no-useless-constructor': 'warn',
'@typescript-eslint/no-non-null-asserted-nullish-coalescing': 'error',
'@typescript-eslint/no-invalid-void-type': 'warn',
'@typescript-eslint/prefer-literal-enum-member': 'warn',
'@typescript-eslint/prefer-enum-initializers': 'warn',
'no-shadow': 'off',
'@typescript-eslint/no-shadow': 'error',
'no-array-constructor': 'off',
};
const TS_FILES = ['**/*.ts', '**/*.tsx', '**/*.mts', '**/*.cts'];
const JS_FILES = ['**/*.js', '**/*.jsx', '**/*.mjs', '**/*.cjs'];
const tsConfigs = ts.configs.recommended.map(cfg => ({
...cfg,
files: (cfg).files ?? TS_FILES,
}));
// 复刻内置默认配置(与 ESLintAdapter.getDefaultConfig 一致)+ legacysyntax script 模式
const defaultConfig = [
js.configs.recommended,
{ files: JS_FILES, languageOptions: { parserOptions: { ecmaFeatures: { jsx: true } } } },
...tsConfigs,
{ rules: eslintExtraRules },
{ files: TS_FILES, rules: eslintExtraTsRules },
{ files: ['**/legacysyntax.js'], languageOptions: { sourceType: 'script' } },
];
const files = readdirSync(SRC).filter(f => /\.(js|ts|mjs|cjs)$/.test(f));
const engine = new ESLint({ cwd: SRC, overrideConfigFile: true, overrideConfig: defaultConfig });
// ===== 实际生效规则(severity != off=====
async function effectiveRules(sampleFile) {
const cfg = await engine.calculateConfigForFile(join(SRC, sampleFile));
const out = new Set();
for (const [name, val] of Object.entries(cfg.rules ?? {})) {
const sev = Array.isArray(val) ? val[0] : val;
if (sev !== 'off' && sev !== 0) out.add(name);
}
return out;
}
const jsEnabled = await effectiveRules('common.js');
const tsEnabled = await effectiveRules('typescript.ts');
const builtinAll = new Set([...jsEnabled, ...tsEnabled]);
// ===== 逐文件 lint =====
const actualByFile = {};
const parseErrors = {};
let violationTotal = 0;
for (const f of files) {
const text = readFileSync(join(SRC, f), 'utf8');
const results = await engine.lintText(text, { filePath: join(SRC, f) });
const rulesHit = new Set();
for (const r of results) {
for (const m of r.messages) {
if (m.fatal) { parseErrors[f] = m.message; continue; }
violationTotal++;
if (m.ruleId) rulesHit.add(m.ruleId);
}
}
actualByFile[f] = rulesHit;
}
const actualRaw = new Set();
for (const s of Object.values(actualByFile)) for (const r of s) actualRaw.add(r);
// ===== 期望规则提取(demo 注释标注)=====
const NOISE = new Set(['eslint', '本文件', '说明', '保留', '合并', '用于', '异步', '比较', '该文件', 'eslint.config.mjs', '顺手在此以', '文件', '演示', '规则',
// 注释噪音:文件名 / 指令 / 未启用规则名 / 笔误(真实规则为 no-unused-vars
'esm-demo', 'ts-ignore', 'no-empty-function', 'no-useless-vars']);
const expectByFile = {};
for (const f of files) {
const text = readFileSync(join(SRC, f), 'utf8');
const isTs = /\.ts$/.test(f);
const expects = new Set();
for (const line of text.split('\n')) {
const m = line.match(/^\s*\/\/\s*(.+)$/);
if (!m) continue;
const body = m[1];
// 候选 tokenkebab-case 规则名 或 @typescript-eslint/ 前缀规则名
for (const tm of body.matchAll(/(@typescript-eslint\/[a-z0-9-]+|[a-z][a-z0-9]*(-[a-z0-9]+)+)/g)) {
const name = tm[1];
if (NOISE.has(name)) continue;
if (!isTs && name.startsWith('@typescript-eslint/')) continue;
expects.add(name);
}
}
expectByFile[f] = expects;
}
const expectAll = new Set();
for (const s of Object.values(expectByFile)) for (const r of s) expectAll.add(r);
// ===== 统计 =====
// 名称归一化:注释里通常省略 @typescript-eslint/ 前缀,比较时两边都剥掉前缀
const normalize = r => r.replace(/^@typescript-eslint\//, '');
const actualNorm = new Set([...actualRaw].map(normalize));
const jsHit = [...jsEnabled].filter(r => actualRaw.has(r));
const tsHit = [...tsEnabled].filter(r => actualRaw.has(r));
const hitInBuiltin = [...builtinAll].filter(r => actualRaw.has(r));
const expectHit = [...expectAll].filter(r => actualNorm.has(normalize(r)));
const expectMissed = [...expectAll].filter(r => !actualNorm.has(normalize(r))).sort();
console.log('=== ESLint 覆盖率实测结果(v3,生效口径)===');
console.log(`测试文件: ${files.join(', ')}`);
console.log(`解析错误: ${Object.keys(parseErrors).length ? JSON.stringify(parseErrors) : '无'}`);
console.log(`JS 生效规则: ${jsEnabled.size} 条,命中 ${jsHit.length} = ${(jsHit.length / jsEnabled.size * 100).toFixed(1)}%`);
console.log(`TS 生效规则: ${tsEnabled.size} 条,命中 ${tsHit.length} = ${(tsHit.length / tsEnabled.size * 100).toFixed(1)}%`);
console.log(`合并生效规则: ${builtinAll.size} 条,命中 ${hitInBuiltin.length} = ${(hitInBuiltin.length / builtinAll.size * 100).toFixed(1)}%`);
console.log(`demo 期望规则: ${expectAll.size} 条,命中 ${expectHit.length}`);
console.log(`违规总数: ${violationTotal}`);
console.log(`\nJS 未命中 (${jsEnabled.size - jsHit.length}): ${[...jsEnabled].filter(r => !actualRaw.has(r)).sort().join(', ') || '无'}`);
console.log(`\nTS 未命中 (${tsEnabled.size - tsHit.length}): ${[...tsEnabled].filter(r => !actualRaw.has(r)).sort().join(', ') || '无'}`);
console.log(`\n期望未达成: ${expectMissed.join(', ') || '无'}`);
writeFileSync('/data/user/work/results/eslint_result.json', JSON.stringify({
tool: 'eslint',
builtinJsTotal: jsEnabled.size,
builtinTsTotal: tsEnabled.size,
builtinAllTotal: builtinAll.size,
jsHit: jsHit.sort(),
tsHit: tsHit.sort(),
jsMissed: [...jsEnabled].filter(r => !actualRaw.has(r)).sort(),
tsMissed: [...tsEnabled].filter(r => !actualRaw.has(r)).sort(),
actualHitRaw: [...actualRaw].sort(),
expectTotal: expectAll.size,
expectHit: expectHit.sort(),
expectMissed,
violationTotal,
parseErrors,
}, null, 2));
console.log('\n结果已写入 /data/user/work/results/eslint_result.json');