diff --git a/.gitignore b/.gitignore index 5edd8ada36..69b2c1e6d7 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,5 @@ bin/ *.patch */src/site/site.xml pmd-core/dependency-reduced-pom.xml +.bundle +vendor diff --git a/.travis.yml b/.travis.yml index 7c5909a066..a52bb4c806 100644 --- a/.travis.yml +++ b/.travis.yml @@ -29,8 +29,12 @@ before_install: - wget https://github.com/sormuras/bach/raw/master/install-jdk.sh - bash .travis/setup-secrets.sh - bash .travis/configure-maven.sh + - rvm install 2.4.1 + - rvm use 2.4.1 # Install OracleJDK 10 - see https://sormuras.github.io/blog/2018-03-20-jdk-matrix.html -install: . ./install-jdk.sh -F 10 -L BCL +install: + - . ./install-jdk.sh -F 10 -L BCL + - bundle install --without=release_notes_preprocessing before_script: true script: source .travis/build-$BUILD.sh after_success: true @@ -67,6 +71,7 @@ notifications: cache: directories: - "$HOME/.m2" + - vendor/bundle # Secure Keys, that need to be set for snapshot builds @@ -79,7 +84,6 @@ cache: # PMD_SF_USER - the sourceforge user, which is used to upload created binaries to sf files section. Note: an ssh key is # required. See "before_install". # -# # Secure Keys, that need to be set for releases: # # PMD_SF_APIKEY - used to make the new release the default file in the files section. See https://sourceforge.net/auth/preferences/ diff --git a/.travis/all-java.xml b/.travis/all-java.xml new file mode 100644 index 0000000000..bb560b5be1 --- /dev/null +++ b/.travis/all-java.xml @@ -0,0 +1,18 @@ + + + + Every java rule in PMD which is used for the regression tests with pmdtester + + + + + + + + + + + diff --git a/.travis/build-deploy.sh b/.travis/build-deploy.sh index 526e1953bf..bad3f4eb92 100755 --- a/.travis/build-deploy.sh +++ b/.travis/build-deploy.sh @@ -27,8 +27,23 @@ TRAVIS_COMMIT_RANGE=${TRAVIS_COMMIT_RANGE}" fi } +function upload_baseline() { + log_info "Generating and uploading baseline for pmdtester..." + cd .. + pmdtester -m single -r ./pmd -p ${TRAVIS_BRANCH} -pc ./pmd/.travis/all-java.xml -l ./pmd/.travis/project-list.xml -f + cd target/reports + BRANCH_FILENAME="${TRAVIS_BRANCH/\//_}" + zip -q -r ${BRANCH_FILENAME}-baseline.zip ${BRANCH_FILENAME}/ + rsync -avh ${BRANCH_FILENAME}-baseline.zip ${PMD_SF_USER}@web.sourceforge.net:/home/frs/project/pmd/pmd-regression-tester/ + if [ $? -ne 0 ]; then + log_error "Error while uploading ${BRANCH_FILENAME}-baseline.zip to sourceforge!" + log_error "Please upload manually: https://sourceforge.net/projects/pmd/files/pmd-regression-tester/" + else + log_success "Successfully uploaded ${BRANCH_FILENAME}-baseline.zip to sourceforge" + fi +} -VERSION=$(./mvnw -q -Dexec.executable="echo" -Dexec.args='${project.version}' --non-recursive org.codehaus.mojo:exec-maven-plugin:1.5.0:exec | tail -1) +VERSION=$(./mvnw -q -Dexec.executable="echo" -Dexec.args='${project.version}' --non-recursive org.codehaus.mojo:exec-maven-plugin:1.5.0:exec) log_info "Building PMD ${VERSION} on branch ${TRAVIS_BRANCH}" MVN_BUILD_FLAGS="-B -V" @@ -37,6 +52,11 @@ if travis_isPullRequest; then log_info "This is a pull-request build" ./mvnw verify $MVN_BUILD_FLAGS + ( + set +e + log_info "Running danger" + bundle exec danger --verbose + ) elif travis_isPush; then @@ -77,6 +97,9 @@ elif travis_isPush; then else log_success "Successfully uploaded release_notes.md as ReadMe.md to sourceforge" fi + + upload_baseline + true ) diff --git a/.travis/project-list.xml b/.travis/project-list.xml new file mode 100644 index 0000000000..5471d6f2e3 --- /dev/null +++ b/.travis/project-list.xml @@ -0,0 +1,27 @@ + + + + Standard Projects + + + checkstyle + git + https://github.com/checkstyle/checkstyle + checkstyle-8.10 + + + + spring-framework + git + https://github.com/spring-projects/spring-framework + v5.0.6.RELEASE + + + + diff --git a/.travis/release.sh b/.travis/release.sh index c6f9e12a62..883f9721d4 100755 --- a/.travis/release.sh +++ b/.travis/release.sh @@ -28,11 +28,16 @@ if [ "${BUILD}" = "deploy" ]; then true ) +# install the gems required for rendering the release notes +bundle install --with=release_notes_preprocessing + +# renders, and skips the first 6 lines - the Jekyll front-matter +RENDERED_RELEASE_NOTES=$(bundle exec .travis/render_release_notes.rb docs/pages/release_notes.md | tail -n +6) # Assumes, the release has already been created by travis github releases provider RELEASE_ID=$(curl -s -H "Authorization: token ${GITHUB_OAUTH_TOKEN}" https://api.github.com/repos/pmd/pmd/releases/tags/pmd_releases/${RELEASE_VERSION}|jq ".id") RELEASE_NAME="PMD ${RELEASE_VERSION} ($(date -u +%d-%B-%Y))" -RELEASE_BODY=$(tail -n +6 docs/pages/release_notes.md) # skips the first 6 lines - the heading 'PMD Release Notes' +RELEASE_BODY="$RENDERED_RELEASE_NOTES" RELEASE_BODY="${RELEASE_BODY//'\'/\\\\}" RELEASE_BODY="${RELEASE_BODY//$'\r'/}" RELEASE_BODY="${RELEASE_BODY//$'\n'/\\r\\n}" @@ -79,6 +84,7 @@ mkdir pmd.github.io git pull --depth=1 origin master log_info "Copying documentation from ../docs/pmd-doc-${RELEASE_VERSION}/ ..." rsync -ah --stats ../docs/pmd-doc-${RELEASE_VERSION}/ pmd-${RELEASE_VERSION}/ + git status git add pmd-${RELEASE_VERSION} git commit -q -m "Added pmd-${RELEASE_VERSION}" @@ -87,6 +93,7 @@ mkdir pmd.github.io git add latest git commit -q -m "Copying pmd-${RELEASE_VERSION} to latest" + log_info "Generating sitemap.xml" ../.travis/sitemap_generator.sh > sitemap.xml git add sitemap.xml git commit -q -m "Generated sitemap.xml" @@ -103,7 +110,7 @@ mkdir pmd.github.io log_info "Uploading the new release to pmd.sourceforge.net which serves as an archive..." - travis_wait rsync -ah --stats pmd-doc-${VERSION}/ ${PMD_SF_USER}@web.sourceforge.net:/home/project-web/pmd/htdocs/pmd-${RELEASE_VERSION}/ + travis_wait rsync -ah --stats docs/pmd-doc-${RELEASE_VERSION}/ ${PMD_SF_USER}@web.sourceforge.net:/home/project-web/pmd/htdocs/pmd-${RELEASE_VERSION}/ if [ $? -ne 0 ]; then log_error "Uploading documentation to pmd.sourceforge.net failed..." diff --git a/.travis/render_release_notes.rb b/.travis/render_release_notes.rb new file mode 100755 index 0000000000..d8acab0e74 --- /dev/null +++ b/.travis/render_release_notes.rb @@ -0,0 +1,42 @@ +#!/usr/bin/env ruby + +# Renders the release notes for Github releases, +# and prints them to standard output + +# Doesn't trim the header, which is done in shell + +# Args: +# ARGV[0] : location of the file to render + +require "liquid" +require "safe_yaml" + +# include some custom liquid extensions +require_relative "../docs/_plugins/rule_tag" +require_relative "../docs/_plugins/custom_filters" + +# explicitly setting safe mode to get rid of the warning +SafeYAML::OPTIONS[:default_mode] = :safe + +# START OF THE SCRIPT + +unless ARGV.length == 1 && File.exists?(ARGV[0]) + print "\e[31m[ERROR] In #{$0}: The first arg must be a valid file name\e[0m" + exit 1 +end + +release_notes_file = ARGV[0] + +liquid_env = { + # wrap the config under a "site." namespace because that's how jekyll does it + 'site' => YAML.load_file("docs/_config.yml"), + # This signals the links in {% rule %} tags that they should be rendered as absolute + 'is_release_notes_processor' => true +} + + +to_render = File.read(release_notes_file) +rendered = Liquid::Template.parse(to_render).render(liquid_env) + + +print(rendered) diff --git a/Dangerfile b/Dangerfile new file mode 100644 index 0000000000..07a9aef9a2 --- /dev/null +++ b/Dangerfile @@ -0,0 +1,57 @@ +require 'pmdtester' +require 'time' +require 'logger' + +@logger = Logger.new(STDOUT) + +def run_pmdtester + Dir.chdir('..') do + argv = ['-r', './pmd', '-b', "#{ENV['TRAVIS_BRANCH']}", '-p', 'FETCH_HEAD', '-m', 'online', '-a'] + Process.fork do + begin + runner = PmdTester::Runner.new(argv) + introduce_new_pmd_errors = runner.run + warn("The PR may introduce new PMD errors!") if introduce_new_pmd_errors + rescue StandardError => e + warn("Running pmdtester failed, this message is mainly used to remind the maintainers of PMD.") + @logger.error "Running pmdtester failed: #{e.inspect}" + exit 1 + end + end + Process.wait + + upload_report if $?.success? + end +end + +def upload_report + Dir.chdir('target/reports') do + tar_filename = "pr-#{ENV['TRAVIS_PULL_REQUEST']}-diff-report-#{Time.now.strftime("%Y-%m-%dT%H-%M-%SZ")}.tar" + unless Dir.exist?('diff/') + message("No java rules are changed!", sticky: true) + return + end + + `tar -cf #{tar_filename} diff/` + report_url = `curl -u #{ENV['CHUNK_TOKEN']} -T #{tar_filename} chunk.io` + if $?.success? + @logger.info "Successfully uploaded #{tar_filename} to chunk.io" + + # set value of sticky to true and the message is kept after new commits are submited to the PR + message("Please check the [regression diff report](#{report_url.chomp}/diff/index.html) to make sure that everything is expected", sticky: true) + else + @logger.error "Error while uploading #{tar_filename} to chunk.io: #{report_url}" + warn("Uploading the diff report failed, this message is mainly used to remind the maintainers of PMD.") + end + end +end + +# Perform regression testing +can_merge = github.pr_json['mergeable'] +if can_merge + run_pmdtester +else + warn("This PR cannot be merged yet.", sticky: false) +end + +# vim: syntax=ruby diff --git a/Gemfile b/Gemfile new file mode 100644 index 0000000000..ed76d93bfe --- /dev/null +++ b/Gemfile @@ -0,0 +1,12 @@ +source 'https://rubygems.org/' + +gem 'pmdtester', '~> 1.0.0.pre.beta3' +gem 'danger', '~> 5.6', '>= 5.6' + +# This group is only needed during release (via .travis/release.sh and do-release.sh) +group :release_notes_preprocessing do + gem 'liquid', '>=4.0.0' + gem 'safe_yaml', '>=1.0' +end + +# vim: syntax=ruby diff --git a/do-release.sh b/do-release.sh index 41a858b6fc..5417ffd455 100755 --- a/do-release.sh +++ b/do-release.sh @@ -62,19 +62,20 @@ echo "Press enter to continue..." read +# install bundles needed for rendering release notes +bundle install with=release_notes_preprocessing --path vendor/bundle + + export RELEASE_VERSION export DEVELOPMENT_VERSION export CURRENT_BRANCH RELEASE_RULESET="pmd-core/src/main/resources/rulesets/releases/${RELEASE_VERSION//\./}.xml" -echo "* Update version/release info in **docs/pages/release_notes.md**." -echo -echo " ## $(date -u +%d-%B-%Y) - ${RELEASE_VERSION}" -echo echo "* Update date info in **docs/_config.yml**." +echo " date: $(date -u +%d-%B-%Y)" echo -echo "* Ensure all the new rules are listed in a the proper file:" +echo "* Ensure all the new rules are listed in the proper file:" echo " ${RELEASE_RULESET}" echo echo "* Update **../pmd.github.io/_config.yml** to mention the new release" @@ -109,18 +110,6 @@ echo echo "Tag has been pushed.... now check travis build: " echo echo -echo "Submit news to SF on page. You can use" -echo "the following template:" -echo -cat < docs/pages/release_notes_old.md echo "$NEW_RELEASE_NOTES" >> docs/pages/release_notes_old.md echo >> docs/pages/release_notes_old.md @@ -154,11 +143,11 @@ permalink: pmd_release_notes.html keywords: changelog, release notes --- -## ????? - ${DEVELOPMENT_VERSION} +## {{ site.pmd.date }} - {{ site.pmd.version | append_unless: is_release_version, "-SNAPSHOT" }} -The PMD team is pleased to announce PMD ${DEVELOPMENT_VERSION%-SNAPSHOT}. +The PMD team is pleased to announce PMD {{ site.pmd.version }}. -This is a minor release. +This is a {{ site.pmd.release_type }} release. ### Table Of Contents @@ -185,15 +174,16 @@ echo echo echo "Verify the new release on github: " echo +echo "* Submit news to SF on page. Use same text as in the email below." +echo "* Send out an announcement mail to the mailing list:" echo -echo "Send out an announcement mail to the mailing list:" echo "To: PMD Developers List " echo "Subject: [ANNOUNCE] PMD ${RELEASE_VERSION} Released" echo echo " * Downloads: https://github.com/pmd/pmd/releases/tag/pmd_releases%2F${RELEASE_VERSION}" echo " * Documentation: https://pmd.github.io/pmd-${RELEASE_VERSION}/" echo -echo " And Copy-Paste the release notes" +echo "$NEW_RELEASE_NOTES" echo echo echo @@ -203,4 +193,3 @@ echo "------------------------------------------" echo - diff --git a/docs/_config.yml b/docs/_config.yml index 10a16227aa..86838109bd 100644 --- a/docs/_config.yml +++ b/docs/_config.yml @@ -1,8 +1,9 @@ repository: pmd/pmd pmd: - version: 6.6.0 + version: 6.7.0 date: 2018-??-?? + release_type: minor output: web # this property is useful for conditional filtering of content that is separate from the PDF. diff --git a/docs/_data/sidebars/pmd_sidebar.yml b/docs/_data/sidebars/pmd_sidebar.yml index 760a09fc4f..7e01f5c0c4 100644 --- a/docs/_data/sidebars/pmd_sidebar.yml +++ b/docs/_data/sidebars/pmd_sidebar.yml @@ -325,6 +325,9 @@ entries: - title: How PMD works url: /pmd_devdocs_how_pmd_works.html output: web, pdf + - title: Pmdtester + url: /pmd_devdocs_pmdtester.html + output: web, pdf - title: null output: web, pdf subfolders: diff --git a/docs/_plugins/custom_filters.rb b/docs/_plugins/custom_filters.rb index 3d77f8af0c..42ca4b0fef 100644 --- a/docs/_plugins/custom_filters.rb +++ b/docs/_plugins/custom_filters.rb @@ -68,6 +68,19 @@ module CustomFilters end end + # Append the suffix only if the condition argument is truthy + def append_if(str, condition, suffix) + if condition + str + suffix + else + str + end + end + + def append_unless(str, condition, suffix) + append_if(str, !condition, suffix) + end + def render_markdown(input) if input res = input diff --git a/docs/_plugins/rule_tag.rb b/docs/_plugins/rule_tag.rb new file mode 100644 index 0000000000..0e81e64bdd --- /dev/null +++ b/docs/_plugins/rule_tag.rb @@ -0,0 +1,73 @@ + + +# Tag to reference a rule +# +# Usage: +# {% rule "java/codestyle/LinguisticNaming" %} works from anywhere +# If inside the doc page of a ruleset/category, the language and +# category segment can be dropped, they're taken to be the same. +# +# That means rule descriptions can also reference rules e.g. by simply +# saying {% rule AvoidFinalLocalVars %} if they're in the same category +# This could allow deprecated rule notices to link to the replacement rule + +class RuleTag < Liquid::Tag + def initialize(tag_name, rule_ref, tokens) + super + + if %r!(?:(?:(\w+)/)?(\w+)/)?(\w+)! =~ rule_ref + + @lang_name = $1 + @category_name = $2 + @rule_name = $3 + + else + fail "Invalid rule reference format" + end + + end + + def render(context) + + + + if /pmd_rules_(\w+)_(\w+)\.html/ =~ context["page.permalink"] + # If we're in a page describing a ruleset, + # omitted language or category are taken to be that of this page + @lang_name = @lang_name || $1 + @category_name = @category_name || $2 + end + + + unless @category_name + fail "no category for rule reference, and no implicit category name available" + end + + unless @lang_name + fail "no language for rule reference, and no implicit language name available" + end + + + url_prefix = "" + # This is passed from the release notes processing script + # When generating links for the release notes, the links should be absolute + if context["is_release_notes_processor"] + url_prefix = "https://pmd.github.io/pmd-#{context["site.pmd.version"]}/" + end + + markup_link(@rule_name, url_prefix + relativelink(@lang_name, @category_name, @rule_name)) + end + + private + + def relativelink(lang, cat, rname) + "pmd_rules_#{lang}_#{cat}.html##{rname.downcase}" + end + + def markup_link(rname, link) + "[`#{rname}`](#{link})" + end + +end + +Liquid::Template.register_tag('rule', RuleTag) diff --git a/docs/pages/pmd/devdocs/pmdtester.md b/docs/pages/pmd/devdocs/pmdtester.md new file mode 100644 index 0000000000..407ddd6de5 --- /dev/null +++ b/docs/pages/pmd/devdocs/pmdtester.md @@ -0,0 +1,24 @@ +--- +title: Pmdtester +tags: [devdocs] +permalink: pmd_devdocs_pmdtester.html +author: Binguo Bao +--- + +## Introduction +Pmdtester is a regression testing tool that ensures no new problems and unexpected behaviors will be introduced to PMD after fixing an issue. +It can also be used to verify, that new rules work as expected.It has been integrated into travis CI and is actually used automatically for PRs. +Regression difference reports are commented back to the PR for the reviewer's information e.g. https://github.com/pmd/pmd/pull/1265#issuecomment-408945709 + +## Run pmdtester locally +**Install pmdtester** + +`gem install pmdtester --pre` + +**Verifying your local changes and generate a diff-report locally** + +`pmdtester -r YOUR_LOCAL_PMD_GIT_REPO_ROOT_DIR -b master -p YOUR_DEVELOPMENT_BRANCH` + +The regression difference report is placed in the `YOUR_WORKING_DIR/target/reports/diff` directory. + +For more documentation on pmdtester, see [README.rdoc](https://github.com/pmd/pmd-regression-tester/blob/master/README.rdoc) diff --git a/docs/pages/pmd/projectdocs/committers/releasing.md b/docs/pages/pmd/projectdocs/committers/releasing.md index 622b88fe93..5800289cac 100644 --- a/docs/pages/pmd/projectdocs/committers/releasing.md +++ b/docs/pages/pmd/projectdocs/committers/releasing.md @@ -25,12 +25,9 @@ Make sure code is up to date and everything is committed and pushed with git: ### The Release Notes and docs -At a very minimum, the current date must be noted in the release notes and the download section. Also, the version -must be adjusted. E.g. by removing "-SNAPSHOT". - You can find the release notes here: `docs/pages/release_notes.md`. -The date for the download section is to be entered in `docs/_config.yml`, e.g. +The date and the version must be updated in `docs/_config.yml`, e.g. ``` pmd: @@ -140,6 +137,7 @@ the following template: * Move version/release info from **docs/pages/release_notes.md** to **docs/pages/release_notes_old.md**. * Update version/release info in **docs/pages/release_notes.md**. Use the following template: +{%raw%} ``` --- title: PMD Release Notes @@ -147,11 +145,11 @@ permalink: pmd_release_notes.html keywords: changelog, release notes --- -## ????? - ${DEVELOPMENT_VERSION} +## {{ site.pmd.date }} - {{ site.pmd.version | append_unless: is_release_version, "-SNAPSHOT" }} -The PMD team is pleased to announce PMD ${DEVELOPMENT_VERSION%-SNAPSHOT}. +The PMD team is pleased to announce PMD {{ site.pmd.version }}. -This is a bug fixing release. +This is a {{ site.pmd.release_type }} release. ### Table Of Contents @@ -169,6 +167,8 @@ This is a bug fixing release. ### External Contributions ``` +{%endraw%} + Commit and push diff --git a/docs/pages/pmd/rules/java.md b/docs/pages/pmd/rules/java.md index 2ead22586f..48387179af 100644 --- a/docs/pages/pmd/rules/java.md +++ b/docs/pages/pmd/rules/java.md @@ -28,7 +28,7 @@ folder: pmd/rules * [JUnit4TestShouldUseBeforeAnnotation](pmd_rules_java_bestpractices.html#junit4testshouldusebeforeannotation): In JUnit 3, the setUp method was used to set up all data entities required in running tests. JUni... * [JUnit4TestShouldUseTestAnnotation](pmd_rules_java_bestpractices.html#junit4testshouldusetestannotation): In JUnit 3, the framework executed all methods which started with the word test as a unit test. I... * [JUnitAssertionsShouldIncludeMessage](pmd_rules_java_bestpractices.html#junitassertionsshouldincludemessage): JUnit assertions should include an informative message - i.e., use the three-argument version of ... -* [JUnitTestContainsTooManyAsserts](pmd_rules_java_bestpractices.html#junittestcontainstoomanyasserts): JUnit tests should not contain too many asserts. Many asserts are indicative of a complex test, ... +* [JUnitTestContainsTooManyAsserts](pmd_rules_java_bestpractices.html#junittestcontainstoomanyasserts): Unit tests should not contain too many asserts. Many asserts are indicative of a complex test, fo... * [JUnitTestsShouldIncludeAssert](pmd_rules_java_bestpractices.html#junittestsshouldincludeassert): JUnit tests should include at least one assertion. This makes the tests more robust, and using a... * [JUnitUseExpected](pmd_rules_java_bestpractices.html#junituseexpected): In JUnit4, use the @Test(expected) annotation to denote tests that should throw exceptions. * [LooseCoupling](pmd_rules_java_bestpractices.html#loosecoupling): The use of implementation types (i.e., HashSet) as object references limits your ability to use a... @@ -79,6 +79,7 @@ folder: pmd/rules * [EmptyMethodInAbstractClassShouldBeAbstract](pmd_rules_java_codestyle.html#emptymethodinabstractclassshouldbeabstract): Empty or auto-generated methods in an abstract class should be tagged as abstract. This helps to ... * [ExtendsObject](pmd_rules_java_codestyle.html#extendsobject): No need to explicitly extend Object. * [FieldDeclarationsShouldBeAtStartOfClass](pmd_rules_java_codestyle.html#fielddeclarationsshouldbeatstartofclass): Fields should be declared at the top of the class, before any method declarations, constructors, ... +* [FieldNamingConventions](pmd_rules_java_codestyle.html#fieldnamingconventions): Configurable naming conventions for field declarations. This rule reports variable declarations ... * [ForLoopShouldBeWhileLoop](pmd_rules_java_codestyle.html#forloopshouldbewhileloop): Some for loops can be simplified to while loops, this makes them more concise. * [ForLoopsMustUseBraces](pmd_rules_java_codestyle.html#forloopsmustusebraces): Deprecated Avoid using 'for' statements without using curly braces. If the code formatting or indentation is... * [FormalParameterNamingConventions](pmd_rules_java_codestyle.html#formalparameternamingconventions): Configurable naming conventions for formal parameters of methods and lambdas. This rul... @@ -86,6 +87,7 @@ folder: pmd/rules * [IdenticalCatchBranches](pmd_rules_java_codestyle.html#identicalcatchbranches): Identical 'catch' branches use up vertical space and increase the complexity of code without ... * [IfElseStmtsMustUseBraces](pmd_rules_java_codestyle.html#ifelsestmtsmustusebraces): Deprecated Avoid using if..else statements without using surrounding braces. If the code formatting or inden... * [IfStmtsMustUseBraces](pmd_rules_java_codestyle.html#ifstmtsmustusebraces): Deprecated Avoid using if statements without using braces to surround the code block. If the code formatting... +* [LinguisticNaming](pmd_rules_java_codestyle.html#linguisticnaming): This rule finds Linguistic Naming Antipatterns. It checks for fields, that are named, as if they ... * [LocalHomeNamingConvention](pmd_rules_java_codestyle.html#localhomenamingconvention): The Local Home interface of a Session EJB should be suffixed by 'LocalHome'. * [LocalInterfaceSessionNamingConvention](pmd_rules_java_codestyle.html#localinterfacesessionnamingconvention): The Local Interface of a Session EJB should be suffixed by 'Local'. * [LocalVariableCouldBeFinal](pmd_rules_java_codestyle.html#localvariablecouldbefinal): A local variable assigned only once can be declared final. diff --git a/docs/pages/pmd/rules/java/bestpractices.md b/docs/pages/pmd/rules/java/bestpractices.md index 9b76e94176..56759de176 100644 --- a/docs/pages/pmd/rules/java/bestpractices.md +++ b/docs/pages/pmd/rules/java/bestpractices.md @@ -456,8 +456,8 @@ through the @RunWith(Suite.class) annotation. **This rule is defined by the following XPath expression:** ``` xpath //ClassOrInterfaceBodyDeclaration[MethodDeclaration/MethodDeclarator[@Image='suite']] -[MethodDeclaration/ResultType/Type/ReferenceType/ClassOrInterfaceType[@Image='Test' or @Image = 'junit.framework.Test']] -[not(MethodDeclaration/Block//ClassOrInterfaceType[@Image='JUnit4TestAdapter'])] +[MethodDeclaration/ResultType/Type/ReferenceType/ClassOrInterfaceType[pmd-java:typeIs('junit.framework.Test')]] +[not(MethodDeclaration/Block//ClassOrInterfaceType[pmd-java:typeIs('junit.framework.JUnit4TestAdapter')])] ``` **Example(s):** @@ -488,13 +488,18 @@ public class GoodTest { **Priority:** Medium (3) In JUnit 3, the tearDown method was used to clean up all data entities required in running tests. -JUnit 4 skips the tearDown method and executes all methods annotated with @After after running each test +JUnit 4 skips the tearDown method and executes all methods annotated with @After after running each test. +JUnit 5 introduced @AfterEach and @AfterAll annotations to execute methods after each test or after all tests in the class, respectively. **This rule is defined by the following XPath expression:** ``` xpath -//CompilationUnit[not(ImportDeclaration/Name[starts-with(@Image, "org.testng")])] -//ClassOrInterfaceBodyDeclaration[MethodDeclaration/MethodDeclarator[@Image='tearDown']] -[count(Annotation//Name[@Image='After'])=0] +//ClassOrInterfaceBodyDeclaration + [MethodDeclaration/MethodDeclarator[@Image='tearDown']] + [count(Annotation//Name[ + pmd-java:typeIs('org.junit.After') + or pmd-java:typeIs('org.junit.jupiter.api.AfterEach') + or pmd-java:typeIs('org.junit.jupiter.api.AfterAll') + or pmd-java:typeIs('org.testng.annotations.AfterMethod')])=0] ``` **Example(s):** @@ -524,13 +529,18 @@ public class MyTest2 { **Priority:** Medium (3) In JUnit 3, the setUp method was used to set up all data entities required in running tests. -JUnit 4 skips the setUp method and executes all methods annotated with @Before before all tests +JUnit 4 skips the setUp method and executes all methods annotated with @Before before all tests. +JUnit 5 introduced @BeforeEach and @BeforeAll annotations to execute methods before each test or before all tests in the class, respectively. **This rule is defined by the following XPath expression:** ``` xpath -//CompilationUnit[not(ImportDeclaration/Name[starts-with(@Image, "org.testng")])] -//ClassOrInterfaceBodyDeclaration[MethodDeclaration/MethodDeclarator[@Image='setUp']] -[count(Annotation//Name[@Image='Before'])=0] +//ClassOrInterfaceBodyDeclaration + [MethodDeclaration/MethodDeclarator[@Image='setUp']] + [count(Annotation//Name[ + pmd-java:typeIs('org.junit.Before') + or pmd-java:typeIs('org.junit.jupiter.api.BeforeEach') + or pmd-java:typeIs('org.junit.jupiter.api.BeforeAll') + or pmd-java:typeIs('org.testng.annotations.BeforeMethod')])=0] ``` **Example(s):** @@ -561,6 +571,7 @@ public class MyTest2 { In JUnit 3, the framework executed all methods which started with the word test as a unit test. In JUnit 4, only methods annotated with the @Test annotation are executed. +In JUnit 5, one of the following annotations should be used for tests: @Test, @RepeatedTest, @TestFactory, @TestTemplate or @ParameterizedTest. **This rule is defined by the following XPath expression:** ``` xpath @@ -569,7 +580,12 @@ In JUnit 4, only methods annotated with the @Test annotation are executed. or ExtendsList/ClassOrInterfaceType[pmd-java:typeIs('junit.framework.TestCase')]] /ClassOrInterfaceBody/ClassOrInterfaceBodyDeclaration[MethodDeclaration[@Public=true()]/MethodDeclarator[starts-with(@Image, 'test')]] - [not(Annotation//Name[pmd-java:typeIs('org.junit.Test')])] + [not(Annotation//Name[ + pmd-java:typeIs('org.junit.Test') + or pmd-java:typeIs('org.junit.jupiter.api.Test') or pmd-java:typeIs('org.junit.jupiter.api.RepeatedTest') + or pmd-java:typeIs('org.junit.jupiter.api.TestFactory') or pmd-java:typeIs('org.junit.jupiter.api.TestTemplate') + or pmd-java:typeIs('org.junit.jupiter.params.ParameterizedTest') + ])] ``` **Example(s):** @@ -633,13 +649,24 @@ public class Foo extends TestCase { **Priority:** Medium (3) -JUnit tests should not contain too many asserts. Many asserts are indicative of a complex test, for which -it is harder to verify correctness. Consider breaking the test scenario into multiple, shorter test scenarios. +Unit tests should not contain too many asserts. Many asserts are indicative of a complex test, for which +it is harder to verify correctness. Consider breaking the test scenario into multiple, shorter test scenarios. Customize the maximum number of assertions used by this Rule to suit your needs. +This rule checks for JUnit4, JUnit5 and TestNG Tests, as well as methods starting with "test". + **This rule is defined by the following XPath expression:** ``` xpath -//MethodDeclarator[(@Image[fn:matches(.,'^test')] or ../../Annotation/MarkerAnnotation/Name[@Image='Test']) and count(..//PrimaryPrefix/Name[@Image[fn:matches(.,'^assert')]]) > $maximumAsserts] +//MethodDeclarator[@Image[fn:matches(.,'^test')] or ../../Annotation/MarkerAnnotation/Name[ + pmd-java:typeIs('org.junit.Test') + or pmd-java:typeIs('org.junit.jupiter.api.Test') + or pmd-java:typeIs('org.junit.jupiter.api.RepeatedTest') + or pmd-java:typeIs('org.junit.jupiter.api.TestFactory') + or pmd-java:typeIs('org.junit.jupiter.api.TestTemplate') + or pmd-java:typeIs('org.junit.jupiter.params.ParameterizedTest') + or pmd-java:typeIs('org.testng.annotations.Test') + ]] + [count(..//PrimaryPrefix/Name[@Image[fn:matches(.,'^assert')]]) > $maximumAsserts] ``` **Example(s):** @@ -1312,7 +1339,14 @@ This rule detects JUnit assertions in object equality. These assertions should b PrimarySuffix/Arguments/ArgumentList/Expression/PrimaryExpression/PrimaryPrefix/Name [ends-with(@Image, '.equals')] ] -[ancestor::ClassOrInterfaceDeclaration[//ClassOrInterfaceType[pmd-java:typeIs('junit.framework.TestCase')] or //MarkerAnnotation/Name[pmd-java:typeIs('org.junit.Test')]]] +[ancestor::ClassOrInterfaceDeclaration[//ClassOrInterfaceType[pmd-java:typeIs('junit.framework.TestCase')] + or //MarkerAnnotation/Name[ + pmd-java:typeIs('org.junit.Test') + or pmd-java:typeIs('org.junit.jupiter.api.Test') or pmd-java:typeIs('org.junit.jupiter.api.RepeatedTest') + or pmd-java:typeIs('org.junit.jupiter.api.TestFactory') or pmd-java:typeIs('org.junit.jupiter.api.TestTemplate') + or pmd-java:typeIs('org.junit.jupiter.params.ParameterizedTest') + ] +]] ``` **Example(s):** @@ -1350,7 +1384,14 @@ more specific methods, like assertNull, assertNotNull. Expression/EqualityExpression/PrimaryExpression/PrimaryPrefix/Literal/NullLiteral ] ] -[ancestor::ClassOrInterfaceDeclaration[//ClassOrInterfaceType[pmd-java:typeIs('junit.framework.TestCase')] or //MarkerAnnotation/Name[pmd-java:typeIs('org.junit.Test')]]] +[ancestor::ClassOrInterfaceDeclaration[//ClassOrInterfaceType[pmd-java:typeIs('junit.framework.TestCase')] + or //MarkerAnnotation/Name[ + pmd-java:typeIs('org.junit.Test') + or pmd-java:typeIs('org.junit.jupiter.api.Test') or pmd-java:typeIs('org.junit.jupiter.api.RepeatedTest') + or pmd-java:typeIs('org.junit.jupiter.api.TestFactory') or pmd-java:typeIs('org.junit.jupiter.api.TestTemplate') + or pmd-java:typeIs('org.junit.jupiter.params.ParameterizedTest') + ] +]] ``` **Example(s):** @@ -1390,7 +1431,14 @@ by more specific methods, like assertSame, assertNotSame. [PrimarySuffix/Arguments /ArgumentList/Expression /EqualityExpression[count(.//NullLiteral) = 0]] -[ancestor::ClassOrInterfaceDeclaration[//ClassOrInterfaceType[pmd-java:typeIs('junit.framework.TestCase')] or //MarkerAnnotation/Name[pmd-java:typeIs('org.junit.Test')]]] +[ancestor::ClassOrInterfaceDeclaration[//ClassOrInterfaceType[pmd-java:typeIs('junit.framework.TestCase')] + or //MarkerAnnotation/Name[ + pmd-java:typeIs('org.junit.Test') + or pmd-java:typeIs('org.junit.jupiter.api.Test') or pmd-java:typeIs('org.junit.jupiter.api.RepeatedTest') + or pmd-java:typeIs('org.junit.jupiter.api.TestFactory') or pmd-java:typeIs('org.junit.jupiter.api.TestTemplate') + or pmd-java:typeIs('org.junit.jupiter.params.ParameterizedTest') + ] +]] ``` **Example(s):** diff --git a/docs/pages/pmd/rules/java/codestyle.md b/docs/pages/pmd/rules/java/codestyle.md index fb83a279c4..b6f2a4db50 100644 --- a/docs/pages/pmd/rules/java/codestyle.md +++ b/docs/pages/pmd/rules/java/codestyle.md @@ -5,7 +5,7 @@ permalink: pmd_rules_java_codestyle.html folder: pmd/rules/java sidebaractiveurl: /pmd_rules_java.html editmepath: ../pmd-java/src/main/resources/category/java/codestyle.xml -keywords: Code Style, AbstractNaming, AtLeastOneConstructor, AvoidDollarSigns, AvoidFinalLocalVariable, AvoidPrefixingMethodParameters, AvoidProtectedFieldInFinalClass, AvoidProtectedMethodInFinalClassNotExtending, AvoidUsingNativeCode, BooleanGetMethodName, CallSuperInConstructor, ClassNamingConventions, CommentDefaultAccessModifier, ConfusingTernary, ControlStatementBraces, DefaultPackage, DontImportJavaLang, DuplicateImports, EmptyMethodInAbstractClassShouldBeAbstract, ExtendsObject, FieldDeclarationsShouldBeAtStartOfClass, ForLoopShouldBeWhileLoop, ForLoopsMustUseBraces, FormalParameterNamingConventions, GenericsNaming, IdenticalCatchBranches, IfElseStmtsMustUseBraces, IfStmtsMustUseBraces, LocalHomeNamingConvention, LocalInterfaceSessionNamingConvention, LocalVariableCouldBeFinal, LocalVariableNamingConventions, LongVariable, MDBAndSessionBeanNamingConvention, MethodArgumentCouldBeFinal, MethodNamingConventions, MIsLeadingVariableName, NoPackage, OnlyOneReturn, PackageCase, PrematureDeclaration, RemoteInterfaceNamingConvention, RemoteSessionInterfaceNamingConvention, ShortClassName, ShortMethodName, ShortVariable, SuspiciousConstantFieldName, TooManyStaticImports, UnnecessaryAnnotationValueElement, UnnecessaryConstructor, UnnecessaryFullyQualifiedName, UnnecessaryLocalBeforeReturn, UnnecessaryModifier, UnnecessaryReturn, UselessParentheses, UselessQualifiedThis, VariableNamingConventions, WhileLoopsMustUseBraces +keywords: Code Style, AbstractNaming, AtLeastOneConstructor, AvoidDollarSigns, AvoidFinalLocalVariable, AvoidPrefixingMethodParameters, AvoidProtectedFieldInFinalClass, AvoidProtectedMethodInFinalClassNotExtending, AvoidUsingNativeCode, BooleanGetMethodName, CallSuperInConstructor, ClassNamingConventions, CommentDefaultAccessModifier, ConfusingTernary, ControlStatementBraces, DefaultPackage, DontImportJavaLang, DuplicateImports, EmptyMethodInAbstractClassShouldBeAbstract, ExtendsObject, FieldDeclarationsShouldBeAtStartOfClass, FieldNamingConventions, ForLoopShouldBeWhileLoop, ForLoopsMustUseBraces, FormalParameterNamingConventions, GenericsNaming, IdenticalCatchBranches, IfElseStmtsMustUseBraces, IfStmtsMustUseBraces, LinguisticNaming, LocalHomeNamingConvention, LocalInterfaceSessionNamingConvention, LocalVariableCouldBeFinal, LocalVariableNamingConventions, LongVariable, MDBAndSessionBeanNamingConvention, MethodArgumentCouldBeFinal, MethodNamingConventions, MIsLeadingVariableName, NoPackage, OnlyOneReturn, PackageCase, PrematureDeclaration, RemoteInterfaceNamingConvention, RemoteSessionInterfaceNamingConvention, ShortClassName, ShortMethodName, ShortVariable, SuspiciousConstantFieldName, TooManyStaticImports, UnnecessaryAnnotationValueElement, UnnecessaryConstructor, UnnecessaryFullyQualifiedName, UnnecessaryLocalBeforeReturn, UnnecessaryModifier, UnnecessaryReturn, UselessParentheses, UselessQualifiedThis, VariableNamingConventions, WhileLoopsMustUseBraces language: Java --- ## AbstractNaming @@ -405,11 +405,11 @@ public class Éléphant {} |Name|Default Value|Description|Multivalued| |----|-------------|-----------|-----------| -|classPattern|[A-Z][a-zA-Z0-9]+|Regex which applies to concrete class names|no| -|abstractClassPattern|[A-Z][a-zA-Z0-9]+|Regex which applies to abstract class names|no| -|interfacePattern|[A-Z][a-zA-Z0-9]+|Regex which applies to interface names|no| -|enumPattern|[A-Z][a-zA-Z0-9]+|Regex which applies to enum names|no| -|annotationPattern|[A-Z][a-zA-Z0-9]+|Regex which applies to annotation names|no| +|classPattern|[A-Z][a-zA-Z0-9]*|Regex which applies to concrete class names|no| +|abstractClassPattern|[A-Z][a-zA-Z0-9]*|Regex which applies to abstract class names|no| +|interfacePattern|[A-Z][a-zA-Z0-9]*|Regex which applies to interface names|no| +|enumPattern|[A-Z][a-zA-Z0-9]*|Regex which applies to enum names|no| +|annotationPattern|[A-Z][a-zA-Z0-9]*|Regex which applies to annotation names|no| |utilityClassPattern|[A-Z][a-zA-Z0-9]+(Utils?\|Helper)|Regex which applies to utility class names|no| **Use this rule by referencing it:** @@ -756,6 +756,58 @@ public class HelloWorldBean { ``` +## FieldNamingConventions + +**Since:** PMD 6.7.0 + +**Priority:** High (1) + +Configurable naming conventions for field declarations. This rule reports variable declarations +which do not match the regex that applies to their specific kind ---e.g. constants (static final), +enum constant, final field. Each regex can be configured through properties. + +By default this rule uses the standard Java naming convention (Camel case), and uses the ALL_UPPER +convention for constants and enum constants. + +**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.codestyle.FieldNamingConventionsRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codestyle/FieldNamingConventionsRule.java) + +**Example(s):** + +``` java +class Foo { + int myField = 1; // This is in camel case, so it's ok + int my_Field = 1; // This contains an underscore, it's not ok by default + // but you may allow it, or even require the "my_" prefix + + final int FinalField = 1; // you may configure a different convention for final fields, + // e.g. here PascalCase: [A-Z][a-zA-Z0-9]* + + interface Interface { + double PI = 3.14; // interface "fields" use the constantPattern property + } + + enum AnEnum { + ORG, NET, COM; // These use a separate property but are set to ALL_UPPER by default + } + } +``` + +**This rule has the following properties:** + +|Name|Default Value|Description|Multivalued| +|----|-------------|-----------|-----------| +|publicConstantPattern|[A-Z][A-Z_0-9]*|Regex which applies to public constant names|no| +|constantPattern|[A-Z][A-Z_0-9]*|Regex which applies to non-public static final field names|no| +|enumConstantPattern|[A-Z][A-Z_0-9]*|Regex which applies to enum constant names|no| +|finalFieldPattern|[a-z][a-zA-Z0-9]*|Regex which applies to final field names|no| +|staticFieldPattern|[a-z][a-zA-Z0-9]*|Regex which applies to static field names|no| +|defaultFieldPattern|[a-z][a-zA-Z0-9]*|Regex which applies to field names|no| + +**Use this rule by referencing it:** +``` xml + +``` + ## ForLoopShouldBeWhileLoop **Since:** PMD 1.02 @@ -860,10 +912,10 @@ class Foo { |Name|Default Value|Description|Multivalued| |----|-------------|-----------|-----------| -|methodParameterPattern|[a-z][a-zA-Z0-9]+|Regex which applies to formal parameter names|no| -|finalMethodParameterPattern|[a-z][a-zA-Z0-9]+|Regex which applies to final formal parameter names|no| -|lambdaParameterPattern|[a-z][a-zA-Z0-9]+|Regex which applies to inferred-type lambda parameter names|no| -|explicitLambdaParameterPattern|[a-z][a-zA-Z0-9]+|Regex which applies to explicitly-typed lambda parameter names|no| +|methodParameterPattern|[a-z][a-zA-Z0-9]*|Regex which applies to formal parameter names|no| +|finalMethodParameterPattern|[a-z][a-zA-Z0-9]*|Regex which applies to final formal parameter names|no| +|lambdaParameterPattern|[a-z][a-zA-Z0-9]*|Regex which applies to inferred-type lambda parameter names|no| +|explicitLambdaParameterPattern|[a-z][a-zA-Z0-9]*|Regex which applies to explicitly-typed lambda parameter names|no| **Use this rule by referencing it:** ``` xml @@ -1020,6 +1072,87 @@ if (foo) { // preferred approach ``` +## LinguisticNaming + +**Since:** PMD 6.7.0 + +**Priority:** Medium (3) + +This rule finds Linguistic Naming Antipatterns. It checks for fields, that are named, as if they should +be boolean but have a different type. It also checks for methods, that according to their name, should +return a boolean, but don't. Further, it checks, that getters return something and setters won't. +Finally, it checks that methods, that start with "to" - so called transform methods - actually return +something, since according to their name, they should convert or transform one object into another. +There is additionally an option, to check for methods that contain "To" in their name - which are +also transform methods. However, this is disabled by default, since this detection is prone to +false positives. + +For more information, see [Linguistic Antipatterns - What They Are and How +Developers Perceive Them](https://doi.org/10.1007/s10664-014-9350-8). + +**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.codestyle.LinguisticNamingRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codestyle/LinguisticNamingRule.java) + +**Example(s):** + +``` java +public class LinguisticNaming { + int isValid; // the field name indicates a boolean, but it is an int. + boolean isTrue; // correct type of the field + + void myMethod() { + int hasMoneyLocal; // the local variable name indicates a boolean, but it is an int. + boolean hasSalaryLocal; // correct naming and type + } + + // the name of the method indicates, it is a boolean, but the method returns an int. + int isValid() { + return 1; + } + // correct naming and return type + boolean isSmall() { + return true; + } + + // the name indicates, this is a setter, but it returns something + int setName() { + return 1; + } + + // the name indicates, this is a getter, but it doesn't return anything + void getName() { + // nothing to return? + } + + // the name indicates, it transforms an object and should return the result + void toDataType() { + // nothing to return? + } + // the name indicates, it transforms an object and should return the result + void grapeToWine() { + // nothing to return? + } +} +``` + +**This rule has the following properties:** + +|Name|Default Value|Description|Multivalued| +|----|-------------|-----------|-----------| +|booleanFieldPrefixes|is \| has \| can \| have \| will \| should|the prefixes of fields and variables that indicate boolean|yes. Delimiter is '\|'.| +|checkVariables|true|Check local variable names and types for inconsistent naming|no| +|checkFields|true|Check field names and types for inconsistent naming|no| +|booleanMethodPrefixes|is \| has \| can \| have \| will \| should|the prefixes of methods that return boolean|yes. Delimiter is '\|'.| +|checkPrefixedTransformMethods|true|Check return type of methods whose names start with 'to'|no| +|checkTransformMethods|false|Check return type of methods which contain 'To' in their name|no| +|checkSetters|true|Check return type of setters|no| +|checkGetters|true|Check return type of getters|no| +|checkBooleanMethod|true|Check method names and types for inconsistent naming|no| + +**Use this rule by referencing it:** +``` xml + +``` + ## LocalHomeNamingConvention **Since:** PMD 4.0 @@ -1157,9 +1290,9 @@ class Foo { |Name|Default Value|Description|Multivalued| |----|-------------|-----------|-----------| -|localVarPattern|[a-z][a-zA-Z0-9]+|Regex which applies to non-final local variable names|no| -|finalVarPattern|[a-z][a-zA-Z0-9]+|Regex which applies to final local variable names|no| -|catchParameterPattern|[a-z][a-zA-Z0-9]+|Regex which applies to exception block parameter names|no| +|localVarPattern|[a-z][a-zA-Z0-9]*|Regex which applies to non-final local variable names|no| +|finalVarPattern|[a-z][a-zA-Z0-9]*|Regex which applies to final local variable names|no| +|catchParameterPattern|[a-z][a-zA-Z0-9]*|Regex which applies to exception block parameter names|no| **Use this rule by referencing it:** ``` xml @@ -1298,11 +1431,11 @@ public class Foo { |Name|Default Value|Description|Multivalued| |----|-------------|-----------|-----------| |checkNativeMethods|true|Deprecated Check native methods|no| -|methodPattern|[a-z][a-zA-Z0-9]+|Regex which applies to instance method names|no| -|staticPattern|[a-z][a-zA-Z0-9]+|Regex which applies to static method names|no| -|nativePattern|[a-z][a-zA-Z0-9]+|Regex which applies to native method names|no| +|methodPattern|[a-z][a-zA-Z0-9]*|Regex which applies to instance method names|no| +|staticPattern|[a-z][a-zA-Z0-9]*|Regex which applies to static method names|no| +|nativePattern|[a-z][a-zA-Z0-9]*|Regex which applies to native method names|no| |junit3TestPattern|test[A-Z0-9][a-zA-Z0-9]*|Regex which applies to JUnit 3 test method names|no| -|junit4TestPattern|[a-z][a-zA-Z0-9]+|Regex which applies to JUnit 4 test method names|no| +|junit4TestPattern|[a-z][a-zA-Z0-9]*|Regex which applies to JUnit 4 test method names|no| **Use this rule by referencing it:** ``` xml @@ -1936,6 +2069,7 @@ Useless parentheses should be removed. [not(./CastExpression)] [not(./ConditionalExpression)] [not(./AdditiveExpression)] + [not(./AssignmentOperator)] | //Expression[not(parent::PrimaryPrefix)]/PrimaryExpression[count(*)=1] /PrimaryPrefix/Expression diff --git a/docs/pages/pmd/rules/java/design.md b/docs/pages/pmd/rules/java/design.md index c9d7d9cae9..f0eb4ed843 100644 --- a/docs/pages/pmd/rules/java/design.md +++ b/docs/pages/pmd/rules/java/design.md @@ -23,7 +23,7 @@ protected constructor in order to prevent instantiation than make the class misl //ClassOrInterfaceDeclaration [@Abstract = 'true'] [count(//MethodDeclaration) + count(//ConstructorDeclaration) = 0] - [not(../Annotation/MarkerAnnotation/Name[typeIs('com.google.auto.value.AutoValue')])] + [not(../Annotation/MarkerAnnotation/Name[pmd-java:typeIs('com.google.auto.value.AutoValue')])] ``` **Example(s):** @@ -259,13 +259,13 @@ Exception, or Error, use a subclassed exception or error instead. ``` xpath //ThrowStatement//AllocationExpression /ClassOrInterfaceType[ - typeIsExactly('java.lang.Throwable') + pmd-java:typeIsExactly('java.lang.Throwable') or - typeIsExactly('java.lang.Exception') + pmd-java:typeIsExactly('java.lang.Exception') or - typeIsExactly('java.lang.Error') + pmd-java:typeIsExactly('java.lang.Error') or - typeIsExactly('java.lang.RuntimeException') + pmd-java:typeIsExactly('java.lang.RuntimeException') ] ``` @@ -518,7 +518,7 @@ Errors are system exceptions. Do not extend them. **This rule is defined by the following XPath expression:** ``` xpath //ClassOrInterfaceDeclaration/ExtendsList/ClassOrInterfaceType - [typeIs('java.lang.Error')] + [pmd-java:typeIs('java.lang.Error')] ``` **Example(s):** @@ -1406,7 +1406,14 @@ PrimaryExpression/PrimarySuffix/Arguments/ArgumentList /Expression/UnaryExpressionNotPlusMinus[@Image='!'] /PrimaryExpression/PrimaryPrefix ] -[ancestor::ClassOrInterfaceDeclaration[//ClassOrInterfaceType[pmd-java:typeIs('junit.framework.TestCase')] or //MarkerAnnotation/Name[pmd-java:typeIs('org.junit.Test')]]] +[ancestor::ClassOrInterfaceDeclaration[//ClassOrInterfaceType[pmd-java:typeIs('junit.framework.TestCase')] + or //MarkerAnnotation/Name[ + pmd-java:typeIs('org.junit.Test') + or pmd-java:typeIs('org.junit.jupiter.api.Test') or pmd-java:typeIs('org.junit.jupiter.api.RepeatedTest') + or pmd-java:typeIs('org.junit.jupiter.api.TestFactory') or pmd-java:typeIs('org.junit.jupiter.api.TestTemplate') + or pmd-java:typeIs('org.junit.jupiter.params.ParameterizedTest') + ] +]] ``` **Example(s):** diff --git a/docs/pages/pmd/rules/java/documentation.md b/docs/pages/pmd/rules/java/documentation.md index e80a77f7d9..98d9988dec 100644 --- a/docs/pages/pmd/rules/java/documentation.md +++ b/docs/pages/pmd/rules/java/documentation.md @@ -132,7 +132,7 @@ and unintentional empty constructors. ``` xpath //ConstructorDeclaration[@Private='false'] [count(BlockStatement) = 0 and ($ignoreExplicitConstructorInvocation = 'true' or not(ExplicitConstructorInvocation)) and @containsComment = 'false'] - [not(../Annotation/MarkerAnnotation/Name[typeIs('javax.inject.Inject')])] + [not(../Annotation/MarkerAnnotation/Name[pmd-java:typeIs('javax.inject.Inject')])] ``` **Example(s):** diff --git a/docs/pages/pmd/rules/java/errorprone.md b/docs/pages/pmd/rules/java/errorprone.md index bfa1e4a5f9..225226cc72 100644 --- a/docs/pages/pmd/rules/java/errorprone.md +++ b/docs/pages/pmd/rules/java/errorprone.md @@ -796,9 +796,9 @@ Super should be called at the start of the method /Block[not( (BlockStatement[1]/Statement/StatementExpression/PrimaryExpression[./PrimaryPrefix[@SuperModifier='true']]/PrimarySuffix[@Image= ancestor::MethodDeclaration/MethodDeclarator/@Image]))] [ancestor::ClassOrInterfaceDeclaration[ExtendsList/ClassOrInterfaceType[ - typeIs('android.app.Activity') or - typeIs('android.app.Application') or - typeIs('android.app.Service') + pmd-java:typeIs('android.app.Activity') or + pmd-java:typeIs('android.app.Application') or + pmd-java:typeIs('android.app.Service') ]]] ``` @@ -839,9 +839,9 @@ Super should be called at the end of the method /Block/BlockStatement[last()] [not(Statement/StatementExpression/PrimaryExpression[./PrimaryPrefix[@SuperModifier='true']]/PrimarySuffix[@Image= ancestor::MethodDeclaration/MethodDeclarator/@Image])] [ancestor::ClassOrInterfaceDeclaration[ExtendsList/ClassOrInterfaceType[ - typeIs('android.app.Activity') or - typeIs('android.app.Application') or - typeIs('android.app.Service') + pmd-java:typeIs('android.app.Activity') or + pmd-java:typeIs('android.app.Application') or + pmd-java:typeIs('android.app.Service') ]]] ``` @@ -2176,7 +2176,14 @@ Some JUnit framework methods are easy to misspell. or (not(@Image = 'tearDown') and translate(@Image, 'TEARdOWN', 'tearDown') = 'tearDown')] [FormalParameters[count(*) = 0]] -[ancestor::ClassOrInterfaceDeclaration[//ClassOrInterfaceType[pmd-java:typeIs('junit.framework.TestCase')] or //MarkerAnnotation/Name[pmd-java:typeIs('org.junit.Test')]]] +[ancestor::ClassOrInterfaceDeclaration[//ClassOrInterfaceType[pmd-java:typeIs('junit.framework.TestCase')] + or //MarkerAnnotation/Name[ + pmd-java:typeIs('org.junit.Test') + or pmd-java:typeIs('org.junit.jupiter.api.Test') or pmd-java:typeIs('org.junit.jupiter.api.RepeatedTest') + or pmd-java:typeIs('org.junit.jupiter.api.TestFactory') or pmd-java:typeIs('org.junit.jupiter.api.TestTemplate') + or pmd-java:typeIs('org.junit.jupiter.params.ParameterizedTest') + ] +]] ``` **Example(s):** @@ -2208,7 +2215,14 @@ The suite() method in a JUnit test needs to be both public and static. //MethodDeclaration[not(@Static='true') or not(@Public='true')] [MethodDeclarator/@Image='suite'] [MethodDeclarator/FormalParameters/@ParameterCount=0] -[ancestor::ClassOrInterfaceDeclaration[//ClassOrInterfaceType[pmd-java:typeIs('junit.framework.TestCase')] or //MarkerAnnotation/Name[pmd-java:typeIs('org.junit.Test')]]] +[ancestor::ClassOrInterfaceDeclaration[//ClassOrInterfaceType[pmd-java:typeIs('junit.framework.TestCase')] + or //MarkerAnnotation/Name[ + pmd-java:typeIs('org.junit.Test') + or pmd-java:typeIs('org.junit.jupiter.api.Test') or pmd-java:typeIs('org.junit.jupiter.api.RepeatedTest') + or pmd-java:typeIs('org.junit.jupiter.api.TestFactory') or pmd-java:typeIs('org.junit.jupiter.api.TestTemplate') + or pmd-java:typeIs('org.junit.jupiter.params.ParameterizedTest') + ] +]] ``` **Example(s):** @@ -2399,16 +2413,10 @@ Serializable classes should provide a serialVersionUID field. **This rule is defined by the following XPath expression:** ``` xpath //ClassOrInterfaceDeclaration - [ - count(ClassOrInterfaceBody/ClassOrInterfaceBodyDeclaration - /FieldDeclaration/VariableDeclarator/VariableDeclaratorId[@Image='serialVersionUID']) = 0 -and - count(ImplementsList - [ClassOrInterfaceType/@Image='Serializable' - or ClassOrInterfaceType/@Image='java.io.Serializable']) =1 -and - @Abstract = 'false' -] + [@Abstract = 'false'] + [count(ClassOrInterfaceBody/ClassOrInterfaceBodyDeclaration + /FieldDeclaration/VariableDeclarator/VariableDeclaratorId[@Image='serialVersionUID']) = 0] + [(ImplementsList | ExtendsList)/ClassOrInterfaceType[pmd-java:typeIs('java.io.Serializable')]] ``` **Example(s):** @@ -3196,7 +3204,14 @@ or UnaryExpressionNotPlusMinus[@Image='!'] /PrimaryExpression/PrimaryPrefix[Literal/BooleanLiteral or Name[count(../../*)=1]]] ] -[ancestor::ClassOrInterfaceDeclaration[//ClassOrInterfaceType[pmd-java:typeIs('junit.framework.TestCase')] or //MarkerAnnotation/Name[pmd-java:typeIs('org.junit.Test')]]] +[ancestor::ClassOrInterfaceDeclaration[//ClassOrInterfaceType[pmd-java:typeIs('junit.framework.TestCase')] + or //MarkerAnnotation/Name[ + pmd-java:typeIs('org.junit.Test') + or pmd-java:typeIs('org.junit.jupiter.api.Test') or pmd-java:typeIs('org.junit.jupiter.api.RepeatedTest') + or pmd-java:typeIs('org.junit.jupiter.api.TestFactory') or pmd-java:typeIs('org.junit.jupiter.api.TestTemplate') + or pmd-java:typeIs('org.junit.jupiter.params.ParameterizedTest') + ] +]] ``` **Example(s):** diff --git a/docs/pages/pmd/rules/java/multithreading.md b/docs/pages/pmd/rules/java/multithreading.md index 9b6107e238..6c673f40a8 100644 --- a/docs/pages/pmd/rules/java/multithreading.md +++ b/docs/pages/pmd/rules/java/multithreading.md @@ -166,8 +166,8 @@ Explicitly calling Thread.run() method will execute in the caller's thread of co [ ./Name[ends-with(@Image, '.run') or @Image = 'run'] and substring-before(Name/@Image, '.') =//VariableDeclarator/VariableDeclaratorId/@Image - [../../../Type/ReferenceType/ClassOrInterfaceType[typeIs('java.lang.Thread')]] - or (./AllocationExpression/ClassOrInterfaceType[typeIs('java.lang.Thread')] + [../../../Type/ReferenceType/ClassOrInterfaceType[pmd-java:typeIs('java.lang.Thread')]] + or (./AllocationExpression/ClassOrInterfaceType[pmd-java:typeIs('java.lang.Thread')] and ../PrimarySuffix[@Image = 'run']) ] ] diff --git a/docs/pages/pmd/rules/java/performance.md b/docs/pages/pmd/rules/java/performance.md index 38315f4ab8..d86c5214a5 100644 --- a/docs/pages/pmd/rules/java/performance.md +++ b/docs/pages/pmd/rules/java/performance.md @@ -132,10 +132,10 @@ The FileReader and FileWriter constructors instantiate FileInputStream and FileO **This rule is defined by the following XPath expression:** ``` xpath //PrimaryPrefix/AllocationExpression/ClassOrInterfaceType[ - typeIs('java.io.FileInputStream') - or typeIs('java.io.FileOutputStream') - or typeIs('java.io.FileReader') - or typeIs('java.io.FileWriter') + pmd-java:typeIs('java.io.FileInputStream') + or pmd-java:typeIs('java.io.FileOutputStream') + or pmd-java:typeIs('java.io.FileReader') + or pmd-java:typeIs('java.io.FileWriter') ] ``` @@ -208,10 +208,10 @@ adverse impacts on performance. ``` xpath //FieldDeclaration/Type/PrimitiveType[@Image = 'short'] | -//ClassOrInterfaceBodyDeclaration[not(Annotation/MarkerAnnotation/Name[typeIs('java.lang.Override')])] +//ClassOrInterfaceBodyDeclaration[not(Annotation/MarkerAnnotation/Name[pmd-java:typeIs('java.lang.Override')])] /MethodDeclaration/ResultType/Type/PrimitiveType[@Image = 'short'] | -//ClassOrInterfaceBodyDeclaration[not(Annotation/MarkerAnnotation/Name[typeIs('java.lang.Override')])] +//ClassOrInterfaceBodyDeclaration[not(Annotation/MarkerAnnotation/Name[pmd-java:typeIs('java.lang.Override')])] /MethodDeclaration/MethodDeclarator/FormalParameters/FormalParameter/Type/PrimitiveType[@Image = 'short'] | //LocalVariableDeclaration/Type/PrimitiveType[@Image = 'short'] @@ -300,7 +300,7 @@ Note that new Byte() is deprecated since JDK 9 for that reason. ``` xpath //AllocationExpression [not (ArrayDimsAndInits) -and ClassOrInterfaceType[typeIs('java.lang.Byte')]] +and ClassOrInterfaceType[pmd-java:typeIs('java.lang.Byte')]] ``` **Example(s):** @@ -498,7 +498,7 @@ Note that new Integer() is deprecated since JDK 9 for that reason. ``` xpath //AllocationExpression [not (ArrayDimsAndInits) - and ClassOrInterfaceType[typeIs('java.lang.Integer')]] + and ClassOrInterfaceType[pmd-java:typeIs('java.lang.Integer')]] ``` **Example(s):** @@ -528,7 +528,7 @@ Note that new Long() is deprecated since JDK 9 for that reason. ``` xpath //AllocationExpression [not (ArrayDimsAndInits) -and ClassOrInterfaceType[typeIs('java.lang.Long')]] +and ClassOrInterfaceType[pmd-java:typeIs('java.lang.Long')]] ``` **Example(s):** @@ -647,7 +647,7 @@ Note that new Short() is deprecated since JDK 9 for that reason. ``` xpath //AllocationExpression [not (ArrayDimsAndInits) -and ClassOrInterfaceType[typeIs('java.lang.Short')]] +and ClassOrInterfaceType[pmd-java:typeIs('java.lang.Short')]] ``` **Example(s):** diff --git a/docs/pages/pmd/rules/plsql.md b/docs/pages/pmd/rules/plsql.md index 5697383c35..4761c81638 100644 --- a/docs/pages/pmd/rules/plsql.md +++ b/docs/pages/pmd/rules/plsql.md @@ -16,6 +16,7 @@ folder: pmd/rules {% include callout.html content="Rules which enforce a specific coding style." %} +* [ForLoopNaming](pmd_rules_plsql_codestyle.html#forloopnaming): In case you have loops please name the loop variables more meaningful. * [MisplacedPragma](pmd_rules_plsql_codestyle.html#misplacedpragma): Oracle states that the PRAQMA AUTONOMOUS_TRANSACTION must be in the declaration block,but the cod... ## Design diff --git a/docs/pages/pmd/rules/plsql/codestyle.md b/docs/pages/pmd/rules/plsql/codestyle.md index 93abe1ef55..17cabf92bf 100644 --- a/docs/pages/pmd/rules/plsql/codestyle.md +++ b/docs/pages/pmd/rules/plsql/codestyle.md @@ -5,9 +5,73 @@ permalink: pmd_rules_plsql_codestyle.html folder: pmd/rules/plsql sidebaractiveurl: /pmd_rules_plsql.html editmepath: ../pmd-plsql/src/main/resources/category/plsql/codestyle.xml -keywords: Code Style, MisplacedPragma +keywords: Code Style, MisplacedPragma, ForLoopNaming language: PLSQL --- +## ForLoopNaming + +**Since:** PMD 6.7.0 + +**Priority:** Medium (3) + +In case you have loops please name the loop variables more meaningful. + +**This rule is defined by the following XPath expression:** +``` xpath +//CursorForLoopStatement[ + $allowSimpleLoops = 'false' or + (Statement//CursorForLoopStatement or ancestor::CursorForLoopStatement) +] +/ForIndex[not(matches(@Image, $cursorPattern))] +| +//ForStatement[ + $allowSimpleLoops = 'false' or + (Statement//ForStatement or ancestor::ForStatement) +] +/ForIndex[not(matches(@Image, $indexPattern))] +``` + +**Example(s):** + +``` sql +-- good example +BEGIN +FOR company IN (SELECT * FROM companies) LOOP + FOR contact IN (SELECT * FROM contacts) LOOP + FOR party IN (SELECT * FROM parties) LOOP + NULL; + END LOOP; + END LOOP; +END LOOP; +END; +/ + +-- bad example +BEGIN +FOR c1 IN (SELECT * FROM companies) LOOP + FOR c2 IN (SELECT * FROM contacts) LOOP + FOR c3 IN (SELECT * FROM parties) LOOP + NULL; + END LOOP; + END LOOP; +END LOOP; +END; +/ +``` + +**This rule has the following properties:** + +|Name|Default Value|Description|Multivalued| +|----|-------------|-----------|-----------| +|allowSimpleLoops|false|Ignore simple loops, that are not nested|no| +|cursorPattern|[a-zA-Z_0-9]{5,}|The pattern used for the curosr loop variable|no| +|indexPattern|[a-zA-Z_0-9]{5,}|The pattern used for the index loop variable|no| + +**Use this rule by referencing it:** +``` xml + +``` + ## MisplacedPragma **Since:** PMD 5.5.2 diff --git a/docs/pages/pmd/userdocs/making_rulesets.md b/docs/pages/pmd/userdocs/making_rulesets.md index 54f176d24e..dc6a87b032 100644 --- a/docs/pages/pmd/userdocs/making_rulesets.md +++ b/docs/pages/pmd/userdocs/making_rulesets.md @@ -24,7 +24,7 @@ The first step is to create a new empty ruleset. You can use the following templ + xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.net/ruleset_2_0_0.xsd"> My custom rules diff --git a/docs/pages/release_notes.md b/docs/pages/release_notes.md index 23bf24e76d..a324c2ee28 100644 --- a/docs/pages/release_notes.md +++ b/docs/pages/release_notes.md @@ -4,11 +4,11 @@ permalink: pmd_release_notes.html keywords: changelog, release notes --- -## ????? - 6.6.0-SNAPSHOT +## {{ site.pmd.date }} - {{ site.pmd.version | append_unless: is_release_version, "-SNAPSHOT" }} -The PMD team is pleased to announce PMD 6.6.0. +The PMD team is pleased to announce PMD {{ site.pmd.version }}. -This is a minor release. +This is a {{ site.pmd.release_type }} release. ### Table Of Contents @@ -22,37 +22,58 @@ This is a minor release. #### New Rules -* The new Java rule [`LocalVariableNamingConventions`](pmd_rules_java_codestyle.html#localvariablenamingconventions) (`java-codestlye`) - detects local variable names that don't comply to a given convention. It defaults to standrd Java convention of using camelCase, - but can be configured. Special cases can be configured for final variables and catched exceptions' names. +* The new Java rule {% rule java/codestyle/LinguisticNaming %} (`java-codestyle`) + detects cases, when a method name indicates it returns a boolean (such as `isSmall()`) but it doesn't. + Besides method names, the rule also checks field and variable names. It also checks, that getters return + something but setters won't. The rule has several properties with which it can be customized. -* The new Java rule [`FormalParameterNamingConventions`](pmd_rules_java_codestyle.html#formalparameternamingconventions) (`java-codestlye`) - detects formal parameter names that don't comply to a given convention. It defaults to standrd Java convention of using camelCase, - but can be configured. Special cases can be configured for final parameters and lambda parameters (considering wether they are - explicitly typed or not) +* The new PL/SQL rule {% rule plsql/codestyle/ForLoopNaming %} (`plsql-codestyle`) + enforces a naming convention for "for loops". Both "cursor for loops" and "index for loops" are covered. + The rule can be customized via patterns. By default, short variable names are reported. + +* The new Java rule {% rule java/codestyle/FieldNamingConventions %} (`java-codestyle`) + detects field names that don't comply to a given convention. It defaults to standard Java convention of using camelCase, + but can be configured with ease for e.g. constants or static fields. ### Fixed Issues -* doc - * [#1215](https://github.com/pmd/pmd/issues/1215): \[doc] TOC links don't work? +* core + * [#1191](https://github.com/pmd/pmd/issues/1191): \[core] Test Framework: Sort violations by line/column + * [#1283](https://github.com/pmd/pmd/issues/1283): \[core] Deprecate ReportTree + * [#1288](https://github.com/pmd/pmd/issues/1288): \[core] No supported build listeners found with Gradle + * [#1300](https://github.com/pmd/pmd/issues/1300): \[core] PMD stops processing file completely, if one rule in a rule chain fails +* java-bestpractices + * [#940](https://github.com/pmd/pmd/issues/940): \[java] JUnit 4 false positives for JUnit 5 tests + * [#1267](https://github.com/pmd/pmd/pull/1267): \[java] MissingOverrideRule: Avoid NoClassDefFoundError with incomplete classpath * java-codestyle - * [#1211](https://github.com/pmd/pmd/issues/1211): \[java] CommentDefaultAccessModifier false positive with nested interfaces (regression from 6.4.0) - * [#1216](https://github.com/pmd/pmd/issues/1216): \[java] UnnecessaryFullyQualifiedName false positive for the same name method -* java-design - * [#1217](https://github.com/pmd/pmd/issues/1217): \[java] CyclomaticComplexityRule counts ?-operator twice + * [#1255](https://github.com/pmd/pmd/issues/1255): \[java] UnnecessaryFullyQualifiedName false positive: static method on shadowed implicitly imported class + * [#1258](https://github.com/pmd/pmd/issues/1285): \[java] False positive "UselessParentheses" for parentheses that contain assignment +* java-errorprone + * [#1078](https://github.com/pmd/pmd/issues/1078): \[java] MissingSerialVersionUID rule does not seem to catch inherited classes +* java-performance + * [#1298](https://github.com/pmd/pmd/issues/1298): \[java] RedundantFieldInitializer - NumberFormatException with Long +* jsp + * [#1274](https://github.com/pmd/pmd/issues/1274): \[jsp] Support EL in tag attributes + * [#1276](https://github.com/pmd/pmd/issues/1276): \[jsp] add support for jspf and tag extensions * plsql - * [#980](https://github.com/pmd/pmd/issues/980): \[plsql] ParseException for CREATE TABLE - * [#981](https://github.com/pmd/pmd/issues/981): \[plsql] ParseException when parsing VIEW - * [#1047](https://github.com/pmd/pmd/issues/1047): \[plsql] ParseException when parsing EXECUTE IMMEDIATE -* ui - * [#1233](https://github.com/pmd/pmd/issues/1233): \[ui] XPath autocomplete arrows on first and last items + * [#681](https://github.com/pmd/pmd/issues/681): \[plsql] Parse error with Cursor For Loop ### API Changes -* The `findDescendantsOfType` methods in `net.sourceforge.pmd.lang.ast.AbstractNode` no longer search for exact type matches, but will - match subclasses too. That means, it's now possible to look for abstract node types such as `AbstractJavaTypeNode` and not only for it's concrete subtypes. +* All classes in the package `net.sourceforge.pmd.lang.dfa.report` have been deprecated and will be removed + with PMD 7.0.0. This includes the class `net.sourceforge.pmd.lang.dfa.report.ReportTree`. The reason is, + that this class is very specific to Java and not suitable for other languages. It has only been used for + `YAHTMLRenderer`, which has been rewritten to work without these classes. ### External Contributions -* [#1182](https://github.com/pmd/pmd/pull/1182): \[ui] XPath AutoComplete - [Akshat Bahety](https://github.com/akshatbahety) -* [#1231](https://github.com/pmd/pmd/pull/1231): \[doc] Minor typo fix in installation.md - [Ashish Rana](https://github.com/ashishrana160796) +* [#109](https://github.com/pmd/pmd/pull/109): \[java] Add two linguistics rules under naming - [Arda Aslan](https://github.com/ardaasln) +* [#1254](https://github.com/pmd/pmd/pull/1254): \[ci] \[GSoC] Integrating the danger and pmdtester to travis CI - [BBG](https://github.com/djydewang) +* [#1258](https://github.com/pmd/pmd/pull/1258): \[java] Use typeof in MissingSerialVersionUID - [krichter722](https://github.com/krichter722) +* [#1264](https://github.com/pmd/pmd/pull/1264): \[cpp] Fix NullPointerException in CPPTokenizer:99 - [Rafael Cortês](https://github.com/mrfyda) +* [#1277](https://github.com/pmd/pmd/pull/1277): \[jsp] #1276 add support for jspf and tag extensions - [Jordi Llach](https://github.com/jordillachmrf) +* [#1275](https://github.com/pmd/pmd/pull/1275): \[jsp] Issue #1274 - Support EL in tag attributes - [Jordi Llach](https://github.com/jordillachmrf) +* [#1278](https://github.com/pmd/pmd/pull/1278): \[ci] \[GSoC] Use pmdtester 1.0.0.pre.beta3 - [BBG](https://github.com/djydewang) +* [#1289](https://github.com/pmd/pmd/pull/1289): \[java] UselessParentheses: Fix false positive with assignments - [cobratbq](https://github.com/cobratbq) +* [#1290](https://github.com/pmd/pmd/pull/1290): \[docs] \[GSoC] Create the documentation about pmdtester - [BBG](https://github.com/djydewang) +* [#1256](https://github.com/pmd/pmd/pull/1256): \[java] #940 Avoid JUnit 4 false positives for JUnit 5 tests - [Alex Shesterov](https://github.com/vovkss) diff --git a/docs/pages/release_notes_old.md b/docs/pages/release_notes_old.md index 7d8ba85e46..3c3cd9e857 100644 --- a/docs/pages/release_notes_old.md +++ b/docs/pages/release_notes_old.md @@ -6,6 +6,83 @@ permalink: pmd_release_notes_old.html Previous versions of PMD can be downloaded here: http://sourceforge.net/projects/pmd/files/pmd/ +## 29-July-2018 - 6.6.0 + +The PMD team is pleased to announce PMD 6.6.0. + +This is a minor release. + +### Table Of Contents + +* [New and noteworthy](#new-and-noteworthy) + * [Java 11 Support](#java-11-support) + * [New Rules](#new-rules) + * [Modified Rules](#modified-rules) +* [Fixed Issues](#fixed-issues) +* [API Changes](#api-changes) +* [External Contributions](#external-contributions) + +### New and noteworthy + +#### Java 11 Support + +PMD is now able to parse the local-variable declaration syntax `var xxx`, that has been +extended for lambda parameters with Java 11 via +[JEP 323: Local-Variable Syntax for Lambda Parameters](http://openjdk.java.net/jeps/323). + +#### New Rules + +* The new Java rule [`LocalVariableNamingConventions`](pmd_rules_java_codestyle.html#localvariablenamingconventions) + (`java-codestyle`) detects local variable names that don't comply to a given convention. It defaults to standard + Java convention of using camelCase, but can be configured. Special cases can be configured for final variables + and caught exceptions' names. + +* The new Java rule [`FormalParameterNamingConventions`](pmd_rules_java_codestyle.html#formalparameternamingconventions) + (`java-codestyle`) detects formal parameter names that don't comply to a given convention. It defaults to + standard Java convention of using camelCase, but can be configured. Special cases can be configured for final + parameters and lambda parameters (considering whether they are explicitly typed or not). + +#### Modified Rules + +* The Java rules [`AccessorClassGeneration`](pmd_rules_java_bestpracices.html#accessorclassgeneration) and + [`AccessorMethodGeneration`](pmd_rules_java_bestpracices.html#accessormethodgeneration) (both in category + `java-bestpractices`) have been modified to be only valid up until Java 10. Java 11 adds support for + [JEP 181: Nest-Based Access Control](http://openjdk.java.net/jeps/181) which avoids the generation of + accessor classes / methods altogether. + +### Fixed Issues + +* core + * [#1178](https://github.com/pmd/pmd/issues/1178): \[core] "Unsupported build listener" in gradle build + * [#1225](https://github.com/pmd/pmd/issues/1225): \[core] Error in sed expression on line 82 of run.sh while detecting installed version of Java +* doc + * [#1215](https://github.com/pmd/pmd/issues/1215): \[doc] TOC links don't work? +* java-codestyle + * [#1211](https://github.com/pmd/pmd/issues/1211): \[java] CommentDefaultAccessModifier false positive with nested interfaces (regression from 6.4.0) + * [#1216](https://github.com/pmd/pmd/issues/1216): \[java] UnnecessaryFullyQualifiedName false positive for the same name method +* java-design + * [#1217](https://github.com/pmd/pmd/issues/1217): \[java] CyclomaticComplexityRule counts ?-operator twice + * [#1226](https://github.com/pmd/pmd/issues/1226): \[java] NPath complexity false negative due to overflow +* plsql + * [#980](https://github.com/pmd/pmd/issues/980): \[plsql] ParseException for CREATE TABLE + * [#981](https://github.com/pmd/pmd/issues/981): \[plsql] ParseException when parsing VIEW + * [#1047](https://github.com/pmd/pmd/issues/1047): \[plsql] ParseException when parsing EXECUTE IMMEDIATE +* ui + * [#1233](https://github.com/pmd/pmd/issues/1233): \[ui] XPath autocomplete arrows on first and last items + +### API Changes + +* The `findDescendantsOfType` methods in `net.sourceforge.pmd.lang.ast.AbstractNode` no longer search for + exact type matches, but will match subclasses, too. That means, it's now possible to look for abstract node + types such as `AbstractJavaTypeNode` and not only for it's concrete subtypes. + +### External Contributions + +* [#1182](https://github.com/pmd/pmd/pull/1182): \[ui] XPath AutoComplete - [Akshat Bahety](https://github.com/akshatbahety) +* [#1231](https://github.com/pmd/pmd/pull/1231): \[doc] Minor typo fix in installation.md - [Ashish Rana](https://github.com/ashishrana160796) +* [#1250](https://github.com/pmd/pmd/pull/1250): \[ci] \[GSoC] Upload baseline of pmdtester automatically - [BBG](https://github.com/djydewang) + + ## 26-June-2018 - 6.5.0 The PMD team is pleased to announce PMD 6.5.0. diff --git a/pmd-apex-jorje/pom.xml b/pmd-apex-jorje/pom.xml index 0298d90241..f53d989da7 100644 --- a/pmd-apex-jorje/pom.xml +++ b/pmd-apex-jorje/pom.xml @@ -8,7 +8,7 @@ net.sourceforge.pmd pmd - 6.6.0-SNAPSHOT + 6.7.0-SNAPSHOT diff --git a/pmd-apex/pom.xml b/pmd-apex/pom.xml index cbea57a45f..1fc38e5979 100644 --- a/pmd-apex/pom.xml +++ b/pmd-apex/pom.xml @@ -7,7 +7,7 @@ net.sourceforge.pmd pmd - 6.6.0-SNAPSHOT + 6.7.0-SNAPSHOT diff --git a/pmd-core/pom.xml b/pmd-core/pom.xml index 5140684ed5..5147f55afa 100644 --- a/pmd-core/pom.xml +++ b/pmd-core/pom.xml @@ -7,7 +7,7 @@ net.sourceforge.pmd pmd - 6.6.0-SNAPSHOT + 6.7.0-SNAPSHOT diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/lang/dfa/report/AbstractReportNode.java b/pmd-core/src/main/java/net/sourceforge/pmd/lang/dfa/report/AbstractReportNode.java index 0c22b3d49b..bcf8866405 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/lang/dfa/report/AbstractReportNode.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/lang/dfa/report/AbstractReportNode.java @@ -7,6 +7,7 @@ package net.sourceforge.pmd.lang.dfa.report; import java.util.ArrayList; import java.util.List; +@Deprecated // will be removed with PMD 7.0.0 without replacement. See net.sourceforge.pmd.lang.dfa.report.ReportTree for details. public abstract class AbstractReportNode { private List childNodes = new ArrayList<>(); private AbstractReportNode parentNode = null; diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/lang/dfa/report/ClassNode.java b/pmd-core/src/main/java/net/sourceforge/pmd/lang/dfa/report/ClassNode.java index 747be9e52a..eb6ec4d588 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/lang/dfa/report/ClassNode.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/lang/dfa/report/ClassNode.java @@ -4,6 +4,7 @@ package net.sourceforge.pmd.lang.dfa.report; +@Deprecated // will be removed with PMD 7.0.0 without replacement. See net.sourceforge.pmd.lang.dfa.report.ReportTree for details. public class ClassNode extends AbstractReportNode { private String className; diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/lang/dfa/report/PackageNode.java b/pmd-core/src/main/java/net/sourceforge/pmd/lang/dfa/report/PackageNode.java index 24d2e9ac5a..8ba7c1ba4c 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/lang/dfa/report/PackageNode.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/lang/dfa/report/PackageNode.java @@ -4,6 +4,7 @@ package net.sourceforge.pmd.lang.dfa.report; +@Deprecated // will be removed with PMD 7.0.0 without replacement. See net.sourceforge.pmd.lang.dfa.report.ReportTree for details. public class PackageNode extends AbstractReportNode { private String packageName; diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/lang/dfa/report/ReportHTMLPrintVisitor.java b/pmd-core/src/main/java/net/sourceforge/pmd/lang/dfa/report/ReportHTMLPrintVisitor.java index a7d664eb7e..4fe31734c2 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/lang/dfa/report/ReportHTMLPrintVisitor.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/lang/dfa/report/ReportHTMLPrintVisitor.java @@ -25,6 +25,7 @@ import net.sourceforge.pmd.RuleViolation; * * @author raik */ +@Deprecated // will be removed with PMD 7.0.0 without replacement. See net.sourceforge.pmd.lang.dfa.report.ReportTree for details. public class ReportHTMLPrintVisitor extends ReportVisitor { @SuppressWarnings("PMD.AvoidStringBufferField") diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/lang/dfa/report/ReportTree.java b/pmd-core/src/main/java/net/sourceforge/pmd/lang/dfa/report/ReportTree.java index 18d58771d1..988a29e21d 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/lang/dfa/report/ReportTree.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/lang/dfa/report/ReportTree.java @@ -8,6 +8,14 @@ import java.util.Iterator; import net.sourceforge.pmd.RuleViolation; +/** + * + * @deprecated This class will be removed with PMD 7.0.0 without replacement. + * It is very specific for Java as it tries to recreate the package hierarchy + * of the analyzed classes and put the found violations in there. + * So it is of limited use for any other language. + */ +@Deprecated // will be removed with PMD 7.0.0 without replacement public class ReportTree implements Iterable { private PackageNode rootNode = new PackageNode(""); diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/lang/dfa/report/ReportVisitor.java b/pmd-core/src/main/java/net/sourceforge/pmd/lang/dfa/report/ReportVisitor.java index 6c9adf413b..0794187d13 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/lang/dfa/report/ReportVisitor.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/lang/dfa/report/ReportVisitor.java @@ -4,6 +4,7 @@ package net.sourceforge.pmd.lang.dfa.report; +@Deprecated // will be removed with PMD 7.0.0 without replacement. See net.sourceforge.pmd.lang.dfa.report.ReportTree for details. public abstract class ReportVisitor { public void visit(AbstractReportNode node) { diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/lang/dfa/report/ViolationNode.java b/pmd-core/src/main/java/net/sourceforge/pmd/lang/dfa/report/ViolationNode.java index 184d7d701c..4b0c7cb683 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/lang/dfa/report/ViolationNode.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/lang/dfa/report/ViolationNode.java @@ -6,6 +6,7 @@ package net.sourceforge.pmd.lang.dfa.report; import net.sourceforge.pmd.RuleViolation; +@Deprecated // will be removed with PMD 7.0.0 without replacement. See net.sourceforge.pmd.lang.dfa.report.ReportTree for details. public class ViolationNode extends AbstractReportNode { private RuleViolation ruleViolation; diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/lang/rule/AbstractRuleChainVisitor.java b/pmd-core/src/main/java/net/sourceforge/pmd/lang/rule/AbstractRuleChainVisitor.java index f08c44635a..bb3c5fb3d8 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/lang/rule/AbstractRuleChainVisitor.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/lang/rule/AbstractRuleChainVisitor.java @@ -12,7 +12,10 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.logging.Level; +import java.util.logging.Logger; +import net.sourceforge.pmd.Report; import net.sourceforge.pmd.Rule; import net.sourceforge.pmd.RuleContext; import net.sourceforge.pmd.RuleSet; @@ -27,6 +30,8 @@ import net.sourceforge.pmd.lang.ast.Node; * expressed interest in. */ public abstract class AbstractRuleChainVisitor implements RuleChainVisitor { + private static final Logger LOG = Logger.getLogger(AbstractRuleChainVisitor.class.getName()); + /** * These are all the rules participating in the RuleChain, grouped by * RuleSet. @@ -93,6 +98,17 @@ public abstract class AbstractRuleChainVisitor implements RuleChainVisitor { visits += ns.size(); } rcto.close(visits); + } catch (RuntimeException e) { + if (ctx.isIgnoreExceptions()) { + ctx.getReport().addError(new Report.ProcessingError(e, ctx.getSourceCodeFilename())); + + if (LOG.isLoggable(Level.WARNING)) { + LOG.log(Level.WARNING, "Exception applying rule " + rule.getName() + " on file " + + ctx.getSourceCodeFilename() + ", continuing with next rule", e); + } + } else { + throw e; + } } } } diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/renderers/YAHTMLRenderer.java b/pmd-core/src/main/java/net/sourceforge/pmd/renderers/YAHTMLRenderer.java index 1fd00730fa..f716b536e4 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/renderers/YAHTMLRenderer.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/renderers/YAHTMLRenderer.java @@ -4,11 +4,20 @@ package net.sourceforge.pmd.renderers; +import java.io.File; +import java.io.FileWriter; import java.io.IOException; +import java.io.PrintWriter; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.SortedMap; +import java.util.TreeMap; + +import org.apache.commons.lang3.StringUtils; import net.sourceforge.pmd.PMD; -import net.sourceforge.pmd.lang.dfa.report.ReportHTMLPrintVisitor; -import net.sourceforge.pmd.lang.dfa.report.ReportTree; +import net.sourceforge.pmd.RuleViolation; import net.sourceforge.pmd.properties.StringProperty; /** @@ -17,9 +26,10 @@ import net.sourceforge.pmd.properties.StringProperty; public class YAHTMLRenderer extends AbstractAccumulatingRenderer { public static final String NAME = "yahtml"; - public static final StringProperty OUTPUT_DIR = new StringProperty("outputDir", "Output directory.", null, 0); + private SortedMap reportNodesByPackage = new TreeMap<>(); + public YAHTMLRenderer() { // YA = Yet Another? super(NAME, "Yet Another HTML format."); @@ -31,12 +41,211 @@ public class YAHTMLRenderer extends AbstractAccumulatingRenderer { return "html"; } + private void addViolation(RuleViolation violation) { + String packageName = violation.getPackageName(); + + // report each part of the package name: e.g. net.sf.pmd.test will create nodes for + // net, net.sf, net.sf.pmd, and net.sf.pmd.test + int index = packageName.indexOf('.', 0); + while (index > -1) { + String currentPackage = packageName.substring(0, index); + ReportNode reportNode = reportNodesByPackage.get(currentPackage); + if (reportNode == null) { + reportNode = new ReportNode(currentPackage); + reportNodesByPackage.put(currentPackage, reportNode); + } + reportNode.incrementViolations(); + + int oldIndex = index; + index = packageName.indexOf('.', index + 1); + if (index == -1 && oldIndex != packageName.length()) { + index = packageName.length(); + } + } + + // add one node per class collecting the actual violations + String fqClassName = packageName + "." + violation.getClassName(); + ReportNode classNode = reportNodesByPackage.get(fqClassName); + if (classNode == null) { + classNode = new ReportNode(packageName, violation.getClassName()); + reportNodesByPackage.put(fqClassName, classNode); + } + classNode.addRuleViolation(violation); + + // count the overall violations in the root node + ReportNode rootNode = reportNodesByPackage.get(ReportNode.ROOT_NODE_NAME); + if (rootNode == null) { + rootNode = new ReportNode("Aggregate"); + reportNodesByPackage.put(ReportNode.ROOT_NODE_NAME, rootNode); + } + rootNode.incrementViolations(); + } + @Override public void end() throws IOException { String outputDir = getProperty(OUTPUT_DIR); - ReportTree tree = report.getViolationTree(); - tree.getRootNode().accept(new ReportHTMLPrintVisitor(outputDir == null ? ".." : outputDir)); + + Iterator violations = report.iterator(); + while (violations.hasNext()) { + addViolation(violations.next()); + } + + renderIndex(outputDir); + renderClasses(outputDir); + writer.write("

