Files
2026Technology-Competition/docs/superpowers/specs/2026-07-30-pmd-rule-enhancement-design.md
T
范智鹏 5848eaa82a feat: Linter 规则精细化增强 + 模板导入/导出闭环
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
2026-07-30 23:17:20 +08:00

33 KiB
Raw Blame History

PMD 规则增强设计书

一、背景与目标

1.1 现状

当前 PmdAdaptersrc/adapters/pmd.ts)通过内置 JAR 包调用 PMD 7.26.0,使用 XML ruleset 文件配置规则。Java 规则集(jars/pmd/pmd-java-ruleset.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 条误报率高的噪音规则:如 TooManyMethodsCyclomaticComplexityShortVariable 等阈值类规则,在真实项目中产生大量噪音诊断
  • 未启用 Security 分类Java Security 分类包含 2 条高价值安全规则(HardCodedCryptoKeyInsecureCryptoIv),当前未启用
  • 全分类引用缺乏精细控制:无法排除特定规则,所有规则(含弃用和噪音)均被启用

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 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 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> 阈值,而是直接排除?

部分噪音规则(如 CyclomaticComplexityTooManyMethods)支持通过 <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 分类规则条目:

{"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 条已弃用规则条目(如果存在):

{"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": "..."}

注:AvoidCatchingGenericExceptionstatic-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 字段

"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 依赖。

// 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/jsstylelint-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 条)

弃用规则 替代规则 弃用原因
AvoidCatchingGenericExceptionDesign 版) AvoidCatchingGenericExceptionError 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 字符 ixy 等短名是惯用写法
ShortMethodName 3 字符 getsetaddrun 等是标准写法
ShortClassName 4 字符 URLURIMap 等是标准缩写

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 条规则的优先级分布(估算):

优先级 排除前(含弃用/噪音) 排除后 变化
1Blocker ~40 ~40 无变化(弃用和噪音规则多为 P2-P4)
2Critical ~60 ~50 -10(排除 Design 噪音规则,多为 P2)
3Major ~90 ~80 -10(部分噪音规则为 P3
4Minor ~100 ~90 -10(排除 Short/Long 命名规则等 P4
5Info ~19 ~14 -5Documentation 未启用,弃用规则含部分 P5)
合计 ~309 274 -35

排除的规则主要集中在 P2-P4 级别,这些是误报率最高的级别。P1(Blocker)级规则全部保留,确保高危 bug 检测能力不受影响。

5.4 与 ESLint/Stylelint 级别设计的差异

维度 ESLint Stylelint PMD
级别体系 error / warn / off trueerror/ nulloff priority 1-5
级别配置 逐条规则配置 逐条规则配置 规则默认优先级,可覆盖
适配器映射 直接映射 true → error priority 1-2 → Error3 → Warning4-5 → Info
本设计操作 追加规则配置级别 追加规则配置级别 不修改优先级,仅排除规则

六、兼容性分析

6.1 对现有用户代码的影响

排除规则后,之前被噪音规则标记的诊断将消失,审查结果更加精简:

影响程度 规则 说明
诊断大幅减少 TooManyMethods 等 10 条 Design 噪音规则 大型类不再被标记,减少噪音诊断
诊断大幅减少 ShortVariable 等 4 条 Code Style 噪音规则 短变量名不再被标记
诊断大幅减少 DoNotUseThreads 等 3 条 Multithreading 噪音规则 合理使用线程/volatile/synchronized 不再被标记
诊断少量减少 20 条已弃用规则 替代规则(活跃版本)仍生效,功能等价
诊断少量新增 HardCodedCryptoKeyInsecureCryptoIv 新增 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.jsonnode_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/jsstylelint-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/HardCodedCryptoKeypmd/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:AvoidCatchingGenericExceptionError 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 规则误报 极低 HardCodedCryptoKeyInsecureCryptoIv 检测的是确定性的硬编码模式,误报率极低
用户未更新 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 规则来源参考

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 文件的架构设计。