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:
@@ -6,3 +6,4 @@ dist/
|
||||
.env
|
||||
*.log
|
||||
.superpowers/
|
||||
data/demo-pmd/target/
|
||||
|
||||
@@ -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→269;src/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.json(rootDir ".",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.md;git 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 异步 fn,result 为 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`*
|
||||
|
||||
BIN
Binary file not shown.
@@ -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
|
||||
Vendored
+8
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"java.project.referencedLibraries": [
|
||||
"lib/**/*.jar"
|
||||
],
|
||||
"java.project.sourcePaths": [
|
||||
"src"
|
||||
]
|
||||
}
|
||||
@@ -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.
Vendored
+138
@@ -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
|
||||
Vendored
+193
@@ -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%
|
||||
@@ -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.0,Java 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/269(100%,7/7 类满覆盖)+ JSP 10/10(100%)= **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=10(Java 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/ChildFix、ParentRemain/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 AvoidMessageDigestField:MessageDigest 作为字段(线程不安全)
|
||||
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 AvoidStringBufferField:StringBuilder 字段
|
||||
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 AvoidPrintStackTrace:printStackTrace
|
||||
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 ForLoopVariableCount:for 循环多个控制变量
|
||||
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 NonExhaustiveSwitch:switch 应为穷尽式
|
||||
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 PrimitiveWrapperInstantiation:new 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 UseCollectionIsEmpty:size()==0
|
||||
class ColEmpty {
|
||||
void bad(List<String> l) {
|
||||
if (l.size() == 0) { // 应使用 isEmpty()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 57 UseEnumCollections:EnumSet/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 AvoidProtectedFieldInFinalClass:final 类 protected 字段
|
||||
final class FinalClass {
|
||||
protected int x;
|
||||
}
|
||||
|
||||
// 64 AvoidProtectedMethodInFinalClassNotExtending:final 类 protected 方法
|
||||
final class FinalClass2 {
|
||||
protected void m() {
|
||||
}
|
||||
}
|
||||
|
||||
// 65 AvoidUsingNativeCode:JNI
|
||||
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 LocalHomeNamingConvention:LocalHome 后缀
|
||||
public interface MissingLocalHomeSuffixBean extends EJBLocalHome { // 应 ...LocalHome
|
||||
}
|
||||
|
||||
// 84 LocalInterfaceSessionNamingConvention:Local 后缀
|
||||
public interface MissingLocalSuffixBean extends EJBLocalObject { // 应 ...Local
|
||||
}
|
||||
|
||||
// 87 MDBAndSessionBeanNamingConvention:Bean 后缀
|
||||
public class MissingBeanSuffix implements SessionBean { // 应 ...Bean
|
||||
}
|
||||
|
||||
// 95 RemoteInterfaceNamingConvention:远程接口不应带后缀
|
||||
public interface BadSuffixSession extends EJBObject { // 不应 Session 后缀
|
||||
}
|
||||
|
||||
// 96 RemoteSessionInterfaceNamingConvention:Home 后缀
|
||||
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 AvoidUncheckedExceptionsInSignatures:throws 非受检异常
|
||||
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 FinalFieldCouldBeStatic:final 字段可为 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 InvalidJavaBean:Bean 不合规
|
||||
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 SwitchDensity:switch 密度过高(保留 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 SwitchDensity:switch 密度过高
|
||||
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 可能为 null,null 检查位置错误
|
||||
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 AvoidAccessibilityAlteration:setAccessible(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 AvoidLiteralsInIfCondition:if 中魔术数字
|
||||
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 CallSuperFirst:super 应首先调用
|
||||
class CallSuperFirst {
|
||||
void bad() {
|
||||
doSomething();
|
||||
super.toString(); // super 未首先调用? 占位
|
||||
}
|
||||
void doSomething() {
|
||||
}
|
||||
}
|
||||
|
||||
// 166 CallSuperLast:super 应最后调用
|
||||
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 IdempotentOperations:x = 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 CloneMethodMustImplementCloneable:clone() 但未实现 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 ProperLogger:logger 应为 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 NonSerializableClass:Serializable 类字段不可序列化
|
||||
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 UnitTestShouldUseAfterAnnotation:tearDown 无 @After 注解
|
||||
public class MissingAfterTest {
|
||||
public void tearDown() { // 应加 @After
|
||||
}
|
||||
}
|
||||
|
||||
// 45 UnitTestShouldUseBeforeAnnotation:setUp 无 @Before 注解
|
||||
public class MissingBeforeTest {
|
||||
public void setUp() { // 应加 @Before
|
||||
}
|
||||
}
|
||||
|
||||
// 46 UnitTestShouldUseTestAnnotation:test 方法无 @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 CallSuperFirst:onCreate 应首先调用 super.onCreate
|
||||
class MissingSuperFirst extends Activity {
|
||||
@Override
|
||||
protected void onCreate(Bundle bundle) {
|
||||
foo(); // 未先调用 super.onCreate
|
||||
}
|
||||
|
||||
void foo() {
|
||||
}
|
||||
}
|
||||
|
||||
// 166 CallSuperLast:onPause 应最后调用 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 JUnitSpelling:setup/TearDown 拼写错误
|
||||
public class JUnitSpellingTest extends TestCase {
|
||||
public void setup() { // 应 setUp
|
||||
}
|
||||
public void TearDown() { // 应 tearDown
|
||||
}
|
||||
}
|
||||
|
||||
// 200 JUnitStaticSuite:suite 方法应为 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 UnitTestShouldUseAfterAnnotation:tearDown 无 @After
|
||||
class MissingAfterTest {
|
||||
@Test
|
||||
public void testCleanup() {
|
||||
Assert.assertTrue(true);
|
||||
}
|
||||
public void tearDown() { // 应加 @After
|
||||
}
|
||||
}
|
||||
|
||||
// 45 UnitTestShouldUseBeforeAnnotation:setUp 无 @Before
|
||||
class MissingBeforeTest {
|
||||
@Test
|
||||
public void testSetup() {
|
||||
Assert.assertTrue(true);
|
||||
}
|
||||
public void setUp() { // 应加 @Before
|
||||
}
|
||||
}
|
||||
|
||||
// 46 UnitTestShouldUseTestAnnotation:test 方法无 @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 AssertEqualsArgumentOrder:expected/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 JUnitJupiterTestShouldBePackagePrivate:JUnit5 测试应为包私有
|
||||
public class JupiterPublicTest {
|
||||
@Test
|
||||
public void testPublic() { // public 测试方法
|
||||
}
|
||||
}
|
||||
|
||||
// 198 JUnitJupiterTestNoPrivateModifier:JUnit5 测试不应为 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 ProperLogger:logger 应为 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 演示样例 — StaticEJBFieldShouldBeFinal:EJB 静态字段应为 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");
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
<%-- NoHtmlComments:HTML 注释太少,占位 --%>
|
||||
<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>
|
||||
@@ -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>
|
||||
<%-- NoHtmlComments:HTML 注释(非 JSP 注释)--%>
|
||||
<!-- this is an html comment -->
|
||||
<div style="background-color: #f00;">inline style</div>
|
||||
<%-- NoScriptlets:scriptlet --%>
|
||||
<%
|
||||
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>
|
||||
@@ -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>
|
||||
<%-- IframeMissingSrcAttribute:iframe 缺少 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 InvalidJavaBean:Bean 非法(不可序列化、缺 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`*
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user