diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 6d529e9e99..a1c9a7bd37 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,10 +1,19 @@ - +## Describe the PR -Before submitting a PR, please check that: - - [ ] The PR is submitted against `master`. The PMD team will merge back to support branches as needed. - - [ ] `./mvnw clean verify` passes. This will [build](https://github.com/pmd/pmd/blob/master/BUILDING.md) and test PMD, execute PMD and checkstyle rules. [Check this for more info](https://github.com/pmd/pmd/blob/master/CONTRIBUTING.md#code-style) + -**PR Description:** +## Related issues + + + +- Fixes # + +## Ready? + + + +- [ ] Added unit tests for fixed bug/feature +- [ ] Passing all unit tests +- [ ] Complete build `./mvnw clean verify` passes (checked automatically by travis) +- [ ] Added (in-code) documentation (if needed) diff --git a/.travis.yml b/.travis.yml index bcfbfccb5c..2276f63309 100644 --- a/.travis.yml +++ b/.travis.yml @@ -49,7 +49,7 @@ jobs: env: BUILD=publish before_install: - - bash .travis/before_install.sh "11.0.6+10" + - bash .travis/before_install.sh "11.0.7+10" - source ${HOME}/java.env install: true before_script: true diff --git a/do-release.sh b/do-release.sh index 614c8f12c4..f276238301 100755 --- a/do-release.sh +++ b/do-release.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash set -e # Make sure, everything is English... @@ -15,7 +15,7 @@ if [ ! -f pom.xml -o ! -d ../pmd.github.io ]; then exit 1 fi - +LAST_VERSION= RELEASE_VERSION= DEVELOPMENT_VERSION= CURRENT_BRANCH= @@ -33,11 +33,16 @@ PATCH=$(echo $RELEASE_VERSION | cut -d . -f 3) if [ "$PATCH" == "0" ]; then NEXT_MINOR=$(expr ${MINOR} + 1) NEXT_PATCH="0" + LAST_MINOR=$(expr ${MINOR} - 1) + LAST_PATCH="0" else # this is a bugfixing release NEXT_MINOR="${MINOR}" NEXT_PATCH=$(expr ${PATCH} + 1) + LAST_MINOR="${MINOR}" + LAST_PATCH=$(expr ${PATCH} - 1) fi +LAST_VERSION="$MAJOR.$LAST_MINOR.$LAST_PATCH" DEVELOPMENT_VERSION="$MAJOR.$NEXT_MINOR.$NEXT_PATCH" DEVELOPMENT_VERSION="${DEVELOPMENT_VERSION}-SNAPSHOT" @@ -52,17 +57,18 @@ CURRENT_BRANCH=$(git symbolic-ref -q HEAD) CURRENT_BRANCH=${CURRENT_BRANCH##refs/heads/} CURRENT_BRANCH=${CURRENT_BRANCH:-HEAD} -echo "RELEASE_VERSION: ${RELEASE_VERSION}" -echo "DEVELOPMENT_VERSION: ${DEVELOPMENT_VERSION}" +echo "LAST_VERSION: ${LAST_VERSION}" +echo "RELEASE_VERSION: ${RELEASE_VERSION} (this release)" +echo "DEVELOPMENT_VERSION: ${DEVELOPMENT_VERSION} (the next version after the release)" echo "CURRENT_BRANCH: ${CURRENT_BRANCH}" echo echo "Is this correct?" echo -echo "Press enter to continue..." +echo "Press enter to continue... (or CTRL+C to cancel)" read - +export LAST_VERSION export RELEASE_VERSION export DEVELOPMENT_VERSION export CURRENT_BRANCH @@ -89,6 +95,26 @@ echo echo "Press enter to continue..." read + +# calculating stats for release notes + +STATS=$( +echo "### Stats" +echo "* $(git log pmd_releases/${LAST_VERSION}..HEAD --oneline --no-merges |wc -l) commits" +echo "* $(curl -s https://api.github.com/repos/pmd/pmd/milestones|jq ".[] | select(.title == \"$RELEASE_VERSION\") | .closed_issues") closed tickets & PRs" +echo "* Days since last release: $(( ( $(date +%s) - $(git log --max-count=1 --format="%at" pmd_releases/${LAST_VERSION}) ) / 86400))" +) + +TEMP_RELEASE_NOTES=$(cat docs/pages/release_notes.md) +TEMP_RELEASE_NOTES=${TEMP_RELEASE_NOTES/\{\% endtocmaker \%\}/$STATS$'\n'$'\n'\{\% endtocmaker \%\}$'\n'} +echo "${TEMP_RELEASE_NOTES}" > docs/pages/release_notes.md + +echo +echo "Updated stats in release notes:" +echo "$STATS" +echo +echo + # install bundles needed for rendering release notes bundle install --with=release_notes_preprocessing --path vendor/bundle diff --git a/docs/_data/sidebars/pmd_sidebar.yml b/docs/_data/sidebars/pmd_sidebar.yml index 0d6145cd6c..6c84019247 100644 --- a/docs/_data/sidebars/pmd_sidebar.yml +++ b/docs/_data/sidebars/pmd_sidebar.yml @@ -55,6 +55,21 @@ entries: - title: PMD CLI reference url: /pmd_userdocs_cli_reference.html output: web, pdf + - title: PMD Report formats + url: /pmd_userdocs_report_formats.html + output: web, pdf + - title: null + output: web, pdf + subfolders: + - title: CPD reference + output: web, pdf + subfolderitems: + - title: Copy-paste detection + url: /pmd_userdocs_cpd.html + output: web, pdf + - title: CPD Report formats + url: /pmd_userdocs_cpd_report_formats.html + output: web, pdf - title: null output: web, pdf subfolders: @@ -88,9 +103,6 @@ entries: - title: Testing your rules url: /pmd_userdocs_extending_testing.html output: web, pdf - - title: Copy-paste detection - url: /pmd_userdocs_cpd.html - output: web, pdf - title: null output: web, pdf subfolders: diff --git a/docs/pages/pmd/projectdocs/committers/releasing.md b/docs/pages/pmd/projectdocs/committers/releasing.md index 0951ac7e16..f8966a7730 100644 --- a/docs/pages/pmd/projectdocs/committers/releasing.md +++ b/docs/pages/pmd/projectdocs/committers/releasing.md @@ -53,6 +53,25 @@ The designer lives at [pmd/pmd-designer](https://github.com/pmd/pmd-designer). Update property `pmd-designer.version` in **pom.xml** to reference the latest pmd-designer release. See for the available releases. +Starting with PMD 6.23.0 we'll provide small statistics for every release. This needs to be added +to the release notes as the last section. To count the closed issues and pull requests, the milestone +on github with the title of the new release is searched. Make sure, there is a milestone +on . The following snippet will +create the numbers, that can be attached to the release notes as a last section: + +```shell +LAST_VERSION=6.22.0 +NEW_VERSION=6.23.0 +NEW_VERSION_COMMITISH=HEAD + +echo "### Stats" +echo "* $(git log pmd_releases/${LAST_VERSION}..${NEW_VERSION_COMMITISH} --oneline --no-merges |wc -l) commits" +echo "* $(curl -s https://api.github.com/repos/pmd/pmd/milestones|jq ".[] | select(.title == \"$NEW_VERSION\") | .closed_issues") closed tickets & PRs" +echo "* Days since last release: $(( ( $(date +%s) - $(git log --max-count=1 --format="%at" pmd_releases/${LAST_VERSION}) ) / 86400))" +``` + +Note: this part is also integrated into `do-release.sh`. + Check in all (version) changes to branch master or any other branch, from which the release takes place: $ git commit -a -m "Prepare pmd release " diff --git a/docs/pages/pmd/userdocs/cli_reference.md b/docs/pages/pmd/userdocs/cli_reference.md index ca45b101de..a43c809081 100644 --- a/docs/pages/pmd/userdocs/cli_reference.md +++ b/docs/pages/pmd/userdocs/cli_reference.md @@ -198,76 +198,5 @@ Example: ## Available Report Formats PMD comes with many different renderers. -The mnemonics in bold are used to select them on the command line, as -arguments to the `-format` option. Some formats accept *properties*, -which can be specified with the `-property` option on the command-line. +All formats are described at [PMD Report formats](pmd_userdocs_report_formats.html) -* **codeclimate**: Renderer for Code Climate JSON format. - -* **csv**: Comma-separated values tabular format. - - Properties: - - * problem: Include problem column. Default: true. - * package: Include package column. Default: true. - * file: Include file column. Default: true. - * priority: Include priority column. Default: true. - * line: Include line column. Default: true. - * desc: Include description column. Default: true. - * ruleSet: Include Rule set column. Default: true. - * rule: Include Rule column. Default: true. - -* **emacs**: GNU Emacs integration. - -* **html**: HTML format. - - Properties: - - * linePrefix: Prefix for line number anchor in the source file. - * linkPrefix: Path to HTML source. - -* **ideaj**: IntelliJ IDEA integration. - - Properties: - - * classAndMethodName: Class and method name, pass `.method` when processing a directory. - * sourcePath: - * fileName: - -* **summaryhtml**: Summary HTML format. - - Properties: - - * linePrefix: Prefix for line number anchor in the source file. - * linkPrefix: Path to HTML source. - -* **text**: Text format. - -* **textcolor**: Text format, with color support (requires ANSI console support, e.g. xterm, rxvt, etc.). - - Properties: - - * color: Enables colors with anything other than `false` or `0`. Default: yes. - -* **textpad**: TextPad integration. - -* **vbhtml**: Vladimir Bossicard HTML format. - -* **xml**: XML format. - - Properties: - - * encoding: XML encoding format, defaults to UTF-8. - -* **xslt**: XML with a XSL transformation applied. - - Properties: - - * encoding: XML encoding format, defaults to UTF-8. - * xsltFilename: The XSLT file name. - -* **yahtml**: Yet Another HTML format. - - Properties: - - * outputDir: Output directory. diff --git a/docs/pages/pmd/userdocs/cpd.md b/docs/pages/pmd/userdocs/cpd/cpd.md similarity index 99% rename from docs/pages/pmd/userdocs/cpd.md rename to docs/pages/pmd/userdocs/cpd/cpd.md index e9843661fe..bb341e8e2a 100644 --- a/docs/pages/pmd/userdocs/cpd.md +++ b/docs/pages/pmd/userdocs/cpd/cpd.md @@ -239,6 +239,7 @@ This behavior has been introduced to ease CPD integration into scripts or hooks, * csv_with_linecount_per_file * vs +For details, see [CPD Report Formats](pmd_userdocs_cpd_report_formats.html). ## Ant task diff --git a/docs/pages/pmd/userdocs/cpd/cpd_report_formats.md b/docs/pages/pmd/userdocs/cpd/cpd_report_formats.md new file mode 100644 index 0000000000..d224b28ee7 --- /dev/null +++ b/docs/pages/pmd/userdocs/cpd/cpd_report_formats.md @@ -0,0 +1,220 @@ +--- +title: Report formats for CPD +tags: [cpd, userdocs] +keywords: [formats, renderers] +summary: "Overview of the built-in report formats for CPD" +permalink: pmd_userdocs_cpd_report_formats.html +author: Andreas Dangel +--- + +## Overview + +CPD collects occurrences of found duplications and provides them to the selected report format. +Each found code duplication appears in one or more other files, so that each code duplication can +have multiple locations. Not all report formats display all locations. + +The following examples always describe the same duplications: + +1. a code block of 239 tokens spanning 33 lines in RuleReferenceTest. This is a duplication within the same file. +2. a code block of 110 tokens spanning 16 lines in JaxenXPathRuleQueryTest. This is a duplication that appears + 3 times within the same file. + + +## text + +This is the default format. + +All duplications are reported one after another. For each duplication, the complete code snippet is output. +Each duplication is separated by `======`. + +Example: + +``` +Found a 33 line (239 tokens) duplication in the following files: +Starting at line 32 of /home/pmd/source/pmd-core/src/test/java/net/sourceforge/pmd/RuleReferenceTest.java +Starting at line 68 of /home/pmd/source/pmd-core/src/test/java/net/sourceforge/pmd/RuleReferenceTest.java + + public void testOverride() { + final StringProperty PROPERTY1_DESCRIPTOR = new StringProperty("property1", "Test property", null, 0f); + MockRule rule = new MockRule(); + rule.definePropertyDescriptor(PROPERTY1_DESCRIPTOR); + rule.setLanguage(LanguageRegistry.getLanguage(Dummy2LanguageModule.NAME)); + rule.setName("name1"); + rule.setProperty(PROPERTY1_DESCRIPTOR, "value1"); + rule.setMessage("message1"); + rule.setDescription("description1"); + rule.addExample("example1"); + rule.setExternalInfoUrl("externalInfoUrl1"); + rule.setPriority(RulePriority.HIGH); + + final StringProperty PROPERTY2_DESCRIPTOR = new StringProperty("property2", "Test property", null, 0f); + RuleReference ruleReference = new RuleReference(); + ruleReference.setRule(rule); + ruleReference.definePropertyDescriptor(PROPERTY2_DESCRIPTOR); + ruleReference.setLanguage(LanguageRegistry.getLanguage(DummyLanguageModule.NAME)); + ruleReference + .setMinimumLanguageVersion(LanguageRegistry.getLanguage(DummyLanguageModule.NAME).getVersion("1.3")); + ruleReference + .setMaximumLanguageVersion(LanguageRegistry.getLanguage(DummyLanguageModule.NAME).getVersion("1.7")); + ruleReference.setDeprecated(true); + ruleReference.setName("name2"); + ruleReference.setProperty(PROPERTY1_DESCRIPTOR, "value2"); + ruleReference.setProperty(PROPERTY2_DESCRIPTOR, "value3"); + ruleReference.setMessage("message2"); + ruleReference.setDescription("description2"); + ruleReference.addExample("example2"); + ruleReference.setExternalInfoUrl("externalInfoUrl2"); + ruleReference.setPriority(RulePriority.MEDIUM_HIGH); + + validateOverridenValues(PROPERTY1_DESCRIPTOR, PROPERTY2_DESCRIPTOR, ruleReference); +===================================================================== +Found a 16 line (110 tokens) duplication in the following files: +Starting at line 66 of /home/pmd/source/pmd-core/src/test/java/net/sourceforge/pmd/lang/rule/xpath/JaxenXPathRuleQueryTest.java +Starting at line 88 of /home/pmd/source/pmd-core/src/test/java/net/sourceforge/pmd/lang/rule/xpath/JaxenXPathRuleQueryTest.java +Starting at line 110 of /home/pmd/source/pmd-core/src/test/java/net/sourceforge/pmd/lang/rule/xpath/JaxenXPathRuleQueryTest.java + + JaxenXPathRuleQuery query = createQuery(xpath); + List ruleChainVisits = query.getRuleChainVisits(); + Assert.assertEquals(2, ruleChainVisits.size()); + Assert.assertTrue(ruleChainVisits.contains("dummyNode")); + // Note: Having AST_ROOT in the rule chain visits is probably a mistake. But it doesn't hurt, it shouldn't + // match a real node name. + Assert.assertTrue(ruleChainVisits.contains(JaxenXPathRuleQuery.AST_ROOT)); + + DummyNodeWithListAndEnum dummy = new DummyNodeWithListAndEnum(1); + RuleContext data = new RuleContext(); + data.setLanguageVersion(LanguageRegistry.findLanguageByTerseName("dummy").getDefaultVersion()); + + query.evaluate(dummy, data); + // note: the actual xpath queries are only available after evaluating + Assert.assertEquals(2, query.nodeNameToXPaths.size()); + Assert.assertEquals("self::node()[(attribute::Test1 = \"false\")][(attribute::Test2 = \"true\")]", query.nodeNameToXPaths.get("dummyNode").get(0).toString()); +``` + + +## xml + +This format uses XML to output the duplications in a more structured format. + +Example: + +```xml + + + + + + + + + + + + ruleChainVisits = query.getRuleChainVisits(); + Assert.assertEquals(2, ruleChainVisits.size()); + Assert.assertTrue(ruleChainVisits.contains("dummyNode")); + // Note: Having AST_ROOT in the rule chain visits is probably a mistake. But it doesn't hurt, it shouldn't + // match a real node name. + Assert.assertTrue(ruleChainVisits.contains(JaxenXPathRuleQuery.AST_ROOT)); + + DummyNodeWithListAndEnum dummy = new DummyNodeWithListAndEnum(1); + RuleContext data = new RuleContext(); + data.setLanguageVersion(LanguageRegistry.findLanguageByTerseName("dummy").getDefaultVersion()); + + query.evaluate(dummy, data); + // note: the actual xpath queries are only available after evaluating + Assert.assertEquals(2, query.nodeNameToXPaths.size()); + Assert.assertEquals("self::node()[(attribute::Test1 = \"false\")][(attribute::Test2 = \"true\")]", query.nodeNameToXPaths.get("dummyNode").get(0).toString());]]> + + +``` + + +## csv + +This outputs the duplication as comma separated values. It only reports the duplication size (number +of lines and tokens) and the number of occurrences. After that, the begin lines and filenames are reported on +after another. + +Example: + +``` +lines,tokens,occurrences +33,239,2,32,/home/pmd/source/pmd-core/src/test/java/net/sourceforge/pmd/RuleReferenceTest.java,68,/home/pmd/source/pmd-core/src/test/java/net/sourceforge/pmd/RuleReferenceTest.java +16,110,3,66,/home/pmd/source/pmd-core/src/test/java/net/sourceforge/pmd/lang/rule/xpath/JaxenXPathRuleQueryTest.java,88,/home/pmd/source/pmd-core/src/test/java/net/sourceforge/pmd/lang/rule/xpath/JaxenXPathRuleQueryTest.java,110,/home/pmd/source/pmd-core/src/test/java/net/sourceforge/pmd/lang/rule/xpath/JaxenXPathRuleQueryTest.java +``` + + +## csv_with_linecount_per_file + +This format is similar to "csv", but it has one difference: The duplication size in number of lines is reported +for each occurrence separately. While the tokens are the same, due to formatting or comments, the code blocks might be +different. Whitespace and comments are usually ignored when finding duplicated code. + +In each line, the duplication size in tokens is reported, then the number of occurrences. And after that, for each +file, the begin line, the number of duplicated lines and the filename. + +Example: + +``` +tokens,occurrences +239,2,32,33,/home/pmd/source/pmd-core/src/test/java/net/sourceforge/pmd/RuleReferenceTest.java,68,33,/home/pmd/source/pmd-core/src/test/java/net/sourceforge/pmd/RuleReferenc +eTest.java +110,3,66,16,/home/pmd/source/pmd-core/src/test/java/net/sourceforge/pmd/lang/rule/xpath/JaxenXPathRuleQueryTest.java,88,16,/home/pmd/source/pmd-core/src/test/java/net/sourceforge/pmd/lang/rule/xpath/JaxenXPathRuleQueryTest.java,110,16,/home/pmd/source/pmd-core/src/test/java/net/sourceforge/pmd/lang/rule/xpath/JaxenXPathRuleQueryTest.java +``` + + +## vs + +This outputs the duplication in a format, that Visual Studio. CPD can be added as a external tool and the output +is shown in the console. You can then click on the filenames to jump to the source where the duplication is located. + +Each occurrence of a duplication is reported in a separate line, that's why in this example, we have 5 lines. + +Example: + +``` +/home/pmd/source/pmd-core/src/test/java/net/sourceforge/pmd/RuleReferenceTest.java(32): Between lines 32 and 65 +/home/pmd/source/pmd-core/src/test/java/net/sourceforge/pmd/RuleReferenceTest.java(68): Between lines 68 and 101 +/home/pmd/source/pmd-core/src/test/java/net/sourceforge/pmd/lang/rule/xpath/JaxenXPathRuleQueryTest.java(66): Between lines 66 and 82 +/home/pmd/source/pmd-core/src/test/java/net/sourceforge/pmd/lang/rule/xpath/JaxenXPathRuleQueryTest.java(88): Between lines 88 and 104 +/home/pmd/source/pmd-core/src/test/java/net/sourceforge/pmd/lang/rule/xpath/JaxenXPathRuleQueryTest.java(110): Between lines 110 and 126 +``` diff --git a/docs/pages/pmd/userdocs/pmd_report_formats.md b/docs/pages/pmd/userdocs/pmd_report_formats.md new file mode 100644 index 0000000000..ff7fd639e7 --- /dev/null +++ b/docs/pages/pmd/userdocs/pmd_report_formats.md @@ -0,0 +1,339 @@ +--- +title: Report formats for PMD +tags: [pmd, userdocs] +keywords: [formats, renderers] +summary: "Overview of the built-in report formats for CPD" +permalink: pmd_userdocs_report_formats.html +author: Andreas Dangel +--- + +## Overview + +PMD can report the found rule violations in various formats. Some formats can +be customized further via properties. Violations might also be suppressed and there might +be processing errors or configuration errors. Not all report formats display all information. + +The header of the sections below are used to select the format on the command line, as +arguments to the `-format` option. When a format accepts *properties*, +those can be specified with the `-property` / `-P` option on the command-line. + +{% include note.html content="Suppressed violations are only reported, if the CLI parameter `-showsuppressed` is set." %} + +## codeclimate + +Renderer for Code Climate JSON format. + +This format is used when running PMD within [Code Climate](https://codeclimate.com/). +The renderer will stream JSON objects, each object is a own issue (a PMD rule violation). Each issue +is separated by the null character (`\0`). + +The format is specified here: . + +The code climate format doesn't support suppressed violations. It also doesn't report any errors. But it contains +the full rule details for each reported rule violation. + +Example: + +``` +{"type":"issue","check_name":"GuardLogStatement","description":"Logger calls should be surrounded by log level guards.","content":{"body":"## GuardLogStatement\n\nSince: PMD 5.1.0\n\nPriority: Medium High\n\n[Categories](https://github.com/codeclimate/spec/blob/master/SPEC.md#categories): Style\n\n[Remediation Points](https://github.com/codeclimate/spec/blob/master/SPEC.md#remediation-points): 50000\n\nWhenever using a log level, one should check if the loglevel is actually enabled, or otherwise skip the associate String creation and manipulation.\n\n### Example:\n\n```java\n\n\n // Add this for performance\n if (log.isDebugEnabled() { ...\n log.debug('log something' + ' and ' + 'concat strings');\n\n \n``` \n\n### [PMD properties](https://pmd.github.io/pmd-6.22.0/pmd_devdocs_working_with_properties.html)\n\nName | Value | Description\n--- | --- | ---\nviolationSuppressRegex | | Suppress violations with messages matching a regular expression\nviolationSuppressXPath | | Suppress violations on nodes which match a given relative XPath expression.\nlogLevels | trace,debug,info,warn,error,log,finest,finer,fine,info,warning,severe | LogLevels to guard\nguardsMethods | isTraceEnabled,isDebugEnabled,isInfoEnabled,isWarnEnabled,isErrorEnabled,isLoggable | Method use to guard the log statement\n"},"categories":["Style"],"location":{"path":"/home/pmd/source/pmd-core/src/main/java/net/sourceforge/pmd/RuleContext.java","lines":{"begin":124,"end":125}},"severity":"normal","remediation_points":50000} +{"type":"issue","check_name":"ForLoopCanBeForeach","description":"This for loop can be replaced by a foreach loop","content":{"body":"## ForLoopCanBeForeach\n\nSince: PMD 6.0.0\n\nPriority: Medium\n\n[Categories](https://github.com/codeclimate/spec/blob/master/SPEC.md#categories): Style\n\n[Remediation Points](https://github.com/codeclimate/spec/blob/master/SPEC.md#remediation-points): 50000\n\nReports loops that can be safely replaced with the foreach syntax. The rule considers loops over lists, arrays and iterators. A loop is safe to replace if it only uses the index variable to access an element of the list or array, only has one update statement, and loops through *every* element of the list or array left to right.\n\n### Example:\n\n```java\n\n\npublic class MyClass {\n void loop(List l) {\n for (int i = 0; i < l.size(); i++) { // pre Java 1.5\n System.out.println(l.get(i));\n }\n\n for (String s : l) { // post Java 1.5\n System.out.println(s);\n }\n }\n}\n\n \n``` \n\n### [PMD properties](https://pmd.github.io/pmd-6.22.0/pmd_devdocs_working_with_properties.html)\n\nName | Value | Description\n--- | --- | ---\nviolationSuppressRegex | | Suppress violations with messages matching a regular expression\nviolationSuppressXPath | | Suppress violations on nodes which match a given relative XPath expression.\n"},"categories":["Style"],"location":{"path":"/home/pmd/source/pmd-core/src/main/java/net/sourceforge/pmd/benchmark/Benchmarker.java","lines":{"begin":58,"end":62}},"severity":"normal","remediation_points":50000} +``` + +## csv + +Comma-separated values tabular format. + +This format only renders rule violations. Suppressed violations or errors are ignored. + +Example: + +``` +"Problem","Package","File","Priority","Line","Description","Rule set","Rule" +"1","net.sourceforge.pmd","/home/pmd/source/pmd-core/src/main/java/net/sourceforge/pmd/RuleContext.java","2","124","Logger calls should be surrounded by log level guards.","Best Practices","GuardLogStatement" +"1","net.sourceforge.pmd.benchmark","/home/pmd/source/pmd-core/src/main/java/net/sourceforge/pmd/benchmark/Benchmarker.java","3","58","This for loop can be replaced by a foreach loop","Best Practices","ForLoopCanBeForeach" +``` + +This format can be configured to display only certain columns. In order to not show the problem counter and package +columns, use these CLI parameters additionally: `-property problem=false -property package=false` + +**Properties:** + +* problem: Include problem column. Default: true. +* package: Include package column. Default: true. +* file: Include file column. Default: true. +* priority: Include priority column. Default: true. +* line: Include line column. Default: true. +* desc: Include description column. Default: true. +* ruleSet: Include Rule set column. Default: true. +* rule: Include Rule column. Default: true. + +## emacs + +GNU Emacs integration. + +Example: + +``` +/home/pmd/source/pmd-core/src/main/java/net/sourceforge/pmd/RuleContext.java:124: Logger calls should be surrounded by log level guards. +/home/pmd/source/pmd-core/src/main/java/net/sourceforge/pmd/benchmark/Benchmarker.java:58: This for loop can be replaced by a foreach loop +``` + +## html + +HTML format. + +This renderer provides two properties. If these are provided, then a link to the source where the violations +have been found is rendered. The following example has been created with `-property linkPrefix=https://github.com/pmd/pmd/blob/master/ -property linePrefix=L -shortnames -d pmd`. + +When using [Maven JXR Plugin](https://maven.apache.org/jxr/maven-jxr-plugin/index.html) to generate a html view +of the project's sources, then the property "htmlExtension" needs to be set to "true". This will then replace the +normal source file extensions (e.g. ".java") with ".html", so that the generated html pages are referenced. + +[Example](report-examples/pmd-report-html.html) + +**Properties:** + +* linePrefix: Prefix for line number anchor in the source file. +* linkPrefix: Path to HTML source. +* htmlExtension: Replace file extension with .html for the links (default: false) + +## ideaj + +IntelliJ IDEA integration. + +{% include warning.html content="This format can only be used as described in [Tools: IDEA](pmd_userdocs_tools.html#idea)." %} + +It has two ways of calling: + +1. For a single file: then all three properties need to be provided + +`run.sh pmd -d src/Foo.java -R rulesets/java/quickstart.xml -f ideaj -P fileName=src/Foo.java -P sourcePath=/home/pmd/src -P classAndMethodName=Foo` + +2. For a directory: then the fileName property can be omitted + +`run.sh pmd -d src -R rulesets/java/quickstart.xml -f ideaj -P sourcePath=/home/pmd/src -P classAndMethodName=.method` + +Example: + +``` +Logger calls should be surrounded by log level guards. + at Foo(:124) +This for loop can be replaced by a foreach loop + at Foo(:58) +``` + +**Properties:** + +* classAndMethodName: Class and method name, pass `.method` when processing a directory. +* sourcePath: +* fileName: + +## summaryhtml + +Summary HTML format. + +This is the [html renderer](#html) but with an extra section, the summarizes the violations per rule. + +[Example](report-examples/pmd-report-summaryhtml.html) + +**Properties:** + +* linePrefix: Prefix for line number anchor in the source file. +* linkPrefix: Path to HTML source. +* htmlExtension: Replace file extension with .html for the links (default: false) + +## text (default) + +This is the default format. + +This format outputs one line per violation. At the end, processing errors, suppressed violations +and configuration errors are reported. + +Example: + +``` +/home/pmd/source/pmd-core/src/main/java/net/sourceforge/pmd/RuleContext.java:124: Logger calls should be surrounded by log level guards. +/home/pmd/source/pmd-core/src/main/java/net/sourceforge/pmd/benchmark/Benchmarker.java:58: This for loop can be replaced by a foreach loop +/home/pmd/source/pmd-core/src/test/resources/net/sourceforge/pmd/cpd/files/file_with_ISO-8859-1_encoding.java - PMDException: Error while parsing /home/pmd/source/pmd-core/src/test/resources/net/sourceforge/pmd/cpd/files/file_with_ISO-8859-1_encoding.java +CloseResource rule violation suppressed by Annotation in /home/pmd/source/pmd-core/src/main/java/net/sourceforge/pmd/PMD.java +LoosePackageCoupling - No packages or classes specified +``` + +## textcolor + +Text format, with color support (requires ANSI console support, e.g. xterm, rxvt, etc.). + +Example: + +
+* file: ./pmd-core/src/main/java/net/sourceforge/pmd/RuleContext.java
+    src:  RuleContext.java:124:125
+    rule: GuardLogStatement
+    msg:  Logger calls should be surrounded by log level guards.
+    code: LOG.warning("The method RuleContext::setSourceCodeFilename(String) has been deprecated and will be removed."
+
+* file: ./pmd-core/src/main/java/net/sourceforge/pmd/benchmark/Benchmarker.java
+    src:  Benchmarker.java:58:62
+    rule: ForLoopCanBeForeach
+    msg:  This for loop can be replaced by a foreach loop
+    code: for (int i = 0; i < args.length; i++) {
+
+
+
+Summary:
+
+net.sourceforge.pmd.RuleContext : 1
+net.sourceforge.pmd.benchmark.Benchmarker : 1
+* file: ./pmd-core/src/test/resources/net/sourceforge/pmd/cpd/files/file_with_ISO-8859-1_encoding.java
+    err:  PMDException: Error while parsing /home/pmd/source/pmd-core/src/test/resources/net/sourceforge/pmd/cpd/files/file_with_ISO-8859-1_encoding.java
+net.sourceforge.pmd.PMDException: Error while parsing /home/pmd/source/pmd-core/src/test/resources/net/sourceforge/pmd/cpd/files/file_with_ISO-8859-1_encoding.java
+    at net.sourceforge.pmd.SourceCodeProcessor.processSourceCodeWithoutCache(SourceCodeProcessor.java:110)
+    at net.sourceforge.pmd.SourceCodeProcessor.processSourceCode(SourceCodeProcessor.java:89)
+    at net.sourceforge.pmd.SourceCodeProcessor.processSourceCode(SourceCodeProcessor.java:51)
+    at net.sourceforge.pmd.processor.PmdRunnable.call(PmdRunnable.java:78)
+    at net.sourceforge.pmd.processor.PmdRunnable.call(PmdRunnable.java:24)
+    at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)
+    at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:515)
+    at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)
+    at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1130)
+    at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:630)
+    at java.base/java.lang.Thread.run(Thread.java:832)
+Caused by: net.sourceforge.pmd.lang.java.ast.ParseException: Encountered " "-" "- "" at line 6, column 30.
+Was expecting one of:
+    "extends" ...
+    "implements" ...
+    "{" ...
+    "<" ...
+    
+    at net.sourceforge.pmd.lang.java.ast.JavaParser.generateParseException(JavaParser.java:12713)
+    at net.sourceforge.pmd.lang.java.ast.JavaParser.jj_consume_token(JavaParser.java:12597)
+    at net.sourceforge.pmd.lang.java.ast.JavaParser.ClassOrInterfaceBody(JavaParser.java:1554)
+    at net.sourceforge.pmd.lang.java.ast.JavaParser.ClassOrInterfaceDeclaration(JavaParser.java:732)
+    at net.sourceforge.pmd.lang.java.ast.JavaParser.TypeDeclaration(JavaParser.java:639)
+    at net.sourceforge.pmd.lang.java.ast.JavaParser.CompilationUnit(JavaParser.java:373)
+    at net.sourceforge.pmd.lang.java.AbstractJavaParser.parse(AbstractJavaParser.java:62)
+    at net.sourceforge.pmd.SourceCodeProcessor.parse(SourceCodeProcessor.java:121)
+    at net.sourceforge.pmd.SourceCodeProcessor.processSource(SourceCodeProcessor.java:185)
+    at net.sourceforge.pmd.SourceCodeProcessor.processSourceCodeWithoutCache(SourceCodeProcessor.java:107)
+    ... 10 more
+
+
+* rule: LoosePackageCoupling
+    err:  No packages or classes specified
+
+* errors:   2
+* warnings: 2
+
+ +**Properties:** + +* color: Enables colors with anything other than `false` or `0`. Default: yes. + +## textpad + +TextPad integration. + +Example: + +``` +/home/pmd/source/pmd-core/src/main/java/net/sourceforge/pmd/RuleContext.java(124, GuardLogStatement): Logger calls should be surrounded by log level guards. +/home/pmd/source/pmd-core/src/main/java/net/sourceforge/pmd/benchmark/Benchmarker.java(58, ForLoopCanBeForeach): This for loop can be replaced by a foreach loop +``` + +## vbhtml + +Vladimir Bossicard HTML format. + + +## xml + +XML format. + +This format is a XML document, that can be validated by a XSD schema. The schema is [report_2_0_0.xsd](https://github.com/pmd/pmd/blob/master/pmd-core/src/main/resources/report_2_0_0.xsd). + +Example: + +```xml + + + + +Logger calls should be surrounded by log level guards. + + + + +This for loop can be replaced by a foreach loop + + + + + + + + +``` + +**Properties:** + +* encoding: XML encoding format, defaults to UTF-8. + +## xslt + +XML with a XSL transformation applied. + +PMD provides one built-in stylesheet, that is used by default, if no other +stylesheet with the property "xsltFilename" is specified. It is called [pmd-nicerhtml.xsl](https://github.com/pmd/pmd/blob/master/pmd-core/src/main/resources/pmd-nicerhtml.xsl) and can be used for customization. + +[Example with pmd-nicerhtml.xsl](report-examples/pmd-report-pmd-nicerhtml.html) + +**Properties:** + +* encoding: XML encoding format, defaults to UTF-8. +* xsltFilename: The XSLT file name. + +## yahtml + +Yet Another HTML format. + +This renderer creates an html file per analyzed source file, hence you need to specify a output directory. +The output directory must exist. If not specified, the html files are created in the current directory. + +[Example](report-examples/pmd-report-yahtml/index.html) + +**Properties:** + +* outputDir: Output directory. diff --git a/docs/pages/pmd/userdocs/tools/tools.md b/docs/pages/pmd/userdocs/tools/tools.md index b8427311fe..b3e8d550c9 100644 --- a/docs/pages/pmd/userdocs/tools/tools.md +++ b/docs/pages/pmd/userdocs/tools/tools.md @@ -243,11 +243,11 @@ Here's how to set it up as an "External Tool": * Name: PMD * Description: PMD, good for what ails you. * Menu: Select the "Main menu", "Project views", "Editor menu", and "Search results" checkboxes. - * Program: $JDKPath$\bin\java.exe + * Program: `c:\pmd\bin\pmd.bat` * For the next parameter you'll need to plug in the location of your PMD installation and the rulesets you want to use * Parameters: - `-cp %CLASSPATH%;c:\pmd\lib\pmd-{{pmd.site.version}}.jar;c:\pmd\lib\asm-3.2.jar;c:\pmd\lib\jaxen-1.1.1.jar net.sourceforge.pmd.PMD "$FilePath$" ideaj unusedcode,imports "$Sourcepath$" $FileClass$.method $FileName$` + `-d "$FilePath$" -f ideaj -R rulesets/java/quickstart.xml -P sourcePath="$Sourcepath$" -P classAndMethodName=$FileClass$.method -P fileName=$FileName$` That's pretty much it. Now you can right click on a source directory and select PMD, it'll run recursively on the source files, and the results should diff --git a/docs/pages/release_notes.md b/docs/pages/release_notes.md index b20e4ec7ff..ab8bffd0a5 100644 --- a/docs/pages/release_notes.md +++ b/docs/pages/release_notes.md @@ -46,12 +46,18 @@ Note that XPath 1.0 support, the default XPath version, is deprecated since PMD * [#2210](https://github.com/pmd/pmd/issues/2210): \[apex] ApexCRUDViolation: Support WITH SECURITY_ENFORCED * [#2399](https://github.com/pmd/pmd/issues/2399): \[apex] ApexCRUDViolation: false positive with security enforced with line break * core + * [#2019](https://github.com/pmd/pmd/issues/2019): \[core] Insufficient deprecation warnings for XPath attributes +* doc * [#2355](https://github.com/pmd/pmd/issues/2355): \[doc] Improve documentation about incremental analysis * [#2356](https://github.com/pmd/pmd/issues/2356): \[doc] Add missing doc about pmd.github.io * [#2412](https://github.com/pmd/pmd/issues/2412): \[core] HTMLRenderer doesn't render links to source files + * [#2413](https://github.com/pmd/pmd/issues/2413): \[doc] Improve documentation about the available renderers (PMD/CPD) * java * [#2378](https://github.com/pmd/pmd/issues/2378): \[java] AbstractJUnitRule has bad performance on large code bases +* java-bestpractices + * [#2398](https://github.com/pmd/pmd/issues/2398): \[java] AbstractClassWithoutAbstractMethod false negative with inner abstract classes * java-codestyle + * [#1164](https://github.com/pmd/pmd/issues/1164): \[java] ClassNamingConventions suggests to add Util for class containing only static constants * [#1723](https://github.com/pmd/pmd/issues/1723): \[java] UseDiamondOperator false-positive inside lambda * java-design * [#2390](https://github.com/pmd/pmd/issues/2390): \[java] AbstractClassWithoutAnyMethod: missing violation for nested classes @@ -59,6 +65,8 @@ Note that XPath 1.0 support, the default XPath version, is deprecated since PMD * [#2402](https://github.com/pmd/pmd/issues/2402): \[java] CloseResource possible false positive with Primitive Streams * java-multithreading * [#2313](https://github.com/pmd/pmd/issues/2313): \[java] Documenation for DoNotUseThreads is outdated +* javascript-errorprone + * [#384](https://github.com/pmd/pmd/issues/384): \[javascript] Trailing commas not detected on French default locale ### API Changes @@ -69,6 +77,11 @@ Note that XPath 1.0 support, the default XPath version, is deprecated since PMD 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 core::lang.rule.xpath.AbstractXPathRuleQuery %} +* {% jdoc core::lang.rule.xpath.JaxenXPathRuleQuery %} +* {% jdoc core::lang.rule.xpath.SaxonXPathRuleQuery %} +* {% jdoc core::lang.rule.xpath.XPathRuleQuery %} + ##### In ASTs As part of the changes we'd like to do to AST classes for 7.0.0, we would like to @@ -118,6 +131,12 @@ implementations, and their corresponding Parser if it exists (in the same packag * {% jdoc matlab::lang.matlab.MatlabTokenManager %} * {% jdoc objectivec::lang.objectivec.ObjectiveCTokenManager %} +In the **Java AST** the following attributes are deprecated and will issue a warning when used in XPath rules: + +* {% jdoc !!java::lang.java.ast.ASTAdditiveExpression#getImage() %} - use `getOperator()` instead +* {% jdoc !!java::lang.java.ast.ASTVariableDeclaratorId#getImage() %} - use `getName()` instead +* {% jdoc !!java::lang.java.ast.ASTVariableDeclaratorId#getVariableName() %} - use `getName()` instead + ##### For removal * {% jdoc !!core::lang.Parser#getTokenManager(java.lang.String,java.io.Reader) %} @@ -125,6 +144,10 @@ implementations, and their corresponding Parser if it exists (in the same packag * {% jdoc !!core::lang.ast.AbstractTokenManager#setFileName(java.lang.String) %} * {% jdoc !!core::lang.ast.AbstractTokenManager#getFileName(java.lang.String) %} * {% jdoc !!core::cpd.token.AntlrToken#getType() %} - use `getKind()` instead. +* {% jdoc core::lang.rule.ImmutableLanguage %} +* {% jdoc core::lang.rule.MockRule %} +* {% jdoc !!java::lang.java.ast.ASTRecordDeclaration#getComponentList() %} +* Multiple fields, constructors and methods in {% jdoc core::lang.rule.XPathRule %}. See javadoc for details. ### External Contributions @@ -137,6 +160,9 @@ implementations, and their corresponding Parser if it exists (in the same packag * [#2397](https://github.com/pmd/pmd/pull/2397): \[apex] fixed WITH SECURITY_ENFORCED regex to recognise line break characters - [Kieran Black](https://github.com/kieranlblack) * [#2401](https://github.com/pmd/pmd/pull/2401): \[doc] Update DoNotUseThreads rule documentation - [Saikat Sengupta](https://github.com/s4ik4t) * [#2403](https://github.com/pmd/pmd/pull/2403): \[java] #2402 fix false-positives on Primitive Streams - [Bernd Farka](https://github.com/BerndFarkaDyna) +* [#2409](https://github.com/pmd/pmd/pull/2409): \[java] ClassNamingConventions suggests to add Util for class containing only static constants, fixes #1164 - [Binu R J](https://github.com/binu-r) +* [#2411](https://github.com/pmd/pmd/pull/2411): \[java] Fix UseAssertEqualsInsteadOfAssertTrue Example - [Moritz Scheve](https://github.com/Blightbuster) +* [#2423](https://github.com/pmd/pmd/pull/2423): \[core] Fix Checkstyle OperatorWrap in AbstractTokenizer - [Harsh Kukreja](https://github.com/harsh-kukreja) {% endtocmaker %} diff --git a/docs/report-examples/pmd-report-html.html b/docs/report-examples/pmd-report-html.html new file mode 100644 index 0000000000..2c1c41342d --- /dev/null +++ b/docs/report-examples/pmd-report-html.html @@ -0,0 +1,67 @@ +PMD +