The HTML files are located " + (outputDir == null ? "above the project directory" : "in '" + outputDir + '\'') + ".

" + PMD.EOL); } + + private void renderIndex(String outputDir) throws IOException { + try (PrintWriter out = new PrintWriter(new FileWriter(new File(outputDir, "index.html")))) { + out.println(""); + out.println(" "); + out.println(" PMD"); + out.println(" "); + out.println(" "); + out.println("

Package View

"); + out.println(" "); + out.println(" "); + + for (ReportNode node : reportNodesByPackage.values()) { + out.print(" "); + out.println(); + } + + out.println("
PackageClass#
"); + out.print(node.getPackageName()); + out.print(" "); + if (node.hasViolations()) { + out.print(""); + out.print(node.getClassName()); + out.print(""); + } else { + out.print(node.getClassName()); + } + out.print(" "); + out.print(node.getViolationCount()); + out.print("
"); + out.println(" "); + out.println(""); + } + } + + private void renderClasses(String outputDir) throws IOException { + for (ReportNode node : reportNodesByPackage.values()) { + if (node.hasViolations()) { + try (PrintWriter out = new PrintWriter(new FileWriter(new File(outputDir, node.getClassName() + ".html")))) { + out.println(""); + out.println(" "); + out.print(" PMD - "); + out.print(node.getClassName()); + out.println(""); + out.println(" "); + out.println(" "); + out.println("

