forked from phoedos/pmd
Merge branch 'master' into avoid-image
This commit is contained in:
@ -7279,6 +7279,15 @@
|
||||
"bug"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "nakul777",
|
||||
"name": "Nakul Sharma",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/1551545?v=4",
|
||||
"profile": "https://github.com/nakul777",
|
||||
"contributions": [
|
||||
"bug"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "shai-bennathan",
|
||||
"name": "Shai Bennathan",
|
||||
|
47
.ci/build.sh
47
.ci/build.sh
@ -36,7 +36,7 @@ function build() {
|
||||
|
||||
if pmd_ci_utils_is_fork_or_pull_request; then
|
||||
pmd_ci_log_group_start "Build with mvnw"
|
||||
./mvnw clean install -Pcli-dist --show-version --errors --batch-mode "${PMD_MAVEN_EXTRA_OPTS[@]}"
|
||||
./mvnw clean install --show-version --errors --batch-mode "${PMD_MAVEN_EXTRA_OPTS[@]}"
|
||||
pmd_ci_log_group_end
|
||||
|
||||
# Execute danger and dogfood only for pull requests in our own repository
|
||||
@ -70,7 +70,7 @@ function build() {
|
||||
|
||||
if [ "$(pmd_ci_utils_get_os)" != "linux" ]; then
|
||||
pmd_ci_log_group_start "Build with mvnw"
|
||||
./mvnw clean verify -Pcli-dist --show-version --errors --batch-mode "${PMD_MAVEN_EXTRA_OPTS[@]}"
|
||||
./mvnw clean verify --show-version --errors --batch-mode "${PMD_MAVEN_EXTRA_OPTS[@]}"
|
||||
pmd_ci_log_group_end
|
||||
|
||||
pmd_ci_log_info "Stopping build here, because os is not linux"
|
||||
@ -87,7 +87,7 @@ function build() {
|
||||
|
||||
if [ "${PMD_CI_BRANCH}" = "experimental-apex-parser" ]; then
|
||||
pmd_ci_log_group_start "Build with mvnw"
|
||||
./mvnw clean install -Pcli-dist --show-version --errors --batch-mode "${PMD_MAVEN_EXTRA_OPTS[@]}"
|
||||
./mvnw clean install --show-version --errors --batch-mode "${PMD_MAVEN_EXTRA_OPTS[@]}"
|
||||
pmd_ci_log_group_end
|
||||
|
||||
pmd_ci_log_group_start "Creating new baseline for regression tester"
|
||||
@ -107,7 +107,7 @@ function build() {
|
||||
pmd_ci_log_group_end
|
||||
|
||||
# release is published only for the case b) pmd-cli/pmd-dist release
|
||||
if pmd_ci_maven_isReleaseBuild && [[ "${PMD_CI_TAG}" == *-dist ]]; then
|
||||
if pmd_ci_maven_isReleaseBuild && [ "${BUILD_CLI_DIST_ONLY}" = "true" ]; then
|
||||
pmd_ci_log_group_start "Publishing Release"
|
||||
pmd_ci_gh_releases_publishRelease "$GH_RELEASE"
|
||||
pmd_ci_sourceforge_selectDefault "${PMD_CI_MAVEN_PROJECT_VERSION}"
|
||||
@ -117,7 +117,7 @@ function build() {
|
||||
|
||||
# create a baseline for snapshot builds (when pmd-dist is built)
|
||||
# or for release builds for case b) when pmd-cli/pmd-dist is released
|
||||
if pmd_ci_maven_isSnapshotBuild || [[ "${PMD_CI_TAG}" == *-dist ]]; then
|
||||
if pmd_ci_maven_isSnapshotBuild || [ "${BUILD_CLI_DIST_ONLY}" = "true" ]; then
|
||||
pmd_ci_log_group_start "Creating new baseline for regression tester"
|
||||
regression_tester_setup_ci
|
||||
regression_tester_uploadBaseline
|
||||
@ -133,6 +133,7 @@ function build() {
|
||||
pmd_ci_log_group_end
|
||||
|
||||
pmd_ci_log_group_start "Executing build with sonar"
|
||||
pmd_ci_openjdk_setdefault 17
|
||||
# Note: Sonar also needs GITHUB_TOKEN (!)
|
||||
./mvnw \
|
||||
-Dmaven.javadoc.skip=true \
|
||||
@ -141,11 +142,12 @@ function build() {
|
||||
-Dpmd.skip \
|
||||
--show-version --errors --batch-mode \
|
||||
clean package \
|
||||
sonar:sonar -Dsonar.login="${SONAR_TOKEN}" -Psonar,cli-dist
|
||||
sonar:sonar -Dsonar.login="${SONAR_TOKEN}" -Psonar
|
||||
pmd_ci_log_success "New sonar results: https://sonarcloud.io/dashboard?id=net.sourceforge.pmd%3Apmd"
|
||||
pmd_ci_log_group_end
|
||||
|
||||
pmd_ci_log_group_start "Executing build with coveralls"
|
||||
pmd_ci_openjdk_setdefault 11
|
||||
export CI_NAME="github actions"
|
||||
export CI_BUILD_URL="${PMD_CI_JOB_URL}"
|
||||
export CI_BRANCH="${PMD_CI_BRANCH}"
|
||||
@ -157,7 +159,7 @@ function build() {
|
||||
-DrepoToken="${COVERALLS_REPO_TOKEN}" \
|
||||
--show-version --errors --batch-mode \
|
||||
clean package jacoco:report \
|
||||
coveralls:report -Pcoveralls,cli-dist
|
||||
coveralls:report -Pcoveralls
|
||||
pmd_ci_log_success "New coveralls result: https://coveralls.io/github/pmd/pmd"
|
||||
pmd_ci_log_group_end
|
||||
fi
|
||||
@ -185,16 +187,16 @@ function pmd_ci_build_run() {
|
||||
mvn_profiles="${mvn_profiles},pmd-release"
|
||||
|
||||
# There are two possible (release) builds:
|
||||
if [[ "${PMD_CI_TAG}" != *-dist ]]; then
|
||||
# a) pmd-core and languages modules
|
||||
./mvnw clean deploy -P"${mvn_profiles}",'!cli-dist' --show-version --errors --batch-mode "${PMD_MAVEN_EXTRA_OPTS[@]}"
|
||||
if [ "${BUILD_CLI_DIST_ONLY}" = "false" ]; then
|
||||
# a) everything without pmd-cli and pmd-dist
|
||||
./mvnw clean deploy -P"${mvn_profiles}" -Dskip-cli-dist --show-version --errors --batch-mode "${PMD_MAVEN_EXTRA_OPTS[@]}"
|
||||
else
|
||||
# b) pmd-cli and pmd-dist
|
||||
./mvnw clean deploy -P"${mvn_profiles},cli-dist" -pl pmd-cli,pmd-dist --show-version --errors --batch-mode "${PMD_MAVEN_EXTRA_OPTS[@]}"
|
||||
# b) only pmd-cli and pmd-dist
|
||||
./mvnw clean deploy -P"${mvn_profiles}" -pl pmd-cli,pmd-dist --show-version --errors --batch-mode "${PMD_MAVEN_EXTRA_OPTS[@]}"
|
||||
fi
|
||||
else
|
||||
pmd_ci_log_info "This is a snapshot build"
|
||||
./mvnw clean deploy -P"${mvn_profiles},cli-dist" --show-version --errors --batch-mode "${PMD_MAVEN_EXTRA_OPTS[@]}"
|
||||
./mvnw clean deploy -P"${mvn_profiles}" --show-version --errors --batch-mode "${PMD_MAVEN_EXTRA_OPTS[@]}"
|
||||
fi
|
||||
|
||||
}
|
||||
@ -214,15 +216,15 @@ function pmd_ci_deploy_build_artifacts() {
|
||||
pmd_ci_sourceforge_uploadFile "pmd/${PMD_CI_MAVEN_PROJECT_VERSION}" "pmd-dist/target/pmd-${PMD_CI_MAVEN_PROJECT_VERSION}-cyclonedx.json"
|
||||
fi
|
||||
|
||||
# release build case a): only pmd-core and language modules released
|
||||
if pmd_ci_maven_isReleaseBuild && [[ "${PMD_CI_TAG}" != *-dist ]]; then
|
||||
# release build case a): everything without pmd-cli and pmd-dist is released
|
||||
if pmd_ci_maven_isReleaseBuild && [ "${BUILD_CLI_DIST_ONLY}" = "false" ]; then
|
||||
# create a draft github release
|
||||
pmd_ci_gh_releases_createDraftRelease "${PMD_CI_TAG}" "$(git rev-list -n 1 "${PMD_CI_TAG}")"
|
||||
GH_RELEASE="$RESULT"
|
||||
fi
|
||||
|
||||
# release build case b): pmd-cli and pmd-dist are released
|
||||
if pmd_ci_maven_isReleaseBuild && [[ "${PMD_CI_TAG}" == *-dist ]]; then
|
||||
# release build case b): only pmd-cli and pmd-dist are released
|
||||
if pmd_ci_maven_isReleaseBuild && [ "${BUILD_CLI_DIST_ONLY}" = "true" ]; then
|
||||
# Deploy to sourceforge files https://sourceforge.net/projects/pmd/files/pmd/
|
||||
pmd_ci_sourceforge_uploadFile "pmd/${PMD_CI_MAVEN_PROJECT_VERSION}" "pmd-dist/target/pmd-dist-${PMD_CI_MAVEN_PROJECT_VERSION}-bin.zip"
|
||||
pmd_ci_sourceforge_uploadFile "pmd/${PMD_CI_MAVEN_PROJECT_VERSION}" "pmd-dist/target/pmd-dist-${PMD_CI_MAVEN_PROJECT_VERSION}-src.zip"
|
||||
@ -254,7 +256,7 @@ function pmd_ci_build_and_upload_doc() {
|
||||
pmd_doc_create_archive
|
||||
|
||||
pmd_ci_sourceforge_uploadFile "pmd/${PMD_CI_MAVEN_PROJECT_VERSION}" "docs/pmd-dist-${PMD_CI_MAVEN_PROJECT_VERSION}-doc.zip"
|
||||
if pmd_ci_maven_isReleaseBuild && [[ "${PMD_CI_TAG}" != *-dist ]]; then
|
||||
if pmd_ci_maven_isReleaseBuild && [ "${BUILD_CLI_DIST_ONLY}" = "false" ]; then
|
||||
pmd_ci_gh_releases_uploadAsset "$GH_RELEASE" "docs/pmd-dist-${PMD_CI_MAVEN_PROJECT_VERSION}-doc.zip"
|
||||
fi
|
||||
|
||||
@ -263,7 +265,7 @@ function pmd_ci_build_and_upload_doc() {
|
||||
# Deploy javadoc to https://docs.pmd-code.org/apidocs/*/${PMD_CI_MAVEN_PROJECT_VERSION}/
|
||||
pmd_code_uploadJavadoc "${PMD_CI_MAVEN_PROJECT_VERSION}" "$(pwd)"
|
||||
|
||||
if pmd_ci_maven_isSnapshotBuild || [[ "${PMD_CI_TAG}" != *-dist ]]; then
|
||||
if pmd_ci_maven_isSnapshotBuild || [ "${BUILD_CLI_DIST_ONLY}" = "false" ]; then
|
||||
# render release notes
|
||||
# updating github release text
|
||||
rm -f .bundle/config
|
||||
@ -289,7 +291,7 @@ function pmd_ci_build_and_upload_doc() {
|
||||
pmd_ci_sourceforge_rsyncSnapshotDocumentation "${PMD_CI_MAVEN_PROJECT_VERSION}" "snapshot"
|
||||
fi
|
||||
|
||||
if pmd_ci_maven_isReleaseBuild && [[ "${PMD_CI_TAG}" != *-dist ]]; then
|
||||
if pmd_ci_maven_isReleaseBuild && [ "${BUILD_CLI_DIST_ONLY}" = "false" ]; then
|
||||
# documentation is already uploaded to https://docs.pmd-code.org/pmd-doc-${PMD_CI_MAVEN_PROJECT_VERSION}
|
||||
# we only need to setup symlinks for the released version
|
||||
pmd_code_createSymlink "${PMD_CI_MAVEN_PROJECT_VERSION}" "latest"
|
||||
@ -321,16 +323,15 @@ ${rendered_release_notes}"
|
||||
#
|
||||
function pmd_ci_dogfood() {
|
||||
local mpmdVersion=()
|
||||
./mvnw versions:set -DnewVersion="${PMD_CI_MAVEN_PROJECT_VERSION}-dogfood" -DgenerateBackupPoms=false -Pcli-dist
|
||||
./mvnw versions:set -DnewVersion="${PMD_CI_MAVEN_PROJECT_VERSION}-dogfood" -DgenerateBackupPoms=false
|
||||
sed -i 's/<version>[0-9]\{1,\}\.[0-9]\{1,\}\.[0-9]\{1,\}.*<\/version>\( *<!-- pmd.dogfood.version -->\)/<version>'"${PMD_CI_MAVEN_PROJECT_VERSION}"'<\/version>\1/' pom.xml
|
||||
./mvnw verify --show-version --errors --batch-mode "${PMD_MAVEN_EXTRA_OPTS[@]}" \
|
||||
-Pcli-dist \
|
||||
"${mpmdVersion[@]}" \
|
||||
-DskipTests \
|
||||
-Dmaven.javadoc.skip=true \
|
||||
-Dmaven.source.skip=true \
|
||||
-Dcheckstyle.skip=true
|
||||
./mvnw versions:set -DnewVersion="${PMD_CI_MAVEN_PROJECT_VERSION}" -DgenerateBackupPoms=false -Pcli-dist
|
||||
./mvnw versions:set -DnewVersion="${PMD_CI_MAVEN_PROJECT_VERSION}" -DgenerateBackupPoms=false
|
||||
git checkout -- pom.xml
|
||||
}
|
||||
|
||||
|
13
.github/workflows/build.yml
vendored
13
.github/workflows/build.yml
vendored
@ -14,6 +14,12 @@ on:
|
||||
# build it monthly: At 04:00 on day-of-month 1.
|
||||
- cron: '0 4 1 * *'
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
build_cli_dist_only:
|
||||
description: "Build only modules cli and dist"
|
||||
required: true
|
||||
type: boolean
|
||||
default: false
|
||||
|
||||
permissions:
|
||||
contents: read # to fetch code (actions/checkout)
|
||||
@ -35,7 +41,7 @@ jobs:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 2
|
||||
- uses: actions/cache@v3
|
||||
- uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~/.m2/repository
|
||||
@ -55,7 +61,7 @@ jobs:
|
||||
run: |
|
||||
echo "LANG=en_US.UTF-8" >> $GITHUB_ENV
|
||||
echo "MAVEN_OPTS=-Daether.connector.http.connectionMaxTtl=180 -DautoReleaseAfterClose=true -DstagingProgressTimeoutMinutes=30" >> $GITHUB_ENV
|
||||
echo "PMD_CI_SCRIPTS_URL=https://raw.githubusercontent.com/pmd/build-tools/22/scripts" >> $GITHUB_ENV
|
||||
echo "PMD_CI_SCRIPTS_URL=https://raw.githubusercontent.com/pmd/build-tools/master/scripts" >> $GITHUB_ENV
|
||||
- name: Check Environment
|
||||
shell: bash
|
||||
run: |
|
||||
@ -68,13 +74,14 @@ jobs:
|
||||
run: .ci/build.sh
|
||||
shell: bash
|
||||
env:
|
||||
BUILD_CLI_DIST_ONLY: ${{ inputs.build_cli_dist_only }}
|
||||
PMD_CI_SECRET_PASSPHRASE: ${{ secrets.PMD_CI_SECRET_PASSPHRASE }}
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Workaround actions/upload-artifact#176
|
||||
run: |
|
||||
echo "artifacts_path=$(realpath ..)" >> $GITHUB_ENV
|
||||
- name: Upload regression tester report
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: pmd-regression-tester
|
||||
path: ${{ env.artifacts_path }}/target/pr-*-diff-report-*.tar.gz
|
||||
|
2
.github/workflows/git-repo-sync.yml
vendored
2
.github/workflows/git-repo-sync.yml
vendored
@ -24,7 +24,7 @@ jobs:
|
||||
shell: bash
|
||||
run: |
|
||||
echo "LANG=en_US.UTF-8" >> $GITHUB_ENV
|
||||
echo "PMD_CI_SCRIPTS_URL=https://raw.githubusercontent.com/pmd/build-tools/22/scripts" >> $GITHUB_ENV
|
||||
echo "PMD_CI_SCRIPTS_URL=https://raw.githubusercontent.com/pmd/build-tools/master/scripts" >> $GITHUB_ENV
|
||||
- name: Sync
|
||||
run: .ci/git-repo-sync.sh
|
||||
shell: bash
|
||||
|
4
.github/workflows/troubleshooting.yml
vendored
4
.github/workflows/troubleshooting.yml
vendored
@ -16,7 +16,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/cache@v3
|
||||
- uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~/.m2/repository
|
||||
@ -36,7 +36,7 @@ jobs:
|
||||
run: |
|
||||
echo "LANG=en_US.UTF-8" >> $GITHUB_ENV
|
||||
echo "MAVEN_OPTS=-Daether.connector.http.connectionMaxTtl=180 -DstagingProgressTimeoutMinutes=30" >> $GITHUB_ENV
|
||||
echo "PMD_CI_SCRIPTS_URL=https://raw.githubusercontent.com/pmd/build-tools/22/scripts" >> $GITHUB_ENV
|
||||
echo "PMD_CI_SCRIPTS_URL=https://raw.githubusercontent.com/pmd/build-tools/master/scripts" >> $GITHUB_ENV
|
||||
- name: Check Environment
|
||||
shell: bash
|
||||
run: |
|
||||
|
@ -1,4 +1,6 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# abort the script on the first failing sub command
|
||||
set -e
|
||||
|
||||
# Make sure, everything is English...
|
||||
@ -165,13 +167,32 @@ git commit -a -m "Prepare pmd release ${RELEASE_VERSION}"
|
||||
fi
|
||||
)
|
||||
|
||||
./mvnw -B release:clean release:prepare \
|
||||
-Dtag="pmd_releases/${RELEASE_VERSION}" \
|
||||
-DreleaseVersion="${RELEASE_VERSION}" \
|
||||
-DdevelopmentVersion="${DEVELOPMENT_VERSION}" \
|
||||
-DscmCommentPrefix="[release] " \
|
||||
-Darguments='-Pgenerate-rule-docs,!cli-dist' \
|
||||
'-Pgenerate-rule-docs,!cli-dist'
|
||||
# check that there are no uncommitted changes
|
||||
UNCOMMITTED_CHANGES=$(git status --short --untracked-files=no)
|
||||
if [ -n "${UNCOMMITTED_CHANGES}" ]; then
|
||||
echo "There are uncommitted changes:"
|
||||
echo "${UNCOMMITTED_CHANGES}"
|
||||
exit 1
|
||||
fi
|
||||
# check that there are no SNAPSHOT dependencies -> done by the enforcer plugin, see enforce-no-snapshots
|
||||
echo "Change version in the POMs to ${RELEASE_VERSION} and update build timestamp"
|
||||
./mvnw --quiet versions:set -DnewVersion="${RELEASE_VERSION}" -DgenerateBackupPoms=false -DupdateBuildOutputTimestampPolicy=always
|
||||
echo "Transform the SCM information in the POM"
|
||||
sed -i "s|<tag>.\+</tag>|<tag>pmd_releases/${RELEASE_VERSION}</tag>|" pom.xml
|
||||
echo "Run the project tests against the changed POMs to confirm everything is in running order (skipping cli and dist)"
|
||||
./mvnw clean verify -Dskip-cli-dist -Pgenerate-rule-docs
|
||||
echo "Commit and create tag"
|
||||
git commit -a -m "[release] prepare release pmd_releases/${RELEASE_VERSION}"
|
||||
git tag -m "[release] copy for tag pmd_releases/${RELEASE_VERSION}" "pmd_releases/${RELEASE_VERSION}"
|
||||
echo "Update POMs to set the new development version ${DEVELOPMENT_VERSION}"
|
||||
./mvnw --quiet versions:set -DnewVersion="${DEVELOPMENT_VERSION}" -DgenerateBackupPoms=false -DupdateBuildOutputTimestampPolicy=never
|
||||
sed -i "s|<tag>.\+</tag>|<tag>HEAD</tag>|" pom.xml
|
||||
echo "Commit"
|
||||
git commit -a -m "[release] prepare for next development iteration"
|
||||
echo "Push branch and tag pmd_releases/${RELEASE_VERSION}"
|
||||
git push origin "${CURRENT_BRANCH}"
|
||||
git push origin tag "pmd_releases/${RELEASE_VERSION}"
|
||||
|
||||
|
||||
echo
|
||||
echo "Tag has been pushed.... now check github actions: <https://github.com/pmd/pmd/actions>"
|
||||
@ -232,9 +253,8 @@ This is a {{ site.pmd.release_type }} release.
|
||||
|
||||
EOF
|
||||
|
||||
git commit -a -m "Prepare next development version [skip ci]"
|
||||
git commit -a -m "[release] Prepare next development version [skip ci]"
|
||||
git push origin "${CURRENT_BRANCH}"
|
||||
./mvnw -B release:clean
|
||||
|
||||
echo
|
||||
echo
|
||||
@ -248,28 +268,19 @@ echo "<https://github.com/pmd/pmd-designer/blob/master/releasing.md>"
|
||||
echo
|
||||
echo "Press enter to continue when pmd-designer is available in maven-central..."
|
||||
echo "<https://repo.maven.apache.org/maven2/net/sourceforge/pmd/pmd-ui/maven-metadata.xml>."
|
||||
echo
|
||||
echo "Note: If there is no new pmd-designer release needed, you can directly proceed."
|
||||
read -r
|
||||
|
||||
echo
|
||||
echo "Continuing with release of pmd-cli and pmd-dist..."
|
||||
git checkout "pmd_releases/${RELEASE_VERSION}"
|
||||
./mvnw versions:update-parent -DparentVersion="${RELEASE_VERSION}" -DskipResolution=true -DgenerateBackupPoms=false -pl pmd-cli,pmd-dist
|
||||
git add pmd-cli/pom.xml pmd-dist/pom.xml
|
||||
git commit -m "[release] prepare release pmd_releases/${RELEASE_VERSION}-dist"
|
||||
git tag -m "[release] copy for tag pmd_releases/${RELEASE_VERSION}-dist" "pmd_releases/${RELEASE_VERSION}-dist"
|
||||
git push origin tag "pmd_releases/${RELEASE_VERSION}-dist"
|
||||
git checkout master
|
||||
# make sure parent reference is correct
|
||||
./mvnw versions:update-parent -DparentVersion="${DEVELOPMENT_VERSION}" -DskipResolution=true -DgenerateBackupPoms=false -pl pmd-cli,pmd-dist
|
||||
git add pmd-cli/pom.xml pmd-dist/pom.xml
|
||||
changes=$(git status --porcelain 2>/dev/null | grep -c -E "^[AMDRC]" || true)
|
||||
if [ "$changes" -gt 0 ]; then
|
||||
git commit -m "Prepare next development version [skip ci]"
|
||||
git push origin "${CURRENT_BRANCH}"
|
||||
fi
|
||||
|
||||
echo
|
||||
echo "Second tag 'pmd_releases/${RELEASE_VERSION}-dist' has been pushed ... now check github actions: <https://github.com/pmd/pmd/actions>"
|
||||
echo "Go to <https://github.com/pmd/pmd/actions/workflows/build.yml> and manually trigger a new build"
|
||||
echo "from tag 'pmd_releases/${RELEASE_VERSION}' and with option 'Build only modules cli and dist' checked."
|
||||
echo
|
||||
echo "This triggers the second stage release and eventually publishes the release on GitHub."
|
||||
echo
|
||||
echo "Now check github actions: <https://github.com/pmd/pmd/actions>"
|
||||
echo
|
||||
echo
|
||||
echo "Verify the new release on github: <https://github.com/pmd/pmd/releases/tag/pmd_releases/${RELEASE_VERSION}>"
|
||||
@ -297,5 +308,3 @@ echo "------------------------------------------"
|
||||
echo "Done."
|
||||
echo "------------------------------------------"
|
||||
echo
|
||||
|
||||
|
||||
|
@ -111,8 +111,8 @@ langs:
|
||||
examples:
|
||||
- code: '//*[pmd-java:nodeIs("Expression")]'
|
||||
outcome: "Matches all nodes that implement {% jdoc jast::ASTExpression %}"
|
||||
- code: '//*[pmd-java:nodeIs("AnyTypeDeclaration")]'
|
||||
outcome: "Matches all nodes that implement {% jdoc jast::ASTAnyTypeDeclaration %}"
|
||||
- code: '//*[pmd-java:nodeIs("TypeDeclaration")]'
|
||||
outcome: "Matches all nodes that implement {% jdoc jast::ASTTypeDeclaration %}"
|
||||
- code: '//*[pmd-java:nodeIs("Foo")]'
|
||||
outcome: "Runtime error, there's no class ASTFoo in the package"
|
||||
|
||||
@ -129,7 +129,7 @@ langs:
|
||||
examples:
|
||||
- code: '//FormalParameter[pmd-java:typeIs("java.lang.String[]")]'
|
||||
outcome: "Matches formal parameters of type `String[]` (including vararg parameters)"
|
||||
- code: '//VariableDeclaratorId[pmd-java:typeIs("java.lang.List")]'
|
||||
- code: '//VariableId[pmd-java:typeIs("java.lang.List")]'
|
||||
outcome: "Matches variable declarators of type `List` or any of its subtypes (including e.g. `ArrayList`)"
|
||||
|
||||
- name: typeIsExactly
|
||||
@ -142,7 +142,7 @@ langs:
|
||||
parameters:
|
||||
- *qname_param
|
||||
examples:
|
||||
- code: '//VariableDeclaratorId[pmd-java:typeIsExactly("java.lang.List")]'
|
||||
- code: '//VariableId[pmd-java:typeIsExactly("java.lang.List")]'
|
||||
outcome: "Matches variable declarators of type `List` (but not e.g. `ArrayList`)"
|
||||
|
||||
|
||||
@ -159,7 +159,7 @@ langs:
|
||||
type: "xs:string"
|
||||
description: "The name of a metric in {% jdoc jmx::JavaMetrics %} (or an alias thereof)."
|
||||
examples:
|
||||
- code: "//ClassOrInterfaceDeclaration[metric('NCSS') > 200]"
|
||||
- code: "//ClassDeclaration[metric('NCSS') > 200]"
|
||||
outcome: ""
|
||||
- code: "//MethodDeclaration[metric('CYCLO') > 10 and metric('NCSS') > 20]"
|
||||
outcome: ""
|
||||
@ -186,7 +186,7 @@ langs:
|
||||
Returns a sequence of the effective modifiers of a node as strings.
|
||||
This is documented on {% jdoc jast::ASTModifierList#getEffectiveModifiers() %}.
|
||||
|
||||
notes: "The context node must be an {% jdoc jast::AccessNode %}, otherwise this returns an empty sequence"
|
||||
notes: "The context node must be an {% jdoc jast::ModifierOwner %}, otherwise this returns an empty sequence"
|
||||
parameters:
|
||||
examples:
|
||||
- code: '//MethodDeclaration[pmd-java:modifiers() = "native"]'
|
||||
@ -210,7 +210,7 @@ langs:
|
||||
Returns a sequence of the explicit modifiers of a node as strings.
|
||||
This is documented on {% jdoc jast::ASTModifierList#getExplicitModifiers() %}.
|
||||
|
||||
notes: "The context node must be an {% jdoc jast::AccessNode %}, otherwise this returns an empty sequence"
|
||||
notes: "The context node must be an {% jdoc jast::ModifierOwner %}, otherwise this returns an empty sequence"
|
||||
parameters:
|
||||
examples:
|
||||
- code: '//MethodDeclaration[pmd-java:explicitModifiers() = "public"]'
|
||||
|
@ -1,6 +1,6 @@
|
||||
{% for lang in site.data.xpath_funs.langs %}
|
||||
|
||||
<!-- Generates the documentation of XPath functions. -->
|
||||
<!-- Generates the documentation of XPath functions from file docs/_data/xpath_funs.yml -->
|
||||
|
||||
### {{ lang.name }}
|
||||
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 129 KiB After Width: | Height: | Size: 168 KiB |
Binary file not shown.
Before Width: | Height: | Size: 101 KiB After Width: | Height: | Size: 159 KiB |
@ -68,25 +68,23 @@ $ cat Foo.xml
|
||||
<?xml version='1.0' encoding='UTF-8' ?>
|
||||
<CompilationUnit Image='' PackageName='' declarationsAreInDefaultPackage='true'>
|
||||
<TypeDeclaration Image=''>
|
||||
<ClassOrInterfaceDeclaration Abstract='false' BinaryName='Foo' Default='false' Final='false' Image='Foo' Interface='false' Local='false' Modifiers='1' Native='false' Nested='false' PackagePrivate='false' Private='false' Protected='false' Public='true' SimpleName='Foo' Static='false' Strictfp='false' Synchronized='false' Transient='false' TypeKind='CLASS' Volatile='false'>
|
||||
<ClassOrInterfaceBody AnonymousInnerClass='false' EnumChild='false' Image=''>
|
||||
<ClassOrInterfaceBodyDeclaration AnonymousInnerClass='false' EnumChild='false' Image='' Kind='FIELD'>
|
||||
<ClassDeclaration Abstract='false' BinaryName='Foo' Default='false' Final='false' Image='Foo' Interface='false' Local='false' Modifiers='1' Native='false' Nested='false' PackagePrivate='false' Private='false' Protected='false' Public='true' SimpleName='Foo' Static='false' Strictfp='false' Synchronized='false' Transient='false' TypeKind='CLASS' Volatile='false'>
|
||||
<ClassBody AnonymousInnerClass='false' EnumChild='false' Image=''>
|
||||
<FieldDeclaration Abstract='false' AnnotationMember='false' Array='false' ArrayDepth='0' Default='false' Final='false' Image='' InterfaceMember='false' Modifiers='0' Native='false' PackagePrivate='true' Private='false' Protected='false' Public='false' Static='false' Strictfp='false' Synchronized='false' SyntacticallyFinal='false' SyntacticallyPublic='false' SyntacticallyStatic='false' Transient='false' VariableName='a' Volatile='false'>
|
||||
<Type Array='false' ArrayDepth='0' ArrayType='false' Image='' TypeImage='int'>
|
||||
<PrimitiveType Array='false' ArrayDepth='0' Boolean='false' Image='int' />
|
||||
</Type>
|
||||
<VariableDeclarator Image='' Initializer='false' Name='a'>
|
||||
<VariableDeclaratorId Array='false' ArrayDepth='0' ArrayType='false' ExceptionBlockParameter='false' ExplicitReceiverParameter='false' Field='true' Final='false' FormalParameter='false' Image='a' LambdaParameter='false' LocalVariable='false' ResourceDeclaration='false' TypeInferred='false' VariableName='a' />
|
||||
<VariableId Array='false' ArrayDepth='0' ArrayType='false' ExceptionBlockParameter='false' ExplicitReceiverParameter='false' Field='true' Final='false' FormalParameter='false' Image='a' LambdaParameter='false' LocalVariable='false' ResourceDeclaration='false' TypeInferred='false' VariableName='a' />
|
||||
</VariableDeclarator>
|
||||
</FieldDeclaration>
|
||||
</ClassOrInterfaceBodyDeclaration>
|
||||
</ClassOrInterfaceBody>
|
||||
</ClassOrInterfaceDeclaration>
|
||||
</ClassBody>
|
||||
</ClassDeclaration>
|
||||
</TypeDeclaration>
|
||||
</CompilationUnit>
|
||||
|
||||
$ xmlstarlet select -t -c "//VariableDeclaratorId[@VariableName='a']" Foo.xml
|
||||
<VariableDeclaratorId Array="false" ArrayDepth="0" ArrayType="false" ExceptionBlockParameter="false" ExplicitReceiverParameter="false" Field="true" Final="false" FormalParameter="false" Image="a" LambdaParameter="false" LocalVariable="false" ResourceDeclaration="false" TypeInferred="false" VariableName="a"/>
|
||||
$ xmlstarlet select -t -c "//VariableId[@VariableName='a']" Foo.xml
|
||||
<VariableId Array="false" ArrayDepth="0" ArrayType="false" ExceptionBlockParameter="false" ExplicitReceiverParameter="false" Field="true" Final="false" FormalParameter="false" Image="a" LambdaParameter="false" LocalVariable="false" ResourceDeclaration="false" TypeInferred="false" VariableName="a"/>
|
||||
```
|
||||
|
||||
This example uses [xmlstarlet](http://xmlstar.sourceforge.net/) to query the xml document for any variables/fields
|
||||
|
@ -3,7 +3,7 @@ title: Adding PMD support for a new ANTLR grammar based language
|
||||
short_title: Adding a new language with ANTLR
|
||||
tags: [devdocs, extending]
|
||||
summary: "How to add a new language to PMD using ANTLR grammar."
|
||||
last_updated: April 2023 (7.0.0)
|
||||
last_updated: December 2023 (7.0.0)
|
||||
sidebar: pmd_sidebar
|
||||
permalink: pmd_devdocs_major_adding_new_language_antlr.html
|
||||
folder: pmd/devdocs
|
||||
@ -51,7 +51,9 @@ definitely don't come for free. It is much effort and requires perseverance to i
|
||||
|
||||
" %}
|
||||
|
||||
## 1. Start with a new sub-module
|
||||
## Steps
|
||||
|
||||
### 1. Start with a new sub-module
|
||||
* See pmd-swift for examples.
|
||||
* Make sure to add your new module to PMD's parent pom as `<module>` entry, so that it is built alongside the
|
||||
other languages.
|
||||
@ -59,7 +61,7 @@ definitely don't come for free. It is much effort and requires perseverance to i
|
||||
is automatically available in the binary distribution (pmd-dist).
|
||||
|
||||
|
||||
## 2. Implement an AST parser for your language
|
||||
### 2. Implement an AST parser for your language
|
||||
* ANTLR will generate the parser for you based on the grammar file. The grammar file needs to be placed in the
|
||||
folder `src/main/antlr4` in the appropriate sub package `ast` of the language. E.g. for swift, the grammar
|
||||
file is [Swift.g4](https://github.com/pmd/pmd/blob/master/pmd-swift/src/main/antlr4/net/sourceforge/pmd/lang/swift/ast/Swift.g4)
|
||||
@ -67,7 +69,7 @@ definitely don't come for free. It is much effort and requires perseverance to i
|
||||
* Configure the options "superClass" and "contextSuperClass". These are the base classes for the generated
|
||||
classes.
|
||||
|
||||
## 3. Create AST node classes
|
||||
### 3. Create AST node classes
|
||||
* The individual AST nodes are generated, but you need to define the common interface for them.
|
||||
* You need to define the supertype interface for all nodes of the language. For that, we provide
|
||||
[`AntlrNode`](https://github.com/pmd/pmd/blob/master/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/impl/antlr4/AntlrNode.java).
|
||||
@ -106,7 +108,7 @@ definitely don't come for free. It is much effort and requires perseverance to i
|
||||
* You can add additional methods in your "InnerNode" (e.g. `SwiftInnerNode`) that are available on all nodes.
|
||||
But on most cases you won't need to do anything.
|
||||
|
||||
## 4. Generate your parser (using ANTLR)
|
||||
### 4. Generate your parser (using ANTLR)
|
||||
* Make sure, you have the property `<antlr4.visitor>true</antlr4.visitor>` in your `pom.xml` file.
|
||||
* This is just a matter of building the language module. ANTLR is called via ant, and this step is added
|
||||
to the phase `generate-sources`. So you can just call e.g. `./mvnw generate-sources -pl pmd-swift` to
|
||||
@ -115,7 +117,7 @@ definitely don't come for free. It is much effort and requires perseverance to i
|
||||
source control.
|
||||
* You should review [`pmd-swift/pom.xml`](https://github.com/pmd/pmd/blob/master/pmd-swift/pom.xml).
|
||||
|
||||
## 5. Create a TokenManager
|
||||
### 5. Create a TokenManager
|
||||
* This is needed to support CPD (copy paste detection)
|
||||
* We provide a default implementation using [`AntlrTokenManager`](https://github.com/pmd/pmd/blob/master/pmd-core/src/main/java/net/sourceforge/pmd/cpd/impl/AntlrTokenizer.java).
|
||||
* You must create your own "AntlrTokenizer" such as we do with
|
||||
@ -130,13 +132,13 @@ definitely don't come for free. It is much effort and requires perseverance to i
|
||||
If you don't need a custom token filter, you don't need to override the method. It returns the default
|
||||
`AntlrTokenFilter` which doesn't filter anything.
|
||||
|
||||
## 6. Create a PMD parser “adapter”
|
||||
### 6. Create a PMD parser “adapter”
|
||||
* Create your own parser, that adapts the ANLTR interface to PMD's parser interface.
|
||||
* We provide a [`AntlrBaseParser`](https://github.com/pmd/pmd/blob/master/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/impl/antlr4/AntlrBaseParser.java)
|
||||
implementation that you need to extend to create your own adapter as we do with
|
||||
[`PmdSwiftParser`](https://github.com/pmd/pmd/blob/master/pmd-swift/src/main/java/net/sourceforge/pmd/lang/swift/ast/PmdSwiftParser.java).
|
||||
|
||||
## 7. Create a language version handler
|
||||
### 7. Create a language version handler
|
||||
* Now you need to create your version handler, as we did with [`SwiftHandler`](https://github.com/pmd/pmd/blob/master/pmd-swift/src/main/java/net/sourceforge/pmd/lang/swift/SwiftHandler.java).
|
||||
* This class is sort of a gateway between PMD and all parsing logic specific to your language.
|
||||
* For a minimal implementation, it just needs to return a parser *(see step #6)*.
|
||||
@ -148,7 +150,7 @@ definitely don't come for free. It is much effort and requires perseverance to i
|
||||
* metrics
|
||||
* custom XPath functions
|
||||
|
||||
## 8. Create a base visitor
|
||||
### 8. Create a base visitor
|
||||
* A parser visitor adapter is not needed anymore with PMD 7. The visitor interface now provides a default
|
||||
implementation.
|
||||
* The visitor for ANTLR based AST is generated along the parser from the ANTLR grammar file. The
|
||||
@ -158,7 +160,7 @@ definitely don't come for free. It is much effort and requires perseverance to i
|
||||
See [`SwiftVisitorBase`](https://github.com/pmd/pmd/blob/master/pmd-swift/src/main/java/net/sourceforge/pmd/lang/swift/ast/SwiftVisitorBase.java)
|
||||
as an example.
|
||||
|
||||
## 9. Make PMD recognize your language
|
||||
### 9. Make PMD recognize your language
|
||||
* Create your own subclass of `net.sourceforge.pmd.lang.impl.SimpleLanguageModuleBase`, see Swift as an example:
|
||||
[`SwiftLanguageModule`](https://github.com/pmd/pmd/blob/master/pmd-swift/src/main/java/net/sourceforge/pmd/lang/swift/SwiftLanguageModule.java).
|
||||
* Add for each version of your language a call to `addVersion` in your language module’s constructor.
|
||||
@ -167,7 +169,7 @@ definitely don't come for free. It is much effort and requires perseverance to i
|
||||
* Create the service registration via the text file `src/main/resources/META-INF/services/net.sourceforge.pmd.lang.Language`.
|
||||
Add your fully qualified class name as a single line into it.
|
||||
|
||||
## 10. Create an abstract rule class for the language
|
||||
### 10. Create an abstract rule class for the language
|
||||
* You need to create your own abstract rule class in order to interface your language with PMD's generic rule
|
||||
execution.
|
||||
* See [`AbstractSwiftRule`](https://github.com/pmd/pmd/blob/master/pmd-swift/src/main/java/net/sourceforge/pmd/lang/swift/rule/AbstractSwiftRule.java) as an example.
|
||||
@ -184,7 +186,7 @@ definitely don't come for free. It is much effort and requires perseverance to i
|
||||
interface of the specific language). Now the rule just provides a visitor, which can be hidden and potentially
|
||||
shared between rules.
|
||||
|
||||
## 11. Create rules
|
||||
### 11. Create rules
|
||||
* Creating rules is already pretty well documented in PMD - and it’s no different for a new language, except you
|
||||
may have different AST nodes.
|
||||
* PMD supports 2 types of rules, through visitors or XPath.
|
||||
@ -207,7 +209,7 @@ definitely don't come for free. It is much effort and requires perseverance to i
|
||||
</resources>
|
||||
```
|
||||
|
||||
## 14. Test the rules
|
||||
### 12. Test the rules
|
||||
* Testing rules is described in depth in [Testing your rules](pmd_userdocs_extending_testing.html).
|
||||
* Each rule has its own test class: Create a test class for your rule extending `PmdRuleTst`
|
||||
*(see
|
||||
@ -231,7 +233,7 @@ definitely don't come for free. It is much effort and requires perseverance to i
|
||||
|
||||
*Note:* You'll need to add your ruleset to `categories.properties`, so that it can be found.
|
||||
|
||||
### 15. Create documentation page
|
||||
### 13. Create documentation page
|
||||
Finishing up your new language module by adding a page in the documentation. Create a new markdown file
|
||||
`<langId>.md` in `docs/pages/pmd/languages/`. This file should have the following frontmatter:
|
||||
|
||||
@ -252,3 +254,10 @@ There is also the following Jekyll Include, that creates summary box for the lan
|
||||
{% include language_info.html name='<Language Name>' id='<langId>' implementation='<langId>::lang.<langId>.<langId>LanguageModule' supports_cpd=true supports_pmd=true %}
|
||||
{% endraw %}
|
||||
```
|
||||
|
||||
## Optional features
|
||||
|
||||
See [Optional features in JavaCC based languages](pmd_devdocs_major_adding_new_language_javacc.html#optional-features).
|
||||
|
||||
In order to implement these, most likely an AST needs to be developed first. The parse tree (CST, concrete
|
||||
syntax tree) is not suitable to add methods such as `getSymbol()` to the node classes.
|
||||
|
@ -3,7 +3,7 @@ title: Adding PMD support for a new JavaCC grammar based language
|
||||
short_title: Adding a new language with JavaCC
|
||||
tags: [devdocs, extending]
|
||||
summary: "How to add a new language to PMD using JavaCC grammar."
|
||||
last_updated: February 2023 (7.0.0)
|
||||
last_updated: December 2023 (7.0.0)
|
||||
sidebar: pmd_sidebar
|
||||
permalink: pmd_devdocs_major_adding_new_language_javacc.html
|
||||
folder: pmd/devdocs
|
||||
@ -56,6 +56,8 @@ definitely don't come for free. It is much effort and requires perseverance to i
|
||||
* It’s a good idea to create a parent AST class for all AST classes of the language. This simplifies rule
|
||||
creation later. *(see SimpleNode for Velocity and AbstractJavaNode for Java for example)*
|
||||
* Note: These AST node classes are generated usually once by javacc/jjtree and can then be modified as needed.
|
||||
* You can add additional methods in your AST node classes, that can be used in rules. Most getters
|
||||
are also available for XPath rules, see section [XPath integration](#xpath-integration) below.
|
||||
|
||||
### 4. Generate your parser (using JJT)
|
||||
* An ant script is being used to compile jjt files into classes. This is in `javacc-wrapper.xml` file in the
|
||||
@ -205,6 +207,40 @@ There is also the following Jekyll Include, that creates summary box for the lan
|
||||
{% endraw %}
|
||||
```
|
||||
|
||||
## XPath integration
|
||||
|
||||
PMD exposes the AST nodes for use by XPath based rules (see [DOM representation of ASTs](pmd_userdocs_extending_writing_xpath_rules.html#dom-representation-of-asts)).
|
||||
Most Java getters in the AST classes are made available by default. These getters constitute the API of the language.
|
||||
If a getter method is renamed, then every XPath rule that uses this getter also needs to be adjusted. In order to
|
||||
have more control over this, there are two annotations that can be used for AST classes and their methods:
|
||||
|
||||
* {% jdoc core::lang.rule.xpath.DeprecatedAttribute %}: Getters might be annotated with that indicating, that
|
||||
this getter method should not be used in XPath rules. When a XPath rule uses such a method, a warning is
|
||||
issued. If the method additionally has the standard Java `@Deprecated` annotation, then the getter is also
|
||||
deprecated for java usage. Otherwise, the getter is only deprecated for usage in XPath rules.
|
||||
|
||||
When a getter is deprecated and there is a different getter to be used instead, then the
|
||||
attribute `replaceWith` should be used.
|
||||
|
||||
* {% jdoc core::lang.rule.xpath.NoAttribute %}: This annotation can be used on an AST node type or on individual
|
||||
methods in order to filter out which methods are available for XPath rules.
|
||||
When used on a type, either all methods can be filtered or only inherited methods (see attribute `scope`).
|
||||
When used directly on an individual method, then only this method will be filtered out.
|
||||
That way methods can be added in AST nodes, that should only be used in Java rules, e.g. as auxiliary methods.
|
||||
|
||||
{% include note.html content="
|
||||
Not all getters are available for XPath rules. It depends on the result type.
|
||||
Especially **Lists** or Collections in general are **not supported**." %}
|
||||
|
||||
Only the following Java result types are supported:
|
||||
* String
|
||||
* any Enum-type
|
||||
* int
|
||||
* boolean
|
||||
* double
|
||||
* long
|
||||
* char
|
||||
* float
|
||||
|
||||
## Debugging with Rule Designer
|
||||
|
||||
@ -229,3 +265,52 @@ If you want to add support for computing metrics:
|
||||
* Implement {% jdoc core::lang.LanguageVersionHandler#getLanguageMetricsProvider() %}, to make the metrics available in the designer.
|
||||
|
||||
See {% jdoc java::lang.java.metrics.JavaMetrics %} for an example.
|
||||
|
||||
### Symbol table
|
||||
|
||||
A symbol table keeps track of variables and their usages. It is part of semantic analysis and would
|
||||
be executed in your parser adapter as an additional pass after you got the initial AST.
|
||||
|
||||
There is no general language independent API in PMD core. For now, each language will need to implement
|
||||
its own solution. The symbol information that has been resolved in the additional parser pass
|
||||
can be made available on the AST nodes via extra methods, e.g. `getSymbolTable()`, `getSymbol()`, or
|
||||
`getUsages()`.
|
||||
|
||||
Currently only Java provides an implementation for symbol table,
|
||||
see [Java-specific features and guidance](pmd_languages_java.html).
|
||||
|
||||
{% capture deprecated_symbols_api_note %}
|
||||
With PMD 7.0.0 the symbol table and type resolution implementation has been
|
||||
rewritten from scratch. There is still an old API for symbol table support, that is used by PLSQL,
|
||||
see {% jdoc_package core::lang.symboltable %}. This will be deprecated and should not be used.
|
||||
{% endcapture %}
|
||||
{% include note.html content=deprecated_symbols_api_note %}
|
||||
|
||||
### Type resolution
|
||||
|
||||
For typed languages like Java type information can be useful for writing rules, that trigger only on
|
||||
specific types. Resolving types of expressions and variables would be done after in your parser
|
||||
adapter as yet another additional pass, potentially after resolving the symbol table.
|
||||
|
||||
Type resolution tries to find the actual class type of each used type, following along method calls
|
||||
(including overloaded and overwritten methods), allowing to query subtypes and type hierarchy.
|
||||
This might require additional configuration for the language, e.g. in Java you need
|
||||
to configure an auxiliary classpath.
|
||||
|
||||
There is no general language independent API in PMD core. For now, each language will need to implement
|
||||
its own solution. The type information can be made available on the AST nodes via extra methods,
|
||||
e.g. `getType()`.
|
||||
|
||||
Currently only Java provides an implementation for type resolution,
|
||||
see [Java-specific features and guidance](pmd_languages_java.html).
|
||||
|
||||
### Call and data flow analysis
|
||||
|
||||
Call and data flow analysis keep track of the data as it is moving through different execution paths
|
||||
a program has. This would be yet another analysis pass.
|
||||
|
||||
There is no general language independent API in PMD core. For now, each language will need to implement
|
||||
its own solution.
|
||||
|
||||
Currently Java has some limited support for data flow analysis,
|
||||
see [Java-specific features and guidance](pmd_languages_java.html).
|
||||
|
@ -76,7 +76,7 @@ Here's a short overview:
|
||||
| `{% raw %}{% jdoc !a!core::Rule#setName(java.lang.String) %}{% endraw %}` | {% jdoc !a!core::Rule#setName(java.lang.String) %} |
|
||||
| `{% raw %}{% jdoc !ac!core::Rule#setName(java.lang.String) %}{% endraw %}` | {% jdoc !ac!core::Rule#setName(java.lang.String) %} |
|
||||
| `{% raw %}{% jdoc core::properties.PropertyDescriptor %}{% endraw %}` | {% jdoc core::properties.PropertyDescriptor %} |
|
||||
| `{% raw %}{% jdoc_nspace :jast java::lang.java.ast %}{% jdoc jast::ASTAnyTypeDeclaration %}{% endraw %}` | {% jdoc_nspace :jast java::lang.java.ast %}{% jdoc jast::ASTAnyTypeDeclaration %} |
|
||||
| `{% raw %}{% jdoc_nspace :jast java::lang.java.ast %}{% jdoc jast::ASTTypeDeclaration %}{% endraw %}` | {% jdoc_nspace :jast java::lang.java.ast %}{% jdoc jast::ASTTypeDeclaration %} |
|
||||
| `{% raw %}{% jdoc_nspace :jast java::lang.java.ast %}{% jdoc_package :jast %}{% endraw %}` | {% jdoc_nspace :jast java::lang.java.ast %}{% jdoc_package :jast %} |
|
||||
| `{% raw %}{% jdoc_nspace :PrD core::properties.PropertyDescriptor %}{% jdoc !ac!:PrD#uiOrder() %}{% endraw %}` | {% jdoc_nspace :PrD core::properties.PropertyDescriptor %}{% jdoc !ac!:PrD#uiOrder() %} |
|
||||
| `{% raw %}{% jdoc_old core::Rule %}{% endraw %}` | {% jdoc_old core::Rule %}
|
||||
|
@ -2,15 +2,13 @@
|
||||
title: Java support
|
||||
permalink: pmd_languages_java.html
|
||||
author: Clément Fournier
|
||||
last_updated: September 2023 (7.0.0)
|
||||
last_updated: December 2023 (7.0.0)
|
||||
tags: [languages, PmdCapableLanguage, CpdCapableLanguage]
|
||||
summary: "Java-specific features and guidance"
|
||||
---
|
||||
|
||||
{% include language_info.html name='Java' id='java' implementation='java::lang.java.JavaLanguageModule' supports_pmd=true supports_cpd=true since='1.0.0' %}
|
||||
|
||||
{% include warning.html content="WIP, todo for pmd 7" %}
|
||||
|
||||
## Overview of supported Java language versions
|
||||
|
||||
Usually the latest non-preview Java Version is the default version.
|
||||
@ -55,20 +53,94 @@ See [Java language properties](pmd_languages_configuration.html#java-language-pr
|
||||
|
||||
## Type and symbol resolution
|
||||
|
||||
Java being a statically typed language, a Java program contains more information that just its syntax tree; for instance, every expression has a static type, and every method call is bound to a method overload statically (even if that overload is virtual). In PMD, much of this information is resolved from the AST by additional passes, which run after parsing, and before rules can inspect the tree.
|
||||
Java being a statically typed language, a Java program contains more information than just its syntax tree;
|
||||
for instance, every expression has a static type, and every method call is bound to a method overload
|
||||
statically (even if that overload is virtual). In PMD, much of this information is resolved from the AST
|
||||
by additional passes, which run after parsing, and before rules can inspect the tree.
|
||||
|
||||
The semantic analysis roughly works like so:
|
||||
1. The first passes resolve *symbols*, which are a model of the named entities that Java programs declare, like classes, methods, and variables.
|
||||
2. Then, each name in the tree is resolved to a symbol, according to the language's scoping rules. This may modify the tree to remove *ambiguous names* (names which could be either a type, package, or variable).
|
||||
3. The last pass resolves the types of expressions, which performs overload resolution on method calls, and type inference.
|
||||
1. The first passes resolve *symbols*, which are a model of the named entities that Java programs declare,
|
||||
like classes, methods, and variables.
|
||||
2. Then, each name in the tree is resolved to a symbol, according to the language's scoping rules. This may
|
||||
modify the tree to remove *ambiguous names* (names which could be either a type, package, or variable).
|
||||
3. The last pass resolves the types of expressions, which performs overload resolution on method calls,
|
||||
and type inference.
|
||||
|
||||
TODO describe
|
||||
* why we need auxclasspath
|
||||
* how disambiguation can fail
|
||||
The analyzed code might reference types from other places of the project or even from external
|
||||
dependencies. If e.g. the code extends a class from an external dependency, then PMD needs to know
|
||||
this external dependency in order to figure out, that a method is actually an override.
|
||||
|
||||
## Type and symbol APIs
|
||||
In order to resolve such types, a complete so-called auxiliary classpath need to be provided.
|
||||
Technically, PMD uses the [ASM framework](https://asm.ow2.io/index.html) to read the bytecode and build
|
||||
up its own representation to resolve names and types. It also reads the bytecode of the Java runtime
|
||||
in order to resolve Java API references.
|
||||
|
||||
TODO describe APIs: see #4319 and #2689
|
||||
## Providing the auxiliary classpath
|
||||
|
||||
The auxiliary classpath (or short "auxClasspath") is configured via the
|
||||
[Language Property "auxClasspath"](pmd_languages_configuration.html#java-language-properties).
|
||||
It is a string containing multiple paths separated by either a colon (`:`) under Linux/MacOS
|
||||
or a semicolon (`;`) under Windows.
|
||||
|
||||
In order to resolve the types of the Java API correctly, the Java Runtime must be on the
|
||||
auxClasspath as well. As the Java API and Runtime evolves from version to version, it is important
|
||||
to use the correct Java version, that is being analyzed. This might not necessarily be the
|
||||
same Java runtime version that is being used to run PMD.
|
||||
|
||||
Until Java 8, there exists the jar file `rt.jar` in `${JAVA_HOME}/jre/lib`. It is enough, to include
|
||||
this jar file in the auxClasspath. Usually, you would add this as the first entry in the auxClasspath.
|
||||
|
||||
Beginning with Java 9, the Java platform has been modularized and [Modular Run-Time Images](https://openjdk.org/jeps/220)
|
||||
have been introduced. The file `${JAVA_HOME}/lib/modules` contains now all the classes, but it is not a jar file
|
||||
anymore. However, each Java installation provides an implementation to read such Run-Time Images in
|
||||
`${JAVA_HOME}/lib/jrt-fs.jar`. This is an implementation of the `jrt://` filesystem and through this, the bytecode
|
||||
of the Java runtime classes can be loaded. In order to use this with PMD, the file `${JAVA_HOME}/lib/jrt-fs.jar`
|
||||
needs to be added to the auxClasspath as the first entry. PMD will make sure, to load the Java runtime classes
|
||||
using the jrt-filesystem.
|
||||
|
||||
If neither `${JAVA_HOME}/jre/lib/rt.jar` nor `${JAVA_HOME}/lib/jrt-fs.jar` is added to the auxClasspath, PMD falls
|
||||
back to load the JAva runtime classes **from the current runtime**, that is the runtime that was used to
|
||||
execute PMD. This might not be the correct version, e.g. you might run PMD with Java 8, but analyze code
|
||||
written for Java 21. In that case, you have to provide "jrt-fs.jar" on the auxClasspath.
|
||||
|
||||
## Symbol table APIs
|
||||
|
||||
{% jdoc_nspace :ast java::lang.java.ast %}
|
||||
{% jdoc_nspace :symbols java::lang.java.symbols %}
|
||||
|
||||
Symbol table API related classes are in the package {% jdoc_package :symbols %}.
|
||||
The root interface for symbols is {%jdoc symbols::JElementSymbol %}.
|
||||
|
||||
The symbol table can be requested on any node with the method {% jdoc ast::AbstractJavaNode#getSymbolTable() %}.
|
||||
This returns a {% jdoc symbols::table.JSymbolTable %} which gives you access to variables, methods and types that are
|
||||
within scope.
|
||||
|
||||
A {% jdoc ast::ASTExpression %} might represent a {% jdoc ast::ASTAssignableExpr.ASTNamedReferenceExpr %}
|
||||
if it e.g. references a variable name. In that case, you can access the referenced variable symbol
|
||||
with the method {% jdoc ast::ASTAssignableExpr.ASTNamedReferenceExpr#getReferencedSym() %}.
|
||||
|
||||
Declaration nodes, such as {% jdoc ast::ASTVariableDeclaratorId %} implement the interface
|
||||
{%jdoc ast::SymbolDeclaratorNode %}. Through the method
|
||||
{% jdoc ast::SymbolDeclaratorNode#getSymbol() %} you can also access the symbol.
|
||||
|
||||
To find usages, you can call {% jdoc ast::ASTVariableDeclaratorId#getLocalUsages() %}.
|
||||
|
||||
## Type resolution APIs
|
||||
|
||||
{% jdoc_nspace :types java::lang.java.types %}
|
||||
|
||||
Type resolution API related classes are in the package {% jdoc_package :types %}.
|
||||
|
||||
The core of the framework is a set of interfaces to represent types. The root interface is
|
||||
{% jdoc types::JTypeMirror %}. Type mirrors are created by a
|
||||
{% jdoc types::TypeSystem %} object. This object is analysis-global.
|
||||
|
||||
The utility class {% jdoc types::TypeTestUtil %} provides simple methods to check types,
|
||||
e.g. `TypeTestUtil.isA(String.class, variableDeclaratorIdNode)` tests, whether the given
|
||||
variableDeclaratorId is of type "String".
|
||||
|
||||
Any {% jdoc ast::TypeNode %} provides access to the type with the method {% jdoc ast::TypeNode#getTypeMirror() %}.
|
||||
E.g. this can be called on {% jdoc ast::ASTMethodCall %} to retrieve the return type of the called method.
|
||||
|
||||
## Metrics framework
|
||||
|
||||
@ -101,3 +173,10 @@ Java does this by adding the following additional information for each reported
|
||||
* {% jdoc core::RuleViolation#PACKAGE_NAME %}
|
||||
|
||||
You can access these via {% jdoc core::RuleViolation#getAdditionalInfo() %}
|
||||
|
||||
## Dataflow
|
||||
|
||||
There is no API yet for dataflow analysis. However, some rules such as {% rule java/bestpractices/UnusedAssignment %}
|
||||
or {% rule java/design/ImmutableField %} are using an internal implementation of an additional
|
||||
AST pass that adds dataflow information. The implementation can be found in
|
||||
[net.sourceforge.pmd.lang.java.rule.internal.DataflowPass](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/internal/DataflowPass.java).
|
||||
|
@ -9,24 +9,28 @@ This page describes the current status of the release process.
|
||||
|
||||
Since 6.30.0, the automated release process is using [Github Actions](https://github.com/pmd/pmd/actions).
|
||||
|
||||
However, there are still a few steps, that need manual examination.
|
||||
Since 7.0.0-rc4, the release happens in two phases: First pmd-core with all the languages are released.
|
||||
This allows to release then pmd-designer or any other project, that just depends on pmd-core and the
|
||||
languages. And in the second phase, pmd-cli and pmd-dist are released. These include e.g. pmd-designer.
|
||||
|
||||
While the release is mostly automated, there are still a few steps, that need manual examination.
|
||||
|
||||
## Overview
|
||||
|
||||
This page gives an overview which tasks are automated to do a full release of PMD. This knowledge is
|
||||
required in order to verify that the release was successful or in case the automated process fails for
|
||||
some reason. Then individual steps need to be executed manually. Because the build is reproducible, these
|
||||
steps can be repeated again if the same tag is used.
|
||||
steps can be repeated if the same tag is used.
|
||||
|
||||
There is one special case in this project: The release of PMD is done in two steps:
|
||||
There is one special case in this project: As outlined above, the release of PMD consists of two phases or parts:
|
||||
1. All modules except pmd-cli and pmd-dist are released. That means, pmd-core and all the language modules
|
||||
are released. This is, so that these libs can be used by pmd-designer to create a new release.
|
||||
2. pmd-cli and pmd-dist are released afterwards. Both depend on pmd-designer, and this two-step release
|
||||
2. pmd-cli and pmd-dist are released after that. Both depend on pmd-designer, and this two-step release
|
||||
process is used for now to break the cycling release dependency.
|
||||
|
||||
The three main steps are:
|
||||
|
||||
* Preparations (which creates the tags) - use `do-release.sh` for that
|
||||
* Preparations (which sets the versions and creates the tags) - use `do-release.sh` for that
|
||||
* The actual release (which is automated) - GitHub Actions will build the tags when they have been pushed.
|
||||
* Prepare the next release (make sure the current main branch is ready for further development)
|
||||
|
||||
@ -57,9 +61,9 @@ Also make sure, that the repo "pmd.github.io" is locally up-to-date and has no l
|
||||
Before the release, you need to verify the release notes: Does it contain all the relevant changes for the
|
||||
release? Is it formatted properly? Are there any typos? Does it render properly?
|
||||
|
||||
As the release notes are part of the source code, it is not simple to change it afterwards. While the source
|
||||
code for a tag cannot be changed anymore, the published release notes on the github releases pages or the
|
||||
new posts can be changed afterwards (although that's an entirely manual process).
|
||||
As the release notes are part of the source code, it is not that simple to change it afterward. While the source
|
||||
code for a tag cannot be changed anymore, the published release notes on the GitHub Releases pages or the
|
||||
news posts can be changed afterward (although that's an entirely manual process).
|
||||
|
||||
You can find the release notes here: `docs/pages/release_notes.md`.
|
||||
|
||||
@ -86,7 +90,12 @@ Add the new rules as comments to the quickstart rulesets:
|
||||
|
||||
The designer lives at [pmd/pmd-designer](https://github.com/pmd/pmd-designer).
|
||||
Update property `pmd-designer.version` in **pom.xml** to reference the new version, that will be released
|
||||
shortly. Note: This version does at the moment not exist.
|
||||
shortly. Note: This version does at the moment not exist. That means, that a full build of the sources
|
||||
will currently fail. That's why the first phase of the release will build only pmd-core and languages but
|
||||
not pmd-cli and pmd-dist.
|
||||
|
||||
In case, there is no need for a new pmd-designer version, we could stick to the latest already available version.
|
||||
Then we can skip the release of pmd-designer and immediately start the second phase of the release.
|
||||
|
||||
Starting with PMD 6.23.0 we'll provide small statistics for every release. This needs to be added
|
||||
to the release notes as the last section. To count the closed issues and pull requests, the milestone
|
||||
@ -115,7 +124,7 @@ Check in all (version) changes to branch master or any other branch, from which
|
||||
|
||||
### The Homepage
|
||||
|
||||
The github repo `pmd.github.io` hosts the homepage for [https://pmd.github.io](https://pmd.github.io).
|
||||
The GitHub repo `pmd.github.io` hosts the homepage for [https://pmd.github.io](https://pmd.github.io).
|
||||
All the following tasks are to be done in this repo.
|
||||
|
||||
The new version needs to be entered into `_config.yml`, e.g.:
|
||||
@ -132,7 +141,22 @@ in this list, so remove the oldest version.
|
||||
Then create a new page for the new release, e.g. `_posts/2021-04-24-PMD-6.34.0.md` and copy
|
||||
the release notes into this page. This will appear under the news section.
|
||||
|
||||
Check in all (version) changes to branch master:
|
||||
Note: The release notes typically contain some Jekyll macros for linking to the rule pages. These macros won't
|
||||
work in a plain markdown version. Therefore, you need to render the release notes first:
|
||||
|
||||
```shell
|
||||
# install bundles needed for rendering release notes
|
||||
bundle config set --local path vendor/bundle
|
||||
bundle config set --local with release_notes_preprocessing
|
||||
bundle install
|
||||
|
||||
RELEASE_NOTES_POST="_posts/$(date -u +%Y-%m-%d)-PMD-${RELEASE_VERSION}.md"
|
||||
echo "Generating ../pmd.github.io/${RELEASE_NOTES_POST}..."
|
||||
NEW_RELEASE_NOTES=$(bundle exec docs/render_release_notes.rb docs/pages/release_notes.md | tail -n +6)
|
||||
cat > "../pmd.github.io/${RELEASE_NOTES_POST}" <<EOF
|
||||
```
|
||||
|
||||
Check in all (version, blog post) changes to branch master:
|
||||
|
||||
$ git commit -a -m "Prepare pmd release <version>"
|
||||
$ git push
|
||||
@ -140,33 +164,45 @@ Check in all (version) changes to branch master:
|
||||
|
||||
## The actual release
|
||||
|
||||
The actual release starts with one last local command: calling **maven-release-plugin**.
|
||||
The actual release is done by changing the versions, creating a tag and pushing this tag. Previously this was done
|
||||
by calling _maven-release-plugin_, but these steps are done without the plugin to have more control. And since we
|
||||
might reference a not yet released pmd-designer version, the test-build will fail.
|
||||
|
||||
This plugin changes the version by basically removing the "-SNAPSHOT" suffix, builds the changed project
|
||||
locally, commits the version change, creates
|
||||
a new tag from this commit, changes the version of the project to the next snapshot, commits this change
|
||||
and pushes everything.
|
||||
We first change the version of PMD and all modules by basically removing the "-SNAPSHOT" suffix, building the changed
|
||||
project locally with tests (and with skipping pmd-cli and pmd-dist) in order to be sure, everything is in working
|
||||
order. Then the version changes are committed and a new release tag is created. Then, the versions are changed to
|
||||
the next snapshot. As last step, everything is pushed.
|
||||
|
||||
`RELEASE_VERSION` is the version of the release. It is reused for the tag. `DEVELOPMENT_VERSION` is the
|
||||
next snapshot version after the release.
|
||||
next snapshot version after the release. Skipping the builds of pmd-cli and pmd-dist is done by setting
|
||||
the property `skip-cli-dist`.
|
||||
|
||||
```shell
|
||||
RELEASE_VERSION=6.34.0
|
||||
DEVELOPMENT_VERSION=6.35.0-SNAPSHOT
|
||||
./mvnw -B release:clean release:prepare \
|
||||
-Dtag="pmd_releases/${RELEASE_VERSION}" \
|
||||
-DreleaseVersion="${RELEASE_VERSION}" \
|
||||
-DdevelopmentVersion="${DEVELOPMENT_VERSION}" \
|
||||
-DscmCommentPrefix="[release] " \
|
||||
-Darguments='-Pgenerate-rule-docs,!cli-dist' \
|
||||
'-Pgenerate-rule-docs,!cli-dist'
|
||||
# Change version in the POMs to ${RELEASE_VERSION} and update build timestamp
|
||||
./mvnw --quiet versions:set -DnewVersion="${RELEASE_VERSION}" -DgenerateBackupPoms=false -DupdateBuildOutputTimestampPolicy=always
|
||||
# Transform the SCM information in the POM
|
||||
sed -i "s|<tag>.\+</tag>|<tag>pmd_releases/${RELEASE_VERSION}</tag>|" pom.xml
|
||||
# Run the project tests against the changed POMs to confirm everything is in running order (skipping cli and dist)
|
||||
./mvnw clean verify -Dskip-cli-dist -Pgenerate-rule-docs
|
||||
# Commit and create tag
|
||||
git commit -a -m "[release] prepare release pmd_releases/${RELEASE_VERSION}"
|
||||
git tag -m "[release] copy for tag pmd_releases/${RELEASE_VERSION}" "pmd_releases/${RELEASE_VERSION}"
|
||||
# Update POMs to set the new development version ${DEVELOPMENT_VERSION}
|
||||
./mvnw --quiet versions:set -DnewVersion="${DEVELOPMENT_VERSION}" -DgenerateBackupPoms=false -DupdateBuildOutputTimestampPolicy=never
|
||||
sed -i "s|<tag>.\+</tag>|<tag>HEAD</tag>|" pom.xml
|
||||
git commit -a -m "[release] prepare for next development iteration"
|
||||
# Push branch and tag pmd_releases/${RELEASE_VERSION}
|
||||
git push origin "${CURRENT_BRANCH}"
|
||||
git push origin tag "pmd_releases/${RELEASE_VERSION}"
|
||||
```
|
||||
|
||||
Once the maven plugin has pushed the tag, github actions will start and build a new version from this tag. Since
|
||||
it is a tag build and a released version build, the build script will do a couple of additional stuff.
|
||||
Once we have pushed the tag, GitHub Actions take over and build a new version from this tag. Since
|
||||
it is a tag build and a release version (version without SNAPSHOT), the build script will do a couple of additional stuff.
|
||||
This is all automated in `.ci/build.sh`.
|
||||
|
||||
Note: The profile "cli-dist" is deactivated, so this release command doesn't include pmd-cli and pmd-dist.
|
||||
Note: The property "skip-cli-dist" is activated, so this release command doesn't include pmd-cli and pmd-dist.
|
||||
They will be released separately after pmd-designer is released. Since pmd-dist is not included in this first
|
||||
step, no binaries are created yet.
|
||||
|
||||
@ -174,13 +210,15 @@ Here is, what happens:
|
||||
|
||||
* Deploy and release the build to maven central, so that it can be downloaded from
|
||||
<https://repo.maven.apache.org/maven2/net/sourceforge/pmd/pmd/>. This is done automatically, if
|
||||
all unit tests pass and the build doesn't fail for any other reason.
|
||||
The plugin [nexus-staging-maven-plugin](https://github.com/sonatype/nexus-maven-plugins/tree/master/staging/maven-plugin) is used for that.
|
||||
the build doesn't fail for any reason. Note, that unit tests are not executed anymore, since they have been
|
||||
run already locally before pushing the tag.
|
||||
The plugin [nexus-staging-maven-plugin](https://github.com/sonatype/nexus-maven-plugins/tree/master/staging/maven-plugin)
|
||||
is used to upload and publish the artifacts to maven central.
|
||||
* Create a draft release on GitHub and upload the release notes from `docs/pages/release_notes.md`.
|
||||
Note: During the process, the release is a draft mode and not visible yet.
|
||||
At the end of the process, the release will be published.
|
||||
* Render the documentation in `docs/` with `bundle exec jekyll build` and create a zip file from it.
|
||||
* Upload the doc zip file to the current github release under <https://github.com/pmd/pmd/releases> and
|
||||
* Upload the doc zip file to the current (draft) GitHub Release under <https://github.com/pmd/pmd/releases> and
|
||||
to <https://sourceforge.net/projects/pmd/files/pmd/>.
|
||||
* Upload the documentation to <https://docs.pmd-code.org>, e.g. <https://docs.pmd-code.org/pmd-doc-6.34.0/> and
|
||||
create a symlink, so that <https://docs.pmd-code.org/latest/> points to the new version.
|
||||
@ -196,40 +234,21 @@ Here is, what happens:
|
||||
<https://pmd.sourceforge.io/pmd-6.34.0/>. All previously copied versions are listed
|
||||
under <https://pmd.sourceforge.io/archive.phtml>.
|
||||
|
||||
The release on github actions currently takes about 30-45 minutes. Once this is done, you
|
||||
The release on GitHub Actions currently takes about 30-45 minutes. Once this is done, you
|
||||
can proceed with releasing pmd designer, see <https://github.com/pmd/pmd-designer/blob/master/releasing.md>.
|
||||
Make sure to release the version, you have used earlier for the property `pmd-designer.version`.
|
||||
|
||||
Once the pmd-designer release is done, you can proceed with part 2. We'll checkout the release tag, add
|
||||
a new commit with the changed versions for pmd-cli and pmd-dist on top of it and create a new tag:
|
||||
Once the pmd-designer release is done, you can proceed with part 2. This is simply triggering manually
|
||||
a build on GitHub Actions: <https://github.com/pmd/pmd/actions/workflows/build.yml> from the same tag again, but
|
||||
with the parameter "build_cli_dist_only" set to "true". With this parameter, the script `.ci/build.sh` will
|
||||
perform the following steps:
|
||||
|
||||
```shell
|
||||
git checkout "pmd_releases/${RELEASE_VERSION}"
|
||||
./mvnw versions:update-parent -DparentVersion="${RELEASE_VERSION}" -DskipResolution=true -DgenerateBackupPoms=false -pl pmd-cli,pmd-dist
|
||||
git add pmd-cli/pom.xml pmd-dist/pom.xml
|
||||
git commit -m "[release] prepare release pmd_releases/${RELEASE_VERSION}-dist"
|
||||
git tag -m "[release] copy for tag pmd_releases/${RELEASE_VERSION}-dist" "pmd_releases/${RELEASE_VERSION}-dist"
|
||||
git push origin tag "pmd_releases/${RELEASE_VERSION}-dist"
|
||||
git checkout master
|
||||
# make sure parent reference is correct
|
||||
./mvnw versions:update-parent -DparentVersion="${DEVELOPMENT_VERSION}" -DskipResolution=true -DgenerateBackupPoms=false -pl pmd-cli,pmd-dist
|
||||
git add pmd-cli/pom.xml pmd-dist/pom.xml
|
||||
git commit -m "Prepare next development version [skip ci]"
|
||||
git push origin "${CURRENT_BRANCH}"
|
||||
```
|
||||
|
||||
Since pmd-cli/pmd-dist were not part of the first maven-release-plugin call, we might need to fix the parent references
|
||||
manually to set to the new development version.
|
||||
|
||||
The new created tag ends with the suffix `-dist`, and this is used as a marker for the GitHub action. The same build
|
||||
script `.ci/build.sh` is executed, but now, it does the following steps:
|
||||
|
||||
* Build only modules pmd-cli and pmd-dist
|
||||
* Upload the new binaries to the existing draft release under <https://github.com/pmd/pmd/releases>.
|
||||
* Build only modules pmd-cli and pmd-dist (via maven parameter `-pl pmd-cli,pmd-dist`).
|
||||
* Upload the new binaries to the existing draft GitHub Release under <https://github.com/pmd/pmd/releases>.
|
||||
* Upload the new binaries additionally to sourceforge, so that they can be downloaded from
|
||||
<https://sourceforge.net/projects/pmd/files/pmd/>, including the release notes.
|
||||
* After all this is done, the release on github (<https://github.com/pmd/pmd/releases>) is published
|
||||
and the news post on sourceforge (https://sourceforge.net/p/pmd/news/> is published as well.
|
||||
<https://sourceforge.net/projects/pmd/files/pmd/>.
|
||||
* After all this is done, the release on GitHub (<https://github.com/pmd/pmd/releases>) is published
|
||||
and the news post on sourceforge <https://sourceforge.net/p/pmd/news/> is published as well.
|
||||
* The new binary at <https://sourceforge.net/projects/pmd/files/pmd/> is
|
||||
selected as the new default for PMD.
|
||||
* As a last step, a new baseline for the [regression tester](https://github.com/pmd/pmd-regression-tester)
|
||||
@ -326,7 +345,7 @@ This is a {{ site.pmd.release_type }} release.
|
||||
{%endraw%}
|
||||
|
||||
|
||||
Finally commit and push the changes:
|
||||
Finally, commit and push the changes:
|
||||
|
||||
$ git commit -m "Prepare next development version"
|
||||
$ git push origin master
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -9,7 +9,7 @@ adr_status: "Accepted"
|
||||
last_updated: September 2022
|
||||
---
|
||||
|
||||
# Context
|
||||
## Context
|
||||
|
||||
PMD has grown over 20 years as an open-source project. Along the way many decisions have been made, but they are not
|
||||
explicitly documented. PMD is also developed by many individuals and the original developers might
|
||||
@ -33,7 +33,7 @@ by Michael Nygard.
|
||||
There are many templates around to choose from. <https://github.com/joelparkerhenderson/architecture-decision-record>
|
||||
gives a nice summary. The page <https://adr.github.io/> gives a good overview on ADR and for adr-related tooling.
|
||||
|
||||
# Decision
|
||||
## Decision
|
||||
|
||||
We will document the decisions we make as a project as a collection of "Architecture Decision Records".
|
||||
In order to keep it simple, we will use only a simple template proposed by Michael Nygard.
|
||||
@ -49,11 +49,11 @@ The change can be to amend the ADR or to challenge it and maybe deprecate it. A
|
||||
"Change History" section should be added to summary the change. When maintainer consensus is reached
|
||||
during the PR review, then the PR can be merged and the ADR is updated.
|
||||
|
||||
# Status
|
||||
## Status
|
||||
|
||||
{{ page.adr_status }} (Last updated: {{ page.last_updated }})
|
||||
|
||||
# Consequences
|
||||
## Consequences
|
||||
|
||||
Explicitly documenting decisions has the benefit that new developers joining the projects know about the decisions
|
||||
and can read the context and consequences of the decisions. This will likely also improve the overall quality
|
||||
@ -61,7 +61,7 @@ as the decisions need to be formulated and written down. Everybody is on the sam
|
||||
|
||||
However, this also adds additional tasks, and it takes time to write down and document the decisions.
|
||||
|
||||
# Change History
|
||||
## Change History
|
||||
|
||||
2022-09-30: Status changed to "Accepted".
|
||||
|
||||
|
@ -9,7 +9,7 @@ adr_status: "Accepted"
|
||||
last_updated: September 2022
|
||||
---
|
||||
|
||||
# Context
|
||||
## Context
|
||||
|
||||
We currently use Kotlin only for unit tests at some places (e.g. pmd-lang-test module provides a couple of base
|
||||
test classes). We were cautious to expand Kotlin because of poor development support outside JetBrain's
|
||||
@ -26,7 +26,7 @@ However - PMD is a tool that deals with many, many languages anyway, so this is
|
||||
|
||||
Nevertheless, extending the usage of Kotlin within PMD can also increase contributions.
|
||||
|
||||
# Decision
|
||||
## Decision
|
||||
|
||||
We are generally open to the idea to increase usage of Kotlin within PMD. In order to gain experience
|
||||
and to keep it within bounds and therefore maintainable we came up with the following rules:
|
||||
@ -50,11 +50,11 @@ and to keep it within bounds and therefore maintainable we came up with the foll
|
||||
not make incompatible changes. If compatibility (binary or source) can't be maintained, then that would be a
|
||||
major version change.
|
||||
|
||||
# Status
|
||||
## Status
|
||||
|
||||
{{ page.adr_status }} (Last updated: {{ page.last_updated }})
|
||||
|
||||
# Consequences
|
||||
## Consequences
|
||||
|
||||
Allowing more Kotlin in PMD can attract new contributions. It might make it easier to develop small DSLs.
|
||||
In the future we might also consider to use other languages than Kotlin, e.g. for `pmd-scala` Scala might make sense.
|
||||
@ -64,7 +64,7 @@ when Kotlin is used. Eclipse can't be used practically anymore.
|
||||
|
||||
Maintaining a polyglot code base with multiple languages is likely to be more challenging.
|
||||
|
||||
# Change History
|
||||
## Change History
|
||||
|
||||
2022-09-30: Changed status to "Accepted".
|
||||
|
||||
|
187
docs/pages/pmd/projectdocs/decisions/adr-3.md
Normal file
187
docs/pages/pmd/projectdocs/decisions/adr-3.md
Normal file
@ -0,0 +1,187 @@
|
||||
---
|
||||
title: ADR 3 - API evolution principles
|
||||
sidebar: pmd_sidebar
|
||||
permalink: pmd_projectdocs_decisions_adr_3.html
|
||||
sidebaractiveurl: /pmd_projectdocs_decisions.html
|
||||
adr: true
|
||||
# Proposed / Accepted / Deprecated / Superseded
|
||||
adr_status: "Proposed"
|
||||
last_updated: December 2023
|
||||
---
|
||||
|
||||
<!-- https://github.com/joelparkerhenderson/architecture-decision-record/blob/main/templates/decision-record-template-by-michael-nygard/index.md -->
|
||||
|
||||
## Context
|
||||
|
||||
The API of PMD has been growing over the years and needed some cleanup. The goal is, to
|
||||
have a clear separation between a well-defined API and the implementation, which is internal.
|
||||
This should help us in future development.
|
||||
|
||||
Until PMD 7.0.0, all released public members and types were implicitly considered part
|
||||
of public PMD API, including inheritance-specific members (protected members, abstract methods).
|
||||
We have maintained those APIs with the goal to preserve full binary compatibility between minor releases,
|
||||
only breaking those APIs infrequently, for major releases.
|
||||
|
||||
PMD is used and integrated in many different tools such as IDE plugins or build plugins. These plugins
|
||||
use our public API and rely on it being stable, hence we tried to break it only infrequently.
|
||||
|
||||
In order to allow PMD to move forward at a faster pace, this implicit contract will
|
||||
be invalidated with PMD 7.0.0 and onwards. We now introduce more fine-grained distinctions between
|
||||
the type of compatibility support we guarantee for our libraries, and ways to make
|
||||
them explicit to clients of PMD.
|
||||
|
||||
The actual API development and marking some part of the API as internal or add new API is an ongoing task,
|
||||
that will need to be done everytime. We won't just define an API and then are done with it.
|
||||
The API will change as new features want to be implemented.
|
||||
|
||||
This decision document aims to document principles and guidelines that are used for PMD development.
|
||||
|
||||
## Decision
|
||||
|
||||
### Semantic Versioning
|
||||
|
||||
PMD and all its modules are versioned together. PMD uses [Semantic Versioning 2.0.0](https://semver.org/spec/v2.0.0.html).
|
||||
This means, that each PMD version consists of MAJOR.MINOR.PATCH components:
|
||||
|
||||
* MAJOR version is incremented for incompatible API changes
|
||||
* MINOR version is incremented for added functionality in a backwards compatible way
|
||||
* PATCH version is incremented for backward compatible bug fixes
|
||||
|
||||
Additional labels for release candidates might be used.
|
||||
|
||||
Incompatible API changes shouldn't be introduced lightly. See
|
||||
[FAQ: If even the tiniest backward incompatible changes to the public API require a major version bump, won’t I end up at version 42.0.0 very rapidly?](https://semver.org/spec/v2.0.0.html#if-even-the-tiniest-backward-incompatible-changes-to-the-public-api-require-a-major-version-bump-wont-i-end-up-at-version-4200-very-rapidly).
|
||||
|
||||
### Project structure and Java base packages names
|
||||
|
||||
PMD is mainly developed in the Java programming language. The build tool is Maven and the PMD build consists
|
||||
of several maven modules.
|
||||
|
||||
* All packages belonging to a given module should have a common package prefix.
|
||||
* Given a package name, it should be easy to figure out to which module this package belongs. There is a 1:1 mapping
|
||||
between maven module and package. This rule helps to find the source code for any fully qualified (Java) class name.
|
||||
* Two modules must not define the same packages. That means, it is not allowed that any given package spans more than
|
||||
one module. Otherwise, the mapping between module and package wouldn't be unambiguous.
|
||||
* The base package for all PMD source code is `net.sourceforge.pmd`. There are many different sub packages.
|
||||
* The core module `pmd-core` uses directly the base package as the only module. All other modules must use
|
||||
specific sub packages.
|
||||
* Language modules use the base package `net.sourceforge.pmd.lang.<language id>`.
|
||||
E.g. `pmd-java` uses the package `net.sourceforge.pmd.lang.java`.
|
||||
* All other modules use the base package `net.sourceforge.pmd.<module>`,
|
||||
E.g. `pmd-cli` uses the package `net.sourceforge.pmd.cli`.
|
||||
|
||||
### Criteria for public API
|
||||
|
||||
Public API is
|
||||
|
||||
* API needed to execute PMD analysis
|
||||
* Renderers
|
||||
* RuleSet XML Schema
|
||||
* Configuration
|
||||
* Ant Tasks
|
||||
* API needed to implement custom rules
|
||||
* AST structure and classes of languages (incl. AST structure for XPath rules)
|
||||
* XPath functions
|
||||
* Language Symbol Table / Metrics / Type Resolution (Not the implementation)
|
||||
|
||||
**Not** public API is
|
||||
|
||||
* Anything in packages `internal` and `impl`
|
||||
* Inheritance-specific members of AST related classes and interfaces. E.g. adding a member to an
|
||||
interface shouldn't be considered API breaking
|
||||
* Setters in AST classes are private. They are only used in the parser.
|
||||
|
||||
### Separation between public API, internal and implementation
|
||||
|
||||
All packages are considered to be public API by default, with **two exceptions**:
|
||||
|
||||
* Any package that contains an `internal` segment is considered internal. E.g. `net.sourceforge.pmd.internal`.
|
||||
*Internal API* is meant for use *only* by the main PMD codebase. Internal types and methods
|
||||
may be modified in any way, or even removed, at any time without a MAJOR version change.
|
||||
|
||||
The `@InternalApi` annotation will be used for types that have to live outside of
|
||||
these packages, e.g. methods of a public type that shouldn't be used outside PMD (again,
|
||||
these can be removed anytime).
|
||||
|
||||
* Any package that contains an `impl` segment is considered internal. E.g. `net.sourceforge.pmd.lang.impl`.
|
||||
These packages contain base classes that are needed for extending PMD (like adding a new language).
|
||||
These can change at any time without a MAJOR version change.
|
||||
|
||||
In a later version, the `impl` packages could be promoted as a public API for implementing new
|
||||
languages for PMD outside the main monorepo. In that sense, e.g. the module `pmd-java` is allowed
|
||||
to depend on `impl` packages of `pmd-core`, but ideally it doesn't depend on `internal` packages of
|
||||
`pmd-core` (or any other module). However, for now, the `impl` packages are **explicitly considered
|
||||
internal** until this decision is revised.
|
||||
|
||||
### Deprecation and removing of old APIs
|
||||
|
||||
* APIs can be deprecated at any time (even in PATCH versions). Deprecated APIs are marked with the
|
||||
`@Deprecated` annotation.
|
||||
* Deprecations should be listed in the release notes.
|
||||
* Deprecated APIs can only be removed with a MAJOR version change.
|
||||
|
||||
### Experimental APIs
|
||||
|
||||
* New features often introduce new APIs. These new APIs can be marked with the annotation `@Experimental` at
|
||||
the class or method level.
|
||||
* APIs marked with the `@Experimental` annotation are subject to change and are considered **not stable**.
|
||||
They can be modified in any way, or even removed, at any time. You should not use or rely
|
||||
on them in any production code. They are purely to allow broad testing and feedback.
|
||||
* Experimental APIs can be introduced or removed with at least a MINOR version change.
|
||||
These experimental APIs should be listed in the release notes.
|
||||
* Experimental APIs can be promoted to Public APIs with at least a MINOR version change.
|
||||
|
||||
### Guidelines for AST classes
|
||||
|
||||
AST classes of the individual language modules are used by custom rule implementations and are considered
|
||||
Public API in general. Rules only read the AST and do not need to modify it.
|
||||
|
||||
In order to minimize the public API surface of AST classes, the following guidelines apply:
|
||||
|
||||
* Concrete AST classes should be final, to avoid custom subclasses.
|
||||
* Concrete AST classes should only have a package private constructor to avoid manual instantiation.
|
||||
Only the parser of the language (which lives in the same package) should be able to create new instances
|
||||
of AST classes.
|
||||
* Concrete AST classes should not have public setters. All setters should be package private, so that
|
||||
only the parser of the language can call the setters during AST construction.
|
||||
|
||||
Non-concrete AST classes (like base classes or common interfaces) should follow similar guidelines:
|
||||
* Only package private constructor
|
||||
* Only package private setters
|
||||
|
||||
### Summary of the annotations
|
||||
|
||||
* `@InternalApi` (`net.sourceforge.pmd.annotation.InternalApi`)
|
||||
|
||||
This annotation is used for API members that are not publicly supported API but have to live in
|
||||
public packages (outside `internal` packages).
|
||||
Such members may be removed, renamed, moved, or otherwise broken at any time and should not be
|
||||
relied upon outside the main PMD codebase.
|
||||
|
||||
* `@Experimental` (`net.sourceforge.pmd.annotation.Experimental`)
|
||||
|
||||
API members marked with the `@Experimental` annotation at the class or method level are subject to change.
|
||||
It is an indication that the feature is in experimental, unstable state.
|
||||
The API members can be modified in any way, or even removed, at any time, without warning.
|
||||
You should not use or rely on them in any production code. They are purely to allow broad testing and feedback.
|
||||
|
||||
* `@Deprecated` (`java.lang.Deprecated`)
|
||||
|
||||
API members marked with the `@Deprecated` annotation at the class or method level will remain supported
|
||||
until the next major release, but it is recommended to stop using them. These members might be
|
||||
removed with the next MAJOR release.
|
||||
|
||||
## Status
|
||||
|
||||
{{ page.adr_status }} (Last updated: {{ page.last_updated }})
|
||||
|
||||
## Consequences
|
||||
|
||||
* Clearly defining the API PMD provides will help to further modularize PMD using the
|
||||
Java [Module System](https://openjdk.org/jeps/261).
|
||||
* Simpler decisions when to increase MAJOR, MINOR of PATCH version.
|
||||
* Refactoring of the implementation is possible without affecting public API.
|
||||
|
||||
## Change History
|
||||
|
||||
2023-12-01: Proposed initial version.
|
@ -11,23 +11,23 @@ last_updated: July 2022
|
||||
|
||||
<!-- https://github.com/joelparkerhenderson/architecture-decision-record/blob/main/templates/decision-record-template-by-michael-nygard/index.md -->
|
||||
|
||||
# Context
|
||||
## Context
|
||||
|
||||
What is the issue that we're seeing that is motivating this decision or change?
|
||||
|
||||
# Decision
|
||||
## Decision
|
||||
|
||||
What is the change that we're proposing and/or doing?
|
||||
|
||||
# Status
|
||||
## Status
|
||||
|
||||
{{ page.adr_status }} (Last updated: {{ page.last_updated }})
|
||||
|
||||
# Consequences
|
||||
## Consequences
|
||||
|
||||
What becomes easier or more difficult to do because of this change?
|
||||
|
||||
# Change History
|
||||
## Change History
|
||||
|
||||
YYYY-MM-DD: Add xyz.
|
||||
|
||||
|
@ -120,11 +120,6 @@ The tool comes with a rather extensive help text, simply running with `--help`!
|
||||
Language detection is only influenced by file extensions and the `--force-language` option.</p>
|
||||
<p>See also [Supported Languages](#supported-languages).</p>"
|
||||
%}
|
||||
{% include custom/cli_option_row.html options="-language,-l"
|
||||
option_arg="lang"
|
||||
description="Specify the language PMD should use. Used together with `-version`. See also [Supported Languages](#supported-languages).
|
||||
<p><span class=\"label label-default\">Deprecated</span> since PMD 6.52.0. Use `--use-version` instead.</p>"
|
||||
%}
|
||||
{% include custom/cli_option_row.html options="--minimum-priority"
|
||||
option_arg="priority"
|
||||
description="Rule priority threshold; rules with lower priority than configured here won't be used.
|
||||
@ -303,6 +298,6 @@ Alternatively, you can create a filelist to only analyze files with a given exte
|
||||
id="file-list"
|
||||
linux="find src/ -name \"*.ext\" > filelist.txt
|
||||
pmd check --file-list filelist.txt -f text -R ruleset.xml --force-language xml"
|
||||
windows="for /r src/ %i in (*.ext) do echo %i >> filelist.txt
|
||||
windows="for /r src\ %i in (*.ext) do echo %i >> filelist.txt
|
||||
pmd.bat check --file-list filelist.txt -f text -R ruleset.xml --force-language xml" %}
|
||||
|
||||
|
@ -3,7 +3,7 @@ title: Defining rule properties
|
||||
short_title: Defining rule properties
|
||||
tags: [extending, userdocs]
|
||||
summary: "Learn how to define your own properties both for Java and XPath rules."
|
||||
last_updated: August 2022 (7.0.0)
|
||||
last_updated: December 2023 (7.0.0)
|
||||
permalink: pmd_userdocs_extending_defining_properties.html
|
||||
author: Hooper Bloob <hooperbloob@users.sourceforge.net>, Romain Pelisse <rpelisse@users.sourceforge.net>, Clément Fournier <clement.fournier76@gmail.com>
|
||||
---
|
||||
@ -12,17 +12,19 @@ author: Hooper Bloob <hooperbloob@users.sourceforge.net>, Romain Pelisse <rpelis
|
||||
{% jdoc_nspace :PF props::PropertyFactory %}
|
||||
|
||||
Rule properties are a way to make your rules configurable directly from the
|
||||
ruleset XML. Their usage is described on the [Configuring Rules](pmd_userdocs_configuring_rules.html#rule-properties) page.
|
||||
ruleset XML. Their usage is described on the
|
||||
[Configuring Rules](pmd_userdocs_configuring_rules.html#rule-properties) page.
|
||||
|
||||
If you're a rule developer, you may want to think about what would be useful for
|
||||
a user of your rule to parameterise. It could be a numeric report level, a boolean
|
||||
a user of your rule to parameterize. It could be a numeric report level, a boolean
|
||||
flag changing the behaviour of your rule... Chances are there *is* some detail
|
||||
that can be abstracted away from your implementation, and in that case, this
|
||||
page can help you squeeze that sweet flexibility out of your rule.
|
||||
|
||||
## Overview of properties
|
||||
|
||||
The basic thing you need to do as a developer is to define a **property descriptor** and declare that your rule uses it. A property descriptor defines a number of attributes for your property:
|
||||
The basic thing you need to do as a developer is to define a **property descriptor** and declare
|
||||
that your rule uses it. A property descriptor defines a number of attributes for your property:
|
||||
* Its *name*, with which the user will refer to your property;
|
||||
* Its *description*, for documentation purposes;
|
||||
* Its *default value*
|
||||
@ -35,9 +37,15 @@ All of these attributes can be specified in a single Java statement (or XML elem
|
||||
The procedure to define a property is quite straightforward:
|
||||
* Create a property descriptor of the type you want, by using a
|
||||
builder from {% jdoc :PF %}
|
||||
* Call {% jdoc !a!props::PropertySource#definePropertyDescriptor(props::PropertyDescriptor) %}` in the rule's noarg constructor.
|
||||
* Call {% jdoc !a!props::PropertySource#definePropertyDescriptor(props::PropertyDescriptor) %}
|
||||
in the rule's noarg constructor.
|
||||
|
||||
You can then retrieve the value of the property at any time using {% jdoc !a!props::PropertySource#getProperty(props::PropertyDescriptor) %}.
|
||||
You can then retrieve the value of the property at any time using
|
||||
{% jdoc !a!props::PropertySource#getProperty(props::PropertyDescriptor) %}.
|
||||
|
||||
Note: The base class for all rule implementations is {% jdoc core::lang.rule.AbstractRule %}, which
|
||||
is a {% jdoc props::PropertySource %}. So you can directly call `definePropertyDescriptor(...)`
|
||||
or `getProperty(...)` within your rule.
|
||||
|
||||
### Creating a descriptor
|
||||
|
||||
@ -51,7 +59,8 @@ PropertyFactory.stringProperty("myProperty")
|
||||
.build();
|
||||
```
|
||||
|
||||
This is fairly more readable than a constructor call, but keep in mind the description and the default value are not optional.
|
||||
This is fairly more readable than a constructor call, but keep in mind the description
|
||||
and the default value are not optional.
|
||||
|
||||
For **numeric properties**, you can add constraints on the range of acceptable values, e.g.
|
||||
```java
|
||||
@ -103,7 +112,9 @@ doesn't change).
|
||||
|
||||
## For XPath rules
|
||||
|
||||
XPath rules can also define their own properties. To do so, you must add a `property` element in the `properties` element of your rule, which **declares the `type` attribute**. This attribute conditions what type the underlying property has, and can have the following values:
|
||||
XPath rules can also define their own properties. To do so, you must add a `property` element in
|
||||
the `properties` element of your rule, which **declares the `type` attribute**. This attribute conditions
|
||||
what type the underlying property has, and can have the following values:
|
||||
|
||||
| `type` attribute | XSD type |
|
||||
|------------------|------------|
|
||||
@ -115,7 +126,7 @@ XPath rules can also define their own properties. To do so, you must add a `prop
|
||||
| Character | xs:string |
|
||||
| Regex | xs:string |
|
||||
|
||||
Note that enumerated properties are not available in XPath rules (yet?).
|
||||
Note that enumerated properties are not available in XPath rules.
|
||||
|
||||
Properties defined in XPath also *must* declare the `description` attribute.
|
||||
Numeric properties also expect the `min` and `max` attributes for now. Here are
|
||||
@ -134,9 +145,11 @@ You can then use the property in XPath with the syntax `$propertyName`, for exam
|
||||
<property name="maxStatements" type="Integer" value="10" min="1" max="40"
|
||||
description="Max number of statements per method"/>
|
||||
<property name="xpath">
|
||||
<value>
|
||||
<![CDATA[
|
||||
//MethodDeclaration/Block[count(//BlockStatement) > $maxStatements]
|
||||
]]></property>
|
||||
//MethodDeclaration/Block[count(./*) > $maxStatements]
|
||||
]]></value>
|
||||
</property>
|
||||
</properties>
|
||||
</rule>
|
||||
```
|
||||
@ -157,16 +170,17 @@ with a backslash when needed.
|
||||
<property name="reportedIdentifiers" type="List[String]" value="foo,bar"
|
||||
description="A StringMultiProperty." />
|
||||
<property name="xpath">
|
||||
<![CDATA[
|
||||
//VariableDeclaratorId[@Image = $reportedIdentifiers]
|
||||
]]></property>
|
||||
<value><![CDATA[
|
||||
//VariableId[@Image = $reportedIdentifiers]
|
||||
]]></value>
|
||||
</property>
|
||||
</properties>
|
||||
</rule>
|
||||
```
|
||||
|
||||
Notice that in the example above, `@Image = $reportedIdentifiers` doesn't test
|
||||
`@Image` for equality with the whole sequence `('foo', 'bar')`, it tests whether
|
||||
the sequence *contains* `@Image`. That is, the above rule will report all variables
|
||||
Notice that in the example above, `@Name = $reportedIdentifiers` doesn't test
|
||||
`@Name` for equality with the whole sequence `('foo', 'bar')`, it tests whether
|
||||
the sequence *contains* `@Name`. That is, the above rule will report all variables
|
||||
named `foo` or `bar`. All other XPath 2.0 [functions operating on sequences](https://www.w3.org/TR/xpath-functions/#sequence-functions)
|
||||
are supported.
|
||||
|
||||
|
@ -3,16 +3,19 @@ title: The rule designer
|
||||
short_title: Rule designer
|
||||
tags: [extending, userdocs]
|
||||
summary: "Learn about the usage and features of the rule designer."
|
||||
last_updated: August 2019 (6.18.0)
|
||||
last_updated: December 2023 (7.0.0)
|
||||
permalink: pmd_userdocs_extending_designer_reference.html
|
||||
author: Clément Fournier <clement.fournier76@gmail.com>
|
||||
---
|
||||
|
||||
## Installing, running, updating
|
||||
|
||||
The designer is part of PMD's binary distributions. To **install a distribution**, see the [documentation page about installing PMD](pmd_userdocs_installation.html).
|
||||
The designer is part of PMD's binary distributions. To **install a distribution**, see the
|
||||
[documentation page about installing PMD](pmd_userdocs_installation.html).
|
||||
|
||||
The app needs JRE 1.8 or above to run. Be aware that on JRE 11+, the JavaFX distribution should be installed separately. Visit the [JavaFX download page](https://gluonhq.com/products/javafx/) to download a distribution, extract it, and set the JAVAFX_HOME environment variable.
|
||||
The app needs JRE 1.8 or above to run. Be aware that on JRE 11+, the JavaFX distribution should be installed
|
||||
separately. Visit the [JavaFX download page](https://gluonhq.com/products/javafx/) to download a distribution,
|
||||
extract it, and set the JAVAFX_HOME environment variable.
|
||||
|
||||
If the bin directory of your PMD distribution is on your shell's path, then you can **launch the app** with
|
||||
|
||||
@ -26,18 +29,23 @@ If the bin directory of your PMD distribution is on your shell's path, then you
|
||||
|
||||
|
||||
This is to allow easy updating, and let you choose the dependencies you're interested in.
|
||||
The available language modules are those on the classpath of the app's JVM. That's why it's recommended to use the standard PMD startup scripts, which setup the classpath with the available PMD libraries.
|
||||
The available language modules are those on the classpath of the app's JVM. That's why it's recommended to use the
|
||||
standard PMD startup scripts, which setups the classpath with the available PMD libraries.
|
||||
|
||||
|
||||
### Updating
|
||||
|
||||
The latest version of the designer currently **works with PMD 6.12.0 and above**. You can simply replace pmd-ui-6.X.Y.jar with the [latest build](https://github.com/pmd/pmd-designer/releases) in the installation folder of your PMD distribution, and run it normally. Note that updating may cause some persisted state to get lost, for example the code snippet.
|
||||
The latest version of the designer currently **works with PMD 7.0.0 and above**. You can simply replace
|
||||
pmd-ui-7.X.Y.jar with the [latest build](https://github.com/pmd/pmd-designer/releases) in the installation
|
||||
folder of your PMD distribution, and run it normally. Note that updating may cause some persisted state
|
||||
to get lost, for example the code snippet.
|
||||
|
||||
|
||||
# Usage reference
|
||||
|
||||
|
||||
The rule designer is both a tool to inspect the tree on which PMD rules run on, and to write XPath rules in an integrated manner. This page describes the features that enable this.
|
||||
The rule designer is both a tool to inspect the tree on which PMD rules run on, and to write XPath rules
|
||||
in an integrated manner. This page describes the features that enable this.
|
||||
|
||||
|
||||
## AST inspection
|
||||
@ -57,8 +65,12 @@ You can enter source code in the middle zone.
|
||||
|
||||
There are several ways to focus a node for inspection:
|
||||
* **From the tree view:** just click on an item
|
||||
* Since 6.16.0, the tree view is also searchable: press CTRL+F when it's focused, or click on the `Search` button and enter a search query. You can cycle through results with `CTRL+TAB` or `CTRL+F3`, and cycle back with `CTRL+SHIFT+TAB` or `CTRL+SHIFT+F3`
|
||||
* **From the crumb bar:** the crumb bar below the code area shows the ancestors of the currently selected node, and is empty if you have no selection:
|
||||
* Since 6.16.0, the tree view is also searchable: press <kbd>Ctlr</kbd>+<kbd>F</kbd> when it's focused,
|
||||
or click on the `Search` button and enter a search query. You can cycle through results with
|
||||
<kbd>Ctrl</kbd>+<kbd>Tab</kbd> or <kbd>Ctrl</kbd>+<kbd>F3</kbd>, and cycle back with
|
||||
<kbd>Ctrl</kbd>+<kbd>Shift</kbd>+<kbd>Tab</kbd> or <kbd>Ctrl</kbd>+<kbd>Shift</kbd>+<kbd>F3</kbd>.
|
||||
* **From the crumb bar:** the crumb bar below the code area shows the ancestors of the currently selected node,
|
||||
and is empty if you have no selection:
|
||||
|
||||
{% details Ancestor crumb bar demo %}
|
||||
|
||||
@ -67,9 +79,10 @@ There are several ways to focus a node for inspection:
|
||||
{% enddetails %}
|
||||
|
||||
|
||||
* **From the source code:** maintain **CTRL** for a second until the code area becomes mostly blue. Then, each node you hover over on the code area will be selected automatically. Example:
|
||||
* **From the source code:** maintain <kbd>Ctrl</kbd> for a second until the code area becomes mostly blue.
|
||||
Then, each node you hover over on the code area will be selected automatically. Example:
|
||||
|
||||
{% details CTRL-hover selection demo %}
|
||||
{% details Ctrl-hover selection demo %}
|
||||
|
||||
![CTRL-hover selection demo](images/designer/hover-selection.gif)
|
||||
|
||||
@ -79,9 +92,12 @@ There are several ways to focus a node for inspection:
|
||||
|
||||
The left panel displays the following information:
|
||||
|
||||
* **XPath attributes:** this basically are all the attributes available in XPath queries. Those attributes are wrappers around a Java getter, so you can obtain documentation on the relevant Javadoc (that's not yet integrated into the designer)
|
||||
* **XPath attributes:** this basically are all the attributes available in XPath queries. Those attributes are
|
||||
wrappers around a Java getter, so you can obtain documentation on the relevant Javadoc (that's not yet
|
||||
integrated into the designer)
|
||||
* **Metrics:** for nodes that support it, the values of metrics are displayed in this panel
|
||||
* **Scopes:** This is java specific and displays some representation of the symbol table. You mostly don't need it. If you select eg a variable id, its usages are already highlighted automatically without opening the panel:
|
||||
* **Scopes:** This is java specific and displays some representation of the symbol table. You mostly don't need
|
||||
it. If you select e.g. a variable id, its usages are already highlighted automatically without opening the panel:
|
||||
|
||||
![Usages highlight example](images/designer/usages.gif)
|
||||
|
||||
@ -94,13 +110,16 @@ The bottom part of the UI is dedicated to designing XPath rules:
|
||||
![Bottom UI](images/designer/bottom-ui.png)
|
||||
|
||||
|
||||
The center is an XPath expression. As you type it, the matched nodes are updated on the right, and highlighted on the code area. Autocompletion is available on some languages.
|
||||
The center is an XPath expression. As you type it, the matched nodes are updated on the right, and highlighted
|
||||
on the code area. Autocompletion is available on some languages.
|
||||
|
||||
Note: you can keep several rules in the editor (there's a tab for each of them).
|
||||
|
||||
### Rule properties
|
||||
|
||||
Above the XPath expression area, the **"Properties"** button allows you to [define new properties](pmd_userdocs_extending_defining_properties.html#for-xpath-rules) for your prototype rule. You can also edit the existing properties.
|
||||
Above the XPath expression area, the **"Properties"** button allows you to
|
||||
[define new properties](pmd_userdocs_extending_defining_properties.html#for-xpath-rules) for your prototype rule.
|
||||
You can also edit the existing properties.
|
||||
|
||||
When you click on it, a small popup appears:
|
||||
|
||||
@ -114,17 +133,19 @@ The popup contains in the center a list of currently defined properties, display
|
||||
|
||||
#### Editing properties
|
||||
|
||||
The edition menu of a property looks like the following:
|
||||
The edit menu of a property looks like the following:
|
||||
|
||||
![Property edition popup](images/designer/property-edit.png)
|
||||
|
||||
* You can edit the name, description, expected type, and default value of the property
|
||||
* All this information is exported with the rule definition (see [Exporting to an XML rule](#exporting-to-an-xml-rule))
|
||||
* The default value is used unless you're editing a test case, and you set a custom value for the test case. TODO link
|
||||
* The default value is used unless you're editing a test case, and you set a custom value for the test case
|
||||
(see [Testing a rule](#testing-a-rule))
|
||||
|
||||
### Exporting to an XML rule
|
||||
|
||||
The little **export icon** next to the gear icon opens a menu to export your rule. This menu lets you fill-in the metadata necessary for an XPath rule to be included in a ruleset.
|
||||
The little **export icon** next to the gear icon opens a menu to export your rule. This menu lets you fill-in the
|
||||
metadata necessary for an XPath rule to be included in a ruleset.
|
||||
|
||||
{% details Rule export demo %}
|
||||
|
||||
@ -135,7 +156,9 @@ The little **export icon** next to the gear icon opens a menu to export your rul
|
||||
|
||||
## Testing a rule
|
||||
|
||||
PMD has its own XML format to describe rule tests and execute them using our test framework. The designer includes a test editor, which allows you to edit such files or create a new one directly as you edit the rule. This is what the panel left of the XPath expression area is for.
|
||||
PMD has its own XML format to describe rule tests and execute them using our test framework. The designer includes
|
||||
a test editor, which allows you to edit such files or create a new one directly as you edit the rule.
|
||||
This is what the panel left of the XPath expression area is for.
|
||||
|
||||
See also [the test framework documentation](pmd_userdocs_extending_testing.html).
|
||||
|
||||
@ -147,7 +170,8 @@ A rule test describes
|
||||
* the expected violations
|
||||
* a description (to name the test)
|
||||
|
||||
When executing a test, the rule is run on the source with the given configuration, then the violations it finds are compared to the expected ones.
|
||||
When executing a test, the rule is run on the source with the given configuration, then the violations it finds
|
||||
are compared to the expected ones.
|
||||
|
||||
### Adding tests
|
||||
|
||||
@ -161,11 +185,14 @@ Tests can be added in one of four ways:
|
||||
{% enddetails %}
|
||||
|
||||
|
||||
* **From the current source:** A new test case with a default configuration is created, with the source that is currently in the editor
|
||||
* **From the current source:** A new test case with a default configuration is created, with the source that is
|
||||
currently in the editor
|
||||
|
||||
* **With an empty source:** A new test case with a default configuration is created, with an empty source file. You must edit the source yourself then.
|
||||
* **With an empty source:** A new test case with a default configuration is created, with an empty source file.
|
||||
You must edit the source yourself then.
|
||||
|
||||
* **From an existing test case:** Each test case list item has a "Copy" button which duplicates the test and loads the new one.
|
||||
* **From an existing test case:** Each test case list item has a "Copy" button which duplicates the test and loads
|
||||
the new one.
|
||||
|
||||
### Test status
|
||||
|
||||
@ -185,7 +212,8 @@ A failing test (orange):
|
||||
|
||||
### Loading a test case
|
||||
|
||||
Each test has a piece of source, which you can edit independently of the others, when the test is **loaded in the editor**. Additional rule configuration options can be chosen when the test is loaded.
|
||||
Each test has a piece of source, which you can edit independently of the others, when the test is
|
||||
**loaded in the editor**. Additional rule configuration options can be chosen when the test is loaded.
|
||||
|
||||
Loading is done with the **Load** button:
|
||||
|
||||
@ -197,11 +225,13 @@ Loading is done with the **Load** button:
|
||||
{% enddetails %}
|
||||
|
||||
|
||||
Only one test case may be loaded at a time. If the loaded test is unloaded, the editor reverts back to the state it had before the first test case was loaded.
|
||||
Only one test case may be loaded at a time. If the loaded test is unloaded, the editor reverts back to the state
|
||||
it had before the first test case was loaded.
|
||||
|
||||
### Editing a loaded test case
|
||||
|
||||
When a test is loaded, *the source you edit in the code area is the source of the test*. Changes are independent from other tests, and from the piece of source that was previously in the editor.
|
||||
When a test is loaded, *the source you edit in the code area is the source of the test*. Changes are independent
|
||||
from other tests, and from the piece of source that was previously in the editor.
|
||||
|
||||
When a test is loaded, an additional toolbar shows up at the top of the code area:
|
||||
|
||||
@ -211,7 +241,8 @@ When a test is loaded, an additional toolbar shows up at the top of the code are
|
||||
|
||||
The **"Expected violations"** button is used to add or edit the expected violations.
|
||||
|
||||
Initially the list of violations is empty. You can add violations by **dragging and dropping nodes** onto the button or its popup, from any control that displays nodes. For example:
|
||||
Initially the list of violations is empty. You can add violations by **dragging and dropping nodes** onto the
|
||||
button or its popup, from any control that displays nodes. For example:
|
||||
|
||||
{% details Adding a violation demo %}
|
||||
|
||||
@ -234,7 +265,8 @@ This configuration will be used when executing the test to check its status.
|
||||
|
||||
### Exporting tests
|
||||
|
||||
When you're done editing tests, it's a good idea to save the test file to an XML file. Exporting is done using the **"Export"** button above the list of test cases:
|
||||
When you're done editing tests, it's a good idea to save the test file to an XML file. Exporting is done using
|
||||
the **"Export"** button above the list of test cases:
|
||||
|
||||
{% details Test export demo %}
|
||||
|
||||
@ -242,15 +274,9 @@ When you're done editing tests, it's a good idea to save the test file to an XML
|
||||
|
||||
{% enddetails %}
|
||||
|
||||
Note that the exported file does not contain any information about the rule. The rule must be in a ruleset file somewhere else.
|
||||
|
||||
If you want to use PMD's test framework to use the test file in your build, please refer to the conventions explained in [the test framework documentation](pmd_userdocs_extending_testing.html#where-to-place-the-test-code).
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Note that the exported file does not contain any information about the rule. The rule must be in a ruleset file
|
||||
somewhere else.
|
||||
|
||||
If you want to use PMD's test framework to use the test file in your build, please refer to the conventions
|
||||
explained in [the test framework documentation](pmd_userdocs_extending_testing.html#where-to-place-the-test-code).
|
||||
|
||||
|
@ -2,17 +2,17 @@
|
||||
title: Rule guidelines
|
||||
tags: [extending, userdocs]
|
||||
summary: "Rule Guidelines, or the last touches to a rule"
|
||||
last_updated: Mai 2023 (7.0.0)
|
||||
last_updated: December 2023 (7.0.0)
|
||||
permalink: pmd_userdocs_extending_rule_guidelines.html
|
||||
author: Xavier Le Vourch, Ryan Gustafson, Romain Pelisse
|
||||
---
|
||||
|
||||
|
||||
Here is a bunch of thing to do you may consider once your rule is “up and running”.
|
||||
Here is a bunch of things to do you may consider once your rule is “up and running”.
|
||||
|
||||
## How to define rules priority
|
||||
|
||||
Rule priority may, of course, changes a lot depending on the context of the project. However, you can use the
|
||||
Rule priority may, of course, change a lot depending on the context of the project. However, you can use the
|
||||
following guidelines to assert the legitimate priority of your rule:
|
||||
|
||||
1. **High: Change absolutely required.** Behavior is critically broken/buggy.
|
||||
@ -22,10 +22,10 @@ following guidelines to assert the legitimate priority of your rule:
|
||||
standards/style/good taste.
|
||||
5. **Low: Change highly optional.** Nice to have, such as a consistent naming policy for package/class/fields…
|
||||
|
||||
For instance, let's take the DoNotCallGarbageCollectionExplicitly rule
|
||||
For instance, let's take the rule {% rule java/errorprone/DoNotCallGarbageCollectionExplicitly %}
|
||||
(“Do not explicitly trigger a garbage collection.”). Calling GC is
|
||||
a bad idea, but it doesn't break the application. So we skip priority one. However, as explicit call to gc may really
|
||||
hinder application performances, we set for the priority 2 ("Medium High").
|
||||
hinder application performances, we settle for priority 2 ("Medium High").
|
||||
|
||||
## Correctness
|
||||
|
||||
@ -36,14 +36,13 @@ If your rule is stateful, make sure that it is reinitialized correctly for each
|
||||
|
||||
## Performance issues
|
||||
|
||||
When writing a new rule, using command line option `--benchmark` on a few rules can give an indication on how
|
||||
the rule compares to others. To get the full picture, use the `rulesets/internal/all-java.xml` ruleset
|
||||
with “--benchmark”.
|
||||
When writing a new rule, using command line option [`--benchmark`](pmd_userdocs_cli_reference.html#-benchmark)
|
||||
on a few rules can give an indication on how the rule compares to others. To get the full picture
|
||||
use the `rulesets/internal/all-java.xml` ruleset with `--benchmark`.
|
||||
|
||||
Rules which use the RuleChain to visit the AST are faster than rules which perform manual visitation of the AST.
|
||||
Rules which use the [RuleChain](pmd_userdocs_extending_writing_java_rules.html#economic-traversal-the-rulechain)
|
||||
to visit the AST are faster than rules which perform manual visitation of the AST.
|
||||
The difference is small for an individual Java rule, but when running 100s of rules, it is measurable.
|
||||
For XPath rules, the difference is extremely noticeable due to the overhead for AST navigation.
|
||||
Make sure your XPath rules using the RuleChain. If RuleChain can't be used for your XPath rule, then this fact
|
||||
is logged as a debug message.
|
||||
|
||||
(TODO How does one know except by running in a debugger or horrendous performance?).
|
||||
|
@ -3,7 +3,7 @@ title: Testing your rules
|
||||
tags: [extending, userdocs]
|
||||
summary: "Learn how to use PMD's simple test framework for unit testing rules."
|
||||
permalink: pmd_userdocs_extending_testing.html
|
||||
last_updated: January 2023
|
||||
last_updated: December 2023 (7.0.0)
|
||||
author: Andreas Dangel <andreas.dangel@adangel.org>
|
||||
---
|
||||
|
||||
@ -25,7 +25,7 @@ Each category-ruleset has a single abstract base test class, from which the indi
|
||||
We have one test class per rule, which executes all test cases for a single rule. The actual test cases are
|
||||
stored in separate XML files, for each rule a separate file is used.
|
||||
|
||||
All the test classes inherit from `net.sourceforge.pmd.testframework.PmdRuleTst`,
|
||||
All the test classes inherit from {% jdoc test::testframework.PmdRuleTst %},
|
||||
which provides the seamless integration with JUnit5. This base class determines the language, the category name
|
||||
and the rule name from the concrete test class. It then searches the test code on its own.
|
||||
E.g. the individual rule test class
|
||||
@ -41,8 +41,9 @@ test case and just execute this one.
|
||||
|
||||
## Where to place the test code
|
||||
|
||||
The `PmdRuleTst` class searches the XML file, that describes the test cases for a certain rule
|
||||
using the following convention:
|
||||
The {% jdoc test::testframework.PmdRuleTst %} class searches the XML file, that describes the test cases
|
||||
for a certain rule using the following convention:
|
||||
|
||||
The XML file is a test resource, so it is searched in the tree under `src/test/resources`.
|
||||
|
||||
The sub package `xml` of the test class's package should contain a file with the same name as the rule's name
|
||||
@ -58,8 +59,10 @@ The test code for the rule can be found in the file:
|
||||
|
||||
In general, the class name and file name pattern for the test class and data is this:
|
||||
|
||||
net.sourceforge.pmd.lang.<Language Terse Name>.rule.<Category Name>.<Rule Name>Test
|
||||
src/test/resources/net/sourceforge/pmd/lang/<Language Terse Name>/rule/<Category Name>/xml/<Rule Name>.xml
|
||||
net.sourceforge.pmd.lang.<Language Id>.rule.<Category Name>.<Rule Name>Test
|
||||
src/test/resources/net/sourceforge/pmd/lang/<Language Id>/rule/<Category Name>/xml/<Rule Name>.xml
|
||||
|
||||
Note: Language Id is the id defined by the language module, see {% jdoc core::lang.Language#getId() %}.
|
||||
|
||||
{%include tip.html content="This convention allows you to quickly find the test cases for a given rule:
|
||||
Just search in the project for a file `<Rule Name>.xml`. Search for a class `<Rule Name>Test` to find the
|
||||
@ -73,8 +76,8 @@ see [Using the test framework externally](#using-the-test-framework-externally).
|
||||
|
||||
### Test Class: AbstractClassWithoutAbstractMethodTest
|
||||
|
||||
This class inherits from `PmdRuleTst` and is located in the package "bestpractices", since the rule
|
||||
belongs to the category "Best Practices":
|
||||
This class inherits from {% jdoc test::testframework.PmdRuleTst %} and is located in the package "bestpractices",
|
||||
since the rule belongs to the category "Best Practices":
|
||||
|
||||
``` java
|
||||
package net.sourceforge.pmd.lang.java.rule.bestpractices;
|
||||
@ -137,7 +140,8 @@ The `<test-code>` elements understands the following optional attributes:
|
||||
|
||||
* **disabled**: By default, it's `false`. Set it to `true`, to ignore and skip a test case.
|
||||
|
||||
* **focused**: By default, it's `false`. Set it to `true`, to ignore all other test cases.
|
||||
* **focused**: By default, it's `false`. Set it to `true`, to ignore all other test cases. This is useful while
|
||||
debugging a rule and you want to focus only on one specific case.
|
||||
|
||||
### `<test-code>` children
|
||||
|
||||
@ -147,7 +151,7 @@ The `<test-code>` elements understands the following optional attributes:
|
||||
* **`<rule-property>`**: Optional rule properties, if the rule is configurable. Just add multiple elements, to
|
||||
set multiple properties for one test case. For an example, see below.
|
||||
|
||||
* **`<expected-problems>`**: The the raw number of expected rule violations, that this rule is expected to report.
|
||||
* **`<expected-problems>`**: The raw number of expected rule violations, that this rule is expected to report.
|
||||
For false-positive test cases, this is always "0". For false-negative test cases, it can be any positive number.
|
||||
|
||||
* **`<expected-linenumbers>`**: Optional element. It's a comma separated list of line numbers.
|
||||
@ -260,7 +264,7 @@ class CustomRuleTest extends SimpleAggregatorTst {
|
||||
This will then search for a rule named "CustomRule" in the ruleset, that is located in "src/main/resources" under
|
||||
the path "com/example/pmd/ruleset.xml".
|
||||
|
||||
The test data should be placed in an xml file located in "src/test/resources" under the path
|
||||
The test data should be placed in an XML file located in "src/test/resources" under the path
|
||||
"com/example/pmd/rules/xml/CustomRule.xml".
|
||||
|
||||
## How the test framework is implemented
|
||||
@ -268,16 +272,24 @@ The test data should be placed in an xml file located in "src/test/resources" un
|
||||
The framework uses the [dynamic test feature](https://junit.org/junit5/docs/current/user-guide/#writing-tests-dynamic-tests)
|
||||
of JUnit5 under the hood, among a couple of utility classes:
|
||||
|
||||
* `PmdRuleTst`: This is the base class for tests in PMD's code base. It is a subclass of `RuleTst` and just
|
||||
* {% jdoc test::testframework.PmdRuleTst %}: This is the base class for tests in PMD's code base. It is a subclass of
|
||||
{% jdoc test::testframework.RuleTst %} and just
|
||||
contains the logic to determine the test resources based on the test class name.
|
||||
|
||||
* `SimpleAggregatorTst`: This is a more generic base class for the test classes.
|
||||
* {% jdoc test::testframework.SimpleAggregatorTst %}: This is a more generic base class for the test classes.
|
||||
It doesn't register any test cases on its own. You can register your own rule tests.
|
||||
It itself is a subclass of `RuleTst`.
|
||||
It itself is a subclass of {% jdoc test::testframework.RuleTst %}.
|
||||
|
||||
* The maven module "pmd-test-schema" contains the logic to parse the XML files and provide a `RuleTestCollection`. This
|
||||
in turn contains a list of `RuleTestDescriptor`s. Each rule test descriptor describes a single test case.
|
||||
* The maven module "pmd-test-schema" contains the logic to parse the XML files and provides a
|
||||
{% jdoc test-schema::test.schema.RuleTestCollection %}. This in turn contains a list of
|
||||
{% jdoc test-schema::test.schema.RuleTestDescriptor %}s. Each rule test descriptor describes a single test case.
|
||||
|
||||
* `RuleTst`: uses the `TestSchemaParser` from module "pmd-test-schema" to parse the test cases, executes each
|
||||
* {% jdoc test::testframework.RuleTst %}: uses the {%jdoc test-schema::test.schema.TestSchemaParser %}
|
||||
from module "pmd-test-schema" to parse the test cases, executes each
|
||||
rule test descriptor and asserts the results. It defines a test method `ruleTests()` which is a test factory
|
||||
and returns one dynamic test per rule test.
|
||||
|
||||
## Example projects
|
||||
|
||||
See <https://github.com/pmd/pmd-examples> for a couple of example projects, that
|
||||
create custom PMD rules for different languages including tests.
|
||||
|
@ -2,7 +2,7 @@
|
||||
title: Writing a custom rule
|
||||
tags: [extending, userdocs]
|
||||
summary: "Learn how to write a custom rule for PMD"
|
||||
last_updated: February 2020 (6.22.0)
|
||||
last_updated: December 2023 (7.0.0)
|
||||
permalink: pmd_userdocs_extending_writing_java_rules.html
|
||||
author: Tom Copeland <tomcopeland@users.sourceforge.net>
|
||||
---
|
||||
@ -13,9 +13,8 @@ author: Tom Copeland <tomcopeland@users.sourceforge.net>
|
||||
{% jdoc_nspace :jast java::lang.java.ast %}
|
||||
{% jdoc_nspace :jrule java::lang.java.rule %}
|
||||
|
||||
{% include note.html content="TODO All that should be written in the Javadocs,
|
||||
not sure we even need a doc page. Would be simpler to maintain too" %}
|
||||
{% include warning.html content="WIP lots of stuff missing" %}
|
||||
{% include note.html content="Ideally most of what is written in this document would be directly
|
||||
in the Javadocs of the relevant classes. This is not the case yet." %}
|
||||
|
||||
This page covers the specifics of writing a rule in Java. The basic development
|
||||
process is very similar to the process for XPath rules, which is described in
|
||||
@ -32,12 +31,13 @@ very similar for other languages.
|
||||
## Basics
|
||||
|
||||
To write a rule in Java you'll have to:
|
||||
1. write a Java class that implements the interface {% jdoc core::Rule %}. Each
|
||||
|
||||
1. Write a Java class that implements the interface {% jdoc core::Rule %}. Each
|
||||
language implementation provides a base rule class to ease your pain,
|
||||
e.g. {% jdoc jrule::AbstractJavaRule %}.
|
||||
2. compile this class, linking it to PMD APIs (eg using PMD as a maven dependency)
|
||||
3. bundle this into a JAR and add it to the execution classpath of PMD
|
||||
4. declare the rule in your ruleset XML
|
||||
2. Compile this class, linking it to PMD APIs (e.g. using PMD as a Maven dependency)
|
||||
3. Bundle this into a JAR and add it to the execution classpath of PMD
|
||||
4. Declare the rule in your ruleset XML
|
||||
|
||||
## Rule execution
|
||||
|
||||
@ -61,8 +61,8 @@ public class MyRule extends AbstractJavaRule {
|
||||
|
||||
Generally, a rule wants to check for only some node types. In our XPath example
|
||||
in [Your First Rule](pmd_userdocs_extending_your_first_rule.html),
|
||||
we wanted to check for some `VariableDeclaratorId` nodes. That's the XPath name,
|
||||
but in Java, you'll get access to the {% jdoc jast::ASTVariableDeclaratorId %}
|
||||
we wanted to check for some `VariableId` nodes. That's the XPath name,
|
||||
but in Java, you'll get access to the {% jdoc jast::ASTVariableId %}
|
||||
full API.
|
||||
|
||||
If you want to check for some specific node types, you can override the
|
||||
@ -72,15 +72,15 @@ corresponding `visit` method:
|
||||
public class MyRule extends AbstractJavaRule {
|
||||
|
||||
@Override
|
||||
public Object visit(ASTVariableDeclaratorId node, Object data) {
|
||||
// This method is called on each node of type ASTVariableDeclaratorId
|
||||
public Object visit(ASTVariableId node, Object data) {
|
||||
// This method is called on each node of type ASTVariableId
|
||||
// in the AST
|
||||
|
||||
if (node.getType() == short.class) {
|
||||
// reports a violation at the position of the node
|
||||
// the "data" parameter is a context object handed to by your rule
|
||||
// the message for the violation is the message defined in the rule declaration XML element
|
||||
addViolation(data, node);
|
||||
asCtx(data).addViolation(node);
|
||||
}
|
||||
|
||||
// this calls back to the default implementation, which recurses further down the subtree
|
||||
@ -110,11 +110,89 @@ speed-up your rule by using the **rulechain**.
|
||||
|
||||
That mechanism doesn't recurse on all the tree, instead, your rule will only be
|
||||
passed the nodes it is interested in. To use the rulechain correctly:
|
||||
* Your rule must register those node types by calling {% jdoc core::Rule#addRuleChainVisit(java.lang.Class) %}
|
||||
in its constructor.
|
||||
* Your rule must override the method {% jdoc core::lang.rule.AbstractRule#buildTargetSelector() %}. This method
|
||||
should return a target selector, that selects all the node types you are interested in. E.g. the factory
|
||||
method {% jdoc core::lang.rule.RuleTargetSelector#forTypes(java.lang.Class,java.lang.Class...) %} can be used
|
||||
to create such a selector.
|
||||
* For the Java language, there is another base class, to make it easier:
|
||||
{% jdoc java::lang.java.rule.AbstractJavaRulechainRule %}. You'll need to call the super constructor and
|
||||
provide the node types you are interested in.
|
||||
* Your visit methods **must not recurse!** In effect, you should call never
|
||||
call `super.visit` in the methods.
|
||||
|
||||
#### Manual AST navigation
|
||||
|
||||
In Java rule implementations, you often need to navigate the AST to find the interesting nodes.
|
||||
In your `visit` implementation, you can start navigating the AST from the given node.
|
||||
|
||||
The {% jdoc core::lang.ast.Node %} interface provides a couple of useful methods
|
||||
that return a {%jdoc core::lang.ast.NodeStream %} and can be used to query the AST:
|
||||
|
||||
* {% jdoc core::lang.ast.Node#ancestors() %}
|
||||
* {% jdoc core::lang.ast.Node#ancestorsOrSelf() %}
|
||||
* {% jdoc core::lang.ast.Node#children() %}
|
||||
* {% jdoc core::lang.ast.Node#descendants() %}
|
||||
* {% jdoc core::lang.ast.Node#descendantsOrSelf() %}
|
||||
* {% jdoc core::lang.ast.Node#ancestors(java.lang.Class) %}
|
||||
* {% jdoc core::lang.ast.Node#children(java.lang.Class) %}
|
||||
* {% jdoc core::lang.ast.Node#descendants(java.lang.Class) %}
|
||||
|
||||
The returned NodeStream API provides easy to use methods that follow the Java Stream API (`java.util.stream`).
|
||||
|
||||
Example:
|
||||
|
||||
```java
|
||||
NodeStream.of(someNode) // the stream here is empty if the node is null
|
||||
.filterIs(ASTVariableDeclaratorId.class)// the stream here is empty if the node was not a variable declarator id
|
||||
.followingSiblings() // the stream here contains only the siblings, not the original node
|
||||
.filterIs(ASTVariableInitializer.class)
|
||||
.children(ASTExpression.class)
|
||||
.children(ASTPrimaryExpression.class)
|
||||
.children(ASTPrimaryPrefix.class)
|
||||
.children(ASTLiteral.class)
|
||||
.filterMatching(Node::getImage, "0")
|
||||
.filterNot(ASTLiteral::isStringLiteral)
|
||||
.nonEmpty(); // If the stream is non empty here, then all the pipeline matched
|
||||
```
|
||||
|
||||
The {% jdoc core::lang.ast.Node %} interface provides also an alternative way to navigate the AST for convenience:
|
||||
|
||||
* {% jdoc core::lang.ast.Node#getParent() %}
|
||||
* {% jdoc core::lang.ast.Node#getNumChildren() %}
|
||||
* {% jdoc core::lang.ast.Node#getChild(int) %}
|
||||
* {% jdoc core::lang.ast.Node#getFirstChild() %}
|
||||
* {% jdoc core::lang.ast.Node#getLastChild() %}
|
||||
* {% jdoc core::lang.ast.Node#getPreviousSibling() %}
|
||||
* {% jdoc core::lang.ast.Node#getNextSibling() %}
|
||||
* {% jdoc core::lang.ast.Node#firstChild(java.lang.Class) %}
|
||||
|
||||
Depending on the AST of the language, there might also be more specific methods that can be used to
|
||||
navigate. E.g. in Java there exists the method {% jdoc !!java::lang.java.ast.ASTIfStatement#getCondition() %}
|
||||
to get the condition of an If-statement.
|
||||
|
||||
### Reporting violations
|
||||
|
||||
In your visit method, you have access to the {% jdoc core::RuleContext %} which is the entry point into
|
||||
reporting back during the analysis.
|
||||
|
||||
* {% jdoc core::RuleContext#addViolation(core::lang.ast.Node) %} reports a rule violation at
|
||||
the position of the given node with the message defined in the rule declaration XML element.
|
||||
* The message defined in the rule declaration XML element might contain **placeholder**, such as `{0}`.
|
||||
In that case, you need to call {% jdoc core::RuleContext#addViolation(core::lang.ast.Node,java.lang.Object...) %}
|
||||
and provide the values for the placeholders. The message is actually processed as a `java.text.MessageFormat`.
|
||||
* Sometimes a rule might want to differentiate between different cases of a violation and use different
|
||||
messages. This is possible by calling the methods
|
||||
{% jdoc core::RuleContext#addViolationWithMessage(core::lang.ast.Node,java.lang.String) %} or
|
||||
{% jdoc core::RuleContext#addViolationWithMessage(core::lang.ast.Node,java.lang.String,java.lang.Object...) %}.
|
||||
Using these methods, the message defined in the rule declaration XML element is _not used_.
|
||||
* Rules can be customized using properties and sometimes you want to include the actual value of a property
|
||||
in the message, e.g. if the rule enforces a specific limit.
|
||||
The syntax for such placeholders is: `${propertyName}`.
|
||||
* Some languages support additional placeholder variables. E.g. for Java, you can use `${methodName}` to insert
|
||||
the name of the method in which the violation occurred.
|
||||
See [Java-specific features and guidance](pmd_languages_java.html#violation-decorators).
|
||||
|
||||
|
||||
### Execution across files, thread-safety and statefulness
|
||||
|
||||
When starting execution, PMD will instantiate a new instance of your rule.
|
||||
@ -123,27 +201,41 @@ instance of the rule. This means, that the rule implementation **does not need t
|
||||
threading issues**, as PMD makes sure, that a single instance is not used concurrently
|
||||
by multiple threads.
|
||||
|
||||
However, for performance reasons, the rule instances are used for multiple files.
|
||||
However, for performance reasons, the rule instances are reused for multiple files.
|
||||
This means, that the constructor of the rule is only executed once (per thread)
|
||||
and the rule instance is reused. If you rely on a proper initialization of instance
|
||||
properties, you can do the initialization e.g. in the visit-method of the {% jdoc jast::ASTCompilationUnit %}
|
||||
node - which is visited first and only once per file. However, this
|
||||
solution would only work for rules written for the Java language. A language
|
||||
independent way is to override the method `start` of the rule.
|
||||
properties, you can do the initialization in the `start` method of the rule
|
||||
(you need to override this method).
|
||||
The start method is called exactly once per file.
|
||||
|
||||
<!-- We don't support language-independent rules anyway... -->
|
||||
### Using metrics
|
||||
|
||||
Some languages might support metrics.
|
||||
|
||||
* [Apex-specific features and guidance](pmd_languages_apex.html#metrics-framework)
|
||||
* [Java-specific features and guidance](pmd_languages_java.html#metrics-framework)
|
||||
|
||||
### Using symbol table
|
||||
|
||||
Some languages might support symbol table.
|
||||
|
||||
* [Java-specific features and guidance](pmd_languages_java.html#symbol-table-apis)
|
||||
|
||||
### Using type resolution
|
||||
|
||||
Some languages might support type resolution.
|
||||
|
||||
* [Java-specific features and guidance](pmd_languages_java.html#type-resolution-apis)
|
||||
|
||||
## Rule lifecycle reference
|
||||
|
||||
### Construction
|
||||
|
||||
Exactly once:
|
||||
Exactly once (per thread):
|
||||
|
||||
1. The rule's no-arg constructor is called when loading the ruleset.
|
||||
The rule's constructor must define:
|
||||
* [Rulechain visits](#economic-traversal-the-rulechain)
|
||||
* [Property descriptors](pmd_userdocs_extending_defining_properties.html#for-java-rules)
|
||||
The rule's constructor must define already any
|
||||
[Property descriptors](pmd_userdocs_extending_defining_properties.html#for-java-rules) the rule wants to use.
|
||||
2. If the rule was included in the ruleset as a rule reference,
|
||||
some properties [may be overridden](pmd_userdocs_configuring_rules.html#rule-properties).
|
||||
If an overridden property is unknown, an error is reported.
|
||||
@ -152,12 +244,17 @@ If an overridden property is unknown, an error is reported.
|
||||
### Execution
|
||||
|
||||
For each thread, a deep copy of the rule is created. Each thread is given
|
||||
a different set of files to analyse. Then, for each such file, for each
|
||||
a different set of files to analyse. Then, for each such file and for each
|
||||
rule copy:
|
||||
|
||||
3. {% jdoc core::Rule#start(core::RuleContext) %} is called once, before parsing
|
||||
4. {% jdoc core::Rule#apply(java.util.List,core::RuleContext) %} is called with the root
|
||||
1. {% jdoc core::Rule#start(core::RuleContext) %} is called once, before parsing
|
||||
2. {% jdoc core::Rule#apply(core::lang.ast.Node,core::RuleContext) %} is called with the root
|
||||
of the AST. That method performs the AST traversal that ultimately calls visit methods.
|
||||
It's not called for RuleChain rules.
|
||||
5. {% jdoc core::Rule#end(core::RuleContext) %} is called when the rule is done processing
|
||||
3. {% jdoc core::Rule#end(core::RuleContext) %} is called when the rule is done processing
|
||||
the file
|
||||
|
||||
## Example projects
|
||||
|
||||
See <https://github.com/pmd/pmd-examples> for a couple of example projects, that
|
||||
create custom PMD rules for different languages.
|
||||
|
@ -2,7 +2,7 @@
|
||||
title: Introduction to writing PMD rules
|
||||
tags: [extending, userdocs, getting_started]
|
||||
summary: "Writing your own PMD rules"
|
||||
last_updated: February 2020 (6.22.0)
|
||||
last_updated: December 2023 (7.0.0)
|
||||
permalink: pmd_userdocs_extending_writing_rules_intro.html
|
||||
author: Clément Fournier <clement.fournier76@gmail.com>
|
||||
---
|
||||
@ -14,19 +14,16 @@ team.
|
||||
## How rules work: the AST
|
||||
|
||||
Before running rules, PMD parses the source file into a data structure called an
|
||||
**abstract syntax tree (AST)**. This tree represents the syntactic structure of the
|
||||
**[abstract syntax tree (AST)](https://en.wikipedia.org/wiki/Abstract_syntax_tree)**.
|
||||
This tree represents the syntactic structure of the
|
||||
code, and encodes syntactic relations between source code elements. For instance,
|
||||
in Java, method declarations belong to a class: in the AST, the nodes representing
|
||||
method declarations will be descendants of a node representing the declaration of
|
||||
their enclosing class. This representation is thus much richer than the original
|
||||
source code (which, for a program, is just a chain of characters), or the token
|
||||
chain produced by a lexer (which is e.g. what Checkstyle works on). For example:
|
||||
chain produced by a lexer. For example:
|
||||
|
||||
<table>
|
||||
<colgroup>
|
||||
<col width="40%" />
|
||||
<col width="70%" />
|
||||
</colgroup>
|
||||
<thead>
|
||||
<tr class="header">
|
||||
<th>Sample code (Java)</th>
|
||||
@ -49,10 +46,11 @@ class Foo extends Object {
|
||||
```java
|
||||
└─ CompilationUnit
|
||||
└─ TypeDeclaration
|
||||
└─ ClassOrInterfaceDeclaration "Foo"
|
||||
└─ ClassDeclaration "Foo"
|
||||
├─ ModifierList
|
||||
├─ ExtendsList
|
||||
│ └─ ClassOrInterfaceType "Object"
|
||||
└─ ClassOrInterfaceBody
|
||||
│ └─ ClassType "Object"
|
||||
└─ ClassBody
|
||||
```
|
||||
|
||||
</td>
|
||||
@ -95,6 +93,8 @@ complicated processing, to which an XPath rule couldn't scale.
|
||||
In the end, choosing one strategy or the other depends on the difficulty of what
|
||||
your rule does. I'd advise to keep to XPath unless you have no other choice.
|
||||
|
||||
Note: Despite that fact, the Java rules are written in Java, any language that PMD supports
|
||||
can be analyzed. E.g. you can write a Java rule to analyze Apex source code.
|
||||
|
||||
## XML rule definition
|
||||
|
||||
@ -103,10 +103,12 @@ case for both XPath and Java rules. To do this, the `rule` element is used, but
|
||||
instead of mentioning the `ref` attribute, it mentions the `class` attribute,
|
||||
with the implementation class of your rule.
|
||||
|
||||
* **For Java rules:** this is the class extending AbstractRule (transitively)
|
||||
* **For XPath rules:** this is `net.sourceforge.pmd.lang.rule.XPathRule`
|
||||
* **For Java rules:** this is the concrete class extending AbstractRule (transitively)
|
||||
* **For XPath rules:** this is `net.sourceforge.pmd.lang.rule.XPathRule`.
|
||||
* **For XPath rules analyzing XML-based languages:** this is `net.sourceforge.pmd.lang.xml.rule.DomXPathRule`.
|
||||
See [XPath rules in XML](pmd_languages_xml.html#xpath-rules-in-xml) for more info.
|
||||
|
||||
Example:
|
||||
Example for Java rule:
|
||||
|
||||
```xml
|
||||
<rule name="MyJavaRule"
|
||||
@ -120,11 +122,31 @@ Example:
|
||||
</rule>
|
||||
```
|
||||
|
||||
{% include note.html content="In PMD 7, the `language` attribute will be required on all `rule`
|
||||
elements that declare a new rule. Some base rule classes set the language implicitly in their
|
||||
constructor, and so this is not required in all cases for the rule to work. But this
|
||||
behavior will be discontinued in PMD 7, so missing `language` attributes are
|
||||
reported beginning with PMD 6.27.0 as a forward compatibility warning." %}
|
||||
Example for XPath rule:
|
||||
|
||||
```xml
|
||||
<rule name="MyXPathRule"
|
||||
language="java"
|
||||
message="Violation!"
|
||||
class="net.sourceforge.pmd.lang.rule.XPathRule">
|
||||
<description>
|
||||
Description
|
||||
</description>
|
||||
<priority>3</priority>
|
||||
<properties>
|
||||
<property name="xpath">
|
||||
<value><![CDATA[
|
||||
//ClassOrInterfaceDeclaration
|
||||
]]></value>
|
||||
</property>
|
||||
</properties>
|
||||
</rule>
|
||||
```
|
||||
|
||||
|
||||
{% include note.html content="Since PMD 7, the `language` attribute is required on all `rule`
|
||||
elements that declare a new rule. In PMD 6, this was optional, as the base rule classes sometimes set
|
||||
the language implicitly in their constructor." %}
|
||||
|
||||
## Resource index
|
||||
|
||||
|
@ -2,7 +2,7 @@
|
||||
title: Writing XPath rules
|
||||
tags: [extending, userdocs]
|
||||
summary: "This page describes XPath rule support in more details"
|
||||
last_updated: February 2020 (6.22.0)
|
||||
last_updated: December 2023 (7.0.0)
|
||||
permalink: pmd_userdocs_extending_writing_xpath_rules.html
|
||||
author: Miguel Griffa <mikkey@users.sourceforge.net>, Clément Fournier <clement.fournier76@gmail.com>
|
||||
---
|
||||
@ -15,17 +15,19 @@ author: Miguel Griffa <mikkey@users.sourceforge.net>, Clément Fournier <clement
|
||||
|
||||
|
||||
This page describes some points of XPath rule support in more details. See
|
||||
also [the tutorial about how to write an XPath rule](pmd_userdocs_extending_your_first_rule.html).
|
||||
also [the tutorial about how to write a first (XPath) rule](pmd_userdocs_extending_your_first_rule.html).
|
||||
|
||||
<!-- Later we can document the specific subset of XPath features our wrappers support -->
|
||||
|
||||
## XPath version
|
||||
|
||||
PMD uses XPath 3.1 for its XPath rules since PMD 7. Before then, the default version was XPath 1.0, with opt-in support for XPath 2.0.
|
||||
PMD uses XPath 3.1 for its XPath rules since PMD 7. Before then, the default version was XPath 1.0,
|
||||
with opt-in support for XPath 2.0.
|
||||
|
||||
See [the Saxonica documentation](https://www.saxonica.com/html/documentation/expressions/xpath31new.html) for an introduction to new features in XPath 3.1.
|
||||
See [the Saxonica documentation](https://www.saxonica.com/html/documentation/expressions/xpath31new.html)
|
||||
for an introduction to new features in XPath 3.1.
|
||||
|
||||
The property `version` of XPathRule is deprecated and will be removed.
|
||||
The property `version` of {% jdoc core::lang.rule.XPathRule %} is deprecated and will be removed.
|
||||
|
||||
|
||||
## DOM representation of ASTs
|
||||
@ -38,11 +40,13 @@ defined on. Concretely, this means:
|
||||
* Some Java getters are exposed as XML attributes on those elements
|
||||
* This means, that documentation for attributes can be found in our Javadocs. For
|
||||
example, the attribute `@SimpleName` of the Java node `EnumDeclaration` is backed
|
||||
by the Java getter {% jdoc java::lang.java.ast.ASTAnyTypeDeclaration#getSimpleName() %}.
|
||||
by the Java getter {% jdoc java::lang.java.ast.ASTTypeDeclaration#getSimpleName() %}.
|
||||
|
||||
### Value conversion
|
||||
|
||||
To represent attributes, we must map Java values to [XPath Data Model (XDM)](https://www.w3.org/TR/xpath-datamodel/) values. In the following table we refer to the type conversion function as `conv`, a function from Java types to XDM types.
|
||||
To represent attributes, we must map Java values to [XPath Data Model (XDM)](https://www.w3.org/TR/xpath-datamodel/)
|
||||
values. In the following table we refer to the type conversion function as `conv`, a function from Java types
|
||||
to XDM types.
|
||||
|
||||
| Java type `T` | XSD type `conv(T)` |
|
||||
|---------------|---------------------------------------|
|
||||
@ -73,14 +77,9 @@ The same `conv` function is used to translate rule property values to XDM values
|
||||
PMD provides some language-specific XPath functions to access semantic
|
||||
information from the AST.
|
||||
|
||||
On XPath 2.0, the namespace of custom PMD function must be explicitly mentioned.
|
||||
The namespace of custom PMD functions must be explicitly mentioned.
|
||||
|
||||
{% render %}
|
||||
{% include custom/xpath_fun_doc.html %}
|
||||
{% endrender %}
|
||||
|
||||
{% include note.html content='There is also a `typeOf` function which is
|
||||
deprecated and whose usages should be replaced with uses of `typeIs` or
|
||||
`typeIsExactly`. That one will be removed with PMD 7.0.0.' %}
|
||||
|
||||
|
||||
|
@ -1,8 +1,8 @@
|
||||
---
|
||||
title: Your first rule XPath
|
||||
title: Your first rule
|
||||
tags: [extending, userdocs]
|
||||
summary: "Introduction to rule writing through an example."
|
||||
last_updated: February 2020 (6.22.0)
|
||||
summary: "Introduction to rule writing through an example for a XPath rule."
|
||||
last_updated: December 2023 (7.0.0)
|
||||
permalink: pmd_userdocs_extending_your_first_rule.html
|
||||
author: Miguel Griffa <mikkey@users.sourceforge.net>, Clément Fournier <clement.fournier76@gmail.com>
|
||||
---
|
||||
@ -11,7 +11,7 @@ This page is a gentle introduction to rule writing, and the Rule Designer.
|
||||
|
||||
Using the designer is useful both to write Java
|
||||
rules and XPath rules, but it's more specifically geared towards XPath rules.
|
||||
This page uses a simple XPath rule to illustrate the common workflow. We assume
|
||||
This page uses a **simple XPath rule** to illustrate the common workflow. We assume
|
||||
here that you already know what XPath is and how to read basic XPath queries. W3C
|
||||
has a good tutorial [here](https://www.w3schools.com/xml/xpath_syntax.asp) if
|
||||
you don't (in the context of XML only), and [the Saxon documentation](https://www.saxonica.com/documentation/index.html#!expressions)
|
||||
@ -57,7 +57,7 @@ The basic development process is straightforward:
|
||||
2. Examine the AST and determine what node the violation should be reported on
|
||||
3. Write an XPath expression matching that node in the XPath editor
|
||||
4. Refine the XPath expression iteratively using different code snippets, so that
|
||||
it matches violation cases, but no other node
|
||||
it matches violation cases, but no other nodes
|
||||
5. Export your XPath expression to an XML rule element, and place it in your ruleset
|
||||
|
||||
Each time you test your rule against a different snippet, it's a good idea to
|
||||
@ -83,11 +83,11 @@ public class KeepingItSerious {
|
||||
|
||||
```
|
||||
|
||||
Examining the AST, you find out that the LocalVariableDeclaration has a VariableDeclaratorId
|
||||
descendant, whose `Image` XPath attribute is exactly `bill`. You thus write your first attempt
|
||||
Examining the AST, you find out that the LocalVariableDeclaration has a VariableId
|
||||
descendant, whose `Name` XPath attribute is exactly `bill`. You thus write your first attempt
|
||||
in the XPath editor:
|
||||
```xpath
|
||||
//VariableDeclaratorId[@Image = "bill"]
|
||||
//VariableId[@Name = "bill"]
|
||||
```
|
||||
|
||||
You can see the XPath result list is updated with the variable declarator.
|
||||
@ -112,13 +112,14 @@ based on your examination of the Type node of the field and local variable
|
||||
declaration nodes.
|
||||
|
||||
```xpath
|
||||
//VariableDeclaratorId[@Image = "bill" and ../../Type[@TypeImage = "short"]]
|
||||
//VariableId[@Name = "bill" and ../../Type[@TypeImage = "short"]]
|
||||
```
|
||||
|
||||
### Exporting to XML
|
||||
|
||||
You estimate that your rule is now production ready, and you'd like to use it in your ruleset.
|
||||
The `File > Export XPath to rule...` allows you to do that in a few clicks: just enter some
|
||||
The second button in the toolbar above the XPath editor (Tooltip: `Export XPath to rule...`)
|
||||
allows you to do that in a few clicks: just enter some
|
||||
additional metadata for your rule, and the popup will generate an XML element that you can
|
||||
copy-paste into your ruleset XML. The resulting element looks like so:
|
||||
|
||||
@ -135,7 +136,7 @@ TODO
|
||||
<property name="xpath">
|
||||
<value>
|
||||
<![CDATA[
|
||||
//VariableDeclaratorId[../../Type[@TypeImage="short"] and @Image = "bill"]
|
||||
//VariableId[@Name = "bill"][../../Type[@TypeImage="short"]]
|
||||
]]>
|
||||
</value>
|
||||
</property>
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user