PMD report

Problems found

+ + + + + + + + + + + + + +
#FileLineProblem
1pmd-core/src/main/java/net/sourceforge/pmd/RuleContext.java124Logger calls should be surrounded by log level guards.
2pmd-core/src/main/java/net/sourceforge/pmd/benchmark/Benchmarker.java58This for loop can be replaced by a foreach loop

Processing errors

+ + + + + +
FileProblem
pmd-core/src/test/resources/net/sourceforge/pmd/cpd/files/file_with_ISO-8859-1_encoding.java
net.sourceforge.pmd.PMDException: Error while parsing pmd-core/src/test/resources/net/sourceforge/pmd/cpd/files/file_with_ISO-8859-1_encoding.java
+    at net.sourceforge.pmd.SourceCodeProcessor.processSourceCodeWithoutCache(SourceCodeProcessor.java:110)
+    at net.sourceforge.pmd.SourceCodeProcessor.processSourceCode(SourceCodeProcessor.java:89)
+    at net.sourceforge.pmd.SourceCodeProcessor.processSourceCode(SourceCodeProcessor.java:51)
+    at net.sourceforge.pmd.processor.PmdRunnable.call(PmdRunnable.java:78)
+    at net.sourceforge.pmd.processor.PmdRunnable.call(PmdRunnable.java:24)
+    at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)
+    at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:515)
+    at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)
+    at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1130)
+    at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:630)
+    at java.base/java.lang.Thread.run(Thread.java:832)
+Caused by: net.sourceforge.pmd.lang.java.ast.ParseException: Encountered " "-" "- "" at line 6, column 30.
+Was expecting one of:
+    "extends" ...
+    "implements" ...
+    "{" ...
+    "<" ...
+    
+    at net.sourceforge.pmd.lang.java.ast.JavaParser.generateParseException(JavaParser.java:12713)
+    at net.sourceforge.pmd.lang.java.ast.JavaParser.jj_consume_token(JavaParser.java:12597)
+    at net.sourceforge.pmd.lang.java.ast.JavaParser.ClassOrInterfaceBody(JavaParser.java:1554)
+    at net.sourceforge.pmd.lang.java.ast.JavaParser.ClassOrInterfaceDeclaration(JavaParser.java:732)
+    at net.sourceforge.pmd.lang.java.ast.JavaParser.TypeDeclaration(JavaParser.java:639)
+    at net.sourceforge.pmd.lang.java.ast.JavaParser.CompilationUnit(JavaParser.java:373)
+    at net.sourceforge.pmd.lang.java.AbstractJavaParser.parse(AbstractJavaParser.java:62)
+    at net.sourceforge.pmd.SourceCodeProcessor.parse(SourceCodeProcessor.java:121)
+    at net.sourceforge.pmd.SourceCodeProcessor.processSource(SourceCodeProcessor.java:185)
+    at net.sourceforge.pmd.SourceCodeProcessor.processSourceCodeWithoutCache(SourceCodeProcessor.java:107)
+    ... 10 more
+

Suppressed warnings

+ + + + + + + + +
FileLineRuleNOPMD or AnnotationReason
pmd-core/src/main/java/net/sourceforge/pmd/PMD.java505CloseResourceAnnotation

Configuration errors

+ + + + + +
RuleProblem
LoosePackageCouplingNo packages or classes specified
\ No newline at end of file diff --git a/docs/report-examples/pmd-report-pmd-nicerhtml.html b/docs/report-examples/pmd-report-pmd-nicerhtml.html new file mode 100644 index 0000000000..702d8b1e8f --- /dev/null +++ b/docs/report-examples/pmd-report-pmd-nicerhtml.html @@ -0,0 +1,223 @@ + + + + + PMD 6.22.0 Report + + + + + + + + +
+ +

PMD 6.22.0 Report. Generated on 2020-04-11 - 19:23:45

+
+
+

Summary

+ + + + + + + + + + + + + + + + + + + +
FilesTotal +
Priority 1
+
+
Priority 2
+
+
Priority 3
+
+
Priority 4
+
+
Priority 5
+
2201100
+
+

Rules

+ + + + + + + + + + + + + + + + +
RuleViolationsSeverity
+ [Best Practices] GuardLogStatement1 +
2
+
+ [Best Practices] ForLoopCanBeForeach1 +
3
+
+
+

Files

+ + + + + + + + + + + + + + + + + + + + + + + + + +
File +
5
+
+
4
+
+
3
+
+
2
+
+
1
+
+ /home/pmd/source/pmd-core/src/main/java/net/sourceforge/pmd/RuleContext.java + 00010
+ /home/pmd/source/pmd-core/src/main/java/net/sourceforge/pmd/benchmark/Benchmarker.java + 00100
+
+
+

File /home/pmd/source/pmd-core/src/main/java/net/sourceforge/pmd/RuleContext.java

+ + + + + + + + + + + +
ViolationError DescriptionLine
+
2
+
+ [Best Practices.GuardLogStatement] + - + +Logger calls should be surrounded by log level guards. + + 124 - 125
+
Back to top + +

