feat: 规则转换器/导入/过滤/i18n 基础设施

- converters: 8 个转换器(markdown/txt/excel/docx/pptx/yaml + prompt-builder + converter 接口)
- import: import-preview/import-service/import-types
- rule-filter: 规则预过滤功能
- static-rules.json: 静态规则数据
- i18n/messages.ts: 国际化消息
- types/mammoth.d.ts: mammoth 类型声明
This commit is contained in:
范智鹏
2026-07-26 23:04:13 +08:00
parent 6d29f304e3
commit 83712aa260
16 changed files with 4396 additions and 0 deletions
+7
View File
@@ -0,0 +1,7 @@
import * as vscode from 'vscode';
import type { CustomRule } from '../../types';
export interface RuleConverter {
supportedExtensions: string[];
convert(srcPath: string, context: vscode.ExtensionContext, existingRules?: CustomRule[]): Promise<string | null>;
}
+30
View File
@@ -0,0 +1,30 @@
import * as vscode from 'vscode';
import * as mammoth from 'mammoth';
import { RuleConverter } from './converter';
import { convertContentWithAI } from '../import-service';
import { buildSystemPrompt } from './prompt-builder';
import type { CustomRule } from '../../types';
import { t } from '../../i18n/messages';
export class DocxConverter implements RuleConverter {
supportedExtensions = ['.docx'];
async convert(srcPath: string, context: vscode.ExtensionContext, existingRules?: CustomRule[]): Promise<string | null> {
let result: mammoth.Result;
try {
result = await mammoth.extractRawText({ path: srcPath });
} catch (err) {
const msg = err instanceof Error ? err.message : String(err);
vscode.window.showErrorMessage(t('import.docxReadFail', { 0: msg }));
return null;
}
const content = result.value.trim();
if (!content) {
vscode.window.showErrorMessage(t('import.docxEmpty'));
return null;
}
return convertContentWithAI(content, context, buildSystemPrompt('freeform', existingRules));
}
}
+64
View File
@@ -0,0 +1,64 @@
import * as vscode from 'vscode';
import * as XLSX from 'xlsx';
import { RuleConverter } from './converter';
import { convertContentWithAI } from '../import-service';
import { buildSystemPrompt } from './prompt-builder';
import type { CustomRule } from '../../types';
import { t } from '../../i18n/messages';
function buildMarkdownTable(rows: Record<string, unknown>[], sheetName: string): string {
const keys = Object.keys(rows[0]);
const header = `| ${keys.join(' | ')} |`;
const separator = `| ${keys.map(() => '---').join(' | ')} |`;
const dataLines = rows.map(row => {
const cells = keys.map(k => String(row[k] ?? ''));
return `| ${cells.join(' | ')} |`;
});
return [`## ${sheetName}`, header, separator, ...dataLines].join('\n');
}
export class ExcelConverter implements RuleConverter {
supportedExtensions = ['.xlsx', '.xls'];
async convert(srcPath: string, context: vscode.ExtensionContext, existingRules?: CustomRule[]): Promise<string | null> {
let workbook: XLSX.WorkBook;
try {
workbook = XLSX.readFile(srcPath);
} catch (err) {
const msg = err instanceof Error ? err.message : String(err);
vscode.window.showErrorMessage(t('import.excelReadFail', { 0: msg }));
return null;
}
if (workbook.SheetNames.length === 0) {
vscode.window.showErrorMessage(t('import.excelEmpty'));
return null;
}
let parts: string[];
try {
parts = [];
for (const sheetName of workbook.SheetNames) {
const sheet = workbook.Sheets[sheetName];
if (!sheet) { continue; }
const rows = XLSX.utils.sheet_to_json<Record<string, string>>(sheet);
if (rows.length === 0) {
continue;
}
parts.push(buildMarkdownTable(rows, sheetName));
}
} catch (err) {
const msg = err instanceof Error ? err.message : String(err);
vscode.window.showErrorMessage(t('import.excelReadFail', { 0: msg }));
return null;
}
if (parts.length === 0) {
vscode.window.showErrorMessage(t('import.excelNoData'));
return null;
}
const combined = parts.join('\n\n');
return convertContentWithAI(combined, context, buildSystemPrompt('spreadsheet', existingRules));
}
}
+15
View File
@@ -0,0 +1,15 @@
import * as fs from 'fs';
import * as vscode from 'vscode';
import { RuleConverter } from './converter';
import { convertContentWithAI } from '../import-service';
import { buildSystemPrompt } from './prompt-builder';
import type { CustomRule } from '../../types';
export class MdConverter implements RuleConverter {
supportedExtensions = ['.md'];
async convert(srcPath: string, context: vscode.ExtensionContext, existingRules?: CustomRule[]): Promise<string | null> {
const content = fs.readFileSync(srcPath, 'utf-8');
return convertContentWithAI(content, context, buildSystemPrompt('freeform', existingRules));
}
}
+30
View File
@@ -0,0 +1,30 @@
import * as vscode from 'vscode';
import { OfficeParser, type OfficeParserAST } from 'officeparser';
import { RuleConverter } from './converter';
import { convertContentWithAI } from '../import-service';
import { buildSystemPrompt } from './prompt-builder';
import type { CustomRule } from '../../types';
import { t } from '../../i18n/messages';
export class PptxConverter implements RuleConverter {
supportedExtensions = ['.pptx'];
async convert(srcPath: string, context: vscode.ExtensionContext, existingRules?: CustomRule[]): Promise<string | null> {
let ast: OfficeParserAST;
try {
ast = await OfficeParser.parseOffice(srcPath);
} catch (err) {
const msg = err instanceof Error ? err.message : String(err);
vscode.window.showErrorMessage(t('import.pptxReadFail', { 0: msg }));
return null;
}
const text = ast.toText().trim();
if (!text) {
vscode.window.showErrorMessage(t('import.pptxEmpty'));
return null;
}
return convertContentWithAI(text, context, buildSystemPrompt('freeform', existingRules));
}
}
+446
View File
@@ -0,0 +1,446 @@
import staticRules from '../static-rules.json';
import type { CustomRule } from '../../types';
import { getLanguage } from '../../i18n/messages';
export type PromptInputType = 'freeform' | 'spreadsheet';
type Lang = 'zh-CN' | 'en' | 'ja';
interface PromptStrings {
role: (inputDesc: string) => string;
inputToleranceTitle: string;
inputToleranceLines: string[];
nonRuleFilterTitle: string;
nonRuleFilterLines: string[];
fieldDefsTitle: string;
fieldDefsLines: string[];
languageRulesTitle: string;
languageRulesLines: string[];
exampleTitle: string;
example1Input: string;
example1Output: string;
example2Input: string;
example2Output: string;
staticAnalysisTitle: string;
staticAnalysisLines: string[];
finalInstruction: string;
dedupHeader: string;
dedupLinterLabel: (name: string, count: number) => string;
dedupCustomLabel: (count: number) => string;
dedupFooter: string;
outputLang: string;
}
const p: Record<Lang, PromptStrings> = {
'zh-CN': {
role: (inputDesc) => `你是一个代码审查规则转换器。将用户提供的${inputDesc},转换为结构化的 YAML 格式,用于代码审查工具。`,
inputToleranceTitle: '## 输入容忍说明',
inputToleranceLines: [
'用户输入可能有多种形态,你必须接受并处理以下任一形式:',
'- 自然语言段落(一段或多段话描述规则)',
'- 无序列表(每条规则一行或一段)',
'- 表格(列名不固定,从语义推断)',
'- 混合形式(段落 + 列表 + 表格组合)',
'',
'不得因输入格式非标准而拒绝转换。应主动从松散描述中提取规则语义。',
],
nonRuleFilterTitle: '## 非规则内容过滤',
nonRuleFilterLines: [
'用户输入中可能混入项目介绍、背景说明、代码示例、章节标题等非规则内容。你必须:',
'- 识别并跳过非规则内容,仅将真正的编码规则转为 YAML 条目',
'- 代码示例、项目介绍等仅作为理解规则语义的上下文,自身不输出为规则',
'- 若某段内容无法判断为规则(既无规则意图也无违反提示),直接忽略,不强行转换',
],
fieldDefsTitle: '## 字段定义',
fieldDefsLines: [
'每条规则需要包含以下字段:',
'- id: 规则唯一标识(kebab-case 英文,语义化、简短,如 no-console-log、avoid-magic-number',
' **必须**基于规则描述内容自动生成语义化的 id',
' 即使输入中无显式 id 标识,也必须根据 description/message 的语义推断出合适的 id',
' 多条规则之间 id 不得重复',
'- severity: 严重级别(error / warning / info',
' **必须**输出。按规则语义推断:',
' error: 会导致 bug / 安全问题 / 数据损坏',
' warning: 潜在问题 / 不良实践',
' info: 风格 / 可读性建议',
' 即使输入中无显式严重级别,也必须根据规则后果的严重程度推断',
'- description: 规则简短描述',
' **必须**输出。若输入中不明显,从 message 的内容反向推导出简短描述',
'- message: 违反时的提示消息',
' **必须**输出。若输入中不明显,从 description 的内容推导出违反提示',
' description 与 message 语义可相近,无需强行区分口吻,但两者都必须填写',
'- languages: 适用语言数组(可选,如 [javascript, typescript]',
'- excludeLanguages: 明确排除的语言数组(可选,如 [css, sql]',
'- duplicateOf: 重复的规则 IDlinter 如 eslint/no-console;自定义如 custom/my-rule',
'- duplicateLevel: 重复程度(exact / overlap / none',
'- duplicateReason: 重复/重叠原因说明(overlap 档必填)',
],
languageRulesTitle: '## 语言字段规则(严格遵守)',
languageRulesLines: [
'对于每条规则的 languages 字段:',
'1. 规则描述中含明确语言关键词(如 "Java"、"JavaScript"、"CSS")→ 使用 languages 白名单',
'2. 规则适用于大多数语言,只有少数不适用 → 使用 excludeLanguages 黑名单',
'3. 无法确定适用语言,或规则为通用规范 → languages 与 excludeLanguages 均留空',
'4. languages 和 excludeLanguages 不可同时非空',
'5. 语言名使用小写:java, javascript, typescript, css, sql, plsql, jsp',
'6. 严禁猜测。留空比猜测错误更安全。',
],
exampleTitle: '## 输入输出示例',
example1Input: '不要用 console.log,生产环境会泄露信息。还有不要留下未使用的变量,看着乱。',
example1Output: [
'- id: no-console-log',
' severity: warning',
' description: 禁止使用 console.log',
' message: 请使用 logger 工具替代 console.log',
' duplicateLevel: none',
'- id: no-unused-vars',
' severity: warning',
' description: 禁止未使用的变量',
' message: 未使用的变量应删除或注释',
' duplicateOf: eslint/no-unused-vars',
' duplicateLevel: exact',
].join('\n'),
example2Input: [
'本项目是一个电商后台管理系统,主要使用 Java + Spring Boot 开发。',
'代码规范要求:Service 层方法必须有日志记录,方便排查问题。',
'示例代码:',
' public void createOrder(Order order) { ... }',
'另外,Controller 层返回值统一用 Result 包装,不要直接返回 Map。',
].join('\n'),
example2Output: [
'- id: require-service-logging',
' severity: warning',
' description: Service 层方法必须有日志记录',
' message: Service 方法缺少日志记录,请补充以便排查问题',
' languages: [java]',
' duplicateLevel: none',
'- id: require-result-wrapper',
' severity: warning',
' description: Controller 返回值必须用 Result 包装',
' message: 请用 Result 包装返回值,不要直接返回 Map',
' languages: [java]',
' duplicateLevel: none',
].join('\n'),
staticAnalysisTitle: '## 静态分析重复检测',
staticAnalysisLines: [
'对于每条规则,判断其检测目标与触发条件是否与上述某个 linter 规则或自定义规则重复:',
'',
'- **exact**:检测目标与触发条件完全一致(会报出同样的问题行)→ 输出 duplicateOf + duplicateLevel: exact',
'- **overlap**:检测目标相同,但本规则有额外要求或更窄范围 → 输出 duplicateOf + duplicateLevel: overlap + duplicateReason',
'- **none**:检测目标不同 → 输出 duplicateLevel: none',
'',
'仅"话题相似"不算重复。例如:',
'- "未使用变量应删除" → exact(重复 eslint/no-unused-vars',
'- "禁止在 console.log 中输出敏感信息" → none(检测目标不同)',
'',
'仅输出 YAML,不要额外说明。',
],
finalInstruction: '',
dedupHeader: '## 已知规则清单(用于重复检测)',
dedupLinterLabel: (name, count) => `### ${name} (${count} 条)`,
dedupCustomLabel: (count) => `### 已导入的自定义规则 (${count} 条)`,
dedupFooter: '判定时请精确匹配上述规则 ID,而非模糊匹配分类。',
outputLang: '输出语言:zh-CN',
},
en: {
role: (inputDesc) => `You are a code review rule converter. Convert the ${inputDesc} provided by the user into structured YAML format for a code review tool. All descriptions and messages must be in English.`,
inputToleranceTitle: '## Input Tolerance',
inputToleranceLines: [
'User input may come in various forms. You must accept and process any of the following:',
'- Natural language paragraphs (one or more paragraphs describing rules)',
'- Unordered lists (one rule per line or paragraph)',
'- Tables (column names may vary; infer from semantics)',
'- Mixed forms (paragraphs + lists + tables)',
'',
'Do not reject conversion due to non-standard input format. Actively extract rule semantics from loose descriptions.',
],
nonRuleFilterTitle: '## Non-Rule Content Filtering',
nonRuleFilterLines: [
'User input may contain project introductions, background info, code examples, section titles, etc. You must:',
'- Identify and skip non-rule content; only convert actual coding rules into YAML entries',
'- Code examples, project descriptions etc. serve only as context for understanding rule semantics; do not output them as rules',
'- If content cannot be identified as a rule (no rule intent or violation hint), ignore it; do not force conversion',
],
fieldDefsTitle: '## Field Definitions',
fieldDefsLines: [
'Each rule must include the following fields:',
'- id: Unique rule identifier (kebab-case English, semantic and concise, e.g., no-console-log, avoid-magic-number)',
' **Must** generate a semantic id based on the rule description content',
' Even if no explicit id is present in the input, infer a suitable id from the description/message semantics',
' IDs must not be duplicated across rules',
'- severity: Severity level (error / warning / info)',
' **Must** output. Infer based on rule semantics:',
' error: causes bugs / security issues / data corruption',
' warning: potential issues / bad practices',
' info: style / readability suggestions',
' Even if no explicit severity is given, infer from the rule\'s impact',
'- description: Short rule description',
' **Must** output. If not obvious from input, derive from message content',
'- message: Violation message',
' **Must** output. If not obvious from input, derive from description content',
' description and message may be semantically similar; no need to force different tones, but both must be filled',
'- languages: Applicable language array (optional, e.g., [javascript, typescript])',
'- excludeLanguages: Explicitly excluded language array (optional, e.g., [css, sql])',
'- duplicateOf: Duplicate rule ID (linter e.g., eslint/no-console; custom e.g., custom/my-rule)',
'- duplicateLevel: Duplicate level (exact / overlap / none)',
'- duplicateReason: Duplicate/overlap reason (required for overlap)',
],
languageRulesTitle: '## Language Field Rules (Strict)',
languageRulesLines: [
'For each rule\'s languages field:',
'1. If rule description mentions specific languages (e.g., "Java", "JavaScript", "CSS") → use languages whitelist',
'2. If rule applies to most languages, with few exceptions → use excludeLanguages blacklist',
'3. If applicable language cannot be determined, or rule is general → leave both languages and excludeLanguages empty',
'4. languages and excludeLanguages must not both be non-empty simultaneously',
'5. Use lowercase language names: java, javascript, typescript, css, sql, plsql, jsp',
'6. Never guess. Leaving empty is safer than guessing incorrectly.',
],
exampleTitle: '## Input/Output Examples',
example1Input: 'Do not use console.log, it leaks information in production. Also do not leave unused variables, they look messy.',
example1Output: [
'- id: no-console-log',
' severity: warning',
' description: Forbid using console.log',
' message: Use a logger tool instead of console.log',
' duplicateLevel: none',
'- id: no-unused-vars',
' severity: warning',
' description: Forbid unused variables',
' message: Unused variables should be deleted or commented out',
' duplicateOf: eslint/no-unused-vars',
' duplicateLevel: exact',
].join('\n'),
example2Input: [
'This project is an e-commerce backend, mainly using Java + Spring Boot.',
'Coding rules: Service layer methods must have logging for debugging.',
'Example code:',
' public void createOrder(Order order) { ... }',
'Also, Controller layer return values should use Result wrapper, do not return Map directly.',
].join('\n'),
example2Output: [
'- id: require-service-logging',
' severity: warning',
' description: Service layer methods must have logging',
' message: Service method missing logging, add for debugging',
' languages: [java]',
' duplicateLevel: none',
'- id: require-result-wrapper',
' severity: warning',
' description: Controller return values must use Result wrapper',
' message: Use Result wrapper for return values, do not return Map directly',
' languages: [java]',
' duplicateLevel: none',
].join('\n'),
staticAnalysisTitle: '## Static Analysis Duplicate Detection',
staticAnalysisLines: [
'For each rule, determine whether its detection target and trigger conditions duplicate any linter rule or custom rule above:',
'',
'- **exact**: Detection target and trigger conditions are completely identical (would flag the same line) → output duplicateOf + duplicateLevel: exact',
'- **overlap**: Same detection target but this rule has additional requirements or narrower scope → output duplicateOf + duplicateLevel: overlap + duplicateReason',
'- **none**: Different detection targets → output duplicateLevel: none',
'',
'"Same topic" alone does not count as duplicate. For example:',
'- "Unused variables should be deleted" → exact (duplicate of eslint/no-unused-vars)',
'- "Do not output sensitive info in console.log" → none (different detection target)',
'',
'Output YAML only, no extra explanation.',
],
finalInstruction: 'All descriptions and messages must be written in English.',
dedupHeader: '## Known Rules (for duplicate detection)',
dedupLinterLabel: (name, count) => `### ${name} (${count} rules)`,
dedupCustomLabel: (count) => `### Imported custom rules (${count} rules)`,
dedupFooter: 'Match exactly by rule ID above, not by fuzzy category matching.',
outputLang: 'Output language: en',
},
ja: {
role: (inputDesc) => `あなたはコードレビュールール変換ツールです。ユーザーが提供した${inputDesc}を、コードレビューツール用の構造化YAML形式に変換してください。すべての説明とメッセージは日本語で出力してください。`,
inputToleranceTitle: '## 入力許容について',
inputToleranceLines: [
'ユーザー入力は様々な形式である可能性があります。以下の形式を受け入れ、処理する必要があります:',
'- 自然言語の段落(1つ以上の段落でルールを記述)',
'- 順不同リスト(各ルールが1行または1段落)',
'- テーブル(列名は固定されていません。意味から推測してください)',
'- 混合形式(段落 + リスト + テーブルの組み合わせ)',
'',
'非標準的な入力形式であっても変換を拒否してはいけません。緩やかな記述からルールの意味を積極的に抽出してください。',
],
nonRuleFilterTitle: '## 非ルールコンテンツのフィルタリング',
nonRuleFilterLines: [
'ユーザー入力には、プロジェクト紹介、背景説明、コード例、セクションタイトルなどの非ルールコンテンツが混入している可能性があります。以下を行う必要があります:',
'- 非ルールコンテンツを識別してスキップし、実際のコーディングルールのみをYAMLエントリに変換する',
'- コード例やプロジェクト紹介などはルール意味理解のコンテキストとしてのみ使用し、これら自体をルールとして出力しない',
'- ルールと判断できない内容(ルール意図も違反のヒントもない場合)は無視し、無理に変換しない',
],
fieldDefsTitle: '## フィールド定義',
fieldDefsLines: [
'各ルールには以下のフィールドが必要です:',
'- id: ルールの一意識別子(kebab-caseの英語、意味的で簡潔、例:no-console-log、avoid-magic-number',
' **必須** ルール説明内容に基づいて意味的なidを自動生成する',
' 入力に明示的なidがない場合でも、description/messageの意味から適切なidを推測する',
' 複数ルール間でidが重複してはいけない',
'- severity: 重大度(error / warning / info',
' **必須**で出力。ルールの意味に従って推測:',
' error: バグ/セキュリティ問題/データ破損を引き起こす',
' warning: 潜在的な問題/悪い慣行',
' info: スタイル/可読性の提案',
' 入力に明示的な重大度がない場合でも、ルールの影響の重大さから推測する',
'- description: ルールの簡単な説明',
' **必須**で出力。入力で不明確な場合、messageの内容から逆算して短い説明を導出',
'- message: 違反時のメッセージ',
' **必須**で出力。入力で不明確な場合、descriptionの内容から違反メッセージを導出',
' descriptionとmessageは意味的に近くても構いません。口調を無理に区別する必要はありませんが、両方とも必須です',
'- languages: 対象言語配列(オプション、例:[javascript, typescript]',
'- excludeLanguages: 明示的に除外する言語配列(オプション、例:[css, sql]',
'- duplicateOf: 重複するルールID(リンター例:eslint/no-console、カスタム例:custom/my-rule',
'- duplicateLevel: 重複レベル(exact / overlap / none',
'- duplicateReason: 重複/重複理由の説明(overlapの場合は必須)',
],
languageRulesTitle: '## 言語フィールドルール(厳守)',
languageRulesLines: [
'各ルールのlanguagesフィールドについて:',
'1. ルール説明に明確な言語キーワードがある場合(例:「Java」「JavaScript」「CSS」)→ languagesにホワイトリストを使用',
'2. ルールがほとんどの言語に適用され、一部のみ適用外の場合 → excludeLanguagesにブラックリストを使用',
'3. 適用言語が判断できない場合、またはルールが汎用の場合 → languagesとexcludeLanguagesの両方を空にする',
'4. languagesとexcludeLanguagesは同時に空であってはいけない',
'5. 言語名は小文字を使用:java, javascript, typescript, css, sql, plsql, jsp',
'6. 推測は厳禁。空のままにする方が誤った推測より安全です。',
],
exampleTitle: '## 入出力例',
example1Input: 'console.logは本番環境で情報漏洩するため使用しないでください。また、未使用の変数は残さないでください。散らかって見えます。',
example1Output: [
'- id: no-console-log',
' severity: warning',
' description: console.logの使用禁止',
' message: loggerツールを使用してconsole.logを代替してください',
' duplicateLevel: none',
'- id: no-unused-vars',
' severity: warning',
' description: 未使用変数の禁止',
' message: 未使用の変数は削除またはコメントアウトしてください',
' duplicateOf: eslint/no-unused-vars',
' duplicateLevel: exact',
].join('\n'),
example2Input: [
'本プロジェクトはECサイト管理システムで、主にJava + Spring Bootを使用しています。',
'コード規約:Service層のメソッドには必ずログ記録が必要です。問題調査のためです。',
'コード例:',
' public void createOrder(Order order) { ... }',
'また、Controller層の戻り値は統一してResultでラップし、Mapを直接返さないでください。',
].join('\n'),
example2Output: [
'- id: require-service-logging',
' severity: warning',
' description: Service層メソッドにはログ記録が必須',
' message: Serviceメソッドにログ記録がありません。問題調査のため追加してください',
' languages: [java]',
' duplicateLevel: none',
'- id: require-result-wrapper',
' severity: warning',
' description: Controllerの戻り値はResultでラップすること',
' message: Resultで戻り値をラップし、Mapを直接返さないでください',
' languages: [java]',
' duplicateLevel: none',
].join('\n'),
staticAnalysisTitle: '## 静的解析重複検出',
staticAnalysisLines: [
'各ルールについて、その検出対象とトリガー条件が上記のリンタールールまたはカスタムルールと重複するか判断:',
'',
'- **exact**: 検出対象とトリガー条件が完全に一致(同じ問題行を報告する)→ duplicateOf + duplicateLevel: exact を出力',
'- **overlap**: 検出対象は同じだが、このルールに追加要件やより狭い範囲がある → duplicateOf + duplicateLevel: overlap + duplicateReason を出力',
'- **none**: 検出対象が異なる → duplicateLevel: none を出力',
'',
'単に「トピックが類似している」だけでは重複とみなされません。例:',
'- 「未使用変数は削除すべき」→ exacteslint/no-unused-varsと重複)',
'- 「console.logで機密情報を出力しない」→ none(検出対象が異なる)',
'',
'YAMLのみを出力し、追加説明は不要です。',
],
finalInstruction: 'すべてのdescriptionとmessageは日本語で出力してください。',
dedupHeader: '## 既知ルール一覧(重複検出用)',
dedupLinterLabel: (name, count) => `### ${name}${count} 件)`,
dedupCustomLabel: (count) => `### インポート済みカスタムルール(${count} 件)`,
dedupFooter: '上記ルールIDで正確にマッチングしてください。曖昧なカテゴリマッチングは避けてください。',
outputLang: '出力言語:ja',
},
};
function getLang(): Lang {
const lang = getLanguage();
if (lang === 'en' || lang === 'ja') { return lang; }
return 'zh-CN';
}
function buildDedupPromptSection(existingCustomRules?: CustomRule[], lang?: Lang): string {
const l = lang ?? getLang();
const s = p[l];
const lines: string[] = [s.dedupHeader];
for (const [linter, rules] of Object.entries(staticRules.rules)) {
lines.push(s.dedupLinterLabel(linter, rules.length));
for (const rule of rules) {
lines.push(`- ${rule.id}: ${rule.description}`);
}
lines.push('');
}
if (existingCustomRules && existingCustomRules.length > 0) {
lines.push(s.dedupCustomLabel(existingCustomRules.length));
for (const rule of existingCustomRules) {
lines.push(`- custom/${rule.id}: ${rule.description}`);
}
lines.push('');
}
lines.push(s.dedupFooter);
return lines.join('\n');
}
export function buildSystemPrompt(inputType: PromptInputType, existingRules?: CustomRule[]): string {
const lang = getLang();
const s = p[lang];
const inputDesc = inputType === 'spreadsheet'
? (lang === 'zh-CN' ? '表格规则数据' : lang === 'ja' ? '表形式のルールデータ' : 'spreadsheet rule data')
: (lang === 'zh-CN' ? '自然语言规则描述' : lang === 'ja' ? '自然言語のルール記述' : 'natural language rule description');
const parts: string[] = [
s.role(inputDesc),
'',
s.inputToleranceTitle,
...s.inputToleranceLines,
'',
s.nonRuleFilterTitle,
...s.nonRuleFilterLines,
'',
s.fieldDefsTitle,
...s.fieldDefsLines,
'',
s.languageRulesTitle,
...s.languageRulesLines,
'',
s.exampleTitle,
'',
`${lang === 'zh-CN' ? '示例 1 — 松散段落输入:' : lang === 'ja' ? '例1 — 緩やかな段落入力:' : 'Example 1 — Loose paragraph input:'}`,
`${lang === 'zh-CN' ? '输入' : lang === 'ja' ? '入力' : 'Input'}${s.example1Input}`,
`${lang === 'zh-CN' ? '输出' : lang === 'ja' ? '出力' : 'Output'}`,
s.example1Output,
'',
`${lang === 'zh-CN' ? '示例 2 — 含非规则内容的混合输入:' : lang === 'ja' ? '例2 — 非ルールコンテンツを含む混合入力:' : 'Example 2 — Mixed input with non-rule content:'}`,
`${lang === 'zh-CN' ? '输入' : lang === 'ja' ? '入力' : 'Input'}${s.example2Input}`,
`${lang === 'zh-CN' ? '输出' : lang === 'ja' ? '出力' : 'Output'}`,
s.example2Output,
'',
buildDedupPromptSection(existingRules, lang),
'',
s.staticAnalysisTitle,
...s.staticAnalysisLines,
'',
s.finalInstruction,
s.outputLang,
];
return parts.join('\n');
}
+15
View File
@@ -0,0 +1,15 @@
import * as fs from 'fs';
import * as vscode from 'vscode';
import { RuleConverter } from './converter';
import { convertContentWithAI } from '../import-service';
import { buildSystemPrompt } from './prompt-builder';
import type { CustomRule } from '../../types';
export class TxtConverter implements RuleConverter {
supportedExtensions = ['.txt'];
async convert(srcPath: string, context: vscode.ExtensionContext, existingRules?: CustomRule[]): Promise<string | null> {
const content = fs.readFileSync(srcPath, 'utf-8');
return convertContentWithAI(content, context, buildSystemPrompt('freeform', existingRules));
}
}
+12
View File
@@ -0,0 +1,12 @@
import * as fs from 'fs';
import * as vscode from 'vscode';
import { RuleConverter } from './converter';
import type { CustomRule } from '../../types';
export class YamlConverter implements RuleConverter {
supportedExtensions = ['.yaml', '.yml'];
async convert(srcPath: string, _context: vscode.ExtensionContext, _existingRules?: CustomRule[]): Promise<string | null> {
return fs.readFileSync(srcPath, 'utf-8');
}
}
+593
View File
@@ -0,0 +1,593 @@
import * as vscode from 'vscode';
import type { ConversionResult, PreviewDecision, ImportableRule } from './import-types';
import { t } from '../i18n/messages';
export async function showImportPreview(
result: ConversionResult,
): Promise<PreviewDecision | null> {
return new Promise((resolve) => {
const panel = vscode.window.createWebviewPanel(
'ruleImportPreview',
t('importPreview.title'),
vscode.ViewColumn.Active,
{ enableScripts: true },
);
const keepRule: Record<string, boolean> = {};
for (const rule of result.rules) {
keepRule[rule.id] = rule.duplicateLevel !== 'exact';
}
panel.webview.html = renderPreviewHtml(result, keepRule);
panel.webview.onDidReceiveMessage((msg) => {
if (msg.type === 'toggleRule') {
keepRule[msg.ruleId] = msg.keep;
} else if (msg.type === 'confirm') {
resolve({
keepRule,
confirmed: true,
editedRules: msg.editedRules as ImportableRule[] | undefined,
});
panel.dispose();
} else if (msg.type === 'cancel') {
resolve(null);
panel.dispose();
}
});
panel.onDidDispose(() => resolve(null));
});
}
const SEVERITY_OPTIONS = ['error', 'warning', 'info'];
const SEVERITY_COLORS: Record<string, string> = {
error: '#f48771',
warning: '#d29922',
info: '#58a6ff',
};
function renderPreviewHtml(
result: ConversionResult,
keepRule: Record<string, boolean>,
): string {
const exactRules = result.rules.filter(r => r.duplicateLevel === 'exact');
const overlapRules = result.rules.filter(r => r.duplicateLevel === 'overlap');
const noneRules = result.rules.filter(
r => r.duplicateLevel !== 'exact' && r.duplicateLevel !== 'overlap'
);
const totalKept = Object.values(keepRule).filter(Boolean).length;
const totalCommented = Object.values(keepRule).filter(v => !v).length;
function renderRuleCard(rule: ImportableRule): string {
const kept = keepRule[rule.id];
const color = SEVERITY_COLORS[rule.severity] || '#8b949e';
const editRule = rule;
let duplicateInfo = '';
if (rule.duplicateLevel === 'exact') {
const prefix = rule.duplicateOf?.startsWith('custom/') ? `${t('import.customRulePrefix')} ${rule.duplicateOf.slice(7)}` : (rule.duplicateOf ?? 'unknown');
duplicateInfo = `<div style="color:#8b949e;font-size:12px;margin-top:4px;">${t('import.duplicateOf', { 0: prefix })}</div>`;
} else if (rule.duplicateLevel === 'overlap') {
const prefix = rule.duplicateOf?.startsWith('custom/') ? `${t('import.customRulePrefix')} ${rule.duplicateOf.slice(7)}` : (rule.duplicateOf ?? 'unknown');
duplicateInfo = `
<div style="color:#d29922;font-size:12px;margin-top:4px;">${t('import.overlapWith', { 0: prefix })}</div>
${rule.duplicateReason ? `<div style="color:#8b949e;font-size:12px;margin-top:2px;">${t('import.overlapReason', { 0: rule.duplicateReason })}</div>` : ''}
`;
}
const statusBadge = rule.duplicateLevel === 'exact'
? `<span class="badge badge-exact">${kept ? t('import.badgeRestored') : t('import.badgeWillComment')}</span>`
: rule.duplicateLevel === 'overlap'
? `<span class="badge badge-overlap">${kept ? t('importPreview.keep') : t('importPreview.comment')}</span>`
: `<span class="badge badge-none">${t('importPreview.keep')}</span>`;
const tagValue = (tags: string[] | undefined) => tags && tags.length > 0 ? tags.join(',') : '';
const tagDisplay = (tags: string[] | undefined) => tags && tags.length > 0 ? tags.map(t => `<span class="tag" data-value="${t}">${t}<span class="tag-remove" data-tag="${t}">×</span></span>`).join('') : '';
return `
<div class="rule-card" data-ruleid="${rule.id}">
<div class="rule-card-header" onclick="toggleCard('${rule.id}')">
<div class="rule-card-summary">
<input class="rule-id-input${/^rule-\d+$/.test(rule.id) ? ' placeholder-id' : ''}" value="${rule.id}" onchange="syncId('${rule.id}', this.value)" onclick="event.stopPropagation()">
<span class="rule-severity-tag" style="background:${color}20;color:${color};border:1px solid ${color}40;">${editRule.severity}</span>
<span class="rule-desc-preview">${editRule.description}</span>
</div>
<div class="rule-card-meta">
${statusBadge}
<span class="expand-icon">▼</span>
</div>
</div>
<div class="rule-card-body" id="body-${rule.id}" style="display:none;">
<div class="edit-header">
<div class="id-row">
<span class="id-display-label">${t('import.idLabel')}</span>
<input class="id-display-input${/^rule-\d+$/.test(rule.id) ? ' placeholder-id' : ''}" value="${rule.id}" onchange="syncId('${rule.id}', this.value)">
${/^rule-\d+$/.test(rule.id) ? `<span class="placeholder-hint">${t('import.placeholderIdHint')}</span>` : ''}
</div>
<div class="keep-toggle">
<button class="toggle-btn ${kept ? 'active' : ''}" data-action="keep" onclick="event.stopPropagation();toggleKeep('${rule.id}', true)">${t('importPreview.keep')}</button>
<button class="toggle-btn ${!kept ? 'active' : ''}" data-action="comment" onclick="event.stopPropagation();toggleKeep('${rule.id}', false)">${t('importPreview.comment')}</button>
</div>
</div>
${duplicateInfo}
<div class="edit-field">
<label>${t('import.severityLabel')}</label>
<select onchange="updateRule('${rule.id}','severity',this.value)">
${SEVERITY_OPTIONS.map(s => `<option value="${s}" ${s === editRule.severity ? 'selected' : ''}>${s}</option>`).join('')}
</select>
</div>
<div class="edit-field">
<label>${t('import.descriptionLabel')}</label>
<textarea rows="2" onchange="updateRule('${rule.id}','description',this.value)">${editRule.description}</textarea>
</div>
<div class="edit-field">
<label>${t('import.messageLabel')}</label>
<textarea rows="2" onchange="updateRule('${rule.id}','message',this.value)">${editRule.message}</textarea>
</div>
<div class="edit-field">
<label>${t('import.languagesLabel')}</label>
<div class="tag-input-wrapper">
<div class="tag-list" data-ruleid="${rule.id}" data-field="languages">
${tagDisplay(editRule.languages)}
</div>
<input class="tag-input" data-ruleid="${rule.id}" data-field="languages" placeholder="${t('import.tagPlaceholder')}" value="">
</div>
</div>
<div class="edit-field">
<label>${t('import.excludeLanguagesLabel')}</label>
<div class="tag-input-wrapper">
<div class="tag-list" data-ruleid="${rule.id}" data-field="excludeLanguages">
${tagDisplay(editRule.excludeLanguages)}
</div>
<input class="tag-input" data-ruleid="${rule.id}" data-field="excludeLanguages" placeholder="${t('import.tagPlaceholder')}" value="">
</div>
</div>
</div>
</div>
`;
}
function renderSection(title: string, icon: string, rules: ImportableRule[], _defaultExpanded: boolean): string {
if (rules.length === 0) { return ''; }
const sectionId = `section-${title.replace(/\s/g, '')}`;
const show = rules.some(r => keepRule[r.id] !== undefined);
return `
<div style="margin-bottom:12px;">
<div class="section-header" onclick="toggleSection('${sectionId}')">
<span style="font-size:14px;">${icon}</span>
<span class="section-title">${title}${t('import.ruleCount', { 0: rules.length })}</span>
<span class="section-arrow">▶</span>
</div>
<div id="${sectionId}" style="display:${show ? 'block' : 'none'};">
${rules.map(renderRuleCard).join('')}
</div>
</div>
`;
}
return `<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<style>
* { box-sizing: border-box; margin: 0; padding: 0; }
body {
font-family: var(--vscode-font-family);
font-size: var(--vscode-font-size);
color: var(--vscode-foreground);
background: var(--vscode-editor-background);
padding: 16px; line-height: 1.5;
}
.header {
border-bottom: 1px solid var(--vscode-panel-border);
padding-bottom: 12px; margin-bottom: 12px;
}
.header-title { font-size: 16px; font-weight: 700; margin-bottom: 4px; }
.header-sub { font-size: 12px; color: var(--vscode-descriptionForeground); }
.summary {
display: flex; gap: 12px; margin-bottom: 16px;
}
.summary-item {
padding: 8px 12px; border-radius: 6px; font-size: 12px;
border: 1px solid var(--vscode-panel-border);
background: var(--vscode-sideBar-background, var(--vscode-editor-background));
}
.summary-bar {
margin-bottom: 16px; padding: 8px 12px; border-radius: 6px;
font-size: 12px; background: rgba(139,92,246,0.1);
border: 1px solid rgba(139,92,246,0.3); color: #a78bfa;
}
.actions {
display: flex; gap: 8px; padding-top: 12px;
border-top: 1px solid var(--vscode-panel-border);
justify-content: flex-end;
}
.btn {
padding: 6px 16px; border: 1px solid var(--vscode-panel-border);
background: var(--vscode-button-secondaryBackground);
color: var(--vscode-button-secondaryForeground);
border-radius: 6px; cursor: pointer; font-size: 12px;
}
.btn:hover { background: var(--vscode-button-secondaryHoverBackground); }
.btn-primary { background: #7c3aed; color: #fff; border-color: #7c3aed; }
.btn-primary:hover { background: #8b5cf6; }
.section-header {
display: flex; align-items: center; gap: 8px; margin-bottom: 8px;
cursor: pointer; padding: 4px 0;
}
.section-title { font-weight: 600; font-size: 13px; }
.section-arrow { font-size: 10px; color: var(--vscode-descriptionForeground); }
.rule-card {
border: 1px solid var(--vscode-panel-border);
border-radius: 8px; margin-bottom: 8px; overflow: hidden;
}
.rule-card-header {
display: flex; align-items: center; justify-content: space-between;
padding: 10px 12px; cursor: pointer; gap: 8px;
}
.rule-card-header:hover { background: var(--vscode-list-hoverBackground); }
.rule-card-summary {
display: flex; align-items: center; gap: 8px; flex: 1; min-width: 0;
}
.rule-id-input {
font-family: monospace; font-size: 13px; font-weight: 600; white-space: nowrap;
background: transparent;
border: 1px solid transparent;
border-radius: 3px;
color: var(--vscode-foreground);
padding: 1px 4px; outline: none;
width: auto; min-width: 120px;
}
.rule-id-input:focus {
border-color: var(--vscode-focusBorder, #7c3aed);
background: var(--vscode-input-background);
}
.rule-id-input.placeholder-id {
border-color: #f59e0b !important;
box-shadow: 0 0 0 1px rgba(245, 158, 11, 0.3);
}
.rule-severity-tag {
padding: 1px 8px; border-radius: 10px; font-size: 11px; font-weight: 600; white-space: nowrap;
}
.rule-desc-preview {
font-size: 12px; color: var(--vscode-descriptionForeground);
overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.rule-card-meta {
display: flex; align-items: center; gap: 8px; flex-shrink: 0;
}
.expand-icon { font-size: 10px; color: var(--vscode-descriptionForeground); }
.badge {
padding: 1px 8px; border-radius: 10px; font-size: 11px; font-weight: 600;
}
.badge-exact { background: rgba(248,81,73,0.15); color: #f48771; }
.badge-overlap { background: rgba(210,153,34,0.15); color: #d29922; }
.badge-none { background: rgba(35,134,54,0.15); color: #3fb950; }
.rule-card-body {
padding: 0 12px 12px; border-top: 1px solid var(--vscode-panel-border);
}
.edit-header {
display: flex; align-items: center; justify-content: space-between;
padding: 10px 0 8px;
}
.id-row {
display: flex; align-items: center; gap: 8px;
}
.keep-toggle { display: flex; gap: 4px; }
.toggle-btn {
padding: 3px 12px; border-radius: 4px; cursor: pointer; font-size: 11px;
border: 1px solid var(--vscode-panel-border);
background: transparent; color: var(--vscode-foreground);
}
.toggle-btn.active {
background: rgba(35,134,54,0.15); color: #3fb950; border-color: rgba(35,134,54,0.3);
}
.toggle-btn.active[data-action="comment"] {
background: rgba(248,81,73,0.15); color: #f48771; border-color: rgba(248,81,73,0.3);
}
.edit-field { margin-top: 10px; }
.edit-field label {
display: block; font-size: 11px; font-weight: 600;
color: var(--vscode-descriptionForeground); margin-bottom: 4px;
text-transform: uppercase; letter-spacing: 0.5px;
}
.id-display-label {
font-size: 11px; font-weight: 600;
color: var(--vscode-descriptionForeground);
text-transform: uppercase; letter-spacing: 0.5px;
}
.id-display-input {
font-family: monospace; font-size: 13px; font-weight: 600;
background: var(--vscode-input-background);
border: 1px solid var(--vscode-panel-border);
border-radius: 3px;
color: var(--vscode-input-foreground);
padding: 4px 8px; outline: none;
width: auto; min-width: 200px;
}
.id-display-input:focus {
border-color: var(--vscode-focusBorder, #7c3aed);
}
.id-display-input.placeholder-id {
border-color: #f59e0b !important;
box-shadow: 0 0 0 1px rgba(245, 158, 11, 0.3);
}
.placeholder-hint {
color: #d29922; font-size: 11px;
}
.edit-field select, .edit-field textarea {
width: 100%; padding: 6px 8px; border-radius: 4px;
border: 1px solid var(--vscode-panel-border);
background: var(--vscode-input-background);
color: var(--vscode-input-foreground);
font-family: var(--vscode-font-family);
font-size: var(--vscode-font-size);
}
.edit-field textarea { resize: vertical; }
.tag-input-wrapper {
border: 1px solid var(--vscode-panel-border);
border-radius: 4px; padding: 4px 6px;
background: var(--vscode-input-background);
display: flex; flex-wrap: wrap; gap: 4px; align-items: center;
}
.tag-list { display: flex; flex-wrap: wrap; gap: 4px; }
.tag {
display: inline-flex; align-items: center; gap: 3px;
padding: 1px 6px; border-radius: 3px; font-size: 11px;
background: rgba(88,166,255,0.15); color: #58a6ff;
border: 1px solid rgba(88,166,255,0.3);
}
.tag-remove {
cursor: pointer; font-size: 13px; line-height: 1; opacity: 0.7;
}
.tag-remove:hover { opacity: 1; }
.tag-input {
border: none; outline: none; flex: 1; min-width: 80px;
background: transparent; color: var(--vscode-input-foreground);
font-size: 12px; padding: 2px 0;
}
.tag-input::placeholder { color: var(--vscode-input-placeholderForeground); }
.validation-error {
padding: 8px 12px; margin-bottom: 12px; border-radius: 6px;
background: rgba(248,81,73,0.15); color: #f48771;
border: 1px solid rgba(248,81,73,0.3); font-size: 12px;
}
</style>
</head>
<body>
<div class="header">
<div class="header-title">${t('importPreview.title')}</div>
<div class="header-sub">${t('importPreview.source', { 0: result.sourceFileName, 1: String(result.rules.length) })}</div>
</div>
<div class="summary">
<div class="summary-item" style="border-color:rgba(248,81,73,0.3);color:#f48771;">${t('import.exactDuplicate', { 0: exactRules.length })}</div>
<div class="summary-item" style="border-color:rgba(210,153,34,0.3);color:#d29922;">${t('import.overlapDuplicate', { 0: overlapRules.length })}</div>
<div class="summary-item" style="border-color:rgba(35,134,54,0.3);color:#3fb950;">${t('import.noDuplicate', { 0: noneRules.length })}</div>
</div>
<div class="summary-bar" id="statusBar">
${t('import.statusBar', { 0: `<b id="keepCount">${totalKept}</b>`, 1: `<b id="commentCount">${totalCommented}</b>` })}
<span id="editHint" style="display:none;">${t('import.editedHint', { 0: '<b id="editCount">0</b>' })}</span>
</div>
<div id="validationError" class="validation-error" style="display:none;"></div>
${renderSection(t('import.sectionExact'), '⛔', exactRules, false)}
${renderSection(t('import.sectionOverlap'), '⚠️', overlapRules, true)}
${renderSection(t('import.sectionNone'), '✅', noneRules, false)}
<div class="actions">
<button class="btn" onclick="cancel()">${t('importPreview.cancel')}</button>
<button class="btn btn-primary" onclick="doConfirm()">${t('importPreview.confirm')}</button>
</div>
<script>
const vscode = acquireVsCodeApi();
const editedRules = {};
const VALIDATION_DESC_EMPTY = ${JSON.stringify(t('import.validationDescEmpty'))};
const VALIDATION_MSG_EMPTY = ${JSON.stringify(t('import.validationMsgEmpty'))};
const VALIDATION_ID_EMPTY = ${JSON.stringify(t('import.validationIdEmpty'))};
function syncId(originalId, newValue) {
const card = document.querySelector('.rule-card[data-ruleid="' + originalId + '"]');
if (!card) return;
const headerInput = card.querySelector('.rule-id-input');
const panelInput = card.querySelector('.id-display-input');
if (headerInput) headerInput.value = newValue;
if (panelInput) panelInput.value = newValue;
if (!editedRules[originalId]) editedRules[originalId] = {};
editedRules[originalId].id = newValue;
updateEditHint();
const isPlaceholder = /^rule-\\d+$/.test(newValue);
[headerInput, panelInput].forEach(el => {
if (el) {
el.classList.toggle('placeholder-id', isPlaceholder);
}
});
}
function toggleCard(ruleId) {
const body = document.getElementById('body-' + ruleId);
const card = body.closest('.rule-card');
const icon = card.querySelector('.expand-icon');
if (body.style.display === 'none') {
body.style.display = 'block';
icon.textContent = '▲';
} else {
body.style.display = 'none';
icon.textContent = '▼';
}
}
function toggleSection(id) {
const el = document.getElementById(id);
const arrow = el.previousElementSibling.querySelector('.section-arrow');
if (el.style.display === 'none') {
el.style.display = 'block';
arrow.textContent = '▼';
} else {
el.style.display = 'none';
arrow.textContent = '▶';
}
}
function toggleKeep(ruleId, keep) {
vscode.postMessage({ type: 'toggleRule', ruleId, keep });
const card = document.querySelector('.rule-card[data-ruleid="' + ruleId + '"]');
if (!card) return;
const btns = card.querySelectorAll('.toggle-btn');
btns.forEach(b => b.classList.toggle('active', (keep && b.dataset.action === 'keep') || (!keep && b.dataset.action === 'comment')));
updateSummary();
}
function updateRule(ruleId, field, value) {
if (!editedRules[ruleId]) {
editedRules[ruleId] = {};
}
editedRules[ruleId][field] = value;
updateEditHint();
}
function collectEditedRules() {
const result = [];
document.querySelectorAll('.rule-card').forEach(card => {
const originalId = card.dataset.ruleid;
const idInput = card.querySelector('.id-display-input');
const ruleId = idInput ? idInput.value.trim() || originalId : originalId;
const severityEl = card.querySelector('.edit-field select');
const textareas = card.querySelectorAll('.edit-field textarea');
const descEl = textareas[0];
const msgEl = textareas[1];
const langList = card.querySelector('.tag-list[data-field="languages"]');
const exclList = card.querySelector('.tag-list[data-field="excludeLanguages"]');
const rule = {
id: ruleId,
severity: severityEl ? severityEl.value : 'warning',
description: descEl ? descEl.value : '',
message: msgEl ? msgEl.value : '',
languages: langList ? Array.from(langList.querySelectorAll('.tag')).map(t => t.dataset.value) : [],
excludeLanguages: exclList ? Array.from(exclList.querySelectorAll('.tag')).map(t => t.dataset.value) : [],
};
result.push(rule);
});
return result;
}
function validate() {
const rules = collectEditedRules();
for (const rule of rules) {
if (!rule.id || !rule.id.trim()) {
return VALIDATION_ID_EMPTY;
}
if (!rule.description || !rule.description.trim()) {
return VALIDATION_DESC_EMPTY.replace('{0}', rule.id);
}
if (!rule.message || !rule.message.trim()) {
return VALIDATION_MSG_EMPTY.replace('{0}', rule.id);
}
}
return null;
}
function doConfirm() {
const err = validate();
if (err) {
const errEl = document.getElementById('validationError');
errEl.textContent = err;
errEl.style.display = 'block';
return;
}
const edited = collectEditedRules();
const hasEdits = Object.keys(editedRules).length > 0;
vscode.postMessage({ type: 'confirm', editedRules: hasEdits ? edited : undefined });
}
function cancel() {
vscode.postMessage({ type: 'cancel' });
}
function updateSummary() {
let keepCount = 0, commentCount = 0;
document.querySelectorAll('.rule-card').forEach(card => {
const ruleId = card.dataset.ruleid;
const keepBtns = card.querySelectorAll('.toggle-btn');
let isKept = true;
keepBtns.forEach(b => {
if (b.classList.contains('active') && b.dataset.action === 'comment') isKept = false;
});
if (isKept) keepCount++; else commentCount++;
});
document.getElementById('keepCount').textContent = keepCount;
document.getElementById('commentCount').textContent = commentCount;
}
function updateEditHint() {
const count = Object.keys(editedRules).length;
const hint = document.getElementById('editHint');
const countEl = document.getElementById('editCount');
if (count > 0) {
hint.style.display = 'inline';
countEl.textContent = count;
} else {
hint.style.display = 'none';
}
}
document.addEventListener('keydown', function(e) {
if (e.target.classList.contains('tag-input') && e.key === 'Enter') {
e.preventDefault();
const input = e.target;
const val = input.value.trim();
if (!val) return;
const ruleId = input.dataset.ruleid;
const field = input.dataset.field;
const list = input.parentElement.querySelector('.tag-list');
const existing = list.querySelectorAll('.tag');
const exists = Array.from(existing).some(t => t.dataset.value === val);
if (exists) { input.value = ''; return; }
const tag = document.createElement('span');
tag.className = 'tag';
tag.dataset.value = val;
tag.innerHTML = val + '<span class="tag-remove">×</span>';
list.appendChild(tag);
input.value = '';
const tags = Array.from(list.querySelectorAll('.tag')).map(t => t.dataset.value);
if (!editedRules[ruleId]) editedRules[ruleId] = {};
editedRules[ruleId][field] = tags;
updateEditHint();
}
});
document.addEventListener('click', function(e) {
if (e.target.classList.contains('tag-remove')) {
const tag = e.target.closest('.tag');
const list = tag.closest('.tag-list');
const ruleId = list.dataset.ruleid;
const field = list.dataset.field;
tag.remove();
const remaining = Array.from(list.querySelectorAll('.tag')).map(t => t.dataset.value);
if (!editedRules[ruleId]) editedRules[ruleId] = {};
editedRules[ruleId][field] = remaining;
updateEditHint();
}
});
</script>
</body>
</html>`;
}
+390
View File
@@ -0,0 +1,390 @@
import * as vscode from 'vscode';
import * as path from 'path';
import * as fs from 'fs';
import { getApiKey } from '../config/secret';
import { getAIConfig, getAITimeout } from '../config/ai';
import { createProvider } from '../ai/factory';
import { RuleConverter } from './converters/converter';
import { loadActiveRules } from './yaml-parser';
import type { ConversionResult, ImportableRule, PreviewDecision } from './import-types';
import { t, getLanguage } from '../i18n/messages';
interface ParsedYamlItem {
id?: string;
severity?: string;
description?: string;
message?: string;
languages?: string[];
excludeLanguages?: string[];
duplicateOf?: string;
duplicateLevel?: string;
duplicateReason?: string;
[key: string]: unknown;
}
function parseSimpleYaml(content: string): ParsedYamlItem[] {
const items: ParsedYamlItem[] = [];
let current: ParsedYamlItem | null = null;
for (const line of content.split('\n')) {
const trimmed = line.trim();
if (!trimmed || trimmed.startsWith('#')) { continue; }
if (trimmed.startsWith('- ')) {
if (current) { items.push(current); }
current = {};
const indentMatch = trimmed.match(/^- (\w[\w-]*)\s*:\s*(.*)$/);
if (indentMatch) {
const key = indentMatch[1];
const raw = indentMatch[2].trim();
if (raw.startsWith('[') && raw.endsWith(']')) {
current[key] = raw.slice(1, -1).split(',').map(s =>
s.trim().replace(/^['"]|['"]$/g, '')
);
} else {
current[key] = raw;
}
}
} else if (current) {
const propMatch = trimmed.match(/^(\w[\w-]*)\s*:\s*(.*)$/);
if (propMatch) {
const key = propMatch[1];
const raw = propMatch[2].trim();
if (!raw || raw === '[]') {
current[key] = [];
} else if (raw.startsWith('[') && raw.endsWith(']')) {
current[key] = raw.slice(1, -1).split(',').map(s =>
s.trim().replace(/^['"]|['"]$/g, '')
);
} else {
current[key] = raw;
}
}
}
}
if (current) { items.push(current); }
return items;
}
export function parseImportableYaml(content: string): ImportableRule[] {
const items = parseSimpleYaml(content);
const validSeverities = new Set(['error', 'warning', 'info']);
return items
.filter(item => item.description || item.message)
.map((item, idx) => ({
id: (item.id && item.id.trim()) ? item.id.trim() : `rule-${idx + 1}`,
severity: item.severity && validSeverities.has(item.severity)
? item.severity as 'error' | 'warning' | 'info'
: 'warning',
description: (item.description ?? item.message ?? ''),
message: (item.message ?? item.description ?? ''),
languages: item.languages as string[] | undefined,
excludeLanguages: item.excludeLanguages as string[] | undefined,
duplicateOf: item.duplicateOf as string | undefined,
duplicateLevel: item.duplicateLevel as 'exact' | 'overlap' | 'none' | undefined,
duplicateReason: item.duplicateReason as string | undefined,
}));
}
export function buildFinalYaml(
yamlContent: string,
rules: ImportableRule[],
decision: PreviewDecision,
): string {
if (decision.editedRules && decision.editedRules.length > 0) {
return renderRulesToYaml(decision.editedRules, decision);
}
return buildFinalYamlFromRaw(yamlContent, rules, decision);
}
function renderRulesToYaml(
rules: ImportableRule[],
decision: PreviewDecision,
): string {
const lines: string[] = [];
for (const rule of rules) {
const keep = decision.keepRule[rule.id] ?? rule.duplicateLevel !== 'exact';
const ruleLines: string[] = [];
ruleLines.push(`- id: ${rule.id}`);
ruleLines.push(` severity: ${rule.severity}`);
ruleLines.push(` description: ${rule.description}`);
ruleLines.push(` message: ${rule.message}`);
if (rule.languages && rule.languages.length > 0) {
ruleLines.push(` languages: [${rule.languages.join(', ')}]`);
}
if (rule.excludeLanguages && rule.excludeLanguages.length > 0) {
ruleLines.push(` excludeLanguages: [${rule.excludeLanguages.join(', ')}]`);
}
if (keep) {
lines.push(...ruleLines);
} else {
const dupLevel = rule.duplicateLevel ?? 'none';
const dupOf = rule.duplicateOf ?? 'manual';
if (dupLevel === 'exact') {
lines.push(t('yaml.duplicateExact', { 0: dupOf }));
} else if (dupLevel === 'overlap') {
lines.push(t('yaml.duplicateOverlap', { 0: dupOf }));
if (rule.duplicateReason) {
lines.push(t('yaml.overlapReason', { 0: rule.duplicateReason }));
}
} else {
lines.push(t('yaml.manualComment'));
}
lines.push(t('yaml.enableHint'));
for (const rl of ruleLines) {
lines.push(`# ${rl}`);
}
}
lines.push('');
}
return lines.join('\n');
}
function buildFinalYamlFromRaw(
yamlContent: string,
rules: ImportableRule[],
decision: PreviewDecision,
): string {
const defaultKeep = (rule: ImportableRule) => rule.duplicateLevel !== 'exact';
const shouldKeep = (rule: ImportableRule) =>
decision.keepRule[rule.id] ?? defaultKeep(rule);
const lines = yamlContent.split('\n');
const output: string[] = [];
let currentRuleId: string | null = null;
let ruleLines: string[] = [];
function flushRule(): void {
if (currentRuleId === null) {
output.push(...ruleLines);
} else {
const rule = rules.find(r => r.id === currentRuleId);
const keep = rule ? shouldKeep(rule) : true;
if (keep) {
const filtered = ruleLines.filter(
line => !line.trimStart().startsWith('duplicateOf:') &&
!line.trimStart().startsWith('duplicateLevel:') &&
!line.trimStart().startsWith('duplicateReason:')
);
output.push(...filtered);
} else {
const level = rule?.duplicateLevel ?? 'exact';
const dupInfo = rule?.duplicateOf ?? 'unknown';
const reason = rule?.duplicateReason ?? '';
if (level === 'exact') {
if (dupInfo.startsWith('custom/')) {
output.push(t('yaml.duplicateExactCustom', { 0: dupInfo.slice(7) }));
} else {
output.push(t('yaml.duplicateExactGeneric', { 0: dupInfo }));
}
} else {
if (dupInfo.startsWith('custom/')) {
output.push(t('yaml.duplicateOverlapCustom', { 0: level, 1: dupInfo.slice(7) }));
} else {
output.push(t('yaml.duplicateOverlapGeneric', { 0: level, 1: dupInfo }));
}
if (reason) { output.push(t('yaml.overlapReason', { 0: reason })); }
}
output.push(t('yaml.enableHint'));
for (const line of ruleLines) {
output.push(line.trim() ? `# ${line}` : '#');
}
}
}
ruleLines = [];
}
for (const line of lines) {
const ruleStart = line.match(/^-\s+id:\s*(.+)/);
if (ruleStart) {
flushRule();
currentRuleId = ruleStart[1].trim();
ruleLines = [line];
} else if (currentRuleId) {
ruleLines.push(line);
} else {
ruleLines.push(line);
}
}
flushRule();
return output.join('\n');
}
export class ImportService {
private converters: Map<string, RuleConverter> = new Map();
registerConverter(converter: RuleConverter): void {
for (const ext of converter.supportedExtensions) {
this.converters.set(ext, converter);
}
}
async convert(
srcPath: string,
context: vscode.ExtensionContext,
): Promise<ConversionResult> {
const ext = path.extname(srcPath).toLowerCase();
const converter = this.converters.get(ext);
if (!converter) {
throw new Error(t('import.unsupportedFormat', { 0: ext }));
}
const workspaceRoot = vscode.workspace.workspaceFolders?.[0]?.uri.fsPath;
const existingRules = workspaceRoot ? loadActiveRules(workspaceRoot) : [];
const yamlContent = await converter.convert(srcPath, context, existingRules);
if (!yamlContent) {
throw new Error(t('import.conversionFailed'));
}
const rules = parseImportableYaml(yamlContent);
const exactCount = rules.filter(r => r.duplicateLevel === 'exact').length;
const overlapCount = rules.filter(r => r.duplicateLevel === 'overlap').length;
return {
rules,
yamlContent,
sourceFileName: path.basename(srcPath),
exactCount,
overlapCount,
};
}
applyConversion(
result: ConversionResult,
decision: PreviewDecision,
targetPath: string,
): void {
const finalYaml = buildFinalYaml(result.yamlContent, result.rules, decision);
const dir = path.dirname(targetPath);
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir, { recursive: true });
}
fs.writeFileSync(targetPath, finalYaml, 'utf-8');
}
}
function buildFallbackPrompt(): string {
const lang = getLanguage();
if (lang === 'ja') {
return `あなたはコードレビュールール変換ツールです。ユーザーが提供した自然言語のルール記述を、構造化されたYAML形式に変換してください。
各ルールには以下のフィールドが必要です:
- id: ルールの一意識別子(kebab-case英語)
- severity: 重要度(error / warning / info
- description: ルールの簡単な説明
- message: 違反時のメッセージ
- languages: 対象言語配列(オプション、例:[javascript, typescript]
出力形式の例:
- id: no-console-log
severity: warning
description: console.logの使用禁止
message: loggerツールを使用してconsole.logを代替してください
languages: [javascript, typescript]
YAMLのみを出力し、追加説明は不要です。
出力言語:ja`;
}
if (lang === 'en') {
return `You are a code review rule converter. Convert the natural language rule description provided by the user into structured YAML format for a code review tool.
Each rule must include the following fields:
- id: Unique rule identifier (kebab-case English)
- severity: Severity level (error / warning / info)
- description: Short rule description
- message: Violation message
- languages: Applicable language array (optional, e.g., [javascript, typescript])
Output format example:
- id: no-console-log
severity: warning
description: Forbid using console.log
message: Use a logger tool instead of console.log
languages: [javascript, typescript]
Output YAML only, no extra explanation.
Output language: en`;
}
return `你是一个代码审查规则转换器。将用户提供的自然语言规则描述,转换为结构化的 YAML 格式,用于代码审查工具。
每条规则需要包含以下字段:
- id: 规则唯一标识(kebab-case 英文)
- severity: 严重级别(error / warning / info
- description: 规则简短描述
- message: 违反时的提示消息
- languages: 适用语言数组(可选,如 [javascript, typescript]
输出格式示例:
- id: no-console-log
severity: warning
description: 禁止使用 console.log
message: 请使用 logger 工具替代 console.log
languages: [javascript, typescript]
仅输出 YAML,不要额外说明。
输出语言:zh-CN`;
}
export async function convertContentWithAI(
content: string,
context: vscode.ExtensionContext,
systemPrompt?: string,
): Promise<string | null> {
if (!content.trim()) {
vscode.window.showErrorMessage(t('import.emptyFile'));
return null;
}
const apiKey = await getApiKey(context);
if (!apiKey) {
vscode.window.showErrorMessage(t('import.needApiKey'));
return null;
}
const config = getAIConfig();
const provider = createProvider(config.provider, apiKey, config.baseUrl);
const prompt = systemPrompt ?? buildFallbackPrompt();
let yamlOutput: string;
try {
yamlOutput = await provider.chat(prompt, content, {
model: config.model,
temperature: 0.1,
maxTokens: 4096,
timeoutMs: getAITimeout() * 1000,
});
} catch (err) {
if (err instanceof DOMException && err.name === 'AbortError') {
vscode.window.showErrorMessage(t('import.timeout'));
} else {
const msg = err instanceof Error ? err.message : String(err);
vscode.window.showErrorMessage(t('import.aiFail', { 0: msg }));
}
return null;
}
const cleaned = yamlOutput
.replace(/```(yaml|yml)?\s*/gi, '')
.replace(/```\s*$/gm, '')
.trim();
if (!cleaned) {
vscode.window.showErrorMessage(t('import.emptyResponse'));
return null;
}
return cleaned;
}
+21
View File
@@ -0,0 +1,21 @@
import type { CustomRule } from '../types';
export interface ImportableRule extends CustomRule {
duplicateOf?: string;
duplicateLevel?: 'exact' | 'overlap' | 'none';
duplicateReason?: string;
}
export interface ConversionResult {
rules: ImportableRule[];
yamlContent: string;
sourceFileName: string;
exactCount: number;
overlapCount: number;
}
export interface PreviewDecision {
keepRule: Record<string, boolean>;
confirmed: boolean;
editedRules?: ImportableRule[];
}
+68
View File
@@ -0,0 +1,68 @@
import * as vscode from 'vscode';
import type { CustomRule } from '../types';
const LANGUAGE_ALIASES: Record<string, string[]> = {
typescriptreact: ['typescript', 'typescriptreact', 'tsx'],
javascriptreact: ['javascript', 'javascriptreact', 'jsx'],
};
const LANGUAGE_GROUPS: Record<string, string[]> = {
sql: ['sql', 'plsql'],
plsql: ['sql', 'plsql'],
};
const JSP_SUB_LANGUAGES = ['java', 'javascript', 'typescript', 'css', 'jsp', 'html'];
const JSP_EXTENSIONS = ['.jsp', '.jspx'];
function expandLanguageId(languageId: string): string[] {
const aliases = LANGUAGE_ALIASES[languageId] ?? [languageId];
const groups = LANGUAGE_GROUPS[languageId] ?? [];
return [...new Set([...aliases, ...groups, languageId])];
}
function isJspFile(document: vscode.TextDocument): boolean {
return JSP_EXTENSIONS.some(ext => document.fileName.toLowerCase().endsWith(ext));
}
function matchesLanguage(rule: CustomRule, expandedLangs: string[]): boolean {
if (rule.excludeLanguages && rule.excludeLanguages.length > 0) {
if (rule.excludeLanguages.some(l => expandedLangs.includes(l))) {
return false;
}
}
if (!rule.languages || rule.languages.length === 0) {
return true;
}
return rule.languages.some(l => expandedLangs.includes(l));
}
export function filterForDocument(
rules: CustomRule[],
document: vscode.TextDocument,
): CustomRule[] {
const langId = document.languageId;
if (langId === 'html' && isJspFile(document)) {
return rules.filter(rule => matchesLanguage(rule, JSP_SUB_LANGUAGES));
}
const expandedLangs = expandLanguageId(langId);
return rules.filter(rule => matchesLanguage(rule, expandedLangs));
}
export interface FilterResult {
relevant: CustomRule[];
filteredOut: CustomRule[];
skippedRequestA: boolean;
}
export function filterAndSummarize(
rules: CustomRule[],
document: vscode.TextDocument,
): FilterResult {
const relevant = filterForDocument(rules, document);
const filteredOut = rules.filter(r => !relevant.includes(r));
return {
relevant,
filteredOut,
skippedRequestA: relevant.length === 0,
};
}
+1788
View File
@@ -0,0 +1,1788 @@
{
"version": "1.0.1",
"linterVersion": {
"eslint": "9.x (recommended)",
"ts-eslint": "8.x (recommended)",
"stylelint": "16.x (12 rules)",
"pmd": "7.26.0 (6 categories)",
"sql-lint": "4.2.2 (default)"
},
"rules": {
"eslint": [
{
"id": "eslint/constructor-super",
"description": "Verify calls of super() in constructors"
},
{
"id": "eslint/for-direction",
"description": "Enforce for loop update clause moving the counter in the right direction"
},
{
"id": "eslint/getter-return",
"description": "Enforce return statements in getters"
},
{
"id": "eslint/no-async-promise-executor",
"description": "Disallow using an async function as a Promise executor"
},
{
"id": "eslint/no-case-declarations",
"description": "Disallow lexical declarations in case clauses"
},
{
"id": "eslint/no-class-assign",
"description": "Disallow reassigning class members"
},
{
"id": "eslint/no-compare-neg-zero",
"description": "Disallow comparing against -0"
},
{
"id": "eslint/no-cond-assign",
"description": "Disallow assignment operators in conditional expressions"
},
{
"id": "eslint/no-const-assign",
"description": "Disallow reassigning const variables"
},
{
"id": "eslint/no-constant-binary-expression",
"description": "Disallow constant binary expressions"
},
{
"id": "eslint/no-constant-condition",
"description": "Disallow constant expressions in conditions"
},
{
"id": "eslint/no-control-regex",
"description": "Disallow control characters in regular expressions"
},
{
"id": "eslint/no-debugger",
"description": "Disallow the use of debugger"
},
{
"id": "eslint/no-delete-var",
"description": "Disallow deleting variables"
},
{
"id": "eslint/no-dupe-args",
"description": "Disallow duplicate arguments in function definitions"
},
{
"id": "eslint/no-dupe-class-members",
"description": "Disallow duplicate class members"
},
{
"id": "eslint/no-dupe-else-if",
"description": "Disallow duplicate conditions in if-else-if chains"
},
{
"id": "eslint/no-dupe-keys",
"description": "Disallow duplicate keys in object literals"
},
{
"id": "eslint/no-duplicate-case",
"description": "Disallow duplicate case labels"
},
{
"id": "eslint/no-empty",
"description": "Disallow empty block statements"
},
{
"id": "eslint/no-empty-character-class",
"description": "Disallow empty character classes in regular expressions"
},
{
"id": "eslint/no-empty-pattern",
"description": "Disallow empty destructuring patterns"
},
{
"id": "eslint/no-empty-static-block",
"description": "Disallow empty static blocks"
},
{
"id": "eslint/no-ex-assign",
"description": "Disallow reassigning exceptions in catch clauses"
},
{
"id": "eslint/no-extra-boolean-cast",
"description": "Disallow unnecessary boolean casts"
},
{
"id": "eslint/no-fallthrough",
"description": "Disallow fallthrough of case statements"
},
{
"id": "eslint/no-func-assign",
"description": "Disallow reassigning function declarations"
},
{
"id": "eslint/no-global-assign",
"description": "Disallow assignments to native objects or read-only global variables"
},
{
"id": "eslint/no-import-assign",
"description": "Disallow assigning to imported bindings"
},
{
"id": "eslint/no-invalid-regexp",
"description": "Disallow invalid regular expression strings in RegExp constructors"
},
{
"id": "eslint/no-irregular-whitespace",
"description": "Disallow irregular whitespace"
},
{
"id": "eslint/no-loss-of-precision",
"description": "Disallow literal numbers that lose precision"
},
{
"id": "eslint/no-misleading-character-class",
"description": "Disallow characters which are made with multiple code points in character class syntax"
},
{
"id": "eslint/no-new-native-nonconstructor",
"description": "Disallow new operators with global non-constructor functions"
},
{
"id": "eslint/no-nonoctal-decimal-escape",
"description": "Disallow \\8 and \\9 escape sequences in string literals"
},
{
"id": "eslint/no-obj-calls",
"description": "Disallow calling global object properties as functions"
},
{
"id": "eslint/no-octal",
"description": "Disallow octal literals"
},
{
"id": "eslint/no-prototype-builtins",
"description": "Disallow calling some Object.prototype methods directly on objects"
},
{
"id": "eslint/no-redeclare",
"description": "Disallow variable redeclaration"
},
{
"id": "eslint/no-regex-spaces",
"description": "Disallow multiple spaces in regular expression literals"
},
{
"id": "eslint/no-self-assign",
"description": "Disallow assignments where both sides are exactly the same"
},
{
"id": "eslint/no-setter-return",
"description": "Disallow returning values from setters"
},
{
"id": "eslint/no-shadow-restricted-names",
"description": "Disallow identifiers from shadowing restricted names"
},
{
"id": "eslint/no-sparse-arrays",
"description": "Disallow sparse arrays"
},
{
"id": "eslint/no-this-before-super",
"description": "Disallow this/super before calling super() in constructors"
},
{
"id": "eslint/no-undef",
"description": "Disallow undeclared variables"
},
{
"id": "eslint/no-unexpected-multiline",
"description": "Disallow confusing multiline expressions"
},
{
"id": "eslint/no-unreachable",
"description": "Disallow unreachable code after return, throw, continue, and break statements"
},
{
"id": "eslint/no-unsafe-finally",
"description": "Disallow control flow statements in finally blocks"
},
{
"id": "eslint/no-unsafe-negation",
"description": "Disallow negating the left operand of relational operators"
},
{
"id": "eslint/no-unsafe-optional-chaining",
"description": "Disallow use of optional chaining in contexts where undefined is not allowed"
},
{
"id": "eslint/no-unused-labels",
"description": "Disallow unused labels"
},
{
"id": "eslint/no-unused-private-class-members",
"description": "Disallow unused private class members"
},
{
"id": "eslint/no-unused-vars",
"description": "Disallow unused variables"
},
{
"id": "eslint/no-useless-backreference",
"description": "Disallow useless backreferences in regular expressions"
},
{
"id": "eslint/no-useless-catch",
"description": "Disallow unnecessary catch clauses"
},
{
"id": "eslint/no-useless-escape",
"description": "Disallow unnecessary escape characters"
},
{
"id": "eslint/no-with",
"description": "Disallow with statements"
},
{
"id": "eslint/require-yield",
"description": "Require generator functions to contain yield"
},
{
"id": "eslint/use-isnan",
"description": "Require calls to isNaN() when checking for NaN"
},
{
"id": "eslint/valid-typeof",
"description": "Enforce comparing typeof expressions against valid strings"
}
],
"ts-eslint": [
{
"id": "ts-eslint/ban-ts-comment",
"description": "Disallow @ts-<directive> comments"
},
{
"id": "ts-eslint/no-array-constructor",
"description": "Disallow generic Array constructors"
},
{
"id": "ts-eslint/no-duplicate-enum-values",
"description": "Disallow duplicate enum member values"
},
{
"id": "ts-eslint/no-empty-object-type",
"description": "Disallow empty object types"
},
{
"id": "ts-eslint/no-explicit-any",
"description": "Disallow the any type"
},
{
"id": "ts-eslint/no-extra-non-null-assertion",
"description": "Disallow extra non-null assertions"
},
{
"id": "ts-eslint/no-misused-new",
"description": "Enforce valid definition of new and constructor"
},
{
"id": "ts-eslint/no-namespace",
"description": "Disallow custom TypeScript modules and namespaces"
},
{
"id": "ts-eslint/no-non-null-asserted-optional-chain",
"description": "Disallow non-null assertions after optional chain"
},
{
"id": "ts-eslint/no-require-imports",
"description": "Disallow invocation of require()"
},
{
"id": "ts-eslint/no-this-alias",
"description": "Disallow aliasing this"
},
{
"id": "ts-eslint/no-unnecessary-type-constraint",
"description": "Disallow unnecessary constraints on generic types"
},
{
"id": "ts-eslint/no-unsafe-declaration-merging",
"description": "Disallow unsafe declaration merging"
},
{
"id": "ts-eslint/no-unsafe-function-type",
"description": "Disallow using Function as a type"
},
{
"id": "ts-eslint/no-unused-expressions",
"description": "Disallow unused expressions"
},
{
"id": "ts-eslint/no-unused-vars",
"description": "Disallow unused variables"
},
{
"id": "ts-eslint/no-wrapper-object-types",
"description": "Disallow wrapper object types (String, Number, Boolean)"
},
{
"id": "ts-eslint/prefer-as-const",
"description": "Prefer as const over literal type annotation"
},
{
"id": "ts-eslint/prefer-namespace-keyword",
"description": "Require using namespace keyword over module keyword"
},
{
"id": "ts-eslint/triple-slash-reference",
"description": "Disallow certain triple slash directives"
},
{
"id": "ts-eslint/no-var",
"description": "Require let or const instead of var"
},
{
"id": "ts-eslint/prefer-const",
"description": "Require const declarations for never-reassigned variables"
},
{
"id": "ts-eslint/prefer-rest-params",
"description": "Require rest parameters instead of arguments"
},
{
"id": "ts-eslint/prefer-spread",
"description": "Require spread operator instead of .apply()"
}
],
"stylelint": [
{
"id": "stylelint/color-hex-length",
"description": "Specify short or long hexadecimal color values"
},
{
"id": "stylelint/color-named",
"description": "Require (where possible) or disallow named colors"
},
{
"id": "stylelint/color-no-invalid-hex",
"description": "Disallow invalid hex colors"
},
{
"id": "stylelint/length-zero-no-unit",
"description": "Disallow units for zero lengths"
},
{
"id": "stylelint/font-family-no-missing-generic-family-keyword",
"description": "Disallow missing generic families in font-family"
},
{
"id": "stylelint/block-no-empty",
"description": "Disallow empty blocks"
},
{
"id": "stylelint/declaration-block-no-duplicate-properties",
"description": "Disallow duplicate properties within declaration blocks"
},
{
"id": "stylelint/no-descending-specificity",
"description": "Disallow selectors of lower specificity from overriding higher specificity"
},
{
"id": "stylelint/unit-no-unknown",
"description": "Disallow unknown units"
},
{
"id": "stylelint/property-no-unknown",
"description": "Disallow unknown properties"
},
{
"id": "stylelint/selector-pseudo-class-no-unknown",
"description": "Disallow unknown pseudo-class selectors"
},
{
"id": "stylelint/selector-pseudo-element-no-unknown",
"description": "Disallow unknown pseudo-element selectors"
}
],
"pmd": [
{
"id": "pmd/AbstractClassWithoutAbstractMethod",
"description": "Abstract class does not contain any abstract methods"
},
{
"id": "pmd/AccessorClassGeneration",
"description": "Avoid instantiation through private constructors from outside"
},
{
"id": "pmd/AccessorMethodGeneration",
"description": "Avoid synthetic accessor methods"
},
{
"id": "pmd/ArrayIsStoredDirectly",
"description": "Clone objects before storing in constructors/methods"
},
{
"id": "pmd/AssertStatementInTest",
"description": "Assert statements should not be used in test code"
},
{
"id": "pmd/AvoidMessageDigestField",
"description": "Don't declare MessageDigest as field (thread safety)"
},
{
"id": "pmd/AvoidPrintStackTrace",
"description": "Use logger instead of printStackTrace()"
},
{
"id": "pmd/AvoidReassigningCatchVariables",
"description": "Don't reassign caught exception variables"
},
{
"id": "pmd/AvoidReassigningLoopVariables",
"description": "Don't reassign loop control variables"
},
{
"id": "pmd/AvoidReassigningParameters",
"description": "Don't reassign method parameters"
},
{
"id": "pmd/AvoidStringBufferField",
"description": "Avoid StringBuffer/StringBuilder as fields"
},
{
"id": "pmd/AvoidUsingHardCodedIP",
"description": "Externalize IP addresses"
},
{
"id": "pmd/CheckResultSet",
"description": "Always check navigation method return values of ResultSet"
},
{
"id": "pmd/ConstantsInInterface",
"description": "Avoid constants in interfaces"
},
{
"id": "pmd/DefaultLabelNotLastInSwitch",
"description": "Default label should be last in switch"
},
{
"id": "pmd/DoubleBraceInitialization",
"description": "Avoid double-brace initialization"
},
{
"id": "pmd/EnumComparison",
"description": "Compare enums with == not equals()"
},
{
"id": "pmd/ExhaustiveSwitchHasDefault",
"description": "Exhaustive switch should not have default case"
},
{
"id": "pmd/ForLoopCanBeForeach",
"description": "Replace for loop with foreach"
},
{
"id": "pmd/ForLoopVariableCount",
"description": "Limit control variables in for loops"
},
{
"id": "pmd/GuardLogStatement",
"description": "Check log level before logging"
},
{
"id": "pmd/ImplicitFunctionalInterface",
"description": "Annotate functional interfaces with @FunctionalInterface"
},
{
"id": "pmd/JUnit4SuitesShouldUseSuiteAnnotation",
"description": "Use @RunWith(Suite.class) annotation"
},
{
"id": "pmd/JUnitJupiterTestShouldBePackagePrivate",
"description": "JUnit 5 tests should be package-private"
},
{
"id": "pmd/JUnitUseExpected",
"description": "Use @Test(expected) annotation"
},
{
"id": "pmd/LabeledStatement",
"description": "Avoid labeled statements"
},
{
"id": "pmd/LiteralsFirstInComparisons",
"description": "Position literals first in String comparisons"
},
{
"id": "pmd/LooseCoupling",
"description": "Use interfaces instead of implementation types"
},
{
"id": "pmd/MethodReturnsInternalArray",
"description": "Return copy of internal array"
},
{
"id": "pmd/MissingOverride",
"description": "Add @Override annotation"
},
{
"id": "pmd/NonExhaustiveSwitch",
"description": "Switch should be exhaustive"
},
{
"id": "pmd/OneDeclarationPerLine",
"description": "One declaration per line"
},
{
"id": "pmd/PreserveStackTrace",
"description": "Preserve stack trace when rethrowing exceptions"
},
{
"id": "pmd/PrimitiveWrapperInstantiation",
"description": "Use valueOf() instead of new Type()"
},
{
"id": "pmd/RelianceOnDefaultCharset",
"description": "Specify charset explicitly"
},
{
"id": "pmd/ReplaceEnumerationWithIterator",
"description": "Use Iterator instead of Enumeration"
},
{
"id": "pmd/ReplaceHashtableWithMap",
"description": "Use Map instead of Hashtable"
},
{
"id": "pmd/ReplaceVectorWithList",
"description": "Use List/ArrayList instead of Vector"
},
{
"id": "pmd/SimplifiableTestAssertion",
"description": "Use more specific assertion methods"
},
{
"id": "pmd/SystemPrintln",
"description": "Use logger instead of System.out/err"
},
{
"id": "pmd/UnitTestAssertionsShouldIncludeMessage",
"description": "Include message in assertions"
},
{
"id": "pmd/UnitTestContainsTooManyAsserts",
"description": "Limit asserts per test"
},
{
"id": "pmd/UnitTestShouldIncludeAssert",
"description": "Test should include assertions"
},
{
"id": "pmd/UnitTestShouldUseAfterAnnotation",
"description": "Use @After/@AfterEach annotation"
},
{
"id": "pmd/UnitTestShouldUseBeforeAnnotation",
"description": "Use @Before/@BeforeEach annotation"
},
{
"id": "pmd/UnitTestShouldUseTestAnnotation",
"description": "Use @Test annotation"
},
{
"id": "pmd/UnnecessaryVarargsArrayCreation",
"description": "Don't create explicit array for varargs"
},
{
"id": "pmd/UnnecessaryWarningSuppression",
"description": "Remove unused PMD suppressions"
},
{
"id": "pmd/UnusedAssignment",
"description": "Remove unused assignments"
},
{
"id": "pmd/UnusedFormalParameter",
"description": "Remove unused parameters"
},
{
"id": "pmd/UnusedLabel",
"description": "Remove unused labels"
},
{
"id": "pmd/UnusedLocalVariable",
"description": "Remove unused local variables"
},
{
"id": "pmd/UnusedPrivateField",
"description": "Remove unused private fields"
},
{
"id": "pmd/UnusedPrivateMethod",
"description": "Remove unused private methods"
},
{
"id": "pmd/UseCollectionIsEmpty",
"description": "Use isEmpty() instead of size()==0"
},
{
"id": "pmd/UseEnumCollections",
"description": "Use EnumSet/EnumMap instead of HashSet/HashMap"
},
{
"id": "pmd/UseStandardCharsets",
"description": "Use StandardCharsets constants"
},
{
"id": "pmd/UseTryWithResources",
"description": "Use try-with-resources"
},
{
"id": "pmd/UseVarargs",
"description": "Use varargs instead of array parameter"
},
{
"id": "pmd/WhileLoopWithLiteralBoolean",
"description": "Simplify while loops with literal booleans"
},
{
"id": "pmd/AtLeastOneConstructor",
"description": "Each class should have a constructor"
},
{
"id": "pmd/AvoidDollarSigns",
"description": "Avoid $ in names"
},
{
"id": "pmd/AvoidProtectedFieldInFinalClass",
"description": "Don't use protected fields in final classes"
},
{
"id": "pmd/AvoidProtectedMethodInFinalClassNotExtending",
"description": "Don't use protected methods in final classes not extending"
},
{
"id": "pmd/AvoidUsingNativeCode",
"description": "Avoid JNI calls"
},
{
"id": "pmd/BooleanGetMethodName",
"description": "Boolean getters should be named isX()"
},
{
"id": "pmd/CallSuperInConstructor",
"description": "Call super() in constructor"
},
{
"id": "pmd/ClassNamingConventions",
"description": "PascalCase naming"
},
{
"id": "pmd/CommentDefaultAccessModifier",
"description": "Comment default access modifier"
},
{
"id": "pmd/ConfusingTernary",
"description": "Avoid negation in if with else"
},
{
"id": "pmd/ControlStatementBraces",
"description": "Require braces on control statements"
},
{
"id": "pmd/EmptyControlStatement",
"description": "Report empty control statements"
},
{
"id": "pmd/EmptyMethodInAbstractClassShouldBeAbstract",
"description": "Empty methods in abstract classes should be abstract"
},
{
"id": "pmd/ExtendsObject",
"description": "No need to explicitly extend Object"
},
{
"id": "pmd/FieldDeclarationsShouldBeAtStartOfClass",
"description": "Fields at top of class"
},
{
"id": "pmd/FieldNamingConventions",
"description": "Configurable field naming conventions"
},
{
"id": "pmd/FinalParameterInAbstractMethod",
"description": "Final parameter in abstract method is useless"
},
{
"id": "pmd/ForLoopShouldBeWhileLoop",
"description": "Simplify for loops to while"
},
{
"id": "pmd/FormalParameterNamingConventions",
"description": "Parameter naming conventions"
},
{
"id": "pmd/GenericsNaming",
"description": "Single uppercase letter for generics"
},
{
"id": "pmd/IdenticalCatchBranches",
"description": "Collapse identical catch branches"
},
{
"id": "pmd/LambdaCanBeMethodReference",
"description": "Replace lambda with method reference"
},
{
"id": "pmd/LinguisticNaming",
"description": "Method name/return type consistency"
},
{
"id": "pmd/LocalHomeNamingConvention",
"description": "EJB LocalHome suffix"
},
{
"id": "pmd/LocalInterfaceSessionNamingConvention",
"description": "EJB Local suffix"
},
{
"id": "pmd/LocalVariableCouldBeFinal",
"description": "Declare local variables final when possible"
},
{
"id": "pmd/LocalVariableNamingConventions",
"description": "Variable naming conventions"
},
{
"id": "pmd/LongVariable",
"description": "Avoid excessively long variable names (>17 chars)"
},
{
"id": "pmd/MDBAndSessionBeanNamingConvention",
"description": "EJB Bean suffix"
},
{
"id": "pmd/MethodArgumentCouldBeFinal",
"description": "Declare parameters final when possible"
},
{
"id": "pmd/MethodNamingConventions",
"description": "Method naming conventions"
},
{
"id": "pmd/ModifierOrder",
"description": "Enforce JLS modifier order"
},
{
"id": "pmd/NoPackage",
"description": "All types must belong to a named package"
},
{
"id": "pmd/OnlyOneReturn",
"description": "Single exit point per method"
},
{
"id": "pmd/PackageCase",
"description": "Package names lowercase"
},
{
"id": "pmd/PrematureDeclaration",
"description": "Declare variables close to usage"
},
{
"id": "pmd/UselessParentheses",
"description": "Remove unnecessary parentheses"
},
{
"id": "pmd/UselessQualifiedThis",
"description": "Remove unnecessary qualified this"
},
{
"id": "pmd/UnnecessaryAnnotationValueElement",
"description": "Remove unnecessary annotation value element"
},
{
"id": "pmd/UnnecessaryBoxing",
"description": "Avoid unnecessary boxing"
},
{
"id": "pmd/UnnecessaryCast",
"description": "Remove unnecessary casts"
},
{
"id": "pmd/UnnecessaryConstructor",
"description": "Remove unnecessary constructors"
},
{
"id": "pmd/UnnecessaryFullyQualifiedName",
"description": "Remove unnecessary fully qualified names"
},
{
"id": "pmd/UnnecessaryLocalBeforeReturn",
"description": "Remove unnecessary local before return"
},
{
"id": "pmd/UnnecessaryModifier",
"description": "Remove unnecessary modifiers"
},
{
"id": "pmd/UnnecessaryReturn",
"description": "Remove unnecessary returns"
},
{
"id": "pmd/UnnecessarySemicolon",
"description": "Remove unnecessary semicolons"
},
{
"id": "pmd/UnnecessaryUnboxing",
"description": "Avoid unnecessary unboxing"
},
{
"id": "pmd/UpperLowerCaseNamingConventions",
"description": "Naming conventions for cases"
},
{
"id": "pmd/UseShortArrayInitializer",
"description": "Use short array initializer"
},
{
"id": "pmd/AbstractClassWithoutAnyMethod",
"description": "Abstract class without methods should use private constructor"
},
{
"id": "pmd/AvoidDeeplyNestedIfStmts",
"description": "Avoid deeply nested if statements"
},
{
"id": "pmd/AvoidRethrowingException",
"description": "Avoid catch-and-rethrow"
},
{
"id": "pmd/AvoidThrowingNewInstanceOfSameException",
"description": "Avoid wrapping same exception type"
},
{
"id": "pmd/AvoidThrowingNullPointerException",
"description": "Don't throw NPE manually"
},
{
"id": "pmd/AvoidThrowingRawExceptionTypes",
"description": "Don't throw raw Exception/RuntimeException/Throwable/Error"
},
{
"id": "pmd/AvoidUncheckedExceptionsInSignatures",
"description": "Don't declare unchecked exceptions in throws"
},
{
"id": "pmd/ClassWithOnlyPrivateConstructorsShouldBeFinal",
"description": "Make class final if only private constructors"
},
{
"id": "pmd/CognitiveComplexity",
"description": "Methods with high cognitive complexity"
},
{
"id": "pmd/CollapsibleIfStatements",
"description": "Merge nested if statements"
},
{
"id": "pmd/CouplingBetweenObjects",
"description": "High coupling threshold"
},
{
"id": "pmd/CyclomaticComplexity",
"description": "High cyclomatic complexity"
},
{
"id": "pmd/DataClass",
"description": "Suspected Data Class"
},
{
"id": "pmd/DoNotExtendJavaLangError",
"description": "Don't extend Error"
},
{
"id": "pmd/ExceptionAsFlowControl",
"description": "Don't use exceptions for flow control"
},
{
"id": "pmd/ExcessiveImports",
"description": "Too many imports"
},
{
"id": "pmd/ExcessiveParameterList",
"description": "Too many parameters"
},
{
"id": "pmd/ExcessivePublicCount",
"description": "Too many public methods/attributes"
},
{
"id": "pmd/FinalFieldCouldBeStatic",
"description": "Make final field static if compile-time constant"
},
{
"id": "pmd/GodClass",
"description": "God Class detection"
},
{
"id": "pmd/ImmutableField",
"description": "Field could be final"
},
{
"id": "pmd/InvalidJavaBean",
"description": "Bean doesn't follow JavaBeans spec"
},
{
"id": "pmd/LawOfDemeter",
"description": "Potential LoD violation"
},
{
"id": "pmd/LogicInversion",
"description": "Use opposite operator instead of !"
},
{
"id": "pmd/LoosePackageCoupling",
"description": "Avoid using classes from outside package hierarchy"
},
{
"id": "pmd/MutableStaticState",
"description": "Non-private non-final static fields"
},
{
"id": "pmd/NcssCount",
"description": "Non-Commenting Source Statements metric"
},
{
"id": "pmd/NPathComplexity",
"description": "NPath complexity threshold"
},
{
"id": "pmd/PublicMemberInNonPublicType",
"description": "Public member in non-public type"
},
{
"id": "pmd/SignatureDeclareThrowsException",
"description": "Don't declare throws Exception"
},
{
"id": "pmd/SimplifiedTernary",
"description": "Simplify ternary with boolean literals"
},
{
"id": "pmd/SimplifyBooleanExpressions",
"description": "Remove unnecessary boolean comparisons"
},
{
"id": "pmd/SimplifyBooleanReturns",
"description": "Simplify boolean returns"
},
{
"id": "pmd/SimplifyConditional",
"description": "Simplify conditional expressions"
},
{
"id": "pmd/SingularField",
"description": "Field may be local variable"
},
{
"id": "pmd/TooManyFields",
"description": "Too many fields"
},
{
"id": "pmd/TooManyMethods",
"description": "Too many methods"
},
{
"id": "pmd/UselessOverridingMethod",
"description": "Useless overriding method"
},
{
"id": "pmd/AssertEqualsArgumentOrder",
"description": "assertEquals expected/actual swapped"
},
{
"id": "pmd/AssignmentInOperand",
"description": "Avoid assignments in operands"
},
{
"id": "pmd/AssignmentToNonFinalStatic",
"description": "Unsafe static field assignment in constructor"
},
{
"id": "pmd/AvoidAccessibilityAlteration",
"description": "Don't use setAccessible(true)"
},
{
"id": "pmd/AvoidAssertAsIdentifier",
"description": "assert is reserved word (Java <1.4)"
},
{
"id": "pmd/AvoidBranchingStatementAsLastInLoop",
"description": "Branching statement as last in loop"
},
{
"id": "pmd/AvoidCallingFinalize",
"description": "Don't call finalize() explicitly"
},
{
"id": "pmd/AvoidCatchingGenericException",
"description": "Don't catch generic exceptions"
},
{
"id": "pmd/AvoidDecimalLiteralsInBigDecimalConstructor",
"description": "Use String constructor for BigDecimal"
},
{
"id": "pmd/AvoidDuplicateLiterals",
"description": "Avoid duplicate String literals"
},
{
"id": "pmd/AvoidEnumAsIdentifier",
"description": "enum is reserved word (Java <1.5)"
},
{
"id": "pmd/AvoidFieldNameMatchingMethodName",
"description": "Field name matching method name"
},
{
"id": "pmd/AvoidFieldNameMatchingTypeName",
"description": "Field name matching type name"
},
{
"id": "pmd/AvoidInstanceofChecksInCatchClause",
"description": "Use separate catch clauses"
},
{
"id": "pmd/AvoidLiteralsInIfCondition",
"description": "Avoid magic numbers in if conditions"
},
{
"id": "pmd/AvoidMultipleUnaryOperators",
"description": "Avoid multiple unary operators"
},
{
"id": "pmd/AvoidUsingOctalValues",
"description": "Avoid octal literals"
},
{
"id": "pmd/BrokenNullCheck",
"description": "Broken null check (|| vs &&)"
},
{
"id": "pmd/CallSuperFirst",
"description": "super should be called first"
},
{
"id": "pmd/CallSuperLast",
"description": "super should be called last"
},
{
"id": "pmd/CheckSkipResult",
"description": "Check skip() return value"
},
{
"id": "pmd/ClassCastExceptionWithToArray",
"description": "Collection.toArray() ClassCastException"
},
{
"id": "pmd/CloneMethodMustBePublic",
"description": "clone() must be public if Cloneable"
},
{
"id": "pmd/CloneMethodMustImplementCloneable",
"description": "clone() only if Cloneable"
},
{
"id": "pmd/CloneMethodReturnTypeMustMatchClassName",
"description": "clone() covariant return type"
},
{
"id": "pmd/CloseResource",
"description": "Ensure resources are closed"
},
{
"id": "pmd/CollectionTypeMismatch",
"description": "Type mismatch in collection methods"
},
{
"id": "pmd/CompareObjectsWithEquals",
"description": "Use equals() not == for objects"
},
{
"id": "pmd/ComparisonWithNaN",
"description": "NaN comparisons always return false"
},
{
"id": "pmd/ConfusingArgumentToVarargsMethod",
"description": "Clarify varargs intent"
},
{
"id": "pmd/ConstructorCallsOverridableMethod",
"description": "Constructor calls overridable method"
},
{
"id": "pmd/DataflowAnomalyAnalysis",
"description": "Data flow anomalies"
},
{
"id": "pmd/DoNotCallGarbageCollectionExplicitly",
"description": "Don't call System.gc()"
},
{
"id": "pmd/DoNotCallSystemExit",
"description": "Don't call System.exit()"
},
{
"id": "pmd/DoNotHardCodeSDCard",
"description": "Don't hardcode /sdcard path"
},
{
"id": "pmd/DoNotThrowExceptionInFinally",
"description": "Don't throw in finally"
},
{
"id": "pmd/DoNotUseThreads",
"description": "Don't use Threads"
},
{
"id": "pmd/DontImportSun",
"description": "Don't import sun.* packages"
},
{
"id": "pmd/EmptyCatchBlock",
"description": "Empty catch blocks"
},
{
"id": "pmd/EqualsNull",
"description": "Equal comparison to null"
},
{
"id": "pmd/FinallyBlockDoesNothing",
"description": "Finally block does nothing"
},
{
"id": "pmd/IdempotentOperations",
"description": "Idempotent operations"
},
{
"id": "pmd/ImportFromSamePackage",
"description": "Import from same package"
},
{
"id": "pmd/InstantiationToGetClass",
"description": "Instantiation just to get class"
},
{
"id": "pmd/InvalidLogMessageFormat",
"description": "Invalid SLF4J message format"
},
{
"id": "pmd/JUnitSpelling",
"description": "JUnit method spelling"
},
{
"id": "pmd/JUnitStaticSuite",
"description": "JUnit static suite method"
},
{
"id": "pmd/JumbledIncrementer",
"description": "Jumbled incrementer"
},
{
"id": "pmd/LoggerIsNotStaticFinal",
"description": "Logger not static final"
},
{
"id": "pmd/MethodWithSameNameAsEnclosingClass",
"description": "Method same name as enclosing class"
},
{
"id": "pmd/MisplacedNullCheck",
"description": "Misplaced null check"
},
{
"id": "pmd/MissingBreakInSwitch",
"description": "Missing break in switch"
},
{
"id": "pmd/MissingSerialVersionUID",
"description": "Missing serialVersionUID"
},
{
"id": "pmd/MissingStaticMethodInNonInstantiatableClass",
"description": "Non-instantiatable class missing static method"
},
{
"id": "pmd/MoreThanOneLogger",
"description": "More than one logger"
},
{
"id": "pmd/NonCaseLabelInSwitch",
"description": "Non-case label in switch"
},
{
"id": "pmd/NonStaticInitializer",
"description": "Non-static initializer"
},
{
"id": "pmd/NullAssignment",
"description": "Null assignment"
},
{
"id": "pmd/NumberConstructor",
"description": "Number constructor (deprecated)"
},
{
"id": "pmd/ObjectFinalize",
"description": "Object finalize issues"
},
{
"id": "pmd/OperationWithCloning",
"description": "Operation with cloning"
},
{
"id": "pmd/PackageDeclaration",
"description": "Package declaration"
},
{
"id": "pmd/ProperCloneImplementation",
"description": "Proper clone implementation"
},
{
"id": "pmd/ProperLogger",
"description": "Proper logger"
},
{
"id": "pmd/ReturnFromFinallyBlock",
"description": "Return from finally"
},
{
"id": "pmd/SimpleDateFormatNeedsLocale",
"description": "SimpleDateFormat needs locale"
},
{
"id": "pmd/SingleMethodSingleton",
"description": "Singleton pattern issues"
},
{
"id": "pmd/SingletonClassReturningNewInstance",
"description": "Singleton returning new instance"
},
{
"id": "pmd/StaticEJBFieldShouldBeFinal",
"description": "Static EJB field should be final"
},
{
"id": "pmd/StringBufferInstantiationWithChar",
"description": "StringBuffer with char"
},
{
"id": "pmd/SuspiciousConstantFieldName",
"description": "Constant field naming"
},
{
"id": "pmd/SuspiciousEqualsMethodName",
"description": "equals() method signature"
},
{
"id": "pmd/SuspiciousHashcodeMethodName",
"description": "hashCode() method signature"
},
{
"id": "pmd/SuspiciousOctalEscape",
"description": "Suspicious octal escape"
},
{
"id": "pmd/TestClassWithoutTestCases",
"description": "Test class without test cases"
},
{
"id": "pmd/UnconditionalIfStatement",
"description": "Unconditional if statement"
},
{
"id": "pmd/UnnecessaryBooleanAssertion",
"description": "Unnecessary boolean assertion"
},
{
"id": "pmd/UnnecessaryCaseChange",
"description": "Unnecessary case change"
},
{
"id": "pmd/UnnecessaryConversionTemporal",
"description": "Unnecessary temporal conversion"
},
{
"id": "pmd/UnusedNullCheckInEquals",
"description": "Unused null check in equals"
},
{
"id": "pmd/UseCorrectExceptionLogging",
"description": "Correct exception logging"
},
{
"id": "pmd/UseEqualsToCompareStrings",
"description": "Use equals() for strings"
},
{
"id": "pmd/UselessOperationOnImmutable",
"description": "Useless operation on immutable"
},
{
"id": "pmd/UseLocaleWithCaseConversions",
"description": "Use locale with case conversions"
},
{
"id": "pmd/UseProperClassLoader",
"description": "Use proper classloader"
},
{
"id": "pmd/AddEmptyString",
"description": "Don't add empty strings"
},
{
"id": "pmd/AppendCharacterWithChar",
"description": "Append char not string in StringBuffer"
},
{
"id": "pmd/AvoidArrayLoops",
"description": "Use Arrays.copyOf or System.arraycopy"
},
{
"id": "pmd/AvoidCalendarDateCreation",
"description": "Avoid Calendar for current time"
},
{
"id": "pmd/AvoidFileStream",
"description": "Avoid FileInputStream/FileOutputStream/FileReader/FileWriter"
},
{
"id": "pmd/AvoidInstantiatingObjectsInLoops",
"description": "Don't instantiate objects in loops"
},
{
"id": "pmd/BigIntegerInstantiation",
"description": "Use BigInteger.ZERO/ONE/TEN"
},
{
"id": "pmd/ConsecutiveAppendsShouldReuse",
"description": "Chain StringBuilder.append calls"
},
{
"id": "pmd/ConsecutiveLiteralAppends",
"description": "Combine literal appends"
},
{
"id": "pmd/InefficientEmptyStringCheck",
"description": "Use isBlank() instead of trim().isEmpty()"
},
{
"id": "pmd/InefficientStringBuffering",
"description": "Avoid concatenating in StringBuffer constructor"
},
{
"id": "pmd/InsufficientStringBufferDeclaration",
"description": "Pre-size StringBuilder"
},
{
"id": "pmd/OptimizableToArrayCall",
"description": "Use new Foo[0] instead of new Foo[size]"
},
{
"id": "pmd/RedundantFieldInitializer",
"description": "Remove redundant field initializers"
},
{
"id": "pmd/StringInstantiation",
"description": "Avoid new String()"
},
{
"id": "pmd/StringToString",
"description": "Avoid toString() on String"
},
{
"id": "pmd/TooFewBranchesForSwitch",
"description": "Switch with less than 3 branches"
},
{
"id": "pmd/UseArrayListInsteadOfVector",
"description": "ArrayList instead of Vector"
},
{
"id": "pmd/UseArraysAsList",
"description": "Use Arrays.asList() instead of loop"
},
{
"id": "pmd/UseIndexOfChar",
"description": "Use indexOf(char) not indexOf(String)"
},
{
"id": "pmd/UseIOStreamsWithApacheCommonsFileItem",
"description": "Use getInputStream() not get()"
},
{
"id": "pmd/UselessStringValueOf",
"description": "Don't wrap with String.valueOf()"
},
{
"id": "pmd/UseStringBufferForStringAppends",
"description": "Use StringBuilder for concatenation"
},
{
"id": "pmd/UseStringBufferLength",
"description": "Use length() instead of toString().equals(\"\")"
},
{
"id": "pmd/HardCodedCryptoKey",
"description": "Don't hard code encryption keys"
},
{
"id": "pmd/InsecureCryptoIv",
"description": "Don't hard code initialization vectors"
}
],
"pmd-jsp": [
{
"id": "pmd-jsp/DontNestJsfInJstlIteration",
"description": "Do not nest JSF components inside JSTL iteration"
},
{
"id": "pmd-jsp/NoClassAttribute",
"description": "Use styleclass not class attribute"
},
{
"id": "pmd-jsp/NoHtmlComments",
"description": "Use JSP comments instead of HTML comments"
},
{
"id": "pmd-jsp/NoJspForward",
"description": "Do not forward from within a JSP"
},
{
"id": "pmd-jsp/DuplicateJspImports",
"description": "Avoid duplicate imports in JSP"
},
{
"id": "pmd-jsp/NoInlineScript",
"description": "Externalize HTML script content"
},
{
"id": "pmd-jsp/NoInlineStyleInformation",
"description": "Put styles in CSS files"
},
{
"id": "pmd-jsp/NoLongScripts",
"description": "Avoid long scripts in JSP"
},
{
"id": "pmd-jsp/NoScriptlets",
"description": "Avoid scriptlets in JSP"
},
{
"id": "pmd-jsp/JspEncoding",
"description": "JSP files should use UTF-8 encoding"
}
],
"sql-lint": [
{
"id": "sql-lint/AL01",
"description": "Implicit/explicit aliasing of table"
},
{
"id": "sql-lint/AL02",
"description": "Implicit/explicit aliasing of columns"
},
{
"id": "sql-lint/AL03",
"description": "Column expression without alias"
},
{
"id": "sql-lint/AL04",
"description": "Table aliases should be unique within each clause"
},
{
"id": "sql-lint/AL05",
"description": "Tables should not be aliased if unused"
},
{
"id": "sql-lint/AL06",
"description": "Enforce table alias lengths"
},
{
"id": "sql-lint/AL07",
"description": "Avoid table aliases"
},
{
"id": "sql-lint/AL08",
"description": "Column aliases should be unique within each clause"
},
{
"id": "sql-lint/AL09",
"description": "Column aliases should not alias to itself"
},
{
"id": "sql-lint/AL10",
"description": "Derived tables must have an alias"
},
{
"id": "sql-lint/AM01",
"description": "Ambiguous use of DISTINCT with GROUP BY"
},
{
"id": "sql-lint/AM02",
"description": "UNION DISTINCT/ALL preferred over just UNION"
},
{
"id": "sql-lint/AM03",
"description": "Ambiguous ordering directions"
},
{
"id": "sql-lint/AM04",
"description": "Query produces unknown number of result columns"
},
{
"id": "sql-lint/AM05",
"description": "Join clauses should be fully qualified"
},
{
"id": "sql-lint/AM06",
"description": "Inconsistent column references in GROUP BY/ORDER BY"
},
{
"id": "sql-lint/AM07",
"description": "Queries within set query produce different numbers of columns"
},
{
"id": "sql-lint/AM08",
"description": "Implicit cross join detected"
},
{
"id": "sql-lint/AM09",
"description": "LIMIT/OFFSET without ORDER BY non-deterministic"
},
{
"id": "sql-lint/CP01",
"description": "Inconsistent capitalisation of keywords"
},
{
"id": "sql-lint/CP02",
"description": "Inconsistent capitalisation of unquoted identifiers"
},
{
"id": "sql-lint/CP03",
"description": "Inconsistent capitalisation of function names"
},
{
"id": "sql-lint/CP04",
"description": "Inconsistent capitalisation of boolean/null literal"
},
{
"id": "sql-lint/CP05",
"description": "Inconsistent capitalisation of datatypes"
},
{
"id": "sql-lint/CV01",
"description": "Consistent usage of != or <>"
},
{
"id": "sql-lint/CV02",
"description": "Use COALESCE instead of IFNULL/NVL"
},
{
"id": "sql-lint/CV03",
"description": "Trailing commas within select clause"
},
{
"id": "sql-lint/CV04",
"description": "Consistent syntax for count number of rows"
},
{
"id": "sql-lint/CV05",
"description": "Comparisons with NULL should use IS or IS NOT"
},
{
"id": "sql-lint/CV06",
"description": "Statements must end with a semi-colon"
},
{
"id": "sql-lint/CV07",
"description": "Top-level statements should not be wrapped in brackets"
},
{
"id": "sql-lint/CV08",
"description": "Use LEFT JOIN instead of RIGHT JOIN"
},
{
"id": "sql-lint/CV09",
"description": "Block a list of configurable words"
},
{
"id": "sql-lint/CV10",
"description": "Consistent usage of preferred quotes for quoted literals"
},
{
"id": "sql-lint/CV11",
"description": "Enforce consistent type casting style"
},
{
"id": "sql-lint/CV12",
"description": "Use JOIN ... ON ... instead of WHERE ... for join conditions"
},
{
"id": "sql-lint/JJ01",
"description": "Jinja tags should have single whitespace on either side"
},
{
"id": "sql-lint/LT01",
"description": "Inappropriate Spacing"
},
{
"id": "sql-lint/LT02",
"description": "Incorrect Indentation"
},
{
"id": "sql-lint/LT03",
"description": "Operators before/after newlines"
},
{
"id": "sql-lint/LT04",
"description": "Leading/Trailing comma enforcement"
},
{
"id": "sql-lint/LT05",
"description": "Line is too long"
},
{
"id": "sql-lint/LT06",
"description": "Function name not followed by parenthesis"
},
{
"id": "sql-lint/LT07",
"description": "WITH clause closing bracket on new line"
},
{
"id": "sql-lint/LT08",
"description": "Blank line after CTE closing bracket"
},
{
"id": "sql-lint/LT09",
"description": "Select targets on new line"
},
{
"id": "sql-lint/LT10",
"description": "SELECT modifiers on same line as SELECT"
},
{
"id": "sql-lint/LT11",
"description": "Set operators surrounded by newlines"
},
{
"id": "sql-lint/LT12",
"description": "Files must end with single trailing newline"
},
{
"id": "sql-lint/LT13",
"description": "Files must not begin with newlines/whitespace"
},
{
"id": "sql-lint/LT14",
"description": "Keyword clauses before/after newlines"
},
{
"id": "sql-lint/LT15",
"description": "Too many consecutive blank lines"
},
{
"id": "sql-lint/OR01",
"description": "Remove empty batches"
},
{
"id": "sql-lint/PG01",
"description": "Avoid excessive locks in PostgreSQL DDL"
},
{
"id": "sql-lint/RF01",
"description": "References cannot reference objects not in FROM clause"
},
{
"id": "sql-lint/RF02",
"description": "References should be qualified if multiple tables"
},
{
"id": "sql-lint/RF03",
"description": "Column references consistent in single table statements"
},
{
"id": "sql-lint/RF04",
"description": "Keywords should not be used as identifiers"
},
{
"id": "sql-lint/RF05",
"description": "No special characters in identifiers"
},
{
"id": "sql-lint/RF06",
"description": "Unnecessary quoted identifier"
},
{
"id": "sql-lint/ST01",
"description": "Do not specify else null in CASE WHEN"
},
{
"id": "sql-lint/ST02",
"description": "Unnecessary CASE statement"
},
{
"id": "sql-lint/ST03",
"description": "Unused CTE"
},
{
"id": "sql-lint/ST04",
"description": "Nested CASE in ELSE clause can be flattened"
},
{
"id": "sql-lint/ST05",
"description": "Subqueries in Join/From clauses; use CTEs"
},
{
"id": "sql-lint/ST06",
"description": "Column order: wildcards, simple targets, then calculations"
},
{
"id": "sql-lint/ST07",
"description": "Prefer ON over USING for join keys"
},
{
"id": "sql-lint/ST08",
"description": "DISTINCT used with parentheses"
},
{
"id": "sql-lint/ST09",
"description": "Join condition order"
},
{
"id": "sql-lint/ST10",
"description": "Redundant constant expression"
},
{
"id": "sql-lint/ST11",
"description": "Joined table not referenced"
},
{
"id": "sql-lint/ST12",
"description": "Consecutive semicolons"
},
{
"id": "sql-lint/TQ01",
"description": "SP_ prefix should not be used for user-defined stored procedures"
},
{
"id": "sql-lint/TQ02",
"description": "Procedure bodies with multiple statements wrapped in BEGIN/END"
},
{
"id": "sql-lint/TQ03",
"description": "Remove empty batches"
}
]
}
}