Merge branch 'master' into avoid-image

This commit is contained in:
Andreas Dangel
2024-02-02 16:56:54 +01:00
628 changed files with 11834 additions and 10552 deletions

View File

@ -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",

View File

@ -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
}

View File

@ -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

View File

@ -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

View File

@ -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: |

View File

@ -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

View File

@ -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"]'

View File

@ -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

View File

@ -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

View File

@ -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 modules 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 its 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.

View File

@ -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
* Its 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).

View File

@ -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 %}

View File

@ -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).

View File

@ -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

View File

@ -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".

View File

@ -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".

View 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, wont 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.

View File

@ -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.

View File

@ -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" %}

View File

@ -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.

View File

@ -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).

View File

@ -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?).

View File

@ -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.

View File

@ -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.

View File

@ -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

View File

@ -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.' %}

View File

@ -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