File /home/pmd/source/pmd-core/src/main/java/net/sourceforge/pmd/benchmark/Benchmarker.java

+ + + + + + + + + + + +
ViolationError DescriptionLine
+
3
+
+ [Best Practices.ForLoopCanBeForeach] + - + +This for loop can be replaced by a foreach loop + + 58 - 62
+
Back to top +
+ + \ No newline at end of file diff --git a/docs/report-examples/pmd-report-summaryhtml.html b/docs/report-examples/pmd-report-summaryhtml.html new file mode 100644 index 0000000000..92dc3e1f72 --- /dev/null +++ b/docs/report-examples/pmd-report-summaryhtml.html @@ -0,0 +1,74 @@ +PMD +

Summary

+ + + + +
Rule nameNumber of violations
GuardLogStatement1
ForLoopCanBeForeach1
+

Detail

+

PMD report

Problems found

+ + + + + + + + + + + + + +
#FileLineProblem
1pmd-core/src/main/java/net/sourceforge/pmd/RuleContext.java124Logger calls should be surrounded by log level guards.
2pmd-core/src/main/java/net/sourceforge/pmd/benchmark/Benchmarker.java58This for loop can be replaced by a foreach loop

Processing errors

+ + + + + +
FileProblem
pmd-core/src/test/resources/net/sourceforge/pmd/cpd/files/file_with_ISO-8859-1_encoding.java
net.sourceforge.pmd.PMDException: Error while parsing pmd-core/src/test/resources/net/sourceforge/pmd/cpd/files/file_with_ISO-8859-1_encoding.java
+    at net.sourceforge.pmd.SourceCodeProcessor.processSourceCodeWithoutCache(SourceCodeProcessor.java:110)
+    at net.sourceforge.pmd.SourceCodeProcessor.processSourceCode(SourceCodeProcessor.java:89)
+    at net.sourceforge.pmd.SourceCodeProcessor.processSourceCode(SourceCodeProcessor.java:51)
+    at net.sourceforge.pmd.processor.PmdRunnable.call(PmdRunnable.java:78)
+    at net.sourceforge.pmd.processor.PmdRunnable.call(PmdRunnable.java:1)
+    at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)
+    at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:515)
+    at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)
+    at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
+    at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
+    at java.base/java.lang.Thread.run(Thread.java:834)
+Caused by: net.sourceforge.pmd.lang.java.ast.ParseException: Encountered " "-" "- "" at line 6, column 30.
+Was expecting one of:
+    "extends" ...
+    "implements" ...
+    "{" ...
+    "<" ...
+    
+    at net.sourceforge.pmd.lang.java.ast.JavaParser.generateParseException(JavaParser.java:12731)
+    at net.sourceforge.pmd.lang.java.ast.JavaParser.jj_consume_token(JavaParser.java:12615)
+    at net.sourceforge.pmd.lang.java.ast.JavaParser.ClassOrInterfaceBody(JavaParser.java:1574)
+    at net.sourceforge.pmd.lang.java.ast.JavaParser.ClassOrInterfaceDeclaration(JavaParser.java:779)
+    at net.sourceforge.pmd.lang.java.ast.JavaParser.TypeDeclaration(JavaParser.java:686)
+    at net.sourceforge.pmd.lang.java.ast.JavaParser.CompilationUnit(JavaParser.java:420)
+    at net.sourceforge.pmd.lang.java.AbstractJavaParser.parse(AbstractJavaParser.java:62)
+    at net.sourceforge.pmd.SourceCodeProcessor.parse(SourceCodeProcessor.java:121)
+    at net.sourceforge.pmd.SourceCodeProcessor.processSource(SourceCodeProcessor.java:185)
+    at net.sourceforge.pmd.SourceCodeProcessor.processSourceCodeWithoutCache(SourceCodeProcessor.java:107)
+    ... 10 more
+

Suppressed warnings

+ + + + + + + + +
FileLineRuleNOPMD or AnnotationReason
pmd-core/src/main/java/net/sourceforge/pmd/PMD.java505CloseResourceAnnotation

Configuration errors

+ + + + + +
RuleProblem
LoosePackageCouplingNo packages or classes specified
\ No newline at end of file diff --git a/docs/report-examples/pmd-report-vbhtml.html b/docs/report-examples/pmd-report-vbhtml.html new file mode 100644 index 0000000000..f6ce69c610 --- /dev/null +++ b/docs/report-examples/pmd-report-vbhtml.html @@ -0,0 +1,42 @@ +PMD
+ +
 /home/pmd/source/pmd-core/src/main/java/net/sourceforge/pmd/RuleContext.java
124   Logger calls should be surrounded by log level guards.
+ +
 /home/pmd/source/pmd-core/src/main/java/net/sourceforge/pmd/benchmark/Benchmarker.java
58   This for loop can be replaced by a foreach loop

 Problems found
/home/pmd/source/pmd-core/src/test/resources/net/sourceforge/pmd/cpd/files/file_with_ISO-8859-1_encoding.java
net.sourceforge.pmd.PMDException: Error while parsing /home/pmd/source/pmd-core/src/test/resources/net/sourceforge/pmd/cpd/files/file_with_ISO-8859-1_encoding.java
+    at net.sourceforge.pmd.SourceCodeProcessor.processSourceCodeWithoutCache(SourceCodeProcessor.java:110)
+    at net.sourceforge.pmd.SourceCodeProcessor.processSourceCode(SourceCodeProcessor.java:89)
+    at net.sourceforge.pmd.SourceCodeProcessor.processSourceCode(SourceCodeProcessor.java:51)
+    at net.sourceforge.pmd.processor.PmdRunnable.call(PmdRunnable.java:78)
+    at net.sourceforge.pmd.processor.PmdRunnable.call(PmdRunnable.java:24)
+    at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)
+    at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:515)
+    at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)
+    at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1130)
+    at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:630)
+    at java.base/java.lang.Thread.run(Thread.java:832)
+Caused by: net.sourceforge.pmd.lang.java.ast.ParseException: Encountered " "-" "- "" at line 6, column 30.
+Was expecting one of:
+    "extends" ...
+    "implements" ...
+    "{" ...
+    "<" ...
+    
+    at net.sourceforge.pmd.lang.java.ast.JavaParser.generateParseException(JavaParser.java:12713)
+    at net.sourceforge.pmd.lang.java.ast.JavaParser.jj_consume_token(JavaParser.java:12597)
+    at net.sourceforge.pmd.lang.java.ast.JavaParser.ClassOrInterfaceBody(JavaParser.java:1554)
+    at net.sourceforge.pmd.lang.java.ast.JavaParser.ClassOrInterfaceDeclaration(JavaParser.java:732)
+    at net.sourceforge.pmd.lang.java.ast.JavaParser.TypeDeclaration(JavaParser.java:639)
+    at net.sourceforge.pmd.lang.java.ast.JavaParser.CompilationUnit(JavaParser.java:373)
+    at net.sourceforge.pmd.lang.java.AbstractJavaParser.parse(AbstractJavaParser.java:62)
+    at net.sourceforge.pmd.SourceCodeProcessor.parse(SourceCodeProcessor.java:121)
+    at net.sourceforge.pmd.SourceCodeProcessor.processSource(SourceCodeProcessor.java:185)
+    at net.sourceforge.pmd.SourceCodeProcessor.processSourceCodeWithoutCache(SourceCodeProcessor.java:107)
+    ... 10 more
+
 Configuration problems found
LoosePackageCouplingNo packages or classes specified
diff --git a/docs/report-examples/pmd-report-yahtml/Benchmarker.html b/docs/report-examples/pmd-report-yahtml/Benchmarker.html new file mode 100644 index 0000000000..2e0da807d1 --- /dev/null +++ b/docs/report-examples/pmd-report-yahtml/Benchmarker.html @@ -0,0 +1,15 @@ + + + + + PMD - Benchmarker + + +

Class View

+

Class: Benchmarker

+ + + +
MethodViolation
findBooleanSwitch
Rule:ForLoopCanBeForeach
Description:This for loop can be replaced by a foreach loop
Line:58 and 62
+ + diff --git a/docs/report-examples/pmd-report-yahtml/RuleContext.html b/docs/report-examples/pmd-report-yahtml/RuleContext.html new file mode 100644 index 0000000000..1f401103f4 --- /dev/null +++ b/docs/report-examples/pmd-report-yahtml/RuleContext.html @@ -0,0 +1,15 @@ + + + + + PMD - RuleContext + + +

Class View

+

Class: RuleContext

+ + + +
MethodViolation
setSourceCodeFilename
Rule:GuardLogStatement
Description:Logger calls should be surrounded by log level guards.
Line:124 and 125
+ + diff --git a/docs/report-examples/pmd-report-yahtml/index.html b/docs/report-examples/pmd-report-yahtml/index.html new file mode 100644 index 0000000000..c8a696b998 --- /dev/null +++ b/docs/report-examples/pmd-report-yahtml/index.html @@ -0,0 +1,20 @@ + + + + + PMD + + +

Package View

