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:
@@ -115,3 +115,8 @@
|
||||
| 2026-07-26 14:16 | ① 用户提出 → ② 需求澄清 → ③ 方案设计 → ④ 人类审批 → ⑤ 编码实现 → ⑥ 审查验证 | AI 转换器输出语言修复:5 个 converter(md/txt/excel/docx/pptx)和 import-service.ts fallback prompt 去掉字段描述中的(中文)硬编码,追加 `\n输出语言:${getAIOutputLanguage()}` | 无 | src/rules/converters/md-converter.ts src/rules/converters/txt-converter.ts src/rules/converters/excel-converter.ts src/rules/converters/docx-converter.ts src/rules/converters/pptx-converter.ts src/rules/import-service.ts | deepseek-v4-flash |
|
||||
| 2026-07-26 14:54 | ① 用户提出 → ② 需求澄清 → ③ 方案设计 → ④ 人类审批 → ⑤ 编码实现 → ⑥ 审查验证 | 规则名称空值时输入框红框+错误提示文字 | 无 | src/i18n/messages.ts src/views/setupView.ts src/views/setupView.js | deepseek-v4-flash-free |
|
||||
| 2026-07-26 15:26 | ① 用户提出 → ⑤ 编码实现 → ⑥ 审查验证 | repairJsonEscapes 去掉 replace 无差别解引号,防止 AI 正确转义的 \" 被破坏导致 JSON 解析失败 | 无 | src/ai/engine.ts | deepseek-v4-flash-free |
|
||||
| 2026-07-26 21:43 | ① 用户提出 → ② 需求澄清 → ③ 方案设计 → ④ 人类审批 → ⑤ 编码实现 → ⑥ 审查验证 | 自定义规则导入体验改进:提示词增强(集中到 prompt-builder.ts 的 buildSystemPrompt,增加输入容忍说明/非规则过滤/id+severity推断/description+message互推/对照示例)+ 解析侧兜底(severity缺/非法→warning,id缺→rule-N,desc+msg皆缺→丢弃)+ 预览 id 可编辑(header+panel 双 input 同步,占位 id 橙色高亮,校验加 id 非空)+ 5 个 converter 移除本地 buildSystemPrompt 改为调用公共函数 + 8 个新增测试 | 设计文档 docs/superpowers/specs/2026-07-26-custom-rule-import-ux-design.md(用户已提供);Stage ② 澄清:导入入口指引去掉/desc-message互填策略「AI提示词要求,解析侧仅两人皆缺才丢弃」/提示词抽公共/id两处同步/占位 id 保留高亮 | src/rules/converters/prompt-builder.ts src/rules/converters/md-converter.ts src/rules/converters/txt-converter.ts src/rules/converters/docx-converter.ts src/rules/converters/pptx-converter.ts src/rules/converters/excel-converter.ts src/rules/import-service.ts src/rules/import-preview.ts src/i18n/messages.ts src/test/import-dedup.test.ts | deepseek-v4-pro |
|
||||
| 2026-07-26 22:00 | ① 用户提出 → ② 需求澄清 → ③ 方案设计 → ④ 人类审批 → ⑤ 编码实现 → ⑥ 审查验证 | 语言下拉框选项文本固定为母语名称(中文(简体)/English/日本語),不再随界面语言切换而翻译;删除未使用的 getLanguageLabel 函数和 languageLabel 字段 | 无 | src/views/setupView.ts | deepseek-v4-flash |
|
||||
| 2026-07-26 22:28 | ① 用户提出 → ② 需求澄清 → ③ 方案设计 → ④ 人类审批 → ⑤ 编码实现 → ⑥ 审查验证 | AI 规则导入提示词三语支持:prompt-builder.ts 重构为多语言模板(zh-CN/en/ja),AI 直接看到目标语言的完整提示词,不再依赖单行语言指令切换;import-service.ts fallback prompt 和 YAML 注释(重复/重叠/手动注释等)通过 t() 本地化 | 配置项设计:先用 Record<Language, PromptStrings> 结构组织多语言文本 | src/rules/converters/prompt-builder.ts src/rules/import-service.ts src/i18n/messages.ts | deepseek-v4-flash |
|
||||
| 2026-07-26 22:41 | ⑤ 编码实现 → ⑥ 审查验证 | 修复日语提示词遗漏:角色描述开头追加「すべての説明とメッセージは日本語で出力してください」;finalInstruction 追加日语输出指令;示例标签 输入/输出/Input/Output 按语言本地化 | 无 | src/rules/converters/prompt-builder.ts | deepseek-v4-flash |
|
||||
| 2026-07-26 22:51 | ① 用户提出 → ② 需求澄清 → ③ 方案设计 → ④ 人类审批 → ⑤ 编码实现 → ⑥ 审查验证 | AI 引擎提示词三语化:engine.ts 的 CUSTOM_RULE_SYSTEM_PROMPT 和 DEEP_REVIEW_SYSTEM_PROMPT 从硬编码中文改为三语模板(zh-CN/en/ja),内置输出语言指令;parseJsonResponse 错误消息改为 t() 本地化;用户消息标签(自定义规则/代码/静态分析结果)按语言本地化 | 无 | src/ai/engine.ts src/i18n/messages.ts | deepseek-v4-flash |
|
||||
|
||||
@@ -0,0 +1,896 @@
|
||||
import * as vscode from 'vscode';
|
||||
|
||||
export type Language = 'zh-CN' | 'en' | 'ja';
|
||||
|
||||
const defaultLang: Language = 'zh-CN';
|
||||
|
||||
const messages: Record<string, Record<Language, string>> = {
|
||||
'review.noEditor': {
|
||||
'zh-CN': '请先打开一个文件',
|
||||
en: 'Please open a file first',
|
||||
ja: '最初にファイルを開いてください',
|
||||
},
|
||||
'review.noSelection': {
|
||||
'zh-CN': '请先选中要审查的代码',
|
||||
en: 'Please select code to review first',
|
||||
ja: 'レビューするコードを選択してください',
|
||||
},
|
||||
'review.needApiKey': {
|
||||
'zh-CN': '请先在设置面板中配置 API Key',
|
||||
en: 'Please configure API Key in Setup first',
|
||||
ja: '最初に設定パネルでAPIキーを設定してください',
|
||||
},
|
||||
'review.running': {
|
||||
'zh-CN': '正在审查...',
|
||||
en: 'Reviewing...',
|
||||
ja: 'レビュー中...',
|
||||
},
|
||||
'review.staticAnalysis': {
|
||||
'zh-CN': '运行静态分析...',
|
||||
en: 'Running static analysis...',
|
||||
ja: '静的解析を実行中...',
|
||||
},
|
||||
'review.aiReview': {
|
||||
'zh-CN': '运行 AI 审查...',
|
||||
en: 'Running AI review...',
|
||||
ja: 'AIレビューを実行中...',
|
||||
},
|
||||
'review.reviewingSelection': {
|
||||
'zh-CN': '审查选中代码...',
|
||||
en: 'Reviewing selected code...',
|
||||
ja: '選択したコードをレビュー中...',
|
||||
},
|
||||
'review.selectionComplete': {
|
||||
'zh-CN': '选中代码审查完成: {0} 个问题',
|
||||
en: 'Selection review complete: {0} issue(s)',
|
||||
ja: '選択コードのレビュー完了: {0} 件の問題',
|
||||
},
|
||||
'review.fixNotAvailable': {
|
||||
'zh-CN': '单条修复功能开发中',
|
||||
en: 'Single fix is under development',
|
||||
ja: '単一修正機能は開発中です',
|
||||
},
|
||||
'review.fixAllNotAvailable': {
|
||||
'zh-CN': '批量修复功能开发中',
|
||||
en: 'Batch fix is under development',
|
||||
ja: '一括修正機能は開発中です',
|
||||
},
|
||||
|
||||
'export.needRunFirst': {
|
||||
'zh-CN': '请先运行完整审查生成报告',
|
||||
en: 'Run a full review first to generate a report',
|
||||
ja: '最初に完全レビューを実行してレポートを生成してください',
|
||||
},
|
||||
'export.copyToClipboard': {
|
||||
'zh-CN': '📋 复制到剪贴板',
|
||||
en: '📋 Copy to Clipboard',
|
||||
ja: '📋 クリップボードにコピー',
|
||||
},
|
||||
'export.copyDescription': {
|
||||
'zh-CN': '将报告内容以 Markdown 格式复制到剪贴板',
|
||||
en: 'Copy report in Markdown format to clipboard',
|
||||
ja: 'レポートをMarkdown形式でクリップボードにコピー',
|
||||
},
|
||||
'export.downloadMarkdown': {
|
||||
'zh-CN': '📄 下载 Markdown 文件',
|
||||
en: '📄 Download Markdown File',
|
||||
ja: '📄 Markdownファイルをダウンロード',
|
||||
},
|
||||
'export.saveDescription': {
|
||||
'zh-CN': '将报告保存为 .md 文件',
|
||||
en: 'Save report as .md file',
|
||||
ja: 'レポートを.mdファイルとして保存',
|
||||
},
|
||||
'export.selectMethod': {
|
||||
'zh-CN': '选择导出方式',
|
||||
en: 'Select export method',
|
||||
ja: 'エクスポート方法を選択',
|
||||
},
|
||||
'export.copied': {
|
||||
'zh-CN': '报告已复制到剪贴板',
|
||||
en: 'Report copied to clipboard',
|
||||
ja: 'レポートをクリップボードにコピーしました',
|
||||
},
|
||||
'export.saveDialogTitle': {
|
||||
'zh-CN': '保存审查报告',
|
||||
en: 'Save Review Report',
|
||||
ja: 'レビューレポートを保存',
|
||||
},
|
||||
'export.saved': {
|
||||
'zh-CN': '报告已保存到 {0}',
|
||||
en: 'Report saved to {0}',
|
||||
ja: 'レポートを {0} に保存しました',
|
||||
},
|
||||
|
||||
'setup.noWorkspace': {
|
||||
'zh-CN': '请先打开工作区',
|
||||
en: 'Please open a workspace first',
|
||||
ja: '最初にワークスペースを開いてください',
|
||||
},
|
||||
'setup.manageRulesHint': {
|
||||
'zh-CN': '请在设置面板中管理自定义规则',
|
||||
en: 'Manage custom rules in the Setup panel',
|
||||
ja: '設定パネルでカスタムルールを管理してください',
|
||||
},
|
||||
'setup.openSetupFail': {
|
||||
'zh-CN': '无法打开设置面板',
|
||||
en: 'Cannot open setup panel',
|
||||
ja: '設定パネルを開けません',
|
||||
},
|
||||
'setup.openSettingsJson': {
|
||||
'zh-CN': '打开设置 (JSON)',
|
||||
en: 'Open Settings (JSON)',
|
||||
ja: '設定を開く (JSON)',
|
||||
},
|
||||
'setup.setApiKeyFirst': {
|
||||
'zh-CN': '请先设置 API Key',
|
||||
en: 'Please set API Key first',
|
||||
ja: '最初にAPIキーを設定してください',
|
||||
},
|
||||
'setup.setBaseUrlFirst': {
|
||||
'zh-CN': '请先设置 Base URL',
|
||||
en: 'Please set Base URL first',
|
||||
ja: '最初にベースURLを設定してください',
|
||||
},
|
||||
'setup.testSuccess': {
|
||||
'zh-CN': '✓ 连接成功',
|
||||
en: '✓ Connection successful',
|
||||
ja: '✓ 接続成功',
|
||||
},
|
||||
'setup.testFail': {
|
||||
'zh-CN': '✗ 连接失败: {0}',
|
||||
en: '✗ Connection failed: {0}',
|
||||
ja: '✗ 接続失敗: {0}',
|
||||
},
|
||||
'setup.selectRuleFile': {
|
||||
'zh-CN': '选择规则文件',
|
||||
en: 'Select rule file',
|
||||
ja: 'ルールファイルを選択',
|
||||
},
|
||||
'setup.fileExists': {
|
||||
'zh-CN': '文件 {0} 已存在',
|
||||
en: 'File {0} already exists',
|
||||
ja: 'ファイル {0} は既に存在します',
|
||||
},
|
||||
'setup.importSuccess': {
|
||||
'zh-CN': '规则文件已导入: {0}',
|
||||
en: 'Rule file imported: {0}',
|
||||
ja: 'ルールファイルをインポートしました: {0}',
|
||||
},
|
||||
'setup.importing': {
|
||||
'zh-CN': '正在导入自定义规则...',
|
||||
en: 'Importing custom rules...',
|
||||
ja: 'カスタムルールをインポート中...',
|
||||
},
|
||||
'setup.importCancelled': {
|
||||
'zh-CN': '导入已取消',
|
||||
en: 'Import cancelled',
|
||||
ja: 'インポートをキャンセルしました',
|
||||
},
|
||||
'setup.importDedupResult': {
|
||||
'zh-CN': '规则已导入: {0}({1} 条,{2} 条完全重复已注释,{3} 条部分重叠已标注)',
|
||||
en: 'Rules imported: {0} ({1} total, {2} exact duplicate(s) commented, {3} partial overlap(s) marked)',
|
||||
ja: 'ルールをインポートしました: {0}({1} 件、{2} 件の完全重複をコメントアウト、{3} 件の部分重複をマーク)',
|
||||
},
|
||||
'setup.importFail': {
|
||||
'zh-CN': '规则导入失败: {0}',
|
||||
en: 'Rule import failed: {0}',
|
||||
ja: 'ルールのインポートに失敗しました: {0}',
|
||||
},
|
||||
'setup.header': {
|
||||
'zh-CN': '净码特工 · 代码审查 · 设置',
|
||||
en: 'Code Purifier · Setup',
|
||||
ja: 'コードピュリファイア · 設定',
|
||||
},
|
||||
'setup.quickStart': {
|
||||
'zh-CN': '快速开始',
|
||||
en: 'Quick Start',
|
||||
ja: 'クイックスタート',
|
||||
},
|
||||
'setup.gettingStarted': {
|
||||
'zh-CN': '三步启用代码审核',
|
||||
en: '3 Steps to Enable Code Review',
|
||||
ja: '3ステップでコードレビューを有効化',
|
||||
},
|
||||
'setup.step1': {
|
||||
'zh-CN': '安装插件后,<b>配置 AI 模型</b>及 API Key,激活智能审核能力',
|
||||
en: 'After installing, <b>configure AI model</b> and API Key to activate intelligent review',
|
||||
ja: 'インストール後、<b>AIモデル</b>とAPIキーを設定してインテリジェントレビューを有効化',
|
||||
},
|
||||
'setup.step2': {
|
||||
'zh-CN': '启用 <b>自定义规则</b>,补充团队特有的编码规范',
|
||||
en: 'Enable <b>custom rules</b> to add team-specific coding standards',
|
||||
ja: '<b>カスタムルール</b>を有効にしてチーム固有のコーディング規約を追加',
|
||||
},
|
||||
'setup.step3': {
|
||||
'zh-CN': '<b>保存并测试连接</b>,验证配置无误后即可触发审核',
|
||||
en: '<b>Save and test connection</b>, verify config then trigger review',
|
||||
ja: '<b>保存して接続テスト</b>、設定を確認してレビューを開始',
|
||||
},
|
||||
'setup.step3Hint': {
|
||||
'zh-CN': '按 <b>Ctrl + Shift + R</b> 快捷键触发审核,结果实时显示在 <b>审核结果报告</b>页面中',
|
||||
en: 'Press <b>Ctrl + Shift + R</b> to trigger review, results appear in the <b>Review Report</b> panel',
|
||||
ja: '<b>Ctrl + Shift + R</b> でレビューを実行、結果は<b>レビューレポート</b>に表示',
|
||||
},
|
||||
'setup.engineSection': {
|
||||
'zh-CN': '审核引擎',
|
||||
en: 'Review Engines',
|
||||
ja: 'レビューエンジン',
|
||||
},
|
||||
'setup.commonRules': {
|
||||
'zh-CN': '共通规则',
|
||||
en: 'Common Rules',
|
||||
ja: '共通ルール',
|
||||
},
|
||||
'setup.linterStatic': {
|
||||
'zh-CN': 'Linter 静态分析',
|
||||
en: 'Linter Static Analysis',
|
||||
ja: 'リンター静的解析',
|
||||
},
|
||||
'setup.customRules': {
|
||||
'zh-CN': '自定义规则',
|
||||
en: 'Custom Rules',
|
||||
ja: 'カスタムルール',
|
||||
},
|
||||
'setup.teamCoding': {
|
||||
'zh-CN': '团队编码规范',
|
||||
en: 'Team Coding Standards',
|
||||
ja: 'チームコーディング規約',
|
||||
},
|
||||
'setup.aiReview': {
|
||||
'zh-CN': 'AI 审核',
|
||||
en: 'AI Review',
|
||||
ja: 'AIレビュー',
|
||||
},
|
||||
'setup.deepReview': {
|
||||
'zh-CN': '深度代码审查',
|
||||
en: 'Deep Code Review',
|
||||
ja: '詳細コードレビュー',
|
||||
},
|
||||
'setup.aiConfig': {
|
||||
'zh-CN': 'AI 模型配置',
|
||||
en: 'AI Model Configuration',
|
||||
ja: 'AIモデル設定',
|
||||
},
|
||||
'setup.provider': {
|
||||
'zh-CN': '模型提供商',
|
||||
en: 'Model Provider',
|
||||
ja: 'モデルプロバイダー',
|
||||
},
|
||||
'setup.notConfigured': {
|
||||
'zh-CN': '未配置',
|
||||
en: 'Not configured',
|
||||
ja: '未設定',
|
||||
},
|
||||
'setup.model': {
|
||||
'zh-CN': '模型名称',
|
||||
en: 'Model Name',
|
||||
ja: 'モデル名',
|
||||
},
|
||||
'setup.modelHint': {
|
||||
'zh-CN': '建议使用支持结构化输出的模型。',
|
||||
en: 'Use a model that supports structured output.',
|
||||
ja: '構造化出力をサポートするモデルの使用を推奨。',
|
||||
},
|
||||
'setup.apiKey': {
|
||||
'zh-CN': 'API Key',
|
||||
en: 'API Key',
|
||||
ja: 'APIキー',
|
||||
},
|
||||
'setup.baseUrl': {
|
||||
'zh-CN': 'Base URL',
|
||||
en: 'Base URL',
|
||||
ja: 'ベースURL',
|
||||
},
|
||||
'setup.keyStorageHint': {
|
||||
'zh-CN': 'Key 仅存储在本地 VS Code 安全存储中。',
|
||||
en: 'Key is stored locally in VS Code secure storage.',
|
||||
ja: 'キーはVS Codeの安全なストレージにのみ保存されます。',
|
||||
},
|
||||
'setup.outputLang': {
|
||||
'zh-CN': '语言',
|
||||
en: 'Language',
|
||||
ja: '言語',
|
||||
},
|
||||
'setup.outputLangHint': {
|
||||
'zh-CN': '更改插件的显示语言',
|
||||
en: 'Change plugin display language',
|
||||
ja: 'プラグインの表示言語を変更する',
|
||||
},
|
||||
'setup.customRulesSection': {
|
||||
'zh-CN': '自定义规则',
|
||||
en: 'Custom Rules',
|
||||
ja: 'カスタムルール',
|
||||
},
|
||||
'setup.ruleList': {
|
||||
'zh-CN': '规则列表',
|
||||
en: 'Rule List',
|
||||
ja: 'ルール一覧',
|
||||
},
|
||||
'setup.ruleCount': {
|
||||
'zh-CN': '{0} 条',
|
||||
en: '{0} rule(s)',
|
||||
ja: '{0} 件',
|
||||
},
|
||||
'setup.ruleNamePlaceholder': {
|
||||
'zh-CN': '输入规则名称...',
|
||||
en: 'Enter rule name...',
|
||||
ja: 'ルール名を入力...',
|
||||
},
|
||||
'setup.add': {
|
||||
'zh-CN': '+ 添加',
|
||||
en: '+ Add',
|
||||
ja: '+ 追加',
|
||||
},
|
||||
'setup.ruleNameHint': {
|
||||
'zh-CN': '建议使用英文名称,无需输入 .yaml 后缀(如 security-rules)',
|
||||
en: 'Use English names, no .yaml suffix needed (e.g. security-rules)',
|
||||
ja: '英語名を使用してください、.yaml拡張子は不要です(例: security-rules)',
|
||||
},
|
||||
'setup.ruleNameRequired': {
|
||||
'zh-CN': '请输入规则名称',
|
||||
en: 'Please enter a rule name',
|
||||
ja: 'ルール名を入力してください',
|
||||
},
|
||||
'setup.reset': {
|
||||
'zh-CN': '重置',
|
||||
en: 'Reset',
|
||||
ja: 'リセット',
|
||||
},
|
||||
'setup.saveAndTest': {
|
||||
'zh-CN': '保存并测试连接',
|
||||
en: 'Save & Test Connection',
|
||||
ja: '保存して接続テスト',
|
||||
},
|
||||
'importPreview.title': {
|
||||
'zh-CN': '规则导入预览',
|
||||
en: 'Rule Import Preview',
|
||||
ja: 'ルールインポートプレビュー',
|
||||
},
|
||||
'importPreview.source': {
|
||||
'zh-CN': '来源:{0} · 检测到 {1} 条规则',
|
||||
en: 'Source: {0} · {1} rule(s) detected',
|
||||
ja: 'ソース: {0} · {1} 件のルールを検出',
|
||||
},
|
||||
'importPreview.keep': {
|
||||
'zh-CN': '保留',
|
||||
en: 'Keep',
|
||||
ja: '保持',
|
||||
},
|
||||
'importPreview.comment': {
|
||||
'zh-CN': '注释',
|
||||
en: 'Comment Out',
|
||||
ja: 'コメントアウト',
|
||||
},
|
||||
'importPreview.cancel': {
|
||||
'zh-CN': '取消',
|
||||
en: 'Cancel',
|
||||
ja: 'キャンセル',
|
||||
},
|
||||
'importPreview.confirm': {
|
||||
'zh-CN': '确认导入',
|
||||
en: 'Confirm Import',
|
||||
ja: 'インポートを確認',
|
||||
},
|
||||
'import.customRulePrefix': {
|
||||
'zh-CN': '自定义规则',
|
||||
en: 'Custom rule',
|
||||
ja: 'カスタムルール',
|
||||
},
|
||||
'import.duplicateOf': {
|
||||
'zh-CN': '重复:{0}',
|
||||
en: 'Duplicate: {0}',
|
||||
ja: '重複:{0}',
|
||||
},
|
||||
'import.overlapWith': {
|
||||
'zh-CN': '与 {0} 部分重叠',
|
||||
en: 'Overlap with {0}',
|
||||
ja: '{0} と部分的に重複',
|
||||
},
|
||||
'import.overlapReason': {
|
||||
'zh-CN': '重叠原因:{0}',
|
||||
en: 'Overlap reason: {0}',
|
||||
ja: '重複理由:{0}',
|
||||
},
|
||||
'import.badgeRestored': {
|
||||
'zh-CN': '已恢复',
|
||||
en: 'Restored',
|
||||
ja: '復元済み',
|
||||
},
|
||||
'import.badgeWillComment': {
|
||||
'zh-CN': '将注释',
|
||||
en: 'Will comment out',
|
||||
ja: 'コメントアウト予定',
|
||||
},
|
||||
'import.idLabel': {
|
||||
'zh-CN': 'id(规则唯一标识)',
|
||||
en: 'id (unique identifier)',
|
||||
ja: 'id(一意識別子)',
|
||||
},
|
||||
'import.severityLabel': {
|
||||
'zh-CN': 'severity(严重级别)',
|
||||
en: 'severity',
|
||||
ja: 'severity(重要度)',
|
||||
},
|
||||
'import.descriptionLabel': {
|
||||
'zh-CN': 'description(规则描述)',
|
||||
en: 'description',
|
||||
ja: 'description(ルール説明)',
|
||||
},
|
||||
'import.messageLabel': {
|
||||
'zh-CN': 'message(触发提示消息)',
|
||||
en: 'message',
|
||||
ja: 'message(トリガーメッセージ)',
|
||||
},
|
||||
'import.languagesLabel': {
|
||||
'zh-CN': 'languages(适用语言)',
|
||||
en: 'languages',
|
||||
ja: 'languages(対象言語)',
|
||||
},
|
||||
'import.excludeLanguagesLabel': {
|
||||
'zh-CN': 'excludeLanguages(排除语言)',
|
||||
en: 'excludeLanguages',
|
||||
ja: 'excludeLanguages(除外言語)',
|
||||
},
|
||||
'import.tagPlaceholder': {
|
||||
'zh-CN': '输入语言名,Enter 添加',
|
||||
en: 'Type language name, Enter to add',
|
||||
ja: '言語名を入力、Enterで追加',
|
||||
},
|
||||
'import.ruleCount': {
|
||||
'zh-CN': '{0} 条',
|
||||
en: '{0}',
|
||||
ja: '{0} 件',
|
||||
},
|
||||
'import.exactDuplicate': {
|
||||
'zh-CN': '⛔ 完全重复 {0} 条',
|
||||
en: '⛔ Exact duplicate {0}',
|
||||
ja: '⛔ 完全重複 {0} 件',
|
||||
},
|
||||
'import.overlapDuplicate': {
|
||||
'zh-CN': '⚠️ 部分重叠 {0} 条',
|
||||
en: '⚠️ Partial overlap {0}',
|
||||
ja: '⚠️ 部分的重複 {0} 件',
|
||||
},
|
||||
'import.noDuplicate': {
|
||||
'zh-CN': '✅ 无重复 {0} 条',
|
||||
en: '✅ No duplicate {0}',
|
||||
ja: '✅ 重複なし {0} 件',
|
||||
},
|
||||
'import.statusBar': {
|
||||
'zh-CN': '将保留 {0} 条规则,注释 {1} 条规则',
|
||||
en: 'Keep {0} rule(s), comment out {1} rule(s)',
|
||||
ja: '{0} 件保持、{1} 件コメントアウト',
|
||||
},
|
||||
'import.editedHint': {
|
||||
'zh-CN': ' · 已编辑 {0} 条规则',
|
||||
en: ' · Edited {0} rule(s)',
|
||||
ja: ' · {0} 件を編集済み',
|
||||
},
|
||||
'import.sectionExact': {
|
||||
'zh-CN': '完全重复',
|
||||
en: 'Exact Duplicate',
|
||||
ja: '完全重複',
|
||||
},
|
||||
'import.sectionOverlap': {
|
||||
'zh-CN': '部分重叠',
|
||||
en: 'Partial Overlap',
|
||||
ja: '部分的重複',
|
||||
},
|
||||
'import.sectionNone': {
|
||||
'zh-CN': '无重复',
|
||||
en: 'No Duplicate',
|
||||
ja: '重複なし',
|
||||
},
|
||||
'import.validationDescEmpty': {
|
||||
'zh-CN': '规则 "{0}" 的 description 不能为空',
|
||||
en: 'Description of rule "{0}" cannot be empty',
|
||||
ja: 'ルール "{0}" の description は必須です',
|
||||
},
|
||||
'import.validationMsgEmpty': {
|
||||
'zh-CN': '规则 "{0}" 的 message 不能为空',
|
||||
en: 'Message of rule "{0}" cannot be empty',
|
||||
ja: 'ルール "{0}" の message は必須です',
|
||||
},
|
||||
'import.placeholderIdHint': {
|
||||
'zh-CN': '占位 ID,请修改为有意义的标识',
|
||||
en: 'Placeholder ID, please change to a meaningful identifier',
|
||||
ja: 'プレースホルダーID、意味のある識別子に変更してください',
|
||||
},
|
||||
'import.validationIdEmpty': {
|
||||
'zh-CN': '规则 id 不能为空',
|
||||
en: 'Rule id cannot be empty',
|
||||
ja: 'ルールIDは必須です',
|
||||
},
|
||||
|
||||
'report.panelTitle': {
|
||||
'zh-CN': '净码特工 · 代码审查报告',
|
||||
en: 'Code Purifier · Review Report',
|
||||
ja: 'コードピュリファイア · レビューレポート',
|
||||
},
|
||||
'report.totalIssues': {
|
||||
'zh-CN': '总计问题',
|
||||
en: 'Total Issues',
|
||||
ja: '問題の総数',
|
||||
},
|
||||
'report.errors': {
|
||||
'zh-CN': '错误',
|
||||
en: 'Errors',
|
||||
ja: 'エラー',
|
||||
},
|
||||
'report.warnings': {
|
||||
'zh-CN': '警告',
|
||||
en: 'Warnings',
|
||||
ja: '警告',
|
||||
},
|
||||
'report.info': {
|
||||
'zh-CN': '建议',
|
||||
en: 'Info',
|
||||
ja: '情報',
|
||||
},
|
||||
'report.fixAll': {
|
||||
'zh-CN': '全部修复',
|
||||
en: 'Fix All',
|
||||
ja: 'すべて修正',
|
||||
},
|
||||
'report.rerun': {
|
||||
'zh-CN': '重新审查',
|
||||
en: 'Re-run Review',
|
||||
ja: '再レビュー',
|
||||
},
|
||||
'report.export': {
|
||||
'zh-CN': '导出报告',
|
||||
en: 'Export Report',
|
||||
ja: 'レポートをエクスポート',
|
||||
},
|
||||
'report.sourceLinter': {
|
||||
'zh-CN': 'Linter',
|
||||
en: 'Linter',
|
||||
ja: 'リンター',
|
||||
},
|
||||
'report.sourceCustom': {
|
||||
'zh-CN': '自定义',
|
||||
en: 'Custom',
|
||||
ja: 'カスタム',
|
||||
},
|
||||
'report.sourceAI': {
|
||||
'zh-CN': 'AI',
|
||||
en: 'AI',
|
||||
ja: 'AI',
|
||||
},
|
||||
'report.noIssues': {
|
||||
'zh-CN': '未发现任何问题',
|
||||
en: 'No issues found',
|
||||
ja: '問題は見つかりませんでした',
|
||||
},
|
||||
'report.noRuleViolations': {
|
||||
'zh-CN': '未发现规则违规',
|
||||
en: 'No rule violations found',
|
||||
ja: 'ルール違反は見つかりませんでした',
|
||||
},
|
||||
'report.noAIFindings': {
|
||||
'zh-CN': '无 AI 审查建议',
|
||||
en: 'No AI review findings',
|
||||
ja: 'AIレビューによる指摘はありません',
|
||||
},
|
||||
'report.skipCustomRules': {
|
||||
'zh-CN': '当前文件语言无匹配的自定义规则,已跳过规则评估',
|
||||
en: 'No custom rules match this file language, rule evaluation skipped',
|
||||
ja: 'このファイル言語に一致するカスタムルールがありません、ルール評価をスキップしました',
|
||||
},
|
||||
'report.executionErrors': {
|
||||
'zh-CN': '执行错误',
|
||||
en: 'Execution Errors',
|
||||
ja: '実行エラー',
|
||||
},
|
||||
'report.degradedBanner': {
|
||||
'zh-CN': '部分 AI 功能不可用,报告已降级',
|
||||
en: 'Some AI features unavailable, report degraded',
|
||||
ja: '一部のAI機能が利用できません、レポートは縮退しています',
|
||||
},
|
||||
|
||||
'report.title': {
|
||||
'zh-CN': '代码审查报告',
|
||||
en: 'Code Review Report',
|
||||
ja: 'コードレビューレポート',
|
||||
},
|
||||
'report.file': {
|
||||
'zh-CN': '文件',
|
||||
en: 'File',
|
||||
ja: 'ファイル',
|
||||
},
|
||||
'report.language': {
|
||||
'zh-CN': '语言',
|
||||
en: 'Language',
|
||||
ja: '言語',
|
||||
},
|
||||
'report.duration': {
|
||||
'zh-CN': '耗时',
|
||||
en: 'Duration',
|
||||
ja: '所要時間',
|
||||
},
|
||||
'report.tools': {
|
||||
'zh-CN': '分析工具',
|
||||
en: 'Analysis Tools',
|
||||
ja: '解析ツール',
|
||||
},
|
||||
'report.totalSummary': {
|
||||
'zh-CN': '总计: {0} | 错误: {1} | 警告: {2} | 建议: {3}',
|
||||
en: 'Total: {0} | Errors: {1} | Warnings: {2} | Info: {3}',
|
||||
ja: '合計: {0} | エラー: {1} | 警告: {2} | 情報: {3}',
|
||||
},
|
||||
'report.staticSection': {
|
||||
'zh-CN': '静态分析 · {0} 个问题',
|
||||
en: 'Static Analysis · {0} issue(s)',
|
||||
ja: '静的解析 · {0} 件の問題',
|
||||
},
|
||||
'report.customSection': {
|
||||
'zh-CN': '自定义规则 · {0} 个问题',
|
||||
en: 'Custom Rules · {0} issue(s)',
|
||||
ja: 'カスタムルール · {0} 件の問題',
|
||||
},
|
||||
'report.aiSection': {
|
||||
'zh-CN': 'AI 审查 · {0} 条建议',
|
||||
en: 'AI Review · {0} finding(s)',
|
||||
ja: 'AIレビュー · {0} 件の指摘',
|
||||
},
|
||||
'report.suggestion': {
|
||||
'zh-CN': '建议',
|
||||
en: 'Suggestion',
|
||||
ja: '提案',
|
||||
},
|
||||
'report.noProblems': {
|
||||
'zh-CN': '未发现问题',
|
||||
en: 'No issues found',
|
||||
ja: '問題は見つかりませんでした',
|
||||
},
|
||||
'report.issuesCount': {
|
||||
'zh-CN': '{0} 个问题',
|
||||
en: '{0} issue(s)',
|
||||
ja: '{0} 件の問題',
|
||||
},
|
||||
'report.itemsCount': {
|
||||
'zh-CN': '{0} 条',
|
||||
en: '{0} item(s)',
|
||||
ja: '{0} 件',
|
||||
},
|
||||
'report.injectedCount': {
|
||||
'zh-CN': '(注入 {0}/{1} 条)',
|
||||
en: '(injected {0}/{1})',
|
||||
ja: '(注入 {0}/{1} 件)',
|
||||
},
|
||||
'report.injectedRules': {
|
||||
'zh-CN': '(注入 {0}/{1} 条规则)',
|
||||
en: '(injected {0}/{1} rules)',
|
||||
ja: '(注入 {0}/{1} 件ルール)',
|
||||
},
|
||||
|
||||
'import.unsupportedFormat': {
|
||||
'zh-CN': '不支持的文件格式: {0}',
|
||||
en: 'Unsupported file format: {0}',
|
||||
ja: 'サポートされていないファイル形式: {0}',
|
||||
},
|
||||
'import.conversionFailed': {
|
||||
'zh-CN': '转换失败,未生成规则内容',
|
||||
en: 'Conversion failed, no rule content generated',
|
||||
ja: '変換に失敗しました、ルール内容が生成されませんでした',
|
||||
},
|
||||
'import.emptyFile': {
|
||||
'zh-CN': '所选文件为空',
|
||||
en: 'Selected file is empty',
|
||||
ja: '選択したファイルは空です',
|
||||
},
|
||||
'import.needApiKey': {
|
||||
'zh-CN': '请先在设置面板中配置 API Key',
|
||||
en: 'Please configure API Key in Setup first',
|
||||
ja: '最初に設定パネルでAPIキーを設定してください',
|
||||
},
|
||||
'import.timeout': {
|
||||
'zh-CN': 'AI 生成规则超时,请检查网络或增大 ai.timeout 配置',
|
||||
en: 'AI rule generation timed out, check network or increase ai.timeout',
|
||||
ja: 'AIルール生成がタイムアウトしました、ネットワークを確認するかai.timeoutを増やしてください',
|
||||
},
|
||||
'import.aiFail': {
|
||||
'zh-CN': 'AI 生成规则失败: {0}',
|
||||
en: 'AI rule generation failed: {0}',
|
||||
ja: 'AIルール生成に失敗しました: {0}',
|
||||
},
|
||||
'import.emptyResponse': {
|
||||
'zh-CN': 'AI 返回内容为空',
|
||||
en: 'AI returned empty response',
|
||||
ja: 'AIが空の応答を返しました',
|
||||
},
|
||||
'import.excelReadFail': {
|
||||
'zh-CN': '读取 Excel 文件失败: {0}',
|
||||
en: 'Failed to read Excel file: {0}',
|
||||
ja: 'Excelファイルの読み込みに失敗しました: {0}',
|
||||
},
|
||||
'import.excelEmpty': {
|
||||
'zh-CN': 'Excel 文件没有工作表',
|
||||
en: 'Excel file has no worksheets',
|
||||
ja: 'Excelファイルにワークシートがありません',
|
||||
},
|
||||
'import.excelNoData': {
|
||||
'zh-CN': 'Excel 工作表中没有数据',
|
||||
en: 'No data in Excel worksheet',
|
||||
ja: 'Excelワークシートにデータがありません',
|
||||
},
|
||||
'import.docxReadFail': {
|
||||
'zh-CN': '读取 Word 文件失败: {0}',
|
||||
en: 'Failed to read Word file: {0}',
|
||||
ja: 'Wordファイルの読み込みに失敗しました: {0}',
|
||||
},
|
||||
'import.docxEmpty': {
|
||||
'zh-CN': 'Word 文件中没有可提取的文本内容',
|
||||
en: 'No extractable text in Word file',
|
||||
ja: 'Wordファイルに抽出可能なテキストがありません',
|
||||
},
|
||||
'import.pptxReadFail': {
|
||||
'zh-CN': '读取 PowerPoint 文件失败: {0}',
|
||||
en: 'Failed to read PowerPoint file: {0}',
|
||||
ja: 'PowerPointファイルの読み込みに失敗しました: {0}',
|
||||
},
|
||||
'import.pptxEmpty': {
|
||||
'zh-CN': 'PowerPoint 文件中没有可提取的文本内容',
|
||||
en: 'No extractable text in PowerPoint file',
|
||||
ja: 'PowerPointファイルに抽出可能なテキストがありません',
|
||||
},
|
||||
|
||||
'yaml.duplicateExact': {
|
||||
'zh-CN': '# [DUPLICATE: exact] 重复 {0}(检测目标完全一致)',
|
||||
en: '# [DUPLICATE: exact] duplicate of {0} (identical detection target)',
|
||||
ja: '# [DUPLICATE: exact] {0} と重複(検出対象が完全に一致)',
|
||||
},
|
||||
'yaml.duplicateOverlap': {
|
||||
'zh-CN': '# [DUPLICATE: overlap] 与 {0} 部分重叠',
|
||||
en: '# [DUPLICATE: overlap] overlap with {0}',
|
||||
ja: '# [DUPLICATE: overlap] {0} と部分的に重複',
|
||||
},
|
||||
'yaml.overlapReason': {
|
||||
'zh-CN': '# 重叠原因:{0}',
|
||||
en: '# Overlap reason: {0}',
|
||||
ja: '# 重複理由:{0}',
|
||||
},
|
||||
'yaml.manualComment': {
|
||||
'zh-CN': '# [手动注释] 用户选择不启用此规则',
|
||||
en: '# [manual] User chose not to enable this rule',
|
||||
ja: '# [手動コメント] ユーザーがこのルールを有効にしないことを選択',
|
||||
},
|
||||
'yaml.enableHint': {
|
||||
'zh-CN': '# 如需启用,删除以下每行开头的 # 即可',
|
||||
en: '# To enable, remove the leading # from each line below',
|
||||
ja: '# 有効にするには、以下の各行の先頭の # を削除してください',
|
||||
},
|
||||
'yaml.duplicateExactCustom': {
|
||||
'zh-CN': '# [DUPLICATE: exact] 重复自定义规则 {0}(检测目标完全一致)',
|
||||
en: '# [DUPLICATE: exact] duplicate of custom rule {0} (identical detection target)',
|
||||
ja: '# [DUPLICATE: exact] カスタムルール {0} と重複(検出対象が完全に一致)',
|
||||
},
|
||||
'yaml.duplicateExactGeneric': {
|
||||
'zh-CN': '# [DUPLICATE: exact] 重复 {0}(检测目标完全一致)',
|
||||
en: '# [DUPLICATE: exact] duplicate of {0} (identical detection target)',
|
||||
ja: '# [DUPLICATE: exact] {0} と重複(検出対象が完全に一致)',
|
||||
},
|
||||
'yaml.duplicateOverlapCustom': {
|
||||
'zh-CN': '# [DUPLICATE: {0}] 与自定义规则 {1} 部分重叠',
|
||||
en: '# [DUPLICATE: {0}] overlap with custom rule {1}',
|
||||
ja: '# [DUPLICATE: {0}] カスタムルール {1} と部分的に重複',
|
||||
},
|
||||
'yaml.duplicateOverlapGeneric': {
|
||||
'zh-CN': '# [DUPLICATE: {0}] 与 {1} 部分重叠',
|
||||
en: '# [DUPLICATE: {0}] overlap with {1}',
|
||||
ja: '# [DUPLICATE: {0}] {1} と部分的に重複',
|
||||
},
|
||||
|
||||
'engine.jsonNotFound': {
|
||||
'zh-CN': '响应中未找到 JSON。原始响应(前200字符):{0}',
|
||||
en: 'JSON not found in response. Raw response (first 200 chars): {0}',
|
||||
ja: 'レスポンスにJSONが見つかりません。生のレスポンス(先頭200文字):{0}',
|
||||
},
|
||||
'engine.jsonParseFail': {
|
||||
'zh-CN': 'JSON 解析失败。原始响应(前200字符):{0}',
|
||||
en: 'JSON parse failed. Raw response (first 200 chars): {0}',
|
||||
ja: 'JSON解析に失敗しました。生のレスポンス(先頭200文字):{0}',
|
||||
},
|
||||
|
||||
'adapter.javaNotInstalled': {
|
||||
'zh-CN': 'Java 11+ 未安装或不在 PATH 中',
|
||||
en: 'Java 11+ not installed or not in PATH',
|
||||
ja: 'Java 11+ がインストールされていないかPATHにありません',
|
||||
},
|
||||
'adapter.sqlfluffNotInstalled': {
|
||||
'zh-CN': 'sqlfluff 未安装,请执行 pip install sqlfluff',
|
||||
en: 'sqlfluff not installed, run: pip install sqlfluff',
|
||||
ja: 'sqlfluffがインストールされていません、pip install sqlfluff を実行してください',
|
||||
},
|
||||
'adapter.invalidApiKey': {
|
||||
'zh-CN': 'API Key 无效,请重新设置',
|
||||
en: 'Invalid API Key, please reconfigure',
|
||||
ja: 'APIキーが無効です、再設定してください',
|
||||
},
|
||||
'adapter.noApiKey': {
|
||||
'zh-CN': '未配置 API Key',
|
||||
en: 'API Key not configured',
|
||||
ja: 'APIキーが設定されていません',
|
||||
},
|
||||
'adapter.createProviderFail': {
|
||||
'zh-CN': '创建 Provider 失败: {0}',
|
||||
en: 'Failed to create provider: {0}',
|
||||
ja: 'プロバイダーの作成に失敗しました: {0}',
|
||||
},
|
||||
'adapter.customRuleParseFail': {
|
||||
'zh-CN': '自定义规则响应解析失败: {0}',
|
||||
en: 'Custom rule response parse failed: {0}',
|
||||
ja: 'カスタムルール応答の解析に失敗しました: {0}',
|
||||
},
|
||||
'adapter.customRuleRequestFail': {
|
||||
'zh-CN': '自定义规则请求失败: {0}',
|
||||
en: 'Custom rule request failed: {0}',
|
||||
ja: 'カスタムルールリクエストに失敗しました: {0}',
|
||||
},
|
||||
'adapter.aiReviewParseFail': {
|
||||
'zh-CN': 'AI 审查响应解析失败: {0}',
|
||||
en: 'AI review response parse failed: {0}',
|
||||
ja: 'AIレビュー応答の解析に失敗しました: {0}',
|
||||
},
|
||||
'adapter.aiReviewRequestFail': {
|
||||
'zh-CN': 'AI 审查请求失败: {0}',
|
||||
en: 'AI review request failed: {0}',
|
||||
ja: 'AIレビューリクエストに失敗しました: {0}',
|
||||
},
|
||||
|
||||
'extension.activated': {
|
||||
'zh-CN': '净码特工 · Code Purifier 已激活',
|
||||
en: 'Code Purifier activated',
|
||||
ja: 'コードピュリファイアが有効化されました',
|
||||
},
|
||||
|
||||
'lang.zhCN': {
|
||||
'zh-CN': '中文(简体)',
|
||||
en: 'Chinese (Simplified)',
|
||||
ja: '中国語(簡体字)',
|
||||
},
|
||||
'lang.en': {
|
||||
'zh-CN': 'English',
|
||||
en: 'English',
|
||||
ja: 'English',
|
||||
},
|
||||
'lang.ja': {
|
||||
'zh-CN': '日本語',
|
||||
en: '日本語',
|
||||
ja: '日本語',
|
||||
},
|
||||
};
|
||||
|
||||
let currentLang: Language = defaultLang;
|
||||
|
||||
const languageChangeEmitter = new vscode.EventEmitter<Language>();
|
||||
|
||||
export function t(key: string, vars?: Record<string, string | number>): string {
|
||||
const msg = messages[key]?.[currentLang] ?? messages[key]?.[defaultLang];
|
||||
let result = msg ?? key;
|
||||
if (vars) {
|
||||
for (const [k, v] of Object.entries(vars)) {
|
||||
result = result.replace(`{${k}}`, String(v));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
export function setLanguage(lang: Language): void {
|
||||
if (currentLang === lang) { return; }
|
||||
currentLang = lang;
|
||||
languageChangeEmitter.fire(lang);
|
||||
}
|
||||
|
||||
export function onLanguageChange(listener: (lang: Language) => void): vscode.Disposable {
|
||||
return languageChangeEmitter.event(listener);
|
||||
}
|
||||
|
||||
export function getLanguage(): Language {
|
||||
return currentLang;
|
||||
}
|
||||
|
||||
export function getMessageKeys(): string[] {
|
||||
return Object.keys(messages);
|
||||
}
|
||||
@@ -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>;
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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: 重复的规则 ID(linter 如 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 を出力',
|
||||
'',
|
||||
'単に「トピックが類似している」だけでは重複とみなされません。例:',
|
||||
'- 「未使用変数は削除すべき」→ exact(eslint/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');
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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');
|
||||
}
|
||||
}
|
||||
@@ -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>`;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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[];
|
||||
}
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
Vendored
+16
@@ -0,0 +1,16 @@
|
||||
declare module 'mammoth' {
|
||||
interface Result {
|
||||
value: string;
|
||||
messages: unknown[];
|
||||
}
|
||||
|
||||
interface ExtractRawTextOptions {
|
||||
path: string;
|
||||
}
|
||||
|
||||
export function extractRawText(input: ExtractRawTextOptions): Promise<Result>;
|
||||
|
||||
export function convertToHtml(input: ExtractRawTextOptions): Promise<Result>;
|
||||
|
||||
export function convertToMarkdown(input: ExtractRawTextOptions): Promise<Result>;
|
||||
}
|
||||
Reference in New Issue
Block a user