ESLint: +29 条 P1/P2 规则 + 12 条 TS 专属规则(含 no-shadow/no-array-constructor 冲突处理) Stylelint: 集成 stylelint-config-recommended + 27 条额外规则 PMD: 排除 20 弃用 + 17 噪音规则,补启 Security/Multithreading,274+12 条精选 SQL-lint: 内置精选 57 条规则配置 + 按 tier 分级 severity + 无项目配置时自动注入临时配置 模板导出: export-service.ts 导出 2-sheet xlsx(复用 xlsx 零新依赖) 模板导入: template-converter.ts 固定列映射解析 + dedup-prompt.ts AI 语义去重 导入预览增强: 错误规则分组置顶只读、跳过行提示、空数据提示 i18n: 新增 18 条模板导入/导出相关翻译 WebView: 静态分析/自定义规则项默认可展开显示 suggestion
20 KiB
ESLint 规则增强设计书
一、背景与目标
1.1 现状
当前 ESLintAdapter(src/adapters/eslint.ts)内置默认配置为:
public static getDefaultConfig(): any[] {
if (!ESLintAdapter.defaultConfig) {
ESLintAdapter.defaultConfig = [
js.configs.recommended, // @eslint/js recommended → 61 条
...ts.configs.recommended, // typescript-eslint recommended → 24 条
];
}
return ESLintAdapter.defaultConfig;
}
仅启用了官方 recommended 的 61 条 ESLint 核心规则,全部属于 "Possible Problems" 类(语法错误、死代码等)。ESLint 9.x 活跃规则共 199 条,当前覆盖率仅 31%。
1.2 目标
将内置配置从 61 条扩展到 94 条(P0 官方推荐 61 + P1 强烈推荐 17 + P2 建议启用 16),使静态分析能捕获更多高频真实 bug,同时保持低误报率。
1.3 设计原则
- 不破坏现有配置优先级:全局配置 > 项目配置 > 内置配置,三层择一逻辑不变
- 不引入新依赖:P1/P2 规则全部来自
eslint包内置规则,无需额外安装插件 - 同步更新去重数据:
static-rules.json必须同步追加新增规则,保证自定义规则导入时的去重检测覆盖完整 - 不修改规则 ID 前缀格式:诊断结果仍使用
eslint:{ruleId}格式
二、影响范围分析
2.1 需要修改的文件
| 文件 | 修改类型 | 修改内容 |
|---|---|---|
src/adapters/eslint.ts |
代码修改 | getDefaultConfig() 方法增加 P1+P2 规则配置 |
src/rules/static-rules.json |
数据修改 | rules.eslint 数组追加 33 条规则条目 |
package.json |
依赖修改 | dependencies 中补充 @eslint/js 显式声明 |
2.2 不需要修改的文件
| 文件 | 原因 |
|---|---|
scripts/build.mjs |
@eslint/js 不在 external 列表中,已被 esbuild 正确打包 |
src/adapters/jsp.ts |
内部复用 ESLintAdapter 实例,自动继承新配置 |
src/orchestrator/orchestrator.ts |
仅做调度,不涉及配置逻辑 |
src/config/linter.ts |
配置读取层不变 |
eslint.config.mjs |
项目自身 lint 配置,与运行时适配器配置无关 |
2.3 不受影响的功能
- 全局配置(
linters.eslintConfigPath):用户指定配置文件时完全替代内置配置,不受影响 - 项目配置(
.eslintrc.*/eslint.config.*):存在时完全替代内置配置,不受影响 - JSP 适配器:复用
ESLintAdapter,自动获得增强后的配置 - 规则 ID 输出格式:仍为
eslint:{ruleId},不变
三、详细设计
3.1 修改 src/adapters/eslint.ts
3.1.1 当前代码
import js from '@eslint/js';
import ts from 'typescript-eslint';
// ... 中间部分不变 ...
public static getDefaultConfig(): any[] {
if (!ESLintAdapter.defaultConfig) {
ESLintAdapter.defaultConfig = [
js.configs.recommended,
...ts.configs.recommended,
];
}
return ESLintAdapter.defaultConfig;
}
3.1.2 修改后代码
import js from '@eslint/js';
import ts from 'typescript-eslint';
// 新增:P1+P2 额外规则配置常量
const extraRules: Record<string, 'error' | 'warn'> = {
// === P1:强烈推荐(error)===
'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-constant-binary-expression': 'error',
'no-eval': 'error',
'no-implied-eval': 'error',
'no-extend-native': 'error',
// === P2:建议启用 ===
'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',
};
// ... 中间部分不变 ...
public static getDefaultConfig(): any[] {
if (!ESLintAdapter.defaultConfig) {
ESLintAdapter.defaultConfig = [
js.configs.recommended,
...ts.configs.recommended,
// 新增:P1+P2 额外规则
{ rules: extraRules },
];
}
return ESLintAdapter.defaultConfig;
}
3.1.3 设计说明
为什么用 extraRules 常量而不是内联对象?
- 可读性:33 条规则单独成文件级常量,与
getDefaultConfig()逻辑分离 - 可维护性:未来增删规则只需修改
extraRules对象,不需要动方法逻辑 - 可测试性:常量可以被测试文件直接导入验证
为什么放在 recommended 之后?
ESLint Flat Config 的规则是后者覆盖前者。extraRules 放在最后,如果其中某些规则已在 recommended 中启用(如 no-implied-eval 在 recommended 中已有),extraRules 的配置会覆盖其级别。但实际上 P1/P2 选型时已排除了与 recommended 重复的规则,不存在冲突。
为什么不用 @eslint/js 的 configs.all?
js.configs.all 启用全部 199 条活跃规则,包含大量不应在代码审查工具中强制启用的规则(如 no-magic-numbers、max-lines 等)。精确选择 33 条更合理。
3.2 修改 src/rules/static-rules.json
3.2.1 修改内容
在 rules.eslint 数组末尾追加以下 33 条规则条目(格式与现有 61 条一致):
{"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-constant-binary-expression", "description": "Disallow expressions where the operation doesn't affect the value"},
{"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"}
注:
no-implied-eval已在 recommended 中启用,不在追加列表中。追加的 33 条均为 recommended 之外的新增规则。
3.2.2 修改 linterVersion 字段
"linterVersion": {
"eslint": "9.x (94 rules)",
...
}
将 "9.x (recommended)" 改为 "9.x (94 rules)",反映实际启用规则数量。
3.2.3 为什么必须同步更新 static-rules.json
static-rules.json 有两个用途:
- UI 展示:在设置面板中展示当前 linter 支持的规则清单
- 去重检测:自定义规则导入时,按 ID 匹配
static-rules.json中的规则,若已存在则提示重复
如果不更新,用户新增的 eqeqeq 自定义规则不会被识别为重复(因为 static-rules.json 中没有这条),导致重复规则。
3.3 修改 package.json
3.3.1 当前代码
"dependencies": {
"eslint": "^9.39.3",
"stylelint": "^17.14.0",
"typescript-eslint": "^8.56.1",
"xlsx": "^0.18.5"
}
3.3.2 修改后代码
"dependencies": {
"@eslint/js": "^9.39.3",
"eslint": "^9.39.3",
"stylelint": "^17.14.0",
"typescript-eslint": "^8.56.1",
"xlsx": "^0.18.5"
}
3.3.3 修改理由
eslint.ts 中 import js from '@eslint/js' 已被使用,但 @eslint/js 未在 package.json 中显式声明,仅作为 eslint 包的传递依赖存在。这属于版本迭代遗留问题(见 _AI_USAGE_LOG.md 第82条记录)。显式声明可消除依赖不确定性。
四、新增规则分类详解
4.1 P1 强烈推荐(17 条,error 级别)
这些规则检测高频真实 bug,误报率极低。
4.1.1 等值与比较(3 条)
| 规则 | 检测场景 | 误报评估 |
|---|---|---|
eqeqeq |
== 隐式类型转换 |
极低,几乎所有现代项目都启用 |
no-eq-null |
x == null 同时匹配 null 和 undefined |
低,通常只需判断其一 |
no-self-compare |
a === a 恒为 true |
零误报,几乎一定是笔误 |
4.1.2 异步与 Promise(2 条)
| 规则 | 检测场景 | 误报评估 |
|---|---|---|
no-await-in-loop |
循环中串行 await 导致性能问题 | 中,设为 warn,允许逐条忽略 |
no-promise-executor-return |
executor 返回值被忽略 | 零误报 |
4.1.3 作用域与变量(4 条)
| 规则 | 检测场景 | 误报评估 |
|---|---|---|
no-shadow |
内层变量遮蔽外层变量 | 低,遮蔽几乎都是 bug |
no-unassigned-vars |
读取未赋值的 let/var | 零误报 |
no-useless-assignment |
赋值后值未被使用 | 低 |
block-scoped-var |
var 变量在声明作用域外使用 | 低 |
4.1.4 控制流与逻辑(5 条)
| 规则 | 检测场景 | 误报评估 |
|---|---|---|
default-case |
switch 缺少 default 分支 | 低 |
default-case-last |
default 不在最后 | 零误报 |
no-unmodified-loop-condition |
循环条件未修改导致死循环 | 零误报 |
no-unreachable-loop |
循环体只允许一次迭代 | 低 |
no-constant-binary-expression |
操作不影响结果值 | 低 |
4.1.5 安全(3 条)
| 规则 | 检测场景 | 误报评估 |
|---|---|---|
no-eval |
eval() 执行任意代码 | 零误报 |
no-implied-eval |
setTimeout("code") 类似 eval | 零误报(已在 recommended 中) |
no-extend-native |
修改 Array.prototype 等 | 零误报 |
4.2 P2 建议启用(16 条)
4.2.1 现代 JS 语法(6 条)
| 规则 | 级别 | 检测场景 |
|---|---|---|
no-var |
error | 使用 var 代替 let/const |
prefer-template |
warn | 字符串拼接代替模板字面量 |
prefer-object-spread |
warn | Object.assign 代替对象展开 |
prefer-rest-params |
warn | arguments 代替 rest 参数 |
prefer-spread |
warn | .apply() 代替展开运算符 |
prefer-object-has-own |
warn | 旧式 hasOwnProperty 代替 Object.hasOwn |
4.2.2 代码简洁性(4 条)
| 规则 | 级别 | 检测场景 |
|---|---|---|
no-useless-concat |
warn | 不必要的字符串拼接 |
no-useless-return |
warn | 多余的 return 语句 |
no-useless-computed-key |
warn | 不必要的计算属性键 |
no-useless-rename |
warn | 重命名为相同名称 |
4.2.3 防御性编程(3 条)
| 规则 | 级别 | 检测场景 |
|---|---|---|
no-param-reassign |
warn | 修改函数参数(副作用风险) |
no-return-assign |
error | return 中赋值(笔误风险) |
no-throw-literal |
error | 抛出字面量而非 Error 对象 |
4.2.4 命名与风格(3 条)
| 规则 | 级别 | 检测场景 |
|---|---|---|
camelcase |
warn | 非驼峰命名 |
new-cap |
warn | 构造函数未大写 |
no-array-constructor |
error | 使用 Array 构造函数 |
五、规则级别设计
5.1 级别分配原则
| 级别 | 适用场景 | 数量 |
|---|---|---|
error |
确定性 bug、安全问题、语法错误 | 74 条 |
warn |
代码质量建议、风格偏好(可逐条忽略) | 20 条 |
5.2 为什么部分规则设为 warn 而非 error
以下规则设为 warn 是因为它们有合理的例外场景:
| 规则 | warn 理由 |
|---|---|
no-await-in-loop |
某些场景确实需要串行 await(如分页请求) |
prefer-template |
超长字符串拼接时 + 可能更清晰 |
prefer-rest-params |
兼容旧环境时可能需要 arguments |
no-param-reassign |
Redux reducer 等模式需要修改参数 |
camelcase |
对接外部 API 时可能需要 snake_case |
其余规则设为 error 是因为它们的触发几乎一定意味着 bug 或安全问题。
六、兼容性分析
6.1 对现有用户代码的影响
启用新规则后,之前能通过审查的代码可能会新增诊断:
| 影响程度 | 规则 | 说明 |
|---|---|---|
| 可能大量新增诊断 | eqeqeq |
大量旧代码使用 ==,但这是必要的改进 |
| 可能大量新增诊断 | no-shadow |
嵌套作用域中常见,需逐个审查 |
| 中等新增诊断 | no-param-reassign |
函数中修改参数较常见 |
| 少量新增诊断 | default-case |
大多数 switch 已有 default |
| 少量新增诊断 | no-var |
现代 TS 项目已普遍使用 let/const |
| 极少新增诊断 | no-eval / no-extend-native |
正常项目几乎不会用 |
6.2 对 JSP 适配器的影响
JSP 适配器(src/adapters/jsp.ts)内部持有 ESLintAdapter 实例,提取 <script> 块后复用其 check() 方法。新增规则自动生效,无需额外修改。
6.3 对去重功能的影响
static-rules.json 同步更新后,去重检测范围从 61 条扩展到 94 条。用户导入的自定义规则如果与这 33 条新规则 ID 匹配,将正确识别为重复。
6.4 对构建的影响
@eslint/js已被 esbuild 正确打包(不在 external 列表中)- 新增的 33 条规则全部来自
eslint包内置,无需安装额外 npm 包 - 构建产物体积影响可忽略(规则配置仅为一个 JS 对象)
七、实施步骤
步骤 1:修改 package.json
在 dependencies 中添加 "@eslint/js": "^9.39.3"。
步骤 2:修改 src/adapters/eslint.ts
- 在文件顶部(
import之后、PROJECT_CONFIG_FILES之前)添加extraRules常量 - 在
getDefaultConfig()方法的返回数组末尾追加{ rules: extraRules }
步骤 3:修改 src/rules/static-rules.json
- 在
rules.eslint数组末尾追加 33 条规则条目 - 更新
linterVersion.eslint为"9.x (94 rules)"
步骤 4:验证
- 执行
npm run lint确认项目自身代码无新增报错 - 执行
npm run compile确认编译通过 - 执行
npm run build确认打包成功 - 检查
static-rules.json中 eslint 规则数量为 94 条
八、测试要点
8.1 单元测试
| 测试项 | 验证内容 |
|---|---|
getDefaultConfig() 返回值 |
数组长度为 3(recommended + ts recommended + extraRules) |
extraRules 规则数 |
恰好 33 条 |
extraRules 级别 |
17 条 error + 16 条 warn |
| 规则 ID 无重复 | 与 recommended 61 条无交集 |
8.2 集成测试
| 测试项 | 验证内容 |
|---|---|
== 触发 eqeqeq |
诊断结果包含 eslint:eqeqeq |
var x = 1 触发 no-var |
诊断结果包含 eslint:no-var |
eval("code") 触发 no-eval |
诊断结果包含 eslint:no-eval |
switch 无 default 触发 default-case |
诊断结果包含 eslint:default-case |
| 全局配置存在时忽略内置配置 | 使用用户配置,不触发新规则 |
| 项目配置存在时忽略内置配置 | 使用项目配置,不触发新规则 |
8.3 去重测试
| 测试项 | 验证内容 |
|---|---|
导入 eqeqeq 自定义规则 |
提示与已有规则重复 |
导入 no-eval 自定义规则 |
提示与已有规则重复 |
| 导入 recommended 中已有的规则 | 仍提示重复(未改变原有行为) |
九、风险评估
9.1 主要风险
| 风险 | 级别 | 缓解措施 |
|---|---|---|
| 用户代码新增大量诊断 | 中 | P2 规则设为 warn,用户可逐条忽略 |
no-shadow 误报较多 |
低 | 该规则在大型项目中较常见,但遮蔽几乎都是 bug |
camelcase 对接外部 API 报错 |
低 | 用户可通过项目配置覆盖该规则 |
@eslint/js 版本不匹配 |
低 | 锁定与 eslint 相同的 ^9.39.3 |
9.2 回滚方案
如果新规则导致严重问题,回滚步骤:
- 从
getDefaultConfig()中移除{ rules: extraRules } - 从
static-rules.json中移除追加的 33 条规则 - 恢复
linterVersion.eslint为"9.x (recommended)"
package.json 中 @eslint/js 的声明可以保留(本就是应该声明的依赖)。
十、附录
10.1 规则来源参考
- ESLint 官方规则文档:https://eslint.org/docs/latest/rules
- 推荐规则分析文件:
/workspace/eslint-recommended-rules.md - 完整规则清单:
/workspace/eslint-9-rules.md
10.2 规则数量统计
| 分类 | 数量 | 级别 |
|---|---|---|
| P0 官方 recommended | 61 | error(由 recommended 定义) |
| P1 强烈推荐 | 17 | error |
| P2 建议启用 | 16 | 13 warn + 3 error |
| 合计 | 94 | 74 error + 20 warn |
| 未启用(冻结/弃用/格式化/过度限制) | 218 | — |