+ + + + + + + + + +
PackageClass#
Aggregate - 2
net - 2
net.sourceforge - 2
net.sourceforge.pmd - 2
net.sourceforge.pmd RuleContext 1
net.sourceforge.pmd.benchmark - 1
net.sourceforge.pmd.benchmark Benchmarker 1
+ + diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ApexHandler.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ApexHandler.java index 561ec95e63..00bfdff2d9 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ApexHandler.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ApexHandler.java @@ -12,7 +12,6 @@ import net.sourceforge.pmd.lang.AbstractLanguageVersionHandler; import net.sourceforge.pmd.lang.Parser; import net.sourceforge.pmd.lang.ParserOptions; import net.sourceforge.pmd.lang.VisitorStarter; -import net.sourceforge.pmd.lang.XPathHandler; import net.sourceforge.pmd.lang.apex.ast.ASTMethod; import net.sourceforge.pmd.lang.apex.ast.ASTUserClassOrInterface; import net.sourceforge.pmd.lang.apex.ast.ApexNode; @@ -22,7 +21,6 @@ import net.sourceforge.pmd.lang.apex.metrics.api.ApexClassMetricKey; import net.sourceforge.pmd.lang.apex.metrics.api.ApexOperationMetricKey; import net.sourceforge.pmd.lang.apex.multifile.ApexMultifileVisitorFacade; import net.sourceforge.pmd.lang.apex.rule.ApexRuleViolationFactory; -import net.sourceforge.pmd.lang.ast.xpath.DefaultASTXPathHandler; import net.sourceforge.pmd.lang.metrics.LanguageMetricsProvider; import net.sourceforge.pmd.lang.metrics.internal.AbstractLanguageMetricsProvider; import net.sourceforge.pmd.lang.rule.RuleViolationFactory; @@ -38,12 +36,6 @@ public class ApexHandler extends AbstractLanguageVersionHandler { return rootNode -> new ApexMultifileVisitorFacade().initializeWith((ApexNode) rootNode); } - - @Override - public XPathHandler getXPathHandler() { - return new DefaultASTXPathHandler(); - } - @Override public RuleViolationFactory getRuleViolationFactory() { return ApexRuleViolationFactory.INSTANCE; diff --git a/pmd-apex/src/main/resources/category/apex/codestyle.xml b/pmd-apex/src/main/resources/category/apex/codestyle.xml index f04a7580f8..0c0ea51a4a 100644 --- a/pmd-apex/src/main/resources/category/apex/codestyle.xml +++ b/pmd-apex/src/main/resources/category/apex/codestyle.xml @@ -45,12 +45,13 @@ from the rest. 3 + 0] +//IfBlockStatement/BlockStatement[@CurlyBrace= false()][count(child::*) > 0] | -//IfElseBlockStatement/BlockStatement[@CurlyBrace='false'][count(child::*) > 0] +//IfElseBlockStatement/BlockStatement[@CurlyBrace= false()][count(child::*) > 0] ]]> @@ -82,10 +83,11 @@ controlled from the rest. 3 + @@ -163,12 +165,13 @@ from the rest. 3 + @@ -272,6 +275,7 @@ can lead to quite messy code. This rule looks for several declarations on the sa 1 + 3 + diff --git a/pmd-apex/src/main/resources/category/apex/errorprone.xml b/pmd-apex/src/main/resources/category/apex/errorprone.xml index fb4b39c19b..ecea64c472 100644 --- a/pmd-apex/src/main/resources/category/apex/errorprone.xml +++ b/pmd-apex/src/main/resources/category/apex/errorprone.xml @@ -59,6 +59,7 @@ Avoid directly accessing Trigger.old and Trigger.new as it can lead to a bug. Tr 3 + 3 + 3 + 3 + @@ -221,6 +225,7 @@ Avoid empty try or finally blocks - what's the point? 3 + 3 + 0) { // We are done, we found the end of the string... done = true; - } else if (tok == '\\') { // Found an escaped char - escaped = true; - } else { // Adding char... - escaped = false; + } else { + // Found an escaped char? + escaped = tok == '\\'; } // Adding char to String:" + token.toString()); token.append(tok); loc++; } // Handling multiple lines string - if (!done && // ... we didn't find the end of the string - loc >= currentLine.length() && // ... we have reach the end of - // the line ( the String is - // incomplete, for the moment at - // least) - spanMultipleLinesString && // ... the language allow multiple - // line span Strings - lineNumber < code.size() - 1 // ... there is still more lines to - // parse + if (!done // ... we didn't find the end of the string (but the end of the line) + && spanMultipleLinesString // ... the language allow multiple line span Strings + && lineNumber < code.size() - 1 // ... there is still more lines to parse ) { // removes last character, if it is the line continuation (e.g. // backslash) character - if (spanMultipleLinesLineContinuationCharacter != null && token.length() > 0 - && token.charAt(token.length() - 1) == spanMultipleLinesLineContinuationCharacter.charValue()) { + if (spanMultipleLinesLineContinuationCharacter != null + && token.length() > 0 + && token.charAt(token.length() - 1) == spanMultipleLinesLineContinuationCharacter) { token.deleteCharAt(token.length() - 1); } // parsing new line diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/lang/AbstractLanguageVersionHandler.java b/pmd-core/src/main/java/net/sourceforge/pmd/lang/AbstractLanguageVersionHandler.java index 9fb7fb820f..634e00fe24 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/lang/AbstractLanguageVersionHandler.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/lang/AbstractLanguageVersionHandler.java @@ -18,6 +18,7 @@ import net.sourceforge.pmd.util.designerbindings.DesignerBindings; */ public abstract class AbstractLanguageVersionHandler implements LanguageVersionHandler { + @Override public DataFlowHandler getDataFlowHandler() { return DataFlowHandler.DUMMY; diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/lang/XPathHandler.java b/pmd-core/src/main/java/net/sourceforge/pmd/lang/XPathHandler.java index 95728185d7..ebdf801d80 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/lang/XPathHandler.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/lang/XPathHandler.java @@ -7,6 +7,7 @@ package net.sourceforge.pmd.lang; import org.jaxen.Navigator; import net.sourceforge.pmd.annotation.InternalApi; +import net.sourceforge.pmd.lang.ast.xpath.DefaultASTXPathHandler; import net.sourceforge.pmd.lang.xpath.Initializer; import net.sf.saxon.sxpath.IndependentContext; @@ -19,22 +20,7 @@ import net.sf.saxon.sxpath.IndependentContext; @Deprecated public interface XPathHandler { - XPathHandler DUMMY = new XPathHandler() { - @Override - public void initialize() { - // empty handler - does nothing - } - - @Override - public void initialize(IndependentContext context) { - // empty handler - does nothing - } - - @Override - public Navigator getNavigator() { - return null; - } - }; + XPathHandler DUMMY = new DefaultASTXPathHandler(); /** * Initialize. This is intended to be called by {@link Initializer} to @@ -52,7 +38,8 @@ public interface XPathHandler { * Get a Jaxen Navigator for this Language. May return null if * there is no Jaxen Navigation for this language. * - * @deprecated Support for Jaxen will be removed come 7.0.0 + * @deprecated Support for Jaxen will be removed come 7.0.0. This isn't used + * anymore */ @Deprecated Navigator getNavigator(); diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/AbstractNode.java b/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/AbstractNode.java index c14ede1d12..2f8d3eb78c 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/AbstractNode.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/AbstractNode.java @@ -25,6 +25,8 @@ import net.sourceforge.pmd.annotation.InternalApi; import net.sourceforge.pmd.lang.ast.xpath.Attribute; import net.sourceforge.pmd.lang.ast.xpath.AttributeAxisIterator; import net.sourceforge.pmd.lang.ast.xpath.DocumentNavigator; +import net.sourceforge.pmd.lang.ast.xpath.internal.ContextualizedNavigator; +import net.sourceforge.pmd.lang.ast.xpath.internal.DeprecatedAttrLogger; import net.sourceforge.pmd.lang.dfa.DataFlowNode; import net.sourceforge.pmd.util.DataMap; import net.sourceforge.pmd.util.DataMap.DataKey; @@ -513,7 +515,8 @@ public abstract class AbstractNode implements Node { @Override @SuppressWarnings("unchecked") public List findChildNodesWithXPath(final String xpathString) throws JaxenException { - return new BaseXPath(xpathString, new DocumentNavigator()).selectNodes(this); + return new BaseXPath(xpathString, new ContextualizedNavigator(DeprecatedAttrLogger.createAdHocLogger())) + .selectNodes(this); } @Override diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/xpath/AbstractASTXPathHandler.java b/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/xpath/AbstractASTXPathHandler.java index f4c1e8ddbe..eb470c94bd 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/xpath/AbstractASTXPathHandler.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/xpath/AbstractASTXPathHandler.java @@ -25,4 +25,14 @@ public abstract class AbstractASTXPathHandler implements XPathHandler { public void initialize(IndependentContext context, Language language, Class functionsClass) { context.declareNamespace("pmd-" + language.getTerseName(), "java:" + functionsClass.getName()); } + + @Override + public void initialize() { + // override if needed + } + + @Override + public void initialize(IndependentContext context) { + // override if needed + } } diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/xpath/Attribute.java b/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/xpath/Attribute.java index cee0734a9c..7f4573ce97 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/xpath/Attribute.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/xpath/Attribute.java @@ -9,12 +9,9 @@ import java.lang.reflect.Method; import java.util.Collections; import java.util.List; import java.util.Objects; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentMap; -import java.util.logging.Level; -import java.util.logging.Logger; import net.sourceforge.pmd.annotation.Experimental; +import net.sourceforge.pmd.annotation.InternalApi; import net.sourceforge.pmd.lang.ast.Node; import net.sourceforge.pmd.lang.ast.xpath.internal.DeprecatedAttribute; @@ -29,11 +26,6 @@ import net.sourceforge.pmd.lang.ast.xpath.internal.DeprecatedAttribute; * @author daniels */ public class Attribute { - - - private static final Logger LOG = Logger.getLogger(Attribute.class.getName()); - static final ConcurrentMap DETECTED_DEPRECATED_ATTRIBUTES = new ConcurrentHashMap<>(); - private static final Object[] EMPTY_OBJ_ARRAY = new Object[0]; private final Node parent; @@ -73,9 +65,22 @@ public class Attribute { return method == null ? String.class : method.getReturnType(); } - private boolean isAttributeDeprecated() { - return method != null && (method.isAnnotationPresent(Deprecated.class) - || method.isAnnotationPresent(DeprecatedAttribute.class)); + /** + * Returns null for "not deprecated", empty string for "deprecated without replacement", + * otherwise name of replacement attribute. + */ + @InternalApi + public String replacementIfDeprecated() { + if (method == null) { + return null; + } else { + DeprecatedAttribute annot = method.getAnnotation(DeprecatedAttribute.class); + return annot != null + ? annot.replaceWith() + : method.isAnnotationPresent(Deprecated.class) + ? DeprecatedAttribute.NO_REPLACEMENT + : null; + } } public Object getValue() { @@ -83,12 +88,6 @@ public class Attribute { return value.get(0); } - if (LOG.isLoggable(Level.WARNING) && isAttributeDeprecated() - && DETECTED_DEPRECATED_ATTRIBUTES.putIfAbsent(getLoggableAttributeName(), Boolean.TRUE) == null) { - // this message needs to be kept in sync with PMDCoverageTest / BinaryDistributionIT - LOG.warning("Use of deprecated attribute '" + getLoggableAttributeName() + "' in XPath query"); - } - // this lazy loading reduces calls to Method.invoke() by about 90% try { value = Collections.singletonList(method.invoke(parent, EMPTY_OBJ_ARRAY)); @@ -129,11 +128,6 @@ public class Attribute { return Objects.hash(parent, name); } - - private String getLoggableAttributeName() { - return parent.getXPathNodeName() + "/@" + name; - } - @Override public String toString() { return name + ':' + getValue() + ':' + parent.getXPathNodeName(); diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/xpath/DefaultASTXPathHandler.java b/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/xpath/DefaultASTXPathHandler.java index 974d426e80..3fa844f28a 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/xpath/DefaultASTXPathHandler.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/xpath/DefaultASTXPathHandler.java @@ -12,6 +12,7 @@ import net.sf.saxon.sxpath.IndependentContext; @Deprecated @InternalApi public class DefaultASTXPathHandler extends AbstractASTXPathHandler { + @Override public void initialize() { // override if needed diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/xpath/internal/ContextualizedNavigator.java b/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/xpath/internal/ContextualizedNavigator.java new file mode 100644 index 0000000000..358d7d7ce7 --- /dev/null +++ b/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/xpath/internal/ContextualizedNavigator.java @@ -0,0 +1,27 @@ +/* + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.ast.xpath.internal; + +import net.sourceforge.pmd.lang.ast.xpath.Attribute; +import net.sourceforge.pmd.lang.ast.xpath.DocumentNavigator; + +/** + * Navigator that records attribute usages. + */ +public class ContextualizedNavigator extends DocumentNavigator { + + private final DeprecatedAttrLogger ctx; + + public ContextualizedNavigator(DeprecatedAttrLogger ctx) { + this.ctx = ctx; + } + + @Override + public String getAttributeStringValue(Object arg0) { + Attribute attr = (Attribute) arg0; + ctx.recordUsageOf(attr); + return attr.getStringValue(); + } +} diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/xpath/internal/DeprecatedAttrLogger.java b/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/xpath/internal/DeprecatedAttrLogger.java new file mode 100644 index 0000000000..e6c2358a06 --- /dev/null +++ b/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/xpath/internal/DeprecatedAttrLogger.java @@ -0,0 +1,117 @@ +/* + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.ast.xpath.internal; + +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.logging.Level; +import java.util.logging.Logger; + +import net.sourceforge.pmd.lang.ast.xpath.Attribute; +import net.sourceforge.pmd.lang.rule.XPathRule; + +/** + * Records usages of deprecated attributes in XPath rules. This needs + * to be threadsafe, XPath rules have one each (and share it). + */ +public abstract class DeprecatedAttrLogger { + + private static final Logger LOG = Logger.getLogger(Attribute.class.getName()); + + + public abstract void recordUsageOf(Attribute attribute); + + /** + * Create a new context for the given rule, returns a noop implementation + * if the warnings would be ignored anyway. + */ + public static DeprecatedAttrLogger create(XPathRule rule) { + if (LOG.isLoggable(Level.WARNING)) { + return new AttrLoggerImpl(rule); + } else { + return noop(); + } + } + + public static DeprecatedAttrLogger createAdHocLogger() { + if (LOG.isLoggable(Level.WARNING)) { + return new AdhocLoggerImpl(); + } else { + return noop(); + } + } + + public static Noop noop() { + return Noop.INSTANCE; + } + + private static String getLoggableAttributeName(Attribute attr) { + return attr.getParent().getXPathNodeName() + "/@" + attr.getName(); + } + + private static class Noop extends DeprecatedAttrLogger { + + static final Noop INSTANCE = new Noop(); + + @Override + public void recordUsageOf(Attribute attribute) { + // do nothing + } + } + + private static class AttrLoggerImpl extends DeprecatedAttrLogger { + + private final ConcurrentMap deprecated = new ConcurrentHashMap<>(); + private final XPathRule rule; + + private AttrLoggerImpl(XPathRule rule) { + this.rule = rule; + } + + @Override + public void recordUsageOf(Attribute attribute) { + String replacement = attribute.replacementIfDeprecated(); + if (replacement != null) { + String name = getLoggableAttributeName(attribute); + Boolean b = deprecated.putIfAbsent(name, Boolean.TRUE); + if (b == null) { + // this message needs to be kept in sync with PMDCoverageTest / BinaryDistributionIT + String msg = "Use of deprecated attribute '" + name + "' by XPath rule " + ruleToString(); + if (!replacement.isEmpty()) { + msg += ", please use " + replacement + " instead"; + } + LOG.warning(msg); + } + } + } + + public String ruleToString() { + // we can't compute that beforehand because the name is set + // outside of the rule constructor + String name = "'" + rule.getName() + "'"; + if (rule.getRuleSetName() != null) { + name += " (in ruleset '" + rule.getRuleSetName() + "')"; + } + return name; + } + } + + private static class AdhocLoggerImpl extends DeprecatedAttrLogger { + @Override + public void recordUsageOf(Attribute attribute) { + String replacement = attribute.replacementIfDeprecated(); + if (replacement != null) { + String name = getLoggableAttributeName(attribute); + // this message needs to be kept in sync with PMDCoverageTest / BinaryDistributionIT + String msg = "Use of deprecated attribute '" + name + "' in a findChildNodesWithXPath navigation"; + if (!replacement.isEmpty()) { + msg += ", please use " + replacement + " instead"; + } + // log with execption stack trace to help figure out where exactly the xpath is used. + LOG.log(Level.WARNING, msg, new RuntimeException(msg)); + } + } + } +} diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/xpath/internal/DeprecatedAttribute.java b/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/xpath/internal/DeprecatedAttribute.java index 75e80d4f5f..430cf5dd73 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/xpath/internal/DeprecatedAttribute.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/xpath/internal/DeprecatedAttribute.java @@ -22,4 +22,13 @@ import java.lang.annotation.Target; @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface DeprecatedAttribute { + + String NO_REPLACEMENT = ""; + + + /** + * The simple name of the attribute to use for replacement (with '@' prefix). + * If empty, then the attribute is deprecated for removal. + */ + String replaceWith() default NO_REPLACEMENT; } diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/xpath/saxon/AbstractNodeInfo.java b/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/xpath/saxon/AbstractNodeInfo.java index 1e7c8e0825..822bb9d797 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/xpath/saxon/AbstractNodeInfo.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/xpath/saxon/AbstractNodeInfo.java @@ -260,11 +260,11 @@ public class AbstractNodeInfo implements VirtualNode, SiblingCountingNode { */ @Override public AxisIterator iterateAxis(byte axisNumber, NodeTest nodeTest) { - AxisIterator axisIterator = iterateAxis(axisNumber); - if (nodeTest != null) { - axisIterator = new AxisFilter(axisIterator, nodeTest); - } - return axisIterator; + return filter(iterateAxis(axisNumber), nodeTest); + } + + protected static AxisIterator filter(AxisIterator axisIterator, NodeTest nodeTest) { + return nodeTest != null ? new AxisFilter(axisIterator, nodeTest) : axisIterator; } /** diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/xpath/saxon/AttributeAxisIterator.java b/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/xpath/saxon/AttributeAxisIterator.java index 9a0e23baf1..d22adb33ab 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/xpath/saxon/AttributeAxisIterator.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/xpath/saxon/AttributeAxisIterator.java @@ -42,7 +42,7 @@ public class AttributeAxisIterator extends Navigator.BaseEnumeration { public void advance() { if (this.iterator.hasNext()) { Attribute attribute = this.iterator.next(); - super.current = new AttributeNode(attribute, super.position()); + super.current = new AttributeNode(startNodeInfo, attribute, super.position()); } else { super.current = null; } diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/xpath/saxon/AttributeNode.java b/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/xpath/saxon/AttributeNode.java index 528b4fc578..7a919967ef 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/xpath/saxon/AttributeNode.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/xpath/saxon/AttributeNode.java @@ -8,6 +8,7 @@ import java.util.List; import net.sourceforge.pmd.annotation.InternalApi; import net.sourceforge.pmd.lang.ast.xpath.Attribute; +import net.sourceforge.pmd.lang.ast.xpath.internal.DeprecatedAttrLogger; import net.sourceforge.pmd.lang.rule.xpath.SaxonXPathRuleQuery; import net.sf.saxon.om.NodeInfo; @@ -23,7 +24,8 @@ import net.sf.saxon.value.Value; */ @Deprecated @InternalApi -public class AttributeNode extends AbstractNodeInfo { +public class AttributeNode extends BaseNodeInfo { + protected final Attribute attribute; protected final int id; protected Value value; @@ -32,30 +34,28 @@ public class AttributeNode extends AbstractNodeInfo { /** * Creates a new AttributeNode from a PMD Attribute. * - * @param id The index within the attribute order + * @param parent Parent elemtn + * @param id The index within the attribute order */ - public AttributeNode(Attribute attribute, int id) { + public AttributeNode(ElementNode parent, Attribute attribute, int id) { + super(Type.ATTRIBUTE, parent.getNamePool(), attribute.getName(), parent); this.attribute = attribute; this.id = id; } - @Override - public int getNodeKind() { - return Type.ATTRIBUTE; - } - @Override public String getLocalPart() { return attribute.getName(); } - @Override - public String getURI() { - return ""; + private DeprecatedAttrLogger getAttrCtx() { + return parent == null ? DeprecatedAttrLogger.noop() + : parent.document.getAttrCtx(); } @Override public Value atomize() { + getAttrCtx().recordUsageOf(attribute); if (value == null) { Object data = attribute.getValue(); if (data instanceof List) { @@ -79,6 +79,7 @@ public class AttributeNode extends AbstractNodeInfo { @Override public int compareOrder(NodeInfo other) { + return Integer.signum(this.id - ((AttributeNode) other).id); } } diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/xpath/saxon/BaseNodeInfo.java b/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/xpath/saxon/BaseNodeInfo.java new file mode 100644 index 0000000000..ea6e4477ac --- /dev/null +++ b/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/xpath/saxon/BaseNodeInfo.java @@ -0,0 +1,76 @@ +/* + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.ast.xpath.saxon; + + +import net.sf.saxon.om.FingerprintedNode; +import net.sf.saxon.om.NamePool; +import net.sf.saxon.om.NodeInfo; +import net.sf.saxon.om.SiblingCountingNode; +import net.sf.saxon.om.VirtualNode; + +abstract class BaseNodeInfo extends AbstractNodeInfo implements VirtualNode, SiblingCountingNode, FingerprintedNode { + + // It's important that all our NodeInfo implementations share the + // same getNodeKind implementation, otherwise NameTest spends a lot + // of time in virtual dispatch + private final int nodeKind; + private final NamePool namePool; + private final int fingerprint; + + protected final ElementNode parent; + + BaseNodeInfo(int nodeKind, NamePool namePool, String localName, ElementNode parent) { + this.nodeKind = nodeKind; + this.namePool = namePool; + this.fingerprint = namePool.allocate("", "", localName) & NamePool.FP_MASK; + this.parent = parent; + } + + @Override + public final String getURI() { + return ""; + } + + @Override + public final String getBaseURI() { + return ""; + } + + @Override + public String getPrefix() { + return ""; + } + + @Override + public final NodeInfo getParent() { + return parent; + } + + @Override + public int getNameCode() { + // note: the nameCode is only equal to the fingerprint because + // this implementation does not use namespace prefixes + // if we change that (eg for embedded language support) then + // we'll need to worry about this. See NamePool.FP_MASK + return fingerprint; + } + + @Override + public final int getFingerprint() { + return fingerprint; + } + + @Override + public final NamePool getNamePool() { + return namePool; + } + + @Override + public final int getNodeKind() { + return nodeKind; + } + +} diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/xpath/saxon/DocumentNode.java b/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/xpath/saxon/DocumentNode.java index f91718c816..d91f1828a6 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/xpath/saxon/DocumentNode.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/xpath/saxon/DocumentNode.java @@ -10,10 +10,13 @@ import java.util.Map; import net.sourceforge.pmd.annotation.InternalApi; import net.sourceforge.pmd.lang.ast.Node; +import net.sourceforge.pmd.lang.ast.xpath.internal.DeprecatedAttrLogger; +import net.sourceforge.pmd.lang.rule.xpath.SaxonXPathRuleQuery; import net.sf.saxon.om.Axis; import net.sf.saxon.om.AxisIterator; import net.sf.saxon.om.DocumentInfo; +import net.sf.saxon.om.NamePool; import net.sf.saxon.om.Navigator; import net.sf.saxon.om.NodeInfo; import net.sf.saxon.om.SingleNodeIterator; @@ -24,7 +27,7 @@ import net.sf.saxon.type.Type; */ @Deprecated @InternalApi -public class DocumentNode extends AbstractNodeInfo implements DocumentInfo { +public class DocumentNode extends BaseNodeInfo implements DocumentInfo { /** * The root ElementNode of the DocumentNode. @@ -36,17 +39,25 @@ public class DocumentNode extends AbstractNodeInfo implements DocumentInfo { */ public final Map nodeToElementNode = new HashMap<>(); + private DeprecatedAttrLogger attrCtx; + /** * Construct a DocumentNode, with the given AST Node serving as the root * ElementNode. * - * @param node - * The root AST Node. + * @param node The root AST Node. + * @param namePool Pool to share names * * @see ElementNode */ + public DocumentNode(Node node, NamePool namePool) { + super(Type.DOCUMENT, namePool, "", null); + this.rootNode = new ElementNode(this, new IdGenerator(), null, node, -1, namePool); + } + + @Deprecated public DocumentNode(Node node) { - this.rootNode = new ElementNode(this, new IdGenerator(), null, node, -1); + this(node, SaxonXPathRuleQuery.getNamePool()); } @Override @@ -64,11 +75,6 @@ public class DocumentNode extends AbstractNodeInfo implements DocumentInfo { throw createUnsupportedOperationException("DocumentInfo.selectID(String)"); } - @Override - public int getNodeKind() { - return Type.DOCUMENT; - } - @Override public DocumentInfo getDocumentRoot() { return this; @@ -92,4 +98,12 @@ public class DocumentNode extends AbstractNodeInfo implements DocumentInfo { return super.iterateAxis(axisNumber); } } + + public DeprecatedAttrLogger getAttrCtx() { + return attrCtx == null ? DeprecatedAttrLogger.noop() : attrCtx; + } + + public void setAttrCtx(DeprecatedAttrLogger attrCtx) { + this.attrCtx = attrCtx; + } } diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/xpath/saxon/ElementNode.java b/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/xpath/saxon/ElementNode.java index 4663e7f1bf..6db157eac4 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/xpath/saxon/ElementNode.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/xpath/saxon/ElementNode.java @@ -4,25 +4,41 @@ package net.sourceforge.pmd.lang.ast.xpath.saxon; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; + import net.sourceforge.pmd.annotation.InternalApi; import net.sourceforge.pmd.lang.ast.Node; +import net.sourceforge.pmd.lang.ast.xpath.Attribute; +import net.sourceforge.pmd.lang.rule.xpath.SaxonXPathRuleQuery; import net.sf.saxon.om.Axis; import net.sf.saxon.om.AxisIterator; import net.sf.saxon.om.DocumentInfo; import net.sf.saxon.om.EmptyIterator; +import net.sf.saxon.om.NamePool; import net.sf.saxon.om.Navigator; +import net.sf.saxon.om.Navigator.BaseEnumeration; import net.sf.saxon.om.NodeArrayIterator; import net.sf.saxon.om.NodeInfo; +import net.sf.saxon.om.SequenceIterator; import net.sf.saxon.om.SingleNodeIterator; +import net.sf.saxon.om.SingletonIterator; +import net.sf.saxon.pattern.NameTest; +import net.sf.saxon.pattern.NodeTest; import net.sf.saxon.type.Type; +import net.sf.saxon.value.AtomicValue; +import net.sf.saxon.value.StringValue; +import net.sf.saxon.value.UntypedAtomicValue; +import net.sf.saxon.value.Value; /** * A Saxon OM Element type node for an AST Node. */ @Deprecated @InternalApi -public class ElementNode extends AbstractNodeInfo { +public class ElementNode extends BaseNodeInfo { protected final DocumentNode document; protected final ElementNode parent; @@ -31,17 +47,31 @@ public class ElementNode extends AbstractNodeInfo { protected final int siblingPosition; protected final NodeInfo[] children; - public ElementNode(DocumentNode document, IdGenerator idGenerator, ElementNode parent, Node node, - int siblingPosition) { + private Map attributes; + + @Deprecated + public ElementNode(DocumentNode document, IdGenerator idGenerator, ElementNode parent, Node node, int siblingPosition) { + this(document, idGenerator, parent, node, siblingPosition, SaxonXPathRuleQuery.getNamePool()); + } + + public ElementNode(DocumentNode document, + IdGenerator idGenerator, + ElementNode parent, + Node node, + int siblingPosition, + NamePool namePool) { + super(Type.ELEMENT, namePool, node.getXPathNodeName(), parent); + this.document = document; this.parent = parent; this.node = node; this.id = idGenerator.getNextId(); this.siblingPosition = siblingPosition; + if (node.getNumChildren() > 0) { this.children = new NodeInfo[node.getNumChildren()]; for (int i = 0; i < children.length; i++) { - children[i] = new ElementNode(document, idGenerator, this, node.getChild(i), i); + children[i] = new ElementNode(document, idGenerator, this, node.getChild(i), i, namePool); } } else { this.children = null; @@ -49,6 +79,20 @@ public class ElementNode extends AbstractNodeInfo { document.nodeToElementNode.put(node, this); } + private Map getAttributes() { + if (attributes == null) { + attributes = new HashMap<>(); + Iterator iter = node.getXPathAttributesIterator(); + int idx = 0; + while (iter.hasNext()) { + Attribute next = iter.next(); + AttributeNode attrNode = new AttributeNode(this, next, idx++); + attributes.put(attrNode.getFingerprint(), attrNode); + } + } + return attributes; + } + @Override public Object getUnderlyingNode() { return node; @@ -74,11 +118,6 @@ public class ElementNode extends AbstractNodeInfo { return children != null; } - @Override - public int getNodeKind() { - return Type.ELEMENT; - } - @Override public DocumentInfo getDocumentRoot() { return document; @@ -89,14 +128,26 @@ public class ElementNode extends AbstractNodeInfo { return node.getXPathNodeName(); } + @Override - public String getURI() { - return ""; + public SequenceIterator getTypedValue() { + return SingletonIterator.makeIterator((AtomicValue) atomize()); } @Override - public NodeInfo getParent() { - return parent; + public Value atomize() { + switch (getNodeKind()) { + case Type.COMMENT: + case Type.PROCESSING_INSTRUCTION: + return new StringValue(getStringValueCS()); + default: + return new UntypedAtomicValue(getStringValueCS()); + } + } + + @Override + public CharSequence getStringValueCS() { + return ""; } @Override @@ -122,16 +173,40 @@ public class ElementNode extends AbstractNodeInfo { return result; } + + @Override + public String getDisplayName() { + return getLocalPart(); + } + + + @Override + public AxisIterator iterateAxis(byte axisNumber, NodeTest nodeTest) { + if (axisNumber == Axis.ATTRIBUTE) { + if (nodeTest instanceof NameTest) { + if ((nodeTest.getNodeKindMask() & (1 << Type.ATTRIBUTE)) == 0) { + return EmptyIterator.getInstance(); + } else { + int fp = nodeTest.getFingerprint(); + if (fp != -1) { + return SingleNodeIterator.makeIterator(getAttributes().get(fp)); + } + } + } + } + return super.iterateAxis(axisNumber, nodeTest); + } + @SuppressWarnings("PMD.MissingBreakInSwitch") @Override - public AxisIterator iterateAxis(byte axisNumber) { + public AxisIterator iterateAxis(final byte axisNumber) { switch (axisNumber) { case Axis.ANCESTOR: return new Navigator.AncestorEnumeration(this, false); case Axis.ANCESTOR_OR_SELF: return new Navigator.AncestorEnumeration(this, true); case Axis.ATTRIBUTE: - return new AttributeAxisIterator(this); + return new AttributeEnumeration(); case Axis.CHILD: if (children == null) { return EmptyIterator.getInstance(); @@ -171,4 +246,22 @@ public class ElementNode extends AbstractNodeInfo { } } + private class AttributeEnumeration extends BaseEnumeration { + + private final Iterator iter = getAttributes().values().iterator(); + + @Override + public void advance() { + if (iter.hasNext()) { + current = iter.next(); + } else { + current = null; + } + } + + @Override + public SequenceIterator getAnother() { + return new AttributeEnumeration(); + } + } } diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/lang/rule/ImmutableLanguage.java b/pmd-core/src/main/java/net/sourceforge/pmd/lang/rule/ImmutableLanguage.java index b71e01d24d..73aeeb4265 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/lang/rule/ImmutableLanguage.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/lang/rule/ImmutableLanguage.java @@ -8,6 +8,10 @@ package net.sourceforge.pmd.lang.rule; * This is a tag interface to indicate that a Rule implementation class does not * support changes to it's Language. The Language is integral to the proper * functioning of the Rule. + * + * @deprecated No rule supports a change to their language. This will + * be made the default behaviour with PMD 7.0.0. */ +@Deprecated public interface ImmutableLanguage { } diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/lang/rule/MockRule.java b/pmd-core/src/main/java/net/sourceforge/pmd/lang/rule/MockRule.java index 0cfad63c71..5318030a02 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/lang/rule/MockRule.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/lang/rule/MockRule.java @@ -20,7 +20,12 @@ import net.sourceforge.pmd.properties.PropertyFactory; * functional Rule is not needed. For example, during unit testing, or as an * editable surrogate used by IDE plugins. The Language of this Rule defaults to * Java. + * + * @deprecated This is not a supported API. You need the pmd-test module + * on your classpath, or pmd-core's test sources. This will be removed + * in 7.0.0 */ +@Deprecated public class MockRule extends AbstractRule { public MockRule() { diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/lang/rule/XPathRule.java b/pmd-core/src/main/java/net/sourceforge/pmd/lang/rule/XPathRule.java index 773d64a5c0..0f91715bcc 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/lang/rule/XPathRule.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/lang/rule/XPathRule.java @@ -12,25 +12,30 @@ import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Objects; import org.apache.commons.lang3.StringUtils; +import net.sourceforge.pmd.Rule; import net.sourceforge.pmd.RuleContext; import net.sourceforge.pmd.lang.ast.Node; +import net.sourceforge.pmd.lang.ast.xpath.internal.DeprecatedAttrLogger; import net.sourceforge.pmd.lang.rule.xpath.JaxenXPathRuleQuery; import net.sourceforge.pmd.lang.rule.xpath.SaxonXPathRuleQuery; import net.sourceforge.pmd.lang.rule.xpath.XPathRuleQuery; +import net.sourceforge.pmd.lang.rule.xpath.XPathVersion; import net.sourceforge.pmd.properties.EnumeratedProperty; import net.sourceforge.pmd.properties.StringProperty; /** * Rule that tries to match an XPath expression against a DOM view of an AST. - * - *

This rule needs a "xpath" property value in order to function.

