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
684 lines
33 KiB
Markdown
684 lines
33 KiB
Markdown
# PMD 规则增强设计书
|
||
|
||
## 一、背景与目标
|
||
|
||
### 1.1 现状
|
||
|
||
当前 `PmdAdapter`(`src/adapters/pmd.ts`)通过内置 JAR 包调用 PMD 7.26.0,使用 XML ruleset 文件配置规则。Java 规则集(`jars/pmd/pmd-java-ruleset.xml`)采用**全分类引用**方式启用规则:
|
||
|
||
```xml
|
||
<ruleset name="java-ruleset" ...>
|
||
<description>Java code review rules</description>
|
||
<rule ref="category/java/bestpractices.xml"/>
|
||
<rule ref="category/java/codestyle.xml"/>
|
||
<rule ref="category/java/design.xml"/>
|
||
<rule ref="category/java/errorprone.xml"/>
|
||
<rule ref="category/java/multithreading.xml"/>
|
||
<rule ref="category/java/performance.xml"/>
|
||
</ruleset>
|
||
```
|
||
|
||
这种方式启用了 6 个分类共 309 条规则(活跃 289 + 已弃用 20),存在以下问题:
|
||
|
||
- **启用 20 条已弃用规则**:这些规则已标记为弃用,计划在 PMD 8.0.0 中移除,继续启用增加噪音并为未来迁移增加负担
|
||
- **启用 17 条误报率高的噪音规则**:如 `TooManyMethods`、`CyclomaticComplexity`、`ShortVariable` 等阈值类规则,在真实项目中产生大量噪音诊断
|
||
- **未启用 Security 分类**:Java Security 分类包含 2 条高价值安全规则(`HardCodedCryptoKey`、`InsecureCryptoIv`),当前未启用
|
||
- **全分类引用缺乏精细控制**:无法排除特定规则,所有规则(含弃用和噪音)均被启用
|
||
|
||
JSP 规则集(`jars/pmd/pmd-jsp-ruleset.xml`)已启用全部 5 个分类共 12 条规则,全部活跃,无需调整。
|
||
|
||
### 1.2 目标
|
||
|
||
将 Java 规则集从"全分类引用(309 条)"优化为"精选规则集(274 条活跃规则)":
|
||
|
||
1. **补启 Security 分类**(+2 条):新增 2 条高价值安全规则
|
||
2. **排除 20 条已弃用规则**(-20 条):为 PMD 8.0.0 迁移做准备
|
||
3. **排除 17 条噪音规则**(-17 条):减少误报,提升诊断信噪比
|
||
4. **同步更新 `static-rules.json`**:确保去重检测覆盖完整
|
||
|
||
最终 Java 启用规则数:289(当前活跃)- 17(噪音)+ 2(安全)= **274 条活跃规则**,覆盖 7 个分类。
|
||
|
||
### 1.3 设计原则
|
||
|
||
- **从粗到精**:PMD 的增强方向与 ESLint/Stylelint 相反——不是"从少到多"地添加规则,而是"从粗到精"地从全分类引用中排除弃用和噪音规则。这一特点贯穿整个设计
|
||
- **不破坏现有配置优先级**:全局配置 > 项目配置 > 内置配置,三层择一逻辑不变
|
||
- **不引入新依赖**:PMD 通过内置 JAR 包调用,无需修改 `package.json`
|
||
- **不修改适配器代码**:`PmdAdapter` 读取 ruleset XML 文件,仅需修改 XML 文件本身
|
||
- **同步更新去重数据**:`static-rules.json` 必须同步追加 Security 规则、移除已弃用规则
|
||
- **不修改规则 ID 前缀格式**:诊断结果仍使用 `pmd:{ruleName}`(Java)和 `pmd-jsp:{ruleName}`(JSP)格式
|
||
|
||
---
|
||
|
||
## 二、影响范围分析
|
||
|
||
### 2.1 需要修改的文件
|
||
|
||
| 文件 | 修改类型 | 修改内容 |
|
||
|------|----------|----------|
|
||
| `jars/pmd/pmd-java-ruleset.xml` | 配置修改 | 为 6 个分类添加 `<exclude>` 排除弃用和噪音规则,新增 Security 分类引用 |
|
||
| `src/rules/static-rules.json` | 数据修改 | `rules.pmd` 数组追加 2 条 Security 规则,移除 20 条已弃用规则条目 |
|
||
|
||
### 2.2 不需要修改的文件
|
||
|
||
| 文件 | 原因 |
|
||
|------|------|
|
||
| `package.json` | PMD 通过内置 JAR 包调用,不依赖 npm 包,无需添加依赖 |
|
||
| `src/adapters/pmd.ts` | 适配器读取 ruleset XML 文件路径,不涉及规则配置逻辑,无需修改 |
|
||
| `scripts/build.mjs` | JAR 包和 XML 文件作为静态资源打包,构建脚本无需修改 |
|
||
| `jars/pmd/pmd-jsp-ruleset.xml` | JSP 规则集已完整启用 5 个分类,无需调整 |
|
||
| `src/adapters/jsp.ts` | JSP 适配器复用 PMD JAR,内部读取 JSP ruleset,不受 Java ruleset变更影响 |
|
||
| `src/orchestrator/orchestrator.ts` | 仅做调度,不涉及配置逻辑 |
|
||
| `src/config/linter.ts` | 配置读取层不变 |
|
||
|
||
### 2.3 不受影响的功能
|
||
|
||
- 全局配置(`linters.pmdConfigPath`):用户指定 ruleset 文件时完全替代内置 ruleset,不受影响
|
||
- 项目配置(项目根目录下的 `.xml` ruleset 文件):存在时完全替代内置配置,不受影响
|
||
- JSP 审查功能:JSP 规则集无变化,审查行为不变
|
||
- 规则 ID 输出格式:Java 仍为 `pmd:{ruleName}`,JSP 仍为 `pmd-jsp:{ruleName}`,不变
|
||
- PMD JAR 包版本:仍为 7.26.0,不升级
|
||
|
||
---
|
||
|
||
## 三、详细设计
|
||
|
||
### 3.1 修改 `jars/pmd/pmd-java-ruleset.xml`
|
||
|
||
#### 3.1.1 当前代码
|
||
|
||
```xml
|
||
<?xml version="1.0" encoding="UTF-8"?>
|
||
<ruleset name="java-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>Java code review rules</description>
|
||
<rule ref="category/java/bestpractices.xml"/>
|
||
<rule ref="category/java/codestyle.xml"/>
|
||
<rule ref="category/java/design.xml"/>
|
||
<rule ref="category/java/errorprone.xml"/>
|
||
<rule ref="category/java/multithreading.xml"/>
|
||
<rule ref="category/java/performance.xml"/>
|
||
</ruleset>
|
||
```
|
||
|
||
#### 3.1.2 修改后代码
|
||
|
||
```xml
|
||
<?xml version="1.0" encoding="UTF-8"?>
|
||
<ruleset name="java-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>Java code review rules (274 active rules, 7 categories)</description>
|
||
|
||
<!-- Best Practices:排除 9 条已弃用规则 -->
|
||
<rule ref="category/java/bestpractices.xml">
|
||
<exclude name="DefaultLabelNotLastInSwitchStmt"/>
|
||
<exclude name="JUnit4TestShouldUseAfterAnnotation"/>
|
||
<exclude name="JUnit4TestShouldUseBeforeAnnotation"/>
|
||
<exclude name="JUnit4TestShouldUseTestAnnotation"/>
|
||
<exclude name="JUnit5TestShouldBePackagePrivate"/>
|
||
<exclude name="JUnitAssertionsShouldIncludeMessage"/>
|
||
<exclude name="JUnitTestContainsTooManyAsserts"/>
|
||
<exclude name="JUnitTestsShouldIncludeAssert"/>
|
||
<exclude name="SwitchStmtsShouldHaveDefault"/>
|
||
</rule>
|
||
|
||
<!-- Code Style:排除 2 条已弃用 + 4 条噪音规则 -->
|
||
<rule ref="category/java/codestyle.xml">
|
||
<exclude name="GenericsNaming"/>
|
||
<exclude name="UnnecessaryLocalBeforeReturn"/>
|
||
<exclude name="LongVariable"/>
|
||
<exclude name="ShortVariable"/>
|
||
<exclude name="ShortMethodName"/>
|
||
<exclude name="ShortClassName"/>
|
||
</rule>
|
||
|
||
<!-- Design:排除 2 条已弃用 + 10 条噪音规则 -->
|
||
<rule ref="category/java/design.xml">
|
||
<exclude name="AvoidCatchingGenericException"/>
|
||
<exclude name="UseObjectForClearerAPI"/>
|
||
<exclude name="TooManyMethods"/>
|
||
<exclude name="CyclomaticComplexity"/>
|
||
<exclude name="NPathComplexity"/>
|
||
<exclude name="CognitiveComplexity"/>
|
||
<exclude name="NcssCount"/>
|
||
<exclude name="TooManyFields"/>
|
||
<exclude name="ExcessiveParameterList"/>
|
||
<exclude name="ExcessivePublicCount"/>
|
||
<exclude name="ExcessiveImports"/>
|
||
<exclude name="CouplingBetweenObjects"/>
|
||
</rule>
|
||
|
||
<!-- Error Prone:排除 6 条已弃用规则 -->
|
||
<rule ref="category/java/errorprone.xml">
|
||
<exclude name="AvoidCatchingNPE"/>
|
||
<exclude name="AvoidCatchingThrowable"/>
|
||
<exclude name="AvoidLosingExceptionInformation"/>
|
||
<exclude name="DontImportSun"/>
|
||
<exclude name="NonCaseLabelInSwitchStatement"/>
|
||
<exclude name="UselessOperationOnImmutable"/>
|
||
</rule>
|
||
|
||
<!-- Multithreading:排除 3 条噪音规则 -->
|
||
<rule ref="category/java/multithreading.xml">
|
||
<exclude name="AvoidUsingVolatile"/>
|
||
<exclude name="DoNotUseThreads"/>
|
||
<exclude name="AvoidSynchronizedStatement"/>
|
||
</rule>
|
||
|
||
<!-- Performance:排除 1 条已弃用规则 -->
|
||
<rule ref="category/java/performance.xml">
|
||
<exclude name="TooFewBranchesForASwitchStatement"/>
|
||
</rule>
|
||
|
||
<!-- Security:新增,2 条高价值安全规则 -->
|
||
<rule ref="category/java/security.xml"/>
|
||
</ruleset>
|
||
```
|
||
|
||
#### 3.1.3 设计说明
|
||
|
||
**为什么仍使用分类引用 + exclude,而非逐条引用?**
|
||
|
||
PMD 的分类引用(`<rule ref="category/java/X.xml"/>`)会启用该分类下的全部规则,通过 `<exclude>` 排除特定规则。与 ESLint/Stylelint 逐条列出规则名的方式相比,PMD 的分类引用方式有以下优势:
|
||
|
||
- **维护成本低**:PMD 版本升级新增规则时,分类引用自动包含新规则,无需手动添加。逐条引用则需要每次升级都对比新增规则并手动追加
|
||
- **排除列表更稳定**:需要排除的规则(弃用 + 噪音)远少于需要保留的规则(37 vs 272),维护 exclude 列表比维护 include 列表更简洁
|
||
- **符合 PMD 官方推荐**:PMD 官方文档推荐使用分类引用作为规则集的基础
|
||
|
||
**为什么不为排除规则配置 `<properties>` 阈值,而是直接排除?**
|
||
|
||
部分噪音规则(如 `CyclomaticComplexity`、`TooManyMethods`)支持通过 `<properties>` 调整阈值。但本设计选择直接排除而非调阈值,原因如下:
|
||
|
||
- 阈值类规则的"合理值"因项目而异,无法找到一个通用默认值
|
||
- 即使调高阈值,仍会在边界处产生误报
|
||
- 代码审查工具的目标是发现确定性 bug,而非度量代码复杂度。复杂度度量更适合 SonarQube 等专门的代码质量平台
|
||
- 直接排除减少配置复杂度,用户如有需求可通过自定义 ruleset 单独启用并配置阈值
|
||
|
||
**`AvoidCatchingGenericException` 的跨分类特殊处理**
|
||
|
||
该规则名在 Design 和 Error Prone 两个分类中各存在一条:
|
||
- Design 分类中的为**已弃用**版本(PMD 7 起迁移至 Error Prone 分类)
|
||
- Error Prone 分类中的为**活跃**版本(引入版本 4.2.6)
|
||
|
||
在 Design 分类中排除弃用版本后,Error Prone 分类中的活跃版本仍然生效。两版本功能一致,不会出现规则缺失。
|
||
|
||
### 3.2 修改 `src/rules/static-rules.json`
|
||
|
||
#### 3.2.1 追加 Security 规则
|
||
|
||
在 `rules.pmd` 数组中追加 2 条 Security 分类规则条目:
|
||
|
||
```json
|
||
{"id": "pmd/HardCodedCryptoKey", "description": "Do not use hard coded encryption keys"},
|
||
{"id": "pmd/InsecureCryptoIv", "description": "Do not use hard coded initialization vectors in encryption operations"}
|
||
```
|
||
|
||
#### 3.2.2 移除已弃用规则
|
||
|
||
从 `rules.pmd` 数组中移除以下 20 条已弃用规则条目(如果存在):
|
||
|
||
```json
|
||
{"id": "pmd/DefaultLabelNotLastInSwitchStmt", "description": "..."},
|
||
{"id": "pmd/JUnit4TestShouldUseAfterAnnotation", "description": "..."},
|
||
{"id": "pmd/JUnit4TestShouldUseBeforeAnnotation", "description": "..."},
|
||
{"id": "pmd/JUnit4TestShouldUseTestAnnotation", "description": "..."},
|
||
{"id": "pmd/JUnit5TestShouldBePackagePrivate", "description": "..."},
|
||
{"id": "pmd/JUnitAssertionsShouldIncludeMessage", "description": "..."},
|
||
{"id": "pmd/JUnitTestContainsTooManyAsserts", "description": "..."},
|
||
{"id": "pmd/JUnitTestsShouldIncludeAssert", "description": "..."},
|
||
{"id": "pmd/SwitchStmtsShouldHaveDefault", "description": "..."},
|
||
{"id": "pmd/GenericsNaming", "description": "..."},
|
||
{"id": "pmd/UnnecessaryLocalBeforeReturn", "description": "..."},
|
||
{"id": "pmd/AvoidCatchingGenericException", "description": "..."}, // 仅移除 Design 弃用版本的条目
|
||
{"id": "pmd/UseObjectForClearerAPI", "description": "..."},
|
||
{"id": "pmd/AvoidCatchingNPE", "description": "..."},
|
||
{"id": "pmd/AvoidCatchingThrowable", "description": "..."},
|
||
{"id": "pmd/AvoidLosingExceptionInformation", "description": "..."},
|
||
{"id": "pmd/DontImportSun", "description": "..."},
|
||
{"id": "pmd/NonCaseLabelInSwitchStatement", "description": "..."},
|
||
{"id": "pmd/UselessOperationOnImmutable", "description": "..."},
|
||
{"id": "pmd/TooFewBranchesForASwitchStatement", "description": "..."}
|
||
```
|
||
|
||
> 注:`AvoidCatchingGenericException` 在 `static-rules.json` 中可能仅有一条条目(对应 Error Prone 活跃版本)。如果该条目存在,应保留(对应活跃版本)。仅当存在明确标注为 Design 弃用版本的条目时才移除。实际操作时应检查条目内容确认。
|
||
|
||
#### 3.2.3 噪音规则的处理
|
||
|
||
17 条噪音规则**不从 `static-rules.json` 中移除**。原因:
|
||
|
||
- 噪音规则仍是 PMD 的有效规则,用户可能通过自定义 ruleset 启用并配置阈值
|
||
- `static-rules.json` 的用途是展示 linter 支持的规则清单和去重检测,保留噪音规则条目有助于用户了解这些规则的存在
|
||
- 噪音规则仅在默认 ruleset XML 中排除,不影响 `static-rules.json` 的完整性
|
||
|
||
#### 3.2.4 修改 `linterVersion` 字段
|
||
|
||
```json
|
||
"linterVersion": {
|
||
"pmd": "7.26.0 (274 Java rules + 12 JSP rules)",
|
||
...
|
||
}
|
||
```
|
||
|
||
将原有标记更新为反映实际启用规则数量。
|
||
|
||
#### 3.2.5 为什么必须同步更新 static-rules.json
|
||
|
||
`static-rules.json` 有两个用途:
|
||
|
||
1. **UI 展示**:在设置面板中展示当前 linter 支持的规则清单
|
||
2. **去重检测**:自定义规则导入时,按 ID 匹配 `static-rules.json` 中的规则,若已存在则提示重复
|
||
|
||
如果不更新:
|
||
- 追加 Security 规则:用户新增的 `HardCodedCryptoKey` 自定义规则不会被识别为重复
|
||
- 移除弃用规则:已弃用规则仍出现在规则清单中,误导用户认为这些规则仍被推荐使用
|
||
|
||
### 3.3 不需要修改 `package.json`
|
||
|
||
PMD 通过内置 JAR 包调用,不依赖 npm 包。当前 `package.json` 中与 PMD 相关的配置仅为 JAR 包路径(通过 `jars/pmd/` 目录引用),无需添加任何 npm 依赖。
|
||
|
||
```json
|
||
// package.json 中无 PMD 相关依赖项,PMD 完全由内置 JAR 包提供
|
||
"dependencies": {
|
||
"eslint": "^9.39.3",
|
||
"stylelint": "^17.14.0",
|
||
"typescript-eslint": "^8.56.1",
|
||
"xlsx": "^0.18.5"
|
||
// 无 PMD 相关条目
|
||
}
|
||
```
|
||
|
||
这与 ESLint/Stylelint 适配器需要添加 `@eslint/js` 或 `stylelint-config-recommended` 依赖形成鲜明对比——PMD 的依赖管理完全脱离 npm 体系。
|
||
|
||
---
|
||
|
||
## 四、新增/排除规则分类详解
|
||
|
||
### 4.1 新增:Security 分类(+2 条)
|
||
|
||
当前 Java 规则集未启用 Security 分类。该分类仅含 2 条规则,均为高价值安全规则。
|
||
|
||
| 规则 | 检测场景 | 误报评估 |
|
||
|------|----------|----------|
|
||
| `HardCodedCryptoKey` | `SecretKeySpec("mykey".getBytes(), "AES")` 等硬编码密钥 | 零误报,硬编码密钥几乎一定是安全问题 |
|
||
| `InsecureCryptoIv` | `IvParameterSpec(new byte[16])` 等硬编码/空 IV | 零误报,硬编码 IV 破坏加密语义安全 |
|
||
|
||
> Security 分类无需排除任何规则,直接全分类引用 `<rule ref="category/java/security.xml"/>`。
|
||
|
||
### 4.2 排除:已弃用规则(-20 条)
|
||
|
||
#### 4.2.1 Best Practices 弃用规则(9 条)
|
||
|
||
均为 JUnit 相关规则在 PMD 7 中的重命名,旧名称已弃用,新名称在同类分类中活跃。
|
||
|
||
| 弃用规则 | 替代规则 | 弃用原因 |
|
||
|----------|----------|----------|
|
||
| `DefaultLabelNotLastInSwitchStmt` | `DefaultLabelNotLastInSwitch` | PMD 7.7.0 起支持 switch 表达式,重命名 |
|
||
| `JUnit4TestShouldUseAfterAnnotation` | `UnitTestShouldUseAfterAnnotation` | PMD 7 起泛化为通用测试框架 |
|
||
| `JUnit4TestShouldUseBeforeAnnotation` | `UnitTestShouldUseBeforeAnnotation` | 同上 |
|
||
| `JUnit4TestShouldUseTestAnnotation` | `UnitTestShouldUseTestAnnotation` | 同上 |
|
||
| `JUnit5TestShouldBePackagePrivate` | `JUnitJupiterTestShouldBePackagePrivate` | PMD 7 起重命名 |
|
||
| `JUnitAssertionsShouldIncludeMessage` | `UnitTestAssertionsShouldIncludeMessage` | 同上 |
|
||
| `JUnitTestContainsTooManyAsserts` | `UnitTestContainsTooManyAsserts` | 同上 |
|
||
| `JUnitTestsShouldIncludeAssert` | `UnitTestShouldIncludeAssert` | 同上 |
|
||
| `SwitchStmtsShouldHaveDefault` | `NonExhaustiveSwitch` | PMD 7 起重命名,支持 switch 表达式 |
|
||
|
||
> 排除弃用规则后,替代规则(活跃版本)仍随分类引用自动启用,功能不受影响。
|
||
|
||
#### 4.2.2 Code Style 弃用规则(2 条)
|
||
|
||
| 弃用规则 | 替代规则 | 弃用原因 |
|
||
|----------|----------|----------|
|
||
| `GenericsNaming` | `TypeParameterNamingConventions` | PMD 7.17.0 起用更通用的命名约定规则替代 |
|
||
| `UnnecessaryLocalBeforeReturn` | 无 | 规则价值低,PMD 7.17.0 起移除 |
|
||
|
||
#### 4.2.3 Design 弃用规则(2 条)
|
||
|
||
| 弃用规则 | 替代规则 | 弃用原因 |
|
||
|----------|----------|----------|
|
||
| `AvoidCatchingGenericException`(Design 版) | `AvoidCatchingGenericException`(Error Prone 版,活跃) | 迁移至 Error Prone 分类 |
|
||
| `UseObjectForClearerAPI` | 无 | PMD 7.26.0 起弃用,规则建议过于主观 |
|
||
|
||
#### 4.2.4 Error Prone 弃用规则(6 条)
|
||
|
||
| 弃用规则 | 替代规则 | 弃用原因 |
|
||
|----------|----------|----------|
|
||
| `AvoidCatchingNPE` | `AvoidCatchingGenericException` | PMD 7.18.0 起合并至通用规则 |
|
||
| `AvoidCatchingThrowable` | `AvoidCatchingGenericException` | 同上 |
|
||
| `AvoidLosingExceptionInformation` | `UselessPureMethodCall` | PMD 7.17.0 起用更通用的规则替代 |
|
||
| `DontImportSun` | `UnsupportedJdkApiUsage` | PMD 7.21.0 起用更通用的规则替代 |
|
||
| `NonCaseLabelInSwitchStatement` | `NonCaseLabelInSwitch` | PMD 7.7.0 起重命名 |
|
||
| `UselessOperationOnImmutable` | `UselessPureMethodCall` | PMD 7.17.0 起用更通用的规则替代 |
|
||
|
||
#### 4.2.5 Performance 弃用规则(1 条)
|
||
|
||
| 弃用规则 | 替代规则 | 弃用原因 |
|
||
|----------|----------|----------|
|
||
| `TooFewBranchesForASwitchStatement` | `TooFewBranchesForSwitch` | PMD 7.7.0 起重命名 |
|
||
|
||
### 4.3 排除:噪音规则(-17 条)
|
||
|
||
#### 4.3.1 Design 噪音规则(10 条)
|
||
|
||
这些规则均为阈值类复杂度/规模度量规则,误报率高,不适合作为代码审查工具的默认规则。
|
||
|
||
| 规则 | 默认阈值 | 排除理由 |
|
||
|------|----------|----------|
|
||
| `TooManyMethods` | 10 | 工具类、控制器类天然方法多,阈值过于机械 |
|
||
| `CyclomaticComplexity` | 10 | 复杂业务逻辑合理需要高复杂度 |
|
||
| `NPathComplexity` | 200 | 与圈复杂度类似,机械阈值弊大于利 |
|
||
| `CognitiveComplexity` | 15 | 适合作为度量指标而非 lint 规则 |
|
||
| `NcssCount` | 100 | 方法/类长度限制因场景而异 |
|
||
| `TooManyFields` | 15 | DTO/Entity 类天然字段多 |
|
||
| `ExcessiveParameterList` | 10 | 构建器模式、配置类合理需要多参数 |
|
||
| `ExcessivePublicCount` | 45 | API 类天然公共成员多 |
|
||
| `ExcessiveImports` | 30 | 整合层/门面类合理需要多导入 |
|
||
| `CouplingBetweenObjects` | 20 | 耦合度难以精确度量,更适合架构审查工具 |
|
||
|
||
#### 4.3.2 Code Style 噪音规则(4 条)
|
||
|
||
这些规则基于名称长度阈值,过于主观,在真实项目中误报率高。
|
||
|
||
| 规则 | 默认阈值 | 排除理由 |
|
||
|------|----------|----------|
|
||
| `LongVariable` | 30 字符 | 描述性变量名通常更长更清晰 |
|
||
| `ShortVariable` | 3 字符 | `i`、`x`、`y` 等短名是惯用写法 |
|
||
| `ShortMethodName` | 3 字符 | `get`、`set`、`add`、`run` 等是标准写法 |
|
||
| `ShortClassName` | 4 字符 | `URL`、`URI`、`Map` 等是标准缩写 |
|
||
|
||
#### 4.3.3 Multithreading 噪音规则(3 条)
|
||
|
||
这些规则过于激进,在通用 Java 项目中误报率高。
|
||
|
||
| 规则 | 排除理由 |
|
||
|------|----------|
|
||
| `AvoidUsingVolatile` | volatile 在双重检查锁等场景有合理用途 |
|
||
| `DoNotUseThreads` | 面向 J2EE webapp 容器场景,不适用于通用项目 |
|
||
| `AvoidSynchronizedStatement` | synchronized 在简单并发场景有合理用途 |
|
||
|
||
### 4.4 不启用:Documentation 分类(6 条)
|
||
|
||
Documentation 分类共 6 条规则,不适合作为代码审查工具的默认规则。详见推荐规则文档中的"不推荐启用"部分。如团队有强制 Javadoc 需求,可通过自定义 ruleset 单独启用 `DanglingJavadoc` 等规则。
|
||
|
||
---
|
||
|
||
## 五、规则级别设计
|
||
|
||
### 5.1 PMD priority 体系
|
||
|
||
PMD 使用 5 级优先级体系(priority 1-5),每条规则有默认优先级,在 ruleset XML 中可通过 `<priority>` 元素覆盖:
|
||
|
||
| 优先级 | 名称 | 含义 | 适配器映射(建议) |
|
||
|--------|------|------|-------------------|
|
||
| 1 | Blocker | 高概率的代码错误,运行时必崩 | Error |
|
||
| 2 | Critical | 设计问题、严重的最佳实践违反 | Error |
|
||
| 3 | Major | 开发实践问题、潜在 bug | Warning |
|
||
| 4 | Minor | 代码风格、命名约定 | Info / Hint |
|
||
| 5 | Info | 文档、注释相关 | Info / Hint |
|
||
|
||
### 5.2 当前优先级策略
|
||
|
||
本设计**不覆盖任何规则的默认优先级**。原因:
|
||
|
||
- PMD 为每条规则设置的默认优先级已经过社区验证,与规则的严重程度匹配
|
||
- 分类引用方式自动继承规则的默认优先级,无需逐条配置
|
||
- 适配器(`pmd.ts`)在解析 PMD 输出时,根据 priority 值映射为 VS Code 诊断严重级别
|
||
|
||
### 5.3 排除规则对优先级分布的影响
|
||
|
||
排除 37 条规则后,剩余 274 条规则的优先级分布(估算):
|
||
|
||
| 优先级 | 排除前(含弃用/噪音) | 排除后 | 变化 |
|
||
|--------|---------------------|--------|------|
|
||
| 1(Blocker) | ~40 | ~40 | 无变化(弃用和噪音规则多为 P2-P4) |
|
||
| 2(Critical) | ~60 | ~50 | -10(排除 Design 噪音规则,多为 P2) |
|
||
| 3(Major) | ~90 | ~80 | -10(部分噪音规则为 P3) |
|
||
| 4(Minor) | ~100 | ~90 | -10(排除 Short/Long 命名规则等 P4) |
|
||
| 5(Info) | ~19 | ~14 | -5(Documentation 未启用,弃用规则含部分 P5) |
|
||
| **合计** | **~309** | **274** | **-35** |
|
||
|
||
> 排除的规则主要集中在 P2-P4 级别,这些是误报率最高的级别。P1(Blocker)级规则全部保留,确保高危 bug 检测能力不受影响。
|
||
|
||
### 5.4 与 ESLint/Stylelint 级别设计的差异
|
||
|
||
| 维度 | ESLint | Stylelint | PMD |
|
||
|------|--------|-----------|-----|
|
||
| 级别体系 | `error` / `warn` / `off` | `true`(error)/ `null`(off) | priority 1-5 |
|
||
| 级别配置 | 逐条规则配置 | 逐条规则配置 | 规则默认优先级,可覆盖 |
|
||
| 适配器映射 | 直接映射 | true → error | priority 1-2 → Error,3 → Warning,4-5 → Info |
|
||
| 本设计操作 | 追加规则配置级别 | 追加规则配置级别 | 不修改优先级,仅排除规则 |
|
||
|
||
---
|
||
|
||
## 六、兼容性分析
|
||
|
||
### 6.1 对现有用户代码的影响
|
||
|
||
排除规则后,之前被噪音规则标记的诊断将消失,审查结果更加精简:
|
||
|
||
| 影响程度 | 规则 | 说明 |
|
||
|----------|------|------|
|
||
| **诊断大幅减少** | `TooManyMethods` 等 10 条 Design 噪音规则 | 大型类不再被标记,减少噪音诊断 |
|
||
| **诊断大幅减少** | `ShortVariable` 等 4 条 Code Style 噪音规则 | 短变量名不再被标记 |
|
||
| **诊断大幅减少** | `DoNotUseThreads` 等 3 条 Multithreading 噪音规则 | 合理使用线程/volatile/synchronized 不再被标记 |
|
||
| **诊断少量减少** | 20 条已弃用规则 | 替代规则(活跃版本)仍生效,功能等价 |
|
||
| **诊断少量新增** | `HardCodedCryptoKey`、`InsecureCryptoIv` | 新增 Security 规则,仅检测硬编码密钥/IV,误报率极低 |
|
||
|
||
### 6.2 对 JSP 审查的影响
|
||
|
||
JSP 规则集无变化,JSP 审查行为完全不受影响。
|
||
|
||
### 6.3 对去重功能的影响
|
||
|
||
`static-rules.json` 同步更新后:
|
||
- 追加 2 条 Security 规则:去重检测范围扩展,用户新增的 `HardCodedCryptoKey` 自定义规则将被正确识别为重复
|
||
- 移除 20 条已弃用规则:已弃用规则不再出现在规则清单中,避免误导用户
|
||
|
||
### 6.4 对 PMD 8.0.0 迁移的影响
|
||
|
||
排除 20 条已弃用规则后,未来升级到 PMD 8.0.0(计划移除弃用规则)时:
|
||
- ruleset XML 中的 `<exclude>` 引用的弃用规则名将不存在,PMD 会输出警告但不影响其他规则
|
||
- 迁移时只需移除这些无效的 `<exclude>` 条目即可
|
||
- 提前排除弃用规则可减少迁移时的规则数量变化,降低用户感知
|
||
|
||
### 6.5 对构建的影响
|
||
|
||
- ruleset XML 文件作为静态资源打包,修改不影响构建流程
|
||
- JAR 包版本不变,无需更新 JAR 文件
|
||
- `static-rules.json` 修改不影响构建产物体积(JSON 数据文件)
|
||
- 无新增 npm 依赖,`package.json` 和 `node_modules` 不变
|
||
|
||
---
|
||
|
||
## 七、与 ESLint/Stylelint/ts-eslint 适配器的架构对比
|
||
|
||
### 7.1 增强方向对比
|
||
|
||
| 维度 | ESLint / Stylelint / ts-eslint | PMD |
|
||
|------|-------------------------------|-----|
|
||
| **增强方向** | 从少到多(从 recommended 扩展到更多规则) | 从粗到精(从全分类引用中排除弃用和噪音) |
|
||
| **起点** | 官方 recommended 配置(61/41/24 条) | 全分类引用(309 条) |
|
||
| **终点** | recommended + 额外规则(92/68/~40 条) | 精选规则集(274 条) |
|
||
| **规则数变化** | 增加(+31/+27/~16) | 减少(-35,含排除弃用和噪音,+2 安全) |
|
||
| **核心操作** | 追加规则到配置对象 | 从分类引用中排除规则 |
|
||
|
||
> 这是 PMD 适配器与其他 linter 适配器最本质的差异。其他 linter 从"太少"走向"刚好",PMD 从"太多"走向"刚好"。
|
||
|
||
### 7.2 配置方式对比
|
||
|
||
| 维度 | ESLint | Stylelint | PMD |
|
||
|------|--------|-----------|-----|
|
||
| 配置格式 | JS 对象(Flat Config) | JS 对象 | XML ruleset |
|
||
| 规则引用 | 逐条规则名 + 级别 | 逐条规则名 + 级别 | 分类引用 + exclude |
|
||
| 排除方式 | 不列入规则对象 | 不列入规则对象 | `<exclude name="..."/>` |
|
||
| 新增方式 | 追加规则到对象 | 追加规则到对象 | 追加 `<rule ref="category/..."/>` |
|
||
| 官方配置包 | `@eslint/js` recommended | `stylelint-config-recommended` | 无(分类文件随 JAR 提供) |
|
||
| 合并机制 | Flat Config 数组后者覆盖 | 对象展开 | XML 引用叠加 |
|
||
|
||
### 7.3 依赖管理对比
|
||
|
||
| 维度 | ESLint / Stylelint | PMD |
|
||
|------|-------------------|-----|
|
||
| 依赖来源 | npm 包 | 内置 JAR 包 |
|
||
| 版本管理 | `package.json` + `package-lock.json` | JAR 文件版本(`jars/pmd/lib/`) |
|
||
| 配置包 | `@eslint/js`、`stylelint-config-recommended` | 无(分类 XML 随 JAR 内置) |
|
||
| 升级方式 | `npm update` | 替换 JAR 文件 |
|
||
| `package.json` 修改 | 需要添加依赖 | **不需要** |
|
||
|
||
### 7.4 适配器代码修改对比
|
||
|
||
| 适配器 | 是否修改代码 | 修改内容 |
|
||
|--------|------------|----------|
|
||
| ESLint | 是 | `getDefaultConfig()` 追加 `extraRules` 对象 |
|
||
| Stylelint | 是 | `DEFAULT_CONFIG` 替换为 recommended + extraRules |
|
||
| ts-eslint | 是 | `getDefaultConfig()` 追加 ts-eslint 额外规则 |
|
||
| **PMD** | **否** | **仅修改 XML 文件和 JSON 数据,适配器代码不变** |
|
||
|
||
> PMD 适配器的设计将规则配置完全外部化到 XML 文件,使得规则增强无需修改任何 TypeScript 代码。这是 PMD 适配器的架构优势。
|
||
|
||
---
|
||
|
||
## 八、实施步骤
|
||
|
||
### 步骤 1:修改 `jars/pmd/pmd-java-ruleset.xml`
|
||
|
||
1. 为 `bestpractices.xml` 添加 9 条 `<exclude>`(弃用规则)
|
||
2. 为 `codestyle.xml` 添加 6 条 `<exclude>`(2 弃用 + 4 噪音)
|
||
3. 为 `design.xml` 添加 12 条 `<exclude>`(2 弃用 + 10 噪音)
|
||
4. 为 `errorprone.xml` 添加 6 条 `<exclude>`(弃用规则)
|
||
5. 为 `multithreading.xml` 添加 3 条 `<exclude>`(噪音规则)
|
||
6. 为 `performance.xml` 添加 1 条 `<exclude>`(弃用规则)
|
||
7. 在文件末尾追加 `<rule ref="category/java/security.xml"/>`
|
||
8. 更新 `<description>` 内容
|
||
|
||
### 步骤 2:修改 `src/rules/static-rules.json`
|
||
|
||
1. 在 `rules.pmd` 数组中追加 2 条 Security 规则条目
|
||
2. 从 `rules.pmd` 数组中移除 20 条已弃用规则条目(逐条检查是否存在)
|
||
3. 更新 `linterVersion.pmd` 为 `"7.26.0 (274 Java rules + 12 JSP rules)"`
|
||
|
||
### 步骤 3:验证
|
||
|
||
1. 执行 `npm run lint` 确认项目自身代码无新增报错
|
||
2. 执行 `npm run compile` 确认编译通过
|
||
3. 执行 `npm run build` 确认打包成功
|
||
4. 检查 `static-rules.json` 中 PMD Java 规则数量变化(+2 Security,-20 弃用)
|
||
5. 用测试 Java 文件验证:
|
||
- 硬编码密钥触发 `pmd:HardCodedCryptoKey`
|
||
- 硬编码 IV 触发 `pmd:InsecureCryptoIv`
|
||
- 短变量名不再触发 `pmd:ShortVariable`
|
||
- 方法过多不再触发 `pmd:TooManyMethods`
|
||
|
||
---
|
||
|
||
## 九、测试要点
|
||
|
||
### 9.1 单元测试
|
||
|
||
| 测试项 | 验证内容 |
|
||
|--------|----------|
|
||
| ruleset XML 格式校验 | XML 格式合法,所有 `<exclude>` 的 `name` 属性值正确 |
|
||
| Security 规则启用 | ruleset 中包含 `<rule ref="category/java/security.xml"/>` |
|
||
| 弃用规则排除 | 20 条弃用规则均在对应分类的 `<exclude>` 列表中 |
|
||
| 噪音规则排除 | 17 条噪音规则均在对应分类的 `<exclude>` 列表中 |
|
||
| `static-rules.json` Security 规则 | 包含 `pmd/HardCodedCryptoKey` 和 `pmd/InsecureCryptoIv` |
|
||
| `static-rules.json` 弃用规则移除 | 不包含 `pmd/JUnit4TestShouldUseTestAnnotation` 等已移除的弃用规则 |
|
||
|
||
### 9.2 集成测试
|
||
|
||
| 测试项 | 验证内容 |
|
||
|--------|----------|
|
||
| 硬编码密钥触发 Security 规则 | 诊断结果包含 `pmd:HardCodedCryptoKey` |
|
||
| 硬编码 IV 触发 Security 规则 | 诊断结果包含 `pmd:InsecureCryptoIv` |
|
||
| 短变量名不触发 `ShortVariable` | `int i = 0;` 不产生 `pmd:ShortVariable` 诊断 |
|
||
| 方法过多不触发 `TooManyMethods` | 含 15 个方法的类不产生 `pmd:TooManyMethods` 诊断 |
|
||
| 弃用规则不触发 | 代码不产生 `pmd:JUnit4TestShouldUseTestAnnotation` 等弃用规则诊断 |
|
||
| 替代规则仍生效 | `switch` 缺少 `default` 触发 `pmd:NonExhaustiveSwitch`(替代 `SwitchStmtsShouldHaveDefault`) |
|
||
| `AvoidCatchingGenericException` 仍生效 | `catch (Exception e)` 触发 `pmd:AvoidCatchingGenericException`(Error Prone 活跃版本) |
|
||
| 全局配置存在时忽略内置配置 | 使用用户 ruleset,不触发新规则 |
|
||
| JSP 审查不受影响 | JSP 文件审查行为与修改前一致 |
|
||
|
||
### 9.3 去重测试
|
||
|
||
| 测试项 | 验证内容 |
|
||
|--------|----------|
|
||
| 导入 `HardCodedCryptoKey` 自定义规则 | 提示与已有规则重复 |
|
||
| 导入 `InsecureCryptoIv` 自定义规则 | 提示与已有规则重复 |
|
||
| 导入已移除的弃用规则(如 `JUnit4TestShouldUseTestAnnotation`) | 不提示重复(已从 static-rules.json 移除) |
|
||
|
||
---
|
||
|
||
## 十、风险评估
|
||
|
||
### 10.1 主要风险
|
||
|
||
| 风险 | 级别 | 缓解措施 |
|
||
|------|------|----------|
|
||
| 用户依赖被排除的噪音规则 | 低 | 噪音规则仍在 `static-rules.json` 中,用户可通过自定义 ruleset 启用并配置阈值 |
|
||
| 用户依赖被排除的弃用规则 | 低 | 替代规则(活跃版本)自动生效,功能等价。如确需旧名称,可通过自定义 ruleset 引用 |
|
||
| PMD 8.0.0 移除弃用规则后 `<exclude>` 失效 | 低 | PMD 8.0.0 升级时移除无效的 `<exclude>` 条目即可,不影响其他规则 |
|
||
| Security 规则误报 | 极低 | `HardCodedCryptoKey` 和 `InsecureCryptoIv` 检测的是确定性的硬编码模式,误报率极低 |
|
||
| 用户未更新 `static-rules.json` 导致去重失效 | 低 | 实施步骤中明确要求同步更新,且有测试用例覆盖 |
|
||
|
||
### 10.2 回滚方案
|
||
|
||
如果增强后导致严重问题,回滚步骤:
|
||
|
||
1. 将 `jars/pmd/pmd-java-ruleset.xml` 恢复为修改前的全分类引用版本(移除所有 `<exclude>` 和 Security 引用)
|
||
2. 将 `src/rules/static-rules.json` 恢复为修改前的状态(移除 Security 规则,恢复弃用规则)
|
||
3. 恢复 `linterVersion.pmd` 为原值
|
||
|
||
回滚仅需还原 2 个文件,不涉及代码修改和依赖变更,操作简单安全。
|
||
|
||
---
|
||
|
||
## 十一、附录
|
||
|
||
### 11.1 规则来源参考
|
||
|
||
- PMD 7.26.0 官方规则文档:https://docs.pmd-code.org/pmd-doc-7.26.0/pmd_rules_java.html
|
||
- 推荐规则分析文件:`/workspace/pmd-recommended-rules.md`
|
||
- 完整规则清单:`/workspace/pmd-7-rules.md`
|
||
- 适配器配置分析:`/workspace/custom-rule-analysis.md`
|
||
|
||
### 11.2 规则数量统计
|
||
|
||
| 分类 | 数量 | 操作 | 来源 |
|
||
|------|------|------|------|
|
||
| P0 当前已启用(保留) | 272 | 保留 | 6 个分类的活跃规则,排除噪音后 |
|
||
| P1 新增推荐 | 2 | 新增 | Security 分类 |
|
||
| P2 建议排除(弃用) | 20 | 排除 | 5 个分类的已弃用规则 |
|
||
| P2 建议排除(噪音) | 17 | 排除 | 3 个分类的误报率高的阈值规则 |
|
||
| 不推荐 | 6 | 不启用 | Documentation 分类 |
|
||
| **推荐启用合计** | **274** | — | P0 + P1 |
|
||
|
||
### 11.3 排除规则分类汇总
|
||
|
||
| 分类 | 弃用排除 | 噪音排除 | 合计排除 | 保留 |
|
||
|------|----------|----------|----------|------|
|
||
| Best Practices | 9 | 0 | 9 | 60 |
|
||
| Code Style | 2 | 4 | 6 | 57 |
|
||
| Design | 2 | 10 | 12 | 30 |
|
||
| Error Prone | 6 | 0 | 6 | 92 |
|
||
| Multithreading | 0 | 3 | 3 | 9 |
|
||
| Performance | 1 | 0 | 1 | 24 |
|
||
| Security | 0 | 0 | 0 | 2(新增) |
|
||
| **合计** | **20** | **17** | **37** | **274** |
|
||
|
||
### 11.4 JSP 规则集(无变化)
|
||
|
||
JSP 规则集当前已完整启用 5 个分类共 12 条规则,全部活跃,无弃用规则,无需任何修改。
|
||
|
||
### 11.5 与其他适配器增强方案对比
|
||
|
||
| 适配器 | 增强方向 | 修改文件数 | 代码修改 | 依赖修改 | 最终规则数 |
|
||
|--------|----------|-----------|----------|----------|-----------|
|
||
| ESLint | 从少到多 | 3 | 是(`eslint.ts`) | 是(`@eslint/js`) | 92 |
|
||
| Stylelint | 从少到多 | 3 | 是(`stylelint.ts`) | 是(`stylelint-config-recommended`) | 68 |
|
||
| ts-eslint | 从少到多 | 2 | 是(`eslint.ts`) | 否 | ~40 |
|
||
| **PMD** | **从粗到精** | **2** | **否** | **否** | **274** |
|
||
|
||
> PMD 适配器的增强方案修改文件最少(仅 XML 和 JSON),无需修改 TypeScript 代码和 npm 依赖,实施成本最低。这得益于 PMD 将规则配置完全外部化到 XML ruleset 文件的架构设计。
|