test: 目录对齐赛道二成果物规范(tests/ 迁移+实验报告+提效测量脚本)

- src/test 迁移至根目录 tests/(16 测试 + fixtures/manual),导入改为 ../src/
- 新增 tsconfig.test.json 独立编译测试;.vscode-test.mjs / package.json / .gitignore 同步
- 新增 tests/test-execution-log.md、tests/test-cases.md(116 用例清单)
- 新增 tests/measure/measure-review-time.mjs + performance-comparison.md(提效测量)
- 重命名 3 个中文报告文件为 demo-*-coverage-report.md
- git rm --cached 解除 vsix 跟踪;README 标注演示视频进行中
This commit is contained in:
范智鹏
2026-08-26 19:17:44 +08:00
parent 313ae24346
commit e9762bfbe1
39 changed files with 2406 additions and 3 deletions
+150
View File
@@ -0,0 +1,150 @@
import { ESLint } from 'eslint';
import js from '@eslint/js';
import ts from 'typescript-eslint';
import stylelint from 'stylelint';
import recommendedConfig from 'stylelint-config-recommended';
import { readFileSync, readdirSync, writeFileSync, mkdirSync, existsSync } from 'fs';
import { join, dirname } from 'path';
import { fileURLToPath } from 'url';
const __dirname = dirname(fileURLToPath(import.meta.url));
const DATA = join(__dirname, '..', '..', 'data');
const OUT_DIR = join(__dirname, 'results');
// ===== 以下与 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 stylelintExtraRules = {
'color-no-invalid-hex': true,
'function-linear-gradient-no-nonstandard-direction': true,
'function-no-unknown': true,
'unit-no-unknown': true,
'no-unknown-animations': true,
'no-unknown-custom-media': true,
'no-unknown-custom-properties': true,
'at-rule-no-vendor-prefix': true,
'media-feature-name-no-vendor-prefix': true,
'property-no-vendor-prefix': true,
'selector-no-vendor-prefix': true,
'value-no-vendor-prefix': true,
'color-hex-length': 'short',
'color-function-notation': 'modern',
'length-zero-no-unit': true,
'selector-pseudo-element-colon-notation': 'double',
'import-notation': 'string',
'alpha-value-notation': 'number',
'hue-degree-notation': 'angle',
'keyframe-selector-notation': 'percentage',
};
const TS_FILES = ['**/*.ts', '**/*.tsx', '**/*.mts', '**/*.cts'];
const JS_FILES = ['**/*.js', '**/*.jsx', '**/*.mjs', '**/*.cjs'];
function timing(ms) {
return `${ms.toFixed(1)}ms`;
}
async function measure(name, fn) {
const start = process.hrtime.bigint();
const result = await fn();
const end = process.hrtime.bigint();
const ms = Number(end - start) / 1e6;
return { name, ms, result };
}
async function main() {
if (!existsSync(OUT_DIR)) mkdirSync(OUT_DIR, { recursive: true });
const rows = [];
const summary = { tool: 'code-reviewer', measureDate: new Date().toISOString() };
const eslintConfig = [
js.configs.recommended,
{ files: JS_FILES, languageOptions: { parserOptions: { ecmaFeatures: { jsx: true } } } },
...ts.configs.recommended.map(cfg => ({ ...cfg, files: cfg.files ?? TS_FILES })),
{ rules: eslintExtraRules },
{ files: TS_FILES, rules: eslintExtraTsRules },
];
const eslintDemo = join(DATA, 'demo-eslint', 'src');
const eslintFiles = readdirSync(eslintDemo).filter(f => /\.(js|ts|mjs|cjs)$/.test(f));
if (eslintFiles.length > 0) {
const engine = new ESLint({ cwd: eslintDemo, overrideConfigFile: true, overrideConfig: eslintConfig });
const t = measure('eslint static analysis (demo-eslint/src)', async () => {
const results = [];
for (const f of eslintFiles) {
const text = readFileSync(join(eslintDemo, f), 'utf8');
const [res] = await engine.lintText(text, { filePath: join(eslintDemo, f) });
results.push({ file: f, messages: res.messages.length });
}
return results;
});
const { ms, result } = await t;
rows.push({ target: 'demo-eslint', files: eslintFiles.length, ms, diagnostics: result.reduce((s, r) => s + r.messages, 0) });
}
const stylelintDemo = join(DATA, 'demo-stylelint', 'src');
const stylelintFiles = readdirSync(stylelintDemo).filter(f => /\.css$/.test(f));
if (stylelintFiles.length > 0) {
const styleConfig = {
...recommendedConfig,
rules: { ...recommendedConfig.rules, ...stylelintExtraRules },
};
const t = measure('stylelint static analysis (demo-stylelint/src)', async () => {
const results = [];
for (const f of stylelintFiles) {
const code = readFileSync(join(stylelintDemo, f), 'utf8');
const res = await stylelint.lint({ code, codeFilename: join(stylelintDemo, f), config: styleConfig, cwd: stylelintDemo });
const warnings = res.results.flatMap(r => r.warnings).length;
results.push({ file: f, warnings });
}
return results;
});
const { ms, result } = await t;
rows.push({ target: 'demo-stylelint', files: stylelintFiles.length, ms, diagnostics: result.reduce((s, r) => s + r.warnings, 0) });
}
const totalMs = rows.reduce((s, r) => s + r.ms, 0);
summary.rows = rows;
summary.totalMs = totalMs;
writeFileSync(join(OUT_DIR, 'measure-results.json'), JSON.stringify(summary, null, 2));
console.log('=== Code Purifier 静态分析耗时测量 ===');
for (const r of rows) {
console.log(`${r.target}: ${r.files} 文件, ${r.diagnostics} 条诊断, ${timing(r.ms)}`);
}
console.log(`合计: ${timing(totalMs)}`);
console.log(`结果已写入 tests/measure/results/measure-results.json`);
}
main().catch(err => {
console.error(err);
process.exit(1);
});
+45
View File
@@ -0,0 +1,45 @@
# 提效对比报告(基线 vs 提效后)
## 一、测量方法
**场景**:对给定样例代码进行代码规范检查,比较两种方式的耗时与产出。
- **基线(人工审查)**:由人工阅读样例代码并列出规范问题(耗时依赖个人经验,需实测采集)。
- **提效后(Code Purifier 插件静态分析链路)**:使用与插件内置配置逐字一致的 ESLint / Stylelint 引擎对 `data/demo-*/src/*` 执行静态分析,计时(可复现:`node tests/measure/measure-review-time.mjs`)。
> 测量脚本只产插件侧耗时;**人工基线需实测后手动填入下方表格**,原始记录需保留(时间、审查人、发现数)。
## 二、对比维度
| 维度 | 基线(人工审查) | 提效后(插件静态分析) |
|---|---|---|
| 审查耗时 | 待实测填入 | 见下表(脚本产出) |
| 发现规范问题数 | 待实测填入 | 脚本产出诊断数 |
| 覆盖率口径 | 依赖个人经验 | 内置配置规则全覆盖(demo 实测 100%) |
| 可重复性 | 低(因人而异) | 高(同配置逐字可复现) |
## 三、插件侧实测数据(脚本产出)
> 运行:`node tests/measure/measure-review-time.mjs`,结果另存 `tests/measure/results/measure-results.json`。
> 实测日期:2026-08-26Node.js v24.16.0。
| 目标 | 文件数 | 诊断数 | 耗时 |
|---|---|---|---|
| demo-eslint | 6 | 294 | 192.2ms |
| demo-stylelint | 2 | 115 | 2106.7ms |
| **合计** | **8** | **409** | **2298.9ms** |
## 四、人工基线(待实测)
> 请按以下步骤实测后填写,并保留原始记录:
> 1. 取 `data/demo-eslint/src/common.js`(约 12KB)为样例;
> 2. 审查人逐行阅读,列出发现的规范问题(命名/风格/潜在 bug 等),记录耗时;
> 3. 多人重复(建议 ≥3 人)取平均,填入下表。
| 审查人 | 样例 | 耗时 | 发现问题数 | 日期 |
|---|---|---|---|---|
| _待填_ | common.js | _待填_ | _待填_ | _待填_ |
## 五、提效结论(待数据完备后填写)
_待人工基线数据填入后,据此计算提效幅度并总结结论。_
@@ -0,0 +1,19 @@
{
"tool": "code-reviewer",
"measureDate": "2026-08-26T11:16:33.653Z",
"rows": [
{
"target": "demo-eslint",
"files": 6,
"ms": 192.1965,
"diagnostics": 294
},
{
"target": "demo-stylelint",
"files": 2,
"ms": 2106.6858,
"diagnostics": 115
}
],
"totalMs": 2298.8823
}