Merge branch 'pmd/7.0.x' into pr-2022
This commit is contained in:
117
.mvn/wrapper/MavenWrapperDownloader.java
vendored
Normal file
117
.mvn/wrapper/MavenWrapperDownloader.java
vendored
Normal file
@ -0,0 +1,117 @@
|
||||
/*
|
||||
* Copyright 2007-present the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import java.net.*;
|
||||
import java.io.*;
|
||||
import java.nio.channels.*;
|
||||
import java.util.Properties;
|
||||
|
||||
public class MavenWrapperDownloader {
|
||||
|
||||
private static final String WRAPPER_VERSION = "0.5.6";
|
||||
/**
|
||||
* Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided.
|
||||
*/
|
||||
private static final String DEFAULT_DOWNLOAD_URL = "https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/"
|
||||
+ WRAPPER_VERSION + "/maven-wrapper-" + WRAPPER_VERSION + ".jar";
|
||||
|
||||
/**
|
||||
* Path to the maven-wrapper.properties file, which might contain a downloadUrl property to
|
||||
* use instead of the default one.
|
||||
*/
|
||||
private static final String MAVEN_WRAPPER_PROPERTIES_PATH =
|
||||
".mvn/wrapper/maven-wrapper.properties";
|
||||
|
||||
/**
|
||||
* Path where the maven-wrapper.jar will be saved to.
|
||||
*/
|
||||
private static final String MAVEN_WRAPPER_JAR_PATH =
|
||||
".mvn/wrapper/maven-wrapper.jar";
|
||||
|
||||
/**
|
||||
* Name of the property which should be used to override the default download url for the wrapper.
|
||||
*/
|
||||
private static final String PROPERTY_NAME_WRAPPER_URL = "wrapperUrl";
|
||||
|
||||
public static void main(String args[]) {
|
||||
System.out.println("- Downloader started");
|
||||
File baseDirectory = new File(args[0]);
|
||||
System.out.println("- Using base directory: " + baseDirectory.getAbsolutePath());
|
||||
|
||||
// If the maven-wrapper.properties exists, read it and check if it contains a custom
|
||||
// wrapperUrl parameter.
|
||||
File mavenWrapperPropertyFile = new File(baseDirectory, MAVEN_WRAPPER_PROPERTIES_PATH);
|
||||
String url = DEFAULT_DOWNLOAD_URL;
|
||||
if(mavenWrapperPropertyFile.exists()) {
|
||||
FileInputStream mavenWrapperPropertyFileInputStream = null;
|
||||
try {
|
||||
mavenWrapperPropertyFileInputStream = new FileInputStream(mavenWrapperPropertyFile);
|
||||
Properties mavenWrapperProperties = new Properties();
|
||||
mavenWrapperProperties.load(mavenWrapperPropertyFileInputStream);
|
||||
url = mavenWrapperProperties.getProperty(PROPERTY_NAME_WRAPPER_URL, url);
|
||||
} catch (IOException e) {
|
||||
System.out.println("- ERROR loading '" + MAVEN_WRAPPER_PROPERTIES_PATH + "'");
|
||||
} finally {
|
||||
try {
|
||||
if(mavenWrapperPropertyFileInputStream != null) {
|
||||
mavenWrapperPropertyFileInputStream.close();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
// Ignore ...
|
||||
}
|
||||
}
|
||||
}
|
||||
System.out.println("- Downloading from: " + url);
|
||||
|
||||
File outputFile = new File(baseDirectory.getAbsolutePath(), MAVEN_WRAPPER_JAR_PATH);
|
||||
if(!outputFile.getParentFile().exists()) {
|
||||
if(!outputFile.getParentFile().mkdirs()) {
|
||||
System.out.println(
|
||||
"- ERROR creating output directory '" + outputFile.getParentFile().getAbsolutePath() + "'");
|
||||
}
|
||||
}
|
||||
System.out.println("- Downloading to: " + outputFile.getAbsolutePath());
|
||||
try {
|
||||
downloadFileFromURL(url, outputFile);
|
||||
System.out.println("Done");
|
||||
System.exit(0);
|
||||
} catch (Throwable e) {
|
||||
System.out.println("- Error downloading");
|
||||
e.printStackTrace();
|
||||
System.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
private static void downloadFileFromURL(String urlString, File destination) throws Exception {
|
||||
if (System.getenv("MVNW_USERNAME") != null && System.getenv("MVNW_PASSWORD") != null) {
|
||||
String username = System.getenv("MVNW_USERNAME");
|
||||
char[] password = System.getenv("MVNW_PASSWORD").toCharArray();
|
||||
Authenticator.setDefault(new Authenticator() {
|
||||
@Override
|
||||
protected PasswordAuthentication getPasswordAuthentication() {
|
||||
return new PasswordAuthentication(username, password);
|
||||
}
|
||||
});
|
||||
}
|
||||
URL website = new URL(urlString);
|
||||
ReadableByteChannel rbc;
|
||||
rbc = Channels.newChannel(website.openStream());
|
||||
FileOutputStream fos = new FileOutputStream(destination);
|
||||
fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE);
|
||||
fos.close();
|
||||
rbc.close();
|
||||
}
|
||||
|
||||
}
|
BIN
.mvn/wrapper/maven-wrapper.jar
vendored
Executable file → Normal file
BIN
.mvn/wrapper/maven-wrapper.jar
vendored
Executable file → Normal file
Binary file not shown.
3
.mvn/wrapper/maven-wrapper.properties
vendored
Executable file → Normal file
3
.mvn/wrapper/maven-wrapper.properties
vendored
Executable file → Normal file
@ -1 +1,2 @@
|
||||
distributionUrl=https://repo1.maven.org/maven2/org/apache/maven/apache-maven/3.5.4/apache-maven-3.5.4-bin.zip
|
||||
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.6.3/apache-maven-3.6.3-bin.zip
|
||||
wrapperUrl=https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar
|
||||
|
@ -10,6 +10,7 @@ env:
|
||||
- secure: "B2quYqla3mqu3QHqeRGMde66IFAluUHhzY87q/giPvONm/V7xmIcxzC6JNQ7X4pHotC7MUbsMf7vdcL1tyuPP5nqyfaxk0nnGnVoJZIQhMaBQ6Lkyt2Ac5u5/QBeHujrj8gqGjT7vzgPIYMYiSLV09VcPgMaYS6WxCrFmXOEJOeVGOGjG9J2wApAmlf6m/hBogLEbdbzRheqJwX5gMxppKX5XRaiqvKGR7hHFiW5HBaAiQ6caQEw6DZL9GnjMsqd5VMVL7DmbETncNE1utLVDO7mFwDMbkeonmwjsmQsJAo5Be+WFl/a/yRQLrqhhTFwag+V7g7RjcBKAdJ5IZJkN0EC9GjY0ZGOjE2cmcrX03U1g1voO7oreH6+25VqPTTGWHv5K8wDWM5QnhBJGtH2yH76p5XYK8iNOXPTqvjubN9sG3INsnTjxBHuQw+aY2vgwBGCvW+Asqe73BV8SDrmqcCqgmm/BUy0kZRd8mhaQdFL0SAO3suV7uf+fzIsmRtndFx24J5Cm6LE6NnTmtbhHhs4mUEqfngMq0XZBXj5z4LDhzOH4g3rf/Y7jHBjAAa6sKeh1sINdHrMdB843dDfNQPp/7g3hfKi31U9ZMczndsr2QYqm2OC/3lPiRHC+8Grj5PxXmiBplzTppyhl+1pjZWy5SCoQi6ulBOyaZcwfYM="
|
||||
- secure: "Kmh31kozw9NBbwtkucpf+QslinJIclB7rcktHz4RokVS5xiMD7njWvD8ptZhXqZG/tNcNiXkWskgM9EV/OvLTTavJToHE1gMhJel5ODxKM0b5QZDy6tkW/wI0Kdl0lck90EAJ7bmsOmF+oGczGsauiLPSlBOZV0tn1zdeJKwwmg46C9UyIhTDcFqUD1ehxExkDHnbOk6HmEPGCsZQCGNXY2t++dO7fRodIgjZAKcyTwgrh6WEZDfQDjvC51yQlVzL2mrcFqHi8qYAx3OHnuidRoMZ4huaoxLimw/1bGii/txTZwJ/pQdF90kGqSqLpRiO2lHLq5l8WAd4Z1XPSs1ZzbKUEhWO8VGgfR0Cp+Bkkd6P/vcuTq7bdhpvdnZn9FRUI/bfgZi827Skj/WoUh0RR7ns6RuDfQ5uGD9NiWDsbfoKLbvvYjyQycQlQTKglYQVcU/pWsuqVlx+27vM6JltR0Pct4uAJxAN+EFA4sTT/u4HiZ0NQyZWisZauu7CyMRN29fkKVVyYHHCr6QcNMUpvp1vvKYaUN7GeykGNVfoHB7CWAKcc6banh33c+QqG5DSTE++ZNa712gxEizQnO5MmLECUxNDpOGZnLsTlgbABwDxxe4PxFxXvBLm9pkJftq5cp9T9Syzz/yyT7z5weSGvRes94eUtSt0agoeX2MKsE="
|
||||
- secure: "VezxV+VdmbmtrQYT8AZIyg41WBROxuxpumerkcubADF7V4wV6lwx9Rd2G6yAr0VuHCNUUhS4m+gPFIsuiQbAhyupiEkwhzUYqk1tF+LITlLLPegLypjiLmhJMwGUNuDSSsih1Icmg9FzrP4VyzgGn9pBjoG9QYj1civBZeGwg++e/XDYlHMXrpd/UEfMKVB71JwB0tle4fKJZSvblIqP62yvbBaKHx6A4+ZWzJV5Vps0DoIeNtKCNmNNloKZVHfjbsvqSjnMYUJzkOzyPkM822q41N/D+3IAufO16+jH/W0vAZeN0e4GXiN5W+CVkr2Gbh0FwkVQcI3bekaOIn45XLUMLKdf+JsWDPKz9RraHelR9YxL5GoJ7ntwvmucxw0p8EVyJ/xLk/pBCP8iHq0Jb8//js25XHgxzzAWI37MErPAAGgTKZAVdAN0mGXbe63tWmwaBlEbK8h2A8di6abW5x6YHTkTo2BRlHUSTU8dE3VqTnpSkne5n1SlEa4g1Bci3J45M0/pLmHV6yCxCM5BrVXS5ByaB61py/umSbpmdIBFV6TM1MaKK3lAucQrR+8To/vCbm8XqPyujJdOR+ENIuuDgEU/Yh5Hv5SAODekUYaCp4pjfGzFADHQWVNDxIOXrwBN4OfSiAvRc1x6HXndOmNI4QtOxheuCRFFthq8VZI="
|
||||
- GITHUB_BASE_URL=https://api.github.com/repos/pmd/pmd
|
||||
|
||||
jobs:
|
||||
fast_finish: true
|
||||
@ -48,7 +49,7 @@ jobs:
|
||||
env: BUILD=publish
|
||||
|
||||
before_install:
|
||||
- bash .travis/before_install.sh "11.0.5+10"
|
||||
- bash .travis/before_install.sh "11.0.6+10"
|
||||
- source ${HOME}/java.env
|
||||
install: true
|
||||
before_script: true
|
||||
@ -96,5 +97,5 @@ cache:
|
||||
# add the encrypted GPG keyring file to repo (https://docs.travis-ci.com/user/encrypting-files/#Automated-Encryption), decrypt it and install it at the beginning of .travis-deploy.sh
|
||||
#
|
||||
# GITHUB_OAUTH_TOKEN - the token used to upload the binaries to github releases
|
||||
# GITHUB_BASE_URL - the api url to use for github releases - does not need to be secure (https://api.github.com/repos/pmd/pmd)
|
||||
#
|
||||
|
||||
|
@ -37,7 +37,7 @@ elif travis_isPush; then
|
||||
echo -e "\n\n"
|
||||
|
||||
# create a draft github release
|
||||
gh_releases_createDraftRelease "${TRAVIS_TAG}" "$(git show-ref --hash ${TRAVIS_TAG})"
|
||||
gh_releases_createDraftRelease "${TRAVIS_TAG}" "$(git rev-list -n 1 ${TRAVIS_TAG})"
|
||||
GH_RELEASE="$RESULT"
|
||||
|
||||
# Build and deploy to ossrh / maven-central
|
||||
|
@ -1,9 +1,10 @@
|
||||
#
|
||||
# The functions here require the following scripts:
|
||||
# .travis/logger.sh
|
||||
# logger.sh
|
||||
#
|
||||
# The functions here require the following environment variables:
|
||||
# GITHUB_OAUTH_TOKEN
|
||||
# GITHUB_BASE_URL
|
||||
#
|
||||
|
||||
#
|
||||
@ -30,13 +31,13 @@ function gh_releases_createDraftRelease() {
|
||||
EOF
|
||||
)
|
||||
|
||||
log_debug "POST https://api.github.com/repos/pmd/pmd/releases"
|
||||
log_info "Creating gihtub draft release"
|
||||
log_debug "POST $GITHUB_BASE_URL/releases"
|
||||
log_info "Creating github draft release"
|
||||
RESULT=$(curl --fail -s -H "Authorization: token ${GITHUB_OAUTH_TOKEN}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-X POST \
|
||||
--data "${request}" \
|
||||
"https://api.github.com/repos/pmd/pmd/releases")
|
||||
"$GITHUB_BASE_URL/releases")
|
||||
log_debug " -> response: $RESULT"
|
||||
|
||||
log_success "Created draft release with id $(echo $RESULT | jq --raw-output ".url")"
|
||||
@ -53,9 +54,9 @@ function gh_releases_createDraftRelease() {
|
||||
#
|
||||
function gh_releases_getLatestDraftRelease() {
|
||||
log_debug "$FUNCNAME"
|
||||
log_debug "GET https://api.github.com/repos/pmd/pmd/releases?per_page=1"
|
||||
log_debug "GET $GITHUB_BASE_URL/releases?per_page=1"
|
||||
RESULT=$(curl --fail -s -H "Authorization: token ${GITHUB_OAUTH_TOKEN}" \
|
||||
"https://api.github.com/repos/pmd/pmd/releases?per_page=1" | jq ".[0]")
|
||||
"$GITHUB_BASE_URL/releases?per_page=1" | jq ".[0]")
|
||||
log_debug " -> response: $RESULT"
|
||||
local draft=$(echo $RESULT | jq ".draft")
|
||||
if [ "$draft" != "true" ]; then
|
||||
@ -77,12 +78,12 @@ function gh_release_deleteRelease() {
|
||||
gh_release_getIdFromData "$release"
|
||||
local releaseId="$RESULT"
|
||||
log_debug "$FUNCNAME id=$releaseId"
|
||||
log_debug "DELETE https://api.github.com/repos/pmd/pmd/releases/$releaseId"
|
||||
log_debug "DELETE $GITHUB_BASE_URL/releases/$releaseId"
|
||||
log_info "Deleting github release $releaseId"
|
||||
local response
|
||||
response=$(curl --fail -s -H "Authorization: token ${GITHUB_OAUTH_TOKEN}" \
|
||||
-X DELETE \
|
||||
"https://api.github.com/repos/pmd/pmd/releases/$releaseId")
|
||||
"$GITHUB_BASE_URL/releases/$releaseId")
|
||||
log_debug " -> response: $response"
|
||||
log_success "Deleted release with id $releaseId"
|
||||
}
|
||||
@ -98,6 +99,17 @@ function gh_release_getIdFromData() {
|
||||
RESULT=$(echo $release | jq --raw-output ".id")
|
||||
}
|
||||
|
||||
#
|
||||
# Determines the tag_name from the given JSON release data.
|
||||
#
|
||||
# RESULT = "the tag name"
|
||||
#
|
||||
function gh_release_getTagNameFromData() {
|
||||
local release="$1"
|
||||
|
||||
RESULT=$(echo $release | jq --raw-output ".tag_name")
|
||||
}
|
||||
|
||||
#
|
||||
# Uploads a asset to an existing release.
|
||||
#
|
||||
@ -141,7 +153,9 @@ function gh_release_updateRelease() {
|
||||
|
||||
gh_release_getIdFromData "$release"
|
||||
local releaseId="$RESULT"
|
||||
log_debug "$FUNCNAME releaseId=$releaseId name=$name"
|
||||
gh_release_getTagNameFromData "$release"
|
||||
local tagName="$RESULT"
|
||||
log_debug "$FUNCNAME releaseId=$releaseId name=$name tag_name=$tagName"
|
||||
|
||||
body="${body//'\'/\\\\}"
|
||||
body="${body//$'\r'/}"
|
||||
@ -150,13 +164,14 @@ function gh_release_updateRelease() {
|
||||
|
||||
local request=$(cat <<-EOF
|
||||
{
|
||||
"tag_name": "${tagName}",
|
||||
"name": "${name}",
|
||||
"body": "${body}"
|
||||
}
|
||||
EOF
|
||||
)
|
||||
|
||||
log_debug "PATCH https://api.github.com/repos/pmd/pmd/releases/${releaseId}"
|
||||
log_debug "PATCH $GITHUB_BASE_URL/releases/${releaseId}"
|
||||
log_debug " -> request: $request"
|
||||
log_info "Updating github release $releaseId"
|
||||
local response
|
||||
@ -164,7 +179,7 @@ function gh_release_updateRelease() {
|
||||
-H "Content-Type: application/json" \
|
||||
--data "${request}" \
|
||||
-X PATCH \
|
||||
"https://api.github.com/repos/pmd/pmd/releases/${releaseId}")
|
||||
"$GITHUB_BASE_URL/releases/${releaseId}")
|
||||
log_debug " -> response: $response"
|
||||
log_success "Updated release with id=$releaseId"
|
||||
}
|
||||
@ -184,7 +199,7 @@ function gh_release_publishRelease() {
|
||||
log_debug "$FUNCNAME releaseId=$releaseId"
|
||||
|
||||
local request='{"draft":false}'
|
||||
log_debug "PATCH https://api.github.com/repos/pmd/pmd/releases/${releaseId}"
|
||||
log_debug "PATCH $GITHUB_BASE_URL/releases/${releaseId}"
|
||||
log_debug " -> request: $request"
|
||||
log_info "Publishing github release $releaseId"
|
||||
local response
|
||||
@ -192,9 +207,8 @@ function gh_release_publishRelease() {
|
||||
-H "Content-Type: application/json" \
|
||||
--data "${request}" \
|
||||
-X PATCH \
|
||||
"https://api.github.com/repos/pmd/pmd/releases/${releaseId}")
|
||||
"$GITHUB_BASE_URL/releases/${releaseId}")
|
||||
log_debug " -> response: $response"
|
||||
local htmlUrl=$(echo "$response" | jq --raw-output ".html_url")
|
||||
log_success "Published release with id=$releaseId at $htmlUrl"
|
||||
}
|
||||
|
||||
|
@ -204,6 +204,16 @@ echo "$NEW_RELEASE_NOTES"
|
||||
echo
|
||||
echo
|
||||
echo
|
||||
tweet="PMD ${RELEASE_VERSION} released: https://github.com/pmd/pmd/releases/tag/pmd_releases/${RELEASE_VERSION} #PMD"
|
||||
tweet="${tweet// /%20}"
|
||||
tweet="${tweet//:/%3A}"
|
||||
tweet="${tweet//#/%23}"
|
||||
tweet="${tweet//\//%2F}"
|
||||
tweet="${tweet//$'\r'//}"
|
||||
tweet="${tweet//$'\n'//%0A}"
|
||||
echo "* Tweet about this release on https://twitter.com/pmd_analyzer:"
|
||||
echo " <https://twitter.com/intent/tweet?text=$tweet>"
|
||||
echo
|
||||
echo "------------------------------------------"
|
||||
echo "Done."
|
||||
echo "------------------------------------------"
|
||||
|
@ -1,11 +1,12 @@
|
||||
GEM
|
||||
remote: https://rubygems.org/
|
||||
specs:
|
||||
activesupport (4.2.11.1)
|
||||
i18n (~> 0.7)
|
||||
activesupport (6.0.2.1)
|
||||
concurrent-ruby (~> 1.0, >= 1.0.2)
|
||||
i18n (>= 0.7, < 2)
|
||||
minitest (~> 5.1)
|
||||
thread_safe (~> 0.3, >= 0.3.4)
|
||||
tzinfo (~> 1.1)
|
||||
zeitwerk (~> 2.2)
|
||||
addressable (2.7.0)
|
||||
public_suffix (>= 2.0.2, < 5.0)
|
||||
coffee-script (2.4.1)
|
||||
@ -15,7 +16,7 @@ GEM
|
||||
colorator (1.1.0)
|
||||
commonmarker (0.17.13)
|
||||
ruby-enum (~> 0.5)
|
||||
concurrent-ruby (1.1.5)
|
||||
concurrent-ruby (1.1.6)
|
||||
dnsruby (1.61.3)
|
||||
addressable (~> 2.5)
|
||||
em-websocket (0.5.1)
|
||||
@ -25,33 +26,32 @@ GEM
|
||||
ffi (>= 1.3.0)
|
||||
eventmachine (1.2.7)
|
||||
execjs (2.7.0)
|
||||
faraday (0.16.2)
|
||||
faraday (1.0.0)
|
||||
multipart-post (>= 1.2, < 3)
|
||||
ffi (1.11.1)
|
||||
ffi (1.12.2)
|
||||
forwardable-extended (2.6.0)
|
||||
gemoji (3.0.1)
|
||||
github-pages (200)
|
||||
activesupport (= 4.2.11.1)
|
||||
github-pages (204)
|
||||
github-pages-health-check (= 1.16.1)
|
||||
jekyll (= 3.8.5)
|
||||
jekyll-avatar (= 0.6.0)
|
||||
jekyll-avatar (= 0.7.0)
|
||||
jekyll-coffeescript (= 1.1.1)
|
||||
jekyll-commonmark-ghpages (= 0.1.5)
|
||||
jekyll-commonmark-ghpages (= 0.1.6)
|
||||
jekyll-default-layout (= 0.1.4)
|
||||
jekyll-feed (= 0.11.0)
|
||||
jekyll-feed (= 0.13.0)
|
||||
jekyll-gist (= 1.5.0)
|
||||
jekyll-github-metadata (= 2.12.1)
|
||||
jekyll-mentions (= 1.4.1)
|
||||
jekyll-optional-front-matter (= 0.3.0)
|
||||
jekyll-github-metadata (= 2.13.0)
|
||||
jekyll-mentions (= 1.5.1)
|
||||
jekyll-optional-front-matter (= 0.3.2)
|
||||
jekyll-paginate (= 1.1.0)
|
||||
jekyll-readme-index (= 0.2.0)
|
||||
jekyll-redirect-from (= 0.14.0)
|
||||
jekyll-relative-links (= 0.6.0)
|
||||
jekyll-remote-theme (= 0.4.0)
|
||||
jekyll-readme-index (= 0.3.0)
|
||||
jekyll-redirect-from (= 0.15.0)
|
||||
jekyll-relative-links (= 0.6.1)
|
||||
jekyll-remote-theme (= 0.4.1)
|
||||
jekyll-sass-converter (= 1.5.2)
|
||||
jekyll-seo-tag (= 2.5.0)
|
||||
jekyll-sitemap (= 1.2.0)
|
||||
jekyll-swiss (= 0.4.0)
|
||||
jekyll-seo-tag (= 2.6.1)
|
||||
jekyll-sitemap (= 1.4.0)
|
||||
jekyll-swiss (= 1.0.0)
|
||||
jekyll-theme-architect (= 0.1.1)
|
||||
jekyll-theme-cayman (= 0.1.1)
|
||||
jekyll-theme-dinky (= 0.1.1)
|
||||
@ -61,19 +61,18 @@ GEM
|
||||
jekyll-theme-midnight (= 0.1.1)
|
||||
jekyll-theme-minimal (= 0.1.1)
|
||||
jekyll-theme-modernist (= 0.1.1)
|
||||
jekyll-theme-primer (= 0.5.3)
|
||||
jekyll-theme-primer (= 0.5.4)
|
||||
jekyll-theme-slate (= 0.1.1)
|
||||
jekyll-theme-tactile (= 0.1.1)
|
||||
jekyll-theme-time-machine (= 0.1.1)
|
||||
jekyll-titles-from-headings (= 0.5.1)
|
||||
jemoji (= 0.10.2)
|
||||
jekyll-titles-from-headings (= 0.5.3)
|
||||
jemoji (= 0.11.1)
|
||||
kramdown (= 1.17.0)
|
||||
liquid (= 4.0.0)
|
||||
listen (= 3.1.5)
|
||||
liquid (= 4.0.3)
|
||||
mercenary (~> 0.3)
|
||||
minima (= 2.5.0)
|
||||
minima (= 2.5.1)
|
||||
nokogiri (>= 1.10.4, < 2.0)
|
||||
rouge (= 2.2.1)
|
||||
rouge (= 3.13.0)
|
||||
terminal-table (~> 1.4)
|
||||
github-pages-health-check (1.16.1)
|
||||
addressable (~> 2.3)
|
||||
@ -81,7 +80,7 @@ GEM
|
||||
octokit (~> 4.0)
|
||||
public_suffix (~> 3.0)
|
||||
typhoeus (~> 1.3)
|
||||
html-pipeline (2.12.0)
|
||||
html-pipeline (2.12.3)
|
||||
activesupport (>= 2)
|
||||
nokogiri (>= 1.4)
|
||||
http_parser.rb (0.6.0)
|
||||
@ -100,50 +99,50 @@ GEM
|
||||
pathutil (~> 0.9)
|
||||
rouge (>= 1.7, < 4)
|
||||
safe_yaml (~> 1.0)
|
||||
jekyll-avatar (0.6.0)
|
||||
jekyll (~> 3.0)
|
||||
jekyll-avatar (0.7.0)
|
||||
jekyll (>= 3.0, < 5.0)
|
||||
jekyll-coffeescript (1.1.1)
|
||||
coffee-script (~> 2.2)
|
||||
coffee-script-source (~> 1.11.1)
|
||||
jekyll-commonmark (1.3.1)
|
||||
commonmarker (~> 0.14)
|
||||
jekyll (>= 3.7, < 5.0)
|
||||
jekyll-commonmark-ghpages (0.1.5)
|
||||
jekyll-commonmark-ghpages (0.1.6)
|
||||
commonmarker (~> 0.17.6)
|
||||
jekyll-commonmark (~> 1)
|
||||
rouge (~> 2)
|
||||
jekyll-commonmark (~> 1.2)
|
||||
rouge (>= 2.0, < 4.0)
|
||||
jekyll-default-layout (0.1.4)
|
||||
jekyll (~> 3.0)
|
||||
jekyll-feed (0.11.0)
|
||||
jekyll (~> 3.3)
|
||||
jekyll-feed (0.13.0)
|
||||
jekyll (>= 3.7, < 5.0)
|
||||
jekyll-gist (1.5.0)
|
||||
octokit (~> 4.2)
|
||||
jekyll-github-metadata (2.12.1)
|
||||
jekyll (~> 3.4)
|
||||
jekyll-github-metadata (2.13.0)
|
||||
jekyll (>= 3.4, < 5.0)
|
||||
octokit (~> 4.0, != 4.4.0)
|
||||
jekyll-mentions (1.4.1)
|
||||
jekyll-mentions (1.5.1)
|
||||
html-pipeline (~> 2.3)
|
||||
jekyll (~> 3.0)
|
||||
jekyll-optional-front-matter (0.3.0)
|
||||
jekyll (~> 3.0)
|
||||
jekyll (>= 3.7, < 5.0)
|
||||
jekyll-optional-front-matter (0.3.2)
|
||||
jekyll (>= 3.0, < 5.0)
|
||||
jekyll-paginate (1.1.0)
|
||||
jekyll-readme-index (0.2.0)
|
||||
jekyll (~> 3.0)
|
||||
jekyll-redirect-from (0.14.0)
|
||||
jekyll (~> 3.3)
|
||||
jekyll-relative-links (0.6.0)
|
||||
jekyll (~> 3.3)
|
||||
jekyll-remote-theme (0.4.0)
|
||||
jekyll-readme-index (0.3.0)
|
||||
jekyll (>= 3.0, < 5.0)
|
||||
jekyll-redirect-from (0.15.0)
|
||||
jekyll (>= 3.3, < 5.0)
|
||||
jekyll-relative-links (0.6.1)
|
||||
jekyll (>= 3.3, < 5.0)
|
||||
jekyll-remote-theme (0.4.1)
|
||||
addressable (~> 2.0)
|
||||
jekyll (~> 3.5)
|
||||
rubyzip (>= 1.2.1, < 3.0)
|
||||
jekyll (>= 3.5, < 5.0)
|
||||
rubyzip (>= 1.3.0)
|
||||
jekyll-sass-converter (1.5.2)
|
||||
sass (~> 3.4)
|
||||
jekyll-seo-tag (2.5.0)
|
||||
jekyll (~> 3.3)
|
||||
jekyll-sitemap (1.2.0)
|
||||
jekyll (~> 3.3)
|
||||
jekyll-swiss (0.4.0)
|
||||
jekyll-seo-tag (2.6.1)
|
||||
jekyll (>= 3.3, < 5.0)
|
||||
jekyll-sitemap (1.4.0)
|
||||
jekyll (>= 3.7, < 5.0)
|
||||
jekyll-swiss (1.0.0)
|
||||
jekyll-theme-architect (0.1.1)
|
||||
jekyll (~> 3.5)
|
||||
jekyll-seo-tag (~> 2.0)
|
||||
@ -171,8 +170,8 @@ GEM
|
||||
jekyll-theme-modernist (0.1.1)
|
||||
jekyll (~> 3.5)
|
||||
jekyll-seo-tag (~> 2.0)
|
||||
jekyll-theme-primer (0.5.3)
|
||||
jekyll (~> 3.5)
|
||||
jekyll-theme-primer (0.5.4)
|
||||
jekyll (> 3.5, < 5.0)
|
||||
jekyll-github-metadata (~> 2.9)
|
||||
jekyll-seo-tag (~> 2.0)
|
||||
jekyll-theme-slate (0.1.1)
|
||||
@ -184,43 +183,42 @@ GEM
|
||||
jekyll-theme-time-machine (0.1.1)
|
||||
jekyll (~> 3.5)
|
||||
jekyll-seo-tag (~> 2.0)
|
||||
jekyll-titles-from-headings (0.5.1)
|
||||
jekyll (~> 3.3)
|
||||
jekyll-titles-from-headings (0.5.3)
|
||||
jekyll (>= 3.3, < 5.0)
|
||||
jekyll-watch (2.2.1)
|
||||
listen (~> 3.0)
|
||||
jemoji (0.10.2)
|
||||
jemoji (0.11.1)
|
||||
gemoji (~> 3.0)
|
||||
html-pipeline (~> 2.2)
|
||||
jekyll (~> 3.0)
|
||||
jekyll (>= 3.0, < 5.0)
|
||||
kramdown (1.17.0)
|
||||
liquid (4.0.0)
|
||||
listen (3.1.5)
|
||||
rb-fsevent (~> 0.9, >= 0.9.4)
|
||||
rb-inotify (~> 0.9, >= 0.9.7)
|
||||
ruby_dep (~> 1.2)
|
||||
liquid (4.0.3)
|
||||
listen (3.2.1)
|
||||
rb-fsevent (~> 0.10, >= 0.10.3)
|
||||
rb-inotify (~> 0.9, >= 0.9.10)
|
||||
mercenary (0.3.6)
|
||||
mini_portile2 (2.4.0)
|
||||
minima (2.5.0)
|
||||
jekyll (~> 3.5)
|
||||
minima (2.5.1)
|
||||
jekyll (>= 3.5, < 5.0)
|
||||
jekyll-feed (~> 0.9)
|
||||
jekyll-seo-tag (~> 2.1)
|
||||
minitest (5.12.2)
|
||||
minitest (5.14.0)
|
||||
multipart-post (2.1.1)
|
||||
nokogiri (1.10.4)
|
||||
nokogiri (1.10.8)
|
||||
mini_portile2 (~> 2.4.0)
|
||||
octokit (4.14.0)
|
||||
octokit (4.16.0)
|
||||
faraday (>= 0.9)
|
||||
sawyer (~> 0.8.0, >= 0.5.3)
|
||||
pathutil (0.16.2)
|
||||
forwardable-extended (~> 2.6)
|
||||
public_suffix (3.1.1)
|
||||
rb-fsevent (0.10.3)
|
||||
rb-inotify (0.10.0)
|
||||
rb-inotify (0.10.1)
|
||||
ffi (~> 1.0)
|
||||
rouge (2.2.1)
|
||||
rouge (3.13.0)
|
||||
ruby-enum (0.7.2)
|
||||
i18n
|
||||
ruby_dep (1.5.0)
|
||||
rubyzip (2.0.0)
|
||||
rubyzip (2.2.0)
|
||||
safe_yaml (1.0.5)
|
||||
sass (3.7.4)
|
||||
sass-listen (~> 4.0.0)
|
||||
@ -235,9 +233,10 @@ GEM
|
||||
thread_safe (0.3.6)
|
||||
typhoeus (1.3.1)
|
||||
ethon (>= 0.9.0)
|
||||
tzinfo (1.2.5)
|
||||
tzinfo (1.2.6)
|
||||
thread_safe (~> 0.1)
|
||||
unicode-display_width (1.6.0)
|
||||
unicode-display_width (1.6.1)
|
||||
zeitwerk (2.2.2)
|
||||
|
||||
PLATFORMS
|
||||
ruby
|
||||
@ -247,4 +246,4 @@ DEPENDENCIES
|
||||
jekyll
|
||||
|
||||
BUNDLED WITH
|
||||
1.17.3
|
||||
2.1.4
|
||||
|
@ -1,9 +1,9 @@
|
||||
repository: pmd/pmd
|
||||
|
||||
pmd:
|
||||
version: 6.21.0
|
||||
previous_version: 6.20.0
|
||||
date: ??-December-2019
|
||||
version: 6.22.0
|
||||
previous_version: 6.21.0
|
||||
date: ??-????-2020
|
||||
release_type: minor
|
||||
|
||||
# release types: major, minor, bugfix
|
||||
|
@ -61,12 +61,18 @@ entries:
|
||||
- title: Extending PMD
|
||||
output: web, pdf
|
||||
subfolderitems:
|
||||
- title: Writing a rule
|
||||
url: /pmd_userdocs_extending_writing_pmd_rules.html
|
||||
- title: Introduction to writing rules
|
||||
url: /pmd_userdocs_extending_writing_rules_intro.html
|
||||
output: web, pdf
|
||||
- title: Writing XPath rules
|
||||
- title: Your first rule
|
||||
url: /pmd_userdocs_extending_your_first_rule.html
|
||||
output: web, pdf
|
||||
- title: XPath rules
|
||||
url: /pmd_userdocs_extending_writing_xpath_rules.html
|
||||
output: web, pdf
|
||||
- title: Java rules
|
||||
url: /pmd_userdocs_extending_writing_java_rules.html
|
||||
output: web, pdf
|
||||
- title: Rule designer reference
|
||||
url: /pmd_userdocs_extending_designer_reference.html
|
||||
output: web, pdf
|
||||
@ -91,12 +97,18 @@ entries:
|
||||
- title: Tools / Integrations
|
||||
output: web, pdf
|
||||
subfolderitems:
|
||||
- title: Maven PMD plugin
|
||||
- title: Maven PMD Plugin
|
||||
output: web, pdf
|
||||
url: /pmd_userdocs_tools_maven.html
|
||||
- title: Gradle
|
||||
output: web, pdf
|
||||
url: /pmd_userdocs_tools_gradle.html
|
||||
- title: Ant
|
||||
output: web, pdf
|
||||
url: /pmd_userdocs_tools_ant.html
|
||||
- title: PMD Java API
|
||||
output: web, pdf
|
||||
url: /pmd_userdocs_tools_java_api.html
|
||||
- title: CI integrations
|
||||
output: web, pdf
|
||||
url: /pmd_userdocs_tools_ci.html
|
||||
@ -382,6 +394,12 @@ entries:
|
||||
- title: Adding metrics support to a language
|
||||
url: /pmd_devdocs_major_adding_new_metrics_framework.html
|
||||
output: web, pdf
|
||||
- title: Experimental features
|
||||
output: web, pdf
|
||||
subfolderitems:
|
||||
- title: Creating (XML) dump of the AST
|
||||
url: /pmd_devdocs_experimental_ast_dump.html
|
||||
output: web, pdf
|
||||
- title: Project documentation
|
||||
output: web, pdf
|
||||
folderitems:
|
||||
|
55
docs/_data/xpath_funs.yml
Normal file
55
docs/_data/xpath_funs.yml
Normal file
@ -0,0 +1,55 @@
|
||||
# This file describes custom XPath functions per language
|
||||
# This is rendered using _includes/custom/xpath_fun_doc.html
|
||||
|
||||
aliases:
|
||||
- &qname_param
|
||||
name: javaQualifiedName
|
||||
type: "xs:string"
|
||||
description: "The qualified name of a Java class, possibly with pairs of brackets to indicate an array type.
|
||||
Can also be a primitive type name."
|
||||
- &needs_typenode "The context node must be a {% jdoc jast::TypeNode %}"
|
||||
|
||||
langs:
|
||||
- name: "Java"
|
||||
ns: "pmd-java"
|
||||
funs:
|
||||
- name: typeIs
|
||||
returnType: "xs:boolean"
|
||||
shortDescription: "Tests a node's static type"
|
||||
description: "Returns true if the context node's static Java type is a subtype of the given type.
|
||||
This tests for the resolved type of the Java construct, not the type of the AST node.
|
||||
For example, the AST node for a literal (e.g. `5d`) has type ASTLiteral, however this
|
||||
function will compare the type of the literal (eg here, `double`) against the argument."
|
||||
notes: *needs_typenode
|
||||
parameters:
|
||||
- *qname_param
|
||||
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")]'
|
||||
outcome: "Matches variable declarators of type `List` or any of its subtypes (including e.g. `ArrayList`)"
|
||||
|
||||
|
||||
- name: typeIsExactly
|
||||
returnType: "xs:boolean"
|
||||
shortDescription: "Tests a node's static type, ignoring subtypes"
|
||||
description: "Returns true if the context node's static type is exactly the given type.
|
||||
In particular, returns false if the context node's type is
|
||||
a subtype of the given type."
|
||||
notes: *needs_typenode
|
||||
parameters:
|
||||
- *qname_param
|
||||
examples:
|
||||
- code: '//VariableDeclaratorId[pmd-java:typeIsExactly("java.lang.List")]'
|
||||
outcome: "Matches variable declarators of type `List` (but not e.g. `ArrayList`)"
|
||||
|
||||
|
||||
- name: metric
|
||||
returnType: "xs:decimal?"
|
||||
shortDescription: "Computes and returns the value of a metric"
|
||||
description: "Returns the value of the metric as evaluated on the context node"
|
||||
notes: "The context node must be a {% jdoc jast::ASTAnyTypeDeclaration %} or a {% jdoc jast::MethodLikeNode %}"
|
||||
parameters:
|
||||
- name: "metricKey"
|
||||
type: "xs:string"
|
||||
description: "The name of an enum constant in {% jdoc jmx::api.JavaOperationMetricKey %} or {% jdoc jmx::api.JavaClassMetricKey %}"
|
101
docs/_includes/custom/xpath_fun_doc.html
Normal file
101
docs/_includes/custom/xpath_fun_doc.html
Normal file
@ -0,0 +1,101 @@
|
||||
{% for lang in site.data.xpath_funs.langs %}
|
||||
|
||||
<!-- Generates the documentation of XPath functions. -->
|
||||
|
||||
### {{ lang.name }}
|
||||
|
||||
{{ lang.name }} functions are in the namespace `{{ lang.ns }}`.
|
||||
|
||||
<div class="table-responsive">
|
||||
<table width="100%">
|
||||
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Function name</th>
|
||||
<th>Description (click for details)</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
|
||||
{% for fun in lang.funs %}
|
||||
|
||||
{% capture fun_id %}{{ lang.ns | append: '-' | append: fun.name }}{% endcapture %}
|
||||
|
||||
<tr data-toggle="collapse" id="{{ fun_id }}" data-target="{{ fun_id | prepend: '#' | append: '-expand' }}" class="accordion-toggle">
|
||||
|
||||
<td>{{ fun.name }}</td>
|
||||
<td>{{ fun.shortDescription }}</td>
|
||||
<td>
|
||||
<button type="button" class="btn btn-link"><i
|
||||
class="fa fa-ellipsis-h"></i></button>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td colspan="4" class="hiddenRow">
|
||||
<div class="accordion-body collapse" id="{{ fun_id | append: '-expand' }}">
|
||||
|
||||
<div style="margin-left: 15px" class="xpath-fun-doc">
|
||||
|
||||
<!-- Yeah the ID is duplicated but it's ok on my browser and makes -->
|
||||
<!--links somehow work when details are collapsed -->
|
||||
<h4 class="fun-details-header" id="{{ fun_id }}">
|
||||
<span class="fun-ns">{{ lang.ns | append: ':' }}</span><span
|
||||
class="fun-name">{{ fun.name }}</span><span
|
||||
class="fun-ns">{{ fun | xpath_fun_type }}</span>
|
||||
</h4>
|
||||
|
||||
<div style="margin-left: 30px">
|
||||
|
||||
<dl>
|
||||
<dd>{{ fun.description | render_markdown }}</dd>
|
||||
<dt>Remarks</dt>
|
||||
<dd>{{ fun.notes | render_markdown }}</dd>
|
||||
|
||||
{% if fun.parameters.size > 0 %}
|
||||
|
||||
<dt>Parameters</dt>
|
||||
|
||||
<dd>
|
||||
<dl>
|
||||
{% for param in fun.parameters %}
|
||||
<dt>
|
||||
<span class="param-name">{{ param.name }}</span>
|
||||
<span class="param-type"> as {{ param.type }}</span>
|
||||
</dt>
|
||||
<dd>{{ param.description | render_markdown }}</dd>
|
||||
{% endfor %}
|
||||
</dl>
|
||||
</dd>
|
||||
|
||||
{% endif %}
|
||||
|
||||
{% if fun.examples.size > 0 %}
|
||||
|
||||
<dt>Examples</dt>
|
||||
|
||||
<dd>
|
||||
<dl class="code-examples">
|
||||
{% for example in fun.examples %}
|
||||
<dt><code>{{ example.code }}</code></dt>
|
||||
<dd>{{ example.outcome | render_markdown }}</dd>
|
||||
{% endfor %}
|
||||
</dl>
|
||||
</dd>
|
||||
{% endif %}
|
||||
</dl>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
|
||||
{% endfor %}
|
||||
|
||||
</table>
|
||||
|
||||
</div>
|
||||
|
||||
{% endfor %}
|
@ -1,3 +1,5 @@
|
||||
require 'kramdown'
|
||||
|
||||
module CustomFilters
|
||||
|
||||
# set intersection
|
||||
@ -44,16 +46,27 @@ module CustomFilters
|
||||
|
||||
end
|
||||
|
||||
def render_markdown(text)
|
||||
Kramdown::Document.new(text).to_html
|
||||
end
|
||||
|
||||
def xpath_fun_type(fun_yaml)
|
||||
|
||||
res = '('
|
||||
|
||||
params = fun_yaml['parameters']
|
||||
|
||||
res += params.map {|it| it['type']}.join(', ') if params
|
||||
|
||||
res + ') as ' + fun_yaml['returnType']
|
||||
end
|
||||
|
||||
def regex_replace(str, regex, subst)
|
||||
if str && regex
|
||||
str.gsub(Regexp::new(regex), subst || "")
|
||||
end
|
||||
str.gsub(Regexp.new(regex), subst || '') if str && regex
|
||||
end
|
||||
|
||||
def regex_split(str, regex = nil)
|
||||
if str
|
||||
str.split(regex && Regexp::new(regex))
|
||||
end
|
||||
str.split(regex && Regexp.new(regex)) if str
|
||||
end
|
||||
|
||||
# Takes an array of strings and maps every element x to {{ x | append: suffix }}
|
||||
@ -63,9 +76,7 @@ module CustomFilters
|
||||
|
||||
# Returns the initial argument only if the second argument is truthy
|
||||
def keep_if(any, test)
|
||||
if test
|
||||
any
|
||||
end
|
||||
any if test
|
||||
end
|
||||
|
||||
# Append the suffix only if the condition argument is truthy
|
||||
@ -91,14 +102,18 @@ module CustomFilters
|
||||
end
|
||||
end
|
||||
|
||||
def random_alphabetic(length)
|
||||
('a'..'z').to_a.shuffle[0, length].join
|
||||
end
|
||||
|
||||
|
||||
private
|
||||
|
||||
def flatten_rec(seq)
|
||||
seq.map {|h|
|
||||
if (subs = h["folderitems"] || h["subfolderitems"] || h["subfolders"])
|
||||
if (subs = h['folderitems'] || h['subfolderitems'] || h['subfolders'])
|
||||
flatten_rec(subs).flatten
|
||||
elsif (page = h["url"])
|
||||
elsif (page = h['url'])
|
||||
page
|
||||
end
|
||||
}.flatten
|
||||
@ -106,10 +121,10 @@ module CustomFilters
|
||||
|
||||
def rank_lookup_from_sidebar(sidebar)
|
||||
|
||||
folders = sidebar["entries"][0]["folders"]
|
||||
folders = sidebar['entries'][0]['folders']
|
||||
|
||||
ordered = flatten_rec(folders).select {|url|
|
||||
url && url.end_with?(".html")
|
||||
url && url.end_with?('.html')
|
||||
}
|
||||
|
||||
Hash[ordered.zip (0...ordered.size)]
|
||||
|
@ -161,7 +161,7 @@ class JavadocTag < Liquid::Tag
|
||||
end
|
||||
|
||||
def markup_link(rname, link)
|
||||
"[`#{rname}`](#{link})"
|
||||
"<a href=\"#{link}\"><code>#{rname}</code></a>"
|
||||
end
|
||||
|
||||
|
||||
|
17
docs/_plugins/render_block.rb
Normal file
17
docs/_plugins/render_block.rb
Normal file
@ -0,0 +1,17 @@
|
||||
#
|
||||
# Apply a second pass of rendering on a block
|
||||
#
|
||||
class RenderBlock < Liquid::Block
|
||||
|
||||
def initialize(tag_name, arg, tokens)
|
||||
super
|
||||
@body = tokens
|
||||
end
|
||||
|
||||
def render(context)
|
||||
template = @body.render(context)
|
||||
Liquid::Template.parse(template).render(context)
|
||||
end
|
||||
end
|
||||
|
||||
Liquid::Template.register_tag('render', RenderBlock)
|
@ -52,3 +52,35 @@ details[open] summary {
|
||||
background-color: #347DBE;
|
||||
color: white;
|
||||
}
|
||||
|
||||
|
||||
.hiddenRow {
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
.xpath-fun-doc .fun-name {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.xpath-fun-doc .fun-details-header {
|
||||
font-family: monospace;
|
||||
font-size: larger;
|
||||
}
|
||||
|
||||
.xpath-fun-doc .fun-ns {
|
||||
color: darkgray;
|
||||
}
|
||||
|
||||
.xpath-fun-doc span.param-name {
|
||||
font-weight: normal;
|
||||
|
||||
}
|
||||
|
||||
.xpath-fun-doc span.param-type {
|
||||
font-weight: lighter;
|
||||
font-style: italic;
|
||||
color: darkgray;
|
||||
}
|
||||
.xpath-fun-doc .code-examples dt {
|
||||
font-weight: normal;
|
||||
}
|
||||
|
BIN
docs/images/userdocs/designer-overview-with-nums.png
Normal file
BIN
docs/images/userdocs/designer-overview-with-nums.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 129 KiB |
BIN
docs/images/userdocs/designer-overview.png
Normal file
BIN
docs/images/userdocs/designer-overview.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 101 KiB |
@ -73,6 +73,78 @@ the breaking API changes will be performed in 7.0.0.
|
||||
an API is tagged as `@Deprecated` or not in the latest minor release. During the development of 7.0.0,
|
||||
we may decide to remove some APIs that were not tagged as deprecated, though we'll try to avoid it." %}
|
||||
|
||||
#### 6.21.0
|
||||
|
||||
##### Deprecated APIs
|
||||
|
||||
###### Internal API
|
||||
|
||||
Those APIs are not intended to be used by clients, and will be hidden or removed with PMD 7.0.0.
|
||||
You can identify them with the `@InternalApi` annotation. You'll also get a deprecation warning.
|
||||
|
||||
* {% jdoc java::lang.java.JavaLanguageHandler %}
|
||||
* {% jdoc java::lang.java.JavaLanguageParser %}
|
||||
* {% jdoc java::lang.java.JavaDataFlowHandler %}
|
||||
* Implementations of {% jdoc core::lang.rule.RuleViolationFactory %} in each
|
||||
language module, eg {% jdoc java::lang.java.rule.JavaRuleViolationFactory %}.
|
||||
See javadoc of {% jdoc core::lang.rule.RuleViolationFactory %}.
|
||||
* Implementations of {% jdoc core::RuleViolation %} in each language module,
|
||||
eg {% jdoc java::lang.java.rule.JavaRuleViolation %}. See javadoc of
|
||||
{% jdoc core::RuleViolation %}.
|
||||
|
||||
* {% jdoc core::rules.RuleFactory %}
|
||||
* {% jdoc core::rules.RuleBuilder %}
|
||||
* Constructors of {% jdoc core::RuleSetFactory %}, use factory methods from {% jdoc core::RulesetsFactoryUtils %} instead
|
||||
* {% jdoc core::RulesetsFactoryUtils#getRulesetFactory(core::PMDConfiguration, core::util.ResourceLoader) %}
|
||||
|
||||
* {% jdoc apex::lang.apex.ast.AbstractApexNode %}
|
||||
* {% jdoc apex::lang.apex.ast.AbstractApexNodeBase %}, and the related `visit`
|
||||
methods on {% jdoc apex::lang.apex.ast.ApexParserVisitor %} and its implementations.
|
||||
Use {% jdoc apex::lang.apex.ast.ApexNode %} instead, now considers comments too.
|
||||
|
||||
* {% jdoc core::lang.ast.CharStream %}, {% jdoc core::lang.ast.JavaCharStream %},
|
||||
{% jdoc core::lang.ast.SimpleCharStream %}: these are APIs used by our JavaCC
|
||||
implementations and that will be moved/refactored for PMD 7.0.0. They should not
|
||||
be used, extended or implemented directly.
|
||||
* All classes generated by JavaCC, eg {% jdoc java::lang.java.ast.JJTJavaParserState %}.
|
||||
This includes token classes, which will be replaced with a single implementation, and
|
||||
subclasses of {% jdoc core::lang.ast.ParseException %}, whose usages will be replaced
|
||||
by just that superclass.
|
||||
|
||||
###### For removal
|
||||
|
||||
* pmd-core
|
||||
* Many methods on the {% jdoc core::lang.ast.Node %} interface
|
||||
and {% jdoc core::lang.ast.AbstractNode %} base class. See their javadoc for details.
|
||||
* {% jdoc !!core::lang.ast.Node#isFindBoundary() %} is deprecated for XPath queries.
|
||||
* pmd-java
|
||||
* {% jdoc java::lang.java.AbstractJavaParser %}
|
||||
* {% jdoc java::lang.java.AbstractJavaHandler %}
|
||||
* [`ASTAnyTypeDeclaration.TypeKind`](https://javadoc.io/page/net.sourceforge.pmd/pmd-java/6.21.0/net/sourceforge/pmd/lang/java/ast/ASTAnyTypeDeclaration.TypeKind.html)
|
||||
* {% jdoc !!java::lang.java.ast.ASTAnyTypeDeclaration#getKind() %}
|
||||
* {% jdoc java::lang.java.ast.JavaQualifiedName %}
|
||||
* {% jdoc !!java::lang.java.ast.ASTCatchStatement#getBlock() %}
|
||||
* {% jdoc !!java::lang.java.ast.ASTCompilationUnit#declarationsAreInDefaultPackage() %}
|
||||
* {% jdoc java::lang.java.ast.JavaQualifiableNode %}
|
||||
* {% jdoc !!java::lang.java.ast.ASTAnyTypeDeclaration#getQualifiedName() %}
|
||||
* {% jdoc !!java::lang.java.ast.ASTMethodOrConstructorDeclaration#getQualifiedName() %}
|
||||
* {% jdoc !!java::lang.java.ast.ASTLambdaExpression#getQualifiedName() %}
|
||||
* {% jdoc_package java::lang.java.qname %} and its contents
|
||||
* {% jdoc java::lang.java.ast.MethodLikeNode %}
|
||||
* Its methods will also be removed from its implementations,
|
||||
{% jdoc java::lang.java.ast.ASTMethodOrConstructorDeclaration %},
|
||||
{% jdoc java::lang.java.ast.ASTLambdaExpression %}.
|
||||
* {% jdoc !!java::lang.java.ast.ASTAnyTypeDeclaration#getImage() %} will be removed. Please use `getSimpleName()`
|
||||
instead. This affects {% jdoc !!java::lang.java.ast.ASTAnnotationTypeDeclaration#getImage() %},
|
||||
{% jdoc !!java::lang.java.ast.ASTClassOrInterfaceDeclaration#getImage() %}, and
|
||||
{% jdoc !!java::lang.java.ast.ASTEnumDeclaration#getImage() %}.
|
||||
* Several methods of {% jdoc java::lang.java.ast.ASTTryStatement %}, replacements with other names
|
||||
have been added. This includes the XPath attribute `@Finally`, replace it with a test for `child::FinallyStatement`.
|
||||
* Several methods named `getGuardExpressionNode` are replaced with `getCondition`. This affects the
|
||||
following nodes: WhileStatement, DoStatement, ForStatement, IfStatement, AssertStatement, ConditionalExpression.
|
||||
* {% jdoc java::lang.java.ast.ASTYieldStatement %} will not implement {% jdoc java::lang.java.ast.TypeNode %}
|
||||
anymore come 7.0.0. Test the type of the expression nested within it.
|
||||
|
||||
#### 6.20.0
|
||||
|
||||
No changes.
|
||||
|
111
docs/pages/pmd/devdocs/experimental/ast_dump.md
Normal file
111
docs/pages/pmd/devdocs/experimental/ast_dump.md
Normal file
@ -0,0 +1,111 @@
|
||||
---
|
||||
title: Creating XML dump of the AST
|
||||
tags: [devdocs, experimental]
|
||||
summary: Creating a XML representation of the AST allows to analyze the AST with other tools.
|
||||
last_updated: January 17, 2020 (6.21.0)
|
||||
permalink: pmd_devdocs_experimental_ast_dump.html
|
||||
---
|
||||
|
||||
## Command line usage
|
||||
|
||||
```shell
|
||||
$ run.sh ast-dump --help
|
||||
Usage: ast-dump [options]
|
||||
Options:
|
||||
--encoding, -e
|
||||
Encoding of the source file.
|
||||
Default: UTF-8
|
||||
--file
|
||||
The file to dump
|
||||
--format, -f
|
||||
The output format.
|
||||
Default: xml
|
||||
--help, -h
|
||||
Display usage.
|
||||
--language, -l
|
||||
Specify the language to use.
|
||||
Default: java
|
||||
--read-stdin, -i
|
||||
Read source from standard input
|
||||
Default: false
|
||||
-P
|
||||
Properties for the renderer.
|
||||
Syntax: -Pkey=value
|
||||
Default: {}
|
||||
|
||||
Available languages: apex ecmascript java jsp modelica plsql pom scala text vf vm wsdl xml xsl
|
||||
Available formats: xml XML format with the same structure as the one used in XPath
|
||||
+ Properties
|
||||
+ singleQuoteAttributes Use single quotes to delimit attribute values (default true)
|
||||
+ lineSeparator Line separator to use. The default is platform-specific. (default \n)
|
||||
+ renderProlog True to output a prolog (default true)
|
||||
+ renderCommonAttributes True to render attributes like BeginLine, EndLine, etc. (default false)
|
||||
```
|
||||
|
||||
## Example
|
||||
|
||||
```shell
|
||||
$ cat Foo.java
|
||||
public class Foo {
|
||||
int a;
|
||||
}
|
||||
|
||||
$ run.sh ast-dump --format xml --language java --file Foo.java > Foo.xml
|
||||
-------------------------------------------------------------------------------
|
||||
This command line utility is experimental. It might change at any time without
|
||||
prior notice.
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
$ 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'>
|
||||
<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' />
|
||||
</VariableDeclarator>
|
||||
</FieldDeclaration>
|
||||
</ClassOrInterfaceBodyDeclaration>
|
||||
</ClassOrInterfaceBody>
|
||||
</ClassOrInterfaceDeclaration>
|
||||
</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"/>
|
||||
```
|
||||
|
||||
This example uses [xmlstarlet](http://xmlstar.sourceforge.net/) to query the xml document for any variables/fields
|
||||
with the name "a".
|
||||
|
||||
|
||||
## Programmatic usage
|
||||
|
||||
Just parse your source code to get the AST and pass it on to the `XmlTreeRenderer`:
|
||||
|
||||
```java
|
||||
import java.io.IOException;
|
||||
import java.io.StringReader;
|
||||
|
||||
import net.sourceforge.pmd.lang.LanguageRegistry;
|
||||
import net.sourceforge.pmd.lang.LanguageVersionHandler;
|
||||
import net.sourceforge.pmd.lang.Parser;
|
||||
import net.sourceforge.pmd.lang.ast.Node;
|
||||
import net.sourceforge.pmd.util.treeexport.XmlTreeRenderer;
|
||||
|
||||
public class TreeExport {
|
||||
public static void main(String[] args) throws IOException {
|
||||
LanguageVersionHandler java = LanguageRegistry.getLanguage("Java").getDefaultVersion().getLanguageVersionHandler();
|
||||
Parser parser = java.getParser(java.getDefaultParserOptions());
|
||||
Node root = parser.parse("foo", new StringReader("class Foo {}"));
|
||||
|
||||
new XmlTreeRenderer().renderSubtree(root, System.out);
|
||||
}
|
||||
}
|
||||
```
|
@ -5,32 +5,15 @@ tags: [devdocs, extending, metrics]
|
||||
summary: "PMD's Java module has an extensive framework for the calculation of metrics, which allows rule developers
|
||||
to implement and use new code metrics very simply. Most of the functionality of this framework is abstracted in such
|
||||
a way that any PMD supported language can implement such a framework without too much trouble. Here's how."
|
||||
last_updated: December 2017
|
||||
last_updated: February 2020
|
||||
permalink: pmd_devdocs_major_adding_new_metrics_framework.html
|
||||
author: Clément Fournier <clement.fournier76@gmail.com>
|
||||
---
|
||||
|
||||
|
||||
## Internal architecture of the metrics framework
|
||||
|
||||
### Overview of the Java framework
|
||||
|
||||
The framework has several subsystems, the two most easily identifiable being:
|
||||
* A **project memoizer** (`ProjectMemoizer`). When a metric is computed, it's stored back in this structure and can be
|
||||
reused later. This
|
||||
reduces the overhead on the calculation of e.g. aggregate results (`ResultOption` calculations). The contents of
|
||||
this data structure are indexed with fully qualified names (`JavaQualifiedName`), which must identify unambiguously
|
||||
classes and methods.
|
||||
|
||||
* The **façade**. The static end-user façade (`JavaMetrics`) is backed by an instance of a `JavaMetricsFaçade`. This
|
||||
allows us to abstract the functionality of the façade into `pmd-core` for other frameworks to use. The façade
|
||||
instance contains a project memoizer for the analysed project, and a metrics computer
|
||||
(`JavaMetricsComputer`). It's this last object which really computes the metric and stores back its result in the
|
||||
project mirror, while the façade only handles parameters.
|
||||
|
||||
Metrics (`Metric<N>`) plug in to this static system and only provide behaviour that's executed by the metrics computer.
|
||||
Internally, metric keys (`MetricKey<N>`) are parameterized with their version (`MetricVersion`) to index memoisation
|
||||
maps (see `ParameterizedMetricKey<N>`). This allows us to memoise several versions of the same metric without conflict.
|
||||
The framework is pretty simple. On a high level, a `Metric<N>` describes some numeric computation on a node of type `N`.
|
||||
You should wrap it into a `MetricKey<N>`, so that it can be cached on nodes (implemented by {%jdoc core::lang.metrics.MetricsUtil %}).
|
||||
|
||||
At the very least, a metrics framework has those two components and is just a convenient way to compute and memoize
|
||||
metrics on a single file. The expressive power of metrics can be improved by implementing *signature matching* capabilities,
|
||||
@ -38,53 +21,12 @@ which allows a metric to count signatures matching a specific pattern (a mask) o
|
||||
designed to work across files, given a working usage resolution. However, making that work with incremental analysis is
|
||||
harder than it looks, and has been rescheduled to another project.
|
||||
|
||||
|
||||
### Abstraction layer
|
||||
|
||||
As you may have seen, most of the functionality of the first two components are abstracted into `pmd-core`. This
|
||||
allows us to implement new metrics frameworks quite quickly. These abstract components are parameterized by the
|
||||
node types of the class and operation AST nodes. Moreover, it makes the external behaviour of the framework very
|
||||
stable across languages, yet each component can easily be customized by adding methods or overriding existing ones.
|
||||
|
||||
The signature matching aspect is framed by generic interfaces, but it can't really be abstracted more
|
||||
than that. The info given in the signatures is usually very language specific, as it includes info about e.g.
|
||||
visibility modifiers. So more work is required to implement that, but it can already be used to implement
|
||||
sophisticated metrics, that already give access to detection strategies.
|
||||
|
||||
## Implementation of a new framework
|
||||
|
||||
### 1. Groundwork
|
||||
|
||||
* Create a class implementing `QualifiedName`. This implementation must be tailored to the target language so
|
||||
that it can indentify unambiguously any class and operation in the analysed project. You
|
||||
must implement `equals`, `hashCode` and `toString`.
|
||||
[Example](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/JavaQualifiedName.java)
|
||||
* Determine the AST nodes that correspond to class and method declaration in your language. These types are
|
||||
referred hereafter as `T` and `O`, respectively. Both these types must implement the interface `QualifiableNode`,
|
||||
which means they must expose a `getQualifiedName` method to give access to their qualified name.
|
||||
|
||||
### 2. Implement the façade
|
||||
* Create a class extending `AbstractMetricsComputer<T, O>`. This object will be responsible for calculating metrics
|
||||
given a memoizer, a node and info about the metric. Typically, this object is stateless so you might as well make it
|
||||
a singleton.
|
||||
* Create a class extending `BasicProjectMemoizer<T, O>`. There's no abstract functionality to implement.
|
||||
[Example](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/JavaProjectMemoizer.java)
|
||||
[Example](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/JavaMetricsComputer.java)
|
||||
* Create a class extending `AbstractMetricsFacade<T, O>`. This class needs a reference to your `ProjectMemoizer` and
|
||||
your `MetricsComputer`. It backs the real end user façade, and handles user provided parameters before delegating to
|
||||
your `MetricsComputer`.
|
||||
[Example](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/JavaMetricsFacade.java)
|
||||
* Create the static façade of your framework. This one has an instance of your `MetricsFaçade` object and delegates
|
||||
static methods to that instance.
|
||||
[Example](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/JavaMetrics.java)
|
||||
* Create classes `AbstractOperationMetric` and `AbstractClassMetric`. These must implement `Metric<T>` and
|
||||
`Metric<O>`, respectively. They typically provide defaults for the `supports` method of each metric.
|
||||
[Example](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/AbstractJavaOperationMetric.java)
|
||||
* Create enums `ClassMetricKey` and `OperationMetricKey`. These must implement `MetricKey<T>` and `MetricKey<O>`. The
|
||||
enums list all available metric keys for your language.
|
||||
[Example](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/api/JavaOperationMetricKey.java)
|
||||
* Create metrics by extending your base classes, reference them in your enums, and you can start using them with your
|
||||
façade!
|
||||
* Implement metrics (typically in an internal package)
|
||||
* Create some public enums/ utility classes to expose metric keys
|
||||
* Implement a {%jdoc core::lang.metrics.LanguageMetricsProvider %}, to expose your metrics to the designer
|
||||
* Use your metric keys in rules with {%jdoc core::lang.metrics.MetricsUtil %}
|
||||
|
||||
### Optional: Signature matching
|
||||
|
||||
|
@ -88,17 +88,16 @@ author: Tom Copeland <tom@infoether.org>
|
||||
* November 2003 - [JavaWorld: "Bug patrol"](http://www.javaworld.com/javaworld/jw-11-2003/jw-1121-quality.html) -
|
||||
Various code inspection tools
|
||||
|
||||
* June 2003 - [Software Development Times [link broken]](http://www.sdtimes.com/news/080/story15.htm) - PMD
|
||||
is embedded in QStudio.
|
||||
* June 2003 - [Software Development Times](https://web.archive.org/web/20070914023950/http://www.sdtimes.com/article/story-20030615-15.html) - PMD is embedded in QStudio.
|
||||
|
||||
* May 2003 - [techrepublic.com](http://www.techrepublic.com/article/three-tools-that-make-java-code-review-painless-and-effective/5031836) -
|
||||
PMD, Checkstyle, and Jalopy
|
||||
|
||||
* April 2003 - [O'Reilly OnJava.com](http://onjava.com/pub/a/onjava/2003/04/09/pmd_rules.html) - PMD custom rules
|
||||
* April 2003 - [O'Reilly OnJava.com](https://web.archive.org/web/20180505093751/http://www.onjava.com/pub/a/onjava/2003/04/09/pmd_rules.html) - PMD custom rules
|
||||
|
||||
* March 2003 - [O'Reilly OnJava.com](http://onjava.com/pub/a/onjava/2003/03/12/pmd_cpd.html) - overview of CPD
|
||||
* March 2003 - [O'Reilly OnJava.com](https://web.archive.org/web/20180505092514/http://www.onjava.com/pub/a/onjava/2003/03/12/pmd_cpd.html) - overview of CPD
|
||||
|
||||
* February 2003 - [O'Reilly OnJava.com](http://onjava.com/pub/a/onjava/2003/02/12/static_analysis.html) - overview of PMD
|
||||
* February 2003 - [O'Reilly OnJava.com](https://web.archive.org/web/20180506055525/http://www.onjava.com/pub/a/onjava/2003/02/12/static_analysis.html) - overview of PMD
|
||||
|
||||
* January 2003 - [Sprout](http://netbeans.org/community/articles/interviews/tom_copeland_ole-martin_fr.html) -
|
||||
* January 2003 - [Sprout](https://netbeans.org/community/articles/interviews/tom_copeland_ole-martin_fr.html) -
|
||||
interview with Ole-Martin and Tom
|
||||
|
@ -3,18 +3,24 @@ 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: December 2017 (6.0.0)
|
||||
last_updated: February 2020 (6.22.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>
|
||||
---
|
||||
|
||||
{% jdoc_nspace :props core::properties %}
|
||||
{% jdoc_nspace :PF props::PropertyFactory %}
|
||||
|
||||
## Defining properties
|
||||
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.
|
||||
|
||||
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 flag changing the behaviour of your rule... PMD ships with many types of properties ready to use!
|
||||
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
|
||||
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
|
||||
## 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:
|
||||
* Its *name*, with which the user will refer to your property;
|
||||
@ -23,64 +29,57 @@ The basic thing you need to do as a developer is to define a **property descript
|
||||
|
||||
Don't worry, all of these attributes can be specified in a single Java statement (or xml element for XPath rules).
|
||||
|
||||
Without further ado, here is the list of available (single-value) properties:
|
||||
|
||||
|Class name|Value type|
|
||||
|----------|----------|
|
||||
|IntegerProperty | int
|
||||
|DoubleProperty | double
|
||||
|FloatProperty | float
|
||||
|LongProperty | long
|
||||
|EnumeratedProperty\<*E*\>| *E*
|
||||
|StringProperty|String
|
||||
|BooleanProperty|boolean
|
||||
|CharacterProperty|char
|
||||
|FileProperty|java.io.File
|
||||
|MethodProperty|java.lang.reflect.Method
|
||||
|TypeProperty|java.lang.Class\<?\>
|
||||
|RegexProperty|java.util.regex.Pattern
|
||||
|
||||
Each of these is complemented by a multivalued variant, whose name ends with "MultiProperty", and which returns a list of values, e.g.
|
||||
|
||||
|Class name|Value type|
|
||||
|----------|----------|
|
||||
|LongMultiProperty | List\<Long\>
|
||||
|EnumeratedMultiProperty\<*E*\>| List\<*E*\>
|
||||
|
||||
Note that RegexProperty doesn't have a multivalued variant, since the delimiters could be part of a specific value.
|
||||
|
||||
### For Java rules
|
||||
## For Java rules
|
||||
|
||||
The procedure to define a property is quite straightforward:
|
||||
* Create a property descriptor of the type you want, using its builder;
|
||||
* 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.
|
||||
|
||||
You can then retrieve the value of the property at any time using {% jdoc !a!props::PropertySource#getProperty(props::PropertyDescriptor) %}.
|
||||
|
||||
#### Creating a descriptor
|
||||
### Creating a descriptor
|
||||
|
||||
From version 6.0.0 on, properties can be built using specific **builders**. For example, to build a string property, you'd call
|
||||
Properties can be built using type-specific **builders**, which can be obtained
|
||||
from the factory methods of {% jdoc :PF %}. For example, to build a
|
||||
string property, you'd call
|
||||
```java
|
||||
StringProperty.named("myProperty")
|
||||
.desc("This is my property")
|
||||
.defaultValue("foo")
|
||||
.build();
|
||||
PropertyFactory.stringProperty("myProperty")
|
||||
.desc("This is my property")
|
||||
.defaultValue("foo")
|
||||
.build();
|
||||
```
|
||||
|
||||
This is fairly more readable than a constructor call, but keep in mind the description and the default value are not optional.
|
||||
|
||||
{%include note.html content="The constructors may be deprecated in a future release, so please use the builders instead." %}
|
||||
{%include note.html
|
||||
content='As of version 6.10.0, all property concrete classes are deprecated for
|
||||
removal in 7.0.0. See the <a href="pmd_next_major_development.html#properties-framework">detailed list of planned removals</a> for
|
||||
information about how to migrate.' %}
|
||||
|
||||
For **numeric properties**, you'd add a call to `range` to define the range of acceptable values, e.g.
|
||||
|
||||
For **numeric properties**, you can add constraints on the range of acceptable values, e.g.
|
||||
```java
|
||||
IntegerProperty.named("myIntProperty")
|
||||
PropertyFactory.intProperty("myIntProperty")
|
||||
.desc("This is my property")
|
||||
.defaultValue(3)
|
||||
.require(positive())
|
||||
.range(0, 100)
|
||||
.build();
|
||||
```
|
||||
|
||||
**Enumerated properties** are a bit less straightforward to define, though they are arguably more powerful. These properties don't have a specific value type, instead, you can choose any type of value, provided the values are from a closed set. To make that actionable, you give string labels to each of the acceptable values, and the user will provide one of those labels as a value in the XML. The property will give you back the associated value, not the label. Here's an example:
|
||||
The {% jdoc props::constraints.NumericConstraints#positive() %} method is part of
|
||||
the {% jdoc props::constraints.NumericConstraints %} class, which provides some
|
||||
other constraints. The constraint mechanism will be completely unlocked with 7.0.0,
|
||||
since we'll be migrating our API to Java 8.
|
||||
|
||||
**Enumerated properties** are a bit less straightforward to define, though they are
|
||||
arguably more powerful. These properties don't have a specific value type, instead,
|
||||
you can choose any type of value, provided the values are from a closed set. To make
|
||||
that actionable, you give string labels to each of the acceptable values, and the user
|
||||
will provide one of those labels as a value in the XML. The property will give you back
|
||||
the associated value, not the label. Here's an example:
|
||||
```java
|
||||
static Map<String, ModeStrategy> map = new HashMap<>();
|
||||
|
||||
@ -89,46 +88,54 @@ static {
|
||||
map.put("hardMode", new HardStrategy());
|
||||
}
|
||||
|
||||
static EnumeratedProperty<ModeStrategy> modeProperty
|
||||
= EnumeratedProperty.<ModeStrategy>named("modeProperty")
|
||||
.desc("This is my property")
|
||||
.defaultValue(new EasyStrategy())
|
||||
.mappings(map)
|
||||
.type(ModeStrategy.class)
|
||||
.build();
|
||||
static PropertyDescriptor<ModeStrategy> modeProperty
|
||||
= PropertyFactory.enumProperty("modeProperty", map)
|
||||
.desc("This is my property")
|
||||
.defaultValue(new EasyStrategy())
|
||||
.build();
|
||||
```
|
||||
|
||||
Note that you're required to fill in the type of the values too, using `type()`.
|
||||
### Example
|
||||
|
||||
#### Example
|
||||
|
||||
You can see an example of properties used in a PMD rule [here](https://github.com/pmd/pmd/blob/ac2ff0f6af8d16f739584ba8d00b7ea1a6311ccc/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/complexity/AvoidDeeplyNestedIfStmtsRule.java#L17).
|
||||
You can see an example of properties used in a PMD rule [here](https://github.com/pmd/pmd/blob/d06b01785a712e61d33f366520f37c2473f5bd1a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/SingularFieldRule.java#L43-L52).
|
||||
There are several things to notice here:
|
||||
* The property descriptor is declared `static final`, which should generally be the case, as descriptors are immutable and can be shared between instances of the same rule;
|
||||
* The property is declared using `definePropertyDescriptor` *in the constructor*, which ensures the property gets recognised by PMD;
|
||||
* The value of the property is *not retrieved in the constructor*, but in one of the `visit` methods (typically on the highest node in the tree, since the property doesn't change).
|
||||
* The property descriptors are declared `static final`, which should generally be
|
||||
the case, as descriptors are immutable and can be shared between instances of the same rule;
|
||||
* The property is declared using {% jdoc props::PropertySource#definePropertyDescriptor(props::PropertyDescriptor) %}` *in the constructor*,
|
||||
which ensures the property gets recognised by PMD at the time the properties
|
||||
are overridden (which happens before rule execution);
|
||||
* The value of the property is *not retrieved in the constructor*, but in one of
|
||||
the `visit` methods (typically on the highest node in the tree, since the property
|
||||
doesn't change).
|
||||
|
||||
|
||||
|
||||
### For XPath rules
|
||||
## 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:
|
||||
|
||||
| `type` attribute | Property type|
|
||||
| `type` attribute | XSD type
|
||||
|----------|----------|
|
||||
|Integer|IntegerProperty
|
||||
|Double | DoubleProperty
|
||||
|Float|FloatProperty
|
||||
|Long| LongProperty
|
||||
|String|StringProperty
|
||||
|Character|CharacterProperty
|
||||
|Boolean|BooleanProperty
|
||||
|Class|TypeProperty
|
||||
|Regex|RegexProperty
|
||||
|Integer | xs:integer
|
||||
|Long | xs:integer
|
||||
|Double | xs:decimal
|
||||
|Boolean | xs:boolean
|
||||
|String | xs:string
|
||||
|Character| xs:string
|
||||
|Regex | xs:string
|
||||
|
||||
{% include note.html
|
||||
content="In XPath 1.0 mode, all values are actually represented as
|
||||
string values, which is mostly fine as there is no type
|
||||
checking. This is a problem when [migrating from XPath 1.0
|
||||
to 2.0](pmd_userdocs_extending_writing_xpath_rules.html#migrating-from-10-to-20) though" %}
|
||||
|
||||
|
||||
Note that enumerated properties are not available in XPath rules (yet?).
|
||||
|
||||
Properties defined in XPath also *must* declare the `description` attribute. Numeric properties also expect the `min` and `max` attributes. Here are a few examples to sum it up:
|
||||
Properties defined in XPath also *must* declare the `description` attribute.
|
||||
Numeric properties also expect the `min` and `max` attributes for now. Here are
|
||||
a few examples to sum it up:
|
||||
|
||||
```xml
|
||||
<property name="stringProp" type="Boolean" value="true" description="A BooleanProperty."/>
|
||||
@ -150,9 +157,14 @@ You can then use the property in XPath with the syntax `$propertyName`, for exam
|
||||
</rule>
|
||||
```
|
||||
|
||||
#### Multivalued properties
|
||||
### Multivalued properties
|
||||
|
||||
Multivalued properties are also allowed and their `type` attribute has the form `List[Boolean]` or `List[Character]`, with every above type allowed. These properties **require XPath 2.0** to work properly, and make use of the **sequence datatype** provided by that language. You thus need to set the `version` property to `2.0` to use them. Properties can also declare the `delimiter` attribute.
|
||||
Multivalued properties are also allowed and their `type` attribute has the form
|
||||
`List[Boolean]` or `List[Character]`, with every above type allowed. These
|
||||
properties **require XPath 2.0** to work properly, and make use of the
|
||||
**sequence datatype** provided by that language. You thus need to set the
|
||||
`version` property to `2.0` to use them. Properties can also declare the
|
||||
`delimiter` attribute.
|
||||
|
||||
|
||||
|
||||
@ -171,5 +183,10 @@ Multivalued properties are also allowed and their `type` attribute has the form
|
||||
</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 named `foo` or `bar`. All other XPath 2.0 [functions operating on sequences](https://www.w3.org/TR/xpath-functions/#sequence-functions) are supported.
|
||||
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
|
||||
named `foo` or `bar`. All other XPath 2.0 [functions operating on sequences](https://www.w3.org/TR/xpath-functions/#sequence-functions)
|
||||
are supported.
|
||||
|
||||
{%include tip.html content="You can also [define properties directly in the designer](pmd_userdocs_extending_designer_reference.html#rule-properties)" %}
|
||||
|
@ -1,15 +1,12 @@
|
||||
---
|
||||
title: Rule guidelines
|
||||
tags: [extending, userdocs]
|
||||
summary: Rule Guidelines
|
||||
last_updated: July 3, 2016
|
||||
summary: "Rule Guidelines, or the last touches to a rule"
|
||||
last_updated: February 2020 (6.22.0)
|
||||
permalink: pmd_userdocs_extending_rule_guidelines.html
|
||||
author: Xavier Le Vourch, Ryan Gustafson, Romain Pelisse
|
||||
---
|
||||
|
||||
# Rule Guidelines
|
||||
|
||||
Or - Last touches to a rules
|
||||
|
||||
Here is a bunch of thing to do you may consider once your rule is “up and running”.
|
||||
|
||||
@ -25,15 +22,6 @@ Rule priority may, of course, changes a lot depending on the context of the proj
|
||||
|
||||
For instance, let’s take the ExplicitCallToGC rule (“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.
|
||||
|
||||
## Code formatting
|
||||
|
||||
We try to keep a consistent code formatting through out PMD code base to ensure an easier maintenance and also make
|
||||
the pull request as readable as possible.
|
||||
|
||||
In order to ensure this, we use a PMD specific Eclipse formatter configuration, which is maintained in a
|
||||
separate project - "build-tools": [eclipse-code-formatter.xml](https://github.com/pmd/build-tools/blob/master/eclipse/pmd-eclipse-code-formatter.xml).
|
||||
Please do not forget to use it before committing or any source code!
|
||||
|
||||
## Correctness
|
||||
|
||||
You should try to run the rule on a large code base, like the jdk source code for instance. This will help ensure that the rule does not raise exceptions when dealing with unusual constructs.
|
||||
@ -47,28 +35,3 @@ When writing a new rule, using command line option “-benchmark” on a few rul
|
||||
Rules which use 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 Jaxen overhead for AST navigation. Make sure your XPath rules using the RuleChain.
|
||||
|
||||
(TODO How does one know except by running in a debugger or horrendous performance?).
|
||||
|
||||
## Adding test cases
|
||||
|
||||
See [Testing your rules](pmd_userdocs_extending_testing.html) for the general documentation
|
||||
|
||||
### … for a rule I want to submit (in a patch)
|
||||
|
||||
Figure out the category to which you want to the rule. Then add your rule to the appropriate test class for
|
||||
the category and add the XML test data in the correct xml subpackage.
|
||||
|
||||
### … for something too specific, that I won’t be able to submit
|
||||
|
||||
See [Using the test framework externally](pmd_userdocs_extending_testing.html#using-the-test-framework-externally)
|
||||
|
||||
## Code quality
|
||||
|
||||
If you want to contribute a java rule to PMD, you should run PMD on it (Using the dogfood rulesets), to ensure that you rule follow the rules defined by the PMD community.
|
||||
|
||||
Also note, that if this is not a strong policy, most developers uses the berkeley braces syntax.
|
||||
|
||||
## Committing
|
||||
|
||||
Before committing changes, make sure the verify phase of a maven build succeeds without test failures. Drink a beer while you wait for it to finish.
|
||||
|
||||
Then read the output to make sure no fatal errors are present.
|
||||
|
164
docs/pages/pmd/userdocs/extending/writing_java_rules.md
Normal file
164
docs/pages/pmd/userdocs/extending/writing_java_rules.md
Normal file
@ -0,0 +1,164 @@
|
||||
---
|
||||
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)
|
||||
permalink: pmd_userdocs_extending_writing_java_rules.html
|
||||
author: Tom Copeland <tomcopeland@users.sourceforge.net>
|
||||
---
|
||||
|
||||
{% jdoc_nspace :coremx core::lang.metrics %}
|
||||
{% jdoc_nspace :coreast core::lang.ast %}
|
||||
{% jdoc_nspace :jmx java::lang.java.metrics %}
|
||||
{% 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" %}
|
||||
|
||||
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
|
||||
[Your First Rule](pmd_userdocs_extending_your_first_rule.html#rule-development-process).
|
||||
|
||||
Basically, you open the designer, look at the structure of the AST, and refine
|
||||
your rule as you add test cases.
|
||||
|
||||
In this page we'll talk about rules for the Java language, but the process is
|
||||
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
|
||||
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
|
||||
|
||||
## Rule execution
|
||||
|
||||
Most base rule classes use a [Visitor pattern](https://sourcemaking.com/design_patterns/visitor)
|
||||
to explore the AST.
|
||||
|
||||
### Tree traversal
|
||||
|
||||
When a rule is applied to a file, it's handed the root of the AST and told
|
||||
to traverse all the tree to look for violations. Each rule defines a specific
|
||||
`visit` method for each type of node for of the language, which
|
||||
by default just visits the children.
|
||||
|
||||
So the following rule would traverse the whole tree and do nothing:
|
||||
|
||||
```java
|
||||
public class MyRule extends AbstractJavaRule {
|
||||
// all methods are default implementations!
|
||||
}
|
||||
```
|
||||
|
||||
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 %}
|
||||
full API.
|
||||
|
||||
If you want to check for some specific node types, you can override the
|
||||
corresponding `visit` method:
|
||||
|
||||
```java
|
||||
public class MyRule extends AbstractJavaRule {
|
||||
|
||||
@Override
|
||||
public Object visit(ASTVariableDeclaratorId node, Object data) {
|
||||
// This method is called on each node of type ASTVariableDeclaratorId
|
||||
// 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);
|
||||
}
|
||||
|
||||
// this calls back to the default implementation, which recurses further down the subtree
|
||||
return super.visit(node, data);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The `super.visit(node, data)` call is super common in rule implementations,
|
||||
because it makes the traversal continue by visiting all the descendants of the
|
||||
current node.
|
||||
|
||||
#### Stopping the traversal
|
||||
|
||||
Sometimes you have checked all you needed and you're sure that the descendants
|
||||
of a node may not contain violations. In that case, you can avoid calling the
|
||||
`super` implementation and the traversal will not continue further down. This
|
||||
means that your callbacks (`visit` implementations) won't be called on the rest
|
||||
of the subtree. The siblings of the current node may be visited
|
||||
recursively nevertheless.
|
||||
|
||||
#### Economic traversal: the rulechain
|
||||
|
||||
If you don't care about the order in which the nodes are traversed (e.g. your
|
||||
rule doesn't maintain any state between visits), then you can monumentally
|
||||
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 visit methods **must not recurse!** In effect, you should call never
|
||||
call `super.visit` in the methods.
|
||||
|
||||
### Execution across files, thread-safety and statefulness
|
||||
|
||||
When starting execution, PMD will instantiate a new instance of your rule.
|
||||
If PMD is executed in multiple threads, then each thread is using its own
|
||||
instance of the rule. This means, that the rule implementation **does not need to care about
|
||||
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.
|
||||
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.
|
||||
The start method is called exactly once per file.
|
||||
|
||||
<!-- We don't support language-independent rules anyway... -->
|
||||
|
||||
|
||||
|
||||
## Rule lifecycle reference
|
||||
|
||||
### Construction
|
||||
|
||||
Exactly once:
|
||||
|
||||
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)
|
||||
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.
|
||||
3. Misconfigured rules are removed from the ruleset
|
||||
|
||||
### 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
|
||||
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
|
||||
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
|
||||
the file
|
File diff suppressed because it is too large
Load Diff
141
docs/pages/pmd/userdocs/extending/writing_rules_intro.md
Normal file
141
docs/pages/pmd/userdocs/extending/writing_rules_intro.md
Normal file
@ -0,0 +1,141 @@
|
||||
---
|
||||
title: Introduction to writing PMD rules
|
||||
tags: [extending, userdocs, getting_started]
|
||||
summary: "Writing your own PMD rules"
|
||||
last_updated: February 2020 (6.22.0)
|
||||
permalink: pmd_userdocs_extending_writing_rules_intro.html
|
||||
author: Clément Fournier <clement.fournier76@gmail.com>
|
||||
---
|
||||
|
||||
PMD is a framework to perform code analysis. You can create your own rules to
|
||||
check for patterns specific to your codebase, or the coding practices of your
|
||||
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
|
||||
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:
|
||||
|
||||
<table>
|
||||
<colgroup>
|
||||
<col width="40%" />
|
||||
<col width="70%" />
|
||||
</colgroup>
|
||||
<thead>
|
||||
<tr class="header">
|
||||
<th>Sample code (Java)</th>
|
||||
<th>AST</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td markdown="block">
|
||||
|
||||
```java
|
||||
class Foo extends Object {
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
</td>
|
||||
<td markdown="block">
|
||||
|
||||
```java
|
||||
└─ CompilationUnit
|
||||
└─ TypeDeclaration
|
||||
└─ ClassOrInterfaceDeclaration "Foo"
|
||||
├─ ExtendsList
|
||||
│ └─ ClassOrInterfaceType "Object"
|
||||
└─ ClassOrInterfaceBody
|
||||
```
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
Conceptually, PMD rules work by **matching a "pattern" against the AST** of a
|
||||
file.
|
||||
Rules explore the AST and find nodes that satisfy some conditions that are characteristic
|
||||
of the specific thing the rule is trying to flag. Rules then report a violation on these nodes.
|
||||
|
||||
### Discovering the AST
|
||||
|
||||
|
||||
ASTs are represented by Java classes deriving from {% jdoc core::lang.ast.Node %}.
|
||||
Each PMD language has its own set of such classes, and its own rules about how
|
||||
these classes relate to one another, based on the grammar of the language. For
|
||||
example, all Java AST nodes extend {% jdoc java::lang.java.ast.JavaNode %}.
|
||||
|
||||
The structure of the AST can be discovered through
|
||||
* the [Rule Designer](pmd_userdocs_extending_designer_reference.html#ast-inspection)
|
||||
* the [AST dump feature](pmd_devdocs_experimental_ast_dump.html)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Writing new rules
|
||||
|
||||
PMD supports two ways to define rules: using an **XPath query**, or using a
|
||||
**Java visitor**. XPath rules are much easier to set up, since they're defined
|
||||
directly in your ruleset XML, and are expressive enough for nearly any task.
|
||||
|
||||
On the other hand, some parts of PMD's API are only accessible from Java, e.g.
|
||||
accessing the usages of a declaration. And Java rules allow you to do some
|
||||
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.
|
||||
|
||||
|
||||
## XML rule definition
|
||||
|
||||
New rules must be declared in a ruleset before they're referenced. This is the
|
||||
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`
|
||||
|
||||
Example:
|
||||
|
||||
```xml
|
||||
<rule name="MyJavaRule"
|
||||
language="java"
|
||||
message="Violation!"
|
||||
class="com.me.MyJavaRule" >
|
||||
<description>
|
||||
Description
|
||||
</description>
|
||||
<priority>3</priority>
|
||||
</rule>
|
||||
```
|
||||
|
||||
|
||||
## Resource index
|
||||
|
||||
To learn how to write a rule:
|
||||
|
||||
* [Your First Rule](pmd_userdocs_extending_your_first_rule.html)
|
||||
introduces the basic development process of a rule with a running example
|
||||
* [Writing XPath Rules](pmd_userdocs_extending_writing_xpath_rules.html)
|
||||
explains a bit more about XPath rules and our XPath API
|
||||
* [Writing Java Rules](pmd_userdocs_extending_writing_java_rules.html)
|
||||
describes how to write a rule in Java
|
||||
|
||||
To go further:
|
||||
* [Defining Properties](pmd_userdocs_extending_defining_properties.html)
|
||||
describes how to make your rules more configurable with rule properties
|
||||
* [Testing your Rules](pmd_userdocs_extending_testing.html) introduces
|
||||
our testing framework and how you can use it to safeguard the quality of
|
||||
your rule
|
||||
|
@ -1,198 +1,142 @@
|
||||
---
|
||||
title: Writing XPath rules
|
||||
tags: [extending, userdocs]
|
||||
summary: "Writing XPath rules for PMD"
|
||||
last_updated: July 3, 2016
|
||||
summary: "This page describes XPath rule support in more details"
|
||||
last_updated: February 2020 (6.22.0)
|
||||
permalink: pmd_userdocs_extending_writing_xpath_rules.html
|
||||
author: Miguel Griffa <mikkey@users.sourceforge.net>
|
||||
author: Miguel Griffa <mikkey@users.sourceforge.net>, Clément Fournier <clement.fournier76@gmail.com>
|
||||
---
|
||||
|
||||
# XPath Rule tutorial
|
||||
|
||||
{% include note.html content="For a translation to Georgian, see [webhostinggeeks.com/science/xpath-sourceforge-ka](http://webhostinggeeks.com/science/xpath-sourceforge-ka)" %}
|
||||
{% jdoc_nspace :coremx core::lang.metrics %}
|
||||
{% jdoc_nspace :coreast core::lang.ast %}
|
||||
{% jdoc_nspace :jmx java::lang.java.metrics %}
|
||||
{% jdoc_nspace :jast java::lang.java.ast %}
|
||||
|
||||
|
||||
Writing PMD rules with XPath can be a bit easier than writing rules with Java code. Here’s an introduction on how to do that.
|
||||
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).
|
||||
|
||||
## Introduction
|
||||
<!-- Later we can document the specific subset of XPath features our wrappers support -->
|
||||
|
||||
PMD provides a very handy method for writing rules by writing an XPath query. When the XPath query finds a match, a violation is added to the report. This document focuses on XPath rules. You can go [here](pmd_userdocs_extending_writing_pmd_rules.html) for more information about writing a rule.
|
||||
## XPath version
|
||||
|
||||
## What is the Abstract Syntax Tree (AST)?
|
||||
PMD supports three XPath versions for now: 1.0, 2.0, and 1.0 compatibility mode.
|
||||
The version can be specified with the `version` property in the rule definition, like so:
|
||||
|
||||
From [FOLDOC](http://foldoc.org/abstract+syntax+tree) an AST is
|
||||
|
||||
> A data structure representing something which has been parsed, often used as a compiler or interpreter’s internal representation of a program while it is being optimised and from which code generation is performed.
|
||||
|
||||
In our context, this means that we basically have a tree representation of the Java source file. This tree can viewed as a structured document - just like XML. And since it’s conceptually similar to XML, it can be queried with XPath to find a pattern.
|
||||
|
||||
## Using Designer
|
||||
|
||||
PMD comes with a handy tool that you will love if you want to write an XPath rule. Designer, runnable from a script in `bin/`, is a very simple and useful utility for writing rules.
|
||||
|
||||
The basic steps involved in writing XPath rules are these:
|
||||
|
||||
1. Write a simple Java example source snippet in Designer
|
||||
2. See the AST for the class you wrote
|
||||
3. Write an XPath expression that matches the violation you are searching
|
||||
4. Modify the Java class and go back to previous step to refine the XPath expression
|
||||
|
||||
See [Designer Reference](pmd_userdocs_extending_designer_reference.html) for a more detailed explanation on how to use the designer.
|
||||
|
||||
## Simple XPath expressions
|
||||
|
||||
This section provides hands-on examples of XPath queries over the AST. You will probably find this section more useful if you follow it with Designer and copy/paste the examples.
|
||||
|
||||
Copy the following Java source code to Designer:
|
||||
|
||||
```java
|
||||
public class a {
|
||||
int fOne;
|
||||
int fTwo;
|
||||
|
||||
private void run() {
|
||||
int one;
|
||||
int two;
|
||||
}
|
||||
}
|
||||
```xml
|
||||
<property version="2.0" /> <!-- or "1.0", or "1.0 compatibility" -->
|
||||
```
|
||||
|
||||
Let’s assume you want to match something on class variable names. You see in the ASTVviewer that VariableDeclaratorId contains the variable name - in XML terms, the name is in the `@Image` attribute. So you try an XPath expression as follows:
|
||||
The default has always been version 1.0.
|
||||
|
||||
`//VariableDeclaratorId`
|
||||
**As of PMD version 6.22.0, XPath versions 1.0 and the 1.0 compatibility mode are
|
||||
deprecated**. XPath 2.0 is superior in many ways, for example for its support for
|
||||
type checking, sequence values, or quantified expressions. For a detailed
|
||||
but approachable review of the features of XPath 2.0 and above, see [the Saxon documentation](https://www.saxonica.com/documentation/index.html#!expressions).
|
||||
|
||||
If you try this expression you’ll see that variables declared in methods are also matched. A more precise expression for matching field declarations is, well, using the FieldDeclaration node. This expression matches only the two fields declared in the class:
|
||||
It is recommended that you migrate to 2.0 before 7.0.0, but we expect
|
||||
to be able to provide an automatic migration tool when releasing 7.0.0.
|
||||
See [the migration guide](#migrating-from-10-to-20) below.
|
||||
|
||||
`//FieldDeclaration`
|
||||
|
||||
In a similar way, you can match only local variables with this expression
|
||||
## DOM representation of ASTs
|
||||
|
||||
`//LocalVariableDeclaration`
|
||||
XPath rules view the AST as an XML-like DOM, which is what the XPath language is
|
||||
defined on. Concretely, this means:
|
||||
* Every AST node is viewed as an XML element
|
||||
* The element has for local name the value of {% jdoc core::lang.ast.Node#getXPathNodeName() %}
|
||||
for the given node
|
||||
* 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() %}.
|
||||
|
||||
With local variables we need to be more careful. Consider the following class:
|
||||
### Value conversion
|
||||
|
||||
```java
|
||||
public class a {
|
||||
private void run() {
|
||||
final int one;
|
||||
int two;
|
||||
To represent attributes, we must map Java values to [XPath Data Model (XDM)](https://www.w3.org/TR/xpath-datamodel/) values. The conversion
|
||||
depends on the XPath version used.
|
||||
|
||||
{
|
||||
int a;
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
#### XPath 1.0
|
||||
|
||||
Local variable declarations will match ‘a’, since it is a perfectly legal Java local variable. Now, a more interesting expression is to match variables declared in a method, and not on an internal block, nor in the class. Maybe you’ll start with an expression like this:
|
||||
On XPath 1.0 we map every Java value to an `xs:string` value by using the `toString`
|
||||
of the object. Since XPath 1.0 allows many implicit conversions this works, but it
|
||||
causes some incompatibilities with XPath 2.0 (see the section about migration further
|
||||
down).
|
||||
|
||||
`//MethodDeclaration//LocalVariableDeclaration`
|
||||
#### XPath 2.0
|
||||
|
||||
You’ll quickly see that all three local variables are matched. A possible solution for this is to request that the parent of the local variable declaration is the MethodDeclaration node:
|
||||
XPath 2.0 is a strongly typed language, and so we use more precise type annotations.
|
||||
In the following table we refer to the type conversion function as `conv`, a
|
||||
function from Java types to XDM types.
|
||||
|
||||
`//LocalVariableDeclaration[name(../../..) = 'MethodDeclaration']`
|
||||
| Java type `T` | XSD type `conv(T)`
|
||||
|-----------------|---------------------|
|
||||
|`int` | `xs:integer`
|
||||
|`long` | `xs:integer`
|
||||
|`double` | `xs:decimal`
|
||||
|`float` | `xs:decimal`
|
||||
|`boolean` | `xs:boolean`
|
||||
|`String` | `xs:string`
|
||||
|`Character` | `xs:string`
|
||||
|`Enum<E>` | `xs:string` (uses `Object::toString`)
|
||||
|`List<E>` | `conv(E)*` (a sequence type)
|
||||
|
||||
## Matching variables by name
|
||||
|
||||
Let’s consider that we are writing rules for logger. Let’s assume we use the Java logging API and we want to find all classes that have more than one logger. The following expression returns all variable declarations whose type is ‘Logger’.
|
||||
The same `conv` function is used to translate rule property values to XDM values.
|
||||
|
||||
`//VariableDeclarator[../Type/ReferenceType/ClassOrInterfaceType[@Image='Logger']]`
|
||||
|
||||
Finding a class with more than one logger is quite easy now. This expression matches the classes we are looking for.
|
||||
## Migrating from 1.0 to 2.0
|
||||
|
||||
```xpath
|
||||
TypeDeclaration[count(//VariableDeclarator[../Type/ReferenceType/ClassOrInterfaceType[@Image='Logger']])>1
|
||||
```
|
||||
XPath 1.0 and 2.0 have some incompatibilities. The [XPath 2.0 specification](https://www.w3.org/TR/xpath20/#id-incompat-in-false-mode)
|
||||
describes them precisely. Those are however mostly corner cases and XPath
|
||||
rules usually don't feature any of them.
|
||||
|
||||
But let’s refine this expression a little bit more. Consider the following class:
|
||||
The incompatibilities that are most relevant to migrating your rules are not
|
||||
caused by the specification, but by the different engines we use to run
|
||||
XPath 1.0 and 2.0 queries. Here's a list of known incompatibilities:
|
||||
|
||||
```java
|
||||
public class a {
|
||||
Logger log = null;
|
||||
Logger log = null;
|
||||
int b;
|
||||
* The namespace prefixes `fn:` and `string:` should not be mentioned explicitly.
|
||||
In XPath 2.0 mode, the engine will complain about an undeclared namespace, but
|
||||
the functions are in the default namespace. Removing the namespace prefixes fixes it.
|
||||
* <code><b style="color:red">fn:</b>substring("Foo", 1)</code> → `substring("Foo", 1)`
|
||||
* Conversely, calls to custom PMD functions like `typeIs` *must* be prefixed
|
||||
with the namespace of the declaring module (`pmd-java`).
|
||||
* `typeIs("Foo")` → <code><b style="color:green">pmd-java:</b>typeIs("Foo")</code>
|
||||
* Boolean attribute values on our 1.0 engine are represented as the string values
|
||||
`"true"` and `"false"`. In 2.0 mode though, boolean values are truly represented
|
||||
as boolean values, which in XPath may only be obtained through the functions
|
||||
`true()` and `false()`.
|
||||
If your XPath 1.0 rule tests an attribute like `@Private="true"`, then it just
|
||||
needs to be changed to `@Private=true()` when migrating. A type error will warn
|
||||
you that you must update the comparison. More is explained on [issue #1244](https://github.com/pmd/pmd/issues/1244).
|
||||
* `"true"`, `'true'` → `true()`
|
||||
* `"false"`, `'false'` → `false()`
|
||||
|
||||
void myMethod() {
|
||||
Logger log = null;
|
||||
int a;
|
||||
}
|
||||
class c {
|
||||
Logger a;
|
||||
Logger a;
|
||||
}
|
||||
}
|
||||
```
|
||||
* In XPath 1.0, comparing a number to a string coerces the string to a number.
|
||||
In XPath 2.0, a type error occurs. Like for boolean values, numeric values are
|
||||
represented by our 1.0 implementation as strings, meaning that `@BeginLine > "1"`
|
||||
worked ---that's not the case in 2.0 mode.
|
||||
* <code>@ArgumentCount > <b style="color:red">'</b>1<b style="color:red">'</b></code> → `@ArgumentCount > 1`
|
||||
|
||||
With this class we will only be matching one violation, when we probably would have wanted to produce two violations (one for each class). The following refined expression matches classes that contain more than one logger.
|
||||
## Rule properties
|
||||
|
||||
```xpath
|
||||
//ClassOrInterfaceBodyDeclaration[count(//VariableDeclarator[../Type/ReferenceType/ClassOrInterfaceType[@Image='Logger']])>1]
|
||||
```
|
||||
**See [Defining rule properties](pmd_userdocs_extending_defining_properties.html#for-xpath-rules)**
|
||||
|
||||
Let’s assume we have a Factory class, that could be always declared final. We’ll search an xpath expression that matches all declarations of Factory and reports a violation if it is not declared final. Consider the following class:
|
||||
|
||||
```java
|
||||
public class a {
|
||||
Factory f1;
|
||||
## PMD extension functions
|
||||
|
||||
void myMethod() {
|
||||
Factory f2;
|
||||
int a;
|
||||
}
|
||||
}
|
||||
```
|
||||
PMD provides some language-specific XPath functions to access semantic
|
||||
information from the AST.
|
||||
|
||||
The following expression does the magic we need:
|
||||
On XPath 2.0, the namespace of custom PMD function must be explicitly mentioned.
|
||||
|
||||
```xpath
|
||||
//VariableDeclarator
|
||||
[../Type/ReferenceType/ClassOrInterfaceType
|
||||
[@Image = 'Factory'] and ..[@Final='false']]
|
||||
```
|
||||
{% render %}
|
||||
{% include custom/xpath_fun_doc.html %}
|
||||
{% endrender %}
|
||||
|
||||
We recommend at this point that you experiment with Designer putting the final modifier to the Factory and verifying that the results produced are those expected.
|
||||
{% 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.' %}
|
||||
|
||||
## Creating a new rule definition
|
||||
|
||||
To actually use your new XPath rule, it needs to be in a ruleset. You can create a new custom ruleset which just
|
||||
contains your new XPath rule. You can use the following template. Just make sure, to replace the `xpath` property,
|
||||
the example code and give your rule a useful name and message.
|
||||
|
||||
``` xml
|
||||
<?xml version="1.0"?>
|
||||
|
||||
<ruleset name="Custom Rules"
|
||||
xmlns="http://pmd.sourceforge.net/ruleset/2.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd">
|
||||
<description>
|
||||
Custom rules
|
||||
</description>
|
||||
|
||||
<rule name="My Rule"
|
||||
language="java"
|
||||
message="violation message"
|
||||
class="net.sourceforge.pmd.lang.rule.XPathRule">
|
||||
<description>
|
||||
Rule Description
|
||||
</description>
|
||||
<priority>3</priority>
|
||||
<properties>
|
||||
<property name="xpath">
|
||||
<value><![CDATA[
|
||||
--- here comes your XPath expression
|
||||
]]></value>
|
||||
</property>
|
||||
</properties>
|
||||
<example>
|
||||
<![CDATA[
|
||||
public class ExampleCode {
|
||||
public void foo() {
|
||||
}
|
||||
}
|
||||
]]>
|
||||
</example>
|
||||
</rule>
|
||||
</ruleset>
|
||||
```
|
||||
|
||||
|
142
docs/pages/pmd/userdocs/extending/your_first_rule.md
Normal file
142
docs/pages/pmd/userdocs/extending/your_first_rule.md
Normal file
@ -0,0 +1,142 @@
|
||||
---
|
||||
title: Your first rule XPath
|
||||
tags: [extending, userdocs]
|
||||
summary: "Introduction to rule writing through an example."
|
||||
last_updated: February 2020 (6.22.0)
|
||||
permalink: pmd_userdocs_extending_your_first_rule.html
|
||||
author: Miguel Griffa <mikkey@users.sourceforge.net>, Clément Fournier <clement.fournier76@gmail.com>
|
||||
---
|
||||
|
||||
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
|
||||
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)
|
||||
features a comprehensive but approachable description of the syntax of XPath
|
||||
expressions.
|
||||
|
||||
## The Rule Designer
|
||||
|
||||
The rule designer is a tool that packs a lot of features to help you develop XPath
|
||||
rules quickly and painlessly. Basically, it allows you to examine the AST of a code
|
||||
snippet and evaluate an XPath expression against it.
|
||||
|
||||
Like for PMD and CPD, you can launch it using `run.sh designer` on Linux/Unix
|
||||
and `designer.bat` on Windows. The interface looks like the following:
|
||||
|
||||
{% include image.html file="userdocs/designer-overview-with-nums.png" alt="Designer overview" %}
|
||||
|
||||
The zone (2) is the **main editor**. When you write a code snippet in the
|
||||
code area to the left, you'll see that the tree to the right will be updated
|
||||
automatically: it's the AST of the code.
|
||||
Note that the code snippet must be a syntactically valid compilation unit for the
|
||||
language you've chosen, e.g. for Java, a compilation unit necessarily has a top-level
|
||||
type declaration.
|
||||
|
||||
If you select a node in the AST, its specific properties will also be displayed
|
||||
in the panel (1): they're the XPath attributes of the node. More on that later.
|
||||
|
||||
The zone (3) is the **XPath editor**. If you enter an XPath query in that area,
|
||||
it will be evaluated on the current AST and the results will be displayed in the
|
||||
list to the bottom right.
|
||||
|
||||
### Rule development process
|
||||
|
||||
|
||||
The basic development process is straightforward:
|
||||
|
||||
1. Write a code snippet in the main editor that features the offending code you're looking for
|
||||
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
|
||||
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
|
||||
save it to [make test cases](pmd_userdocs_extending_testing.html).
|
||||
|
||||
In the following sections, we walk through several examples to refine your rule.
|
||||
|
||||
## A simple rule
|
||||
|
||||
Let's say you want to prevent your coding team from naming variables of type
|
||||
`short` after your boss, whose name is Bill. You try the designer on the following
|
||||
offending code snippet:
|
||||
|
||||
```java
|
||||
|
||||
public class KeepingItSerious {
|
||||
|
||||
public void method() {
|
||||
short bill; // LocalVariableDeclaration
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
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
|
||||
in the XPath editor:
|
||||
```xpath
|
||||
//VariableDeclaratorId[@Image = "bill"]
|
||||
```
|
||||
|
||||
You can see the XPath result list is updated with the variable declarator.
|
||||
If you try the query against the following updated snippet though, you can
|
||||
see that the field declaration id is matched even though it's not of type `short`.
|
||||
|
||||
```java
|
||||
public class KeepingItSerious {
|
||||
|
||||
Delegator bill; // FieldDeclaration
|
||||
|
||||
public void method() {
|
||||
short bill; // LocalVariableDeclaration
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
You thus refine your XPath expression with an additional predicate,
|
||||
based on your examination of the Type node of the field and local variable
|
||||
declaration nodes.
|
||||
|
||||
```xpath
|
||||
//VariableDeclaratorId[@Image = "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
|
||||
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:
|
||||
|
||||
```xml
|
||||
<rule name="DontCallBossShort"
|
||||
language="java"
|
||||
message="Boss wants to talk to you."
|
||||
class="net.sourceforge.pmd.lang.rule.XPathRule" >
|
||||
<description>
|
||||
TODO
|
||||
</description>
|
||||
<priority>3</priority>
|
||||
<properties>
|
||||
<property name="xpath">
|
||||
<value>
|
||||
<![CDATA[
|
||||
//VariableDeclaratorId[../../Type[@TypeImage="short"] and @Image = "bill"]
|
||||
]]>
|
||||
</value>
|
||||
</property>
|
||||
</properties>
|
||||
</rule>
|
||||
```
|
||||
|
||||
You can notice that your XPath expression ends up inside a [property](pmd_userdocs_configuring_rules.html#rule-properties)
|
||||
of a rule of type XPathRule, which is how XPath rules are implemented.
|
@ -32,7 +32,7 @@ for you:
|
||||
changing the Rule, but you do not need to submit a patch back to the
|
||||
PMD project.
|
||||
|
||||
If you need to modify the Rule, see [How to write a rule](pmd_userdocs_extending_writing_pmd_rules.html).
|
||||
If you need to modify the Rule, see [How to write a rule](pmd_userdocs_extending_writing_rules_intro.html).
|
||||
Otherwise, the other suppression methods are explained in the following sections.
|
||||
|
||||
## Annotations
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user