feat: demo-pmd 样本工程入库 + Windows spawn cmd EINVAL 修复 + 实测报告清理

- data/demo-pmd 全量入库(26 源文件 + pom/mvnw/lib/reports),.gitignore 排除 target/
- 归档 demo-pmd 实测覆盖率报告(279/279 全覆盖)至 reports/
- 删除 4 份覆盖率报告中的后续建议/实测进度总览(全部实测已收官)
- 修复 Windows spawn .cmd/.bat EINVAL(auxClasspath.ts 经 cmd.exe /d /s /c 间接执行)
This commit is contained in:
范智鹏
2026-08-26 22:04:47 +08:00
parent e9762bfbe1
commit 3c390cfa90
47 changed files with 4363 additions and 23 deletions
+1
View File
@@ -6,3 +6,4 @@ dist/
.env
*.log
.superpowers/
data/demo-pmd/target/
+1
View File
@@ -228,3 +228,4 @@
| 2026-08-25 21:54 | ① 用户提出 → ② 需求澄清 → ③ 方案设计 → ④ 人类审批 → ⑤ 编码实现 → ⑥ 审查验证 | 从插件内置 PMD 配置移除 5 条实际不可触发的规则(AvoidAssertAsIdentifier/AvoidEnumAsIdentifier 语言版本上限 1.3/1.4、AccessorClassGeneration/AccessorMethodGeneration 上限 Java 10、LoosePackageCoupling 需显式 packages 配置),使内置配置=全部可触发,demo-pmd 覆盖率基线对齐 269+12=281 可达成 100%。改动:jars/pmd/pmd-java-ruleset.xml 三个分类 exclude 各追加(bestpractices 2 + design 1 + errorprone 2)并将 description 计数 274→269src/rules/static-rules.json linterVersion.pmd 改 269、移除 5 条规则条目(pmd 295→290);scripts/translations/pmd-1.mjs/pmd-2.mjs 同步删除 5 条翻译种子项。验证:PMD 实际跑内置 ruleset 无加载异常 / lint 0 error / compile 通过 / npm test 117 passing | 中间产物:static-rules.json 第一处编辑误保留 AccessorClassGeneration 块(只删了 AccessorMethodGeneration),复查 JSON 计数发现后补删 | jars/pmd/pmd-java-ruleset.xml src/rules/static-rules.json scripts/translations/pmd-1.mjs scripts/translations/pmd-2.mjs | deepseek-v4-flash |
| 2026-08-26 19:05 | ① 用户提出 → ② 需求澄清 → ③ 方案设计 → ④ 人类审批 → ⑤ 编码实现 → ⑥ 审查验证 | 仓库目录对齐赛道二 §8 成果物规范:分支 vscode-code-reviewer 合并入 main(冲突文件 docs/superpowers/specs/2026-07-10-code-reviewer-design.md 取开发分支完整版,add/add 冲突已解决);src/test 迁移至根目录 tests/16 个测试 + fixtures/manual),36 处 import '../' → '../src/';新建 tsconfig.test.jsonrootDir "."include tests + src/types 声明文件);.vscode-test.mjs files 改 'out/tests/**/*.test.js'package.json 新增 compile:test、test 改为 compile:test && vscode-test.gitignore 移除 src/test/3 个中文文件名 demo-*插件实测覆盖率报告.md 重命名为 demo-*-coverage-report.mdgit rm --cached 解除 vscode-code-reviewer-1.0.0.vsix 跟踪;README 追加演示视频(进行中)章节。验证:lint 0 error / compile 通过 / compile:test 通过 / npm test 116 passing | 中间产物:tsconfig.test.json 初版 include 仅 tests/**/*,编译报 stylelint-config-recommended 声明缺失(主 tsconfig include src/**/* 含 src/types 声明文件而 test 配置未含),补 include src/types/**/* 后通过 | tests/ tests/fixtures/ tests/manual/ tsconfig.test.json(新建) .vscode-test.mjs package.json .gitignore README.md data/demo-eslint/reports/demo-eslint-coverage-report.md data/demo-sqlfluff/reports/demo-sqlfluff-coverage-report.md data/demo-stylelint/reports/demo-stylelint-coverage-report.md | deepseek-v4-flash |
| 2026-08-26 19:16 | ① 用户提出 → ② 需求澄清 → ③ 方案设计 → ④ 人类审批 → ⑤ 编码实现 → ⑥ 审查验证 | 补齐 tests/ 实验报告(§8 成果物 04,coverage/ 按用户确认忽略):新建 tests/test-execution-log.md(测试执行日志:命令/环境/结果 116 passing 0 failing);tests/test-cases.md(从 16 个测试文件提取 suite/test 声明,116 条用例清单+覆盖点+统计);tests/measure/measure-review-time.mjs(提效测量脚本:内联复刻 src/rules/builtin-rules.ts 的 ESLint/Stylelint 内置配置,用 eslint/stylelint 引擎直跑 data/demo-*/src 样例计时,输出 tests/measure/results/measure-results.json);tests/measure/performance-comparison.md(提效对比骨架:测量方法+对比维度+插件侧实测数据已填 demo-eslint 6 文件 294 诊断 192.2ms / demo-stylelint 2 文件 115 诊断 2106.7ms / 合计 409 诊断 2298.9ms,人工基线留待实测)。验证:measure 脚本实测运行产出 JSON / lint 0 error(仅既有 mockDocument 2 warning/ 主流程 npm test 116 passing 不受影响 | 中间产物:measure 脚本初版 import '../../src/rules/builtin-rules'ESM 无法直接加载 TS 报 ERR_MODULE_NOT_FOUND)→ 改 import 编译产物 '../../out/rules/builtin-rules.js' 又因模块顶层 import vscode 脱离扩展宿主报 MODULE_NOT_FOUND → 最终改为内联规则配置(与 data/demo-eslint/run-coverage.mjs 既有模式一致);measure() 计时函数初版未 await 异步 fnresult 为 Promise 导致 result.reduce 报错,改为 async/await 后通过 | tests/test-execution-log.md(新建) tests/test-cases.md(新建) tests/measure/measure-review-time.mjs(新建) tests/measure/performance-comparison.md(新建) tests/measure/results/measure-results.json(新建) | deepseek-v4-flash |
| 2026-08-26 21:29 | ① 用户提出 → ② 需求澄清 → ③ 方案设计 → ④ 人类审批 → ⑤ 编码实现 → ⑥ 审查验证 | 修复 Windows 下 spawn 拉起 .cmd/.bat 必然 EINVAL 问题(方案 A):auxClasspath.ts exec() 对 Windows 批处理命令(mvnw.cmd/gradlew.bat/mvn.cmd/gradle.bat)改走 spawn('cmd.exe', ['/d','/s','/c', 双层引号包装命令]) + windowsVerbatimArguments:true,非批处理分支不变;新增私有 helper quoteCmdArg(安全字符正则含 \ 及 Windows 路径符,含空格/引号参数用 cmd 双引号转义 "→"")。修复 Maven/Gradle 自动 aux classpath 在 Windows 完全失效问题。验证:实测 npm.cmd 与含空格路径+含空格参数 .cmd 冒烟通过(exit 0/ lint 0 error(仅既有 mockDocument 2 warning/ compile 通过 / npm test 116 passing | 中间产物:①cmd /s /c 单层引号方案失败——命令首字符为引号时 cmd 剥首尾引号并移除最后一个引号字符,导致路径后残留引号拼接成非法命令(实测 'F:\nodejs\node.exe" -e "console.log' 无法识别),改为外层再包一层引号(双层引号)后通过;②quoteCmdArg safe 正则初版漏 \ 反斜杠,考虑 -Dmdep.outputFile=C:\... 参数补入;③PowerShell 双引号 here-string 内联 node 测试脚本引号被解析破坏报 SyntaxError,改用单引号 here-string 后通过;④是否加 windowsVerbatimArguments 决策点——不加则 Node 二次转义拼好的命令行,实测确认必须加 | src/services/auxClasspath.ts | deepseek-v4-flash |
@@ -109,11 +109,6 @@
2. ✅ 内置配置全覆盖达成(126/126 种规则,零解析错误)
3. ✅ 「内置配置回退」路径完整验证(工程无配置文件的场景)
**后续建议**(如需继续扩展验证):
- **其他 3 个工程复测**`demo-sqlfluff``demo-pmd``demo-stylelint` 按同样方法(先本地模拟基线 → 实测比对),其中 PMD 注意 Java 版本与 ruleset、SQLFluff 注意 dialect 与 AL06 样例、Stylelint 注意配置优先级。
- **「工程配置优先」路径补验**(可选):若后续想验证插件发现工程配置的行为,可在工程根添加 `eslint.config.mjs` 并以工程根为第一个工作区文件夹打开,预期插件改用工程配置(诊断会有差异,属正常)。
---
*报告生成于 2026-08-25 · demo-eslint 插件实测 · 比对脚本:`round2-compare.mjs` · 数据:`round2-verify.json`*
Binary file not shown.
+2
View File
@@ -0,0 +1,2 @@
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.9/apache-maven-3.9.9-bin.zip
wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.3.2/maven-wrapper-3.3.2.jar
+8
View File
@@ -0,0 +1,8 @@
{
"java.project.referencedLibraries": [
"lib/**/*.jar"
],
"java.project.sourcePaths": [
"src"
]
}
+1
View File
@@ -0,0 +1 @@
C:\Users\Fan\.m2\repository\junit\junit\4.13.2\junit-4.13.2.jar;C:\Users\Fan\.m2\repository\org\hamcrest\hamcrest-core\1.3\hamcrest-core-1.3.jar;C:\Users\Fan\.m2\repository\org\junit\jupiter\junit-jupiter-api\5.10.2\junit-jupiter-api-5.10.2.jar;C:\Users\Fan\.m2\repository\org\opentest4j\opentest4j\1.3.0\opentest4j-1.3.0.jar;C:\Users\Fan\.m2\repository\org\junit\platform\junit-platform-commons\1.10.2\junit-platform-commons-1.10.2.jar;C:\Users\Fan\.m2\repository\org\apiguardian\apiguardian-api\1.1.2\apiguardian-api-1.1.2.jar;C:\Users\Fan\.m2\repository\org\slf4j\slf4j-api\2.0.16\slf4j-api-2.0.16.jar;C:\Users\Fan\.m2\repository\commons-logging\commons-logging\1.3.4\commons-logging-1.3.4.jar
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+138
View File
@@ -0,0 +1,138 @@
#!/bin/sh
# ----------------------------------------------------------------------------
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
# ----------------------------------------------------------------------------
# ----------------------------------------------------------------------------
# Apache Maven Wrapper startup script, version 3.3.2
# ----------------------------------------------------------------------------
# Required ENV vars:
# JAVA_HOME - location of a JDK home dir
#
# Optional ENV vars:
# MAVEN_OPTS - parameters passed to the Java VM, e.g. -Xmx512m
# MAVEN_SKIP_RC - flag to disable loading of mavenrc files
if [ -z "$MAVEN_SKIP_RC" ] ; then
if [ -f /usr/local/etc/mavenrc ] ; then
. /usr/local/etc/mavenrc
fi
if [ -f /etc/mavenrc ] ; then
. /etc/mavenrc
fi
if [ -f "$HOME/.mavenrc" ] ; then
. "$HOME/.mavenrc"
fi
fi
# OS specific support.
cygwin=false;
darwin=false;
mingw=false
case "$(uname)" in
CYGWIN*) cygwin=true ;;
MINGW*) mingw=true;;
Darwin*) darwin=true
if [ -z "$JAVA_VERSION" ] ; then
JAVA_VERSION="MajorVersion"
fi
;;
esac
[ -n "$JAVA_HOME" ] && export PATH="$JAVA_HOME/bin:$PATH"
if [ -z "$JAVA_HOME" ] ; then
if $darwin ; then
if [ -z "$JAVA_VERSION" ] ; then
JAVA_VERSION="MajorVersion"
fi
JAVA_HOME=$(/usr/libexec/java_home -v "$JAVA_VERSION")
if [ $? -ne 0 ] ; then
echo "Unable to find Java version: $JAVA_VERSION" >&2
exit 1
fi
else
JAVA_HOME=$(which java 2>/dev/null)
if [ -z "$JAVA_HOME" ] ; then
echo "Error: JAVA_HOME not found in your environment." >&2
echo "Please set the JAVA_HOME variable in your environment to match the" >&2
echo "location of your Java installation." >&2
exit 1
fi
JAVA_HOME=$(dirname "$(dirname "$JAVA_HOME")")
fi
fi
# For Cygwin, ensure paths are in UNIX format
if $cygwin ; then
[ -n "$JAVA_HOME" ] && JAVA_HOME=$(cygpath --unix "$JAVA_HOME")
[ -n "$CLASSPATH" ] && CLASSPATH=$(cygpath --path --unix "$CLASSPATH")
fi
# For Mingw, ensure paths are in UNIX format
if $mingw ; then
[ -n "$JAVA_HOME" ] && JAVA_HOME="$(cd "$JAVA_HOME" && pwd)"
fi
# shellcheck disable=SC2034
MAVEN_PROJECTBASEDIR="${MAVEN_BASEDIR:-$(cd "$(dirname "$0")" && pwd)}"
MAVEN_CMD_LINE_ARGS="$@"
# shellcheck disable=SC2086
MAVEN_OPTS="${MAVEN_OPTS:--Xmx512m} $([ -f "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config" ] && cat "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config")"
# Provide a "standardized" way to retrieve the CLI args that will
# work with both Windows and non-Windows executions.
MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@"
WRAPPER_JAR="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar"
WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
if [ -f "$WRAPPER_JAR" ]; then
if [ "$MVNW_VERBOSE" = true ]; then
echo "Found $WRAPPER_JAR"
fi
else
if [ "$MVNW_VERBOSE" = true ]; then
echo "Couldn't find $WRAPPER_JAR, downloading it ..."
fi
WRAPPER_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.3.2/maven-wrapper-3.3.2.jar"
if [ -n "$MVNW_REPOURL" ]; then
WRAPPER_URL="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.3.2/maven-wrapper-3.3.2.jar"
fi
if command -v curl > /dev/null 2>&1; then
curl -o "$WRAPPER_JAR" "$WRAPPER_URL"
elif command -v wget > /dev/null 2>&1; then
wget -O "$WRAPPER_JAR" "$WRAPPER_URL"
else
echo "Error: Unable to download $WRAPPER_URL" >&2
exit 1
fi
fi
# shellcheck disable=SC2086
exec "$JAVA_HOME/bin/java" \
$MAVEN_OPTS \
-classpath "$WRAPPER_JAR" \
"-Dmaven.multiModuleProjectDirectory=$MAVEN_PROJECTBASEDIR" \
$WRAPPER_LAUNCHER $MAVEN_CMD_LINE_ARGS
+193
View File
@@ -0,0 +1,193 @@
@REM ----------------------------------------------------------------------------
@REM Licensed to the Apache Software Foundation (ASF) under one
@REM or more contributor license agreements. See the NOTICE file
@REM distributed with this work for additional information
@REM regarding copyright ownership. The ASF licenses this file
@REM to you under the Apache License, Version 2.0 (the
@REM "License"); you may not use this file except in compliance
@REM with the License. You may obtain a copy of the License at
@REM
@REM http://www.apache.org/licenses/LICENSE-2.0
@REM
@REM Unless required by applicable law or agreed to in writing,
@REM software distributed under the License is distributed on an
@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
@REM KIND, either express or implied. See the License for the
@REM specific language governing permissions and limitations
@REM under the License.
@REM ----------------------------------------------------------------------------
@REM ----------------------------------------------------------------------------
@REM Apache Maven Wrapper startup batch script, version 3.3.2
@REM
@REM Required ENV vars:
@REM JAVA_HOME - location of a JDK home dir
@REM
@REM Optional ENV vars
@REM MAVEN_OPTS - parameters passed to the Java VM, e.g. -Xmx512m
@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files
@REM ----------------------------------------------------------------------------
@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on'
@echo off
@REM set title of command window
title %0
@REM enable echo by setting MAVEN_BATCH_ECHO to 'on'
@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO%
@REM set %HOME% to equivalent of $HOME
if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%")
@REM Execute a user defined script before this one
if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre
@REM check for pre script, once with legacy .bat ending and once with .cmd ending
if exist "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %*
if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\mavenrc_pre.cmd" %*
:skipRcPre
@setlocal
set ERROR_CODE=0
@REM To isolate internal variables from possible post scripts, we use another setlocal
@setlocal
@REM ==== START VALIDATION ====
if not "%JAVA_HOME%" == "" goto OkJHome
echo.
echo Error: JAVA_HOME not found in your environment. >&2
echo Please set the JAVA_HOME variable in your environment to match the >&2
echo location of your Java installation. >&2
echo.
goto error
:OkJHome
if exist "%JAVA_HOME%\bin\java.exe" goto init
echo.
echo Error: JAVA_HOME is set to an invalid directory. >&2
echo JAVA_HOME = "%JAVA_HOME%" >&2
echo Please set the JAVA_HOME variable in your environment to match the >&2
echo location of your Java installation. >&2
echo.
goto error
@REM ==== END VALIDATION ====
:init
set MAVEN_CMD_LINE_ARGS=%*
@REM Find the project base dir
set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR%
if not "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir
set EXEC_DIR=%CD%
set WDIR=%EXEC_DIR%
:findBaseDir
IF EXIST "%WDIR%\.mvn" goto baseDirFound
cd ..
IF "%WDIR%"=="%CD%" goto baseDirNotFound
set WDIR=%CD%
goto findBaseDir
:baseDirFound
set MAVEN_PROJECTBASEDIR=%WDIR%
cd "%EXEC_DIR%"
goto endDetectBaseDir
:baseDirNotFound
set MAVEN_PROJECTBASEDIR=%EXEC_DIR%
:endDetectBaseDir
IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig
@setlocal EnableExtensions EnableDelayedExpansion
for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a
@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS%
:endReadAdditionalConfig
SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe"
set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar"
set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
set WRAPPER_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.3.2/maven-wrapper-3.3.2.jar"
FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO (
IF "%%A"=="wrapperUrl" SET WRAPPER_URL=%%B
)
@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
@REM This allows using the maven wrapper in projects that prohibit checking in binary data.
if exist %WRAPPER_JAR% (
if "%MVNW_VERBOSE%" == "true" (
echo Found %WRAPPER_JAR%
)
) else (
if not "%MVNW_REPOURL%" == "" (
SET WRAPPER_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.3.2/maven-wrapper-3.3.2.jar"
)
if "%MVNW_VERBOSE%" == "true" (
echo Couldn't find %WRAPPER_JAR%, downloading it ...
echo Downloading from: %WRAPPER_URL%
)
powershell -Command "&{"^
"$ErrorActionPreference = 'Stop';"^
"if (!(Test-Path -Path '%WRAPPER_JAR%')) {"^
"New-Item -Path '%WRAPPER_JAR%' -Force | Out-Null;"^
"}"^
"if (!(Test-Path -Path '%MAVEN_PROJECTBASEDIR%\.mvn\wrapper')) {"^
"New-Item -Path '%MAVEN_PROJECTBASEDIR%\.mvn\wrapper' -ItemType Directory -Force | Out-Null;"^
"}"^
"$webclient = New-Object System.Net.WebClient;"^
"try {"^
"$webclient.DownloadFile('%WRAPPER_URL%', '%WRAPPER_JAR%')"^
"}"^
"catch {"^
"if (!$?) {"^
"Write-Output ""Unable to download %WRAPPER_URL%"";"^
"exit 1;"^
"}"^
"}"^
"}"
if "%MVNW_VERBOSE%" == "true" (
echo Finished downloading %WRAPPER_JAR%
)
)
@REM Provide a "standardized" way to retrieve the CLI args that will
@REM work with both Windows and non-Windows executions.
set MAVEN_CMD_LINE_ARGS=%*
%MAVEN_JAVA_EXE% ^
%JVM_CONFIG_MAVEN_PROPS% ^
%MAVEN_OPTS% ^
%MAVEN_DEBUG_OPTS% ^
-classpath %WRAPPER_JAR% ^
"-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" ^
%WRAPPER_LAUNCHER% %MAVEN_CONFIG% %*
if ERRORLEVEL 1 goto error
goto end
:error
set ERROR_CODE=1
:end
@endlocal & set ERROR_CODE=%ERROR_CODE%
if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost
@REM check for post script, once with legacy .bat ending and once with .cmd ending
if exist "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat"
if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\mavenrc_post.cmd"
:skipRcPost
@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on'
if "%MAVEN_BATCH_PAUSE%" == "on" pause
if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE%
exit /B %ERROR_CODE%
+56
View File
@@ -0,0 +1,56 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.demo</groupId>
<artifactId>demo-pmd</artifactId>
<version>1.0.0</version>
<packaging>jar</packaging>
<name>PMD Demo</name>
<description>PMD 规则演示项目,包含 JUnit 3/4/5 及 SLF4J 依赖</description>
<properties>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<build>
<!-- 源码目录是 src/ 而非默认的 src/main/java -->
<sourceDirectory>src</sourceDirectory>
</build>
<dependencies>
<!-- JUnit 4 (包含 JUnit 3 的 TestCase) -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
</dependency>
<!-- JUnit 5 (Jupiter) -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>5.10.2</version>
</dependency>
<!-- SLF4J -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>2.0.16</version>
</dependency>
<!-- Commons Logging -->
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.3.4</version>
</dependency>
</dependencies>
</project>
@@ -0,0 +1,174 @@
# demo-pmd 插件实测覆盖率报告
> **工程**`demo-pmd`PMD 内置规则集形态:`jars/pmd/pmd-java-ruleset.xml` + `pmd-jsp-ruleset.xml`
> **测试对象**:插件 PMD 适配器(引擎 PMD 7.26.0Java 269 条 / JSP 10 条内置规则)
> **实测日期**2026-08-26(当日完成含辅助 classpath 的全量复测收官)
> **比对基线**:插件同版本引擎 + 同规则集独立复现的基线(比对键 = 规则 + 行号;JSP 另含三层架构逐层复现)
---
## 一、核心结论
| 指标 | 结果 | 状态 |
|---|---|---|
| 插件行为一致性(vs 基线) | **1813/1813 条逐条一致,零偏差** | ✅ 100% |
| Java 规则覆盖率 | **269/269 种(100%** | ✅ 7/7 类满覆盖 |
| JSP 规则覆盖率 | **10/10 种(100%** | ✅ 全覆盖 |
| 规则总覆盖率 | **279/279 种(100%** | ✅ 全覆盖 |
| 解析错误 | **0 处** | ✅ |
| 静态诊断总数 | 1819 条(PMD 1813 + ESLint 6 | — |
**实测完全达成全覆盖基准**:演示工程全部 26 个文件(23 Java + 3 JSP)的插件报告,共 1813 条 PMD 静态诊断与独立基线**规则名、行号逐条完全一致**,零误报、零漏报、零解析错误;Java 269 条与 JSP 10 条内置规则**全部触发**,两类规则集均达 100% 覆盖。**PMD 适配器静态分析链路(规则加载、Java/JSP 双语言路由、scriptlet 包装、诊断映射、辅助 classpath 自动解析)验证通过。**
---
## 二、逐文件比对(实测 vs 基线)
**Java 23 份**(基线 = PMD 7.26.0 + `pmd-java-ruleset.xml`;★ 为启用辅助 classpath 后复测的文件):
| 文件 | 实测 | 基线 | 一致性 | 说明 |
|---|---:|---:|---|---|
| `DesignErrorProne.java` | 762 | 762 | ✅ 逐条一致 | 单文件最大(design + errorprone 主样例) |
| `BestPracticesCodeStyle.java` | 517 | 517 | ✅ 逐条一致 | bestpractices + codestyle 主样例 |
| `MultiPerfSecurity.java` | 190 | 190 | ✅ 逐条一致 | multithreading + performance + security 样例 |
| `GodClassDemo.java` | 79 | 79 | ✅ 逐条一致 | design 类规则样例 |
| `JUnit4RulesTest.java` ★ | 67 | 67 | ✅ 逐条一致 | JUnit4 相关规则样例(classpath 后 +16 |
| `AccessorAndIpDemo.java` | 14 | 14 | ✅ 逐条一致 | 访问器 + 硬编码 IP 样例 |
| `JUnit3RulesTest.java` | 15 | 15 | ✅ 逐条一致 | JUnit3 相关规则样例 |
| `AccessorDemo.java` | 12 | 12 | ✅ 逐条一致 | 访问器生成样例 |
| `FileItemDemo.java` | 12 | 12 | ✅ 逐条一致 | commons-fileupload 样例 |
| `AssertStmtTest.java` | 10 | 10 | ✅ 逐条一致 | 断言语句样例 |
| `LoggerDemo.java` ★ | 11 | 11 | ✅ 逐条一致 | 日志调用样例(classpath 后 +1 |
| `AccessorGen2.java` | 11 | 11 | ✅ 逐条一致 | 内部类访问器样例 |
| `StaticEJBFieldDemo.java` | 9 | 9 | ✅ 逐条一致 | EJB 静态字段样例 |
| `JUnit5RulesTest.java` | 9 | 9 | ✅ 逐条一致 | JUnit5 相关规则样例 |
| `JUnit5ArgOrderTest.java` ★ | 9 | 9 | ✅ 逐条一致 | 断言参数顺序样例(classpath 后 +2 |
| `EJBNamingDemo.java` | 8 | 8 | ✅ 逐条一致 | EJB 命名样例 |
| `AfterBeforeTest.java` | 7 | 7 | ✅ 逐条一致 | setUp/tearDown 样例 |
| `CallSuperDemo.java` | 7 | 7 | ✅ 逐条一致 | CallSuper 样例 |
| `InvalidBeanDemo.java` | 7 | 7 | ✅ 逐条一致 | JavaBean 规范样例 |
| `ApiService.java` | 2 | 2 | ✅ 逐条一致 | 接口设计样例 |
| `NoPackageDemo.java` | 4 | 4 | ✅ 逐条一致 | 无包声明样例 |
| `PackageCaseDemo.java` | 4 | 4 | ✅ 逐条一致 | 包命名规范样例 |
| `LoosePackageCouplingDemo.java` | 3 | 3 | ✅ 逐条一致 | 包耦合度样例 |
| **Java 合计** | **1769** | **1769** | **✅ 零偏差** | — |
> ★ 三份复测说明:`JUnit4RulesTest` / `JUnit5ArgOrderTest` / `LoggerDemo` 首轮以无 classpath 模式测得 51 / 7 / 10 条(与 noaux 基线逐条一致);辅助 classpath 生效后复测为 67 / 9 / 11 条(与 aux 基线逐条一致),新增的 19 条全部来自 4 个类型解析规则(见第三节)。两轮均零偏差。
**JSP 3 份**(基线 = 三层架构逐层复现,见第四节):
| 文件 | PMD 实测 | PMD 基线 | ESLint | 一致性 |
|---|---:|---:|---:|---|
| `JspFix.jsp` | 35 | 35 | 3 | ✅ 逐条一致 |
| `DemoJsp.jsp` | 9 | 9 | 3 | ✅ 逐条一致 |
| `SecurityDemo.jsp` | 0 | 0 | 0 | ✅ 一致(无 scriptlet/script,三层均无输出) |
| **JSP 合计** | **44** | **44** | **6** | **✅ 零偏差** |
> 比对方法:将插件报告中的每条静态诊断(规则名 + 行号)与基线做多重集合比对;26 个文件全部 `missing = 0``extra = 0`
---
## 三、覆盖率分布与最后 4 条规则的达成
**Java 规则集按类别**(269 条,7 类,全部满覆盖):
| 类别 | 总数 | 已覆盖 | 覆盖率 |
|---|---:|---:|---:|
| Error Prone | 90 | 90 | 100% |
| Best Practices | 58 | 58 | 100% |
| Code Style | 57 | 57 | 100% |
| Design | 29 | 29 | 100% |
| Performance | 24 | 24 | 100% |
| Multithreading | 9 | 9 | 100% |
| Security | 2 | 2 | 100% |
| **合计** | **269** | **269** | **100%** |
**JSP 规则集**bestpractices / codestyle / design / errorprone 四类共 10 条全部触发(`JspFix` 贡献 6 种、`DemoJsp` 贡献 6 种,并集 10/10)。
**最后 4 条规则的两阶段达成过程**:
| 规则 | 类别 | 触发依赖 | 达成方式 |
|---|---|---|---|
| `UnitTestAssertionsShouldIncludeMessage` | Best Practices | JUnit `assertEquals` 类型解析 | classpath 后 `JUnit4RulesTest` +15、`JUnit5ArgOrderTest` +1 |
| `SimplifiableTestAssertion` | Best Practices | JUnit 断言 API 类型解析 | classpath 后 `JUnit4RulesTest` +1 |
| `AssertEqualsArgumentOrder` | Error Prone | JUnit `assertEquals` 签名解析 | classpath 后 `JUnit5ArgOrderTest` +1 |
| `UseCorrectExceptionLogging` | Error Prone | commons-logging `Logger` 类型解析 | classpath 后 `LoggerDemo` +1 |
该 4 条规则依赖**辅助 classpath 的类型解析**,触发条件是工程根有 `pom.xml`/`build.gradle` 且插件成功解析依赖。演示工程 `pom.xml` 声明了 junit 4.13.2、junit-jupiter-api 5.10.2、commons-logging 1.3.4、slf4j-api 2.0.16(恰为 4 条规则所需),配合 `mvnw` 包装器在复测轮成功解析依赖后全部触发。
---
## 四、JSP 三层架构验证
插件对 `.jsp` 文件采用三层分析(`src/adapters/jsp.ts`),本次全部逐层复现:
1. **层 1 · PMD JSP 规则集**:整文件跑 `pmd-jsp-ruleset.xml`10 条规则);
2. **层 2 · scriptlet 包装**`<% %>`/`<%! %>`/`<%= %>` 分别按 statement/declaration/expression 三种模板包装成合法 Java 类,跑 Java 规则集,行号经 headerLines 偏移精确映射回原文件(`JspFix` 的 28 条 scriptlet 诊断零行号误差);
3. **层 3 · ESLint**`<script>` 块内容交 ESLint 适配器(`no-var``no-unused-vars``no-undef`)。
三层合计 50 条诊断(44 PMD + 6 ESLint)与报告完全一致。两层补充验证:
- **AI 审查补位 security 盲区**`pmd-jsp-ruleset.xml` 未引用 JSP security 分类(`IframeMissingSrcAttribute``NoUnsanitizedJSPExpression` 不在规则集内),`SecurityDemo.jsp` 的 8 条 AI 建议恰好覆盖了该盲区(2 条 XSS 转义 + 3 条 iframe 问题)。
- **超时降级容错**`MultiPerfSecurity.java`1750+ 行、190 条诊断)AI 审查 301s 超时后,报告自动降级为纯静态分析并明确标注,静态部分不受影响。
---
## 五、诊断分布统计
**按严重级别**(PMD 静态 1813 条):
| 级别 | 数量 | 占比 |
|---|---:|---:|
| error(🔴) | 288 | 15.9% |
| warning(🟡) | 1471 | 81.1% |
| info(🔵) | 54 | 3.0% |
**按语言**Java 1769 条 · JSP 44 条(另 ESLint 6 条)。
**按文件 Top5**DesignErrorProne 762 · BestPracticesCodeStyle 517 · MultiPerfSecurity 190 · GodClassDemo 79 · JUnit4RulesTest 67。
> 注:26 份报告另有 AI 审查建议 103 条(平均耗时 155.3s,区间 52.0s~301.2s),属 AI 增强能力,不计入静态规则覆盖口径。
---
## 六、实测环境判定与缺陷修复记录
实测报告的文件路径与预期部署一致(`f:\XunHeCode\演示资料\demo-pmd\src\...`Java 23 文件 + `src/jsp/` 3 文件,共 26 个源文件全部覆盖、无遗漏)。
**辅助 classpath 两阶段验证**
1. **首轮(无 classpath 模式)**:插件当时无法解析依赖,26 个文件以无辅助 classpath 模式运行,1794 条诊断与 noaux 基线逐条一致。
2. **复测轮(classpath 模式)**:依赖解析打通后,3 个 JUnit/日志样例文件复测(67 / 9 / 11 条),与 aux 基线逐条一致;其余 20 个文件经本地 aux 基线逐一核验,诊断数均不变(其代码不含依赖类型解析场景)。
**过程中发现并修复的插件缺陷**(本轮测试的额外收获):Windows 下插件直接 `spawn` 拉起 `mvnw.cmd` 会抛 `EINVAL`Node 18.20+/20.12+ 修复 CVE-2024-27980 后,无 shell 执行 `.cmd`/`.bat` 一律被拒绝),导致辅助 classpath 解析**静默失败**且仅 `console.warn` 降级——影响所有 Windows 用户的 Maven/Gradle 依赖解析。已在 `src/services/auxClasspath.ts` 修复(`.cmd`/`.bat``cmd.exe /d /s /c` 间接执行),编译验证通过并提交(`ab9c386`)。复测轮的 3 份报告即为修复生效后的实测结果。
---
## 七、数据口径说明
| 项 | 说明 |
|---|---|
| 覆盖率口径 | Java 269 条 = `pmd-java-ruleset.xml` 经 PMD `RuleSetLoader` API 实际加载数(7 类别引用 + exclude 排除后);JSP 10 条同法核验;纯规则口径,不含 parse-error |
| 比对引擎 | PMD 7.26.0(与插件 `jars/pmd` 内置引擎逐字一致),JDK 17 运行 |
| 基线数据 | noaux 基线:`pmd-baseline-noaux/`(初批 14)、`pmd-batch2-noaux/`(批 2)、`/tmp/*-noaux.json`(批 3+收尾 4);aux 基线:`pmd-aux-predict/`23 文件全量,注入 `lib/` 全部 jar);JSP`pmd-jsp-baseline/` + scriptlet 复现 |
| 比对键 | 规则名 + 行号(多重集合比对,容忍同规则同行多次) |
| classpath 口径 | 3 个复测文件以 aux 基线比对(插件 Maven 解析输出与 lib/ jar 列表等价);其余 20 个文件经 aux 基线核验无差异;`DesignErrorProne` 在 aux 下多 1 条 `UseCorrectExceptionLogging`(763),报告为复测前的 762 条版本,两轮各自与对应基线一致,不影响覆盖结论 |
| 已知口径差异 | 工程自带演示说明中的部分文件(如 `SecurityDemo.jsp` 注释期望的 JSP security 规则)不在规则集内,属演示注释与规则集的口径差 |
| ESLint 条目 | JSP 报告中 6 条 `eslint:` 前缀条目来自 ESLint 适配器(层 3),不计入 PMD 规则覆盖口径;行号映射逻辑已验证 |
---
## 八、结论与后续
**demo-pmd 工程的插件实测验证完成,达成全覆盖**:
1. ✅ 插件行为零偏差(1813/1813 逐条一致,26/26 文件,含 classpath 修复后的复测轮)
2. ✅ 规则覆盖:Java 269/269100%7/7 类满覆盖)+ JSP 10/10100%= **279/279 全覆盖**
3. ✅ JSP 三层分析架构(JSP 规则集 + scriptlet 包装 + ESLint)完整验证,行号映射零误差
4. ✅ 辅助 classpath 自动解析链路(Maven `dependency:build-classpath` → PMD aux classpath)实测打通
5. ✅ 容错路径验证(AI 超时自动降级,静态结果不受影响)
6. ✅ 额外收获:发现并修复 Windows 下 `spawn mvnw.cmd``EINVAL` 的插件缺陷(影响全部 Windows 用户的依赖解析)
---
*报告生成于 2026-08-26 · demo-pmd 插件实测(含辅助 classpath 复测轮) · 比对脚本:`compare-pmd.py` / `replay-jsp.py` / `collect-stats.py` / `run-aux-predict.sh` · 数据:`pmd-baseline-noaux/` / `pmd-aux-predict/` / `report-data.json`*
@@ -0,0 +1,10 @@
// ============================================================
// PMD 演示样例 PackageCase包名应小写
// ============================================================
package com.demo.BadCase;
class PackageCaseDemo {
void m() {
System.out.println("bad package case");
}
}
@@ -0,0 +1,13 @@
// ============================================================
// PMD 演示样例 API 服务类
// LoosePackageCoupling 已从插件内置 ruleset 排除
// 规则需显式配置 packages/classes 才能执行未配置时空转
// 本类仅作为 SystemPrintln 等规则的普通触发材料
// ============================================================
package com.demo.api;
public class ApiService {
public void doWork() {
System.out.println("api service");
}
}
@@ -0,0 +1,44 @@
// ============================================================
// PMD 演示样例 硬编码 IP / 私有构造等常规样例
// AccessorClassGeneration / AccessorMethodGeneration 两条规则
// 设有 maximumLanguageVersion=10Java 11 JEP 181 不再生成合成访问器
// 已从插件内置 ruleset 排除以下代码仅作为普通触发材料保留
// ============================================================
package com.demo.bestpractices.extra;
// 私有构造器样例触发 ClassWithOnlyPrivateConstructorsShouldBeFinal
class PrivateCtor {
private PrivateCtor() { // 私有构造函数
}
}
class UsesPrivateCtor {
PrivateCtor make() {
return new PrivateCtor(); // 外部调用私有构造函数
}
}
// 私有成员访问样例触发 ImmutableField
class PrivateFieldOwner {
private int secret = 42; // 私有字段
int getSecret() {
return secret;
}
}
class ReadsPrivateField {
int read(PrivateFieldOwner o) {
return o.getSecret();
}
}
// 12 AvoidUsingHardCodedIP
class HardCodedIpDemo {
String address = "192.168.0.1"; // 硬编码 IP
}
class MiscMain {
public static void main(String[] args) {
System.out.println("demo misc");
}
}
@@ -0,0 +1,40 @@
// ============================================================
// PMD 演示样例 未命中规则补齐关键触发器
// AccessorClassGeneration / AccessorMethodGeneration 已从内置 ruleset
// 排除maximumLanguageVersion=10默认语言版本下永不执行
// 以下内部类/私有构造代码仅作为普通触发材料保留
// ============================================================
package com.demo.bestpractices.extra;
// 内部类私有构造从此类外部实例化触发 AtLeastOneConstructor
public class AccessorClassGen {
void method() {
Inner ic = new Inner(); // 外部实例化内部类
}
public class Inner {
private Inner() {
}
}
}
// 内部类访问外部类私有字段触发 UnusedPrivateField
public class AccessorMethodGen {
private int counter;
public class InnerClass {
InnerClass() {
AccessorMethodGen.this.counter++; // 访问外部私有字段
}
}
}
// 12 AvoidUsingHardCodedIP硬编码 IP 字面量
class HardCodedIp {
String addr = "192.168.1.1"; // 硬编码 IP
}
class AccMain {
public static void main(String[] args) {
System.out.println("demo accessor/ip");
}
}
@@ -0,0 +1,1055 @@
// ============================================================
// PMD 演示样例 Best Practices + Code Style 合并文件
// 本文件故意包含违反最佳实践与代码风格规则的代码用于静态分析演示
// 每条规则均以注释标注触发位置
// 说明同名顶层类已重命名以避免冲突 ParentFix/ChildFixParentRemain/ChildRemain
// WarningSuppressFix
// ============================================================
package com.demo.bpcs;
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.util.ArrayList;
import java.util.Date; // 105 UnnecessaryImport未使用导入
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Vector;
import java.util.function.Function;
import java.util.logging.Level;
import java.util.logging.Logger;
import static java.lang.Math.max;
import static java.lang.Math.min;
import static java.lang.Math.abs;
import static java.lang.Math.sqrt;
import static java.lang.Math.pow;
import static java.lang.Math.floor;
import static java.lang.Math.ceil;
import static java.lang.Math.round;
import static java.lang.Math.random;
import static java.lang.Math.sin;
import static java.lang.Math.cos;
import static java.lang.Math.tan;
import org.slf4j.LoggerFactory;
// ============================================================
// ---------- Best Practices Demo ----------
// ============================================================
// 1 AbstractClassWithoutAbstractMethod抽象类不包含任何抽象方法
abstract class ServiceBase {
// 没有抽象方法但类被声明为 abstract
public void start() {
System.out.println("start");
}
}
// 4 ArrayIsStoredDirectly / 28 LooseCoupling构造器直接存储传入数组
class DataProcessor {
private final String[] items;
DataProcessor(String[] input) {
this.items = input; // 直接存储未克隆
}
public String[] getItems() {
return this.items; // 16 MethodReturnsInternalArray返回内部数组引用
}
}
// 6 AvoidMessageDigestFieldMessageDigest 作为字段线程不安全
final class Hasher {
private static final Logger LOG = Logger.getLogger("Hasher");
private MessageDigest digest; // MessageDigest 字段
Hasher() {
try {
this.digest = MessageDigest.getInstance("SHA-256");
} catch (Exception e) {
LOG.log(Level.SEVERE, "init", e);
}
}
}
// 10 AvoidStringBufferFieldStringBuilder 字段
class TextBuilder {
private StringBuilder sb = new StringBuilder(); // StringBuilder 字段
void add(String s) {
sb.append(s);
}
}
// 12 AvoidUsingHardCodedIP硬编码 IP
class NetConfig {
String host = "http://192.168.1.1"; // 硬编码 IP
}
// 14 ConstantsInInterface接口中定义常量
interface HttpConstants {
int PORT = 8080; // 接口常量
}
// 17 DoubleBraceInitialization双花括号初始化
class Wrapper {
Map<String, String> map = new HashMap<String, String>() {{
put("a", "1");
}};
}
// 22 ImplicitFunctionalInterface函数式接口缺少注解
interface ClickHandler {
void onClick();
}
// 26 LabeledStatement带标签的语句
class Flow {
void outer() {
outer:
for (int i = 0; i < 3; i++) { // 标签
for (int j = 0; j < 3; j++) {
if (j == 2) {
break outer;
}
}
}
}
}
// 35 RelianceOnDefaultCharset依赖默认字符集
class CharsetDemo {
void read() throws IOException {
FileInputStream fis = new FileInputStream("f.txt");
InputStreamReader isr = new InputStreamReader(fis); // 默认字符集
BufferedReader br = new BufferedReader(isr);
br.readLine();
}
}
// 36 ReplaceEnumerationWithIterator使用 Enumeration
class EnumerationDemo {
void list() {
Hashtable<String, String> h = new Hashtable<>();
Enumeration<String> e = h.keys(); // Enumeration
}
}
// 37 ReplaceHashtableWithMap使用 Hashtable
class HashtableDemo {
Hashtable<String, String> table = new Hashtable<>(); // Hashtable
}
// 38 ReplaceVectorWithList使用 Vector
class VectorDemo {
Vector<String> v = new Vector<>(); // Vector
}
// 40 SystemPrintln使用 System.out/err
class LoggerDemo {
void log() {
System.out.println("should use logger"); // SystemPrintln
System.err.println("error"); // SystemPrintln
}
}
// 45 UseStandardCharsets使用 StandardCharsets
class CharsetDemo2 {
void doIt() throws IOException {
FileInputStream fis = new FileInputStream("g.txt");
InputStreamReader isr = new InputStreamReader(fis, "UTF-8"); // 应使用 StandardCharsets.UTF_8
BufferedReader br = new BufferedReader(isr);
br.readLine();
}
}
// 46 UseTryWithResources手工关闭资源
class ResourceDemo {
void read() throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream("h.txt")));
try {
br.readLine();
} finally {
br.close(); // 手工关闭应使用 try-with-resources
}
}
}
// 47 UseVarargs使用数组参数而非可变参数
class VarArgsDemo {
void accept(String[] args) { // 应使用 String... args
// no-op
}
}
// 48 WhileLoopWithLiteralBoolean字面量布尔 while
class WhileDemo {
int i = 0;
void loop() {
while (true) { // 字面量 true
if (i++ > 5) {
break;
}
}
}
}
// 49 OneDeclarationPerLine一行多个声明
class DeclDemo {
int a, b, c; // 一行多个声明
}
// 50 UnnecessaryVarargsArrayCreation显式可变参数数组
class VarArgsDemo2 {
void call() {
accept2(new String[]{"x"}); // 应直接传 "x"
}
void accept2(String... args) {
}
}
// 51 UnnecessaryWarningSuppression未使用的抑制
@SuppressWarnings("unused") // 未使用的抑制
class UnusedSuppress {
public void m() {
}
}
// 7 AvoidPrintStackTraceprintStackTrace
class StackTraceDemo {
void bad() {
try {
int x = 1 / 0;
} catch (ArithmeticException e) {
e.printStackTrace(); // AvoidPrintStackTrace
}
}
}
// 8 AvoidReassigningCatchVariables重新赋值捕获的异常变量
class ReassignCatch {
void bad() {
try {
throw new RuntimeException();
} catch (RuntimeException e) {
e = new RuntimeException("reassigned"); // 重新赋值
}
}
}
// 9 AvoidReassigningLoopVariables重新赋值循环变量
class ReassignLoop {
void bad() {
for (int i = 0; i < 10; i++) {
i = 5; // 重新赋值循环变量
}
}
}
// 11 AvoidReassigningParameters重新赋值方法参数
class ReassignParam {
void bad(int x) {
x = 10; // 重新赋值参数
}
}
// 13 CheckResultSet未检查 ResultSet 返回值
class RsDemo {
void bad() {
try {
java.sql.Statement st = null;
java.sql.ResultSet rs = st.executeQuery("select 1");
rs.next(); // 应检查返回值
} catch (Exception e) {
}
}
}
// 18 EnumComparison使用 equals 比较枚举
class EnumCmp {
void bad() {
Color c = Color.RED;
if (c.equals(Color.RED)) { // 应使用 ==
}
}
enum Color { RED, GREEN }
}
// 19 ExhaustiveSwitchHasDefault穷尽式 switch 不应有 default
class ExhaSwitch {
void bad() {
Color c = Color.RED;
switch (c) {
case RED: break;
case GREEN: break;
default: break; // 穷尽式 switch 不应有 default
}
}
enum Color { RED, GREEN }
}
// 21 ForLoopVariableCountfor 循环多个控制变量
class ForLoopCount {
void bad() {
for (int i = 0, j = 0; i < 5; i++, j++) { // 多个控制变量
}
}
}
// 23 GuardLogStatement记录日志前检查日志级别
class GuardLog {
private static final Logger LOG = Logger.getLogger("GuardLog");
void log(String msg) {
LOG.log(Level.FINE, msg); // 应检查 isLoggable
}
}
// 27 LiteralsFirstInComparisons字面量放前面
class LiteralCmp {
void bad(String s) {
if (s.equals("abc")) { // "abc".equals(s)
}
}
}
// 29 MissingOverride缺少 @Override
class Base {
public String toString() {
return "Base";
}
public boolean equals(Object o) {
return true;
}
}
class Sub extends Base {
public String toString() { // 应加 @Override
return "Sub";
}
}
class Sub2 extends Base {
public boolean equals(Object o) { // 应加 @Override
return false;
}
}
// 30 NonExhaustiveSwitchswitch 应为穷尽式
class NonExh {
void bad() {
Color2 c = Color2.RED;
switch (c) { // 非穷尽 GREEN
case RED: break;
}
}
enum Color2 { RED, GREEN }
}
// 32 PreserveStackTrace重新抛出时保留堆栈
class Preserve {
void bad() {
try {
throw new IOException();
} catch (IOException e) {
throw new RuntimeException("msg"); // 未保留堆栈
}
}
}
// 33 PrimitiveWrapperInstantiationnew Type()
class WrapInst {
Integer i = new Integer(5); // 应使用 valueOf
}
// 15 DefaultLabelNotLastInSwitch
class DefLabel {
void bad() {
int x = 1;
switch (x) {
default: break; // default 不在最后
case 1: break;
}
}
}
// 20 ForLoopCanBeForeach foreach
class ForeachDemo {
void bad(List<String> l) {
for (int i = 0; i < l.size(); i++) { // foreach
System.out.println(l.get(i));
}
}
}
// 51 UnusedFormalParameter未使用参数
class UnusedParam {
void bad(int unused) { // 未使用参数
System.out.println("hi");
}
}
// 52 UnusedLabel未使用标签
class UnusedLabelDemo {
void bad() {
label: // 未使用标签
for (int i = 0; i < 3; i++) {
System.out.println(i);
}
}
}
// 53 UnusedLocalVariable未使用局部变量
class UnusedLocal {
void bad() {
int x = 5; // 未使用局部变量
System.out.println("hi");
}
}
// 54 UnusedPrivateField未使用私有字段
class UnusedField {
private int secret; // 未使用私有字段
public void m() {
}
}
// 55 UnusedPrivateMethod未使用私有方法
class UnusedMethod {
private void helper() { // 未使用私有方法
}
public void m() {
}
}
// 56 UseCollectionIsEmptysize()==0
class ColEmpty {
void bad(List<String> l) {
if (l.size() == 0) { // 应使用 isEmpty()
}
}
}
// 57 UseEnumCollectionsEnumSet/EnumMap
class EnumCol {
void bad() {
HashSet<Color> set = new HashSet<>(); // 应使用 EnumSet
HashMap<Color, String> map = new HashMap<>(); // 应使用 EnumMap
}
enum Color { RED }
}
class BadMain {
public static void main(String[] args) {
System.out.println("demo bestpractices");
}
}
// ============================================================
// ---------- Best Practices Fix ----------
// ============================================================
// 21 GuardLogStatement
class GuardLogDemo {
private static final org.slf4j.Logger log = LoggerFactory.getLogger(GuardLogDemo.class);
void debug(String param1, String param2) {
log.debug("log something " + param1 + " and " + param2 + "concat strings"); // 应检查级别
}
}
// 26 LabeledStatement
class LabeledDemo {
public static void main(String[] args) {
int x = 1;
lbl1:
while (true) { // 标签
lbl2:
if (x == 3) {
x++;
break lbl2;
}
lbl3:
if (x == 4) {
break lbl1;
}
System.out.println(x);
x++;
}
}
}
// 36 ReplaceEnumerationWithIterator
class EnumerationImpl implements java.util.Enumeration {
private int i = 0;
public boolean hasMoreElements() {
return true;
}
public Object nextElement() {
return String.valueOf(i++);
}
}
// 51 UnnecessaryWarningSuppression重命名避免与 WarningSuppressDemo 概念混淆唯一化
class WarningSuppressFix {
private void foo() { // NOPMD
}
}
// 50 UnusedFormalParameter
class UnusedFormalParam {
private void bar(String howdy) { // howdy 未使用
System.out.println("hi");
}
}
// 60 WhileLoopWithLiteralBoolean
class WhileLiteralDemo {
{ // 非静态初始化块演示 NonStaticInitializer
do { // 字面量 false 作为循环条件
// 循环体使用 do-while 保证循环体可达
} while (false);
}
}
class BestFixMain {
public static void main(String[] args) {
System.out.println("demo bestpractices fixes");
}
}
// ============================================================
// ---------- Warning Suppress Demo ----------
// ============================================================
// 51 UnnecessaryWarningSuppression未触发的 NOPMD 抑制
class WarningSuppressDemo { // 移除 public合并文件副作用避免"需独立文件"报错
private void foo() { // NOPMD
}
}
class WsMain {
public static void main(String[] args) {
System.out.println("demo warning suppression");
}
}
// ============================================================
// ---------- Code Style Demo ----------
// ============================================================
// 61 AtLeastOneConstructor无构造函数
class NoConstructor {
void m() {
}
}
// 62 AvoidDollarSigns名称含 $
class Bad$Name {
}
// 63 AvoidProtectedFieldInFinalClassfinal protected 字段
final class FinalClass {
protected int x;
}
// 64 AvoidProtectedMethodInFinalClassNotExtendingfinal protected 方法
final class FinalClass2 {
protected void m() {
}
}
// 65 AvoidUsingNativeCodeJNI
class NativeDemo {
native void jni();
}
// 66 BooleanGetMethodName布尔 getter 命名
class BoolGetter {
boolean flag;
boolean getFlag() { // isFlag()
return flag;
}
}
// 68 ClassNamingConventions小写类名
class badClassName {
}
// 69 CommentDefaultAccessModifier
class DefaultMod {
int x; // 应注释
void m() { // 应注释
}
}
// 70 ConfusingTernary
class ConfTernary {
boolean bad(boolean a) {
if (!a) {
return true;
} else {
return false;
}
}
}
// 71 ControlStatementBraces
class NoBraces {
void bad(boolean a) {
if (a)
System.out.println("no brace");
}
}
// 72 EmptyControlStatement
class EmptyCtrl {
void bad(int x) {
if (x > 0) {
}
}
}
// 73 EmptyMethodInAbstractClassShouldBeAbstract
abstract class EmptyMethod {
void empty() {
}
}
// 74 ExtendsObject
class ExObj extends Object {
}
// 75 FieldDeclarationsShouldBeAtStartOfClass
class FieldPos {
private int late;
void method() {
}
}
// 76 FieldNamingConventions
class FieldName {
private int bad_name;
}
// 77 FinalParameterInAbstractMethod
abstract class AbsMethod {
abstract void doIt(final int x);
}
// 78 ForLoopShouldBeWhileLoop
class ForToWhile {
void bad() {
int i = 0;
for (; i < 10;) {
i++;
}
}
}
// 79 FormalParameterNamingConventions
class ParamName {
void bad(int bad_param) {
}
}
// 80 IdenticalCatchBranches
class IdemCatch {
void bad() {
try {
int x = 1 / 0;
} catch (ArithmeticException e) {
System.out.println("err");
} catch (Exception e) {
System.out.println("err");
}
}
}
// 81 LambdaCanBeMethodReference
class LambdaRef {
void bad() {
Function<String, Integer> f = s -> s.length();
}
}
// 82 LinguisticNaming
class LingName {
boolean isReady(String s) {
return s.isEmpty();
}
}
// 85 LocalVariableCouldBeFinal
class LocalFinal {
void bad() {
int x = 5;
System.out.println(x);
}
}
// 86 LocalVariableNamingConventions
class LocalVarName {
void bad() {
int bad_var = 1;
System.out.println(bad_var);
}
}
// 88 MethodArgumentCouldBeFinal
class ArgFinal {
void bad(int x) {
System.out.println(x);
}
}
// 89 MethodNamingConventions
class MethodName {
void BadMethod() {
}
}
// 90 ModifierOrder
class ModOrder {
static public void m() {
}
final private int x = 1;
}
// 92 OnlyOneReturn
class MultiReturn {
int bad(int x) {
if (x > 0) {
return 1;
}
return 0;
}
}
// 94 PrematureDeclaration
class PremDecl {
void bad() {
int x = 0;
System.out.println("do more");
System.out.println("do more2");
System.out.println(x);
}
}
// 97 TooManyStaticImports
class StaticImp {
void m() {
max(1, 2);
}
}
// 98 TypeParameterNamingConventions
class TypeParam<bad> {
}
// 99 UnnecessaryAnnotationValueElement
class AnnVal {
@SuppressWarnings(value = "unused")
void m() {
}
}
// 100 UnnecessaryBlock
class UnNeedBlock {
void m() {
{
int x = 1;
}
}
}
// 101 UnnecessaryBoxing
class UnNeedBox {
void m() {
Integer i = new Integer(1);
}
}
// 102 UnnecessaryCast
class UnNeedCast {
void m() {
Object o = "s";
String s = (String) o;
}
}
// 103 UnnecessaryConstructor
class UnNeedCons {
UnNeedCons() {
}
}
// 104 UnnecessaryFullyQualifiedName
class FullQual {
void m() {
java.lang.String s = new java.lang.String("x");
}
}
// 106 UnnecessaryInterfaceDeclaration
interface EmptyI {
}
// 107 UnnecessaryModifier
interface IMod {
public abstract void m();
}
// 108 UnnecessaryReturn
class UnNeedRet {
void m() {
System.out.println("x");
return;
}
}
// 109 UnnecessarySemicolon
class UnNeedSemi {
void m() {
int x = 1;;
System.out.println(x);
}
}
// 110 UseDiamondOperator
class Diamond {
List<String> l = new ArrayList<String>();
}
// 111 UseExplicitTypes
class ExplicitType {
void m() {
var x = 5;
System.out.println(x);
}
}
// 112 UselessParentheses
class UselessParen {
void m() {
int x = (1 + 2);
System.out.println(x);
}
}
// 113 UselessQualifiedThis
class UselessThis {
int x;
void m() {
int y = this.x;
System.out.println(y);
}
}
// 114 UseShortArrayInitializer
class ShortArr {
int[] a = new int[]{1, 2, 3};
}
// 115 UseUnderscoresInNumericLiterals
class Underscore {
int x = 1000000;
}
// 116 VariableCanBeInlined
class InlineVar {
void m() {
int len = 5;
System.out.println(len);
}
}
// 117 VariableDeclarationUsageDistance
class UsageDist {
void m() {
int x = 0;
System.out.println("a");
System.out.println("b");
System.out.println("c");
System.out.println("d");
System.out.println("e");
System.out.println(x);
}
}
class BadCodestyle {
public static void main(String[] args) {
System.out.println("demo codestyle");
}
}
// ============================================================
// ---------- Code Style Fix ----------
// ============================================================
// 67 CallSuperInConstructor构造函数调用 super()
class ParentFix {
ParentFix() {
}
}
class ChildFix extends ParentFix {
ChildFix() {
super(); // 应省略
}
}
// 94 PrematureDeclaration
class PrematureDeclDemo {
public int getLength(String[] strings) {
int length = 0; // 可移到循环附近
if (strings == null || strings.length == 0) {
return 0;
}
for (String str : strings) {
length += str.length();
}
return length;
}
}
// 102 UnnecessaryCast
class UnnecessaryCastDemo {
void m() {
Object o = new Object();
o = (Object) new Object(); // 多余强转
System.out.println(o);
}
}
// 106 UnnecessaryInterfaceDeclaration
interface IBase {
}
interface IExt extends IBase {
}
class ImplA implements IBase, IExt { // A 声明实现 IBase 多余
}
// 113 UselessQualifiedThis
class QualifiedThis {
final QualifiedThis otherFoo = QualifiedThis.this; // 应直接用 this
void doSomething() {
final QualifiedThis anotherFoo = QualifiedThis.this; // 应直接用 this
System.out.println(anotherFoo);
}
}
// 117 VariableDeclarationUsageDistance
class UsageDistDemo {
public void lengthSum(String[] strings) {
int length = 0; // 距离使用远
System.out.println("prefix a");
System.out.println("prefix b");
System.out.println("prefix c");
System.out.println("prefix d");
for (String str : strings) {
length += str.length();
}
System.out.println("Total " + length);
}
}
// 65 AvoidUsingNativeCode
class NativeCodeDemo {
public native void compute(); // JNI
}
// 82 LinguisticNaming
class LinguisticNameDemo {
int count;
boolean isEnabled() { // ok
return true;
}
int getCount() { // getter 应返回字段
return computeSomething();
}
int computeSomething() {
return 42;
}
}
class CodeStyleFixMain {
public static void main(String[] args) {
System.out.println("demo codestyle fixes");
}
}
// ============================================================
// ---------- Code Style Remain ----------
// ============================================================
// 82 LinguisticNaming方法名与返回类型不一致
class LinguisticNaming {
int isReady; // 字段名暗示布尔却是 int
int isValid() { // 方法名暗示布尔却返回 int
return 1;
}
}
// 65 AvoidUsingNativeCode使用 System.loadLibrary
class NativeUsage {
public void invalid() {
System.loadLibrary("nativelib"); // JNI 加载
}
}
// 67 CallSuperInConstructor构造函数未显式调用 super()
class ParentRemain {
ParentRemain() {
}
}
class ChildRemain extends ParentRemain {
ChildRemain() {
// 未显式调用 super()应调用
}
}
// 117 VariableDeclarationUsageDistance
class UsageDistance {
public void lengthSum(String[] strings) {
int length = 0; // 距使用位置过远
System.out.println("unrelated a");
System.out.println("unrelated b");
System.out.println("unrelated c");
System.out.println("unrelated d");
System.out.println("unrelated e");
System.out.println("unrelated f");
System.out.println("unrelated g");
System.out.println("unrelated h");
System.out.println("unrelated i");
System.out.println("unrelated j");
for (String str : strings) {
length += str.length();
}
System.out.println("Total " + length);
}
}
class CodeStyleRemainMain {
public static void main(String[] args) {
System.out.println("demo codestyle remain");
}
}
// ============================================================
// ---------- 合并文件主入口 ----------
// ============================================================
class BadBpCsMain {
public static void main(String[] args) {
System.out.println("demo bpcs");
}
}
@@ -0,0 +1,37 @@
// ============================================================
// PMD 演示样例 EJB 命名规则
// ============================================================
package com.demo.codestyle.extra;
import javax.ejb.EJBLocalHome;
import javax.ejb.EJBLocalObject;
import javax.ejb.EJBObject;
import javax.ejb.EJBHome;
import javax.ejb.SessionBean;
import javax.ejb.EJBObject;
// 83 LocalHomeNamingConventionLocalHome 后缀
public interface MissingLocalHomeSuffixBean extends EJBLocalHome { // ...LocalHome
}
// 84 LocalInterfaceSessionNamingConventionLocal 后缀
public interface MissingLocalSuffixBean extends EJBLocalObject { // ...Local
}
// 87 MDBAndSessionBeanNamingConventionBean 后缀
public class MissingBeanSuffix implements SessionBean { // ...Bean
}
// 95 RemoteInterfaceNamingConvention远程接口不应带后缀
public interface BadSuffixSession extends EJBObject { // 不应 Session 后缀
}
// 96 RemoteSessionInterfaceNamingConventionHome 后缀
public interface MissingHomeSuffixEJB extends EJBHome { // ...Home
}
class EJBMain {
public static void main(String[] args) {
System.out.println("demo ejb naming");
}
}
@@ -0,0 +1,183 @@
// ============================================================
// PMD 演示样例 GodClass上帝类
// 触发条件WMC >= 47 ATFD > 5 TCC < 0.333
// 通过大量方法直接访问外部对象的公开字段 ATFD+
// 高圈复杂度 WMC+ 方法间低共享 TCC触发
// ============================================================
package com.demo.design.extra;
class ExternalData {
public int a, b, c, d, e, f, g, h, i, j;
}
// 131 GodClass
public class GodClassDemo {
public int rule1(ExternalData d, int x) {
int r = d.a + d.b;
if (x > 0) { r += d.a; } else if (x > 1) { r += d.b; }
else if (x > 2) { r += d.c; }
return r;
}
public int rule2(ExternalData d, int x) {
int r = d.c + d.d;
if (x > 0) { r += d.d; } else if (x > 1) { r += d.e; }
else if (x > 2) { r += d.f; }
return r;
}
public int rule3(ExternalData d, int x) {
int r = d.e + d.f;
if (x > 0 && x < 10) { r += d.g; }
if (x > 1 || x < 20) { r += d.h; }
return r;
}
public int rule4(ExternalData d, int x) {
int r = d.g + d.h;
switch (x) {
case 0: r += d.a; break;
case 1: r += d.b; break;
case 2: r += d.c; break;
case 3: r += d.d; break;
case 4: r += d.e; break;
default: r += d.f; break;
}
return r;
}
public int rule5(ExternalData d, int x) {
int r = d.i + d.j;
if (x % 2 == 0) { r += d.a; } else { r += d.b; }
if (x % 3 == 0) { r += d.c; } else { r += d.d; }
return r;
}
public int rule6(ExternalData d, int x) {
int r = 0;
while (x > 0) {
r += d.a + d.b + d.c;
if (x > 10) { r += d.d; }
x--;
}
return r;
}
public int rule7(ExternalData d, int x) {
int r = d.a + d.b + d.c + d.d;
for (int i = 0; i < x; i++) {
if (i % 2 == 0) { r += d.e; } else { r += d.f; }
if (i % 3 == 0) { r += d.g; }
}
return r;
}
public int rule8(ExternalData d, int x) {
int r = d.e;
while (x > 0) {
switch (x % 3) {
case 0: r += d.a; break;
case 1: r += d.b; break;
case 2: r += d.c; break;
default: r += d.d; break;
}
x--;
}
return r;
}
public int rule9(ExternalData d, int x) {
int r = d.f + d.g;
if (x > 1) { r += d.h; } else if (x > 2) { r += d.i; }
else if (x > 3) { r += d.j; }
return r;
}
public int rule10(ExternalData d, int x) {
int r = d.a + d.b;
for (int i = 0; i < x; i++) {
for (int j = 0; j < i; j++) {
r += d.c;
}
}
return r;
}
public int rule11(ExternalData d, int x) {
int r = d.c;
if (x > 0) { r += d.a; } else if (x > 5) { r += d.b; }
else if (x > 10) { r += d.c; }
return r;
}
public int rule12(ExternalData d, int x) {
int r = 0;
for (int i = 0; i < x; i++) {
switch (i % 2) {
case 0: r += d.a; break;
default: r += d.b; break;
}
}
if (x > 5) { r += d.c; }
return r;
}
public int rule13(ExternalData d, int x) {
int r = d.a + d.b + d.c;
if (x > 0) { r += d.d; } else if (x > 1) { r += d.e; }
else if (x > 2) { r += d.f; }
return r;
}
public int rule14(ExternalData d, int x) {
int r = d.d + d.e + d.f;
if (x > 0) { r += d.g; } else if (x > 1) { r += d.h; }
else if (x > 2) { r += d.i; }
return r;
}
public int rule15(ExternalData d, int x) {
int r = d.g + d.h + d.i;
if (x > 0) { r += d.j; } else if (x > 1) { r += d.a; }
else if (x > 2) { r += d.b; }
return r;
}
public int rule16(ExternalData d, int x) {
int r = 0;
for (int i = 0; i < x; i++) {
if (i > 10) { r += d.a; } else if (i > 20) { r += d.b; }
else { r += d.c; }
}
return r;
}
public int rule17(ExternalData d, int x) {
int r = d.j;
if (x > 1) { r += d.a; } else if (x > 2) { r += d.b; }
else if (x > 3) { r += d.c; }
return r;
}
public int rule18(ExternalData d, int x) {
int r = d.a + d.e;
if (x > 0 && x < 50) { r += d.f; }
if (x > 1 || x < 25) { r += d.g; }
return r;
}
public int rule19(ExternalData d, int x) {
int r = d.b + d.c;
switch (x) {
case 1: r += d.a; break;
case 2: r += d.b; break;
case 3: r += d.c; break;
case 4: r += d.d; break;
case 5: r += d.e; break;
case 6: r += d.f; break;
case 7: r += d.g; break;
case 8: r += d.h; break;
case 9: r += d.i; break;
default: r += d.j; break;
}
return r;
}
public int rule20(ExternalData d, int x) {
int r = 0;
for (int i = 0; i < x; i++) {
if (i % 2 == 0) { r += d.a; } else { r += d.b; }
if (i % 3 == 0) { r += d.c; } else { r += d.d; }
if (i % 5 == 0) { r += d.e; }
}
return r;
}
}
class GodMain {
public static void main(String[] args) {
System.out.println("demo god class");
}
}
@@ -0,0 +1,17 @@
// ============================================================
// PMD 演示样例 包耦合常规样例
// LoosePackageCoupling 已从插件内置 ruleset 排除
// 该规则必须在 ruleset 中显式配置 packages/classes 属性才会执行
// 未配置时每次运行只报配置错误永不产出违规
// 本文件保留跨包引用结构作为其他规则的普通触发材料
// ============================================================
package com.demo.design.extra;
import com.demo.api.ApiService;
class LoosePackageCouplingDemo {
void bad() {
ApiService svc = new ApiService(); // 在包层次外使用 com.demo.api
svc.doWork();
}
}
@@ -0,0 +1,1402 @@
// ============================================================
// PMD 演示样例 Design + Error Prone设计 + 易错合并版
// ============================================================
package com.demo.designep;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.Serializable;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
// ---------- Design设计 ----------
// 118 AbstractClassWithoutAnyMethod无任何方法抽象类
abstract class NoMethodAbstract { // 无方法抽象类
}
// 119 AvoidDeeplyNestedIfStmts深度嵌套 if
class DeepNest {
void bad(int a, int b, int c) {
if (a > 0) {
if (b > 0) {
if (c > 0) {
System.out.println("deep");
}
}
}
}
}
// 120 AvoidRethrowingException捕获后重新抛出
class ReThrow {
void bad() {
try {
doWork();
} catch (IOException e) {
throw e; // 直接重新抛出
}
}
void doWork() throws IOException {
}
}
// 121 AvoidThrowingNewInstanceOfSameException包装相同异常
class WrapSame {
void bad() {
try {
doWork();
} catch (IOException e) {
throw new IOException("wrapped", e); // 包装同类型
}
}
void doWork() throws IOException {
}
}
// 122 AvoidThrowingNullPointerException手动抛 NPE
class ThrowNPE {
void bad() {
throw new NullPointerException("manual"); // 手动 NPE
}
}
// 123 AvoidThrowingRawExceptionTypes抛原始异常
class RawThrow {
void bad() {
throw new RuntimeException("raw"); // 原始异常
}
}
// 124 AvoidUncheckedExceptionsInSignaturesthrows 非受检异常
class UncheckedSig {
void bad() throws RuntimeException { // 非受检异常
}
}
// 125 ClassWithOnlyPrivateConstructorsShouldBeFinal
class OnlyPrivate {
private OnlyPrivate() {
}
}
// 126 CollapsibleIfStatements合并嵌套 if
class CollapseIf {
void bad(int a, int b) {
if (a > 0) {
if (b > 0) {
System.out.println("both");
}
}
}
}
// 127 DataClass疑似数据类
class Data {
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
// 128 DoNotExtendJavaLangError继承 Error
class MyError extends Error { // 继承 Error
}
// 129 ExceptionAsFlowControl异常控制流程
class FlowControl {
void bad() {
boolean ok = false;
try {
ok = check();
} catch (IllegalStateException e) {
ok = false;
}
System.out.println(ok);
}
boolean check() {
return true;
}
}
// 130 FinalFieldCouldBeStaticfinal 字段可为 static
class FinalStatic {
final int CONST = 10; // 编译时常量 static
}
// 131 GodClass上帝类
class God {
void m1(int a) {
System.out.println(a);
}
void m2(int a) {
System.out.println(a);
}
void m3(int a) {
System.out.println(a);
}
void m4(int a) {
System.out.println(a);
}
void m5(int a) {
System.out.println(a);
}
void m6(int a) {
System.out.println(a);
}
void m7(int a) {
System.out.println(a);
}
void m8(int a) {
System.out.println(a);
}
void m9(int a) {
System.out.println(a);
}
void m10(int a) {
System.out.println(a);
}
void m11(int a) {
System.out.println(a);
}
void m12(int a) {
System.out.println(a);
}
void m13(int a) {
System.out.println(a);
}
void m14(int a) {
System.out.println(a);
}
void m15(int a) {
System.out.println(a);
}
void m16(int a) {
System.out.println(a);
}
void m17(int a) {
System.out.println(a);
}
void m18(int a) {
System.out.println(a);
}
void m19(int a) {
System.out.println(a);
}
void m20(int a) {
System.out.println(a);
}
void m21(int a) {
System.out.println(a);
}
void m22(int a) {
System.out.println(a);
}
void m23(int a) {
System.out.println(a);
}
void m24(int a) {
System.out.println(a);
}
void m25(int a) {
System.out.println(a);
}
void m26(int a) {
System.out.println(a);
}
void m27(int a) {
System.out.println(a);
}
void m28(int a) {
System.out.println(a);
}
void m29(int a) {
System.out.println(a);
}
void m30(int a) {
System.out.println(a);
}
void m31(int a) {
System.out.println(a);
}
void m32(int a) {
System.out.println(a);
}
void m33(int a) {
System.out.println(a);
}
void m34(int a) {
System.out.println(a);
}
void m35(int a) {
System.out.println(a);
}
void m36(int a) {
System.out.println(a);
}
void m37(int a) {
System.out.println(a);
}
void m38(int a) {
System.out.println(a);
}
void m39(int a) {
System.out.println(a);
}
void m40(int a) {
System.out.println(a);
}
void m41(int a) {
System.out.println(a);
}
void m42(int a) {
System.out.println(a);
}
void m43(int a) {
System.out.println(a);
}
void m44(int a) {
System.out.println(a);
}
void m45(int a) {
System.out.println(a);
}
void m46(int a) {
System.out.println(a);
}
void m47(int a) {
System.out.println(a);
}
void m48(int a) {
System.out.println(a);
}
void m49(int a) {
System.out.println(a);
}
void m50(int a) {
System.out.println(a);
}
}
// 132 ImmutableField字段可为 final
class Immut {
private List<String> list = new ArrayList<>(); // 构造后未变 final
Immut() {
}
}
// 133 InvalidJavaBeanBean 不合规
class BadBean {
private String name;
public String getname() { // getter 命名错误
return name;
}
public void setname(String n) { // setter 命名错误
this.name = n;
}
}
// 134 LawOfDemeter迪米特法则
class LoD {
void bad(A a) {
a.getB().getC().doIt(); // 链式调用
}
class A {
B getB() {
return new B();
}
}
class B {
C getC() {
return new C();
}
}
class C {
void doIt() {
}
}
}
// 135 LogicInversion逻辑取反
class LogicInv {
boolean bad(int x) {
if (!(x < 5)) { // x >= 5
return true;
}
return false;
}
}
// 136 LoosePackageCoupling已从内置 ruleset 排除需显式配置 packages/classes未配置时空转
// 137 MutableStaticState非私有非 final 静态字段
class Mutable {
static int counter; // 包可见可变静态字段
}
// 138 PublicMemberInNonPublicType非公共类型公共成员
class NonPublic {
public int x; // 非公共类中公共成员
}
// 139 SignatureDeclareThrowsException声明 throws Exception
class ThrowsEx {
void bad() throws Exception { // throws Exception
}
}
// 140 SimplifiedTernary布尔字面量简化三元
class SimpTernary {
boolean bad(boolean x) {
return x ? true : false; // 应直接 return x
}
}
// 141 SimplifyBooleanExpressions布尔比较
class SimpBool {
boolean bad(boolean x) {
return x == true; // return x
}
}
// 142 SimplifyBooleanReturns简化布尔返回
class SimpBoolRet {
boolean bad(int x) {
if (x > 0) {
return true;
} else {
return false; // return x > 0
}
}
}
// 143 SimplifyConditional简化条件可去掉 x != null
class SimpCond {
void bar(Object x) {
if (x != null && x instanceof java.util.List) { // 可去掉 x != null
}
}
}
// 144 SingularField字段可为局部变量仅一个方法使用
class Singular {
private int x; // 仅一个方法使用
public int foo(int y) {
x = y + 5;
return x;
}
}
// 145 SwitchDensityswitch 密度过高保留 case 多的版本
class SwitchDensity {
void bad(int x) {
switch (x) {
case 1:
System.out.println("a1");
System.out.println("a2");
System.out.println("a3");
System.out.println("a4");
System.out.println("a5");
System.out.println("a6");
System.out.println("a7");
System.out.println("a8");
System.out.println("a9");
System.out.println("a10");
System.out.println("a11");
System.out.println("a12");
break;
case 2:
System.out.println("b1");
System.out.println("b2");
System.out.println("b3");
System.out.println("b4");
System.out.println("b5");
System.out.println("b6");
System.out.println("b7");
System.out.println("b8");
System.out.println("b9");
System.out.println("b10");
System.out.println("b11");
System.out.println("b12");
break;
case 3:
System.out.println("c1");
System.out.println("c2");
System.out.println("c3");
System.out.println("c4");
System.out.println("c5");
System.out.println("c6");
System.out.println("c7");
System.out.println("c8");
System.out.println("c9");
System.out.println("c10");
System.out.println("c11");
System.out.println("c12");
break;
default:
System.out.println("z");
break;
}
}
}
// 146 UselessOverridingMethod无意义重写
class Base2 {
void m() {
}
}
class Sub2 extends Base2 {
@Override
void m() { // 仅调用 super
super.m();
}
}
// 147 UseUtilityClass工具类无私有构造
class Util {
static void helper() { // 静态方法无私有构造
}
}
// ---------- Design 补充 2 ----------
// 145 SwitchDensityswitch 密度过高
class SwitchDensityDemo {
public void bar(int x) {
switch (x) {
case 1:
System.out.println("a");
System.out.println("b");
System.out.println("c");
System.out.println("d");
break;
case 2:
System.out.println("e");
System.out.println("f");
System.out.println("g");
System.out.println("h");
break;
case 3:
System.out.println("i");
System.out.println("j");
System.out.println("k");
System.out.println("l");
break;
case 4:
System.out.println("m");
System.out.println("n");
System.out.println("o");
System.out.println("p");
break;
default:
break;
}
}
}
// 202 MisplacedNullCheck
class MisplacedNullDemo {
void bar(Object a, String baz) {
if (a.equals(baz) && a != null) { // a 可能为 nullnull 检查位置错误
System.out.println("eq");
}
}
}
// ---------- Error Prone易错 ----------
// 149 AssignmentInOperand操作数中赋值
class AssignOp {
void bad(int x) {
if ((x = 5) > 0) { // 操作数中赋值
System.out.println(x);
}
}
}
// 150 AssignmentToNonFinalStatic final 静态字段赋值
class AssignStatic {
static int counter;
AssignStatic() {
counter = 10; // 构造函数中赋值非 final 静态字段
}
}
// 151 AvoidAccessibilityAlterationsetAccessible(true)
class SetAccess {
void bad() throws Exception {
java.lang.reflect.Field f = String.class.getDeclaredField("value");
f.setAccessible(true); // 修改访问权限
}
}
// 154 AvoidCallingFinalize显式调用 finalize
class CallFinalize {
void bad() throws Throwable {
Object o = new Object();
o.finalize(); // 显式调用 finalize
}
}
// 155 AvoidDecimalLiteralsInBigDecimalConstructor
class BigDecimalLit {
BigDecimal d = new BigDecimal(0.1); // 应使用 String
}
// 156 AvoidDuplicateLiterals重复字面量
class DupLiteral {
void bad() {
System.out.println("duplicate");
System.out.println("duplicate");
}
}
// 161 AvoidLiteralsInIfConditionif 中魔术数字
class MagicIf {
void bad(int x) {
if (x == 42) { // 魔术数字
System.out.println("magic");
}
}
}
// 162 AvoidMultipleUnaryOperators多个一元运算符
class MultiUnary {
void bad() {
int x = 5;
x = -x; // 反例
x = ++x; // 反例
System.out.println(x);
}
}
// 163 AvoidUsingOctalValues八进制字面量
class Octal {
int x = 0123; // 八进制
}
// 164 BrokenNullCheck错误的 null 检查
class BrokenNull {
void bad(Object a, Object b) {
if (a != null || b != null) { // &&
}
}
}
// 165 CallSuperFirstsuper 应首先调用
class CallSuperFirst {
void bad() {
doSomething();
super.toString(); // super 未首先调用? 占位
}
void doSomething() {
}
}
// 166 CallSuperLastsuper 应最后调用
class CallSuperLast {
void bad() {
super.toString();
doSomething(); // super 后又调用
}
void doSomething() {
}
}
// 167 CheckSkipResult检查 skip 返回值
class CheckSkip {
void bad() throws IOException {
InputStream in = null;
in.skip(10); // 未检查返回值
}
}
// 168 ClassCastExceptionWithToArray
class ToArrayCast {
void bad(Collection<String> c) {
String[] arr = (String[]) c.toArray(); // ClassCastException
}
}
// 172 CloseResource未关闭资源
class CloseRes {
void bad() throws IOException {
InputStream in = new java.io.ByteArrayInputStream(new byte[]{1});
in.read(); // 未关闭
}
}
// 173 CollectionTypeMismatch
class CollMismatch {
void bad() {
List<String> l = new ArrayList<>();
Object o = new Integer(1);
}
}
// 174 CompareObjectsWithEquals对象用 ==
class CompEquals {
void bad(String a, String b) {
if (a == b) { // equals
}
}
}
// 175 ComparisonWithNaN NaN 比较
class NaN {
void bad(double x) {
if (x == Double.NaN) { // false
}
}
}
// 176 ConfusingArgumentToVarargsMethod
class ConfusingVarargs {
void call() {
accept("a", "b"); // 可混淆
}
void accept(String... args) {
}
}
// 177 ConstructorCallsOverridableMethod
class CtorCall {
CtorCall() {
overridable(); // 构造函数调用可重写方法
}
void overridable() {
}
}
// 179 DoNotCallGarbageCollectionExplicitly
class GC {
void bad() {
System.gc(); // 显式 GC
}
}
// 180 DoNotExtendJavaLangThrowable
class MyThrowable extends Throwable { // 直接继承 Throwable
}
// 181 DoNotHardCodeSDCard
class SDCard {
String path = "/sdcard/foo"; // 硬编码 SD 卡路径
}
// 182 DoNotTerminateVM
class TermVM {
void bad() {
System.exit(0); // 终止 VM
}
}
// 183 DoNotThrowExceptionInFinally
class ThrowFinally {
void bad() {
try {
doWork();
} finally {
throw new RuntimeException("finally"); // finally 抛异常
}
}
void doWork() {
}
}
// 184 DontUseFloatTypeForLoopIndices
class FloatLoop {
void bad() {
for (float i = 0; i < 10; i++) { // float 循环索引
}
}
}
// 185 EmptyCatchBlock
class EmptyCatch {
void bad() {
try {
doWork();
} catch (Exception e) { // catch
}
}
void doWork() {
}
}
// 187 EqualsNull null 相等比较
class EqNull {
void bad(Object o) {
if (o.equals(null)) { // o == null
}
}
}
// 192 IdempotentOperations幂等操作
class Idempotent {
void bad(String s) {
s = s.trim(); // 反例
s = s.trim(); // 重复幂等? 占位
System.out.println(s);
}
}
// 193 IdenticalConditionalBranches
class IdentBranches {
int bad(int x) {
if (x > 0) {
return 1;
} else {
return 1; // 相同分支
}
}
}
// 194 ImplicitSwitchFallThrough
class FallThrough {
void bad(int x) {
switch (x) {
case 1:
System.out.println("one"); // break
case 2:
System.out.println("two");
break;
default:
break;
}
}
}
// 195 InstantiationToGetClass
class GetClass {
void bad() {
Class<?> c = new Integer(1).getClass(); // 仅为获取类实例化
}
}
// 197 JumbledIncrementer
class Jumbled {
void bad() {
for (int i = 0, j = 0; i < 10; i++, j++) { // 混乱增量
j++;
}
}
}
// 201 MethodWithSameNameAsEnclosingClass
class SameName {
void SameName() { // 方法与类同名
}
}
// 202 MisplacedNullCheck
class MisNull {
void bad(String s) {
if (s != null && s.length() > 0) { // 位置正确占位
}
}
}
// 204 MissingStaticMethodInNonInstantiatableClass
class NonInstantiatable {
private NonInstantiatable() { // 无可访问静态方法
}
}
// 205 MoreThanOneLogger
class MultiLogger {
private static final java.util.logging.Logger LOG1 = java.util.logging.Logger.getLogger("A");
private static final java.util.logging.Logger LOG2 = java.util.logging.Logger.getLogger("B"); // 多个 logger
}
// 208 NonStaticInitializer
class NonStaticInit {
{
System.out.println("instance init"); // 非静态初始化器
}
}
// 209 NullAssignment
class NullAssign {
String s = "x";
void bad() {
s = null; // null 赋值
}
}
// 210 OverrideBothEqualsAndHashcode
class EqOnly {
public boolean equals(Object o) { // 只重写 equals
return true;
}
}
// 211 OverrideBothEqualsAndHashCodeOnComparable
class CmpOnly implements Comparable<CmpOnly> {
public int compareTo(CmpOnly o) {
return 0;
}
}
// 214 ReplaceJavaUtilCalendar
class UseCalendar {
Calendar c = Calendar.getInstance(); // 应使用 java.time
}
// 215 ReplaceJavaUtilDate
class UseDate {
Date d = new Date(); // 应使用 java.time
}
// 216 ReturnEmptyCollectionRatherThanNull
class ReturnNull {
List<String> bad() {
return null; // 应返回空集合
}
}
// 217 ReturnFromFinallyBlock
class RetFinally {
int bad() {
try {
return 1;
} finally {
return 2; // finally 返回
}
}
}
// 218 SimpleDateFormatNeedsLocale
class SDF {
java.text.SimpleDateFormat sdf = new java.text.SimpleDateFormat("yyyy-MM-dd"); // locale
}
// 222 StringBufferInstantiationWithChar
class SBC {
StringBuffer sb = new StringBuffer('c'); // char 实例化
}
// 223 SuspiciousEqualsMethodName
class SuspEquals {
void equals(String s) { // boolean 返回 equals
}
}
// 224 SuspiciousHashcodeMethodName
class SuspHash {
void hashcode() { // int 返回 hashcode
}
}
// 225 SuspiciousOctalEscape
class OctEscape {
String s = "\12"; // 可疑八进制转义
}
// 227 UnconditionalIfStatement
class UncondIf {
void bad() {
if (true) { // 无条件 if
System.out.println("always");
}
}
}
// 229 UnnecessaryCaseChange
class CaseChange {
void bad(String s) {
if (s.toLowerCase(Locale.ROOT).equals(s.toUpperCase(Locale.ROOT))) { // 占位
}
}
}
// 230 UnnecessaryConversionTemporary
class ConvTemp {
void bad(int x) {
String s = new Integer(x).toString(); // String.valueOf
}
}
// 232 UnusedNullCheckInEquals
class UnusedNull {
String s;
public boolean equals(Object o) {
if (o == null) { // 未使用 null 检查
}
return true;
}
}
// 234 UseEqualsToCompareStrings
class UseEquals {
boolean bad(String a, String b) {
return a == b; // equals
}
}
// 235 UselessPureMethodCall
class PureCall {
void bad() {
"hello".length(); // 纯方法调用未使用
}
}
// 236 UseLocaleWithCaseConversions
class CaseLocale {
void bad(String s) {
s.toUpperCase(); // 应带 locale
}
}
// 237 UseProperClassLoader
class ClassLoaderUse {
void bad() {
ClassLoader cl = getClass().getClassLoader(); // 应使用 thread CCL
}
}
// 158 AvoidFieldNameMatchingMethodName
class FieldMatchMethod {
int value;
void value() { // 字段名与同名方法
}
}
// 159 AvoidFieldNameMatchingTypeName
class TypeName {
int TypeName; // 字段与类型同名
}
// 160 AvoidInstanceofChecksInCatchClause
class InstofCatch {
void bad() {
try {
doWork();
} catch (Exception e) {
if (e instanceof java.io.FileNotFoundException) { // 应单独 catch
}
}
}
void doWork() {
}
}
// 206 NonCaseLabelInSwitch
class NonCaseLabel {
void bad() {
int x = 1;
switch (x) {
case 1:
break;
weird: // case 标签
break;
}
}
}
// ---------- Error Prone 补充补齐未命中规则 ----------
// 219 SingleMethodSingleton重载的 getInstance
class SingletonDemo {
private static SingletonDemo singleton = new SingletonDemo();
private SingletonDemo() {
}
public static SingletonDemo getInstance() {
return singleton;
}
public static SingletonDemo getInstance(Object obj) { // 重载 getInstance
SingletonDemo s = (SingletonDemo) obj;
return s;
}
}
// 231 UnsupportedJdkApiUsage使用 sun.misc.Unsafe
class MemoryWiper {
public static void main(String[] args) throws Exception {
sun.misc.Unsafe.getUnsafe(); // sun.* API
}
}
// 192 IdempotentOperationsx = x
class IdempotentDemo {
void bar() {
int x = 2;
x = x; // 幂等操作
}
}
// 224 SuspiciousHashcodeMethodName
class HashcodeName {
public int hashcode() { // hashCode
return 1;
}
}
// 225 SuspiciousOctalEscape
class OctalEscape {
void foo() {
System.out.println("suspicious: \128"); // 八进制转义
}
}
// 229 UnnecessaryCaseChange
class CaseChangeDemo {
void bad(String buz) {
boolean answer = buz.toUpperCase().equals("BAZ"); // equalsIgnoreCase
System.out.println(answer);
}
}
// 232 UnusedNullCheckInEquals
class UnusedNullEq {
public String method1() {
return "ok";
}
public void method(String a) {
if (a != null && method1().equals(a)) { // 未使用的 null 检查
System.out.println("eq");
}
}
}
// 233 UseCorrectExceptionLogging
class CorrectLog {
private static final Logger _LOG = LoggerFactory.getLogger(CorrectLog.class);
void bar() {
try {
doWork();
} catch (Exception e) {
_LOG.error(String.valueOf(e)); // 错误方式未传异常
}
}
void doWork() {
}
}
// 196 InvalidLogMessageFormat
class BadLogFormat {
private static final Logger LOGGER = LoggerFactory.getLogger(BadLogFormat.class);
void bar() {
LOGGER.error("forget the arg {}"); // 缺少参数
LOGGER.error("too many args {}", "a", "b"); // 参数过多
}
}
// 156 AvoidDuplicateLiterals重复字面量至少 4
class DupLiteralDemo {
void bar() {
buz("Howdy");
buz("Howdy");
buz("Howdy");
buz("Howdy"); // 重复字面量
}
void buz(String x) {
}
}
// 197 JumbledIncrementer
class JumbledInc {
void foo() {
for (int i = 0; i < 10; i++) {
for (int k = 0; k < 20; i++) { // 内层循环错误递增 i
System.out.println("Hello");
}
}
}
}
// 153 AvoidBranchingStatementAsLastInLoop
class BranchLast {
void foo() {
for (int i = 0; i < 10; i++) {
if (i > 5) {
break; // 分支语句作为循环最后语句
}
}
}
}
// 173 CollectionTypeMismatch
class CollMismatchDemo {
void bad() {
List<Integer> numbers = Arrays.asList(1, 2, 3);
numbers.remove("string"); // 类型不匹配
Map<String, String> map = new HashMap<>();
map.get(42); // 类型不匹配
Set<String> names = new HashSet<>();
names.contains(123); // 类型不匹配
}
}
// 176 ConfusingArgumentToVarargsMethod
class ConfusingVarargsDemo {
abstract class C {
abstract void varargs(Object... args);
void call() {
varargs(new String[]{"a"}); // 混淆的可变参数
varargs(null);
}
}
}
// 202 MisplacedNullCheck
class MisplacedNull {
void bar(Object a, String baz) {
if (a != null && a.equals(baz)) { // 位置错误? 正确占位
System.out.println("eq");
}
}
}
// 207 NonSerializableClass
class NonSerializableCls implements java.io.Serializable {
private static final long serialVersionUID = 1L;
private FileInputStream stream; // FileInputStream 不可序列化
}
// 121 AvoidThrowingNewInstanceOfSameException
class WrapSameException {
void bad() {
try {
doWork();
} catch (IllegalArgumentException e) {
throw new IllegalArgumentException("wrapped", e); // 包装相同类型
}
}
void doWork() {
throw new IllegalArgumentException();
}
}
// 162 AvoidMultipleUnaryOperators
class MultiUnaryDemo {
void bad() {
int x = 5;
x = -x; // 多个一元运算符
System.out.println(x);
}
}
// ---------- Error Prone 剩余规则补齐 ----------
// 153 AvoidBranchingStatementAsLastInLoop
class BranchLastInLoop {
void foo() {
for (int i = 0; i < 10; i++) {
System.out.println(i);
if (i > 25) {
continue;
}
break; // 分支语句作为循环最后语句
}
}
}
// 162 AvoidMultipleUnaryOperators重命名避免与上文 MultiUnary 冲突
class MultiUnaryRemain {
void foo() {
int i = - -1; // 多个一元运算符
boolean b = !!true;
System.out.println(i + " " + b);
}
}
// 164 BrokenNullCheck
class BrokenNullCheck {
public String bar(String string) {
if (string != null || !string.equals("")) { // &&
return string;
}
if (string == null && string.equals("")) { // ||
return string;
}
return null;
}
}
// ---------- Clone克隆 ----------
// 170 CloneMethodMustImplementCloneableclone() 但未实现 Cloneable
class CloneNoInterface {
@Override
public Object clone() { // 未实现 Cloneable
return new CloneNoInterface();
}
}
// 169 CloneMethodMustBePublic实现 Cloneable clone public
class CloneNotPublic implements Cloneable {
@Override
protected Object clone() { // public
try {
return super.clone();
} catch (CloneNotSupportedException e) {
return null;
}
}
}
// 171 CloneMethodReturnTypeMustMatchClassName
class CloneWrongType implements Cloneable {
@Override
public Object clone() { // 返回类型非 CloneWrongType
try {
return super.clone();
} catch (CloneNotSupportedException e) {
return null;
}
}
}
// 212 ProperCloneImplementation
class CloneProperImplements implements Cloneable {
@Override
public Object clone() throws CloneNotSupportedException { // 未调用 super.clone()
return new CloneProperImplements();
}
}
// ---------- Finalize终结方法 ----------
// 186 EmptyFinalizer finalize
class EmptyFinal {
@Override
protected void finalize() { // finalize
}
}
// 188 FinalizeDoesNotCallSuperFinalize
class NoSuperFinal {
@Override
protected void finalize() {
cleanup(); // 未调用 super.finalize()
}
void cleanup() {
}
}
// 189 FinalizeOnlyCallsSuperFinalize
class OnlySuperFinal {
@Override
protected void finalize() throws Throwable {
super.finalize(); // 只调用 super.finalize()
}
}
// 190 FinalizeOverloaded
class OverloadedFinal {
protected void finalize() throws Throwable {
super.finalize();
}
protected void finalize(int x) { // 重载 finalize
}
}
// 191 FinalizeShouldBeProtected
class PublicFinal {
@Override
public void finalize() throws Throwable { // 应为 protected
super.finalize();
}
}
// ---------- ProperLogger / UseCorrectExceptionLogging ----------
// 213 ProperLoggerlogger 应为 static final
class BadLogger {
protected Log LOG = LogFactory.getLog(BadLogger.class); // static final
}
// 233 UseCorrectExceptionLogging
class CorrectExceptionLog {
private static final Log _LOG = LogFactory.getLog(CorrectExceptionLog.class);
void bar() {
try {
doWork();
} catch (Exception e) {
_LOG.error(e); // 错误直接传异常参数
}
}
void doWork() {
}
}
// ---------- Serialization序列化 ----------
// 203 MissingSerialVersionUID缺少 serialVersionUID
class MissingSerial implements Serializable { // 实现 Serializable serialVersionUID
private int value;
}
// 207 NonSerializableClassSerializable 类字段不可序列化
class NonSerField implements Serializable {
private static final long serialVersionUID = 1L;
private Object nonSerializableField = new Object(); // Serializable 字段
}
// ---------- Singleton单例 ----------
// 219 SingleMethodSingleton单例只有 getInstance 方法
class SingleMethod {
private static SingleMethod INSTANCE = new SingleMethod();
private SingleMethod() {
}
public static SingleMethod getInstance() { // 单例仅此一法
return INSTANCE;
}
}
// 220 SingletonClassReturningNewInstance
class SingletonNew {
private static SingletonNew INSTANCE;
private SingletonNew() {
}
public static SingletonNew getInstance() {
if (INSTANCE == null) {
INSTANCE = new SingletonNew();
}
return INSTANCE; // 正确模式但被误报? 占位
}
}
class BadDeEpMain {
public static void main(String[] args) {
System.out.println("demo designep");
}
}
@@ -0,0 +1,38 @@
// ============================================================
// PMD 演示样例 Accessor访问器生成
// AccessorClassGeneration / AccessorMethodGeneration 已从内置 ruleset
// 排除maximumLanguageVersion=10默认语言版本下永不执行
// 以下代码仅作为其他规则UnnecessaryImport / UnusedLocalVariable 的触发材料
// ============================================================
package com.demo.errorprone.extra;
import java.util.ArrayList;
import java.util.List;
// AccessorClassGeneration避免从外部通过私有构造函数实例化
class AccessorClass {
private AccessorClass() {
} // 私有构造函数
static AccessorClass create() {
return new AccessorClass();
}
}
class AccessorUser {
void bad() {
// 通过外部访问私有构造函数触发合成访问器
AccessorClass a = AccessorClass.create();
CallerGen cg = new CallerGen();
String s = cg.getHidden(); // 访问私有字段
}
}
// AccessorMethodGeneration避免合成访问器方法
class CallerGen {
private String hidden = "x";
String getHidden() {
return hidden;
}
}
@@ -0,0 +1,29 @@
// ============================================================
// PMD 演示样例 UnitTestShouldUseAfter/BeforeAnnotation
// tearDown() / setUp() 方法缺少注解JUnit 3 升级提示
// ============================================================
package com.demo.errorprone.extra;
// 44 UnitTestShouldUseAfterAnnotationtearDown @After 注解
public class MissingAfterTest {
public void tearDown() { // 应加 @After
}
}
// 45 UnitTestShouldUseBeforeAnnotationsetUp @Before 注解
public class MissingBeforeTest {
public void setUp() { // 应加 @Before
}
}
// 46 UnitTestShouldUseTestAnnotationtest 方法无 @Test 注解
public class MissingTestAnn {
public void testSomething() { // 应加 @Test
}
}
class AfterBeforeMain {
public static void main(String[] args) {
System.out.println("demo after/before");
}
}
@@ -0,0 +1,29 @@
// ============================================================
// PMD 演示样例 AssertStatementInTest / JUnitUseExpected
// ============================================================
package com.demo.errorprone.extra;
import org.junit.Test;
// 5 AssertStatementInTest测试中使用 assert 语句
public class AssertStmtTest {
@Test
public void testSomething() {
int x = 1;
assert x == 1; // 应使用 Assert.assertEquals
}
}
// 25 JUnitUseExpected使用 @Test(expected)
class JUnitExpectedTest {
@Test(expected = ArithmeticException.class) // 应使用 assertThrows
public void testExpected() {
int x = 1 / 0;
}
}
class AssertMain {
public static void main(String[] args) {
System.out.println("demo assert stmt");
}
}
@@ -0,0 +1,36 @@
// ============================================================
// PMD 演示样例 CallSuperFirst / CallSuperLast
// 使用 android.app.Activity 生命周期方法
// ============================================================
package com.demo.errorprone.extra;
import android.app.Activity;
import android.os.Bundle;
// 165 CallSuperFirstonCreate 应首先调用 super.onCreate
class MissingSuperFirst extends Activity {
@Override
protected void onCreate(Bundle bundle) {
foo(); // 未先调用 super.onCreate
}
void foo() {
}
}
// 166 CallSuperLastonPause 应最后调用 super.onPause
class MissingSuperLast extends Activity {
@Override
protected void onPause() {
foo(); // 未最后调用 super.onPause
}
void foo() {
}
}
class CallSuperMain {
public static void main(String[] args) {
System.out.println("demo call super");
}
}
@@ -0,0 +1,32 @@
// ============================================================
// PMD 演示样例 JUnit 3 规则TestCase 继承
// ============================================================
package com.demo.errorprone.extra;
import junit.framework.TestCase;
// 199 JUnitSpellingsetup/TearDown 拼写错误
public class JUnitSpellingTest extends TestCase {
public void setup() { // setUp
}
public void TearDown() { // tearDown
}
}
// 200 JUnitStaticSuitesuite 方法应为 static
class JUnitStaticSuiteTest extends TestCase {
public void suite() { // static
}
}
// 178 DetachedTestCase独立测试方法无 @Test
class DetachedTest extends TestCase {
public void testSomething() { // 独立测试方法
}
}
class Junit3Main {
public static void main(String[] args) {
System.out.println("demo junit3");
}
}
@@ -0,0 +1,113 @@
// ============================================================
// PMD 演示样例 JUnit 4 测试规则
// ============================================================
package com.demo.errorprone.extra;
import org.junit.Test;
import org.junit.Assert;
public class JUnit4RulesTest {
// 39 SimplifiableTestAssertion
@Test
public void testSimplifiable() {
Object a = new Object();
Object b = new Object();
Assert.assertTrue(a.equals(b)); // assertEquals
}
// 41 UnitTestAssertionsShouldIncludeMessage
@Test
public void testNoMessage() {
Assert.assertEquals("foo", "bar"); // 应带消息三参版本
}
// 42 UnitTestContainsTooManyAsserts
@Test
public void testTooMany() {
Assert.assertTrue(true);
Assert.assertTrue(true);
Assert.assertTrue(true);
Assert.assertTrue(true);
Assert.assertEquals(1, 1);
Assert.assertEquals(2, 2);
Assert.assertEquals(3, 3);
Assert.assertEquals(4, 4);
Assert.assertEquals(5, 5);
}
// 148 AssertEqualsArgumentOrder参数顺序颠倒
@Test
public void testArgOrder() {
String actual = "actual";
String expected = "expected";
Assert.assertEquals(actual, expected); // 顺序颠倒
}
// 228 UnnecessaryBooleanAssertion
@Test
public void testBoolean() {
Assert.assertTrue(true); // 无意义断言
}
// 25 JUnitUseExpected应使用 @Test(expected)
@Test
public void testExpected() {
try {
doSomething();
Assert.fail("should have thrown");
} catch (NullPointerException e) {
// 应使用 @Test(expected = NullPointerException.class)
}
}
private void doSomething() {
throw new NullPointerException();
}
}
// 44 UnitTestShouldUseAfterAnnotationtearDown @After
class MissingAfterTest {
@Test
public void testCleanup() {
Assert.assertTrue(true);
}
public void tearDown() { // 应加 @After
}
}
// 45 UnitTestShouldUseBeforeAnnotationsetUp @Before
class MissingBeforeTest {
@Test
public void testSetup() {
Assert.assertTrue(true);
}
public void setUp() { // 应加 @Before
}
}
// 46 UnitTestShouldUseTestAnnotationtest 方法无 @Test
class MissingTestAnnotationTest {
public void testSomething() { // 应加 @Test
}
}
// 199 JUnitSpelling方法名拼写
class SpellingTest {
public void setup() { // setUp
}
public void teardown() { // tearDown
}
}
// 200 JUnitStaticSuite
class StaticSuiteTest {
public static junit.framework.Test suite() { // 静态 suite
return null;
}
}
// 23 JUnit4SuitesShouldUseSuiteAnnotation
class SuiteClassTest {
public static junit.framework.Test suite() { // @RunWith(Suite.class)
return null;
}
}
@@ -0,0 +1,26 @@
// ============================================================
// PMD 演示样例 JUnit 5 assertEquals 参数顺序
// ============================================================
package com.demo.errorprone.extra;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
// 148 AssertEqualsArgumentOrderexpected/actual 顺序颠倒
public class JUnit5ArgOrderTest {
@Test
public void testX() {
String actual = next("foo");
assertEquals(actual, "bar"); // 顺序颠倒actual 在前字面量在后
}
String next(String s) {
return s;
}
}
class Junit5Main {
public static void main(String[] args) {
System.out.println("demo junit5 arg order");
}
}
@@ -0,0 +1,27 @@
// ============================================================
// PMD 演示样例 JUnit 5 (Jupiter) 规则
// ============================================================
package com.demo.errorprone.extra;
import org.junit.jupiter.api.Test;
// 24 JUnitJupiterTestShouldBePackagePrivateJUnit5 测试应为包私有
public class JupiterPublicTest {
@Test
public void testPublic() { // public 测试方法
}
}
// 198 JUnitJupiterTestNoPrivateModifierJUnit5 测试不应为 private
class JupiterPrivateTest {
@Test
private void testPrivate() { // private 测试方法不会执行
}
}
// 25 补充JUnitUseExpected 已在 JUnit4 文件
class JupiterMain {
public static void main(String[] args) {
System.out.println("demo jupiter");
}
}
@@ -0,0 +1,32 @@
// ============================================================
// PMD 演示样例 ProperLogger / UseCorrectExceptionLogging
// ============================================================
package com.demo.errorprone.extra;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
// 213 ProperLoggerlogger 应为 static final
public class BadLogger {
protected Log LOG = LogFactory.getLog(BadLogger.class); // static final
}
// 233 UseCorrectExceptionLogging
class CorrectExceptionLog {
private static final Log _LOG = LogFactory.getLog(CorrectExceptionLog.class);
void bar() {
try {
doWork();
} catch (Exception e) {
_LOG.error(e); // 错误直接传异常参数
}
}
void doWork() {
}
}
class LoggerMain {
public static void main(String[] args) {
System.out.println("demo logger");
}
}
@@ -0,0 +1,22 @@
// ============================================================
// PMD 演示样例 StaticEJBFieldShouldBeFinalEJB 静态字段应为 final
// ============================================================
package com.demo.errorprone.extra;
import javax.ejb.EJBObject;
import javax.ejb.EJBLocalHome;
// 221 StaticEJBFieldShouldBeFinal
public class SomeEJB extends EJBObject implements EJBLocalHome {
private static int CountA; // 可写静态字段违例
int CountC;
public void work() {
CountA++;
}
}
class EJBStaticMain {
public static void main(String[] args) {
System.out.println("demo ejb static");
}
}
@@ -0,0 +1,384 @@
// ============================================================
// PMD 演示样例 MTPs多线程 / 性能 / 安全合并版
// 合并自MultiThreadDemo / MultiThreadFix / PerformanceDemo /
// PerformanceFix / SecurityDemo / SecurityFix
// 保留所有规则触发类及其规则编号注释未修复任何错误
// ============================================================
package com.demo.mtps;
import java.text.SimpleDateFormat;
import java.util.HashMap;
import java.util.Map;
import java.util.Hashtable;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
// ==================================================================
// Multithreading多线程
// ==================================================================
// 240 AvoidSynchronizedAtMethodLevel方法级 synchronized
class SyncMethod {
private int count;
public synchronized void incr() { // 方法级 synchronized
count++;
}
}
// 241 AvoidThreadGroup使用 ThreadGroup
class ThreadGroupUse {
void bad() {
ThreadGroup g = new ThreadGroup("group"); // ThreadGroup
Thread t = new Thread(g, () -> {});
}
}
// 242 DontCallThreadRun调用 Thread.run()
class CallRun {
void bad() {
Thread t = new Thread();
t.run(); // 调用 run() 而非 start()
}
}
// 243 DoubleCheckedLocking
class DCL {
private static Object instance;
static Object get() {
if (instance == null) {
synchronized (DCL.class) {
if (instance == null) {
instance = new Object();
}
}
}
return instance;
}
}
// 244 NonThreadSafeSingleton
class NTSingleton {
private static NTSingleton instance;
static NTSingleton get() {
if (instance == null) {
instance = new NTSingleton();
}
return instance;
}
}
// 245 OverridingThreadRun重写 Thread.run()
class MyThread extends Thread {
@Override
public void run() { // 重写 run()
}
}
// 246 UnsynchronizedStaticFormatter
class StaticFormatter {
private static final SimpleDateFormat SDF = new SimpleDateFormat("yyyy"); // 静态 formatter
}
// 247 UseConcurrentHashMap
class ConcMap {
Map<String, String> map = new HashMap<>(); // 多线程下应使用 ConcurrentHashMap
}
// 248 UseNotifyAllInsteadOfNotify
class NotifyUse {
void bad() {
Object lock = new Object();
synchronized (lock) {
lock.notify(); // notifyAll
}
}
}
// 239 UnsynchronizedStaticFormatter
class StaticFormatterDemo {
private static final SimpleDateFormat sdf = new SimpleDateFormat("yyyy");
void bar() {
sdf.format(new java.util.Date()); // 静态 formatter 未同步
}
}
// ==================================================================
// Performance性能
// ==================================================================
// 249 AddEmptyString
class AddEmpty {
String bad(int x) {
return "" + x; // String.valueOf
}
}
// 250 AppendCharacterWithChar
class AppendChar {
void bad(StringBuffer sb) {
sb.append("a"); // append('a')
}
}
// 251 AvoidArrayLoops
class ArrayLoops {
void bad(int[] src, int[] dst) {
for (int i = 0; i < src.length; i++) {
dst[i] = src[i]; // System.arraycopy
}
}
}
// 252 AvoidCalendarDateCreation
class CalDate {
void bad() {
Calendar c = Calendar.getInstance();
c.set(2020, 1, 1); // CreatorX
c.getTime().getTime(); // 占位
}
}
// 253 AvoidFileStream
class FileStream {
void bad() throws IOException {
FileInputStream fis = new FileInputStream("f.bin"); // Files.newInputStream
fis.read();
fis.close();
}
}
// 254 AvoidInstantiatingObjectsInLoops
class LoopObj {
void bad() {
for (int i = 0; i < 100; i++) {
Object o = new Object(); // 循环内实例化
}
}
}
// 255 BigIntegerInstantiation
class BigInt {
BigInteger b = new BigInteger("10"); // BigInteger.TEN
}
// 256 ConsecutiveAppendsShouldReuse
class ConsecAppend {
void bad(StringBuilder sb) {
sb.append("hello");
sb.append(" "); // 应合并
sb.append("world");
}
}
// 257 ConsecutiveLiteralAppends
class ConsecLiteral {
void bad(StringBuilder sb) {
sb.append("hello " + "world"); // 应单次 append
}
}
// 258 InefficientEmptyStringCheck
class InefficientEmpty {
boolean bad(String s) {
return s.equals(""); // isEmpty()
}
}
// 259 InefficientStringBuffering
class InefficientBuf {
String bad(int a, int b) {
String s = "value: " + a + " and " + b; // 应使用 StringBuilder
return s;
}
}
// 260 InsufficientStringBufferDeclaration
class InsufficientBuf {
void bad(String s) {
StringBuffer sb = new StringBuffer(); // 应指定容量
sb.append(s);
}
}
// 261 OptimizableToArrayCall
class OptToArray {
void bad(List<String> l) {
String[] arr = l.toArray(new String[l.size()]); // (String[]) l.toArray()
}
}
// 262 RedundantFieldInitializer
class RedundantInit {
int x = 0; // 冗余初始化
boolean b = false; // 冗余初始化
}
// 263 StringInstantiation
class StringInst {
void bad(String s) {
String t = new String(s); // 应直接引用
}
}
// 264 StringToString
class ToString {
void bad(String s) {
String t = s.toString(); // 冗余 toString
}
}
// 266 UseArrayListInsteadOfVector
class UseArrayList {
java.util.Vector<String> v = new java.util.Vector<>(); // ArrayList
}
// 267 UseArraysAsList
class UseArraysAsList {
void bad(String[] arr) {
List<String> l = new ArrayList<>();
for (String s : arr) {
l.add(s); // Arrays.asList
}
}
}
// 268 UseIndexOfChar
class UseIndexOfChar {
int bad(String s) {
return s.indexOf("x"); // indexOf('x')
}
}
// 269 UseStringBufferForStringAppends
class UseStringBuffer {
void bad(String s) {
s += "appended"; // StringBuilder
}
}
// 270 UseStringBufferLength
class UseBufLength {
int bad(StringBuffer sb) {
return sb.toString().length(); // sb.length()
}
}
// 271 UselessStringValueOf
class UselessValueOf {
String bad(String s) {
return String.valueOf(s); // 冗余 valueOf
}
}
// 252 AvoidCalendarDateCreation
class CalendarDateDemo {
private Date bad1() {
return Calendar.getInstance().getTime(); // new Date()
}
private long bad2() {
return Calendar.getInstance().getTimeInMillis(); // System.currentTimeMillis()
}
}
// 258 InefficientEmptyStringCheck
class InefficientEmptyCheck {
void bar(String string) {
if (string != null && string.trim().length() > 0) { // isEmpty 优化
System.out.println("non-empty");
}
}
}
// 259 InefficientStringBuffering
class InefficientBufDemo {
String bad() {
StringBuffer sb = new StringBuffer("tmp = " + System.getProperty("java.io.tmpdir")); // 双重缓冲
return sb.toString();
}
}
// 260 InsufficientStringBufferDeclaration
class InsufficientBufDemo {
String bad(String s) {
StringBuilder sb = new StringBuilder();
sb.append("This is a long string that will exceed the default 16 characters");
return sb.toString();
}
}
// 269 UseStringBufferForStringAppends
class InefficientConcat {
String bad() {
String result = "";
for (int i = 0; i < 10; i++) {
result += getString(i); // 应使用 StringBuilder
}
return result;
}
String getString(int i) {
return "s" + i;
}
}
// 271 UselessStringValueOf
class UselessValueOfDemo {
public String convert(int i) {
String s;
s = "a" + String.valueOf(i); // 应直接 "a" + i
return s;
}
}
// ==================================================================
// Security安全
// ==================================================================
// 272 HardCodedCryptoKey硬编码密钥
class HardKey {
void bad() {
byte[] key = "0123456789abcdef".getBytes(); // 硬编码密钥
new SecretKeySpec(key, "AES");
}
}
// 273 InsecureCryptoIv不安全 IV
class InsecureIv {
void bad() {
byte[] iv = new byte[16]; // 全零 IV
new IvParameterSpec(iv);
}
}
// 273 InsecureCryptoIv
class InsecureIvDemo {
void bad() {
byte[] iv = new byte[]{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,}; // 全零 IV
new IvParameterSpec(iv);
}
void alsoBad() {
byte[] iv = "secret iv in here".getBytes(); // 固定 IV
new IvParameterSpec(iv);
}
}
// ==================================================================
// 主类
// ==================================================================
class BadMtpsMain {
public static void main(String[] args) {
System.out.println("demo mtps");
}
}
@@ -0,0 +1,27 @@
// ============================================================
// PMD 演示样例 UseIOStreamsWithApacheCommonsFileItem
// 触发条件调用 FileItem.get() FileItem.getString()
// 需要 commons-fileupload aux classpath 以完成类型解析
// ============================================================
package com.demo.performance.extra;
import org.apache.commons.fileupload.FileItem;
// 268 UseIOStreamsWithApacheCommonsFileItem
class UseIOStreamFileItem {
void process(FileItem item) {
byte[] data = item.get(); // 应使用 getInputStream()
System.out.println(data.length);
}
void processString(FileItem item) {
String s = item.getString(); // 应使用 getInputStream()
System.out.println(s);
}
}
class PerfFileItemMain {
public static void main(String[] args) {
System.out.println("demo fileitem");
}
}
+47
View File
@@ -0,0 +1,47 @@
<%@ page contentType="text/html; charset=UTF-8" %>
<%-- ============================================================
PMD 演示样例 — JSP 规则
============================================================ --%>
<html>
<head>
<title>JSP Demo</title>
</head>
<body>
<%-- NoScriptlets:使用 scriptlet --%>
<%
String name = request.getParameter("name");
out.println("Hello " + name);
%>
<%-- NoInlineScript:内联脚本 --%>
<script type="text/javascript">
function init() {
var x = 1;
alert(x);
}
</script>
<%-- NoInlineStyleInformation:内联样式 --%>
<p align="center"><b>Bold inline style</b> <font color="red">Font inline style</font></p>
<div style="color: red;">Inline style</div>
<%-- NoHtmlCommentsHTML 注释太少,占位 --%>
<p>Content</p>
<%-- DontNestJsfInJstlIteration:占位 --%>
<c:forEach items="${items}" var="item">
<h:outputText value="${item}"/>
</c:forEach>
<%-- NoClassAttribute:占位 --%>
<p class="highlight">Class attribute</p>
<%-- NoJspForward:占位 --%>
<%-- DuplicateJspImports:重复导入 --%>
<%@ page import="java.util.List" %>
<%@ page import="java.util.List" %>
<%-- JspEncoding:页面编码 --%>
<p>End</p>
</body>
</html>
+64
View File
@@ -0,0 +1,64 @@
<%--
"pageEncoding" deliberately omitted to trigger JspEncoding
--%>
<%@ page contentType="text/html" %>
<html>
<head><title>JSP Fix Demo</title>
<%-- NoInlineScript:内联脚本 --%>
<script type="text/javascript">
function doStuff() {
var total = 0;
for (var i = 0; i < 10; i++) {
total += i;
}
return total;
}
function doMore() {
return doStuff();
}
</script>
</head>
<body>
<%-- NoHtmlCommentsHTML 注释(非 JSP 注释)--%>
<!-- this is an html comment -->
<div style="background-color: #f00;">inline style</div>
<%-- NoScriptletsscriptlet --%>
<%
int x = 1;
int y = 2;
int z = x + y;
out.println(z);
%>
<%-- NoJspForward:转发操作 --%>
<jsp:forward page="other.jsp"/>
<%-- NoLongScripts:长 scriptlet --%>
<%
int a = 1;
int b = 2;
int c = 3;
int d = 4;
int e = 5;
int f = 6;
int g = 7;
int h = 8;
int i2 = 9;
int j2 = 10;
int k = 11;
int l = 12;
int m = 13;
int n = 14;
int o = 15;
int p = 16;
int q = 17;
int r = 18;
int s2 = 19;
int t = 20;
int u = 21;
int v = 22;
int w = 23;
int xx = 24;
int yy = 25;
out.println(xx + yy);
%>
</body>
</html>
+23
View File
@@ -0,0 +1,23 @@
<%@ page contentType="text/html; charset=UTF-8" %>
<%-- ============================================================
PMD 演示样例 — JSP security 分类规则
(对应 pmd-jsp-ruleset.xml 的 category/jsp/security.xml
============================================================ --%>
<html>
<head>
<title>JSP Security Demo</title>
</head>
<body>
<%-- IframeMissingSrcAttributeiframe 缺少 src 属性 --%>
<iframe></iframe>
<iframe width="300" height="200"></iframe>
<%-- NoUnsanitizedJSPExpression:未转义的 EL 表达式直接输出,存在 XSS 风险 --%>
<p>Hello, ${userInput}</p>
<p>Search: ${searchTerm}</p>
<%-- 对照组:正确写法(转义 / 带 src 的 iframe),不应触发规则 --%>
<p>Safe: <c:out value="${userInput}" /></p>
<iframe src="https://example.com/embed"></iframe>
</body>
</html>
@@ -0,0 +1,10 @@
// ============================================================
// PMD 演示样例 NoPackage所有类型必须属于命名包
// 本文件故意没有 package 声明
// ============================================================
class NoPackageClass {
void m() {
System.out.println("no package");
}
}
@@ -0,0 +1,32 @@
// ============================================================
// PMD 演示样例 Design 剩余规则补齐
// ============================================================
package org.example.beans;
// 133 InvalidJavaBeanBean 非法不可序列化 setter
public class MyBean {
private String label; // setter
public String getLabel() {
return label;
}
}
// 121 AvoidThrowingNewInstanceOfSameException
class WrapException {
void bar() {
try {
doWork();
} catch (IllegalStateException se) {
throw new IllegalStateException(se); // 包装相同异常类型
}
}
void doWork() {
}
}
class BeansMain {
public static void main(String[] args) {
System.out.println("demo beans");
}
}
@@ -137,12 +137,6 @@ const endPos = (v.end_line_pos ?? v.start_line_pos) - 1;
3. ✅ 插件缺陷闭环:JJ01 行号 LNaN → 修复 → 复测验证 L2(发现、定位、修复、验证全流程完成)
4. ✅ 「内置配置回退」路径验证通过(工程无 `.sqlfluff` 配置文件场景)
**后续建议**
- ~~插件侧修复 JJ01~~(✅ 已完成并验证)
- **其他工程**`demo-pmd` 按同样方法实测(先本地基线复现 → 实测逐条比对),注意 Java 版本与 ruleset
- **可选**:若需验证「工程配置优先」路径,可在工程根添加 `.sqlfluff` 配置文件复测(预期插件改用工程配置)
---
*报告生成于 2026-08-25 · demo-sqlfluff 插件实测 · 比对脚本:`sqlfluff-final-compare.mjs` · 数据:`sqlfluff-final-compare.json`*
@@ -113,17 +113,6 @@
3. ✅ 「内置配置回退」路径完整验证(工程无 `.stylelintrc` 场景)
4. ✅ 零缺陷(对照 SQLFlutt 发现的 JJ01 NaN 问题,Stylelint 适配器的 endLine 回退写法正确)
**实测进度总览**(四个 demo 工程):
| 工程 | 实测状态 | 结果 |
|---|---|---|
| demo-eslint | ✅ 完成 | 126/126 种 · 292/292 条逐条一致 |
| demo-sqlfluff | ✅ 完成 | 57/57 种 · 113 处(112 条零偏差 + JJ01 行号显示缺陷) |
| demo-stylelint | ✅ 完成 | **68/68 种 · 145/145 条逐条零偏差** |
| demo-pmd | ⏳ 待实测 | 预期 281 条规则(Java 269 + JSP 12),注意 Java 版本与 ruleset |
**后续建议**:仅剩 `demo-pmd` 待实测——测试机需 JDK 8+(插件用 PmdRunner.class 直跑,无沙箱 JDK 11 限制),打开 Java/JSP 样例文件后与预期基线比对即可,全套四工程验证收官。
---
*报告生成于 2026-08-25 · demo-stylelint 插件实测 · 比对脚本:`stylelint-compare.mjs` · 数据:`stylelint-final-compare.json`*
+17 -1
View File
@@ -157,6 +157,13 @@ export class AuxClasspathResolver {
return systemName;
}
private quoteCmdArg(arg: string): string {
if (/^[\w.,:=/@%\\+-]+$/.test(arg)) {
return arg;
}
return `"${arg.replace(/"/g, '""')}"`;
}
private lastNonEmptyLine(output: string): string {
const lines = output.split(/\r?\n/).map((line) => line.trim()).filter((line) => line.length > 0);
return lines.length > 0 ? lines[lines.length - 1] : '';
@@ -174,7 +181,16 @@ export class AuxClasspathResolver {
reject(new Error(`Command timed out after ${BUILD_TIMEOUT}ms: ${cmd}`));
}, BUILD_TIMEOUT);
try {
proc = spawn(cmd, args, { cwd, windowsHide: true });
if (isWindows() && /\.(cmd|bat)$/i.test(cmd)) {
const inner = `${this.quoteCmdArg(cmd)} ${args.map((arg) => this.quoteCmdArg(arg)).join(' ')}`;
proc = spawn('cmd.exe', ['/d', '/s', '/c', `"${inner}"`], {
cwd,
windowsHide: true,
windowsVerbatimArguments: true,
});
} else {
proc = spawn(cmd, args, { cwd, windowsHide: true });
}
} catch (err) {
clearTimeout(timer);
reject(err);