feat: 创建项目配置=内置配置(三语注释)+ 设置面板帮助图标 + 快速开始三步文案重写 + 图标更新
This commit is contained in:
@@ -179,3 +179,9 @@
|
||||
| 2026-08-04 20:59 | ① 用户提出 → ② 需求澄清 → ③ 方案设计 → ④ 人类审批 → ⑤ 编码实现 → ⑥ 审查验证 | 修复 AI 审查行号显示错误(整文件审查多 1 行):根因是 AI 返回的 findings[].line 为 1 基(基于带行号代码),而 webview/report 按 0 基处理(buildIssueItem/formatLine 显示 line+1、navigate 直接作 0 基跳转),方法级审查因 commands.ts 已转 0 基而正确、两条路径基准不一致。修复:mergeResults 统一将 aiFindings.line 转 0 基(Math.max(0, f.line-1)),reviewMethod 的 aiFindings 偏移由 f.line+methodLine-1 改为 f.line+methodLine(传 1 基文件行号交 mergeResults 转换,与 customRuleResults 语义一致);更新 merger.test.ts 排序断言(aiFindings error:3/warning:6)+ 新增 0 基转换用例 | 中间产物:无;根因追踪:先假设「mergeResults 统一转 0 基」并在 merger.test.ts 改写断言验证(4 passing);全量测试沿用既有惯例临时移开 adapter/extension.test.js 后 70 用例全部通过(新增 1 个转换用例),恢复原文件 | src/merger/merger.ts, src/activation/commands.ts, src/test/merger.test.ts | deepseek-v4-flash |
|
||||
| 2026-08-04 21:20 | ① 用户提出 → ② 需求澄清 → ③ 方案设计 → ④ 人类审批 → ⑤ 编码实现 → ⑥ 审查验证 | SQLFluff PRS 解析错误呈现优化:sqlfluff.ts 新增可导出纯函数 buildPRSMessage(正则提取 Found unparsable section 引号内片段、正则失败回退原文、换行单行化 \\n、超 80 字符截断 +...),check() 中 v.code==='PRS' 时 message 改用 i18n 友好提示(含当前生效方言 effectiveDialect=cliDialect??fallbackDialect + 配置指引)、severity 提升为 error(解析失败时后续规则不可信);messages.ts 新增 adapter.sqlfluffPRS 三语 key;新增 sqlfluff-prs.test.ts 4 用例(片段提取/换行单行化/截断/回退)。背景:问题面板报 PRS 显示原始 SQL 片段+重复行号前缀观感差,且 DIALECT_MAP 默认方言已改为 oracle(08-04 20:34 未提交改动)致 MySQL 风格 DDL 误报 PRS(已实证:同一 SQL mysql 方言解析通过、oracle 报 PRS) | 中间产物:测试首次用 msg.length<200 断言截断不稳定(中英模板长度差异致英文下超限,DEBUG 日志确认实际失败的是 truncate 用例而非 fallback 用例)→ 改为断言 !includes(longFragment)(验证全文被截断,不依赖模板总长);全量测试沿用既有惯例临时移开 adapter/extension.test.js 后 74 用例全部通过(新增 4 个 PRS 用例),恢复原文件 | src/adapters/sqlfluff.ts, src/i18n/messages.ts, src/test/sqlfluff-prs.test.ts(新增) | deepseek-v4-flash |
|
||||
| 2026-08-04 21:45 | ① 用户提出 → ② 需求澄清 → ③ 方案设计 → ④ 人类审批 → ⑤ 编码实现 → ⑥ 审查验证 | 修复已打开文件不静态分析、无波浪线问题:根因两处——① package.json activationEvents 为空导致插件不自动激活(onDidOpenTextDocument 对激活前已打开的文件永不触发);② activate 未遍历存量 textDocuments。改动:package.json activationEvents 加 onStartupFinished;extension.ts 新增 scheduleAnalysis(统一按 URI 防抖调度器,gate file scheme + markers.enabled)与 analyzeOpenDocuments(遍历已打开文档、活动文件优先、串行 await runStaticAndApply),activate 时调用;onDidChangeTextDocument 改为 clear 波浪线 + 1s 防抖自动重分析;onDidSaveTextDocument 改用 scheduleAnalysis(500) 替换原独立 debounceTimers Map。复用 runStaticAndApply 版本竞态保护,行为不变。设计文档 docs/superpowers/specs/2026-08-04-auto-static-analysis-design.md 已产出 | 中间产物:无(澄清阶段就"并发 vs 串行"向用户说明各 linter 执行成本后定为串行;激活顺序定为活动文件优先);lint/compile 通过 | package.json, src/extension.ts, docs/superpowers/specs/2026-08-04-auto-static-analysis-design.md(新增) | deepseek-v4-flash |
|
||||
| 2026-08-05 22:10 | ① 用户提出 → ② 需求澄清 → ③ 方案设计 → ④ 人类审批 → ⑤ 编码实现 → ⑥ 审查验证 | 设置面板「共通规则」区画面优化:适配器卡片名称旁与图例优先级行末尾新增「?」帮助图标,悬停 tooltip 展示分步配置说明。i18n 新增 setup.adapter.modeLegendHelp 与 pmd/sql/eslint/stylelintHelp 五组三语 key(四段式:前置条件原因 + 内置规则开箱即用 + 主路径步骤 + 全局备选),删除已无引用的 4 组 xxxGuide;setupView.ts 移除 AdapterConfigStatus.guideText 字段及 collectAdapterStatus 赋值、pushConfig i18n payload 注入 5 个 help 字段、HTML 图例行末尾加「?」、CSS 新增 .help-icon 多行 tooltip(attr(data-help)+white-space:pre-line,卡片相对 header 左对齐/图例相对 mode-legend 右对齐防溢出)与 .adapter-card-name-wrap、删除 .adapter-guide 样式;setupView.js renderAdapters 删除 guide div、卡片标题行名称旁渲染「?」并新增 escapeAttr(转义 & < > ",保证含英文双引号的 data-help 属性安全)。lint/compile 通过;全量测试受既有 stylelint-config-recommended 无 exports main 环境问题阻碍(extension 激活失败),改用 stub vscode 的 node 脚本直接校验 i18n 255 key 三语无缺失、新 5 key 存在、旧 4 key 已删 | 设计迭代:v1 分步文案被用户指出「不知道为什么装 Java / ①② 两条路(项目/全局)混淆 / ruleset.xml 用途不明」→ 改四段式;复用现有 guide 文案被否(要新增配置说明);guide 第二句三选一(移除/动态状态提示/精简保留)用户选移除;图例「?」位置三选一(说明行末尾/副标题旁/每项单独)选说明行末尾;HTML 预览请求在计划模式被只读拦截 → 改为 inline 输出,切 build 后落盘 temp 预览文件供审核;实现中发现 escapeHtml(textContent→innerHTML)不转义双引号,英文 help 文案含 " 会使 data-help 属性提前闭合 → 新增 escapeAttr | src/i18n/messages.ts, src/views/setupView.ts, src/views/setupView.js | deepseek-v4-flash |
|
||||
| 2026-08-05 22:25 | ① 用户提出 → ② 需求澄清 → ③ 方案设计 → ④ 人类审批 → ⑤ 编码实现 → ⑥ 审查验证 | 修复图例区「?」位置与 tooltip 方向:根因——.mode-legend-desc 为 width:100% 独占第二行,紧随其后的 .help-icon 被 flex-wrap 挤到第三行且靠左,tooltip 以 right:0 锚定左移图标向右延伸溢出。改动:setupView.ts HTML 把说明文字与「?」包进 .mode-legend-desc-row(display:flex + justify-content:space-between + width:100%),「?」回到第二行行末;CSS 新增 .mode-legend-desc-row、.mode-legend-desc 去掉 width:100%(row 接管整行),图例 tooltip 保持 .mode-legend .help-icon:hover::after { right:0; left:auto } 使提示相对行末图标向左下方延伸。lint/compile 通过 | 中间产物:无(首版 CSS 就写了 right:0 锚定,实际渲染差源自图标被挤到第三行左侧,定位到根因是 flex-wrap + width:100% 独占行) | src/views/setupView.ts | deepseek-v4-flash |
|
||||
| 2026-08-05 22:55 | ① 用户提出 → ② 需求澄清 → ③ 方案设计 → ④ 人类审批 → ⑤ 编码实现 → ⑥ 审查验证 | 「快速开始」三步卡贴近实际重写:现状是卡片原三步(配置 AI / 启用自定义规则 / 保存并测试)暗示必须配 AI 才能审核,但插件静态分析实际开箱即用(onStartupFinished 自动分析+波浪线)、AI 配置只影响 AI 审核引擎、自定义规则为可选增强,且只用静态分析时原三步全灰有误导。改动:i18n 重写 setup.gettingStarted(三步启用代码审核→三步开始使用)与 step1/step2/step3 及 step3Hint 三语;setupView.js _step1Done 判定由 provider+model+apiKey+baseUrl 改为 msg.adapterStatus.some(a=>a.enabled)(任一适配器启用即完成,默认满足,如实反映静态分析开箱即用)。步骤①文案经两轮微调定稿为「插件已内置 Linter 静态分析,开箱即用,也可在共通规则中配置项目/全局规则」三语。lint/compile 通过 | 澄清阶段向用户展示「卡片文案 vs 实际行为」差距表(静态分析开箱即用/AI 仅深度审查/自定义规则可选/只用静态分析时三步全灰误导)后选「贴近实际重写三步」;标题「三步启用」与开箱即用矛盾,用户选改为「三步开始使用」;步骤①文案两轮迭代:初稿「打开代码文件自动静态分析,无需配置」→ 用户要求强调内置、去掉波浪线 →「已内置 Linter 静态分析,开箱即用,无需配置」→ 用户指出「无需配置」有歧义(易误以为不能/不必再配项目配置)→ 定稿补「也可在共通规则中配置项目/全局规则」(候选「只删无需配置」未选) | src/i18n/messages.ts, src/views/setupView.js | deepseek-v4-flash |
|
||||
| 2026-08-06 19:52 | ① 用户提出 → ② 需求澄清 → ③ 方案设计 | 生成「创建项目配置 = 内置配置(含三语规则注释)」设计书 | 中间产物:SQLFluff 内置非 rules=all 的纠错、ESLint 方案 A/B 抉择(选 B 仅 JS 零依赖)、注释语言三选一(选三语切换)、链接策略从逐条改为每文件单个索引链接、PMD 类别级注释而非 274 条展开 | docs/superpowers/specs/2026-08-06-builtin-config-template-design.md | deepseek-v4-flash |
|
||||
| 2026-08-06 20:02 | ① 用户提出 → ② 需求澄清 → ③ 方案设计 → ④ 人类审批 → ⑤ 编码实现 | 实现「创建项目配置 = 内置配置(含三语规则注释)」,验证 lint/compile/test 全通过 | 中间产物:ESLint 内置 JS 实际 92 条(61+31)而非方案初稿 93/32;Stylelint 顶层静态 import 纯 ESM 包为既有 bug(CJS require 报 No exports main),改为动态 import;PMD 22 条 exclude 缺描述补写进 static-rules.json;SQLFluff 内置非 rules=all 的纠错 | src/rules/builtin-rules.ts(新建) src/adapters/eslint.ts src/adapters/stylelint.ts src/adapters/sqlfluff.ts src/views/setupView.ts src/rules/static-rules.json src/i18n/messages.ts docs/superpowers/specs/2026-08-06-builtin-config-template-design.md | deepseek-v4-flash |
|
||||
| 2026-08-06 20:37 | ① 用户提出 → ② 需求澄清 → ③ 方案设计 → ④ 人类审批 → ⑤ 编码实现 → ⑥ 审查验证 | 修复安装验证发现的 3 个问题:①PMD 创建配置不生成文件(resolvePmdRulesetPath 打包后 __dirname 退级过多,改逐级向上探测 5 级 + getExtension 路径);②static-rules.json 新增 57 条描述中文被 PowerShell stdin 管道 ASCII 编码破坏成 '?'(改 UTF-8 落盘 .mjs 脚本重写修复);③生成 eslint.config.js/.stylelintrc.js 被插件静态分析报 no-undef 'module'(首行加 /* global module */)。重新打包 1.2.0 VSIX | 中间产物:修复②初判为 esbuild 编码问题(bundle 中文存大写 \\uXXXX 转义,运行时解码正常,排除);仓库根因测试生成的 eslint.config.js 覆盖自身 lint 配置致 146 no-undef,已删除 | src/rules/builtin-rules.ts src/rules/static-rules.json docs/superpowers/specs/2026-08-06-builtin-config-template-design.md | deepseek-v4-flash |
|
||||
|
||||
@@ -0,0 +1,298 @@
|
||||
# 创建项目配置 = 内置配置(含三语规则注释)— Design Spec
|
||||
|
||||
## 1. 概述
|
||||
|
||||
当用户在 Setup 面板点击「创建项目配置」时,插件在项目根目录生成对应适配器的配置文件(`ruleset.xml` / `.sqlfluff` / `.stylelintrc.js` / `eslint.config.js`)。
|
||||
|
||||
本设计将生成的"示例模板"改为**与插件内置运行规则完全一致的配置**,并为每条规则附加三语(zh-CN / en / ja)描述注释,帮助用户理解已启用的规则、按相同格式添加新规则。
|
||||
|
||||
## 2. 背景与问题
|
||||
|
||||
### 2.1 现状
|
||||
|
||||
| 适配器 | 内置运行配置 | 当前"创建项目配置"生成内容 | 问题 |
|
||||
|--------|-------------|---------------------------|------|
|
||||
| PMD | `jars/pmd/pmd-java-ruleset.xml`(7 类 274 条 + 41 处排除) | 仅引用 2 个类别(bestpractices + codestyle)的示例 | 规则范围显著缩小 |
|
||||
| SQLFluff | 精选 26 项规则清单 + `max_line_length=80 / indent_unit=space / tab_space_size=4` | `rules = all` + `dialect = mysql` | 范围过大,且与内置不一致 |
|
||||
| Stylelint | `stylelint-config-recommended`(41 条)+ 27 条 extra | 仅 3 条示例规则 | 规则范围显著缩小 |
|
||||
| ESLint | `@eslint/js` recommended(61 条)+ 31 条 extra(JS) | 仅 3 条示例规则 | 规则范围显著缩小 |
|
||||
|
||||
### 2.2 核心问题
|
||||
|
||||
1. **规则缩水**:项目配置文件的优先级高于内置配置(`eslint.ts:105-115`、`stylelint.ts:120-125`、`pmd.ts:89-92`)。一旦创建当前示例模板,实际检查范围从数十条骤降至 3 条(或 2 个类别)。
|
||||
2. **不可知**:用户看不到内置规则清单,也不知道配置文件中的规则写法,难以添加新规则。
|
||||
|
||||
### 2.3 目标
|
||||
|
||||
- 「创建项目配置」生成的 4 个文件内容 = 插件内置配置,规则范围精确一致。
|
||||
- 每条规则带三语描述注释(随插件 `ai.outputLanguage` 切换)。
|
||||
- 每个文件头部含一段"如何添加规则"注释 + **单个**"该工具全部规则索引"链接。
|
||||
- 内置规则与生成文件永远一致(单一数据源,杜绝漂移)。
|
||||
|
||||
### 2.4 非目标
|
||||
|
||||
- 不在 Setup 面板增加规则目录 UI(生成文件本身即规则清单)。
|
||||
- 不对 ESLint 生成 TypeScript 专项规则(方案 B:仅 JS,零依赖)。
|
||||
- 不将 PMD 274 条规则展开为逐条 `<rule>`(保持类别引用结构)。
|
||||
- 不新增"全量规则枚举"(各工具全部规则目录)。
|
||||
|
||||
## 3. 方案设计
|
||||
|
||||
### 3.1 架构概览
|
||||
|
||||
```
|
||||
adapters/*.ts (内置运行规则)
|
||||
│ import 共享常量
|
||||
▼
|
||||
src/rules/builtin-rules.ts (单一数据源)
|
||||
│ 4 个生成函数
|
||||
▼
|
||||
setupView.ts 模板 → 点击「创建项目配置」→ 写入项目根目录
|
||||
│
|
||||
└─ 注释语言 getLanguage() / 规则描述 static-rules.json
|
||||
```
|
||||
|
||||
### 3.2 共享数据源模块 `src/rules/builtin-rules.ts`(新建)
|
||||
|
||||
从各 adapter 迁出常量,adapter 与模板共用:
|
||||
|
||||
```typescript
|
||||
import staticRules from './static-rules.json';
|
||||
import type { Language } from '../i18n/messages';
|
||||
|
||||
// —— 共享常量(从 adapters 迁出)——
|
||||
export const eslintExtraRules: Record<string, 'error' | 'warn'>; // 自 eslint.ts
|
||||
export const eslintExtraTsRules: Record<string, 'error' | 'warn' | 'off'>; // 自 eslint.ts(生成文件不含,仅 adapter 使用)
|
||||
export const stylelintExtraRules: Record<string, unknown>; // 自 stylelint.ts
|
||||
export const BUILTIN_SQLFLUFF_RULES: string; // 自 sqlfluff.ts(26 项)
|
||||
export function buildBuiltinSqlfluffConfig(dialect: string): string; // 自 sqlfluff.ts(含行宽/缩进设置)
|
||||
|
||||
// —— 规则描述查询(三语)——
|
||||
export function getRuleDescription(linter: string, ruleId: string, lang: Language): string | undefined;
|
||||
|
||||
// —— 文档索引链接(每工具仅 1 个)——
|
||||
const ESLINT_RULES_URL = 'https://eslint.org/docs/latest/rules/';
|
||||
const STYLELINT_RULES_URL = 'https://stylelint.io/user-guide/rules/';
|
||||
const SQLFLUFF_RULES_URL = 'https://docs.sqlfluff.com/en/stable/reference/rules.html';
|
||||
// PMD 版本取自 staticRules.linterVersion.pmd → `https://docs.pmd-code.org/pmd-doc-<version>/pmd_rules_java.html`
|
||||
|
||||
// —— 生成函数 ——
|
||||
export function buildEslintProjectConfigText(lang: Language): string;
|
||||
export async function buildStylelintProjectConfigText(lang: Language): Promise<string>;
|
||||
export function buildSqlfluffProjectConfigText(dialect: string, lang: Language): string;
|
||||
export async function buildPmdProjectRulesetText(lang: Language): Promise<string>;
|
||||
```
|
||||
|
||||
**依赖注入**:
|
||||
- `buildEslintProjectConfigText` 内联 `@eslint/js` 的 `configs.recommended.rules`(61 条,core 规则、无插件依赖)。
|
||||
- `buildStylelintProjectConfigText` 运行时 `await import('stylelint-config-recommended')`(ESM-only)后合并 extra,序列化为纯对象(零依赖)。
|
||||
|
||||
### 3.3 生成文件内容规范
|
||||
|
||||
#### 3.3.1 ESLint `eslint.config.js`(方案 B:仅 JS,零依赖)
|
||||
|
||||
```js
|
||||
// ============================================================
|
||||
// 项目配置 — 与插件内置 ESLint 规则一致(由插件生成)
|
||||
// 如何添加规则:
|
||||
// 1. 在 rules 中新增一行,如 'no-alert': 'warn'
|
||||
// 2. 带参数写法,如 'semi': ['error', 'always']
|
||||
// 全部可用规则:https://eslint.org/docs/latest/rules/
|
||||
// 注意:本文件仅包含 JS 内置规则。TS 项目如需 TS 专项规则,
|
||||
// 请安装 typescript-eslint 后自行追加:
|
||||
// const ts = require('typescript-eslint');
|
||||
// module.exports = [ ...本文件配置, ...ts.configs.recommended ];
|
||||
// ============================================================
|
||||
module.exports = [
|
||||
{
|
||||
rules: {
|
||||
// --- @eslint/js recommended (61) ---
|
||||
'constructor-super': 'error', // 在构造函数中校验 super() 的调用
|
||||
'for-direction': 'error', // 确保 for 循环更新子句朝正确方向移动计数器
|
||||
// ...(61 条全量内联)
|
||||
// --- 插件补充 (31) ---
|
||||
'eqeqeq': 'error', // 强制使用 === 和 !==
|
||||
'no-eq-null': 'error', // 禁止与 null 使用 == 比较
|
||||
// ...(31 条全量内联)
|
||||
},
|
||||
},
|
||||
];
|
||||
```
|
||||
|
||||
#### 3.3.2 Stylelint `.stylelintrc.js`
|
||||
|
||||
```js
|
||||
// ============================================================
|
||||
// 项目配置 — 与插件内置 Stylelint 规则一致(由插件生成)
|
||||
// 如何添加规则:在 rules 中新增一行,如 'indentation': 2
|
||||
// 全部可用规则:https://stylelint.io/user-guide/rules/
|
||||
// ============================================================
|
||||
module.exports = {
|
||||
rules: {
|
||||
'annotation-no-unknown': true, // 禁止未知注解
|
||||
// ...(41 条 recommended 全量内联)
|
||||
'color-no-invalid-hex': true, // 禁止无效的十六进制颜色
|
||||
// ...(27 条 extra 全量内联)
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
#### 3.3.3 SQLFluff `.sqlfluff`
|
||||
|
||||
```ini
|
||||
[sqlfluff]
|
||||
# 数据库方言:postgres / mysql / bigquery / snowflake 等
|
||||
dialect = mysql
|
||||
# 想启用全部规则时改为 → rules = all
|
||||
# 内置精选规则清单:
|
||||
# core 核心规则组(稳定、跨方言的语法类基础规则)
|
||||
# AM03 禁止隐式交叉连接
|
||||
# CV01 ...(逐条:规则码 + 三语描述,共 26 项)
|
||||
# 全部可用规则:https://docs.sqlfluff.com/en/stable/reference/rules.html
|
||||
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
|
||||
max_line_length = 80
|
||||
indent_unit = space
|
||||
tab_space_size = 4
|
||||
```
|
||||
|
||||
#### 3.3.4 PMD `ruleset.xml`
|
||||
|
||||
```xml
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ruleset name="Java Rules"
|
||||
xmlns="http://pmd.sourceforge.net/ruleset/2.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd">
|
||||
<description>Java Code Review Rules (274 active rules, 7 categories) — 与插件内置规则集一致(由插件生成)</description>
|
||||
<!-- 全部可用规则:https://docs.pmd-code.org/pmd-doc-7.26.0/pmd_rules_java.html -->
|
||||
|
||||
<!-- 最佳实践类(避免空 catch、关闭流等) -->
|
||||
<rule ref="category/java/bestpractices.xml">
|
||||
<!-- 排除:不强制 switch 默认分支位于最后 -->
|
||||
<exclude name="DefaultLabelNotLastInSwitchStmt"/>
|
||||
<!-- ...(41 处排除项逐条注释) -->
|
||||
</rule>
|
||||
<!-- 代码风格类(命名规范、花括号位置等) -->
|
||||
<rule ref="category/java/codestyle.xml">...</rule>
|
||||
<!-- 设计类 / errorprone / multithreading / performance / security 同理 -->
|
||||
</ruleset>
|
||||
```
|
||||
|
||||
### 3.4 注释策略
|
||||
|
||||
| 文件 | 注释粒度 | 内容 |
|
||||
|------|---------|------|
|
||||
| `eslint.config.js` / `.stylelintrc.js` | 逐条规则 | `规则码 + 三语描述`(尾行 `//` 注释) |
|
||||
| `.sqlfluff` | 逐条规则 | `rules =` 行上方注释块(`# 规则码 三语描述`) |
|
||||
| `ruleset.xml` | 类别级 | 7 个 `<rule>` 类别 + 41 处 `<exclude>`(`<!-- -->`) |
|
||||
|
||||
- 描述来源:`static-rules.json` 的 `description` / `descriptionZh` / `descriptionJa`,按 `getLanguage()` 取对应语言。
|
||||
- exclude 描述:从 `static-rules.json` pmd 段(273 条)按规则名查询;查不到的仅保留名字、不加注释。
|
||||
- 链接:**每个文件仅 1 个**"全部可用规则"索引链接,位于文件头注释块;逐条注释不含链接。
|
||||
- 注释语言:跟随 `ai.outputLanguage`(`getLanguage()`),三语切换。
|
||||
|
||||
### 3.5 setupView 改造(`src/views/setupView.ts`)
|
||||
|
||||
- `ADAPTER_METADATA` 的 `configFileTemplate` 类型:`() => string` → `() => string | Promise<string>`。
|
||||
- 4 个模板函数全部委托给 `builtin-rules` 生成函数(删掉 `getEslintTemplate` / `getStylelintTemplate` / `getSqlfluffTemplate` / `getPmdRulesetTemplate` 的示例内容)。
|
||||
- `handleAdapterConfig`(`setupView.ts:1231`):
|
||||
```typescript
|
||||
const content = await meta.configFileTemplate();
|
||||
fs.writeFileSync(filePath, content, 'utf-8');
|
||||
```
|
||||
- PMD 规则集读取路径(复用 `pmd.ts:26-44` 的解析逻辑):
|
||||
`ext.extensionPath/jars/pmd/pmd-java-ruleset.xml`,找不到时回退到 `out/jars` / 源码相对路径。
|
||||
- SQLFluff 方言:固定 `mysql`(用户已确认接受)。
|
||||
|
||||
### 3.6 adapter 常量抽取(`eslint.ts` / `stylelint.ts` / `sqlfluff.ts`)
|
||||
|
||||
| 文件 | 迁出常量 | 改为 |
|
||||
|------|---------|------|
|
||||
| `src/adapters/eslint.ts` | `extraRules` / `extraTsRules` | `import { eslintExtraRules, eslintExtraTsRules } from '../rules/builtin-rules'` |
|
||||
| `src/adapters/stylelint.ts` | `extraRules` | `import { stylelintExtraRules } from '../rules/builtin-rules'` |
|
||||
| `src/adapters/sqlfluff.ts` | `BUILTIN_SQLFLUFF_RULES` / `buildBuiltinConfig` | 从 `builtin-rules` 导入 |
|
||||
|
||||
### 3.7 static-rules.json 补充(`src/rules/static-rules.json`)
|
||||
|
||||
- **stylelint**:补写 34 条 recommended 规则的三语描述(`annotation-no-unknown`、`at-rule-no-unknown`、`block-no-empty` 等),使 stylelint 段达到 68 条全覆盖(41 recommended + 27 extra)。
|
||||
- **sqlfluff**:补 `core` 规则组条目(如"SQLFluff 核心规则组(稳定、跨方言的语法类基础规则)"),使内置 26 项均有描述。
|
||||
|
||||
### 3.8 i18n 帮助文案(`src/i18n/messages.ts:515-549`)
|
||||
|
||||
更新 4 个 `setup.adapter.*Help` 文案,核心变化:
|
||||
- 说明「创建项目配置将生成与内置一致的规则文件(含三语规则注释)」。
|
||||
- ESLint 文案补充:生成文件仅含 JS 内置规则;TS 项目如需 TS 专项规则,自行安装 `typescript-eslint` 并参考文件头注释追加。
|
||||
|
||||
## 4. 关键接口定义
|
||||
|
||||
```typescript
|
||||
// src/rules/builtin-rules.ts
|
||||
export const eslintExtraRules: Record<string, 'error' | 'warn'>;
|
||||
export const eslintExtraTsRules: Record<string, 'error' | 'warn' | 'off'>;
|
||||
export const stylelintExtraRules: Record<string, unknown>;
|
||||
export const BUILTIN_SQLFLUFF_RULES: string;
|
||||
export function buildBuiltinSqlfluffConfig(dialect: string): string;
|
||||
export function getRuleDescription(linter: string, ruleId: string, lang: Language): string | undefined;
|
||||
export function buildEslintProjectConfigText(lang: Language): string;
|
||||
export function buildStylelintProjectConfigText(lang: Language): Promise<string>;
|
||||
export function buildSqlfluffProjectConfigText(dialect: string, lang: Language): string;
|
||||
export function buildPmdProjectRulesetText(lang: Language): Promise<string>;
|
||||
|
||||
// src/views/setupView.ts(改造)
|
||||
type ConfigFileTemplate = () => string | Promise<string>;
|
||||
```
|
||||
|
||||
## 5. 文件变更清单
|
||||
|
||||
| 类型 | 文件 | 说明 |
|
||||
|------|------|------|
|
||||
| 新建 | `src/rules/builtin-rules.ts` | 共享常量 + 描述查询 + 4 个生成函数 |
|
||||
| 修改 | `src/adapters/eslint.ts` | 常量改为从 builtin-rules 导入 |
|
||||
| 修改 | `src/adapters/stylelint.ts` | 常量改为从 builtin-rules 导入;修复既有 CJS/ESM 加载 bug(`stylelint-config-recommended` 为纯 ESM,顶层静态 import 改动态 import) |
|
||||
| 修改 | `src/adapters/sqlfluff.ts` | 内置清单/构建函数改为从 builtin-rules 导入 |
|
||||
| 修改 | `src/views/setupView.ts` | 模板函数委托 + configFileTemplate 异步化 + handleAdapterConfig await |
|
||||
| 修改 | `src/rules/static-rules.json` | stylelint 补 34 条、sqlfluff 补 core、pmd 补 22 条 exclude 规则描述 |
|
||||
| 修改 | `src/i18n/messages.ts` | 4 个 adapter help 文案更新 |
|
||||
| 修改 | `docs/superpowers/specs/2026-08-06-builtin-config-template-design.md` | 本设计书 |
|
||||
|
||||
> 实现期发现并修复:`src/adapters/stylelint.ts` 顶层 `import stylelint-config-recommended`(纯 ESM 包)在 CJS 编译下必然报 "No exports main",属既有 bug,阻塞测试套件。已改为动态 `import`(与 `getModule()` 加载 stylelint 一致)。
|
||||
>
|
||||
> 实际规则数量:ESLint 内置 JS 为 **92 条**(js recommended 61 + extra 31,方案初稿误记为 93/32);Stylelint **68 条**;SQLFluff **26 项**;PMD **37 处 exclude 全部可注释**(补写了 22 条缺失描述)。
|
||||
|
||||
## 9. 验证反馈修复(2026-08-06)
|
||||
|
||||
安装 VSIX 实测发现并修复 3 个问题:
|
||||
|
||||
1. **PMD「创建项目配置」不生成文件**:`resolvePmdRulesetPath` 原用 `path.resolve(__dirname, '..', '..', 'jars', ...)`,在 esbuild 打包后 `__dirname` = `<ext>/out`,该路径多退一级找不到 `jars/`(开发时 `out/rules/` 下退两级才正确)。改为从 `__dirname` 逐级向上探测 5 级目录 + `getExtension` 路径,兼容打包/开发两种布局。
|
||||
2. **生成的配置里部分注释为 `?`**:新增的 57 条规则描述(stylelint 34 + sqlfluff core + pmd 22)经 PowerShell 管道 `$s | node` 注入 `static-rules.json` 时,中文被 `$OutputEncoding`(ASCII)破坏成字面 `?`。改用 UTF-8 落盘的 .mjs 脚本重写这 57 条 `descriptionZh/descriptionJa` 修复。
|
||||
3. **生成的 eslint.config.js / .stylelintrc.js 被插件静态分析报 `[eslint:no-undef] 'module' is not defined`**:两个配置文件是 CommonJS(`module.exports`),内置规则含 `no-undef` 且未声明 Node 全局。在两个生成文件首行加 `/* global module */` 消除误报。
|
||||
|
||||
另:本仓库根目录因安装验证生成的 `eslint.config.js` 会覆盖仓库自身 lint 配置(致 146 个 no-undef),为测试产物已删除。
|
||||
|
||||
## 6. 影响范围
|
||||
|
||||
- 仅影响「创建项目配置」的文件内容与 Setup 面板帮助文案。
|
||||
- adapter 运行逻辑不变(仅常量来源变化,行为一致)。
|
||||
- 生成的配置文件位于用户项目根目录,不影响本仓库运行。
|
||||
|
||||
## 7. 验证方案
|
||||
|
||||
1. `npm run lint` — ESLint 自检
|
||||
2. `npm run compile` — tsc 编译
|
||||
3. `npm test` — @vscode/test-cli 运行测试
|
||||
4. 手工验证:打开 Setup 面板 → 点击 4 个适配器的「创建项目配置」→ 检查生成内容与 §3.3 示例一致、注释随语言切换。
|
||||
|
||||
## 8. 已知取舍
|
||||
|
||||
| 项 | 说明 |
|
||||
|----|------|
|
||||
| ESLint 仅 JS | TS 项目使用生成配置后,ESLint 用默认 espree 解析 `.ts` 会产生语法解析错误;文件头注释给出追加 `typescript-eslint` 的示例引导。 |
|
||||
| SQLFluff 方言固定 mysql | 插件设置留空时内置回退方言为 oracle/ansi,与生成文件有差异(用户已确认接受)。 |
|
||||
| PMD 类别级注释 | 不展开 274 条逐条 `<rule>`,保持类别引用结构,注释以 7 类 + 41 处 exclude 为粒度。 |
|
||||
| 快照行为 | 生成文件 = 内置规则快照;插件日后升级改内置规则时,已生成的项目文件不会自动同步(项目配置优先级最高)。 |
|
||||
|
||||
## 9. 排除范围(非本期)
|
||||
|
||||
- Setup 面板「规则目录」UI(查→复制→粘贴)。
|
||||
- 各工具全量规则枚举(超出内置子集的规则)。
|
||||
- ESLint TS 专项规则生成。
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 478 B After Width: | Height: | Size: 64 KiB |
+3
-53
@@ -7,57 +7,7 @@ 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',
|
||||
'no-eq-null': 'error',
|
||||
'no-self-compare': 'error',
|
||||
'no-promise-executor-return': 'error',
|
||||
'no-shadow': 'error',
|
||||
'no-unassigned-vars': 'error',
|
||||
'no-useless-assignment': 'error',
|
||||
'block-scoped-var': 'error',
|
||||
'default-case': 'error',
|
||||
'default-case-last': 'error',
|
||||
'no-unmodified-loop-condition': 'error',
|
||||
'no-unreachable-loop': 'error',
|
||||
'no-eval': 'error',
|
||||
'no-extend-native': 'error',
|
||||
'no-var': 'error',
|
||||
'no-await-in-loop': 'warn',
|
||||
'prefer-template': 'warn',
|
||||
'prefer-object-spread': 'warn',
|
||||
'prefer-rest-params': 'warn',
|
||||
'prefer-spread': 'warn',
|
||||
'prefer-object-has-own': 'warn',
|
||||
'no-useless-concat': 'warn',
|
||||
'no-useless-return': 'warn',
|
||||
'no-useless-computed-key': 'warn',
|
||||
'no-useless-rename': 'warn',
|
||||
'no-param-reassign': 'warn',
|
||||
'no-return-assign': 'error',
|
||||
'no-throw-literal': 'error',
|
||||
'camelcase': 'warn',
|
||||
'new-cap': 'warn',
|
||||
'no-array-constructor': 'error',
|
||||
};
|
||||
|
||||
const extraTsRules: Record<string, 'error' | 'warn' | 'off'> = {
|
||||
'@typescript-eslint/no-non-null-assertion': 'error',
|
||||
'@typescript-eslint/no-dynamic-delete': 'error',
|
||||
'@typescript-eslint/no-useless-empty-export': 'error',
|
||||
'@typescript-eslint/consistent-type-imports': 'error',
|
||||
'@typescript-eslint/unified-signatures': 'error',
|
||||
'@typescript-eslint/no-extraneous-class': 'warn',
|
||||
'@typescript-eslint/no-useless-constructor': 'warn',
|
||||
'@typescript-eslint/no-non-null-asserted-nullish-coalescing': 'error',
|
||||
'@typescript-eslint/no-invalid-void-type': 'warn',
|
||||
'@typescript-eslint/prefer-literal-enum-member': 'warn',
|
||||
'@typescript-eslint/prefer-enum-initializers': 'warn',
|
||||
'no-shadow': 'off',
|
||||
'@typescript-eslint/no-shadow': 'error',
|
||||
'no-array-constructor': 'off',
|
||||
};
|
||||
import { eslintExtraRules, eslintExtraTsRules } from '../rules/builtin-rules';
|
||||
|
||||
const TS_FILES = ['**/*.ts', '**/*.tsx', '**/*.mts', '**/*.cts'];
|
||||
|
||||
@@ -126,8 +76,8 @@ export class ESLintAdapter implements LinterAdapter {
|
||||
ESLintAdapter.defaultConfig = [
|
||||
js.configs.recommended,
|
||||
...ts.configs.recommended,
|
||||
{ rules: extraRules },
|
||||
{ files: TS_FILES, rules: extraTsRules },
|
||||
{ rules: eslintExtraRules },
|
||||
{ files: TS_FILES, rules: eslintExtraTsRules },
|
||||
];
|
||||
}
|
||||
return ESLintAdapter.defaultConfig;
|
||||
|
||||
@@ -7,6 +7,7 @@ import type { LinterAdapter, AdapterResult, LinterDiagnostic, Severity } from '.
|
||||
import { getSqlFluffConfigFile, getSqlFluffDialect } from '../config';
|
||||
import { t } from '../i18n/messages';
|
||||
import staticRules from '../rules/static-rules.json';
|
||||
import { BUILTIN_SQLFLUFF_RULES, buildBuiltinSqlfluffConfig } from '../rules/builtin-rules';
|
||||
|
||||
const DIALECT_MAP: Record<string, string> = {
|
||||
sql: 'oracle',
|
||||
@@ -20,19 +21,6 @@ const SUPPORTED_DIALECTS = [
|
||||
'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>();
|
||||
@@ -156,7 +144,7 @@ export class SqlFluffAdapter implements LinterAdapter {
|
||||
} else if (hasProjectSqlfluffConfig(workingDir)) {
|
||||
} else {
|
||||
tempConfigPath = path.join(os.tmpdir(), `vscode-code-reviewer-sqlfluff-${Date.now()}.cfg`);
|
||||
fs.writeFileSync(tempConfigPath, buildBuiltinConfig(cliDialect ?? fallbackDialect), 'utf-8');
|
||||
fs.writeFileSync(tempConfigPath, buildBuiltinSqlfluffConfig(cliDialect ?? fallbackDialect), 'utf-8');
|
||||
configPath = tempConfigPath;
|
||||
}
|
||||
|
||||
|
||||
+14
-44
@@ -3,7 +3,7 @@ import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import type { LinterAdapter, AdapterResult, LinterDiagnostic, Severity } from './adapter';
|
||||
import { getStylelintConfigPath } from '../config';
|
||||
import recommendedConfig from 'stylelint-config-recommended';
|
||||
import { stylelintExtraRules } from '../rules/builtin-rules';
|
||||
|
||||
const CONFIG_FILE_NAMES = [
|
||||
'.stylelintrc',
|
||||
@@ -16,48 +16,18 @@ const CONFIG_FILE_NAMES = [
|
||||
'stylelint.config.cjs',
|
||||
];
|
||||
|
||||
const extraRules: Record<string, unknown> = {
|
||||
'color-no-invalid-hex': true,
|
||||
'function-linear-gradient-no-nonstandard-direction': true,
|
||||
'function-no-unknown': true,
|
||||
'unit-no-unknown': true,
|
||||
'no-unknown-animations': true,
|
||||
'no-unknown-custom-media': true,
|
||||
'no-unknown-custom-properties': true,
|
||||
|
||||
'at-rule-no-vendor-prefix': true,
|
||||
'media-feature-name-no-vendor-prefix': true,
|
||||
'property-no-vendor-prefix': true,
|
||||
'selector-no-vendor-prefix': true,
|
||||
'value-no-vendor-prefix': true,
|
||||
|
||||
'color-hex-length': 'short',
|
||||
'color-function-notation': 'modern',
|
||||
'length-zero-no-unit': true,
|
||||
'selector-pseudo-element-colon-notation': 'double',
|
||||
'import-notation': 'string',
|
||||
'alpha-value-notation': 'number',
|
||||
'hue-degree-notation': 'angle',
|
||||
'keyframe-selector-notation': 'percentage',
|
||||
|
||||
'declaration-block-no-redundant-longhand-properties': true,
|
||||
'shorthand-property-no-redundant-values': true,
|
||||
'block-no-redundant-nested-style-rules': true,
|
||||
|
||||
'color-named': 'never',
|
||||
'font-family-name-quotes': 'always-where-required',
|
||||
|
||||
'number-max-precision': 4,
|
||||
'comment-whitespace-inside': 'always',
|
||||
};
|
||||
|
||||
const DEFAULT_CONFIG: Record<string, unknown> = {
|
||||
...(recommendedConfig as Record<string, unknown>),
|
||||
rules: {
|
||||
...((recommendedConfig as Record<string, unknown>).rules as Record<string, unknown>),
|
||||
...extraRules,
|
||||
},
|
||||
};
|
||||
async function getDefaultConfig(): Promise<Record<string, unknown>> {
|
||||
const mod = await import('stylelint-config-recommended');
|
||||
const recommendedConfig = (mod.default ?? mod) as Record<string, unknown>;
|
||||
const recommendedRules = (recommendedConfig.rules ?? {}) as Record<string, unknown>;
|
||||
return {
|
||||
...recommendedConfig,
|
||||
rules: {
|
||||
...recommendedRules,
|
||||
...stylelintExtraRules,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
interface LinterOptions {
|
||||
code?: string;
|
||||
@@ -121,7 +91,7 @@ export class StylelintAdapter implements LinterAdapter {
|
||||
if (globalPath && globalPath.trim() !== '') {
|
||||
lintOptions.configFile = globalPath;
|
||||
} else if (!hasExternalConfig(workingDir)) {
|
||||
lintOptions.config = DEFAULT_CONFIG;
|
||||
lintOptions.config = await getDefaultConfig();
|
||||
}
|
||||
|
||||
const result = await stylelint.lint(lintOptions);
|
||||
|
||||
+36
-31
@@ -193,29 +193,29 @@ const messages: Record<string, Record<Language, string>> = {
|
||||
ja: 'クイックスタート',
|
||||
},
|
||||
'setup.gettingStarted': {
|
||||
'zh-CN': '三步启用代码审核',
|
||||
en: '3 Steps to Enable Code Review',
|
||||
ja: '3ステップでコードレビューを有効化',
|
||||
'zh-CN': '三步开始使用',
|
||||
en: '3 Steps to Get Started',
|
||||
ja: '3ステップで使い始める',
|
||||
},
|
||||
'setup.step1': {
|
||||
'zh-CN': '安装插件后,<b>配置 AI 模型</b>及 API Key,激活智能审核能力',
|
||||
en: 'After installing, <b>configure AI model</b> and API Key to activate intelligent review',
|
||||
ja: 'インストール後、<b>AIモデル</b>とAPIキーを設定してインテリジェントレビューを有効化',
|
||||
'zh-CN': '插件已内置 <b>Linter 静态分析</b>,开箱即用,也可在共通规则中配置项目/全局规则',
|
||||
en: 'The extension includes <b>built-in Linter static analysis</b>, ready to use out of the box, and you can also configure project/global rules in Common Rules',
|
||||
ja: '拡張機能には<b>Linter静的解析</b>が組み込まれており、そのまま利用できるほか、共通ルールでプロジェクト/グローバルルールも設定できます',
|
||||
},
|
||||
'setup.step2': {
|
||||
'zh-CN': '启用 <b>自定义规则</b>,补充团队特有的编码规范',
|
||||
en: 'Enable <b>custom rules</b> to add team-specific coding standards',
|
||||
ja: '<b>カスタムルール</b>を有効にしてチーム固有のコーディング規約を追加',
|
||||
'zh-CN': '在 <b>自定义规则</b> 标签页导入团队编码规范,增强审查(可选)',
|
||||
en: 'Import team coding standards in the <b>Custom Rules</b> tab to enhance reviews (optional)',
|
||||
ja: '<b>カスタムルール</b>タブでチームのコーディング規約をインポート(任意)',
|
||||
},
|
||||
'setup.step3': {
|
||||
'zh-CN': '<b>保存并测试连接</b>,验证配置无误后即可触发审核',
|
||||
en: '<b>Save and test connection</b>, verify config then trigger review',
|
||||
ja: '<b>保存して接続テスト</b>、設定を確認してレビューを開始',
|
||||
'zh-CN': '配置 <b>AI 模型与 API Key</b> 并<b>保存并测试连接</b>,启用 AI 深度审查',
|
||||
en: 'Configure <b>AI model & API Key</b> and <b>save & test the connection</b> to enable deep AI review',
|
||||
ja: '<b>AIモデルとAPIキー</b>を設定し<b>保存して接続テスト</b>、AI詳細レビューを有効化',
|
||||
},
|
||||
'setup.step3Hint': {
|
||||
'zh-CN': '按 <b>Ctrl + Shift + R</b> 快捷键触发审核,结果实时显示在 <b>审核结果报告</b>页面中',
|
||||
en: 'Press <b>Ctrl + Shift + R</b> to trigger review, results appear in the <b>Review Report</b> panel',
|
||||
ja: '<b>Ctrl + Shift + R</b> でレビューを実行、結果は<b>レビューレポート</b>に表示',
|
||||
'zh-CN': '按 <b>Ctrl + Shift + R</b> 触发完整审核,结果实时显示在<b>审核结果报告</b>页面',
|
||||
en: 'Press <b>Ctrl + Shift + R</b> to run a full review, results appear in the <b>Review Report</b> panel',
|
||||
ja: '<b>Ctrl + Shift + R</b> で完全なレビューを実行、結果は<b>レビューレポート</b>に表示',
|
||||
},
|
||||
'setup.aiConnectionConfig': {
|
||||
'zh-CN': 'AI 连接配置',
|
||||
@@ -407,6 +407,11 @@ const messages: Record<string, Record<Language, string>> = {
|
||||
en: 'Auto-selects config by priority: Built-in < Global < Project',
|
||||
ja: '優先順位に従って自動選択: 組み込み < グローバル < プロジェクト',
|
||||
},
|
||||
'setup.adapter.modeLegendHelp': {
|
||||
'zh-CN': '三种配置来源的含义:\n· 内置规则:插件自带的规则集,开箱即用,无需配置\n· 全局配置:在 VS Code 设置中指定的文件路径,对所有项目生效\n· 项目配置:项目根目录下的配置文件,仅对当前项目生效\n\n优先级:内置 < 全局 < 项目,插件自动选择优先级最高的可用配置',
|
||||
en: 'What the three config sources mean:\n· Built-in rules: bundled with the extension, work out of the box\n· Global config: a file path set in VS Code settings, applies to all projects\n· Project config: a config file in the project root, applies to the current project only\n\nPriority: Built-in < Global < Project; the highest-priority available source is used',
|
||||
ja: '3つの設定ソースの意味:\n· 組み込みルール:拡張機能に同梱のルール、設定不要ですぐに使用可\n· グローバル設定:VS Code設定で指定したファイルパス、全プロジェクトに適用\n· プロジェクト設定:プロジェクト直下の設定ファイル、現在のプロジェクトのみに適用\n\n優先度:組み込み < グローバル < プロジェクト、利用可能な中で最優先のものを自動選択',
|
||||
},
|
||||
'setup.adapter.configYes': {
|
||||
'zh-CN': '已配置',
|
||||
en: 'Configured',
|
||||
@@ -507,40 +512,40 @@ const messages: Record<string, Record<Language, string>> = {
|
||||
en: 'Java (including Java code in JSP, e.g. <% ... %>)',
|
||||
ja: 'Java(JSP内のJavaコードを含む、例: <% ... %>)',
|
||||
},
|
||||
'setup.adapter.pmdGuide': {
|
||||
'zh-CN': '需要 Java 运行环境;项目根目录创建 ruleset.xml 或在设置中配置 pmd.rulesetPath',
|
||||
en: 'Requires Java runtime; create ruleset.xml in project root or set pmd.rulesetPath in settings',
|
||||
ja: 'Java実行環境が必要。プロジェクトルートにruleset.xmlを作成するか、設定でpmd.rulesetPathを設定してください',
|
||||
'setup.adapter.pmdHelp': {
|
||||
'zh-CN': 'PMD 由 Java 编写,需先安装 Java 运行环境(JDK 8+),否则无法执行。\n\n插件已内置一套 PMD 规则集(7 类 274 条),安装 Java 后即可开箱使用,无需额外配置。\n点击「创建项目配置」,会在项目根目录生成 ruleset.xml,内容与插件内置规则集完全一致(含每类规则的说明注释),方便查看与调整审查范围。\n保存文件即自动生效,无需重启。\n\n若多个项目共用同一份规则,可点「修改全局设置」,在 pmd.rulesetPath 中填写该文件路径',
|
||||
en: 'PMD is written in Java, so a Java runtime (JDK 8+) must be installed first.\n\nThe extension ships a built-in PMD ruleset (7 categories, 274 rules), usable right after installing Java.\nClick "Create Project Config" to generate ruleset.xml in the project root, identical to the built-in ruleset (including per-category comment descriptions) for review and adjustment.\nChanges take effect on save, no restart needed.\n\nTo share one ruleset across projects, click "Modify Global Settings" and set pmd.rulesetPath',
|
||||
ja: 'PMDはJava製のため、まずJava実行環境(JDK 8+)のインストールが必要です。\n\n拡張機能にはPMDルールセット(7カテゴリ・274ルール)が同梱されており、Java導入後すぐに利用できます。\n「プロジェクト設定を作成」をクリックすると、プロジェクト直下にruleset.xmlが生成されます。内容は組み込みルールセットと完全一致し(カテゴリごとの説明コメント付き)、確認・調整が可能です。\n保存後すぐに反映され、再起動は不要です。\n\n複数プロジェクトで同じルールを共有する場合、「グローバル設定を変更」でpmd.rulesetPathを設定してください',
|
||||
},
|
||||
'setup.adapter.sqlLanguages': {
|
||||
'zh-CN': 'SQL',
|
||||
en: 'SQL',
|
||||
ja: 'SQL',
|
||||
},
|
||||
'setup.adapter.sqlGuide': {
|
||||
'zh-CN': '需要 Python 环境和 sqlfluff;运行 pip install sqlfluff,项目根目录创建 .sqlfluff',
|
||||
en: 'Requires Python and sqlfluff; run pip install sqlfluff, create .sqlfluff in project root',
|
||||
ja: 'Python環境とsqlfluffが必要。pip install sqlfluff を実行し、プロジェクトルートに.sqlfluffを作成してください',
|
||||
'setup.adapter.sqlHelp': {
|
||||
'zh-CN': 'SQLFluff 是 Python 命令,需先安装 Python 环境与 sqlfluff(pip install sqlfluff),否则无法执行。\n\n插件已内置一套 SQLFluff 精选规则(26 项),安装后即可开箱使用,无需额外配置。\n点击「创建项目配置」,会在项目根目录生成 .sqlfluff,内容与插件内置配置一致(含每条规则的说明注释,以及 rules = all 的启用提示),方便查看与调整。\n保存文件即自动生效,无需重启。\n\n若多个项目共用同一份配置,可点「修改全局设置」填写全局路径',
|
||||
en: 'SQLFluff is a Python command; install Python and sqlfluff (pip install sqlfluff) first.\n\nThe extension ships built-in SQLFluff curated rules (26 items), usable right after install.\nClick "Create Project Config" to generate .sqlfluff in the project root, identical to the built-in config (including per-rule comment descriptions and a rules = all hint) for review and adjustment.\nChanges take effect on save, no restart needed.\n\nTo share one config across projects, click "Modify Global Settings" and set the global path',
|
||||
ja: 'SQLFluffはPython製コマンドのため、まずPython環境とsqlfluff(pip install sqlfluff)が必要です。\n\n拡張機能にはSQLFluff精選ルール(26項目)が同梱されており、インストール後すぐに利用できます。\n「プロジェクト設定を作成」をクリックすると、プロジェクト直下に.sqlfluffが生成されます。内容は組み込み設定と一致し(ルールごとの説明コメントとrules = allのヒント付き)、確認・調整が可能です。\n保存後すぐに反映され、再起動は不要です。\n\n複数プロジェクトで同じ設定を共有する場合、「グローバル設定を変更」でパスを設定してください',
|
||||
},
|
||||
'setup.adapter.eslintLanguages': {
|
||||
'zh-CN': 'JS, TS, JSX, TSX(含 JSP 中的 JavaScript 代码,例如<script>)',
|
||||
en: 'JS, TS, JSX, TSX (including JavaScript in JSP, e.g. <script>)',
|
||||
ja: 'JS, TS, JSX, TSX(JSP内のJavaScriptコードを含む、例: <script>)',
|
||||
},
|
||||
'setup.adapter.eslintGuide': {
|
||||
'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.eslintHelp': {
|
||||
'zh-CN': 'ESLint 需项目先安装 eslint(npm install eslint),否则无法解析规则文件。\n\n插件已内置一套 ESLint 规则(基于 recommended),开箱即用,无需额外配置。\n点击「创建项目配置」,会在项目根目录生成 eslint.config.js,内容与插件内置规则一致(92 条 JS 规则,含逐条说明注释),零依赖即可运行。\n注意:生成文件仅包含 JS 内置规则;TS 项目如需 TS 专项规则,请自行安装 typescript-eslint 并按文件头注释示例追加。\n若多个项目共用同一份规则,可点「修改全局设置」,在 eslintConfigPath 中填写该文件路径',
|
||||
en: 'ESLint must be installed in the project first (npm install eslint).\n\nThe extension ships built-in ESLint rules (based on recommended), usable out of the box.\nClick "Create Project Config" to generate eslint.config.js in the project root, identical to the built-in rules (92 JS rules with per-rule comment descriptions), running with zero extra dependencies.\nNote: the generated file only contains JS built-in rules; for TypeScript-specific rules, install typescript-eslint and follow the example in the file header comment.\nTo share one config across projects, click "Modify Global Settings" and set eslintConfigPath',
|
||||
ja: 'プロジェクトで先にeslintをインストール(npm install eslint)しておく必要があります。\n\n拡張機能にはESLintルール(recommendedベース)が同梱されており、設定不要で利用できます。\n「プロジェクト設定を作成」をクリックすると、プロジェクト直下にeslint.config.jsが生成されます。内容は組み込みルールと一致し(JSルール92件・各ルールに説明コメント付き)、追加依存なしで動作します。\n注: 生成ファイルにはJS組み込みルールのみ含まれます。TS専用ルールが必要な場合は、typescript-eslintをインストールし、ファイル先頭のコメント例に従って追記してください。\n複数プロジェクトで同じルールを共有する場合、「グローバル設定を変更」でeslintConfigPathを設定してください',
|
||||
},
|
||||
'setup.adapter.stylelintLanguages': {
|
||||
'zh-CN': 'CSS, SCSS, Less(含 JSP 中的 CSS 代码,例如<style>)',
|
||||
en: 'CSS, SCSS, Less (including CSS in JSP, e.g. <style>)',
|
||||
ja: 'CSS, SCSS, Less(JSP内のCSSコードを含む、例: <style>)',
|
||||
},
|
||||
'setup.adapter.stylelintGuide': {
|
||||
'zh-CN': '项目根目录创建 .stylelintrc 或在 VS Code 设置中配置 stylelintConfigPath',
|
||||
en: 'Create .stylelintrc in project root or set stylelintConfigPath in VS Code settings',
|
||||
ja: 'プロジェクトルートに.stylelintrcを作成するか、VS Code設定でstylelintConfigPathを設定してください',
|
||||
'setup.adapter.stylelintHelp': {
|
||||
'zh-CN': 'Stylelint 需项目先安装 stylelint(npm install stylelint),否则无法解析规则文件。\n\n插件已内置一套 Stylelint 规则(基于 recommended),开箱即用,无需额外配置。\n点击「创建项目配置」,会在项目根目录生成 .stylelintrc.js,内容与插件内置规则一致(68 条,含逐条说明注释),零依赖即可运行。\n若多个项目共用同一份规则,可点「修改全局设置」,在 stylelintConfigPath 中填写该文件路径',
|
||||
en: 'Stylelint must be installed in the project first (npm install stylelint).\n\nThe extension ships built-in Stylelint rules (based on recommended), usable out of the box.\nClick "Create Project Config" to generate .stylelintrc.js in the project root, identical to the built-in rules (68 rules with per-rule comment descriptions), running with zero extra dependencies.\nTo share one config across projects, click "Modify Global Settings" and set stylelintConfigPath',
|
||||
ja: 'プロジェクトで先にstylelintをインストール(npm install stylelint)しておく必要があります。\n\n拡張機能にはStylelintルール(recommendedベース)が同梱されており、設定不要で利用できます。\n「プロジェクト設定を作成」をクリックすると、プロジェクト直下に.stylelintrc.jsが生成されます。内容は組み込みルールと一致し(68件・各ルールに説明コメント付き)、追加依存なしで動作します。\n複数プロジェクトで同じルールを共有する場合、「グローバル設定を変更」でstylelintConfigPathを設定してください',
|
||||
},
|
||||
'setup.aiReviewStatusCapability': {
|
||||
'zh-CN': '审查能力',
|
||||
|
||||
@@ -0,0 +1,371 @@
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import * as vscode from 'vscode';
|
||||
import js from '@eslint/js';
|
||||
import staticRules from './static-rules.json';
|
||||
import type { Language } from '../i18n/messages';
|
||||
|
||||
export const eslintExtraRules: Record<string, 'error' | 'warn'> = {
|
||||
'eqeqeq': 'error',
|
||||
'no-eq-null': 'error',
|
||||
'no-self-compare': 'error',
|
||||
'no-promise-executor-return': 'error',
|
||||
'no-shadow': 'error',
|
||||
'no-unassigned-vars': 'error',
|
||||
'no-useless-assignment': 'error',
|
||||
'block-scoped-var': 'error',
|
||||
'default-case': 'error',
|
||||
'default-case-last': 'error',
|
||||
'no-unmodified-loop-condition': 'error',
|
||||
'no-unreachable-loop': 'error',
|
||||
'no-eval': 'error',
|
||||
'no-extend-native': 'error',
|
||||
'no-var': 'error',
|
||||
'no-await-in-loop': 'warn',
|
||||
'prefer-template': 'warn',
|
||||
'prefer-object-spread': 'warn',
|
||||
'prefer-rest-params': 'warn',
|
||||
'prefer-spread': 'warn',
|
||||
'prefer-object-has-own': 'warn',
|
||||
'no-useless-concat': 'warn',
|
||||
'no-useless-return': 'warn',
|
||||
'no-useless-computed-key': 'warn',
|
||||
'no-useless-rename': 'warn',
|
||||
'no-param-reassign': 'warn',
|
||||
'no-return-assign': 'error',
|
||||
'no-throw-literal': 'error',
|
||||
'camelcase': 'warn',
|
||||
'new-cap': 'warn',
|
||||
'no-array-constructor': 'error',
|
||||
};
|
||||
|
||||
export const eslintExtraTsRules: Record<string, 'error' | 'warn' | 'off'> = {
|
||||
'@typescript-eslint/no-non-null-assertion': 'error',
|
||||
'@typescript-eslint/no-dynamic-delete': 'error',
|
||||
'@typescript-eslint/no-useless-empty-export': 'error',
|
||||
'@typescript-eslint/consistent-type-imports': 'error',
|
||||
'@typescript-eslint/unified-signatures': 'error',
|
||||
'@typescript-eslint/no-extraneous-class': 'warn',
|
||||
'@typescript-eslint/no-useless-constructor': 'warn',
|
||||
'@typescript-eslint/no-non-null-asserted-nullish-coalescing': 'error',
|
||||
'@typescript-eslint/no-invalid-void-type': 'warn',
|
||||
'@typescript-eslint/prefer-literal-enum-member': 'warn',
|
||||
'@typescript-eslint/prefer-enum-initializers': 'warn',
|
||||
'no-shadow': 'off',
|
||||
'@typescript-eslint/no-shadow': 'error',
|
||||
'no-array-constructor': 'off',
|
||||
};
|
||||
|
||||
export const stylelintExtraRules: Record<string, unknown> = {
|
||||
'color-no-invalid-hex': true,
|
||||
'function-linear-gradient-no-nonstandard-direction': true,
|
||||
'function-no-unknown': true,
|
||||
'unit-no-unknown': true,
|
||||
'no-unknown-animations': true,
|
||||
'no-unknown-custom-media': true,
|
||||
'no-unknown-custom-properties': true,
|
||||
|
||||
'at-rule-no-vendor-prefix': true,
|
||||
'media-feature-name-no-vendor-prefix': true,
|
||||
'property-no-vendor-prefix': true,
|
||||
'selector-no-vendor-prefix': true,
|
||||
'value-no-vendor-prefix': true,
|
||||
|
||||
'color-hex-length': 'short',
|
||||
'color-function-notation': 'modern',
|
||||
'length-zero-no-unit': true,
|
||||
'selector-pseudo-element-colon-notation': 'double',
|
||||
'import-notation': 'string',
|
||||
'alpha-value-notation': 'number',
|
||||
'hue-degree-notation': 'angle',
|
||||
'keyframe-selector-notation': 'percentage',
|
||||
|
||||
'declaration-block-no-redundant-longhand-properties': true,
|
||||
'shorthand-property-no-redundant-values': true,
|
||||
'block-no-redundant-nested-style-rules': true,
|
||||
|
||||
'color-named': 'never',
|
||||
'font-family-name-quotes': 'always-where-required',
|
||||
|
||||
'number-max-precision': 4,
|
||||
'comment-whitespace-inside': 'always',
|
||||
};
|
||||
|
||||
export 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';
|
||||
|
||||
export function buildBuiltinSqlfluffConfig(dialect: string): string {
|
||||
return `[sqlfluff]
|
||||
rules = ${BUILTIN_SQLFLUFF_RULES}
|
||||
dialect = ${dialect}
|
||||
max_line_length = 80
|
||||
indent_unit = space
|
||||
tab_space_size = 4
|
||||
`;
|
||||
}
|
||||
|
||||
interface StaticRuleEntry {
|
||||
id: string;
|
||||
description?: string;
|
||||
descriptionZh?: string;
|
||||
descriptionJa?: string;
|
||||
}
|
||||
|
||||
export function getRuleDescription(linter: string, ruleId: string, lang: Language): string | undefined {
|
||||
const section = staticRules.rules[linter as keyof typeof staticRules.rules] as StaticRuleEntry[] | undefined;
|
||||
if (!section) { return undefined; }
|
||||
const entry = section.find(r => r.id === `${linter}/${ruleId}`);
|
||||
if (!entry) { return undefined; }
|
||||
if (lang === 'zh-CN') { return entry.descriptionZh ?? entry.description; }
|
||||
if (lang === 'ja') { return entry.descriptionJa ?? entry.description; }
|
||||
return entry.description;
|
||||
}
|
||||
|
||||
const ESLINT_RULES_URL = 'https://eslint.org/docs/latest/rules/';
|
||||
const STYLELINT_RULES_URL = 'https://stylelint.io/user-guide/rules/';
|
||||
const SQLFLUFF_RULES_URL = 'https://docs.sqlfluff.com/en/stable/reference/rules.html';
|
||||
|
||||
interface HeaderLabels {
|
||||
projectConfig: (name: string) => string;
|
||||
howToAdd: string;
|
||||
addExample: string;
|
||||
addExampleStyle: string;
|
||||
allRules: string;
|
||||
dialect: string;
|
||||
enableAll: string;
|
||||
ruleList: string;
|
||||
excludePrefix: string;
|
||||
}
|
||||
|
||||
const HEADER: Record<Language, HeaderLabels> = {
|
||||
'zh-CN': {
|
||||
projectConfig: name => `项目配置 — 与插件内置 ${name} 规则一致(由插件生成)`,
|
||||
howToAdd: '如何添加规则:在 rules 中新增一行',
|
||||
addExample: "如 'rule-name': 'warn' 或 'rule-name': ['error', 'always']",
|
||||
addExampleStyle: "如 'indentation': 2 或 'rule-name': true",
|
||||
allRules: '全部可用规则:',
|
||||
dialect: '数据库方言:postgres / mysql / bigquery / snowflake 等',
|
||||
enableAll: '想启用全部规则时改为 → rules = all',
|
||||
ruleList: '内置精选规则清单:',
|
||||
excludePrefix: '排除:',
|
||||
},
|
||||
en: {
|
||||
projectConfig: name => `Project config — matches the extension's built-in ${name} rules (generated by the extension)`,
|
||||
howToAdd: 'To add a rule: add a line in rules',
|
||||
addExample: "e.g. 'rule-name': 'warn' or 'rule-name': ['error', 'always']",
|
||||
addExampleStyle: "e.g. 'indentation': 2 or 'rule-name': true",
|
||||
allRules: 'All available rules:',
|
||||
dialect: 'Database dialect: postgres / mysql / bigquery / snowflake etc.',
|
||||
enableAll: 'Change to rules = all to enable all rules',
|
||||
ruleList: 'Built-in curated rule list:',
|
||||
excludePrefix: 'Excluded: ',
|
||||
},
|
||||
ja: {
|
||||
projectConfig: name => `プロジェクト設定 — 拡張機能の組み込み${name}ルールと一致(拡張機能が生成)`,
|
||||
howToAdd: 'ルールを追加するには: rulesに1行追加します',
|
||||
addExample: "例: 'rule-name': 'warn' または 'rule-name': ['error', 'always']",
|
||||
addExampleStyle: "例: 'indentation': 2 または 'rule-name': true",
|
||||
allRules: '利用可能な全ルール: ',
|
||||
dialect: 'データベース方言: postgres / mysql / bigquery / snowflake など',
|
||||
enableAll: '全ルールを有効にする場合は rules = all に変更',
|
||||
ruleList: '組み込みの精選ルール一覧:',
|
||||
excludePrefix: '除外:',
|
||||
},
|
||||
};
|
||||
|
||||
const TS_NOTE: Record<Language, string[]> = {
|
||||
'zh-CN': [
|
||||
'注意:本文件仅包含 JS 内置规则;TS 项目如需 TS 专项规则,',
|
||||
' 请安装 typescript-eslint 后自行追加,例如:',
|
||||
" const ts = require('typescript-eslint');",
|
||||
' module.exports = [ ...本文件配置, ...ts.configs.recommended ];',
|
||||
],
|
||||
en: [
|
||||
'Note: this file only contains JS built-in rules. For TypeScript-specific rules,',
|
||||
' install typescript-eslint and extend, e.g.:',
|
||||
" const ts = require('typescript-eslint');",
|
||||
' module.exports = [ ...this config, ...ts.configs.recommended ];',
|
||||
],
|
||||
ja: [
|
||||
'注: このファイルはJS組み込みルールのみです。TS専用ルールが必要な場合は、',
|
||||
' typescript-eslintをインストールして追記してください。例:',
|
||||
" const ts = require('typescript-eslint');",
|
||||
' module.exports = [ ...本設定, ...ts.configs.recommended ];',
|
||||
],
|
||||
};
|
||||
|
||||
const PMD_CATEGORY_COMMENTS: Record<string, Record<Language, string>> = {
|
||||
bestpractices: {
|
||||
'zh-CN': '最佳实践类(避免空 catch、关闭流等)',
|
||||
en: 'Best practices (avoid empty catch, close streams, etc.)',
|
||||
ja: 'ベストプラクティス(空のcatch回避、ストリームクローズ等)',
|
||||
},
|
||||
codestyle: {
|
||||
'zh-CN': '代码风格类(命名规范、花括号位置等)',
|
||||
en: 'Code style (naming conventions, brace placement, etc.)',
|
||||
ja: 'コードスタイル(命名規則、ブレース位置等)',
|
||||
},
|
||||
design: {
|
||||
'zh-CN': '设计类(过度耦合、复杂度、设计缺陷等)',
|
||||
en: 'Design (over-coupling, complexity, design flaws, etc.)',
|
||||
ja: '設計(過度な結合、複雑度、設計上の欠陥等)',
|
||||
},
|
||||
errorprone: {
|
||||
'zh-CN': '易错类(空 catch、错误处理遗漏等)',
|
||||
en: 'Error-prone (empty catch, missed error handling, etc.)',
|
||||
ja: 'エラーを起こしやすい(空のcatch、エラー処理の見落とし等)',
|
||||
},
|
||||
multithreading: {
|
||||
'zh-CN': '多线程类(线程使用、并发问题等)',
|
||||
en: 'Multithreading (thread usage, concurrency issues, etc.)',
|
||||
ja: 'マルチスレッド(スレッド使用、並行性の問題等)',
|
||||
},
|
||||
performance: {
|
||||
'zh-CN': '性能类(重复对象创建、低效操作等)',
|
||||
en: 'Performance (repeated object creation, inefficient operations, etc.)',
|
||||
ja: 'パフォーマンス(オブジェクトの再生成、非効率な操作等)',
|
||||
},
|
||||
security: {
|
||||
'zh-CN': '安全类(不安全的编码实践等)',
|
||||
en: 'Security (unsafe coding practices, etc.)',
|
||||
ja: 'セキュリティ(安全でないコーディング慣行等)',
|
||||
},
|
||||
};
|
||||
|
||||
function extractPmdVersion(): string {
|
||||
const v = staticRules.linterVersion.pmd as string | undefined;
|
||||
const m = v?.match(/(\d+\.\d+\.\d+)/);
|
||||
return m ? m[1] : 'latest';
|
||||
}
|
||||
|
||||
function resolvePmdRulesetPath(): string {
|
||||
const candidates: string[] = [];
|
||||
try {
|
||||
const ext = vscode.extensions.getExtension?.('vscode-code-reviewer');
|
||||
if (ext?.extensionPath) {
|
||||
candidates.push(path.join(ext.extensionPath, 'jars', 'pmd', 'pmd-java-ruleset.xml'));
|
||||
}
|
||||
} catch { /* ignore */ }
|
||||
let dir = __dirname;
|
||||
for (let i = 0; i < 5; i++) {
|
||||
candidates.push(path.join(dir, 'jars', 'pmd', 'pmd-java-ruleset.xml'));
|
||||
dir = path.dirname(dir);
|
||||
}
|
||||
const found = candidates.find(p => fs.existsSync(p));
|
||||
if (found) { return found; }
|
||||
throw new Error(`PMD bundled ruleset not found: ${candidates.join(', ')}`);
|
||||
}
|
||||
|
||||
export function buildEslintProjectConfigText(lang: Language): string {
|
||||
const l = HEADER[lang];
|
||||
const jsRules = js.configs.recommended.rules as Record<string, unknown>;
|
||||
const rules: Array<[string, unknown]> = [...Object.entries(jsRules), ...Object.entries(eslintExtraRules)];
|
||||
const body = rules.map(([id, sev]) => {
|
||||
const desc = getRuleDescription('eslint', id, lang);
|
||||
return ` '${id}': ${JSON.stringify(sev)},${desc ? ` // ${desc}` : ''}`;
|
||||
});
|
||||
const lines = [
|
||||
'/* global module */',
|
||||
'// ============================================================',
|
||||
`// ${l.projectConfig('ESLint')}`,
|
||||
`// ${l.howToAdd}`,
|
||||
`// ${l.addExample}`,
|
||||
`// ${l.allRules}${ESLINT_RULES_URL}`,
|
||||
...TS_NOTE[lang].map(n => `// ${n}`),
|
||||
'// ============================================================',
|
||||
'module.exports = [',
|
||||
' {',
|
||||
' rules: {',
|
||||
...body,
|
||||
' },',
|
||||
' },',
|
||||
'];',
|
||||
'',
|
||||
];
|
||||
return lines.join('\n');
|
||||
}
|
||||
|
||||
export async function buildStylelintProjectConfigText(lang: Language): Promise<string> {
|
||||
const l = HEADER[lang];
|
||||
const rec = (await import('stylelint-config-recommended')).default;
|
||||
const rules: Record<string, unknown> = { ...rec.rules, ...stylelintExtraRules };
|
||||
const body = Object.entries(rules).map(([id, val]) => {
|
||||
const desc = getRuleDescription('stylelint', id, lang);
|
||||
return ` '${id}': ${JSON.stringify(val)},${desc ? ` // ${desc}` : ''}`;
|
||||
});
|
||||
const lines = [
|
||||
'/* global module */',
|
||||
'// ============================================================',
|
||||
`// ${l.projectConfig('Stylelint')}`,
|
||||
`// ${l.howToAdd}`,
|
||||
`// ${l.addExampleStyle}`,
|
||||
`// ${l.allRules}${STYLELINT_RULES_URL}`,
|
||||
'// ============================================================',
|
||||
'module.exports = {',
|
||||
' rules: {',
|
||||
...body,
|
||||
' },',
|
||||
'};',
|
||||
'',
|
||||
];
|
||||
return lines.join('\n');
|
||||
}
|
||||
|
||||
export function buildSqlfluffProjectConfigText(dialect: string, lang: Language): string {
|
||||
const l = HEADER[lang];
|
||||
const ruleIds = BUILTIN_SQLFLUFF_RULES.split(',');
|
||||
const ruleComments = ruleIds.map(id => {
|
||||
const desc = getRuleDescription('sqlfluff', id, lang);
|
||||
return `# ${id} ${desc ?? ''}`;
|
||||
});
|
||||
const lines = [
|
||||
'[sqlfluff]',
|
||||
`# ${l.dialect}`,
|
||||
`dialect = ${dialect}`,
|
||||
`# ${l.enableAll}`,
|
||||
`# ${l.ruleList}`,
|
||||
...ruleComments,
|
||||
`# ${l.allRules}${SQLFLUFF_RULES_URL}`,
|
||||
`rules = ${BUILTIN_SQLFLUFF_RULES}`,
|
||||
'max_line_length = 80',
|
||||
'indent_unit = space',
|
||||
'tab_space_size = 4',
|
||||
'',
|
||||
];
|
||||
return lines.join('\n');
|
||||
}
|
||||
|
||||
export async function buildPmdProjectRulesetText(lang: Language): Promise<string> {
|
||||
const l = HEADER[lang];
|
||||
const rulesetPath = resolvePmdRulesetPath();
|
||||
const content = await fs.promises.readFile(rulesetPath, 'utf-8');
|
||||
const allRulesUrl = `https://docs.pmd-code.org/pmd-doc-${extractPmdVersion()}/pmd_rules_java.html`;
|
||||
const out: string[] = [];
|
||||
for (const line of content.split('\n')) {
|
||||
if (/<description>/.test(line)) {
|
||||
out.push(line);
|
||||
out.push(` <!-- ${l.allRules}${allRulesUrl} -->`);
|
||||
continue;
|
||||
}
|
||||
const ruleRefMatch = line.match(/<rule ref="category\/java\/(\w+)\.xml"/);
|
||||
if (ruleRefMatch) {
|
||||
const cat = ruleRefMatch[1];
|
||||
const comment = PMD_CATEGORY_COMMENTS[cat]?.[lang] ?? cat;
|
||||
out.push(` <!-- ${comment} -->`);
|
||||
out.push(line);
|
||||
continue;
|
||||
}
|
||||
const excludeMatch = line.match(/<exclude name="([A-Za-z0-9_]+)"\/>/);
|
||||
if (excludeMatch) {
|
||||
const name = excludeMatch[1];
|
||||
const desc = getRuleDescription('pmd', name, lang);
|
||||
if (desc) {
|
||||
out.push(` <!-- ${l.excludePrefix}${desc} -->`);
|
||||
}
|
||||
out.push(line);
|
||||
continue;
|
||||
}
|
||||
out.push(line);
|
||||
}
|
||||
return out.join('\n');
|
||||
}
|
||||
@@ -984,6 +984,210 @@
|
||||
"description": "Require or disallow whitespace inside comments",
|
||||
"descriptionZh": "要求或禁止注释内部空白",
|
||||
"descriptionJa": "コメント内の空白を要求または禁止する"
|
||||
},
|
||||
{
|
||||
"id": "stylelint/annotation-no-unknown",
|
||||
"description": "Disallow unknown annotations",
|
||||
"descriptionZh": "禁止未知注解",
|
||||
"descriptionJa": "未知の注釈を禁止する"
|
||||
},
|
||||
{
|
||||
"id": "stylelint/at-rule-descriptor-no-unknown",
|
||||
"description": "Disallow unknown descriptors within at-rules",
|
||||
"descriptionZh": "禁止 @ 规则中的未知描述符",
|
||||
"descriptionJa": "@規則内の未知のディスクリプタを禁止する"
|
||||
},
|
||||
{
|
||||
"id": "stylelint/at-rule-descriptor-value-no-unknown",
|
||||
"description": "Disallow unknown values for at-rule descriptors",
|
||||
"descriptionZh": "禁止 @ 规则描述符的未知值",
|
||||
"descriptionJa": "@規則のディスクリプタの未知の値を禁止する"
|
||||
},
|
||||
{
|
||||
"id": "stylelint/at-rule-no-deprecated",
|
||||
"description": "Disallow deprecated at-rules",
|
||||
"descriptionZh": "禁止已弃用的 @ 规则",
|
||||
"descriptionJa": "非推奨の@規則を禁止する"
|
||||
},
|
||||
{
|
||||
"id": "stylelint/at-rule-no-unknown",
|
||||
"description": "Disallow unknown at-rules",
|
||||
"descriptionZh": "禁止未知的 @ 规则",
|
||||
"descriptionJa": "未知の@規則を禁止する"
|
||||
},
|
||||
{
|
||||
"id": "stylelint/at-rule-prelude-no-invalid",
|
||||
"description": "Disallow invalid at-rule preludes",
|
||||
"descriptionZh": "禁止无效的 @ 规则前奏",
|
||||
"descriptionJa": "無効な@規則のプレリュードを禁止する"
|
||||
},
|
||||
{
|
||||
"id": "stylelint/comment-no-empty",
|
||||
"description": "Disallow empty comments",
|
||||
"descriptionZh": "禁止空注释",
|
||||
"descriptionJa": "空のコメントを禁止する"
|
||||
},
|
||||
{
|
||||
"id": "stylelint/custom-property-no-missing-var-function",
|
||||
"description": "Disallow custom properties that do not use the var() function",
|
||||
"descriptionZh": "禁止未使用 var() 函数的自定义属性",
|
||||
"descriptionJa": "var()関数を使用しないカスタムプロパティを禁止する"
|
||||
},
|
||||
{
|
||||
"id": "stylelint/declaration-block-no-duplicate-custom-properties",
|
||||
"description": "Disallow duplicate custom properties within declaration blocks",
|
||||
"descriptionZh": "禁止声明块中的重复自定义属性",
|
||||
"descriptionJa": "宣言ブロック内の重複したカスタムプロパティを禁止する"
|
||||
},
|
||||
{
|
||||
"id": "stylelint/declaration-block-no-shorthand-property-overrides",
|
||||
"description": "Disallow shorthand properties that override related longhand properties",
|
||||
"descriptionZh": "禁止覆盖相关长写属性的简写属性",
|
||||
"descriptionJa": "関連するロングハンドプロパティを上書きするショートハンドを禁止する"
|
||||
},
|
||||
{
|
||||
"id": "stylelint/declaration-property-value-keyword-no-deprecated",
|
||||
"description": "Disallow deprecated keyword values within declarations",
|
||||
"descriptionZh": "禁止声明中的已弃用关键字值",
|
||||
"descriptionJa": "宣言内の非推奨のキーワード値を禁止する"
|
||||
},
|
||||
{
|
||||
"id": "stylelint/declaration-property-value-no-unknown",
|
||||
"description": "Disallow unknown values for properties within declarations",
|
||||
"descriptionZh": "禁止声明中属性的未知值",
|
||||
"descriptionJa": "宣言内のプロパティの未知の値を禁止する"
|
||||
},
|
||||
{
|
||||
"id": "stylelint/font-family-no-duplicate-names",
|
||||
"description": "Disallow duplicate font family names",
|
||||
"descriptionZh": "禁止重复的字体族名称",
|
||||
"descriptionJa": "重複したフォントファミリ名を禁止する"
|
||||
},
|
||||
{
|
||||
"id": "stylelint/function-calc-no-unspaced-operator",
|
||||
"description": "Disallow unspaced operators within calc functions",
|
||||
"descriptionZh": "禁止 calc 函数中运算符两侧无空格",
|
||||
"descriptionJa": "calc関数内で演算子の前後にスペースがないことを禁止する"
|
||||
},
|
||||
{
|
||||
"id": "stylelint/keyframe-block-no-duplicate-selectors",
|
||||
"description": "Disallow duplicate selectors within keyframe blocks",
|
||||
"descriptionZh": "禁止关键帧块中的重复选择器",
|
||||
"descriptionJa": "キーフレームブロック内の重複セレクタを禁止する"
|
||||
},
|
||||
{
|
||||
"id": "stylelint/keyframe-declaration-no-important",
|
||||
"description": "Disallow !important within keyframe declarations",
|
||||
"descriptionZh": "禁止关键帧声明中的 !important",
|
||||
"descriptionJa": "キーフレーム宣言内の!importantを禁止する"
|
||||
},
|
||||
{
|
||||
"id": "stylelint/media-feature-name-no-unknown",
|
||||
"description": "Disallow unknown media feature names",
|
||||
"descriptionZh": "禁止未知的媒体特性名称",
|
||||
"descriptionJa": "未知のメディア特性名を禁止する"
|
||||
},
|
||||
{
|
||||
"id": "stylelint/media-feature-name-value-no-unknown",
|
||||
"description": "Disallow unknown values for media features",
|
||||
"descriptionZh": "禁止媒体特性的未知值",
|
||||
"descriptionJa": "メディア特性の未知の値を禁止する"
|
||||
},
|
||||
{
|
||||
"id": "stylelint/media-query-no-invalid",
|
||||
"description": "Disallow invalid media queries",
|
||||
"descriptionZh": "禁止无效的媒体查询",
|
||||
"descriptionJa": "無効なメディアクエリを禁止する"
|
||||
},
|
||||
{
|
||||
"id": "stylelint/media-type-no-deprecated",
|
||||
"description": "Disallow deprecated media types",
|
||||
"descriptionZh": "禁止已弃用的媒体类型",
|
||||
"descriptionJa": "非推奨のメディアタイプを禁止する"
|
||||
},
|
||||
{
|
||||
"id": "stylelint/named-grid-areas-no-invalid",
|
||||
"description": "Disallow invalid named grid areas",
|
||||
"descriptionZh": "禁止无效的命名网格区域",
|
||||
"descriptionJa": "無効な名前付きグリッドエリアを禁止する"
|
||||
},
|
||||
{
|
||||
"id": "stylelint/nesting-selector-no-missing-scoping-root",
|
||||
"description": "Disallow nesting selectors without a scoping root",
|
||||
"descriptionZh": "禁止无作用域根的嵌套选择器",
|
||||
"descriptionJa": "スコープルートのないネストセレクタを禁止する"
|
||||
},
|
||||
{
|
||||
"id": "stylelint/no-duplicate-at-import-rules",
|
||||
"description": "Disallow duplicate @import rules",
|
||||
"descriptionZh": "禁止重复的 @import 规则",
|
||||
"descriptionJa": "重複した@import規則を禁止する"
|
||||
},
|
||||
{
|
||||
"id": "stylelint/no-duplicate-selectors",
|
||||
"description": "Disallow duplicate selectors within a stylesheet",
|
||||
"descriptionZh": "禁止样式表中重复的选择器",
|
||||
"descriptionJa": "スタイルシート内の重複セレクタを禁止する"
|
||||
},
|
||||
{
|
||||
"id": "stylelint/no-empty-source",
|
||||
"description": "Disallow empty sources",
|
||||
"descriptionZh": "禁止空样式源",
|
||||
"descriptionJa": "空のスタイルシートを禁止する"
|
||||
},
|
||||
{
|
||||
"id": "stylelint/no-invalid-double-slash-comments",
|
||||
"description": "Disallow invalid double-slash comments",
|
||||
"descriptionZh": "禁止无效的双斜杠注释",
|
||||
"descriptionJa": "無効なダブルスラッシュコメントを禁止する"
|
||||
},
|
||||
{
|
||||
"id": "stylelint/no-invalid-position-at-import-rule",
|
||||
"description": "Disallow invalid positions of @import rules",
|
||||
"descriptionZh": "禁止 @import 规则位于无效位置",
|
||||
"descriptionJa": "@import規則の無効な位置を禁止する"
|
||||
},
|
||||
{
|
||||
"id": "stylelint/no-invalid-position-declaration",
|
||||
"description": "Disallow invalid positions of declarations",
|
||||
"descriptionZh": "禁止声明位于无效位置",
|
||||
"descriptionJa": "宣言の無効な位置を禁止する"
|
||||
},
|
||||
{
|
||||
"id": "stylelint/no-irregular-whitespace",
|
||||
"description": "Disallow irregular whitespace",
|
||||
"descriptionZh": "禁止不规则空白",
|
||||
"descriptionJa": "不規則な空白を禁止する"
|
||||
},
|
||||
{
|
||||
"id": "stylelint/property-no-deprecated",
|
||||
"description": "Disallow deprecated properties",
|
||||
"descriptionZh": "禁止已弃用的属性",
|
||||
"descriptionJa": "非推奨のプロパティを禁止する"
|
||||
},
|
||||
{
|
||||
"id": "stylelint/selector-anb-no-unmatchable",
|
||||
"description": "Disallow unmatchable An+B selectors",
|
||||
"descriptionZh": "禁止无法匹配的 An+B 选择器",
|
||||
"descriptionJa": "マッチしないAn+Bセレクタを禁止する"
|
||||
},
|
||||
{
|
||||
"id": "stylelint/selector-type-no-unknown",
|
||||
"description": "Disallow unknown type selectors",
|
||||
"descriptionZh": "禁止未知的类型选择器",
|
||||
"descriptionJa": "未知のタイプセレクタを禁止する"
|
||||
},
|
||||
{
|
||||
"id": "stylelint/string-no-newline",
|
||||
"description": "Disallow newlines within strings",
|
||||
"descriptionZh": "禁止字符串中的换行",
|
||||
"descriptionJa": "文字列内の改行を禁止する"
|
||||
},
|
||||
{
|
||||
"id": "stylelint/syntax-string-no-invalid",
|
||||
"description": "Disallow invalid strings",
|
||||
"descriptionZh": "禁止无效字符串",
|
||||
"descriptionJa": "無効な文字列を禁止する"
|
||||
}
|
||||
],
|
||||
"pmd": [
|
||||
@@ -2624,6 +2828,138 @@
|
||||
"description": "Don't hard code initialization vectors",
|
||||
"descriptionZh": "不要硬编码初始化向量",
|
||||
"descriptionJa": "初期化ベクタをハードコードしない"
|
||||
},
|
||||
{
|
||||
"id": "pmd/DefaultLabelNotLastInSwitchStmt",
|
||||
"description": "Allow the default case to appear at any position in a switch statement",
|
||||
"descriptionZh": "允许 default 分支出现在 switch 语句中的任意位置",
|
||||
"descriptionJa": "default ケースが switch 文内の任意の位置にあることを許可"
|
||||
},
|
||||
{
|
||||
"id": "pmd/JUnit4TestShouldUseAfterAnnotation",
|
||||
"description": "Allow @After annotation instead of tearDown()",
|
||||
"descriptionZh": "允许使用 @After 注解替代 tearDown()",
|
||||
"descriptionJa": "@After アノテーションの使用を許可"
|
||||
},
|
||||
{
|
||||
"id": "pmd/JUnit4TestShouldUseBeforeAnnotation",
|
||||
"description": "Allow @Before annotation instead of setUp()",
|
||||
"descriptionZh": "允许使用 @Before 注解替代 setUp()",
|
||||
"descriptionJa": "@Before アノテーションの使用を許可"
|
||||
},
|
||||
{
|
||||
"id": "pmd/JUnit4TestShouldUseTestAnnotation",
|
||||
"description": "Allow JUnit 3 test methods instead of @Test annotation",
|
||||
"descriptionZh": "允许 JUnit3 风格测试方法替代 @Test 注解",
|
||||
"descriptionJa": "@Test アノテーションの代わりに JUnit3 スタイルのテストメソッドを許可"
|
||||
},
|
||||
{
|
||||
"id": "pmd/JUnit5TestShouldBePackagePrivate",
|
||||
"description": "Allow public JUnit 5 test methods",
|
||||
"descriptionZh": "允许 public 的 JUnit5 测试方法",
|
||||
"descriptionJa": "public な JUnit5 テストメソッドを許可"
|
||||
},
|
||||
{
|
||||
"id": "pmd/JUnitAssertionsShouldIncludeMessage",
|
||||
"description": "Allow assertions without a message",
|
||||
"descriptionZh": "允许不带消息的断言",
|
||||
"descriptionJa": "メッセージなしのアサーションを許可"
|
||||
},
|
||||
{
|
||||
"id": "pmd/JUnitTestContainsTooManyAsserts",
|
||||
"description": "Allow many assertions in a single test method",
|
||||
"descriptionZh": "允许单个测试方法中包含多个断言",
|
||||
"descriptionJa": "単一テストメソッド内の複数アサーションを許可"
|
||||
},
|
||||
{
|
||||
"id": "pmd/JUnitTestsShouldIncludeAssert",
|
||||
"description": "Allow test methods without assertions",
|
||||
"descriptionZh": "允许不含断言的测试方法",
|
||||
"descriptionJa": "アサーションを含まないテストメソッドを許可"
|
||||
},
|
||||
{
|
||||
"id": "pmd/SwitchStmtsShouldHaveDefault",
|
||||
"description": "Allow switch statements without a default case",
|
||||
"descriptionZh": "允许不带 default 分支的 switch 语句",
|
||||
"descriptionJa": "default ケースのない switch 文を許可"
|
||||
},
|
||||
{
|
||||
"id": "pmd/GenericsNaming",
|
||||
"description": "Allow non-canonical generic type naming",
|
||||
"descriptionZh": "允许非规范的泛型类型命名",
|
||||
"descriptionJa": "非標準的なジェネリクス型命名を許可"
|
||||
},
|
||||
{
|
||||
"id": "pmd/UnnecessaryLocalBeforeReturn",
|
||||
"description": "Allow local variables that are returned immediately",
|
||||
"descriptionZh": "允许先赋值后立即返回的局部变量",
|
||||
"descriptionJa": "即座に返すだけのローカル変数を許可"
|
||||
},
|
||||
{
|
||||
"id": "pmd/ShortVariable",
|
||||
"description": "Allow short variable names",
|
||||
"descriptionZh": "允许短变量名",
|
||||
"descriptionJa": "短い変数名を許可"
|
||||
},
|
||||
{
|
||||
"id": "pmd/ShortMethodName",
|
||||
"description": "Allow short method names",
|
||||
"descriptionZh": "允许短方法名",
|
||||
"descriptionJa": "短いメソッド名を許可"
|
||||
},
|
||||
{
|
||||
"id": "pmd/ShortClassName",
|
||||
"description": "Allow short class names",
|
||||
"descriptionZh": "允许短类名",
|
||||
"descriptionJa": "短いクラス名を許可"
|
||||
},
|
||||
{
|
||||
"id": "pmd/UseObjectForClearerAPI",
|
||||
"description": "Allow multiple primitive parameters in public methods",
|
||||
"descriptionZh": "允许公共方法中使用多个原始类型参数",
|
||||
"descriptionJa": "公開メソッドでの複数のプリミティブ引数を許可"
|
||||
},
|
||||
{
|
||||
"id": "pmd/AvoidCatchingNPE",
|
||||
"description": "Allow catching NullPointerException",
|
||||
"descriptionZh": "允许捕获 NullPointerException",
|
||||
"descriptionJa": "NullPointerException の捕捉を許可"
|
||||
},
|
||||
{
|
||||
"id": "pmd/AvoidCatchingThrowable",
|
||||
"description": "Allow catching Throwable",
|
||||
"descriptionZh": "允许捕获 Throwable",
|
||||
"descriptionJa": "Throwable の捕捉を許可"
|
||||
},
|
||||
{
|
||||
"id": "pmd/AvoidLosingExceptionInformation",
|
||||
"description": "Allow catching exceptions without preserving information",
|
||||
"descriptionZh": "允许丢失异常信息的异常捕获",
|
||||
"descriptionJa": "例外情報を失う捕捉を許可"
|
||||
},
|
||||
{
|
||||
"id": "pmd/DontImportSun",
|
||||
"description": "Allow imports from the sun package",
|
||||
"descriptionZh": "允许导入 sun 包",
|
||||
"descriptionJa": "sun パッケージのインポートを許可"
|
||||
},
|
||||
{
|
||||
"id": "pmd/NonCaseLabelInSwitchStatement",
|
||||
"description": "Allow non-case labels in switch statements",
|
||||
"descriptionZh": "允许 switch 语句中的非 case 标签",
|
||||
"descriptionJa": "switch 文内の case 以外のラベルを許可"
|
||||
},
|
||||
{
|
||||
"id": "pmd/UselessOperationOnImmutable",
|
||||
"description": "Allow operations on immutable objects",
|
||||
"descriptionZh": "允许对不可变对象执行操作",
|
||||
"descriptionJa": "不変オブジェクトへの操作を許可"
|
||||
},
|
||||
{
|
||||
"id": "pmd/TooFewBranchesForASwitchStatement",
|
||||
"description": "Allow switch statements with only one branch",
|
||||
"descriptionZh": "允许仅有一个分支的 switch 语句",
|
||||
"descriptionJa": "1つの分岐のみの switch 文を許可"
|
||||
}
|
||||
],
|
||||
"pmd-jsp": [
|
||||
@@ -3213,6 +3549,12 @@
|
||||
"tier": "P2",
|
||||
"descriptionZh": "移除空批次",
|
||||
"descriptionJa": "空のバッチを削除する"
|
||||
},
|
||||
{
|
||||
"id": "sqlfluff/core",
|
||||
"description": "Core rule group (stable, cross-dialect, syntax-related foundational rules)",
|
||||
"descriptionZh": "核心规则组(稳定、跨方言的语法类基础规则)",
|
||||
"descriptionJa": "コアルールグループ(安定、方言横断的な構文系の基本ルール)"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
+19
-3
@@ -90,6 +90,10 @@
|
||||
return d.innerHTML;
|
||||
}
|
||||
|
||||
function escapeAttr(text) {
|
||||
return text.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
|
||||
}
|
||||
|
||||
function populateModelOptions(providerId, currentModel) {
|
||||
var input = document.getElementById('modelInput');
|
||||
if (!input) { return; }
|
||||
@@ -186,7 +190,7 @@
|
||||
ruleList.innerHTML = '<div style="font-size:12px;color:#484f58;padding:8px 0;text-align:center;">' + msg.i18n.noRuleFiles + '</div>';
|
||||
}
|
||||
|
||||
_step1Done = c.provider && c.model && c.apiKeyConfigured && c.baseUrlConfigured;
|
||||
_step1Done = msg.adapterStatus && msg.adapterStatus.some(function (a) { return a.enabled; });
|
||||
_step2Done = msg.ruleFiles && msg.ruleFiles.length > 0;
|
||||
updateSteps(msg.connectionTested && msg.connectionSuccess);
|
||||
|
||||
@@ -256,10 +260,23 @@
|
||||
var toggleTooltip = a.enabled
|
||||
? i18n.toggleDisable.replace('{0}', a.name)
|
||||
: i18n.toggleEnable.replace('{0}', a.name);
|
||||
var helpMap = {
|
||||
pmd: i18n.pmdHelp,
|
||||
sqlfluff: i18n.sqlHelp,
|
||||
eslint: i18n.eslintHelp,
|
||||
stylelint: i18n.stylelintHelp,
|
||||
};
|
||||
var helpText = helpMap[a.id] || '';
|
||||
var helpIcon = helpText
|
||||
? '<span class="help-icon" data-help="' + escapeAttr(helpText) + '">?</span>'
|
||||
: '';
|
||||
|
||||
return '<div class="adapter-card' + (a.enabled ? '' : ' disabled') + '">' +
|
||||
'<div class="adapter-card-header">' +
|
||||
'<span class="adapter-card-name">' + a.name + '</span>' +
|
||||
'<span class="adapter-card-name-wrap">' +
|
||||
'<span class="adapter-card-name">' + a.name + '</span>' +
|
||||
helpIcon +
|
||||
'</span>' +
|
||||
'<div class="adapter-toggle' + (a.enabled ? '' : ' off') + '" data-adapter-id="' + a.id + '" data-tooltip="' + toggleTooltip + '"></div>' +
|
||||
'</div>' +
|
||||
'<div class="adapter-badges">' +
|
||||
@@ -268,7 +285,6 @@
|
||||
configBadge +
|
||||
'</div>' +
|
||||
'<div class="adapter-languages"><span class="adapter-lang-label">' + i18n.langLabel + '</span>' + escapeHtml(a.languages) + '</div>' +
|
||||
'<div class="adapter-guide">' + a.guideText + '</div>' +
|
||||
'<div class="adapter-actions">' +
|
||||
'<button class="adapter-btn" data-action="openAdapterConfig" data-adapter-id="' + a.id + '" data-tooltip="' + i18n.tooltipCreate.replace('{0}', a.projectConfigFileName) + '">' + i18n.btnCreateConfig + '</button>' +
|
||||
'<button class="adapter-btn" data-action="openSettings" data-settings-target="' + a.settingsTarget + '" data-tooltip="' + i18n.tooltipEdit + '">' + i18n.btnEditGlobal + '</button>' +
|
||||
|
||||
+63
-56
@@ -15,8 +15,14 @@ import { TxtConverter } from '../rules/converters/txt-converter';
|
||||
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 { t, getLanguage, onLanguageChange } from '../i18n/messages';
|
||||
import { getEslintConfigPath, getStylelintConfigPath, getPMDRulesetPath, getSqlFluffConfigFile, isAdapterEnabled, setAdapterEnabled } from '../config/linter';
|
||||
import {
|
||||
buildEslintProjectConfigText,
|
||||
buildStylelintProjectConfigText,
|
||||
buildSqlfluffProjectConfigText,
|
||||
buildPmdProjectRulesetText,
|
||||
} from '../rules/builtin-rules';
|
||||
|
||||
type ConfigMode = 'builtin' | 'project' | 'global';
|
||||
|
||||
@@ -30,55 +36,12 @@ interface AdapterConfigStatus {
|
||||
dependencyStatus: DependencyStatus;
|
||||
dependencyLabel?: string;
|
||||
configured: boolean;
|
||||
guideText: string;
|
||||
languages: string;
|
||||
projectConfigFileName: string;
|
||||
settingsTarget: string;
|
||||
}
|
||||
|
||||
function getPmdRulesetTemplate(): string {
|
||||
return `<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ruleset xmlns="http://pmd.sourceforge.net/ruleset/2.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd">
|
||||
<description>Custom PMD Ruleset</description>
|
||||
<!-- ${t('setup.template.pmdBestPractices')} -->
|
||||
<rule ref="category/java/bestpractices.xml" />
|
||||
<!-- ${t('setup.template.pmdCodeStyle')} -->
|
||||
<rule ref="category/java/codestyle.xml" />
|
||||
</ruleset>`;
|
||||
}
|
||||
|
||||
function getSqlfluffTemplate(): string {
|
||||
return `[sqlfluff]
|
||||
# ${t('setup.template.sqlfluffDialect')}
|
||||
dialect = mysql
|
||||
# ${t('setup.template.sqlfluffRules')}
|
||||
rules = all`;
|
||||
}
|
||||
|
||||
function getEslintTemplate(): string {
|
||||
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 {
|
||||
return `module.exports = {
|
||||
extends: 'stylelint-config-standard',
|
||||
rules: {
|
||||
'indentation': 2, // ${t('setup.template.stylelintComment1')}
|
||||
'no-empty': true, // ${t('setup.template.stylelintComment2')}
|
||||
},
|
||||
};`;
|
||||
}
|
||||
const SQLFLUFF_CONFIG_DIALECT = 'mysql';
|
||||
|
||||
const ADAPTER_METADATA: Record<string, {
|
||||
name: string;
|
||||
@@ -86,7 +49,7 @@ const ADAPTER_METADATA: Record<string, {
|
||||
settingsTarget: string;
|
||||
hasExternalDependency: boolean;
|
||||
dependencyLabel?: string;
|
||||
configFileTemplate: () => string;
|
||||
configFileTemplate: () => string | Promise<string>;
|
||||
i18nKey: string;
|
||||
}> = {
|
||||
pmd: {
|
||||
@@ -95,7 +58,7 @@ const ADAPTER_METADATA: Record<string, {
|
||||
settingsTarget: 'vscode-code-reviewer.pmd',
|
||||
hasExternalDependency: true,
|
||||
dependencyLabel: 'Java',
|
||||
configFileTemplate: getPmdRulesetTemplate,
|
||||
configFileTemplate: () => buildPmdProjectRulesetText(getLanguage()),
|
||||
i18nKey: 'pmd',
|
||||
},
|
||||
'sqlfluff': {
|
||||
@@ -104,7 +67,7 @@ const ADAPTER_METADATA: Record<string, {
|
||||
settingsTarget: 'vscode-code-reviewer.sqlfluff',
|
||||
hasExternalDependency: true,
|
||||
dependencyLabel: 'Python + sqlfluff',
|
||||
configFileTemplate: getSqlfluffTemplate,
|
||||
configFileTemplate: () => buildSqlfluffProjectConfigText(SQLFLUFF_CONFIG_DIALECT, getLanguage()),
|
||||
i18nKey: 'sql',
|
||||
},
|
||||
eslint: {
|
||||
@@ -112,7 +75,7 @@ const ADAPTER_METADATA: Record<string, {
|
||||
projectConfigFileName: 'eslint.config.js',
|
||||
settingsTarget: 'vscode-code-reviewer.linters',
|
||||
hasExternalDependency: false,
|
||||
configFileTemplate: getEslintTemplate,
|
||||
configFileTemplate: () => buildEslintProjectConfigText(getLanguage()),
|
||||
i18nKey: 'eslint',
|
||||
},
|
||||
stylelint: {
|
||||
@@ -120,7 +83,7 @@ const ADAPTER_METADATA: Record<string, {
|
||||
projectConfigFileName: '.stylelintrc.js',
|
||||
settingsTarget: 'vscode-code-reviewer.linters',
|
||||
hasExternalDependency: false,
|
||||
configFileTemplate: getStylelintTemplate,
|
||||
configFileTemplate: () => buildStylelintProjectConfigText(getLanguage()),
|
||||
i18nKey: 'stylelint',
|
||||
},
|
||||
};
|
||||
@@ -377,6 +340,10 @@ export class SetupViewProvider implements vscode.WebviewViewProvider {
|
||||
tooltipEdit: t('setup.adapter.tooltipEdit'),
|
||||
toggleEnable: t('setup.adapter.toggleEnable'),
|
||||
toggleDisable: t('setup.adapter.toggleDisable'),
|
||||
pmdHelp: t('setup.adapter.pmdHelp'),
|
||||
sqlHelp: t('setup.adapter.sqlHelp'),
|
||||
eslintHelp: t('setup.adapter.eslintHelp'),
|
||||
stylelintHelp: t('setup.adapter.stylelintHelp'),
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -780,9 +747,10 @@ input::placeholder { color: var(--vscode-input-placeholderForeground, var(--vsco
|
||||
.adapter-card.disabled { opacity: 0.45; }
|
||||
.adapter-card-header {
|
||||
display: flex; align-items: center; justify-content: space-between;
|
||||
margin-bottom: 6px;
|
||||
margin-bottom: 6px; position: relative;
|
||||
}
|
||||
.adapter-card-name { font-size: 12px; font-weight: 600; color: var(--vscode-foreground); }
|
||||
.adapter-card-name-wrap { display: inline-flex; align-items: center; min-width: 0; }
|
||||
.adapter-toggle {
|
||||
width: 30px; height: 16px; border-radius: 8px;
|
||||
background: #3fb950; position: relative; cursor: pointer;
|
||||
@@ -807,7 +775,6 @@ input::placeholder { color: var(--vscode-input-placeholderForeground, var(--vsco
|
||||
.adapter-badge-error { background: rgba(248,81,73,0.15); color: #f48771; }
|
||||
.adapter-languages { font-size: 10px; color: var(--vscode-descriptionForeground); margin-bottom: 4px; }
|
||||
.adapter-lang-label { color: var(--vscode-descriptionForeground); }
|
||||
.adapter-guide { font-size: 10px; color: var(--vscode-descriptionForeground); line-height: 1.4; margin-bottom: 7px; }
|
||||
.adapter-actions { display: flex; gap: 5px; }
|
||||
.adapter-btn {
|
||||
padding: 2px 8px; border-radius: 3px; font-size: 10px;
|
||||
@@ -890,10 +857,47 @@ input::placeholder { color: var(--vscode-input-placeholderForeground, var(--vsco
|
||||
background: var(--vscode-sideBar-background, var(--vscode-editor-background));
|
||||
border: 1px solid var(--vscode-panel-border); border-radius: 6px;
|
||||
font-size: 10px; color: var(--vscode-descriptionForeground);
|
||||
position: relative;
|
||||
}
|
||||
.mode-legend-item { display: inline-flex; align-items: center; gap: 4px; }
|
||||
.mode-legend-dot { width: 6px; height: 6px; border-radius: 50%; flex-shrink: 0; }
|
||||
.mode-legend-desc { width: 100%; font-size: 10px; opacity: 0.75; }
|
||||
.mode-legend-desc-row {
|
||||
display: flex; align-items: center; justify-content: space-between;
|
||||
gap: 4px; width: 100%;
|
||||
}
|
||||
.mode-legend-desc { font-size: 10px; opacity: 0.75; }
|
||||
|
||||
/* Help icon & tooltip */
|
||||
.help-icon {
|
||||
position: relative;
|
||||
display: inline-flex; align-items: center; justify-content: center;
|
||||
width: 14px; height: 14px; margin-left: 4px;
|
||||
border-radius: 50%;
|
||||
border: 1px solid var(--vscode-descriptionForeground);
|
||||
color: var(--vscode-descriptionForeground);
|
||||
font-size: 9px; font-weight: 700; line-height: 1;
|
||||
cursor: help; user-select: none; flex-shrink: 0;
|
||||
}
|
||||
.help-icon:hover { color: #8b5cf6; border-color: #8b5cf6; }
|
||||
.help-icon:hover::after {
|
||||
content: attr(data-help);
|
||||
position: absolute;
|
||||
top: calc(100% + 6px); left: 0;
|
||||
z-index: 200;
|
||||
width: max-content; max-width: 280px;
|
||||
background: var(--vscode-editorWidget-background, var(--vscode-editor-background));
|
||||
color: var(--vscode-foreground);
|
||||
border: 1px solid var(--vscode-widget-border, var(--vscode-panel-border));
|
||||
border-radius: 4px;
|
||||
padding: 8px 10px;
|
||||
font-size: 11px; font-weight: 400; line-height: 1.6;
|
||||
white-space: pre-line;
|
||||
box-shadow: 0 2px 8px rgba(0,0,0,0.4);
|
||||
}
|
||||
.mode-legend .help-icon:hover::after {
|
||||
top: calc(100% + 8px); right: 0; left: auto;
|
||||
max-width: 300px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
@@ -1030,7 +1034,10 @@ input::placeholder { color: var(--vscode-input-placeholderForeground, var(--vsco
|
||||
<span class="mode-legend-item"><span class="mode-legend-dot" style="background:#8b5cf6;"></span>${t('setup.adapter.modeBuiltin')}</span>
|
||||
<span class="mode-legend-item"><span class="mode-legend-dot" style="background:#3fb950;"></span>${t('setup.adapter.modeProject')}</span>
|
||||
<span class="mode-legend-item"><span class="mode-legend-dot" style="background:#d29922;"></span>${t('setup.adapter.modeGlobal')}</span>
|
||||
<span class="mode-legend-desc">${t('setup.adapter.modeLegend')}</span>
|
||||
<span class="mode-legend-desc-row">
|
||||
<span class="mode-legend-desc">${t('setup.adapter.modeLegend')}</span>
|
||||
<span class="help-icon" data-help="${t('setup.adapter.modeLegendHelp')}">?</span>
|
||||
</span>
|
||||
</div>
|
||||
<div id="adapter-list"></div>
|
||||
</div>
|
||||
@@ -1176,7 +1183,6 @@ input::placeholder { color: var(--vscode-input-placeholderForeground, var(--vsco
|
||||
dependencyStatus,
|
||||
dependencyLabel: meta.dependencyLabel,
|
||||
configured,
|
||||
guideText: t(`setup.adapter.${meta.i18nKey}Guide`),
|
||||
languages: t(`setup.adapter.${meta.i18nKey}Languages`),
|
||||
projectConfigFileName: meta.projectConfigFileName,
|
||||
settingsTarget: meta.settingsTarget,
|
||||
@@ -1200,7 +1206,8 @@ input::placeholder { color: var(--vscode-input-placeholderForeground, var(--vsco
|
||||
const filePath = path.join(rootPath, meta.projectConfigFileName);
|
||||
|
||||
if (!fs.existsSync(filePath)) {
|
||||
fs.writeFileSync(filePath, meta.configFileTemplate(), 'utf-8');
|
||||
const content = await meta.configFileTemplate();
|
||||
fs.writeFileSync(filePath, content, 'utf-8');
|
||||
vscode.window.showInformationMessage(`配置文件已创建: ${meta.projectConfigFileName}`);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user