Class View

"); + out.print("

Class: "); + out.print(node.getClassName()); + out.println("

"); + out.println(" "); + out.println(" "); + for (RuleViolation violation : node.getViolations()) { + out.print(" "); + out.println(); + } + out.println("
MethodViolation
"); + out.print(violation.getMethodName()); + out.print(""); + out.print(""); + + out.print(renderViolationRow("Rule:", violation.getRule().getName())); + out.print(renderViolationRow("Description:", violation.getDescription())); + + if (StringUtils.isNotBlank(violation.getVariableName())) { + out.print(renderViolationRow("Variable:", violation.getVariableName())); + } + + out.print(renderViolationRow("Line:", violation.getEndLine() > 0 + ? violation.getBeginLine() + " and " + violation.getEndLine() + : String.valueOf(violation.getBeginLine()))); + + out.print("
"); + + out.print("
"); + out.println(" "); + out.println(""); + } + } + } + } + + private String renderViolationRow(String name, String value) { + StringBuilder row = new StringBuilder(40 + name.length() + value.length()); + row.append("") + .append(name) + .append("") + .append("") + .append(value) + .append(""); + return row.toString(); + } + + private static class ReportNode { + // deliberately starts with a space, so that it is sorted before the packages + private static final String ROOT_NODE_NAME = " "; + + private final String packageName; + private final String className; + private int violationCount; + private final List violations = new LinkedList<>(); + + ReportNode(String packageName) { + this.packageName = packageName; + this.className = "-"; + } + + ReportNode(String packageName, String className) { + this.packageName = packageName; + this.className = className; + } + + public void incrementViolations() { + violationCount++; + } + + public void addRuleViolation(RuleViolation violation) { + violations.add(violation); + } + + public String getPackageName() { + return packageName; + } + + public String getClassName() { + return className; + } + + public int getViolationCount() { + return violationCount + violations.size(); + } + + public List getViolations() { + return violations; + } + + public boolean hasViolations() { + return !violations.isEmpty(); + } + + @Override + public String toString() { + return "ReportNode[packageName=" + packageName + + ",className=" + className + + ",violationCount=" + violationCount + + ",violations=" + violations.size() + + "]"; + } + } } diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/util/log/AntLogHandler.java b/pmd-core/src/main/java/net/sourceforge/pmd/util/log/AntLogHandler.java index e1fe7702cf..26c29b702a 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/util/log/AntLogHandler.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/util/log/AntLogHandler.java @@ -7,6 +7,7 @@ package net.sourceforge.pmd.util.log; import java.io.PrintWriter; import java.io.StringWriter; import java.lang.reflect.Field; +import java.lang.reflect.Method; import java.util.logging.Formatter; import java.util.logging.Handler; import java.util.logging.Level; @@ -27,15 +28,23 @@ import org.apache.tools.ant.taskdefs.RecorderEntry; public class AntLogHandler extends Handler { private Project project; + private static final Level DEFAULT_LEVEL = Level.WARNING; + private static final Formatter FORMATTER = new PmdLogFormatter(); + + // Maps from ant's Project.MSG_* to java.util.logging.Level private static final Level[] LOG_LEVELS = { - Level.SEVERE, Level.WARNING, Level.INFO, Level.CONFIG, Level.FINEST, + Level.SEVERE, // Project.MSG_ERR=0 + Level.WARNING, // Project.MSG_WARN=1 + Level.INFO, // Project.MSG_INFO=2 + Level.CONFIG, // Project.MSG_VERBOSE=3 + Level.FINEST, // Project.MSG_DEBUG=4 }; public AntLogHandler(Project project) { this.project = project; } - + public Level getAntLogLevel() { for (final BuildListener l : project.getBuildListeners()) { Field declaredField = null; @@ -46,24 +55,34 @@ public class AntLogHandler extends Handler { declaredField = XmlLogger.class.getDeclaredField("msgOutputLevel"); } else if (l instanceof RecorderEntry) { declaredField = RecorderEntry.class.getDeclaredField("loglevel"); + } else if (l.getClass().getName().equals("org.gradle.api.internal.project.ant.AntLoggingAdapter")) { + return determineGradleLogLevel(l); } else { try { declaredField = l.getClass().getDeclaredField("logLevel"); + if (declaredField.getType() != Integer.class && declaredField.getType() != int.class) { + declaredField = null; + project.log("Unsupported build listener: " + l.getClass(), Project.MSG_DEBUG); + } } catch (final NoSuchFieldException e) { - project.log("Unsupported build listener: " + l.getClass(), Project.MSG_WARN); + project.log("Unsupported build listener: " + l.getClass(), Project.MSG_DEBUG); } } - + if (declaredField != null) { declaredField.setAccessible(true); return LOG_LEVELS[declaredField.getInt(l)]; } - } catch (final NoSuchFieldException | IllegalArgumentException | IllegalAccessException ignored) { + + } catch (final ReflectiveOperationException ignored) { // Just ignore it } } - - return Level.FINEST; + + project.log("Could not determine ant log level, no supported build listeners found. " + + "Log level is set to " + DEFAULT_LEVEL, Project.MSG_WARN); + + return DEFAULT_LEVEL; } @Override @@ -107,4 +126,42 @@ public class AntLogHandler extends Handler { public void flush() { // nothing to do } + + private Level determineGradleLogLevel(BuildListener l) { + try { + project.log("Detected gradle AntLoggingAdapter", Project.MSG_DEBUG); + Field loggerField = l.getClass().getDeclaredField("logger"); + loggerField.setAccessible(true); + // org.gradle.internal.logging.slf4j.OutputEventListenerBackedLogger + Object logger = loggerField.get(l); + + Class gradleLogLevel = l.getClass().getClassLoader().loadClass("org.gradle.api.logging.LogLevel"); + + Method isLevelAtMostMethod = logger.getClass().getDeclaredMethod("isLevelAtMost", gradleLogLevel); + isLevelAtMostMethod.setAccessible(true); + + Object[] logLevels = gradleLogLevel.getEnumConstants(); + // the log levels in gradle are declared in the order DEBUG, INFO, LIFECYCLE, WARN, QUIET, ERROR + Level[] mapping = new Level[] { + Level.FINEST, // DEBUG + Level.CONFIG, // INFO + Level.INFO, // LIFECYCLE + Level.WARNING, // WARN + Level.SEVERE, // QUIET + Level.SEVERE, // ERROR + }; + + for (int i = 0; i < Math.min(logLevels.length, mapping.length); i++) { + boolean enabled = (boolean) isLevelAtMostMethod.invoke(logger, logLevels[i]); + if (enabled) { + project.log("Current log level: " + logLevels[i] + " -> " + mapping[i], Project.MSG_DEBUG); + return mapping[i]; + } + } + } catch (ReflectiveOperationException ignored) { + // ignored + } + project.log("Could not determine log level, falling back to default: " + DEFAULT_LEVEL, Project.MSG_WARN); + return DEFAULT_LEVEL; + } } diff --git a/pmd-core/src/main/resources/rulesets/releases/660.xml b/pmd-core/src/main/resources/rulesets/releases/660.xml index bb7d39faf2..2f84d06b53 100644 --- a/pmd-core/src/main/resources/rulesets/releases/660.xml +++ b/pmd-core/src/main/resources/rulesets/releases/660.xml @@ -1,6 +1,6 @@ - diff --git a/pmd-core/src/main/resources/rulesets/releases/670.xml b/pmd-core/src/main/resources/rulesets/releases/670.xml new file mode 100644 index 0000000000..b250a1cabf --- /dev/null +++ b/pmd-core/src/main/resources/rulesets/releases/670.xml @@ -0,0 +1,16 @@ + + + + +This ruleset contains links to rules that are new in PMD v6.7.0 + + + + + + + + diff --git a/pmd-core/src/test/java/net/sourceforge/pmd/ReportTest.java b/pmd-core/src/test/java/net/sourceforge/pmd/ReportTest.java index 3d8283ca68..b19eabfddf 100644 --- a/pmd-core/src/test/java/net/sourceforge/pmd/ReportTest.java +++ b/pmd-core/src/test/java/net/sourceforge/pmd/ReportTest.java @@ -88,14 +88,15 @@ public class ReportTest implements ThreadSafeReportListener { public void testSortedReportLine() throws IOException { Report r = new Report(); RuleContext ctx = new RuleContext(); - ctx.setSourceCodeFilename("foo1"); - Node s = getNode(10, 5); - Rule rule1 = new MockRule("rule2", "rule2", "msg", "rulesetname"); - r.addRuleViolation(new ParametricRuleViolation<>(rule1, ctx, s, rule1.getMessage())); - ctx.setSourceCodeFilename("foo2"); - Node s1 = getNode(20, 5); - Rule rule2 = new MockRule("rule1", "rule1", "msg", "rulesetname"); - r.addRuleViolation(new ParametricRuleViolation<>(rule2, ctx, s1, rule2.getMessage())); + ctx.setSourceCodeFilename("foo1"); // same file!! + Node node1 = getNode(20, 5); // line 20: after rule2 violation + Rule rule1 = new MockRule("rule1", "rule1", "msg", "rulesetname"); + r.addRuleViolation(new ParametricRuleViolation<>(rule1, ctx, node1, rule1.getMessage())); + + ctx.setSourceCodeFilename("foo1"); // same file!! + Node node2 = getNode(10, 5); // line 10: before rule1 violation + Rule rule2 = new MockRule("rule2", "rule2", "msg", "rulesetname"); + r.addRuleViolation(new ParametricRuleViolation<>(rule2, ctx, node2, rule2.getMessage())); Renderer rend = new XMLRenderer(); String result = render(rend, r); assertTrue("sort order wrong", result.indexOf("rule2") < result.indexOf("rule1")); @@ -172,8 +173,8 @@ public class ReportTest implements ThreadSafeReportListener { parent.testingOnlySetBeginLine(line); parent.testingOnlySetBeginColumn(column); s.jjtSetParent(parent); - s.testingOnlySetBeginLine(10); - s.testingOnlySetBeginColumn(5); + s.testingOnlySetBeginLine(line); + s.testingOnlySetBeginColumn(column); return s; } diff --git a/pmd-core/src/test/java/net/sourceforge/pmd/RuleSetTest.java b/pmd-core/src/test/java/net/sourceforge/pmd/RuleSetTest.java index 429136f67b..3d47494e86 100644 --- a/pmd-core/src/test/java/net/sourceforge/pmd/RuleSetTest.java +++ b/pmd-core/src/test/java/net/sourceforge/pmd/RuleSetTest.java @@ -551,4 +551,76 @@ public class RuleSetTest { context.setIgnoreExceptions(false); ruleset.apply(makeCompilationUnits(), context); } + + @Test + public void ruleExceptionShouldNotStopProcessingFile() { + RuleSet ruleset = createRuleSetBuilder("ruleExceptionShouldBeReported").addRule(new MockRule() { + @Override + public void apply(List nodes, RuleContext ctx) { + throw new RuntimeException("Test exception while applying rule"); + } + }).addRule(new MockRule() { + @Override + public void apply(List nodes, RuleContext ctx) { + for (Node node : nodes) { + addViolationWithMessage(ctx, node, "Test violation of the second rule in the ruleset"); + } + } + }).build(); + RuleContext context = new RuleContext(); + context.setReport(new Report()); + context.setLanguageVersion(LanguageRegistry.getLanguage(DummyLanguageModule.NAME).getDefaultVersion()); + context.setSourceCodeFilename(RuleSetTest.class.getName() + ".ruleExceptionShouldBeReported"); + context.setIgnoreExceptions(true); // the default + ruleset.apply(makeCompilationUnits(), context); + + assertTrue("Report should have processing errors", context.getReport().hasErrors()); + List errors = CollectionUtil.toList(context.getReport().errors()); + assertEquals("Errors expected", 1, errors.size()); + assertEquals("Wrong error message", "Test exception while applying rule", errors.get(0).getMsg()); + assertTrue("Should be a RuntimeException", errors.get(0).getError() instanceof RuntimeException); + + assertEquals("There should be a violation", 1, context.getReport().size()); + } + + @Test + public void ruleExceptionShouldNotStopProcessingFileWithRuleChain() { + RuleSet ruleset = createRuleSetBuilder("ruleExceptionShouldBeReported").addRule(new MockRule() { + { + addRuleChainVisit("dummyNode"); + } + + @Override + public void apply(List nodes, RuleContext ctx) { + throw new RuntimeException("Test exception while applying rule"); + } + }).addRule(new MockRule() { + { + addRuleChainVisit("dummyNode"); + } + + @Override + public void apply(List nodes, RuleContext ctx) { + for (Node node : nodes) { + addViolationWithMessage(ctx, node, "Test violation of the second rule in the ruleset"); + } + } + }).build(); + RuleContext context = new RuleContext(); + context.setReport(new Report()); + context.setLanguageVersion(LanguageRegistry.getLanguage(DummyLanguageModule.NAME).getDefaultVersion()); + context.setSourceCodeFilename(RuleSetTest.class.getName() + ".ruleExceptionShouldBeReported"); + context.setIgnoreExceptions(true); // the default + RuleSets rulesets = new RuleSets(ruleset); + rulesets.apply(makeCompilationUnits(), context, LanguageRegistry.getLanguage(DummyLanguageModule.NAME)); + + assertTrue("Report should have processing errors", context.getReport().hasErrors()); + List errors = CollectionUtil.toList(context.getReport().errors()); + assertEquals("Errors expected", 1, errors.size()); + assertEquals("Wrong error message", "Test exception while applying rule", errors.get(0).getMsg()); + assertTrue("Should be a RuntimeException", errors.get(0).getError() instanceof RuntimeException); + + assertEquals("There should be a violation", 1, context.getReport().size()); + } + } diff --git a/pmd-core/src/test/java/net/sourceforge/pmd/renderers/AbstractRendererTst.java b/pmd-core/src/test/java/net/sourceforge/pmd/renderers/AbstractRendererTst.java index f0e2b96773..36a0960155 100644 --- a/pmd-core/src/test/java/net/sourceforge/pmd/renderers/AbstractRendererTst.java +++ b/pmd-core/src/test/java/net/sourceforge/pmd/renderers/AbstractRendererTst.java @@ -37,7 +37,7 @@ public abstract class AbstractRendererTst { public String getExpectedError(ProcessingError error) { return ""; } - + public String getExpectedError(ConfigurationError error) { return ""; } @@ -68,7 +68,7 @@ public abstract class AbstractRendererTst { return report; } - private RuleViolation newRuleViolation(int endColumn) { + protected RuleViolation newRuleViolation(int endColumn) { DummyNode node = createNode(endColumn); RuleContext ctx = new RuleContext(); ctx.setSourceCodeFilename(getSourceCodeFilename()); @@ -127,7 +127,7 @@ public abstract class AbstractRendererTst { String actual = ReportTest.render(getRenderer(), rep); assertEquals(filter(getExpectedError(err)), filter(actual)); } - + @Test public void testConfigError() throws Exception { Report rep = new Report(); diff --git a/pmd-core/src/test/java/net/sourceforge/pmd/renderers/YAHTMLRendererTest.java b/pmd-core/src/test/java/net/sourceforge/pmd/renderers/YAHTMLRendererTest.java index 06fe984d39..7e968a7c30 100644 --- a/pmd-core/src/test/java/net/sourceforge/pmd/renderers/YAHTMLRendererTest.java +++ b/pmd-core/src/test/java/net/sourceforge/pmd/renderers/YAHTMLRendererTest.java @@ -4,15 +4,31 @@ package net.sourceforge.pmd.renderers; -import java.io.File; -import java.io.IOException; +import static org.junit.Assert.assertEquals; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; + +import org.apache.commons.io.IOUtils; import org.junit.After; import org.junit.Before; +import org.junit.Test; +import net.sourceforge.pmd.FooRule; import net.sourceforge.pmd.PMD; +import net.sourceforge.pmd.Report; import net.sourceforge.pmd.Report.ConfigurationError; import net.sourceforge.pmd.Report.ProcessingError; +import net.sourceforge.pmd.ReportTest; +import net.sourceforge.pmd.RuleContext; +import net.sourceforge.pmd.RuleViolation; +import net.sourceforge.pmd.lang.ast.DummyNode; +import net.sourceforge.pmd.lang.ast.Node; +import net.sourceforge.pmd.lang.rule.ParametricRuleViolation; public class YAHTMLRendererTest extends AbstractRendererTst { @@ -51,6 +67,50 @@ public class YAHTMLRendererTest extends AbstractRendererTst { dir.delete(); } + private RuleViolation newRuleViolation(int endColumn, final String packageNameArg, final String classNameArg) { + DummyNode node = createNode(endColumn); + RuleContext ctx = new RuleContext(); + ctx.setSourceCodeFilename(getSourceCodeFilename()); + return new ParametricRuleViolation(new FooRule(), ctx, node, "blah") { + { + packageName = packageNameArg; + className = classNameArg; + } + }; + } + + @Override + protected RuleViolation newRuleViolation(int endColumn) { + return newRuleViolation(endColumn, "net.sf.pmd.test", "YAHTMLSampleClass"); + } + + @Test + public void testReportMultipleViolations() throws Exception { + Report report = new Report(); + report.addRuleViolation(newRuleViolation(1, "net.sf.pmd.test", "YAHTMLSampleClass1")); + report.addRuleViolation(newRuleViolation(2, "net.sf.pmd.test", "YAHTMLSampleClass1")); + report.addRuleViolation(newRuleViolation(1, "net.sf.pmd.other", "YAHTMLSampleClass2")); + String actual = ReportTest.render(getRenderer(), report); + assertEquals(filter(getExpected()), filter(actual)); + + String[] htmlFiles = new File(outputDir).list(); + assertEquals(3, htmlFiles.length); + Arrays.sort(htmlFiles); + assertEquals("YAHTMLSampleClass1.html", htmlFiles[0]); + assertEquals("YAHTMLSampleClass2.html", htmlFiles[1]); + assertEquals("index.html", htmlFiles[2]); + + for (String file : htmlFiles) { + try (FileInputStream in = new FileInputStream(new File(outputDir, file)); + InputStream expectedIn = YAHTMLRendererTest.class.getResourceAsStream("yahtml/" + file)) { + String data = IOUtils.toString(in, StandardCharsets.UTF_8); + String expected = IOUtils.toString(expectedIn, StandardCharsets.UTF_8); + + assertEquals("File " + file + " is different", expected, data); + } + } + } + @Override public Renderer getRenderer() { Renderer result = new YAHTMLRenderer(); diff --git a/pmd-core/src/test/resources/net/sourceforge/pmd/renderers/yahtml/YAHTMLSampleClass1.html b/pmd-core/src/test/resources/net/sourceforge/pmd/renderers/yahtml/YAHTMLSampleClass1.html new file mode 100644 index 0000000000..290e7104d9 --- /dev/null +++ b/pmd-core/src/test/resources/net/sourceforge/pmd/renderers/yahtml/YAHTMLSampleClass1.html @@ -0,0 +1,14 @@ + + + PMD - YAHTMLSampleClass1 + + +

