- 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 标注演示视频进行中
335 lines
12 KiB
TypeScript
335 lines
12 KiB
TypeScript
import * as assert from 'assert';
|
|
import { mergeResults, MergedReport } from '../src/merger/merger';
|
|
import { CustomRuleResult, AIFinding } from '../src/ai/schema';
|
|
import { LinterDiagnostic } from '../src/types';
|
|
|
|
function range(line: number): any {
|
|
return new (require('vscode').Range)(line, 0, line, 1);
|
|
}
|
|
|
|
suite('Merger Tests', () => {
|
|
test('mergeResults counts correctly', () => {
|
|
const staticDiags: LinterDiagnostic[] = [
|
|
{ severity: 'error', ruleId: 'eslint:no-unused', message: 'x is unused', range: new (require('vscode').Range)(0, 0, 0, 1) },
|
|
];
|
|
const customResults: CustomRuleResult[] = [
|
|
{ ruleId: 'custom:no-console', line: 5, severity: 'warning', message: 'avoid console.log' },
|
|
];
|
|
const aiFindings: AIFinding[] = [
|
|
{ ruleId: 'hardcoded-secret', severity: 'error', category: 'security', title: 'Hardcoded', description: 'Found secret', suggestion: 'Use env', line: 3 },
|
|
];
|
|
|
|
const report = mergeResults({
|
|
staticDiagnostics: staticDiags,
|
|
customRuleResults: customResults,
|
|
translatedDiagnostics: [],
|
|
aiFindings,
|
|
errors: [],
|
|
degraded: false,
|
|
startTime: Date.now(),
|
|
filePath: '/test/sample.js',
|
|
language: 'javascript',
|
|
adapterIds: ['eslint'],
|
|
});
|
|
|
|
assert.strictEqual(report.linterCount, 1);
|
|
assert.strictEqual(report.customRuleCount, 1);
|
|
assert.strictEqual(report.aiCount, 1);
|
|
assert.strictEqual(report.degraded, false);
|
|
assert.strictEqual(report.language, 'javascript');
|
|
});
|
|
|
|
test('mergeResults marks degraded when AI fails', () => {
|
|
const report = mergeResults({
|
|
staticDiagnostics: [],
|
|
customRuleResults: [],
|
|
translatedDiagnostics: [],
|
|
aiFindings: [],
|
|
errors: ['AI 请求超时'],
|
|
degraded: true,
|
|
startTime: Date.now(),
|
|
filePath: '/test/sample.js',
|
|
language: 'javascript',
|
|
adapterIds: ['eslint'],
|
|
});
|
|
|
|
assert.strictEqual(report.degraded, true);
|
|
assert.strictEqual(report.errors.length, 1);
|
|
});
|
|
|
|
test('mergeResults sorts diagnostics by severity then line', () => {
|
|
const staticDiags: LinterDiagnostic[] = [
|
|
{ severity: 'info', ruleId: 'eslint:info-5', message: 'i5', range: range(5) },
|
|
{ severity: 'error', ruleId: 'eslint:err-10', message: 'e10', range: range(10) },
|
|
{ severity: 'warning', ruleId: 'eslint:warn-3', message: 'w3', range: range(3) },
|
|
{ severity: 'error', ruleId: 'eslint:err-1', message: 'e1', range: range(1) },
|
|
];
|
|
const customResults: CustomRuleResult[] = [
|
|
{ ruleId: 'custom:i-9', line: 9, severity: 'info', message: 'i9' },
|
|
{ ruleId: 'custom:e-2', line: 2, severity: 'error', message: 'e2' },
|
|
];
|
|
const aiFindings: AIFinding[] = [
|
|
{ ruleId: 'ai:w-7', severity: 'warning', category: 'style', title: 'w7', description: 'w7', line: 7, suggestion: '' },
|
|
{ ruleId: 'ai:e-4', severity: 'error', category: 'bug', title: 'e4', description: 'e4', line: 4, suggestion: '' },
|
|
];
|
|
|
|
const report = mergeResults({
|
|
staticDiagnostics: staticDiags,
|
|
customRuleResults: customResults,
|
|
translatedDiagnostics: [],
|
|
aiFindings,
|
|
errors: [],
|
|
degraded: false,
|
|
startTime: Date.now(),
|
|
filePath: '/test/sample.js',
|
|
language: 'javascript',
|
|
adapterIds: ['eslint'],
|
|
});
|
|
|
|
assert.deepStrictEqual(
|
|
report.linterDiagnostics.map(d => `${d.severity}:${d.range.start.line}`),
|
|
['error:1', 'error:10', 'warning:3', 'info:5']
|
|
);
|
|
assert.deepStrictEqual(
|
|
report.customRuleDiagnostics.map(d => `${d.severity}:${d.range.start.line}`),
|
|
['error:1', 'info:8']
|
|
);
|
|
assert.deepStrictEqual(
|
|
report.aiFindings.map(f => `${f.severity}:${f.line}`),
|
|
['error:3', 'warning:6']
|
|
);
|
|
assert.deepStrictEqual(report.fixableLinterIndices, []);
|
|
assert.deepStrictEqual(report.fixableCustomIndices, []);
|
|
});
|
|
|
|
test('mergeResults marks fixable only diagnostics with fix object', () => {
|
|
const staticDiags: LinterDiagnostic[] = [
|
|
{ severity: 'error', ruleId: 'eslint:fixable-1', message: 'f1', range: range(1), fix: { range: [0, 5], text: 'x' } },
|
|
{ severity: 'warning', ruleId: 'eslint:nofix-2', message: 'n2', range: range(2) },
|
|
{ severity: 'error', ruleId: 'eslint:fixable-3', message: 'f3', range: range(3), fix: { range: [6, 9], text: 'y' } },
|
|
];
|
|
|
|
const report = mergeResults({
|
|
staticDiagnostics: staticDiags,
|
|
customRuleResults: [],
|
|
translatedDiagnostics: [],
|
|
aiFindings: [],
|
|
errors: [],
|
|
degraded: false,
|
|
startTime: Date.now(),
|
|
filePath: '/test/sample.js',
|
|
language: 'javascript',
|
|
adapterIds: ['eslint'],
|
|
});
|
|
|
|
const sorted = report.linterDiagnostics;
|
|
const fixableIndices = sorted.map((_, i) => (sorted[i].fix ? i : -1)).filter(i => i !== -1);
|
|
assert.deepStrictEqual(report.fixableLinterIndices, fixableIndices);
|
|
assert.strictEqual(report.fixableCustomIndices.length, 0);
|
|
});
|
|
|
|
test('mergeResults converts aiFindings line to 0-based', () => {
|
|
const aiFindings: AIFinding[] = [
|
|
{ ruleId: 'ai:first', severity: 'warning', category: 'style', title: 'first', description: 'd', line: 1, suggestion: '' },
|
|
{ ruleId: 'ai:last', severity: 'info', category: 'design', title: 'last', description: 'd', line: 8, suggestion: '' },
|
|
];
|
|
|
|
const report = mergeResults({
|
|
staticDiagnostics: [],
|
|
customRuleResults: [],
|
|
translatedDiagnostics: [],
|
|
aiFindings,
|
|
errors: [],
|
|
degraded: false,
|
|
startTime: Date.now(),
|
|
filePath: '/test/sample.js',
|
|
language: 'javascript',
|
|
adapterIds: ['eslint'],
|
|
});
|
|
|
|
assert.deepStrictEqual(
|
|
report.aiFindings.map(f => f.line),
|
|
[0, 7]
|
|
);
|
|
});
|
|
|
|
test('mergeResults pairs translations by originalRuleId not index', () => {
|
|
const staticDiags: LinterDiagnostic[] = [
|
|
{ severity: 'error', ruleId: 'eslint:no-unused', message: 'x is unused', range: range(1) },
|
|
{ severity: 'warning', ruleId: 'eslint:no-console', message: 'console call', range: range(2) },
|
|
{ severity: 'error', ruleId: 'eslint:no-eval', message: 'eval used', range: range(3) },
|
|
];
|
|
|
|
const report = mergeResults({
|
|
staticDiagnostics: staticDiags,
|
|
customRuleResults: [],
|
|
translatedDiagnostics: [
|
|
{ originalRuleId: 'eslint:no-eval', translatedMessage: '评估使用', translatedSuggestion: '避免' },
|
|
{ originalRuleId: 'eslint:no-unused', translatedMessage: '未使用', translatedSuggestion: '删除' },
|
|
],
|
|
aiFindings: [],
|
|
errors: [],
|
|
degraded: false,
|
|
startTime: Date.now(),
|
|
filePath: '/test/sample.js',
|
|
language: 'javascript',
|
|
adapterIds: ['eslint'],
|
|
});
|
|
|
|
assert.strictEqual(report.linterDiagnostics[0].message, '未使用');
|
|
assert.strictEqual(report.linterDiagnostics[0].suggestion, '删除');
|
|
assert.strictEqual(report.linterDiagnostics[1].message, '评估使用');
|
|
assert.strictEqual(report.linterDiagnostics[2].message, 'console call');
|
|
});
|
|
|
|
test('mergeResults handles duplicate ruleId translations in order', () => {
|
|
const staticDiags: LinterDiagnostic[] = [
|
|
{ severity: 'error', ruleId: 'eslint:no-console', message: 'first console', range: range(1) },
|
|
{ severity: 'error', ruleId: 'eslint:no-console', message: 'second console', range: range(2) },
|
|
];
|
|
|
|
const report = mergeResults({
|
|
staticDiagnostics: staticDiags,
|
|
customRuleResults: [],
|
|
translatedDiagnostics: [
|
|
{ originalRuleId: 'eslint:no-console', translatedMessage: '首次', translatedSuggestion: '' },
|
|
{ originalRuleId: 'eslint:no-console', translatedMessage: '二次', translatedSuggestion: '' },
|
|
],
|
|
aiFindings: [],
|
|
errors: [],
|
|
degraded: false,
|
|
startTime: Date.now(),
|
|
filePath: '/test/sample.js',
|
|
language: 'javascript',
|
|
adapterIds: ['eslint'],
|
|
});
|
|
|
|
assert.strictEqual(report.linterDiagnostics[0].message, '首次');
|
|
assert.strictEqual(report.linterDiagnostics[1].message, '二次');
|
|
});
|
|
|
|
test('mergeResults matches translations by normalized ruleId when prefix missing', () => {
|
|
const staticDiags: LinterDiagnostic[] = [
|
|
{ severity: 'error', ruleId: 'eslint:no-var', message: 'Unexpected var', range: range(1) },
|
|
{ severity: 'warning', ruleId: 'eslint:no-console', message: 'console call', range: range(2) },
|
|
];
|
|
|
|
const report = mergeResults({
|
|
staticDiagnostics: staticDiags,
|
|
customRuleResults: [],
|
|
translatedDiagnostics: [
|
|
{ originalRuleId: 'no-var', translatedMessage: '应使用 let/const', translatedSuggestion: '改写成 let x = 1' },
|
|
{ originalRuleId: 'no-console', translatedMessage: '避免 console', translatedSuggestion: '' },
|
|
],
|
|
aiFindings: [],
|
|
errors: [],
|
|
degraded: false,
|
|
startTime: Date.now(),
|
|
filePath: '/test/sample.js',
|
|
language: 'javascript',
|
|
adapterIds: ['eslint'],
|
|
});
|
|
|
|
assert.strictEqual(report.linterDiagnostics[0].message, '应使用 let/const');
|
|
assert.strictEqual(report.linterDiagnostics[0].suggestion, '改写成 let x = 1');
|
|
assert.strictEqual(report.linterDiagnostics[1].message, '避免 console');
|
|
});
|
|
|
|
test('mergeResults matches translations by normalized ruleId with slash variant', () => {
|
|
const staticDiags: LinterDiagnostic[] = [
|
|
{ severity: 'error', ruleId: 'eslint:@typescript-eslint/no-unused-vars', message: 'x unused', range: range(1) },
|
|
];
|
|
|
|
const report = mergeResults({
|
|
staticDiagnostics: staticDiags,
|
|
customRuleResults: [],
|
|
translatedDiagnostics: [
|
|
{ originalRuleId: '@typescript-eslint/no-unused-vars', translatedMessage: '未使用变量', translatedSuggestion: '删除 x' },
|
|
],
|
|
aiFindings: [],
|
|
errors: [],
|
|
degraded: false,
|
|
startTime: Date.now(),
|
|
filePath: '/test/sample.ts',
|
|
language: 'typescript',
|
|
adapterIds: ['eslint'],
|
|
});
|
|
|
|
assert.strictEqual(report.linterDiagnostics[0].message, '未使用变量');
|
|
assert.strictEqual(report.linterDiagnostics[0].suggestion, '删除 x');
|
|
});
|
|
|
|
test('mergeResults prefers exact ruleId match over normalized', () => {
|
|
const staticDiags: LinterDiagnostic[] = [
|
|
{ severity: 'error', ruleId: 'eslint:no-shadow', message: 'shadow', range: range(1) },
|
|
];
|
|
|
|
const report = mergeResults({
|
|
staticDiagnostics: staticDiags,
|
|
customRuleResults: [],
|
|
translatedDiagnostics: [
|
|
{ originalRuleId: 'no-shadow', translatedMessage: '归一化命中', translatedSuggestion: '' },
|
|
{ originalRuleId: 'eslint:no-shadow', translatedMessage: '精确命中', translatedSuggestion: '' },
|
|
],
|
|
aiFindings: [],
|
|
errors: [],
|
|
degraded: false,
|
|
startTime: Date.now(),
|
|
filePath: '/test/sample.js',
|
|
language: 'javascript',
|
|
adapterIds: ['eslint'],
|
|
});
|
|
|
|
assert.strictEqual(report.linterDiagnostics[0].message, '精确命中');
|
|
});
|
|
|
|
test('mergeResults computes aiFixableLinterIndices excluding native fix and sqlfluff', () => {
|
|
const staticDiags: LinterDiagnostic[] = [
|
|
{ severity: 'error', ruleId: 'eslint:no-var', message: 'v', range: range(1), fix: { range: [0, 3], text: 'let' } },
|
|
{ severity: 'error', ruleId: 'eslint:no-undef', message: 'u', range: range(2) },
|
|
{ severity: 'error', ruleId: 'sqlfluff:AL01', message: 's', range: range(3) },
|
|
{ severity: 'error', ruleId: 'pmd:UnusedLocalVariable', message: 'p', range: range(4) },
|
|
];
|
|
|
|
const report = mergeResults({
|
|
staticDiagnostics: staticDiags,
|
|
customRuleResults: [],
|
|
translatedDiagnostics: [],
|
|
aiFindings: [],
|
|
errors: [],
|
|
degraded: false,
|
|
startTime: Date.now(),
|
|
filePath: '/test/a.java',
|
|
language: 'java',
|
|
adapterIds: ['pmd'],
|
|
aiFixAvailable: true,
|
|
});
|
|
|
|
assert.strictEqual(report.aiFixAvailable, true);
|
|
assert.deepStrictEqual(report.fixableLinterIndices, [0]);
|
|
assert.deepStrictEqual(report.aiFixableLinterIndices, [1, 3]);
|
|
});
|
|
|
|
test('mergeResults aiFixableLinterIndices empty when AI unavailable', () => {
|
|
const staticDiags: LinterDiagnostic[] = [
|
|
{ severity: 'error', ruleId: 'eslint:no-undef', message: 'u', range: range(2) },
|
|
];
|
|
|
|
const report = mergeResults({
|
|
staticDiagnostics: staticDiags,
|
|
customRuleResults: [],
|
|
translatedDiagnostics: [],
|
|
aiFindings: [],
|
|
errors: [],
|
|
degraded: false,
|
|
startTime: Date.now(),
|
|
filePath: '/test/sample.js',
|
|
language: 'javascript',
|
|
adapterIds: ['eslint'],
|
|
});
|
|
|
|
assert.deepStrictEqual(report.aiFixableLinterIndices, []);
|
|
assert.strictEqual(report.aiFixAvailable, false);
|
|
});
|
|
});
|