chore: 移除 .superpowers/ 和 src/test/ 文件并加入 .gitignore
This commit is contained in:
@@ -5,3 +5,5 @@ dist/
|
||||
.vscode-test/
|
||||
.env
|
||||
*.log
|
||||
.superpowers/
|
||||
src/test/
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
import * as assert from 'assert';
|
||||
import * as vscode from 'vscode';
|
||||
import { ESLintAdapter } from '../adapters/eslint';
|
||||
import { StylelintAdapter } from '../adapters/stylelint';
|
||||
|
||||
suite('Adapter Tests', () => {
|
||||
test('ESLintAdapter has correct id and languages', () => {
|
||||
const adapter = new ESLintAdapter();
|
||||
assert.strictEqual(adapter.id, 'eslint');
|
||||
assert.deepStrictEqual(adapter.supportedLanguages, ['javascript', 'typescript']);
|
||||
});
|
||||
|
||||
test('StylelintAdapter has correct id and languages', () => {
|
||||
const adapter = new StylelintAdapter();
|
||||
assert.strictEqual(adapter.id, 'stylelint');
|
||||
assert.deepStrictEqual(adapter.supportedLanguages, ['css']);
|
||||
});
|
||||
|
||||
test('ESLintAdapter check returns AdapterResult structure', async () => {
|
||||
const adapter = new ESLintAdapter();
|
||||
if (!adapter.isAvailable()) { return; }
|
||||
|
||||
const doc = await vscode.workspace.openTextDocument({
|
||||
content: 'const x = 1;\nconsole.log(x);\n',
|
||||
language: 'javascript',
|
||||
});
|
||||
|
||||
const result = await adapter.check(doc, __dirname);
|
||||
assert.ok(result.status === 'ok' || result.status === 'tool-unavailable');
|
||||
assert.ok(Array.isArray(result.diagnostics));
|
||||
});
|
||||
});
|
||||
@@ -1,33 +0,0 @@
|
||||
import * as assert from 'assert';
|
||||
import { getAIConfig, getAIProvider, getAIModel, getAIBaseUrl, getAITemperature, getAITimeout, getAIMaxTokens, getAIOutputLanguage } from '../config/ai';
|
||||
import { getLinterForLanguage, getPMDJarPath, getPMDRulesetPath } from '../config/linter';
|
||||
import { getContextLines } from '../config/fixer';
|
||||
|
||||
suite('Config Tests', () => {
|
||||
test('getAIConfig returns default values', () => {
|
||||
const config = getAIConfig();
|
||||
assert.strictEqual(config.provider, 'deepseek');
|
||||
assert.strictEqual(config.model, 'deepseek-chat');
|
||||
assert.strictEqual(config.baseUrl, 'https://api.deepseek.com/v1');
|
||||
assert.strictEqual(config.outputLanguage, 'zh-CN');
|
||||
});
|
||||
|
||||
test('AI individual getters return defaults', () => {
|
||||
assert.strictEqual(getAIProvider(), 'deepseek');
|
||||
assert.strictEqual(getAIModel(), 'deepseek-chat');
|
||||
assert.strictEqual(getAIBaseUrl(), 'https://api.deepseek.com/v1');
|
||||
assert.strictEqual(getAITemperature(), 0.2);
|
||||
assert.strictEqual(getAITimeout(), 300);
|
||||
assert.strictEqual(getAIMaxTokens(), 8192);
|
||||
assert.strictEqual(getAIOutputLanguage(), 'zh-CN');
|
||||
});
|
||||
|
||||
test('getLinterForLanguage returns configured linter', () => {
|
||||
assert.strictEqual(getLinterForLanguage('javascript'), 'eslint');
|
||||
assert.strictEqual(getLinterForLanguage('java'), 'pmd');
|
||||
});
|
||||
|
||||
test('getContextLines returns default', () => {
|
||||
assert.strictEqual(getContextLines(), 5);
|
||||
});
|
||||
});
|
||||
@@ -1,20 +0,0 @@
|
||||
import * as assert from 'assert';
|
||||
import * as vscode from 'vscode';
|
||||
|
||||
suite('Extension Test Suite', () => {
|
||||
vscode.window.showInformationMessage('Start all tests.');
|
||||
|
||||
test('Extension registers codeReviewer commands', async () => {
|
||||
const ext = vscode.extensions.getExtension('undefined.vscode-code-reviewer')
|
||||
|| vscode.extensions.all.find(e => e.id.includes('vscode-code-reviewer'));
|
||||
if (ext && !ext.isActive) {
|
||||
await ext.activate();
|
||||
}
|
||||
|
||||
const commands = await vscode.commands.getCommands(false);
|
||||
const reviewerCommands = commands.filter(c => c.startsWith('codeReviewer.'));
|
||||
assert.ok(reviewerCommands.length > 0, 'No codeReviewer.* commands found');
|
||||
assert.ok(reviewerCommands.includes('codeReviewer.review'));
|
||||
assert.ok(reviewerCommands.includes('codeReviewer.openPanel'));
|
||||
});
|
||||
});
|
||||
Vendored
-7
@@ -1,7 +0,0 @@
|
||||
public class Sample {
|
||||
public void test() {
|
||||
String password = "admin123";
|
||||
System.out.println("debug");
|
||||
System.out.println("debug");
|
||||
}
|
||||
}
|
||||
Vendored
-2
@@ -1,2 +0,0 @@
|
||||
.hello { color: black; background: #FFF; }
|
||||
#test { margin: 0px; }
|
||||
Vendored
-5
@@ -1,5 +0,0 @@
|
||||
function test() {
|
||||
var unused = 1;
|
||||
console.log('debug');
|
||||
return "hello world";
|
||||
}
|
||||
@@ -1,345 +0,0 @@
|
||||
import * as assert from 'assert';
|
||||
import { buildFinalYaml, parseImportableYaml } from '../rules/import-service';
|
||||
import type { ImportableRule, PreviewDecision } from '../rules/import-types';
|
||||
|
||||
function makeRules(data: Array<Partial<ImportableRule>>): ImportableRule[] {
|
||||
return data.map(d => ({
|
||||
id: d.id ?? 'test-rule',
|
||||
severity: d.severity ?? 'warning',
|
||||
description: d.description ?? 'test description',
|
||||
message: d.message ?? 'test message',
|
||||
languages: d.languages,
|
||||
excludeLanguages: d.excludeLanguages,
|
||||
duplicateOf: d.duplicateOf,
|
||||
duplicateLevel: d.duplicateLevel,
|
||||
duplicateReason: d.duplicateReason,
|
||||
}));
|
||||
}
|
||||
|
||||
function defaultDecision(rules: ImportableRule[]): PreviewDecision {
|
||||
const keepRule: Record<string, boolean> = {};
|
||||
for (const rule of rules) {
|
||||
keepRule[rule.id] = rule.duplicateLevel !== 'exact';
|
||||
}
|
||||
return { keepRule, confirmed: true };
|
||||
}
|
||||
|
||||
function makeYaml(rules: Array<{ id: string; fields: Record<string, string> }>): string {
|
||||
return rules.map(r => {
|
||||
const lines = [`- id: ${r.id}`];
|
||||
for (const [key, value] of Object.entries(r.fields)) {
|
||||
lines.push(` ${key}: ${value}`);
|
||||
}
|
||||
return lines.join('\n');
|
||||
}).join('\n\n') + '\n';
|
||||
}
|
||||
|
||||
suite('Import Dedup Tests', () => {
|
||||
|
||||
test('无重复规则→全部保留,无注释行', () => {
|
||||
const rules = makeRules([
|
||||
{ id: 'rule-a', duplicateLevel: 'none' },
|
||||
{ id: 'rule-b' },
|
||||
]);
|
||||
const yaml = makeYaml([
|
||||
{ id: 'rule-a', fields: { severity: 'warning', description: 'desc a', message: 'msg a', duplicateLevel: 'none' } },
|
||||
{ id: 'rule-b', fields: { severity: 'info', description: 'desc b', message: 'msg b' } },
|
||||
]);
|
||||
const result = buildFinalYaml(yaml, rules, defaultDecision(rules));
|
||||
assert.ok(!result.includes('# [DUPLICATE'), 'Should have no duplicate headers');
|
||||
assert.ok(result.includes('id: rule-a'));
|
||||
assert.ok(result.includes('id: rule-b'));
|
||||
assert.ok(!result.includes('duplicateLevel:'), 'duplicateLevel should be stripped');
|
||||
});
|
||||
|
||||
test('全部 exact→全部注释', () => {
|
||||
const rules = makeRules([
|
||||
{ id: 'rule-a', duplicateOf: 'eslint/no-console', duplicateLevel: 'exact' },
|
||||
{ id: 'rule-b', duplicateOf: 'eslint/no-debugger', duplicateLevel: 'exact' },
|
||||
]);
|
||||
const yaml = makeYaml([
|
||||
{ id: 'rule-a', fields: { severity: 'warning', description: 'desc a', message: 'msg a', duplicateOf: 'eslint/no-console', duplicateLevel: 'exact' } },
|
||||
{ id: 'rule-b', fields: { severity: 'error', description: 'desc b', message: 'msg b', duplicateOf: 'eslint/no-debugger', duplicateLevel: 'exact' } },
|
||||
]);
|
||||
const result = buildFinalYaml(yaml, rules, defaultDecision(rules));
|
||||
assert.ok(!result.match(/^[^#]*- id:/m), 'No non-commented rule lines');
|
||||
assert.ok(result.includes('[DUPLICATE: exact]'));
|
||||
assert.ok(result.includes('# 如需启用'));
|
||||
});
|
||||
|
||||
test('全部 overlap→全部保留,无注释行', () => {
|
||||
const rules = makeRules([
|
||||
{ id: 'rule-a', duplicateOf: 'eslint/no-console', duplicateLevel: 'overlap', duplicateReason: '额外要求 logger' },
|
||||
{ id: 'rule-b', duplicateOf: 'eslint/no-unused', duplicateLevel: 'overlap', duplicateReason: '更窄范围' },
|
||||
]);
|
||||
const yaml = makeYaml([
|
||||
{ id: 'rule-a', fields: { severity: 'warning', description: 'desc a', message: 'msg a', duplicateOf: 'eslint/no-console', duplicateLevel: 'overlap', duplicateReason: '额外要求 logger' } },
|
||||
{ id: 'rule-b', fields: { severity: 'warning', description: 'desc b', message: 'msg b', duplicateOf: 'eslint/no-unused', duplicateLevel: 'overlap', duplicateReason: '更窄范围' } },
|
||||
]);
|
||||
const result = buildFinalYaml(yaml, rules, defaultDecision(rules));
|
||||
assert.ok(!result.includes('# [DUPLICATE'), 'Overlap rules should not be commented by default');
|
||||
assert.ok(result.includes('id: rule-a'));
|
||||
assert.ok(result.includes('id: rule-b'));
|
||||
});
|
||||
|
||||
test('混合三档→exact注释,overlap和none保留', () => {
|
||||
const rules = makeRules([
|
||||
{ id: 'exact-rule', duplicateOf: 'eslint/no-console', duplicateLevel: 'exact' },
|
||||
{ id: 'overlap-rule', duplicateOf: 'eslint/no-unused', duplicateLevel: 'overlap' },
|
||||
{ id: 'none-rule', duplicateLevel: 'none' },
|
||||
]);
|
||||
const yaml = makeYaml([
|
||||
{ id: 'exact-rule', fields: { severity: 'warning', description: 'd1', message: 'm1', duplicateOf: 'eslint/no-console', duplicateLevel: 'exact' } },
|
||||
{ id: 'overlap-rule', fields: { severity: 'warning', description: 'd2', message: 'm2', duplicateOf: 'eslint/no-unused', duplicateLevel: 'overlap' } },
|
||||
{ id: 'none-rule', fields: { severity: 'info', description: 'd3', message: 'm3', duplicateLevel: 'none' } },
|
||||
]);
|
||||
const result = buildFinalYaml(yaml, rules, defaultDecision(rules));
|
||||
assert.ok(result.includes('[DUPLICATE: exact]'));
|
||||
assert.ok(!result.match(/^-\s+id:\s+exact-rule/m), 'exact rule should be commented');
|
||||
assert.ok(result.match(/^-\s+id:\s+overlap-rule/m), 'overlap rule should be active');
|
||||
assert.ok(result.match(/^-\s+id:\s+none-rule/m), 'none rule should be active');
|
||||
});
|
||||
|
||||
test('用户恢复 exact 规则→取消注释', () => {
|
||||
const rules = makeRules([
|
||||
{ id: 'restored', duplicateOf: 'eslint/no-console', duplicateLevel: 'exact' },
|
||||
]);
|
||||
const yaml = makeYaml([
|
||||
{ id: 'restored', fields: { severity: 'warning', description: 'd', message: 'm', duplicateOf: 'eslint/no-console', duplicateLevel: 'exact' } },
|
||||
]);
|
||||
const decision: PreviewDecision = { keepRule: { restored: true }, confirmed: true };
|
||||
const result = buildFinalYaml(yaml, rules, decision);
|
||||
assert.ok(!result.includes('[DUPLICATE'), 'Restored rule should have no duplicate annotation');
|
||||
assert.ok(result.match(/^-\s+id:\s+restored/m), 'Restored rule should be active');
|
||||
});
|
||||
|
||||
test('用户注释 overlap 规则→加 # 前缀和注释头', () => {
|
||||
const rules = makeRules([
|
||||
{ id: 'commented', duplicateOf: 'eslint/no-console', duplicateLevel: 'overlap', duplicateReason: '额外要求' },
|
||||
]);
|
||||
const yaml = makeYaml([
|
||||
{ id: 'commented', fields: { severity: 'warning', description: 'd', message: 'm', duplicateOf: 'eslint/no-console', duplicateLevel: 'overlap', duplicateReason: '额外要求' } },
|
||||
]);
|
||||
const decision: PreviewDecision = { keepRule: { commented: false }, confirmed: true };
|
||||
const result = buildFinalYaml(yaml, rules, decision);
|
||||
assert.ok(result.includes('[DUPLICATE: overlap]'));
|
||||
assert.ok(result.includes('重叠原因:额外要求'));
|
||||
assert.ok(!result.match(/^-\s+id:\s+commented/m), 'Commented rule should have # prefix');
|
||||
});
|
||||
|
||||
test('用户注释 none 规则→加 # 前缀', () => {
|
||||
const rules = makeRules([
|
||||
{ id: 'comment-none', duplicateLevel: 'none' },
|
||||
]);
|
||||
const yaml = makeYaml([
|
||||
{ id: 'comment-none', fields: { severity: 'warning', description: 'd', message: 'm', duplicateLevel: 'none' } },
|
||||
]);
|
||||
const decision: PreviewDecision = { keepRule: { 'comment-none': false }, confirmed: true };
|
||||
const result = buildFinalYaml(yaml, rules, decision);
|
||||
assert.ok(!result.match(/^-\s+id:\s+comment-none/m), 'Should be commented');
|
||||
assert.ok(result.includes('# - id: comment-none'));
|
||||
});
|
||||
|
||||
test('保留规则→duplicateLevel/duplicateOf/duplicateReason 行被移除', () => {
|
||||
const rules = makeRules([
|
||||
{ id: 'kept', duplicateOf: 'eslint/no-console', duplicateLevel: 'overlap', duplicateReason: 'reason' },
|
||||
]);
|
||||
const yaml = makeYaml([
|
||||
{ id: 'kept', fields: { severity: 'warning', description: 'd', message: 'm', duplicateOf: 'eslint/no-console', duplicateLevel: 'overlap', duplicateReason: 'reason' } },
|
||||
]);
|
||||
const decision: PreviewDecision = { keepRule: { kept: true }, confirmed: true };
|
||||
const result = buildFinalYaml(yaml, rules, decision);
|
||||
assert.ok(!result.includes('duplicateOf:'));
|
||||
assert.ok(!result.includes('duplicateLevel:'));
|
||||
assert.ok(!result.includes('duplicateReason:'));
|
||||
assert.ok(result.includes('id: kept'));
|
||||
});
|
||||
|
||||
test('注释规则含 duplicateReason→注释头包含重叠原因', () => {
|
||||
const rules = makeRules([
|
||||
{ id: 'r1', duplicateOf: 'eslint/no-console', duplicateLevel: 'overlap', duplicateReason: '检测目标相同但额外要求 logger' },
|
||||
]);
|
||||
const yaml = makeYaml([
|
||||
{ id: 'r1', fields: { severity: 'warning', description: 'd', message: 'm', duplicateOf: 'eslint/no-console', duplicateLevel: 'overlap', duplicateReason: '检测目标相同但额外要求 logger' } },
|
||||
]);
|
||||
const decision: PreviewDecision = { keepRule: { r1: false }, confirmed: true };
|
||||
const result = buildFinalYaml(yaml, rules, decision);
|
||||
assert.ok(result.includes('重叠原因:检测目标相同但额外要求 logger'));
|
||||
});
|
||||
|
||||
test('编辑 description 后确认→YAML 使用新值', () => {
|
||||
const rules = makeRules([
|
||||
{ id: 'edit-desc', duplicateLevel: 'none' },
|
||||
]);
|
||||
const yaml = makeYaml([
|
||||
{ id: 'edit-desc', fields: { severity: 'warning', description: '旧描述', message: '旧消息' } },
|
||||
]);
|
||||
const editedRules = makeRules([
|
||||
{ id: 'edit-desc', severity: 'warning', description: '新描述', message: '新消息' },
|
||||
]);
|
||||
const decision: PreviewDecision = { keepRule: { 'edit-desc': true }, confirmed: true, editedRules };
|
||||
const result = buildFinalYaml(yaml, rules, decision);
|
||||
assert.ok(result.includes('description: 新描述'));
|
||||
assert.ok(!result.includes('旧描述'));
|
||||
assert.ok(result.includes('message: 新消息'));
|
||||
});
|
||||
|
||||
test('编辑 severity 后确认→YAML 使用新 severity', () => {
|
||||
const rules = makeRules([
|
||||
{ id: 'edit-sev', duplicateLevel: 'none' },
|
||||
]);
|
||||
const yaml = makeYaml([
|
||||
{ id: 'edit-sev', fields: { severity: 'warning', description: 'd', message: 'm' } },
|
||||
]);
|
||||
const editedRules = makeRules([
|
||||
{ id: 'edit-sev', severity: 'error', description: 'd', message: 'm' },
|
||||
]);
|
||||
const decision: PreviewDecision = { keepRule: { 'edit-sev': true }, confirmed: true, editedRules };
|
||||
const result = buildFinalYaml(yaml, rules, decision);
|
||||
assert.ok(result.includes('severity: error'));
|
||||
assert.ok(!result.includes('severity: warning'));
|
||||
});
|
||||
|
||||
test('编辑 languages 后确认→YAML 含新 languages', () => {
|
||||
const rules = makeRules([
|
||||
{ id: 'edit-lang', duplicateLevel: 'none', languages: ['java'] },
|
||||
]);
|
||||
const yaml = makeYaml([
|
||||
{ id: 'edit-lang', fields: { severity: 'warning', description: 'd', message: 'm', languages: '[java]' } },
|
||||
]);
|
||||
const editedRules = makeRules([
|
||||
{ id: 'edit-lang', severity: 'warning', description: 'd', message: 'm', languages: ['javascript', 'typescript'] },
|
||||
]);
|
||||
const decision: PreviewDecision = { keepRule: { 'edit-lang': true }, confirmed: true, editedRules };
|
||||
const result = buildFinalYaml(yaml, rules, decision);
|
||||
assert.ok(result.includes('languages: [javascript, typescript]'));
|
||||
assert.ok(!result.includes('languages: [java]'), 'old java language should be gone');
|
||||
assert.ok(!result.includes(' [java]'), 'standalone java tag should be gone');
|
||||
});
|
||||
|
||||
test('编辑后切换为注释→注释内容为编辑后的值', () => {
|
||||
const rules = makeRules([
|
||||
{ id: 'edit-comment', duplicateLevel: 'none' },
|
||||
]);
|
||||
const yaml = makeYaml([
|
||||
{ id: 'edit-comment', fields: { severity: 'warning', description: '原描述', message: '原消息' } },
|
||||
]);
|
||||
const editedRules = makeRules([
|
||||
{ id: 'edit-comment', severity: 'error', description: '新描述', message: '新消息' },
|
||||
]);
|
||||
const decision: PreviewDecision = { keepRule: { 'edit-comment': false }, confirmed: true, editedRules };
|
||||
const result = buildFinalYaml(yaml, rules, decision);
|
||||
assert.ok(result.includes('# severity: error'), 'should have commented severity: error');
|
||||
assert.ok(result.includes('# description: 新描述'));
|
||||
assert.ok(result.includes('# message: 新消息'));
|
||||
assert.ok(result.includes('[手动注释]'));
|
||||
});
|
||||
|
||||
test('无编辑场景→回退到原始 yamlContent 处理', () => {
|
||||
const rules = makeRules([
|
||||
{ id: 'fallback', duplicateLevel: 'exact', duplicateOf: 'eslint/no-console' },
|
||||
]);
|
||||
const yaml = makeYaml([
|
||||
{ id: 'fallback', fields: { severity: 'warning', description: 'd', message: 'm', duplicateOf: 'eslint/no-console', duplicateLevel: 'exact' } },
|
||||
]);
|
||||
const decision: PreviewDecision = { keepRule: { fallback: false }, confirmed: true };
|
||||
const result = buildFinalYaml(yaml, rules, decision);
|
||||
assert.ok(result.includes('[DUPLICATE: exact]'));
|
||||
assert.ok(!result.match(/^-\s+id:\s+fallback/m));
|
||||
assert.ok(result.includes('duplicateOf:'));
|
||||
});
|
||||
});
|
||||
|
||||
suite('parseImportableYaml Fallback Tests', () => {
|
||||
|
||||
test('severity 缺失→降级为 warning', () => {
|
||||
const yaml = `- id: test-rule
|
||||
description: test desc
|
||||
message: test msg`;
|
||||
const rules = parseImportableYaml(yaml);
|
||||
assert.strictEqual(rules.length, 1);
|
||||
assert.strictEqual(rules[0].severity, 'warning');
|
||||
});
|
||||
|
||||
test('severity 非法值→降级为 warning', () => {
|
||||
const yaml = `- id: test-rule
|
||||
severity: critical
|
||||
description: test desc
|
||||
message: test msg`;
|
||||
const rules = parseImportableYaml(yaml);
|
||||
assert.strictEqual(rules.length, 1);
|
||||
assert.strictEqual(rules[0].severity, 'warning');
|
||||
});
|
||||
|
||||
test('id 缺失→生成 rule-N', () => {
|
||||
const yaml = `- severity: warning
|
||||
description: test desc
|
||||
message: test msg`;
|
||||
const rules = parseImportableYaml(yaml);
|
||||
assert.strictEqual(rules.length, 1);
|
||||
assert.strictEqual(rules[0].id, 'rule-1');
|
||||
});
|
||||
|
||||
test('多条 id 缺失→rule-1, rule-2...', () => {
|
||||
const yaml = `- severity: warning
|
||||
description: desc a
|
||||
message: msg a
|
||||
- severity: info
|
||||
description: desc b
|
||||
message: msg b`;
|
||||
const rules = parseImportableYaml(yaml);
|
||||
assert.strictEqual(rules.length, 2);
|
||||
assert.strictEqual(rules[0].id, 'rule-1');
|
||||
assert.strictEqual(rules[1].id, 'rule-2');
|
||||
});
|
||||
|
||||
test('description 缺失、message 存在→互填', () => {
|
||||
const yaml = `- id: test-rule
|
||||
severity: error
|
||||
message: test msg`;
|
||||
const rules = parseImportableYaml(yaml);
|
||||
assert.strictEqual(rules.length, 1);
|
||||
assert.strictEqual(rules[0].description, 'test msg');
|
||||
assert.strictEqual(rules[0].message, 'test msg');
|
||||
});
|
||||
|
||||
test('message 缺失、description 存在→互填', () => {
|
||||
const yaml = `- id: test-rule
|
||||
severity: error
|
||||
description: test desc`;
|
||||
const rules = parseImportableYaml(yaml);
|
||||
assert.strictEqual(rules.length, 1);
|
||||
assert.strictEqual(rules[0].description, 'test desc');
|
||||
assert.strictEqual(rules[0].message, 'test desc');
|
||||
});
|
||||
|
||||
test('description 与 message 同时缺失→丢弃', () => {
|
||||
const yaml = `- id: test-rule
|
||||
severity: error
|
||||
- id: test-rule2
|
||||
severity: warning
|
||||
description: test desc
|
||||
message: test msg`;
|
||||
const rules = parseImportableYaml(yaml);
|
||||
assert.strictEqual(rules.length, 1);
|
||||
assert.strictEqual(rules[0].id, 'test-rule2');
|
||||
});
|
||||
|
||||
test('正常完整输入→无回归', () => {
|
||||
const yaml = `- id: no-console-log
|
||||
severity: error
|
||||
description: 禁止使用 console.log
|
||||
message: 请使用 logger 替代
|
||||
- id: no-unused-vars
|
||||
severity: warning
|
||||
description: 禁止未使用变量
|
||||
message: 删除或注释未使用变量
|
||||
duplicateOf: eslint/no-unused-vars
|
||||
duplicateLevel: exact`;
|
||||
const rules = parseImportableYaml(yaml);
|
||||
assert.strictEqual(rules.length, 2);
|
||||
assert.strictEqual(rules[0].severity, 'error');
|
||||
assert.strictEqual(rules[0].description, '禁止使用 console.log');
|
||||
assert.strictEqual(rules[1].duplicateLevel, 'exact');
|
||||
});
|
||||
});
|
||||
@@ -1,16 +0,0 @@
|
||||
public class Buggy {
|
||||
public void test() {
|
||||
String password = "admin123";
|
||||
String name = "test";
|
||||
int x = 1;
|
||||
int y = 2;
|
||||
int z = x + y;
|
||||
System.out.println("debug");
|
||||
System.out.println("done");
|
||||
}
|
||||
|
||||
public void duplicate() {
|
||||
String password = "secret";
|
||||
System.out.println(password);
|
||||
}
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
.hello { color: #FFFFFF; background: black; }
|
||||
#test { margin: 0px; }
|
||||
.foo { font-size: 12px; }
|
||||
@@ -1,11 +0,0 @@
|
||||
var x = 1;
|
||||
var y = 2;
|
||||
var x = 3;
|
||||
|
||||
function test() {
|
||||
var unused = 'hello';
|
||||
console.log('debug');
|
||||
return "world";
|
||||
}
|
||||
|
||||
test();
|
||||
@@ -1,42 +0,0 @@
|
||||
<%@ page language="java" contentType="text/html" %>
|
||||
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
|
||||
<html>
|
||||
<head>
|
||||
<title>${title}</title>
|
||||
<style>
|
||||
.btn { color: #fff; background: blue; }
|
||||
.btn { font-size: 14px; }
|
||||
body { color: #fff; }
|
||||
body { margin: 0; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<%
|
||||
String password = "admin123";
|
||||
String name = "test";
|
||||
int x = 1;
|
||||
%>
|
||||
|
||||
<p>${message} ${message}</p>
|
||||
|
||||
<c:if test="${not empty user}">
|
||||
<p>Welcome, ${user.name}</p>
|
||||
</c:if>
|
||||
|
||||
<c:forEach items="${list}" var="item">
|
||||
<span>${item}</span>
|
||||
</c:forEach>
|
||||
|
||||
<c:out value="${xssInput}" />
|
||||
|
||||
<c:forEach begin="1" end="5">
|
||||
<span>hello</span>
|
||||
</c:forEach>
|
||||
|
||||
<script>
|
||||
var msg = "hello";
|
||||
var msg = "world";
|
||||
console.log(msg);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,3 +0,0 @@
|
||||
SELECT name FORM users;
|
||||
SELECT * FORM products;
|
||||
INSERT INTO customers VALUES (1, 'test');
|
||||
@@ -1,55 +0,0 @@
|
||||
import * as assert from 'assert';
|
||||
import { mergeResults, MergedReport } from '../merger/merger';
|
||||
import { CustomRuleResult, AIFinding } from '../ai/schema';
|
||||
import { LinterDiagnostic } from '../types';
|
||||
|
||||
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);
|
||||
});
|
||||
});
|
||||
@@ -1,72 +0,0 @@
|
||||
import * as assert from 'assert';
|
||||
import { t, setLanguage, getLanguage, getMessageKeys, Language } from '../i18n/messages';
|
||||
|
||||
suite('I18n Tests', () => {
|
||||
test('all message keys have values for all three languages', () => {
|
||||
const languages: Language[] = ['zh-CN', 'en', 'ja'];
|
||||
const keys = getMessageKeys();
|
||||
|
||||
for (const lang of languages) {
|
||||
setLanguage(lang);
|
||||
for (const key of keys) {
|
||||
const result = t(key);
|
||||
assert.ok(result, `Key "${key}" is empty for language "${lang}"`);
|
||||
assert.notStrictEqual(result, key, `Key "${key}" has no translation for language "${lang}"`);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
test('default language is zh-CN', () => {
|
||||
setLanguage('zh-CN');
|
||||
assert.strictEqual(getLanguage(), 'zh-CN');
|
||||
});
|
||||
|
||||
test('setLanguage changes current language', () => {
|
||||
setLanguage('en');
|
||||
assert.strictEqual(getLanguage(), 'en');
|
||||
setLanguage('ja');
|
||||
assert.strictEqual(getLanguage(), 'ja');
|
||||
setLanguage('zh-CN');
|
||||
});
|
||||
|
||||
test('t() falls back to key when key does not exist', () => {
|
||||
setLanguage('zh-CN');
|
||||
const result = t('nonexistent.key');
|
||||
assert.strictEqual(result, 'nonexistent.key');
|
||||
});
|
||||
|
||||
test('t() returns zh-CN string in zh-CN language', () => {
|
||||
setLanguage('zh-CN');
|
||||
assert.strictEqual(t('review.noEditor'), '请先打开一个文件');
|
||||
});
|
||||
|
||||
test('t() returns English string in en language', () => {
|
||||
setLanguage('en');
|
||||
assert.strictEqual(t('review.noEditor'), 'Please open a file first');
|
||||
});
|
||||
|
||||
test('t() returns Japanese string in ja language', () => {
|
||||
setLanguage('ja');
|
||||
assert.strictEqual(t('review.noEditor'), '最初にファイルを開いてください');
|
||||
});
|
||||
|
||||
test('t() with template variables', () => {
|
||||
setLanguage('en');
|
||||
assert.strictEqual(
|
||||
t('export.saved', { 0: '/home/user/report.md' }),
|
||||
'Report saved to /home/user/report.md'
|
||||
);
|
||||
});
|
||||
|
||||
test('t() with multiple template variables', () => {
|
||||
setLanguage('zh-CN');
|
||||
assert.strictEqual(
|
||||
t('report.totalSummary', { 0: '10', 1: '3', 2: '5', 3: '2' }),
|
||||
'总计: 10 | 错误: 3 | 警告: 5 | 建议: 2'
|
||||
);
|
||||
});
|
||||
|
||||
teardown(() => {
|
||||
setLanguage('zh-CN');
|
||||
});
|
||||
});
|
||||
@@ -1,37 +0,0 @@
|
||||
import * as assert from 'assert';
|
||||
import * as vscode from 'vscode';
|
||||
import * as path from 'path';
|
||||
import { ESLintAdapter } from '../adapters/eslint';
|
||||
import { mergeResults } from '../merger/merger';
|
||||
|
||||
suite('Pipeline Tests', () => {
|
||||
test('Full pipeline: linter check + merge', async () => {
|
||||
const adapter = new ESLintAdapter();
|
||||
if (!adapter.isAvailable()) { return; }
|
||||
|
||||
const doc = await vscode.workspace.openTextDocument({
|
||||
content: 'var x = 1;\nvar y = 2;\n',
|
||||
language: 'javascript',
|
||||
});
|
||||
|
||||
const staticResult = await adapter.check(doc, __dirname);
|
||||
assert.ok(staticResult.status === 'ok');
|
||||
|
||||
const report = mergeResults({
|
||||
staticDiagnostics: staticResult.diagnostics,
|
||||
customRuleResults: [],
|
||||
translatedDiagnostics: [],
|
||||
aiFindings: [],
|
||||
errors: [],
|
||||
degraded: false,
|
||||
startTime: Date.now(),
|
||||
filePath: 'virtual-doc',
|
||||
language: 'javascript',
|
||||
adapterIds: ['eslint'],
|
||||
});
|
||||
|
||||
assert.ok(typeof report.duration === 'number');
|
||||
assert.ok(typeof report.linterCount === 'number');
|
||||
assert.strictEqual(report.language, 'javascript');
|
||||
});
|
||||
});
|
||||
@@ -1,145 +0,0 @@
|
||||
import * as assert from 'assert';
|
||||
import { filterForDocument, filterAndSummarize } from '../rules/rule-filter';
|
||||
import type { CustomRule } from '../types';
|
||||
|
||||
function mockDoc(languageId: string, fileName: string): { languageId: string; fileName: string } {
|
||||
return { languageId, fileName };
|
||||
}
|
||||
|
||||
const baseRules: CustomRule[] = [
|
||||
{ id: 'java-rule', severity: 'error', description: '', message: '', languages: ['java'] },
|
||||
{ id: 'js-rule', severity: 'error', description: '', message: '', languages: ['javascript'] },
|
||||
{ id: 'ts-rule', severity: 'error', description: '', message: '', languages: ['typescript'] },
|
||||
{ id: 'css-rule', severity: 'error', description: '', message: '', languages: ['css'] },
|
||||
{ id: 'universal', severity: 'warning', description: '', message: '' },
|
||||
{ id: 'no-css', severity: 'info', description: '', message: '', excludeLanguages: ['css'] },
|
||||
{ id: 'empty-langs', severity: 'info', description: '', message: '', languages: [] },
|
||||
];
|
||||
|
||||
suite('Rule Filter Tests', () => {
|
||||
|
||||
test('白名单命中', () => {
|
||||
const doc = mockDoc('java', '/test/Foo.java');
|
||||
const result = filterForDocument(baseRules, doc as any);
|
||||
const ids = result.map(r => r.id);
|
||||
assert.ok(ids.includes('java-rule'));
|
||||
assert.ok(ids.includes('universal'));
|
||||
});
|
||||
|
||||
test('白名单未命中', () => {
|
||||
const doc = mockDoc('java', '/test/Foo.java');
|
||||
const result = filterForDocument(baseRules, doc as any);
|
||||
const ids = result.map(r => r.id);
|
||||
assert.ok(!ids.includes('js-rule'));
|
||||
assert.ok(!ids.includes('css-rule'));
|
||||
});
|
||||
|
||||
test('白名单为空→全语言保留', () => {
|
||||
const doc = mockDoc('css', '/test/test.css');
|
||||
const result = filterForDocument(baseRules, doc as any);
|
||||
const ids = result.map(r => r.id);
|
||||
assert.ok(ids.includes('universal'));
|
||||
assert.ok(ids.includes('empty-langs'));
|
||||
});
|
||||
|
||||
test('白名单缺失→全语言保留', () => {
|
||||
const doc = mockDoc('css', '/test/test.css');
|
||||
const result = filterForDocument(baseRules, doc as any);
|
||||
const ids = result.map(r => r.id);
|
||||
assert.ok(ids.includes('universal'));
|
||||
});
|
||||
|
||||
test('黑名单命中→剔除', () => {
|
||||
const doc = mockDoc('css', '/test/test.css');
|
||||
const result = filterForDocument(baseRules, doc as any);
|
||||
const ids = result.map(r => r.id);
|
||||
assert.ok(!ids.includes('no-css'));
|
||||
});
|
||||
|
||||
test('黑名单未命中→保留', () => {
|
||||
const doc = mockDoc('java', '/test/Foo.java');
|
||||
const result = filterForDocument(baseRules, doc as any);
|
||||
const ids = result.map(r => r.id);
|
||||
assert.ok(ids.includes('no-css'));
|
||||
});
|
||||
|
||||
test('黑名单为空→保留', () => {
|
||||
const rules: CustomRule[] = [
|
||||
{ id: 'r1', severity: 'info', description: '', message: '', excludeLanguages: [] },
|
||||
];
|
||||
const doc = mockDoc('css', '/test/test.css');
|
||||
const result = filterForDocument(rules, doc as any);
|
||||
assert.strictEqual(result.length, 1);
|
||||
});
|
||||
|
||||
test('白名单+黑名单交集→黑名单胜出剔除', () => {
|
||||
const rules: CustomRule[] = [
|
||||
{ id: 'r1', severity: 'info', description: '', message: '', languages: ['java'], excludeLanguages: ['java'] },
|
||||
];
|
||||
const doc = mockDoc('java', '/test/Foo.java');
|
||||
const result = filterForDocument(rules, doc as any);
|
||||
assert.strictEqual(result.length, 0);
|
||||
});
|
||||
|
||||
test('typescriptreact 别名→命中 typescript 规则', () => {
|
||||
const doc = mockDoc('typescriptreact', '/test/App.tsx');
|
||||
const result = filterForDocument(baseRules, doc as any);
|
||||
const ids = result.map(r => r.id);
|
||||
assert.ok(ids.includes('ts-rule'));
|
||||
});
|
||||
|
||||
test('plsql 同组→命中 sql 规则', () => {
|
||||
const rules: CustomRule[] = [
|
||||
{ id: 'sql-rule', severity: 'error', description: '', message: '', languages: ['sql'] },
|
||||
{ id: 'plsql-rule', severity: 'error', description: '', message: '', languages: ['plsql'] },
|
||||
];
|
||||
const doc = mockDoc('plsql', '/test/test.plsql');
|
||||
const result = filterForDocument(rules, doc as any);
|
||||
const ids = result.map(r => r.id);
|
||||
assert.ok(ids.includes('sql-rule'));
|
||||
assert.ok(ids.includes('plsql-rule'));
|
||||
});
|
||||
|
||||
test('JSP 并集→保留 java 规则', () => {
|
||||
const doc = mockDoc('html', '/test/test.jsp');
|
||||
const result = filterForDocument(baseRules, doc as any);
|
||||
const ids = result.map(r => r.id);
|
||||
assert.ok(ids.includes('java-rule'));
|
||||
assert.ok(ids.includes('js-rule'));
|
||||
assert.ok(ids.includes('ts-rule'));
|
||||
assert.ok(ids.includes('css-rule'));
|
||||
});
|
||||
|
||||
test('JSP 并集→保留 css 规则', () => {
|
||||
const doc = mockDoc('html', '/test/test.jspx');
|
||||
const result = filterForDocument(baseRules, doc as any);
|
||||
const ids = result.map(r => r.id);
|
||||
assert.ok(ids.includes('css-rule'));
|
||||
});
|
||||
|
||||
test('普通HTML不触发JSP→剔除java规则', () => {
|
||||
const doc = mockDoc('html', '/test/index.html');
|
||||
const result = filterForDocument(baseRules, doc as any);
|
||||
const ids = result.map(r => r.id);
|
||||
assert.ok(!ids.includes('java-rule'));
|
||||
});
|
||||
|
||||
test('全部过滤→skippedRequestA=true', () => {
|
||||
const rules: CustomRule[] = [
|
||||
{ id: 'java-rule', severity: 'error', description: '', message: '', languages: ['java'] },
|
||||
];
|
||||
const doc = mockDoc('css', '/test/test.css');
|
||||
const result = filterAndSummarize(rules, doc as any);
|
||||
assert.strictEqual(result.relevant.length, 0);
|
||||
assert.strictEqual(result.filteredOut.length, 1);
|
||||
assert.strictEqual(result.skippedRequestA, true);
|
||||
});
|
||||
|
||||
test('部分过滤→skippedRequestA=false', () => {
|
||||
const doc = mockDoc('java', '/test/Foo.java');
|
||||
const result = filterAndSummarize(baseRules, doc as any);
|
||||
assert.ok(result.relevant.length > 0);
|
||||
assert.ok(result.filteredOut.length > 0);
|
||||
assert.strictEqual(result.skippedRequestA, false);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user