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
|
.env
|
||||||
*.log
|
*.log
|
||||||
.superpowers/
|
.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-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: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 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 种规则,零解析错误)
|
2. ✅ 内置配置全覆盖达成(126/126 种规则,零解析错误)
|
||||||
3. ✅ 「内置配置回退」路径完整验证(工程无配置文件的场景)
|
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`*
|
*报告生成于 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(发现、定位、修复、验证全流程完成)
|
3. ✅ 插件缺陷闭环:JJ01 行号 LNaN → 修复 → 复测验证 L2(发现、定位、修复、验证全流程完成)
|
||||||
4. ✅ 「内置配置回退」路径验证通过(工程无 `.sqlfluff` 配置文件场景)
|
4. ✅ 「内置配置回退」路径验证通过(工程无 `.sqlfluff` 配置文件场景)
|
||||||
|
|
||||||
**后续建议**:
|
|
||||||
|
|
||||||
- ~~插件侧修复 JJ01~~(✅ 已完成并验证)
|
|
||||||
- **其他工程**:`demo-pmd` 按同样方法实测(先本地基线复现 → 实测逐条比对),注意 Java 版本与 ruleset
|
|
||||||
- **可选**:若需验证「工程配置优先」路径,可在工程根添加 `.sqlfluff` 配置文件复测(预期插件改用工程配置)
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
*报告生成于 2026-08-25 · demo-sqlfluff 插件实测 · 比对脚本:`sqlfluff-final-compare.mjs` · 数据:`sqlfluff-final-compare.json`*
|
*报告生成于 2026-08-25 · demo-sqlfluff 插件实测 · 比对脚本:`sqlfluff-final-compare.mjs` · 数据:`sqlfluff-final-compare.json`*
|
||||||
|
|||||||
@@ -113,17 +113,6 @@
|
|||||||
3. ✅ 「内置配置回退」路径完整验证(工程无 `.stylelintrc` 场景)
|
3. ✅ 「内置配置回退」路径完整验证(工程无 `.stylelintrc` 场景)
|
||||||
4. ✅ 零缺陷(对照 SQLFlutt 发现的 JJ01 NaN 问题,Stylelint 适配器的 endLine 回退写法正确)
|
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`*
|
*报告生成于 2026-08-25 · demo-stylelint 插件实测 · 比对脚本:`stylelint-compare.mjs` · 数据:`stylelint-final-compare.json`*
|
||||||
|
|||||||
@@ -157,6 +157,13 @@ export class AuxClasspathResolver {
|
|||||||
return systemName;
|
return systemName;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private quoteCmdArg(arg: string): string {
|
||||||
|
if (/^[\w.,:=/@%\\+-]+$/.test(arg)) {
|
||||||
|
return arg;
|
||||||
|
}
|
||||||
|
return `"${arg.replace(/"/g, '""')}"`;
|
||||||
|
}
|
||||||
|
|
||||||
private lastNonEmptyLine(output: string): string {
|
private lastNonEmptyLine(output: string): string {
|
||||||
const lines = output.split(/\r?\n/).map((line) => line.trim()).filter((line) => line.length > 0);
|
const lines = output.split(/\r?\n/).map((line) => line.trim()).filter((line) => line.length > 0);
|
||||||
return lines.length > 0 ? lines[lines.length - 1] : '';
|
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}`));
|
reject(new Error(`Command timed out after ${BUILD_TIMEOUT}ms: ${cmd}`));
|
||||||
}, BUILD_TIMEOUT);
|
}, BUILD_TIMEOUT);
|
||||||
try {
|
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) {
|
} catch (err) {
|
||||||
clearTimeout(timer);
|
clearTimeout(timer);
|
||||||
reject(err);
|
reject(err);
|
||||||
|
|||||||
Reference in New Issue
Block a user