feat: 方法级代码审查 + 模板导入/预览增强 + SQLFluff 方言 + AI 空响应报错修复

- 方法级审查:CodeLens 触发 + 单次 AI 调用(规则匹配 + 6 维度深度审查),新增 method-extractor / status-cache / codeLensProvider
- 模板导入:severity 保留原始值 + 占位 id、去重对照统一 known-rules、重复提示条双语翻译、箭头展开/折叠 UI、520 条静态规则补 zh/ja 翻译
- SQL:sql-lint 重命名 sqlfluff + sqlfluff.dialect 方言可配置 + 默认方言调整
- ESLint:v9 flat config 接线修复(overrideConfigFile)+ legacy 迁移提示
- AI:空响应 EmptyContentError + 重试一次 + max_tokens 截断专用报错
- JSP:整文件检查走 PMD JSP 规则集 + scriptlet 包装解析 + 行号映射
- 诊断按 severity + 行号排序
This commit is contained in:
范智鹏
2026-08-03 22:53:20 +08:00
parent 5848eaa82a
commit 247f19fd44
46 changed files with 6143 additions and 937 deletions
+19
View File
@@ -155,3 +155,22 @@
| 2026-07-30 22:38 | ① 用户提出 → ② 需求澄清 → ③ 方案设计 → ④ 人类审批 → ⑤ 编码实现 | 实现模板导出/导入功能全链路 | 无 | src/rules/import-types.ts, src/rules/export-service.ts(新), src/rules/converters/template-converter.ts(新), src/rules/converters/dedup-prompt.ts(新), src/rules/import-service.ts, src/rules/import-preview.ts, src/views/setupView.ts, src/views/setupView.js, src/activation/commands.ts, package.json, src/i18n/messages.ts | deepseek-v4-pro |
| 2026-07-30 23:03 | ① 用户提出 → ⑤ 编码实现 | 导出按钮图标 📤→↓ 与 ➕添加 风格统一;复选框移至 field-hint 同行右对齐 | 图标迭代 📤→⬇→↓;复选框初版在 field-hint 下方独立一行,被要求移到提示文字右侧 | src/views/setupView.ts, src/i18n/messages.ts | deepseek-v4-pro |
| 2026-07-30 23:08 | ⑥ 审查验证 | 闭环校验模板文件:rowNumber 计算 bug 修复(先 filter 后 map 导致空行后索引偏移,改为先记行号再 filter | 无 | src/rules/converters/template-converter.ts | deepseek-v4-pro |
| 2026-07-31 19:43 | ① 用户提出 → ② 需求澄清 → ③ 方案设计 → ④ 人类审批 → ⑤ 编码实现 → ⑥ 审查验证 | 模板导入预览中错误规则改为可编辑+可「添加」:错误卡片复用完整编辑表单(id/severity/description/message/languages/excludeLanguages),新增「添加」按钮——扩展端先格式校验(id/description/message 非空、severity 合法),再 id 冲突拦截(对照预览已有有效规则),随后单条 AI 去重(dedupSingleRule 复用 buildDedupOnlyPrompt,失败重试 1 次,仍失败降级 none);前端收 ruleAdded 消息后做 DOM 手术(去 data-error、换保留/注释按钮、移入 exact/overlap/none 分区、更新计数),确认写盘时 hasEdits 或 addedRules>0 即回传 editedRules 走 renderRulesToYaml 保证已添加规则不丢失;convertContentWithAI 增加 quiet 参数抑制去重失败 toast;分区容器加 data-section 属性、确认按钮改 JS 动态控制 | 初版用 JS 字符串拼接构造保留/注释按钮('...toggleKeep(\\'' + id ...),tsc 下编译后转义链断裂(browser 端 SyntaxErrornode --check 验证),改为 createElement + addEventListener 闭包规避全部转义问题;错误卡片初版设计只读展示改为完整表单;renderSection 计数初版把 i18n 计数模板拼进 HTML 出错,改为 data-section-title 空 span 由 JS 填充 | docs\superpowers\specs\2026-07-31-import-error-rule-edit-design.md, src/rules/import-service.ts, src/rules/import-preview.ts, src/views/setupView.ts, src/i18n/messages.ts | deepseek-v4-flash |
| 2026-07-31 20:07 | ① 用户提出 → ② 需求澄清 → ③ 方案设计 → ④ 人类审批 → ⑤ 编码实现 → ⑥ 审查验证 | 导入预览错误规则报红从「整卡红边+半透明+顶部 issues 横幅」改为字段级:整卡恢复普通样式(仅保留错误徽标);按 validationIssues.field 把红框+原因文字定位到对应输入控件(id/severity/description/message,字段下方 .field-error-msg);删除顶部 issues 横幅;validateRule 改返回 {field,message}addError 消息新增 field 字段(id 冲突→field:'id');document 委托监听 input/change 实时清除红框(id/description/message 非空、severity change 即清);moveCardToSection 时 clearCardFieldErrors 清理全部字段错误;新增 CSS .field-error-input/.field-error-msg/.id-row flex-wrap | 字段定位 wrap 初版用 el.closest('.edit-field')id 输入框所在 .id-row 匹配不到导致 id 错误提示不显示,改为 closest('.edit-field, .id-row') 并给 .id-row 加 flex-wrap 让提示独占一行;初版测试断言预置了 field-error 类导致结果失真,重写 mock(支持后代选择器/逗号选择器/className setter/document.createElement)后 15 用例全绿 | docs\superpowers\specs\2026-07-31-import-field-error-design.md, src/rules/import-preview.ts | deepseek-v4-flash |
| 2026-07-31 21:37 | ① 用户提出 → ② 需求澄清 → ③ 方案设计 → ④ 人类审批 | 编写方法级代码审查实施计划书:CodeLens 行内触发 + 单次 AI 调用合并规则匹配与 6 维度深度审查;后迭代回加自定义规则审核(补全 prompt builder 签名/命令处理器规则加载/结果合并/验收标准),再为 CodeLens 文案加 Code Purifier 品牌前缀 | 5 方案头脑风暴中 4 个被淘汰(全文件扫描/Git diff 触发/保存自动触发/命令面板选择);审查流程从「静态分析+自定义规则+AI」简化为「AI-only」后又回加自定义规则;MethodReviewResult 初版只有 findings 后加 customRuleResultsbuildMethodUserPrompt 签名漏 customRules 参数后补全;命令处理器初版未加载规则、mergeResults 传空 customRuleResults 后补全 loadActiveRules+filterForDocument;对比表输出结构从「findings only」修正为「customRuleResults + findings」;CodeLens 文案从无品牌改为加 Code Purifier 前缀 | docs\superpowers\specs\2026-07-31-method-level-review-plan.md | Trae 内部模型 |
| 2026-07-31 22:05 | ① 用户提出 → ② 需求澄清 → ③ 方案设计 → ④ 人类审批 → ⑤ 编码实现 → ⑥ 审查验证 | 方法级代码审查功能编码实现:新增 method-extractorSymbol API+正则回退+调用链粗匹配)/status-cache/CodeLensProviderengine.ts 新增 runMethodReview 单次 AI 调用(规则匹配+6 维度深度审查,en/zh/ja 三分支 prompt);commands.ts 新增 codeReviewer.reviewMethod(含右键光标 fallback、方法相对行号→文件行号偏移);extension.ts 注册 CodeLens+状态缓存;webview AI 标签页渲染 pathi18n 新增 6 keypackage.json 命令/配置/右键菜单 | 类型兼容方案多轮迭代:初按设计书 MethodFinding[] 直接传 aiFindings 编译失败 → 讨论「不混入全文件审查」后定案加宽 AIFinding.category 联合为 8 值 + MethodFinding extends AIFinding6 维度收窄)+ path 可选;面板展示迭代:先按用户要求方案 A 独立 methodReview 标签页,用户改口「不多加标签页,还是显示在 ai 审核里面」,退回 AI 标签页内渲染;runMethodReview 初版照设计书写 result.status==='fulfilled'chat 实际返回 Promise<string>)→ 改为 try/catchCodeLens 构造器初版传 symbol.range.startPosition)→ 改为单行 Range;自审查发现 AI 返回方法内相对行号未偏移导致面板跳错行 → customRuleResults 偏移 +methodLine、aiFindings 偏移 +methodLine-1(对齐 mergeResults 与面板各自的换算);method-extractor 零宽范围回退路径取文本为空 → 统一 expandToMethodBody 后用完整范围算调用链 | src/scope/method-extractor.ts(新), src/scope/status-cache.ts(新), src/views/codeLensProvider.ts(新), src/ai/schema.ts, src/ai/engine.ts, src/activation/commands.ts, src/extension.ts, src/panel/webview.ts, src/i18n/messages.ts, package.json | deepseek-v4-flash |
| 2026-07-31 23:02 | ① 用户提出 → ② 需求澄清 → ③ 方案设计 → ④ 人类审批 → ⑤ 编码实现 → ⑥ 审查验证 | 模板导入(Excel)两处体验修复:①错误规则卡片的 severity 不再误导性显示降级后的 warning——ImportableRule 新增 originalSeverity/idPlaceholderparseTemplate 保留降级前原始 severity、空 id 行生成唯一占位 idrule-<行号>)并打 id 缺失 issue、过滤条件改为「id/description/message 三者全空才丢弃」(原为空 id 即丢弃);预览卡片 severity 标签改红色错误样式显示原始值或「severity 缺失」,select 增加禁用占位项「请选择 severity」迫使用户显式选择;handleAddErrorRule 增加占位 id 未改动即拒绝的守卫(杜绝 rule-N 泛化 id 与降级 warning 静默落盘);severity 非法提示文案改用 i18nmessages.ts 新增 import.idMissing/severityMissing/severitySelectHint 三语 key | 临时验证测试 src/test/template-parse-verify.test.tsXLSX 内存构造 fixture 断言占位 id/全空丢弃/原始 severity)验证通过后删除;模板路径决策在「错误区手动补 id(推荐)」与「自动占位直接导入」间二选一,用户选前者;测试运行受既有 stylelint-config-recommended 无 exports main 环境问题阻碍(extension 激活失败),临时移开 adapter.test.ts + 清理 out 陈旧产物后 54 用例全绿,adapter 问题与本次改动无关 | src/rules/import-types.ts, src/rules/converters/template-converter.ts, src/rules/import-preview.ts, src/i18n/messages.ts | deepseek-v4-flash |
| 2026-07-31 23:30 | ① 用户提出 → ② 需求澄清 → ③ 方案设计 → ④ 人类审批 → ⑤ 编码实现 → ⑥ 审查验证 | 统一所有导入路径的去重对照源:新建共享模块 known-rules.tsbuildKnownRulesSection,按 linter 分组渲染 static-rules.json 的 520 条内置静态规则 + custom/ 前缀自定义规则,三语文案);prompt-builder.ts 删除本地 buildDedupPromptSection 及其 4 个 label 字段,buildSystemPrompt 改调共享函数(AI 转换路径行为不变);dedup-prompt.ts 的 buildDedupOnlyPrompt 改调共享函数补上静态规则区块(修复模板批量去重与预览单条添加去重仅对照自定义规则的缺口),删除 existingTitle/noExisting 字段,三语 taskLines 措辞改为「对照内置静态分析规则与已导入的自定义规则」;新增测试 src/test/dedup-prompt.test.ts(断言含 pmd/AvoidDeeplyNestedIfStmts、eslint/no-unused-vars、sql-lint/AL01 与 custom/ 条目) | 三路径对比后确认②模板批量与③单条添加本就是同一 buildDedupOnlyPrompt,①AI 转换因转换+去重融合无法复用纯去重提示词,仅「已知规则清单」渲染可抽公共件;测试断言首次写成 !includes('custom/') 被 taskLines 的 custom/my-rule 字样误触发,改为断言 '- custom/' 前缀;测试运行仍受既有 stylelint-config-recommended 激活失败阻碍,沿用移开 adapter.test.ts 方案,56 用例全绿 | src/rules/converters/known-rules.ts(新), src/rules/converters/prompt-builder.ts, src/rules/converters/dedup-prompt.ts, src/test/dedup-prompt.test.ts(新) | deepseek-v4-flash |
| 2026-07-31 23:57 | ① 用户提出 → ② 需求澄清 → ③ 方案设计 → ④ 人类审批 → ⑤ 编码实现 → ⑥ 审查验证 | 导入预览展开区重复提示升级:展开后 id 下方不再只显示重复对象 id(与 id 行撞车),改为彩色提示条——exact 红底「完全重复」+ 与规则「{0}」完全重复 + description 具体信息 + 默认动作提示;overlap 橙底「部分重叠」+ 与规则「{0}」部分重叠 + description + 重叠原因;新增 resolveDupDescriptioncustom/<id> 从工作区 loadActiveRules 查、<linter>/<id> 从 static-rules.json 查,查不到省略该行),renderRuleCard 内构建;新增 .dup-banner/.dup-exact/.dup-overlap 等 CSSmessages.ts 新增 import.dupExactTitle/dupOverlapTitle/dupExactText/dupOverlapText/dupDescriptionLabel/dupExactHint 六条三语 key(重叠原因复用 import.overlapReason | 方案讨论中用户澄清问题不在折叠而在「展开后 id 下方展示不够直接、与 id 重复」→ 明确只改展开区这一块,头部徽章、分区标题、汇总条、moveCardToSection 单条添加路径均不改;resolveDupDescription 初版考虑在单条添加消息中透传 description,用户限定范围后放弃;测试运行仍受既有 stylelint-config-recommended 激活失败阻碍,沿用移开 adapter.test.ts 方案,56 用例全绿(含 I18n 三语完整校验) | src/rules/import-preview.ts, src/i18n/messages.ts | deepseek-v4-flash |
| 2026-08-01 00:53 | ① 用户提出 → ② 需求澄清 → ③ 方案设计 → ④ 人类审批 → ⑤ 编码实现 → ⑥ 审查验证 | 统一导入预览折叠/展开箭头为标准惯例:卡片初始图标由「错误卡▲/正常卡▼」反直觉改为「展开▼/折叠▶」;toggleCard 展开设▼、折叠设▶;分区初始箭头 renderSection 由写死「▶」改为按 show 状态「展开▼/折叠▶」,修复正常分区默认展开时箭头与实际不符、且与错误分区初始「▼」约定矛盾的问题;错误分区保持▼,toggleSection 不变;全文件 7 处箭头统一为 折叠▶/展开▼ | 分析发现卡片用「折叠▼/展开▲」与分区用「折叠▶/展开▼」两套相反约定且卡片反直觉,用户确认按标准惯例(向右=可展开、向下=已展开)统一 | src/rules/import-preview.ts | deepseek-v4-flash |
| 2026-08-01 00:58 | ① 用户提出 → ② 需求澄清 → ③ 方案设计 → ④ 人类审批 → ⑤ 编码实现 → ⑥ 审查验证 | 箭头加丝滑过渡:把「切换 ▶/▼ 字符」改为固定 ▼ 字形 + CSS transform rotate(-90deg/0deg) + transition 0.15s ease 旋转过渡;卡片与分区统一通过 expanded 类控制方向(默认折叠=向右,加 expanded 类=向下);toggleCard/toggleSection 由改 textContent 改为 card.classList / section-wrapper.classList 切换 expandedrenderRuleCard/renderSection/renderErrorSection 初始按状态带 expanded 类,箭头统一渲染 ▼;moveCardToSection 目标分区展开时补 expanded 类 | 直接切换字符无法过渡,需改字形+旋转方案;moveCardToSection 初版只设 display 未加 expanded 类导致移入卡片后分区箭头状态不符,补充 wrap.classList.add('expanded') | src/rules/import-preview.ts | deepseek-v4-flash |
| 2026-08-01 01:06 | ① 用户提出 → ② 需求澄清 → ③ 方案设计 → ④ 人类审批 → ⑤ 编码实现 → ⑥ 审查验证 | 重复提示条 description 按界面语言附加翻译括号:static-rules.json 全部 520 条内置规则新增 descriptionZh/descriptionJa 字段(中文/日文翻译);import-preview.ts 的 resolveDupDescription 引入 getLanguage——zh-CN 时返回「英文 (中文)」、ja 时返回「英文 (日本語)」、en 时仅返回英文不加括号;自定义规则无翻译字段保持原样;新增 7 个翻译数据文件 scripts/translations/{eslint,ts-eslint,stylelint,pmd-1,pmd-2,pmd-jsp,sql-lint}.mjs 与合并脚本 scripts/add-static-translations.mjs520 条全部命中,missing 0) | 范围澄清:只改重复提示条(不涉卡片头部预览),翻译来源选 static-rules.json 内置字段;初稿合并后用 PowerShell 校验显示乱码「?」疑似编码损坏,经 Node latin1/utf8 双读确认文件实为完好 UTF-8PowerShell 5.1 控制台按 ANSI 读取显示所致),JSON.parse 通过、样例中文/日文正确;测试运行仍受既有 stylelint-config-recommended 激活失败阻碍,沿用移开 adapter.test.ts 方案,56 用例全绿 | src/rules/static-rules.json, src/rules/import-preview.ts, scripts/translations/eslint.mjs(新), scripts/translations/ts-eslint.mjs(新), scripts/translations/stylelint.mjs(新), scripts/translations/pmd-1.mjs(新), scripts/translations/pmd-2.mjs(新), scripts/translations/pmd-jsp.mjs(新), scripts/translations/sql-lint.mjs(新), scripts/add-static-translations.mjs(新) | deepseek-v4-flash |
| 2026-08-01 01:17 | ① 用户提出 → ② 需求澄清 → ③ 方案设计 → ④ 人类审批 → ⑤ 编码实现 → ⑥ 审查验证 | 修复规则 id 为纯数字(如 123)的卡片无法展开:根因是 AI 去重重新输出 YAML 时把 `id: 123` 序列化为 `id: '123'`(带引号),parseSimpleYaml/parseYamlSimple 对标量值原样保留引号,导致 id 变成字面量 `'123'`,内联处理器 `toggleCard(''123'')` 等生成 JS 语法错误、点击失效;新增 stripQuotes 统一剥离标量前后匹配引号(import-service.ts 与 yaml-parser.ts 两处解析器);同时把 webview 内联事件处理器从「把 rule.id 拼进 JS 字符串」全部改为「传 this + 从卡片 data-ruleid 取 id」(toggleCard/syncId/toggleKeep/updateRule/addErrorRule/collectCardRule),彻底消除 id 含特殊字符破坏处理器的隐患;新增 2 个回归测试(id 带引号剥离、scalar 字段引号剥离) | 初判以为是 id 特殊字符破坏内联 onclick,用户澄清「文件 id 是 123,显示却带单引号」→ 定位到 YAML 标量引号未剥离的根因;内联处理器重构的 toggleKeep/updateRule/syncId 均改为从 el.closest('.rule-card').dataset.ruleid 取值;测试运行仍受既有 stylelint-config-recommended 激活失败阻碍,沿用移开 adapter.test.ts 方案,58 用例全绿 | src/rules/import-service.ts, src/rules/yaml-parser.ts, src/rules/import-preview.ts, src/test/import-dedup.test.ts | deepseek-v4-flash |
| 2026-08-02 21:44 | ① 用户提出 → ② 需求澄清 → ③ 方案设计 → ④ 人类审批 → ⑤ 编码实现 → ⑥ 审查验证 | 修复 ESLint v9 配置接线全瘫 bugresolveEslintConfig 三条路径全坏(eslintConfigPath/项目配置传 v9 已移除的 configFile 选项抛「Unknown options: configFile」;无配置只传 overrideConfig 时 v9 仍向上查找 eslint.config.js 找不到抛「Could not find config file」,内置规则从未生效)。改为判别联合 EslintConfigResult + v9 合法选项:eslintConfigPath 存在(fs.existsSync 校验+相对路径解析)→ overrideConfigFile;项目 eslint.config.*(新增 cjs/ts/mts/cts)→ overrideConfigFile;只有 legacy .eslintrc.* → 执行错误框提示迁移(adapter.eslintLegacyConfig 三语);无配置 → overrideConfigFile:true + 内置规则。同步设置面板:projectConfigFileName 改 eslint.config.js、getEslintTemplate 改 flat 格式(languageOptions 替代 env/root)、PROJECT_CONFIG_FILES 移除 .eslintrc.*、eslintGuide 与 package.json 描述更新。lint/compile 通过;临时目录三场景实测:flat 项目配置命中 no-var、legacy 报迁移提示、内置规则命中 no-var/eqeqeq/no-empty;全量测试套件 61 用例全绿(临时新增 src/test/eslint-adapter-verify.test.ts 3 用例覆盖内置兜底/legacy 报错/项目 flat 配置,验证后删除并还原 adapter.test.ts、extension.test.ts | 方案迭代:初版「configFile 存在性校验」被实测推翻(v9 已删 configFile 选项,报错是 Unknown options 而非 Could not find config file);「内置规则是否有效」经 18 条诊断实测确认规则本身没问题、纯接线 bug;legacy 处理用户先选 B(报错提示迁移)再在设置面板同步问题上确认 scope 扩大;flat 模板先考虑 module.exports 对象(legacy 风格)后改数组;setupView PROJECT_CONFIG_FILES 决策点 A(保留 legacy 展示)B(移除)用户选 B;验证脚本先因 PowerShell 引号嵌套失败改 base64 注入 | src/adapters/eslint.ts, src/views/setupView.ts, src/i18n/messages.ts, package.json | deepseek-v4-flash |
| 2026-08-02 22:04 | ① 用户提出 → ② 需求澄清 → ③ 方案设计 → ④ 人类审批 → ⑤ 编码实现 → ⑥ 审查验证 | 修复 AI 空响应被静默吞掉的问题:三个 provideropenai-compatible/claude/gemini)对空内容返回 `?? ''`parseJsonResponse('') 抛「响应中未找到 JSON」且原始响应为空、诊断信息全丢;连接测试只查不抛异常、掩盖问题。改动:base.ts 新增 EmptyContentError 标记类;三 provider 空内容(content 缺失/null/trim 空)抛 EmptyContentError 附诊断(openai-compatible 带 finish_reason/choices 数/error.messageclaude 带 stop_reasongemini 带 candidates 数/blockReason),消息用 adapter.emptyContent 三语包裹;engine.ts 新增 chatWithRetry(对 EmptyContentError 重试一次,仍空透传)并接入 runAIReview 双并行请求与 runMethodReviewparseJsonResponse 对空白串抛独立 engine.emptyResponsesetupView 连接测试校验返回内容非空否则抛 setup.emptyResponse。lint/compile 通过;新增正式测试 src/test/ai-empty-response.test.ts 5 用例(空串抛空响应提示/正常 JSON 解析/空内容重试一次/重试仍空透传 EmptyContentError/非空内容错误不重试)全绿,全量套件 63 用例通过(沿用移开 adapter/extension 测试文件 workaround,已还原)。另确认 fixer/import-service 两处 provider.chat 调用点均有 try/catchEmptyContentError 安全 | 方案讨论中确认用户实际配置模型 deepseek-v4-flash(非 DeepSeek 官方公开模型名 deepseek-chat/deepseek-reasoner,很可能是空响应上游根因,属配置问题非代码问题);测试初版第 4 用例用正则 /空内容\|empty content/ 断言但测试内直接 new EmptyContentError('finish_reason=length') 消息不匹配,改为 assert.rejects(..., EmptyContentError) 构造函数断言;重试范围最初考虑覆盖 fixer/import-service,用户批准方案未含,维持仅审查引擎 | src/ai/providers/base.ts, src/ai/providers/openai-compatible.ts, src/ai/providers/claude.ts, src/ai/providers/gemini.ts, src/ai/engine.ts, src/views/setupView.ts, src/i18n/messages.ts, src/test/ai-empty-response.test.ts(新) | deepseek-v4-flash |
| 2026-08-02 22:21 | ① 用户提出 → ② 需求澄清 → ③ 方案设计 → ④ 人类审批 → ⑤ 编码实现 → ⑥ 审查验证 | B 方案:AI 输出被 max_tokens 截断的专用报错。用户实测根因为 maxTokens(默认8192) 太小、模型被截断(finish_reason=length, choices=1, content 空,重试同果)。openai-compatible.ts 在空内容且 finish_reason==='length' 时抛专用 EmptyContentErroradapter.maxTokensTruncated 三语「AI 输出被 max_tokens 截断(当前 {0}),请调大设置 ai.maxTokens 或更换模型」(带当前 options.maxTokens 值),区别于通用空内容错误;其余分支不变。新增测试:ai-empty-response.test.ts 第 6 用例 mock global.fetch 返回 finish_reason=length + content null,断言抛 EmptyContentError 且消息含 max_tokens 与 8192。lint/compile 通过;全量套件 64 用例全绿(沿用移开 adapter/extension workaround,已还原) | 用户先手动调大 maxTokens 后确认根因是 token 太小,退回要求只做 B(代码报错更可操作),未选 C 自动加码重试;claude/gemini 的 stop_reason/max_tokens 等价处理未纳入 B 范围(仅 openai-compatible),留作潜在后续 | src/ai/providers/openai-compatible.ts, src/i18n/messages.ts, src/test/ai-empty-response.test.ts | deepseek-v4-flash |
| 2026-08-03 21:24 | ① 用户提出 → ② 需求澄清 → ③ 方案设计 → ④ 人类审批 → ⑤ 编码实现 → ⑥ 审查验证 | 全量重命名 sql-lint → sqlfluff:适配器文件/类名/id、规则前缀 sql-lint:→sqlfluff:、config 读取键 sqlfluff.configFile、orchestrator 引用、setupView 面板元数据/settingsTarget/监听、package.json 配置键(linters.sql/plsql 默认值与枚举、sqlfluff.configFile、linter.sqlfluff.enabled)、static-rules.json 命名空间与 75 条规则 ID、dedup-prompt 测试断言、翻译脚本重命名 | 中间产物:无(插件未发布,无需旧配置迁移);测试沿用既有 stylelint-config-recommended 阻碍的移开 adapter.test.ts 方案,64 用例通过(1 个既有 extension 激活失败与本变更无关) | src/adapters/sqlfluff.ts(改名自 sql-lint.ts), src/config/linter.ts, src/orchestrator/orchestrator.ts, src/views/setupView.ts, package.json, src/rules/static-rules.json, src/test/dedup-prompt.test.ts, scripts/translations/sqlfluff.mjs(改名), scripts/add-static-translations.mjs | deepseek-v4-flash |
| 2026-08-03 21:51 | ① 用户提出 → ② 需求澄清 → ③ 方案设计 → ④ 人类审批 → ⑤ 编码实现 → ⑥ 审查验证 | SQL 方言可配置化:新增配置 sqlfluff.dialectenum 28 方言,空=自动);方言优先级改为「显式设置 > 全局/项目配置 > 语言映射」——仅显式设置时才传 --dialect,修复 CLI --dialect 无条件覆盖配置文件方言的 bugplsql 映射 postgres→oracle;内置配置常量 BUILTIN_SQLFLUFF_CONFIG 改为 buildBuiltinConfig(dialect) 动态生成(兜底时按语言映射注入方言);runSqlfluff dialect 参数改为可选;非法方言静默回退 | 中间产物:无;实证验证(sqlfluff 4.2.2):MySQL 反引号 SQL 用 config dialect=mysql 无 --dialect 解析通过,+--dialect postgres 产生 PRS 解析错误,证实配置方言生效且 CLI 覆盖配置;测试沿用移开 adapter.test.ts 方案 64 用例通过(1 个既有 extension 激活失败与本变更无关) | src/adapters/sqlfluff.ts, src/config/linter.ts, package.json | deepseek-v4-flash |
| 2026-08-03 22:11 | ① 用户提出 → ② 需求澄清 → ③ 方案设计 → ④ 人类审批 → ⑤ 编码实现 → ⑥ 审查验证 | 默认方言调整:DIALECT_MAP sql 映射 ansi→mysqlplsql 保持 oracle),setupView 项目配置模板 dialect postgres→mysql;优先级不变(显式 sqlfluff.dialect > 全局/项目配置 > 语言映射兜底) | 中间产物:无;实证(sqlfluff 4.2.2):反引号 MySQL 脚本在适配器内置配置(dialect=mysql)下正常解析无 PRS(此前 ansi 会 PRS 失败),验证中途发现 PowerShell 转义将反引号变双引号导致的 PRS 误报(MySQL 双引号=字符串,非产品问题);测试沿用移开 adapter.test.ts 方案 64 用例通过(1 个既有 extension 激活失败与本变更无关) | src/adapters/sqlfluff.ts, src/views/setupView.ts | deepseek-v4-flash |
| 2026-08-03 22:18 | ① 用户提出 → ② 需求澄清 → ③ 方案设计 → ④ 人类审批 → ⑤ 编码实现 → ⑥ 审查验证 | 修复 JSP 适配器两处缺陷:(1) 整文件检查改用 PMD JSP 规则集(新增 pmd.ts checkJsp(),优先级 getPMDJspRulesetPath > 内置 pmd-jsp-ruleset.xml,此前该配置零使用导致整文件 files:[]);(2) 提取的 <% %> Java 片段裸语句无法被 PMD 按编译单元解析(ParseException)→ 按 scriptletKind 包装成 package jsp; class JspScriptlet{...} 再交 PMD,用 codeLineOffset 映射回原文行号并过滤合成包装噪音(NoPackage/AtLeastOneConstructor 等);jsp-extractor.ts 区分 5 种标签(注释/指令跳过、declaration/expression/statement 标记 kind);PmdRunner.java 新增第 3 参数指定 stdin 临时文件扩展名(jsp 用 .jsp,未保存 JSP 也可走 JSP 语言模块)并重编译 | 中间产物:无;实证(PMD 7.26.0):含违规 JSP 检出 JspEncoding/NoScriptlets + EmptyControlStatement/UnusedLocalVariable/UnusedPrivateField 等,行号正确映射、无合成噪音;stdin+.jsp 扩展名路径验证通过;测试沿用移开 adapter.test.ts 方案 64 用例通过(1 个既有 extension 激活失败与本变更无关) | src/jsp/jsp-extractor.ts, src/adapters/jsp.ts, src/adapters/pmd.ts, jars/pmd/PmdRunner.java(重编译 PmdRunner.class) | deepseek-v4-flash |
| 2026-08-03 22:42 | ① 用户提出 → ② 需求澄清 → ③ 方案设计 → ④ 人类审批 → ⑤ 编码实现 → ⑥ 审查验证 | 诊断按严重度+行号排序:mergeResults 集中排序(error<warning<info<其他,同级按 range.start.line 升序,Node 22 稳定排序),linterDiagnostics/customRuleDiagnostics/aiFindings 三数组统一;翻译映射在排序前完成;fixableLinterIndices/fixableCustomIndices 基于排序后数组重算;新增 merger.test.ts 排序测试 | 中间产物:测试断言首次行号基准错误(customRuleDiagnostics range 为 line-1 0 基,断言误用 1 基)→ 修正为 error:1/info:8;测试沿用移开 adapter.test.ts 方案 65 用例通过(新增排序用例,1 个既有 extension 激活失败与本变更无关) | src/merger/merger.ts, src/test/merger.test.ts | deepseek-v4-flash |
@@ -0,0 +1,417 @@
# 导入预览 · 错误规则可编辑与「添加」设计书
> 版本:v1.0
> 日期:2026-07-31
> 适用项目:vscode-code-reviewer
> 参考文档:`2026-07-30-export-template-design-v2.md`(错误规则组雏形)、`2026-07-25-import-preview-edit-design.md`(预览编辑)
---
## 一、方案概览
### 1.1 目标
模板导入(勾选「使用模板文件导入」)进入预览后,**错误规则**不再是纯只读展示,而是:
- 卡片提供**完整编辑表单**(与有效规则一致:id / severity / description / message / languages / excludeLanguages
- 每张错误卡片有独立「添加」按钮,点击后:
1. **重新校验格式**id/description/message 非空、severity 合法)
2. 校验通过后**单条 AI 去重**(复用 `buildDedupOnlyPrompt`),失败先重试一次,仍失败降级为 `none`
3. 按去重结果(exact/overlap/none)把该规则**移入对应分区**,变为正常可编辑规则(带保留/注释切换)
- 添加时若 id 与预览中已有规则重复 → **阻止并提示修改 id**
- 未添加的错误规则维持现状:确认导入时自动丢弃、不参与校验
- 确认写盘必须包含已添加的规则(避免走 raw yaml 路径丢失)
### 1.2 数据流
```
现状(仅展示):
parseTemplate → errorRules(带 validationIssues)→ 预览只读展示 → 确认时自动丢弃
改造后:
parseTemplate → errorRules → 预览可编辑错误卡片
├─ 用户编辑字段 → 点击「添加」
│ ├─ [扩展端] 格式校验 → 失败 → postMessage addError(卡片内提示)
│ ├─ [扩展端] id 冲突检查(对照预览已有有效规则)→ 冲突 → addError
│ ├─ [扩展端] AI 单条去重(重试 1 次 → 降级 none)→ postMessage ruleAdded
│ └─ [前端] DOM 手术:错误卡片移入 exact/overlap/none 分区,
│ 去除 data-error、换保留/注释按钮、更新计数
└─ 确认导入
├─ 已添加或已编辑 → 前端回传 editedRules(含已添加规则)→ renderRulesToYaml 写盘
└─ 无编辑无添加 → 走原 buildFinalYamlFromRaw(零回归)
```
### 1.3 范围
| 类型 | 内容 |
|------|------|
| 含 | 错误卡片完整编辑表单;「添加」按钮(校验 + 单条 AI 去重 + 移入分区);id 冲突拦截;去重失败重试/降级;确认写盘含已添加规则;计数动态更新 |
| 不含 | 非模板导入路径(AI 链路无 validationIssues,不受影响);批量「全部添加」按钮;错误规则的本地重复检查 |
| 不触碰 | `parseTemplate` 校验逻辑、`buildDedupOnlyPrompt``applyConversion`、其余转换器 |
---
## 二、架构设计
### 2.1 模块划分
```
src/rules/
├── import-service.ts ← 修改: 新增 export async function dedupSingleRule()
├── import-preview.ts ← 修改: 错误卡片可编辑 + 添加流程(前端 JS + 扩展消息处理)
└── import-types.ts ← 不改(复用现有类型)
src/i18n/messages.ts ← 修改: 新增 key,调整 import.cannotImport 文案
```
### 2.2 关键接口
```ts
// import-service.ts 新增导出函数
export interface DedupResult {
duplicateLevel: 'exact' | 'overlap' | 'none';
duplicateOf?: string;
duplicateReason?: string;
}
// 单条规则 AI 去重:失败重试 1 次,仍失败返回 null(调用方降级为 none)
export async function dedupSingleRule(
rule: ImportableRule,
context: vscode.ExtensionContext,
): Promise<DedupResult | null>
```
```ts
// import-preview.ts — Webview 消息协议扩展
// 前端 → 扩展
interface AddErrorRuleMessage {
type: 'addErrorRule';
ruleId: string; // 原始卡片 id(用于在 result.rules 中定位)
rule: { // 当前卡片全部字段(含用户编辑)
id: string;
severity: string;
description: string;
message: string;
languages?: string[];
excludeLanguages?: string[];
};
}
// 扩展 → 前端
interface AddErrorMessage {
type: 'addError';
ruleId: string;
message: string;
}
interface RuleAddedMessage {
type: 'ruleAdded';
ruleId: string; // 原始卡片 id(前端按此定位 DOM)
id: string; // 去重后的最终 id(可能被用户编辑过)
duplicateLevel: 'exact' | 'overlap' | 'none';
duplicateOf?: string;
duplicateReason?: string;
dedupFailed: boolean; // true 表示降级为 none
}
```
`convertContentWithAI` 增加可选第 4 参 `quiet?: boolean`(去重失败时抑制内置 error toast,改由前端降级提示)。现有调用点均传 3 参,向后兼容。
---
## 三、详细实现
### 3.1 `import-service.ts`:新增 `dedupSingleRule`
```ts
export interface DedupResult {
duplicateLevel: 'exact' | 'overlap' | 'none';
duplicateOf?: string;
duplicateReason?: string;
}
export async function dedupSingleRule(
rule: ImportableRule,
context: vscode.ExtensionContext,
): Promise<DedupResult | null> {
const workspaceRoot = vscode.workspace.workspaceFolders?.[0]?.uri.fsPath;
const existingRules = workspaceRoot ? loadActiveRules(workspaceRoot) : [];
const singleYaml = [
`- id: ${rule.id}`,
` severity: ${rule.severity}`,
` description: ${rule.description}`,
` message: ${rule.message}`,
...(rule.languages?.length ? [` languages: [${rule.languages.join(', ')}]`] : []),
...(rule.excludeLanguages?.length ? [` excludeLanguages: [${rule.excludeLanguages.join(', ')}]`] : []),
].join('\n');
const { system, user } = buildDedupOnlyPrompt(singleYaml, existingRules);
for (let attempt = 0; attempt < 2; attempt++) {
const out = await convertContentWithAI(user, context, system, true); // quiet
if (!out) continue;
const parsed = parseImportableYaml(out);
if (parsed.length === 0) continue;
const r = parsed[0];
return {
duplicateLevel: r.duplicateLevel ?? 'none',
duplicateOf: r.duplicateOf,
duplicateReason: r.duplicateReason,
};
}
return null;
}
```
### 3.2 `import-service.ts``convertContentWithAI` 增加 quiet 参数
```ts
export async function convertContentWithAI(
content: string,
context: vscode.ExtensionContext,
systemPrompt?: string,
quiet?: boolean,
): Promise<string | null> {
// ... getApiKey 失败:quiet 时仅返回 null,不弹 toast
// ... provider.chat 异常:quiet 时仅返回 null,不弹 toast
// ... 其余逻辑不变
}
```
### 3.3 `import-preview.ts`:错误卡片渲染改造
**废弃 `renderErrorCard` / `renderErrorSection` 的只读版**,统一由 `renderRuleCard` 承担,新增 `isError` 分支:
```
renderRuleCard(rule, { isError })
├─ 普通卡片:现状逻辑不变
└─ 错误卡片:
├─ 卡片属性 data-ruleid + data-error="true" + 错误边框(保留现 opacity/border 样式)
├─ 折叠态摘要:id(可编辑)+ 错误徽标(新 i18n,如「需修复后添加」)
├─ 展开态表单:与普通卡片完全一致(id 可改、severity 下拉、
│ description/message textarea、languages/excludeLanguages 标签)
├─ 表单顶部:错误原因列表(复用现 issues 渲染)
├─ 顶部操作区:用「添加」按钮替代「保留/注释」toggle(新 i18n
└─ 无 duplicateInfo(尚未去重)
```
**默认展开**错误卡片(`body-<id>` display:block),让用户立即看到错误原因。
**分区容器加 `data-section` 属性**(前端 DOM 手术定位用):
- 错误分区容器:`data-section="error"`(原 `renderErrorSection`,标题/图标不变)
- `renderSection` 三个分区容器分别加 `data-section="exact" | "overlap" | "none"`
**顶部 summary 计数加 id**(前端更新用):
- `⛔ 完全重复 N 条``<span id="count-exact">`
- `⚠️ 部分重叠 N 条``<span id="count-overlap">`
- `✅ 无重复 N 条``<span id="count-none">`
- 错误分区标题计数 → `<span id="count-error">`(放在 `renderErrorSection` 的 section-title 内)
**确认按钮不再静态禁用**:改为 JS 动态控制。初始无有效规则时保持禁用 + 显示 `emptyValidHint`;一旦「添加」成功移入有效分区,JS 启用按钮并隐藏提示。
### 3.4 `import-preview.ts`:扩展端消息处理
`showImportPreview``onDidReceiveMessage` 增加分支:
```ts
panel.webview.onDidReceiveMessage(async (msg) => {
if (msg.type === 'toggleRule') {
keepRule[msg.ruleId] = msg.keep;
} else if (msg.type === 'addErrorRule') {
await handleAddErrorRule(msg, result, keepRule, context, panel);
} else if (msg.type === 'confirm') {
resolve({ keepRule, confirmed: true, editedRules: msg.editedRules });
panel.dispose();
} else if (msg.type === 'cancel') {
resolve(null);
panel.dispose();
}
});
```
`handleAddErrorRule` 逻辑(顺序严格):
```ts
async function handleAddErrorRule(msg, result, keepRule, context, panel) {
const rule = msg.rule;
// [1] 格式校验(复用前端同一套规则)
const err = validateRule(rule); // id/severity/description/message
if (err) {
panel.webview.postMessage({ type: 'addError', ruleId: msg.ruleId, message: err });
return;
}
// [2] id 冲突检查(对照 result.rules 中非错误规则,忽略大小写)
const conflict = result.rules.some(r =>
!r.validationIssues?.length && r.id.toLowerCase() === rule.id.toLowerCase()
);
if (conflict) {
panel.webview.postMessage({
type: 'addError', ruleId: msg.ruleId,
message: t('import.idConflict', { 0: rule.id }),
});
return;
}
// [3] AI 单条去重(内部已重试 1 次),失败降级 none
const dedup = await dedupSingleRule(rule, context);
const level = dedup?.duplicateLevel ?? 'none';
const dedupFailed = !dedup;
// [4] 更新 result.rules 中该条规则(按原始 id 定位)
const idx = result.rules.findIndex(r => r.id === msg.ruleId);
if (idx >= 0) {
result.rules[idx] = {
...result.rules[idx],
id: rule.id,
severity: rule.severity,
description: rule.description,
message: rule.message,
languages: rule.languages,
excludeLanguages: rule.excludeLanguages,
duplicateLevel: level,
duplicateOf: dedup?.duplicateOf,
duplicateReason: dedup?.duplicateReason,
validationIssues: undefined,
};
}
// [5] keepRule 按新 id 记录(exact → false
keepRule[rule.id] = level !== 'exact';
// [6] 通知前端移动卡片
panel.webview.postMessage({
type: 'ruleAdded',
ruleId: msg.ruleId, // 原始 id,前端定位 DOM
id: rule.id, // 新 id
duplicateLevel: level,
duplicateOf: dedup?.duplicateOf,
duplicateReason: dedup?.duplicateReason,
dedupFailed,
});
}
function validateRule(rule): string | null {
if (!rule.id || !rule.id.trim()) return t('import.validationIdEmpty');
if (!['error', 'warning', 'info'].includes(rule.severity)) return t('import.validationSeverityInvalid');
if (!rule.description || !rule.description.trim()) return t('import.validationDescEmpty', { 0: rule.id });
if (!rule.message || !rule.message.trim()) return t('import.validationMsgEmpty', { 0: rule.id });
return null;
}
```
> `context` 需传入 `showImportPreview`(新增参数)或在模块内暂存——采用**新增参数**`showImportPreview(result, context)`。
### 3.5 `import-preview.ts`:前端 JS 改造
新增 / 修改函数:
```js
// 从单张卡片提取当前字段(供添加与校验复用;从 collectEditedRules 抽取公共逻辑)
function collectCardRule(ruleId) { /* 读 id-display-input / select / textareas / tag-lists */ }
let addedRules = 0; // 已成功添加的错误规则数
// 点击「添加」
function addErrorRule(ruleId) {
const btn = document.querySelector(`[data-addbtn="${ruleId}"]`);
btn.disabled = true; btn.textContent = ADDING_TEXT;
const rule = collectCardRule(ruleId);
vscode.postMessage({ type: 'addErrorRule', ruleId, rule });
}
// 接收扩展消息
window.addEventListener('message', e => {
const msg = e.data;
if (msg.type === 'addError') {
// 卡片内展示 msg.message,恢复添加按钮可点击
} else if (msg.type === 'ruleAdded') {
moveCardToSection(msg);
}
});
function moveCardToSection(msg) {
const card = document.querySelector(`.rule-card[data-ruleid="${msg.ruleId}"]`);
// 1. 更新 id 相关:card.dataset.ruleid = msg.id;两个 id input 值为 msg.id
// 2. 移除 data-error 与错误边框样式
// 3. 还原「添加」按钮为保留/注释 toggle(按 keepRule 状态)—— 需从扩展同步 keep 状态:
// msg.duplicateLevel === 'exact' 时默认注释态,否则保留态
// 4. 更新徽标:exact →「将注释」;overlap/none →「保留」(badge-exact/overlap/none
// 5. 追加 duplicateInfoexact/overlap 文案,复用现有拼接逻辑)
// 6. 移入对应分区:document.querySelector(`[data-section="${section}"]`).appendChild(card)
// 7. addedRules++;更新计数与确认按钮状态
}
// 分区标题计数(N 条)与顶部 summary 计数统一重算
function updateSectionCounts() {
// 按 data-section 遍历,重算 4 个分区标题计数 + count-exact/overlap/none
// 错误分区为空 → 隐藏整个分区容器
// 无任何有效规则 → 禁用确认按钮 + 显示 emptyValidHint;否则启用
}
// 确认:hasEdits 或 addedRules>0 时必带 editedRules
function doConfirm() {
const err = validate();
if (err) { /* 现逻辑 */ return; }
const edited = collectEditedRules();
const hasEdits = Object.keys(editedRules).length > 0;
const withData = (hasEdits || addedRules > 0) ? edited : undefined;
vscode.postMessage({ type: 'confirm', editedRules: withData });
}
```
`updateSummary()`(保留/注释计数)已按 `data-error` 跳过错误卡片,无需改动;`moveCardToSection` 移除 `data-error` 后该卡片自动纳入统计。
### 3.6 关键边界
| 边界 | 处理 |
|------|------|
| 添加时 id 冲突(预览内已有有效规则) | `addError` 提示改 id,卡片留在错误分区 |
| AI 去重首次失败 | 自动重试 1 次(共 2 次尝试,quiet 模式不弹错) |
| 重试仍失败 | 降级 `none` 移入无重复分区,前端提示「AI 去重失败,已以无重复方式添加」 |
| 添加后 id 被修改导致与后续规则重复 | 后续添加时冲突检查覆盖全量有效规则,会拦截 |
| 只添加未编辑字段 | `addedRules>0` 仍回传 `editedRules`,走 `renderRulesToYaml`,已添加规则不会丢失 |
| 错误规则未添加即确认 | 维持现状:`data-error` 卡片被 `collectEditedRules`/`validate` 跳过,自动丢弃 |
| 初始无有效规则 | 确认按钮禁用;「添加」成功第一条后 JS 启用 |
| 未配置 API Key | `convertContentWithAI` 返回 nullquiet),重试后降级 none,流程不中断 |
### 3.7 i18n 新增 / 调整
| key | zh-CN | en | ja |
|-----|-------|----|----|
| `import.add`(新增) | 添加 | Add | 追加 |
| `import.adding`(新增) | 校验并去重中... | Validating & deduping... | 検証・重複排除中... |
| `import.idConflict`(新增) | id {0} 与已有规则重复,请修改 id | id {0} conflicts with an existing rule, change the id | id {0} が既存ルールと重複、id を変更してください |
| `import.addDedupFallback`(新增) | AI 去重失败,已以无重复方式添加 | AI dedup failed, added as no-duplicate | AI 重複排除失敗、重複なしとして追加 |
| `import.validationSeverityInvalid`(新增) | severity 非法 | Invalid severity | severity が不正です |
| `import.cannotImport`(调整) | 需修复后点击添加 | Fix then click Add | 修正して「追加」をクリック |
---
## 四、文件变更清单
| 文件 | 操作 | 内容 |
|------|------|------|
| `src/rules/import-service.ts` | 修改 | 新增 `dedupSingleRule`(含 `DedupResult` 接口);`convertContentWithAI` 增加 `quiet?` 参数 |
| `src/rules/import-preview.ts` | 修改 | 错误卡片完整表单 + 添加流程;`showImportPreview(result, context)``handleAddErrorRule`;前端 `addErrorRule`/`moveCardToSection`/`updateSectionCounts`/`doConfirm`;分区 `data-section` 与计数 id;确认按钮动态控制 |
| `src/i18n/messages.ts` | 修改 | 新增 5 个 key,调整 `import.cannotImport` 三语文案 |
| `src/rules/import-types.ts` | 不改 | — |
| `src/rules/converters/template-converter.ts` | 不改 | 校验逻辑不动 |
## 五、验证
验证顺序 `lint → compile`(当前仓库无测试文件):
- `npm run lint`ESLint `src/`
- `npm run compile`tsc
- 手动验证(Extension Dev Host):
1. 导出模板 → 填一行错误数据(如空 description、拼错 severity)→ 模板导入 → 预览中错误卡片可展开编辑
2. 修复后点「添加」→ 卡片移入对应分区、计数更新、可切换保留/注释
3. 不改任何字段再添加一条 → 确认导入 → 写盘 YAML 含已添加规则
4. id 冲突 → 添加被拦截并提示
5. 断网/无 Key → 重试后降级无重复分区,卡片有降级提示
6. 未添加的错误规则 → 确认后自动丢弃
@@ -0,0 +1,244 @@
# 导入预览 · 错误规则字段级报红设计书
> 版本:v1.0
> 日期:2026-07-31
> 适用项目:vscode-code-reviewer
> 参考文档:`2026-07-31-import-error-rule-edit-design.md`(错误规则可编辑 + 添加流程)
---
## 一、方案概览
### 1.1 目标
导入预览中的错误规则卡片,报红从「整卡红边 + 半透明 + 顶部 issues 横幅」改为**字段级报红**:
- 整卡恢复普通样式
- 仅出错的输入框加红框高亮,错误原因文字显示在该字段下方
- 用户修复字段时**实时清除**该字段的红框与提示
- 「添加」失败与 id 冲突同样精确定位到具体字段
### 1.2 现状 → 改造
```
现状:
┌─ 规则卡片(opacity:0.7 + 红边框)─────────────┐
│ [需修复后点击添加] │
│ ⚠ description 为空 │ ← 顶部 issues 横幅
│ ⚠ message 为空 │
│ [id] [severity] [description] [message] ... │ ← 全部无高亮
└────────────────────────────────────────────────┘
改造后:
┌─ 规则卡片(普通样式)─────────────────────────┐
│ [需修复后点击添加] │
│ [id] │
│ [severity] │
│ [description] ← 红框 │
│ ⚠ description 为空 │ ← 字段下方提示
│ [message] ← 红框 │
│ ⚠ message 为空 │
└────────────────────────────────────────────────┘
```
### 1.3 范围
| 类型 | 内容 |
|------|------|
| 含 | 初始错误字段红框 + 字段下方原因;添加失败/id 冲突定位到字段;实时清除;卡片样式还原 |
| 不含 | 新增 languages/excludeLanguages 校验(维持现状);错误卡片以外的样式改动 |
| 不触碰 | `import-service.ts``import-types.ts`、i18n 结构、扩展端去重流程 |
---
## 二、详细实现
改动文件**仅 `src/rules/import-preview.ts`**(前端渲染 + JS + 少量 CSS,扩展端消息协议兼容扩展)。
### 2.1 字段 → 表单元素映射
| field | 表单元素 |
|-------|---------|
| `id` | `.id-display-input` |
| `severity` | `.edit-field select` |
| `description` | `.edit-field textarea`(第 1 个) |
| `message` | `.edit-field textarea`(第 2 个) |
### 2.2 `renderRuleCard` 错误分支改造
1. **卡片样式还原**:去掉 `opacity:0.7` 与红边框,`cardStyle` 仅保留错误徽标。
2. **去掉顶部 issues 横幅**:删除 `issuesHtml` 变量及其渲染。
3. **字段级渲染**:渲染各 `edit-field` 时,若 `validationIssues` 中存在对应 `field`,给该 `edit-field` 追加:
```html
<div class="edit-field field-error">
<label>description(规则描述)</label>
<textarea rows="2" ...>...</textarea>
<div class="field-error-msg">⚠ description 为空</div>
</div>
```
- `edit-field``field-error` 类;输入控件本身加 `field-error-input`
- 提示文字 `<div class="field-error-msg">⚠ {message}</div>` 插在输入控件之后、`edit-field` 内部末尾
实现方式:渲染前构建 `const issueByField = new Map((rule.validationIssues||[]).map(i => [i.field, i]))`;渲染 severity/description/message 三个字段时按 map 命中追加。
### 2.3 `validateRule` 返回字段
```ts
function validateRule(rule: ImportableRule): {
field: 'id' | 'severity' | 'description' | 'message';
message: string;
} | null {
if (!rule.id || !rule.id.trim()) {
return { field: 'id', message: t('import.validationIdEmpty') };
}
if (!['error', 'warning', 'info'].includes(rule.severity)) {
return { field: 'severity', message: t('import.validationSeverityInvalid') };
}
if (!rule.description || !rule.description.trim()) {
return { field: 'description', message: t('import.validationDescEmpty', { 0: rule.id }) };
}
if (!rule.message || !rule.message.trim()) {
return { field: 'message', message: t('import.validationMsgEmpty', { 0: rule.id }) };
}
return null;
}
```
`handleAddErrorRule` 中:
- `validateRule` 失败 → `postMessage({ type:'addError', ruleId, field, message })`
- id 冲突 → `postMessage({ type:'addError', ruleId, field:'id', message: t('import.idConflict', ...) })`
### 2.4 前端消息处理改造
`showCardError(ruleId, message)``showCardError(ruleId, field, message)`
```js
function setFieldError(card, field, message) {
const el = fieldElement(card, field);
if (!el) return;
el.classList.add('field-error-input');
const wrap = el.closest('.edit-field');
if (!wrap) return;
wrap.classList.add('field-error');
let msg = wrap.querySelector('.field-error-msg');
if (!msg) {
msg = document.createElement('div');
msg.className = 'field-error-msg';
wrap.appendChild(msg);
}
msg.textContent = '⚠ ' + message;
}
function clearFieldError(card, field) {
const el = fieldElement(card, field);
if (!el) return;
el.classList.remove('field-error-input');
const wrap = el.closest('.edit-field');
if (wrap) {
wrap.classList.remove('field-error');
const msg = wrap.querySelector('.field-error-msg');
if (msg) msg.remove();
}
}
function fieldElement(card, field) {
if (field === 'id') return card.querySelector('.id-display-input');
if (field === 'severity') return card.querySelector('.edit-field select');
const tas = card.querySelectorAll('.edit-field textarea');
return field === 'description' ? (tas[0] || null) : (tas[1] || null);
}
```
`window.addEventListener('message')``addError` 分支改为透传 `field`
### 2.5 实时清除(事件委托)
`document` 上委托监听 `input``change`
```js
function liveClear(event) {
const card = event.target.closest('.rule-card');
if (!card || !card.hasAttribute('data-error')) return;
const target = event.target;
if (target.classList.contains('id-display-input') || target.classList.contains('rule-id-input')) {
if (target.value.trim()) clearFieldError(card, 'id');
} else if (target.tagName === 'SELECT') {
clearFieldError(card, 'severity');
} else if (target.tagName === 'TEXTAREA') {
const tas = card.querySelectorAll('.edit-field textarea');
const field = tas[0] === target ? 'description' : (tas[1] === target ? 'message' : null);
if (field && target.value.trim()) clearFieldError(card, field);
}
}
document.addEventListener('input', liveClear);
document.addEventListener('change', liveClear);
```
> severity 下拉天然只会给出合法值,故 `change` 即清除;id/description/message 以非空 trim 判定。
### 2.6 `moveCardToSection` 清理
卡片移入有效分区后,清除该卡全部字段级错误:
```js
function clearCardFieldErrors(card) {
card.querySelectorAll('.field-error-input').forEach(el => {
el.classList.remove('field-error-input');
});
card.querySelectorAll('.field-error').forEach(wrap => {
wrap.classList.remove('field-error');
const msg = wrap.querySelector('.field-error-msg');
if (msg) msg.remove();
});
}
```
在移除 `data-error` 后调用。
### 2.7 CSS
```css
.field-error-input {
border-color: rgba(248,81,73,0.7) !important;
box-shadow: 0 0 0 1px rgba(248,81,73,0.25);
}
.field-error-msg {
color: #f48771; font-size: 11px; margin-top: 4px;
}
```
删除不再使用的 `.error-issues` 规则(或保留无引用,推荐删除)。
---
## 三、边界与影响
| 边界 | 处理 |
|------|------|
| 同一卡片多字段出错 | 每个字段独立红框 + 独立提示,互不影响 |
| 字段修复后再点「添加」 | 校验通过即入区;实时清除逻辑保证先显示绿色状态 |
| 添加失败(未修复) | 红框/提示重新命中对应字段 |
| id 冲突 | `field:'id'` 定位到 id 输入框 |
| severity 原始非法但默认值为合法 | 红框+提示展示原始问题,下拉 change 即清除 |
| 确认导入校验(`validate()` | 仍跳过 `data-error` 卡片,行为不变 |
| 非模板导入路径 | 无 `validationIssues`,不受影响 |
## 四、文件变更清单
| 文件 | 操作 | 内容 |
|------|------|------|
| `src/rules/import-preview.ts` | 修改 | 卡片样式还原;字段级错误渲染;`validateRule` 返回字段;`addError` 消息带 field`setFieldError`/`clearFieldError`/`clearCardFieldErrors`/`fieldElement`input/change 委托实时清除;`moveCardToSection` 清理;CSS `.field-error-*` |
i18n、import-service、import-types 均不改动。
## 五、验证
- `npm run lint` + `npm run compile`
- 复用既有 harness 思路,mock vscode 渲染 webview 脚本并校验语法
- 扩展端消息流验证:
- 初始错误卡片:description/message 空 → 对应文本域带 `field-error-input`,无整卡红边、无顶部横幅
- 添加失败(description 空)→ `addError` 消息带 `field:'description'`
- id 冲突 → `addError` 消息带 `field:'id'`
- 模拟 input 事件 → 修复后红框清除
@@ -0,0 +1,1000 @@
# 方法级代码审查功能实施计划书
> 面向 AI 编码 agent 的技术实施文档。本文档包含完整的架构设计、文件清单、代码模板和验收标准,可直接据此编码。
## 项目上下文
- 仓库:`sdjndaq/2026-ai-b3`Gitee 私有仓库)
- 分支:`vscode-code-reviewer`
- 项目名:**代码审查官 · Code Purifier**`vscode-code-reviewer`
- 版本:1.2.0
- 类型:VS Code 扩展插件(TypeScript
- 技术栈:TypeScript + VS Code Extension API + DeepSeek AI
## 功能目标
为现有代码审查插件新增**方法级触发**能力。用户在每个函数声明行上方看到 CodeLens 按钮,点击后对该方法执行**自定义规则审查 + AI 深度审查**。方法级审查不执行静态分析(IDE 已实时提供 linter 诊断),但加载自定义规则与 AI 协同工作:AI 先按自定义规则做匹配,再以 6 维度增强策略做深度审查,单次调用同时产出两类结果。
### 核心设计决策
| 决策 | 选择 | 理由 |
|------|------|------|
| 审查流程 | 自定义规则 + AI(不走静态分析) | IDE 已实时提供 linter 诊断,静态分析冗余;自定义规则保留是因为团队规则仍需在方法级生效 |
| AI 调用模式 | 单次调用合并规则匹配 + 深度审查 | 方法代码短,token 预算充足,合并为单次调用减少延迟 |
| 触发方式 | CodeLens 行内按钮 | 最直观的交互,用户无需记忆命令或快捷键 |
| 方法提取 | VS Code Symbol API | 原生支持,覆盖 TS/JS/Java/Python 等主流语言 |
| 调用链 | 同文件粗匹配 | 首版限制在同文件内,控制实现复杂度 |
| AI 审查范围 | 6 维度增强 | AI 同时承担规则匹配和深度审查,维度覆盖比全文件更广 |
## 现有架构摘要
编码前必须理解以下现有代码结构,所有新代码需与这些模式保持一致。
### 入口与注册
`src/extension.ts``activate(context)` 函数完成三件事:
1. 创建 `Orchestrator` 实例
2. 注册 `SetupViewProvider`Webview View
3. 调用 `registerCommands(context, orchestrator)` 注册所有命令
4. 监听 `onDidSaveTextDocument` 执行防抖静态分析
5. 监听 `onDidChangeConfiguration` 切换 i18n 语言
### 命令注册
`src/activation/commands.ts``registerCommands(context, orchestrator)` 注册命令。现有命令通过 `vscode.window.withProgress` 包裹执行,使用 `t()` 函数做 i18n。核心命令 `codeReviewer.review` 的执行流程:
```
获取 document + workingDir
→ withProgress:
→ orchestrator.runStaticAnalysis(document, workingDir)
→ loadActiveRules(workspaceRoot) + filterAndSummarize
→ runAIReview(context, code, staticDiagnostics, relevantRules)
→ mergeResults(...)
→ ReviewPanel.createOrShow + panel.update(currentReport)
```
### AI 引擎
`src/ai/engine.ts` 导出 `runAIReview(context, code, staticDiagnostics, customRules)` 函数。内部流程:
1. `getApiKey(context)` 获取 API Key
2. `createProvider(providerId, apiKey, baseUrl, extensionUri)` 创建 AI Provider
3. 并行发起两个请求:
- `requestA`:自定义规则匹配(`buildCustomRuleSystemPrompt` + `buildUserPromptCustomRules`
- `requestB`:深度审查(`buildDeepReviewSystemPrompt` + `buildUserPromptDeepReview`
4. `parseJsonResponse` 解析 JSON
5. 返回 `AIEngineResult`
现有 `buildDeepReviewSystemPrompt()` 的定位是"补充静态分析的盲区",任务包含翻译静态诊断和发现额外问题。方法级审查不复用这个 prompt。
### 类型定义
`src/types.ts` 定义核心类型:
```typescript
interface CustomRule {
id: string;
severity: Severity;
description: string;
message: string;
languages?: string[];
excludeLanguages?: string[];
}
interface LinterDiagnostic {
severity: Severity;
ruleId: string;
message: string;
range: vscode.Range;
suggestion?: string;
}
interface LinterAdapter {
id: string;
supportedLanguages: string[];
check(document: vscode.TextDocument, workingDir: string): Promise<AdapterResult>;
isAvailable(): boolean;
}
```
`src/ai/schema.ts` 定义 AI 输出类型:
```typescript
interface AIFinding {
ruleId: string;
severity: 'error' | 'warning' | 'info';
category: 'bug' | 'performance' | 'security' | 'style' | 'design';
title: string;
description: string;
suggestion: string;
codeDiff?: string;
line: number;
}
interface AIEngineResult {
customRuleResults: CustomRuleResult[];
translatedDiagnostics: TranslatedDiagnostic[];
findings: AIFinding[];
degraded: boolean;
error?: string;
}
```
### 结果合并与展示
`src/merger/merger.ts``mergeResults(...)` 将静态诊断、AI 发现等聚合成 `MergedReport``src/panel/webview.ts``ReviewPanel` 类负责 Webview 面板的创建和更新。
### i18n
`src/i18n/messages.ts` 导出 `t(key, params?)``setLanguage(lang)` 函数。所有用户可见文案必须通过 `t()` 获取。
## 改动总览
### 新增文件(3 个)
| 文件路径 | 职责 |
|----------|------|
| `src/scope/method-extractor.ts` | 从文档中提取方法符号、代码、签名、调用链 |
| `src/views/codeLensProvider.ts` | CodeLens Provider,在函数声明上方渲染审查按钮 |
| `src/scope/status-cache.ts` | 方法审查状态缓存,驱动 CodeLens 按钮文案刷新 |
### 修改文件(4 个 + package.json
| 文件路径 | 改动内容 |
|----------|----------|
| `src/activation/commands.ts` | 新增 `codeReviewer.reviewMethod` 命令 |
| `src/ai/engine.ts` | 新增 `runMethodReview` 函数 + 方法级 prompt builder |
| `src/ai/schema.ts` | 新增 `MethodFinding` 类型 + 扩展 category |
| `src/extension.ts` | 注册 CodeLensProvider |
| `package.json` | 新增 command、codelens 配置项 |
### 不修改的文件
`src/orchestrator/orchestrator.ts``src/merger/merger.ts``src/panel/webview.ts``src/ai/providers/` 目录、`src/i18n/` 目录均不需要修改。方法级审查的执行路径不经过 Orchestrator,直接从命令层调用 engine.ts。自定义规则的加载复用现有 `src/rules/rule-loader.ts``loadActiveRules` 函数,过滤复用 `src/rules/rule-filter.ts``filterForDocument` 函数,均无需修改。
---
## 新增文件详细设计
### 1. `src/scope/method-extractor.ts`
#### 职责
从 VS Code 文档中提取方法级别的信息,包括方法代码、签名和同文件内的调用链。
#### 导出类型
```typescript
import * as vscode from 'vscode';
export interface MethodScope {
/** 方法名称 */
name: string;
/** 方法在文档中的完整范围 */
range: vscode.Range;
/** 方法代码文本(带行号) */
code: string;
/** 方法签名(一行摘要,如 "processOrder(order: Order): Promise<Result>" */
signature: string;
/** 调用者方法名列表(同文件内引用了本方法的其他方法) */
callers: string[];
/** 被调用方法名列表(本方法体内调用的其他方法) */
callees: string[];
/** 方法在业务流中的角色描述(由符号层级推断) */
role: string;
}
export interface MethodSymbol {
/** 方法名称 */
name: string;
/** 方法范围 */
range: vscode.Range;
/** 方法所在类/模块名(如果有) */
containerName?: string;
}
```
#### 导出函数
```typescript
/**
* 获取文档中所有方法/函数符号
* 使用 VS Code Symbol API,无 Provider 时回退到正则匹配
*/
export async function getMethodSymbols(
document: vscode.TextDocument
): Promise<MethodSymbol[]>;
/**
* 提取指定位置所在方法的完整 MethodScope
* @param document 文档
* @param position 光标位置或 Symbol 范围
*/
export async function extractMethodScope(
document: vscode.TextDocument,
range: vscode.Range
): Promise<MethodScope | null>;
```
#### 实现要点
**Symbol API 获取符号树:**
```typescript
const symbols = await vscode.commands.executeCommand<vscode.DocumentSymbol[]>(
'vscode.executeDocumentSymbolProvider',
document.uri
);
```
过滤 `SymbolKind.Function``SymbolKind.Method``SymbolKind.Constructor` 类型的节点。递归遍历子符号(类内的方法)。
**正则回退(无 Symbol Provider 时):**
针对 JavaScript/TypeScript 使用正则 `/(?:async\s+)?function\s+(\w+)|(?:const|let|var)\s+(\w+)\s*=\s*(?:async\s*)?\(/g` 匹配函数声明。针对 Java 使用 `/((?:public|private|protected|static)\s+)*\w+(?:<[^>]+>)?\s+(\w+)\s*\(/g`
**调用链粗匹配:**
获取同文件内所有方法符号后,对每个方法:
- **被调用者**:在本方法代码文本中搜索其他方法名是否出现(正则 `\b方法名\s*\(`
- **调用者**:遍历其他所有方法的代码,检查本方法名是否出现
**签名提取:**
取方法代码的第一行(到 `{``)` 结束),去除注释和多余空白。
**role 字段推断:**
根据 `containerName` 和方法名粗略推断,例如:
- `containerName` 包含 "Controller" → "HTTP 请求处理入口"
- `containerName` 包含 "Service" → "业务逻辑处理"
- 方法名以 "get/set/is" 开头 → "属性访问器"
- 默认 → "通用方法"
### 2. `src/views/codeLensProvider.ts`
#### 职责
实现 `vscode.CodeLensProvider`,在每个函数声明行上方渲染审查按钮。
#### 类定义
```typescript
import * as vscode from 'vscode';
import { getMethodSymbols } from '../scope/method-extractor';
import { ReviewStatusCache } from '../scope/status-cache';
import { t } from '../i18n/messages';
export class MethodCodeLensProvider implements vscode.CodeLensProvider {
private _onDidChangeCodeLenses: vscode.EventEmitter<void> =
new vscode.EventEmitter<void>();
readonly onDidChangeCodeLenses: vscode.Event<void> =
this._onDidChangeCodeLenses.event;
constructor(private statusCache: ReviewStatusCache) {}
/** 触发 CodeLens 刷新 */
refresh(): void {
this._onDidChangeCodeLenses.fire();
}
async provideCodeLenses(
document: vscode.TextDocument,
token: vscode.CancellationToken
): Promise<vscode.CodeLens[]> {
// 1. 检查配置是否启用
const config = vscode.workspace.getConfiguration('vscode-code-reviewer');
const enabled = config.get<boolean>('codelens.enabled', true);
if (!enabled) return [];
// 2. 检查语言是否在允许列表内
const languages = config.get<string[]>('codelens.languages', [
'typescript', 'javascript', 'java', 'python'
]);
if (!languages.includes(document.languageId)) return [];
// 3. 获取方法符号
const symbols = await getMethodSymbols(document);
if (symbols.length === 0) return [];
// 4. 方法数量上限保护
if (symbols.length > 50) return [];
// 5. 为每个方法生成 CodeLens
const lenses: vscode.CodeLens[] = [];
for (const symbol of symbols) {
const status = this.statusCache.get(document.uri, symbol.name);
const title = this.buildLensTitle(status);
lenses.push(new vscode.CodeLens(symbol.range.start, {
command: 'codeReviewer.reviewMethod',
title: title,
arguments: [symbol.range],
}));
}
return lenses;
}
private buildLensTitle(status: ReviewStatus | null): string {
if (!status) {
return t('codelens.reviewMethod');
}
if (status.issueCount === 0) {
return t('codelens.reviewedClean');
}
return t('codelens.reviewedWithIssues', { 0: String(status.issueCount) });
}
}
```
#### i18n key 约定
需要在 `src/i18n/messages.ts` 中新增以下 key(中英文):
| key | 中文 | English |
|-----|------|---------|
| `codelens.reviewMethod` | `🔍 Code Purifier: 审查此方法` | `🔍 Code Purifier: Review This Method` |
| `codelens.reviewedClean` | `✓ Code Purifier: 已审查(无问题)` | `✓ Code Purifier: Reviewed (No Issues)` |
| `codelens.reviewedWithIssues` | `✓ Code Purifier: 已审查({0} 个问题)` | `✓ Code Purifier: Reviewed ({0} Issues)` |
| `methodReview.running` | `Code Purifier 正在审查方法:{0}` | `Code Purifier: Reviewing method: {0}` |
| `methodReview.noMethod` | `当前位置未检测到方法` | `No method detected at current position` |
| `methodReview.complete` | `方法审查完成,发现 {0} 个问题` | `Method review complete, {0} issues found` |
### 3. `src/scope/status-cache.ts`
#### 职责
内存缓存,记录每个方法最近一次的审查结果,驱动 CodeLens 按钮文案刷新。
#### 类定义
```typescript
import * as vscode from 'vscode';
export interface ReviewStatus {
/** 发现的问题数量 */
issueCount: number;
/** 审查时间戳 */
timestamp: number;
}
export class ReviewStatusCache {
/** key 格式:documentUri.toString() + '::' + methodName */
private cache = new Map<string, ReviewStatus>();
get(uri: vscode.Uri, methodName: string): ReviewStatus | null {
const key = this.buildKey(uri, methodName);
return this.cache.get(key) ?? null;
}
set(uri: vscode.Uri, methodName: string, issueCount: number): void {
const key = this.buildKey(uri, methodName);
this.cache.set(key, {
issueCount,
timestamp: Date.now(),
});
}
/** 清除指定文档的所有缓存(文档关闭时调用) */
clearDocument(uri: vscode.Uri): void {
const prefix = uri.toString() + '::';
for (const key of this.cache.keys()) {
if (key.startsWith(prefix)) {
this.cache.delete(key);
}
}
}
private buildKey(uri: vscode.Uri, methodName: string): string {
return uri.toString() + '::' + methodName;
}
}
```
---
## 修改文件详细设计
### 4. `src/ai/schema.ts`
在现有类型基础上新增方法级审查的类型定义,不修改现有类型。
#### 新增内容
```typescript
/** 方法级审查的 category 类型(6 维度) */
export type MethodFindingCategory =
| 'correctness' // 正确性:分支覆盖、边界条件、异常路径
| 'security' // 安全性:输入校验、注入风险、权限检查
| 'design' // 设计:职责单一、参数合理性、调用链适配
| 'convention' // 规范:命名、复杂度、魔法数字、注释
| 'performance' // 性能:时间/空间复杂度、资源泄漏
| 'testability'; // 可测试性:副作用隔离、依赖可 Mock 性
/** 方法级审查的 finding,扩展 AIFinding */
export interface MethodFinding {
ruleId: string;
severity: 'error' | 'warning' | 'info';
category: MethodFindingCategory;
title: string;
description: string;
suggestion: string;
codeDiff?: string;
line: number;
/** 触发路径描述,如 "if(order == null) → NPE on .getId()" */
path?: string;
}
/** 方法级审查的 AI 返回结构 */
export interface MethodReviewResult {
/** 自定义规则匹配结果(AI 按规则做语义判断) */
customRuleResults: CustomRuleResult[];
/** AI 深度审查发现 */
findings: MethodFinding[];
degraded: boolean;
error?: string;
}
```
### 5. `src/ai/engine.ts`
新增方法级审查的入口函数和专用 prompt builder。不修改现有 `runAIReview` 及其相关函数。
#### 新增函数
```typescript
/**
* 方法级 AI 审查
* 不依赖静态诊断,但加载自定义规则与 AI 协同工作
* 单次 AI 调用同时完成规则匹配和 6 维度深度审查
*/
export async function runMethodReview(
context: vscode.ExtensionContext,
scope: MethodScope,
customRules: CustomRule[],
): Promise<MethodReviewResult>;
```
#### 内部实现流程
```typescript
export async function runMethodReview(
context: vscode.ExtensionContext,
scope: MethodScope,
customRules: CustomRule[],
): Promise<MethodReviewResult> {
// 1. 获取 API Key
const apiKey = await getApiKey(context);
if (!apiKey) {
return {
customRuleResults: [],
findings: [],
degraded: true,
error: t('adapter.noApiKey'),
};
}
// 2. 创建 Provider(复用现有工厂)
const providerId = getAIProvider();
const baseUrl = getAIBaseUrl();
let provider: AIProvider;
try {
provider = createProvider(providerId, apiKey, baseUrl, context.extensionUri);
} catch (err) {
return {
customRuleResults: [],
findings: [],
degraded: true,
error: t('adapter.createProviderFail', { 0: err instanceof Error ? err.message : String(err) }),
};
}
// 3. 构建请求
const options = {
model: getAIModel(),
temperature: getAITemperature(),
maxTokens: getAIMaxTokens(),
timeoutMs: getAITimeout() * 1000,
};
const numberedCode = addLineNumbers(scope.code);
// 4. 单次 AI 调用(规则匹配 + 深度审查合并为一次)
// 有规则时 prompt 包含规则匹配任务,无规则时只做深度审查
const hasRules = customRules.length > 0;
const result = await provider.chat(
buildMethodReviewSystemPrompt(hasRules),
buildMethodUserPrompt(scope, numberedCode, customRules),
options,
);
// 5. 解析结果
const errors: string[] = [];
let customRuleResults: CustomRuleResult[] = [];
let findings: MethodFinding[] = [];
if (result.status === 'fulfilled') {
try {
const parsed = parseJsonResponse(result.value) as {
customRuleResults?: CustomRuleResult[];
findings?: MethodFinding[];
};
customRuleResults = (parsed.customRuleResults ?? []).map(r => ({
...r,
ruleId: r.ruleId.startsWith('custom:') ? r.ruleId : `custom:${r.ruleId}`,
}));
findings = (parsed.findings ?? []).map(f => ({
...f,
ruleId: f.ruleId.startsWith('method:') ? f.ruleId : `method:${f.ruleId}`,
}));
} catch (e) {
errors.push(t('adapter.aiReviewParseFail', { 0: e instanceof Error ? e.message : String(e) }));
}
} else {
errors.push(t('adapter.aiReviewRequestFail', { 0: result.reason }));
}
return {
customRuleResults,
findings,
degraded: errors.length > 0,
error: errors.join('; ') || undefined,
};
}
```
#### 新增 prompt builder
```typescript
function buildMethodReviewSystemPrompt(hasRules: boolean): string {
const lang = getLanguage();
if (lang === 'en') {
const ruleSection = hasRules
? `## Task 1: Custom Rule Matching
Evaluate whether the method violates any of the provided custom rules.
Understand semantics, not text matching.
Report violations in "customRuleResults".\n\n`
: '';
const ruleOutput = hasRules
? ` "customRuleResults": [
{
"ruleId": "original rule id",
"line": line_number,
"severity": "error|warning|info",
"message": "violation description"
}
],\n`
: '';
return `You are a senior code review expert reviewing a single method.
There is no static analysis before you — you handle rule matching AND deep review.
${ruleSection}## Review Strategy: Path Enumeration
- Walk through every if/else/switch branch, note coverage and gaps
- Enumerate boundary values for every parameter (null, empty collection, extreme values, wrong types)
- Check every throw/catch path for proper fallback strategy
- Trace the method's role in its call chain
## Required Dimensions (do not skip any)
A. Correctness: branch coverage, boundary conditions, exception path completeness
B. Security: input validation, injection risk, permission check, sensitive data leakage
C. Design: single responsibility, parameter design, return value contract, call chain adaptation
D. Convention: naming, cyclomatic complexity, magic numbers, missing comments
E. Performance: time/space complexity, resource leaks, unnecessary computation
F. Testability: side effect isolation, dependency mockability, deterministic output
## Call Chain Analysis
- Check whether callers' arguments match this method's expectations
- Check whether this method's return value is correctly handled by callers
- Check whether exceptions are caught or declared by callers
Output JSON only. Double quotes in strings must be escaped with \\".
Format:
{
${ruleOutput} "findings": [
{
"ruleId": "method-boundary-null",
"severity": "error|warning|info",
"category": "correctness|security|design|convention|performance|testability",
"title": "issue title",
"description": "detailed description",
"suggestion": "fix suggestion",
"codeDiff": "optional fix diff",
"line": line_number,
"path": "trigger path description, e.g. if(order==null) -> NPE on .getId()"
}
]
}
If no issues found, return empty arrays.
Output language: en`;
}
// 中文 prompt(默认)
const ruleSection = hasRules
? `## 任务一:自定义规则匹配
评估方法是否违反了提供的自定义规则。
理解语义,而非文本匹配。
在 "customRuleResults" 中报告违规。\n\n`
: '';
const ruleOutput = hasRules
? ` "customRuleResults": [
{
"ruleId": "原始规则 ID",
"line": 行号,
"severity": "error|warning|info",
"message": "违规描述"
}
],\n`
: '';
return `你是资深代码审查专家,正在审查单个方法。
没有静态分析的前置过滤——你同时负责规则匹配和深度审查。
${ruleSection}## 审查策略:逐路径枚举
- 遍历每个 if/else/switch 分支,标注覆盖与遗漏
- 枚举每个入参的边界值(null、空集合、极值、错误类型)
- 检查每个 throw/catch 路径的降级策略
- 追踪方法在调用链中的角色
## 必须覆盖的维度(不可跳过)
A. 正确性:分支覆盖、边界条件、异常路径完整性
B. 安全性:输入校验、注入风险、权限检查、敏感信息泄露
C. 设计:职责单一性、参数设计合理性、返回值契约、调用链适配
D. 规范:命名、圈复杂度、魔法数字、注释缺失
E. 性能:时间/空间复杂度、资源泄漏、不必要的计算
F. 可测试性:副作用隔离、依赖可 Mock 性、确定性输出
## 调用链分析
- 检查调用者传入的参数是否符合本方法预期
- 检查本方法的返回值是否被调用者正确处理
- 检查异常是否被调用者捕获或声明
输出 JSON,字符串中的双引号必须用 \\" 转义。
格式:
{
${ruleOutput} "findings": [
{
"ruleId": "method-boundary-null",
"severity": "error|warning|info",
"category": "correctness|security|design|convention|performance|testability",
"title": "问题标题",
"description": "详细描述",
"suggestion": "修复建议",
"codeDiff": "可选的修复 diff",
"line": 行号,
"path": "触发路径描述,如 if(order==null) -> NPE on .getId()"
}
]
}
如果未发现问题,返回空数组。
输出语言:zh-CN`;
}
function buildMethodUserPrompt(
scope: MethodScope,
numberedCode: string,
customRules: CustomRule[],
): string {
const lang = getLanguage();
const codeLabel = lang === 'en' ? 'Method Code (with line numbers)' : '方法代码(带行号)';
const sigLabel = lang === 'en' ? 'Method Signature' : '方法签名';
const chainLabel = lang === 'en' ? 'Call Chain Context' : '调用链上下文';
const roleLabel = lang === 'en' ? 'Role in Business Flow' : '业务流中的角色';
const callersLabel = lang === 'en' ? 'Callers' : '调用者';
const calleesLabel = lang === 'en' ? 'Callees' : '被调用者';
const noneLabel = lang === 'en' ? '(none)' : '(无)';
// 自定义规则块:有规则时注入,无规则时省略
let ruleBlock = '';
if (customRules.length > 0) {
const ruleLabel = lang === 'en' ? 'Custom Rules to Match' : '需匹配的自定义规则';
const ruleLines = customRules
.map((r, i) => `${i + 1}. [${r.id}] (${r.severity}) ${r.description}\n ${r.message}`)
.join('\n');
ruleBlock = `\n## ${ruleLabel}\n${ruleLines}\n`;
}
return `## ${sigLabel}
${scope.signature}
## ${codeLabel}
${numberedCode}
${ruleBlock}
## ${chainLabel}
${roleLabel}: ${scope.role}
${callersLabel}: ${scope.callers.length > 0 ? scope.callers.join(', ') : noneLabel}
${calleesLabel}: ${scope.callees.length > 0 ? scope.callees.join(', ') : noneLabel}`;
}
```
#### 与现有 `runAIReview` 的关键差异
| 对比项 | 现有 `runAIReview` | 新增 `runMethodReview` |
|--------|--------------------|-----------------------|
| 输入参数 | `code, staticDiagnostics, customRules` | `scope: MethodScope, customRules` |
| AI 调用次数 | 2 次(requestA + requestB 并行) | 1 次(规则匹配 + 深度审查合并) |
| prompt 定位 | 补充静态分析盲区 | 唯一审查者,全覆盖 |
| 输出结构 | `customRuleResults + translatedDiagnostics + findings` | `customRuleResults + findings` |
| category | 5 类 | 6 类(新增 correctness、testability |
| path 字段 | 无 | 有(触发路径描述) |
| 规则匹配方式 | 独立 prompt + 独立请求 | 与深度审查合并为单次 prompt |
### 6. `src/activation/commands.ts`
新增 `codeReviewer.reviewMethod` 命令。不修改现有命令。
#### 新增命令注册
`registerCommands` 函数内追加:
```typescript
context.subscriptions.push(
vscode.commands.registerCommand(
'codeReviewer.reviewMethod',
async (symbolRange: vscode.Range) => {
const editor = vscode.window.activeTextEditor;
if (!editor) return;
const document = editor.document;
const workspaceRoot = vscode.workspace.getWorkspaceFolder(document.uri)?.uri.fsPath;
// 1. 提取方法
const scope = await extractMethodScope(document, symbolRange);
if (!scope) {
vscode.window.showWarningMessage(t('methodReview.noMethod'));
return;
}
// 2. 加载并过滤自定义规则(复用现有 rule-filter
let customRules: CustomRule[] = [];
if (workspaceRoot) {
const allRules = await loadActiveRules(workspaceRoot);
customRules = filterForDocument(allRules, document);
}
// 3. 执行 AI 审查(传入自定义规则)
await vscode.window.withProgress(
{
location: vscode.ProgressLocation.Notification,
title: t('methodReview.running', { 0: scope.name }),
cancellable: false,
},
async () => {
const result = await runMethodReview(context, scope, customRules);
// 4. 构造 MergedReport(复用现有 merger
// customRuleResults 和 findings 均来自 AI 单次调用
const totalIssues = result.customRuleResults.length + result.findings.length;
currentReport = mergeResults({
staticDiagnostics: [],
customRuleResults: result.customRuleResults,
translatedDiagnostics: [],
aiFindings: result.findings, // MethodFinding[] 兼容 AIFinding[]
errors: result.error ? [result.error] : [],
degraded: result.degraded,
startTime: Date.now(),
filePath: document.uri.fsPath,
language: document.languageId,
adapterIds: [],
customRuleFilterInfo: undefined,
});
// 5. 更新状态缓存并刷新 CodeLens
statusCache.set(document.uri, scope.name, totalIssues);
codeLensProvider.refresh();
// 6. 面板展示
const panel = ReviewPanel.createOrShow(context.extensionUri);
panel.update(currentReport);
// 7. 提示
vscode.window.showInformationMessage(
t('methodReview.complete', { 0: String(totalIssues) })
);
}
);
}
)
);
```
#### 函数签名变更
`registerCommands` 需要新增两个参数:
```typescript
export function registerCommands(
context: vscode.ExtensionContext,
orchestrator: Orchestrator,
codeLensProvider: MethodCodeLensProvider, // 新增
statusCache: ReviewStatusCache, // 新增
): void {
```
#### 新增 import
`commands.ts` 顶部需追加以下 import(现有 import 不变):
```typescript
import { loadActiveRules } from '../rules/rule-loader';
import { filterForDocument } from '../rules/rule-filter';
import { CustomRule } from '../types';
```
### 7. `src/extension.ts`
注册 CodeLensProvider 和 StatusCache。
#### 修改内容
`activate` 函数中,`registerCommands` 调用之前新增:
```typescript
export function activate(context: vscode.ExtensionContext) {
const lang = getAIOutputLanguage() as Language;
setLanguage(lang);
console.log(t('extension.activated'));
orchestrator = new Orchestrator();
const setupProvider = new SetupViewProvider(context);
context.subscriptions.push(
vscode.window.registerWebviewViewProvider('codeReviewer.setupView', setupProvider)
);
// ===== 新增:方法级审查基础设施 =====
const statusCache = new ReviewStatusCache();
const codeLensProvider = new MethodCodeLensProvider(statusCache);
context.subscriptions.push(
vscode.languages.registerCodeLensProvider(
{ scheme: 'file' },
codeLensProvider
)
);
// 文档关闭时清理状态缓存
context.subscriptions.push(
vscode.workspace.onDidCloseTextDocument((document) => {
statusCache.clearDocument(document.uri);
})
);
// ===== 新增结束 =====
registerCommands(context, orchestrator, codeLensProvider, statusCache);
// ... 保留现有的 onDidSaveTextDocument 和 onDidChangeConfiguration 逻辑不变 ...
}
```
### 8. `package.json`
#### 新增 command
`contributes.commands` 数组中追加:
```json
{
"command": "codeReviewer.reviewMethod",
"title": "Code Purifier: 审查此方法"
}
```
#### 新增 configuration
`contributes.configuration.properties` 中追加:
```json
"vscode-code-reviewer.codelens.enabled": {
"type": "boolean",
"default": true,
"description": "在函数声明上方显示方法级审查按钮"
},
"vscode-code-reviewer.codelens.languages": {
"type": "array",
"default": ["typescript", "javascript", "java", "python"],
"description": "启用方法级 CodeLens 的语言列表"
}
```
#### 新增 menu(可选)
`contributes.menus` 中追加右键菜单入口(作为 CodeLens 的补充):
```json
"editor/context": [
{
"command": "codeReviewer.reviewMethod",
"when": "editorTextFocus",
"group": "navigation"
}
]
```
注意:右键菜单触发时 `symbolRange` 参数为 `undefined`,命令内需要处理此情况——使用 `editor.selection.active` 作为 fallback 位置传入 `extractMethodScope`
---
## 实施顺序
按以下顺序实施,每步完成后可独立验证:
### Phase 1:基础设施
| 步骤 | 文件 | 验证方式 |
|------|------|----------|
| 1.1 | `src/scope/status-cache.ts` | 单元测试:set/get/clearDocument 行为正确 |
| 1.2 | `src/scope/method-extractor.ts` | 手动测试:在 TS 文件中调用 `getMethodSymbols``extractMethodScope`,确认能提取方法名、代码、调用链 |
| 1.3 | `src/ai/schema.ts` | TypeScript 编译通过,类型无冲突 |
### Phase 2AI 引擎
| 步骤 | 文件 | 验证方式 |
|------|------|----------|
| 2.1 | `src/ai/engine.ts` 新增 `buildMethodReviewSystemPrompt` | 确认 prompt 文本包含 6 维度、逐路径枚举策略,且有规则时包含 Task 1 规则匹配任务 |
| 2.2 | `src/ai/engine.ts` 新增 `buildMethodUserPrompt` | 确认输出包含签名、代码、调用链,且传入 `customRules` 时包含规则列表块 |
| 2.3 | `src/ai/engine.ts` 新增 `runMethodReview` | 配置 API Key + 自定义规则后手动调用,确认返回 `customRuleResults``findings` 两类结果 |
### Phase 3:命令与集成
| 步骤 | 文件 | 验证方式 |
|------|------|----------|
| 3.1 | `src/activation/commands.ts` 新增 `reviewMethod` 命令 | 通过命令面板触发,确认加载自定义规则并传入 `runMethodReview`,面板展示审查结果 |
| 3.2 | `src/extension.ts` 注册 CodeLensProvider | 打开 TS 文件,确认函数上方出现 CodeLens 按钮 |
| 3.3 | `src/views/codeLensProvider.ts` | 点击 CodeLens 按钮,确认触发方法级审查 |
| 3.4 | `package.json` 新增配置项 | 在设置中修改 `codelens.enabled`,确认 CodeLens 消失/出现 |
### Phase 4i18n 与打磨
| 步骤 | 文件 | 验证方式 |
|------|------|----------|
| 4.1 | `src/i18n/messages.ts` 新增 key | 切换输出语言为 en,确认 CodeLens 文案变为英文 |
| 4.2 | 右键菜单 fallback 处理 | 右键触发 `reviewMethod`,确认能用光标位置定位方法 |
| 4.3 | 状态缓存刷新 | 审查完成后确认 CodeLens 文案变为 `✓ 已审查(N 个问题)` |
---
## 验收标准
### 功能验收
1. 打开任意 `.ts` / `.js` / `.java` / `.py` 文件,每个函数声明上方出现 `🔍 Code Purifier: 审查此方法` CodeLens 按钮
2. 点击按钮后,右下角弹出进度通知 `Code Purifier 正在审查方法:xxx`
3. 审查完成后,CodeLens 按钮文案变为 `✓ Code Purifier: 已审查(N 个问题)``✓ Code Purifier: 已审查(无问题)`,其中 N = 自定义规则违规数 + AI 深度审查发现数
4. 审查面板自动打开,展示方法级的 AI 审查结果
5. 审查结果中,自定义规则违规的 `ruleId``custom:` 前缀标识,AI 深度审查发现的 `ruleId``method:` 前缀标识
6. 审查结果覆盖 6 个维度(correctness / security / design / convention / performance / testability
7. 审查结果中的 `path` 字段描述了触发路径
8. 当工作区配置了自定义规则时,审查结果中包含 `customRuleResults`,且违规内容与规则语义相关(非文本匹配)
9. 当工作区无自定义规则时,审查结果中 `customRuleResults` 为空数组,AI 仅做 6 维度深度审查
### 边界验收
1. 在没有 Symbol Provider 的纯文本文件中,CodeLens 不出现(不报错)
2. 文件内方法数超过 50 个时,CodeLens 不出现(性能保护)
3. 在设置中关闭 `codelens.enabled` 后,CodeLens 消失
4. 在设置中将某语言从 `codelens.languages` 中移除后,该语言文件不显示 CodeLens
5. 未配置 API Key 时,提示需要 API Key(不崩溃)
6. AI 返回非 JSON 时,降级提示解析失败(不崩溃)
7. 文档关闭后重新打开,CodeLens 恢复为初始状态 `🔍 Code Purifier: 审查此方法`(缓存已清理)
### 架构验收
1. `src/orchestrator/orchestrator.ts` 未被修改
2. `src/rules/rule-filter.ts` 未被修改
3. 现有 `codeReviewer.review` 命令行为不变(文档级三阶段审查正常工作)
4. 现有 `codeReviewer.reviewSelection` 命令行为不变
5. TypeScript 编译无错误(`npm run compile`
6. ESLint 检查无错误(`npm run lint`
---
## 注意事项
- 所有用户可见文案必须通过 `t()` 函数获取,支持中英文切换
- `runMethodReview` 内复用现有的 `getApiKey``createProvider``parseJsonResponse``addLineNumbers` 等工具函数,不重复实现
- `MethodFinding` 的字段与 `AIFinding` 高度重叠,`mergeResults` 可直接接收 `MethodFinding[]` 作为 `aiFindings` 参数(结构兼容)
- CodeLens 的 `refresh()` 通过 `_onDidChangeCodeLenses.fire()` 触发,VS Code 会重新调用 `provideCodeLenses`
- `package.json` 中的 `engines.vscode` 字段为 `^1.120.0`CodeLens API 和 DocumentSymbol API 在该版本完全支持
- 自定义规则通过 `loadActiveRules` + `filterForDocument` 加载,与现有 `codeReviewer.review` 命令使用同一加载路径,保证规则一致性
- AI 对自定义规则做语义匹配而非文本匹配——prompt 中明确要求"理解语义,而非文本匹配",避免简单关键词命中导致的误报
-`customRules` 为空数组时,system prompt 中的 Task 1 规则匹配段和 `customRuleResults` 输出段自动省略,AI 仅执行 6 维度深度审查
Binary file not shown.
+3 -2
View File
@@ -6,18 +6,19 @@ import net.sourceforge.pmd.renderers.*;
public class PmdRunner {
public static void main(String[] args) throws Exception {
if (args.length < 2) {
System.err.println("Usage: PmdRunner <filePath|- for stdin> <rulesetPath>");
System.err.println("Usage: PmdRunner <filePath|- for stdin> <rulesetPath> [extension]");
System.exit(1);
return;
}
String filePath = args[0];
String rulesetPath = args[1];
String extension = args.length >= 3 ? args[2] : "java";
Path tempFile = null;
if ("-".equals(filePath)) {
String code = new String(System.in.readAllBytes());
tempFile = Files.createTempFile("pmd-stdin-", ".java");
tempFile = Files.createTempFile("pmd-stdin-", "." + extension);
Files.writeString(tempFile, code);
filePath = tempFile.toString();
}
+62 -8
View File
@@ -48,6 +48,10 @@
{
"command": "codeReviewer.exportTemplate",
"title": "Code Purifier: 导出规则模板"
},
{
"command": "codeReviewer.reviewMethod",
"title": "Code Purifier: 审查此方法"
}
],
"keybindings": [
@@ -84,6 +88,10 @@
{
"command": "codeReviewer.reviewSelection",
"when": "editorHasSelection"
},
{
"command": "codeReviewer.reviewMethod",
"when": "editorTextFocus"
}
]
},
@@ -186,19 +194,19 @@
},
"vscode-code-reviewer.linters.sql": {
"type": "string",
"default": "sql-lint",
"default": "sqlfluff",
"enum": [
"",
"sql-lint"
"sqlfluff"
],
"description": "SQL linter"
},
"vscode-code-reviewer.linters.plsql": {
"type": "string",
"default": "sql-lint",
"default": "sqlfluff",
"enum": [
"",
"sql-lint"
"sqlfluff"
],
"description": "PL/SQL linter"
},
@@ -217,15 +225,51 @@
"default": "",
"description": "JSP 规则集 XML 路径(空=使用内置)"
},
"vscode-code-reviewer.sql-lint.configFile": {
"vscode-code-reviewer.sqlfluff.configFile": {
"type": "string",
"default": "",
"description": "sqlfluff 配置文件路径"
},
"vscode-code-reviewer.sqlfluff.dialect": {
"type": "string",
"default": "",
"enum": [
"",
"ansi",
"athena",
"bigquery",
"clickhouse",
"databricks",
"db2",
"doris",
"duckdb",
"exasol",
"flink",
"greenplum",
"hive",
"impala",
"mariadb",
"materialize",
"mysql",
"oracle",
"postgres",
"redshift",
"snowflake",
"soql",
"sparksql",
"sqlite",
"starrocks",
"teradata",
"trino",
"tsql",
"vertica"
],
"description": "SQLFluff 方言(空=自动:全局/项目配置 > 语言映射)"
},
"vscode-code-reviewer.linters.eslintConfigPath": {
"type": "string",
"default": "",
"description": "ESLint 自定义配置文件路径(绝对路径)。留空则使用项目 .eslintrc 或内置规则"
"description": "ESLint 自定义配置文件路径(绝对路径)。留空则使用项目 eslint.config.* 或内置规则"
},
"vscode-code-reviewer.linters.stylelintConfigPath": {
"type": "string",
@@ -237,10 +281,10 @@
"default": true,
"description": "启用/禁用 PMD 适配器"
},
"vscode-code-reviewer.linter.sql-lint.enabled": {
"vscode-code-reviewer.linter.sqlfluff.enabled": {
"type": "boolean",
"default": true,
"description": "启用/禁用 SQL-Lint 适配器"
"description": "启用/禁用 SQLFluff 适配器"
},
"vscode-code-reviewer.linter.eslint.enabled": {
"type": "boolean",
@@ -256,6 +300,16 @@
"type": "number",
"default": 5,
"description": "AI 修复时提取的上下文行数"
},
"vscode-code-reviewer.codelens.enabled": {
"type": "boolean",
"default": true,
"description": "在函数声明上方显示方法级审查按钮"
},
"vscode-code-reviewer.codelens.languages": {
"type": "array",
"default": ["typescript", "javascript", "java", "python"],
"description": "启用方法级 CodeLens 的语言列表"
}
}
}
+38
View File
@@ -0,0 +1,38 @@
import { readFileSync, writeFileSync } from 'fs';
import { resolve, dirname } from 'path';
import { fileURLToPath } from 'url';
import eslint from './translations/eslint.mjs';
import tsEsLint from './translations/ts-eslint.mjs';
import stylelint from './translations/stylelint.mjs';
import pmd1 from './translations/pmd-1.mjs';
import pmd2 from './translations/pmd-2.mjs';
import pmdJsp from './translations/pmd-jsp.mjs';
import sqlFluff from './translations/sqlfluff.mjs';
const __dirname = dirname(fileURLToPath(import.meta.url));
const srcPath = resolve(__dirname, '..', 'src', 'rules', 'static-rules.json');
const data = JSON.parse(readFileSync(srcPath, 'utf-8'));
const all = { ...eslint, ...tsEsLint, ...stylelint, ...pmd1, ...pmd2, ...pmdJsp, ...sqlFluff };
let added = 0;
const missing = [];
for (const rules of Object.values(data.rules)) {
for (const rule of rules) {
const t = all[rule.id];
if (t) {
rule.descriptionZh = t.zh;
rule.descriptionJa = t.ja;
added++;
} else {
missing.push(rule.id);
}
}
}
writeFileSync(srcPath, JSON.stringify(data, null, 2) + '\n', 'utf-8');
console.log(`added: ${added}`);
console.log(`missing: ${missing.length}`);
if (missing.length > 0) {
console.log(missing.join('\n'));
}
+94
View File
@@ -0,0 +1,94 @@
export default {
'eslint/constructor-super': { zh: '在构造函数中校验 super() 的调用', ja: 'コンストラクタで super() の呼び出しを検証する' },
'eslint/for-direction': { zh: '确保 for 循环更新子句朝正确方向移动计数器', ja: 'for ループの更新句がカウンタを正しい方向に進めることを強制する' },
'eslint/getter-return': { zh: '强制 getter 中有 return 语句', ja: 'getter に return 文を強制する' },
'eslint/no-async-promise-executor': { zh: '禁止使用 async 函数作为 Promise 执行器', ja: 'async 関数を Promise のエグゼキュータとして使用しない' },
'eslint/no-case-declarations': { zh: '禁止在 case 子句中声明词法变量', ja: 'case 節での語彙宣言を禁止する' },
'eslint/no-class-assign': { zh: '禁止重新赋值类成员', ja: 'クラスメンバーへの再代入を禁止する' },
'eslint/no-compare-neg-zero': { zh: '禁止与 -0 进行比较', ja: '-0 との比較を禁止する' },
'eslint/no-cond-assign': { zh: '禁止在条件表达式中使用赋值运算符', ja: '条件式での代入演算子を禁止する' },
'eslint/no-const-assign': { zh: '禁止重新赋值 const 变量', ja: 'const 変数への再代入を禁止する' },
'eslint/no-constant-binary-expression': { zh: '禁止常量二元表达式', ja: '定数の二項式を禁止する' },
'eslint/no-constant-condition': { zh: '禁止在条件中使用常量表达式', ja: '条件での定数式を禁止する' },
'eslint/no-control-regex': { zh: '禁止正则表达式中的控制字符', ja: '正規表現内の制御文字を禁止する' },
'eslint/no-debugger': { zh: '禁止使用 debugger 语句', ja: 'debugger 文の使用を禁止する' },
'eslint/no-delete-var': { zh: '禁止删除变量', ja: '変数の削除を禁止する' },
'eslint/no-dupe-args': { zh: '禁止函数定义中重复的参数', ja: '関数定義内の重複引数を禁止する' },
'eslint/no-dupe-class-members': { zh: '禁止重复的类成员', ja: '重複するクラスメンバーを禁止する' },
'eslint/no-dupe-else-if': { zh: '禁止 if-else-if 链中的重复条件', ja: 'if-else-if チェーン内の重複条件を禁止する' },
'eslint/no-dupe-keys': { zh: '禁止对象字面量中重复的键', ja: 'オブジェクトリテラル内の重複キーを禁止する' },
'eslint/no-duplicate-case': { zh: '禁止重复的 case 标签', ja: '重複する case ラベルを禁止する' },
'eslint/no-empty': { zh: '禁止空块语句', ja: '空のブロック文を禁止する' },
'eslint/no-empty-character-class': { zh: '禁止正则表达式中的空字符类', ja: '正規表現内の空の文字クラスを禁止する' },
'eslint/no-empty-pattern': { zh: '禁止空解构模式', ja: '空の分割代入パターンを禁止する' },
'eslint/no-empty-static-block': { zh: '禁止空的静态块', ja: '空の静的ブロックを禁止する' },
'eslint/no-ex-assign': { zh: '禁止在 catch 子句中重新赋值异常', ja: 'catch 句での例外への再代入を禁止する' },
'eslint/no-extra-boolean-cast': { zh: '禁止不必要的布尔转换', ja: '不要なブール変換を禁止する' },
'eslint/no-fallthrough': { zh: '禁止 case 语句的 fallthrough', ja: 'case 文のフォールスルーを禁止する' },
'eslint/no-func-assign': { zh: '禁止重新赋值函数声明', ja: '関数宣言への再代入を禁止する' },
'eslint/no-global-assign': { zh: '禁止对原生对象或只读全局变量赋值', ja: 'ネイティブオブジェクトや読み取り専用グローバルへの代入を禁止する' },
'eslint/no-import-assign': { zh: '禁止对导入的绑定赋值', ja: 'インポートされたバインディングへの代入を禁止する' },
'eslint/no-invalid-regexp': { zh: '禁止 RegExp 构造函数中的无效正则字符串', ja: 'RegExp コンストラクタ内の不正な正規表現文字列を禁止する' },
'eslint/no-irregular-whitespace': { zh: '禁止不规则空白', ja: '不規則な空白を禁止する' },
'eslint/no-loss-of-precision': { zh: '禁止会丢失精度的字面数字', ja: '精度を失うリテラル数値を禁止する' },
'eslint/no-misleading-character-class': { zh: '禁止字符类语法中使用多个码点构成的字符', ja: '文字クラス構文で複数のコードポイントからなる文字を禁止する' },
'eslint/no-new-native-nonconstructor': { zh: '禁止对全局非构造函数使用 new', ja: 'グローバルな非コンストラクタ関数への new を禁止する' },
'eslint/no-nonoctal-decimal-escape': { zh: '禁止字符串字面量中的 \\8 和 \\9 转义序列', ja: '文字列リテラル内の \\8 と \\9 のエスケープシーケンスを禁止する' },
'eslint/no-obj-calls': { zh: '禁止将全局对象属性作为函数调用', ja: 'グローバルオブジェクトのプロパティを関数として呼び出すことを禁止する' },
'eslint/no-octal': { zh: '禁止八进制字面量', ja: '8進数リテラルを禁止する' },
'eslint/no-prototype-builtins': { zh: '禁止直接在对象上调用某些 Object.prototype 方法', ja: 'オブジェクトで Object.prototype の一部メソッドを直接呼ぶことを禁止する' },
'eslint/no-redeclare': { zh: '禁止变量重新声明', ja: '変数の再宣言を禁止する' },
'eslint/no-regex-spaces': { zh: '禁止正则表达式字面量中的多个空格', ja: '正規表現リテラル内の複数スペースを禁止する' },
'eslint/no-self-assign': { zh: '禁止两侧完全相同的赋值', ja: '両辺が完全に同一の代入を禁止する' },
'eslint/no-setter-return': { zh: '禁止 setter 返回值', ja: 'setter からの戻り値を禁止する' },
'eslint/no-shadow-restricted-names': { zh: '禁止标识符遮蔽受限名称', ja: '予約名を遮蔽する識別子を禁止する' },
'eslint/no-sparse-arrays': { zh: '禁止稀疏数组', ja: '疎配列を禁止する' },
'eslint/no-this-before-super': { zh: '禁止在构造函数中调用 super() 之前使用 this/super', ja: 'コンストラクタで super() 呼び出し前の this/super 使用を禁止する' },
'eslint/no-undef': { zh: '禁止使用未声明的变量', ja: '未宣言の変数の使用を禁止する' },
'eslint/no-unexpected-multiline': { zh: '禁止令人困惑的多行表达式', ja: '紛らわしい複数行式を禁止する' },
'eslint/no-unreachable': { zh: '禁止 return/throw/continue/break 之后不可达的代码', ja: 'return/throw/continue/break 後の到達不能コードを禁止する' },
'eslint/no-unsafe-finally': { zh: '禁止 finally 块中的控制流语句', ja: 'finally ブロック内の制御フロー文を禁止する' },
'eslint/no-unsafe-negation': { zh: '禁止对关系运算符左操作数取反', ja: '関係演算子の左オペランドの否定を禁止する' },
'eslint/no-unsafe-optional-chaining': { zh: '禁止在 undefined 不允许的上下文中使用可选链', ja: 'undefined が許されない文脈でのオプショナルチェーンを禁止する' },
'eslint/no-unused-labels': { zh: '禁止未使用的标签', ja: '未使用のラベルを禁止する' },
'eslint/no-unused-private-class-members': { zh: '禁止未使用的私有类成员', ja: '未使用のプライベートクラスメンバーを禁止する' },
'eslint/no-unused-vars': { zh: '禁止未使用的变量', ja: '未使用の変数を禁止する' },
'eslint/no-useless-backreference': { zh: '禁止正则表达式中无用的反向引用', ja: '正規表現内の無用な後方参照を禁止する' },
'eslint/no-useless-catch': { zh: '禁止不必要的 catch 子句', ja: '不要な catch 句を禁止する' },
'eslint/no-useless-escape': { zh: '禁止不必要的转义字符', ja: '不要なエスケープ文字を禁止する' },
'eslint/no-with': { zh: '禁止 with 语句', ja: 'with 文を禁止する' },
'eslint/require-yield': { zh: '要求生成器函数包含 yield', ja: 'ジェネレータ関数に yield を含めることを要求する' },
'eslint/use-isnan': { zh: '检查 NaN 时要求调用 isNaN()', ja: 'NaN のチェック時に isNaN() の呼び出しを要求する' },
'eslint/valid-typeof': { zh: '强制 typeof 表达式与有效字符串比较', ja: 'typeof 式と有効な文字列の比較を強制する' },
'eslint/eqeqeq': { zh: '要求使用 === 和 !==', ja: '=== と !== の使用を要求する' },
'eslint/no-eq-null': { zh: '禁止无类型检查的 null 比较', ja: '型チェックなしの null 比較を禁止する' },
'eslint/no-self-compare': { zh: '禁止两侧相同的比较', ja: '両辺が同一の比較を禁止する' },
'eslint/no-await-in-loop': { zh: '禁止在循环内使用 await', ja: 'ループ内での await を禁止する' },
'eslint/no-promise-executor-return': { zh: '禁止 Promise 执行器返回值', ja: 'Promise エグゼキュータからの戻り値を禁止する' },
'eslint/no-shadow': { zh: '禁止变量声明遮蔽外层作用域中的变量', ja: '外側スコープの変数を遮蔽する宣言を禁止する' },
'eslint/no-unassigned-vars': { zh: '禁止只读但从未赋值的 let/var 变量', ja: '読み取られるが代入されない let/var 変数を禁止する' },
'eslint/no-useless-assignment': { zh: '禁止值未被使用的变量赋值', ja: '値が使われない変数への代入を禁止する' },
'eslint/block-scoped-var': { zh: '强制变量在其定义的作用域内使用', ja: '変数を定義されたスコープ内で使用することを強制する' },
'eslint/default-case': { zh: '要求 switch 语句有 default 子句', ja: 'switch 文に default 句を要求する' },
'eslint/default-case-last': { zh: '强制 switch 语句中 default 子句在最后', ja: 'switch 文で default 句を最後にすることを強制する' },
'eslint/no-unmodified-loop-condition': { zh: '禁止未修改的循环条件', ja: '変更されないループ条件を禁止する' },
'eslint/no-unreachable-loop': { zh: '禁止只允许一次迭代的循环体', ja: '一度しか反復できないループを禁止する' },
'eslint/no-eval': { zh: '禁止使用 eval()', ja: 'eval() の使用を禁止する' },
'eslint/no-extend-native': { zh: '禁止扩展原生类型', ja: 'ネイティブ型の拡張を禁止する' },
'eslint/no-var': { zh: '要求使用 let 或 const 替代 var', ja: 'var の代わりに let または const を要求する' },
'eslint/prefer-template': { zh: '要求使用模板字面量替代字符串拼接', ja: '文字列連結の代わりにテンプレートリテラルを要求する' },
'eslint/prefer-object-spread': { zh: '禁止 Object.assign,优先使用对象展开', ja: 'Object.assign を禁止しオブジェクト展開を推奨する' },
'eslint/prefer-rest-params': { zh: '要求使用剩余参数替代 arguments', ja: 'arguments の代わりに残余引数を要求する' },
'eslint/prefer-spread': { zh: '要求使用展开运算符替代 .apply()', ja: '.apply() の代わりにスプレッド演算子を要求する' },
'eslint/prefer-object-has-own': { zh: '禁止 Object.prototype.hasOwnProperty.call(),优先使用 Object.hasOwn()', ja: 'Object.prototype.hasOwnProperty.call() を禁止し Object.hasOwn() を推奨する' },
'eslint/no-useless-concat': { zh: '禁止不必要的字面量或模板字面量拼接', ja: '不要なリテラルやテンプレートリテラルの連結を禁止する' },
'eslint/no-useless-return': { zh: '禁止冗余的 return 语句', ja: '冗長な return 文を禁止する' },
'eslint/no-useless-computed-key': { zh: '禁止对象和类中不必要的计算属性键', ja: 'オブジェクトとクラス内の不要な算出プロパティキーを禁止する' },
'eslint/no-useless-rename': { zh: '禁止将导入、导出和解构赋值重命名为相同名称', ja: 'インポート・エクスポート・分割代入を同名に改名することを禁止する' },
'eslint/no-param-reassign': { zh: '禁止重新赋值函数参数', ja: '関数パラメータへの再代入を禁止する' },
'eslint/no-return-assign': { zh: '禁止在 return 语句中使用赋值运算符', ja: 'return 文での代入演算子を禁止する' },
'eslint/no-throw-literal': { zh: '禁止将字面量作为异常抛出', ja: 'リテラルを例外として投げることを禁止する' },
'eslint/camelcase': { zh: '强制使用 camelCase 命名规范', ja: 'camelCase 命名規則を強制する' },
'eslint/new-cap': { zh: '要求构造函数名以大写字母开头', ja: 'コンストラクタ名を大文字で始めることを要求する' },
'eslint/no-array-constructor': { zh: '禁止使用 Array 构造函数', ja: 'Array コンストラクタを禁止する' },
};
+153
View File
@@ -0,0 +1,153 @@
export default {
'pmd/AbstractClassWithoutAbstractMethod': { zh: '抽象类不包含任何抽象方法', ja: '抽象クラスに抽象メソッドが含まれていない' },
'pmd/AccessorClassGeneration': { zh: '避免从外部通过私有构造函数实例化', ja: '外部からプライベートコンストラクタでインスタンス化することを避ける' },
'pmd/AccessorMethodGeneration': { zh: '避免合成访问器方法', ja: '合成アクセッサメソッドを避ける' },
'pmd/ArrayIsStoredDirectly': { zh: '存储到构造函数/方法前应克隆对象', ja: 'コンストラクタやメソッドに格納する前にオブジェクトをクローンする' },
'pmd/AssertStatementInTest': { zh: '测试代码中不应使用断言语句', ja: 'テストコードで assert 文を使用すべきでない' },
'pmd/AvoidMessageDigestField': { zh: '不要将 MessageDigest 声明为字段(线程安全)', ja: 'MessageDigest をフィールドとして宣言しない(スレッド安全性)' },
'pmd/AvoidPrintStackTrace': { zh: '使用 logger 替代 printStackTrace()', ja: 'printStackTrace() の代わりにロガーを使用する' },
'pmd/AvoidReassigningCatchVariables': { zh: '不要重新赋值捕获的异常变量', ja: '捕捉した例外変数に再代入しない' },
'pmd/AvoidReassigningLoopVariables': { zh: '不要重新赋值循环控制变量', ja: 'ループ制御変数に再代入しない' },
'pmd/AvoidReassigningParameters': { zh: '不要重新赋值方法参数', ja: 'メソッドパラメータに再代入しない' },
'pmd/AvoidStringBufferField': { zh: '避免将 StringBuffer/StringBuilder 用作字段', ja: 'StringBuffer/StringBuilder をフィールドとして使うことを避ける' },
'pmd/AvoidUsingHardCodedIP': { zh: '外部化 IP 地址', ja: 'IP アドレスを外部化する' },
'pmd/CheckResultSet': { zh: '始终检查 ResultSet 导航方法的返回值', ja: 'ResultSet のナビゲーションメソッドの戻り値を常に確認する' },
'pmd/ConstantsInInterface': { zh: '避免在接口中定义常量', ja: 'インターフェースでの定数定義を避ける' },
'pmd/DefaultLabelNotLastInSwitch': { zh: 'switch 中 default 标签应放在最后', ja: 'switch では default ラベルを最後に置く' },
'pmd/DoubleBraceInitialization': { zh: '避免双花括号初始化', ja: '二重波括弧初期化を避ける' },
'pmd/EnumComparison': { zh: '使用 == 而非 equals() 比较枚举', ja: '列挙の比較には equals() ではなく == を使用する' },
'pmd/ExhaustiveSwitchHasDefault': { zh: '穷尽式 switch 不应有 default 子句', ja: '網羅的な switch に default を置くべきでない' },
'pmd/ForLoopCanBeForeach': { zh: '用 foreach 替代 for 循环', ja: 'for ループを foreach に置き換える' },
'pmd/ForLoopVariableCount': { zh: '限制 for 循环中的控制变量数量', ja: 'for ループ内の制御変数の数を制限する' },
'pmd/GuardLogStatement': { zh: '记录日志前检查日志级别', ja: 'ログ出力前にログレベルを確認する' },
'pmd/ImplicitFunctionalInterface': { zh: '用 @FunctionalInterface 注解函数式接口', ja: '関数型インターフェースに @FunctionalInterface を付ける' },
'pmd/JUnit4SuitesShouldUseSuiteAnnotation': { zh: '使用 @RunWith(Suite.class) 注解', ja: '@RunWith(Suite.class) アノテーションを使用する' },
'pmd/JUnitJupiterTestShouldBePackagePrivate': { zh: 'JUnit 5 测试应为包私有', ja: 'JUnit 5 のテストはパッケージプライベートにする' },
'pmd/JUnitUseExpected': { zh: '使用 @Test(expected) 注解', ja: '@Test(expected) アノテーションを使用する' },
'pmd/LabeledStatement': { zh: '避免带标签的语句', ja: 'ラベル付き文を避ける' },
'pmd/LiteralsFirstInComparisons': { zh: '字符串比较中将字面量放在前面', ja: '文字列比較ではリテラルを先頭に置く' },
'pmd/LooseCoupling': { zh: '使用接口而非实现类型', ja: '実装型ではなくインターフェースを使用する' },
'pmd/MethodReturnsInternalArray': { zh: '返回内部数组的副本', ja: '内部配列のコピーを返す' },
'pmd/MissingOverride': { zh: '添加 @Override 注解', ja: '@Override アノテーションを追加する' },
'pmd/NonExhaustiveSwitch': { zh: 'switch 应为穷尽式', ja: 'switch を網羅的にする' },
'pmd/OneDeclarationPerLine': { zh: '每行一个声明', ja: '1行に1つの宣言' },
'pmd/PreserveStackTrace': { zh: '重新抛出异常时保留堆栈跟踪', ja: '例外を再スローする際にスタックトレースを保持する' },
'pmd/PrimitiveWrapperInstantiation': { zh: '使用 valueOf() 而非 new Type()', ja: 'new Type() の代わりに valueOf() を使用する' },
'pmd/RelianceOnDefaultCharset': { zh: '显式指定字符集', ja: '文字セットを明示的に指定する' },
'pmd/ReplaceEnumerationWithIterator': { zh: '使用 Iterator 替代 Enumeration', ja: 'Enumeration の代わりに Iterator を使用する' },
'pmd/ReplaceHashtableWithMap': { zh: '使用 Map 替代 Hashtable', ja: 'Hashtable の代わりに Map を使用する' },
'pmd/ReplaceVectorWithList': { zh: '使用 List/ArrayList 替代 Vector', ja: 'Vector の代わりに List/ArrayList を使用する' },
'pmd/ReturnEmptyCollectionRatherThanNull': { zh: '返回空集合而非 null', ja: 'null ではなく空のコレクションを返す' },
'pmd/SimplifiableTestAssertion': { zh: '使用更具体的断言方法', ja: 'より具体的なアサーションメソッドを使用する' },
'pmd/SystemPrintln': { zh: '使用 logger 替代 System.out/err', ja: 'System.out/err の代わりにロガーを使用する' },
'pmd/UnitTestAssertionsShouldIncludeMessage': { zh: '断言中包含消息', ja: 'アサーションにメッセージを含める' },
'pmd/UnitTestContainsTooManyAsserts': { zh: '限制每个测试的断言数量', ja: 'テストごとのアサーション数を制限する' },
'pmd/UnitTestShouldIncludeAssert': { zh: '测试应包含断言', ja: 'テストにアサーションを含めるべきである' },
'pmd/UnitTestShouldUseAfterAnnotation': { zh: '使用 @After/@AfterEach 注解', ja: '@After/@AfterEach アノテーションを使用する' },
'pmd/UnitTestShouldUseBeforeAnnotation': { zh: '使用 @Before/@BeforeEach 注解', ja: '@Before/@BeforeEach アノテーションを使用する' },
'pmd/UnitTestShouldUseTestAnnotation': { zh: '使用 @Test 注解', ja: '@Test アノテーションを使用する' },
'pmd/UnnecessaryVarargsArrayCreation': { zh: '不要为可变参数创建显式数组', ja: '可変長引数用に明示的な配列を作成しない' },
'pmd/UnnecessaryWarningSuppression': { zh: '移除未使用的 PMD 抑制', ja: '未使用の PMD 抑制を削除する' },
'pmd/UnsynchronizedStaticFormatter': { zh: '静态 formatter 应同步', ja: '静的フォーマッタは同期化すべきである' },
'pmd/UnusedAssignment': { zh: '移除未使用的赋值', ja: '未使用の代入を削除する' },
'pmd/UnusedFormalParameter': { zh: '移除未使用的参数', ja: '未使用のパラメータを削除する' },
'pmd/UnusedLabel': { zh: '移除未使用的标签', ja: '未使用のラベルを削除する' },
'pmd/UnusedLocalVariable': { zh: '移除未使用的局部变量', ja: '未使用のローカル変数を削除する' },
'pmd/UnusedPrivateField': { zh: '移除未使用的私有字段', ja: '未使用のプライベートフィールドを削除する' },
'pmd/UnusedPrivateMethod': { zh: '移除未使用的私有方法', ja: '未使用のプライベートメソッドを削除する' },
'pmd/UseCollectionIsEmpty': { zh: '使用 isEmpty() 替代 size()==0', ja: 'size()==0 の代わりに isEmpty() を使用する' },
'pmd/UseEnumCollections': { zh: '使用 EnumSet/EnumMap 替代 HashSet/HashMap', ja: 'HashSet/HashMap の代わりに EnumSet/EnumMap を使用する' },
'pmd/UseStandardCharsets': { zh: '使用 StandardCharsets 常量', ja: 'StandardCharsets 定数を使用する' },
'pmd/UseTryWithResources': { zh: '使用 try-with-resources', ja: 'try-with-resources を使用する' },
'pmd/UseUtilityClass': { zh: '工具类应有私有构造函数', ja: 'ユーティリティクラスはプライベートコンストラクタを持つべきである' },
'pmd/UseVarargs': { zh: '使用可变参数替代数组参数', ja: '配列パラメータの代わりに可変長引数を使用する' },
'pmd/VariableCanBeInlined': { zh: '变量可以内联', ja: '変数をインライン化できる' },
'pmd/WhileLoopWithLiteralBoolean': { zh: '简化带字面量布尔值的 while 循环', ja: 'リテラルブール値を持つ while ループを簡略化する' },
'pmd/AtLeastOneConstructor': { zh: '每个类都应有一个构造函数', ja: '各クラスにコンストラクタを1つ持つべきである' },
'pmd/AvoidDollarSigns': { zh: '避免在名称中使用 $', ja: '名前に $ を使用することを避ける' },
'pmd/AvoidProtectedFieldInFinalClass': { zh: 'final 类中不要使用 protected 字段', ja: 'final クラスで protected フィールドを使わない' },
'pmd/AvoidProtectedMethodInFinalClassNotExtending': { zh: '非继承的 final 类中不要使用 protected 方法', ja: '継承しない final クラスで protected メソッドを使わない' },
'pmd/AvoidUsingNativeCode': { zh: '避免 JNI 调用', ja: 'JNI 呼び出しを避ける' },
'pmd/BooleanGetMethodName': { zh: '布尔 getter 应命名为 isX()', ja: 'ブールゲッターは isX() と命名する' },
'pmd/CallSuperInConstructor': { zh: '在构造函数中调用 super()', ja: 'コンストラクタで super() を呼び出す' },
'pmd/ClassNamingConventions': { zh: 'PascalCase 命名', ja: 'PascalCase 命名' },
'pmd/CommentDefaultAccessModifier': { zh: '注释默认访问修饰符', ja: 'デフォルトアクセス修飾子をコメントする' },
'pmd/ConfusingTernary': { zh: '避免在带 else 的 if 中使用取反', ja: 'else 付き if での否定を避ける' },
'pmd/ControlStatementBraces': { zh: '控制语句要求花括号', ja: '制御文に波括弧を要求する' },
'pmd/EmptyControlStatement': { zh: '报告空的控制语句', ja: '空の制御文を報告する' },
'pmd/EmptyMethodInAbstractClassShouldBeAbstract': { zh: '抽象类中的空方法应为抽象方法', ja: '抽象クラスの空メソッドは抽象にすべきである' },
'pmd/ExtendsObject': { zh: '无需显式继承 Object', ja: 'Object を明示的に継承する必要はない' },
'pmd/FieldDeclarationsShouldBeAtStartOfClass': { zh: '字段放在类的顶部', ja: 'フィールドをクラスの先頭に置く' },
'pmd/FieldNamingConventions': { zh: '可配置的字段命名规范', ja: '設定可能なフィールド命名規則' },
'pmd/FinalParameterInAbstractMethod': { zh: '抽象方法中的 final 参数无用', ja: '抽象メソッドの final パラメータは無意味である' },
'pmd/ForLoopShouldBeWhileLoop': { zh: '将 for 循环简化为 while', ja: 'for ループを while に簡略化する' },
'pmd/FormalParameterNamingConventions': { zh: '参数命名规范', ja: 'パラメータ命名規則' },
'pmd/IdenticalCatchBranches': { zh: '合并相同的 catch 分支', ja: '同一の catch ブランチを統合する' },
'pmd/LambdaCanBeMethodReference': { zh: '用方法引用替代 lambda', ja: 'ラムダをメソッド参照に置き換える' },
'pmd/LinguisticNaming': { zh: '方法名与返回类型一致性', ja: 'メソッド名と戻り値型の整合性' },
'pmd/LocalHomeNamingConvention': { zh: 'EJB LocalHome 后缀', ja: 'EJB LocalHome サフィックス' },
'pmd/LocalInterfaceSessionNamingConvention': { zh: 'EJB Local 后缀', ja: 'EJB Local サフィックス' },
'pmd/LocalVariableCouldBeFinal': { zh: '尽可能将局部变量声明为 final', ja: '可能な限りローカル変数を final で宣言する' },
'pmd/LocalVariableNamingConventions': { zh: '变量命名规范', ja: '変数命名規則' },
'pmd/LongVariable': { zh: '避免过长的变量名(超过17个字符)', ja: '過度に長い変数名(17文字超)を避ける' },
'pmd/MDBAndSessionBeanNamingConvention': { zh: 'EJB Bean 后缀', ja: 'EJB Bean サフィックス' },
'pmd/MethodArgumentCouldBeFinal': { zh: '尽可能将参数声明为 final', ja: '可能な限りパラメータを final で宣言する' },
'pmd/MethodNamingConventions': { zh: '方法命名规范', ja: 'メソッド命名規則' },
'pmd/ModifierOrder': { zh: '强制 JLS 修饰符顺序', ja: 'JLS 修飾子の順序を強制する' },
'pmd/NoPackage': { zh: '所有类型必须属于命名包', ja: 'すべての型は名前付きパッケージに属すべきである' },
'pmd/OnlyOneReturn': { zh: '每个方法只有一个出口', ja: 'メソッドに出口を1つだけ持たせる' },
'pmd/PackageCase': { zh: '包名使用小写', ja: 'パッケージ名は小文字にする' },
'pmd/PrematureDeclaration': { zh: '变量声明靠近使用处', ja: '変数を使用箇所の近くで宣言する' },
'pmd/UselessParentheses': { zh: '移除不必要的括号', ja: '不要な括弧を削除する' },
'pmd/UselessQualifiedThis': { zh: '移除不必要的限定 this', ja: '不要な限定 this を削除する' },
'pmd/UnnecessaryAnnotationValueElement': { zh: '移除不必要的注解值元素', ja: '不要なアノテーション値要素を削除する' },
'pmd/UnnecessaryBoxing': { zh: '避免不必要的装箱', ja: '不要なボクシングを避ける' },
'pmd/UnnecessaryCast': { zh: '移除不必要的强制转换', ja: '不要なキャストを削除する' },
'pmd/UnnecessaryConstructor': { zh: '移除不必要的构造函数', ja: '不要なコンストラクタを削除する' },
'pmd/UnnecessaryFullyQualifiedName': { zh: '移除不必要的全限定名', ja: '不要な完全修飾名を削除する' },
'pmd/UnnecessaryImport': { zh: '移除不必要的导入', ja: '不要なインポートを削除する' },
'pmd/UnnecessaryModifier': { zh: '移除不必要的修饰符', ja: '不要な修飾子を削除する' },
'pmd/UnnecessaryReturn': { zh: '移除不必要的 return', ja: '不要な return を削除する' },
'pmd/UnnecessarySemicolon': { zh: '移除不必要的分号', ja: '不要なセミコロンを削除する' },
'pmd/UnnecessaryUnboxing': { zh: '避免不必要的拆箱', ja: '不要なアンボクシングを避ける' },
'pmd/UpperLowerCaseNamingConventions': { zh: '大小写命名规范', ja: '大文字・小文字の命名規則' },
'pmd/UseShortArrayInitializer': { zh: '使用简短的数组初始化器', ja: '簡潔な配列初期化子を使用する' },
'pmd/AbstractClassWithoutAnyMethod': { zh: '没有任何方法的抽象类应使用私有构造函数', ja: 'メソッドのない抽象クラスはプライベートコンストラクタを使うべきである' },
'pmd/AvoidDeeplyNestedIfStmts': { zh: '避免深度嵌套的 if 语句', ja: '深くネストした if 文を避ける' },
'pmd/AvoidRethrowingException': { zh: '避免捕获后重新抛出', ja: 'catch-and-rethrow を避ける' },
'pmd/AvoidThrowingNewInstanceOfSameException': { zh: '避免包装相同异常类型', ja: '同じ例外型のラップを避ける' },
'pmd/AvoidThrowingNullPointerException': { zh: '不要手动抛出 NPE', ja: 'NPE を手動で投げない' },
'pmd/AvoidThrowingRawExceptionTypes': { zh: '不要抛出原始 Exception/RuntimeException/Throwable/Error', ja: '生の Exception/RuntimeException/Throwable/Error を投げない' },
'pmd/AvoidUncheckedExceptionsInSignatures': { zh: '不要在 throws 中声明非受检异常', ja: 'throws で非チェック例外を宣言しない' },
'pmd/ClassWithOnlyPrivateConstructorsShouldBeFinal': { zh: '只有私有构造函数的类应为 final', ja: 'プライベートコンストラクタのみのクラスは final にする' },
'pmd/CognitiveComplexity': { zh: '高认知复杂度的方法', ja: '認知複雑度の高いメソッド' },
'pmd/CollapsibleIfStatements': { zh: '合并嵌套的 if 语句', ja: 'ネストした if 文を統合する' },
'pmd/CouplingBetweenObjects': { zh: '高耦合阈值', ja: '高い結合度の閾値' },
'pmd/CyclomaticComplexity': { zh: '高圈复杂度', ja: '高い循環的複雑度' },
'pmd/DataClass': { zh: '疑似数据类', ja: '疑わしいデータクラス' },
'pmd/DoNotExtendJavaLangError': { zh: '不要继承 Error', ja: 'Error を継承しない' },
'pmd/ExceptionAsFlowControl': { zh: '不要使用异常控制流程', ja: '制御フローに例外を使わない' },
'pmd/ExcessiveImports': { zh: '导入过多', ja: 'インポートが多すぎる' },
'pmd/ExcessiveParameterList': { zh: '参数过多', ja: 'パラメータが多すぎる' },
'pmd/ExcessivePublicCount': { zh: '公共方法/属性过多', ja: 'public メソッド/属性が多すぎる' },
'pmd/FinalFieldCouldBeStatic': { zh: '编译时常量 final 字段应为 static', ja: 'コンパイル時定数の final フィールドは static にできる' },
'pmd/GodClass': { zh: '上帝类检测', ja: 'God クラスの検出' },
'pmd/ImmutableField': { zh: '字段可以声明为 final', ja: 'フィールドは final にできる' },
'pmd/InvalidJavaBean': { zh: 'Bean 不符合 JavaBeans 规范', ja: 'Bean が JavaBeans 仕様に従っていない' },
'pmd/LawOfDemeter': { zh: '潜在的迪米特法则违规', ja: '潜在的な LoD 違反' },
'pmd/LogicInversion': { zh: '使用相反的运算符替代 !', ja: '! の代わりに反対の演算子を使用する' },
'pmd/LoosePackageCoupling': { zh: '避免使用包层次之外的类', ja: 'パッケージ階層外のクラスの使用を避ける' },
'pmd/MutableStaticState': { zh: '非私有非 final 的静态字段', ja: '非 private かつ非 final の静的フィールド' },
'pmd/NcssCount': { zh: '非注释源码语句度量', ja: '非コメントソース文のメトリクス' },
'pmd/NPathComplexity': { zh: 'NPath 复杂度阈值', ja: 'NPath 複雑度の閾値' },
'pmd/PublicMemberInNonPublicType': { zh: '非公共类型中的公共成员', ja: '非 public 型内の public メンバー' },
'pmd/SignatureDeclareThrowsException': { zh: '不要声明 throws Exception', ja: 'throws Exception を宣言しない' },
'pmd/SimplifiedTernary': { zh: '用布尔字面量简化三元表达式', ja: 'ブールリテラルで三項演算子を簡略化する' },
'pmd/SimplifyBooleanExpressions': { zh: '移除不必要的布尔比较', ja: '不要なブール比較を削除する' },
'pmd/SimplifyBooleanReturns': { zh: '简化布尔返回', ja: 'ブールの戻り値を簡略化する' },
'pmd/SimplifyConditional': { zh: '简化条件表达式', ja: '条件式を簡略化する' },
'pmd/SingularField': { zh: '字段可能应为局部变量', ja: 'フィールドはローカル変数にできる' },
'pmd/TooManyFields': { zh: '字段过多', ja: 'フィールドが多すぎる' },
'pmd/TooManyMethods': { zh: '方法过多', ja: 'メソッドが多すぎる' },
'pmd/UselessOverridingMethod': { zh: '无意义的重写方法', ja: '無意味なオーバーライドメソッド' },
};
+124
View File
@@ -0,0 +1,124 @@
export default {
'pmd/AssertEqualsArgumentOrder': { zh: 'assertEquals 的 expected/actual 参数顺序颠倒', ja: 'assertEquals の expected/actual 引数が逆' },
'pmd/AssignmentInOperand': { zh: '避免在操作数中赋值', ja: 'オペランド内での代入を避ける' },
'pmd/AssignmentToNonFinalStatic': { zh: '构造函数中对非 final 静态字段的不安全赋值', ja: 'コンストラクタ内の非 final 静的フィールドへの安全でない代入' },
'pmd/AvoidAccessibilityAlteration': { zh: '不要使用 setAccessible(true)', ja: 'setAccessible(true) を使用しない' },
'pmd/AvoidAssertAsIdentifier': { zh: 'assert 是保留字(Java <1.4', ja: 'assert は予約語である(Java <1.4' },
'pmd/AvoidBranchingStatementAsLastInLoop': { zh: '循环体最后的跳转语句', ja: 'ループの最後の分岐文' },
'pmd/AvoidCallingFinalize': { zh: '不要显式调用 finalize()', ja: 'finalize() を明示的に呼ばない' },
'pmd/AvoidCatchingGenericException': { zh: '不要捕获泛化异常', ja: '汎用例外を捕捉しない' },
'pmd/AvoidDecimalLiteralsInBigDecimalConstructor': { zh: 'BigDecimal 使用 String 构造函数', ja: 'BigDecimal には String コンストラクタを使用する' },
'pmd/AvoidDuplicateLiterals': { zh: '避免重复的 String 字面量', ja: '重複する文字列リテラルを避ける' },
'pmd/AvoidEnumAsIdentifier': { zh: 'enum 是保留字(Java <1.5', ja: 'enum は予約語である(Java <1.5' },
'pmd/AvoidFieldNameMatchingMethodName': { zh: '字段名与方法名相同', ja: 'フィールド名とメソッド名が一致する' },
'pmd/AvoidFieldNameMatchingTypeName': { zh: '字段名与类型名相同', ja: 'フィールド名と型名が一致する' },
'pmd/AvoidInstanceofChecksInCatchClause': { zh: '使用单独的 catch 子句', ja: '個別の catch 句を使用する' },
'pmd/AvoidLiteralsInIfCondition': { zh: '避免 if 条件中的魔术数字', ja: 'if 条件内のマジックナンバーを避ける' },
'pmd/AvoidMultipleUnaryOperators': { zh: '避免多个一元运算符', ja: '複数の単項演算子を避ける' },
'pmd/AvoidSynchronizedStatement': { zh: '避免 synchronized 语句', ja: 'synchronized 文を避ける' },
'pmd/AvoidSynchronizedAtMethodLevel': { zh: '避免在方法级别使用 synchronized', ja: 'メソッドレベルでの synchronized を避ける' },
'pmd/AvoidThreadGroup': { zh: '避免使用 ThreadGroup', ja: 'ThreadGroup の使用を避ける' },
'pmd/AvoidUsingOctalValues': { zh: '避免八进制字面量', ja: '8進数リテラルを避ける' },
'pmd/AvoidUsingVolatile': { zh: '避免 volatile 关键字', ja: 'volatile キーワードを避ける' },
'pmd/BrokenNullCheck': { zh: '错误的 null 检查(|| 与 &&', ja: '壊れた null チェック(|| vs &&' },
'pmd/CallSuperFirst': { zh: 'super 应首先调用', ja: 'super を最初に呼ぶべきである' },
'pmd/CallSuperLast': { zh: 'super 应最后调用', ja: 'super を最後に呼ぶべきである' },
'pmd/CheckSkipResult': { zh: '检查 skip() 的返回值', ja: 'skip() の戻り値を確認する' },
'pmd/ClassCastExceptionWithToArray': { zh: 'Collection.toArray() 的 ClassCastException', ja: 'Collection.toArray() の ClassCastException' },
'pmd/CloneMethodMustBePublic': { zh: '实现 Cloneable 时 clone() 必须是 public', ja: 'Cloneable の場合 clone() は public でなければならない' },
'pmd/CloneMethodMustImplementCloneable': { zh: '只有实现 Cloneable 时才有 clone()', ja: 'Cloneable の場合のみ clone() を持つ' },
'pmd/CloneMethodReturnTypeMustMatchClassName': { zh: 'clone() 协变返回类型', ja: 'clone() の共変戻り値型' },
'pmd/CloseResource': { zh: '确保资源被关闭', ja: 'リソースが閉じられることを保証する' },
'pmd/CollectionTypeMismatch': { zh: '集合方法中的类型不匹配', ja: 'コレクションメソッド内の型不一致' },
'pmd/CompareObjectsWithEquals': { zh: '对象比较使用 equals() 而非 ==', ja: 'オブジェクトの比較に == ではなく equals() を使用する' },
'pmd/ComparisonWithNaN': { zh: 'NaN 比较总是返回 false', ja: 'NaN との比較は常に false を返す' },
'pmd/ConfusingArgumentToVarargsMethod': { zh: '澄清可变参数意图', ja: '可変長引数の意図を明確にする' },
'pmd/ConstructorCallsOverridableMethod': { zh: '构造函数调用可重写方法', ja: 'コンストラクタがオーバーライド可能なメソッドを呼ぶ' },
'pmd/DataflowAnomalyAnalysis': { zh: '数据流异常', ja: 'データフロー異常' },
'pmd/DoNotCallGarbageCollectionExplicitly': { zh: '不要显式调用 System.gc()', ja: 'System.gc() を明示的に呼ばない' },
'pmd/DoNotCallSystemExit': { zh: '不要调用 System.exit()', ja: 'System.exit() を呼ばない' },
'pmd/DoNotHardCodeSDCard': { zh: '不要硬编码 /sdcard 路径', ja: '/sdcard パスをハードコードしない' },
'pmd/DoNotThrowExceptionInFinally': { zh: '不要在 finally 中抛出异常', ja: 'finally で例外を投げない' },
'pmd/DoNotUseThreads': { zh: '不要使用线程', ja: 'スレッドを使用しない' },
'pmd/DontCallThreadRun': { zh: '不要调用 Thread.run()', ja: 'Thread.run() を呼ばない' },
'pmd/DoubleCheckedLocking': { zh: '双重检查锁定不是线程安全的', ja: '二重チェックロッキングはスレッド安全でない' },
'pmd/EmptyCatchBlock': { zh: '空 catch 块', ja: '空の catch ブロック' },
'pmd/EqualsNull': { zh: '与 null 进行相等比较', ja: 'null との等価比較' },
'pmd/FinallyBlockDoesNothing': { zh: 'finally 块什么也不做', ja: 'finally ブロックが何もしない' },
'pmd/IdempotentOperations': { zh: '幂等操作', ja: '冪等な操作' },
'pmd/ImplicitSwitchFallThrough': { zh: '隐式 switch fall through', ja: '暗黙の switch フォールスルー' },
'pmd/ImportFromSamePackage': { zh: '从同包导入', ja: '同一パッケージからのインポート' },
'pmd/InstantiationToGetClass': { zh: '仅为获取类而实例化', ja: 'クラス取得のためだけのインスタンス化' },
'pmd/InvalidLogMessageFormat': { zh: '无效的 SLF4J 消息格式', ja: '無効な SLF4J メッセージ形式' },
'pmd/JUnitSpelling': { zh: 'JUnit 方法拼写', ja: 'JUnit メソッドの綴り' },
'pmd/JUnitStaticSuite': { zh: 'JUnit 静态 suite 方法', ja: 'JUnit の静的 suite メソッド' },
'pmd/JumbledIncrementer': { zh: '混乱的增量器', ja: '入り混じったインクリメンタ' },
'pmd/LoggerIsNotStaticFinal': { zh: 'Logger 不是 static final', ja: 'Logger が static final でない' },
'pmd/MethodWithSameNameAsEnclosingClass': { zh: '方法与包围类同名', ja: 'メソッドが囲むクラスと同名' },
'pmd/MisplacedNullCheck': { zh: '位置错误的 null 检查', ja: '誤った位置の null チェック' },
'pmd/MissingBreakInSwitch': { zh: 'switch 中缺少 break', ja: 'switch 内の break 欠落' },
'pmd/MissingSerialVersionUID': { zh: '缺少 serialVersionUID', ja: 'serialVersionUID の欠落' },
'pmd/MissingStaticMethodInNonInstantiatableClass': { zh: '不可实例化类缺少静态方法', ja: 'インスタンス化できないクラスに静的メソッドがない' },
'pmd/MoreThanOneLogger': { zh: '多于一个 logger', ja: 'logger が複数ある' },
'pmd/NonCaseLabelInSwitch': { zh: 'switch 中的非 case 标签', ja: 'switch 内の非 case ラベル' },
'pmd/NonStaticInitializer': { zh: '非静态初始化器', ja: '非静的初期化子' },
'pmd/NonThreadSafeSingleton': { zh: '单例不是线程安全的', ja: 'シングルトンがスレッド安全でない' },
'pmd/NullAssignment': { zh: 'null 赋值', ja: 'null 代入' },
'pmd/NumberConstructor': { zh: 'Number 构造函数(已废弃)', ja: 'Number コンストラクタ(非推奨)' },
'pmd/ObjectFinalize': { zh: 'Object finalize 问题', ja: 'Object finalize の問題' },
'pmd/OperationWithCloning': { zh: '克隆操作', ja: 'クローン操作' },
'pmd/OverrideBothEqualsAndHashcode': { zh: '同时重写 equals() 和 hashCode()', ja: 'equals() と hashCode() の両方をオーバーライドする' },
'pmd/OverridingThreadRun': { zh: '不要重写 Thread.run()', ja: 'Thread.run() をオーバーライドしない' },
'pmd/PackageDeclaration': { zh: '包声明', ja: 'パッケージ宣言' },
'pmd/ProperCloneImplementation': { zh: '正确的 clone 实现', ja: '適切な clone 実装' },
'pmd/ProperLogger': { zh: '正确的 logger', ja: '適切な logger' },
'pmd/ReturnFromFinallyBlock': { zh: '从 finally 返回', ja: 'finally からの return' },
'pmd/SimpleDateFormatNeedsLocale': { zh: 'SimpleDateFormat 需要 locale', ja: 'SimpleDateFormat に locale が必要' },
'pmd/SingleMethodSingleton': { zh: '单例模式问题', ja: 'シングルトンパターンの問題' },
'pmd/SingletonClassReturningNewInstance': { zh: '单例返回新实例', ja: 'シングルトンが新しいインスタンスを返す' },
'pmd/StaticEJBFieldShouldBeFinal': { zh: '静态 EJB 字段应为 final', ja: '静的 EJB フィールドは final にする' },
'pmd/StringBufferInstantiationWithChar': { zh: 'StringBuffer 带 char 实例化', ja: 'char を伴う StringBuffer インスタンス化' },
'pmd/SuspiciousConstantFieldName': { zh: '常量字段命名', ja: '定数フィールドの命名' },
'pmd/SuspiciousEqualsMethodName': { zh: 'equals() 方法签名', ja: 'equals() メソッドのシグネチャ' },
'pmd/SuspiciousHashcodeMethodName': { zh: 'hashCode() 方法签名', ja: 'hashCode() メソッドのシグネチャ' },
'pmd/SuspiciousOctalEscape': { zh: '可疑的八进制转义', ja: '疑わしい8進エスケープ' },
'pmd/TestClassWithoutTestCases': { zh: '没有测试用例的测试类', ja: 'テストケースのないテストクラス' },
'pmd/UnconditionalIfStatement': { zh: '无条件 if 语句', ja: '無条件の if 文' },
'pmd/UnnecessaryBooleanAssertion': { zh: '不必要的布尔断言', ja: '不要なブールアサーション' },
'pmd/UnnecessaryCaseChange': { zh: '不必要的大小写转换', ja: '不要なケース変換' },
'pmd/UnnecessaryConversionTemporal': { zh: '不必要的时间转换', ja: '不要な時間変換' },
'pmd/UnusedNullCheckInEquals': { zh: 'equals 中未使用的 null 检查', ja: 'equals 内の未使用 null チェック' },
'pmd/UseConcurrentHashMap': { zh: '并发访问使用 ConcurrentHashMap', ja: '並行アクセスに ConcurrentHashMap を使用する' },
'pmd/UseCorrectExceptionLogging': { zh: '正确的异常日志', ja: '適切な例外ログ' },
'pmd/UseDiamondOperator': { zh: '使用菱形运算符 <>', ja: 'ダイヤモンド演算子 <> を使用する' },
'pmd/UseEqualsToCompareStrings': { zh: '字符串比较使用 equals()', ja: '文字列の比較に equals() を使用する' },
'pmd/UseLocaleWithCaseConversions': { zh: '大小写转换使用 locale', ja: 'ケース変換に locale を使用する' },
'pmd/UseNotifyAllInsteadOfNotify': { zh: '使用 notifyAll() 替代 notify()', ja: 'notify() の代わりに notifyAll() を使用する' },
'pmd/UseProperClassLoader': { zh: '使用正确的类加载器', ja: '適切なクラスローダーを使用する' },
'pmd/AddEmptyString': { zh: '不要添加空字符串', ja: '空文字列を追加しない' },
'pmd/AppendCharacterWithChar': { zh: 'StringBuffer 中追加 char 而非 String', ja: 'StringBuffer には String ではなく char を追加する' },
'pmd/AvoidArrayLoops': { zh: '使用 Arrays.copyOf 或 System.arraycopy', ja: 'Arrays.copyOf または System.arraycopy を使用する' },
'pmd/AvoidCalendarDateCreation': { zh: '获取当前时间避免使用 Calendar', ja: '現在時刻に Calendar を使用しない' },
'pmd/AvoidFileStream': { zh: '避免 FileInputStream/FileOutputStream/FileReader/FileWriter', ja: 'FileInputStream/FileOutputStream/FileReader/FileWriter を避ける' },
'pmd/AvoidInstantiatingObjectsInLoops': { zh: '不要在循环中实例化对象', ja: 'ループ内でオブジェクトをインスタンス化しない' },
'pmd/BigIntegerInstantiation': { zh: '使用 BigInteger.ZERO/ONE/TEN', ja: 'BigInteger.ZERO/ONE/TEN を使用する' },
'pmd/ConsecutiveAppendsShouldReuse': { zh: '链式调用 StringBuilder.append', ja: 'StringBuilder.append をチェーンで呼ぶ' },
'pmd/ConsecutiveLiteralAppends': { zh: '合并字面量追加', ja: 'リテラルの追加を統合する' },
'pmd/InefficientEmptyStringCheck': { zh: '使用 isBlank() 替代 trim().isEmpty()', ja: 'trim().isEmpty() の代わりに isBlank() を使用する' },
'pmd/InefficientStringBuffering': { zh: '避免在 StringBuffer 构造函数中拼接', ja: 'StringBuffer コンストラクタ内での連結を避ける' },
'pmd/InsufficientStringBufferDeclaration': { zh: '预先指定 StringBuilder 容量', ja: 'StringBuilder の容量を事前指定する' },
'pmd/OptimizableToArrayCall': { zh: '使用 new Foo[0] 替代 new Foo[size]', ja: 'new Foo[size] の代わりに new Foo[0] を使用する' },
'pmd/RedundantFieldInitializer': { zh: '移除冗余的字段初始化器', ja: '冗長なフィールド初期化子を削除する' },
'pmd/StringInstantiation': { zh: '避免 new String()', ja: 'new String() を避ける' },
'pmd/StringToString': { zh: '避免对 String 调用 toString()', ja: 'String への toString() を避ける' },
'pmd/TooFewBranchesForSwitch': { zh: '少于3个分支的 switch', ja: '3未満のブランチの switch' },
'pmd/UseArrayListInsteadOfVector': { zh: '使用 ArrayList 替代 Vector', ja: 'Vector の代わりに ArrayList を使用する' },
'pmd/UseArraysAsList': { zh: '使用 Arrays.asList() 替代循环', ja: 'ループの代わりに Arrays.asList() を使用する' },
'pmd/UseIndexOfChar': { zh: '使用 indexOf(char) 而非 indexOf(String)', ja: 'indexOf(String) ではなく indexOf(char) を使用する' },
'pmd/UseIOStreamsWithApacheCommonsFileItem': { zh: '使用 getInputStream() 而非 get()', ja: 'get() ではなく getInputStream() を使用する' },
'pmd/UselessStringValueOf': { zh: '不要用 String.valueOf() 包裹', ja: 'String.valueOf() でラップしない' },
'pmd/UseStringBufferForStringAppends': { zh: '使用 StringBuilder 进行拼接', ja: '文字列連結に StringBuilder を使用する' },
'pmd/UseStringBufferLength': { zh: '使用 length() 替代 toString().equals("")', ja: 'toString().equals("") の代わりに length() を使用する' },
'pmd/HardCodedCryptoKey': { zh: '不要硬编码加密密钥', ja: '暗号鍵をハードコードしない' },
'pmd/InsecureCryptoIv': { zh: '不要硬编码初始化向量', ja: '初期化ベクタをハードコードしない' },
};
+12
View File
@@ -0,0 +1,12 @@
export default {
'pmd-jsp/DontNestJsfInJstlIteration': { zh: '不要在 JSTL 迭代内嵌套 JSF 组件', ja: 'JSTL 反復内に JSF コンポーネントをネストしない' },
'pmd-jsp/NoClassAttribute': { zh: '使用 styleclass 而非 class 属性', ja: 'class 属性ではなく styleclass を使用する' },
'pmd-jsp/NoHtmlComments': { zh: '使用 JSP 注释而非 HTML 注释', ja: 'HTML コメントではなく JSP コメントを使用する' },
'pmd-jsp/NoJspForward': { zh: '不要在 JSP 内转发', ja: 'JSP 内からフォワードしない' },
'pmd-jsp/DuplicateJspImports': { zh: '避免 JSP 中重复导入', ja: 'JSP 内の重複インポートを避ける' },
'pmd-jsp/NoInlineScript': { zh: '将 HTML 脚本内容外部化', ja: 'HTML スクリプト内容を外部化する' },
'pmd-jsp/NoInlineStyleInformation': { zh: '将样式放入 CSS 文件', ja: 'スタイルを CSS ファイルに置く' },
'pmd-jsp/NoLongScripts': { zh: '避免 JSP 中的长脚本', ja: 'JSP 内の長いスクリプトを避ける' },
'pmd-jsp/NoScriptlets': { zh: '避免 JSP 中的 scriptlet', ja: 'JSP 内のスクリプトレットを避ける' },
'pmd-jsp/JspEncoding': { zh: 'JSP 文件应使用 UTF-8 编码', ja: 'JSP ファイルは UTF-8 エンコーディングを使用すべきである' },
};
+77
View File
@@ -0,0 +1,77 @@
export default {
'sqlfluff/AL01': { zh: '表的隐式/显式别名', ja: 'テーブルの暗黙的/明示的エイリアス' },
'sqlfluff/AL02': { zh: '列的隐式/显式别名', ja: '列の暗黙的/明示的エイリアス' },
'sqlfluff/AL03': { zh: '无别名的列表达式', ja: 'エイリアスなしの列式' },
'sqlfluff/AL04': { zh: '表别名在每个子句中应唯一', ja: '表エイリアスは各句内で一意にすべきである' },
'sqlfluff/AL05': { zh: '未使用的表不应加别名', ja: '未使用のテーブルにエイリアスを付けない' },
'sqlfluff/AL06': { zh: '强制表别名长度', ja: '表エイリアスの長さを強制する' },
'sqlfluff/AL07': { zh: '避免表别名', ja: '表エイリアスを避ける' },
'sqlfluff/AL08': { zh: '列别名在每个子句中应唯一', ja: '列エイリアスは各句内で一意にすべきである' },
'sqlfluff/AL09': { zh: '列别名不应与自身相同', ja: '列エイリアスが自分自身と同じにならないようにする' },
'sqlfluff/AL10': { zh: '派生表必须使用别名', ja: '派生テーブルにはエイリアスが必要である' },
'sqlfluff/AM01': { zh: 'DISTINCT 与 GROUP BY 的歧义用法', ja: 'DISTINCT と GROUP BY の曖昧な使用' },
'sqlfluff/AM02': { zh: '优先使用 UNION DISTINCT/ALL 而非仅 UNION', ja: '単なる UNION より UNION DISTINCT/ALL を優先する' },
'sqlfluff/AM03': { zh: '歧义的排序方向', ja: '曖昧なソート方向' },
'sqlfluff/AM04': { zh: '查询产生未知数量的结果列', ja: 'クエリが未知数の結果列を生成する' },
'sqlfluff/AM05': { zh: '连接子句应完全限定', ja: '結合句は完全修飾すべきである' },
'sqlfluff/AM06': { zh: 'GROUP BY/ORDER BY 中列引用不一致', ja: 'GROUP BY/ORDER BY 内の列参照の不整合' },
'sqlfluff/AM07': { zh: '集合查询中的子查询产生不同数量的列', ja: '集合クエリ内のサブクエリが異なる数の列を生成する' },
'sqlfluff/AM08': { zh: '检测到隐式交叉连接', ja: '暗黙のクロスジョインを検出' },
'sqlfluff/AM09': { zh: '无 ORDER BY 的 LIMIT/OFFSET 是非确定性的', ja: 'ORDER BY なしの LIMIT/OFFSET は非決定的である' },
'sqlfluff/CP01': { zh: '关键字大小写不一致', ja: 'キーワードの大文字小文字が不統一' },
'sqlfluff/CP02': { zh: '未加引号的标识符大小写不一致', ja: '引用なし識別子の大文字小文字が不統一' },
'sqlfluff/CP03': { zh: '函数名大小写不一致', ja: '関数名の大文字小文字が不統一' },
'sqlfluff/CP04': { zh: '布尔/null 字面量大小写不一致', ja: 'ブール/null リテラルの大文字小文字が不統一' },
'sqlfluff/CP05': { zh: '数据类型大小写不一致', ja: 'データ型の大文字小文字が不統一' },
'sqlfluff/CV01': { zh: '一致地使用 != 或 <>', ja: '!= または <> を一貫して使用する' },
'sqlfluff/CV02': { zh: '使用 COALESCE 替代 IFNULL/NVL', ja: 'IFNULL/NVL の代わりに COALESCE を使用する' },
'sqlfluff/CV03': { zh: 'select 子句中的尾随逗号', ja: 'select 句内の末尾カンマ' },
'sqlfluff/CV04': { zh: '计数行数的一致语法', ja: '行数を数える一貫した構文' },
'sqlfluff/CV05': { zh: '与 NULL 比较应使用 IS 或 IS NOT', ja: 'NULL との比較には IS または IS NOT を使用する' },
'sqlfluff/CV06': { zh: '语句必须以分号结尾', ja: '文はセミコロンで終わらせる' },
'sqlfluff/CV07': { zh: '顶层语句不应包裹在括号中', ja: 'トップレベルの文を括弧で囲まない' },
'sqlfluff/CV08': { zh: '使用 LEFT JOIN 替代 RIGHT JOIN', ja: 'RIGHT JOIN の代わりに LEFT JOIN を使用する' },
'sqlfluff/CV09': { zh: '屏蔽一组可配置的词', ja: '設定可能な語のリストをブロックする' },
'sqlfluff/CV10': { zh: '引用的字面量一致使用首选引号', ja: '引用リテラルに推奨引用符を一貫して使用する' },
'sqlfluff/CV11': { zh: '强制一致的类型转换风格', ja: '一貫した型変換スタイルを強制する' },
'sqlfluff/CV12': { zh: '连接条件使用 JOIN ... ON ... 而非 WHERE', ja: '結合条件に WHERE ではなく JOIN ... ON ... を使用する' },
'sqlfluff/JJ01': { zh: 'Jinja 标签两侧应各有一个空格', ja: 'Jinja タグの両側に空白を1つ置く' },
'sqlfluff/LT01': { zh: '不合适的间距', ja: '不適切な間隔' },
'sqlfluff/LT02': { zh: '不正确的缩进', ja: '不適切なインデント' },
'sqlfluff/LT03': { zh: '运算符在换行前/后', ja: '改行前後への演算子の配置' },
'sqlfluff/LT04': { zh: '前导/尾随逗号的强制', ja: '先頭/末尾カンマの強制' },
'sqlfluff/LT05': { zh: '行过长', ja: '行が長すぎる' },
'sqlfluff/LT06': { zh: '函数名后未跟括号', ja: '関数名の後に括弧がない' },
'sqlfluff/LT07': { zh: 'WITH 子句的右括号应在新行', ja: 'WITH 句の閉じ括弧を新しい行に置く' },
'sqlfluff/LT08': { zh: 'CTE 右括号后应有空行', ja: 'CTE の閉じ括弧の後に空行を置く' },
'sqlfluff/LT09': { zh: 'select 目标在新行', ja: 'select 対象を新しい行に置く' },
'sqlfluff/LT10': { zh: 'SELECT 修饰符与 SELECT 同行', ja: 'SELECT 修飾子を SELECT と同じ行に置く' },
'sqlfluff/LT11': { zh: '集合运算符周围应有换行', ja: '集合演算子を改行で囲む' },
'sqlfluff/LT12': { zh: '文件必须以单个尾随换行结束', ja: 'ファイルは単一の末尾改行で終わる' },
'sqlfluff/LT13': { zh: '文件不得以换行/空白开头', ja: 'ファイルを改行/空白で始めない' },
'sqlfluff/LT14': { zh: '关键字子句在换行前/后', ja: 'キーワード句の改行前後の配置' },
'sqlfluff/LT15': { zh: '连续空行过多', ja: '連続する空行が多すぎる' },
'sqlfluff/OR01': { zh: '移除空批次', ja: '空のバッチを削除する' },
'sqlfluff/PG01': { zh: '避免 PostgreSQL DDL 中过度的锁', ja: 'PostgreSQL DDL での過度なロックを避ける' },
'sqlfluff/RF01': { zh: '引用不能引用 FROM 子句中不存在的对象', ja: 'FROM 句にないオブジェクトを参照できない' },
'sqlfluff/RF02': { zh: '多表时应限定引用', ja: '複数テーブルの場合は参照を修飾すべきである' },
'sqlfluff/RF03': { zh: '单表语句中列引用一致', ja: '単一テーブル文での列参照の一貫性' },
'sqlfluff/RF04': { zh: '关键字不应用作标识符', ja: 'キーワードを識別子として使用しない' },
'sqlfluff/RF05': { zh: '标识符中不应有特殊字符', ja: '識別子に特殊文字を含めない' },
'sqlfluff/RF06': { zh: '不必要的加引号标识符', ja: '不要な引用付き識別子' },
'sqlfluff/ST01': { zh: '不要在 CASE WHEN 中指定 else null', ja: 'CASE WHEN で else null を指定しない' },
'sqlfluff/ST02': { zh: '不必要的 CASE 语句', ja: '不要な CASE 文' },
'sqlfluff/ST03': { zh: '未使用的 CTE', ja: '未使用の CTE' },
'sqlfluff/ST04': { zh: 'ELSE 子句中的嵌套 CASE 可以扁平化', ja: 'ELSE 句内のネスト CASE は平坦化できる' },
'sqlfluff/ST05': { zh: 'Join/From 子句中的子查询;使用 CTE', ja: 'Join/From 句内のサブクエリ;CTE を使用する' },
'sqlfluff/ST06': { zh: '列顺序:通配符、简单目标、然后计算', ja: '列の順序:ワイルドカード、単純対象、計算の順' },
'sqlfluff/ST07': { zh: '连接键优先使用 ON 而非 USING', ja: '結合キーに USING より ON を優先する' },
'sqlfluff/ST08': { zh: 'DISTINCT 与括号一起使用', ja: 'DISTINCT が括弧付きで使用されている' },
'sqlfluff/ST09': { zh: '连接条件顺序', ja: '結合条件の順序' },
'sqlfluff/ST10': { zh: '冗余的常量表达式', ja: '冗長な定数式' },
'sqlfluff/ST11': { zh: '被连接的表未被引用', ja: '結合されたテーブルが参照されていない' },
'sqlfluff/ST12': { zh: '连续的分号', ja: '連続するセミコロン' },
'sqlfluff/TQ01': { zh: '用户定义存储过程不应使用 SP_ 前缀', ja: 'ユーザー定義ストアドプロシージャに SP_ プレフィックスを使わない' },
'sqlfluff/TQ02': { zh: '多语句的过程体用 BEGIN/END 包裹', ja: '複数文のプロシージャ本体を BEGIN/END で囲む' },
'sqlfluff/TQ03': { zh: '移除空批次', ja: '空のバッチを削除する' },
};
+36
View File
@@ -0,0 +1,36 @@
export default {
'stylelint/color-hex-length': { zh: '指定十六进制颜色值的短或长格式', ja: '16進カラー値の短い形式または長い形式を指定する' },
'stylelint/color-named': { zh: '要求(尽可能)或禁止使用命名颜色', ja: '名前付きカラーの使用を(可能な限り)要求または禁止する' },
'stylelint/color-no-invalid-hex': { zh: '禁止无效的十六进制颜色', ja: '無効な16進カラーを禁止する' },
'stylelint/length-zero-no-unit': { zh: '禁止零长度带单位', ja: 'ゼロ長に単位を付けることを禁止する' },
'stylelint/font-family-no-missing-generic-family-keyword': { zh: '禁止 font-family 中缺少通用字体族', ja: 'font-family での汎用ファミリーキーワードの欠落を禁止する' },
'stylelint/block-no-empty': { zh: '禁止空块', ja: '空のブロックを禁止する' },
'stylelint/declaration-block-no-duplicate-properties': { zh: '禁止声明块内重复的属性', ja: '宣言ブロック内の重複プロパティを禁止する' },
'stylelint/no-descending-specificity': { zh: '禁止低特异性的选择器覆盖高特异性', ja: '低特異性のセレクタが高特異性を上書きすることを禁止する' },
'stylelint/unit-no-unknown': { zh: '禁止未知单位', ja: '未知の単位を禁止する' },
'stylelint/property-no-unknown': { zh: '禁止未知属性', ja: '未知のプロパティを禁止する' },
'stylelint/selector-pseudo-class-no-unknown': { zh: '禁止未知的伪类选择器', ja: '未知の疑似クラスセレクタを禁止する' },
'stylelint/selector-pseudo-element-no-unknown': { zh: '禁止未知的伪元素选择器', ja: '未知の疑似要素セレクタを禁止する' },
'stylelint/function-linear-gradient-no-nonstandard-direction': { zh: '禁止 linear-gradient 中的非标准方向', ja: 'linear-gradient 内の非標準方向を禁止する' },
'stylelint/function-no-unknown': { zh: '禁止未知函数', ja: '未知の関数を禁止する' },
'stylelint/no-unknown-animations': { zh: '禁止未知动画', ja: '未知のアニメーションを禁止する' },
'stylelint/no-unknown-custom-media': { zh: '禁止未知的自定义媒体查询', ja: '未知のカスタムメディアクエリを禁止する' },
'stylelint/no-unknown-custom-properties': { zh: '禁止未知的自定义属性', ja: '未知のカスタムプロパティを禁止する' },
'stylelint/at-rule-no-vendor-prefix': { zh: '禁止 at 规则使用厂商前缀', ja: 'atルールへのベンダープレフィックスを禁止する' },
'stylelint/media-feature-name-no-vendor-prefix': { zh: '禁止媒体特性名称使用厂商前缀', ja: 'メディア特性名へのベンダープレフィックスを禁止する' },
'stylelint/property-no-vendor-prefix': { zh: '禁止属性使用厂商前缀', ja: 'プロパティへのベンダープレフィックスを禁止する' },
'stylelint/selector-no-vendor-prefix': { zh: '禁止选择器使用厂商前缀', ja: 'セレクタへのベンダープレフィックスを禁止する' },
'stylelint/value-no-vendor-prefix': { zh: '禁止值使用厂商前缀', ja: '値へのベンダープレフィックスを禁止する' },
'stylelint/color-function-notation': { zh: '要求颜色函数使用现代或传统记法', ja: '色関数に現代または従来の記法を要求する' },
'stylelint/selector-pseudo-element-colon-notation': { zh: '伪元素使用单冒号或双冒号记法', ja: '疑似要素に単コロンまたは二重コロン記法を使用する' },
'stylelint/import-notation': { zh: '要求 @import 使用字符串或 url 记法', ja: '@import に文字列または url 記法を要求する' },
'stylelint/alpha-value-notation': { zh: '要求透明度值使用百分比或数字记法', ja: 'アルファ値にパーセンテージまたは数値記法を要求する' },
'stylelint/hue-degree-notation': { zh: '要求色相度数使用数字或角度记法', ja: '色相の度に数値または角度記法を要求する' },
'stylelint/keyframe-selector-notation': { zh: '要求关键帧选择器使用关键字或百分比记法', ja: 'キーフレームセレクタにキーワードまたはパーセンテージ記法を要求する' },
'stylelint/declaration-block-no-redundant-longhand-properties': { zh: '禁止声明块中冗余的 longhand 属性', ja: '宣言ブロック内の冗長なロングハンドプロパティを禁止する' },
'stylelint/shorthand-property-no-redundant-values': { zh: '禁止 shorthand 属性中的冗余值', ja: 'shorthand プロパティ内の冗長な値を禁止する' },
'stylelint/block-no-redundant-nested-style-rules': { zh: '禁止块内冗余的嵌套样式规则', ja: 'ブロック内の冗長なネストスタイルルールを禁止する' },
'stylelint/font-family-name-quotes': { zh: '要求 font-family 名称使用引号', ja: 'font-family 名に引用符を要求する' },
'stylelint/number-max-precision': { zh: '限制数字的小数位数', ja: '数値の小数桁数を制限する' },
'stylelint/comment-whitespace-inside': { zh: '要求或禁止注释内部空白', ja: 'コメント内の空白を要求または禁止する' },
};
+38
View File
@@ -0,0 +1,38 @@
export default {
'ts-eslint/ban-ts-comment': { zh: '禁止使用 @ts-<指令> 注释', ja: '@ts-<ディレクティブ> コメントを禁止する' },
'ts-eslint/no-array-constructor': { zh: '禁止泛型 Array 构造函数', ja: 'ジェネリック Array コンストラクタを禁止する' },
'ts-eslint/no-duplicate-enum-values': { zh: '禁止重复的枚举成员值', ja: '重複する列挙メンバー値を禁止する' },
'ts-eslint/no-empty-object-type': { zh: '禁止空对象类型', ja: '空のオブジェクト型を禁止する' },
'ts-eslint/no-explicit-any': { zh: '禁止使用 any 类型', ja: 'any 型の使用を禁止する' },
'ts-eslint/no-extra-non-null-assertion': { zh: '禁止多余的非空断言', ja: '余分な非nullアサーションを禁止する' },
'ts-eslint/no-misused-new': { zh: '强制 new 和 constructor 的有效定义', ja: 'new とコンストラクタの有効な定義を強制する' },
'ts-eslint/no-namespace': { zh: '禁止自定义 TypeScript 模块和命名空间', ja: 'カスタム TypeScript モジュールと名前空間を禁止する' },
'ts-eslint/no-non-null-asserted-optional-chain': { zh: '禁止可选链之后的非空断言', ja: 'オプショナルチェーン後の非nullアサーションを禁止する' },
'ts-eslint/no-require-imports': { zh: '禁止调用 require()', ja: 'require() の呼び出しを禁止する' },
'ts-eslint/no-this-alias': { zh: '禁止为 this 创建别名', ja: 'this のエイリアス作成を禁止する' },
'ts-eslint/no-unnecessary-type-constraint': { zh: '禁止泛型类型上不必要的约束', ja: 'ジェネリック型の不要な制約を禁止する' },
'ts-eslint/no-unsafe-declaration-merging': { zh: '禁止不安全的声明合并', ja: '安全でない宣言のマージを禁止する' },
'ts-eslint/no-unsafe-function-type': { zh: '禁止使用 Function 作为类型', ja: 'Function を型として使用することを禁止する' },
'ts-eslint/no-unused-expressions': { zh: '禁止未使用的表达式', ja: '未使用の式を禁止する' },
'ts-eslint/no-unused-vars': { zh: '禁止未使用的变量', ja: '未使用の変数を禁止する' },
'ts-eslint/no-wrapper-object-types': { zh: '禁止包装对象类型(String、Number、Boolean', ja: 'ラッパーオブジェクト型(String、Number、Boolean)を禁止する' },
'ts-eslint/prefer-as-const': { zh: '优先使用 as const 而非字面量类型注解', ja: 'リテラル型注釈より as const を推奨する' },
'ts-eslint/prefer-namespace-keyword': { zh: '要求使用 namespace 关键字替代 module', ja: 'module キーワードより namespace の使用を要求する' },
'ts-eslint/triple-slash-reference': { zh: '禁止某些三斜线指令', ja: '特定のトリプルスラッシュディレクティブを禁止する' },
'ts-eslint/no-var': { zh: '要求使用 let 或 const 替代 var', ja: 'var の代わりに let または const を要求する' },
'ts-eslint/prefer-const': { zh: '对从未重新赋值的变量要求使用 const', ja: '再代入されない変数に const を要求する' },
'ts-eslint/prefer-rest-params': { zh: '要求使用剩余参数替代 arguments', ja: 'arguments の代わりに残余引数を要求する' },
'ts-eslint/prefer-spread': { zh: '要求使用展开运算符替代 .apply()', ja: '.apply() の代わりにスプレッド演算子を要求する' },
'ts-eslint/no-non-null-assertion': { zh: '禁止使用 ! 后缀运算符进行非空断言', ja: '! 接尾辞演算子による非nullアサーションを禁止する' },
'ts-eslint/no-dynamic-delete': { zh: '禁止对计算键表达式使用 delete 运算符', ja: '算出キー式への delete 演算子の使用を禁止する' },
'ts-eslint/no-useless-empty-export': { zh: '禁止不改变模块内容的空导出', ja: 'モジュールに変更を加えない空のエクスポートを禁止する' },
'ts-eslint/consistent-type-imports': { zh: '强制类型导入的一致用法', ja: '型インポートの一貫した使用を強制する' },
'ts-eslint/unified-signatures': { zh: '禁止可合并为单一签名的两个重载', ja: '単一のシグネチャに統合できる2つのオーバーロードを禁止する' },
'ts-eslint/no-extraneous-class': { zh: '禁止仅用作命名空间的类', ja: '名前空間としてのみ使用されるクラスを禁止する' },
'ts-eslint/no-useless-constructor': { zh: '禁止不必要的构造函数', ja: '不要なコンストラクタを禁止する' },
'ts-eslint/no-non-null-asserted-nullish-coalescing': { zh: '禁止空值合并运算符左操作数中的非空断言', ja: 'null合体演算子の左オペランドでの非nullアサーションを禁止する' },
'ts-eslint/no-invalid-void-type': { zh: '禁止泛型或返回类型之外的 void 类型', ja: 'ジェネリックまたは戻り値型以外での void 型を禁止する' },
'ts-eslint/prefer-literal-enum-member': { zh: '要求所有枚举成员为字面量值', ja: 'すべての列挙メンバーにリテラル値を要求する' },
'ts-eslint/prefer-enum-initializers': { zh: '要求每个枚举成员值被显式初始化', ja: '各列挙メンバー値の明示的な初期化を要求する' },
'ts-eslint/no-shadow': { zh: '禁止变量声明遮蔽外层作用域中声明的变量', ja: '外側スコープで宣言された変数を遮蔽する宣言を禁止する' },
};
+72 -2
View File
@@ -1,20 +1,26 @@
import * as vscode from 'vscode';
import { Orchestrator } from '../orchestrator/orchestrator';
import { runAIReview } from '../ai/engine';
import { runAIReview, runMethodReview } from '../ai/engine';
import { loadActiveRules } from '../rules/yaml-parser';
import { filterAndSummarize } from '../rules/rule-filter';
import { filterAndSummarize, filterForDocument } from '../rules/rule-filter';
import { mergeResults, MergedReport } from '../merger/merger';
import { reportToMarkdown } from '../utils/report';
import { getApiKey } from '../config';
import { ReviewPanel } from '../panel/webview';
import { t } from '../i18n/messages';
import { exportTemplate } from '../rules/export-service';
import { extractMethodScope } from '../scope/method-extractor';
import { ReviewStatusCache } from '../scope/status-cache';
import { MethodCodeLensProvider } from '../views/codeLensProvider';
import type { CustomRule } from '../types';
let currentReport: MergedReport | null = null;
export function registerCommands(
context: vscode.ExtensionContext,
orchestrator: Orchestrator,
codeLensProvider: MethodCodeLensProvider,
statusCache: ReviewStatusCache,
): void {
context.subscriptions.push(
@@ -105,6 +111,70 @@ export function registerCommands(
})
);
context.subscriptions.push(
vscode.commands.registerCommand('codeReviewer.reviewMethod', async (symbolRange?: vscode.Range) => {
const editor = vscode.window.activeTextEditor;
if (!editor) { return; }
const document = editor.document;
const workspaceRoot = vscode.workspace.getWorkspaceFolder(document.uri)?.uri.fsPath;
const targetRange = symbolRange ?? new vscode.Range(editor.selection.active, editor.selection.active);
const scope = await extractMethodScope(document, targetRange);
if (!scope) {
vscode.window.showWarningMessage(t('methodReview.noMethod'));
return;
}
let customRules: CustomRule[] = [];
if (workspaceRoot) {
const allRules = loadActiveRules(workspaceRoot);
customRules = filterForDocument(allRules, document);
}
await vscode.window.withProgress({
location: vscode.ProgressLocation.Notification,
title: t('methodReview.running', { 0: scope.name }),
cancellable: false,
}, async () => {
const result = await runMethodReview(context, scope, customRules);
const methodLine = scope.range.start.line;
const totalIssues = result.customRuleResults.length + result.findings.length;
currentReport = mergeResults({
staticDiagnostics: [],
customRuleResults: result.customRuleResults.map(r => ({
...r,
line: r.line + methodLine,
})),
translatedDiagnostics: [],
aiFindings: result.findings.map(f => ({
...f,
line: f.line + methodLine - 1,
})),
errors: result.error ? [result.error] : [],
degraded: result.degraded,
startTime: Date.now(),
filePath: document.uri.fsPath,
language: document.languageId,
adapterIds: [],
customRuleFilterInfo: undefined,
});
statusCache.set(document.uri, scope.name, totalIssues);
codeLensProvider.refresh();
const panel = ReviewPanel.createOrShow(context.extensionUri);
panel.update(currentReport);
vscode.window.showInformationMessage(
t('methodReview.complete', { 0: String(totalIssues) })
);
});
})
);
context.subscriptions.push(
vscode.commands.registerCommand('codeReviewer.openPanel', () => {
ReviewPanel.createOrShow(context.extensionUri);
+39 -10
View File
@@ -6,6 +6,7 @@ import js from '@eslint/js';
import ts from 'typescript-eslint';
import type { LinterAdapter, AdapterResult, LinterDiagnostic } from './adapter';
import { getEslintConfigPath } from '../config';
import { t } from '../i18n/messages';
const extraRules: Record<string, 'error' | 'warn'> = {
'eqeqeq': 'error',
@@ -61,17 +62,25 @@ const extraTsRules: Record<string, 'error' | 'warn' | 'off'> = {
const TS_FILES = ['**/*.ts', '**/*.tsx', '**/*.mts', '**/*.cts'];
const PROJECT_CONFIG_FILES = [
'eslint.config.js',
'eslint.config.mjs',
'eslint.config.cjs',
'eslint.config.ts',
'eslint.config.mts',
'eslint.config.cts',
];
const LEGACY_CONFIG_FILES = [
'.eslintrc.js',
'.eslintrc.cjs',
'.eslintrc.json',
'.eslintrc.yaml',
'.eslintrc.yml',
'.eslintrc',
'eslint.config.js',
'eslint.config.mjs',
];
function findProjectConfig(dir: string): string | null {
for (const name of PROJECT_CONFIG_FILES) {
function findConfigFile(dir: string, names: string[]): string | null {
for (const name of names) {
const p = path.join(dir, name);
if (fs.existsSync(p)) {
return p;
@@ -80,18 +89,30 @@ function findProjectConfig(dir: string): string | null {
return null;
}
function resolveEslintConfig(workingDir: string): { configFile?: string; overrideConfig?: any[] } {
type EslintConfigResult =
| { kind: 'use'; config: { overrideConfigFile?: string | true; overrideConfig?: any[] } }
| { kind: 'legacy'; path: string };
function resolveEslintConfig(workingDir: string): EslintConfigResult {
const globalPath = getEslintConfigPath();
if (globalPath && globalPath.trim() !== '') {
return { configFile: globalPath };
const abs = path.isAbsolute(globalPath) ? globalPath : path.resolve(workingDir, globalPath);
if (fs.existsSync(abs)) {
return { kind: 'use', config: { overrideConfigFile: abs } };
}
}
const projectConfig = findProjectConfig(workingDir);
const projectConfig = findConfigFile(workingDir, PROJECT_CONFIG_FILES);
if (projectConfig) {
return { configFile: projectConfig };
return { kind: 'use', config: { overrideConfigFile: projectConfig } };
}
return { overrideConfig: ESLintAdapter.getDefaultConfig() };
const legacyConfig = findConfigFile(workingDir, LEGACY_CONFIG_FILES);
if (legacyConfig) {
return { kind: 'legacy', path: legacyConfig };
}
return { kind: 'use', config: { overrideConfigFile: true, overrideConfig: ESLintAdapter.getDefaultConfig() } };
}
export class ESLintAdapter implements LinterAdapter {
@@ -119,9 +140,17 @@ export class ESLintAdapter implements LinterAdapter {
async check(document: vscode.TextDocument, workingDir: string): Promise<AdapterResult> {
try {
const resolved = resolveEslintConfig(workingDir);
if (resolved.kind === 'legacy') {
return {
diagnostics: [],
status: 'execution-failed',
errorMessage: t('adapter.eslintLegacyConfig', { 0: resolved.path }),
};
}
const engine = new ESLint({
cwd: workingDir,
...resolved,
...resolved.config,
});
const ext = document.languageId === 'typescript' ? 'ts' : 'js';
const isVirtual = document.uri.scheme === 'untitled';
+31 -5
View File
@@ -3,7 +3,7 @@ import type { LinterAdapter, LinterDiagnostic, AdapterResult } from '../types';
import { PmdAdapter } from './pmd';
import { ESLintAdapter } from './eslint';
import { StylelintAdapter } from './stylelint';
import { extractJspSections } from '../jsp/jsp-extractor';
import { extractJspSections, type JspSection } from '../jsp/jsp-extractor';
import { getLinterForLanguage } from '../config';
function mockDocument(code: string, language: string): vscode.TextDocument {
@@ -53,6 +53,28 @@ function mockDocument(code: string, language: string): vscode.TextDocument {
} as unknown as vscode.TextDocument;
}
const WRAP_TEMPLATES: Record<NonNullable<JspSection['scriptletKind']>, {
header: string;
footer: string;
headerLines: number;
}> = {
statement: { header: 'package jsp;\nclass JspScriptlet {\n void run() {\n', footer: '\n }\n}', headerLines: 3 },
expression: { header: 'package jsp;\nclass JspScriptlet {\n Object run() {\n return\n', footer: '\n }\n}', headerLines: 4 },
declaration: { header: 'package jsp;\nclass JspScriptlet {\n', footer: '\n}', headerLines: 2 },
};
function wrapJavaSection(section: JspSection): { code: string; headerLines: number } {
if (section.language !== 'java' || !section.scriptletKind) {
return { code: section.code, headerLines: 0 };
}
const tmpl = WRAP_TEMPLATES[section.scriptletKind];
let body = section.code;
if (section.scriptletKind === 'expression' && body.trim() !== '' && !body.trim().endsWith(';')) {
body += ';';
}
return { code: tmpl.header + body + tmpl.footer, headerLines: tmpl.headerLines };
}
export class JspAdapter implements LinterAdapter {
id = 'jsp';
supportedLanguages = ['jsp', 'html'];
@@ -69,7 +91,7 @@ export class JspAdapter implements LinterAdapter {
const cssEnabled = getLinterForLanguage('css') !== '';
const javaEnabled = getLinterForLanguage('java') !== '';
const pmdResult = await this.pmdAdapter.check(document, workingDir);
const pmdResult = await this.pmdAdapter.checkJsp(document, workingDir);
allDiagnostics.push(...pmdResult.diagnostics);
if (pmdResult.status !== 'ok') {
errors.push(`PMD: ${pmdResult.errorMessage ?? pmdResult.status}`);
@@ -87,13 +109,17 @@ export class JspAdapter implements LinterAdapter {
if (!adapter) { continue; }
try {
const result = await adapter.check(mockDocument(section.code, section.language), workingDir);
const { code, headerLines } = wrapJavaSection(section);
const result = await adapter.check(mockDocument(code, section.language), workingDir);
for (const diag of result.diagnostics) {
const startLine = diag.range.start.line - headerLines;
const endLine = diag.range.end.line - headerLines;
if (startLine < 0) { continue; }
const adjustedRange = new vscode.Range(
diag.range.start.line + section.lineOffset,
startLine + section.lineOffset,
diag.range.start.character,
diag.range.end.line + section.lineOffset,
endLine + section.lineOffset,
diag.range.end.character,
);
allDiagnostics.push({ ...diag, range: adjustedRange });
+29 -12
View File
@@ -3,7 +3,7 @@ import * as path from 'path';
import { existsSync } from 'fs';
import { execSync, spawn } from 'child_process';
import type { LinterAdapter, LinterDiagnostic, AdapterResult } from '../types';
import { getPMDJarPath, getPMDRulesetPath } from '../config';
import { getPMDJarPath, getPMDRulesetPath, getPMDJspRulesetPath } from '../config';
import { t } from '../i18n/messages';
export class PmdAdapter implements LinterAdapter {
@@ -52,23 +52,22 @@ export class PmdAdapter implements LinterAdapter {
}
async check(document: vscode.TextDocument, workingDir: string): Promise<AdapterResult> {
return this.run(document, workingDir, false);
}
async checkJsp(document: vscode.TextDocument, workingDir: string): Promise<AdapterResult> {
return this.run(document, workingDir, true);
}
private async run(document: vscode.TextDocument, workingDir: string, isJsp: boolean): Promise<AdapterResult> {
try {
const globalRuleset = getPMDRulesetPath();
let ruleset: string;
if (globalRuleset && globalRuleset.trim() !== '') {
ruleset = globalRuleset;
} else {
const projectRuleset = path.join(workingDir, 'ruleset.xml');
ruleset = existsSync(projectRuleset)
? projectRuleset
: path.join(this.getPmdRunnerClasspath(), 'pmd-java-ruleset.xml');
}
const ruleset = isJsp ? this.resolveJspRuleset() : this.resolveJavaRuleset(workingDir);
const classpath = `${this.getPmdLibClasspath()};${this.getPmdRunnerClasspath()}`;
const isVirtual = document.uri.scheme === 'untitled';
const fileArg = isVirtual ? '-' : document.uri.fsPath;
const javaArgs = ['-cp', classpath, 'PmdRunner', fileArg, ruleset];
const javaArgs = ['-cp', classpath, 'PmdRunner', fileArg, ruleset, isJsp ? 'jsp' : 'java'];
const result = await this.execPmd(javaArgs, isVirtual ? document.getText() : null, workingDir);
const diagnostics = this.parsePmdOutput(result);
@@ -82,6 +81,24 @@ export class PmdAdapter implements LinterAdapter {
}
}
private resolveJavaRuleset(workingDir: string): string {
const globalRuleset = getPMDRulesetPath();
if (globalRuleset && globalRuleset.trim() !== '') {
return globalRuleset;
}
const projectRuleset = path.join(workingDir, 'ruleset.xml');
return existsSync(projectRuleset)
? projectRuleset
: path.join(this.getPmdRunnerClasspath(), 'pmd-java-ruleset.xml');
}
private resolveJspRuleset(): string {
const globalRuleset = getPMDJspRulesetPath();
return globalRuleset && globalRuleset.trim() !== ''
? globalRuleset
: path.join(this.getPmdRunnerClasspath(), 'pmd-jsp-ruleset.xml');
}
private execPmd(args: string[], stdinInput: string | null, cwd: string): Promise<string> {
return new Promise((resolve, reject) => {
const proc = spawn('java', args, { cwd });
@@ -4,32 +4,44 @@ import * as path from 'path';
import * as os from 'os';
import { spawn } from 'child_process';
import type { LinterAdapter, AdapterResult, LinterDiagnostic, Severity } from './adapter';
import { getSqlLintConfigFile } from '../config';
import { getSqlFluffConfigFile, getSqlFluffDialect } from '../config';
import { t } from '../i18n/messages';
import staticRules from '../rules/static-rules.json';
const DIALECT_MAP: Record<string, string> = {
sql: 'ansi',
plsql: 'postgres',
sql: 'mysql',
plsql: 'oracle',
};
const BUILTIN_SQLFLUFF_CONFIG = `[sqlfluff]
rules = core,AM03,AM05,AM08,CV01,CV02,CV06,CV08,CV12,LT13,LT14,LT15,ST01,ST02,ST04,ST05,ST06,ST07,ST09,ST10,ST11,ST12,RF02,RF04,RF05,RF06
dialect = ansi
const SUPPORTED_DIALECTS = [
'ansi', 'athena', 'bigquery', 'clickhouse', 'databricks', 'db2', 'doris',
'duckdb', 'exasol', 'flink', 'greenplum', 'hive', 'impala', 'mariadb',
'materialize', 'mysql', 'oracle', 'postgres', 'redshift', 'snowflake',
'soql', 'sparksql', 'sqlite', 'starrocks', 'teradata', 'trino', 'tsql', 'vertica',
];
const BUILTIN_SQLFLUFF_RULES =
'core,AM03,AM05,AM08,CV01,CV02,CV06,CV08,CV12,LT13,LT14,LT15,ST01,ST02,ST04,ST05,ST06,ST07,ST09,ST10,ST11,ST12,RF02,RF04,RF05,RF06';
function buildBuiltinConfig(dialect: string): string {
return `[sqlfluff]
rules = ${BUILTIN_SQLFLUFF_RULES}
dialect = ${dialect}
max_line_length = 80
indent_unit = space
tab_space_size = 4
`;
}
interface RuleEntry { id: string; description: string; tier?: string; }
const tierMap = new Map<string, string>();
try {
const sqlfluffRules = (staticRules as any).rules?.['sql-lint'] as RuleEntry[] | undefined;
const sqlfluffRules = (staticRules as any).rules?.['sqlfluff'] as RuleEntry[] | undefined;
if (sqlfluffRules) {
for (const rule of sqlfluffRules) {
if (rule.id && rule.tier) {
tierMap.set(rule.id.replace('sql-lint/', ''), rule.tier);
tierMap.set(rule.id.replace('sqlfluff/', ''), rule.tier);
}
}
}
@@ -65,9 +77,12 @@ interface SqlFluffResult {
violations: SqlFluffViolation[];
}
function runSqlfluff(dialect: string, code: string, cwd: string, configPath?: string): Promise<string> {
function runSqlfluff(code: string, cwd: string, configPath?: string, dialect?: string): Promise<string> {
return new Promise((resolve, reject) => {
const args = ['lint', '--dialect', dialect, '--format', 'json'];
const args = ['lint', '--format', 'json'];
if (dialect) {
args.push('--dialect', dialect);
}
if (configPath) {
args.push('--config', configPath);
}
@@ -104,8 +119,8 @@ function runSqlfluff(dialect: string, code: string, cwd: string, configPath?: st
});
}
export class SqlLintAdapter implements LinterAdapter {
id = 'sql-lint';
export class SqlFluffAdapter implements LinterAdapter {
id = 'sqlfluff';
supportedLanguages = ['sql', 'plsql'];
isAvailable(): boolean {
@@ -114,23 +129,28 @@ export class SqlLintAdapter implements LinterAdapter {
async check(document: vscode.TextDocument, workingDir: string): Promise<AdapterResult> {
const languageId = document.languageId;
const dialect = DIALECT_MAP[languageId] || 'ansi';
const fallbackDialect = DIALECT_MAP[languageId] ?? 'ansi';
const explicitDialect = getSqlFluffDialect();
const cliDialect = explicitDialect && SUPPORTED_DIALECTS.includes(explicitDialect)
? explicitDialect
: undefined;
let configPath: string | undefined;
let tempConfigPath: string | undefined;
const globalConfig = getSqlLintConfigFile();
const globalConfig = getSqlFluffConfigFile();
if (globalConfig && globalConfig.trim() !== '') {
configPath = globalConfig;
} else if (hasProjectSqlfluffConfig(workingDir)) {
} else {
tempConfigPath = path.join(os.tmpdir(), `vscode-code-reviewer-sqlfluff-${Date.now()}.cfg`);
fs.writeFileSync(tempConfigPath, BUILTIN_SQLFLUFF_CONFIG, 'utf-8');
fs.writeFileSync(tempConfigPath, buildBuiltinConfig(cliDialect ?? fallbackDialect), 'utf-8');
configPath = tempConfigPath;
}
try {
const stdout = await runSqlfluff(dialect, document.getText(), workingDir, configPath);
const stdout = await runSqlfluff(document.getText(), workingDir, configPath, cliDialect);
const results: SqlFluffResult[] = JSON.parse(stdout);
const diagnostics: LinterDiagnostic[] = [];
@@ -138,7 +158,7 @@ export class SqlLintAdapter implements LinterAdapter {
for (const v of result.violations) {
diagnostics.push({
severity: tierToSeverity(tierMap.get(v.code)),
ruleId: `sql-lint:${v.code}`,
ruleId: `sqlfluff:${v.code}`,
message: v.description,
range: new vscode.Range(
v.start_line_no - 1,
+384 -4
View File
@@ -1,5 +1,6 @@
import * as vscode from 'vscode';
import type { AIProvider } from './providers/base';
import { EmptyContentError } from './providers/base';
import type { AIProvider, ChatOptions } from './providers/base';
import { createProvider } from './factory';
import { getAIProvider, getAIModel, getAIBaseUrl, getAITemperature, getAITimeout, getAIMaxTokens, getAIOutputLanguage, getApiKey } from '../config';
import type { LinterDiagnostic, CustomRule } from '../types';
@@ -8,7 +9,10 @@ import type {
CustomRuleResult,
TranslatedDiagnostic,
AIFinding,
MethodFinding,
MethodReviewResult,
} from './schema';
import type { MethodScope } from '../scope/method-extractor';
import { t, getLanguage } from '../i18n/messages';
function buildCustomRulePrompt(rules: CustomRule[]): string {
@@ -56,8 +60,11 @@ function repairJsonEscapes(str: string): string {
return out;
}
function parseJsonResponse(raw: string): object {
export function parseJsonResponse(raw: string): object {
const trimmed = raw.trim();
if (trimmed === '') {
throw new Error(t('engine.emptyResponse'));
}
const start = trimmed.indexOf('{');
const end = trimmed.lastIndexOf('}');
if (start === -1 || end === -1) {
@@ -76,6 +83,22 @@ function parseJsonResponse(raw: string): object {
}
}
export async function chatWithRetry(
provider: AIProvider,
systemPrompt: string,
userPrompt: string,
options: ChatOptions
): Promise<string> {
try {
return await provider.chat(systemPrompt, userPrompt, options);
} catch (err) {
if (err instanceof EmptyContentError) {
return provider.chat(systemPrompt, userPrompt, options);
}
throw err;
}
}
function buildCustomRuleSystemPrompt(): string {
const lang = getLanguage();
if (lang === 'ja') {
@@ -214,14 +237,16 @@ export async function runAIReview(
const requestA =
customRules.length > 0
? provider.chat(
? chatWithRetry(
provider,
buildCustomRuleSystemPrompt(),
buildUserPromptCustomRules(customRules, numberedCode),
options
)
: Promise.resolve('{}');
const requestB = provider.chat(
const requestB = chatWithRetry(
provider,
buildDeepReviewSystemPrompt(),
buildUserPromptDeepReview(numberedCode, staticDiagnostics),
options
@@ -272,3 +297,358 @@ export async function runAIReview(
error: errors.join('; '),
};
}
export async function runMethodReview(
context: vscode.ExtensionContext,
scope: MethodScope,
customRules: CustomRule[]
): Promise<MethodReviewResult> {
const apiKey = await getApiKey(context);
if (!apiKey) {
return {
customRuleResults: [],
findings: [],
degraded: true,
error: t('adapter.noApiKey'),
};
}
const providerId = getAIProvider();
const baseUrl = getAIBaseUrl();
let provider: AIProvider;
try {
provider = createProvider(providerId, apiKey, baseUrl, context.extensionUri);
} catch (err) {
return {
customRuleResults: [],
findings: [],
degraded: true,
error: t('adapter.createProviderFail', { 0: err instanceof Error ? err.message : String(err) }),
};
}
const options = {
model: getAIModel(),
temperature: getAITemperature(),
maxTokens: getAIMaxTokens(),
timeoutMs: getAITimeout() * 1000,
};
const numberedCode = addLineNumbers(scope.code);
const hasRules = customRules.length > 0;
let response: string;
try {
response = await chatWithRetry(
provider,
buildMethodReviewSystemPrompt(hasRules),
buildMethodUserPrompt(scope, numberedCode, customRules),
options
);
} catch (err) {
return {
customRuleResults: [],
findings: [],
degraded: true,
error: t('adapter.aiReviewRequestFail', { 0: err instanceof Error ? err.message : String(err) }),
};
}
const errors: string[] = [];
let customRuleResults: CustomRuleResult[] = [];
let findings: MethodFinding[] = [];
try {
const parsed = parseJsonResponse(response) as {
customRuleResults?: CustomRuleResult[];
findings?: MethodFinding[];
};
customRuleResults = (parsed.customRuleResults ?? []).map(r => {
const id = String(r.ruleId ?? '');
return { ...r, ruleId: id.startsWith('custom:') ? id : `custom:${id}` };
});
findings = (parsed.findings ?? []).map(f => {
const id = String(f.ruleId ?? '');
return { ...f, ruleId: id.startsWith('method:') ? id : `method:${id}` };
});
} catch (e) {
errors.push(t('adapter.aiReviewParseFail', { 0: e instanceof Error ? e.message : String(e) }));
}
return {
customRuleResults,
findings,
degraded: errors.length > 0,
error: errors.join('; ') || undefined,
};
}
function buildMethodReviewSystemPrompt(hasRules: boolean): string {
const lang = getLanguage();
if (lang === 'ja') {
return buildMethodSystemPromptJa(hasRules);
}
if (lang === 'en') {
return buildMethodSystemPromptEn(hasRules);
}
return buildMethodSystemPromptZh(hasRules);
}
function buildMethodSystemPromptEn(hasRules: boolean): string {
const ruleSection = hasRules
? `## Task 1: Custom Rule Matching
Evaluate whether the method violates any of the provided custom rules.
Understand semantics, not text matching.
Report violations in "customRuleResults".\n\n`
: '';
const ruleOutput = hasRules
? ` "customRuleResults": [
{
"ruleId": "original rule id",
"line": line_number,
"severity": "error|warning|info",
"message": "violation description"
}
],\n`
: '';
return `You are a senior code review expert reviewing a single method.
There is no static analysis before you — you handle rule matching AND deep review.
${ruleSection}## Review Strategy: Path Enumeration
- Walk through every if/else/switch branch, note coverage and gaps
- Enumerate boundary values for every parameter (null, empty collection, extreme values, wrong types)
- Check every throw/catch path for proper fallback strategy
- Trace the method's role in its call chain
## Required Dimensions (do not skip any)
A. Correctness: branch coverage, boundary conditions, exception path completeness
B. Security: input validation, injection risk, permission check, sensitive data leakage
C. Design: single responsibility, parameter design, return value contract, call chain adaptation
D. Convention: naming, cyclomatic complexity, magic numbers, missing comments
E. Performance: time/space complexity, resource leaks, unnecessary computation
F. Testability: side effect isolation, dependency mockability, deterministic output
## Call Chain Analysis
- Check whether callers' arguments match this method's expectations
- Check whether this method's return value is correctly handled by callers
- Check whether exceptions are caught or declared by callers
Output JSON only. Double quotes in strings must be escaped with \\".
Format:
{
${ruleOutput} "findings": [
{
"ruleId": "method-boundary-null",
"severity": "error|warning|info",
"category": "correctness|security|design|convention|performance|testability",
"title": "issue title",
"description": "detailed description",
"suggestion": "fix suggestion",
"codeDiff": "optional fix diff",
"line": line_number,
"path": "trigger path description, e.g. if(order==null) -> NPE on .getId()"
}
]
}
If no issues found, return empty arrays.
Output language: en`;
}
function buildMethodSystemPromptZh(hasRules: boolean): string {
const ruleSection = hasRules
? `## 任务一:自定义规则匹配
评估方法是否违反了提供的自定义规则。
理解语义,而非文本匹配。
在 "customRuleResults" 中报告违规。\n\n`
: '';
const ruleOutput = hasRules
? ` "customRuleResults": [
{
"ruleId": "原始规则 ID",
"line": 行号,
"severity": "error|warning|info",
"message": "违规描述"
}
],\n`
: '';
return `你是资深代码审查专家,正在审查单个方法。
没有静态分析的前置过滤——你同时负责规则匹配和深度审查。
${ruleSection}## 审查策略:逐路径枚举
- 遍历每个 if/else/switch 分支,标注覆盖与遗漏
- 枚举每个入参的边界值(null、空集合、极值、错误类型)
- 检查每个 throw/catch 路径的降级策略
- 追踪方法在调用链中的角色
## 必须覆盖的维度(不可跳过)
A. 正确性:分支覆盖、边界条件、异常路径完整性
B. 安全性:输入校验、注入风险、权限检查、敏感信息泄露
C. 设计:职责单一性、参数设计合理性、返回值契约、调用链适配
D. 规范:命名、圈复杂度、魔法数字、注释缺失
E. 性能:时间/空间复杂度、资源泄漏、不必要的计算
F. 可测试性:副作用隔离、依赖可 Mock 性、确定性输出
## 调用链分析
- 检查调用者传入的参数是否符合本方法预期
- 检查本方法的返回值是否被调用者正确处理
- 检查异常是否被调用者捕获或声明
输出 JSON,字符串中的双引号必须用 \\" 转义。
格式:
{
${ruleOutput} "findings": [
{
"ruleId": "method-boundary-null",
"severity": "error|warning|info",
"category": "correctness|security|design|convention|performance|testability",
"title": "问题标题",
"description": "详细描述",
"suggestion": "修复建议",
"codeDiff": "可选的修复 diff",
"line": 行号,
"path": "触发路径描述,如 if(order==null) -> NPE on .getId()"
}
]
}
如果未发现问题,返回空数组。
输出语言:zh-CN`;
}
function buildMethodSystemPromptJa(hasRules: boolean): string {
const ruleSection = hasRules
? `## タスク1:カスタムルールマッチング
提供されたカスタムルールの違反があるか評価してください。
意味を理解し、テキストの一致ではなく判断してください。
違反を "customRuleResults" で報告してください。\n\n`
: '';
const ruleOutput = hasRules
? ` "customRuleResults": [
{
"ruleId": "元のルールID",
"line": 行番号,
"severity": "error|warning|info",
"message": "違反の説明"
}
],\n`
: '';
return `あなたはシニアコードレビュー専門家です。単一のメソッドをレビューしています。
事前の静的解析はありません——あなたがルールマッチングと詳細レビューの両方を担当します。
${ruleSection}## レビュー戦略:パス列挙
- すべての if/else/switch 分岐を辿り、カバレッジと漏れを確認
- すべての引数の境界値(null、空コレクション、極値、誤った型)を列挙
- すべての throw/catch パスのフォールバック戦略を確認
- コールチェーンにおけるメソッドの役割を追跡
## 必須カバレッジ(スキップ不可)
A. 正しさ:分岐カバレッジ、境界条件、例外パスの完全性
B. セキュリティ:入力検証、インジェクションリスク、権限チェック、機密情報漏洩
C. 設計:単一責任、パラメータ設計、戻り値契約、コールチェーン適合
D. 規約:命名、循環的複雑度、マジックナンバー、コメント欠落
E. パフォーマンス:時間/空間複雑度、リソースリーク、不要な計算
F. テスタビリティ:副作用の分離、依存のモック化容易性、決定的出力
## コールチェーン分析
- 呼び出し元の引数がこのメソッドの期待と一致しているか確認
- このメソッドの戻り値が呼び出し元で正しく処理されているか確認
- 例外が呼び出し元でキャッチまたは宣言されているか確認
JSONのみを出力。文字列内の二重引用符は \\" でエスケープしてください。
形式:
{
${ruleOutput} "findings": [
{
"ruleId": "method-boundary-null",
"severity": "error|warning|info",
"category": "correctness|security|design|convention|performance|testability",
"title": "問題のタイトル",
"description": "詳細な説明",
"suggestion": "修正提案",
"codeDiff": "オプションの修正diff",
"line": 行番号,
"path": "トリガーパス説明、例: if(order==null) -> .getId() で NPE"
}
]
}
問題がない場合は空配列を返してください。
出力言語:ja`;
}
interface MethodPromptLabels {
signature: string;
code: string;
rule: string;
chain: string;
role: string;
callers: string;
callees: string;
none: string;
}
function getMethodPromptLabels(lang: string): MethodPromptLabels {
if (lang === 'ja') {
return {
signature: 'メソッド署名',
code: 'メソッドコード(行番号付き)',
rule: 'マッチングするカスタムルール',
chain: 'コールチェーンコンテキスト',
role: '業務フローでの役割',
callers: '呼び出し元',
callees: '呼び出し先',
none: '(なし)',
};
}
if (lang === 'en') {
return {
signature: 'Method Signature',
code: 'Method Code (with line numbers)',
rule: 'Custom Rules to Match',
chain: 'Call Chain Context',
role: 'Role in Business Flow',
callers: 'Callers',
callees: 'Callees',
none: '(none)',
};
}
return {
signature: '方法签名',
code: '方法代码(带行号)',
rule: '需匹配的自定义规则',
chain: '调用链上下文',
role: '业务流中的角色',
callers: '调用者',
callees: '被调用者',
none: '(无)',
};
}
function buildMethodUserPrompt(
scope: MethodScope,
numberedCode: string,
customRules: CustomRule[]
): string {
const labels = getMethodPromptLabels(getLanguage());
let ruleBlock = '';
if (customRules.length > 0) {
const ruleLines = customRules
.map((r, i) => `${i + 1}. [${r.id}] (${r.severity}) ${r.description}\n ${r.message}`)
.join('\n');
ruleBlock = `\n## ${labels.rule}\n${ruleLines}\n`;
}
return `## ${labels.signature}
${scope.signature}
## ${labels.code}
${numberedCode}
${ruleBlock}
## ${labels.chain}
${labels.role}: ${scope.role}
${labels.callers}: ${scope.callers.length > 0 ? scope.callers.join(', ') : labels.none}
${labels.callees}: ${scope.callees.length > 0 ? scope.callees.join(', ') : labels.none}`;
}
+7
View File
@@ -21,3 +21,10 @@ export abstract class AIProvider {
options: ChatOptions
): Promise<string>;
}
export class EmptyContentError extends Error {
constructor(detail: string) {
super(detail);
this.name = 'EmptyContentError';
}
}
+15 -2
View File
@@ -1,4 +1,5 @@
import { AIProvider, ChatOptions } from './base';
import { AIProvider, ChatOptions, EmptyContentError } from './base';
import { t } from '../../i18n/messages';
export class ClaudeProvider extends AIProvider {
id = 'claude';
@@ -42,8 +43,20 @@ export class ClaudeProvider extends AIProvider {
const data = await response.json() as {
content?: Array<{ text?: string }>;
stop_reason?: string;
error?: { message?: string };
};
return data.content?.[0]?.text ?? '';
const text = data.content?.[0]?.text;
if (text === undefined || text === null || text.trim() === '') {
const parts = [`stop_reason=${data.stop_reason ?? 'unknown'}`];
if (data.error?.message) {
parts.push(data.error.message);
}
throw new EmptyContentError(t('adapter.emptyContent', { 0: parts.join(', ') }));
}
return text;
} finally {
clearTimeout(timeout);
}
+17 -2
View File
@@ -1,4 +1,5 @@
import { AIProvider, ChatOptions } from './base';
import { AIProvider, ChatOptions, EmptyContentError } from './base';
import { t } from '../../i18n/messages';
export class GeminiProvider extends AIProvider {
id = 'gemini';
@@ -42,9 +43,23 @@ export class GeminiProvider extends AIProvider {
candidates?: Array<{
content?: { parts?: Array<{ text?: string }> };
}>;
promptFeedback?: { blockReason?: string };
error?: { message?: string };
};
return data.candidates?.[0]?.content?.parts?.[0]?.text ?? '';
const text = data.candidates?.[0]?.content?.parts?.[0]?.text;
if (text === undefined || text === null || text.trim() === '') {
const parts = [
`candidates=${data.candidates?.length ?? 0}`,
`blockReason=${data.promptFeedback?.blockReason ?? 'none'}`,
];
if (data.error?.message) {
parts.push(data.error.message);
}
throw new EmptyContentError(t('adapter.emptyContent', { 0: parts.join(', ') }));
}
return text;
} finally {
clearTimeout(timeout);
}
+24 -3
View File
@@ -1,4 +1,4 @@
import { AIProvider, ChatOptions } from './base';
import { AIProvider, ChatOptions, EmptyContentError } from './base';
import { t } from '../../i18n/messages';
export class OpenAICompatibleProvider extends AIProvider {
@@ -53,9 +53,30 @@ export class OpenAICompatibleProvider extends AIProvider {
}
const data = await response.json() as {
choices: Array<{ message: { content: string } }>;
choices?: Array<{
message?: { content?: string | null };
finish_reason?: string | null;
}>;
error?: { message?: string };
};
return data.choices[0]?.message?.content ?? '';
const content = data.choices?.[0]?.message?.content;
if (content === undefined || content === null || content.trim() === '') {
const finish = data.choices?.[0]?.finish_reason ?? 'unknown';
if (finish === 'length') {
throw new EmptyContentError(t('adapter.maxTokensTruncated', { 0: String(options.maxTokens) }));
}
const parts = [
`finish_reason=${finish}`,
`choices=${data.choices?.length ?? 0}`,
];
if (data.error?.message) {
parts.push(data.error.message);
}
throw new EmptyContentError(t('adapter.emptyContent', { 0: parts.join(', ') }));
}
return content;
} finally {
clearTimeout(timeout);
}
+21 -1
View File
@@ -15,7 +15,7 @@ export interface CustomRuleResult {
export interface AIFinding {
ruleId: string;
severity: 'error' | 'warning' | 'info';
category: 'bug' | 'performance' | 'security' | 'style' | 'design';
category: 'bug' | 'performance' | 'security' | 'style' | 'design' | 'correctness' | 'convention' | 'testability';
title: string;
description: string;
suggestion: string;
@@ -23,6 +23,26 @@ export interface AIFinding {
line: number;
}
export type MethodFindingCategory =
| 'correctness'
| 'security'
| 'design'
| 'convention'
| 'performance'
| 'testability';
export interface MethodFinding extends AIFinding {
category: MethodFindingCategory;
path?: string;
}
export interface MethodReviewResult {
customRuleResults: CustomRuleResult[];
findings: MethodFinding[];
degraded: boolean;
error?: string;
}
export interface AIResponse {
translatedDiagnostics: TranslatedDiagnostic[];
customRuleResults: CustomRuleResult[];
+6 -2
View File
@@ -18,8 +18,12 @@ export function getPMDJspRulesetPath(): string {
return vscode.workspace.getConfiguration(ROOT).get<string>('pmd.jspRulesetPath', '');
}
export function getSqlLintConfigFile(): string {
return vscode.workspace.getConfiguration(ROOT).get<string>('sql-lint.configFile', '');
export function getSqlFluffConfigFile(): string {
return vscode.workspace.getConfiguration(ROOT).get<string>('sqlfluff.configFile', '');
}
export function getSqlFluffDialect(): string {
return vscode.workspace.getConfiguration(ROOT).get<string>('sqlfluff.dialect', '');
}
export function getEslintConfigPath(): string {
+19 -1
View File
@@ -4,6 +4,8 @@ import { registerCommands } from './activation/commands';
import { SetupViewProvider } from './views/setupView';
import { setLanguage, t, type Language } from './i18n/messages';
import { getAIOutputLanguage } from './config';
import { ReviewStatusCache } from './scope/status-cache';
import { MethodCodeLensProvider } from './views/codeLensProvider';
let orchestrator: Orchestrator;
@@ -19,7 +21,23 @@ export function activate(context: vscode.ExtensionContext) {
vscode.window.registerWebviewViewProvider('codeReviewer.setupView', setupProvider)
);
registerCommands(context, orchestrator);
const statusCache = new ReviewStatusCache();
const codeLensProvider = new MethodCodeLensProvider(statusCache);
context.subscriptions.push(
vscode.languages.registerCodeLensProvider(
{ scheme: 'file' },
codeLensProvider
)
);
context.subscriptions.push(
vscode.workspace.onDidCloseTextDocument((document) => {
statusCache.clearDocument(document.uri);
})
);
registerCommands(context, orchestrator, codeLensProvider, statusCache);
const debounceTimers = new Map<string, NodeJS.Timeout>();
+132 -6
View File
@@ -142,6 +142,11 @@ const messages: Record<string, Record<Language, string>> = {
en: '✗ Connection failed: {0}',
ja: '✗ 接続失敗: {0}',
},
'setup.emptyResponse': {
'zh-CN': 'AI 返回空响应,请检查模型配置',
en: 'AI returned an empty response, please check the model configuration',
ja: 'AIが空の応答を返しました。モデル設定を確認してください',
},
'setup.selectRuleFile': {
'zh-CN': '选择规则文件',
en: 'Select rule file',
@@ -523,9 +528,9 @@ const messages: Record<string, Record<Language, string>> = {
ja: 'JS, TS, JSX, TSXJSP内のJavaScriptコードを含む、例: <script>',
},
'setup.adapter.eslintGuide': {
'zh-CN': '项目根目录创建 .eslintrc.js 或在 VS Code 设置中配置 eslintConfigPath',
en: 'Create .eslintrc.js in project root or set eslintConfigPath in VS Code settings',
ja: 'プロジェクトルートに.eslintrc.jsを作成するか、VS Code設定でeslintConfigPathを設定してください',
'zh-CN': '项目根目录创建 eslint.config.js 或在 VS Code 设置中配置 eslintConfigPath',
en: 'Create eslint.config.js in project root or set eslintConfigPath in VS Code settings',
ja: 'プロジェクトルートにeslint.config.jsを作成するか、VS Code設定でeslintConfigPathを設定してください',
},
'setup.adapter.stylelintLanguages': {
'zh-CN': 'CSS, SCSS, Less(含 JSP 中的 CSS 代码,例如<style>',
@@ -602,6 +607,36 @@ const messages: Record<string, Record<Language, string>> = {
en: 'Overlap reason: {0}',
ja: '重複理由:{0}',
},
'import.dupExactTitle': {
'zh-CN': '完全重复',
en: 'Exact duplicate',
ja: '完全重複',
},
'import.dupOverlapTitle': {
'zh-CN': '部分重叠',
en: 'Partial overlap',
ja: '部分的重複',
},
'import.dupExactText': {
'zh-CN': '与规则「{0}」完全重复',
en: 'Exact duplicate of rule "{0}"',
ja: 'ルール「{0}」と完全重複',
},
'import.dupOverlapText': {
'zh-CN': '与规则「{0}」部分重叠',
en: 'Partially overlaps rule "{0}"',
ja: 'ルール「{0}」と部分的に重複',
},
'import.dupDescriptionLabel': {
'zh-CN': 'description{0}',
en: 'description: {0}',
ja: 'description{0}',
},
'import.dupExactHint': {
'zh-CN': '默认将注释导入,点击「保留」可恢复',
en: 'Imported as commented out by default; click "Keep" to restore',
ja: 'デフォルトでコメントアウトとしてインポートされます。「保持」をクリックすると復元します',
},
'import.badgeRestored': {
'zh-CN': '已恢复',
en: 'Restored',
@@ -712,6 +747,21 @@ const messages: Record<string, Record<Language, string>> = {
en: 'Rule id cannot be empty',
ja: 'ルールIDは必須です',
},
'import.idMissing': {
'zh-CN': 'id 缺失,请补充',
en: 'id missing, please fill in',
ja: 'id がありません、入力してください',
},
'import.severityMissing': {
'zh-CN': 'severity 缺失',
en: 'severity missing',
ja: 'severity が未設定',
},
'import.severitySelectHint': {
'zh-CN': '请选择 severity',
en: 'Select severity',
ja: 'severity を選択',
},
'report.panelTitle': {
'zh-CN': '净码特工 · 代码审查报告',
@@ -1002,6 +1052,11 @@ const messages: Record<string, Record<Language, string>> = {
en: 'JSON parse failed. Raw response (first 200 chars): {0}',
ja: 'JSON解析に失敗しました。生のレスポンス(先頭200文字):{0}',
},
'engine.emptyResponse': {
'zh-CN': 'AI 返回空响应',
en: 'AI returned an empty response',
ja: 'AIが空の応答を返しました',
},
'adapter.javaNotInstalled': {
'zh-CN': 'Java 11+ 未安装或不在 PATH 中',
@@ -1048,6 +1103,21 @@ const messages: Record<string, Record<Language, string>> = {
en: 'AI review request failed: {0}',
ja: 'AIレビューリクエストに失敗しました: {0}',
},
'adapter.eslintLegacyConfig': {
'zh-CN': '项目使用旧版 .eslintrc 配置({0}),ESLint v9 已不支持。请迁移到 eslint.config.js 或删除该文件以使用内置规则',
en: 'Project uses legacy .eslintrc config ({0}), which is not supported by ESLint v9. Please migrate to eslint.config.js or remove the file to use built-in rules',
ja: 'プロジェクトは旧形式の.eslintrc設定({0})を使用しています。ESLint v9ではサポートされていません。eslint.config.jsへの移行、またはファイル削除で組み込みルールを使用してください',
},
'adapter.emptyContent': {
'zh-CN': 'AI 返回空内容({0}',
en: 'AI returned empty content ({0})',
ja: 'AIが空のコンテンツを返しました({0})',
},
'adapter.maxTokensTruncated': {
'zh-CN': 'AI 输出被 max_tokens 截断(当前 {0}),请调大设置 ai.maxTokens 或更换模型',
en: 'AI output was truncated by max_tokens (current: {0}). Please increase ai.maxTokens or switch to a different model',
ja: 'AI出力がmax_tokensで打ち切られました(現在 {0})。ai.maxTokensを増やすか、モデルを変更してください',
},
'extension.activated': {
'zh-CN': '净码特工 · Code Purifier 已激活',
@@ -1116,9 +1186,9 @@ const messages: Record<string, Record<Language, string>> = {
ja: 'エラー行',
},
'import.cannotImport': {
'zh-CN': '将自动丢弃,请修改文件后重新导入',
en: 'Will be auto-discarded, please fix the file and retry',
ja: '自動破棄されます、ファイルを修正して再インポートしてください',
'zh-CN': '需修复后点击添加',
en: 'Fix then click Add',
ja: '修正して「追加」をクリック',
},
'import.dedupFailed': {
'zh-CN': 'AI 去重失败,规则将不带去重标记导入',
@@ -1135,6 +1205,31 @@ const messages: Record<string, Record<Language, string>> = {
en: '⚠',
ja: '⚠',
},
'import.add': {
'zh-CN': '添加',
en: 'Add',
ja: '追加',
},
'import.adding': {
'zh-CN': '校验并去重中...',
en: 'Validating & deduping...',
ja: '検証・重複排除中...',
},
'import.idConflict': {
'zh-CN': 'id {0} 与已有规则重复,请修改 id',
en: 'id {0} conflicts with an existing rule, change the id',
ja: 'id {0} が既存ルールと重複、id を変更してください',
},
'import.addDedupFallback': {
'zh-CN': 'AI 去重失败,已以无重复方式添加',
en: 'AI dedup failed, added as no-duplicate',
ja: 'AI 重複排除失敗、重複なしとして追加',
},
'import.validationSeverityInvalid': {
'zh-CN': 'severity 非法',
en: 'Invalid severity',
ja: 'severity が不正です',
},
'exportTemplate.saveLabel': {
'zh-CN': '导出模板',
en: 'Export Template',
@@ -1155,6 +1250,37 @@ const messages: Record<string, Record<Language, string>> = {
en: 'Reveal in Folder',
ja: 'フォルダを開く',
},
'codelens.reviewMethod': {
'zh-CN': '🔍 Code Purifier: 审查此方法',
en: '🔍 Code Purifier: Review This Method',
ja: '🔍 Code Purifier: このメソッドを審査',
},
'codelens.reviewedClean': {
'zh-CN': '✓ Code Purifier: 已审查(无问题)',
en: '✓ Code Purifier: Reviewed (No Issues)',
ja: '✓ Code Purifier: 審査済み(問題なし)',
},
'codelens.reviewedWithIssues': {
'zh-CN': '✓ Code Purifier: 已审查({0} 个问题)',
en: '✓ Code Purifier: Reviewed ({0} Issues)',
ja: '✓ Code Purifier: 審査済み({0} 件の問題)',
},
'methodReview.running': {
'zh-CN': 'Code Purifier 正在审查方法:{0}',
en: 'Code Purifier: Reviewing method: {0}',
ja: 'Code Purifier がメソッドを審査中:{0}',
},
'methodReview.noMethod': {
'zh-CN': '当前位置未检测到方法',
en: 'No method detected at current position',
ja: '現在位置でメソッドが検出されませんでした',
},
'methodReview.complete': {
'zh-CN': '方法审查完成,发现 {0} 个问题',
en: 'Method review complete, {0} issues found',
ja: 'メソッド審査完了、{0} 件の問題を発見',
},
};
let currentLang: Language = defaultLang;
+24 -3
View File
@@ -4,6 +4,7 @@ export interface JspSection {
lineOffset: number;
sourceStart: number;
sourceEnd: number;
scriptletKind?: 'statement' | 'declaration' | 'expression';
}
export function extractJspSections(content: string): JspSection[] {
@@ -38,17 +39,37 @@ export function extractJspSections(content: string): JspSection[] {
});
}
const scriptletRegex = /<%=?([\s\S]*?)%>/g;
while ((match = scriptletRegex.exec(content)) !== null) {
const code = match[1];
const jspTagRegex = /<%--([\s\S]*?)--%>|<%!([\s\S]*?)%>|<%=([\s\S]*?)%>|<%@([\s\S]*?)%>|<%([\s\S]*?)%>/g;
while ((match = jspTagRegex.exec(content)) !== null) {
const beforeMatch = content.substring(0, match.index);
const lineOffset = beforeMatch.split('\n').length - 1;
let code: string | undefined;
let scriptletKind: JspSection['scriptletKind'] | undefined;
if (match[1] !== undefined || match[4] !== undefined) {
continue;
}
if (match[2] !== undefined) {
code = match[2];
scriptletKind = 'declaration';
} else if (match[3] !== undefined) {
code = match[3];
scriptletKind = 'expression';
} else if (match[5] !== undefined) {
code = match[5];
scriptletKind = 'statement';
}
if (code === undefined) { continue; }
sections.push({
language: 'java',
code,
lineOffset,
sourceStart: match.index,
sourceEnd: match.index + match[0].length,
scriptletKind,
});
}
+35 -17
View File
@@ -45,27 +45,45 @@ interface MergeInput {
};
}
export function mergeResults(input: MergeInput): MergedReport {
const customRuleDiagnostics: LinterDiagnostic[] = input.customRuleResults.map(r => ({
severity: r.severity as Severity,
ruleId: r.ruleId,
message: r.message,
range: new vscode.Range(Math.max(0, r.line - 1), 0, Math.max(0, r.line - 1), 1),
}));
const SEVERITY_RANK: Record<string, number> = { error: 0, warning: 1, info: 2 };
const linterDiagnostics = input.staticDiagnostics.map((d, i) => {
const td = input.translatedDiagnostics[i];
if (td) {
return { ...d, message: td.translatedMessage, suggestion: td.translatedSuggestion || d.suggestion };
}
return d;
function sortBySeverityAndLine<T extends { severity: string }>(items: T[], lineOf: (item: T) => number): T[] {
return [...items].sort((a, b) => {
const rankDiff = (SEVERITY_RANK[a.severity] ?? 3) - (SEVERITY_RANK[b.severity] ?? 3);
if (rankDiff !== 0) { return rankDiff; }
return lineOf(a) - lineOf(b);
});
}
const linterCount = input.staticDiagnostics.length;
export function mergeResults(input: MergeInput): MergedReport {
const customRuleDiagnostics: LinterDiagnostic[] = sortBySeverityAndLine(
input.customRuleResults.map(r => ({
severity: r.severity as Severity,
ruleId: r.ruleId,
message: r.message,
range: new vscode.Range(Math.max(0, r.line - 1), 0, Math.max(0, r.line - 1), 1),
})),
d => d.range.start.line
);
const linterDiagnostics = sortBySeverityAndLine(
input.staticDiagnostics.map((d, i) => {
const td = input.translatedDiagnostics[i];
if (td) {
return { ...d, message: td.translatedMessage, suggestion: td.translatedSuggestion || d.suggestion };
}
return d;
}),
d => d.range.start.line
);
const aiFindings = sortBySeverityAndLine(input.aiFindings, f => f.line);
const linterCount = linterDiagnostics.length;
const customRuleCount = customRuleDiagnostics.length;
const aiCount = input.aiFindings.length;
const aiCount = aiFindings.length;
const fixableLinterIndices = input.staticDiagnostics.map((_, i) => i);
const fixableLinterIndices = linterDiagnostics.map((_, i) => i);
const fixableCustomIndices = customRuleDiagnostics
.map((_, i) => i);
@@ -74,7 +92,7 @@ export function mergeResults(input: MergeInput): MergedReport {
linterDiagnostics,
customRuleDiagnostics,
translatedDiagnostics: input.translatedDiagnostics,
aiFindings: input.aiFindings,
aiFindings,
linterCount,
customRuleCount,
aiCount,
+2 -2
View File
@@ -4,7 +4,7 @@ import { getLinterForLanguage, isAdapterEnabled } from '../config';
import { ESLintAdapter } from '../adapters/eslint';
import { PmdAdapter } from '../adapters/pmd';
import { StylelintAdapter } from '../adapters/stylelint';
import { SqlLintAdapter } from '../adapters/sql-lint';
import { SqlFluffAdapter } from '../adapters/sqlfluff';
import { JspAdapter } from '../adapters/jsp';
export interface StaticAnalysisResult {
@@ -22,7 +22,7 @@ export class Orchestrator {
new ESLintAdapter(),
new PmdAdapter(),
new StylelintAdapter(),
new SqlLintAdapter(),
new SqlFluffAdapter(),
new JspAdapter(),
];
}
+4
View File
@@ -332,6 +332,10 @@ ${errorBox}
const parts: string[] = [`<div class="section-header"><span class="section-header-title">${t('report.sourceAI')} · ${t('report.itemsCount', { 0: report.aiCount })}</span><button class="btn" onclick="send('fixAll')">${t('report.fixAll')}</button></div>`];
for (const f of report.aiFindings) {
const details: string[] = [];
const path = (f as { path?: string }).path;
if (path) {
details.push(`<div class="detail-text">🔗 ${esc(path)}</div>`);
}
details.push(`<div class="detail-text">${esc(f.description)}</div>`);
if (f.category) {
details.push(`<span class="detail-category">🎯 ${esc(f.category)}</span>`);
+5 -19
View File
@@ -1,5 +1,6 @@
import type { CustomRule } from '../../types';
import { getLanguage, type Language } from '../../i18n/messages';
import { buildKnownRulesSection } from './known-rules';
export function buildDedupOnlyPrompt(
yamlContent: string,
@@ -8,12 +9,6 @@ export function buildDedupOnlyPrompt(
const lang = getLanguage();
const s = PROMPTS[lang];
const existingList = existingRules.length === 0
? s.noExisting
: existingRules.map(r =>
`- id: ${r.id} | severity: ${r.severity} | description: ${r.description} | message: ${r.message}`
).join('\n');
const system = [
s.role,
s.taskTitle,
@@ -22,8 +17,7 @@ export function buildDedupOnlyPrompt(
s.rulesLines.join('\n'),
s.constraintTitle,
s.constraintLines.join('\n'),
s.existingTitle,
existingList,
buildKnownRulesSection(existingRules),
].join('\n\n');
const user = s.userPrefix + '\n\n' + yamlContent;
@@ -38,15 +32,13 @@ const PROMPTS: Record<Language, {
rulesLines: string[];
constraintTitle: string;
constraintLines: string[];
existingTitle: string;
noExisting: string;
userPrefix: string;
}> = {
'zh-CN': {
role: '你是规则去重判定助手。你只输出 YAML,不输出任何解释。',
taskTitle: '## 任务',
taskLines: [
'下面是已标准化的规则 YAML。你只负责对照"现有规则"为每条规则标注去重字段。',
'下面是已标准化的规则 YAML。你只负责对照"内置静态分析规则与已导入的自定义规则"为每条规则标注去重字段。',
'为每条规则补充以下字段(如果无重复则标注 none):',
'- duplicateOf: 重复的规则 ID(如 eslint/no-console、custom/my-rule',
'- duplicateLevel: exact(完全相同)/ overlap(部分重叠)/ none(无重复)',
@@ -67,15 +59,13 @@ const PROMPTS: Record<Language, {
'5. 如果某条规则与现有规则无任何重复,设置 duplicateLevel: none 即可,不需要补充 duplicateOf',
'6. 输出纯 YAML,不要用 markdown 代码块包裹',
],
existingTitle: '## 现有规则',
noExisting: '(无)',
userPrefix: '## 待去重的规则 YAML',
},
'en': {
role: 'You are a rule deduplication assistant. Output YAML only, no explanations.',
taskTitle: '## Task',
taskLines: [
'Below is standardized rule YAML. Only annotate dedup fields against the "Existing Rules" list.',
'Below is standardized rule YAML. Only annotate dedup fields by comparing against the "built-in static analysis rules and existing custom rules".',
'For each rule, add (mark none if no conflict):',
'- duplicateOf: duplicated rule ID (e.g. eslint/no-console, custom/my-rule)',
'- duplicateLevel: exact / overlap / none',
@@ -96,15 +86,13 @@ const PROMPTS: Record<Language, {
'5. If a rule has no duplication, set duplicateLevel: none without duplicateOf',
'6. Output pure YAML, do NOT wrap in markdown code fences',
],
existingTitle: '## Existing Rules',
noExisting: '(none)',
userPrefix: '## YAML to deduplicate',
},
'ja': {
role: 'あなたはルール重複判定アシスタントです。YAML のみ出力し、説明は不要です。',
taskTitle: '## タスク',
taskLines: [
'以下は標準化されたルール YAML です。既存ルール照合し、重複フィールドのみ注釈してください。',
'以下は標準化されたルール YAML です。組み込みの静的解析ルールと既存カスタムルール照合し、重複フィールドのみ注釈してください。',
'各ルールに以下を追加(重複がない場合は none と表記):',
'- duplicateOf: 重複ルール ID(例: eslint/no-console, custom/my-rule',
'- duplicateLevel: exact / overlap / none',
@@ -125,8 +113,6 @@ const PROMPTS: Record<Language, {
'5. 重複がないルールは duplicateLevel: none とし、duplicateOf は付けない',
'6. 純粋な YAML を出力し、markdown コードブロックで囲まない',
],
existingTitle: '## 既存ルール',
noExisting: '(なし)',
userPrefix: '## 重複排除対象の YAML',
},
};
+57
View File
@@ -0,0 +1,57 @@
import staticRules from '../static-rules.json';
import type { CustomRule } from '../../types';
import { getLanguage, type Language } from '../../i18n/messages';
interface KnownRulesLabels {
header: string;
linterLabel: (name: string, count: number) => string;
customLabel: (count: number) => string;
footer: string;
}
const LABELS: Record<Language, KnownRulesLabels> = {
'zh-CN': {
header: '## 已知规则清单(用于重复检测)',
linterLabel: (name, count) => `### ${name} (${count} 条)`,
customLabel: (count) => `### 已导入的自定义规则 (${count} 条)`,
footer: '判定时请精确匹配上述规则 ID,而非模糊匹配分类。',
},
en: {
header: '## Known Rules (for duplicate detection)',
linterLabel: (name, count) => `### ${name} (${count} rules)`,
customLabel: (count) => `### Imported custom rules (${count} rules)`,
footer: 'Match exactly by rule ID above, not by fuzzy category matching.',
},
ja: {
header: '## 既知ルール一覧(重複検出用)',
linterLabel: (name, count) => `### ${name}${count} 件)`,
customLabel: (count) => `### インポート済みカスタムルール(${count} 件)`,
footer: '上記ルールIDで正確にマッチングしてください。曖昧なカテゴリマッチングは避けてください。',
},
};
export function buildKnownRulesSection(existingCustomRules?: CustomRule[]): string {
const lang = getLanguage();
const l = LABELS[lang] ?? LABELS['zh-CN'];
const lines: string[] = [l.header];
for (const [linter, rules] of Object.entries(staticRules.rules)) {
lines.push(l.linterLabel(linter, rules.length));
for (const rule of rules) {
lines.push(`- ${rule.id}: ${rule.description}`);
}
lines.push('');
}
if (existingCustomRules && existingCustomRules.length > 0) {
lines.push(l.customLabel(existingCustomRules.length));
for (const rule of existingCustomRules) {
lines.push(`- custom/${rule.id}: ${rule.description}`);
}
lines.push('');
}
lines.push(l.footer);
return lines.join('\n');
}
+2 -44
View File
@@ -1,6 +1,6 @@
import staticRules from '../static-rules.json';
import type { CustomRule } from '../../types';
import { getLanguage } from '../../i18n/messages';
import { buildKnownRulesSection } from './known-rules';
export type PromptInputType = 'freeform' | 'spreadsheet';
@@ -24,10 +24,6 @@ interface PromptStrings {
staticAnalysisTitle: string;
staticAnalysisLines: string[];
finalInstruction: string;
dedupHeader: string;
dedupLinterLabel: (name: string, count: number) => string;
dedupCustomLabel: (count: number) => string;
dedupFooter: string;
outputLang: string;
}
@@ -145,10 +141,6 @@ const p: Record<Lang, PromptStrings> = {
'仅输出 YAML,不要额外说明。',
],
finalInstruction: '只输出 YAML 内容,不要输出 markdown 代码块标记,不要输出解释性文字',
dedupHeader: '## 已知规则清单(用于重复检测)',
dedupLinterLabel: (name, count) => `### ${name} (${count} 条)`,
dedupCustomLabel: (count) => `### 已导入的自定义规则 (${count} 条)`,
dedupFooter: '判定时请精确匹配上述规则 ID,而非模糊匹配分类。',
outputLang: '输出语言:zh-CN',
},
@@ -265,10 +257,6 @@ const p: Record<Lang, PromptStrings> = {
'Output YAML only, no extra explanation.',
],
finalInstruction: 'All descriptions and messages must be written in English.\nOutput YAML only, no markdown code fences, no explanatory text',
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',
},
@@ -385,10 +373,6 @@ const p: Record<Lang, PromptStrings> = {
'YAMLのみを出力し、追加説明は不要です。',
],
finalInstruction: 'すべてのdescriptionとmessageは日本語で出力してください。\nYAML のみ出力、マークダウンコードブロックなし、説明テキストなし',
dedupHeader: '## 既知ルール一覧(重複検出用)',
dedupLinterLabel: (name, count) => `### ${name}${count} 件)`,
dedupCustomLabel: (count) => `### インポート済みカスタムルール(${count} 件)`,
dedupFooter: '上記ルールIDで正確にマッチングしてください。曖昧なカテゴリマッチングは避けてください。',
outputLang: '出力言語:ja',
},
};
@@ -399,32 +383,6 @@ function getLang(): 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];
@@ -460,7 +418,7 @@ export function buildSystemPrompt(inputType: PromptInputType, existingRules?: Cu
`${lang === 'zh-CN' ? '输出' : lang === 'ja' ? '出力' : 'Output'}`,
s.example2Output,
'',
buildDedupPromptSection(existingRules, lang),
buildKnownRulesSection(existingRules),
'',
s.staticAnalysisTitle,
...s.staticAnalysisLines,
+21 -3
View File
@@ -43,14 +43,30 @@ export function parseTemplate(srcPath: string): TemplateParseResult {
const totalRows = rows.length;
const rules: ImportableRule[] = rows
.map((r, idx) => ({ r, rowNo: idx + 2 }))
.filter(({ r }) => String(r.id ?? '').trim() !== '')
.filter(({ r }) => {
const id = String(r.id ?? '').trim();
const description = String(r.description ?? '').trim();
const message = String(r.message ?? '').trim();
return id !== '' || description !== '' || message !== '';
})
.map(({ r, rowNo }) => {
const issues: ValidationIssue[] = [];
const rawId = String(r.id ?? '').trim();
let id = rawId;
let idPlaceholder = false;
if (!rawId) {
id = `rule-${rowNo}`;
idPlaceholder = true;
issues.push({ field: 'id', severity: 'error', message: t('import.idMissing') });
}
const sevRaw = String(r.severity ?? '').trim().toLowerCase();
const originalSeverity = String(r.severity ?? '').trim();
const severity: Severity = VALID_SEVERITY.includes(sevRaw) ? (sevRaw as Severity) : 'warning';
if (!VALID_SEVERITY.includes(sevRaw)) {
issues.push({ field: 'severity', severity: 'warning', message: `severity 非法: "${r.severity ?? ''}"` });
const detail = originalSeverity ? `: "${originalSeverity}"` : '';
issues.push({ field: 'severity', severity: 'warning', message: `${t('import.validationSeverityInvalid')}${detail}` });
}
const description = String(r.description ?? '').trim();
@@ -64,13 +80,15 @@ export function parseTemplate(srcPath: string): TemplateParseResult {
}
return {
id: String(r.id).trim(),
id,
severity,
description,
message,
languages: splitList(r.languages),
excludeLanguages: splitList(r.excludeLanguages),
rowNumber: rowNo,
originalSeverity,
idPlaceholder,
validationIssues: issues.length > 0 ? issues : undefined,
};
});
+636 -140
View File
@@ -1,9 +1,13 @@
import * as vscode from 'vscode';
import type { ConversionResult, PreviewDecision, ImportableRule } from './import-types';
import { t } from '../i18n/messages';
import type { ConversionResult, PreviewDecision, ImportableRule, ValidationIssue } from './import-types';
import { dedupSingleRule } from './import-service';
import { loadActiveRules } from './yaml-parser';
import staticRules from './static-rules.json';
import { t, getLanguage } from '../i18n/messages';
export async function showImportPreview(
result: ConversionResult,
context: vscode.ExtensionContext,
): Promise<PreviewDecision | null> {
return new Promise((resolve) => {
const panel = vscode.window.createWebviewPanel(
@@ -22,9 +26,11 @@ export async function showImportPreview(
panel.webview.html = renderPreviewHtml(result, keepRule);
panel.webview.onDidReceiveMessage((msg) => {
panel.webview.onDidReceiveMessage(async (msg) => {
if (msg.type === 'toggleRule') {
keepRule[msg.ruleId] = msg.keep;
} else if (msg.type === 'addErrorRule') {
await handleAddErrorRule(msg, result, keepRule, context, panel);
} else if (msg.type === 'confirm') {
resolve({
keepRule,
@@ -42,6 +48,108 @@ export async function showImportPreview(
});
}
interface RuleValidationError {
field: 'id' | 'severity' | 'description' | 'message';
message: string;
}
function validateRule(rule: ImportableRule): RuleValidationError | null {
if (!rule.id || !rule.id.trim()) {
return { field: 'id', message: t('import.validationIdEmpty') };
}
if (!['error', 'warning', 'info'].includes(rule.severity)) {
return { field: 'severity', message: t('import.validationSeverityInvalid') };
}
if (!rule.description || !rule.description.trim()) {
return { field: 'description', message: t('import.validationDescEmpty', { 0: rule.id }) };
}
if (!rule.message || !rule.message.trim()) {
return { field: 'message', message: t('import.validationMsgEmpty', { 0: rule.id }) };
}
return null;
}
async function handleAddErrorRule(
msg: {
ruleId: string;
rule: ImportableRule;
},
result: ConversionResult,
keepRule: Record<string, boolean>,
context: vscode.ExtensionContext,
panel: vscode.WebviewPanel,
): Promise<void> {
const rule = msg.rule;
const validationError = validateRule(rule);
if (validationError) {
panel.webview.postMessage({
type: 'addError',
ruleId: msg.ruleId,
field: validationError.field,
message: validationError.message,
});
return;
}
const original = result.rules.find(r => r.id === msg.ruleId);
if (original?.idPlaceholder && rule.id === msg.ruleId) {
panel.webview.postMessage({
type: 'addError',
ruleId: msg.ruleId,
field: 'id',
message: t('import.idMissing'),
});
return;
}
const conflict = result.rules.some(r =>
!r.validationIssues?.length && r.id.toLowerCase() === rule.id.toLowerCase()
);
if (conflict) {
panel.webview.postMessage({
type: 'addError',
ruleId: msg.ruleId,
field: 'id',
message: t('import.idConflict', { 0: rule.id }),
});
return;
}
const dedup = await dedupSingleRule(rule, context);
const level = dedup?.duplicateLevel ?? 'none';
const dedupFailed = !dedup;
const idx = result.rules.findIndex(r => r.id === msg.ruleId);
if (idx >= 0) {
result.rules[idx] = {
...result.rules[idx],
id: rule.id,
severity: rule.severity,
description: rule.description,
message: rule.message,
languages: rule.languages,
excludeLanguages: rule.excludeLanguages,
duplicateLevel: level,
duplicateOf: dedup?.duplicateOf,
duplicateReason: dedup?.duplicateReason,
validationIssues: undefined,
};
}
keepRule[rule.id] = level !== 'exact';
panel.webview.postMessage({
type: 'ruleAdded',
ruleId: msg.ruleId,
id: rule.id,
duplicateLevel: level,
duplicateOf: dedup?.duplicateOf,
duplicateReason: dedup?.duplicateReason,
dedupFailed,
});
}
const SEVERITY_OPTIONS = ['error', 'warning', 'info'];
const SEVERITY_COLORS: Record<string, string> = {
error: '#f48771',
@@ -69,92 +177,151 @@ function renderPreviewHtml(
? `<div class="summary-bar" style="border-color:rgba(88,166,255,0.3);color:#58a6ff;">${t('import.template.skipped', { 0: String(result.skippedCount) })}</div>`
: '';
const hasValidRules = cleanRules.length > 0;
const emptyValidHint = !hasValidRules
? `<div class="validation-error" style="display:block;">${t('import.emptyValidRules')}</div>`
: '';
function dupLabel(dupOf: string | undefined): string {
return dupOf?.startsWith('custom/')
? `${t('import.customRulePrefix')} ${dupOf.slice(7)}`
: (dupOf ?? 'unknown');
}
const confirmBtnAttrs = hasValidRules
? 'onclick="doConfirm()"'
: 'disabled style="opacity:0.5;cursor:not-allowed;"';
const workspaceRoot = vscode.workspace.workspaceFolders?.[0]?.uri.fsPath;
const customRules = workspaceRoot ? loadActiveRules(workspaceRoot) : [];
function renderRuleCard(rule: ImportableRule): string {
function resolveDupDescription(dupOf: string | undefined): string | undefined {
if (!dupOf) { return undefined; }
if (dupOf.startsWith('custom/')) {
const id = dupOf.slice(7);
return customRules.find(r => r.id === id)?.description;
}
const slash = dupOf.indexOf('/');
const linter = slash > 0 ? dupOf.slice(0, slash) : '';
const linterRules = (staticRules.rules as Record<string, Array<{ id: string; description: string; descriptionZh?: string; descriptionJa?: string }>>)[linter];
const rule = linterRules?.find(r => r.id === dupOf);
if (!rule) { return undefined; }
const lang = getLanguage();
if (lang === 'zh-CN' && rule.descriptionZh) {
return `${rule.description} (${rule.descriptionZh})`;
}
if (lang === 'ja' && rule.descriptionJa) {
return `${rule.description} (${rule.descriptionJa})`;
}
return rule.description;
}
function renderRuleCard(rule: ImportableRule, isError = false): string {
const kept = keepRule[rule.id];
const color = SEVERITY_COLORS[rule.severity] || '#8b949e';
const editRule = rule;
const sevIssue = !!rule.validationIssues?.some(i => i.field === 'severity');
const color = sevIssue ? '#f48771' : (SEVERITY_COLORS[rule.severity] || '#8b949e');
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>` : ''}
`;
let statusBadge = '';
if (!isError) {
if (rule.duplicateLevel === 'exact') {
const dupDesc = resolveDupDescription(rule.duplicateOf);
duplicateInfo = `
<div class="dup-banner dup-exact">
<div class="dup-banner-title">${t('import.dupExactTitle')}</div>
<div class="dup-banner-text">${t('import.dupExactText', { 0: dupLabel(rule.duplicateOf) })}</div>
${dupDesc ? `<div class="dup-banner-desc">${t('import.dupDescriptionLabel', { 0: dupDesc })}</div>` : ''}
<div class="dup-banner-hint">${t('import.dupExactHint')}</div>
</div>
`;
statusBadge = `<span class="badge badge-exact">${kept ? t('import.badgeRestored') : t('import.badgeWillComment')}</span>`;
} else if (rule.duplicateLevel === 'overlap') {
const dupDesc = resolveDupDescription(rule.duplicateOf);
duplicateInfo = `
<div class="dup-banner dup-overlap">
<div class="dup-banner-title">${t('import.dupOverlapTitle')}</div>
<div class="dup-banner-text">${t('import.dupOverlapText', { 0: dupLabel(rule.duplicateOf) })}</div>
${dupDesc ? `<div class="dup-banner-desc">${t('import.dupDescriptionLabel', { 0: dupDesc })}</div>` : ''}
${rule.duplicateReason ? `<div class="dup-banner-reason">${t('import.overlapReason', { 0: rule.duplicateReason })}</div>` : ''}
</div>
`;
statusBadge = `<span class="badge badge-overlap">${kept ? t('importPreview.keep') : t('importPreview.comment')}</span>`;
} else {
statusBadge = `<span class="badge badge-none">${t('importPreview.keep')}</span>`;
}
} else {
statusBadge = `<span class="badge" style="background:rgba(248,81,73,0.15);color:#f48771;">${t('import.cannotImport')}</span>`;
}
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 tagDisplay = (tags: string[] | undefined) => tags && tags.length > 0 ? tags.map(tag => `<span class="tag" data-value="${tag}">${tag}<span class="tag-remove" data-tag="${tag}">×</span></span>`).join('') : '';
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('') : '';
const issueByField = new Map<string, ValidationIssue>();
if (isError) {
for (const i of rule.validationIssues || []) {
if (!issueByField.has(i.field)) {
issueByField.set(i.field, i);
}
}
}
const issueCls = (field: string) => issueByField.has(field) ? ' field-error' : '';
const inputCls = (field: string) => issueByField.has(field) ? 'field-error-input ' : '';
const issueMsg = (field: string) => issueByField.has(field)
? `<div class="field-error-msg">${t('import.issuePrefix')} ${issueByField.get(field)!.message}</div>`
: '';
const actionArea = isError
? `<button class="add-btn" data-addbtn="${rule.id}" onclick="event.stopPropagation();addErrorRule(this)">${t('import.add')}</button>`
: `<div class="keep-toggle">
<button class="toggle-btn ${kept ? 'active' : ''}" data-action="keep" onclick="event.stopPropagation();toggleKeep(this, true)">${t('importPreview.keep')}</button>
<button class="toggle-btn ${!kept ? 'active' : ''}" data-action="comment" onclick="event.stopPropagation();toggleKeep(this, false)">${t('importPreview.comment')}</button>
</div>`;
const bodyDisplay = isError ? 'block' : 'none';
const expandIcon = '▼';
return `
<div class="rule-card" data-ruleid="${rule.id}">
<div class="rule-card-header" onclick="toggleCard('${rule.id}')">
<div class="rule-card${isError ? ' expanded' : ''}" data-ruleid="${rule.id}"${isError ? ' data-error="true"' : ''}>
<div class="rule-card-header" onclick="toggleCard(this)">
<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>
<input class="rule-id-input${/^rule-\d+$/.test(rule.id) ? ' placeholder-id' : ''}" value="${rule.id}" onchange="syncId(this)" onclick="event.stopPropagation()">
<span class="rule-severity-tag" style="background:${color}20;color:${color};border:1px solid ${color}40;">${sevIssue ? (rule.originalSeverity || t('import.severityMissing')) : rule.severity}</span>
<span class="rule-desc-preview">${rule.description}</span>
</div>
<div class="rule-card-meta">
${statusBadge}
<span class="expand-icon"></span>
<span class="expand-icon">${expandIcon}</span>
</div>
</div>
<div class="rule-card-body" id="body-${rule.id}" style="display:none;">
<div class="rule-card-body" id="body-${rule.id}" style="display:${bodyDisplay};">
<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)">
<input class="id-display-input${/^rule-\d+$/.test(rule.id) ? ' placeholder-id' : ''}" value="${rule.id}" onchange="syncId(this)">
${/^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>
${actionArea}
</div>
${duplicateInfo}
<div class="edit-field">
<div class="edit-field${issueCls('severity')}">
<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 class="${inputCls('severity')}" onchange="updateRule(this,'severity',this.value)">
${sevIssue ? `<option value="" disabled selected>${t('import.severitySelectHint')}</option>` : ''}
${SEVERITY_OPTIONS.map(s => `<option value="${s}" ${!sevIssue && s === rule.severity ? 'selected' : ''}>${s}</option>`).join('')}
</select>
${issueMsg('severity')}
</div>
<div class="edit-field">
<div class="edit-field${issueCls('description')}">
<label>${t('import.descriptionLabel')}</label>
<textarea rows="2" onchange="updateRule('${rule.id}','description',this.value)">${editRule.description}</textarea>
<textarea rows="2" class="${inputCls('description')}" onchange="updateRule(this,'description',this.value)">${rule.description}</textarea>
${issueMsg('description')}
</div>
<div class="edit-field">
<div class="edit-field${issueCls('message')}">
<label>${t('import.messageLabel')}</label>
<textarea rows="2" onchange="updateRule('${rule.id}','message',this.value)">${editRule.message}</textarea>
<textarea rows="2" class="${inputCls('message')}" onchange="updateRule(this,'message',this.value)">${rule.message}</textarea>
${issueMsg('message')}
</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)}
${tagDisplay(rule.languages)}
</div>
<input class="tag-input" data-ruleid="${rule.id}" data-field="languages" placeholder="${t('import.tagPlaceholder')}" value="">
</div>
@@ -164,7 +331,7 @@ function renderPreviewHtml(
<label>${t('import.excludeLanguagesLabel')}</label>
<div class="tag-input-wrapper">
<div class="tag-list" data-ruleid="${rule.id}" data-field="excludeLanguages">
${tagDisplay(editRule.excludeLanguages)}
${tagDisplay(rule.excludeLanguages)}
</div>
<input class="tag-input" data-ruleid="${rule.id}" data-field="excludeLanguages" placeholder="${t('import.tagPlaceholder')}" value="">
</div>
@@ -174,59 +341,35 @@ function renderPreviewHtml(
`;
}
function renderErrorCard(rule: ImportableRule): string {
const issues = (rule.validationIssues || []).map(i =>
`<div style="color:#f48771;font-size:12px;margin-bottom:4px;">${t('import.issuePrefix')} ${i.message}</div>`
).join('');
return `
<div class="rule-card" data-error="true" style="opacity:0.7;border-color:rgba(248,81,73,0.3);">
<div class="rule-card-header" style="cursor:default;">
<div class="rule-card-summary">
<span style="font-family:monospace;font-size:13px;font-weight:600;">${rule.id}</span>
<span style="color:#f48771;font-size:11px;font-weight:600;">${t('import.cannotImport')}</span>
</div>
</div>
<div class="rule-card-body" style="border-top:1px solid rgba(248,81,73,0.15);padding-top:8px;">
${issues}
<div style="color:#8b949e;font-size:11px;margin-top:6px;">
severity: ${rule.severity} | description: ${rule.description} | message: ${rule.message}
</div>
</div>
</div>
`;
}
function renderErrorSection(rules: ImportableRule[]): string {
if (rules.length === 0) { return ''; }
const sectionId = 'section-error';
return `
<div style="margin-bottom:12px;">
<div class="section-wrapper expanded" data-section-wrap="error" style="margin-bottom:12px;${rules.length === 0 ? 'display:none;' : ''}">
<div class="section-header" onclick="toggleSection('${sectionId}')">
<span style="font-size:14px;">🚫</span>
<span class="section-title">${t('import.sectionInvalid')}${rules.length}</span>
<span class="section-title" data-section-title="error"></span>
<span class="section-arrow">▼</span>
</div>
<div id="${sectionId}">
${rules.map(renderErrorCard).join('')}
<div id="${sectionId}" data-section="error" style="display:block;">
${rules.map(r => renderRuleCard(r, true)).join('')}
</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, '')}`;
function renderSection(title: string, icon: string, key: string, rules: ImportableRule[]): string {
const sectionId = `section-${key}`;
const count = rules.length;
const show = rules.some(r => keepRule[r.id] !== undefined);
return `
<div style="margin-bottom:12px;">
<div class="section-wrapper${show ? ' expanded' : ''}" data-section-wrap="${key}" style="margin-bottom:12px;${count === 0 ? 'display:none;' : ''}">
<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>
<span class="section-title" data-section-title="${key}"></span>
<span class="section-arrow"></span>
</div>
<div id="${sectionId}" style="display:${show ? 'block' : 'none'};">
${rules.map(renderRuleCard).join('')}
<div id="${sectionId}" data-section="${key}" style="display:${show ? 'block' : 'none'};">
${rules.map(r => renderRuleCard(r)).join('')}
</div>
</div>
`;
@@ -265,6 +408,10 @@ body {
font-size: 12px; background: rgba(139,92,246,0.1);
border: 1px solid rgba(139,92,246,0.3); color: #a78bfa;
}
.summary-bar.warn {
border-color: rgba(210,153,34,0.3); color: #d29922;
background: rgba(210,153,34,0.1);
}
.actions {
display: flex; gap: 8px; padding-top: 12px;
border-top: 1px solid var(--vscode-panel-border);
@@ -284,7 +431,13 @@ body {
cursor: pointer; padding: 4px 0;
}
.section-title { font-weight: 600; font-size: 13px; }
.section-arrow { font-size: 10px; color: var(--vscode-descriptionForeground); }
.section-arrow {
display: inline-block;
font-size: 10px;
color: var(--vscode-descriptionForeground);
transform: rotate(-90deg);
transition: transform 0.15s ease;
}
.rule-card {
border: 1px solid var(--vscode-panel-border);
border-radius: 8px; margin-bottom: 8px; overflow: hidden;
@@ -324,7 +477,17 @@ body {
.rule-card-meta {
display: flex; align-items: center; gap: 8px; flex-shrink: 0;
}
.expand-icon { font-size: 10px; color: var(--vscode-descriptionForeground); }
.expand-icon {
display: inline-block;
font-size: 10px;
color: var(--vscode-descriptionForeground);
transform: rotate(-90deg);
transition: transform 0.15s ease;
}
.rule-card.expanded .expand-icon,
.section-wrapper.expanded .section-arrow {
transform: rotate(0deg);
}
.badge {
padding: 1px 8px; border-radius: 10px; font-size: 11px; font-weight: 600;
}
@@ -339,7 +502,10 @@ body {
padding: 10px 0 8px;
}
.id-row {
display: flex; align-items: center; gap: 8px;
display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
}
.id-row .field-error-msg {
flex-basis: 100%; margin-left: 0;
}
.keep-toggle { display: flex; gap: 4px; }
.toggle-btn {
@@ -353,6 +519,20 @@ body {
.toggle-btn.active[data-action="comment"] {
background: rgba(248,81,73,0.15); color: #f48771; border-color: rgba(248,81,73,0.3);
}
.add-btn {
padding: 4px 14px; border-radius: 4px; cursor: pointer; font-size: 11px;
border: 1px solid rgba(35,134,54,0.4);
background: rgba(35,134,54,0.15); color: #3fb950;
}
.add-btn:hover { background: rgba(35,134,54,0.25); }
.add-btn:disabled { opacity: 0.6; cursor: not-allowed; }
.field-error-input {
border-color: rgba(248,81,73,0.7) !important;
box-shadow: 0 0 0 1px rgba(248,81,73,0.25);
}
.field-error-msg {
color: #f48771; font-size: 11px; margin-top: 4px;
}
.edit-field { margin-top: 10px; }
.edit-field label {
display: block; font-size: 11px; font-weight: 600;
@@ -420,6 +600,26 @@ body {
background: rgba(248,81,73,0.15); color: #f48771;
border: 1px solid rgba(248,81,73,0.3); font-size: 12px;
}
.dup-banner {
border-radius: 6px; padding: 8px 12px; margin-top: 10px;
font-size: 12px; line-height: 1.6;
}
.dup-exact {
background: rgba(248,81,73,0.1);
border: 1px solid rgba(248,81,73,0.3);
border-left: 3px solid #f48771;
}
.dup-overlap {
background: rgba(210,153,34,0.1);
border: 1px solid rgba(210,153,34,0.3);
border-left: 3px solid #d29922;
}
.dup-banner-title { font-weight: 600; }
.dup-exact .dup-banner-title { color: #f48771; }
.dup-overlap .dup-banner-title { color: #d29922; }
.dup-banner-desc { color: var(--vscode-foreground); }
.dup-banner-reason { color: #d29922; }
.dup-banner-hint { color: var(--vscode-descriptionForeground); }
</style>
</head>
<body>
@@ -428,40 +628,73 @@ body {
<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 class="summary-item" style="border-color:rgba(248,81,73,0.3);color:#f48771;">${t('import.exactDuplicate', { 0: `<span id="count-exact">${exactRules.length}</span>` })}</div>
<div class="summary-item" style="border-color:rgba(210,153,34,0.3);color:#d29922;">${t('import.overlapDuplicate', { 0: `<span id="count-overlap">${overlapRules.length}</span>` })}</div>
<div class="summary-item" style="border-color:rgba(35,134,54,0.3);color:#3fb950;">${t('import.noDuplicate', { 0: `<span id="count-none">${noneRules.length}</span>` })}</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="addHint" class="summary-bar warn" style="display:none;"></div>
<div id="validationError" class="validation-error" style="display:none;"></div>
${skippedHint}
${renderErrorSection(errorRules)}
${renderSection(t('import.sectionExact'), '⛔', exactRules, false)}
${renderSection(t('import.sectionOverlap'), '⚠️', overlapRules, true)}
${renderSection(t('import.sectionNone'), '✅', noneRules, false)}
${renderSection(t('import.sectionExact'), '⛔', 'exact', exactRules)}
${renderSection(t('import.sectionOverlap'), '⚠️', 'overlap', overlapRules)}
${renderSection(t('import.sectionNone'), '✅', 'none', noneRules)}
${emptyValidHint}
<div id="emptyValidHint" class="validation-error" style="display:none;">${t('import.emptyValidRules')}</div>
<div class="actions">
<button class="btn" onclick="cancel()">${t('importPreview.cancel')}</button>
<button class="btn btn-primary" ${confirmBtnAttrs}>${t('importPreview.confirm')}</button>
<button class="btn btn-primary" id="confirmBtn" onclick="doConfirm()">${t('importPreview.confirm')}</button>
</div>
<script>
const vscode = acquireVsCodeApi();
const editedRules = {};
let addedRules = 0;
let addingRuleId = null;
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'))};
const ADD_TEXT = ${JSON.stringify(t('import.add'))};
const ADDING_TEXT = ${JSON.stringify(t('import.adding'))};
const KEEP_TEXT = ${JSON.stringify(t('importPreview.keep'))};
const COMMENT_TEXT = ${JSON.stringify(t('importPreview.comment'))};
const WILL_COMMENT_TEXT = ${JSON.stringify(t('import.badgeWillComment'))};
const DEDUP_FALLBACK_TEXT = ${JSON.stringify(t('import.addDedupFallback'))};
const CUSTOM_PREFIX = ${JSON.stringify(t('import.customRulePrefix'))};
const DUPLICATE_OF_TEXT = ${JSON.stringify(t('import.duplicateOf'))};
const OVERLAP_WITH_TEXT = ${JSON.stringify(t('import.overlapWith'))};
const OVERLAP_REASON_TEXT = ${JSON.stringify(t('import.overlapReason'))};
const RULE_COUNT_TEMPLATE = ${JSON.stringify(t('import.ruleCount'))};
const SECTION_TITLES = {
error: ${JSON.stringify(t('import.sectionInvalid'))},
exact: ${JSON.stringify(t('import.sectionExact'))},
overlap: ${JSON.stringify(t('import.sectionOverlap'))},
none: ${JSON.stringify(t('import.sectionNone'))},
};
function syncId(originalId, newValue) {
const card = document.querySelector('.rule-card[data-ruleid="' + originalId + '"]');
function setSectionTitle(key, count) {
const el = document.querySelector('[data-section-title="' + key + '"]');
if (el) {
el.textContent = SECTION_TITLES[key] + '' + RULE_COUNT_TEMPLATE.replace('{0}', count) + '';
}
}
function fmt(tpl, v) {
return tpl.replace('{0}', v);
}
function syncId(el) {
const card = el.closest('.rule-card');
if (!card) return;
const originalId = card.dataset.ruleid;
const newValue = el.value;
const headerInput = card.querySelector('.rule-id-input');
const panelInput = card.querySelector('.id-display-input');
if (headerInput) headerInput.value = newValue;
@@ -472,48 +705,52 @@ function syncId(originalId, newValue) {
updateEditHint();
const isPlaceholder = /^rule-\\d+$/.test(newValue);
[headerInput, panelInput].forEach(el => {
if (el) {
el.classList.toggle('placeholder-id', isPlaceholder);
[headerInput, panelInput].forEach(input => {
if (input) {
input.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');
function toggleCard(el) {
const card = el.closest('.rule-card');
if (!card) return;
const body = card.querySelector('.rule-card-body');
if (body.style.display === 'none') {
body.style.display = 'block';
icon.textContent = '▲';
card.classList.add('expanded');
} else {
body.style.display = 'none';
icon.textContent = '▼';
card.classList.remove('expanded');
}
}
function toggleSection(id) {
const el = document.getElementById(id);
const arrow = el.previousElementSibling.querySelector('.section-arrow');
const wrap = el.closest('.section-wrapper');
if (el.style.display === 'none') {
el.style.display = 'block';
arrow.textContent = '▼';
wrap.classList.add('expanded');
} else {
el.style.display = 'none';
arrow.textContent = '▶';
wrap.classList.remove('expanded');
}
}
function toggleKeep(ruleId, keep) {
vscode.postMessage({ type: 'toggleRule', ruleId, keep });
const card = document.querySelector('.rule-card[data-ruleid="' + ruleId + '"]');
function toggleKeep(el, keep) {
const card = el.closest('.rule-card');
if (!card) return;
const ruleId = card.dataset.ruleid;
vscode.postMessage({ type: 'toggleRule', ruleId, keep });
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) {
function updateRule(el, field, value) {
const card = el.closest('.rule-card');
if (!card) return;
const ruleId = card.dataset.ruleid;
if (!editedRules[ruleId]) {
editedRules[ruleId] = {};
}
@@ -521,34 +758,288 @@ function updateRule(ruleId, field, value) {
updateEditHint();
}
function collectCardRule(card) {
if (!card) return null;
const idInput = card.querySelector('.id-display-input');
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"]');
return {
id: idInput ? idInput.value.trim() : card.dataset.ruleid,
severity: severityEl ? severityEl.value : 'warning',
description: descEl ? descEl.value : '',
message: msgEl ? msgEl.value : '',
languages: langList ? Array.from(langList.querySelectorAll('.tag')).map(tag => tag.dataset.value) : [],
excludeLanguages: exclList ? Array.from(exclList.querySelectorAll('.tag')).map(tag => tag.dataset.value) : [],
};
}
function collectEditedRules() {
const result = [];
document.querySelectorAll('.rule-card').forEach(card => {
if (card.hasAttribute('data-error')) { return; }
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);
const rule = collectCardRule(card);
if (rule) { result.push(rule); }
});
return result;
}
function addErrorRule(el) {
if (addingRuleId) { return; }
const card = el.closest('.rule-card');
if (!card) return;
const btn = el;
if (btn.disabled) { return; }
btn.disabled = true;
btn.textContent = ADDING_TEXT;
const ruleId = card.dataset.ruleid;
addingRuleId = ruleId;
const rule = collectCardRule(card);
if (!rule) {
addingRuleId = null;
btn.disabled = false;
btn.textContent = ADD_TEXT;
return;
}
vscode.postMessage({ type: 'addErrorRule', ruleId, rule });
}
function fieldElement(card, field) {
if (field === 'id') return card.querySelector('.id-display-input');
if (field === 'severity') return card.querySelector('.edit-field select');
const tas = card.querySelectorAll('.edit-field textarea');
return field === 'description' ? (tas[0] || null) : (tas[1] || null);
}
function setFieldError(card, field, message) {
const el = fieldElement(card, field);
if (!el) return;
el.classList.add('field-error-input');
const wrap = el.closest('.edit-field, .id-row');
if (!wrap) return;
wrap.classList.add('field-error');
let msg = wrap.querySelector('.field-error-msg');
if (!msg) {
msg = document.createElement('div');
msg.className = 'field-error-msg';
wrap.appendChild(msg);
}
msg.textContent = '⚠ ' + message;
}
function clearFieldError(card, field) {
const el = fieldElement(card, field);
if (!el) return;
el.classList.remove('field-error-input');
const wrap = el.closest('.edit-field, .id-row');
if (wrap) {
wrap.classList.remove('field-error');
const msg = wrap.querySelector('.field-error-msg');
if (msg) { msg.remove(); }
}
}
function clearCardFieldErrors(card) {
card.querySelectorAll('.field-error-input').forEach(function (el) {
el.classList.remove('field-error-input');
});
card.querySelectorAll('.field-error').forEach(function (wrap) {
wrap.classList.remove('field-error');
const msg = wrap.querySelector('.field-error-msg');
if (msg) { msg.remove(); }
});
}
function liveClear(event) {
const card = event.target.closest('.rule-card');
if (!card || !card.hasAttribute('data-error')) return;
const target = event.target;
if (target.classList.contains('id-display-input') || target.classList.contains('rule-id-input')) {
if (target.value.trim()) { clearFieldError(card, 'id'); }
} else if (target.tagName === 'SELECT') {
clearFieldError(card, 'severity');
} else if (target.tagName === 'TEXTAREA') {
const tas = card.querySelectorAll('.edit-field textarea');
const field = tas[0] === target ? 'description' : (tas[1] === target ? 'message' : null);
if (field && target.value.trim()) { clearFieldError(card, field); }
}
}
function showCardError(ruleId, field, message) {
const card = document.querySelector('.rule-card[data-ruleid="' + ruleId + '"]');
if (card && field) {
setFieldError(card, field, message);
}
const btn = document.querySelector('[data-addbtn="' + ruleId + '"]');
if (btn) {
btn.disabled = false;
btn.textContent = ADD_TEXT;
}
addingRuleId = null;
}
function dupInfoHtml(level, dupOf, reason) {
const prefix = dupOf && dupOf.startsWith('custom/')
? CUSTOM_PREFIX + ' ' + dupOf.slice(7)
: (dupOf || 'unknown');
if (level === 'exact') {
return '<div style="color:#8b949e;font-size:12px;margin-top:4px;">' + fmt(DUPLICATE_OF_TEXT, prefix) + '</div>';
}
if (level === 'overlap') {
let html = '<div style="color:#d29922;font-size:12px;margin-top:4px;">' + fmt(OVERLAP_WITH_TEXT, prefix) + '</div>';
if (reason) {
html += '<div style="color:#8b949e;font-size:12px;margin-top:2px;">' + fmt(OVERLAP_REASON_TEXT, reason) + '</div>';
}
return html;
}
return '';
}
function moveCardToSection(msg) {
const card = document.querySelector('.rule-card[data-ruleid="' + msg.ruleId + '"]');
if (!card) return;
card.dataset.ruleid = msg.id;
const headerInput = card.querySelector('.rule-id-input');
const panelInput = card.querySelector('.id-display-input');
if (headerInput) headerInput.value = msg.id;
if (panelInput) panelInput.value = msg.id;
const isPlaceholder = /^rule-\\d+$/.test(msg.id);
[headerInput, panelInput].forEach(el => {
if (el) el.classList.toggle('placeholder-id', isPlaceholder);
});
card.removeAttribute('data-error');
clearCardFieldErrors(card);
const addBtn = card.querySelector('.add-btn');
if (addBtn) { addBtn.remove(); }
const kept = msg.duplicateLevel !== 'exact';
const toggle = document.createElement('div');
toggle.className = 'keep-toggle';
function makeToggleBtn(action, active, label) {
const btn = document.createElement('button');
btn.className = 'toggle-btn' + (active ? ' active' : '');
btn.dataset.action = action;
btn.textContent = label;
btn.addEventListener('click', function (ev) {
ev.stopPropagation();
toggleKeep(this, action === 'keep');
});
return btn;
}
toggle.appendChild(makeToggleBtn('keep', kept, KEEP_TEXT));
toggle.appendChild(makeToggleBtn('comment', !kept, COMMENT_TEXT));
card.querySelector('.edit-header').appendChild(toggle);
const meta = card.querySelector('.rule-card-meta');
const oldBadge = meta.querySelector('.badge');
if (oldBadge) { oldBadge.remove(); }
const badge = document.createElement('span');
badge.className = 'badge';
if (msg.duplicateLevel === 'exact') {
badge.classList.add('badge-exact');
badge.textContent = WILL_COMMENT_TEXT;
} else if (msg.duplicateLevel === 'overlap') {
badge.classList.add('badge-overlap');
badge.textContent = KEEP_TEXT;
} else {
badge.classList.add('badge-none');
badge.textContent = KEEP_TEXT;
}
const icon = meta.querySelector('.expand-icon');
meta.insertBefore(badge, icon);
const body = card.querySelector('.rule-card-body');
const infoHtml = dupInfoHtml(msg.duplicateLevel, msg.duplicateOf, msg.duplicateReason);
if (infoHtml) {
const infoDiv = document.createElement('div');
infoDiv.innerHTML = infoHtml;
const firstField = body.querySelector('.edit-field');
body.insertBefore(infoDiv, firstField);
}
const section = msg.duplicateLevel === 'exact'
? 'exact'
: (msg.duplicateLevel === 'overlap' ? 'overlap' : 'none');
const target = document.querySelector('[data-section="' + section + '"]');
if (target) {
const wrap = target.closest('[data-section-wrap]');
if (wrap) {
wrap.style.display = '';
wrap.classList.add('expanded');
}
target.style.display = 'block';
target.appendChild(card);
}
addedRules++;
if (msg.dedupFailed) {
showAddHint(DEDUP_FALLBACK_TEXT);
}
updateSectionCounts();
updateSummary();
updateEditHint();
}
function showAddHint(text) {
const el = document.getElementById('addHint');
el.textContent = text;
el.style.display = 'block';
setTimeout(function () { el.style.display = 'none'; }, 5000);
}
function updateSectionCounts() {
const sections = ['error', 'exact', 'overlap', 'none'];
const counts = {};
let totalValid = 0;
for (const key of sections) {
const container = document.querySelector('[data-section="' + key + '"]');
const count = container ? container.querySelectorAll('.rule-card').length : 0;
counts[key] = count;
if (key !== 'error') { totalValid += count; }
setSectionTitle(key, count);
if (container) {
const wrap = container.closest('[data-section-wrap]');
if (wrap) { wrap.style.display = count > 0 ? '' : 'none'; }
}
}
document.getElementById('count-exact').textContent = counts.exact;
document.getElementById('count-overlap').textContent = counts.overlap;
document.getElementById('count-none').textContent = counts.none;
const btn = document.getElementById('confirmBtn');
const hint = document.getElementById('emptyValidHint');
if (totalValid === 0) {
btn.disabled = true;
btn.style.opacity = '0.5';
btn.style.cursor = 'not-allowed';
hint.style.display = 'block';
} else {
btn.disabled = false;
btn.style.opacity = '';
btn.style.cursor = '';
hint.style.display = 'none';
}
}
window.addEventListener('message', function (e) {
const msg = e.data;
if (!msg) { return; }
if (msg.type === 'addError') {
showCardError(msg.ruleId, msg.field, msg.message);
} else if (msg.type === 'ruleAdded') {
moveCardToSection(msg);
}
});
function validate() {
const rules = collectEditedRules();
for (const rule of rules) {
@@ -575,7 +1066,8 @@ function doConfirm() {
}
const edited = collectEditedRules();
const hasEdits = Object.keys(editedRules).length > 0;
vscode.postMessage({ type: 'confirm', editedRules: hasEdits ? edited : undefined });
const withData = (hasEdits || addedRules > 0) ? edited : undefined;
vscode.postMessage({ type: 'confirm', editedRules: withData });
}
function cancel() {
@@ -586,7 +1078,6 @@ function updateSummary() {
let keepCount = 0, commentCount = 0;
document.querySelectorAll('.rule-card').forEach(card => {
if (card.hasAttribute('data-error')) { return; }
const ruleId = card.dataset.ruleid;
const keepBtns = card.querySelectorAll('.toggle-btn');
let isKept = true;
keepBtns.forEach(b => {
@@ -621,7 +1112,7 @@ document.addEventListener('keydown', function(e) {
const list = input.parentElement.querySelector('.tag-list');
const existing = list.querySelectorAll('.tag');
const exists = Array.from(existing).some(t => t.dataset.value === val);
const exists = Array.from(existing).some(tag => tag.dataset.value === val);
if (exists) { input.value = ''; return; }
const tag = document.createElement('span');
@@ -631,7 +1122,7 @@ document.addEventListener('keydown', function(e) {
list.appendChild(tag);
input.value = '';
const tags = Array.from(list.querySelectorAll('.tag')).map(t => t.dataset.value);
const tags = Array.from(list.querySelectorAll('.tag')).map(tag => tag.dataset.value);
if (!editedRules[ruleId]) editedRules[ruleId] = {};
editedRules[ruleId][field] = tags;
updateEditHint();
@@ -645,12 +1136,17 @@ document.addEventListener('click', function(e) {
const ruleId = list.dataset.ruleid;
const field = list.dataset.field;
tag.remove();
const remaining = Array.from(list.querySelectorAll('.tag')).map(t => t.dataset.value);
const remaining = Array.from(list.querySelectorAll('.tag')).map(tag => tag.dataset.value);
if (!editedRules[ruleId]) editedRules[ruleId] = {};
editedRules[ruleId][field] = remaining;
updateEditHint();
}
});
document.addEventListener('input', liveClear);
document.addEventListener('change', liveClear);
updateSectionCounts();
</script>
</body>
</html>`;
+63 -10
View File
@@ -24,6 +24,11 @@ interface ParsedYamlItem {
[key: string]: unknown;
}
function stripQuotes(raw: string): string {
const m = raw.match(/^(['"])(.*)\1$/);
return m ? m[2] : raw;
}
function parseSimpleYaml(content: string): ParsedYamlItem[] {
const items: ParsedYamlItem[] = [];
let current: ParsedYamlItem | null = null;
@@ -44,7 +49,7 @@ function parseSimpleYaml(content: string): ParsedYamlItem[] {
s.trim().replace(/^['"]|['"]$/g, '')
);
} else {
current[key] = raw;
current[key] = stripQuotes(raw);
}
}
} else if (current) {
@@ -59,7 +64,7 @@ function parseSimpleYaml(content: string): ParsedYamlItem[] {
s.trim().replace(/^['"]|['"]$/g, '')
);
} else {
current[key] = raw;
current[key] = stripQuotes(raw);
}
}
}
@@ -90,6 +95,45 @@ export function parseImportableYaml(content: string): ImportableRule[] {
}));
}
export interface DedupResult {
duplicateLevel: 'exact' | 'overlap' | 'none';
duplicateOf?: string;
duplicateReason?: string;
}
export async function dedupSingleRule(
rule: ImportableRule,
context: vscode.ExtensionContext,
): Promise<DedupResult | null> {
const workspaceRoot = vscode.workspace.workspaceFolders?.[0]?.uri.fsPath;
const existingRules = workspaceRoot ? loadActiveRules(workspaceRoot) : [];
const singleYaml = [
`- id: ${rule.id}`,
` severity: ${rule.severity}`,
` description: ${rule.description}`,
` message: ${rule.message}`,
...(rule.languages?.length ? [` languages: [${rule.languages.join(', ')}]`] : []),
...(rule.excludeLanguages?.length ? [` excludeLanguages: [${rule.excludeLanguages.join(', ')}]`] : []),
].join('\n');
const { system, user } = buildDedupOnlyPrompt(singleYaml, existingRules);
for (let attempt = 0; attempt < 2; attempt++) {
const out = await convertContentWithAI(user, context, system, true);
if (!out) { continue; }
const parsed = parseImportableYaml(out);
if (parsed.length === 0) { continue; }
const r = parsed[0];
return {
duplicateLevel: r.duplicateLevel ?? 'none',
duplicateOf: r.duplicateOf,
duplicateReason: r.duplicateReason,
};
}
return null;
}
export function buildFinalYaml(
yamlContent: string,
rules: ImportableRule[],
@@ -405,15 +449,20 @@ export async function convertContentWithAI(
content: string,
context: vscode.ExtensionContext,
systemPrompt?: string,
quiet?: boolean,
): Promise<string | null> {
if (!content.trim()) {
vscode.window.showErrorMessage(t('import.emptyFile'));
if (!quiet) {
vscode.window.showErrorMessage(t('import.emptyFile'));
}
return null;
}
const apiKey = await getApiKey(context);
if (!apiKey) {
vscode.window.showErrorMessage(t('import.needApiKey'));
if (!quiet) {
vscode.window.showErrorMessage(t('import.needApiKey'));
}
return null;
}
@@ -432,11 +481,13 @@ export async function convertContentWithAI(
seed: 42,
});
} 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 }));
if (!quiet) {
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;
}
@@ -447,7 +498,9 @@ export async function convertContentWithAI(
.trim();
if (!cleaned) {
vscode.window.showErrorMessage(t('import.emptyResponse'));
if (!quiet) {
vscode.window.showErrorMessage(t('import.emptyResponse'));
}
return null;
}
+2
View File
@@ -12,6 +12,8 @@ export interface ImportableRule extends CustomRule {
duplicateReason?: string;
validationIssues?: ValidationIssue[];
rowNumber?: number;
originalSeverity?: string;
idPlaceholder?: boolean;
}
export interface ConversionResult {
+1766 -597
View File
@@ -5,2045 +5,3214 @@
"ts-eslint": "8.x (35 rules)",
"stylelint": "16.x (68 rules)",
"pmd": "7.26.0 (274 Java rules + 12 JSP rules)",
"sql-lint": "4.2.2 (57 recommended)"
"sqlfluff": "4.2.2 (57 recommended)"
},
"rules": {
"eslint": [
{
"id": "eslint/constructor-super",
"description": "Verify calls of super() in constructors"
"description": "Verify calls of super() in constructors",
"descriptionZh": "在构造函数中校验 super() 的调用",
"descriptionJa": "コンストラクタで super() の呼び出しを検証する"
},
{
"id": "eslint/for-direction",
"description": "Enforce for loop update clause moving the counter in the right direction"
"description": "Enforce for loop update clause moving the counter in the right direction",
"descriptionZh": "确保 for 循环更新子句朝正确方向移动计数器",
"descriptionJa": "for ループの更新句がカウンタを正しい方向に進めることを強制する"
},
{
"id": "eslint/getter-return",
"description": "Enforce return statements in getters"
"description": "Enforce return statements in getters",
"descriptionZh": "强制 getter 中有 return 语句",
"descriptionJa": "getter に return 文を強制する"
},
{
"id": "eslint/no-async-promise-executor",
"description": "Disallow using an async function as a Promise executor"
"description": "Disallow using an async function as a Promise executor",
"descriptionZh": "禁止使用 async 函数作为 Promise 执行器",
"descriptionJa": "async 関数を Promise のエグゼキュータとして使用しない"
},
{
"id": "eslint/no-case-declarations",
"description": "Disallow lexical declarations in case clauses"
"description": "Disallow lexical declarations in case clauses",
"descriptionZh": "禁止在 case 子句中声明词法变量",
"descriptionJa": "case 節での語彙宣言を禁止する"
},
{
"id": "eslint/no-class-assign",
"description": "Disallow reassigning class members"
"description": "Disallow reassigning class members",
"descriptionZh": "禁止重新赋值类成员",
"descriptionJa": "クラスメンバーへの再代入を禁止する"
},
{
"id": "eslint/no-compare-neg-zero",
"description": "Disallow comparing against -0"
"description": "Disallow comparing against -0",
"descriptionZh": "禁止与 -0 进行比较",
"descriptionJa": "-0 との比較を禁止する"
},
{
"id": "eslint/no-cond-assign",
"description": "Disallow assignment operators in conditional expressions"
"description": "Disallow assignment operators in conditional expressions",
"descriptionZh": "禁止在条件表达式中使用赋值运算符",
"descriptionJa": "条件式での代入演算子を禁止する"
},
{
"id": "eslint/no-const-assign",
"description": "Disallow reassigning const variables"
"description": "Disallow reassigning const variables",
"descriptionZh": "禁止重新赋值 const 变量",
"descriptionJa": "const 変数への再代入を禁止する"
},
{
"id": "eslint/no-constant-binary-expression",
"description": "Disallow constant binary expressions"
"description": "Disallow constant binary expressions",
"descriptionZh": "禁止常量二元表达式",
"descriptionJa": "定数の二項式を禁止する"
},
{
"id": "eslint/no-constant-condition",
"description": "Disallow constant expressions in conditions"
"description": "Disallow constant expressions in conditions",
"descriptionZh": "禁止在条件中使用常量表达式",
"descriptionJa": "条件での定数式を禁止する"
},
{
"id": "eslint/no-control-regex",
"description": "Disallow control characters in regular expressions"
"description": "Disallow control characters in regular expressions",
"descriptionZh": "禁止正则表达式中的控制字符",
"descriptionJa": "正規表現内の制御文字を禁止する"
},
{
"id": "eslint/no-debugger",
"description": "Disallow the use of debugger"
"description": "Disallow the use of debugger",
"descriptionZh": "禁止使用 debugger 语句",
"descriptionJa": "debugger 文の使用を禁止する"
},
{
"id": "eslint/no-delete-var",
"description": "Disallow deleting variables"
"description": "Disallow deleting variables",
"descriptionZh": "禁止删除变量",
"descriptionJa": "変数の削除を禁止する"
},
{
"id": "eslint/no-dupe-args",
"description": "Disallow duplicate arguments in function definitions"
"description": "Disallow duplicate arguments in function definitions",
"descriptionZh": "禁止函数定义中重复的参数",
"descriptionJa": "関数定義内の重複引数を禁止する"
},
{
"id": "eslint/no-dupe-class-members",
"description": "Disallow duplicate class members"
"description": "Disallow duplicate class members",
"descriptionZh": "禁止重复的类成员",
"descriptionJa": "重複するクラスメンバーを禁止する"
},
{
"id": "eslint/no-dupe-else-if",
"description": "Disallow duplicate conditions in if-else-if chains"
"description": "Disallow duplicate conditions in if-else-if chains",
"descriptionZh": "禁止 if-else-if 链中的重复条件",
"descriptionJa": "if-else-if チェーン内の重複条件を禁止する"
},
{
"id": "eslint/no-dupe-keys",
"description": "Disallow duplicate keys in object literals"
"description": "Disallow duplicate keys in object literals",
"descriptionZh": "禁止对象字面量中重复的键",
"descriptionJa": "オブジェクトリテラル内の重複キーを禁止する"
},
{
"id": "eslint/no-duplicate-case",
"description": "Disallow duplicate case labels"
"description": "Disallow duplicate case labels",
"descriptionZh": "禁止重复的 case 标签",
"descriptionJa": "重複する case ラベルを禁止する"
},
{
"id": "eslint/no-empty",
"description": "Disallow empty block statements"
"description": "Disallow empty block statements",
"descriptionZh": "禁止空块语句",
"descriptionJa": "空のブロック文を禁止する"
},
{
"id": "eslint/no-empty-character-class",
"description": "Disallow empty character classes in regular expressions"
"description": "Disallow empty character classes in regular expressions",
"descriptionZh": "禁止正则表达式中的空字符类",
"descriptionJa": "正規表現内の空の文字クラスを禁止する"
},
{
"id": "eslint/no-empty-pattern",
"description": "Disallow empty destructuring patterns"
"description": "Disallow empty destructuring patterns",
"descriptionZh": "禁止空解构模式",
"descriptionJa": "空の分割代入パターンを禁止する"
},
{
"id": "eslint/no-empty-static-block",
"description": "Disallow empty static blocks"
"description": "Disallow empty static blocks",
"descriptionZh": "禁止空的静态块",
"descriptionJa": "空の静的ブロックを禁止する"
},
{
"id": "eslint/no-ex-assign",
"description": "Disallow reassigning exceptions in catch clauses"
"description": "Disallow reassigning exceptions in catch clauses",
"descriptionZh": "禁止在 catch 子句中重新赋值异常",
"descriptionJa": "catch 句での例外への再代入を禁止する"
},
{
"id": "eslint/no-extra-boolean-cast",
"description": "Disallow unnecessary boolean casts"
"description": "Disallow unnecessary boolean casts",
"descriptionZh": "禁止不必要的布尔转换",
"descriptionJa": "不要なブール変換を禁止する"
},
{
"id": "eslint/no-fallthrough",
"description": "Disallow fallthrough of case statements"
"description": "Disallow fallthrough of case statements",
"descriptionZh": "禁止 case 语句的 fallthrough",
"descriptionJa": "case 文のフォールスルーを禁止する"
},
{
"id": "eslint/no-func-assign",
"description": "Disallow reassigning function declarations"
"description": "Disallow reassigning function declarations",
"descriptionZh": "禁止重新赋值函数声明",
"descriptionJa": "関数宣言への再代入を禁止する"
},
{
"id": "eslint/no-global-assign",
"description": "Disallow assignments to native objects or read-only global variables"
"description": "Disallow assignments to native objects or read-only global variables",
"descriptionZh": "禁止对原生对象或只读全局变量赋值",
"descriptionJa": "ネイティブオブジェクトや読み取り専用グローバルへの代入を禁止する"
},
{
"id": "eslint/no-import-assign",
"description": "Disallow assigning to imported bindings"
"description": "Disallow assigning to imported bindings",
"descriptionZh": "禁止对导入的绑定赋值",
"descriptionJa": "インポートされたバインディングへの代入を禁止する"
},
{
"id": "eslint/no-invalid-regexp",
"description": "Disallow invalid regular expression strings in RegExp constructors"
"description": "Disallow invalid regular expression strings in RegExp constructors",
"descriptionZh": "禁止 RegExp 构造函数中的无效正则字符串",
"descriptionJa": "RegExp コンストラクタ内の不正な正規表現文字列を禁止する"
},
{
"id": "eslint/no-irregular-whitespace",
"description": "Disallow irregular whitespace"
"description": "Disallow irregular whitespace",
"descriptionZh": "禁止不规则空白",
"descriptionJa": "不規則な空白を禁止する"
},
{
"id": "eslint/no-loss-of-precision",
"description": "Disallow literal numbers that lose precision"
"description": "Disallow literal numbers that lose precision",
"descriptionZh": "禁止会丢失精度的字面数字",
"descriptionJa": "精度を失うリテラル数値を禁止する"
},
{
"id": "eslint/no-misleading-character-class",
"description": "Disallow characters which are made with multiple code points in character class syntax"
"description": "Disallow characters which are made with multiple code points in character class syntax",
"descriptionZh": "禁止字符类语法中使用多个码点构成的字符",
"descriptionJa": "文字クラス構文で複数のコードポイントからなる文字を禁止する"
},
{
"id": "eslint/no-new-native-nonconstructor",
"description": "Disallow new operators with global non-constructor functions"
"description": "Disallow new operators with global non-constructor functions",
"descriptionZh": "禁止对全局非构造函数使用 new",
"descriptionJa": "グローバルな非コンストラクタ関数への new を禁止する"
},
{
"id": "eslint/no-nonoctal-decimal-escape",
"description": "Disallow \\8 and \\9 escape sequences in string literals"
"description": "Disallow \\8 and \\9 escape sequences in string literals",
"descriptionZh": "禁止字符串字面量中的 \\8 和 \\9 转义序列",
"descriptionJa": "文字列リテラル内の \\8 と \\9 のエスケープシーケンスを禁止する"
},
{
"id": "eslint/no-obj-calls",
"description": "Disallow calling global object properties as functions"
"description": "Disallow calling global object properties as functions",
"descriptionZh": "禁止将全局对象属性作为函数调用",
"descriptionJa": "グローバルオブジェクトのプロパティを関数として呼び出すことを禁止する"
},
{
"id": "eslint/no-octal",
"description": "Disallow octal literals"
"description": "Disallow octal literals",
"descriptionZh": "禁止八进制字面量",
"descriptionJa": "8進数リテラルを禁止する"
},
{
"id": "eslint/no-prototype-builtins",
"description": "Disallow calling some Object.prototype methods directly on objects"
"description": "Disallow calling some Object.prototype methods directly on objects",
"descriptionZh": "禁止直接在对象上调用某些 Object.prototype 方法",
"descriptionJa": "オブジェクトで Object.prototype の一部メソッドを直接呼ぶことを禁止する"
},
{
"id": "eslint/no-redeclare",
"description": "Disallow variable redeclaration"
"description": "Disallow variable redeclaration",
"descriptionZh": "禁止变量重新声明",
"descriptionJa": "変数の再宣言を禁止する"
},
{
"id": "eslint/no-regex-spaces",
"description": "Disallow multiple spaces in regular expression literals"
"description": "Disallow multiple spaces in regular expression literals",
"descriptionZh": "禁止正则表达式字面量中的多个空格",
"descriptionJa": "正規表現リテラル内の複数スペースを禁止する"
},
{
"id": "eslint/no-self-assign",
"description": "Disallow assignments where both sides are exactly the same"
"description": "Disallow assignments where both sides are exactly the same",
"descriptionZh": "禁止两侧完全相同的赋值",
"descriptionJa": "両辺が完全に同一の代入を禁止する"
},
{
"id": "eslint/no-setter-return",
"description": "Disallow returning values from setters"
"description": "Disallow returning values from setters",
"descriptionZh": "禁止 setter 返回值",
"descriptionJa": "setter からの戻り値を禁止する"
},
{
"id": "eslint/no-shadow-restricted-names",
"description": "Disallow identifiers from shadowing restricted names"
"description": "Disallow identifiers from shadowing restricted names",
"descriptionZh": "禁止标识符遮蔽受限名称",
"descriptionJa": "予約名を遮蔽する識別子を禁止する"
},
{
"id": "eslint/no-sparse-arrays",
"description": "Disallow sparse arrays"
"description": "Disallow sparse arrays",
"descriptionZh": "禁止稀疏数组",
"descriptionJa": "疎配列を禁止する"
},
{
"id": "eslint/no-this-before-super",
"description": "Disallow this/super before calling super() in constructors"
"description": "Disallow this/super before calling super() in constructors",
"descriptionZh": "禁止在构造函数中调用 super() 之前使用 this/super",
"descriptionJa": "コンストラクタで super() 呼び出し前の this/super 使用を禁止する"
},
{
"id": "eslint/no-undef",
"description": "Disallow undeclared variables"
"description": "Disallow undeclared variables",
"descriptionZh": "禁止使用未声明的变量",
"descriptionJa": "未宣言の変数の使用を禁止する"
},
{
"id": "eslint/no-unexpected-multiline",
"description": "Disallow confusing multiline expressions"
"description": "Disallow confusing multiline expressions",
"descriptionZh": "禁止令人困惑的多行表达式",
"descriptionJa": "紛らわしい複数行式を禁止する"
},
{
"id": "eslint/no-unreachable",
"description": "Disallow unreachable code after return, throw, continue, and break statements"
"description": "Disallow unreachable code after return, throw, continue, and break statements",
"descriptionZh": "禁止 return/throw/continue/break 之后不可达的代码",
"descriptionJa": "return/throw/continue/break 後の到達不能コードを禁止する"
},
{
"id": "eslint/no-unsafe-finally",
"description": "Disallow control flow statements in finally blocks"
"description": "Disallow control flow statements in finally blocks",
"descriptionZh": "禁止 finally 块中的控制流语句",
"descriptionJa": "finally ブロック内の制御フロー文を禁止する"
},
{
"id": "eslint/no-unsafe-negation",
"description": "Disallow negating the left operand of relational operators"
"description": "Disallow negating the left operand of relational operators",
"descriptionZh": "禁止对关系运算符左操作数取反",
"descriptionJa": "関係演算子の左オペランドの否定を禁止する"
},
{
"id": "eslint/no-unsafe-optional-chaining",
"description": "Disallow use of optional chaining in contexts where undefined is not allowed"
"description": "Disallow use of optional chaining in contexts where undefined is not allowed",
"descriptionZh": "禁止在 undefined 不允许的上下文中使用可选链",
"descriptionJa": "undefined が許されない文脈でのオプショナルチェーンを禁止する"
},
{
"id": "eslint/no-unused-labels",
"description": "Disallow unused labels"
"description": "Disallow unused labels",
"descriptionZh": "禁止未使用的标签",
"descriptionJa": "未使用のラベルを禁止する"
},
{
"id": "eslint/no-unused-private-class-members",
"description": "Disallow unused private class members"
"description": "Disallow unused private class members",
"descriptionZh": "禁止未使用的私有类成员",
"descriptionJa": "未使用のプライベートクラスメンバーを禁止する"
},
{
"id": "eslint/no-unused-vars",
"description": "Disallow unused variables"
"description": "Disallow unused variables",
"descriptionZh": "禁止未使用的变量",
"descriptionJa": "未使用の変数を禁止する"
},
{
"id": "eslint/no-useless-backreference",
"description": "Disallow useless backreferences in regular expressions"
"description": "Disallow useless backreferences in regular expressions",
"descriptionZh": "禁止正则表达式中无用的反向引用",
"descriptionJa": "正規表現内の無用な後方参照を禁止する"
},
{
"id": "eslint/no-useless-catch",
"description": "Disallow unnecessary catch clauses"
"description": "Disallow unnecessary catch clauses",
"descriptionZh": "禁止不必要的 catch 子句",
"descriptionJa": "不要な catch 句を禁止する"
},
{
"id": "eslint/no-useless-escape",
"description": "Disallow unnecessary escape characters"
"description": "Disallow unnecessary escape characters",
"descriptionZh": "禁止不必要的转义字符",
"descriptionJa": "不要なエスケープ文字を禁止する"
},
{
"id": "eslint/no-with",
"description": "Disallow with statements"
"description": "Disallow with statements",
"descriptionZh": "禁止 with 语句",
"descriptionJa": "with 文を禁止する"
},
{
"id": "eslint/require-yield",
"description": "Require generator functions to contain yield"
"description": "Require generator functions to contain yield",
"descriptionZh": "要求生成器函数包含 yield",
"descriptionJa": "ジェネレータ関数に yield を含めることを要求する"
},
{
"id": "eslint/use-isnan",
"description": "Require calls to isNaN() when checking for NaN"
"description": "Require calls to isNaN() when checking for NaN",
"descriptionZh": "检查 NaN 时要求调用 isNaN()",
"descriptionJa": "NaN のチェック時に isNaN() の呼び出しを要求する"
},
{
"id": "eslint/valid-typeof",
"description": "Enforce comparing typeof expressions against valid strings"
"description": "Enforce comparing typeof expressions against valid strings",
"descriptionZh": "强制 typeof 表达式与有效字符串比较",
"descriptionJa": "typeof 式と有効な文字列の比較を強制する"
},
{"id": "eslint/eqeqeq", "description": "Require === and !=="},
{"id": "eslint/no-eq-null", "description": "Disallow null comparisons without type-checking"},
{"id": "eslint/no-self-compare", "description": "Disallow comparisons where both sides are the same"},
{"id": "eslint/no-await-in-loop", "description": "Disallow await inside loops"},
{"id": "eslint/no-promise-executor-return", "description": "Disallow returning values from Promise executor"},
{"id": "eslint/no-shadow", "description": "Disallow variable declarations from shadowing variables in outer scopes"},
{"id": "eslint/no-unassigned-vars", "description": "Disallow let or var variables that are read but never assigned"},
{"id": "eslint/no-useless-assignment", "description": "Disallow variable assignments where the value is not used"},
{"id": "eslint/block-scoped-var", "description": "Enforce variables within the scope they are defined"},
{"id": "eslint/default-case", "description": "Require default cases in switch statements"},
{"id": "eslint/default-case-last", "description": "Enforce default clauses in switch statements to be last"},
{"id": "eslint/no-unmodified-loop-condition", "description": "Disallow unmodified loop conditions"},
{"id": "eslint/no-unreachable-loop", "description": "Disallow loops with a body that allows only one iteration"},
{"id": "eslint/no-eval", "description": "Disallow the use of eval()"},
{"id": "eslint/no-extend-native", "description": "Disallow extending native types"},
{"id": "eslint/no-var", "description": "Require let or const instead of var"},
{"id": "eslint/prefer-template", "description": "Require template literals instead of string concatenation"},
{"id": "eslint/prefer-object-spread", "description": "Disallow Object.assign and prefer object spread"},
{"id": "eslint/prefer-rest-params", "description": "Require rest parameters instead of arguments"},
{"id": "eslint/prefer-spread", "description": "Require spread operator instead of .apply()"},
{"id": "eslint/prefer-object-has-own", "description": "Disallow Object.prototype.hasOwnProperty.call() and prefer Object.hasOwn()"},
{"id": "eslint/no-useless-concat", "description": "Disallow unnecessary concatenation of literals or template literals"},
{"id": "eslint/no-useless-return", "description": "Disallow redundant return statements"},
{"id": "eslint/no-useless-computed-key", "description": "Disallow unnecessary computed property keys in objects and classes"},
{"id": "eslint/no-useless-rename", "description": "Disallow renaming import, export, and destructured assignments to the same name"},
{"id": "eslint/no-param-reassign", "description": "Disallow reassigning function parameters"},
{"id": "eslint/no-return-assign", "description": "Disallow assignment operators in return statements"},
{"id": "eslint/no-throw-literal", "description": "Disallow throwing literals as exceptions"},
{"id": "eslint/camelcase", "description": "Enforce camelcase naming convention"},
{"id": "eslint/new-cap", "description": "Require constructor names to begin with a capital letter"},
{"id": "eslint/no-array-constructor", "description": "Disallow Array constructors"}
{
"id": "eslint/eqeqeq",
"description": "Require === and !==",
"descriptionZh": "要求使用 === 和 !==",
"descriptionJa": "=== と !== の使用を要求する"
},
{
"id": "eslint/no-eq-null",
"description": "Disallow null comparisons without type-checking",
"descriptionZh": "禁止无类型检查的 null 比较",
"descriptionJa": "型チェックなしの null 比較を禁止する"
},
{
"id": "eslint/no-self-compare",
"description": "Disallow comparisons where both sides are the same",
"descriptionZh": "禁止两侧相同的比较",
"descriptionJa": "両辺が同一の比較を禁止する"
},
{
"id": "eslint/no-await-in-loop",
"description": "Disallow await inside loops",
"descriptionZh": "禁止在循环内使用 await",
"descriptionJa": "ループ内での await を禁止する"
},
{
"id": "eslint/no-promise-executor-return",
"description": "Disallow returning values from Promise executor",
"descriptionZh": "禁止 Promise 执行器返回值",
"descriptionJa": "Promise エグゼキュータからの戻り値を禁止する"
},
{
"id": "eslint/no-shadow",
"description": "Disallow variable declarations from shadowing variables in outer scopes",
"descriptionZh": "禁止变量声明遮蔽外层作用域中的变量",
"descriptionJa": "外側スコープの変数を遮蔽する宣言を禁止する"
},
{
"id": "eslint/no-unassigned-vars",
"description": "Disallow let or var variables that are read but never assigned",
"descriptionZh": "禁止只读但从未赋值的 let/var 变量",
"descriptionJa": "読み取られるが代入されない let/var 変数を禁止する"
},
{
"id": "eslint/no-useless-assignment",
"description": "Disallow variable assignments where the value is not used",
"descriptionZh": "禁止值未被使用的变量赋值",
"descriptionJa": "値が使われない変数への代入を禁止する"
},
{
"id": "eslint/block-scoped-var",
"description": "Enforce variables within the scope they are defined",
"descriptionZh": "强制变量在其定义的作用域内使用",
"descriptionJa": "変数を定義されたスコープ内で使用することを強制する"
},
{
"id": "eslint/default-case",
"description": "Require default cases in switch statements",
"descriptionZh": "要求 switch 语句有 default 子句",
"descriptionJa": "switch 文に default 句を要求する"
},
{
"id": "eslint/default-case-last",
"description": "Enforce default clauses in switch statements to be last",
"descriptionZh": "强制 switch 语句中 default 子句在最后",
"descriptionJa": "switch 文で default 句を最後にすることを強制する"
},
{
"id": "eslint/no-unmodified-loop-condition",
"description": "Disallow unmodified loop conditions",
"descriptionZh": "禁止未修改的循环条件",
"descriptionJa": "変更されないループ条件を禁止する"
},
{
"id": "eslint/no-unreachable-loop",
"description": "Disallow loops with a body that allows only one iteration",
"descriptionZh": "禁止只允许一次迭代的循环体",
"descriptionJa": "一度しか反復できないループを禁止する"
},
{
"id": "eslint/no-eval",
"description": "Disallow the use of eval()",
"descriptionZh": "禁止使用 eval()",
"descriptionJa": "eval() の使用を禁止する"
},
{
"id": "eslint/no-extend-native",
"description": "Disallow extending native types",
"descriptionZh": "禁止扩展原生类型",
"descriptionJa": "ネイティブ型の拡張を禁止する"
},
{
"id": "eslint/no-var",
"description": "Require let or const instead of var",
"descriptionZh": "要求使用 let 或 const 替代 var",
"descriptionJa": "var の代わりに let または const を要求する"
},
{
"id": "eslint/prefer-template",
"description": "Require template literals instead of string concatenation",
"descriptionZh": "要求使用模板字面量替代字符串拼接",
"descriptionJa": "文字列連結の代わりにテンプレートリテラルを要求する"
},
{
"id": "eslint/prefer-object-spread",
"description": "Disallow Object.assign and prefer object spread",
"descriptionZh": "禁止 Object.assign,优先使用对象展开",
"descriptionJa": "Object.assign を禁止しオブジェクト展開を推奨する"
},
{
"id": "eslint/prefer-rest-params",
"description": "Require rest parameters instead of arguments",
"descriptionZh": "要求使用剩余参数替代 arguments",
"descriptionJa": "arguments の代わりに残余引数を要求する"
},
{
"id": "eslint/prefer-spread",
"description": "Require spread operator instead of .apply()",
"descriptionZh": "要求使用展开运算符替代 .apply()",
"descriptionJa": ".apply() の代わりにスプレッド演算子を要求する"
},
{
"id": "eslint/prefer-object-has-own",
"description": "Disallow Object.prototype.hasOwnProperty.call() and prefer Object.hasOwn()",
"descriptionZh": "禁止 Object.prototype.hasOwnProperty.call(),优先使用 Object.hasOwn()",
"descriptionJa": "Object.prototype.hasOwnProperty.call() を禁止し Object.hasOwn() を推奨する"
},
{
"id": "eslint/no-useless-concat",
"description": "Disallow unnecessary concatenation of literals or template literals",
"descriptionZh": "禁止不必要的字面量或模板字面量拼接",
"descriptionJa": "不要なリテラルやテンプレートリテラルの連結を禁止する"
},
{
"id": "eslint/no-useless-return",
"description": "Disallow redundant return statements",
"descriptionZh": "禁止冗余的 return 语句",
"descriptionJa": "冗長な return 文を禁止する"
},
{
"id": "eslint/no-useless-computed-key",
"description": "Disallow unnecessary computed property keys in objects and classes",
"descriptionZh": "禁止对象和类中不必要的计算属性键",
"descriptionJa": "オブジェクトとクラス内の不要な算出プロパティキーを禁止する"
},
{
"id": "eslint/no-useless-rename",
"description": "Disallow renaming import, export, and destructured assignments to the same name",
"descriptionZh": "禁止将导入、导出和解构赋值重命名为相同名称",
"descriptionJa": "インポート・エクスポート・分割代入を同名に改名することを禁止する"
},
{
"id": "eslint/no-param-reassign",
"description": "Disallow reassigning function parameters",
"descriptionZh": "禁止重新赋值函数参数",
"descriptionJa": "関数パラメータへの再代入を禁止する"
},
{
"id": "eslint/no-return-assign",
"description": "Disallow assignment operators in return statements",
"descriptionZh": "禁止在 return 语句中使用赋值运算符",
"descriptionJa": "return 文での代入演算子を禁止する"
},
{
"id": "eslint/no-throw-literal",
"description": "Disallow throwing literals as exceptions",
"descriptionZh": "禁止将字面量作为异常抛出",
"descriptionJa": "リテラルを例外として投げることを禁止する"
},
{
"id": "eslint/camelcase",
"description": "Enforce camelcase naming convention",
"descriptionZh": "强制使用 camelCase 命名规范",
"descriptionJa": "camelCase 命名規則を強制する"
},
{
"id": "eslint/new-cap",
"description": "Require constructor names to begin with a capital letter",
"descriptionZh": "要求构造函数名以大写字母开头",
"descriptionJa": "コンストラクタ名を大文字で始めることを要求する"
},
{
"id": "eslint/no-array-constructor",
"description": "Disallow Array constructors",
"descriptionZh": "禁止使用 Array 构造函数",
"descriptionJa": "Array コンストラクタを禁止する"
}
],
"ts-eslint": [
{
"id": "ts-eslint/ban-ts-comment",
"description": "Disallow @ts-<directive> comments"
"description": "Disallow @ts-<directive> comments",
"descriptionZh": "禁止使用 @ts-<指令> 注释",
"descriptionJa": "@ts-<ディレクティブ> コメントを禁止する"
},
{
"id": "ts-eslint/no-array-constructor",
"description": "Disallow generic Array constructors"
"description": "Disallow generic Array constructors",
"descriptionZh": "禁止泛型 Array 构造函数",
"descriptionJa": "ジェネリック Array コンストラクタを禁止する"
},
{
"id": "ts-eslint/no-duplicate-enum-values",
"description": "Disallow duplicate enum member values"
"description": "Disallow duplicate enum member values",
"descriptionZh": "禁止重复的枚举成员值",
"descriptionJa": "重複する列挙メンバー値を禁止する"
},
{
"id": "ts-eslint/no-empty-object-type",
"description": "Disallow empty object types"
"description": "Disallow empty object types",
"descriptionZh": "禁止空对象类型",
"descriptionJa": "空のオブジェクト型を禁止する"
},
{
"id": "ts-eslint/no-explicit-any",
"description": "Disallow the any type"
"description": "Disallow the any type",
"descriptionZh": "禁止使用 any 类型",
"descriptionJa": "any 型の使用を禁止する"
},
{
"id": "ts-eslint/no-extra-non-null-assertion",
"description": "Disallow extra non-null assertions"
"description": "Disallow extra non-null assertions",
"descriptionZh": "禁止多余的非空断言",
"descriptionJa": "余分な非nullアサーションを禁止する"
},
{
"id": "ts-eslint/no-misused-new",
"description": "Enforce valid definition of new and constructor"
"description": "Enforce valid definition of new and constructor",
"descriptionZh": "强制 new 和 constructor 的有效定义",
"descriptionJa": "new とコンストラクタの有効な定義を強制する"
},
{
"id": "ts-eslint/no-namespace",
"description": "Disallow custom TypeScript modules and namespaces"
"description": "Disallow custom TypeScript modules and namespaces",
"descriptionZh": "禁止自定义 TypeScript 模块和命名空间",
"descriptionJa": "カスタム TypeScript モジュールと名前空間を禁止する"
},
{
"id": "ts-eslint/no-non-null-asserted-optional-chain",
"description": "Disallow non-null assertions after optional chain"
"description": "Disallow non-null assertions after optional chain",
"descriptionZh": "禁止可选链之后的非空断言",
"descriptionJa": "オプショナルチェーン後の非nullアサーションを禁止する"
},
{
"id": "ts-eslint/no-require-imports",
"description": "Disallow invocation of require()"
"description": "Disallow invocation of require()",
"descriptionZh": "禁止调用 require()",
"descriptionJa": "require() の呼び出しを禁止する"
},
{
"id": "ts-eslint/no-this-alias",
"description": "Disallow aliasing this"
"description": "Disallow aliasing this",
"descriptionZh": "禁止为 this 创建别名",
"descriptionJa": "this のエイリアス作成を禁止する"
},
{
"id": "ts-eslint/no-unnecessary-type-constraint",
"description": "Disallow unnecessary constraints on generic types"
"description": "Disallow unnecessary constraints on generic types",
"descriptionZh": "禁止泛型类型上不必要的约束",
"descriptionJa": "ジェネリック型の不要な制約を禁止する"
},
{
"id": "ts-eslint/no-unsafe-declaration-merging",
"description": "Disallow unsafe declaration merging"
"description": "Disallow unsafe declaration merging",
"descriptionZh": "禁止不安全的声明合并",
"descriptionJa": "安全でない宣言のマージを禁止する"
},
{
"id": "ts-eslint/no-unsafe-function-type",
"description": "Disallow using Function as a type"
"description": "Disallow using Function as a type",
"descriptionZh": "禁止使用 Function 作为类型",
"descriptionJa": "Function を型として使用することを禁止する"
},
{
"id": "ts-eslint/no-unused-expressions",
"description": "Disallow unused expressions"
"description": "Disallow unused expressions",
"descriptionZh": "禁止未使用的表达式",
"descriptionJa": "未使用の式を禁止する"
},
{
"id": "ts-eslint/no-unused-vars",
"description": "Disallow unused variables"
"description": "Disallow unused variables",
"descriptionZh": "禁止未使用的变量",
"descriptionJa": "未使用の変数を禁止する"
},
{
"id": "ts-eslint/no-wrapper-object-types",
"description": "Disallow wrapper object types (String, Number, Boolean)"
"description": "Disallow wrapper object types (String, Number, Boolean)",
"descriptionZh": "禁止包装对象类型(String、Number、Boolean",
"descriptionJa": "ラッパーオブジェクト型(String、Number、Boolean)を禁止する"
},
{
"id": "ts-eslint/prefer-as-const",
"description": "Prefer as const over literal type annotation"
"description": "Prefer as const over literal type annotation",
"descriptionZh": "优先使用 as const 而非字面量类型注解",
"descriptionJa": "リテラル型注釈より as const を推奨する"
},
{
"id": "ts-eslint/prefer-namespace-keyword",
"description": "Require using namespace keyword over module keyword"
"description": "Require using namespace keyword over module keyword",
"descriptionZh": "要求使用 namespace 关键字替代 module",
"descriptionJa": "module キーワードより namespace の使用を要求する"
},
{
"id": "ts-eslint/triple-slash-reference",
"description": "Disallow certain triple slash directives"
"description": "Disallow certain triple slash directives",
"descriptionZh": "禁止某些三斜线指令",
"descriptionJa": "特定のトリプルスラッシュディレクティブを禁止する"
},
{
"id": "ts-eslint/no-var",
"description": "Require let or const instead of var"
"description": "Require let or const instead of var",
"descriptionZh": "要求使用 let 或 const 替代 var",
"descriptionJa": "var の代わりに let または const を要求する"
},
{
"id": "ts-eslint/prefer-const",
"description": "Require const declarations for never-reassigned variables"
"description": "Require const declarations for never-reassigned variables",
"descriptionZh": "对从未重新赋值的变量要求使用 const",
"descriptionJa": "再代入されない変数に const を要求する"
},
{
"id": "ts-eslint/prefer-rest-params",
"description": "Require rest parameters instead of arguments"
"description": "Require rest parameters instead of arguments",
"descriptionZh": "要求使用剩余参数替代 arguments",
"descriptionJa": "arguments の代わりに残余引数を要求する"
},
{
"id": "ts-eslint/prefer-spread",
"description": "Require spread operator instead of .apply()"
"description": "Require spread operator instead of .apply()",
"descriptionZh": "要求使用展开运算符替代 .apply()",
"descriptionJa": ".apply() の代わりにスプレッド演算子を要求する"
},
{"id": "ts-eslint/no-non-null-assertion", "description": "Disallow non-null assertions using the ! postfix operator"},
{"id": "ts-eslint/no-dynamic-delete", "description": "Disallow using the delete operator on computed key expressions"},
{"id": "ts-eslint/no-useless-empty-export", "description": "Disallow empty exports that don't change anything in a module"},
{"id": "ts-eslint/consistent-type-imports", "description": "Enforce consistent usage of type imports"},
{"id": "ts-eslint/unified-signatures", "description": "Disallow two overloads that could be unified into a single signature"},
{"id": "ts-eslint/no-extraneous-class", "description": "Disallow classes only being used as namespaces"},
{"id": "ts-eslint/no-useless-constructor", "description": "Disallow unnecessary constructors"},
{"id": "ts-eslint/no-non-null-asserted-nullish-coalescing", "description": "Disallow non-null assertions in the left operand of a nullish coalescing operator"},
{"id": "ts-eslint/no-invalid-void-type", "description": "Disallow void type outside of generic or return types"},
{"id": "ts-eslint/prefer-literal-enum-member", "description": "Require all enum members to be literal values"},
{"id": "ts-eslint/prefer-enum-initializers", "description": "Require each enum member value to be explicitly initialized"},
{"id": "ts-eslint/no-shadow", "description": "Disallow variable declarations from shadowing variables declared in the outer scope"}
{
"id": "ts-eslint/no-non-null-assertion",
"description": "Disallow non-null assertions using the ! postfix operator",
"descriptionZh": "禁止使用 ! 后缀运算符进行非空断言",
"descriptionJa": "! 接尾辞演算子による非nullアサーションを禁止する"
},
{
"id": "ts-eslint/no-dynamic-delete",
"description": "Disallow using the delete operator on computed key expressions",
"descriptionZh": "禁止对计算键表达式使用 delete 运算符",
"descriptionJa": "算出キー式への delete 演算子の使用を禁止する"
},
{
"id": "ts-eslint/no-useless-empty-export",
"description": "Disallow empty exports that don't change anything in a module",
"descriptionZh": "禁止不改变模块内容的空导出",
"descriptionJa": "モジュールに変更を加えない空のエクスポートを禁止する"
},
{
"id": "ts-eslint/consistent-type-imports",
"description": "Enforce consistent usage of type imports",
"descriptionZh": "强制类型导入的一致用法",
"descriptionJa": "型インポートの一貫した使用を強制する"
},
{
"id": "ts-eslint/unified-signatures",
"description": "Disallow two overloads that could be unified into a single signature",
"descriptionZh": "禁止可合并为单一签名的两个重载",
"descriptionJa": "単一のシグネチャに統合できる2つのオーバーロードを禁止する"
},
{
"id": "ts-eslint/no-extraneous-class",
"description": "Disallow classes only being used as namespaces",
"descriptionZh": "禁止仅用作命名空间的类",
"descriptionJa": "名前空間としてのみ使用されるクラスを禁止する"
},
{
"id": "ts-eslint/no-useless-constructor",
"description": "Disallow unnecessary constructors",
"descriptionZh": "禁止不必要的构造函数",
"descriptionJa": "不要なコンストラクタを禁止する"
},
{
"id": "ts-eslint/no-non-null-asserted-nullish-coalescing",
"description": "Disallow non-null assertions in the left operand of a nullish coalescing operator",
"descriptionZh": "禁止空值合并运算符左操作数中的非空断言",
"descriptionJa": "null合体演算子の左オペランドでの非nullアサーションを禁止する"
},
{
"id": "ts-eslint/no-invalid-void-type",
"description": "Disallow void type outside of generic or return types",
"descriptionZh": "禁止泛型或返回类型之外的 void 类型",
"descriptionJa": "ジェネリックまたは戻り値型以外での void 型を禁止する"
},
{
"id": "ts-eslint/prefer-literal-enum-member",
"description": "Require all enum members to be literal values",
"descriptionZh": "要求所有枚举成员为字面量值",
"descriptionJa": "すべての列挙メンバーにリテラル値を要求する"
},
{
"id": "ts-eslint/prefer-enum-initializers",
"description": "Require each enum member value to be explicitly initialized",
"descriptionZh": "要求每个枚举成员值被显式初始化",
"descriptionJa": "各列挙メンバー値の明示的な初期化を要求する"
},
{
"id": "ts-eslint/no-shadow",
"description": "Disallow variable declarations from shadowing variables declared in the outer scope",
"descriptionZh": "禁止变量声明遮蔽外层作用域中声明的变量",
"descriptionJa": "外側スコープで宣言された変数を遮蔽する宣言を禁止する"
}
],
"stylelint": [
{
"id": "stylelint/color-hex-length",
"description": "Specify short or long hexadecimal color values"
"description": "Specify short or long hexadecimal color values",
"descriptionZh": "指定十六进制颜色值的短或长格式",
"descriptionJa": "16進カラー値の短い形式または長い形式を指定する"
},
{
"id": "stylelint/color-named",
"description": "Require (where possible) or disallow named colors"
"description": "Require (where possible) or disallow named colors",
"descriptionZh": "要求(尽可能)或禁止使用命名颜色",
"descriptionJa": "名前付きカラーの使用を(可能な限り)要求または禁止する"
},
{
"id": "stylelint/color-no-invalid-hex",
"description": "Disallow invalid hex colors"
"description": "Disallow invalid hex colors",
"descriptionZh": "禁止无效的十六进制颜色",
"descriptionJa": "無効な16進カラーを禁止する"
},
{
"id": "stylelint/length-zero-no-unit",
"description": "Disallow units for zero lengths"
"description": "Disallow units for zero lengths",
"descriptionZh": "禁止零长度带单位",
"descriptionJa": "ゼロ長に単位を付けることを禁止する"
},
{
"id": "stylelint/font-family-no-missing-generic-family-keyword",
"description": "Disallow missing generic families in font-family"
"description": "Disallow missing generic families in font-family",
"descriptionZh": "禁止 font-family 中缺少通用字体族",
"descriptionJa": "font-family での汎用ファミリーキーワードの欠落を禁止する"
},
{
"id": "stylelint/block-no-empty",
"description": "Disallow empty blocks"
"description": "Disallow empty blocks",
"descriptionZh": "禁止空块",
"descriptionJa": "空のブロックを禁止する"
},
{
"id": "stylelint/declaration-block-no-duplicate-properties",
"description": "Disallow duplicate properties within declaration blocks"
"description": "Disallow duplicate properties within declaration blocks",
"descriptionZh": "禁止声明块内重复的属性",
"descriptionJa": "宣言ブロック内の重複プロパティを禁止する"
},
{
"id": "stylelint/no-descending-specificity",
"description": "Disallow selectors of lower specificity from overriding higher specificity"
"description": "Disallow selectors of lower specificity from overriding higher specificity",
"descriptionZh": "禁止低特异性的选择器覆盖高特异性",
"descriptionJa": "低特異性のセレクタが高特異性を上書きすることを禁止する"
},
{
"id": "stylelint/unit-no-unknown",
"description": "Disallow unknown units"
"description": "Disallow unknown units",
"descriptionZh": "禁止未知单位",
"descriptionJa": "未知の単位を禁止する"
},
{
"id": "stylelint/property-no-unknown",
"description": "Disallow unknown properties"
"description": "Disallow unknown properties",
"descriptionZh": "禁止未知属性",
"descriptionJa": "未知のプロパティを禁止する"
},
{
"id": "stylelint/selector-pseudo-class-no-unknown",
"description": "Disallow unknown pseudo-class selectors"
"description": "Disallow unknown pseudo-class selectors",
"descriptionZh": "禁止未知的伪类选择器",
"descriptionJa": "未知の疑似クラスセレクタを禁止する"
},
{
"id": "stylelint/selector-pseudo-element-no-unknown",
"description": "Disallow unknown pseudo-element selectors"
"description": "Disallow unknown pseudo-element selectors",
"descriptionZh": "禁止未知的伪元素选择器",
"descriptionJa": "未知の疑似要素セレクタを禁止する"
},
{
"id": "stylelint/function-linear-gradient-no-nonstandard-direction",
"description": "Disallow non-standard directions in linear-gradient"
"description": "Disallow non-standard directions in linear-gradient",
"descriptionZh": "禁止 linear-gradient 中的非标准方向",
"descriptionJa": "linear-gradient 内の非標準方向を禁止する"
},
{
"id": "stylelint/function-no-unknown",
"description": "Disallow unknown functions"
"description": "Disallow unknown functions",
"descriptionZh": "禁止未知函数",
"descriptionJa": "未知の関数を禁止する"
},
{
"id": "stylelint/no-unknown-animations",
"description": "Disallow unknown animations"
"description": "Disallow unknown animations",
"descriptionZh": "禁止未知动画",
"descriptionJa": "未知のアニメーションを禁止する"
},
{
"id": "stylelint/no-unknown-custom-media",
"description": "Disallow unknown custom media queries"
"description": "Disallow unknown custom media queries",
"descriptionZh": "禁止未知的自定义媒体查询",
"descriptionJa": "未知のカスタムメディアクエリを禁止する"
},
{
"id": "stylelint/no-unknown-custom-properties",
"description": "Disallow unknown custom properties"
"description": "Disallow unknown custom properties",
"descriptionZh": "禁止未知的自定义属性",
"descriptionJa": "未知のカスタムプロパティを禁止する"
},
{
"id": "stylelint/at-rule-no-vendor-prefix",
"description": "Disallow vendor prefixes for at-rules"
"description": "Disallow vendor prefixes for at-rules",
"descriptionZh": "禁止 at 规则使用厂商前缀",
"descriptionJa": "atルールへのベンダープレフィックスを禁止する"
},
{
"id": "stylelint/media-feature-name-no-vendor-prefix",
"description": "Disallow vendor prefixes for media feature names"
"description": "Disallow vendor prefixes for media feature names",
"descriptionZh": "禁止媒体特性名称使用厂商前缀",
"descriptionJa": "メディア特性名へのベンダープレフィックスを禁止する"
},
{
"id": "stylelint/property-no-vendor-prefix",
"description": "Disallow vendor prefixes for properties"
"description": "Disallow vendor prefixes for properties",
"descriptionZh": "禁止属性使用厂商前缀",
"descriptionJa": "プロパティへのベンダープレフィックスを禁止する"
},
{
"id": "stylelint/selector-no-vendor-prefix",
"description": "Disallow vendor prefixes for selectors"
"description": "Disallow vendor prefixes for selectors",
"descriptionZh": "禁止选择器使用厂商前缀",
"descriptionJa": "セレクタへのベンダープレフィックスを禁止する"
},
{
"id": "stylelint/value-no-vendor-prefix",
"description": "Disallow vendor prefixes for values"
"description": "Disallow vendor prefixes for values",
"descriptionZh": "禁止值使用厂商前缀",
"descriptionJa": "値へのベンダープレフィックスを禁止する"
},
{
"id": "stylelint/color-function-notation",
"description": "Require modern or legacy notation for color-functions"
"description": "Require modern or legacy notation for color-functions",
"descriptionZh": "要求颜色函数使用现代或传统记法",
"descriptionJa": "色関数に現代または従来の記法を要求する"
},
{
"id": "stylelint/selector-pseudo-element-colon-notation",
"description": "Use single or double colon notation for pseudo-elements"
"description": "Use single or double colon notation for pseudo-elements",
"descriptionZh": "伪元素使用单冒号或双冒号记法",
"descriptionJa": "疑似要素に単コロンまたは二重コロン記法を使用する"
},
{
"id": "stylelint/import-notation",
"description": "Require string or url notation for @import"
"description": "Require string or url notation for @import",
"descriptionZh": "要求 @import 使用字符串或 url 记法",
"descriptionJa": "@import に文字列または url 記法を要求する"
},
{
"id": "stylelint/alpha-value-notation",
"description": "Require percentage or number notation for alpha-values"
"description": "Require percentage or number notation for alpha-values",
"descriptionZh": "要求透明度值使用百分比或数字记法",
"descriptionJa": "アルファ値にパーセンテージまたは数値記法を要求する"
},
{
"id": "stylelint/hue-degree-notation",
"description": "Require number or angle notation for hue degrees"
"description": "Require number or angle notation for hue degrees",
"descriptionZh": "要求色相度数使用数字或角度记法",
"descriptionJa": "色相の度に数値または角度記法を要求する"
},
{
"id": "stylelint/keyframe-selector-notation",
"description": "Require keyword or percentage notation for keyframe selectors"
"description": "Require keyword or percentage notation for keyframe selectors",
"descriptionZh": "要求关键帧选择器使用关键字或百分比记法",
"descriptionJa": "キーフレームセレクタにキーワードまたはパーセンテージ記法を要求する"
},
{
"id": "stylelint/declaration-block-no-redundant-longhand-properties",
"description": "Disallow redundant longhand properties within declaration blocks"
"description": "Disallow redundant longhand properties within declaration blocks",
"descriptionZh": "禁止声明块中冗余的 longhand 属性",
"descriptionJa": "宣言ブロック内の冗長なロングハンドプロパティを禁止する"
},
{
"id": "stylelint/shorthand-property-no-redundant-values",
"description": "Disallow redundant values within shorthand properties"
"description": "Disallow redundant values within shorthand properties",
"descriptionZh": "禁止 shorthand 属性中的冗余值",
"descriptionJa": "shorthand プロパティ内の冗長な値を禁止する"
},
{
"id": "stylelint/block-no-redundant-nested-style-rules",
"description": "Disallow redundant nested style rules within blocks"
"description": "Disallow redundant nested style rules within blocks",
"descriptionZh": "禁止块内冗余的嵌套样式规则",
"descriptionJa": "ブロック内の冗長なネストスタイルルールを禁止する"
},
{
"id": "stylelint/font-family-name-quotes",
"description": "Require quotes for font-family names"
"description": "Require quotes for font-family names",
"descriptionZh": "要求 font-family 名称使用引号",
"descriptionJa": "font-family 名に引用符を要求する"
},
{
"id": "stylelint/number-max-precision",
"description": "Limit the number of decimal places in numbers"
"description": "Limit the number of decimal places in numbers",
"descriptionZh": "限制数字的小数位数",
"descriptionJa": "数値の小数桁数を制限する"
},
{
"id": "stylelint/comment-whitespace-inside",
"description": "Require or disallow whitespace inside comments"
"description": "Require or disallow whitespace inside comments",
"descriptionZh": "要求或禁止注释内部空白",
"descriptionJa": "コメント内の空白を要求または禁止する"
}
],
"pmd": [
{
"id": "pmd/AbstractClassWithoutAbstractMethod",
"description": "Abstract class does not contain any abstract methods"
"description": "Abstract class does not contain any abstract methods",
"descriptionZh": "抽象类不包含任何抽象方法",
"descriptionJa": "抽象クラスに抽象メソッドが含まれていない"
},
{
"id": "pmd/AccessorClassGeneration",
"description": "Avoid instantiation through private constructors from outside"
"description": "Avoid instantiation through private constructors from outside",
"descriptionZh": "避免从外部通过私有构造函数实例化",
"descriptionJa": "外部からプライベートコンストラクタでインスタンス化することを避ける"
},
{
"id": "pmd/AccessorMethodGeneration",
"description": "Avoid synthetic accessor methods"
"description": "Avoid synthetic accessor methods",
"descriptionZh": "避免合成访问器方法",
"descriptionJa": "合成アクセッサメソッドを避ける"
},
{
"id": "pmd/ArrayIsStoredDirectly",
"description": "Clone objects before storing in constructors/methods"
"description": "Clone objects before storing in constructors/methods",
"descriptionZh": "存储到构造函数/方法前应克隆对象",
"descriptionJa": "コンストラクタやメソッドに格納する前にオブジェクトをクローンする"
},
{
"id": "pmd/AssertStatementInTest",
"description": "Assert statements should not be used in test code"
"description": "Assert statements should not be used in test code",
"descriptionZh": "测试代码中不应使用断言语句",
"descriptionJa": "テストコードで assert 文を使用すべきでない"
},
{
"id": "pmd/AvoidMessageDigestField",
"description": "Don't declare MessageDigest as field (thread safety)"
"description": "Don't declare MessageDigest as field (thread safety)",
"descriptionZh": "不要将 MessageDigest 声明为字段(线程安全)",
"descriptionJa": "MessageDigest をフィールドとして宣言しない(スレッド安全性)"
},
{
"id": "pmd/AvoidPrintStackTrace",
"description": "Use logger instead of printStackTrace()"
"description": "Use logger instead of printStackTrace()",
"descriptionZh": "使用 logger 替代 printStackTrace()",
"descriptionJa": "printStackTrace() の代わりにロガーを使用する"
},
{
"id": "pmd/AvoidReassigningCatchVariables",
"description": "Don't reassign caught exception variables"
"description": "Don't reassign caught exception variables",
"descriptionZh": "不要重新赋值捕获的异常变量",
"descriptionJa": "捕捉した例外変数に再代入しない"
},
{
"id": "pmd/AvoidReassigningLoopVariables",
"description": "Don't reassign loop control variables"
"description": "Don't reassign loop control variables",
"descriptionZh": "不要重新赋值循环控制变量",
"descriptionJa": "ループ制御変数に再代入しない"
},
{
"id": "pmd/AvoidReassigningParameters",
"description": "Don't reassign method parameters"
"description": "Don't reassign method parameters",
"descriptionZh": "不要重新赋值方法参数",
"descriptionJa": "メソッドパラメータに再代入しない"
},
{
"id": "pmd/AvoidStringBufferField",
"description": "Avoid StringBuffer/StringBuilder as fields"
"description": "Avoid StringBuffer/StringBuilder as fields",
"descriptionZh": "避免将 StringBuffer/StringBuilder 用作字段",
"descriptionJa": "StringBuffer/StringBuilder をフィールドとして使うことを避ける"
},
{
"id": "pmd/AvoidUsingHardCodedIP",
"description": "Externalize IP addresses"
"description": "Externalize IP addresses",
"descriptionZh": "外部化 IP 地址",
"descriptionJa": "IP アドレスを外部化する"
},
{
"id": "pmd/CheckResultSet",
"description": "Always check navigation method return values of ResultSet"
"description": "Always check navigation method return values of ResultSet",
"descriptionZh": "始终检查 ResultSet 导航方法的返回值",
"descriptionJa": "ResultSet のナビゲーションメソッドの戻り値を常に確認する"
},
{
"id": "pmd/ConstantsInInterface",
"description": "Avoid constants in interfaces"
"description": "Avoid constants in interfaces",
"descriptionZh": "避免在接口中定义常量",
"descriptionJa": "インターフェースでの定数定義を避ける"
},
{
"id": "pmd/DefaultLabelNotLastInSwitch",
"description": "Default label should be last in switch"
"description": "Default label should be last in switch",
"descriptionZh": "switch 中 default 标签应放在最后",
"descriptionJa": "switch では default ラベルを最後に置く"
},
{
"id": "pmd/DoubleBraceInitialization",
"description": "Avoid double-brace initialization"
"description": "Avoid double-brace initialization",
"descriptionZh": "避免双花括号初始化",
"descriptionJa": "二重波括弧初期化を避ける"
},
{
"id": "pmd/EnumComparison",
"description": "Compare enums with == not equals()"
"description": "Compare enums with == not equals()",
"descriptionZh": "使用 == 而非 equals() 比较枚举",
"descriptionJa": "列挙の比較には equals() ではなく == を使用する"
},
{
"id": "pmd/ExhaustiveSwitchHasDefault",
"description": "Exhaustive switch should not have default case"
"description": "Exhaustive switch should not have default case",
"descriptionZh": "穷尽式 switch 不应有 default 子句",
"descriptionJa": "網羅的な switch に default を置くべきでない"
},
{
"id": "pmd/ForLoopCanBeForeach",
"description": "Replace for loop with foreach"
"description": "Replace for loop with foreach",
"descriptionZh": "用 foreach 替代 for 循环",
"descriptionJa": "for ループを foreach に置き換える"
},
{
"id": "pmd/ForLoopVariableCount",
"description": "Limit control variables in for loops"
"description": "Limit control variables in for loops",
"descriptionZh": "限制 for 循环中的控制变量数量",
"descriptionJa": "for ループ内の制御変数の数を制限する"
},
{
"id": "pmd/GuardLogStatement",
"description": "Check log level before logging"
"description": "Check log level before logging",
"descriptionZh": "记录日志前检查日志级别",
"descriptionJa": "ログ出力前にログレベルを確認する"
},
{
"id": "pmd/ImplicitFunctionalInterface",
"description": "Annotate functional interfaces with @FunctionalInterface"
"description": "Annotate functional interfaces with @FunctionalInterface",
"descriptionZh": "用 @FunctionalInterface 注解函数式接口",
"descriptionJa": "関数型インターフェースに @FunctionalInterface を付ける"
},
{
"id": "pmd/JUnit4SuitesShouldUseSuiteAnnotation",
"description": "Use @RunWith(Suite.class) annotation"
"description": "Use @RunWith(Suite.class) annotation",
"descriptionZh": "使用 @RunWith(Suite.class) 注解",
"descriptionJa": "@RunWith(Suite.class) アノテーションを使用する"
},
{
"id": "pmd/JUnitJupiterTestShouldBePackagePrivate",
"description": "JUnit 5 tests should be package-private"
"description": "JUnit 5 tests should be package-private",
"descriptionZh": "JUnit 5 测试应为包私有",
"descriptionJa": "JUnit 5 のテストはパッケージプライベートにする"
},
{
"id": "pmd/JUnitUseExpected",
"description": "Use @Test(expected) annotation"
"description": "Use @Test(expected) annotation",
"descriptionZh": "使用 @Test(expected) 注解",
"descriptionJa": "@Test(expected) アノテーションを使用する"
},
{
"id": "pmd/LabeledStatement",
"description": "Avoid labeled statements"
"description": "Avoid labeled statements",
"descriptionZh": "避免带标签的语句",
"descriptionJa": "ラベル付き文を避ける"
},
{
"id": "pmd/LiteralsFirstInComparisons",
"description": "Position literals first in String comparisons"
"description": "Position literals first in String comparisons",
"descriptionZh": "字符串比较中将字面量放在前面",
"descriptionJa": "文字列比較ではリテラルを先頭に置く"
},
{
"id": "pmd/LooseCoupling",
"description": "Use interfaces instead of implementation types"
"description": "Use interfaces instead of implementation types",
"descriptionZh": "使用接口而非实现类型",
"descriptionJa": "実装型ではなくインターフェースを使用する"
},
{
"id": "pmd/MethodReturnsInternalArray",
"description": "Return copy of internal array"
"description": "Return copy of internal array",
"descriptionZh": "返回内部数组的副本",
"descriptionJa": "内部配列のコピーを返す"
},
{
"id": "pmd/MissingOverride",
"description": "Add @Override annotation"
"description": "Add @Override annotation",
"descriptionZh": "添加 @Override 注解",
"descriptionJa": "@Override アノテーションを追加する"
},
{
"id": "pmd/NonExhaustiveSwitch",
"description": "Switch should be exhaustive"
"description": "Switch should be exhaustive",
"descriptionZh": "switch 应为穷尽式",
"descriptionJa": "switch を網羅的にする"
},
{
"id": "pmd/OneDeclarationPerLine",
"description": "One declaration per line"
"description": "One declaration per line",
"descriptionZh": "每行一个声明",
"descriptionJa": "1行に1つの宣言"
},
{
"id": "pmd/PreserveStackTrace",
"description": "Preserve stack trace when rethrowing exceptions"
"description": "Preserve stack trace when rethrowing exceptions",
"descriptionZh": "重新抛出异常时保留堆栈跟踪",
"descriptionJa": "例外を再スローする際にスタックトレースを保持する"
},
{
"id": "pmd/PrimitiveWrapperInstantiation",
"description": "Use valueOf() instead of new Type()"
"description": "Use valueOf() instead of new Type()",
"descriptionZh": "使用 valueOf() 而非 new Type()",
"descriptionJa": "new Type() の代わりに valueOf() を使用する"
},
{
"id": "pmd/RelianceOnDefaultCharset",
"description": "Specify charset explicitly"
"description": "Specify charset explicitly",
"descriptionZh": "显式指定字符集",
"descriptionJa": "文字セットを明示的に指定する"
},
{
"id": "pmd/ReplaceEnumerationWithIterator",
"description": "Use Iterator instead of Enumeration"
"description": "Use Iterator instead of Enumeration",
"descriptionZh": "使用 Iterator 替代 Enumeration",
"descriptionJa": "Enumeration の代わりに Iterator を使用する"
},
{
"id": "pmd/ReplaceHashtableWithMap",
"description": "Use Map instead of Hashtable"
"description": "Use Map instead of Hashtable",
"descriptionZh": "使用 Map 替代 Hashtable",
"descriptionJa": "Hashtable の代わりに Map を使用する"
},
{
"id": "pmd/ReplaceVectorWithList",
"description": "Use List/ArrayList instead of Vector"
"description": "Use List/ArrayList instead of Vector",
"descriptionZh": "使用 List/ArrayList 替代 Vector",
"descriptionJa": "Vector の代わりに List/ArrayList を使用する"
},
{
"id": "pmd/ReturnEmptyCollectionRatherThanNull",
"description": "Return empty collection rather than null"
"description": "Return empty collection rather than null",
"descriptionZh": "返回空集合而非 null",
"descriptionJa": "null ではなく空のコレクションを返す"
},
{
"id": "pmd/SimplifiableTestAssertion",
"description": "Use more specific assertion methods"
"description": "Use more specific assertion methods",
"descriptionZh": "使用更具体的断言方法",
"descriptionJa": "より具体的なアサーションメソッドを使用する"
},
{
"id": "pmd/SystemPrintln",
"description": "Use logger instead of System.out/err"
"description": "Use logger instead of System.out/err",
"descriptionZh": "使用 logger 替代 System.out/err",
"descriptionJa": "System.out/err の代わりにロガーを使用する"
},
{
"id": "pmd/UnitTestAssertionsShouldIncludeMessage",
"description": "Include message in assertions"
"description": "Include message in assertions",
"descriptionZh": "断言中包含消息",
"descriptionJa": "アサーションにメッセージを含める"
},
{
"id": "pmd/UnitTestContainsTooManyAsserts",
"description": "Limit asserts per test"
"description": "Limit asserts per test",
"descriptionZh": "限制每个测试的断言数量",
"descriptionJa": "テストごとのアサーション数を制限する"
},
{
"id": "pmd/UnitTestShouldIncludeAssert",
"description": "Test should include assertions"
"description": "Test should include assertions",
"descriptionZh": "测试应包含断言",
"descriptionJa": "テストにアサーションを含めるべきである"
},
{
"id": "pmd/UnitTestShouldUseAfterAnnotation",
"description": "Use @After/@AfterEach annotation"
"description": "Use @After/@AfterEach annotation",
"descriptionZh": "使用 @After/@AfterEach 注解",
"descriptionJa": "@After/@AfterEach アノテーションを使用する"
},
{
"id": "pmd/UnitTestShouldUseBeforeAnnotation",
"description": "Use @Before/@BeforeEach annotation"
"description": "Use @Before/@BeforeEach annotation",
"descriptionZh": "使用 @Before/@BeforeEach 注解",
"descriptionJa": "@Before/@BeforeEach アノテーションを使用する"
},
{
"id": "pmd/UnitTestShouldUseTestAnnotation",
"description": "Use @Test annotation"
"description": "Use @Test annotation",
"descriptionZh": "使用 @Test 注解",
"descriptionJa": "@Test アノテーションを使用する"
},
{
"id": "pmd/UnnecessaryVarargsArrayCreation",
"description": "Don't create explicit array for varargs"
"description": "Don't create explicit array for varargs",
"descriptionZh": "不要为可变参数创建显式数组",
"descriptionJa": "可変長引数用に明示的な配列を作成しない"
},
{
"id": "pmd/UnnecessaryWarningSuppression",
"description": "Remove unused PMD suppressions"
"description": "Remove unused PMD suppressions",
"descriptionZh": "移除未使用的 PMD 抑制",
"descriptionJa": "未使用の PMD 抑制を削除する"
},
{
"id": "pmd/UnsynchronizedStaticFormatter",
"description": "Static formatter should be synchronized"
"description": "Static formatter should be synchronized",
"descriptionZh": "静态 formatter 应同步",
"descriptionJa": "静的フォーマッタは同期化すべきである"
},
{
"id": "pmd/UnusedAssignment",
"description": "Remove unused assignments"
"description": "Remove unused assignments",
"descriptionZh": "移除未使用的赋值",
"descriptionJa": "未使用の代入を削除する"
},
{
"id": "pmd/UnusedFormalParameter",
"description": "Remove unused parameters"
"description": "Remove unused parameters",
"descriptionZh": "移除未使用的参数",
"descriptionJa": "未使用のパラメータを削除する"
},
{
"id": "pmd/UnusedLabel",
"description": "Remove unused labels"
"description": "Remove unused labels",
"descriptionZh": "移除未使用的标签",
"descriptionJa": "未使用のラベルを削除する"
},
{
"id": "pmd/UnusedLocalVariable",
"description": "Remove unused local variables"
"description": "Remove unused local variables",
"descriptionZh": "移除未使用的局部变量",
"descriptionJa": "未使用のローカル変数を削除する"
},
{
"id": "pmd/UnusedPrivateField",
"description": "Remove unused private fields"
"description": "Remove unused private fields",
"descriptionZh": "移除未使用的私有字段",
"descriptionJa": "未使用のプライベートフィールドを削除する"
},
{
"id": "pmd/UnusedPrivateMethod",
"description": "Remove unused private methods"
"description": "Remove unused private methods",
"descriptionZh": "移除未使用的私有方法",
"descriptionJa": "未使用のプライベートメソッドを削除する"
},
{
"id": "pmd/UseCollectionIsEmpty",
"description": "Use isEmpty() instead of size()==0"
"description": "Use isEmpty() instead of size()==0",
"descriptionZh": "使用 isEmpty() 替代 size()==0",
"descriptionJa": "size()==0 の代わりに isEmpty() を使用する"
},
{
"id": "pmd/UseEnumCollections",
"description": "Use EnumSet/EnumMap instead of HashSet/HashMap"
"description": "Use EnumSet/EnumMap instead of HashSet/HashMap",
"descriptionZh": "使用 EnumSet/EnumMap 替代 HashSet/HashMap",
"descriptionJa": "HashSet/HashMap の代わりに EnumSet/EnumMap を使用する"
},
{
"id": "pmd/UseStandardCharsets",
"description": "Use StandardCharsets constants"
"description": "Use StandardCharsets constants",
"descriptionZh": "使用 StandardCharsets 常量",
"descriptionJa": "StandardCharsets 定数を使用する"
},
{
"id": "pmd/UseTryWithResources",
"description": "Use try-with-resources"
"description": "Use try-with-resources",
"descriptionZh": "使用 try-with-resources",
"descriptionJa": "try-with-resources を使用する"
},
{
"id": "pmd/UseUtilityClass",
"description": "Utility class should have private constructor"
"description": "Utility class should have private constructor",
"descriptionZh": "工具类应有私有构造函数",
"descriptionJa": "ユーティリティクラスはプライベートコンストラクタを持つべきである"
},
{
"id": "pmd/UseVarargs",
"description": "Use varargs instead of array parameter"
"description": "Use varargs instead of array parameter",
"descriptionZh": "使用可变参数替代数组参数",
"descriptionJa": "配列パラメータの代わりに可変長引数を使用する"
},
{
"id": "pmd/VariableCanBeInlined",
"description": "Variable can be inlined"
"description": "Variable can be inlined",
"descriptionZh": "变量可以内联",
"descriptionJa": "変数をインライン化できる"
},
{
"id": "pmd/WhileLoopWithLiteralBoolean",
"description": "Simplify while loops with literal booleans"
"description": "Simplify while loops with literal booleans",
"descriptionZh": "简化带字面量布尔值的 while 循环",
"descriptionJa": "リテラルブール値を持つ while ループを簡略化する"
},
{
"id": "pmd/AtLeastOneConstructor",
"description": "Each class should have a constructor"
"description": "Each class should have a constructor",
"descriptionZh": "每个类都应有一个构造函数",
"descriptionJa": "各クラスにコンストラクタを1つ持つべきである"
},
{
"id": "pmd/AvoidDollarSigns",
"description": "Avoid $ in names"
"description": "Avoid $ in names",
"descriptionZh": "避免在名称中使用 $",
"descriptionJa": "名前に $ を使用することを避ける"
},
{
"id": "pmd/AvoidProtectedFieldInFinalClass",
"description": "Don't use protected fields in final classes"
"description": "Don't use protected fields in final classes",
"descriptionZh": "final 类中不要使用 protected 字段",
"descriptionJa": "final クラスで protected フィールドを使わない"
},
{
"id": "pmd/AvoidProtectedMethodInFinalClassNotExtending",
"description": "Don't use protected methods in final classes not extending"
"description": "Don't use protected methods in final classes not extending",
"descriptionZh": "非继承的 final 类中不要使用 protected 方法",
"descriptionJa": "継承しない final クラスで protected メソッドを使わない"
},
{
"id": "pmd/AvoidUsingNativeCode",
"description": "Avoid JNI calls"
"description": "Avoid JNI calls",
"descriptionZh": "避免 JNI 调用",
"descriptionJa": "JNI 呼び出しを避ける"
},
{
"id": "pmd/BooleanGetMethodName",
"description": "Boolean getters should be named isX()"
"description": "Boolean getters should be named isX()",
"descriptionZh": "布尔 getter 应命名为 isX()",
"descriptionJa": "ブールゲッターは isX() と命名する"
},
{
"id": "pmd/CallSuperInConstructor",
"description": "Call super() in constructor"
"description": "Call super() in constructor",
"descriptionZh": "在构造函数中调用 super()",
"descriptionJa": "コンストラクタで super() を呼び出す"
},
{
"id": "pmd/ClassNamingConventions",
"description": "PascalCase naming"
"description": "PascalCase naming",
"descriptionZh": "PascalCase 命名",
"descriptionJa": "PascalCase 命名"
},
{
"id": "pmd/CommentDefaultAccessModifier",
"description": "Comment default access modifier"
"description": "Comment default access modifier",
"descriptionZh": "注释默认访问修饰符",
"descriptionJa": "デフォルトアクセス修飾子をコメントする"
},
{
"id": "pmd/ConfusingTernary",
"description": "Avoid negation in if with else"
"description": "Avoid negation in if with else",
"descriptionZh": "避免在带 else 的 if 中使用取反",
"descriptionJa": "else 付き if での否定を避ける"
},
{
"id": "pmd/ControlStatementBraces",
"description": "Require braces on control statements"
"description": "Require braces on control statements",
"descriptionZh": "控制语句要求花括号",
"descriptionJa": "制御文に波括弧を要求する"
},
{
"id": "pmd/EmptyControlStatement",
"description": "Report empty control statements"
"description": "Report empty control statements",
"descriptionZh": "报告空的控制语句",
"descriptionJa": "空の制御文を報告する"
},
{
"id": "pmd/EmptyMethodInAbstractClassShouldBeAbstract",
"description": "Empty methods in abstract classes should be abstract"
"description": "Empty methods in abstract classes should be abstract",
"descriptionZh": "抽象类中的空方法应为抽象方法",
"descriptionJa": "抽象クラスの空メソッドは抽象にすべきである"
},
{
"id": "pmd/ExtendsObject",
"description": "No need to explicitly extend Object"
"description": "No need to explicitly extend Object",
"descriptionZh": "无需显式继承 Object",
"descriptionJa": "Object を明示的に継承する必要はない"
},
{
"id": "pmd/FieldDeclarationsShouldBeAtStartOfClass",
"description": "Fields at top of class"
"description": "Fields at top of class",
"descriptionZh": "字段放在类的顶部",
"descriptionJa": "フィールドをクラスの先頭に置く"
},
{
"id": "pmd/FieldNamingConventions",
"description": "Configurable field naming conventions"
"description": "Configurable field naming conventions",
"descriptionZh": "可配置的字段命名规范",
"descriptionJa": "設定可能なフィールド命名規則"
},
{
"id": "pmd/FinalParameterInAbstractMethod",
"description": "Final parameter in abstract method is useless"
"description": "Final parameter in abstract method is useless",
"descriptionZh": "抽象方法中的 final 参数无用",
"descriptionJa": "抽象メソッドの final パラメータは無意味である"
},
{
"id": "pmd/ForLoopShouldBeWhileLoop",
"description": "Simplify for loops to while"
"description": "Simplify for loops to while",
"descriptionZh": "将 for 循环简化为 while",
"descriptionJa": "for ループを while に簡略化する"
},
{
"id": "pmd/FormalParameterNamingConventions",
"description": "Parameter naming conventions"
"description": "Parameter naming conventions",
"descriptionZh": "参数命名规范",
"descriptionJa": "パラメータ命名規則"
},
{
"id": "pmd/IdenticalCatchBranches",
"description": "Collapse identical catch branches"
"description": "Collapse identical catch branches",
"descriptionZh": "合并相同的 catch 分支",
"descriptionJa": "同一の catch ブランチを統合する"
},
{
"id": "pmd/LambdaCanBeMethodReference",
"description": "Replace lambda with method reference"
"description": "Replace lambda with method reference",
"descriptionZh": "用方法引用替代 lambda",
"descriptionJa": "ラムダをメソッド参照に置き換える"
},
{
"id": "pmd/LinguisticNaming",
"description": "Method name/return type consistency"
"description": "Method name/return type consistency",
"descriptionZh": "方法名与返回类型一致性",
"descriptionJa": "メソッド名と戻り値型の整合性"
},
{
"id": "pmd/LocalHomeNamingConvention",
"description": "EJB LocalHome suffix"
"description": "EJB LocalHome suffix",
"descriptionZh": "EJB LocalHome 后缀",
"descriptionJa": "EJB LocalHome サフィックス"
},
{
"id": "pmd/LocalInterfaceSessionNamingConvention",
"description": "EJB Local suffix"
"description": "EJB Local suffix",
"descriptionZh": "EJB Local 后缀",
"descriptionJa": "EJB Local サフィックス"
},
{
"id": "pmd/LocalVariableCouldBeFinal",
"description": "Declare local variables final when possible"
"description": "Declare local variables final when possible",
"descriptionZh": "尽可能将局部变量声明为 final",
"descriptionJa": "可能な限りローカル変数を final で宣言する"
},
{
"id": "pmd/LocalVariableNamingConventions",
"description": "Variable naming conventions"
"description": "Variable naming conventions",
"descriptionZh": "变量命名规范",
"descriptionJa": "変数命名規則"
},
{
"id": "pmd/LongVariable",
"description": "Avoid excessively long variable names (>17 chars)"
"description": "Avoid excessively long variable names (>17 chars)",
"descriptionZh": "避免过长的变量名(超过17个字符)",
"descriptionJa": "過度に長い変数名(17文字超)を避ける"
},
{
"id": "pmd/MDBAndSessionBeanNamingConvention",
"description": "EJB Bean suffix"
"description": "EJB Bean suffix",
"descriptionZh": "EJB Bean 后缀",
"descriptionJa": "EJB Bean サフィックス"
},
{
"id": "pmd/MethodArgumentCouldBeFinal",
"description": "Declare parameters final when possible"
"description": "Declare parameters final when possible",
"descriptionZh": "尽可能将参数声明为 final",
"descriptionJa": "可能な限りパラメータを final で宣言する"
},
{
"id": "pmd/MethodNamingConventions",
"description": "Method naming conventions"
"description": "Method naming conventions",
"descriptionZh": "方法命名规范",
"descriptionJa": "メソッド命名規則"
},
{
"id": "pmd/ModifierOrder",
"description": "Enforce JLS modifier order"
"description": "Enforce JLS modifier order",
"descriptionZh": "强制 JLS 修饰符顺序",
"descriptionJa": "JLS 修飾子の順序を強制する"
},
{
"id": "pmd/NoPackage",
"description": "All types must belong to a named package"
"description": "All types must belong to a named package",
"descriptionZh": "所有类型必须属于命名包",
"descriptionJa": "すべての型は名前付きパッケージに属すべきである"
},
{
"id": "pmd/OnlyOneReturn",
"description": "Single exit point per method"
"description": "Single exit point per method",
"descriptionZh": "每个方法只有一个出口",
"descriptionJa": "メソッドに出口を1つだけ持たせる"
},
{
"id": "pmd/PackageCase",
"description": "Package names lowercase"
"description": "Package names lowercase",
"descriptionZh": "包名使用小写",
"descriptionJa": "パッケージ名は小文字にする"
},
{
"id": "pmd/PrematureDeclaration",
"description": "Declare variables close to usage"
"description": "Declare variables close to usage",
"descriptionZh": "变量声明靠近使用处",
"descriptionJa": "変数を使用箇所の近くで宣言する"
},
{
"id": "pmd/UselessParentheses",
"description": "Remove unnecessary parentheses"
"description": "Remove unnecessary parentheses",
"descriptionZh": "移除不必要的括号",
"descriptionJa": "不要な括弧を削除する"
},
{
"id": "pmd/UselessQualifiedThis",
"description": "Remove unnecessary qualified this"
"description": "Remove unnecessary qualified this",
"descriptionZh": "移除不必要的限定 this",
"descriptionJa": "不要な限定 this を削除する"
},
{
"id": "pmd/UnnecessaryAnnotationValueElement",
"description": "Remove unnecessary annotation value element"
"description": "Remove unnecessary annotation value element",
"descriptionZh": "移除不必要的注解值元素",
"descriptionJa": "不要なアノテーション値要素を削除する"
},
{
"id": "pmd/UnnecessaryBoxing",
"description": "Avoid unnecessary boxing"
"description": "Avoid unnecessary boxing",
"descriptionZh": "避免不必要的装箱",
"descriptionJa": "不要なボクシングを避ける"
},
{
"id": "pmd/UnnecessaryCast",
"description": "Remove unnecessary casts"
"description": "Remove unnecessary casts",
"descriptionZh": "移除不必要的强制转换",
"descriptionJa": "不要なキャストを削除する"
},
{
"id": "pmd/UnnecessaryConstructor",
"description": "Remove unnecessary constructors"
"description": "Remove unnecessary constructors",
"descriptionZh": "移除不必要的构造函数",
"descriptionJa": "不要なコンストラクタを削除する"
},
{
"id": "pmd/UnnecessaryFullyQualifiedName",
"description": "Remove unnecessary fully qualified names"
"description": "Remove unnecessary fully qualified names",
"descriptionZh": "移除不必要的全限定名",
"descriptionJa": "不要な完全修飾名を削除する"
},
{
"id": "pmd/UnnecessaryImport",
"description": "Remove unnecessary imports"
"description": "Remove unnecessary imports",
"descriptionZh": "移除不必要的导入",
"descriptionJa": "不要なインポートを削除する"
},
{
"id": "pmd/UnnecessaryModifier",
"description": "Remove unnecessary modifiers"
"description": "Remove unnecessary modifiers",
"descriptionZh": "移除不必要的修饰符",
"descriptionJa": "不要な修飾子を削除する"
},
{
"id": "pmd/UnnecessaryReturn",
"description": "Remove unnecessary returns"
"description": "Remove unnecessary returns",
"descriptionZh": "移除不必要的 return",
"descriptionJa": "不要な return を削除する"
},
{
"id": "pmd/UnnecessarySemicolon",
"description": "Remove unnecessary semicolons"
"description": "Remove unnecessary semicolons",
"descriptionZh": "移除不必要的分号",
"descriptionJa": "不要なセミコロンを削除する"
},
{
"id": "pmd/UnnecessaryUnboxing",
"description": "Avoid unnecessary unboxing"
"description": "Avoid unnecessary unboxing",
"descriptionZh": "避免不必要的拆箱",
"descriptionJa": "不要なアンボクシングを避ける"
},
{
"id": "pmd/UpperLowerCaseNamingConventions",
"description": "Naming conventions for cases"
"description": "Naming conventions for cases",
"descriptionZh": "大小写命名规范",
"descriptionJa": "大文字・小文字の命名規則"
},
{
"id": "pmd/UseShortArrayInitializer",
"description": "Use short array initializer"
"description": "Use short array initializer",
"descriptionZh": "使用简短的数组初始化器",
"descriptionJa": "簡潔な配列初期化子を使用する"
},
{
"id": "pmd/AbstractClassWithoutAnyMethod",
"description": "Abstract class without methods should use private constructor"
"description": "Abstract class without methods should use private constructor",
"descriptionZh": "没有任何方法的抽象类应使用私有构造函数",
"descriptionJa": "メソッドのない抽象クラスはプライベートコンストラクタを使うべきである"
},
{
"id": "pmd/AvoidDeeplyNestedIfStmts",
"description": "Avoid deeply nested if statements"
"description": "Avoid deeply nested if statements",
"descriptionZh": "避免深度嵌套的 if 语句",
"descriptionJa": "深くネストした if 文を避ける"
},
{
"id": "pmd/AvoidRethrowingException",
"description": "Avoid catch-and-rethrow"
"description": "Avoid catch-and-rethrow",
"descriptionZh": "避免捕获后重新抛出",
"descriptionJa": "catch-and-rethrow を避ける"
},
{
"id": "pmd/AvoidThrowingNewInstanceOfSameException",
"description": "Avoid wrapping same exception type"
"description": "Avoid wrapping same exception type",
"descriptionZh": "避免包装相同异常类型",
"descriptionJa": "同じ例外型のラップを避ける"
},
{
"id": "pmd/AvoidThrowingNullPointerException",
"description": "Don't throw NPE manually"
"description": "Don't throw NPE manually",
"descriptionZh": "不要手动抛出 NPE",
"descriptionJa": "NPE を手動で投げない"
},
{
"id": "pmd/AvoidThrowingRawExceptionTypes",
"description": "Don't throw raw Exception/RuntimeException/Throwable/Error"
"description": "Don't throw raw Exception/RuntimeException/Throwable/Error",
"descriptionZh": "不要抛出原始 Exception/RuntimeException/Throwable/Error",
"descriptionJa": "生の Exception/RuntimeException/Throwable/Error を投げない"
},
{
"id": "pmd/AvoidUncheckedExceptionsInSignatures",
"description": "Don't declare unchecked exceptions in throws"
"description": "Don't declare unchecked exceptions in throws",
"descriptionZh": "不要在 throws 中声明非受检异常",
"descriptionJa": "throws で非チェック例外を宣言しない"
},
{
"id": "pmd/ClassWithOnlyPrivateConstructorsShouldBeFinal",
"description": "Make class final if only private constructors"
"description": "Make class final if only private constructors",
"descriptionZh": "只有私有构造函数的类应为 final",
"descriptionJa": "プライベートコンストラクタのみのクラスは final にする"
},
{
"id": "pmd/CognitiveComplexity",
"description": "Methods with high cognitive complexity"
"description": "Methods with high cognitive complexity",
"descriptionZh": "高认知复杂度的方法",
"descriptionJa": "認知複雑度の高いメソッド"
},
{
"id": "pmd/CollapsibleIfStatements",
"description": "Merge nested if statements"
"description": "Merge nested if statements",
"descriptionZh": "合并嵌套的 if 语句",
"descriptionJa": "ネストした if 文を統合する"
},
{
"id": "pmd/CouplingBetweenObjects",
"description": "High coupling threshold"
"description": "High coupling threshold",
"descriptionZh": "高耦合阈值",
"descriptionJa": "高い結合度の閾値"
},
{
"id": "pmd/CyclomaticComplexity",
"description": "High cyclomatic complexity"
"description": "High cyclomatic complexity",
"descriptionZh": "高圈复杂度",
"descriptionJa": "高い循環的複雑度"
},
{
"id": "pmd/DataClass",
"description": "Suspected Data Class"
"description": "Suspected Data Class",
"descriptionZh": "疑似数据类",
"descriptionJa": "疑わしいデータクラス"
},
{
"id": "pmd/DoNotExtendJavaLangError",
"description": "Don't extend Error"
"description": "Don't extend Error",
"descriptionZh": "不要继承 Error",
"descriptionJa": "Error を継承しない"
},
{
"id": "pmd/ExceptionAsFlowControl",
"description": "Don't use exceptions for flow control"
"description": "Don't use exceptions for flow control",
"descriptionZh": "不要使用异常控制流程",
"descriptionJa": "制御フローに例外を使わない"
},
{
"id": "pmd/ExcessiveImports",
"description": "Too many imports"
"description": "Too many imports",
"descriptionZh": "导入过多",
"descriptionJa": "インポートが多すぎる"
},
{
"id": "pmd/ExcessiveParameterList",
"description": "Too many parameters"
"description": "Too many parameters",
"descriptionZh": "参数过多",
"descriptionJa": "パラメータが多すぎる"
},
{
"id": "pmd/ExcessivePublicCount",
"description": "Too many public methods/attributes"
"description": "Too many public methods/attributes",
"descriptionZh": "公共方法/属性过多",
"descriptionJa": "public メソッド/属性が多すぎる"
},
{
"id": "pmd/FinalFieldCouldBeStatic",
"description": "Make final field static if compile-time constant"
"description": "Make final field static if compile-time constant",
"descriptionZh": "编译时常量 final 字段应为 static",
"descriptionJa": "コンパイル時定数の final フィールドは static にできる"
},
{
"id": "pmd/GodClass",
"description": "God Class detection"
"description": "God Class detection",
"descriptionZh": "上帝类检测",
"descriptionJa": "God クラスの検出"
},
{
"id": "pmd/ImmutableField",
"description": "Field could be final"
"description": "Field could be final",
"descriptionZh": "字段可以声明为 final",
"descriptionJa": "フィールドは final にできる"
},
{
"id": "pmd/InvalidJavaBean",
"description": "Bean doesn't follow JavaBeans spec"
"description": "Bean doesn't follow JavaBeans spec",
"descriptionZh": "Bean 不符合 JavaBeans 规范",
"descriptionJa": "Bean が JavaBeans 仕様に従っていない"
},
{
"id": "pmd/LawOfDemeter",
"description": "Potential LoD violation"
"description": "Potential LoD violation",
"descriptionZh": "潜在的迪米特法则违规",
"descriptionJa": "潜在的な LoD 違反"
},
{
"id": "pmd/LogicInversion",
"description": "Use opposite operator instead of !"
"description": "Use opposite operator instead of !",
"descriptionZh": "使用相反的运算符替代 !",
"descriptionJa": "! の代わりに反対の演算子を使用する"
},
{
"id": "pmd/LoosePackageCoupling",
"description": "Avoid using classes from outside package hierarchy"
"description": "Avoid using classes from outside package hierarchy",
"descriptionZh": "避免使用包层次之外的类",
"descriptionJa": "パッケージ階層外のクラスの使用を避ける"
},
{
"id": "pmd/MutableStaticState",
"description": "Non-private non-final static fields"
"description": "Non-private non-final static fields",
"descriptionZh": "非私有非 final 的静态字段",
"descriptionJa": "非 private かつ非 final の静的フィールド"
},
{
"id": "pmd/NcssCount",
"description": "Non-Commenting Source Statements metric"
"description": "Non-Commenting Source Statements metric",
"descriptionZh": "非注释源码语句度量",
"descriptionJa": "非コメントソース文のメトリクス"
},
{
"id": "pmd/NPathComplexity",
"description": "NPath complexity threshold"
"description": "NPath complexity threshold",
"descriptionZh": "NPath 复杂度阈值",
"descriptionJa": "NPath 複雑度の閾値"
},
{
"id": "pmd/PublicMemberInNonPublicType",
"description": "Public member in non-public type"
"description": "Public member in non-public type",
"descriptionZh": "非公共类型中的公共成员",
"descriptionJa": "非 public 型内の public メンバー"
},
{
"id": "pmd/SignatureDeclareThrowsException",
"description": "Don't declare throws Exception"
"description": "Don't declare throws Exception",
"descriptionZh": "不要声明 throws Exception",
"descriptionJa": "throws Exception を宣言しない"
},
{
"id": "pmd/SimplifiedTernary",
"description": "Simplify ternary with boolean literals"
"description": "Simplify ternary with boolean literals",
"descriptionZh": "用布尔字面量简化三元表达式",
"descriptionJa": "ブールリテラルで三項演算子を簡略化する"
},
{
"id": "pmd/SimplifyBooleanExpressions",
"description": "Remove unnecessary boolean comparisons"
"description": "Remove unnecessary boolean comparisons",
"descriptionZh": "移除不必要的布尔比较",
"descriptionJa": "不要なブール比較を削除する"
},
{
"id": "pmd/SimplifyBooleanReturns",
"description": "Simplify boolean returns"
"description": "Simplify boolean returns",
"descriptionZh": "简化布尔返回",
"descriptionJa": "ブールの戻り値を簡略化する"
},
{
"id": "pmd/SimplifyConditional",
"description": "Simplify conditional expressions"
"description": "Simplify conditional expressions",
"descriptionZh": "简化条件表达式",
"descriptionJa": "条件式を簡略化する"
},
{
"id": "pmd/SingularField",
"description": "Field may be local variable"
"description": "Field may be local variable",
"descriptionZh": "字段可能应为局部变量",
"descriptionJa": "フィールドはローカル変数にできる"
},
{
"id": "pmd/TooManyFields",
"description": "Too many fields"
"description": "Too many fields",
"descriptionZh": "字段过多",
"descriptionJa": "フィールドが多すぎる"
},
{
"id": "pmd/TooManyMethods",
"description": "Too many methods"
"description": "Too many methods",
"descriptionZh": "方法过多",
"descriptionJa": "メソッドが多すぎる"
},
{
"id": "pmd/UselessOverridingMethod",
"description": "Useless overriding method"
"description": "Useless overriding method",
"descriptionZh": "无意义的重写方法",
"descriptionJa": "無意味なオーバーライドメソッド"
},
{
"id": "pmd/AssertEqualsArgumentOrder",
"description": "assertEquals expected/actual swapped"
"description": "assertEquals expected/actual swapped",
"descriptionZh": "assertEquals 的 expected/actual 参数顺序颠倒",
"descriptionJa": "assertEquals の expected/actual 引数が逆"
},
{
"id": "pmd/AssignmentInOperand",
"description": "Avoid assignments in operands"
"description": "Avoid assignments in operands",
"descriptionZh": "避免在操作数中赋值",
"descriptionJa": "オペランド内での代入を避ける"
},
{
"id": "pmd/AssignmentToNonFinalStatic",
"description": "Unsafe static field assignment in constructor"
"description": "Unsafe static field assignment in constructor",
"descriptionZh": "构造函数中对非 final 静态字段的不安全赋值",
"descriptionJa": "コンストラクタ内の非 final 静的フィールドへの安全でない代入"
},
{
"id": "pmd/AvoidAccessibilityAlteration",
"description": "Don't use setAccessible(true)"
"description": "Don't use setAccessible(true)",
"descriptionZh": "不要使用 setAccessible(true)",
"descriptionJa": "setAccessible(true) を使用しない"
},
{
"id": "pmd/AvoidAssertAsIdentifier",
"description": "assert is reserved word (Java <1.4)"
"description": "assert is reserved word (Java <1.4)",
"descriptionZh": "assert 是保留字(Java <1.4",
"descriptionJa": "assert は予約語である(Java <1.4"
},
{
"id": "pmd/AvoidBranchingStatementAsLastInLoop",
"description": "Branching statement as last in loop"
"description": "Branching statement as last in loop",
"descriptionZh": "循环体最后的跳转语句",
"descriptionJa": "ループの最後の分岐文"
},
{
"id": "pmd/AvoidCallingFinalize",
"description": "Don't call finalize() explicitly"
"description": "Don't call finalize() explicitly",
"descriptionZh": "不要显式调用 finalize()",
"descriptionJa": "finalize() を明示的に呼ばない"
},
{
"id": "pmd/AvoidCatchingGenericException",
"description": "Don't catch generic exceptions"
"description": "Don't catch generic exceptions",
"descriptionZh": "不要捕获泛化异常",
"descriptionJa": "汎用例外を捕捉しない"
},
{
"id": "pmd/AvoidDecimalLiteralsInBigDecimalConstructor",
"description": "Use String constructor for BigDecimal"
"description": "Use String constructor for BigDecimal",
"descriptionZh": "BigDecimal 使用 String 构造函数",
"descriptionJa": "BigDecimal には String コンストラクタを使用する"
},
{
"id": "pmd/AvoidDuplicateLiterals",
"description": "Avoid duplicate String literals"
"description": "Avoid duplicate String literals",
"descriptionZh": "避免重复的 String 字面量",
"descriptionJa": "重複する文字列リテラルを避ける"
},
{
"id": "pmd/AvoidEnumAsIdentifier",
"description": "enum is reserved word (Java <1.5)"
"description": "enum is reserved word (Java <1.5)",
"descriptionZh": "enum 是保留字(Java <1.5",
"descriptionJa": "enum は予約語である(Java <1.5"
},
{
"id": "pmd/AvoidFieldNameMatchingMethodName",
"description": "Field name matching method name"
"description": "Field name matching method name",
"descriptionZh": "字段名与方法名相同",
"descriptionJa": "フィールド名とメソッド名が一致する"
},
{
"id": "pmd/AvoidFieldNameMatchingTypeName",
"description": "Field name matching type name"
"description": "Field name matching type name",
"descriptionZh": "字段名与类型名相同",
"descriptionJa": "フィールド名と型名が一致する"
},
{
"id": "pmd/AvoidInstanceofChecksInCatchClause",
"description": "Use separate catch clauses"
"description": "Use separate catch clauses",
"descriptionZh": "使用单独的 catch 子句",
"descriptionJa": "個別の catch 句を使用する"
},
{
"id": "pmd/AvoidLiteralsInIfCondition",
"description": "Avoid magic numbers in if conditions"
"description": "Avoid magic numbers in if conditions",
"descriptionZh": "避免 if 条件中的魔术数字",
"descriptionJa": "if 条件内のマジックナンバーを避ける"
},
{
"id": "pmd/AvoidMultipleUnaryOperators",
"description": "Avoid multiple unary operators"
"description": "Avoid multiple unary operators",
"descriptionZh": "避免多个一元运算符",
"descriptionJa": "複数の単項演算子を避ける"
},
{
"id": "pmd/AvoidSynchronizedStatement",
"description": "Avoid synchronized statements"
"description": "Avoid synchronized statements",
"descriptionZh": "避免 synchronized 语句",
"descriptionJa": "synchronized 文を避ける"
},
{
"id": "pmd/AvoidSynchronizedAtMethodLevel",
"description": "Avoid synchronized at method level"
"description": "Avoid synchronized at method level",
"descriptionZh": "避免在方法级别使用 synchronized",
"descriptionJa": "メソッドレベルでの synchronized を避ける"
},
{
"id": "pmd/AvoidThreadGroup",
"description": "Avoid using ThreadGroup"
"description": "Avoid using ThreadGroup",
"descriptionZh": "避免使用 ThreadGroup",
"descriptionJa": "ThreadGroup の使用を避ける"
},
{
"id": "pmd/AvoidUsingOctalValues",
"description": "Avoid octal literals"
"description": "Avoid octal literals",
"descriptionZh": "避免八进制字面量",
"descriptionJa": "8進数リテラルを避ける"
},
{
"id": "pmd/AvoidUsingVolatile",
"description": "Avoid the volatile keyword"
"description": "Avoid the volatile keyword",
"descriptionZh": "避免 volatile 关键字",
"descriptionJa": "volatile キーワードを避ける"
},
{
"id": "pmd/BrokenNullCheck",
"description": "Broken null check (|| vs &&)"
"description": "Broken null check (|| vs &&)",
"descriptionZh": "错误的 null 检查(|| 与 &&",
"descriptionJa": "壊れた null チェック(|| vs &&"
},
{
"id": "pmd/CallSuperFirst",
"description": "super should be called first"
"description": "super should be called first",
"descriptionZh": "super 应首先调用",
"descriptionJa": "super を最初に呼ぶべきである"
},
{
"id": "pmd/CallSuperLast",
"description": "super should be called last"
"description": "super should be called last",
"descriptionZh": "super 应最后调用",
"descriptionJa": "super を最後に呼ぶべきである"
},
{
"id": "pmd/CheckSkipResult",
"description": "Check skip() return value"
"description": "Check skip() return value",
"descriptionZh": "检查 skip() 的返回值",
"descriptionJa": "skip() の戻り値を確認する"
},
{
"id": "pmd/ClassCastExceptionWithToArray",
"description": "Collection.toArray() ClassCastException"
"description": "Collection.toArray() ClassCastException",
"descriptionZh": "Collection.toArray() 的 ClassCastException",
"descriptionJa": "Collection.toArray() の ClassCastException"
},
{
"id": "pmd/CloneMethodMustBePublic",
"description": "clone() must be public if Cloneable"
"description": "clone() must be public if Cloneable",
"descriptionZh": "实现 Cloneable 时 clone() 必须是 public",
"descriptionJa": "Cloneable の場合 clone() は public でなければならない"
},
{
"id": "pmd/CloneMethodMustImplementCloneable",
"description": "clone() only if Cloneable"
"description": "clone() only if Cloneable",
"descriptionZh": "只有实现 Cloneable 时才有 clone()",
"descriptionJa": "Cloneable の場合のみ clone() を持つ"
},
{
"id": "pmd/CloneMethodReturnTypeMustMatchClassName",
"description": "clone() covariant return type"
"description": "clone() covariant return type",
"descriptionZh": "clone() 协变返回类型",
"descriptionJa": "clone() の共変戻り値型"
},
{
"id": "pmd/CloseResource",
"description": "Ensure resources are closed"
"description": "Ensure resources are closed",
"descriptionZh": "确保资源被关闭",
"descriptionJa": "リソースが閉じられることを保証する"
},
{
"id": "pmd/CollectionTypeMismatch",
"description": "Type mismatch in collection methods"
"description": "Type mismatch in collection methods",
"descriptionZh": "集合方法中的类型不匹配",
"descriptionJa": "コレクションメソッド内の型不一致"
},
{
"id": "pmd/CompareObjectsWithEquals",
"description": "Use equals() not == for objects"
"description": "Use equals() not == for objects",
"descriptionZh": "对象比较使用 equals() 而非 ==",
"descriptionJa": "オブジェクトの比較に == ではなく equals() を使用する"
},
{
"id": "pmd/ComparisonWithNaN",
"description": "NaN comparisons always return false"
"description": "NaN comparisons always return false",
"descriptionZh": "NaN 比较总是返回 false",
"descriptionJa": "NaN との比較は常に false を返す"
},
{
"id": "pmd/ConfusingArgumentToVarargsMethod",
"description": "Clarify varargs intent"
"description": "Clarify varargs intent",
"descriptionZh": "澄清可变参数意图",
"descriptionJa": "可変長引数の意図を明確にする"
},
{
"id": "pmd/ConstructorCallsOverridableMethod",
"description": "Constructor calls overridable method"
"description": "Constructor calls overridable method",
"descriptionZh": "构造函数调用可重写方法",
"descriptionJa": "コンストラクタがオーバーライド可能なメソッドを呼ぶ"
},
{
"id": "pmd/DataflowAnomalyAnalysis",
"description": "Data flow anomalies"
"description": "Data flow anomalies",
"descriptionZh": "数据流异常",
"descriptionJa": "データフロー異常"
},
{
"id": "pmd/DoNotCallGarbageCollectionExplicitly",
"description": "Don't call System.gc()"
"description": "Don't call System.gc()",
"descriptionZh": "不要显式调用 System.gc()",
"descriptionJa": "System.gc() を明示的に呼ばない"
},
{
"id": "pmd/DoNotCallSystemExit",
"description": "Don't call System.exit()"
"description": "Don't call System.exit()",
"descriptionZh": "不要调用 System.exit()",
"descriptionJa": "System.exit() を呼ばない"
},
{
"id": "pmd/DoNotHardCodeSDCard",
"description": "Don't hardcode /sdcard path"
"description": "Don't hardcode /sdcard path",
"descriptionZh": "不要硬编码 /sdcard 路径",
"descriptionJa": "/sdcard パスをハードコードしない"
},
{
"id": "pmd/DoNotThrowExceptionInFinally",
"description": "Don't throw in finally"
"description": "Don't throw in finally",
"descriptionZh": "不要在 finally 中抛出异常",
"descriptionJa": "finally で例外を投げない"
},
{
"id": "pmd/DoNotUseThreads",
"description": "Don't use Threads"
"description": "Don't use Threads",
"descriptionZh": "不要使用线程",
"descriptionJa": "スレッドを使用しない"
},
{
"id": "pmd/DontCallThreadRun",
"description": "Don't call Thread.run()"
"description": "Don't call Thread.run()",
"descriptionZh": "不要调用 Thread.run()",
"descriptionJa": "Thread.run() を呼ばない"
},
{
"id": "pmd/DoubleCheckedLocking",
"description": "Double-checked locking is not thread-safe"
"description": "Double-checked locking is not thread-safe",
"descriptionZh": "双重检查锁定不是线程安全的",
"descriptionJa": "二重チェックロッキングはスレッド安全でない"
},
{
"id": "pmd/EmptyCatchBlock",
"description": "Empty catch blocks"
"description": "Empty catch blocks",
"descriptionZh": "空 catch 块",
"descriptionJa": "空の catch ブロック"
},
{
"id": "pmd/EqualsNull",
"description": "Equal comparison to null"
"description": "Equal comparison to null",
"descriptionZh": "与 null 进行相等比较",
"descriptionJa": "null との等価比較"
},
{
"id": "pmd/FinallyBlockDoesNothing",
"description": "Finally block does nothing"
"description": "Finally block does nothing",
"descriptionZh": "finally 块什么也不做",
"descriptionJa": "finally ブロックが何もしない"
},
{
"id": "pmd/IdempotentOperations",
"description": "Idempotent operations"
"description": "Idempotent operations",
"descriptionZh": "幂等操作",
"descriptionJa": "冪等な操作"
},
{
"id": "pmd/ImplicitSwitchFallThrough",
"description": "Implicit switch fall through"
"description": "Implicit switch fall through",
"descriptionZh": "隐式 switch fall through",
"descriptionJa": "暗黙の switch フォールスルー"
},
{
"id": "pmd/ImportFromSamePackage",
"description": "Import from same package"
"description": "Import from same package",
"descriptionZh": "从同包导入",
"descriptionJa": "同一パッケージからのインポート"
},
{
"id": "pmd/InstantiationToGetClass",
"description": "Instantiation just to get class"
"description": "Instantiation just to get class",
"descriptionZh": "仅为获取类而实例化",
"descriptionJa": "クラス取得のためだけのインスタンス化"
},
{
"id": "pmd/InvalidLogMessageFormat",
"description": "Invalid SLF4J message format"
"description": "Invalid SLF4J message format",
"descriptionZh": "无效的 SLF4J 消息格式",
"descriptionJa": "無効な SLF4J メッセージ形式"
},
{
"id": "pmd/JUnitSpelling",
"description": "JUnit method spelling"
"description": "JUnit method spelling",
"descriptionZh": "JUnit 方法拼写",
"descriptionJa": "JUnit メソッドの綴り"
},
{
"id": "pmd/JUnitStaticSuite",
"description": "JUnit static suite method"
"description": "JUnit static suite method",
"descriptionZh": "JUnit 静态 suite 方法",
"descriptionJa": "JUnit の静的 suite メソッド"
},
{
"id": "pmd/JumbledIncrementer",
"description": "Jumbled incrementer"
"description": "Jumbled incrementer",
"descriptionZh": "混乱的增量器",
"descriptionJa": "入り混じったインクリメンタ"
},
{
"id": "pmd/LoggerIsNotStaticFinal",
"description": "Logger not static final"
"description": "Logger not static final",
"descriptionZh": "Logger 不是 static final",
"descriptionJa": "Logger が static final でない"
},
{
"id": "pmd/MethodWithSameNameAsEnclosingClass",
"description": "Method same name as enclosing class"
"description": "Method same name as enclosing class",
"descriptionZh": "方法与包围类同名",
"descriptionJa": "メソッドが囲むクラスと同名"
},
{
"id": "pmd/MisplacedNullCheck",
"description": "Misplaced null check"
"description": "Misplaced null check",
"descriptionZh": "位置错误的 null 检查",
"descriptionJa": "誤った位置の null チェック"
},
{
"id": "pmd/MissingBreakInSwitch",
"description": "Missing break in switch"
"description": "Missing break in switch",
"descriptionZh": "switch 中缺少 break",
"descriptionJa": "switch 内の break 欠落"
},
{
"id": "pmd/MissingSerialVersionUID",
"description": "Missing serialVersionUID"
"description": "Missing serialVersionUID",
"descriptionZh": "缺少 serialVersionUID",
"descriptionJa": "serialVersionUID の欠落"
},
{
"id": "pmd/MissingStaticMethodInNonInstantiatableClass",
"description": "Non-instantiatable class missing static method"
"description": "Non-instantiatable class missing static method",
"descriptionZh": "不可实例化类缺少静态方法",
"descriptionJa": "インスタンス化できないクラスに静的メソッドがない"
},
{
"id": "pmd/MoreThanOneLogger",
"description": "More than one logger"
"description": "More than one logger",
"descriptionZh": "多于一个 logger",
"descriptionJa": "logger が複数ある"
},
{
"id": "pmd/NonCaseLabelInSwitch",
"description": "Non-case label in switch"
"description": "Non-case label in switch",
"descriptionZh": "switch 中的非 case 标签",
"descriptionJa": "switch 内の非 case ラベル"
},
{
"id": "pmd/NonStaticInitializer",
"description": "Non-static initializer"
"description": "Non-static initializer",
"descriptionZh": "非静态初始化器",
"descriptionJa": "非静的初期化子"
},
{
"id": "pmd/NonThreadSafeSingleton",
"description": "Singleton is not thread-safe"
"description": "Singleton is not thread-safe",
"descriptionZh": "单例不是线程安全的",
"descriptionJa": "シングルトンがスレッド安全でない"
},
{
"id": "pmd/NullAssignment",
"description": "Null assignment"
"description": "Null assignment",
"descriptionZh": "null 赋值",
"descriptionJa": "null 代入"
},
{
"id": "pmd/NumberConstructor",
"description": "Number constructor (deprecated)"
"description": "Number constructor (deprecated)",
"descriptionZh": "Number 构造函数(已废弃)",
"descriptionJa": "Number コンストラクタ(非推奨)"
},
{
"id": "pmd/ObjectFinalize",
"description": "Object finalize issues"
"description": "Object finalize issues",
"descriptionZh": "Object finalize 问题",
"descriptionJa": "Object finalize の問題"
},
{
"id": "pmd/OperationWithCloning",
"description": "Operation with cloning"
"description": "Operation with cloning",
"descriptionZh": "克隆操作",
"descriptionJa": "クローン操作"
},
{
"id": "pmd/OverrideBothEqualsAndHashcode",
"description": "Override both equals() and hashCode()"
"description": "Override both equals() and hashCode()",
"descriptionZh": "同时重写 equals() 和 hashCode()",
"descriptionJa": "equals() と hashCode() の両方をオーバーライドする"
},
{
"id": "pmd/OverridingThreadRun",
"description": "Don't override Thread.run()"
"description": "Don't override Thread.run()",
"descriptionZh": "不要重写 Thread.run()",
"descriptionJa": "Thread.run() をオーバーライドしない"
},
{
"id": "pmd/PackageDeclaration",
"description": "Package declaration"
"description": "Package declaration",
"descriptionZh": "包声明",
"descriptionJa": "パッケージ宣言"
},
{
"id": "pmd/ProperCloneImplementation",
"description": "Proper clone implementation"
"description": "Proper clone implementation",
"descriptionZh": "正确的 clone 实现",
"descriptionJa": "適切な clone 実装"
},
{
"id": "pmd/ProperLogger",
"description": "Proper logger"
"description": "Proper logger",
"descriptionZh": "正确的 logger",
"descriptionJa": "適切な logger"
},
{
"id": "pmd/ReturnFromFinallyBlock",
"description": "Return from finally"
"description": "Return from finally",
"descriptionZh": "从 finally 返回",
"descriptionJa": "finally からの return"
},
{
"id": "pmd/SimpleDateFormatNeedsLocale",
"description": "SimpleDateFormat needs locale"
"description": "SimpleDateFormat needs locale",
"descriptionZh": "SimpleDateFormat 需要 locale",
"descriptionJa": "SimpleDateFormat に locale が必要"
},
{
"id": "pmd/SingleMethodSingleton",
"description": "Singleton pattern issues"
"description": "Singleton pattern issues",
"descriptionZh": "单例模式问题",
"descriptionJa": "シングルトンパターンの問題"
},
{
"id": "pmd/SingletonClassReturningNewInstance",
"description": "Singleton returning new instance"
"description": "Singleton returning new instance",
"descriptionZh": "单例返回新实例",
"descriptionJa": "シングルトンが新しいインスタンスを返す"
},
{
"id": "pmd/StaticEJBFieldShouldBeFinal",
"description": "Static EJB field should be final"
"description": "Static EJB field should be final",
"descriptionZh": "静态 EJB 字段应为 final",
"descriptionJa": "静的 EJB フィールドは final にする"
},
{
"id": "pmd/StringBufferInstantiationWithChar",
"description": "StringBuffer with char"
"description": "StringBuffer with char",
"descriptionZh": "StringBuffer 带 char 实例化",
"descriptionJa": "char を伴う StringBuffer インスタンス化"
},
{
"id": "pmd/SuspiciousConstantFieldName",
"description": "Constant field naming"
"description": "Constant field naming",
"descriptionZh": "常量字段命名",
"descriptionJa": "定数フィールドの命名"
},
{
"id": "pmd/SuspiciousEqualsMethodName",
"description": "equals() method signature"
"description": "equals() method signature",
"descriptionZh": "equals() 方法签名",
"descriptionJa": "equals() メソッドのシグネチャ"
},
{
"id": "pmd/SuspiciousHashcodeMethodName",
"description": "hashCode() method signature"
"description": "hashCode() method signature",
"descriptionZh": "hashCode() 方法签名",
"descriptionJa": "hashCode() メソッドのシグネチャ"
},
{
"id": "pmd/SuspiciousOctalEscape",
"description": "Suspicious octal escape"
"description": "Suspicious octal escape",
"descriptionZh": "可疑的八进制转义",
"descriptionJa": "疑わしい8進エスケープ"
},
{
"id": "pmd/TestClassWithoutTestCases",
"description": "Test class without test cases"
"description": "Test class without test cases",
"descriptionZh": "没有测试用例的测试类",
"descriptionJa": "テストケースのないテストクラス"
},
{
"id": "pmd/UnconditionalIfStatement",
"description": "Unconditional if statement"
"description": "Unconditional if statement",
"descriptionZh": "无条件 if 语句",
"descriptionJa": "無条件の if 文"
},
{
"id": "pmd/UnnecessaryBooleanAssertion",
"description": "Unnecessary boolean assertion"
"description": "Unnecessary boolean assertion",
"descriptionZh": "不必要的布尔断言",
"descriptionJa": "不要なブールアサーション"
},
{
"id": "pmd/UnnecessaryCaseChange",
"description": "Unnecessary case change"
"description": "Unnecessary case change",
"descriptionZh": "不必要的大小写转换",
"descriptionJa": "不要なケース変換"
},
{
"id": "pmd/UnnecessaryConversionTemporal",
"description": "Unnecessary temporal conversion"
"description": "Unnecessary temporal conversion",
"descriptionZh": "不必要的时间转换",
"descriptionJa": "不要な時間変換"
},
{
"id": "pmd/UnusedNullCheckInEquals",
"description": "Unused null check in equals"
"description": "Unused null check in equals",
"descriptionZh": "equals 中未使用的 null 检查",
"descriptionJa": "equals 内の未使用 null チェック"
},
{
"id": "pmd/UseConcurrentHashMap",
"description": "Use ConcurrentHashMap for concurrent access"
"description": "Use ConcurrentHashMap for concurrent access",
"descriptionZh": "并发访问使用 ConcurrentHashMap",
"descriptionJa": "並行アクセスに ConcurrentHashMap を使用する"
},
{
"id": "pmd/UseCorrectExceptionLogging",
"description": "Correct exception logging"
"description": "Correct exception logging",
"descriptionZh": "正确的异常日志",
"descriptionJa": "適切な例外ログ"
},
{
"id": "pmd/UseDiamondOperator",
"description": "Use diamond operator <>"
"description": "Use diamond operator <>",
"descriptionZh": "使用菱形运算符 <>",
"descriptionJa": "ダイヤモンド演算子 <> を使用する"
},
{
"id": "pmd/UseEqualsToCompareStrings",
"description": "Use equals() for strings"
"description": "Use equals() for strings",
"descriptionZh": "字符串比较使用 equals()",
"descriptionJa": "文字列の比較に equals() を使用する"
},
{
"id": "pmd/UseLocaleWithCaseConversions",
"description": "Use locale with case conversions"
"description": "Use locale with case conversions",
"descriptionZh": "大小写转换使用 locale",
"descriptionJa": "ケース変換に locale を使用する"
},
{
"id": "pmd/UseNotifyAllInsteadOfNotify",
"description": "Use notifyAll() instead of notify()"
"description": "Use notifyAll() instead of notify()",
"descriptionZh": "使用 notifyAll() 替代 notify()",
"descriptionJa": "notify() の代わりに notifyAll() を使用する"
},
{
"id": "pmd/UseProperClassLoader",
"description": "Use proper classloader"
"description": "Use proper classloader",
"descriptionZh": "使用正确的类加载器",
"descriptionJa": "適切なクラスローダーを使用する"
},
{
"id": "pmd/AddEmptyString",
"description": "Don't add empty strings"
"description": "Don't add empty strings",
"descriptionZh": "不要添加空字符串",
"descriptionJa": "空文字列を追加しない"
},
{
"id": "pmd/AppendCharacterWithChar",
"description": "Append char not string in StringBuffer"
"description": "Append char not string in StringBuffer",
"descriptionZh": "StringBuffer 中追加 char 而非 String",
"descriptionJa": "StringBuffer には String ではなく char を追加する"
},
{
"id": "pmd/AvoidArrayLoops",
"description": "Use Arrays.copyOf or System.arraycopy"
"description": "Use Arrays.copyOf or System.arraycopy",
"descriptionZh": "使用 Arrays.copyOf 或 System.arraycopy",
"descriptionJa": "Arrays.copyOf または System.arraycopy を使用する"
},
{
"id": "pmd/AvoidCalendarDateCreation",
"description": "Avoid Calendar for current time"
"description": "Avoid Calendar for current time",
"descriptionZh": "获取当前时间避免使用 Calendar",
"descriptionJa": "現在時刻に Calendar を使用しない"
},
{
"id": "pmd/AvoidFileStream",
"description": "Avoid FileInputStream/FileOutputStream/FileReader/FileWriter"
"description": "Avoid FileInputStream/FileOutputStream/FileReader/FileWriter",
"descriptionZh": "避免 FileInputStream/FileOutputStream/FileReader/FileWriter",
"descriptionJa": "FileInputStream/FileOutputStream/FileReader/FileWriter を避ける"
},
{
"id": "pmd/AvoidInstantiatingObjectsInLoops",
"description": "Don't instantiate objects in loops"
"description": "Don't instantiate objects in loops",
"descriptionZh": "不要在循环中实例化对象",
"descriptionJa": "ループ内でオブジェクトをインスタンス化しない"
},
{
"id": "pmd/BigIntegerInstantiation",
"description": "Use BigInteger.ZERO/ONE/TEN"
"description": "Use BigInteger.ZERO/ONE/TEN",
"descriptionZh": "使用 BigInteger.ZERO/ONE/TEN",
"descriptionJa": "BigInteger.ZERO/ONE/TEN を使用する"
},
{
"id": "pmd/ConsecutiveAppendsShouldReuse",
"description": "Chain StringBuilder.append calls"
"description": "Chain StringBuilder.append calls",
"descriptionZh": "链式调用 StringBuilder.append",
"descriptionJa": "StringBuilder.append をチェーンで呼ぶ"
},
{
"id": "pmd/ConsecutiveLiteralAppends",
"description": "Combine literal appends"
"description": "Combine literal appends",
"descriptionZh": "合并字面量追加",
"descriptionJa": "リテラルの追加を統合する"
},
{
"id": "pmd/InefficientEmptyStringCheck",
"description": "Use isBlank() instead of trim().isEmpty()"
"description": "Use isBlank() instead of trim().isEmpty()",
"descriptionZh": "使用 isBlank() 替代 trim().isEmpty()",
"descriptionJa": "trim().isEmpty() の代わりに isBlank() を使用する"
},
{
"id": "pmd/InefficientStringBuffering",
"description": "Avoid concatenating in StringBuffer constructor"
"description": "Avoid concatenating in StringBuffer constructor",
"descriptionZh": "避免在 StringBuffer 构造函数中拼接",
"descriptionJa": "StringBuffer コンストラクタ内での連結を避ける"
},
{
"id": "pmd/InsufficientStringBufferDeclaration",
"description": "Pre-size StringBuilder"
"description": "Pre-size StringBuilder",
"descriptionZh": "预先指定 StringBuilder 容量",
"descriptionJa": "StringBuilder の容量を事前指定する"
},
{
"id": "pmd/OptimizableToArrayCall",
"description": "Use new Foo[0] instead of new Foo[size]"
"description": "Use new Foo[0] instead of new Foo[size]",
"descriptionZh": "使用 new Foo[0] 替代 new Foo[size]",
"descriptionJa": "new Foo[size] の代わりに new Foo[0] を使用する"
},
{
"id": "pmd/RedundantFieldInitializer",
"description": "Remove redundant field initializers"
"description": "Remove redundant field initializers",
"descriptionZh": "移除冗余的字段初始化器",
"descriptionJa": "冗長なフィールド初期化子を削除する"
},
{
"id": "pmd/StringInstantiation",
"description": "Avoid new String()"
"description": "Avoid new String()",
"descriptionZh": "避免 new String()",
"descriptionJa": "new String() を避ける"
},
{
"id": "pmd/StringToString",
"description": "Avoid toString() on String"
"description": "Avoid toString() on String",
"descriptionZh": "避免对 String 调用 toString()",
"descriptionJa": "String への toString() を避ける"
},
{
"id": "pmd/TooFewBranchesForSwitch",
"description": "Switch with less than 3 branches"
"description": "Switch with less than 3 branches",
"descriptionZh": "少于3个分支的 switch",
"descriptionJa": "3未満のブランチの switch"
},
{
"id": "pmd/UseArrayListInsteadOfVector",
"description": "ArrayList instead of Vector"
"description": "ArrayList instead of Vector",
"descriptionZh": "使用 ArrayList 替代 Vector",
"descriptionJa": "Vector の代わりに ArrayList を使用する"
},
{
"id": "pmd/UseArraysAsList",
"description": "Use Arrays.asList() instead of loop"
"description": "Use Arrays.asList() instead of loop",
"descriptionZh": "使用 Arrays.asList() 替代循环",
"descriptionJa": "ループの代わりに Arrays.asList() を使用する"
},
{
"id": "pmd/UseIndexOfChar",
"description": "Use indexOf(char) not indexOf(String)"
"description": "Use indexOf(char) not indexOf(String)",
"descriptionZh": "使用 indexOf(char) 而非 indexOf(String)",
"descriptionJa": "indexOf(String) ではなく indexOf(char) を使用する"
},
{
"id": "pmd/UseIOStreamsWithApacheCommonsFileItem",
"description": "Use getInputStream() not get()"
"description": "Use getInputStream() not get()",
"descriptionZh": "使用 getInputStream() 而非 get()",
"descriptionJa": "get() ではなく getInputStream() を使用する"
},
{
"id": "pmd/UselessStringValueOf",
"description": "Don't wrap with String.valueOf()"
"description": "Don't wrap with String.valueOf()",
"descriptionZh": "不要用 String.valueOf() 包裹",
"descriptionJa": "String.valueOf() でラップしない"
},
{
"id": "pmd/UseStringBufferForStringAppends",
"description": "Use StringBuilder for concatenation"
"description": "Use StringBuilder for concatenation",
"descriptionZh": "使用 StringBuilder 进行拼接",
"descriptionJa": "文字列連結に StringBuilder を使用する"
},
{
"id": "pmd/UseStringBufferLength",
"description": "Use length() instead of toString().equals(\"\")"
"description": "Use length() instead of toString().equals(\"\")",
"descriptionZh": "使用 length() 替代 toString().equals(\"\")",
"descriptionJa": "toString().equals(\"\") の代わりに length() を使用する"
},
{
"id": "pmd/HardCodedCryptoKey",
"description": "Don't hard code encryption keys"
"description": "Don't hard code encryption keys",
"descriptionZh": "不要硬编码加密密钥",
"descriptionJa": "暗号鍵をハードコードしない"
},
{
"id": "pmd/InsecureCryptoIv",
"description": "Don't hard code initialization vectors"
"description": "Don't hard code initialization vectors",
"descriptionZh": "不要硬编码初始化向量",
"descriptionJa": "初期化ベクタをハードコードしない"
}
],
"pmd-jsp": [
{
"id": "pmd-jsp/DontNestJsfInJstlIteration",
"description": "Do not nest JSF components inside JSTL iteration"
"description": "Do not nest JSF components inside JSTL iteration",
"descriptionZh": "不要在 JSTL 迭代内嵌套 JSF 组件",
"descriptionJa": "JSTL 反復内に JSF コンポーネントをネストしない"
},
{
"id": "pmd-jsp/NoClassAttribute",
"description": "Use styleclass not class attribute"
"description": "Use styleclass not class attribute",
"descriptionZh": "使用 styleclass 而非 class 属性",
"descriptionJa": "class 属性ではなく styleclass を使用する"
},
{
"id": "pmd-jsp/NoHtmlComments",
"description": "Use JSP comments instead of HTML comments"
"description": "Use JSP comments instead of HTML comments",
"descriptionZh": "使用 JSP 注释而非 HTML 注释",
"descriptionJa": "HTML コメントではなく JSP コメントを使用する"
},
{
"id": "pmd-jsp/NoJspForward",
"description": "Do not forward from within a JSP"
"description": "Do not forward from within a JSP",
"descriptionZh": "不要在 JSP 内转发",
"descriptionJa": "JSP 内からフォワードしない"
},
{
"id": "pmd-jsp/DuplicateJspImports",
"description": "Avoid duplicate imports in JSP"
"description": "Avoid duplicate imports in JSP",
"descriptionZh": "避免 JSP 中重复导入",
"descriptionJa": "JSP 内の重複インポートを避ける"
},
{
"id": "pmd-jsp/NoInlineScript",
"description": "Externalize HTML script content"
"description": "Externalize HTML script content",
"descriptionZh": "将 HTML 脚本内容外部化",
"descriptionJa": "HTML スクリプト内容を外部化する"
},
{
"id": "pmd-jsp/NoInlineStyleInformation",
"description": "Put styles in CSS files"
"description": "Put styles in CSS files",
"descriptionZh": "将样式放入 CSS 文件",
"descriptionJa": "スタイルを CSS ファイルに置く"
},
{
"id": "pmd-jsp/NoLongScripts",
"description": "Avoid long scripts in JSP"
"description": "Avoid long scripts in JSP",
"descriptionZh": "避免 JSP 中的长脚本",
"descriptionJa": "JSP 内の長いスクリプトを避ける"
},
{
"id": "pmd-jsp/NoScriptlets",
"description": "Avoid scriptlets in JSP"
"description": "Avoid scriptlets in JSP",
"descriptionZh": "避免 JSP 中的 scriptlet",
"descriptionJa": "JSP 内のスクリプトレットを避ける"
},
{
"id": "pmd-jsp/JspEncoding",
"description": "JSP files should use UTF-8 encoding"
"description": "JSP files should use UTF-8 encoding",
"descriptionZh": "JSP 文件应使用 UTF-8 编码",
"descriptionJa": "JSP ファイルは UTF-8 エンコーディングを使用すべきである"
}
],
"sql-lint": [
"sqlfluff": [
{
"id": "sql-lint/AL01",
"id": "sqlfluff/AL01",
"description": "Implicit/explicit aliasing of table",
"tier": "P2"
"tier": "P2",
"descriptionZh": "表的隐式/显式别名",
"descriptionJa": "テーブルの暗黙的/明示的エイリアス"
},
{
"id": "sql-lint/AL02",
"id": "sqlfluff/AL02",
"description": "Implicit/explicit aliasing of columns",
"tier": "P0"
"tier": "P0",
"descriptionZh": "列的隐式/显式别名",
"descriptionJa": "列の暗黙的/明示的エイリアス"
},
{
"id": "sql-lint/AL03",
"id": "sqlfluff/AL03",
"description": "Column expression without alias",
"tier": "P0"
"tier": "P0",
"descriptionZh": "无别名的列表达式",
"descriptionJa": "エイリアスなしの列式"
},
{
"id": "sql-lint/AL04",
"id": "sqlfluff/AL04",
"description": "Table aliases should be unique within each clause",
"tier": "P0"
"tier": "P0",
"descriptionZh": "表别名在每个子句中应唯一",
"descriptionJa": "表エイリアスは各句内で一意にすべきである"
},
{
"id": "sql-lint/AL05",
"id": "sqlfluff/AL05",
"description": "Tables should not be aliased if unused",
"tier": "P0"
"tier": "P0",
"descriptionZh": "未使用的表不应加别名",
"descriptionJa": "未使用のテーブルにエイリアスを付けない"
},
{
"id": "sql-lint/AL06",
"id": "sqlfluff/AL06",
"description": "Enforce table alias lengths",
"tier": "P0"
"tier": "P0",
"descriptionZh": "强制表别名长度",
"descriptionJa": "表エイリアスの長さを強制する"
},
{
"id": "sql-lint/AL07",
"id": "sqlfluff/AL07",
"description": "Avoid table aliases",
"tier": "excluded"
"tier": "excluded",
"descriptionZh": "避免表别名",
"descriptionJa": "表エイリアスを避ける"
},
{
"id": "sql-lint/AL08",
"id": "sqlfluff/AL08",
"description": "Column aliases should be unique within each clause",
"tier": "P0"
"tier": "P0",
"descriptionZh": "列别名在每个子句中应唯一",
"descriptionJa": "列エイリアスは各句内で一意にすべきである"
},
{
"id": "sql-lint/AL09",
"id": "sqlfluff/AL09",
"description": "Column aliases should not alias to itself",
"tier": "P0"
"tier": "P0",
"descriptionZh": "列别名不应与自身相同",
"descriptionJa": "列エイリアスが自分自身と同じにならないようにする"
},
{
"id": "sql-lint/AL10",
"id": "sqlfluff/AL10",
"description": "Derived tables must have an alias",
"tier": "P0"
"tier": "P0",
"descriptionZh": "派生表必须使用别名",
"descriptionJa": "派生テーブルにはエイリアスが必要である"
},
{
"id": "sql-lint/AM01",
"id": "sqlfluff/AM01",
"description": "Ambiguous use of DISTINCT with GROUP BY",
"tier": "P0"
"tier": "P0",
"descriptionZh": "DISTINCT 与 GROUP BY 的歧义用法",
"descriptionJa": "DISTINCT と GROUP BY の曖昧な使用"
},
{
"id": "sql-lint/AM02",
"id": "sqlfluff/AM02",
"description": "UNION DISTINCT/ALL preferred over just UNION",
"tier": "P0"
"tier": "P0",
"descriptionZh": "优先使用 UNION DISTINCT/ALL 而非仅 UNION",
"descriptionJa": "単なる UNION より UNION DISTINCT/ALL を優先する"
},
{
"id": "sql-lint/AM03",
"id": "sqlfluff/AM03",
"description": "Ambiguous ordering directions",
"tier": "P1"
"tier": "P1",
"descriptionZh": "歧义的排序方向",
"descriptionJa": "曖昧なソート方向"
},
{
"id": "sql-lint/AM04",
"id": "sqlfluff/AM04",
"description": "Query produces unknown number of result columns",
"tier": "P2"
"tier": "P2",
"descriptionZh": "查询产生未知数量的结果列",
"descriptionJa": "クエリが未知数の結果列を生成する"
},
{
"id": "sql-lint/AM05",
"id": "sqlfluff/AM05",
"description": "Join clauses should be fully qualified",
"tier": "P1"
"tier": "P1",
"descriptionZh": "连接子句应完全限定",
"descriptionJa": "結合句は完全修飾すべきである"
},
{
"id": "sql-lint/AM06",
"id": "sqlfluff/AM06",
"description": "Inconsistent column references in GROUP BY/ORDER BY",
"tier": "P0"
"tier": "P0",
"descriptionZh": "GROUP BY/ORDER BY 中列引用不一致",
"descriptionJa": "GROUP BY/ORDER BY 内の列参照の不整合"
},
{
"id": "sql-lint/AM07",
"id": "sqlfluff/AM07",
"description": "Queries within set query produce different numbers of columns",
"tier": "P2"
"tier": "P2",
"descriptionZh": "集合查询中的子查询产生不同数量的列",
"descriptionJa": "集合クエリ内のサブクエリが異なる数の列を生成する"
},
{
"id": "sql-lint/AM08",
"id": "sqlfluff/AM08",
"description": "Implicit cross join detected",
"tier": "P1"
"tier": "P1",
"descriptionZh": "检测到隐式交叉连接",
"descriptionJa": "暗黙のクロスジョインを検出"
},
{
"id": "sql-lint/AM09",
"id": "sqlfluff/AM09",
"description": "LIMIT/OFFSET without ORDER BY non-deterministic",
"tier": "P2"
"tier": "P2",
"descriptionZh": "无 ORDER BY 的 LIMIT/OFFSET 是非确定性的",
"descriptionJa": "ORDER BY なしの LIMIT/OFFSET は非決定的である"
},
{
"id": "sql-lint/CP01",
"id": "sqlfluff/CP01",
"description": "Inconsistent capitalisation of keywords",
"tier": "P0"
"tier": "P0",
"descriptionZh": "关键字大小写不一致",
"descriptionJa": "キーワードの大文字小文字が不統一"
},
{
"id": "sql-lint/CP02",
"id": "sqlfluff/CP02",
"description": "Inconsistent capitalisation of unquoted identifiers",
"tier": "P0"
"tier": "P0",
"descriptionZh": "未加引号的标识符大小写不一致",
"descriptionJa": "引用なし識別子の大文字小文字が不統一"
},
{
"id": "sql-lint/CP03",
"id": "sqlfluff/CP03",
"description": "Inconsistent capitalisation of function names",
"tier": "P0"
"tier": "P0",
"descriptionZh": "函数名大小写不一致",
"descriptionJa": "関数名の大文字小文字が不統一"
},
{
"id": "sql-lint/CP04",
"id": "sqlfluff/CP04",
"description": "Inconsistent capitalisation of boolean/null literal",
"tier": "P0"
"tier": "P0",
"descriptionZh": "布尔/null 字面量大小写不一致",
"descriptionJa": "ブール/null リテラルの大文字小文字が不統一"
},
{
"id": "sql-lint/CP05",
"id": "sqlfluff/CP05",
"description": "Inconsistent capitalisation of datatypes",
"tier": "P0"
"tier": "P0",
"descriptionZh": "数据类型大小写不一致",
"descriptionJa": "データ型の大文字小文字が不統一"
},
{
"id": "sql-lint/CV01",
"id": "sqlfluff/CV01",
"description": "Consistent usage of != or <>",
"tier": "P1"
"tier": "P1",
"descriptionZh": "一致地使用 != 或 <>",
"descriptionJa": "!= または <> を一貫して使用する"
},
{
"id": "sql-lint/CV02",
"id": "sqlfluff/CV02",
"description": "Use COALESCE instead of IFNULL/NVL",
"tier": "P1"
"tier": "P1",
"descriptionZh": "使用 COALESCE 替代 IFNULL/NVL",
"descriptionJa": "IFNULL/NVL の代わりに COALESCE を使用する"
},
{
"id": "sql-lint/CV03",
"id": "sqlfluff/CV03",
"description": "Trailing commas within select clause",
"tier": "P0"
"tier": "P0",
"descriptionZh": "select 子句中的尾随逗号",
"descriptionJa": "select 句内の末尾カンマ"
},
{
"id": "sql-lint/CV04",
"id": "sqlfluff/CV04",
"description": "Consistent syntax for count number of rows",
"tier": "P0"
"tier": "P0",
"descriptionZh": "计数行数的一致语法",
"descriptionJa": "行数を数える一貫した構文"
},
{
"id": "sql-lint/CV05",
"id": "sqlfluff/CV05",
"description": "Comparisons with NULL should use IS or IS NOT",
"tier": "P0"
"tier": "P0",
"descriptionZh": "与 NULL 比较应使用 IS 或 IS NOT",
"descriptionJa": "NULL との比較には IS または IS NOT を使用する"
},
{
"id": "sql-lint/CV06",
"id": "sqlfluff/CV06",
"description": "Statements must end with a semi-colon",
"tier": "P1"
"tier": "P1",
"descriptionZh": "语句必须以分号结尾",
"descriptionJa": "文はセミコロンで終わらせる"
},
{
"id": "sql-lint/CV07",
"id": "sqlfluff/CV07",
"description": "Top-level statements should not be wrapped in brackets",
"tier": "P2"
"tier": "P2",
"descriptionZh": "顶层语句不应包裹在括号中",
"descriptionJa": "トップレベルの文を括弧で囲まない"
},
{
"id": "sql-lint/CV08",
"id": "sqlfluff/CV08",
"description": "Use LEFT JOIN instead of RIGHT JOIN",
"tier": "P1"
"tier": "P1",
"descriptionZh": "使用 LEFT JOIN 替代 RIGHT JOIN",
"descriptionJa": "RIGHT JOIN の代わりに LEFT JOIN を使用する"
},
{
"id": "sql-lint/CV09",
"id": "sqlfluff/CV09",
"description": "Block a list of configurable words",
"tier": "excluded"
"tier": "excluded",
"descriptionZh": "屏蔽一组可配置的词",
"descriptionJa": "設定可能な語のリストをブロックする"
},
{
"id": "sql-lint/CV10",
"id": "sqlfluff/CV10",
"description": "Consistent usage of preferred quotes for quoted literals",
"tier": "excluded"
"tier": "excluded",
"descriptionZh": "引用的字面量一致使用首选引号",
"descriptionJa": "引用リテラルに推奨引用符を一貫して使用する"
},
{
"id": "sql-lint/CV11",
"id": "sqlfluff/CV11",
"description": "Enforce consistent type casting style",
"tier": "P2"
"tier": "P2",
"descriptionZh": "强制一致的类型转换风格",
"descriptionJa": "一貫した型変換スタイルを強制する"
},
{
"id": "sql-lint/CV12",
"id": "sqlfluff/CV12",
"description": "Use JOIN ... ON ... instead of WHERE ... for join conditions",
"tier": "P1"
"tier": "P1",
"descriptionZh": "连接条件使用 JOIN ... ON ... 而非 WHERE",
"descriptionJa": "結合条件に WHERE ではなく JOIN ... ON ... を使用する"
},
{
"id": "sql-lint/JJ01",
"id": "sqlfluff/JJ01",
"description": "Jinja tags should have single whitespace on either side",
"tier": "P0"
"tier": "P0",
"descriptionZh": "Jinja 标签两侧应各有一个空格",
"descriptionJa": "Jinja タグの両側に空白を1つ置く"
},
{
"id": "sql-lint/LT01",
"id": "sqlfluff/LT01",
"description": "Inappropriate Spacing",
"tier": "P0"
"tier": "P0",
"descriptionZh": "不合适的间距",
"descriptionJa": "不適切な間隔"
},
{
"id": "sql-lint/LT02",
"id": "sqlfluff/LT02",
"description": "Incorrect Indentation",
"tier": "P0"
"tier": "P0",
"descriptionZh": "不正确的缩进",
"descriptionJa": "不適切なインデント"
},
{
"id": "sql-lint/LT03",
"id": "sqlfluff/LT03",
"description": "Operators before/after newlines",
"tier": "excluded"
"tier": "excluded",
"descriptionZh": "运算符在换行前/后",
"descriptionJa": "改行前後への演算子の配置"
},
{
"id": "sql-lint/LT04",
"id": "sqlfluff/LT04",
"description": "Leading/Trailing comma enforcement",
"tier": "excluded"
"tier": "excluded",
"descriptionZh": "前导/尾随逗号的强制",
"descriptionJa": "先頭/末尾カンマの強制"
},
{
"id": "sql-lint/LT05",
"id": "sqlfluff/LT05",
"description": "Line is too long",
"tier": "P0"
"tier": "P0",
"descriptionZh": "行过长",
"descriptionJa": "行が長すぎる"
},
{
"id": "sql-lint/LT06",
"id": "sqlfluff/LT06",
"description": "Function name not followed by parenthesis",
"tier": "P0"
"tier": "P0",
"descriptionZh": "函数名后未跟括号",
"descriptionJa": "関数名の後に括弧がない"
},
{
"id": "sql-lint/LT07",
"id": "sqlfluff/LT07",
"description": "WITH clause closing bracket on new line",
"tier": "P0"
"tier": "P0",
"descriptionZh": "WITH 子句的右括号应在新行",
"descriptionJa": "WITH 句の閉じ括弧を新しい行に置く"
},
{
"id": "sql-lint/LT08",
"id": "sqlfluff/LT08",
"description": "Blank line after CTE closing bracket",
"tier": "P0"
"tier": "P0",
"descriptionZh": "CTE 右括号后应有空行",
"descriptionJa": "CTE の閉じ括弧の後に空行を置く"
},
{
"id": "sql-lint/LT09",
"id": "sqlfluff/LT09",
"description": "Select targets on new line",
"tier": "excluded"
"tier": "excluded",
"descriptionZh": "select 目标在新行",
"descriptionJa": "select 対象を新しい行に置く"
},
{
"id": "sql-lint/LT10",
"id": "sqlfluff/LT10",
"description": "SELECT modifiers on same line as SELECT",
"tier": "P0"
"tier": "P0",
"descriptionZh": "SELECT 修饰符与 SELECT 同行",
"descriptionJa": "SELECT 修飾子を SELECT と同じ行に置く"
},
{
"id": "sql-lint/LT11",
"id": "sqlfluff/LT11",
"description": "Set operators surrounded by newlines",
"tier": "P0"
"tier": "P0",
"descriptionZh": "集合运算符周围应有换行",
"descriptionJa": "集合演算子を改行で囲む"
},
{
"id": "sql-lint/LT12",
"id": "sqlfluff/LT12",
"description": "Files must end with single trailing newline",
"tier": "P0"
"tier": "P0",
"descriptionZh": "文件必须以单个尾随换行结束",
"descriptionJa": "ファイルは単一の末尾改行で終わる"
},
{
"id": "sql-lint/LT13",
"id": "sqlfluff/LT13",
"description": "Files must not begin with newlines/whitespace",
"tier": "P1"
"tier": "P1",
"descriptionZh": "文件不得以换行/空白开头",
"descriptionJa": "ファイルを改行/空白で始めない"
},
{
"id": "sql-lint/LT14",
"id": "sqlfluff/LT14",
"description": "Keyword clauses before/after newlines",
"tier": "P1"
"tier": "P1",
"descriptionZh": "关键字子句在换行前/后",
"descriptionJa": "キーワード句の改行前後の配置"
},
{
"id": "sql-lint/LT15",
"id": "sqlfluff/LT15",
"description": "Too many consecutive blank lines",
"tier": "P1"
"tier": "P1",
"descriptionZh": "连续空行过多",
"descriptionJa": "連続する空行が多すぎる"
},
{
"id": "sql-lint/OR01",
"id": "sqlfluff/OR01",
"description": "Remove empty batches",
"tier": "P2"
"tier": "P2",
"descriptionZh": "移除空批次",
"descriptionJa": "空のバッチを削除する"
},
{
"id": "sql-lint/PG01",
"id": "sqlfluff/PG01",
"description": "Avoid excessive locks in PostgreSQL DDL",
"tier": "P2"
"tier": "P2",
"descriptionZh": "避免 PostgreSQL DDL 中过度的锁",
"descriptionJa": "PostgreSQL DDL での過度なロックを避ける"
},
{
"id": "sql-lint/RF01",
"id": "sqlfluff/RF01",
"description": "References cannot reference objects not in FROM clause",
"tier": "P0"
"tier": "P0",
"descriptionZh": "引用不能引用 FROM 子句中不存在的对象",
"descriptionJa": "FROM 句にないオブジェクトを参照できない"
},
{
"id": "sql-lint/RF02",
"id": "sqlfluff/RF02",
"description": "References should be qualified if multiple tables",
"tier": "P1"
"tier": "P1",
"descriptionZh": "多表时应限定引用",
"descriptionJa": "複数テーブルの場合は参照を修飾すべきである"
},
{
"id": "sql-lint/RF03",
"id": "sqlfluff/RF03",
"description": "Column references consistent in single table statements",
"tier": "excluded"
"tier": "excluded",
"descriptionZh": "单表语句中列引用一致",
"descriptionJa": "単一テーブル文での列参照の一貫性"
},
{
"id": "sql-lint/RF04",
"id": "sqlfluff/RF04",
"description": "Keywords should not be used as identifiers",
"tier": "P1"
"tier": "P1",
"descriptionZh": "关键字不应用作标识符",
"descriptionJa": "キーワードを識別子として使用しない"
},
{
"id": "sql-lint/RF05",
"id": "sqlfluff/RF05",
"description": "No special characters in identifiers",
"tier": "P1"
"tier": "P1",
"descriptionZh": "标识符中不应有特殊字符",
"descriptionJa": "識別子に特殊文字を含めない"
},
{
"id": "sql-lint/RF06",
"id": "sqlfluff/RF06",
"description": "Unnecessary quoted identifier",
"tier": "P1"
"tier": "P1",
"descriptionZh": "不必要的加引号标识符",
"descriptionJa": "不要な引用付き識別子"
},
{
"id": "sql-lint/ST01",
"id": "sqlfluff/ST01",
"description": "Do not specify else null in CASE WHEN",
"tier": "P1"
"tier": "P1",
"descriptionZh": "不要在 CASE WHEN 中指定 else null",
"descriptionJa": "CASE WHEN で else null を指定しない"
},
{
"id": "sql-lint/ST02",
"id": "sqlfluff/ST02",
"description": "Unnecessary CASE statement",
"tier": "P1"
"tier": "P1",
"descriptionZh": "不必要的 CASE 语句",
"descriptionJa": "不要な CASE 文"
},
{
"id": "sql-lint/ST03",
"id": "sqlfluff/ST03",
"description": "Unused CTE",
"tier": "P0"
"tier": "P0",
"descriptionZh": "未使用的 CTE",
"descriptionJa": "未使用の CTE"
},
{
"id": "sql-lint/ST04",
"id": "sqlfluff/ST04",
"description": "Nested CASE in ELSE clause can be flattened",
"tier": "P1"
"tier": "P1",
"descriptionZh": "ELSE 子句中的嵌套 CASE 可以扁平化",
"descriptionJa": "ELSE 句内のネスト CASE は平坦化できる"
},
{
"id": "sql-lint/ST05",
"id": "sqlfluff/ST05",
"description": "Subqueries in Join/From clauses; use CTEs",
"tier": "P1"
"tier": "P1",
"descriptionZh": "Join/From 子句中的子查询;使用 CTE",
"descriptionJa": "Join/From 句内のサブクエリ;CTE を使用する"
},
{
"id": "sql-lint/ST06",
"id": "sqlfluff/ST06",
"description": "Column order: wildcards, simple targets, then calculations",
"tier": "P1"
"tier": "P1",
"descriptionZh": "列顺序:通配符、简单目标、然后计算",
"descriptionJa": "列の順序:ワイルドカード、単純対象、計算の順"
},
{
"id": "sql-lint/ST07",
"id": "sqlfluff/ST07",
"description": "Prefer ON over USING for join keys",
"tier": "P1"
"tier": "P1",
"descriptionZh": "连接键优先使用 ON 而非 USING",
"descriptionJa": "結合キーに USING より ON を優先する"
},
{
"id": "sql-lint/ST08",
"id": "sqlfluff/ST08",
"description": "DISTINCT used with parentheses",
"tier": "P0"
"tier": "P0",
"descriptionZh": "DISTINCT 与括号一起使用",
"descriptionJa": "DISTINCT が括弧付きで使用されている"
},
{
"id": "sql-lint/ST09",
"id": "sqlfluff/ST09",
"description": "Join condition order",
"tier": "P1"
"tier": "P1",
"descriptionZh": "连接条件顺序",
"descriptionJa": "結合条件の順序"
},
{
"id": "sql-lint/ST10",
"id": "sqlfluff/ST10",
"description": "Redundant constant expression",
"tier": "P1"
"tier": "P1",
"descriptionZh": "冗余的常量表达式",
"descriptionJa": "冗長な定数式"
},
{
"id": "sql-lint/ST11",
"id": "sqlfluff/ST11",
"description": "Joined table not referenced",
"tier": "P1"
"tier": "P1",
"descriptionZh": "被连接的表未被引用",
"descriptionJa": "結合されたテーブルが参照されていない"
},
{
"id": "sql-lint/ST12",
"id": "sqlfluff/ST12",
"description": "Consecutive semicolons",
"tier": "P1"
"tier": "P1",
"descriptionZh": "连续的分号",
"descriptionJa": "連続するセミコロン"
},
{
"id": "sql-lint/TQ01",
"id": "sqlfluff/TQ01",
"description": "SP_ prefix should not be used for user-defined stored procedures",
"tier": "P2"
"tier": "P2",
"descriptionZh": "用户定义存储过程不应使用 SP_ 前缀",
"descriptionJa": "ユーザー定義ストアドプロシージャに SP_ プレフィックスを使わない"
},
{
"id": "sql-lint/TQ02",
"id": "sqlfluff/TQ02",
"description": "Procedure bodies with multiple statements wrapped in BEGIN/END",
"tier": "P2"
"tier": "P2",
"descriptionZh": "多语句的过程体用 BEGIN/END 包裹",
"descriptionJa": "複数文のプロシージャ本体を BEGIN/END で囲む"
},
{
"id": "sql-lint/TQ03",
"id": "sqlfluff/TQ03",
"description": "Remove empty batches",
"tier": "P2"
"tier": "P2",
"descriptionZh": "移除空批次",
"descriptionJa": "空のバッチを削除する"
}
]
}
+7 -2
View File
@@ -11,6 +11,11 @@ interface RuleYamlItem {
excludeLanguages?: string[];
}
function stripQuotes(raw: string): string {
const m = raw.match(/^(['"])(.*)\1$/);
return m ? m[2] : raw;
}
function parseYamlSimple(content: string): object[] {
const items: Array<Record<string, unknown>> = [];
let current: Record<string, unknown> | null = null;
@@ -31,7 +36,7 @@ function parseYamlSimple(content: string): object[] {
s.trim().replace(/^['"]|['"]$/g, '')
);
} else {
current[key] = raw;
current[key] = stripQuotes(raw);
}
}
} else if (current) {
@@ -46,7 +51,7 @@ function parseYamlSimple(content: string): object[] {
s.trim().replace(/^['"]|['"]$/g, '')
);
} else {
current[key] = raw;
current[key] = stripQuotes(raw);
}
}
}
+251
View File
@@ -0,0 +1,251 @@
import * as vscode from 'vscode';
export interface MethodScope {
name: string;
range: vscode.Range;
code: string;
signature: string;
callers: string[];
callees: string[];
role: string;
}
export interface MethodSymbol {
name: string;
range: vscode.Range;
containerName?: string;
}
interface RawDocSymbol {
name: string;
kind: vscode.SymbolKind;
range?: vscode.Range;
children?: RawDocSymbol[];
location?: vscode.Location;
}
const CONTAINER_KINDS = new Set([
vscode.SymbolKind.Class,
vscode.SymbolKind.Interface,
vscode.SymbolKind.Namespace,
vscode.SymbolKind.Module,
vscode.SymbolKind.Object,
vscode.SymbolKind.Struct,
vscode.SymbolKind.Enum,
vscode.SymbolKind.Package,
]);
function isMethodKind(kind: vscode.SymbolKind): boolean {
return (
kind === vscode.SymbolKind.Function ||
kind === vscode.SymbolKind.Method ||
kind === vscode.SymbolKind.Constructor
);
}
function collectSymbols(symbol: RawDocSymbol, containerName: string | undefined, out: MethodSymbol[]): void {
const kind = symbol.kind;
if (isMethodKind(kind)) {
const range = symbol.range ?? symbol.location?.range;
if (range) {
out.push({ name: symbol.name, range, containerName });
}
}
const nextContainer = CONTAINER_KINDS.has(kind)
? containerName
? `${containerName}.${symbol.name}`
: symbol.name
: containerName;
for (const child of symbol.children ?? []) {
collectSymbols(child, nextContainer, out);
}
}
export async function getMethodSymbols(document: vscode.TextDocument): Promise<MethodSymbol[]> {
let raw: RawDocSymbol[] | undefined;
try {
raw = await vscode.commands.executeCommand<RawDocSymbol[]>(
'vscode.executeDocumentSymbolProvider',
document.uri
);
} catch {
raw = undefined;
}
if (raw && raw.length > 0) {
const symbols: MethodSymbol[] = [];
for (const symbol of raw) {
collectSymbols(symbol, undefined, symbols);
}
if (symbols.length > 0) {
return symbols;
}
}
return fallbackRegexSymbols(document);
}
export async function extractMethodScope(
document: vscode.TextDocument,
range: vscode.Range
): Promise<MethodScope | null> {
const symbols = await getMethodSymbols(document);
if (symbols.length === 0) { return null; }
const target = findTargetSymbol(symbols, range);
if (!target) { return null; }
const ranges = new Map<MethodSymbol, vscode.Range>();
for (const symbol of symbols) {
ranges.set(
symbol,
symbol.range.isEmpty
? expandToMethodBody(document, symbol.range.start.line)
: symbol.range
);
}
const targetRange = ranges.get(target)!;
const code = document.getText(targetRange);
if (!code.trim()) { return null; }
const callers: string[] = [];
const callees: string[] = [];
const targetPattern = new RegExp(`\\b${escapeRegExp(target.name)}\\s*\\(`);
for (const symbol of symbols) {
if (symbol === target) { continue; }
const otherCode = document.getText(ranges.get(symbol)!);
if (otherCode && targetPattern.test(otherCode)) {
callers.push(symbol.name);
}
const otherPattern = new RegExp(`\\b${escapeRegExp(symbol.name)}\\s*\\(`);
if (code && otherPattern.test(code)) {
callees.push(symbol.name);
}
}
return {
name: target.name,
range: targetRange,
code,
signature: extractSignature(code, target.name),
callers,
callees,
role: inferRole(target),
};
}
function findTargetSymbol(symbols: MethodSymbol[], range: vscode.Range): MethodSymbol | null {
const containing = symbols.filter(s => s.range.contains(range));
if (containing.length === 0) { return null; }
let best = containing[0];
for (const symbol of containing) {
if (symbol.range.start.isAfter(best.range.start)) {
best = symbol;
}
}
return best;
}
function escapeRegExp(str: string): string {
return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}
function extractSignature(code: string, fallbackName: string): string {
const lines = code.split('\n');
const sigLines: string[] = [];
for (const line of lines) {
const trimmed = line.trim();
if (!trimmed) { continue; }
if (trimmed.startsWith('//') || trimmed.startsWith('/*') || trimmed.startsWith('*') || trimmed.startsWith('*/')) {
continue;
}
sigLines.push(trimmed);
if (trimmed.includes('{') || trimmed.endsWith(')')) { break; }
}
let sig = sigLines.join(' ').replace(/\{.*$/, '').trim();
sig = sig.replace(/\s{2,}/g, ' ');
return sig || fallbackName;
}
function inferRole(symbol: MethodSymbol): string {
const container = symbol.containerName ?? '';
const name = symbol.name.toLowerCase();
if (container.includes('Controller')) { return 'HTTP 请求处理入口'; }
if (container.includes('Service')) { return '业务逻辑处理'; }
if (container.includes('Repository') || container.includes('Dao')) { return '数据访问'; }
if (name.startsWith('get') || name.startsWith('set') || name.startsWith('is')) { return '属性访问器'; }
if (name.startsWith('init') || name.startsWith('on')) { return '生命周期回调'; }
if (name.startsWith('handle') || name.startsWith('process')) { return '流程处理'; }
if (name.startsWith('build') || name.startsWith('create')) { return '工厂/构建'; }
if (name.startsWith('parse') || name.startsWith('convert') || name.startsWith('transform')) { return '数据转换'; }
return '通用方法';
}
function fallbackRegexSymbols(document: vscode.TextDocument): MethodSymbol[] {
const text = document.getText();
const lang = document.languageId;
const patterns: RegExp[] = [];
if (['typescript', 'javascript', 'typescriptreact', 'javascriptreact'].includes(lang)) {
patterns.push(/(?:async\s+)?function\s+(\w+)/g);
patterns.push(/(?:const|let|var)\s+(\w+)\s*=\s*(?:async\s*)?(?:function\s*)?\(/g);
} else if (['java', 'kotlin', 'go'].includes(lang)) {
patterns.push(/((?:public|private|protected|static)\s+)*\w+(?:<[^>]+>)?\s+(\w+)\s*\(/g);
}
const symbols: MethodSymbol[] = [];
const seen = new Set<string>();
for (const pattern of patterns) {
pattern.lastIndex = 0;
let match: RegExpExecArray | null;
while ((match = pattern.exec(text)) !== null) {
const name = match[1] ?? match[2];
if (!name) { continue; }
const start = document.positionAt(match.index);
const key = `${name}@${start.line}`;
if (seen.has(key)) { continue; }
seen.add(key);
symbols.push({ name, range: new vscode.Range(start, start) });
}
}
return symbols;
}
function expandToMethodBody(document: vscode.TextDocument, startLine: number): vscode.Range {
const start = new vscode.Position(startLine, 0);
const text = document.getText();
const startOffset = document.offsetAt(start);
let depth = 0;
let inString: string | null = null;
let i = startOffset;
while (i < text.length) {
const ch = text[i];
if (inString) {
if (ch === '\\') { i += 2; continue; }
if (ch === inString) { inString = null; }
} else if (ch === '"' || ch === "'" || ch === '`') {
inString = ch;
} else if (ch === '/') {
if (text[i + 1] === '/') {
while (i < text.length && text[i] !== '\n') { i++; }
continue;
}
if (text[i + 1] === '*') {
i += 2;
while (i < text.length && !(text[i] === '*' && text[i + 1] === '/')) { i++; }
i += 2;
continue;
}
} else if (ch === '{') {
depth++;
} else if (ch === '}') {
depth--;
if (depth === 0) {
return new vscode.Range(start, document.positionAt(i + 1));
}
}
i++;
}
const lastLine = document.lineCount - 1;
return new vscode.Range(start, document.lineAt(lastLine).range.end);
}
+36
View File
@@ -0,0 +1,36 @@
import * as vscode from 'vscode';
export interface ReviewStatus {
issueCount: number;
timestamp: number;
}
export class ReviewStatusCache {
private cache = new Map<string, ReviewStatus>();
get(uri: vscode.Uri, methodName: string): ReviewStatus | null {
const key = this.buildKey(uri, methodName);
return this.cache.get(key) ?? null;
}
set(uri: vscode.Uri, methodName: string, issueCount: number): void {
const key = this.buildKey(uri, methodName);
this.cache.set(key, {
issueCount,
timestamp: Date.now(),
});
}
clearDocument(uri: vscode.Uri): void {
const prefix = uri.toString() + '::';
for (const key of this.cache.keys()) {
if (key.startsWith(prefix)) {
this.cache.delete(key);
}
}
}
private buildKey(uri: vscode.Uri, methodName: string): string {
return uri.toString() + '::' + methodName;
}
}
+56
View File
@@ -0,0 +1,56 @@
import * as vscode from 'vscode';
import { getMethodSymbols } from '../scope/method-extractor';
import { ReviewStatusCache, type ReviewStatus } from '../scope/status-cache';
import { t } from '../i18n/messages';
export class MethodCodeLensProvider implements vscode.CodeLensProvider {
private _onDidChangeCodeLenses: vscode.EventEmitter<void> = new vscode.EventEmitter<void>();
readonly onDidChangeCodeLenses: vscode.Event<void> = this._onDidChangeCodeLenses.event;
constructor(private statusCache: ReviewStatusCache) {}
refresh(): void {
this._onDidChangeCodeLenses.fire();
}
async provideCodeLenses(
document: vscode.TextDocument,
token: vscode.CancellationToken
): Promise<vscode.CodeLens[]> {
const config = vscode.workspace.getConfiguration('vscode-code-reviewer');
const enabled = config.get<boolean>('codelens.enabled', true);
if (!enabled) { return []; }
const languages = config.get<string[]>('codelens.languages', [
'typescript', 'javascript', 'java', 'python'
]);
if (!languages.includes(document.languageId)) { return []; }
const symbols = await getMethodSymbols(document);
if (symbols.length === 0) { return []; }
if (symbols.length > 50) { return []; }
const lenses: vscode.CodeLens[] = [];
for (const symbol of symbols) {
const status = this.statusCache.get(document.uri, symbol.name);
const title = this.buildLensTitle(status);
const line = symbol.range.start.line;
lenses.push(new vscode.CodeLens(new vscode.Range(line, 0, line, 0), {
command: 'codeReviewer.reviewMethod',
title,
arguments: [symbol.range],
}));
}
return lenses;
}
private buildLensTitle(status: ReviewStatus | null): string {
if (!status) {
return t('codelens.reviewMethod');
}
if (status.issueCount === 0) {
return t('codelens.reviewedClean');
}
return t('codelens.reviewedWithIssues', { 0: String(status.issueCount) });
}
}
+26 -23
View File
@@ -16,7 +16,7 @@ import { ExcelConverter } from '../rules/converters/excel-converter';
import { DocxConverter } from '../rules/converters/docx-converter';
import { PptxConverter } from '../rules/converters/pptx-converter';
import { t, onLanguageChange } from '../i18n/messages';
import { getEslintConfigPath, getStylelintConfigPath, getPMDRulesetPath, getSqlLintConfigFile, isAdapterEnabled, setAdapterEnabled } from '../config/linter';
import { getEslintConfigPath, getStylelintConfigPath, getPMDRulesetPath, getSqlFluffConfigFile, isAdapterEnabled, setAdapterEnabled } from '../config/linter';
type ConfigMode = 'builtin' | 'project' | 'global';
@@ -52,22 +52,22 @@ function getPmdRulesetTemplate(): string {
function getSqlfluffTemplate(): string {
return `[sqlfluff]
# ${t('setup.template.sqlfluffDialect')}
dialect = postgres
dialect = mysql
# ${t('setup.template.sqlfluffRules')}
rules = all`;
}
function getEslintTemplate(): string {
return `module.exports = {
root: true,
env: { node: true, es2022: true },
parserOptions: { ecmaVersion: 2022, sourceType: 'module' },
rules: {
'no-unused-vars': 'warn', // ${t('setup.template.eslintComment1')}
'no-console': 'off', // ${t('setup.template.eslintComment2')}
'semi': ['error', 'always'], // ${t('setup.template.eslintComment3')}
return `module.exports = [
{
languageOptions: { ecmaVersion: 2022, sourceType: 'module' },
rules: {
'no-unused-vars': 'warn', // ${t('setup.template.eslintComment1')}
'no-console': 'off', // ${t('setup.template.eslintComment2')}
'semi': ['error', 'always'], // ${t('setup.template.eslintComment3')}
},
},
};`;
];`;
}
function getStylelintTemplate(): string {
@@ -98,10 +98,10 @@ const ADAPTER_METADATA: Record<string, {
configFileTemplate: getPmdRulesetTemplate,
i18nKey: 'pmd',
},
'sql-lint': {
name: 'SQL-Lint',
'sqlfluff': {
name: 'SQLFluff',
projectConfigFileName: '.sqlfluff',
settingsTarget: 'vscode-code-reviewer.sql-lint',
settingsTarget: 'vscode-code-reviewer.sqlfluff',
hasExternalDependency: true,
dependencyLabel: 'Python + sqlfluff',
configFileTemplate: getSqlfluffTemplate,
@@ -109,7 +109,7 @@ const ADAPTER_METADATA: Record<string, {
},
eslint: {
name: 'ESLint',
projectConfigFileName: '.eslintrc.js',
projectConfigFileName: 'eslint.config.js',
settingsTarget: 'vscode-code-reviewer.linters',
hasExternalDependency: false,
configFileTemplate: getEslintTemplate,
@@ -126,17 +126,17 @@ const ADAPTER_METADATA: Record<string, {
};
const PROJECT_CONFIG_FILES: Record<string, string[]> = {
eslint: ['.eslintrc.js', '.eslintrc.json', '.eslintrc.yaml', '.eslintrc.yml', '.eslintrc', 'eslint.config.js', 'eslint.config.mjs'],
eslint: ['eslint.config.js', 'eslint.config.mjs', 'eslint.config.cjs', 'eslint.config.ts', 'eslint.config.mts', 'eslint.config.cts'],
stylelint: ['.stylelintrc.js', '.stylelintrc.json', '.stylelintrc.yaml', '.stylelintrc.yml', '.stylelintrc', 'stylelint.config.js'],
pmd: ['ruleset.xml'],
'sql-lint': ['.sqlfluff'],
'sqlfluff': ['.sqlfluff'],
};
const GLOBAL_CONFIG_GETTERS: Record<string, () => string> = {
eslint: getEslintConfigPath,
stylelint: getStylelintConfigPath,
pmd: getPMDRulesetPath,
'sql-lint': getSqlLintConfigFile,
'sqlfluff': getSqlFluffConfigFile,
};
export class SetupViewProvider implements vscode.WebviewViewProvider {
@@ -305,7 +305,7 @@ export class SetupViewProvider implements vscode.WebviewViewProvider {
e.affectsConfiguration('vscode-code-reviewer.linter') ||
e.affectsConfiguration('vscode-code-reviewer.linters') ||
e.affectsConfiguration('vscode-code-reviewer.pmd') ||
e.affectsConfiguration('vscode-code-reviewer.sql-lint')
e.affectsConfiguration('vscode-code-reviewer.sqlfluff')
) {
this.pushConfig();
}
@@ -397,12 +397,15 @@ export class SetupViewProvider implements vscode.WebviewViewProvider {
try {
const provider = createProvider(config.provider, apiKey, config.baseUrl, this.context.extensionUri);
await provider.chat('回复 ok', 'ping', {
const result = await provider.chat('回复 ok', 'ping', {
model: config.model,
temperature: 0,
maxTokens: 1024,
timeoutMs: 15000,
});
if (!result || result.trim() === '') {
throw new Error(t('setup.emptyResponse'));
}
this.connectionTested = true;
this.connectionSuccess = true;
await this.saveConnectionState();
@@ -452,7 +455,7 @@ export class SetupViewProvider implements vscode.WebviewViewProvider {
);
});
const decision = await showImportPreview(conversion);
const decision = await showImportPreview(conversion, this.context);
if (!decision || !decision.confirmed) {
vscode.window.showInformationMessage(t('setup.importCancelled'));
return;
@@ -512,7 +515,7 @@ export class SetupViewProvider implements vscode.WebviewViewProvider {
title: t('setup.importing'),
}, () => this.importService.convert(srcPath, this.context));
const decision = await showImportPreview(conversion);
const decision = await showImportPreview(conversion, this.context);
if (!decision || !decision.confirmed) {
vscode.window.showInformationMessage(t('setup.importCancelled'));
return;
@@ -1158,7 +1161,7 @@ input::placeholder { color: var(--vscode-input-placeholderForeground, var(--vsco
if (meta.hasExternalDependency) {
if (id === 'pmd') {
dependencyStatus = this.checkJavaReady() ? 'ready' : 'missing';
} else if (id === 'sql-lint') {
} else if (id === 'sqlfluff') {
dependencyStatus = this.checkPythonReady() ? 'ready' : 'missing';
}
}