Class View

+

Class: YAHTMLSampleClass1

+ + + + +
MethodViolation
Rule:Foo
Description:blah
Line:1 and 1
Rule:Foo
Description:blah
Line:1 and 1
+ + diff --git a/pmd-core/src/test/resources/net/sourceforge/pmd/renderers/yahtml/YAHTMLSampleClass2.html b/pmd-core/src/test/resources/net/sourceforge/pmd/renderers/yahtml/YAHTMLSampleClass2.html new file mode 100644 index 0000000000..df94031c6a --- /dev/null +++ b/pmd-core/src/test/resources/net/sourceforge/pmd/renderers/yahtml/YAHTMLSampleClass2.html @@ -0,0 +1,13 @@ + + + PMD - YAHTMLSampleClass2 + + +

Class View

+

Class: YAHTMLSampleClass2

+ + + +
MethodViolation
Rule:Foo
Description:blah
Line:1 and 1
+ + diff --git a/pmd-core/src/test/resources/net/sourceforge/pmd/renderers/yahtml/index.html b/pmd-core/src/test/resources/net/sourceforge/pmd/renderers/yahtml/index.html new file mode 100644 index 0000000000..65b0d3a738 --- /dev/null +++ b/pmd-core/src/test/resources/net/sourceforge/pmd/renderers/yahtml/index.html @@ -0,0 +1,19 @@ + + + PMD + + +

Package View