*/ public class XPathRule extends AbstractRule { - // TODO 7.0.0 use PropertyDescriptor + /** + * @deprecated Use {@link #XPathRule(XPathVersion, String)} + */ + @Deprecated public static final StringProperty XPATH_DESCRIPTOR = StringProperty.named("xpath") .desc("XPath expression") .defaultValue("") @@ -47,7 +52,11 @@ public class XPathRule extends AbstractRule { XPATH_VERSIONS = Collections.unmodifiableMap(tmp); } - // published, can't be converted + + /** + * @deprecated Use {@link #XPathRule(XPathVersion, String)} + */ + @Deprecated public static final EnumeratedProperty VERSION_DESCRIPTOR = EnumeratedProperty.named("version") .desc("XPath specification version") .mappings(XPATH_VERSIONS) @@ -61,8 +70,13 @@ public class XPathRule extends AbstractRule { */ private XPathRuleQuery xpathRuleQuery; + // this is shared with rules forked by deepCopy, used by the XPathRuleQuery + private DeprecatedAttrLogger attrLogger = DeprecatedAttrLogger.create(this); + /** * Creates a new XPathRule without the corresponding XPath query. + * + * @deprecated Use {@link #XPathRule(XPathVersion, String)} */ public XPathRule() { definePropertyDescriptor(XPATH_DESCRIPTOR); @@ -73,6 +87,8 @@ public class XPathRule extends AbstractRule { /** * Creates a new XPathRule and associates the XPath query. + * + * @deprecated Use {@link #XPathRule(XPathVersion, String)} */ public XPathRule(final String xPath) { this(); @@ -80,21 +96,62 @@ public class XPathRule extends AbstractRule { } /** - * Sets the XPath to query against the desired nodes in {@link #apply(List, RuleContext)}. + * Make a new XPath rule with the given version + expression * - * @param xPath the XPath query + * @param version Version of the XPath language + * @param expression XPath expression + * + * @throws NullPointerException If any of the arguments is null */ + public XPathRule(XPathVersion version, String expression) { + this(); + Objects.requireNonNull(version, "XPath version is null"); + Objects.requireNonNull(expression, "XPath expression is null"); + setXPath(expression); + setVersion(version.getXmlName()); + } + + + @Override + public Rule deepCopy() { + XPathRule rule = (XPathRule) super.deepCopy(); + rule.attrLogger = this.attrLogger; + return rule; + } + + /** + * Returns the version for this rule. Returns null if this is not + * set or invalid. + */ + public XPathVersion getVersion() { + return XPathVersion.ofId(getProperty(VERSION_DESCRIPTOR)); + } + + /** + * Returns the XPath expression that implements this rule. + */ + public String getXPathExpression() { + return getProperty(XPATH_DESCRIPTOR); + } + + /** + * @deprecated Use the constructor {@link #XPathRule(XPathVersion, String)}, + * don't set the expression after the fact. + */ + @Deprecated public void setXPath(final String xPath) { setProperty(XPathRule.XPATH_DESCRIPTOR, xPath); } + /** + * @deprecated Use the constructor {@link #XPathRule(XPathVersion, String)}, + * don't set the version after the fact. + */ + @Deprecated public void setVersion(final String version) { setProperty(XPathRule.VERSION_DESCRIPTOR, version); } - /** - * Apply the rule to all nodes. - */ @Override public void apply(List nodes, RuleContext ctx) { for (Node node : nodes) { @@ -107,7 +164,10 @@ public class XPathRule extends AbstractRule { * * @param node The Node that to be checked. * @param data The RuleContext. + * + * @deprecated Use {@link #apply(List, RuleContext)} */ + @Deprecated public void evaluate(final Node node, final RuleContext data) { if (xPathRuleQueryNeedsInitialization()) { initXPathRuleQuery(); @@ -124,13 +184,21 @@ public class XPathRule extends AbstractRule { * engine in which the query will be run it looks at the XPath version. */ private void initXPathRuleQuery() { - String xpath = getProperty(XPATH_DESCRIPTOR); - String version = getProperty(VERSION_DESCRIPTOR); + String xpath = getXPathExpression(); + XPathVersion version = getVersion(); - initRuleQueryBasedOnVersion(version); + if (version == null) { + throw new IllegalStateException("Invalid XPath version, should have been caught by Rule::dysfunctionReason"); + } + + if (version == XPathVersion.XPATH_1_0) { + xpathRuleQuery = new JaxenXPathRuleQuery(attrLogger); + } else { + xpathRuleQuery = new SaxonXPathRuleQuery(attrLogger); + } xpathRuleQuery.setXPath(xpath); - xpathRuleQuery.setVersion(version); + xpathRuleQuery.setVersion(version.getXmlName()); xpathRuleQuery.setProperties(getPropertiesByPropertyDescriptor()); } @@ -143,10 +211,6 @@ public class XPathRule extends AbstractRule { return xpathRuleQuery == null; } - private void initRuleQueryBasedOnVersion(final String version) { - xpathRuleQuery = XPATH_1_0.equals(version) ? new JaxenXPathRuleQuery() : new SaxonXPathRuleQuery(); - } - @Override public List getRuleChainVisits() { if (xPathRuleQueryNeedsInitialization()) { @@ -161,10 +225,11 @@ public class XPathRule extends AbstractRule { @Override public String dysfunctionReason() { - return hasXPathExpression() ? null : "Missing xPath expression"; - } - - private boolean hasXPathExpression() { - return StringUtils.isNotBlank(getProperty(XPATH_DESCRIPTOR)); + if (getVersion() == null) { + return "Invalid XPath version '" + getProperty(VERSION_DESCRIPTOR) + "'"; + } else if (StringUtils.isBlank(getXPathExpression())) { + return "Missing XPath expression"; + } + return null; } } diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/lang/rule/xpath/AbstractXPathRuleQuery.java b/pmd-core/src/main/java/net/sourceforge/pmd/lang/rule/xpath/AbstractXPathRuleQuery.java index 69c6d0afdf..5647fb9ec8 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/lang/rule/xpath/AbstractXPathRuleQuery.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/lang/rule/xpath/AbstractXPathRuleQuery.java @@ -9,12 +9,17 @@ import java.util.List; import java.util.Map; import net.sourceforge.pmd.RuleContext; +import net.sourceforge.pmd.annotation.InternalApi; import net.sourceforge.pmd.lang.ast.Node; import net.sourceforge.pmd.properties.PropertyDescriptor; /** * This implementation of XPathRuleQuery provides support for RuleChain visits. + * + * @deprecated Internal API */ +@Deprecated +@InternalApi public abstract class AbstractXPathRuleQuery implements XPathRuleQuery { /** diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/lang/rule/xpath/JaxenXPathRuleQuery.java b/pmd-core/src/main/java/net/sourceforge/pmd/lang/rule/xpath/JaxenXPathRuleQuery.java index 2ecd213c9a..4ef992f19d 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/lang/rule/xpath/JaxenXPathRuleQuery.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/lang/rule/xpath/JaxenXPathRuleQuery.java @@ -31,25 +31,38 @@ import org.jaxen.expr.XPathFactory; import org.jaxen.saxpath.Axis; import net.sourceforge.pmd.RuleContext; +import net.sourceforge.pmd.annotation.InternalApi; import net.sourceforge.pmd.lang.ast.Node; +import net.sourceforge.pmd.lang.ast.xpath.internal.ContextualizedNavigator; +import net.sourceforge.pmd.lang.ast.xpath.internal.DeprecatedAttrLogger; import net.sourceforge.pmd.properties.PropertyDescriptor; /** * This is a Jaxen based XPathRule query. + * + * @deprecated Internal API */ +@Deprecated +@InternalApi public class JaxenXPathRuleQuery extends AbstractXPathRuleQuery { private static final Logger LOG = Logger.getLogger(JaxenXPathRuleQuery.class.getName()); - private enum InitializationStatus { - NONE, PARTIAL, FULL - } + static final String AST_ROOT = "_AST_ROOT_"; private InitializationStatus initializationStatus = InitializationStatus.NONE; // Mapping from Node name to applicable XPath queries Map> nodeNameToXPaths; - static final String AST_ROOT = "_AST_ROOT_"; + private final DeprecatedAttrLogger attrCtx; + + public JaxenXPathRuleQuery() { + this(DeprecatedAttrLogger.noop()); + } + + public JaxenXPathRuleQuery(DeprecatedAttrLogger attrCtx) { + this.attrCtx = attrCtx; + } @Override public boolean isSupportedVersion(String version) { @@ -61,7 +74,7 @@ public class JaxenXPathRuleQuery extends AbstractXPathRuleQuery { final List results = new ArrayList<>(); try { - initializeExpressionIfStatusIsNoneOrPartial(data.getLanguageVersion().getLanguageVersionHandler().getXPathHandler().getNavigator()); + initializeExpressionIfStatusIsNoneOrPartial(new ContextualizedNavigator(attrCtx)); List xPaths = getXPathsForNodeOrDefault(node.getXPathNodeName()); for (XPath xpath : xPaths) { @@ -261,4 +274,9 @@ public class JaxenXPathRuleQuery extends AbstractXPathRuleQuery { } return xpath; } + + + private enum InitializationStatus { + NONE, PARTIAL, FULL + } } diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/lang/rule/xpath/SaxonXPathRuleQuery.java b/pmd-core/src/main/java/net/sourceforge/pmd/lang/rule/xpath/SaxonXPathRuleQuery.java index 3f4178a754..4bcb8f7423 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/lang/rule/xpath/SaxonXPathRuleQuery.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/lang/rule/xpath/SaxonXPathRuleQuery.java @@ -7,7 +7,6 @@ package net.sourceforge.pmd.lang.rule.xpath; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; -import java.util.LinkedHashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; @@ -16,15 +15,21 @@ import java.util.logging.Logger; import java.util.regex.Pattern; import net.sourceforge.pmd.RuleContext; +import net.sourceforge.pmd.annotation.InternalApi; import net.sourceforge.pmd.lang.ast.Node; +import net.sourceforge.pmd.lang.ast.xpath.internal.DeprecatedAttrLogger; import net.sourceforge.pmd.lang.ast.xpath.saxon.DocumentNode; import net.sourceforge.pmd.lang.ast.xpath.saxon.ElementNode; import net.sourceforge.pmd.lang.rule.xpath.internal.RuleChainAnalyzer; import net.sourceforge.pmd.lang.xpath.Initializer; import net.sourceforge.pmd.properties.PropertyDescriptor; +import net.sourceforge.pmd.util.DataMap; +import net.sourceforge.pmd.util.DataMap.DataKey; +import net.sourceforge.pmd.util.DataMap.SimpleDataKey; import net.sf.saxon.expr.Expression; import net.sf.saxon.om.Item; +import net.sf.saxon.om.NamePool; import net.sf.saxon.om.NamespaceConstant; import net.sf.saxon.om.SequenceIterator; import net.sf.saxon.om.ValueRepresentation; @@ -50,8 +55,13 @@ import net.sf.saxon.value.Value; /** * This is a Saxon based XPathRule query. + * + * @deprecated Internal API */ +@Deprecated +@InternalApi public class SaxonXPathRuleQuery extends AbstractXPathRuleQuery { + /** * Special nodeName that references the root expression. */ @@ -59,15 +69,10 @@ public class SaxonXPathRuleQuery extends AbstractXPathRuleQuery { private static final Logger LOG = Logger.getLogger(SaxonXPathRuleQuery.class.getName()); - private static final int MAX_CACHE_SIZE = 20; - private static final Map CACHE = new LinkedHashMap(MAX_CACHE_SIZE) { - private static final long serialVersionUID = -7653916493967142443L; + private static final NamePool NAME_POOL = new NamePool(); - @Override - protected boolean removeEldestEntry(final Map.Entry eldest) { - return size() > MAX_CACHE_SIZE; - } - }; + /** Cache key for the wrapped tree for saxon. */ + private static final SimpleDataKey SAXON_TREE_CACHE_KEY = DataMap.simpleDataKey("saxon.tree"); /** * Contains for each nodeName a sub expression, used for implementing rule chain. @@ -86,21 +91,33 @@ public class SaxonXPathRuleQuery extends AbstractXPathRuleQuery { */ private List xpathVariables; + private final DeprecatedAttrLogger attrCtx; + + @Deprecated + public SaxonXPathRuleQuery() { + this(DeprecatedAttrLogger.noop()); + } + + public SaxonXPathRuleQuery(DeprecatedAttrLogger attrCtx) { + this.attrCtx = attrCtx; + } + @Override public boolean isSupportedVersion(String version) { return XPATH_1_0_COMPATIBILITY.equals(version) || XPATH_2_0.equals(version); } @Override - @SuppressWarnings("unchecked") public List evaluate(final Node node, final RuleContext data) { initializeXPathExpression(); try { final DocumentNode documentNode = getDocumentNodeForRootNode(node); + documentNode.setAttrCtx(attrCtx); // // Map AST Node -> Saxon Node final ElementNode rootElementNode = documentNode.nodeToElementNode.get(node); + assert rootElementNode != null : "Cannot find " + node; final XPathDynamicContext xpathDynamicContext = createDynamicContext(rootElementNode); final List nodes = new LinkedList<>(); @@ -182,15 +199,13 @@ public class SaxonXPathRuleQuery extends AbstractXPathRuleQuery { private DocumentNode getDocumentNodeForRootNode(final Node node) { final Node root = getRootNode(node); - DocumentNode documentNode; - synchronized (CACHE) { - documentNode = CACHE.get(root); - if (documentNode == null) { - documentNode = new DocumentNode(root); - CACHE.put(root, documentNode); - } + DataMap> userMap = root.getUserMap(); + DocumentNode docNode = userMap.get(SAXON_TREE_CACHE_KEY); + if (docNode == null) { + docNode = new DocumentNode(root, getNamePool()); + userMap.set(SAXON_TREE_CACHE_KEY, docNode); } - return documentNode; + return docNode; } /** @@ -224,13 +239,14 @@ public class SaxonXPathRuleQuery extends AbstractXPathRuleQuery { try { final XPathEvaluator xpathEvaluator = new XPathEvaluator(); final XPathStaticContext xpathStaticContext = xpathEvaluator.getStaticContext(); + xpathStaticContext.getConfiguration().setNamePool(getNamePool()); // Enable XPath 1.0 compatibility if (XPATH_1_0_COMPATIBILITY.equals(version)) { ((AbstractStaticContext) xpathStaticContext).setBackwardsCompatibilityMode(true); } - ((IndependentContext) xpathEvaluator.getStaticContext()).declareNamespace("fn", NamespaceConstant.FN); + ((IndependentContext) xpathStaticContext).declareNamespace("fn", NamespaceConstant.FN); // Register PMD functions Initializer.initialize((IndependentContext) xpathStaticContext); @@ -349,4 +365,8 @@ public class SaxonXPathRuleQuery extends AbstractXPathRuleQuery { initializeXPathExpression(); return super.getRuleChainVisits(); } + + public static NamePool getNamePool() { + return NAME_POOL; + } } diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/lang/rule/xpath/XPathRuleQuery.java b/pmd-core/src/main/java/net/sourceforge/pmd/lang/rule/xpath/XPathRuleQuery.java index dedd90deab..cd1d36c32f 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/lang/rule/xpath/XPathRuleQuery.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/lang/rule/xpath/XPathRuleQuery.java @@ -8,6 +8,7 @@ import java.util.List; import java.util.Map; import net.sourceforge.pmd.RuleContext; +import net.sourceforge.pmd.annotation.InternalApi; import net.sourceforge.pmd.lang.ast.Node; import net.sourceforge.pmd.properties.PropertyDescriptor; @@ -22,24 +23,38 @@ import net.sourceforge.pmd.properties.PropertyDescriptor; * are recommended to manage internal state that is invariant over AST Nodes in * a fashion which facilities high performance (e.g. caching). *

+ * + * @deprecated This will be internalized in 7.0.0. */ +@InternalApi +@Deprecated public interface XPathRuleQuery { /** * XPath 1.0 version. + * + * @deprecated Use {@link XPathVersion} */ + @Deprecated String XPATH_1_0 = "1.0"; /** * XPath 1.0 compatibility version. + * + * @deprecated Use {@link XPathVersion} */ + @Deprecated String XPATH_1_0_COMPATIBILITY = "1.0 compatibility"; /** * XPath 2.0 version. + * + * @deprecated Use {@link XPathVersion} */ + @Deprecated String XPATH_2_0 = "2.0"; + /** * Set the XPath query string to be used. * diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/lang/rule/xpath/XPathVersion.java b/pmd-core/src/main/java/net/sourceforge/pmd/lang/rule/xpath/XPathVersion.java new file mode 100644 index 0000000000..7177afa443 --- /dev/null +++ b/pmd-core/src/main/java/net/sourceforge/pmd/lang/rule/xpath/XPathVersion.java @@ -0,0 +1,70 @@ +/* + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.rule.xpath; + +import java.util.HashMap; +import java.util.Map; + +/** + * Constants for XPath language version used in XPath queries. + */ +public enum XPathVersion { + /** + * XPath 1.0. + * + * @deprecated Will become unsupported in 7.0.0 + */ + @Deprecated + XPATH_1_0(XPathRuleQuery.XPATH_1_0), + + /** + * XPath 1.0 compatibility mode. + * + * @deprecated Will become unsupported in 7.0.0 + */ + @Deprecated + XPATH_1_0_COMPATIBILITY(XPathRuleQuery.XPATH_1_0_COMPATIBILITY), + + /** XPath 2.0. */ + XPATH_2_0(XPathRuleQuery.XPATH_2_0); + + private static final Map BY_NAME = new HashMap<>(); + private final String version; + + + static { + for (XPathVersion value : values()) { + BY_NAME.put(value.getXmlName(), value); + } + } + + + XPathVersion(String version) { + this.version = version; + } + + + /** + * Returns the string used to represent the version in the XML. + * + * @return A string representation + */ + public String getXmlName() { + return version; + } + + + /** + * Gets an XPath version from the string used to represent + * it in the XML. + * + * @param version A version string + * + * @return An XPath version, or null if the argument is not a valid version + */ + public static XPathVersion ofId(String version) { + return BY_NAME.get(version); + } +} diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/renderers/CodeClimateRenderer.java b/pmd-core/src/main/java/net/sourceforge/pmd/renderers/CodeClimateRenderer.java index 4cc5645b92..0463368e8a 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/renderers/CodeClimateRenderer.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/renderers/CodeClimateRenderer.java @@ -33,7 +33,7 @@ public class CodeClimateRenderer extends AbstractIncrementingRenderer { public static final int REMEDIATION_POINTS_DEFAULT = 50000; public static final String[] CODECLIMATE_DEFAULT_CATEGORIES = new String[] {"Style"}; - // Note: required by https://github.com/codeclimate/spec/blob/master/SPEC.md + // Note: required by https://github.com/codeclimate/platform/blob/master/spec/analyzers/SPEC.md protected static final String NULL_CHARACTER = "\u0000"; protected static final List INTERNAL_DEV_PROPERTIES = Arrays.asList("version", "xpath"); private static final String PMD_PROPERTIES_URL = getPmdPropertiesURL(); @@ -146,9 +146,9 @@ public class CodeClimateRenderer extends AbstractIncrementingRenderer { private String getBody() { String result = "## " + rule.getName() + "\\n\\n" + "Since: PMD " + rule.getSince() + "\\n\\n" + "Priority: " + rule.getPriority() + "\\n\\n" - + "[Categories](https://github.com/codeclimate/spec/blob/master/SPEC.md#categories): " + + "[Categories](https://github.com/codeclimate/platform/blob/master/spec/analyzers/SPEC.md#categories): " + Arrays.toString(getCategories()).replaceAll("[\\[\\]]", "") + "\\n\\n" - + "[Remediation Points](https://github.com/codeclimate/spec/blob/master/SPEC.md#remediation-points): " + + "[Remediation Points](https://github.com/codeclimate/platform/blob/master/spec/analyzers/SPEC.md#remediation-points): " + getRemediationPoints() + "\\n\\n" + cleaned(rule.getDescription()); if (!rule.getExamples().isEmpty()) { diff --git a/pmd-core/src/test/java/net/sourceforge/pmd/lang/ast/AbstractNodeTest.java b/pmd-core/src/test/java/net/sourceforge/pmd/lang/ast/AbstractNodeTest.java index b5ece64c46..d23db81a28 100644 --- a/pmd-core/src/test/java/net/sourceforge/pmd/lang/ast/AbstractNodeTest.java +++ b/pmd-core/src/test/java/net/sourceforge/pmd/lang/ast/AbstractNodeTest.java @@ -1,4 +1,4 @@ -/** +/* * BSD-style license; for more info see http://pmd.sourceforge.net/license.html */ @@ -228,7 +228,6 @@ public class AbstractNodeTest { assertEquals(0, grandChild.getNumChildren()); } - @Test public void testDeprecatedAttributeXPathQuery() throws JaxenException { class MyRootNode extends DummyNode implements RootNode { @@ -238,7 +237,8 @@ public class AbstractNodeTest { } } - addChild(new MyRootNode(nextId()), new DummyNodeWithDeprecatedAttribute(2)).findChildNodesWithXPath("//dummyNode[@Size=1]"); + Node root = addChild(new MyRootNode(nextId()), new DummyNodeWithDeprecatedAttribute(2)); + root.findChildNodesWithXPath("//dummyNode[@Size=1]"); String log = loggingRule.getLog(); @@ -247,5 +247,4 @@ public class AbstractNodeTest { assertTrue(log.contains("dummyNode/@Size")); } - } diff --git a/pmd-core/src/test/java/net/sourceforge/pmd/lang/ast/DummyNode.java b/pmd-core/src/test/java/net/sourceforge/pmd/lang/ast/DummyNode.java index 8cfa55925e..10ddb52a1d 100644 --- a/pmd-core/src/test/java/net/sourceforge/pmd/lang/ast/DummyNode.java +++ b/pmd-core/src/test/java/net/sourceforge/pmd/lang/ast/DummyNode.java @@ -26,6 +26,21 @@ public class DummyNode extends AbstractNode { this.xpathName = xpathName; } + public void setBeginColumn(int i) { + beginColumn = i; + } + + public void setBeginLine(int i) { + beginLine = i; + } + + public void setCoords(int bline, int bcol, int eline, int ecol) { + beginLine = bline; + beginColumn = bcol; + endLine = eline; + endColumn = ecol; + } + @Override public String toString() { return xpathName; diff --git a/pmd-core/src/test/java/net/sourceforge/pmd/lang/ast/DummyNodeWithDeprecatedAttribute.java b/pmd-core/src/test/java/net/sourceforge/pmd/lang/ast/DummyNodeWithDeprecatedAttribute.java index 7deca9984b..0e5dd20606 100644 --- a/pmd-core/src/test/java/net/sourceforge/pmd/lang/ast/DummyNodeWithDeprecatedAttribute.java +++ b/pmd-core/src/test/java/net/sourceforge/pmd/lang/ast/DummyNodeWithDeprecatedAttribute.java @@ -25,7 +25,7 @@ public class DummyNodeWithDeprecatedAttribute extends DummyNode { // this is a attribute that is deprecated for xpath, because it will be removed. // it should still be available via Java. - @DeprecatedAttribute + @DeprecatedAttribute(replaceWith = "@Image") public String getName() { return "foo"; } diff --git a/pmd-core/src/test/java/net/sourceforge/pmd/lang/ast/xpath/AttributeAxisIteratorTest.java b/pmd-core/src/test/java/net/sourceforge/pmd/lang/ast/xpath/AttributeAxisIteratorTest.java index 1d9e274921..a1dda2cb58 100644 --- a/pmd-core/src/test/java/net/sourceforge/pmd/lang/ast/xpath/AttributeAxisIteratorTest.java +++ b/pmd-core/src/test/java/net/sourceforge/pmd/lang/ast/xpath/AttributeAxisIteratorTest.java @@ -5,7 +5,6 @@ package net.sourceforge.pmd.lang.ast.xpath; -import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; @@ -16,15 +15,10 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import org.hamcrest.Matchers; -import org.hamcrest.collection.IsMapContaining; import org.junit.Assert; -import org.junit.Rule; import org.junit.Test; -import net.sourceforge.pmd.junit.JavaUtilLoggingRule; import net.sourceforge.pmd.lang.ast.DummyNode; -import net.sourceforge.pmd.lang.ast.DummyNodeWithDeprecatedAttribute; import net.sourceforge.pmd.lang.ast.Node; @@ -33,32 +27,6 @@ import net.sourceforge.pmd.lang.ast.Node; */ public class AttributeAxisIteratorTest { - @Rule - public JavaUtilLoggingRule loggingRule = new JavaUtilLoggingRule(Attribute.class.getName()); - - /** - * Verifies that attributes are returned, even if they are deprecated. - * Deprecated attributes are still accessible, but a warning is logged, when - * the value is used. - */ - @Test - public void testAttributeDeprecation() { - // make sure, we log - Attribute.DETECTED_DEPRECATED_ATTRIBUTES.clear(); - - Node dummy = new DummyNodeWithDeprecatedAttribute(2); - Map attributes = toMap(new AttributeAxisIterator(dummy)); - assertThat(attributes, IsMapContaining.hasKey("Size")); - assertThat(attributes, IsMapContaining.hasKey("Name")); - - assertThat(attributes.get("Size").getStringValue(), Matchers.is("2")); - assertThat(attributes.get("Name").getStringValue(), Matchers.is("foo")); - - String log = loggingRule.getLog(); - assertThat(log, Matchers.containsString("Use of deprecated attribute 'dummyNode/@Size' in XPath query")); - assertThat(log, Matchers.containsString("Use of deprecated attribute 'dummyNode/@Name' in XPath query")); - } - /** * Test hasNext and next. */ diff --git a/pmd-core/src/test/java/net/sourceforge/pmd/lang/rule/XPathRuleTest.java b/pmd-core/src/test/java/net/sourceforge/pmd/lang/rule/XPathRuleTest.java index e9bc85a985..07605202d5 100644 --- a/pmd-core/src/test/java/net/sourceforge/pmd/lang/rule/XPathRuleTest.java +++ b/pmd-core/src/test/java/net/sourceforge/pmd/lang/rule/XPathRuleTest.java @@ -4,18 +4,37 @@ package net.sourceforge.pmd.lang.rule; +import static java.util.Collections.singletonList; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.Assert.assertEquals; + +import org.hamcrest.Matchers; import org.junit.Assert; +import org.junit.Rule; import org.junit.Test; +import net.sourceforge.pmd.RuleContext; +import net.sourceforge.pmd.junit.JavaUtilLoggingRule; +import net.sourceforge.pmd.lang.DummyLanguageModule; +import net.sourceforge.pmd.lang.LanguageRegistry; +import net.sourceforge.pmd.lang.ast.DummyNode; +import net.sourceforge.pmd.lang.ast.DummyNodeWithDeprecatedAttribute; +import net.sourceforge.pmd.lang.ast.xpath.Attribute; +import net.sourceforge.pmd.lang.rule.xpath.XPathVersion; + public class XPathRuleTest { + @Rule + public JavaUtilLoggingRule loggingRule = new JavaUtilLoggingRule(Attribute.class.getName()); + /** * It's easy to forget the attribute "typeResolution=true" when * defining XPath rules in xml. Therefore we by default enable * typeresolution. For Java rules, type resolution was enabled by * default long time ago. * - * @see #2048 [core] Enable type resolution by default for XPath rules + * @see #2048 [core] Enable type resolution by default for XPath + * rules */ @Test public void typeResolutionShouldBeEnabledByDefault() { @@ -26,4 +45,75 @@ public class XPathRuleTest { Assert.assertTrue(rule2.isTypeResolution()); } + + @Test + public void testAttributeDeprecation10() { + testDeprecation(XPathVersion.XPATH_1_0); + } + + @Test + public void testAttributeDeprecation20() { + testDeprecation(XPathVersion.XPATH_2_0); + } + + public void testDeprecation(XPathVersion version) { + XPathRule xpr = makeRule(version, "SomeRule"); + + loggingRule.clear(); + + RuleContext ctx = new RuleContext(); + ctx.setLanguageVersion(LanguageRegistry.getLanguage(DummyLanguageModule.NAME).getDefaultVersion()); + DummyNode firstNode = newNode(); + eval(ctx, xpr, firstNode); + assertEquals(1, ctx.getReport().size()); + + String log = loggingRule.getLog(); + assertThat(log, Matchers.containsString("Use of deprecated attribute 'dummyNode/@Size' by XPath rule 'SomeRule'")); + assertThat(log, Matchers.containsString("Use of deprecated attribute 'dummyNode/@Name' by XPath rule 'SomeRule', please use @Image instead")); + + + loggingRule.clear(); + + eval(ctx, xpr, newNode()); // with another node + assertEquals(2, ctx.getReport().size()); + + assertEquals("", loggingRule.getLog()); // no additional warnings + + + // with another rule forked from the same one (in multithreaded processor) + eval(ctx, xpr.deepCopy(), newNode()); + assertEquals(3, ctx.getReport().size()); + + assertEquals("", loggingRule.getLog()); // no additional warnings + + // with another rule on the same node, new warnings + XPathRule otherRule = makeRule(version, "OtherRule"); + otherRule.setRuleSetName("rset.xml"); + eval(ctx, otherRule, firstNode); + assertEquals(4, ctx.getReport().size()); + + log = loggingRule.getLog(); + assertThat(log, Matchers.containsString("Use of deprecated attribute 'dummyNode/@Size' by XPath rule 'OtherRule' (in ruleset 'rset.xml')")); + assertThat(log, Matchers.containsString("Use of deprecated attribute 'dummyNode/@Name' by XPath rule 'OtherRule' (in ruleset 'rset.xml'), please use @Image instead")); + + } + + public XPathRule makeRule(XPathVersion version, String name) { + XPathRule xpr = new XPathRule(version, "//dummyNode[@Size >= 2 and @Name='foo']"); + xpr.setName(name); + xpr.setMessage("gotcha"); + return xpr; + } + + public void eval(RuleContext ctx, net.sourceforge.pmd.Rule rule, DummyNode node) { + rule.apply(singletonList(node), ctx); + } + + public DummyNode newNode() { + DummyNode dummy = new DummyNodeWithDeprecatedAttribute(2); + dummy.setCoords(1, 1, 1, 2); + return dummy; + } + + } diff --git a/pmd-core/src/test/java/net/sourceforge/pmd/renderers/CodeClimateRendererTest.java b/pmd-core/src/test/java/net/sourceforge/pmd/renderers/CodeClimateRendererTest.java index 28ca250b06..c81b425668 100644 --- a/pmd-core/src/test/java/net/sourceforge/pmd/renderers/CodeClimateRendererTest.java +++ b/pmd-core/src/test/java/net/sourceforge/pmd/renderers/CodeClimateRendererTest.java @@ -30,8 +30,8 @@ public class CodeClimateRendererTest extends AbstractRendererTest { public String getExpected() { return "{\"type\":\"issue\",\"check_name\":\"Foo\",\"description\":\"blah\"," + "\"content\":{\"body\":\"## Foo\\n\\nSince: PMD null\\n\\nPriority: Low\\n\\n" - + "[Categories](https://github.com/codeclimate/spec/blob/master/SPEC.md#categories): Style\\n\\n" - + "[Remediation Points](https://github.com/codeclimate/spec/blob/master/SPEC.md#remediation-points): 50000\\n\\n" + + "[Categories](https://github.com/codeclimate/platform/blob/master/spec/analyzers/SPEC.md#categories): Style\\n\\n" + + "[Remediation Points](https://github.com/codeclimate/platform/blob/master/spec/analyzers/SPEC.md#remediation-points): 50000\\n\\n" + "desc\\n\\n" + "### [PMD properties](https://pmd.github.io/latest/pmd_devdocs_working_with_properties.html)\\n\\n" + "Name | Value | Description\\n" + "--- | --- | ---\\n" @@ -45,8 +45,8 @@ public class CodeClimateRendererTest extends AbstractRendererTest { public String getExpectedWithProperties() { return "{\"type\":\"issue\",\"check_name\":\"Foo\",\"description\":\"blah\"," + "\"content\":{\"body\":\"## Foo\\n\\nSince: PMD null\\n\\nPriority: Low\\n\\n" - + "[Categories](https://github.com/codeclimate/spec/blob/master/SPEC.md#categories): Style\\n\\n" - + "[Remediation Points](https://github.com/codeclimate/spec/blob/master/SPEC.md#remediation-points): 50000\\n\\n" + + "[Categories](https://github.com/codeclimate/platform/blob/master/spec/analyzers/SPEC.md#categories): Style\\n\\n" + + "[Remediation Points](https://github.com/codeclimate/platform/blob/master/spec/analyzers/SPEC.md#remediation-points): 50000\\n\\n" + "desc\\n\\n" + "### [PMD properties](https://pmd.github.io/latest/pmd_devdocs_working_with_properties.html)\\n\\n" + "Name | Value | Description\\n" + "--- | --- | ---\\n" @@ -67,8 +67,8 @@ public class CodeClimateRendererTest extends AbstractRendererTest { public String getExpectedMultiple() { return "{\"type\":\"issue\",\"check_name\":\"Foo\",\"description\":\"blah\"," + "\"content\":{\"body\":\"## Foo\\n\\nSince: PMD null\\n\\nPriority: Low\\n\\n" - + "[Categories](https://github.com/codeclimate/spec/blob/master/SPEC.md#categories): Style\\n\\n" - + "[Remediation Points](https://github.com/codeclimate/spec/blob/master/SPEC.md#remediation-points): 50000\\n\\n" + + "[Categories](https://github.com/codeclimate/platform/blob/master/spec/analyzers/SPEC.md#categories): Style\\n\\n" + + "[Remediation Points](https://github.com/codeclimate/platform/blob/master/spec/analyzers/SPEC.md#remediation-points): 50000\\n\\n" + "desc\\n\\n" + "### [PMD properties](https://pmd.github.io/latest/pmd_devdocs_working_with_properties.html)\\n\\n" + "Name | Value | Description\\n" + "--- | --- | ---\\n" @@ -77,8 +77,8 @@ public class CodeClimateRendererTest extends AbstractRendererTest { + "\"},\"categories\":[\"Style\"],\"location\":{\"path\":\"" + getSourceCodeFilename() + "\",\"lines\":{\"begin\":1,\"end\":1}},\"severity\":\"info\",\"remediation_points\":50000}" + "\u0000" + PMD.EOL + "{\"type\":\"issue\",\"check_name\":\"Foo\",\"description\":\"blah\"," + "\"content\":{\"body\":\"## Foo\\n\\nSince: PMD null\\n\\nPriority: Low\\n\\n" - + "[Categories](https://github.com/codeclimate/spec/blob/master/SPEC.md#categories): Style\\n\\n" - + "[Remediation Points](https://github.com/codeclimate/spec/blob/master/SPEC.md#remediation-points): 50000\\n\\n" + + "[Categories](https://github.com/codeclimate/platform/blob/master/spec/analyzers/SPEC.md#categories): Style\\n\\n" + + "[Remediation Points](https://github.com/codeclimate/platform/blob/master/spec/analyzers/SPEC.md#remediation-points): 50000\\n\\n" + "desc\\n\\n" + "### [PMD properties](https://pmd.github.io/latest/pmd_devdocs_working_with_properties.html)\\n\\n" + "Name | Value | Description\\n" + "--- | --- | ---\\n" diff --git a/pmd-core/src/test/resources/net/sourceforge/pmd/xml/j2ee.xml b/pmd-core/src/test/resources/net/sourceforge/pmd/xml/j2ee.xml index f158927f95..17443c62ab 100644 --- a/pmd-core/src/test/resources/net/sourceforge/pmd/xml/j2ee.xml +++ b/pmd-core/src/test/resources/net/sourceforge/pmd/xml/j2ee.xml @@ -20,6 +20,7 @@ 3 + 4 + 4 + 4 + 4 + 4 + 3 + 3 + @@ -355,6 +363,7 @@ public class SomeEJB extends EJBObject implements EJBLocalHome { 3 + 3 + 3 + - \ No newline at end of file + diff --git a/pmd-doc/src/main/java/net/sourceforge/pmd/docs/DeadLinksChecker.java b/pmd-doc/src/main/java/net/sourceforge/pmd/docs/DeadLinksChecker.java index 1ab25d40a6..66d8c45a7e 100644 --- a/pmd-doc/src/main/java/net/sourceforge/pmd/docs/DeadLinksChecker.java +++ b/pmd-doc/src/main/java/net/sourceforge/pmd/docs/DeadLinksChecker.java @@ -177,6 +177,12 @@ public class DeadLinksChecker { } else { linkOk = linkTarget.isEmpty() || htmlPages.contains(linkTarget); } + + // maybe a local file + if (!linkOk) { + Path localResource = docsDirectory.resolve(linkTarget); + linkOk = Files.exists(localResource); + } } if (!linkOk) { diff --git a/pmd-doc/src/main/java/net/sourceforge/pmd/docs/RuleDocGenerator.java b/pmd-doc/src/main/java/net/sourceforge/pmd/docs/RuleDocGenerator.java index 4d1ffa2dee..e0e6c2511f 100644 --- a/pmd-doc/src/main/java/net/sourceforge/pmd/docs/RuleDocGenerator.java +++ b/pmd-doc/src/main/java/net/sourceforge/pmd/docs/RuleDocGenerator.java @@ -405,10 +405,11 @@ public class RuleDocGenerator { lines.addAll(EscapeUtils.escapeLines(toLines(stripIndentation(rule.getDescription())))); lines.add(""); - if (rule instanceof XPathRule || rule instanceof RuleReference && ((RuleReference) rule).getRule() instanceof XPathRule) { + XPathRule xpathRule = asXPathRule(rule); + if (xpathRule != null) { lines.add("**This rule is defined by the following XPath expression:**"); lines.add("``` xpath"); - lines.addAll(toLines(StringUtils.stripToEmpty(rule.getProperty(XPathRule.XPATH_DESCRIPTOR)))); + lines.addAll(toLines(StringUtils.stripToEmpty(xpathRule.getXPathExpression()))); lines.add("```"); } else { lines.add("**This rule is defined by the following Java class:** " @@ -502,6 +503,15 @@ public class RuleDocGenerator { } } + private XPathRule asXPathRule(Rule rule) { + if (rule instanceof XPathRule) { + return (XPathRule) rule; + } else if (rule instanceof RuleReference && ((RuleReference) rule).getRule() instanceof XPathRule) { + return (XPathRule) ((RuleReference) rule).getRule(); + } + return null; + } + private static boolean isDeprecated(PropertyDescriptor propertyDescriptor) { return propertyDescriptor.description() != null && propertyDescriptor.description().toLowerCase(Locale.ROOT).startsWith(DEPRECATED_RULE_PROPERTY_MARKER); diff --git a/pmd-doc/src/test/resources/rulesets/ruledoctest/sample.xml b/pmd-doc/src/test/resources/rulesets/ruledoctest/sample.xml index 44e2bb76fb..78591da860 100644 --- a/pmd-doc/src/test/resources/rulesets/ruledoctest/sample.xml +++ b/pmd-doc/src/test/resources/rulesets/ruledoctest/sample.xml @@ -57,6 +57,7 @@ Here might be <script>alert('XSS');</script> as well. And "quotes". 3 + 3 + 3 + 3 + 0; } + /** + * @deprecated Use {@link #getName()} + * @return + */ + @Override + @DeprecatedAttribute(replaceWith = "@Name") + @Deprecated + public String getImage() { + return getName(); + } + + /** Returns the name of the variable. */ + public String getName() { + return super.getImage(); + } /** * Returns true if the declared variable has an array type. @@ -161,9 +177,13 @@ public class ASTVariableDeclaratorId extends AbstractJavaTypeNode implements Dim /** * Returns the name of the variable. + * + * @deprecated Use {@link #getName()} */ + @Deprecated + @DeprecatedAttribute(replaceWith = "@Name") public String getVariableName() { - return getImage(); + return getName(); } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/AbstractAnyTypeDeclaration.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/AbstractAnyTypeDeclaration.java index fbdacd93bc..ed9076c669 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/AbstractAnyTypeDeclaration.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/AbstractAnyTypeDeclaration.java @@ -6,6 +6,7 @@ package net.sourceforge.pmd.lang.java.ast; import net.sourceforge.pmd.annotation.InternalApi; import net.sourceforge.pmd.lang.ast.Node; +import net.sourceforge.pmd.lang.ast.xpath.internal.DeprecatedAttribute; import net.sourceforge.pmd.lang.java.qname.JavaTypeQualifiedName; import net.sourceforge.pmd.lang.java.typeresolution.typedefinition.JavaTypeDefinition; @@ -37,10 +38,14 @@ public abstract class AbstractAnyTypeDeclaration extends AbstractJavaAccessTypeN || getParent() instanceof ASTRecordBody; } - @Override + /** + * @deprecated Use {@link #getSimpleName()} + */ @Deprecated + @DeprecatedAttribute(replaceWith = "@SimpleName") + @Override public String getImage() { - return super.getImage(); + return getSimpleName(); } @Override @@ -50,7 +55,17 @@ public abstract class AbstractAnyTypeDeclaration extends AbstractJavaAccessTypeN @Override public String getSimpleName() { - return getImage(); + return super.getImage(); + } + + + /** + * Returns the record component list, or null if this is not a record + * declaration. + */ + // @Nullable // TODO pull up to ASTAnyTypeDecl on 7.0.x + public ASTRecordComponentList getRecordComponents() { + return getFirstChildOfType(ASTRecordComponentList.class); } /** diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/AbstractClassWithoutAbstractMethodRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/AbstractClassWithoutAbstractMethodRule.java new file mode 100644 index 0000000000..3f20b559d5 --- /dev/null +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/AbstractClassWithoutAbstractMethodRule.java @@ -0,0 +1,50 @@ +/* + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + + +package net.sourceforge.pmd.lang.java.rule.bestpractices; + +import net.sourceforge.pmd.lang.java.ast.ASTAnyTypeBodyDeclaration; +import net.sourceforge.pmd.lang.java.ast.ASTAnyTypeBodyDeclaration.DeclarationKind; +import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceDeclaration; +import net.sourceforge.pmd.lang.java.ast.ASTExtendsList; +import net.sourceforge.pmd.lang.java.ast.ASTImplementsList; +import net.sourceforge.pmd.lang.java.ast.ASTMethodDeclaration; +import net.sourceforge.pmd.lang.java.rule.AbstractJavaRule; + +public class AbstractClassWithoutAbstractMethodRule extends AbstractJavaRule { + + public AbstractClassWithoutAbstractMethodRule() { + addRuleChainVisit(ASTClassOrInterfaceDeclaration.class); + } + + @Override + public Object visit(ASTClassOrInterfaceDeclaration node, Object data) { + if (!node.isAbstract() || doesExtend(node) || doesImplement(node)) { + return data; + } + + int countOfAbstractMethods = 0; + for (ASTAnyTypeBodyDeclaration decl : node.getDeclarations()) { + if (decl.getKind() == DeclarationKind.METHOD) { + ASTMethodDeclaration methodDecl = (ASTMethodDeclaration) decl.getDeclarationNode(); + if (methodDecl.isAbstract()) { + countOfAbstractMethods++; + } + } + } + if (countOfAbstractMethods == 0) { + addViolation(data, node); + } + return data; + } + + private boolean doesExtend(ASTClassOrInterfaceDeclaration node) { + return node.getFirstChildOfType(ASTExtendsList.class) != null; + } + + private boolean doesImplement(ASTClassOrInterfaceDeclaration node) { + return node.getFirstChildOfType(ASTImplementsList.class) != null; + } +} diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/AbstractPositionLiteralsFirstInComparisons.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/AbstractPositionLiteralsFirstInComparisons.java new file mode 100644 index 0000000000..3b09462725 --- /dev/null +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/AbstractPositionLiteralsFirstInComparisons.java @@ -0,0 +1,119 @@ +/* + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.bestpractices; + +import net.sourceforge.pmd.lang.ast.Node; +import net.sourceforge.pmd.lang.java.ast.ASTArgumentList; +import net.sourceforge.pmd.lang.java.ast.ASTArguments; +import net.sourceforge.pmd.lang.java.ast.ASTConditionalAndExpression; +import net.sourceforge.pmd.lang.java.ast.ASTConditionalOrExpression; +import net.sourceforge.pmd.lang.java.ast.ASTEqualityExpression; +import net.sourceforge.pmd.lang.java.ast.ASTExpression; +import net.sourceforge.pmd.lang.java.ast.ASTLiteral; +import net.sourceforge.pmd.lang.java.ast.ASTName; +import net.sourceforge.pmd.lang.java.ast.ASTNullLiteral; +import net.sourceforge.pmd.lang.java.ast.ASTPrimaryExpression; +import net.sourceforge.pmd.lang.java.ast.ASTPrimaryPrefix; +import net.sourceforge.pmd.lang.java.ast.ASTPrimarySuffix; +import net.sourceforge.pmd.lang.java.ast.JavaNode; +import net.sourceforge.pmd.lang.java.rule.AbstractJavaRule; + +class AbstractPositionLiteralsFirstInComparisons extends AbstractJavaRule { + + private final String equalsImage; + + AbstractPositionLiteralsFirstInComparisons(String equalsImage) { + addRuleChainVisit(ASTPrimaryExpression.class); + this.equalsImage = equalsImage; + } + + @Override + public Object visit(ASTPrimaryExpression node, Object data) { + ASTPrimaryPrefix primaryPrefix = node.getFirstChildOfType(ASTPrimaryPrefix.class); + ASTPrimarySuffix primarySuffix = node.getFirstChildOfType(ASTPrimarySuffix.class); + if (primaryPrefix != null && primarySuffix != null) { + ASTName name = primaryPrefix.getFirstChildOfType(ASTName.class); + if (name == null || !name.getImage().endsWith(equalsImage)) { + return data; + } + if (!isSingleStringLiteralArgument(primarySuffix)) { + return data; + } + if (isWithinNullComparison(node)) { + return data; + } + addViolation(data, node); + } + return node; + } + + private boolean isWithinNullComparison(ASTPrimaryExpression node) { + for (ASTExpression parentExpr : node.getParentsOfType(ASTExpression.class)) { + if (isComparisonWithNull(parentExpr, "==", ASTConditionalOrExpression.class) + || isComparisonWithNull(parentExpr, "!=", ASTConditionalAndExpression.class)) { + return true; + } + } + return false; + } + + /* + * Expression/ConditionalAndExpression//EqualityExpression(@Image='!=']//NullLiteral + * Expression/ConditionalOrExpression//EqualityExpression(@Image='==']//NullLiteral + */ + private boolean isComparisonWithNull(ASTExpression parentExpr, String equalOperator, Class condition) { + Node condExpr = null; + ASTEqualityExpression eqExpr = null; + if (parentExpr != null) { + condExpr = parentExpr.getFirstChildOfType(condition); + } + if (condExpr != null) { + eqExpr = condExpr.getFirstDescendantOfType(ASTEqualityExpression.class); + } + if (eqExpr != null) { + return eqExpr.hasImageEqualTo(equalOperator) && eqExpr.hasDescendantOfType(ASTNullLiteral.class); + } + return false; + } + + /* + * This corresponds to the following XPath expression: + * (../PrimarySuffix/Arguments/ArgumentList/Expression/PrimaryExpression/PrimaryPrefix/Literal[@StringLiteral= true()]) + * and + * ( count(../PrimarySuffix/Arguments/ArgumentList/Expression) = 1 ) + */ + private boolean isSingleStringLiteralArgument(ASTPrimarySuffix primarySuffix) { + if (!primarySuffix.isArguments() || primarySuffix.getArgumentCount() != 1) { + return false; + } + Node node = primarySuffix; + node = node.getFirstChildOfType(ASTArguments.class); + if (node != null) { + node = node.getFirstChildOfType(ASTArgumentList.class); + if (node.getNumChildren() != 1) { + return false; + } + } + if (node != null) { + node = node.getFirstChildOfType(ASTExpression.class); + } + if (node != null) { + node = node.getFirstChildOfType(ASTPrimaryExpression.class); + } + if (node != null) { + node = node.getFirstChildOfType(ASTPrimaryPrefix.class); + } + if (node != null) { + node = node.getFirstChildOfType(ASTLiteral.class); + } + if (node != null) { + ASTLiteral literal = (ASTLiteral) node; + if (literal.isStringLiteral()) { + return true; + } + } + return false; + } +} diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/ForLoopCanBeForeachRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/ForLoopCanBeForeachRule.java index fccfd94132..fb7873371d 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/ForLoopCanBeForeachRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/ForLoopCanBeForeachRule.java @@ -229,7 +229,7 @@ public class ForLoopCanBeForeachRule extends AbstractJavaRule { + "/Name[matches(@Image,'\\w+\\.(size|length)')]" + "|" + "./RelationalExpression[@Image='<=']/AdditiveExpression[count(*)=2 and " - + "@Image='-' and PrimaryExpression/PrimaryPrefix/Literal[@Image='1']]" + + "@Operator='-' and PrimaryExpression/PrimaryPrefix/Literal[@Image='1']]" + "/PrimaryExpression/PrimaryPrefix/Name[matches(@Image,'\\w+\\.(size|length)')]"); if (left.isEmpty()) { diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/PositionLiteralsFirstInCaseInsensitiveComparisonsRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/PositionLiteralsFirstInCaseInsensitiveComparisonsRule.java new file mode 100644 index 0000000000..1c84769c01 --- /dev/null +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/PositionLiteralsFirstInCaseInsensitiveComparisonsRule.java @@ -0,0 +1,13 @@ +/* + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.bestpractices; + +public class PositionLiteralsFirstInCaseInsensitiveComparisonsRule extends AbstractPositionLiteralsFirstInComparisons { + + public PositionLiteralsFirstInCaseInsensitiveComparisonsRule() { + super(".equalsIgnoreCase"); + } + +} diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/PositionLiteralsFirstInComparisonsRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/PositionLiteralsFirstInComparisonsRule.java new file mode 100644 index 0000000000..2b9edd12c4 --- /dev/null +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/PositionLiteralsFirstInComparisonsRule.java @@ -0,0 +1,13 @@ +/* + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.bestpractices; + +public class PositionLiteralsFirstInComparisonsRule extends AbstractPositionLiteralsFirstInComparisons { + + public PositionLiteralsFirstInComparisonsRule() { + super(".equals"); + } + +} diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codestyle/ClassNamingConventionsRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codestyle/ClassNamingConventionsRule.java index b90c1804aa..efd4a782c5 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codestyle/ClassNamingConventionsRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codestyle/ClassNamingConventionsRule.java @@ -30,7 +30,7 @@ public class ClassNamingConventionsRule extends AbstractNamingConventionRule interfaceRegex = defaultProp("interface").build(); private final PropertyDescriptor enumerationRegex = defaultProp("enum").build(); private final PropertyDescriptor annotationRegex = defaultProp("annotation").build(); - private final PropertyDescriptor utilityClassRegex = defaultProp("utility class").defaultValue("[A-Z][a-zA-Z0-9]+(Utils?|Helper)").build(); + private final PropertyDescriptor utilityClassRegex = defaultProp("utility class").defaultValue("[A-Z][a-zA-Z0-9]+(Utils?|Helper|Constants)").build(); public ClassNamingConventionsRule() { diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/JUnitSpellingRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/JUnitSpellingRule.java new file mode 100644 index 0000000000..8fd2249133 --- /dev/null +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/JUnitSpellingRule.java @@ -0,0 +1,28 @@ +/* + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + + +package net.sourceforge.pmd.lang.java.rule.errorprone; + +import net.sourceforge.pmd.lang.java.ast.ASTMethodDeclaration; +import net.sourceforge.pmd.lang.java.rule.AbstractJUnitRule; + +public class JUnitSpellingRule extends AbstractJUnitRule { + + @Override + public Object visit(ASTMethodDeclaration node, Object data) { + if (node.getArity() != 0) { + return super.visit(node, data); + } + + String name = node.getName(); + if (!"setUp".equals(name) && "setup".equalsIgnoreCase(name)) { + addViolation(data, node); + } + if (!"tearDown".equals(name) && "teardown".equalsIgnoreCase(name)) { + addViolation(data, node); + } + return super.visit(node, data); + } +} diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/JUnitStaticSuiteRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/JUnitStaticSuiteRule.java new file mode 100644 index 0000000000..de0f71ada6 --- /dev/null +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/JUnitStaticSuiteRule.java @@ -0,0 +1,24 @@ +/* + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + + +package net.sourceforge.pmd.lang.java.rule.errorprone; + +import net.sourceforge.pmd.lang.java.ast.ASTMethodDeclaration; +import net.sourceforge.pmd.lang.java.rule.AbstractJUnitRule; + +public class JUnitStaticSuiteRule extends AbstractJUnitRule { + + @Override + public Object visit(ASTMethodDeclaration node, Object data) { + if (node.getArity() != 0) { + return super.visit(node, data); + } + String name = node.getName(); + if ("suite".equals(name) && (!node.isStatic() || !node.isPublic())) { + addViolation(data, node); + } + return super.visit(node, data); + } +} diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/ProperCloneImplementationRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/ProperCloneImplementationRule.java new file mode 100644 index 0000000000..19262a5535 --- /dev/null +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/ProperCloneImplementationRule.java @@ -0,0 +1,52 @@ +/* + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + + +package net.sourceforge.pmd.lang.java.rule.errorprone; + +import java.util.List; + +import net.sourceforge.pmd.lang.java.ast.ASTAllocationExpression; +import net.sourceforge.pmd.lang.java.ast.ASTBlock; +import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceDeclaration; +import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceType; +import net.sourceforge.pmd.lang.java.ast.ASTMethodDeclaration; +import net.sourceforge.pmd.lang.java.rule.AbstractJavaRule; + +public class ProperCloneImplementationRule extends AbstractJavaRule { + + public ProperCloneImplementationRule() { + addRuleChainVisit(ASTMethodDeclaration.class); + } + + @Override + public Object visit(ASTMethodDeclaration node, Object data) { + if (!"clone".equals(node.getName()) || node.getArity() > 0) { + return data; + } + + ASTBlock block = node.getFirstChildOfType(ASTBlock.class); + if (block == null) { + return data; + } + + String enclosingClassName = node.getFirstParentOfType(ASTClassOrInterfaceDeclaration.class).getSimpleName(); + if (blockHasAllocations(block, enclosingClassName)) { + addViolation(data, node); + } + + return data; + } + + private boolean blockHasAllocations(ASTBlock block, String enclosingClassName) { + List allocations = block.findDescendantsOfType(ASTAllocationExpression.class); + for (ASTAllocationExpression alloc : allocations) { + ASTClassOrInterfaceType type = alloc.getFirstChildOfType(ASTClassOrInterfaceType.class); + if (type.hasImageEqualTo(enclosingClassName)) { + return true; + } + } + return false; + } +} diff --git a/pmd-java/src/main/resources/category/java/bestpractices.xml b/pmd-java/src/main/resources/category/java/bestpractices.xml index d15c60e627..3264cd40a3 100644 --- a/pmd-java/src/main/resources/category/java/bestpractices.xml +++ b/pmd-java/src/main/resources/category/java/bestpractices.xml @@ -13,7 +13,7 @@ Rules which enforce generally accepted best practices. language="java" since="3.0" message="This abstract class does not have any abstract methods" - class="net.sourceforge.pmd.lang.rule.XPathRule" + class="net.sourceforge.pmd.lang.java.rule.bestpractices.AbstractClassWithoutAbstractMethodRule" externalInfoUrl="${pmd.website.baseurl}/pmd_rules_java_bestpractices.html#abstractclasswithoutabstractmethod"> The abstract class does not contain any abstract methods. An abstract class suggests @@ -22,19 +22,6 @@ abstract methods. If the class is intended to be used as a base class only (not directly) a protected constructor can be provided prevent direct instantiation. 3 - - - - - - - 3 + 3 + 3 + @@ -422,12 +412,13 @@ By convention, the default label should be the last label in a switch statement. 3 + @@ -541,6 +532,7 @@ the loop iterates over. By default this rule allows a regular 'for' loop with on + //ForInit/LocalVariableDeclaration[count(VariableDeclarator) > $maximumVariables] @@ -587,12 +579,13 @@ through the @RunWith(Suite.class) annotation. 3 + @@ -628,16 +621,17 @@ JUnit 5 introduced @AfterEach and @AfterAll annotations to execute methods after 3 + @@ -672,16 +666,17 @@ JUnit 5 introduced @BeforeEach and @BeforeAll annotations to execute methods bef 3 + @@ -716,6 +711,7 @@ In JUnit 5, one of the following annotations should be used for tests: @Test, @R 3 + 3 + 4 + Position literals first in comparisons, if the second argument is null then NullPointerExceptions can be avoided, they will just return false. 3 - - - - - - - Position literals first in comparisons, if the second argument is null then NullPointerExceptions can be avoided, they will just return false. 3 - - - - - - - 3 + 3 + //Type/ReferenceType/ClassOrInterfaceType[@Image='Hashtable'] @@ -1206,6 +1166,7 @@ Consider replacing Vector usages with the newer java.util.ArrayList if expensive 3 + //Type/ReferenceType/ClassOrInterfaceType[@Image='Vector'] @@ -1267,6 +1228,7 @@ will (and by priority) and avoid clogging the Standard out log. 2 + 3 + @@ -1445,7 +1407,7 @@ public class FooTest extends TestCase { void testCode() { Object a, b; assertTrue(a.equals(b)); // bad usage - assertEquals(?a should equals b?, a, b); // good usage + assertEquals("a should equals b", a, b); // good usage } } ]]> @@ -1465,17 +1427,11 @@ more specific methods, like assertNull, assertNotNull. 3 + @@ -1519,17 +1481,11 @@ by more specific methods, like assertSame, assertNotSame. 3 + @@ -1569,6 +1531,7 @@ When asserting a value is the same as a literal or Boxed boolean, use assertTrue 3 + 3 + @@ -112,11 +113,12 @@ If the goal is to avoid defining constants in a scope smaller than the class, th 3 + 4 + @@ -210,12 +213,13 @@ Clarify your intent by using private or package access modifiers instead. 3 + @@ -244,12 +248,13 @@ visibility cannot be reduced). Clarify your intent by using private or package a 3 + @@ -276,6 +281,7 @@ and increases the maintenance burden. 2 + //Name[starts-with(@Image,'System.loadLibrary')] @@ -313,12 +319,13 @@ prefix for these methods. 4 + 3 + 0 ] +//ClassOrInterfaceDeclaration[ExtendsList/*] /ClassOrInterfaceBody /ClassOrInterfaceBodyDeclaration - /ConstructorDeclaration[ count (.//ExplicitConstructorInvocation)=0 ] + /ConstructorDeclaration[ not(.//ExplicitConstructorInvocation) ] ]]> @@ -549,16 +557,17 @@ The rule allows methods and fields annotated with Guava's @VisibleForTesting. 3 + @@ -620,23 +629,24 @@ usage by developers who should be implementing their own versions in the concret 1 + @@ -666,6 +676,7 @@ public abstract class ShouldBeAbstract { No need to explicitly extend Object. 4 + 3 + 3 + //ForStatement[not(Statement/Block)] @@ -860,13 +873,14 @@ Names for references to generic values should be limited to a single uppercase l 4 + 1 or - string:upper-case(@Image) != @Image + upper-case(@Image) != @Image ] ]]> @@ -943,11 +957,12 @@ by the rule {% rule java/codestyle/ControlStatementBraces %}. 3 + @@ -985,6 +1000,7 @@ by the rule {% rule java/codestyle/ControlStatementBraces %}. 3 + 4 + 4 + 3 + $minimum] +//VariableDeclaratorId[string-length(@Name) > $minimum] ]]> @@ -1248,6 +1267,7 @@ The EJB Specification states that any MessageDrivenBean or SessionBean should be 4 + 3 + @@ -1478,6 +1499,7 @@ Detects when a package definition contains uppercase characters. 3 + //PackageDeclaration/Name[lower-case(@Image)!=@Image] @@ -1531,6 +1553,7 @@ Remote Interface of a Session EJB should not have a suffix. 4 + 4 + 4 + 3 + 3 + @@ -1764,10 +1791,11 @@ which class a static member comes from (Sun 1.5 Language Guide). + $maximumStaticImports] +.[count(ImportDeclaration[@Static = true()]) > $maximumStaticImports] ]]> @@ -2003,6 +2031,7 @@ List stringsWithDiamond = new ArrayList<>(); // using the diamond operat Useless parentheses should be removed. 4 + stringsWithDiamond = new ArrayList<>(); // using the diamond operat | //Expression/ConditionalAndExpression/PrimaryExpression/PrimaryPrefix/Expression[ count(*)=1 and - count(./CastExpression)=0 and - count(./EqualityExpression/MultiplicativeExpression)=0 and - count(./ConditionalExpression)=0 and - count(./ConditionalOrExpression)=0] + not(./CastExpression) and + not(./EqualityExpression/MultiplicativeExpression) and + not(./ConditionalExpression) and + not(./ConditionalOrExpression)] | //Expression/ConditionalOrExpression/PrimaryExpression/PrimaryPrefix/Expression[ count(*)=1 and @@ -2034,11 +2063,11 @@ List stringsWithDiamond = new ArrayList<>(); // using the diamond operat not(./CastExpression) and not(./EqualityExpression)] | -//Expression/AdditiveExpression[not(./PrimaryExpression/PrimaryPrefix/Literal[@StringLiteral='true'])] +//Expression/AdditiveExpression[not(./PrimaryExpression/PrimaryPrefix/Literal[@StringLiteral= true()])] /PrimaryExpression[1]/PrimaryPrefix/Expression[ count(*)=1 and not(./CastExpression) and - not(./AdditiveExpression[@Image = '-']) and + not(./AdditiveExpression[@Operator = '-']) and not(./ShiftExpression) and not(./RelationalExpression) and not(./InstanceOfExpression) and @@ -2091,14 +2120,15 @@ public class Foo { 3 + @@ -2150,6 +2180,7 @@ E.g. `int[] x = new int[] { 1, 2, 3 };` can be written as `int[] x = { 1, 2, 3 } 3 + 3 + //WhileStatement[not(Statement/Block)] diff --git a/pmd-java/src/main/resources/category/java/design.xml b/pmd-java/src/main/resources/category/java/design.xml index 2fb4fe92d8..dba3f14cec 100644 --- a/pmd-java/src/main/resources/category/java/design.xml +++ b/pmd-java/src/main/resources/category/java/design.xml @@ -23,12 +23,14 @@ protected constructor in order to prevent instantiation than make the class misl 1 + @@ -55,6 +57,7 @@ Avoid catching generic exceptions such as NullPointerException, RuntimeException 3 + 3 + @@ -161,6 +165,7 @@ code size and runtime complexity. 3 + 1 + 1 + 3 + 1 + = 1 ] -[count(./ClassOrInterfaceBody/ClassOrInterfaceBodyDeclaration/ConstructorDeclaration[(@Public = 'true') or (@Protected = 'true') or (@PackagePrivate = 'true')]) = 0 ] +//TypeDeclaration[count(../TypeDeclaration) = 1]/ClassOrInterfaceDeclaration +[@Final = false()] +[ClassOrInterfaceBody/ClassOrInterfaceBodyDeclaration/ConstructorDeclaration[@Private = true()]] +[not(./ClassOrInterfaceBody/ClassOrInterfaceBodyDeclaration/ConstructorDeclaration[(@Public = true()) or (@Protected = true()) or (@PackagePrivate = true())])] [not(.//ClassOrInterfaceDeclaration)] ]]> @@ -367,15 +376,16 @@ Sometimes two consecutive 'if' statements can be consolidated by separating thei 3 + @@ -532,6 +542,7 @@ Errors are system exceptions. Do not extend them. 3 + 3 + @@ -845,6 +857,7 @@ Use opposite operator instead of negating the whole expression with a logic comp 3 + 3 + 3 + @@ -1297,6 +1311,7 @@ Avoid unnecessary comparisons in boolean expressions, they serve no purpose and 3 + 3 + 3 + /Block /BlockStatement[last()] [not(Statement/StatementExpression/PrimaryExpression - [./PrimaryPrefix[@SuperModifier='true']] + [./PrimaryPrefix[@SuperModifier= true()]] [./PrimarySuffix[@Image='finalize']] ) ] [not(Statement/TryStatement/FinallyStatement /Block/BlockStatement/Statement/StatementExpression/PrimaryExpression - [./PrimaryPrefix[@SuperModifier='true']] + [./PrimaryPrefix[@SuperModifier= true()]] [./PrimarySuffix[@Image='finalize']] ) ] @@ -1917,6 +1938,7 @@ If the finalize() is implemented, it should do something besides just calling su 3 + @@ -1954,6 +1976,7 @@ Note that Oracle has declared Object.finalize() as deprecated since JDK 9. 3 + 3 + @@ -2057,6 +2081,7 @@ Avoid instantiating an object just to call getClass() on it; use the .class publ 4 + 3 + @@ -2146,37 +2172,12 @@ public class JumbledIncrementerRule1 { language="java" since="1.0" message="You may have misspelled a JUnit framework method (setUp or tearDown)" - class="net.sourceforge.pmd.lang.rule.XPathRule" + class="net.sourceforge.pmd.lang.java.rule.errorprone.JUnitSpellingRule" externalInfoUrl="${pmd.website.baseurl}/pmd_rules_java_errorprone.html#junitspelling"> Some JUnit framework methods are easy to misspell. 3 - - - - - - - The suite() method in a JUnit test needs to be both public and static. 3 - - - - - - - 2 + @@ -2343,7 +2322,7 @@ Either the check is useless (the variable will never be "null") or it is incorre public class Foo { void bar() { if (a.equals(baz) && a != null) {} // a could be null, misplaced null check - + if (a != null && a.equals(baz)) {} // correct null check } } @@ -2354,7 +2333,7 @@ public class Foo { public class Foo { void bar() { if (a.equals(baz) || a == null) {} // a could be null, misplaced null check - + if (a == null || a.equals(baz)) {} // correct null check } } @@ -2374,6 +2353,7 @@ may indicate problematic behaviour. Empty cases are ignored as these indicate an 3 + @@ -2427,13 +2407,14 @@ chain needs an own serialVersionUID field. See also [Should an abstract class ha 3 + @@ -2470,14 +2451,13 @@ See the property `annotations`. 3 + //SwitchStatement//BlockStatement/Statement/LabeledStatement @@ -2684,29 +2661,12 @@ public class Foo { // perfect, both methods provided language="java" since="1.4" message="Object clone() should be implemented with super.clone()" - class="net.sourceforge.pmd.lang.rule.XPathRule" + class="net.sourceforge.pmd.lang.java.rule.errorprone.ProperCloneImplementationRule" externalInfoUrl="${pmd.website.baseurl}/pmd_rules_java_errorprone.html#propercloneimplementation"> Object clone() should be implemented with super.clone(). 2 - - - - 0 -] -]]> - - - @@ -2793,12 +2753,13 @@ NullPointerExceptions. 1 + 3 + 3 + @@ -3024,6 +2987,7 @@ new StringBuilder("A") // 1 + 16 = 17 4 + 2 + 3 + 3 + @@ -3319,14 +3285,15 @@ After checking an object reference for null, you should invoke equals() on that 3 + @@ -3385,16 +3352,21 @@ To make sure the full stacktrace is printed out, use the logging statement with 3 + @@ -3428,14 +3400,15 @@ Use the equals() method instead. 3 + @@ -3502,6 +3475,7 @@ class Test { 3 + 3 + //PrimarySuffix[@Image='getClassLoader'] diff --git a/pmd-java/src/main/resources/category/java/multithreading.xml b/pmd-java/src/main/resources/category/java/multithreading.xml index 3a26eb7a07..b70ea643db 100644 --- a/pmd-java/src/main/resources/category/java/multithreading.xml +++ b/pmd-java/src/main/resources/category/java/multithreading.xml @@ -22,8 +22,9 @@ gets it. 3 + - //MethodDeclaration[@Synchronized='true'] + //MethodDeclaration[@Synchronized= true()] @@ -65,6 +66,7 @@ it contains methods that are not thread-safe. 3 + 2 + - //FieldDeclaration[contains(@Volatile,'true')] + //FieldDeclaration[@Volatile = true()] @@ -129,6 +132,7 @@ Also EJB's might be moved between machines in a cluster and only managed resourc 3 + 4 + 3 + 3 + - \ No newline at end of file + diff --git a/pmd-java/src/main/resources/category/java/performance.xml b/pmd-java/src/main/resources/category/java/performance.xml index 7d0c75b226..8213f85f83 100644 --- a/pmd-java/src/main/resources/category/java/performance.xml +++ b/pmd-java/src/main/resources/category/java/performance.xml @@ -21,6 +21,7 @@ It is much better to use one of the type-specific toString() methods instead. 3 + 3 + 1 + 1 + 2 + 2 + 2 + 3 + 3 + 2 + 3 + 3 + 0] +//CompilationUnit[not(ImportDeclaration) or ImportDeclaration[@ImportedName='java.util.Vector']] //AllocationExpression/ClassOrInterfaceType [@Image='Vector' or @Image='java.util.Vector'] ]]> @@ -846,11 +858,12 @@ You must use new ArrayList<>(Arrays.asList(...)) if that is inconvenient for you 3 + (Arrays.asList(...)) if that is inconvenient for you @Image="ArrayList" ] )=1 - ]/@Image + ]/@Name ] and PrimaryExpression/PrimarySuffix/Arguments/ArgumentList/Expression/PrimaryExpression/PrimaryPrefix/Name [ - @Image = ancestor::MethodDeclaration[1]//LocalVariableDeclaration/VariableDeclarator/VariableDeclaratorId[@ArrayType="true"]/@Image + @Image = ancestor::MethodDeclaration[1]//LocalVariableDeclaration/VariableDeclarator/VariableDeclaratorId[@ArrayType=true()]/@Name or - @Image = ancestor::MethodDeclaration[1]//FormalParameter/VariableDeclaratorId/@Image + @Image = ancestor::MethodDeclaration[1]//FormalParameter/VariableDeclaratorId/@Name ] /../..[count(.//PrimarySuffix) =1]/PrimarySuffix/Expression/PrimaryExpression/PrimaryPrefix diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/ReportTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/ReportTest.java index cac861109b..383d64cbde 100644 --- a/pmd-java/src/test/java/net/sourceforge/pmd/ReportTest.java +++ b/pmd-java/src/test/java/net/sourceforge/pmd/ReportTest.java @@ -40,7 +40,7 @@ public class ReportTest extends RuleTst { public void testExclusionsInReportWithRuleViolationSuppressXPath() { Report rpt = new Report(); Rule rule = new FooRule(); - rule.setProperty(Rule.VIOLATION_SUPPRESS_XPATH_DESCRIPTOR, ".[@Image = 'Foo']"); + rule.setProperty(Rule.VIOLATION_SUPPRESS_XPATH_DESCRIPTOR, ".[@SimpleName = 'Foo']"); runTestFromString(TEST1, rule, rpt, defaultLanguage); assertTrue(rpt.isEmpty()); assertEquals(1, rpt.getSuppressedRuleViolations().size()); diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/metrics/xpath/XPathMetricFunctionTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/metrics/xpath/XPathMetricFunctionTest.java index 48d4c672ec..2dc75f74cc 100644 --- a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/metrics/xpath/XPathMetricFunctionTest.java +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/metrics/xpath/XPathMetricFunctionTest.java @@ -26,6 +26,7 @@ import net.sourceforge.pmd.lang.LanguageRegistry; import net.sourceforge.pmd.lang.java.JavaLanguageModule; import net.sourceforge.pmd.lang.java.xpath.MetricFunction; import net.sourceforge.pmd.lang.rule.XPathRule; +import net.sourceforge.pmd.lang.rule.xpath.XPathVersion; /** * @author Clément Fournier @@ -33,6 +34,8 @@ import net.sourceforge.pmd.lang.rule.XPathRule; */ public class XPathMetricFunctionTest { + // TODO 7.0 when removing jaxen these tests need to be updated to use pmd-java:metric + private static final String VIOLATION_MESSAGE = "violation"; @org.junit.Rule @@ -40,8 +43,7 @@ public class XPathMetricFunctionTest { private Rule makeXpathRuleFromXPath(String xpath) { - XPathRule rule = new XPathRule(); - rule.setXPath(xpath); + XPathRule rule = new XPathRule(XPathVersion.XPATH_1_0, xpath); rule.setMessage(VIOLATION_MESSAGE); rule.setLanguage(LanguageRegistry.getLanguage(JavaLanguageModule.NAME)); return rule; diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/XPathRuleTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/XPathRuleTest.java index 2a1ca7afa4..90e01fabe5 100644 --- a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/XPathRuleTest.java +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/XPathRuleTest.java @@ -1,4 +1,4 @@ -/** +/* * BSD-style license; for more info see http://pmd.sourceforge.net/license.html */ @@ -12,7 +12,6 @@ import java.util.HashMap; import java.util.Iterator; import java.util.List; -import org.junit.Before; import org.junit.Test; import net.sourceforge.pmd.PMD; @@ -35,9 +34,9 @@ import net.sourceforge.pmd.lang.rule.XPathRule; import net.sourceforge.pmd.lang.rule.xpath.JaxenXPathRuleQuery; import net.sourceforge.pmd.lang.rule.xpath.SaxonXPathRuleQuery; import net.sourceforge.pmd.lang.rule.xpath.XPathRuleQuery; +import net.sourceforge.pmd.lang.rule.xpath.XPathVersion; import net.sourceforge.pmd.properties.PropertyDescriptor; -import net.sourceforge.pmd.properties.StringMultiProperty; -import net.sourceforge.pmd.properties.StringProperty; +import net.sourceforge.pmd.properties.PropertyFactory; import net.sourceforge.pmd.testframework.RuleTst; /** @@ -45,18 +44,16 @@ import net.sourceforge.pmd.testframework.RuleTst; */ public class XPathRuleTest extends RuleTst { - XPathRule rule; - - @Before - public void setUp() { - rule = new XPathRule(); + private XPathRule makeXPath(String expression) { + XPathRule rule = new XPathRule(XPathVersion.XPATH_2_0, expression); rule.setLanguage(LanguageRegistry.getLanguage(JavaLanguageModule.NAME)); rule.setMessage("XPath Rule Failed"); + return rule; } @Test public void testPluginname() throws Exception { - rule.setXPath("//VariableDeclaratorId[string-length(@Image) < 3]"); + XPathRule rule = makeXPath("//VariableDeclaratorId[string-length(@Name) < 3]"); rule.setMessage("{0}"); Report report = getReportForTestString(rule, TEST1); RuleViolation rv = report.iterator().next(); @@ -66,15 +63,14 @@ public class XPathRuleTest extends RuleTst { @Test public void testXPathMultiProperty() throws Exception { - rule.setXPath("//VariableDeclaratorId[@Image=$forbiddenNames]"); + XPathRule rule = makeXPath("//VariableDeclaratorId[@Name=$forbiddenNames]"); rule.setMessage("Avoid vars"); - rule.setVersion(XPathRuleQuery.XPATH_2_0); - StringMultiProperty varDescriptor - = StringMultiProperty.named("forbiddenNames") - .desc("Forbidden names") - .defaultValues("forbid1", "forbid2") - .delim('$') - .build(); + PropertyDescriptor> varDescriptor + = PropertyFactory.stringListProperty("forbiddenNames") + .desc("Forbidden names") + .defaultValues("forbid1", "forbid2") + .delim('$') + .build(); rule.definePropertyDescriptor(varDescriptor); @@ -90,9 +86,10 @@ public class XPathRuleTest extends RuleTst { @Test public void testVariables() throws Exception { - rule.setXPath("//VariableDeclaratorId[@Image=$var]"); + XPathRule rule = makeXPath("//VariableDeclaratorId[@Name=$var]"); rule.setMessage("Avoid vars"); - StringProperty varDescriptor = new StringProperty("var", "Test var", null, 1.0f); + PropertyDescriptor varDescriptor = + PropertyFactory.stringProperty("var").desc("Test var").defaultValue("").build(); rule.definePropertyDescriptor(varDescriptor); rule.setProperty(varDescriptor, "fiddle"); Report report = getReportForTestString(rule, TEST2); @@ -102,8 +99,7 @@ public class XPathRuleTest extends RuleTst { @Test public void testFnPrefixOnSaxon() throws Exception { - rule.setXPath("//VariableDeclaratorId[fn:matches(@Image, 'fiddle')]"); - rule.setVersion(XPathRuleQuery.XPATH_2_0); + XPathRule rule = makeXPath("//VariableDeclaratorId[fn:matches(@Name, 'fiddle')]"); Report report = getReportForTestString(rule, TEST2); RuleViolation rv = report.iterator().next(); assertEquals(3, rv.getBeginLine()); @@ -111,8 +107,7 @@ public class XPathRuleTest extends RuleTst { @Test public void testNoFnPrefixOnSaxon() throws Exception { - rule.setXPath("//VariableDeclaratorId[matches(@Image, 'fiddle')]"); - rule.setVersion(XPathRuleQuery.XPATH_2_0); + XPathRule rule = makeXPath("//VariableDeclaratorId[matches(@Name, 'fiddle')]"); Report report = getReportForTestString(rule, TEST2); RuleViolation rv = report.iterator().next(); assertEquals(3, rv.getBeginLine()); diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/typeresolution/ClassTypeResolverTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/typeresolution/ClassTypeResolverTest.java index 57ae0deec3..94756287bb 100644 --- a/pmd-java/src/test/java/net/sourceforge/pmd/typeresolution/ClassTypeResolverTest.java +++ b/pmd-java/src/test/java/net/sourceforge/pmd/typeresolution/ClassTypeResolverTest.java @@ -452,8 +452,7 @@ public class ClassTypeResolverTest { ASTCompilationUnit acu = java5.parseClass(Promotion.class); List expressions = convertList( acu.findChildNodesWithXPath( - "//Block[preceding-sibling::MethodDeclarator[@Image = " - + "'unaryNumericPromotion']]//Expression[UnaryExpression]"), + "//MethodDeclaration[@Name = 'unaryNumericPromotion']/Block//Expression[UnaryExpression]"), ASTExpression.class); int index = 0; @@ -474,8 +473,7 @@ public class ClassTypeResolverTest { ASTCompilationUnit acu = java5.parseClass(Promotion.class); List expressions = convertList( acu.findChildNodesWithXPath( - "//Block[preceding-sibling::MethodDeclarator[@Image = " - + "'binaryNumericPromotion']]//Expression[AdditiveExpression]"), + "//MethodDeclaration[@Name = 'binaryNumericPromotion']/Block//Expression[AdditiveExpression]"), ASTExpression.class); int index = 0; @@ -545,7 +543,7 @@ public class ClassTypeResolverTest { ASTCompilationUnit acu = java5.parseClass(Promotion.class); List expressions = convertList( acu.findChildNodesWithXPath( - "//Block[preceding-sibling::MethodDeclarator[@Image = 'binaryStringPromotion']]//Expression"), + "//MethodDeclaration[@Name = 'binaryStringPromotion']/Block//Expression"), ASTExpression.class); int index = 0; @@ -564,7 +562,7 @@ public class ClassTypeResolverTest { ASTCompilationUnit acu = java5.parseClass(Operators.class); List expressions = convertList( acu.findChildNodesWithXPath( - "//Block[preceding-sibling::MethodDeclarator[@Image = 'unaryLogicalOperators']]//Expression"), + "//MethodDeclaration[@Name = 'unaryLogicalOperators']/Block//Expression"), ASTExpression.class); int index = 0; @@ -580,7 +578,7 @@ public class ClassTypeResolverTest { ASTCompilationUnit acu = java5.parseClass(Operators.class); List expressions = convertList( acu.findChildNodesWithXPath( - "//Block[preceding-sibling::MethodDeclarator[@Image = 'binaryLogicalOperators']]//Expression"), + "//MethodDeclaration[@Name = 'binaryLogicalOperators']/Block//Expression"), ASTExpression.class); int index = 0; @@ -606,24 +604,22 @@ public class ClassTypeResolverTest { public void testUnaryNumericOperators() throws JaxenException { ASTCompilationUnit acu = java5.parseClass(Operators.class); List expressions = new ArrayList<>(); + final String baseXPath = "//MethodDeclaration[@Name = 'unaryNumericOperators']/Block"; expressions.addAll(convertList( acu.findChildNodesWithXPath( - "//Block[preceding-sibling::MethodDeclarator[@Image = 'unaryNumericOperators']]//Expression"), + baseXPath + "//Expression"), TypeNode.class)); expressions.addAll(convertList( acu.findChildNodesWithXPath( - "//Block[preceding-sibling::MethodDeclarator[@Image = " - + "'unaryNumericOperators']]//PostfixExpression"), + baseXPath + "//PostfixExpression"), TypeNode.class)); expressions.addAll(convertList( acu.findChildNodesWithXPath( - "//Block[preceding-sibling::MethodDeclarator[@Image = " - + "'unaryNumericOperators']]//PreIncrementExpression"), + baseXPath + "//PreIncrementExpression"), TypeNode.class)); expressions.addAll(convertList( acu.findChildNodesWithXPath( - "//Block[preceding-sibling::MethodDeclarator[@Image = " - + "'unaryNumericOperators']]//PreDecrementExpression"), + baseXPath + "//PreDecrementExpression"), TypeNode.class)); int index = 0; @@ -643,7 +639,7 @@ public class ClassTypeResolverTest { ASTCompilationUnit acu = java5.parseClass(Operators.class); List expressions = convertList( acu.findChildNodesWithXPath( - "//Block[preceding-sibling::MethodDeclarator[@Image = 'binaryNumericOperators']]//Expression"), + "//MethodDeclaration[@Name = 'binaryNumericOperators']/Block//Expression"), ASTExpression.class); int index = 0; @@ -665,8 +661,7 @@ public class ClassTypeResolverTest { ASTCompilationUnit acu = java5.parseClass(Operators.class); List expressions = convertList( acu.findChildNodesWithXPath( - "//Block[preceding-sibling::MethodDeclarator[@Image = " - + "'assignmentOperators']]//StatementExpression"), + "//MethodDeclaration[@Name = 'assignmentOperators']/Block//StatementExpression"), ASTStatementExpression.class); int index = 0; diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/ant/classpathtest/ruleset.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/ant/classpathtest/ruleset.xml index 8b3efddecc..660898adc4 100644 --- a/pmd-java/src/test/resources/net/sourceforge/pmd/ant/classpathtest/ruleset.xml +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/ant/classpathtest/ruleset.xml @@ -19,6 +19,7 @@ Avoid jumbled loop incrementers - its usually a mistake, and is confusing even i 3 + diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/UnusedFormalParameter.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/UnusedFormalParameter.xml index ba281bbf8a..879d7d748a 100644 --- a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/UnusedFormalParameter.xml +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/UnusedFormalParameter.xml @@ -217,7 +217,7 @@ class Foo { violation suppression xpath works, by name ]]> - .[@Image = 'paramB'] + .[@Name = 'paramB'] 0 Utility class convention 1 - The utility class name 'Foo' doesn't match '[A-Z][a-zA-Z0-9]+(Utils?|Helper)' + The utility class name 'Foo' doesn't match '[A-Z][a-zA-Z0-9]+(Utils?|Helper|Constants)' Class with only static members except constructors should be a utility class 1 - The utility class name 'Foo' doesn't match '[A-Z][a-zA-Z0-9]+(Utils?|Helper)' + The utility class name 'Foo' doesn't match '[A-Z][a-zA-Z0-9]+(Utils?|Helper|Constants)' + + Utility class can have name constants + 0 + + + diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/AvoidDecimalLiteralsInBigDecimalConstructor.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/AvoidDecimalLiteralsInBigDecimalConstructor.xml index 0c20b4e280..9098d2fb3f 100644 --- a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/AvoidDecimalLiteralsInBigDecimalConstructor.xml +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/AvoidDecimalLiteralsInBigDecimalConstructor.xml @@ -4,11 +4,11 @@ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://pmd.sourceforge.net/rule-tests http://pmd.sourceforge.net/rule-tests_1_0_0.xsd"> - + bad, new BigDecimal(.1) 1 - + ok, new BigDecimal(".1") 0 ok, new BigDecimal(10) 0 - + bad, same as #1 but outside an initializer context 1 #1187 double variable with AvoidDecimalLiteralsInBigDecimalConstructor 8 - 4,8,11,14,18,22,25,28 + 6,10,13,16,20,24,27,30 diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/UseCorrectExceptionLogging.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/UseCorrectExceptionLogging.xml index cc94e27c55..6722f48456 100644 --- a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/UseCorrectExceptionLogging.xml +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/UseCorrectExceptionLogging.xml @@ -3,10 +3,9 @@ xmlns="http://pmd.sourceforge.net/rule-tests" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://pmd.sourceforge.net/rule-tests http://pmd.sourceforge.net/rule-tests_1_0_0.xsd"> + - + ok 0 + - + failure case - two calls 2 + - + must be in a catch block 0 + - + bug 1626232, the rule should not be confused by inner classes 0 + - + bug 1626232, should work with a static block 1 + + + XPath problem: A sequence of more than one item is not allowed as the first argument of concat() + 0 + + diff --git a/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/Ecmascript3Handler.java b/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/Ecmascript3Handler.java index c43f3b5255..d27138b7a3 100644 --- a/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/Ecmascript3Handler.java +++ b/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/Ecmascript3Handler.java @@ -10,9 +10,7 @@ import net.sourceforge.pmd.lang.AbstractLanguageVersionHandler; import net.sourceforge.pmd.lang.Parser; import net.sourceforge.pmd.lang.ParserOptions; import net.sourceforge.pmd.lang.VisitorStarter; -import net.sourceforge.pmd.lang.XPathHandler; import net.sourceforge.pmd.lang.ast.Node; -import net.sourceforge.pmd.lang.ast.xpath.DefaultASTXPathHandler; import net.sourceforge.pmd.lang.ecmascript.ast.DumpFacade; import net.sourceforge.pmd.lang.ecmascript.ast.EcmascriptNode; import net.sourceforge.pmd.lang.ecmascript.rule.EcmascriptRuleViolationFactory; @@ -23,11 +21,6 @@ import net.sourceforge.pmd.lang.rule.RuleViolationFactory; */ public class Ecmascript3Handler extends AbstractLanguageVersionHandler { - @Override - public XPathHandler getXPathHandler() { - return new DefaultASTXPathHandler(); - } - @Override public RuleViolationFactory getRuleViolationFactory() { return EcmascriptRuleViolationFactory.INSTANCE; diff --git a/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/ast/EcmascriptTreeBuilder.java b/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/ast/EcmascriptTreeBuilder.java index e60282d493..86cd811622 100644 --- a/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/ast/EcmascriptTreeBuilder.java +++ b/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/ast/EcmascriptTreeBuilder.java @@ -11,6 +11,7 @@ import java.util.List; import java.util.Map; import java.util.Stack; +import org.mozilla.javascript.ScriptRuntime; import org.mozilla.javascript.ast.ArrayComprehension; import org.mozilla.javascript.ast.ArrayComprehensionLoop; import org.mozilla.javascript.ast.ArrayLiteral; @@ -220,13 +221,18 @@ public final class EcmascriptTreeBuilder implements NodeVisitor { TrailingCommaNode trailingCommaNode = (TrailingCommaNode) node; int nodeStart = node.getNode().getAbsolutePosition(); int nodeEnd = nodeStart + node.getNode().getLength() - 1; + + // This will fetch the localized message + // See https://github.com/pmd/pmd/issues/384 + String trailingCommaLocalizedMessage = ScriptRuntime.getMessage0("msg.extra.trailing.comma"); + for (ParseProblem parseProblem : parseProblems) { + // The node overlaps the comma (i.e. end of the problem)? int problemStart = parseProblem.getFileOffset(); int commaPosition = problemStart + parseProblem.getLength() - 1; if (nodeStart <= commaPosition && commaPosition <= nodeEnd) { - if ("Trailing comma is not legal in an ECMA-262 object initializer" - .equals(parseProblem.getMessage())) { + if (trailingCommaLocalizedMessage.equals(parseProblem.getMessage())) { // Report on the shortest code block containing the // problem (i.e. inner most code in nested structures). EcmascriptNode currentNode = (EcmascriptNode) parseProblemToNode.get(parseProblem); diff --git a/pmd-javascript/src/main/resources/category/ecmascript/bestpractices.xml b/pmd-javascript/src/main/resources/category/ecmascript/bestpractices.xml index 52cdac1822..5b39319cd2 100644 --- a/pmd-javascript/src/main/resources/category/ecmascript/bestpractices.xml +++ b/pmd-javascript/src/main/resources/category/ecmascript/bestpractices.xml @@ -18,6 +18,7 @@ Rules which enforce generally accepted best practices. Avoid using with - it's bad news 1 + 1 + @@ -116,6 +118,7 @@ is better to explicitly scope the variable name to the nearest enclosing scope w 1 + 1 + 2 + @@ -72,6 +73,7 @@ Avoid using 'for' statements without using curly braces. 3 + 3 + @@ -146,10 +149,11 @@ Avoid using if statements without using curly braces. 3 + @@ -182,10 +186,11 @@ See also: <http://eslint.org/docs/rules/no-else-return> 3 + @@ -221,6 +226,7 @@ be misleading. Considering removing this unnecessary Block. 3 + Unnecessary parentheses should be removed. 4 + 1 + 3 + 1 + @@ -57,14 +58,14 @@ same type. The === operator avoids the casting. 3 + @@ -104,10 +105,11 @@ precision in a floating point number. This may result in numeric calculations b 2 + diff --git a/pmd-javascript/src/test/java/net/sourceforge/pmd/lang/ecmascript/ast/DefaultLocale.java b/pmd-javascript/src/test/java/net/sourceforge/pmd/lang/ecmascript/ast/DefaultLocale.java new file mode 100644 index 0000000000..e0cb18f4a5 --- /dev/null +++ b/pmd-javascript/src/test/java/net/sourceforge/pmd/lang/ecmascript/ast/DefaultLocale.java @@ -0,0 +1,56 @@ +/* + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.ecmascript.ast; + +import java.util.Locale; + +import org.junit.rules.TestRule; +import org.junit.runner.Description; +import org.junit.runners.model.Statement; + +/** + * A JUnit rule to change the system locale during a test. + */ +public class DefaultLocale implements TestRule { + + private boolean statementIsExecuting = false; + private Locale loc = Locale.getDefault(); + + /** Set the locale value (overwrites previously set value). */ + public void set(Locale locale) { + if (statementIsExecuting) { + Locale.setDefault(locale); + } else { + this.loc = locale; + } + } + + @Override + public Statement apply(Statement base, Description description) { + return new EnvironmentVariablesStatement(base); + } + + private class EnvironmentVariablesStatement extends Statement { + + final Statement baseStatement; + + EnvironmentVariablesStatement(Statement baseStatement) { + this.baseStatement = baseStatement; + } + + @Override + public void evaluate() throws Throwable { + Locale prev = Locale.getDefault(); + statementIsExecuting = true; + try { + Locale.setDefault(loc); + baseStatement.evaluate(); + } finally { + statementIsExecuting = false; + Locale.setDefault(prev); + } + } + } +} diff --git a/pmd-javascript/src/test/java/net/sourceforge/pmd/lang/ecmascript/ast/TrailingCommaTest.java b/pmd-javascript/src/test/java/net/sourceforge/pmd/lang/ecmascript/ast/TrailingCommaTest.java new file mode 100644 index 0000000000..82318930b2 --- /dev/null +++ b/pmd-javascript/src/test/java/net/sourceforge/pmd/lang/ecmascript/ast/TrailingCommaTest.java @@ -0,0 +1,41 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.ecmascript.ast; + +import java.util.Locale; + +import org.junit.Assert; +import org.junit.Rule; +import org.junit.Test; + +public class TrailingCommaTest extends EcmascriptParserTestBase { + + @Rule + public DefaultLocale defaultLocale = new DefaultLocale(); + + + @Test + public void testTrailingCommaDefaultLocale() { + testTrailingComma(); + } + + @Test + public void testTrailingCommaFrFr() { + defaultLocale.set(Locale.FRANCE); + testTrailingComma(); + } + + @Test + public void testTrailingCommaRootLocale() { + defaultLocale.set(Locale.ROOT); + testTrailingComma(); + } + + public void testTrailingComma() { + ASTAstRoot node = js.parse("x = {a : 1, };\n"); + ASTObjectLiteral fn = node.getFirstDescendantOfType(ASTObjectLiteral.class); + Assert.assertTrue(fn.isTrailingComma()); + } +} diff --git a/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/JspHandler.java b/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/JspHandler.java index 8c1dd425e5..f1184c8f23 100644 --- a/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/JspHandler.java +++ b/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/JspHandler.java @@ -10,9 +10,7 @@ import net.sourceforge.pmd.lang.AbstractLanguageVersionHandler; import net.sourceforge.pmd.lang.Parser; import net.sourceforge.pmd.lang.ParserOptions; import net.sourceforge.pmd.lang.VisitorStarter; -import net.sourceforge.pmd.lang.XPathHandler; import net.sourceforge.pmd.lang.ast.Node; -import net.sourceforge.pmd.lang.ast.xpath.DefaultASTXPathHandler; import net.sourceforge.pmd.lang.jsp.ast.DumpFacade; import net.sourceforge.pmd.lang.jsp.ast.JspNode; import net.sourceforge.pmd.lang.jsp.rule.JspRuleViolationFactory; @@ -25,11 +23,6 @@ import net.sourceforge.pmd.lang.rule.RuleViolationFactory; */ public class JspHandler extends AbstractLanguageVersionHandler { - @Override - public XPathHandler getXPathHandler() { - return new DefaultASTXPathHandler(); - } - @Override public RuleViolationFactory getRuleViolationFactory() { return JspRuleViolationFactory.INSTANCE; diff --git a/pmd-jsp/src/main/resources/category/jsp/bestpractices.xml b/pmd-jsp/src/main/resources/category/jsp/bestpractices.xml index 564c645975..b98e02c1a9 100644 --- a/pmd-jsp/src/main/resources/category/jsp/bestpractices.xml +++ b/pmd-jsp/src/main/resources/category/jsp/bestpractices.xml @@ -20,6 +20,7 @@ Do not nest JSF component custom actions inside a custom action that iterates ov 3 + 2 + 2 + 3 + 3 + 2 + 3 + 3 + 2 + 3 + 3 + 3 + 3 + 3 + 3 + 3 + 3 + " - \ No newline at end of file + diff --git a/pmd-visualforce/src/main/java/net/sourceforge/pmd/lang/vf/VfHandler.java b/pmd-visualforce/src/main/java/net/sourceforge/pmd/lang/vf/VfHandler.java index afaf88eedd..e40d90f8ab 100644 --- a/pmd-visualforce/src/main/java/net/sourceforge/pmd/lang/vf/VfHandler.java +++ b/pmd-visualforce/src/main/java/net/sourceforge/pmd/lang/vf/VfHandler.java @@ -10,9 +10,7 @@ import net.sourceforge.pmd.lang.AbstractLanguageVersionHandler; import net.sourceforge.pmd.lang.Parser; import net.sourceforge.pmd.lang.ParserOptions; import net.sourceforge.pmd.lang.VisitorStarter; -import net.sourceforge.pmd.lang.XPathHandler; import net.sourceforge.pmd.lang.ast.Node; -import net.sourceforge.pmd.lang.ast.xpath.DefaultASTXPathHandler; import net.sourceforge.pmd.lang.rule.RuleViolationFactory; import net.sourceforge.pmd.lang.vf.ast.DumpFacade; import net.sourceforge.pmd.lang.vf.ast.VfNode; @@ -20,11 +18,6 @@ import net.sourceforge.pmd.lang.vf.rule.VfRuleViolationFactory; public class VfHandler extends AbstractLanguageVersionHandler { - @Override - public XPathHandler getXPathHandler() { - return new DefaultASTXPathHandler(); - } - @Override public RuleViolationFactory getRuleViolationFactory() { return VfRuleViolationFactory.INSTANCE; diff --git a/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/VmHandler.java b/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/VmHandler.java index c3cf2a665a..d3f5e54e7c 100644 --- a/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/VmHandler.java +++ b/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/VmHandler.java @@ -10,9 +10,7 @@ import net.sourceforge.pmd.lang.AbstractLanguageVersionHandler; import net.sourceforge.pmd.lang.Parser; import net.sourceforge.pmd.lang.ParserOptions; import net.sourceforge.pmd.lang.VisitorStarter; -import net.sourceforge.pmd.lang.XPathHandler; import net.sourceforge.pmd.lang.ast.Node; -import net.sourceforge.pmd.lang.ast.xpath.DefaultASTXPathHandler; import net.sourceforge.pmd.lang.rule.RuleViolationFactory; import net.sourceforge.pmd.lang.vm.ast.AbstractVmNode; import net.sourceforge.pmd.lang.vm.rule.VmRuleViolationFactory; @@ -23,11 +21,6 @@ import net.sourceforge.pmd.lang.vm.rule.VmRuleViolationFactory; */ public class VmHandler extends AbstractLanguageVersionHandler { - @Override - public XPathHandler getXPathHandler() { - return new DefaultASTXPathHandler(); - } - @Override public RuleViolationFactory getRuleViolationFactory() { return VmRuleViolationFactory.INSTANCE; diff --git a/pmd-vm/src/main/resources/category/vm/design.xml b/pmd-vm/src/main/resources/category/vm/design.xml index 7bea5920f7..d5d65f4f20 100644 --- a/pmd-vm/src/main/resources/category/vm/design.xml +++ b/pmd-vm/src/main/resources/category/vm/design.xml @@ -67,6 +67,7 @@ Avoid inline styles. Use css classes instead. 2 + 3 + 3 + diff --git a/pmd-xml/src/main/resources/category/xml/errorprone.xml b/pmd-xml/src/main/resources/category/xml/errorprone.xml index 46f598d8e4..48f7cceea2 100644 --- a/pmd-xml/src/main/resources/category/xml/errorprone.xml +++ b/pmd-xml/src/main/resources/category/xml/errorprone.xml @@ -20,6 +20,7 @@ An XML CDATA section begins with a <![CDATA[ marker, which has only one [, an 3 + 3 +