+ + + + + + + + + + +
PackageClass#
Aggregate - 3
net - 3
net.sf - 3
net.sf.pmd - 3
net.sf.pmd.other - 1
net.sf.pmd.other YAHTMLSampleClass2 1
net.sf.pmd.test - 2
net.sf.pmd.test YAHTMLSampleClass1 2
+ + diff --git a/pmd-cpp/pom.xml b/pmd-cpp/pom.xml index d07a1d8969..52db8345e4 100644 --- a/pmd-cpp/pom.xml +++ b/pmd-cpp/pom.xml @@ -7,7 +7,7 @@ net.sourceforge.pmd pmd - 6.6.0-SNAPSHOT + 6.7.0-SNAPSHOT diff --git a/pmd-cpp/src/main/java/net/sourceforge/pmd/cpd/CPPLanguage.java b/pmd-cpp/src/main/java/net/sourceforge/pmd/cpd/CPPLanguage.java index e4462cba84..ad27da1536 100644 --- a/pmd-cpp/src/main/java/net/sourceforge/pmd/cpd/CPPLanguage.java +++ b/pmd-cpp/src/main/java/net/sourceforge/pmd/cpd/CPPLanguage.java @@ -16,7 +16,12 @@ public class CPPLanguage extends AbstractLanguage { * for c/c++ files. */ public CPPLanguage() { + this(System.getProperties()); + } + + public CPPLanguage(Properties properties) { super("C++", "cpp", new CPPTokenizer(), ".h", ".hpp", ".hxx", ".c", ".cpp", ".cxx", ".cc", ".C"); + setProperties(properties); } /* diff --git a/pmd-cs/pom.xml b/pmd-cs/pom.xml index 328c06e801..72e8afcc7c 100644 --- a/pmd-cs/pom.xml +++ b/pmd-cs/pom.xml @@ -7,7 +7,7 @@ net.sourceforge.pmd pmd - 6.6.0-SNAPSHOT + 6.7.0-SNAPSHOT diff --git a/pmd-dist/pom.xml b/pmd-dist/pom.xml index 9da1483ff1..c09618a706 100644 --- a/pmd-dist/pom.xml +++ b/pmd-dist/pom.xml @@ -8,7 +8,7 @@ net.sourceforge.pmd pmd - 6.6.0-SNAPSHOT + 6.7.0-SNAPSHOT diff --git a/pmd-dist/src/main/scripts/run.sh b/pmd-dist/src/main/scripts/run.sh index fcd9f0cb18..5cd03fb76a 100755 --- a/pmd-dist/src/main/scripts/run.sh +++ b/pmd-dist/src/main/scripts/run.sh @@ -75,8 +75,16 @@ check_lib_dir() { } jre_specific_vm_options() { + full_ver=$(java -version 2>&1) # java_ver is eg "18" for java 1.8, "90" for java 9.0, "100" for java 10.0.x - java_ver=$(java -version 2>&1 | sed -n -e 's/-ea/.0.0/i' -e 's/^.* version "\(.*\)\.\(.*\)\..*".*$/\1\2/p') + java_ver=$(echo $full_ver | sed -n '{ + # replace early access versions, e.g. 11-ea with 11.0.0 + s/-ea/.0.0/ + # replace versions such as 10 with 10.0.0 + s/version "\([0-9]\{1,\}\)"/version "\1.0.0"/ + # extract the major and minor parts of the version + s/^.* version "\(.*\)\.\(.*\)\..*".*$/\1\2/p + }') options="" if [ "$java_ver" -ge 90 ] && [ "${APPNAME}" = "designer" ] diff --git a/pmd-doc/pom.xml b/pmd-doc/pom.xml index 642a6bd8f2..7c9ddc5239 100644 --- a/pmd-doc/pom.xml +++ b/pmd-doc/pom.xml @@ -8,7 +8,7 @@ net.sourceforge.pmd pmd - 6.6.0-SNAPSHOT + 6.7.0-SNAPSHOT diff --git a/pmd-fortran/pom.xml b/pmd-fortran/pom.xml index 38c60d8389..d62194c4f9 100644 --- a/pmd-fortran/pom.xml +++ b/pmd-fortran/pom.xml @@ -7,7 +7,7 @@ net.sourceforge.pmd pmd - 6.6.0-SNAPSHOT + 6.7.0-SNAPSHOT diff --git a/pmd-go/pom.xml b/pmd-go/pom.xml index 39ea74070f..13635af528 100644 --- a/pmd-go/pom.xml +++ b/pmd-go/pom.xml @@ -7,7 +7,7 @@ net.sourceforge.pmd pmd - 6.6.0-SNAPSHOT + 6.7.0-SNAPSHOT diff --git a/pmd-groovy/pom.xml b/pmd-groovy/pom.xml index 5cb8b3de2a..d7f804400a 100644 --- a/pmd-groovy/pom.xml +++ b/pmd-groovy/pom.xml @@ -7,7 +7,7 @@ net.sourceforge.pmd pmd - 6.6.0-SNAPSHOT + 6.7.0-SNAPSHOT diff --git a/pmd-java/etc/grammar/Java.jjt b/pmd-java/etc/grammar/Java.jjt index 6593c7f7a1..ed713afee8 100644 --- a/pmd-java/etc/grammar/Java.jjt +++ b/pmd-java/etc/grammar/Java.jjt @@ -2206,10 +2206,38 @@ void LambdaExpression() : { checkForBadLambdaUsage(); } { VariableDeclaratorId() "->" ( Expression() | Block() ) -| LOOKAHEAD(3) FormalParameters() "->" ( Expression() | Block() ) +| LOOKAHEAD(3) LambdaParameters() "->" ( Expression() | Block() ) | LOOKAHEAD(3) "(" VariableDeclaratorId() ( "," VariableDeclaratorId() )* ")" "->" ( Expression() | Block() ) } +void LambdaParameters() #FormalParameters : +{} +{ + "(" [ LambdaParameterList() ] ")" +} + +void LambdaParameterList() #void : +{} +{ + LambdaParameter() ( "," LambdaParameter() )* +} + +void LambdaParameter() #FormalParameter : +{} +{ + ( "final" {jjtThis.setFinal(true);} | Annotation() )* + LambdaParameterType() + [ "..." {checkForBadVariableArgumentsUsage();} {jjtThis.setVarargs();} ] + VariableDeclaratorId() +} + +void LambdaParameterType() #void : +{} +{ + LOOKAHEAD( { jdkVersion >= 11 && isKeyword("var") } ) + | Type() +} + void PrimarySuffix() : {Token t;} { LOOKAHEAD(2) "." "this" diff --git a/pmd-java/pom.xml b/pmd-java/pom.xml index 5f8b021191..d43dbd7a6a 100644 --- a/pmd-java/pom.xml +++ b/pmd-java/pom.xml @@ -7,7 +7,7 @@ net.sourceforge.pmd pmd - 6.6.0-SNAPSHOT + 6.7.0-SNAPSHOT diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/JavaLanguageModule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/JavaLanguageModule.java index aa1503ca28..f5fb17220a 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/JavaLanguageModule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/JavaLanguageModule.java @@ -24,7 +24,8 @@ public class JavaLanguageModule extends BaseLanguageModule { addVersion("1.7", new JavaLanguageHandler(7), false); addVersion("1.8", new JavaLanguageHandler(8), false); addVersion("9", new JavaLanguageHandler(9), false); - addVersion("10", new JavaLanguageHandler(10), true); + addVersion("10", new JavaLanguageHandler(10), false); + addVersion("11", new JavaLanguageHandler(11), true); } } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTClassOrInterfaceBody.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTClassOrInterfaceBody.java index a7d765ffde..838ee1b1b8 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTClassOrInterfaceBody.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTClassOrInterfaceBody.java @@ -5,6 +5,16 @@ package net.sourceforge.pmd.lang.java.ast; +/** + * Represents the body of a {@linkplain ASTClassOrInterfaceDeclaration class or interface declaration}. + * This includes anonymous classes, including those defined within an {@linkplain ASTEnumConstant enum constant}. + * + *
+ *
+ * ClassOrInterfaceBody ::=  "{"  {@linkplain ASTClassOrInterfaceBodyDeclaration ClassOrInterfaceBodyDeclaration}* "}"
+ *
+ * 
+ */ public class ASTClassOrInterfaceBody extends AbstractJavaNode { public ASTClassOrInterfaceBody(int id) { super(id); diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTClassOrInterfaceDeclaration.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTClassOrInterfaceDeclaration.java index 74851a3547..9f1ed15a26 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTClassOrInterfaceDeclaration.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTClassOrInterfaceDeclaration.java @@ -13,17 +13,17 @@ import net.sourceforge.pmd.util.CollectionUtil; /** - * Represents class and interface declarations. + * Represents class and interface declarations. This is a {@linkplain Node#isFindBoundary() find boundary} + * for tree traversal methods. * *
  *
- * ClassOrInterfaceDeclaration ::=
- *          ( "class" | "interface" )
- *          <IDENTIFIER>
- *          [ TypeParameters ]
- *          [ ExtendsList ]
- *          [ ImplementsList ]
- *          ClassOrInterfaceBody
+ * ClassOrInterfaceDeclaration ::= ( "class" | "interface" )
+ *                                 <IDENTIFIER>
+ *                                 {@linkplain ASTTypeParameters TypeParameters}?
+ *                                 {@linkplain ASTExtendsList ExtendsList}?
+ *                                 {@linkplain ASTImplementsList ImplementsList}?
+ *                                 {@linkplain ASTClassOrInterfaceBody ClassOrInterfaceBody}
  * 
* */ diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTEnumConstant.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTEnumConstant.java index 5c8b9085bb..30c6337921 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTEnumConstant.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTEnumConstant.java @@ -8,6 +8,17 @@ package net.sourceforge.pmd.lang.java.ast; import net.sourceforge.pmd.lang.java.qname.JavaTypeQualifiedName; +/** + * Represents an enum constant declaration within an {@linkplain ASTEnumDeclaration enum declaration}. + * + *

TODO since there's no VariableDeclaratorId, this might not play well with the symbol table! + * + *

+ *
+ * EnumConstant ::= <IDENTIFIER> {@linkplain ASTArguments Arguments}? {@linkplain ASTClassOrInterfaceBody ClassOrInterfaceBody}?
+ *
+ * 
+ */ public class ASTEnumConstant extends AbstractJavaNode implements JavaQualifiableNode { private JavaTypeQualifiedName qualifiedName; diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTFieldDeclaration.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTFieldDeclaration.java index 877d327f52..15e396118d 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTFieldDeclaration.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTFieldDeclaration.java @@ -5,10 +5,36 @@ package net.sourceforge.pmd.lang.java.ast; +import java.util.Iterator; + import net.sourceforge.pmd.lang.ast.SignedNode; import net.sourceforge.pmd.lang.java.multifile.signature.JavaFieldSignature; -public class ASTFieldDeclaration extends AbstractJavaAccessTypeNode implements Dimensionable, SignedNode { + +/** + * Represents a field declaration in the body of a type declaration. + * + *

This statement may define several variables, possibly of different types (see {@link ASTVariableDeclaratorId#getType()}). + * The nodes corresponding to the declared variables are accessible through {@link #iterator()}. + * + *

{@link AccessNode} methods take into account the syntactic context of the + * declaration, e.g. {@link #isPublic()} will always return true if the field is + * declared inside an interface, regardless of whether the {@code public} modifier + * was specified or not. If you want to know whether the modifier was explicitly + * stated, use e.g {@link #isSyntacticallyPublic()}. + * + *

+ *
+ * FieldDeclaration ::= Modifiers {@linkplain ASTType Type} {@linkplain ASTVariableDeclarator VariableDeclarator} ( "," {@linkplain ASTVariableDeclarator VariableDeclarator} )*
+ *
+ * Modifiers        ::= "public" | "static" | "protected" | "private"
+ *                    | "final"  | "abstract" | "synchronized"
+ *                    | "native" | "transient" | "volatile" | "strictfp"
+ *                    | "default"  | {@linkplain ASTAnnotation Annotation}
+ *
+ * 
+ */ +public class ASTFieldDeclaration extends AbstractJavaAccessTypeNode implements Dimensionable, SignedNode, Iterable { private JavaFieldSignature signature; @@ -152,4 +178,15 @@ public class ASTFieldDeclaration extends AbstractJavaAccessTypeNode implements D return signature; } + + + /** + * Returns an iterator over the ids of the fields + * declared in this statement. + */ + @Override + public Iterator iterator() { + return ASTVariableDeclarator.iterateIds(this); + } + } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTFormalParameter.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTFormalParameter.java index 681b4268bd..b2d5420c4c 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTFormalParameter.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTFormalParameter.java @@ -14,6 +14,7 @@ import net.sourceforge.pmd.lang.java.typeresolution.typedefinition.JavaTypeDefin * production of {@link ASTMethodDeclarator} to represent a * method's formal parameter. Also used in the {@link ASTCatchStatement} * production to represent the declared exception variable. + * Also used in LambdaExpressions for the LambdaParameters. *
  *      ( "final" | Annotation )* Type ( "|" Type )* [ "..." ] VariableDeclaratorId
  * 
@@ -63,6 +64,16 @@ public class ASTFormalParameter extends AbstractJavaAccessTypeNode implements Di return getVariableDeclaratorId().isExplicitReceiverParameter(); } + /** + * If true, this formal parameter represents one without explit types. + * This can appear as part of a lambda expression with java11 using "var". + * + * @see ASTVariableDeclaratorId#isTypeInferred() + */ + public boolean isTypeInferred() { + return getTypeNode() == null; + } + @Override public Object jjtAccept(JavaParserVisitor visitor, Object data) { return visitor.visit(this, data); @@ -93,7 +104,9 @@ public class ASTFormalParameter extends AbstractJavaAccessTypeNode implements Di */ @Override public boolean isArray() { - return isVarargs() || getTypeNode().isArray() || getVariableDeclaratorId().isArray(); + return isVarargs() + || getTypeNode() != null && getTypeNode().isArray() + || getVariableDeclaratorId().isArray(); } @Override diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTLiteral.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTLiteral.java index 16e0d4db5b..2a18732390 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTLiteral.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTLiteral.java @@ -5,6 +5,7 @@ package net.sourceforge.pmd.lang.java.ast; +import java.math.BigInteger; import java.util.Locale; import java.util.regex.Pattern; @@ -146,11 +147,14 @@ public class ASTLiteral extends AbstractJavaTypeNode { } public int getValueAsInt() { - return (int) getValueAsLong(); // the downcast allows to parse 0x80000000+ numbers as negative instead of a NumberFormatException + // the downcast allows to parse 0x80000000+ numbers as negative instead of a NumberFormatException + return (int) getValueAsLong(); } public long getValueAsLong() { - return Long.parseLong(stripIntValue(), getIntBase()); + // Using BigInteger to allow parsing 0x8000000000000000+ numbers as negative instead of a NumberFormatException + BigInteger bigInt = new BigInteger(stripIntValue(), getIntBase()); + return bigInt.longValue(); } public float getValueAsFloat() { diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTLocalVariableDeclaration.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTLocalVariableDeclaration.java index 58d741a26e..d0089bdb32 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTLocalVariableDeclaration.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTLocalVariableDeclaration.java @@ -18,11 +18,11 @@ import net.sourceforge.pmd.Rule; *

This statement may define several variables, possibly of different types (see {@link ASTVariableDeclaratorId#getType()}). * The nodes corresponding to the declared variables are accessible through {@link #iterator()}. * - *

+ *

  *
- * LocalVariableDeclaration ::=  ( "final" | {@linkplain ASTAnnotation Annotation} )* {@linkplain ASTType Type} {@linkplain ASTVariableDeclarator VariableDeclarator} ( "," {@linkplain ASTVariableDeclarator VariableDeclarator} )*
+ * LocalVariableDeclaration ::= ( "final" | {@linkplain ASTAnnotation Annotation} )* {@linkplain ASTType Type} {@linkplain ASTVariableDeclarator VariableDeclarator} ( "," {@linkplain ASTVariableDeclarator VariableDeclarator} )*
  *
- * 

+ *
*/ public class ASTLocalVariableDeclaration extends AbstractJavaAccessNode implements Dimensionable, CanSuppressWarnings, Iterable { @@ -127,6 +127,6 @@ public class ASTLocalVariableDeclaration extends AbstractJavaAccessNode implemen */ @Override public Iterator iterator() { - return new NodeChildrenIterator<>(this, ASTVariableDeclaratorId.class); + return ASTVariableDeclarator.iterateIds(this); } } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTVariableDeclarator.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTVariableDeclarator.java index 60fdd00c24..7d28205df6 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTVariableDeclarator.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTVariableDeclarator.java @@ -5,20 +5,96 @@ package net.sourceforge.pmd.lang.java.ast; +import java.util.Iterator; + +import net.sourceforge.pmd.lang.ast.Node; + + +/** + * Groups a variable ID and its initializer if it exists. + * May be found as a child of {@linkplain ASTFieldDeclaration field declarations} and + * {@linkplain ASTLocalVariableDeclaration local variable declarations}. + * + *
+ *
+ * VariableDeclarator ::= {@linkplain ASTVariableDeclaratorId VariableDeclaratorId} ( "=" {@linkplain ASTVariableInitializer VariableInitializer} )?
+ *
+ * 
+ */ public class ASTVariableDeclarator extends AbstractJavaTypeNode { public ASTVariableDeclarator(int id) { super(id); } + public ASTVariableDeclarator(JavaParser p, int id) { super(p, id); } - /** - * Accept the visitor. * - */ + @Override public Object jjtAccept(JavaParserVisitor visitor, Object data) { return visitor.visit(this, data); } + + + /** + * Returns the name of the declared variable. + */ + public String getName() { + // first child will be VariableDeclaratorId + return jjtGetChild(0).getImage(); + } + + + /** + * Returns the id of the declared variable. + */ + public ASTVariableDeclaratorId getVariableId() { + return (ASTVariableDeclaratorId) jjtGetChild(0); + } + + + /** + * Returns true if the declared variable is initialized. + * Otherwise, {@link #getInitializer()} returns null. + */ + public boolean hasInitializer() { + return jjtGetNumChildren() > 1; + } + + + /** + * Returns the initializer, of the variable, or null if it doesn't exist. + */ + public ASTVariableInitializer getInitializer() { + return hasInitializer() ? (ASTVariableInitializer) jjtGetChild(1) : null; + } + + + /* only for LocalVarDeclaration and FieldDeclaration */ + static Iterator iterateIds(Node parent) { + // TODO this can be made clearer with iterator mapping (Java 8) + final Iterator declarators = new NodeChildrenIterator<>(parent, ASTVariableDeclarator.class); + + return new Iterator() { + @Override + public boolean hasNext() { + return declarators.hasNext(); + } + + + @Override + public ASTVariableDeclaratorId next() { + return declarators.next().getVariableId(); + } + + + @Override + public void remove() { + throw new UnsupportedOperationException(); + } + }; + } + } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTVariableDeclaratorId.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTVariableDeclaratorId.java index 8322be0b26..9e4cb65496 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTVariableDeclaratorId.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTVariableDeclaratorId.java @@ -205,8 +205,7 @@ public class ASTVariableDeclaratorId extends AbstractJavaTypeNode implements Dim * since the type node is absent. */ public boolean isTypeInferred() { - // TODO think about supporting var for lambda parameters - return isLambdaParamWithNoType() || isLocalVariableTypeInferred(); + return isLambdaParamWithNoType() || isLocalVariableTypeInferred() || isLambdaTypeInferred(); } @@ -222,6 +221,11 @@ public class ASTVariableDeclaratorId extends AbstractJavaTypeNode implements Dim return false; } + private boolean isLambdaTypeInferred() { + return getNthParent(3) instanceof ASTLambdaExpression + && jjtGetParent().getFirstChildOfType(ASTType.class) == null; + } + /** * Returns the first child of the node returned by {@link #getTypeNode()}. * The image of that node can usually be interpreted as the image of the diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/impl/visitors/NpathBaseVisitor.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/impl/visitors/NpathBaseVisitor.java index ba88b112f6..328d64b23b 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/impl/visitors/NpathBaseVisitor.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/impl/visitors/NpathBaseVisitor.java @@ -39,7 +39,16 @@ public class NpathBaseVisitor extends JavaParserVisitorReducedAdapter { for (int i = 0; i < node.jjtGetNumChildren(); i++) { JavaNode n = (JavaNode) node.jjtGetChild(i); - product *= (Integer) n.jjtAccept(this, data); + int childComplexity = (int) n.jjtAccept(this, data); + + int newProduct = product * childComplexity; + if (newProduct >= product) { + product = newProduct; + } else { + // Overflow happened + product = Integer.MAX_VALUE; + break; + } } return product; @@ -52,7 +61,16 @@ public class NpathBaseVisitor extends JavaParserVisitorReducedAdapter { for (int i = 0; i < node.jjtGetNumChildren(); i++) { JavaNode n = (JavaNode) node.jjtGetChild(i); - sum += (Integer) n.jjtAccept(this, data); + int childComplexity = (int) n.jjtAccept(this, data); + + int newSum = sum + childComplexity; + if (newSum >= sum) { + sum = newSum; + } else { + // Overflow happened + sum = Integer.MAX_VALUE; + break; + } } return sum; @@ -81,7 +99,7 @@ public class NpathBaseVisitor extends JavaParserVisitorReducedAdapter { int complexity = node.hasElse() ? 0 : 1; for (ASTStatement element : statementChildren) { - complexity += (Integer) element.jjtAccept(this, data); + complexity += (int) element.jjtAccept(this, data); } int boolCompIf = CycloMetric.booleanExpressionComplexity(node.getFirstChildOfType(ASTExpression.class)); @@ -95,7 +113,7 @@ public class NpathBaseVisitor extends JavaParserVisitorReducedAdapter { int boolCompWhile = CycloMetric.booleanExpressionComplexity(node.getFirstChildOfType(ASTExpression.class)); - int nPathWhile = (Integer) node.getFirstChildOfType(ASTStatement.class).jjtAccept(this, data); + int nPathWhile = (int) node.getFirstChildOfType(ASTStatement.class).jjtAccept(this, data); return boolCompWhile + nPathWhile + 1; } @@ -107,7 +125,7 @@ public class NpathBaseVisitor extends JavaParserVisitorReducedAdapter { int boolCompDo = CycloMetric.booleanExpressionComplexity(node.getFirstChildOfType(ASTExpression.class)); - int nPathDo = (Integer) node.getFirstChildOfType(ASTStatement.class).jjtAccept(this, data); + int nPathDo = (int) node.getFirstChildOfType(ASTStatement.class).jjtAccept(this, data); return boolCompDo + nPathDo + 1; } @@ -119,7 +137,7 @@ public class NpathBaseVisitor extends JavaParserVisitorReducedAdapter { int boolCompFor = CycloMetric.booleanExpressionComplexity(node.getFirstDescendantOfType(ASTExpression.class)); - int nPathFor = (Integer) node.getFirstChildOfType(ASTStatement.class).jjtAccept(this, data); + int nPathFor = (int) node.getFirstChildOfType(ASTStatement.class).jjtAccept(this, data); return boolCompFor + nPathFor + 1; } @@ -162,7 +180,7 @@ public class NpathBaseVisitor extends JavaParserVisitorReducedAdapter { npath += caseRange; caseRange = 1; } else { - Integer complexity = (Integer) n.jjtAccept(this, data); + int complexity = (int) n.jjtAccept(this, data); caseRange *= complexity; } } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/qname/JavaTypeQualifiedName.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/qname/JavaTypeQualifiedName.java index 0556ca050b..9df796b3ad 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/qname/JavaTypeQualifiedName.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/qname/JavaTypeQualifiedName.java @@ -41,9 +41,9 @@ public final class JavaTypeQualifiedName extends JavaQualifiedName { private Class representedType; private boolean typeLoaded; - private ClassLoader classLoader; + private final ClassLoader classLoader; - JavaTypeQualifiedName(ImmutableList packages, ImmutableList classes, ImmutableList localIndices) { + JavaTypeQualifiedName(ImmutableList packages, ImmutableList classes, ImmutableList localIndices, ClassLoader classLoader) { Objects.requireNonNull(packages); Objects.requireNonNull(classes); Objects.requireNonNull(localIndices); @@ -55,16 +55,8 @@ public final class JavaTypeQualifiedName extends JavaQualifiedName { this.packages = packages; this.classes = classes; this.localIndices = localIndices; - } - - /** - * Sets the classloader to be used when resolving the actual type of this qualified name. - * @see #getType() - */ - JavaTypeQualifiedName withClassLoader(ClassLoader classLoader) { - this.classLoader = classLoader; - return this; + this.classLoader = classLoader; // classLoader may be null } @@ -177,17 +169,15 @@ public final class JavaTypeQualifiedName extends JavaQualifiedName { */ public Class getType() { synchronized (this) { - if (typeLoaded) { - return representedType; - } else { + if (!typeLoaded) { typeLoaded = true; try { representedType = loadType(); } catch (ClassNotFoundException e) { representedType = null; } - return representedType; } + return representedType; } } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/qname/QualifiedNameFactory.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/qname/QualifiedNameFactory.java index a074f9f15a..b57e5b2549 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/qname/QualifiedNameFactory.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/qname/QualifiedNameFactory.java @@ -15,6 +15,8 @@ import net.sourceforge.pmd.lang.java.qname.ImmutableList.ListFactory; /** * Static factory methods for JavaQualifiedName. + * These are intended only for tests, even though some deprecated + * APIs use it. May be moved to an internal package? * * @author Clément Fournier * @since 6.1.0 @@ -108,13 +110,10 @@ public final class QualifiedNameFactory { name = '.' + name; // unnamed package, marked by a full stop. See ofString's format below } - JavaTypeQualifiedName qualifiedName = (JavaTypeQualifiedName) ofString(name); - if (qualifiedName != null) { - // Note: this assumes, that clazz has been loaded through the correct classloader, - // specifically through the auxclasspath classloader. - qualifiedName.withClassLoader(clazz.getClassLoader()); - } - return qualifiedName; + // Note: this assumes, that clazz has been loaded through the correct classloader, + // specifically through the auxclasspath classloader. + // But this method should only be used in tests anyway + return (JavaTypeQualifiedName) ofStringWithClassLoader(name, clazz.getClassLoader()); } @@ -146,6 +145,10 @@ public final class QualifiedNameFactory { * @return A qualified name instance corresponding to the parsed string. */ public static JavaQualifiedName ofString(String name) { + return ofStringWithClassLoader(name, null); + } + + private static JavaQualifiedName ofStringWithClassLoader(String name, ClassLoader classLoader) { Matcher matcher = FORMAT.matcher(name); if (!matcher.matches()) { @@ -153,8 +156,8 @@ public final class QualifiedNameFactory { } ImmutableList packages = StringUtils.isBlank(matcher.group(PACKAGES_GROUP_INDEX)) - ? ListFactory.emptyList() - : ListFactory.split(matcher.group(PACKAGES_GROUP_INDEX), "\\."); + ? ListFactory.emptyList() + : ListFactory.split(matcher.group(PACKAGES_GROUP_INDEX), "\\."); String operation = matcher.group(OPERATION_GROUP_INDEX) == null ? null : matcher.group(OPERATION_GROUP_INDEX).substring(1); boolean isLambda = operation != null && COMPILED_LAMBDA_PATTERN.matcher(operation).matches(); @@ -175,7 +178,7 @@ public final class QualifiedNameFactory { } } - JavaTypeQualifiedName parent = new JavaTypeQualifiedName(packages, classes, localIndices); + JavaTypeQualifiedName parent = new JavaTypeQualifiedName(packages, classes, localIndices, classLoader); return operation == null ? parent : new JavaOperationQualifiedName(parent, operation, isLambda); } } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/qname/QualifiedNameResolver.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/qname/QualifiedNameResolver.java index babdd7a494..d7f7226a6a 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/qname/QualifiedNameResolver.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/qname/QualifiedNameResolver.java @@ -9,6 +9,7 @@ import static net.sourceforge.pmd.lang.java.qname.JavaTypeQualifiedName.NOTLOCAL import java.util.HashMap; import java.util.Map; import java.util.Stack; +import java.util.concurrent.ConcurrentHashMap; import org.apache.commons.lang3.mutable.MutableInt; @@ -45,7 +46,7 @@ public class QualifiedNameResolver extends JavaParserVisitorReducedAdapter { // Package names to package representation. // Allows reusing the same list instance for the same packages. // Package prefixes are also shared. - private final Map> foundPackages = new HashMap<>(128); + private static final Map> FOUND_PACKAGES = new ConcurrentHashMap<>(128); // The following stacks stack some counter of the // visited classes. A new entry is pushed when @@ -147,7 +148,7 @@ public class QualifiedNameResolver extends JavaParserVisitorReducedAdapter { } final String image = pack.getPackageNameImage(); - ImmutableList fullExisting = foundPackages.get(image); + ImmutableList fullExisting = FOUND_PACKAGES.get(image); if (fullExisting != null) { return fullExisting; @@ -166,7 +167,7 @@ public class QualifiedNameResolver extends JavaParserVisitorReducedAdapter { for (int i = longestPrefix.size(); i < allPacks.length; i++) { longestPrefix = longestPrefix.prepend(allPacks[i]); prefixImage.append(allPacks[i]); - foundPackages.put(prefixImage.toString(), longestPrefix); + FOUND_PACKAGES.put(prefixImage.toString(), longestPrefix); } return longestPrefix; @@ -184,7 +185,7 @@ public class QualifiedNameResolver extends JavaParserVisitorReducedAdapter { * the total number of packages in the package name */ private ImmutableList getLongestPackagePrefix(String acc, int i) { - ImmutableList prefix = foundPackages.get(acc); + ImmutableList prefix = FOUND_PACKAGES.get(acc); if (prefix != null) { return prefix; } @@ -356,8 +357,7 @@ public class QualifiedNameResolver extends JavaParserVisitorReducedAdapter { /** Creates a new class qname from the current context (fields). */ private JavaTypeQualifiedName contextClassQName() { - return new JavaTypeQualifiedName(packages, classNames, localIndices) - .withClassLoader(classLoader); + return new JavaTypeQualifiedName(packages, classNames, localIndices, classLoader); } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/MissingOverrideRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/MissingOverrideRule.java index 9400c5e1bd..3b97daa27d 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/MissingOverrideRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/MissingOverrideRule.java @@ -109,25 +109,30 @@ public class MissingOverrideRule extends AbstractJavaRule { return null; } - Set overridden = overriddenMethods(exploredType); - Map>> result = new HashMap<>(); - - for (Method m : exploredType.getDeclaredMethods()) { - if (!result.containsKey(m.getName())) { - result.put(m.getName(), new HashMap>()); + try { + Set overridden = overriddenMethods(exploredType); + Map>> result = new HashMap<>(); + + for (Method m : exploredType.getDeclaredMethods()) { + if (!result.containsKey(m.getName())) { + result.put(m.getName(), new HashMap>()); + } + + Map> pCountToOverloads = result.get(m.getName()); + + int paramCount = m.getParameterTypes().length; + if (!pCountToOverloads.containsKey(paramCount)) { + pCountToOverloads.put(paramCount, new ArrayList()); + } + + pCountToOverloads.get(paramCount).add(m); } - - Map> pCountToOverloads = result.get(m.getName()); - - int paramCount = m.getParameterTypes().length; - if (!pCountToOverloads.containsKey(paramCount)) { - pCountToOverloads.put(paramCount, new ArrayList()); - } - - pCountToOverloads.get(paramCount).add(m); + + return new MethodLookup(result, overridden); + } catch (final LinkageError e) { + // we may have an incomplete auxclasspath + return null; } - - return new MethodLookup(result, overridden); } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codestyle/AbstractNamingConventionRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codestyle/AbstractNamingConventionRule.java index 77d4dd17ad..e53f3a9c23 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codestyle/AbstractNamingConventionRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codestyle/AbstractNamingConventionRule.java @@ -26,8 +26,8 @@ import net.sourceforge.pmd.util.StringUtil; */ abstract class AbstractNamingConventionRule extends AbstractJavaRule { - static final String CAMEL_CASE = "[a-z][a-zA-Z0-9]+"; - static final String PASCAL_CASE = "[A-Z][a-zA-Z0-9]+"; + static final String CAMEL_CASE = "[a-z][a-zA-Z0-9]*"; + static final String PASCAL_CASE = "[A-Z][a-zA-Z0-9]*"; /** The argument is interpreted as the display name, and is converted to camel case to get the property name. */ RegexPBuilder defaultProp(String displayName) { diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codestyle/FieldNamingConventionsRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codestyle/FieldNamingConventionsRule.java new file mode 100644 index 0000000000..f68dcb2556 --- /dev/null +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codestyle/FieldNamingConventionsRule.java @@ -0,0 +1,99 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.codestyle; + +import java.util.regex.Pattern; + +import net.sourceforge.pmd.lang.java.ast.ASTEnumConstant; +import net.sourceforge.pmd.lang.java.ast.ASTFieldDeclaration; +import net.sourceforge.pmd.lang.java.ast.ASTVariableDeclaratorId; +import net.sourceforge.pmd.properties.PropertyDescriptor; +import net.sourceforge.pmd.properties.RegexProperty; + + +/** + * Configurable naming conventions for field declarations. + * + * @author Clément Fournier + * @since 6.7.0 + */ +public class FieldNamingConventionsRule extends AbstractNamingConventionRule { + private final RegexProperty publicConstantFieldRegex = defaultProp("public constant").defaultValue("[A-Z][A-Z_0-9]*").build(); + private final RegexProperty constantFieldRegex = defaultProp("constant").desc("Regex which applies to non-public static final field names").defaultValue("[A-Z][A-Z_0-9]*").build(); + private final RegexProperty enumConstantRegex = defaultProp("enum constant").defaultValue("[A-Z][A-Z_0-9]*").build(); + private final RegexProperty finalFieldRegex = defaultProp("final field").build(); + private final RegexProperty staticFieldRegex = defaultProp("static field").build(); + private final RegexProperty defaultFieldRegex = defaultProp("defaultField", "field").build(); + + + public FieldNamingConventionsRule() { + definePropertyDescriptor(publicConstantFieldRegex); + definePropertyDescriptor(constantFieldRegex); + definePropertyDescriptor(enumConstantRegex); + definePropertyDescriptor(finalFieldRegex); + definePropertyDescriptor(staticFieldRegex); + definePropertyDescriptor(defaultFieldRegex); + + addRuleChainVisit(ASTFieldDeclaration.class); + addRuleChainVisit(ASTEnumConstant.class); + } + + + @Override + public Object visit(ASTFieldDeclaration node, Object data) { + + for (ASTVariableDeclaratorId id : node) { + if (node.isFinal() && node.isStatic()) { + checkMatches(id, node.isPublic() ? publicConstantFieldRegex : constantFieldRegex, data); + } else if (node.isFinal()) { + checkMatches(id, finalFieldRegex, data); + } else if (node.isStatic()) { + checkMatches(id, staticFieldRegex, data); + } else { + checkMatches(id, defaultFieldRegex, data); + } + } + return data; + } + + + @Override + public Object visit(ASTEnumConstant node, Object data) { + // This inlines checkMatches because there's no variable declarator id + + if (!getProperty(enumConstantRegex).matcher(node.getImage()).matches()) { + addViolation(data, node, new Object[]{ + "enum constant", + node.getImage(), + getProperty(enumConstantRegex).toString(), + }); + } + + return data; + } + + + @Override + String defaultConvention() { + return CAMEL_CASE; + } + + + @Override + String kindDisplayName(ASTVariableDeclaratorId node, PropertyDescriptor descriptor) { + ASTFieldDeclaration field = (ASTFieldDeclaration) node.getNthParent(2); + + if (field.isFinal() && field.isStatic()) { + return field.isPublic() ? "public constant" : "constant"; + } else if (field.isFinal()) { + return "final field"; + } else if (field.isStatic()) { + return "static field"; + } else { + return "field"; + } + } + +} diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codestyle/LinguisticNamingRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codestyle/LinguisticNamingRule.java new file mode 100644 index 0000000000..7ab22598c0 --- /dev/null +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codestyle/LinguisticNamingRule.java @@ -0,0 +1,186 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.codestyle; + +import java.util.List; + +import net.sourceforge.pmd.lang.java.ast.ASTFieldDeclaration; +import net.sourceforge.pmd.lang.java.ast.ASTLocalVariableDeclaration; +import net.sourceforge.pmd.lang.java.ast.ASTMethodDeclaration; +import net.sourceforge.pmd.lang.java.ast.ASTResultType; +import net.sourceforge.pmd.lang.java.ast.ASTType; +import net.sourceforge.pmd.lang.java.ast.ASTVariableDeclarator; +import net.sourceforge.pmd.lang.java.rule.AbstractJavaRule; +import net.sourceforge.pmd.properties.BooleanProperty; +import net.sourceforge.pmd.properties.StringMultiProperty; + +public class LinguisticNamingRule extends AbstractJavaRule { + private static final BooleanProperty CHECK_BOOLEAN_METHODS = BooleanProperty.named("checkBooleanMethod") + .defaultValue(true).desc("Check method names and types for inconsistent naming").uiOrder(1.0f).build(); + private static final BooleanProperty CHECK_GETTERS = BooleanProperty.named("checkGetters").defaultValue(true) + .desc("Check return type of getters").uiOrder(2.0f).build(); + private static final BooleanProperty CHECK_SETTERS = BooleanProperty.named("checkSetters").defaultValue(true) + .desc("Check return type of setters").uiOrder(3.0f).build(); + private static final BooleanProperty CHECK_PREFIXED_TRANSFORM_METHODS = BooleanProperty + .named("checkPrefixedTransformMethods") + .defaultValue(true).desc("Check return type of methods whose names start with 'to'").uiOrder(4.0f).build(); + private static final BooleanProperty CHECK_TRANSFORM_METHODS = BooleanProperty.named("checkTransformMethods") + .defaultValue(false).desc("Check return type of methods which contain 'To' in their name").uiOrder(4.0f).build(); + private static final StringMultiProperty BOOLEAN_METHOD_PREFIXES_PROPERTY = StringMultiProperty + .named("booleanMethodPrefixes").defaultValues("is", "has", "can", "have", "will", "should") + .desc("the prefixes of methods that return boolean").uiOrder(5.0f).build(); + + private static final BooleanProperty CHECK_FIELDS = BooleanProperty.named("checkFields").defaultValue(true) + .desc("Check field names and types for inconsistent naming").uiOrder(6.0f).build(); + private static final BooleanProperty CHECK_VARIABLES = BooleanProperty.named("checkVariables").defaultValue(true) + .desc("Check local variable names and types for inconsistent naming").uiOrder(7.0f).build(); + private static final StringMultiProperty BOOLEAN_FIELD_PREFIXES_PROPERTY = StringMultiProperty + .named("booleanFieldPrefixes").defaultValues("is", "has", "can", "have", "will", "should") + .desc("the prefixes of fields and variables that indicate boolean").uiOrder(8.0f).build(); + + public LinguisticNamingRule() { + definePropertyDescriptor(CHECK_BOOLEAN_METHODS); + definePropertyDescriptor(CHECK_GETTERS); + definePropertyDescriptor(CHECK_SETTERS); + definePropertyDescriptor(CHECK_PREFIXED_TRANSFORM_METHODS); + definePropertyDescriptor(CHECK_TRANSFORM_METHODS); + definePropertyDescriptor(BOOLEAN_METHOD_PREFIXES_PROPERTY); + definePropertyDescriptor(CHECK_FIELDS); + definePropertyDescriptor(CHECK_VARIABLES); + definePropertyDescriptor(BOOLEAN_FIELD_PREFIXES_PROPERTY); + addRuleChainVisit(ASTMethodDeclaration.class); + addRuleChainVisit(ASTFieldDeclaration.class); + addRuleChainVisit(ASTLocalVariableDeclaration.class); + } + + @Override + public Object visit(ASTMethodDeclaration node, Object data) { + String nameOfMethod = node.getMethodName(); + + if (getProperty(CHECK_BOOLEAN_METHODS)) { + checkBooleanMethods(node, data, nameOfMethod); + } + + if (getProperty(CHECK_SETTERS)) { + checkSetters(node, data, nameOfMethod); + } + + if (getProperty(CHECK_GETTERS)) { + checkGetters(node, data, nameOfMethod); + } + + if (getProperty(CHECK_PREFIXED_TRANSFORM_METHODS)) { + checkPrefixedTransformMethods(node, data, nameOfMethod); + } + + if (getProperty(CHECK_TRANSFORM_METHODS)) { + checkTransformMethods(node, data, nameOfMethod); + } + + return data; + } + + private void checkPrefixedTransformMethods(ASTMethodDeclaration node, Object data, String nameOfMethod) { + ASTResultType resultType = node.getResultType(); + if (resultType.isVoid() && hasPrefix(nameOfMethod, "to")) { + // To as prefix + addViolationWithMessage(data, node, "Linguistics Antipattern - The transform method ''{0}'' should not return void linguistically", + new Object[] { nameOfMethod }); + } + } + + private void checkTransformMethods(ASTMethodDeclaration node, Object data, String nameOfMethod) { + ASTResultType resultType = node.getResultType(); + if (resultType.isVoid() && containsWord(nameOfMethod, "To")) { + // To in the middle somewhere + addViolationWithMessage(data, node, "Linguistics Antipattern - The transform method ''{0}'' should not return void linguistically", + new Object[] { nameOfMethod }); + } + } + + private void checkGetters(ASTMethodDeclaration node, Object data, String nameOfMethod) { + ASTResultType resultType = node.getResultType(); + if (hasPrefix(nameOfMethod, "get") && resultType.isVoid()) { + addViolationWithMessage(data, node, "Linguistics Antipattern - The getter ''{0}'' should not return void linguistically", + new Object[] { nameOfMethod }); + } + } + + private void checkSetters(ASTMethodDeclaration node, Object data, String nameOfMethod) { + ASTResultType resultType = node.getResultType(); + if (hasPrefix(nameOfMethod, "set") && !resultType.isVoid()) { + addViolationWithMessage(data, node, "Linguistics Antipattern - The setter ''{0}'' should not return any type except void linguistically", + new Object[] { nameOfMethod }); + } + } + + private void checkBooleanMethods(ASTMethodDeclaration node, Object data, String nameOfMethod) { + ASTResultType resultType = node.getResultType(); + ASTType t = node.getResultType().getFirstChildOfType(ASTType.class); + if (!resultType.isVoid() && t != null) { + for (String prefix : getProperty(BOOLEAN_METHOD_PREFIXES_PROPERTY)) { + if (hasPrefix(nameOfMethod, prefix) && !"boolean".equalsIgnoreCase(t.getTypeImage())) { + addViolationWithMessage(data, node, "Linguistics Antipattern - The method ''{0}'' indicates linguistically it returns a boolean, but it returns ''{1}''", + new Object[] { nameOfMethod, t.getTypeImage() }); + } + } + } + } + + private void checkField(String typeImage, ASTVariableDeclarator node, Object data) { + for (String prefix : getProperty(BOOLEAN_FIELD_PREFIXES_PROPERTY)) { + if (hasPrefix(node.getName(), prefix) && !"boolean".equalsIgnoreCase(typeImage)) { + addViolationWithMessage(data, node, "Linguistics Antipattern - The field ''{0}'' indicates linguistically it is a boolean, but it is ''{1}''", + new Object[] { node.getName(), typeImage }); + } + } + } + + private void checkVariable(String typeImage, ASTVariableDeclarator node, Object data) { + for (String prefix : getProperty(BOOLEAN_FIELD_PREFIXES_PROPERTY)) { + if (hasPrefix(node.getName(), prefix) && !"boolean".equalsIgnoreCase(typeImage)) { + addViolationWithMessage(data, node, "Linguistics Antipattern - The variable ''{0}'' indicates linguistically it is a boolean, but it is ''{1}''", + new Object[] { node.getName(), typeImage }); + } + } + } + + @Override + public Object visit(ASTFieldDeclaration node, Object data) { + ASTType type = node.getFirstChildOfType(ASTType.class); + if (type != null && getProperty(CHECK_FIELDS)) { + List fields = node.findChildrenOfType(ASTVariableDeclarator.class); + for (ASTVariableDeclarator field : fields) { + checkField(type.getTypeImage(), field, data); + } + } + return data; + } + + @Override + public Object visit(ASTLocalVariableDeclaration node, Object data) { + ASTType type = node.getFirstChildOfType(ASTType.class); + if (type != null && getProperty(CHECK_VARIABLES)) { + List variables = node.findChildrenOfType(ASTVariableDeclarator.class); + for (ASTVariableDeclarator variable : variables) { + checkVariable(type.getTypeImage(), variable, data); + } + } + return data; + } + + private static boolean hasPrefix(String name, String prefix) { + return name.startsWith(prefix) && name.length() > prefix.length() + && Character.isUpperCase(name.charAt(prefix.length())); + } + + private static boolean containsWord(String name, String word) { + int index = name.indexOf(word); + if (index >= 0 && name.length() > index + word.length()) { + return Character.isUpperCase(name.charAt(index + word.length())); + } + return false; + } +} diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codestyle/UnnecessaryFullyQualifiedNameRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codestyle/UnnecessaryFullyQualifiedNameRule.java index 37c2b06d9d..ad12b080e8 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codestyle/UnnecessaryFullyQualifiedNameRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codestyle/UnnecessaryFullyQualifiedNameRule.java @@ -17,7 +17,11 @@ import net.sourceforge.pmd.lang.java.ast.ASTCompilationUnit; import net.sourceforge.pmd.lang.java.ast.ASTImportDeclaration; import net.sourceforge.pmd.lang.java.ast.ASTName; import net.sourceforge.pmd.lang.java.ast.ASTPackageDeclaration; +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.AbstractJavaTypeNode; +import net.sourceforge.pmd.lang.java.ast.JavaNode; import net.sourceforge.pmd.lang.java.rule.AbstractJavaRule; import net.sourceforge.pmd.lang.java.symboltable.SourceFileScope; @@ -79,6 +83,16 @@ public class UnnecessaryFullyQualifiedNameRule extends AbstractJavaRule { && name.lastIndexOf('.') == decl.getImportedName().length(); } + private boolean couldBeMethodCall(JavaNode node) { + if (node.getNthParent(2) instanceof ASTPrimaryExpression && node.getNthParent(1) instanceof ASTPrimaryPrefix) { + int nextSibling = node.jjtGetParent().jjtGetChildIndex() + 1; + if (node.getNthParent(2).jjtGetNumChildren() > nextSibling) { + return node.getNthParent(2).jjtGetChild(nextSibling) instanceof ASTPrimarySuffix; + } + } + return false; + } + private void checkImports(AbstractJavaTypeNode node, Object data) { String name = node.getImage(); List matches = new ArrayList<>(); @@ -127,7 +141,7 @@ public class UnnecessaryFullyQualifiedNameRule extends AbstractJavaRule { matches.add(importDeclaration); } } else { - // Last 2 parts match? + // Last 2 parts match? Class + Method name if (nameParts[nameParts.length - 1].equals(importParts[importParts.length - 1]) && nameParts[nameParts.length - 2].equals(importParts[importParts.length - 2])) { matches.add(importDeclaration); @@ -137,6 +151,10 @@ public class UnnecessaryFullyQualifiedNameRule extends AbstractJavaRule { // last part matches? if (nameParts[nameParts.length - 1].equals(importParts[importParts.length - 1])) { matches.add(importDeclaration); + } else if (couldBeMethodCall(node) + && nameParts.length > 1 && nameParts[nameParts.length - 2].equals(importParts[importParts.length - 1])) { + // maybe the Name is part of a method call, then the second two last part needs to match + matches.add(importDeclaration); } } } @@ -147,7 +165,7 @@ public class UnnecessaryFullyQualifiedNameRule extends AbstractJavaRule { } if (!matches.isEmpty()) { - ASTImportDeclaration firstMatch = matches.get(0); + ASTImportDeclaration firstMatch = findFirstMatch(matches); // Could this done to avoid a conflict? if (!isAvoidingConflict(node, name, firstMatch)) { @@ -159,13 +177,37 @@ public class UnnecessaryFullyQualifiedNameRule extends AbstractJavaRule { } } + private ASTImportDeclaration findFirstMatch(List imports) { + // first search only static imports + ASTImportDeclaration result = null; + for (ASTImportDeclaration importDeclaration : imports) { + if (importDeclaration.isStatic()) { + result = importDeclaration; + break; + } + } + + // then search all non-static, if needed + if (result == null) { + for (ASTImportDeclaration importDeclaration : imports) { + if (!importDeclaration.isStatic()) { + result = importDeclaration; + break; + } + } + } + + return result; + } + private boolean isJavaLangImplicit(AbstractJavaTypeNode node) { String name = node.getImage(); boolean isJavaLang = name != null && name.startsWith("java.lang."); - if (isJavaLang && node.getType() != null) { + if (isJavaLang && node.getType() != null && node.getType().getPackage() != null) { // valid would be ProcessBuilder.Redirect.PIPE but not java.lang.ProcessBuilder.Redirect.PIPE - String packageName = node.getType().getPackage().getName(); + String packageName = node.getType().getPackage() // package might be null, if type is an array type... + .getName(); return "java.lang".equals(packageName); } else if (isJavaLang) { // only java.lang.* is implicitly imported, but not e.g. java.lang.reflection.* @@ -231,15 +273,29 @@ public class UnnecessaryFullyQualifiedNameRule extends AbstractJavaRule { } // There could be a conflict between an import of a class with the same name as the FQN + String importName = firstMatch.getImportedName(); + String importUnqualified = importName.substring(importName.lastIndexOf('.') + 1); if (!firstMatch.isImportOnDemand() && !firstMatch.isStatic()) { - String importName = firstMatch.getImportedName(); - String importUnqualified = importName.substring(importName.lastIndexOf('.') + 1); // the package is different, but the unqualified name is same if (!firstMatch.getImportedName().equals(name) && importUnqualified.equals(unqualifiedName)) { return true; } } + // There could be a conflict between an import of a class with the same name as the FQN, which + // could be a method call: + // import x.y.Thread; + // valid qualification (node): java.util.Thread.currentThread() + if (couldBeMethodCall(node)) { + String[] nameParts = name.split("\\."); + String fqnName = name.substring(0, name.lastIndexOf('.')); + // seems to be a static method call on a different FQN + if (!fqnName.equals(importName) && !firstMatch.isStatic() && !firstMatch.isImportOnDemand() + && nameParts.length > 1 && nameParts[nameParts.length - 2].equals(importUnqualified)) { + return true; + } + } + // Is it a conflict with a class in the same file? final Set qualifiedTypes = node.getScope().getEnclosingScope(SourceFileScope.class) .getQualifiedTypeNames().keySet(); diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/NPathComplexityRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/NPathComplexityRule.java index f395787294..59891ef461 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/NPathComplexityRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/NPathComplexityRule.java @@ -9,6 +9,7 @@ import java.util.logging.Logger; import net.sourceforge.pmd.lang.java.ast.ASTCompilationUnit; import net.sourceforge.pmd.lang.java.ast.ASTMethodDeclaration; import net.sourceforge.pmd.lang.java.ast.ASTMethodOrConstructorDeclaration; +import net.sourceforge.pmd.lang.java.ast.MethodLikeNode; import net.sourceforge.pmd.lang.java.metrics.JavaMetrics; import net.sourceforge.pmd.lang.java.metrics.api.JavaOperationMetricKey; import net.sourceforge.pmd.lang.java.rule.AbstractJavaMetricsRule; @@ -68,7 +69,7 @@ public class NPathComplexityRule extends AbstractJavaMetricsRule { @Override public final Object visit(ASTMethodOrConstructorDeclaration node, Object data) { - int npath = (int) JavaMetrics.get(JavaOperationMetricKey.NPATH, node); + int npath = (int) JavaMetrics.get(JavaOperationMetricKey.NPATH, (MethodLikeNode) node); if (npath >= reportLevel) { addViolation(data, node, new String[]{node instanceof ASTMethodDeclaration ? "method" : "constructor", node.getQualifiedName().getOperation(), "" + npath, }); diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/typeresolution/visitors/PMDASMVisitor.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/typeresolution/visitors/PMDASMVisitor.java index fe29dd3d50..dce2ae802c 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/typeresolution/visitors/PMDASMVisitor.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/typeresolution/visitors/PMDASMVisitor.java @@ -36,7 +36,7 @@ public class PMDASMVisitor extends ClassVisitor { public List innerClasses; public PMDASMVisitor(String outerName) { - super(Opcodes.ASM6); + super(Opcodes.ASM7_EXPERIMENTAL); this.outerName = outerName; } diff --git a/pmd-java/src/main/resources/category/java/bestpractices.xml b/pmd-java/src/main/resources/category/java/bestpractices.xml index 012f8798c0..4e7d4fa7eb 100644 --- a/pmd-java/src/main/resources/category/java/bestpractices.xml +++ b/pmd-java/src/main/resources/category/java/bestpractices.xml @@ -49,6 +49,7 @@ public abstract class Foo { @@ -77,6 +78,7 @@ public class Outer { @@ -408,6 +410,7 @@ otherwise skip the associate String creation and manipulation. since="4.0" message="JUnit 4 indicates test suites via annotations, not the suite method." class="net.sourceforge.pmd.lang.rule.XPathRule" + typeResolution="true" externalInfoUrl="${pmd.website.baseurl}/pmd_rules_java_bestpractices.html#junit4suitesshouldusesuiteannotation"> In JUnit 3, test suites are indicated by the suite() method. In JUnit 4, suites are indicated @@ -419,8 +422,8 @@ through the @RunWith(Suite.class) annotation. @@ -445,21 +448,27 @@ public class GoodTest { In JUnit 3, the tearDown method was used to clean up all data entities required in running tests. -JUnit 4 skips the tearDown method and executes all methods annotated with @After after running each test +JUnit 4 skips the tearDown method and executes all methods annotated with @After after running each test. +JUnit 5 introduced @AfterEach and @AfterAll annotations to execute methods after each test or after all tests in the class, respectively. 3 @@ -483,21 +492,27 @@ public class MyTest2 { In JUnit 3, the setUp method was used to set up all data entities required in running tests. -JUnit 4 skips the setUp method and executes all methods annotated with @Before before all tests +JUnit 4 skips the setUp method and executes all methods annotated with @Before before all tests. +JUnit 5 introduced @BeforeEach and @BeforeAll annotations to execute methods before each test or before all tests in the class, respectively. 3 @@ -521,13 +536,14 @@ public class MyTest2 { In JUnit 3, the framework executed all methods which started with the word test as a unit test. In JUnit 4, only methods annotated with the @Test annotation are executed. +In JUnit 5, one of the following annotations should be used for tests: @Test, @RepeatedTest, @TestFactory, @TestTemplate or @ParameterizedTest. 3 @@ -539,7 +555,12 @@ In JUnit 4, only methods annotated with the @Test annotation are executed. or ExtendsList/ClassOrInterfaceType[pmd-java:typeIs('junit.framework.TestCase')]] /ClassOrInterfaceBody/ClassOrInterfaceBodyDeclaration[MethodDeclaration[@Public=true()]/MethodDeclarator[starts-with(@Image, 'test')]] - [not(Annotation//Name[pmd-java:typeIs('org.junit.Test')])] + [not(Annotation//Name[ + pmd-java:typeIs('org.junit.Test') + or pmd-java:typeIs('org.junit.jupiter.api.Test') or pmd-java:typeIs('org.junit.jupiter.api.RepeatedTest') + or pmd-java:typeIs('org.junit.jupiter.api.TestFactory') or pmd-java:typeIs('org.junit.jupiter.api.TestTemplate') + or pmd-java:typeIs('org.junit.jupiter.params.ParameterizedTest') + ])] ]]> @@ -589,13 +610,16 @@ public class Foo extends TestCase { -JUnit tests should not contain too many asserts. Many asserts are indicative of a complex test, for which -it is harder to verify correctness. Consider breaking the test scenario into multiple, shorter test scenarios. +Unit tests should not contain too many asserts. Many asserts are indicative of a complex test, for which +it is harder to verify correctness. Consider breaking the test scenario into multiple, shorter test scenarios. Customize the maximum number of assertions used by this Rule to suit your needs. + +This rule checks for JUnit4, JUnit5 and TestNG Tests, as well as methods starting with "test". 3 @@ -603,7 +627,16 @@ Customize the maximum number of assertions used by this Rule to suit your needs. $maximumAsserts] +//MethodDeclarator[@Image[fn:matches(.,'^test')] or ../../Annotation/MarkerAnnotation/Name[ + pmd-java:typeIs('org.junit.Test') + or pmd-java:typeIs('org.junit.jupiter.api.Test') + or pmd-java:typeIs('org.junit.jupiter.api.RepeatedTest') + or pmd-java:typeIs('org.junit.jupiter.api.TestFactory') + or pmd-java:typeIs('org.junit.jupiter.api.TestTemplate') + or pmd-java:typeIs('org.junit.jupiter.params.ParameterizedTest') + or pmd-java:typeIs('org.testng.annotations.Test') + ]] + [count(..//PrimaryPrefix/Name[@Image[fn:matches(.,'^assert')]]) > $maximumAsserts] ]]> @@ -1196,6 +1229,7 @@ public class Something { since="3.1" message="Use assertEquals(x, y) instead of assertTrue(x.equals(y))" class="net.sourceforge.pmd.lang.rule.XPathRule" + typeResolution="true" externalInfoUrl="${pmd.website.baseurl}/pmd_rules_java_bestpractices.html#useassertequalsinsteadofasserttrue"> This rule detects JUnit assertions in object equality. These assertions should be made by more specific methods, like assertEquals. @@ -1211,8 +1245,14 @@ This rule detects JUnit assertions in object equality. These assertions should b PrimarySuffix/Arguments/ArgumentList/Expression/PrimaryExpression/PrimaryPrefix/Name [ends-with(@Image, '.equals')] ] -[ancestor::ClassOrInterfaceDeclaration[//ClassOrInterfaceType[pmd-java:typeIs('junit.framework.TestCase')] or //MarkerAnnotation/Name[pmd-java:typeIs('org.junit.Test')]]] -]]> +[ancestor::ClassOrInterfaceDeclaration[//ClassOrInterfaceType[pmd-java:typeIs('junit.framework.TestCase')] + or //MarkerAnnotation/Name[ + pmd-java:typeIs('org.junit.Test') + or pmd-java:typeIs('org.junit.jupiter.api.Test') or pmd-java:typeIs('org.junit.jupiter.api.RepeatedTest') + or pmd-java:typeIs('org.junit.jupiter.api.TestFactory') or pmd-java:typeIs('org.junit.jupiter.api.TestTemplate') + or pmd-java:typeIs('org.junit.jupiter.params.ParameterizedTest') + ] +]]]]> @@ -1234,6 +1274,7 @@ public class FooTest extends TestCase { since="3.5" message="Use assertNull(x) instead of assertTrue(x==null), or assertNotNull(x) vs assertFalse(x==null)" class="net.sourceforge.pmd.lang.rule.XPathRule" + typeResolution="true" externalInfoUrl="${pmd.website.baseurl}/pmd_rules_java_bestpractices.html#useassertnullinsteadofasserttrue"> This rule detects JUnit assertions in object references equality. These assertions should be made by @@ -1251,8 +1292,14 @@ more specific methods, like assertNull, assertNotNull. Expression/EqualityExpression/PrimaryExpression/PrimaryPrefix/Literal/NullLiteral ] ] -[ancestor::ClassOrInterfaceDeclaration[//ClassOrInterfaceType[pmd-java:typeIs('junit.framework.TestCase')] or //MarkerAnnotation/Name[pmd-java:typeIs('org.junit.Test')]]] -]]> +[ancestor::ClassOrInterfaceDeclaration[//ClassOrInterfaceType[pmd-java:typeIs('junit.framework.TestCase')] + or //MarkerAnnotation/Name[ + pmd-java:typeIs('org.junit.Test') + or pmd-java:typeIs('org.junit.jupiter.api.Test') or pmd-java:typeIs('org.junit.jupiter.api.RepeatedTest') + or pmd-java:typeIs('org.junit.jupiter.api.TestFactory') or pmd-java:typeIs('org.junit.jupiter.api.TestTemplate') + or pmd-java:typeIs('org.junit.jupiter.params.ParameterizedTest') + ] +]]]]> @@ -1276,6 +1323,7 @@ public class FooTest extends TestCase { since="3.1" message="Use assertSame(x, y) instead of assertTrue(x==y), or assertNotSame(x,y) vs assertFalse(x==y)" class="net.sourceforge.pmd.lang.rule.XPathRule" + typeResolution="true" externalInfoUrl="${pmd.website.baseurl}/pmd_rules_java_bestpractices.html#useassertsameinsteadofasserttrue"> This rule detects JUnit assertions in object references equality. These assertions should be made @@ -1293,8 +1341,14 @@ by more specific methods, like assertSame, assertNotSame. [PrimarySuffix/Arguments /ArgumentList/Expression /EqualityExpression[count(.//NullLiteral) = 0]] -[ancestor::ClassOrInterfaceDeclaration[//ClassOrInterfaceType[pmd-java:typeIs('junit.framework.TestCase')] or //MarkerAnnotation/Name[pmd-java:typeIs('org.junit.Test')]]] -]]> +[ancestor::ClassOrInterfaceDeclaration[//ClassOrInterfaceType[pmd-java:typeIs('junit.framework.TestCase')] + or //MarkerAnnotation/Name[ + pmd-java:typeIs('org.junit.Test') + or pmd-java:typeIs('org.junit.jupiter.api.Test') or pmd-java:typeIs('org.junit.jupiter.api.RepeatedTest') + or pmd-java:typeIs('org.junit.jupiter.api.TestFactory') or pmd-java:typeIs('org.junit.jupiter.api.TestTemplate') + or pmd-java:typeIs('org.junit.jupiter.params.ParameterizedTest') + ] +]]]]> diff --git a/pmd-java/src/main/resources/category/java/codestyle.xml b/pmd-java/src/main/resources/category/java/codestyle.xml index 01aae5f509..f80f81051b 100644 --- a/pmd-java/src/main/resources/category/java/codestyle.xml +++ b/pmd-java/src/main/resources/category/java/codestyle.xml @@ -687,6 +687,43 @@ public class HelloWorldBean { + + + + Configurable naming conventions for field declarations. This rule reports variable declarations + which do not match the regex that applies to their specific kind ---e.g. constants (static final), + enum constant, final field. Each regex can be configured through properties. + + By default this rule uses the standard Java naming convention (Camel case), and uses the ALL_UPPER + convention for constants and enum constants. + + 1 + + + + + + + + This rule finds Linguistic Naming Antipatterns. It checks for fields, that are named, as if they should + be boolean but have a different type. It also checks for methods, that according to their name, should + return a boolean, but don't. Further, it checks, that getters return something and setters won't. + Finally, it checks that methods, that start with "to" - so called transform methods - actually return + something, since according to their name, they should convert or transform one object into another. + There is additionally an option, to check for methods that contain "To" in their name - which are + also transform methods. However, this is disabled by default, since this detection is prone to + false positives. + + For more information, see [Linguistic Antipatterns - What They Are and How +Developers Perceive Them](https://doi.org/10.1007/s10664-014-9350-8). + + 3 + + + + + @@ -267,13 +267,13 @@ Exception, or Error, use a subclassed exception or error instead. @@ -503,7 +503,7 @@ Errors are system exceptions. Do not extend them. @@ -1187,6 +1187,7 @@ public class Foo { since="3.6" message="assertTrue(!expr) can be replaced by assertFalse(expr)" class="net.sourceforge.pmd.lang.rule.XPathRule" + typeResolution="true" externalInfoUrl="${pmd.website.baseurl}/pmd_rules_java_design.html#simplifybooleanassertion"> Avoid negation in an assertTrue or assertFalse test. @@ -1213,8 +1214,14 @@ PrimaryExpression/PrimarySuffix/Arguments/ArgumentList /Expression/UnaryExpressionNotPlusMinus[@Image='!'] /PrimaryExpression/PrimaryPrefix ] -[ancestor::ClassOrInterfaceDeclaration[//ClassOrInterfaceType[pmd-java:typeIs('junit.framework.TestCase')] or //MarkerAnnotation/Name[pmd-java:typeIs('org.junit.Test')]]] -]]> +[ancestor::ClassOrInterfaceDeclaration[//ClassOrInterfaceType[pmd-java:typeIs('junit.framework.TestCase')] + or //MarkerAnnotation/Name[ + pmd-java:typeIs('org.junit.Test') + or pmd-java:typeIs('org.junit.jupiter.api.Test') or pmd-java:typeIs('org.junit.jupiter.api.RepeatedTest') + or pmd-java:typeIs('org.junit.jupiter.api.TestFactory') or pmd-java:typeIs('org.junit.jupiter.api.TestTemplate') + or pmd-java:typeIs('org.junit.jupiter.params.ParameterizedTest') + ] +]]]]> diff --git a/pmd-java/src/main/resources/category/java/documentation.xml b/pmd-java/src/main/resources/category/java/documentation.xml index 6e1d7e8e61..7882ad02ca 100644 --- a/pmd-java/src/main/resources/category/java/documentation.xml +++ b/pmd-java/src/main/resources/category/java/documentation.xml @@ -96,7 +96,7 @@ and unintentional empty constructors. diff --git a/pmd-java/src/main/resources/category/java/errorprone.xml b/pmd-java/src/main/resources/category/java/errorprone.xml index 5c322a7351..e6fdcd2de2 100644 --- a/pmd-java/src/main/resources/category/java/errorprone.xml +++ b/pmd-java/src/main/resources/category/java/errorprone.xml @@ -707,9 +707,9 @@ public String bar(String string) { /Block[not( (BlockStatement[1]/Statement/StatementExpression/PrimaryExpression[./PrimaryPrefix[@SuperModifier='true']]/PrimarySuffix[@Image= ancestor::MethodDeclaration/MethodDeclarator/@Image]))] [ancestor::ClassOrInterfaceDeclaration[ExtendsList/ClassOrInterfaceType[ - typeIs('android.app.Activity') or - typeIs('android.app.Application') or - typeIs('android.app.Service') + pmd-java:typeIs('android.app.Activity') or + pmd-java:typeIs('android.app.Application') or + pmd-java:typeIs('android.app.Service') ]]] ]]> @@ -752,9 +752,9 @@ Super should be called at the end of the method /Block/BlockStatement[last()] [not(Statement/StatementExpression/PrimaryExpression[./PrimaryPrefix[@SuperModifier='true']]/PrimarySuffix[@Image= ancestor::MethodDeclaration/MethodDeclarator/@Image])] [ancestor::ClassOrInterfaceDeclaration[ExtendsList/ClassOrInterfaceType[ - typeIs('android.app.Activity') or - typeIs('android.app.Application') or - typeIs('android.app.Service') + pmd-java:typeIs('android.app.Activity') or + pmd-java:typeIs('android.app.Application') or + pmd-java:typeIs('android.app.Service') ]]] ]]> @@ -2058,8 +2058,14 @@ Some JUnit framework methods are easy to misspell. or (not(@Image = 'tearDown') and translate(@Image, 'TEARdOWN', 'tearDown') = 'tearDown')] [FormalParameters[count(*) = 0]] -[ancestor::ClassOrInterfaceDeclaration[//ClassOrInterfaceType[pmd-java:typeIs('junit.framework.TestCase')] or //MarkerAnnotation/Name[pmd-java:typeIs('org.junit.Test')]]] -]]> +[ancestor::ClassOrInterfaceDeclaration[//ClassOrInterfaceType[pmd-java:typeIs('junit.framework.TestCase')] + or //MarkerAnnotation/Name[ + pmd-java:typeIs('org.junit.Test') + or pmd-java:typeIs('org.junit.jupiter.api.Test') or pmd-java:typeIs('org.junit.jupiter.api.RepeatedTest') + or pmd-java:typeIs('org.junit.jupiter.api.TestFactory') or pmd-java:typeIs('org.junit.jupiter.api.TestTemplate') + or pmd-java:typeIs('org.junit.jupiter.params.ParameterizedTest') + ] +]]]]>
@@ -2080,6 +2086,7 @@ public class Foo extends TestCase { since="1.0" message="You have a suite() method that is not both public and static, so JUnit won't call it to get your TestSuite. Is that what you wanted to do?" class="net.sourceforge.pmd.lang.rule.XPathRule" + typeResolution="true" externalInfoUrl="${pmd.website.baseurl}/pmd_rules_java_errorprone.html#junitstaticsuite"> The suite() method in a JUnit test needs to be both public and static. @@ -2092,8 +2099,14 @@ The suite() method in a JUnit test needs to be both public and static. //MethodDeclaration[not(@Static='true') or not(@Public='true')] [MethodDeclarator/@Image='suite'] [MethodDeclarator/FormalParameters/@ParameterCount=0] -[ancestor::ClassOrInterfaceDeclaration[//ClassOrInterfaceType[pmd-java:typeIs('junit.framework.TestCase')] or //MarkerAnnotation/Name[pmd-java:typeIs('org.junit.Test')]]] -]]> +[ancestor::ClassOrInterfaceDeclaration[//ClassOrInterfaceType[pmd-java:typeIs('junit.framework.TestCase')] + or //MarkerAnnotation/Name[ + pmd-java:typeIs('org.junit.Test') + or pmd-java:typeIs('org.junit.jupiter.api.Test') or pmd-java:typeIs('org.junit.jupiter.api.RepeatedTest') + or pmd-java:typeIs('org.junit.jupiter.api.TestFactory') or pmd-java:typeIs('org.junit.jupiter.api.TestTemplate') + or pmd-java:typeIs('org.junit.jupiter.params.ParameterizedTest') + ] +]]]]>
@@ -2277,6 +2290,7 @@ public void bar(int status) { since="3.0" message="Classes implementing Serializable should set a serialVersionUID" class="net.sourceforge.pmd.lang.rule.XPathRule" + typeResolution="true" externalInfoUrl="${pmd.website.baseurl}/pmd_rules_java_errorprone.html#missingserialversionuid"> Serializable classes should provide a serialVersionUID field. @@ -2287,16 +2301,10 @@ Serializable classes should provide a serialVersionUID field. @@ -3037,6 +3045,7 @@ public class Foo { since="3.0" message="assertTrue(true) or similar statements are unnecessary" class="net.sourceforge.pmd.lang.rule.XPathRule" + typeResolution="true" externalInfoUrl="${pmd.website.baseurl}/pmd_rules_java_errorprone.html#unnecessarybooleanassertion"> A JUnit test assertion with a boolean literal is unnecessary since it always will evaluate to the same thing. @@ -3059,8 +3068,14 @@ or UnaryExpressionNotPlusMinus[@Image='!'] /PrimaryExpression/PrimaryPrefix[Literal/BooleanLiteral or Name[count(../../*)=1]]] ] -[ancestor::ClassOrInterfaceDeclaration[//ClassOrInterfaceType[pmd-java:typeIs('junit.framework.TestCase')] or //MarkerAnnotation/Name[pmd-java:typeIs('org.junit.Test')]]] -]]> +[ancestor::ClassOrInterfaceDeclaration[//ClassOrInterfaceType[pmd-java:typeIs('junit.framework.TestCase')] + or //MarkerAnnotation/Name[ + pmd-java:typeIs('org.junit.Test') + or pmd-java:typeIs('org.junit.jupiter.api.Test') or pmd-java:typeIs('org.junit.jupiter.api.RepeatedTest') + or pmd-java:typeIs('org.junit.jupiter.api.TestFactory') or pmd-java:typeIs('org.junit.jupiter.api.TestTemplate') + or pmd-java:typeIs('org.junit.jupiter.params.ParameterizedTest') + ] +]]]]>
diff --git a/pmd-java/src/main/resources/category/java/multithreading.xml b/pmd-java/src/main/resources/category/java/multithreading.xml index fcede3d979..f2d629356d 100644 --- a/pmd-java/src/main/resources/category/java/multithreading.xml +++ b/pmd-java/src/main/resources/category/java/multithreading.xml @@ -169,8 +169,8 @@ Explicitly calling Thread.run() method will execute in the caller's thread of co [ ./Name[ends-with(@Image, '.run') or @Image = 'run'] and substring-before(Name/@Image, '.') =//VariableDeclarator/VariableDeclaratorId/@Image - [../../../Type/ReferenceType/ClassOrInterfaceType[typeIs('java.lang.Thread')]] - or (./AllocationExpression/ClassOrInterfaceType[typeIs('java.lang.Thread')] + [../../../Type/ReferenceType/ClassOrInterfaceType[pmd-java:typeIs('java.lang.Thread')]] + or (./AllocationExpression/ClassOrInterfaceType[pmd-java:typeIs('java.lang.Thread')] and ../PrimarySuffix[@Image = 'run']) ] ] diff --git a/pmd-java/src/main/resources/category/java/performance.xml b/pmd-java/src/main/resources/category/java/performance.xml index 685537197b..66e1583bdb 100644 --- a/pmd-java/src/main/resources/category/java/performance.xml +++ b/pmd-java/src/main/resources/category/java/performance.xml @@ -135,10 +135,10 @@ The FileReader and FileWriter constructors instantiate FileInputStream and FileO @@ -209,10 +209,10 @@ adverse impacts on performance. @@ -467,7 +467,7 @@ Note that new Integer() is deprecated since JDK 9 for that reason. @@ -500,7 +500,7 @@ Note that new Long() is deprecated since JDK 9 for that reason. @@ -664,7 +664,7 @@ Note that new Short() is deprecated since JDK 9 for that reason. diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/LanguageVersionDiscovererTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/LanguageVersionDiscovererTest.java index f8c30b8f57..f2972b1407 100644 --- a/pmd-java/src/test/java/net/sourceforge/pmd/LanguageVersionDiscovererTest.java +++ b/pmd-java/src/test/java/net/sourceforge/pmd/LanguageVersionDiscovererTest.java @@ -26,8 +26,8 @@ public class LanguageVersionDiscovererTest { File javaFile = new File("/path/to/MyClass.java"); LanguageVersion languageVersion = discoverer.getDefaultLanguageVersionForFile(javaFile); - assertEquals("LanguageVersion must be Java 10 !", - LanguageRegistry.getLanguage(JavaLanguageModule.NAME).getVersion("10"), languageVersion); + assertEquals("LanguageVersion must be Java 11 !", + LanguageRegistry.getLanguage(JavaLanguageModule.NAME).getVersion("11"), languageVersion); } /** @@ -48,7 +48,7 @@ public class LanguageVersionDiscovererTest { public void testLanguageVersionDiscoverer() { PMDConfiguration configuration = new PMDConfiguration(); LanguageVersionDiscoverer languageVersionDiscoverer = configuration.getLanguageVersionDiscoverer(); - assertEquals("Default Java version", LanguageRegistry.getLanguage(JavaLanguageModule.NAME).getVersion("10"), + assertEquals("Default Java version", LanguageRegistry.getLanguage(JavaLanguageModule.NAME).getVersion("11"), languageVersionDiscoverer .getDefaultLanguageVersion(LanguageRegistry.getLanguage(JavaLanguageModule.NAME))); configuration diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/LanguageVersionTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/LanguageVersionTest.java index 984a99b706..1adfc7c185 100644 --- a/pmd-java/src/test/java/net/sourceforge/pmd/LanguageVersionTest.java +++ b/pmd-java/src/test/java/net/sourceforge/pmd/LanguageVersionTest.java @@ -34,6 +34,12 @@ public class LanguageVersionTest extends AbstractLanguageVersionTest { LanguageRegistry.getLanguage(JavaLanguageModule.NAME).getVersion("1.7"), }, { JavaLanguageModule.NAME, JavaLanguageModule.TERSE_NAME, "1.8", LanguageRegistry.getLanguage(JavaLanguageModule.NAME).getVersion("1.8"), }, + { JavaLanguageModule.NAME, JavaLanguageModule.TERSE_NAME, "9", + LanguageRegistry.getLanguage(JavaLanguageModule.NAME).getVersion("9"), }, + { JavaLanguageModule.NAME, JavaLanguageModule.TERSE_NAME, "10", + LanguageRegistry.getLanguage(JavaLanguageModule.NAME).getVersion("10"), }, + { JavaLanguageModule.NAME, JavaLanguageModule.TERSE_NAME, "11", + LanguageRegistry.getLanguage(JavaLanguageModule.NAME).getVersion("11"), }, // this one won't be found: case sensitive! { "JAVA", "JAVA", "1.7", null, }, }); diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/Java11Test.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/Java11Test.java new file mode 100644 index 0000000000..aeb7942d4b --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/Java11Test.java @@ -0,0 +1,89 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.ast; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.List; + +import org.apache.commons.io.IOUtils; +import org.junit.Assert; +import org.junit.Test; + +import net.sourceforge.pmd.lang.ast.Node; +import net.sourceforge.pmd.lang.java.ParserTstUtil; + +public class Java11Test { + private static String loadSource(String name) { + try { + return IOUtils.toString(Java10Test.class.getResourceAsStream("jdkversiontests/java11/" + name), + StandardCharsets.UTF_8); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + @Test + public void testLocalVariableSyntaxForLambdaParametersWithJava10() { + ASTCompilationUnit compilationUnit = ParserTstUtil.parseAndTypeResolveJava("10", + loadSource("LocalVariableSyntaxForLambdaParameters.java")); + + List lambdas = compilationUnit.findDescendantsOfType(ASTLambdaExpression.class); + Assert.assertEquals(4, lambdas.size()); + + // (var x) -> String.valueOf(x); + List formalParameters = lambdas.get(0).findDescendantsOfType(ASTFormalParameter.class); + Assert.assertEquals(1, formalParameters.size()); + ASTType type = formalParameters.get(0).getFirstChildOfType(ASTType.class); + assertEquals("var", type.getTypeImage()); + assertEquals(1, type.jjtGetNumChildren()); + ASTReferenceType referenceType = type.getFirstChildOfType(ASTReferenceType.class); + assertNotNull(referenceType); + assertEquals(1, referenceType.jjtGetNumChildren()); + ASTClassOrInterfaceType classType = referenceType.getFirstChildOfType(ASTClassOrInterfaceType.class); + assertNotNull(classType); + assertEquals("var", classType.getImage()); + + // (var x, var y) -> x + y; + formalParameters = lambdas.get(1).findDescendantsOfType(ASTFormalParameter.class); + Assert.assertEquals(2, formalParameters.size()); + type = formalParameters.get(0).getFirstChildOfType(ASTType.class); + assertEquals("var", type.getTypeImage()); + assertEquals(1, type.jjtGetNumChildren()); + referenceType = type.getFirstChildOfType(ASTReferenceType.class); + assertNotNull(referenceType); + assertEquals(1, referenceType.jjtGetNumChildren()); + classType = referenceType.getFirstChildOfType(ASTClassOrInterfaceType.class); + assertNotNull(classType); + assertEquals("var", classType.getImage()); + type = formalParameters.get(1).getFirstChildOfType(ASTType.class); + assertEquals("var", type.getTypeImage()); + assertEquals(1, type.jjtGetNumChildren()); + + // (@Nonnull var x) -> String.valueOf(x); + formalParameters = lambdas.get(2).findDescendantsOfType(ASTFormalParameter.class); + Assert.assertEquals(1, formalParameters.size()); + Node firstChild = formalParameters.get(0).jjtGetChild(0); + Assert.assertTrue(firstChild instanceof ASTAnnotation); + } + + @Test + public void testLocalVariableSyntaxForLambdaParametersWithJava11() { + ASTCompilationUnit compilationUnit = ParserTstUtil.parseAndTypeResolveJava("11", + loadSource("LocalVariableSyntaxForLambdaParameters.java")); + + List lambdas = compilationUnit.findDescendantsOfType(ASTLambdaExpression.class); + Assert.assertEquals(4, lambdas.size()); + + // (var x) -> String.valueOf(x); + List formalParameters = lambdas.get(0).findDescendantsOfType(ASTFormalParameter.class); + Assert.assertEquals(1, formalParameters.size()); + Assert.assertNull(formalParameters.get(0).getTypeNode()); + Assert.assertTrue(formalParameters.get(0).isTypeInferred()); + } +} 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 7cdaa12da2..5a64ad0efe 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 @@ -53,6 +53,7 @@ public class XPathMetricFunctionTest { Report report = new Report(); ctx.setReport(report); ctx.setSourceCodeFilename("n/a"); + ctx.setIgnoreExceptions(false); // for test, we want immediate exceptions thrown and not collect them RuleSet rules = new RuleSetFactory().createSingleRuleRuleSet(rule); p.getSourceCodeProcessor().processSourceCode(new StringReader(code), new RuleSets(rules), ctx); return report.iterator(); diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codestyle/CodeStyleRulesTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codestyle/CodeStyleRulesTest.java index 98b22b1ecf..e2fb35e5ed 100644 --- a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codestyle/CodeStyleRulesTest.java +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codestyle/CodeStyleRulesTest.java @@ -42,6 +42,7 @@ public class CodeStyleRulesTest extends SimpleAggregatorTst { addRule(RULESET, "IdenticalCatchBranches"); addRule(RULESET, "IfElseStmtsMustUseBraces"); addRule(RULESET, "IfStmtsMustUseBraces"); + addRule(RULESET, "LinguisticNaming"); addRule(RULESET, "LocalHomeNamingConvention"); addRule(RULESET, "LocalInterfaceSessionNamingConvention"); addRule(RULESET, "LocalVariableCouldBeFinal"); diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/MissingSerialVersionUIDBase.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/MissingSerialVersionUIDBase.java new file mode 100644 index 0000000000..f402d9a793 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/MissingSerialVersionUIDBase.java @@ -0,0 +1,14 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.errorprone; + +import java.io.Serializable; + +/** + * This class is used for a test case for the rule MissingSerialVersionUID. + */ +public class MissingSerialVersionUIDBase implements Serializable { + private static final long serialVersionUID = 1234567L; +} diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/ast/jdkversiontests/java11/LocalVariableSyntaxForLambdaParameters.java b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/ast/jdkversiontests/java11/LocalVariableSyntaxForLambdaParameters.java new file mode 100644 index 0000000000..7daa4efd82 --- /dev/null +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/ast/jdkversiontests/java11/LocalVariableSyntaxForLambdaParameters.java @@ -0,0 +1,28 @@ +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.util.function.BiFunction; +import java.util.function.Function; + +public class LocalVariableSyntaxForLambdaParameters { + + @Target({ElementType.PARAMETER}) + @Retention(RetentionPolicy.RUNTIME) + public @interface Nonnull { } + + public void createLambdas() { + //var lambda = (var x, var y) -> x.process(y); + + Function lambda1 = (var x) -> String.valueOf(x); + BiFunction lambda2 = (var x, var y) -> x + y; + } + + public void createAnnotatedLambdaParameters() { + //@Nonnull var x = new Foo(); + //(@Nonnull var x, @Nullable var y) -> x.process(y) + + Function lambda1 = (@Nonnull var x) -> String.valueOf(x); + BiFunction lambda2 = (@Nonnull var x, @Nonnull var y) -> x + y; + } +} \ No newline at end of file diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/metrics/impl/xml/NPathTest.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/metrics/impl/xml/NPathTest.xml index 0180ebfb9e..8a305fa9f5 100644 --- a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/metrics/impl/xml/NPathTest.xml +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/metrics/impl/xml/NPathTest.xml @@ -459,4 +459,51 @@ class Bar { } ]]> + + + #1226 [java] NPath complexity false negative + 0 + 1 + + 'NPathComplexityOverflow#complexMethod()' has value 2147483647. + + + diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/AccessorClassGeneration.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/AccessorClassGeneration.xml index 091b027d96..9bd3fd0345 100644 --- a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/AccessorClassGeneration.xml +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/AccessorClassGeneration.xml @@ -4,9 +4,7 @@ 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"> - + inner class has private constructor 1 + java 10 + - + inner class has public constructor 0 + java 10 + - + outer class has public constructor 1 + java 10 + - + final inner class 0 + java 10 + - + interface inner class has private constructor 1 + java 10 + - + there's a check for int declaration - not sure right now why 1 + java 10 + #1452 ArrayIndexOutOfBoundsException with Annotations for AccessorClassGenerationRule 0 + java 10 + - + #291 - Private constructor called from anonymous class 1 + java 10 + - + Array initializer is not a class body 0 + java 10 diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/AccessorMethodGeneration.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/AccessorMethodGeneration.xml index 17b12632f4..bf1b8fd638 100644 --- a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/AccessorMethodGeneration.xml +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/AccessorMethodGeneration.xml @@ -4,9 +4,7 @@ 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"> - + inner class accesses private field from outer class 1 8 + java 10 + - + inner class accesses private field from outer class unqualified 1 8 + java 10 + - + outer class accesses private field from inner class 1 9 + java 10 - - + + + non private fields are ok 0 + java 10 + - + inner class accesses private method of outer class, unqualified 1 4 + java 10 + - + inner class accesses private method of outer class, qualified 1 4 + java 10 + - + outer class accesses private method of inner class 1 8 + java 10 - + inner class accesses non-private methods of outer class 0 + java 10 + - + #274 - Method inside static inner class incorrectly reported as generating accessor methods 0 + java 10 diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/JUnit4TestShouldUseAfterAnnotation.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/JUnit4TestShouldUseAfterAnnotation.xml index a71967faf6..e0854a0c66 100644 --- a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/JUnit4TestShouldUseAfterAnnotation.xml +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/JUnit4TestShouldUseAfterAnnotation.xml @@ -61,6 +61,34 @@ public class Foo { public void tearDown(Method m) { //... } +} + ]]> + + + #940 False positive with JUnit4TestShouldUseAfterAnnotation when JUnit5's 'AfterEach' is used + 0 + + + + #940 False positive with JUnit4TestShouldUseAfterAnnotation when JUnit5's 'AfterAll' is used + 0 + diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/JUnit4TestShouldUseBeforeAnnotation.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/JUnit4TestShouldUseBeforeAnnotation.xml index 272663322d..b9243c2bbe 100644 --- a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/JUnit4TestShouldUseBeforeAnnotation.xml +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/JUnit4TestShouldUseBeforeAnnotation.xml @@ -73,6 +73,34 @@ public class Foo { public void setUp(Method m) { //... } +} + ]]> + + + #940 False positive with JUnit4TestShouldUseBeforeAnnotation when JUnit5's 'BeforeEach' is used + 0 + + + + #940 False positive with JUnit4TestShouldUseBeforeAnnotation when JUnit5's 'BeforeAll' is used + 0 + diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/JUnit4TestShouldUseTestAnnotation.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/JUnit4TestShouldUseTestAnnotation.xml index 1f0d250eb2..7385b233bf 100644 --- a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/JUnit4TestShouldUseTestAnnotation.xml +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/JUnit4TestShouldUseTestAnnotation.xml @@ -151,6 +151,36 @@ public class TestForX { + + + #940 False positives with JUnit4TestShouldUseTestAnnotation when JUnit5 is used + 0 + diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/JUnitTestContainsTooManyAsserts.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/JUnitTestContainsTooManyAsserts.xml index 839d9cb0e5..1701f8bd70 100644 --- a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/JUnitTestContainsTooManyAsserts.xml +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/JUnitTestContainsTooManyAsserts.xml @@ -133,4 +133,74 @@ } ]]> + + + JUnit 5 Test contains more than one assert + 5 + 10,17,24,31,39 + + + + + TestNG Test contains more than one assert + 1 + 5 + + \ No newline at end of file diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/UseAssertEqualsInsteadOfAssertTrue.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/UseAssertEqualsInsteadOfAssertTrue.xml index da8e405407..82d90cfc3c 100644 --- a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/UseAssertEqualsInsteadOfAssertTrue.xml +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/UseAssertEqualsInsteadOfAssertTrue.xml @@ -65,6 +65,22 @@ JUnit4 - TEST2 1 + + + + 1 + 1 + + + + 1 + 1 + + + + 1 + [a-z][A-Za-z]* 1 - The class name 'foo' doesn't match '[A-Z][a-zA-Z0-9]+' + The class name 'foo' doesn't match '[A-Z][a-zA-Z0-9]*' + + + Test property defaults + 4 + + The field name 'Foo' doesn't match '[a-z][a-zA-Z0-9]*' + The final field name 'Hoo' doesn't match '[a-z][a-zA-Z0-9]*' + The static field name 'Bar' doesn't match '[a-z][a-zA-Z0-9]*' + The constant name 'BOLGaaa_FIELD' doesn't match '[A-Z][A-Z_0-9]*' + + + + + + Test default field property + [A-Z][A-Z0-9]+ + 3 + + The field name 'Foo' doesn't match '[A-Z][A-Z0-9]+' + The final field name 'Hoo' doesn't match '[a-z][a-zA-Z0-9]*' + The static field name 'Bar' doesn't match '[a-z][a-zA-Z0-9]*' + + + + + + Test final field property + [A-Z][A-Z0-9]+ + 3 + + The field name 'Foo' doesn't match '[a-z][a-zA-Z0-9]*' + The final field name 'Hoo' doesn't match '[A-Z][A-Z0-9]+' + The static field name 'Bar' doesn't match '[a-z][a-zA-Z0-9]*' + + + + + + Test static field property + [A-Z][A-Z0-9]+ + 3 + + The field name 'Foo' doesn't match '[a-z][a-zA-Z0-9]*' + The final field name 'Hoo' doesn't match '[a-z][a-zA-Z0-9]*' + The static field name 'Bar' doesn't match '[A-Z][A-Z0-9]+' + + + + + + Test constant field property + cons_[A-Z][A-Z0-9]+ + 4 + + The field name 'Foo' doesn't match '[a-z][a-zA-Z0-9]*' + The final field name 'Hoo' doesn't match '[a-z][a-zA-Z0-9]*' + The static field name 'Bar' doesn't match '[a-z][a-zA-Z0-9]*' + The constant name 'BOLG_FIELD' doesn't match 'cons_[A-Z][A-Z0-9]+' + + + + + + Test enum constant property + cons_[A-Z][A-Z0-9]+ + 2 + + The enum constant name 'NET' doesn't match 'cons_[A-Z][A-Z0-9]+' + The enum constant name 'ORG' doesn't match 'cons_[A-Z][A-Z0-9]+' + + + + + + + Test public constant property + cons_[A-Z][A-Z0-9]+ + 2 + + The public constant name 'DDD' doesn't match 'cons_[A-Z][A-Z0-9]+' + The constant name 'cons_BOLG_FIELD' doesn't match '[A-Z][A-Z0-9]+' + + + + + + + + Interface fields should be treated like constants + 3 + + The constant name 'Foo' doesn't match '[A-Z][A-Z_0-9]*' + The constant name 'Hoo' doesn't match '[A-Z][A-Z_0-9]*' + The constant name 'Bar' doesn't match '[A-Z][A-Z_0-9]*' + + + + + diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/codestyle/xml/FormalParameterNamingConventions.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/codestyle/xml/FormalParameterNamingConventions.xml index 60c2f9237b..e7af6d9c9f 100644 --- a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/codestyle/xml/FormalParameterNamingConventions.xml +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/codestyle/xml/FormalParameterNamingConventions.xml @@ -7,11 +7,11 @@ Default is camel case 5 - The method parameter name 'Foo' doesn't match '[a-z][a-zA-Z0-9]+' - The final method parameter name 'Hoo' doesn't match '[a-z][a-zA-Z0-9]+' - The lambda parameter name 'Koo' doesn't match '[a-z][a-zA-Z0-9]+' - The explicitly-typed lambda parameter name 'Voo' doesn't match '[a-z][a-zA-Z0-9]+' - The explicitly-typed lambda parameter name 'Ooo' doesn't match '[a-z][a-zA-Z0-9]+' + The method parameter name 'Foo' doesn't match '[a-z][a-zA-Z0-9]*' + The final method parameter name 'Hoo' doesn't match '[a-z][a-zA-Z0-9]*' + The lambda parameter name 'Koo' doesn't match '[a-z][a-zA-Z0-9]*' + The explicitly-typed lambda parameter name 'Voo' doesn't match '[a-z][a-zA-Z0-9]*' + The explicitly-typed lambda parameter name 'Ooo' doesn't match '[a-z][a-zA-Z0-9]*' + + One character lambda Parameters should be allowed by default + 0 + i = (s) -> { + + }; + + Consumer k = (String s) -> { + + }; + + Consumer l = (final String s) -> { + + }; + } + } + ]]> + + Test method param pattern [A-Z]+ 5 The method parameter name 'Foo' doesn't match '[A-Z]+' - The final method parameter name 'Hoo' doesn't match '[a-z][a-zA-Z0-9]+' - The lambda parameter name 'Koo' doesn't match '[a-z][a-zA-Z0-9]+' - The explicitly-typed lambda parameter name 'Voo' doesn't match '[a-z][a-zA-Z0-9]+' - The explicitly-typed lambda parameter name 'Ooo' doesn't match '[a-z][a-zA-Z0-9]+' + The final method parameter name 'Hoo' doesn't match '[a-z][a-zA-Z0-9]*' + The lambda parameter name 'Koo' doesn't match '[a-z][a-zA-Z0-9]*' + The explicitly-typed lambda parameter name 'Voo' doesn't match '[a-z][a-zA-Z0-9]*' + The explicitly-typed lambda parameter name 'Ooo' doesn't match '[a-z][a-zA-Z0-9]*' [A-Z]+ 5 - The method parameter name 'Foo' doesn't match '[a-z][a-zA-Z0-9]+' + The method parameter name 'Foo' doesn't match '[a-z][a-zA-Z0-9]*' The final method parameter name 'Hoo' doesn't match '[A-Z]+' - The lambda parameter name 'Koo' doesn't match '[a-z][a-zA-Z0-9]+' - The explicitly-typed lambda parameter name 'Voo' doesn't match '[a-z][a-zA-Z0-9]+' - The explicitly-typed lambda parameter name 'Ooo' doesn't match '[a-z][a-zA-Z0-9]+' + The lambda parameter name 'Koo' doesn't match '[a-z][a-zA-Z0-9]*' + The explicitly-typed lambda parameter name 'Voo' doesn't match '[a-z][a-zA-Z0-9]*' + The explicitly-typed lambda parameter name 'Ooo' doesn't match '[a-z][a-zA-Z0-9]*' [A-Z]+ 5 - The method parameter name 'Foo' doesn't match '[a-z][a-zA-Z0-9]+' - The final method parameter name 'Hoo' doesn't match '[a-z][a-zA-Z0-9]+' + The method parameter name 'Foo' doesn't match '[a-z][a-zA-Z0-9]*' + The final method parameter name 'Hoo' doesn't match '[a-z][a-zA-Z0-9]*' The lambda parameter name 'Koo' doesn't match '[A-Z]+' - The explicitly-typed lambda parameter name 'Voo' doesn't match '[a-z][a-zA-Z0-9]+' - The explicitly-typed lambda parameter name 'Ooo' doesn't match '[a-z][a-zA-Z0-9]+' + The explicitly-typed lambda parameter name 'Voo' doesn't match '[a-z][a-zA-Z0-9]*' + The explicitly-typed lambda parameter name 'Ooo' doesn't match '[a-z][a-zA-Z0-9]*' [A-Z]+ 5 - The method parameter name 'Foo' doesn't match '[a-z][a-zA-Z0-9]+' - The final method parameter name 'Hoo' doesn't match '[a-z][a-zA-Z0-9]+' - The lambda parameter name 'Koo' doesn't match '[a-z][a-zA-Z0-9]+' + The method parameter name 'Foo' doesn't match '[a-z][a-zA-Z0-9]*' + The final method parameter name 'Hoo' doesn't match '[a-z][a-zA-Z0-9]*' + The lambda parameter name 'Koo' doesn't match '[a-z][a-zA-Z0-9]*' The explicitly-typed lambda parameter name 'Voo' doesn't match '[A-Z]+' The explicitly-typed lambda parameter name 'Ooo' doesn't match '[A-Z]+' diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/codestyle/xml/LinguisticNaming.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/codestyle/xml/LinguisticNaming.xml new file mode 100644 index 0000000000..68a35fabcf --- /dev/null +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/codestyle/xml/LinguisticNaming.xml @@ -0,0 +1,417 @@ + + + + + + Method Prefix is + 1 + 6 + + Linguistics Antipattern - The method 'isValid' indicates linguistically it returns a boolean, but it returns 'int' + + + + + + Method Prefix Has + 1 + 6 + + Linguistics Antipattern - The method 'hasChild' indicates linguistically it returns a boolean, but it returns 'int' + + + + + + Method Prefix Have + 1 + 6 + + Linguistics Antipattern - The method 'haveChild' indicates linguistically it returns a boolean, but it returns 'int' + + + + + + Method Prefix can + 1 + 6 + + Linguistics Antipattern - The method 'canFly' indicates linguistically it returns a boolean, but it returns 'int' + + + + + + Method Prefix will + 1 + 6 + + Linguistics Antipattern - The method 'willFly' indicates linguistically it returns a boolean, but it returns 'int' + + + + + + Method Prefix should + 1 + 6 + + Linguistics Antipattern - The method 'shouldFly' indicates linguistically it returns a boolean, but it returns 'long' + + + + + + Method Setters + 1 + 6 + + Linguistics Antipattern - The setter 'setName' should not return any type except void linguistically + + + + + + Method Getters + 1 + 6 + + Linguistics Antipattern - The getter 'getName' should not return void linguistically + + + + + + Method Prefix to: Transform Method + 1 + 6 + + Linguistics Antipattern - The transform method 'toDataType' should not return void linguistically + + + + + + Method Contains To: Transformation methods + true + 1 + 2 + + Linguistics Antipattern - The transform method 'grapeToWine' should not return void linguistically + + + + + + Field/Variable Prefix is + 2 + 3,8 + + Linguistics Antipattern - The field 'isValid' indicates linguistically it is a boolean, but it is 'int' + Linguistics Antipattern - The variable 'isValidLocal' indicates linguistically it is a boolean, but it is 'int' + + + + + + Field/Variable Prefix has + 2 + 3,8 + + Linguistics Antipattern - The field 'hasMoney' indicates linguistically it is a boolean, but it is 'int' + Linguistics Antipattern - The variable 'hasMoneyLocal' indicates linguistically it is a boolean, but it is 'int' + + + + + + Field/Variable Prefix can + 2 + 3,8 + + Linguistics Antipattern - The field 'canFly' indicates linguistically it is a boolean, but it is 'int' + Linguistics Antipattern - The variable 'canFlyLocal' indicates linguistically it is a boolean, but it is 'int' + + + + + + Field/Variable Prefix will + 2 + 3,8 + + Linguistics Antipattern - The field 'willMove' indicates linguistically it is a boolean, but it is 'int' + Linguistics Antipattern - The variable 'willMoveLocal' indicates linguistically it is a boolean, but it is 'int' + + + + + + Field/Variable Prefix have + 2 + 3,8 + + Linguistics Antipattern - The field 'haveLegs' indicates linguistically it is a boolean, but it is 'int' + Linguistics Antipattern - The variable 'haveLegsLocal' indicates linguistically it is a boolean, but it is 'int' + + + + + + Field/Variable Prefix should + 2 + 3,8 + + Linguistics Antipattern - The field 'shouldClimb' indicates linguistically it is a boolean, but it is 'int' + Linguistics Antipattern - The variable 'shouldClimbLocal' indicates linguistically it is a boolean, but it is 'int' + + + + + + Multiple fields/local variables + 4 + 2,2,4,4 + + + + + Boolean fields/methods false positive + 0 + + + diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/codestyle/xml/LocalVariableNamingConventions.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/codestyle/xml/LocalVariableNamingConventions.xml index 279131ffe3..e54e1fed1d 100644 --- a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/codestyle/xml/LocalVariableNamingConventions.xml +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/codestyle/xml/LocalVariableNamingConventions.xml @@ -7,9 +7,9 @@ Default is camel case 3 - The local variable name 'Foo' doesn't match '[a-z][a-zA-Z0-9]+' - The final local variable name 'Hoo' doesn't match '[a-z][a-zA-Z0-9]+' - The exception block parameter name 'E' doesn't match '[a-z][a-zA-Z0-9]+' + The local variable name 'Foo' doesn't match '[a-z][a-zA-Z0-9]*' + The final local variable name 'Hoo' doesn't match '[a-z][a-zA-Z0-9]*' + The exception block parameter name 'E' doesn't match '[a-z][a-zA-Z0-9]*' 3 The local variable name 'Foo' doesn't match '[A-Z][A-Z0-9]+' - The final local variable name 'Hoo' doesn't match '[a-z][a-zA-Z0-9]+' - The exception block parameter name 'E' doesn't match '[a-z][a-zA-Z0-9]+' + The final local variable name 'Hoo' doesn't match '[a-z][a-zA-Z0-9]*' + The exception block parameter name 'E' doesn't match '[a-z][a-zA-Z0-9]*' [A-Z][A-Z0-9]+ 3 - The local variable name 'Foo' doesn't match '[a-z][a-zA-Z0-9]+' + The local variable name 'Foo' doesn't match '[a-z][a-zA-Z0-9]*' The final local variable name 'Hoo' doesn't match '[A-Z][A-Z0-9]+' - The exception block parameter name 'E' doesn't match '[a-z][a-zA-Z0-9]+' + The exception block parameter name 'E' doesn't match '[a-z][a-zA-Z0-9]*' [A-Z][A-Z0-9]+ 3 - The local variable name 'Foo' doesn't match '[a-z][a-zA-Z0-9]+' - The final local variable name 'Hoo' doesn't match '[a-z][a-zA-Z0-9]+' + The local variable name 'Foo' doesn't match '[a-z][a-zA-Z0-9]*' + The final local variable name 'Hoo' doesn't match '[a-z][a-zA-Z0-9]*' The exception block parameter name 'Eff' doesn't match '[A-Z][A-Z0-9]+' + + One character for loop variables should be ok by default + 0 + data = Arrays.asList("a", "b"); + for (String s : data) { } + } + } + ]]> + diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/codestyle/xml/MethodNamingConventions.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/codestyle/xml/MethodNamingConventions.xml index c54c8e4b99..c494c196af 100644 --- a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/codestyle/xml/MethodNamingConventions.xml +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/codestyle/xml/MethodNamingConventions.xml @@ -16,7 +16,7 @@ public class Foo { method names should not contain underscores 1 - The instance method name 'bar_foo' doesn't match '[a-z][a-zA-Z0-9]+' + The instance method name 'bar_foo' doesn't match '[a-z][a-zA-Z0-9]*' 1 2 - The native method name '__surfunc__' doesn't match '[a-z][a-zA-Z0-9]+' + The native method name '__surfunc__' doesn't match '[a-z][a-zA-Z0-9]*' + + + #1255 [java] UnnecessaryFullyQualifiedName false positive: static method on shadowed implicitly imported class + 0 + + + + + Nullpointer in isJavaLangImplicit for java.lang.String[] arrays + 1 + 6 + + diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/codestyle/xml/UselessParentheses.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/codestyle/xml/UselessParentheses.xml index 2a2de5804d..777c87bcca 100644 --- a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/codestyle/xml/UselessParentheses.xml +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/codestyle/xml/UselessParentheses.xml @@ -483,6 +483,22 @@ public class Test { return (Character.toUpperCase(str1.charAt(0)) + str1.substring(1)).replace('_', ' '); } +} + ]]> + + + + [java] False positive "UselessParentheses" for parentheses that contain assignment #1285 + 0 + diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/design/xml/SimplifyBooleanAssertion.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/design/xml/SimplifyBooleanAssertion.xml index cc43b31f29..fbc4958638 100644 --- a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/design/xml/SimplifyBooleanAssertion.xml +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/design/xml/SimplifyBooleanAssertion.xml @@ -70,6 +70,23 @@ JUnit 4 - assertFalse(!) 1 + + + + 1 + + + + #1078 [java] MissingSerialVersionUID rule does not seem to catch inherited classes + 1 + + diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/UnnecessaryBooleanAssertion.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/UnnecessaryBooleanAssertion.xml index d06f84fb16..a0be95c777 100644 --- a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/UnnecessaryBooleanAssertion.xml +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/UnnecessaryBooleanAssertion.xml @@ -110,6 +110,22 @@ JUnit 4 - failure case 1 + + + + 1 + + + + + #1298 [java] RedundantFieldInitializer - NumberFormatException with Long + 0 + diff --git a/pmd-java8/pom.xml b/pmd-java8/pom.xml index 9b0d5310d2..2fa4054c77 100644 --- a/pmd-java8/pom.xml +++ b/pmd-java8/pom.xml @@ -7,7 +7,7 @@ net.sourceforge.pmd pmd - 6.6.0-SNAPSHOT + 6.7.0-SNAPSHOT diff --git a/pmd-javascript/pom.xml b/pmd-javascript/pom.xml index f3590ca568..e6c8d5a17a 100644 --- a/pmd-javascript/pom.xml +++ b/pmd-javascript/pom.xml @@ -7,7 +7,7 @@ net.sourceforge.pmd pmd - 6.6.0-SNAPSHOT + 6.7.0-SNAPSHOT diff --git a/pmd-jsp/etc/grammar/JspParser.jjt b/pmd-jsp/etc/grammar/JspParser.jjt index 7ea94dcf5f..44481522b2 100644 --- a/pmd-jsp/etc/grammar/JspParser.jjt +++ b/pmd-jsp/etc/grammar/JspParser.jjt @@ -248,7 +248,7 @@ PARSER_END(JspParser) TOKEN : { - > + | "${" ( | )* "}") > | " > : AfterTagState | " | "!>") > : AfterTagState | " > : AfterTagState diff --git a/pmd-jsp/pom.xml b/pmd-jsp/pom.xml index c593722c19..614119b8e5 100644 --- a/pmd-jsp/pom.xml +++ b/pmd-jsp/pom.xml @@ -7,7 +7,7 @@ net.sourceforge.pmd pmd - 6.6.0-SNAPSHOT + 6.7.0-SNAPSHOT diff --git a/pmd-jsp/src/main/java/net/sourceforge/pmd/cpd/JSPLanguage.java b/pmd-jsp/src/main/java/net/sourceforge/pmd/cpd/JSPLanguage.java index 0d29af459e..543602a60c 100644 --- a/pmd-jsp/src/main/java/net/sourceforge/pmd/cpd/JSPLanguage.java +++ b/pmd-jsp/src/main/java/net/sourceforge/pmd/cpd/JSPLanguage.java @@ -6,6 +6,6 @@ package net.sourceforge.pmd.cpd; public class JSPLanguage extends AbstractLanguage { public JSPLanguage() { - super("JSP", "jsp", new JSPTokenizer(), ".jsp", ".jspx"); + super("JSP", "jsp", new JSPTokenizer(), ".jsp", ".jspx", ".jspf", ".tag"); } } diff --git a/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/JspLanguageModule.java b/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/JspLanguageModule.java index 8fa12452f2..2ad43f34be 100644 --- a/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/JspLanguageModule.java +++ b/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/JspLanguageModule.java @@ -16,7 +16,7 @@ public class JspLanguageModule extends BaseLanguageModule { public static final String TERSE_NAME = "jsp"; public JspLanguageModule() { - super(NAME, "JSP", TERSE_NAME, JspRuleChainVisitor.class, "jsp"); + super(NAME, "JSP", TERSE_NAME, JspRuleChainVisitor.class, "jsp", "jspx", "jspf", "tag"); addVersion("", new JspHandler(), true); } } diff --git a/pmd-jsp/src/test/java/net/sourceforge/pmd/lang/jsp/JspParserTest.java b/pmd-jsp/src/test/java/net/sourceforge/pmd/lang/jsp/JspParserTest.java index b05018c207..6f4cae6f8b 100644 --- a/pmd-jsp/src/test/java/net/sourceforge/pmd/lang/jsp/JspParserTest.java +++ b/pmd-jsp/src/test/java/net/sourceforge/pmd/lang/jsp/JspParserTest.java @@ -4,12 +4,16 @@ package net.sourceforge.pmd.lang.jsp; +import java.io.File; import java.io.StringReader; +import java.nio.file.Paths; import org.junit.Assert; import org.junit.Test; import net.sourceforge.pmd.lang.LanguageRegistry; +import net.sourceforge.pmd.lang.LanguageVersion; +import net.sourceforge.pmd.lang.LanguageVersionDiscoverer; import net.sourceforge.pmd.lang.LanguageVersionHandler; import net.sourceforge.pmd.lang.Parser; import net.sourceforge.pmd.lang.ast.Node; @@ -29,7 +33,21 @@ public class JspParserTest { "$129.00"); Assert.assertNotNull(node); } - + + @Test + public void testParseELAttribute() { + Node node = parse( + "
Div content here.
"); + Assert.assertNotNull(node); + } + + @Test + public void testParseELAttributeValue() { + Node node = parse( + "
Div content here.
"); + Assert.assertNotNull(node); + } + /** * Verifies bug #311 Jsp parser fails on boolean attribute */ @@ -40,6 +58,29 @@ public class JspParserTest { Assert.assertNotNull(node); } + @Test + public void testParseJsp() { + testInternalJspFile(Paths.get("sample.jsp").toFile()); + testInternalJspFile(Paths.get("sample.jspx").toFile()); + } + + @Test + public void testParseTag() { + testInternalJspFile(Paths.get("sample.tag").toFile()); + } + + @Test(expected = AssertionError.class) + public void testParseWrong() { + testInternalJspFile(Paths.get("sample.xxx").toFile()); + } + + private void testInternalJspFile(File jspFile) { + LanguageVersionDiscoverer discoverer = new LanguageVersionDiscoverer(); + LanguageVersion languageVersion = discoverer.getDefaultLanguageVersionForFile(jspFile); + Assert.assertEquals("LanguageVersion must be JSP!", + LanguageRegistry.getLanguage(JspLanguageModule.NAME).getDefaultVersion(), languageVersion); + } + private Node parse(String code) { LanguageVersionHandler jspLang = LanguageRegistry.getLanguage(JspLanguageModule.NAME).getDefaultVersion() .getLanguageVersionHandler(); diff --git a/pmd-matlab/pom.xml b/pmd-matlab/pom.xml index d3f6d1e404..c08b83faa1 100644 --- a/pmd-matlab/pom.xml +++ b/pmd-matlab/pom.xml @@ -7,7 +7,7 @@ net.sourceforge.pmd pmd - 6.6.0-SNAPSHOT + 6.7.0-SNAPSHOT diff --git a/pmd-objectivec/pom.xml b/pmd-objectivec/pom.xml index 7f7b5bc551..049e8bc890 100644 --- a/pmd-objectivec/pom.xml +++ b/pmd-objectivec/pom.xml @@ -7,7 +7,7 @@ net.sourceforge.pmd pmd - 6.6.0-SNAPSHOT + 6.7.0-SNAPSHOT diff --git a/pmd-perl/pom.xml b/pmd-perl/pom.xml index e0a9de5e30..70f6d334e7 100644 --- a/pmd-perl/pom.xml +++ b/pmd-perl/pom.xml @@ -7,7 +7,7 @@ net.sourceforge.pmd pmd - 6.6.0-SNAPSHOT + 6.7.0-SNAPSHOT diff --git a/pmd-php/pom.xml b/pmd-php/pom.xml index 3d558889b9..83b4e422a5 100644 --- a/pmd-php/pom.xml +++ b/pmd-php/pom.xml @@ -7,7 +7,7 @@ net.sourceforge.pmd pmd - 6.6.0-SNAPSHOT + 6.7.0-SNAPSHOT diff --git a/pmd-plsql/etc/grammar/PldocAST.jjt b/pmd-plsql/etc/grammar/PldocAST.jjt index 0d162e6bec..f265be2476 100644 --- a/pmd-plsql/etc/grammar/PldocAST.jjt +++ b/pmd-plsql/etc/grammar/PldocAST.jjt @@ -28,6 +28,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. /** * Added more complete support for CREATE TABLE + * Added ASTCursorForLoop and ASTSelectStatement * * Andreas Dangel 07/2018 *==================================================================== @@ -1176,6 +1177,7 @@ ASTUnlabelledStatement UnlabelledStatement() : | LOOKAHEAD(3) ContinueStatement() ";" // CONTINUE keyword was added in 11G, so Oracle compilation supports CONTINUE as a variable name | CaseStatement() ";" | IfStatement() ";" + | LOOKAHEAD( ID() "("