diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md index 6808cd0d3a0..4f159c57c65 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE.md @@ -1,6 +1,9 @@ -Please, prefix the report title with the language it applies to within brackets, such as *[java]* or *[apex]*. If not specific to a language, you can use *[core]* + -**Rule Set:** +**Affects PMD Version:** + +**Rule:** **Description:** @@ -12,3 +15,4 @@ Please, prefix the report title with the language it applies to within brackets, **Running PMD through:** *[CLI | Ant | Maven | Gradle | Designer | Other]* + diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 806b082a1d3..6d529e9e99d 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,9 +1,10 @@ + Before submitting a PR, please check that: - [ ] The PR is submitted against `master`. The PMD team will merge back to support branches as needed. - - [ ] `mvn test` passes. - - [ ] `mvn checkstyle:check` passes. [Check this for more info](https://github.com/pmd/pmd/blob/master/CONTRIBUTING.md#code-style) + - [ ] `./mvnw clean verify` passes. This will [build](https://github.com/pmd/pmd/blob/master/BUILDING.md) and test PMD, execute PMD and checkstyle rules. [Check this for more info](https://github.com/pmd/pmd/blob/master/CONTRIBUTING.md#code-style) **PR Description:** diff --git a/.gitignore b/.gitignore index e0312faab27..69b2c1e6d7f 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,6 @@ bin/ .idea *.patch */src/site/site.xml +pmd-core/dependency-reduced-pom.xml +.bundle +vendor diff --git a/.mvn/wrapper/maven-wrapper.jar b/.mvn/wrapper/maven-wrapper.jar index 9cc84ea9b4d..f775b1c04cf 100755 Binary files a/.mvn/wrapper/maven-wrapper.jar and b/.mvn/wrapper/maven-wrapper.jar differ diff --git a/.mvn/wrapper/maven-wrapper.properties b/.mvn/wrapper/maven-wrapper.properties index 56bb0164ec1..c9023edfe7c 100755 --- a/.mvn/wrapper/maven-wrapper.properties +++ b/.mvn/wrapper/maven-wrapper.properties @@ -1 +1 @@ -distributionUrl=https://repo1.maven.org/maven2/org/apache/maven/apache-maven/3.5.0/apache-maven-3.5.0-bin.zip \ No newline at end of file +distributionUrl=https://repo1.maven.org/maven2/org/apache/maven/apache-maven/3.5.4/apache-maven-3.5.4-bin.zip \ No newline at end of file diff --git a/.travis.yml b/.travis.yml index 627df0e99cd..9475419f49a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,39 +1,58 @@ -dist: trusty -sudo: false addons: - apt: - packages: - - oracle-java8-installer ssh_known_hosts: - web.sourceforge.net language: java -jdk: oraclejdk8 - env: global: - secure: KBEuB6U1p5RQXSYe157AwydFr/zpXQPA0IChVCgZV+X1mMyy9ZtrjH1J1AXuviseDDXDbaT25sRnsvpl82rfRw2xOkMGXHy4N95/ylTSr8DjHxTao71BhXsvFycNobFva5y2EGNWqDvpS8I2oSZo7Qk4la3yep3rcJQvcy6RDbbhpDTbL1QMFyadunIBm0WtqbunrMqtjSqaoPsXz8TiQuxHvX4vEXzVbaxV1QQt79Vi+daa6wAV3mRQAugnx+UffsC8JqMxgm06usWeJgCJzxgm8E7clZCLmf53B2TL8dK6bIYbqyvOY3uFxitsTG0d8Z0GOJwXBgZNgbniTRO8ZJSty5eZP8LBybbjVLSL25DNTWtCjADUL/uySnXIEidlMt2N/3QmH7zrGAfAk/tIwKpdRca2GLLydeXf6PSkiahnPEkIY/QupcsOLELhdifpdOjb8QW1OenA+vUbNM9dccLwKnX6Fj9cu4VQG601AcYDr2eyhq8WYkr3wYdw/6KdUa3hmplowTBs+qguppP+eOSgGuEsy38KLtqnvm6WlHy6tcLmcVYKG3DmR1b7TWXsOXC6/VMH8BHBkvsF1QdRg9+Cgx07vX3Hw7roPiYzmaO9Ajs20ATsUfRskMuWCTeTSK5pN8X27veRCZlhFjeKQMDdmfVwzpAfRgKsl3TEn1I= - secure: U1DfAv6acUUWe+dao/ZSDUX64JRadNJY16rITsdrM4ZNAJSuXpEY3p/LWcYjN7D49YmyutbXH9+L3KKQUQGrGXj9QTarfYvd8ZsKt4FK8yv7AFy+RQNIbAjNEHBzHx15p+srMheTaetl7aLwY0qhF+D/RtGapxHKyY4dBHrb0lp8VGyiCiL7Aop8GGskosi1mtirPBp/BStPZ2bEyxG0QzU5SsVWkJWwV9aWLPVAR/n7Xgx/6Gjl6Fed2c/WSrWi4vchm3Ny8pfTweOax3PGYYjBVxIfuX0mqmwuJsY7gNfXCfN3dPiPKGJPFy1pC+LGyGkklO5ReKFLd4O1ME6fU0dlIGfD6n+Q4H6/w9FHXegcKTfWIJm/MFa6vA/tJM5R6zJQuiTQJboHm/UmS/iQj76z0p6sK15Xp5vFId+/dHKqa8xY+Bt6HiXy6z401HOc8QcYBAf7TqhqUt/ZE7HN4be46uR90KmzIrWz6wEoDW7HfwQ9ZMbs55zoOXrvekyE9/gXskypO0p2JT3Y0vlvO27KQvIrSwI480kOVOrlyrYA+LZqlcKaayOuCuZh4lITQUYinUoZZict5joYthH+Cyh2zovoBpxsntDJdMnaZNLtSC7hlhpbMBYaT2y1O6vZH5Yix0mxuDvs/x6ogP5CNBeUYlXhaL+g4GnwKyr0ZA0= - - secure: "ElWjttpoMwqezP2zyHkx7CiPON/mRLUW7SsSlEqdlTUYRIaSaL7sShUORJUzj58U/Nnfi/eY4Rweo0CtVu20sG16s9B4adnmPS44LAitztybPR/co93gN9qb8jiIl41nVcOJq+Rut2Z4nr8AGRhVCG9Drg4+DreCqQ4DE5ZD6J99GN0IFGrREt59HQUlBlT9jD0AndK/1GQ6kWAenuSREE2P5ih59rf6FrrfddQO5iqyFFoaHek3JiOmpaLK+z7i4EvMKsAJEooFNuGhHVuyaJJRRZHAncGiQ+uW/yL7G5rY51G6YSNJczzEtfrRg+YrIUbokUPmifSIbnXO/AIZRAq1dMhA8exRc5n+75RB1X164qm4a+yggdybFBh0L59xu606PJQJAuWy+x10nled7FAVXiotxbB0r2bhlVmWFEN+FWPqXg2rVkHvw6+5PM67rG5g1LCgjKzb9I6JWPiVI+EMn8Pw8gryL2Sdqtl08Da0Ypbl2ZK/afU4CEPEKnrDc9ecXKjacehTYwGS48V213XvArcmoEpsyZDRSMTrvK3/e29GEaxoK/ZzWJylpeOtsICEEFYEBEiDsiIW4C50MIUPT2xHV6MaPQzhkuwrbQ6QFYzHzihBaiQuMmXUQ4DMb3fOPkNZAQHQo8CgJ78IcbCABAoGv9mu/fo2KoLo11U=" + - secure: "gxI7W4V4fUPQLMCvecXXdet1/mCh6m0RIQMtErVVOnwxEEjeDmko/3rHSl+wk6IeFaaQKcmHJJSJEj+e5TgWRH1uuzCnbFHZhuH28ce/H0EqLJ3GTXeDEgFLzhh//T3ySOZChZELgJ67cKxZJNFMhBVOR2/QIGjZjdzvl36ugsu80Ak3XJb8HCm8D2P8Vuezz/OlCkSOGXaiqZXfflV/cuaLWuueGfQGW9x/UyDTNsuBdB7YnEcxOWt8RgB4JrcPGV7/etxLHXTw9IMEeUhQ/RsxLZHKNYt9cx/QOZkpXemhdT9L2pAIi77eO98x+yAfB1qV6T6IeDd/OiABUzfb7asNBwE+bYRuVkCiWLo3x3hs8FcgsPe4L10guxfRMJPxax9E7uYb9TFxtvc42dDWNyp1BVCtEtmjErO4CJeW97ukhgkUkPhzUZgQ2Gj5Tn4VRjQOQyzh/S/YsHyfxoAQrscc/3bq5PBmtdowyGQd5dsvShdpE6glf+HnBZ8TCw5BaxQuTjapQWjsGuJVCVsrNDtwOFy5UTfxK9OeTUsjwfO5JjkoIeqGyLHPnTMWyZ6EYa//nWPOU1KgVE6Hrzfr3zxEzL1nN/TYahzhKOxXE701W9YhTmhCVljMlSYioTUfTaXHgGLpojvSWlhsTbc3r0gj4NglqakIy7MKgQLgES8=" - secure: "otZkFNhApMofp2Jl2baZdpNEPcTa79Xc9wyOw1gl1+ubCLtOGr32FDUSr0HQWKrGXcYZR4bc0A80hx0/ykZ/twJaRAabcNYtjwRMDKKrp3plvp8RTZlB1QW85nsdmk3qmHACzc94wKnmcaDohM5FPxnWspZG5Y4ejYX4HEK6r1fwL4FjMPSLd5QW8yCaAc7dpBSm2UyIXuuDjkekXblJFe8ydbf9aF97WKJMCwqIs9Zo5oJa2pnWCw47ixwjVdkaH5x5727I+YmFqn5rjdEideUFEAlggqMaxuOtfYhuG/8k5W3Q/+WX+G7rEPTcemjmaosbbpoOUE0YUHV3Wi3R5D66NuWIpQwX6kw4rTwL0YIh//0aNb8jv4Z8KwkiTMrYuj0we5tXzARp3yYn56P0JBAm6BihNhfEBv06655Tg5LUsWqUTZCl3KJhwtO1N+OuOkE5qoZm59knq6DeZOsYWAVb3MpzklP06kXGYTjb44PVMUc6ynuuZjgPJDv/0906feA3REHBJVxd3PvJp1c8VvQSeW3ndghEz0akJHY/jxV1rJZAZC1ZeshYUpBNmh0sFDty/UE43IvIB4EInypQHyTtQvMc6LuLjTeu2E5ZMsA3udj66EMrYm4Y2eheJycSfttDqzhsKXIVJ4LmA/xtDR01cp1jsqbbZBhVI67rA40=" - secure: "mmHRnK/8CHfgtwaTN3MgjFD1R8EVQhIb/a0Aag3gPaHWW52Ex6xWG7GsF4kRcdzHdIdrQxHZTiDa2oVMWbnQ16CAu4dZJadwTxhBW4JmE1tsi2C6i+uwMBPvnKBXHwt+QOAqNB0L/ByYwWLkucSLoyRs6qU0i7sJ3ZC/EPCuLI8VfBlFUVegSbp2WmclmFnxOi5MKr15Zwd8BGgyWUsvd1OHxhvO3Xr/ZPO4R+1kIgyojloTMu6oGM/shYgsrUXgmI/WvZRTq+25SRCjUmATJtuDXXgVeJtYabi/+Zju5Z7K06oaKvFYM+BgZWPmGQkVCxKVjSlug0rx6+qgRKIf/B/cPxmQDS0ZZVDmDcbxeibBL9JhRqIDwDb0XS9t1qBXvIJ5E4ZmdX/XuvLCnC4xJptu/vHyKl1wbRM3dcXEBk8J0knJc4JvY2yupF3LS32sfEHftLwzr63U3/0dOQvsCSJRBy5zgI8ieABUZAeZmmaQ6RCGOTEYrGO2G4CCF2uVq4YdrCOsfmrb0DzH8llNMkXKzXlITRvmkuBlTRRKxGnPzS3vf0iicdAkmTiU5u7lArFFc7IKcW4ypO+RKDTUpq74K+UZBI9eBjV7Po368EO8epJ5aIx32a2kyS/hYWkS/dZh7I0fNZa2qBUceZO/KZGRjH4dQFW9t0J9Q7LLXJY=" - secure: "geOU/7VR83KYTOCR5XkgFqnHJEnXeB6hNDE7UFcN4ZsL9uVLhxlDVWV3ujJ87nyBzggG1KfSnUx11UwOQrbBl0K6sEXo/B2q5ytOsWoSLi7+0qm/uhhw/DTBOq57p8TIDKFM7rDhO7uajSn7Y86LeZkh9wIs5265Fh1yhCkiPbczDUMsX4P0GGg2qgkIKSBw4DZf6sPDc4xjJq/4/cLnjXo/K0ZYAIoSLqDfe1gmkzMtQlad+1t8Lwv/gOJ4BNRF4a5aEvGF6kbFovFoeFWCQTKlkn6VJtY+BETGKW2RH9efGeMs7JbBbNm+nnpoNsbJ0gdmGlhSbWT8enIm72/P0ThTBJWVcLJ30tUd0UeYD9C49wbJ3RHSxsoUKP9YhHJ+XKNx+8irkJ8LPCkNMQEFE153gEVvU7tCgah9Uq6laYZv9cQE0dWzNt3//Ymls8BzY38Ha4PbrIIJqEuWwnAX6ZgTgzVtXx1AJjkDFG9KFKPhN7NfNMbh7rn5C84a1h5zrIydm+6RcIBC+xTLDEiN7djiquf3PcNtyGLcO8dNN4Uv8iU0Jw8ch3+w0PJO53v5py+IS1V8pwwYaYF2iVPOoziS0tP8MsgYVGwpTZhvz3v8x0ge8r6KJE4zdvoPtCRNr2Oo3sJGYqkt86Hu6agKFiljP5AgJNx7iQD5GQn6J3w=" - secure: "gqyPF3d75JuwZptnQQQKO6rGUILXGO09y9dZhShdX8qq5Mexa2ISk77Y/b0yyAdhJMgEglls5cWlbHkwCAB0i4yL25RE5CtQUe4er+CIKwBIs0M56ghbcOwWAFB/cXeyyWmXDHBVnVUeKm7IJ34txsEclnMzQSR/DInSzsCFMUQok8HEaWZNssMjyRgo6goKGdG5vNWGfLLY7mKGMe/6PpYJD0G8k+Na08aX06ZWph38He8O3imUAngoQrxiSp0UYmnHDkigbV7S2LdZOsj7kMxkiyigzK/P1eygHVbFAhohLroaqS9B4CBK7QeOyup2vxVKqJVzFwOCylD0786CImvaG2ZYMtvJkapvaEE0mdMlyYpHgdIkmHZu28aMW7hXEhxHyBAxKuPcj5eGCGziO+ztCvfYIA9CGs1uA4KLrED7SahvB0oXnt3KTFdRc857cWcpp2NmRKihLzVdgHlmT8zcYaec3zP1VdMoa2bmLHoaZtAwvMTcAG56FcET1p7f9v+uiU88ZeptEjTJfpAWlq34tpwZXNyIQi5IROocSHM+PKTgLAsAMP5klGascr8E0sEzREl7tg8hqEwXKym/sVKyT+HMptA3UZcrZZhm07/cmQ0pUVvbV9QDb0MzU3O2wF7vT3U7R7Fb1u0LxMjblPyEazTp8ldabVRJREwlsDc=" - - secure: "JIhuqaI0i+zvuqqXiQBHpuKr7AQ8jfk6Gbr8Qgiq4yJtdEWXZGxnAT9BmlbjkgT7ABXvLgxf2CIdUOMo1yYfBlxQL/y5+e89jaVpYF3tvNAzYQ1e12VzQRsd/jDb7qvm7tw3rDHEn3dSEot7Q6KbPcL6WzWJINVMCCmOgvq9gKHgE6Y5q5EgZ5rxiXyuO27ndzcbxaor4PIaiSzHO9+AJQ7p2zDLP+kG4nKVTBX0l9VoKiYFhIpIhpbigi3jyLDMDRiWpwTWZC6P8/RXfZg/lc5ADOuM2DM8oXPpZuqOa/g31LWQOSCuEnQ1G16vbLgipSPpgAc7jYWD5cywhG9dLkiKaZDh5x0meLM2RoAgz6eAnQfTTqJ68OM9o9yXjubEedsNpNRAr9/DXMd+fbh10W2vbvL5HCNB3lic3anehhR9le7PLuEKxg654wXt3KM2PZGVWbotIyBK0CvGzqGkppvwT23QdDDqSdkWuGQIhGQ0xBOdYkwebycxP5wwPUmObG+mymQ1Be2BXvmghttsiJdKlt4CVSYOJUMus6kU32G95hdTgKblsX4J1Of2i1nYsjyMKh3k945tqXwQrIsxOOQug0oIkz24zlLaOaQcorWtJ6Y1HPaZKpVIFUEF0y8Uq/O4oB2bOYC6WDUQfpj7nRG6xbi+BeBS84m1ttCEk4g=" - matrix: - - BUILD=deploy - - BUILD=doc - - BUILD=sonar - - BUILD=coveralls + - secure: "VezxV+VdmbmtrQYT8AZIyg41WBROxuxpumerkcubADF7V4wV6lwx9Rd2G6yAr0VuHCNUUhS4m+gPFIsuiQbAhyupiEkwhzUYqk1tF+LITlLLPegLypjiLmhJMwGUNuDSSsih1Icmg9FzrP4VyzgGn9pBjoG9QYj1civBZeGwg++e/XDYlHMXrpd/UEfMKVB71JwB0tle4fKJZSvblIqP62yvbBaKHx6A4+ZWzJV5Vps0DoIeNtKCNmNNloKZVHfjbsvqSjnMYUJzkOzyPkM822q41N/D+3IAufO16+jH/W0vAZeN0e4GXiN5W+CVkr2Gbh0FwkVQcI3bekaOIn45XLUMLKdf+JsWDPKz9RraHelR9YxL5GoJ7ntwvmucxw0p8EVyJ/xLk/pBCP8iHq0Jb8//js25XHgxzzAWI37MErPAAGgTKZAVdAN0mGXbe63tWmwaBlEbK8h2A8di6abW5x6YHTkTo2BRlHUSTU8dE3VqTnpSkne5n1SlEa4g1Bci3J45M0/pLmHV6yCxCM5BrVXS5ByaB61py/umSbpmdIBFV6TM1MaKK3lAucQrR+8To/vCbm8XqPyujJdOR+ENIuuDgEU/Yh5Hv5SAODekUYaCp4pjfGzFADHQWVNDxIOXrwBN4OfSiAvRc1x6HXndOmNI4QtOxheuCRFFthq8VZI=" matrix: fast_finish: true + include: + - name: "linux - mvn deploy" + os: linux + dist: trusty + sudo: false + env: BUILD=deploy + - name: "linux - build documentation" + os: linux + dist: trusty + sudo: false + env: BUILD=doc + - name: "linux - run sonar" + os: linux + dist: trusty + sudo: false + env: BUILD=sonar + - name: "linux - run coveralls" + os: linux + dist: trusty + sudo: false + env: BUILD=coveralls + - name: "macosx - mvn verify" + os: osx + env: BUILD=deploy before_install: + - wget https://github.com/sormuras/bach/raw/master/install-jdk.sh - bash .travis/setup-secrets.sh - bash .travis/configure-maven.sh -install: true + - rvm install 2.4.1 + - rvm use 2.4.1 +# Install OpenJDK 11 - see https://sormuras.github.io/blog/2018-03-20-jdk-matrix.html +install: + - . ./install-jdk.sh -F 11 -L GPL -W $HOME/jdk + - gem install bundler + - bundle install --with=release_notes_preprocessing --path=vendor/bundle before_script: true script: source .travis/build-$BUILD.sh after_success: true @@ -46,24 +65,33 @@ deploy: file_glob: true file: - pmd-dist/target/pmd-*.zip - - target/pmd-doc-*.zip + - docs/pmd-doc-*.zip skip_cleanup: true on: tags: true repo: pmd/pmd - condition: "${TRAVIS_SECURE_ENV_VARS} = true && ${BUILD} != sonar" + condition: "${TRAVIS_SECURE_ENV_VARS} = true && ${BUILD} != sonar && ${BUILD} != coveralls" after_deploy: bash .travis/release.sh notifications: + webhooks: + urls: + - https://webhooks.gitter.im/e/0e6dd310db54b96c2653 + - https://webhooks.gitter.im/e/65efc1ea65175ad23ab6 + on_success: always # options: [always|never|change] default: always + on_failure: always # options: [always|never|change] default: always + on_start: never # options: [always|never|change] default: always email: recipients: - andreas.dangel@adangel.org - on_success: always + on_success: change on_failure: always cache: directories: - "$HOME/.m2" - + - vendor/bundle + - "$HOME/jdk" + - "$HOME/.rvm/" # Secure Keys, that need to be set for snapshot builds # @@ -75,7 +103,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 00000000000..2a4f1345dc5 --- /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-coveralls.sh b/.travis/build-coveralls.sh index b2af3bb6108..b58629a5fa0 100755 --- a/.travis/build-coveralls.sh +++ b/.travis/build-coveralls.sh @@ -1,16 +1,28 @@ #!/bin/bash set -e +source .travis/logger.sh source .travis/common-functions.sh -VERSION=$(./mvnw -q -Dexec.executable="echo" -Dexec.args='${project.version}' --non-recursive org.codehaus.mojo:exec-maven-plugin:1.5.0:exec | tail -1) -echo "Building PMD Coveralls.io report ${VERSION} on branch ${TRAVIS_BRANCH}" +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 Coveralls.io report ${VERSION} on branch ${TRAVIS_BRANCH}" if ! travis_isPush; then echo "Not proceeding, since this is not a push!" exit 0 fi +( + # disable fast fail, exit immediately, in this subshell + set +e -travis_wait ./mvnw -q clean test jacoco:report coveralls:report -Pcoveralls + ./mvnw clean install -DskipTests=true -Dmaven.javadoc.skip=true -B -V + ./mvnw package jacoco:report coveralls:report -Pcoveralls -B -V + if [ $? -ne 0 ]; then + log_error "Error creating coveralls report" + else + log_success "New coveralls result: https://coveralls.io/github/pmd/pmd" + fi + true +) diff --git a/.travis/build-deploy.sh b/.travis/build-deploy.sh index 17e22e0c26c..c5b9f2547b2 100755 --- a/.travis/build-deploy.sh +++ b/.travis/build-deploy.sh @@ -1,57 +1,137 @@ #!/bin/bash set -e +source .travis/logger.sh source .travis/common-functions.sh function push_docs() { if git diff --quiet docs; then - echo "No changes in docs..." + log_info "No changes in docs..." else - echo "Found changes in docs..." + log_info "Found changes in docs..." if [ "$TRAVIS_BRANCH" == "master" ]; then git config user.name "Travis CI (pmd-bot)" git config user.email "andreas.dangel+pmd-bot@adangel.org" git add -A docs - git commit -m "Update documentation" + MSG="Update documentation + +TRAVIS_JOB_NUMBER=${TRAVIS_JOB_NUMBER} +TRAVIS_COMMIT_RANGE=${TRAVIS_COMMIT_RANGE}" + git commit -m "$MSG" git push git@github.com:pmd/pmd.git HEAD:master + log_success "Successfully pushed docs update" else - echo "Not on master branch, won't commit+push" + log_info "Not on master branch, won't commit+push" fi fi } +function upload_baseline() { + log_info "Generating and uploading baseline for pmdtester..." + cd .. + bundle config --local gemfile pmd/Gemfile + bundle exec 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) +log_info "Building PMD ${VERSION} on branch ${TRAVIS_BRANCH}" + +MVN_BUILD_FLAGS="-B -V" -VERSION=$(./mvnw -q -Dexec.executable="echo" -Dexec.args='${project.version}' --non-recursive org.codehaus.mojo:exec-maven-plugin:1.5.0:exec | tail -1) -echo "Building PMD ${VERSION} on branch ${TRAVIS_BRANCH}" +if travis_isOSX; then -if travis_isPullRequest; then + log_info "The build is running on OSX" + ./mvnw verify $MVN_BUILD_FLAGS - echo "This is a pull-request build" - ./mvnw verify -B -V +elif 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 - if [[ "$VERSION" != *-SNAPSHOT && "$TRAVIS_TAG" != "" ]]; then - echo "This is a release build for tag $TRAVIS_TAG" - ./mvnw deploy -Possrh,pmd-release -B -V - elif [[ "$VERSION" == *-SNAPSHOT ]]; then - echo "This is a snapshot build" - ./mvnw deploy -Possrh -B -V + if [[ "${VERSION}" != *-SNAPSHOT && "${TRAVIS_TAG}" != "" ]]; then + echo -e "\n\n" + log_info "This is a release build for tag ${TRAVIS_TAG}" + echo -e "\n\n" + ./mvnw deploy -Possrh,pmd-release $MVN_BUILD_FLAGS + elif [[ "${VERSION}" == *-SNAPSHOT ]]; then + log_info "This is a snapshot build" + ./mvnw deploy -Possrh $MVN_BUILD_FLAGS push_docs else # other build. Can happen during release: the commit with a non snapshot version is built, but not from the tag. - echo "This is some other build, probably during release: commit with a non-snapshot version on branch master..." - ./mvnw verify -Possrh -B -V + log_info "This is some other build, probably during release: commit with a non-snapshot version on branch master..." + ./mvnw verify $MVN_BUILD_FLAGS # we stop here - no need to execute further steps exit 0 fi - # Uploading pmd distribution to sourceforge - rsync -avh pmd-dist/target/pmd-*-${VERSION}.zip ${PMD_SF_USER}@web.sourceforge.net:/home/frs/project/pmd/pmd/${VERSION}/ - rsync -avh docs/pages/release_notes.md ${PMD_SF_USER}@web.sourceforge.net:/home/frs/project/pmd/pmd/${VERSION}/ReadMe.md + ( + # disable fast fail, exit immediately, in this subshell + set +e + + echo -e "\n\n" + log_info "Uploading pmd distribution to sourceforge..." + rsync -avh pmd-dist/target/pmd-*-${VERSION}.zip ${PMD_SF_USER}@web.sourceforge.net:/home/frs/project/pmd/pmd/${VERSION}/ + if [ $? -ne 0 ]; then + log_error "Error while uploading pmd-*-${VERSION}.zip to sourceforge!" + log_error "Please upload manually: https://sourceforge.net/projects/pmd/files/pmd/" + else + log_success "Successfully uploaded pmd-*-${VERSION}.zip to sourceforge" + fi + + ) + + ( # UPLOAD RELEASE NOTES TO SOURCEFORGE + + # This handler is called if any command fails + function release_notes_fail() { + log_error "Error while uploading release_notes.md as ReadMe.md to sourceforge!" + log_error "Please upload manually: https://sourceforge.net/projects/pmd/files/pmd/" + } + + # exit subshell after trap + set -e + trap release_notes_fail ERR + + RELEASE_NOTES_TMP=$(mktemp -t) + + .travis/render_release_notes.rb docs/pages/release_notes.md | tail -n +6 > "$RELEASE_NOTES_TMP" + + rsync -avh "$RELEASE_NOTES_TMP" ${PMD_SF_USER}@web.sourceforge.net:/home/frs/project/pmd/pmd/${VERSION}/ReadMe.md + + log_success "Successfully uploaded release_notes.md as ReadMe.md to sourceforge" + + ) + + + ( + # disable fast fail, exit immediately, in this subshell + set +e + + upload_baseline + + true + ) else - echo "This is neither a pull request nor a push!" + log_info "This is neither a pull request nor a push. Not executing any build." exit 1 fi diff --git a/.travis/build-doc.sh b/.travis/build-doc.sh index 5feb08fbad0..e4ec9c9a9b5 100755 --- a/.travis/build-doc.sh +++ b/.travis/build-doc.sh @@ -1,39 +1,91 @@ #!/bin/bash set -e +source .travis/logger.sh source .travis/common-functions.sh -VERSION=$(./mvnw -q -Dexec.executable="echo" -Dexec.args='${project.version}' --non-recursive org.codehaus.mojo:exec-maven-plugin:1.5.0:exec | tail -1) -echo "Building PMD Documentation ${VERSION} on branch ${TRAVIS_BRANCH}" +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 Documentation ${VERSION} on branch ${TRAVIS_BRANCH}" if ! travis_isPush; then - echo "Not building site, since this is not a push!" + log_info "Not building site, since this is not a push!" exit 0 fi pushd docs # run jekyll -echo -e "\n\nBuilding documentation using jekyll...\n\n" +echo -e "\n\n" +log_info "Building documentation using jekyll..." bundle install bundle exec jekyll build # create pmd-doc archive -echo -e "\n\nCreating pmd-doc archive...\n\n" +echo -e "\n\n" +log_info "Creating pmd-doc archive..." mv _site pmd-doc-${VERSION} zip -qr pmd-doc-${VERSION}.zip pmd-doc-${VERSION}/ +log_success "Successfully created pmd-doc-${VERSION}.zip:" +ls -lh pmd-doc-${VERSION}.zip +( + # disable fast fail, exit immediately, in this subshell + set +e -# Uploading pmd doc distribution to sourceforge -if [[ "$TRAVIS_TAG" != "" || "$VERSION" == *-SNAPSHOT ]]; then - echo -e "\n\nUploading pmd-doc archive to sourceforge...\n\n" - rsync -avh pmd-doc-${VERSION}.zip ${PMD_SF_USER}@web.sourceforge.net:/home/frs/project/pmd/pmd/${VERSION}/ -fi + if [[ "${TRAVIS_TAG}" != "" || "${VERSION}" == *-SNAPSHOT ]]; then + echo -e "\n\n" + log_info "Uploading pmd doc distribution to sourceforge..." + rsync -avh pmd-doc-${VERSION}.zip ${PMD_SF_USER}@web.sourceforge.net:/home/frs/project/pmd/pmd/${VERSION}/ + if [ $? -ne 0 ]; then + log_error "Couldn't upload pmd-doc-${VERSION}.zip!" + log_error "Please upload manually: https://sourceforge.net/projects/pmd/files/pmd/" + else + log_success "Successfully uploaded pmd-doc-${VERSION}.zip to sourceforge" + fi + fi + + # rsync site to pmd.sourceforge.net/snapshot + if [[ "${VERSION}" == *-SNAPSHOT && "${TRAVIS_BRANCH}" == "master" ]] && has_docs_change; then + echo -e "\n\n" + log_info "Uploading snapshot site to pmd.sourceforge.net/snapshot..." + travis_wait rsync -ah --stats --delete pmd-doc-${VERSION}/ ${PMD_SF_USER}@web.sourceforge.net:/home/project-web/pmd/htdocs/snapshot/ + if [ $? -ne 0 ]; then + log_error "Couldn't upload the snapshot documentation. It won't be current on http://pmd.sourceforge.net/snapshot/" + else + log_success "Successfully uploaded snapshot documentation: http://pmd.sourceforge.net/snapshot/" + fi + fi + + true +) + + + +# +# Push the generated site to gh-pages branch +# only for snapshot builds from branch master +# +if [[ "${VERSION}" == *-SNAPSHOT && "${TRAVIS_BRANCH}" == "master" ]] && has_docs_change; then + echo -e "\n\n" + log_info "Pushing the new site to github pages..." + git clone --branch gh-pages --depth 1 git@github.com:pmd/pmd.git pmd-gh-pages + # clear the files first + rm -rf pmd-gh-pages/* + # copy the new site + cp -a pmd-doc-${VERSION}/* pmd-gh-pages/ + ( + cd pmd-gh-pages + git config user.name "Travis CI (pmd-bot)" + git config user.email "andreas.dangel+pmd-bot@adangel.org" + git add -A + MSG="Update documentation -# rsync site to pmd.sourceforge.net/snapshot -if [[ "$VERSION" == *-SNAPSHOT && "$TRAVIS_BRANCH" == "master" ]]; then - echo -e "\n\nUploading snapshot site...\n\n" - travis_wait rsync -ah --stats --delete pmd-doc-${VERSION}/ ${PMD_SF_USER}@web.sourceforge.net:/home/project-web/pmd/htdocs/snapshot/ +TRAVIS_JOB_NUMBER=${TRAVIS_JOB_NUMBER} +TRAVIS_COMMIT_RANGE=${TRAVIS_COMMIT_RANGE}" + git commit -q -m "$MSG" + git push git@github.com:pmd/pmd.git HEAD:gh-pages + log_success "Successfully pushed site to https://pmd.github.io/pmd/" + ) fi popd diff --git a/.travis/build-sonar.sh b/.travis/build-sonar.sh index c852ec977b9..7e51aaf46df 100755 --- a/.travis/build-sonar.sh +++ b/.travis/build-sonar.sh @@ -1,17 +1,33 @@ #!/bin/bash set -e +source .travis/logger.sh source .travis/common-functions.sh -VERSION=$(./mvnw -q -Dexec.executable="echo" -Dexec.args='${project.version}' --non-recursive org.codehaus.mojo:exec-maven-plugin:1.5.0:exec | tail -1) -echo "Building PMD Sonar ${VERSION} on branch ${TRAVIS_BRANCH}" +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 Sonar ${VERSION} on branch ${TRAVIS_BRANCH}" if ! travis_isPush; then echo "Not updating sonar, since this is not a push!" exit 0 fi +( + # for sonar, we need to use java10, until sonarjava 5.8.0 is released (Sept. 2018) + JAVA_HOME=$(bash ./install-jdk.sh -F 10 -L GPL -W $HOME/jdk --emit-java-home | tail --lines 1) + export JAVA_HOME + export PATH=${JAVA_HOME}/bin:$PATH -# Run the build, truncate output due to Travis log limits -travis_wait ./mvnw clean org.jacoco:jacoco-maven-plugin:prepare-agent package sonar:sonar -Dsonar.host.url=https://sonarqube.com -Dsonar.login=${SONAR_TOKEN} -B -V -q + # disable fast fail, exit immediately, in this subshell + set +e + # Run the build + ./mvnw clean package sonar:sonar -Dsonar.login=${SONAR_TOKEN} -Psonar -B -V + + if [ $? -ne 0 ]; then + log_error "Error updating sonar..." + else + log_success "New sonar results: https://sonarcloud.io/dashboard?id=net.sourceforge.pmd%3Apmd" + fi + true +) diff --git a/.travis/common-functions.sh b/.travis/common-functions.sh index 6e9c9a8e26a..887d15b7596 100755 --- a/.travis/common-functions.sh +++ b/.travis/common-functions.sh @@ -2,18 +2,18 @@ set -e -echo "TRAVIS_REPO_SLUG: $TRAVIS_REPO_SLUG" -echo "TRAVIS_PULL_REQUEST_SLUG: $TRAVIS_PULL_REQUEST_SLUG" -echo "TRAVIS_PULL_REQUEST_BRANCH: $TRAVIS_PULL_REQUEST_BRANCH" -echo "TRAVIS_PULL_REQUEST: $TRAVIS_PULL_REQUEST" -echo "TRAVIS_SECURE_ENV_VARS: $TRAVIS_SECURE_ENV_VARS" -echo "TRAVIS_BRANCH: $TRAVIS_BRANCH" -echo "TRAVIS_TAG: $TRAVIS_TAG" -echo "TRAVIS_ALLOW_FAILURE: $TRAVIS_ALLOW_FAILURE" - +echo "TRAVIS_REPO_SLUG: ${TRAVIS_REPO_SLUG}" +echo "TRAVIS_PULL_REQUEST_SLUG: ${TRAVIS_PULL_REQUEST_SLUG}" +echo "TRAVIS_PULL_REQUEST_BRANCH: ${TRAVIS_PULL_REQUEST_BRANCH}" +echo "TRAVIS_PULL_REQUEST: ${TRAVIS_PULL_REQUEST}" +echo "TRAVIS_SECURE_ENV_VARS: ${TRAVIS_SECURE_ENV_VARS}" +echo "TRAVIS_BRANCH: ${TRAVIS_BRANCH}" +echo "TRAVIS_TAG: ${TRAVIS_TAG}" +echo "TRAVIS_ALLOW_FAILURE: ${TRAVIS_ALLOW_FAILURE}" +echo "TRAVIS_OS_NAME: ${TRAVIS_OS_NAME}" function travis_isPullRequest() { - if [ "$TRAVIS_PULL_REQUEST" != "false" ]; then + if [ "${TRAVIS_REPO_SLUG}" != "pmd/pmd" ] || [ "${TRAVIS_PULL_REQUEST}" != "false" ]; then return 0 else return 1 @@ -21,9 +21,27 @@ function travis_isPullRequest() { } function travis_isPush() { - if [ "$TRAVIS_PULL_REQUEST" = "false" ] && [ "${TRAVIS_SECURE_ENV_VARS}" = "true" ]; then + if [ "${TRAVIS_REPO_SLUG}" = "pmd/pmd" ] && [ "${TRAVIS_PULL_REQUEST}" = "false" ] && [ "${TRAVIS_SECURE_ENV_VARS}" = "true" ]; then + return 0 + else + return 1 + fi +} + +function travis_isOSX() { + if [[ $TRAVIS_OS_NAME == 'osx' ]]; then + return 0 + else + return 1 + fi +} + +function has_docs_change() { + if [[ $(git diff --name-only ${TRAVIS_COMMIT_RANGE}) = *"docs/"* ]]; then + log_info "Checking for changes in docs/ (TRAVIS_COMMIT_RANGE=${TRAVIS_COMMIT_RANGE}): changes found" return 0 else + log_info "Checking for changes in docs/ (TRAVIS_COMMIT_RANGE=${TRAVIS_COMMIT_RANGE}): no changes" return 1 fi } diff --git a/.travis/configure-maven.sh b/.travis/configure-maven.sh index a2b17f10541..0b8f10a17b2 100755 --- a/.travis/configure-maven.sh +++ b/.travis/configure-maven.sh @@ -1,7 +1,6 @@ #!/bin/bash set -e -echo "MAVEN_OPTS='-Xms1g -Xmx1g'" > $HOME/.mavenrc -mkdir -p $HOME/.m2 -cp .travis/travis-toolchains.xml $HOME/.m2/toolchains.xml -cp .travis/travis-settings.xml $HOME/.m2/settings.xml +echo "MAVEN_OPTS='-Xms1g -Xmx1g'" > ${HOME}/.mavenrc +mkdir -p ${HOME}/.m2 +cp .travis/travis-settings.xml ${HOME}/.m2/settings.xml diff --git a/.travis/logger.sh b/.travis/logger.sh new file mode 100644 index 00000000000..1c88df94135 --- /dev/null +++ b/.travis/logger.sh @@ -0,0 +1,18 @@ +#!/bin/bash + +COL_GREEN="\e[32m" +COL_RED="\e[31m" +COL_RESET="\e[0m" +COL_YELLOW="\e[33;1m" + +function log_error() { + echo -e "${COL_RED}[ERROR] $*${COL_RESET}" +} + +function log_info() { + echo -e "${COL_YELLOW}[INFO] $*${COL_RESET}" +} + +function log_success() { + echo -e "${COL_GREEN}[SUCCESS] $*${COL_RESET}" +} diff --git a/.travis/project-list.xml b/.travis/project-list.xml new file mode 100644 index 00000000000..5471d6f2e39 --- /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 bede0b29ea3..9d468e4611b 100755 --- a/.travis/release.sh +++ b/.travis/release.sh @@ -1,6 +1,8 @@ #!/bin/bash set -e +source .travis/logger.sh + echo "BUILD: $BUILD" RELEASE_VERSION=$(./mvnw -q -Dexec.executable="echo" -Dexec.args='${project.version}' --non-recursive org.codehaus.mojo:exec-maven-plugin:1.5.0:exec | tail -1) echo "RELEASE_VERSION: $RELEASE_VERSION" @@ -9,16 +11,30 @@ if [ "${BUILD}" = "deploy" ]; then # Deploy to ossrh has already been done with the usual build. See build-deploy.sh -# The site has been built before, the files have already been uploaded to sourceforge. -# Since this is a release, making the binary the new default file... -curl -H "Accept: application/json" -X PUT -d "default=windows&default=mac&default=linux&default=bsd&default=solaris&default=others" \ - -d "api_key=${PMD_SF_APIKEY}" https://sourceforge.net/projects/pmd/files/pmd/${RELEASE_VERSION}/pmd-bin-${RELEASE_VERSION}.zip +( + # disable fast fail, exit immediately, in this subshell + set +e + # The site has been built before, the files have already been uploaded to sourceforge. + # Since this is a release, making the binary the new default file... + log_info "Selecting pmd-bin-${RELEASE_VERSION} as default on sourceforge.net..." + curl -H "Accept: application/json" -X PUT -d "default=windows&default=mac&default=linux&default=bsd&default=solaris&default=others" \ + -d "api_key=${PMD_SF_APIKEY}" https://sourceforge.net/projects/pmd/files/pmd/${RELEASE_VERSION}/pmd-bin-${RELEASE_VERSION}.zip + if [ $? -ne 0 ]; then + log_error "Couldn't select latest binary as default on sourceforge.net" + else + log_info "pmd-bin-${RELEASE_VERSION} is now the default download option." + fi + true +) + +# 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}" @@ -29,26 +45,28 @@ cat > release-edit-request.json < .git/info/sparse-checkout git pull --depth=1 origin master - rsync -a ../target/pmd-doc-${RELEASE_VERSION}/ pmd-${RELEASE_VERSION}/ + log_info "Copying documentation from ../docs/pmd-doc-${RELEASE_VERSION}/ to pmd-${RELEASE_VERSION}/ ..." + rsync -ah --stats ../docs/pmd-doc-${RELEASE_VERSION}/ pmd-${RELEASE_VERSION}/ + git status + echo "Executing: git add pmd-${RELEASE_VERSION}" git add pmd-${RELEASE_VERSION} + echo "Executing: git commit..." git commit -q -m "Added pmd-${RELEASE_VERSION}" + + log_info "Copying pmd-${RELEASE_VERSION} to latest ..." git rm -qr latest cp -a pmd-${RELEASE_VERSION} latest + echo "Executing: git add latest" git add latest + echo "Executing: git commit..." git commit -q -m "Copying pmd-${RELEASE_VERSION} to latest" + + log_info "Generating sitemap.xml" + ../.travis/sitemap_generator.sh > sitemap.xml + echo "Executing: git add sitemap.xml" + git add sitemap.xml + echo "Executing: git commit..." + git commit -q -m "Generated sitemap.xml" + + echo "Executing: git push origin master" git push origin master ) + + +( + echo -e "\n\n" + + # disable fast fail, exit immediately, in this subshell + set +e + + log_info "Uploading the new release to pmd.sourceforge.net which serves as an archive..." + + 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..." + log_error "Please upload manually (PMD Version: ${RELEASE_VERSION})" + else + log_success "The documentation is now available under http://pmd.sourceforge.net/pmd-${RELEASE_VERSION}/" + fi + true +) + + fi diff --git a/.travis/render_release_notes.rb b/.travis/render_release_notes.rb new file mode 100755 index 00000000000..dc0eb0877dd --- /dev/null +++ b/.travis/render_release_notes.rb @@ -0,0 +1,43 @@ +#!/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/tocmaker_block" +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/.travis/setup-secrets.sh b/.travis/setup-secrets.sh index a52143ea77e..b8d4bb04e72 100755 --- a/.travis/setup-secrets.sh +++ b/.travis/setup-secrets.sh @@ -1,13 +1,18 @@ #!/bin/bash set -e -if [ "$TRAVIS_PULL_REQUEST" != "false" ] || [ "${TRAVIS_SECURE_ENV_VARS}" != "true" ]; then - echo "Not setting up secrets (TRAVIS_PULL_REQUEST=${TRAVIS_PULL_REQUEST} TRAVIS_SECURE_ENV_VARS=${TRAVIS_SECURE_ENV_VARS})." +if [ "${TRAVIS_REPO_SLUG}" != "pmd/pmd" ] || [ "${TRAVIS_PULL_REQUEST}" != "false" ] || [ "${TRAVIS_SECURE_ENV_VARS}" != "true" ] || [ "${encrypted_5630fbebf057_iv}" = "" ]; then + echo "Not setting up secrets:" + echo " TRAVIS_REPO_SLUG=${TRAVIS_REPO_SLUG}" + echo " TRAVIS_PULL_REQUEST=${TRAVIS_PULL_REQUEST}" + echo " TRAVIS_SECURE_ENV_VARS=${TRAVIS_SECURE_ENV_VARS}" + [ "${encrypted_5630fbebf057_iv}" = "" ] && echo " Variable encrypted_5630fbebf057_iv is not set" exit 0 fi +echo "Setting up secrets..." -openssl aes-256-cbc -K $encrypted_5630fbebf057_key -iv $encrypted_5630fbebf057_iv -in .travis/secrets.tar.enc -out .travis/secrets.tar -d +openssl aes-256-cbc -K ${encrypted_5630fbebf057_key} -iv ${encrypted_5630fbebf057_iv} -in .travis/secrets.tar.enc -out .travis/secrets.tar -d pushd .travis && tar xfv secrets.tar && popd mkdir -p "$HOME/.ssh" chmod 700 "$HOME/.ssh" diff --git a/.travis/sitemap_generator.sh b/.travis/sitemap_generator.sh new file mode 100755 index 00000000000..1d38e8ee966 --- /dev/null +++ b/.travis/sitemap_generator.sh @@ -0,0 +1,56 @@ +#!/bin/bash + +# Sitemap generator +# Assumes we have the latest version of the site under "latest" and "pmd-${RELEASE_VERSION}" +# https://www.sitemaps.org/protocol.html + +WEBSITE_PREFIX="https://pmd.github.io/" +DOC_PREFIX="latest/" # "pmd-${RELEASE_VERSION}/" +DATE=`date +%Y-%m-%d` +# Priority is relative to the website, can be chosen in {0.1, 0.2, ..., 1} +# Default priority is 0.5 +LATEST_PRIORITY=0.8 + + +# Writes to standard output + +cat << HEADER_END + + + + + ${WEBSITE_PREFIX}index.html + 1 + monthly + $DATE + + + + ${WEBSITE_PREFIX}${DOC_PREFIX}index.html + 0.9 + monthly + $DATE + + + + +HEADER_END + + +for page in ${DOC_PREFIX}pmd_*.html +do + + cat << ENTRY_END + + ${WEBSITE_PREFIX}$page + $LATEST_PRIORITY + monthly + $DATE + + +ENTRY_END + +done + +echo "" + diff --git a/.travis/travis-settings.xml b/.travis/travis-settings.xml index 20ca3d60c86..d3c3c6446cf 100644 --- a/.travis/travis-settings.xml +++ b/.travis/travis-settings.xml @@ -6,7 +6,9 @@ - + + org.sonarsource.scanner.maven + ossrh diff --git a/.travis/travis-toolchains.xml b/.travis/travis-toolchains.xml deleted file mode 100644 index caba9380c7a..00000000000 --- a/.travis/travis-toolchains.xml +++ /dev/null @@ -1,34 +0,0 @@ - - - - - - - - jdk - - 1.6 - - - /usr/lib/jvm/java-6-openjdk-amd64 - - - - jdk - - 1.7 - - - /usr/lib/jvm/java-7-openjdk-amd64 - - - - jdk - - 1.8 - - - /usr/lib/jvm/java-8-oracle - - - \ No newline at end of file diff --git a/BUILDING.md b/BUILDING.md index 04bb7f1df70..d02537a33db 100644 --- a/BUILDING.md +++ b/BUILDING.md @@ -1,11 +1,8 @@ # How to build PMD -PMD uses [Maven](https://maven.apache.org/). - -You'll need to have a `~/.m2/toolchains.xml` file setup -with jdk 1.7 and jdk 1.8 (for some features in pmd). -See [maven toolchains](https://maven.apache.org/guides/mini/guide-using-toolchains.html). -A example file can be found here: [example-toolchains.xml](https://github.com/pmd/pmd/blob/master/example-toolchains.xml). +PMD uses [Maven](https://maven.apache.org/) and requires at least Java 10 for building. +You can get Java 10 from [Oracle](http://www.oracle.com/technetwork/java/javase/downloads/index.html) +or from the [OpenJDK Project](http://jdk.java.net). PMD uses the [maven wrapper](https://github.com/takari/maven-wrapper), so you can simply build PMD as following: @@ -19,11 +16,14 @@ This will create the zip files in the directory `pmd-dist/target`: That's all ! -## How to build the documentation (maven site)? +**Note:** While Java 10 is required for building, running PMD only requires Java 7 (or Java 8 for Apex and the Designer). + +## How to build the documentation? -Building the maven site is done with the following commands: + cd docs + bundle install # once + bundle exec jekyll build - ./mvnw clean install -DskipTests=true - ./mvnw install site site:stage -Psite +You'll find the built site in the directory `_site/`. -You'll find the built site in the directory `target/staging/`. +For more info, see [README in docs directory](docs/README.md). diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 7de37c7b021..106a7144525 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -29,10 +29,11 @@ When filing a bug report, please provide as much information as possible, so tha ## Documentation -There is some documentation available under . Feel free to create a bug report if -documentation is missing, incomplete or outdated. +There is some documentation available under . Feel free to create a bug report if +documentation is missing, incomplete or outdated. See [Bug reports](#bug-reports). -The documentation is generated as a maven site, the source is available at: +The documentation is generated as a Jekyll site, the source is available at: . You can find build instructions there. +For more on contributing documentation check ## Questions diff --git a/Dangerfile b/Dangerfile new file mode 100644 index 00000000000..daba7c415ca --- /dev/null +++ b/Dangerfile @@ -0,0 +1,52 @@ +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'] + begin + runner = PmdTester::Runner.new(argv) + @new_errors, @removed_errors, @new_violations, @removed_violations = runner.run + upload_report + 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}" + end + 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("This changeset introduces #{@new_violations} new violations and #{@new_errors} new errors,\n" + + "removes #{@removed_violations} violations and #{@removed_errors} errors. [Full report](#{report_url.chomp}/diff/index.html)", 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 00000000000..2b9ab0d1216 --- /dev/null +++ b/Gemfile @@ -0,0 +1,14 @@ +source 'https://rubygems.org/' + +gem 'pmdtester', :git => 'https://github.com/pmd/pmd-regression-tester.git' +gem 'danger', '~> 5.6', '>= 5.6' + +# This group is only needed for rendering release notes +# this happens during release (.travis/release.sh and do-release.sh) +# but also during regular builds (.travis/build-deploy.sh) +group :release_notes_preprocessing do + gem 'liquid', '>=4.0.0' + gem 'safe_yaml', '>=1.0' +end + +# vim: syntax=ruby diff --git a/README.md b/README.md index f4bd567a195..2c40e966e10 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,10 @@ # PMD +[![Join the chat at https://gitter.im/pmd/pmd](https://badges.gitter.im/pmd/pmd.svg)](https://gitter.im/pmd/pmd?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [![Build Status](https://travis-ci.org/pmd/pmd.svg?branch=master)](https://travis-ci.org/pmd/pmd) [![Maven Central](https://maven-badges.herokuapp.com/maven-central/net.sourceforge.pmd/pmd/badge.svg)](https://maven-badges.herokuapp.com/maven-central/net.sourceforge.pmd/pmd) [![Coverage Status](https://coveralls.io/repos/github/pmd/pmd/badge.svg)](https://coveralls.io/github/pmd/pmd) - +[![Codacy Badge](https://api.codacy.com/project/badge/Grade/a674ee8642ed44c6ba7633626ee95967)](https://www.codacy.com/app/pmd/pmd?utm_source=github.com&utm_medium=referral&utm_content=pmd/pmd&utm_campaign=Badge_Grade) ## About **PMD** is a source code analyzer. It finds common programming flaws like unused variables, empty catch blocks, diff --git a/do-release.sh b/do-release.sh index 5023077a28d..a38a0556601 100755 --- a/do-release.sh +++ b/do-release.sh @@ -1,7 +1,8 @@ #!/bin/bash +set -e # Make sure, everything is English... -export LANG=C.UTF8 +export LANG=C.UTF-8 # verify the current directory if [ ! -f pom.xml -o ! -d ../pmd.github.io ]; then @@ -24,7 +25,7 @@ echo "Releasing PMD" echo "-------------------------------------------" # see also https://gist.github.com/pdunnavant/4743895 -CURRENT_VERSION=$(./mvnw -q -Dexec.executable="echo" -Dexec.args='${project.version}' --non-recursive org.codehaus.mojo:exec-maven-plugin:1.5.0:exec|tail -1) +CURRENT_VERSION=$(./mvnw -q -Dexec.executable="echo" -Dexec.args='${project.version}' --non-recursive org.codehaus.mojo:exec-maven-plugin:1.5.0:exec) RELEASE_VERSION=${CURRENT_VERSION%-SNAPSHOT} MAJOR=$(echo $RELEASE_VERSION | cut -d . -f 1) MINOR=$(echo $RELEASE_VERSION | cut -d . -f 2) @@ -40,6 +41,12 @@ fi DEVELOPMENT_VERSION="$MAJOR.$NEXT_MINOR.$NEXT_PATCH" DEVELOPMENT_VERSION="${DEVELOPMENT_VERSION}-SNAPSHOT" +# allow to override the next version, e.g. via "NEXT_VERSION=7.0.0 ./do-release.sh" +if [ "$NEXT_VERSION" != "" ]; then + DEVELOPMENT_VERSION="${NEXT_VERSION}-SNAPSHOT" +fi + + # http://stackoverflow.com/questions/1593051/how-to-programmatically-determine-the-current-checked-out-git-branch CURRENT_BRANCH=$(git symbolic-ref -q HEAD) CURRENT_BRANCH=${CURRENT_BRANCH##refs/heads/} @@ -60,22 +67,48 @@ export RELEASE_VERSION export DEVELOPMENT_VERSION export CURRENT_BRANCH -echo "* Update version/release info in **docs/pages/release_notes.md**." +RELEASE_RULESET="pmd-core/src/main/resources/rulesets/releases/${RELEASE_VERSION//\./}.xml" + +echo "* Update date info in **docs/_config.yml**." +echo " date: $(date -u +%d-%B-%Y)" echo -echo " ## $(date -u +%d-%B-%Y) - ${RELEASE_VERSION}" +echo "* Ensure all the new rules are listed in the proper file:" +echo " ${RELEASE_RULESET}" echo -echo "* Ensure all the new rules are listed in a the proper file:" -echo " pmd-core/src/main/resources/rulesets/releases/${RELEASE_VERSION}.xml file." +echo "* Update **docs/pages/next_major_development.md** with the API changes for" +echo " the new release based on the release notes" echo -echo "* Update **../pmd.github.io/index.html** to mention the new release" +echo "* Update **../pmd.github.io/_config.yml** to mention the new release" echo echo "Press enter to continue..." read + +# install bundles needed for rendering release notes +bundle install --with=release_notes_preprocessing --path vendor/bundle + +export RELEASE_NOTES_POST="_posts/$(date -u +%Y-%m-%d)-PMD-${RELEASE_VERSION}.md" +echo "Generating ../pmd.github.io/${RELEASE_NOTES_POST}..." +NEW_RELEASE_NOTES=$(bundle exec .travis/render_release_notes.rb docs/pages/release_notes.md | tail -n +6) +cat > ../pmd.github.io/${RELEASE_NOTES_POST} <" echo echo -echo "Submit news to SF on page. You can use" -echo "the following template:" -echo -cat <" -echo +echo "Check the milestone on github:" +echo "" +echo " --> move any open issues to the next milestone, close the current milestone" echo echo echo "Prepare Next development version:" -echo "* Move version/release info from **docs/pages/release_notes.md** to **docs/pages/release_notes_old.md**." -echo "* Update version/release info in **docs/pages/release_notes.md**." +echo "* Update version/date info in **docs/_config.yml**." +echo 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 +echo "$OLD_RELEASE_NOTES" >> docs/pages/release_notes_old.md + +# reset release notes template +cat > docs/pages/release_notes.md <" 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 "Body: !!Copy Changelog!!" +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 "$NEW_RELEASE_NOTES" +echo echo echo echo "------------------------------------------" @@ -168,4 +201,3 @@ echo "------------------------------------------" echo - diff --git a/docs/.gitignore b/docs/.gitignore index c28a8b0a595..6fccb64f3b7 100644 --- a/docs/.gitignore +++ b/docs/.gitignore @@ -3,3 +3,5 @@ _site/ .jekyll-metadata _pdf .idea/ +vendor/ +.bundle/ diff --git a/docs/404.md b/docs/404.md index a7b58c00214..2567a5cba76 100644 --- a/docs/404.md +++ b/docs/404.md @@ -3,4 +3,6 @@ title: "Page Not Found" search: exclude --- -Sorry, but the page you were trying to view does not exist. Try searching for it or looking at the URL to see if it looks correct. +Sorry, but the page you were trying to view does not exist. Check the URL for correctness, or try using keyword search using our search bar. + +[Back to the index](index.html) diff --git a/docs/Dockerfile b/docs/Dockerfile index b1fa52c4736..04f57ec3e06 100644 --- a/docs/Dockerfile +++ b/docs/Dockerfile @@ -1,4 +1,4 @@ -FROM ruby:2.1 +FROM ruby:2.4.2 MAINTAINER mrafayaleem@gmail.com RUN apt-get clean \ diff --git a/docs/Gemfile b/docs/Gemfile index 8e6b122ae25..3e24f04d7a7 100644 --- a/docs/Gemfile +++ b/docs/Gemfile @@ -1,4 +1,4 @@ -source "https://rubygems.org" +source 'https://rubygems.org' gem 'jekyll' gem 'github-pages' diff --git a/docs/Gemfile.lock b/docs/Gemfile.lock index 994b58d4358..beeebfcad2e 100644 --- a/docs/Gemfile.lock +++ b/docs/Gemfile.lock @@ -1,7 +1,7 @@ GEM remote: https://rubygems.org/ specs: - activesupport (4.2.8) + activesupport (4.2.10) i18n (~> 0.7) minitest (~> 5.1) thread_safe (~> 0.3, >= 0.3.4) @@ -11,186 +11,217 @@ GEM coffee-script (2.4.1) coffee-script-source execjs - coffee-script-source (1.12.2) + coffee-script-source (1.11.1) colorator (1.1.0) - ethon (0.10.1) + commonmarker (0.17.13) + ruby-enum (~> 0.5) + concurrent-ruby (1.1.3) + dnsruby (1.61.2) + addressable (~> 2.5) + em-websocket (0.5.1) + eventmachine (>= 0.12.9) + http_parser.rb (~> 0.6.0) + ethon (0.11.0) ffi (>= 1.3.0) + eventmachine (1.2.7) execjs (2.7.0) - faraday (0.13.1) + faraday (0.15.3) multipart-post (>= 1.2, < 3) - ffi (1.9.18) + ffi (1.9.25) forwardable-extended (2.6.0) gemoji (3.0.0) - github-pages (158) - activesupport (= 4.2.8) - github-pages-health-check (= 1.3.5) - jekyll (= 3.5.2) - jekyll-avatar (= 0.4.2) - jekyll-coffeescript (= 1.0.1) + github-pages (192) + activesupport (= 4.2.10) + github-pages-health-check (= 1.8.1) + jekyll (= 3.7.4) + jekyll-avatar (= 0.6.0) + jekyll-coffeescript (= 1.1.1) + jekyll-commonmark-ghpages (= 0.1.5) jekyll-default-layout (= 0.1.4) - jekyll-feed (= 0.9.2) - jekyll-gist (= 1.4.1) - jekyll-github-metadata (= 2.9.1) - jekyll-mentions (= 1.2.0) - jekyll-optional-front-matter (= 0.2.0) + jekyll-feed (= 0.10.0) + jekyll-gist (= 1.5.0) + jekyll-github-metadata (= 2.9.4) + jekyll-mentions (= 1.4.1) + jekyll-optional-front-matter (= 0.3.0) jekyll-paginate (= 1.1.0) - jekyll-readme-index (= 0.1.0) - jekyll-redirect-from (= 0.12.1) - jekyll-relative-links (= 0.4.1) - jekyll-sass-converter (= 1.5.0) - jekyll-seo-tag (= 2.3.0) - jekyll-sitemap (= 1.0.0) + jekyll-readme-index (= 0.2.0) + jekyll-redirect-from (= 0.14.0) + jekyll-relative-links (= 0.5.3) + jekyll-remote-theme (= 0.3.1) + jekyll-sass-converter (= 1.5.2) + jekyll-seo-tag (= 2.5.0) + jekyll-sitemap (= 1.2.0) jekyll-swiss (= 0.4.0) - jekyll-theme-architect (= 0.1.0) - jekyll-theme-cayman (= 0.1.0) - jekyll-theme-dinky (= 0.1.0) - jekyll-theme-hacker (= 0.1.0) - jekyll-theme-leap-day (= 0.1.0) - jekyll-theme-merlot (= 0.1.0) - jekyll-theme-midnight (= 0.1.0) - jekyll-theme-minimal (= 0.1.0) - jekyll-theme-modernist (= 0.1.0) - jekyll-theme-primer (= 0.5.2) - jekyll-theme-slate (= 0.1.0) - jekyll-theme-tactile (= 0.1.0) - jekyll-theme-time-machine (= 0.1.0) - jekyll-titles-from-headings (= 0.4.0) - jemoji (= 0.8.0) - kramdown (= 1.13.2) + jekyll-theme-architect (= 0.1.1) + jekyll-theme-cayman (= 0.1.1) + jekyll-theme-dinky (= 0.1.1) + jekyll-theme-hacker (= 0.1.1) + jekyll-theme-leap-day (= 0.1.1) + jekyll-theme-merlot (= 0.1.1) + jekyll-theme-midnight (= 0.1.1) + jekyll-theme-minimal (= 0.1.1) + jekyll-theme-modernist (= 0.1.1) + jekyll-theme-primer (= 0.5.3) + jekyll-theme-slate (= 0.1.1) + jekyll-theme-tactile (= 0.1.1) + jekyll-theme-time-machine (= 0.1.1) + jekyll-titles-from-headings (= 0.5.1) + jemoji (= 0.10.1) + kramdown (= 1.17.0) liquid (= 4.0.0) - listen (= 3.0.6) + listen (= 3.1.5) mercenary (~> 0.3) - minima (= 2.1.1) - rouge (= 1.11.1) + minima (= 2.5.0) + nokogiri (>= 1.8.2, < 2.0) + rouge (= 2.2.1) terminal-table (~> 1.4) - github-pages-health-check (1.3.5) + github-pages-health-check (1.8.1) addressable (~> 2.3) - net-dns (~> 0.8) + dnsruby (~> 1.60) octokit (~> 4.0) public_suffix (~> 2.0) - typhoeus (~> 0.7) - html-pipeline (2.7.0) + typhoeus (~> 1.3) + html-pipeline (2.9.0) activesupport (>= 2) nokogiri (>= 1.4) - i18n (0.8.6) - jekyll (3.5.2) + http_parser.rb (0.6.0) + i18n (0.9.5) + concurrent-ruby (~> 1.0) + jekyll (3.7.4) addressable (~> 2.4) colorator (~> 1.0) + em-websocket (~> 0.5) + i18n (~> 0.7) jekyll-sass-converter (~> 1.0) - jekyll-watch (~> 1.1) - kramdown (~> 1.3) + jekyll-watch (~> 2.0) + kramdown (~> 1.14) liquid (~> 4.0) mercenary (~> 0.3.3) pathutil (~> 0.9) - rouge (~> 1.7) + rouge (>= 1.7, < 4) safe_yaml (~> 1.0) - jekyll-avatar (0.4.2) + jekyll-avatar (0.6.0) jekyll (~> 3.0) - jekyll-coffeescript (1.0.1) + jekyll-coffeescript (1.1.1) coffee-script (~> 2.2) + coffee-script-source (~> 1.11.1) + jekyll-commonmark (1.2.0) + commonmarker (~> 0.14) + jekyll (>= 3.0, < 4.0) + jekyll-commonmark-ghpages (0.1.5) + commonmarker (~> 0.17.6) + jekyll-commonmark (~> 1) + rouge (~> 2) jekyll-default-layout (0.1.4) jekyll (~> 3.0) - jekyll-feed (0.9.2) + jekyll-feed (0.10.0) jekyll (~> 3.3) - jekyll-gist (1.4.1) + jekyll-gist (1.5.0) octokit (~> 4.2) - jekyll-github-metadata (2.9.1) + jekyll-github-metadata (2.9.4) jekyll (~> 3.1) octokit (~> 4.0, != 4.4.0) - jekyll-mentions (1.2.0) - activesupport (~> 4.0) + jekyll-mentions (1.4.1) html-pipeline (~> 2.3) jekyll (~> 3.0) - jekyll-optional-front-matter (0.2.0) + jekyll-optional-front-matter (0.3.0) jekyll (~> 3.0) jekyll-paginate (1.1.0) - jekyll-readme-index (0.1.0) + jekyll-readme-index (0.2.0) jekyll (~> 3.0) - jekyll-redirect-from (0.12.1) + jekyll-redirect-from (0.14.0) jekyll (~> 3.3) - jekyll-relative-links (0.4.1) + jekyll-relative-links (0.5.3) jekyll (~> 3.3) - jekyll-sass-converter (1.5.0) + jekyll-remote-theme (0.3.1) + jekyll (~> 3.5) + rubyzip (>= 1.2.1, < 3.0) + jekyll-sass-converter (1.5.2) sass (~> 3.4) - jekyll-seo-tag (2.3.0) + jekyll-seo-tag (2.5.0) jekyll (~> 3.3) - jekyll-sitemap (1.0.0) + jekyll-sitemap (1.2.0) jekyll (~> 3.3) jekyll-swiss (0.4.0) - jekyll-theme-architect (0.1.0) + jekyll-theme-architect (0.1.1) jekyll (~> 3.5) jekyll-seo-tag (~> 2.0) - jekyll-theme-cayman (0.1.0) + jekyll-theme-cayman (0.1.1) jekyll (~> 3.5) jekyll-seo-tag (~> 2.0) - jekyll-theme-dinky (0.1.0) + jekyll-theme-dinky (0.1.1) jekyll (~> 3.5) jekyll-seo-tag (~> 2.0) - jekyll-theme-hacker (0.1.0) + jekyll-theme-hacker (0.1.1) jekyll (~> 3.5) jekyll-seo-tag (~> 2.0) - jekyll-theme-leap-day (0.1.0) + jekyll-theme-leap-day (0.1.1) jekyll (~> 3.5) jekyll-seo-tag (~> 2.0) - jekyll-theme-merlot (0.1.0) + jekyll-theme-merlot (0.1.1) jekyll (~> 3.5) jekyll-seo-tag (~> 2.0) - jekyll-theme-midnight (0.1.0) + jekyll-theme-midnight (0.1.1) jekyll (~> 3.5) jekyll-seo-tag (~> 2.0) - jekyll-theme-minimal (0.1.0) + jekyll-theme-minimal (0.1.1) jekyll (~> 3.5) jekyll-seo-tag (~> 2.0) - jekyll-theme-modernist (0.1.0) + jekyll-theme-modernist (0.1.1) jekyll (~> 3.5) jekyll-seo-tag (~> 2.0) - jekyll-theme-primer (0.5.2) + jekyll-theme-primer (0.5.3) jekyll (~> 3.5) jekyll-github-metadata (~> 2.9) - jekyll-seo-tag (~> 2.2) - jekyll-theme-slate (0.1.0) + jekyll-seo-tag (~> 2.0) + jekyll-theme-slate (0.1.1) jekyll (~> 3.5) jekyll-seo-tag (~> 2.0) - jekyll-theme-tactile (0.1.0) + jekyll-theme-tactile (0.1.1) jekyll (~> 3.5) jekyll-seo-tag (~> 2.0) - jekyll-theme-time-machine (0.1.0) + jekyll-theme-time-machine (0.1.1) jekyll (~> 3.5) jekyll-seo-tag (~> 2.0) - jekyll-titles-from-headings (0.4.0) + jekyll-titles-from-headings (0.5.1) jekyll (~> 3.3) - jekyll-watch (1.5.0) - listen (~> 3.0, < 3.1) - jemoji (0.8.0) - activesupport (~> 4.0) + jekyll-watch (2.1.2) + listen (~> 3.0) + jemoji (0.10.1) gemoji (~> 3.0) html-pipeline (~> 2.2) - jekyll (>= 3.0) - kramdown (1.13.2) + jekyll (~> 3.0) + kramdown (1.17.0) liquid (4.0.0) - listen (3.0.6) - rb-fsevent (>= 0.9.3) - rb-inotify (>= 0.9.7) + listen (3.1.5) + rb-fsevent (~> 0.9, >= 0.9.4) + rb-inotify (~> 0.9, >= 0.9.7) + ruby_dep (~> 1.2) mercenary (0.3.6) - mini_portile2 (2.2.0) - minima (2.1.1) - jekyll (~> 3.3) - minitest (5.10.3) + mini_portile2 (2.3.0) + minima (2.5.0) + jekyll (~> 3.5) + jekyll-feed (~> 0.9) + jekyll-seo-tag (~> 2.1) + minitest (5.11.3) multipart-post (2.0.0) - net-dns (0.8.0) - nokogiri (1.8.0) - mini_portile2 (~> 2.2.0) - octokit (4.7.0) + nokogiri (1.8.5) + mini_portile2 (~> 2.3.0) + octokit (4.13.0) sawyer (~> 0.8.0, >= 0.5.3) - pathutil (0.14.0) + pathutil (0.16.2) forwardable-extended (~> 2.6) public_suffix (2.0.5) - rb-fsevent (0.10.2) + rb-fsevent (0.10.3) rb-inotify (0.9.10) ffi (>= 0.5.0, < 2) - rouge (1.11.1) + rouge (2.2.1) + ruby-enum (0.7.2) + i18n + ruby_dep (1.5.0) + rubyzip (1.2.2) safe_yaml (1.0.4) - sass (3.5.1) + sass (3.7.2) sass-listen (~> 4.0.0) sass-listen (4.0.0) rb-fsevent (~> 0.9, >= 0.9.4) @@ -201,11 +232,11 @@ GEM terminal-table (1.8.0) unicode-display_width (~> 1.1, >= 1.1.1) thread_safe (0.3.6) - typhoeus (0.8.0) - ethon (>= 0.8.0) - tzinfo (1.2.3) + typhoeus (1.3.1) + ethon (>= 0.9.0) + tzinfo (1.2.5) thread_safe (~> 0.1) - unicode-display_width (1.3.0) + unicode-display_width (1.4.0) PLATFORMS ruby @@ -215,4 +246,4 @@ DEPENDENCIES jekyll BUNDLED WITH - 1.15.1 + 1.16.2 diff --git a/docs/README.md b/docs/README.md index a183bdfa9a3..d0aef2914f7 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,5 +1,8 @@ -# PMD-New-Site -New Site For PMD Core Open Source Project +# PMD Documentation + +The documentation is available at: + +The documentation for the latest release is at: ## Site Theme @@ -7,6 +10,14 @@ This site was built using the tomjohnson1492/documentation-theme-jekyll theme A Jekyll-based theme designed for documentation and help systems. See the link for detailed instructions on setting up and configuring everything. http://idratherbewriting.com/documentation-theme-jekyll/ +## Building using Script + + bash build-docs.sh + +This will run bundler to fetch and potentially update the ruby gems. +And then it will execute jekyll and build a offline site. +Open the file `_site/index.html` with your browser to see the site. + ## Building using Bundler bundle install # once @@ -14,6 +25,9 @@ A Jekyll-based theme designed for documentation and help systems. See the link f Go to: http://localhost:4005/ +This variant is useful to get constant updates: When you modify a file, jekyll will automatically rebuild +the site, so you just need to hit Refresh in the browser to see the update. + ## Building using Docker docker build --no-cache -t pmd-doc . # once diff --git a/docs/_config.yml b/docs/_config.yml index 2064c7c9d4e..7236c2817f9 100644 --- a/docs/_config.yml +++ b/docs/_config.yml @@ -1,8 +1,9 @@ repository: pmd/pmd pmd: - version: 6.0.0 - date: to-be-defined + version: 6.10.0 + date: ??-????-2018 + release_type: minor output: web # this property is useful for conditional filtering of content that is separate from the PDF. @@ -28,10 +29,16 @@ host: 127.0.0.1 port: 4005 # the port where the preview is rendered. You can leave this as is unless you have other Jekyll builds using this same port that might cause conflicts. in that case, use another port such as 4006. +# these are the files and directories that jekyll will exclude from the build exclude: - .idea/ - .gitignore -# these are the files and directories that jekyll will exclude from the build + - vendor/ + - Gemfile + - Gemfile.lock + - README.md + - Dockerfile + - build-docs.sh feedback_subject_line: PMD Source Code Analyzer @@ -103,5 +110,5 @@ description: "Intended as a documentation theme based on Jekyll for technical wr # the description is used in the feed.xml file # needed for sitemap.xml file only -url: http://pmd.sourceforge.net +url: https://pmd.github.io/pmd baseurl: "" diff --git a/docs/_data/sidebars/pmd_sidebar.yml b/docs/_data/sidebars/pmd_sidebar.yml index c3099fd0ea0..593dc5e9a19 100644 --- a/docs/_data/sidebars/pmd_sidebar.yml +++ b/docs/_data/sidebars/pmd_sidebar.yml @@ -1,124 +1,300 @@ -# This is your sidebar TOC. The sidebar code loops through sections here and -# provides the appropriate formatting. - entries: - title: sidebar product: PMD - version: "!PMD_VERSION!" + version: '!PMD_VERSION!' folders: - - - title: + - title: null output: pdf type: frontmatter folderitems: - - title: + - title: null url: /titlepage output: pdf type: frontmatter - - title: + - title: null url: /tocpage output: pdf type: frontmatter - - - title: User Documentation + - title: About output: web, pdf folderitems: - - title: Introduction + - title: Home url: /index.html output: web, pdf type: homepage - - title: "Release Notes" + - title: Release notes url: /pmd_release_notes.html output: web, pdf - - title: Getting Started - url: /pmd_userdocs_getting_started.html + - title: PMD 7.0.0 development + url: /pmd_next_major_development.html + output: web, pdf + - title: Getting help + url: /pmd_about_help.html + output: web, pdf + - title: User Documentation + output: web, pdf + folderitems: + - title: Installation and basic CLI usage + url: /pmd_userdocs_installation.html + output: web, pdf + - title: Making rulesets + url: /pmd_userdocs_making_rulesets.html output: web, pdf - - title: Understanding Rulesets - url: /pmd_userdocs_understanding_rulesets.html + - title: Configuring rules + url: /pmd_userdocs_configuring_rules.html output: web, pdf - - title: Best Pratices + - title: Best practices url: /pmd_userdocs_best_practices.html output: web, pdf - - title: Copy-Paste Detection + - title: Suppressing warnings + url: /pmd_userdocs_suppressing_warnings.html + output: web, pdf + - title: Incremental analysis + url: /pmd_userdocs_incremental_analysis.html + output: web, pdf + - title: PMD CLI reference + url: /pmd_userdocs_cli_reference.html + output: web, pdf + - title: null + output: web, pdf + subfolders: + - title: Extending PMD + output: web, pdf + subfolderitems: + - title: Writing a rule + url: /pmd_userdocs_extending_writing_pmd_rules.html + output: web, pdf + - title: Writing XPath rules + url: /pmd_userdocs_extending_writing_xpath_rules.html + output: web, pdf + - title: Defining rule properties + url: /pmd_userdocs_extending_defining_properties.html + output: web, pdf + - title: Using and defining code metrics + url: /pmd_userdocs_extending_metrics_howto.html + output: web, pdf + - title: Rule guidelines + url: /pmd_userdocs_extending_rule_guidelines.html + output: web, pdf + - title: Testing your rules + url: /pmd_userdocs_extending_testing.html + output: web, pdf + - title: Copy-paste detection url: /pmd_userdocs_cpd.html output: web, pdf - - title: Suppressing - url: /pmd_userdocs_suppressing.html + - title: null output: web, pdf subfolders: - title: Tools / Integrations output: web, pdf subfolderitems: - - title: Maven PMD Plugin + - title: Maven PMD plugin output: web, pdf url: /pmd_userdocs_tools_maven.html - title: Ant output: web, pdf url: /pmd_userdocs_tools_ant.html + - title: CI integrations + output: web, pdf + url: /pmd_userdocs_tools_ci.html - title: Other Tools / Integrations output: web, pdf url: /pmd_userdocs_tools.html - - - title: FAQ - url: /pmd_userdocs_faq.html - output: web, pdf - - title: Getting Help - url: /pmd_userdocs_help.html - output: web, pdf - - title: PMD in the press - url: /pmd_userdocs_news.html - output: web, pdf - - title: Products/books related to PMD - url: /pmd_userdocs_products.html - output: web, pdf - - title: Similar Projects - url: /pmd_userdocs_similarprojects.html - output: web, pdf - - title: License - url: /license.html - output: web, pdf - - title: Credits - url: /pmd_userdocs_credits.html - output: web, pdf - - title: What does 'PMD' mean? - url: /pmd_userdocs_meaning.html - output: web, pdf - - title: Old Release Notes - url: /pmd_release_notes_old.html - output: web, pdf - title: Rule Reference output: web, pdf folderitems: - - title: Apex Rules - url: /pmd_rules_apex.html + - title: null output: web, pdf - - title: Java Rules - url: /pmd_rules_java.html + subfolders: + - title: Apex Rules + output: web, pdf + subfolderitems: + - title: Index + output: web, pdf + url: /pmd_rules_apex.html + - title: Best Practices + output: web, pdf + url: /pmd_rules_apex_bestpractices.html + - title: Code Style + output: web, pdf + url: /pmd_rules_apex_codestyle.html + - title: Design + output: web, pdf + url: /pmd_rules_apex_design.html + - title: Error Prone + output: web, pdf + url: /pmd_rules_apex_errorprone.html + - title: Performance + output: web, pdf + url: /pmd_rules_apex_performance.html + - title: Security + output: web, pdf + url: /pmd_rules_apex_security.html + - title: null output: web, pdf - - title: JavaScript Rules - url: /pmd_rules_ecmascript.html + subfolders: + - title: Ecmascript Rules + output: web, pdf + subfolderitems: + - title: Index + output: web, pdf + url: /pmd_rules_ecmascript.html + - title: Best Practices + output: web, pdf + url: /pmd_rules_ecmascript_bestpractices.html + - title: Code Style + output: web, pdf + url: /pmd_rules_ecmascript_codestyle.html + - title: Error Prone + output: web, pdf + url: /pmd_rules_ecmascript_errorprone.html + - title: null output: web, pdf - - title: JSP Rules - url: /pmd_rules_jsp.html + subfolders: + - title: Java Rules + output: web, pdf + subfolderitems: + - title: Index + output: web, pdf + url: /pmd_rules_java.html + - title: Best Practices + output: web, pdf + url: /pmd_rules_java_bestpractices.html + - title: Code Style + output: web, pdf + url: /pmd_rules_java_codestyle.html + - title: Design + output: web, pdf + url: /pmd_rules_java_design.html + - title: Documentation + output: web, pdf + url: /pmd_rules_java_documentation.html + - title: Error Prone + output: web, pdf + url: /pmd_rules_java_errorprone.html + - title: Multithreading + output: web, pdf + url: /pmd_rules_java_multithreading.html + - title: Performance + output: web, pdf + url: /pmd_rules_java_performance.html + - title: Security + output: web, pdf + url: /pmd_rules_java_security.html + - title: null output: web, pdf - - title: PLSQL Rules - url: /pmd_rules_plsql.html + subfolders: + - title: Java Server Pages Rules + output: web, pdf + subfolderitems: + - title: Index + output: web, pdf + url: /pmd_rules_jsp.html + - title: Best Practices + output: web, pdf + url: /pmd_rules_jsp_bestpractices.html + - title: Code Style + output: web, pdf + url: /pmd_rules_jsp_codestyle.html + - title: Design + output: web, pdf + url: /pmd_rules_jsp_design.html + - title: Error Prone + output: web, pdf + url: /pmd_rules_jsp_errorprone.html + - title: Security + output: web, pdf + url: /pmd_rules_jsp_security.html + - title: null output: web, pdf - - title: Maven POM Rules - url: /pmd_rules_pom.html + subfolders: + - title: Maven POM Rules + output: web, pdf + subfolderitems: + - title: Index + output: web, pdf + url: /pmd_rules_pom.html + - title: Error Prone + output: web, pdf + url: /pmd_rules_pom_errorprone.html + - title: null output: web, pdf - - title: VisualForce Rules - url: /pmd_rules_vf.html + subfolders: + - title: PLSQL Rules + output: web, pdf + subfolderitems: + - title: Index + output: web, pdf + url: /pmd_rules_plsql.html + - title: Best Practices + output: web, pdf + url: /pmd_rules_plsql_bestpractices.html + - title: Code Style + output: web, pdf + url: /pmd_rules_plsql_codestyle.html + - title: Design + output: web, pdf + url: /pmd_rules_plsql_design.html + - title: Error Prone + output: web, pdf + url: /pmd_rules_plsql_errorprone.html + - title: null output: web, pdf - - title: Apache Velocity Rules - url: /pmd_rules_vm.html + subfolders: + - title: Salesforce VisualForce Rules + output: web, pdf + subfolderitems: + - title: Index + output: web, pdf + url: /pmd_rules_vf.html + - title: Security + output: web, pdf + url: /pmd_rules_vf_security.html + - title: null output: web, pdf - - title: XML Rules - url: /pmd_rules_xml.html + subfolders: + - title: VM Rules + output: web, pdf + subfolderitems: + - title: Index + output: web, pdf + url: /pmd_rules_vm.html + - title: Best Practices + output: web, pdf + url: /pmd_rules_vm_bestpractices.html + - title: Design + output: web, pdf + url: /pmd_rules_vm_design.html + - title: Error Prone + output: web, pdf + url: /pmd_rules_vm_errorprone.html + - title: null output: web, pdf - - title: XSL Rules - url: /pmd_rules_xsl.html + subfolders: + - title: XML Rules + output: web, pdf + subfolderitems: + - title: Index + output: web, pdf + url: /pmd_rules_xml.html + - title: Error Prone + output: web, pdf + url: /pmd_rules_xml_errorprone.html + - title: null output: web, pdf + subfolders: + - title: XSL Rules + output: web, pdf + subfolderitems: + - title: Index + output: web, pdf + url: /pmd_rules_xsl.html + - title: Code Style + output: web, pdf + url: /pmd_rules_xsl_codestyle.html + - title: Performance + output: web, pdf + url: /pmd_rules_xsl_performance.html - title: Language Specific Documentation output: web, pdf folderitems: @@ -134,63 +310,85 @@ entries: - title: Developer Documentation output: web, pdf folderitems: - - title: Developer Resources + - title: Developer resources url: /pmd_devdocs_development.html output: web, pdf - - title: Writing Documentation - url: /pmd_devdocs_writing_documentation.html - output: web, pdf - - title: Code Style - url: /pmd_devdocs_codestyle.html - output: web, pdf - - title: Setting up your IDE - url: /pmd_devdocs_setting_up_ide.html - output: web, pdf - - title: Pull Requests - url: /pmd_devdocs_pull_requests.html - output: web, pdf - title: Building PMD from source url: /pmd_devdocs_building.html output: web, pdf - - title: Testing / Test Framework - url: /pmd_devdocs_testing.html + - title: Contributing + external_url: https://github.com/pmd/pmd/blob/master/CONTRIBUTING.md output: web, pdf - - title: Releasing - url: /pmd_devdocs_releasing.html - output: web, pdf - - title: Architecture - url: /pmd_devdocs_architecture.html + - title: Writing documentation + url: /pmd_devdocs_writing_documentation.html output: web, pdf - title: Roadmap url: /pmd_devdocs_roadmap.html output: web, pdf - - title: How PMD Works + - title: How PMD works url: /pmd_devdocs_how_pmd_works.html output: web, pdf - - title: Writing a Rule - url: /pmd_devdocs_writing_pmd_rules.html + - title: Pmdtester + url: /pmd_devdocs_pmdtester.html output: web, pdf - - title: Writing XPath Rules - url: /pmd_devdocs_writing_xpath_rules.html + - title: null output: web, pdf - - title: Using code metrics in custom rules - url: /pmd_devdocs_metrics_howto.html - output: web, pdf - - title: Making Rulesets - url: /pmd_devdocs_making_rulesets.html + subfolders: + - title: Major contributions + output: web, pdf + subfolderitems: + - title: Adding a new language + url: /pmd_devdocs_major_adding_new_language.html + output: web, pdf + - title: Adding a new CPD language + url: /pmd_devdocs_major_adding_new_cpd_language.html + output: web, pdf + - title: Adding metrics support to a language + url: /pmd_devdocs_major_adding_new_metrics_framework.html + output: web, pdf + - title: Project documentation + output: web, pdf + folderitems: + - title: null output: web, pdf - - title: Rule Guidelines - url: /pmd_devdocs_rule_guidelines.html + subfolders: + - title: Trivia about PMD + output: web, pdf + subfolderitems: + - title: PMD in the press + url: /pmd_projectdocs_trivia_news.html + output: web, pdf + - title: Products & books related to PMD + url: /pmd_projectdocs_trivia_products.html + output: web, pdf + - title: Similar projects + url: /pmd_projectdocs_trivia_similarprojects.html + output: web, pdf + - title: What does 'PMD' mean? + url: /pmd_projectdocs_trivia_meaning.html + output: web, pdf + - title: FAQ + url: /pmd_projectdocs_faq.html output: web, pdf - - title: Info for Developers - url: /pmd_devdocs_info_for_developers.html + - title: License + url: /license.html output: web, pdf - - title: Adding a New Language - url: /pmd_devdocs_adding_new_language.html + - title: Credits + url: /pmd_projectdocs_credits.html output: web, pdf - - title: Adding a New CPD Language - url: /pmd_devdocs_adding_new_cpd_language.html + - title: Old release notes + url: /pmd_release_notes_old.html output: web, pdf - - title: Adding metrics support to a language - url: /pmd_devdocs_adding_metrics_support_to_language.html + - title: null output: web, pdf + subfolders: + - title: Project management + output: web, pdf + subfolderitems: + - title: Release process + url: /pmd_projectdocs_committers_releasing.html + output: web, pdf + - title: Merging pull requests + url: /pmd_projectdocs_committers_merging_pull_requests.html + output: web, pdf + diff --git a/docs/_data/tags.yml b/docs/_data/tags.yml index ebc199df73d..0fe60c394a7 100644 --- a/docs/_data/tags.yml +++ b/docs/_data/tags.yml @@ -1,15 +1,12 @@ allowed-tags: - - getting_started + - getting_started # Getting started series + - rule_references # For the generated rule indices - release_notes - - content_types - - navigation - - formatting - - publishing - - single_sourcing - - special_layouts - - collaboration - news - troubleshooting - - mobile - - customizing - - languages \ No newline at end of file + - userdocs # Pages about usage, making rulesets, configuring rules, + - extending # Pages about writing rules, metrics, properties, etc. + - metrics # About using metrics, and metrics indices + - tools # About tools and integrations, Maven, gradle, etc. + - devdocs # About PMD internals, contributing, building, projects + - languages diff --git a/docs/_includes/custom/cli_option_row.html b/docs/_includes/custom/cli_option_row.html new file mode 100644 index 00000000000..bad3a99dd28 --- /dev/null +++ b/docs/_includes/custom/cli_option_row.html @@ -0,0 +1,21 @@ + + + + + + + + + + +{% assign arg = {{ include.option_arg | prepend: " <" | append: ">" | escape_once | keep_if: include.option_arg }} %} +{% capture required_label %}{% if include.required %}Required {% endif %}{% endcapture %} + +{% assign option_id = include.options | split: ',' | first | replace_first: '-', '' | prepend: {{ table_id }} | url_encode %} + + + {{ include.options | split: ',' | mappend: arg | join: "
" }}
+ {{ required_label }}{{ include.description | render_markdown }} + {{ include.default }} + {{ include.languages }} + diff --git a/docs/_includes/custom/knowledge_base_topic.html b/docs/_includes/custom/knowledge_base_topic.html new file mode 100644 index 00000000000..030d14e8f3c --- /dev/null +++ b/docs/_includes/custom/knowledge_base_topic.html @@ -0,0 +1,22 @@ + + + + + + + +
+
+
+ + + + +
+
+

{{ include.title }}

+

{{ include.description }}

+ +
+
+
diff --git a/docs/_includes/custom/panel_scroll.html b/docs/_includes/custom/panel_scroll.html new file mode 100644 index 00000000000..8dc89de9819 --- /dev/null +++ b/docs/_includes/custom/panel_scroll.html @@ -0,0 +1,27 @@ + + + \ No newline at end of file diff --git a/docs/_includes/custom/shuffle_panel.html b/docs/_includes/custom/shuffle_panel.html new file mode 100644 index 00000000000..ffe3b4adb0d --- /dev/null +++ b/docs/_includes/custom/shuffle_panel.html @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + +{% assign titlemaker = include.titlemaker | default: "page.title" %} +{% assign include_tags = include.tags | split: "," %} +{% assign exclude_tags = include.except_tags | split: "," %} +{% assign sorted_pages = site.pages | sort_using: site.data.sidebars.pmd_sidebar %} + +{% assign panel_id = include.title | downcase | strip | replace: ' ', '-' | prepend: "shuffle-panel-" %} + +
+
+ + + {% if include.image != "" and include.image != nil %} +
+ + + + +
+ {% endif %} + +
{{ include.title }}
+
+ +

+ {{ include.description }} +

+
    + {% for page in sorted_pages %} + {% capture included %}{{ page.tags | intersect: include_tags | equals: include_tags }}{% endcapture %} + {% capture excluded %}{{ page.tags | intersect: exclude_tags | empty }}{% endcapture %} + + {% if included contains "true" and excluded contains "true" %} +
  • {% eval titlemaker %}
  • + {% endif %} + {% endfor %} +
+
+
+
diff --git a/docs/_includes/head.html b/docs/_includes/head.html index f5ca45688a5..8e115b3fa0b 100644 --- a/docs/_includes/head.html +++ b/docs/_includes/head.html @@ -13,6 +13,7 @@ + diff --git a/docs/_includes/initialize_shuffle.html b/docs/_includes/initialize_shuffle.html index 9a0f048dd11..73354870b7e 100644 --- a/docs/_includes/initialize_shuffle.html +++ b/docs/_includes/initialize_shuffle.html @@ -30,21 +30,30 @@ // Set up button clicks setupFilters = function() { - var $btns = $filterOptions.children(); + var $btns = $filterOptions.children().add('.topical-filter'); $btns.on('click', function() { var $this = $(this), isActive = $this.hasClass( 'active' ), + isMainTopicButton = $this.hasClass('topical-filter'), group = isActive ? 'all' : $this.data('group'); // Hide current label, show current label in title if ( !isActive ) { - $('.filter-options .active').removeClass('active'); + $('.filter-options .active').add('.topical-filter.active').removeClass('active'); } $this.toggleClass('active'); // Filter elements $grid.shuffle( 'shuffle', group ); + + // scroll to the grid + if ( isMainTopicButton && !isActive ) { + $('html, body').animate({ + scrollTop: $("#grid-rule").offset().top + }, 500); + } + }); $btns = null; @@ -98,9 +107,9 @@ - + - diff --git a/docs/_layouts/default.html b/docs/_layouts/default.html index eeeb4d56150..84e0c2cefe4 100644 --- a/docs/_layouts/default.html +++ b/docs/_layouts/default.html @@ -73,6 +73,9 @@ + + +
{% include topnav.html %}
@@ -81,22 +84,22 @@
{% assign content_col_size = "col-md-12" %} {% unless page.hide_sidebar %} - -
- {% include sidebar.html %} -
- {% assign content_col_size = "col-md-9" %} + +
+ {% include sidebar.html %} +
+ {% assign content_col_size = "col-md-9" %} {% endunless %}
{{content}}
- -
- +
- + +
+ {% if site.google_analytics %} {% include google_analytics.html %} diff --git a/docs/_plugins/custom_filters.rb b/docs/_plugins/custom_filters.rb new file mode 100644 index 00000000000..42ca4b0fefa --- /dev/null +++ b/docs/_plugins/custom_filters.rb @@ -0,0 +1,121 @@ +module CustomFilters + + # set intersection + def intersect(xs, ys) + if !xs || !ys + [] + else + Array(xs) & Array(ys) + end + end + + # set equality between two arrays + def equals(xs, ys) + a = Array(xs) + b = Array(ys) + + ((a | b) - (a & b)).empty? + end + + def empty(xs) + Array(xs).empty? + end + + + # print & pass + def pp(any) + puts "#{any}" + any + end + + # sorts an array using the order defined by the given sidebar + def sort_using(xs, sidebar) + # caching is possible but doesn't improve significantly the build times + + rank_lookup = rank_lookup_from_sidebar(sidebar) + + xs.sort {|x, y| + # The default rank is very high so that pages that don't appear in the sidebar are put at the end + rx = rank_lookup[x.url] || 10000 + ry = rank_lookup[y.url] || 10000 + + rx <=> ry + } + + end + + def regex_replace(str, regex, subst) + if str && regex + str.gsub(Regexp::new(regex), subst || "") + end + end + + def regex_split(str, regex = nil) + if str + str.split(regex && Regexp::new(regex)) + end + end + + # Takes an array of strings and maps every element x to {{ x | append: suffix }} + def mappend(xs, suffix) + Array(xs).map {|x| "#{x}#{suffix}" } + end + + # Returns the initial argument only if the second argument is truthy + def keep_if(any, test) + if test + any + 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 + res = res.gsub(/`(.*?)`/, '\1') + res = res.gsub(/\*\*(.*?)\*\*/, '\1') + res = res.gsub(/\*(.*?)\*/, '\1') + res.gsub(/\[(.*?)\]\((.*?)\)/, '\1') + end + end + + + private + + def flatten_rec(seq) + seq.map {|h| + if (subs = h["folderitems"] || h["subfolderitems"] || h["subfolders"]) + flatten_rec(subs).flatten + elsif (page = h["url"]) + page + end + }.flatten + end + + def rank_lookup_from_sidebar(sidebar) + + folders = sidebar["entries"][0]["folders"] + + ordered = flatten_rec(folders).select {|url| + url && url.end_with?(".html") + } + + Hash[ordered.zip (0...ordered.size)] + + end + +end + +Liquid::Template.register_filter(CustomFilters) diff --git a/docs/_plugins/eval_tag.rb b/docs/_plugins/eval_tag.rb new file mode 100644 index 00000000000..6c2318329ae --- /dev/null +++ b/docs/_plugins/eval_tag.rb @@ -0,0 +1,29 @@ + +# This tag takes a variable name as an input, and evaluates its value twice (dereferences it once) +# E.g. if the symbol table is E = {"foo" => "bar", "bar" => "baz"}, +# then {% eval foo %} ~> E[E["foo"]] ~> E["bar"] ~> "baz" + +class EvalTag < Liquid::Tag + + def initialize(tag_name, name_expression, tokens) + super + @name_expression = name_expression.strip + end + + + # Lookup allows access to the page/post variables through the tag context + def lookup(context, name) + lookup = context + name.strip.split(".").each {|value| + lookup = lookup[value] + } + lookup + end + + def render(context) + lookup(context, lookup(context, @name_expression)) + end +end + + +Liquid::Template.register_tag('eval', EvalTag) diff --git a/docs/_plugins/rule_tag.rb b/docs/_plugins/rule_tag.rb new file mode 100644 index 00000000000..0e81e64bdd6 --- /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/_plugins/tocmaker_block.rb b/docs/_plugins/tocmaker_block.rb new file mode 100644 index 00000000000..990d02e7a89 --- /dev/null +++ b/docs/_plugins/tocmaker_block.rb @@ -0,0 +1,60 @@ +# Generates a table of contents based on markdown headers in the body +# +# The block has 2 optional args: +# * A variable name. If provided, the toc will only be generated if the var is true +# * An integer, describing the maximum depth at which headers are added to the toc + +class TocMakerBlock < Liquid::Block + + def initialize(tag_name, arg, tokens) + super + + condition, depth = arg.split + + @max_depth = depth.to_s.empty? ? 100 : depth.to_i + @condition_var = condition.strip unless condition.to_s.empty? + + @body = tokens + end + + def to_internal_link(header) + url = header.downcase.gsub(/\s+/, "-") + + "[#{header}](##{url})" + end + + def render(context) + + contents = @body.render(context) + + if @condition_var && !context[@condition_var] + # If the condition is false, the toc is not generated + return contents + end + + headers = contents.lines.map {|l| + if /^(#+)\s+(\S.*)$/ =~ l + [$1.length, $2] + end + }.compact + + min_indent = headers.map {|t| t[0]}.min + + headers = headers.map {|t| + actual_depth = t[0] - min_indent + if actual_depth < @max_depth then + + indent = " " * actual_depth + + "#{indent}* #{to_internal_link(t[1])}" + end + }.compact + + headers.unshift("### Table Of Contents\n") + + headers.join("\n") + contents + end +end + + +Liquid::Template.register_tag('tocmaker', TocMakerBlock) diff --git a/docs/_plugins/xpath_lexer.rb b/docs/_plugins/xpath_lexer.rb new file mode 100644 index 00000000000..53b5a569fc5 --- /dev/null +++ b/docs/_plugins/xpath_lexer.rb @@ -0,0 +1,69 @@ +# -*- coding: utf-8 -*- # + +require 'rouge' + +module Rouge + module Lexers + class XPath < RegexLexer + + title "XPath" + desc "The XPath query language (http://en.wikipedia.org/wiki/XPath)" + tag 'xpath' + aliases 'xpath' + filenames '*.xpath' + mimetypes 'text/x-xpath' + + state :root do + mixin :basic + mixin :operators + mixin :names + end + + state :basic do + rule /\s+/, Text::Whitespace + rule /[(]:(?![)])/, Comment, :nested_comment + + rule /[\[\](){}|.,;!]/, Punctuation + + rule /"([^"]|"")*+"/, Str::Double + rule /'([^']|'')*+'/, Str::Single + + rule /\.\d++\b/, Num + rule /\b\d++\./, Num + rule /\b\d++(\.\d*+)?([eE][+-]?\d+)?\b/, Num + end + + state :operators do + rule /(<|>|=<|>=|==|:=|\/\/|[|\/*+-])(?=\s|[a-zA-Z0-9\[])/, Operator + # operators + rule /(or|and|not|mod|ne|eq|lt|le|gt|ge)/, Operator::Word + # keywords + rule /some|in|satisfies|as|is|for|every|cast|castable|treat|instance|of|to|if|then|else|return|let|intersect|except|union|div|idiv/, Keyword::Reserved + # axes + rule /(self|child|attribute|descendant|descendant-or-self|ancestor|ancestor-or-self|following|following-sibling|namespace|parent|preceding-sibling)::/, Keyword::Namespace + # kind tests + rule /(node|document-node|text|comment|namespace-node|processing-instruction|attribute|schema-attribute|element|schema-element|function)\(\)/, Keyword::Reserved + end + + state :names do + # Function or node namespace + rule /[a-zA-Z\-]+:/, Name::Namespace + # Attributes + rule /@[a-zA-Z][_\-a-zA-Z0-9]*/, Name::Attribute + # XPath variables + rule /\$\s*[a-zA-Z][_\-a-zA-Z0-9]*/, Name::Variable + # Functions + rule /[a-zA-Z\-]+(?=\s*+\()/, Name::Function + # Node names + rule /[a-zA-Z]+/, Name::Tag + end + + state :nested_comment do + rule /[^(:)]+/, Comment + rule /\(:/, Comment, :push + rule /:\)/, Comment, :pop! + rule /[(:)]/, Comment + end + end + end +end diff --git a/docs/build-docs.sh b/docs/build-docs.sh new file mode 100644 index 00000000000..b1056030750 --- /dev/null +++ b/docs/build-docs.sh @@ -0,0 +1,3 @@ +bundle install --path vendor/bundle +bundle update +bundle exec jekyll build diff --git a/docs/css/customstyles.css b/docs/css/customstyles.css index 452b23bf121..d505dbd8de9 100644 --- a/docs/css/customstyles.css +++ b/docs/css/customstyles.css @@ -79,8 +79,8 @@ h4[id], h5[id], h6[id], dt[id]{ -padding-top: 60px; -margin-top: -40px +padding-top: 20px; +margin-top: 0px } body h1 {margin-top:40px;} diff --git a/docs/css/pmd-customstyles.css b/docs/css/pmd-customstyles.css new file mode 100644 index 00000000000..e9de59c7135 --- /dev/null +++ b/docs/css/pmd-customstyles.css @@ -0,0 +1,22 @@ + +.panel-body { + padding-top: 0cm; +} + +.landing-page.cat-title { + margin-bottom: .5cm; + margin-top: .5cm; +} + +.landing-page.cat-description { + margin-top: .4cm; +} + +/* Offsets contents by the height of the nav bar */ +#topbar-content-offset { + overflow: scroll; + position: absolute; + top: 50px; /* height of the nav bar */ + bottom: 0px; + width: 100%; +} \ No newline at end of file diff --git a/docs/images/credits/MD_logo4c_120x120.png b/docs/images/credits/MD_logo4c_120x120.png new file mode 100644 index 00000000000..c641799fa9d Binary files /dev/null and b/docs/images/credits/MD_logo4c_120x120.png differ diff --git a/docs/index.md b/docs/index.md index 2f63d0f86d9..55e9c9e5cd6 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,97 +1,155 @@ --- -title: PMD Introduction +title: Documentation Index keywords: java -tags: [getting_started] permalink: index.html toc: false summary: > - Welcome to PMD, an extensible cross-language static code analyzer. - It finds common programming flaws like unused variables, empty catch blocks, unnecessary object creation, - and so forth. Additionally it includes CPD, the copy-paste-detector. CPD finds duplicated code. + Welcome to the documentation site for PMD and CPD!

+ + last_updated: August 2017 -author: Jeff Jensen , Andreas Dangel +author: Jeff Jensen , Andreas Dangel , + Clément Fournier --- -{% include image.html file="pmd-logo-big.png" alt="PMD Logo" %} -
-**PMD** scans source code in Java and other languages and looks for potential problems like: +{% unless site.output == "pdf" %} + + + +{% include custom/panel_scroll.html %} +{% endunless %} + + +## Overview + + + + +**PMD** is a static source code analyzer. It finds common programming flaws like +unused variables, empty catch blocks, unnecessary object creation, and +so forth. It's mainly concerned with **Java and Apex**, but **supports six other +languages**. + +PMD features many **built-in checks** (in PMD lingo, *rules*), which are documented +for each language in our [Rule references](#shuffle-panel-rule-references). We +also support an extensive API to [**write your own rules**](#shuffle-panel-writing-rules), +which you can do either in Java or as a self-contained XPath query. + +PMD is most useful when **integrated into your build process**. It can then be +used as a quality gate, to enforce a coding standard for your codebase. Among other +things, PMD can be run: +* As a [Maven goal](pmd_userdocs_tools_maven.html) +* As an [Ant task](pmd_userdocs_tools_ant.html) +* As a [Gradle task](https://docs.gradle.org/current/userguide/pmd_plugin.html) +* From [command-line](pmd_userdocs_installation.html#running-pmd-via-command-line) + +**CPD**, the **copy-paste detector**, is also distributed with PMD. You can also use it +in a variety of ways, which are [documented here](pmd_userdocs_cpd.html). + +## Download + +The latest release of PMD can be downloaded from our [Github releases page](https://github.com/pmd/pmd/releases/latest). + + +## Documentation + +The rest of this page exposes the contents of the documentation site thematically, +which you can further scope down using the blue filter buttons. To navigate the site, +you may also use the search bar in the top right, or the sidebar on the left. + + +
+ + + + +
+ + + + + +
+ + -* Possible bugs - empty try/catch/finally/switch statements -* Dead code - unused local variables, parameters and private methods -* Suboptimal code - wasteful String/StringBuffer usage -* Overcomplicated expressions - unnecessary if statements, for loops that could be while loops +
+
-**CPD**, the copy-paste-detector, finds duplicated code in many languages: + + {% include custom/shuffle_panel.html + title="Getting started" + tags="getting_started" + datagroups='["getting_started"]' + description="These pages summarize the gist of PMD usage to get you started quickly." %} -* Duplicate code is often just copied and pasted. This means, the bugs are also copied and pasted. Fixing - them means, fix all duplicated code locations. -## Features + {% include custom/shuffle_panel.html + title="Rule references" + tags="rule_references" + datagroups='["userdocs"]' + description="Pick your language to find out about the rule it supports." + image="fa-database" + titlemaker="page.language_name" %} -{::options parse_block_html="true" /} + {% include custom/shuffle_panel.html + title="Writing rules" + tags="userdocs,extending" + datagroups='["userdocs", "extending", "contributing"]' + description="These pages document the process of writing and testing custom rules and metrics for PMD." + %} -
-### PMD + {% include custom/shuffle_panel.html + title="Usage and configuration" + tags="userdocs" + except_tags="extending,tools" + datagroups='["userdocs"]' + image="fa-cog" + description="Learn how to build effective and versatile rulesets." + %} -Features: -* Supporting 8 languages -* Many ready-to-use built-in rules. -* Custom rules can be written in Java -* Custom rules can be written using XPath expression that query the AST of the sources -* Many output formats -* Many integrations into IDEs, build tools + {% include custom/shuffle_panel.html + title="Contributing" + tags="devdocs" + except_tags="extending" + datagroups='["contributing"]' + image="fa-github" + description="If you'd like to help us build PMD, these topics may interest you. See you around!" + %} -Supported Languages: -* [Java](pmd_rules_java.html) -* [JavaScript](pmd_rules_ecmascript.html) -* [Salesforce.com Apex](pmd_rules_apex.html) and [Visualforce](pmd_rules_vf.html) -* [PLSQL](pmd_rules_plsql.html) -* [Apache Velocity](pmd_rules_vm.html) -* [XML](pmd_rules_xml.html) and [Maven POM](pmd_rules_pom.html) -* [XSL](pmd_rules_xsl.html) -
-### CPD + {% include custom/shuffle_panel.html + title="Tools and integrations" + tags="tools" + datagroups='["userdocs"]' + description="These pages describe solutions that integrate PMD within your build process." + %} -Features: + {% include custom/shuffle_panel.html + title="Major contributions" + tags="devdocs,extending" + datagroups='["contributing","extending"]' + description="" + %} -* Supporting 19 languages -* Simple GUI -* Fast -* Many integrations -Supported Languages: + +
-* Java -* C, C++ -* C# -* Groovy -* PHP -* Ruby -* Fortran -* JavaScript -* PLSQL -* Apache Velocity -* Scala -* Objective C -* Matlab -* Python -* Go -* Swift -* Salesforce.com Apex and Visualforce +
+
-
+ -## Download PMD {{ site.pmd.version }} +{% unless site.output == "pdf" %} -Latest Version: {{ site.pmd.version }} ({{ site.pmd.date }}) +{% include initialize_shuffle.html %} -* [Release Notes](pmd_release_notes.html) -* [Download](https://github.com/pmd/pmd/releases) +{% endunless %} diff --git a/docs/js/jquery-ui.min.js b/docs/js/jquery-ui.min.js new file mode 100644 index 00000000000..db0af4c4d59 --- /dev/null +++ b/docs/js/jquery-ui.min.js @@ -0,0 +1,6 @@ +/*! jQuery UI - v1.12.1 - 2018-05-27 +* http://jqueryui.com +* Includes: effect.js, effects/effect-blind.js, effects/effect-bounce.js, effects/effect-clip.js, effects/effect-drop.js, effects/effect-explode.js, effects/effect-fade.js, effects/effect-fold.js, effects/effect-highlight.js, effects/effect-puff.js, effects/effect-pulsate.js, effects/effect-scale.js, effects/effect-shake.js, effects/effect-size.js, effects/effect-slide.js, effects/effect-transfer.js +* Copyright jQuery Foundation and other contributors; Licensed MIT */ + +(function(t){"function"==typeof define&&define.amd?define(["jquery"],t):t(jQuery)})(function(t){t.ui=t.ui||{},t.ui.version="1.12.1";var e="ui-effects-",i="ui-effects-style",s="ui-effects-animated",n=t;t.effects={effect:{}},function(t,e){function i(t,e,i){var s=u[e.type]||{};return null==t?i||!e.def?null:e.def:(t=s.floor?~~t:parseFloat(t),isNaN(t)?e.def:s.mod?(t+s.mod)%s.mod:0>t?0:t>s.max?s.max:t)}function s(i){var s=h(),n=s._rgba=[];return i=i.toLowerCase(),f(l,function(t,o){var a,r=o.re.exec(i),l=r&&o.parse(r),h=o.space||"rgba";return l?(a=s[h](l),s[c[h].cache]=a[c[h].cache],n=s._rgba=a._rgba,!1):e}),n.length?("0,0,0,0"===n.join()&&t.extend(n,o.transparent),s):o[i]}function n(t,e,i){return i=(i+1)%1,1>6*i?t+6*(e-t)*i:1>2*i?e:2>3*i?t+6*(e-t)*(2/3-i):t}var o,a="backgroundColor borderBottomColor borderLeftColor borderRightColor borderTopColor color columnRuleColor outlineColor textDecorationColor textEmphasisColor",r=/^([\-+])=\s*(\d+\.?\d*)/,l=[{re:/rgba?\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*(?:,\s*(\d?(?:\.\d+)?)\s*)?\)/,parse:function(t){return[t[1],t[2],t[3],t[4]]}},{re:/rgba?\(\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*(?:,\s*(\d?(?:\.\d+)?)\s*)?\)/,parse:function(t){return[2.55*t[1],2.55*t[2],2.55*t[3],t[4]]}},{re:/#([a-f0-9]{2})([a-f0-9]{2})([a-f0-9]{2})/,parse:function(t){return[parseInt(t[1],16),parseInt(t[2],16),parseInt(t[3],16)]}},{re:/#([a-f0-9])([a-f0-9])([a-f0-9])/,parse:function(t){return[parseInt(t[1]+t[1],16),parseInt(t[2]+t[2],16),parseInt(t[3]+t[3],16)]}},{re:/hsla?\(\s*(\d+(?:\.\d+)?)\s*,\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*(?:,\s*(\d?(?:\.\d+)?)\s*)?\)/,space:"hsla",parse:function(t){return[t[1],t[2]/100,t[3]/100,t[4]]}}],h=t.Color=function(e,i,s,n){return new t.Color.fn.parse(e,i,s,n)},c={rgba:{props:{red:{idx:0,type:"byte"},green:{idx:1,type:"byte"},blue:{idx:2,type:"byte"}}},hsla:{props:{hue:{idx:0,type:"degrees"},saturation:{idx:1,type:"percent"},lightness:{idx:2,type:"percent"}}}},u={"byte":{floor:!0,max:255},percent:{max:1},degrees:{mod:360,floor:!0}},d=h.support={},p=t("

")[0],f=t.each;p.style.cssText="background-color:rgba(1,1,1,.5)",d.rgba=p.style.backgroundColor.indexOf("rgba")>-1,f(c,function(t,e){e.cache="_"+t,e.props.alpha={idx:3,type:"percent",def:1}}),h.fn=t.extend(h.prototype,{parse:function(n,a,r,l){if(n===e)return this._rgba=[null,null,null,null],this;(n.jquery||n.nodeType)&&(n=t(n).css(a),a=e);var u=this,d=t.type(n),p=this._rgba=[];return a!==e&&(n=[n,a,r,l],d="array"),"string"===d?this.parse(s(n)||o._default):"array"===d?(f(c.rgba.props,function(t,e){p[e.idx]=i(n[e.idx],e)}),this):"object"===d?(n instanceof h?f(c,function(t,e){n[e.cache]&&(u[e.cache]=n[e.cache].slice())}):f(c,function(e,s){var o=s.cache;f(s.props,function(t,e){if(!u[o]&&s.to){if("alpha"===t||null==n[t])return;u[o]=s.to(u._rgba)}u[o][e.idx]=i(n[t],e,!0)}),u[o]&&0>t.inArray(null,u[o].slice(0,3))&&(u[o][3]=1,s.from&&(u._rgba=s.from(u[o])))}),this):e},is:function(t){var i=h(t),s=!0,n=this;return f(c,function(t,o){var a,r=i[o.cache];return r&&(a=n[o.cache]||o.to&&o.to(n._rgba)||[],f(o.props,function(t,i){return null!=r[i.idx]?s=r[i.idx]===a[i.idx]:e})),s}),s},_space:function(){var t=[],e=this;return f(c,function(i,s){e[s.cache]&&t.push(i)}),t.pop()},transition:function(t,e){var s=h(t),n=s._space(),o=c[n],a=0===this.alpha()?h("transparent"):this,r=a[o.cache]||o.to(a._rgba),l=r.slice();return s=s[o.cache],f(o.props,function(t,n){var o=n.idx,a=r[o],h=s[o],c=u[n.type]||{};null!==h&&(null===a?l[o]=h:(c.mod&&(h-a>c.mod/2?a+=c.mod:a-h>c.mod/2&&(a-=c.mod)),l[o]=i((h-a)*e+a,n)))}),this[n](l)},blend:function(e){if(1===this._rgba[3])return this;var i=this._rgba.slice(),s=i.pop(),n=h(e)._rgba;return h(t.map(i,function(t,e){return(1-s)*n[e]+s*t}))},toRgbaString:function(){var e="rgba(",i=t.map(this._rgba,function(t,e){return null==t?e>2?1:0:t});return 1===i[3]&&(i.pop(),e="rgb("),e+i.join()+")"},toHslaString:function(){var e="hsla(",i=t.map(this.hsla(),function(t,e){return null==t&&(t=e>2?1:0),e&&3>e&&(t=Math.round(100*t)+"%"),t});return 1===i[3]&&(i.pop(),e="hsl("),e+i.join()+")"},toHexString:function(e){var i=this._rgba.slice(),s=i.pop();return e&&i.push(~~(255*s)),"#"+t.map(i,function(t){return t=(t||0).toString(16),1===t.length?"0"+t:t}).join("")},toString:function(){return 0===this._rgba[3]?"transparent":this.toRgbaString()}}),h.fn.parse.prototype=h.fn,c.hsla.to=function(t){if(null==t[0]||null==t[1]||null==t[2])return[null,null,null,t[3]];var e,i,s=t[0]/255,n=t[1]/255,o=t[2]/255,a=t[3],r=Math.max(s,n,o),l=Math.min(s,n,o),h=r-l,c=r+l,u=.5*c;return e=l===r?0:s===r?60*(n-o)/h+360:n===r?60*(o-s)/h+120:60*(s-n)/h+240,i=0===h?0:.5>=u?h/c:h/(2-c),[Math.round(e)%360,i,u,null==a?1:a]},c.hsla.from=function(t){if(null==t[0]||null==t[1]||null==t[2])return[null,null,null,t[3]];var e=t[0]/360,i=t[1],s=t[2],o=t[3],a=.5>=s?s*(1+i):s+i-s*i,r=2*s-a;return[Math.round(255*n(r,a,e+1/3)),Math.round(255*n(r,a,e)),Math.round(255*n(r,a,e-1/3)),o]},f(c,function(s,n){var o=n.props,a=n.cache,l=n.to,c=n.from;h.fn[s]=function(s){if(l&&!this[a]&&(this[a]=l(this._rgba)),s===e)return this[a].slice();var n,r=t.type(s),u="array"===r||"object"===r?s:arguments,d=this[a].slice();return f(o,function(t,e){var s=u["object"===r?t:e.idx];null==s&&(s=d[e.idx]),d[e.idx]=i(s,e)}),c?(n=h(c(d)),n[a]=d,n):h(d)},f(o,function(e,i){h.fn[e]||(h.fn[e]=function(n){var o,a=t.type(n),l="alpha"===e?this._hsla?"hsla":"rgba":s,h=this[l](),c=h[i.idx];return"undefined"===a?c:("function"===a&&(n=n.call(this,c),a=t.type(n)),null==n&&i.empty?this:("string"===a&&(o=r.exec(n),o&&(n=c+parseFloat(o[2])*("+"===o[1]?1:-1))),h[i.idx]=n,this[l](h)))})})}),h.hook=function(e){var i=e.split(" ");f(i,function(e,i){t.cssHooks[i]={set:function(e,n){var o,a,r="";if("transparent"!==n&&("string"!==t.type(n)||(o=s(n)))){if(n=h(o||n),!d.rgba&&1!==n._rgba[3]){for(a="backgroundColor"===i?e.parentNode:e;(""===r||"transparent"===r)&&a&&a.style;)try{r=t.css(a,"backgroundColor"),a=a.parentNode}catch(l){}n=n.blend(r&&"transparent"!==r?r:"_default")}n=n.toRgbaString()}try{e.style[i]=n}catch(l){}}},t.fx.step[i]=function(e){e.colorInit||(e.start=h(e.elem,i),e.end=h(e.end),e.colorInit=!0),t.cssHooks[i].set(e.elem,e.start.transition(e.end,e.pos))}})},h.hook(a),t.cssHooks.borderColor={expand:function(t){var e={};return f(["Top","Right","Bottom","Left"],function(i,s){e["border"+s+"Color"]=t}),e}},o=t.Color.names={aqua:"#00ffff",black:"#000000",blue:"#0000ff",fuchsia:"#ff00ff",gray:"#808080",green:"#008000",lime:"#00ff00",maroon:"#800000",navy:"#000080",olive:"#808000",purple:"#800080",red:"#ff0000",silver:"#c0c0c0",teal:"#008080",white:"#ffffff",yellow:"#ffff00",transparent:[null,null,null,0],_default:"#ffffff"}}(n),function(){function e(e){var i,s,n=e.ownerDocument.defaultView?e.ownerDocument.defaultView.getComputedStyle(e,null):e.currentStyle,o={};if(n&&n.length&&n[0]&&n[n[0]])for(s=n.length;s--;)i=n[s],"string"==typeof n[i]&&(o[t.camelCase(i)]=n[i]);else for(i in n)"string"==typeof n[i]&&(o[i]=n[i]);return o}function i(e,i){var s,n,a={};for(s in i)n=i[s],e[s]!==n&&(o[s]||(t.fx.step[s]||!isNaN(parseFloat(n)))&&(a[s]=n));return a}var s=["add","remove","toggle"],o={border:1,borderBottom:1,borderColor:1,borderLeft:1,borderRight:1,borderTop:1,borderWidth:1,margin:1,padding:1};t.each(["borderLeftStyle","borderRightStyle","borderBottomStyle","borderTopStyle"],function(e,i){t.fx.step[i]=function(t){("none"!==t.end&&!t.setAttr||1===t.pos&&!t.setAttr)&&(n.style(t.elem,i,t.end),t.setAttr=!0)}}),t.fn.addBack||(t.fn.addBack=function(t){return this.add(null==t?this.prevObject:this.prevObject.filter(t))}),t.effects.animateClass=function(n,o,a,r){var l=t.speed(o,a,r);return this.queue(function(){var o,a=t(this),r=a.attr("class")||"",h=l.children?a.find("*").addBack():a;h=h.map(function(){var i=t(this);return{el:i,start:e(this)}}),o=function(){t.each(s,function(t,e){n[e]&&a[e+"Class"](n[e])})},o(),h=h.map(function(){return this.end=e(this.el[0]),this.diff=i(this.start,this.end),this}),a.attr("class",r),h=h.map(function(){var e=this,i=t.Deferred(),s=t.extend({},l,{queue:!1,complete:function(){i.resolve(e)}});return this.el.animate(this.diff,s),i.promise()}),t.when.apply(t,h.get()).done(function(){o(),t.each(arguments,function(){var e=this.el;t.each(this.diff,function(t){e.css(t,"")})}),l.complete.call(a[0])})})},t.fn.extend({addClass:function(e){return function(i,s,n,o){return s?t.effects.animateClass.call(this,{add:i},s,n,o):e.apply(this,arguments)}}(t.fn.addClass),removeClass:function(e){return function(i,s,n,o){return arguments.length>1?t.effects.animateClass.call(this,{remove:i},s,n,o):e.apply(this,arguments)}}(t.fn.removeClass),toggleClass:function(e){return function(i,s,n,o,a){return"boolean"==typeof s||void 0===s?n?t.effects.animateClass.call(this,s?{add:i}:{remove:i},n,o,a):e.apply(this,arguments):t.effects.animateClass.call(this,{toggle:i},s,n,o)}}(t.fn.toggleClass),switchClass:function(e,i,s,n,o){return t.effects.animateClass.call(this,{add:i,remove:e},s,n,o)}})}(),function(){function n(e,i,s,n){return t.isPlainObject(e)&&(i=e,e=e.effect),e={effect:e},null==i&&(i={}),t.isFunction(i)&&(n=i,s=null,i={}),("number"==typeof i||t.fx.speeds[i])&&(n=s,s=i,i={}),t.isFunction(s)&&(n=s,s=null),i&&t.extend(e,i),s=s||i.duration,e.duration=t.fx.off?0:"number"==typeof s?s:s in t.fx.speeds?t.fx.speeds[s]:t.fx.speeds._default,e.complete=n||i.complete,e}function o(e){return!e||"number"==typeof e||t.fx.speeds[e]?!0:"string"!=typeof e||t.effects.effect[e]?t.isFunction(e)?!0:"object"!=typeof e||e.effect?!1:!0:!0}function a(t,e){var i=e.outerWidth(),s=e.outerHeight(),n=/^rect\((-?\d*\.?\d*px|-?\d+%|auto),?\s*(-?\d*\.?\d*px|-?\d+%|auto),?\s*(-?\d*\.?\d*px|-?\d+%|auto),?\s*(-?\d*\.?\d*px|-?\d+%|auto)\)$/,o=n.exec(t)||["",0,i,s,0];return{top:parseFloat(o[1])||0,right:"auto"===o[2]?i:parseFloat(o[2]),bottom:"auto"===o[3]?s:parseFloat(o[3]),left:parseFloat(o[4])||0}}t.expr&&t.expr.filters&&t.expr.filters.animated&&(t.expr.filters.animated=function(e){return function(i){return!!t(i).data(s)||e(i)}}(t.expr.filters.animated)),t.uiBackCompat!==!1&&t.extend(t.effects,{save:function(t,i){for(var s=0,n=i.length;n>s;s++)null!==i[s]&&t.data(e+i[s],t[0].style[i[s]])},restore:function(t,i){for(var s,n=0,o=i.length;o>n;n++)null!==i[n]&&(s=t.data(e+i[n]),t.css(i[n],s))},setMode:function(t,e){return"toggle"===e&&(e=t.is(":hidden")?"show":"hide"),e},createWrapper:function(e){if(e.parent().is(".ui-effects-wrapper"))return e.parent();var i={width:e.outerWidth(!0),height:e.outerHeight(!0),"float":e.css("float")},s=t("

").addClass("ui-effects-wrapper").css({fontSize:"100%",background:"transparent",border:"none",margin:0,padding:0}),n={width:e.width(),height:e.height()},o=document.activeElement;try{o.id}catch(a){o=document.body}return e.wrap(s),(e[0]===o||t.contains(e[0],o))&&t(o).trigger("focus"),s=e.parent(),"static"===e.css("position")?(s.css({position:"relative"}),e.css({position:"relative"})):(t.extend(i,{position:e.css("position"),zIndex:e.css("z-index")}),t.each(["top","left","bottom","right"],function(t,s){i[s]=e.css(s),isNaN(parseInt(i[s],10))&&(i[s]="auto")}),e.css({position:"relative",top:0,left:0,right:"auto",bottom:"auto"})),e.css(n),s.css(i).show()},removeWrapper:function(e){var i=document.activeElement;return e.parent().is(".ui-effects-wrapper")&&(e.parent().replaceWith(e),(e[0]===i||t.contains(e[0],i))&&t(i).trigger("focus")),e}}),t.extend(t.effects,{version:"1.12.1",define:function(e,i,s){return s||(s=i,i="effect"),t.effects.effect[e]=s,t.effects.effect[e].mode=i,s},scaledDimensions:function(t,e,i){if(0===e)return{height:0,width:0,outerHeight:0,outerWidth:0};var s="horizontal"!==i?(e||100)/100:1,n="vertical"!==i?(e||100)/100:1;return{height:t.height()*n,width:t.width()*s,outerHeight:t.outerHeight()*n,outerWidth:t.outerWidth()*s}},clipToBox:function(t){return{width:t.clip.right-t.clip.left,height:t.clip.bottom-t.clip.top,left:t.clip.left,top:t.clip.top}},unshift:function(t,e,i){var s=t.queue();e>1&&s.splice.apply(s,[1,0].concat(s.splice(e,i))),t.dequeue()},saveStyle:function(t){t.data(i,t[0].style.cssText)},restoreStyle:function(t){t[0].style.cssText=t.data(i)||"",t.removeData(i)},mode:function(t,e){var i=t.is(":hidden");return"toggle"===e&&(e=i?"show":"hide"),(i?"hide"===e:"show"===e)&&(e="none"),e},getBaseline:function(t,e){var i,s;switch(t[0]){case"top":i=0;break;case"middle":i=.5;break;case"bottom":i=1;break;default:i=t[0]/e.height}switch(t[1]){case"left":s=0;break;case"center":s=.5;break;case"right":s=1;break;default:s=t[1]/e.width}return{x:s,y:i}},createPlaceholder:function(i){var s,n=i.css("position"),o=i.position();return i.css({marginTop:i.css("marginTop"),marginBottom:i.css("marginBottom"),marginLeft:i.css("marginLeft"),marginRight:i.css("marginRight")}).outerWidth(i.outerWidth()).outerHeight(i.outerHeight()),/^(static|relative)/.test(n)&&(n="absolute",s=t("<"+i[0].nodeName+">").insertAfter(i).css({display:/^(inline|ruby)/.test(i.css("display"))?"inline-block":"block",visibility:"hidden",marginTop:i.css("marginTop"),marginBottom:i.css("marginBottom"),marginLeft:i.css("marginLeft"),marginRight:i.css("marginRight"),"float":i.css("float")}).outerWidth(i.outerWidth()).outerHeight(i.outerHeight()).addClass("ui-effects-placeholder"),i.data(e+"placeholder",s)),i.css({position:n,left:o.left,top:o.top}),s},removePlaceholder:function(t){var i=e+"placeholder",s=t.data(i);s&&(s.remove(),t.removeData(i))},cleanUp:function(e){t.effects.restoreStyle(e),t.effects.removePlaceholder(e)},setTransition:function(e,i,s,n){return n=n||{},t.each(i,function(t,i){var o=e.cssUnit(i);o[0]>0&&(n[i]=o[0]*s+o[1])}),n}}),t.fn.extend({effect:function(){function e(e){function n(){l.removeData(s),t.effects.cleanUp(l),"hide"===i.mode&&l.hide(),r()}function r(){t.isFunction(h)&&h.call(l[0]),t.isFunction(e)&&e()}var l=t(this);i.mode=u.shift(),t.uiBackCompat===!1||a?"none"===i.mode?(l[c](),r()):o.call(l[0],i,n):(l.is(":hidden")?"hide"===c:"show"===c)?(l[c](),r()):o.call(l[0],i,r)}var i=n.apply(this,arguments),o=t.effects.effect[i.effect],a=o.mode,r=i.queue,l=r||"fx",h=i.complete,c=i.mode,u=[],d=function(e){var i=t(this),n=t.effects.mode(i,c)||a;i.data(s,!0),u.push(n),a&&("show"===n||n===a&&"hide"===n)&&i.show(),a&&"none"===n||t.effects.saveStyle(i),t.isFunction(e)&&e()};return t.fx.off||!o?c?this[c](i.duration,h):this.each(function(){h&&h.call(this)}):r===!1?this.each(d).each(e):this.queue(l,d).queue(l,e)},show:function(t){return function(e){if(o(e))return t.apply(this,arguments);var i=n.apply(this,arguments);return i.mode="show",this.effect.call(this,i)}}(t.fn.show),hide:function(t){return function(e){if(o(e))return t.apply(this,arguments);var i=n.apply(this,arguments);return i.mode="hide",this.effect.call(this,i)}}(t.fn.hide),toggle:function(t){return function(e){if(o(e)||"boolean"==typeof e)return t.apply(this,arguments);var i=n.apply(this,arguments);return i.mode="toggle",this.effect.call(this,i)}}(t.fn.toggle),cssUnit:function(e){var i=this.css(e),s=[];return t.each(["em","px","%","pt"],function(t,e){i.indexOf(e)>0&&(s=[parseFloat(i),e])}),s},cssClip:function(t){return t?this.css("clip","rect("+t.top+"px "+t.right+"px "+t.bottom+"px "+t.left+"px)"):a(this.css("clip"),this)},transfer:function(e,i){var s=t(this),n=t(e.to),o="fixed"===n.css("position"),a=t("body"),r=o?a.scrollTop():0,l=o?a.scrollLeft():0,h=n.offset(),c={top:h.top-r,left:h.left-l,height:n.innerHeight(),width:n.innerWidth()},u=s.offset(),d=t("
").appendTo("body").addClass(e.className).css({top:u.top-r,left:u.left-l,height:s.innerHeight(),width:s.innerWidth(),position:o?"fixed":"absolute"}).animate(c,e.duration,e.easing,function(){d.remove(),t.isFunction(i)&&i()})}}),t.fx.step.clip=function(e){e.clipInit||(e.start=t(e.elem).cssClip(),"string"==typeof e.end&&(e.end=a(e.end,e.elem)),e.clipInit=!0),t(e.elem).cssClip({top:e.pos*(e.end.top-e.start.top)+e.start.top,right:e.pos*(e.end.right-e.start.right)+e.start.right,bottom:e.pos*(e.end.bottom-e.start.bottom)+e.start.bottom,left:e.pos*(e.end.left-e.start.left)+e.start.left})}}(),function(){var e={};t.each(["Quad","Cubic","Quart","Quint","Expo"],function(t,i){e[i]=function(e){return Math.pow(e,t+2)}}),t.extend(e,{Sine:function(t){return 1-Math.cos(t*Math.PI/2)},Circ:function(t){return 1-Math.sqrt(1-t*t)},Elastic:function(t){return 0===t||1===t?t:-Math.pow(2,8*(t-1))*Math.sin((80*(t-1)-7.5)*Math.PI/15)},Back:function(t){return t*t*(3*t-2)},Bounce:function(t){for(var e,i=4;((e=Math.pow(2,--i))-1)/11>t;);return 1/Math.pow(4,3-i)-7.5625*Math.pow((3*e-2)/22-t,2)}}),t.each(e,function(e,i){t.easing["easeIn"+e]=i,t.easing["easeOut"+e]=function(t){return 1-i(1-t)},t.easing["easeInOut"+e]=function(t){return.5>t?i(2*t)/2:1-i(-2*t+2)/2}})}();var o=t.effects;t.effects.define("blind","hide",function(e,i){var s={up:["bottom","top"],vertical:["bottom","top"],down:["top","bottom"],left:["right","left"],horizontal:["right","left"],right:["left","right"]},n=t(this),o=e.direction||"up",a=n.cssClip(),r={clip:t.extend({},a)},l=t.effects.createPlaceholder(n);r.clip[s[o][0]]=r.clip[s[o][1]],"show"===e.mode&&(n.cssClip(r.clip),l&&l.css(t.effects.clipToBox(r)),r.clip=a),l&&l.animate(t.effects.clipToBox(r),e.duration,e.easing),n.animate(r,{queue:!1,duration:e.duration,easing:e.easing,complete:i})}),t.effects.define("bounce",function(e,i){var s,n,o,a=t(this),r=e.mode,l="hide"===r,h="show"===r,c=e.direction||"up",u=e.distance,d=e.times||5,p=2*d+(h||l?1:0),f=e.duration/p,m=e.easing,g="up"===c||"down"===c?"top":"left",_="up"===c||"left"===c,v=0,b=a.queue().length;for(t.effects.createPlaceholder(a),o=a.css(g),u||(u=a["top"===g?"outerHeight":"outerWidth"]()/3),h&&(n={opacity:1},n[g]=o,a.css("opacity",0).css(g,_?2*-u:2*u).animate(n,f,m)),l&&(u/=Math.pow(2,d-1)),n={},n[g]=o;d>v;v++)s={},s[g]=(_?"-=":"+=")+u,a.animate(s,f,m).animate(n,f,m),u=l?2*u:u/2;l&&(s={opacity:0},s[g]=(_?"-=":"+=")+u,a.animate(s,f,m)),a.queue(i),t.effects.unshift(a,b,p+1)}),t.effects.define("clip","hide",function(e,i){var s,n={},o=t(this),a=e.direction||"vertical",r="both"===a,l=r||"horizontal"===a,h=r||"vertical"===a;s=o.cssClip(),n.clip={top:h?(s.bottom-s.top)/2:s.top,right:l?(s.right-s.left)/2:s.right,bottom:h?(s.bottom-s.top)/2:s.bottom,left:l?(s.right-s.left)/2:s.left},t.effects.createPlaceholder(o),"show"===e.mode&&(o.cssClip(n.clip),n.clip=s),o.animate(n,{queue:!1,duration:e.duration,easing:e.easing,complete:i})}),t.effects.define("drop","hide",function(e,i){var s,n=t(this),o=e.mode,a="show"===o,r=e.direction||"left",l="up"===r||"down"===r?"top":"left",h="up"===r||"left"===r?"-=":"+=",c="+="===h?"-=":"+=",u={opacity:0};t.effects.createPlaceholder(n),s=e.distance||n["top"===l?"outerHeight":"outerWidth"](!0)/2,u[l]=h+s,a&&(n.css(u),u[l]=c+s,u.opacity=1),n.animate(u,{queue:!1,duration:e.duration,easing:e.easing,complete:i})}),t.effects.define("explode","hide",function(e,i){function s(){b.push(this),b.length===u*d&&n()}function n(){p.css({visibility:"visible"}),t(b).remove(),i()}var o,a,r,l,h,c,u=e.pieces?Math.round(Math.sqrt(e.pieces)):3,d=u,p=t(this),f=e.mode,m="show"===f,g=p.show().css("visibility","hidden").offset(),_=Math.ceil(p.outerWidth()/d),v=Math.ceil(p.outerHeight()/u),b=[];for(o=0;u>o;o++)for(l=g.top+o*v,c=o-(u-1)/2,a=0;d>a;a++)r=g.left+a*_,h=a-(d-1)/2,p.clone().appendTo("body").wrap("
").css({position:"absolute",visibility:"visible",left:-a*_,top:-o*v}).parent().addClass("ui-effects-explode").css({position:"absolute",overflow:"hidden",width:_,height:v,left:r+(m?h*_:0),top:l+(m?c*v:0),opacity:m?0:1}).animate({left:r+(m?0:h*_),top:l+(m?0:c*v),opacity:m?1:0},e.duration||500,e.easing,s)}),t.effects.define("fade","toggle",function(e,i){var s="show"===e.mode;t(this).css("opacity",s?0:1).animate({opacity:s?1:0},{queue:!1,duration:e.duration,easing:e.easing,complete:i})}),t.effects.define("fold","hide",function(e,i){var s=t(this),n=e.mode,o="show"===n,a="hide"===n,r=e.size||15,l=/([0-9]+)%/.exec(r),h=!!e.horizFirst,c=h?["right","bottom"]:["bottom","right"],u=e.duration/2,d=t.effects.createPlaceholder(s),p=s.cssClip(),f={clip:t.extend({},p)},m={clip:t.extend({},p)},g=[p[c[0]],p[c[1]]],_=s.queue().length;l&&(r=parseInt(l[1],10)/100*g[a?0:1]),f.clip[c[0]]=r,m.clip[c[0]]=r,m.clip[c[1]]=0,o&&(s.cssClip(m.clip),d&&d.css(t.effects.clipToBox(m)),m.clip=p),s.queue(function(i){d&&d.animate(t.effects.clipToBox(f),u,e.easing).animate(t.effects.clipToBox(m),u,e.easing),i()}).animate(f,u,e.easing).animate(m,u,e.easing).queue(i),t.effects.unshift(s,_,4)}),t.effects.define("highlight","show",function(e,i){var s=t(this),n={backgroundColor:s.css("backgroundColor")};"hide"===e.mode&&(n.opacity=0),t.effects.saveStyle(s),s.css({backgroundImage:"none",backgroundColor:e.color||"#ffff99"}).animate(n,{queue:!1,duration:e.duration,easing:e.easing,complete:i})}),t.effects.define("size",function(e,i){var s,n,o,a=t(this),r=["fontSize"],l=["borderTopWidth","borderBottomWidth","paddingTop","paddingBottom"],h=["borderLeftWidth","borderRightWidth","paddingLeft","paddingRight"],c=e.mode,u="effect"!==c,d=e.scale||"both",p=e.origin||["middle","center"],f=a.css("position"),m=a.position(),g=t.effects.scaledDimensions(a),_=e.from||g,v=e.to||t.effects.scaledDimensions(a,0);t.effects.createPlaceholder(a),"show"===c&&(o=_,_=v,v=o),n={from:{y:_.height/g.height,x:_.width/g.width},to:{y:v.height/g.height,x:v.width/g.width}},("box"===d||"both"===d)&&(n.from.y!==n.to.y&&(_=t.effects.setTransition(a,l,n.from.y,_),v=t.effects.setTransition(a,l,n.to.y,v)),n.from.x!==n.to.x&&(_=t.effects.setTransition(a,h,n.from.x,_),v=t.effects.setTransition(a,h,n.to.x,v))),("content"===d||"both"===d)&&n.from.y!==n.to.y&&(_=t.effects.setTransition(a,r,n.from.y,_),v=t.effects.setTransition(a,r,n.to.y,v)),p&&(s=t.effects.getBaseline(p,g),_.top=(g.outerHeight-_.outerHeight)*s.y+m.top,_.left=(g.outerWidth-_.outerWidth)*s.x+m.left,v.top=(g.outerHeight-v.outerHeight)*s.y+m.top,v.left=(g.outerWidth-v.outerWidth)*s.x+m.left),a.css(_),("content"===d||"both"===d)&&(l=l.concat(["marginTop","marginBottom"]).concat(r),h=h.concat(["marginLeft","marginRight"]),a.find("*[width]").each(function(){var i=t(this),s=t.effects.scaledDimensions(i),o={height:s.height*n.from.y,width:s.width*n.from.x,outerHeight:s.outerHeight*n.from.y,outerWidth:s.outerWidth*n.from.x},a={height:s.height*n.to.y,width:s.width*n.to.x,outerHeight:s.height*n.to.y,outerWidth:s.width*n.to.x};n.from.y!==n.to.y&&(o=t.effects.setTransition(i,l,n.from.y,o),a=t.effects.setTransition(i,l,n.to.y,a)),n.from.x!==n.to.x&&(o=t.effects.setTransition(i,h,n.from.x,o),a=t.effects.setTransition(i,h,n.to.x,a)),u&&t.effects.saveStyle(i),i.css(o),i.animate(a,e.duration,e.easing,function(){u&&t.effects.restoreStyle(i)})})),a.animate(v,{queue:!1,duration:e.duration,easing:e.easing,complete:function(){var e=a.offset();0===v.opacity&&a.css("opacity",_.opacity),u||(a.css("position","static"===f?"relative":f).offset(e),t.effects.saveStyle(a)),i()}})}),t.effects.define("scale",function(e,i){var s=t(this),n=e.mode,o=parseInt(e.percent,10)||(0===parseInt(e.percent,10)?0:"effect"!==n?0:100),a=t.extend(!0,{from:t.effects.scaledDimensions(s),to:t.effects.scaledDimensions(s,o,e.direction||"both"),origin:e.origin||["middle","center"]},e);e.fade&&(a.from.opacity=1,a.to.opacity=0),t.effects.effect.size.call(this,a,i)}),t.effects.define("puff","hide",function(e,i){var s=t.extend(!0,{},e,{fade:!0,percent:parseInt(e.percent,10)||150});t.effects.effect.scale.call(this,s,i)}),t.effects.define("pulsate","show",function(e,i){var s=t(this),n=e.mode,o="show"===n,a="hide"===n,r=o||a,l=2*(e.times||5)+(r?1:0),h=e.duration/l,c=0,u=1,d=s.queue().length;for((o||!s.is(":visible"))&&(s.css("opacity",0).show(),c=1);l>u;u++)s.animate({opacity:c},h,e.easing),c=1-c;s.animate({opacity:c},h,e.easing),s.queue(i),t.effects.unshift(s,d,l+1)}),t.effects.define("shake",function(e,i){var s=1,n=t(this),o=e.direction||"left",a=e.distance||20,r=e.times||3,l=2*r+1,h=Math.round(e.duration/l),c="up"===o||"down"===o?"top":"left",u="up"===o||"left"===o,d={},p={},f={},m=n.queue().length;for(t.effects.createPlaceholder(n),d[c]=(u?"-=":"+=")+a,p[c]=(u?"+=":"-=")+2*a,f[c]=(u?"-=":"+=")+2*a,n.animate(d,h,e.easing);r>s;s++)n.animate(p,h,e.easing).animate(f,h,e.easing);n.animate(p,h,e.easing).animate(d,h/2,e.easing).queue(i),t.effects.unshift(n,m,l+1)}),t.effects.define("slide","show",function(e,i){var s,n,o=t(this),a={up:["bottom","top"],down:["top","bottom"],left:["right","left"],right:["left","right"]},r=e.mode,l=e.direction||"left",h="up"===l||"down"===l?"top":"left",c="up"===l||"left"===l,u=e.distance||o["top"===h?"outerHeight":"outerWidth"](!0),d={};t.effects.createPlaceholder(o),s=o.cssClip(),n=o.position()[h],d[h]=(c?-1:1)*u+n,d.clip=o.cssClip(),d.clip[a[l][1]]=d.clip[a[l][0]],"show"===r&&(o.cssClip(d.clip),o.css(h,d[h]),d.clip=s,d[h]=n),o.animate(d,{queue:!1,duration:e.duration,easing:e.easing,complete:i})});var o;t.uiBackCompat!==!1&&(o=t.effects.define("transfer",function(e,i){t(this).transfer(e,i)}))}); \ No newline at end of file diff --git a/docs/pages/next_major_development.md b/docs/pages/next_major_development.md new file mode 100644 index 00000000000..c7abcaa68c1 --- /dev/null +++ b/docs/pages/next_major_development.md @@ -0,0 +1,206 @@ +--- +title: PMD 7.0.0 development +permalink: pmd_next_major_development.html +keywords: changelog, release notes, deprecation, api changes +--- + +We're excited to bring you the next major version of PMD! Here are the major features and changes we're working on. +To give us feedback or to suggest a new feature, drop us a line on [Gitter](https://gitter.im/pmd/pmd)! + +## New Features + +TODO + +## Java grammar changes + +{% include note.html content="Current plans are listed [here](https://github.com/pmd/pmd/labels/in%3Aast) and in particular [here](https://github.com/pmd/pmd/issues/1019)" %} + + + +## New API support guidelines + +### What's new + +Until now, all released public members and types were implicitly considered part +of PMD's public API, including inheritance-specific members (protected members, abstract methods). +We have maintained those APIs with the goal to preserve full binary compatibility between minor releases, +only breaking those APIs infrequently, for major releases. + +In order to allow PMD to move forward at a faster pace, this implicit contract will +be invalidated with PMD 7.0.0. We now introduce more fine-grained distinctions between +the type of compatibility support we guarantee for our libraries, and ways to make +them explicit to clients of PMD. + +#### `.internal` packages and `@InternalApi` annotation + +*Internal API* is meant for use *only* by the main PMD codebase. Internal types and methods +may be modified in any way, or even removed, at any time. + +Any API in a package that contains an `.internal` segment is considered internal. +The `@InternalApi` annotation will be used for APIs that have to live outside of +these packages, e.g. methods of a public type that shouldn't be used outside of PMD (again, +these can be removed anytime). + +#### `@ReservedSubclassing` + +Types marked with the `@ReservedSubclassing` annotation are only meant to be subclassed +by classes within PMD. As such, we may add new abstract methods, or remove protected methods, +at any time. All published public members remain supported. The annotation is *not* inherited, which +means a reserved interface doesn't prevent its implementors to be subclassed. + +#### `@Experimental` + +APIs marked with the `@Experimental` annotation at the class or method level are subject to change. +They can be modified in any way, or even removed, at any time. You should not use or rely + on them in any production code. They are purely to allow broad testing and feedback. + +#### `@Deprecated` + +APIs marked with the `@Deprecated` annotation at the class or method level will remain supported +until the next major release but it is recommended to stop using them. + +### The transition + +*All currently supported APIs will remain so until 7.0.0*. All APIs that are to be moved to +`.internal` packages or hidden will be tagged `@InternalApi` before that major release, and +the breaking API changes will be performed in 7.0.0. + +## Planned API removals + +### List of currently deprecated APIs + +{% include warning.html content="This list is not exhaustive. The ultimate reference is whether +an API is tagged as `@Deprecated` or not in the latest minor release. During the development of 7.0.0, +we may decide to remove some APIs that were not tagged as deprecated, though we'll try to avoid it." %} + +#### 6.8.0 + +* A couple of methods and fields in `net.sourceforge.pmd.properties.AbstractPropertySource` have been + deprecated, as they are replaced by already existing functionality or expose internal implementation + details: `propertyDescriptors`, `propertyValuesByDescriptor`, + `copyPropertyDescriptors()`, `copyPropertyValues()`, `ignoredProperties()`, `usesDefaultValues()`, + `useDefaultValueFor()`. + +* Some methods in `net.sourceforge.pmd.properties.PropertySource` have been deprecated as well: + `usesDefaultValues()`, `useDefaultValueFor()`, `ignoredProperties()`. + +* The class `net.sourceforge.pmd.lang.rule.AbstractDelegateRule` has been deprecated and will + be removed with PMD 7.0.0. It is internally only in use by RuleReference. + +* The default constructor of `net.sourceforge.pmd.lang.rule.RuleReference` has been deprecated + and will be removed with PMD 7.0.0. RuleReferences should only be created by providing a Rule and + a RuleSetReference. Furthermore the following methods are deprecated: `setRuleReference()`, + `hasOverriddenProperty()`, `usesDefaultValues()`, `useDefaultValueFor()`. + +#### 6.7.0 + +* 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. + +* The nodes RUNSIGNEDSHIFT and RSIGNEDSHIFT are deprecated and will be removed from the AST with PMD 7.0.0. + These represented the operator of ShiftExpression in two cases out of three, but they're not needed and + make ShiftExpression inconsistent. The operator of a ShiftExpression is now accessible through + ShiftExpression#getOperator. + +#### 6.5.0 + +* The utility class `net.sourceforge.pmd.lang.java.ast.CommentUtil` has been deprecated and will be removed + with PMD 7.0.0. Its methods have been intended to parse javadoc tags. A more useful solution will be added + around the AST node `FormalComment`, which contains as children `JavadocElement` nodes, which in + turn provide access to the `JavadocTag`. + + All comment AST nodes (`FormalComment`, `MultiLineComment`, `SingleLineComment`) have a new method + `getFilteredComment()` which provide access to the comment text without the leading `/*` markers. + +* The method `AbstractCommentRule.tagsIndicesIn()` has been deprecated and will be removed with + PMD 7.0.0. It is not very useful, since it doesn't extract the information + in a useful way. You would still need check, which tags have been found, and with which + data they might be accompanied. + +#### 6.4.0 + +* The following classes in package `net.sourceforge.pmd.benchmark` have been deprecated: `Benchmark`, `Benchmarker`, + `BenchmarkReport`, `BenchmarkResult`, `RuleDuration`, `StringBuilderCR` and `TextReport`. Their API is not supported anymore + and is disconnected from the internals of PMD. Use the newer API based around `TimeTracker` instead, which can be found + in the same package. +* The class `net.sourceforge.pmd.lang.java.xpath.TypeOfFunction` has been deprecated. Use the newer `TypeIsFunction` in the same package. +* The `typeof` methods in `net.sourceforge.pmd.lang.java.xpath.JavaFunctions` have been deprecated. + Use the newer `typeIs` method in the same class instead.. +* The methods `isA`, `isEither` and `isNeither` of `net.sourceforge.pmd.lang.java.typeresolution.TypeHelper`. + Use the new `isExactlyAny` and `isExactlyNone` methods in the same class instead. + +#### 6.2.0 + +* The static method `PMDParameters.transformParametersIntoConfiguration(PMDParameters)` is now deprecated, + for removal in 7.0.0. The new instance method `PMDParameters.toConfiguration()` replaces it. + +* The method `ASTConstructorDeclaration.getParameters()` has been deprecated in favor of the new method + `getFormalParameters()`. This method is available for both `ASTConstructorDeclaration` and + `ASTMethodDeclaration`. + +#### 6.1.0 + +* The method `getXPathNodeName` is added to the `Node` interface, which removes the +use of the `toString` of a node to get its XPath element name (see [#569](https://github.com/pmd/pmd/issues/569)). + * The default implementation provided in `AbstractNode`, will + be removed with 7.0.0 + * With 7.0.0, the `Node.toString` method will not necessarily provide its XPath node + name anymore. + +* The interface `net.sourceforge.pmd.cpd.Renderer` has been deprecated. A new interface +`net.sourceforge.pmd.cpd.renderer.CPDRenderer` has been introduced to replace it. The main +difference is that the new interface is meant to render directly to a `java.io.Writer` +rather than to a String. This allows to greatly reduce the memory footprint of CPD, as on +large projects, with many duplications, it was causing `OutOfMemoryError`s (see [#795](https://github.com/pmd/pmd/issues/795)). + + `net.sourceforge.pmd.cpd.FileReporter` has also been deprecated as part of this change, as it's no longer needed. + +#### 6.0.1 + +* The constant `net.sourceforge.pmd.PMD.VERSION` has been deprecated and will be removed with PMD 7.0.0. + Please use `net.sourceforge.pmd.PMDVersion.VERSION` instead. + +### List of currently deprecated rules + +* The Java rules {% rule java/codestyle/VariableNamingConventions %}, {% rule java/codestyle/MIsLeadingVariableName %}, + {% rule java/codestyle/SuspiciousConstantFieldName %}, and {% rule java/codestyle/AvoidPrefixingMethodParameters %} are + now deprecated, and will be removed with version 7.0.0. They are replaced by the more general + {% rule java/codestyle/FieldNamingConventions %}, {% rule java/codestyle/FormalParameterNamingConventions %}, and + {% rule java/codestyle/LocalVariableNamingConventions %}. + +* The Java rule {% rule java/codestyle/AbstractNaming %} is deprecated + in favour of {% rule java/codestyle/ClassNamingConventions %}. + +* The Java rules {% rule java/codestyle/WhileLoopsMustUseBraces %}, {% rule java/codestyle/ForLoopMustUseBraces %}, {% rule java/codestyle/IfStmtMustUseBraces %}, and {% rule java/codestyle/IfElseStmtMustUseBraces %} + are deprecated. They will be replaced by the new rule {% rule java/codestyle/ControlStatementBraces %} + +* The Java rules {% rule java/codestyle/NcssConstructorCount %}, {% rule java/codestyle/NcssMethodCount %}, and {% rule java/codestyle/NcssTypeCount %} have been + deprecated. They will be replaced by the new rule {% rule java/design/NcssCount %} in the category `design`. + +* The Java rule `LooseCoupling` in ruleset `java-typeresolution` is deprecated. Use the rule with the same name from category `bestpractices` instead. + +* The Java rule `CloneMethodMustImplementCloneable` in ruleset `java-typeresolution` is deprecated. Use the rule with the same name from category `errorprone` instead. + +* The Java rule `UnusedImports` in ruleset `java-typeresolution` is deprecated. Use the rule with + the same name from category `bestpractices` instead. + +* The Java rule `SignatureDeclareThrowsException` in ruleset `java-typeresolution` is deprecated. Use the rule with the same name from category `design` instead. + +* The Java rule `EmptyStaticInitializer` in ruleset `java-empty` is deprecated. Use the rule {% rule java/errorprone/EmptyInitializer %}, which covers both static and non-static empty initializers.` + +* The Java rules `GuardDebugLogging` (ruleset `java-logging-jakarta-commons`) and `GuardLogStatementJavaUtil` + (ruleset `java-logging-java`) have been deprecated. Use the rule {% rule java/bestpractices/GuardLogStatement %}, which covers all cases regardless of the logging framework. + + + + + + + + + + + + diff --git a/docs/pages/pmd/about/help.md b/docs/pages/pmd/about/help.md new file mode 100644 index 00000000000..9460394cdb5 --- /dev/null +++ b/docs/pages/pmd/about/help.md @@ -0,0 +1,21 @@ +--- +title: Getting Help +permalink: pmd_about_help.html +author: Andreas Dangel +last_updated: September 2017 +--- + +There are numerous ways of getting help: + +* Search for already existing questions on [StackOverflow](https://stackoverflow.com/). + +* If you can't find your problem, post a new question. Don't forget to tag the question with [`pmd`](https://stackoverflow.com/questions/tagged/pmd). + +* If you found a bug, please create a new [github issue](https://github.com/pmd/pmd/issues). + +* You can also ask questions in our [sourceforge forum](https://sourceforge.net/p/pmd/discussion/). + +* Or you can join the [Mailing List](https://lists.sourceforge.net/lists/listinfo/pmd-devel) or browse + through the archives ([archive1](http://java-pmd.30631.n5.nabble.com/), [archive2](http://blog.gmane.org/gmane.comp.java.audit.pmd.devel)). + +* Of course, you can also directly jump to our [source code on github](https://github.com/pmd/pmd). diff --git a/docs/pages/pmd/devdocs/adding_metrics_support_to_language.md b/docs/pages/pmd/devdocs/adding_metrics_support_to_language.md deleted file mode 100644 index 2e14e78412b..00000000000 --- a/docs/pages/pmd/devdocs/adding_metrics_support_to_language.md +++ /dev/null @@ -1,140 +0,0 @@ ---- -title: Adding support for metrics to a language -short_title: Implement a metrics framework -tags: [customizing] -summary: "PMD's Java module has an extensive framework for the calculation of metrics, which allows rule developers -to implement and use new code metrics very simply. Most of the functionality of this framework is abstracted in such -a way that any PMD supported language can implement such a framework without too much trouble. Here's how." -last_updated: August 2017 -permalink: pmd_devdocs_adding_metrics_support_to_language.html -author: Clément Fournier ---- - -{% include warning.html content="WIP, unstable API" %} - -## Internal architecture of the metrics framework - -### Overview of the Java framework - -The framework has several subsystems, the two most easily identifiable being: -* A **project memoizer** (`ProjectMemoizer`). When a metric is computed, it's stored back in this structure and can be -reused later. This - reduces the overhead on the calculation of e.g. aggregate results (`ResultOption` calculations). The contents of - this data structure are indexed with fully qualified names (`JavaQualifiedName`), which must identify unambiguously - classes and methods. - -* The **façade**. The static end-user façade (`JavaMetrics`) is backed by an instance of a `JavaMetricsFaçade`. This - allows us to abstract the functionality of the façade into `pmd-core` for other frameworks to use. The façade - instance contains a project memoizer for the analysed project, and a metrics computer - (`JavaMetricsComputer`). It's this last object which really computes the metric and stores back its result in the - project mirror, while the façade only handles parameters. - -Metrics (`Metric`) plug in to this static system and only provide behaviour that's executed by the metrics computer. -Internally, metric keys (`MetricKey`) are parameterized with their version (`MetricVersion`) to index memoisation -maps (see `ParameterizedMetricKey`). This allows us to memoise several versions of the same metric without conflict. - -{% include important.html content="The following will be moved when multifile analysis and metrics are separated" %} - - -At the very least, a metrics framework has those two components and is just a convenient way to compute and memoize -metrics on a single file. Yet, one of the goals of the metrics framework is to allow for **multi-file analysis**, which -make it possible, for instance, to compute the coupling between two classes. This feature uses two major -components: -* A **project mirror**. This data structure that stores info about all classes and operations (and other relevant - entities, such as fields, packages, etc.) of the analysed project. This is implemented by `PackageStats` in the Java - framework. The role of this structure is to make info about other files available to rules. It's filled by a visitor before rules apply. - - The information stored in this data structure that's accessible to metrics is mainly comprised of method and field - signatures (e.g. `JavaOperationSignature`), which describes concisely the characteristics of the method or field - (roughly, its modifiers). - -* Some kind of method and field **usage resolution**, i.e. some way to find the fully qualified name of a method from a - method call expression node. This is the trickiest part to implement. In Java it depends on type resolution. - -### Abstraction layer - -As you may have seen, most of the functionality of the first two components are abstracted into `pmd-core`. This -allows us to implement new metrics frameworks quite quickly. These abstract components are parameterized by the -node types of the class and operation AST nodes. Moreover, it makes the external behaviour of the framework is very -stable across languages, yet each component can easily be customized by adding methods or overriding existing ones. - -The signature matching aspect is framed by generic interfaces, but it can't really be abstracted more -than that. For instance, the project mirror is very language specific. Java's implementation uses the natural structure -provided by the language's package system to structure the project's content. Apex, on the other, has no package -system and thus can't use the same mechanism. That explains why the interfaces framing the project mirror are very -loose. Their main goal is to provide type safety through generics. - -Moreover, usage resolution depends on the availability of type resolution for the given language, which is only implemented in -Java. For these reasons, signature matching is considered an optional feature of the metrics framework. But despite -this limitation, signature matching still provides a elegant way to find information about the class we're in. This -feature requires no usage resolution and can be used to implement sophisticated metrics, that already give access to -detection strategies. - -## Implementation of a new framework - -### 1. Groundwork - -* Create a class implementing `QualifiedName`. This implementation must be tailored to the target language so - that it can indentify unambiguously any class and operation in the analysed project. You - must implement `equals`, `hashCode` and `toString`. - [Example](https://github.com/pmd/pmd/blob/52d78d2fa97913cf73814d0307a1c1ae6125a437/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/JavaQualifiedName.java) -* Determine the AST nodes that correspond to class and method declaration in your language. These types are - referred hereafter as `T` and `O`, respectively. Both these types must implement the interface `QualifiableNode`, - which means they must expose a `getQualifiedName` method to give access to their qualified name. - -### 2. Implement and wire the project memoizer -* Create a class extending `BasicProjectMemoizer`. There's no abstract functionality to implement. - [Example](https://github.com/pmd/pmd/blob/52d78d2fa97913cf73814d0307a1c1ae6125a437/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/JavaProjectMemoizer.java) -* Create an AST visitor that fills the project memoizer with memoizers. For that, you use `BasicProjectMemoizer`'s - `addClassMemoizer` and `addOperationMemoizer` methods with a qualified name. - [Example](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/JavaMetricsVisitor.java) -* Create a façade class for your visitor. This class extends a `*ParserVisitorAdapter` class and only overrides the - `initializeWith(Node)` method. It's supposed to make your real visitor accept the node in parameter. - [Example](https://github.com/pmd/pmd/blob/52d78d2fa97913cf73814d0307a1c1ae6125a437/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/JavaMetricsVisitorFacade.java) -* Override the `getMetricsVisitorFacade()` method in your language's handler (e.g. `ApexHandler`). This method gives - back a `VisitorStarter` which initializes your façade with a `Node`. - [Example](https://github.com/pmd/pmd/blob/52d78d2fa97913cf73814d0307a1c1ae6125a437/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/AbstractJavaHandler.java#L100-L108) -* Your project memoizer should now get filled when the `metrics` attribute is set to `true` in the rule XML. - -### 3. Implement the façade -* Create a class extending `AbstractMetricsComputer`. This object will be responsible for calculating metrics - given a memoizer, a node and info about the metric. Typically, this object is stateless so you might as well make it - a singleton. - [Example](https://github.com/pmd/pmd/blob/52d78d2fa97913cf73814d0307a1c1ae6125a437/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/JavaMetricsComputer.java) -* Create a class extending `AbstractMetricsFacade`. This class needs a reference to your `ProjectMemoizer` and - your `MetricsComputer`. It backs the real end user façade, and handles user provided parameters before delegating to - your `MetricsComputer`. - [Example](https://github.com/pmd/pmd/blob/52d78d2fa97913cf73814d0307a1c1ae6125a437/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/JavaMetricsFacade.java) -* Create the static façade of your framework. This one has an instance of your `MetricsFaçade` object and delegates - static methods to that instance. - [Example](https://github.com/pmd/pmd/blob/52d78d2fa97913cf73814d0307a1c1ae6125a437/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/JavaMetrics.java) -* Create classes `AbstractOperationMetric` and `AbstractClassMetric`. These must implement `Metric` and - `Metric`, respectively. They typically provide defaults for the `supports` method of each metric. - [Example](https://github.com/pmd/pmd/blob/52d78d2fa97913cf73814d0307a1c1ae6125a437/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/impl/AbstractJavaOperationMetric.java) -* Create enums `ClassMetricKey` and `OperationMetricKey`. These must implement `MetricKey` and `MetricKey`. The - enums list all available metric keys for your language. - [Example](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/api/JavaOperationMetricKey.java) -* Create metrics by extending your base classes, reference them in your enums, and you can start using them with your - façade! - -{% include important.html content="The following section will be moved when multifile analysis and metrics are separated" %} - -### Optional: Signature matching - -You can match the signature of anything: method, field, class, package... It depends on what's useful for you. -Suppose you want to be able to match signatures for nodes of type `N`. What you have to do then is the following: - -* Create a class implementing the interface `Signature`. Signatures describe basic information about the node, -which typically includes most of the modifiers they declare (eg visibility, abstract or virtual, etc.). -It's up to you to define the right level of detail, depending on the accuracy of the pattern matching required. -* Make type `N` implement `SignedNode`. This makes the node capable of giving its signature. Factory methods to -build a `Signature` from a `N` are a good idea. -* Create signature masks. A mask is an object that matches some signatures based on their features. For example, with - the Java framework, you can build a `JavaOperationSigMask` that matches all method signatures with visibility - `public`. A sigmask implements `SigMask`, where `S` is the type of signature your mask handles. -* Typically, the project mirror stores the signatures, so you have to implement it in a way that makes it possible to - associate a signature with the qualified name of its node. -* If you want to implement signature matching, create an `AbstractMetric` class, which gives access to a -`SignatureMatcher` to your metrics. Typically, your implementation of `ProjectMirror` implements a -custom `SignatureMatcher` interface, and your façade can give back its instance of the project mirror. diff --git a/docs/pages/pmd/devdocs/architecture.md b/docs/pages/pmd/devdocs/architecture.md deleted file mode 100644 index 9d4c295bf54..00000000000 --- a/docs/pages/pmd/devdocs/architecture.md +++ /dev/null @@ -1,8 +0,0 @@ ---- -title: Architecture -sidebar: pmd_sidebar -permalink: pmd_devdocs_architecture.html -folder: pmd/devdocs ---- - -PMD high-level building blocks diff --git a/docs/pages/pmd/devdocs/building.md b/docs/pages/pmd/devdocs/building.md index 35b4e5e676c..c76ed1db04a 100644 --- a/docs/pages/pmd/devdocs/building.md +++ b/docs/pages/pmd/devdocs/building.md @@ -1,37 +1,42 @@ --- title: Building PMD from source +tags: [devdocs] permalink: pmd_devdocs_building.html author: Tom Copeland, Xavier Le Vourch --- + +{%include note.html content="TODO add IDE specific indications" %} + # Compiling PMD -* JDK 1.7 or higher -* [Apache Maven](http://maven.apache.org) 3 or later. +* JDK 10 or higher + +{% include note.html content="While Java 10 is required for building, running PMD only requires Java 7 (or Java 8 for Apex and the Designer)." %} You’ll need to either check out the source code or download the latest source release. Assuming you’ve got the latest source release, unzip it to a directory: ``` [tom@hal building]$ ls -l total 5716 --rw-rw-r-- 1 tom tom 5837216 Jul 17 13:09 pmd-src-5.5.0.zip -[tom@hal building]$ unzip -q pmd-src-5.5.0.zip +-rw-rw-r-- 1 tom tom 5837216 Jul 17 13:09 pmd-src-{{site.pmd.version}}.zip +[tom@hal building]$ unzip -q pmd-src-{{site.pmd.version}}.zip [tom@hal building]$ ``` Now cd down into the `pmd` directory: ``` -[tom@hal building]$ cd pmd-src-5.5.0 -[tom@hal pmd-src-5.5.0]$ ls -l | grep pom.xml +[tom@hal building]$ cd pmd-src-{{site.pmd.version}} +[tom@hal pmd-src-{{site.pmd.version}}]$ ls -l | grep pom.xml -rw-rw-r-- 1 tom tom 36482 14\. Nov 17:36 pom.xml -[tom@hal pmd-src-5.5.0]$ +[tom@hal pmd-src-{{site.pmd.version}}]$ ``` That’s the project configuration for maven… let’s compile! ``` -[tom@hal pmd-src-5.5.0]$ mvn clean package +[tom@hal pmd-src-{{site.pmd.version}}]$ ./mvnw clean verify [INFO] Scanning for projects... [INFO] ------------------------------------------------------------------------ [INFO] Reactor Build Order: @@ -71,13 +76,16 @@ That’s the project configuration for maven… let’s compile! [INFO] Finished at: 2015-11-14T17:46:06+01:00 [INFO] Final Memory: 63M/765M [INFO] ------------------------------------------------------------------------ -[tom@hal pmd-src-5.5.0]$ +[tom@hal pmd-src-{{site.pmd.version}}]$ ``` -Now the source and binary distribution zip files can be found in the folder pmd-dist/target. +Now the source and binary distribution zip files can be found in the folder `pmd-dist/target`. -Notes: +**Notes:** -* The rules that have already been written are specified in the src/main/resources/rulesets/ directories of the specific languages, e.g. `pmd-java/src/main/resources/rulesets`. They’re also in the jar file that’s included with both the source and binary distributions. +* The rules that have already been written are specified in the `src/main/resources/rulesets/` directories of +the specific languages, e.g. `pmd-java/src/main/resources/rulesets`. +They’re also in the jar file that’s included with both the source and binary distributions. -A paucity of detail, I’m sure you’d agree. If you think this document can be improved, please post [here](http://sourceforge.net/p/pmd/discussion/188192) and let me know how. Thanks! +A paucity of detail, I’m sure you’d agree. If you think this document can be improved, +please post [here](http://sourceforge.net/p/pmd/discussion/188192) and let me know how. Thanks! diff --git a/docs/pages/pmd/devdocs/codestyle.md b/docs/pages/pmd/devdocs/codestyle.md deleted file mode 100644 index af3ef0f886f..00000000000 --- a/docs/pages/pmd/devdocs/codestyle.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -title: Code Style -sidebar: pmd_sidebar -permalink: pmd_devdocs_codestyle.html -folder: pmd/devdocs ---- diff --git a/docs/pages/pmd/devdocs/development.md b/docs/pages/pmd/devdocs/development.md index d3acec6821c..450a47cda29 100644 --- a/docs/pages/pmd/devdocs/development.md +++ b/docs/pages/pmd/devdocs/development.md @@ -1,5 +1,6 @@ --- title: Developer Resources +tags: [devdocs] permalink: pmd_devdocs_development.html last_updated: August 2017 --- diff --git a/docs/pages/pmd/devdocs/how_pmd_works.md b/docs/pages/pmd/devdocs/how_pmd_works.md index a0ea2ddb57f..1ac3f999292 100644 --- a/docs/pages/pmd/devdocs/how_pmd_works.md +++ b/docs/pages/pmd/devdocs/how_pmd_works.md @@ -1,22 +1,45 @@ --- -title: PMD How it Works -tags: [customizing] -summary: How PMD Works -last_updated: July 3, 2016 +title: How PMD Works +tags: [devdocs] +summary: Processing overview of the different steps taken by PMD. +last_updated: September 2017 permalink: pmd_devdocs_how_pmd_works.html -author: Tom Copeland +author: Tom Copeland, Andreas Dangel --- -# How it works +## Overview -PMD checks source code against rules and produces a report. Like this: +The processing starts e.g. with the main class: `net.sourceforge.pmd.PMD` -* Something passes a file name and a RuleSet into PMD. -* PMD hands an InputStream of the source file to a JavaCC-generated parser. -* PMD gets a reference to an Abstract Syntax Tree back from the parser. -* PMD hands the AST off to the symbol table layer which builds scopes, finds declarations, and find usages. -* If any rules need data flow analysis, PMD hands the AST over to the DFA layer for building control flow graphs and data flow nodes. -* Each Rule in the RuleSet gets to traverse the AST and check for problems. The rules can also poke around the symbol table and DFA nodes. -* The Report is now filled with RuleViolations, and those get printed out in XML or HTML or whatever. +{%include note.html content="This is the command line interface. There are many other means, who +PMD can be invoked. E.g. via ant, maven, gradle..." %} -Not much detail here… if you think this document can be improved, please post [here](http://sourceforge.net/p/pmd/discussion/188192) and let us know how. Thanks! +* Parse command line parameters (see net.sourceforge.pmd.cli.PMDParameters) + Also load the incremental analysis cache file +* Load rulesets/rules +* Determine languages (rules of different languages might be mixed in rulesets) +* Determine files (uses the given source directory, filter by the language's file extensions) +* Prepare the renderer +* Sort the files by name +* Check whether we can use the incremental analysis cache (if the rulesets changed, it will be invalid) +* Prepare the SourceCodeProcessor based on the configuration +* Analyze the files. Either single threaded or multi-threaded parallel. This task is encapsulated + in `net.sourceforge.pmd.processor.PMDRunnable`: + * Create input stream + * Call source code processor (`net.sourceforge.pmd.SourceCodeProcessor`): + 1. Determine the language + 2. Check whether the file is already analyzed and a result is available from the analysis cache + 3. Parse the source code. Result is the root AST node. + 4. Always run the SymbolFacade visitor. It builds scopes, finds declarations and usages. + 5. Run DFA (data flow analysis) visitor (if at least one rule requires it) for building + control flow graphs and data flow nodes. + 6. Run TypeResolution visitor (if at least one rule requires it) + 7. FUTURE: Run multifile analysis (if at least one rule requires it) + 8. Execute the rules: + * First run the rules that opted in for the rule chain mechanism + * Run all the other rules and let them traverse the AST. The rules can use the symbol table, + type resolution information and DFA nodes. + * The rules will report found problems as RuleViolations. +* Render the found violations into the wanted format (XML, text, HTML, ...) +* Store the incremental analysis cache +* Depending on the number of violations found, exit with code 0 or 4. diff --git a/docs/pages/pmd/devdocs/info_for_developers.md b/docs/pages/pmd/devdocs/info_for_developers.md deleted file mode 100644 index 425db6a2c4b..00000000000 --- a/docs/pages/pmd/devdocs/info_for_developers.md +++ /dev/null @@ -1,183 +0,0 @@ ---- -title: PMD Info for Developers -short_title: Info for Developers -tags: [customizing] -summary: Info for PMD Developers -last_updated: July 3, 2016 -permalink: pmd_devdocs_info_for_developers.html -author: Hooper Bloob , Romain Pelisse ---- - -# Information for PMD Developers - -Some piece of information handy to PMD developers. Clearly not everything, but some useful bits :) - -## PMD Properties - -The new PMD properties subsystem is intended to bring some rigor and expanded functionality to the wild world of rule properies. It defines a value type template that can be used by IDE plugins to enumerate the properties specified by individual rules and provides validation and serialization services for multi-value properties. It uses custom serialization routines to generate human-readable values that can be edited in the XML files. - -The subsystem implements the following property constructors with the leading name and description arguments not shown: - -1. BooleanProperty(…, boolean defaultValue, float uiOrder) -2. BooleanProperty(…, boolean[] defaultValues, uiOrder, maxValues) BooleanProperty(…, Boolean[] defaultValues, float uiOrder, int maxValues) -3. IntegerProperty(…, int defaultValue, float uiOrder) -4. IntegerProperty(…, int[]defaultValues, float uiOrder, int maxValues) IntegerProperty(…, Integer[] defaultValues, float uiOrder, int maxValues) -5. FloatProperty -6. FloatProperty(…, float defaultValue, float uiOrder) -7. FloatProperty(…, float[]defaultValues, float uiOrder, int maxValues) FloatProperty(…, Float[] defaultValues, float uiOrder, int maxValues) -8. DoubleProperty -9. DoubleProperty(…, int defaultValue, float uiOrder) -10. DoubleProperty (…, double[]defaultValues, float uiOrder, int maxValues) DoubleProperty (…, Double[] defaultValues, float uiOrder, int maxValues) -11. StringProperty -12. StringProperty(…, String defaultValue, float uiOrder) -13. StringProperty(…, String[] defaultValues, float uiOrder, char delimiter) - -The delimiter character is used in the serialized string and cannot be part of the property value strings. - -1. TypeProperty(…, Class defaultValue, float uiOrder) -2. TypeProperty(…, Class[]defaultValues, float uiOrder) - ->(PMD doesn’t currently support full type resolution at the moment) - -1. CharacterProperty(…, char defaultValue, float uiOrder) -2. CharacterProperty(…, char[] defaultValues, float uiOrder, char delimiter) -3. CharacterProperty(…, Character[] defaultValues, float uiOrder, char delimiter) -4. CharacterProperty(…, String defaultValues, float uiOrder, char delimiter) - -The delimiter character is used in the serialized string and cannot be part of the property value characters. - -1. EnumeratedProperty(…, Object[][] values, float uiOrder) -2. EnumeratedProperty(…, Object[][] values, float uiOrder, int maxValues) - -The 2D value array holds the label-value tuples in the order that they should be presented in the UI widget. See usage below. - -## For Rule Developers - -All rule properties need to be characterized via individual PropertyDescriptors so that they can be viewed and adjusted the IDE plugin users. Since the descriptors never change at runtime we only need one of each so we create them as static singletons within the rule class definition. The following rule usage example makes use of a pair of integer properties: - -```java -public MyVarNameLengthRule extends AbstractRule() { - -private static final PropertyDescriptor minVarNameLength = - new IntegerProperty( - "minVarNameLength", - "Minimum length for variable names", - 3, - 1.0f - ); - -private static final PropertyDescriptor maxVarNameLength = - new IntegerProperty( - "maxVarNameLength", - "Maximum length for variable names", - 30, - 1.1f - ); - -private static final Map propertyDescriptorsByName = asFixedMap( - new PropertyDescriptor[] { minVarNameLength, maxVarNameLength } -); - -public MyVarNameLengthRule() { }; - -protected Map propertiesByName() { - return propertyDescriptorsByName; -}; - -// rule body methods... -} -``` - -All property descriptors must be returned via the propertiesByName() method for each rule class. - -Properties can also be multi-valued, that is, we can capture and define a set of them at once: - -```java -private static final PropertyDescriptor booleanPrefixes = - new StringProperty( - "booleanPrefixes", - "Legal prefixes to use for boolean field names", - new String[] { "is", "has", "can" }, - 1.0f, - '|' // reserved as delimiter - ); -``` - -There are at least two constructors for each property type, one that limits the property to a single value and another that accepts more than one. - -In addition to the regular Java types such as Boolean, Integer, Float, Character, String, and Class/Type values you can also allow your rule users to pick between complex mixed datatypes such as maps or graphs that you define at compilation time: - -```java -private static final Object[][] mixedItems = new Object[][] { - {"map", new HashMap()}, - {"emptyArray", new Object[0]}, - {"list", new ArrayList()}, - {"string", "Hello World!"}, -}; - -private static final PropertyDescriptor sampleObjects = - new EnumeratedProperty( - "testEnumerations", - "Test enumerations with complex types", - mixedItems, - 1.0f - ); -``` - -Note that Java values held by the EnumeratedProperty are not written out as property values themselves, we just write out the labels they are associated with. Specifying a label in the XML file for an object that doesn’t exist will result in an IllegalArgumentException. - -## XML values - -Defining the property rules within the ruleset XML files is straightforward for single values: - -```xml - - - -``` - -When specifying multiple values you will need to separate them using the delimiter held by the property descriptor, most commonly a single pipe character, `|`: - -```xml - - - -``` - -You can define your own datatypes by implementing a subclass of AbstractPMDProperty and implementing the serialization, and validation routines listed in the PMDProperty interface. Just ensure that you create a corresponding JUnit test in the test.net.sourceforge.pmd.properties package to go along with it. - -One of the implementation goals in this system is to try and come up with property constructors sufficiently useful that we don’t need to assemble them within static blocks. A single statement should be enough to build a rule property descriptor. - -## For IDE Plugin Developers - -In order to assemble an effective UI to manage the rule properties the following setup sequence is suggested: - -* Determine whether the value is open-ended or enumerated. For example, can the user type in their own values or should they pick them from a list? Invoke the choices() method that may return a 2D array of label-value pairs you can use to populate your list widget. If the method returns null then jump to step #2. - - You may need to maintain a mapping of the label-value pairs to translate between them depending on the capabilities of your list widget. The first pair in the array represents the default property value. - -* For open-ended values, determine the cardinality of the property via the maxValueCount() method. If it returns a value of one then use the type() method to determine the type-specific entry field could use next. I.e. a checkbox for booleans, text field for strings, spin buttons for ints, etc. - -* If the property supports more than one value then you may opt to use a single text field and parse the entries after the user leaves or hits the return key or you can create a read-only widget and add/remove values via a popup dialog. - -* All multi-value properties make use of a character to delimit the values in their serialized form so you will need to ensure that you prevent the user from entering values containing it. Retrieve the delimiter via the multiValueDelimiter() method. - -You can use the errorFor(value) method to validate the values entered by the user or check the values held by the rule configuration file. It returns null or an error message as appropriate. It would be best to flag and disable rules that have invalid property values. - -Use the defaultValue() method to reset the rule properties to their default value. - -The two serialization methods, valueFrom() and asDelimitedString(), are to be used to retrive and store property values respectively. - -Widgets should be ordered vertically according to the values returned by the uiOrder() method with lower-valued properties appearing above the ones with higher values. The order of the property descriptors returned from the rule cannot be guaranteed to be the same as the presentation order. If the two or more widgets share the same integer value then you can use the fractional portions to place their widgets in a horizontal sequence (if possible). - -For types that can have null values, such as strings, then use the isRequired() method to flag any possible missing values. - -If a property field is multi-valued then the maximum number of values it can hold is set to largest possible int value unless set explicitly in a rule property constructor. - -## ToDo items - -1. Expand this note with further examples -2. Internationalize error strings in the code -3. Provide for additional datatypes such as Date -4. Figure out the best way to add the rowCount value to the StringProperty constructor diff --git a/docs/pages/pmd/devdocs/adding_new_cpd_language.md b/docs/pages/pmd/devdocs/major_contributions/adding_new_cpd_language.md similarity index 75% rename from docs/pages/pmd/devdocs/adding_new_cpd_language.md rename to docs/pages/pmd/devdocs/major_contributions/adding_new_cpd_language.md index 7286b5ce57e..0cc1127ca48 100644 --- a/docs/pages/pmd/devdocs/adding_new_cpd_language.md +++ b/docs/pages/pmd/devdocs/major_contributions/adding_new_cpd_language.md @@ -1,15 +1,13 @@ --- -title: PMD How to Add a New CPD Language -short_title: Add a New CPD Language -tags: [customizing] -summary: How to Add a New CPD Language +title: How to add a new CPD language +short_title: Add a new CPD language +tags: [devdocs, extending] +summary: How to add a new CPD language last_updated: July 3, 2016 -permalink: pmd_devdocs_adding_new_cpd_language.html +permalink: pmd_devdocs_major_adding_new_cpd_language.html author: Romain PELISSE --- -# How to Add a New Language to CPD - If you wish CPD to parse a unsupported language, you can easily develop a new parser for CPD. All you need to is implements the following interface: * net.sourceforge.pmd.cpd.Language diff --git a/docs/pages/pmd/devdocs/adding_new_language.md b/docs/pages/pmd/devdocs/major_contributions/adding_new_language.md similarity index 95% rename from docs/pages/pmd/devdocs/adding_new_language.md rename to docs/pages/pmd/devdocs/major_contributions/adding_new_language.md index 040adb69c6f..394e7b151e0 100644 --- a/docs/pages/pmd/devdocs/adding_new_language.md +++ b/docs/pages/pmd/devdocs/major_contributions/adding_new_language.md @@ -1,15 +1,14 @@ --- -title: PMD Adding a New Language -short_title: Adding a New Language -tags: [customizing] -summary: Adding a New Language to PMD +title: Adding PMD support for a new language +short_title: Adding a new language +tags: [devdocs, extending] +summary: "How to add a new language to PMD." last_updated: July 3, 2016 sidebar: pmd_sidebar -permalink: pmd_devdocs_adding_new_language.html +permalink: pmd_devdocs_major_adding_new_language.html folder: pmd/devdocs --- -# How to Add a New Language to PMD ## 1. Start with a new sub-module. * See pmd-java or pmd-vm for examples. @@ -75,7 +74,7 @@ folder: pmd/devdocs * All other rules for your language should extend this class. The purpose of this class is to implement visit methods for all AST types to simply delegate to default behavior. This is useful because most rules care only about specific AST nodes, but PMD needs to know what to do with each node - so this just lets you use default behavior for nodes you don’t care about. ## 13. Create rules -* Rules are ceated by extending the abstract rule class created in step 12 *(see `EmptyForeachStmtRule` for example)* +* Rules are created by extending the abstract rule class created in step 12 *(see `EmptyForeachStmtRule` for example)* * Creating rules is already pretty well documented in PMD - and it’s no different for a new language, except you may have different AST nodes. ## 14. Test the rules diff --git a/docs/pages/pmd/devdocs/major_contributions/adding_new_metrics_framework.md b/docs/pages/pmd/devdocs/major_contributions/adding_new_metrics_framework.md new file mode 100644 index 00000000000..4aac437ffdc --- /dev/null +++ b/docs/pages/pmd/devdocs/major_contributions/adding_new_metrics_framework.md @@ -0,0 +1,104 @@ +--- +title: Adding support for metrics to a language +short_title: Implement a metrics framework +tags: [devdocs, extending, metrics] +summary: "PMD's Java module has an extensive framework for the calculation of metrics, which allows rule developers +to implement and use new code metrics very simply. Most of the functionality of this framework is abstracted in such +a way that any PMD supported language can implement such a framework without too much trouble. Here's how." +last_updated: December 2017 +permalink: pmd_devdocs_major_adding_new_metrics_framework.html +author: Clément Fournier +--- + + +## Internal architecture of the metrics framework + +### Overview of the Java framework + +The framework has several subsystems, the two most easily identifiable being: +* A **project memoizer** (`ProjectMemoizer`). When a metric is computed, it's stored back in this structure and can be +reused later. This + reduces the overhead on the calculation of e.g. aggregate results (`ResultOption` calculations). The contents of + this data structure are indexed with fully qualified names (`JavaQualifiedName`), which must identify unambiguously + classes and methods. + +* The **façade**. The static end-user façade (`JavaMetrics`) is backed by an instance of a `JavaMetricsFaçade`. This + allows us to abstract the functionality of the façade into `pmd-core` for other frameworks to use. The façade + instance contains a project memoizer for the analysed project, and a metrics computer + (`JavaMetricsComputer`). It's this last object which really computes the metric and stores back its result in the + project mirror, while the façade only handles parameters. + +Metrics (`Metric`) plug in to this static system and only provide behaviour that's executed by the metrics computer. +Internally, metric keys (`MetricKey`) are parameterized with their version (`MetricVersion`) to index memoisation +maps (see `ParameterizedMetricKey`). This allows us to memoise several versions of the same metric without conflict. + +At the very least, a metrics framework has those two components and is just a convenient way to compute and memoize +metrics on a single file. The expressive power of metrics can be improved by implementing *signature matching* capabilities, +which allows a metric to count signatures matching a specific pattern (a mask) over a whole class. This was originally +designed to work across files, given a working usage resolution. However, making that work with incremental analysis is +harder than it looks, and has been rescheduled to another project. + + +### Abstraction layer + +As you may have seen, most of the functionality of the first two components are abstracted into `pmd-core`. This +allows us to implement new metrics frameworks quite quickly. These abstract components are parameterized by the +node types of the class and operation AST nodes. Moreover, it makes the external behaviour of the framework very +stable across languages, yet each component can easily be customized by adding methods or overriding existing ones. + +The signature matching aspect is framed by generic interfaces, but it can't really be abstracted more +than that. The info given in the signatures is usually very language specific, as it includes info about e.g. +visibility modifiers. So more work is required to implement that, but it can already be used to implement +sophisticated metrics, that already give access to detection strategies. + +## Implementation of a new framework + +### 1. Groundwork + +* Create a class implementing `QualifiedName`. This implementation must be tailored to the target language so + that it can indentify unambiguously any class and operation in the analysed project. You + must implement `equals`, `hashCode` and `toString`. + [Example](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/JavaQualifiedName.java) +* Determine the AST nodes that correspond to class and method declaration in your language. These types are + referred hereafter as `T` and `O`, respectively. Both these types must implement the interface `QualifiableNode`, + which means they must expose a `getQualifiedName` method to give access to their qualified name. + +### 2. Implement the façade +* Create a class extending `AbstractMetricsComputer`. This object will be responsible for calculating metrics + given a memoizer, a node and info about the metric. Typically, this object is stateless so you might as well make it + a singleton. +* Create a class extending `BasicProjectMemoizer`. There's no abstract functionality to implement. + [Example](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/JavaProjectMemoizer.java) + [Example](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/JavaMetricsComputer.java) +* Create a class extending `AbstractMetricsFacade`. This class needs a reference to your `ProjectMemoizer` and + your `MetricsComputer`. It backs the real end user façade, and handles user provided parameters before delegating to + your `MetricsComputer`. + [Example](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/JavaMetricsFacade.java) +* Create the static façade of your framework. This one has an instance of your `MetricsFaçade` object and delegates + static methods to that instance. + [Example](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/JavaMetrics.java) +* Create classes `AbstractOperationMetric` and `AbstractClassMetric`. These must implement `Metric` and + `Metric`, respectively. They typically provide defaults for the `supports` method of each metric. + [Example](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/impl/AbstractJavaOperationMetric.java) +* Create enums `ClassMetricKey` and `OperationMetricKey`. These must implement `MetricKey` and `MetricKey`. The + enums list all available metric keys for your language. + [Example](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/api/JavaOperationMetricKey.java) +* Create metrics by extending your base classes, reference them in your enums, and you can start using them with your + façade! + +### Optional: Signature matching + +You can match the signature of anything: method, field, class, package... It depends on what's useful for you. +Suppose you want to be able to match signatures for nodes of type `N`. What you have to do then is the following: + +* Create a class implementing the interface `Signature`. Signatures describe basic information about the node, +which typically includes most of the modifiers they declare (eg visibility, abstract or virtual, etc.). +It's up to you to define the right level of detail, depending on the accuracy of the pattern matching required. +* Make type `N` implement `SignedNode`. This makes the node capable of giving its signature. Factory methods to +build a `Signature` from a `N` are a good idea. +* Create signature masks. A mask is an object that matches some signatures based on their features. For example, with + the Java framework, you can build a `JavaOperationSigMask` that matches all method signatures with visibility + `public`. A sigmask implements `SigMask`, where `S` is the type of signature your mask handles. +* Create utility methods in your abstract class metric class to count signatures matching a specific mask. +[Example](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/AbstractJavaClassMetric.java#L50) + diff --git a/docs/pages/pmd/devdocs/making_rulesets.md b/docs/pages/pmd/devdocs/making_rulesets.md deleted file mode 100644 index 8ee2c766d68..00000000000 --- a/docs/pages/pmd/devdocs/making_rulesets.md +++ /dev/null @@ -1,134 +0,0 @@ ---- -title: PMD Making Rulesets -tags: [customizing] -summary: Making Custom Rulesets for PMD -last_updated: July 3, 2016 -permalink: pmd_devdocs_making_rulesets.html -author: Tom Copeland ---- - -# How to make a new rule set - -Say you want to pick specific rules from various rule sets and customize them. You can do this by making your own rule set. - -## Create a new ruleset.xml file - -Use one of the current rulesets as an example. Copy and paste it into your new file, delete all the old rules from it, and change the name and description. Like this: - -```xml - - - - This ruleset checks my code for bad stuff - - -```` - -## Add some rule references to it - -After you add these references it’ll look something like this: - -```xml - - - - - This ruleset checks my code for bad stuff - - - - - - - - - - - - - - 2 - - - - - - - - - - - - - - -``` - ->Notice that you can customize individual referenced rules. Everything but the class of the rule can be overridden in your custom ruleset. - -## Excluding rules from a ruleset - -You can also make a custom ruleset that excludes rules, like this: - -```xml - - - Just the braces rules I like - - - - - -``` - -## Excluding files from a ruleset - -You can also exclude certain files from being processed by a ruleset using exclude patterns, with an optional overriding include pattern. A file will be excluded from processing when there is a matching exclude pattern, but no matching include pattern. Path separators in the source file path are normalized to be the ‘/’ character, so the same ruleset can be used on multiple platforms transparently. Additionally, this exclude/include technique works regardless of how PMD is used (e.g. command line, IDE, Ant), making it easier to keep application of your PMD rules consistent throughout your environment. Here is an example: - -```xml - - - My ruleset - .*/some/package/.* - .*/some/other/package/FunkyClassNamePrefix.* - .*/some/package/ButNotThisClass.* - ... - -``` - -## Reference it in your Ant task - -You can specify the full path to your custom ruleset name alongside of the built-in PMD rulesets - like this: - -```xml - - - - - - -``` - -## To see it in your IDE, add it to rulesets/rulesets.properties - -At least, that’s the way some of the IDE plugins do it. Some have other ways of adding custom rulesets. - -## Send us feedback - -If you have suggestions on clarifying this document, please post them to [the forum](http://sourceforge.net/p/pmd/discussion/188192). Thanks! - -Finally, for many more details on building custom rulesets, pick up [PMD Applied](http://pmdapplied.com/)! diff --git a/docs/pages/pmd/devdocs/pmdtester.md b/docs/pages/pmd/devdocs/pmdtester.md new file mode 100644 index 00000000000..407ddd6de57 --- /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/devdocs/pull_requests.md b/docs/pages/pmd/devdocs/pull_requests.md deleted file mode 100644 index 3580a8d757e..00000000000 --- a/docs/pages/pmd/devdocs/pull_requests.md +++ /dev/null @@ -1,173 +0,0 @@ ---- -title: Pull-Request Merge -permalink: pmd_devdocs_pull_requests.html -last_updated: August 2017 -author: Andreas Dangel ---- - -## Contributing via pull requests - -First off, thanks for taking the time to contribute! - -* Please create your pull request against the `master` branch. We will rebase/merge it to the maintenance - branches, if necessary. Just fork the [pmd repo](https://github.com/pmd/pmd/) and - create a [pull request](https://github.com/pmd/pmd/pulls). - -* We are using [checkstyle](http://checkstyle.sourceforge.net/) to enforce a common code style. - The check is integrated into the default build - so, make sure, you can [build PMD](pmd_devdocs_building.html) - without errors. - - See [code style](pmd_devdocs_codestyle.html) for more info. - -## Merging pull requests - -### Example 1: Merging PR #123 into master - -1. Review the pull request - - * Compilation and checkstyle is verified already by travis build: PRs are automatically checked. - * If it is a bug fix, a new unit test, that reproduces the bug, is mandatory. Without such a test, we might accidentally reintroduce the bug again. - * Add the appropriate labels on the github issue: If the PR fixes a bug, the label "a:bug" should be used. - * Make sure, the PR is added to the appropriate milestone. If the PR fixes a bug, make sure, that the bug issue is added to the same milestone. - -2. The actual merge commands: - - We assume, that the PR has been created from the master branch. If this is not the case, - then we'll either need to rebase or ask for rebasing before merging. - - ``` - git checkout master && git pull origin master # make sure, you have the latest code - git fetch origin pull/123/head:pr-123 && git checkout pr-123 # creates a new temporary branch - ``` - -3. Update the [release notes](https://github.com/pmd/pmd/blob/master/docs/pages/release_notes.md): - - * Are there any API changes, that need to be documented? (Section "API Changes") - * Are there any significant changes to existing rules, that should be mentioned? - (Section "Modified Rules" / "New Rules" / "Removed Rules") - * If the PR fixes a bug, make sure, it is listed under the section "Fixed Issues". - * In any case, add the PR to the section "External Contributions" - * Commit these changes with the message: - - git add docs/pages/release_notes.md - git commit -m "Update release notes, refs #123" - - {% include note.html content="If the PR fixes a bug, verify, that we have a commit with the message - \"Fixes #issue-number\". If this doesn't exist, you can add it to the commit message when - updating the release notes: `Update release notes, refs #123, fixes #issue-number`. - This will automatically close the github issue." %} - -4. Now merge the pull request into the master branch: - - ``` - git checkout master - git merge --no-ff pr-123 - ``` - - {%include note.html content="If there are merge conflicts, you'll need to deal with them here." %} - -5. Run the complete build: `./mvnw clean verify` - - {% include note.html content="This will execute all the unit tests and the checkstyle tests. It ensures, - that the complete project can be build and is functioning on top of the current master." %} - -6. If the build was successful, you are ready to push: - - ``` - git push origin master - ``` - - Since the temporary branch is now not needed anymore, you can delete it: - `git branch -d pr-123`. - - -### Example 2: Merging PR #124 into a maintenance branch - -We ask, to create every pull request against master, to make it easier to contribute. -But if a pull request is intended to fix a bug in an older version of PMD, then we need to backport this pull request. - -#### Creating a maintenance branch - -For older versions, we use maintenance branches, like `pmd/5.8.x`. If there is no maintenance branch for -the specific version, then we'll have to create it first. Let's say, we want a maintenance branch for -PMD version 5.8.0, so that we can create a bugfix release 5.8.1. - -1. We'll simply create a new branch off of the release tag: - - ``` - git branch pmd/5.8.x pmd_releases/5.8.0 && git checkout pmd/5.8.x - ``` - -2. Now we'll need to adjust the version, since it's currently the same as the release version. - We'll change the version to the next patch version: "5.8.1-SNAPSHOT". - - ``` - ./mvnw versions:set -DnewVersion=5.8.1-SNAPSHOT - git add pom.xml \*/pom.xml - git commit -m "prepare next version 5.8.1-SNAPSHOT" - ``` - -#### Merging the PR - -1. As above: Review the PR - -2. Fetch the PR and rebase it onto the maintenance branch: - - ``` - git fetch origin pull/124/head:pr-124 && git checkout pr-124 # creates a new temporary branch - git rebase master --onto pmd/5.8.x - ./mvnw clean verify # make sure, everything works after the rebase - ``` - - {%include note.html content="You might need to fix conflicts / backport the commits for the older - PMD version." %} - -3. Update the release notes. See above for details. - -4. Now merge the pull request into the maintenance branch: - - ``` - git checkout pmd/5.8.x - git merge --no-ff pr-124 - ``` - -5. Just to be sure, run the complete build again: `./mvnw clean verify`. - -6. If the build was successful, you are ready to push: - - ``` - git push origin pmd/5.8.x - ``` - -7. Since we have rebased the pull request, it won't appear as merged on github. - You need to manually close the pull request. Leave a comment, that it has been - rebased onto the maintenance branch. - -#### Merging into master - -Now the PR has been merged into the maintenance branch, but it is missing in any later version of PMD. -Therefore, we merge first into the next minor version maintenance branch (if existing): - - git checkout pmd/5.9.x - git merge pmd/5.8.x - -After that, we merge the changes into the master branch: - - git checkout master - git merge pmd/5.9.x - -{%include note.html content="This ensures, that every change on the maintenance branch eventually ends -up in the master branch and therefore in any future version of PMD.
-The downside is however, that there are inevitable merge conflicts for the maven `pom.xml` files, since -every branch changed the version number differently.
-We could avoid this by merging only the temporary branch \"pr-124\" into each maintenance branch and -eventually into master, with the risk of missing single commits in a maintenance branch, that have been -done outside the temporary branch." %} - -#### Merging vs. Cherry-Picking - -We are not using cherry-picking, so that each fix is represented by a single commit. -Cherry-picking would duplicate the commit and you can't see in the log, on which branches the fix has been -integrated (e.g. gitk and github show the branches, from which the specific commit is reachable). - -The downside is a more complex history - the maintenance branches and master branch are "connected" and not separate. diff --git a/docs/pages/pmd/devdocs/roadmap.md b/docs/pages/pmd/devdocs/roadmap.md index ee08fcc41a2..362dffddf96 100644 --- a/docs/pages/pmd/devdocs/roadmap.md +++ b/docs/pages/pmd/devdocs/roadmap.md @@ -1,12 +1,15 @@ --- title: Roadmap +tags: [devdocs] permalink: pmd_devdocs_roadmap.html author: > Tom Copeland , Ryan Gustavson, Romain Pelisse , Juan Martín Sotuyo Dodero , Andreas Dangel --- +TODO: +* Update * Future direction * projects, plans * Google Summer of Code diff --git a/docs/pages/pmd/devdocs/setting_up_ide.md b/docs/pages/pmd/devdocs/setting_up_ide.md deleted file mode 100644 index 72287eeda4e..00000000000 --- a/docs/pages/pmd/devdocs/setting_up_ide.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -title: Setting up your IDE -sidebar: pmd_sidebar -permalink: pmd_devdocs_setting_up_ide.html -folder: pmd/devdocs ---- diff --git a/docs/pages/pmd/devdocs/testing.md b/docs/pages/pmd/devdocs/testing.md deleted file mode 100644 index 395e8048ab0..00000000000 --- a/docs/pages/pmd/devdocs/testing.md +++ /dev/null @@ -1,267 +0,0 @@ ---- -title: Testing / Test Framework -permalink: pmd_devdocs_testing.html -summary: "PMD provides a simple test framework for unit testing rules" -last_updated: September 2017 -author: Andreas Dangel ---- - -## Introduction - -Good rules have tests. At least a positive test case - a code example, that triggers the rule and reports -a violation - and a negative test case - a code example, that doesn't trigger the rule - should be created. -Of course, the more tests, the better the rule is verified. If the rule is more complex or defines properties, -with which the behavior can be modified, then these different cases can also be tested. - -And if there is a bug fix for a rule, be it a false positive or a false negative case, should be accompanied -with an additional test case, so that the bug is not accidentally reintroduced later on. - -## How it works - -PMD's built-in rules are organized in rulesets, such as "java-basic". Each ruleset has a single test class, -which executes all the test cases for all rules in this ruleset. The actual test cases are stored in separate -XML files, for each rule a separate file is used. - -The test class subclasses `net.sourceforge.pmd.testframework.SimpleAggregatorTst`, which provides the seamless -integration with JUnit. You basically tell the framework, which rules should be tested and it searches -the test code on its own. - -The test code (see below [Test XML Reference](#test-xml-reference)) describes the test case completely with -the expected behavior like number of expected rule violations, where the violations are expected, and so on. - -When you are running the test class in your IDE (e.g. Eclipse or IntelliJ IDEA) you can also select a single -test case and just execute this one. - -## Where to place the test code - -The `SimpleAggregatorTst` class searches the XML file, that describes the test cases for a certain rule -using the following convention: -The XML file is a test resource, so it is searched in the tree under `src/test/resources`. - -The sub package `xml` of the test class's package should contain a file with the same name as the rule's name -which is under test. - -For example, to test the "Java Basic Ruleset", the fully qualified test class is: - - net.sourceforge.pmd.lang.java.rule.basic.BasicRulesTest - -The test code for the rule "AvoidBranchingStatementAsLastInLoop" can be found in the file: - - src/test/resources/net/sourceforge/pmd/lang/java/rule/basic/xml/AvoidBranchingStatementAsLastInLoop.xml - -In general, the class name and file name pattern for the test class and data is this: - - net.sourceforge.pmd.lang..rule..RulesTest - src/test/resources/net/sourceforge/pmd/lang//rule//xml/.xml - -{%include tip.html content="This convention allows you to quickly find the test cases for a given rule: -Just search in the project for a file `.xml`. Looking at the path of the file, you can figure -out the ruleset name. Searching for a class `RulesTest` gives you the test class." %} - -## Simple example - -### Test Class: BasicRulesTest - -This is a stripped down example for the Java Basic Ruleset: - -``` java -package net.sourceforge.pmd.lang.java.rule.basic; - -import net.sourceforge.pmd.testframework.SimpleAggregatorTst; - -public class BasicRulesTest extends SimpleAggregatorTst { - - private static final String RULESET = "java-basic"; - - @Override - public void setUp() { - addRule(RULESET, "AvoidBranchingStatementAsLastInLoop"); - addRule(RULESET, "AvoidDecimalLiteralsInBigDecimalConstructor"); - } -} -``` - -This test class overrides the method `setUp` in order to register test cases for the two rules. If there -are more rules, just add additional `addRule(...)` calls. - -{%include note.html content="The `RULESET` constant points to `java-basic` which is just a shortcut -for `rulesets/java/basic.xml`. " %} - -{%include note.html content="You can also add additionally standard JUnit test methods annotated with `@Test` to -this test class." %} - -### Test Data: AvoidBranchingStatementAsLastInLoop.xml - -This is a stripped down example which just contains two test cases. - -``` xml - - - - ok: no violations - 0 - - - - - violations: return:for - 1 - 4 - - - -``` - -Each test case is in an own `` element. The first defines 0 expected problems, means this code doesn't -trigger the rule. The second test case expects 1 problem. Since the rule violations also report the exact AST node, -you can verify the line number, too. - -## Test XML Reference - -The root element is ``. It can contain one or more `` and `` elements. -Each `` element defines a single test case. `` elements are used to share code snippets -between different test cases. - -{%include note.html content="The XML schema is available at [rule-tests.xsd](https://github.com/pmd/pmd/blob/master/pmd-test/src/main/resources/rule-tests_1_0_0.xsd)." %} - -### `` attributes - -The `` elements understands three optional attributes: - -* **reinitializeRule**: By default, it's `true`, so each test case starts with a fresh instantiated rule. Set it - to `false` to reproduce cases, where the previous run has influences. - -* **regressionTest**: By default, it's `true`. Set it to `false`, to ignore and skip a test case. - -* **useAuxClasspath**: By default, it's `true`. Set it to `false` to reproduce issues which only - appear without type resolution. - -### `` children - -* **``**: Short description of the test case. This will be the JUnit test name in the report. - If applicable, this description should contain a reference to the bug number, this test case reproduces. - -* **``**: Optional rule properties, if the rule is configurable. Just add multiple elements, to - set multiple properties for one test case. For an example, see below. - -* **``**: The the raw number of expected rule violations, that this rule is expected to report. - For false-positive test cases, this is always "0". For false-negative test cases, it can be any positive number. - -* **``**: Optional element. It's a comma separated list of line numbers. - If there are rule violations reported, then this allows you to - assert the line numbers. Useful if multiple violations should be detected and to be sure that - false positives and negatives don't erase each other. - -* **``**: Optional element, with `` elements as children. - Can be used to validate the correct error message, e.g. if the error message references a variable name. - -* **``**: Either the `` element or the `` element is required. It provides the actual code - snippet on which the rule is executed. The code itself is usually wrapped in a "CDATA" section, so that no - further XML escapes (entity references such as &lt;) are necessary. - -* **``**: Alternative to ``. References a `` defined earlier in the file. - This allows you to share the same code snippet with several test cases. The attribute `id` must match the - id of the references code fragment. - -* **``**: Optional element that specifies the source code language. This defines the parser that - is used for parsing the code snippet. If not given, **java** is used as default. - -### `` - -The code fragment has just one required attribute: **id**. This is used to reference it via a `` element -inside a ``. Similar like the `` element, the content of `` is usually wrapped -in a "CDATA" section, so that no further XML escapes (entity references such as &lt;) are necessary. - -### Complete XML example - -``` xml - - - - Just a description, will be used as the test name for JUnit in the reports - propValue - 2 - 5,14 - - Violation message 1 - Violation message 2 - - - apex - - - - - test case using a code fragment - 0 - - - -``` - -## Using the test framework externally - -It is also possible to use the test framework for custom rules developed outside the PMD source base. -Therefore you just need to reference the dependency `net.sourceforge.pmd:pmd-test`. - -For maven, you can use this snippet: - - - net.sourceforge.pmd - pmd-test - {{site.pmd.version}} - test - - -Then proceed as described earlier: create your test class, create your test cases and run the unit test. - -## How the test framework is implemented - -The framework uses a custom JUnit test runner under the hood, among a couple of utility classes: - -* `SimpleAggregatorTst`: This is the base class for the test classes and defines the custom JUnit test runner. - It itself is a subclass of `RuleTst`. - -* `RuleTst`: contains the logic to parse the XML files and provide a list of `TestDescriptor`s. Each test descriptor - describes a single test case. It also contains the logic to execute such a test descriptor and assert the results. - -* `PMDTestRunner`: A custom JUnit test runner, that combines two separate test runners: The custom `RuleTestRunner` - and the standard `JUnit4` test runner. This combination allows you to add additional standard unit test methods - annotated with `@Test` to your test class. - - *Note:* Since the test class is executed through two test runners, it is actually instantiated twice. Be aware - of this, if you do any initialization in the constructor. Also, the static hooks `@BeforeClass` and `@AfterClass` - will be executed twice. - -* `RuleTestRunner`: This test runner executes the test descriptors with the help of `RuleTst`. diff --git a/docs/pages/pmd/devdocs/writing_documentation.md b/docs/pages/pmd/devdocs/writing_documentation.md index 078477f2789..1a02a2a8a32 100644 --- a/docs/pages/pmd/devdocs/writing_documentation.md +++ b/docs/pages/pmd/devdocs/writing_documentation.md @@ -1,5 +1,6 @@ --- -title: Writing Documentation +title: Writing documentation +tags: [devdocs] last_update: August 2017 permalink: pmd_devdocs_writing_documentation.html keywords: documentation, jekyll, markdown @@ -17,13 +18,48 @@ The pages are in general in [Github Flavored Markdown](https://kramdown.gettalon ## Structure -All documentation is stored in the folder `docs/`. This is the folder, that github and the travis-ci scripts -use to render the site. +The documentation sources can be found in two places based on how they are generated: +- the ones that are manually written (like the one you are reading); +- and the ones that are generated automatically from the category files. All the rule documentation +pages are generated that way. -New pages are stored in the different subfolders under `pages`. The folder structure resembles the sidebar structure. +### Handwritten documentation + +All handwritten documentation is stored in the subfolders under `docs/pages`. The folder structure resembles the sidebar structure. Since all pages use a simple *permalink*, in the rendered html pages, all pages are flattened in one directory. This makes it easy to view the documentation also offline. +### Rule documentation + +The categories for a language `%lang%` are located in +`pmd-%lang%/src/main/resources/category/%lang% `. So for Java the categories +can be found under [pmd-java/src/main/resources/category/java](https://github.com/pmd/pmd/tree/master/pmd-java/src/main/resources/category/java). +The XML category files in this directory are transformed during build into markdown pages +describing the rules they contain. These pages are placed under `docs/` like the handwritten +documentation, and are then rendered with Jekyll like the rest of them. The rule documentation +generator is the separate submodule `pmd-doc`. + +Modifying the documentation of a rule should thus not be done on the markdown page, +but directly on the XML `rule` tag corresponding to the rule, in the relevant +category file. + +The XML documentation of rules can contain GitHub flavoured markdown. +Just wrap the markdown inside CDATA section in the xml. CDATA sections preserve +all formatting inside the delimiters, and allow to write code samples without + escaping special xml characters. For example: +``` + + + + + ... + +``` + ## Building There are two ways, to execute jekyll: diff --git a/docs/pages/pmd/languages/apex_metrics_index.md b/docs/pages/pmd/languages/apex_metrics_index.md index 7c909b27baa..6b0b62bf7b4 100644 --- a/docs/pages/pmd/languages/apex_metrics_index.md +++ b/docs/pages/pmd/languages/apex_metrics_index.md @@ -1,6 +1,6 @@ --- title: Index of Apex code metrics -tags: [customizing] +tags: [extending, metrics] summary: "Index of the code metrics available out of the box to Apex rule developers." last_updated: July 20, 2017 permalink: pmd_apex_metrics_index.html diff --git a/docs/pages/pmd/languages/java_metrics_index.md b/docs/pages/pmd/languages/java_metrics_index.md index 6a18564e367..cc5d28e5d06 100644 --- a/docs/pages/pmd/languages/java_metrics_index.md +++ b/docs/pages/pmd/languages/java_metrics_index.md @@ -1,6 +1,6 @@ --- title: Index of Java code metrics -tags: [customizing] +tags: [extending, metrics] summary: "Index of the code metrics available out of the box to Java rule developers." last_updated: July 20, 2017 permalink: pmd_java_metrics_index.html diff --git a/docs/pages/pmd/projectdocs/committers/merging_pull_requests.md b/docs/pages/pmd/projectdocs/committers/merging_pull_requests.md new file mode 100644 index 00000000000..73df32671e4 --- /dev/null +++ b/docs/pages/pmd/projectdocs/committers/merging_pull_requests.md @@ -0,0 +1,157 @@ +--- +title: Merging pull requests +permalink: pmd_projectdocs_committers_merging_pull_requests.html +last_updated: August 2017 +author: Andreas Dangel +--- + +## Example 1: Merging PR #123 into master + +1. Review the pull request + + * Compilation and checkstyle is verified already by travis build: PRs are automatically checked. + * If it is a bug fix, a new unit test, that reproduces the bug, is mandatory. Without such a test, we might accidentally reintroduce the bug again. + * Add the appropriate labels on the github issue: If the PR fixes a bug, the label "a:bug" should be used. + * Make sure, the PR is added to the appropriate milestone. If the PR fixes a bug, make sure, that the bug issue is added to the same milestone. + +2. The actual merge commands: + + We assume, that the PR has been created from the master branch. If this is not the case, + then we'll either need to rebase or ask for rebasing before merging. + + ``` + git checkout master && git pull origin master # make sure, you have the latest code + git fetch origin pull/123/head:pr-123 && git checkout pr-123 # creates a new temporary branch + ``` + +3. Update the [release notes](https://github.com/pmd/pmd/blob/master/docs/pages/release_notes.md): + + * Are there any API changes, that need to be documented? (Section "API Changes") + * Are there any significant changes to existing rules, that should be mentioned? + (Section "Modified Rules" / "New Rules" / "Removed Rules") + * If the PR fixes a bug, make sure, it is listed under the section "Fixed Issues". + * In any case, add the PR to the section "External Contributions" + * Commit these changes with the message: + + git add docs/pages/release_notes.md + git commit -m "Update release notes, refs #123" + + {% include note.html content="If the PR fixes a bug, verify, that we have a commit with the message + \"Fixes #issue-number\". If this doesn't exist, you can add it to the commit message when + updating the release notes: `Update release notes, refs #123, fixes #issue-number`. + This will automatically close the github issue." %} + +4. Now merge the pull request into the master branch: + + ``` + git checkout master + git merge --no-ff pr-123 + ``` + + {%include note.html content="If there are merge conflicts, you'll need to deal with them here." %} + +5. Run the complete build: `./mvnw clean verify` + + {% include note.html content="This will execute all the unit tests and the checkstyle tests. It ensures, + that the complete project can be build and is functioning on top of the current master." %} + +6. If the build was successful, you are ready to push: + + ``` + git push origin master + ``` + + Since the temporary branch is now not needed anymore, you can delete it: + `git branch -d pr-123`. + + +## Example 2: Merging PR #124 into a maintenance branch + +We ask, to create every pull request against master, to make it easier to contribute. +But if a pull request is intended to fix a bug in an older version of PMD, then we need to backport this pull request. + +### Creating a maintenance branch + +For older versions, we use maintenance branches, like `pmd/5.8.x`. If there is no maintenance branch for +the specific version, then we'll have to create it first. Let's say, we want a maintenance branch for +PMD version 5.8.0, so that we can create a bugfix release 5.8.1. + +1. We'll simply create a new branch off of the release tag: + + ``` + git branch pmd/5.8.x pmd_releases/5.8.0 && git checkout pmd/5.8.x + ``` + +2. Now we'll need to adjust the version, since it's currently the same as the release version. + We'll change the version to the next patch version: "5.8.1-SNAPSHOT". + + ``` + ./mvnw versions:set -DnewVersion=5.8.1-SNAPSHOT + git add pom.xml \*/pom.xml + git commit -m "prepare next version 5.8.1-SNAPSHOT" + ``` + +### Merging the PR + +1. As above: Review the PR + +2. Fetch the PR and rebase it onto the maintenance branch: + + ``` + git fetch origin pull/124/head:pr-124 && git checkout pr-124 # creates a new temporary branch + git rebase master --onto pmd/5.8.x + ./mvnw clean verify # make sure, everything works after the rebase + ``` + + {%include note.html content="You might need to fix conflicts / backport the commits for the older + PMD version." %} + +3. Update the release notes. See above for details. + +4. Now merge the pull request into the maintenance branch: + + ``` + git checkout pmd/5.8.x + git merge --no-ff pr-124 + ``` + +5. Just to be sure, run the complete build again: `./mvnw clean verify`. + +6. If the build was successful, you are ready to push: + + ``` + git push origin pmd/5.8.x + ``` + +7. Since we have rebased the pull request, it won't appear as merged on github. + You need to manually close the pull request. Leave a comment, that it has been + rebased onto the maintenance branch. + +### Merging into master + +Now the PR has been merged into the maintenance branch, but it is missing in any later version of PMD. +Therefore, we merge first into the next minor version maintenance branch (if existing): + + git checkout pmd/5.9.x + git merge pmd/5.8.x + +After that, we merge the changes into the master branch: + + git checkout master + git merge pmd/5.9.x + +{%include note.html content="This ensures, that every change on the maintenance branch eventually ends +up in the master branch and therefore in any future version of PMD.
+The downside is however, that there are inevitable merge conflicts for the maven `pom.xml` files, since +every branch changed the version number differently.
+We could avoid this by merging only the temporary branch \"pr-124\" into each maintenance branch and +eventually into master, with the risk of missing single commits in a maintenance branch, that have been +done outside the temporary branch." %} + +### Merging vs. Cherry-Picking + +We are not using cherry-picking, so that each fix is represented by a single commit. +Cherry-picking would duplicate the commit and you can't see in the log, on which branches the fix has been +integrated (e.g. gitk and github show the branches, from which the specific commit is reachable). + +The downside is a more complex history - the maintenance branches and master branch are "connected" and not separate. diff --git a/docs/pages/pmd/devdocs/releasing.md b/docs/pages/pmd/projectdocs/committers/releasing.md similarity index 83% rename from docs/pages/pmd/devdocs/releasing.md rename to docs/pages/pmd/projectdocs/committers/releasing.md index cf911cc79da..4aeeb7e1a2c 100644 --- a/docs/pages/pmd/devdocs/releasing.md +++ b/docs/pages/pmd/projectdocs/committers/releasing.md @@ -1,6 +1,6 @@ --- title: Releasing -permalink: pmd_devdocs_releasing.html +permalink: pmd_projectdocs_committers_releasing.html author: Romain Pelisse , Andreas Dangel --- @@ -17,23 +17,32 @@ through the release process. Make sure code is up to date and everything is committed and pushed with git: - $ mvn clean + $ ./mvnw clean $ git pull $ git status -### The Release Notes - -At a very minimum, the current date must be noted in the release notes. Also, the version -must be adjusted. E.g. by removing "-SNAPSHOT". +### The Release Notes and docs You can find the release notes here: `docs/pages/release_notes.md`. +The date and the version must be updated in `docs/_config.yml`, e.g. + +``` +pmd: + version: 6.0.0 + date: 2017-12-15 +``` + + The release notes usual mention any new rules that have been added since the last release. Please double check the file `pmd-core/src/main/resources/rulesets/releases/.xml`, so that all new rules are listed. +We maintain a documentation for the [next major release](pmd_next_major_development.html). Copy the API +changes from the current release notes to this document: `docs/pages/next_major_development.md`. + Check in all (version) changes to branch master or any other branch, from which the release takes place: $ git commit -a -m "Prepare pmd release " @@ -43,12 +52,19 @@ Check in all (version) changes to branch master or any other branch, from which ### The Homepage The github repo `pmd.github.io` hosts the homepage for [https://pmd.github.io](https://pmd.github.io). -The `index.html` page needes to be updated to display the new release. The new release is mentioned -* on the start page -* in the announcements -* and in the previous releases section +The new version needs to be entered into `_config.yml`, e.g.: +``` +pmd: + latestVersion: 6.0.0 + latestVersionDate: 15th December 2017 +``` + +Also move the previous version down into the "downloads" section. + +Then create a new page for the new release, e.g. `_posts/2017-12-15-PMD-6.0.0.md` and copy +the release notes into this page. This will appear under the news section. Check in all (version) changes to branch master: @@ -120,9 +136,11 @@ the following template: ### Prepare the new release notes +* Update version in **docs/_config.yml** * 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 @@ -130,18 +148,13 @@ permalink: pmd_release_notes.html keywords: changelog, release notes --- -## ????? - ${DEVELOPMENT_VERSION} +## {{ site.pmd.date }} - {{ site.pmd.version }} -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 - -* [New and noteworthy](#new-and-noteworthy) -* [Fixed Issues](#fixed-issues) -* [API Changes](#api-changes) -* [External Contributions](#external-contributions) +{% tocmaker %} ### New and noteworthy @@ -151,7 +164,11 @@ This is a bug fixing release. ### External Contributions +{% endtocmaker %} + ``` +{%endraw%} + Commit and push @@ -191,6 +208,6 @@ At some point, it might be time for a new maintenance branch. Such a branch is u the `master` branch. Here are the steps: * Create a new branch: `git branch pmd/5.6.x master` -* Update the version in both the new branch and master, e.g. `mvn versions:set -Dversion=5.6.0-SNAPSHOT` - and `mvn versions:set -Dversion=5.7.0-SNAPSHOT`. +* Update the version in both the new branch and master, e.g. `mvn versions:set -DnewVersion=5.6.1-SNAPSHOT` + and `mvn versions:set -DnewVersion=5.7.0-SNAPSHOT`. * Update the release notes on both the new branch and master diff --git a/docs/pages/pmd/userdocs/credits.md b/docs/pages/pmd/projectdocs/credits.md similarity index 98% rename from docs/pages/pmd/userdocs/credits.md rename to docs/pages/pmd/projectdocs/credits.md index d53b833e7e0..25002640654 100644 --- a/docs/pages/pmd/userdocs/credits.md +++ b/docs/pages/pmd/projectdocs/credits.md @@ -1,6 +1,6 @@ --- title: Credits -permalink: pmd_userdocs_credits.html +permalink: pmd_projectdocs_credits.html author: Tom Copeland --- @@ -436,6 +436,16 @@ author: Tom Copeland ## Organizations + + + + ") + .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/rules/RuleBuilder.java b/pmd-core/src/main/java/net/sourceforge/pmd/rules/RuleBuilder.java new file mode 100644 index 00000000000..15d4bbc6355 --- /dev/null +++ b/pmd-core/src/main/java/net/sourceforge/pmd/rules/RuleBuilder.java @@ -0,0 +1,219 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.rules; + +import java.util.ArrayList; +import java.util.List; + +import org.apache.commons.lang3.StringUtils; +import org.w3c.dom.Element; + +import net.sourceforge.pmd.Rule; +import net.sourceforge.pmd.RulePriority; +import net.sourceforge.pmd.RuleSetReference; +import net.sourceforge.pmd.lang.Language; +import net.sourceforge.pmd.lang.LanguageRegistry; +import net.sourceforge.pmd.lang.LanguageVersion; +import net.sourceforge.pmd.properties.PropertyDescriptor; + + +/** + * Builds a rule, validating its parameters throughout. The builder can define property descriptors, but not override + * them. For that, use {@link RuleFactory#decorateRule(Rule, RuleSetReference, Element)}. + * + * @author Clément Fournier + * @since 6.0.0 + */ +public class RuleBuilder { + + private List> definedProperties = new ArrayList<>(); + private String name; + private String clazz; + private Language language; + private String minimumVersion; + private String maximumVersion; + private String since; + private String message; + private String externalInfoUrl; + private String description; + private List examples = new ArrayList<>(1); + private RulePriority priority; + private boolean isDeprecated; + private boolean isUsesDfa; + private boolean isUsesMultifile; + private boolean isUsesTyperesolution; + + public RuleBuilder(String name, String clazz, String language) { + this.name = name; + language(language); + className(clazz); + } + + public void usesDFA(boolean usesDFA) { + isUsesDfa = usesDFA; + } + + public void usesMultifile(boolean usesMultifile) { + isUsesMultifile = usesMultifile; + } + + public void usesTyperesolution(boolean usesTyperesolution) { + isUsesTyperesolution = usesTyperesolution; + } + + private void language(String languageName) { + if (StringUtils.isBlank(languageName)) { + // Some languages don't need the attribute because the rule's + // constructor calls setLanguage, see e.g. AbstractJavaRule + return; + } + + Language lang = LanguageRegistry.findLanguageByTerseName(languageName); + if (lang == null) { + throw new IllegalArgumentException( + "Unknown Language '" + languageName + "' for rule" + name + ", supported Languages are " + + LanguageRegistry.commaSeparatedTerseNamesForLanguage(LanguageRegistry.findWithRuleSupport())); + } + language = lang; + } + + private void className(String className) { + if (StringUtils.isBlank(className)) { + throw new IllegalArgumentException("The 'class' field of rule can't be null, nor empty."); + } + + this.clazz = className; + } + + public void minimumLanguageVersion(String minimum) { + minimumVersion = minimum; + } + + public void maximumLanguageVersion(String maximum) { + maximumVersion = maximum; + } + + private void checkLanguageVersionsAreOrdered(Rule rule) { + if (rule.getMinimumLanguageVersion() != null && rule.getMaximumLanguageVersion() != null + && rule.getMinimumLanguageVersion().compareTo(rule.getMaximumLanguageVersion()) > 0) { + throw new IllegalArgumentException( + "The minimum Language Version '" + rule.getMinimumLanguageVersion().getTerseName() + + "' must be prior to the maximum Language Version '" + + rule.getMaximumLanguageVersion().getTerseName() + "' for Rule '" + name + + "'; perhaps swap them around?"); + } + } + + public void since(String sinceStr) { + if (StringUtils.isNotBlank(sinceStr)) { + since = sinceStr; + } + } + + public void externalInfoUrl(String externalInfoUrl) { + this.externalInfoUrl = externalInfoUrl; + } + + public void message(String message) { + this.message = message; + } + + public void defineProperty(PropertyDescriptor descriptor) { + definedProperties.add(descriptor); + } + + + public void setDeprecated(boolean deprecated) { + isDeprecated = deprecated; + } + + + public void description(String description) { + this.description = description; + } + + + public void addExample(String example) { + examples.add(example); + } + + + public void priority(int priorityString) { + this.priority = RulePriority.valueOf(priorityString); + } + + // Must be loaded after rule construction to know the Language + private void loadLanguageMinMaxVersions(Rule rule) { + + if (minimumVersion != null) { + LanguageVersion minimumLanguageVersion = rule.getLanguage().getVersion(minimumVersion); + if (minimumLanguageVersion == null) { + throwUnknownLanguageVersionException("minimum", minimumVersion); + } else { + rule.setMinimumLanguageVersion(minimumLanguageVersion); + } + } + + if (maximumVersion != null) { + LanguageVersion maximumLanguageVersion = rule.getLanguage().getVersion(maximumVersion); + if (maximumLanguageVersion == null) { + throwUnknownLanguageVersionException("maximum", maximumVersion); + } else { + rule.setMaximumLanguageVersion(maximumLanguageVersion); + } + } + + checkLanguageVersionsAreOrdered(rule); + } + + private void throwUnknownLanguageVersionException(String minOrMax, String unknownVersion) { + throw new IllegalArgumentException("Unknown " + minOrMax + " Language Version '" + unknownVersion + + "' for Language '" + language.getTerseName() + + "' for Rule " + name + + "; supported Language Versions are: " + + LanguageRegistry.commaSeparatedTerseNamesForLanguageVersion(language.getVersions())); + } + + public Rule build() throws ClassNotFoundException, IllegalAccessException, InstantiationException { + Rule rule = (Rule) RuleBuilder.class.getClassLoader().loadClass(clazz).newInstance(); + + rule.setName(name); + rule.setRuleClass(clazz); + + if (rule.getLanguage() == null) { + rule.setLanguage(language); + } + + loadLanguageMinMaxVersions(rule); + rule.setSince(since); + rule.setMessage(message); + rule.setExternalInfoUrl(externalInfoUrl); + rule.setDeprecated(isDeprecated); + rule.setDescription(description); + rule.setPriority(priority == null ? RulePriority.LOW : priority); + + for (String example : examples) { + rule.addExample(example); + } + + if (isUsesDfa) { + rule.setDfa(isUsesDfa); + } + if (isUsesMultifile) { + rule.setMultifile(isUsesMultifile); + } + if (isUsesTyperesolution) { + rule.setTypeResolution(isUsesTyperesolution); + } + + for (PropertyDescriptor descriptor : definedProperties) { + if (!rule.getPropertyDescriptors().contains(descriptor)) { + rule.definePropertyDescriptor(descriptor); + } + } + + return rule; + } +} diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/rules/RuleFactory.java b/pmd-core/src/main/java/net/sourceforge/pmd/rules/RuleFactory.java new file mode 100644 index 00000000000..65eeee6283b --- /dev/null +++ b/pmd-core/src/main/java/net/sourceforge/pmd/rules/RuleFactory.java @@ -0,0 +1,381 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.rules; + +import static net.sourceforge.pmd.properties.PropertyDescriptorField.DEFAULT_VALUE; + +import java.util.AbstractMap.SimpleEntry; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.apache.commons.lang3.StringUtils; +import org.w3c.dom.Attr; +import org.w3c.dom.Element; +import org.w3c.dom.NamedNodeMap; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; + +import net.sourceforge.pmd.Rule; +import net.sourceforge.pmd.RulePriority; +import net.sourceforge.pmd.RuleSetReference; +import net.sourceforge.pmd.lang.rule.RuleReference; +import net.sourceforge.pmd.properties.PropertyDescriptor; +import net.sourceforge.pmd.properties.PropertyDescriptorField; +import net.sourceforge.pmd.properties.PropertyTypeId; +import net.sourceforge.pmd.properties.builders.PropertyDescriptorExternalBuilder; + + +/** + * Builds rules from rule XML nodes. + * + * @author Clément Fournier + * @since 6.0.0 + */ +public class RuleFactory { + + private static final Logger LOG = Logger.getLogger(RuleFactory.class.getName()); + + private static final String DEPRECATED = "deprecated"; + private static final String NAME = "name"; + private static final String MESSAGE = "message"; + private static final String EXTERNAL_INFO_URL = "externalInfoUrl"; + private static final String MINIMUM_LANGUAGE_VERSION = "minimumLanguageVersion"; + private static final String MAXIMUM_LANGUAGE_VERSION = "maximumLanguageVersion"; + private static final String SINCE = "since"; + private static final String PROPERTIES = "properties"; + private static final String PRIORITY = "priority"; + private static final String EXAMPLE = "example"; + private static final String DESCRIPTION = "description"; + private static final String PROPERTY = "property"; + private static final String CLASS = "class"; + + private static final List REQUIRED_ATTRIBUTES = Collections.unmodifiableList(Arrays.asList(NAME, CLASS)); + + /** + * Decorates a referenced rule with the metadata that are overridden in the given rule element. + * + *

Declaring a property in the overriding element throws an exception (the property must exist in the referenced + * rule). + * + * @param referencedRule Referenced rule + * @param ruleSetReference the ruleset, where the referenced rule is defined + * @param ruleElement Element overriding some metadata about the rule + * + * @return A rule reference to the referenced rule + */ + public RuleReference decorateRule(Rule referencedRule, RuleSetReference ruleSetReference, Element ruleElement) { + RuleReference ruleReference = new RuleReference(referencedRule, ruleSetReference); + + if (ruleElement.hasAttribute(DEPRECATED)) { + ruleReference.setDeprecated(Boolean.parseBoolean(ruleElement.getAttribute(DEPRECATED))); + } + if (ruleElement.hasAttribute(NAME)) { + ruleReference.setName(ruleElement.getAttribute(NAME)); + } + if (ruleElement.hasAttribute(MESSAGE)) { + ruleReference.setMessage(ruleElement.getAttribute(MESSAGE)); + } + if (ruleElement.hasAttribute(EXTERNAL_INFO_URL)) { + ruleReference.setExternalInfoUrl(ruleElement.getAttribute(EXTERNAL_INFO_URL)); + } + + for (int i = 0; i < ruleElement.getChildNodes().getLength(); i++) { + Node node = ruleElement.getChildNodes().item(i); + if (node.getNodeType() == Node.ELEMENT_NODE) { + switch (node.getNodeName()) { + case DESCRIPTION: + ruleReference.setDescription(parseTextNode(node)); + break; + case EXAMPLE: + ruleReference.addExample(parseTextNode(node)); + break; + case PRIORITY: + ruleReference.setPriority(RulePriority.valueOf(Integer.parseInt(parseTextNode(node)))); + break; + case PROPERTIES: + setPropertyValues(ruleReference, (Element) node); + break; + default: + throw new IllegalArgumentException("Unexpected element <" + node.getNodeName() + + "> encountered as child of element for Rule " + + ruleReference.getName()); + } + } + } + + return ruleReference; + } + + /** + * Parses a rule element and returns a new rule instance. + * + *

Notes: The ruleset name is not set here. Exceptions raised from this method indicate invalid XML structure, + * with regards to the expected schema, while RuleBuilder validates the semantics. + * + * @param ruleElement The rule element to parse + * + * @return A new instance of the rule described by this element + * @throws IllegalArgumentException if the element doesn't describe a valid rule. + */ + public Rule buildRule(Element ruleElement) { + checkRequiredAttributesArePresent(ruleElement); + + String name = ruleElement.getAttribute(NAME); + + RuleBuilder builder = new RuleBuilder(name, + ruleElement.getAttribute(CLASS), + ruleElement.getAttribute("language")); + + if (ruleElement.hasAttribute(MINIMUM_LANGUAGE_VERSION)) { + builder.minimumLanguageVersion(ruleElement.getAttribute(MINIMUM_LANGUAGE_VERSION)); + } + + if (ruleElement.hasAttribute(MAXIMUM_LANGUAGE_VERSION)) { + builder.maximumLanguageVersion(ruleElement.getAttribute(MAXIMUM_LANGUAGE_VERSION)); + } + + if (ruleElement.hasAttribute(SINCE)) { + builder.since(ruleElement.getAttribute(SINCE)); + } + + builder.message(ruleElement.getAttribute(MESSAGE)); + builder.externalInfoUrl(ruleElement.getAttribute(EXTERNAL_INFO_URL)); + builder.setDeprecated(hasAttributeSetTrue(ruleElement, DEPRECATED)); + builder.usesDFA(hasAttributeSetTrue(ruleElement, "dfa")); + builder.usesTyperesolution(hasAttributeSetTrue(ruleElement, "typeResolution")); + // Disabled until it's safe + // builder.usesMultifile(hasAttributeSetTrue(ruleElement, "multifile")); + + Element propertiesElement = null; + + final NodeList nodeList = ruleElement.getChildNodes(); + for (int i = 0; i < nodeList.getLength(); i++) { + Node node = nodeList.item(i); + if (node.getNodeType() != Node.ELEMENT_NODE) { + continue; + } + + switch (node.getNodeName()) { + case DESCRIPTION: + builder.description(parseTextNode(node)); + break; + case EXAMPLE: + builder.addExample(parseTextNode(node)); + break; + case PRIORITY: + builder.priority(Integer.parseInt(parseTextNode(node).trim())); + break; + case PROPERTIES: + parsePropertiesForDefinitions(builder, node); + propertiesElement = (Element) node; + break; + default: + throw new IllegalArgumentException("Unexpected element <" + node.getNodeName() + + "> encountered as child of element for Rule " + + name); + } + } + + Rule rule; + try { + rule = builder.build(); + } catch (ClassNotFoundException | IllegalAccessException | InstantiationException e) { + LOG.log(Level.SEVERE, "Error instantiating a rule", e); + throw new RuntimeException(e); + } + + if (propertiesElement != null) { + setPropertyValues(rule, propertiesElement); + } + + return rule; + } + + private void checkRequiredAttributesArePresent(Element ruleElement) { + // add an attribute name here to make it required + + for (String att : REQUIRED_ATTRIBUTES) { + if (!ruleElement.hasAttribute(att)) { + throw new IllegalArgumentException("Missing '" + att + "' attribute"); + } + } + } + + /** + * Parses a properties element looking only for the values of the properties defined or overridden. + * + * @param propertiesNode Node to parse + * + * @return A map of property names to their value + */ + private Map getPropertyValuesFrom(Element propertiesNode) { + Map overridenProperties = new HashMap<>(); + + for (int i = 0; i < propertiesNode.getChildNodes().getLength(); i++) { + Node node = propertiesNode.getChildNodes().item(i); + if (node.getNodeType() == Node.ELEMENT_NODE && PROPERTY.equals(node.getNodeName())) { + Entry overridden = getPropertyValue((Element) node); + overridenProperties.put(overridden.getKey(), overridden.getValue()); + } + } + + return overridenProperties; + } + + /** + * Parses the properties node and adds property definitions to the builder. Doesn't care for value overriding, that + * will be handled after the rule instantiation. + * + * @param builder Rule builder + * @param propertiesNode Node to parse + */ + private void parsePropertiesForDefinitions(RuleBuilder builder, Node propertiesNode) { + for (int i = 0; i < propertiesNode.getChildNodes().getLength(); i++) { + Node node = propertiesNode.getChildNodes().item(i); + if (node.getNodeType() == Node.ELEMENT_NODE && PROPERTY.equals(node.getNodeName()) + && isPropertyDefinition((Element) node)) { + PropertyDescriptor descriptor = parsePropertyDefinition((Element) node); + builder.defineProperty(descriptor); + } + } + } + + /** + * Gets a mapping of property name to its value from the given property element. + * + * @param propertyElement Property element + * + * @return An entry of property name to its value + */ + private Entry getPropertyValue(Element propertyElement) { + String name = propertyElement.getAttribute(PropertyDescriptorField.NAME.attributeName()); + return new SimpleEntry<>(name, valueFrom(propertyElement)); + } + + /** + * Overrides the rule's properties with the values defined in the element. + * + * @param rule The rule + * @param propertiesElt The {@literal } element + */ + private void setPropertyValues(Rule rule, Element propertiesElt) { + Map overridden = getPropertyValuesFrom(propertiesElt); + + for (Entry e : overridden.entrySet()) { + PropertyDescriptor descriptor = rule.getPropertyDescriptor(e.getKey()); + if (descriptor == null) { + throw new IllegalArgumentException( + "Cannot set non-existent property '" + e.getKey() + "' on Rule " + rule.getName()); + } + + setRulePropertyCapture(rule, descriptor, e.getValue()); + } + } + + private void setRulePropertyCapture(Rule rule, PropertyDescriptor descriptor, String value) { + rule.setProperty(descriptor, descriptor.valueFrom(value)); + } + + /** + * Finds out if the property element defines a property. + * + * @param node Property element + * + * @return True if this element defines a new property, false if this is just stating a value + */ + private static boolean isPropertyDefinition(Element node) { + return node.hasAttribute(PropertyDescriptorField.TYPE.attributeName()); + } + + /** + * Parses a property definition node and returns the defined property descriptor. + * + * @param propertyElement Property node to parse + * + * @return The property descriptor + */ + private static PropertyDescriptor parsePropertyDefinition(Element propertyElement) { + String typeId = propertyElement.getAttribute(PropertyDescriptorField.TYPE.attributeName()); + + PropertyDescriptorExternalBuilder pdFactory = PropertyTypeId.factoryFor(typeId); + if (pdFactory == null) { + throw new IllegalArgumentException("No property descriptor factory for type: " + typeId); + } + + Map values = new HashMap<>(); + NamedNodeMap atts = propertyElement.getAttributes(); + + /// populate a map of values for an individual descriptor + for (int i = 0; i < atts.getLength(); i++) { + Attr a = (Attr) atts.item(i); + values.put(PropertyDescriptorField.getConstant(a.getName()), a.getValue()); + } + + if (StringUtils.isBlank(values.get(DEFAULT_VALUE))) { + NodeList children = propertyElement.getElementsByTagName(DEFAULT_VALUE.attributeName()); + if (children.getLength() == 1) { + values.put(DEFAULT_VALUE, children.item(0).getTextContent()); + } else { + throw new IllegalArgumentException("No value defined!"); + } + } + + // casting is not pretty but prevents the interface from having this method + return pdFactory.build(values); + } + + /** Gets the string value from a property node. */ + private static String valueFrom(Element propertyNode) { + String strValue = propertyNode.getAttribute(DEFAULT_VALUE.attributeName()); + + if (StringUtils.isNotBlank(strValue)) { + return strValue; + } + + final NodeList nodeList = propertyNode.getChildNodes(); + + for (int i = 0; i < nodeList.getLength(); i++) { + Node node = nodeList.item(i); + if (node.getNodeType() == Node.ELEMENT_NODE && "value".equals(node.getNodeName())) { + return parseTextNode(node); + } + } + return null; + } + + private static boolean hasAttributeSetTrue(Element element, String attributeId) { + return element.hasAttribute(attributeId) && "true".equalsIgnoreCase(element.getAttribute(attributeId)); + } + + /** + * Parse a String from a textually type node. + * + * @param node The node. + * + * @return The String. + */ + private static String parseTextNode(Node node) { + final int nodeCount = node.getChildNodes().getLength(); + if (nodeCount == 0) { + return ""; + } + + StringBuilder buffer = new StringBuilder(); + + for (int i = 0; i < nodeCount; i++) { + Node childNode = node.getChildNodes().item(i); + if (childNode.getNodeType() == Node.CDATA_SECTION_NODE || childNode.getNodeType() == Node.TEXT_NODE) { + buffer.append(childNode.getNodeValue()); + } + } + return buffer.toString(); + } +} diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/util/ClasspathClassLoader.java b/pmd-core/src/main/java/net/sourceforge/pmd/util/ClasspathClassLoader.java index fd9d057e0b4..841baa81e5f 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/util/ClasspathClassLoader.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/util/ClasspathClassLoader.java @@ -17,7 +17,6 @@ import java.util.logging.Level; import java.util.logging.Logger; -import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.StringUtils; /** @@ -30,15 +29,29 @@ public class ClasspathClassLoader extends URLClassLoader { private static final Logger LOG = Logger.getLogger(ClasspathClassLoader.class.getName()); - + static { registerAsParallelCapable(); } + public ClasspathClassLoader(List files, ClassLoader parent) throws IOException { + super(fileToURL(files), parent); + } + public ClasspathClassLoader(String classpath, ClassLoader parent) throws IOException { super(initURLs(classpath), parent); } + private static URL[] fileToURL(List files) throws IOException { + + List urlList = new ArrayList<>(); + + for (File f : files) { + urlList.add(f.toURI().toURL()); + } + return urlList.toArray(new URL[0]); + } + private static URL[] initURLs(String classpath) throws IOException { if (classpath == null) { throw new IllegalArgumentException("classpath argument cannot be null"); @@ -51,7 +64,7 @@ private static URL[] initURLs(String classpath) throws IOException { // Treat as classpath addClasspathURLs(urls, classpath); } - return urls.toArray(new URL[urls.size()]); + return urls.toArray(new URL[0]); } private static void addClasspathURLs(final List urls, final String classpath) throws MalformedURLException { @@ -64,9 +77,7 @@ private static void addClasspathURLs(final List urls, final String classpat } private static void addFileURLs(List urls, URL fileURL) throws IOException { - BufferedReader in = null; - try { - in = new BufferedReader(new InputStreamReader(fileURL.openStream())); + try (BufferedReader in = new BufferedReader(new InputStreamReader(fileURL.openStream()))) { String line; while ((line = in.readLine()) != null) { LOG.log(Level.FINE, "Read classpath entry line: <{0}>", line); @@ -76,8 +87,6 @@ private static void addFileURLs(List urls, URL fileURL) throws IOException urls.add(createURLFromPath(line)); } } - } finally { - IOUtils.closeQuietly(in); } } @@ -86,9 +95,6 @@ private static URL createURLFromPath(String path) throws MalformedURLException { return file.getAbsoluteFile().toURI().toURL(); } - /** - * {@inheritDoc} - */ @Override public String toString() { return new StringBuilder(getClass().getSimpleName()) @@ -96,4 +102,27 @@ public String toString() { .append(StringUtils.join(getURLs(), ":")) .append("] parent: ").append(getParent()).append(']').toString(); } + + @Override + protected Class loadClass(final String name, final boolean resolve) throws ClassNotFoundException { + synchronized (getClassLoadingLock(name)) { + // First, check if the class has already been loaded + Class c = findLoadedClass(name); + if (c == null) { + try { + // checking local + c = findClass(name); + } catch (final ClassNotFoundException | SecurityException e) { + // checking parent + // This call to loadClass may eventually call findClass again, in case the parent doesn't find anything. + c = super.loadClass(name, resolve); + } + } + + if (resolve) { + resolveClass(c); + } + return c; + } + } } diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/util/CollectionUtil.java b/pmd-core/src/main/java/net/sourceforge/pmd/util/CollectionUtil.java index 3bf817ca6ae..4592e487e93 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/util/CollectionUtil.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/util/CollectionUtil.java @@ -10,8 +10,10 @@ import java.util.Collection; import java.util.HashMap; import java.util.HashSet; +import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Set; /** @@ -161,6 +163,26 @@ public static Map invertedMapFrom(Map source) { return map; } + + /** + * Consumes all the elements of the iterator and + * returns a list containing them. The iterator is + * then unusable + * + * @param it An iterator + * + * @return a list containing the elements remaining + * on the iterator + */ + public static List toList(Iterator it) { + List list = new ArrayList<>(); + while (it.hasNext()) { + list.add(it.next()); + } + return list; + } + + /** * Returns true if the objects are array instances and each of their * elements compares via equals as well. @@ -170,7 +192,7 @@ public static Map invertedMapFrom(Map source) { * @param otherValue * Object * @return boolean - * @deprecated {@see Objects#deepEquals(Object, Object)} + * @deprecated {@link Objects#deepEquals(Object, Object)} */ @Deprecated public static boolean arraysAreEqual(Object value, Object otherValue) { @@ -192,7 +214,7 @@ public static boolean arraysAreEqual(Object value, Object otherValue) { * @param thatArray * Object[] * @return boolean - * @deprecated {@see Arrays#deepEquals(Object[], Object[])} + * @deprecated {@link Arrays#deepEquals(Object[], Object[])} */ @Deprecated public static boolean valuesAreTransitivelyEqual(Object[] thisArray, Object[] thatArray) { @@ -221,7 +243,7 @@ public static boolean valuesAreTransitivelyEqual(Object[] thisArray, Object[] th * @param otherValue * Object * @return boolean - * @deprecated {@see Objects#deepEquals(Object, Object)} + * @deprecated {@link Objects#deepEquals(Object, Object)} */ @Deprecated @SuppressWarnings("PMD.CompareObjectsWithEquals") @@ -271,7 +293,7 @@ public static boolean isNotEmpty(Object[] items) { * @param a * @param b * @return boolean - * @deprecated {@see Arrays#deepEquals(Object[], Object[])} + * @deprecated {@link Arrays#deepEquals(Object[], Object[])} */ @Deprecated public static boolean areSemanticEquals(T[] a, T[] b) { diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/util/CompoundIterator.java b/pmd-core/src/main/java/net/sourceforge/pmd/util/CompoundIterator.java index 543d6c3fcc0..0bd0d9d794e 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/util/CompoundIterator.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/util/CompoundIterator.java @@ -29,17 +29,11 @@ public CompoundIterator(Iterator... iterators) { this.index = 0; } - /** - * {@inheritDoc} - */ @Override public boolean hasNext() { return getNextIterator() != null; } - /** - * {@inheritDoc} - */ @Override public T next() { Iterator iterator = getNextIterator(); @@ -50,9 +44,6 @@ public T next() { } } - /** - * {@inheritDoc} - */ @Override public void remove() { Iterator iterator = getNextIterator(); diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/util/NumericConstants.java b/pmd-core/src/main/java/net/sourceforge/pmd/util/NumericConstants.java index 8f42b4f95c7..e472814db8e 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/util/NumericConstants.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/util/NumericConstants.java @@ -4,7 +4,7 @@ package net.sourceforge.pmd.util; -public class NumericConstants { +public final class NumericConstants { public static final Integer ZERO = 0; diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/util/ResourceLoader.java b/pmd-core/src/main/java/net/sourceforge/pmd/util/ResourceLoader.java index 87748290ae1..27587aa57d2 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/util/ResourceLoader.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/util/ResourceLoader.java @@ -12,12 +12,11 @@ import java.net.HttpURLConnection; import java.net.URL; import java.net.URLConnection; +import java.util.Objects; import net.sourceforge.pmd.RuleSetNotFoundException; -/** - */ -public final class ResourceLoader { +public class ResourceLoader { public static final int TIMEOUT; @@ -31,86 +30,93 @@ public final class ResourceLoader { TIMEOUT = timeoutProperty; } - // Only static methods, so we shouldn't allow an instance to be created + private final ClassLoader classLoader; + /** * Constructor for ResourceLoader. */ - private ResourceLoader() { + public ResourceLoader() { + this(ResourceLoader.class.getClassLoader()); } /** - * Method to find a file, first by finding it as a file (either by the - * absolute or relative path), then as a URL, and then finally seeing if it - * is on the classpath. - *

- * Caller is responsible for closing the {@link InputStream}. - * - * @param name - * String - * @return InputStream - * @throws RuleSetNotFoundException + * Constructor for ResourceLoader. */ - public static InputStream loadResourceAsStream(String name) throws RuleSetNotFoundException { - InputStream stream = loadResourceAsStream(name, ResourceLoader.class.getClassLoader()); - if (stream == null) { - throw new RuleSetNotFoundException("Can't find resource " + name - + ". Make sure the resource is a valid file or URL or is on the CLASSPATH"); - } - return stream; + public ResourceLoader(final ClassLoader cl) { + this.classLoader = Objects.requireNonNull(cl); } /** - * Uses the ClassLoader passed in to attempt to load the resource if it's - * not a File or a URL + * Attempts to load the resource from file, a URL or the claspath *

* Caller is responsible for closing the {@link InputStream}. * - * @param name - * String - * @param loader - * ClassLoader + * @param name The resource to attempt and load * @return InputStream * @throws RuleSetNotFoundException */ - public static InputStream loadResourceAsStream(String name, ClassLoader loader) throws RuleSetNotFoundException { - File file = new File(name); + public InputStream loadResourceAsStream(final String name) throws RuleSetNotFoundException { + // Search file locations first + final File file = new File(name); if (file.exists()) { try { return new FileInputStream(file); - } catch (FileNotFoundException e) { + } catch (final FileNotFoundException e) { // if the file didn't exist, we wouldn't be here + throw new RuntimeException(e); // somehow the file vanished between checking for existence and opening } - } else { + } + + // Maybe it's a url? + try { + final HttpURLConnection connection = (HttpURLConnection) new URL(name).openConnection(); + connection.setConnectTimeout(TIMEOUT); + connection.setReadTimeout(TIMEOUT); + return connection.getInputStream(); + } catch (final Exception e) { try { - HttpURLConnection connection = (HttpURLConnection) new URL(name).openConnection(); - connection.setConnectTimeout(TIMEOUT); - connection.setReadTimeout(TIMEOUT); - return connection.getInputStream(); - } catch (Exception e) { - try { - /* - * Don't use getResourceAsStream to void reusing connections between threads - * See https://github.com/pmd/pmd/issues/234 - */ - URL resource = loader.getResource(name); - if (resource == null) { - // Don't throw RuleSetNotFoundException, keep API compatibility - return null; - } else { - final URLConnection connection = resource.openConnection(); - // This avoids reusing the underlaying file, if the resource is loaded from a Jar file. - // The file is closed with the input stream then thus not leaving a leaked resource behind. - // See https://github.com/pmd/pmd/issues/364 and https://github.com/pmd/pmd/issues/337 - connection.setUseCaches(false); - final InputStream inputStream = connection.getInputStream(); - return inputStream; - } - } catch (IOException e1) { - // Ignored - } + return loadClassPathResourceAsStream(name); + } catch (final IOException ignored) { + // We will throw our own exception, with a different message } } + throw new RuleSetNotFoundException("Can't find resource " + name + ". Make sure the resource is a valid file or URL or is on the CLASSPATH"); } + + public InputStream loadClassPathResourceAsStream(final String name) throws IOException { + /* + * Don't use getResourceAsStream to avoid reusing connections between threads + * See https://github.com/pmd/pmd/issues/234 + */ + final URL resource = classLoader.getResource(name); + if (resource == null) { + // Don't throw RuleSetNotFoundException, keep API compatibility + return null; + } else { + final URLConnection connection = resource.openConnection(); + // This avoids reusing the underlying file, if the resource is loaded from a Jar file. + // The file is closed with the input stream then thus not leaving a leaked resource behind. + // See https://github.com/pmd/pmd/issues/364 and https://github.com/pmd/pmd/issues/337 + connection.setUseCaches(false); + return connection.getInputStream(); + } + } + + public InputStream loadClassPathResourceAsStreamOrThrow(final String name) throws RuleSetNotFoundException { + InputStream is = null; + try { + is = loadClassPathResourceAsStream(name); + } catch (final IOException ignored) { + // ignored + } + + if (is == null) { + throw new RuleSetNotFoundException("Can't find resource " + name + + ". Make sure the resource is on the CLASSPATH"); + } + + return is; + } } diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/util/StringUtil.java b/pmd-core/src/main/java/net/sourceforge/pmd/util/StringUtil.java index 57bc4666bc1..84caeac6cea 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/util/StringUtil.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/util/StringUtil.java @@ -52,7 +52,7 @@ public static String percentageString(double val, int numDecimals) { * @param prefixes * * @return boolean - * @deprecated {@see StringUtils#startsWithAny(CharSequence, CharSequence...)} + * @deprecated {@link StringUtils#startsWithAny(CharSequence, CharSequence...)} */ @Deprecated public static boolean startsWithAny(String text, String... prefixes) { @@ -112,7 +112,7 @@ public static String withoutPrefixes(String text, String... prefixes) { * @param value String * * @return boolean - * @deprecated {@see StringUtils#isNotBlank(CharSequence)} + * @deprecated {@link StringUtils#isNotBlank(CharSequence)} */ @Deprecated public static boolean isNotEmpty(String value) { @@ -128,7 +128,7 @@ public static boolean isNotEmpty(String value) { * @param value String to test * * @return true if the value is empty, false otherwise. - * @deprecated {@see StringUtils#isBlank(CharSequence)} + * @deprecated {@link StringUtils#isBlank(CharSequence)} */ @Deprecated public static boolean isEmpty(String value) { @@ -142,7 +142,7 @@ public static boolean isEmpty(String value) { * @param value String to test * * @return True if the argument is null or the empty string - * @deprecated {@see StringUtils#isEmpty(CharSequence)} + * @deprecated {@link StringUtils#isEmpty(CharSequence)} */ @Deprecated public static boolean isMissing(String value) { @@ -179,7 +179,7 @@ public static boolean areSemanticEquals(String a, String b) { * @param newString String * * @return String - * @deprecated {@see StringUtils#replace(String, String, String)} + * @deprecated {@link StringUtils#replace(String, String, String)} */ @Deprecated public static String replaceString(final String original, final String oldString, final String newString) { @@ -207,7 +207,6 @@ public static String replaceString(final String original, final String oldString * @param supportUTF8 override the default setting, whether special characters should be replaced with entities ( * false) or should be included as is ( true). * - * @see #appendXmlEscaped(StringBuilder, String) */ public static void appendXmlEscaped(StringBuilder buf, String src, boolean supportUTF8) { char c; @@ -267,7 +266,7 @@ public static String escapeWhitespace(Object o) { * @param newString String * * @return String - * @deprecated {@see StringUtils#replace(String, String, String)} or {@see StringUtils#replaceChars(String, char, char)} + * @deprecated {@link StringUtils#replace(String, String, String)} or {@link StringUtils#replaceChars(String, char, char)} */ @Deprecated public static String replaceString(final String original, char oldChar, final String newString) { @@ -300,7 +299,7 @@ public static String replaceString(final String original, char oldChar, final St * @param delimiter char * * @return String[] - * @deprecated {@see StringUtils#split(String, char)} + * @deprecated {@link StringUtils#split(String, char)} */ @Deprecated public static String[] substringsOf(String source, char delimiter) { @@ -348,7 +347,7 @@ public static String[] substringsOf(String source, char delimiter) { * @param separator char * * @return String[] - * @deprecated {@see StringUtils#split(String, String)} + * @deprecated {@link StringUtils#split(String, String)} */ @Deprecated public static String[] substringsOf(String str, String separator) { @@ -371,7 +370,7 @@ public static String[] substringsOf(String str, String separator) { index = str.indexOf(separator, currPos); } list.add(str.substring(currPos)); - return list.toArray(new String[list.size()]); + return list.toArray(new String[0]); } @@ -382,7 +381,7 @@ public static String[] substringsOf(String str, String separator) { * @param sb StringBuffer * @param iter Iterator * @param separator String - * @deprecated {@see StringUtils#join(Iterator, String)} + * @deprecated {@link StringUtils#join(Iterator, String)} */ @Deprecated public static void asStringOn(StringBuffer sb, Iterator iter, String separator) { @@ -407,7 +406,7 @@ public static void asStringOn(StringBuffer sb, Iterator iter, String separato * @param sb StringBuilder * @param items Object[] * @param separator String - * @deprecated {@see StringUtils#join(Iterable, String)} + * @deprecated {@link StringUtils#join(Iterable, String)} */ @Deprecated public static void asStringOn(StringBuilder sb, Object[] items, String separator) { @@ -519,7 +518,7 @@ public static String[] trimStartOn(String[] strings, int trimDepth) { * @param length The desired minimum length of the resulting padded String * * @return The resulting left padded String - * @deprecated {@see StringUtils#leftPad(String, int)} + * @deprecated {@link StringUtils#leftPad(String, int)} */ @Deprecated public static String lpad(String s, int length) { @@ -604,4 +603,55 @@ public static String asString(Object[] items, String separator) { public static String[] getEmptyStrings() { return EMPTY_STRINGS; } + + + /** + * Converts the given string to Camel case, + * that is, removing all spaces, and capitalising + * the first letter of each word except the first. + * + *

If the first word starts with an uppercase + * letter, it's kept as is. This method can thus + * be used for Pascal case too. + * + * @param name The string to convert + * + * @return The string converted to Camel case + */ + public static String toCamelCase(String name) { + return toCamelCase(name, false); + } + + + /** + * Converts the given string to Camel case, + * that is, removing all spaces, and capitalising + * the first letter of each word except the first. + * + *

The second parameter can be used to force the + * words to be converted to lowercase before capitalising. + * This can be useful if eg the first word contains + * several uppercase letters. + * + * @param name The string to convert + * @param forceLowerCase Whether to force removal of all upper + * case letters except on word start + * + * @return The string converted to Camel case + */ + public static String toCamelCase(String name, boolean forceLowerCase) { + StringBuilder sb = new StringBuilder(); + boolean isFirst = true; + for (String word : name.trim().split("\\s++")) { + String pretreated = forceLowerCase ? word.toLowerCase(Locale.ROOT) : word; + if (isFirst) { + sb.append(pretreated); + isFirst = false; + } else { + sb.append(StringUtils.capitalize(pretreated)); + } + } + return sb.toString(); + } + } diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/util/database/DBMSMetadata.java b/pmd-core/src/main/java/net/sourceforge/pmd/util/database/DBMSMetadata.java index 634c3154f9a..4dfde3d00b9 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/util/database/DBMSMetadata.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/util/database/DBMSMetadata.java @@ -332,7 +332,6 @@ public List getSourceObjectList() { public List getSourceObjectList(List languages, List schemas, List sourceCodeTypes, List sourceCodeNames) { - ResultSet sourceCodeObjects = null; List sourceObjectsList = new ArrayList<>(); List searchLanguages = languages; @@ -388,57 +387,14 @@ public List getSourceObjectList(List languages, List procedure catalog (may be null) - * PROCEDURE_SCHEM String => procedure schema - * (may be null) PROCEDURE_NAME String => - * procedure name reserved for future use - * reserved for future use reserved for future - * use REMARKS String => explanatory comment on - * the procedure PROCEDURE_TYPE short => kind of - * procedure: procedureResultUnknown - Cannot - * determine if a return value will be returned - * procedureNoResult - Does not return a return - * value procedureReturnsResult - Returns a - * return value SPECIFIC_NAME String => The name - * which uniquely identifies this procedure - * within its schema. - */ - while (sourceCodeObjects.next()) { - LOGGER.finest(String.format("Found schema=%s,object_type=%s,object_name=%s", - sourceCodeObjects.getString("PROCEDURE_SCHEM"), - sourceCodeObjects.getString("PROCEDURE_TYPE"), - sourceCodeObjects.getString("PROCEDURE_NAME"))); - - sourceObjectsList - .add(new SourceObject(sourceCodeObjects.getString("PROCEDURE_SCHEM"), - sourceCodeObjects.getString("PROCEDURE_TYPE"), - sourceCodeObjects.getString("PROCEDURE_NAME"), null)); + try (PreparedStatement sourceCodeObjectsStatement = getConnection() + .prepareStatement(returnSourceCodeObjectsStatement)) { + for (String language : searchLanguages) { + for (String schema : searchSchemas) { + for (String sourceCodeType : searchSourceCodeTypes) { + for (String sourceCodeName : searchSourceCodeNames) { + sourceObjectsList.addAll(findSourceObjects(sourceCodeObjectsStatement, language, schema, + sourceCodeType, sourceCodeName)); } } } @@ -453,74 +409,7 @@ public List getSourceObjectList(List languages, List schemasList = dburi.getSchemasList(); for (String schema : schemasList) { for (String sourceCodeName : dburi.getSourceCodeNamesList()) { - /* - * public ResultSet getProcedures(String catalog , - * String schemaPattern , String procedureNamePattern) - * throws SQLException - */ - sourceCodeObjects = metadata.getProcedures(null, schema, sourceCodeName); - /* - * From Javadoc .... Each procedure description has the - * the following columns: PROCEDURE_CAT String => - * procedure catalog (may be null) PROCEDURE_SCHEM - * String => procedure schema (may be null) - * PROCEDURE_NAME String => procedure name reserved for - * future use reserved for future use reserved for - * future use REMARKS String => explanatory comment on - * the procedure PROCEDURE_TYPE short => kind of - * procedure: procedureResultUnknown - Cannot determine - * if a return value will be returned procedureNoResult - * - Does not return a return value - * procedureReturnsResult - Returns a return value - * SPECIFIC_NAME String => The name which uniquely - * identifies this procedure within its schema. - * - * Oracle getProcedures actually returns these 8 - * columns:- ResultSet "Matched Procedures" has 8 - * columns and contains ... - * [PROCEDURE_CAT,PROCEDURE_SCHEM,PROCEDURE_NAME,NULL, - * NULL,NULL,REMARKS,PROCEDURE_TYPE - * ,null,PHPDEMO,ADD_JOB_HISTORY,null,null,null, - * Standalone procedure or function,1 - * ,FETCHPERFPKG,PHPDEMO,BULKSELECTPRC,null,null,null, - * Packaged function,2 - * ,FETCHPERFPKG,PHPDEMO,BULKSELECTPRC,null,null,null, - * Packaged procedure,1 - * ,null,PHPDEMO,CITY_LIST,null,null,null,Standalone - * procedure or function,1 - * ,null,PHPDEMO,EDDISCOUNT,null,null,null,Standalone - * procedure or function,2 - * ,SELPKG_BA,PHPDEMO,EMPSELBULK,null,null,null,Packaged - * function,2 - * ,SELPKG_BA,PHPDEMO,EMPSELBULK,null,null,null,Packaged - * procedure,1 - * ,INSPKG,PHPDEMO,INSFORALL,null,null,null,Packaged - * procedure,1 - * ,null,PHPDEMO,MYDOFETCH,null,null,null,Standalone - * procedure or function,2 - * ,null,PHPDEMO,MYPROC1,null,null,null,Standalone - * procedure or function,1 - * ,null,PHPDEMO,MYPROC2,null,null,null,Standalone - * procedure or function,1 - * ,null,PHPDEMO,MYXAQUERY,null,null,null,Standalone - * procedure or function,1 - * ,null,PHPDEMO,POLICY_VPDPARTS,null,null,null, - * Standalone procedure or function,2 - * ,FETCHPERFPKG,PHPDEMO,REFCURPRC,null,null,null, - * Packaged procedure,1 - * ,null,PHPDEMO,SECURE_DML,null,null,null,Standalone - * procedure or function,1 ... ] - */ - while (sourceCodeObjects.next()) { - LOGGER.finest(String.format("Located schema=%s,object_type=%s,object_name=%s\n", - sourceCodeObjects.getString("PROCEDURE_SCHEM"), - sourceCodeObjects.getString("PROCEDURE_TYPE"), - sourceCodeObjects.getString("PROCEDURE_NAME"))); - - sourceObjectsList.add(new SourceObject(sourceCodeObjects.getString("PROCEDURE_SCHEM"), - sourceCodeObjects.getString("PROCEDURE_TYPE"), - sourceCodeObjects.getString("PROCEDURE_NAME"), null)); - } + sourceObjectsList.addAll(findSourceObjectFromMetaData(metadata, schema, sourceCodeName)); } } } @@ -532,4 +421,130 @@ public List getSourceObjectList(List languages, List findSourceObjectFromMetaData(DatabaseMetaData metadata, + String schema, String sourceCodeName) throws SQLException { + List sourceObjectsList = new ArrayList<>(); + /* + * public ResultSet getProcedures(String catalog , + * String schemaPattern , String procedureNamePattern) + * throws SQLException + */ + try (ResultSet sourceCodeObjects = metadata.getProcedures(null, schema, sourceCodeName)) { + /* + * From Javadoc .... Each procedure description has the + * the following columns: PROCEDURE_CAT String => + * procedure catalog (may be null) PROCEDURE_SCHEM + * String => procedure schema (may be null) + * PROCEDURE_NAME String => procedure name reserved for + * future use reserved for future use reserved for + * future use REMARKS String => explanatory comment on + * the procedure PROCEDURE_TYPE short => kind of + * procedure: procedureResultUnknown - Cannot determine + * if a return value will be returned procedureNoResult + * - Does not return a return value + * procedureReturnsResult - Returns a return value + * SPECIFIC_NAME String => The name which uniquely + * identifies this procedure within its schema. + * + * Oracle getProcedures actually returns these 8 + * columns:- ResultSet "Matched Procedures" has 8 + * columns and contains ... + * [PROCEDURE_CAT,PROCEDURE_SCHEM,PROCEDURE_NAME,NULL, + * NULL,NULL,REMARKS,PROCEDURE_TYPE + * ,null,PHPDEMO,ADD_JOB_HISTORY,null,null,null, + * Standalone procedure or function,1 + * ,FETCHPERFPKG,PHPDEMO,BULKSELECTPRC,null,null,null, + * Packaged function,2 + * ,FETCHPERFPKG,PHPDEMO,BULKSELECTPRC,null,null,null, + * Packaged procedure,1 + * ,null,PHPDEMO,CITY_LIST,null,null,null,Standalone + * procedure or function,1 + * ,null,PHPDEMO,EDDISCOUNT,null,null,null,Standalone + * procedure or function,2 + * ,SELPKG_BA,PHPDEMO,EMPSELBULK,null,null,null,Packaged + * function,2 + * ,SELPKG_BA,PHPDEMO,EMPSELBULK,null,null,null,Packaged + * procedure,1 + * ,INSPKG,PHPDEMO,INSFORALL,null,null,null,Packaged + * procedure,1 + * ,null,PHPDEMO,MYDOFETCH,null,null,null,Standalone + * procedure or function,2 + * ,null,PHPDEMO,MYPROC1,null,null,null,Standalone + * procedure or function,1 + * ,null,PHPDEMO,MYPROC2,null,null,null,Standalone + * procedure or function,1 + * ,null,PHPDEMO,MYXAQUERY,null,null,null,Standalone + * procedure or function,1 + * ,null,PHPDEMO,POLICY_VPDPARTS,null,null,null, + * Standalone procedure or function,2 + * ,FETCHPERFPKG,PHPDEMO,REFCURPRC,null,null,null, + * Packaged procedure,1 + * ,null,PHPDEMO,SECURE_DML,null,null,null,Standalone + * procedure or function,1 ... ] + */ + while (sourceCodeObjects.next()) { + LOGGER.finest(String.format("Located schema=%s,object_type=%s,object_name=%s\n", + sourceCodeObjects.getString("PROCEDURE_SCHEM"), + sourceCodeObjects.getString("PROCEDURE_TYPE"), + sourceCodeObjects.getString("PROCEDURE_NAME"))); + + sourceObjectsList.add(new SourceObject(sourceCodeObjects.getString("PROCEDURE_SCHEM"), + sourceCodeObjects.getString("PROCEDURE_TYPE"), + sourceCodeObjects.getString("PROCEDURE_NAME"), null)); + } + } + return sourceObjectsList; + } + + private List findSourceObjects(PreparedStatement sourceCodeObjectsStatement, + String language, String schema, String sourceCodeType, String sourceCodeName) throws SQLException { + List sourceObjectsList = new ArrayList<>(); + sourceCodeObjectsStatement.setString(1, language); + sourceCodeObjectsStatement.setString(2, schema); + sourceCodeObjectsStatement.setString(3, sourceCodeType); + sourceCodeObjectsStatement.setString(4, sourceCodeName); + LOGGER.finer(String.format( + "searching for language=\"%s\", schema=\"%s\", sourceCodeType=\"%s\", sourceCodeNames=\"%s\" ", + language, schema, sourceCodeType, sourceCodeName)); + + /* + * public ResultSet getProcedures(String catalog + * , String schemaPattern , String + * procedureNamePattern) throws SQLException + */ + try (ResultSet sourceCodeObjects = sourceCodeObjectsStatement.executeQuery()) { + + /* + * From Javadoc .... Each procedure description + * has the the following columns: PROCEDURE_CAT + * String => procedure catalog (may be null) + * PROCEDURE_SCHEM String => procedure schema + * (may be null) PROCEDURE_NAME String => + * procedure name reserved for future use + * reserved for future use reserved for future + * use REMARKS String => explanatory comment on + * the procedure PROCEDURE_TYPE short => kind of + * procedure: procedureResultUnknown - Cannot + * determine if a return value will be returned + * procedureNoResult - Does not return a return + * value procedureReturnsResult - Returns a + * return value SPECIFIC_NAME String => The name + * which uniquely identifies this procedure + * within its schema. + */ + while (sourceCodeObjects.next()) { + LOGGER.finest(String.format("Found schema=%s,object_type=%s,object_name=%s", + sourceCodeObjects.getString("PROCEDURE_SCHEM"), + sourceCodeObjects.getString("PROCEDURE_TYPE"), + sourceCodeObjects.getString("PROCEDURE_NAME"))); + + sourceObjectsList + .add(new SourceObject(sourceCodeObjects.getString("PROCEDURE_SCHEM"), + sourceCodeObjects.getString("PROCEDURE_TYPE"), + sourceCodeObjects.getString("PROCEDURE_NAME"), null)); + } + } + return sourceObjectsList; + } } diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/util/database/DBType.java b/pmd-core/src/main/java/net/sourceforge/pmd/util/database/DBType.java index 27b7e6975d8..56d8a978a08 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/util/database/DBType.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/util/database/DBType.java @@ -15,8 +15,6 @@ import java.util.logging.Level; import java.util.logging.Logger; -import org.apache.commons.io.IOUtils; - /** * Encapsulate the settings needed to access database source code. * @@ -159,7 +157,6 @@ private Properties loadDBProperties(String matchString) throws IOException { LOGGER.entering(DBType.class.getCanonicalName(), matchString); // Locale locale = Control.g; ResourceBundle resourceBundle = null; - InputStream stream = null; if (LOGGER.isLoggable(Level.FINEST)) { LOGGER.finest("class_path+" + System.getProperty("java.class.path")); @@ -170,12 +167,11 @@ private Properties loadDBProperties(String matchString) throws IOException { * properties suffix File path without properties suffix Resource * without class prefix Resource with class prefix */ - try { - File propertiesFile = new File(matchString); - if (LOGGER.isLoggable(Level.FINEST)) { - LOGGER.finest("Attempting File no file suffix: " + matchString); - } - stream = new FileInputStream(propertiesFile); + File propertiesFile = new File(matchString); + if (LOGGER.isLoggable(Level.FINEST)) { + LOGGER.finest("Attempting File no file suffix: " + matchString); + } + try (InputStream stream = new FileInputStream(propertiesFile)) { resourceBundle = new PropertyResourceBundle(stream); propertiesSource = propertiesFile.getAbsolutePath(); LOGGER.finest("FileSystemWithoutExtension"); @@ -184,9 +180,8 @@ private Properties loadDBProperties(String matchString) throws IOException { LOGGER.finest("notFoundOnFilesystemWithoutExtension"); LOGGER.finest("Attempting File with added file suffix: " + matchString + ".properties"); } - try { - File propertiesFile = new File(matchString + ".properties"); - stream = new FileInputStream(propertiesFile); + try (InputStream stream = new FileInputStream(propertiesFile)) { + propertiesFile = new File(matchString + ".properties"); resourceBundle = new PropertyResourceBundle(stream); propertiesSource = propertiesFile.getAbsolutePath(); LOGGER.finest("FileSystemWithExtension"); @@ -214,8 +209,6 @@ private Properties loadDBProperties(String matchString) throws IOException { } } } - } finally { - IOUtils.closeQuietly(stream); } // Properties in this matched resource diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/util/database/SourceObject.java b/pmd-core/src/main/java/net/sourceforge/pmd/util/database/SourceObject.java index 7b1d6f20252..5f73974de85 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/util/database/SourceObject.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/util/database/SourceObject.java @@ -4,6 +4,7 @@ package net.sourceforge.pmd.util.database; +import java.util.Locale; import java.util.logging.Logger; import net.sourceforge.pmd.cpd.SourceCode; @@ -130,21 +131,21 @@ public String getSuffixFromType() { LOG.entering(CLASS_NAME, "getSuffixFromType", this); if (null == type || type.isEmpty()) { return ""; - } else if (type.toUpperCase().indexOf("JAVA") >= 0) { + } else if (type.toUpperCase(Locale.ROOT).indexOf("JAVA") >= 0) { return ".java"; - } else if (type.toUpperCase().indexOf("TRIGGER") >= 0) { + } else if (type.toUpperCase(Locale.ROOT).indexOf("TRIGGER") >= 0) { return ".trg"; - } else if (type.toUpperCase().indexOf("FUNCTION") >= 0) { + } else if (type.toUpperCase(Locale.ROOT).indexOf("FUNCTION") >= 0) { return ".fnc"; - } else if (type.toUpperCase().indexOf("PROCEDURE") >= 0) { + } else if (type.toUpperCase(Locale.ROOT).indexOf("PROCEDURE") >= 0) { return ".prc"; - } else if (type.toUpperCase().indexOf("PACKAGE_BODY") >= 0) { + } else if (type.toUpperCase(Locale.ROOT).indexOf("PACKAGE_BODY") >= 0) { return ".pkb"; - } else if (type.toUpperCase().indexOf("PACKAGE") >= 0) { + } else if (type.toUpperCase(Locale.ROOT).indexOf("PACKAGE") >= 0) { return ".pks"; - } else if (type.toUpperCase().indexOf("TYPE_BODY") >= 0) { + } else if (type.toUpperCase(Locale.ROOT).indexOf("TYPE_BODY") >= 0) { return ".tpb"; - } else if (type.toUpperCase().indexOf("TYPE") >= 0) { + } else if (type.toUpperCase(Locale.ROOT).indexOf("TYPE") >= 0) { return ".tps"; } else { return ""; @@ -157,8 +158,7 @@ public String getSuffixFromType() { * parser is used. */ public String getPseudoFileName() { - String falseFilePath = String.format("/Database/%s/%s/%s%s", getSchema(), getType(), getName(), + return String.format("/Database/%s/%s/%s%s", getSchema(), getType(), getName(), getSuffixFromType()); - return falseFilePath; } } diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/util/datasource/FileDataSource.java b/pmd-core/src/main/java/net/sourceforge/pmd/util/datasource/FileDataSource.java index a3691033fc6..6c52cd2e29b 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/util/datasource/FileDataSource.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/util/datasource/FileDataSource.java @@ -8,6 +8,8 @@ import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; +import java.nio.file.Path; +import java.nio.file.Paths; /** * DataSource implementation to read data from a file. @@ -16,7 +18,7 @@ public class FileDataSource implements DataSource { private static final String FILE_SEPARATOR = System.getProperty("file.separator"); - private File file; + private final File file; /** * @param file @@ -37,14 +39,27 @@ public String getNiceFileName(boolean shortNames, String inputFileName) { } private String glomName(boolean shortNames, String inputFileName, File file) { - if (shortNames && inputFileName.indexOf(',') == -1) { - if (new File(inputFileName).isDirectory()) { - return trimAnyPathSep(file.getPath().substring(inputFileName.length())); - } else { - if (inputFileName.indexOf(FILE_SEPARATOR.charAt(0)) == -1) { - return inputFileName; + if (shortNames) { + if (inputFileName != null) { + for (final String prefix : inputFileName.split(",")) { + final Path prefPath = Paths.get(prefix).toAbsolutePath(); + final String prefPathString = prefPath.toString(); + final String absoluteFilePath = file.getAbsolutePath(); + + if (absoluteFilePath.startsWith(prefPathString)) { + if (prefPath.toFile().isDirectory()) { + return trimAnyPathSep(absoluteFilePath.substring(prefPathString.length())); + } else { + if (inputFileName.indexOf(FILE_SEPARATOR.charAt(0)) == -1) { + return inputFileName; + } + return trimAnyPathSep(absoluteFilePath.substring(prefPathString.lastIndexOf(FILE_SEPARATOR))); + } + } } - return trimAnyPathSep(inputFileName.substring(inputFileName.lastIndexOf(FILE_SEPARATOR))); + } else { + // if the 'master' file is not specified, just use the file name + return file.getName(); } } @@ -59,4 +74,45 @@ private String trimAnyPathSep(String name) { return name.startsWith(FILE_SEPARATOR) ? name.substring(1) : name; } + + @Override + public String toString() { + return new StringBuilder(FileDataSource.class.getSimpleName()) + .append('[') + .append(file.getPath()) + .append(']') + .toString(); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((file == null) ? 0 : file.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + FileDataSource other = (FileDataSource) obj; + if (file == null) { + if (other.file != null) { + return false; + } + } else if (!file.equals(other.file)) { + return false; + } + return true; + } + + } diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/util/datasource/ReaderDataSource.java b/pmd-core/src/main/java/net/sourceforge/pmd/util/datasource/ReaderDataSource.java index fe8d819a59d..a05632dbeab 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/util/datasource/ReaderDataSource.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/util/datasource/ReaderDataSource.java @@ -17,7 +17,7 @@ public class ReaderDataSource implements DataSource { /** * Reader */ - private Reader reader; + private final Reader reader; /** * Real or pseudo filename or path name. @@ -86,6 +86,10 @@ public void setDataSourceName(String dataSourceName) { @Override public String toString() { - return dataSourceName; + return new StringBuilder(ReaderDataSource.class.getSimpleName()) + .append('[') + .append(dataSourceName) + .append(']') + .toString(); } } diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/util/datasource/ZipDataSource.java b/pmd-core/src/main/java/net/sourceforge/pmd/util/datasource/ZipDataSource.java index 7667f7705a9..f24ea8b051f 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/util/datasource/ZipDataSource.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/util/datasource/ZipDataSource.java @@ -13,8 +13,8 @@ * DataSource implementation to read data from an entry in a zip or jar file. */ public class ZipDataSource implements DataSource { - private ZipFile zipFile; - private ZipEntry zipEntry; + private final ZipFile zipFile; + private final ZipEntry zipEntry; /** * @param zipFile @@ -37,4 +37,53 @@ public String getNiceFileName(boolean shortNames, String inputFileName) { // FIXME: this could probably be done better return zipFile.getName() + ":" + zipEntry.getName(); } + + @Override + public String toString() { + return new StringBuilder(ZipDataSource.class.getSimpleName()) + .append('[') + .append(zipFile.getName()) + .append('!') + .append(zipEntry.getName()) + .append(']') + .toString(); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((zipEntry == null) ? 0 : zipEntry.getName().hashCode()); + result = prime * result + ((zipFile == null) ? 0 : zipFile.getName().hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + ZipDataSource other = (ZipDataSource) obj; + if (zipEntry == null) { + if (other.zipEntry != null) { + return false; + } + } else if (!zipEntry.getName().equals(other.zipEntry.getName())) { + return false; + } + if (zipFile == null) { + if (other.zipFile != null) { + return false; + } + } else if (!zipFile.getName().equals(other.zipFile.getName())) { + return false; + } + return true; + } } diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/util/designer/CodeEditorTextPane.java b/pmd-core/src/main/java/net/sourceforge/pmd/util/designer/CodeEditorTextPane.java index cfa96661a81..ac13ce6ef5f 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/util/designer/CodeEditorTextPane.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/util/designer/CodeEditorTextPane.java @@ -8,6 +8,7 @@ import net.sourceforge.pmd.lang.ast.Node; +@Deprecated // to be removed with PMD 7.0.0 public class CodeEditorTextPane extends JTextPane implements LineGetter { private String[] getLines() { diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/util/designer/CreateXMLRulePanel.java b/pmd-core/src/main/java/net/sourceforge/pmd/util/designer/CreateXMLRulePanel.java index 006f421e530..fe8475bfc51 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/util/designer/CreateXMLRulePanel.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/util/designer/CreateXMLRulePanel.java @@ -8,7 +8,6 @@ import java.awt.GridBagLayout; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; - import javax.swing.JButton; import javax.swing.JLabel; import javax.swing.JPanel; @@ -24,6 +23,7 @@ * This class is responsible for creating the contentpanel for the Create Rule * XML Frame. */ +@Deprecated // to be removed with PMD 7.0.0 public class CreateXMLRulePanel extends JPanel implements ActionListener { private JTextField rulenameField = new JTextField(30); diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/util/designer/DFAPanel.java b/pmd-core/src/main/java/net/sourceforge/pmd/util/designer/DFAPanel.java index 9430ef18298..2e7dec8bf99 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/util/designer/DFAPanel.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/util/designer/DFAPanel.java @@ -10,7 +10,6 @@ import java.awt.FontMetrics; import java.awt.Graphics; import java.util.List; - import javax.swing.BorderFactory; import javax.swing.DefaultListModel; import javax.swing.JComponent; @@ -29,6 +28,7 @@ import net.sourceforge.pmd.lang.dfa.VariableAccess; import net.sourceforge.pmd.util.StringUtil; +@Deprecated // to be removed with PMD 7.0.0 public class DFAPanel extends JComponent implements ListSelectionListener { public static class DFACanvas extends JPanel { diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/util/designer/Designer.java b/pmd-core/src/main/java/net/sourceforge/pmd/util/designer/Designer.java index dceb47a3c12..c3c683ec44b 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/util/designer/Designer.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/util/designer/Designer.java @@ -35,7 +35,6 @@ import java.util.List; import java.util.Map; import java.util.Map.Entry; - import javax.swing.AbstractAction; import javax.swing.AbstractButton; import javax.swing.ActionMap; @@ -93,7 +92,6 @@ import javax.xml.transform.dom.DOMSource; import javax.xml.transform.stream.StreamResult; -import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.StringUtils; import org.w3c.dom.Document; import org.w3c.dom.Element; @@ -102,6 +100,7 @@ import net.sourceforge.pmd.PMD; import net.sourceforge.pmd.PMDConfiguration; +import net.sourceforge.pmd.PMDVersion; import net.sourceforge.pmd.RuleContext; import net.sourceforge.pmd.RuleSet; import net.sourceforge.pmd.RuleSetFactory; @@ -124,6 +123,7 @@ import net.sourceforge.pmd.lang.symboltable.ScopedNode; import net.sourceforge.pmd.lang.xpath.Initializer; +@Deprecated // to be removed with PMD 7.0.0 public class Designer implements ClipboardOwner { private boolean exitOnClose = true; @@ -134,7 +134,7 @@ public class Designer implements ClipboardOwner { private final JTextArea xpathQueryArea = new JTextArea(15, 30); private final ButtonGroup xpathVersionButtonGroup = new ButtonGroup(); private final TreeWidget symbolTableTreeWidget = new TreeWidget(new Object[0]); - private final JFrame frame = new JFrame("PMD Rule Designer (v " + PMD.VERSION + ')'); + private final JFrame frame = new JFrame("PMD Rule Designer (v " + PMDVersion.VERSION + ')'); private final DFAPanel dfaPanel = new DFAPanel(); private final JRadioButtonMenuItem[] languageVersionMenuItems = new JRadioButtonMenuItem[getSupportedLanguageVersions().length]; private static final String SETTINGS_FILE_NAME = System.getProperty("user.home") @@ -221,7 +221,7 @@ private static LanguageVersion[] getSupportedLanguageVersions() { } } } - return languageVersions.toArray(new LanguageVersion[languageVersions.size()]); + return languageVersions.toArray(new LanguageVersion[0]); } private LanguageVersion getLanguageVersion() { @@ -311,7 +311,7 @@ public String label() { @Override public Enumeration children() { - Enumeration e = new Enumeration() { + return new Enumeration() { int i = 0; @Override @@ -324,7 +324,6 @@ public ExceptionNode nextElement() { return kids[i++]; } }; - return e; } @Override @@ -394,7 +393,7 @@ public Enumeration children() { getChildAt(0); // force it to build kids } - Enumeration e = new Enumeration() { + return new Enumeration() { int i = 0; @Override @@ -407,7 +406,6 @@ public ASTTreeNode nextElement() { return kids[i++]; } }; - return e; } @Override @@ -911,6 +909,7 @@ public void actionPerformed(ActionEvent evt) { undoManager.undo(); } } catch (CannotUndoException e) { + throw new RuntimeException(e); } } }); @@ -924,6 +923,7 @@ public void actionPerformed(ActionEvent evt) { undoManager.redo(); } } catch (CannotRedoException e) { + throw new RuntimeException(e); } } }); @@ -987,15 +987,14 @@ private static String getXmlString(Node node) throws TransformerException { @Override public void lostOwnership(Clipboard clipboard, Transferable contents) { + // ignored } private void loadSettings() { - InputStream stream = null; - try { - File file = new File(SETTINGS_FILE_NAME); - if (file.exists()) { + File file = new File(SETTINGS_FILE_NAME); + if (file.exists()) { + try (InputStream stream = new FileInputStream(file)) { DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder(); - stream = new FileInputStream(file); Document document = builder.parse(stream); Element settingsElement = document.getDocumentElement(); Element codeElement = (Element) settingsElement.getElementsByTagName("code").item(0); @@ -1016,15 +1015,13 @@ private void loadSettings() { break; } } + } catch (ParserConfigurationException e) { + e.printStackTrace(); + } catch (IOException e) { + e.printStackTrace(); + } catch (SAXException e) { + e.printStackTrace(); } - } catch (ParserConfigurationException e) { - e.printStackTrace(); - } catch (IOException e) { - e.printStackTrace(); - } catch (SAXException e) { - e.printStackTrace(); - } finally { - IOUtils.closeQuietly(stream); } } diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/util/designer/LineGetter.java b/pmd-core/src/main/java/net/sourceforge/pmd/util/designer/LineGetter.java index fcd7743007b..4e657341fc6 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/util/designer/LineGetter.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/util/designer/LineGetter.java @@ -4,6 +4,7 @@ package net.sourceforge.pmd.util.designer; +@Deprecated // to be removed with PMD 7.0.0 public interface LineGetter { String getLine(int number); } diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/util/filter/AbstractDelegateFilter.java b/pmd-core/src/main/java/net/sourceforge/pmd/util/filter/AbstractDelegateFilter.java index 96ad9cb7c4a..eea8ddf9a3f 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/util/filter/AbstractDelegateFilter.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/util/filter/AbstractDelegateFilter.java @@ -15,6 +15,7 @@ public abstract class AbstractDelegateFilter implements Filter { protected Filter filter; public AbstractDelegateFilter() { + // default constructor } public AbstractDelegateFilter(Filter filter) { diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/util/filter/FileExtensionFilter.java b/pmd-core/src/main/java/net/sourceforge/pmd/util/filter/FileExtensionFilter.java index f0b18afc68c..ef6e5bae516 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/util/filter/FileExtensionFilter.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/util/filter/FileExtensionFilter.java @@ -5,6 +5,7 @@ package net.sourceforge.pmd.util.filter; import java.io.File; +import java.util.Locale; public class FileExtensionFilter implements Filter { protected final String[] extensions; @@ -25,7 +26,7 @@ public FileExtensionFilter(boolean ignoreCase, String... extensions) { this.ignoreCase = ignoreCase; if (ignoreCase) { for (int i = 0; i < this.extensions.length; i++) { - this.extensions[i] = this.extensions[i].toUpperCase(); + this.extensions[i] = this.extensions[i].toUpperCase(Locale.ROOT); } } } @@ -36,7 +37,7 @@ public boolean filter(File file) { if (!accept) { for (String extension : extensions) { String name = file.getName(); - if (ignoreCase ? name.toUpperCase().endsWith(extension) : name.endsWith(extension)) { + if (ignoreCase ? name.toUpperCase(Locale.ROOT).endsWith(extension) : name.endsWith(extension)) { accept = true; break; } diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/util/filter/Filters.java b/pmd-core/src/main/java/net/sourceforge/pmd/util/filter/Filters.java index 9e8fecd3095..7bd9b0c6304 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/util/filter/Filters.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/util/filter/Filters.java @@ -14,7 +14,7 @@ * Utility class for working with Filters. Contains builder style methods, apply * methods, as well as mechanisms for adapting Filters and FilenameFilters. */ -public class Filters { +public final class Filters { private Filters() { } diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/util/filter/RegexStringFilter.java b/pmd-core/src/main/java/net/sourceforge/pmd/util/filter/RegexStringFilter.java index 606416d5494..93fa19c2a98 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/util/filter/RegexStringFilter.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/util/filter/RegexStringFilter.java @@ -67,7 +67,7 @@ protected void optimize() { } else { try { this.pattern = Pattern.compile(this.regex); - } catch (PatternSyntaxException e) { + } catch (PatternSyntaxException ignored) { // If the regular expression is invalid, then pattern will be // null. } 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 fe15dba4440..26c29b702a2 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 @@ -6,12 +6,18 @@ 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; import java.util.logging.LogRecord; +import org.apache.tools.ant.BuildListener; +import org.apache.tools.ant.DefaultLogger; import org.apache.tools.ant.Project; +import org.apache.tools.ant.XmlLogger; +import org.apache.tools.ant.taskdefs.RecorderEntry; /** * AntLogHandler sends log messages to an Ant Task, so the regular Ant logging @@ -22,12 +28,63 @@ 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, // 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; + try { + if (l instanceof DefaultLogger) { + declaredField = DefaultLogger.class.getDeclaredField("msgOutputLevel"); + } else if (l instanceof XmlLogger) { + 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_DEBUG); + } + } + + if (declaredField != null) { + declaredField.setAccessible(true); + return LOG_LEVELS[declaredField.getInt(l)]; + } + + } catch (final ReflectiveOperationException ignored) { + // Just ignore it + } + } + + 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 public void publish(LogRecord logRecord) { // Map the log levels from java.util.logging to Ant @@ -62,9 +119,49 @@ public void publish(LogRecord logRecord) { @Override public void close() throws SecurityException { + // nothing to do } @Override 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/java/net/sourceforge/pmd/util/log/ConsoleLogHandler.java b/pmd-core/src/main/java/net/sourceforge/pmd/util/log/ConsoleLogHandler.java index 1092dddb5d7..8e61d3d27b7 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/util/log/ConsoleLogHandler.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/util/log/ConsoleLogHandler.java @@ -14,7 +14,9 @@ * Log to the console using a basic formatter. * * @author Wouter Zelle + * @deprecated This class will be complety removed in 7.0.0 */ +@Deprecated public class ConsoleLogHandler extends Handler { private static final Formatter FORMATTER = new PmdLogFormatter(); @@ -35,9 +37,11 @@ public void publish(LogRecord logRecord) { @Override public void close() throws SecurityException { + // nothing to do } @Override public void flush() { + System.out.flush(); } } diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/util/log/ScopedLogHandlersManager.java b/pmd-core/src/main/java/net/sourceforge/pmd/util/log/ScopedLogHandlersManager.java index c6e26b6dda6..53cb8249449 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/util/log/ScopedLogHandlersManager.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/util/log/ScopedLogHandlersManager.java @@ -36,7 +36,9 @@ public ScopedLogHandlersManager(Level level, Handler... handlers) { } for (Handler handler : newHandlers) { logger.addHandler(handler); + handler.setLevel(level); } + logger.setUseParentHandlers(false); } public void close() { @@ -47,5 +49,6 @@ public void close() { logger.addHandler(handler); } logger.setLevel(oldLogLevel); + logger.setUseParentHandlers(true); } } diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/util/viewer/Viewer.java b/pmd-core/src/main/java/net/sourceforge/pmd/util/viewer/Viewer.java index 2ae10bbaa47..a040cef667f 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/util/viewer/Viewer.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/util/viewer/Viewer.java @@ -13,7 +13,8 @@ * @author Boris Gruschko ( boris at gruschko.org ) * @version $Id$ */ -public class Viewer { +@Deprecated // to be removed with PMD 7.0.0 +public final class Viewer { private Viewer() { } diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/util/viewer/gui/ASTPanel.java b/pmd-core/src/main/java/net/sourceforge/pmd/util/viewer/gui/ASTPanel.java index 778b4c25586..190ce66c797 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/util/viewer/gui/ASTPanel.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/util/viewer/gui/ASTPanel.java @@ -10,7 +10,6 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; - import javax.swing.BorderFactory; import javax.swing.JPanel; import javax.swing.JPopupMenu; @@ -35,7 +34,7 @@ * @author Boris Gruschko ( boris at gruschko.org ) * @version $Id$ */ - +@Deprecated // to be removed with PMD 7.0.0 public class ASTPanel extends JPanel implements ViewerModelListener, TreeSelectionListener { private ViewerModel model; private JTree tree; diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/util/viewer/gui/ActionCommands.java b/pmd-core/src/main/java/net/sourceforge/pmd/util/viewer/gui/ActionCommands.java index 17d67665c2b..6f9f13dfc4a 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/util/viewer/gui/ActionCommands.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/util/viewer/gui/ActionCommands.java @@ -9,7 +9,7 @@ * * @author Boris Gruschko ( boris at gruschko.org ) */ - +@Deprecated // to be removed with PMD 7.0.0 public final class ActionCommands { public static final String COMPILE_ACTION = "Compile"; public static final String EVALUATE_ACTION = "Evaluate"; diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/util/viewer/gui/EvaluationResultsPanel.java b/pmd-core/src/main/java/net/sourceforge/pmd/util/viewer/gui/EvaluationResultsPanel.java index de6dd42be04..d6089da4ba2 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/util/viewer/gui/EvaluationResultsPanel.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/util/viewer/gui/EvaluationResultsPanel.java @@ -6,7 +6,6 @@ import java.awt.BorderLayout; import java.util.Vector; - import javax.swing.JList; import javax.swing.JPanel; import javax.swing.JScrollPane; @@ -25,6 +24,7 @@ * @author Boris Gruschko ( boris at gruschko.org ) * @version $Id$ */ +@Deprecated // to be removed with PMD 7.0.0 public class EvaluationResultsPanel extends JPanel implements ViewerModelListener { private ViewerModel model; private JList list; diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/util/viewer/gui/MainFrame.java b/pmd-core/src/main/java/net/sourceforge/pmd/util/viewer/gui/MainFrame.java index f6ef38715b4..c7cf8dbc699 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/util/viewer/gui/MainFrame.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/util/viewer/gui/MainFrame.java @@ -8,7 +8,6 @@ import java.awt.FlowLayout; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; - import javax.swing.ButtonGroup; import javax.swing.JButton; import javax.swing.JFrame; @@ -21,7 +20,7 @@ import javax.swing.SwingConstants; import javax.swing.WindowConstants; -import net.sourceforge.pmd.PMD; +import net.sourceforge.pmd.PMDVersion; import net.sourceforge.pmd.lang.LanguageRegistry; import net.sourceforge.pmd.lang.LanguageVersion; import net.sourceforge.pmd.lang.ast.ParseException; @@ -35,7 +34,7 @@ * * @author Boris Gruschko ( boris at gruschko.org ) */ - +@Deprecated // to be removed with PMD 7.0.0 public class MainFrame extends JFrame implements ActionListener, ViewerModelListener { private ViewerModel model; private SourceCodePanel sourcePanel; @@ -53,7 +52,7 @@ public class MainFrame extends JFrame implements ActionListener, ViewerModelList * constructs and shows the frame */ public MainFrame() { - super(NLS.nls("MAIN.FRAME.TITLE") + " (v " + PMD.VERSION + ')'); + super(NLS.nls("MAIN.FRAME.TITLE") + " (v " + PMDVersion.VERSION + ')'); init(); } diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/util/viewer/gui/ParseExceptionHandler.java b/pmd-core/src/main/java/net/sourceforge/pmd/util/viewer/gui/ParseExceptionHandler.java index c78b268c453..46a6411d20c 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/util/viewer/gui/ParseExceptionHandler.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/util/viewer/gui/ParseExceptionHandler.java @@ -8,7 +8,6 @@ import java.awt.FlowLayout; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; - import javax.swing.BorderFactory; import javax.swing.JButton; import javax.swing.JDialog; @@ -25,7 +24,7 @@ * @author Boris Gruschko ( boris at gruschko.org ) * @version $Id$ */ - +@Deprecated // to be removed with PMD 7.0.0 public class ParseExceptionHandler extends JDialog implements ActionListener { private Exception exc; private JButton okBtn; diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/util/viewer/gui/SourceCodePanel.java b/pmd-core/src/main/java/net/sourceforge/pmd/util/viewer/gui/SourceCodePanel.java index 35f6126627e..b5e1e6ba883 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/util/viewer/gui/SourceCodePanel.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/util/viewer/gui/SourceCodePanel.java @@ -6,7 +6,6 @@ import java.awt.BorderLayout; import java.awt.Color; - import javax.swing.BorderFactory; import javax.swing.JPanel; import javax.swing.JScrollPane; @@ -27,7 +26,7 @@ * @author Boris Gruschko ( boris at gruschko.org ) * @version $Id$ */ - +@Deprecated // to be removed with PMD 7.0.0 public class SourceCodePanel extends JPanel implements ViewerModelListener { private ViewerModel model; diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/util/viewer/gui/XPathPanel.java b/pmd-core/src/main/java/net/sourceforge/pmd/util/viewer/gui/XPathPanel.java index 3cf6b421b40..4c0fe0e7426 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/util/viewer/gui/XPathPanel.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/util/viewer/gui/XPathPanel.java @@ -5,7 +5,6 @@ package net.sourceforge.pmd.util.viewer.gui; import java.awt.Dimension; - import javax.swing.BorderFactory; import javax.swing.JScrollPane; import javax.swing.JTabbedPane; @@ -23,7 +22,7 @@ * @author Boris Gruschko ( boris at gruschko.org ) * @version $Id$ */ - +@Deprecated // to be removed with PMD 7.0.0 public class XPathPanel extends JTabbedPane implements ViewerModelListener { private ViewerModel model; private JTextArea xPathArea; diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/util/viewer/gui/menu/ASTNodePopupMenu.java b/pmd-core/src/main/java/net/sourceforge/pmd/util/viewer/gui/menu/ASTNodePopupMenu.java index 0b124b53b6b..bc4c2655529 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/util/viewer/gui/menu/ASTNodePopupMenu.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/util/viewer/gui/menu/ASTNodePopupMenu.java @@ -15,6 +15,7 @@ * @author Boris Gruschko ( boris at gruschko.org ) * @version $Id$ */ +@Deprecated // to be removed with PMD 7.0.0 public class ASTNodePopupMenu extends JPopupMenu { private ViewerModel model; private Node node; diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/util/viewer/gui/menu/AttributesSubMenu.java b/pmd-core/src/main/java/net/sourceforge/pmd/util/viewer/gui/menu/AttributesSubMenu.java index 54dd1862b14..0dc6331bd3c 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/util/viewer/gui/menu/AttributesSubMenu.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/util/viewer/gui/menu/AttributesSubMenu.java @@ -5,7 +5,6 @@ package net.sourceforge.pmd.util.viewer.gui.menu; import java.text.MessageFormat; - import javax.swing.JMenu; import net.sourceforge.pmd.lang.ast.Node; @@ -21,6 +20,7 @@ * @author Boris Gruschko ( boris at gruschko.org ) * @version $Id$ */ +@Deprecated // to be removed with PMD 7.0.0 public class AttributesSubMenu extends JMenu { private ViewerModel model; private Node node; diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/util/viewer/gui/menu/SimpleNodeSubMenu.java b/pmd-core/src/main/java/net/sourceforge/pmd/util/viewer/gui/menu/SimpleNodeSubMenu.java index d3323aec769..5c084d991ec 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/util/viewer/gui/menu/SimpleNodeSubMenu.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/util/viewer/gui/menu/SimpleNodeSubMenu.java @@ -5,7 +5,6 @@ package net.sourceforge.pmd.util.viewer.gui.menu; import java.text.MessageFormat; - import javax.swing.JMenu; import net.sourceforge.pmd.lang.ast.Node; @@ -18,6 +17,7 @@ * @author Boris Gruschko ( boris at gruschko.org ) * @version $Id$ */ +@Deprecated // to be removed with PMD 7.0.0 public class SimpleNodeSubMenu extends JMenu { private ViewerModel model; private Node node; diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/util/viewer/gui/menu/XPathFragmentAddingItem.java b/pmd-core/src/main/java/net/sourceforge/pmd/util/viewer/gui/menu/XPathFragmentAddingItem.java index aac92d912f7..11b0003fb88 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/util/viewer/gui/menu/XPathFragmentAddingItem.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/util/viewer/gui/menu/XPathFragmentAddingItem.java @@ -6,7 +6,6 @@ import java.awt.event.ActionEvent; import java.awt.event.ActionListener; - import javax.swing.JMenuItem; import net.sourceforge.pmd.util.viewer.model.ViewerModel; @@ -17,6 +16,7 @@ * @author Boris Gruschko ( boris at gruschko.org ) * @version $Id$ */ +@Deprecated // to be removed with PMD 7.0.0 public class XPathFragmentAddingItem extends JMenuItem implements ActionListener { private ViewerModel model; private String fragment; diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/util/viewer/model/ASTModel.java b/pmd-core/src/main/java/net/sourceforge/pmd/util/viewer/model/ASTModel.java index de0f82f7de8..72abb21646e 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/util/viewer/model/ASTModel.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/util/viewer/model/ASTModel.java @@ -6,7 +6,6 @@ import java.util.ArrayList; import java.util.List; - import javax.swing.event.TreeModelEvent; import javax.swing.event.TreeModelListener; import javax.swing.tree.TreeModel; @@ -20,7 +19,7 @@ * @author Boris Gruschko ( boris at gruschko.org ) * @version $Id$ */ - +@Deprecated // to be removed with PMD 7.0.0 public class ASTModel implements TreeModel { private Node root; diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/util/viewer/model/AttributeToolkit.java b/pmd-core/src/main/java/net/sourceforge/pmd/util/viewer/model/AttributeToolkit.java index cc03f0949f7..aedb293b715 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/util/viewer/model/AttributeToolkit.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/util/viewer/model/AttributeToolkit.java @@ -12,8 +12,8 @@ * @author Boris Gruschko ( boris at gruschko.org ) * @version $Id$ */ - -public class AttributeToolkit { +@Deprecated // to be removed with PMD 7.0.0 +public final class AttributeToolkit { private AttributeToolkit() { } diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/util/viewer/model/SimpleNodeTreeNodeAdapter.java b/pmd-core/src/main/java/net/sourceforge/pmd/util/viewer/model/SimpleNodeTreeNodeAdapter.java index 6190948ba07..c7dd5b4683c 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/util/viewer/model/SimpleNodeTreeNodeAdapter.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/util/viewer/model/SimpleNodeTreeNodeAdapter.java @@ -8,7 +8,6 @@ import java.util.Collections; import java.util.Enumeration; import java.util.List; - import javax.swing.tree.TreeNode; import net.sourceforge.pmd.lang.ast.Node; @@ -19,7 +18,7 @@ * @author Boris Gruschko ( boris at gruschko.org ) * @version $Id$ */ - +@Deprecated // to be removed with PMD 7.0.0 public class SimpleNodeTreeNodeAdapter implements TreeNode { private Node node; diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/util/viewer/model/ViewerModel.java b/pmd-core/src/main/java/net/sourceforge/pmd/util/viewer/model/ViewerModel.java index 93ef74f1f77..4369b7d8ae3 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/util/viewer/model/ViewerModel.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/util/viewer/model/ViewerModel.java @@ -20,6 +20,7 @@ import net.sourceforge.pmd.lang.ast.ParseException; import net.sourceforge.pmd.lang.ast.xpath.DocumentNavigator; +@Deprecated // to be removed with PMD 7.0.0 public class ViewerModel { private static final Logger LOGGER = Logger.getLogger(ViewerModel.class.getName()); diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/util/viewer/model/ViewerModelEvent.java b/pmd-core/src/main/java/net/sourceforge/pmd/util/viewer/model/ViewerModelEvent.java index 0c36a588e13..ab8f8122169 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/util/viewer/model/ViewerModelEvent.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/util/viewer/model/ViewerModelEvent.java @@ -11,6 +11,7 @@ * * @author Boris Gruschko ( boris at gruschko.org ) */ +@Deprecated // to be removed with PMD 7.0.0 public class ViewerModelEvent { /** * reason in the case of code recompilation diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/util/viewer/model/ViewerModelListener.java b/pmd-core/src/main/java/net/sourceforge/pmd/util/viewer/model/ViewerModelListener.java index e79e2a51157..a38a3826ab8 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/util/viewer/model/ViewerModelListener.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/util/viewer/model/ViewerModelListener.java @@ -10,6 +10,7 @@ * @author Boris Gruschko ( boris at gruschko.org ) * @version $Id$ */ +@Deprecated // to be removed with PMD 7.0.0 public interface ViewerModelListener { void viewerModelChanged(ViewerModelEvent e); } diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/util/viewer/util/NLS.java b/pmd-core/src/main/java/net/sourceforge/pmd/util/viewer/util/NLS.java index ed703f9899b..5848281e396 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/util/viewer/util/NLS.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/util/viewer/util/NLS.java @@ -12,7 +12,8 @@ * @author Boris Gruschko ( boris at gruschko.org ) * @version $Id$ */ -public class NLS { +@Deprecated // to be removed with PMD 7.0.0 +public final class NLS { private static final ResourceBundle BUNDLE; static { diff --git a/pmd-core/src/main/resources/ruleset_2_0_0.xsd b/pmd-core/src/main/resources/ruleset_2_0_0.xsd index d05e3269b4e..2af324694a0 100644 --- a/pmd-core/src/main/resources/ruleset_2_0_0.xsd +++ b/pmd-core/src/main/resources/ruleset_2_0_0.xsd @@ -8,83 +8,58 @@ - - - - + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - + + + + + - - + + + + + + + + + + + + - - - - - + + + diff --git a/pmd-core/src/main/resources/ruleset_3_0_0.dtd b/pmd-core/src/main/resources/ruleset_3_0_0.dtd deleted file mode 100644 index d13028e111c..00000000000 --- a/pmd-core/src/main/resources/ruleset_3_0_0.dtd +++ /dev/null @@ -1,33 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/pmd-core/src/main/resources/ruleset_3_0_0.xsd b/pmd-core/src/main/resources/ruleset_3_0_0.xsd deleted file mode 100644 index f45924922bc..00000000000 --- a/pmd-core/src/main/resources/ruleset_3_0_0.xsd +++ /dev/null @@ -1,91 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/pmd-core/src/main/resources/rulesets/internal/all-ecmascript.xml b/pmd-core/src/main/resources/rulesets/internal/all-ecmascript.xml index f83e1c08f07..e208b7cbb1b 100644 --- a/pmd-core/src/main/resources/rulesets/internal/all-ecmascript.xml +++ b/pmd-core/src/main/resources/rulesets/internal/all-ecmascript.xml @@ -3,10 +3,17 @@ + xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd"> Every ECMAScript Rule in PMD - - - + + + + + + + + + + diff --git a/pmd-core/src/main/resources/rulesets/internal/all-java.xml b/pmd-core/src/main/resources/rulesets/internal/all-java.xml index c5c86dda10c..569b015ced2 100644 --- a/pmd-core/src/main/resources/rulesets/internal/all-java.xml +++ b/pmd-core/src/main/resources/rulesets/internal/all-java.xml @@ -3,33 +3,33 @@ + xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd"> Every Java Rule in PMD - - - - - - - - - - - - - - - - - - - - - - - - - - + + + .*/ant/java/EncodingTestClass.java + .*/net/sourceforge/pmd/cpd/badandgood/BadFile.java + + + .*/net/sourceforge/pmd/lang/java/ast/jdkversiontests/assert_test5.java + .*/net/sourceforge/pmd/lang/java/ast/jdkversiontests/assert_test5_a.java + .*/net/sourceforge/pmd/lang/java/ast/jdkversiontests/assert_test7.java + .*/net/sourceforge/pmd/lang/java/ast/jdkversiontests/jdk14_enum.java + .*/net/sourceforge/pmd/lang/java/ast/jdkversiontests/jdk9_invalid_identifier.java + .*/net/sourceforge/pmd/lang/java/ast/jdkversiontests/java10/LocalVariableTypeInference_varAsAnnotationName.java + .*/net/sourceforge/pmd/lang/java/ast/jdkversiontests/java10/LocalVariableTypeInference_varAsEnumName.java + .*/net/sourceforge/pmd/lang/java/ast/jdkversiontests/java10/LocalVariableTypeInference_varAsTypeIdentifier.java + + + + + + + + + + diff --git a/pmd-core/src/main/resources/rulesets/internal/dogfood-goal.xml b/pmd-core/src/main/resources/rulesets/internal/dogfood-goal.xml deleted file mode 100644 index ff49b78093b..00000000000 --- a/pmd-core/src/main/resources/rulesets/internal/dogfood-goal.xml +++ /dev/null @@ -1,119 +0,0 @@ - - - Dogfood goal, delete this when we reach it. - - - .*net/sourceforge/pmd/lang/ast/JavaCharStream.java - .*net/sourceforge/pmd/lang/ast/SimpleCharStream.java - .*net/sourceforge/pmd/lang/ast/TokenMgrError.java - .*lang/(java|jsp|cpp)/ast/.*Parser.java - .*lang/(java|jsp|cpp)/ast/.*ParserConstants.java - .*lang/(java|jsp|cpp)/ast/.*ParserTokenManager.java - .*lang/(java|jsp|cpp)/ast/.*ParserTreeConstants.java - .*lang/(java|jsp|cpp)/ast/.*ParserVisitor.java - .*lang/(java|jsp|cpp)/ast/JJT.*ParserState.java - .*lang/(java|jsp|cpp)/ast/ParseException.java - .*lang/(java|jsp|cpp)/ast/Token.java - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/pmd-core/src/main/resources/rulesets/internal/dogfood.xml b/pmd-core/src/main/resources/rulesets/internal/dogfood.xml deleted file mode 100644 index 6c0db0f1bd3..00000000000 --- a/pmd-core/src/main/resources/rulesets/internal/dogfood.xml +++ /dev/null @@ -1,212 +0,0 @@ - - - Rules to check PMD itself. This RuleSet is written to include all Rules, - and exclude those which we know are explicitly decided as not applicable to PMD itself. - This is the most encompassing form of RuleSet. - - - .*net/sourceforge/pmd/lang/ast/JavaCharStream.java - .*net/sourceforge/pmd/lang/ast/SimpleCharStream.java - .*net/sourceforge/pmd/lang/ast/TokenMgrError.java - .*lang/(java|jsp|cpp)/ast/.*Parser.java - .*lang/(java|jsp|cpp)/ast/.*ParserConstants.java - .*lang/(java|jsp|cpp)/ast/.*ParserTokenManager.java - .*lang/(java|jsp|cpp)/ast/.*ParserTreeConstants.java - .*lang/(java|jsp|cpp)/ast/.*ParserVisitor.java - .*lang/(java|jsp|cpp)/ast/JJT.*ParserState.java - .*lang/(java|jsp|cpp)/ast/ParseException.java - .*lang/(java|jsp|cpp)/ast/Token.java - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/pmd-core/src/main/resources/rulesets/internal/pmdspecific.xml b/pmd-core/src/main/resources/rulesets/internal/pmdspecific.xml deleted file mode 100644 index fa8730ff4cd..00000000000 --- a/pmd-core/src/main/resources/rulesets/internal/pmdspecific.xml +++ /dev/null @@ -1,62 +0,0 @@ - - - - These rules check for problems that are specific to the PMD codebase and may not be applicable to other projects. - - - replace o.getClass().equals(MyClass.class) with o instanceof MyClass. Make sure MyClass doesn't have descendants - 3 - - - - - - - - - - - replace MyClass.class.equals(o.getClass()) with o instanceof MyClass. Make sure MyClass doesn't have descendants - 3 - - - - - - - - - - diff --git a/pmd-core/src/main/resources/rulesets/internal/regress-dogfood-goal.xml b/pmd-core/src/main/resources/rulesets/internal/regress-dogfood-goal.xml deleted file mode 100644 index c7d472daffc..00000000000 --- a/pmd-core/src/main/resources/rulesets/internal/regress-dogfood-goal.xml +++ /dev/null @@ -1,15 +0,0 @@ - - - - Rules to check PMD itself. - - .*/xml/.*.java - - - - - - diff --git a/pmd-core/src/main/resources/rulesets/internal/regress-dogfood.xml b/pmd-core/src/main/resources/rulesets/internal/regress-dogfood.xml deleted file mode 100644 index 1b1f8cee07d..00000000000 --- a/pmd-core/src/main/resources/rulesets/internal/regress-dogfood.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - - Rules to check PMD itself. - - .*/xml/.*.java - - - - - - - - - diff --git a/pmd-core/src/main/resources/rulesets/releases/33.xml b/pmd-core/src/main/resources/rulesets/releases/33.xml index a7cec977773..08d1dcd8bfb 100644 --- a/pmd-core/src/main/resources/rulesets/releases/33.xml +++ b/pmd-core/src/main/resources/rulesets/releases/33.xml @@ -3,7 +3,7 @@ + xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd"> This ruleset contains links to rules that are new in PMD v3.3 diff --git a/pmd-core/src/main/resources/rulesets/releases/34.xml b/pmd-core/src/main/resources/rulesets/releases/34.xml index 522db0729a3..8b61c84fcbb 100644 --- a/pmd-core/src/main/resources/rulesets/releases/34.xml +++ b/pmd-core/src/main/resources/rulesets/releases/34.xml @@ -3,7 +3,7 @@ + xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd"> This ruleset contains links to rules that are new in PMD v3.4 diff --git a/pmd-core/src/main/resources/rulesets/releases/35.xml b/pmd-core/src/main/resources/rulesets/releases/35.xml index cebee04b68c..7753b454c6e 100644 --- a/pmd-core/src/main/resources/rulesets/releases/35.xml +++ b/pmd-core/src/main/resources/rulesets/releases/35.xml @@ -3,7 +3,7 @@ + xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd"> This ruleset contains links to rules that are new in PMD v3.5 diff --git a/pmd-core/src/main/resources/rulesets/releases/36.xml b/pmd-core/src/main/resources/rulesets/releases/36.xml index 4b50e2792bc..8f60abd5944 100644 --- a/pmd-core/src/main/resources/rulesets/releases/36.xml +++ b/pmd-core/src/main/resources/rulesets/releases/36.xml @@ -3,7 +3,7 @@ + xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd"> This ruleset contains links to rules that are new in PMD v3.6 diff --git a/pmd-core/src/main/resources/rulesets/releases/37-jsp.xml b/pmd-core/src/main/resources/rulesets/releases/37-jsp.xml index 5fe02d9e81a..96bf1f2492a 100644 --- a/pmd-core/src/main/resources/rulesets/releases/37-jsp.xml +++ b/pmd-core/src/main/resources/rulesets/releases/37-jsp.xml @@ -3,7 +3,7 @@ + xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd"> This ruleset contains links to JSP rules that are new in PMD v3.7 diff --git a/pmd-core/src/main/resources/rulesets/releases/37.xml b/pmd-core/src/main/resources/rulesets/releases/37.xml index cc5672cadc5..b037858e3a4 100644 --- a/pmd-core/src/main/resources/rulesets/releases/37.xml +++ b/pmd-core/src/main/resources/rulesets/releases/37.xml @@ -3,7 +3,7 @@ + xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd"> This ruleset contains links to rules that are new in PMD v3.7 diff --git a/pmd-core/src/main/resources/rulesets/releases/38.xml b/pmd-core/src/main/resources/rulesets/releases/38.xml index 0a59caaa3bf..b0528c8caa9 100644 --- a/pmd-core/src/main/resources/rulesets/releases/38.xml +++ b/pmd-core/src/main/resources/rulesets/releases/38.xml @@ -3,7 +3,7 @@ + xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd"> This ruleset contains links to rules that are new in PMD v3.8 diff --git a/pmd-core/src/main/resources/rulesets/releases/39.xml b/pmd-core/src/main/resources/rulesets/releases/39.xml index 30a6eebf7ea..4e0e276d67c 100644 --- a/pmd-core/src/main/resources/rulesets/releases/39.xml +++ b/pmd-core/src/main/resources/rulesets/releases/39.xml @@ -3,7 +3,7 @@ + xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd"> This ruleset contains links to rules that are new in PMD v3.9 diff --git a/pmd-core/src/main/resources/rulesets/releases/40rc1.xml b/pmd-core/src/main/resources/rulesets/releases/40rc1.xml index 5f335ee5f99..132f866adf3 100644 --- a/pmd-core/src/main/resources/rulesets/releases/40rc1.xml +++ b/pmd-core/src/main/resources/rulesets/releases/40rc1.xml @@ -3,7 +3,7 @@ + xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd"> This ruleset contains links to rules that are new in PMD v4.0rc1 diff --git a/pmd-core/src/main/resources/rulesets/releases/41.xml b/pmd-core/src/main/resources/rulesets/releases/41.xml index d8308b0d75a..e04c7862e88 100644 --- a/pmd-core/src/main/resources/rulesets/releases/41.xml +++ b/pmd-core/src/main/resources/rulesets/releases/41.xml @@ -3,7 +3,7 @@ + xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd"> This ruleset contains links to rules that are new in PMD v4.1 diff --git a/pmd-core/src/main/resources/rulesets/releases/42.xml b/pmd-core/src/main/resources/rulesets/releases/42.xml index b5658742eca..01fe98bc027 100644 --- a/pmd-core/src/main/resources/rulesets/releases/42.xml +++ b/pmd-core/src/main/resources/rulesets/releases/42.xml @@ -3,7 +3,7 @@ + xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd"> This ruleset contains links to rules that are new in PMD v4.2 diff --git a/pmd-core/src/main/resources/rulesets/releases/50.xml b/pmd-core/src/main/resources/rulesets/releases/50.xml index 759dce17e93..6be499cf4c0 100644 --- a/pmd-core/src/main/resources/rulesets/releases/50.xml +++ b/pmd-core/src/main/resources/rulesets/releases/50.xml @@ -3,7 +3,7 @@ + xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd"> This ruleset contains links to rules that are new in PMD v5.0 diff --git a/pmd-core/src/main/resources/rulesets/releases/501.xml b/pmd-core/src/main/resources/rulesets/releases/501.xml index 7c8daab9c3d..46101afc690 100644 --- a/pmd-core/src/main/resources/rulesets/releases/501.xml +++ b/pmd-core/src/main/resources/rulesets/releases/501.xml @@ -3,7 +3,7 @@ + xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd"> This ruleset contains links to rules that are new in PMD v5.0.1 diff --git a/pmd-core/src/main/resources/rulesets/releases/510.xml b/pmd-core/src/main/resources/rulesets/releases/510.xml index 62349ba42b3..82e6c2530e8 100644 --- a/pmd-core/src/main/resources/rulesets/releases/510.xml +++ b/pmd-core/src/main/resources/rulesets/releases/510.xml @@ -3,7 +3,7 @@ + xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd"> This ruleset contains links to rules that are new in PMD v5.1.0 @@ -31,14 +31,14 @@ This ruleset contains links to rules that are new in PMD v5.1.0 - - - - - - - - - + + + + + + + + + diff --git a/pmd-core/src/main/resources/rulesets/releases/512.xml b/pmd-core/src/main/resources/rulesets/releases/512.xml index 79a98457d2c..d22d43ac4ab 100644 --- a/pmd-core/src/main/resources/rulesets/releases/512.xml +++ b/pmd-core/src/main/resources/rulesets/releases/512.xml @@ -3,7 +3,7 @@ + xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd"> This ruleset contains links to rules that are new in PMD v5.1.2 diff --git a/pmd-core/src/main/resources/rulesets/releases/520.xml b/pmd-core/src/main/resources/rulesets/releases/520.xml index 566e47d79e6..0d85130cd4d 100644 --- a/pmd-core/src/main/resources/rulesets/releases/520.xml +++ b/pmd-core/src/main/resources/rulesets/releases/520.xml @@ -3,7 +3,7 @@ + xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd"> This ruleset contains links to rules that are new in PMD v5.2.0 diff --git a/pmd-core/src/main/resources/rulesets/releases/540.xml b/pmd-core/src/main/resources/rulesets/releases/540.xml index 2086af32057..e7c4771122c 100644 --- a/pmd-core/src/main/resources/rulesets/releases/540.xml +++ b/pmd-core/src/main/resources/rulesets/releases/540.xml @@ -3,7 +3,7 @@ + xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd"> This ruleset contains links to rules that are new in PMD v5.4.0 diff --git a/pmd-core/src/main/resources/rulesets/releases/550.xml b/pmd-core/src/main/resources/rulesets/releases/550.xml index f5ca3d297f8..d7ac9f868ff 100644 --- a/pmd-core/src/main/resources/rulesets/releases/550.xml +++ b/pmd-core/src/main/resources/rulesets/releases/550.xml @@ -3,7 +3,7 @@ + xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd"> This ruleset contains links to rules that are new in PMD v5.5.0 @@ -20,7 +20,7 @@ This ruleset contains links to rules that are new in PMD v5.5.0 - + diff --git a/pmd-core/src/main/resources/rulesets/releases/551.xml b/pmd-core/src/main/resources/rulesets/releases/551.xml index 41d74d7037d..27a8321d9f0 100644 --- a/pmd-core/src/main/resources/rulesets/releases/551.xml +++ b/pmd-core/src/main/resources/rulesets/releases/551.xml @@ -3,7 +3,7 @@ + xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd"> This ruleset contains links to rules that are new in PMD v5.5.1 diff --git a/pmd-core/src/main/resources/rulesets/releases/552.xml b/pmd-core/src/main/resources/rulesets/releases/552.xml index dd70f67ef79..f236338c8bb 100644 --- a/pmd-core/src/main/resources/rulesets/releases/552.xml +++ b/pmd-core/src/main/resources/rulesets/releases/552.xml @@ -3,7 +3,7 @@ + xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd"> This ruleset contains links to rules that are new in PMD v5.5.2 diff --git a/pmd-core/src/main/resources/rulesets/releases/553.xml b/pmd-core/src/main/resources/rulesets/releases/553.xml index 4761403fb8d..0a52e3b6e39 100644 --- a/pmd-core/src/main/resources/rulesets/releases/553.xml +++ b/pmd-core/src/main/resources/rulesets/releases/553.xml @@ -3,7 +3,7 @@ + xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd"> This ruleset contains links to rules that are new in PMD v5.5.3 diff --git a/pmd-core/src/main/resources/rulesets/releases/554.xml b/pmd-core/src/main/resources/rulesets/releases/554.xml index 4e6b8f2d15b..e7ec18ff6c2 100644 --- a/pmd-core/src/main/resources/rulesets/releases/554.xml +++ b/pmd-core/src/main/resources/rulesets/releases/554.xml @@ -3,7 +3,7 @@ + xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd"> This ruleset contains links to rules that are new in PMD v5.5.4 diff --git a/pmd-core/src/main/resources/rulesets/releases/560.xml b/pmd-core/src/main/resources/rulesets/releases/560.xml index bb459e8df41..28f5d31f47b 100644 --- a/pmd-core/src/main/resources/rulesets/releases/560.xml +++ b/pmd-core/src/main/resources/rulesets/releases/560.xml @@ -3,7 +3,7 @@ + xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd"> This ruleset contains links to rules that are new in PMD v5.6.0 diff --git a/pmd-core/src/main/resources/rulesets/releases/580.xml b/pmd-core/src/main/resources/rulesets/releases/580.xml index 0478e1aa35e..826ebc56783 100644 --- a/pmd-core/src/main/resources/rulesets/releases/580.xml +++ b/pmd-core/src/main/resources/rulesets/releases/580.xml @@ -3,7 +3,7 @@ + xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd"> This ruleset contains links to rules that are new in PMD v5.8.0 diff --git a/pmd-core/src/main/resources/rulesets/releases/600.xml b/pmd-core/src/main/resources/rulesets/releases/600.xml index cebdca85293..629a435f5a5 100644 --- a/pmd-core/src/main/resources/rulesets/releases/600.xml +++ b/pmd-core/src/main/resources/rulesets/releases/600.xml @@ -3,13 +3,25 @@ + xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd"> This ruleset contains links to rules that are new in PMD v6.0.0 - - + + + + + + + + + + + + + + diff --git a/pmd-core/src/main/resources/rulesets/releases/6100.xml b/pmd-core/src/main/resources/rulesets/releases/6100.xml new file mode 100644 index 00000000000..61132559f27 --- /dev/null +++ b/pmd-core/src/main/resources/rulesets/releases/6100.xml @@ -0,0 +1,14 @@ + + + + +This ruleset contains links to rules that are new in PMD v6.10.0 + + + + + + diff --git a/pmd-core/src/main/resources/rulesets/releases/620.xml b/pmd-core/src/main/resources/rulesets/releases/620.xml new file mode 100644 index 00000000000..7da81bcbcdb --- /dev/null +++ b/pmd-core/src/main/resources/rulesets/releases/620.xml @@ -0,0 +1,15 @@ + + + + +This ruleset contains links to rules that are new in PMD v6.2.0 + + + + + + + diff --git a/pmd-core/src/main/resources/rulesets/releases/630.xml b/pmd-core/src/main/resources/rulesets/releases/630.xml new file mode 100644 index 00000000000..c2731348742 --- /dev/null +++ b/pmd-core/src/main/resources/rulesets/releases/630.xml @@ -0,0 +1,13 @@ + + + + +This ruleset contains links to rules that are new in PMD v6.3.0 + + + + + diff --git a/pmd-core/src/main/resources/rulesets/releases/640.xml b/pmd-core/src/main/resources/rulesets/releases/640.xml new file mode 100644 index 00000000000..716bc3bd9fc --- /dev/null +++ b/pmd-core/src/main/resources/rulesets/releases/640.xml @@ -0,0 +1,14 @@ + + + + +This ruleset contains links to rules that are new in PMD v6.4.0 + + + + + + diff --git a/pmd-core/src/main/resources/rulesets/releases/650.xml b/pmd-core/src/main/resources/rulesets/releases/650.xml new file mode 100644 index 00000000000..e4c22023403 --- /dev/null +++ b/pmd-core/src/main/resources/rulesets/releases/650.xml @@ -0,0 +1,13 @@ + + + + +This ruleset contains links to rules that are new in PMD v6.5.0 + + + + + diff --git a/pmd-core/src/main/resources/rulesets/releases/660.xml b/pmd-core/src/main/resources/rulesets/releases/660.xml new file mode 100644 index 00000000000..ddfc6b2081d --- /dev/null +++ b/pmd-core/src/main/resources/rulesets/releases/660.xml @@ -0,0 +1,14 @@ + + + + +This ruleset contains links to rules that are new in PMD v6.6.0 + + + + + + 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 00000000000..57924722a91 --- /dev/null +++ b/pmd-core/src/main/resources/rulesets/releases/670.xml @@ -0,0 +1,17 @@ + + + + +This ruleset contains links to rules that are new in PMD v6.7.0 + + + + + + + + + diff --git a/pmd-core/src/main/resources/rulesets/releases/680.xml b/pmd-core/src/main/resources/rulesets/releases/680.xml new file mode 100644 index 00000000000..900dc53a4a2 --- /dev/null +++ b/pmd-core/src/main/resources/rulesets/releases/680.xml @@ -0,0 +1,14 @@ + + + + +This ruleset contains links to rules that are new in PMD v6.8.0 + + + + + + diff --git a/pmd-core/src/main/resources/rulesets/releases/690.xml b/pmd-core/src/main/resources/rulesets/releases/690.xml new file mode 100644 index 00000000000..5a50dabc807 --- /dev/null +++ b/pmd-core/src/main/resources/rulesets/releases/690.xml @@ -0,0 +1,14 @@ + + + + +This ruleset contains links to rules that are new in PMD v6.9.0 + + + + + + diff --git a/pmd-core/src/test/java/net/sourceforge/pmd/AbstractRuleTest.java b/pmd-core/src/test/java/net/sourceforge/pmd/AbstractRuleTest.java index 9404532f1e3..999488418d1 100644 --- a/pmd-core/src/test/java/net/sourceforge/pmd/AbstractRuleTest.java +++ b/pmd-core/src/test/java/net/sourceforge/pmd/AbstractRuleTest.java @@ -26,12 +26,12 @@ public class AbstractRuleTest { - private static class MyRule extends AbstractRule { + public static class MyRule extends AbstractRule { private static final StringProperty FOO_PROPERTY = new StringProperty("foo", "foo property", "x", 1.0f); private static final StringProperty XPATH_PROPERTY = new StringProperty("xpath", "xpath property", "", 2.0f); - MyRule() { + public MyRule() { definePropertyDescriptor(FOO_PROPERTY); definePropertyDescriptor(XPATH_PROPERTY); setName("MyRule"); @@ -96,7 +96,7 @@ public void testCreateRV2() { @Test public void testRuleWithVariableInMessage() { MyRule r = new MyRule(); - r.definePropertyDescriptor(new IntegerProperty("testInt", "description", 0, 100, 10, 0)); + r.definePropertyDescriptor(IntegerProperty.named("testInt").desc("description").range(0, 100).defaultValue(10).uiOrder(0).build()); r.setMessage("Message ${packageName} ${className} ${methodName} ${variableName} ${testInt} ${noSuchProperty}"); RuleContext ctx = new RuleContext(); ctx.setLanguageVersion(LanguageRegistry.getLanguage(DummyLanguageModule.NAME).getDefaultVersion()); @@ -202,6 +202,26 @@ public void testEquals10() { assertEquals("Rules with different messages are still equal", r1, r2); assertEquals("Rules that are equal must have the an equal hashcode", r1.hashCode(), r2.hashCode()); } + + @Test + public void testDeepCopyRule() { + MyRule r1 = new MyRule(); + MyRule r2 = (MyRule) r1.deepCopy(); + assertEquals(r1.getDescription(), r2.getDescription()); + assertEquals(r1.getExamples(), r2.getExamples()); + assertEquals(r1.getExternalInfoUrl(), r2.getExternalInfoUrl()); + assertEquals(r1.getLanguage(), r2.getLanguage()); + assertEquals(r1.getMaximumLanguageVersion(), r2.getMaximumLanguageVersion()); + assertEquals(r1.getMessage(), r2.getMessage()); + assertEquals(r1.getMinimumLanguageVersion(), r2.getMinimumLanguageVersion()); + assertEquals(r1.getName(), r2.getName()); + assertEquals(r1.getPriority(), r2.getPriority()); + assertEquals(r1.getPropertyDescriptors(), r2.getPropertyDescriptors()); + assertEquals(r1.getRuleChainVisits(), r2.getRuleChainVisits()); + assertEquals(r1.getRuleClass(), r2.getRuleClass()); + assertEquals(r1.getRuleSetName(), r2.getRuleSetName()); + assertEquals(r1.getSince(), r2.getSince()); + } public static junit.framework.Test suite() { return new junit.framework.JUnit4TestAdapter(AbstractRuleTest.class); diff --git a/pmd-core/src/test/java/net/sourceforge/pmd/ConfigurationTest.java b/pmd-core/src/test/java/net/sourceforge/pmd/ConfigurationTest.java index 98f98291894..f1251cd25e1 100644 --- a/pmd-core/src/test/java/net/sourceforge/pmd/ConfigurationTest.java +++ b/pmd-core/src/test/java/net/sourceforge/pmd/ConfigurationTest.java @@ -5,6 +5,7 @@ package net.sourceforge.pmd; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; @@ -12,6 +13,7 @@ import java.io.File; import java.io.IOException; import java.net.URL; +import java.nio.charset.StandardCharsets; import java.util.Properties; import org.junit.Test; @@ -76,9 +78,9 @@ public void testMinimumPriority() { @Test public void testSourceEncoding() { PMDConfiguration configuration = new PMDConfiguration(); - assertEquals("Default source encoding", System.getProperty("file.encoding"), configuration.getSourceEncoding()); - configuration.setSourceEncoding("some_other_encoding"); - assertEquals("Changed source encoding", "some_other_encoding", configuration.getSourceEncoding()); + assertEquals("Default source encoding", System.getProperty("file.encoding"), configuration.getSourceEncoding().name()); + configuration.setSourceEncoding(StandardCharsets.UTF_16LE.name()); + assertEquals("Changed source encoding", StandardCharsets.UTF_16LE, configuration.getSourceEncoding()); } @Test @@ -198,4 +200,21 @@ public void testAnalysisCacheLocation() throws IOException { assertTrue("File cache location doesn't produce a file cache", configuration.getAnalysisCache() instanceof FileAnalysisCache); } + + + @Test + public void testIgnoreIncrementalAnalysis() throws IOException { + final PMDConfiguration configuration = new PMDConfiguration(); + + // set dummy cache location + final File cacheFile = File.createTempFile("pmd-", ".cache"); + cacheFile.deleteOnExit(); + final FileAnalysisCache analysisCache = new FileAnalysisCache(cacheFile); + configuration.setAnalysisCache(analysisCache); + assertNotNull("Null cache location accepted", configuration.getAnalysisCache()); + assertFalse("Non null cache location, cache should not be noop", configuration.getAnalysisCache() instanceof NoopAnalysisCache); + + configuration.setIgnoreIncrementalAnalysis(true); + assertTrue("Ignoring incremental analysis should turn the cache into a noop", configuration.getAnalysisCache() instanceof NoopAnalysisCache); + } } 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 3d8283ca680..b19eabfddf3 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 void testSortedReportFile() throws IOException { 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 @@ private static Node getNode(int line, int column) { 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/RuleSetFactoryCompatibilityTest.java b/pmd-core/src/test/java/net/sourceforge/pmd/RuleSetFactoryCompatibilityTest.java index 7bcb877f881..842c5f482fc 100644 --- a/pmd-core/src/test/java/net/sourceforge/pmd/RuleSetFactoryCompatibilityTest.java +++ b/pmd-core/src/test/java/net/sourceforge/pmd/RuleSetFactoryCompatibilityTest.java @@ -13,6 +13,8 @@ import org.junit.Assert; import org.junit.Test; +import net.sourceforge.pmd.util.ResourceLoader; + public class RuleSetFactoryCompatibilityTest { private static final Charset ISO_8859_1 = Charset.forName("ISO-8859-1"); private static final Charset UTF_8 = Charset.forName("UTF-8"); @@ -22,7 +24,7 @@ public void testCorrectOldReference() throws Exception { final String ruleset = "\n" + "\n" + "\n" + + " xsi:schemaLocation=\"http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd\">\n" + " Test\n" + "\n" + " \n" + "\n"; @@ -38,7 +40,7 @@ public void testCorrectMovedAndRename() throws Exception { final String ruleset = "\n" + "\n" + "\n" + + " xsi:schemaLocation=\"http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd\">\n" + " Test\n" + "\n" + " \n" + "\n"; @@ -60,7 +62,7 @@ public void testExclusion() throws Exception { final String ruleset = "\n" + "\n" + "\n" + + " xsi:schemaLocation=\"http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd\">\n" + " Test\n" + "\n" + " \n" + " \n" + " \n" + "\n"; @@ -83,7 +85,7 @@ public void testFilter() throws Exception { String in = "\n" + "\n" + "\n" + + " xsi:schemaLocation=\"http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd\">\n" + " Test\n" + "\n" + " \n" + " \n" @@ -109,7 +111,7 @@ public void testExclusionFilter() throws Exception { String in = "\n" + "\n" + "\n" + + " xsi:schemaLocation=\"http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd\">\n" + " Test\n" + "\n" + " \n" + " \n" + " \n" + "\n"; InputStream stream = new ByteArrayInputStream(in.getBytes(ISO_8859_1)); @@ -136,7 +138,7 @@ private RuleSet createRulesetFromString(final String ruleset, RuleSetFactory fac throws RuleSetNotFoundException { return factory.createRuleSet(new RuleSetReferenceId(null) { @Override - public InputStream getInputStream(ClassLoader classLoader) throws RuleSetNotFoundException { + public InputStream getInputStream(ResourceLoader resourceLoader) throws RuleSetNotFoundException { return new ByteArrayInputStream(ruleset.getBytes(UTF_8)); } }); diff --git a/pmd-core/src/test/java/net/sourceforge/pmd/RuleSetFactoryTest.java b/pmd-core/src/test/java/net/sourceforge/pmd/RuleSetFactoryTest.java index 966ecb6ffa5..f498d71ed01 100644 --- a/pmd-core/src/test/java/net/sourceforge/pmd/RuleSetFactoryTest.java +++ b/pmd-core/src/test/java/net/sourceforge/pmd/RuleSetFactoryTest.java @@ -20,6 +20,7 @@ import java.util.Set; import org.junit.Test; +import org.junit.rules.ExpectedException; import net.sourceforge.pmd.junit.JavaUtilLoggingRule; import net.sourceforge.pmd.lang.DummyLanguageModule; @@ -30,6 +31,10 @@ import net.sourceforge.pmd.util.ResourceLoader; public class RuleSetFactoryTest { + + @org.junit.Rule + public ExpectedException ex = ExpectedException.none(); + @Test public void testRuleSetFileName() throws RuleSetNotFoundException { RuleSet rs = loadRuleSet(EMPTY_RULESET); @@ -55,8 +60,7 @@ public void testRefs() throws Exception { @Test public void testExtendedReferences() throws Exception { - InputStream in = ResourceLoader.loadResourceAsStream("net/sourceforge/pmd/rulesets/reference-ruleset.xml", - this.getClass().getClassLoader()); + InputStream in = new ResourceLoader().loadClassPathResourceAsStream("net/sourceforge/pmd/rulesets/reference-ruleset.xml"); assertNotNull("Test ruleset not found - can't continue with test!", in); in.close(); @@ -175,7 +179,7 @@ public void testStringMultiPropertyDefaultDelimiter() throws Exception { + "class=\"net.sourceforge.pmd.lang.rule.XPathRule\" language=\"dummy\">\n" + " Please move your class to the right folder(rest \nfolder)\n" + " 2\n \n \n "); PropertyDescriptor> prop = (PropertyDescriptor>) r.getPropertyDescriptor("packageRegEx"); List values = r.getProperty(prop); @@ -190,7 +194,7 @@ public void testStringMultiPropertyDelimiter() throws Exception { + " instead.\" \n" + "class=\"net.sourceforge.pmd.lang.rule.XPathRule\" language=\"dummy\">\n" + " Please move your class to the right folder(rest \nfolder)\n" + " 2\n \n \n" + " " + ""); PropertyDescriptor> prop = (PropertyDescriptor>) r.getPropertyDescriptor("packageRegEx"); @@ -245,12 +249,12 @@ public void testXPath() throws RuleSetNotFoundException { @Test public void testFacadesOffByDefault() throws RuleSetNotFoundException { Rule r = loadFirstRule(XPATH); - assertFalse(r.usesDFA()); + assertFalse(r.isDfa()); } @Test public void testDFAFlag() throws RuleSetNotFoundException { - assertTrue(loadFirstRule(DFA).usesDFA()); + assertTrue(loadFirstRule(DFA).isDfa()); } @Test @@ -268,9 +272,13 @@ public void testExternalReferenceOverride() throws RuleSetNotFoundException { PropertyDescriptor test3Descriptor = r.getPropertyDescriptor("test3"); assertNotNull("test3 descriptor", test3Descriptor); assertEquals("override3", r.getProperty(test3Descriptor)); - PropertyDescriptor test4Descriptor = r.getPropertyDescriptor("test4"); - assertNotNull("test3 descriptor", test4Descriptor); - assertEquals("new property", r.getProperty(test4Descriptor)); + } + + @Test + public void testExternalReferenceOverrideNonExistent() throws RuleSetNotFoundException { + ex.expect(IllegalArgumentException.class); + ex.expectMessage("Cannot set non-existent property 'test4' on Rule TestNameOverride"); + loadFirstRule(REF_OVERRIDE_NONEXISTENT); } @Test @@ -325,7 +333,8 @@ public void testReferenceInternalToExternalChain() throws RuleSetNotFoundExcepti @Test public void testReferencePriority() throws RuleSetNotFoundException { - RuleSetFactory rsf = new RuleSetFactory(getClass().getClassLoader(), RulePriority.LOW, false, true); + ResourceLoader rl = new ResourceLoader(); + RuleSetFactory rsf = new RuleSetFactory(rl, RulePriority.LOW, false, true); RuleSet ruleSet = rsf.createRuleSet(createRuleSetReferenceId(REF_INTERNAL_TO_INTERNAL_CHAIN)); assertEquals("Number of Rules", 3, ruleSet.getRules().size()); @@ -333,36 +342,75 @@ public void testReferencePriority() throws RuleSetNotFoundException { assertNotNull(ruleSet.getRuleByName("MockRuleNameRef")); assertNotNull(ruleSet.getRuleByName("MockRuleNameRefRef")); - rsf = new RuleSetFactory(getClass().getClassLoader(), RulePriority.MEDIUM_HIGH, false, true); + rsf = new RuleSetFactory(rl, RulePriority.MEDIUM_HIGH, false, true); ruleSet = rsf.createRuleSet(createRuleSetReferenceId(REF_INTERNAL_TO_INTERNAL_CHAIN)); assertEquals("Number of Rules", 2, ruleSet.getRules().size()); assertNotNull(ruleSet.getRuleByName("MockRuleNameRef")); assertNotNull(ruleSet.getRuleByName("MockRuleNameRefRef")); - rsf = new RuleSetFactory(getClass().getClassLoader(), RulePriority.HIGH, false, true); + rsf = new RuleSetFactory(rl, RulePriority.HIGH, false, true); ruleSet = rsf.createRuleSet(createRuleSetReferenceId(REF_INTERNAL_TO_INTERNAL_CHAIN)); assertEquals("Number of Rules", 1, ruleSet.getRules().size()); assertNotNull(ruleSet.getRuleByName("MockRuleNameRefRef")); - rsf = new RuleSetFactory(getClass().getClassLoader(), RulePriority.LOW, false, true); + rsf = new RuleSetFactory(rl, RulePriority.LOW, false, true); ruleSet = rsf.createRuleSet(createRuleSetReferenceId(REF_INTERNAL_TO_EXTERNAL_CHAIN)); assertEquals("Number of Rules", 3, ruleSet.getRules().size()); assertNotNull(ruleSet.getRuleByName("ExternalRefRuleName")); assertNotNull(ruleSet.getRuleByName("ExternalRefRuleNameRef")); assertNotNull(ruleSet.getRuleByName("ExternalRefRuleNameRefRef")); - rsf = new RuleSetFactory(getClass().getClassLoader(), RulePriority.MEDIUM_HIGH, false, true); + rsf = new RuleSetFactory(rl, RulePriority.MEDIUM_HIGH, false, true); ruleSet = rsf.createRuleSet(createRuleSetReferenceId(REF_INTERNAL_TO_EXTERNAL_CHAIN)); assertEquals("Number of Rules", 2, ruleSet.getRules().size()); assertNotNull(ruleSet.getRuleByName("ExternalRefRuleNameRef")); assertNotNull(ruleSet.getRuleByName("ExternalRefRuleNameRefRef")); - rsf = new RuleSetFactory(getClass().getClassLoader(), RulePriority.HIGH, false, true); + rsf = new RuleSetFactory(rl, RulePriority.HIGH, false, true); ruleSet = rsf.createRuleSet(createRuleSetReferenceId(REF_INTERNAL_TO_EXTERNAL_CHAIN)); assertEquals("Number of Rules", 1, ruleSet.getRules().size()); assertNotNull(ruleSet.getRuleByName("ExternalRefRuleNameRefRef")); } + @Test + public void testOverridePriorityLoadWithMinimum() throws RuleSetNotFoundException { + RuleSetFactory rsf = new RuleSetFactory(new ResourceLoader(), RulePriority.MEDIUM_LOW, true, true); + RuleSet ruleset = rsf.createRuleSet("net/sourceforge/pmd/rulesets/ruleset-minimum-priority.xml"); + // only one rule should remain, since we filter out the other rules by minimum priority + assertEquals("Number of Rules", 1, ruleset.getRules().size()); + + // Priority is overridden and applied, rule is missing + assertNull(ruleset.getRuleByName("DummyBasicMockRule")); + + // that's the remaining rule + assertNotNull(ruleset.getRuleByName("SampleXPathRule")); + + // now, load with default minimum priority + rsf = new RuleSetFactory(); + ruleset = rsf.createRuleSet("net/sourceforge/pmd/rulesets/ruleset-minimum-priority.xml"); + assertEquals("Number of Rules", 2, ruleset.getRules().size()); + Rule dummyBasicMockRule = ruleset.getRuleByName("DummyBasicMockRule"); + assertEquals("Wrong Priority", RulePriority.LOW, dummyBasicMockRule.getPriority()); + } + + @Test + public void testExcludeWithMinimumPriority() throws RuleSetNotFoundException { + RuleSetFactory rsf = new RuleSetFactory(new ResourceLoader(), RulePriority.HIGH, true, true); + RuleSet ruleset = rsf.createRuleSet("net/sourceforge/pmd/rulesets/ruleset-minimum-priority-exclusion.xml"); + // no rules should be loaded + assertEquals("Number of Rules", 0, ruleset.getRules().size()); + + // now, load with default minimum priority + rsf = new RuleSetFactory(); + ruleset = rsf.createRuleSet("net/sourceforge/pmd/rulesets/ruleset-minimum-priority-exclusion.xml"); + // only one rule, we have excluded one... + assertEquals("Number of Rules", 1, ruleset.getRules().size()); + // rule is excluded + assertNull(ruleset.getRuleByName("DummyBasicMockRule")); + // that's the remaining rule + assertNotNull(ruleset.getRuleByName("SampleXPathRule")); + } + @Test public void testOverrideMessage() throws RuleSetNotFoundException { Rule r = loadFirstRule(REF_OVERRIDE_ORIGINAL_NAME); @@ -382,9 +430,10 @@ public void testIncorrectExternalRef() throws IllegalArgumentException, RuleSetN @Test public void testSetPriority() throws RuleSetNotFoundException { - RuleSetFactory rsf = new RuleSetFactory(getClass().getClassLoader(), RulePriority.MEDIUM_HIGH, false, true); + ResourceLoader rl = new ResourceLoader(); + RuleSetFactory rsf = new RuleSetFactory(rl, RulePriority.MEDIUM_HIGH, false, true); assertEquals(0, rsf.createRuleSet(createRuleSetReferenceId(SINGLE_RULE)).size()); - rsf = new RuleSetFactory(getClass().getClassLoader(), RulePriority.MEDIUM_LOW, false, true); + rsf = new RuleSetFactory(rl, RulePriority.MEDIUM_LOW, false, true); assertEquals(1, rsf.createRuleSet(createRuleSetReferenceId(SINGLE_RULE)).size()); } @@ -451,7 +500,7 @@ public void testRuleSetReferenceWithDeprecatedRule() throws RuleSetNotFoundExcep assertNotNull("RuleSet", ruleSet); assertFalse("RuleSet empty", ruleSet.getRules().isEmpty()); // No deprecated Rules should be loaded when loading an entire RuleSet - // by reference. + // by reference - unless it contains only deprecated rules - then all rules would be added Rule r = ruleSet.getRuleByName(DEPRECATED_RULE_NAME); assertNull("Deprecated Rule Reference", r); for (Rule rule : ruleSet.getRules()) { @@ -459,6 +508,13 @@ public void testRuleSetReferenceWithDeprecatedRule() throws RuleSetNotFoundExcep } } + @Test + public void testDeprecatedRuleSetReference() throws RuleSetNotFoundException { + RuleSetFactory ruleSetFactory = new RuleSetFactory(); + RuleSet ruleSet = ruleSetFactory.createRuleSet("net/sourceforge/pmd/rulesets/ruleset-deprecated.xml"); + assertEquals(2, ruleSet.getRules().size()); + } + @Test public void testExternalReferences() throws RuleSetNotFoundException { RuleSet rs = loadRuleSet(EXTERNAL_REFERENCE_RULE_SET); @@ -511,7 +567,7 @@ public void testEmptyRuleSetFile() throws Exception { RuleSetReferenceId ref = createRuleSetReferenceId("\n" + "\n" + "\n" + + " xsi:schemaLocation=\"http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd\">\n" + " PMD Ruleset.\n" + "\n" + " .*Test.*\n" + "\n" + "\n"); RuleSetFactory ruleSetFactory = new RuleSetFactory(); @@ -519,6 +575,29 @@ public void testEmptyRuleSetFile() throws Exception { assertEquals(0, ruleset.getRules().size()); } + /** + * See https://github.com/pmd/pmd/issues/782 + * Empty ruleset should be interpreted as deprecated. + * + * @throws Exception + * any error + */ + @Test + public void testEmptyRuleSetReferencedShouldNotBeDeprecated() throws Exception { + RuleSetReferenceId ref = createRuleSetReferenceId("\n" + "\n" + + "\n" + + " Ruleset which references a empty ruleset\n" + "\n" + + " \n" + + "\n"); + RuleSetFactory ruleSetFactory = new RuleSetFactory(new ResourceLoader(), RulePriority.LOW, true, true); + RuleSet ruleset = ruleSetFactory.createRuleSet(ref); + assertEquals(0, ruleset.getRules().size()); + + assertTrue(logging.getLog().isEmpty()); + } + /** * See https://sourceforge.net/p/pmd/bugs/1231/ * @@ -531,7 +610,7 @@ public void testWrongRuleNameReferenced() throws Exception { + "\n" + + " xsi:schemaLocation=\"http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd\">\n" + " Custom ruleset for tests\n" + " \n" + "\n"); RuleSetFactory ruleSetFactory = new RuleSetFactory(); @@ -550,7 +629,7 @@ public void testRuleReferenceWithNameOverridden() throws Exception { + "\n" + + " xsi:schemaLocation=\"http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd\">\n" + " PMD Plugin preferences rule set\n" + "\n" + "\n" + "\n" + "\n" + ""); @@ -575,7 +654,7 @@ public void testWrongRuleNameExcluded() throws Exception { "\n" + "\n" + + " xsi:schemaLocation=\"http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd\">\n" + " Custom ruleset for tests\n" + " \n" + " \n" + " \n" + "\n"); @@ -604,7 +683,7 @@ public void testExcludeAndImportTwice() throws Exception { "\n" + "\n" + + " xsi:schemaLocation=\"http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd\">\n" + " Custom ruleset for tests\n" + " \n" + " \n" + " \n" + "\n"); @@ -616,7 +695,7 @@ public void testExcludeAndImportTwice() throws Exception { "\n" + "\n" + + " xsi:schemaLocation=\"http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd\">\n" + " Custom ruleset for tests\n" + " \n" + " \n" + " \n" + " \n" + "\n"); @@ -628,7 +707,7 @@ public void testExcludeAndImportTwice() throws Exception { "\n" + "\n" + + " xsi:schemaLocation=\"http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd\">\n" + " Custom ruleset for tests\n" + " \n" + " \n" + " \n" + " \n" + "\n"); @@ -646,7 +725,7 @@ public void testMissingRuleSetNameIsWarning() throws Exception { "\n" + "\n" + + " xsi:schemaLocation=\"http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd\">\n" + " Custom ruleset for tests\n" + " \n" + " \n"); @@ -662,7 +741,7 @@ public void testMissingRuleSetDescriptionIsWarning() throws Exception { "\n" + "\n" + + " xsi:schemaLocation=\"http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd\">\n" + " \n" + " \n"); RuleSetFactory ruleSetFactory = new RuleSetFactory(); @@ -687,16 +766,26 @@ public void testMissingRuleSetDescriptionIsWarning() throws Exception { private static final String REF_OVERRIDE = "" + PMD.EOL + "" + PMD.EOL + " testdesc" + PMD.EOL + " " + PMD.EOL + " Test description override" + PMD.EOL + " Test example override" + PMD.EOL + " 3" + PMD.EOL + " " + PMD.EOL + " " + PMD.EOL - + " override3" - + PMD.EOL + " " + + " override3" + // + PMD.EOL + " " // Nonsense + PMD.EOL + " " + PMD.EOL + " " + PMD.EOL + ""; + private static final String REF_OVERRIDE_NONEXISTENT = "" + PMD.EOL + "" + PMD.EOL + + " testdesc" + PMD.EOL + " " + PMD.EOL + + " Test description override" + PMD.EOL + + " Test example override" + PMD.EOL + " 3" + PMD.EOL + + " " + PMD.EOL + + " " + PMD.EOL // inexistent property + + " " + PMD.EOL + " " + PMD.EOL + ""; + private static final String REF_INTERNAL_TO_INTERNAL = "" + PMD.EOL + "" + PMD.EOL + " testdesc" + PMD.EOL + "" + PMD.EOL + "" + PMD.EOL + " " + PMD.EOL + " This ruleset checks my code for bad stuff" + PMD.EOL @@ -134,7 +90,6 @@ private String generateRuleSet(String version, boolean withMetrics) { + " " + PMD.EOL + " " + PMD.EOL + " Just for test" + PMD.EOL @@ -152,15 +107,12 @@ private String generateRuleSet(String version, boolean withMetrics) { public static class PMDRuleSetEntityResolver implements EntityResolver { private static URL schema2 = RuleSetFactory.class.getResource("/ruleset_2_0_0.xsd"); - private static URL schema3 = RuleSetFactory.class.getResource("/ruleset_3_0_0.xsd"); private static SchemaFactory schemaFactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI); @Override public InputSource resolveEntity(String publicId, String systemId) throws SAXException, IOException { - if ("http://pmd.sourceforge.net/ruleset_2_0_0.xsd".equals(systemId)) { + if ("https://pmd.sourceforge.io/ruleset_2_0_0.xsd".equals(systemId)) { return new InputSource(schema2.toExternalForm()); - } else if ("http://pmd.sourceforge.net/ruleset_3_0_0.xsd".equals(systemId)) { - return new InputSource(schema3.toExternalForm()); } throw new IllegalArgumentException("Unable to resolve entity (publicId=" + publicId + ", systemId=" + systemId + ")"); } @@ -168,10 +120,6 @@ public InputSource resolveEntity(String publicId, String systemId) throws SAXExc public static Schema getSchemaVersion2() throws SAXException { return schemaFactory.newSchema(schema2); } - - public static Schema getSchemaVersion3() throws SAXException { - return schemaFactory.newSchema(schema3); - } } public static class CollectingErrorHandler implements ErrorHandler { 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 831fa44691c..3d47494e861 100644 --- a/pmd-core/src/test/java/net/sourceforge/pmd/RuleSetTest.java +++ b/pmd-core/src/test/java/net/sourceforge/pmd/RuleSetTest.java @@ -22,6 +22,7 @@ import org.junit.Test; +import net.sourceforge.pmd.Report.ProcessingError; import net.sourceforge.pmd.RuleSet.RuleSetBuilder; import net.sourceforge.pmd.lang.Dummy2LanguageModule; import net.sourceforge.pmd.lang.DummyLanguageModule; @@ -30,6 +31,7 @@ import net.sourceforge.pmd.lang.ast.Node; import net.sourceforge.pmd.lang.rule.MockRule; import net.sourceforge.pmd.lang.rule.RuleReference; +import net.sourceforge.pmd.util.CollectionUtil; public class RuleSetTest { @@ -63,7 +65,7 @@ public void testNoDFA() { public void testIncludesRuleWithDFA() { MockRule mock = new MockRule("name", "desc", "msg", "rulesetname"); mock.setLanguage(LanguageRegistry.getLanguage(DummyLanguageModule.NAME)); - mock.setUsesDFA(); + mock.setDfa(true); RuleSet rs = new RuleSetFactory().createSingleRuleRuleSet(mock); assertTrue(rs.usesDFA(LanguageRegistry.getLanguage(DummyLanguageModule.NAME))); } @@ -416,7 +418,7 @@ public void testIncludeExcludeMultipleRuleSetWithRuleChainApplies() throws PMDEx rule.setName("FooRule1"); rule.setLanguage(LanguageRegistry.getLanguage(DummyLanguageModule.NAME)); rule.addRuleChainVisit("dummyNode"); - assertTrue("RuleChain rule", rule.usesRuleChain()); + assertTrue("RuleChain rule", rule.isRuleChain()); RuleSet ruleSet1 = createRuleSetBuilder("RuleSet1") .addRule(rule) .build(); @@ -507,4 +509,118 @@ private List makeCompilationUnits() { nodes.add(node); return nodes; } + + @Test + public void ruleExceptionShouldBeReported() { + RuleSet ruleset = createRuleSetBuilder("ruleExceptionShouldBeReported") + .addRule(new MockRule() { + @Override + public void apply(List nodes, RuleContext ctx) { + throw new RuntimeException("Test exception while applying rule"); + } + }) + .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); + } + + @Test(expected = RuntimeException.class) + public void ruleExceptionShouldBeThrownIfNotIgnored() { + RuleSet ruleset = createRuleSetBuilder("ruleExceptionShouldBeReported") + .addRule(new MockRule() { + @Override + public void apply(List nodes, RuleContext ctx) { + throw new RuntimeException("Test exception while applying rule"); + } + }) + .build(); + RuleContext context = new RuleContext(); + context.setReport(new Report()); + context.setLanguageVersion(LanguageRegistry.getLanguage(DummyLanguageModule.NAME).getDefaultVersion()); + context.setSourceCodeFilename(RuleSetTest.class.getName() + ".ruleExceptionShouldBeThrownIfNotIgnored"); + 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/cache/FileAnalysisCacheTest.java b/pmd-core/src/test/java/net/sourceforge/pmd/cache/FileAnalysisCacheTest.java index 973d2276638..50bc478d297 100644 --- a/pmd-core/src/test/java/net/sourceforge/pmd/cache/FileAnalysisCacheTest.java +++ b/pmd-core/src/test/java/net/sourceforge/pmd/cache/FileAnalysisCacheTest.java @@ -24,6 +24,7 @@ import org.junit.Before; import org.junit.Rule; import org.junit.Test; +import org.junit.contrib.java.lang.system.RestoreSystemProperties; import org.junit.rules.TemporaryFolder; import org.mockito.Mockito; @@ -35,6 +36,9 @@ public class FileAnalysisCacheTest { @Rule public TemporaryFolder tempFolder = new TemporaryFolder(); + @Rule + public RestoreSystemProperties restoreSystemProperties = new RestoreSystemProperties(); + private File unexistingCacheFile; private File newCacheFile; private File emptyCacheFile; @@ -110,7 +114,7 @@ public void testStorePersistsFilesWithViolations() { @Test public void testCacheValidityWithNoChanges() { final RuleSets rs = mock(RuleSets.class); - final URLClassLoader cl = mock(URLClassLoader.class); + final ClassLoader cl = mock(ClassLoader.class); setupCacheWithFiles(newCacheFile, rs, cl, sourceFile); @@ -123,7 +127,7 @@ public void testCacheValidityWithNoChanges() { @Test public void testRulesetChangeInvalidatesCache() { final RuleSets rs = mock(RuleSets.class); - final URLClassLoader cl = mock(URLClassLoader.class); + final ClassLoader cl = mock(ClassLoader.class); setupCacheWithFiles(newCacheFile, rs, cl, sourceFile); @@ -135,36 +139,122 @@ public void testRulesetChangeInvalidatesCache() { } @Test - public void testClasspathChangeWithoutDFAorTypeResolutionDoesNotInvalidatesCache() throws MalformedURLException, IOException { + public void testAuxClasspathNonExistingAuxclasspathEntriesIgnored() throws MalformedURLException, IOException { final RuleSets rs = mock(RuleSets.class); final URLClassLoader cl = mock(URLClassLoader.class); + when(cl.getURLs()).thenReturn(new URL[] { new File(tempFolder.getRoot(), "non-existing-dir").toURI().toURL(), }); + + setupCacheWithFiles(newCacheFile, rs, cl, sourceFile); + + final FileAnalysisCache analysisCache = new FileAnalysisCache(newCacheFile); + when(cl.getURLs()).thenReturn(new URL[] {}); + analysisCache.checkValidity(rs, cl); + assertTrue("Cache believes unmodified file is not up to date after non-existing auxclasspath entry removed", + analysisCache.isUpToDate(sourceFile)); + } + + @Test + public void testAuxClasspathChangeWithoutDFAorTypeResolutionDoesNotInvalidatesCache() throws MalformedURLException, IOException { + final RuleSets rs = mock(RuleSets.class); + final URLClassLoader cl = mock(URLClassLoader.class); + when(cl.getURLs()).thenReturn(new URL[] { }); setupCacheWithFiles(newCacheFile, rs, cl, sourceFile); final FileAnalysisCache reloadedCache = new FileAnalysisCache(newCacheFile); when(cl.getURLs()).thenReturn(new URL[] { tempFolder.newFile().toURI().toURL(), }); reloadedCache.checkValidity(rs, cl); - assertTrue("Cache believes unmodified file is not up to date after classpath changed when no rule cares", + assertTrue("Cache believes unmodified file is not up to date after auxclasspath changed when no rule cares", reloadedCache.isUpToDate(sourceFile)); } @Test - public void testClasspathChangeInvalidatesCache() throws MalformedURLException, IOException { + public void testAuxClasspathChangeInvalidatesCache() throws MalformedURLException, IOException { final RuleSets rs = mock(RuleSets.class); final URLClassLoader cl = mock(URLClassLoader.class); + when(cl.getURLs()).thenReturn(new URL[] { }); setupCacheWithFiles(newCacheFile, rs, cl, sourceFile); final FileAnalysisCache reloadedCache = new FileAnalysisCache(newCacheFile); - when(cl.getURLs()).thenReturn(new URL[] { tempFolder.newFile().toURI().toURL(), }); + final File classpathFile = tempFolder.newFile(); + when(cl.getURLs()).thenReturn(new URL[] { classpathFile.toURI().toURL(), }); + + // Make sure the auxclasspath file is not empty + Files.write(Paths.get(classpathFile.getAbsolutePath()), "some text".getBytes()); + + final net.sourceforge.pmd.Rule r = mock(net.sourceforge.pmd.Rule.class); + when(r.isDfa()).thenReturn(true); + when(rs.getAllRules()).thenReturn(Collections.singleton(r)); + reloadedCache.checkValidity(rs, cl); + assertFalse("Cache believes unmodified file is up to date after auxclasspath changed", + reloadedCache.isUpToDate(sourceFile)); + } + + @Test + public void testAuxClasspathJarContentsChangeInvalidatesCache() throws MalformedURLException, IOException { + final RuleSets rs = mock(RuleSets.class); + final URLClassLoader cl = mock(URLClassLoader.class); + + final File classpathFile = tempFolder.newFile(); + when(cl.getURLs()).thenReturn(new URL[] { classpathFile.toURI().toURL(), }); + final net.sourceforge.pmd.Rule r = mock(net.sourceforge.pmd.Rule.class); - when(r.usesDFA()).thenReturn(true); + when(r.isDfa()).thenReturn(true); when(rs.getAllRules()).thenReturn(Collections.singleton(r)); + + setupCacheWithFiles(newCacheFile, rs, cl, sourceFile); + + // Edit the auxclasspath referenced file + Files.write(Paths.get(classpathFile.getAbsolutePath()), "some text".getBytes()); + + final FileAnalysisCache reloadedCache = new FileAnalysisCache(newCacheFile); reloadedCache.checkValidity(rs, cl); - assertFalse("Cache believes unmodified file is up to date after classpath changed", + assertFalse("Cache believes cache is up to date when a auxclasspath file changed", reloadedCache.isUpToDate(sourceFile)); } + @Test + public void testClasspathChangeInvalidatesCache() throws MalformedURLException, IOException { + final RuleSets rs = mock(RuleSets.class); + final ClassLoader cl = mock(ClassLoader.class); + + final File classpathFile = tempFolder.newFile(); + + setupCacheWithFiles(newCacheFile, rs, cl, sourceFile); + + // Edit the classpath referenced file + Files.write(Paths.get(classpathFile.getAbsolutePath()), "some text".getBytes()); + System.setProperty("java.class.path", System.getProperty("java.class.path") + File.pathSeparator + classpathFile.getAbsolutePath()); + + final FileAnalysisCache reloadedCache = new FileAnalysisCache(newCacheFile); + reloadedCache.checkValidity(rs, cl); + assertFalse("Cache believes cache is up to date when the classpath changed", + reloadedCache.isUpToDate(sourceFile)); + } + + @Test + public void testClasspathContentsChangeInvalidatesCache() throws MalformedURLException, IOException { + final RuleSets rs = mock(RuleSets.class); + final ClassLoader cl = mock(ClassLoader.class); + + final File classpathFile = tempFolder.newFile(); + + // Add a file to classpath + Files.write(Paths.get(classpathFile.getAbsolutePath()), "some text".getBytes()); + System.setProperty("java.class.path", System.getProperty("java.class.path") + File.pathSeparator + classpathFile.getAbsolutePath()); + + setupCacheWithFiles(newCacheFile, rs, cl, sourceFile); + + // Change the file's contents + Files.write(Paths.get(classpathFile.getAbsolutePath()), "some other text".getBytes()); + + final FileAnalysisCache reloadedCache = new FileAnalysisCache(newCacheFile); + reloadedCache.checkValidity(rs, cl); + assertFalse("Cache believes cache is up to date when a classpath file changed", + reloadedCache.isUpToDate(sourceFile)); + } + @Test public void testUnknownFileIsNotUpToDate() throws IOException { final FileAnalysisCache cache = new FileAnalysisCache(newCacheFile); diff --git a/pmd-core/src/test/java/net/sourceforge/pmd/cli/PMDCommandLineInterfaceTest.java b/pmd-core/src/test/java/net/sourceforge/pmd/cli/PMDCommandLineInterfaceTest.java index 7acaa377c89..ef775a2f7a3 100644 --- a/pmd-core/src/test/java/net/sourceforge/pmd/cli/PMDCommandLineInterfaceTest.java +++ b/pmd-core/src/test/java/net/sourceforge/pmd/cli/PMDCommandLineInterfaceTest.java @@ -4,6 +4,8 @@ package net.sourceforge.pmd.cli; +import static org.junit.Assert.assertTrue; + import org.junit.Assert; import org.junit.Before; import org.junit.Rule; @@ -11,6 +13,10 @@ import org.junit.contrib.java.lang.system.ExpectedSystemExit; import org.junit.contrib.java.lang.system.RestoreSystemProperties; +import net.sourceforge.pmd.PMDConfiguration; +import net.sourceforge.pmd.cache.NoopAnalysisCache; + + /** * Unit test for {@link PMDCommandLineInterface} */ @@ -48,6 +54,19 @@ public void testMultipleProperties() { Assert.assertEquals("Foo.method", params.getProperties().getProperty("classAndMethodName")); } + + @Test + public void testNoCacheSwitch() { + PMDParameters params = new PMDParameters(); + String[] args = {"-d", "source_folder", "-f", "ideaj", "-R", "java-empty", "-cache", "/home/user/.pmd/cache", "-no-cache", }; + PMDCommandLineInterface.extractParameters(params, args, "PMD"); + + assertTrue(params.isIgnoreIncrementalAnalysis()); + PMDConfiguration config = params.toConfiguration(); + assertTrue(config.isIgnoreIncrementalAnalysis()); + assertTrue(config.getAnalysisCache() instanceof NoopAnalysisCache); + } + @Test public void testSetStatusCodeOrExitDoExit() { exit.expectSystemExitWithStatus(0); diff --git a/pmd-core/src/test/java/net/sourceforge/pmd/cli/PMDFilelistTest.java b/pmd-core/src/test/java/net/sourceforge/pmd/cli/PMDFilelistTest.java index b138dda52a8..65943d5b81d 100644 --- a/pmd-core/src/test/java/net/sourceforge/pmd/cli/PMDFilelistTest.java +++ b/pmd-core/src/test/java/net/sourceforge/pmd/cli/PMDFilelistTest.java @@ -4,6 +4,8 @@ package net.sourceforge.pmd.cli; +import java.util.Collections; +import java.util.Comparator; import java.util.HashSet; import java.util.List; import java.util.Set; @@ -47,4 +49,47 @@ public void testGetApplicableFilesMultipleLines() { Assert.assertTrue(applicableFiles.get(2).getNiceFileName(false, "").endsWith("somefile.dummy")); } + @Test + public void testGetApplicatbleFilesWithIgnores() { + Set languages = new HashSet<>(); + languages.add(new DummyLanguageModule()); + + PMDConfiguration configuration = new PMDConfiguration(); + configuration.setInputFilePath("src/test/resources/net/sourceforge/pmd/cli/filelist3.txt"); + configuration.setIgnoreFilePath("src/test/resources/net/sourceforge/pmd/cli/ignorelist.txt"); + + List applicableFiles = PMD.getApplicableFiles(configuration, languages); + Assert.assertEquals(2, applicableFiles.size()); + Assert.assertTrue(applicableFiles.get(0).getNiceFileName(false, "").endsWith("somefile2.dummy")); + Assert.assertTrue(applicableFiles.get(1).getNiceFileName(false, "").endsWith("somefile4.dummy")); + } + + @Test + public void testGetApplicatbleFilesWithDirAndIgnores() { + Set languages = new HashSet<>(); + languages.add(new DummyLanguageModule()); + + PMDConfiguration configuration = new PMDConfiguration(); + configuration.setInputPaths("src/test/resources/net/sourceforge/pmd/cli/src"); + configuration.setIgnoreFilePath("src/test/resources/net/sourceforge/pmd/cli/ignorelist.txt"); + + List applicableFiles = PMD.getApplicableFiles(configuration, languages); + Assert.assertEquals(4, applicableFiles.size()); + Collections.sort(applicableFiles, new Comparator() { + @Override + public int compare(DataSource o1, DataSource o2) { + if (o1 == null && o2 != null) { + return -1; + } else if (o1 != null && o2 == null) { + return 1; + } else { + return o1.getNiceFileName(false, "").compareTo(o2.getNiceFileName(false, "")); + } + } + }); + Assert.assertTrue(applicableFiles.get(0).getNiceFileName(false, "").endsWith("anotherfile.dummy")); + Assert.assertTrue(applicableFiles.get(1).getNiceFileName(false, "").endsWith("somefile.dummy")); + Assert.assertTrue(applicableFiles.get(2).getNiceFileName(false, "").endsWith("somefile2.dummy")); + Assert.assertTrue(applicableFiles.get(3).getNiceFileName(false, "").endsWith("somefile4.dummy")); + } } diff --git a/pmd-core/src/test/java/net/sourceforge/pmd/cpd/CPDCommandLineInterfaceTest.java b/pmd-core/src/test/java/net/sourceforge/pmd/cpd/CPDCommandLineInterfaceTest.java index f1ae19a6dbb..01c561a2012 100644 --- a/pmd-core/src/test/java/net/sourceforge/pmd/cpd/CPDCommandLineInterfaceTest.java +++ b/pmd-core/src/test/java/net/sourceforge/pmd/cpd/CPDCommandLineInterfaceTest.java @@ -12,8 +12,6 @@ import org.junit.contrib.java.lang.system.StandardOutputStreamLog; import org.junit.rules.TestRule; -import net.sourceforge.pmd.PMD; - public class CPDCommandLineInterfaceTest { @Rule public final TestRule restoreSystemProperties = new RestoreSystemProperties(); @@ -25,6 +23,6 @@ public void testEmptyResultRendering() { System.setProperty(CPDCommandLineInterface.NO_EXIT_AFTER_RUN, "true"); CPDCommandLineInterface.main(new String[] { "--minimum-tokens", "340", "--language", "java", "--files", "src/test/resources/net/sourceforge/pmd/cpd/files/", "--format", "xml", }); - Assert.assertEquals("" + "\n" + "" + PMD.EOL, log.getLog()); + Assert.assertEquals("" + "\n" + "", log.getLog()); } } diff --git a/pmd-core/src/test/java/net/sourceforge/pmd/cpd/CPDConfigurationTest.java b/pmd-core/src/test/java/net/sourceforge/pmd/cpd/CPDConfigurationTest.java index fb658e9f0e2..f5749fa78b9 100644 --- a/pmd-core/src/test/java/net/sourceforge/pmd/cpd/CPDConfigurationTest.java +++ b/pmd-core/src/test/java/net/sourceforge/pmd/cpd/CPDConfigurationTest.java @@ -10,21 +10,39 @@ import org.junit.Assert; import org.junit.Test; +import net.sourceforge.pmd.cpd.renderer.CPDRenderer; + public class CPDConfigurationTest { @Test public void testRenderers() { - Map> renderersToTest = new HashMap<>(); + Map> renderersToTest = new HashMap<>(); renderersToTest.put("csv", CSVRenderer.class); renderersToTest.put("xml", XMLRenderer.class); renderersToTest.put("csv_with_linecount_per_file", CSVWithLinecountPerFileRenderer.class); renderersToTest.put("vs", VSRenderer.class); renderersToTest.put("text", SimpleRenderer.class); - for (Map.Entry> entry : renderersToTest.entrySet()) { + for (Map.Entry> entry : renderersToTest.entrySet()) { Renderer r = CPDConfiguration.getRendererFromString(entry.getKey(), "UTF-8"); Assert.assertNotNull(r); Assert.assertSame(entry.getValue(), r.getClass()); } } + + @Test + public void testCPDRenderers() { + Map> renderersToTest = new HashMap<>(); + renderersToTest.put("csv", CSVRenderer.class); + renderersToTest.put("xml", XMLRenderer.class); + renderersToTest.put("csv_with_linecount_per_file", CSVWithLinecountPerFileRenderer.class); + renderersToTest.put("vs", VSRenderer.class); + renderersToTest.put("text", SimpleRenderer.class); + + for (Map.Entry> entry : renderersToTest.entrySet()) { + CPDRenderer r = CPDConfiguration.getCPDRendererFromString(entry.getKey(), "UTF-8"); + Assert.assertNotNull(r); + Assert.assertSame(entry.getValue(), r.getClass()); + } + } } diff --git a/pmd-core/src/test/java/net/sourceforge/pmd/cpd/CSVRendererTest.java b/pmd-core/src/test/java/net/sourceforge/pmd/cpd/CSVRendererTest.java index 79771f7d6fd..af976c44456 100644 --- a/pmd-core/src/test/java/net/sourceforge/pmd/cpd/CSVRendererTest.java +++ b/pmd-core/src/test/java/net/sourceforge/pmd/cpd/CSVRendererTest.java @@ -6,17 +6,20 @@ import static org.junit.Assert.assertEquals; +import java.io.IOException; +import java.io.StringWriter; import java.util.ArrayList; import java.util.List; import org.junit.Test; import net.sourceforge.pmd.PMD; +import net.sourceforge.pmd.cpd.renderer.CPDRenderer; public class CSVRendererTest { @Test - public void testLineCountPerFile() { - Renderer renderer = new CSVRenderer(true); + public void testLineCountPerFile() throws IOException { + CPDRenderer renderer = new CSVRenderer(true); List list = new ArrayList<>(); String codeFragment = "code\nfragment"; Mark mark1 = createMark("public", "/var/Foo.java", 48, 10, codeFragment); @@ -24,7 +27,9 @@ public void testLineCountPerFile() { Match match = new Match(75, mark1, mark2); list.add(match); - String report = renderer.render(list.iterator()); + StringWriter sw = new StringWriter(); + renderer.render(list.iterator(), sw); + String report = sw.toString(); String expectedReport = "tokens,occurrences" + PMD.EOL + "75,2,48,10,/var/Foo.java,73,20,/var/Bar.java" + PMD.EOL; @@ -32,8 +37,8 @@ public void testLineCountPerFile() { } @Test - public void testFilenameEscapes() { - Renderer renderer = new CSVRenderer(); + public void testFilenameEscapes() throws IOException { + CPDRenderer renderer = new CSVRenderer(); List list = new ArrayList<>(); String codeFragment = "code\nfragment"; Mark mark1 = createMark("public", "/var,with,commas/Foo.java", 48, 10, codeFragment); @@ -41,7 +46,9 @@ public void testFilenameEscapes() { Match match = new Match(75, mark1, mark2); list.add(match); - String report = renderer.render(list.iterator()); + StringWriter sw = new StringWriter(); + renderer.render(list.iterator(), sw); + String report = sw.toString(); String expectedReport = "lines,tokens,occurrences" + PMD.EOL + "10,75,2,48,\"/var,with,commas/Foo.java\",73,\"/var,with,commas/Bar.java\"" + PMD.EOL; assertEquals(expectedReport, report); diff --git a/pmd-core/src/test/java/net/sourceforge/pmd/cpd/XMLRendererTest.java b/pmd-core/src/test/java/net/sourceforge/pmd/cpd/XMLRendererTest.java index c9ebadc77e1..c6307b735ae 100644 --- a/pmd-core/src/test/java/net/sourceforge/pmd/cpd/XMLRendererTest.java +++ b/pmd-core/src/test/java/net/sourceforge/pmd/cpd/XMLRendererTest.java @@ -9,9 +9,10 @@ import static org.junit.Assert.fail; import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.StringWriter; import java.util.ArrayList; import java.util.List; - import javax.xml.parsers.DocumentBuilderFactory; import org.junit.Test; @@ -19,6 +20,8 @@ import org.w3c.dom.Node; import org.w3c.dom.NodeList; +import net.sourceforge.pmd.cpd.renderer.CPDRenderer; + /** * @author Philippe T'Seyen * @author Romain Pelisse <belaran@gmail.com> @@ -29,11 +32,13 @@ public class XMLRendererTest { private static final String ENCODING = (String) System.getProperties().get("file.encoding"); @Test - public void testWithNoDuplication() { + public void testWithNoDuplication() throws IOException { - Renderer renderer = new XMLRenderer(); + CPDRenderer renderer = new XMLRenderer(); List list = new ArrayList<>(); - String report = renderer.render(list.iterator()); + StringWriter sw = new StringWriter(); + renderer.render(list.iterator(), sw); + String report = sw.toString(); try { Document doc = DocumentBuilderFactory.newInstance().newDocumentBuilder() .parse(new ByteArrayInputStream(report.getBytes(ENCODING))); @@ -48,8 +53,8 @@ public void testWithNoDuplication() { } @Test - public void testWithOneDuplication() { - Renderer renderer = new XMLRenderer(); + public void testWithOneDuplication() throws IOException { + CPDRenderer renderer = new XMLRenderer(); List list = new ArrayList<>(); int lineCount = 6; String codeFragment = "code\nfragment"; @@ -58,7 +63,9 @@ public void testWithOneDuplication() { Match match = new Match(75, mark1, mark2); list.add(match); - String report = renderer.render(list.iterator()); + StringWriter sw = new StringWriter(); + renderer.render(list.iterator(), sw); + String report = sw.toString(); try { Document doc = DocumentBuilderFactory.newInstance().newDocumentBuilder() .parse(new ByteArrayInputStream(report.getBytes(ENCODING))); @@ -88,8 +95,8 @@ public void testWithOneDuplication() { } @Test - public void testRenderWithMultipleMatch() { - Renderer renderer = new XMLRenderer(); + public void testRenderWithMultipleMatch() throws IOException { + CPDRenderer renderer = new XMLRenderer(); List list = new ArrayList<>(); int lineCount1 = 6; String codeFragment1 = "code fragment"; @@ -105,7 +112,9 @@ public void testRenderWithMultipleMatch() { list.add(match1); list.add(match2); - String report = renderer.render(list.iterator()); + StringWriter sw = new StringWriter(); + renderer.render(list.iterator(), sw); + String report = sw.toString(); try { Document doc = DocumentBuilderFactory.newInstance().newDocumentBuilder() .parse(new ByteArrayInputStream(report.getBytes(ENCODING))); @@ -118,15 +127,18 @@ public void testRenderWithMultipleMatch() { } @Test - public void testRendererEncodedPath() { - Renderer renderer = new XMLRenderer(); + public void testRendererEncodedPath() throws IOException { + CPDRenderer renderer = new XMLRenderer(); List list = new ArrayList<>(); final String espaceChar = "<"; Mark mark1 = createMark("public", "/var/F" + '<' + "oo.java", 48, 6, "code fragment"); Mark mark2 = createMark("void", "/var/F 0) { + nread += n; + } + + // if the last call to read returned -1, then we're done + if (n < 0) { + break; + } + + // need to allocate a larger buffer + if (capacity <= maxBufferSize - capacity) { + capacity = capacity << 1; + } else { + if (capacity == maxBufferSize) { + throw new OutOfMemoryError("Required array size too large"); + } + capacity = maxBufferSize; + } + buf = Arrays.copyOf(buf, capacity); + } + return (capacity == nread) ? buf : Arrays.copyOf(buf, nread); + } + + @Test + public void insertVariousTokensIntoTheFileShouldSucceed() throws IOException { + writeContentToTemporaryFile("static void main(String[] args) {}"); + + try (DocumentFile documentFile = new DocumentFile(temporaryFile, StandardCharsets.UTF_8)) { + documentFile.insert(0, 0, "public "); + documentFile.insert(0, 17, "final "); + } + + try (FileInputStream stream = new FileInputStream(temporaryFile)) { + final String actualContent = new String(readAllBytes(stream)); + assertEquals("public static void main(final String[] args) {}", actualContent); + } + } + + @Test + public void insertAtTheEndOfTheFileShouldSucceed() throws IOException { + final String code = "public static void main(String[] args)"; + writeContentToTemporaryFile(code); + + try (DocumentFile documentFile = new DocumentFile(temporaryFile, StandardCharsets.UTF_8)) { + documentFile.insert(0, code.length(), "{}"); + } + + try (FileInputStream stream = new FileInputStream(temporaryFile)) { + final String actualContent = new String(readAllBytes(stream)); + assertEquals("public static void main(String[] args){}", actualContent); + } + } + + @Test + public void removeTokenShouldSucceed() throws IOException { + final String code = "public static void main(final String[] args) {}"; + writeContentToTemporaryFile(code); + + try (DocumentFile documentFile = new DocumentFile(temporaryFile, StandardCharsets.UTF_8)) { + documentFile.delete(new RegionByLineImp(0, 0, 24, 30)); + } + + try (FileInputStream stream = new FileInputStream(temporaryFile)) { + final String actualContent = new String(readAllBytes(stream)); + assertEquals("public static void main(String[] args) {}", actualContent); + } + } + + @Test + public void insertAndRemoveTokensShouldSucceed() throws IOException { + final String code = "static void main(final String[] args) {}"; + writeContentToTemporaryFile(code); + + try (DocumentFile documentFile = new DocumentFile(temporaryFile, StandardCharsets.UTF_8)) { + documentFile.insert(0, 0, "public "); + documentFile.delete(new RegionByLineImp(0, 0, 17, 23)); + } + + try (FileInputStream stream = new FileInputStream(temporaryFile)) { + final String actualContent = new String(readAllBytes(stream)); + assertEquals("public static void main(String[] args) {}", actualContent); + } + } + + @Test + public void insertAndDeleteVariousTokensShouldSucceed() throws IOException { + final String code = "void main(String[] args) {}"; + writeContentToTemporaryFile(code); + + try (DocumentFile documentFile = new DocumentFile(temporaryFile, StandardCharsets.UTF_8)) { + documentFile.insert(0, 0, "public "); + documentFile.insert(0, 0, "static "); + documentFile.delete(new RegionByLineImp(0, 0, 0, 4)); + documentFile.insert(0, 10, "final "); + documentFile.delete(new RegionByLineImp(0, 0, 25, 28)); + } + + try (FileInputStream stream = new FileInputStream(temporaryFile)) { + final String actualContent = new String(readAllBytes(stream)); + assertEquals("public static main(final String[] args) ", actualContent); + } + } + + @Test + public void replaceATokenShouldSucceed() throws IOException { + final String code = "int main(String[] args) {}"; + writeContentToTemporaryFile(code); + + try (DocumentFile documentFile = new DocumentFile(temporaryFile, StandardCharsets.UTF_8)) { + documentFile.replace(new RegionByLineImp(0, 0, 0, 3), "void"); + } + + try (FileInputStream stream = new FileInputStream(temporaryFile)) { + final String actualContent = new String(readAllBytes(stream)); + assertEquals("void main(String[] args) {}", actualContent); + } + } + + @Test + public void replaceVariousTokensShouldSucceed() throws IOException { + final String code = "int main(String[] args) {}"; + writeContentToTemporaryFile(code); + + try (DocumentFile documentFile = new DocumentFile(temporaryFile, StandardCharsets.UTF_8)) { + documentFile.replace(new RegionByLineImp(0, 0, 0, "int".length()), "void"); + documentFile.replace(new RegionByLineImp(0, 0, 4, 4 + "main".length()), "foo"); + documentFile.replace(new RegionByLineImp(0, 0, 9, 9 + "String".length()), "CharSequence"); + } + + try (FileInputStream stream = new FileInputStream(temporaryFile)) { + final String actualContent = new String(readAllBytes(stream)); + assertEquals("void foo(CharSequence[] args) {}", actualContent); + } + } + + @Test + public void insertDeleteAndReplaceVariousTokensShouldSucceed() throws IOException { + final String code = "static int main(CharSequence[] args) {}"; + writeContentToTemporaryFile(code); + + try (DocumentFile documentFile = new DocumentFile(temporaryFile, StandardCharsets.UTF_8)) { + documentFile.insert(0, 0, "public"); + documentFile.delete(new RegionByLineImp(0, 0, 0, 6)); + documentFile.replace(new RegionByLineImp(0, 0, 7, 7 + "int".length()), "void"); + documentFile.insert(0, 16, "final "); + documentFile.replace(new RegionByLineImp(0, 0, 16, 16 + "CharSequence".length()), "String"); + } + + try (FileInputStream stream = new FileInputStream(temporaryFile)) { + final String actualContent = new String(readAllBytes(stream)); + assertEquals("public void main(final String[] args) {}", actualContent); + } + } + + @Test + public void lineToOffsetMappingWithLineFeedShouldSucceed() throws IOException { + final String code = "public static int main(String[] args) {" + '\n' + + "int var;" + '\n' + + "}"; + writeContentToTemporaryFile(code); + + final List expectedLineToOffset = new ArrayList<>(); + expectedLineToOffset.add(0); + expectedLineToOffset.add(40); + expectedLineToOffset.add(49); + + try (DocumentFile documentFile = new DocumentFile(temporaryFile, StandardCharsets.UTF_8)) { + assertEquals(expectedLineToOffset, documentFile.getLineToOffset()); + } + } + + @Test + public void lineToOffsetMappingWithCarriageReturnFeedLineFeedShouldSucceed() throws IOException { + final String code = "public static int main(String[] args) {" + "\r\n" + + "int var;" + "\r\n" + + "}"; + writeContentToTemporaryFile(code); + + final List expectedLineToOffset = new ArrayList<>(); + expectedLineToOffset.add(0); + expectedLineToOffset.add(41); + expectedLineToOffset.add(51); + + try (DocumentFile documentFile = new DocumentFile(temporaryFile, StandardCharsets.UTF_8)) { + assertEquals(expectedLineToOffset, documentFile.getLineToOffset()); + } + } + + @Test + public void lineToOffsetMappingWithMixedLineSeparatorsShouldSucceed() throws IOException { + final String code = "public static int main(String[] args) {" + "\r\n" + + "int var;" + "\n" + + "}"; + writeContentToTemporaryFile(code); + + final List expectedLineToOffset = new ArrayList<>(); + expectedLineToOffset.add(0); + expectedLineToOffset.add(41); + expectedLineToOffset.add(50); + + try (DocumentFile documentFile = new DocumentFile(temporaryFile, StandardCharsets.UTF_8)) { + assertEquals(expectedLineToOffset, documentFile.getLineToOffset()); + } + } + + private void writeContentToTemporaryFile(final String content) throws IOException { + try (BufferedWriter writer = Files.newBufferedWriter(temporaryFile.toPath(), StandardCharsets.UTF_8)) { + writer.write(content); + } + } +} diff --git a/pmd-core/src/test/java/net/sourceforge/pmd/document/DocumentOperationsApplierForNonOverlappingRegionsWithDocumentFileTest.java b/pmd-core/src/test/java/net/sourceforge/pmd/document/DocumentOperationsApplierForNonOverlappingRegionsWithDocumentFileTest.java new file mode 100644 index 00000000000..cd3b9b51f76 --- /dev/null +++ b/pmd-core/src/test/java/net/sourceforge/pmd/document/DocumentOperationsApplierForNonOverlappingRegionsWithDocumentFileTest.java @@ -0,0 +1,224 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.document; + +import static org.junit.Assert.assertEquals; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileWriter; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; + +public class DocumentOperationsApplierForNonOverlappingRegionsWithDocumentFileTest { + + private static final String FILE_PATH = "psvm.java"; + + @Rule + public TemporaryFolder temporaryFolder = new TemporaryFolder(); + private File temporaryFile; + + private DocumentOperationsApplierForNonOverlappingRegions applier; + + @Before + public void setUpTemporaryFiles() throws IOException { + temporaryFile = temporaryFolder.newFile(FILE_PATH); + } + + @Test + public void insertAtStartOfTheDocumentShouldSucceed() throws IOException { + writeContentToTemporaryFile("static void main(String[] args) {}"); + + try (DocumentFile documentFile = new DocumentFile(temporaryFile, StandardCharsets.UTF_8)) { + applier = new DocumentOperationsApplierForNonOverlappingRegions(documentFile); + applier.addDocumentOperation(new InsertDocumentOperation(0, 0, "public ")); + + applier.apply(); + } + + try (FileInputStream stream = new FileInputStream(temporaryFile)) { + final String actualContent = new String(readAllBytes(stream)); + assertEquals("public static void main(String[] args) {}", actualContent); + } + } + + private byte[] readAllBytes(final FileInputStream stream) throws IOException { + final int defaultBufferSize = 8192; + final int maxBufferSize = Integer.MAX_VALUE - 8; + + byte[] buf = new byte[defaultBufferSize]; + int capacity = buf.length; + int nread = 0; + int n; + while (true) { + // read to EOF which may read more or less than initial buffer size + while ((n = stream.read(buf, nread, capacity - nread)) > 0) { + nread += n; + } + + // if the last call to read returned -1, then we're done + if (n < 0) { + break; + } + + // need to allocate a larger buffer + if (capacity <= maxBufferSize - capacity) { + capacity = capacity << 1; + } else { + if (capacity == maxBufferSize) { + throw new OutOfMemoryError("Required array size too large"); + } + capacity = maxBufferSize; + } + buf = Arrays.copyOf(buf, capacity); + } + return (capacity == nread) ? buf : Arrays.copyOf(buf, nread); + } + + @Test + public void removeTokenShouldSucceed() throws IOException { + writeContentToTemporaryFile("public static void main(String[] args) {}"); + + try (DocumentFile documentFile = new DocumentFile(temporaryFile, StandardCharsets.UTF_8)) { + applier = new DocumentOperationsApplierForNonOverlappingRegions(documentFile); + applier.addDocumentOperation(new DeleteDocumentOperation(0, 0, 7, 13)); + + applier.apply(); + } + + try (FileInputStream stream = new FileInputStream(temporaryFile)) { + final String actualContent = new String(readAllBytes(stream)); + assertEquals("public void main(String[] args) {}", actualContent); + } + } + + @Test + public void insertAndRemoveTokensShouldSucceed() throws IOException { + final String code = "static void main(final String[] args) {}"; + writeContentToTemporaryFile(code); + + try (DocumentFile documentFile = new DocumentFile(temporaryFile, StandardCharsets.UTF_8)) { + applier = new DocumentOperationsApplierForNonOverlappingRegions(documentFile); + applier.addDocumentOperation(new InsertDocumentOperation(0, 0, "public ")); + applier.addDocumentOperation(new DeleteDocumentOperation(0, 0, 17, 23)); + + applier.apply(); + } + + try (FileInputStream stream = new FileInputStream(temporaryFile)) { + final String actualContent = new String(readAllBytes(stream)); + assertEquals("public static void main(String[] args) {}", actualContent); + } + } + + @Test + public void insertAndDeleteVariousTokensShouldSucceed() throws IOException { + final String code = "void main(String[] args) {}"; + writeContentToTemporaryFile(code); + + try (DocumentFile documentFile = new DocumentFile(temporaryFile, StandardCharsets.UTF_8)) { + applier = new DocumentOperationsApplierForNonOverlappingRegions(documentFile); + + applier.addDocumentOperation(new InsertDocumentOperation(0, 0, "public ")); + applier.addDocumentOperation(new InsertDocumentOperation(0, 0, "static ")); + applier.addDocumentOperation(new DeleteDocumentOperation(0, 0, 0, 4)); + applier.addDocumentOperation(new InsertDocumentOperation(0, 10, "final ")); + applier.addDocumentOperation(new DeleteDocumentOperation(0, 0, 25, 27)); + + applier.apply(); + } + + try (FileInputStream stream = new FileInputStream(temporaryFile)) { + final String actualContent = new String(readAllBytes(stream)); + assertEquals("public static main(final String[] args) ", actualContent); + } + } + + @Test + public void replaceATokenShouldSucceed() throws IOException { + final String code = "int main(String[] args) {}"; + writeContentToTemporaryFile(code); + + try (DocumentFile documentFile = new DocumentFile(temporaryFile, StandardCharsets.UTF_8)) { + applier = new DocumentOperationsApplierForNonOverlappingRegions(documentFile); + + applier.addDocumentOperation(new ReplaceDocumentOperation(0, 0, 0, "int".length(), "void")); + + applier.apply(); + } + + try (FileInputStream stream = new FileInputStream(temporaryFile)) { + final String actualContent = new String(readAllBytes(stream)); + assertEquals("void main(String[] args) {}", actualContent); + } + } + + @Test + public void replaceVariousTokensShouldSucceed() throws IOException { + final String code = "int main(String[] args) {}"; + writeContentToTemporaryFile(code); + + final List documentOperations = new LinkedList<>(); + documentOperations.add(new ReplaceDocumentOperation(0, 0, 0, "int".length(), "void")); + documentOperations.add(new ReplaceDocumentOperation(0, 0, 4, 4 + "main".length(), "foo")); + documentOperations.add(new ReplaceDocumentOperation(0, 0, 9, 9 + "String".length(), "CharSequence")); + + shuffleAndApplyOperations(documentOperations); + + try (FileInputStream stream = new FileInputStream(temporaryFile)) { + final String actualContent = new String(readAllBytes(stream)); + assertEquals("void foo(CharSequence[] args) {}", actualContent); + } + } + + private void shuffleAndApplyOperations(List documentOperations) throws IOException { + try (DocumentFile documentFile = new DocumentFile(temporaryFile, StandardCharsets.UTF_8)) { + applier = new DocumentOperationsApplierForNonOverlappingRegions(documentFile); + + Collections.shuffle(documentOperations); + + for (final DocumentOperation operation : documentOperations) { + applier.addDocumentOperation(operation); + } + + applier.apply(); + } + } + + @Test + public void insertDeleteAndReplaceVariousTokensShouldSucceed() throws IOException { + final String code = "static int main(CharSequence[] args) {}"; + writeContentToTemporaryFile(code); + + final List documentOperations = new LinkedList<>(); + documentOperations.add(new InsertDocumentOperation(0, 0, "public")); + documentOperations.add(new DeleteDocumentOperation(0, 0, 0, 6)); + documentOperations.add(new ReplaceDocumentOperation(0, 0, 7, 7 + "int".length(), "void")); + documentOperations.add(new InsertDocumentOperation(0, 16, "final ")); + documentOperations.add(new ReplaceDocumentOperation(0, 0, 16, 16 + "CharSequence".length(), "String")); + + shuffleAndApplyOperations(documentOperations); + + try (FileInputStream stream = new FileInputStream(temporaryFile)) { + final String actualContent = new String(readAllBytes(stream)); + assertEquals("public void main(final String[] args) {}", actualContent); + } + } + + private void writeContentToTemporaryFile(final String content) throws IOException { + try (FileWriter writer = new FileWriter(temporaryFile)) { + writer.write(content); + } + } +} diff --git a/pmd-core/src/test/java/net/sourceforge/pmd/jaxen/MatchesFunctionTest.java b/pmd-core/src/test/java/net/sourceforge/pmd/jaxen/MatchesFunctionTest.java index f5dfd14f054..5bbedef6df8 100644 --- a/pmd-core/src/test/java/net/sourceforge/pmd/jaxen/MatchesFunctionTest.java +++ b/pmd-core/src/test/java/net/sourceforge/pmd/jaxen/MatchesFunctionTest.java @@ -38,6 +38,12 @@ public void setClassName(String className) { public String getClassName() { return className; } + + + @Override + public String getXPathNodeName() { + return "MyNode"; + } } @Test diff --git a/pmd-core/src/test/java/net/sourceforge/pmd/junit/JavaUtilLoggingRule.java b/pmd-core/src/test/java/net/sourceforge/pmd/junit/JavaUtilLoggingRule.java index 674775120cb..7895ee01bfc 100644 --- a/pmd-core/src/test/java/net/sourceforge/pmd/junit/JavaUtilLoggingRule.java +++ b/pmd-core/src/test/java/net/sourceforge/pmd/junit/JavaUtilLoggingRule.java @@ -30,7 +30,12 @@ public class JavaUtilLoggingRule extends ExternalResource { public JavaUtilLoggingRule(String loggerName) { this.logger = Logger.getLogger(loggerName); this.stream = new ByteArrayOutputStream(); - this.customLogHandler = new StreamHandler(stream, logger.getParent().getHandlers()[0].getFormatter()); + + Logger currentLogger = logger; + while (currentLogger.getHandlers().length == 0) { + currentLogger = currentLogger.getParent(); + } + this.customLogHandler = new StreamHandler(stream, currentLogger.getHandlers()[0].getFormatter()); } @Override diff --git a/pmd-core/src/test/java/net/sourceforge/pmd/lang/DummyLanguageModule.java b/pmd-core/src/test/java/net/sourceforge/pmd/lang/DummyLanguageModule.java index 4f5312fa0b8..dc885889656 100644 --- a/pmd-core/src/test/java/net/sourceforge/pmd/lang/DummyLanguageModule.java +++ b/pmd-core/src/test/java/net/sourceforge/pmd/lang/DummyLanguageModule.java @@ -107,9 +107,9 @@ protected RuleViolation createRuleViolation(Rule rule, RuleContext ruleContext, protected RuleViolation createRuleViolation(Rule rule, RuleContext ruleContext, Node node, String message, int beginLine, int endLine) { ParametricRuleViolation rv = new ParametricRuleViolation(rule, ruleContext, node, message) { - { - this.packageName = "foo"; // just for testing variable - // expansion + public String getPackageName() { + this.packageName = "foo"; // just for testing variable expansion + return super.getPackageName(); } }; rv.setLines(beginLine, endLine); diff --git a/pmd-core/src/test/java/net/sourceforge/pmd/lang/LanguageRegistryTest.java b/pmd-core/src/test/java/net/sourceforge/pmd/lang/LanguageRegistryTest.java index 7c67d740f60..8feaaa7f280 100644 --- a/pmd-core/src/test/java/net/sourceforge/pmd/lang/LanguageRegistryTest.java +++ b/pmd-core/src/test/java/net/sourceforge/pmd/lang/LanguageRegistryTest.java @@ -17,4 +17,17 @@ public void getDefaultLanguageTest() { // available language now -> DummyLanguage Assert.assertSame(DummyLanguageModule.class, defaultLanguage.getClass()); } + + @Test + public void getDefaultVersionLanguageTest() { + LanguageVersion dummy12 = LanguageRegistry.findLanguageVersionByTerseName("dummy 1.2"); + Assert.assertNotNull(dummy12); + + Language dummy = LanguageRegistry.findLanguageByTerseName("dummy"); + LanguageVersion dummyDefault = dummy.getDefaultVersion(); + + LanguageVersion dummyDefault2 = LanguageRegistry.findLanguageVersionByTerseName("dummy "); + Assert.assertNotNull(dummyDefault2); + Assert.assertSame(dummyDefault, dummyDefault2); + } } diff --git a/pmd-core/src/test/java/net/sourceforge/pmd/lang/ast/AbstractNodeTest.java b/pmd-core/src/test/java/net/sourceforge/pmd/lang/ast/AbstractNodeTest.java new file mode 100644 index 00000000000..f17a8c2a3e0 --- /dev/null +++ b/pmd-core/src/test/java/net/sourceforge/pmd/lang/ast/AbstractNodeTest.java @@ -0,0 +1,251 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.ast; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import org.jaxen.JaxenException; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +import net.sourceforge.pmd.junit.JavaUtilLoggingRule; +import net.sourceforge.pmd.lang.ast.xpath.Attribute; + +import junitparams.JUnitParamsRunner; +import junitparams.Parameters; + + +/** + * Unit test for {@link AbstractNode}. + */ +@RunWith(JUnitParamsRunner.class) +public class AbstractNodeTest { + private static final int NUM_CHILDREN = 3; + private static final int NUM_GRAND_CHILDREN = 3; + + @Rule + public JavaUtilLoggingRule loggingRule = new JavaUtilLoggingRule(Attribute.class.getName()); + + // Note that in order to successfully run JUnitParams, we need to explicitly use `Integer` instead of `int` + + private Integer[] childrenIndexes() { + return getIntRange(NUM_CHILDREN); + } + + private Integer[] grandChildrenIndexes() { + return getIntRange(NUM_GRAND_CHILDREN); + } + + private static Integer[] getIntRange(final int exclusiveLimit) { + final Integer[] childIndexes = new Integer[exclusiveLimit]; + for (int i = 0; i < exclusiveLimit; i++) { + childIndexes[i] = i; + } + return childIndexes; + } + + public Object childrenAndGrandChildrenIndexes() { + final Integer[] childrenIndexes = childrenIndexes(); + final Integer[] grandChildrenIndexes = grandChildrenIndexes(); + final Object[] indexes = new Object[childrenIndexes.length * grandChildrenIndexes.length]; + int i = 0; + for (final int childIndex : childrenIndexes) { + for (final int grandChildIndex : grandChildrenIndexes) { + indexes[i++] = new Integer[] { childIndex, grandChildIndex }; + } + } + return indexes; + } + + private int id; + private Node rootNode; + + private int nextId() { + return id++; + } + + private Node newDummyNode() { + return new DummyNode(nextId()); + } + + private static Node addChild(final Node parent, final Node child) { + parent.jjtAddChild(child, parent.jjtGetNumChildren()); // Append child at the end + child.jjtSetParent(parent); + return parent; + } + + @Before + public void setUpSampleNodeTree() { + id = 0; + rootNode = newDummyNode(); + + for (int i = 0; i < NUM_CHILDREN; i++) { + final Node child = newDummyNode(); + for (int j = 0; j < NUM_GRAND_CHILDREN; j++) { + final Node grandChild = newDummyNode(); + addChild(child, grandChild); + } + addChild(rootNode, child); + } + } + + /** + * Explicitly tests the {@code remove} method, and implicitly the {@code removeChildAtIndex} method + */ + @Test + @Parameters(method = "childrenIndexes") + public void testRemoveChildOfRootNode(final int childIndex) { + final Node child = rootNode.jjtGetChild(childIndex); + final Node[] grandChildren = new Node[child.jjtGetNumChildren()]; + for (int i = 0; i < grandChildren.length; i++) { + final Node grandChild = child.jjtGetChild(i); + grandChildren[i] = grandChild; + } + + // Do the actual removal + child.remove(); + + // Check that conditions have been successfully changed + assertEquals(NUM_CHILDREN - 1, rootNode.jjtGetNumChildren()); + assertNull(child.jjtGetParent()); + // The child node is expected to still have all its children and vice versa + assertEquals(NUM_GRAND_CHILDREN, child.jjtGetNumChildren()); + for (final Node grandChild : grandChildren) { + assertEquals(child, grandChild.jjtGetParent()); + } + } + + /** + * Explicitly tests the {@code remove} method, and implicitly the {@code removeChildAtIndex} method. + * This is a border case as the root node does not have any parent. + */ + @Test + public void testRemoveRootNode() { + // Check that the root node has the expected properties + final Node[] children = new Node[rootNode.jjtGetNumChildren()]; + for (int i = 0; i < children.length; i++) { + final Node child = rootNode.jjtGetChild(i); + children[i] = child; + } + + // Do the actual removal + rootNode.remove(); + + // Check that conditions have been successfully changed, i.e., + // the root node is expected to still have all its children and vice versa + assertEquals(NUM_CHILDREN, rootNode.jjtGetNumChildren()); + assertNull(rootNode.jjtGetParent()); + for (final Node aChild : children) { + assertEquals(rootNode, aChild.jjtGetParent()); + } + } + + /** + * Explicitly tests the {@code remove} method, and implicitly the {@code removeChildAtIndex} method. + * These are border cases as grandchildren nodes do not have any child. + */ + @Test + @Parameters(method = "childrenAndGrandChildrenIndexes") + public void testRemoveGrandChildNode(final int childIndex, final int grandChildIndex) { + final Node child = rootNode.jjtGetChild(childIndex); + final Node grandChild = child.jjtGetChild(grandChildIndex); + + // Do the actual removal + grandChild.remove(); + + // Check that conditions have been successfully changed + assertEquals(NUM_GRAND_CHILDREN - 1, child.jjtGetNumChildren()); + assertEquals(0, grandChild.jjtGetNumChildren()); + assertNull(grandChild.jjtGetParent()); + } + + /** + * Explicitly tests the {@code removeChildAtIndex} method. + */ + @Test + @Parameters(method = "childrenIndexes") + public void testRemoveRootNodeChildAtIndex(final int childIndex) { + final Node[] originalChildren = new Node[rootNode.jjtGetNumChildren()]; + + for (int i = 0; i < originalChildren.length; i++) { + originalChildren[i] = rootNode.jjtGetChild(i); + } + + // Do the actual removal + rootNode.removeChildAtIndex(childIndex); + + // Check that conditions have been successfully changed + assertEquals(NUM_CHILDREN - 1, rootNode.jjtGetNumChildren()); + int j = 0; + for (int i = 0; i < rootNode.jjtGetNumChildren(); i++) { + if (j == childIndex) { // Skip the removed child + j++; + } + // Check that the nodes have been rightly shifted + assertEquals(originalChildren[j], rootNode.jjtGetChild(i)); + // Check that the child index has been updated + assertEquals(i, rootNode.jjtGetChild(i).jjtGetChildIndex()); + j++; + } + } + + /** + * Explicitly tests the {@code removeChildAtIndex} method. + * Test that invalid indexes cases are handled without exception. + */ + @Test + public void testRemoveChildAtIndexWithInvalidIndex() { + try { + rootNode.removeChildAtIndex(-1); + rootNode.removeChildAtIndex(rootNode.jjtGetNumChildren()); + } catch (final Exception e) { + fail("No exception was expected."); + } + } + + /** + * Explicitly tests the {@code removeChildAtIndex} method. + * This is a border case as the method invocation should do nothing. + */ + @Test + @Parameters(method = "grandChildrenIndexes") + public void testRemoveChildAtIndexOnNodeWithNoChildren(final int grandChildIndex) { + // grandChild does not have any child + final Node grandChild = rootNode.jjtGetChild(grandChildIndex).jjtGetChild(grandChildIndex); + + // Do the actual removal + grandChild.removeChildAtIndex(0); + + // If here, no exception has been thrown + // Check that this node still does not have any children + assertEquals(0, grandChild.jjtGetNumChildren()); + } + + + @Test + public void testDeprecatedAttributeXPathQuery() throws JaxenException { + class MyRootNode extends DummyNode implements RootNode { + + private MyRootNode(int id) { + super(id); + } + } + + addChild(new MyRootNode(nextId()), new DummyNodeWithDeprecatedAttribute(2)).findChildNodesWithXPath("//dummyNode[@Size=1]"); + + String log = loggingRule.getLog(); + + assertTrue(log.contains("deprecated")); + assertTrue(log.contains("attribute")); + assertTrue(log.contains("dummyNode/@Size")); + } + + +} diff --git a/pmd-core/src/test/java/net/sourceforge/pmd/lang/ast/AbstractNodeTransversalTest.java b/pmd-core/src/test/java/net/sourceforge/pmd/lang/ast/AbstractNodeTransversalTest.java new file mode 100644 index 00000000000..221747193a6 --- /dev/null +++ b/pmd-core/src/test/java/net/sourceforge/pmd/lang/ast/AbstractNodeTransversalTest.java @@ -0,0 +1,72 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.ast; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import java.util.ArrayList; +import java.util.List; + +import org.junit.Before; +import org.junit.Test; + +/** + * Unit test for {@link AbstractNode} tree transversal methods + */ +public class AbstractNodeTransversalTest { + private int id; + private Node rootNode; + + private int nextId() { + return id++; + } + + private Node newDummyNode(boolean boundary) { + return new DummyNode(nextId(), boundary); + } + + private Node addChild(final Node parent, final Node child) { + parent.jjtAddChild(child, parent.jjtGetNumChildren()); // Append child at the end + child.jjtSetParent(parent); + return parent; + } + + @Before + public void setUpSampleNodeTree() { + id = 0; + rootNode = newDummyNode(false); + } + + @Test + public void testBoundaryIsHonored() { + addChild(rootNode, addChild(newDummyNode(true), newDummyNode(false))); + + List descendantsOfType = rootNode.findDescendantsOfType(DummyNode.class); + assertEquals(1, descendantsOfType.size()); + assertTrue(descendantsOfType.get(0).isFindBoundary()); + } + + @Test + public void testSearchFromBoundary() { + addChild(rootNode, addChild(newDummyNode(true), newDummyNode(false))); + + List descendantsOfType = rootNode.findDescendantsOfType(DummyNode.class).get(0).findDescendantsOfType(DummyNode.class); + assertEquals(1, descendantsOfType.size()); + assertFalse(descendantsOfType.get(0).isFindBoundary()); + } + + @Test + public void testSearchIgnoringBoundary() { + addChild(rootNode, addChild(newDummyNode(true), newDummyNode(false))); + + List descendantsOfType = new ArrayList<>(); + rootNode.findDescendantsOfType(DummyNode.class, descendantsOfType, true); + assertEquals(2, descendantsOfType.size()); + assertTrue(descendantsOfType.get(0).isFindBoundary()); + assertFalse(descendantsOfType.get(1).isFindBoundary()); + } +} diff --git a/pmd-core/src/test/java/net/sourceforge/pmd/lang/ast/DummyNode.java b/pmd-core/src/test/java/net/sourceforge/pmd/lang/ast/DummyNode.java index 3043df1f848..0f4bdf11fc1 100644 --- a/pmd-core/src/test/java/net/sourceforge/pmd/lang/ast/DummyNode.java +++ b/pmd-core/src/test/java/net/sourceforge/pmd/lang/ast/DummyNode.java @@ -5,12 +5,29 @@ package net.sourceforge.pmd.lang.ast; public class DummyNode extends AbstractNode { + private final boolean findBoundary; + public DummyNode(int id) { + this(id, false); + } + + public DummyNode(int id, boolean findBoundary) { super(id); + this.findBoundary = findBoundary; } @Override public String toString() { return "dummyNode"; } + + @Override + public String getXPathNodeName() { + return "dummyNode"; + } + + @Override + public boolean isFindBoundary() { + return findBoundary; + } } diff --git a/pmd-core/src/test/java/net/sourceforge/pmd/lang/ast/DummyNodeWithDeprecatedAttribute.java b/pmd-core/src/test/java/net/sourceforge/pmd/lang/ast/DummyNodeWithDeprecatedAttribute.java new file mode 100644 index 00000000000..7f6f67d8f76 --- /dev/null +++ b/pmd-core/src/test/java/net/sourceforge/pmd/lang/ast/DummyNodeWithDeprecatedAttribute.java @@ -0,0 +1,23 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.ast; + +/** + * @author Clément Fournier + * @since 6.3.0 + */ +public class DummyNodeWithDeprecatedAttribute extends DummyNode { + + + public DummyNodeWithDeprecatedAttribute(int id) { + super(id); + } + + // this is the deprecated attribute + @Deprecated + public int getSize() { + return 2; + } +} diff --git a/pmd-core/src/test/java/net/sourceforge/pmd/lang/ast/xpath/AttributeAxisIteratorTest.java b/pmd-core/src/test/java/net/sourceforge/pmd/lang/ast/xpath/AttributeAxisIteratorTest.java index 89bdc7772d3..e8326ca32de 100644 --- a/pmd-core/src/test/java/net/sourceforge/pmd/lang/ast/xpath/AttributeAxisIteratorTest.java +++ b/pmd-core/src/test/java/net/sourceforge/pmd/lang/ast/xpath/AttributeAxisIteratorTest.java @@ -4,19 +4,33 @@ package net.sourceforge.pmd.lang.ast.xpath; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.Assert.assertTrue; + import java.util.HashMap; import java.util.Map; +import org.hamcrest.collection.IsMapContaining; import org.junit.Assert; import org.junit.Test; import net.sourceforge.pmd.lang.ast.DummyNode; +import net.sourceforge.pmd.lang.ast.DummyNodeWithDeprecatedAttribute; +import net.sourceforge.pmd.lang.ast.Node; + /** * Unit test for {@link AttributeAxisIterator} */ public class AttributeAxisIteratorTest { + @Test + public void testAttributeDeprecation() { + Node dummy = new DummyNodeWithDeprecatedAttribute(2); + assertThat(toMap(new AttributeAxisIterator(dummy)), IsMapContaining.hasKey("Size")); + } + /** * Test hasNext and next. */ @@ -27,18 +41,24 @@ public void testAttributeAxisIterator() { dummyNode.testingOnlySetBeginColumn(1); AttributeAxisIterator it = new AttributeAxisIterator(dummyNode); + Map atts = toMap(it); + Assert.assertEquals(7, atts.size()); + assertTrue(atts.containsKey("BeginColumn")); + assertTrue(atts.containsKey("BeginLine")); + assertTrue(atts.containsKey("FindBoundary")); + assertTrue(atts.containsKey("Image")); + assertTrue(atts.containsKey("SingleLine")); + assertTrue(atts.containsKey("EndColumn")); + assertTrue(atts.containsKey("EndLine")); + } + + + private Map toMap(AttributeAxisIterator it) { Map atts = new HashMap<>(); while (it.hasNext()) { Attribute attribute = it.next(); atts.put(attribute.getName(), attribute); } - Assert.assertEquals(7, atts.size()); - Assert.assertTrue(atts.containsKey("BeginColumn")); - Assert.assertTrue(atts.containsKey("BeginLine")); - Assert.assertTrue(atts.containsKey("FindBoundary")); - Assert.assertTrue(atts.containsKey("Image")); - Assert.assertTrue(atts.containsKey("SingleLine")); - Assert.assertTrue(atts.containsKey("EndColumn")); - Assert.assertTrue(atts.containsKey("EndLine")); + return atts; } } diff --git a/pmd-core/src/test/java/net/sourceforge/pmd/processor/MultiThreadProcessorTest.java b/pmd-core/src/test/java/net/sourceforge/pmd/processor/MultiThreadProcessorTest.java index 8a425163505..5ff85111581 100644 --- a/pmd-core/src/test/java/net/sourceforge/pmd/processor/MultiThreadProcessorTest.java +++ b/pmd-core/src/test/java/net/sourceforge/pmd/processor/MultiThreadProcessorTest.java @@ -9,6 +9,7 @@ import java.io.InputStream; import java.util.ArrayList; import java.util.Collections; +import java.util.Iterator; import java.util.List; import java.util.concurrent.atomic.AtomicInteger; @@ -16,33 +17,64 @@ import org.junit.Test; import net.sourceforge.pmd.PMDConfiguration; +import net.sourceforge.pmd.Report; +import net.sourceforge.pmd.Report.ConfigurationError; import net.sourceforge.pmd.RuleContext; import net.sourceforge.pmd.RuleSetFactory; import net.sourceforge.pmd.RuleViolation; import net.sourceforge.pmd.ThreadSafeReportListener; import net.sourceforge.pmd.lang.ast.Node; import net.sourceforge.pmd.lang.rule.AbstractRule; +import net.sourceforge.pmd.renderers.AbstractAccumulatingRenderer; import net.sourceforge.pmd.renderers.Renderer; import net.sourceforge.pmd.stat.Metric; import net.sourceforge.pmd.util.datasource.DataSource; public class MultiThreadProcessorTest { - @Test - public void testRulesThreadSafety() { + private RuleContext ctx; + private MultiThreadProcessor processor; + private RuleSetFactory ruleSetFactory; + private List files; + private SimpleReportListener reportListener; + + public void setUpForTest(final String ruleset) { PMDConfiguration configuration = new PMDConfiguration(); - configuration.setRuleSets("rulesets/MultiThreadProcessorTest/basic.xml"); + configuration.setRuleSets(ruleset); configuration.setThreads(2); - List files = new ArrayList<>(); + files = new ArrayList<>(); files.add(new StringDataSource("file1-violation.dummy", "ABC")); files.add(new StringDataSource("file2-foo.dummy", "DEF")); - SimpleReportListener reportListener = new SimpleReportListener(); - RuleContext ctx = new RuleContext(); + reportListener = new SimpleReportListener(); + ctx = new RuleContext(); ctx.getReport().addListener(reportListener); - MultiThreadProcessor processor = new MultiThreadProcessor(configuration); - RuleSetFactory ruleSetFactory = new RuleSetFactory(); + processor = new MultiThreadProcessor(configuration); + ruleSetFactory = new RuleSetFactory(); + } + + @Test + public void testRulesDysnfunctionalLog() throws IOException { + setUpForTest("rulesets/MultiThreadProcessorTest/dysfunctional.xml"); + final SimpleRenderer renderer = new SimpleRenderer(null, null); + renderer.start(); + processor.processFiles(ruleSetFactory, files, ctx, Collections.singletonList(renderer)); + renderer.end(); + + final Iterator configErrors = renderer.getReport().configErrors(); + final ConfigurationError error = configErrors.next(); + + Assert.assertEquals("Dysfunctional rule message not present", + DysfunctionalRule.DYSFUNCTIONAL_RULE_REASON, error.issue()); + Assert.assertEquals("Dysfunctional rule is wrong", + DysfunctionalRule.class, error.rule().getClass()); + Assert.assertFalse("More configuration errors found than expected", configErrors.hasNext()); + } + + @Test + public void testRulesThreadSafety() { + setUpForTest("rulesets/MultiThreadProcessorTest/basic.xml"); processor.processFiles(ruleSetFactory, files, ctx, Collections.emptyList()); // if the rule is not executed, then maybe a @@ -104,6 +136,21 @@ private void letTheOtherThreadRun(int millis) { } } } + + public static class DysfunctionalRule extends AbstractRule { + + public static final String DYSFUNCTIONAL_RULE_REASON = "dysfunctional rule is dysfunctional"; + + @Override + public void apply(List nodes, RuleContext ctx) { + // noop + } + + @Override + public String dysfunctionReason() { + return DYSFUNCTIONAL_RULE_REASON; + } + } private static class SimpleReportListener implements ThreadSafeReportListener { public AtomicInteger violations = new AtomicInteger(0); @@ -117,4 +164,24 @@ public void ruleViolationAdded(RuleViolation ruleViolation) { public void metricAdded(Metric metric) { } } + + private static class SimpleRenderer extends AbstractAccumulatingRenderer { + + /* default */ SimpleRenderer(String name, String description) { + super(name, description); + } + + @Override + public String defaultFileExtension() { + return null; + } + + @Override + public void end() throws IOException { + } + + public Report getReport() { + return report; + } + } } diff --git a/pmd-core/src/test/java/net/sourceforge/pmd/properties/AbstractNumericPropertyDescriptorTester.java b/pmd-core/src/test/java/net/sourceforge/pmd/properties/AbstractNumericPropertyDescriptorTester.java index aae68e30153..c9a880f5dca 100644 --- a/pmd-core/src/test/java/net/sourceforge/pmd/properties/AbstractNumericPropertyDescriptorTester.java +++ b/pmd-core/src/test/java/net/sourceforge/pmd/properties/AbstractNumericPropertyDescriptorTester.java @@ -10,6 +10,10 @@ import org.junit.Test; +import net.sourceforge.pmd.properties.builders.MultiNumericPropertyBuilder; +import net.sourceforge.pmd.properties.builders.SingleNumericPropertyBuilder; + + /** * @author Clément Fournier */ @@ -33,15 +37,15 @@ public void testLowerUpperLimit() { public void testMissingMinThreshold() { Map attributes = getPropertyDescriptorValues(); attributes.remove(PropertyDescriptorField.MIN); - getSingleFactory().createWith(attributes); + getSingleFactory().build(attributes); } @Override protected Map getPropertyDescriptorValues() { Map attributes = super.getPropertyDescriptorValues(); - attributes.put(PropertyDescriptorField.MIN, "0"); - attributes.put(PropertyDescriptorField.MAX, "10"); + attributes.put(PropertyDescriptorField.MIN, min().toString()); + attributes.put(PropertyDescriptorField.MAX, max().toString()); return attributes; } @@ -50,7 +54,30 @@ protected Map getPropertyDescriptorValues() { public void testMissingMaxThreshold() { Map attributes = getPropertyDescriptorValues(); attributes.remove(PropertyDescriptorField.MAX); - getSingleFactory().createWith(attributes); + getSingleFactory().build(attributes); + + } + + + @Test(expected = IllegalArgumentException.class) + public void testBadDefaultValue() { + singleBuilder().defaultValue(createBadValue()).build(); + } + + @Test(expected = IllegalArgumentException.class) + @SuppressWarnings("unchecked") + public void testMultiBadDefaultValue() { + multiBuilder().defaultValues(createValue(), createBadValue()).build(); } + + + protected abstract SingleNumericPropertyBuilder singleBuilder(); + + protected abstract MultiNumericPropertyBuilder multiBuilder(); + + + protected abstract T min(); + + protected abstract T max(); } diff --git a/pmd-core/src/test/java/net/sourceforge/pmd/properties/AbstractPackagedPropertyDescriptorTester.java b/pmd-core/src/test/java/net/sourceforge/pmd/properties/AbstractPackagedPropertyDescriptorTester.java index cec27a06b7a..7190685b383 100644 --- a/pmd-core/src/test/java/net/sourceforge/pmd/properties/AbstractPackagedPropertyDescriptorTester.java +++ b/pmd-core/src/test/java/net/sourceforge/pmd/properties/AbstractPackagedPropertyDescriptorTester.java @@ -22,8 +22,8 @@ public abstract class AbstractPackagedPropertyDescriptorTester extends Abstra public void testMissingPackageNames() { Map attributes = getPropertyDescriptorValues(); attributes.remove(PropertyDescriptorField.LEGAL_PACKAGES); - getMultiFactory().createWith(attributes); // no exception, null is ok - getSingleFactory().createWith(attributes); + getMultiFactory().build(attributes); // no exception, null is ok + getSingleFactory().build(attributes); } diff --git a/pmd-core/src/test/java/net/sourceforge/pmd/properties/AbstractPropertyDescriptorTester.java b/pmd-core/src/test/java/net/sourceforge/pmd/properties/AbstractPropertyDescriptorTester.java index dceaf101f32..b80e16d8e57 100644 --- a/pmd-core/src/test/java/net/sourceforge/pmd/properties/AbstractPropertyDescriptorTester.java +++ b/pmd-core/src/test/java/net/sourceforge/pmd/properties/AbstractPropertyDescriptorTester.java @@ -15,13 +15,17 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Random; +import java.util.regex.Pattern; import org.junit.Test; +import net.sourceforge.pmd.properties.builders.PropertyDescriptorExternalBuilder; + + /** - * Base functionality for all concrete subclasses that evaluate type-specific - * property descriptors. Checks for error conditions during construction, error - * value detection, serialization, etc. + * Base functionality for all concrete subclasses that evaluate type-specific property descriptors. Checks for error + * conditions during construction, error value detection, serialization, etc. * * @author Brian Remedios */ @@ -34,6 +38,9 @@ public abstract class AbstractPropertyDescriptorTester { public static final String ALPHA_NUMERIC_CHARS = DIGIT_CHARS + ALPHA_CHARS; public static final String ALL_CHARS = PUNCTUATION_CHARS + WHITESPACE_CHARS + ALPHA_NUMERIC_CHARS; private static final int MULTI_VALUE_COUNT = 10; + + private static final Random RANDOM = new Random(); + protected final String typeName; @@ -47,20 +54,23 @@ public AbstractPropertyDescriptorTester(String typeName) { @Test public void testFactorySingleValue() { - PropertyDescriptor prop = getSingleFactory().createWith(getPropertyDescriptorValues()); + PropertyDescriptor prop = getSingleFactory().build(getPropertyDescriptorValues()); T originalValue = createValue(); - T value = prop.valueFrom( - originalValue instanceof Class ? ((Class) originalValue).getName() : String.valueOf(originalValue)); - String asDelimitedString = prop.asDelimitedString(value); - Object value2 = prop.valueFrom(asDelimitedString); - assertEquals(value, value2); + T value = prop.valueFrom(originalValue instanceof Class ? ((Class) originalValue).getName() : String.valueOf(originalValue)); + T value2 = prop.valueFrom(prop.asDelimitedString(value)); + if (Pattern.class.equals(prop.type())) { + // Pattern.equals uses object identity... + // we're forced to do that to make it compare the string values of the pattern + assertEquals(String.valueOf(value), String.valueOf(value2)); + } else { + assertEquals(value, value2); + } } - @SuppressWarnings("unchecked") - protected final PropertyDescriptorFactory getSingleFactory() { - return (PropertyDescriptorFactory) PropertyDescriptorUtil.factoryFor(typeName); + protected final PropertyDescriptorExternalBuilder getSingleFactory() { + return (PropertyDescriptorExternalBuilder) PropertyTypeId.factoryFor(typeName); } @@ -83,8 +93,8 @@ protected Map getPropertyDescriptorValues() { @Test public void testFactoryMultiValueDefaultDelimiter() { - PropertyDescriptorFactory> multiFactory = getMultiFactory(); - PropertyDescriptor> prop = multiFactory.createWith(getPropertyDescriptorValues()); + PropertyDescriptorExternalBuilder> multiFactory = getMultiFactory(); + PropertyDescriptor> prop = multiFactory.build(getPropertyDescriptorValues()); List originalValue = createMultipleValues(MULTI_VALUE_COUNT); String asDelimitedString = prop.asDelimitedString(originalValue); List value2 = prop.valueFrom(asDelimitedString); @@ -93,8 +103,8 @@ public void testFactoryMultiValueDefaultDelimiter() { @SuppressWarnings("unchecked") - protected final PropertyDescriptorFactory> getMultiFactory() { - return (PropertyDescriptorFactory>) PropertyDescriptorUtil.factoryFor("List<" + typeName + ">"); + protected final PropertyDescriptorExternalBuilder> getMultiFactory() { + return (PropertyDescriptorExternalBuilder>) PropertyTypeId.factoryFor("List[" + typeName + "]"); } @@ -110,12 +120,12 @@ private List createMultipleValues(int count) { @Test public void testFactoryMultiValueCustomDelimiter() { - PropertyDescriptorFactory> multiFactory = getMultiFactory(); + PropertyDescriptorExternalBuilder> multiFactory = getMultiFactory(); Map valuesById = getPropertyDescriptorValues(); String customDelimiter = "ä"; assertFalse(ALL_CHARS.contains(customDelimiter)); valuesById.put(PropertyDescriptorField.DELIMITER, customDelimiter); - PropertyDescriptor> prop = multiFactory.createWith(valuesById); + PropertyDescriptor> prop = multiFactory.build(valuesById); List originalValue = createMultipleValues(MULTI_VALUE_COUNT); String asDelimitedString = prop.asDelimitedString(originalValue); List value2 = prop.valueFrom(asDelimitedString); @@ -149,8 +159,8 @@ public void testConstructors() { /** - * Attempt to create a property with faulty configuration values. This - * method should throw an IllegalArgumentException if done correctly. + * Attempt to create a property with faulty configuration values. This method should throw an + * IllegalArgumentException if done correctly. * * @return PropertyDescriptor */ @@ -183,7 +193,13 @@ public void testValueFrom() { T returnedValue = pmdProp.valueFrom(storeValue); - assertEquals(returnedValue, testValue); + if (Pattern.class.equals(pmdProp.type())) { + // Pattern.equals uses object identity... + // we're forced to do that to make it compare the string values of the pattern + assertEquals(String.valueOf(returnedValue), String.valueOf(testValue)); + } else { + assertEquals(returnedValue, testValue); + } } @@ -219,8 +235,7 @@ public void testErrorForBadSingle() { /** - * Return a value(s) that is known to be faulty per the general scope of the - * descriptor. + * Return a value(s) that is known to be faulty per the general scope of the descriptor. * * @return Object */ @@ -250,19 +265,26 @@ private List createMultipleBadValues(int count) { @Test public void testIsMultiValue() { assertFalse(createProperty().isMultiValue()); - assertTrue(createMultiProperty().isMultiValue()); } + @Test + public void testIsMultiValueMulti() { + assertTrue(createMultiProperty().isMultiValue()); + } + @Test public void testAddAttributes() { Map atts = createProperty().attributeValuesById(); - Map multiAtts = createMultiProperty().attributeValuesById(); - assertTrue(atts.containsKey(PropertyDescriptorField.NAME)); assertTrue(atts.containsKey(PropertyDescriptorField.DESCRIPTION)); assertTrue(atts.containsKey(PropertyDescriptorField.DEFAULT_VALUE)); + } + + @Test + public void testAddAttributesMulti() { + Map multiAtts = createMultiProperty().attributeValuesById(); assertTrue(multiAtts.containsKey(PropertyDescriptorField.DELIMITER)); assertTrue(multiAtts.containsKey(PropertyDescriptorField.NAME)); assertTrue(multiAtts.containsKey(PropertyDescriptorField.DESCRIPTION)); @@ -272,85 +294,47 @@ public void testAddAttributes() { @Test public void testType() { - assertNotNull(createMultiProperty().type()); assertNotNull(createProperty().type()); } - public static boolean randomBool() { - return ((Math.random() * 100) % 2) == 0; - } - - - public static int randomInt() { - - int randomVal = (int) (Math.random() * 100 + 1D); - return randomVal + (int) (Math.random() * 100000D); + @Test + public void testTypeMulti() { + assertNotNull(createMultiProperty().type()); } - - public static String randomString(int length) { - - final char[] chars = ALPHA_CHARS.toCharArray(); - - StringBuilder sb = new StringBuilder(length); - for (int i = 0; i < length; i++) { - sb.append(randomChar(chars)); - } - return sb.toString(); + static boolean randomBool() { + return RANDOM.nextBoolean(); } - public static char randomChar(char[] characters) { - return characters[randomInt(0, characters.length - 1)]; + static char randomChar(char[] characters) { + return characters[randomInt(0, characters.length)]; } - public static int randomInt(int min, int max) { - if (max < min) { - max = min; - } - int range = Math.abs(max - min); - int x = (int) (range * Math.random()); - return x + min; + static int randomInt(int min, int max) { + return (int) randomLong(min, max); } - public static float randomFloat(float min, float max) { - + static float randomFloat(float min, float max) { return (float) randomDouble(min, max); } - public static double randomDouble(double min, double max) { - if (max < min) { - max = min; - } - double range = Math.abs(max - min); - double x = range * Math.random(); - return x + min; + static double randomDouble(double min, double max) { + return min + RANDOM.nextDouble() * Math.abs(max - min); } - public static long randomLong(long min, long max) { - if (max < min) { - max = min; - } - long range = Math.abs(max - min); - long x = (long) (range * Math.random()); - return x + min; + static long randomLong(long min, long max) { + return min + RANDOM.nextInt((int) Math.abs(max - min)); } - /** - * Method randomChoice. - * - * @param items Object[] - * - * @return Object - */ - public static T randomChoice(T[] items) { - return items[randomInt(0, items.length - 1)]; + static T randomChoice(T[] items) { + return items[randomInt(0, items.length)]; } @@ -359,22 +343,21 @@ public static T randomChoice(T[] items) { * * @param chars char[] * @param removeChar char - * * @return char[] */ - protected static final char[] filter(char[] chars, char removeChar) { + protected static char[] filter(char[] chars, char removeChar) { int count = 0; - for (int i = 0; i < chars.length; i++) { - if (chars[i] == removeChar) { + for (char c : chars) { + if (c == removeChar) { count++; } } char[] results = new char[chars.length - count]; int index = 0; - for (int i = 0; i < chars.length; i++) { - if (chars[i] != removeChar) { - results[index++] = chars[i]; + for (char c : chars) { + if (c != removeChar) { + results[index++] = c; } } return results; diff --git a/pmd-core/src/test/java/net/sourceforge/pmd/properties/BooleanPropertyTest.java b/pmd-core/src/test/java/net/sourceforge/pmd/properties/BooleanPropertyTest.java index 45b765b9201..2e5cda931b6 100644 --- a/pmd-core/src/test/java/net/sourceforge/pmd/properties/BooleanPropertyTest.java +++ b/pmd-core/src/test/java/net/sourceforge/pmd/properties/BooleanPropertyTest.java @@ -20,7 +20,7 @@ public BooleanPropertyTest() { @Override protected Boolean createValue() { - return System.currentTimeMillis() % 1 > 0 ? Boolean.TRUE : Boolean.FALSE; + return randomBool(); } diff --git a/pmd-core/src/test/java/net/sourceforge/pmd/properties/DoublePropertyTest.java b/pmd-core/src/test/java/net/sourceforge/pmd/properties/DoublePropertyTest.java index 40d087f9028..bc940ecf9c5 100644 --- a/pmd-core/src/test/java/net/sourceforge/pmd/properties/DoublePropertyTest.java +++ b/pmd-core/src/test/java/net/sourceforge/pmd/properties/DoublePropertyTest.java @@ -39,6 +39,18 @@ protected Double createBadValue() { } + protected DoubleProperty.DoublePBuilder singleBuilder() { + return DoubleProperty.named("test").desc("foo") + .range(MIN, MAX).defaultValue(createValue()).uiOrder(1.0f); + } + + + protected DoubleMultiProperty.DoubleMultiPBuilder multiBuilder() { + return DoubleMultiProperty.named("test").desc("foo") + .range(MIN, MAX).defaultValues(createValue(), createValue()).uiOrder(1.0f); + } + + @Override protected PropertyDescriptor createProperty() { return new DoubleProperty("testDouble", "Test double property", MIN, MAX, 9.0, 1.0f); @@ -63,4 +75,16 @@ protected PropertyDescriptor> createBadMultiProperty() { return new DoubleMultiProperty("testDouble", "Test double property", MIN, MAX, new Double[] {MIN - SHIFT, MIN, MIN + SHIFT, MAX + SHIFT}, 1.0f); } + + + @Override + protected Double min() { + return MIN; + } + + + @Override + protected Double max() { + return MAX; + } } diff --git a/pmd-core/src/test/java/net/sourceforge/pmd/properties/FloatPropertyTest.java b/pmd-core/src/test/java/net/sourceforge/pmd/properties/FloatPropertyTest.java index 7d2657c304a..4ebe325a7d7 100644 --- a/pmd-core/src/test/java/net/sourceforge/pmd/properties/FloatPropertyTest.java +++ b/pmd-core/src/test/java/net/sourceforge/pmd/properties/FloatPropertyTest.java @@ -6,6 +6,7 @@ import java.util.List; + /** * Evaluates the functionality of the FloatProperty descriptor by testing its * ability to catch creation errors (illegal args), flag out-of-range test @@ -34,7 +35,21 @@ protected Float createValue() { @Override protected Float createBadValue() { - return randomBool() ? randomFloat(MIN - SHIFT, MIN) : randomFloat(MAX, MAX + SHIFT); + return randomBool() ? randomFloat(MIN - SHIFT, MIN) : randomFloat(MAX + 1, MAX + SHIFT); + } + + + @Override + protected FloatProperty.FloatPBuilder singleBuilder() { + return FloatProperty.named("test").desc("foo") + .range(MIN, MAX).defaultValue(createValue()).uiOrder(1.0f); + } + + + @Override + protected FloatMultiProperty.FloatMultiPBuilder multiBuilder() { + return FloatMultiProperty.named("test").desc("foo") + .range(MIN, MAX).defaultValues(createValue(), createValue()).uiOrder(1.0f); } @@ -47,7 +62,7 @@ protected PropertyDescriptor createProperty() { @Override protected PropertyDescriptor> createMultiProperty() { return new FloatMultiProperty("testFloat", "Test float property", MIN, MAX, - new Float[] {-1f, 0f, 1f, 2f}, 1.0f); + new Float[]{6f, 9f, 1f, 2f}, 1.0f); } @@ -60,15 +75,20 @@ protected PropertyDescriptor createBadProperty() { @Override protected PropertyDescriptor> createBadMultiProperty() { return new FloatMultiProperty("testFloat", "Test float property", 0f, 5f, - new Float[] {-1f, 0f, 1f, 2f}, 1.0f); + new Float[]{-1f, 0f, 1f, 2f}, 1.0f); } - public static FloatProperty randomProperty(int nameLength, int descLength, boolean multiValue) { + @Override + protected Float min() { + return MIN; + } - float defalt = randomFloat(0, 1000f); - return new FloatProperty(randomString(nameLength), randomString(descLength), defalt - 1000f, defalt + 1000, - defalt, 0f); + @Override + protected Float max() { + return MAX; } + + } diff --git a/pmd-core/src/test/java/net/sourceforge/pmd/properties/IntegerPropertyTest.java b/pmd-core/src/test/java/net/sourceforge/pmd/properties/IntegerPropertyTest.java index 13416f762f4..746c7e242a7 100644 --- a/pmd-core/src/test/java/net/sourceforge/pmd/properties/IntegerPropertyTest.java +++ b/pmd-core/src/test/java/net/sourceforge/pmd/properties/IntegerPropertyTest.java @@ -6,6 +6,7 @@ import java.util.List; + /** * Evaluates the functionality of the IntegerProperty descriptor by testing its * ability to catch creation errors (illegal args), flag out-of-range test @@ -49,6 +50,18 @@ protected Integer createBadValue() { } + protected IntegerProperty.IntegerPBuilder singleBuilder() { + return IntegerProperty.named("test").desc("foo") + .range(MIN, MAX).defaultValue(createValue()).uiOrder(1.0f); + } + + + protected IntegerMultiProperty.IntegerMultiPBuilder multiBuilder() { + return IntegerMultiProperty.named("test").desc("foo") + .range(MIN, MAX).defaultValues(createValue(), createValue()).uiOrder(1.0f); + } + + @Override protected PropertyDescriptor createProperty() { return new IntegerProperty("testInteger", "Test integer property", MIN, MAX, MAX - 1, 1.0f); @@ -73,4 +86,16 @@ protected PropertyDescriptor createBadProperty() { protected PropertyDescriptor> createBadMultiProperty() { return new IntegerMultiProperty("testInteger", "", MIN, MAX, new Integer[] {MIN - 1, MAX}, 1.0f); } + + + @Override + protected Integer min() { + return MIN; + } + + + @Override + protected Integer max() { + return MAX; + } } diff --git a/pmd-core/src/test/java/net/sourceforge/pmd/properties/LongPropertyTest.java b/pmd-core/src/test/java/net/sourceforge/pmd/properties/LongPropertyTest.java index 123ec37a97a..a35ce908db5 100644 --- a/pmd-core/src/test/java/net/sourceforge/pmd/properties/LongPropertyTest.java +++ b/pmd-core/src/test/java/net/sourceforge/pmd/properties/LongPropertyTest.java @@ -6,6 +6,7 @@ import java.util.List; + /** * @author Clément Fournier */ @@ -29,7 +30,21 @@ protected Long createValue() { @Override protected Long createBadValue() { - return randomBool() ? randomLong(MIN - SHIFT, MIN) : randomLong(MAX, MAX + SHIFT); + return randomBool() ? randomLong(MIN - SHIFT, MIN) : randomLong(MAX + 1, MAX + SHIFT); + } + + + @Override + protected LongProperty.LongPBuilder singleBuilder() { + return LongProperty.named("test").desc("foo") + .range(MIN, MAX).defaultValue(createValue()).uiOrder(1.0f); + } + + + @Override + protected LongMultiProperty.LongMultiPBuilder multiBuilder() { + return LongMultiProperty.named("test").desc("foo") + .range(MIN, MAX).defaultValues(createValue(), createValue()).uiOrder(1.0f); } @@ -42,7 +57,7 @@ protected PropertyDescriptor createProperty() { @Override protected PropertyDescriptor> createMultiProperty() { return new LongMultiProperty("testFloat", "Test float property", MIN, MAX, - new Long[] {-1000L, 0L, 100L, 20L}, 1.0f); + new Long[]{1000L, 10L, 100L, 20L}, 1.0f); } @@ -55,7 +70,18 @@ protected PropertyDescriptor createBadProperty() { @Override protected PropertyDescriptor> createBadMultiProperty() { return new LongMultiProperty("testFloat", "Test float property", 0L, 5L, - new Long[] {-1000L, 0L, 100L, 20L}, 1.0f); + new Long[]{-1000L, 0L, 100L, 20L}, 1.0f); + } + + + @Override + protected Long min() { + return MIN; } + + @Override + protected Long max() { + return MAX; + } } diff --git a/pmd-core/src/test/java/net/sourceforge/pmd/properties/MethodPropertyTest.java b/pmd-core/src/test/java/net/sourceforge/pmd/properties/MethodPropertyTest.java index 5d675607b4e..3ed12aa0fa6 100644 --- a/pmd-core/src/test/java/net/sourceforge/pmd/properties/MethodPropertyTest.java +++ b/pmd-core/src/test/java/net/sourceforge/pmd/properties/MethodPropertyTest.java @@ -10,12 +10,15 @@ import java.lang.reflect.Method; import java.util.HashMap; import java.util.List; +import java.util.Map; +import org.junit.Assume; import org.junit.Test; import net.sourceforge.pmd.properties.modules.MethodPropertyModule; import net.sourceforge.pmd.util.ClassUtil; + /** * Evaluates the functionality of the MethodProperty descriptor by testing its * ability to catch creation errors (illegal args), flag invalid methods per the @@ -41,14 +44,24 @@ public MethodPropertyTest() { } + @Override + @Test + public void testMissingPackageNames() { + Map attributes = getPropertyDescriptorValues(); + attributes.remove(PropertyDescriptorField.LEGAL_PACKAGES); + new MethodProperty("p", "d", ALL_METHODS[1], null, 1.0f); // no exception, null is ok + new MethodMultiProperty("p", "d", new Method[]{ALL_METHODS[2], ALL_METHODS[3]}, null, 1.0f); // no exception, null is ok + } + + @Test public void testAsStringOn() { Method method; - for (int i = 0; i < METHOD_SIGNATURES.length; i++) { - method = ValueParserConstants.METHOD_PARSER.valueOf(METHOD_SIGNATURES[i]); - assertNotNull("Unable to identify method: " + METHOD_SIGNATURES[i], method); + for (String methodSignature : METHOD_SIGNATURES) { + method = ValueParserConstants.METHOD_PARSER.valueOf(methodSignature); + assertNotNull("Unable to identify method: " + methodSignature, method); } } @@ -86,28 +99,50 @@ protected Method createBadValue() { @Override protected PropertyDescriptor createProperty() { - return new MethodProperty("methodProperty", "asdf", ALL_METHODS[1], new String[] {"java.lang", "org.apache"}, - 1.0f); + return new MethodProperty("methodProperty", "asdf", ALL_METHODS[1], new String[]{"java.lang", "org.apache"}, + 1.0f); } @Override protected PropertyDescriptor> createMultiProperty() { - return new MethodMultiProperty("methodProperty", "asdf", new Method[] {ALL_METHODS[2], ALL_METHODS[3]}, - new String[] {"java.lang"}, 1.0f); + return new MethodMultiProperty("methodProperty", "asdf", new Method[]{ALL_METHODS[2], ALL_METHODS[3]}, + new String[]{"java.lang"}, 1.0f); } @Override protected PropertyDescriptor createBadProperty() { - return new MethodProperty("methodProperty", "asdf", ALL_METHODS[1], new String[] {"java.util"}, 1.0f); + return new MethodProperty("methodProperty", "asdf", ALL_METHODS[1], new String[]{"java.util"}, 1.0f); } @Override protected PropertyDescriptor> createBadMultiProperty() { - return new MethodMultiProperty("methodProperty", "asdf", new Method[] {ALL_METHODS[2], ALL_METHODS[3]}, - new String[] {"java.util"}, 1.0f); + return new MethodMultiProperty("methodProperty", "asdf", new Method[]{ALL_METHODS[2], ALL_METHODS[3]}, + new String[]{"java.util"}, 1.0f); + } + + + @Override + @Test + public void testFactorySingleValue() { + Assume.assumeTrue("MethodProperty cannot be built from XPath (#762)", false); } + + + @Override + @Test + public void testFactoryMultiValueCustomDelimiter() { + Assume.assumeTrue("MethodProperty cannot be built from XPath (#762)", false); + } + + + @Override + @Test + public void testFactoryMultiValueDefaultDelimiter() { + Assume.assumeTrue("MethodProperty cannot be built from XPath (#762)", false); + } + } diff --git a/pmd-core/src/test/java/net/sourceforge/pmd/properties/NonRuleWithAllPropertyTypes.java b/pmd-core/src/test/java/net/sourceforge/pmd/properties/NonRuleWithAllPropertyTypes.java index 9c4b4205c4b..cc93b240e3b 100644 --- a/pmd-core/src/test/java/net/sourceforge/pmd/properties/NonRuleWithAllPropertyTypes.java +++ b/pmd-core/src/test/java/net/sourceforge/pmd/properties/NonRuleWithAllPropertyTypes.java @@ -34,8 +34,7 @@ public class NonRuleWithAllPropertyTypes extends AbstractRule { public static final StringProperty SINGLE_STR = new StringProperty("singleStr", "String value", "hello world", 3.0f); public static final StringMultiProperty MULTI_STR = new StringMultiProperty("multiStr", "Multiple string values", new String[] {"hello", "world"}, 5.0f, '|'); - public static final IntegerProperty SINGLE_INT = new IntegerProperty("singleInt", "Single integer value", 1, 10, 8, - 3.0f); + public static final IntegerProperty SINGLE_INT = IntegerProperty.named("singleInt").desc("Single integer value").range(1, 10).defaultValue(8).uiOrder(3.0f).build(); public static final IntegerMultiProperty MULTI_INT = new IntegerMultiProperty("multiInt", "Multiple integer values", 0, 10, new Integer[] {1, 2, 3, 4}, 5.0f); public static final LongProperty SINGLE_LONG = new LongProperty("singleLong", "Single long value", 1L, 10L, 8L, diff --git a/pmd-core/src/test/java/net/sourceforge/pmd/properties/RegexPropertyTest.java b/pmd-core/src/test/java/net/sourceforge/pmd/properties/RegexPropertyTest.java new file mode 100644 index 00000000000..5f760e82807 --- /dev/null +++ b/pmd-core/src/test/java/net/sourceforge/pmd/properties/RegexPropertyTest.java @@ -0,0 +1,101 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.properties; + +import java.util.List; +import java.util.regex.Pattern; + + +/** + * Since there's no RegexMultiProperty the base class is only partially implemented, + * and some tests are overridden with no-op ones. + * + * @author Clément Fournier + * @since 6.2.0 + */ +public class RegexPropertyTest extends AbstractPropertyDescriptorTester { + public RegexPropertyTest() { + super("Regex"); + } + + + @Override + protected Pattern createValue() { + return Pattern.compile("abc++"); + } + + + @Override + protected Pattern createBadValue() { + return null; + } + + + @Override + protected PropertyDescriptor createProperty() { + return RegexProperty.named("foo").defaultValue("(ec|sa)+").desc("the description").build(); + } + + + @Override + protected PropertyDescriptor createBadProperty() { + return RegexProperty.named("foo").defaultValue("(ec|sa").desc("the description").build(); + } + + + // The following are deliberately unimplemented, since they are only relevant to the tests of the multiproperty + + @Override + protected PropertyDescriptor> createMultiProperty() { + throw new UnsupportedOperationException(); + } + + + @Override + protected PropertyDescriptor> createBadMultiProperty() { + throw new UnsupportedOperationException(); + } + + @Override + public void testAddAttributesMulti() { + } + + + @Override + public void testAsDelimitedString() { + } + + + @Override + public void testErrorForBadMulti() { + } + + + @Override + public void testErrorForCorrectMulti() { + } + + + @Override + public void testFactoryMultiValueDefaultDelimiter() { + } + + + @Override + public void testFactoryMultiValueCustomDelimiter() { + } + + + @Override + public void testTypeMulti() { + } + + + @Override + public void testIsMultiValueMulti() { + } + + +} diff --git a/pmd-core/src/test/java/net/sourceforge/pmd/properties/SimpleEnumeratedPropertyTest.java b/pmd-core/src/test/java/net/sourceforge/pmd/properties/SimpleEnumeratedPropertyTest.java index 7668f79a959..6626ccf6296 100644 --- a/pmd-core/src/test/java/net/sourceforge/pmd/properties/SimpleEnumeratedPropertyTest.java +++ b/pmd-core/src/test/java/net/sourceforge/pmd/properties/SimpleEnumeratedPropertyTest.java @@ -6,9 +6,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; -import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.List; @@ -80,29 +78,18 @@ protected PropertyDescriptor> createMultiProperty() { } - @Test + @Test(expected = IllegalArgumentException.class) public void testDefaultIndexOutOfBounds() { - try { - EnumeratedMultiProperty multi - = new EnumeratedMultiProperty<>("testEnumerations", "Test enumerations with simple type", + new EnumeratedMultiProperty<>("testEnumerations", "Test enumerations with simple type", KEYS, VALUES, new int[] {99}, Foo.class, 1.0f); - } catch (IllegalArgumentException iae) { - return; - } - fail(); } - @Test + @Test(expected = IllegalArgumentException.class) public void testNoMappingForDefault() { - try { - EnumeratedMultiProperty multi - = new EnumeratedMultiProperty<>("testEnumerations", "Test enumerations with simple type", - MAPPINGS, Arrays.asList(Foo.IGNORED), Foo.class, 1.0f); - } catch (IllegalArgumentException iae) { - return; - } - fail(); + new EnumeratedMultiProperty<>("testEnumerations", "Test enumerations with simple type", + MAPPINGS, Collections.singletonList(Foo.IGNORED), Foo.class, 1.0f); + } @@ -120,13 +107,13 @@ public void creationTest() { @Override protected Foo createValue() { - return randomChoice(Foo.values()); + return randomChoice(VALUES); } @Override protected Foo createBadValue() { - return null; // not in the set of values + return Foo.IGNORED; // not in the set of values } 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 24149668422..36a09601553 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 String getExpectedWithProperties() { public String getExpectedError(ProcessingError error) { return ""; } - + public String getExpectedError(ConfigurationError error) { return ""; } @@ -68,14 +68,14 @@ private Report reportTwoViolations() { return report; } - private RuleViolation newRuleViolation(int endColumn) { + protected RuleViolation newRuleViolation(int endColumn) { DummyNode node = createNode(endColumn); RuleContext ctx = new RuleContext(); ctx.setSourceCodeFilename(getSourceCodeFilename()); return new ParametricRuleViolation(new FooRule(), ctx, node, "blah"); } - private static DummyNode createNode(int endColumn) { + protected static DummyNode createNode(int endColumn) { DummyNode node = new DummyNode(1); node.testingOnlySetBeginLine(1); node.testingOnlySetBeginColumn(1); @@ -127,7 +127,7 @@ public void testError() throws Exception { 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/CodeClimateRendererTest.java b/pmd-core/src/test/java/net/sourceforge/pmd/renderers/CodeClimateRendererTest.java index ac5dcd5833d..3db40998388 100644 --- a/pmd-core/src/test/java/net/sourceforge/pmd/renderers/CodeClimateRendererTest.java +++ b/pmd-core/src/test/java/net/sourceforge/pmd/renderers/CodeClimateRendererTest.java @@ -4,7 +4,18 @@ package net.sourceforge.pmd.renderers; +import static org.junit.Assert.assertEquals; + +import org.junit.Test; + import net.sourceforge.pmd.PMD; +import net.sourceforge.pmd.Report; +import net.sourceforge.pmd.ReportTest; +import net.sourceforge.pmd.RuleContext; +import net.sourceforge.pmd.lang.ast.DummyNode; +import net.sourceforge.pmd.lang.ast.Node; +import net.sourceforge.pmd.lang.rule.ParametricRuleViolation; +import net.sourceforge.pmd.lang.rule.XPathRule; public class CodeClimateRendererTest extends AbstractRendererTst { @@ -20,7 +31,7 @@ public String getExpected() { + "[Categories](https://github.com/codeclimate/spec/blob/master/SPEC.md#categories): Style\\n\\n" + "[Remediation Points](https://github.com/codeclimate/spec/blob/master/SPEC.md#remediation-points): 50000\\n\\n" + "desc\\n\\n" - + "### [PMD properties](http://pmd.sourceforge.net/snapshot/customizing/pmd-developer.html)\\n\\n" + + "### [PMD properties](https://pmd.github.io/latest/pmd_devdocs_working_with_properties.html)\\n\\n" + "Name | Value | Description\\n" + "--- | --- | ---\\n" + "violationSuppressRegex | | Suppress violations with messages matching a regular expression\\n" + "violationSuppressXPath | | Suppress violations on nodes which match a given relative XPath expression.\\n" @@ -35,7 +46,7 @@ public String getExpectedWithProperties() { + "[Categories](https://github.com/codeclimate/spec/blob/master/SPEC.md#categories): Style\\n\\n" + "[Remediation Points](https://github.com/codeclimate/spec/blob/master/SPEC.md#remediation-points): 50000\\n\\n" + "desc\\n\\n" - + "### [PMD properties](http://pmd.sourceforge.net/snapshot/customizing/pmd-developer.html)\\n\\n" + + "### [PMD properties](https://pmd.github.io/latest/pmd_devdocs_working_with_properties.html)\\n\\n" + "Name | Value | Description\\n" + "--- | --- | ---\\n" + "violationSuppressRegex | | Suppress violations with messages matching a regular expression\\n" + "violationSuppressXPath | | Suppress violations on nodes which match a given relative XPath expression.\\n" @@ -57,7 +68,7 @@ public String getExpectedMultiple() { + "[Categories](https://github.com/codeclimate/spec/blob/master/SPEC.md#categories): Style\\n\\n" + "[Remediation Points](https://github.com/codeclimate/spec/blob/master/SPEC.md#remediation-points): 50000\\n\\n" + "desc\\n\\n" - + "### [PMD properties](http://pmd.sourceforge.net/snapshot/customizing/pmd-developer.html)\\n\\n" + + "### [PMD properties](https://pmd.github.io/latest/pmd_devdocs_working_with_properties.html)\\n\\n" + "Name | Value | Description\\n" + "--- | --- | ---\\n" + "violationSuppressRegex | | Suppress violations with messages matching a regular expression\\n" + "violationSuppressXPath | | Suppress violations on nodes which match a given relative XPath expression.\\n" @@ -67,11 +78,31 @@ public String getExpectedMultiple() { + "[Categories](https://github.com/codeclimate/spec/blob/master/SPEC.md#categories): Style\\n\\n" + "[Remediation Points](https://github.com/codeclimate/spec/blob/master/SPEC.md#remediation-points): 50000\\n\\n" + "desc\\n\\n" - + "### [PMD properties](http://pmd.sourceforge.net/snapshot/customizing/pmd-developer.html)\\n\\n" + + "### [PMD properties](https://pmd.github.io/latest/pmd_devdocs_working_with_properties.html)\\n\\n" + "Name | Value | Description\\n" + "--- | --- | ---\\n" + "violationSuppressRegex | | Suppress violations with messages matching a regular expression\\n" + "violationSuppressXPath | | Suppress violations on nodes which match a given relative XPath expression.\\n" + "\"},\"categories\":[\"Style\"],\"location\":{\"path\":\"n/a\",\"lines\":{\"begin\":1,\"end\":1}},\"severity\":\"info\",\"remediation_points\":50000}" + "\u0000" + PMD.EOL; } + + @Test + public void testXPathRule() throws Exception { + DummyNode node = createNode(1); + RuleContext ctx = new RuleContext(); + ctx.setSourceCodeFilename(getSourceCodeFilename()); + Report report = new Report(); + XPathRule theRule = new XPathRule(); + theRule.setProperty(XPathRule.XPATH_DESCRIPTOR, "//dummyNode"); + + // Setup as FooRule + theRule.setDescription("desc"); + theRule.setName("Foo"); + + report.addRuleViolation(new ParametricRuleViolation(theRule, ctx, node, "blah")); + String rendered = ReportTest.render(getRenderer(), report); + + // Output should be the exact same as for non xpath rules + assertEquals(filter(getExpected()), filter(rendered)); + } } diff --git a/pmd-core/src/test/java/net/sourceforge/pmd/renderers/XMLRendererTest.java b/pmd-core/src/test/java/net/sourceforge/pmd/renderers/XMLRendererTest.java index 89f1292efe4..b8b0df7694a 100644 --- a/pmd-core/src/test/java/net/sourceforge/pmd/renderers/XMLRendererTest.java +++ b/pmd-core/src/test/java/net/sourceforge/pmd/renderers/XMLRendererTest.java @@ -5,7 +5,6 @@ package net.sourceforge.pmd.renderers; import java.io.StringReader; - import javax.xml.parsers.DocumentBuilderFactory; import org.junit.Assert; @@ -16,6 +15,7 @@ import net.sourceforge.pmd.FooRule; import net.sourceforge.pmd.PMD; +import net.sourceforge.pmd.PMDVersion; import net.sourceforge.pmd.Report; import net.sourceforge.pmd.Report.ConfigurationError; import net.sourceforge.pmd.Report.ProcessingError; @@ -116,6 +116,6 @@ public String getHeader() { + "" + PMD.EOL; + + " version=\"" + PMDVersion.VERSION + "\" timestamp=\"2014-10-06T19:30:51.262\">" + PMD.EOL; } } 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 06fe984d397..7e968a7c300 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 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 @@ private void deleteDirectory(File dir) { 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/java/net/sourceforge/pmd/util/database/DBTypeTest.java b/pmd-core/src/test/java/net/sourceforge/pmd/util/database/DBTypeTest.java index 2e9dce0ee46..2bdd29465f6 100644 --- a/pmd-core/src/test/java/net/sourceforge/pmd/util/database/DBTypeTest.java +++ b/pmd-core/src/test/java/net/sourceforge/pmd/util/database/DBTypeTest.java @@ -11,7 +11,6 @@ import java.util.Properties; import java.util.ResourceBundle; -import org.apache.commons.io.IOUtils; import org.junit.After; import org.junit.Assert; import org.junit.Before; @@ -39,17 +38,12 @@ public void setUp() throws Exception { includeProperties.putAll(testProperties); includeProperties.put("prop3", "include3"); - PrintStream printStream = null; - try { - absoluteFile = File.createTempFile("dbtypetest", ".properties"); - FileOutputStream fileOutputStream = new FileOutputStream(absoluteFile); - printStream = new PrintStream(fileOutputStream); - + absoluteFile = File.createTempFile("dbtypetest", ".properties"); + try (FileOutputStream fileOutputStream = new FileOutputStream(absoluteFile); + PrintStream printStream = new PrintStream(fileOutputStream)) { for (Entry entry : testProperties.entrySet()) { printStream.printf("%s=%s\n", entry.getKey(), entry.getValue()); } - } finally { - IOUtils.closeQuietly(printStream); } } diff --git a/pmd-core/src/test/java/net/sourceforge/pmd/util/datasource/FileDataSourceTest.java b/pmd-core/src/test/java/net/sourceforge/pmd/util/datasource/FileDataSourceTest.java new file mode 100644 index 00000000000..6f166bb4ae5 --- /dev/null +++ b/pmd-core/src/test/java/net/sourceforge/pmd/util/datasource/FileDataSourceTest.java @@ -0,0 +1,105 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.util.datasource; + +import static org.junit.Assert.assertEquals; + +import java.io.File; +import java.io.IOException; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; + +public class FileDataSourceTest { + + @Rule + public TemporaryFolder tempFolder = new TemporaryFolder(); + + private static final String SOMEFILE_DIR = "path/"; + private static final String SOMEFILE_TXT = "somefile.txt"; + private static final String SOMEFILE_TXT_FULL_PATH = SOMEFILE_DIR + SOMEFILE_TXT; + + private FileDataSource ds; + private File someFile; + private File someFolder; + + @Before + public void setup() throws IOException { + someFolder = tempFolder.newFolder(SOMEFILE_DIR); + someFile = tempFolder.newFile(SOMEFILE_TXT_FULL_PATH); + ds = new FileDataSource(someFile); + } + + @Test + public void testShortNamesSingleFile() { + assertEquals(SOMEFILE_TXT, ds.getNiceFileName(true, someFile.getAbsolutePath())); + } + + @Test + public void testShortNamesSingleDir() { + assertEquals(SOMEFILE_TXT, ds.getNiceFileName(true, someFolder.getAbsolutePath())); + } + + @Test + public void testShortNamesNullBase() { + assertEquals(SOMEFILE_TXT, ds.getNiceFileName(true, null)); + } + + @Test + public void testShortNamesCommaSeparatedDirs() { + // use 2 dirs, one relative (similar, but not resolving to the same location) and one absolute + assertEquals(SOMEFILE_TXT, ds.getNiceFileName(true, SOMEFILE_DIR + "," + someFolder.getAbsolutePath())); + } + + @Test + public void testShortNamesCommaSeparatedFiles() { + // use 2 files, one relative (similar, but not resolving to the same location) and one absolute + assertEquals(SOMEFILE_TXT, ds.getNiceFileName(true, SOMEFILE_TXT_FULL_PATH + "," + someFile.getAbsolutePath())); + } + + @Test + public void testShortNamesCommaSeparatedMixed() { + // use a file and a dir, one relative (similar, but not resolving to the same location) and one absolute + assertEquals(SOMEFILE_TXT, ds.getNiceFileName(true, SOMEFILE_TXT_FULL_PATH + "," + someFolder.getAbsolutePath())); + } + + @Test + public void testLongNamesSingleFile() throws IOException { + assertEquals(someFile.getCanonicalFile().getAbsolutePath(), ds.getNiceFileName(false, someFile.getAbsolutePath())); + } + + @Test + public void testLongNamesSingleDir() throws IOException { + assertEquals(someFile.getCanonicalFile().getAbsolutePath(), ds.getNiceFileName(false, someFolder.getAbsolutePath())); + } + + @Test + public void testLongNamesNullBase() throws IOException { + assertEquals(someFile.getCanonicalFile().getAbsolutePath(), ds.getNiceFileName(false, null)); + } + + @Test + public void testLongNamesCommaSeparatedDirs() throws IOException { + // use 2 dirs, one relative (similar, but not resolving to the same location) and one absolute + assertEquals(someFile.getCanonicalFile().getAbsolutePath(), + ds.getNiceFileName(false, SOMEFILE_DIR + "," + someFolder.getAbsolutePath())); + } + + @Test + public void testLongNamesCommaSeparatedFiles() throws IOException { + // use 2 files, one relative (similar, but not resolving to the same location) and one absolute + assertEquals(someFile.getCanonicalFile().getAbsolutePath(), + ds.getNiceFileName(false, SOMEFILE_TXT_FULL_PATH + "," + someFile.getAbsolutePath())); + } + + @Test + public void testLongNamesCommaSeparatedMixed() throws IOException { + // use a file and a dir, one relative (similar, but not resolving to the same location) and one absolute + assertEquals(someFile.getCanonicalFile().getAbsolutePath(), + ds.getNiceFileName(false, SOMEFILE_TXT_FULL_PATH + "," + someFolder.getAbsolutePath())); + } +} diff --git a/pmd-core/src/test/resources/net/sourceforge/pmd/TestRuleset1.xml b/pmd-core/src/test/resources/net/sourceforge/pmd/TestRuleset1.xml index a3925767ef5..aaee1b98f06 100644 --- a/pmd-core/src/test/resources/net/sourceforge/pmd/TestRuleset1.xml +++ b/pmd-core/src/test/resources/net/sourceforge/pmd/TestRuleset1.xml @@ -1,6 +1,6 @@ + xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd"> Ruleset used by test net.sourceforge.pmd.RuleSetWriter and RuleSetFactoryTest @@ -32,6 +32,24 @@ Just for test + + + Just for test + + 3 + + + + + + + + + + \ No newline at end of file diff --git a/pmd-core/src/test/resources/net/sourceforge/pmd/TestRuleset2.xml b/pmd-core/src/test/resources/net/sourceforge/pmd/TestRuleset2.xml index b8e13e031d4..dfaa96ecea1 100644 --- a/pmd-core/src/test/resources/net/sourceforge/pmd/TestRuleset2.xml +++ b/pmd-core/src/test/resources/net/sourceforge/pmd/TestRuleset2.xml @@ -1,6 +1,6 @@ + xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd"> Ruleset used by test RuleSetFactoryTest diff --git a/pmd-core/src/test/resources/net/sourceforge/pmd/TestRuleset3.xml b/pmd-core/src/test/resources/net/sourceforge/pmd/TestRuleset3.xml index 1df2429a60c..7b3f1a89246 100644 --- a/pmd-core/src/test/resources/net/sourceforge/pmd/TestRuleset3.xml +++ b/pmd-core/src/test/resources/net/sourceforge/pmd/TestRuleset3.xml @@ -1,6 +1,6 @@ + xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd"> Ruleset used by test RuleSetFactoryTest diff --git a/pmd-core/src/test/resources/net/sourceforge/pmd/TestRuleset4.xml b/pmd-core/src/test/resources/net/sourceforge/pmd/TestRuleset4.xml index 74577f33a79..0923d45e34c 100644 --- a/pmd-core/src/test/resources/net/sourceforge/pmd/TestRuleset4.xml +++ b/pmd-core/src/test/resources/net/sourceforge/pmd/TestRuleset4.xml @@ -1,6 +1,6 @@ + xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd"> Ruleset used by test RuleSetFactoryTest diff --git a/pmd-core/src/test/resources/net/sourceforge/pmd/cli/filelist3.txt b/pmd-core/src/test/resources/net/sourceforge/pmd/cli/filelist3.txt new file mode 100644 index 00000000000..e34bdbb5439 --- /dev/null +++ b/pmd-core/src/test/resources/net/sourceforge/pmd/cli/filelist3.txt @@ -0,0 +1 @@ +src/test/resources/net/sourceforge/pmd/cli/src/somefile1.dummy,src/test/resources/net/sourceforge/pmd/cli/src/somefile2.dummy,src/test/resources/net/sourceforge/pmd/cli/src/somefile3.dummy,src/test/resources/net/sourceforge/pmd/cli/src/somefile4.dummy \ No newline at end of file diff --git a/pmd-core/src/test/resources/net/sourceforge/pmd/cli/ignorelist.txt b/pmd-core/src/test/resources/net/sourceforge/pmd/cli/ignorelist.txt new file mode 100644 index 00000000000..e606537e12e --- /dev/null +++ b/pmd-core/src/test/resources/net/sourceforge/pmd/cli/ignorelist.txt @@ -0,0 +1 @@ +src/test/resources/net/sourceforge/pmd/cli/src/somefile3.dummy,src/test/resources/net/sourceforge/pmd/cli/src/somefile1.dummy \ No newline at end of file diff --git a/pmd-core/src/test/resources/net/sourceforge/pmd/cli/src/somefile1.dummy b/pmd-core/src/test/resources/net/sourceforge/pmd/cli/src/somefile1.dummy new file mode 100644 index 00000000000..901f2e4bd02 --- /dev/null +++ b/pmd-core/src/test/resources/net/sourceforge/pmd/cli/src/somefile1.dummy @@ -0,0 +1 @@ +Some file for testing diff --git a/pmd-core/src/test/resources/net/sourceforge/pmd/cli/src/somefile2.dummy b/pmd-core/src/test/resources/net/sourceforge/pmd/cli/src/somefile2.dummy new file mode 100644 index 00000000000..901f2e4bd02 --- /dev/null +++ b/pmd-core/src/test/resources/net/sourceforge/pmd/cli/src/somefile2.dummy @@ -0,0 +1 @@ +Some file for testing diff --git a/pmd-core/src/test/resources/net/sourceforge/pmd/cli/src/somefile3.dummy b/pmd-core/src/test/resources/net/sourceforge/pmd/cli/src/somefile3.dummy new file mode 100644 index 00000000000..901f2e4bd02 --- /dev/null +++ b/pmd-core/src/test/resources/net/sourceforge/pmd/cli/src/somefile3.dummy @@ -0,0 +1 @@ +Some file for testing diff --git a/pmd-core/src/test/resources/net/sourceforge/pmd/cli/src/somefile4.dummy b/pmd-core/src/test/resources/net/sourceforge/pmd/cli/src/somefile4.dummy new file mode 100644 index 00000000000..901f2e4bd02 --- /dev/null +++ b/pmd-core/src/test/resources/net/sourceforge/pmd/cli/src/somefile4.dummy @@ -0,0 +1 @@ +Some file for testing diff --git a/pmd-core/src/test/resources/net/sourceforge/pmd/external-reference-ruleset.xml b/pmd-core/src/test/resources/net/sourceforge/pmd/external-reference-ruleset.xml index 2ee424a3172..3f6b18bb105 100644 --- a/pmd-core/src/test/resources/net/sourceforge/pmd/external-reference-ruleset.xml +++ b/pmd-core/src/test/resources/net/sourceforge/pmd/external-reference-ruleset.xml @@ -1,6 +1,6 @@ + xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd"> Ruleset used by test net.sourceforge.pmd.RuleSetFactoryTest 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 00000000000..290e7104d94 --- /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

+
+ MicroDoc for sponsoring PMD development. + MicroDoc is a software business serving an international customer base. Since 1991 MicroDoc + has grown into a technology oriented software engineering and professional services company. + Our focus on complex software technology and software infrastructure made us a well + respected partner for large corporations and even for other software businesses. +
diff --git a/docs/pages/pmd/userdocs/faq.md b/docs/pages/pmd/projectdocs/faq.md similarity index 97% rename from docs/pages/pmd/userdocs/faq.md rename to docs/pages/pmd/projectdocs/faq.md index 3290de321d9..030e0b92f22 100644 --- a/docs/pages/pmd/userdocs/faq.md +++ b/docs/pages/pmd/projectdocs/faq.md @@ -1,7 +1,7 @@ --- title: FAQ sidebar: pmd_sidebar -permalink: pmd_userdocs_faq.html +permalink: pmd_projectdocs_faq.html folder: pmd/userdocs --- diff --git a/docs/pages/pmd/userdocs/meaning.md b/docs/pages/pmd/projectdocs/trivia/meaning.md similarity index 93% rename from docs/pages/pmd/userdocs/meaning.md rename to docs/pages/pmd/projectdocs/trivia/meaning.md index b2bd1bc71f3..1c033dab4f1 100644 --- a/docs/pages/pmd/userdocs/meaning.md +++ b/docs/pages/pmd/projectdocs/trivia/meaning.md @@ -1,6 +1,6 @@ --- title: What does 'PMD' mean? -permalink: pmd_userdocs_meaning.html +permalink: pmd_projectdocs_trivia_meaning.html author: David Dixon-Peugh --- diff --git a/docs/pages/pmd/userdocs/news.md b/docs/pages/pmd/projectdocs/trivia/news.md similarity index 99% rename from docs/pages/pmd/userdocs/news.md rename to docs/pages/pmd/projectdocs/trivia/news.md index 937c1bbcf4b..42eab2a3461 100644 --- a/docs/pages/pmd/userdocs/news.md +++ b/docs/pages/pmd/projectdocs/trivia/news.md @@ -1,6 +1,6 @@ --- title: PMD in the press -permalink: pmd_userdocs_news.html +permalink: pmd_projectdocs_trivia_news.html author: Tom Copeland --- diff --git a/docs/pages/pmd/userdocs/products.md b/docs/pages/pmd/projectdocs/trivia/products.md similarity index 98% rename from docs/pages/pmd/userdocs/products.md rename to docs/pages/pmd/projectdocs/trivia/products.md index ffba09cb9f6..54175793a47 100644 --- a/docs/pages/pmd/userdocs/products.md +++ b/docs/pages/pmd/projectdocs/trivia/products.md @@ -1,6 +1,6 @@ --- title: Products/books related to PMD -permalink: pmd_userdocs_products.html +permalink: pmd_projectdocs_trivia_products.html author: Tom Copeland --- diff --git a/docs/pages/pmd/userdocs/similarprojects.md b/docs/pages/pmd/projectdocs/trivia/similarprojects.md similarity index 95% rename from docs/pages/pmd/userdocs/similarprojects.md rename to docs/pages/pmd/projectdocs/trivia/similarprojects.md index 60f574aaf35..e076052416f 100644 --- a/docs/pages/pmd/userdocs/similarprojects.md +++ b/docs/pages/pmd/projectdocs/trivia/similarprojects.md @@ -1,6 +1,6 @@ --- title: Similar projects -permalink: pmd_userdocs_similarprojects.html +permalink: pmd_projectdocs_trivia_similarprojects.html author: Tom Copeland , David Dixon-Peugh --- @@ -16,6 +16,7 @@ author: Tom Copeland , David Dixon-Peugh Uses Java Modeling Language annotations. * FindBugs - works on bytecode, uses BCEL. Source code uses templates, nifty stuff! +* SpotBugs - SpotBugs is the spiritual successor of FindBugs, carrying on from the point where it left off with support of its community. * Hammurapi - Uses ANTLR, excellent documentation, lots of rules * Jamit - bytecode analyzer, nice graphs diff --git a/docs/pages/pmd/rules/apex.md b/docs/pages/pmd/rules/apex.md index 6c4a2ae36e1..682964277be 100644 --- a/docs/pages/pmd/rules/apex.md +++ b/docs/pages/pmd/rules/apex.md @@ -1,43 +1,74 @@ --- title: Apex Rules +tags: [rule_references, apex] +summary: Index of all built-in rules available for Apex +language_name: Apex permalink: pmd_rules_apex.html folder: pmd/rules --- -List of rulesets and rules contained in each ruleset. - -* [ApexUnit](pmd_rules_apex_apexunit.html): These rules deal with different problems that can occur with Apex unit tests. -* [Braces](pmd_rules_apex_braces.html): The Braces ruleset contains rules regarding the use and placement of braces. -* [Complexity](pmd_rules_apex_complexity.html): The Complexity ruleset contains rules that find problems related to code size or complexity. -* [Performance](pmd_rules_apex_performance.html): The Performance ruleset contains a collection of good practices which should be followed. -* [Security](pmd_rules_apex_security.html): These rules deal with different security problems that can occur within Apex. -* [Style](pmd_rules_apex_style.html): The Style Ruleset contains rules regarding preferred usage of names and identifiers. - -## ApexUnit -* [ApexUnitTestClassShouldHaveAsserts](pmd_rules_apex_apexunit.html#apexunittestclassshouldhaveasserts): Apex unit tests should include at least one assertion. This makes the tests more robust, and usi... -* [ApexUnitTestShouldNotUseSeeAllDataTrue](pmd_rules_apex_apexunit.html#apexunittestshouldnotuseseealldatatrue): Apex unit tests should not use @isTest(seeAllData=true) because it opens up the existing database... - -## Braces -* [ForLoopsMustUseBraces](pmd_rules_apex_braces.html#forloopsmustusebraces): Avoid using 'for' statements without using surrounding braces. If the code formatting orindentati... -* [IfElseStmtsMustUseBraces](pmd_rules_apex_braces.html#ifelsestmtsmustusebraces): Avoid using if..else statements without using surrounding braces. If the code formattingor indent... -* [IfStmtsMustUseBraces](pmd_rules_apex_braces.html#ifstmtsmustusebraces): Avoid using if statements without using braces to surround the code block. If the codeformatting ... -* [WhileLoopsMustUseBraces](pmd_rules_apex_braces.html#whileloopsmustusebraces): Avoid using 'while' statements without using braces to surround the code block. If the codeformat... - -## Complexity -* [AvoidDeeplyNestedIfStmts](pmd_rules_apex_complexity.html#avoiddeeplynestedifstmts): Avoid creating deeply nested if-then statements since they are harder to read and error-prone to ... -* [ExcessiveClassLength](pmd_rules_apex_complexity.html#excessiveclasslength): Excessive class file lengths are usually indications that the class may be burdened with excessiv... -* [ExcessiveParameterList](pmd_rules_apex_complexity.html#excessiveparameterlist): Methods with numerous parameters are a challenge to maintain, especially if most of them share th... -* [ExcessivePublicCount](pmd_rules_apex_complexity.html#excessivepubliccount): Classes with large numbers of public methods and attributes require disproportionate testing effo... -* [NcssConstructorCount](pmd_rules_apex_complexity.html#ncssconstructorcount): This rule uses the NCSS (Non-Commenting Source Statements) algorithm to determine the number of l... -* [NcssMethodCount](pmd_rules_apex_complexity.html#ncssmethodcount): This rule uses the NCSS (Non-Commenting Source Statements) algorithm to determine the number of l... -* [NcssTypeCount](pmd_rules_apex_complexity.html#ncsstypecount): This rule uses the NCSS (Non-Commenting Source Statements) algorithm to determine the number of l... -* [StdCyclomaticComplexity](pmd_rules_apex_complexity.html#stdcyclomaticcomplexity): Complexity directly affects maintenance costs is determined by the number of decision points in a... -* [TooManyFields](pmd_rules_apex_complexity.html#toomanyfields): Classes that have too many fields can become unwieldy and could be redesigned to have fewer field... +## Best Practices + +{% include callout.html content="Rules which enforce generally accepted best practices." %} + +* [ApexUnitTestClassShouldHaveAsserts](pmd_rules_apex_bestpractices.html#apexunittestclassshouldhaveasserts): Apex unit tests should include at least one assertion. This makes the tests more robust, and usi... +* [ApexUnitTestShouldNotUseSeeAllDataTrue](pmd_rules_apex_bestpractices.html#apexunittestshouldnotuseseealldatatrue): Apex unit tests should not use @isTest(seeAllData=true) because it opens up the existing database... +* [AvoidGlobalModifier](pmd_rules_apex_bestpractices.html#avoidglobalmodifier): Global classes should be avoided (especially in managed packages) as they can never be deleted or... +* [AvoidLogicInTrigger](pmd_rules_apex_bestpractices.html#avoidlogicintrigger): As triggers do not allow methods like regular classes they are less flexible and suited to apply ... + +## Code Style + +{% include callout.html content="Rules which enforce a specific coding style." %} + +* [ClassNamingConventions](pmd_rules_apex_codestyle.html#classnamingconventions): Class names should always begin with an upper case character. +* [ForLoopsMustUseBraces](pmd_rules_apex_codestyle.html#forloopsmustusebraces): Avoid using 'for' statements without using surrounding braces. If the code formatting orindentati... +* [IfElseStmtsMustUseBraces](pmd_rules_apex_codestyle.html#ifelsestmtsmustusebraces): Avoid using if..else statements without using surrounding braces. If the code formattingor indent... +* [IfStmtsMustUseBraces](pmd_rules_apex_codestyle.html#ifstmtsmustusebraces): Avoid using if statements without using braces to surround the code block. If the codeformatting ... +* [MethodNamingConventions](pmd_rules_apex_codestyle.html#methodnamingconventions): Method names should always begin with a lower case character, and should not contain underscores. +* [OneDeclarationPerLine](pmd_rules_apex_codestyle.html#onedeclarationperline): Apex allows the use of several variables declaration of the same type on one line. However, itcan... +* [VariableNamingConventions](pmd_rules_apex_codestyle.html#variablenamingconventions): A variable naming conventions rule - customize this to your liking. Currently, itchecks for fina... +* [WhileLoopsMustUseBraces](pmd_rules_apex_codestyle.html#whileloopsmustusebraces): Avoid using 'while' statements without using braces to surround the code block. If the codeformat... + +## Design + +{% include callout.html content="Rules that help you discover design issues." %} + +* [AvoidDeeplyNestedIfStmts](pmd_rules_apex_design.html#avoiddeeplynestedifstmts): Avoid creating deeply nested if-then statements since they are harder to read and error-prone to ... +* [CyclomaticComplexity](pmd_rules_apex_design.html#cyclomaticcomplexity): The complexity of methods directly affects maintenance costs and readability. Concentrating too m... +* [ExcessiveClassLength](pmd_rules_apex_design.html#excessiveclasslength): Excessive class file lengths are usually indications that the class may be burdened with excessiv... +* [ExcessiveParameterList](pmd_rules_apex_design.html#excessiveparameterlist): Methods with numerous parameters are a challenge to maintain, especially if most of them share th... +* [ExcessivePublicCount](pmd_rules_apex_design.html#excessivepubliccount): Classes with large numbers of public methods and attributes require disproportionate testing effo... +* [NcssConstructorCount](pmd_rules_apex_design.html#ncssconstructorcount): This rule uses the NCSS (Non-Commenting Source Statements) algorithm to determine the number of l... +* [NcssMethodCount](pmd_rules_apex_design.html#ncssmethodcount): This rule uses the NCSS (Non-Commenting Source Statements) algorithm to determine the number of l... +* [NcssTypeCount](pmd_rules_apex_design.html#ncsstypecount): This rule uses the NCSS (Non-Commenting Source Statements) algorithm to determine the number of l... +* [StdCyclomaticComplexity](pmd_rules_apex_design.html#stdcyclomaticcomplexity): Complexity directly affects maintenance costs is determined by the number of decision points in a... +* [TooManyFields](pmd_rules_apex_design.html#toomanyfields): Classes that have too many fields can become unwieldy and could be redesigned to have fewer field... + +## Error Prone + +{% include callout.html content="Rules to detect constructs that are either broken, extremely confusing or prone to runtime errors." %} + +* [AvoidDirectAccessTriggerMap](pmd_rules_apex_errorprone.html#avoiddirectaccesstriggermap): Avoid directly accessing Trigger.old and Trigger.new as it can lead to a bug. Triggers should be ... +* [AvoidHardcodingId](pmd_rules_apex_errorprone.html#avoidhardcodingid): When deploying Apex code between sandbox and production environments, or installing Force.com App... +* [AvoidNonExistentAnnotations](pmd_rules_apex_errorprone.html#avoidnonexistentannotations): Apex supported non existent annotations for legacy reasons. In the future, use of such... +* [EmptyCatchBlock](pmd_rules_apex_errorprone.html#emptycatchblock): Empty Catch Block finds instances where an exception is caught, but nothing is done. In most cir... +* [EmptyIfStmt](pmd_rules_apex_errorprone.html#emptyifstmt): Empty If Statement finds instances where a condition is checked but nothing is done about it. +* [EmptyStatementBlock](pmd_rules_apex_errorprone.html#emptystatementblock): Empty block statements serve no purpose and should be removed. +* [EmptyTryOrFinallyBlock](pmd_rules_apex_errorprone.html#emptytryorfinallyblock): Avoid empty try or finally blocks - what's the point? +* [EmptyWhileStmt](pmd_rules_apex_errorprone.html#emptywhilestmt): Empty While Statement finds all instances where a while statement does nothing. If it is a timin... +* [MethodWithSameNameAsEnclosingClass](pmd_rules_apex_errorprone.html#methodwithsamenameasenclosingclass): Non-constructor methods should not have the same name as the enclosing class. ## Performance + +{% include callout.html content="Rules that flag suboptimal code." %} + * [AvoidDmlStatementsInLoops](pmd_rules_apex_performance.html#avoiddmlstatementsinloops): Avoid DML statements inside loops to avoid hitting the DML governor limit. Instead, try to batch ... * [AvoidSoqlInLoops](pmd_rules_apex_performance.html#avoidsoqlinloops): New objects created within loops should be checked to see if they can created outside them and re... +* [AvoidSoslInLoops](pmd_rules_apex_performance.html#avoidsoslinloops): Sosl calls within loops can cause governor limit exceptions. ## Security + +{% include callout.html content="Rules that flag potential security flaws." %} + * [ApexBadCrypto](pmd_rules_apex_security.html#apexbadcrypto): The rule makes sure you are using randomly generated IVs and keys for 'Crypto' calls.Hard-wiring ... * [ApexCRUDViolation](pmd_rules_apex_security.html#apexcrudviolation): The rule validates you are checking for access permissions before a SOQL/SOSL/DML operation.Since... * [ApexCSRF](pmd_rules_apex_security.html#apexcsrf): Check to avoid making DML operations in Apex class constructor/init method. This preventsmodifica... @@ -50,11 +81,78 @@ List of rulesets and rules contained in each ruleset. * [ApexXSSFromEscapeFalse](pmd_rules_apex_security.html#apexxssfromescapefalse): Reports on calls to 'addError' with disabled escaping. The message passed to 'addError'will be di... * [ApexXSSFromURLParam](pmd_rules_apex_security.html#apexxssfromurlparam): Makes sure that all values obtained from URL parameters are properly escaped / sanitizedto avoid ... -## Style -* [AvoidGlobalModifier](pmd_rules_apex_style.html#avoidglobalmodifier): Global classes should be avoided (especially in managed packages) as they can never be deleted or... -* [AvoidLogicInTrigger](pmd_rules_apex_style.html#avoidlogicintrigger): As triggers do not allow methods like regular classes they are less flexible and suited to apply ... -* [ClassNamingConventions](pmd_rules_apex_style.html#classnamingconventions): Class names should always begin with an upper case character. -* [MethodNamingConventions](pmd_rules_apex_style.html#methodnamingconventions): Method names should always begin with a lower case character, and should not contain underscores. -* [MethodWithSameNameAsEnclosingClass](pmd_rules_apex_style.html#methodwithsamenameasenclosingclass): Non-constructor methods should not have the same name as the enclosing class. -* [VariableNamingConventions](pmd_rules_apex_style.html#variablenamingconventions): A variable naming conventions rule - customize this to your liking. Currently, itchecks for fina... +## Additional rulesets + +* ApexUnit (`rulesets/apex/apexunit.xml`): + + Deprecated This ruleset is for backwards compatibility. + + It contains the following rules: + + [ApexUnitTestClassShouldHaveAsserts](pmd_rules_apex_bestpractices.html#apexunittestclassshouldhaveasserts), [ApexUnitTestShouldNotUseSeeAllDataTrue](pmd_rules_apex_bestpractices.html#apexunittestshouldnotuseseealldatatrue) + +* Braces (`rulesets/apex/braces.xml`): + + Deprecated This ruleset is for backwards compatibility. + + It contains the following rules: + + [ForLoopsMustUseBraces](pmd_rules_apex_codestyle.html#forloopsmustusebraces), [IfElseStmtsMustUseBraces](pmd_rules_apex_codestyle.html#ifelsestmtsmustusebraces), [IfStmtsMustUseBraces](pmd_rules_apex_codestyle.html#ifstmtsmustusebraces), [WhileLoopsMustUseBraces](pmd_rules_apex_codestyle.html#whileloopsmustusebraces) + +* Complexity (`rulesets/apex/complexity.xml`): + + Deprecated This ruleset is for backwards compatibility. + + It contains the following rules: + + [AvoidDeeplyNestedIfStmts](pmd_rules_apex_design.html#avoiddeeplynestedifstmts), [ExcessiveClassLength](pmd_rules_apex_design.html#excessiveclasslength), [ExcessiveParameterList](pmd_rules_apex_design.html#excessiveparameterlist), [ExcessivePublicCount](pmd_rules_apex_design.html#excessivepubliccount), [NcssConstructorCount](pmd_rules_apex_design.html#ncssconstructorcount), [NcssMethodCount](pmd_rules_apex_design.html#ncssmethodcount), [NcssTypeCount](pmd_rules_apex_design.html#ncsstypecount), [StdCyclomaticComplexity](pmd_rules_apex_design.html#stdcyclomaticcomplexity), [TooManyFields](pmd_rules_apex_design.html#toomanyfields) + +* Default ruleset used by the CodeClimate Engine for Salesforce.com Apex (`rulesets/apex/ruleset.xml`): + + Default ruleset used by the Code Climate Engine for Salesforce.com Apex + + It contains the following rules: + + [ApexBadCrypto](pmd_rules_apex_security.html#apexbadcrypto), [ApexCRUDViolation](pmd_rules_apex_security.html#apexcrudviolation), [ApexCSRF](pmd_rules_apex_security.html#apexcsrf), [ApexDangerousMethods](pmd_rules_apex_security.html#apexdangerousmethods), [ApexDoc](pmd_rules_apex_documentation.html#apexdoc), [ApexInsecureEndpoint](pmd_rules_apex_security.html#apexinsecureendpoint), [ApexOpenRedirect](pmd_rules_apex_security.html#apexopenredirect), [ApexSharingViolations](pmd_rules_apex_security.html#apexsharingviolations), [ApexSOQLInjection](pmd_rules_apex_security.html#apexsoqlinjection), [ApexSuggestUsingNamedCred](pmd_rules_apex_security.html#apexsuggestusingnamedcred), [ApexUnitTestClassShouldHaveAsserts](pmd_rules_apex_bestpractices.html#apexunittestclassshouldhaveasserts), [ApexUnitTestShouldNotUseSeeAllDataTrue](pmd_rules_apex_bestpractices.html#apexunittestshouldnotuseseealldatatrue), [ApexXSSFromEscapeFalse](pmd_rules_apex_security.html#apexxssfromescapefalse), [ApexXSSFromURLParam](pmd_rules_apex_security.html#apexxssfromurlparam), [AvoidDeeplyNestedIfStmts](pmd_rules_apex_design.html#avoiddeeplynestedifstmts), [AvoidDirectAccessTriggerMap](pmd_rules_apex_errorprone.html#avoiddirectaccesstriggermap), [AvoidDmlStatementsInLoops](pmd_rules_apex_performance.html#avoiddmlstatementsinloops), [AvoidGlobalModifier](pmd_rules_apex_bestpractices.html#avoidglobalmodifier), [AvoidHardcodingId](pmd_rules_apex_errorprone.html#avoidhardcodingid), [AvoidLogicInTrigger](pmd_rules_apex_bestpractices.html#avoidlogicintrigger), [AvoidNonExistentAnnotations](pmd_rules_apex_errorprone.html#avoidnonexistentannotations), [AvoidSoqlInLoops](pmd_rules_apex_performance.html#avoidsoqlinloops), [AvoidSoslInLoops](pmd_rules_apex_performance.html#avoidsoslinloops), [ClassNamingConventions](pmd_rules_apex_codestyle.html#classnamingconventions), [CyclomaticComplexity](pmd_rules_apex_design.html#cyclomaticcomplexity), [EmptyCatchBlock](pmd_rules_apex_errorprone.html#emptycatchblock), [EmptyIfStmt](pmd_rules_apex_errorprone.html#emptyifstmt), [EmptyStatementBlock](pmd_rules_apex_errorprone.html#emptystatementblock), [EmptyTryOrFinallyBlock](pmd_rules_apex_errorprone.html#emptytryorfinallyblock), [EmptyWhileStmt](pmd_rules_apex_errorprone.html#emptywhilestmt), [ExcessiveClassLength](pmd_rules_apex_design.html#excessiveclasslength), [ExcessiveParameterList](pmd_rules_apex_design.html#excessiveparameterlist), [ExcessivePublicCount](pmd_rules_apex_design.html#excessivepubliccount), [ForLoopsMustUseBraces](pmd_rules_apex_codestyle.html#forloopsmustusebraces), [IfElseStmtsMustUseBraces](pmd_rules_apex_codestyle.html#ifelsestmtsmustusebraces), [IfStmtsMustUseBraces](pmd_rules_apex_codestyle.html#ifstmtsmustusebraces), [MethodNamingConventions](pmd_rules_apex_codestyle.html#methodnamingconventions), [MethodWithSameNameAsEnclosingClass](pmd_rules_apex_errorprone.html#methodwithsamenameasenclosingclass), [NcssConstructorCount](pmd_rules_apex_design.html#ncssconstructorcount), [NcssMethodCount](pmd_rules_apex_design.html#ncssmethodcount), [NcssTypeCount](pmd_rules_apex_design.html#ncsstypecount), [OneDeclarationPerLine](pmd_rules_apex_codestyle.html#onedeclarationperline), [StdCyclomaticComplexity](pmd_rules_apex_design.html#stdcyclomaticcomplexity), [TooManyFields](pmd_rules_apex_design.html#toomanyfields), [VariableNamingConventions](pmd_rules_apex_codestyle.html#variablenamingconventions), [WhileLoopsMustUseBraces](pmd_rules_apex_codestyle.html#whileloopsmustusebraces) + +* Empty Code (`rulesets/apex/empty.xml`): + + Deprecated This ruleset is for backwards compatibility. + + It contains the following rules: + + [EmptyCatchBlock](pmd_rules_apex_errorprone.html#emptycatchblock), [EmptyIfStmt](pmd_rules_apex_errorprone.html#emptyifstmt), [EmptyStatementBlock](pmd_rules_apex_errorprone.html#emptystatementblock), [EmptyTryOrFinallyBlock](pmd_rules_apex_errorprone.html#emptytryorfinallyblock), [EmptyWhileStmt](pmd_rules_apex_errorprone.html#emptywhilestmt) + +* Metrics temporary ruleset (`rulesets/apex/metrics.xml`): + + Deprecated This ruleset is for backwards compatibility. + + It contains the following rules: + + [CyclomaticComplexity](pmd_rules_apex_design.html#cyclomaticcomplexity) + +* Performance (`rulesets/apex/performance.xml`): + + Deprecated This ruleset is for backwards compatibility. + + It contains the following rules: + + [AvoidDmlStatementsInLoops](pmd_rules_apex_performance.html#avoiddmlstatementsinloops), [AvoidSoqlInLoops](pmd_rules_apex_performance.html#avoidsoqlinloops), [AvoidSoslInLoops](pmd_rules_apex_performance.html#avoidsoslinloops) + +* Security (`rulesets/apex/security.xml`): + + Deprecated This ruleset is for backwards compatibility. + + It contains the following rules: + + [ApexBadCrypto](pmd_rules_apex_security.html#apexbadcrypto), [ApexCRUDViolation](pmd_rules_apex_security.html#apexcrudviolation), [ApexCSRF](pmd_rules_apex_security.html#apexcsrf), [ApexDangerousMethods](pmd_rules_apex_security.html#apexdangerousmethods), [ApexInsecureEndpoint](pmd_rules_apex_security.html#apexinsecureendpoint), [ApexOpenRedirect](pmd_rules_apex_security.html#apexopenredirect), [ApexSharingViolations](pmd_rules_apex_security.html#apexsharingviolations), [ApexSOQLInjection](pmd_rules_apex_security.html#apexsoqlinjection), [ApexSuggestUsingNamedCred](pmd_rules_apex_security.html#apexsuggestusingnamedcred), [ApexXSSFromEscapeFalse](pmd_rules_apex_security.html#apexxssfromescapefalse), [ApexXSSFromURLParam](pmd_rules_apex_security.html#apexxssfromurlparam) + +* Style (`rulesets/apex/style.xml`): + + Deprecated This ruleset is for backwards compatibility. + + It contains the following rules: + + [AvoidDirectAccessTriggerMap](pmd_rules_apex_errorprone.html#avoiddirectaccesstriggermap), [AvoidGlobalModifier](pmd_rules_apex_bestpractices.html#avoidglobalmodifier), [AvoidHardcodingId](pmd_rules_apex_errorprone.html#avoidhardcodingid), [AvoidLogicInTrigger](pmd_rules_apex_bestpractices.html#avoidlogicintrigger), [ClassNamingConventions](pmd_rules_apex_codestyle.html#classnamingconventions), [MethodNamingConventions](pmd_rules_apex_codestyle.html#methodnamingconventions), [MethodWithSameNameAsEnclosingClass](pmd_rules_apex_errorprone.html#methodwithsamenameasenclosingclass), [VariableNamingConventions](pmd_rules_apex_codestyle.html#variablenamingconventions) + diff --git a/docs/pages/pmd/rules/apex/apexunit.md b/docs/pages/pmd/rules/apex/apexunit.md deleted file mode 100644 index b6b26fda984..00000000000 --- a/docs/pages/pmd/rules/apex/apexunit.md +++ /dev/null @@ -1,84 +0,0 @@ ---- -title: ApexUnit -summary: These rules deal with different problems that can occur with Apex unit tests. -permalink: pmd_rules_apex_apexunit.html -folder: pmd/rules/apex -sidebaractiveurl: /pmd_rules_apex.html -editmepath: ../pmd-apex/src/main/resources/rulesets/apex/apexunit.xml -keywords: ApexUnit, ApexUnitTestClassShouldHaveAsserts, ApexUnitTestShouldNotUseSeeAllDataTrue ---- -## ApexUnitTestClassShouldHaveAsserts - -**Since:** PMD 5.5.1 - -**Priority:** Medium (3) - -Apex unit tests should include at least one assertion. This makes the tests more robust, and using assert - with messages provide the developer a clearer idea of what the test does. - -**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.apex.rule.apexunit.ApexUnitTestClassShouldHaveAssertsRule](https://github.com/pmd/pmd/blob/master/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/apexunit/ApexUnitTestClassShouldHaveAssertsRule.java) - -**Example(s):** - -``` java -@isTest -public class Foo { - public static testMethod void testSomething() { - Account a = null; - // This is better than having a NullPointerException - // System.assertNotEquals(a, null, 'account not found'); - a.toString(); - } -} -``` - -**This rule has the following properties:** - -|Name|Default Value|Description| -|----|-------------|-----------| -|cc_categories|[Style]|Code Climate Categories| -|cc_remediation_points_multiplier|1|Code Climate Remediation Points multiplier| -|cc_block_highlighting|false|Code Climate Block Highlighting| - -**Use this rule by referencing it:** -``` xml - -``` - -## ApexUnitTestShouldNotUseSeeAllDataTrue - -**Since:** PMD 5.5.1 - -**Priority:** Medium (3) - -Apex unit tests should not use @isTest(seeAllData=true) because it opens up the existing database data for unexpected modification by tests. - -**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.apex.rule.apexunit.ApexUnitTestShouldNotUseSeeAllDataTrueRule](https://github.com/pmd/pmd/blob/master/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/apexunit/ApexUnitTestShouldNotUseSeeAllDataTrueRule.java) - -**Example(s):** - -``` java -@isTest(seeAllData = true) -public class Foo { - public static testMethod void testSomething() { - Account a = null; - // This is better than having a NullPointerException - // System.assertNotEquals(a, null, 'account not found'); - a.toString(); - } -} -``` - -**This rule has the following properties:** - -|Name|Default Value|Description| -|----|-------------|-----------| -|cc_categories|[Style]|Code Climate Categories| -|cc_remediation_points_multiplier|1|Code Climate Remediation Points multiplier| -|cc_block_highlighting|false|Code Climate Block Highlighting| - -**Use this rule by referencing it:** -``` xml - -``` - diff --git a/docs/pages/pmd/rules/apex/bestpractices.md b/docs/pages/pmd/rules/apex/bestpractices.md new file mode 100644 index 00000000000..d59c846baf3 --- /dev/null +++ b/docs/pages/pmd/rules/apex/bestpractices.md @@ -0,0 +1,163 @@ +--- +title: Best Practices +summary: Rules which enforce generally accepted best practices. +permalink: pmd_rules_apex_bestpractices.html +folder: pmd/rules/apex +sidebaractiveurl: /pmd_rules_apex.html +editmepath: ../pmd-apex/src/main/resources/category/apex/bestpractices.xml +keywords: Best Practices, ApexUnitTestClassShouldHaveAsserts, ApexUnitTestShouldNotUseSeeAllDataTrue, AvoidGlobalModifier, AvoidLogicInTrigger +language: Apex +--- +## ApexUnitTestClassShouldHaveAsserts + +**Since:** PMD 5.5.1 + +**Priority:** Medium (3) + +Apex unit tests should include at least one assertion. This makes the tests more robust, and using assert +with messages provide the developer a clearer idea of what the test does. + +**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.apex.rule.bestpractices.ApexUnitTestClassShouldHaveAssertsRule](https://github.com/pmd/pmd/blob/master/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/bestpractices/ApexUnitTestClassShouldHaveAssertsRule.java) + +**Example(s):** + +``` java +@isTest +public class Foo { + public static testMethod void testSomething() { + Account a = null; + // This is better than having a NullPointerException + // System.assertNotEquals(a, null, 'account not found'); + a.toString(); + } +} +``` + +**This rule has the following properties:** + +|Name|Default Value|Description|Multivalued| +|----|-------------|-----------|-----------| +|cc_categories|Style|Code Climate Categories|yes. Delimiter is '\|'.| +|cc_remediation_points_multiplier|1|Code Climate Remediation Points multiplier|no| +|cc_block_highlighting|false|Code Climate Block Highlighting|no| + +**Use this rule by referencing it:** +``` xml + +``` + +## ApexUnitTestShouldNotUseSeeAllDataTrue + +**Since:** PMD 5.5.1 + +**Priority:** Medium (3) + +Apex unit tests should not use @isTest(seeAllData=true) because it opens up the existing database data for unexpected modification by tests. + +**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.apex.rule.bestpractices.ApexUnitTestShouldNotUseSeeAllDataTrueRule](https://github.com/pmd/pmd/blob/master/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/bestpractices/ApexUnitTestShouldNotUseSeeAllDataTrueRule.java) + +**Example(s):** + +``` java +@isTest(seeAllData = true) +public class Foo { + public static testMethod void testSomething() { + Account a = null; + // This is better than having a NullPointerException + // System.assertNotEquals(a, null, 'account not found'); + a.toString(); + } +} +``` + +**This rule has the following properties:** + +|Name|Default Value|Description|Multivalued| +|----|-------------|-----------|-----------| +|cc_categories|Style|Code Climate Categories|yes. Delimiter is '\|'.| +|cc_remediation_points_multiplier|1|Code Climate Remediation Points multiplier|no| +|cc_block_highlighting|false|Code Climate Block Highlighting|no| + +**Use this rule by referencing it:** +``` xml + +``` + +## AvoidGlobalModifier + +**Since:** PMD 5.5.0 + +**Priority:** Medium (3) + +Global classes should be avoided (especially in managed packages) as they can never be deleted or changed in signature. Always check twice if something needs to be global. +Many interfaces (e.g. Batch) required global modifiers in the past but don't require this anymore. Don't lock yourself in. + +**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.apex.rule.bestpractices.AvoidGlobalModifierRule](https://github.com/pmd/pmd/blob/master/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/bestpractices/AvoidGlobalModifierRule.java) + +**Example(s):** + +``` java +global class Unchangeable { + global UndeletableType unchangable(UndeletableType param) { + // ... + } +} +``` + +**This rule has the following properties:** + +|Name|Default Value|Description|Multivalued| +|----|-------------|-----------|-----------| +|cc_categories|Style|Code Climate Categories|yes. Delimiter is '\|'.| +|cc_remediation_points_multiplier|1|Code Climate Remediation Points multiplier|no| +|cc_block_highlighting|false|Code Climate Block Highlighting|no| + +**Use this rule by referencing it:** +``` xml + +``` + +## AvoidLogicInTrigger + +**Since:** PMD 5.5.0 + +**Priority:** Medium (3) + +As triggers do not allow methods like regular classes they are less flexible and suited to apply good encapsulation style. +Therefore delegate the triggers work to a regular class (often called Trigger handler class). + +See more here: https://developer.salesforce.com/page/Trigger_Frameworks_and_Apex_Trigger_Best_Practices + +**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.apex.rule.bestpractices.AvoidLogicInTriggerRule](https://github.com/pmd/pmd/blob/master/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/bestpractices/AvoidLogicInTriggerRule.java) + +**Example(s):** + +``` java +trigger Accounts on Account (before insert, before update, before delete, after insert, after update, after delete, after undelete) { + for(Account acc : Trigger.new) { + if(Trigger.isInsert) { + // ... + } + + // ... + + if(Trigger.isDelete) { + // ... + } + } +} +``` + +**This rule has the following properties:** + +|Name|Default Value|Description|Multivalued| +|----|-------------|-----------|-----------| +|cc_categories|Style|Code Climate Categories|yes. Delimiter is '\|'.| +|cc_remediation_points_multiplier|1|Code Climate Remediation Points multiplier|no| +|cc_block_highlighting|false|Code Climate Block Highlighting|no| + +**Use this rule by referencing it:** +``` xml + +``` + diff --git a/docs/pages/pmd/rules/apex/braces.md b/docs/pages/pmd/rules/apex/braces.md deleted file mode 100644 index 5f4b7e565aa..00000000000 --- a/docs/pages/pmd/rules/apex/braces.md +++ /dev/null @@ -1,167 +0,0 @@ ---- -title: Braces -summary: The Braces ruleset contains rules regarding the use and placement of braces. -permalink: pmd_rules_apex_braces.html -folder: pmd/rules/apex -sidebaractiveurl: /pmd_rules_apex.html -editmepath: ../pmd-apex/src/main/resources/rulesets/apex/braces.xml -keywords: Braces, IfStmtsMustUseBraces, WhileLoopsMustUseBraces, IfElseStmtsMustUseBraces, ForLoopsMustUseBraces ---- -## ForLoopsMustUseBraces - -**Since:** PMD 5.6.0 - -**Priority:** Medium (3) - -Avoid using 'for' statements without using surrounding braces. If the code formatting or -indentation is lost then it becomes difficult to separate the code being controlled -from the rest. - -``` -//ForLoopStatement/BlockStatement[@CurlyBrace='false'] -| -//ForEachStatement/BlockStatement[@CurlyBrace='false'] -``` - -**Example(s):** - -``` java -for (int i = 0; i < 42; i++) // not recommended - foo(); - -for (int i = 0; i < 42; i++) { // preferred approach - foo(); -} -``` - -**This rule has the following properties:** - -|Name|Default Value|Description| -|----|-------------|-----------| -|cc_categories|[Style]|Code Climate Categories| -|cc_remediation_points_multiplier|1|Code Climate Remediation Points multiplier| -|cc_block_highlighting|false|Code Climate Block Highlighting| - -**Use this rule by referencing it:** -``` xml - -``` - -## IfElseStmtsMustUseBraces - -**Since:** PMD 5.6.0 - -**Priority:** Medium (3) - -Avoid using if..else statements without using surrounding braces. If the code formatting -or indentation is lost then it becomes difficult to separate the code being controlled -from the rest. - -``` -//IfBlockStatement/BlockStatement[@CurlyBrace='false'][count(child::*) > 0] -| -//IfElseBlockStatement/BlockStatement[@CurlyBrace='false'][count(child::*) > 0] -``` - -**Example(s):** - -``` java -// this is OK -if (foo) x++; - -// but this is not -if (foo) - x = x+1; -else - x = x-1; -``` - -**This rule has the following properties:** - -|Name|Default Value|Description| -|----|-------------|-----------| -|cc_categories|[Style]|Code Climate Categories| -|cc_remediation_points_multiplier|1|Code Climate Remediation Points multiplier| -|cc_block_highlighting|false|Code Climate Block Highlighting| - -**Use this rule by referencing it:** -``` xml - -``` - -## IfStmtsMustUseBraces - -**Since:** PMD 5.6.0 - -**Priority:** Medium (3) - -Avoid using if statements without using braces to surround the code block. If the code -formatting or indentation is lost then it becomes difficult to separate the code being -controlled from the rest. - -``` -//IfBlockStatement/BlockStatement[@CurlyBrace='false'] -``` - -**Example(s):** - -``` java -if (foo) // not recommended - x++; - -if (foo) { // preferred approach - x++; -} -``` - -**This rule has the following properties:** - -|Name|Default Value|Description| -|----|-------------|-----------| -|cc_categories|[Style]|Code Climate Categories| -|cc_remediation_points_multiplier|1|Code Climate Remediation Points multiplier| -|cc_block_highlighting|false|Code Climate Block Highlighting| - -**Use this rule by referencing it:** -``` xml - -``` - -## WhileLoopsMustUseBraces - -**Since:** PMD 5.6.0 - -**Priority:** Medium (3) - -Avoid using 'while' statements without using braces to surround the code block. If the code -formatting or indentation is lost then it becomes difficult to separate the code being -controlled from the rest. - -``` -//WhileLoopStatement/BlockStatement[@CurlyBrace='false'] -``` - -**Example(s):** - -``` java -while (true) // not recommended - x++; - -while (true) { // preferred approach - x++; -} -``` - -**This rule has the following properties:** - -|Name|Default Value|Description| -|----|-------------|-----------| -|cc_categories|[Style]|Code Climate Categories| -|cc_remediation_points_multiplier|1|Code Climate Remediation Points multiplier| -|cc_block_highlighting|false|Code Climate Block Highlighting| - -**Use this rule by referencing it:** -``` xml - -``` - diff --git a/docs/pages/pmd/rules/apex/codestyle.md b/docs/pages/pmd/rules/apex/codestyle.md new file mode 100644 index 00000000000..0e59a82e233 --- /dev/null +++ b/docs/pages/pmd/rules/apex/codestyle.md @@ -0,0 +1,325 @@ +--- +title: Code Style +summary: Rules which enforce a specific coding style. +permalink: pmd_rules_apex_codestyle.html +folder: pmd/rules/apex +sidebaractiveurl: /pmd_rules_apex.html +editmepath: ../pmd-apex/src/main/resources/category/apex/codestyle.xml +keywords: Code Style, ClassNamingConventions, IfElseStmtsMustUseBraces, IfStmtsMustUseBraces, ForLoopsMustUseBraces, MethodNamingConventions, OneDeclarationPerLine, VariableNamingConventions, WhileLoopsMustUseBraces +language: Apex +--- +## ClassNamingConventions + +**Since:** PMD 5.5.0 + +**Priority:** High (1) + +Class names should always begin with an upper case character. + +**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.apex.rule.codestyle.ClassNamingConventionsRule](https://github.com/pmd/pmd/blob/master/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/codestyle/ClassNamingConventionsRule.java) + +**Example(s):** + +``` java +public class Foo {} +``` + +**This rule has the following properties:** + +|Name|Default Value|Description|Multivalued| +|----|-------------|-----------|-----------| +|cc_categories|Style|Code Climate Categories|yes. Delimiter is '\|'.| +|cc_remediation_points_multiplier|1|Code Climate Remediation Points multiplier|no| +|cc_block_highlighting|false|Code Climate Block Highlighting|no| + +**Use this rule by referencing it:** +``` xml + +``` + +## ForLoopsMustUseBraces + +**Since:** PMD 5.6.0 + +**Priority:** Medium (3) + +Avoid using 'for' statements without using surrounding braces. If the code formatting or +indentation is lost then it becomes difficult to separate the code being controlled +from the rest. + +**This rule is defined by the following XPath expression:** +``` xpath +//ForLoopStatement/BlockStatement[@CurlyBrace='false'] +| +//ForEachStatement/BlockStatement[@CurlyBrace='false'] +``` + +**Example(s):** + +``` java +for (int i = 0; i < 42; i++) // not recommended + foo(); + +for (int i = 0; i < 42; i++) { // preferred approach + foo(); +} +``` + +**This rule has the following properties:** + +|Name|Default Value|Description|Multivalued| +|----|-------------|-----------|-----------| +|cc_categories|Style|Code Climate Categories|yes. Delimiter is '\|'.| +|cc_remediation_points_multiplier|1|Code Climate Remediation Points multiplier|no| +|cc_block_highlighting|false|Code Climate Block Highlighting|no| + +**Use this rule by referencing it:** +``` xml + +``` + +## IfElseStmtsMustUseBraces + +**Since:** PMD 5.6.0 + +**Priority:** Medium (3) + +Avoid using if..else statements without using surrounding braces. If the code formatting +or indentation is lost then it becomes difficult to separate the code being controlled +from the rest. + +**This rule is defined by the following XPath expression:** +``` xpath +//IfBlockStatement/BlockStatement[@CurlyBrace='false'][count(child::*) > 0] +| +//IfElseBlockStatement/BlockStatement[@CurlyBrace='false'][count(child::*) > 0] +``` + +**Example(s):** + +``` java +// this is OK +if (foo) x++; + +// but this is not +if (foo) + x = x+1; +else + x = x-1; +``` + +**This rule has the following properties:** + +|Name|Default Value|Description|Multivalued| +|----|-------------|-----------|-----------| +|cc_categories|Style|Code Climate Categories|yes. Delimiter is '\|'.| +|cc_remediation_points_multiplier|1|Code Climate Remediation Points multiplier|no| +|cc_block_highlighting|false|Code Climate Block Highlighting|no| + +**Use this rule by referencing it:** +``` xml + +``` + +## IfStmtsMustUseBraces + +**Since:** PMD 5.6.0 + +**Priority:** Medium (3) + +Avoid using if statements without using braces to surround the code block. If the code +formatting or indentation is lost then it becomes difficult to separate the code being +controlled from the rest. + +**This rule is defined by the following XPath expression:** +``` xpath +//IfBlockStatement/BlockStatement[@CurlyBrace='false'] +``` + +**Example(s):** + +``` java +if (foo) // not recommended + x++; + +if (foo) { // preferred approach + x++; +} +``` + +**This rule has the following properties:** + +|Name|Default Value|Description|Multivalued| +|----|-------------|-----------|-----------| +|cc_categories|Style|Code Climate Categories|yes. Delimiter is '\|'.| +|cc_remediation_points_multiplier|1|Code Climate Remediation Points multiplier|no| +|cc_block_highlighting|false|Code Climate Block Highlighting|no| + +**Use this rule by referencing it:** +``` xml + +``` + +## MethodNamingConventions + +**Since:** PMD 5.5.0 + +**Priority:** High (1) + +Method names should always begin with a lower case character, and should not contain underscores. + +**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.apex.rule.codestyle.MethodNamingConventionsRule](https://github.com/pmd/pmd/blob/master/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/codestyle/MethodNamingConventionsRule.java) + +**Example(s):** + +``` java +public class Foo { + public void fooStuff() { + } +} +``` + +**This rule has the following properties:** + +|Name|Default Value|Description|Multivalued| +|----|-------------|-----------|-----------| +|cc_categories|Style|Code Climate Categories|yes. Delimiter is '\|'.| +|cc_remediation_points_multiplier|1|Code Climate Remediation Points multiplier|no| +|cc_block_highlighting|false|Code Climate Block Highlighting|no| + +**Use this rule by referencing it:** +``` xml + +``` + +## OneDeclarationPerLine + +**Since:** PMD 6.7.0 + +**Priority:** High (1) + +Apex allows the use of several variables declaration of the same type on one line. However, it +can lead to quite messy code. This rule looks for several declarations on the same line. + +**This rule is defined by the following XPath expression:** +``` xpath +//VariableDeclarationStatements + [count(VariableDeclaration) > 1] + [$strictMode or count(distinct-values(VariableDeclaration/@BeginLine)) != count(VariableDeclaration)] +| +//FieldDeclarationStatements + [count(FieldDeclaration) > 1] + [$strictMode or count(distinct-values(FieldDeclaration/VariableExpression/@BeginLine)) != count(FieldDeclaration/VariableExpression)] +``` + +**Example(s):** + +``` java +Integer a, b; // not recommended + +Integer a, + b; // ok by default, can be flagged setting the strictMode property + +Integer a; // preferred approach +Integer b; +``` + +**This rule has the following properties:** + +|Name|Default Value|Description|Multivalued| +|----|-------------|-----------|-----------| +|cc_categories|Style|Code Climate Categories|yes. Delimiter is '\|'.| +|cc_remediation_points_multiplier|1|Code Climate Remediation Points multiplier|no| +|cc_block_highlighting|false|Code Climate Block Highlighting|no| +|strictMode|false|If true, mark combined declaration even if the declarations are on separate lines.|no| + +**Use this rule by referencing it:** +``` xml + +``` + +## VariableNamingConventions + +**Since:** PMD 5.5.0 + +**Priority:** High (1) + +A variable naming conventions rule - customize this to your liking. Currently, it +checks for final variables that should be fully capitalized and non-final variables +that should not include underscores. + +**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.apex.rule.codestyle.VariableNamingConventionsRule](https://github.com/pmd/pmd/blob/master/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/codestyle/VariableNamingConventionsRule.java) + +**Example(s):** + +``` java +public class Foo { + public static final Integer MY_NUM = 0; + public String myTest = ''; + DataModule dmTest = new DataModule(); +} +``` + +**This rule has the following properties:** + +|Name|Default Value|Description|Multivalued| +|----|-------------|-----------|-----------| +|parameterSuffix||Method parameter variable suffixes|yes. Delimiter is ','.| +|parameterPrefix||Method parameter variable prefixes|yes. Delimiter is ','.| +|localSuffix||Local variable suffixes|yes. Delimiter is ','.| +|localPrefix||Local variable prefixes|yes. Delimiter is ','.| +|memberSuffix||Member variable suffixes|yes. Delimiter is ','.| +|memberPrefix||Member variable prefixes|yes. Delimiter is ','.| +|staticSuffix||Static variable suffixes|yes. Delimiter is ','.| +|staticPrefix||Static variable prefixes|yes. Delimiter is ','.| +|checkParameters|true|Check constructor and method parameter variables|no| +|checkLocals|true|Check local variables|no| +|cc_categories|Style|Code Climate Categories|yes. Delimiter is '\|'.| +|cc_remediation_points_multiplier|1|Code Climate Remediation Points multiplier|no| +|cc_block_highlighting|false|Code Climate Block Highlighting|no| +|checkMembers|true|Check member variables|no| + +**Use this rule by referencing it:** +``` xml + +``` + +## WhileLoopsMustUseBraces + +**Since:** PMD 5.6.0 + +**Priority:** Medium (3) + +Avoid using 'while' statements without using braces to surround the code block. If the code +formatting or indentation is lost then it becomes difficult to separate the code being +controlled from the rest. + +**This rule is defined by the following XPath expression:** +``` xpath +//WhileLoopStatement/BlockStatement[@CurlyBrace='false'] +``` + +**Example(s):** + +``` java +while (true) // not recommended + x++; + +while (true) { // preferred approach + x++; +} +``` + +**This rule has the following properties:** + +|Name|Default Value|Description|Multivalued| +|----|-------------|-----------|-----------| +|cc_categories|Style|Code Climate Categories|yes. Delimiter is '\|'.| +|cc_remediation_points_multiplier|1|Code Climate Remediation Points multiplier|no| +|cc_block_highlighting|false|Code Climate Block Highlighting|no| + +**Use this rule by referencing it:** +``` xml + +``` + diff --git a/docs/pages/pmd/rules/apex/complexity.md b/docs/pages/pmd/rules/apex/complexity.md deleted file mode 100644 index b3832f5631b..00000000000 --- a/docs/pages/pmd/rules/apex/complexity.md +++ /dev/null @@ -1,428 +0,0 @@ ---- -title: Complexity -summary: The Complexity ruleset contains rules that find problems related to code size or complexity. -permalink: pmd_rules_apex_complexity.html -folder: pmd/rules/apex -sidebaractiveurl: /pmd_rules_apex.html -editmepath: ../pmd-apex/src/main/resources/rulesets/apex/complexity.xml -keywords: Complexity, AvoidDeeplyNestedIfStmts, ExcessiveParameterList, ExcessiveClassLength, NcssMethodCount, NcssTypeCount, NcssConstructorCount, StdCyclomaticComplexity, TooManyFields, ExcessivePublicCount ---- -## AvoidDeeplyNestedIfStmts - -**Since:** PMD 5.5.0 - -**Priority:** Medium (3) - -Avoid creating deeply nested if-then statements since they are harder to read and error-prone to maintain. - -**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.apex.rule.complexity.AvoidDeeplyNestedIfStmtsRule](https://github.com/pmd/pmd/blob/master/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/complexity/AvoidDeeplyNestedIfStmtsRule.java) - -**Example(s):** - -``` java -public class Foo { - public void bar(Integer x, Integer y, Integer z) { - if (x>y) { - if (y>z) { - if (z==x) { - // !! too deep - } - } - } - } -} -``` - -**This rule has the following properties:** - -|Name|Default Value|Description| -|----|-------------|-----------| -|cc_categories|[Style]|Code Climate Categories| -|cc_remediation_points_multiplier|1|Code Climate Remediation Points multiplier| -|cc_block_highlighting|false|Code Climate Block Highlighting| -|problemDepth|3|The if statement depth reporting threshold| - -**Use this rule by referencing it:** -``` xml - -``` - -## ExcessiveClassLength - -**Since:** PMD 5.5.0 - -**Priority:** Medium (3) - -Excessive class file lengths are usually indications that the class may be burdened with excessive -responsibilities that could be provided by external classes or functions. In breaking these methods -apart the code becomes more managable and ripe for reuse. - -**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.apex.rule.complexity.ExcessiveClassLengthRule](https://github.com/pmd/pmd/blob/master/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/complexity/ExcessiveClassLengthRule.java) - -**Example(s):** - -``` java -public class Foo { - public void bar1() { - // 1000 lines of code - } - public void bar2() { - // 1000 lines of code - } - public void bar3() { - // 1000 lines of code - } - public void barN() { - // 1000 lines of code - } -} -``` - -**This rule has the following properties:** - -|Name|Default Value|Description| -|----|-------------|-----------| -|topscore||Top score value| -|minimum||Minimum reporting threshold| -|cc_categories|[Style]|Code Climate Categories| -|cc_remediation_points_multiplier|1|Code Climate Remediation Points multiplier| -|cc_block_highlighting|false|Code Climate Block Highlighting| -|sigma||Sigma value| - -**Use this rule by referencing it:** -``` xml - -``` - -## ExcessiveParameterList - -**Since:** PMD 5.5.0 - -**Priority:** Medium (3) - -Methods with numerous parameters are a challenge to maintain, especially if most of them share the -same datatype. These situations usually denote the need for new objects to wrap the numerous parameters. - -**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.apex.rule.complexity.ExcessiveParameterListRule](https://github.com/pmd/pmd/blob/master/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/complexity/ExcessiveParameterListRule.java) - -**Example(s):** - -``` java -// too many arguments liable to be mixed up -public void addPerson(int birthYear, int birthMonth, int birthDate, int height, int weight, int ssn) { - // ... -} -// preferred approach -public void addPerson(Date birthdate, BodyMeasurements measurements, int ssn) { - // ... -} -``` - -**This rule has the following properties:** - -|Name|Default Value|Description| -|----|-------------|-----------| -|topscore||Top score value| -|minimum||Minimum reporting threshold| -|cc_categories|[Style]|Code Climate Categories| -|cc_remediation_points_multiplier|1|Code Climate Remediation Points multiplier| -|cc_block_highlighting|false|Code Climate Block Highlighting| -|sigma||Sigma value| - -**Use this rule by referencing it:** -``` xml - -``` - -## ExcessivePublicCount - -**Since:** PMD 5.5.0 - -**Priority:** Medium (3) - -Classes with large numbers of public methods and attributes require disproportionate testing efforts -since combinational side effects grow rapidly and increase risk. Refactoring these classes into -smaller ones not only increases testability and reliability but also allows new variations to be -developed easily. - -**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.apex.rule.complexity.ExcessivePublicCountRule](https://github.com/pmd/pmd/blob/master/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/complexity/ExcessivePublicCountRule.java) - -**Example(s):** - -``` java -public class Foo { - public String value; - public Bar something; - public Variable var; - // [... more more public attributes ...] - - public void doWork() {} - public void doMoreWork() {} - public void doWorkAgain() {} - // [... more more public methods ...] -} -``` - -**This rule has the following properties:** - -|Name|Default Value|Description| -|----|-------------|-----------| -|topscore||Top score value| -|minimum||Minimum reporting threshold| -|cc_categories|[Style]|Code Climate Categories| -|cc_remediation_points_multiplier|1|Code Climate Remediation Points multiplier| -|cc_block_highlighting|false|Code Climate Block Highlighting| -|sigma||Sigma value| - -**Use this rule by referencing it:** -``` xml - -``` - -## NcssConstructorCount - -**Since:** PMD 5.5.0 - -**Priority:** Medium (3) - -This rule uses the NCSS (Non-Commenting Source Statements) algorithm to determine the number of lines -of code for a given constructor. NCSS ignores comments, and counts actual statements. Using this algorithm, -lines of code that are split are counted as one. - -**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.apex.rule.complexity.NcssConstructorCountRule](https://github.com/pmd/pmd/blob/master/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/complexity/NcssConstructorCountRule.java) - -**Example(s):** - -``` java -public class Foo extends Bar { - //this constructor only has 1 NCSS lines - public Foo() { - super(); - - - - - super.foo(); -} -} -``` - -**This rule has the following properties:** - -|Name|Default Value|Description| -|----|-------------|-----------| -|topscore||Top score value| -|minimum||Minimum reporting threshold| -|cc_categories|[Style]|Code Climate Categories| -|cc_remediation_points_multiplier|1|Code Climate Remediation Points multiplier| -|cc_block_highlighting|false|Code Climate Block Highlighting| -|sigma||Sigma value| - -**Use this rule by referencing it:** -``` xml - -``` - -## NcssMethodCount - -**Since:** PMD 5.5.0 - -**Priority:** Medium (3) - -This rule uses the NCSS (Non-Commenting Source Statements) algorithm to determine the number of lines -of code for a given method. NCSS ignores comments, and counts actual statements. Using this algorithm, -lines of code that are split are counted as one. - -**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.apex.rule.complexity.NcssMethodCountRule](https://github.com/pmd/pmd/blob/master/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/complexity/NcssMethodCountRule.java) - -**Example(s):** - -``` java -public class Foo extends Bar { - //this method only has 1 NCSS lines - public Integer methd() { - super.methd(); - - - - return 1; - } -} -``` - -**This rule has the following properties:** - -|Name|Default Value|Description| -|----|-------------|-----------| -|topscore||Top score value| -|minimum||Minimum reporting threshold| -|cc_categories|[Style]|Code Climate Categories| -|cc_remediation_points_multiplier|1|Code Climate Remediation Points multiplier| -|cc_block_highlighting|false|Code Climate Block Highlighting| -|sigma||Sigma value| - -**Use this rule by referencing it:** -``` xml - -``` - -## NcssTypeCount - -**Since:** PMD 5.5.0 - -**Priority:** Medium (3) - -This rule uses the NCSS (Non-Commenting Source Statements) algorithm to determine the number of lines -of code for a given type. NCSS ignores comments, and counts actual statements. Using this algorithm, -lines of code that are split are counted as one. - -**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.apex.rule.complexity.NcssTypeCountRule](https://github.com/pmd/pmd/blob/master/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/complexity/NcssTypeCountRule.java) - -**Example(s):** - -``` java -//this class only has 6 NCSS lines -public class Foo extends Bar { - public Foo() { - super(); - - - - - - super.foo(); - } -} -``` - -**This rule has the following properties:** - -|Name|Default Value|Description| -|----|-------------|-----------| -|topscore||Top score value| -|minimum||Minimum reporting threshold| -|cc_categories|[Style]|Code Climate Categories| -|cc_remediation_points_multiplier|1|Code Climate Remediation Points multiplier| -|cc_block_highlighting|false|Code Climate Block Highlighting| -|sigma||Sigma value| - -**Use this rule by referencing it:** -``` xml - -``` - -## StdCyclomaticComplexity - -**Since:** PMD 5.5.0 - -**Priority:** Medium (3) - -Complexity directly affects maintenance costs is determined by the number of decision points in a method -plus one for the method entry. The decision points include 'if', 'while', 'for', and 'case labels' calls. -Generally, numbers ranging from 1-4 denote low complexity, 5-7 denote moderate complexity, 8-10 denote -high complexity, and 11+ is very high complexity. - -**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.apex.rule.complexity.StdCyclomaticComplexityRule](https://github.com/pmd/pmd/blob/master/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/complexity/StdCyclomaticComplexityRule.java) - -**Example(s):** - -``` java -// This has a Cyclomatic Complexity = 12 -public class Foo { -1 public void example() { -2 if (a == b || (c == d && e == f)) { -3 if (a1 == b1) { - fiddle(); -4 } else if a2 == b2) { - fiddle(); - } else { - fiddle(); - } -5 } else if (c == d) { -6 while (c == d) { - fiddle(); - } -7 } else if (e == f) { -8 for (int n = 0; n < h; n++) { - fiddle(); - } - } else { - switch (z) { -9 case 1: - fiddle(); - break; -10 case 2: - fiddle(); - break; -11 case 3: - fiddle(); - break; -12 default: - fiddle(); - break; - } - } -} -``` - -**This rule has the following properties:** - -|Name|Default Value|Description| -|----|-------------|-----------| -|showMethodsComplexity|true|Add method average violations to the report| -|showClassesComplexity|true|Add class average violations to the report| -|cc_categories|[Style]|Code Climate Categories| -|cc_remediation_points_multiplier|1|Code Climate Remediation Points multiplier| -|cc_block_highlighting|false|Code Climate Block Highlighting| -|reportLevel|10|Cyclomatic Complexity reporting threshold| - -**Use this rule by referencing it:** -``` xml - -``` - -## TooManyFields - -**Since:** PMD 5.5.0 - -**Priority:** Medium (3) - -Classes that have too many fields can become unwieldy and could be redesigned to have fewer fields, -possibly through grouping related fields in new objects. For example, a class with individual -city/state/zip fields could park them within a single Address field. - -**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.apex.rule.complexity.TooManyFieldsRule](https://github.com/pmd/pmd/blob/master/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/complexity/TooManyFieldsRule.java) - -**Example(s):** - -``` java -public class Person { - // too many separate fields - int birthYear; - int birthMonth; - int birthDate; - float height; - float weight; -} - -public class Person { - // this is more manageable - Date birthDate; - BodyMeasurements measurements; -} -``` - -**This rule has the following properties:** - -|Name|Default Value|Description| -|----|-------------|-----------| -|cc_categories|[Style]|Code Climate Categories| -|cc_remediation_points_multiplier|1|Code Climate Remediation Points multiplier| -|cc_block_highlighting|false|Code Climate Block Highlighting| -|maxfields|15|Max allowable fields| - -**Use this rule by referencing it:** -``` xml - -``` - diff --git a/docs/pages/pmd/rules/apex/design.md b/docs/pages/pmd/rules/apex/design.md new file mode 100644 index 00000000000..8ac0a075469 --- /dev/null +++ b/docs/pages/pmd/rules/apex/design.md @@ -0,0 +1,495 @@ +--- +title: Design +summary: Rules that help you discover design issues. +permalink: pmd_rules_apex_design.html +folder: pmd/rules/apex +sidebaractiveurl: /pmd_rules_apex.html +editmepath: ../pmd-apex/src/main/resources/category/apex/design.xml +keywords: Design, AvoidDeeplyNestedIfStmts, CyclomaticComplexity, ExcessiveClassLength, ExcessiveParameterList, ExcessivePublicCount, NcssConstructorCount, NcssMethodCount, NcssTypeCount, StdCyclomaticComplexity, TooManyFields +language: Apex +--- +## AvoidDeeplyNestedIfStmts + +**Since:** PMD 5.5.0 + +**Priority:** Medium (3) + +Avoid creating deeply nested if-then statements since they are harder to read and error-prone to maintain. + +**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.apex.rule.design.AvoidDeeplyNestedIfStmtsRule](https://github.com/pmd/pmd/blob/master/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/design/AvoidDeeplyNestedIfStmtsRule.java) + +**Example(s):** + +``` java +public class Foo { + public void bar(Integer x, Integer y, Integer z) { + if (x>y) { + if (y>z) { + if (z==x) { + // !! too deep + } + } + } + } +} +``` + +**This rule has the following properties:** + +|Name|Default Value|Description|Multivalued| +|----|-------------|-----------|-----------| +|cc_categories|Style|Code Climate Categories|yes. Delimiter is '\|'.| +|cc_remediation_points_multiplier|1|Code Climate Remediation Points multiplier|no| +|cc_block_highlighting|false|Code Climate Block Highlighting|no| +|problemDepth|3|The if statement depth reporting threshold|no| + +**Use this rule by referencing it:** +``` xml + +``` + +## CyclomaticComplexity + +**Since:** PMD 6.0.0 + +**Priority:** Medium (3) + +The complexity of methods directly affects maintenance costs and readability. Concentrating too much decisional logic +in a single method makes its behaviour hard to read and change. + +Cyclomatic complexity assesses the complexity of a method by counting the number of decision points in a method, +plus one for the method entry. Decision points are places where the control flow jumps to another place in the +program. As such, they include all control flow statements, such as 'if', 'while', 'for', and 'case'. + +Generally, numbers ranging from 1-4 denote low complexity, 5-7 denote moderate complexity, 8-10 denote +high complexity, and 11+ is very high complexity. By default, this rule reports methods with a complexity >= 10. +Additionnally, classes with many methods of moderate complexity get reported as well once the total of their +methods' complexities reaches 40, even if none of the methods was directly reported. + +Reported methods should be broken down into several smaller methods. Reported classes should probably be broken down +into subcomponents. + +**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.apex.rule.design.CyclomaticComplexityRule](https://github.com/pmd/pmd/blob/master/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/design/CyclomaticComplexityRule.java) + +**Example(s):** + +``` java +public class Complicated { + public void example() { // This method has a cyclomatic complexity of 12 + int x = 0, y = 1, z = 2, t = 2; + boolean a = false, b = true, c = false, d = true; + if (a && b || b && d) { + if (y == z) { + x = 2; + } else if (y == t && !d) { + x = 2; + } else { + x = 2; + } + } else if (c && d) { + while (z < y) { + x = 2; + } + } else { + for (int n = 0; n < t; n++) { + x = 2; + } + } + } +} +``` + +**This rule has the following properties:** + +|Name|Default Value|Description|Multivalued| +|----|-------------|-----------|-----------| +|methodReportLevel|10|Cyclomatic complexity reporting threshold|no| +|cc_categories|Style|Code Climate Categories|yes. Delimiter is '\|'.| +|cc_remediation_points_multiplier|1|Code Climate Remediation Points multiplier|no| +|cc_block_highlighting|false|Code Climate Block Highlighting|no| +|classReportLevel|40|Total class complexity reporting threshold|no| + +**Use this rule by referencing it:** +``` xml + +``` + +## ExcessiveClassLength + +**Since:** PMD 5.5.0 + +**Priority:** Medium (3) + +Excessive class file lengths are usually indications that the class may be burdened with excessive +responsibilities that could be provided by external classes or functions. In breaking these methods +apart the code becomes more managable and ripe for reuse. + +**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.apex.rule.design.ExcessiveClassLengthRule](https://github.com/pmd/pmd/blob/master/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/design/ExcessiveClassLengthRule.java) + +**Example(s):** + +``` java +public class Foo { + public void bar1() { + // 1000 lines of code + } + public void bar2() { + // 1000 lines of code + } + public void bar3() { + // 1000 lines of code + } + public void barN() { + // 1000 lines of code + } +} +``` + +**This rule has the following properties:** + +|Name|Default Value|Description|Multivalued| +|----|-------------|-----------|-----------| +|topscore||Top score value|no| +|minimum||Minimum reporting threshold|no| +|cc_categories|Style|Code Climate Categories|yes. Delimiter is '\|'.| +|cc_remediation_points_multiplier|1|Code Climate Remediation Points multiplier|no| +|cc_block_highlighting|false|Code Climate Block Highlighting|no| +|sigma||Sigma value|no| + +**Use this rule by referencing it:** +``` xml + +``` + +## ExcessiveParameterList + +**Since:** PMD 5.5.0 + +**Priority:** Medium (3) + +Methods with numerous parameters are a challenge to maintain, especially if most of them share the +same datatype. These situations usually denote the need for new objects to wrap the numerous parameters. + +**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.apex.rule.design.ExcessiveParameterListRule](https://github.com/pmd/pmd/blob/master/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/design/ExcessiveParameterListRule.java) + +**Example(s):** + +``` java +// too many arguments liable to be mixed up +public void addPerson(int birthYear, int birthMonth, int birthDate, int height, int weight, int ssn) { + // ... +} +// preferred approach +public void addPerson(Date birthdate, BodyMeasurements measurements, int ssn) { + // ... +} +``` + +**This rule has the following properties:** + +|Name|Default Value|Description|Multivalued| +|----|-------------|-----------|-----------| +|topscore||Top score value|no| +|minimum||Minimum reporting threshold|no| +|cc_categories|Style|Code Climate Categories|yes. Delimiter is '\|'.| +|cc_remediation_points_multiplier|1|Code Climate Remediation Points multiplier|no| +|cc_block_highlighting|false|Code Climate Block Highlighting|no| +|sigma||Sigma value|no| + +**Use this rule by referencing it:** +``` xml + +``` + +## ExcessivePublicCount + +**Since:** PMD 5.5.0 + +**Priority:** Medium (3) + +Classes with large numbers of public methods and attributes require disproportionate testing efforts +since combinational side effects grow rapidly and increase risk. Refactoring these classes into +smaller ones not only increases testability and reliability but also allows new variations to be +developed easily. + +**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.apex.rule.design.ExcessivePublicCountRule](https://github.com/pmd/pmd/blob/master/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/design/ExcessivePublicCountRule.java) + +**Example(s):** + +``` java +public class Foo { + public String value; + public Bar something; + public Variable var; + // [... more more public attributes ...] + + public void doWork() {} + public void doMoreWork() {} + public void doWorkAgain() {} + // [... more more public methods ...] +} +``` + +**This rule has the following properties:** + +|Name|Default Value|Description|Multivalued| +|----|-------------|-----------|-----------| +|topscore||Top score value|no| +|minimum||Minimum reporting threshold|no| +|cc_categories|Style|Code Climate Categories|yes. Delimiter is '\|'.| +|cc_remediation_points_multiplier|1|Code Climate Remediation Points multiplier|no| +|cc_block_highlighting|false|Code Climate Block Highlighting|no| +|sigma||Sigma value|no| + +**Use this rule by referencing it:** +``` xml + +``` + +## NcssConstructorCount + +**Since:** PMD 5.5.0 + +**Priority:** Medium (3) + +This rule uses the NCSS (Non-Commenting Source Statements) algorithm to determine the number of lines +of code for a given constructor. NCSS ignores comments, and counts actual statements. Using this algorithm, +lines of code that are split are counted as one. + +**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.apex.rule.design.NcssConstructorCountRule](https://github.com/pmd/pmd/blob/master/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/design/NcssConstructorCountRule.java) + +**Example(s):** + +``` java +public class Foo extends Bar { + //this constructor only has 1 NCSS lines + public Foo() { + super(); + + + + + super.foo(); +} +} +``` + +**This rule has the following properties:** + +|Name|Default Value|Description|Multivalued| +|----|-------------|-----------|-----------| +|topscore||Top score value|no| +|minimum||Minimum reporting threshold|no| +|cc_categories|Style|Code Climate Categories|yes. Delimiter is '\|'.| +|cc_remediation_points_multiplier|1|Code Climate Remediation Points multiplier|no| +|cc_block_highlighting|false|Code Climate Block Highlighting|no| +|sigma||Sigma value|no| + +**Use this rule by referencing it:** +``` xml + +``` + +## NcssMethodCount + +**Since:** PMD 5.5.0 + +**Priority:** Medium (3) + +This rule uses the NCSS (Non-Commenting Source Statements) algorithm to determine the number of lines +of code for a given method. NCSS ignores comments, and counts actual statements. Using this algorithm, +lines of code that are split are counted as one. + +**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.apex.rule.design.NcssMethodCountRule](https://github.com/pmd/pmd/blob/master/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/design/NcssMethodCountRule.java) + +**Example(s):** + +``` java +public class Foo extends Bar { + //this method only has 1 NCSS lines + public Integer methd() { + super.methd(); + + + + return 1; + } +} +``` + +**This rule has the following properties:** + +|Name|Default Value|Description|Multivalued| +|----|-------------|-----------|-----------| +|topscore||Top score value|no| +|minimum||Minimum reporting threshold|no| +|cc_categories|Style|Code Climate Categories|yes. Delimiter is '\|'.| +|cc_remediation_points_multiplier|1|Code Climate Remediation Points multiplier|no| +|cc_block_highlighting|false|Code Climate Block Highlighting|no| +|sigma||Sigma value|no| + +**Use this rule by referencing it:** +``` xml + +``` + +## NcssTypeCount + +**Since:** PMD 5.5.0 + +**Priority:** Medium (3) + +This rule uses the NCSS (Non-Commenting Source Statements) algorithm to determine the number of lines +of code for a given type. NCSS ignores comments, and counts actual statements. Using this algorithm, +lines of code that are split are counted as one. + +**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.apex.rule.design.NcssTypeCountRule](https://github.com/pmd/pmd/blob/master/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/design/NcssTypeCountRule.java) + +**Example(s):** + +``` java +//this class only has 6 NCSS lines +public class Foo extends Bar { + public Foo() { + super(); + + + + + + super.foo(); + } +} +``` + +**This rule has the following properties:** + +|Name|Default Value|Description|Multivalued| +|----|-------------|-----------|-----------| +|topscore||Top score value|no| +|minimum||Minimum reporting threshold|no| +|cc_categories|Style|Code Climate Categories|yes. Delimiter is '\|'.| +|cc_remediation_points_multiplier|1|Code Climate Remediation Points multiplier|no| +|cc_block_highlighting|false|Code Climate Block Highlighting|no| +|sigma||Sigma value|no| + +**Use this rule by referencing it:** +``` xml + +``` + +## StdCyclomaticComplexity + +**Since:** PMD 5.5.0 + +**Priority:** Medium (3) + +Complexity directly affects maintenance costs is determined by the number of decision points in a method +plus one for the method entry. The decision points include 'if', 'while', 'for', and 'case labels' calls. +Generally, numbers ranging from 1-4 denote low complexity, 5-7 denote moderate complexity, 8-10 denote +high complexity, and 11+ is very high complexity. + +**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.apex.rule.design.StdCyclomaticComplexityRule](https://github.com/pmd/pmd/blob/master/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/design/StdCyclomaticComplexityRule.java) + +**Example(s):** + +``` java +// This has a Cyclomatic Complexity = 12 +public class Foo { +1 public void example() { +2 if (a == b || (c == d && e == f)) { +3 if (a1 == b1) { + fiddle(); +4 } else if a2 == b2) { + fiddle(); + } else { + fiddle(); + } +5 } else if (c == d) { +6 while (c == d) { + fiddle(); + } +7 } else if (e == f) { +8 for (int n = 0; n < h; n++) { + fiddle(); + } + } else { + switch (z) { +9 case 1: + fiddle(); + break; +10 case 2: + fiddle(); + break; +11 case 3: + fiddle(); + break; +12 default: + fiddle(); + break; + } + } +} +``` + +**This rule has the following properties:** + +|Name|Default Value|Description|Multivalued| +|----|-------------|-----------|-----------| +|showMethodsComplexity|true|Add method average violations to the report|no| +|showClassesComplexity|true|Add class average violations to the report|no| +|cc_categories|Style|Code Climate Categories|yes. Delimiter is '\|'.| +|cc_remediation_points_multiplier|1|Code Climate Remediation Points multiplier|no| +|cc_block_highlighting|false|Code Climate Block Highlighting|no| +|reportLevel|10|Cyclomatic Complexity reporting threshold|no| + +**Use this rule by referencing it:** +``` xml + +``` + +## TooManyFields + +**Since:** PMD 5.5.0 + +**Priority:** Medium (3) + +Classes that have too many fields can become unwieldy and could be redesigned to have fewer fields, +possibly through grouping related fields in new objects. For example, a class with individual +city/state/zip fields could park them within a single Address field. + +**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.apex.rule.design.TooManyFieldsRule](https://github.com/pmd/pmd/blob/master/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/design/TooManyFieldsRule.java) + +**Example(s):** + +``` java +public class Person { + // too many separate fields + int birthYear; + int birthMonth; + int birthDate; + float height; + float weight; +} + +public class Person { + // this is more manageable + Date birthDate; + BodyMeasurements measurements; +} +``` + +**This rule has the following properties:** + +|Name|Default Value|Description|Multivalued| +|----|-------------|-----------|-----------| +|cc_categories|Style|Code Climate Categories|yes. Delimiter is '\|'.| +|cc_remediation_points_multiplier|1|Code Climate Remediation Points multiplier|no| +|cc_block_highlighting|false|Code Climate Block Highlighting|no| +|maxfields|15|Max allowable fields|no| + +**Use this rule by referencing it:** +``` xml + +``` + diff --git a/docs/pages/pmd/rules/apex/errorprone.md b/docs/pages/pmd/rules/apex/errorprone.md new file mode 100644 index 00000000000..8e1aa7d2c6f --- /dev/null +++ b/docs/pages/pmd/rules/apex/errorprone.md @@ -0,0 +1,367 @@ +--- +title: Error Prone +summary: Rules to detect constructs that are either broken, extremely confusing or prone to runtime errors. +permalink: pmd_rules_apex_errorprone.html +folder: pmd/rules/apex +sidebaractiveurl: /pmd_rules_apex.html +editmepath: ../pmd-apex/src/main/resources/category/apex/errorprone.xml +keywords: Error Prone, AvoidDirectAccessTriggerMap, AvoidHardcodingId, EmptyCatchBlock, EmptyIfStmt, EmptyStatementBlock, EmptyTryOrFinallyBlock, EmptyWhileStmt, MethodWithSameNameAsEnclosingClass, AvoidNonExistentAnnotations +language: Apex +--- +## AvoidDirectAccessTriggerMap + +**Since:** PMD 6.0.0 + +**Priority:** Medium (3) + +Avoid directly accessing Trigger.old and Trigger.new as it can lead to a bug. Triggers should be bulkified and iterate through the map to handle the actions for each item separately. + +**This rule is defined by the following XPath expression:** +``` xpath +//ArrayLoadExpression[TriggerVariableExpression and LiteralExpression] +``` + +**Example(s):** + +``` java +trigger AccountTrigger on Account (before insert, before update) { + Account a = Trigger.new[0]; //Bad: Accessing the trigger array directly is not recommended. + + foreach ( Account a : Trigger.new ){ + //Good: Iterate through the trigger.new array instead. + } +} +``` + +**This rule has the following properties:** + +|Name|Default Value|Description|Multivalued| +|----|-------------|-----------|-----------| +|cc_categories|Style|Code Climate Categories|yes. Delimiter is '\|'.| +|cc_remediation_points_multiplier|1|Code Climate Remediation Points multiplier|no| +|cc_block_highlighting|false|Code Climate Block Highlighting|no| + +**Use this rule by referencing it:** +``` xml + +``` + +## AvoidHardcodingId + +**Since:** PMD 6.0.0 + +**Priority:** Medium (3) + +When deploying Apex code between sandbox and production environments, or installing Force.com AppExchange packages, +it is essential to avoid hardcoding IDs in the Apex code. By doing so, if the record IDs change between environments, +the logic can dynamically identify the proper data to operate against and not fail. + +**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.apex.rule.errorprone.AvoidHardcodingIdRule](https://github.com/pmd/pmd/blob/master/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/errorprone/AvoidHardcodingIdRule.java) + +**Example(s):** + +``` java +public without sharing class Foo { + void foo() { + //Error - hardcoded the record type id + if(a.RecordTypeId == '012500000009WAr'){ + //do some logic here..... + } else if(a.RecordTypeId == '0123000000095Km'){ + //do some logic here for a different record type... + } + } +} +``` + +**This rule has the following properties:** + +|Name|Default Value|Description|Multivalued| +|----|-------------|-----------|-----------| +|cc_categories|Style|Code Climate Categories|yes. Delimiter is '\|'.| +|cc_remediation_points_multiplier|1|Code Climate Remediation Points multiplier|no| +|cc_block_highlighting|false|Code Climate Block Highlighting|no| + +**Use this rule by referencing it:** +``` xml + +``` + +## AvoidNonExistentAnnotations + +**Since:** PMD 6.5.0 + +**Priority:** Medium (3) + +Apex supported non existent annotations for legacy reasons. +In the future, use of such non-existent annotations could result in broken apex code that will not compile. +This will prevent users of garbage annotations from being able to use legitimate annotations added to Apex in the future. +A full list of supported annotations can be found at https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_classes_annotation.htm + +**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.apex.rule.errorprone.AvoidNonExistentAnnotationsRule](https://github.com/pmd/pmd/blob/master/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/errorprone/AvoidNonExistentAnnotationsRule.java) + +**Example(s):** + +``` java +@NonExistentAnnotation public class ClassWithNonexistentAnnotation { + @NonExistentAnnotation public void methodWithNonExistentAnnotation() { + // ... + } +} +``` + +**This rule has the following properties:** + +|Name|Default Value|Description|Multivalued| +|----|-------------|-----------|-----------| +|cc_categories|Style|Code Climate Categories|yes. Delimiter is '\|'.| +|cc_remediation_points_multiplier|1|Code Climate Remediation Points multiplier|no| +|cc_block_highlighting|false|Code Climate Block Highlighting|no| + +**Use this rule by referencing it:** +``` xml + +``` + +## EmptyCatchBlock + +**Since:** PMD 6.0.0 + +**Priority:** Medium (3) + +Empty Catch Block finds instances where an exception is caught, but nothing is done. +In most circumstances, this swallows an exception which should either be acted on +or reported. + +**This rule is defined by the following XPath expression:** +``` xpath +//CatchBlockStatement[./BlockStatement[count(*) = 0]] +``` + +**Example(s):** + +``` java +public void doSomething() { + ... + try { + insert accounts; + } catch (DmlException dmle) { + // not good + } +} +``` + +**This rule has the following properties:** + +|Name|Default Value|Description|Multivalued| +|----|-------------|-----------|-----------| +|cc_categories|Style|Code Climate Categories|yes. Delimiter is '\|'.| +|cc_remediation_points_multiplier|1|Code Climate Remediation Points multiplier|no| +|cc_block_highlighting|false|Code Climate Block Highlighting|no| + +**Use this rule by referencing it:** +``` xml + +``` + +## EmptyIfStmt + +**Since:** PMD 6.0.0 + +**Priority:** Medium (3) + +Empty If Statement finds instances where a condition is checked but nothing is done about it. + +**This rule is defined by the following XPath expression:** +``` xpath +//IfBlockStatement + [BlockStatement[count(*) = 0]] +``` + +**Example(s):** + +``` java +public class Foo { + public void bar(Integer x) { + if (x == 0) { + // empty! + } + } +} +``` + +**This rule has the following properties:** + +|Name|Default Value|Description|Multivalued| +|----|-------------|-----------|-----------| +|cc_categories|Style|Code Climate Categories|yes. Delimiter is '\|'.| +|cc_remediation_points_multiplier|1|Code Climate Remediation Points multiplier|no| +|cc_block_highlighting|false|Code Climate Block Highlighting|no| + +**Use this rule by referencing it:** +``` xml + +``` + +## EmptyStatementBlock + +**Since:** PMD 6.0.0 + +**Priority:** Medium (3) + +Empty block statements serve no purpose and should be removed. + +**This rule is defined by the following XPath expression:** +``` xpath +//Method/ModifierNode[@Abstract!='true' and ../BlockStatement[count(*) = 0]] +| //Method/BlockStatement//BlockStatement[count(*) = 0 and @Location != parent::*/@Location] +``` + +**Example(s):** + +``` java +public class Foo { + + private int _bar; + + public void setBar(int bar) { + // empty + } + +} +``` + +**This rule has the following properties:** + +|Name|Default Value|Description|Multivalued| +|----|-------------|-----------|-----------| +|cc_categories|Style|Code Climate Categories|yes. Delimiter is '\|'.| +|cc_remediation_points_multiplier|1|Code Climate Remediation Points multiplier|no| +|cc_block_highlighting|false|Code Climate Block Highlighting|no| + +**Use this rule by referencing it:** +``` xml + +``` + +## EmptyTryOrFinallyBlock + +**Since:** PMD 6.0.0 + +**Priority:** Medium (3) + +Avoid empty try or finally blocks - what's the point? + +**This rule is defined by the following XPath expression:** +``` xpath +//TryCatchFinallyBlockStatement[./BlockStatement[count(*) = 0]] +``` + +**Example(s):** + +``` java +public class Foo { + public void bar() { + try { + // empty ! + } catch (Exception e) { + e.printStackTrace(); + } + } +} + +public class Foo { + public void bar() { + try { + int x=2; + } finally { + // empty! + } + } +} +``` + +**This rule has the following properties:** + +|Name|Default Value|Description|Multivalued| +|----|-------------|-----------|-----------| +|cc_categories|Style|Code Climate Categories|yes. Delimiter is '\|'.| +|cc_remediation_points_multiplier|1|Code Climate Remediation Points multiplier|no| +|cc_block_highlighting|false|Code Climate Block Highlighting|no| + +**Use this rule by referencing it:** +``` xml + +``` + +## EmptyWhileStmt + +**Since:** PMD 6.0.0 + +**Priority:** Medium (3) + +Empty While Statement finds all instances where a while statement does nothing. +If it is a timing loop, then you should use Thread.sleep() for it; if it is +a while loop that does a lot in the exit expression, rewrite it to make it clearer. + +**This rule is defined by the following XPath expression:** +``` xpath +//WhileLoopStatement[./BlockStatement[count(*) = 0]] +``` + +**Example(s):** + +``` java +public void bar(Integer a, Integer b) { + while (a == b) { + // empty! + } +} +``` + +**This rule has the following properties:** + +|Name|Default Value|Description|Multivalued| +|----|-------------|-----------|-----------| +|cc_categories|Style|Code Climate Categories|yes. Delimiter is '\|'.| +|cc_remediation_points_multiplier|1|Code Climate Remediation Points multiplier|no| +|cc_block_highlighting|false|Code Climate Block Highlighting|no| + +**Use this rule by referencing it:** +``` xml + +``` + +## MethodWithSameNameAsEnclosingClass + +**Since:** PMD 5.5.0 + +**Priority:** Medium (3) + +Non-constructor methods should not have the same name as the enclosing class. + +**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.apex.rule.errorprone.MethodWithSameNameAsEnclosingClassRule](https://github.com/pmd/pmd/blob/master/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/errorprone/MethodWithSameNameAsEnclosingClassRule.java) + +**Example(s):** + +``` java +public class MyClass { + // this is OK because it is a constructor + public MyClass() {} + // this is bad because it is a method + public void MyClass() {} +} +``` + +**This rule has the following properties:** + +|Name|Default Value|Description|Multivalued| +|----|-------------|-----------|-----------| +|cc_categories|Style|Code Climate Categories|yes. Delimiter is '\|'.| +|cc_remediation_points_multiplier|1|Code Climate Remediation Points multiplier|no| +|cc_block_highlighting|false|Code Climate Block Highlighting|no| + +**Use this rule by referencing it:** +``` xml + +``` + diff --git a/docs/pages/pmd/rules/apex/performance.md b/docs/pages/pmd/rules/apex/performance.md index 6cf63881068..1d293a87ed3 100644 --- a/docs/pages/pmd/rules/apex/performance.md +++ b/docs/pages/pmd/rules/apex/performance.md @@ -1,11 +1,12 @@ --- title: Performance -summary: The Performance ruleset contains a collection of good practices which should be followed. +summary: Rules that flag suboptimal code. permalink: pmd_rules_apex_performance.html folder: pmd/rules/apex sidebaractiveurl: /pmd_rules_apex.html -editmepath: ../pmd-apex/src/main/resources/rulesets/apex/performance.xml -keywords: Performance, AvoidSoqlInLoops, AvoidDmlStatementsInLoops +editmepath: ../pmd-apex/src/main/resources/category/apex/performance.xml +keywords: Performance, AvoidDmlStatementsInLoops, AvoidSoqlInLoops, AvoidSoslInLoops +language: Apex --- ## AvoidDmlStatementsInLoops @@ -33,15 +34,15 @@ public class Something { **This rule has the following properties:** -|Name|Default Value|Description| -|----|-------------|-----------| -|cc_categories|[Style]|Code Climate Categories| -|cc_remediation_points_multiplier|1|Code Climate Remediation Points multiplier| -|cc_block_highlighting|false|Code Climate Block Highlighting| +|Name|Default Value|Description|Multivalued| +|----|-------------|-----------|-----------| +|cc_categories|Style|Code Climate Categories|yes. Delimiter is '\|'.| +|cc_remediation_points_multiplier|1|Code Climate Remediation Points multiplier|no| +|cc_block_highlighting|false|Code Climate Block Highlighting|no| **Use this rule by referencing it:** ``` xml - + ``` ## AvoidSoqlInLoops @@ -68,14 +69,49 @@ public class Something { **This rule has the following properties:** -|Name|Default Value|Description| -|----|-------------|-----------| -|cc_categories|[Style]|Code Climate Categories| -|cc_remediation_points_multiplier|1|Code Climate Remediation Points multiplier| -|cc_block_highlighting|false|Code Climate Block Highlighting| +|Name|Default Value|Description|Multivalued| +|----|-------------|-----------|-----------| +|cc_categories|Style|Code Climate Categories|yes. Delimiter is '\|'.| +|cc_remediation_points_multiplier|1|Code Climate Remediation Points multiplier|no| +|cc_block_highlighting|false|Code Climate Block Highlighting|no| **Use this rule by referencing it:** ``` xml - + +``` + +## AvoidSoslInLoops + +**Since:** PMD 6.0.0 + +**Priority:** Medium (3) + +Sosl calls within loops can cause governor limit exceptions. + +**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.apex.rule.performance.AvoidSoslInLoopsRule](https://github.com/pmd/pmd/blob/master/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/performance/AvoidSoslInLoopsRule.java) + +**Example(s):** + +``` java +public class Something { + public static void main( String as[] ) { + for (Integer i = 0; i < 10; i++) { + List> searchList = [FIND 'map*' IN ALL FIELDS RETURNING Account (Id, Name), Contact, Opportunity, Lead]; + } + } +} +``` + +**This rule has the following properties:** + +|Name|Default Value|Description|Multivalued| +|----|-------------|-----------|-----------| +|cc_categories|Style|Code Climate Categories|yes. Delimiter is '\|'.| +|cc_remediation_points_multiplier|1|Code Climate Remediation Points multiplier|no| +|cc_block_highlighting|false|Code Climate Block Highlighting|no| + +**Use this rule by referencing it:** +``` xml + ``` diff --git a/docs/pages/pmd/rules/apex/security.md b/docs/pages/pmd/rules/apex/security.md index 09ca5f39051..9648289959b 100644 --- a/docs/pages/pmd/rules/apex/security.md +++ b/docs/pages/pmd/rules/apex/security.md @@ -1,11 +1,12 @@ --- title: Security -summary: These rules deal with different security problems that can occur within Apex. +summary: Rules that flag potential security flaws. permalink: pmd_rules_apex_security.html folder: pmd/rules/apex sidebaractiveurl: /pmd_rules_apex.html -editmepath: ../pmd-apex/src/main/resources/rulesets/apex/security.xml -keywords: Security, ApexSharingViolations, ApexOpenRedirect, ApexInsecureEndpoint, ApexXSSFromURLParam, ApexXSSFromEscapeFalse, ApexBadCrypto, ApexCSRF, ApexSOQLInjection, ApexCRUDViolation, ApexDangerousMethods, ApexSuggestUsingNamedCred +editmepath: ../pmd-apex/src/main/resources/category/apex/security.xml +keywords: Security, ApexBadCrypto, ApexCRUDViolation, ApexCSRF, ApexDangerousMethods, ApexInsecureEndpoint, ApexOpenRedirect, ApexSharingViolations, ApexSOQLInjection, ApexSuggestUsingNamedCred, ApexXSSFromEscapeFalse, ApexXSSFromURLParam +language: Apex --- ## ApexBadCrypto @@ -31,15 +32,15 @@ public without sharing class Foo { **This rule has the following properties:** -|Name|Default Value|Description| -|----|-------------|-----------| -|cc_categories|[Style]|Code Climate Categories| -|cc_remediation_points_multiplier|1|Code Climate Remediation Points multiplier| -|cc_block_highlighting|false|Code Climate Block Highlighting| +|Name|Default Value|Description|Multivalued| +|----|-------------|-----------|-----------| +|cc_categories|Style|Code Climate Categories|yes. Delimiter is '\|'.| +|cc_remediation_points_multiplier|1|Code Climate Remediation Points multiplier|no| +|cc_block_highlighting|false|Code Climate Block Highlighting|no| **Use this rule by referencing it:** ``` xml - + ``` ## ApexCRUDViolation @@ -75,15 +76,15 @@ public class Foo { **This rule has the following properties:** -|Name|Default Value|Description| -|----|-------------|-----------| -|cc_categories|[Style]|Code Climate Categories| -|cc_remediation_points_multiplier|1|Code Climate Remediation Points multiplier| -|cc_block_highlighting|false|Code Climate Block Highlighting| +|Name|Default Value|Description|Multivalued| +|----|-------------|-----------|-----------| +|cc_categories|Style|Code Climate Categories|yes. Delimiter is '\|'.| +|cc_remediation_points_multiplier|1|Code Climate Remediation Points multiplier|no| +|cc_block_highlighting|false|Code Climate Block Highlighting|no| **Use this rule by referencing it:** ``` xml - + ``` ## ApexCSRF @@ -113,15 +114,15 @@ public class Foo { **This rule has the following properties:** -|Name|Default Value|Description| -|----|-------------|-----------| -|cc_categories|[Style]|Code Climate Categories| -|cc_remediation_points_multiplier|1|Code Climate Remediation Points multiplier| -|cc_block_highlighting|false|Code Climate Block Highlighting| +|Name|Default Value|Description|Multivalued| +|----|-------------|-----------|-----------| +|cc_categories|Style|Code Climate Categories|yes. Delimiter is '\|'.| +|cc_remediation_points_multiplier|1|Code Climate Remediation Points multiplier|no| +|cc_block_highlighting|false|Code Climate Block Highlighting|no| **Use this rule by referencing it:** ``` xml - + ``` ## ApexDangerousMethods @@ -153,15 +154,15 @@ public class Foo { **This rule has the following properties:** -|Name|Default Value|Description| -|----|-------------|-----------| -|cc_categories|[Style]|Code Climate Categories| -|cc_remediation_points_multiplier|1|Code Climate Remediation Points multiplier| -|cc_block_highlighting|false|Code Climate Block Highlighting| +|Name|Default Value|Description|Multivalued| +|----|-------------|-----------|-----------| +|cc_categories|Style|Code Climate Categories|yes. Delimiter is '\|'.| +|cc_remediation_points_multiplier|1|Code Climate Remediation Points multiplier|no| +|cc_block_highlighting|false|Code Climate Block Highlighting|no| **Use this rule by referencing it:** ``` xml - + ``` ## ApexInsecureEndpoint @@ -188,15 +189,15 @@ public without sharing class Foo { **This rule has the following properties:** -|Name|Default Value|Description| -|----|-------------|-----------| -|cc_categories|[Style]|Code Climate Categories| -|cc_remediation_points_multiplier|1|Code Climate Remediation Points multiplier| -|cc_block_highlighting|false|Code Climate Block Highlighting| +|Name|Default Value|Description|Multivalued| +|----|-------------|-----------|-----------| +|cc_categories|Style|Code Climate Categories|yes. Delimiter is '\|'.| +|cc_remediation_points_multiplier|1|Code Climate Remediation Points multiplier|no| +|cc_block_highlighting|false|Code Climate Block Highlighting|no| **Use this rule by referencing it:** ``` xml - + ``` ## ApexOpenRedirect @@ -223,15 +224,15 @@ public without sharing class Foo { **This rule has the following properties:** -|Name|Default Value|Description| -|----|-------------|-----------| -|cc_categories|[Style]|Code Climate Categories| -|cc_remediation_points_multiplier|1|Code Climate Remediation Points multiplier| -|cc_block_highlighting|false|Code Climate Block Highlighting| +|Name|Default Value|Description|Multivalued| +|----|-------------|-----------|-----------| +|cc_categories|Style|Code Climate Categories|yes. Delimiter is '\|'.| +|cc_remediation_points_multiplier|1|Code Climate Remediation Points multiplier|no| +|cc_block_highlighting|false|Code Climate Block Highlighting|no| **Use this rule by referencing it:** ``` xml - + ``` ## ApexSharingViolations @@ -255,15 +256,15 @@ public without sharing class Foo { **This rule has the following properties:** -|Name|Default Value|Description| -|----|-------------|-----------| -|cc_categories|[Style]|Code Climate Categories| -|cc_remediation_points_multiplier|1|Code Climate Remediation Points multiplier| -|cc_block_highlighting|false|Code Climate Block Highlighting| +|Name|Default Value|Description|Multivalued| +|----|-------------|-----------|-----------| +|cc_categories|Style|Code Climate Categories|yes. Delimiter is '\|'.| +|cc_remediation_points_multiplier|1|Code Climate Remediation Points multiplier|no| +|cc_block_highlighting|false|Code Climate Block Highlighting|no| **Use this rule by referencing it:** ``` xml - + ``` ## ApexSOQLInjection @@ -288,15 +289,15 @@ public class Foo { **This rule has the following properties:** -|Name|Default Value|Description| -|----|-------------|-----------| -|cc_categories|[Style]|Code Climate Categories| -|cc_remediation_points_multiplier|1|Code Climate Remediation Points multiplier| -|cc_block_highlighting|false|Code Climate Block Highlighting| +|Name|Default Value|Description|Multivalued| +|----|-------------|-----------|-----------| +|cc_categories|Style|Code Climate Categories|yes. Delimiter is '\|'.| +|cc_remediation_points_multiplier|1|Code Climate Remediation Points multiplier|no| +|cc_block_highlighting|false|Code Climate Block Highlighting|no| **Use this rule by referencing it:** ``` xml - + ``` ## ApexSuggestUsingNamedCred @@ -335,15 +336,15 @@ public class Foo { **This rule has the following properties:** -|Name|Default Value|Description| -|----|-------------|-----------| -|cc_categories|[Style]|Code Climate Categories| -|cc_remediation_points_multiplier|1|Code Climate Remediation Points multiplier| -|cc_block_highlighting|false|Code Climate Block Highlighting| +|Name|Default Value|Description|Multivalued| +|----|-------------|-----------|-----------| +|cc_categories|Style|Code Climate Categories|yes. Delimiter is '\|'.| +|cc_remediation_points_multiplier|1|Code Climate Remediation Points multiplier|no| +|cc_block_highlighting|false|Code Climate Block Highlighting|no| **Use this rule by referencing it:** ``` xml - + ``` ## ApexXSSFromEscapeFalse @@ -368,15 +369,15 @@ public without sharing class Foo { **This rule has the following properties:** -|Name|Default Value|Description| -|----|-------------|-----------| -|cc_categories|[Style]|Code Climate Categories| -|cc_remediation_points_multiplier|1|Code Climate Remediation Points multiplier| -|cc_block_highlighting|false|Code Climate Block Highlighting| +|Name|Default Value|Description|Multivalued| +|----|-------------|-----------|-----------| +|cc_categories|Style|Code Climate Categories|yes. Delimiter is '\|'.| +|cc_remediation_points_multiplier|1|Code Climate Remediation Points multiplier|no| +|cc_block_highlighting|false|Code Climate Block Highlighting|no| **Use this rule by referencing it:** ``` xml - + ``` ## ApexXSSFromURLParam @@ -401,14 +402,14 @@ public without sharing class Foo { **This rule has the following properties:** -|Name|Default Value|Description| -|----|-------------|-----------| -|cc_categories|[Style]|Code Climate Categories| -|cc_remediation_points_multiplier|1|Code Climate Remediation Points multiplier| -|cc_block_highlighting|false|Code Climate Block Highlighting| +|Name|Default Value|Description|Multivalued| +|----|-------------|-----------|-----------| +|cc_categories|Style|Code Climate Categories|yes. Delimiter is '\|'.| +|cc_remediation_points_multiplier|1|Code Climate Remediation Points multiplier|no| +|cc_block_highlighting|false|Code Climate Block Highlighting|no| **Use this rule by referencing it:** ``` xml - + ``` diff --git a/docs/pages/pmd/rules/apex/style.md b/docs/pages/pmd/rules/apex/style.md deleted file mode 100644 index 660db734fd4..00000000000 --- a/docs/pages/pmd/rules/apex/style.md +++ /dev/null @@ -1,228 +0,0 @@ ---- -title: Style -summary: The Style Ruleset contains rules regarding preferred usage of names and identifiers. -permalink: pmd_rules_apex_style.html -folder: pmd/rules/apex -sidebaractiveurl: /pmd_rules_apex.html -editmepath: ../pmd-apex/src/main/resources/rulesets/apex/style.xml -keywords: Style, VariableNamingConventions, MethodNamingConventions, ClassNamingConventions, MethodWithSameNameAsEnclosingClass, AvoidLogicInTrigger, AvoidGlobalModifier ---- -## AvoidGlobalModifier - -**Since:** PMD 5.5.0 - -**Priority:** Medium (3) - -Global classes should be avoided (especially in managed packages) as they can never be deleted or changed in signature. Always check twice if something needs to be global. -Many interfaces (e.g. Batch) required global modifiers in the past but don't require this anymore. Don't lock yourself in. - -**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.apex.rule.style.AvoidGlobalModifierRule](https://github.com/pmd/pmd/blob/master/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/style/AvoidGlobalModifierRule.java) - -**Example(s):** - -``` java -global class Unchangeable { - global UndeletableType unchangable(UndeletableType param) { - // ... - } -} -``` - -**This rule has the following properties:** - -|Name|Default Value|Description| -|----|-------------|-----------| -|cc_categories|[Style]|Code Climate Categories| -|cc_remediation_points_multiplier|1|Code Climate Remediation Points multiplier| -|cc_block_highlighting|false|Code Climate Block Highlighting| - -**Use this rule by referencing it:** -``` xml - -``` - -## AvoidLogicInTrigger - -**Since:** PMD 5.5.0 - -**Priority:** Medium (3) - -As triggers do not allow methods like regular classes they are less flexible and suited to apply good encapsulation style. -Therefore delegate the triggers work to a regular class (often called Trigger handler class). - -See more here: https://developer.salesforce.com/page/Trigger_Frameworks_and_Apex_Trigger_Best_Practices - -**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.apex.rule.style.AvoidLogicInTriggerRule](https://github.com/pmd/pmd/blob/master/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/style/AvoidLogicInTriggerRule.java) - -**Example(s):** - -``` java -trigger Accounts on Account (before insert, before update, before delete, after insert, after update, after delete, after undelete) { - for(Account acc : Trigger.new) { - if(Trigger.isInsert) { - // ... - } - - // ... - - if(Trigger.isDelete) { - // ... - } - } -} -``` - -**This rule has the following properties:** - -|Name|Default Value|Description| -|----|-------------|-----------| -|cc_categories|[Style]|Code Climate Categories| -|cc_remediation_points_multiplier|1|Code Climate Remediation Points multiplier| -|cc_block_highlighting|false|Code Climate Block Highlighting| - -**Use this rule by referencing it:** -``` xml - -``` - -## ClassNamingConventions - -**Since:** PMD 5.5.0 - -**Priority:** High (1) - -Class names should always begin with an upper case character. - -**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.apex.rule.style.ClassNamingConventionsRule](https://github.com/pmd/pmd/blob/master/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/style/ClassNamingConventionsRule.java) - -**Example(s):** - -``` java -public class Foo {} -``` - -**This rule has the following properties:** - -|Name|Default Value|Description| -|----|-------------|-----------| -|cc_categories|[Style]|Code Climate Categories| -|cc_remediation_points_multiplier|1|Code Climate Remediation Points multiplier| -|cc_block_highlighting|false|Code Climate Block Highlighting| - -**Use this rule by referencing it:** -``` xml - -``` - -## MethodNamingConventions - -**Since:** PMD 5.5.0 - -**Priority:** High (1) - -Method names should always begin with a lower case character, and should not contain underscores. - -**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.apex.rule.style.MethodNamingConventionsRule](https://github.com/pmd/pmd/blob/master/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/style/MethodNamingConventionsRule.java) - -**Example(s):** - -``` java -public class Foo { - public void fooStuff() { - } -} -``` - -**This rule has the following properties:** - -|Name|Default Value|Description| -|----|-------------|-----------| -|cc_categories|[Style]|Code Climate Categories| -|cc_remediation_points_multiplier|1|Code Climate Remediation Points multiplier| -|cc_block_highlighting|false|Code Climate Block Highlighting| - -**Use this rule by referencing it:** -``` xml - -``` - -## MethodWithSameNameAsEnclosingClass - -**Since:** PMD 5.5.0 - -**Priority:** Medium (3) - -Non-constructor methods should not have the same name as the enclosing class. - -**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.apex.rule.style.MethodWithSameNameAsEnclosingClassRule](https://github.com/pmd/pmd/blob/master/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/style/MethodWithSameNameAsEnclosingClassRule.java) - -**Example(s):** - -``` java -public class MyClass { - // this is OK because it is a constructor - public MyClass() {} - // this is bad because it is a method - public void MyClass() {} -} -``` - -**This rule has the following properties:** - -|Name|Default Value|Description| -|----|-------------|-----------| -|cc_categories|[Style]|Code Climate Categories| -|cc_remediation_points_multiplier|1|Code Climate Remediation Points multiplier| -|cc_block_highlighting|false|Code Climate Block Highlighting| - -**Use this rule by referencing it:** -``` xml - -``` - -## VariableNamingConventions - -**Since:** PMD 5.5.0 - -**Priority:** High (1) - -A variable naming conventions rule - customize this to your liking. Currently, it -checks for final variables that should be fully capitalized and non-final variables -that should not include underscores. - -**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.apex.rule.style.VariableNamingConventionsRule](https://github.com/pmd/pmd/blob/master/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/style/VariableNamingConventionsRule.java) - -**Example(s):** - -``` java -public class Foo { - public static final Integer MY_NUM = 0; - public String myTest = ''; - DataModule dmTest = new DataModule(); -} -``` - -**This rule has the following properties:** - -|Name|Default Value|Description| -|----|-------------|-----------| -|parameterSuffix|[]|Method parameter variable suffixes| -|parameterPrefix|[]|Method parameter variable prefixes| -|localSuffix|[]|Local variable suffixes| -|localPrefix|[]|Local variable prefixes| -|memberSuffix|[]|Member variable suffixes| -|memberPrefix|[]|Member variable prefixes| -|staticSuffix|[]|Static variable suffixes| -|staticPrefix|[]|Static variable prefixes| -|checkParameters|true|Check constructor and method parameter variables| -|checkLocals|true|Check local variables| -|cc_categories|[Style]|Code Climate Categories| -|cc_remediation_points_multiplier|1|Code Climate Remediation Points multiplier| -|cc_block_highlighting|false|Code Climate Block Highlighting| -|checkMembers|true|Check member variables| - -**Use this rule by referencing it:** -``` xml - -``` - diff --git a/docs/pages/pmd/rules/ecmascript.md b/docs/pages/pmd/rules/ecmascript.md index 84bbba4703c..6e500d0d9a6 100644 --- a/docs/pages/pmd/rules/ecmascript.md +++ b/docs/pages/pmd/rules/ecmascript.md @@ -1,37 +1,75 @@ --- title: Ecmascript Rules +tags: [rule_references, ecmascript] +summary: Index of all built-in rules available for Ecmascript +language_name: Ecmascript permalink: pmd_rules_ecmascript.html folder: pmd/rules --- -List of rulesets and rules contained in each ruleset. - -* [Basic Ecmascript](pmd_rules_ecmascript_basic.html): Rules concerning basic ECMAScript guidelines. -* [Braces](pmd_rules_ecmascript_braces.html): The Braces Ruleset contains a collection of braces rules. -* [Controversial Ecmascript](pmd_rules_ecmascript_controversial.html): The Controversial ruleset contains rules that, for whatever reason, are considered controversial. They are held here to allow people to include them as they see fit within their custom rulesets. -* [Unnecessary](pmd_rules_ecmascript_unnecessary.html): The Unnecessary Ruleset contains a collection of rules for unnecessary code. - -## Basic Ecmascript -* [AssignmentInOperand](pmd_rules_ecmascript_basic.html#assignmentinoperand): Avoid assignments in operands; this can make code more complicated and harder to read. This is s... -* [AvoidTrailingComma](pmd_rules_ecmascript_basic.html#avoidtrailingcomma): This rule helps improve code portability due to differences in browser treatment of trailing comm... -* [ConsistentReturn](pmd_rules_ecmascript_basic.html#consistentreturn): ECMAScript does provide for return types on functions, and therefore there is no solid rule as to... -* [EqualComparison](pmd_rules_ecmascript_basic.html#equalcomparison): Using == in condition may lead to unexpected results, as the variables are automatically casted t... -* [GlobalVariable](pmd_rules_ecmascript_basic.html#globalvariable): This rule helps to avoid using accidently global variables by simply missing the "var" declaratio... -* [InnaccurateNumericLiteral](pmd_rules_ecmascript_basic.html#innaccuratenumericliteral): The numeric literal will have at different value at runtime, which can happen if you provide too ... -* [ScopeForInVariable](pmd_rules_ecmascript_basic.html#scopeforinvariable): A for-in loop in which the variable name is not explicitly scoped to the enclosing scope with the... -* [UnreachableCode](pmd_rules_ecmascript_basic.html#unreachablecode): A 'return', 'break', 'continue', or 'throw' statement should be the last in a block. Statements a... -* [UseBaseWithParseInt](pmd_rules_ecmascript_basic.html#usebasewithparseint): This rule checks for usages of parseInt. While the second parameter is optional and usually defau... - -## Braces -* [ForLoopsMustUseBraces](pmd_rules_ecmascript_braces.html#forloopsmustusebraces): Avoid using 'for' statements without using curly braces. -* [IfElseStmtsMustUseBraces](pmd_rules_ecmascript_braces.html#ifelsestmtsmustusebraces): Avoid using if..else statements without using curly braces. -* [IfStmtsMustUseBraces](pmd_rules_ecmascript_braces.html#ifstmtsmustusebraces): Avoid using if statements without using curly braces. -* [WhileLoopsMustUseBraces](pmd_rules_ecmascript_braces.html#whileloopsmustusebraces): Avoid using 'while' statements without using curly braces. - -## Controversial Ecmascript -* [AvoidWithStatement](pmd_rules_ecmascript_controversial.html#avoidwithstatement): Avoid using with - it's bad news - -## Unnecessary -* [NoElseReturn](pmd_rules_ecmascript_unnecessary.html#noelsereturn): The else block in a if-else-construct is unnecessary if the 'if' block contains a return.Then the... -* [UnnecessaryBlock](pmd_rules_ecmascript_unnecessary.html#unnecessaryblock): An unnecessary Block is present. Such Blocks are often used in other languages tointroduce a new... -* [UnnecessaryParentheses](pmd_rules_ecmascript_unnecessary.html#unnecessaryparentheses): Unnecessary parentheses should be removed. +## Best Practices + +{% include callout.html content="Rules which enforce generally accepted best practices." %} + +* [AvoidWithStatement](pmd_rules_ecmascript_bestpractices.html#avoidwithstatement): Avoid using with - it's bad news +* [ConsistentReturn](pmd_rules_ecmascript_bestpractices.html#consistentreturn): ECMAScript does provide for return types on functions, and therefore there is no solid rule as to... +* [GlobalVariable](pmd_rules_ecmascript_bestpractices.html#globalvariable): This rule helps to avoid using accidently global variables by simply missing the "var" declaratio... +* [ScopeForInVariable](pmd_rules_ecmascript_bestpractices.html#scopeforinvariable): A for-in loop in which the variable name is not explicitly scoped to the enclosing scope with the... +* [UseBaseWithParseInt](pmd_rules_ecmascript_bestpractices.html#usebasewithparseint): This rule checks for usages of parseInt. While the second parameter is optional and usually defau... + +## Code Style + +{% include callout.html content="Rules which enforce a specific coding style." %} + +* [AssignmentInOperand](pmd_rules_ecmascript_codestyle.html#assignmentinoperand): Avoid assignments in operands; this can make code more complicated and harder to read. This is s... +* [ForLoopsMustUseBraces](pmd_rules_ecmascript_codestyle.html#forloopsmustusebraces): Avoid using 'for' statements without using curly braces. +* [IfElseStmtsMustUseBraces](pmd_rules_ecmascript_codestyle.html#ifelsestmtsmustusebraces): Avoid using if..else statements without using curly braces. +* [IfStmtsMustUseBraces](pmd_rules_ecmascript_codestyle.html#ifstmtsmustusebraces): Avoid using if statements without using curly braces. +* [NoElseReturn](pmd_rules_ecmascript_codestyle.html#noelsereturn): The else block in a if-else-construct is unnecessary if the 'if' block contains a return.Then the... +* [UnnecessaryBlock](pmd_rules_ecmascript_codestyle.html#unnecessaryblock): An unnecessary Block is present. Such Blocks are often used in other languages tointroduce a new... +* [UnnecessaryParentheses](pmd_rules_ecmascript_codestyle.html#unnecessaryparentheses): Unnecessary parentheses should be removed. +* [UnreachableCode](pmd_rules_ecmascript_codestyle.html#unreachablecode): A 'return', 'break', 'continue', or 'throw' statement should be the last in a block. Statements a... +* [WhileLoopsMustUseBraces](pmd_rules_ecmascript_codestyle.html#whileloopsmustusebraces): Avoid using 'while' statements without using curly braces. + +## Error Prone + +{% include callout.html content="Rules to detect constructs that are either broken, extremely confusing or prone to runtime errors." %} + +* [AvoidTrailingComma](pmd_rules_ecmascript_errorprone.html#avoidtrailingcomma): This rule helps improve code portability due to differences in browser treatment of trailing comm... +* [EqualComparison](pmd_rules_ecmascript_errorprone.html#equalcomparison): Using == in condition may lead to unexpected results, as the variables are automatically casted t... +* [InnaccurateNumericLiteral](pmd_rules_ecmascript_errorprone.html#innaccuratenumericliteral): The numeric literal will have a different value at runtime, which can happen if you provide too m... + +## Additional rulesets + +* Basic Ecmascript (`rulesets/ecmascript/basic.xml`): + + Deprecated This ruleset is for backwards compatibility. + + It contains the following rules: + + [AssignmentInOperand](pmd_rules_ecmascript_codestyle.html#assignmentinoperand), [AvoidTrailingComma](pmd_rules_ecmascript_errorprone.html#avoidtrailingcomma), [ConsistentReturn](pmd_rules_ecmascript_bestpractices.html#consistentreturn), [EqualComparison](pmd_rules_ecmascript_errorprone.html#equalcomparison), [GlobalVariable](pmd_rules_ecmascript_bestpractices.html#globalvariable), [InnaccurateNumericLiteral](pmd_rules_ecmascript_errorprone.html#innaccuratenumericliteral), [ScopeForInVariable](pmd_rules_ecmascript_bestpractices.html#scopeforinvariable), [UnreachableCode](pmd_rules_ecmascript_codestyle.html#unreachablecode), [UseBaseWithParseInt](pmd_rules_ecmascript_bestpractices.html#usebasewithparseint) + +* Braces (`rulesets/ecmascript/braces.xml`): + + Deprecated This ruleset is for backwards compatibility. + + It contains the following rules: + + [ForLoopsMustUseBraces](pmd_rules_ecmascript_codestyle.html#forloopsmustusebraces), [IfElseStmtsMustUseBraces](pmd_rules_ecmascript_codestyle.html#ifelsestmtsmustusebraces), [IfStmtsMustUseBraces](pmd_rules_ecmascript_codestyle.html#ifstmtsmustusebraces), [WhileLoopsMustUseBraces](pmd_rules_ecmascript_codestyle.html#whileloopsmustusebraces) + +* Controversial Ecmascript (`rulesets/ecmascript/controversial.xml`): + + Deprecated This ruleset is for backwards compatibility. + + It contains the following rules: + + [AvoidWithStatement](pmd_rules_ecmascript_bestpractices.html#avoidwithstatement) + +* Unnecessary (`rulesets/ecmascript/unnecessary.xml`): + + Deprecated This ruleset is for backwards compatibility. + + It contains the following rules: + + [NoElseReturn](pmd_rules_ecmascript_codestyle.html#noelsereturn), [UnnecessaryBlock](pmd_rules_ecmascript_codestyle.html#unnecessaryblock), [UnnecessaryParentheses](pmd_rules_ecmascript_codestyle.html#unnecessaryparentheses) + diff --git a/docs/pages/pmd/rules/ecmascript/basic.md b/docs/pages/pmd/rules/ecmascript/basic.md deleted file mode 100644 index 9663b6c9d0a..00000000000 --- a/docs/pages/pmd/rules/ecmascript/basic.md +++ /dev/null @@ -1,378 +0,0 @@ ---- -title: Basic Ecmascript -summary: Rules concerning basic ECMAScript guidelines. -permalink: pmd_rules_ecmascript_basic.html -folder: pmd/rules/ecmascript -sidebaractiveurl: /pmd_rules_ecmascript.html -editmepath: ../pmd-javascript/src/main/resources/rulesets/ecmascript/basic.xml -keywords: Basic Ecmascript, AssignmentInOperand, UnreachableCode, InnaccurateNumericLiteral, ConsistentReturn, ScopeForInVariable, EqualComparison, GlobalVariable, AvoidTrailingComma, UseBaseWithParseInt ---- -## AssignmentInOperand - -**Since:** PMD 5.0 - -**Priority:** Medium High (2) - -Avoid assignments in operands; this can make code more complicated and harder to read. This is sometime -indicative of the bug where the assignment operator '=' was used instead of the equality operator '=='. - -``` -//IfStatement[$allowIf = "false"]/child::node()[1]/descendant-or-self::node()[self::Assignment or self::UnaryExpression[$allowIncrementDecrement = "false" and (@Image = "--" or @Image = "++")]] -| - //WhileLoop[$allowWhile = "false"]/child::node()[1]/descendant-or-self::node()[self::Assignment or self::UnaryExpression[$allowIncrementDecrement = "false" and (@Image = "--" or @Image = "++")]] -| - //DoLoop[$allowWhile = "false"]/child::node()[2]/descendant-or-self::node()[self::Assignment or self::UnaryExpression[$allowIncrementDecrement = "false" and (@Image = "--" or @Image = "++")]] -| - //ForLoop[$allowFor = "false"]/child::node()[2]/descendant-or-self::node()[self::Assignment or self::UnaryExpression[$allowIncrementDecrement = "false" and (@Image = "--" or @Image = "++")]] -| - //ConditionalExpression[$allowTernary = "false"]/child::node()[1]/descendant-or-self::node()[self::Assignment or self::UnaryExpression[$allowIncrementDecrement = "false" and (@Image = "--" or @Image = "++")]] -| - //ConditionalExpression[$allowTernaryResults = "false"]/child::node()[position() = 2 or position() = 3]/descendant-or-self::node()[self::Assignment or self::UnaryExpression[$allowIncrementDecrement = "false" and (@Image = "--" or @Image = "++")]] -``` - -**Example(s):** - -``` javascript -var x = 2; -// Bad -if ((x = getX()) == 3) { - alert('3!'); -} - -function getX() { - return 3; -} -``` - -**This rule has the following properties:** - -|Name|Default Value|Description| -|----|-------------|-----------| -|allowIf|false|Allow assignment within the conditional expression of an if statement| -|allowFor|false|Allow assignment within the conditional expression of a for statement| -|allowWhile|false|Allow assignment within the conditional expression of a while statement| -|allowTernary|false|Allow assignment within the conditional expression of a ternary operator| -|allowTernaryResults|false|Allow assignment within the result expressions of a ternary operator| -|allowIncrementDecrement|false|Allow increment or decrement operators within the conditional expression of an if, for, or while statement| - -**Use this rule by referencing it:** -``` xml - -``` - -## AvoidTrailingComma - -**Since:** PMD 5.1 - -**Priority:** High (1) - -This rule helps improve code portability due to differences in browser treatment of trailing commas in object or array literals. - -``` -//ObjectLiteral[$allowObjectLiteral = "false" and @TrailingComma = 'true'] -| -//ArrayLiteral[$allowArrayLiteral = "false" and @TrailingComma = 'true'] -``` - -**Example(s):** - -``` javascript -function(arg) { - var obj1 = { a : 1 }; // Ok - var arr1 = [ 1, 2 ]; // Ok - - var obj2 = { a : 1, }; // Syntax error in some browsers! - var arr2 = [ 1, 2, ]; // Length 2 or 3 depending on the browser! -} -``` - -**This rule has the following properties:** - -|Name|Default Value|Description| -|----|-------------|-----------| -|allowObjectLiteral|false|Allow a trailing comma within an object literal| -|allowArrayLiteral|false|Allow a trailing comma within an array literal| - -**Use this rule by referencing it:** -``` xml - -``` - -## ConsistentReturn - -**Since:** PMD 5.0 - -**Priority:** Medium High (2) - -ECMAScript does provide for return types on functions, and therefore there is no solid rule as to their usage. -However, when a function does use returns they should all have a value, or all with no value. Mixed return -usage is likely a bug, or at best poor style. - -**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.ecmascript.rule.basic.ConsistentReturnRule](https://github.com/pmd/pmd/blob/master/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/rule/basic/ConsistentReturnRule.java) - -**Example(s):** - -``` javascript -// Ok -function foo() { - if (condition1) { - return true; - } - return false; -} - -// Bad -function bar() { - if (condition1) { - return; - } - return false; -} -``` - -**This rule has the following properties:** - -|Name|Default Value|Description| -|----|-------------|-----------| -|rhinoLanguageVersion|VERSION_DEFAULT|Specifies the Rhino Language Version to use for parsing. Defaults to Rhino default.| -|recordingLocalJsDocComments|true|Specifies that JsDoc comments are produced in the AST.| -|recordingComments|true|Specifies that comments are produced in the AST.| - -**Use this rule by referencing it:** -``` xml - -``` - -## EqualComparison - -**Since:** PMD 5.0 - -**Priority:** Medium (3) - -Using == in condition may lead to unexpected results, as the variables are automatically casted to be of the -same type. The === operator avoids the casting. - -``` -//InfixExpression[(@Image = "==" or @Image = "!=") - and - (child::KeywordLiteral[@Image = "true" or @Image = "false"] - or - child::NumberLiteral) -] -``` - -**Example(s):** - -``` javascript -// Ok -if (someVar === true) { - ... -} -// Ok -if (someVar !== 3) { - ... -} -// Bad -if (someVar == true) { - ... -} -// Bad -if (someVar != 3) { - ... -} -``` - -**Use this rule by referencing it:** -``` xml - -``` - -## GlobalVariable - -**Since:** PMD 5.0 - -**Priority:** High (1) - -This rule helps to avoid using accidently global variables by simply missing the "var" declaration. -Global variables can lead to side-effects that are hard to debug. - -``` -//Assignment[Name/@GlobalName = 'true'] -``` - -**Example(s):** - -``` javascript -function(arg) { - notDeclaredVariable = 1; // this will create a global variable and trigger the rule - - var someVar = 1; // this is a local variable, that's ok - - window.otherGlobal = 2; // this will not trigger the rule, although it is a global variable. -} -``` - -**Use this rule by referencing it:** -``` xml - -``` - -## InnaccurateNumericLiteral - -**Since:** PMD 5.0 - -**Priority:** Medium High (2) - -The numeric literal will have at different value at runtime, which can happen if you provide too much -precision in a floating point number. This may result in numeric calculations being in error. - -``` -//NumberLiteral[ - @Image != @Number - and translate(@Image, "e", "E") != @Number - and concat(@Image, ".0") != @Number - and @Image != substring-before(translate(@Number, ".", ""), "E")] -``` - -**Example(s):** - -``` javascript -var a = 9; // Ok -var b = 999999999999999; // Ok -var c = 999999999999999999999; // Not good -var w = 1.12e-4; // Ok -var x = 1.12; // Ok -var y = 1.1234567890123; // Ok -var z = 1.12345678901234567; // Not good -``` - -**Use this rule by referencing it:** -``` xml - -``` - -## ScopeForInVariable - -**Since:** PMD 5.0 - -**Priority:** High (1) - -A for-in loop in which the variable name is not explicitly scoped to the enclosing scope with the 'var' keyword can -refer to a variable in an enclosing scope outside the nearest enclosing scope. This will overwrite the -existing value of the variable in the outer scope when the body of the for-in is evaluated. When the for-in loop -has finished, the variable will contain the last value used in the for-in, and the original value from before -the for-in loop will be gone. Since the for-in variable name is most likely intended to be a temporary name, it -is better to explicitly scope the variable name to the nearest enclosing scope with 'var'. - -``` -//ForInLoop[not(child::VariableDeclaration)]/Name[1] -``` - -**Example(s):** - -``` javascript -// Ok -function foo() { - var p = 'clean'; - function() { - var obj = { dirty: 'dirty' }; - for (var p in obj) { // Use 'var' here. - obj[p] = obj[p]; - } - return x; - }(); - - // 'p' still has value of 'clean'. -} -// Bad -function bar() { - var p = 'clean'; - function() { - var obj = { dirty: 'dirty' }; - for (p in obj) { // Oh no, missing 'var' here! - obj[p] = obj[p]; - } - return x; - }(); - - // 'p' is trashed and has value of 'dirty'! -} -``` - -**Use this rule by referencing it:** -``` xml - -``` - -## UnreachableCode - -**Since:** PMD 5.0 - -**Priority:** High (1) - -A 'return', 'break', 'continue', or 'throw' statement should be the last in a block. Statements after these -will never execute. This is a bug, or extremely poor style. - -``` -//ReturnStatement[following-sibling::node()] -| - //ContinueStatement[following-sibling::node()] -| - //BreakStatement[following-sibling::node()] -| - //ThrowStatement[following-sibling::node()] -``` - -**Example(s):** - -``` javascript -// Ok -function foo() { - return 1; -} -// Bad -function bar() { - var x = 1; - return x; - x = 2; -} -``` - -**Use this rule by referencing it:** -``` xml - -``` - -## UseBaseWithParseInt - -**Since:** PMD 5.0.1 - -**Priority:** High (1) - -This rule checks for usages of parseInt. While the second parameter is optional and usually defaults -to 10 (base/radix is 10 for a decimal number), different implementations may behave differently. -It also improves readability, if the base is given. - -See also: [parseInt()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/parseInt) - -``` -//FunctionCall/Name[ - @Image = 'parseInt' - and - count(../*) < 3 -] -``` - -**Example(s):** - -``` javascript -parseInt("010"); // unclear, could be interpreted as 10 or 7 (with a base of 7) - -parseInt("10", 10); // good -``` - -**Use this rule by referencing it:** -``` xml - -``` - diff --git a/docs/pages/pmd/rules/ecmascript/bestpractices.md b/docs/pages/pmd/rules/ecmascript/bestpractices.md new file mode 100644 index 00000000000..ef55bc95344 --- /dev/null +++ b/docs/pages/pmd/rules/ecmascript/bestpractices.md @@ -0,0 +1,200 @@ +--- +title: Best Practices +summary: Rules which enforce generally accepted best practices. +permalink: pmd_rules_ecmascript_bestpractices.html +folder: pmd/rules/ecmascript +sidebaractiveurl: /pmd_rules_ecmascript.html +editmepath: ../pmd-javascript/src/main/resources/category/ecmascript/bestpractices.xml +keywords: Best Practices, AvoidWithStatement, ConsistentReturn, GlobalVariable, ScopeForInVariable, UseBaseWithParseInt +language: Ecmascript +--- +## AvoidWithStatement + +**Since:** PMD 5.0.1 + +**Priority:** High (1) + +Avoid using with - it's bad news + +**This rule is defined by the following XPath expression:** +``` xpath +//WithStatement +``` + +**Example(s):** + +``` javascript +with (object) { + property = 3; // Might be on object, might be on window: who knows. +} +``` + +**Use this rule by referencing it:** +``` xml + +``` + +## ConsistentReturn + +**Since:** PMD 5.0 + +**Priority:** Medium High (2) + +ECMAScript does provide for return types on functions, and therefore there is no solid rule as to their usage. +However, when a function does use returns they should all have a value, or all with no value. Mixed return +usage is likely a bug, or at best poor style. + +**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.ecmascript.rule.bestpractices.ConsistentReturnRule](https://github.com/pmd/pmd/blob/master/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/rule/bestpractices/ConsistentReturnRule.java) + +**Example(s):** + +``` javascript +// Ok +function foo() { + if (condition1) { + return true; + } + return false; +} + +// Bad +function bar() { + if (condition1) { + return; + } + return false; +} +``` + +**This rule has the following properties:** + +|Name|Default Value|Description|Multivalued| +|----|-------------|-----------|-----------| +|rhinoLanguageVersion|VERSION\_DEFAULT|Specifies the Rhino Language Version to use for parsing. Defaults to Rhino default.|no| +|recordingLocalJsDocComments|true|Specifies that JsDoc comments are produced in the AST.|no| +|recordingComments|true|Specifies that comments are produced in the AST.|no| + +**Use this rule by referencing it:** +``` xml + +``` + +## GlobalVariable + +**Since:** PMD 5.0 + +**Priority:** High (1) + +This rule helps to avoid using accidently global variables by simply missing the "var" declaration. +Global variables can lead to side-effects that are hard to debug. + +**This rule is defined by the following XPath expression:** +``` xpath +//Assignment[Name/@GlobalName = 'true'] +``` + +**Example(s):** + +``` javascript +function(arg) { + notDeclaredVariable = 1; // this will create a global variable and trigger the rule + + var someVar = 1; // this is a local variable, that's ok + + window.otherGlobal = 2; // this will not trigger the rule, although it is a global variable. +} +``` + +**Use this rule by referencing it:** +``` xml + +``` + +## ScopeForInVariable + +**Since:** PMD 5.0 + +**Priority:** High (1) + +A for-in loop in which the variable name is not explicitly scoped to the enclosing scope with the 'var' keyword can +refer to a variable in an enclosing scope outside the nearest enclosing scope. This will overwrite the +existing value of the variable in the outer scope when the body of the for-in is evaluated. When the for-in loop +has finished, the variable will contain the last value used in the for-in, and the original value from before +the for-in loop will be gone. Since the for-in variable name is most likely intended to be a temporary name, it +is better to explicitly scope the variable name to the nearest enclosing scope with 'var'. + +**This rule is defined by the following XPath expression:** +``` xpath +//ForInLoop[not(child::VariableDeclaration)]/Name[1] +``` + +**Example(s):** + +``` javascript +// Ok +function foo() { + var p = 'clean'; + function() { + var obj = { dirty: 'dirty' }; + for (var p in obj) { // Use 'var' here. + obj[p] = obj[p]; + } + return x; + }(); + + // 'p' still has value of 'clean'. +} +// Bad +function bar() { + var p = 'clean'; + function() { + var obj = { dirty: 'dirty' }; + for (p in obj) { // Oh no, missing 'var' here! + obj[p] = obj[p]; + } + return x; + }(); + + // 'p' is trashed and has value of 'dirty'! +} +``` + +**Use this rule by referencing it:** +``` xml + +``` + +## UseBaseWithParseInt + +**Since:** PMD 5.0.1 + +**Priority:** High (1) + +This rule checks for usages of parseInt. While the second parameter is optional and usually defaults +to 10 (base/radix is 10 for a decimal number), different implementations may behave differently. +It also improves readability, if the base is given. + +See also: [parseInt()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/parseInt) + +**This rule is defined by the following XPath expression:** +``` xpath +//FunctionCall/Name[ + @Image = 'parseInt' + and + count(../*) < 3 +] +``` + +**Example(s):** + +``` javascript +parseInt("010"); // unclear, could be interpreted as 10 or 7 (with a base of 7) + +parseInt("10", 10); // good +``` + +**Use this rule by referencing it:** +``` xml + +``` + diff --git a/docs/pages/pmd/rules/ecmascript/braces.md b/docs/pages/pmd/rules/ecmascript/braces.md deleted file mode 100644 index 5f67d6dcf73..00000000000 --- a/docs/pages/pmd/rules/ecmascript/braces.md +++ /dev/null @@ -1,137 +0,0 @@ ---- -title: Braces -summary: The Braces Ruleset contains a collection of braces rules. -permalink: pmd_rules_ecmascript_braces.html -folder: pmd/rules/ecmascript -sidebaractiveurl: /pmd_rules_ecmascript.html -editmepath: ../pmd-javascript/src/main/resources/rulesets/ecmascript/braces.xml -keywords: Braces, IfStmtsMustUseBraces, IfElseStmtsMustUseBraces, WhileLoopsMustUseBraces, ForLoopsMustUseBraces ---- -## ForLoopsMustUseBraces - -**Since:** PMD 5.0 - -**Priority:** Medium (3) - -Avoid using 'for' statements without using curly braces. - -``` -//ForLoop[not(child::Scope)] -| -//ForInLoop[not(child::Scope)] -``` - -**Example(s):** - -``` javascript -// Ok -for (var i = 0; i < 42; i++) { - foo(); -} - -// Bad -for (var i = 0; i < 42; i++) - foo(); -``` - -**Use this rule by referencing it:** -``` xml - -``` - -## IfElseStmtsMustUseBraces - -**Since:** PMD 5.0 - -**Priority:** Medium (3) - -Avoid using if..else statements without using curly braces. - -``` -//ExpressionStatement[parent::IfStatement[@Else = "true"]] - [not(child::Scope)] - [not(child::IfStatement)] -``` - -**Example(s):** - -``` javascript -// Ok -if (foo) { - x++; -} else { - y++; -} - -// Bad -if (foo) - x++; -else - y++; -``` - -**Use this rule by referencing it:** -``` xml - -``` - -## IfStmtsMustUseBraces - -**Since:** PMD 5.0 - -**Priority:** Medium (3) - -Avoid using if statements without using curly braces. - -``` -//IfStatement[@Else = "false" and not(child::Scope)] -``` - -**Example(s):** - -``` javascript -// Ok -if (foo) { - x++; -} - -// Bad -if (foo) - x++; -``` - -**Use this rule by referencing it:** -``` xml - -``` - -## WhileLoopsMustUseBraces - -**Since:** PMD 5.0 - -**Priority:** Medium (3) - -Avoid using 'while' statements without using curly braces. - -``` -//WhileLoop[not(child::Scope)] -``` - -**Example(s):** - -``` javascript -// Ok -while (true) { - x++; -} - -// Bad -while (true) - x++; -``` - -**Use this rule by referencing it:** -``` xml - -``` - diff --git a/docs/pages/pmd/rules/ecmascript/codestyle.md b/docs/pages/pmd/rules/ecmascript/codestyle.md new file mode 100644 index 00000000000..a1892c4b9ff --- /dev/null +++ b/docs/pages/pmd/rules/ecmascript/codestyle.md @@ -0,0 +1,337 @@ +--- +title: Code Style +summary: Rules which enforce a specific coding style. +permalink: pmd_rules_ecmascript_codestyle.html +folder: pmd/rules/ecmascript +sidebaractiveurl: /pmd_rules_ecmascript.html +editmepath: ../pmd-javascript/src/main/resources/category/ecmascript/codestyle.xml +keywords: Code Style, AssignmentInOperand, ForLoopsMustUseBraces, IfElseStmtsMustUseBraces, IfStmtsMustUseBraces, NoElseReturn, UnnecessaryBlock, UnnecessaryParentheses, UnreachableCode, WhileLoopsMustUseBraces +language: Ecmascript +--- +## AssignmentInOperand + +**Since:** PMD 5.0 + +**Priority:** Medium High (2) + +Avoid assignments in operands; this can make code more complicated and harder to read. This is sometime +indicative of the bug where the assignment operator '=' was used instead of the equality operator '=='. + +**This rule is defined by the following XPath expression:** +``` xpath +//IfStatement[$allowIf = "false"]/child::node()[1]/descendant-or-self::node()[self::Assignment or self::UnaryExpression[$allowIncrementDecrement = "false" and (@Image = "--" or @Image = "++")]] +| + //WhileLoop[$allowWhile = "false"]/child::node()[1]/descendant-or-self::node()[self::Assignment or self::UnaryExpression[$allowIncrementDecrement = "false" and (@Image = "--" or @Image = "++")]] +| + //DoLoop[$allowWhile = "false"]/child::node()[2]/descendant-or-self::node()[self::Assignment or self::UnaryExpression[$allowIncrementDecrement = "false" and (@Image = "--" or @Image = "++")]] +| + //ForLoop[$allowFor = "false"]/child::node()[2]/descendant-or-self::node()[self::Assignment or self::UnaryExpression[$allowIncrementDecrement = "false" and (@Image = "--" or @Image = "++")]] +| + //ConditionalExpression[$allowTernary = "false"]/child::node()[1]/descendant-or-self::node()[self::Assignment or self::UnaryExpression[$allowIncrementDecrement = "false" and (@Image = "--" or @Image = "++")]] +| + //ConditionalExpression[$allowTernaryResults = "false"]/child::node()[position() = 2 or position() = 3]/descendant-or-self::node()[self::Assignment or self::UnaryExpression[$allowIncrementDecrement = "false" and (@Image = "--" or @Image = "++")]] +``` + +**Example(s):** + +``` javascript +var x = 2; +// Bad +if ((x = getX()) == 3) { + alert('3!'); +} + +function getX() { + return 3; +} +``` + +**This rule has the following properties:** + +|Name|Default Value|Description|Multivalued| +|----|-------------|-----------|-----------| +|allowIf|false|Allow assignment within the conditional expression of an if statement|no| +|allowFor|false|Allow assignment within the conditional expression of a for statement|no| +|allowWhile|false|Allow assignment within the conditional expression of a while statement|no| +|allowTernary|false|Allow assignment within the conditional expression of a ternary operator|no| +|allowTernaryResults|false|Allow assignment within the result expressions of a ternary operator|no| +|allowIncrementDecrement|false|Allow increment or decrement operators within the conditional expression of an if, for, or while statement|no| + +**Use this rule by referencing it:** +``` xml + +``` + +## ForLoopsMustUseBraces + +**Since:** PMD 5.0 + +**Priority:** Medium (3) + +Avoid using 'for' statements without using curly braces. + +**This rule is defined by the following XPath expression:** +``` xpath +//ForLoop[not(child::Scope)] +| +//ForInLoop[not(child::Scope)] +``` + +**Example(s):** + +``` javascript +// Ok +for (var i = 0; i < 42; i++) { + foo(); +} + +// Bad +for (var i = 0; i < 42; i++) + foo(); +``` + +**Use this rule by referencing it:** +``` xml + +``` + +## IfElseStmtsMustUseBraces + +**Since:** PMD 5.0 + +**Priority:** Medium (3) + +Avoid using if..else statements without using curly braces. + +**This rule is defined by the following XPath expression:** +``` xpath +//ExpressionStatement[parent::IfStatement[@Else = "true"]] + [not(child::Scope)] + [not(child::IfStatement)] +``` + +**Example(s):** + +``` javascript +// Ok +if (foo) { + x++; +} else { + y++; +} + +// Bad +if (foo) + x++; +else + y++; +``` + +**Use this rule by referencing it:** +``` xml + +``` + +## IfStmtsMustUseBraces + +**Since:** PMD 5.0 + +**Priority:** Medium (3) + +Avoid using if statements without using curly braces. + +**This rule is defined by the following XPath expression:** +``` xpath +//IfStatement[@Else = "false" and not(child::Scope)] +``` + +**Example(s):** + +``` javascript +// Ok +if (foo) { + x++; +} + +// Bad +if (foo) + x++; +``` + +**Use this rule by referencing it:** +``` xml + +``` + +## NoElseReturn + +**Since:** PMD 5.5.0 + +**Priority:** Medium (3) + +The else block in a if-else-construct is unnecessary if the `if` block contains a return. +Then the content of the else block can be put outside. + +See also: + +**This rule is defined by the following XPath expression:** +``` xpath +//IfStatement[@Else="true"][Scope[1]/ReturnStatement] +``` + +**Example(s):** + +``` javascript +// Bad: +if (x) { + return y; +} else { + return z; +} + +// Good: +if (x) { + return y; +} +return z; +``` + +**Use this rule by referencing it:** +``` xml + +``` + +## UnnecessaryBlock + +**Since:** PMD 5.0 + +**Priority:** Medium (3) + +An unnecessary Block is present. Such Blocks are often used in other languages to +introduce a new variable scope. Blocks do not behave like this in ECMAScipt, and using them can +be misleading. Considering removing this unnecessary Block. + +**This rule is defined by the following XPath expression:** +``` xpath +//Block[not(parent::FunctionNode or parent::IfStatement or parent::ForLoop or parent::ForInLoop + or parent::WhileLoop or parent::DoLoop or parent::TryStatement or parent::CatchClause)] +| +//Scope[not(parent::FunctionNode or parent::IfStatement or parent::ForLoop or parent::ForInLoop + or parent::WhileLoop or parent::DoLoop or parent::TryStatement or parent::CatchClause)] +``` + +**Example(s):** + +``` javascript +if (foo) { + // Ok +} +if (bar) { + { + // Bad + } +} +``` + +**Use this rule by referencing it:** +``` xml + +``` + +## UnnecessaryParentheses + +**Since:** PMD 5.0 + +**Priority:** Medium Low (4) + +Unnecessary parentheses should be removed. + +**This rule is defined by the following XPath expression:** +``` xpath +//ParenthesizedExpression/ParenthesizedExpression +``` + +**Example(s):** + +``` javascript +var x = 1; // Ok +var y = (1 + 1); // Ok +var z = ((1 + 1)); // Bad +``` + +**Use this rule by referencing it:** +``` xml + +``` + +## UnreachableCode + +**Since:** PMD 5.0 + +**Priority:** High (1) + +A 'return', 'break', 'continue', or 'throw' statement should be the last in a block. Statements after these +will never execute. This is a bug, or extremely poor style. + +**This rule is defined by the following XPath expression:** +``` xpath +//ReturnStatement[following-sibling::node()] +| + //ContinueStatement[following-sibling::node()] +| + //BreakStatement[following-sibling::node()] +| + //ThrowStatement[following-sibling::node()] +``` + +**Example(s):** + +``` javascript +// Ok +function foo() { + return 1; +} +// Bad +function bar() { + var x = 1; + return x; + x = 2; +} +``` + +**Use this rule by referencing it:** +``` xml + +``` + +## WhileLoopsMustUseBraces + +**Since:** PMD 5.0 + +**Priority:** Medium (3) + +Avoid using 'while' statements without using curly braces. + +**This rule is defined by the following XPath expression:** +``` xpath +//WhileLoop[not(child::Scope)] +``` + +**Example(s):** + +``` javascript +// Ok +while (true) { + x++; +} + +// Bad +while (true) + x++; +``` + +**Use this rule by referencing it:** +``` xml + +``` + diff --git a/docs/pages/pmd/rules/ecmascript/controversial.md b/docs/pages/pmd/rules/ecmascript/controversial.md deleted file mode 100644 index b73ea9d35b5..00000000000 --- a/docs/pages/pmd/rules/ecmascript/controversial.md +++ /dev/null @@ -1,34 +0,0 @@ ---- -title: Controversial Ecmascript -summary: The Controversial ruleset contains rules that, for whatever reason, are considered controversial. They are held here to allow people to include them as they see fit within their custom rulesets. -permalink: pmd_rules_ecmascript_controversial.html -folder: pmd/rules/ecmascript -sidebaractiveurl: /pmd_rules_ecmascript.html -editmepath: ../pmd-javascript/src/main/resources/rulesets/ecmascript/controversial.xml -keywords: Controversial Ecmascript, AvoidWithStatement ---- -## AvoidWithStatement - -**Since:** PMD 5.0.1 - -**Priority:** High (1) - -Avoid using with - it's bad news - -``` -//WithStatement -``` - -**Example(s):** - -``` javascript -with (object) { - property = 3; // Might be on object, might be on window: who knows. -} -``` - -**Use this rule by referencing it:** -``` xml - -``` - diff --git a/docs/pages/pmd/rules/ecmascript/errorprone.md b/docs/pages/pmd/rules/ecmascript/errorprone.md new file mode 100644 index 00000000000..2fdf7e27025 --- /dev/null +++ b/docs/pages/pmd/rules/ecmascript/errorprone.md @@ -0,0 +1,125 @@ +--- +title: Error Prone +summary: Rules to detect constructs that are either broken, extremely confusing or prone to runtime errors. +permalink: pmd_rules_ecmascript_errorprone.html +folder: pmd/rules/ecmascript +sidebaractiveurl: /pmd_rules_ecmascript.html +editmepath: ../pmd-javascript/src/main/resources/category/ecmascript/errorprone.xml +keywords: Error Prone, AvoidTrailingComma, EqualComparison, InnaccurateNumericLiteral +language: Ecmascript +--- +## AvoidTrailingComma + +**Since:** PMD 5.1 + +**Priority:** High (1) + +This rule helps improve code portability due to differences in browser treatment of trailing commas in object or array literals. + +**This rule is defined by the following XPath expression:** +``` xpath +//ObjectLiteral[$allowObjectLiteral = "false" and @TrailingComma = 'true'] +| +//ArrayLiteral[$allowArrayLiteral = "false" and @TrailingComma = 'true'] +``` + +**Example(s):** + +``` javascript +function(arg) { + var obj1 = { a : 1 }; // Ok + var arr1 = [ 1, 2 ]; // Ok + + var obj2 = { a : 1, }; // Syntax error in some browsers! + var arr2 = [ 1, 2, ]; // Length 2 or 3 depending on the browser! +} +``` + +**This rule has the following properties:** + +|Name|Default Value|Description|Multivalued| +|----|-------------|-----------|-----------| +|allowObjectLiteral|false|Allow a trailing comma within an object literal|no| +|allowArrayLiteral|false|Allow a trailing comma within an array literal|no| + +**Use this rule by referencing it:** +``` xml + +``` + +## EqualComparison + +**Since:** PMD 5.0 + +**Priority:** Medium (3) + +Using == in condition may lead to unexpected results, as the variables are automatically casted to be of the +same type. The === operator avoids the casting. + +**This rule is defined by the following XPath expression:** +``` xpath +//InfixExpression[(@Image = "==" or @Image = "!=") + and + (child::KeywordLiteral[@Image = "true" or @Image = "false"] + or + child::NumberLiteral) +] +``` + +**Example(s):** + +``` javascript +// Ok +if (someVar === true) { + ... +} +// Ok +if (someVar !== 3) { + ... +} +// Bad +if (someVar == true) { + ... +} +// Bad +if (someVar != 3) { + ... +} +``` + +**Use this rule by referencing it:** +``` xml + +``` + +## InnaccurateNumericLiteral + +**Since:** PMD 5.0 + +**Priority:** Medium High (2) + +The numeric literal will have a different value at runtime, which can happen if you provide too much +precision in a floating point number. This may result in numeric calculations being in error. + +**This rule is defined by the following XPath expression:** +``` xpath +//NumberLiteral[@NormalizedImage != @Number] +``` + +**Example(s):** + +``` javascript +var a = 9; // Ok +var b = 999999999999999; // Ok +var c = 999999999999999999999; // Not good +var w = 1.12e-4; // Ok +var x = 1.12; // Ok +var y = 1.1234567890123; // Ok +var z = 1.12345678901234567; // Not good +``` + +**Use this rule by referencing it:** +``` xml + +``` + diff --git a/docs/pages/pmd/rules/ecmascript/unnecessary.md b/docs/pages/pmd/rules/ecmascript/unnecessary.md deleted file mode 100644 index 6c5c0f5ec08..00000000000 --- a/docs/pages/pmd/rules/ecmascript/unnecessary.md +++ /dev/null @@ -1,107 +0,0 @@ ---- -title: Unnecessary -summary: The Unnecessary Ruleset contains a collection of rules for unnecessary code. -permalink: pmd_rules_ecmascript_unnecessary.html -folder: pmd/rules/ecmascript -sidebaractiveurl: /pmd_rules_ecmascript.html -editmepath: ../pmd-javascript/src/main/resources/rulesets/ecmascript/unnecessary.xml -keywords: Unnecessary, UnnecessaryParentheses, UnnecessaryBlock, NoElseReturn ---- -## NoElseReturn - -**Since:** PMD 5.5.0 - -**Priority:** Medium (3) - -The else block in a if-else-construct is unnecessary if the `if` block contains a return. -Then the content of the else block can be put outside. - -See also: - -``` -//IfStatement[@Else="true"][Scope[1]/ReturnStatement] -``` - -**Example(s):** - -``` javascript -// Bad: -if (x) { - return y; -} else { - return z; -} - -// Good: -if (x) { - return y; -} -return z; -``` - -**Use this rule by referencing it:** -``` xml - -``` - -## UnnecessaryBlock - -**Since:** PMD 5.0 - -**Priority:** Medium (3) - -An unnecessary Block is present. Such Blocks are often used in other languages to -introduce a new variable scope. Blocks do not behave like this in ECMAScipt, and using them can -be misleading. Considering removing this unnecessary Block. - -``` -//Block[not(parent::FunctionNode or parent::IfStatement or parent::ForLoop or parent::ForInLoop - or parent::WhileLoop or parent::DoLoop or parent::TryStatement or parent::CatchClause)] -| -//Scope[not(parent::FunctionNode or parent::IfStatement or parent::ForLoop or parent::ForInLoop - or parent::WhileLoop or parent::DoLoop or parent::TryStatement or parent::CatchClause)] -``` - -**Example(s):** - -``` javascript -if (foo) { - // Ok -} -if (bar) { - { - // Bad - } -} -``` - -**Use this rule by referencing it:** -``` xml - -``` - -## UnnecessaryParentheses - -**Since:** PMD 5.0 - -**Priority:** Medium Low (4) - -Unnecessary parentheses should be removed. - -``` -//ParenthesizedExpression/ParenthesizedExpression -``` - -**Example(s):** - -``` javascript -var x = 1; // Ok -var y = (1 + 1); // Ok -var z = ((1 + 1)); // Bad -``` - -**Use this rule by referencing it:** -``` xml - -``` - diff --git a/docs/pages/pmd/rules/java.md b/docs/pages/pmd/rules/java.md index 92c8d37e804..12b3ab615d6 100644 --- a/docs/pages/pmd/rules/java.md +++ b/docs/pages/pmd/rules/java.md @@ -1,367 +1,584 @@ --- title: Java Rules +tags: [rule_references, java] +summary: Index of all built-in rules available for Java +language_name: Java permalink: pmd_rules_java.html folder: pmd/rules --- -List of rulesets and rules contained in each ruleset. - -* [Android](pmd_rules_java_android.html): These rules deal with the Android SDK, mostly related to best practices. To get better results, make sure that the auxclasspath is defined for type resolution to work. -* [Basic](pmd_rules_java_basic.html): The Basic ruleset contains a collection of good practices which should be followed. -* [Braces](pmd_rules_java_braces.html): The Braces ruleset contains rules regarding the use and placement of braces. -* [Clone Implementation](pmd_rules_java_clone.html): The Clone Implementation ruleset contains a collection of rules that find questionable usages of the clone() method. -* [Code Size](pmd_rules_java_codesize.html): The Code Size ruleset contains rules that find problems related to code size or complexity. -* [Comments](pmd_rules_java_comments.html): Rules intended to catch errors related to code comments -* [Controversial](pmd_rules_java_controversial.html): The Controversial ruleset contains rules that, for whatever reason, are considered controversial. They are held here to allow people to include them as they see fit within their custom rulesets. -* [Coupling](pmd_rules_java_coupling.html): Rules which find instances of high or inappropriate coupling between objects and packages. -* [Design](pmd_rules_java_design.html): The Design ruleset contains rules that flag suboptimal code implementations. Alternate approaches are suggested. -* [Empty Code](pmd_rules_java_empty.html): The Empty Code ruleset contains rules that find empty statements of any kind (empty method, empty block statement, empty try or catch block,...). -* [Finalizer](pmd_rules_java_finalizers.html): These rules deal with different problems that can occur with finalizers. -* [Import Statements](pmd_rules_java_imports.html): These rules deal with different problems that can occur with import statements. -* [J2EE](pmd_rules_java_j2ee.html): Rules specific to the use of J2EE implementations. -* [Jakarta Commons Logging](pmd_rules_java_logging-jakarta-commons.html): The Jakarta Commons Logging ruleset contains a collection of rules that find questionable usages of that framework. -* [Java Logging](pmd_rules_java_logging-java.html): The Java Logging ruleset contains a collection of rules that find questionable usages of the logger. -* [JavaBeans](pmd_rules_java_javabeans.html): The JavaBeans Ruleset catches instances of bean rules not being followed. -* [JUnit](pmd_rules_java_junit.html): These rules deal with different problems that can occur with JUnit tests. -* [Migration](pmd_rules_java_migrating.html): Contains rules about migrating from one JDK version to another. Don't use these rules directly, rather, use a wrapper ruleset such as migrating_to_13.xml. -* [Naming](pmd_rules_java_naming.html): The Naming Ruleset contains rules regarding preferred usage of names and identifiers. -* [Optimization](pmd_rules_java_optimizations.html): These rules deal with different optimizations that generally apply to best practices. -* [Security Code Guidelines](pmd_rules_java_sunsecure.html): These rules check the security guidelines from Sun, published at http://java.sun.com/security/seccodeguide.html#gcg -* [Strict Exceptions](pmd_rules_java_strictexception.html): These rules provide some strict guidelines about throwing and catching exceptions. -* [String and StringBuffer](pmd_rules_java_strings.html): These rules deal with different issues that can arise with manipulation of the String, StringBuffer, or StringBuilder instances. -* [Unnecessary](pmd_rules_java_unnecessary.html): The Unnecessary Ruleset contains a collection of rules for unnecessary code. -* [Unused Code](pmd_rules_java_unusedcode.html): The Unused Code ruleset contains rules that find unused or ineffective code. - -## Android -* [CallSuperFirst](pmd_rules_java_android.html#callsuperfirst): Super should be called at the start of the method -* [CallSuperLast](pmd_rules_java_android.html#callsuperlast): Super should be called at the end of the method -* [DoNotHardCodeSDCard](pmd_rules_java_android.html#donothardcodesdcard): Use Environment.getExternalStorageDirectory() instead of "/sdcard" - -## Basic -* [AvoidBranchingStatementAsLastInLoop](pmd_rules_java_basic.html#avoidbranchingstatementaslastinloop): Using a branching statement as the last part of a loop may be a bug, and/or is confusing.Ensure t... -* [AvoidDecimalLiteralsInBigDecimalConstructor](pmd_rules_java_basic.html#avoiddecimalliteralsinbigdecimalconstructor): One might assume that the result of "new BigDecimal(0.1)" is exactly equal to 0.1, but it is actu... -* [AvoidMultipleUnaryOperators](pmd_rules_java_basic.html#avoidmultipleunaryoperators): The use of multiple unary operators may be problematic, and/or confusing.Ensure that the intended... -* [AvoidThreadGroup](pmd_rules_java_basic.html#avoidthreadgroup): Avoid using java.lang.ThreadGroup; although it is intended to be used in a threaded environmentit... -* [AvoidUsingHardCodedIP](pmd_rules_java_basic.html#avoidusinghardcodedip): Application with hard-coded IP addresses can become impossible to deploy in some cases.Externaliz... -* [AvoidUsingOctalValues](pmd_rules_java_basic.html#avoidusingoctalvalues): Integer literals should not start with zero since this denotes that the rest of literal will bein... -* [BigIntegerInstantiation](pmd_rules_java_basic.html#bigintegerinstantiation): Don't create instances of already existing BigInteger (BigInteger.ZERO, BigInteger.ONE) andfor Ja... -* [BooleanInstantiation](pmd_rules_java_basic.html#booleaninstantiation): Avoid instantiating Boolean objects; you can reference Boolean.TRUE, Boolean.FALSE, or call Boole... -* [BrokenNullCheck](pmd_rules_java_basic.html#brokennullcheck): The null check is broken since it will throw a NullPointerException itself.It is likely that you ... -* [CheckResultSet](pmd_rules_java_basic.html#checkresultset): Always check the return values of navigation methods (next, previous, first, last) of a ResultSet... -* [CheckSkipResult](pmd_rules_java_basic.html#checkskipresult): The skip() method may skip a smaller number of bytes than requested. Check the returned value to ... -* [ClassCastExceptionWithToArray](pmd_rules_java_basic.html#classcastexceptionwithtoarray): When deriving an array of a specific class from your Collection, one should provide an array ofth... -* [CollapsibleIfStatements](pmd_rules_java_basic.html#collapsibleifstatements): Sometimes two consecutive 'if' statements can be consolidated by separating their conditions with... -* [DontCallThreadRun](pmd_rules_java_basic.html#dontcallthreadrun): Explicitly calling Thread.run() method will execute in the caller's thread of control. Instead, ... -* [DontUseFloatTypeForLoopIndices](pmd_rules_java_basic.html#dontusefloattypeforloopindices): Don't use floating point for loop indices. If you must use floating point, use doubleunless you'r... -* [DoubleCheckedLocking](pmd_rules_java_basic.html#doublecheckedlocking): Partially created objects can be returned by the Double Checked Locking pattern when used in Java... -* [ExtendsObject](pmd_rules_java_basic.html#extendsobject): No need to explicitly extend Object. -* [ForLoopShouldBeWhileLoop](pmd_rules_java_basic.html#forloopshouldbewhileloop): Some for loops can be simplified to while loops, this makes them more concise. -* [JumbledIncrementer](pmd_rules_java_basic.html#jumbledincrementer): Avoid jumbled loop incrementers - its usually a mistake, and is confusing even if intentional. -* [MisplacedNullCheck](pmd_rules_java_basic.html#misplacednullcheck): The null check here is misplaced. If the variable is null a NullPointerException will be thrown.E... -* [OverrideBothEqualsAndHashcode](pmd_rules_java_basic.html#overridebothequalsandhashcode): Override both public boolean Object.equals(Object other), and public int Object.hashCode(), or ov... -* [ReturnFromFinallyBlock](pmd_rules_java_basic.html#returnfromfinallyblock): Avoid returning from a finally block, this can discard exceptions. -* [SimplifiedTernary](pmd_rules_java_basic.html#simplifiedternary): Look for ternary operators with the form 'condition ? literalBoolean : foo'or 'condition ? foo : ... -* [UnconditionalIfStatement](pmd_rules_java_basic.html#unconditionalifstatement): Do not use "if" statements whose conditionals are always true or always false. - -## Braces -* [ForLoopsMustUseBraces](pmd_rules_java_braces.html#forloopsmustusebraces): Avoid using 'for' statements without using curly braces. If the code formatting or indentation is... -* [IfElseStmtsMustUseBraces](pmd_rules_java_braces.html#ifelsestmtsmustusebraces): Avoid using if..else statements without using surrounding braces. If the code formatting or inden... -* [IfStmtsMustUseBraces](pmd_rules_java_braces.html#ifstmtsmustusebraces): Avoid using if statements without using braces to surround the code block. If the code formatting... -* [WhileLoopsMustUseBraces](pmd_rules_java_braces.html#whileloopsmustusebraces): Avoid using 'while' statements without using braces to surround the code block. If the code forma... - -## Clone Implementation -* [CloneMethodMustBePublic](pmd_rules_java_clone.html#clonemethodmustbepublic): The java Manual says "By convention, classes that implement this interface should overrideObject.... -* [CloneMethodMustImplementCloneable](pmd_rules_java_clone.html#clonemethodmustimplementcloneable): The method clone() should only be implemented if the class implements the Cloneable interface wit... -* [CloneMethodReturnTypeMustMatchClassName](pmd_rules_java_clone.html#clonemethodreturntypemustmatchclassname): If a class implements cloneable the return type of the method clone() must be the class name. Tha... -* [CloneThrowsCloneNotSupportedException](pmd_rules_java_clone.html#clonethrowsclonenotsupportedexception): The method clone() should throw a CloneNotSupportedException. -* [ProperCloneImplementation](pmd_rules_java_clone.html#propercloneimplementation): Object clone() should be implemented with super.clone(). - -## Code Size -* [CyclomaticComplexity](pmd_rules_java_codesize.html#cyclomaticcomplexity): Deprecated Complexity directly affects maintenance costs is determined by the number of decision points in a... -* [ExcessiveClassLength](pmd_rules_java_codesize.html#excessiveclasslength): Excessive class file lengths are usually indications that the class may be burdened with excessiv... -* [ExcessiveMethodLength](pmd_rules_java_codesize.html#excessivemethodlength): When methods are excessively long this usually indicates that the method is doing more than itsna... -* [ExcessiveParameterList](pmd_rules_java_codesize.html#excessiveparameterlist): Methods with numerous parameters are a challenge to maintain, especially if most of them share th... -* [ExcessivePublicCount](pmd_rules_java_codesize.html#excessivepubliccount): Classes with large numbers of public methods and attributes require disproportionate testing effo... -* [ModifiedCyclomaticComplexity](pmd_rules_java_codesize.html#modifiedcyclomaticcomplexity): Deprecated Complexity directly affects maintenance costs is determined by the number of decision points in a... -* [NcssConstructorCount](pmd_rules_java_codesize.html#ncssconstructorcount): Deprecated This rule uses the NCSS (Non-Commenting Source Statements) algorithm to determine the number of l... -* [NcssCount](pmd_rules_java_codesize.html#ncsscount): This rule uses the NCSS (Non-Commenting Source Statements) metric to determine the number of line... -* [NcssMethodCount](pmd_rules_java_codesize.html#ncssmethodcount): Deprecated This rule uses the NCSS (Non-Commenting Source Statements) algorithm to determine the number of l... -* [NcssTypeCount](pmd_rules_java_codesize.html#ncsstypecount): Deprecated This rule uses the NCSS (Non-Commenting Source Statements) algorithm to determine the number of l... -* [NPathComplexity](pmd_rules_java_codesize.html#npathcomplexity): The NPath complexity of a method is the number of acyclic execution paths through that method.A t... -* [StdCyclomaticComplexity](pmd_rules_java_codesize.html#stdcyclomaticcomplexity): Deprecated Complexity directly affects maintenance costs is determined by the number of decision points in a... -* [TooManyFields](pmd_rules_java_codesize.html#toomanyfields): Classes that have too many fields can become unwieldy and could be redesigned to have fewer field... -* [TooManyMethods](pmd_rules_java_codesize.html#toomanymethods): A class with too many methods is probably a good suspect for refactoring, in order to reduce itsc... - -## Comments -* [CommentContent](pmd_rules_java_comments.html#commentcontent): A rule for the politically correct... we don't want to offend anyone. -* [CommentDefaultAccessModifier](pmd_rules_java_comments.html#commentdefaultaccessmodifier): To avoid mistakes if we want that a Method, Constructor, Field or Nested class have a default acc... -* [CommentRequired](pmd_rules_java_comments.html#commentrequired): Denotes whether comments are required (or unwanted) for specific language elements. -* [CommentSize](pmd_rules_java_comments.html#commentsize): Determines whether the dimensions of non-header comments found are within the specified limits. - -## Controversial -* [AssignmentInOperand](pmd_rules_java_controversial.html#assignmentinoperand): Avoid assignments in operands; this can make code more complicated and harder to read. -* [AtLeastOneConstructor](pmd_rules_java_controversial.html#atleastoneconstructor): Each class should declare at least one constructor. -* [AvoidAccessibilityAlteration](pmd_rules_java_controversial.html#avoidaccessibilityalteration): Methods such as getDeclaredConstructors(), getDeclaredConstructor(Class[]) and setAccessible(),as... -* [AvoidFinalLocalVariable](pmd_rules_java_controversial.html#avoidfinallocalvariable): Avoid using final local variables, turn them into fields. -* [AvoidLiteralsInIfCondition](pmd_rules_java_controversial.html#avoidliteralsinifcondition): Avoid using hard-coded literals in conditional statements. By declaring them as static variableso... -* [AvoidPrefixingMethodParameters](pmd_rules_java_controversial.html#avoidprefixingmethodparameters): Prefixing parameters by 'in' or 'out' pollutes the name of the parameters and reduces code readab... -* [AvoidUsingNativeCode](pmd_rules_java_controversial.html#avoidusingnativecode): Unnecessary reliance on Java Native Interface (JNI) calls directly reduces application portabilit... -* [AvoidUsingShortType](pmd_rules_java_controversial.html#avoidusingshorttype): Java uses the 'short' type to reduce memory usage, not to optimize calculation. In fact, the JVM ... -* [AvoidUsingVolatile](pmd_rules_java_controversial.html#avoidusingvolatile): Use of the keyword 'volatile' is generally used to fine tune a Java application, and therefore, r... -* [CallSuperInConstructor](pmd_rules_java_controversial.html#callsuperinconstructor): It is a good practice to call super() in a constructor. If super() is not called butanother const... -* [DataflowAnomalyAnalysis](pmd_rules_java_controversial.html#dataflowanomalyanalysis): The dataflow analysis tracks local definitions, undefinitions and references to variables on diff... -* [DefaultPackage](pmd_rules_java_controversial.html#defaultpackage): Use explicit scoping instead of accidental usage of default package private level.The rule allows... -* [DoNotCallGarbageCollectionExplicitly](pmd_rules_java_controversial.html#donotcallgarbagecollectionexplicitly): Calls to System.gc(), Runtime.getRuntime().gc(), and System.runFinalization() are not advised. Co... -* [DontImportSun](pmd_rules_java_controversial.html#dontimportsun): Avoid importing anything from the 'sun.' packages. These packages are not portable and are likel... -* [NullAssignment](pmd_rules_java_controversial.html#nullassignment): Assigning a "null" to a variable (outside of its declaration) is usually bad form. Sometimes, th... -* [OneDeclarationPerLine](pmd_rules_java_controversial.html#onedeclarationperline): Java allows the use of several variables declaration of the same type on one line. However, itcan... -* [OnlyOneReturn](pmd_rules_java_controversial.html#onlyonereturn): A method should have only one exit point, and that should be the last statement in the method. -* [SuspiciousOctalEscape](pmd_rules_java_controversial.html#suspiciousoctalescape): A suspicious octal escape sequence was found inside a String literal.The Java language specificat... -* [UnnecessaryConstructor](pmd_rules_java_controversial.html#unnecessaryconstructor): This rule detects when a constructor is not necessary; i.e., when there is only one constructor,i... -* [UnnecessaryParentheses](pmd_rules_java_controversial.html#unnecessaryparentheses): Deprecated The rule has been moved to another ruleset. Use instead [UselessParentheses](pmd_rules_java_unnecessary.html#uselessparentheses). -* [UseConcurrentHashMap](pmd_rules_java_controversial.html#useconcurrenthashmap): Since Java5 brought a new implementation of the Map designed for multi-threaded access, you canpe... -* [UseObjectForClearerAPI](pmd_rules_java_controversial.html#useobjectforclearerapi): When you write a public method, you should be thinking in terms of an API. If your method is publ... - -## Coupling -* [CouplingBetweenObjects](pmd_rules_java_coupling.html#couplingbetweenobjects): This rule counts the number of unique attributes, local variables, and return types within an obj... -* [ExcessiveImports](pmd_rules_java_coupling.html#excessiveimports): A high number of imports can indicate a high degree of coupling within an object. This rule count... -* [LawOfDemeter](pmd_rules_java_coupling.html#lawofdemeter): The Law of Demeter is a simple rule, that says "only talk to friends". It helps to reduce couplin... -* [LooseCoupling](pmd_rules_java_coupling.html#loosecoupling): The use of implementation types as object references limits your ability to use alternateimplemen... -* [LoosePackageCoupling](pmd_rules_java_coupling.html#loosepackagecoupling): Avoid using classes from the configured package hierarchy outside of the package hierarchy, excep... +## Best Practices + +{% include callout.html content="Rules which enforce generally accepted best practices." %} + +* [AbstractClassWithoutAbstractMethod](pmd_rules_java_bestpractices.html#abstractclasswithoutabstractmethod): The abstract class does not contain any abstract methods. An abstract class suggestsan incomplete... +* [AccessorClassGeneration](pmd_rules_java_bestpractices.html#accessorclassgeneration): Instantiation by way of private constructors from outside of the constructor's class often causes... +* [AccessorMethodGeneration](pmd_rules_java_bestpractices.html#accessormethodgeneration): When accessing a private field / method from another class, the Java compiler will generate a acc... +* [ArrayIsStoredDirectly](pmd_rules_java_bestpractices.html#arrayisstoreddirectly): Constructors and methods receiving arrays should clone objects and store the copy.This prevents f... +* [AvoidPrintStackTrace](pmd_rules_java_bestpractices.html#avoidprintstacktrace): Avoid printStackTrace(); use a logger call instead. +* [AvoidReassigningParameters](pmd_rules_java_bestpractices.html#avoidreassigningparameters): Reassigning values to incoming parameters is not recommended. Use temporary local variables inst... +* [AvoidStringBufferField](pmd_rules_java_bestpractices.html#avoidstringbufferfield): StringBuffers/StringBuilders can grow considerably, and so may become a source of memory leaksif ... +* [AvoidUsingHardCodedIP](pmd_rules_java_bestpractices.html#avoidusinghardcodedip): Application with hard-coded IP addresses can become impossible to deploy in some cases.Externaliz... +* [CheckResultSet](pmd_rules_java_bestpractices.html#checkresultset): Always check the return values of navigation methods (next, previous, first, last) of a ResultSet... +* [ConstantsInInterface](pmd_rules_java_bestpractices.html#constantsininterface): Avoid constants in interfaces. Interfaces should define types, constants are implementation detai... +* [DefaultLabelNotLastInSwitchStmt](pmd_rules_java_bestpractices.html#defaultlabelnotlastinswitchstmt): By convention, the default label should be the last label in a switch statement. +* [ForLoopCanBeForeach](pmd_rules_java_bestpractices.html#forloopcanbeforeach): Reports loops that can be safely replaced with the foreach syntax. The rule considers loops overl... +* [GuardLogStatement](pmd_rules_java_bestpractices.html#guardlogstatement): Whenever using a log level, one should check if the loglevel is actually enabled, orotherwise ski... +* [JUnit4SuitesShouldUseSuiteAnnotation](pmd_rules_java_bestpractices.html#junit4suitesshouldusesuiteannotation): In JUnit 3, test suites are indicated by the suite() method. In JUnit 4, suites are indicatedthro... +* [JUnit4TestShouldUseAfterAnnotation](pmd_rules_java_bestpractices.html#junit4testshoulduseafterannotation): In JUnit 3, the tearDown method was used to clean up all data entities required in running tests.... +* [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): 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... +* [MethodReturnsInternalArray](pmd_rules_java_bestpractices.html#methodreturnsinternalarray): Exposing internal arrays to the caller violates object encapsulation since elements can be remove... +* [MissingOverride](pmd_rules_java_bestpractices.html#missingoverride): Annotating overridden methods with @Override ensures at compile time that the method r... +* [OneDeclarationPerLine](pmd_rules_java_bestpractices.html#onedeclarationperline): Java allows the use of several variables declaration of the same type on one line. However, itcan... +* [PositionLiteralsFirstInCaseInsensitiveComparisons](pmd_rules_java_bestpractices.html#positionliteralsfirstincaseinsensitivecomparisons): Position literals first in comparisons, if the second argument is null then NullPointerExceptions... +* [PositionLiteralsFirstInComparisons](pmd_rules_java_bestpractices.html#positionliteralsfirstincomparisons): Position literals first in comparisons, if the second argument is null then NullPointerExceptions... +* [PreserveStackTrace](pmd_rules_java_bestpractices.html#preservestacktrace): Throwing a new exception from a catch block without passing the original exception into thenew ex... +* [ReplaceEnumerationWithIterator](pmd_rules_java_bestpractices.html#replaceenumerationwithiterator): Consider replacing Enumeration usages with the newer java.util.Iterator +* [ReplaceHashtableWithMap](pmd_rules_java_bestpractices.html#replacehashtablewithmap): Consider replacing Hashtable usage with the newer java.util.Map if thread safety is not required. +* [ReplaceVectorWithList](pmd_rules_java_bestpractices.html#replacevectorwithlist): Consider replacing Vector usages with the newer java.util.ArrayList if expensive thread-safe oper... +* [SwitchStmtsShouldHaveDefault](pmd_rules_java_bestpractices.html#switchstmtsshouldhavedefault): All switch statements should include a default option to catch any unspecified values. +* [SystemPrintln](pmd_rules_java_bestpractices.html#systemprintln): References to System.(out\|err).print are usually intended for debugging purposes and can remain ... +* [UnusedFormalParameter](pmd_rules_java_bestpractices.html#unusedformalparameter): Avoid passing parameters to methods or constructors without actually referencing them in the meth... +* [UnusedImports](pmd_rules_java_bestpractices.html#unusedimports): Avoid unused import statements to prevent unwanted dependencies.This rule will also find unused o... +* [UnusedLocalVariable](pmd_rules_java_bestpractices.html#unusedlocalvariable): Detects when a local variable is declared and/or assigned, but not used. +* [UnusedPrivateField](pmd_rules_java_bestpractices.html#unusedprivatefield): Detects when a private field is declared and/or assigned a value, but not used. +* [UnusedPrivateMethod](pmd_rules_java_bestpractices.html#unusedprivatemethod): Unused Private Method detects when a private method is declared but is unused. +* [UseAssertEqualsInsteadOfAssertTrue](pmd_rules_java_bestpractices.html#useassertequalsinsteadofasserttrue): This rule detects JUnit assertions in object equality. These assertions should be made by more sp... +* [UseAssertNullInsteadOfAssertTrue](pmd_rules_java_bestpractices.html#useassertnullinsteadofasserttrue): This rule detects JUnit assertions in object references equality. These assertions should be made... +* [UseAssertSameInsteadOfAssertTrue](pmd_rules_java_bestpractices.html#useassertsameinsteadofasserttrue): This rule detects JUnit assertions in object references equality. These assertions should be made... +* [UseAssertTrueInsteadOfAssertEquals](pmd_rules_java_bestpractices.html#useasserttrueinsteadofassertequals): When asserting a value is the same as a literal or Boxed boolean, use assertTrue/assertFalse, ins... +* [UseCollectionIsEmpty](pmd_rules_java_bestpractices.html#usecollectionisempty): The isEmpty() method on java.util.Collection is provided to determine if a collection has any ele... +* [UseVarargs](pmd_rules_java_bestpractices.html#usevarargs): Java 5 introduced the varargs parameter declaration for methods and constructors. This syntactic... + +## Code Style + +{% include callout.html content="Rules which enforce a specific coding style." %} + +* [AbstractNaming](pmd_rules_java_codestyle.html#abstractnaming): Deprecated Abstract classes should be named 'AbstractXXX'. +* [AtLeastOneConstructor](pmd_rules_java_codestyle.html#atleastoneconstructor): Each non-static class should declare at least one constructor.Classes with solely static members ... +* [AvoidDollarSigns](pmd_rules_java_codestyle.html#avoiddollarsigns): Avoid using dollar signs in variable/method/class/interface names. +* [AvoidFinalLocalVariable](pmd_rules_java_codestyle.html#avoidfinallocalvariable): Avoid using final local variables, turn them into fields. +* [AvoidPrefixingMethodParameters](pmd_rules_java_codestyle.html#avoidprefixingmethodparameters): Deprecated Prefixing parameters by 'in' or 'out' pollutes the name of the parameters and reduces code readab... +* [AvoidProtectedFieldInFinalClass](pmd_rules_java_codestyle.html#avoidprotectedfieldinfinalclass): Do not use protected fields in final classes since they cannot be subclassed.Clarify your intent ... +* [AvoidProtectedMethodInFinalClassNotExtending](pmd_rules_java_codestyle.html#avoidprotectedmethodinfinalclassnotextending): Do not use protected methods in most final classes since they cannot be subclassed. This shouldon... +* [AvoidUsingNativeCode](pmd_rules_java_codestyle.html#avoidusingnativecode): Unnecessary reliance on Java Native Interface (JNI) calls directly reduces application portabilit... +* [BooleanGetMethodName](pmd_rules_java_codestyle.html#booleangetmethodname): Methods that return boolean results should be named as predicate statements to denote this.I.e, '... +* [CallSuperInConstructor](pmd_rules_java_codestyle.html#callsuperinconstructor): It is a good practice to call super() in a constructor. If super() is not called butanother const... +* [ClassNamingConventions](pmd_rules_java_codestyle.html#classnamingconventions): Configurable naming conventions for type declarations. This rule reports type declarat... +* [CommentDefaultAccessModifier](pmd_rules_java_codestyle.html#commentdefaultaccessmodifier): To avoid mistakes if we want that a Method, Constructor, Field or Nested class have a default acc... +* [ConfusingTernary](pmd_rules_java_codestyle.html#confusingternary): Avoid negation within an "if" expression with an "else" clause. For example, rephrase:'if (x != ... +* [ControlStatementBraces](pmd_rules_java_codestyle.html#controlstatementbraces): Enforce a policy for braces on control statements. It is recommended to use braces on 'if ... els... +* [DefaultPackage](pmd_rules_java_codestyle.html#defaultpackage): Use explicit scoping instead of accidental usage of default package private level.The rule allows... +* [DontImportJavaLang](pmd_rules_java_codestyle.html#dontimportjavalang): Avoid importing anything from the package 'java.lang'. These classes are automatically imported ... +* [DuplicateImports](pmd_rules_java_codestyle.html#duplicateimports): Duplicate or overlapping import statements should be avoided. +* [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... +* [GenericsNaming](pmd_rules_java_codestyle.html#genericsnaming): Names for references to generic values should be limited to a single uppercase letter. +* [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. +* [LocalVariableNamingConventions](pmd_rules_java_codestyle.html#localvariablenamingconventions): Configurable naming conventions for local variable declarations and other locally-scoped ... +* [LongVariable](pmd_rules_java_codestyle.html#longvariable): Fields, formal arguments, or local variable names that are too long can make the code difficult t... +* [MDBAndSessionBeanNamingConvention](pmd_rules_java_codestyle.html#mdbandsessionbeannamingconvention): The EJB Specification states that any MessageDrivenBean or SessionBean should be suffixed by 'Bean'. +* [MethodArgumentCouldBeFinal](pmd_rules_java_codestyle.html#methodargumentcouldbefinal): A method argument that is never re-assigned within the method can be declared final. +* [MethodNamingConventions](pmd_rules_java_codestyle.html#methodnamingconventions): Configurable naming conventions for method declarations. This rule reports method decl... +* [MIsLeadingVariableName](pmd_rules_java_codestyle.html#misleadingvariablename): Deprecated Detects when a non-field has a name starting with 'm_'. This usually denotes a field and could b... +* [NoPackage](pmd_rules_java_codestyle.html#nopackage): Detects when a class or interface does not have a package definition. +* [OnlyOneReturn](pmd_rules_java_codestyle.html#onlyonereturn): A method should have only one exit point, and that should be the last statement in the method. +* [PackageCase](pmd_rules_java_codestyle.html#packagecase): Detects when a package definition contains uppercase characters. +* [PrematureDeclaration](pmd_rules_java_codestyle.html#prematuredeclaration): Checks for variables that are defined before they might be used. A reference is deemed to be prem... +* [RemoteInterfaceNamingConvention](pmd_rules_java_codestyle.html#remoteinterfacenamingconvention): Remote Interface of a Session EJB should not have a suffix. +* [RemoteSessionInterfaceNamingConvention](pmd_rules_java_codestyle.html#remotesessioninterfacenamingconvention): A Remote Home interface type of a Session EJB should be suffixed by 'Home'. +* [ShortClassName](pmd_rules_java_codestyle.html#shortclassname): Short Classnames with fewer than e.g. five characters are not recommended. +* [ShortMethodName](pmd_rules_java_codestyle.html#shortmethodname): Method names that are very short are not helpful to the reader. +* [ShortVariable](pmd_rules_java_codestyle.html#shortvariable): Fields, local variables, or parameter names that are very short are not helpful to the reader. +* [SuspiciousConstantFieldName](pmd_rules_java_codestyle.html#suspiciousconstantfieldname): Deprecated Field names using all uppercase characters - Sun's Java naming conventions indicating constants -... +* [TooManyStaticImports](pmd_rules_java_codestyle.html#toomanystaticimports): If you overuse the static import feature, it can make your program unreadable and unmaintainable,... +* [UnnecessaryAnnotationValueElement](pmd_rules_java_codestyle.html#unnecessaryannotationvalueelement): Avoid the use of value in annotations when it's the only element. +* [UnnecessaryConstructor](pmd_rules_java_codestyle.html#unnecessaryconstructor): This rule detects when a constructor is not necessary; i.e., when there is only one constructor a... +* [UnnecessaryFullyQualifiedName](pmd_rules_java_codestyle.html#unnecessaryfullyqualifiedname): Import statements allow the use of non-fully qualified names. The use of a fully qualified namew... +* [UnnecessaryLocalBeforeReturn](pmd_rules_java_codestyle.html#unnecessarylocalbeforereturn): Avoid the creation of unnecessary local variables +* [UnnecessaryModifier](pmd_rules_java_codestyle.html#unnecessarymodifier): Fields in interfaces and annotations are automatically 'public static final', and methods are 'pu... +* [UnnecessaryReturn](pmd_rules_java_codestyle.html#unnecessaryreturn): Avoid the use of unnecessary return statements. +* [UselessParentheses](pmd_rules_java_codestyle.html#uselessparentheses): Useless parentheses should be removed. +* [UselessQualifiedThis](pmd_rules_java_codestyle.html#uselessqualifiedthis): Reports qualified this usages in the same class. +* [UseUnderscoresInNumericLiterals](pmd_rules_java_codestyle.html#useunderscoresinnumericliterals): Since Java 1.7, numeric literals can use underscores to separate digits. This rule enforces that ... +* [VariableNamingConventions](pmd_rules_java_codestyle.html#variablenamingconventions): Deprecated A variable naming conventions rule - customize this to your liking. Currently, itchecks for fina... +* [WhileLoopsMustUseBraces](pmd_rules_java_codestyle.html#whileloopsmustusebraces): Deprecated Avoid using 'while' statements without using braces to surround the code block. If the code forma... ## Design -* [AbstractClassWithoutAbstractMethod](pmd_rules_java_design.html#abstractclasswithoutabstractmethod): The abstract class does not contain any abstract methods. An abstract class suggestsan incomplete... + +{% include callout.html content="Rules that help you discover design issues." %} + * [AbstractClassWithoutAnyMethod](pmd_rules_java_design.html#abstractclasswithoutanymethod): If an abstract class does not provides any methods, it may be acting as a simple data containerth... -* [AccessorClassGeneration](pmd_rules_java_design.html#accessorclassgeneration): Instantiation by way of private constructors from outside of the constructor's class often causes... -* [AccessorMethodGeneration](pmd_rules_java_design.html#accessormethodgeneration): When accessing a private field / method from another class, the Java compiler will generate a acc... -* [AssignmentToNonFinalStatic](pmd_rules_java_design.html#assignmenttononfinalstatic): Identifies a possible unsafe usage of a static field. +* [AvoidCatchingGenericException](pmd_rules_java_design.html#avoidcatchinggenericexception): Avoid catching generic exceptions such as NullPointerException, RuntimeException, Exception in tr... * [AvoidDeeplyNestedIfStmts](pmd_rules_java_design.html#avoiddeeplynestedifstmts): Avoid creating deeply nested if-then statements since they are harder to read and error-prone to ... -* [AvoidInstanceofChecksInCatchClause](pmd_rules_java_design.html#avoidinstanceofchecksincatchclause): Each caught exception type should be handled in its own catch clause. -* [AvoidProtectedFieldInFinalClass](pmd_rules_java_design.html#avoidprotectedfieldinfinalclass): Do not use protected fields in final classes since they cannot be subclassed.Clarify your intent ... -* [AvoidProtectedMethodInFinalClassNotExtending](pmd_rules_java_design.html#avoidprotectedmethodinfinalclassnotextending): Do not use protected methods in most final classes since they cannot be subclassed. This shouldon... -* [AvoidReassigningParameters](pmd_rules_java_design.html#avoidreassigningparameters): Reassigning values to incoming parameters is not recommended. Use temporary local variables inst... -* [AvoidSynchronizedAtMethodLevel](pmd_rules_java_design.html#avoidsynchronizedatmethodlevel): Method-level synchronization can cause problems when new code is added to the method.Block-level ... -* [BadComparison](pmd_rules_java_design.html#badcomparison): Avoid equality comparisons with Double.NaN. Due to the implicit lack of representationprecision w... +* [AvoidRethrowingException](pmd_rules_java_design.html#avoidrethrowingexception): Catch blocks that merely rethrow a caught exception only add to code size and runtime complexity. +* [AvoidThrowingNewInstanceOfSameException](pmd_rules_java_design.html#avoidthrowingnewinstanceofsameexception): Catch blocks that merely rethrow a caught exception wrapped inside a new instance of the same typ... +* [AvoidThrowingNullPointerException](pmd_rules_java_design.html#avoidthrowingnullpointerexception): Avoid throwing NullPointerExceptions manually. These are confusing because most people will assum... +* [AvoidThrowingRawExceptionTypes](pmd_rules_java_design.html#avoidthrowingrawexceptiontypes): Avoid throwing certain exception types. Rather than throw a raw RuntimeException, Throwable,Excep... * [ClassWithOnlyPrivateConstructorsShouldBeFinal](pmd_rules_java_design.html#classwithonlyprivateconstructorsshouldbefinal): A class with only private constructors should be final, unless the private constructoris invoked ... -* [CloseResource](pmd_rules_java_design.html#closeresource): Ensure that resources (like Connection, Statement, and ResultSet objects) are always closed after... -* [CompareObjectsWithEquals](pmd_rules_java_design.html#compareobjectswithequals): Use equals() to compare object references; avoid comparing them with ==. -* [ConfusingTernary](pmd_rules_java_design.html#confusingternary): Avoid negation within an "if" expression with an "else" clause. For example, rephrase:'if (x != ... -* [ConstantsInInterface](pmd_rules_java_design.html#constantsininterface): Avoid constants in interfaces. Interfaces should define types, constants are implementation detai... -* [ConstructorCallsOverridableMethod](pmd_rules_java_design.html#constructorcallsoverridablemethod): Calling overridable methods during construction poses a risk of invoking methods on an incomplete... -* [DefaultLabelNotLastInSwitchStmt](pmd_rules_java_design.html#defaultlabelnotlastinswitchstmt): By convention, the default label should be the last label in a switch statement. -* [EmptyMethodInAbstractClassShouldBeAbstract](pmd_rules_java_design.html#emptymethodinabstractclassshouldbeabstract): Empty or auto-generated methods in an abstract class should be tagged as abstract. This helps to ... -* [EqualsNull](pmd_rules_java_design.html#equalsnull): Tests for null should not use the equals() method. The '==' operator should be used instead. -* [FieldDeclarationsShouldBeAtStartOfClass](pmd_rules_java_design.html#fielddeclarationsshouldbeatstartofclass): Fields should be declared at the top of the class, before any method declarations, constructors, ... +* [CollapsibleIfStatements](pmd_rules_java_design.html#collapsibleifstatements): Sometimes two consecutive 'if' statements can be consolidated by separating their conditions with... +* [CouplingBetweenObjects](pmd_rules_java_design.html#couplingbetweenobjects): This rule counts the number of unique attributes, local variables, and return types within an obj... +* [CyclomaticComplexity](pmd_rules_java_design.html#cyclomaticcomplexity): The complexity of methods directly affects maintenance costs and readability. Concentrating too m... +* [DataClass](pmd_rules_java_design.html#dataclass): Data Classes are simple data holders, which reveal most of their state, andwithout complex functi... +* [DoNotExtendJavaLangError](pmd_rules_java_design.html#donotextendjavalangerror): Errors are system exceptions. Do not extend them. +* [ExceptionAsFlowControl](pmd_rules_java_design.html#exceptionasflowcontrol): Using Exceptions as form of flow control is not recommended as they obscure true exceptions when ... +* [ExcessiveClassLength](pmd_rules_java_design.html#excessiveclasslength): Excessive class file lengths are usually indications that the class may be burdened with excessiv... +* [ExcessiveImports](pmd_rules_java_design.html#excessiveimports): A high number of imports can indicate a high degree of coupling within an object. This rule count... +* [ExcessiveMethodLength](pmd_rules_java_design.html#excessivemethodlength): When methods are excessively long this usually indicates that the method is doing more than itsna... +* [ExcessiveParameterList](pmd_rules_java_design.html#excessiveparameterlist): Methods with numerous parameters are a challenge to maintain, especially if most of them share th... +* [ExcessivePublicCount](pmd_rules_java_design.html#excessivepubliccount): Classes with large numbers of public methods and attributes require disproportionate testing effo... * [FinalFieldCouldBeStatic](pmd_rules_java_design.html#finalfieldcouldbestatic): If a final field is assigned to a compile-time constant, it could be made static, thus saving ove... * [GodClass](pmd_rules_java_design.html#godclass): The God Class rule detects the God Class design flaw using metrics. God classes do too many thing... -* [IdempotentOperations](pmd_rules_java_design.html#idempotentoperations): Avoid idempotent operations - they have no effect. * [ImmutableField](pmd_rules_java_design.html#immutablefield): Identifies private fields whose values never change once they are initialized either in the decla... -* [InstantiationToGetClass](pmd_rules_java_design.html#instantiationtogetclass): Avoid instantiating an object just to call getClass() on it; use the .class public member instead. +* [LawOfDemeter](pmd_rules_java_design.html#lawofdemeter): The Law of Demeter is a simple rule, that says "only talk to friends". It helps to reduce couplin... * [LogicInversion](pmd_rules_java_design.html#logicinversion): Use opposite operator instead of negating the whole expression with a logic complement operator. -* [MissingBreakInSwitch](pmd_rules_java_design.html#missingbreakinswitch): Switch statements without break or return statements for each case optionmay indicate problematic... -* [MissingStaticMethodInNonInstantiatableClass](pmd_rules_java_design.html#missingstaticmethodinnoninstantiatableclass): A class that has private constructors and does not have any static methods or fields cannot be used. -* [NonCaseLabelInSwitchStatement](pmd_rules_java_design.html#noncaselabelinswitchstatement): A non-case label (e.g. a named break/continue label) was present in a switch statement.This legal... -* [NonStaticInitializer](pmd_rules_java_design.html#nonstaticinitializer): A non-static initializer block will be called any time a constructor is invoked (just prior toinv... -* [NonThreadSafeSingleton](pmd_rules_java_design.html#nonthreadsafesingleton): Non-thread safe singletons can result in bad state changes. Eliminatestatic singletons if possibl... -* [OptimizableToArrayCall](pmd_rules_java_design.html#optimizabletoarraycall): Calls to a collection's toArray() method should specify target arrays sized to match the size of ... -* [PositionLiteralsFirstInCaseInsensitiveComparisons](pmd_rules_java_design.html#positionliteralsfirstincaseinsensitivecomparisons): Position literals first in comparisons, if the second argument is null then NullPointerExceptions... -* [PositionLiteralsFirstInComparisons](pmd_rules_java_design.html#positionliteralsfirstincomparisons): Position literals first in comparisons, if the second argument is null then NullPointerExceptions... -* [PreserveStackTrace](pmd_rules_java_design.html#preservestacktrace): Throwing a new exception from a catch block without passing the original exception into thenew ex... -* [ReturnEmptyArrayRatherThanNull](pmd_rules_java_design.html#returnemptyarrayratherthannull): For any method that returns an array, it is a better to return an empty array rather than anull r... -* [SimpleDateFormatNeedsLocale](pmd_rules_java_design.html#simpledateformatneedslocale): Be sure to specify a Locale when creating SimpleDateFormat instances to ensure that locale-approp... +* [LoosePackageCoupling](pmd_rules_java_design.html#loosepackagecoupling): Avoid using classes from the configured package hierarchy outside of the package hierarchy, excep... +* [ModifiedCyclomaticComplexity](pmd_rules_java_design.html#modifiedcyclomaticcomplexity): Deprecated Complexity directly affects maintenance costs is determined by the number of decision points in a... +* [NcssConstructorCount](pmd_rules_java_design.html#ncssconstructorcount): Deprecated This rule uses the NCSS (Non-Commenting Source Statements) algorithm to determine the number of l... +* [NcssCount](pmd_rules_java_design.html#ncsscount): This rule uses the NCSS (Non-Commenting Source Statements) metric to determine the number of line... +* [NcssMethodCount](pmd_rules_java_design.html#ncssmethodcount): Deprecated This rule uses the NCSS (Non-Commenting Source Statements) algorithm to determine the number of l... +* [NcssTypeCount](pmd_rules_java_design.html#ncsstypecount): Deprecated This rule uses the NCSS (Non-Commenting Source Statements) algorithm to determine the number of l... +* [NPathComplexity](pmd_rules_java_design.html#npathcomplexity): The NPath complexity of a method is the number of acyclic execution paths through that method.Whi... +* [SignatureDeclareThrowsException](pmd_rules_java_design.html#signaturedeclarethrowsexception): A method/constructor shouldn't explicitly throw the generic java.lang.Exception, since itis uncle... +* [SimplifiedTernary](pmd_rules_java_design.html#simplifiedternary): Look for ternary operators with the form 'condition ? literalBoolean : foo'or 'condition ? foo : ... +* [SimplifyBooleanAssertion](pmd_rules_java_design.html#simplifybooleanassertion): Avoid negation in an assertTrue or assertFalse test.For example, rephrase: assertTrue(!expr);a... * [SimplifyBooleanExpressions](pmd_rules_java_design.html#simplifybooleanexpressions): Avoid unnecessary comparisons in boolean expressions, they serve no purpose and impacts readability. * [SimplifyBooleanReturns](pmd_rules_java_design.html#simplifybooleanreturns): Avoid unnecessary if-then-else statements when returning a boolean. The result ofthe conditional ... * [SimplifyConditional](pmd_rules_java_design.html#simplifyconditional): No need to check for null before an instanceof; the instanceof keyword returns false when given a... -* [SingleMethodSingleton](pmd_rules_java_design.html#singlemethodsingleton): Some classes contain overloaded getInstance. The problem with overloaded getInstance methodsis th... -* [SingletonClassReturningNewInstance](pmd_rules_java_design.html#singletonclassreturningnewinstance): Some classes contain overloaded getInstance. The problem with overloaded getInstance methodsis th... * [SingularField](pmd_rules_java_design.html#singularfield): Fields whose scopes are limited to just single methods do not rely on the containingobject to pro... +* [StdCyclomaticComplexity](pmd_rules_java_design.html#stdcyclomaticcomplexity): Deprecated Complexity directly affects maintenance costs is determined by the number of decision points in a... * [SwitchDensity](pmd_rules_java_design.html#switchdensity): A high ratio of statements to labels in a switch statement implies that the switch statementis ov... -* [SwitchStmtsShouldHaveDefault](pmd_rules_java_design.html#switchstmtsshouldhavedefault): All switch statements should include a default option to catch any unspecified values. -* [TooFewBranchesForASwitchStatement](pmd_rules_java_design.html#toofewbranchesforaswitchstatement): Switch statements are intended to be used to support complex branching behaviour. Using a switch ... -* [UncommentedEmptyConstructor](pmd_rules_java_design.html#uncommentedemptyconstructor): Uncommented Empty Constructor finds instances where a constructor does notcontain statements, but... -* [UncommentedEmptyMethodBody](pmd_rules_java_design.html#uncommentedemptymethodbody): Uncommented Empty Method Body finds instances where a method body does not containstatements, but... -* [UnnecessaryLocalBeforeReturn](pmd_rules_java_design.html#unnecessarylocalbeforereturn): Avoid the creation of unnecessary local variables -* [UnsynchronizedStaticDateFormatter](pmd_rules_java_design.html#unsynchronizedstaticdateformatter): SimpleDateFormat instances are not synchronized. Sun recommends using separate format instancesfo... -* [UseCollectionIsEmpty](pmd_rules_java_design.html#usecollectionisempty): The isEmpty() method on java.util.Collection is provided to determine if a collection has any ele... -* [UseLocaleWithCaseConversions](pmd_rules_java_design.html#uselocalewithcaseconversions): When doing String.toLowerCase()/toUpperCase() conversions, use Locales to avoids problems with la... -* [UseNotifyAllInsteadOfNotify](pmd_rules_java_design.html#usenotifyallinsteadofnotify): Thread.notify() awakens a thread monitoring the object. If more than one thread is monitoring, th... +* [TooManyFields](pmd_rules_java_design.html#toomanyfields): Classes that have too many fields can become unwieldy and could be redesigned to have fewer field... +* [TooManyMethods](pmd_rules_java_design.html#toomanymethods): A class with too many methods is probably a good suspect for refactoring, in order to reduce itsc... +* [UselessOverridingMethod](pmd_rules_java_design.html#uselessoverridingmethod): The overriding method merely calls the same method defined in a superclass. +* [UseObjectForClearerAPI](pmd_rules_java_design.html#useobjectforclearerapi): When you write a public method, you should be thinking in terms of an API. If your method is publ... * [UseUtilityClass](pmd_rules_java_design.html#useutilityclass): For classes that only have static methods, consider making them utility classes.Note that this do... -* [UseVarargs](pmd_rules_java_design.html#usevarargs): Java 5 introduced the varargs parameter declaration for methods and constructors. This syntactic... - -## Empty Code -* [EmptyCatchBlock](pmd_rules_java_empty.html#emptycatchblock): Empty Catch Block finds instances where an exception is caught, but nothing is done. In most cir... -* [EmptyFinallyBlock](pmd_rules_java_empty.html#emptyfinallyblock): Empty finally blocks serve no purpose and should be removed. -* [EmptyIfStmt](pmd_rules_java_empty.html#emptyifstmt): Empty If Statement finds instances where a condition is checked but nothing is done about it. -* [EmptyInitializer](pmd_rules_java_empty.html#emptyinitializer): Empty initializers serve no purpose and should be removed. -* [EmptyStatementBlock](pmd_rules_java_empty.html#emptystatementblock): Empty block statements serve no purpose and should be removed. -* [EmptyStatementNotInLoop](pmd_rules_java_empty.html#emptystatementnotinloop): An empty statement (or a semicolon by itself) that is not used as the sole body of a 'for' or 'wh... -* [EmptyStaticInitializer](pmd_rules_java_empty.html#emptystaticinitializer): An empty static initializer serve no purpose and should be removed. -* [EmptySwitchStatements](pmd_rules_java_empty.html#emptyswitchstatements): Empty switch statements serve no purpose and should be removed. -* [EmptySynchronizedBlock](pmd_rules_java_empty.html#emptysynchronizedblock): Empty synchronized blocks serve no purpose and should be removed. -* [EmptyTryBlock](pmd_rules_java_empty.html#emptytryblock): Avoid empty try blocks - what's the point? -* [EmptyWhileStmt](pmd_rules_java_empty.html#emptywhilestmt): Empty While Statement finds all instances where a while statement does nothing. If it is a timin... - -## Finalizer -* [AvoidCallingFinalize](pmd_rules_java_finalizers.html#avoidcallingfinalize): The method Object.finalize() is called by the garbage collector on an object when garbage collect... -* [EmptyFinalizer](pmd_rules_java_finalizers.html#emptyfinalizer): Empty finalize methods serve no purpose and should be removed. -* [FinalizeDoesNotCallSuperFinalize](pmd_rules_java_finalizers.html#finalizedoesnotcallsuperfinalize): If the finalize() is implemented, its last action should be to call super.finalize. -* [FinalizeOnlyCallsSuperFinalize](pmd_rules_java_finalizers.html#finalizeonlycallssuperfinalize): If the finalize() is implemented, it should do something besides just calling super.finalize(). -* [FinalizeOverloaded](pmd_rules_java_finalizers.html#finalizeoverloaded): Methods named finalize() should not have parameters. It is confusing and most likely an attempt ... -* [FinalizeShouldBeProtected](pmd_rules_java_finalizers.html#finalizeshouldbeprotected): When overriding the finalize(), the new method should be set as protected. If made public, other... - -## Import Statements -* [DontImportJavaLang](pmd_rules_java_imports.html#dontimportjavalang): Avoid importing anything from the package 'java.lang'. These classes are automatically imported ... -* [DuplicateImports](pmd_rules_java_imports.html#duplicateimports): Duplicate or overlapping import statements should be avoided. -* [ImportFromSamePackage](pmd_rules_java_imports.html#importfromsamepackage): There is no need to import a type that lives in the same package. -* [TooManyStaticImports](pmd_rules_java_imports.html#toomanystaticimports): If you overuse the static import feature, it can make your program unreadable and unmaintainable,... -* [UnnecessaryFullyQualifiedName](pmd_rules_java_imports.html#unnecessaryfullyqualifiedname): Import statements allow the use of non-fully qualified names. The use of a fully qualified namew... -* [UnusedImports](pmd_rules_java_imports.html#unusedimports): Avoid the use of unused import statements to prevent unwanted dependencies. - -## J2EE -* [DoNotCallSystemExit](pmd_rules_java_j2ee.html#donotcallsystemexit): Web applications should not call System.exit(), since only the web container or theapplication se... -* [DoNotUseThreads](pmd_rules_java_j2ee.html#donotusethreads): The J2EE specification explicitly forbids the use of threads. -* [LocalHomeNamingConvention](pmd_rules_java_j2ee.html#localhomenamingconvention): The Local Home interface of a Session EJB should be suffixed by 'LocalHome'. -* [LocalInterfaceSessionNamingConvention](pmd_rules_java_j2ee.html#localinterfacesessionnamingconvention): The Local Interface of a Session EJB should be suffixed by 'Local'. -* [MDBAndSessionBeanNamingConvention](pmd_rules_java_j2ee.html#mdbandsessionbeannamingconvention): The EJB Specification states that any MessageDrivenBean or SessionBean should be suffixed by 'Bean'. -* [RemoteInterfaceNamingConvention](pmd_rules_java_j2ee.html#remoteinterfacenamingconvention): Remote Interface of a Session EJB should not have a suffix. -* [RemoteSessionInterfaceNamingConvention](pmd_rules_java_j2ee.html#remotesessioninterfacenamingconvention): A Remote Home interface type of a Session EJB should be suffixed by 'Home'. -* [StaticEJBFieldShouldBeFinal](pmd_rules_java_j2ee.html#staticejbfieldshouldbefinal): According to the J2EE specification, an EJB should not have any static fieldswith write access. H... -* [UseProperClassLoader](pmd_rules_java_j2ee.html#useproperclassloader): In J2EE, the getClassLoader() method might not work as expected. Use Thread.currentThread().getCo... - -## Jakarta Commons Logging -* [GuardDebugLogging](pmd_rules_java_logging-jakarta-commons.html#guarddebuglogging): When log messages are composed by concatenating strings, the whole section should be guardedby a ... -* [GuardLogStatement](pmd_rules_java_logging-jakarta-commons.html#guardlogstatement): Whenever using a log level, one should check if the loglevel is actually enabled, orotherwise ski... -* [ProperLogger](pmd_rules_java_logging-jakarta-commons.html#properlogger): A logger should normally be defined private static final and be associated with the correct class... -* [UseCorrectExceptionLogging](pmd_rules_java_logging-jakarta-commons.html#usecorrectexceptionlogging): To make sure the full stacktrace is printed out, use the logging statement with two arguments: a ... - -## Java Logging -* [AvoidPrintStackTrace](pmd_rules_java_logging-java.html#avoidprintstacktrace): Avoid printStackTrace(); use a logger call instead. -* [GuardLogStatementJavaUtil](pmd_rules_java_logging-java.html#guardlogstatementjavautil): Whenever using a log level, one should check if the loglevel is actually enabled, orotherwise ski... -* [InvalidSlf4jMessageFormat](pmd_rules_java_logging-java.html#invalidslf4jmessageformat): Check for messages in slf4j loggers with non matching number of arguments and placeholders. -* [LoggerIsNotStaticFinal](pmd_rules_java_logging-java.html#loggerisnotstaticfinal): In most cases, the Logger reference can be declared as static and final. -* [MoreThanOneLogger](pmd_rules_java_logging-java.html#morethanonelogger): Normally only one logger is used in each class. -* [SystemPrintln](pmd_rules_java_logging-java.html#systemprintln): References to System.(out\|err).print are usually intended for debugging purposes and can remain ... - -## JavaBeans -* [BeanMembersShouldSerialize](pmd_rules_java_javabeans.html#beanmembersshouldserialize): If a class is a bean, or is referenced by a bean directly or indirectly it needs to be serializab... -* [MissingSerialVersionUID](pmd_rules_java_javabeans.html#missingserialversionuid): Serializable classes should provide a serialVersionUID field. - -## JUnit -* [JUnitAssertionsShouldIncludeMessage](pmd_rules_java_junit.html#junitassertionsshouldincludemessage): JUnit assertions should include an informative message - i.e., use the three-argument version of ... -* [JUnitSpelling](pmd_rules_java_junit.html#junitspelling): Some JUnit framework methods are easy to misspell. -* [JUnitStaticSuite](pmd_rules_java_junit.html#junitstaticsuite): The suite() method in a JUnit test needs to be both public and static. -* [JUnitTestContainsTooManyAsserts](pmd_rules_java_junit.html#junittestcontainstoomanyasserts): JUnit tests should not contain too many asserts. Many asserts are indicative of a complex test, ... -* [JUnitTestsShouldIncludeAssert](pmd_rules_java_junit.html#junittestsshouldincludeassert): JUnit tests should include at least one assertion. This makes the tests more robust, and using a... -* [SimplifyBooleanAssertion](pmd_rules_java_junit.html#simplifybooleanassertion): Avoid negation in an assertTrue or assertFalse test.For example, rephrase: assertTrue(!expr);a... -* [TestClassWithoutTestCases](pmd_rules_java_junit.html#testclasswithouttestcases): Test classes end with the suffix Test. Having a non-test class with that name is not a good pract... -* [UnnecessaryBooleanAssertion](pmd_rules_java_junit.html#unnecessarybooleanassertion): A JUnit test assertion with a boolean literal is unnecessary since it always will evaluate to the... -* [UseAssertEqualsInsteadOfAssertTrue](pmd_rules_java_junit.html#useassertequalsinsteadofasserttrue): This rule detects JUnit assertions in object equality. These assertions should be made by more sp... -* [UseAssertNullInsteadOfAssertTrue](pmd_rules_java_junit.html#useassertnullinsteadofasserttrue): This rule detects JUnit assertions in object references equality. These assertions should be made... -* [UseAssertSameInsteadOfAssertTrue](pmd_rules_java_junit.html#useassertsameinsteadofasserttrue): This rule detects JUnit assertions in object references equality. These assertions should be made... -* [UseAssertTrueInsteadOfAssertEquals](pmd_rules_java_junit.html#useasserttrueinsteadofassertequals): When asserting a value is the same as a literal or Boxed boolean, use assertTrue/assertFalse, ins... - -## Migration -* [AvoidAssertAsIdentifier](pmd_rules_java_migrating.html#avoidassertasidentifier): Use of the term 'assert' will conflict with newer versions of Java since it is a reserved word. -* [AvoidEnumAsIdentifier](pmd_rules_java_migrating.html#avoidenumasidentifier): Use of the term 'enum' will conflict with newer versions of Java since it is a reserved word. -* [ByteInstantiation](pmd_rules_java_migrating.html#byteinstantiation): Calling new Byte() causes memory allocation that can be avoided by the static Byte.valueOf().It m... -* [ForLoopCanBeForeach](pmd_rules_java_migrating.html#forloopcanbeforeach): Reports loops that can be safely replaced with the foreach syntax. The rule considers loops over ... -* [IntegerInstantiation](pmd_rules_java_migrating.html#integerinstantiation): Calling new Integer() causes memory allocation that can be avoided by the static Integer.valueOf(... -* [JUnit4SuitesShouldUseSuiteAnnotation](pmd_rules_java_migrating.html#junit4suitesshouldusesuiteannotation): In JUnit 3, test suites are indicated by the suite() method. In JUnit 4, suites are indicatedthro... -* [JUnit4TestShouldUseAfterAnnotation](pmd_rules_java_migrating.html#junit4testshoulduseafterannotation): In JUnit 3, the tearDown method was used to clean up all data entities required in running tests.... -* [JUnit4TestShouldUseBeforeAnnotation](pmd_rules_java_migrating.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_migrating.html#junit4testshouldusetestannotation): In JUnit 3, the framework executed all methods which started with the word test as a unit test. I... -* [JUnitUseExpected](pmd_rules_java_migrating.html#junituseexpected): In JUnit4, use the @Test(expected) annotation to denote tests that should throw exceptions. -* [LongInstantiation](pmd_rules_java_migrating.html#longinstantiation): Calling new Long() causes memory allocation that can be avoided by the static Long.valueOf().It m... -* [ReplaceEnumerationWithIterator](pmd_rules_java_migrating.html#replaceenumerationwithiterator): Consider replacing Enumeration usages with the newer java.util.Iterator -* [ReplaceHashtableWithMap](pmd_rules_java_migrating.html#replacehashtablewithmap): Consider replacing Hashtable usage with the newer java.util.Map if thread safety is not required. -* [ReplaceVectorWithList](pmd_rules_java_migrating.html#replacevectorwithlist): Consider replacing Vector usages with the newer java.util.ArrayList if expensive thread-safe oper... -* [ShortInstantiation](pmd_rules_java_migrating.html#shortinstantiation): Calling new Short() causes memory allocation that can be avoided by the static Short.valueOf().It... - -## Naming -* [AbstractNaming](pmd_rules_java_naming.html#abstractnaming): Abstract classes should be named 'AbstractXXX'. -* [AvoidDollarSigns](pmd_rules_java_naming.html#avoiddollarsigns): Avoid using dollar signs in variable/method/class/interface names. -* [AvoidFieldNameMatchingMethodName](pmd_rules_java_naming.html#avoidfieldnamematchingmethodname): It can be confusing to have a field name with the same name as a method. While this is permitted,... -* [AvoidFieldNameMatchingTypeName](pmd_rules_java_naming.html#avoidfieldnamematchingtypename): It is somewhat confusing to have a field name matching the declaring class name.This probably mea... -* [BooleanGetMethodName](pmd_rules_java_naming.html#booleangetmethodname): Methods that return boolean results should be named as predicate statements to denote this.I.e, '... -* [ClassNamingConventions](pmd_rules_java_naming.html#classnamingconventions): Class names should always begin with an upper case character. -* [GenericsNaming](pmd_rules_java_naming.html#genericsnaming): Names for references to generic values should be limited to a single uppercase letter. -* [LongVariable](pmd_rules_java_naming.html#longvariable): Fields, formal arguments, or local variable names that are too long can make the code difficult t... -* [MethodNamingConventions](pmd_rules_java_naming.html#methodnamingconventions): Method names should always begin with a lower case character, and should not contain underscores. -* [MethodWithSameNameAsEnclosingClass](pmd_rules_java_naming.html#methodwithsamenameasenclosingclass): Non-constructor methods should not have the same name as the enclosing class. -* [MisleadingVariableName](pmd_rules_java_naming.html#misleadingvariablename): Detects when a non-field has a name starting with 'm_'. This usually denotes a field and could b... -* [NoPackage](pmd_rules_java_naming.html#nopackage): Detects when a class or interface does not have a package definition. -* [PackageCase](pmd_rules_java_naming.html#packagecase): Detects when a package definition contains uppercase characters. -* [ShortClassName](pmd_rules_java_naming.html#shortclassname): Short Classnames with fewer than e.g. five characters are not recommended. -* [ShortMethodName](pmd_rules_java_naming.html#shortmethodname): Method names that are very short are not helpful to the reader. -* [ShortVariable](pmd_rules_java_naming.html#shortvariable): Fields, local variables, or parameter names that are very short are not helpful to the reader. -* [SuspiciousConstantFieldName](pmd_rules_java_naming.html#suspiciousconstantfieldname): Field names using all uppercase characters - Sun's Java naming conventions indicating constants -... -* [SuspiciousEqualsMethodName](pmd_rules_java_naming.html#suspiciousequalsmethodname): The method name and parameter number are suspiciously close to equals(Object), which can denote a... -* [SuspiciousHashcodeMethodName](pmd_rules_java_naming.html#suspicioushashcodemethodname): The method name and return type are suspiciously close to hashCode(), which may denote an intenti... -* [VariableNamingConventions](pmd_rules_java_naming.html#variablenamingconventions): A variable naming conventions rule - customize this to your liking. Currently, itchecks for fina... - -## Optimization -* [AddEmptyString](pmd_rules_java_optimizations.html#addemptystring): The conversion of literals to strings by concatenating them with empty strings is inefficient.It ... -* [AvoidArrayLoops](pmd_rules_java_optimizations.html#avoidarrayloops): Instead of manually copying data between two arrays, use the efficient System.arraycopy method in... -* [AvoidInstantiatingObjectsInLoops](pmd_rules_java_optimizations.html#avoidinstantiatingobjectsinloops): New objects created within loops should be checked to see if they can created outside them and re... -* [LocalVariableCouldBeFinal](pmd_rules_java_optimizations.html#localvariablecouldbefinal): A local variable assigned only once can be declared final. -* [MethodArgumentCouldBeFinal](pmd_rules_java_optimizations.html#methodargumentcouldbefinal): A method argument that is never re-assigned within the method can be declared final. -* [PrematureDeclaration](pmd_rules_java_optimizations.html#prematuredeclaration): Checks for variables that are defined before they might be used. A reference is deemed to be prem... -* [RedundantFieldInitializer](pmd_rules_java_optimizations.html#redundantfieldinitializer): Java will initialize fields with known default values so any explicit initialization of those sam... -* [SimplifyStartsWith](pmd_rules_java_optimizations.html#simplifystartswith): Since it passes in a literal of length 1, calls to (string).startsWith can be rewritten using (st... -* [UnnecessaryWrapperObjectCreation](pmd_rules_java_optimizations.html#unnecessarywrapperobjectcreation): Most wrapper classes provide static conversion methods that avoid the need to create intermediate... -* [UseArrayListInsteadOfVector](pmd_rules_java_optimizations.html#usearraylistinsteadofvector): ArrayList is a much better Collection implementation than Vector if thread-safe operation is not ... -* [UseArraysAsList](pmd_rules_java_optimizations.html#usearraysaslist): The java.util.Arrays class has a "asList" method that should be used when you want to create a ne... -* [UseStringBufferForStringAppends](pmd_rules_java_optimizations.html#usestringbufferforstringappends): The use of the '+=' operator for appending strings causes the JVM to create and use an internal S... - -## Security Code Guidelines -* [ArrayIsStoredDirectly](pmd_rules_java_sunsecure.html#arrayisstoreddirectly): Constructors and methods receiving arrays should clone objects and store the copy.This prevents f... -* [MethodReturnsInternalArray](pmd_rules_java_sunsecure.html#methodreturnsinternalarray): Exposing internal arrays to the caller violates object encapsulation since elements can be remove... - -## Strict Exceptions -* [AvoidCatchingGenericException](pmd_rules_java_strictexception.html#avoidcatchinggenericexception): Avoid catching generic exceptions such as NullPointerException, RuntimeException, Exception in tr... -* [AvoidCatchingNPE](pmd_rules_java_strictexception.html#avoidcatchingnpe): Code should never throw NullPointerExceptions under normal circumstances. A catch block may hide... -* [AvoidCatchingThrowable](pmd_rules_java_strictexception.html#avoidcatchingthrowable): Catching Throwable errors is not recommended since its scope is very broad. It includes runtime i... -* [AvoidLosingExceptionInformation](pmd_rules_java_strictexception.html#avoidlosingexceptioninformation): Statements in a catch block that invoke accessors on the exception without using the informationo... -* [AvoidRethrowingException](pmd_rules_java_strictexception.html#avoidrethrowingexception): Catch blocks that merely rethrow a caught exception only add to code size and runtime complexity. -* [AvoidThrowingNewInstanceOfSameException](pmd_rules_java_strictexception.html#avoidthrowingnewinstanceofsameexception): Catch blocks that merely rethrow a caught exception wrapped inside a new instance of the same typ... -* [AvoidThrowingNullPointerException](pmd_rules_java_strictexception.html#avoidthrowingnullpointerexception): Avoid throwing NullPointerExceptions. These are confusing because most people will assume that th... -* [AvoidThrowingRawExceptionTypes](pmd_rules_java_strictexception.html#avoidthrowingrawexceptiontypes): Avoid throwing certain exception types. Rather than throw a raw RuntimeException, Throwable,Excep... -* [DoNotExtendJavaLangError](pmd_rules_java_strictexception.html#donotextendjavalangerror): Errors are system exceptions. Do not extend them. -* [DoNotThrowExceptionInFinally](pmd_rules_java_strictexception.html#donotthrowexceptioninfinally): Throwing exceptions within a 'finally' block is confusing since they may mask other exceptions or... -* [ExceptionAsFlowControl](pmd_rules_java_strictexception.html#exceptionasflowcontrol): Using Exceptions as form of flow control is not recommended as they obscure true exceptions when ... -* [SignatureDeclareThrowsException](pmd_rules_java_strictexception.html#signaturedeclarethrowsexception): Methods that declare the generic Exception as a possible throwable are not very helpful since the... - -## String and StringBuffer -* [AppendCharacterWithChar](pmd_rules_java_strings.html#appendcharacterwithchar): Avoid concatenating characters as strings in StringBuffer/StringBuilder.append methods. -* [AvoidDuplicateLiterals](pmd_rules_java_strings.html#avoidduplicateliterals): Code containing duplicate String literals can usually be improved by declaring the String as a co... -* [AvoidStringBufferField](pmd_rules_java_strings.html#avoidstringbufferfield): StringBuffers/StringBuilders can grow considerably, and so may become a source of memory leaksif ... -* [ConsecutiveAppendsShouldReuse](pmd_rules_java_strings.html#consecutiveappendsshouldreuse): Consecutive calls to StringBuffer/StringBuilder .append should be chained, reusing the target obj... -* [ConsecutiveLiteralAppends](pmd_rules_java_strings.html#consecutiveliteralappends): Consecutively calling StringBuffer/StringBuilder.append with String literals -* [InefficientEmptyStringCheck](pmd_rules_java_strings.html#inefficientemptystringcheck): String.trim().length() is an inefficient way to check if a String is really empty, as itcreates a... -* [InefficientStringBuffering](pmd_rules_java_strings.html#inefficientstringbuffering): Avoid concatenating non-literals in a StringBuffer constructor or append() since intermediate buf... -* [InsufficientStringBufferDeclaration](pmd_rules_java_strings.html#insufficientstringbufferdeclaration): Failing to pre-size a StringBuffer or StringBuilder properly could cause it to re-size many times... -* [StringBufferInstantiationWithChar](pmd_rules_java_strings.html#stringbufferinstantiationwithchar): Individual character values provided as initialization arguments will be converted into integers.... -* [StringInstantiation](pmd_rules_java_strings.html#stringinstantiation): Avoid instantiating String objects; this is usually unnecessary since they are immutable and can ... -* [StringToString](pmd_rules_java_strings.html#stringtostring): Avoid calling toString() on objects already known to be string instances; this is unnecessary. -* [UnnecessaryCaseChange](pmd_rules_java_strings.html#unnecessarycasechange): Using equalsIgnoreCase() is faster than using toUpperCase/toLowerCase().equals() -* [UseEqualsToCompareStrings](pmd_rules_java_strings.html#useequalstocomparestrings): Using '==' or '!=' to compare strings only works if intern version is used on both sides.Use the ... -* [UseIndexOfChar](pmd_rules_java_strings.html#useindexofchar): Use String.indexOf(char) when checking for the index of a single character; it executes faster. -* [UselessStringValueOf](pmd_rules_java_strings.html#uselessstringvalueof): No need to call String.valueOf to append to a string; just use the valueOf() argument directly. -* [UseStringBufferLength](pmd_rules_java_strings.html#usestringbufferlength): Use StringBuffer.length() to determine StringBuffer length rather than using StringBuffer.toStrin... - -## Unnecessary -* [UnnecessaryConversionTemporary](pmd_rules_java_unnecessary.html#unnecessaryconversiontemporary): Avoid the use temporary objects when converting primitives to Strings. Use the static conversion ... -* [UnnecessaryFinalModifier](pmd_rules_java_unnecessary.html#unnecessaryfinalmodifier): When a class has the final modifier, all the methods are automatically final and do not need to b... -* [UnnecessaryModifier](pmd_rules_java_unnecessary.html#unnecessarymodifier): Fields in interfaces and annotations are automatically 'public static final', and methods are 'pu... -* [UnnecessaryReturn](pmd_rules_java_unnecessary.html#unnecessaryreturn): Avoid the use of unnecessary return statements. -* [UnusedNullCheckInEquals](pmd_rules_java_unnecessary.html#unusednullcheckinequals): After checking an object reference for null, you should invoke equals() on that object rather tha... -* [UselessOperationOnImmutable](pmd_rules_java_unnecessary.html#uselessoperationonimmutable): An operation on an Immutable object (String, BigDecimal or BigInteger) won't change the object it... -* [UselessOverridingMethod](pmd_rules_java_unnecessary.html#uselessoverridingmethod): The overriding method merely calls the same method defined in a superclass. -* [UselessParentheses](pmd_rules_java_unnecessary.html#uselessparentheses): Useless parentheses should be removed. -* [UselessQualifiedThis](pmd_rules_java_unnecessary.html#uselessqualifiedthis): Look for qualified this usages in the same class. - -## Unused Code -* [UnusedFormalParameter](pmd_rules_java_unusedcode.html#unusedformalparameter): Avoid passing parameters to methods or constructors without actually referencing them in the meth... -* [UnusedLocalVariable](pmd_rules_java_unusedcode.html#unusedlocalvariable): Detects when a local variable is declared and/or assigned, but not used. -* [UnusedPrivateField](pmd_rules_java_unusedcode.html#unusedprivatefield): Detects when a private field is declared and/or assigned a value, but not used. -* [UnusedPrivateMethod](pmd_rules_java_unusedcode.html#unusedprivatemethod): Unused Private Method detects when a private method is declared but is unused. + +## Documentation + +{% include callout.html content="Rules that are related to code documentation." %} + +* [CommentContent](pmd_rules_java_documentation.html#commentcontent): A rule for the politically correct... we don't want to offend anyone. +* [CommentRequired](pmd_rules_java_documentation.html#commentrequired): Denotes whether comments are required (or unwanted) for specific language elements. +* [CommentSize](pmd_rules_java_documentation.html#commentsize): Determines whether the dimensions of non-header comments found are within the specified limits. +* [UncommentedEmptyConstructor](pmd_rules_java_documentation.html#uncommentedemptyconstructor): Uncommented Empty Constructor finds instances where a constructor does notcontain statements, but... +* [UncommentedEmptyMethodBody](pmd_rules_java_documentation.html#uncommentedemptymethodbody): Uncommented Empty Method Body finds instances where a method body does not containstatements, but... + +## Error Prone + +{% include callout.html content="Rules to detect constructs that are either broken, extremely confusing or prone to runtime errors." %} + +* [AssignmentInOperand](pmd_rules_java_errorprone.html#assignmentinoperand): Avoid assignments in operands; this can make code more complicated and harder to read. +* [AssignmentToNonFinalStatic](pmd_rules_java_errorprone.html#assignmenttononfinalstatic): Identifies a possible unsafe usage of a static field. +* [AvoidAccessibilityAlteration](pmd_rules_java_errorprone.html#avoidaccessibilityalteration): Methods such as getDeclaredConstructors(), getDeclaredConstructor(Class[]) and setAccessible(),as... +* [AvoidAssertAsIdentifier](pmd_rules_java_errorprone.html#avoidassertasidentifier): Use of the term 'assert' will conflict with newer versions of Java since it is a reserved word. +* [AvoidBranchingStatementAsLastInLoop](pmd_rules_java_errorprone.html#avoidbranchingstatementaslastinloop): Using a branching statement as the last part of a loop may be a bug, and/or is confusing.Ensure t... +* [AvoidCallingFinalize](pmd_rules_java_errorprone.html#avoidcallingfinalize): The method Object.finalize() is called by the garbage collector on an object when garbage collect... +* [AvoidCatchingNPE](pmd_rules_java_errorprone.html#avoidcatchingnpe): Code should never throw NullPointerExceptions under normal circumstances. A catch block may hide... +* [AvoidCatchingThrowable](pmd_rules_java_errorprone.html#avoidcatchingthrowable): Catching Throwable errors is not recommended since its scope is very broad. It includes runtime i... +* [AvoidDecimalLiteralsInBigDecimalConstructor](pmd_rules_java_errorprone.html#avoiddecimalliteralsinbigdecimalconstructor): One might assume that the result of "new BigDecimal(0.1)" is exactly equal to 0.1, but it is actu... +* [AvoidDuplicateLiterals](pmd_rules_java_errorprone.html#avoidduplicateliterals): Code containing duplicate String literals can usually be improved by declaring the String as a co... +* [AvoidEnumAsIdentifier](pmd_rules_java_errorprone.html#avoidenumasidentifier): Use of the term 'enum' will conflict with newer versions of Java since it is a reserved word. +* [AvoidFieldNameMatchingMethodName](pmd_rules_java_errorprone.html#avoidfieldnamematchingmethodname): It can be confusing to have a field name with the same name as a method. While this is permitted,... +* [AvoidFieldNameMatchingTypeName](pmd_rules_java_errorprone.html#avoidfieldnamematchingtypename): It is somewhat confusing to have a field name matching the declaring class name.This probably mea... +* [AvoidInstanceofChecksInCatchClause](pmd_rules_java_errorprone.html#avoidinstanceofchecksincatchclause): Each caught exception type should be handled in its own catch clause. +* [AvoidLiteralsInIfCondition](pmd_rules_java_errorprone.html#avoidliteralsinifcondition): Avoid using hard-coded literals in conditional statements. By declaring them as static variableso... +* [AvoidLosingExceptionInformation](pmd_rules_java_errorprone.html#avoidlosingexceptioninformation): Statements in a catch block that invoke accessors on the exception without using the informationo... +* [AvoidMultipleUnaryOperators](pmd_rules_java_errorprone.html#avoidmultipleunaryoperators): The use of multiple unary operators may be problematic, and/or confusing.Ensure that the intended... +* [AvoidUsingOctalValues](pmd_rules_java_errorprone.html#avoidusingoctalvalues): Integer literals should not start with zero since this denotes that the rest of literal will bein... +* [BadComparison](pmd_rules_java_errorprone.html#badcomparison): Avoid equality comparisons with Double.NaN. Due to the implicit lack of representationprecision w... +* [BeanMembersShouldSerialize](pmd_rules_java_errorprone.html#beanmembersshouldserialize): If a class is a bean, or is referenced by a bean directly or indirectly it needs to be serializab... +* [BrokenNullCheck](pmd_rules_java_errorprone.html#brokennullcheck): The null check is broken since it will throw a NullPointerException itself.It is likely that you ... +* [CallSuperFirst](pmd_rules_java_errorprone.html#callsuperfirst): Super should be called at the start of the method +* [CallSuperLast](pmd_rules_java_errorprone.html#callsuperlast): Super should be called at the end of the method +* [CheckSkipResult](pmd_rules_java_errorprone.html#checkskipresult): The skip() method may skip a smaller number of bytes than requested. Check the returned value to ... +* [ClassCastExceptionWithToArray](pmd_rules_java_errorprone.html#classcastexceptionwithtoarray): When deriving an array of a specific class from your Collection, one should provide an array ofth... +* [CloneMethodMustBePublic](pmd_rules_java_errorprone.html#clonemethodmustbepublic): The java Manual says "By convention, classes that implement this interface should overrideObject.... +* [CloneMethodMustImplementCloneable](pmd_rules_java_errorprone.html#clonemethodmustimplementcloneable): The method clone() should only be implemented if the class implements the Cloneable interface wit... +* [CloneMethodReturnTypeMustMatchClassName](pmd_rules_java_errorprone.html#clonemethodreturntypemustmatchclassname): If a class implements cloneable the return type of the method clone() must be the class name. Tha... +* [CloneThrowsCloneNotSupportedException](pmd_rules_java_errorprone.html#clonethrowsclonenotsupportedexception): The method clone() should throw a CloneNotSupportedException. +* [CloseResource](pmd_rules_java_errorprone.html#closeresource): Ensure that resources (like Connection, Statement, and ResultSet objects) are always closed after... +* [CompareObjectsWithEquals](pmd_rules_java_errorprone.html#compareobjectswithequals): Use equals() to compare object references; avoid comparing them with ==. +* [ConstructorCallsOverridableMethod](pmd_rules_java_errorprone.html#constructorcallsoverridablemethod): Calling overridable methods during construction poses a risk of invoking methods on an incomplete... +* [DataflowAnomalyAnalysis](pmd_rules_java_errorprone.html#dataflowanomalyanalysis): The dataflow analysis tracks local definitions, undefinitions and references to variables on diff... +* [DoNotCallGarbageCollectionExplicitly](pmd_rules_java_errorprone.html#donotcallgarbagecollectionexplicitly): Calls to System.gc(), Runtime.getRuntime().gc(), and System.runFinalization() are not advised. Co... +* [DoNotCallSystemExit](pmd_rules_java_errorprone.html#donotcallsystemexit): Web applications should not call System.exit(), since only the web container or theapplication se... +* [DoNotExtendJavaLangThrowable](pmd_rules_java_errorprone.html#donotextendjavalangthrowable): Extend Exception or RuntimeException instead of Throwable. +* [DoNotHardCodeSDCard](pmd_rules_java_errorprone.html#donothardcodesdcard): Use Environment.getExternalStorageDirectory() instead of "/sdcard" +* [DoNotThrowExceptionInFinally](pmd_rules_java_errorprone.html#donotthrowexceptioninfinally): Throwing exceptions within a 'finally' block is confusing since they may mask other exceptions or... +* [DontImportSun](pmd_rules_java_errorprone.html#dontimportsun): Avoid importing anything from the 'sun.' packages. These packages are not portable and are likel... +* [DontUseFloatTypeForLoopIndices](pmd_rules_java_errorprone.html#dontusefloattypeforloopindices): Don't use floating point for loop indices. If you must use floating point, use doubleunless you'r... +* [EmptyCatchBlock](pmd_rules_java_errorprone.html#emptycatchblock): Empty Catch Block finds instances where an exception is caught, but nothing is done. In most cir... +* [EmptyFinalizer](pmd_rules_java_errorprone.html#emptyfinalizer): Empty finalize methods serve no purpose and should be removed. Note that Oracle has declared Obje... +* [EmptyFinallyBlock](pmd_rules_java_errorprone.html#emptyfinallyblock): Empty finally blocks serve no purpose and should be removed. +* [EmptyIfStmt](pmd_rules_java_errorprone.html#emptyifstmt): Empty If Statement finds instances where a condition is checked but nothing is done about it. +* [EmptyInitializer](pmd_rules_java_errorprone.html#emptyinitializer): Empty initializers serve no purpose and should be removed. +* [EmptyStatementBlock](pmd_rules_java_errorprone.html#emptystatementblock): Empty block statements serve no purpose and should be removed. +* [EmptyStatementNotInLoop](pmd_rules_java_errorprone.html#emptystatementnotinloop): An empty statement (or a semicolon by itself) that is not used as the sole body of a 'for' or 'wh... +* [EmptySwitchStatements](pmd_rules_java_errorprone.html#emptyswitchstatements): Empty switch statements serve no purpose and should be removed. +* [EmptySynchronizedBlock](pmd_rules_java_errorprone.html#emptysynchronizedblock): Empty synchronized blocks serve no purpose and should be removed. +* [EmptyTryBlock](pmd_rules_java_errorprone.html#emptytryblock): Avoid empty try blocks - what's the point? +* [EmptyWhileStmt](pmd_rules_java_errorprone.html#emptywhilestmt): Empty While Statement finds all instances where a while statement does nothing. If it is a timin... +* [EqualsNull](pmd_rules_java_errorprone.html#equalsnull): Tests for null should not use the equals() method. The '==' operator should be used instead. +* [FinalizeDoesNotCallSuperFinalize](pmd_rules_java_errorprone.html#finalizedoesnotcallsuperfinalize): If the finalize() is implemented, its last action should be to call super.finalize. Note that Ora... +* [FinalizeOnlyCallsSuperFinalize](pmd_rules_java_errorprone.html#finalizeonlycallssuperfinalize): If the finalize() is implemented, it should do something besides just calling super.finalize(). N... +* [FinalizeOverloaded](pmd_rules_java_errorprone.html#finalizeoverloaded): Methods named finalize() should not have parameters. It is confusing and most likely an attempt ... +* [FinalizeShouldBeProtected](pmd_rules_java_errorprone.html#finalizeshouldbeprotected): When overriding the finalize(), the new method should be set as protected. If made public, other... +* [IdempotentOperations](pmd_rules_java_errorprone.html#idempotentoperations): Avoid idempotent operations - they have no effect. +* [ImportFromSamePackage](pmd_rules_java_errorprone.html#importfromsamepackage): There is no need to import a type that lives in the same package. +* [InstantiationToGetClass](pmd_rules_java_errorprone.html#instantiationtogetclass): Avoid instantiating an object just to call getClass() on it; use the .class public member instead. +* [InvalidSlf4jMessageFormat](pmd_rules_java_errorprone.html#invalidslf4jmessageformat): Check for messages in slf4j loggers with non matching number of arguments and placeholders. +* [JumbledIncrementer](pmd_rules_java_errorprone.html#jumbledincrementer): Avoid jumbled loop incrementers - its usually a mistake, and is confusing even if intentional. +* [JUnitSpelling](pmd_rules_java_errorprone.html#junitspelling): Some JUnit framework methods are easy to misspell. +* [JUnitStaticSuite](pmd_rules_java_errorprone.html#junitstaticsuite): The suite() method in a JUnit test needs to be both public and static. +* [LoggerIsNotStaticFinal](pmd_rules_java_errorprone.html#loggerisnotstaticfinal): In most cases, the Logger reference can be declared as static and final. +* [MethodWithSameNameAsEnclosingClass](pmd_rules_java_errorprone.html#methodwithsamenameasenclosingclass): Non-constructor methods should not have the same name as the enclosing class. +* [MisplacedNullCheck](pmd_rules_java_errorprone.html#misplacednullcheck): The null check here is misplaced. If the variable is null a NullPointerException will be thrown.E... +* [MissingBreakInSwitch](pmd_rules_java_errorprone.html#missingbreakinswitch): Switch statements without break or return statements for each case optionmay indicate problematic... +* [MissingSerialVersionUID](pmd_rules_java_errorprone.html#missingserialversionuid): Serializable classes should provide a serialVersionUID field.The serialVersionUID field is also n... +* [MissingStaticMethodInNonInstantiatableClass](pmd_rules_java_errorprone.html#missingstaticmethodinnoninstantiatableclass): A class that has private constructors and does not have any static methods or fields cannot be used. +* [MoreThanOneLogger](pmd_rules_java_errorprone.html#morethanonelogger): Normally only one logger is used in each class. +* [NonCaseLabelInSwitchStatement](pmd_rules_java_errorprone.html#noncaselabelinswitchstatement): A non-case label (e.g. a named break/continue label) was present in a switch statement.This legal... +* [NonStaticInitializer](pmd_rules_java_errorprone.html#nonstaticinitializer): A non-static initializer block will be called any time a constructor is invoked (just prior toinv... +* [NullAssignment](pmd_rules_java_errorprone.html#nullassignment): Assigning a "null" to a variable (outside of its declaration) is usually bad form. Sometimes, th... +* [OverrideBothEqualsAndHashcode](pmd_rules_java_errorprone.html#overridebothequalsandhashcode): Override both public boolean Object.equals(Object other), and public int Object.hashCode(), or ov... +* [ProperCloneImplementation](pmd_rules_java_errorprone.html#propercloneimplementation): Object clone() should be implemented with super.clone(). +* [ProperLogger](pmd_rules_java_errorprone.html#properlogger): A logger should normally be defined private static final and be associated with the correct class... +* [ReturnEmptyArrayRatherThanNull](pmd_rules_java_errorprone.html#returnemptyarrayratherthannull): For any method that returns an array, it is a better to return an empty array rather than anull r... +* [ReturnFromFinallyBlock](pmd_rules_java_errorprone.html#returnfromfinallyblock): Avoid returning from a finally block, this can discard exceptions. +* [SimpleDateFormatNeedsLocale](pmd_rules_java_errorprone.html#simpledateformatneedslocale): Be sure to specify a Locale when creating SimpleDateFormat instances to ensure that locale-approp... +* [SingleMethodSingleton](pmd_rules_java_errorprone.html#singlemethodsingleton): Some classes contain overloaded getInstance. The problem with overloaded getInstance methodsis th... +* [SingletonClassReturningNewInstance](pmd_rules_java_errorprone.html#singletonclassreturningnewinstance): Some classes contain overloaded getInstance. The problem with overloaded getInstance methodsis th... +* [StaticEJBFieldShouldBeFinal](pmd_rules_java_errorprone.html#staticejbfieldshouldbefinal): According to the J2EE specification, an EJB should not have any static fieldswith write access. H... +* [StringBufferInstantiationWithChar](pmd_rules_java_errorprone.html#stringbufferinstantiationwithchar): Individual character values provided as initialization arguments will be converted into integers.... +* [SuspiciousEqualsMethodName](pmd_rules_java_errorprone.html#suspiciousequalsmethodname): The method name and parameter number are suspiciously close to equals(Object), which can denote a... +* [SuspiciousHashcodeMethodName](pmd_rules_java_errorprone.html#suspicioushashcodemethodname): The method name and return type are suspiciously close to hashCode(), which may denote an intenti... +* [SuspiciousOctalEscape](pmd_rules_java_errorprone.html#suspiciousoctalescape): A suspicious octal escape sequence was found inside a String literal.The Java language specificat... +* [TestClassWithoutTestCases](pmd_rules_java_errorprone.html#testclasswithouttestcases): Test classes end with the suffix Test. Having a non-test class with that name is not a good pract... +* [UnconditionalIfStatement](pmd_rules_java_errorprone.html#unconditionalifstatement): Do not use "if" statements whose conditionals are always true or always false. +* [UnnecessaryBooleanAssertion](pmd_rules_java_errorprone.html#unnecessarybooleanassertion): A JUnit test assertion with a boolean literal is unnecessary since it always will evaluate to the... +* [UnnecessaryCaseChange](pmd_rules_java_errorprone.html#unnecessarycasechange): Using equalsIgnoreCase() is faster than using toUpperCase/toLowerCase().equals() +* [UnnecessaryConversionTemporary](pmd_rules_java_errorprone.html#unnecessaryconversiontemporary): Avoid the use temporary objects when converting primitives to Strings. Use the static conversion ... +* [UnusedNullCheckInEquals](pmd_rules_java_errorprone.html#unusednullcheckinequals): After checking an object reference for null, you should invoke equals() on that object rather tha... +* [UseCorrectExceptionLogging](pmd_rules_java_errorprone.html#usecorrectexceptionlogging): To make sure the full stacktrace is printed out, use the logging statement with two arguments: a ... +* [UseEqualsToCompareStrings](pmd_rules_java_errorprone.html#useequalstocomparestrings): Using '==' or '!=' to compare strings only works if intern version is used on both sides.Use the ... +* [UselessOperationOnImmutable](pmd_rules_java_errorprone.html#uselessoperationonimmutable): An operation on an Immutable object (String, BigDecimal or BigInteger) won't change the object it... +* [UseLocaleWithCaseConversions](pmd_rules_java_errorprone.html#uselocalewithcaseconversions): When doing String.toLowerCase()/toUpperCase() conversions, use Locales to avoids problems with la... +* [UseProperClassLoader](pmd_rules_java_errorprone.html#useproperclassloader): In J2EE, the getClassLoader() method might not work as expected. Use Thread.currentThread().getCo... + +## Multithreading + +{% include callout.html content="Rules that flag issues when dealing with multiple threads of execution." %} + +* [AvoidSynchronizedAtMethodLevel](pmd_rules_java_multithreading.html#avoidsynchronizedatmethodlevel): Method-level synchronization can cause problems when new code is added to the method.Block-level ... +* [AvoidThreadGroup](pmd_rules_java_multithreading.html#avoidthreadgroup): Avoid using java.lang.ThreadGroup; although it is intended to be used in a threaded environmentit... +* [AvoidUsingVolatile](pmd_rules_java_multithreading.html#avoidusingvolatile): Use of the keyword 'volatile' is generally used to fine tune a Java application, and therefore, r... +* [DoNotUseThreads](pmd_rules_java_multithreading.html#donotusethreads): The J2EE specification explicitly forbids the use of threads. +* [DontCallThreadRun](pmd_rules_java_multithreading.html#dontcallthreadrun): Explicitly calling Thread.run() method will execute in the caller's thread of control. Instead, ... +* [DoubleCheckedLocking](pmd_rules_java_multithreading.html#doublecheckedlocking): Partially created objects can be returned by the Double Checked Locking pattern when used in Java... +* [NonThreadSafeSingleton](pmd_rules_java_multithreading.html#nonthreadsafesingleton): Non-thread safe singletons can result in bad state changes. Eliminatestatic singletons if possibl... +* [UnsynchronizedStaticDateFormatter](pmd_rules_java_multithreading.html#unsynchronizedstaticdateformatter): SimpleDateFormat instances are not synchronized. Sun recommends using separate format instancesfo... +* [UseConcurrentHashMap](pmd_rules_java_multithreading.html#useconcurrenthashmap): Since Java5 brought a new implementation of the Map designed for multi-threaded access, you canpe... +* [UseNotifyAllInsteadOfNotify](pmd_rules_java_multithreading.html#usenotifyallinsteadofnotify): Thread.notify() awakens a thread monitoring the object. If more than one thread is monitoring, th... + +## Performance + +{% include callout.html content="Rules that flag suboptimal code." %} + +* [AddEmptyString](pmd_rules_java_performance.html#addemptystring): The conversion of literals to strings by concatenating them with empty strings is inefficient.It ... +* [AppendCharacterWithChar](pmd_rules_java_performance.html#appendcharacterwithchar): Avoid concatenating characters as strings in StringBuffer/StringBuilder.append methods. +* [AvoidArrayLoops](pmd_rules_java_performance.html#avoidarrayloops): Instead of manually copying data between two arrays, use the efficient Arrays.copyOf or System.ar... +* [AvoidFileStream](pmd_rules_java_performance.html#avoidfilestream): The FileInputStream and FileOutputStream classes contains a finalizer method which will cause gar... +* [AvoidInstantiatingObjectsInLoops](pmd_rules_java_performance.html#avoidinstantiatingobjectsinloops): New objects created within loops should be checked to see if they can created outside them and re... +* [AvoidUsingShortType](pmd_rules_java_performance.html#avoidusingshorttype): Java uses the 'short' type to reduce memory usage, not to optimize calculation. In fact, the JVM ... +* [BigIntegerInstantiation](pmd_rules_java_performance.html#bigintegerinstantiation): Don't create instances of already existing BigInteger (BigInteger.ZERO, BigInteger.ONE) andfor Ja... +* [BooleanInstantiation](pmd_rules_java_performance.html#booleaninstantiation): Avoid instantiating Boolean objects; you can reference Boolean.TRUE, Boolean.FALSE, or call Boole... +* [ByteInstantiation](pmd_rules_java_performance.html#byteinstantiation): Calling new Byte() causes memory allocation that can be avoided by the static Byte.valueOf().It m... +* [ConsecutiveAppendsShouldReuse](pmd_rules_java_performance.html#consecutiveappendsshouldreuse): Consecutive calls to StringBuffer/StringBuilder .append should be chained, reusing the target obj... +* [ConsecutiveLiteralAppends](pmd_rules_java_performance.html#consecutiveliteralappends): Consecutively calling StringBuffer/StringBuilder.append(...) with literals should be avoided.Sinc... +* [InefficientEmptyStringCheck](pmd_rules_java_performance.html#inefficientemptystringcheck): String.trim().length() == 0 (or String.trim().isEmpty() for the same reason) is an inefficientway... +* [InefficientStringBuffering](pmd_rules_java_performance.html#inefficientstringbuffering): Avoid concatenating non-literals in a StringBuffer constructor or append() since intermediate buf... +* [InsufficientStringBufferDeclaration](pmd_rules_java_performance.html#insufficientstringbufferdeclaration): Failing to pre-size a StringBuffer or StringBuilder properly could cause it to re-size many times... +* [IntegerInstantiation](pmd_rules_java_performance.html#integerinstantiation): Calling new Integer() causes memory allocation that can be avoided by the static Integer.valueOf(... +* [LongInstantiation](pmd_rules_java_performance.html#longinstantiation): Calling new Long() causes memory allocation that can be avoided by the static Long.valueOf().It m... +* [OptimizableToArrayCall](pmd_rules_java_performance.html#optimizabletoarraycall): Calls to a collection's 'toArray(E[])' method should specify a target array of zero size. This al... +* [RedundantFieldInitializer](pmd_rules_java_performance.html#redundantfieldinitializer): Java will initialize fields with known default values so any explicit initialization of those sam... +* [ShortInstantiation](pmd_rules_java_performance.html#shortinstantiation): Calling new Short() causes memory allocation that can be avoided by the static Short.valueOf().It... +* [SimplifyStartsWith](pmd_rules_java_performance.html#simplifystartswith): Since it passes in a literal of length 1, calls to (string).startsWith can be rewritten using (st... +* [StringInstantiation](pmd_rules_java_performance.html#stringinstantiation): Avoid instantiating String objects; this is usually unnecessary since they are immutable and can ... +* [StringToString](pmd_rules_java_performance.html#stringtostring): Avoid calling toString() on objects already known to be string instances; this is unnecessary. +* [TooFewBranchesForASwitchStatement](pmd_rules_java_performance.html#toofewbranchesforaswitchstatement): Switch statements are intended to be used to support complex branching behaviour. Using a switch ... +* [UnnecessaryWrapperObjectCreation](pmd_rules_java_performance.html#unnecessarywrapperobjectcreation): Most wrapper classes provide static conversion methods that avoid the need to create intermediate... +* [UseArrayListInsteadOfVector](pmd_rules_java_performance.html#usearraylistinsteadofvector): ArrayList is a much better Collection implementation than Vector if thread-safe operation is not ... +* [UseArraysAsList](pmd_rules_java_performance.html#usearraysaslist): The java.util.Arrays class has a "asList" method that should be used when you want to create a ne... +* [UseIndexOfChar](pmd_rules_java_performance.html#useindexofchar): Use String.indexOf(char) when checking for the index of a single character; it executes faster. +* [UselessStringValueOf](pmd_rules_java_performance.html#uselessstringvalueof): No need to call String.valueOf to append to a string; just use the valueOf() argument directly. +* [UseStringBufferForStringAppends](pmd_rules_java_performance.html#usestringbufferforstringappends): The use of the '+=' operator for appending strings causes the JVM to create and use an internal S... +* [UseStringBufferLength](pmd_rules_java_performance.html#usestringbufferlength): Use StringBuffer.length() to determine StringBuffer length rather than using StringBuffer.toStrin... + +## Security + +{% include callout.html content="Rules that flag potential security flaws." %} + +* [HardCodedCryptoKey](pmd_rules_java_security.html#hardcodedcryptokey): Do not use hard coded values for cryptographic operations. Please store keys outside of source code. +* [InsecureCryptoIv](pmd_rules_java_security.html#insecurecryptoiv): Do not use hard coded initialization vector in cryptographic operations. Please use a randomly ge... + +## Additional rulesets + +* Android (`rulesets/java/android.xml`): + + Deprecated This ruleset is for backwards compatibility. + + It contains the following rules: + + [CallSuperFirst](pmd_rules_java_errorprone.html#callsuperfirst), [CallSuperLast](pmd_rules_java_errorprone.html#callsuperlast), [DoNotHardCodeSDCard](pmd_rules_java_errorprone.html#donothardcodesdcard) + +* Basic (`rulesets/java/basic.xml`): + + Deprecated This ruleset is for backwards compatibility. + + It contains the following rules: + + [AvoidBranchingStatementAsLastInLoop](pmd_rules_java_errorprone.html#avoidbranchingstatementaslastinloop), [AvoidDecimalLiteralsInBigDecimalConstructor](pmd_rules_java_errorprone.html#avoiddecimalliteralsinbigdecimalconstructor), [AvoidMultipleUnaryOperators](pmd_rules_java_errorprone.html#avoidmultipleunaryoperators), [AvoidThreadGroup](pmd_rules_java_multithreading.html#avoidthreadgroup), [AvoidUsingHardCodedIP](pmd_rules_java_bestpractices.html#avoidusinghardcodedip), [AvoidUsingOctalValues](pmd_rules_java_errorprone.html#avoidusingoctalvalues), [BigIntegerInstantiation](pmd_rules_java_performance.html#bigintegerinstantiation), [BooleanInstantiation](pmd_rules_java_performance.html#booleaninstantiation), [BrokenNullCheck](pmd_rules_java_errorprone.html#brokennullcheck), [CheckResultSet](pmd_rules_java_bestpractices.html#checkresultset), [CheckSkipResult](pmd_rules_java_errorprone.html#checkskipresult), [ClassCastExceptionWithToArray](pmd_rules_java_errorprone.html#classcastexceptionwithtoarray), [CollapsibleIfStatements](pmd_rules_java_design.html#collapsibleifstatements), [DontCallThreadRun](pmd_rules_java_multithreading.html#dontcallthreadrun), [DontUseFloatTypeForLoopIndices](pmd_rules_java_errorprone.html#dontusefloattypeforloopindices), [DoubleCheckedLocking](pmd_rules_java_multithreading.html#doublecheckedlocking), [ExtendsObject](pmd_rules_java_codestyle.html#extendsobject), [ForLoopShouldBeWhileLoop](pmd_rules_java_codestyle.html#forloopshouldbewhileloop), [JumbledIncrementer](pmd_rules_java_errorprone.html#jumbledincrementer), [MisplacedNullCheck](pmd_rules_java_errorprone.html#misplacednullcheck), [OverrideBothEqualsAndHashcode](pmd_rules_java_errorprone.html#overridebothequalsandhashcode), [ReturnFromFinallyBlock](pmd_rules_java_errorprone.html#returnfromfinallyblock), [SimplifiedTernary](pmd_rules_java_design.html#simplifiedternary), [UnconditionalIfStatement](pmd_rules_java_errorprone.html#unconditionalifstatement) + +* Braces (`rulesets/java/braces.xml`): + + Deprecated This ruleset is for backwards compatibility. + + It contains the following rules: + + [ForLoopsMustUseBraces](pmd_rules_java_codestyle.html#forloopsmustusebraces), [IfElseStmtsMustUseBraces](pmd_rules_java_codestyle.html#ifelsestmtsmustusebraces), [IfStmtsMustUseBraces](pmd_rules_java_codestyle.html#ifstmtsmustusebraces), [WhileLoopsMustUseBraces](pmd_rules_java_codestyle.html#whileloopsmustusebraces) + +* Clone Implementation (`rulesets/java/clone.xml`): + + Deprecated This ruleset is for backwards compatibility. + + It contains the following rules: + + [CloneMethodMustBePublic](pmd_rules_java_errorprone.html#clonemethodmustbepublic), [CloneMethodMustImplementCloneable](pmd_rules_java_errorprone.html#clonemethodmustimplementcloneable), [CloneMethodReturnTypeMustMatchClassName](pmd_rules_java_errorprone.html#clonemethodreturntypemustmatchclassname), [CloneThrowsCloneNotSupportedException](pmd_rules_java_errorprone.html#clonethrowsclonenotsupportedexception), [ProperCloneImplementation](pmd_rules_java_errorprone.html#propercloneimplementation) + +* Code Size (`rulesets/java/codesize.xml`): + + Deprecated This ruleset is for backwards compatibility. + + It contains the following rules: + + [CyclomaticComplexity](pmd_rules_java_design.html#cyclomaticcomplexity), [ExcessiveClassLength](pmd_rules_java_design.html#excessiveclasslength), [ExcessiveMethodLength](pmd_rules_java_design.html#excessivemethodlength), [ExcessiveParameterList](pmd_rules_java_design.html#excessiveparameterlist), [ExcessivePublicCount](pmd_rules_java_design.html#excessivepubliccount), [ModifiedCyclomaticComplexity](pmd_rules_java_design.html#modifiedcyclomaticcomplexity), [NcssConstructorCount](pmd_rules_java_design.html#ncssconstructorcount), [NcssCount](pmd_rules_java_design.html#ncsscount), [NcssMethodCount](pmd_rules_java_design.html#ncssmethodcount), [NcssTypeCount](pmd_rules_java_design.html#ncsstypecount), [NPathComplexity](pmd_rules_java_design.html#npathcomplexity), [StdCyclomaticComplexity](pmd_rules_java_design.html#stdcyclomaticcomplexity), [TooManyFields](pmd_rules_java_design.html#toomanyfields), [TooManyMethods](pmd_rules_java_design.html#toomanymethods) + +* Comments (`rulesets/java/comments.xml`): + + Deprecated This ruleset is for backwards compatibility. + + It contains the following rules: + + [CommentContent](pmd_rules_java_documentation.html#commentcontent), [CommentDefaultAccessModifier](pmd_rules_java_codestyle.html#commentdefaultaccessmodifier), [CommentRequired](pmd_rules_java_documentation.html#commentrequired), [CommentSize](pmd_rules_java_documentation.html#commentsize) + +* Controversial (`rulesets/java/controversial.xml`): + + Deprecated This ruleset is for backwards compatibility. + + It contains the following rules: + + [AssignmentInOperand](pmd_rules_java_errorprone.html#assignmentinoperand), [AtLeastOneConstructor](pmd_rules_java_codestyle.html#atleastoneconstructor), [AvoidAccessibilityAlteration](pmd_rules_java_errorprone.html#avoidaccessibilityalteration), [AvoidFinalLocalVariable](pmd_rules_java_codestyle.html#avoidfinallocalvariable), [AvoidLiteralsInIfCondition](pmd_rules_java_errorprone.html#avoidliteralsinifcondition), [AvoidPrefixingMethodParameters](pmd_rules_java_codestyle.html#avoidprefixingmethodparameters), [AvoidUsingNativeCode](pmd_rules_java_codestyle.html#avoidusingnativecode), [AvoidUsingShortType](pmd_rules_java_performance.html#avoidusingshorttype), [AvoidUsingVolatile](pmd_rules_java_multithreading.html#avoidusingvolatile), [CallSuperInConstructor](pmd_rules_java_codestyle.html#callsuperinconstructor), [DataflowAnomalyAnalysis](pmd_rules_java_errorprone.html#dataflowanomalyanalysis), [DefaultPackage](pmd_rules_java_codestyle.html#defaultpackage), [DoNotCallGarbageCollectionExplicitly](pmd_rules_java_errorprone.html#donotcallgarbagecollectionexplicitly), [DontImportSun](pmd_rules_java_errorprone.html#dontimportsun), [NullAssignment](pmd_rules_java_errorprone.html#nullassignment), [OneDeclarationPerLine](pmd_rules_java_bestpractices.html#onedeclarationperline), [OnlyOneReturn](pmd_rules_java_codestyle.html#onlyonereturn), [SuspiciousOctalEscape](pmd_rules_java_errorprone.html#suspiciousoctalescape), [UnnecessaryConstructor](pmd_rules_java_codestyle.html#unnecessaryconstructor), [UnnecessaryParentheses](pmd_rules_java_codestyle.html#uselessparentheses), [UseConcurrentHashMap](pmd_rules_java_multithreading.html#useconcurrenthashmap), [UseObjectForClearerAPI](pmd_rules_java_design.html#useobjectforclearerapi) + +* Coupling (`rulesets/java/coupling.xml`): + + Deprecated This ruleset is for backwards compatibility. + + It contains the following rules: + + [CouplingBetweenObjects](pmd_rules_java_design.html#couplingbetweenobjects), [ExcessiveImports](pmd_rules_java_design.html#excessiveimports), [LawOfDemeter](pmd_rules_java_design.html#lawofdemeter), [LooseCoupling](pmd_rules_java_bestpractices.html#loosecoupling), [LoosePackageCoupling](pmd_rules_java_design.html#loosepackagecoupling) + +* Design (`rulesets/java/design.xml`): + + Deprecated This ruleset is for backwards compatibility. + + It contains the following rules: + + [AbstractClassWithoutAbstractMethod](pmd_rules_java_bestpractices.html#abstractclasswithoutabstractmethod), [AbstractClassWithoutAnyMethod](pmd_rules_java_design.html#abstractclasswithoutanymethod), [AccessorClassGeneration](pmd_rules_java_bestpractices.html#accessorclassgeneration), [AccessorMethodGeneration](pmd_rules_java_bestpractices.html#accessormethodgeneration), [AssignmentToNonFinalStatic](pmd_rules_java_errorprone.html#assignmenttononfinalstatic), [AvoidDeeplyNestedIfStmts](pmd_rules_java_design.html#avoiddeeplynestedifstmts), [AvoidInstanceofChecksInCatchClause](pmd_rules_java_errorprone.html#avoidinstanceofchecksincatchclause), [AvoidProtectedFieldInFinalClass](pmd_rules_java_codestyle.html#avoidprotectedfieldinfinalclass), [AvoidProtectedMethodInFinalClassNotExtending](pmd_rules_java_codestyle.html#avoidprotectedmethodinfinalclassnotextending), [AvoidReassigningParameters](pmd_rules_java_bestpractices.html#avoidreassigningparameters), [AvoidSynchronizedAtMethodLevel](pmd_rules_java_multithreading.html#avoidsynchronizedatmethodlevel), [BadComparison](pmd_rules_java_errorprone.html#badcomparison), [ClassWithOnlyPrivateConstructorsShouldBeFinal](pmd_rules_java_design.html#classwithonlyprivateconstructorsshouldbefinal), [CloseResource](pmd_rules_java_errorprone.html#closeresource), [CompareObjectsWithEquals](pmd_rules_java_errorprone.html#compareobjectswithequals), [ConfusingTernary](pmd_rules_java_codestyle.html#confusingternary), [ConstantsInInterface](pmd_rules_java_bestpractices.html#constantsininterface), [ConstructorCallsOverridableMethod](pmd_rules_java_errorprone.html#constructorcallsoverridablemethod), [DataClass](pmd_rules_java_design.html#dataclass), [DefaultLabelNotLastInSwitchStmt](pmd_rules_java_bestpractices.html#defaultlabelnotlastinswitchstmt), [EmptyMethodInAbstractClassShouldBeAbstract](pmd_rules_java_codestyle.html#emptymethodinabstractclassshouldbeabstract), [EqualsNull](pmd_rules_java_errorprone.html#equalsnull), [FieldDeclarationsShouldBeAtStartOfClass](pmd_rules_java_codestyle.html#fielddeclarationsshouldbeatstartofclass), [FinalFieldCouldBeStatic](pmd_rules_java_design.html#finalfieldcouldbestatic), [GodClass](pmd_rules_java_design.html#godclass), [IdempotentOperations](pmd_rules_java_errorprone.html#idempotentoperations), [ImmutableField](pmd_rules_java_design.html#immutablefield), [InstantiationToGetClass](pmd_rules_java_errorprone.html#instantiationtogetclass), [LogicInversion](pmd_rules_java_design.html#logicinversion), [MissingBreakInSwitch](pmd_rules_java_errorprone.html#missingbreakinswitch), [MissingStaticMethodInNonInstantiatableClass](pmd_rules_java_errorprone.html#missingstaticmethodinnoninstantiatableclass), [NonCaseLabelInSwitchStatement](pmd_rules_java_errorprone.html#noncaselabelinswitchstatement), [NonStaticInitializer](pmd_rules_java_errorprone.html#nonstaticinitializer), [NonThreadSafeSingleton](pmd_rules_java_multithreading.html#nonthreadsafesingleton), [OptimizableToArrayCall](pmd_rules_java_performance.html#optimizabletoarraycall), [PositionLiteralsFirstInCaseInsensitiveComparisons](pmd_rules_java_bestpractices.html#positionliteralsfirstincaseinsensitivecomparisons), [PositionLiteralsFirstInComparisons](pmd_rules_java_bestpractices.html#positionliteralsfirstincomparisons), [PreserveStackTrace](pmd_rules_java_bestpractices.html#preservestacktrace), [ReturnEmptyArrayRatherThanNull](pmd_rules_java_errorprone.html#returnemptyarrayratherthannull), [SimpleDateFormatNeedsLocale](pmd_rules_java_errorprone.html#simpledateformatneedslocale), [SimplifyBooleanExpressions](pmd_rules_java_design.html#simplifybooleanexpressions), [SimplifyBooleanReturns](pmd_rules_java_design.html#simplifybooleanreturns), [SimplifyConditional](pmd_rules_java_design.html#simplifyconditional), [SingleMethodSingleton](pmd_rules_java_errorprone.html#singlemethodsingleton), [SingletonClassReturningNewInstance](pmd_rules_java_errorprone.html#singletonclassreturningnewinstance), [SingularField](pmd_rules_java_design.html#singularfield), [SwitchDensity](pmd_rules_java_design.html#switchdensity), [SwitchStmtsShouldHaveDefault](pmd_rules_java_bestpractices.html#switchstmtsshouldhavedefault), [TooFewBranchesForASwitchStatement](pmd_rules_java_performance.html#toofewbranchesforaswitchstatement), [UncommentedEmptyConstructor](pmd_rules_java_documentation.html#uncommentedemptyconstructor), [UncommentedEmptyMethodBody](pmd_rules_java_documentation.html#uncommentedemptymethodbody), [UnnecessaryLocalBeforeReturn](pmd_rules_java_codestyle.html#unnecessarylocalbeforereturn), [UnsynchronizedStaticDateFormatter](pmd_rules_java_multithreading.html#unsynchronizedstaticdateformatter), [UseCollectionIsEmpty](pmd_rules_java_bestpractices.html#usecollectionisempty), [UseLocaleWithCaseConversions](pmd_rules_java_errorprone.html#uselocalewithcaseconversions), [UseNotifyAllInsteadOfNotify](pmd_rules_java_multithreading.html#usenotifyallinsteadofnotify), [UseUtilityClass](pmd_rules_java_design.html#useutilityclass), [UseVarargs](pmd_rules_java_bestpractices.html#usevarargs) + +* Empty Code (`rulesets/java/empty.xml`): + + Deprecated This ruleset is for backwards compatibility. + + It contains the following rules: + + [EmptyCatchBlock](pmd_rules_java_errorprone.html#emptycatchblock), [EmptyFinallyBlock](pmd_rules_java_errorprone.html#emptyfinallyblock), [EmptyIfStmt](pmd_rules_java_errorprone.html#emptyifstmt), [EmptyInitializer](pmd_rules_java_errorprone.html#emptyinitializer), [EmptyStatementBlock](pmd_rules_java_errorprone.html#emptystatementblock), [EmptyStatementNotInLoop](pmd_rules_java_errorprone.html#emptystatementnotinloop), [EmptyStaticInitializer](pmd_rules_java_errorprone.html#emptyinitializer), [EmptySwitchStatements](pmd_rules_java_errorprone.html#emptyswitchstatements), [EmptySynchronizedBlock](pmd_rules_java_errorprone.html#emptysynchronizedblock), [EmptyTryBlock](pmd_rules_java_errorprone.html#emptytryblock), [EmptyWhileStmt](pmd_rules_java_errorprone.html#emptywhilestmt) + +* Finalizer (`rulesets/java/finalizers.xml`): + + Deprecated This ruleset is for backwards compatibility. + + It contains the following rules: + + [AvoidCallingFinalize](pmd_rules_java_errorprone.html#avoidcallingfinalize), [EmptyFinalizer](pmd_rules_java_errorprone.html#emptyfinalizer), [FinalizeDoesNotCallSuperFinalize](pmd_rules_java_errorprone.html#finalizedoesnotcallsuperfinalize), [FinalizeOnlyCallsSuperFinalize](pmd_rules_java_errorprone.html#finalizeonlycallssuperfinalize), [FinalizeOverloaded](pmd_rules_java_errorprone.html#finalizeoverloaded), [FinalizeShouldBeProtected](pmd_rules_java_errorprone.html#finalizeshouldbeprotected) + +* Import Statements (`rulesets/java/imports.xml`): + + Deprecated This ruleset is for backwards compatibility. + + It contains the following rules: + + [DontImportJavaLang](pmd_rules_java_codestyle.html#dontimportjavalang), [DuplicateImports](pmd_rules_java_codestyle.html#duplicateimports), [ImportFromSamePackage](pmd_rules_java_errorprone.html#importfromsamepackage), [TooManyStaticImports](pmd_rules_java_codestyle.html#toomanystaticimports), [UnnecessaryFullyQualifiedName](pmd_rules_java_codestyle.html#unnecessaryfullyqualifiedname), [UnusedImports](pmd_rules_java_bestpractices.html#unusedimports) + +* J2EE (`rulesets/java/j2ee.xml`): + + Deprecated This ruleset is for backwards compatibility. + + It contains the following rules: + + [DoNotCallSystemExit](pmd_rules_java_errorprone.html#donotcallsystemexit), [DoNotUseThreads](pmd_rules_java_multithreading.html#donotusethreads), [LocalHomeNamingConvention](pmd_rules_java_codestyle.html#localhomenamingconvention), [LocalInterfaceSessionNamingConvention](pmd_rules_java_codestyle.html#localinterfacesessionnamingconvention), [MDBAndSessionBeanNamingConvention](pmd_rules_java_codestyle.html#mdbandsessionbeannamingconvention), [RemoteInterfaceNamingConvention](pmd_rules_java_codestyle.html#remoteinterfacenamingconvention), [RemoteSessionInterfaceNamingConvention](pmd_rules_java_codestyle.html#remotesessioninterfacenamingconvention), [StaticEJBFieldShouldBeFinal](pmd_rules_java_errorprone.html#staticejbfieldshouldbefinal), [UseProperClassLoader](pmd_rules_java_errorprone.html#useproperclassloader) + +* JavaBeans (`rulesets/java/javabeans.xml`): + + Deprecated This ruleset is for backwards compatibility. + + It contains the following rules: + + [BeanMembersShouldSerialize](pmd_rules_java_errorprone.html#beanmembersshouldserialize), [MissingSerialVersionUID](pmd_rules_java_errorprone.html#missingserialversionuid) + +* JUnit (`rulesets/java/junit.xml`): + + Deprecated This ruleset is for backwards compatibility. + + It contains the following rules: + + [JUnitAssertionsShouldIncludeMessage](pmd_rules_java_bestpractices.html#junitassertionsshouldincludemessage), [JUnitSpelling](pmd_rules_java_errorprone.html#junitspelling), [JUnitStaticSuite](pmd_rules_java_errorprone.html#junitstaticsuite), [JUnitTestContainsTooManyAsserts](pmd_rules_java_bestpractices.html#junittestcontainstoomanyasserts), [JUnitTestsShouldIncludeAssert](pmd_rules_java_bestpractices.html#junittestsshouldincludeassert), [SimplifyBooleanAssertion](pmd_rules_java_design.html#simplifybooleanassertion), [TestClassWithoutTestCases](pmd_rules_java_errorprone.html#testclasswithouttestcases), [UnnecessaryBooleanAssertion](pmd_rules_java_errorprone.html#unnecessarybooleanassertion), [UseAssertEqualsInsteadOfAssertTrue](pmd_rules_java_bestpractices.html#useassertequalsinsteadofasserttrue), [UseAssertNullInsteadOfAssertTrue](pmd_rules_java_bestpractices.html#useassertnullinsteadofasserttrue), [UseAssertSameInsteadOfAssertTrue](pmd_rules_java_bestpractices.html#useassertsameinsteadofasserttrue), [UseAssertTrueInsteadOfAssertEquals](pmd_rules_java_bestpractices.html#useasserttrueinsteadofassertequals) + +* Metrics (`rulesets/java/metrics.xml`): + + Deprecated This ruleset is for backwards compatibility. + + It contains the following rules: + + [CyclomaticComplexity](pmd_rules_java_design.html#cyclomaticcomplexity) + +* MigratingToJUnit4 (`rulesets/java/migrating_to_junit4.xml`): + + Deprecated This ruleset is for backwards compatibility. + + It contains the following rules: + + [JUnit4SuitesShouldUseSuiteAnnotation](pmd_rules_java_bestpractices.html#junit4suitesshouldusesuiteannotation), [JUnit4TestShouldUseAfterAnnotation](pmd_rules_java_bestpractices.html#junit4testshoulduseafterannotation), [JUnit4TestShouldUseBeforeAnnotation](pmd_rules_java_bestpractices.html#junit4testshouldusebeforeannotation), [JUnit4TestShouldUseTestAnnotation](pmd_rules_java_bestpractices.html#junit4testshouldusetestannotation), [JUnitUseExpected](pmd_rules_java_bestpractices.html#junituseexpected) + +* Migration (`rulesets/java/migrating.xml`): + + Deprecated This ruleset is for backwards compatibility. + + It contains the following rules: + + [AvoidAssertAsIdentifier](pmd_rules_java_errorprone.html#avoidassertasidentifier), [AvoidEnumAsIdentifier](pmd_rules_java_errorprone.html#avoidenumasidentifier), [ByteInstantiation](pmd_rules_java_performance.html#byteinstantiation), [ForLoopCanBeForeach](pmd_rules_java_bestpractices.html#forloopcanbeforeach), [IntegerInstantiation](pmd_rules_java_performance.html#integerinstantiation), [JUnit4SuitesShouldUseSuiteAnnotation](pmd_rules_java_bestpractices.html#junit4suitesshouldusesuiteannotation), [JUnit4TestShouldUseAfterAnnotation](pmd_rules_java_bestpractices.html#junit4testshoulduseafterannotation), [JUnit4TestShouldUseBeforeAnnotation](pmd_rules_java_bestpractices.html#junit4testshouldusebeforeannotation), [JUnit4TestShouldUseTestAnnotation](pmd_rules_java_bestpractices.html#junit4testshouldusetestannotation), [JUnitUseExpected](pmd_rules_java_bestpractices.html#junituseexpected), [LongInstantiation](pmd_rules_java_performance.html#longinstantiation), [ReplaceEnumerationWithIterator](pmd_rules_java_bestpractices.html#replaceenumerationwithiterator), [ReplaceHashtableWithMap](pmd_rules_java_bestpractices.html#replacehashtablewithmap), [ReplaceVectorWithList](pmd_rules_java_bestpractices.html#replacevectorwithlist), [ShortInstantiation](pmd_rules_java_performance.html#shortinstantiation) + +* Migration13 (`rulesets/java/migrating_to_13.xml`): + + Deprecated This ruleset is for backwards compatibility. + + It contains the following rules: + + [ReplaceEnumerationWithIterator](pmd_rules_java_bestpractices.html#replaceenumerationwithiterator), [ReplaceHashtableWithMap](pmd_rules_java_bestpractices.html#replacehashtablewithmap), [ReplaceVectorWithList](pmd_rules_java_bestpractices.html#replacevectorwithlist) + +* Migration14 (`rulesets/java/migrating_to_14.xml`): + + Deprecated This ruleset is for backwards compatibility. + + It contains the following rules: + + [AvoidAssertAsIdentifier](pmd_rules_java_errorprone.html#avoidassertasidentifier) + +* Migration15 (`rulesets/java/migrating_to_15.xml`): + + Deprecated This ruleset is for backwards compatibility. + + It contains the following rules: + + [AvoidEnumAsIdentifier](pmd_rules_java_errorprone.html#avoidenumasidentifier), [ByteInstantiation](pmd_rules_java_performance.html#byteinstantiation), [IntegerInstantiation](pmd_rules_java_performance.html#integerinstantiation), [LongInstantiation](pmd_rules_java_performance.html#longinstantiation), [ShortInstantiation](pmd_rules_java_performance.html#shortinstantiation) + +* Naming (`rulesets/java/naming.xml`): + + Deprecated This ruleset is for backwards compatibility. + + It contains the following rules: + + [AbstractNaming](pmd_rules_java_codestyle.html#abstractnaming), [AvoidDollarSigns](pmd_rules_java_codestyle.html#avoiddollarsigns), [AvoidFieldNameMatchingMethodName](pmd_rules_java_errorprone.html#avoidfieldnamematchingmethodname), [AvoidFieldNameMatchingTypeName](pmd_rules_java_errorprone.html#avoidfieldnamematchingtypename), [BooleanGetMethodName](pmd_rules_java_codestyle.html#booleangetmethodname), [ClassNamingConventions](pmd_rules_java_codestyle.html#classnamingconventions), [GenericsNaming](pmd_rules_java_codestyle.html#genericsnaming), [LongVariable](pmd_rules_java_codestyle.html#longvariable), [MethodNamingConventions](pmd_rules_java_codestyle.html#methodnamingconventions), [MethodWithSameNameAsEnclosingClass](pmd_rules_java_errorprone.html#methodwithsamenameasenclosingclass), [MisleadingVariableName](pmd_rules_java_codestyle.html#misleadingvariablename), [MIsLeadingVariableName](pmd_rules_java_codestyle.html#misleadingvariablename), [NoPackage](pmd_rules_java_codestyle.html#nopackage), [PackageCase](pmd_rules_java_codestyle.html#packagecase), [ShortClassName](pmd_rules_java_codestyle.html#shortclassname), [ShortMethodName](pmd_rules_java_codestyle.html#shortmethodname), [ShortVariable](pmd_rules_java_codestyle.html#shortvariable), [SuspiciousConstantFieldName](pmd_rules_java_codestyle.html#suspiciousconstantfieldname), [SuspiciousEqualsMethodName](pmd_rules_java_errorprone.html#suspiciousequalsmethodname), [SuspiciousHashcodeMethodName](pmd_rules_java_errorprone.html#suspicioushashcodemethodname), [VariableNamingConventions](pmd_rules_java_codestyle.html#variablenamingconventions) + +* Optimization (`rulesets/java/optimizations.xml`): + + Deprecated This ruleset is for backwards compatibility. + + It contains the following rules: + + [AddEmptyString](pmd_rules_java_performance.html#addemptystring), [AvoidArrayLoops](pmd_rules_java_performance.html#avoidarrayloops), [AvoidInstantiatingObjectsInLoops](pmd_rules_java_performance.html#avoidinstantiatingobjectsinloops), [LocalVariableCouldBeFinal](pmd_rules_java_codestyle.html#localvariablecouldbefinal), [MethodArgumentCouldBeFinal](pmd_rules_java_codestyle.html#methodargumentcouldbefinal), [PrematureDeclaration](pmd_rules_java_codestyle.html#prematuredeclaration), [RedundantFieldInitializer](pmd_rules_java_performance.html#redundantfieldinitializer), [SimplifyStartsWith](pmd_rules_java_performance.html#simplifystartswith), [UnnecessaryWrapperObjectCreation](pmd_rules_java_performance.html#unnecessarywrapperobjectcreation), [UseArrayListInsteadOfVector](pmd_rules_java_performance.html#usearraylistinsteadofvector), [UseArraysAsList](pmd_rules_java_performance.html#usearraysaslist), [UseStringBufferForStringAppends](pmd_rules_java_performance.html#usestringbufferforstringappends) + +* quickstart (`rulesets/java/quickstart.xml`): + + Quickstart configuration of PMD. Includes the rules that are most likely to apply everywhere. + + It contains the following rules: + + [AbstractClassWithoutAnyMethod](pmd_rules_java_design.html#abstractclasswithoutanymethod), [AssignmentInOperand](pmd_rules_java_errorprone.html#assignmentinoperand), [AssignmentToNonFinalStatic](pmd_rules_java_errorprone.html#assignmenttononfinalstatic), [AvoidAccessibilityAlteration](pmd_rules_java_errorprone.html#avoidaccessibilityalteration), [AvoidBranchingStatementAsLastInLoop](pmd_rules_java_errorprone.html#avoidbranchingstatementaslastinloop), [AvoidCatchingThrowable](pmd_rules_java_errorprone.html#avoidcatchingthrowable), [AvoidDecimalLiteralsInBigDecimalConstructor](pmd_rules_java_errorprone.html#avoiddecimalliteralsinbigdecimalconstructor), [AvoidDollarSigns](pmd_rules_java_codestyle.html#avoiddollarsigns), [AvoidInstanceofChecksInCatchClause](pmd_rules_java_errorprone.html#avoidinstanceofchecksincatchclause), [AvoidMultipleUnaryOperators](pmd_rules_java_errorprone.html#avoidmultipleunaryoperators), [AvoidProtectedFieldInFinalClass](pmd_rules_java_codestyle.html#avoidprotectedfieldinfinalclass), [AvoidProtectedMethodInFinalClassNotExtending](pmd_rules_java_codestyle.html#avoidprotectedmethodinfinalclassnotextending), [AvoidStringBufferField](pmd_rules_java_bestpractices.html#avoidstringbufferfield), [AvoidThreadGroup](pmd_rules_java_multithreading.html#avoidthreadgroup), [AvoidUsingHardCodedIP](pmd_rules_java_bestpractices.html#avoidusinghardcodedip), [AvoidUsingOctalValues](pmd_rules_java_errorprone.html#avoidusingoctalvalues), [AvoidUsingVolatile](pmd_rules_java_multithreading.html#avoidusingvolatile), [BadComparison](pmd_rules_java_errorprone.html#badcomparison), [BigIntegerInstantiation](pmd_rules_java_performance.html#bigintegerinstantiation), [BooleanInstantiation](pmd_rules_java_performance.html#booleaninstantiation), [BrokenNullCheck](pmd_rules_java_errorprone.html#brokennullcheck), [CheckResultSet](pmd_rules_java_bestpractices.html#checkresultset), [CheckSkipResult](pmd_rules_java_errorprone.html#checkskipresult), [ClassCastExceptionWithToArray](pmd_rules_java_errorprone.html#classcastexceptionwithtoarray), [ClassNamingConventions](pmd_rules_java_codestyle.html#classnamingconventions), [ClassWithOnlyPrivateConstructorsShouldBeFinal](pmd_rules_java_design.html#classwithonlyprivateconstructorsshouldbefinal), [CloneMethodMustBePublic](pmd_rules_java_errorprone.html#clonemethodmustbepublic), [CloneMethodMustImplementCloneable](pmd_rules_java_errorprone.html#clonemethodmustimplementcloneable), [CloneMethodReturnTypeMustMatchClassName](pmd_rules_java_errorprone.html#clonemethodreturntypemustmatchclassname), [CloneThrowsCloneNotSupportedException](pmd_rules_java_errorprone.html#clonethrowsclonenotsupportedexception), [CloseResource](pmd_rules_java_errorprone.html#closeresource), [CompareObjectsWithEquals](pmd_rules_java_errorprone.html#compareobjectswithequals), [ConstantsInInterface](pmd_rules_java_bestpractices.html#constantsininterface), [ControlStatementBraces](pmd_rules_java_codestyle.html#controlstatementbraces), [DefaultLabelNotLastInSwitchStmt](pmd_rules_java_bestpractices.html#defaultlabelnotlastinswitchstmt), [DoNotCallGarbageCollectionExplicitly](pmd_rules_java_errorprone.html#donotcallgarbagecollectionexplicitly), [DoNotExtendJavaLangError](pmd_rules_java_design.html#donotextendjavalangerror), [DoNotExtendJavaLangThrowable](pmd_rules_java_errorprone.html#donotextendjavalangthrowable), [DontCallThreadRun](pmd_rules_java_multithreading.html#dontcallthreadrun), [DontImportJavaLang](pmd_rules_java_codestyle.html#dontimportjavalang), [DontUseFloatTypeForLoopIndices](pmd_rules_java_errorprone.html#dontusefloattypeforloopindices), [DoubleCheckedLocking](pmd_rules_java_multithreading.html#doublecheckedlocking), [DuplicateImports](pmd_rules_java_codestyle.html#duplicateimports), [EmptyCatchBlock](pmd_rules_java_errorprone.html#emptycatchblock), [EmptyFinalizer](pmd_rules_java_errorprone.html#emptyfinalizer), [EmptyFinallyBlock](pmd_rules_java_errorprone.html#emptyfinallyblock), [EmptyIfStmt](pmd_rules_java_errorprone.html#emptyifstmt), [EmptyInitializer](pmd_rules_java_errorprone.html#emptyinitializer), [EmptyStatementBlock](pmd_rules_java_errorprone.html#emptystatementblock), [EmptyStatementNotInLoop](pmd_rules_java_errorprone.html#emptystatementnotinloop), [EmptySwitchStatements](pmd_rules_java_errorprone.html#emptyswitchstatements), [EmptySynchronizedBlock](pmd_rules_java_errorprone.html#emptysynchronizedblock), [EmptyTryBlock](pmd_rules_java_errorprone.html#emptytryblock), [EmptyWhileStmt](pmd_rules_java_errorprone.html#emptywhilestmt), [EqualsNull](pmd_rules_java_errorprone.html#equalsnull), [ExtendsObject](pmd_rules_java_codestyle.html#extendsobject), [FinalFieldCouldBeStatic](pmd_rules_java_design.html#finalfieldcouldbestatic), [ForLoopCanBeForeach](pmd_rules_java_bestpractices.html#forloopcanbeforeach), [ForLoopShouldBeWhileLoop](pmd_rules_java_codestyle.html#forloopshouldbewhileloop), [FormalParameterNamingConventions](pmd_rules_java_codestyle.html#formalparameternamingconventions), [GenericsNaming](pmd_rules_java_codestyle.html#genericsnaming), [GuardLogStatement](pmd_rules_java_bestpractices.html#guardlogstatement), [IdempotentOperations](pmd_rules_java_errorprone.html#idempotentoperations), [IdenticalCatchBranches](pmd_rules_java_codestyle.html#identicalcatchbranches), [ImportFromSamePackage](pmd_rules_java_errorprone.html#importfromsamepackage), [InstantiationToGetClass](pmd_rules_java_errorprone.html#instantiationtogetclass), [JumbledIncrementer](pmd_rules_java_errorprone.html#jumbledincrementer), [LocalVariableNamingConventions](pmd_rules_java_codestyle.html#localvariablenamingconventions), [LogicInversion](pmd_rules_java_design.html#logicinversion), [LooseCoupling](pmd_rules_java_bestpractices.html#loosecoupling), [MethodNamingConventions](pmd_rules_java_codestyle.html#methodnamingconventions), [MisplacedNullCheck](pmd_rules_java_errorprone.html#misplacednullcheck), [MissingBreakInSwitch](pmd_rules_java_errorprone.html#missingbreakinswitch), [MissingOverride](pmd_rules_java_bestpractices.html#missingoverride), [MissingStaticMethodInNonInstantiatableClass](pmd_rules_java_errorprone.html#missingstaticmethodinnoninstantiatableclass), [NonCaseLabelInSwitchStatement](pmd_rules_java_errorprone.html#noncaselabelinswitchstatement), [NonStaticInitializer](pmd_rules_java_errorprone.html#nonstaticinitializer), [NonThreadSafeSingleton](pmd_rules_java_multithreading.html#nonthreadsafesingleton), [NoPackage](pmd_rules_java_codestyle.html#nopackage), [OneDeclarationPerLine](pmd_rules_java_bestpractices.html#onedeclarationperline), [OptimizableToArrayCall](pmd_rules_java_performance.html#optimizabletoarraycall), [OverrideBothEqualsAndHashcode](pmd_rules_java_errorprone.html#overridebothequalsandhashcode), [PackageCase](pmd_rules_java_codestyle.html#packagecase), [PositionLiteralsFirstInCaseInsensitiveComparisons](pmd_rules_java_bestpractices.html#positionliteralsfirstincaseinsensitivecomparisons), [PositionLiteralsFirstInComparisons](pmd_rules_java_bestpractices.html#positionliteralsfirstincomparisons), [PreserveStackTrace](pmd_rules_java_bestpractices.html#preservestacktrace), [ProperCloneImplementation](pmd_rules_java_errorprone.html#propercloneimplementation), [ProperLogger](pmd_rules_java_errorprone.html#properlogger), [ReturnEmptyArrayRatherThanNull](pmd_rules_java_errorprone.html#returnemptyarrayratherthannull), [ReturnFromFinallyBlock](pmd_rules_java_errorprone.html#returnfromfinallyblock), [SimplifiedTernary](pmd_rules_java_design.html#simplifiedternary), [SimplifyBooleanReturns](pmd_rules_java_design.html#simplifybooleanreturns), [SimplifyConditional](pmd_rules_java_design.html#simplifyconditional), [SingleMethodSingleton](pmd_rules_java_errorprone.html#singlemethodsingleton), [SingletonClassReturningNewInstance](pmd_rules_java_errorprone.html#singletonclassreturningnewinstance), [SingularField](pmd_rules_java_design.html#singularfield), [SuspiciousEqualsMethodName](pmd_rules_java_errorprone.html#suspiciousequalsmethodname), [SuspiciousHashcodeMethodName](pmd_rules_java_errorprone.html#suspicioushashcodemethodname), [SuspiciousOctalEscape](pmd_rules_java_errorprone.html#suspiciousoctalescape), [SwitchStmtsShouldHaveDefault](pmd_rules_java_bestpractices.html#switchstmtsshouldhavedefault), [UncommentedEmptyConstructor](pmd_rules_java_documentation.html#uncommentedemptyconstructor), [UncommentedEmptyMethodBody](pmd_rules_java_documentation.html#uncommentedemptymethodbody), [UnconditionalIfStatement](pmd_rules_java_errorprone.html#unconditionalifstatement), [UnnecessaryAnnotationValueElement](pmd_rules_java_codestyle.html#unnecessaryannotationvalueelement), [UnnecessaryConstructor](pmd_rules_java_codestyle.html#unnecessaryconstructor), [UnnecessaryConversionTemporary](pmd_rules_java_errorprone.html#unnecessaryconversiontemporary), [UnnecessaryFullyQualifiedName](pmd_rules_java_codestyle.html#unnecessaryfullyqualifiedname), [UnnecessaryLocalBeforeReturn](pmd_rules_java_codestyle.html#unnecessarylocalbeforereturn), [UnnecessaryModifier](pmd_rules_java_codestyle.html#unnecessarymodifier), [UnnecessaryReturn](pmd_rules_java_codestyle.html#unnecessaryreturn), [UnsynchronizedStaticDateFormatter](pmd_rules_java_multithreading.html#unsynchronizedstaticdateformatter), [UnusedFormalParameter](pmd_rules_java_bestpractices.html#unusedformalparameter), [UnusedImports](pmd_rules_java_bestpractices.html#unusedimports), [UnusedLocalVariable](pmd_rules_java_bestpractices.html#unusedlocalvariable), [UnusedNullCheckInEquals](pmd_rules_java_errorprone.html#unusednullcheckinequals), [UnusedPrivateField](pmd_rules_java_bestpractices.html#unusedprivatefield), [UnusedPrivateMethod](pmd_rules_java_bestpractices.html#unusedprivatemethod), [UseAssertEqualsInsteadOfAssertTrue](pmd_rules_java_bestpractices.html#useassertequalsinsteadofasserttrue), [UseAssertNullInsteadOfAssertTrue](pmd_rules_java_bestpractices.html#useassertnullinsteadofasserttrue), [UseAssertSameInsteadOfAssertTrue](pmd_rules_java_bestpractices.html#useassertsameinsteadofasserttrue), [UseAssertTrueInsteadOfAssertEquals](pmd_rules_java_bestpractices.html#useasserttrueinsteadofassertequals), [UseCollectionIsEmpty](pmd_rules_java_bestpractices.html#usecollectionisempty), [UseEqualsToCompareStrings](pmd_rules_java_errorprone.html#useequalstocomparestrings), [UselessOperationOnImmutable](pmd_rules_java_errorprone.html#uselessoperationonimmutable), [UselessOverridingMethod](pmd_rules_java_design.html#uselessoverridingmethod), [UselessParentheses](pmd_rules_java_codestyle.html#uselessparentheses), [UselessQualifiedThis](pmd_rules_java_codestyle.html#uselessqualifiedthis), [UseLocaleWithCaseConversions](pmd_rules_java_errorprone.html#uselocalewithcaseconversions), [UseNotifyAllInsteadOfNotify](pmd_rules_java_multithreading.html#usenotifyallinsteadofnotify), [UseUtilityClass](pmd_rules_java_design.html#useutilityclass) + +* Security Code Guidelines (`rulesets/java/sunsecure.xml`): + + Deprecated This ruleset is for backwards compatibility. + + It contains the following rules: + + [ArrayIsStoredDirectly](pmd_rules_java_bestpractices.html#arrayisstoreddirectly), [MethodReturnsInternalArray](pmd_rules_java_bestpractices.html#methodreturnsinternalarray) + +* Strict Exceptions (`rulesets/java/strictexception.xml`): + + Deprecated This ruleset is for backwards compatibility. + + It contains the following rules: + + [AvoidCatchingGenericException](pmd_rules_java_design.html#avoidcatchinggenericexception), [AvoidCatchingNPE](pmd_rules_java_errorprone.html#avoidcatchingnpe), [AvoidCatchingThrowable](pmd_rules_java_errorprone.html#avoidcatchingthrowable), [AvoidLosingExceptionInformation](pmd_rules_java_errorprone.html#avoidlosingexceptioninformation), [AvoidRethrowingException](pmd_rules_java_design.html#avoidrethrowingexception), [AvoidThrowingNewInstanceOfSameException](pmd_rules_java_design.html#avoidthrowingnewinstanceofsameexception), [AvoidThrowingNullPointerException](pmd_rules_java_design.html#avoidthrowingnullpointerexception), [AvoidThrowingRawExceptionTypes](pmd_rules_java_design.html#avoidthrowingrawexceptiontypes), [DoNotExtendJavaLangError](pmd_rules_java_design.html#donotextendjavalangerror), [DoNotThrowExceptionInFinally](pmd_rules_java_errorprone.html#donotthrowexceptioninfinally), [ExceptionAsFlowControl](pmd_rules_java_design.html#exceptionasflowcontrol), [SignatureDeclareThrowsException](pmd_rules_java_design.html#signaturedeclarethrowsexception) + +* String and StringBuffer (`rulesets/java/strings.xml`): + + Deprecated This ruleset is for backwards compatibility. + + It contains the following rules: + + [AppendCharacterWithChar](pmd_rules_java_performance.html#appendcharacterwithchar), [AvoidDuplicateLiterals](pmd_rules_java_errorprone.html#avoidduplicateliterals), [AvoidStringBufferField](pmd_rules_java_bestpractices.html#avoidstringbufferfield), [ConsecutiveAppendsShouldReuse](pmd_rules_java_performance.html#consecutiveappendsshouldreuse), [ConsecutiveLiteralAppends](pmd_rules_java_performance.html#consecutiveliteralappends), [InefficientEmptyStringCheck](pmd_rules_java_performance.html#inefficientemptystringcheck), [InefficientStringBuffering](pmd_rules_java_performance.html#inefficientstringbuffering), [InsufficientStringBufferDeclaration](pmd_rules_java_performance.html#insufficientstringbufferdeclaration), [StringBufferInstantiationWithChar](pmd_rules_java_errorprone.html#stringbufferinstantiationwithchar), [StringInstantiation](pmd_rules_java_performance.html#stringinstantiation), [StringToString](pmd_rules_java_performance.html#stringtostring), [UnnecessaryCaseChange](pmd_rules_java_errorprone.html#unnecessarycasechange), [UseEqualsToCompareStrings](pmd_rules_java_errorprone.html#useequalstocomparestrings), [UseIndexOfChar](pmd_rules_java_performance.html#useindexofchar), [UselessStringValueOf](pmd_rules_java_performance.html#uselessstringvalueof), [UseStringBufferLength](pmd_rules_java_performance.html#usestringbufferlength) + +* Type Resolution (`rulesets/java/typeresolution.xml`): + + Deprecated This ruleset is for backwards compatibility. + + It contains the following rules: + + [CloneMethodMustImplementCloneable](pmd_rules_java_errorprone.html#clonemethodmustimplementcloneable), [LooseCoupling](pmd_rules_java_bestpractices.html#loosecoupling), [SignatureDeclareThrowsException](pmd_rules_java_design.html#signaturedeclarethrowsexception), [UnusedImports](pmd_rules_java_bestpractices.html#unusedimports) + +* Unnecessary (`rulesets/java/unnecessary.xml`): + + Deprecated This ruleset is for backwards compatibility. + + It contains the following rules: + + [UnnecessaryConversionTemporary](pmd_rules_java_errorprone.html#unnecessaryconversiontemporary), [UnnecessaryFinalModifier](pmd_rules_java_codestyle.html#unnecessarymodifier), [UnnecessaryModifier](pmd_rules_java_codestyle.html#unnecessarymodifier), [UnnecessaryReturn](pmd_rules_java_codestyle.html#unnecessaryreturn), [UnusedNullCheckInEquals](pmd_rules_java_errorprone.html#unusednullcheckinequals), [UselessOperationOnImmutable](pmd_rules_java_errorprone.html#uselessoperationonimmutable), [UselessOverridingMethod](pmd_rules_java_design.html#uselessoverridingmethod), [UselessParentheses](pmd_rules_java_codestyle.html#uselessparentheses), [UselessQualifiedThis](pmd_rules_java_codestyle.html#uselessqualifiedthis) + +* Unused Code (`rulesets/java/unusedcode.xml`): + + Deprecated This ruleset is for backwards compatibility. + + It contains the following rules: + + [UnusedFormalParameter](pmd_rules_java_bestpractices.html#unusedformalparameter), [UnusedLocalVariable](pmd_rules_java_bestpractices.html#unusedlocalvariable), [UnusedPrivateField](pmd_rules_java_bestpractices.html#unusedprivatefield), [UnusedPrivateMethod](pmd_rules_java_bestpractices.html#unusedprivatemethod) + diff --git a/docs/pages/pmd/rules/java/android.md b/docs/pages/pmd/rules/java/android.md deleted file mode 100644 index a2ae5d4ed4d..00000000000 --- a/docs/pages/pmd/rules/java/android.md +++ /dev/null @@ -1,124 +0,0 @@ ---- -title: Android -summary: These rules deal with the Android SDK, mostly related to best practices. To get better results, make sure that the auxclasspath is defined for type resolution to work. -permalink: pmd_rules_java_android.html -folder: pmd/rules/java -sidebaractiveurl: /pmd_rules_java.html -editmepath: ../pmd-java/src/main/resources/rulesets/java/android.xml -keywords: Android, CallSuperFirst, CallSuperLast, DoNotHardCodeSDCard ---- -## CallSuperFirst - -**Since:** PMD 4.2.5 - -**Priority:** Medium (3) - -Super should be called at the start of the method - -``` -//MethodDeclaration[MethodDeclarator[ - @Image='onCreate' or - @Image='onConfigurationChanged' or - @Image='onPostCreate' or - @Image='onPostResume' or - @Image='onRestart' or - @Image='onRestoreInstanceState' or - @Image='onResume' or - @Image='onStart' - ]] - /Block[not( - (BlockStatement[1]/Statement/StatementExpression/PrimaryExpression[./PrimaryPrefix[@SuperModifier='true']]/PrimarySuffix[@Image= ancestor::MethodDeclaration/MethodDeclarator/@Image]))] -[ancestor::ClassOrInterfaceDeclaration[ExtendsList/ClassOrInterfaceType[ - typeof(@Image, 'android.app.Activity', 'Activity') or - typeof(@Image, 'android.app.Application', 'Application') or - typeof(@Image, 'android.app.Service', 'Service') -]]] -``` - -**Example(s):** - -``` java -public class DummyActivity extends Activity { - public void onCreate(Bundle bundle) { - // missing call to super.onCreate(bundle) - foo(); - } -} -``` - -**Use this rule by referencing it:** -``` xml - -``` - -## CallSuperLast - -**Since:** PMD 4.2.5 - -**Priority:** Medium (3) - -Super should be called at the end of the method - -``` -//MethodDeclaration[MethodDeclarator[ - @Image='finish' or - @Image='onDestroy' or - @Image='onPause' or - @Image='onSaveInstanceState' or - @Image='onStop' or - @Image='onTerminate' - ]] - /Block/BlockStatement[last()] - [not(Statement/StatementExpression/PrimaryExpression[./PrimaryPrefix[@SuperModifier='true']]/PrimarySuffix[@Image= ancestor::MethodDeclaration/MethodDeclarator/@Image])] -[ancestor::ClassOrInterfaceDeclaration[ExtendsList/ClassOrInterfaceType[ - typeof(@Image, 'android.app.Activity', 'Activity') or - typeof(@Image, 'android.app.Application', 'Application') or - typeof(@Image, 'android.app.Service', 'Service') -]]] -``` - -**Example(s):** - -``` java -public class DummyActivity extends Activity { - public void onPause() { - foo(); - // missing call to super.onPause() - } -} -``` - -**Use this rule by referencing it:** -``` xml - -``` - -## DoNotHardCodeSDCard - -**Since:** PMD 4.2.6 - -**Priority:** Medium (3) - -Use Environment.getExternalStorageDirectory() instead of "/sdcard" - -``` -//Literal[starts-with(@Image,'"/sdcard')] -``` - -**Example(s):** - -``` java -public class MyActivity extends Activity { - protected void foo() { - String storageLocation = "/sdcard/mypackage"; // hard-coded, poor approach - - storageLocation = Environment.getExternalStorageDirectory() + "/mypackage"; // preferred approach - } -} -``` - -**Use this rule by referencing it:** -``` xml - -``` - diff --git a/docs/pages/pmd/rules/java/basic.md b/docs/pages/pmd/rules/java/basic.md deleted file mode 100644 index 6399d7a262b..00000000000 --- a/docs/pages/pmd/rules/java/basic.md +++ /dev/null @@ -1,871 +0,0 @@ ---- -title: Basic -summary: The Basic ruleset contains a collection of good practices which should be followed. -permalink: pmd_rules_java_basic.html -folder: pmd/rules/java -sidebaractiveurl: /pmd_rules_java.html -editmepath: ../pmd-java/src/main/resources/rulesets/java/basic.xml -keywords: Basic, JumbledIncrementer, ForLoopShouldBeWhileLoop, OverrideBothEqualsAndHashcode, DoubleCheckedLocking, ReturnFromFinallyBlock, UnconditionalIfStatement, BooleanInstantiation, CollapsibleIfStatements, ClassCastExceptionWithToArray, AvoidDecimalLiteralsInBigDecimalConstructor, MisplacedNullCheck, AvoidThreadGroup, BrokenNullCheck, BigIntegerInstantiation, AvoidUsingOctalValues, AvoidUsingHardCodedIP, CheckResultSet, AvoidMultipleUnaryOperators, ExtendsObject, CheckSkipResult, AvoidBranchingStatementAsLastInLoop, DontCallThreadRun, DontUseFloatTypeForLoopIndices, SimplifiedTernary ---- -## AvoidBranchingStatementAsLastInLoop - -**Since:** PMD 5.0 - -**Priority:** Medium High (2) - -Using a branching statement as the last part of a loop may be a bug, and/or is confusing. -Ensure that the usage is not a bug, or consider using another approach. - -**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.basic.AvoidBranchingStatementAsLastInLoopRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/basic/AvoidBranchingStatementAsLastInLoopRule.java) - -**Example(s):** - -``` java -// unusual use of branching statement in a loop -for (int i = 0; i < 10; i++) { - if (i*i <= 25) { - continue; - } - break; -} - - // this makes more sense... -for (int i = 0; i < 10; i++) { - if (i*i > 25) { - break; - } -} -``` - -**This rule has the following properties:** - -|Name|Default Value|Description| -|----|-------------|-----------| -|checkReturnLoopTypes|[for, do, while]|Check for return statements in loop types| -|checkContinueLoopTypes|[for, do, while]|Check for continue statements in loop types| -|checkBreakLoopTypes|[for, do, while]|Check for break statements in loop types| - -**Use this rule by referencing it:** -``` xml - -``` - -## AvoidDecimalLiteralsInBigDecimalConstructor - -**Since:** PMD 3.4 - -**Priority:** Medium (3) - -One might assume that the result of "new BigDecimal(0.1)" is exactly equal to 0.1, but it is actually -equal to .1000000000000000055511151231257827021181583404541015625. -This is because 0.1 cannot be represented exactly as a double (or as a binary fraction of any finite -length). Thus, the long value that is being passed in to the constructor is not exactly equal to 0.1, -appearances notwithstanding. - -The (String) constructor, on the other hand, is perfectly predictable: 'new BigDecimal("0.1")' is -exactly equal to 0.1, as one would expect. Therefore, it is generally recommended that the -(String) constructor be used in preference to this one. - -``` -//AllocationExpression -[ClassOrInterfaceType[@Image="BigDecimal"]] -[Arguments/ArgumentList/Expression/PrimaryExpression/PrimaryPrefix - [ - Literal[(not(ends-with(@Image,'"'))) and contains(@Image,".")] - or - Name[ancestor::Block/BlockStatement/LocalVariableDeclaration - [Type[PrimitiveType[@Image='double' or @Image='float'] - or ReferenceType/ClassOrInterfaceType[@Image='Double' or @Image='Float']]] - /VariableDeclarator/VariableDeclaratorId/@Image = @Image - ] - or - Name[ancestor::MethodDeclaration/MethodDeclarator/FormalParameters/FormalParameter - [Type[PrimitiveType[@Image='double' or @Image='float'] - or ReferenceType/ClassOrInterfaceType[@Image='Double' or @Image='Float']]] - /VariableDeclaratorId/@Image = @Image - ] - ] -] -``` - -**Example(s):** - -``` java -BigDecimal bd = new BigDecimal(1.123); // loss of precision, this would trigger the rule - -BigDecimal bd = new BigDecimal("1.123"); // preferred approach - -BigDecimal bd = new BigDecimal(12); // preferred approach, ok for integer values -``` - -**Use this rule by referencing it:** -``` xml - -``` - -## AvoidMultipleUnaryOperators - -**Since:** PMD 4.2 - -**Priority:** Medium High (2) - -The use of multiple unary operators may be problematic, and/or confusing. -Ensure that the intended usage is not a bug, or consider simplifying the expression. - -**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.basic.AvoidMultipleUnaryOperatorsRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/basic/AvoidMultipleUnaryOperatorsRule.java) - -**Example(s):** - -``` java -// These are typo bugs, or at best needlessly complex and confusing: -int i = - -1; -int j = + - +1; -int z = ~~2; -boolean b = !!true; -boolean c = !!!true; - -// These are better: -int i = 1; -int j = -1; -int z = 2; -boolean b = true; -boolean c = false; - -// And these just make your brain hurt: -int i = ~-2; -int j = -~7; -``` - -**Use this rule by referencing it:** -``` xml - -``` - -## AvoidThreadGroup - -**Since:** PMD 3.6 - -**Priority:** Medium (3) - -Avoid using java.lang.ThreadGroup; although it is intended to be used in a threaded environment -it contains methods that are not thread-safe. - -``` -//AllocationExpression/ClassOrInterfaceType[pmd-java:typeof(@Image, 'java.lang.ThreadGroup')]| -//PrimarySuffix[contains(@Image, 'getThreadGroup')] -``` - -**Example(s):** - -``` java -public class Bar { - void buz() { - ThreadGroup tg = new ThreadGroup("My threadgroup"); - tg = new ThreadGroup(tg, "my thread group"); - tg = Thread.currentThread().getThreadGroup(); - tg = System.getSecurityManager().getThreadGroup(); - } -} -``` - -**Use this rule by referencing it:** -``` xml - -``` - -## AvoidUsingHardCodedIP - -**Since:** PMD 4.1 - -**Priority:** Medium (3) - -Application with hard-coded IP addresses can become impossible to deploy in some cases. -Externalizing IP adresses is preferable. - -**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.basic.AvoidUsingHardCodedIPRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/basic/AvoidUsingHardCodedIPRule.java) - -**Example(s):** - -``` java -public class Foo { - private String ip = "127.0.0.1"; // not recommended -} -``` - -**This rule has the following properties:** - -|Name|Default Value|Description| -|----|-------------|-----------| -|checkAddressTypes|[IPv4, IPv6, IPv4 mapped IPv6]|Check for IP address types.| -|pattern|^"[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}"$|Regular Expression| - -**Use this rule by referencing it:** -``` xml - -``` - -## AvoidUsingOctalValues - -**Since:** PMD 3.9 - -**Priority:** Medium (3) - -Integer literals should not start with zero since this denotes that the rest of literal will be -interpreted as an octal value. - -**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.basic.AvoidUsingOctalValuesRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/basic/AvoidUsingOctalValuesRule.java) - -**Example(s):** - -``` java -int i = 012; // set i with 10 not 12 -int j = 010; // set j with 8 not 10 -k = i * j; // set k with 80 not 120 -``` - -**This rule has the following properties:** - -|Name|Default Value|Description| -|----|-------------|-----------| -|strict|false|Detect violations between 00 and 07| - -**Use this rule by referencing it:** -``` xml - -``` - -## BigIntegerInstantiation - -**Since:** PMD 3.9 - -**Priority:** Medium (3) - -Don't create instances of already existing BigInteger (BigInteger.ZERO, BigInteger.ONE) and -for Java 1.5 onwards, BigInteger.TEN and BigDecimal (BigDecimal.ZERO, BigDecimal.ONE, BigDecimal.TEN) - -**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.basic.BigIntegerInstantiationRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/basic/BigIntegerInstantiationRule.java) - -**Example(s):** - -``` java -BigInteger bi = new BigInteger(1); // reference BigInteger.ONE instead -BigInteger bi2 = new BigInteger("0"); // reference BigInteger.ZERO instead -BigInteger bi3 = new BigInteger(0.0); // reference BigInteger.ZERO instead -BigInteger bi4; -bi4 = new BigInteger(0); // reference BigInteger.ZERO instead -``` - -**Use this rule by referencing it:** -``` xml - -``` - -## BooleanInstantiation - -**Since:** PMD 1.2 - -**Priority:** Medium High (2) - -Avoid instantiating Boolean objects; you can reference Boolean.TRUE, Boolean.FALSE, or call Boolean.valueOf() instead. - -**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.basic.BooleanInstantiationRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/basic/BooleanInstantiationRule.java) - -**Example(s):** - -``` java -Boolean bar = new Boolean("true"); // unnecessary creation, just reference Boolean.TRUE; -Boolean buz = Boolean.valueOf(false); // ...., just reference Boolean.FALSE; -``` - -**Use this rule by referencing it:** -``` xml - -``` - -## BrokenNullCheck - -**Since:** PMD 3.8 - -**Priority:** Medium High (2) - -The null check is broken since it will throw a NullPointerException itself. -It is likely that you used || instead of && or vice versa. - -**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.basic.BrokenNullCheckRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/basic/BrokenNullCheckRule.java) - -**Example(s):** - -``` java -public String bar(String string) { - // should be && - if (string!=null || !string.equals("")) - return string; - // should be || - if (string==null && string.equals("")) - return string; -} -``` - -**Use this rule by referencing it:** -``` xml - -``` - -## CheckResultSet - -**Since:** PMD 4.1 - -**Priority:** Medium (3) - -Always check the return values of navigation methods (next, previous, first, last) of a ResultSet. -If the value return is 'false', it should be handled properly. - -**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.basic.CheckResultSetRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/basic/CheckResultSetRule.java) - -**Example(s):** - -``` java -Statement stat = conn.createStatement(); -ResultSet rst = stat.executeQuery("SELECT name FROM person"); -rst.next(); // what if it returns false? bad form -String firstName = rst.getString(1); - -Statement stat = conn.createStatement(); -ResultSet rst = stat.executeQuery("SELECT name FROM person"); -if (rst.next()) { // result is properly examined and used - String firstName = rst.getString(1); - } else { - // handle missing data -} -``` - -**Use this rule by referencing it:** -``` xml - -``` - -## CheckSkipResult - -**Since:** PMD 5.0 - -**Priority:** Medium (3) - -The skip() method may skip a smaller number of bytes than requested. Check the returned value to find out if it was the case or not. - -**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.basic.CheckSkipResultRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/basic/CheckSkipResultRule.java) - -**Example(s):** - -``` java -public class Foo { - - private FileInputStream _s = new FileInputStream("file"); - - public void skip(int n) throws IOException { - _s.skip(n); // You are not sure that exactly n bytes are skipped - } - - public void skipExactly(int n) throws IOException { - while (n != 0) { - long skipped = _s.skip(n); - if (skipped == 0) - throw new EOFException(); - n -= skipped; - } - } -``` - -**Use this rule by referencing it:** -``` xml - -``` - -## ClassCastExceptionWithToArray - -**Since:** PMD 3.4 - -**Priority:** Medium (3) - -When deriving an array of a specific class from your Collection, one should provide an array of -the same class as the parameter of the toArray() method. Doing otherwise you will will result -in a ClassCastException. - -``` -//CastExpression[Type/ReferenceType/ClassOrInterfaceType[@Image != -"Object"]]/PrimaryExpression -[ - PrimaryPrefix/Name[ends-with(@Image, '.toArray')] - and - PrimarySuffix/Arguments[count(*) = 0] -and -count(PrimarySuffix) = 1 -] -``` - -**Example(s):** - -``` java -Collection c = new ArrayList(); -Integer obj = new Integer(1); -c.add(obj); - - // this would trigger the rule (and throw a ClassCastException if executed) -Integer[] a = (Integer [])c.toArray(); - - // this is fine and will not trigger the rule -Integer[] b = (Integer [])c.toArray(new Integer[c.size()]); -``` - -**Use this rule by referencing it:** -``` xml - -``` - -## CollapsibleIfStatements - -**Since:** PMD 3.1 - -**Priority:** Medium (3) - -Sometimes two consecutive 'if' statements can be consolidated by separating their conditions with a boolean short-circuit operator. - -``` -//IfStatement[@Else='false']/Statement - /IfStatement[@Else='false'] - | -//IfStatement[@Else='false']/Statement - /Block[count(BlockStatement)=1]/BlockStatement - /Statement/IfStatement[@Else='false'] -``` - -**Example(s):** - -``` java -void bar() { - if (x) { // original implementation - if (y) { - // do stuff - } - } -} - -void bar() { - if (x && y) { // optimized implementation - // do stuff - } -} -``` - -**Use this rule by referencing it:** -``` xml - -``` - -## DontCallThreadRun - -**Since:** PMD 4.3 - -**Priority:** Medium Low (4) - -Explicitly calling Thread.run() method will execute in the caller's thread of control. Instead, call Thread.start() for the intended behavior. - -``` -//StatementExpression/PrimaryExpression -[ - PrimaryPrefix - [ - ./Name[ends-with(@Image, '.run') or @Image = 'run'] - and substring-before(Name/@Image, '.') =//VariableDeclarator/VariableDeclaratorId/@Image - [../../../Type/ReferenceType/ClassOrInterfaceType[typeof(@Image, 'java.lang.Thread', 'Thread')]] - or (./AllocationExpression/ClassOrInterfaceType[typeof(@Image, 'java.lang.Thread', 'Thread')] - and ../PrimarySuffix[@Image = 'run']) - ] -] -``` - -**Example(s):** - -``` java -Thread t = new Thread(); -t.run(); // use t.start() instead -new Thread().run(); // same violation -``` - -**Use this rule by referencing it:** -``` xml - -``` - -## DontUseFloatTypeForLoopIndices - -**Since:** PMD 4.3 - -**Priority:** Medium (3) - -Don't use floating point for loop indices. If you must use floating point, use double -unless you're certain that float provides enough precision and you have a compelling -performance need (space or time). - -``` -//ForStatement/ForInit/LocalVariableDeclaration -/Type/PrimitiveType[@Image="float"] -``` - -**Example(s):** - -``` java -public class Count { - public static void main(String[] args) { - final int START = 2000000000; - int count = 0; - for (float f = START; f < START + 50; f++) - count++; - //Prints 0 because (float) START == (float) (START + 50). - System.out.println(count); - //The termination test misbehaves due to floating point granularity. - } -} -``` - -**Use this rule by referencing it:** -``` xml - -``` - -## DoubleCheckedLocking - -**Since:** PMD 1.04 - -**Priority:** High (1) - -Partially created objects can be returned by the Double Checked Locking pattern when used in Java. -An optimizing JRE may assign a reference to the baz variable before it calls the constructor of the object the -reference points to. - -Note: With Java 5, you can make Double checked locking work, if you declare the variable to be `volatile`. - -For more details refer to: -or - -**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.basic.DoubleCheckedLockingRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/basic/DoubleCheckedLockingRule.java) - -**Example(s):** - -``` java -public class Foo { - /*volatile */ Object baz = null; // fix for Java5 and later: volatile - Object bar() { - if (baz == null) { // baz may be non-null yet not fully created - synchronized(this) { - if (baz == null) { - baz = new Object(); - } - } - } - return baz; - } -} -``` - -**Use this rule by referencing it:** -``` xml - -``` - -## ExtendsObject - -**Since:** PMD 5.0 - -**Priority:** Medium Low (4) - -No need to explicitly extend Object. - -``` -//ExtendsList/ClassOrInterfaceType[@Image='Object' or @Image='java.lang.Object'] -``` - -**Example(s):** - -``` java -public class Foo extends Object { // not required -} -``` - -**Use this rule by referencing it:** -``` xml - -``` - -## ForLoopShouldBeWhileLoop - -**Since:** PMD 1.02 - -**Priority:** Medium (3) - -Some for loops can be simplified to while loops, this makes them more concise. - -``` -//ForStatement - [count(*) > 1] - [not(LocalVariableDeclaration)] - [not(ForInit)] - [not(ForUpdate)] - [not(Type and Expression and Statement)] -``` - -**Example(s):** - -``` java -public class Foo { - void bar() { - for (;true;) true; // No Init or Update part, may as well be: while (true) - } -} -``` - -**Use this rule by referencing it:** -``` xml - -``` - -## JumbledIncrementer - -**Since:** PMD 1.0 - -**Priority:** Medium (3) - -Avoid jumbled loop incrementers - its usually a mistake, and is confusing even if intentional. - -``` -//ForStatement - [ - ForUpdate/StatementExpressionList/StatementExpression/PostfixExpression/PrimaryExpression/PrimaryPrefix/Name/@Image - = - ancestor::ForStatement/ForInit//VariableDeclaratorId/@Image - ] -``` - -**Example(s):** - -``` java -public class JumbledIncrementerRule1 { - public void foo() { - for (int i = 0; i < 10; i++) { // only references 'i' - for (int k = 0; k < 20; i++) { // references both 'i' and 'k' - System.out.println("Hello"); - } - } - } -} -``` - -**Use this rule by referencing it:** -``` xml - -``` - -## MisplacedNullCheck - -**Since:** PMD 3.5 - -**Priority:** Medium (3) - -The null check here is misplaced. If the variable is null a NullPointerException will be thrown. -Either the check is useless (the variable will never be "null") or it is incorrect. - -``` -//Expression - /*[self::ConditionalOrExpression or self::ConditionalAndExpression] - /descendant::PrimaryExpression/PrimaryPrefix - /Name[starts-with(@Image, - concat(ancestor::PrimaryExpression/following-sibling::EqualityExpression - [./PrimaryExpression/PrimaryPrefix/Literal/NullLiteral] - /PrimaryExpression/PrimaryPrefix - /Name[count(../../PrimarySuffix)=0]/@Image,".") - ) - ] - [count(ancestor::ConditionalAndExpression/EqualityExpression - [@Image='!='] - [./PrimaryExpression/PrimaryPrefix/Literal/NullLiteral] - [starts-with(following-sibling::*/PrimaryExpression/PrimaryPrefix/Name/@Image, - concat(./PrimaryExpression/PrimaryPrefix/Name/@Image, '.'))] - ) = 0 - ] -``` - -**Example(s):** - -``` java -public class Foo { - void bar() { - if (a.equals(baz) && a != null) {} - } -} -``` - -``` java -public class Foo { - void bar() { - if (a.equals(baz) || a == null) {} - } -} -``` - -**Use this rule by referencing it:** -``` xml - -``` - -## OverrideBothEqualsAndHashcode - -**Since:** PMD 0.4 - -**Priority:** Medium (3) - -Override both public boolean Object.equals(Object other), and public int Object.hashCode(), or override neither. Even if you are inheriting a hashCode() from a parent class, consider implementing hashCode and explicitly delegating to your superclass. - -**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.basic.OverrideBothEqualsAndHashcodeRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/basic/OverrideBothEqualsAndHashcodeRule.java) - -**Example(s):** - -``` java -public class Bar { // poor, missing a hashcode() method - public boolean equals(Object o) { - // do some comparison - } -} - -public class Baz { // poor, missing an equals() method - public int hashCode() { - // return some hash value - } -} - -public class Foo { // perfect, both methods provided - public boolean equals(Object other) { - // do some comparison - } - public int hashCode() { - // return some hash value - } -} -``` - -**Use this rule by referencing it:** -``` xml - -``` - -## ReturnFromFinallyBlock - -**Since:** PMD 1.05 - -**Priority:** Medium (3) - -Avoid returning from a finally block, this can discard exceptions. - -``` -//FinallyStatement//ReturnStatement -``` - -**Example(s):** - -``` java -public class Bar { - public String foo() { - try { - throw new Exception( "My Exception" ); - } catch (Exception e) { - throw e; - } finally { - return "A. O. K."; // return not recommended here - } - } -} -``` - -**Use this rule by referencing it:** -``` xml - -``` - -## SimplifiedTernary - -**Since:** PMD 5.4.0 - -**Priority:** Medium (3) - -Look for ternary operators with the form `condition ? literalBoolean : foo` -or `condition ? foo : literalBoolean`. - -These expressions can be simplified respectively to -`condition || foo` when the literalBoolean is true -`!condition && foo` when the literalBoolean is false -or -`!condition || foo` when the literalBoolean is true -`condition && foo` when the literalBoolean is false - -``` -//ConditionalExpression[@Ternary='true'][not(PrimaryExpression/*/Literal) and (Expression/PrimaryExpression/*/Literal/BooleanLiteral)] -| -//ConditionalExpression[@Ternary='true'][not(Expression/PrimaryExpression/*/Literal) and (PrimaryExpression/*/Literal/BooleanLiteral)] -``` - -**Example(s):** - -``` java -public class Foo { - public boolean test() { - return condition ? true : something(); // can be as simple as return condition || something(); - } - - public void test2() { - final boolean value = condition ? false : something(); // can be as simple as value = !condition && something(); - } - - public boolean test3() { - return condition ? something() : true; // can be as simple as return !condition || something(); - } - - public void test4() { - final boolean otherValue = condition ? something() : false; // can be as simple as condition && something(); - } -} -``` - -**Use this rule by referencing it:** -``` xml - -``` - -## UnconditionalIfStatement - -**Since:** PMD 1.5 - -**Priority:** Medium (3) - -Do not use "if" statements whose conditionals are always true or always false. - -``` -//IfStatement/Expression - [count(PrimaryExpression)=1] - /PrimaryExpression/PrimaryPrefix/Literal/BooleanLiteral -``` - -**Example(s):** - -``` java -public class Foo { - public void close() { - if (true) { // fixed conditional, not recommended - // ... - } - } -} -``` - -**Use this rule by referencing it:** -``` xml - -``` - diff --git a/docs/pages/pmd/rules/java/bestpractices.md b/docs/pages/pmd/rules/java/bestpractices.md new file mode 100644 index 00000000000..4c46a7a429a --- /dev/null +++ b/docs/pages/pmd/rules/java/bestpractices.md @@ -0,0 +1,1594 @@ +--- +title: Best Practices +summary: Rules which enforce generally accepted best practices. +permalink: pmd_rules_java_bestpractices.html +folder: pmd/rules/java +sidebaractiveurl: /pmd_rules_java.html +editmepath: ../pmd-java/src/main/resources/category/java/bestpractices.xml +keywords: Best Practices, AbstractClassWithoutAbstractMethod, AccessorClassGeneration, AccessorMethodGeneration, ArrayIsStoredDirectly, AvoidPrintStackTrace, AvoidReassigningParameters, AvoidStringBufferField, AvoidUsingHardCodedIP, CheckResultSet, ConstantsInInterface, DefaultLabelNotLastInSwitchStmt, ForLoopCanBeForeach, GuardLogStatement, JUnit4SuitesShouldUseSuiteAnnotation, JUnit4TestShouldUseAfterAnnotation, JUnit4TestShouldUseBeforeAnnotation, JUnit4TestShouldUseTestAnnotation, JUnitAssertionsShouldIncludeMessage, JUnitTestContainsTooManyAsserts, JUnitTestsShouldIncludeAssert, JUnitUseExpected, LooseCoupling, MethodReturnsInternalArray, MissingOverride, OneDeclarationPerLine, PositionLiteralsFirstInCaseInsensitiveComparisons, PositionLiteralsFirstInComparisons, PreserveStackTrace, ReplaceEnumerationWithIterator, ReplaceHashtableWithMap, ReplaceVectorWithList, SwitchStmtsShouldHaveDefault, SystemPrintln, UnusedFormalParameter, UnusedImports, UnusedLocalVariable, UnusedPrivateField, UnusedPrivateMethod, UseAssertEqualsInsteadOfAssertTrue, UseAssertNullInsteadOfAssertTrue, UseAssertSameInsteadOfAssertTrue, UseAssertTrueInsteadOfAssertEquals, UseCollectionIsEmpty, UseVarargs +language: Java +--- +## AbstractClassWithoutAbstractMethod + +**Since:** PMD 3.0 + +**Priority:** Medium (3) + +The abstract class does not contain any abstract methods. An abstract class suggests +an incomplete implementation, which is to be completed by subclasses implementing the +abstract methods. If the class is intended to be used as a base class only (not to be instantiated +directly) a protected constructor can be provided prevent direct instantiation. + +**This rule is defined by the following XPath expression:** +``` xpath +//ClassOrInterfaceDeclaration + [@Abstract='true' + and count( .//MethodDeclaration[@Abstract='true'] )=0 ] + [count(ImplementsList)=0] + [count(.//ExtendsList)=0] +``` + +**Example(s):** + +``` java +public abstract class Foo { + void int method1() { ... } + void int method2() { ... } + // consider using abstract methods or removing + // the abstract modifier and adding protected constructors +} +``` + +**Use this rule by referencing it:** +``` xml + +``` + +## AccessorClassGeneration + +**Since:** PMD 1.04 + +**Priority:** Medium (3) + +Instantiation by way of private constructors from outside of the constructor's class often causes the +generation of an accessor. A factory method, or non-privatization of the constructor can eliminate this +situation. The generated class file is actually an interface. It gives the accessing class the ability +to invoke a new hidden package scope constructor that takes the interface as a supplementary parameter. +This turns a private constructor effectively into one with package scope, and is challenging to discern. + +**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.bestpractices.AccessorClassGenerationRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/AccessorClassGenerationRule.java) + +**Example(s):** + +``` java +public class Outer { + void method(){ + Inner ic = new Inner();//Causes generation of accessor class + } + public class Inner { + private Inner(){} + } +} +``` + +**Use this rule by referencing it:** +``` xml + +``` + +## AccessorMethodGeneration + +**Since:** PMD 5.5.4 + +**Priority:** Medium (3) + +When accessing a private field / method from another class, the Java compiler will generate a accessor methods +with package-private visibility. This adds overhead, and to the dex method count on Android. This situation can +be avoided by changing the visibility of the field / method from private to package-private. + +**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.bestpractices.AccessorMethodGenerationRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/AccessorMethodGenerationRule.java) + +**Example(s):** + +``` java +public class OuterClass { + private int counter; + /* package */ int id; + + public class InnerClass { + InnerClass() { + OuterClass.this.counter++; // wrong accessor method will be generated + } + + public int getOuterClassId() { + return OuterClass.this.id; // id is package-private, no accessor method needed + } + } +} +``` + +**Use this rule by referencing it:** +``` xml + +``` + +## ArrayIsStoredDirectly + +**Since:** PMD 2.2 + +**Priority:** Medium (3) + +Constructors and methods receiving arrays should clone objects and store the copy. +This prevents future changes from the user from affecting the original array. + +**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.bestpractices.ArrayIsStoredDirectlyRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/ArrayIsStoredDirectlyRule.java) + +**Example(s):** + +``` java +public class Foo { + private String [] x; + public void foo (String [] param) { + // Don't do this, make a copy of the array at least + this.x=param; + } +} +``` + +**Use this rule by referencing it:** +``` xml + +``` + +## AvoidPrintStackTrace + +**Since:** PMD 3.2 + +**Priority:** Medium (3) + +Avoid printStackTrace(); use a logger call instead. + +**This rule is defined by the following XPath expression:** +``` xpath +//PrimaryExpression + [PrimaryPrefix/Name[contains(@Image,'printStackTrace')]] + [PrimarySuffix[not(boolean(Arguments/ArgumentList/Expression))]] +``` + +**Example(s):** + +``` java +class Foo { + void bar() { + try { + // do something + } catch (Exception e) { + e.printStackTrace(); + } + } +} +``` + +**Use this rule by referencing it:** +``` xml + +``` + +## AvoidReassigningParameters + +**Since:** PMD 1.0 + +**Priority:** Medium High (2) + +Reassigning values to incoming parameters is not recommended. Use temporary local variables instead. + +**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.bestpractices.AvoidReassigningParametersRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/AvoidReassigningParametersRule.java) + +**Example(s):** + +``` java +public class Foo { + private void foo(String bar) { + bar = "something else"; + } +} +``` + +**Use this rule by referencing it:** +``` xml + +``` + +## AvoidStringBufferField + +**Since:** PMD 4.2 + +**Priority:** Medium (3) + +StringBuffers/StringBuilders can grow considerably, and so may become a source of memory leaks +if held within objects with long lifetimes. + +**This rule is defined by the following XPath expression:** +``` xpath +//FieldDeclaration/Type/ReferenceType/ClassOrInterfaceType[@Image = 'StringBuffer' or @Image = 'StringBuilder'] +``` + +**Example(s):** + +``` java +public class Foo { + private StringBuffer buffer; // potential memory leak as an instance variable; +} +``` + +**Use this rule by referencing it:** +``` xml + +``` + +## AvoidUsingHardCodedIP + +**Since:** PMD 4.1 + +**Priority:** Medium (3) + +Application with hard-coded IP addresses can become impossible to deploy in some cases. +Externalizing IP adresses is preferable. + +**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.bestpractices.AvoidUsingHardCodedIPRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/AvoidUsingHardCodedIPRule.java) + +**Example(s):** + +``` java +public class Foo { + private String ip = "127.0.0.1"; // not recommended +} +``` + +**This rule has the following properties:** + +|Name|Default Value|Description|Multivalued| +|----|-------------|-----------|-----------| +|checkAddressTypes|IPv4 \| IPv6 \| IPv4 mapped IPv6|Check for IP address types.|yes. Delimiter is '\|'.| + +**Use this rule by referencing it:** +``` xml + +``` + +## CheckResultSet + +**Since:** PMD 4.1 + +**Priority:** Medium (3) + +Always check the return values of navigation methods (next, previous, first, last) of a ResultSet. +If the value return is 'false', it should be handled properly. + +**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.bestpractices.CheckResultSetRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/CheckResultSetRule.java) + +**Example(s):** + +``` java +Statement stat = conn.createStatement(); +ResultSet rst = stat.executeQuery("SELECT name FROM person"); +rst.next(); // what if it returns false? bad form +String firstName = rst.getString(1); + +Statement stat = conn.createStatement(); +ResultSet rst = stat.executeQuery("SELECT name FROM person"); +if (rst.next()) { // result is properly examined and used + String firstName = rst.getString(1); + } else { + // handle missing data +} +``` + +**Use this rule by referencing it:** +``` xml + +``` + +## ConstantsInInterface + +**Since:** PMD 5.5 + +**Priority:** Medium (3) + +Avoid constants in interfaces. Interfaces should define types, constants are implementation details +better placed in classes or enums. See Effective Java, item 19. + +**This rule is defined by the following XPath expression:** +``` xpath +//ClassOrInterfaceDeclaration[@Interface='true'][$ignoreIfHasMethods='false' or not(.//MethodDeclaration)]//FieldDeclaration +``` + +**Example(s):** + +``` java +public interface ConstantInterface { + public static final int CONST1 = 1; // violation, no fields allowed in interface! + static final int CONST2 = 1; // violation, no fields allowed in interface! + final int CONST3 = 1; // violation, no fields allowed in interface! + int CONST4 = 1; // violation, no fields allowed in interface! +} + +// with ignoreIfHasMethods = false +public interface AnotherConstantInterface { + public static final int CONST1 = 1; // violation, no fields allowed in interface! + + int anyMethod(); +} + +// with ignoreIfHasMethods = true +public interface YetAnotherConstantInterface { + public static final int CONST1 = 1; // no violation + + int anyMethod(); +} +``` + +**This rule has the following properties:** + +|Name|Default Value|Description|Multivalued| +|----|-------------|-----------|-----------| +|ignoreIfHasMethods|true|Whether to ignore constants in interfaces if the interface defines any methods|no| + +**Use this rule by referencing it:** +``` xml + +``` + +## DefaultLabelNotLastInSwitchStmt + +**Since:** PMD 1.5 + +**Priority:** Medium (3) + +By convention, the default label should be the last label in a switch statement. + +**This rule is defined by the following XPath expression:** +``` xpath +//SwitchStatement + [not(SwitchLabel[position() = last()][@Default='true'])] + [SwitchLabel[@Default='true']] +``` + +**Example(s):** + +``` java +public class Foo { + void bar(int a) { + switch (a) { + case 1: // do something + break; + default: // the default case should be last, by convention + break; + case 2: + break; + } + } +} +``` + +**Use this rule by referencing it:** +``` xml + +``` + +## ForLoopCanBeForeach + +**Since:** PMD 6.0.0 + +**Priority:** Medium (3) + +**Minimum Language Version:** Java 1.5 + +Reports loops that can be safely replaced with the foreach syntax. The rule considers loops over +lists, arrays and iterators. A loop is safe to replace if it only uses the index variable to +access an element of the list or array, only has one update statement, and loops through *every* +element of the list or array left to right. + +**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.bestpractices.ForLoopCanBeForeachRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/ForLoopCanBeForeachRule.java) + +**Example(s):** + +``` java +public class MyClass { + void loop(List l) { + for (int i = 0; i < l.size(); i++) { // pre Java 1.5 + System.out.println(l.get(i)); + } + + for (String s : l) { // post Java 1.5 + System.out.println(s); + } + } +} +``` + +**Use this rule by referencing it:** +``` xml + +``` + +## GuardLogStatement + +**Since:** PMD 5.1.0 + +**Priority:** Medium High (2) + +Whenever using a log level, one should check if the loglevel is actually enabled, or +otherwise skip the associate String creation and manipulation. + +**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.bestpractices.GuardLogStatementRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/GuardLogStatementRule.java) + +**Example(s):** + +``` java +// Add this for performance + if (log.isDebugEnabled() { ... + log.debug("log something" + " and " + "concat strings"); +``` + +**This rule has the following properties:** + +|Name|Default Value|Description|Multivalued| +|----|-------------|-----------|-----------| +|guardsMethods|isTraceEnabled , isDebugEnabled , isInfoEnabled , isWarnEnabled , isErrorEnabled , isLoggable|method use to guard the log statement|yes. Delimiter is ','.| +|logLevels|trace , debug , info , warn , error , log , finest , finer , fine , info , warning , severe|LogLevels to guard|yes. Delimiter is ','.| + +**Use this rule by referencing it:** +``` xml + +``` + +## JUnit4SuitesShouldUseSuiteAnnotation + +**Since:** PMD 4.0 + +**Priority:** Medium (3) + +In JUnit 3, test suites are indicated by the suite() method. In JUnit 4, suites are indicated +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[pmd-java:typeIs('junit.framework.Test')]] +[not(MethodDeclaration/Block//ClassOrInterfaceType[pmd-java:typeIs('junit.framework.JUnit4TestAdapter')])] +``` + +**Example(s):** + +``` java +public class BadExample extends TestCase{ + + public static Test suite(){ + return new Suite(); + } +} + +@RunWith(Suite.class) +@SuiteClasses( { TestOne.class, TestTwo.class }) +public class GoodTest { +} +``` + +**Use this rule by referencing it:** +``` xml + +``` + +## JUnit4TestShouldUseAfterAnnotation + +**Since:** PMD 4.0 + +**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 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 +//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):** + +``` java +public class MyTest { + public void tearDown() { + bad(); + } +} +public class MyTest2 { + @After public void tearDown() { + good(); + } +} +``` + +**Use this rule by referencing it:** +``` xml + +``` + +## JUnit4TestShouldUseBeforeAnnotation + +**Since:** PMD 4.0 + +**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 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 +//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):** + +``` java +public class MyTest { + public void setUp() { + bad(); + } +} +public class MyTest2 { + @Before public void setUp() { + good(); + } +} +``` + +**Use this rule by referencing it:** +``` xml + +``` + +## JUnit4TestShouldUseTestAnnotation + +**Since:** PMD 4.0 + +**Priority:** Medium (3) + +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 +//ClassOrInterfaceDeclaration[ + matches(@Image, $testClassPattern) + 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') + 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):** + +``` java +public class MyTest { + public void testBad() { + doSomething(); + } + + @Test + public void testGood() { + doSomething(); + } +} +``` + +**This rule has the following properties:** + +|Name|Default Value|Description|Multivalued| +|----|-------------|-----------|-----------| +|testClassPattern|Test|The regex pattern used to identify test classes|no| + +**Use this rule by referencing it:** +``` xml + +``` + +## JUnitAssertionsShouldIncludeMessage + +**Since:** PMD 1.04 + +**Priority:** Medium (3) + +JUnit assertions should include an informative message - i.e., use the three-argument version of +assertEquals(), not the two-argument version. + +**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.bestpractices.JUnitAssertionsShouldIncludeMessageRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/JUnitAssertionsShouldIncludeMessageRule.java) + +**Example(s):** + +``` java +public class Foo extends TestCase { + public void testSomething() { + assertEquals("foo", "bar"); + // Use the form: + // assertEquals("Foo does not equals bar", "foo", "bar"); + // instead + } +} +``` + +**Use this rule by referencing it:** +``` xml + +``` + +## JUnitTestContainsTooManyAsserts + +**Since:** PMD 5.0 + +**Priority:** Medium (3) + +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[ + 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):** + +``` java +public class MyTestCase extends TestCase { + // Ok + public void testMyCaseWithOneAssert() { + boolean myVar = false; + assertFalse("should be false", myVar); + } + + // Bad, too many asserts (assuming max=1) + public void testMyCaseWithMoreAsserts() { + boolean myVar = false; + assertFalse("myVar should be false", myVar); + assertEquals("should equals false", false, myVar); + } +} +``` + +**This rule has the following properties:** + +|Name|Default Value|Description|Multivalued| +|----|-------------|-----------|-----------| +|maximumAsserts|1|Maximum number of Asserts in a test method|no| + +**Use this rule by referencing it:** +``` xml + +``` + +## JUnitTestsShouldIncludeAssert + +**Since:** PMD 2.0 + +**Priority:** Medium (3) + +JUnit tests should include at least one assertion. This makes the tests more robust, and using assert +with messages provide the developer a clearer idea of what the test does. + +**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.bestpractices.JUnitTestsShouldIncludeAssertRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/JUnitTestsShouldIncludeAssertRule.java) + +**Example(s):** + +``` java +public class Foo extends TestCase { + public void testSomething() { + Bar b = findBar(); + // This is better than having a NullPointerException + // assertNotNull("bar not found", b); + b.work(); + } +} +``` + +**Use this rule by referencing it:** +``` xml + +``` + +## JUnitUseExpected + +**Since:** PMD 4.0 + +**Priority:** Medium (3) + +In JUnit4, use the @Test(expected) annotation to denote tests that should throw exceptions. + +**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.bestpractices.JUnitUseExpectedRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/JUnitUseExpectedRule.java) + +**Example(s):** + +``` java +public class MyTest { + @Test + public void testBad() { + try { + doSomething(); + fail("should have thrown an exception"); + } catch (Exception e) { + } + } + + @Test(expected=Exception.class) + public void testGood() { + doSomething(); + } +} +``` + +**Use this rule by referencing it:** +``` xml + +``` + +## LooseCoupling + +**Since:** PMD 0.7 + +**Priority:** Medium (3) + +The use of implementation types (i.e., HashSet) as object references limits your ability to use alternate +implementations in the future as requirements change. Whenever available, referencing objects +by their interface types (i.e, Set) provides much more flexibility. + +**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.bestpractices.LooseCouplingRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/LooseCouplingRule.java) + +**Example(s):** + +``` java +import java.util.ArrayList; +import java.util.HashSet; + +public class Bar { + // sub-optimal approach + private ArrayList list = new ArrayList<>(); + + public HashSet getFoo() { + return new HashSet(); + } + + // preferred approach + private List list = new ArrayList<>(); + + public Set getFoo() { + return new HashSet(); + } +} +``` + +**Use this rule by referencing it:** +``` xml + +``` + +## MethodReturnsInternalArray + +**Since:** PMD 2.2 + +**Priority:** Medium (3) + +Exposing internal arrays to the caller violates object encapsulation since elements can be +removed or replaced outside of the object that owns it. It is safer to return a copy of the array. + +**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.bestpractices.MethodReturnsInternalArrayRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/MethodReturnsInternalArrayRule.java) + +**Example(s):** + +``` java +public class SecureSystem { + UserData [] ud; + public UserData [] getUserData() { + // Don't return directly the internal array, return a copy + return ud; + } +} +``` + +**Use this rule by referencing it:** +``` xml + +``` + +## MissingOverride + +**Since:** PMD 6.2.0 + +**Priority:** Medium (3) + +**Minimum Language Version:** Java 1.5 + +Annotating overridden methods with @Override ensures at compile time that +the method really overrides one, which helps refactoring and clarifies intent. + +**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.bestpractices.MissingOverrideRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/MissingOverrideRule.java) + +**Example(s):** + +``` java +public class Foo implements Runnable { + // This method is overridden, and should have an @Override annotation + public void run() { + + } + } +``` + +**Use this rule by referencing it:** +``` xml + +``` + +## OneDeclarationPerLine + +**Since:** PMD 5.0 + +**Priority:** Medium Low (4) + +Java allows the use of several variables declaration of the same type on one line. However, it +can lead to quite messy code. This rule looks for several declarations on the same line. + +**This rule is defined by the following XPath expression:** +``` xpath +//LocalVariableDeclaration + [count(VariableDeclarator) > 1] + [$strictMode or count(distinct-values(VariableDeclarator/@BeginLine)) != count(VariableDeclarator)] +| +//FieldDeclaration + [count(VariableDeclarator) > 1] + [$strictMode or count(distinct-values(VariableDeclarator/@BeginLine)) != count(VariableDeclarator)] +``` + +**Example(s):** + +``` java +String name; // separate declarations +String lastname; + +String name, lastname; // combined declaration, a violation + +String name, + lastname; // combined declaration on multiple lines, no violation by default. + // Set property strictMode to true to mark this as violation. +``` + +**This rule has the following properties:** + +|Name|Default Value|Description|Multivalued| +|----|-------------|-----------|-----------| +|strictMode|false|If true, mark combined declaration even if the declarations are on separate lines.|no| + +**Use this rule by referencing it:** +``` xml + +``` + +## PositionLiteralsFirstInCaseInsensitiveComparisons + +**Since:** PMD 5.1 + +**Priority:** Medium (3) + +Position literals first in comparisons, if the second argument is null then NullPointerExceptions +can be avoided, they will just return false. + +**This rule is defined by the following XPath expression:** +``` xpath +//PrimaryExpression[ + PrimaryPrefix[Name + [ + (ends-with(@Image, '.equalsIgnoreCase')) + ] + ] + [ + (../PrimarySuffix/Arguments/ArgumentList/Expression/PrimaryExpression/PrimaryPrefix/Literal) + and + ( count(../PrimarySuffix/Arguments/ArgumentList/Expression) = 1 ) + ] +] +[not(ancestor::Expression/ConditionalAndExpression//EqualityExpression[@Image='!=']//NullLiteral)] +[not(ancestor::Expression/ConditionalOrExpression//EqualityExpression[@Image='==']//NullLiteral)] +``` + +**Example(s):** + +``` java +class Foo { + boolean bar(String x) { + return x.equalsIgnoreCase("2"); // should be "2".equalsIgnoreCase(x) + } +} +``` + +**Use this rule by referencing it:** +``` xml + +``` + +## PositionLiteralsFirstInComparisons + +**Since:** PMD 3.3 + +**Priority:** Medium (3) + +Position literals first in comparisons, if the second argument is null then NullPointerExceptions +can be avoided, they will just return false. + +**This rule is defined by the following XPath expression:** +``` xpath +//PrimaryExpression[ + PrimaryPrefix[Name[(ends-with(@Image, '.equals'))]] + [ + (../PrimarySuffix/Arguments/ArgumentList/Expression/PrimaryExpression/PrimaryPrefix/Literal[@StringLiteral='true']) + and + ( count(../PrimarySuffix/Arguments/ArgumentList/Expression) = 1 ) + ] +] +[not(ancestor::Expression/ConditionalAndExpression//EqualityExpression[@Image='!=']//NullLiteral)] +[not(ancestor::Expression/ConditionalOrExpression//EqualityExpression[@Image='==']//NullLiteral)] +``` + +**Example(s):** + +``` java +class Foo { + boolean bar(String x) { + return x.equals("2"); // should be "2".equals(x) + } +} +``` + +**Use this rule by referencing it:** +``` xml + +``` + +## PreserveStackTrace + +**Since:** PMD 3.7 + +**Priority:** Medium (3) + +Throwing a new exception from a catch block without passing the original exception into the +new exception will cause the original stack trace to be lost making it difficult to debug +effectively. + +**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.bestpractices.PreserveStackTraceRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/PreserveStackTraceRule.java) + +**Example(s):** + +``` java +public class Foo { + void good() { + try{ + Integer.parseInt("a"); + } catch (Exception e) { + throw new Exception(e); // first possibility to create exception chain + } + try { + Integer.parseInt("a"); + } catch (Exception e) { + throw (IllegalStateException)new IllegalStateException().initCause(e); // second possibility to create exception chain. + } + } + void bad() { + try{ + Integer.parseInt("a"); + } catch (Exception e) { + throw new Exception(e.getMessage()); + } + } +} +``` + +**Use this rule by referencing it:** +``` xml + +``` + +## ReplaceEnumerationWithIterator + +**Since:** PMD 3.4 + +**Priority:** Medium (3) + +Consider replacing Enumeration usages with the newer java.util.Iterator + +**This rule is defined by the following XPath expression:** +``` xpath +//ImplementsList/ClassOrInterfaceType[@Image='Enumeration'] +``` + +**Example(s):** + +``` java +public class Foo implements Enumeration { + private int x = 42; + public boolean hasMoreElements() { + return true; + } + public Object nextElement() { + return String.valueOf(i++); + } +} +``` + +**Use this rule by referencing it:** +``` xml + +``` + +## ReplaceHashtableWithMap + +**Since:** PMD 3.4 + +**Priority:** Medium (3) + +Consider replacing Hashtable usage with the newer java.util.Map if thread safety is not required. + +**This rule is defined by the following XPath expression:** +``` xpath +//Type/ReferenceType/ClassOrInterfaceType[@Image='Hashtable'] +``` + +**Example(s):** + +``` java +public class Foo { + void bar() { + Hashtable h = new Hashtable(); + } +} +``` + +**Use this rule by referencing it:** +``` xml + +``` + +## ReplaceVectorWithList + +**Since:** PMD 3.4 + +**Priority:** Medium (3) + +Consider replacing Vector usages with the newer java.util.ArrayList if expensive thread-safe operations are not required. + +**This rule is defined by the following XPath expression:** +``` xpath +//Type/ReferenceType/ClassOrInterfaceType[@Image='Vector'] +``` + +**Example(s):** + +``` java +public class Foo { + void bar() { + Vector v = new Vector(); + } +} +``` + +**Use this rule by referencing it:** +``` xml + +``` + +## SwitchStmtsShouldHaveDefault + +**Since:** PMD 1.0 + +**Priority:** Medium (3) + +All switch statements should include a default option to catch any unspecified values. + +**This rule is defined by the following XPath expression:** +``` xpath +//SwitchStatement[@DefaultCase = false() and @ExhaustiveEnumSwitch = false()] +``` + +**Example(s):** + +``` java +public void bar() { + int x = 2; + switch (x) { + case 1: int j = 6; + case 2: int j = 8; + // missing default: here + } +} +``` + +**Use this rule by referencing it:** +``` xml + +``` + +## SystemPrintln + +**Since:** PMD 2.1 + +**Priority:** Medium High (2) + +References to System.(out|err).print are usually intended for debugging purposes and can remain in +the codebase even in production code. By using a logger one can enable/disable this behaviour at +will (and by priority) and avoid clogging the Standard out log. + +**This rule is defined by the following XPath expression:** +``` xpath +//Name[ + starts-with(@Image, 'System.out.print') + or + starts-with(@Image, 'System.err.print') + ] +``` + +**Example(s):** + +``` java +class Foo{ + Logger log = Logger.getLogger(Foo.class.getName()); + public void testA () { + System.out.println("Entering test"); + // Better use this + log.fine("Entering test"); + } +} +``` + +**Use this rule by referencing it:** +``` xml + +``` + +## UnusedFormalParameter + +**Since:** PMD 0.8 + +**Priority:** Medium (3) + +Avoid passing parameters to methods or constructors without actually referencing them in the method body. + +**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.bestpractices.UnusedFormalParameterRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/UnusedFormalParameterRule.java) + +**Example(s):** + +``` java +public class Foo { + private void bar(String howdy) { + // howdy is not used + } +} +``` + +**This rule has the following properties:** + +|Name|Default Value|Description|Multivalued| +|----|-------------|-----------|-----------| +|checkAll|false|Check all methods, including non-private ones|no| + +**Use this rule by referencing it:** +``` xml + +``` + +## UnusedImports + +**Since:** PMD 1.0 + +**Priority:** Medium Low (4) + +Avoid unused import statements to prevent unwanted dependencies. +This rule will also find unused on demand imports, i.e. import com.foo.*. + +**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.bestpractices.UnusedImportsRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/UnusedImportsRule.java) + +**Example(s):** + +``` java +import java.io.File; // not referenced or required +import java.util.*; // not referenced or required + +public class Foo {} +``` + +**Use this rule by referencing it:** +``` xml + +``` + +## UnusedLocalVariable + +**Since:** PMD 0.1 + +**Priority:** Medium (3) + +Detects when a local variable is declared and/or assigned, but not used. + +**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.bestpractices.UnusedLocalVariableRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/UnusedLocalVariableRule.java) + +**Example(s):** + +``` java +public class Foo { + public void doSomething() { + int i = 5; // Unused + } +} +``` + +**Use this rule by referencing it:** +``` xml + +``` + +## UnusedPrivateField + +**Since:** PMD 0.1 + +**Priority:** Medium (3) + +Detects when a private field is declared and/or assigned a value, but not used. + +**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.bestpractices.UnusedPrivateFieldRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/UnusedPrivateFieldRule.java) + +**Example(s):** + +``` java +public class Something { + private static int FOO = 2; // Unused + private int i = 5; // Unused + private int j = 6; + public int addOne() { + return j++; + } +} +``` + +**This rule has the following properties:** + +|Name|Default Value|Description|Multivalued| +|----|-------------|-----------|-----------| +|ignoredAnnotations|lombok.Setter \| lombok.Getter \| lombok.Builder \| lombok.Data \| lombok.RequiredArgsConstructor \| lombok.AllArgsConstructor \| lombok.Value \| lombok.NoArgsConstructor \| java.lang.Deprecated \| javafx.fxml.FXML|Fully qualified names of the annotation types that should be ignored by this rule|yes. Delimiter is '\|'.| + +**Use this rule by referencing it:** +``` xml + +``` + +## UnusedPrivateMethod + +**Since:** PMD 0.7 + +**Priority:** Medium (3) + +Unused Private Method detects when a private method is declared but is unused. + +**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.bestpractices.UnusedPrivateMethodRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/UnusedPrivateMethodRule.java) + +**Example(s):** + +``` java +public class Something { + private void foo() {} // unused +} +``` + +**This rule has the following properties:** + +|Name|Default Value|Description|Multivalued| +|----|-------------|-----------|-----------| +|ignoredAnnotations|java.lang.Deprecated|Fully qualified names of the annotation types that should be ignored by this rule|yes. Delimiter is '\|'.| + +**Use this rule by referencing it:** +``` xml + +``` + +## UseAssertEqualsInsteadOfAssertTrue + +**Since:** PMD 3.1 + +**Priority:** Medium (3) + +This rule detects JUnit assertions in object equality. These assertions should be made by more specific methods, like assertEquals. + +**This rule is defined by the following XPath expression:** +``` xpath +//PrimaryExpression[ + PrimaryPrefix/Name[@Image = 'assertTrue'] +][ + 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') + 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):** + +``` java +public class FooTest extends TestCase { + void testCode() { + Object a, b; + assertTrue(a.equals(b)); // bad usage + assertEquals(?a should equals b?, a, b); // good usage + } +} +``` + +**Use this rule by referencing it:** +``` xml + +``` + +## UseAssertNullInsteadOfAssertTrue + +**Since:** PMD 3.5 + +**Priority:** Medium (3) + +This rule detects JUnit assertions in object references equality. These assertions should be made by +more specific methods, like assertNull, assertNotNull. + +**This rule is defined by the following XPath expression:** +``` xpath +//PrimaryExpression[ + PrimaryPrefix/Name[@Image = 'assertTrue' or @Image = 'assertFalse'] +][ + PrimarySuffix/Arguments/ArgumentList[ + Expression/EqualityExpression/PrimaryExpression/PrimaryPrefix/Literal/NullLiteral + ] +] +[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):** + +``` java +public class FooTest extends TestCase { + void testCode() { + Object a = doSomething(); + assertTrue(a==null); // bad usage + assertNull(a); // good usage + assertTrue(a != null); // bad usage + assertNotNull(a); // good usage + } +} +``` + +**Use this rule by referencing it:** +``` xml + +``` + +## UseAssertSameInsteadOfAssertTrue + +**Since:** PMD 3.1 + +**Priority:** Medium (3) + +This rule detects JUnit assertions in object references equality. These assertions should be made +by more specific methods, like assertSame, assertNotSame. + +**This rule is defined by the following XPath expression:** +``` xpath +//PrimaryExpression[ + PrimaryPrefix/Name + [@Image = 'assertTrue' or @Image = 'assertFalse'] +] +[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') + 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):** + +``` java +public class FooTest extends TestCase { + void testCode() { + Object a, b; + assertTrue(a == b); // bad usage + assertSame(a, b); // good usage + } +} +``` + +**Use this rule by referencing it:** +``` xml + +``` + +## UseAssertTrueInsteadOfAssertEquals + +**Since:** PMD 5.0 + +**Priority:** Medium (3) + +When asserting a value is the same as a literal or Boxed boolean, use assertTrue/assertFalse, instead of assertEquals. + +**This rule is defined by the following XPath expression:** +``` xpath +//PrimaryExpression[PrimaryPrefix/Name[@Image = 'assertEquals']] +[ + PrimarySuffix/Arguments/ArgumentList/Expression/PrimaryExpression/PrimaryPrefix/Literal/BooleanLiteral + or + PrimarySuffix/Arguments/ArgumentList/Expression/PrimaryExpression/PrimaryPrefix + /Name[(@Image = 'Boolean.TRUE' or @Image = 'Boolean.FALSE')] +] +``` + +**Example(s):** + +``` java +public class MyTestCase extends TestCase { + public void testMyCase() { + boolean myVar = true; + // Ok + assertTrue("myVar is true", myVar); + // Bad + assertEquals("myVar is true", true, myVar); + // Bad + assertEquals("myVar is false", false, myVar); + // Bad + assertEquals("myVar is true", Boolean.TRUE, myVar); + // Bad + assertEquals("myVar is false", Boolean.FALSE, myVar); + } +} +``` + +**Use this rule by referencing it:** +``` xml + +``` + +## UseCollectionIsEmpty + +**Since:** PMD 3.9 + +**Priority:** Medium (3) + +The isEmpty() method on java.util.Collection is provided to determine if a collection has any elements. +Comparing the value of size() to 0 does not convey intent as well as the isEmpty() method. + +**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.bestpractices.UseCollectionIsEmptyRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/UseCollectionIsEmptyRule.java) + +**Example(s):** + +``` java +public class Foo { + void good() { + List foo = getList(); + if (foo.isEmpty()) { + // blah + } + } + + void bad() { + List foo = getList(); + if (foo.size() == 0) { + // blah + } + } +} +``` + +**Use this rule by referencing it:** +``` xml + +``` + +## UseVarargs + +**Since:** PMD 5.0 + +**Priority:** Medium Low (4) + +**Minimum Language Version:** Java 1.5 + +Java 5 introduced the varargs parameter declaration for methods and constructors. This syntactic +sugar provides flexibility for users of these methods and constructors, allowing them to avoid +having to deal with the creation of an array. + +**This rule is defined by the following XPath expression:** +``` xpath +//FormalParameters/FormalParameter + [position()=last()] + [@Array='true'] + [@Varargs='false'] + [not (./Type/ReferenceType[@Array='true'][PrimitiveType[@Image='byte']])] + [not (./Type/ReferenceType[ClassOrInterfaceType[@Image='Byte']])] + [not (./Type/PrimitiveType[@Image='byte'])] + [not (ancestor::MethodDeclaration/preceding-sibling::Annotation/*/Name[@Image='Override'])] + [not( + ancestor::MethodDeclaration + [@Public='true' and @Static='true'] + [child::ResultType[@Void='true']] and + ancestor::MethodDeclarator[@Image='main'] and + ..[@ParameterCount='1'] and + ./Type/ReferenceType[ClassOrInterfaceType[@Image='String']] + )] +``` + +**Example(s):** + +``` java +public class Foo { + public void foo(String s, Object[] args) { + // Do something here... + } + + public void bar(String s, Object... args) { + // Ahh, varargs tastes much better... + } +} +``` + +**Use this rule by referencing it:** +``` xml + +``` + diff --git a/docs/pages/pmd/rules/java/braces.md b/docs/pages/pmd/rules/java/braces.md deleted file mode 100644 index a2ea2bab6b5..00000000000 --- a/docs/pages/pmd/rules/java/braces.md +++ /dev/null @@ -1,130 +0,0 @@ ---- -title: Braces -summary: The Braces ruleset contains rules regarding the use and placement of braces. -permalink: pmd_rules_java_braces.html -folder: pmd/rules/java -sidebaractiveurl: /pmd_rules_java.html -editmepath: ../pmd-java/src/main/resources/rulesets/java/braces.xml -keywords: Braces, IfStmtsMustUseBraces, WhileLoopsMustUseBraces, IfElseStmtsMustUseBraces, ForLoopsMustUseBraces ---- -## ForLoopsMustUseBraces - -**Since:** PMD 0.7 - -**Priority:** Medium (3) - -Avoid using 'for' statements without using curly braces. If the code formatting or -indentation is lost then it becomes difficult to separate the code being controlled -from the rest. - -``` -//ForStatement[not(Statement/Block)] -``` - -**Example(s):** - -``` java -for (int i = 0; i < 42; i++) - foo(); -``` - -**Use this rule by referencing it:** -``` xml - -``` - -## IfElseStmtsMustUseBraces - -**Since:** PMD 0.2 - -**Priority:** Medium (3) - -Avoid using if..else statements without using surrounding braces. If the code formatting -or indentation is lost then it becomes difficult to separate the code being controlled -from the rest. - -``` -//Statement - [parent::IfStatement[@Else='true']] - [not(child::Block)] - [not(child::IfStatement)] -``` - -**Example(s):** - -``` java -// this is OK -if (foo) x++; - - // but this is not -if (foo) - x = x+1; - else - x = x-1; -``` - -**Use this rule by referencing it:** -``` xml - -``` - -## IfStmtsMustUseBraces - -**Since:** PMD 1.0 - -**Priority:** Medium (3) - -Avoid using if statements without using braces to surround the code block. If the code -formatting or indentation is lost then it becomes difficult to separate the code being -controlled from the rest. - -``` -//IfStatement[count(*) < 3][not(Statement/Block)] -``` - -**Example(s):** - -``` java -if (foo) // not recommended - x++; - -if (foo) { // preferred approach - x++; -} -``` - -**Use this rule by referencing it:** -``` xml - -``` - -## WhileLoopsMustUseBraces - -**Since:** PMD 0.7 - -**Priority:** Medium (3) - -Avoid using 'while' statements without using braces to surround the code block. If the code -formatting or indentation is lost then it becomes difficult to separate the code being -controlled from the rest. - -``` -//WhileStatement[not(Statement/Block)] -``` - -**Example(s):** - -``` java -while (true) // not recommended - x++; - -while (true) { // preferred approach - x++; -} -``` - -**Use this rule by referencing it:** -``` xml - -``` - diff --git a/docs/pages/pmd/rules/java/clone.md b/docs/pages/pmd/rules/java/clone.md deleted file mode 100644 index 7091400db39..00000000000 --- a/docs/pages/pmd/rules/java/clone.md +++ /dev/null @@ -1,203 +0,0 @@ ---- -title: Clone Implementation -summary: The Clone Implementation ruleset contains a collection of rules that find questionable usages of the clone() method. -permalink: pmd_rules_java_clone.html -folder: pmd/rules/java -sidebaractiveurl: /pmd_rules_java.html -editmepath: ../pmd-java/src/main/resources/rulesets/java/clone.xml -keywords: Clone Implementation, ProperCloneImplementation, CloneThrowsCloneNotSupportedException, CloneMethodMustImplementCloneable, CloneMethodReturnTypeMustMatchClassName, CloneMethodMustBePublic ---- -## CloneMethodMustBePublic - -**Since:** PMD 5.4.0 - -**Priority:** Medium (3) - -The java Manual says "By convention, classes that implement this interface should override -Object.clone (which is protected) with a public method." - -``` -//MethodDeclaration[@Public='false'] - [MethodDeclarator/@Image = 'clone'] - [MethodDeclarator/FormalParameters/@ParameterCount = 0] -``` - -**Example(s):** - -``` java -public class Foo implements Cloneable { - @Override - protected Object clone() throws CloneNotSupportedException { // Violation, must be public - } -} - -public class Foo implements Cloneable { - @Override - protected Foo clone() { // Violation, must be public - } -} - -public class Foo implements Cloneable { - @Override - public Object clone() // Ok -} -``` - -**Use this rule by referencing it:** -``` xml - -``` - -## CloneMethodMustImplementCloneable - -**Since:** PMD 1.9 - -**Priority:** Medium (3) - -The method clone() should only be implemented if the class implements the Cloneable interface with the exception of a final method that only throws CloneNotSupportedException. - -``` -//ClassOrInterfaceDeclaration -[not(./ExtendsList/ClassOrInterfaceType[@Image='Cloneable'])] -[not(./ImplementsList/ClassOrInterfaceType[@Image='Cloneable'])] -/ClassOrInterfaceBody/ClassOrInterfaceBodyDeclaration -[MethodDeclaration -[MethodDeclarator[@Image -= 'clone' and count(FormalParameters/*) = 0]] -[not((../MethodDeclaration[@Final = 'true'] or ancestor::ClassOrInterfaceDeclaration[1][@Final = 'true']) -and Block[count(BlockStatement)=1] -/BlockStatement/Statement/ThrowStatement/Expression -/PrimaryExpression/PrimaryPrefix/AllocationExpression -/ClassOrInterfaceType[@Image = 'CloneNotSupportedException'])]] -``` - -**Example(s):** - -``` java -public class MyClass { - public Object clone() throws CloneNotSupportedException { - return foo; - } -} -``` - -**Use this rule by referencing it:** -``` xml - -``` - -## CloneMethodReturnTypeMustMatchClassName - -**Since:** PMD 5.4.0 - -**Priority:** Medium (3) - -**Minimum Language Version:** Java 1.5 - -If a class implements cloneable the return type of the method clone() must be the class name. That way, the caller -of the clone method doesn't need to cast the returned clone to the correct type. - -Note: This is only possible with Java 1.5 or higher. - -``` -//MethodDeclaration -[ -MethodDeclarator/@Image = 'clone' -and MethodDeclarator/FormalParameters/@ParameterCount = 0 -and not (ResultType//ClassOrInterfaceType/@Image = ancestor::ClassOrInterfaceDeclaration[1]/@Image) -] -``` - -**Example(s):** - -``` java -public class Foo implements Cloneable { - @Override - protected Object clone() { // Violation, Object must be Foo - } -} - -public class Foo implements Cloneable { - @Override - public Foo clone() { //Ok - } -} -``` - -**Use this rule by referencing it:** -``` xml - -``` - -## CloneThrowsCloneNotSupportedException - -**Since:** PMD 1.9 - -**Priority:** Medium (3) - -The method clone() should throw a CloneNotSupportedException. - -``` -//MethodDeclaration -[ -MethodDeclarator/@Image = 'clone' -and count(MethodDeclarator/FormalParameters/*) = 0 -and count(NameList/Name[contains -(@Image,'CloneNotSupportedException')]) = 0 -] -[ -../../../../ClassOrInterfaceDeclaration[@Final = 'false'] -] -``` - -**Example(s):** - -``` java -public class MyClass implements Cloneable{ - public Object clone() { // will cause an error - MyClass clone = (MyClass)super.clone(); - return clone; - } -} -``` - -**Use this rule by referencing it:** -``` xml - -``` - -## ProperCloneImplementation - -**Since:** PMD 1.4 - -**Priority:** Medium High (2) - -Object clone() should be implemented with super.clone(). - -``` -//MethodDeclarator -[@Image = 'clone'] -[count(FormalParameters/*) = 0] -[count(../Block//*[ - (self::AllocationExpression) and - (./ClassOrInterfaceType/@Image = ancestor:: -ClassOrInterfaceDeclaration[1]/@Image) - ])> 0 -] -``` - -**Example(s):** - -``` java -class Foo{ - public Object clone(){ - return new Foo(); // This is bad - } -} -``` - -**Use this rule by referencing it:** -``` xml - -``` - diff --git a/docs/pages/pmd/rules/java/codesize.md b/docs/pages/pmd/rules/java/codesize.md deleted file mode 100644 index de62bd2e1dd..00000000000 --- a/docs/pages/pmd/rules/java/codesize.md +++ /dev/null @@ -1,699 +0,0 @@ ---- -title: Code Size -summary: The Code Size ruleset contains rules that find problems related to code size or complexity. -permalink: pmd_rules_java_codesize.html -folder: pmd/rules/java -sidebaractiveurl: /pmd_rules_java.html -editmepath: ../pmd-java/src/main/resources/rulesets/java/codesize.xml -keywords: Code Size, NPathComplexity, ExcessiveMethodLength, ExcessiveParameterList, ExcessiveClassLength, CyclomaticComplexity, StdCyclomaticComplexity, ModifiedCyclomaticComplexity, ExcessivePublicCount, TooManyFields, NcssMethodCount, NcssTypeCount, NcssConstructorCount, NcssCount, TooManyMethods ---- -## CyclomaticComplexity - -Deprecated - -**Since:** PMD 1.03 - -**Priority:** Medium (3) - -Complexity directly affects maintenance costs is determined by the number of decision points in a method -plus one for the method entry. The decision points include 'if', 'while', 'for', and 'case labels' calls. -Generally, numbers ranging from 1-4 denote low complexity, 5-7 denote moderate complexity, 8-10 denote -high complexity, and 11+ is very high complexity. - -**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.codesize.CyclomaticComplexityRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codesize/CyclomaticComplexityRule.java) - -**Example(s):** - -``` java -public class Foo { // This has a Cyclomatic Complexity = 12 -1 public void example() { -2 if (a == b) { -3 if (a1 == b1) { - fiddle(); -4 } else if a2 == b2) { - fiddle(); - } else { - fiddle(); - } -5 } else if (c == d) { -6 while (c == d) { - fiddle(); - } -7 } else if (e == f) { -8 for (int n = 0; n < h; n++) { - fiddle(); - } - } else{ - switch (z) { -9 case 1: - fiddle(); - break; -10 case 2: - fiddle(); - break; -11 case 3: - fiddle(); - break; -12 default: - fiddle(); - break; - } - } - } -} -``` - -**This rule has the following properties:** - -|Name|Default Value|Description| -|----|-------------|-----------| -|showMethodsComplexity|true|Add method average violations to the report| -|showClassesComplexity|true|Add class average violations to the report| -|reportLevel|10|Cyclomatic Complexity reporting threshold| - -**Use this rule by referencing it:** -``` xml - -``` - -## ExcessiveClassLength - -**Since:** PMD 0.6 - -**Priority:** Medium (3) - -Excessive class file lengths are usually indications that the class may be burdened with excessive -responsibilities that could be provided by external classes or functions. In breaking these methods -apart the code becomes more manageable and ripe for reuse. - -**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.codesize.ExcessiveClassLengthRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codesize/ExcessiveClassLengthRule.java) - -**Example(s):** - -``` java -public class Foo { - public void bar1() { - // 1000 lines of code - } - public void bar2() { - // 1000 lines of code - } - public void bar3() { - // 1000 lines of code - } - - public void barN() { - // 1000 lines of code - } -} -``` - -**This rule has the following properties:** - -|Name|Default Value|Description| -|----|-------------|-----------| -|topscore||Top score value| -|minimum||Minimum reporting threshold| -|sigma||Sigma value| - -**Use this rule by referencing it:** -``` xml - -``` - -## ExcessiveMethodLength - -**Since:** PMD 0.6 - -**Priority:** Medium (3) - -When methods are excessively long this usually indicates that the method is doing more than its -name/signature might suggest. They also become challenging for others to digest since excessive -scrolling causes readers to lose focus. -Try to reduce the method length by creating helper methods and removing any copy/pasted code. - -**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.codesize.ExcessiveMethodLengthRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codesize/ExcessiveMethodLengthRule.java) - -**Example(s):** - -``` java -public void doSomething() { - System.out.println("Hello world!"); - System.out.println("Hello world!"); - // 98 copies omitted for brevity. -} -``` - -**This rule has the following properties:** - -|Name|Default Value|Description| -|----|-------------|-----------| -|topscore||Top score value| -|minimum||Minimum reporting threshold| -|sigma||Sigma value| - -**Use this rule by referencing it:** -``` xml - -``` - -## ExcessiveParameterList - -**Since:** PMD 0.9 - -**Priority:** Medium (3) - -Methods with numerous parameters are a challenge to maintain, especially if most of them share the -same datatype. These situations usually denote the need for new objects to wrap the numerous parameters. - -**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.codesize.ExcessiveParameterListRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codesize/ExcessiveParameterListRule.java) - -**Example(s):** - -``` java -public void addPerson( // too many arguments liable to be mixed up - int birthYear, int birthMonth, int birthDate, int height, int weight, int ssn) { - - . . . . -} - -public void addPerson( // preferred approach - Date birthdate, BodyMeasurements measurements, int ssn) { - - . . . . -} -``` - -**This rule has the following properties:** - -|Name|Default Value|Description| -|----|-------------|-----------| -|topscore||Top score value| -|minimum||Minimum reporting threshold| -|sigma||Sigma value| - -**Use this rule by referencing it:** -``` xml - -``` - -## ExcessivePublicCount - -**Since:** PMD 1.04 - -**Priority:** Medium (3) - -Classes with large numbers of public methods and attributes require disproportionate testing efforts -since combinational side effects grow rapidly and increase risk. Refactoring these classes into -smaller ones not only increases testability and reliability but also allows new variations to be -developed easily. - -**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.codesize.ExcessivePublicCountRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codesize/ExcessivePublicCountRule.java) - -**Example(s):** - -``` java -public class Foo { - public String value; - public Bar something; - public Variable var; - // [... more more public attributes ...] - - public void doWork() {} - public void doMoreWork() {} - public void doWorkAgain() {} - // [... more more public methods ...] -} -``` - -**This rule has the following properties:** - -|Name|Default Value|Description| -|----|-------------|-----------| -|topscore||Top score value| -|minimum||Minimum reporting threshold| -|sigma||Sigma value| - -**Use this rule by referencing it:** -``` xml - -``` - -## ModifiedCyclomaticComplexity - -Deprecated - -**Since:** PMD 5.1.2 - -**Priority:** Medium (3) - -Complexity directly affects maintenance costs is determined by the number of decision points in a method -plus one for the method entry. The decision points include 'if', 'while', 'for', and 'case labels' calls. -Generally, numbers ranging from 1-4 denote low complexity, 5-7 denote moderate complexity, 8-10 denote -high complexity, and 11+ is very high complexity. Modified complexity treats switch statements as a single -decision point. - -**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.codesize.ModifiedCyclomaticComplexityRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codesize/ModifiedCyclomaticComplexityRule.java) - -**Example(s):** - -``` java -public class Foo { // This has a Cyclomatic Complexity = 9 -1 public void example() { -2 if (a == b) { -3 if (a1 == b1) { - fiddle(); -4 } else if a2 == b2) { - fiddle(); - } else { - fiddle(); - } -5 } else if (c == d) { -6 while (c == d) { - fiddle(); - } -7 } else if (e == f) { -8 for (int n = 0; n < h; n++) { - fiddle(); - } - } else{ -9 switch (z) { - case 1: - fiddle(); - break; - case 2: - fiddle(); - break; - case 3: - fiddle(); - break; - default: - fiddle(); - break; - } - } - } -} -``` - -**This rule has the following properties:** - -|Name|Default Value|Description| -|----|-------------|-----------| -|showMethodsComplexity|true|Add method average violations to the report| -|showClassesComplexity|true|Add class average violations to the report| -|reportLevel|10|Cyclomatic Complexity reporting threshold| - -**Use this rule by referencing it:** -``` xml - -``` - -## NcssConstructorCount - -Deprecated - -**Since:** PMD 3.9 - -**Priority:** Medium (3) - -This rule uses the NCSS (Non-Commenting Source Statements) algorithm to determine the number of lines -of code for a given constructor. NCSS ignores comments, and counts actual statements. Using this algorithm, -lines of code that are split are counted as one. - -**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.codesize.NcssConstructorCountRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codesize/NcssConstructorCountRule.java) - -**Example(s):** - -``` java -public class Foo extends Bar { - public Foo() { - super(); - - - - - - //this constructor only has 1 NCSS lines - super.foo(); - } -} -``` - -**This rule has the following properties:** - -|Name|Default Value|Description| -|----|-------------|-----------| -|topscore||Top score value| -|minimum||Minimum reporting threshold| -|sigma||Sigma value| - -**Use this rule by referencing it:** -``` xml - -``` - -## NcssCount - -**Since:** PMD 6.0 - -**Priority:** Medium (3) - -This rule uses the NCSS (Non-Commenting Source Statements) metric to determine the number of lines - of code in a class, method or constructor. NCSS ignores comments, blank lines, and only counts actual - statements. For more details on the calculation, see the documentation of - the [NCSS metric](/pmd_java_metrics_index.html#non-commenting-source-statements-ncss). - -**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.codesize.NcssCountRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codesize/NcssCountRule.java) - -**Example(s):** - -``` java -import java.util.Collections; // +0 -import java.io.IOException; // +0 - -class Foo { // +1, total Ncss = 12 - - public void bigMethod() // +1 - throws IOException { - int x = 0, y = 2; // +1 - boolean a = false, b = true; // +1 - - if (a || b) { // +1 - try { // +1 - do { // +1 - x += 2; // +1 - } while (x < 12); - - System.exit(0); // +1 - } catch (IOException ioe) { // +1 - throw new PatheticFailException(ioe); // +1 - } - } else { - assert false; // +1 - } - } -} -``` - -**This rule has the following properties:** - -|Name|Default Value|Description| -|----|-------------|-----------| -|ncssOptions|[]|Choose options for the calculation of Ncss| -|methodReportLevel|12|Metric reporting threshold for methods| -|classReportLevel|250|Metric reporting threshold for classes| - -**Use this rule by referencing it:** -``` xml - -``` - -## NcssMethodCount - -Deprecated - -**Since:** PMD 3.9 - -**Priority:** Medium (3) - -This rule uses the NCSS (Non-Commenting Source Statements) algorithm to determine the number of lines -of code for a given method. NCSS ignores comments, and counts actual statements. Using this algorithm, -lines of code that are split are counted as one. - -**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.codesize.NcssMethodCountRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codesize/NcssMethodCountRule.java) - -**Example(s):** - -``` java -public class Foo extends Bar { - public int methd() { - super.methd(); - - - - - //this method only has 1 NCSS lines - return 1; - } -} -``` - -**This rule has the following properties:** - -|Name|Default Value|Description| -|----|-------------|-----------| -|topscore||Top score value| -|minimum||Minimum reporting threshold| -|sigma||Sigma value| - -**Use this rule by referencing it:** -``` xml - -``` - -## NcssTypeCount - -Deprecated - -**Since:** PMD 3.9 - -**Priority:** Medium (3) - -This rule uses the NCSS (Non-Commenting Source Statements) algorithm to determine the number of lines -of code for a given type. NCSS ignores comments, and counts actual statements. Using this algorithm, -lines of code that are split are counted as one. - -**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.codesize.NcssTypeCountRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codesize/NcssTypeCountRule.java) - -**Example(s):** - -``` java -public class Foo extends Bar { - public Foo() { - //this class only has 6 NCSS lines - super(); - - - - - - super.foo(); - } -} -``` - -**This rule has the following properties:** - -|Name|Default Value|Description| -|----|-------------|-----------| -|topscore||Top score value| -|minimum||Minimum reporting threshold| -|sigma||Sigma value| - -**Use this rule by referencing it:** -``` xml - -``` - -## NPathComplexity - -**Since:** PMD 3.9 - -**Priority:** Medium (3) - -The NPath complexity of a method is the number of acyclic execution paths through that method. -A threshold of 200 is generally considered the point where measures should be taken to reduce -complexity and increase readability. - -**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.codesize.NPathComplexityRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codesize/NPathComplexityRule.java) - -**Example(s):** - -``` java -void bar() { // this is something more complex than it needs to be, - if (y) { // it should be broken down into smaller methods or functions - for (j = 0; j < m; j++) { - if (j > r) { - doSomething(); - while (f < 5 ) { - anotherThing(); - f -= 27; - } - } else { - tryThis(); - } - } - } - if ( r - n > 45) { - while (doMagic()) { - findRabbits(); - } - } - try { - doSomethingDangerous(); - } catch (Exception ex) { - makeAmends(); - } finally { - dontDoItAgain(); - } -} -``` - -**This rule has the following properties:** - -|Name|Default Value|Description| -|----|-------------|-----------| -|topscore||Top score value| -|minimum||Minimum reporting threshold| -|sigma||Sigma value| - -**Use this rule by referencing it:** -``` xml - -``` - -## StdCyclomaticComplexity - -Deprecated - -**Since:** PMD 5.1.2 - -**Priority:** Medium (3) - -Complexity directly affects maintenance costs is determined by the number of decision points in a method -plus one for the method entry. The decision points include 'if', 'while', 'for', and 'case labels' calls. -Generally, numbers ranging from 1-4 denote low complexity, 5-7 denote moderate complexity, 8-10 denote -high complexity, and 11+ is very high complexity. - -**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.codesize.StdCyclomaticComplexityRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codesize/StdCyclomaticComplexityRule.java) - -**Example(s):** - -``` java -public class Foo { // This has a Cyclomatic Complexity = 12 -1 public void example() { -2 if (a == b || (c == d && e == f)) { // Only one -3 if (a1 == b1) { - fiddle(); -4 } else if a2 == b2) { - fiddle(); - } else { - fiddle(); - } -5 } else if (c == d) { -6 while (c == d) { - fiddle(); - } -7 } else if (e == f) { -8 for (int n = 0; n < h; n++) { - fiddle(); - } - } else{ - switch (z) { -9 case 1: - fiddle(); - break; -10 case 2: - fiddle(); - break; -11 case 3: - fiddle(); - break; -12 default: - fiddle(); - break; - } - } - } -} -``` - -**This rule has the following properties:** - -|Name|Default Value|Description| -|----|-------------|-----------| -|showMethodsComplexity|true|Add method average violations to the report| -|showClassesComplexity|true|Add class average violations to the report| -|reportLevel|10|Cyclomatic Complexity reporting threshold| - -**Use this rule by referencing it:** -``` xml - -``` - -## TooManyFields - -**Since:** PMD 3.0 - -**Priority:** Medium (3) - -Classes that have too many fields can become unwieldy and could be redesigned to have fewer fields, -possibly through grouping related fields in new objects. For example, a class with individual -city/state/zip fields could park them within a single Address field. - -**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.codesize.TooManyFieldsRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codesize/TooManyFieldsRule.java) - -**Example(s):** - -``` java -public class Person { // too many separate fields - int birthYear; - int birthMonth; - int birthDate; - float height; - float weight; -} - -public class Person { // this is more manageable - Date birthDate; - BodyMeasurements measurements; -} -``` - -**This rule has the following properties:** - -|Name|Default Value|Description| -|----|-------------|-----------| -|maxfields|15|Max allowable fields| - -**Use this rule by referencing it:** -``` xml - -``` - -## TooManyMethods - -**Since:** PMD 4.2 - -**Priority:** Medium (3) - -A class with too many methods is probably a good suspect for refactoring, in order to reduce its -complexity and find a way to have more fine grained objects. - -``` -//ClassOrInterfaceDeclaration/ClassOrInterfaceBody - [ - count(./ClassOrInterfaceBodyDeclaration/MethodDeclaration/MethodDeclarator[ - not ( - starts-with(@Image,'get') - or - starts-with(@Image,'set') - or - starts-with(@Image,'is') - ) - ]) > $maxmethods - ] -``` - -**This rule has the following properties:** - -|Name|Default Value|Description| -|----|-------------|-----------| -|maxmethods|10|The method count reporting threshold| - -**Use this rule by referencing it:** -``` xml - -``` - diff --git a/docs/pages/pmd/rules/java/codestyle.md b/docs/pages/pmd/rules/java/codestyle.md new file mode 100644 index 00000000000..4341da7a735 --- /dev/null +++ b/docs/pages/pmd/rules/java/codestyle.md @@ -0,0 +1,2340 @@ +--- +title: Code Style +summary: Rules which enforce a specific coding style. +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, FieldNamingConventions, ForLoopShouldBeWhileLoop, ForLoopsMustUseBraces, FormalParameterNamingConventions, GenericsNaming, IdenticalCatchBranches, IfElseStmtsMustUseBraces, IfStmtsMustUseBraces, LinguisticNaming, LocalHomeNamingConvention, LocalInterfaceSessionNamingConvention, LocalVariableCouldBeFinal, LocalVariableNamingConventions, LongVariable, MDBAndSessionBeanNamingConvention, MethodArgumentCouldBeFinal, MethodNamingConventions, MIsLeadingVariableName, NoPackage, UseUnderscoresInNumericLiterals, OnlyOneReturn, PackageCase, PrematureDeclaration, RemoteInterfaceNamingConvention, RemoteSessionInterfaceNamingConvention, ShortClassName, ShortMethodName, ShortVariable, SuspiciousConstantFieldName, TooManyStaticImports, UnnecessaryAnnotationValueElement, UnnecessaryConstructor, UnnecessaryFullyQualifiedName, UnnecessaryLocalBeforeReturn, UnnecessaryModifier, UnnecessaryReturn, UselessParentheses, UselessQualifiedThis, VariableNamingConventions, WhileLoopsMustUseBraces +language: Java +--- +## AbstractNaming + +Deprecated + +**Since:** PMD 1.4 + +**Priority:** Medium (3) + +Abstract classes should be named 'AbstractXXX'. + +**This rule is defined by the following XPath expression:** +``` xpath +//ClassOrInterfaceDeclaration + [@Abstract='true' and @Interface='false'] + [not (starts-with(@Image,'Abstract'))] +| +//ClassOrInterfaceDeclaration + [@Abstract='false'] + [$strict='true'] + [starts-with(@Image, 'Abstract')] +``` + +**Example(s):** + +``` java +public abstract class Foo { // should be AbstractFoo +} +``` + +**This rule has the following properties:** + +|Name|Default Value|Description|Multivalued| +|----|-------------|-----------|-----------| +|strict|true|Also flag classes, that are named Abstract, but are not abstract.|no| + +**Use this rule by referencing it:** +``` xml + +``` + +## AtLeastOneConstructor + +**Since:** PMD 1.04 + +**Priority:** Medium (3) + +Each non-static class should declare at least one constructor. +Classes with solely static members are ignored, refer to [UseUtilityClassRule](pmd_rules_java_design.html#useutilityclass) to detect those. + +**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.codestyle.AtLeastOneConstructorRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codestyle/AtLeastOneConstructorRule.java) + +**Example(s):** + +``` java +public class Foo { + // missing constructor + public void doSomething() { ... } + public void doOtherThing { ... } +} +``` + +**This rule has the following properties:** + +|Name|Default Value|Description|Multivalued| +|----|-------------|-----------|-----------| +|ignoredAnnotations|lombok.Data \| lombok.Value \| lombok.Builder \| lombok.NoArgsConstructor \| lombok.RequiredArgsConstructor \| lombok.AllArgsConstructorAtLeastOneConstructor|Fully qualified names of the annotation types that should be ignored by this rule|yes. Delimiter is '\|'.| + +**Use this rule by referencing it:** +``` xml + +``` + +## AvoidDollarSigns + +**Since:** PMD 1.5 + +**Priority:** Medium (3) + +Avoid using dollar signs in variable/method/class/interface names. + +**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.codestyle.AvoidDollarSignsRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codestyle/AvoidDollarSignsRule.java) + +**Example(s):** + +``` java +public class Fo$o { // not a recommended name +} +``` + +**Use this rule by referencing it:** +``` xml + +``` + +## AvoidFinalLocalVariable + +**Since:** PMD 4.1 + +**Priority:** Medium (3) + +Avoid using final local variables, turn them into fields. + +**This rule is defined by the following XPath expression:** +``` xpath +//LocalVariableDeclaration[ + @Final = 'true' + and not(../../ForStatement) + and + ( + (count(VariableDeclarator/VariableInitializer) = 0) + or + (VariableDeclarator/VariableInitializer/Expression/PrimaryExpression/PrimaryPrefix/Literal) + ) +] +``` + +**Example(s):** + +``` java +public class MyClass { + public void foo() { + final String finalLocalVariable; + } +} +``` + +**Use this rule by referencing it:** +``` xml + +``` + +## AvoidPrefixingMethodParameters + +Deprecated + +**Since:** PMD 5.0 + +**Priority:** Medium Low (4) + +Prefixing parameters by 'in' or 'out' pollutes the name of the parameters and reduces code readability. +To indicate whether or not a parameter will be modify in a method, its better to document method +behavior with Javadoc. + +**This rule is defined by the following XPath expression:** +``` xpath +//MethodDeclaration/MethodDeclarator/FormalParameters/FormalParameter/VariableDeclaratorId[ + pmd:matches(@Image,'^in[A-Z].*','^out[A-Z].*','^in$','^out$') +] +``` + +**Example(s):** + +``` java +// Not really clear +public class Foo { + public void bar( + int inLeftOperand, + Result outRightOperand) { + outRightOperand.setValue(inLeftOperand * outRightOperand.getValue()); + } +} +``` + +``` java +// Far more useful +public class Foo { + /** + * + * @param leftOperand, (purpose), not modified by method. + * @param rightOperand (purpose), will be modified by the method: contains the result. + */ + public void bar( + int leftOperand, + Result rightOperand) { + rightOperand.setValue(leftOperand * rightOperand.getValue()); + } +} +``` + +**Use this rule by referencing it:** +``` xml + +``` + +## AvoidProtectedFieldInFinalClass + +**Since:** PMD 2.1 + +**Priority:** Medium (3) + +Do not use protected fields in final classes since they cannot be subclassed. +Clarify your intent by using private or package access modifiers instead. + +**This rule is defined by the following XPath expression:** +``` xpath +//ClassOrInterfaceDeclaration[@Final='true'] +/ClassOrInterfaceBody/ClassOrInterfaceBodyDeclaration +/FieldDeclaration[@Protected='true'] +``` + +**Example(s):** + +``` java +public final class Bar { + private int x; + protected int y; // bar cannot be subclassed, so is y really private or package visible? + Bar() {} +} +``` + +**Use this rule by referencing it:** +``` xml + +``` + +## AvoidProtectedMethodInFinalClassNotExtending + +**Since:** PMD 5.1 + +**Priority:** Medium (3) + +Do not use protected methods in most final classes since they cannot be subclassed. This should +only be allowed in final classes that extend other classes with protected methods (whose +visibility cannot be reduced). Clarify your intent by using private or package access modifiers instead. + +**This rule is defined by the following XPath expression:** +``` xpath +//ClassOrInterfaceDeclaration[@Final='true' and not(ExtendsList)] +/ClassOrInterfaceBody/ClassOrInterfaceBodyDeclaration +/MethodDeclaration[@Protected='true'][MethodDeclarator/@Image != 'finalize'] +``` + +**Example(s):** + +``` java +public final class Foo { + private int bar() {} + protected int baz() {} // Foo cannot be subclassed, and doesn't extend anything, so is baz() really private or package visible? +} +``` + +**Use this rule by referencing it:** +``` xml + +``` + +## AvoidUsingNativeCode + +**Since:** PMD 4.1 + +**Priority:** Medium High (2) + +Unnecessary reliance on Java Native Interface (JNI) calls directly reduces application portability +and increases the maintenance burden. + +**This rule is defined by the following XPath expression:** +``` xpath +//Name[starts-with(@Image,'System.loadLibrary')] +``` + +**Example(s):** + +``` java +public class SomeJNIClass { + + public SomeJNIClass() { + System.loadLibrary("nativelib"); + } + + static { + System.loadLibrary("nativelib"); + } + + public void invalidCallsInMethod() throws SecurityException, NoSuchMethodException { + System.loadLibrary("nativelib"); + } +} +``` + +**Use this rule by referencing it:** +``` xml + +``` + +## BooleanGetMethodName + +**Since:** PMD 4.0 + +**Priority:** Medium Low (4) + +Methods that return boolean results should be named as predicate statements to denote this. +I.e, 'isReady()', 'hasValues()', 'canCommit()', 'willFail()', etc. Avoid the use of the 'get' +prefix for these methods. + +**This rule is defined by the following XPath expression:** +``` xpath +//MethodDeclaration[ +MethodDeclarator[count(FormalParameters/FormalParameter) = 0 or $checkParameterizedMethods = 'true'] + [starts-with(@Image, 'get')] +and +ResultType/Type/PrimitiveType[@Image = 'boolean'] +and not(../Annotation//Name[@Image = 'Override']) +] +``` + +**Example(s):** + +``` java +public boolean getFoo(); // bad +public boolean isFoo(); // ok +public boolean getFoo(boolean bar); // ok, unless checkParameterizedMethods=true +``` + +**This rule has the following properties:** + +|Name|Default Value|Description|Multivalued| +|----|-------------|-----------|-----------| +|checkParameterizedMethods|false|Check parameterized methods|no| + +**Use this rule by referencing it:** +``` xml + +``` + +## CallSuperInConstructor + +**Since:** PMD 3.0 + +**Priority:** Medium (3) + +It is a good practice to call super() in a constructor. If super() is not called but +another constructor (such as an overloaded constructor) is called, this rule will not report it. + +**This rule is defined by the following XPath expression:** +``` xpath +//ClassOrInterfaceDeclaration[ count (ExtendsList/*) > 0 ] +/ClassOrInterfaceBody + /ClassOrInterfaceBodyDeclaration + /ConstructorDeclaration[ count (.//ExplicitConstructorInvocation)=0 ] +``` + +**Example(s):** + +``` java +public class Foo extends Bar{ + public Foo() { + // call the constructor of Bar + super(); + } + public Foo(int code) { + // do something with code + this(); + // no problem with this + } +} +``` + +**Use this rule by referencing it:** +``` xml + +``` + +## ClassNamingConventions + +**Since:** PMD 1.2 + +**Priority:** High (1) + +Configurable naming conventions for type declarations. This rule reports +type declarations which do not match the regex that applies to their +specific kind (e.g. enum or interface). Each regex can be configured through +properties. + +By default this rule uses the standard Java naming convention (Pascal case), +and reports utility class names not ending with 'Util'. + +**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.codestyle.ClassNamingConventionsRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codestyle/ClassNamingConventionsRule.java) + +**Example(s):** + +``` java +// This is Pascal case, the recommended naming convention in Java +// Note that the default values of this rule don't allow underscores +// or accented characters in type names +public class FooBar {} + +// You may want abstract classes to be named 'AbstractXXX', +// in which case you can customize the regex for abstract +// classes to 'Abstract[A-Z]\w+' +public abstract class Thing {} + +// This class doesn't respect the convention, and will be flagged +public class Éléphant {} +``` + +**This rule has the following properties:** + +|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| +|utilityClassPattern|\[A-Z\]\[a-zA-Z0-9\]+(Utils?\|Helper)|Regex which applies to utility class names|no| + +**Use this rule by referencing it:** +``` xml + +``` + +## CommentDefaultAccessModifier + +**Since:** PMD 5.4.0 + +**Priority:** Medium (3) + +To avoid mistakes if we want that a Method, Constructor, Field or Nested class have a default access modifier +we must add a comment at the beginning of it's declaration. +By default the comment must be /* default */ or /* package */, if you want another, you have to provide a regular expression. +This rule ignores by default all cases that have a @VisibleForTesting annotation. Use the +property "ignoredAnnotations" to customize the recognized annotations. + +**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.codestyle.CommentDefaultAccessModifierRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codestyle/CommentDefaultAccessModifierRule.java) + +**Example(s):** + +``` java +public class Foo { + final String stringValue = "some string"; + String getString() { + return stringValue; + } + + class NestedFoo { + } +} + +// should be +public class Foo { + /* default */ final String stringValue = "some string"; + /* default */ String getString() { + return stringValue; + } + + /* default */ class NestedFoo { + } +} +``` + +**This rule has the following properties:** + +|Name|Default Value|Description|Multivalued| +|----|-------------|-----------|-----------| +|regex|\\/\\\*\\s+(default\|package)\\s+\\\*\\/|Regular expression|no| +|ignoredAnnotations|com.google.common.annotations.VisibleForTesting \| android.support.annotation.VisibleForTesting|Fully qualified names of the annotation types that should be ignored by this rule|yes. Delimiter is '\|'.| + +**Use this rule by referencing it:** +``` xml + +``` + +## ConfusingTernary + +**Since:** PMD 1.9 + +**Priority:** Medium (3) + +Avoid negation within an "if" expression with an "else" clause. For example, rephrase: +`if (x != y) diff(); else same();` as: `if (x == y) same(); else diff();`. + +Most "if (x != y)" cases without an "else" are often return cases, so consistent use of this +rule makes the code easier to read. Also, this resolves trivial ordering problems, such +as "does the error case go first?" or "does the common case go first?". + +**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.codestyle.ConfusingTernaryRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codestyle/ConfusingTernaryRule.java) + +**Example(s):** + +``` java +boolean bar(int x, int y) { + return (x != y) ? diff : same; +} +``` + +**This rule has the following properties:** + +|Name|Default Value|Description|Multivalued| +|----|-------------|-----------|-----------| +|ignoreElseIf|false|Ignore conditions with an else-if case|no| + +**Use this rule by referencing it:** +``` xml + +``` + +## ControlStatementBraces + +**Since:** PMD 6.2.0 + +**Priority:** Medium (3) + +Enforce a policy for braces on control statements. It is recommended to use braces on 'if ... else' +statements and loop statements, even if they are optional. This usually makes the code clearer, and +helps prepare the future when you need to add another statement. That said, this rule lets you control +which statements are required to have braces via properties. + +From 6.2.0 on, this rule supersedes WhileLoopMustUseBraces, ForLoopMustUseBraces, IfStmtMustUseBraces, +and IfElseStmtMustUseBraces. + +**This rule is defined by the following XPath expression:** +``` xpath +//WhileStatement[$checkWhileStmt and not(Statement/Block) and not($allowEmptyLoop and Statement/EmptyStatement)] + | + //ForStatement[$checkForStmt and not(Statement/Block) and not($allowEmptyLoop and Statement/EmptyStatement)] + | + //DoStatement[$checkDoWhileStmt and not(Statement/Block) and not($allowEmptyLoop and Statement/EmptyStatement)] + | + (: The violation is reported on the sub statement -- not the if statement :) + //Statement[$checkIfElseStmt and parent::IfStatement and not(child::Block or child::IfStatement) + (: Whitelists single if statements :) + and ($checkSingleIfStmt + (: Inside this not(...) is the definition of a "single if statement" :) + or not(count(../Statement) = 1 (: No else stmt :) + (: Not the last branch of an 'if ... else if' chain :) + and not(parent::IfStatement[parent::Statement[parent::IfStatement]])))] + | + (: Reports case labels if one of their subordinate statements is not braced :) + //SwitchLabel[$checkCaseStmt] + [count(following-sibling::BlockStatement except following-sibling::SwitchLabel[1]/following-sibling::BlockStatement) > 1 + or (some $stmt (: in only the block statements until the next label :) + in following-sibling::BlockStatement except following-sibling::SwitchLabel[1]/following-sibling::BlockStatement + satisfies not($stmt/Statement/Block))] +``` + +**Example(s):** + +``` java +while (true) // not recommended + x++; + +while (true) { // preferred approach + x++; +} +``` + +**This rule has the following properties:** + +|Name|Default Value|Description|Multivalued| +|----|-------------|-----------|-----------| +|checkIfElseStmt|true|Require that 'if ... else' statements use braces|no| +|checkSingleIfStmt|true|Require that 'if' statements with a single branch use braces|no| +|checkWhileStmt|true|Require that 'while' loops use braces|no| +|checkForStmt|true|Require that 'for' loops should use braces|no| +|checkDoWhileStmt|true|Require that 'do ... while' loops use braces|no| +|checkCaseStmt|false|Require that cases of a switch have braces|no| +|allowEmptyLoop|false|Allow loops with an empty statement, e.g. 'while(true);'|no| + +**Use this rule by referencing it:** +``` xml + +``` + +## DefaultPackage + +**Since:** PMD 3.4 + +**Priority:** Medium (3) + +Use explicit scoping instead of accidental usage of default package private level. +The rule allows methods and fields annotated with Guava's @VisibleForTesting. + +**This rule is defined by the following XPath expression:** +``` xpath +//ClassOrInterfaceDeclaration[@Interface='false'] +/ClassOrInterfaceBody +/ClassOrInterfaceBodyDeclaration +[not(Annotation//Name[ends-with(@Image, 'VisibleForTesting')])] +[ +FieldDeclaration[@PackagePrivate='true'] +or MethodDeclaration[@PackagePrivate='true'] +] +``` + +**Use this rule by referencing it:** +``` xml + +``` + +## DontImportJavaLang + +**Since:** PMD 0.5 + +**Priority:** Medium Low (4) + +Avoid importing anything from the package 'java.lang'. These classes are automatically imported (JLS 7.5.3). + +**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.codestyle.DontImportJavaLangRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codestyle/DontImportJavaLangRule.java) + +**Example(s):** + +``` java +import java.lang.String; // this is unnecessary + +public class Foo {} + +// --- in another source code file... + +import java.lang.*; // this is bad + +public class Foo {} +``` + +**Use this rule by referencing it:** +``` xml + +``` + +## DuplicateImports + +**Since:** PMD 0.5 + +**Priority:** Medium Low (4) + +Duplicate or overlapping import statements should be avoided. + +**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.codestyle.DuplicateImportsRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codestyle/DuplicateImportsRule.java) + +**Example(s):** + +``` java +import java.lang.String; +import java.lang.*; +public class Foo {} +``` + +**Use this rule by referencing it:** +``` xml + +``` + +## EmptyMethodInAbstractClassShouldBeAbstract + +**Since:** PMD 4.1 + +**Priority:** High (1) + +Empty or auto-generated methods in an abstract class should be tagged as abstract. This helps to remove their inapproprate +usage by developers who should be implementing their own versions in the concrete subclasses. + +**This rule is defined by the following XPath expression:** +``` xpath +//ClassOrInterfaceDeclaration[@Abstract = 'true'] + /ClassOrInterfaceBody + /ClassOrInterfaceBodyDeclaration + /MethodDeclaration[@Abstract = 'false' and @Native = 'false'] + [ + ( boolean(./Block[count(./BlockStatement) = 1]/BlockStatement/Statement/ReturnStatement/Expression/PrimaryExpression/PrimaryPrefix/Literal/NullLiteral) = 'true' ) + or + ( boolean(./Block[count(./BlockStatement) = 1]/BlockStatement/Statement/ReturnStatement/Expression/PrimaryExpression/PrimaryPrefix/Literal[@Image = '0']) = 'true' ) + or + ( boolean(./Block[count(./BlockStatement) = 1]/BlockStatement/Statement/ReturnStatement/Expression/PrimaryExpression/PrimaryPrefix/Literal[string-length(@Image) = 2]) = 'true' ) + or + (./Block[count(./BlockStatement) = 1]/BlockStatement/Statement/EmptyStatement) + or + ( count (./Block/*) = 0 ) + ] +``` + +**Example(s):** + +``` java +public abstract class ShouldBeAbstract { + public Object couldBeAbstract() { + // Should be abstract method ? + return null; + } + + public void couldBeAbstract() { + } +} +``` + +**Use this rule by referencing it:** +``` xml + +``` + +## ExtendsObject + +**Since:** PMD 5.0 + +**Priority:** Medium Low (4) + +No need to explicitly extend Object. + +**This rule is defined by the following XPath expression:** +``` xpath +//ExtendsList/ClassOrInterfaceType[@Image='Object' or @Image='java.lang.Object'] +``` + +**Example(s):** + +``` java +public class Foo extends Object { // not required +} +``` + +**Use this rule by referencing it:** +``` xml + +``` + +## FieldDeclarationsShouldBeAtStartOfClass + +**Since:** PMD 5.0 + +**Priority:** Medium (3) + +Fields should be declared at the top of the class, before any method declarations, constructors, initializers or inner classes. + +**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.codestyle.FieldDeclarationsShouldBeAtStartOfClassRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codestyle/FieldDeclarationsShouldBeAtStartOfClassRule.java) + +**Example(s):** + +``` java +public class HelloWorldBean { + + // Field declared before methods / inner classes - OK + private String _thing; + + public String getMessage() { + return "Hello World!"; + } + + // Field declared after methods / inner classes - avoid this + private String _fieldInWrongLocation; +} +``` + +**This rule has the following properties:** + +|Name|Default Value|Description|Multivalued| +|----|-------------|-----------|-----------| +|ignoreInterfaceDeclarations|false|Ignore Interface Declarations that precede fields.|no| +|ignoreAnonymousClassDeclarations|true|Ignore Field Declarations, that are initialized with anonymous class declarations|no| +|ignoreEnumDeclarations|true|Ignore Enum Declarations that precede fields.|no| + +**Use this rule by referencing it:** +``` xml + +``` + +## 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| +|exclusions|serialVersionUID|Names of fields to whitelist.|yes. Delimiter is '\|'.| + +**Use this rule by referencing it:** +``` xml + +``` + +## ForLoopShouldBeWhileLoop + +**Since:** PMD 1.02 + +**Priority:** Medium (3) + +Some for loops can be simplified to while loops, this makes them more concise. + +**This rule is defined by the following XPath expression:** +``` xpath +//ForStatement + [not(LocalVariableDeclaration)] + [not(ForInit)] + [not(ForUpdate)] + [Expression] +``` + +**Example(s):** + +``` java +public class Foo { + void bar() { + for (;true;) true; // No Init or Update part, may as well be: while (true) + } +} +``` + +**Use this rule by referencing it:** +``` xml + +``` + +## ForLoopsMustUseBraces + +Deprecated + +**Since:** PMD 0.7 + +**Priority:** Medium (3) + +Avoid using 'for' statements without using curly braces. If the code formatting or +indentation is lost then it becomes difficult to separate the code being controlled +from the rest. + +**This rule is defined by the following XPath expression:** +``` xpath +//ForStatement[not(Statement/Block)] +``` + +**Example(s):** + +``` java +for (int i = 0; i < 42; i++) + foo(); +``` + +**Use this rule by referencing it:** +``` xml + +``` + +## FormalParameterNamingConventions + +**Since:** PMD 6.6.0 + +**Priority:** High (1) + +Configurable naming conventions for formal parameters of methods and lambdas. +This rule reports formal parameters which do not match the regex that applies to their +specific kind (e.g. lambda parameter, or final formal parameter). Each regex can be +configured through properties. + +By default this rule uses the standard Java naming convention (Camel case). + +**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.codestyle.FormalParameterNamingConventionsRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codestyle/FormalParameterNamingConventionsRule.java) + +**Example(s):** + +``` java +class Foo { + + abstract void bar(int myInt); // This is Camel case, so it's ok + + void bar(int my_i) { // this will be reported + + } + + void lambdas() { + + // lambdas parameters can be configured separately + Consumer lambda1 = s_str -> { }; + + // lambda parameters with an explicit type can be configured separately + Consumer lambda1 = (String str) -> { }; + + } + + } +``` + +**This rule has the following properties:** + +|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| + +**Use this rule by referencing it:** +``` xml + +``` + +## GenericsNaming + +**Since:** PMD 4.2.6 + +**Priority:** Medium Low (4) + +Names for references to generic values should be limited to a single uppercase letter. + +**This rule is defined by the following XPath expression:** +``` xpath +//TypeDeclaration/ClassOrInterfaceDeclaration/TypeParameters/TypeParameter[ + string-length(@Image) > 1 + or + string:upper-case(@Image) != @Image +] +``` + +**Example(s):** + +``` java +public interface GenericDao extends BaseDao { + // This is ok... +} + +public interface GenericDao { + // Also this +} + +public interface GenericDao { + // 'e' should be an 'E' +} + +public interface GenericDao { + // 'EF' is not ok. +} +``` + +**Use this rule by referencing it:** +``` xml + +``` + +## IdenticalCatchBranches + +**Since:** PMD 6.4.0 + +**Priority:** Medium (3) + +**Minimum Language Version:** Java 1.7 + +Identical `catch` branches use up vertical space and increase the complexity of code without +adding functionality. It's better style to collapse identical branches into a single multi-catch +branch. + +**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.codestyle.IdenticalCatchBranchesRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codestyle/IdenticalCatchBranchesRule.java) + +**Example(s):** + +``` java +try { + // do something +} catch (IllegalArgumentException e) { + throw e; +} catch (IllegalStateException e) { // Can be collapsed into the previous block + throw e; +} + +try { + // do something +} catch (IllegalArgumentException | IllegalStateException e) { // This is better + throw e; +} +``` + +**Use this rule by referencing it:** +``` xml + +``` + +## IfElseStmtsMustUseBraces + +Deprecated + +**Since:** PMD 0.2 + +**Priority:** Medium (3) + +Avoid using if..else statements without using surrounding braces. If the code formatting +or indentation is lost then it becomes difficult to separate the code being controlled +from the rest. + +**This rule is defined by the following XPath expression:** +``` xpath +//Statement + [parent::IfStatement[@Else='true']] + [not(child::Block)] + [not(child::IfStatement)] +``` + +**Example(s):** + +``` java +// this is OK +if (foo) x++; + + // but this is not +if (foo) + x = x+1; + else + x = x-1; +``` + +**Use this rule by referencing it:** +``` xml + +``` + +## IfStmtsMustUseBraces + +Deprecated + +**Since:** PMD 1.0 + +**Priority:** Medium (3) + +Avoid using if statements without using braces to surround the code block. If the code +formatting or indentation is lost then it becomes difficult to separate the code being +controlled from the rest. + +**This rule is defined by the following XPath expression:** +``` xpath +//IfStatement[count(*) < 3][not(Statement/Block)] +``` + +**Example(s):** + +``` java +if (foo) // not recommended + x++; + +if (foo) { // preferred approach + x++; +} +``` + +**Use this rule by referencing it:** +``` xml + +``` + +## 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| +|transformMethodNames|to \| as|The prefixes and infixes that indicate a transform method.|yes. Delimiter is '\|'.| +|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 the configured prefix (see transformMethodNames property).|no| +|checkTransformMethods|false|Check return type of methods which contain the configured infix in their name (see transformMethodNames property).|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 + +**Priority:** Medium Low (4) + +The Local Home interface of a Session EJB should be suffixed by 'LocalHome'. + +**This rule is defined by the following XPath expression:** +``` xpath +//ClassOrInterfaceDeclaration +[ + ( + (./ExtendsList/ClassOrInterfaceType[ends-with(@Image,'EJBLocalHome')]) + ) + and + not + ( + ends-with(@Image,'LocalHome') + ) +] +``` + +**Example(s):** + +``` java +public interface MyBeautifulLocalHome extends javax.ejb.EJBLocalHome {} // proper name + +public interface MissingProperSuffix extends javax.ejb.EJBLocalHome {} // non-standard name +``` + +**Use this rule by referencing it:** +``` xml + +``` + +## LocalInterfaceSessionNamingConvention + +**Since:** PMD 4.0 + +**Priority:** Medium Low (4) + +The Local Interface of a Session EJB should be suffixed by 'Local'. + +**This rule is defined by the following XPath expression:** +``` xpath +//ClassOrInterfaceDeclaration +[ + ( + (./ExtendsList/ClassOrInterfaceType[ends-with(@Image,'EJBLocalObject')]) + ) + and + not + ( + ends-with(@Image,'Local') + ) +] +``` + +**Example(s):** + +``` java +public interface MyLocal extends javax.ejb.EJBLocalObject {} // proper name + +public interface MissingProperSuffix extends javax.ejb.EJBLocalObject {} // non-standard name +``` + +**Use this rule by referencing it:** +``` xml + +``` + +## LocalVariableCouldBeFinal + +**Since:** PMD 2.2 + +**Priority:** Medium (3) + +A local variable assigned only once can be declared final. + +**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.codestyle.LocalVariableCouldBeFinalRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codestyle/LocalVariableCouldBeFinalRule.java) + +**Example(s):** + +``` java +public class Bar { + public void foo () { + String txtA = "a"; // if txtA will not be assigned again it is better to do this: + final String txtB = "b"; + } +} +``` + +**Use this rule by referencing it:** +``` xml + +``` + +## LocalVariableNamingConventions + +**Since:** PMD 6.6.0 + +**Priority:** High (1) + +Configurable naming conventions for local variable declarations and other locally-scoped +variables. This rule reports variable declarations which do not match the regex that applies to their +specific kind (e.g. final variable, or catch-clause parameter). Each regex can be configured through +properties. + +By default this rule uses the standard Java naming convention (Camel case). + +**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.codestyle.LocalVariableNamingConventionsRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codestyle/LocalVariableNamingConventionsRule.java) + +**Example(s):** + +``` java +class Foo { + void bar() { + int localVariable = 1; // This is in camel case, so it's ok + int local_variable = 1; // This will be reported unless you change the regex + + final int i_var = 1; // final local variables can be configured separately + + try { + foo(); + } catch (IllegalArgumentException e_illegal) { + // exception block parameters can be configured separately + } + + } + } +``` + +**This rule has the following properties:** + +|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| + +**Use this rule by referencing it:** +``` xml + +``` + +## LongVariable + +**Since:** PMD 0.3 + +**Priority:** Medium (3) + +Fields, formal arguments, or local variable names that are too long can make the code difficult to follow. + +**This rule is defined by the following XPath expression:** +``` xpath +//VariableDeclaratorId[string-length(@Image) > $minimum] +``` + +**Example(s):** + +``` java +public class Something { + int reallyLongIntName = -3; // VIOLATION - Field + public static void main( String argumentsList[] ) { // VIOLATION - Formal + int otherReallyLongName = -5; // VIOLATION - Local + for (int interestingIntIndex = 0; // VIOLATION - For + interestingIntIndex < 10; + interestingIntIndex ++ ) { + } +} +``` + +**This rule has the following properties:** + +|Name|Default Value|Description|Multivalued| +|----|-------------|-----------|-----------| +|minimum|17|The variable length reporting threshold|no| + +**Use this rule by referencing it:** +``` xml + +``` + +## MDBAndSessionBeanNamingConvention + +**Since:** PMD 4.0 + +**Priority:** Medium Low (4) + +The EJB Specification states that any MessageDrivenBean or SessionBean should be suffixed by 'Bean'. + +**This rule is defined by the following XPath expression:** +``` xpath +//TypeDeclaration/ClassOrInterfaceDeclaration +[ + ( + (./ImplementsList/ClassOrInterfaceType[ends-with(@Image,'SessionBean')]) + or + (./ImplementsList/ClassOrInterfaceType[ends-with(@Image,'MessageDrivenBean')]) + ) + and + not + ( + ends-with(@Image,'Bean') + ) +] +``` + +**Example(s):** + +``` java +public class SomeBean implements SessionBean{} // proper name + +public class MissingTheProperSuffix implements SessionBean {} // non-standard name +``` + +**Use this rule by referencing it:** +``` xml + +``` + +## MethodArgumentCouldBeFinal + +**Since:** PMD 2.2 + +**Priority:** Medium (3) + +A method argument that is never re-assigned within the method can be declared final. + +**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.codestyle.MethodArgumentCouldBeFinalRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codestyle/MethodArgumentCouldBeFinalRule.java) + +**Example(s):** + +``` java +public void foo1 (String param) { // do stuff with param never assigning it + +} + +public void foo2 (final String param) { // better, do stuff with param never assigning it + +} +``` + +**Use this rule by referencing it:** +``` xml + +``` + +## MethodNamingConventions + +**Since:** PMD 1.2 + +**Priority:** High (1) + +Configurable naming conventions for method declarations. This rule reports +method declarations which do not match the regex that applies to their +specific kind (e.g. JUnit test or native method). Each regex can be +configured through properties. + +By default this rule uses the standard Java naming convention (Camel case). + +**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.codestyle.MethodNamingConventionsRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codestyle/MethodNamingConventionsRule.java) + +**Example(s):** + +``` java +public class Foo { + public void fooStuff() { + } +} +``` + +**This rule has the following properties:** + +|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| +|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| + +**Use this rule by referencing it:** +``` xml + +``` + +## MIsLeadingVariableName + +Deprecated + +**Since:** PMD 3.4 + +**Priority:** Medium (3) + +Detects when a non-field has a name starting with 'm_'. This usually denotes a field and could be confusing. + +**This rule is defined by the following XPath expression:** +``` xpath +//VariableDeclaratorId +[starts-with(@Image, 'm_')] +[not (../../../FieldDeclaration)] +``` + +**Example(s):** + +``` java +public class Foo { + private int m_foo; // OK + public void bar(String m_baz) { // Bad + int m_boz = 42; // Bad + } +} +``` + +**Use this rule by referencing it:** +``` xml + +``` + +## NoPackage + +**Since:** PMD 3.3 + +**Priority:** Medium (3) + +Detects when a class or interface does not have a package definition. + +**This rule is defined by the following XPath expression:** +``` xpath +//ClassOrInterfaceDeclaration[count(preceding::PackageDeclaration) = 0] +``` + +**Example(s):** + +``` java +// no package declaration +public class ClassInDefaultPackage { +} +``` + +**Use this rule by referencing it:** +``` xml + +``` + +## OnlyOneReturn + +**Since:** PMD 1.0 + +**Priority:** Medium (3) + +A method should have only one exit point, and that should be the last statement in the method. + +**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.codestyle.OnlyOneReturnRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codestyle/OnlyOneReturnRule.java) + +**Example(s):** + +``` java +public class OneReturnOnly1 { + public void foo(int x) { + if (x > 0) { + return "hey"; // first exit + } + return "hi"; // second exit + } +} +``` + +**Use this rule by referencing it:** +``` xml + +``` + +## PackageCase + +**Since:** PMD 3.3 + +**Priority:** Medium (3) + +Detects when a package definition contains uppercase characters. + +**This rule is defined by the following XPath expression:** +``` xpath +//PackageDeclaration/Name[lower-case(@Image)!=@Image] +``` + +**Example(s):** + +``` java +package com.MyCompany; // should be lowercase name + +public class SomeClass { +} +``` + +**Use this rule by referencing it:** +``` xml + +``` + +## PrematureDeclaration + +**Since:** PMD 5.0 + +**Priority:** Medium (3) + +Checks for variables that are defined before they might be used. A reference is deemed to be premature if it is created right before a block of code that doesn't use it that also has the ability to return or throw an exception. + +**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.codestyle.PrematureDeclarationRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codestyle/PrematureDeclarationRule.java) + +**Example(s):** + +``` java +public int getLength(String[] strings) { + + int length = 0; // declared prematurely + + if (strings == null || strings.length == 0) return 0; + + for (String str : strings) { + length += str.length(); + } + + return length; +} +``` + +**Use this rule by referencing it:** +``` xml + +``` + +## RemoteInterfaceNamingConvention + +**Since:** PMD 4.0 + +**Priority:** Medium Low (4) + +Remote Interface of a Session EJB should not have a suffix. + +**This rule is defined by the following XPath expression:** +``` xpath +//ClassOrInterfaceDeclaration +[ + ( + (./ExtendsList/ClassOrInterfaceType[ends-with(@Image,'EJBObject')]) + ) + and + ( + ends-with(@Image,'Session') + or + ends-with(@Image,'EJB') + or + ends-with(@Image,'Bean') + ) +] +``` + +**Example(s):** + +``` java +/* Poor Session suffix */ +public interface BadSuffixSession extends javax.ejb.EJBObject {} + +/* Poor EJB suffix */ +public interface BadSuffixEJB extends javax.ejb.EJBObject {} + +/* Poor Bean suffix */ +public interface BadSuffixBean extends javax.ejb.EJBObject {} +``` + +**Use this rule by referencing it:** +``` xml + +``` + +## RemoteSessionInterfaceNamingConvention + +**Since:** PMD 4.0 + +**Priority:** Medium Low (4) + +A Remote Home interface type of a Session EJB should be suffixed by 'Home'. + +**This rule is defined by the following XPath expression:** +``` xpath +//ClassOrInterfaceDeclaration +[ + ( + (./ExtendsList/ClassOrInterfaceType[ends-with(@Image,'EJBHome')]) + ) + and + not + ( + ends-with(@Image,'Home') + ) +] +``` + +**Example(s):** + +``` java +public interface MyBeautifulHome extends javax.ejb.EJBHome {} // proper name + +public interface MissingProperSuffix extends javax.ejb.EJBHome {} // non-standard name +``` + +**Use this rule by referencing it:** +``` xml + +``` + +## ShortClassName + +**Since:** PMD 5.0 + +**Priority:** Medium Low (4) + +Short Classnames with fewer than e.g. five characters are not recommended. + +**This rule is defined by the following XPath expression:** +``` xpath +//ClassOrInterfaceDeclaration[string-length(@Image) < $minimum] +``` + +**Example(s):** + +``` java +public class Foo { +} +``` + +**This rule has the following properties:** + +|Name|Default Value|Description|Multivalued| +|----|-------------|-----------|-----------| +|minimum|5|Number of characters that are required as a minimum for a class name.|no| + +**Use this rule by referencing it:** +``` xml + +``` + +## ShortMethodName + +**Since:** PMD 0.3 + +**Priority:** Medium (3) + +Method names that are very short are not helpful to the reader. + +**This rule is defined by the following XPath expression:** +``` xpath +//MethodDeclarator[string-length(@Image) < $minimum] +``` + +**Example(s):** + +``` java +public class ShortMethod { + public void a( int i ) { // Violation + } +} +``` + +**This rule has the following properties:** + +|Name|Default Value|Description|Multivalued| +|----|-------------|-----------|-----------| +|minimum|3|Number of characters that are required as a minimum for a method name.|no| + +**Use this rule by referencing it:** +``` xml + +``` + +## ShortVariable + +**Since:** PMD 0.3 + +**Priority:** Medium (3) + +Fields, local variables, or parameter names that are very short are not helpful to the reader. + +**This rule is defined by the following XPath expression:** +``` xpath +//VariableDeclaratorId[string-length(@Image) < $minimum] + (: ForStatement :) + [not(../../..[self::ForInit])] + (: Foreach statement :) + [not(../../..[self::ForStatement])] + (: Catch statement parameter :) + [not(../..[self::CatchStatement])] + (: Lambda expression parameter :) + [not(parent::LambdaExpression or ../../..[self::LambdaExpression])] +``` + +**Example(s):** + +``` java +public class Something { + private int q = 15; // field - too short + public static void main( String as[] ) { // formal arg - too short + int r = 20 + q; // local var - too short + for (int i = 0; i < 10; i++) { // not a violation (inside 'for' loop) + r += q; + } + for (Integer i : numbers) { // not a violation (inside 'for-each' loop) + r += q; + } + } +} +``` + +**This rule has the following properties:** + +|Name|Default Value|Description|Multivalued| +|----|-------------|-----------|-----------| +|minimum|3|Number of characters that are required as a minimum for a variable name.|no| + +**Use this rule by referencing it:** +``` xml + +``` + +## SuspiciousConstantFieldName + +Deprecated + +**Since:** PMD 2.0 + +**Priority:** Medium (3) + +Field names using all uppercase characters - Sun's Java naming conventions indicating constants - should +be declared as final. + +**This rule is defined by the following XPath expression:** +``` xpath +//ClassOrInterfaceDeclaration[@Interface='false'] + /ClassOrInterfaceBody/ClassOrInterfaceBodyDeclaration/FieldDeclaration + [@Final='false'] + [VariableDeclarator/VariableDeclaratorId[upper-case(@Image)=@Image]] +``` + +**Example(s):** + +``` java +public class Foo { + // this is bad, since someone could accidentally + // do PI = 2.71828; which is actually e + // final double PI = 3.16; is ok + double PI = 3.16; +} +``` + +**Use this rule by referencing it:** +``` xml + +``` + +## TooManyStaticImports + +**Since:** PMD 4.1 + +**Priority:** Medium (3) + +If you overuse the static import feature, it can make your program unreadable and +unmaintainable, polluting its namespace with all the static members you import. +Readers of your code (including you, a few months after you wrote it) will not know +which class a static member comes from (Sun 1.5 Language Guide). + +**This rule is defined by the following XPath expression:** +``` xpath +.[count(ImportDeclaration[@Static = 'true']) > $maximumStaticImports] +``` + +**Example(s):** + +``` java +import static Lennon; +import static Ringo; +import static George; +import static Paul; +import static Yoko; // Too much ! +``` + +**This rule has the following properties:** + +|Name|Default Value|Description|Multivalued| +|----|-------------|-----------|-----------| +|maximumStaticImports|4|All static imports can be disallowed by setting this to 0|no| + +**Use this rule by referencing it:** +``` xml + +``` + +## UnnecessaryAnnotationValueElement + +**Since:** PMD 6.2.0 + +**Priority:** Medium (3) + +Avoid the use of value in annotations when it's the only element. + +**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.codestyle.UnnecessaryAnnotationValueElementRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codestyle/UnnecessaryAnnotationValueElementRule.java) + +**Example(s):** + +``` java +@TestClassAnnotation(value = "TEST") +public class Foo { + + @TestMemberAnnotation(value = "TEST") + private String y; + + @TestMethodAnnotation(value = "TEST") + public void bar() { + int x = 42; + return; + } +} + +// should be + +@TestClassAnnotation("TEST") +public class Foo { + + @TestMemberAnnotation("TEST") + private String y; + + @TestMethodAnnotation("TEST") + public void bar() { + int x = 42; + return; + } +} +``` + +**Use this rule by referencing it:** +``` xml + +``` + +## UnnecessaryConstructor + +**Since:** PMD 1.0 + +**Priority:** Medium (3) + +This rule detects when a constructor is not necessary; i.e., when there is only one constructor and the +constructor is identical to the default constructor. The default constructor should has same access +modifier as the declaring class. In an enum type, the default constructor is implicitly private. + +**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.codestyle.UnnecessaryConstructorRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codestyle/UnnecessaryConstructorRule.java) + +**Example(s):** + +``` java +public class Foo { + public Foo() {} +} +``` + +**This rule has the following properties:** + +|Name|Default Value|Description|Multivalued| +|----|-------------|-----------|-----------| +|ignoredAnnotations|javax.inject.Inject|Fully qualified names of the annotation types that should be ignored by this rule|yes. Delimiter is '\|'.| + +**Use this rule by referencing it:** +``` xml + +``` + +## UnnecessaryFullyQualifiedName + +**Since:** PMD 5.0 + +**Priority:** Medium Low (4) + +Import statements allow the use of non-fully qualified names. The use of a fully qualified name +which is covered by an import statement is redundant. Consider using the non-fully qualified name. + +**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.codestyle.UnnecessaryFullyQualifiedNameRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codestyle/UnnecessaryFullyQualifiedNameRule.java) + +**Example(s):** + +``` java +import java.util.List; + +public class Foo { + private java.util.List list1; // Unnecessary FQN + private List list2; // More appropriate given import of 'java.util.List' +} +``` + +**Use this rule by referencing it:** +``` xml + +``` + +## UnnecessaryLocalBeforeReturn + +**Since:** PMD 3.3 + +**Priority:** Medium (3) + +Avoid the creation of unnecessary local variables + +**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.codestyle.UnnecessaryLocalBeforeReturnRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codestyle/UnnecessaryLocalBeforeReturnRule.java) + +**Example(s):** + +``` java +public class Foo { + public int foo() { + int x = doSomething(); + return x; // instead, just 'return doSomething();' + } +} +``` + +**This rule has the following properties:** + +|Name|Default Value|Description|Multivalued| +|----|-------------|-----------|-----------| +|statementOrderMatters|true|If set to false this rule no longer requires the variable declaration and return statement to be on consecutive lines. Any variable that is used solely in a return statement will be reported.|no| + +**Use this rule by referencing it:** +``` xml + +``` + +## UnnecessaryModifier + +**Since:** PMD 1.02 + +**Priority:** Medium (3) + +Fields in interfaces and annotations are automatically `public static final`, and methods are `public abstract`. +Classes, interfaces or annotations nested in an interface or annotation are automatically `public static` +(all nested interfaces and annotations are automatically static). +Nested enums are automatically `static`. +For historical reasons, modifiers which are implied by the context are accepted by the compiler, but are superfluous. + +**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.codestyle.UnnecessaryModifierRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codestyle/UnnecessaryModifierRule.java) + +**Example(s):** + +``` java +public @interface Annotation { + public abstract void bar(); // both abstract and public are ignored by the compiler + public static final int X = 0; // public, static, and final all ignored + public static class Bar {} // public, static ignored + public static interface Baz {} // ditto +} +public interface Foo { + public abstract void bar(); // both abstract and public are ignored by the compiler + public static final int X = 0; // public, static, and final all ignored + public static class Bar {} // public, static ignored + public static interface Baz {} // ditto +} +public class Bar { + public static interface Baz {} // static ignored + public static enum FoorBar { // static ignored + FOO; + } +} +``` + +**Use this rule by referencing it:** +``` xml + +``` + +## UnnecessaryReturn + +**Since:** PMD 1.3 + +**Priority:** Medium (3) + +Avoid the use of unnecessary return statements. + +**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.codestyle.UnnecessaryReturnRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codestyle/UnnecessaryReturnRule.java) + +**Example(s):** + +``` java +public class Foo { + public void bar() { + int x = 42; + return; + } +} +``` + +**Use this rule by referencing it:** +``` xml + +``` + +## UselessParentheses + +**Since:** PMD 5.0 + +**Priority:** Medium Low (4) + +Useless parentheses should be removed. + +**This rule is defined by the following XPath expression:** +``` xpath +//Expression[not(parent::PrimaryPrefix)]/PrimaryExpression[count(*)>1] + /PrimaryPrefix/Expression + [not(./CastExpression)] + [not(./ConditionalExpression)] + [not(./AdditiveExpression)] + [not(./AssignmentOperator)] +| +//Expression[not(parent::PrimaryPrefix)]/PrimaryExpression[count(*)=1] + /PrimaryPrefix/Expression +| +//Expression/ConditionalAndExpression/PrimaryExpression/PrimaryPrefix/Expression[ + count(*)=1 and + count(./CastExpression)=0 and + count(./EqualityExpression/MultiplicativeExpression)=0 and + count(./ConditionalExpression)=0 and + count(./ConditionalOrExpression)=0] +| +//Expression/ConditionalOrExpression/PrimaryExpression/PrimaryPrefix/Expression[ + count(*)=1 and + not(./CastExpression) and + not(./ConditionalExpression) and + not(./EqualityExpression/MultiplicativeExpression)] +| +//Expression/ConditionalExpression/PrimaryExpression/PrimaryPrefix/Expression[ + count(*)=1 and + not(./CastExpression) and + not(./EqualityExpression)] +| +//Expression/AdditiveExpression[not(./PrimaryExpression/PrimaryPrefix/Literal[@StringLiteral='true'])] + /PrimaryExpression[1]/PrimaryPrefix/Expression[ + count(*)=1 and + not(./CastExpression) and + not(./AdditiveExpression[@Image = '-']) and + not(./ShiftExpression) and + not(./RelationalExpression) and + not(./InstanceOfExpression) and + not(./EqualityExpression) and + not(./AndExpression) and + not(./ExclusiveOrExpression) and + not(./InclusiveOrExpression) and + not(./ConditionalAndExpression) and + not(./ConditionalOrExpression) and + not(./ConditionalExpression)] +| +//Expression/EqualityExpression/PrimaryExpression/PrimaryPrefix/Expression[ + count(*)=1 and + not(./CastExpression) and + not(./AndExpression) and + not(./InclusiveOrExpression) and + not(./ExclusiveOrExpression) and + not(./ConditionalExpression) and + not(./ConditionalAndExpression) and + not(./ConditionalOrExpression) and + not(./EqualityExpression)] +``` + +**Example(s):** + +``` java +public class Foo { + + private int _bar1; + private Integer _bar2; + + public void setBar(int n) { + _bar1 = Integer.valueOf((n)); // here + _bar2 = (n); // and here + } +} +``` + +**Use this rule by referencing it:** +``` xml + +``` + +## UselessQualifiedThis + +**Since:** PMD 5.4.0 + +**Priority:** Medium (3) + +Reports qualified this usages in the same class. + +**This rule is defined by the following XPath expression:** +``` xpath +//PrimaryExpression +[PrimaryPrefix/Name[@Image]] +[PrimarySuffix[@Arguments='false' and @ArrayDereference = 'false']] +[not(PrimarySuffix/MemberSelector)] +[ancestor::ClassOrInterfaceBodyDeclaration[1][@AnonymousInnerClass='false']] +/PrimaryPrefix/Name[@Image = ancestor::ClassOrInterfaceDeclaration[1]/@Image] +``` + +**Example(s):** + +``` java +public class Foo { + final Foo otherFoo = Foo.this; // use "this" directly + + public void doSomething() { + final Foo anotherFoo = Foo.this; // use "this" directly + } + + private ActionListener returnListener() { + return new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + doSomethingWithQualifiedThis(Foo.this); // This is fine + } + }; + } + + private class Foo3 { + final Foo myFoo = Foo.this; // This is fine + } + + private class Foo2 { + final Foo2 myFoo2 = Foo2.this; // Use "this" direclty + } +} +``` + +**Use this rule by referencing it:** +``` xml + +``` + +## UseUnderscoresInNumericLiterals + +**Since:** PMD 6.10.0 + +**Priority:** Medium (3) + +**Minimum Language Version:** Java 1.7 + +Since Java 1.7, numeric literals can use underscores to separate digits. This rule enforces that +numeric literals above a certain length use these underscores to increase readability. + +The rule only supports decimal (base 10) literals for now. The acceptable length under which literals +are not required to have underscores is configurable via a property. Even under that length, underscores +that are misplaced (not making groups of 3 digits) are reported. + +**This rule is defined by the following XPath expression:** +``` xpath +//Literal[ + @IntLiteral = true() + or @LongLiteral = true() + or @DoubleLiteral = true() + or @FloatLiteral = true() +] + (: Filter out literals in base other than 10 :) + [not(matches(@Image, "^0[^.]"))] + (: Filter out ignored field name :) + [not(ancestor::VariableDeclarator[1][@Name = 'serialVersionUID'])] + [ + some $num in tokenize(@Image, "[.dDfFlLeE+\-]") + satisfies not( + string-length($num) <= $acceptableDecimalLength + and not(contains($num,"_")) + or matches($num, "^[0-9]{1,3}(_[0-9]{3})*$") + ) + ] +``` + +**Example(s):** + +``` java +public class Foo { + private int num = 1000000; // should be 1_000_000 +} +``` + +**This rule has the following properties:** + +|Name|Default Value|Description|Multivalued| +|----|-------------|-----------|-----------| +|acceptableDecimalLength|4|Length under which literals in base 10 are not required to have underscores|no| + +**Use this rule by referencing it:** +``` xml + +``` + +## VariableNamingConventions + +Deprecated + +**Since:** PMD 1.2 + +**Priority:** High (1) + +A variable naming conventions rule - customize this to your liking. Currently, it +checks for final variables that should be fully capitalized and non-final variables +that should not include underscores. + +**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.codestyle.VariableNamingConventionsRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codestyle/VariableNamingConventionsRule.java) + +**Example(s):** + +``` java +public class Foo { + public static final int MY_NUM = 0; + public String myTest = ""; + DataModule dmTest = new DataModule(); +} +``` + +**This rule has the following properties:** + +|Name|Default Value|Description|Multivalued| +|----|-------------|-----------|-----------| +|parameterSuffix||Method parameter variable suffixes|yes. Delimiter is ','.| +|parameterPrefix||Method parameter variable prefixes|yes. Delimiter is ','.| +|localSuffix||Local variable suffixes|yes. Delimiter is ','.| +|localPrefix||Local variable prefixes|yes. Delimiter is ','.| +|memberSuffix||Member variable suffixes|yes. Delimiter is ','.| +|memberPrefix||Member variable prefixes|yes. Delimiter is ','.| +|staticSuffix||Static variable suffixes|yes. Delimiter is ','.| +|checkParameters|true|Check constructor and method parameter variables|no| +|checkNativeMethodParameters|true|Check method parameter of native methods|no| +|staticPrefix||Static variable prefixes|yes. Delimiter is ','.| +|checkLocals|true|Check local variables|no| +|checkMembers|true|Check member variables|no| + +**Use this rule by referencing it:** +``` xml + +``` + +## WhileLoopsMustUseBraces + +Deprecated + +**Since:** PMD 0.7 + +**Priority:** Medium (3) + +Avoid using 'while' statements without using braces to surround the code block. If the code +formatting or indentation is lost then it becomes difficult to separate the code being +controlled from the rest. + +**This rule is defined by the following XPath expression:** +``` xpath +//WhileStatement[not(Statement/Block)] +``` + +**Example(s):** + +``` java +while (true) // not recommended + x++; + +while (true) { // preferred approach + x++; +} +``` + +**Use this rule by referencing it:** +``` xml + +``` + diff --git a/docs/pages/pmd/rules/java/comments.md b/docs/pages/pmd/rules/java/comments.md deleted file mode 100644 index ac237f75f6c..00000000000 --- a/docs/pages/pmd/rules/java/comments.md +++ /dev/null @@ -1,165 +0,0 @@ ---- -title: Comments -summary: Rules intended to catch errors related to code comments -permalink: pmd_rules_java_comments.html -folder: pmd/rules/java -sidebaractiveurl: /pmd_rules_java.html -editmepath: ../pmd-java/src/main/resources/rulesets/java/comments.xml -keywords: Comments, CommentRequired, CommentSize, CommentContent, CommentDefaultAccessModifier ---- -## CommentContent - -**Since:** PMD 5.0 - -**Priority:** Medium (3) - -A rule for the politically correct... we don't want to offend anyone. - -**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.comments.CommentContentRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/comments/CommentContentRule.java) - -**Example(s):** - -``` java -//OMG, this is horrible, Bob is an idiot !!! -``` - -**This rule has the following properties:** - -|Name|Default Value|Description| -|----|-------------|-----------| -|disallowedTerms|[idiot, jerk]|Illegal terms or phrases| -|caseSensitive|false|Case sensitive| -|wordsAreRegex|false|Use regular expressions| - -**Use this rule by referencing it:** -``` xml - -``` - -## CommentDefaultAccessModifier - -**Since:** PMD 5.4.0 - -**Priority:** Medium (3) - -To avoid mistakes if we want that a Method, Constructor, Field or Nested class have a default access modifier - we must add a comment at the beginning of it's declaration. - By default the comment must be /* default */, if you want another, you have to provide a regexp. - -**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.comments.CommentDefaultAccessModifierRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/comments/CommentDefaultAccessModifierRule.java) - -**Example(s):** - -``` java -public class Foo { - final String stringValue = "some string"; - String getString() { - return stringValue; - } - - class NestedFoo { - } - } - - // should be - public class Foo { - /* default */ final String stringValue = "some string"; - /* default */ String getString() { - return stringValue; - } - - /* default */ class NestedFoo { - } - } -``` - -**This rule has the following properties:** - -|Name|Default Value|Description| -|----|-------------|-----------| -|regex||Regular expression| - -**Use this rule by referencing it:** -``` xml - -``` - -## CommentRequired - -**Since:** PMD 5.1 - -**Priority:** Medium (3) - -Denotes whether comments are required (or unwanted) for specific language elements. - -**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.comments.CommentRequiredRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/comments/CommentRequiredRule.java) - -**Example(s):** - -``` java -/** -* -* -* @author Jon Doe -*/ -``` - -**This rule has the following properties:** - -|Name|Default Value|Description| -|----|-------------|-----------| -|serialVersionUIDCommentRequired|Ignored|serial version UID commts. Possible values: [Required, Ignored, Unwanted]| -|enumCommentRequirement|Required|Enum comments. Possible values: [Required, Ignored, Unwanted]| -|protectedMethodCommentRequirement|Required|Protected method constructor comments. Possible values: [Required, Ignored, Unwanted]| -|publicMethodCommentRequirement|Required|Public method and constructor comments. Possible values: [Required, Ignored, Unwanted]| -|fieldCommentRequirement|Required|Field comments. Possible values: [Required, Ignored, Unwanted]| -|headerCommentRequirement|Required|Header comments. Possible values: [Required, Ignored, Unwanted]| - -**Use this rule by referencing it:** -``` xml - -``` - -## CommentSize - -**Since:** PMD 5.0 - -**Priority:** Medium (3) - -Determines whether the dimensions of non-header comments found are within the specified limits. - -**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.comments.CommentSizeRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/comments/CommentSizeRule.java) - -**Example(s):** - -``` java -/** -* -* too many lines! -* -* -* -* -* -* -* -* -* -* -* -* -*/ -``` - -**This rule has the following properties:** - -|Name|Default Value|Description| -|----|-------------|-----------| -|maxLines|6|Maximum lines| -|maxLineLength|80|Maximum line length| - -**Use this rule by referencing it:** -``` xml - -``` - diff --git a/docs/pages/pmd/rules/java/controversial.md b/docs/pages/pmd/rules/java/controversial.md deleted file mode 100644 index ec1ec4e7f6f..00000000000 --- a/docs/pages/pmd/rules/java/controversial.md +++ /dev/null @@ -1,888 +0,0 @@ ---- -title: Controversial -summary: The Controversial ruleset contains rules that, for whatever reason, are considered controversial. They are held here to allow people to include them as they see fit within their custom rulesets. -permalink: pmd_rules_java_controversial.html -folder: pmd/rules/java -sidebaractiveurl: /pmd_rules_java.html -editmepath: ../pmd-java/src/main/resources/rulesets/java/controversial.xml -keywords: Controversial, UnnecessaryConstructor, NullAssignment, OnlyOneReturn, AssignmentInOperand, AtLeastOneConstructor, DontImportSun, SuspiciousOctalEscape, CallSuperInConstructor, UnnecessaryParentheses, DefaultPackage, DataflowAnomalyAnalysis, AvoidFinalLocalVariable, AvoidUsingShortType, AvoidUsingVolatile, AvoidUsingNativeCode, AvoidAccessibilityAlteration, DoNotCallGarbageCollectionExplicitly, OneDeclarationPerLine, AvoidPrefixingMethodParameters, AvoidLiteralsInIfCondition, UseObjectForClearerAPI, UseConcurrentHashMap ---- -## AssignmentInOperand - -**Since:** PMD 1.03 - -**Priority:** Medium (3) - -Avoid assignments in operands; this can make code more complicated and harder to read. - -**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.controversial.AssignmentInOperandRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/controversial/AssignmentInOperandRule.java) - -**Example(s):** - -``` java -public void bar() { - int x = 2; - if ((x = getX()) == 3) { - System.out.println("3!"); - } -} -``` - -**This rule has the following properties:** - -|Name|Default Value|Description| -|----|-------------|-----------| -|allowIncrementDecrement|false|Allow increment or decrement operators within the conditional expression of an if, for, or while statement| -|allowWhile|false|Allow assignment within the conditional expression of a while statement| -|allowFor|false|Allow assignment within the conditional expression of a for statement| -|allowIf|false|Allow assignment within the conditional expression of an if statement| - -**Use this rule by referencing it:** -``` xml - -``` - -## AtLeastOneConstructor - -**Since:** PMD 1.04 - -**Priority:** Medium (3) - -Each class should declare at least one constructor. - -``` -//ClassOrInterfaceDeclaration[ - not(ClassOrInterfaceBody/ClassOrInterfaceBodyDeclaration/ConstructorDeclaration) - and - (@Static = 'false') - and - (count(./descendant::MethodDeclaration[@Static = 'true']) < 1) -] - [@Interface='false'] -``` - -**Example(s):** - -``` java -public class Foo { - // missing constructor - public void doSomething() { ... } - public void doOtherThing { ... } -} -``` - -**Use this rule by referencing it:** -``` xml - -``` - -## AvoidAccessibilityAlteration - -**Since:** PMD 4.1 - -**Priority:** Medium (3) - -Methods such as getDeclaredConstructors(), getDeclaredConstructor(Class[]) and setAccessible(), -as the interface PrivilegedAction, allows for the runtime alteration of variable, class, or -method visibility, even if they are private. This violates the principle of encapsulation. - -``` -//PrimaryExpression[ - ( - (PrimarySuffix[ - ends-with(@Image,'getDeclaredConstructors') - or - ends-with(@Image,'getDeclaredConstructor') - or - ends-with(@Image,'setAccessible') - ]) - or - (PrimaryPrefix/Name[ - ends-with(@Image,'getDeclaredConstructor') - or - ends-with(@Image,'getDeclaredConstructors') - or - starts-with(@Image,'AccessibleObject.setAccessible') - ]) - ) - and - (//ImportDeclaration/Name[ - contains(@Image,'java.security.PrivilegedAction')]) - ] -``` - -**Example(s):** - -``` java -import java.lang.reflect.AccessibleObject; -import java.lang.reflect.Method; -import java.security.PrivilegedAction; - -public class Violation { - public void invalidCallsInMethod() throws SecurityException, NoSuchMethodException { - // Possible call to forbidden getDeclaredConstructors - Class[] arrayOfClass = new Class[1]; - this.getClass().getDeclaredConstructors(); - this.getClass().getDeclaredConstructor(arrayOfClass); - Class clazz = this.getClass(); - clazz.getDeclaredConstructor(arrayOfClass); - clazz.getDeclaredConstructors(); - // Possible call to forbidden setAccessible - clazz.getMethod("", arrayOfClass).setAccessible(false); - AccessibleObject.setAccessible(null, false); - Method.setAccessible(null, false); - Method[] methodsArray = clazz.getMethods(); - int nbMethod; - for ( nbMethod = 0; nbMethod < methodsArray.length; nbMethod++ ) { - methodsArray[nbMethod].setAccessible(false); - } - - // Possible call to forbidden PrivilegedAction - PrivilegedAction priv = (PrivilegedAction) new Object(); priv.run(); - } -} -``` - -**Use this rule by referencing it:** -``` xml - -``` - -## AvoidFinalLocalVariable - -**Since:** PMD 4.1 - -**Priority:** Medium (3) - -Avoid using final local variables, turn them into fields. - -``` -//LocalVariableDeclaration[ - @Final = 'true' - and not(../../ForStatement) - and - ( - (count(VariableDeclarator/VariableInitializer) = 0) - or - (VariableDeclarator/VariableInitializer/Expression/PrimaryExpression/PrimaryPrefix/Literal) - ) -] -``` - -**Example(s):** - -``` java -public class MyClass { - public void foo() { - final String finalLocalVariable; - } -} -``` - -**Use this rule by referencing it:** -``` xml - -``` - -## AvoidLiteralsInIfCondition - -**Since:** PMD 4.2.6 - -**Priority:** Medium (3) - -Avoid using hard-coded literals in conditional statements. By declaring them as static variables -or private members with descriptive names maintainability is enhanced. By default, the literals "-1" and "0" are ignored. -More exceptions can be defined with the property "ignoreMagicNumbers". - -``` -//IfStatement/Expression/*/PrimaryExpression/PrimaryPrefix/Literal -[not(NullLiteral)] -[not(BooleanLiteral)] -[empty(index-of(tokenize($ignoreMagicNumbers, '\s*,\s*'), @Image))] -``` - -**Example(s):** - -``` java -private static final int MAX_NUMBER_OF_REQUESTS = 10; - -public void checkRequests() { - - if (i == 10) { // magic number, buried in a method - doSomething(); - } - - if (i == MAX_NUMBER_OF_REQUESTS) { // preferred approach - doSomething(); - } - - if (aString.indexOf('.') != -1) {} // magic number -1, by default ignored - if (aString.indexOf('.') >= 0) { } // alternative approach - - if (aDouble > 0.0) {} // magic number 0.0 - if (aDouble >= Double.MIN_VALUE) {} // preferred approach -} -``` - -**This rule has the following properties:** - -|Name|Default Value|Description| -|----|-------------|-----------| -|ignoreMagicNumbers|-1,0|Comma-separated list of magic numbers, that should be ignored| - -**Use this rule by referencing it:** -``` xml - -``` - -## AvoidPrefixingMethodParameters - -**Since:** PMD 5.0 - -**Priority:** Medium Low (4) - -Prefixing parameters by 'in' or 'out' pollutes the name of the parameters and reduces code readability. -To indicate whether or not a parameter will be modify in a method, its better to document method -behavior with Javadoc. - -``` -//MethodDeclaration/MethodDeclarator/FormalParameters/FormalParameter/VariableDeclaratorId[ - pmd:matches(@Image,'^in[A-Z].*','^out[A-Z].*','^in$','^out$') -] -``` - -**Example(s):** - -``` java -// Not really clear -public class Foo { - public void bar( - int inLeftOperand, - Result outRightOperand) { - outRightOperand.setValue(inLeftOperand * outRightOperand.getValue()); - } -} -``` - -``` java -// Far more useful -public class Foo { - /** - * - * @param leftOperand, (purpose), not modified by method. - * @param rightOperand (purpose), will be modified by the method: contains the result. - */ - public void bar( - int leftOperand, - Result rightOperand) { - rightOperand.setValue(leftOperand * rightOperand.getValue()); - } -} -``` - -**Use this rule by referencing it:** -``` xml - -``` - -## AvoidUsingNativeCode - -**Since:** PMD 4.1 - -**Priority:** Medium High (2) - -Unnecessary reliance on Java Native Interface (JNI) calls directly reduces application portability -and increases the maintenance burden. - -``` -//Name[starts-with(@Image,'System.loadLibrary')] -``` - -**Example(s):** - -``` java -public class SomeJNIClass { - - public SomeJNIClass() { - System.loadLibrary("nativelib"); - } - - static { - System.loadLibrary("nativelib"); - } - - public void invalidCallsInMethod() throws SecurityException, NoSuchMethodException { - System.loadLibrary("nativelib"); - } -} -``` - -**Use this rule by referencing it:** -``` xml - -``` - -## AvoidUsingShortType - -**Since:** PMD 4.1 - -**Priority:** High (1) - -Java uses the 'short' type to reduce memory usage, not to optimize calculation. In fact, the JVM does not have any -arithmetic capabilities for the short type: the JVM must convert the short into an int, do the proper calculation -and convert the int back to a short. Thus any storage gains found through use of the 'short' type may be offset by -adverse impacts on performance. - -``` -//PrimitiveType[@Image = 'short'][name(../..) != 'CastExpression'] -``` - -**Example(s):** - -``` java -public class UsingShort { - private short doNotUseShort = 0; - - public UsingShort() { - short shouldNotBeUsed = 1; - doNotUseShort += shouldNotBeUsed; - } -} -``` - -**Use this rule by referencing it:** -``` xml - -``` - -## AvoidUsingVolatile - -**Since:** PMD 4.1 - -**Priority:** Medium High (2) - -Use of the keyword 'volatile' is generally used to fine tune a Java application, and therefore, requires -a good expertise of the Java Memory Model. Moreover, its range of action is somewhat misknown. Therefore, -the volatile keyword should not be used for maintenance purpose and portability. - -``` -//FieldDeclaration[contains(@Volatile,'true')] -``` - -**Example(s):** - -``` java -public class ThrDeux { - private volatile String var1; // not suggested - private String var2; // preferred -} -``` - -**Use this rule by referencing it:** -``` xml - -``` - -## CallSuperInConstructor - -**Since:** PMD 3.0 - -**Priority:** Medium (3) - -It is a good practice to call super() in a constructor. If super() is not called but -another constructor (such as an overloaded constructor) is called, this rule will not report it. - -``` -//ClassOrInterfaceDeclaration[ count (ExtendsList/*) > 0 ] -/ClassOrInterfaceBody - /ClassOrInterfaceBodyDeclaration - /ConstructorDeclaration[ count (.//ExplicitConstructorInvocation)=0 ] -``` - -**Example(s):** - -``` java -public class Foo extends Bar{ - public Foo() { - // call the constructor of Bar - super(); - } - public Foo(int code) { - // do something with code - this(); - // no problem with this - } -} -``` - -**Use this rule by referencing it:** -``` xml - -``` - -## DataflowAnomalyAnalysis - -**Since:** PMD 3.9 - -**Priority:** Low (5) - -The dataflow analysis tracks local definitions, undefinitions and references to variables on different paths on the data flow. -From those informations there can be found various problems. - -1. UR - Anomaly: There is a reference to a variable that was not defined before. This is a bug and leads to an error. -2. DU - Anomaly: A recently defined variable is undefined. These anomalies may appear in normal source text. -3. DD - Anomaly: A recently defined variable is redefined. This is ominous but don't have to be a bug. - -**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.controversial.DataflowAnomalyAnalysisRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/controversial/DataflowAnomalyAnalysisRule.java) - -**Example(s):** - -``` java -public void foo() { - int buz = 5; - buz = 6; // redefinition of buz -> dd-anomaly - foo(buz); - buz = 2; -} // buz is undefined when leaving scope -> du-anomaly -``` - -**This rule has the following properties:** - -|Name|Default Value|Description| -|----|-------------|-----------| -|maxViolations|100|Maximum number of anomalies per class| -|maxPaths|1000|Maximum number of checked paths per method. A lower value will increase the performance of the rule but may decrease anomalies found.| - -**Use this rule by referencing it:** -``` xml - -``` - -## DefaultPackage - -**Since:** PMD 3.4 - -**Priority:** Medium (3) - -Use explicit scoping instead of accidental usage of default package private level. -The rule allows methods and fields annotated with Guava's @VisibleForTesting. - -``` -//ClassOrInterfaceDeclaration[@Interface='false'] -/ClassOrInterfaceBody -/ClassOrInterfaceBodyDeclaration -[not(Annotation//Name[ends-with(@Image, 'VisibleForTesting')])] -[ -FieldDeclaration[@PackagePrivate='true'] -or MethodDeclaration[@PackagePrivate='true'] -] -``` - -**Use this rule by referencing it:** -``` xml - -``` - -## DoNotCallGarbageCollectionExplicitly - -**Since:** PMD 4.2 - -**Priority:** Medium High (2) - -Calls to System.gc(), Runtime.getRuntime().gc(), and System.runFinalization() are not advised. Code should have the -same behavior whether the garbage collection is disabled using the option -Xdisableexplicitgc or not. -Moreover, "modern" jvms do a very good job handling garbage collections. If memory usage issues unrelated to memory -leaks develop within an application, it should be dealt with JVM options rather than within the code itself. - -``` -//Name[ -(starts-with(@Image, 'System.') and -(starts-with(@Image, 'System.gc') or -starts-with(@Image, 'System.runFinalization'))) or -( -starts-with(@Image,'Runtime.getRuntime') and -../../PrimarySuffix[ends-with(@Image,'gc')] -) -] -``` - -**Example(s):** - -``` java -public class GCCall { - public GCCall() { - // Explicit gc call ! - System.gc(); - } - - public void doSomething() { - // Explicit gc call ! - Runtime.getRuntime().gc(); - } - - public explicitGCcall() { - // Explicit gc call ! - System.gc(); - } - - public void doSomething() { - // Explicit gc call ! - Runtime.getRuntime().gc(); - } -} -``` - -**Use this rule by referencing it:** -``` xml - -``` - -## DontImportSun - -**Since:** PMD 1.5 - -**Priority:** Medium Low (4) - -Avoid importing anything from the 'sun.*' packages. These packages are not portable and are likely to change. - -**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.controversial.DontImportSunRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/controversial/DontImportSunRule.java) - -**Example(s):** - -``` java -import sun.misc.foo; -public class Foo {} -``` - -**Use this rule by referencing it:** -``` xml - -``` - -## NullAssignment - -**Since:** PMD 1.02 - -**Priority:** Medium (3) - -Assigning a "null" to a variable (outside of its declaration) is usually bad form. Sometimes, this type -of assignment is an indication that the programmer doesn't completely understand what is going on in the code. - -NOTE: This sort of assignment may used in some cases to dereference objects and encourage garbage collection. - -**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.controversial.NullAssignmentRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/controversial/NullAssignmentRule.java) - -**Example(s):** - -``` java -public void bar() { - Object x = null; // this is OK - x = new Object(); - // big, complex piece of code here - x = null; // this is not required - // big, complex piece of code here -} -``` - -**Use this rule by referencing it:** -``` xml - -``` - -## OneDeclarationPerLine - -**Since:** PMD 5.0 - -**Priority:** Medium Low (4) - -Java allows the use of several variables declaration of the same type on one line. However, it -can lead to quite messy code. This rule looks for several declarations on the same line. - -``` -//LocalVariableDeclaration - [count(VariableDeclarator) > 1] - [$strictMode or count(distinct-values(VariableDeclarator/@BeginLine)) != count(VariableDeclarator)] -``` - -**Example(s):** - -``` java -String name; // separate declarations -String lastname; - -String name, lastname; // combined declaration, a violation - -String name, - lastname; // combined declaration on multiple lines, no violation by default. - // Set property strictMode to true to mark this as violation. -``` - -**This rule has the following properties:** - -|Name|Default Value|Description| -|----|-------------|-----------| -|strictMode|false|If true, mark combined declaration even if the declarations are on separate lines.| - -**Use this rule by referencing it:** -``` xml - -``` - -## OnlyOneReturn - -**Since:** PMD 1.0 - -**Priority:** Medium (3) - -A method should have only one exit point, and that should be the last statement in the method. - -**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.controversial.OnlyOneReturnRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/controversial/OnlyOneReturnRule.java) - -**Example(s):** - -``` java -public class OneReturnOnly1 { - public void foo(int x) { - if (x > 0) { - return "hey"; // first exit - } - return "hi"; // second exit - } -} -``` - -**Use this rule by referencing it:** -``` xml - -``` - -## SuspiciousOctalEscape - -**Since:** PMD 1.5 - -**Priority:** Medium (3) - -A suspicious octal escape sequence was found inside a String literal. -The Java language specification (section 3.10.6) says an octal -escape sequence inside a literal String shall consist of a backslash -followed by: - - OctalDigit | OctalDigit OctalDigit | ZeroToThree OctalDigit OctalDigit - -Any octal escape sequence followed by non-octal digits can be confusing, -e.g. "\038" is interpreted as the octal escape sequence "\03" followed by -the literal character "8". - -**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.controversial.SuspiciousOctalEscapeRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/controversial/SuspiciousOctalEscapeRule.java) - -**Example(s):** - -``` java -public void foo() { - // interpreted as octal 12, followed by character '8' - System.out.println("suspicious: \128"); -} -``` - -**Use this rule by referencing it:** -``` xml - -``` - -## UnnecessaryConstructor - -**Since:** PMD 1.0 - -**Priority:** Medium (3) - -This rule detects when a constructor is not necessary; i.e., when there is only one constructor, -it's public, has an empty body, and takes no arguments. - -``` -//ClassOrInterfaceBody[count(ClassOrInterfaceBodyDeclaration/ConstructorDeclaration)=1] -/ClassOrInterfaceBodyDeclaration/ConstructorDeclaration -[@Public='true'] -[not(FormalParameters/*)] -[not(BlockStatement)] -[not(NameList)] -[count(ExplicitConstructorInvocation/Arguments/ArgumentList/Expression)=0] -``` - -**Example(s):** - -``` java -public class Foo { - public Foo() {} -} -``` - -**Use this rule by referencing it:** -``` xml - -``` - -## UnnecessaryParentheses - -Deprecated - -The rule has been moved to another ruleset. Use instead: [UselessParentheses](pmd_rules_java_unnecessary.html#uselessparentheses) - -Deprecated - -**Since:** PMD 5.0 - -**Priority:** Medium Low (4) - -Useless parentheses should be removed. - -``` -//Expression[not(parent::PrimaryPrefix)]/PrimaryExpression[count(*)>1] - /PrimaryPrefix/Expression - [not(./CastExpression)] - [not(./ConditionalExpression[@Ternary='true'])] - [not(./AdditiveExpression[//Literal[@StringLiteral='true']])] -| -//Expression[not(parent::PrimaryPrefix)]/PrimaryExpression[count(*)=1] - /PrimaryPrefix/Expression -| -//Expression/ConditionalAndExpression/PrimaryExpression/PrimaryPrefix/Expression[ - count(*)=1 and - count(./CastExpression)=0 and - count(./EqualityExpression/MultiplicativeExpression)=0 and - count(./ConditionalExpression[@Ternary='true'])=0 and - count(./ConditionalOrExpression)=0] -| -//Expression/ConditionalOrExpression/PrimaryExpression/PrimaryPrefix/Expression[ - count(*)=1 and - not(./CastExpression) and - not(./ConditionalExpression[@Ternary='true']) and - not(./EqualityExpression/MultiplicativeExpression)] -| -//Expression/ConditionalExpression/PrimaryExpression/PrimaryPrefix/Expression[ - count(*)=1 and - not(./CastExpression) and - not(./EqualityExpression)] -| -//Expression/AdditiveExpression[not(./PrimaryExpression/PrimaryPrefix/Literal[@StringLiteral='true'])] - /PrimaryExpression[1]/PrimaryPrefix/Expression[ - count(*)=1 and - not(./CastExpression) and - not(./AdditiveExpression[@Image = '-']) and - not(./ShiftExpression) and - not(./RelationalExpression) and - not(./InstanceOfExpression) and - not(./EqualityExpression) and - not(./AndExpression) and - not(./ExclusiveOrExpression) and - not(./InclusiveOrExpression) and - not(./ConditionalAndExpression) and - not(./ConditionalOrExpression) and - not(./ConditionalExpression)] -| -//Expression/EqualityExpression/PrimaryExpression/PrimaryPrefix/Expression[ - count(*)=1 and - not(./CastExpression) and - not(./AndExpression) and - not(./InclusiveOrExpression) and - not(./ExclusiveOrExpression) and - not(./ConditionalExpression) and - not(./ConditionalAndExpression) and - not(./ConditionalOrExpression) and - not(./EqualityExpression)] -``` - -**Example(s):** - -``` java -public class Foo { - - private int _bar1; - private Integer _bar2; - - public void setBar(int n) { - _bar1 = Integer.valueOf((n)); // here - _bar2 = (n); // and here - } -} -``` - -**Use this rule by referencing it:** -``` xml - -``` - -## UseConcurrentHashMap - -**Since:** PMD 4.2.6 - -**Priority:** Medium (3) - -Since Java5 brought a new implementation of the Map designed for multi-threaded access, you can -perform efficient map reads without blocking other threads. - -``` -//Type[../VariableDeclarator/VariableInitializer//AllocationExpression/ClassOrInterfaceType[@Image != 'ConcurrentHashMap']] -/ReferenceType/ClassOrInterfaceType[@Image = 'Map'] -``` - -**Example(s):** - -``` java -public class ConcurrentApp { - public void getMyInstance() { - Map map1 = new HashMap(); // fine for single-threaded access - Map map2 = new ConcurrentHashMap(); // preferred for use with multiple threads - - // the following case will be ignored by this rule - Map map3 = someModule.methodThatReturnMap(); // might be OK, if the returned map is already thread-safe - } -} -``` - -**Use this rule by referencing it:** -``` xml - -``` - -## UseObjectForClearerAPI - -**Since:** PMD 4.2.6 - -**Priority:** Medium (3) - -When you write a public method, you should be thinking in terms of an API. If your method is public, it means other class -will use it, therefore, you want (or need) to offer a comprehensive and evolutive API. If you pass a lot of information -as a simple series of Strings, you may think of using an Object to represent all those information. You'll get a simpler -API (such as doWork(Workload workload), rather than a tedious series of Strings) and more importantly, if you need at some -point to pass extra data, you'll be able to do so by simply modifying or extending Workload without any modification to -your API. - -``` -//MethodDeclaration[@Public]/MethodDeclarator/FormalParameters[ - count(FormalParameter/Type/ReferenceType/ClassOrInterfaceType[@Image = 'String']) > 3 -] -``` - -**Example(s):** - -``` java -public class MyClass { - public void connect(String username, - String pssd, - String databaseName, - String databaseAdress) - // Instead of those parameters object - // would ensure a cleaner API and permit - // to add extra data transparently (no code change): - // void connect(UserData data); - { - - } -} -``` - -**Use this rule by referencing it:** -``` xml - -``` - diff --git a/docs/pages/pmd/rules/java/coupling.md b/docs/pages/pmd/rules/java/coupling.md deleted file mode 100644 index 2139ff4786a..00000000000 --- a/docs/pages/pmd/rules/java/coupling.md +++ /dev/null @@ -1,207 +0,0 @@ ---- -title: Coupling -summary: Rules which find instances of high or inappropriate coupling between objects and packages. -permalink: pmd_rules_java_coupling.html -folder: pmd/rules/java -sidebaractiveurl: /pmd_rules_java.html -editmepath: ../pmd-java/src/main/resources/rulesets/java/coupling.xml -keywords: Coupling, CouplingBetweenObjects, ExcessiveImports, LooseCoupling, LoosePackageCoupling, LawOfDemeter ---- -## CouplingBetweenObjects - -**Since:** PMD 1.04 - -**Priority:** Medium (3) - -This rule counts the number of unique attributes, local variables, and return types within an object. -A number higher than the specified threshold can indicate a high degree of coupling. - -**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.coupling.CouplingBetweenObjectsRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/coupling/CouplingBetweenObjectsRule.java) - -**Example(s):** - -``` java -import com.Blah; -import org.Bar; -import org.Bardo; - -public class Foo { - private Blah var1; - private Bar var2; - - //followed by many imports of unique objects - void ObjectC doWork() { - Bardo var55; - ObjectA var44; - ObjectZ var93; - return something; - } -} -``` - -**This rule has the following properties:** - -|Name|Default Value|Description| -|----|-------------|-----------| -|threshold|20|Unique type reporting threshold| - -**Use this rule by referencing it:** -``` xml - -``` - -## ExcessiveImports - -**Since:** PMD 1.04 - -**Priority:** Medium (3) - -A high number of imports can indicate a high degree of coupling within an object. This rule -counts the number of unique imports and reports a violation if the count is above the -user-specified threshold. - -**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.coupling.ExcessiveImportsRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/coupling/ExcessiveImportsRule.java) - -**Example(s):** - -``` java -import blah.blah.Baz; -import blah.blah.Bif; -// 18 others from the same package elided -public class Foo { - public void doWork() {} -} -``` - -**This rule has the following properties:** - -|Name|Default Value|Description| -|----|-------------|-----------| -|topscore||Top score value| -|minimum||Minimum reporting threshold| -|sigma||Sigma value| - -**Use this rule by referencing it:** -``` xml - -``` - -## LawOfDemeter - -**Since:** PMD 5.0 - -**Priority:** Medium (3) - -The Law of Demeter is a simple rule, that says "only talk to friends". It helps to reduce coupling between classes -or objects. - -See also the references: - -* Andrew Hunt, David Thomas, and Ward Cunningham. The Pragmatic Programmer. From Journeyman to Master. Addison-Wesley Longman, Amsterdam, October 1999.; -* K.J. Lieberherr and I.M. Holland. Assuring good style for object-oriented programs. Software, IEEE, 6(5):38–48, 1989.; -* -* - -**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.coupling.LawOfDemeterRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/coupling/LawOfDemeterRule.java) - -**Example(s):** - -``` java -public class Foo { - /** - * This example will result in two violations. - */ - public void example(Bar b) { - // this method call is ok, as b is a parameter of "example" - C c = b.getC(); - - // this method call is a violation, as we are using c, which we got from B. - // We should ask b directly instead, e.g. "b.doItOnC();" - c.doIt(); - - // this is also a violation, just expressed differently as a method chain without temporary variables. - b.getC().doIt(); - - // a constructor call, not a method call. - D d = new D(); - // this method call is ok, because we have create the new instance of D locally. - d.doSomethingElse(); - } -} -``` - -**Use this rule by referencing it:** -``` xml - -``` - -## LooseCoupling - -**Since:** PMD 0.7 - -**Priority:** Medium (3) - -The use of implementation types as object references limits your ability to use alternate -implementations in the future as requirements change. Whenever available, referencing objects -by their interface types provides much more flexibility. - -**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.coupling.LooseCouplingRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/coupling/LooseCouplingRule.java) - -**Example(s):** - -``` java -// sub-optimal approach -private ArrayList list = new ArrayList<>(); - -public HashSet getFoo() { - return new HashSet(); -} - -// preferred approach -private List list = new ArrayList<>(); - -public Set getFoo() { - return new HashSet(); -} -``` - -**Use this rule by referencing it:** -``` xml - -``` - -## LoosePackageCoupling - -**Since:** PMD 5.0 - -**Priority:** Medium (3) - -Avoid using classes from the configured package hierarchy outside of the package hierarchy, -except when using one of the configured allowed classes. - -**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.coupling.LoosePackageCouplingRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/coupling/LoosePackageCouplingRule.java) - -**Example(s):** - -``` java -package some.package; - -import some.other.package.subpackage.subsubpackage.DontUseThisClass; - -public class Bar { - DontUseThisClass boo = new DontUseThisClass(); -} -``` - -**This rule has the following properties:** - -|Name|Default Value|Description| -|----|-------------|-----------| -|classes|[]|Allowed classes| -|packages|[]|Restricted packages| - -**Use this rule by referencing it:** -``` xml - -``` - diff --git a/docs/pages/pmd/rules/java/design.md b/docs/pages/pmd/rules/java/design.md index a7f58258a69..f0eb4ed8431 100644 --- a/docs/pages/pmd/rules/java/design.md +++ b/docs/pages/pmd/rules/java/design.md @@ -1,47 +1,13 @@ --- title: Design -summary: The Design ruleset contains rules that flag suboptimal code implementations. Alternate approaches are suggested. +summary: Rules that help you discover design issues. permalink: pmd_rules_java_design.html folder: pmd/rules/java sidebaractiveurl: /pmd_rules_java.html -editmepath: ../pmd-java/src/main/resources/rulesets/java/design.xml -keywords: Design, UseUtilityClass, SimplifyBooleanReturns, SimplifyBooleanExpressions, SwitchStmtsShouldHaveDefault, AvoidDeeplyNestedIfStmts, AvoidReassigningParameters, SwitchDensity, ConstructorCallsOverridableMethod, AccessorClassGeneration, FinalFieldCouldBeStatic, CloseResource, NonStaticInitializer, DefaultLabelNotLastInSwitchStmt, NonCaseLabelInSwitchStatement, OptimizableToArrayCall, BadComparison, EqualsNull, ConfusingTernary, InstantiationToGetClass, IdempotentOperations, SimpleDateFormatNeedsLocale, ImmutableField, UseLocaleWithCaseConversions, AvoidProtectedFieldInFinalClass, AssignmentToNonFinalStatic, MissingStaticMethodInNonInstantiatableClass, AvoidSynchronizedAtMethodLevel, MissingBreakInSwitch, UseNotifyAllInsteadOfNotify, AvoidInstanceofChecksInCatchClause, AbstractClassWithoutAbstractMethod, SimplifyConditional, CompareObjectsWithEquals, PositionLiteralsFirstInComparisons, PositionLiteralsFirstInCaseInsensitiveComparisons, UnnecessaryLocalBeforeReturn, NonThreadSafeSingleton, SingleMethodSingleton, SingletonClassReturningNewInstance, UncommentedEmptyMethodBody, UncommentedEmptyConstructor, UnsynchronizedStaticDateFormatter, PreserveStackTrace, UseCollectionIsEmpty, ClassWithOnlyPrivateConstructorsShouldBeFinal, EmptyMethodInAbstractClassShouldBeAbstract, SingularField, ReturnEmptyArrayRatherThanNull, AbstractClassWithoutAnyMethod, TooFewBranchesForASwitchStatement, LogicInversion, UseVarargs, FieldDeclarationsShouldBeAtStartOfClass, GodClass, AvoidProtectedMethodInFinalClassNotExtending, ConstantsInInterface, AccessorMethodGeneration +editmepath: ../pmd-java/src/main/resources/category/java/design.xml +keywords: Design, AbstractClassWithoutAnyMethod, AvoidCatchingGenericException, AvoidDeeplyNestedIfStmts, AvoidRethrowingException, AvoidThrowingNewInstanceOfSameException, AvoidThrowingNullPointerException, AvoidThrowingRawExceptionTypes, ClassWithOnlyPrivateConstructorsShouldBeFinal, CollapsibleIfStatements, CouplingBetweenObjects, CyclomaticComplexity, DataClass, DoNotExtendJavaLangError, ExceptionAsFlowControl, ExcessiveClassLength, ExcessiveImports, ExcessiveMethodLength, ExcessiveParameterList, ExcessivePublicCount, FinalFieldCouldBeStatic, GodClass, ImmutableField, LawOfDemeter, LogicInversion, LoosePackageCoupling, ModifiedCyclomaticComplexity, NcssConstructorCount, NcssCount, NcssMethodCount, NcssTypeCount, NPathComplexity, SignatureDeclareThrowsException, SimplifiedTernary, SimplifyBooleanAssertion, SimplifyBooleanExpressions, SimplifyBooleanReturns, SimplifyConditional, SingularField, StdCyclomaticComplexity, SwitchDensity, TooManyFields, TooManyMethods, UselessOverridingMethod, UseObjectForClearerAPI, UseUtilityClass +language: Java --- -## AbstractClassWithoutAbstractMethod - -**Since:** PMD 3.0 - -**Priority:** Medium (3) - -The abstract class does not contain any abstract methods. An abstract class suggests -an incomplete implementation, which is to be completed by subclasses implementing the -abstract methods. If the class is intended to be used as a base class only (not to be instantiated -directly) a protected constructor can be provided prevent direct instantiation. - -``` -//ClassOrInterfaceDeclaration - [@Abstract='true' - and count( .//MethodDeclaration[@Abstract='true'] )=0 ] - [count(ImplementsList)=0] - [count(.//ExtendsList)=0] -``` - -**Example(s):** - -``` java -public abstract class Foo { - void int method1() { ... } - void int method2() { ... } - // consider using abstract methods or removing - // the abstract modifier and adding protected constructors -} -``` - -**Use this rule by referencing it:** -``` xml - -``` - ## AbstractClassWithoutAnyMethod **Since:** PMD 4.2 @@ -52,17 +18,18 @@ If an abstract class does not provides any methods, it may be acting as a simple that is not meant to be instantiated. In this case, it is probably better to use a private or protected constructor in order to prevent instantiation than make the class misleadingly abstract. -``` +**This rule is defined by the following XPath expression:** +``` xpath //ClassOrInterfaceDeclaration [@Abstract = 'true'] [count(//MethodDeclaration) + count(//ConstructorDeclaration) = 0] - [not(../Annotation/MarkerAnnotation/Name[typeof(@Image, 'com.google.auto.value.AutoValue', 'AutoValue')])] + [not(../Annotation/MarkerAnnotation/Name[pmd-java:typeIs('com.google.auto.value.AutoValue')])] ``` **Example(s):** ``` java -public class abstract Example { +public abstract class Example { String field; int otherField; } @@ -70,67 +37,41 @@ public class abstract Example { **Use this rule by referencing it:** ``` xml - + ``` -## AccessorClassGeneration +## AvoidCatchingGenericException -**Since:** PMD 1.04 +**Since:** PMD 4.2.6 **Priority:** Medium (3) -Instantiation by way of private constructors from outside of the constructor's class often causes the -generation of an accessor. A factory method, or non-privatization of the constructor can eliminate this -situation. The generated class file is actually an interface. It gives the accessing class the ability -to invoke a new hidden package scope constructor that takes the interface as a supplementary parameter. -This turns a private constructor effectively into one with package scope, and is challenging to discern. - -**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.design.AccessorClassGenerationRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/AccessorClassGenerationRule.java) - -**Example(s):** - -``` java -public class Outer { - void method(){ - Inner ic = new Inner();//Causes generation of accessor class - } - public class Inner { - private Inner(){} - } -} -``` +Avoid catching generic exceptions such as NullPointerException, RuntimeException, Exception in try-catch block -**Use this rule by referencing it:** -``` xml - +**This rule is defined by the following XPath expression:** +``` xpath +//CatchStatement/FormalParameter/Type/ReferenceType/ClassOrInterfaceType[ + @Image='NullPointerException' or + @Image='Exception' or + @Image='RuntimeException'] ``` -## AccessorMethodGeneration - -**Since:** PMD 5.5.4 - -**Priority:** Medium (3) - -When accessing a private field / method from another class, the Java compiler will generate a accessor methods -with package-private visibility. This adds overhead, and to the dex method count on Android. This situation can -be avoided by changing the visibility of the field / method from private to package-private. - -**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.design.AccessorMethodGenerationRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/AccessorMethodGenerationRule.java) - **Example(s):** ``` java -public class OuterClass { - private int counter; - /* package */ int id; +package com.igate.primitive; - public class InnerClass { - InnerClass() { - OuterClass.this.counter++; // wrong accessor method will be generated - } +public class PrimitiveType { - public int getOuterClassId() { - return OuterClass.this.id; // id is package-private, no accessor method needed + public void downCastPrimitiveType() { + try { + System.out.println(" i [" + i + "]"); + } catch(Exception e) { + e.printStackTrace(); + } catch(RuntimeException e) { + e.printStackTrace(); + } catch(NullPointerException e) { + e.printStackTrace(); } } } @@ -138,33 +79,7 @@ public class OuterClass { **Use this rule by referencing it:** ``` xml - -``` - -## AssignmentToNonFinalStatic - -**Since:** PMD 2.2 - -**Priority:** Medium (3) - -Identifies a possible unsafe usage of a static field. - -**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.design.AssignmentToNonFinalStaticRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/AssignmentToNonFinalStaticRule.java) - -**Example(s):** - -``` java -public class StaticField { - static int x; - public FinalFields(int y) { - x = y; // unsafe - } -} -``` - -**Use this rule by referencing it:** -``` xml - + ``` ## AvoidDeeplyNestedIfStmts @@ -195,208 +110,178 @@ public class Foo { **This rule has the following properties:** -|Name|Default Value|Description| -|----|-------------|-----------| -|problemDepth|3|The if statement depth reporting threshold| +|Name|Default Value|Description|Multivalued| +|----|-------------|-----------|-----------| +|problemDepth|3|The if statement depth reporting threshold|no| **Use this rule by referencing it:** ``` xml - + ``` -## AvoidInstanceofChecksInCatchClause +## AvoidRethrowingException -**Since:** PMD 3.0 +**Since:** PMD 3.8 **Priority:** Medium (3) -Each caught exception type should be handled in its own catch clause. +Catch blocks that merely rethrow a caught exception only add to code size and runtime complexity. -``` -//CatchStatement/FormalParameter - /following-sibling::Block//InstanceOfExpression/PrimaryExpression/PrimaryPrefix - /Name[ - @Image = ./ancestor::Block/preceding-sibling::FormalParameter - /VariableDeclaratorId/@Image - ] +**This rule is defined by the following XPath expression:** +``` xpath +//CatchStatement[FormalParameter + /VariableDeclaratorId/@Image = Block/BlockStatement/Statement + /ThrowStatement/Expression/PrimaryExpression[count(PrimarySuffix)=0]/PrimaryPrefix/Name/@Image + and count(Block/BlockStatement/Statement) =1] ``` **Example(s):** ``` java -try { // Avoid this - // do something -} catch (Exception ee) { - if (ee instanceof IOException) { - cleanup(); +public void bar() { + try { + // do something + } catch (SomeException se) { + throw se; } } - -try { // Prefer this: - // do something -} catch (IOException ee) { - cleanup(); -} ``` **Use this rule by referencing it:** ``` xml - + ``` -## AvoidProtectedFieldInFinalClass +## AvoidThrowingNewInstanceOfSameException -**Since:** PMD 2.1 +**Since:** PMD 4.2.5 **Priority:** Medium (3) -Do not use protected fields in final classes since they cannot be subclassed. -Clarify your intent by using private or package access modifiers instead. +Catch blocks that merely rethrow a caught exception wrapped inside a new instance of the same type only add to +code size and runtime complexity. -``` -//ClassOrInterfaceDeclaration[@Final='true'] -/ClassOrInterfaceBody/ClassOrInterfaceBodyDeclaration -/FieldDeclaration[@Protected='true'] +**This rule is defined by the following XPath expression:** +``` xpath +//CatchStatement[ + count(Block/BlockStatement/Statement) = 1 + and + FormalParameter/Type/ReferenceType/ClassOrInterfaceType/@Image = Block/BlockStatement/Statement/ThrowStatement/Expression/PrimaryExpression/PrimaryPrefix/AllocationExpression/ClassOrInterfaceType/@Image + and + count(Block/BlockStatement/Statement/ThrowStatement/Expression/PrimaryExpression/PrimaryPrefix/AllocationExpression/Arguments/ArgumentList/Expression) = 1 + and + FormalParameter/VariableDeclaratorId = Block/BlockStatement/Statement/ThrowStatement/Expression/PrimaryExpression/PrimaryPrefix/AllocationExpression/Arguments/ArgumentList/Expression/PrimaryExpression/PrimaryPrefix/Name + ] ``` **Example(s):** ``` java -public final class Bar { - private int x; - protected int y; // bar cannot be subclassed, so is y really private or package visible? - Bar() {} +public void bar() { + try { + // do something + } catch (SomeException se) { + // harmless comment + throw new SomeException(se); + } } ``` **Use this rule by referencing it:** ``` xml - + ``` -## AvoidProtectedMethodInFinalClassNotExtending +## AvoidThrowingNullPointerException -**Since:** PMD 5.1 - -**Priority:** Medium (3) +**Since:** PMD 1.8 -Do not use protected methods in most final classes since they cannot be subclassed. This should -only be allowed in final classes that extend other classes with protected methods (whose -visibility cannot be reduced). Clarify your intent by using private or package access modifiers instead. +**Priority:** High (1) -``` -//ClassOrInterfaceDeclaration[@Final='true' and not(ExtendsList)] -/ClassOrInterfaceBody/ClassOrInterfaceBodyDeclaration -/MethodDeclaration[@Protected='true'][MethodDeclarator/@Image != 'finalize'] -``` +Avoid throwing NullPointerExceptions manually. These are confusing because most people will assume that the +virtual machine threw it. To avoid a method being called with a null parameter, you may consider +using an IllegalArgumentException instead, making it clearly seen as a programmer-initiated exception. +However, there are better ways to handle this: -**Example(s):** +>*Effective Java, 3rd Edition, Item 72: Favor the use of standard exceptions* +> +>Arguably, every erroneous method invocation boils down to an illegal argument or state, +but other exceptions are standardly used for certain kinds of illegal arguments and states. +If a caller passes null in some parameter for which null values are prohibited, convention dictates that +NullPointerException be thrown rather than IllegalArgumentException. -``` java -public final class Foo { - private int bar() {} - protected int baz() {} // Foo cannot be subclassed, and doesn't extend anything, so is baz() really private or package visible? -} -``` +To implement that, you are encouraged to use `java.util.Objects.requireNonNull()` +(introduced in Java 1.7). This method is designed primarily for doing parameter +validation in methods and constructors with multiple parameters. -**Use this rule by referencing it:** -``` xml - +Your parameter validation could thus look like the following: ``` - -## AvoidReassigningParameters - -**Since:** PMD 1.0 - -**Priority:** Medium High (2) - -Reassigning values to incoming parameters is not recommended. Use temporary local variables instead. - -**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.design.AvoidReassigningParametersRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/AvoidReassigningParametersRule.java) - -**Example(s):** - -``` java public class Foo { - private void foo(String bar) { - bar = "something else"; + private String exampleValue; + + void setExampleValue(String exampleValue) { + // check, throw and assignment in a single standard call + this.exampleValue = Objects.requireNonNull(exampleValue, "exampleValue must not be null!"); + } } -} -``` - -**Use this rule by referencing it:** -``` xml - ``` -## AvoidSynchronizedAtMethodLevel - -**Since:** PMD 3.0 - -**Priority:** Medium (3) - -Method-level synchronization can cause problems when new code is added to the method. -Block-level synchronization helps to ensure that only the code that needs synchronization -gets it. - -``` -//MethodDeclaration[@Synchronized='true'] +**This rule is defined by the following XPath expression:** +``` xpath +//AllocationExpression/ClassOrInterfaceType[@Image='NullPointerException'] ``` **Example(s):** ``` java public class Foo { - // Try to avoid this: - synchronized void foo() { - } - // Prefer this: - void bar() { - synchronized(this) { - } - } - - // Try to avoid this for static methods: - static synchronized void fooStatic() { - } - - // Prefer this: - static void barStatic() { - synchronized(Foo.class) { + void bar() { + throw new NullPointerException(); } - } } ``` **Use this rule by referencing it:** ``` xml - + ``` -## BadComparison +## AvoidThrowingRawExceptionTypes **Since:** PMD 1.8 -**Priority:** Medium (3) +**Priority:** High (1) -Avoid equality comparisons with Double.NaN. Due to the implicit lack of representation -precision when comparing floating point numbers these are likely to cause logic errors. +Avoid throwing certain exception types. Rather than throw a raw RuntimeException, Throwable, +Exception, or Error, use a subclassed exception or error instead. -``` -//EqualityExpression[@Image='=='] - /PrimaryExpression/PrimaryPrefix - /Name[@Image='Double.NaN' or @Image='Float.NaN'] +**This rule is defined by the following XPath expression:** +``` xpath +//ThrowStatement//AllocationExpression + /ClassOrInterfaceType[ + pmd-java:typeIsExactly('java.lang.Throwable') +or + pmd-java:typeIsExactly('java.lang.Exception') +or + pmd-java:typeIsExactly('java.lang.Error') +or + pmd-java:typeIsExactly('java.lang.RuntimeException') +] ``` **Example(s):** ``` java -boolean x = (y == Double.NaN); +public class Foo { + public void bar() throws Exception { + throw new Exception(); + } +} ``` **Use this rule by referencing it:** ``` xml - + ``` ## ClassWithOnlyPrivateConstructorsShouldBeFinal @@ -408,7 +293,8 @@ boolean x = (y == Double.NaN); A class with only private constructors should be final, unless the private constructor is invoked by a inner class. -``` +**This rule is defined by the following XPath expression:** +``` xpath TypeDeclaration[count(../TypeDeclaration) = 1]/ClassOrInterfaceDeclaration [@Final = 'false'] [count(./ClassOrInterfaceBody/ClassOrInterfaceBodyDeclaration/ConstructorDeclaration[@Private = 'true']) >= 1 ] @@ -426,365 +312,455 @@ public class Foo { //Should be final **Use this rule by referencing it:** ``` xml - + ``` -## CloseResource +## CollapsibleIfStatements -**Since:** PMD 1.2.2 +**Since:** PMD 3.1 **Priority:** Medium (3) -Ensure that resources (like Connection, Statement, and ResultSet objects) are always closed after use. +Sometimes two consecutive 'if' statements can be consolidated by separating their conditions with a boolean short-circuit operator. -**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.design.CloseResourceRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/CloseResourceRule.java) +**This rule is defined by the following XPath expression:** +``` xpath +//IfStatement[@Else='false']/Statement + /IfStatement[@Else='false'] + | +//IfStatement[@Else='false']/Statement + /Block[count(BlockStatement)=1]/BlockStatement + /Statement/IfStatement[@Else='false'] +``` **Example(s):** ``` java -public class Bar { - public void foo() { - Connection c = pool.getConnection(); - try { - // do stuff - } catch (SQLException ex) { - // handle exception - } finally { - // oops, should close the connection using 'close'! - // c.close(); +void bar() { + if (x) { // original implementation + if (y) { + // do stuff + } } - } } -``` - -**This rule has the following properties:** -|Name|Default Value|Description| -|----|-------------|-----------| -|closeAsDefaultTarget|true|Consider 'close' as a target by default| -|types|[java.sql.Connection, java.sql.Statement, java.sql.ResultSet]|Affected types| -|closeTargets|[]|Methods which may close this resource| +void bar() { + if (x && y) { // optimized implementation + // do stuff + } +} +``` **Use this rule by referencing it:** ``` xml - + ``` -## CompareObjectsWithEquals +## CouplingBetweenObjects -**Since:** PMD 3.2 +**Since:** PMD 1.04 **Priority:** Medium (3) -Use equals() to compare object references; avoid comparing them with ==. +This rule counts the number of unique attributes, local variables, and return types within an object. +A number higher than the specified threshold can indicate a high degree of coupling. -**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.design.CompareObjectsWithEqualsRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/CompareObjectsWithEqualsRule.java) +**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.design.CouplingBetweenObjectsRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/CouplingBetweenObjectsRule.java) **Example(s):** ``` java -class Foo { - boolean bar(String a, String b) { - return a == b; - } +import com.Blah; +import org.Bar; +import org.Bardo; + +public class Foo { + private Blah var1; + private Bar var2; + + //followed by many imports of unique objects + void ObjectC doWork() { + Bardo var55; + ObjectA var44; + ObjectZ var93; + return something; + } } ``` +**This rule has the following properties:** + +|Name|Default Value|Description|Multivalued| +|----|-------------|-----------|-----------| +|threshold|20|Unique type reporting threshold|no| + **Use this rule by referencing it:** ``` xml - + ``` -## ConfusingTernary +## CyclomaticComplexity -**Since:** PMD 1.9 +**Since:** PMD 1.03 **Priority:** Medium (3) -Avoid negation within an "if" expression with an "else" clause. For example, rephrase: -`if (x != y) diff(); else same();` as: `if (x == y) same(); else diff();`. +The complexity of methods directly affects maintenance costs and readability. Concentrating too much decisional logic +in a single method makes its behaviour hard to read and change. -Most "if (x != y)" cases without an "else" are often return cases, so consistent use of this -rule makes the code easier to read. Also, this resolves trivial ordering problems, such -as "does the error case go first?" or "does the common case go first?". +Cyclomatic complexity assesses the complexity of a method by counting the number of decision points in a method, +plus one for the method entry. Decision points are places where the control flow jumps to another place in the +program. As such, they include all control flow statements, such as `if`, `while`, `for`, and `case`. For more +details on the calculation, see the documentation of the [Cyclo metric](/pmd_java_metrics_index.html#cyclomatic-complexity-cyclo). -**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.design.ConfusingTernaryRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/ConfusingTernaryRule.java) +Generally, numbers ranging from 1-4 denote low complexity, 5-7 denote moderate complexity, 8-10 denote +high complexity, and 11+ is very high complexity. By default, this rule reports methods with a complexity >= 10. +Additionnally, classes with many methods of moderate complexity get reported as well once the total of their +methods' complexities reaches 80, even if none of the methods was directly reported. + +Reported methods should be broken down into several smaller methods. Reported classes should probably be broken down +into subcomponents. + +**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.design.CyclomaticComplexityRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/CyclomaticComplexityRule.java) **Example(s):** ``` java -boolean bar(int x, int y) { - return (x != y) ? diff : same; +class Foo { + void baseCyclo() { // Cyclo = 1 + highCyclo(); + } + + void highCyclo() { // Cyclo = 10: reported! + int x = 0, y = 2; + boolean a = false, b = true; + + if (a && (y == 1 ? b : true)) { // +3 + if (y == x) { // +1 + while (true) { // +1 + if (x++ < 20) { // +1 + break; // +1 + } + } + } else if (y == t && !d) { // +2 + x = a ? y : x; // +1 + } else { + x = 2; + } + } + } } ``` **This rule has the following properties:** -|Name|Default Value|Description| -|----|-------------|-----------| -|ignoreElseIf|false|Ignore conditions with an else-if case| +|Name|Default Value|Description|Multivalued| +|----|-------------|-----------|-----------| +|cycloOptions||Choose options for the computation of Cyclo|yes. Delimiter is '\|'.| +|classReportLevel|80|Total class complexity reporting threshold|no| +|methodReportLevel|10|Cyclomatic complexity reporting threshold|no| +|reportLevel|10|Deprecated Cyclomatic Complexity reporting threshold|no| **Use this rule by referencing it:** ``` xml - + ``` -## ConstantsInInterface +## DataClass -**Since:** PMD 5.5 +**Since:** PMD 6.0.0 **Priority:** Medium (3) -Avoid constants in interfaces. Interfaces should define types, constants are implementation details -better placed in classes or enums. See Effective Java, item 19. +Data Classes are simple data holders, which reveal most of their state, and +without complex functionality. The lack of functionality may indicate that +their behaviour is defined elsewhere, which is a sign of poor data-behaviour +proximity. By directly exposing their internals, Data Classes break encapsulation, +and therefore reduce the system's maintainability and understandability. Moreover, +classes tend to strongly rely on their data representation, which makes for a brittle +design. -``` -//ClassOrInterfaceDeclaration[@Interface='true'][$ignoreIfHasMethods='false' or not(.//MethodDeclaration)]//FieldDeclaration -``` +Refactoring a Data Class should focus on restoring a good data-behaviour proximity. In +most cases, that means moving the operations defined on the data back into the class. +In some other cases it may make sense to remove entirely the class and move the data +into the former client classes. + +**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.design.DataClassRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/DataClassRule.java) **Example(s):** ``` java -public interface ConstantInterface { - public static final int CONST1 = 1; // violation, no fields allowed in interface! - static final int CONST2 = 1; // violation, no fields allowed in interface! - final int CONST3 = 1; // violation, no fields allowed in interface! - int CONST4 = 1; // violation, no fields allowed in interface! -} +public class DataClass { -// with ignoreIfHasMethods = false -public interface AnotherConstantInterface { - public static final int CONST1 = 1; // violation, no fields allowed in interface! + public int bar = 0; + public int na = 0; + private int bee = 0; - int anyMethod(); + public void setBee(int n) { + bee = n; + } } +``` -// with ignoreIfHasMethods = true -public interface YetAnotherConstantInterface { - public static final int CONST1 = 1; // no violation +**Use this rule by referencing it:** +``` xml + +``` - int anyMethod(); -} +## DoNotExtendJavaLangError + +**Since:** PMD 4.0 + +**Priority:** Medium (3) + +Errors are system exceptions. Do not extend them. + +**This rule is defined by the following XPath expression:** +``` xpath +//ClassOrInterfaceDeclaration/ExtendsList/ClassOrInterfaceType + [pmd-java:typeIs('java.lang.Error')] ``` -**This rule has the following properties:** +**Example(s):** -|Name|Default Value|Description| -|----|-------------|-----------| -|ignoreIfHasMethods|true|Whether to ignore constants in interfaces if the interface defines any methods| +``` java +public class Foo extends Error { } +``` **Use this rule by referencing it:** ``` xml - + ``` -## ConstructorCallsOverridableMethod +## ExceptionAsFlowControl -**Since:** PMD 1.04 +**Since:** PMD 1.8 -**Priority:** High (1) +**Priority:** Medium (3) -Calling overridable methods during construction poses a risk of invoking methods on an incompletely -constructed object and can be difficult to debug. -It may leave the sub-class unable to construct its superclass or forced to replicate the construction -process completely within itself, losing the ability to call super(). If the default constructor -contains a call to an overridable method, the subclass may be completely uninstantiable. Note that -this includes method calls throughout the control flow graph - i.e., if a constructor Foo() calls a -private method bar() that calls a public method buz(), this denotes a problem. +Using Exceptions as form of flow control is not recommended as they obscure true exceptions when debugging. +Either add the necessary validation or use an alternate control structure. -**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.design.ConstructorCallsOverridableMethodRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/ConstructorCallsOverridableMethodRule.java) +**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.design.ExceptionAsFlowControlRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/ExceptionAsFlowControlRule.java) **Example(s):** ``` java -public class SeniorClass { - public SeniorClass(){ - toString(); //may throw NullPointerException if overridden - } - public String toString(){ - return "IAmSeniorClass"; - } -} -public class JuniorClass extends SeniorClass { - private String name; - public JuniorClass(){ - super(); //Automatic call leads to NullPointerException - name = "JuniorClass"; - } - public String toString(){ - return name.toUpperCase(); - } +public void bar() { + try { + try { + } catch (Exception e) { + throw new WrapperException(e); + // this is essentially a GOTO to the WrapperException catch block + } + } catch (WrapperException e) { + // do some more stuff + } } ``` **Use this rule by referencing it:** ``` xml - + ``` -## DefaultLabelNotLastInSwitchStmt +## ExcessiveClassLength -**Since:** PMD 1.5 +**Since:** PMD 0.6 **Priority:** Medium (3) -By convention, the default label should be the last label in a switch statement. +Excessive class file lengths are usually indications that the class may be burdened with excessive +responsibilities that could be provided by external classes or functions. In breaking these methods +apart the code becomes more manageable and ripe for reuse. -``` -//SwitchStatement - [not(SwitchLabel[position() = last()][@Default='true'])] - [SwitchLabel[@Default='true']] -``` +**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.design.ExcessiveClassLengthRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/ExcessiveClassLengthRule.java) **Example(s):** ``` java public class Foo { - void bar(int a) { - switch (a) { - case 1: // do something - break; - default: // the default case should be last, by convention - break; - case 2: - break; - } - } + public void bar1() { + // 1000 lines of code + } + public void bar2() { + // 1000 lines of code + } + public void bar3() { + // 1000 lines of code + } + + public void barN() { + // 1000 lines of code + } } ``` +**This rule has the following properties:** + +|Name|Default Value|Description|Multivalued| +|----|-------------|-----------|-----------| +|topscore||Top score value|no| +|minimum||Minimum reporting threshold|no| +|sigma||Sigma value|no| + **Use this rule by referencing it:** ``` xml - + ``` -## EmptyMethodInAbstractClassShouldBeAbstract +## ExcessiveImports -**Since:** PMD 4.1 +**Since:** PMD 1.04 -**Priority:** High (1) +**Priority:** Medium (3) -Empty or auto-generated methods in an abstract class should be tagged as abstract. This helps to remove their inapproprate -usage by developers who should be implementing their own versions in the concrete subclasses. +A high number of imports can indicate a high degree of coupling within an object. This rule +counts the number of unique imports and reports a violation if the count is above the +user-specified threshold. -``` -//ClassOrInterfaceDeclaration[@Abstract = 'true'] - /ClassOrInterfaceBody - /ClassOrInterfaceBodyDeclaration - /MethodDeclaration[@Abstract = 'false' and @Native = 'false'] - [ - ( boolean(./Block[count(./BlockStatement) = 1]/BlockStatement/Statement/ReturnStatement/Expression/PrimaryExpression/PrimaryPrefix/Literal/NullLiteral) = 'true' ) - or - ( boolean(./Block[count(./BlockStatement) = 1]/BlockStatement/Statement/ReturnStatement/Expression/PrimaryExpression/PrimaryPrefix/Literal[@Image = '0']) = 'true' ) - or - ( boolean(./Block[count(./BlockStatement) = 1]/BlockStatement/Statement/ReturnStatement/Expression/PrimaryExpression/PrimaryPrefix/Literal[string-length(@Image) = 2]) = 'true' ) - or - (./Block[count(./BlockStatement) = 1]/BlockStatement/Statement/EmptyStatement) - or - ( count (./Block/*) = 0 ) - ] -``` +**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.design.ExcessiveImportsRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/ExcessiveImportsRule.java) **Example(s):** ``` java -public abstract class ShouldBeAbstract { - public Object couldBeAbstract() { - // Should be abstract method ? - return null; - } - - public void couldBeAbstract() { - } +import blah.blah.Baz; +import blah.blah.Bif; +// 18 others from the same package elided +public class Foo { + public void doWork() {} } ``` +**This rule has the following properties:** + +|Name|Default Value|Description|Multivalued| +|----|-------------|-----------|-----------| +|topscore||Top score value|no| +|minimum||Minimum reporting threshold|no| +|sigma||Sigma value|no| + **Use this rule by referencing it:** ``` xml - + ``` -## EqualsNull +## ExcessiveMethodLength -**Since:** PMD 1.9 +**Since:** PMD 0.6 -**Priority:** High (1) +**Priority:** Medium (3) + +When methods are excessively long this usually indicates that the method is doing more than its +name/signature might suggest. They also become challenging for others to digest since excessive +scrolling causes readers to lose focus. +Try to reduce the method length by creating helper methods and removing any copy/pasted code. + +**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.design.ExcessiveMethodLengthRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/ExcessiveMethodLengthRule.java) -Tests for null should not use the equals() method. The '==' operator should be used instead. +**Example(s):** +``` java +public void doSomething() { + System.out.println("Hello world!"); + System.out.println("Hello world!"); + // 98 copies omitted for brevity. +} ``` -//PrimaryExpression - [ - PrimaryPrefix[Name[ends-with(@Image, 'equals')]] - [following-sibling::node()/Arguments/ArgumentList[count(Expression)=1] - /Expression/PrimaryExpression/PrimaryPrefix/Literal/NullLiteral] - or +**This rule has the following properties:** - PrimarySuffix[ends-with(@Image, 'equals')] - [following-sibling::node()/Arguments/ArgumentList[count(Expression)=1] - /Expression/PrimaryExpression/PrimaryPrefix/Literal/NullLiteral] +|Name|Default Value|Description|Multivalued| +|----|-------------|-----------|-----------| +|topscore||Top score value|no| +|minimum||Minimum reporting threshold|no| +|sigma||Sigma value|no| - ] +**Use this rule by referencing it:** +``` xml + ``` +## ExcessiveParameterList + +**Since:** PMD 0.9 + +**Priority:** Medium (3) + +Methods with numerous parameters are a challenge to maintain, especially if most of them share the +same datatype. These situations usually denote the need for new objects to wrap the numerous parameters. + +**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.design.ExcessiveParameterListRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/ExcessiveParameterListRule.java) + **Example(s):** ``` java -String x = "foo"; +public void addPerson( // too many arguments liable to be mixed up + int birthYear, int birthMonth, int birthDate, int height, int weight, int ssn) { -if (x.equals(null)) { // bad form - doSomething(); + . . . . } + +public void addPerson( // preferred approach + Date birthdate, BodyMeasurements measurements, int ssn) { -if (x == null) { // preferred - doSomething(); + . . . . } ``` +**This rule has the following properties:** + +|Name|Default Value|Description|Multivalued| +|----|-------------|-----------|-----------| +|topscore||Top score value|no| +|minimum||Minimum reporting threshold|no| +|sigma||Sigma value|no| + **Use this rule by referencing it:** ``` xml - + ``` -## FieldDeclarationsShouldBeAtStartOfClass +## ExcessivePublicCount -**Since:** PMD 5.0 +**Since:** PMD 1.04 **Priority:** Medium (3) -Fields should be declared at the top of the class, before any method declarations, constructors, initializers or inner classes. +Classes with large numbers of public methods and attributes require disproportionate testing efforts +since combinational side effects grow rapidly and increase risk. Refactoring these classes into +smaller ones not only increases testability and reliability but also allows new variations to be +developed easily. -**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.design.FieldDeclarationsShouldBeAtStartOfClassRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/FieldDeclarationsShouldBeAtStartOfClassRule.java) +**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.design.ExcessivePublicCountRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/ExcessivePublicCountRule.java) **Example(s):** ``` java -public class HelloWorldBean { - - // Field declared before methods / inner classes - OK - private String _thing; - - public String getMessage() { - return "Hello World!"; - } - - // Field declared after methods / inner classes - avoid this - private String _fieldInWrongLocation; +public class Foo { + public String value; + public Bar something; + public Variable var; + // [... more more public attributes ...] + + public void doWork() {} + public void doMoreWork() {} + public void doWorkAgain() {} + // [... more more public methods ...] } ``` **This rule has the following properties:** -|Name|Default Value|Description| -|----|-------------|-----------| -|ignoreInterfaceDeclarations|false|Ignore Interface Declarations that precede fields.| -|ignoreAnonymousClassDeclarations|true|Ignore Field Declarations, that are initialized with anonymous class declarations| -|ignoreEnumDeclarations|true|Ignore Enum Declarations that precede fields.| +|Name|Default Value|Description|Multivalued| +|----|-------------|-----------|-----------| +|topscore||Top score value|no| +|minimum||Minimum reporting threshold|no| +|sigma||Sigma value|no| **Use this rule by referencing it:** ``` xml - + ``` ## FinalFieldCouldBeStatic @@ -796,7 +772,8 @@ public class HelloWorldBean { If a final field is assigned to a compile-time constant, it could be made static, thus saving overhead in each object at runtime. -``` +**This rule is defined by the following XPath expression:** +``` xpath //FieldDeclaration [@Final='true' and @Static='false'] /VariableDeclarator/VariableInitializer/Expression @@ -813,7 +790,7 @@ public class Foo { **Use this rule by referencing it:** ``` xml - + ``` ## GodClass @@ -837,45 +814,19 @@ of Object-Oriented Systems. Springer, Berlin, 1 edition, October 2006. Page 80. **Use this rule by referencing it:** ``` xml - + ``` -## IdempotentOperations +## ImmutableField **Since:** PMD 2.0 **Priority:** Medium (3) -Avoid idempotent operations - they have no effect. +Identifies private fields whose values never change once they are initialized either in the declaration +of the field or by a constructor. This helps in converting existing classes to becoming immutable ones. -**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.design.IdempotentOperationsRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/IdempotentOperationsRule.java) - -**Example(s):** - -``` java -public class Foo { - public void bar() { - int x = 2; - x = x; - } -} -``` - -**Use this rule by referencing it:** -``` xml - -``` - -## ImmutableField - -**Since:** PMD 2.0 - -**Priority:** Medium (3) - -Identifies private fields whose values never change once they are initialized either in the declaration -of the field or by a constructor. This helps in converting existing classes to becoming immutable ones. - -**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.design.ImmutableFieldRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/ImmutableFieldRule.java) +**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.design.ImmutableFieldRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/ImmutableFieldRule.java) **Example(s):** @@ -891,41 +842,64 @@ public class Foo { } ``` +**This rule has the following properties:** + +|Name|Default Value|Description|Multivalued| +|----|-------------|-----------|-----------| +|ignoredAnnotations|lombok.Setter \| lombok.Getter \| lombok.Builder \| lombok.Data \| lombok.RequiredArgsConstructor \| lombok.AllArgsConstructor \| lombok.Value \| lombok.NoArgsConstructor|Fully qualified names of the annotation types that should be ignored by this rule|yes. Delimiter is '\|'.| + **Use this rule by referencing it:** ``` xml - + ``` -## InstantiationToGetClass +## LawOfDemeter -**Since:** PMD 2.0 +**Since:** PMD 5.0 -**Priority:** Medium Low (4) +**Priority:** Medium (3) -Avoid instantiating an object just to call getClass() on it; use the .class public member instead. +The Law of Demeter is a simple rule, that says "only talk to friends". It helps to reduce coupling between classes +or objects. -``` -//PrimarySuffix - [@Image='getClass'] - [parent::PrimaryExpression - [PrimaryPrefix/AllocationExpression] - [count(PrimarySuffix) = 2] - ] -``` +See also the references: + +* Andrew Hunt, David Thomas, and Ward Cunningham. The Pragmatic Programmer. From Journeyman to Master. Addison-Wesley Longman, Amsterdam, October 1999.; +* K.J. Lieberherr and I.M. Holland. Assuring good style for object-oriented programs. Software, IEEE, 6(5):38–48, 1989.; +* +* + +**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.design.LawOfDemeterRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/LawOfDemeterRule.java) **Example(s):** ``` java -// replace this -Class c = new String().getClass(); +public class Foo { + /** + * This example will result in two violations. + */ + public void example(Bar b) { + // this method call is ok, as b is a parameter of "example" + C c = b.getC(); + + // this method call is a violation, as we are using c, which we got from B. + // We should ask b directly instead, e.g. "b.doItOnC();" + c.doIt(); -// with this: -Class c = String.class; + // this is also a violation, just expressed differently as a method chain without temporary variables. + b.getC().doIt(); + + // a constructor call, not a method call. + D d = new D(); + // this method call is ok, because we have create the new instance of D locally. + d.doSomethingElse(); + } +} ``` **Use this rule by referencing it:** ``` xml - + ``` ## LogicInversion @@ -936,7 +910,8 @@ Class c = String.class; Use opposite operator instead of negating the whole expression with a logic complement operator. -``` +**This rule is defined by the following XPath expression:** +``` xpath //UnaryExpressionNotPlusMinus[@Image='!']/PrimaryExpression/PrimaryPrefix/Expression[EqualityExpression or RelationalExpression] ``` @@ -959,450 +934,502 @@ public boolean bar(int a, int b) { **Use this rule by referencing it:** ``` xml - + ``` -## MissingBreakInSwitch +## LoosePackageCoupling -**Since:** PMD 3.0 +**Since:** PMD 5.0 **Priority:** Medium (3) -Switch statements without break or return statements for each case option -may indicate problematic behaviour. Empty cases are ignored as these indicate an intentional fall-through. +Avoid using classes from the configured package hierarchy outside of the package hierarchy, +except when using one of the configured allowed classes. -``` -//SwitchStatement -[(count(.//BreakStatement) - + count(BlockStatement//Statement/ReturnStatement) - + count(BlockStatement//Statement/ContinueStatement) - + count(BlockStatement//Statement/ThrowStatement) - + count(BlockStatement//Statement/IfStatement[@Else='true' and Statement[2][ReturnStatement|ContinueStatement|ThrowStatement]]/Statement[1][ReturnStatement|ContinueStatement|ThrowStatement]) - + count(SwitchLabel[name(following-sibling::node()) = 'SwitchLabel']) - + count(SwitchLabel[count(following-sibling::node()) = 0]) - < count (SwitchLabel))] -``` +**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.design.LoosePackageCouplingRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/LoosePackageCouplingRule.java) **Example(s):** ``` java -public void bar(int status) { - switch(status) { - case CANCELLED: - doCancelled(); - // break; hm, should this be commented out? - case NEW: - doNew(); - // is this really a fall-through? - case REMOVED: - doRemoved(); - // what happens if you add another case after this one? - case OTHER: // empty case - this is interpreted as an intentional fall-through - case ERROR: - doErrorHandling(); - break; - } +package some.package; + +import some.other.package.subpackage.subsubpackage.DontUseThisClass; + +public class Bar { + DontUseThisClass boo = new DontUseThisClass(); } ``` +**This rule has the following properties:** + +|Name|Default Value|Description|Multivalued| +|----|-------------|-----------|-----------| +|classes||Allowed classes|yes. Delimiter is ','.| +|packages||Restricted packages|yes. Delimiter is ','.| + **Use this rule by referencing it:** ``` xml - + ``` -## MissingStaticMethodInNonInstantiatableClass +## ModifiedCyclomaticComplexity -**Since:** PMD 3.0 +Deprecated + +**Since:** PMD 5.1.2 **Priority:** Medium (3) -A class that has private constructors and does not have any static methods or fields cannot be used. +Complexity directly affects maintenance costs is determined by the number of decision points in a method +plus one for the method entry. The decision points include 'if', 'while', 'for', and 'case labels' calls. +Generally, numbers ranging from 1-4 denote low complexity, 5-7 denote moderate complexity, 8-10 denote +high complexity, and 11+ is very high complexity. Modified complexity treats switch statements as a single +decision point. -``` -//ClassOrInterfaceDeclaration[@Nested='false'] -[ - ( - ./ClassOrInterfaceBody/ClassOrInterfaceBodyDeclaration/ConstructorDeclaration - and - count(./ClassOrInterfaceBody/ClassOrInterfaceBodyDeclaration/ConstructorDeclaration) = count(./ClassOrInterfaceBody/ClassOrInterfaceBodyDeclaration/ConstructorDeclaration[@Private='true']) - ) - and - not(.//MethodDeclaration[@Static='true']) - and - not(.//FieldDeclaration[@Private='false'][@Static='true']) - and - not(.//ClassOrInterfaceDeclaration[@Nested='true'] - [@Public='true'] - [@Static='true'] - [not(./ClassOrInterfaceBody/ClassOrInterfaceBodyDeclaration/ConstructorDeclaration) or ./ClassOrInterfaceBody/ClassOrInterfaceBodyDeclaration/ConstructorDeclaration[@Public='true']] - [./ClassOrInterfaceBody/ClassOrInterfaceBodyDeclaration/MethodDeclaration - [@Public='true'] - [./ResultType/Type/ReferenceType/ClassOrInterfaceType - [@Image = //ClassOrInterfaceDeclaration[@Nested='false']/@Image] - ] - ] - ) -] -``` +**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.design.ModifiedCyclomaticComplexityRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/ModifiedCyclomaticComplexityRule.java) **Example(s):** ``` java -// This class is unusable, since it cannot be -// instantiated (private constructor), -// and no static method can be called. - -public class Foo { - private Foo() {} - void foo() {} +public class Foo { // This has a Cyclomatic Complexity = 9 +1 public void example() { +2 if (a == b) { +3 if (a1 == b1) { + fiddle(); +4 } else if a2 == b2) { + fiddle(); + } else { + fiddle(); + } +5 } else if (c == d) { +6 while (c == d) { + fiddle(); + } +7 } else if (e == f) { +8 for (int n = 0; n < h; n++) { + fiddle(); + } + } else{ +9 switch (z) { + case 1: + fiddle(); + break; + case 2: + fiddle(); + break; + case 3: + fiddle(); + break; + default: + fiddle(); + break; + } + } + } } ``` +**This rule has the following properties:** + +|Name|Default Value|Description|Multivalued| +|----|-------------|-----------|-----------| +|showMethodsComplexity|true|Add method average violations to the report|no| +|showClassesComplexity|true|Add class average violations to the report|no| +|reportLevel|10|Cyclomatic Complexity reporting threshold|no| + **Use this rule by referencing it:** ``` xml - + ``` -## NonCaseLabelInSwitchStatement +## NcssConstructorCount + +Deprecated -**Since:** PMD 1.5 +**Since:** PMD 3.9 **Priority:** Medium (3) -A non-case label (e.g. a named break/continue label) was present in a switch statement. -This legal, but confusing. It is easy to mix up the case labels and the non-case labels. +This rule uses the NCSS (Non-Commenting Source Statements) algorithm to determine the number of lines +of code for a given constructor. NCSS ignores comments, and counts actual statements. Using this algorithm, +lines of code that are split are counted as one. -``` -//SwitchStatement//BlockStatement/Statement/LabeledStatement -``` +**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.design.NcssConstructorCountRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/NcssConstructorCountRule.java) **Example(s):** ``` java -public class Foo { - void bar(int a) { - switch (a) { - case 1: - // do something - break; - mylabel: // this is legal, but confusing! - break; - default: - break; +public class Foo extends Bar { + public Foo() { + super(); + + + + + + //this constructor only has 1 NCSS lines + super.foo(); } - } } ``` +**This rule has the following properties:** + +|Name|Default Value|Description|Multivalued| +|----|-------------|-----------|-----------| +|topscore||Top score value|no| +|minimum||Minimum reporting threshold|no| +|sigma||Sigma value|no| + **Use this rule by referencing it:** ``` xml - + ``` -## NonStaticInitializer +## NcssCount -**Since:** PMD 1.5 +**Since:** PMD 6.0.0 **Priority:** Medium (3) -A non-static initializer block will be called any time a constructor is invoked (just prior to -invoking the constructor). While this is a valid language construct, it is rarely used and is -confusing. +This rule uses the NCSS (Non-Commenting Source Statements) metric to determine the number of lines +of code in a class, method or constructor. NCSS ignores comments, blank lines, and only counts actual +statements. For more details on the calculation, see the documentation of +the [NCSS metric](/pmd_java_metrics_index.html#non-commenting-source-statements-ncss). -``` -//Initializer[@Static='false'] -``` +**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.design.NcssCountRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/NcssCountRule.java) **Example(s):** ``` java -public class MyClass { - // this block gets run before any call to a constructor - { - System.out.println("I am about to construct myself"); +import java.util.Collections; // +0 +import java.io.IOException; // +0 + +class Foo { // +1, total Ncss = 12 + + public void bigMethod() // +1 + throws IOException { + int x = 0, y = 2; // +1 + boolean a = false, b = true; // +1 + + if (a || b) { // +1 + try { // +1 + do { // +1 + x += 2; // +1 + } while (x < 12); + + System.exit(0); // +1 + } catch (IOException ioe) { // +1 + throw new PatheticFailException(ioe); // +1 + } + } else { + assert false; // +1 + } } } ``` +**This rule has the following properties:** + +|Name|Default Value|Description|Multivalued| +|----|-------------|-----------|-----------| +|ncssOptions||Choose options for the calculation of Ncss|yes. Delimiter is '\|'.| +|methodReportLevel|60|NCSS reporting threshold for methods|no| +|classReportLevel|1500|NCSS reporting threshold for classes|no| + **Use this rule by referencing it:** ``` xml - + ``` -## NonThreadSafeSingleton - -**Since:** PMD 3.4 +## NcssMethodCount -**Priority:** Medium (3) +Deprecated -Non-thread safe singletons can result in bad state changes. Eliminate -static singletons if possible by instantiating the object directly. Static -singletons are usually not needed as only a single instance exists anyway. -Other possible fixes are to synchronize the entire method or to use an -[initialize-on-demand holder class](https://en.wikipedia.org/wiki/Initialization-on-demand_holder_idiom). +**Since:** PMD 3.9 -Refrain from using the double-checked locking pattern. The Java Memory Model doesn't -guarantee it to work unless the variable is declared as `volatile`, adding an uneeded -performance penalty. [Reference](http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html) +**Priority:** Medium (3) -See Effective Java, item 48. +This rule uses the NCSS (Non-Commenting Source Statements) algorithm to determine the number of lines +of code for a given method. NCSS ignores comments, and counts actual statements. Using this algorithm, +lines of code that are split are counted as one. -**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.design.NonThreadSafeSingletonRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/NonThreadSafeSingletonRule.java) +**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.design.NcssMethodCountRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/NcssMethodCountRule.java) **Example(s):** ``` java -private static Foo foo = null; +public class Foo extends Bar { + public int methd() { + super.methd(); + + + -//multiple simultaneous callers may see partially initialized objects -public static Foo getFoo() { - if (foo==null) { - foo = new Foo(); + + + //this method only has 1 NCSS lines + return 1; } - return foo; } ``` **This rule has the following properties:** -|Name|Default Value|Description| -|----|-------------|-----------| -|checkNonStaticFields|false|Check for non-static fields. Do not set this to true and checkNonStaticMethods to false.| -|checkNonStaticMethods|true|Check for non-static methods. Do not set this to false and checkNonStaticFields to true.| +|Name|Default Value|Description|Multivalued| +|----|-------------|-----------|-----------| +|topscore||Top score value|no| +|minimum||Minimum reporting threshold|no| +|sigma||Sigma value|no| **Use this rule by referencing it:** ``` xml - + ``` -## OptimizableToArrayCall +## NcssTypeCount -**Since:** PMD 1.8 +Deprecated + +**Since:** PMD 3.9 **Priority:** Medium (3) -Calls to a collection's toArray() method should specify target arrays sized to match the size of the -collection. Initial arrays that are too small are discarded in favour of new ones that have to be created -that are the proper size. +This rule uses the NCSS (Non-Commenting Source Statements) algorithm to determine the number of lines +of code for a given type. NCSS ignores comments, and counts actual statements. Using this algorithm, +lines of code that are split are counted as one. -``` -//PrimaryExpression -[PrimaryPrefix/Name[ends-with(@Image, 'toArray')]] -[ -PrimarySuffix/Arguments/ArgumentList/Expression - /PrimaryExpression/PrimaryPrefix/AllocationExpression - /ArrayDimsAndInits/Expression/PrimaryExpression/PrimaryPrefix/Literal[@Image='0'] -] -``` +**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.design.NcssTypeCountRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/NcssTypeCountRule.java) **Example(s):** ``` java -List foos = getFoos(); - - // inefficient, the array will be discarded -Foo[] fooArray = foos.toArray(new Foo[0]); - - // much better; this one sizes the destination array, - // avoiding of a new one via reflection -Foo[] fooArray = foos.toArray(new Foo[foos.size()]); -``` +public class Foo extends Bar { + public Foo() { + //this class only has 6 NCSS lines + super(); -**Use this rule by referencing it:** -``` xml - -``` -## PositionLiteralsFirstInCaseInsensitiveComparisons -**Since:** PMD 5.1 -**Priority:** Medium (3) -Position literals first in comparisons, if the second argument is null then NullPointerExceptions -can be avoided, they will just return false. - -``` -//PrimaryExpression[ - PrimaryPrefix[Name - [ - (ends-with(@Image, '.equalsIgnoreCase')) - ] - ] - [ - (../PrimarySuffix/Arguments/ArgumentList/Expression/PrimaryExpression/PrimaryPrefix/Literal) - and - ( count(../PrimarySuffix/Arguments/ArgumentList/Expression) = 1 ) - ] -] -[not(ancestor::Expression/ConditionalAndExpression//EqualityExpression[@Image='!=']//NullLiteral)] -[not(ancestor::Expression/ConditionalOrExpression//EqualityExpression[@Image='==']//NullLiteral)] + super.foo(); + } +} ``` -**Example(s):** +**This rule has the following properties:** -``` java -class Foo { - boolean bar(String x) { - return x.equalsIgnoreCase("2"); // should be "2".equalsIgnoreCase(x) - } -} -``` +|Name|Default Value|Description|Multivalued| +|----|-------------|-----------|-----------| +|topscore||Top score value|no| +|minimum||Minimum reporting threshold|no| +|sigma||Sigma value|no| **Use this rule by referencing it:** ``` xml - + ``` -## PositionLiteralsFirstInComparisons +## NPathComplexity -**Since:** PMD 3.3 +**Since:** PMD 3.9 **Priority:** Medium (3) -Position literals first in comparisons, if the second argument is null then NullPointerExceptions -can be avoided, they will just return false. +The NPath complexity of a method is the number of acyclic execution paths through that method. +While cyclomatic complexity counts the number of decision points in a method, NPath counts the number of +full paths from the beginning to the end of the block of the method. That metric grows exponentially, as +it multiplies the complexity of statements in the same block. For more details on the calculation, see the +documentation of the [NPath metric](/pmd_java_metrics_index.html#npath-complexity-npath). -``` -//PrimaryExpression[ - PrimaryPrefix[Name[(ends-with(@Image, '.equals'))]] - [ - (../PrimarySuffix/Arguments/ArgumentList/Expression/PrimaryExpression/PrimaryPrefix/Literal[@StringLiteral='true']) - and - ( count(../PrimarySuffix/Arguments/ArgumentList/Expression) = 1 ) - ] -] -[not(ancestor::Expression/ConditionalAndExpression//EqualityExpression[@Image='!=']//NullLiteral)] -[not(ancestor::Expression/ConditionalOrExpression//EqualityExpression[@Image='==']//NullLiteral)] -``` +A threshold of 200 is generally considered the point where measures should be taken to reduce +complexity and increase readability. + +**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.design.NPathComplexityRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/NPathComplexityRule.java) **Example(s):** ``` java -class Foo { - boolean bar(String x) { - return x.equals("2"); // should be "2".equals(x) +public class Foo { + public static void bar() { // Ncss = 252: reported! + boolean a, b = true; + try { // 2 * 2 + 2 = 6 + if (true) { // 2 + List buz = new ArrayList(); + } + + for(int i = 0; i < 19; i++) { // * 2 + List buz = new ArrayList(); + } + } catch(Exception e) { + if (true) { // 2 + e.printStackTrace(); + } + } + + while (j++ < 20) { // * 2 + List buz = new ArrayList(); + } + + switch(j) { // * 7 + case 1: + case 2: break; + case 3: j = 5; break; + case 4: if (b && a) { bar(); } break; + default: break; + } + + do { // * 3 + List buz = new ArrayList(); + } while (a && j++ < 30); } } ``` +**This rule has the following properties:** + +|Name|Default Value|Description|Multivalued| +|----|-------------|-----------|-----------| +|minimum|200.0|Deprecated Minimum reporting threshold|no| +|reportLevel|200|N-Path Complexity reporting threshold|no| + **Use this rule by referencing it:** ``` xml - + ``` -## PreserveStackTrace +## SignatureDeclareThrowsException -**Since:** PMD 3.7 +**Since:** PMD 1.2 **Priority:** Medium (3) -Throwing a new exception from a catch block without passing the original exception into the -new exception will cause the original stack trace to be lost making it difficult to debug -effectively. +A method/constructor shouldn't explicitly throw the generic java.lang.Exception, since it +is unclear which exceptions that can be thrown from the methods. It might be +difficult to document and understand such vague interfaces. Use either a class +derived from RuntimeException or a checked exception. -**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.design.PreserveStackTraceRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/PreserveStackTraceRule.java) +**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.design.SignatureDeclareThrowsExceptionRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/SignatureDeclareThrowsExceptionRule.java) **Example(s):** ``` java -public class Foo { - void good() { - try{ - Integer.parseInt("a"); - } catch (Exception e) { - throw new Exception(e); // first possibility to create exception chain - } - try { - Integer.parseInt("a"); - } catch (Exception e) { - throw (IllegalStateException)new IllegalStateException().initCause(e); // second possibility to create exception chain. - } - } - void bad() { - try{ - Integer.parseInt("a"); - } catch (Exception e) { - throw new Exception(e.getMessage()); - } - } +public void foo() throws Exception { } ``` +**This rule has the following properties:** + +|Name|Default Value|Description|Multivalued| +|----|-------------|-----------|-----------| +|IgnoreJUnitCompletely|false|Allow all methods in a JUnit testcase to throw Exceptions|no| + **Use this rule by referencing it:** ``` xml - + ``` -## ReturnEmptyArrayRatherThanNull +## SimplifiedTernary -**Since:** PMD 4.2 +**Since:** PMD 5.4.0 -**Priority:** High (1) +**Priority:** Medium (3) -For any method that returns an array, it is a better to return an empty array rather than a -null reference. This removes the need for null checking all results and avoids inadvertent -NullPointerExceptions. +Look for ternary operators with the form `condition ? literalBoolean : foo` +or `condition ? foo : literalBoolean`. -``` -//MethodDeclaration - [ - (./ResultType/Type[@Array='true']) - and - (./Block/BlockStatement/Statement/ReturnStatement/Expression/PrimaryExpression/PrimaryPrefix/Literal/NullLiteral) - ] +These expressions can be simplified respectively to +`condition || foo` when the literalBoolean is true +`!condition && foo` when the literalBoolean is false +or +`!condition || foo` when the literalBoolean is true +`condition && foo` when the literalBoolean is false + +**This rule is defined by the following XPath expression:** +``` xpath +//ConditionalExpression[not(PrimaryExpression/*/Literal) and (Expression/PrimaryExpression/*/Literal/BooleanLiteral)] +| +//ConditionalExpression[not(Expression/PrimaryExpression/*/Literal) and (PrimaryExpression/*/Literal/BooleanLiteral)] ``` **Example(s):** ``` java -public class Example { - // Not a good idea... - public int[] badBehavior() { - // ... - return null; +public class Foo { + public boolean test() { + return condition ? true : something(); // can be as simple as return condition || something(); + } + + public void test2() { + final boolean value = condition ? false : something(); // can be as simple as value = !condition && something(); } - // Good behavior - public String[] bonnePratique() { - //... - return new String[0]; + public boolean test3() { + return condition ? something() : true; // can be as simple as return !condition || something(); + } + + public void test4() { + final boolean otherValue = condition ? something() : false; // can be as simple as condition && something(); } } ``` **Use this rule by referencing it:** ``` xml - + ``` -## SimpleDateFormatNeedsLocale +## SimplifyBooleanAssertion -**Since:** PMD 2.0 +**Since:** PMD 3.6 **Priority:** Medium (3) -Be sure to specify a Locale when creating SimpleDateFormat instances to ensure that locale-appropriate -formatting is used. +Avoid negation in an assertTrue or assertFalse test. -``` -//AllocationExpression - [ClassOrInterfaceType[@Image='SimpleDateFormat']] - [Arguments[@ArgumentCount=1]] +For example, rephrase: + + assertTrue(!expr); + +as: + + assertFalse(expr); + +**This rule is defined by the following XPath expression:** +``` xpath +//StatementExpression +[ +.//Name[@Image='assertTrue' or @Image='assertFalse'] +and +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') + 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):** ``` java -public class Foo { - // Should specify Locale.US (or whatever) - private SimpleDateFormat sdf = new SimpleDateFormat("pattern"); +public class SimpleTest extends TestCase { + public void testX() { + assertTrue("not empty", !r.isEmpty()); // replace with assertFalse("not empty", r.isEmpty()) + assertFalse(!r.isEmpty()); // replace with assertTrue(r.isEmpty()) + } } ``` **Use this rule by referencing it:** ``` xml - + ``` ## SimplifyBooleanExpressions @@ -1413,7 +1440,8 @@ public class Foo { Avoid unnecessary comparisons in boolean expressions, they serve no purpose and impacts readability. -``` +**This rule is defined by the following XPath expression:** +``` xpath //EqualityExpression/PrimaryExpression /PrimaryPrefix/Literal/BooleanLiteral ``` @@ -1432,7 +1460,7 @@ public class Bar { **Use this rule by referencing it:** ``` xml - + ``` ## SimplifyBooleanReturns @@ -1464,7 +1492,7 @@ public boolean isBarEqualTo(int x) { **Use this rule by referencing it:** ``` xml - + ``` ## SimplifyConditional @@ -1475,7 +1503,8 @@ public boolean isBarEqualTo(int x) { No need to check for null before an instanceof; the instanceof keyword returns false when given a null argument. -``` +**This rule is defined by the following XPath expression:** +``` xpath //Expression [ConditionalOrExpression [EqualityExpression[@Image='=='] @@ -1516,110 +1545,113 @@ class Foo { **Use this rule by referencing it:** ``` xml - + ``` -## SingleMethodSingleton +## SingularField -**Since:** PMD 5.4 +**Since:** PMD 3.1 -**Priority:** Medium High (2) +**Priority:** Medium (3) -Some classes contain overloaded getInstance. The problem with overloaded getInstance methods -is that the instance created using the overloaded method is not cached and so, -for each call and new objects will be created for every invocation. +Fields whose scopes are limited to just single methods do not rely on the containing +object to provide them to other methods. They may be better implemented as local variables +within those methods. -**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.design.SingleMethodSingletonRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/SingleMethodSingletonRule.java) +**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.design.SingularFieldRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/SingularFieldRule.java) **Example(s):** ``` java -public class Singleton { - - private static Singleton singleton = new Singleton( ); - - private Singleton(){ } - - public static Singleton getInstance( ) { - return singleton; - } - - public static Singleton getInstance(Object obj){ - Singleton singleton = (Singleton) obj; - return singleton; //violation +public class Foo { + private int x; // no reason to exist at the Foo instance level + public void foo(int y) { + x = y + 5; + return x; } } ``` -**Use this rule by referencing it:** -``` xml - -``` - -## SingletonClassReturningNewInstance - -**Since:** PMD 5.4 - -**Priority:** Medium High (2) - -Some classes contain overloaded getInstance. The problem with overloaded getInstance methods -is that the instance created using the overloaded method is not cached and so, -for each call and new objects will be created for every invocation. - -**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.design.SingletonClassReturningNewInstanceRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/SingletonClassReturningNewInstanceRule.java) - -**Example(s):** +**This rule has the following properties:** -``` java -class Singleton { - private static Singleton instance = null; - public static Singleton getInstance() { - synchronized(Singleton.class) { - return new Singleton(); - } - } -} -``` +|Name|Default Value|Description|Multivalued| +|----|-------------|-----------|-----------| +|disallowNotAssignment|false|Disallow violations where the first usage is not an assignment|no| +|checkInnerClasses|false|Check inner classes|no| +|ignoredAnnotations|lombok.Setter \| lombok.Getter \| lombok.Builder \| lombok.Data \| lombok.RequiredArgsConstructor \| lombok.AllArgsConstructor \| lombok.Value \| lombok.NoArgsConstructor|Fully qualified names of the annotation types that should be ignored by this rule|yes. Delimiter is '\|'.| **Use this rule by referencing it:** ``` xml - + ``` -## SingularField +## StdCyclomaticComplexity -**Since:** PMD 3.1 +Deprecated + +**Since:** PMD 5.1.2 **Priority:** Medium (3) -Fields whose scopes are limited to just single methods do not rely on the containing -object to provide them to other methods. They may be better implemented as local variables -within those methods. +Complexity directly affects maintenance costs is determined by the number of decision points in a method +plus one for the method entry. The decision points include 'if', 'while', 'for', and 'case labels' calls. +Generally, numbers ranging from 1-4 denote low complexity, 5-7 denote moderate complexity, 8-10 denote +high complexity, and 11+ is very high complexity. -**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.design.SingularFieldRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/SingularFieldRule.java) +**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.design.StdCyclomaticComplexityRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/StdCyclomaticComplexityRule.java) **Example(s):** ``` java -public class Foo { - private int x; // no reason to exist at the Foo instance level - public void foo(int y) { - x = y + 5; - return x; +public class Foo { // This has a Cyclomatic Complexity = 12 +1 public void example() { +2 if (a == b || (c == d && e == f)) { // Only one +3 if (a1 == b1) { + fiddle(); +4 } else if a2 == b2) { + fiddle(); + } else { + fiddle(); + } +5 } else if (c == d) { +6 while (c == d) { + fiddle(); + } +7 } else if (e == f) { +8 for (int n = 0; n < h; n++) { + fiddle(); + } + } else{ + switch (z) { +9 case 1: + fiddle(); + break; +10 case 2: + fiddle(); + break; +11 case 3: + fiddle(); + break; +12 default: + fiddle(); + break; + } + } } } ``` **This rule has the following properties:** -|Name|Default Value|Description| -|----|-------------|-----------| -|disallowNotAssignment|false|Disallow violations where the first usage is not an assignment| -|checkInnerClasses|false|Check inner classes| +|Name|Default Value|Description|Multivalued| +|----|-------------|-----------|-----------| +|showMethodsComplexity|true|Add method average violations to the report|no| +|showClassesComplexity|true|Add class average violations to the report|no| +|reportLevel|10|Cyclomatic Complexity reporting threshold|no| **Use this rule by referencing it:** ``` xml - + ``` ## SwitchDensity @@ -1654,337 +1686,172 @@ public class Foo { **This rule has the following properties:** -|Name|Default Value|Description| -|----|-------------|-----------| -|topscore||Top score value| -|minimum||Minimum reporting threshold| -|sigma||Sigma value| +|Name|Default Value|Description|Multivalued| +|----|-------------|-----------|-----------| +|topscore||Top score value|no| +|minimum||Minimum reporting threshold|no| +|sigma||Sigma value|no| **Use this rule by referencing it:** ``` xml - + ``` -## SwitchStmtsShouldHaveDefault +## TooManyFields -**Since:** PMD 1.0 +**Since:** PMD 3.0 **Priority:** Medium (3) -All switch statements should include a default option to catch any unspecified values. +Classes that have too many fields can become unwieldy and could be redesigned to have fewer fields, +possibly through grouping related fields in new objects. For example, a class with individual +city/state/zip fields could park them within a single Address field. -``` -//SwitchStatement[not(SwitchLabel[@Default='true'])] -``` +**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.design.TooManyFieldsRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/TooManyFieldsRule.java) **Example(s):** ``` java -public void bar() { - int x = 2; - switch (x) { - case 1: int j = 6; - case 2: int j = 8; - // missing default: here - } +public class Person { // too many separate fields + int birthYear; + int birthMonth; + int birthDate; + float height; + float weight; } -``` - -**Use this rule by referencing it:** -``` xml - -``` - -## TooFewBranchesForASwitchStatement -**Since:** PMD 4.2 - -**Priority:** Medium (3) - -Switch statements are intended to be used to support complex branching behaviour. Using a switch for only a few -cases is ill-advised, since switches are not as easy to understand as if-then statements. In these cases use the -if-then statement to increase code readability. - -``` -//SwitchStatement[ - (count(.//SwitchLabel) < $minimumNumberCaseForASwitch) -] -``` - -**Example(s):** - -``` java -// With a minimumNumberCaseForASwitch of 3 -public class Foo { - public void bar() { - switch (condition) { - case ONE: - instruction; - break; - default: - break; // not enough for a 'switch' stmt, a simple 'if' stmt would have been more appropriate - } - } +public class Person { // this is more manageable + Date birthDate; + BodyMeasurements measurements; } ``` **This rule has the following properties:** -|Name|Default Value|Description| -|----|-------------|-----------| -|minimumNumberCaseForASwitch|3|Minimum number of branches for a switch| +|Name|Default Value|Description|Multivalued| +|----|-------------|-----------|-----------| +|maxfields|15|Max allowable fields|no| **Use this rule by referencing it:** ``` xml - + ``` -## UncommentedEmptyConstructor +## TooManyMethods -**Since:** PMD 3.4 +**Since:** PMD 4.2 **Priority:** Medium (3) -Uncommented Empty Constructor finds instances where a constructor does not -contain statements, but there is no comment. By explicitly commenting empty -constructors it is easier to distinguish between intentional (commented) -and unintentional empty constructors. - -``` -//ConstructorDeclaration[@Private='false'] - [count(BlockStatement) = 0 and ($ignoreExplicitConstructorInvocation = 'true' or not(ExplicitConstructorInvocation)) and @containsComment = 'false'] - [not(../Annotation/MarkerAnnotation/Name[typeof(@Image, 'javax.inject.Inject', 'Inject')])] -``` - -**Example(s):** - -``` java -public Foo() { - // This constructor is intentionally empty. Nothing special is needed here. -} +A class with too many methods is probably a good suspect for refactoring, in order to reduce its +complexity and find a way to have more fine grained objects. + +**This rule is defined by the following XPath expression:** +``` xpath +//ClassOrInterfaceDeclaration/ClassOrInterfaceBody + [ + count(./ClassOrInterfaceBodyDeclaration/MethodDeclaration/MethodDeclarator[ + not ( + starts-with(@Image,'get') + or + starts-with(@Image,'set') + or + starts-with(@Image,'is') + ) + ]) > $maxmethods + ] ``` **This rule has the following properties:** -|Name|Default Value|Description| -|----|-------------|-----------| -|ignoreExplicitConstructorInvocation|false|Ignore explicit constructor invocation when deciding whether constructor is empty or not| +|Name|Default Value|Description|Multivalued| +|----|-------------|-----------|-----------| +|maxmethods|10|The method count reporting threshold|no| **Use this rule by referencing it:** ``` xml - + ``` -## UncommentedEmptyMethodBody +## UselessOverridingMethod -**Since:** PMD 3.4 +**Since:** PMD 3.3 **Priority:** Medium (3) -Uncommented Empty Method Body finds instances where a method body does not contain -statements, but there is no comment. By explicitly commenting empty method bodies -it is easier to distinguish between intentional (commented) and unintentional -empty methods. +The overriding method merely calls the same method defined in a superclass. -``` -//MethodDeclaration/Block[count(BlockStatement) = 0 and @containsComment = 'false'] -``` +**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.design.UselessOverridingMethodRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/UselessOverridingMethodRule.java) **Example(s):** ``` java -public void doSomething() { +public void foo(String bar) { + super.foo(bar); // why bother overriding? } -``` - -**Use this rule by referencing it:** -``` xml - -``` - -## UnnecessaryLocalBeforeReturn - -**Since:** PMD 3.3 -**Priority:** Medium (3) - -Avoid the creation of unnecessary local variables - -**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.design.UnnecessaryLocalBeforeReturnRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/UnnecessaryLocalBeforeReturnRule.java) - -**Example(s):** +public String foo() { + return super.foo(); // why bother overriding? +} -``` java -public class Foo { - public int foo() { - int x = doSomething(); - return x; // instead, just 'return doSomething();' - } +@Id +public Long getId() { + return super.getId(); // OK if 'ignoreAnnotations' is false, which is the default behavior } ``` **This rule has the following properties:** -|Name|Default Value|Description| -|----|-------------|-----------| -|statementOrderMatters|true|If set to false this rule no longer requires the variable declaration and return statement to be on consecutive lines. Any variable that is used solely in a return statement will be reported.| +|Name|Default Value|Description|Multivalued| +|----|-------------|-----------|-----------| +|ignoreAnnotations|false|Ignore annotations|no| **Use this rule by referencing it:** ``` xml - + ``` -## UnsynchronizedStaticDateFormatter +## UseObjectForClearerAPI -**Since:** PMD 3.6 +**Since:** PMD 4.2.6 **Priority:** Medium (3) -SimpleDateFormat instances are not synchronized. Sun recommends using separate format instances -for each thread. If multiple threads must access a static formatter, the formatter must be -synchronized either on method or block level. - -**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.design.UnsynchronizedStaticDateFormatterRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/UnsynchronizedStaticDateFormatterRule.java) - -**Example(s):** - -``` java -public class Foo { - private static final SimpleDateFormat sdf = new SimpleDateFormat(); - void bar() { - sdf.format(); // poor, no thread-safety - } - synchronized void foo() { - sdf.format(); // preferred - } -} -``` - -**Use this rule by referencing it:** -``` xml - +When you write a public method, you should be thinking in terms of an API. If your method is public, it means other class +will use it, therefore, you want (or need) to offer a comprehensive and evolutive API. If you pass a lot of information +as a simple series of Strings, you may think of using an Object to represent all those information. You'll get a simpler +API (such as doWork(Workload workload), rather than a tedious series of Strings) and more importantly, if you need at some +point to pass extra data, you'll be able to do so by simply modifying or extending Workload without any modification to +your API. + +**This rule is defined by the following XPath expression:** +``` xpath +//MethodDeclaration[@Public]/MethodDeclarator/FormalParameters[ + count(FormalParameter/Type/ReferenceType/ClassOrInterfaceType[@Image = 'String']) > 3 +] ``` -## UseCollectionIsEmpty - -**Since:** PMD 3.9 - -**Priority:** Medium (3) - -The isEmpty() method on java.util.Collection is provided to determine if a collection has any elements. -Comparing the value of size() to 0 does not convey intent as well as the isEmpty() method. - -**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.design.UseCollectionIsEmptyRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/UseCollectionIsEmptyRule.java) - **Example(s):** ``` java -public class Foo { - void good() { - List foo = getList(); - if (foo.isEmpty()) { - // blah - } - } +public class MyClass { + public void connect(String username, + String pssd, + String databaseName, + String databaseAdress) + // Instead of those parameters object + // would ensure a cleaner API and permit + // to add extra data transparently (no code change): + // void connect(UserData data); + { - void bad() { - List foo = getList(); - if (foo.size() == 0) { - // blah - } } } ``` **Use this rule by referencing it:** ``` xml - -``` - -## UseLocaleWithCaseConversions - -**Since:** PMD 2.0 - -**Priority:** Medium (3) - -When doing String.toLowerCase()/toUpperCase() conversions, use Locales to avoids problems with languages that -have unusual conventions, i.e. Turkish. - -``` -//PrimaryExpression -[ -PrimaryPrefix -[Name[ends-with(@Image, 'toLowerCase') or ends-with(@Image, 'toUpperCase')]] -[following-sibling::PrimarySuffix[position() = 1]/Arguments[@ArgumentCount=0]] - -or - -PrimarySuffix -[ends-with(@Image, 'toLowerCase') or ends-with(@Image, 'toUpperCase')] -[following-sibling::PrimarySuffix[position() = 1]/Arguments[@ArgumentCount=0]] -] -[not(PrimaryPrefix/Name[ends-with(@Image, 'toHexString')])] -``` - -**Example(s):** - -``` java -class Foo { - // BAD - if (x.toLowerCase().equals("list")) { } - - /* - * This will not match "LIST" when in Turkish locale - * The above could be - * if (x.toLowerCase(Locale.US).equals("list")) { } - * or simply - * if (x.equalsIgnoreCase("list")) { } - */ - // GOOD - String z = a.toLowerCase(Locale.EN); -} -``` - -**Use this rule by referencing it:** -``` xml - -``` - -## UseNotifyAllInsteadOfNotify - -**Since:** PMD 3.0 - -**Priority:** Medium (3) - -Thread.notify() awakens a thread monitoring the object. If more than one thread is monitoring, then only -one is chosen. The thread chosen is arbitrary; thus its usually safer to call notifyAll() instead. - -``` -//StatementExpression/PrimaryExpression -[PrimarySuffix/Arguments[@ArgumentCount = '0']] -[ - PrimaryPrefix[ - ./Name[@Image='notify' or ends-with(@Image,'.notify')] - or ../PrimarySuffix/@Image='notify' - or (./AllocationExpression and ../PrimarySuffix[@Image='notify']) - ] -] -``` - -**Example(s):** - -``` java -void bar() { - x.notify(); - // If many threads are monitoring x, only one (and you won't know which) will be notified. - // use instead: - x.notifyAll(); - } -``` - -**Use this rule by referencing it:** -``` xml - + ``` ## UseUtilityClass @@ -2012,56 +1879,6 @@ public class MaybeAUtility { **Use this rule by referencing it:** ``` xml - -``` - -## UseVarargs - -**Since:** PMD 5.0 - -**Priority:** Medium Low (4) - -**Minimum Language Version:** Java 1.5 - -Java 5 introduced the varargs parameter declaration for methods and constructors. This syntactic -sugar provides flexibility for users of these methods and constructors, allowing them to avoid -having to deal with the creation of an array. - -``` -//FormalParameters/FormalParameter - [position()=last()] - [@Array='true'] - [@Varargs='false'] - [not (./Type/ReferenceType[@Array='true'][PrimitiveType[@Image='byte']])] - [not (./Type/ReferenceType[ClassOrInterfaceType[@Image='Byte']])] - [not (./Type/PrimitiveType[@Image='byte'])] - [not (ancestor::MethodDeclaration/preceding-sibling::Annotation/*/Name[@Image='Override'])] - [not( - ancestor::MethodDeclaration - [@Public='true' and @Static='true'] - [child::ResultType[@Void='true']] and - ancestor::MethodDeclarator[@Image='main'] and - ..[@ParameterCount='1'] and - ./Type/ReferenceType[ClassOrInterfaceType[@Image='String']] - )] -``` - -**Example(s):** - -``` java -public class Foo { - public void foo(String s, Object[] args) { - // Do something here... - } - - public void bar(String s, Object... args) { - // Ahh, varargs tastes much better... - } -} -``` - -**Use this rule by referencing it:** -``` xml - + ``` diff --git a/docs/pages/pmd/rules/java/documentation.md b/docs/pages/pmd/rules/java/documentation.md new file mode 100644 index 00000000000..98d9988decb --- /dev/null +++ b/docs/pages/pmd/rules/java/documentation.md @@ -0,0 +1,184 @@ +--- +title: Documentation +summary: Rules that are related to code documentation. +permalink: pmd_rules_java_documentation.html +folder: pmd/rules/java +sidebaractiveurl: /pmd_rules_java.html +editmepath: ../pmd-java/src/main/resources/category/java/documentation.xml +keywords: Documentation, CommentContent, CommentRequired, CommentSize, UncommentedEmptyConstructor, UncommentedEmptyMethodBody +language: Java +--- +## CommentContent + +**Since:** PMD 5.0 + +**Priority:** Medium (3) + +A rule for the politically correct... we don't want to offend anyone. + +**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.documentation.CommentContentRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/documentation/CommentContentRule.java) + +**Example(s):** + +``` java +//OMG, this is horrible, Bob is an idiot !!! +``` + +**This rule has the following properties:** + +|Name|Default Value|Description|Multivalued| +|----|-------------|-----------|-----------| +|disallowedTerms|idiot \| jerk|Illegal terms or phrases|yes. Delimiter is '\|'.| +|caseSensitive|false|Case sensitive|no| + +**Use this rule by referencing it:** +``` xml + +``` + +## CommentRequired + +**Since:** PMD 5.1 + +**Priority:** Medium (3) + +Denotes whether comments are required (or unwanted) for specific language elements. + +**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.documentation.CommentRequiredRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/documentation/CommentRequiredRule.java) + +**Example(s):** + +``` java +/** +* +* +* @author Jon Doe +*/ +``` + +**This rule has the following properties:** + +|Name|Default Value|Description|Multivalued| +|----|-------------|-----------|-----------| +|serialVersionUIDCommentRequired|Ignored|Serial version UID comments. Possible values: [Required, Ignored, Unwanted]|no| +|enumCommentRequirement|Required|Enum comments. Possible values: [Required, Ignored, Unwanted]|no| +|protectedMethodCommentRequirement|Required|Protected method constructor comments. Possible values: [Required, Ignored, Unwanted]|no| +|publicMethodCommentRequirement|Required|Public method and constructor comments. Possible values: [Required, Ignored, Unwanted]|no| +|fieldCommentRequirement|Required|Field comments. Possible values: [Required, Ignored, Unwanted]|no| +|headerCommentRequirement|Required|Header comments. Possible values: [Required, Ignored, Unwanted]|no| +|methodWithOverrideCommentRequirement|Ignored|Comments on @Override methods. Possible values: [Required, Ignored, Unwanted]|no| +|accessorCommentRequirement|Ignored|Comments on getters and setters". Possible values: [Required, Ignored, Unwanted]|no| + +**Use this rule by referencing it:** +``` xml + +``` + +## CommentSize + +**Since:** PMD 5.0 + +**Priority:** Medium (3) + +Determines whether the dimensions of non-header comments found are within the specified limits. + +**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.documentation.CommentSizeRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/documentation/CommentSizeRule.java) + +**Example(s):** + +``` java +/** +* +* too many lines! +* +* +* +* +* +* +* +* +* +* +* +* +*/ +``` + +**This rule has the following properties:** + +|Name|Default Value|Description|Multivalued| +|----|-------------|-----------|-----------| +|maxLines|6|Maximum lines|no| +|maxLineLength|80|Maximum line length|no| + +**Use this rule by referencing it:** +``` xml + +``` + +## UncommentedEmptyConstructor + +**Since:** PMD 3.4 + +**Priority:** Medium (3) + +Uncommented Empty Constructor finds instances where a constructor does not +contain statements, but there is no comment. By explicitly commenting empty +constructors it is easier to distinguish between intentional (commented) +and unintentional empty constructors. + +**This rule is defined by the following XPath expression:** +``` xpath +//ConstructorDeclaration[@Private='false'] + [count(BlockStatement) = 0 and ($ignoreExplicitConstructorInvocation = 'true' or not(ExplicitConstructorInvocation)) and @containsComment = 'false'] + [not(../Annotation/MarkerAnnotation/Name[pmd-java:typeIs('javax.inject.Inject')])] +``` + +**Example(s):** + +``` java +public Foo() { + // This constructor is intentionally empty. Nothing special is needed here. +} +``` + +**This rule has the following properties:** + +|Name|Default Value|Description|Multivalued| +|----|-------------|-----------|-----------| +|ignoreExplicitConstructorInvocation|false|Ignore explicit constructor invocation when deciding whether constructor is empty or not|no| + +**Use this rule by referencing it:** +``` xml + +``` + +## UncommentedEmptyMethodBody + +**Since:** PMD 3.4 + +**Priority:** Medium (3) + +Uncommented Empty Method Body finds instances where a method body does not contain +statements, but there is no comment. By explicitly commenting empty method bodies +it is easier to distinguish between intentional (commented) and unintentional +empty methods. + +**This rule is defined by the following XPath expression:** +``` xpath +//MethodDeclaration/Block[count(BlockStatement) = 0 and @containsComment = 'false'] +``` + +**Example(s):** + +``` java +public void doSomething() { +} +``` + +**Use this rule by referencing it:** +``` xml + +``` + diff --git a/docs/pages/pmd/rules/java/empty.md b/docs/pages/pmd/rules/java/empty.md deleted file mode 100644 index 818df1c477a..00000000000 --- a/docs/pages/pmd/rules/java/empty.md +++ /dev/null @@ -1,357 +0,0 @@ ---- -title: Empty Code -summary: The Empty Code ruleset contains rules that find empty statements of any kind (empty method, empty block statement, empty try or catch block,...). -permalink: pmd_rules_java_empty.html -folder: pmd/rules/java -sidebaractiveurl: /pmd_rules_java.html -editmepath: ../pmd-java/src/main/resources/rulesets/java/empty.xml -keywords: Empty Code, EmptyCatchBlock, EmptyIfStmt, EmptyWhileStmt, EmptyTryBlock, EmptyFinallyBlock, EmptySwitchStatements, EmptySynchronizedBlock, EmptyStatementNotInLoop, EmptyInitializer, EmptyStatementBlock, EmptyStaticInitializer ---- -## EmptyCatchBlock - -**Since:** PMD 0.1 - -**Priority:** Medium (3) - -Empty Catch Block finds instances where an exception is caught, but nothing is done. -In most circumstances, this swallows an exception which should either be acted on -or reported. - -``` -//CatchStatement - [count(Block/BlockStatement) = 0 and ($allowCommentedBlocks != 'true' or Block/@containsComment = 'false')] - [FormalParameter/Type/ReferenceType - /ClassOrInterfaceType[@Image != 'InterruptedException' and @Image != 'CloneNotSupportedException'] - ] - [FormalParameter/VariableDeclaratorId[not(matches(@Image, $allowExceptionNameRegex))]] -``` - -**Example(s):** - -``` java -public void doSomething() { - try { - FileInputStream fis = new FileInputStream("/tmp/bugger"); - } catch (IOException ioe) { - // not good - } -} -``` - -**This rule has the following properties:** - -|Name|Default Value|Description| -|----|-------------|-----------| -|allowCommentedBlocks|false|Empty blocks containing comments will be skipped| -|allowExceptionNameRegex|^$|Empty blocks catching exceptions with names matching this regular expression will be skipped| - -**Use this rule by referencing it:** -``` xml - -``` - -## EmptyFinallyBlock - -**Since:** PMD 0.4 - -**Priority:** Medium (3) - -Empty finally blocks serve no purpose and should be removed. - -``` -//FinallyStatement[count(Block/BlockStatement) = 0] -``` - -**Example(s):** - -``` java -public class Foo { - public void bar() { - try { - int x=2; - } finally { - // empty! - } - } -} -``` - -**Use this rule by referencing it:** -``` xml - -``` - -## EmptyIfStmt - -**Since:** PMD 0.1 - -**Priority:** Medium (3) - -Empty If Statement finds instances where a condition is checked but nothing is done about it. - -``` -//IfStatement/Statement - [EmptyStatement or Block[count(*) = 0]] -``` - -**Example(s):** - -``` java -public class Foo { - void bar(int x) { - if (x == 0) { - // empty! - } - } -} -``` - -**Use this rule by referencing it:** -``` xml - -``` - -## EmptyInitializer - -**Since:** PMD 5.0 - -**Priority:** Medium (3) - -Empty initializers serve no purpose and should be removed. - -``` -//Initializer/Block[count(*)=0] -``` - -**Example(s):** - -``` java -public class Foo { - - static {} // Why ? - - {} // Again, why ? - -} -``` - -**Use this rule by referencing it:** -``` xml - -``` - -## EmptyStatementBlock - -**Since:** PMD 5.0 - -**Priority:** Medium (3) - -Empty block statements serve no purpose and should be removed. - -``` -//BlockStatement/Statement/Block[count(*) = 0] -``` - -**Example(s):** - -``` java -public class Foo { - - private int _bar; - - public void setBar(int bar) { - { _bar = bar; } // Why not? - {} // But remove this. - } - -} -``` - -**Use this rule by referencing it:** -``` xml - -``` - -## EmptyStatementNotInLoop - -**Since:** PMD 1.5 - -**Priority:** Medium (3) - -An empty statement (or a semicolon by itself) that is not used as the sole body of a 'for' -or 'while' loop is probably a bug. It could also be a double semicolon, which has no purpose -and should be removed. - -``` -//EmptyStatement - [not( - ../../../ForStatement - or ../../../WhileStatement - or ../../../BlockStatement/ClassOrInterfaceDeclaration - or ../../../../../../ForStatement/Statement[1] - /Block[1]/BlockStatement[1]/Statement/EmptyStatement - or ../../../../../../WhileStatement/Statement[1] - /Block[1]/BlockStatement[1]/Statement/EmptyStatement) - ] -``` - -**Example(s):** - -``` java -public void doit() { - // this is probably not what you meant to do - ; - // the extra semicolon here this is not necessary - System.out.println("look at the extra semicolon");; -} -``` - -**Use this rule by referencing it:** -``` xml - -``` - -## EmptyStaticInitializer - -**Since:** PMD 1.5 - -**Priority:** Medium (3) - -An empty static initializer serve no purpose and should be removed. - -``` -//Initializer[@Static='true']/Block[count(*)=0] -``` - -**Example(s):** - -``` java -public class Foo { - static { - // empty - } -} -``` - -**Use this rule by referencing it:** -``` xml - -``` - -## EmptySwitchStatements - -**Since:** PMD 1.0 - -**Priority:** Medium (3) - -Empty switch statements serve no purpose and should be removed. - -``` -//SwitchStatement[count(*) = 1] -``` - -**Example(s):** - -``` java -public void bar() { - int x = 2; - switch (x) { - // once there was code here - // but it's been commented out or something - } -} -``` - -**Use this rule by referencing it:** -``` xml - -``` - -## EmptySynchronizedBlock - -**Since:** PMD 1.3 - -**Priority:** Medium (3) - -Empty synchronized blocks serve no purpose and should be removed. - -``` -//SynchronizedStatement/Block[1][count(*) = 0] -``` - -**Example(s):** - -``` java -public class Foo { - public void bar() { - synchronized (this) { - // empty! - } - } -} -``` - -**Use this rule by referencing it:** -``` xml - -``` - -## EmptyTryBlock - -**Since:** PMD 0.4 - -**Priority:** Medium (3) - -Avoid empty try blocks - what's the point? - -``` -//TryStatement[not(ResourceSpecification)]/Block[1][count(*) = 0] -``` - -**Example(s):** - -``` java -public class Foo { - public void bar() { - try { - } catch (Exception e) { - e.printStackTrace(); - } - } -} -``` - -**Use this rule by referencing it:** -``` xml - -``` - -## EmptyWhileStmt - -**Since:** PMD 0.2 - -**Priority:** Medium (3) - -Empty While Statement finds all instances where a while statement does nothing. -If it is a timing loop, then you should use Thread.sleep() for it; if it is -a while loop that does a lot in the exit expression, rewrite it to make it clearer. - -``` -//WhileStatement/Statement[./Block[count(*) = 0] or ./EmptyStatement] -``` - -**Example(s):** - -``` java -void bar(int a, int b) { - while (a == b) { - // empty! - } -} -``` - -**Use this rule by referencing it:** -``` xml - -``` - diff --git a/docs/pages/pmd/rules/java/errorprone.md b/docs/pages/pmd/rules/java/errorprone.md new file mode 100644 index 00000000000..b81d6b9f201 --- /dev/null +++ b/docs/pages/pmd/rules/java/errorprone.md @@ -0,0 +1,3529 @@ +--- +title: Error Prone +summary: Rules to detect constructs that are either broken, extremely confusing or prone to runtime errors. +permalink: pmd_rules_java_errorprone.html +folder: pmd/rules/java +sidebaractiveurl: /pmd_rules_java.html +editmepath: ../pmd-java/src/main/resources/category/java/errorprone.xml +keywords: Error Prone, AssignmentInOperand, AssignmentToNonFinalStatic, AvoidAccessibilityAlteration, AvoidAssertAsIdentifier, AvoidBranchingStatementAsLastInLoop, AvoidCallingFinalize, AvoidCatchingNPE, AvoidCatchingThrowable, AvoidDecimalLiteralsInBigDecimalConstructor, AvoidDuplicateLiterals, AvoidEnumAsIdentifier, AvoidFieldNameMatchingMethodName, AvoidFieldNameMatchingTypeName, AvoidInstanceofChecksInCatchClause, AvoidLiteralsInIfCondition, AvoidLosingExceptionInformation, AvoidMultipleUnaryOperators, AvoidUsingOctalValues, BadComparison, BeanMembersShouldSerialize, BrokenNullCheck, CallSuperFirst, CallSuperLast, CheckSkipResult, ClassCastExceptionWithToArray, CloneMethodMustBePublic, CloneMethodMustImplementCloneable, CloneMethodReturnTypeMustMatchClassName, CloneThrowsCloneNotSupportedException, CloseResource, CompareObjectsWithEquals, ConstructorCallsOverridableMethod, DataflowAnomalyAnalysis, DoNotCallGarbageCollectionExplicitly, DoNotCallSystemExit, DoNotExtendJavaLangThrowable, DoNotHardCodeSDCard, DoNotThrowExceptionInFinally, DontImportSun, DontUseFloatTypeForLoopIndices, EmptyCatchBlock, EmptyFinalizer, EmptyFinallyBlock, EmptyIfStmt, EmptyInitializer, EmptyStatementBlock, EmptyStatementNotInLoop, EmptySwitchStatements, EmptySynchronizedBlock, EmptyTryBlock, EmptyWhileStmt, EqualsNull, FinalizeDoesNotCallSuperFinalize, FinalizeOnlyCallsSuperFinalize, FinalizeOverloaded, FinalizeShouldBeProtected, IdempotentOperations, ImportFromSamePackage, InstantiationToGetClass, InvalidSlf4jMessageFormat, JumbledIncrementer, JUnitSpelling, JUnitStaticSuite, LoggerIsNotStaticFinal, MethodWithSameNameAsEnclosingClass, MisplacedNullCheck, MissingBreakInSwitch, MissingSerialVersionUID, MissingStaticMethodInNonInstantiatableClass, MoreThanOneLogger, NonCaseLabelInSwitchStatement, NonStaticInitializer, NullAssignment, OverrideBothEqualsAndHashcode, ProperCloneImplementation, ProperLogger, ReturnEmptyArrayRatherThanNull, ReturnFromFinallyBlock, SimpleDateFormatNeedsLocale, SingleMethodSingleton, SingletonClassReturningNewInstance, StaticEJBFieldShouldBeFinal, StringBufferInstantiationWithChar, SuspiciousEqualsMethodName, SuspiciousHashcodeMethodName, SuspiciousOctalEscape, TestClassWithoutTestCases, UnconditionalIfStatement, UnnecessaryBooleanAssertion, UnnecessaryCaseChange, UnnecessaryConversionTemporary, UnusedNullCheckInEquals, UseCorrectExceptionLogging, UseEqualsToCompareStrings, UselessOperationOnImmutable, UseLocaleWithCaseConversions, UseProperClassLoader +language: Java +--- +## AssignmentInOperand + +**Since:** PMD 1.03 + +**Priority:** Medium (3) + +Avoid assignments in operands; this can make code more complicated and harder to read. + +**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.errorprone.AssignmentInOperandRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/AssignmentInOperandRule.java) + +**Example(s):** + +``` java +public void bar() { + int x = 2; + if ((x = getX()) == 3) { + System.out.println("3!"); + } +} +``` + +**This rule has the following properties:** + +|Name|Default Value|Description|Multivalued| +|----|-------------|-----------|-----------| +|allowIncrementDecrement|false|Allow increment or decrement operators within the conditional expression of an if, for, or while statement|no| +|allowWhile|false|Allow assignment within the conditional expression of a while statement|no| +|allowFor|false|Allow assignment within the conditional expression of a for statement|no| +|allowIf|false|Allow assignment within the conditional expression of an if statement|no| + +**Use this rule by referencing it:** +``` xml + +``` + +## AssignmentToNonFinalStatic + +**Since:** PMD 2.2 + +**Priority:** Medium (3) + +Identifies a possible unsafe usage of a static field. + +**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.errorprone.AssignmentToNonFinalStaticRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/AssignmentToNonFinalStaticRule.java) + +**Example(s):** + +``` java +public class StaticField { + static int x; + public FinalFields(int y) { + x = y; // unsafe + } +} +``` + +**Use this rule by referencing it:** +``` xml + +``` + +## AvoidAccessibilityAlteration + +**Since:** PMD 4.1 + +**Priority:** Medium (3) + +Methods such as getDeclaredConstructors(), getDeclaredConstructor(Class[]) and setAccessible(), +as the interface PrivilegedAction, allow for the runtime alteration of variable, class, or +method visibility, even if they are private. This violates the principle of encapsulation. + +**This rule is defined by the following XPath expression:** +``` xpath +//PrimaryExpression[ +( +(PrimarySuffix[ + ends-with(@Image,'getDeclaredConstructors') + or + ends-with(@Image,'getDeclaredConstructor') + or + ends-with(@Image,'setAccessible') + ]) +or +(PrimaryPrefix/Name[ + ends-with(@Image,'getDeclaredConstructor') + or + ends-with(@Image,'getDeclaredConstructors') + or + starts-with(@Image,'AccessibleObject.setAccessible') + ]) +) +and +(//ImportDeclaration/Name[ + contains(@Image,'java.security.PrivilegedAction')]) +] +``` + +**Example(s):** + +``` java +import java.lang.reflect.AccessibleObject; +import java.lang.reflect.Method; +import java.security.PrivilegedAction; + +public class Violation { + public void invalidCallsInMethod() throws SecurityException, NoSuchMethodException { + // Possible call to forbidden getDeclaredConstructors + Class[] arrayOfClass = new Class[1]; + this.getClass().getDeclaredConstructors(); + this.getClass().getDeclaredConstructor(arrayOfClass); + Class clazz = this.getClass(); + clazz.getDeclaredConstructor(arrayOfClass); + clazz.getDeclaredConstructors(); + // Possible call to forbidden setAccessible + clazz.getMethod("", arrayOfClass).setAccessible(false); + AccessibleObject.setAccessible(null, false); + Method.setAccessible(null, false); + Method[] methodsArray = clazz.getMethods(); + int nbMethod; + for ( nbMethod = 0; nbMethod < methodsArray.length; nbMethod++ ) { + methodsArray[nbMethod].setAccessible(false); + } + + // Possible call to forbidden PrivilegedAction + PrivilegedAction priv = (PrivilegedAction) new Object(); priv.run(); + } +} +``` + +**Use this rule by referencing it:** +``` xml + +``` + +## AvoidAssertAsIdentifier + +**Since:** PMD 3.4 + +**Priority:** Medium High (2) + +Use of the term 'assert' will conflict with newer versions of Java since it is a reserved word. + +**This rule is defined by the following XPath expression:** +``` xpath +//VariableDeclaratorId[@Image='assert'] +``` + +**Example(s):** + +``` java +public class A { + public class Foo { + String assert = "foo"; + } +} +``` + +**Use this rule by referencing it:** +``` xml + +``` + +## AvoidBranchingStatementAsLastInLoop + +**Since:** PMD 5.0 + +**Priority:** Medium High (2) + +Using a branching statement as the last part of a loop may be a bug, and/or is confusing. +Ensure that the usage is not a bug, or consider using another approach. + +**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.errorprone.AvoidBranchingStatementAsLastInLoopRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/AvoidBranchingStatementAsLastInLoopRule.java) + +**Example(s):** + +``` java +// unusual use of branching statement in a loop +for (int i = 0; i < 10; i++) { + if (i*i <= 25) { + continue; + } + break; +} + +// this makes more sense... +for (int i = 0; i < 10; i++) { + if (i*i > 25) { + break; + } +} +``` + +**This rule has the following properties:** + +|Name|Default Value|Description|Multivalued| +|----|-------------|-----------|-----------| +|checkReturnLoopTypes|for \| do \| while|Check for return statements in loop types|yes. Delimiter is '\|'.| +|checkContinueLoopTypes|for \| do \| while|Check for continue statements in loop types|yes. Delimiter is '\|'.| +|checkBreakLoopTypes|for \| do \| while|Check for break statements in loop types|yes. Delimiter is '\|'.| + +**Use this rule by referencing it:** +``` xml + +``` + +## AvoidCallingFinalize + +**Since:** PMD 3.0 + +**Priority:** Medium (3) + +The method Object.finalize() is called by the garbage collector on an object when garbage collection determines +that there are no more references to the object. It should not be invoked by application logic. + +Note that Oracle has declared Object.finalize() as deprecated since JDK 9. + +**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.errorprone.AvoidCallingFinalizeRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/AvoidCallingFinalizeRule.java) + +**Example(s):** + +``` java +void foo() { + Bar b = new Bar(); + b.finalize(); +} +``` + +**Use this rule by referencing it:** +``` xml + +``` + +## AvoidCatchingNPE + +**Since:** PMD 1.8 + +**Priority:** Medium (3) + +Code should never throw NullPointerExceptions under normal circumstances. A catch block may hide the +original error, causing other, more subtle problems later on. + +**This rule is defined by the following XPath expression:** +``` xpath +//CatchStatement/FormalParameter/Type + /ReferenceType/ClassOrInterfaceType[@Image='NullPointerException'] +``` + +**Example(s):** + +``` java +public class Foo { + void bar() { + try { + // do something + } catch (NullPointerException npe) { + } + } +} +``` + +**Use this rule by referencing it:** +``` xml + +``` + +## AvoidCatchingThrowable + +**Since:** PMD 1.2 + +**Priority:** Medium (3) + +Catching Throwable errors is not recommended since its scope is very broad. It includes runtime issues such as +OutOfMemoryError that should be exposed and managed separately. + +**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.errorprone.AvoidCatchingThrowableRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/AvoidCatchingThrowableRule.java) + +**Example(s):** + +``` java +public void bar() { + try { + // do something + } catch (Throwable th) { // should not catch Throwable + th.printStackTrace(); + } +} +``` + +**Use this rule by referencing it:** +``` xml + +``` + +## AvoidDecimalLiteralsInBigDecimalConstructor + +**Since:** PMD 3.4 + +**Priority:** Medium (3) + +One might assume that the result of "new BigDecimal(0.1)" is exactly equal to 0.1, but it is actually +equal to .1000000000000000055511151231257827021181583404541015625. +This is because 0.1 cannot be represented exactly as a double (or as a binary fraction of any finite +length). Thus, the long value that is being passed in to the constructor is not exactly equal to 0.1, +appearances notwithstanding. + +The (String) constructor, on the other hand, is perfectly predictable: 'new BigDecimal("0.1")' is +exactly equal to 0.1, as one would expect. Therefore, it is generally recommended that the +(String) constructor be used in preference to this one. + +**This rule is defined by the following XPath expression:** +``` xpath +//AllocationExpression +[ClassOrInterfaceType[@Image="BigDecimal"]] +[Arguments/ArgumentList/Expression/PrimaryExpression/PrimaryPrefix + [ + Literal[(not(ends-with(@Image,'"'))) and contains(@Image,".")] + or + Name[ancestor::Block/BlockStatement/LocalVariableDeclaration + [Type[PrimitiveType[@Image='double' or @Image='float'] + or ReferenceType/ClassOrInterfaceType[@Image='Double' or @Image='Float']]] + /VariableDeclarator/VariableDeclaratorId/@Image = @Image + ] + or + Name[ancestor::MethodDeclaration/MethodDeclarator/FormalParameters/FormalParameter + [Type[PrimitiveType[@Image='double' or @Image='float'] + or ReferenceType/ClassOrInterfaceType[@Image='Double' or @Image='Float']]] + /VariableDeclaratorId/@Image = @Image + ] + ] +] +``` + +**Example(s):** + +``` java +BigDecimal bd = new BigDecimal(1.123); // loss of precision, this would trigger the rule + +BigDecimal bd = new BigDecimal("1.123"); // preferred approach + +BigDecimal bd = new BigDecimal(12); // preferred approach, ok for integer values +``` + +**Use this rule by referencing it:** +``` xml + +``` + +## AvoidDuplicateLiterals + +**Since:** PMD 1.0 + +**Priority:** Medium (3) + +Code containing duplicate String literals can usually be improved by declaring the String as a constant field. + +**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.errorprone.AvoidDuplicateLiteralsRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/AvoidDuplicateLiteralsRule.java) + +**Example(s):** + +``` java +private void bar() { + buz("Howdy"); + buz("Howdy"); + buz("Howdy"); + buz("Howdy"); +} +private void buz(String x) {} +``` + +**This rule has the following properties:** + +|Name|Default Value|Description|Multivalued| +|----|-------------|-----------|-----------| +|exceptionfile||File containing strings to skip (one string per line), only used if ignore list is not set|no| +|separator|,|Ignore list separator|no| +|exceptionList||Strings to ignore|no| +|maxDuplicateLiterals|4|Max duplicate literals|no| +|minimumLength|3|Minimum string length to check|no| +|skipAnnotations|false|Skip literals within annotations|no| + +**Use this rule by referencing it:** +``` xml + +``` + +## AvoidEnumAsIdentifier + +**Since:** PMD 3.4 + +**Priority:** Medium High (2) + +Use of the term 'enum' will conflict with newer versions of Java since it is a reserved word. + +**This rule is defined by the following XPath expression:** +``` xpath +//VariableDeclaratorId[@Image='enum'] +``` + +**Example(s):** + +``` java +public class A { + public class Foo { + String enum = "foo"; + } +} +``` + +**Use this rule by referencing it:** +``` xml + +``` + +## AvoidFieldNameMatchingMethodName + +**Since:** PMD 3.0 + +**Priority:** Medium (3) + +It can be confusing to have a field name with the same name as a method. While this is permitted, +having information (field) and actions (method) is not clear naming. Developers versed in +Smalltalk often prefer this approach as the methods denote accessor methods. + +**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.errorprone.AvoidFieldNameMatchingMethodNameRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/AvoidFieldNameMatchingMethodNameRule.java) + +**Example(s):** + +``` java +public class Foo { + Object bar; + // bar is data or an action or both? + void bar() { + } +} +``` + +**Use this rule by referencing it:** +``` xml + +``` + +## AvoidFieldNameMatchingTypeName + +**Since:** PMD 3.0 + +**Priority:** Medium (3) + +It is somewhat confusing to have a field name matching the declaring class name. +This probably means that type and/or field names should be chosen more carefully. + +**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.errorprone.AvoidFieldNameMatchingTypeNameRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/AvoidFieldNameMatchingTypeNameRule.java) + +**Example(s):** + +``` java +public class Foo extends Bar { + int foo; // There is probably a better name that can be used +} +``` + +**Use this rule by referencing it:** +``` xml + +``` + +## AvoidInstanceofChecksInCatchClause + +**Since:** PMD 3.0 + +**Priority:** Medium (3) + +Each caught exception type should be handled in its own catch clause. + +**This rule is defined by the following XPath expression:** +``` xpath +//CatchStatement/FormalParameter + /following-sibling::Block//InstanceOfExpression/PrimaryExpression/PrimaryPrefix + /Name[ + @Image = ./ancestor::Block/preceding-sibling::FormalParameter + /VariableDeclaratorId/@Image + ] +``` + +**Example(s):** + +``` java +try { // Avoid this + // do something +} catch (Exception ee) { + if (ee instanceof IOException) { + cleanup(); + } +} + +try { // Prefer this: + // do something +} catch (IOException ee) { + cleanup(); +} +``` + +**Use this rule by referencing it:** +``` xml + +``` + +## AvoidLiteralsInIfCondition + +**Since:** PMD 4.2.6 + +**Priority:** Medium (3) + +Avoid using hard-coded literals in conditional statements. By declaring them as static variables +or private members with descriptive names maintainability is enhanced. By default, the literals "-1" and "0" are ignored. +More exceptions can be defined with the property "ignoreMagicNumbers". + +**This rule is defined by the following XPath expression:** +``` xpath +//IfStatement/Expression/*/PrimaryExpression/PrimaryPrefix/Literal +[not(NullLiteral)] +[not(BooleanLiteral)] +[empty(index-of(tokenize($ignoreMagicNumbers, '\s*,\s*'), @Image))] +``` + +**Example(s):** + +``` java +private static final int MAX_NUMBER_OF_REQUESTS = 10; + +public void checkRequests() { + + if (i == 10) { // magic number, buried in a method + doSomething(); + } + + if (i == MAX_NUMBER_OF_REQUESTS) { // preferred approach + doSomething(); + } + + if (aString.indexOf('.') != -1) {} // magic number -1, by default ignored + if (aString.indexOf('.') >= 0) { } // alternative approach + + if (aDouble > 0.0) {} // magic number 0.0 + if (aDouble >= Double.MIN_VALUE) {} // preferred approach +} +``` + +**This rule has the following properties:** + +|Name|Default Value|Description|Multivalued| +|----|-------------|-----------|-----------| +|ignoreMagicNumbers|-1,0|Comma-separated list of magic numbers, that should be ignored|no| + +**Use this rule by referencing it:** +``` xml + +``` + +## AvoidLosingExceptionInformation + +**Since:** PMD 4.2.6 + +**Priority:** Medium High (2) + +Statements in a catch block that invoke accessors on the exception without using the information +only add to code size. Either remove the invocation, or use the return result. + +**This rule is defined by the following XPath expression:** +``` xpath +//CatchStatement/Block/BlockStatement/Statement/StatementExpression/PrimaryExpression/PrimaryPrefix/Name +[ + @Image = concat(../../../../../../../FormalParameter/VariableDeclaratorId/@Image, '.getMessage') + or + @Image = concat(../../../../../../../FormalParameter/VariableDeclaratorId/@Image, '.getLocalizedMessage') + or + @Image = concat(../../../../../../../FormalParameter/VariableDeclaratorId/@Image, '.getCause') + or + @Image = concat(../../../../../../../FormalParameter/VariableDeclaratorId/@Image, '.getStackTrace') + or + @Image = concat(../../../../../../../FormalParameter/VariableDeclaratorId/@Image, '.toString') +] +``` + +**Example(s):** + +``` java +public void bar() { + try { + // do something + } catch (SomeException se) { + se.getMessage(); + } +} +``` + +**Use this rule by referencing it:** +``` xml + +``` + +## AvoidMultipleUnaryOperators + +**Since:** PMD 4.2 + +**Priority:** Medium High (2) + +The use of multiple unary operators may be problematic, and/or confusing. +Ensure that the intended usage is not a bug, or consider simplifying the expression. + +**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.errorprone.AvoidMultipleUnaryOperatorsRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/AvoidMultipleUnaryOperatorsRule.java) + +**Example(s):** + +``` java +// These are typo bugs, or at best needlessly complex and confusing: +int i = - -1; +int j = + - +1; +int z = ~~2; +boolean b = !!true; +boolean c = !!!true; + +// These are better: +int i = 1; +int j = -1; +int z = 2; +boolean b = true; +boolean c = false; + +// And these just make your brain hurt: +int i = ~-2; +int j = -~7; +``` + +**Use this rule by referencing it:** +``` xml + +``` + +## AvoidUsingOctalValues + +**Since:** PMD 3.9 + +**Priority:** Medium (3) + +Integer literals should not start with zero since this denotes that the rest of literal will be +interpreted as an octal value. + +**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.errorprone.AvoidUsingOctalValuesRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/AvoidUsingOctalValuesRule.java) + +**Example(s):** + +``` java +int i = 012; // set i with 10 not 12 +int j = 010; // set j with 8 not 10 +k = i * j; // set k with 80 not 120 +``` + +**This rule has the following properties:** + +|Name|Default Value|Description|Multivalued| +|----|-------------|-----------|-----------| +|strict|false|Detect violations between 00 and 07|no| + +**Use this rule by referencing it:** +``` xml + +``` + +## BadComparison + +**Since:** PMD 1.8 + +**Priority:** Medium (3) + +Avoid equality comparisons with Double.NaN. Due to the implicit lack of representation +precision when comparing floating point numbers these are likely to cause logic errors. + +**This rule is defined by the following XPath expression:** +``` xpath +//EqualityExpression[@Image='=='] + /PrimaryExpression/PrimaryPrefix + /Name[@Image='Double.NaN' or @Image='Float.NaN'] +``` + +**Example(s):** + +``` java +boolean x = (y == Double.NaN); +``` + +**Use this rule by referencing it:** +``` xml + +``` + +## BeanMembersShouldSerialize + +**Since:** PMD 1.1 + +**Priority:** Medium (3) + +If a class is a bean, or is referenced by a bean directly or indirectly it needs to be serializable. +Member variables need to be marked as transient, static, or have accessor methods in the class. Marking +variables as transient is the safest and easiest modification. Accessor methods should follow the Java +naming conventions, i.e. for a variable named foo, getFoo() and setFoo() accessor methods should be provided. + +**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.errorprone.BeanMembersShouldSerializeRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/BeanMembersShouldSerializeRule.java) + +**Example(s):** + +``` java +private transient int someFoo; // good, it's transient +private static int otherFoo; // also OK +private int moreFoo; // OK, has proper accessors, see below +private int badFoo; // bad, should be marked transient + +private void setMoreFoo(int moreFoo){ + this.moreFoo = moreFoo; +} + +private int getMoreFoo(){ + return this.moreFoo; +} +``` + +**This rule has the following properties:** + +|Name|Default Value|Description|Multivalued| +|----|-------------|-----------|-----------| +|prefix||A variable prefix to skip, i.e., m_|no| + +**Use this rule by referencing it:** +``` xml + +``` + +## BrokenNullCheck + +**Since:** PMD 3.8 + +**Priority:** Medium High (2) + +The null check is broken since it will throw a NullPointerException itself. +It is likely that you used || instead of && or vice versa. + +**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.errorprone.BrokenNullCheckRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/BrokenNullCheckRule.java) + +**Example(s):** + +``` java +public String bar(String string) { + // should be && + if (string!=null || !string.equals("")) + return string; + // should be || + if (string==null && string.equals("")) + return string; +} +``` + +**Use this rule by referencing it:** +``` xml + +``` + +## CallSuperFirst + +**Since:** PMD 4.2.5 + +**Priority:** Medium (3) + +Super should be called at the start of the method + +**This rule is defined by the following XPath expression:** +``` xpath +//MethodDeclaration[MethodDeclarator[ + @Image='onCreate' or + @Image='onConfigurationChanged' or + @Image='onPostCreate' or + @Image='onPostResume' or + @Image='onRestart' or + @Image='onRestoreInstanceState' or + @Image='onResume' or + @Image='onStart' + ]] + /Block[not( + (BlockStatement[1]/Statement/StatementExpression/PrimaryExpression[./PrimaryPrefix[@SuperModifier='true']]/PrimarySuffix[@Image= ancestor::MethodDeclaration/MethodDeclarator/@Image]))] +[ancestor::ClassOrInterfaceDeclaration[ExtendsList/ClassOrInterfaceType[ + pmd-java:typeIs('android.app.Activity') or + pmd-java:typeIs('android.app.Application') or + pmd-java:typeIs('android.app.Service') +]]] +``` + +**Example(s):** + +``` java +public class DummyActivity extends Activity { + public void onCreate(Bundle bundle) { + // missing call to super.onCreate(bundle) + foo(); + } +} +``` + +**Use this rule by referencing it:** +``` xml + +``` + +## CallSuperLast + +**Since:** PMD 4.2.5 + +**Priority:** Medium (3) + +Super should be called at the end of the method + +**This rule is defined by the following XPath expression:** +``` xpath +//MethodDeclaration[MethodDeclarator[ + @Image='finish' or + @Image='onDestroy' or + @Image='onPause' or + @Image='onSaveInstanceState' or + @Image='onStop' or + @Image='onTerminate' + ]] + /Block/BlockStatement[last()] + [not(Statement/StatementExpression/PrimaryExpression[./PrimaryPrefix[@SuperModifier='true']]/PrimarySuffix[@Image= ancestor::MethodDeclaration/MethodDeclarator/@Image])] +[ancestor::ClassOrInterfaceDeclaration[ExtendsList/ClassOrInterfaceType[ + pmd-java:typeIs('android.app.Activity') or + pmd-java:typeIs('android.app.Application') or + pmd-java:typeIs('android.app.Service') +]]] +``` + +**Example(s):** + +``` java +public class DummyActivity extends Activity { + public void onPause() { + foo(); + // missing call to super.onPause() + } +} +``` + +**Use this rule by referencing it:** +``` xml + +``` + +## CheckSkipResult + +**Since:** PMD 5.0 + +**Priority:** Medium (3) + +The skip() method may skip a smaller number of bytes than requested. Check the returned value to find out if it was the case or not. + +**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.errorprone.CheckSkipResultRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/CheckSkipResultRule.java) + +**Example(s):** + +``` java +public class Foo { + + private FileInputStream _s = new FileInputStream("file"); + + public void skip(int n) throws IOException { + _s.skip(n); // You are not sure that exactly n bytes are skipped + } + + public void skipExactly(int n) throws IOException { + while (n != 0) { + long skipped = _s.skip(n); + if (skipped == 0) + throw new EOFException(); + n -= skipped; + } + } +``` + +**Use this rule by referencing it:** +``` xml + +``` + +## ClassCastExceptionWithToArray + +**Since:** PMD 3.4 + +**Priority:** Medium (3) + +When deriving an array of a specific class from your Collection, one should provide an array of +the same class as the parameter of the toArray() method. Doing otherwise you will will result +in a ClassCastException. + +**This rule is defined by the following XPath expression:** +``` xpath +//CastExpression[Type/ReferenceType/ClassOrInterfaceType[@Image != +"Object"]]/PrimaryExpression +[ + PrimaryPrefix/Name[ends-with(@Image, '.toArray')] + and + PrimarySuffix/Arguments[count(*) = 0] +and +count(PrimarySuffix) = 1 +] +``` + +**Example(s):** + +``` java +Collection c = new ArrayList(); +Integer obj = new Integer(1); +c.add(obj); + + // this would trigger the rule (and throw a ClassCastException if executed) +Integer[] a = (Integer [])c.toArray(); + + // this is fine and will not trigger the rule +Integer[] b = (Integer [])c.toArray(new Integer[c.size()]); +``` + +**Use this rule by referencing it:** +``` xml + +``` + +## CloneMethodMustBePublic + +**Since:** PMD 5.4.0 + +**Priority:** Medium (3) + +The java Manual says "By convention, classes that implement this interface should override +Object.clone (which is protected) with a public method." + +**This rule is defined by the following XPath expression:** +``` xpath +//MethodDeclaration[@Public='false'] + [MethodDeclarator/@Image = 'clone'] + [MethodDeclarator/FormalParameters/@ParameterCount = 0] +``` + +**Example(s):** + +``` java +public class Foo implements Cloneable { + @Override + protected Object clone() throws CloneNotSupportedException { // Violation, must be public + } +} + +public class Foo implements Cloneable { + @Override + protected Foo clone() { // Violation, must be public + } +} + +public class Foo implements Cloneable { + @Override + public Object clone() // Ok +} +``` + +**Use this rule by referencing it:** +``` xml + +``` + +## CloneMethodMustImplementCloneable + +**Since:** PMD 1.9 + +**Priority:** Medium (3) + +The method clone() should only be implemented if the class implements the Cloneable interface with the exception of +a final method that only throws CloneNotSupportedException. + +The rule can also detect, if the class implements or extends a Cloneable class. + +**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.errorprone.CloneMethodMustImplementCloneableRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/CloneMethodMustImplementCloneableRule.java) + +**Example(s):** + +``` java +public class MyClass { + public Object clone() throws CloneNotSupportedException { + return foo; + } +} +``` + +**Use this rule by referencing it:** +``` xml + +``` + +## CloneMethodReturnTypeMustMatchClassName + +**Since:** PMD 5.4.0 + +**Priority:** Medium (3) + +**Minimum Language Version:** Java 1.5 + +If a class implements cloneable the return type of the method clone() must be the class name. That way, the caller +of the clone method doesn't need to cast the returned clone to the correct type. + +Note: This is only possible with Java 1.5 or higher. + +**This rule is defined by the following XPath expression:** +``` xpath +//MethodDeclaration +[ +MethodDeclarator/@Image = 'clone' +and MethodDeclarator/FormalParameters/@ParameterCount = 0 +and not (ResultType//ClassOrInterfaceType/@Image = ancestor::ClassOrInterfaceDeclaration[1]/@Image) +] +``` + +**Example(s):** + +``` java +public class Foo implements Cloneable { + @Override + protected Object clone() { // Violation, Object must be Foo + } +} + +public class Foo implements Cloneable { + @Override + public Foo clone() { //Ok + } +} +``` + +**Use this rule by referencing it:** +``` xml + +``` + +## CloneThrowsCloneNotSupportedException + +**Since:** PMD 1.9 + +**Priority:** Medium (3) + +The method clone() should throw a CloneNotSupportedException. + +**This rule is defined by the following XPath expression:** +``` xpath +//MethodDeclaration +[ +MethodDeclarator/@Image = 'clone' +and count(MethodDeclarator/FormalParameters/*) = 0 +and count(NameList/Name[contains +(@Image,'CloneNotSupportedException')]) = 0 +] +[ +../../../../ClassOrInterfaceDeclaration[@Final = 'false'] +] +``` + +**Example(s):** + +``` java +public class MyClass implements Cloneable{ + public Object clone() { // will cause an error + MyClass clone = (MyClass)super.clone(); + return clone; + } +} +``` + +**Use this rule by referencing it:** +``` xml + +``` + +## CloseResource + +**Since:** PMD 1.2.2 + +**Priority:** Medium (3) + +Ensure that resources (like Connection, Statement, and ResultSet objects) are always closed after use. + +**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.errorprone.CloseResourceRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/CloseResourceRule.java) + +**Example(s):** + +``` java +public class Bar { + public void foo() { + Connection c = pool.getConnection(); + try { + // do stuff + } catch (SQLException ex) { + // handle exception + } finally { + // oops, should close the connection using 'close'! + // c.close(); + } + } +} +``` + +**This rule has the following properties:** + +|Name|Default Value|Description|Multivalued| +|----|-------------|-----------|-----------| +|closeAsDefaultTarget|true|Consider 'close' as a target by default|no| +|types|java.sql.Connection , java.sql.Statement , java.sql.ResultSet|Affected types|yes. Delimiter is ','.| +|closeTargets||Methods which may close this resource|yes. Delimiter is ','.| + +**Use this rule by referencing it:** +``` xml + +``` + +## CompareObjectsWithEquals + +**Since:** PMD 3.2 + +**Priority:** Medium (3) + +Use equals() to compare object references; avoid comparing them with ==. + +**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.errorprone.CompareObjectsWithEqualsRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/CompareObjectsWithEqualsRule.java) + +**Example(s):** + +``` java +class Foo { + boolean bar(String a, String b) { + return a == b; + } +} +``` + +**Use this rule by referencing it:** +``` xml + +``` + +## ConstructorCallsOverridableMethod + +**Since:** PMD 1.04 + +**Priority:** High (1) + +Calling overridable methods during construction poses a risk of invoking methods on an incompletely +constructed object and can be difficult to debug. +It may leave the sub-class unable to construct its superclass or forced to replicate the construction +process completely within itself, losing the ability to call super(). If the default constructor +contains a call to an overridable method, the subclass may be completely uninstantiable. Note that +this includes method calls throughout the control flow graph - i.e., if a constructor Foo() calls a +private method bar() that calls a public method buz(), this denotes a problem. + +**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.errorprone.ConstructorCallsOverridableMethodRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/ConstructorCallsOverridableMethodRule.java) + +**Example(s):** + +``` java +public class SeniorClass { + public SeniorClass(){ + toString(); //may throw NullPointerException if overridden + } + public String toString(){ + return "IAmSeniorClass"; + } +} +public class JuniorClass extends SeniorClass { + private String name; + public JuniorClass(){ + super(); //Automatic call leads to NullPointerException + name = "JuniorClass"; + } + public String toString(){ + return name.toUpperCase(); + } +} +``` + +**Use this rule by referencing it:** +``` xml + +``` + +## DataflowAnomalyAnalysis + +**Since:** PMD 3.9 + +**Priority:** Low (5) + +The dataflow analysis tracks local definitions, undefinitions and references to variables on different paths on the data flow. +From those informations there can be found various problems. + +1. UR - Anomaly: There is a reference to a variable that was not defined before. This is a bug and leads to an error. +2. DU - Anomaly: A recently defined variable is undefined. These anomalies may appear in normal source text. +3. DD - Anomaly: A recently defined variable is redefined. This is ominous but don't have to be a bug. + +**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.errorprone.DataflowAnomalyAnalysisRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/DataflowAnomalyAnalysisRule.java) + +**Example(s):** + +``` java +public void foo() { + int buz = 5; + buz = 6; // redefinition of buz -> dd-anomaly + foo(buz); + buz = 2; +} // buz is undefined when leaving scope -> du-anomaly +``` + +**This rule has the following properties:** + +|Name|Default Value|Description|Multivalued| +|----|-------------|-----------|-----------| +|maxViolations|100|Maximum number of anomalies per class|no| +|maxPaths|1000|Maximum number of checked paths per method. A lower value will increase the performance of the rule but may decrease anomalies found.|no| + +**Use this rule by referencing it:** +``` xml + +``` + +## DoNotCallGarbageCollectionExplicitly + +**Since:** PMD 4.2 + +**Priority:** Medium High (2) + +Calls to System.gc(), Runtime.getRuntime().gc(), and System.runFinalization() are not advised. Code should have the +same behavior whether the garbage collection is disabled using the option -Xdisableexplicitgc or not. +Moreover, "modern" jvms do a very good job handling garbage collections. If memory usage issues unrelated to memory +leaks develop within an application, it should be dealt with JVM options rather than within the code itself. + +**This rule is defined by the following XPath expression:** +``` xpath +//Name[ +(starts-with(@Image, 'System.') and +(starts-with(@Image, 'System.gc') or +starts-with(@Image, 'System.runFinalization'))) or +( +starts-with(@Image,'Runtime.getRuntime') and +../../PrimarySuffix[ends-with(@Image,'gc')] +) +] +``` + +**Example(s):** + +``` java +public class GCCall { + public GCCall() { + // Explicit gc call ! + System.gc(); + } + + public void doSomething() { + // Explicit gc call ! + Runtime.getRuntime().gc(); + } + + public explicitGCcall() { + // Explicit gc call ! + System.gc(); + } + + public void doSomething() { + // Explicit gc call ! + Runtime.getRuntime().gc(); + } +} +``` + +**Use this rule by referencing it:** +``` xml + +``` + +## DoNotCallSystemExit + +**Since:** PMD 4.1 + +**Priority:** Medium (3) + +Web applications should not call System.exit(), since only the web container or the +application server should stop the JVM. This rule also checks for the equivalent call Runtime.getRuntime().exit(). + +**This rule is defined by the following XPath expression:** +``` xpath +//Name[ + starts-with(@Image,'System.exit') + or + (starts-with(@Image,'Runtime.getRuntime') and ../../PrimarySuffix[ends-with(@Image,'exit')]) +] +``` + +**Example(s):** + +``` java +public void bar() { + System.exit(0); // never call this when running in an application server! +} +public void foo() { + Runtime.getRuntime().exit(0); // never stop the JVM manually, the container will do this. +} +``` + +**Use this rule by referencing it:** +``` xml + +``` + +## DoNotExtendJavaLangThrowable + +**Since:** PMD 6.0.0 + +**Priority:** Medium (3) + +Extend Exception or RuntimeException instead of Throwable. + +**This rule is defined by the following XPath expression:** +``` xpath +//ClassOrInterfaceDeclaration/ExtendsList/ClassOrInterfaceType + [@Image="Throwable" or @Image="java.lang.Throwable"] +``` + +**Example(s):** + +``` java +public class Foo extends Throwable { } +``` + +**Use this rule by referencing it:** +``` xml + +``` + +## DoNotHardCodeSDCard + +**Since:** PMD 4.2.6 + +**Priority:** Medium (3) + +Use Environment.getExternalStorageDirectory() instead of "/sdcard" + +**This rule is defined by the following XPath expression:** +``` xpath +//Literal[starts-with(@Image,'"/sdcard')] +``` + +**Example(s):** + +``` java +public class MyActivity extends Activity { + protected void foo() { + String storageLocation = "/sdcard/mypackage"; // hard-coded, poor approach + + storageLocation = Environment.getExternalStorageDirectory() + "/mypackage"; // preferred approach + } +} +``` + +**Use this rule by referencing it:** +``` xml + +``` + +## DoNotThrowExceptionInFinally + +**Since:** PMD 4.2 + +**Priority:** Medium Low (4) + +Throwing exceptions within a 'finally' block is confusing since they may mask other exceptions +or code defects. +Note: This is a PMD implementation of the Lint4j rule "A throw in a finally block" + +**This rule is defined by the following XPath expression:** +``` xpath +//FinallyStatement[descendant::ThrowStatement] +``` + +**Example(s):** + +``` java +public class Foo { + public void bar() { + try { + // Here do some stuff + } catch( Exception e) { + // Handling the issue + } finally { + // is this really a good idea ? + throw new Exception(); + } + } +} +``` + +**Use this rule by referencing it:** +``` xml + +``` + +## DontImportSun + +**Since:** PMD 1.5 + +**Priority:** Medium Low (4) + +Avoid importing anything from the 'sun.*' packages. These packages are not portable and are likely to change. + +**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.errorprone.DontImportSunRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/DontImportSunRule.java) + +**Example(s):** + +``` java +import sun.misc.foo; +public class Foo {} +``` + +**Use this rule by referencing it:** +``` xml + +``` + +## DontUseFloatTypeForLoopIndices + +**Since:** PMD 4.3 + +**Priority:** Medium (3) + +Don't use floating point for loop indices. If you must use floating point, use double +unless you're certain that float provides enough precision and you have a compelling +performance need (space or time). + +**This rule is defined by the following XPath expression:** +``` xpath +//ForStatement/ForInit/LocalVariableDeclaration +/Type/PrimitiveType[@Image="float"] +``` + +**Example(s):** + +``` java +public class Count { + public static void main(String[] args) { + final int START = 2000000000; + int count = 0; + for (float f = START; f < START + 50; f++) + count++; + //Prints 0 because (float) START == (float) (START + 50). + System.out.println(count); + //The termination test misbehaves due to floating point granularity. + } +} +``` + +**Use this rule by referencing it:** +``` xml + +``` + +## EmptyCatchBlock + +**Since:** PMD 0.1 + +**Priority:** Medium (3) + +Empty Catch Block finds instances where an exception is caught, but nothing is done. +In most circumstances, this swallows an exception which should either be acted on +or reported. + +**This rule is defined by the following XPath expression:** +``` xpath +//CatchStatement + [count(Block/BlockStatement) = 0 and ($allowCommentedBlocks != 'true' or Block/@containsComment = 'false')] + [FormalParameter/Type/ReferenceType + /ClassOrInterfaceType[@Image != 'InterruptedException' and @Image != 'CloneNotSupportedException'] + ] + [FormalParameter/VariableDeclaratorId[not(matches(@Image, $allowExceptionNameRegex))]] +``` + +**Example(s):** + +``` java +public void doSomething() { + try { + FileInputStream fis = new FileInputStream("/tmp/bugger"); + } catch (IOException ioe) { + // not good + } +} +``` + +**This rule has the following properties:** + +|Name|Default Value|Description|Multivalued| +|----|-------------|-----------|-----------| +|allowCommentedBlocks|false|Empty blocks containing comments will be skipped|no| +|allowExceptionNameRegex|^(ignored\|expected)$|Empty blocks catching exceptions with names matching this regular expression will be skipped|no| + +**Use this rule by referencing it:** +``` xml + +``` + +## EmptyFinalizer + +**Since:** PMD 1.5 + +**Priority:** Medium (3) + +Empty finalize methods serve no purpose and should be removed. Note that Oracle has declared Object.finalize() as deprecated since JDK 9. + +**This rule is defined by the following XPath expression:** +``` xpath +//MethodDeclaration[MethodDeclarator[@Image='finalize'][not(FormalParameters/*)]] + /Block[count(*)=0] +``` + +**Example(s):** + +``` java +public class Foo { + protected void finalize() {} +} +``` + +**Use this rule by referencing it:** +``` xml + +``` + +## EmptyFinallyBlock + +**Since:** PMD 0.4 + +**Priority:** Medium (3) + +Empty finally blocks serve no purpose and should be removed. + +**This rule is defined by the following XPath expression:** +``` xpath +//FinallyStatement[count(Block/BlockStatement) = 0] +``` + +**Example(s):** + +``` java +public class Foo { + public void bar() { + try { + int x=2; + } finally { + // empty! + } + } +} +``` + +**Use this rule by referencing it:** +``` xml + +``` + +## EmptyIfStmt + +**Since:** PMD 0.1 + +**Priority:** Medium (3) + +Empty If Statement finds instances where a condition is checked but nothing is done about it. + +**This rule is defined by the following XPath expression:** +``` xpath +//IfStatement/Statement + [EmptyStatement or Block[count(*) = 0]] +``` + +**Example(s):** + +``` java +public class Foo { + void bar(int x) { + if (x == 0) { + // empty! + } + } +} +``` + +**Use this rule by referencing it:** +``` xml + +``` + +## EmptyInitializer + +**Since:** PMD 5.0 + +**Priority:** Medium (3) + +Empty initializers serve no purpose and should be removed. + +**This rule is defined by the following XPath expression:** +``` xpath +//Initializer/Block[count(*)=0] +``` + +**Example(s):** + +``` java +public class Foo { + + static {} // Why ? + + {} // Again, why ? + +} +``` + +**Use this rule by referencing it:** +``` xml + +``` + +## EmptyStatementBlock + +**Since:** PMD 5.0 + +**Priority:** Medium (3) + +Empty block statements serve no purpose and should be removed. + +**This rule is defined by the following XPath expression:** +``` xpath +//BlockStatement/Statement/Block[count(*) = 0] +``` + +**Example(s):** + +``` java +public class Foo { + + private int _bar; + + public void setBar(int bar) { + { _bar = bar; } // Why not? + {} // But remove this. + } + +} +``` + +**Use this rule by referencing it:** +``` xml + +``` + +## EmptyStatementNotInLoop + +**Since:** PMD 1.5 + +**Priority:** Medium (3) + +An empty statement (or a semicolon by itself) that is not used as the sole body of a 'for' +or 'while' loop is probably a bug. It could also be a double semicolon, which has no purpose +and should be removed. + +**This rule is defined by the following XPath expression:** +``` xpath +//EmptyStatement + [not( + ../../../ForStatement + or ../../../WhileStatement + or ../../../BlockStatement/ClassOrInterfaceDeclaration + or ../../../../../../ForStatement/Statement[1] + /Block[1]/BlockStatement[1]/Statement/EmptyStatement + or ../../../../../../WhileStatement/Statement[1] + /Block[1]/BlockStatement[1]/Statement/EmptyStatement) + ] +``` + +**Example(s):** + +``` java +public void doit() { + // this is probably not what you meant to do + ; + // the extra semicolon here this is not necessary + System.out.println("look at the extra semicolon");; +} +``` + +**Use this rule by referencing it:** +``` xml + +``` + +## EmptySwitchStatements + +**Since:** PMD 1.0 + +**Priority:** Medium (3) + +Empty switch statements serve no purpose and should be removed. + +**This rule is defined by the following XPath expression:** +``` xpath +//SwitchStatement[count(*) = 1] +``` + +**Example(s):** + +``` java +public void bar() { + int x = 2; + switch (x) { + // once there was code here + // but it's been commented out or something + } +} +``` + +**Use this rule by referencing it:** +``` xml + +``` + +## EmptySynchronizedBlock + +**Since:** PMD 1.3 + +**Priority:** Medium (3) + +Empty synchronized blocks serve no purpose and should be removed. + +**This rule is defined by the following XPath expression:** +``` xpath +//SynchronizedStatement/Block[1][count(*) = 0] +``` + +**Example(s):** + +``` java +public class Foo { + public void bar() { + synchronized (this) { + // empty! + } + } +} +``` + +**Use this rule by referencing it:** +``` xml + +``` + +## EmptyTryBlock + +**Since:** PMD 0.4 + +**Priority:** Medium (3) + +Avoid empty try blocks - what's the point? + +**This rule is defined by the following XPath expression:** +``` xpath +//TryStatement[not(ResourceSpecification)]/Block[1][count(*) = 0] +``` + +**Example(s):** + +``` java +public class Foo { + public void bar() { + try { + } catch (Exception e) { + e.printStackTrace(); + } + } +} +``` + +**Use this rule by referencing it:** +``` xml + +``` + +## EmptyWhileStmt + +**Since:** PMD 0.2 + +**Priority:** Medium (3) + +Empty While Statement finds all instances where a while statement does nothing. +If it is a timing loop, then you should use Thread.sleep() for it; if it is +a while loop that does a lot in the exit expression, rewrite it to make it clearer. + +**This rule is defined by the following XPath expression:** +``` xpath +//WhileStatement/Statement[./Block[count(*) = 0] or ./EmptyStatement] +``` + +**Example(s):** + +``` java +void bar(int a, int b) { + while (a == b) { + // empty! + } +} +``` + +**Use this rule by referencing it:** +``` xml + +``` + +## EqualsNull + +**Since:** PMD 1.9 + +**Priority:** High (1) + +Tests for null should not use the equals() method. The '==' operator should be used instead. + +**This rule is defined by the following XPath expression:** +``` xpath +//PrimaryExpression + [ + PrimaryPrefix[Name[ends-with(@Image, 'equals')]] + [following-sibling::node()/Arguments/ArgumentList[count(Expression)=1] + /Expression/PrimaryExpression/PrimaryPrefix/Literal/NullLiteral] + + or + + PrimarySuffix[ends-with(@Image, 'equals')] + [following-sibling::node()/Arguments/ArgumentList[count(Expression)=1] + /Expression/PrimaryExpression/PrimaryPrefix/Literal/NullLiteral] + + ] +``` + +**Example(s):** + +``` java +String x = "foo"; + +if (x.equals(null)) { // bad form + doSomething(); +} + +if (x == null) { // preferred + doSomething(); +} +``` + +**Use this rule by referencing it:** +``` xml + +``` + +## FinalizeDoesNotCallSuperFinalize + +**Since:** PMD 1.5 + +**Priority:** Medium (3) + +If the finalize() is implemented, its last action should be to call super.finalize. Note that Oracle has declared Object.finalize() as deprecated since JDK 9. + +**This rule is defined by the following XPath expression:** +``` xpath +//MethodDeclaration[MethodDeclarator[@Image='finalize'][not(FormalParameters/*)]] + /Block + /BlockStatement[last()] + [not(Statement/StatementExpression/PrimaryExpression + [./PrimaryPrefix[@SuperModifier='true']] + [./PrimarySuffix[@Image='finalize']] + ) + ] + [not(Statement/TryStatement/FinallyStatement + /Block/BlockStatement/Statement/StatementExpression/PrimaryExpression + [./PrimaryPrefix[@SuperModifier='true']] + [./PrimarySuffix[@Image='finalize']] + ) + ] +``` + +**Example(s):** + +``` java +protected void finalize() { + something(); + // neglected to call super.finalize() +} +``` + +**Use this rule by referencing it:** +``` xml + +``` + +## FinalizeOnlyCallsSuperFinalize + +**Since:** PMD 1.5 + +**Priority:** Medium (3) + +If the finalize() is implemented, it should do something besides just calling super.finalize(). Note that Oracle has declared Object.finalize() as deprecated since JDK 9. + +**This rule is defined by the following XPath expression:** +``` xpath +//MethodDeclaration[MethodDeclarator[@Image="finalize"][not(FormalParameters/*)]] + /Block[count(BlockStatement)=1] + /BlockStatement[ + Statement/StatementExpression/PrimaryExpression + [./PrimaryPrefix[@SuperModifier='true']] + [./PrimarySuffix[@Image='finalize']] + ] +``` + +**Example(s):** + +``` java +protected void finalize() { + super.finalize(); +} +``` + +**Use this rule by referencing it:** +``` xml + +``` + +## FinalizeOverloaded + +**Since:** PMD 1.5 + +**Priority:** Medium (3) + +Methods named finalize() should not have parameters. It is confusing and most likely an attempt to +overload Object.finalize(). It will not be called by the VM. + +Note that Oracle has declared Object.finalize() as deprecated since JDK 9. + +**This rule is defined by the following XPath expression:** +``` xpath +//MethodDeclaration + /MethodDeclarator[@Image='finalize'][FormalParameters[count(*)>0]] +``` + +**Example(s):** + +``` java +public class Foo { + // this is confusing and probably a bug + protected void finalize(int a) { + } +} +``` + +**Use this rule by referencing it:** +``` xml + +``` + +## FinalizeShouldBeProtected + +**Since:** PMD 1.1 + +**Priority:** Medium (3) + +When overriding the finalize(), the new method should be set as protected. If made public, +other classes may invoke it at inappropriate times. + +Note that Oracle has declared Object.finalize() as deprecated since JDK 9. + +**This rule is defined by the following XPath expression:** +``` xpath +//MethodDeclaration[@Protected="false"] + /MethodDeclarator[@Image="finalize"] + [not(FormalParameters/*)] +``` + +**Example(s):** + +``` java +public void finalize() { + // do something +} +``` + +**Use this rule by referencing it:** +``` xml + +``` + +## IdempotentOperations + +**Since:** PMD 2.0 + +**Priority:** Medium (3) + +Avoid idempotent operations - they have no effect. + +**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.errorprone.IdempotentOperationsRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/IdempotentOperationsRule.java) + +**Example(s):** + +``` java +public class Foo { + public void bar() { + int x = 2; + x = x; + } +} +``` + +**Use this rule by referencing it:** +``` xml + +``` + +## ImportFromSamePackage + +**Since:** PMD 1.02 + +**Priority:** Medium (3) + +There is no need to import a type that lives in the same package. + +**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.errorprone.ImportFromSamePackageRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/ImportFromSamePackageRule.java) + +**Example(s):** + +``` java +package foo; + +import foo.Buz; // no need for this +import foo.*; // or this + +public class Bar{} +``` + +**Use this rule by referencing it:** +``` xml + +``` + +## InstantiationToGetClass + +**Since:** PMD 2.0 + +**Priority:** Medium Low (4) + +Avoid instantiating an object just to call getClass() on it; use the .class public member instead. + +**This rule is defined by the following XPath expression:** +``` xpath +//PrimarySuffix + [@Image='getClass'] + [parent::PrimaryExpression + [PrimaryPrefix/AllocationExpression] + [count(PrimarySuffix) = 2] + ] +``` + +**Example(s):** + +``` java +// replace this +Class c = new String().getClass(); + +// with this: +Class c = String.class; +``` + +**Use this rule by referencing it:** +``` xml + +``` + +## InvalidSlf4jMessageFormat + +**Since:** PMD 5.5.0 + +**Priority:** Low (5) + +Check for messages in slf4j loggers with non matching number of arguments and placeholders. + +**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.errorprone.InvalidSlf4jMessageFormatRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/InvalidSlf4jMessageFormatRule.java) + +**Example(s):** + +``` java +LOGGER.error("forget the arg {}"); +LOGGER.error("too many args {}", "arg1", "arg2"); +LOGGER.error("param {}", "arg1", new IllegalStateException("arg")); //The exception is shown separately, so is correct. +``` + +**Use this rule by referencing it:** +``` xml + +``` + +## JumbledIncrementer + +**Since:** PMD 1.0 + +**Priority:** Medium (3) + +Avoid jumbled loop incrementers - its usually a mistake, and is confusing even if intentional. + +**This rule is defined by the following XPath expression:** +``` xpath +//ForStatement + [ + ForUpdate/StatementExpressionList/StatementExpression/PostfixExpression/PrimaryExpression/PrimaryPrefix/Name/@Image + = + ancestor::ForStatement/ForInit//VariableDeclaratorId/@Image + ] +``` + +**Example(s):** + +``` java +public class JumbledIncrementerRule1 { + public void foo() { + for (int i = 0; i < 10; i++) { // only references 'i' + for (int k = 0; k < 20; i++) { // references both 'i' and 'k' + System.out.println("Hello"); + } + } + } +} +``` + +**Use this rule by referencing it:** +``` xml + +``` + +## JUnitSpelling + +**Since:** PMD 1.0 + +**Priority:** Medium (3) + +Some JUnit framework methods are easy to misspell. + +**This rule is defined by the following XPath expression:** +``` xpath +//MethodDeclarator[(not(@Image = 'setUp') + and translate(@Image, 'SETuP', 'setUp') = 'setUp') + 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') + 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):** + +``` java +import junit.framework.*; + +public class Foo extends TestCase { + public void setup() {} // oops, should be setUp + public void TearDown() {} // oops, should be tearDown +} +``` + +**Use this rule by referencing it:** +``` xml + +``` + +## JUnitStaticSuite + +**Since:** PMD 1.0 + +**Priority:** Medium (3) + +The suite() method in a JUnit test needs to be both public and static. + +**This rule is defined by the following XPath expression:** +``` xpath +//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') + 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):** + +``` java +import junit.framework.*; + +public class Foo extends TestCase { + public void suite() {} // oops, should be static + private static void suite() {} // oops, should be public +} +``` + +**Use this rule by referencing it:** +``` xml + +``` + +## LoggerIsNotStaticFinal + +**Since:** PMD 2.0 + +**Priority:** Medium High (2) + +In most cases, the Logger reference can be declared as static and final. + +**This rule is defined by the following XPath expression:** +``` xpath +//VariableDeclarator + [parent::FieldDeclaration] + [../Type/ReferenceType + /ClassOrInterfaceType[@Image='Logger'] + and + (..[@Final='false'] or ..[@Static = 'false'] ) ] +``` + +**Example(s):** + +``` java +public class Foo{ + Logger log = Logger.getLogger(Foo.class.getName()); // not recommended + + static final Logger log = Logger.getLogger(Foo.class.getName()); // preferred approach +} +``` + +**Use this rule by referencing it:** +``` xml + +``` + +## MethodWithSameNameAsEnclosingClass + +**Since:** PMD 1.5 + +**Priority:** Medium (3) + +Non-constructor methods should not have the same name as the enclosing class. + +**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.errorprone.MethodWithSameNameAsEnclosingClassRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/MethodWithSameNameAsEnclosingClassRule.java) + +**Example(s):** + +``` java +public class MyClass { + + public MyClass() {} // this is OK because it is a constructor + + public void MyClass() {} // this is bad because it is a method +} +``` + +**Use this rule by referencing it:** +``` xml + +``` + +## MisplacedNullCheck + +**Since:** PMD 3.5 + +**Priority:** Medium (3) + +The null check here is misplaced. If the variable is null a NullPointerException will be thrown. +Either the check is useless (the variable will never be "null") or it is incorrect. + +**This rule is defined by the following XPath expression:** +``` xpath +//Expression + /*[self::ConditionalOrExpression or self::ConditionalAndExpression] + /descendant::PrimaryExpression/PrimaryPrefix + /Name[starts-with(@Image, + concat(ancestor::PrimaryExpression/following-sibling::EqualityExpression + [./PrimaryExpression/PrimaryPrefix/Literal/NullLiteral] + /PrimaryExpression/PrimaryPrefix + /Name[count(../../PrimarySuffix)=0]/@Image,".") + ) + ] + [count(ancestor::ConditionalAndExpression/EqualityExpression + [@Image='!='] + [./PrimaryExpression/PrimaryPrefix/Literal/NullLiteral] + [starts-with(following-sibling::*/PrimaryExpression/PrimaryPrefix/Name/@Image, + concat(./PrimaryExpression/PrimaryPrefix/Name/@Image, '.'))] + ) = 0 + ] +``` + +**Example(s):** + +``` java +public class Foo { + void bar() { + if (a.equals(baz) && a != null) {} + } +} +``` + +``` java +public class Foo { + void bar() { + if (a.equals(baz) || a == null) {} + } +} +``` + +**Use this rule by referencing it:** +``` xml + +``` + +## MissingBreakInSwitch + +**Since:** PMD 3.0 + +**Priority:** Medium (3) + +Switch statements without break or return statements for each case option +may indicate problematic behaviour. Empty cases are ignored as these indicate an intentional fall-through. + +**This rule is defined by the following XPath expression:** +``` xpath +//SwitchStatement +[(count(.//BreakStatement) + + count(BlockStatement//Statement/ReturnStatement) + + count(BlockStatement//Statement/ContinueStatement) + + count(BlockStatement//Statement/ThrowStatement) + + count(BlockStatement//Statement/IfStatement[@Else='true' and Statement[2][ReturnStatement|ContinueStatement|ThrowStatement]]/Statement[1][ReturnStatement|ContinueStatement|ThrowStatement]) + + count(SwitchLabel[name(following-sibling::node()) = 'SwitchLabel']) + + count(SwitchLabel[count(following-sibling::node()) = 0]) + < count (SwitchLabel))] +``` + +**Example(s):** + +``` java +public void bar(int status) { + switch(status) { + case CANCELLED: + doCancelled(); + // break; hm, should this be commented out? + case NEW: + doNew(); + // is this really a fall-through? + case REMOVED: + doRemoved(); + // what happens if you add another case after this one? + case OTHER: // empty case - this is interpreted as an intentional fall-through + case ERROR: + doErrorHandling(); + break; + } +} +``` + +**Use this rule by referencing it:** +``` xml + +``` + +## MissingSerialVersionUID + +**Since:** PMD 3.0 + +**Priority:** Medium (3) + +Serializable classes should provide a serialVersionUID field. +The serialVersionUID field is also needed for abstract base classes. Each individual class in the inheritance +chain needs an own serialVersionUID field. See also [Should an abstract class have a serialVersionUID](https://stackoverflow.com/questions/893259/should-an-abstract-class-have-a-serialversionuid). + +**This rule is defined by the following XPath expression:** +``` xpath +//ClassOrInterfaceDeclaration + [@Interface = 'false'] + [count(ClassOrInterfaceBody/ClassOrInterfaceBodyDeclaration + /FieldDeclaration/VariableDeclarator/VariableDeclaratorId[@Image='serialVersionUID']) = 0] + [(ImplementsList | ExtendsList)/ClassOrInterfaceType[pmd-java:typeIs('java.io.Serializable')]] +``` + +**Example(s):** + +``` java +public class Foo implements java.io.Serializable { + String name; + // Define serialization id to avoid serialization related bugs + // i.e., public static final long serialVersionUID = 4328743; +} +``` + +**Use this rule by referencing it:** +``` xml + +``` + +## MissingStaticMethodInNonInstantiatableClass + +**Since:** PMD 3.0 + +**Priority:** Medium (3) + +A class that has private constructors and does not have any static methods or fields cannot be used. + +**This rule is defined by the following XPath expression:** +``` xpath +//ClassOrInterfaceDeclaration[@Nested='false'] +[ + ( + ./ClassOrInterfaceBody/ClassOrInterfaceBodyDeclaration/ConstructorDeclaration + and + count(./ClassOrInterfaceBody/ClassOrInterfaceBodyDeclaration/ConstructorDeclaration) = count(./ClassOrInterfaceBody/ClassOrInterfaceBodyDeclaration/ConstructorDeclaration[@Private='true']) + ) + and + not(.//MethodDeclaration[@Static='true']) + and + not(.//FieldDeclaration[@Private='false'][@Static='true']) + and + not(.//ClassOrInterfaceDeclaration[@Nested='true'] + [@Public='true'] + [@Static='true'] + [not(./ClassOrInterfaceBody/ClassOrInterfaceBodyDeclaration/ConstructorDeclaration) or ./ClassOrInterfaceBody/ClassOrInterfaceBodyDeclaration/ConstructorDeclaration[@Public='true']] + [./ClassOrInterfaceBody/ClassOrInterfaceBodyDeclaration/MethodDeclaration + [@Public='true'] + [./ResultType/Type/ReferenceType/ClassOrInterfaceType + [@Image = //ClassOrInterfaceDeclaration[@Nested='false']/@Image] + ] + ] + ) +] +``` + +**Example(s):** + +``` java +// This class is unusable, since it cannot be +// instantiated (private constructor), +// and no static method can be called. + +public class Foo { + private Foo() {} + void foo() {} +} +``` + +**Use this rule by referencing it:** +``` xml + +``` + +## MoreThanOneLogger + +**Since:** PMD 2.0 + +**Priority:** Medium High (2) + +Normally only one logger is used in each class. + +**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.errorprone.MoreThanOneLoggerRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/MoreThanOneLoggerRule.java) + +**Example(s):** + +``` java +public class Foo { + Logger log = Logger.getLogger(Foo.class.getName()); + // It is very rare to see two loggers on a class, normally + // log information is multiplexed by levels + Logger log2= Logger.getLogger(Foo.class.getName()); +} +``` + +**Use this rule by referencing it:** +``` xml + +``` + +## NonCaseLabelInSwitchStatement + +**Since:** PMD 1.5 + +**Priority:** Medium (3) + +A non-case label (e.g. a named break/continue label) was present in a switch statement. +This legal, but confusing. It is easy to mix up the case labels and the non-case labels. + +**This rule is defined by the following XPath expression:** +``` xpath +//SwitchStatement//BlockStatement/Statement/LabeledStatement +``` + +**Example(s):** + +``` java +public class Foo { + void bar(int a) { + switch (a) { + case 1: + // do something + break; + mylabel: // this is legal, but confusing! + break; + default: + break; + } + } +} +``` + +**Use this rule by referencing it:** +``` xml + +``` + +## NonStaticInitializer + +**Since:** PMD 1.5 + +**Priority:** Medium (3) + +A non-static initializer block will be called any time a constructor is invoked (just prior to +invoking the constructor). While this is a valid language construct, it is rarely used and is +confusing. + +**This rule is defined by the following XPath expression:** +``` xpath +//Initializer[@Static='false'] +``` + +**Example(s):** + +``` java +public class MyClass { + // this block gets run before any call to a constructor + { + System.out.println("I am about to construct myself"); + } +} +``` + +**Use this rule by referencing it:** +``` xml + +``` + +## NullAssignment + +**Since:** PMD 1.02 + +**Priority:** Medium (3) + +Assigning a "null" to a variable (outside of its declaration) is usually bad form. Sometimes, this type +of assignment is an indication that the programmer doesn't completely understand what is going on in the code. + +NOTE: This sort of assignment may used in some cases to dereference objects and encourage garbage collection. + +**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.errorprone.NullAssignmentRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/NullAssignmentRule.java) + +**Example(s):** + +``` java +public void bar() { + Object x = null; // this is OK + x = new Object(); + // big, complex piece of code here + x = null; // this is not required + // big, complex piece of code here +} +``` + +**Use this rule by referencing it:** +``` xml + +``` + +## OverrideBothEqualsAndHashcode + +**Since:** PMD 0.4 + +**Priority:** Medium (3) + +Override both public boolean Object.equals(Object other), and public int Object.hashCode(), or override neither. Even if you are inheriting a hashCode() from a parent class, consider implementing hashCode and explicitly delegating to your superclass. + +**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.errorprone.OverrideBothEqualsAndHashcodeRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/OverrideBothEqualsAndHashcodeRule.java) + +**Example(s):** + +``` java +public class Bar { // poor, missing a hashcode() method + public boolean equals(Object o) { + // do some comparison + } +} + +public class Baz { // poor, missing an equals() method + public int hashCode() { + // return some hash value + } +} + +public class Foo { // perfect, both methods provided + public boolean equals(Object other) { + // do some comparison + } + public int hashCode() { + // return some hash value + } +} +``` + +**Use this rule by referencing it:** +``` xml + +``` + +## ProperCloneImplementation + +**Since:** PMD 1.4 + +**Priority:** Medium High (2) + +Object clone() should be implemented with super.clone(). + +**This rule is defined by the following XPath expression:** +``` xpath +//MethodDeclarator +[@Image = 'clone'] +[count(FormalParameters/*) = 0] +[count(../Block//*[ + (self::AllocationExpression) and + (./ClassOrInterfaceType/@Image = ancestor:: +ClassOrInterfaceDeclaration[1]/@Image) + ])> 0 +] +``` + +**Example(s):** + +``` java +class Foo{ + public Object clone(){ + return new Foo(); // This is bad + } +} +``` + +**Use this rule by referencing it:** +``` xml + +``` + +## ProperLogger + +**Since:** PMD 3.3 + +**Priority:** Medium (3) + +A logger should normally be defined private static final and be associated with the correct class. +Private final Log log; is also allowed for rare cases where loggers need to be passed around, +with the restriction that the logger needs to be passed into the constructor. + +**This rule is defined by the following XPath expression:** +``` xpath +//ClassOrInterfaceBodyDeclaration[FieldDeclaration//ClassOrInterfaceType[@Image='Log'] + and + not(FieldDeclaration[@Final='true'][@Static='true'][@Private='true'][.//VariableDeclaratorId[@Image=$staticLoggerName]] + and + //ArgumentList//ClassOrInterfaceType[@Image = ancestor::ClassOrInterfaceDeclaration/@Image or @Image = ancestor::EnumDeclaration/@Image]) + and + not(FieldDeclaration[@Final='true'][@Private='true'][.//VariableDeclaratorId[@Image='log']] + [count(.//VariableInitializer)=0] + [ancestor::ClassOrInterfaceBody//StatementExpression[.//PrimaryExpression/descendant::*[@Image='log']][count(.//AllocationExpression)=0]] + )] +``` + +**Example(s):** + +``` java +public class Foo { + + private static final Log LOG = LogFactory.getLog(Foo.class); // proper way + + protected Log LOG = LogFactory.getLog(Testclass.class); // wrong approach +} +``` + +**This rule has the following properties:** + +|Name|Default Value|Description|Multivalued| +|----|-------------|-----------|-----------| +|staticLoggerName|LOG|Name of the static Logger variable|no| + +**Use this rule by referencing it:** +``` xml + +``` + +## ReturnEmptyArrayRatherThanNull + +**Since:** PMD 4.2 + +**Priority:** High (1) + +For any method that returns an array, it is a better to return an empty array rather than a +null reference. This removes the need for null checking all results and avoids inadvertent +NullPointerExceptions. + +**This rule is defined by the following XPath expression:** +``` xpath +//MethodDeclaration +[ +(./ResultType/Type[@Array='true']) +and +(./Block/BlockStatement/Statement/ReturnStatement/Expression/PrimaryExpression/PrimaryPrefix/Literal/NullLiteral) +] +``` + +**Example(s):** + +``` java +public class Example { + // Not a good idea... + public int[] badBehavior() { + // ... + return null; + } + + // Good behavior + public String[] bonnePratique() { + //... + return new String[0]; + } +} +``` + +**Use this rule by referencing it:** +``` xml + +``` + +## ReturnFromFinallyBlock + +**Since:** PMD 1.05 + +**Priority:** Medium (3) + +Avoid returning from a finally block, this can discard exceptions. + +**This rule is defined by the following XPath expression:** +``` xpath +//FinallyStatement//ReturnStatement +``` + +**Example(s):** + +``` java +public class Bar { + public String foo() { + try { + throw new Exception( "My Exception" ); + } catch (Exception e) { + throw e; + } finally { + return "A. O. K."; // return not recommended here + } + } +} +``` + +**Use this rule by referencing it:** +``` xml + +``` + +## SimpleDateFormatNeedsLocale + +**Since:** PMD 2.0 + +**Priority:** Medium (3) + +Be sure to specify a Locale when creating SimpleDateFormat instances to ensure that locale-appropriate +formatting is used. + +**This rule is defined by the following XPath expression:** +``` xpath +//AllocationExpression + [ClassOrInterfaceType[@Image='SimpleDateFormat']] + [Arguments[@ArgumentCount=1]] +``` + +**Example(s):** + +``` java +public class Foo { + // Should specify Locale.US (or whatever) + private SimpleDateFormat sdf = new SimpleDateFormat("pattern"); +} +``` + +**Use this rule by referencing it:** +``` xml + +``` + +## SingleMethodSingleton + +**Since:** PMD 5.4 + +**Priority:** Medium High (2) + +Some classes contain overloaded getInstance. The problem with overloaded getInstance methods +is that the instance created using the overloaded method is not cached and so, +for each call and new objects will be created for every invocation. + +**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.errorprone.SingleMethodSingletonRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/SingleMethodSingletonRule.java) + +**Example(s):** + +``` java +public class Singleton { + + private static Singleton singleton = new Singleton( ); + + private Singleton(){ } + + public static Singleton getInstance( ) { + return singleton; + } + + public static Singleton getInstance(Object obj){ + Singleton singleton = (Singleton) obj; + return singleton; //violation + } +} +``` + +**Use this rule by referencing it:** +``` xml + +``` + +## SingletonClassReturningNewInstance + +**Since:** PMD 5.4 + +**Priority:** Medium High (2) + +Some classes contain overloaded getInstance. The problem with overloaded getInstance methods +is that the instance created using the overloaded method is not cached and so, +for each call and new objects will be created for every invocation. + +**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.errorprone.SingletonClassReturningNewInstanceRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/SingletonClassReturningNewInstanceRule.java) + +**Example(s):** + +``` java +class Singleton { + private static Singleton instance = null; + public static Singleton getInstance() { + synchronized(Singleton.class) { + return new Singleton(); + } + } +} +``` + +**Use this rule by referencing it:** +``` xml + +``` + +## StaticEJBFieldShouldBeFinal + +**Since:** PMD 4.1 + +**Priority:** Medium (3) + +According to the J2EE specification, an EJB should not have any static fields +with write access. However, static read-only fields are allowed. This ensures proper +behavior especially when instances are distributed by the container on several JREs. + +**This rule is defined by the following XPath expression:** +``` xpath +//ClassOrInterfaceDeclaration[ + ( + (./ImplementsList/ClassOrInterfaceType[ends-with(@Image,'SessionBean')]) + or + (./ImplementsList/ClassOrInterfaceType[ends-with(@Image,'EJBHome')]) + or + (./ImplementsList/ClassOrInterfaceType[ends-with(@Image,'EJBLocalObject')]) + or + (./ImplementsList/ClassOrInterfaceType[ends-with(@Image,'EJBLocalHome')]) + or + (./ExtendsList/ClassOrInterfaceType[ends-with(@Image,'EJBObject')]) + ) + and + (./ClassOrInterfaceBody/ClassOrInterfaceBodyDeclaration[ + (./FieldDeclaration[@Static = 'true']) + and + (./FieldDeclaration[@Final = 'false']) + ]) +] +``` + +**Example(s):** + +``` java +public class SomeEJB extends EJBObject implements EJBLocalHome { + + private static int CountA; // poor, field can be edited + + private static final int CountB; // preferred, read-only access +} +``` + +**Use this rule by referencing it:** +``` xml + +``` + +## StringBufferInstantiationWithChar + +**Since:** PMD 3.9 + +**Priority:** Medium Low (4) + +Individual character values provided as initialization arguments will be converted into integers. +This can lead to internal buffer sizes that are larger than expected. Some examples: + +``` +new StringBuffer() // 16 +new StringBuffer(6) // 6 +new StringBuffer("hello world") // 11 + 16 = 27 +new StringBuffer('A') // chr(A) = 65 +new StringBuffer("A") // 1 + 16 = 17 + +new StringBuilder() // 16 +new StringBuilder(6) // 6 +new StringBuilder("hello world") // 11 + 16 = 27 +new StringBuilder('C') // chr(C) = 67 +new StringBuilder("A") // 1 + 16 = 17 +``` + +**This rule is defined by the following XPath expression:** +``` xpath +//AllocationExpression/ClassOrInterfaceType +[@Image='StringBuffer' or @Image='StringBuilder'] +/../Arguments/ArgumentList/Expression/PrimaryExpression +/PrimaryPrefix/ +Literal + [starts-with(@Image, "'")] + [ends-with(@Image, "'")] +``` + +**Example(s):** + +``` java +// misleading instantiation, these buffers +// are actually sized to 99 characters long +StringBuffer sb1 = new StringBuffer('c'); +StringBuilder sb2 = new StringBuilder('c'); + +// in these forms, just single characters are allocated +StringBuffer sb3 = new StringBuffer("c"); +StringBuilder sb4 = new StringBuilder("c"); +``` + +**Use this rule by referencing it:** +``` xml + +``` + +## SuspiciousEqualsMethodName + +**Since:** PMD 2.0 + +**Priority:** Medium High (2) + +The method name and parameter number are suspiciously close to equals(Object), which can denote an +intention to override the equals(Object) method. + +**This rule is defined by the following XPath expression:** +``` xpath +//MethodDeclarator[@Image = 'equals'] +[ + (count(FormalParameters/*) = 1 + and not (FormalParameters/FormalParameter/Type/ReferenceType/ClassOrInterfaceType + [@Image = 'Object' or @Image = 'java.lang.Object']) + or not (../ResultType/Type/PrimitiveType[@Image = 'boolean']) + ) or ( + count(FormalParameters/*) = 2 + and ../ResultType/Type/PrimitiveType[@Image = 'boolean'] + and FormalParameters//ClassOrInterfaceType[@Image = 'Object' or @Image = 'java.lang.Object'] + and not(../../Annotation/MarkerAnnotation/Name[@Image='Override']) + ) +] +| //MethodDeclarator[@Image = 'equal'] +[ + count(FormalParameters/*) = 1 + and FormalParameters/FormalParameter/Type/ReferenceType/ClassOrInterfaceType + [@Image = 'Object' or @Image = 'java.lang.Object'] +] +``` + +**Example(s):** + +``` java +public class Foo { + public int equals(Object o) { + // oops, this probably was supposed to be boolean equals + } + public boolean equals(String s) { + // oops, this probably was supposed to be equals(Object) + } + public boolean equals(Object o1, Object o2) { + // oops, this probably was supposed to be equals(Object) + } +} +``` + +**Use this rule by referencing it:** +``` xml + +``` + +## SuspiciousHashcodeMethodName + +**Since:** PMD 1.5 + +**Priority:** Medium (3) + +The method name and return type are suspiciously close to hashCode(), which may denote an intention +to override the hashCode() method. + +**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.errorprone.SuspiciousHashcodeMethodNameRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/SuspiciousHashcodeMethodNameRule.java) + +**Example(s):** + +``` java +public class Foo { + public int hashcode() { // oops, this probably was supposed to be 'hashCode' + } +} +``` + +**Use this rule by referencing it:** +``` xml + +``` + +## SuspiciousOctalEscape + +**Since:** PMD 1.5 + +**Priority:** Medium (3) + +A suspicious octal escape sequence was found inside a String literal. +The Java language specification (section 3.10.6) says an octal +escape sequence inside a literal String shall consist of a backslash +followed by: + + OctalDigit | OctalDigit OctalDigit | ZeroToThree OctalDigit OctalDigit + +Any octal escape sequence followed by non-octal digits can be confusing, +e.g. "\038" is interpreted as the octal escape sequence "\03" followed by +the literal character "8". + +**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.errorprone.SuspiciousOctalEscapeRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/SuspiciousOctalEscapeRule.java) + +**Example(s):** + +``` java +public void foo() { + // interpreted as octal 12, followed by character '8' + System.out.println("suspicious: \128"); +} +``` + +**Use this rule by referencing it:** +``` xml + +``` + +## TestClassWithoutTestCases + +**Since:** PMD 3.0 + +**Priority:** Medium (3) + +Test classes end with the suffix Test. Having a non-test class with that name is not a good practice, +since most people will assume it is a test case. Test classes have test methods named testXXX. + +**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.errorprone.TestClassWithoutTestCasesRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/TestClassWithoutTestCasesRule.java) + +**Example(s):** + +``` java +//Consider changing the name of the class if it is not a test +//Consider adding test methods if it is a test +public class CarTest { + public static void main(String[] args) { + // do something + } + // code +} +``` + +**Use this rule by referencing it:** +``` xml + +``` + +## UnconditionalIfStatement + +**Since:** PMD 1.5 + +**Priority:** Medium (3) + +Do not use "if" statements whose conditionals are always true or always false. + +**This rule is defined by the following XPath expression:** +``` xpath +//IfStatement/Expression + [count(PrimaryExpression)=1] + /PrimaryExpression/PrimaryPrefix/Literal/BooleanLiteral +``` + +**Example(s):** + +``` java +public class Foo { + public void close() { + if (true) { // fixed conditional, not recommended + // ... + } + } +} +``` + +**Use this rule by referencing it:** +``` xml + +``` + +## UnnecessaryBooleanAssertion + +**Since:** PMD 3.0 + +**Priority:** Medium (3) + +A JUnit test assertion with a boolean literal is unnecessary since it always will evaluate to the same thing. +Consider using flow control (in case of assertTrue(false) or similar) or simply removing +statements like assertTrue(true) and assertFalse(false). If you just want a test to halt after finding +an error, use the fail() method and provide an indication message of why it did. + +**This rule is defined by the following XPath expression:** +``` xpath +//StatementExpression +[ +PrimaryExpression/PrimaryPrefix/Name[@Image='assertTrue' or @Image='assertFalse'] +and +PrimaryExpression/PrimarySuffix/Arguments/ArgumentList/Expression +[PrimaryExpression/PrimaryPrefix/Literal/BooleanLiteral +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') + 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):** + +``` java +public class SimpleTest extends TestCase { + public void testX() { + assertTrue(true); // serves no real purpose + } +} +``` + +**Use this rule by referencing it:** +``` xml + +``` + +## UnnecessaryCaseChange + +**Since:** PMD 3.3 + +**Priority:** Medium (3) + +Using equalsIgnoreCase() is faster than using toUpperCase/toLowerCase().equals() + +**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.errorprone.UnnecessaryCaseChangeRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/UnnecessaryCaseChangeRule.java) + +**Example(s):** + +``` java +boolean answer1 = buz.toUpperCase().equals("baz"); // should be buz.equalsIgnoreCase("baz") + +boolean answer2 = buz.toUpperCase().equalsIgnoreCase("baz"); // another unnecessary toUpperCase() +``` + +**Use this rule by referencing it:** +``` xml + +``` + +## UnnecessaryConversionTemporary + +**Since:** PMD 0.1 + +**Priority:** Medium (3) + +Avoid the use temporary objects when converting primitives to Strings. Use the static conversion methods +on the wrapper classes instead. + +**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.errorprone.UnnecessaryConversionTemporaryRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/UnnecessaryConversionTemporaryRule.java) + +**Example(s):** + +``` java +public String convert(int x) { + String foo = new Integer(x).toString(); // this wastes an object + + return Integer.toString(x); // preferred approach +} +``` + +**Use this rule by referencing it:** +``` xml + +``` + +## UnusedNullCheckInEquals + +**Since:** PMD 3.5 + +**Priority:** Medium (3) + +After checking an object reference for null, you should invoke equals() on that object rather than passing it to another object's equals() method. + +**This rule is defined by the following XPath expression:** +``` xpath +(//PrimaryPrefix[ends-with(Name/@Image, '.equals') and Name/@Image != 'Arrays.equals'] | //PrimarySuffix[@Image='equals' and not(../PrimaryPrefix/Literal)]) + /following-sibling::PrimarySuffix/Arguments/ArgumentList/Expression + /PrimaryExpression[count(PrimarySuffix)=0]/PrimaryPrefix + /Name[@Image = ./../../../../../../../../../../Expression/ConditionalAndExpression + /EqualityExpression[@Image="!=" and count(./preceding-sibling::*)=0 and + ./PrimaryExpression/PrimaryPrefix/Literal/NullLiteral] + /PrimaryExpression/PrimaryPrefix/Name/@Image] +``` + +**Example(s):** + +``` java +public class Test { + + public String method1() { return "ok";} + public String method2() { return null;} + + public void method(String a) { + String b; + // I don't know it method1() can be "null" + // but I know "a" is not null.. + // I'd better write a.equals(method1()) + + if (a!=null && method1().equals(a)) { // will trigger the rule + //whatever + } + + if (method1().equals(a) && a != null) { // won't trigger the rule + //whatever + } + + if (a!=null && method1().equals(b)) { // won't trigger the rule + //whatever + } + + if (a!=null && "LITERAL".equals(a)) { // won't trigger the rule + //whatever + } + + if (a!=null && !a.equals("go")) { // won't trigger the rule + a=method2(); + if (method1().equals(a)) { + //whatever + } + } + } +} +``` + +**Use this rule by referencing it:** +``` xml + +``` + +## UseCorrectExceptionLogging + +**Since:** PMD 3.2 + +**Priority:** Medium (3) + +To make sure the full stacktrace is printed out, use the logging statement with two arguments: a String and a Throwable. + +**This rule is defined by the following XPath expression:** +``` xpath +//CatchStatement/Block/BlockStatement/Statement/StatementExpression +/PrimaryExpression[PrimaryPrefix/Name[starts-with(@Image, +concat(ancestor::ClassOrInterfaceDeclaration/ClassOrInterfaceBody/ClassOrInterfaceBodyDeclaration/FieldDeclaration +[Type//ClassOrInterfaceType[@Image='Log']] +/VariableDeclarator/VariableDeclaratorId/@Image, '.'))]] +[PrimarySuffix/Arguments[@ArgumentCount='1']] +[PrimarySuffix/Arguments//Name/@Image = ancestor::CatchStatement/FormalParameter/VariableDeclaratorId/@Image] +``` + +**Example(s):** + +``` java +public class Main { + private static final Log _LOG = LogFactory.getLog( Main.class ); + void bar() { + try { + } catch( Exception e ) { + _LOG.error( e ); //Wrong! + } catch( OtherException oe ) { + _LOG.error( oe.getMessage(), oe ); //Correct + } + } +} +``` + +**Use this rule by referencing it:** +``` xml + +``` + +## UseEqualsToCompareStrings + +**Since:** PMD 4.1 + +**Priority:** Medium (3) + +Using '==' or '!=' to compare strings only works if intern version is used on both sides. +Use the equals() method instead. + +**This rule is defined by the following XPath expression:** +``` xpath +//EqualityExpression/PrimaryExpression +[(PrimaryPrefix/Literal + [starts-with(@Image, '"')] + [ends-with(@Image, '"')] +and count(PrimarySuffix) = 0)] +``` + +**Example(s):** + +``` java +public boolean test(String s) { + if (s == "one") return true; // unreliable + if ("two".equals(s)) return true; // better + return false; +} +``` + +**Use this rule by referencing it:** +``` xml + +``` + +## UselessOperationOnImmutable + +**Since:** PMD 3.5 + +**Priority:** Medium (3) + +An operation on an Immutable object (String, BigDecimal or BigInteger) won't change the object itself +since the result of the operation is a new object. Therefore, ignoring the operation result is an error. + +**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.errorprone.UselessOperationOnImmutableRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/UselessOperationOnImmutableRule.java) + +**Example(s):** + +``` java +import java.math.*; + +class Test { + void method1() { + BigDecimal bd=new BigDecimal(10); + bd.add(new BigDecimal(5)); // this will trigger the rule + } + void method2() { + BigDecimal bd=new BigDecimal(10); + bd = bd.add(new BigDecimal(5)); // this won't trigger the rule + } +} +``` + +**Use this rule by referencing it:** +``` xml + +``` + +## UseLocaleWithCaseConversions + +**Since:** PMD 2.0 + +**Priority:** Medium (3) + +When doing String.toLowerCase()/toUpperCase() conversions, use Locales to avoids problems with languages that +have unusual conventions, i.e. Turkish. + +**This rule is defined by the following XPath expression:** +``` xpath +//PrimaryExpression +[ +PrimaryPrefix +[Name[ends-with(@Image, 'toLowerCase') or ends-with(@Image, 'toUpperCase')]] +[following-sibling::PrimarySuffix[position() = 1]/Arguments[@ArgumentCount=0]] + +or + +PrimarySuffix +[ends-with(@Image, 'toLowerCase') or ends-with(@Image, 'toUpperCase')] +[following-sibling::PrimarySuffix[position() = 1]/Arguments[@ArgumentCount=0]] +] +[not(PrimaryPrefix/Name[ends-with(@Image, 'toHexString')])] +``` + +**Example(s):** + +``` java +class Foo { + // BAD + if (x.toLowerCase().equals("list")) { } + + /* + * This will not match "LIST" when in Turkish locale + * The above could be + * if (x.toLowerCase(Locale.US).equals("list")) { } + * or simply + * if (x.equalsIgnoreCase("list")) { } + */ + // GOOD + String z = a.toLowerCase(Locale.EN); +} +``` + +**Use this rule by referencing it:** +``` xml + +``` + +## UseProperClassLoader + +**Since:** PMD 3.7 + +**Priority:** Medium (3) + +In J2EE, the getClassLoader() method might not work as expected. Use +Thread.currentThread().getContextClassLoader() instead. + +**This rule is defined by the following XPath expression:** +``` xpath +//PrimarySuffix[@Image='getClassLoader'] +``` + +**Example(s):** + +``` java +public class Foo { + ClassLoader cl = Bar.class.getClassLoader(); +} +``` + +**Use this rule by referencing it:** +``` xml + +``` + diff --git a/docs/pages/pmd/rules/java/finalizers.md b/docs/pages/pmd/rules/java/finalizers.md deleted file mode 100644 index 019fe9e0511..00000000000 --- a/docs/pages/pmd/rules/java/finalizers.md +++ /dev/null @@ -1,187 +0,0 @@ ---- -title: Finalizer -summary: These rules deal with different problems that can occur with finalizers. -permalink: pmd_rules_java_finalizers.html -folder: pmd/rules/java -sidebaractiveurl: /pmd_rules_java.html -editmepath: ../pmd-java/src/main/resources/rulesets/java/finalizers.xml -keywords: Finalizer, EmptyFinalizer, FinalizeOnlyCallsSuperFinalize, FinalizeOverloaded, FinalizeDoesNotCallSuperFinalize, FinalizeShouldBeProtected, AvoidCallingFinalize ---- -## AvoidCallingFinalize - -**Since:** PMD 3.0 - -**Priority:** Medium (3) - -The method Object.finalize() is called by the garbage collector on an object when garbage collection determines -that there are no more references to the object. It should not be invoked by application logic. - -**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.finalizers.AvoidCallingFinalizeRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/finalizers/AvoidCallingFinalizeRule.java) - -**Example(s):** - -``` java -void foo() { - Bar b = new Bar(); - b.finalize(); -} -``` - -**Use this rule by referencing it:** -``` xml - -``` - -## EmptyFinalizer - -**Since:** PMD 1.5 - -**Priority:** Medium (3) - -Empty finalize methods serve no purpose and should be removed. - -``` -//MethodDeclaration[MethodDeclarator[@Image='finalize'][not(FormalParameters/*)]] - /Block[count(*)=0] -``` - -**Example(s):** - -``` java -public class Foo { - protected void finalize() {} -} -``` - -**Use this rule by referencing it:** -``` xml - -``` - -## FinalizeDoesNotCallSuperFinalize - -**Since:** PMD 1.5 - -**Priority:** Medium (3) - -If the finalize() is implemented, its last action should be to call super.finalize. - -``` -//MethodDeclaration[MethodDeclarator[@Image='finalize'][not(FormalParameters/*)]] - /Block - /BlockStatement[last()] - [not(Statement/StatementExpression/PrimaryExpression - [./PrimaryPrefix[@SuperModifier='true']] - [./PrimarySuffix[@Image='finalize']] - ) - ] - [not(Statement/TryStatement/FinallyStatement - /Block/BlockStatement/Statement/StatementExpression/PrimaryExpression - [./PrimaryPrefix[@SuperModifier='true']] - [./PrimarySuffix[@Image='finalize']] - ) - ] -``` - -**Example(s):** - -``` java -protected void finalize() { - something(); - // neglected to call super.finalize() -} -``` - -**Use this rule by referencing it:** -``` xml - -``` - -## FinalizeOnlyCallsSuperFinalize - -**Since:** PMD 1.5 - -**Priority:** Medium (3) - -If the finalize() is implemented, it should do something besides just calling super.finalize(). - -``` -//MethodDeclaration[MethodDeclarator[@Image="finalize"][not(FormalParameters/*)]] - /Block[count(BlockStatement)=1] - /BlockStatement[ - Statement/StatementExpression/PrimaryExpression - [./PrimaryPrefix[@SuperModifier='true']] - [./PrimarySuffix[@Image='finalize']] - ] -``` - -**Example(s):** - -``` java -protected void finalize() { - super.finalize(); -} -``` - -**Use this rule by referencing it:** -``` xml - -``` - -## FinalizeOverloaded - -**Since:** PMD 1.5 - -**Priority:** Medium (3) - -Methods named finalize() should not have parameters. It is confusing and most likely an attempt to -overload Object.finalize(). It will not be called by the VM. - -``` -//MethodDeclaration - /MethodDeclarator[@Image='finalize'][FormalParameters[count(*)>0]] -``` - -**Example(s):** - -``` java -public class Foo { - // this is confusing and probably a bug - protected void finalize(int a) { - } -} -``` - -**Use this rule by referencing it:** -``` xml - -``` - -## FinalizeShouldBeProtected - -**Since:** PMD 1.1 - -**Priority:** Medium (3) - -When overriding the finalize(), the new method should be set as protected. If made public, -other classes may invoke it at inappropriate times. - -``` -//MethodDeclaration[@Protected="false"] - /MethodDeclarator[@Image="finalize"] - [not(FormalParameters/*)] -``` - -**Example(s):** - -``` java -public void finalize() { - // do something -} -``` - -**Use this rule by referencing it:** -``` xml - -``` - diff --git a/docs/pages/pmd/rules/java/imports.md b/docs/pages/pmd/rules/java/imports.md deleted file mode 100644 index d9fb36d2ef2..00000000000 --- a/docs/pages/pmd/rules/java/imports.md +++ /dev/null @@ -1,173 +0,0 @@ ---- -title: Import Statements -summary: These rules deal with different problems that can occur with import statements. -permalink: pmd_rules_java_imports.html -folder: pmd/rules/java -sidebaractiveurl: /pmd_rules_java.html -editmepath: ../pmd-java/src/main/resources/rulesets/java/imports.xml -keywords: Import Statements, DuplicateImports, DontImportJavaLang, UnusedImports, ImportFromSamePackage, TooManyStaticImports, UnnecessaryFullyQualifiedName ---- -## DontImportJavaLang - -**Since:** PMD 0.5 - -**Priority:** Medium Low (4) - -Avoid importing anything from the package 'java.lang'. These classes are automatically imported (JLS 7.5.3). - -**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.imports.DontImportJavaLangRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/imports/DontImportJavaLangRule.java) - -**Example(s):** - -``` java -import java.lang.String; // this is unnecessary - -public class Foo {} - -// --- in another source code file... - -import java.lang.*; // this is bad - -public class Foo {} -``` - -**Use this rule by referencing it:** -``` xml - -``` - -## DuplicateImports - -**Since:** PMD 0.5 - -**Priority:** Medium Low (4) - -Duplicate or overlapping import statements should be avoided. - -**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.imports.DuplicateImportsRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/imports/DuplicateImportsRule.java) - -**Example(s):** - -``` java -import java.lang.String; -import java.lang.*; -public class Foo {} -``` - -**Use this rule by referencing it:** -``` xml - -``` - -## ImportFromSamePackage - -**Since:** PMD 1.02 - -**Priority:** Medium (3) - -There is no need to import a type that lives in the same package. - -**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.imports.ImportFromSamePackageRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/imports/ImportFromSamePackageRule.java) - -**Example(s):** - -``` java -package foo; - -import foo.Buz; // no need for this -import foo.*; // or this - -public class Bar{} -``` - -**Use this rule by referencing it:** -``` xml - -``` - -## TooManyStaticImports - -**Since:** PMD 4.1 - -**Priority:** Medium (3) - -If you overuse the static import feature, it can make your program unreadable and -unmaintainable, polluting its namespace with all the static members you import. -Readers of your code (including you, a few months after you wrote it) will not know -which class a static member comes from (Sun 1.5 Language Guide). - -``` -.[count(ImportDeclaration[@Static = 'true']) > $maximumStaticImports] -``` - -**Example(s):** - -``` java -import static Lennon; -import static Ringo; -import static George; -import static Paul; -import static Yoko; // Too much ! -``` - -**This rule has the following properties:** - -|Name|Default Value|Description| -|----|-------------|-----------| -|maximumStaticImports|4|All static imports can be disallowed by setting this to 0| - -**Use this rule by referencing it:** -``` xml - -``` - -## UnnecessaryFullyQualifiedName - -**Since:** PMD 5.0 - -**Priority:** Medium Low (4) - -Import statements allow the use of non-fully qualified names. The use of a fully qualified name -which is covered by an import statement is redundant. Consider using the non-fully qualified name. - -**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.imports.UnnecessaryFullyQualifiedNameRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/imports/UnnecessaryFullyQualifiedNameRule.java) - -**Example(s):** - -``` java -import java.util.List; - -public class Foo { - private java.util.List list1; // Unnecessary FQN - private List list2; // More appropriate given import of 'java.util.List' -} -``` - -**Use this rule by referencing it:** -``` xml - -``` - -## UnusedImports - -**Since:** PMD 1.0 - -**Priority:** Medium Low (4) - -Avoid the use of unused import statements to prevent unwanted dependencies. - -**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.imports.UnusedImportsRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/imports/UnusedImportsRule.java) - -**Example(s):** - -``` java -// this is bad -import java.io.File; -public class Foo {} -``` - -**Use this rule by referencing it:** -``` xml - -``` - diff --git a/docs/pages/pmd/rules/java/j2ee.md b/docs/pages/pmd/rules/java/j2ee.md deleted file mode 100644 index b9a697099f6..00000000000 --- a/docs/pages/pmd/rules/java/j2ee.md +++ /dev/null @@ -1,335 +0,0 @@ ---- -title: J2EE -summary: Rules specific to the use of J2EE implementations. -permalink: pmd_rules_java_j2ee.html -folder: pmd/rules/java -sidebaractiveurl: /pmd_rules_java.html -editmepath: ../pmd-java/src/main/resources/rulesets/java/j2ee.xml -keywords: J2EE, UseProperClassLoader, MDBAndSessionBeanNamingConvention, RemoteSessionInterfaceNamingConvention, LocalInterfaceSessionNamingConvention, LocalHomeNamingConvention, RemoteInterfaceNamingConvention, DoNotCallSystemExit, StaticEJBFieldShouldBeFinal, DoNotUseThreads ---- -## DoNotCallSystemExit - -**Since:** PMD 4.1 - -**Priority:** Medium (3) - -Web applications should not call System.exit(), since only the web container or the -application server should stop the JVM. This rule also checks for the equivalent call Runtime.getRuntime().exit(). - -``` -//Name[ - starts-with(@Image,'System.exit') - or - (starts-with(@Image,'Runtime.getRuntime') and ../../PrimarySuffix[ends-with(@Image,'exit')]) -] -``` - -**Example(s):** - -``` java -public void bar() { - System.exit(0); // never call this when running in an application server! -} -public void foo() { - Runtime.getRuntime().exit(0); // never stop the JVM manually, the container will do this. -} -``` - -**Use this rule by referencing it:** -``` xml - -``` - -## DoNotUseThreads - -**Since:** PMD 4.1 - -**Priority:** Medium (3) - -The J2EE specification explicitly forbids the use of threads. - -``` -//ClassOrInterfaceType[@Image = 'Thread' or @Image = 'Runnable'] -``` - -**Example(s):** - -``` java -// This is not allowed -public class UsingThread extends Thread { - -} - -// Neither this, -public class OtherThread implements Runnable { - // Nor this ... - public void methode() { - Runnable thread = new Thread(); thread.run(); - } -} -``` - -**Use this rule by referencing it:** -``` xml - -``` - -## LocalHomeNamingConvention - -**Since:** PMD 4.0 - -**Priority:** Medium Low (4) - -The Local Home interface of a Session EJB should be suffixed by 'LocalHome'. - -``` -//ClassOrInterfaceDeclaration -[ - ( - (./ExtendsList/ClassOrInterfaceType[ends-with(@Image,'EJBLocalHome')]) - ) - and - not - ( - ends-with(@Image,'LocalHome') - ) -] -``` - -**Example(s):** - -``` java -public interface MyBeautifulLocalHome extends javax.ejb.EJBLocalHome {} // proper name - -public interface MissingProperSuffix extends javax.ejb.EJBLocalHome {} // non-standard name -``` - -**Use this rule by referencing it:** -``` xml - -``` - -## LocalInterfaceSessionNamingConvention - -**Since:** PMD 4.0 - -**Priority:** Medium Low (4) - -The Local Interface of a Session EJB should be suffixed by 'Local'. - -``` -//ClassOrInterfaceDeclaration -[ - ( - (./ExtendsList/ClassOrInterfaceType[ends-with(@Image,'EJBLocalObject')]) - ) - and - not - ( - ends-with(@Image,'Local') - ) -] -``` - -**Example(s):** - -``` java -public interface MyLocal extends javax.ejb.EJBLocalObject {} // proper name - -public interface MissingProperSuffix extends javax.ejb.EJBLocalObject {} // non-standard name -``` - -**Use this rule by referencing it:** -``` xml - -``` - -## MDBAndSessionBeanNamingConvention - -**Since:** PMD 4.0 - -**Priority:** Medium Low (4) - -The EJB Specification states that any MessageDrivenBean or SessionBean should be suffixed by 'Bean'. - -``` -//TypeDeclaration/ClassOrInterfaceDeclaration -[ - ( - (./ImplementsList/ClassOrInterfaceType[ends-with(@Image,'SessionBean')]) - or - (./ImplementsList/ClassOrInterfaceType[ends-with(@Image,'MessageDrivenBean')]) - ) - and - not - ( - ends-with(@Image,'Bean') - ) -] -``` - -**Example(s):** - -``` java -public class SomeBean implements SessionBean{} // proper name - -public class MissingTheProperSuffix implements SessionBean {} // non-standard name -``` - -**Use this rule by referencing it:** -``` xml - -``` - -## RemoteInterfaceNamingConvention - -**Since:** PMD 4.0 - -**Priority:** Medium Low (4) - -Remote Interface of a Session EJB should not have a suffix. - -``` -//ClassOrInterfaceDeclaration -[ - ( - (./ExtendsList/ClassOrInterfaceType[ends-with(@Image,'EJBObject')]) - ) - and - ( - ends-with(@Image,'Session') - or - ends-with(@Image,'EJB') - or - ends-with(@Image,'Bean') - ) -] -``` - -**Example(s):** - -``` java -/* Poor Session suffix */ -public interface BadSuffixSession extends javax.ejb.EJBObject {} - -/* Poor EJB suffix */ -public interface BadSuffixEJB extends javax.ejb.EJBObject {} - -/* Poor Bean suffix */ -public interface BadSuffixBean extends javax.ejb.EJBObject {} -``` - -**Use this rule by referencing it:** -``` xml - -``` - -## RemoteSessionInterfaceNamingConvention - -**Since:** PMD 4.0 - -**Priority:** Medium Low (4) - -A Remote Home interface type of a Session EJB should be suffixed by 'Home'. - -``` -//ClassOrInterfaceDeclaration -[ - ( - (./ExtendsList/ClassOrInterfaceType[ends-with(@Image,'EJBHome')]) - ) - and - not - ( - ends-with(@Image,'Home') - ) -] -``` - -**Example(s):** - -``` java -public interface MyBeautifulHome extends javax.ejb.EJBHome {} // proper name - -public interface MissingProperSuffix extends javax.ejb.EJBHome {} // non-standard name -``` - -**Use this rule by referencing it:** -``` xml - -``` - -## StaticEJBFieldShouldBeFinal - -**Since:** PMD 4.1 - -**Priority:** Medium (3) - -According to the J2EE specification, an EJB should not have any static fields -with write access. However, static read-only fields are allowed. This ensures proper -behavior especially when instances are distributed by the container on several JREs. - -``` -//ClassOrInterfaceDeclaration[ - ( - (./ImplementsList/ClassOrInterfaceType[ends-with(@Image,'SessionBean')]) - or - (./ImplementsList/ClassOrInterfaceType[ends-with(@Image,'EJBHome')]) - or - (./ImplementsList/ClassOrInterfaceType[ends-with(@Image,'EJBLocalObject')]) - or - (./ImplementsList/ClassOrInterfaceType[ends-with(@Image,'EJBLocalHome')]) - or - (./ExtendsList/ClassOrInterfaceType[ends-with(@Image,'EJBObject')]) - ) - and - (./ClassOrInterfaceBody/ClassOrInterfaceBodyDeclaration[ - (./FieldDeclaration[@Static = 'true']) - and - (./FieldDeclaration[@Final = 'false']) - ]) -] -``` - -**Example(s):** - -``` java -public class SomeEJB extends EJBObject implements EJBLocalHome { - - private static int CountA; // poor, field can be edited - - private static final int CountB; // preferred, read-only access -} -``` - -**Use this rule by referencing it:** -``` xml - -``` - -## UseProperClassLoader - -**Since:** PMD 3.7 - -**Priority:** Medium (3) - -In J2EE, the getClassLoader() method might not work as expected. Use -Thread.currentThread().getContextClassLoader() instead. - -``` -//PrimarySuffix[@Image='getClassLoader'] -``` - -**Example(s):** - -``` java -public class Foo { - ClassLoader cl = Bar.class.getClassLoader(); -} -``` - -**Use this rule by referencing it:** -``` xml - -``` - diff --git a/docs/pages/pmd/rules/java/javabeans.md b/docs/pages/pmd/rules/java/javabeans.md deleted file mode 100644 index 8a8a05afd6e..00000000000 --- a/docs/pages/pmd/rules/java/javabeans.md +++ /dev/null @@ -1,87 +0,0 @@ ---- -title: JavaBeans -summary: The JavaBeans Ruleset catches instances of bean rules not being followed. -permalink: pmd_rules_java_javabeans.html -folder: pmd/rules/java -sidebaractiveurl: /pmd_rules_java.html -editmepath: ../pmd-java/src/main/resources/rulesets/java/javabeans.xml -keywords: JavaBeans, BeanMembersShouldSerialize, MissingSerialVersionUID ---- -## BeanMembersShouldSerialize - -**Since:** PMD 1.1 - -**Priority:** Medium (3) - -If a class is a bean, or is referenced by a bean directly or indirectly it needs to be serializable. -Member variables need to be marked as transient, static, or have accessor methods in the class. Marking -variables as transient is the safest and easiest modification. Accessor methods should follow the Java -naming conventions, i.e. for a variable named foo, getFoo() and setFoo() accessor methods should be provided. - -**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.javabeans.BeanMembersShouldSerializeRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/javabeans/BeanMembersShouldSerializeRule.java) - -**Example(s):** - -``` java -private transient int someFoo; // good, it's transient -private static int otherFoo; // also OK -private int moreFoo; // OK, has proper accessors, see below -private int badFoo; // bad, should be marked transient - -private void setMoreFoo(int moreFoo){ - this.moreFoo = moreFoo; -} - -private int getMoreFoo(){ - return this.moreFoo; -} -``` - -**This rule has the following properties:** - -|Name|Default Value|Description| -|----|-------------|-----------| -|prefix||A variable prefix to skip, i.e., m_| - -**Use this rule by referencing it:** -``` xml - -``` - -## MissingSerialVersionUID - -**Since:** PMD 3.0 - -**Priority:** Medium (3) - -Serializable classes should provide a serialVersionUID field. - -``` -//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' -] -``` - -**Example(s):** - -``` java -public class Foo implements java.io.Serializable { - String name; - // Define serialization id to avoid serialization related bugs - // i.e., public static final long serialVersionUID = 4328743; -} -``` - -**Use this rule by referencing it:** -``` xml - -``` - diff --git a/docs/pages/pmd/rules/java/junit.md b/docs/pages/pmd/rules/java/junit.md deleted file mode 100644 index b3d9bc0f408..00000000000 --- a/docs/pages/pmd/rules/java/junit.md +++ /dev/null @@ -1,442 +0,0 @@ ---- -title: JUnit -summary: These rules deal with different problems that can occur with JUnit tests. -permalink: pmd_rules_java_junit.html -folder: pmd/rules/java -sidebaractiveurl: /pmd_rules_java.html -editmepath: ../pmd-java/src/main/resources/rulesets/java/junit.xml -keywords: JUnit, JUnitStaticSuite, JUnitSpelling, JUnitAssertionsShouldIncludeMessage, JUnitTestsShouldIncludeAssert, TestClassWithoutTestCases, UnnecessaryBooleanAssertion, UseAssertEqualsInsteadOfAssertTrue, UseAssertSameInsteadOfAssertTrue, UseAssertNullInsteadOfAssertTrue, SimplifyBooleanAssertion, JUnitTestContainsTooManyAsserts, UseAssertTrueInsteadOfAssertEquals ---- -## JUnitAssertionsShouldIncludeMessage - -**Since:** PMD 1.04 - -**Priority:** Medium (3) - -JUnit assertions should include an informative message - i.e., use the three-argument version of -assertEquals(), not the two-argument version. - -**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.junit.JUnitAssertionsShouldIncludeMessageRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/junit/JUnitAssertionsShouldIncludeMessageRule.java) - -**Example(s):** - -``` java -public class Foo extends TestCase { - public void testSomething() { - assertEquals("foo", "bar"); - // Use the form: - // assertEquals("Foo does not equals bar", "foo", "bar"); - // instead - } -} -``` - -**Use this rule by referencing it:** -``` xml - -``` - -## JUnitSpelling - -**Since:** PMD 1.0 - -**Priority:** Medium (3) - -Some JUnit framework methods are easy to misspell. - -``` -//MethodDeclarator[(not(@Image = 'setUp') - and translate(@Image, 'SETuP', 'setUp') = 'setUp') - or (not(@Image = 'tearDown') - and translate(@Image, 'TEARdOWN', 'tearDown') = 'tearDown')] - [FormalParameters[count(*) = 0]] -[ancestor::ClassOrInterfaceDeclaration[//ClassOrInterfaceType[pmd-java:typeof(@Image, 'junit.framework.TestCase','TestCase')] or //MarkerAnnotation/Name[pmd-java:typeof(@Image, 'org.junit.Test', 'Test')]]] -``` - -**Example(s):** - -``` java -import junit.framework.*; - -public class Foo extends TestCase { - public void setup() {} // oops, should be setUp - public void TearDown() {} // oops, should be tearDown -} -``` - -**Use this rule by referencing it:** -``` xml - -``` - -## JUnitStaticSuite - -**Since:** PMD 1.0 - -**Priority:** Medium (3) - -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:typeof(@Image, 'junit.framework.TestCase','TestCase')] or //MarkerAnnotation/Name[pmd-java:typeof(@Image, 'org.junit.Test', 'Test')]]] -``` - -**Example(s):** - -``` java -import junit.framework.*; - -public class Foo extends TestCase { - public void suite() {} // oops, should be static - private static void suite() {} // oops, should be public -} -``` - -**Use this rule by referencing it:** -``` xml - -``` - -## JUnitTestContainsTooManyAsserts - -**Since:** PMD 5.0 - -**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. -Customize the maximum number of assertions used by this Rule to suit your needs. - -``` -//MethodDeclarator[(@Image[fn:matches(.,'^test')] or ../../Annotation/MarkerAnnotation/Name[@Image='Test']) and count(..//PrimaryPrefix/Name[@Image[fn:matches(.,'^assert')]]) > $maximumAsserts] -``` - -**Example(s):** - -``` java -public class MyTestCase extends TestCase { - // Ok - public void testMyCaseWithOneAssert() { - boolean myVar = false; - assertFalse("should be false", myVar); - } - - // Bad, too many asserts (assuming max=1) - public void testMyCaseWithMoreAsserts() { - boolean myVar = false; - assertFalse("myVar should be false", myVar); - assertEquals("should equals false", false, myVar); - } -} -``` - -**This rule has the following properties:** - -|Name|Default Value|Description| -|----|-------------|-----------| -|maximumAsserts|1|Maximum number of Asserts in a test method| - -**Use this rule by referencing it:** -``` xml - -``` - -## JUnitTestsShouldIncludeAssert - -**Since:** PMD 2.0 - -**Priority:** Medium (3) - -JUnit tests should include at least one assertion. This makes the tests more robust, and using assert -with messages provide the developer a clearer idea of what the test does. - -**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.junit.JUnitTestsShouldIncludeAssertRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/junit/JUnitTestsShouldIncludeAssertRule.java) - -**Example(s):** - -``` java -public class Foo extends TestCase { - public void testSomething() { - Bar b = findBar(); - // This is better than having a NullPointerException - // assertNotNull("bar not found", b); - b.work(); - } -} -``` - -**Use this rule by referencing it:** -``` xml - -``` - -## SimplifyBooleanAssertion - -**Since:** PMD 3.6 - -**Priority:** Medium (3) - -Avoid negation in an assertTrue or assertFalse test. - -For example, rephrase: - - assertTrue(!expr); - -as: - - assertFalse(expr); - -``` -//StatementExpression -[ -.//Name[@Image='assertTrue' or @Image='assertFalse'] -and -PrimaryExpression/PrimarySuffix/Arguments/ArgumentList - /Expression/UnaryExpressionNotPlusMinus[@Image='!'] -/PrimaryExpression/PrimaryPrefix -] -[ancestor::ClassOrInterfaceDeclaration[//ClassOrInterfaceType[pmd-java:typeof(@Image, 'junit.framework.TestCase','TestCase')] or //MarkerAnnotation/Name[pmd-java:typeof(@Image, 'org.junit.Test', 'Test')]]] -``` - -**Example(s):** - -``` java -public class SimpleTest extends TestCase { - public void testX() { - assertTrue("not empty", !r.isEmpty()); // replace with assertFalse("not empty", r.isEmpty()) - assertFalse(!r.isEmpty()); // replace with assertTrue(r.isEmpty()) - } -} -``` - -**Use this rule by referencing it:** -``` xml - -``` - -## TestClassWithoutTestCases - -**Since:** PMD 3.0 - -**Priority:** Medium (3) - -Test classes end with the suffix Test. Having a non-test class with that name is not a good practice, -since most people will assume it is a test case. Test classes have test methods named testXXX. - -**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.junit.TestClassWithoutTestCasesRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/junit/TestClassWithoutTestCasesRule.java) - -**Example(s):** - -``` java -//Consider changing the name of the class if it is not a test -//Consider adding test methods if it is a test -public class CarTest { - public static void main(String[] args) { - // do something - } - // code -} -``` - -**Use this rule by referencing it:** -``` xml - -``` - -## UnnecessaryBooleanAssertion - -**Since:** PMD 3.0 - -**Priority:** Medium (3) - -A JUnit test assertion with a boolean literal is unnecessary since it always will evaluate to the same thing. -Consider using flow control (in case of assertTrue(false) or similar) or simply removing -statements like assertTrue(true) and assertFalse(false). If you just want a test to halt after finding -an error, use the fail() method and provide an indication message of why it did. - -``` -//StatementExpression -[ -PrimaryExpression/PrimaryPrefix/Name[@Image='assertTrue' or @Image='assertFalse'] -and -PrimaryExpression/PrimarySuffix/Arguments/ArgumentList/Expression -[PrimaryExpression/PrimaryPrefix/Literal/BooleanLiteral -or -UnaryExpressionNotPlusMinus[@Image='!'] -/PrimaryExpression/PrimaryPrefix[Literal/BooleanLiteral or Name[count(../../*)=1]]] -] -[ancestor::ClassOrInterfaceDeclaration[//ClassOrInterfaceType[pmd-java:typeof(@Image, 'junit.framework.TestCase','TestCase')] or //MarkerAnnotation/Name[pmd-java:typeof(@Image, 'org.junit.Test', 'Test')]]] -``` - -**Example(s):** - -``` java -public class SimpleTest extends TestCase { - public void testX() { - assertTrue(true); // serves no real purpose - } -} -``` - -**Use this rule by referencing it:** -``` xml - -``` - -## UseAssertEqualsInsteadOfAssertTrue - -**Since:** PMD 3.1 - -**Priority:** Medium (3) - -This rule detects JUnit assertions in object equality. These assertions should be made by more specific methods, like assertEquals. - -``` -//PrimaryExpression[ - PrimaryPrefix/Name[@Image = 'assertTrue'] -][ - PrimarySuffix/Arguments/ArgumentList/Expression/PrimaryExpression/PrimaryPrefix/Name - [ends-with(@Image, '.equals')] -] -[ancestor::ClassOrInterfaceDeclaration[//ClassOrInterfaceType[pmd-java:typeof(@Image, 'junit.framework.TestCase','TestCase')] or //MarkerAnnotation/Name[pmd-java:typeof(@Image, 'org.junit.Test', 'Test')]]] -``` - -**Example(s):** - -``` java -public class FooTest extends TestCase { - void testCode() { - Object a, b; - assertTrue(a.equals(b)); // bad usage - assertEquals(?a should equals b?, a, b); // good usage - } -} -``` - -**Use this rule by referencing it:** -``` xml - -``` - -## UseAssertNullInsteadOfAssertTrue - -**Since:** PMD 3.5 - -**Priority:** Medium (3) - -This rule detects JUnit assertions in object references equality. These assertions should be made by -more specific methods, like assertNull, assertNotNull. - -``` -//PrimaryExpression[ - PrimaryPrefix/Name[@Image = 'assertTrue' or @Image = 'assertFalse'] -][ - PrimarySuffix/Arguments/ArgumentList[ - Expression/EqualityExpression/PrimaryExpression/PrimaryPrefix/Literal/NullLiteral - ] -] -[ancestor::ClassOrInterfaceDeclaration[//ClassOrInterfaceType[pmd-java:typeof(@Image, 'junit.framework.TestCase','TestCase')] or //MarkerAnnotation/Name[pmd-java:typeof(@Image, 'org.junit.Test', 'Test')]]] -``` - -**Example(s):** - -``` java -public class FooTest extends TestCase { - void testCode() { - Object a = doSomething(); - assertTrue(a==null); // bad usage - assertNull(a); // good usage - assertTrue(a != null); // bad usage - assertNotNull(a); // good usage - } -} -``` - -**Use this rule by referencing it:** -``` xml - -``` - -## UseAssertSameInsteadOfAssertTrue - -**Since:** PMD 3.1 - -**Priority:** Medium (3) - -This rule detects JUnit assertions in object references equality. These assertions should be made -by more specific methods, like assertSame, assertNotSame. - -``` -//PrimaryExpression[ - PrimaryPrefix/Name - [@Image = 'assertTrue' or @Image = 'assertFalse'] -] -[PrimarySuffix/Arguments - /ArgumentList/Expression - /EqualityExpression[count(.//NullLiteral) = 0]] -[ancestor::ClassOrInterfaceDeclaration[//ClassOrInterfaceType[pmd-java:typeof(@Image, 'junit.framework.TestCase','TestCase')] or //MarkerAnnotation/Name[pmd-java:typeof(@Image, 'org.junit.Test', 'Test')]]] -``` - -**Example(s):** - -``` java -public class FooTest extends TestCase { - void testCode() { - Object a, b; - assertTrue(a == b); // bad usage - assertSame(a, b); // good usage - } -} -``` - -**Use this rule by referencing it:** -``` xml - -``` - -## UseAssertTrueInsteadOfAssertEquals - -**Since:** PMD 5.0 - -**Priority:** Medium (3) - -When asserting a value is the same as a literal or Boxed boolean, use assertTrue/assertFalse, instead of assertEquals. - -``` -//PrimaryExpression[PrimaryPrefix/Name[@Image = 'assertEquals']] -[ - PrimarySuffix/Arguments/ArgumentList/Expression/PrimaryExpression/PrimaryPrefix/Literal/BooleanLiteral - or - PrimarySuffix/Arguments/ArgumentList/Expression/PrimaryExpression/PrimaryPrefix - /Name[(@Image = 'Boolean.TRUE' or @Image = 'Boolean.FALSE')] -] -``` - -**Example(s):** - -``` java -public class MyTestCase extends TestCase { - public void testMyCase() { - boolean myVar = true; - // Ok - assertTrue("myVar is true", myVar); - // Bad - assertEquals("myVar is true", true, myVar); - // Bad - assertEquals("myVar is false", false, myVar); - // Bad - assertEquals("myVar is true", Boolean.TRUE, myVar); - // Bad - assertEquals("myVar is false", Boolean.FALSE, myVar); - } -} -``` - -**Use this rule by referencing it:** -``` xml - -``` - diff --git a/docs/pages/pmd/rules/java/logging-jakarta-commons.md b/docs/pages/pmd/rules/java/logging-jakarta-commons.md deleted file mode 100644 index 6e3473fe19c..00000000000 --- a/docs/pages/pmd/rules/java/logging-jakarta-commons.md +++ /dev/null @@ -1,172 +0,0 @@ ---- -title: Jakarta Commons Logging -summary: The Jakarta Commons Logging ruleset contains a collection of rules that find questionable usages of that framework. -permalink: pmd_rules_java_logging-jakarta-commons.html -folder: pmd/rules/java -sidebaractiveurl: /pmd_rules_java.html -editmepath: ../pmd-java/src/main/resources/rulesets/java/logging-jakarta-commons.xml -keywords: Jakarta Commons Logging, UseCorrectExceptionLogging, ProperLogger, GuardDebugLogging, GuardLogStatement ---- -## GuardDebugLogging - -**Since:** PMD 4.3 - -**Priority:** Medium (3) - -When log messages are composed by concatenating strings, the whole section should be guarded -by a isDebugEnabled() check to avoid performance and memory issues. - -**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.logging.GuardDebugLoggingRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/logging/GuardDebugLoggingRule.java) - -**Example(s):** - -``` java -public class Test { - private static final Log __log = LogFactory.getLog(Test.class); - public void test() { - // okay: - __log.debug("log something"); - - // okay: - __log.debug("log something with exception", e); - - // bad: - __log.debug("log something" + " and " + "concat strings"); - - // bad: - __log.debug("log something" + " and " + "concat strings", e); - - // good: - if (__log.isDebugEnabled()) { - __log.debug("bla" + "",e ); - } - } -} -``` - -**This rule has the following properties:** - -|Name|Default Value|Description| -|----|-------------|-----------| -|guardsMethods|[]|method use to guard the log statement| -|logLevels|[]|LogLevels to guard| - -**Use this rule by referencing it:** -``` xml - -``` - -## GuardLogStatement - -**Since:** PMD 5.1.0 - -**Priority:** Medium High (2) - -Whenever using a log level, one should check if the loglevel is actually enabled, or -otherwise skip the associate String creation and manipulation. - -**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.logging.GuardLogStatementRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/logging/GuardLogStatementRule.java) - -**Example(s):** - -``` java -// Add this for performance - if (log.isDebugEnabled() { ... - log.debug("log something" + " and " + "concat strings"); -``` - -**This rule has the following properties:** - -|Name|Default Value|Description| -|----|-------------|-----------| -|guardsMethods|[]|method use to guard the log statement| -|logLevels|[]|LogLevels to guard| - -**Use this rule by referencing it:** -``` xml - -``` - -## ProperLogger - -**Since:** PMD 3.3 - -**Priority:** Medium (3) - -A logger should normally be defined private static final and be associated with the correct class. -Private final Log log; is also allowed for rare cases where loggers need to be passed around, -with the restriction that the logger needs to be passed into the constructor. - -``` -//ClassOrInterfaceBodyDeclaration[FieldDeclaration//ClassOrInterfaceType[@Image='Log'] - and - not(FieldDeclaration[@Final='true'][@Static='true'][@Private='true'][.//VariableDeclaratorId[@Image=$staticLoggerName]] - //ArgumentList//ClassOrInterfaceType/@Image = ancestor::ClassOrInterfaceDeclaration/@Image) - and - not(FieldDeclaration[@Final='true'][@Private='true'][.//VariableDeclaratorId[@Image='log']] - [count(.//VariableInitializer)=0] - [ancestor::ClassOrInterfaceBody//StatementExpression[.//PrimaryExpression/descendant::*[@Image='log']][count(.//AllocationExpression)=0]] - )] -``` - -**Example(s):** - -``` java -public class Foo { - - private static final Log LOG = LogFactory.getLog(Foo.class); // proper way - - protected Log LOG = LogFactory.getLog(Testclass.class); // wrong approach -} -``` - -**This rule has the following properties:** - -|Name|Default Value|Description| -|----|-------------|-----------| -|staticLoggerName|LOG|Name of the static Logger variable| - -**Use this rule by referencing it:** -``` xml - -``` - -## UseCorrectExceptionLogging - -**Since:** PMD 3.2 - -**Priority:** Medium (3) - -To make sure the full stacktrace is printed out, use the logging statement with two arguments: a String and a Throwable. - -``` -//CatchStatement/Block/BlockStatement/Statement/StatementExpression -/PrimaryExpression[PrimaryPrefix/Name[starts-with(@Image, -concat(ancestor::ClassOrInterfaceDeclaration/ClassOrInterfaceBody/ClassOrInterfaceBodyDeclaration/FieldDeclaration -[Type//ClassOrInterfaceType[@Image='Log']] -/VariableDeclarator/VariableDeclaratorId/@Image, '.'))]] -[PrimarySuffix/Arguments[@ArgumentCount='1']] -[PrimarySuffix/Arguments//Name/@Image = ancestor::CatchStatement/FormalParameter/VariableDeclaratorId/@Image] -``` - -**Example(s):** - -``` java -public class Main { - private static final Log _LOG = LogFactory.getLog( Main.class ); - void bar() { - try { - } catch( Exception e ) { - _LOG.error( e ); //Wrong! - } catch( OtherException oe ) { - _LOG.error( oe.getMessage(), oe ); //Correct - } - } -} -``` - -**Use this rule by referencing it:** -``` xml - -``` - diff --git a/docs/pages/pmd/rules/java/logging-java.md b/docs/pages/pmd/rules/java/logging-java.md deleted file mode 100644 index 20d350c430a..00000000000 --- a/docs/pages/pmd/rules/java/logging-java.md +++ /dev/null @@ -1,192 +0,0 @@ ---- -title: Java Logging -summary: The Java Logging ruleset contains a collection of rules that find questionable usages of the logger. -permalink: pmd_rules_java_logging-java.html -folder: pmd/rules/java -sidebaractiveurl: /pmd_rules_java.html -editmepath: ../pmd-java/src/main/resources/rulesets/java/logging-java.xml -keywords: Java Logging, MoreThanOneLogger, LoggerIsNotStaticFinal, SystemPrintln, AvoidPrintStackTrace, GuardLogStatementJavaUtil, InvalidSlf4jMessageFormat ---- -## AvoidPrintStackTrace - -**Since:** PMD 3.2 - -**Priority:** Medium (3) - -Avoid printStackTrace(); use a logger call instead. - -``` -//PrimaryExpression - [PrimaryPrefix/Name[contains(@Image,'printStackTrace')]] - [PrimarySuffix[not(boolean(Arguments/ArgumentList/Expression))]] -``` - -**Example(s):** - -``` java -class Foo { - void bar() { - try { - // do something - } catch (Exception e) { - e.printStackTrace(); - } - } -} -``` - -**Use this rule by referencing it:** -``` xml - -``` - -## GuardLogStatementJavaUtil - -**Since:** PMD 5.1.0 - -**Priority:** Medium High (2) - -Whenever using a log level, one should check if the loglevel is actually enabled, or -otherwise skip the associate String creation and manipulation. - -**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.logging.GuardLogStatementJavaUtilRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/logging/GuardLogStatementJavaUtilRule.java) - -**Example(s):** - -``` java -//... -// Add this for performance -if (log.isLoggable(Level.FINE)) { - log.fine("log something" + " and " + "concat strings"); -} -``` - -**This rule has the following properties:** - -|Name|Default Value|Description| -|----|-------------|-----------| -|guardsMethods|[]|method use to guard the log statement| -|logLevels|[]|LogLevels to guard| - -**Use this rule by referencing it:** -``` xml - -``` - -## InvalidSlf4jMessageFormat - -**Since:** PMD 5.5.0 - -**Priority:** Low (5) - -Check for messages in slf4j loggers with non matching number of arguments and placeholders. - -**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.logging.InvalidSlf4jMessageFormatRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/logging/InvalidSlf4jMessageFormatRule.java) - -**Example(s):** - -``` java -LOGGER.error("forget the arg {}"); -LOGGER.error("too many args {}", "arg1", "arg2"); -LOGGER.error("param {}", "arg1", new IllegalStateException("arg")); //The exception is shown separately, so is correct. -``` - -**Use this rule by referencing it:** -``` xml - -``` - -## LoggerIsNotStaticFinal - -**Since:** PMD 2.0 - -**Priority:** Medium High (2) - -In most cases, the Logger reference can be declared as static and final. - -``` -//VariableDeclarator - [parent::FieldDeclaration] - [../Type/ReferenceType - /ClassOrInterfaceType[@Image='Logger'] - and - (..[@Final='false'] or ..[@Static = 'false'] ) ] -``` - -**Example(s):** - -``` java -public class Foo{ - Logger log = Logger.getLogger(Foo.class.getName()); // not recommended - - static final Logger log = Logger.getLogger(Foo.class.getName()); // preferred approach -} -``` - -**Use this rule by referencing it:** -``` xml - -``` - -## MoreThanOneLogger - -**Since:** PMD 2.0 - -**Priority:** Medium High (2) - -Normally only one logger is used in each class. - -**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.logging.MoreThanOneLoggerRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/logging/MoreThanOneLoggerRule.java) - -**Example(s):** - -``` java -public class Foo { - Logger log = Logger.getLogger(Foo.class.getName()); - // It is very rare to see two loggers on a class, normally - // log information is multiplexed by levels - Logger log2= Logger.getLogger(Foo.class.getName()); -} -``` - -**Use this rule by referencing it:** -``` xml - -``` - -## SystemPrintln - -**Since:** PMD 2.1 - -**Priority:** Medium High (2) - -References to System.(out|err).print are usually intended for debugging purposes and can remain in -the codebase even in production code. By using a logger one can enable/disable this behaviour at -will (and by priority) and avoid clogging the Standard out log. - -``` -//Name[ - starts-with(@Image, 'System.out.print') - or - starts-with(@Image, 'System.err.print') - ] -``` - -**Example(s):** - -``` java -class Foo{ - Logger log = Logger.getLogger(Foo.class.getName()); - public void testA () { - System.out.println("Entering test"); - // Better use this - log.fine("Entering test"); - } -} -``` - -**Use this rule by referencing it:** -``` xml - -``` - diff --git a/docs/pages/pmd/rules/java/migrating.md b/docs/pages/pmd/rules/java/migrating.md deleted file mode 100644 index ae8f19c8499..00000000000 --- a/docs/pages/pmd/rules/java/migrating.md +++ /dev/null @@ -1,478 +0,0 @@ ---- -title: Migration -summary: Contains rules about migrating from one JDK version to another. Don't use these rules directly, rather, use a wrapper ruleset such as migrating_to_13.xml. -permalink: pmd_rules_java_migrating.html -folder: pmd/rules/java -sidebaractiveurl: /pmd_rules_java.html -editmepath: ../pmd-java/src/main/resources/rulesets/java/migrating.xml -keywords: Migration, ReplaceVectorWithList, ReplaceHashtableWithMap, ReplaceEnumerationWithIterator, AvoidEnumAsIdentifier, AvoidAssertAsIdentifier, IntegerInstantiation, ByteInstantiation, ShortInstantiation, LongInstantiation, JUnit4TestShouldUseBeforeAnnotation, JUnit4TestShouldUseAfterAnnotation, JUnit4TestShouldUseTestAnnotation, JUnit4SuitesShouldUseSuiteAnnotation, JUnitUseExpected, ForLoopCanBeForeach ---- -## AvoidAssertAsIdentifier - -**Since:** PMD 3.4 - -**Priority:** Medium High (2) - -Use of the term 'assert' will conflict with newer versions of Java since it is a reserved word. - -``` -//VariableDeclaratorId[@Image='assert'] -``` - -**Example(s):** - -``` java -public class A { - public class Foo { - String assert = "foo"; - } -} -``` - -**Use this rule by referencing it:** -``` xml - -``` - -## AvoidEnumAsIdentifier - -**Since:** PMD 3.4 - -**Priority:** Medium High (2) - -Use of the term 'enum' will conflict with newer versions of Java since it is a reserved word. - -``` -//VariableDeclaratorId[@Image='enum'] -``` - -**Example(s):** - -``` java -public class A { - public class Foo { - String enum = "foo"; - } -} -``` - -**Use this rule by referencing it:** -``` xml - -``` - -## ByteInstantiation - -**Since:** PMD 4.0 - -**Priority:** Medium High (2) - -Calling new Byte() causes memory allocation that can be avoided by the static Byte.valueOf(). -It makes use of an internal cache that recycles earlier instances making it more memory efficient. - -``` -//PrimaryPrefix/AllocationExpression -[not (ArrayDimsAndInits) -and (ClassOrInterfaceType/@Image='Byte' -or ClassOrInterfaceType/@Image='java.lang.Byte')] -``` - -**Example(s):** - -``` java -public class Foo { - private Byte i = new Byte(0); // change to Byte i = Byte.valueOf(0); -} -``` - -**Use this rule by referencing it:** -``` xml - -``` - -## ForLoopCanBeForeach - -**Since:** PMD 6.0 - -**Priority:** Medium (3) - -**Minimum Language Version:** Java 1.5 - -Reports loops that can be safely replaced with the foreach syntax. The rule considers loops over - lists, arrays and iterators. A loop is safe to replace if it only uses the index variable to - access an element of the list or array, only has one update statement, and loops through *every* - element of the list or array left to right. - -**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.migrating.ForLoopCanBeForeachRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/migrating/ForLoopCanBeForeachRule.java) - -**Example(s):** - -``` java -public class MyClass { - void loop(List l) { - for (int i = 0; i < l.size(); i++) { // pre Java 1.5 - System.out.println(l.get(i)); - } - - for (String s : l) { // post Java 1.5 - System.out.println(s); - } - } -} -``` - -**Use this rule by referencing it:** -``` xml - -``` - -## IntegerInstantiation - -**Since:** PMD 3.5 - -**Priority:** Medium High (2) - -Calling new Integer() causes memory allocation that can be avoided by the static Integer.valueOf(). -It makes use of an internal cache that recycles earlier instances making it more memory efficient. - -``` -//PrimaryPrefix - /AllocationExpression - [not (ArrayDimsAndInits) - and (ClassOrInterfaceType/@Image='Integer' - or ClassOrInterfaceType/@Image='java.lang.Integer')] -``` - -**Example(s):** - -``` java -public class Foo { - private Integer i = new Integer(0); // change to Integer i = Integer.valueOf(0); -} -``` - -**Use this rule by referencing it:** -``` xml - -``` - -## JUnit4SuitesShouldUseSuiteAnnotation - -**Since:** PMD 4.0 - -**Priority:** Medium (3) - -In JUnit 3, test suites are indicated by the suite() method. In JUnit 4, suites are indicated -through the @RunWith(Suite.class) annotation. - -``` -//ClassOrInterfaceBodyDeclaration[MethodDeclaration/MethodDeclarator[@Image='suite']] -[MethodDeclaration/ResultType/Type/ReferenceType/ClassOrInterfaceType[@Image='Test' or @Image = 'junit.framework.Test']] -[not(MethodDeclaration/Block//ClassOrInterfaceType[@Image='JUnit4TestAdapter'])] -``` - -**Example(s):** - -``` java -public class BadExample extends TestCase{ - - public static Test suite(){ - return new Suite(); - } -} - -@RunWith(Suite.class) -@SuiteClasses( { TestOne.class, TestTwo.class }) -public class GoodTest { -} -``` - -**Use this rule by referencing it:** -``` xml - -``` - -## JUnit4TestShouldUseAfterAnnotation - -**Since:** PMD 4.0 - -**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 - -``` -//CompilationUnit[not(ImportDeclaration/Name[starts-with(@Image, "org.testng")])] -//ClassOrInterfaceBodyDeclaration[MethodDeclaration/MethodDeclarator[@Image='tearDown']] -[count(Annotation//Name[@Image='After'])=0] -``` - -**Example(s):** - -``` java -public class MyTest { - public void tearDown() { - bad(); - } -} -public class MyTest2 { - @After public void tearDown() { - good(); - } -} -``` - -**Use this rule by referencing it:** -``` xml - -``` - -## JUnit4TestShouldUseBeforeAnnotation - -**Since:** PMD 4.0 - -**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 - -``` -//CompilationUnit[not(ImportDeclaration/Name[starts-with(@Image, "org.testng")])] -//ClassOrInterfaceBodyDeclaration[MethodDeclaration/MethodDeclarator[@Image='setUp']] -[count(Annotation//Name[@Image='Before'])=0] -``` - -**Example(s):** - -``` java -public class MyTest { - public void setUp() { - bad(); - } -} -public class MyTest2 { - @Before public void setUp() { - good(); - } -} -``` - -**Use this rule by referencing it:** -``` xml - -``` - -## JUnit4TestShouldUseTestAnnotation - -**Since:** PMD 4.0 - -**Priority:** Medium (3) - -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. - -``` -//ClassOrInterfaceBodyDeclaration[MethodDeclaration[@Public='true']/MethodDeclarator[starts-with(@Image,'test')]] -[count(Annotation//Name[@Image='Test'])=0] -``` - -**Example(s):** - -``` java -public class MyTest { - public void testBad() { - doSomething(); - } - - @Test - public void testGood() { - doSomething(); - } -} -``` - -**Use this rule by referencing it:** -``` xml - -``` - -## JUnitUseExpected - -**Since:** PMD 4.0 - -**Priority:** Medium (3) - -In JUnit4, use the @Test(expected) annotation to denote tests that should throw exceptions. - -**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.migrating.JUnitUseExpectedRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/migrating/JUnitUseExpectedRule.java) - -**Example(s):** - -``` java -public class MyTest { - @Test - public void testBad() { - try { - doSomething(); - fail("should have thrown an exception"); - } catch (Exception e) { - } - } - - @Test(expected=Exception.class) - public void testGood() { - doSomething(); - } -} -``` - -**Use this rule by referencing it:** -``` xml - -``` - -## LongInstantiation - -**Since:** PMD 4.0 - -**Priority:** Medium High (2) - -Calling new Long() causes memory allocation that can be avoided by the static Long.valueOf(). -It makes use of an internal cache that recycles earlier instances making it more memory efficient. - -``` -//PrimaryPrefix -/AllocationExpression -[not (ArrayDimsAndInits) -and (ClassOrInterfaceType/@Image='Long' -or ClassOrInterfaceType/@Image='java.lang.Long')] -``` - -**Example(s):** - -``` java -public class Foo { - private Long i = new Long(0); // change to Long i = Long.valueOf(0); -} -``` - -**Use this rule by referencing it:** -``` xml - -``` - -## ReplaceEnumerationWithIterator - -**Since:** PMD 3.4 - -**Priority:** Medium (3) - -Consider replacing Enumeration usages with the newer java.util.Iterator - -``` -//ImplementsList/ClassOrInterfaceType[@Image='Enumeration'] -``` - -**Example(s):** - -``` java -public class Foo implements Enumeration { - private int x = 42; - public boolean hasMoreElements() { - return true; - } - public Object nextElement() { - return String.valueOf(i++); - } -} -``` - -**Use this rule by referencing it:** -``` xml - -``` - -## ReplaceHashtableWithMap - -**Since:** PMD 3.4 - -**Priority:** Medium (3) - -Consider replacing Hashtable usage with the newer java.util.Map if thread safety is not required. - -``` -//Type/ReferenceType/ClassOrInterfaceType[@Image='Hashtable'] -``` - -**Example(s):** - -``` java -public class Foo { - void bar() { - Hashtable h = new Hashtable(); - } -} -``` - -**Use this rule by referencing it:** -``` xml - -``` - -## ReplaceVectorWithList - -**Since:** PMD 3.4 - -**Priority:** Medium (3) - -Consider replacing Vector usages with the newer java.util.ArrayList if expensive thread-safe operations are not required. - -``` -//Type/ReferenceType/ClassOrInterfaceType[@Image='Vector'] -``` - -**Example(s):** - -``` java -public class Foo { - void bar() { - Vector v = new Vector(); - } -} -``` - -**Use this rule by referencing it:** -``` xml - -``` - -## ShortInstantiation - -**Since:** PMD 4.0 - -**Priority:** Medium High (2) - -Calling new Short() causes memory allocation that can be avoided by the static Short.valueOf(). -It makes use of an internal cache that recycles earlier instances making it more memory efficient. - -``` -//PrimaryPrefix -/AllocationExpression -[not (ArrayDimsAndInits) -and (ClassOrInterfaceType/@Image='Short' -or ClassOrInterfaceType/@Image='java.lang.Short')] -``` - -**Example(s):** - -``` java -public class Foo { - private Short i = new Short(0); // change to Short i = Short.valueOf(0); -} -``` - -**Use this rule by referencing it:** -``` xml - -``` - diff --git a/docs/pages/pmd/rules/java/multithreading.md b/docs/pages/pmd/rules/java/multithreading.md new file mode 100644 index 00000000000..6c673f40a80 --- /dev/null +++ b/docs/pages/pmd/rules/java/multithreading.md @@ -0,0 +1,379 @@ +--- +title: Multithreading +summary: Rules that flag issues when dealing with multiple threads of execution. +permalink: pmd_rules_java_multithreading.html +folder: pmd/rules/java +sidebaractiveurl: /pmd_rules_java.html +editmepath: ../pmd-java/src/main/resources/category/java/multithreading.xml +keywords: Multithreading, AvoidSynchronizedAtMethodLevel, AvoidThreadGroup, AvoidUsingVolatile, DoNotUseThreads, DontCallThreadRun, DoubleCheckedLocking, NonThreadSafeSingleton, UnsynchronizedStaticDateFormatter, UseConcurrentHashMap, UseNotifyAllInsteadOfNotify +language: Java +--- +## AvoidSynchronizedAtMethodLevel + +**Since:** PMD 3.0 + +**Priority:** Medium (3) + +Method-level synchronization can cause problems when new code is added to the method. +Block-level synchronization helps to ensure that only the code that needs synchronization +gets it. + +**This rule is defined by the following XPath expression:** +``` xpath +//MethodDeclaration[@Synchronized='true'] +``` + +**Example(s):** + +``` java +public class Foo { + // Try to avoid this: + synchronized void foo() { + } + // Prefer this: + void bar() { + synchronized(this) { + } + } + + // Try to avoid this for static methods: + static synchronized void fooStatic() { + } + + // Prefer this: + static void barStatic() { + synchronized(Foo.class) { + } + } +} +``` + +**Use this rule by referencing it:** +``` xml + +``` + +## AvoidThreadGroup + +**Since:** PMD 3.6 + +**Priority:** Medium (3) + +Avoid using java.lang.ThreadGroup; although it is intended to be used in a threaded environment +it contains methods that are not thread-safe. + +**This rule is defined by the following XPath expression:** +``` xpath +//AllocationExpression/ClassOrInterfaceType[pmd-java:typeIs('java.lang.ThreadGroup')]| +//PrimarySuffix[contains(@Image, 'getThreadGroup')] +``` + +**Example(s):** + +``` java +public class Bar { + void buz() { + ThreadGroup tg = new ThreadGroup("My threadgroup"); + tg = new ThreadGroup(tg, "my thread group"); + tg = Thread.currentThread().getThreadGroup(); + tg = System.getSecurityManager().getThreadGroup(); + } +} +``` + +**Use this rule by referencing it:** +``` xml + +``` + +## AvoidUsingVolatile + +**Since:** PMD 4.1 + +**Priority:** Medium High (2) + +Use of the keyword 'volatile' is generally used to fine tune a Java application, and therefore, requires +a good expertise of the Java Memory Model. Moreover, its range of action is somewhat misknown. Therefore, +the volatile keyword should not be used for maintenance purpose and portability. + +**This rule is defined by the following XPath expression:** +``` xpath +//FieldDeclaration[contains(@Volatile,'true')] +``` + +**Example(s):** + +``` java +public class ThrDeux { + private volatile String var1; // not suggested + private String var2; // preferred +} +``` + +**Use this rule by referencing it:** +``` xml + +``` + +## DoNotUseThreads + +**Since:** PMD 4.1 + +**Priority:** Medium (3) + +The J2EE specification explicitly forbids the use of threads. + +**This rule is defined by the following XPath expression:** +``` xpath +//ClassOrInterfaceType[@Image = 'Thread' or @Image = 'Runnable'] +``` + +**Example(s):** + +``` java +// This is not allowed +public class UsingThread extends Thread { + +} + +// Neither this, +public class OtherThread implements Runnable { + // Nor this ... + public void methode() { + Runnable thread = new Thread(); thread.run(); + } +} +``` + +**Use this rule by referencing it:** +``` xml + +``` + +## DontCallThreadRun + +**Since:** PMD 4.3 + +**Priority:** Medium Low (4) + +Explicitly calling Thread.run() method will execute in the caller's thread of control. Instead, call Thread.start() for the intended behavior. + +**This rule is defined by the following XPath expression:** +``` xpath +//StatementExpression/PrimaryExpression +[ + PrimaryPrefix + [ + ./Name[ends-with(@Image, '.run') or @Image = 'run'] + and substring-before(Name/@Image, '.') =//VariableDeclarator/VariableDeclaratorId/@Image + [../../../Type/ReferenceType/ClassOrInterfaceType[pmd-java:typeIs('java.lang.Thread')]] + or (./AllocationExpression/ClassOrInterfaceType[pmd-java:typeIs('java.lang.Thread')] + and ../PrimarySuffix[@Image = 'run']) + ] +] +``` + +**Example(s):** + +``` java +Thread t = new Thread(); +t.run(); // use t.start() instead +new Thread().run(); // same violation +``` + +**Use this rule by referencing it:** +``` xml + +``` + +## DoubleCheckedLocking + +**Since:** PMD 1.04 + +**Priority:** High (1) + +Partially created objects can be returned by the Double Checked Locking pattern when used in Java. +An optimizing JRE may assign a reference to the baz variable before it calls the constructor of the object the +reference points to. + +Note: With Java 5, you can make Double checked locking work, if you declare the variable to be `volatile`. + +For more details refer to: +or + +**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.multithreading.DoubleCheckedLockingRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/multithreading/DoubleCheckedLockingRule.java) + +**Example(s):** + +``` java +public class Foo { + /*volatile */ Object baz = null; // fix for Java5 and later: volatile + Object bar() { + if (baz == null) { // baz may be non-null yet not fully created + synchronized(this) { + if (baz == null) { + baz = new Object(); + } + } + } + return baz; + } +} +``` + +**Use this rule by referencing it:** +``` xml + +``` + +## NonThreadSafeSingleton + +**Since:** PMD 3.4 + +**Priority:** Medium (3) + +Non-thread safe singletons can result in bad state changes. Eliminate +static singletons if possible by instantiating the object directly. Static +singletons are usually not needed as only a single instance exists anyway. +Other possible fixes are to synchronize the entire method or to use an +[initialize-on-demand holder class](https://en.wikipedia.org/wiki/Initialization-on-demand_holder_idiom). + +Refrain from using the double-checked locking pattern. The Java Memory Model doesn't +guarantee it to work unless the variable is declared as `volatile`, adding an uneeded +performance penalty. [Reference](http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html) + +See Effective Java, item 48. + +**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.multithreading.NonThreadSafeSingletonRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/multithreading/NonThreadSafeSingletonRule.java) + +**Example(s):** + +``` java +private static Foo foo = null; + +//multiple simultaneous callers may see partially initialized objects +public static Foo getFoo() { + if (foo==null) { + foo = new Foo(); + } + return foo; +} +``` + +**This rule has the following properties:** + +|Name|Default Value|Description|Multivalued| +|----|-------------|-----------|-----------| +|checkNonStaticFields|false|Check for non-static fields. Do not set this to true and checkNonStaticMethods to false.|no| +|checkNonStaticMethods|true|Check for non-static methods. Do not set this to false and checkNonStaticFields to true.|no| + +**Use this rule by referencing it:** +``` xml + +``` + +## UnsynchronizedStaticDateFormatter + +**Since:** PMD 3.6 + +**Priority:** Medium (3) + +SimpleDateFormat instances are not synchronized. Sun recommends using separate format instances +for each thread. If multiple threads must access a static formatter, the formatter must be +synchronized either on method or block level. + +**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.multithreading.UnsynchronizedStaticDateFormatterRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/multithreading/UnsynchronizedStaticDateFormatterRule.java) + +**Example(s):** + +``` java +public class Foo { + private static final SimpleDateFormat sdf = new SimpleDateFormat(); + void bar() { + sdf.format(); // poor, no thread-safety + } + synchronized void foo() { + sdf.format(); // preferred + } +} +``` + +**Use this rule by referencing it:** +``` xml + +``` + +## UseConcurrentHashMap + +**Since:** PMD 4.2.6 + +**Priority:** Medium (3) + +**Minimum Language Version:** Java 1.5 + +Since Java5 brought a new implementation of the Map designed for multi-threaded access, you can +perform efficient map reads without blocking other threads. + +**This rule is defined by the following XPath expression:** +``` xpath +//Type[../VariableDeclarator/VariableInitializer//AllocationExpression/ClassOrInterfaceType[@Image != 'ConcurrentHashMap']] +/ReferenceType/ClassOrInterfaceType[@Image = 'Map'] +``` + +**Example(s):** + +``` java +public class ConcurrentApp { + public void getMyInstance() { + Map map1 = new HashMap(); // fine for single-threaded access + Map map2 = new ConcurrentHashMap(); // preferred for use with multiple threads + + // the following case will be ignored by this rule + Map map3 = someModule.methodThatReturnMap(); // might be OK, if the returned map is already thread-safe + } +} +``` + +**Use this rule by referencing it:** +``` xml + +``` + +## UseNotifyAllInsteadOfNotify + +**Since:** PMD 3.0 + +**Priority:** Medium (3) + +Thread.notify() awakens a thread monitoring the object. If more than one thread is monitoring, then only +one is chosen. The thread chosen is arbitrary; thus its usually safer to call notifyAll() instead. + +**This rule is defined by the following XPath expression:** +``` xpath +//StatementExpression/PrimaryExpression +[PrimarySuffix/Arguments[@ArgumentCount = '0']] +[ + PrimaryPrefix[ + ./Name[@Image='notify' or ends-with(@Image,'.notify')] + or ../PrimarySuffix/@Image='notify' + or (./AllocationExpression and ../PrimarySuffix[@Image='notify']) + ] +] +``` + +**Example(s):** + +``` java +void bar() { + x.notify(); + // If many threads are monitoring x, only one (and you won't know which) will be notified. + // use instead: + x.notifyAll(); + } +``` + +**Use this rule by referencing it:** +``` xml + +``` + diff --git a/docs/pages/pmd/rules/java/naming.md b/docs/pages/pmd/rules/java/naming.md deleted file mode 100644 index db258a25ae2..00000000000 --- a/docs/pages/pmd/rules/java/naming.md +++ /dev/null @@ -1,653 +0,0 @@ ---- -title: Naming -summary: The Naming Ruleset contains rules regarding preferred usage of names and identifiers. -permalink: pmd_rules_java_naming.html -folder: pmd/rules/java -sidebaractiveurl: /pmd_rules_java.html -editmepath: ../pmd-java/src/main/resources/rulesets/java/naming.xml -keywords: Naming, ShortVariable, LongVariable, ShortMethodName, VariableNamingConventions, MethodNamingConventions, ClassNamingConventions, AbstractNaming, AvoidDollarSigns, MethodWithSameNameAsEnclosingClass, SuspiciousHashcodeMethodName, SuspiciousConstantFieldName, SuspiciousEqualsMethodName, AvoidFieldNameMatchingTypeName, AvoidFieldNameMatchingMethodName, NoPackage, PackageCase, MisleadingVariableName, BooleanGetMethodName, ShortClassName, GenericsNaming ---- -## AbstractNaming - -**Since:** PMD 1.4 - -**Priority:** Medium (3) - -Abstract classes should be named 'AbstractXXX'. - -``` -//ClassOrInterfaceDeclaration - [@Abstract='true' and @Interface='false'] - [not (starts-with(@Image,'Abstract'))] -| -//ClassOrInterfaceDeclaration - [@Abstract='false'] - [$strict='true'] - [starts-with(@Image, 'Abstract')] -``` - -**Example(s):** - -``` java -public abstract class Foo { // should be AbstractFoo -} -``` - -**This rule has the following properties:** - -|Name|Default Value|Description| -|----|-------------|-----------| -|strict|true|Also flag classes, that are named Abstract, but are not abstract.| - -**Use this rule by referencing it:** -``` xml - -``` - -## AvoidDollarSigns - -**Since:** PMD 1.5 - -**Priority:** Medium (3) - -Avoid using dollar signs in variable/method/class/interface names. - -**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.naming.AvoidDollarSignsRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/naming/AvoidDollarSignsRule.java) - -**Example(s):** - -``` java -public class Fo$o { // not a recommended name -} -``` - -**Use this rule by referencing it:** -``` xml - -``` - -## AvoidFieldNameMatchingMethodName - -**Since:** PMD 3.0 - -**Priority:** Medium (3) - -It can be confusing to have a field name with the same name as a method. While this is permitted, -having information (field) and actions (method) is not clear naming. Developers versed in -Smalltalk often prefer this approach as the methods denote accessor methods. - -**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.naming.AvoidFieldNameMatchingMethodNameRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/naming/AvoidFieldNameMatchingMethodNameRule.java) - -**Example(s):** - -``` java -public class Foo { - Object bar; - // bar is data or an action or both? - void bar() { - } -} -``` - -**Use this rule by referencing it:** -``` xml - -``` - -## AvoidFieldNameMatchingTypeName - -**Since:** PMD 3.0 - -**Priority:** Medium (3) - -It is somewhat confusing to have a field name matching the declaring class name. -This probably means that type and/or field names should be chosen more carefully. - -**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.naming.AvoidFieldNameMatchingTypeNameRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/naming/AvoidFieldNameMatchingTypeNameRule.java) - -**Example(s):** - -``` java -public class Foo extends Bar { - int foo; // There is probably a better name that can be used -} -``` - -**Use this rule by referencing it:** -``` xml - -``` - -## BooleanGetMethodName - -**Since:** PMD 4.0 - -**Priority:** Medium Low (4) - -Methods that return boolean results should be named as predicate statements to denote this. -I.e, 'isReady()', 'hasValues()', 'canCommit()', 'willFail()', etc. Avoid the use of the 'get' -prefix for these methods. - -``` -//MethodDeclaration[ -MethodDeclarator[count(FormalParameters/FormalParameter) = 0 or $checkParameterizedMethods = 'true'] - [starts-with(@Image, 'get')] -and -ResultType/Type/PrimitiveType[@Image = 'boolean'] -and not(../Annotation//Name[@Image = 'Override']) -] -``` - -**Example(s):** - -``` java -public boolean getFoo(); // bad -public boolean isFoo(); // ok -public boolean getFoo(boolean bar); // ok, unless checkParameterizedMethods=true -``` - -**This rule has the following properties:** - -|Name|Default Value|Description| -|----|-------------|-----------| -|checkParameterizedMethods|false|Check parameterized methods| - -**Use this rule by referencing it:** -``` xml - -``` - -## ClassNamingConventions - -**Since:** PMD 1.2 - -**Priority:** High (1) - -Class names should always begin with an upper case character. - -**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.naming.ClassNamingConventionsRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/naming/ClassNamingConventionsRule.java) - -**Example(s):** - -``` java -public class Foo {} -``` - -**Use this rule by referencing it:** -``` xml - -``` - -## GenericsNaming - -**Since:** PMD 4.2.6 - -**Priority:** Medium Low (4) - -Names for references to generic values should be limited to a single uppercase letter. - -``` -//TypeDeclaration/ClassOrInterfaceDeclaration/TypeParameters/TypeParameter[ - string-length(@Image) > 1 - or - string:upper-case(@Image) != @Image -] -``` - -**Example(s):** - -``` java -public interface GenericDao extends BaseDao { - // This is ok... -} - -public interface GenericDao { - // Also this -} - -public interface GenericDao { - // 'e' should be an 'E' -} - -public interface GenericDao { - // 'EF' is not ok. -} -``` - -**Use this rule by referencing it:** -``` xml - -``` - -## LongVariable - -**Since:** PMD 0.3 - -**Priority:** Medium (3) - -Fields, formal arguments, or local variable names that are too long can make the code difficult to follow. - -``` -//VariableDeclaratorId[string-length(@Image) > $minimum] -``` - -**Example(s):** - -``` java -public class Something { - int reallyLongIntName = -3; // VIOLATION - Field - public static void main( String argumentsList[] ) { // VIOLATION - Formal - int otherReallyLongName = -5; // VIOLATION - Local - for (int interestingIntIndex = 0; // VIOLATION - For - interestingIntIndex < 10; - interestingIntIndex ++ ) { - } -} -``` - -**This rule has the following properties:** - -|Name|Default Value|Description| -|----|-------------|-----------| -|minimum|17|The variable length reporting threshold| - -**Use this rule by referencing it:** -``` xml - -``` - -## MethodNamingConventions - -**Since:** PMD 1.2 - -**Priority:** High (1) - -Method names should always begin with a lower case character, and should not contain underscores. - -**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.naming.MethodNamingConventionsRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/naming/MethodNamingConventionsRule.java) - -**Example(s):** - -``` java -public class Foo { - public void fooStuff() { - } -} -``` - -**This rule has the following properties:** - -|Name|Default Value|Description| -|----|-------------|-----------| -|checkNativeMethods|true|Check native methods| - -**Use this rule by referencing it:** -``` xml - -``` - -## MethodWithSameNameAsEnclosingClass - -**Since:** PMD 1.5 - -**Priority:** Medium (3) - -Non-constructor methods should not have the same name as the enclosing class. - -**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.naming.MethodWithSameNameAsEnclosingClassRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/naming/MethodWithSameNameAsEnclosingClassRule.java) - -**Example(s):** - -``` java -public class MyClass { - - public MyClass() {} // this is OK because it is a constructor - - public void MyClass() {} // this is bad because it is a method -} -``` - -**Use this rule by referencing it:** -``` xml - -``` - -## MisleadingVariableName - -**Since:** PMD 3.4 - -**Priority:** Medium (3) - -Detects when a non-field has a name starting with 'm_'. This usually denotes a field and could be confusing. - -``` -//VariableDeclaratorId -[starts-with(@Image, 'm_')] -[not (../../../FieldDeclaration)] -``` - -**Example(s):** - -``` java -public class Foo { - private int m_foo; // OK - public void bar(String m_baz) { // Bad - int m_boz = 42; // Bad - } -} -``` - -**Use this rule by referencing it:** -``` xml - -``` - -## NoPackage - -**Since:** PMD 3.3 - -**Priority:** Medium (3) - -Detects when a class or interface does not have a package definition. - -``` -//ClassOrInterfaceDeclaration[count(preceding::PackageDeclaration) = 0] -``` - -**Example(s):** - -``` java -// no package declaration -public class ClassInDefaultPackage { -} -``` - -**Use this rule by referencing it:** -``` xml - -``` - -## PackageCase - -**Since:** PMD 3.3 - -**Priority:** Medium (3) - -Detects when a package definition contains uppercase characters. - -``` -//PackageDeclaration/Name[lower-case(@Image)!=@Image] -``` - -**Example(s):** - -``` java -package com.MyCompany; // should be lowercase name - -public class SomeClass { -} -``` - -**Use this rule by referencing it:** -``` xml - -``` - -## ShortClassName - -**Since:** PMD 5.0 - -**Priority:** Medium Low (4) - -Short Classnames with fewer than e.g. five characters are not recommended. - -``` -//ClassOrInterfaceDeclaration[string-length(@Image) < $minimum] -``` - -**Example(s):** - -``` java -public class Foo { -} -``` - -**This rule has the following properties:** - -|Name|Default Value|Description| -|----|-------------|-----------| -|minimum|5|Number of characters that are required as a minimum for a class name.| - -**Use this rule by referencing it:** -``` xml - -``` - -## ShortMethodName - -**Since:** PMD 0.3 - -**Priority:** Medium (3) - -Method names that are very short are not helpful to the reader. - -``` -//MethodDeclarator[string-length(@Image) < $minimum] -``` - -**Example(s):** - -``` java -public class ShortMethod { - public void a( int i ) { // Violation - } -} -``` - -**This rule has the following properties:** - -|Name|Default Value|Description| -|----|-------------|-----------| -|minimum|3|Number of characters that are required as a minimum for a method name.| - -**Use this rule by referencing it:** -``` xml - -``` - -## ShortVariable - -**Since:** PMD 0.3 - -**Priority:** Medium (3) - -Fields, local variables, or parameter names that are very short are not helpful to the reader. - -``` -//VariableDeclaratorId[string-length(@Image) < $minimum] - [not(ancestor::ForInit)] - [not(../../VariableDeclarator and ../../../LocalVariableDeclaration and ../../../../ForStatement)] - [not((ancestor::FormalParameter) and (ancestor::TryStatement))] -``` - -**Example(s):** - -``` java -public class Something { - private int q = 15; // field - too short - public static void main( String as[] ) { // formal arg - too short - int r = 20 + q; // local var - too short - for (int i = 0; i < 10; i++) { // not a violation (inside 'for' loop) - r += q; - } - for (Integer i : numbers) { // not a violation (inside 'for-each' loop) - r += q; - } - } -} -``` - -**This rule has the following properties:** - -|Name|Default Value|Description| -|----|-------------|-----------| -|minimum|3|Number of characters that are required as a minimum for a variable name.| - -**Use this rule by referencing it:** -``` xml - -``` - -## SuspiciousConstantFieldName - -**Since:** PMD 2.0 - -**Priority:** Medium (3) - -Field names using all uppercase characters - Sun's Java naming conventions indicating constants - should -be declared as final. - -``` -//ClassOrInterfaceDeclaration[@Interface='false'] - /ClassOrInterfaceBody/ClassOrInterfaceBodyDeclaration/FieldDeclaration - [@Final='false'] - [VariableDeclarator/VariableDeclaratorId[upper-case(@Image)=@Image]] -``` - -**Example(s):** - -``` java -public class Foo { - // this is bad, since someone could accidentally - // do PI = 2.71828; which is actually e - // final double PI = 3.16; is ok - double PI = 3.16; -} -``` - -**Use this rule by referencing it:** -``` xml - -``` - -## SuspiciousEqualsMethodName - -**Since:** PMD 2.0 - -**Priority:** Medium High (2) - -The method name and parameter number are suspiciously close to equals(Object), which can denote an -intention to override the equals(Object) method. - -``` -//MethodDeclarator[@Image = 'equals'] -[ - (count(FormalParameters/*) = 1 - and not (FormalParameters/FormalParameter/Type/ReferenceType/ClassOrInterfaceType - [@Image = 'Object' or @Image = 'java.lang.Object']) - or not (../ResultType/Type/PrimitiveType[@Image = 'boolean']) - ) or ( - count(FormalParameters/*) = 2 - and ../ResultType/Type/PrimitiveType[@Image = 'boolean'] - and FormalParameters//ClassOrInterfaceType[@Image = 'Object' or @Image = 'java.lang.Object'] - and not(../../Annotation/MarkerAnnotation/Name[@Image='Override']) - ) -] -| //MethodDeclarator[@Image = 'equal'] -[ - count(FormalParameters/*) = 1 - and FormalParameters/FormalParameter/Type/ReferenceType/ClassOrInterfaceType - [@Image = 'Object' or @Image = 'java.lang.Object'] -] -``` - -**Example(s):** - -``` java -public class Foo { - public int equals(Object o) { - // oops, this probably was supposed to be boolean equals - } - public boolean equals(String s) { - // oops, this probably was supposed to be equals(Object) - } - public boolean equals(Object o1, Object o2) { - // oops, this probably was supposed to be equals(Object) - } -} -``` - -**Use this rule by referencing it:** -``` xml - -``` - -## SuspiciousHashcodeMethodName - -**Since:** PMD 1.5 - -**Priority:** Medium (3) - -The method name and return type are suspiciously close to hashCode(), which may denote an intention -to override the hashCode() method. - -**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.naming.SuspiciousHashcodeMethodNameRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/naming/SuspiciousHashcodeMethodNameRule.java) - -**Example(s):** - -``` java -public class Foo { - public int hashcode() { // oops, this probably was supposed to be 'hashCode' - } -} -``` - -**Use this rule by referencing it:** -``` xml - -``` - -## VariableNamingConventions - -**Since:** PMD 1.2 - -**Priority:** High (1) - -A variable naming conventions rule - customize this to your liking. Currently, it -checks for final variables that should be fully capitalized and non-final variables -that should not include underscores. - -**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.naming.VariableNamingConventionsRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/naming/VariableNamingConventionsRule.java) - -**Example(s):** - -``` java -public class Foo { - public static final int MY_NUM = 0; - public String myTest = ""; - DataModule dmTest = new DataModule(); -} -``` - -**This rule has the following properties:** - -|Name|Default Value|Description| -|----|-------------|-----------| -|parameterSuffix|[]|Method parameter variable suffixes| -|parameterPrefix|[]|Method parameter variable prefixes| -|localSuffix|[]|Local variable suffixes| -|localPrefix|[]|Local variable prefixes| -|memberSuffix|[]|Member variable suffixes| -|memberPrefix|[]|Member variable prefixes| -|staticSuffix|[]|Static variable suffixes| -|checkParameters|true|Check constructor and method parameter variables| -|checkNativeMethodParameters|true|Check method parameter of native methods| -|staticPrefix|[]|Static variable prefixes| -|checkLocals|true|Check local variables| -|checkMembers|true|Check member variables| - -**Use this rule by referencing it:** -``` xml - -``` - diff --git a/docs/pages/pmd/rules/java/optimizations.md b/docs/pages/pmd/rules/java/optimizations.md deleted file mode 100644 index a2670e8be45..00000000000 --- a/docs/pages/pmd/rules/java/optimizations.md +++ /dev/null @@ -1,441 +0,0 @@ ---- -title: Optimization -summary: These rules deal with different optimizations that generally apply to best practices. -permalink: pmd_rules_java_optimizations.html -folder: pmd/rules/java -sidebaractiveurl: /pmd_rules_java.html -editmepath: ../pmd-java/src/main/resources/rulesets/java/optimizations.xml -keywords: Optimization, LocalVariableCouldBeFinal, MethodArgumentCouldBeFinal, AvoidInstantiatingObjectsInLoops, UseArrayListInsteadOfVector, SimplifyStartsWith, UseStringBufferForStringAppends, UseArraysAsList, AvoidArrayLoops, UnnecessaryWrapperObjectCreation, AddEmptyString, RedundantFieldInitializer, PrematureDeclaration ---- -## AddEmptyString - -**Since:** PMD 4.0 - -**Priority:** Medium (3) - -The conversion of literals to strings by concatenating them with empty strings is inefficient. -It is much better to use one of the type-specific toString() methods instead. - -``` -//AdditiveExpression/PrimaryExpression/PrimaryPrefix/Literal[@Image='""'] -``` - -**Example(s):** - -``` java -String s = "" + 123; // inefficient -String t = Integer.toString(456); // preferred approach -``` - -**Use this rule by referencing it:** -``` xml - -``` - -## AvoidArrayLoops - -**Since:** PMD 3.5 - -**Priority:** Medium (3) - -Instead of manually copying data between two arrays, use the efficient System.arraycopy method instead. - -``` -//Statement[(ForStatement or WhileStatement) and -count(*//AssignmentOperator[@Image = '='])=1 -and -*/Statement -[ -./Block/BlockStatement/Statement/StatementExpression/PrimaryExpression -/PrimaryPrefix/Name/../../PrimarySuffix/Expression -[(PrimaryExpression or AdditiveExpression) and count -(.//PrimaryPrefix/Name)=1]//PrimaryPrefix/Name/@Image -and -./Block/BlockStatement/Statement/StatementExpression/Expression/PrimaryExpression -/PrimaryPrefix/Name/../../PrimarySuffix[count -(..//PrimarySuffix)=1]/Expression[(PrimaryExpression -or AdditiveExpression) and count(.//PrimaryPrefix/Name)=1] -//PrimaryPrefix/Name/@Image -]] -``` - -**Example(s):** - -``` java -public class Test { - public void bar() { - int[] a = new int[10]; - int[] b = new int[10]; - for (int i=0;i<10;i++) { - b[i]=a[i]; - } - - int[] c = new int[10]; - // this will trigger the rule - for (int i=0;i<10;i++) { - b[i]=a[c[i]]; - } - } -} -``` - -**Use this rule by referencing it:** -``` xml - -``` - -## AvoidInstantiatingObjectsInLoops - -**Since:** PMD 2.2 - -**Priority:** Medium (3) - -New objects created within loops should be checked to see if they can created outside them and reused. - -**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.optimizations.AvoidInstantiatingObjectsInLoopsRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/optimizations/AvoidInstantiatingObjectsInLoopsRule.java) - -**Example(s):** - -``` java -public class Something { - public static void main( String as[] ) { - for (int i = 0; i < 10; i++) { - Foo f = new Foo(); // Avoid this whenever you can it's really expensive - } - } -} -``` - -**Use this rule by referencing it:** -``` xml - -``` - -## LocalVariableCouldBeFinal - -**Since:** PMD 2.2 - -**Priority:** Medium (3) - -A local variable assigned only once can be declared final. - -**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.optimizations.LocalVariableCouldBeFinalRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/optimizations/LocalVariableCouldBeFinalRule.java) - -**Example(s):** - -``` java -public class Bar { - public void foo () { - String txtA = "a"; // if txtA will not be assigned again it is better to do this: - final String txtB = "b"; - } -} -``` - -**Use this rule by referencing it:** -``` xml - -``` - -## MethodArgumentCouldBeFinal - -**Since:** PMD 2.2 - -**Priority:** Medium (3) - -A method argument that is never re-assigned within the method can be declared final. - -**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.optimizations.MethodArgumentCouldBeFinalRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/optimizations/MethodArgumentCouldBeFinalRule.java) - -**Example(s):** - -``` java -public void foo1 (String param) { // do stuff with param never assigning it - -} - -public void foo2 (final String param) { // better, do stuff with param never assigning it - -} -``` - -**Use this rule by referencing it:** -``` xml - -``` - -## PrematureDeclaration - -**Since:** PMD 5.0 - -**Priority:** Medium (3) - -Checks for variables that are defined before they might be used. A reference is deemed to be premature if it is created right before a block of code that doesn't use it that also has the ability to return or throw an exception. - -**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.optimizations.PrematureDeclarationRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/optimizations/PrematureDeclarationRule.java) - -**Example(s):** - -``` java -public int getLength(String[] strings) { - - int length = 0; // declared prematurely - - if (strings == null || strings.length == 0) return 0; - - for (String str : strings) { - length += str.length(); - } - - return length; -} -``` - -**Use this rule by referencing it:** -``` xml - -``` - -## RedundantFieldInitializer - -**Since:** PMD 5.0 - -**Priority:** Medium (3) - -Java will initialize fields with known default values so any explicit initialization of those same defaults -is redundant and results in a larger class file (approximately three additional bytecode instructions per field). - -**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.optimizations.RedundantFieldInitializerRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/optimizations/RedundantFieldInitializerRule.java) - -**Example(s):** - -``` java -public class C { - boolean b = false; // examples of redundant initializers - byte by = 0; - short s = 0; - char c = 0; - int i = 0; - long l = 0; - - float f = .0f; // all possible float literals - doable d = 0d; // all possible double literals - Object o = null; - - MyClass mca[] = null; - int i1 = 0, ia1[] = null; - - class Nested { - boolean b = false; - } -} -``` - -**Use this rule by referencing it:** -``` xml - -``` - -## SimplifyStartsWith - -**Since:** PMD 3.1 - -**Priority:** Medium (3) - -Since it passes in a literal of length 1, calls to (string).startsWith can be rewritten using (string).charAt(0) -at the expense of some readability. - -``` -//PrimaryExpression - [PrimaryPrefix/Name - [ends-with(@Image, '.startsWith')] or PrimarySuffix[@Image='startsWith']] - [PrimarySuffix/Arguments/ArgumentList - /Expression/PrimaryExpression/PrimaryPrefix - /Literal - [string-length(@Image)=3] - [starts-with(@Image, '"')] - [ends-with(@Image, '"')] - ] -``` - -**Example(s):** - -``` java -public class Foo { - - boolean checkIt(String x) { - return x.startsWith("a"); // suboptimal - } - - boolean fasterCheckIt(String x) { - return x.charAt(0) == 'a'; // faster approach - } -} -``` - -**Use this rule by referencing it:** -``` xml - -``` - -## UnnecessaryWrapperObjectCreation - -**Since:** PMD 3.8 - -**Priority:** Medium (3) - -Most wrapper classes provide static conversion methods that avoid the need to create intermediate objects -just to create the primitive forms. Using these avoids the cost of creating objects that also need to be -garbage-collected later. - -**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.optimizations.UnnecessaryWrapperObjectCreationRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/optimizations/UnnecessaryWrapperObjectCreationRule.java) - -**Example(s):** - -``` java -public int convert(String s) { - int i, i2; - - i = Integer.valueOf(s).intValue(); // this wastes an object - i = Integer.parseInt(s); // this is better - - i2 = Integer.valueOf(i).intValue(); // this wastes an object - i2 = i; // this is better - - String s3 = Integer.valueOf(i2).toString(); // this wastes an object - s3 = Integer.toString(i2); // this is better - - return i2; -} -``` - -**Use this rule by referencing it:** -``` xml - -``` - -## UseArrayListInsteadOfVector - -**Since:** PMD 3.0 - -**Priority:** Medium (3) - -ArrayList is a much better Collection implementation than Vector if thread-safe operation is not required. - -``` -//CompilationUnit[count(ImportDeclaration) = 0 or count(ImportDeclaration/Name[@Image='java.util.Vector']) > 0] - //AllocationExpression/ClassOrInterfaceType - [@Image='Vector' or @Image='java.util.Vector'] -``` - -**Example(s):** - -``` java -public class SimpleTest extends TestCase { - public void testX() { - Collection c1 = new Vector(); - Collection c2 = new ArrayList(); // achieves the same with much better performance - } -} -``` - -**Use this rule by referencing it:** -``` xml - -``` - -## UseArraysAsList - -**Since:** PMD 3.5 - -**Priority:** Medium (3) - -The java.util.Arrays class has a "asList" method that should be used when you want to create a new List from -an array of objects. It is faster than executing a loop to copy all the elements of the array one by one. - -``` -//Statement[ - (ForStatement) and (ForStatement//VariableInitializer//Literal[@IntLiteral='true' and @Image='0']) and (count(.//IfStatement)=0) - ] - //StatementExpression[ - PrimaryExpression/PrimaryPrefix/Name[ - substring-before(@Image,'.add') = ancestor::MethodDeclaration//LocalVariableDeclaration[ - ./Type//ClassOrInterfaceType[ - @Image = 'Collection' or - @Image = 'List' or @Image='ArrayList' - ] - ] - /VariableDeclarator/VariableDeclaratorId[ - count(..//AllocationExpression/ClassOrInterfaceType[ - @Image="ArrayList" - ] - )=1 - ]/@Image - ] - and - PrimaryExpression/PrimarySuffix/Arguments/ArgumentList/Expression/PrimaryExpression/PrimaryPrefix/Name - [ - @Image = ancestor::MethodDeclaration//LocalVariableDeclaration[@Array="true"]/VariableDeclarator/VariableDeclaratorId/@Image - or - @Image = ancestor::MethodDeclaration//FormalParameter/VariableDeclaratorId/@Image - ] - /../..[count(.//PrimarySuffix) - =1]/PrimarySuffix/Expression/PrimaryExpression/PrimaryPrefix - /Name - ] -``` - -**Example(s):** - -``` java -public class Test { - public void foo(Integer[] ints) { - // could just use Arrays.asList(ints) - List l= new ArrayList(10); - for (int i=0; i< 100; i++) { - l.add(ints[i]); - } - for (int i=0; i< 100; i++) { - l.add(a[i].toString()); // won't trigger the rule - } - } -} -``` - -**Use this rule by referencing it:** -``` xml - -``` - -## UseStringBufferForStringAppends - -**Since:** PMD 3.1 - -**Priority:** Medium (3) - -The use of the '+=' operator for appending strings causes the JVM to create and use an internal StringBuffer. -If a non-trivial number of these concatenations are being used then the explicit use of a StringBuilder or -threadsafe StringBuffer is recommended to avoid this. - -**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.optimizations.UseStringBufferForStringAppendsRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/optimizations/UseStringBufferForStringAppendsRule.java) - -**Example(s):** - -``` java -public class Foo { - void bar() { - String a; - a = "foo"; - a += " bar"; - // better would be: - // StringBuilder a = new StringBuilder("foo"); - // a.append(" bar); - } -} -``` - -**Use this rule by referencing it:** -``` xml - -``` - diff --git a/docs/pages/pmd/rules/java/performance.md b/docs/pages/pmd/rules/java/performance.md new file mode 100644 index 00000000000..d86c5214a56 --- /dev/null +++ b/docs/pages/pmd/rules/java/performance.md @@ -0,0 +1,1046 @@ +--- +title: Performance +summary: Rules that flag suboptimal code. +permalink: pmd_rules_java_performance.html +folder: pmd/rules/java +sidebaractiveurl: /pmd_rules_java.html +editmepath: ../pmd-java/src/main/resources/category/java/performance.xml +keywords: Performance, AddEmptyString, AppendCharacterWithChar, AvoidArrayLoops, AvoidFileStream, AvoidInstantiatingObjectsInLoops, AvoidUsingShortType, BigIntegerInstantiation, BooleanInstantiation, ByteInstantiation, ConsecutiveAppendsShouldReuse, ConsecutiveLiteralAppends, InefficientEmptyStringCheck, InefficientStringBuffering, InsufficientStringBufferDeclaration, IntegerInstantiation, LongInstantiation, OptimizableToArrayCall, RedundantFieldInitializer, SimplifyStartsWith, ShortInstantiation, StringInstantiation, StringToString, TooFewBranchesForASwitchStatement, UnnecessaryWrapperObjectCreation, UseArrayListInsteadOfVector, UseArraysAsList, UseIndexOfChar, UselessStringValueOf, UseStringBufferForStringAppends, UseStringBufferLength +language: Java +--- +## AddEmptyString + +**Since:** PMD 4.0 + +**Priority:** Medium (3) + +The conversion of literals to strings by concatenating them with empty strings is inefficient. +It is much better to use one of the type-specific toString() methods instead. + +**This rule is defined by the following XPath expression:** +``` xpath +//AdditiveExpression/PrimaryExpression/PrimaryPrefix/Literal[@Image='""'] +``` + +**Example(s):** + +``` java +String s = "" + 123; // inefficient +String t = Integer.toString(456); // preferred approach +``` + +**Use this rule by referencing it:** +``` xml + +``` + +## AppendCharacterWithChar + +**Since:** PMD 3.5 + +**Priority:** Medium (3) + +Avoid concatenating characters as strings in StringBuffer/StringBuilder.append methods. + +**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.performance.AppendCharacterWithCharRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/performance/AppendCharacterWithCharRule.java) + +**Example(s):** + +``` java +StringBuffer sb = new StringBuffer(); +sb.append("a"); // avoid this + +StringBuffer sb = new StringBuffer(); +sb.append('a'); // use this instead +``` + +**Use this rule by referencing it:** +``` xml + +``` + +## AvoidArrayLoops + +**Since:** PMD 3.5 + +**Priority:** Medium (3) + +Instead of manually copying data between two arrays, use the efficient Arrays.copyOf or System.arraycopy method instead. + +**This rule is defined by the following XPath expression:** +``` xpath +//Statement[(ForStatement or WhileStatement) and +count(*//AssignmentOperator[@Image = '='])=1 +and +*/Statement +[ +./Block/BlockStatement/Statement/StatementExpression/PrimaryExpression +/PrimaryPrefix/Name/../../PrimarySuffix/Expression +[(PrimaryExpression or AdditiveExpression) and count +(.//PrimaryPrefix/Name)=1]//PrimaryPrefix/Name/@Image +and +./Block/BlockStatement/Statement/StatementExpression/Expression/PrimaryExpression +/PrimaryPrefix/Name/../../PrimarySuffix[count +(..//PrimarySuffix)=1]/Expression[(PrimaryExpression +or AdditiveExpression) and count(.//PrimaryPrefix/Name)=1] +//PrimaryPrefix/Name/@Image +]] +``` + +**Example(s):** + +``` java +public class Test { + public void bar() { + int[] a = new int[10]; + int[] b = new int[10]; + for (int i=0;i<10;i++) { + b[i]=a[i]; + } + + int[] c = new int[10]; + // this will trigger the rule + for (int i=0;i<10;i++) { + b[i]=a[c[i]]; + } + } +} +``` + +**Use this rule by referencing it:** +``` xml + +``` + +## AvoidFileStream + +**Since:** PMD 6.0.0 + +**Priority:** High (1) + +**Minimum Language Version:** Java 1.7 + +The FileInputStream and FileOutputStream classes contains a finalizer method which will cause garbage collection pauses. See [JDK-8080225](https://bugs.openjdk.java.net/browse/JDK-8080225) for details. + +The FileReader and FileWriter constructors instantiate FileInputStream and FileOutputStream, again causing garbage collection issues while finalizer methods are called. + +* Use `Files.newInputStream(Paths.get(fileName))` instead of `new FileInputStream(fileName)`. +* Use `Files.newOutputStream(Paths.get(fileName))` instead of `new FileOutputStream(fileName)`. +* Use `Files.newBufferedReader(Paths.get(fileName))` instead of `new FileReader(fileName)`. +* Use `Files.newBufferedWriter(Paths.get(fileName))` instead of `new FileWriter(fileName)`. + +**This rule is defined by the following XPath expression:** +``` xpath +//PrimaryPrefix/AllocationExpression/ClassOrInterfaceType[ + 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') + ] +``` + +**Example(s):** + +``` java +// these instantiations cause garbage collection pauses, even if properly closed + + FileInputStream fis = new FileInputStream(fileName); + FileOutputStream fos = new FileOutputStream(fileName); + FileReader fr = new FileReader(fileName); + FileWriter fw = new FileWriter(fileName); + + // the following instantiations help prevent Garbage Collection pauses, no finalization + + try(InputStream is = Files.newInputStream(Paths.get(fileName))) { + } + try(OutputStream os = Files.newOutputStream(Paths.get(fileName))) { + } + try(BufferedReader br = Files.newBufferedReader(Paths.get(fileName), StandardCharsets.UTF_8)) { + } + try(BufferedWriter wr = Files.newBufferedWriter(Paths.get(fileName), StandardCharsets.UTF_8)) { + } +``` + +**Use this rule by referencing it:** +``` xml + +``` + +## AvoidInstantiatingObjectsInLoops + +**Since:** PMD 2.2 + +**Priority:** Medium (3) + +New objects created within loops should be checked to see if they can created outside them and reused. + +**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.performance.AvoidInstantiatingObjectsInLoopsRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/performance/AvoidInstantiatingObjectsInLoopsRule.java) + +**Example(s):** + +``` java +public class Something { + public static void main( String as[] ) { + for (int i = 0; i < 10; i++) { + Foo f = new Foo(); // Avoid this whenever you can it's really expensive + } + } +} +``` + +**Use this rule by referencing it:** +``` xml + +``` + +## AvoidUsingShortType + +**Since:** PMD 4.1 + +**Priority:** High (1) + +Java uses the 'short' type to reduce memory usage, not to optimize calculation. In fact, the JVM does not have any +arithmetic capabilities for the short type: the JVM must convert the short into an int, do the proper calculation +and convert the int back to a short. Thus any storage gains found through use of the 'short' type may be offset by +adverse impacts on performance. + +**This rule is defined by the following XPath expression:** +``` xpath +//FieldDeclaration/Type/PrimitiveType[@Image = 'short'] +| +//ClassOrInterfaceBodyDeclaration[not(Annotation/MarkerAnnotation/Name[pmd-java:typeIs('java.lang.Override')])] + /MethodDeclaration/ResultType/Type/PrimitiveType[@Image = 'short'] +| +//ClassOrInterfaceBodyDeclaration[not(Annotation/MarkerAnnotation/Name[pmd-java:typeIs('java.lang.Override')])] + /MethodDeclaration/MethodDeclarator/FormalParameters/FormalParameter/Type/PrimitiveType[@Image = 'short'] +| +//LocalVariableDeclaration/Type/PrimitiveType[@Image = 'short'] +| +//AnnotationMethodDeclaration/Type/PrimitiveType[@Image = 'short'] +``` + +**Example(s):** + +``` java +public class UsingShort { + private short doNotUseShort = 0; + + public UsingShort() { + short shouldNotBeUsed = 1; + doNotUseShort += shouldNotBeUsed; + } +} +``` + +**Use this rule by referencing it:** +``` xml + +``` + +## BigIntegerInstantiation + +**Since:** PMD 3.9 + +**Priority:** Medium (3) + +Don't create instances of already existing BigInteger (BigInteger.ZERO, BigInteger.ONE) and +for Java 1.5 onwards, BigInteger.TEN and BigDecimal (BigDecimal.ZERO, BigDecimal.ONE, BigDecimal.TEN) + +**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.performance.BigIntegerInstantiationRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/performance/BigIntegerInstantiationRule.java) + +**Example(s):** + +``` java +BigInteger bi = new BigInteger(1); // reference BigInteger.ONE instead +BigInteger bi2 = new BigInteger("0"); // reference BigInteger.ZERO instead +BigInteger bi3 = new BigInteger(0.0); // reference BigInteger.ZERO instead +BigInteger bi4; +bi4 = new BigInteger(0); // reference BigInteger.ZERO instead +``` + +**Use this rule by referencing it:** +``` xml + +``` + +## BooleanInstantiation + +**Since:** PMD 1.2 + +**Priority:** Medium High (2) + +Avoid instantiating Boolean objects; you can reference Boolean.TRUE, Boolean.FALSE, or call Boolean.valueOf() instead. +Note that new Boolean() is deprecated since JDK 9 for that reason. + +**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.performance.BooleanInstantiationRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/performance/BooleanInstantiationRule.java) + +**Example(s):** + +``` java +Boolean bar = new Boolean("true"); // unnecessary creation, just reference Boolean.TRUE; +Boolean buz = Boolean.valueOf(false); // ...., just reference Boolean.FALSE; +``` + +**Use this rule by referencing it:** +``` xml + +``` + +## ByteInstantiation + +**Since:** PMD 4.0 + +**Priority:** Medium High (2) + +Calling new Byte() causes memory allocation that can be avoided by the static Byte.valueOf(). +It makes use of an internal cache that recycles earlier instances making it more memory efficient. +Note that new Byte() is deprecated since JDK 9 for that reason. + +**This rule is defined by the following XPath expression:** +``` xpath +//AllocationExpression +[not (ArrayDimsAndInits) +and ClassOrInterfaceType[pmd-java:typeIs('java.lang.Byte')]] +``` + +**Example(s):** + +``` java +public class Foo { + private Byte i = new Byte(0); // change to Byte i = Byte.valueOf(0); +} +``` + +**Use this rule by referencing it:** +``` xml + +``` + +## ConsecutiveAppendsShouldReuse + +**Since:** PMD 5.1 + +**Priority:** Medium (3) + +Consecutive calls to StringBuffer/StringBuilder .append should be chained, reusing the target object. This can improve the performance +by producing a smaller bytecode, reducing overhead and improving inlining. A complete analysis can be found [here](https://github.com/pmd/pmd/issues/202#issuecomment-274349067) + +**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.performance.ConsecutiveAppendsShouldReuseRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/performance/ConsecutiveAppendsShouldReuseRule.java) + +**Example(s):** + +``` java +String foo = " "; + +StringBuffer buf = new StringBuffer(); +buf.append("Hello"); // poor +buf.append(foo); +buf.append("World"); + +StringBuffer buf = new StringBuffer(); +buf.append("Hello").append(foo).append("World"); // good +``` + +**Use this rule by referencing it:** +``` xml + +``` + +## ConsecutiveLiteralAppends + +**Since:** PMD 3.5 + +**Priority:** Medium (3) + +Consecutively calling StringBuffer/StringBuilder.append(...) with literals should be avoided. +Since the literals are constants, they can already be combined into a single String literal and this String +can be appended in a single method call. + +**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.performance.ConsecutiveLiteralAppendsRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/performance/ConsecutiveLiteralAppendsRule.java) + +**Example(s):** + +``` java +StringBuilder buf = new StringBuilder(); +buf.append("Hello").append(" ").append("World"); // poor +buf.append("Hello World"); // good + +buf.append('h').append('e').append('l').append('l').append('o'); // poor +buf.append("hello"); // good + +buf.append(1).append('m'); // poor +buf.append("1m"); // good +``` + +**This rule has the following properties:** + +|Name|Default Value|Description|Multivalued| +|----|-------------|-----------|-----------| +|threshold|1|Max consecutive appends|no| + +**Use this rule by referencing it:** +``` xml + +``` + +## InefficientEmptyStringCheck + +**Since:** PMD 3.6 + +**Priority:** Medium (3) + +String.trim().length() == 0 (or String.trim().isEmpty() for the same reason) is an inefficient +way to check if a String is really blank, as it creates a new String object just to check its size. +Consider creating a static function that loops through a string, checking Character.isWhitespace() +on each character and returning false if a non-whitespace character is found. A Smarter code to +check for an empty string would be: + +```java +private boolean checkTrimEmpty(String str) { + for(int i = 0; i < str.length(); i++) { + if(!Character.isWhitespace(str.charAt(i))) { + return false; + } + } + return true; +} +``` + +You can refer to Apache's StringUtils#isBlank (in commons-lang), +Spring's StringUtils#hasText (in the Spring framework) or Google's +CharMatcher#whitespace (in Guava) for existing implementations (some might +include the check for != null). + +**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.performance.InefficientEmptyStringCheckRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/performance/InefficientEmptyStringCheckRule.java) + +**Example(s):** + +``` java +public void bar(String string) { + if (string != null && string.trim().length() > 0) { + doSomething(); + } +} +``` + +**Use this rule by referencing it:** +``` xml + +``` + +## InefficientStringBuffering + +**Since:** PMD 3.4 + +**Priority:** Medium (3) + +Avoid concatenating non-literals in a StringBuffer constructor or append() since intermediate buffers will +need to be be created and destroyed by the JVM. + +**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.performance.InefficientStringBufferingRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/performance/InefficientStringBufferingRule.java) + +**Example(s):** + +``` java +// Avoid this, two buffers are actually being created here +StringBuffer sb = new StringBuffer("tmp = "+System.getProperty("java.io.tmpdir")); + +// do this instead +StringBuffer sb = new StringBuffer("tmp = "); +sb.append(System.getProperty("java.io.tmpdir")); +``` + +**Use this rule by referencing it:** +``` xml + +``` + +## InsufficientStringBufferDeclaration + +**Since:** PMD 3.6 + +**Priority:** Medium (3) + +Failing to pre-size a StringBuffer or StringBuilder properly could cause it to re-size many times +during runtime. This rule attempts to determine the total number the characters that are actually +passed into StringBuffer.append(), but represents a best guess "worst case" scenario. An empty +StringBuffer/StringBuilder constructor initializes the object to 16 characters. This default +is assumed if the length of the constructor can not be determined. + +**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.performance.InsufficientStringBufferDeclarationRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/performance/InsufficientStringBufferDeclarationRule.java) + +**Example(s):** + +``` java +StringBuffer bad = new StringBuffer(); +bad.append("This is a long string that will exceed the default 16 characters"); + +StringBuffer good = new StringBuffer(41); +good.append("This is a long string, which is pre-sized"); +``` + +**Use this rule by referencing it:** +``` xml + +``` + +## IntegerInstantiation + +**Since:** PMD 3.5 + +**Priority:** Medium High (2) + +Calling new Integer() causes memory allocation that can be avoided by the static Integer.valueOf(). +It makes use of an internal cache that recycles earlier instances making it more memory efficient. +Note that new Integer() is deprecated since JDK 9 for that reason. + +**This rule is defined by the following XPath expression:** +``` xpath +//AllocationExpression + [not (ArrayDimsAndInits) + and ClassOrInterfaceType[pmd-java:typeIs('java.lang.Integer')]] +``` + +**Example(s):** + +``` java +public class Foo { + private Integer i = new Integer(0); // change to Integer i = Integer.valueOf(0); +} +``` + +**Use this rule by referencing it:** +``` xml + +``` + +## LongInstantiation + +**Since:** PMD 4.0 + +**Priority:** Medium High (2) + +Calling new Long() causes memory allocation that can be avoided by the static Long.valueOf(). +It makes use of an internal cache that recycles earlier instances making it more memory efficient. +Note that new Long() is deprecated since JDK 9 for that reason. + +**This rule is defined by the following XPath expression:** +``` xpath +//AllocationExpression +[not (ArrayDimsAndInits) +and ClassOrInterfaceType[pmd-java:typeIs('java.lang.Long')]] +``` + +**Example(s):** + +``` java +public class Foo { + private Long i = new Long(0); // change to Long i = Long.valueOf(0); +} +``` + +**Use this rule by referencing it:** +``` xml + +``` + +## OptimizableToArrayCall + +**Since:** PMD 1.8 + +**Priority:** Medium (3) + +**Minimum Language Version:** Java 1.6 + +Calls to a collection's `toArray(E[])` method should specify a target array of zero size. This allows the JVM +to optimize the memory allocation and copying as much as possible. + +Previous versions of this rule (pre PMD 6.0.0) suggested the opposite, but current JVM implementations +perform always better, when they have full control over the target array. And allocation an array via +reflection is nowadays as fast as the direct allocation. + +See also [Arrays of Wisdom of the Ancients](https://shipilev.net/blog/2016/arrays-wisdom-ancients/) + +Note: If you don't need an array of the correct type, then the simple `toArray()` method without an array +is faster, but returns only an array of type `Object[]`. + +**This rule is defined by the following XPath expression:** +``` xpath +//PrimaryExpression +[PrimaryPrefix/Name[ends-with(@Image, 'toArray')]] +[ +PrimarySuffix/Arguments/ArgumentList/Expression + /PrimaryExpression/PrimaryPrefix/AllocationExpression + /ArrayDimsAndInits/Expression/PrimaryExpression/PrimaryPrefix[not(Literal[@Image='0'])] +] +``` + +**Example(s):** + +``` java +List foos = getFoos(); + +// much better; this one allows the jvm to allocate an array of the correct size and effectively skip +// the zeroing, since each array element will be overridden anyways +Foo[] fooArray = foos.toArray(new Foo[0]); + +// inefficient, the array needs to be zeroed out by the jvm before it is handed over to the toArray method +Foo[] fooArray = foos.toArray(new Foo[foos.size()]); +``` + +**Use this rule by referencing it:** +``` xml + +``` + +## RedundantFieldInitializer + +**Since:** PMD 5.0 + +**Priority:** Medium (3) + +Java will initialize fields with known default values so any explicit initialization of those same defaults +is redundant and results in a larger class file (approximately three additional bytecode instructions per field). + +**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.performance.RedundantFieldInitializerRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/performance/RedundantFieldInitializerRule.java) + +**Example(s):** + +``` java +public class C { + boolean b = false; // examples of redundant initializers + byte by = 0; + short s = 0; + char c = 0; + int i = 0; + long l = 0; + + float f = .0f; // all possible float literals + doable d = 0d; // all possible double literals + Object o = null; + + MyClass mca[] = null; + int i1 = 0, ia1[] = null; + + class Nested { + boolean b = false; + } +} +``` + +**Use this rule by referencing it:** +``` xml + +``` + +## ShortInstantiation + +**Since:** PMD 4.0 + +**Priority:** Medium High (2) + +Calling new Short() causes memory allocation that can be avoided by the static Short.valueOf(). +It makes use of an internal cache that recycles earlier instances making it more memory efficient. +Note that new Short() is deprecated since JDK 9 for that reason. + +**This rule is defined by the following XPath expression:** +``` xpath +//AllocationExpression +[not (ArrayDimsAndInits) +and ClassOrInterfaceType[pmd-java:typeIs('java.lang.Short')]] +``` + +**Example(s):** + +``` java +public class Foo { + private Short i = new Short(0); // change to Short i = Short.valueOf(0); +} +``` + +**Use this rule by referencing it:** +``` xml + +``` + +## SimplifyStartsWith + +**Since:** PMD 3.1 + +**Priority:** Medium (3) + +Since it passes in a literal of length 1, calls to (string).startsWith can be rewritten using (string).charAt(0) +at the expense of some readability. + +**This rule is defined by the following XPath expression:** +``` xpath +//PrimaryExpression + [PrimaryPrefix/Name + [ends-with(@Image, '.startsWith')] or PrimarySuffix[@Image='startsWith']] + [PrimarySuffix/Arguments/ArgumentList + /Expression/PrimaryExpression/PrimaryPrefix + /Literal + [string-length(@Image)=3] + [starts-with(@Image, '"')] + [ends-with(@Image, '"')] + ] +``` + +**Example(s):** + +``` java +public class Foo { + + boolean checkIt(String x) { + return x.startsWith("a"); // suboptimal + } + + boolean fasterCheckIt(String x) { + return x.charAt(0) == 'a'; // faster approach + } +} +``` + +**Use this rule by referencing it:** +``` xml + +``` + +## StringInstantiation + +**Since:** PMD 1.0 + +**Priority:** Medium High (2) + +Avoid instantiating String objects; this is usually unnecessary since they are immutable and can be safely shared. + +**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.performance.StringInstantiationRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/performance/StringInstantiationRule.java) + +**Example(s):** + +``` java +private String bar = new String("bar"); // just do a String bar = "bar"; +``` + +**Use this rule by referencing it:** +``` xml + +``` + +## StringToString + +**Since:** PMD 1.0 + +**Priority:** Medium (3) + +Avoid calling toString() on objects already known to be string instances; this is unnecessary. + +**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.performance.StringToStringRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/performance/StringToStringRule.java) + +**Example(s):** + +``` java +private String baz() { + String bar = "howdy"; + return bar.toString(); +} +``` + +**Use this rule by referencing it:** +``` xml + +``` + +## TooFewBranchesForASwitchStatement + +**Since:** PMD 4.2 + +**Priority:** Medium (3) + +Switch statements are intended to be used to support complex branching behaviour. Using a switch for only a few +cases is ill-advised, since switches are not as easy to understand as if-then statements. In these cases use the +if-then statement to increase code readability. + +**This rule is defined by the following XPath expression:** +``` xpath +//SwitchStatement[ + (count(.//SwitchLabel) < $minimumNumberCaseForASwitch) +] +``` + +**Example(s):** + +``` java +// With a minimumNumberCaseForASwitch of 3 +public class Foo { + public void bar() { + switch (condition) { + case ONE: + instruction; + break; + default: + break; // not enough for a 'switch' stmt, a simple 'if' stmt would have been more appropriate + } + } +} +``` + +**This rule has the following properties:** + +|Name|Default Value|Description|Multivalued| +|----|-------------|-----------|-----------| +|minimumNumberCaseForASwitch|3|Minimum number of branches for a switch|no| + +**Use this rule by referencing it:** +``` xml + +``` + +## UnnecessaryWrapperObjectCreation + +**Since:** PMD 3.8 + +**Priority:** Medium (3) + +Most wrapper classes provide static conversion methods that avoid the need to create intermediate objects +just to create the primitive forms. Using these avoids the cost of creating objects that also need to be +garbage-collected later. + +**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.performance.UnnecessaryWrapperObjectCreationRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/performance/UnnecessaryWrapperObjectCreationRule.java) + +**Example(s):** + +``` java +public int convert(String s) { + int i, i2; + + i = Integer.valueOf(s).intValue(); // this wastes an object + i = Integer.parseInt(s); // this is better + + i2 = Integer.valueOf(i).intValue(); // this wastes an object + i2 = i; // this is better + + String s3 = Integer.valueOf(i2).toString(); // this wastes an object + s3 = Integer.toString(i2); // this is better + + return i2; +} +``` + +**Use this rule by referencing it:** +``` xml + +``` + +## UseArrayListInsteadOfVector + +**Since:** PMD 3.0 + +**Priority:** Medium (3) + +ArrayList is a much better Collection implementation than Vector if thread-safe operation is not required. + +**This rule is defined by the following XPath expression:** +``` xpath +//CompilationUnit[count(ImportDeclaration) = 0 or count(ImportDeclaration/Name[@Image='java.util.Vector']) > 0] + //AllocationExpression/ClassOrInterfaceType + [@Image='Vector' or @Image='java.util.Vector'] +``` + +**Example(s):** + +``` java +public class SimpleTest extends TestCase { + public void testX() { + Collection c1 = new Vector(); + Collection c2 = new ArrayList(); // achieves the same with much better performance + } +} +``` + +**Use this rule by referencing it:** +``` xml + +``` + +## UseArraysAsList + +**Since:** PMD 3.5 + +**Priority:** Medium (3) + + +The java.util.Arrays class has a "asList" method that should be used when you want to create a new List from +an array of objects. It is faster than executing a loop to copy all the elements of the array one by one. + +Note that the result of Arrays.asList() is backed by the specified array, +changes in the returned list will result in the array to be modified. +For that reason, it is not possible to add new elements to the returned list of Arrays.asList() (UnsupportedOperationException). +You must use new ArrayList<>(Arrays.asList(...)) if that is inconvenient for you (e.g. because of concurrent access). + + + +**This rule is defined by the following XPath expression:** +``` xpath +//Statement[ + (ForStatement) and (ForStatement//VariableInitializer//Literal[@IntLiteral='true' and @Image='0']) and (count(.//IfStatement)=0) + ] + //StatementExpression[ + PrimaryExpression/PrimaryPrefix/Name[ + substring-before(@Image,'.add') = ancestor::MethodDeclaration//LocalVariableDeclaration[ + ./Type//ClassOrInterfaceType[ + @Image = 'Collection' or + @Image = 'List' or @Image='ArrayList' + ] + ] + /VariableDeclarator/VariableDeclaratorId[ + count(..//AllocationExpression/ClassOrInterfaceType[ + @Image="ArrayList" + ] + )=1 + ]/@Image + ] + and + PrimaryExpression/PrimarySuffix/Arguments/ArgumentList/Expression/PrimaryExpression/PrimaryPrefix/Name + [ + @Image = ancestor::MethodDeclaration//LocalVariableDeclaration[@Array="true"]/VariableDeclarator/VariableDeclaratorId/@Image + or + @Image = ancestor::MethodDeclaration//FormalParameter/VariableDeclaratorId/@Image + ] + /../..[count(.//PrimarySuffix) + =1]/PrimarySuffix/Expression/PrimaryExpression/PrimaryPrefix + /Name + ] +``` + +**Example(s):** + +``` java +public class Test { + public void foo(Integer[] ints) { + // could just use Arrays.asList(ints) + List l= new ArrayList<>(100); + for (int i=0; i< 100; i++) { + l.add(ints[i]); + } + for (int i=0; i< 100; i++) { + l.add(a[i].toString()); // won't trigger the rule + } + } +} +``` + +**Use this rule by referencing it:** +``` xml + +``` + +## UseIndexOfChar + +**Since:** PMD 3.5 + +**Priority:** Medium (3) + +Use String.indexOf(char) when checking for the index of a single character; it executes faster. + +**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.performance.UseIndexOfCharRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/performance/UseIndexOfCharRule.java) + +**Example(s):** + +``` java +String s = "hello world"; +// avoid this +if (s.indexOf("d") {} +// instead do this +if (s.indexOf('d') {} +``` + +**Use this rule by referencing it:** +``` xml + +``` + +## UselessStringValueOf + +**Since:** PMD 3.8 + +**Priority:** Medium (3) + +No need to call String.valueOf to append to a string; just use the valueOf() argument directly. + +**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.performance.UselessStringValueOfRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/performance/UselessStringValueOfRule.java) + +**Example(s):** + +``` java +public String convert(int i) { + String s; + s = "a" + String.valueOf(i); // not required + s = "a" + i; // preferred approach + return s; +} +``` + +**Use this rule by referencing it:** +``` xml + +``` + +## UseStringBufferForStringAppends + +**Since:** PMD 3.1 + +**Priority:** Medium (3) + +The use of the '+=' operator for appending strings causes the JVM to create and use an internal StringBuffer. +If a non-trivial number of these concatenations are being used then the explicit use of a StringBuilder or +threadsafe StringBuffer is recommended to avoid this. + +**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.performance.UseStringBufferForStringAppendsRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/performance/UseStringBufferForStringAppendsRule.java) + +**Example(s):** + +``` java +public class Foo { + void bar() { + String a; + a = "foo"; + a += " bar"; + // better would be: + // StringBuilder a = new StringBuilder("foo"); + // a.append(" bar"); + } +} +``` + +**Use this rule by referencing it:** +``` xml + +``` + +## UseStringBufferLength + +**Since:** PMD 3.4 + +**Priority:** Medium (3) + +Use StringBuffer.length() to determine StringBuffer length rather than using StringBuffer.toString().equals("") +or StringBuffer.toString().length() == ... + +**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.performance.UseStringBufferLengthRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/performance/UseStringBufferLengthRule.java) + +**Example(s):** + +``` java +StringBuffer sb = new StringBuffer(); + +if (sb.toString().equals("")) {} // inefficient + +if (sb.length() == 0) {} // preferred +``` + +**Use this rule by referencing it:** +``` xml + +``` + diff --git a/docs/pages/pmd/rules/java/security.md b/docs/pages/pmd/rules/java/security.md new file mode 100644 index 00000000000..5cb70423d63 --- /dev/null +++ b/docs/pages/pmd/rules/java/security.md @@ -0,0 +1,74 @@ +--- +title: Security +summary: Rules that flag potential security flaws. +permalink: pmd_rules_java_security.html +folder: pmd/rules/java +sidebaractiveurl: /pmd_rules_java.html +editmepath: ../pmd-java/src/main/resources/category/java/security.xml +keywords: Security, HardCodedCryptoKey, InsecureCryptoIv +language: Java +--- +## HardCodedCryptoKey + +**Since:** PMD 6.4.0 + +**Priority:** Medium (3) + +Do not use hard coded values for cryptographic operations. Please store keys outside of source code. + +**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.security.HardCodedCryptoKeyRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/security/HardCodedCryptoKeyRule.java) + +**Example(s):** + +``` java +public class Foo { + void good() { + SecretKeySpec secretKeySpec = new SecretKeySpec(Properties.getKey(), "AES"); + } + + void bad() { + SecretKeySpec secretKeySpec = new SecretKeySpec("my secret here".getBytes(), "AES"); + } +} +``` + +**Use this rule by referencing it:** +``` xml + +``` + +## InsecureCryptoIv + +**Since:** PMD 6.3.0 + +**Priority:** Medium (3) + +Do not use hard coded initialization vector in cryptographic operations. Please use a randomly generated IV. + +**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.security.InsecureCryptoIvRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/security/InsecureCryptoIvRule.java) + +**Example(s):** + +``` java +public class Foo { + void good() { + SecureRandom random = new SecureRandom(); + byte iv[] = new byte[16]; + random.nextBytes(bytes); + } + + void bad() { + byte[] iv = new byte[] { 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, }; + } + + void alsoBad() { + byte[] iv = "secret iv in here".getBytes(); + } +} +``` + +**Use this rule by referencing it:** +``` xml + +``` + diff --git a/docs/pages/pmd/rules/java/strictexception.md b/docs/pages/pmd/rules/java/strictexception.md deleted file mode 100644 index bb3ee1265ee..00000000000 --- a/docs/pages/pmd/rules/java/strictexception.md +++ /dev/null @@ -1,403 +0,0 @@ ---- -title: Strict Exceptions -summary: These rules provide some strict guidelines about throwing and catching exceptions. -permalink: pmd_rules_java_strictexception.html -folder: pmd/rules/java -sidebaractiveurl: /pmd_rules_java.html -editmepath: ../pmd-java/src/main/resources/rulesets/java/strictexception.xml -keywords: Strict Exceptions, AvoidCatchingThrowable, SignatureDeclareThrowsException, ExceptionAsFlowControl, AvoidCatchingNPE, AvoidThrowingRawExceptionTypes, AvoidThrowingNullPointerException, AvoidRethrowingException, DoNotExtendJavaLangError, DoNotThrowExceptionInFinally, AvoidThrowingNewInstanceOfSameException, AvoidCatchingGenericException, AvoidLosingExceptionInformation ---- -## AvoidCatchingGenericException - -**Since:** PMD 4.2.6 - -**Priority:** Medium (3) - -Avoid catching generic exceptions such as NullPointerException, RuntimeException, Exception in try-catch block - -``` -//CatchStatement/FormalParameter/Type/ReferenceType/ClassOrInterfaceType[ - @Image='NullPointerException' or - @Image='Exception' or - @Image='RuntimeException'] -``` - -**Example(s):** - -``` java -package com.igate.primitive; - -public class PrimitiveType { - - public void downCastPrimitiveType() { - try { - System.out.println(" i [" + i + "]"); - } catch(Exception e) { - e.printStackTrace(); - } catch(RuntimeException e) { - e.printStackTrace(); - } catch(NullPointerException e) { - e.printStackTrace(); - } - } -} -``` - -**Use this rule by referencing it:** -``` xml - -``` - -## AvoidCatchingNPE - -**Since:** PMD 1.8 - -**Priority:** Medium (3) - -Code should never throw NullPointerExceptions under normal circumstances. A catch block may hide the -original error, causing other, more subtle problems later on. - -``` -//CatchStatement/FormalParameter/Type - /ReferenceType/ClassOrInterfaceType[@Image='NullPointerException'] -``` - -**Example(s):** - -``` java -public class Foo { - void bar() { - try { - // do something - } catch (NullPointerException npe) { - } - } -} -``` - -**Use this rule by referencing it:** -``` xml - -``` - -## AvoidCatchingThrowable - -**Since:** PMD 1.2 - -**Priority:** Medium (3) - -Catching Throwable errors is not recommended since its scope is very broad. It includes runtime issues such as -OutOfMemoryError that should be exposed and managed separately. - -**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.strictexception.AvoidCatchingThrowableRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/strictexception/AvoidCatchingThrowableRule.java) - -**Example(s):** - -``` java -public void bar() { - try { - // do something - } catch (Throwable th) { // should not catch Throwable - th.printStackTrace(); - } -} -``` - -**Use this rule by referencing it:** -``` xml - -``` - -## AvoidLosingExceptionInformation - -**Since:** PMD 4.2.6 - -**Priority:** Medium High (2) - -Statements in a catch block that invoke accessors on the exception without using the information -only add to code size. Either remove the invocation, or use the return result. - -``` -//CatchStatement/Block/BlockStatement/Statement/StatementExpression/PrimaryExpression/PrimaryPrefix/Name -[ - @Image = concat(../../../../../../../FormalParameter/VariableDeclaratorId/@Image, '.getMessage') - or - @Image = concat(../../../../../../../FormalParameter/VariableDeclaratorId/@Image, '.getLocalizedMessage') - or - @Image = concat(../../../../../../../FormalParameter/VariableDeclaratorId/@Image, '.getCause') - or - @Image = concat(../../../../../../../FormalParameter/VariableDeclaratorId/@Image, '.getStackTrace') - or - @Image = concat(../../../../../../../FormalParameter/VariableDeclaratorId/@Image, '.toString') -] -``` - -**Example(s):** - -``` java -public void bar() { - try { - // do something - } catch (SomeException se) { - se.getMessage(); - } -} -``` - -**Use this rule by referencing it:** -``` xml - -``` - -## AvoidRethrowingException - -**Since:** PMD 3.8 - -**Priority:** Medium (3) - -Catch blocks that merely rethrow a caught exception only add to code size and runtime complexity. - -``` -//CatchStatement[FormalParameter - /VariableDeclaratorId/@Image = Block/BlockStatement/Statement - /ThrowStatement/Expression/PrimaryExpression[count(PrimarySuffix)=0]/PrimaryPrefix/Name/@Image - and count(Block/BlockStatement/Statement) =1] -``` - -**Example(s):** - -``` java -public void bar() { - try { - // do something - } catch (SomeException se) { - throw se; - } -} -``` - -**Use this rule by referencing it:** -``` xml - -``` - -## AvoidThrowingNewInstanceOfSameException - -**Since:** PMD 4.2.5 - -**Priority:** Medium (3) - -Catch blocks that merely rethrow a caught exception wrapped inside a new instance of the same type only add to -code size and runtime complexity. - -``` -//CatchStatement[ - count(Block/BlockStatement/Statement) = 1 - and - FormalParameter/Type/ReferenceType/ClassOrInterfaceType/@Image = Block/BlockStatement/Statement/ThrowStatement/Expression/PrimaryExpression/PrimaryPrefix/AllocationExpression/ClassOrInterfaceType/@Image - and - count(Block/BlockStatement/Statement/ThrowStatement/Expression/PrimaryExpression/PrimaryPrefix/AllocationExpression/Arguments/ArgumentList/Expression) = 1 - and - FormalParameter/VariableDeclaratorId = Block/BlockStatement/Statement/ThrowStatement/Expression/PrimaryExpression/PrimaryPrefix/AllocationExpression/Arguments/ArgumentList/Expression/PrimaryExpression/PrimaryPrefix/Name - ] -``` - -**Example(s):** - -``` java -public void bar() { - try { - // do something - } catch (SomeException se) { - // harmless comment - throw new SomeException(se); - } -} -``` - -**Use this rule by referencing it:** -``` xml - -``` - -## AvoidThrowingNullPointerException - -**Since:** PMD 1.8 - -**Priority:** High (1) - -Avoid throwing NullPointerExceptions. These are confusing because most people will assume that the -virtual machine threw it. Consider using an IllegalArgumentException instead; this will be -clearly seen as a programmer-initiated exception. - -``` -//AllocationExpression/ClassOrInterfaceType[@Image='NullPointerException'] -``` - -**Example(s):** - -``` java -public class Foo { - void bar() { - throw new NullPointerException(); - } -} -``` - -**Use this rule by referencing it:** -``` xml - -``` - -## AvoidThrowingRawExceptionTypes - -**Since:** PMD 1.8 - -**Priority:** High (1) - -Avoid throwing certain exception types. Rather than throw a raw RuntimeException, Throwable, -Exception, or Error, use a subclassed exception or error instead. - -``` -//ThrowStatement//AllocationExpression - /ClassOrInterfaceType[ - (@Image='Throwable' and count(//ImportDeclaration/Name[ends-with(@Image,'Throwable')]) = 0) -or - (@Image='Exception' and count(//ImportDeclaration/Name[ends-with(@Image,'Exception')]) = 0) -or - (@Image='Error' and count(//ImportDeclaration/Name[ends-with(@Image,'Error')]) = 0) -or -( @Image='RuntimeException' and count(//ImportDeclaration/Name[ends-with(@Image,'RuntimeException')]) = 0) -] -``` - -**Example(s):** - -``` java -public class Foo { - public void bar() throws Exception { - throw new Exception(); - } -} -``` - -**Use this rule by referencing it:** -``` xml - -``` - -## DoNotExtendJavaLangError - -**Since:** PMD 4.0 - -**Priority:** Medium (3) - -Errors are system exceptions. Do not extend them. - -``` -//ClassOrInterfaceDeclaration/ExtendsList/ClassOrInterfaceType - [@Image="Error" or @Image="java.lang.Error"] -``` - -**Example(s):** - -``` java -public class Foo extends Error { } -``` - -**Use this rule by referencing it:** -``` xml - -``` - -## DoNotThrowExceptionInFinally - -**Since:** PMD 4.2 - -**Priority:** Medium Low (4) - -Throwing exceptions within a 'finally' block is confusing since they may mask other exceptions -or code defects. -Note: This is a PMD implementation of the Lint4j rule "A throw in a finally block" - -``` -//FinallyStatement[descendant::ThrowStatement] -``` - -**Example(s):** - -``` java -public class Foo { - public void bar() { - try { - // Here do some stuff - } catch( Exception e) { - // Handling the issue - } finally { - // is this really a good idea ? - throw new Exception(); - } - } -} -``` - -**Use this rule by referencing it:** -``` xml - -``` - -## ExceptionAsFlowControl - -**Since:** PMD 1.8 - -**Priority:** Medium (3) - -Using Exceptions as form of flow control is not recommended as they obscure true exceptions when debugging. -Either add the necessary validation or use an alternate control structure. - -**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.strictexception.ExceptionAsFlowControlRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/strictexception/ExceptionAsFlowControlRule.java) - -**Example(s):** - -``` java -public void bar() { - try { - try { - } catch (Exception e) { - throw new WrapperException(e); - // this is essentially a GOTO to the WrapperException catch block - } - } catch (WrapperException e) { - // do some more stuff - } -} -``` - -**Use this rule by referencing it:** -``` xml - -``` - -## SignatureDeclareThrowsException - -**Since:** PMD 1.2 - -**Priority:** Medium (3) - -Methods that declare the generic Exception as a possible throwable are not very helpful since their -failure modes are unclear. Use a class derived from RuntimeException or a more specific checked exception. - -**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.strictexception.SignatureDeclareThrowsExceptionRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/strictexception/SignatureDeclareThrowsExceptionRule.java) - -**Example(s):** - -``` java -public void foo() throws Exception { -} -``` - -**Use this rule by referencing it:** -``` xml - -``` - diff --git a/docs/pages/pmd/rules/java/strings.md b/docs/pages/pmd/rules/java/strings.md deleted file mode 100644 index 085f2a21087..00000000000 --- a/docs/pages/pmd/rules/java/strings.md +++ /dev/null @@ -1,471 +0,0 @@ ---- -title: String and StringBuffer -summary: These rules deal with different issues that can arise with manipulation of the String, StringBuffer, or StringBuilder instances. -permalink: pmd_rules_java_strings.html -folder: pmd/rules/java -sidebaractiveurl: /pmd_rules_java.html -editmepath: ../pmd-java/src/main/resources/rulesets/java/strings.xml -keywords: String and StringBuffer, AvoidDuplicateLiterals, StringInstantiation, StringToString, InefficientStringBuffering, UnnecessaryCaseChange, UseStringBufferLength, AppendCharacterWithChar, ConsecutiveAppendsShouldReuse, ConsecutiveLiteralAppends, UseIndexOfChar, InefficientEmptyStringCheck, InsufficientStringBufferDeclaration, UselessStringValueOf, StringBufferInstantiationWithChar, UseEqualsToCompareStrings, AvoidStringBufferField ---- -## AppendCharacterWithChar - -**Since:** PMD 3.5 - -**Priority:** Medium (3) - -Avoid concatenating characters as strings in StringBuffer/StringBuilder.append methods. - -**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.strings.AppendCharacterWithCharRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/strings/AppendCharacterWithCharRule.java) - -**Example(s):** - -``` java -StringBuffer sb = new StringBuffer(); -sb.append("a"); // avoid this - -StringBuffer sb = new StringBuffer(); -sb.append('a'); // use this instead -``` - -**Use this rule by referencing it:** -``` xml - -``` - -## AvoidDuplicateLiterals - -**Since:** PMD 1.0 - -**Priority:** Medium (3) - -Code containing duplicate String literals can usually be improved by declaring the String as a constant field. - -**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.strings.AvoidDuplicateLiteralsRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/strings/AvoidDuplicateLiteralsRule.java) - -**Example(s):** - -``` java -private void bar() { - buz("Howdy"); - buz("Howdy"); - buz("Howdy"); - buz("Howdy"); -} -private void buz(String x) {} -``` - -**This rule has the following properties:** - -|Name|Default Value|Description| -|----|-------------|-----------| -|exceptionfile||File containing strings to skip (one string per line), only used if ignore list is not set| -|separator|,|Ignore list separator| -|exceptionList||Strings to ignore| -|maxDuplicateLiterals|4|Max duplicate literals| -|minimumLength|3|Minimum string length to check| -|skipAnnotations|false|Skip literals within annotations| - -**Use this rule by referencing it:** -``` xml - -``` - -## AvoidStringBufferField - -**Since:** PMD 4.2 - -**Priority:** Medium (3) - -StringBuffers/StringBuilders can grow considerably, and so may become a source of memory leaks -if held within objects with long lifetimes. - -``` -//FieldDeclaration/Type/ReferenceType/ClassOrInterfaceType[@Image = 'StringBuffer' or @Image = 'StringBuilder'] -``` - -**Example(s):** - -``` java -public class Foo { - private StringBuffer buffer; // potential memory leak as an instance variable; -} -``` - -**Use this rule by referencing it:** -``` xml - -``` - -## ConsecutiveAppendsShouldReuse - -**Since:** PMD 5.1 - -**Priority:** Medium (3) - -Consecutive calls to StringBuffer/StringBuilder .append should be chained, reusing the target object. This can improve the performance -by producing a smaller bytecode, reducing overhead and improving inlining. A complete analysis can be found [here](https://github.com/pmd/pmd/issues/202#issuecomment-274349067) - -**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.strings.ConsecutiveAppendsShouldReuseRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/strings/ConsecutiveAppendsShouldReuseRule.java) - -**Example(s):** - -``` java -String foo = " "; - -StringBuffer buf = new StringBuffer(); -buf.append("Hello"); // poor -buf.append(foo); -buf.append("World"); - -StringBuffer buf = new StringBuffer(); -buf.append("Hello").append(foo).append("World"); // good -``` - -**Use this rule by referencing it:** -``` xml - -``` - -## ConsecutiveLiteralAppends - -**Since:** PMD 3.5 - -**Priority:** Medium (3) - -Consecutively calling StringBuffer/StringBuilder.append with String literals - -**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.strings.ConsecutiveLiteralAppendsRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/strings/ConsecutiveLiteralAppendsRule.java) - -**Example(s):** - -``` java -StringBuffer buf = new StringBuffer(); -buf.append("Hello").append(" ").append("World"); // poor -buf.append("Hello World"); // good -``` - -**This rule has the following properties:** - -|Name|Default Value|Description| -|----|-------------|-----------| -|threshold|1|Max consecutive appends| - -**Use this rule by referencing it:** -``` xml - -``` - -## InefficientEmptyStringCheck - -**Since:** PMD 3.6 - -**Priority:** Medium (3) - -String.trim().length() is an inefficient way to check if a String is really empty, as it -creates a new String object just to check its size. Consider creating a static function that -loops through a string, checking Character.isWhitespace() on each character and returning -false if a non-whitespace character is found. You can refer to Apache's StringUtils#isBlank (in commons-lang), -Spring's StringUtils#hasText (in the Spring framework) or Google's CharMatcher#whitespace (in Guava) for -existing implementations. - -**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.strings.InefficientEmptyStringCheckRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/strings/InefficientEmptyStringCheckRule.java) - -**Example(s):** - -``` java -public void bar(String string) { - if (string != null && string.trim().size() > 0) { - doSomething(); - } -} -``` - -**Use this rule by referencing it:** -``` xml - -``` - -## InefficientStringBuffering - -**Since:** PMD 3.4 - -**Priority:** Medium (3) - -Avoid concatenating non-literals in a StringBuffer constructor or append() since intermediate buffers will -need to be be created and destroyed by the JVM. - -**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.strings.InefficientStringBufferingRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/strings/InefficientStringBufferingRule.java) - -**Example(s):** - -``` java -// Avoid this, two buffers are actually being created here -StringBuffer sb = new StringBuffer("tmp = "+System.getProperty("java.io.tmpdir")); - -// do this instead -StringBuffer sb = new StringBuffer("tmp = "); -sb.append(System.getProperty("java.io.tmpdir")); -``` - -**Use this rule by referencing it:** -``` xml - -``` - -## InsufficientStringBufferDeclaration - -**Since:** PMD 3.6 - -**Priority:** Medium (3) - -Failing to pre-size a StringBuffer or StringBuilder properly could cause it to re-size many times -during runtime. This rule attempts to determine the total number the characters that are actually -passed into StringBuffer.append(), but represents a best guess "worst case" scenario. An empty -StringBuffer/StringBuilder constructor initializes the object to 16 characters. This default -is assumed if the length of the constructor can not be determined. - -**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.strings.InsufficientStringBufferDeclarationRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/strings/InsufficientStringBufferDeclarationRule.java) - -**Example(s):** - -``` java -StringBuffer bad = new StringBuffer(); -bad.append("This is a long string that will exceed the default 16 characters"); - -StringBuffer good = new StringBuffer(41); -good.append("This is a long string, which is pre-sized"); -``` - -**Use this rule by referencing it:** -``` xml - -``` - -## StringBufferInstantiationWithChar - -**Since:** PMD 3.9 - -**Priority:** Medium Low (4) - -Individual character values provided as initialization arguments will be converted into integers. -This can lead to internal buffer sizes that are larger than expected. Some examples: - -``` -new StringBuffer() // 16 -new StringBuffer(6) // 6 -new StringBuffer("hello world") // 11 + 16 = 27 -new StringBuffer('A') // chr(A) = 65 -new StringBuffer("A") // 1 + 16 = 17 - -new StringBuilder() // 16 -new StringBuilder(6) // 6 -new StringBuilder("hello world") // 11 + 16 = 27 -new StringBuilder('C') // chr(C) = 67 -new StringBuilder("A") // 1 + 16 = 17 -``` - -``` -//AllocationExpression/ClassOrInterfaceType -[@Image='StringBuffer' or @Image='StringBuilder'] -/../Arguments/ArgumentList/Expression/PrimaryExpression -/PrimaryPrefix/ -Literal - [starts-with(@Image, "'")] - [ends-with(@Image, "'")] -``` - -**Example(s):** - -``` java -// misleading instantiation, these buffers -// are actually sized to 99 characters long -StringBuffer sb1 = new StringBuffer('c'); -StringBuilder sb2 = new StringBuilder('c'); - -// in these forms, just single characters are allocated -StringBuffer sb3 = new StringBuffer("c"); -StringBuilder sb4 = new StringBuilder("c"); -``` - -**Use this rule by referencing it:** -``` xml - -``` - -## StringInstantiation - -**Since:** PMD 1.0 - -**Priority:** Medium High (2) - -Avoid instantiating String objects; this is usually unnecessary since they are immutable and can be safely shared. - -**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.strings.StringInstantiationRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/strings/StringInstantiationRule.java) - -**Example(s):** - -``` java -private String bar = new String("bar"); // just do a String bar = "bar"; -``` - -**Use this rule by referencing it:** -``` xml - -``` - -## StringToString - -**Since:** PMD 1.0 - -**Priority:** Medium (3) - -Avoid calling toString() on objects already known to be string instances; this is unnecessary. - -**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.strings.StringToStringRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/strings/StringToStringRule.java) - -**Example(s):** - -``` java -private String baz() { - String bar = "howdy"; - return bar.toString(); -} -``` - -**Use this rule by referencing it:** -``` xml - -``` - -## UnnecessaryCaseChange - -**Since:** PMD 3.3 - -**Priority:** Medium (3) - -Using equalsIgnoreCase() is faster than using toUpperCase/toLowerCase().equals() - -**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.strings.UnnecessaryCaseChangeRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/strings/UnnecessaryCaseChangeRule.java) - -**Example(s):** - -``` java -boolean answer1 = buz.toUpperCase().equals("baz"); // should be buz.equalsIgnoreCase("baz") - -boolean answer2 = buz.toUpperCase().equalsIgnoreCase("baz"); // another unnecessary toUpperCase() -``` - -**Use this rule by referencing it:** -``` xml - -``` - -## UseEqualsToCompareStrings - -**Since:** PMD 4.1 - -**Priority:** Medium (3) - -Using '==' or '!=' to compare strings only works if intern version is used on both sides. -Use the equals() method instead. - -``` -//EqualityExpression/PrimaryExpression -[(PrimaryPrefix/Literal - [starts-with(@Image, '"')] - [ends-with(@Image, '"')] -and count(PrimarySuffix) = 0)] -``` - -**Example(s):** - -``` java -public boolean test(String s) { - if (s == "one") return true; // unreliable - if ("two".equals(s)) return true; // better - return false; -} -``` - -**Use this rule by referencing it:** -``` xml - -``` - -## UseIndexOfChar - -**Since:** PMD 3.5 - -**Priority:** Medium (3) - -Use String.indexOf(char) when checking for the index of a single character; it executes faster. - -**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.strings.UseIndexOfCharRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/strings/UseIndexOfCharRule.java) - -**Example(s):** - -``` java -String s = "hello world"; -// avoid this -if (s.indexOf("d") {} -// instead do this -if (s.indexOf('d') {} -``` - -**Use this rule by referencing it:** -``` xml - -``` - -## UselessStringValueOf - -**Since:** PMD 3.8 - -**Priority:** Medium (3) - -No need to call String.valueOf to append to a string; just use the valueOf() argument directly. - -**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.strings.UselessStringValueOfRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/strings/UselessStringValueOfRule.java) - -**Example(s):** - -``` java -public String convert(int i) { - String s; - s = "a" + String.valueOf(i); // not required - s = "a" + i; // preferred approach - return s; -} -``` - -**Use this rule by referencing it:** -``` xml - -``` - -## UseStringBufferLength - -**Since:** PMD 3.4 - -**Priority:** Medium (3) - -Use StringBuffer.length() to determine StringBuffer length rather than using StringBuffer.toString().equals("") -or StringBuffer.toString().length() == ... - -**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.strings.UseStringBufferLengthRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/strings/UseStringBufferLengthRule.java) - -**Example(s):** - -``` java -StringBuffer sb = new StringBuffer(); - -if (sb.toString().equals("")) {} // inefficient - -if (sb.length() == 0) {} // preferred -``` - -**Use this rule by referencing it:** -``` xml - -``` - diff --git a/docs/pages/pmd/rules/java/sunsecure.md b/docs/pages/pmd/rules/java/sunsecure.md deleted file mode 100644 index f373281c626..00000000000 --- a/docs/pages/pmd/rules/java/sunsecure.md +++ /dev/null @@ -1,65 +0,0 @@ ---- -title: Security Code Guidelines -summary: These rules check the security guidelines from Sun, published at http://java.sun.com/security/seccodeguide.html#gcg -permalink: pmd_rules_java_sunsecure.html -folder: pmd/rules/java -sidebaractiveurl: /pmd_rules_java.html -editmepath: ../pmd-java/src/main/resources/rulesets/java/sunsecure.xml -keywords: Security Code Guidelines, MethodReturnsInternalArray, ArrayIsStoredDirectly ---- -## ArrayIsStoredDirectly - -**Since:** PMD 2.2 - -**Priority:** Medium (3) - -Constructors and methods receiving arrays should clone objects and store the copy. -This prevents future changes from the user from affecting the original array. - -**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.sunsecure.ArrayIsStoredDirectlyRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/sunsecure/ArrayIsStoredDirectlyRule.java) - -**Example(s):** - -``` java -public class Foo { - private String [] x; - public void foo (String [] param) { - // Don't do this, make a copy of the array at least - this.x=param; - } -} -``` - -**Use this rule by referencing it:** -``` xml - -``` - -## MethodReturnsInternalArray - -**Since:** PMD 2.2 - -**Priority:** Medium (3) - -Exposing internal arrays to the caller violates object encapsulation since elements can be -removed or replaced outside of the object that owns it. It is safer to return a copy of the array. - -**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.sunsecure.MethodReturnsInternalArrayRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/sunsecure/MethodReturnsInternalArrayRule.java) - -**Example(s):** - -``` java -public class SecureSystem { - UserData [] ud; - public UserData [] getUserData() { - // Don't return directly the internal array, return a copy - return ud; - } -} -``` - -**Use this rule by referencing it:** -``` xml - -``` - diff --git a/docs/pages/pmd/rules/java/unnecessary.md b/docs/pages/pmd/rules/java/unnecessary.md deleted file mode 100644 index 42f194a7efa..00000000000 --- a/docs/pages/pmd/rules/java/unnecessary.md +++ /dev/null @@ -1,408 +0,0 @@ ---- -title: Unnecessary -summary: The Unnecessary Ruleset contains a collection of rules for unnecessary code. -permalink: pmd_rules_java_unnecessary.html -folder: pmd/rules/java -sidebaractiveurl: /pmd_rules_java.html -editmepath: ../pmd-java/src/main/resources/rulesets/java/unnecessary.xml -keywords: Unnecessary, UnnecessaryConversionTemporary, UnnecessaryReturn, UnnecessaryFinalModifier, UselessOverridingMethod, UselessOperationOnImmutable, UnusedNullCheckInEquals, UselessParentheses, UselessQualifiedThis, UnnecessaryModifier ---- -## UnnecessaryConversionTemporary - -**Since:** PMD 0.1 - -**Priority:** Medium (3) - -Avoid the use temporary objects when converting primitives to Strings. Use the static conversion methods -on the wrapper classes instead. - -**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.unnecessary.UnnecessaryConversionTemporaryRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/unnecessary/UnnecessaryConversionTemporaryRule.java) - -**Example(s):** - -``` java -public String convert(int x) { - String foo = new Integer(x).toString(); // this wastes an object - - return Integer.toString(x); // preferred approach -} -``` - -**Use this rule by referencing it:** -``` xml - -``` - -## UnnecessaryFinalModifier - -**Since:** PMD 3.0 - -**Priority:** Medium (3) - -When a class has the final modifier, all the methods are automatically final and do not need to be -tagged as such. Similarly, methods that can't be overridden (private methods, methods of anonymous classes, -methods of enum instance) do not need to be tagged either. - -``` -//ClassOrInterfaceDeclaration[@Final='true' and @Interface='false'] - /ClassOrInterfaceBody/ClassOrInterfaceBodyDeclaration - [count(./Annotation/MarkerAnnotation/Name[@Image='SafeVarargs' or @Image='java.lang.SafeVarargs']) = 0] - /MethodDeclaration[@Final='true'] -| //MethodDeclaration[@Final='true' and @Private='true'] -| //EnumConstant/ClassOrInterfaceBody/ClassOrInterfaceBodyDeclaration/MethodDeclaration[@Final='true'] -| //AllocationExpression/ClassOrInterfaceBody/ClassOrInterfaceBodyDeclaration/MethodDeclaration[@Final='true'] -``` - -**Example(s):** - -``` java -public final class Foo { - // This final modifier is not necessary, since the class is final - // and thus, all methods are final - private final void foo() { - } -} -``` - -**Use this rule by referencing it:** -``` xml - -``` - -## UnnecessaryModifier - -**Since:** PMD 1.02 - -**Priority:** Medium (3) - -Fields in interfaces and annotations are automatically `public static final`, and methods are `public abstract`. -Classes, interfaces or annotations nested in an interface or annotation are automatically `public static` -(all nested interfaces and annotations are automatically static). -Nested enums are automatically `static`. -For historical reasons, modifiers which are implied by the context are accepted by the compiler, but are superfluous. - -**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.unnecessary.UnnecessaryModifierRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/unnecessary/UnnecessaryModifierRule.java) - -**Example(s):** - -``` java -public @interface Annotation { - public abstract void bar(); // both abstract and public are ignored by the compiler - public static final int X = 0; // public, static, and final all ignored - public static class Bar {} // public, static ignored - public static interface Baz {} // ditto -} -public interface Foo { - public abstract void bar(); // both abstract and public are ignored by the compiler - public static final int X = 0; // public, static, and final all ignored - public static class Bar {} // public, static ignored - public static interface Baz {} // ditto -} -public class Bar { - public static interface Baz {} // static ignored - public static enum FoorBar { // static ignored - FOO; - } -} -``` - -**Use this rule by referencing it:** -``` xml - -``` - -## UnnecessaryReturn - -**Since:** PMD 1.3 - -**Priority:** Medium (3) - -Avoid the use of unnecessary return statements. - -**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.unnecessary.UnnecessaryReturnRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/unnecessary/UnnecessaryReturnRule.java) - -**Example(s):** - -``` java -public class Foo { - public void bar() { - int x = 42; - return; - } -} -``` - -**Use this rule by referencing it:** -``` xml - -``` - -## UnusedNullCheckInEquals - -**Since:** PMD 3.5 - -**Priority:** Medium (3) - -After checking an object reference for null, you should invoke equals() on that object rather than passing it to another object's equals() method. - -``` -(//PrimaryPrefix[ends-with(Name/@Image, '.equals') and Name/@Image != 'Arrays.equals'] | //PrimarySuffix[@Image='equals' and not(../PrimaryPrefix/Literal)]) - /following-sibling::PrimarySuffix/Arguments/ArgumentList/Expression - /PrimaryExpression[count(PrimarySuffix)=0]/PrimaryPrefix - /Name[@Image = ./../../../../../../../../../../Expression/ConditionalAndExpression - /EqualityExpression[@Image="!=" and count(./preceding-sibling::*)=0 and - ./PrimaryExpression/PrimaryPrefix/Literal/NullLiteral] - /PrimaryExpression/PrimaryPrefix/Name/@Image] -``` - -**Example(s):** - -``` java -public class Test { - - public String method1() { return "ok";} - public String method2() { return null;} - - public void method(String a) { - String b; - // I don't know it method1() can be "null" - // but I know "a" is not null.. - // I'd better write a.equals(method1()) - - if (a!=null && method1().equals(a)) { // will trigger the rule - //whatever - } - - if (method1().equals(a) && a != null) { // won't trigger the rule - //whatever - } - - if (a!=null && method1().equals(b)) { // won't trigger the rule - //whatever - } - - if (a!=null && "LITERAL".equals(a)) { // won't trigger the rule - //whatever - } - - if (a!=null && !a.equals("go")) { // won't trigger the rule - a=method2(); - if (method1().equals(a)) { - //whatever - } - } - } -} -``` - -**Use this rule by referencing it:** -``` xml - -``` - -## UselessOperationOnImmutable - -**Since:** PMD 3.5 - -**Priority:** Medium (3) - -An operation on an Immutable object (String, BigDecimal or BigInteger) won't change the object itself -since the result of the operation is a new object. Therefore, ignoring the operation result is an error. - -**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.unnecessary.UselessOperationOnImmutableRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/unnecessary/UselessOperationOnImmutableRule.java) - -**Example(s):** - -``` java -import java.math.*; - -class Test { - void method1() { - BigDecimal bd=new BigDecimal(10); - bd.add(new BigDecimal(5)); // this will trigger the rule - } - void method2() { - BigDecimal bd=new BigDecimal(10); - bd = bd.add(new BigDecimal(5)); // this won't trigger the rule - } -} -``` - -**Use this rule by referencing it:** -``` xml - -``` - -## UselessOverridingMethod - -**Since:** PMD 3.3 - -**Priority:** Medium (3) - -The overriding method merely calls the same method defined in a superclass. - -**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.unnecessary.UselessOverridingMethodRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/unnecessary/UselessOverridingMethodRule.java) - -**Example(s):** - -``` java -public void foo(String bar) { - super.foo(bar); // why bother overriding? -} - -public String foo() { - return super.foo(); // why bother overriding? -} - -@Id -public Long getId() { - return super.getId(); // OK if 'ignoreAnnotations' is false, which is the default behavior -} -``` - -**This rule has the following properties:** - -|Name|Default Value|Description| -|----|-------------|-----------| -|ignoreAnnotations|false|Ignore annotations| - -**Use this rule by referencing it:** -``` xml - -``` - -## UselessParentheses - -**Since:** PMD 5.0 - -**Priority:** Medium Low (4) - -Useless parentheses should be removed. - -``` -//Expression[not(parent::PrimaryPrefix)]/PrimaryExpression[count(*)>1] - /PrimaryPrefix/Expression - [not(./CastExpression)] - [not(./ConditionalExpression[@Ternary='true'])] - [not(./AdditiveExpression[//Literal[@StringLiteral='true']])] -| -//Expression[not(parent::PrimaryPrefix)]/PrimaryExpression[count(*)=1] - /PrimaryPrefix/Expression -| -//Expression/ConditionalAndExpression/PrimaryExpression/PrimaryPrefix/Expression[ - count(*)=1 and - count(./CastExpression)=0 and - count(./EqualityExpression/MultiplicativeExpression)=0 and - count(./ConditionalExpression[@Ternary='true'])=0 and - count(./ConditionalOrExpression)=0] -| -//Expression/ConditionalOrExpression/PrimaryExpression/PrimaryPrefix/Expression[ - count(*)=1 and - not(./CastExpression) and - not(./ConditionalExpression[@Ternary='true']) and - not(./EqualityExpression/MultiplicativeExpression)] -| -//Expression/ConditionalExpression/PrimaryExpression/PrimaryPrefix/Expression[ - count(*)=1 and - not(./CastExpression) and - not(./EqualityExpression)] -| -//Expression/AdditiveExpression[not(./PrimaryExpression/PrimaryPrefix/Literal[@StringLiteral='true'])] - /PrimaryExpression[1]/PrimaryPrefix/Expression[ - count(*)=1 and - not(./CastExpression) and - not(./AdditiveExpression[@Image = '-']) and - not(./ShiftExpression) and - not(./RelationalExpression) and - not(./InstanceOfExpression) and - not(./EqualityExpression) and - not(./AndExpression) and - not(./ExclusiveOrExpression) and - not(./InclusiveOrExpression) and - not(./ConditionalAndExpression) and - not(./ConditionalOrExpression) and - not(./ConditionalExpression)] -| -//Expression/EqualityExpression/PrimaryExpression/PrimaryPrefix/Expression[ - count(*)=1 and - not(./CastExpression) and - not(./AndExpression) and - not(./InclusiveOrExpression) and - not(./ExclusiveOrExpression) and - not(./ConditionalExpression) and - not(./ConditionalAndExpression) and - not(./ConditionalOrExpression) and - not(./EqualityExpression)] -``` - -**Example(s):** - -``` java -public class Foo { - - private int _bar1; - private Integer _bar2; - - public void setBar(int n) { - _bar1 = Integer.valueOf((n)); // here - _bar2 = (n); // and here - } -} -``` - -**Use this rule by referencing it:** -``` xml - -``` - -## UselessQualifiedThis - -**Since:** PMD 5.4.0 - -**Priority:** Medium (3) - -Look for qualified this usages in the same class. - -``` -//PrimaryExpression -[PrimaryPrefix/Name[@Image]] -[PrimarySuffix[@Arguments='false']] -[not(PrimarySuffix/MemberSelector)] -[ancestor::ClassOrInterfaceBodyDeclaration[1][@AnonymousInnerClass='false']] -/PrimaryPrefix/Name[@Image = ancestor::ClassOrInterfaceDeclaration[1]/@Image] -``` - -**Example(s):** - -``` java -public class Foo { - final Foo otherFoo = Foo.this; // use "this" directly - - public void doSomething() { - final Foo anotherFoo = Foo.this; // use "this" directly - } - - private ActionListener returnListener() { - return new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - doSomethingWithQualifiedThis(Foo.this); // This is fine - } - }; - } - - private class Foo3 { - final Foo myFoo = Foo.this; // This is fine - } - - private class Foo2 { - final Foo2 myFoo2 = Foo2.this; // Use "this" direclty - } -} -``` - -**Use this rule by referencing it:** -``` xml - -``` - diff --git a/docs/pages/pmd/rules/java/unusedcode.md b/docs/pages/pmd/rules/java/unusedcode.md deleted file mode 100644 index bb87f2152b3..00000000000 --- a/docs/pages/pmd/rules/java/unusedcode.md +++ /dev/null @@ -1,116 +0,0 @@ ---- -title: Unused Code -summary: The Unused Code ruleset contains rules that find unused or ineffective code. -permalink: pmd_rules_java_unusedcode.html -folder: pmd/rules/java -sidebaractiveurl: /pmd_rules_java.html -editmepath: ../pmd-java/src/main/resources/rulesets/java/unusedcode.xml -keywords: Unused Code, UnusedPrivateField, UnusedLocalVariable, UnusedPrivateMethod, UnusedFormalParameter ---- -## UnusedFormalParameter - -**Since:** PMD 0.8 - -**Priority:** Medium (3) - -Avoid passing parameters to methods or constructors without actually referencing them in the method body. - -**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.unusedcode.UnusedFormalParameterRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/unusedcode/UnusedFormalParameterRule.java) - -**Example(s):** - -``` java -public class Foo { - private void bar(String howdy) { - // howdy is not used - } -} -``` - -**This rule has the following properties:** - -|Name|Default Value|Description| -|----|-------------|-----------| -|checkAll|false|Check all methods, including non-private ones| - -**Use this rule by referencing it:** -``` xml - -``` - -## UnusedLocalVariable - -**Since:** PMD 0.1 - -**Priority:** Medium (3) - -Detects when a local variable is declared and/or assigned, but not used. - -**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.unusedcode.UnusedLocalVariableRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/unusedcode/UnusedLocalVariableRule.java) - -**Example(s):** - -``` java -public class Foo { - public void doSomething() { - int i = 5; // Unused - } -} -``` - -**Use this rule by referencing it:** -``` xml - -``` - -## UnusedPrivateField - -**Since:** PMD 0.1 - -**Priority:** Medium (3) - -Detects when a private field is declared and/or assigned a value, but not used. - -**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.unusedcode.UnusedPrivateFieldRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/unusedcode/UnusedPrivateFieldRule.java) - -**Example(s):** - -``` java -public class Something { - private static int FOO = 2; // Unused - private int i = 5; // Unused - private int j = 6; - public int addOne() { - return j++; - } -} -``` - -**Use this rule by referencing it:** -``` xml - -``` - -## UnusedPrivateMethod - -**Since:** PMD 0.7 - -**Priority:** Medium (3) - -Unused Private Method detects when a private method is declared but is unused. - -**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.unusedcode.UnusedPrivateMethodRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/unusedcode/UnusedPrivateMethodRule.java) - -**Example(s):** - -``` java -public class Something { - private void foo() {} // unused -} -``` - -**Use this rule by referencing it:** -``` xml - -``` - diff --git a/docs/pages/pmd/rules/jsp.md b/docs/pages/pmd/rules/jsp.md index 9951e6aa564..bad2ea4fb65 100644 --- a/docs/pages/pmd/rules/jsp.md +++ b/docs/pages/pmd/rules/jsp.md @@ -1,26 +1,56 @@ --- title: Java Server Pages Rules +tags: [rule_references, jsp] +summary: Index of all built-in rules available for Java Server Pages +language_name: Java Server Pages permalink: pmd_rules_jsp.html folder: pmd/rules --- -List of rulesets and rules contained in each ruleset. - -* [Basic JSF](pmd_rules_jsp_basic-jsf.html): Rules concerning basic JSF guidelines. -* [Basic JSP](pmd_rules_jsp_basic.html): Rules concerning basic JSP guidelines. - -## Basic JSF -* [DontNestJsfInJstlIteration](pmd_rules_jsp_basic-jsf.html#dontnestjsfinjstliteration): Do not nest JSF component custom actions inside a custom action that iterates over its body. - -## Basic JSP -* [DuplicateJspImports](pmd_rules_jsp_basic.html#duplicatejspimports): Avoid duplicate import statements inside JSP's. -* [IframeMissingSrcAttribute](pmd_rules_jsp_basic.html#iframemissingsrcattribute): IFrames which are missing a src element can cause security information popups in IE if you are ac... -* [JspEncoding](pmd_rules_jsp_basic.html#jspencoding): A missing 'meta' tag or page directive will trigger this rule, as well as a non-UTF-8 charset. -* [NoClassAttribute](pmd_rules_jsp_basic.html#noclassattribute): Do not use an attribute called 'class'. Use "styleclass" for CSS styles. -* [NoHtmlComments](pmd_rules_jsp_basic.html#nohtmlcomments): In a production system, HTML comments increase the payloadbetween the application server to the c... -* [NoInlineScript](pmd_rules_jsp_basic.html#noinlinescript): Avoid inlining HTML script content. Consider externalizing the HTML script using the 'src' attri... -* [NoInlineStyleInformation](pmd_rules_jsp_basic.html#noinlinestyleinformation): Style information should be put in CSS files, not in JSPs. Therefore, don't use or tags... -* [NoJspForward](pmd_rules_jsp_basic.html#nojspforward): Do not do a forward from within a JSP file. -* [NoLongScripts](pmd_rules_jsp_basic.html#nolongscripts): Scripts should be part of Tag Libraries, rather than part of JSP pages. -* [NoScriptlets](pmd_rules_jsp_basic.html#noscriptlets): Scriptlets should be factored into Tag Libraries or JSP declarations, rather than being part of J... -* [NoUnsanitizedJSPExpression](pmd_rules_jsp_basic.html#nounsanitizedjspexpression): Avoid using expressions without escaping / sanitizing. This could lead to cross site scripting - ... +## Best Practices + +{% include callout.html content="Rules which enforce generally accepted best practices." %} + +* [DontNestJsfInJstlIteration](pmd_rules_jsp_bestpractices.html#dontnestjsfinjstliteration): Do not nest JSF component custom actions inside a custom action that iterates over its body. +* [NoClassAttribute](pmd_rules_jsp_bestpractices.html#noclassattribute): Do not use an attribute called 'class'. Use "styleclass" for CSS styles. +* [NoHtmlComments](pmd_rules_jsp_bestpractices.html#nohtmlcomments): In a production system, HTML comments increase the payloadbetween the application server to the c... +* [NoJspForward](pmd_rules_jsp_bestpractices.html#nojspforward): Do not do a forward from within a JSP file. + +## Code Style + +{% include callout.html content="Rules which enforce a specific coding style." %} + +* [DuplicateJspImports](pmd_rules_jsp_codestyle.html#duplicatejspimports): Avoid duplicate import statements inside JSP's. + +## Design + +{% include callout.html content="Rules that help you discover design issues." %} + +* [NoInlineScript](pmd_rules_jsp_design.html#noinlinescript): Avoid inlining HTML script content. Consider externalizing the HTML script using the 'src' attri... +* [NoInlineStyleInformation](pmd_rules_jsp_design.html#noinlinestyleinformation): Style information should be put in CSS files, not in JSPs. Therefore, don't use or tags... +* [NoLongScripts](pmd_rules_jsp_design.html#nolongscripts): Scripts should be part of Tag Libraries, rather than part of JSP pages. +* [NoScriptlets](pmd_rules_jsp_design.html#noscriptlets): Scriptlets should be factored into Tag Libraries or JSP declarations, rather than being part of J... + +## Error Prone + +{% include callout.html content="Rules to detect constructs that are either broken, extremely confusing or prone to runtime errors." %} + +* [JspEncoding](pmd_rules_jsp_errorprone.html#jspencoding): A missing 'meta' tag or page directive will trigger this rule, as well as a non-UTF-8 charset. + +## Security + +{% include callout.html content="Rules that flag potential security flaws." %} + +* [IframeMissingSrcAttribute](pmd_rules_jsp_security.html#iframemissingsrcattribute): IFrames which are missing a src element can cause security information popups in IE if you are ac... +* [NoUnsanitizedJSPExpression](pmd_rules_jsp_security.html#nounsanitizedjspexpression): Avoid using expressions without escaping / sanitizing. This could lead to cross site scripting - ... + +## Additional rulesets + +* Basic JSP (`rulesets/jsp/basic.xml`): + + Deprecated This ruleset is for backwards compatibility. + + It contains the following rules: + + [DuplicateJspImports](pmd_rules_jsp_codestyle.html#duplicatejspimports), [IframeMissingSrcAttribute](pmd_rules_jsp_security.html#iframemissingsrcattribute), [JspEncoding](pmd_rules_jsp_errorprone.html#jspencoding), [NoClassAttribute](pmd_rules_jsp_bestpractices.html#noclassattribute), [NoHtmlComments](pmd_rules_jsp_bestpractices.html#nohtmlcomments), [NoInlineScript](pmd_rules_jsp_design.html#noinlinescript), [NoInlineStyleInformation](pmd_rules_jsp_design.html#noinlinestyleinformation), [NoJspForward](pmd_rules_jsp_bestpractices.html#nojspforward), [NoLongScripts](pmd_rules_jsp_design.html#nolongscripts), [NoScriptlets](pmd_rules_jsp_design.html#noscriptlets), [NoUnsanitizedJSPExpression](pmd_rules_jsp_security.html#nounsanitizedjspexpression) + diff --git a/docs/pages/pmd/rules/jsp/basic-jsf.md b/docs/pages/pmd/rules/jsp/basic-jsf.md deleted file mode 100644 index d8104dca9dd..00000000000 --- a/docs/pages/pmd/rules/jsp/basic-jsf.md +++ /dev/null @@ -1,40 +0,0 @@ ---- -title: Basic JSF -summary: Rules concerning basic JSF guidelines. -permalink: pmd_rules_jsp_basic-jsf.html -folder: pmd/rules/jsp -sidebaractiveurl: /pmd_rules_jsp.html -editmepath: ../pmd-jsp/src/main/resources/rulesets/jsp/basic-jsf.xml -keywords: Basic JSF, DontNestJsfInJstlIteration ---- -## DontNestJsfInJstlIteration - -**Since:** PMD 3.6 - -**Priority:** Medium (3) - -Do not nest JSF component custom actions inside a custom action that iterates over its body. - -``` -//Element[ @Name="c:forEach" ] // Element[ @NamespacePrefix="h" or @NamespacePrefix="f" ] -``` - -**Example(s):** - -``` jsp - - -
    - -
  • -
    -
- - -``` - -**Use this rule by referencing it:** -``` xml - -``` - diff --git a/docs/pages/pmd/rules/jsp/basic.md b/docs/pages/pmd/rules/jsp/basic.md deleted file mode 100644 index f1704787d95..00000000000 --- a/docs/pages/pmd/rules/jsp/basic.md +++ /dev/null @@ -1,325 +0,0 @@ ---- -title: Basic JSP -summary: Rules concerning basic JSP guidelines. -permalink: pmd_rules_jsp_basic.html -folder: pmd/rules/jsp -sidebaractiveurl: /pmd_rules_jsp.html -editmepath: ../pmd-jsp/src/main/resources/rulesets/jsp/basic.xml -keywords: Basic JSP, NoLongScripts, NoScriptlets, NoInlineStyleInformation, NoClassAttribute, NoJspForward, IframeMissingSrcAttribute, NoHtmlComments, DuplicateJspImports, JspEncoding, NoInlineScript, NoUnsanitizedJSPExpression ---- -## DuplicateJspImports - -**Since:** PMD 3.7 - -**Priority:** Medium (3) - -Avoid duplicate import statements inside JSP's. - -**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.jsp.rule.basic.DuplicateJspImportsRule](https://github.com/pmd/pmd/blob/master/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/rule/basic/DuplicateJspImportsRule.java) - -**Example(s):** - -``` jsp -<%@ page import=\"com.foo.MyClass,com.foo.MyClass\"%>/foo\">xxtext -``` - -**Use this rule by referencing it:** -``` xml - -``` - -## IframeMissingSrcAttribute - -**Since:** PMD 3.6 - -**Priority:** Medium High (2) - -IFrames which are missing a src element can cause security information popups in IE if you are accessing the page -through SSL. See http://support.microsoft.com/default.aspx?scid=kb;EN-US;Q261188 - -``` -//Element[upper-case(@Name)="IFRAME"][count(Attribute[upper-case(@Name)="SRC" ]) = 0] -``` - -**Example(s):** - -``` jsp -bad example><BODY> -<iframe></iframe> -</BODY> </HTML> - -<HTML><title>good example><BODY> -<iframe src="foo"></iframe> -</BODY> </HTML> -``` - -**Use this rule by referencing it:** -``` xml -<rule ref="rulesets/jsp/basic.xml/IframeMissingSrcAttribute" /> -``` - -## JspEncoding - -**Since:** PMD 4.0 - -**Priority:** Medium (3) - -A missing 'meta' tag or page directive will trigger this rule, as well as a non-UTF-8 charset. - -``` -//CompilationUnit/Content[ -not(Element[@Name="meta"][ - Attribute[@Name="content"]/AttributeValue[contains(lower-case(@Image),"charset=utf-8")] -]) -and - not(JspDirective[@Name='page']/JspDirectiveAttribute[@Name='contentType'][contains(lower-case(@Value),"charset=utf-8")]) -] -``` - -**Example(s):** - -``` jsp -Most browsers should be able to interpret the following headers: - -<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %> - -<meta http-equiv="Content-Type"  content="text/html; charset=UTF-8" /> -``` - -**Use this rule by referencing it:** -``` xml -<rule ref="rulesets/jsp/basic.xml/JspEncoding" /> -``` - -## NoClassAttribute - -**Since:** PMD 3.6 - -**Priority:** Medium High (2) - -Do not use an attribute called 'class'. Use "styleclass" for CSS styles. - -``` -//Attribute[ upper-case(@Name)="CLASS" ] -``` - -**Example(s):** - -``` jsp -<HTML> <BODY> -<P class="MajorHeading">Some text</P> -</BODY> </HTML> -``` - -**Use this rule by referencing it:** -``` xml -<rule ref="rulesets/jsp/basic.xml/NoClassAttribute" /> -``` - -## NoHtmlComments - -**Since:** PMD 3.6 - -**Priority:** Medium High (2) - -In a production system, HTML comments increase the payload -between the application server to the client, and serve -little other purpose. Consider switching to JSP comments. - -``` -//CommentTag -``` - -**Example(s):** - -``` jsp -<HTML><title>bad example><BODY> -<!-- HTML comment --> -</BODY> </HTML> - -<HTML><title>good example><BODY> -<%-- JSP comment --%> -</BODY> </HTML> -``` - -**Use this rule by referencing it:** -``` xml -<rule ref="rulesets/jsp/basic.xml/NoHtmlComments" /> -``` - -## NoInlineScript - -**Since:** PMD 4.0 - -**Priority:** Medium (3) - -Avoid inlining HTML script content. Consider externalizing the HTML script using the 'src' attribute on the "script" element. -Externalized script could be reused between pages. Browsers can also cache the script, reducing overall download bandwidth. - -``` -//HtmlScript[@Image != ''] -``` - -**Example(s):** - -``` jsp -Most browsers should be able to interpret the following headers: - -<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %> - -<meta http-equiv="Content-Type"  content="text/html; charset=UTF-8" /> -``` - -**Use this rule by referencing it:** -``` xml -<rule ref="rulesets/jsp/basic.xml/NoInlineScript" /> -``` - -## NoInlineStyleInformation - -**Since:** PMD 3.6 - -**Priority:** Medium (3) - -Style information should be put in CSS files, not in JSPs. Therefore, don't use <B> or <FONT> -tags, or attributes like "align='center'". - -**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.jsp.rule.basic.NoInlineStyleInformationRule](https://github.com/pmd/pmd/blob/master/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/rule/basic/NoInlineStyleInformationRule.java) - -**Example(s):** - -``` jsp -<html><body><p align='center'><b>text</b></p></body></html> -``` - -**Use this rule by referencing it:** -``` xml -<rule ref="rulesets/jsp/basic.xml/NoInlineStyleInformation" /> -``` - -## NoJspForward - -**Since:** PMD 3.6 - -**Priority:** Medium (3) - -Do not do a forward from within a JSP file. - -``` -//Element[ @Name="jsp:forward" ] -``` - -**Example(s):** - -``` jsp -<jsp:forward page='UnderConstruction.jsp'/> -``` - -**Use this rule by referencing it:** -``` xml -<rule ref="rulesets/jsp/basic.xml/NoJspForward" /> -``` - -## NoLongScripts - -**Since:** PMD 3.6 - -**Priority:** Medium High (2) - -Scripts should be part of Tag Libraries, rather than part of JSP pages. - -``` -//HtmlScript[(@EndLine - @BeginLine > 10)] -``` - -**Example(s):** - -``` jsp -<HTML> -<BODY> -<!--Java Script--> -<SCRIPT language="JavaScript" type="text/javascript"> -<!-- -function calcDays(){ - var date1 = document.getElementById('d1').lastChild.data; - var date2 = document.getElementById('d2').lastChild.data; - date1 = date1.split("-"); - date2 = date2.split("-"); - var sDate = new Date(date1[0]+"/"+date1[1]+"/"+date1[2]); - var eDate = new Date(date2[0]+"/"+date2[1]+"/"+date2[2]); - var daysApart = Math.abs(Math.round((sDate-eDate)/86400000)); - document.getElementById('diffDays').lastChild.data = daysApart; -} - -onload=calcDays; -//--> -</SCRIPT> -</BODY> -</HTML> -``` - -**Use this rule by referencing it:** -``` xml -<rule ref="rulesets/jsp/basic.xml/NoLongScripts" /> -``` - -## NoScriptlets - -**Since:** PMD 3.6 - -**Priority:** Medium (3) - -Scriptlets should be factored into Tag Libraries or JSP declarations, rather than being part of JSP pages. - -``` -//JspScriptlet -| -//Element[ upper-case(@Name)="JSP:SCRIPTLET" ] -``` - -**Example(s):** - -``` jsp -<HTML> -<HEAD> -<% -response.setHeader("Pragma", "No-cache"); -%> -</HEAD> - <BODY> - <jsp:scriptlet>String title = "Hello world!";</jsp:scriptlet> - </BODY> -</HTML> -``` - -**Use this rule by referencing it:** -``` xml -<rule ref="rulesets/jsp/basic.xml/NoScriptlets" /> -``` - -## NoUnsanitizedJSPExpression - -**Since:** PMD 5.1.4 - -**Priority:** Medium (3) - -Avoid using expressions without escaping / sanitizing. This could lead to cross site scripting - as the expression -would be interpreted by the browser directly (e.g. "<script>alert('hello');</script>"). - -**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.jsp.rule.basic.NoUnsanitizedJSPExpressionRule](https://github.com/pmd/pmd/blob/master/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/rule/basic/NoUnsanitizedJSPExpressionRule.java) - -**Example(s):** - -``` jsp -<%@ page contentType="text/html; charset=UTF-8" %> -<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %> -${expression} <!-- don't use this --> -${fn:escapeXml(expression)} <!-- instead, escape it --> -<c:out value="${expression}" /> <!-- or use c:out --> -``` - -**Use this rule by referencing it:** -``` xml -<rule ref="rulesets/jsp/basic.xml/NoUnsanitizedJSPExpression" /> -``` - diff --git a/docs/pages/pmd/rules/jsp/bestpractices.md b/docs/pages/pmd/rules/jsp/bestpractices.md new file mode 100644 index 00000000000..eb055fb6c99 --- /dev/null +++ b/docs/pages/pmd/rules/jsp/bestpractices.md @@ -0,0 +1,124 @@ +--- +title: Best Practices +summary: Rules which enforce generally accepted best practices. +permalink: pmd_rules_jsp_bestpractices.html +folder: pmd/rules/jsp +sidebaractiveurl: /pmd_rules_jsp.html +editmepath: ../pmd-jsp/src/main/resources/category/jsp/bestpractices.xml +keywords: Best Practices, DontNestJsfInJstlIteration, NoClassAttribute, NoHtmlComments, NoJspForward +language: Java Server Pages +--- +## DontNestJsfInJstlIteration + +**Since:** PMD 3.6 + +**Priority:** Medium (3) + +Do not nest JSF component custom actions inside a custom action that iterates over its body. + +**This rule is defined by the following XPath expression:** +``` xpath +//Element[ @Name="c:forEach" ] // Element[ @NamespacePrefix="h" or @NamespacePrefix="f" ] +``` + +**Example(s):** + +``` jsp +<html> + <body> + <ul> + <c:forEach items='${books}' var='b'> + <li> <h:outputText value='#{b}' /> </li> + </c:forEach> + </ul> + </body> +</html> +``` + +**Use this rule by referencing it:** +``` xml +<rule ref="category/jsp/bestpractices.xml/DontNestJsfInJstlIteration" /> +``` + +## NoClassAttribute + +**Since:** PMD 3.6 + +**Priority:** Medium High (2) + +Do not use an attribute called 'class'. Use "styleclass" for CSS styles. + +**This rule is defined by the following XPath expression:** +``` xpath +//Attribute[ upper-case(@Name)="CLASS" ] +``` + +**Example(s):** + +``` jsp +<HTML> <BODY> +<P class="MajorHeading">Some text</P> +</BODY> </HTML> +``` + +**Use this rule by referencing it:** +``` xml +<rule ref="category/jsp/bestpractices.xml/NoClassAttribute" /> +``` + +## NoHtmlComments + +**Since:** PMD 3.6 + +**Priority:** Medium High (2) + +In a production system, HTML comments increase the payload +between the application server to the client, and serve +little other purpose. Consider switching to JSP comments. + +**This rule is defined by the following XPath expression:** +``` xpath +//CommentTag +``` + +**Example(s):** + +``` jsp +<HTML><title>bad example><BODY> +<!-- HTML comment --> +</BODY> </HTML> + +<HTML><title>good example><BODY> +<%-- JSP comment --%> +</BODY> </HTML> +``` + +**Use this rule by referencing it:** +``` xml +<rule ref="category/jsp/bestpractices.xml/NoHtmlComments" /> +``` + +## NoJspForward + +**Since:** PMD 3.6 + +**Priority:** Medium (3) + +Do not do a forward from within a JSP file. + +**This rule is defined by the following XPath expression:** +``` xpath +//Element[ @Name="jsp:forward" ] +``` + +**Example(s):** + +``` jsp +<jsp:forward page='UnderConstruction.jsp'/> +``` + +**Use this rule by referencing it:** +``` xml +<rule ref="category/jsp/bestpractices.xml/NoJspForward" /> +``` + diff --git a/docs/pages/pmd/rules/jsp/codestyle.md b/docs/pages/pmd/rules/jsp/codestyle.md new file mode 100644 index 00000000000..2654710a2d1 --- /dev/null +++ b/docs/pages/pmd/rules/jsp/codestyle.md @@ -0,0 +1,31 @@ +--- +title: Code Style +summary: Rules which enforce a specific coding style. +permalink: pmd_rules_jsp_codestyle.html +folder: pmd/rules/jsp +sidebaractiveurl: /pmd_rules_jsp.html +editmepath: ../pmd-jsp/src/main/resources/category/jsp/codestyle.xml +keywords: Code Style, DuplicateJspImports +language: Java Server Pages +--- +## DuplicateJspImports + +**Since:** PMD 3.7 + +**Priority:** Medium (3) + +Avoid duplicate import statements inside JSP's. + +**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.jsp.rule.codestyle.DuplicateJspImportsRule](https://github.com/pmd/pmd/blob/master/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/rule/codestyle/DuplicateJspImportsRule.java) + +**Example(s):** + +``` jsp +<%@ page import=\"com.foo.MyClass,com.foo.MyClass\"%><html><body><b><img src=\"<%=Some.get()%>/foo\">xx</img>text</b></body></html> +``` + +**Use this rule by referencing it:** +``` xml +<rule ref="category/jsp/codestyle.xml/DuplicateJspImports" /> +``` + diff --git a/docs/pages/pmd/rules/jsp/design.md b/docs/pages/pmd/rules/jsp/design.md new file mode 100644 index 00000000000..b4b1733fe42 --- /dev/null +++ b/docs/pages/pmd/rules/jsp/design.md @@ -0,0 +1,130 @@ +--- +title: Design +summary: Rules that help you discover design issues. +permalink: pmd_rules_jsp_design.html +folder: pmd/rules/jsp +sidebaractiveurl: /pmd_rules_jsp.html +editmepath: ../pmd-jsp/src/main/resources/category/jsp/design.xml +keywords: Design, NoInlineScript, NoInlineStyleInformation, NoLongScripts, NoScriptlets +language: Java Server Pages +--- +## NoInlineScript + +**Since:** PMD 4.0 + +**Priority:** Medium (3) + +Avoid inlining HTML script content. Consider externalizing the HTML script using the 'src' attribute on the "script" element. +Externalized script could be reused between pages. Browsers can also cache the script, reducing overall download bandwidth. + +**This rule is defined by the following XPath expression:** +``` xpath +//HtmlScript[@Image != ''] +``` + +**Use this rule by referencing it:** +``` xml +<rule ref="category/jsp/design.xml/NoInlineScript" /> +``` + +## NoInlineStyleInformation + +**Since:** PMD 3.6 + +**Priority:** Medium (3) + +Style information should be put in CSS files, not in JSPs. Therefore, don't use <B> or <FONT> +tags, or attributes like "align='center'". + +**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.jsp.rule.design.NoInlineStyleInformationRule](https://github.com/pmd/pmd/blob/master/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/rule/design/NoInlineStyleInformationRule.java) + +**Example(s):** + +``` jsp +<html><body><p align='center'><b>text</b></p></body></html> +``` + +**Use this rule by referencing it:** +``` xml +<rule ref="category/jsp/design.xml/NoInlineStyleInformation" /> +``` + +## NoLongScripts + +**Since:** PMD 3.6 + +**Priority:** Medium High (2) + +Scripts should be part of Tag Libraries, rather than part of JSP pages. + +**This rule is defined by the following XPath expression:** +``` xpath +//HtmlScript[(@EndLine - @BeginLine > 10)] +``` + +**Example(s):** + +``` jsp +<HTML> +<BODY> +<!--Java Script--> +<SCRIPT language="JavaScript" type="text/javascript"> +<!-- +function calcDays(){ + var date1 = document.getElementById('d1').lastChild.data; + var date2 = document.getElementById('d2').lastChild.data; + date1 = date1.split("-"); + date2 = date2.split("-"); + var sDate = new Date(date1[0]+"/"+date1[1]+"/"+date1[2]); + var eDate = new Date(date2[0]+"/"+date2[1]+"/"+date2[2]); + var daysApart = Math.abs(Math.round((sDate-eDate)/86400000)); + document.getElementById('diffDays').lastChild.data = daysApart; +} + +onload=calcDays; +//--> +</SCRIPT> +</BODY> +</HTML> +``` + +**Use this rule by referencing it:** +``` xml +<rule ref="category/jsp/design.xml/NoLongScripts" /> +``` + +## NoScriptlets + +**Since:** PMD 3.6 + +**Priority:** Medium (3) + +Scriptlets should be factored into Tag Libraries or JSP declarations, rather than being part of JSP pages. + +**This rule is defined by the following XPath expression:** +``` xpath +//JspScriptlet +| +//Element[ upper-case(@Name)="JSP:SCRIPTLET" ] +``` + +**Example(s):** + +``` jsp +<HTML> +<HEAD> +<% +response.setHeader("Pragma", "No-cache"); +%> +</HEAD> + <BODY> + <jsp:scriptlet>String title = "Hello world!";</jsp:scriptlet> + </BODY> +</HTML> +``` + +**Use this rule by referencing it:** +``` xml +<rule ref="category/jsp/design.xml/NoScriptlets" /> +``` + diff --git a/docs/pages/pmd/rules/jsp/errorprone.md b/docs/pages/pmd/rules/jsp/errorprone.md new file mode 100644 index 00000000000..b351d9a844d --- /dev/null +++ b/docs/pages/pmd/rules/jsp/errorprone.md @@ -0,0 +1,44 @@ +--- +title: Error Prone +summary: Rules to detect constructs that are either broken, extremely confusing or prone to runtime errors. +permalink: pmd_rules_jsp_errorprone.html +folder: pmd/rules/jsp +sidebaractiveurl: /pmd_rules_jsp.html +editmepath: ../pmd-jsp/src/main/resources/category/jsp/errorprone.xml +keywords: Error Prone, JspEncoding +language: Java Server Pages +--- +## JspEncoding + +**Since:** PMD 4.0 + +**Priority:** Medium (3) + +A missing 'meta' tag or page directive will trigger this rule, as well as a non-UTF-8 charset. + +**This rule is defined by the following XPath expression:** +``` xpath +//CompilationUnit/Content[ +not(Element[@Name="meta"][ + Attribute[@Name="content"]/AttributeValue[contains(lower-case(@Image),"charset=utf-8")] +]) +and + not(JspDirective[@Name='page']/JspDirectiveAttribute[@Name='contentType'][contains(lower-case(@Value),"charset=utf-8")]) +] +``` + +**Example(s):** + +``` jsp +Most browsers should be able to interpret the following headers: + +<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %> + +<meta http-equiv="Content-Type"  content="text/html; charset=UTF-8" /> +``` + +**Use this rule by referencing it:** +``` xml +<rule ref="category/jsp/errorprone.xml/JspEncoding" /> +``` + diff --git a/docs/pages/pmd/rules/jsp/security.md b/docs/pages/pmd/rules/jsp/security.md new file mode 100644 index 00000000000..0cc3cc60e58 --- /dev/null +++ b/docs/pages/pmd/rules/jsp/security.md @@ -0,0 +1,67 @@ +--- +title: Security +summary: Rules that flag potential security flaws. +permalink: pmd_rules_jsp_security.html +folder: pmd/rules/jsp +sidebaractiveurl: /pmd_rules_jsp.html +editmepath: ../pmd-jsp/src/main/resources/category/jsp/security.xml +keywords: Security, IframeMissingSrcAttribute, NoUnsanitizedJSPExpression +language: Java Server Pages +--- +## IframeMissingSrcAttribute + +**Since:** PMD 3.6 + +**Priority:** Medium High (2) + +IFrames which are missing a src element can cause security information popups in IE if you are accessing the page +through SSL. See http://support.microsoft.com/default.aspx?scid=kb;EN-US;Q261188 + +**This rule is defined by the following XPath expression:** +``` xpath +//Element[upper-case(@Name)="IFRAME"][count(Attribute[upper-case(@Name)="SRC" ]) = 0] +``` + +**Example(s):** + +``` jsp +<HTML><title>bad example><BODY> +<iframe></iframe> +</BODY> </HTML> + +<HTML><title>good example><BODY> +<iframe src="foo"></iframe> +</BODY> </HTML> +``` + +**Use this rule by referencing it:** +``` xml +<rule ref="category/jsp/security.xml/IframeMissingSrcAttribute" /> +``` + +## NoUnsanitizedJSPExpression + +**Since:** PMD 5.1.4 + +**Priority:** Medium (3) + +Avoid using expressions without escaping / sanitizing. This could lead to cross site scripting - as the expression +would be interpreted by the browser directly (e.g. "<script>alert('hello');</script>"). + +**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.jsp.rule.security.NoUnsanitizedJSPExpressionRule](https://github.com/pmd/pmd/blob/master/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/rule/security/NoUnsanitizedJSPExpressionRule.java) + +**Example(s):** + +``` jsp +<%@ page contentType="text/html; charset=UTF-8" %> +<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %> +${expression} <!-- don't use this --> +${fn:escapeXml(expression)} <!-- instead, escape it --> +<c:out value="${expression}" /> <!-- or use c:out --> +``` + +**Use this rule by referencing it:** +``` xml +<rule ref="category/jsp/security.xml/NoUnsanitizedJSPExpression" /> +``` + diff --git a/docs/pages/pmd/rules/plsql.md b/docs/pages/pmd/rules/plsql.md index 8263aac1d85..7dfe05bca82 100644 --- a/docs/pages/pmd/rules/plsql.md +++ b/docs/pages/pmd/rules/plsql.md @@ -1,37 +1,82 @@ --- title: PLSQL Rules +tags: [rule_references, plsql] +summary: Index of all built-in rules available for PLSQL +language_name: PLSQL permalink: pmd_rules_plsql.html folder: pmd/rules --- -List of rulesets and rules contained in each ruleset. - -* [Code Size](pmd_rules_plsql_codesize.html): The Code Size ruleset contains rules that find problems related to code size or complexity. -* [PLSQL DATETIME](pmd_rules_plsql_dates.html): The Dates ruleset deals with PLSQL DATETIME usages. -* [Strict Syntax](pmd_rules_plsql_strictsyntax.html): The Strict Syntax ruleset contains rules that highlight invalid plsql syntax, which works, but should be avoided. -* [Tom Kyte's Despair](pmd_rules_plsql_TomKytesDespair.html): Rules based on Thomas Kyte's recommendations on http://asktom.oracle.com/ and http://tkyte.blogspot.com/. - -## Code Size -* [CyclomaticComplexity](pmd_rules_plsql_codesize.html#cyclomaticcomplexity): Complexity directly affects maintenance costs is determined by the number of decision points in a... -* [ExcessiveMethodLength](pmd_rules_plsql_codesize.html#excessivemethodlength): When methods are excessively long this usually indicates that the method is doing more than itsna... -* [ExcessiveObjectLength](pmd_rules_plsql_codesize.html#excessiveobjectlength): Excessive object line lengths are usually indications that the object may be burdened with excess... -* [ExcessivePackageBodyLength](pmd_rules_plsql_codesize.html#excessivepackagebodylength): Excessive class file lengths are usually indications that the class may be burdened with excessiv... -* [ExcessivePackageSpecificationLength](pmd_rules_plsql_codesize.html#excessivepackagespecificationlength): Excessive class file lengths are usually indications that the class may be burdened with excessiv... -* [ExcessiveParameterList](pmd_rules_plsql_codesize.html#excessiveparameterlist): Methods with numerous parameters are a challenge to maintain, especially if most of them share th... -* [ExcessiveTypeLength](pmd_rules_plsql_codesize.html#excessivetypelength): Excessive class file lengths are usually indications that the class may be burdened with excessiv... -* [NcssMethodCount](pmd_rules_plsql_codesize.html#ncssmethodcount): This rule uses the NCSS (Non-Commenting Source Statements) algorithm to determine the number of l... -* [NcssObjectCount](pmd_rules_plsql_codesize.html#ncssobjectcount): This rule uses the NCSS (Non-Commenting Source Statements) algorithm to determine the number of l... -* [NPathComplexity](pmd_rules_plsql_codesize.html#npathcomplexity): The NPath complexity of a method is the number of acyclic execution paths through that method.A t... -* [TooManyFields](pmd_rules_plsql_codesize.html#toomanyfields): Classes that have too many fields can become unwieldy and could be redesigned to have fewer field... -* [TooManyMethods](pmd_rules_plsql_codesize.html#toomanymethods): A package or type with too many methods is probably a good suspect for refactoring, in order to r... - -## PLSQL DATETIME -* [TO_DATE_TO_CHAR](pmd_rules_plsql_dates.html#to_date_to_char): TO_DATE(TO_CHAR(date-variable)) used to remove time component - use TRUNC(date-veriable) -* [TO_DATEWithoutDateFormat](pmd_rules_plsql_dates.html#to_datewithoutdateformat): TO_DATE without date format- use TO_DATE(expression, date-format) -* [TO_TIMESTAMPWithoutDateFormat](pmd_rules_plsql_dates.html#to_timestampwithoutdateformat): TO_TIMESTAMP without date format- use TO_TIMESTAMP(expression, date-format) - -## Strict Syntax -* [MisplacedPragma](pmd_rules_plsql_strictsyntax.html#misplacedpragma): Oracle states that the PRAQMA AUTONOMOUS_TRANSACTION must be in the declaration block,but the cod... - -## Tom Kyte's Despair -* [TomKytesDespair](pmd_rules_plsql_TomKytesDespair.html#tomkytesdespair): "WHEN OTHERS THEN NULL" hides all errors - (Re)RAISE an exception or call RAISE_APPLICATION_ERROR +## Best Practices + +{% include callout.html content="Rules which enforce generally accepted best practices." %} + +* [TomKytesDespair](pmd_rules_plsql_bestpractices.html#tomkytesdespair): "WHEN OTHERS THEN NULL" hides all errors - (Re)RAISE an exception or call RAISE_APPLICATION_ERROR + +## Code Style + +{% include callout.html content="Rules which enforce a specific coding style." %} + +* [CodeFormat](pmd_rules_plsql_codestyle.html#codeformat): This rule verifies that the PLSQL code is properly formatted. The following checks are executed:S... +* [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 + +{% include callout.html content="Rules that help you discover design issues." %} + +* [CyclomaticComplexity](pmd_rules_plsql_design.html#cyclomaticcomplexity): Complexity directly affects maintenance costs is determined by the number of decision points in a... +* [ExcessiveMethodLength](pmd_rules_plsql_design.html#excessivemethodlength): When methods are excessively long this usually indicates that the method is doing more than itsna... +* [ExcessiveObjectLength](pmd_rules_plsql_design.html#excessiveobjectlength): Excessive object line lengths are usually indications that the object may be burdened with excess... +* [ExcessivePackageBodyLength](pmd_rules_plsql_design.html#excessivepackagebodylength): Excessive class file lengths are usually indications that the class may be burdened with excessiv... +* [ExcessivePackageSpecificationLength](pmd_rules_plsql_design.html#excessivepackagespecificationlength): Excessive class file lengths are usually indications that the class may be burdened with excessiv... +* [ExcessiveParameterList](pmd_rules_plsql_design.html#excessiveparameterlist): Methods with numerous parameters are a challenge to maintain, especially if most of them share th... +* [ExcessiveTypeLength](pmd_rules_plsql_design.html#excessivetypelength): Excessive class file lengths are usually indications that the class may be burdened with excessiv... +* [NcssMethodCount](pmd_rules_plsql_design.html#ncssmethodcount): This rule uses the NCSS (Non-Commenting Source Statements) algorithm to determine the number of l... +* [NcssObjectCount](pmd_rules_plsql_design.html#ncssobjectcount): This rule uses the NCSS (Non-Commenting Source Statements) algorithm to determine the number of l... +* [NPathComplexity](pmd_rules_plsql_design.html#npathcomplexity): The NPath complexity of a method is the number of acyclic execution paths through that method.A t... +* [TooManyFields](pmd_rules_plsql_design.html#toomanyfields): Classes that have too many fields can become unwieldy and could be redesigned to have fewer field... +* [TooManyMethods](pmd_rules_plsql_design.html#toomanymethods): A package or type with too many methods is probably a good suspect for refactoring, in order to r... + +## Error Prone + +{% include callout.html content="Rules to detect constructs that are either broken, extremely confusing or prone to runtime errors." %} + +* [TO_DATE_TO_CHAR](pmd_rules_plsql_errorprone.html#to_date_to_char): TO_DATE(TO_CHAR(date-variable)) used to remove time component - use TRUNC(date-variable) +* [TO_DATEWithoutDateFormat](pmd_rules_plsql_errorprone.html#to_datewithoutdateformat): TO_DATE without date format- use TO_DATE(expression, date-format) +* [TO_TIMESTAMPWithoutDateFormat](pmd_rules_plsql_errorprone.html#to_timestampwithoutdateformat): TO_TIMESTAMP without date format- use TO_TIMESTAMP(expression, date-format) + +## Additional rulesets + +* Code Size (`rulesets/plsql/codesize.xml`): + + <span style="border-radius: 0.25em; color: #fff; padding: 0.2em 0.6em 0.3em; display: inline; background-color: #d9534f; font-size: 75%;">Deprecated</span> This ruleset is for backwards compatibility. + + It contains the following rules: + + [CyclomaticComplexity](pmd_rules_plsql_design.html#cyclomaticcomplexity), [ExcessiveMethodLength](pmd_rules_plsql_design.html#excessivemethodlength), [ExcessiveObjectLength](pmd_rules_plsql_design.html#excessiveobjectlength), [ExcessivePackageBodyLength](pmd_rules_plsql_design.html#excessivepackagebodylength), [ExcessivePackageSpecificationLength](pmd_rules_plsql_design.html#excessivepackagespecificationlength), [ExcessiveParameterList](pmd_rules_plsql_design.html#excessiveparameterlist), [ExcessiveTypeLength](pmd_rules_plsql_design.html#excessivetypelength), [NcssMethodCount](pmd_rules_plsql_design.html#ncssmethodcount), [NcssObjectCount](pmd_rules_plsql_design.html#ncssobjectcount), [NPathComplexity](pmd_rules_plsql_design.html#npathcomplexity), [TooManyFields](pmd_rules_plsql_design.html#toomanyfields), [TooManyMethods](pmd_rules_plsql_design.html#toomanymethods) + +* PLSQL DATETIME (`rulesets/plsql/dates.xml`): + + <span style="border-radius: 0.25em; color: #fff; padding: 0.2em 0.6em 0.3em; display: inline; background-color: #d9534f; font-size: 75%;">Deprecated</span> This ruleset is for backwards compatibility. + + It contains the following rules: + + [TO_DATE_TO_CHAR](pmd_rules_plsql_errorprone.html#to_date_to_char), [TO_DATEWithoutDateFormat](pmd_rules_plsql_errorprone.html#to_datewithoutdateformat), [TO_TIMESTAMPWithoutDateFormat](pmd_rules_plsql_errorprone.html#to_timestampwithoutdateformat) + +* Strict Syntax (`rulesets/plsql/strictsyntax.xml`): + + <span style="border-radius: 0.25em; color: #fff; padding: 0.2em 0.6em 0.3em; display: inline; background-color: #d9534f; font-size: 75%;">Deprecated</span> This ruleset is for backwards compatibility. + + It contains the following rules: + + [MisplacedPragma](pmd_rules_plsql_codestyle.html#misplacedpragma) + +* Tom Kyte's Despair (`rulesets/plsql/TomKytesDespair.xml`): + + <span style="border-radius: 0.25em; color: #fff; padding: 0.2em 0.6em 0.3em; display: inline; background-color: #d9534f; font-size: 75%;">Deprecated</span> This ruleset is for backwards compatibility. + + It contains the following rules: + + [TomKytesDespair](pmd_rules_plsql_bestpractices.html#tomkytesdespair) + diff --git a/docs/pages/pmd/rules/plsql/TomKytesDespair.md b/docs/pages/pmd/rules/plsql/TomKytesDespair.md deleted file mode 100644 index d6f2bbdf6bf..00000000000 --- a/docs/pages/pmd/rules/plsql/TomKytesDespair.md +++ /dev/null @@ -1,71 +0,0 @@ ---- -title: Tom Kyte's Despair -summary: Rules based on Thomas Kyte's recommendations on http://asktom.oracle.com/ and http://tkyte.blogspot.com/. -permalink: pmd_rules_plsql_TomKytesDespair.html -folder: pmd/rules/plsql -sidebaractiveurl: /pmd_rules_plsql.html -editmepath: ../pmd-plsql/src/main/resources/rulesets/plsql/TomKytesDespair.xml -keywords: Tom Kyte's Despair, TomKytesDespair ---- -## TomKytesDespair - -**Since:** PMD 5.1 - -**Priority:** Medium (3) - -"WHEN OTHERS THEN NULL" hides all errors - (Re)RAISE an exception or call RAISE_APPLICATION_ERROR - -``` -//ExceptionHandler[QualifiedName/@Image='OTHERS' and upper-case(Statement/UnlabelledStatement/Expression/@Image)='NULL'] -``` - -**Example(s):** - -``` sql -CREATE OR REPLACE PACKAGE BODY update_planned_hrs -IS - -PROCEDURE set_new_planned (p_emp_id IN NUMBER, p_project_id IN NUMBER, p_hours IN NUMBER) -IS -BEGIN - UPDATE employee_on_activity ea - SET ea.ea_planned_hours = p_hours - WHERE - ea.ea_emp_id = p_emp_id - AND ea.ea_proj_id = p_project_id; - -EXCEPTION - WHEN NO_DATA_FOUND THEN - RAISE_APPLICATION_ERROR (-20100, 'No such employee or project'); - -END set_new_planned; - -FUNCTION existing_planned (p_emp_id IN NUMBER, p_project_id IN NUMBER) RETURN NUMBER - -IS - -existing_hours NUMBER(4); - -BEGIN - SELECT ea.ea_planned_hours INTO existing_hours - FROM employee_on_activity ea - WHERE - ea.ea_emp_id = p_emp_id - AND ea.ea_proj_id = p_project_id; - - RETURN (existing_hours); - - EXCEPTION - WHEN OTHERS THEN NULL; - - END existing_planned; - -END update_planned_hrs; -/ -``` - -**Use this rule by referencing it:** -``` xml -<rule ref="rulesets/plsql/TomKytesDespair.xml/TomKytesDespair" /> -``` - diff --git a/docs/pages/pmd/rules/plsql/bestpractices.md b/docs/pages/pmd/rules/plsql/bestpractices.md new file mode 100644 index 00000000000..498922eab21 --- /dev/null +++ b/docs/pages/pmd/rules/plsql/bestpractices.md @@ -0,0 +1,73 @@ +--- +title: Best Practices +summary: Rules which enforce generally accepted best practices. +permalink: pmd_rules_plsql_bestpractices.html +folder: pmd/rules/plsql +sidebaractiveurl: /pmd_rules_plsql.html +editmepath: ../pmd-plsql/src/main/resources/category/plsql/bestpractices.xml +keywords: Best Practices, TomKytesDespair +language: PLSQL +--- +## TomKytesDespair + +**Since:** PMD 5.1 + +**Priority:** Medium (3) + +"WHEN OTHERS THEN NULL" hides all errors - (Re)RAISE an exception or call RAISE_APPLICATION_ERROR + +**This rule is defined by the following XPath expression:** +``` xpath +//ExceptionHandler[QualifiedName/@Image='OTHERS' and upper-case(Statement/UnlabelledStatement/Expression/@Image)='NULL'] +``` + +**Example(s):** + +``` sql +CREATE OR REPLACE PACKAGE BODY update_planned_hrs +IS + +PROCEDURE set_new_planned (p_emp_id IN NUMBER, p_project_id IN NUMBER, p_hours IN NUMBER) +IS +BEGIN + UPDATE employee_on_activity ea + SET ea.ea_planned_hours = p_hours + WHERE + ea.ea_emp_id = p_emp_id + AND ea.ea_proj_id = p_project_id; + +EXCEPTION + WHEN NO_DATA_FOUND THEN + RAISE_APPLICATION_ERROR (-20100, 'No such employee or project'); + +END set_new_planned; + +FUNCTION existing_planned (p_emp_id IN NUMBER, p_project_id IN NUMBER) RETURN NUMBER + +IS + +existing_hours NUMBER(4); + +BEGIN + SELECT ea.ea_planned_hours INTO existing_hours + FROM employee_on_activity ea + WHERE + ea.ea_emp_id = p_emp_id + AND ea.ea_proj_id = p_project_id; + + RETURN (existing_hours); + + EXCEPTION + WHEN OTHERS THEN NULL; + + END existing_planned; + +END update_planned_hrs; +/ +``` + +**Use this rule by referencing it:** +``` xml +<rule ref="category/plsql/bestpractices.xml/TomKytesDespair" /> +``` + diff --git a/docs/pages/pmd/rules/plsql/codesize.md b/docs/pages/pmd/rules/plsql/codesize.md deleted file mode 100644 index 60c525d2e5d..00000000000 --- a/docs/pages/pmd/rules/plsql/codesize.md +++ /dev/null @@ -1,638 +0,0 @@ ---- -title: Code Size -summary: The Code Size ruleset contains rules that find problems related to code size or complexity. -permalink: pmd_rules_plsql_codesize.html -folder: pmd/rules/plsql -sidebaractiveurl: /pmd_rules_plsql.html -editmepath: ../pmd-plsql/src/main/resources/rulesets/plsql/codesize.xml -keywords: Code Size, NPathComplexity, ExcessiveMethodLength, ExcessiveParameterList, ExcessiveObjectLength, ExcessiveTypeLength, ExcessivePackageBodyLength, ExcessivePackageSpecificationLength, CyclomaticComplexity, TooManyFields, NcssMethodCount, NcssObjectCount, TooManyMethods ---- -## CyclomaticComplexity - -**Since:** PMD 5.1 - -**Priority:** Medium (3) - -Complexity directly affects maintenance costs is determined by the number of decision points in a method -plus one for the method entry. The decision points include 'if', 'while', 'for', and 'case labels' calls. -Generally, numbers ranging from 1-4 denote low complexity, 5-7 denote moderate complexity, 8-10 denote -high complexity, and 11+ is very high complexity. - -**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.plsql.rule.codesize.CyclomaticComplexityRule](https://github.com/pmd/pmd/blob/master/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/rule/codesize/CyclomaticComplexityRule.java) - -**Example(s):** - -``` sql --- Cyclomatic Complexity of 25 -CREATE OR REPLACE PACKAGE BODY pkg_pmd_working_sequence AS -1 PROCEDURE ty_logger IS BEGIN -2 IF true - THEN - DBMS_OUTPUT.PUT_LINE('IF/THEN l_Integer='||l_integer); -3 IF true - THEN - DBMS_OUTPUT.PUT_LINE('IF/THEN l_Integer='||l_integer); -4 IF true - THEN - DBMS_OUTPUT.PUT_LINE('IF/THEN l_Integer='||l_integer); -5 ELSIF false - THEN - DBMS_OUTPUT.PUT_LINE('ELSIF l_Integer='||l_integer); - ELSE - DBMS_OUTPUT.PUT_LINE('ELSIF l_Integer='||l_integer); - END IF; -6 ELSIF false - THEN - DBMS_OUTPUT.PUT_LINE('ELSIF l_Integer='||l_integer); -7 IF true - THEN - DBMS_OUTPUT.PUT_LINE('IF/THEN l_Integer='||l_integer); -8 ELSIF false - THEN - DBMS_OUTPUT.PUT_LINE('ELSIF l_Integer='||l_integer); - ELSE - DBMS_OUTPUT.PUT_LINE('ELSIF l_Integer='||l_integer); - END IF; - ELSE - DBMS_OUTPUT.PUT_LINE('ELSIF l_Integer='||l_integer); -9 IF true - THEN - DBMS_OUTPUT.PUT_LINE('IF/THEN l_Integer='||l_integer); -10 ELSIF false - THEN - DBMS_OUTPUT.PUT_LINE('ELSIF l_Integer='||l_integer); - ELSE - DBMS_OUTPUT.PUT_LINE('ELSIF l_Integer='||l_integer); - END IF; - END IF; -11 ELSIF false - THEN - DBMS_OUTPUT.PUT_LINE('ELSIF l_Integer='||l_integer); -12 IF true - THEN - DBMS_OUTPUT.PUT_LINE('IF/THEN l_Integer='||l_integer); -13 IF true - THEN - DBMS_OUTPUT.PUT_LINE('IF/THEN l_Integer='||l_integer); -14 ELSIF false - THEN - DBMS_OUTPUT.PUT_LINE('ELSIF l_Integer='||l_integer); - ELSE - DBMS_OUTPUT.PUT_LINE('ELSIF l_Integer='||l_integer); - END IF; -15 ELSIF false - THEN -16 IF true - THEN - DBMS_OUTPUT.PUT_LINE('IF/THEN l_Integer='||l_integer); -17 ELSIF false - THEN - DBMS_OUTPUT.PUT_LINE('ELSIF l_Integer='||l_integer); - ELSE - DBMS_OUTPUT.PUT_LINE('ELSIF l_Integer='||l_integer); - END IF; - DBMS_OUTPUT.PUT_LINE('ELSIF l_Integer='||l_integer); - ELSE - DBMS_OUTPUT.PUT_LINE('ELSIF l_Integer='||l_integer); - END IF; - ELSE - DBMS_OUTPUT.PUT_LINE('ELSIF l_Integer='||l_integer); -18 IF true - THEN - DBMS_OUTPUT.PUT_LINE('IF/THEN l_Integer='||l_integer); -19 IF true - THEN - DBMS_OUTPUT.PUT_LINE('IF/THEN l_Integer='||l_integer); -20 ELSIF false - THEN - DBMS_OUTPUT.PUT_LINE('ELSIF l_Integer='||l_integer); - ELSE - DBMS_OUTPUT.PUT_LINE('ELSIF l_Integer='||l_integer); - END IF; -21 ELSIF false - THEN - DBMS_OUTPUT.PUT_LINE('ELSIF l_Integer='||l_integer); -22 IF true - THEN - DBMS_OUTPUT.PUT_LINE('IF/THEN l_Integer='||l_integer); -23 ELSIF false - THEN - DBMS_OUTPUT.PUT_LINE('ELSIF l_Integer='||l_integer); - ELSE - DBMS_OUTPUT.PUT_LINE('ELSIF l_Integer='||l_integer); - END IF; - ELSE - DBMS_OUTPUT.PUT_LINE('ELSIF l_Integer='||l_integer); -24 IF true - THEN - DBMS_OUTPUT.PUT_LINE('IF/THEN l_Integer='||l_integer); -25 ELSIF false - THEN - DBMS_OUTPUT.PUT_LINE('ELSIF l_Integer='||l_integer); - ELSE - DBMS_OUTPUT.PUT_LINE('ELSIF l_Integer='||l_integer); - END IF; - END IF; - END IF; -END; - -END; -``` - -**This rule has the following properties:** - -|Name|Default Value|Description| -|----|-------------|-----------| -|showMethodsComplexity|true|Add method average violations to the report| -|showClassesComplexity|true|Add class average violations to the report| -|reportLevel|10|Cyclomatic Complexity reporting threshold| - -**Use this rule by referencing it:** -``` xml -<rule ref="rulesets/plsql/codesize.xml/CyclomaticComplexity" /> -``` - -## ExcessiveMethodLength - -**Since:** PMD 5.1 - -**Priority:** Medium (3) - -When methods are excessively long this usually indicates that the method is doing more than its -name/signature might suggest. They also become challenging for others to digest since excessive -scrolling causes readers to lose focus. -Try to reduce the method length by creating helper methods and removing any copy/pasted code. - -**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.plsql.rule.codesize.ExcessiveMethodLengthRule](https://github.com/pmd/pmd/blob/master/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/rule/codesize/ExcessiveMethodLengthRule.java) - -**Example(s):** - -``` sql -CREATE OR REPLACE -PROCEDURE doSomething BEGIN - DBMS_OUTPUT.PUT_LINE("Hello world!"); - DBMS_OUTPUT.PUT_LINE("Hello world!"); - -- 98 copies omitted for brevity. -END; -``` - -**This rule has the following properties:** - -|Name|Default Value|Description| -|----|-------------|-----------| -|topscore||Top score value| -|minimum||Minimum reporting threshold| -|sigma||Sigma value| - -**Use this rule by referencing it:** -``` xml -<rule ref="rulesets/plsql/codesize.xml/ExcessiveMethodLength" /> -``` - -## ExcessiveObjectLength - -**Since:** PMD 5.1 - -**Priority:** Medium (3) - -Excessive object line lengths are usually indications that the object may be burdened with excessive -responsibilities that could be provided by other objects. In breaking these methods -apart the code becomes more managable and ripe for reuse. - -**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.plsql.rule.codesize.ExcessiveObjectLengthRule](https://github.com/pmd/pmd/blob/master/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/rule/codesize/ExcessiveObjectLengthRule.java) - -**Example(s):** - -``` sql -CREATE OR REPLACE -PACKAGE BODY Foo AS - PROCEDURE bar1 IS BEGIN - -- 1000 lines of code - END bar1; - PROCEDURE bar2 IS BEGIN - -- 1000 lines of code - END bar2; - PROCEDURE bar3 IS BEGIN - -- 1000 lines of code - END bar3; - - - PROCEDURE barN IS BEGIN - -- 1000 lines of code - END barn; -END; -``` - -**This rule has the following properties:** - -|Name|Default Value|Description| -|----|-------------|-----------| -|topscore||Top score value| -|minimum||Minimum reporting threshold| -|sigma||Sigma value| - -**Use this rule by referencing it:** -``` xml -<rule ref="rulesets/plsql/codesize.xml/ExcessiveObjectLength" /> -``` - -## ExcessivePackageBodyLength - -**Since:** PMD 5.1 - -**Priority:** Medium (3) - -Excessive class file lengths are usually indications that the class may be burdened with excessive -responsibilities that could be provided by external classes or functions. In breaking these methods -apart the code becomes more managable and ripe for reuse. - -**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.plsql.rule.codesize.ExcessivePackageBodyLengthRule](https://github.com/pmd/pmd/blob/master/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/rule/codesize/ExcessivePackageBodyLengthRule.java) - -**Example(s):** - -``` sql -CREATE OR REPLACE -PACKAGE BODY Foo AS - PROCEDURE bar1 IS BEGIN - -- 1000 lines of code - END bar1; - PROCEDURE bar2 IS BEGIN - -- 1000 lines of code - END bar2; - PROCEDURE bar3 IS BEGIN - -- 1000 lines of code - END bar3; - - - PROCEDURE barN IS BEGIN - -- 1000 lines of code - END barn; -END; -``` - -**This rule has the following properties:** - -|Name|Default Value|Description| -|----|-------------|-----------| -|topscore||Top score value| -|minimum||Minimum reporting threshold| -|sigma||Sigma value| - -**Use this rule by referencing it:** -``` xml -<rule ref="rulesets/plsql/codesize.xml/ExcessivePackageBodyLength" /> -``` - -## ExcessivePackageSpecificationLength - -**Since:** PMD 5.1 - -**Priority:** Medium (3) - -Excessive class file lengths are usually indications that the class may be burdened with excessive -responsibilities that could be provided by external classes or functions. In breaking these methods -apart the code becomes more managable and ripe for reuse. - -**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.plsql.rule.codesize.ExcessivePackageSpecificationLengthRule](https://github.com/pmd/pmd/blob/master/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/rule/codesize/ExcessivePackageSpecificationLengthRule.java) - -**Example(s):** - -``` sql -CREATE OR REPLACE -PACKAGE Foo AS - PROCEDURE bar1; - PROCEDURE bar2; - PROCEDURE bar3; - - ... - - PROCEDURE barN; -END; -``` - -**This rule has the following properties:** - -|Name|Default Value|Description| -|----|-------------|-----------| -|topscore||Top score value| -|minimum||Minimum reporting threshold| -|sigma||Sigma value| - -**Use this rule by referencing it:** -``` xml -<rule ref="rulesets/plsql/codesize.xml/ExcessivePackageSpecificationLength" /> -``` - -## ExcessiveParameterList - -**Since:** PMD 5.1 - -**Priority:** Medium (3) - -Methods with numerous parameters are a challenge to maintain, especially if most of them share the -same datatype. These situations usually denote the need for new objects to wrap the numerous parameters. - -**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.plsql.rule.codesize.ExcessiveParameterListRule](https://github.com/pmd/pmd/blob/master/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/rule/codesize/ExcessiveParameterListRule.java) - -**Example(s):** - -``` sql -CREATE OR REPLACE -PROCEDURE addPerson( -- too many arguments liable to be mixed up - birthYear pls_integer, birthMonth pls_integer, birthDate pls_integer, height pls_integer, weight pls_integer, ssn pls_integer) { - - . . . . -END ADDPERSON; - -CREATE OR REPLACE -PROCEDURE addPerson( -- preferred approach - birthdate DATE, measurements BodyMeasurements , ssn INTEGER) BEGIN - - . . . . -END; -``` - -**This rule has the following properties:** - -|Name|Default Value|Description| -|----|-------------|-----------| -|topscore||Top score value| -|minimum||Minimum reporting threshold| -|sigma||Sigma value| - -**Use this rule by referencing it:** -``` xml -<rule ref="rulesets/plsql/codesize.xml/ExcessiveParameterList" /> -``` - -## ExcessiveTypeLength - -**Since:** PMD 5.1 - -**Priority:** Medium (3) - -Excessive class file lengths are usually indications that the class may be burdened with excessive -responsibilities that could be provided by external classes or functions. In breaking these methods -apart the code becomes more managable and ripe for reuse. - -**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.plsql.rule.codesize.ExcessiveTypeLengthRule](https://github.com/pmd/pmd/blob/master/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/rule/codesize/ExcessiveTypeLengthRule.java) - -**Example(s):** - -``` sql -CREATE OR REPLACE -TYPE BODY Foo AS - MEMBER PROCEDURE bar1 IS BEGIN - -- 1000 lines of code - END bar1; - MEMBER PROCEDURE bar2 IS BEGIN - -- 1000 lines of code - END bar2; - MEMBER PROCEDURE bar3 IS BEGIN - -- 1000 lines of code - END bar3; - - - MEMBER PROCEDURE barN IS BEGIN - -- 1000 lines of code - END barn; -END; -``` - -**This rule has the following properties:** - -|Name|Default Value|Description| -|----|-------------|-----------| -|topscore||Top score value| -|minimum||Minimum reporting threshold| -|sigma||Sigma value| - -**Use this rule by referencing it:** -``` xml -<rule ref="rulesets/plsql/codesize.xml/ExcessiveTypeLength" /> -``` - -## NcssMethodCount - -**Since:** PMD 5.1 - -**Priority:** Medium (3) - -This rule uses the NCSS (Non-Commenting Source Statements) algorithm to determine the number of lines -of code for a given method. NCSS ignores comments, and counts actual statements. Using this algorithm, -lines of code that are split are counted as one. - -**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.plsql.rule.codesize.NcssMethodCountRule](https://github.com/pmd/pmd/blob/master/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/rule/codesize/NcssMethodCountRule.java) - -**Example(s):** - -``` sql -CREATE OR REPLACE PACKAGE BODY AS - FUNCTION methd RETURN INTEGER IS - BEGIN - RETURN 1;; - END; -END; -``` - -**This rule has the following properties:** - -|Name|Default Value|Description| -|----|-------------|-----------| -|topscore||Top score value| -|minimum||Minimum reporting threshold| -|sigma||Sigma value| - -**Use this rule by referencing it:** -``` xml -<rule ref="rulesets/plsql/codesize.xml/NcssMethodCount" /> -``` - -## NcssObjectCount - -**Since:** PMD 5.1 - -**Priority:** Medium (3) - -This rule uses the NCSS (Non-Commenting Source Statements) algorithm to determine the number of lines -of code for a given Oracle object. NCSS ignores comments, and counts actual statements. Using this algorithm, -lines of code that are split are counted as one. - -**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.plsql.rule.codesize.NcssObjectCountRule](https://github.com/pmd/pmd/blob/master/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/rule/codesize/NcssObjectCountRule.java) - -**Example(s):** - -``` sql -CREATE OR REPLACE PACKAGE pkg_ - PROCEDURE Foo IS - BEGIN - --this class only has 6 NCSS lines - super(); - super(); - END; -} -``` - -**This rule has the following properties:** - -|Name|Default Value|Description| -|----|-------------|-----------| -|topscore||Top score value| -|minimum||Minimum reporting threshold| -|sigma||Sigma value| - -**Use this rule by referencing it:** -``` xml -<rule ref="rulesets/plsql/codesize.xml/NcssObjectCount" /> -``` - -## NPathComplexity - -**Since:** PMD 5.1 - -**Priority:** Medium (3) - -The NPath complexity of a method is the number of acyclic execution paths through that method. -A threshold of 200 is generally considered the point where measures should be taken to reduce -complexity and increase readability. - -**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.plsql.rule.codesize.NPathComplexityRule](https://github.com/pmd/pmd/blob/master/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/rule/codesize/NPathComplexityRule.java) - -**Example(s):** - -``` sql -CREATE OR REPLACE -PROCEDURE bar AS BEGIN -- this is something more complex than it needs to be, - if (y) THEN -- it should be broken down into smaller methods or functions - for j IN 0 .. j-1 LOOP - if (j > r) THEN - doSomething; - while (f < 5 ) LOOP - anotherThing; - f := f - 27; - END LOOP; - else - tryThis(); - END IF; - END LOOP; - END IF; - if ( r - n > 45) THEN - while (doMagic) LOOP - findRabbits; - END LOOP; - END IF; - BEGIN - doSomethingDangerous(); - EXCEPTION WHEN FooException THEN - makeAmends; - BEGIN - dontDoItAgain; - EXCEPTION - WHEN OTHERS THEN - log_problem; - END; - END; -END; -``` - -**This rule has the following properties:** - -|Name|Default Value|Description| -|----|-------------|-----------| -|topscore||Top score value| -|minimum||Minimum reporting threshold| -|sigma||Sigma value| - -**Use this rule by referencing it:** -``` xml -<rule ref="rulesets/plsql/codesize.xml/NPathComplexity" /> -``` - -## TooManyFields - -**Since:** PMD 5.1 - -**Priority:** Medium (3) - -Classes that have too many fields can become unwieldy and could be redesigned to have fewer fields, -possibly through grouping related fields in new objects. For example, a class with individual -city/state/zip fields could park them within a single Address field. - -**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.plsql.rule.codesize.TooManyFieldsRule](https://github.com/pmd/pmd/blob/master/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/rule/codesize/TooManyFieldsRule.java) - -**Example(s):** - -``` sql -CREATE OR REPLACE PACKAGE pkg_too_many_fields AS - C_CHAR_A CONSTANT CHAR(1 CHAR) := 'A'; - C_CHAR_B CONSTANT CHAR(1 CHAR) := 'B'; - ... - C_CHAR_Z CONSTANT CHAR(1 CHAR) := 'Z'; -END pkg_too_many_fields; -``` - -**This rule has the following properties:** - -|Name|Default Value|Description| -|----|-------------|-----------| -|maxfields|15|Max allowable fields| - -**Use this rule by referencing it:** -``` xml -<rule ref="rulesets/plsql/codesize.xml/TooManyFields" /> -``` - -## TooManyMethods - -**Since:** PMD 5.1 - -**Priority:** Medium (3) - -A package or type with too many methods is probably a good suspect for refactoring, in order to reduce its complexity and find a way to -have more fine grained objects. - -``` -//node() - [ ( - local-name(.) = 'PackageSpecification' - or - local-name(.) = 'TypeSpecification' - ) - and - ( - count(/descendant::ProgramUnit[ - not ( - starts-with(@Image,'get') - or - starts-with(@Image,'set') - or - starts-with(@Image,'is') - ) - ] - ) - + - count(/descendant::TypeMethod[ - not ( - starts-with(@Image,'get') - or - starts-with(@Image,'set') - or - starts-with(@Image,'is') - ) - ] - ) - ) > $maxmethods - ] -``` - -**This rule has the following properties:** - -|Name|Default Value|Description| -|----|-------------|-----------| -|maxmethods|1|The method count reporting threshold| - -**Use this rule by referencing it:** -``` xml -<rule ref="rulesets/plsql/codesize.xml/TooManyMethods" /> -``` - diff --git a/docs/pages/pmd/rules/plsql/codestyle.md b/docs/pages/pmd/rules/plsql/codestyle.md new file mode 100644 index 00000000000..eeecd344a33 --- /dev/null +++ b/docs/pages/pmd/rules/plsql/codestyle.md @@ -0,0 +1,192 @@ +--- +title: Code Style +summary: Rules which enforce a specific coding style. +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, CodeFormat, MisplacedPragma, ForLoopNaming +language: PLSQL +--- +## CodeFormat + +**Since:** PMD 6.9.0 + +**Priority:** Medium (3) + +This rule verifies that the PLSQL code is properly formatted. The following checks are executed: + +SQL Queries: + +* The selected columns must be each on a new line +* The keywords (BULK COLLECT INTO, FROM) start on a new line and are indented by one level +* UNION should be on the same indentation level as SELECT +* Each JOIN is on a new line. If there are more than one JOIN conditions, then each condition should be + on a separate line. + +Parameter definitions for procedures: + +* Each parameter should be on a new line +* Variable names as well as their types should be aligned + +Variable declarations: + +* Each variable should be on a new line +* Variable names as well as their types should be aligned + +Calling a procedure: + +* If there are more than 3 parameters + * then named parameters should be used + * and each parameter should be on a new line + +**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.plsql.rule.codestyle.CodeFormatRule](https://github.com/pmd/pmd/blob/master/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/rule/codestyle/CodeFormatRule.java) + +**Example(s):** + +``` sql +BEGIN + -- select columns each on a separate line + SELECT cmer_id + ,version + ,cmp_id + BULK COLLECT INTO v_cmer_ids + ,v_versions + ,v_cmp_ids + FROM cmer; + + -- each parameter on a new line + PROCEDURE create_prospect( + company_info_in IN prospects.company_info%TYPE -- Organization + ,firstname_in IN persons.firstname%TYPE -- FirstName + ,lastname_in IN persons.lastname%TYPE -- LastName + ); + + -- more than three parameters, each parameter on a separate line + webcrm_marketing.prospect_ins( + cmp_id_in => NULL + ,company_info_in => company_info_in + ,firstname_in => firstname_in + ,lastname_in => lastname_in + ,slt_code_in => NULL + ); + +END; +``` + +**This rule has the following properties:** + +|Name|Default Value|Description|Multivalued| +|----|-------------|-----------|-----------| +|indentation|2|Indentation to be used for blocks|no| + +**Use this rule by referencing it:** +``` xml +<rule ref="category/plsql/codestyle.xml/CodeFormat" /> +``` + +## 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 +<rule ref="category/plsql/codestyle.xml/ForLoopNaming" /> +``` + +## MisplacedPragma + +**Since:** PMD 5.5.2 + +**Priority:** Medium (3) + +Oracle states that the PRAQMA AUTONOMOUS_TRANSACTION must be in the declaration block, +but the code does not complain, when being compiled on the 11g DB. +https://docs.oracle.com/cd/B28359_01/appdev.111/b28370/static.htm#BABIIHBJ + +**This rule is defined by the following XPath expression:** +``` xpath +//ProgramUnit/Pragma +``` + +**Example(s):** + +``` sql +create or replace package inline_pragma_error is + +end; +/ + +create or replace package body inline_pragma_error is + procedure do_transaction(p_input_token in varchar(200)) is + PRAGMA AUTONOMOUS_TRANSACTION; /* this is correct place for PRAGMA */ + begin + PRAGMA AUTONOMOUS_TRANSACTION; /* this is the wrong place for PRAGMA -> violation */ + /* do something */ + COMMIT; + end do_transaction; + +end inline_pragma_error; +/ +``` + +**Use this rule by referencing it:** +``` xml +<rule ref="category/plsql/codestyle.xml/MisplacedPragma" /> +``` + diff --git a/docs/pages/pmd/rules/plsql/dates.md b/docs/pages/pmd/rules/plsql/dates.md deleted file mode 100644 index 63e581f4aee..00000000000 --- a/docs/pages/pmd/rules/plsql/dates.md +++ /dev/null @@ -1,146 +0,0 @@ ---- -title: PLSQL DATETIME -summary: The Dates ruleset deals with PLSQL DATETIME usages. -permalink: pmd_rules_plsql_dates.html -folder: pmd/rules/plsql -sidebaractiveurl: /pmd_rules_plsql.html -editmepath: ../pmd-plsql/src/main/resources/rulesets/plsql/dates.xml -keywords: PLSQL DATETIME, TO_DATEWithoutDateFormat, TO_DATE_TO_CHAR, TO_TIMESTAMPWithoutDateFormat ---- -## TO_DATE_TO_CHAR - -**Since:** PMD 5.1 - -**Priority:** Medium (3) - -TO_DATE(TO_CHAR(date-variable)) used to remove time component - use TRUNC(date-veriable) - -``` -//PrimaryExpression - [PrimaryPrefix/Name/@Image='TO_DATE'] - [count(PrimarySuffix/Arguments/ArgumentList/Argument) = 1] - [.//PrimaryExpression - [PrimaryPrefix/Name/@Image='TO_CHAR'] - [count(PrimarySuffix/Arguments/ArgumentList/Argument) = 1] - ] -``` - -**Example(s):** - -``` sql -CREATE OR REPLACE PACKAGE BODY date_utilities -IS - --- Take single parameter, relyimg on current default NLS date format -FUNCTION strip_time (p_date IN DATE) RETURN DATE -IS -BEGIN - RETURN TO_DATE(TO_CHAR(p_date)); -END strip_time; - - -END date_utilities; -/ -``` - -**Use this rule by referencing it:** -``` xml -<rule ref="rulesets/plsql/dates.xml/TO_DATE_TO_CHAR" /> -``` - -## TO_DATEWithoutDateFormat - -**Since:** PMD 5.1 - -**Priority:** Medium (3) - -TO_DATE without date format- use TO_DATE(expression, date-format) - -``` -//PrimaryExpression[PrimaryPrefix/Name/@Image='TO_DATE' and count(PrimarySuffix/Arguments/ArgumentList/Argument) = 1 ] -``` - -**Example(s):** - -``` sql -CREATE OR REPLACE PACKAGE BODY date_utilities -IS - --- Take single parameter, relyimg on current default NLS date format -FUNCTION to_date_single_parameter (p_date_string IN VARCHAR2) RETURN DATE -IS -BEGIN - RETURN TO_DATE(p_date_string); -END to_date_single_parameter ; - --- Take 2 parameters, using an explicit date format string -FUNCTION to_date_two_parameters (p_date_string IN VARCHAR2, p_format_mask IN VARCHAR2) RETURN DATE -IS -BEGIN - TO_DATE(p_date_string, p_date_format); -END to_date_two_parameters; - --- Take 3 parameters, using an explicit date format string and an explicit language -FUNCTION to_date_three_parameters (p_date_string IN VARCHAR2, p_format_mask IN VARCHAR2, p_nls_language VARCHAR2 ) RETURN DATE -IS -BEGIN - TO_DATE(p_date_string, p_format_mask, p_nls_language); -END to_date_three_parameters; - -END date_utilities; -/ -``` - -**Use this rule by referencing it:** -``` xml -<rule ref="rulesets/plsql/dates.xml/TO_DATEWithoutDateFormat" /> -``` - -## TO_TIMESTAMPWithoutDateFormat - -**Since:** PMD 5.1 - -**Priority:** Medium (3) - -TO_TIMESTAMP without date format- use TO_TIMESTAMP(expression, date-format) - -``` -//PrimaryExpression[PrimaryPrefix/Name/@Image='TO_TIMESTAMP' and count(PrimarySuffix/Arguments/ArgumentList/Argument) = 1 ] -``` - -**Example(s):** - -``` sql -CREATE OR REPLACE PACKAGE BODY date_utilities -IS - --- Take single parameter, relyimg on current default NLS date format -FUNCTION to_timestamp_single_parameter (p_date_string IN VARCHAR2) RETURN DATE -IS -BEGIN - RETURN TO_TIMESTAMP(p_date_string); -END to_timestamp_single_parameter; - --- Take 2 parameters, using an explicit date format string -FUNCTION to_timestamp_two_parameters (p_date_string IN VARCHAR2, p_format_mask IN VARCHAR2) RETURN DATE -IS -BEGIN - TO_TIMESTAMP(p_date_string, p_date_format); -END to_timestamp_two_parameters; - --- Take 3 parameters, using an explicit date format string and an explicit language -FUNCTION to_timestamp_three_parameters (p_date_string IN VARCHAR2, p_format_mask IN VARCHAR2, p_nls_language VARCHAR2 ) RETURN DATE -IS -BEGIN - TO_TIMESTAMP(p_date_string, p_format_mask, p_nls_language); -END to_timestamp_three_parameters; - -END date_utilities; -/ -``` - -**Use this rule by referencing it:** -``` xml -<rule ref="rulesets/plsql/dates.xml/TO_TIMESTAMPWithoutDateFormat" /> -``` - diff --git a/docs/pages/pmd/rules/plsql/design.md b/docs/pages/pmd/rules/plsql/design.md new file mode 100644 index 00000000000..68e2fb29b71 --- /dev/null +++ b/docs/pages/pmd/rules/plsql/design.md @@ -0,0 +1,640 @@ +--- +title: Design +summary: Rules that help you discover design issues. +permalink: pmd_rules_plsql_design.html +folder: pmd/rules/plsql +sidebaractiveurl: /pmd_rules_plsql.html +editmepath: ../pmd-plsql/src/main/resources/category/plsql/design.xml +keywords: Design, CyclomaticComplexity, ExcessiveMethodLength, ExcessiveObjectLength, ExcessivePackageBodyLength, ExcessivePackageSpecificationLength, ExcessiveParameterList, ExcessiveTypeLength, NcssMethodCount, NcssObjectCount, NPathComplexity, TooManyFields, TooManyMethods +language: PLSQL +--- +## CyclomaticComplexity + +**Since:** PMD 5.1 + +**Priority:** Medium (3) + +Complexity directly affects maintenance costs is determined by the number of decision points in a method +plus one for the method entry. The decision points include 'if', 'while', 'for', and 'case labels' calls. +Generally, numbers ranging from 1-4 denote low complexity, 5-7 denote moderate complexity, 8-10 denote +high complexity, and 11+ is very high complexity. + +**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.plsql.rule.design.CyclomaticComplexityRule](https://github.com/pmd/pmd/blob/master/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/rule/design/CyclomaticComplexityRule.java) + +**Example(s):** + +``` sql +-- Cyclomatic Complexity of 25 +CREATE OR REPLACE PACKAGE BODY pkg_pmd_working_sequence AS +1 PROCEDURE ty_logger IS BEGIN +2 IF true + THEN + DBMS_OUTPUT.PUT_LINE('IF/THEN l_Integer='||l_integer); +3 IF true + THEN + DBMS_OUTPUT.PUT_LINE('IF/THEN l_Integer='||l_integer); +4 IF true + THEN + DBMS_OUTPUT.PUT_LINE('IF/THEN l_Integer='||l_integer); +5 ELSIF false + THEN + DBMS_OUTPUT.PUT_LINE('ELSIF l_Integer='||l_integer); + ELSE + DBMS_OUTPUT.PUT_LINE('ELSIF l_Integer='||l_integer); + END IF; +6 ELSIF false + THEN + DBMS_OUTPUT.PUT_LINE('ELSIF l_Integer='||l_integer); +7 IF true + THEN + DBMS_OUTPUT.PUT_LINE('IF/THEN l_Integer='||l_integer); +8 ELSIF false + THEN + DBMS_OUTPUT.PUT_LINE('ELSIF l_Integer='||l_integer); + ELSE + DBMS_OUTPUT.PUT_LINE('ELSIF l_Integer='||l_integer); + END IF; + ELSE + DBMS_OUTPUT.PUT_LINE('ELSIF l_Integer='||l_integer); +9 IF true + THEN + DBMS_OUTPUT.PUT_LINE('IF/THEN l_Integer='||l_integer); +10 ELSIF false + THEN + DBMS_OUTPUT.PUT_LINE('ELSIF l_Integer='||l_integer); + ELSE + DBMS_OUTPUT.PUT_LINE('ELSIF l_Integer='||l_integer); + END IF; + END IF; +11 ELSIF false + THEN + DBMS_OUTPUT.PUT_LINE('ELSIF l_Integer='||l_integer); +12 IF true + THEN + DBMS_OUTPUT.PUT_LINE('IF/THEN l_Integer='||l_integer); +13 IF true + THEN + DBMS_OUTPUT.PUT_LINE('IF/THEN l_Integer='||l_integer); +14 ELSIF false + THEN + DBMS_OUTPUT.PUT_LINE('ELSIF l_Integer='||l_integer); + ELSE + DBMS_OUTPUT.PUT_LINE('ELSIF l_Integer='||l_integer); + END IF; +15 ELSIF false + THEN +16 IF true + THEN + DBMS_OUTPUT.PUT_LINE('IF/THEN l_Integer='||l_integer); +17 ELSIF false + THEN + DBMS_OUTPUT.PUT_LINE('ELSIF l_Integer='||l_integer); + ELSE + DBMS_OUTPUT.PUT_LINE('ELSIF l_Integer='||l_integer); + END IF; + DBMS_OUTPUT.PUT_LINE('ELSIF l_Integer='||l_integer); + ELSE + DBMS_OUTPUT.PUT_LINE('ELSIF l_Integer='||l_integer); + END IF; + ELSE + DBMS_OUTPUT.PUT_LINE('ELSIF l_Integer='||l_integer); +18 IF true + THEN + DBMS_OUTPUT.PUT_LINE('IF/THEN l_Integer='||l_integer); +19 IF true + THEN + DBMS_OUTPUT.PUT_LINE('IF/THEN l_Integer='||l_integer); +20 ELSIF false + THEN + DBMS_OUTPUT.PUT_LINE('ELSIF l_Integer='||l_integer); + ELSE + DBMS_OUTPUT.PUT_LINE('ELSIF l_Integer='||l_integer); + END IF; +21 ELSIF false + THEN + DBMS_OUTPUT.PUT_LINE('ELSIF l_Integer='||l_integer); +22 IF true + THEN + DBMS_OUTPUT.PUT_LINE('IF/THEN l_Integer='||l_integer); +23 ELSIF false + THEN + DBMS_OUTPUT.PUT_LINE('ELSIF l_Integer='||l_integer); + ELSE + DBMS_OUTPUT.PUT_LINE('ELSIF l_Integer='||l_integer); + END IF; + ELSE + DBMS_OUTPUT.PUT_LINE('ELSIF l_Integer='||l_integer); +24 IF true + THEN + DBMS_OUTPUT.PUT_LINE('IF/THEN l_Integer='||l_integer); +25 ELSIF false + THEN + DBMS_OUTPUT.PUT_LINE('ELSIF l_Integer='||l_integer); + ELSE + DBMS_OUTPUT.PUT_LINE('ELSIF l_Integer='||l_integer); + END IF; + END IF; + END IF; +END; + +END; +``` + +**This rule has the following properties:** + +|Name|Default Value|Description|Multivalued| +|----|-------------|-----------|-----------| +|showMethodsComplexity|true|Add method average violations to the report|no| +|showClassesComplexity|true|Add class average violations to the report|no| +|reportLevel|10|Cyclomatic Complexity reporting threshold|no| + +**Use this rule by referencing it:** +``` xml +<rule ref="category/plsql/design.xml/CyclomaticComplexity" /> +``` + +## ExcessiveMethodLength + +**Since:** PMD 5.1 + +**Priority:** Medium (3) + +When methods are excessively long this usually indicates that the method is doing more than its +name/signature might suggest. They also become challenging for others to digest since excessive +scrolling causes readers to lose focus. +Try to reduce the method length by creating helper methods and removing any copy/pasted code. + +**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.plsql.rule.design.ExcessiveMethodLengthRule](https://github.com/pmd/pmd/blob/master/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/rule/design/ExcessiveMethodLengthRule.java) + +**Example(s):** + +``` sql +CREATE OR REPLACE +PROCEDURE doSomething BEGIN + DBMS_OUTPUT.PUT_LINE("Hello world!"); + DBMS_OUTPUT.PUT_LINE("Hello world!"); + -- 98 copies omitted for brevity. +END; +``` + +**This rule has the following properties:** + +|Name|Default Value|Description|Multivalued| +|----|-------------|-----------|-----------| +|topscore||Top score value|no| +|minimum||Minimum reporting threshold|no| +|sigma||Sigma value|no| + +**Use this rule by referencing it:** +``` xml +<rule ref="category/plsql/design.xml/ExcessiveMethodLength" /> +``` + +## ExcessiveObjectLength + +**Since:** PMD 5.1 + +**Priority:** Medium (3) + +Excessive object line lengths are usually indications that the object may be burdened with excessive +responsibilities that could be provided by other objects. In breaking these methods +apart the code becomes more managable and ripe for reuse. + +**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.plsql.rule.design.ExcessiveObjectLengthRule](https://github.com/pmd/pmd/blob/master/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/rule/design/ExcessiveObjectLengthRule.java) + +**Example(s):** + +``` sql +CREATE OR REPLACE +PACKAGE BODY Foo AS + PROCEDURE bar1 IS BEGIN + -- 1000 lines of code + END bar1; + PROCEDURE bar2 IS BEGIN + -- 1000 lines of code + END bar2; + PROCEDURE bar3 IS BEGIN + -- 1000 lines of code + END bar3; + + + PROCEDURE barN IS BEGIN + -- 1000 lines of code + END barn; +END; +``` + +**This rule has the following properties:** + +|Name|Default Value|Description|Multivalued| +|----|-------------|-----------|-----------| +|topscore||Top score value|no| +|minimum||Minimum reporting threshold|no| +|sigma||Sigma value|no| + +**Use this rule by referencing it:** +``` xml +<rule ref="category/plsql/design.xml/ExcessiveObjectLength" /> +``` + +## ExcessivePackageBodyLength + +**Since:** PMD 5.1 + +**Priority:** Medium (3) + +Excessive class file lengths are usually indications that the class may be burdened with excessive +responsibilities that could be provided by external classes or functions. In breaking these methods +apart the code becomes more managable and ripe for reuse. + +**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.plsql.rule.design.ExcessivePackageBodyLengthRule](https://github.com/pmd/pmd/blob/master/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/rule/design/ExcessivePackageBodyLengthRule.java) + +**Example(s):** + +``` sql +CREATE OR REPLACE +PACKAGE BODY Foo AS + PROCEDURE bar1 IS BEGIN + -- 1000 lines of code + END bar1; + PROCEDURE bar2 IS BEGIN + -- 1000 lines of code + END bar2; + PROCEDURE bar3 IS BEGIN + -- 1000 lines of code + END bar3; + + + PROCEDURE barN IS BEGIN + -- 1000 lines of code + END barn; +END; +``` + +**This rule has the following properties:** + +|Name|Default Value|Description|Multivalued| +|----|-------------|-----------|-----------| +|topscore||Top score value|no| +|minimum||Minimum reporting threshold|no| +|sigma||Sigma value|no| + +**Use this rule by referencing it:** +``` xml +<rule ref="category/plsql/design.xml/ExcessivePackageBodyLength" /> +``` + +## ExcessivePackageSpecificationLength + +**Since:** PMD 5.1 + +**Priority:** Medium (3) + +Excessive class file lengths are usually indications that the class may be burdened with excessive +responsibilities that could be provided by external classes or functions. In breaking these methods +apart the code becomes more managable and ripe for reuse. + +**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.plsql.rule.design.ExcessivePackageSpecificationLengthRule](https://github.com/pmd/pmd/blob/master/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/rule/design/ExcessivePackageSpecificationLengthRule.java) + +**Example(s):** + +``` sql +CREATE OR REPLACE +PACKAGE Foo AS + PROCEDURE bar1; + PROCEDURE bar2; + PROCEDURE bar3; + + ... + + PROCEDURE barN; +END; +``` + +**This rule has the following properties:** + +|Name|Default Value|Description|Multivalued| +|----|-------------|-----------|-----------| +|topscore||Top score value|no| +|minimum||Minimum reporting threshold|no| +|sigma||Sigma value|no| + +**Use this rule by referencing it:** +``` xml +<rule ref="category/plsql/design.xml/ExcessivePackageSpecificationLength" /> +``` + +## ExcessiveParameterList + +**Since:** PMD 5.1 + +**Priority:** Medium (3) + +Methods with numerous parameters are a challenge to maintain, especially if most of them share the +same datatype. These situations usually denote the need for new objects to wrap the numerous parameters. + +**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.plsql.rule.design.ExcessiveParameterListRule](https://github.com/pmd/pmd/blob/master/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/rule/design/ExcessiveParameterListRule.java) + +**Example(s):** + +``` sql +CREATE OR REPLACE +PROCEDURE addPerson( -- too many arguments liable to be mixed up + birthYear pls_integer, birthMonth pls_integer, birthDate pls_integer, height pls_integer, weight pls_integer, ssn pls_integer) { + + . . . . +END ADDPERSON; + +CREATE OR REPLACE +PROCEDURE addPerson( -- preferred approach + birthdate DATE, measurements BodyMeasurements , ssn INTEGER) BEGIN + + . . . . +END; +``` + +**This rule has the following properties:** + +|Name|Default Value|Description|Multivalued| +|----|-------------|-----------|-----------| +|topscore||Top score value|no| +|minimum||Minimum reporting threshold|no| +|sigma||Sigma value|no| + +**Use this rule by referencing it:** +``` xml +<rule ref="category/plsql/design.xml/ExcessiveParameterList" /> +``` + +## ExcessiveTypeLength + +**Since:** PMD 5.1 + +**Priority:** Medium (3) + +Excessive class file lengths are usually indications that the class may be burdened with excessive +responsibilities that could be provided by external classes or functions. In breaking these methods +apart the code becomes more managable and ripe for reuse. + +**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.plsql.rule.design.ExcessiveTypeLengthRule](https://github.com/pmd/pmd/blob/master/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/rule/design/ExcessiveTypeLengthRule.java) + +**Example(s):** + +``` sql +CREATE OR REPLACE +TYPE BODY Foo AS + MEMBER PROCEDURE bar1 IS BEGIN + -- 1000 lines of code + END bar1; + MEMBER PROCEDURE bar2 IS BEGIN + -- 1000 lines of code + END bar2; + MEMBER PROCEDURE bar3 IS BEGIN + -- 1000 lines of code + END bar3; + + + MEMBER PROCEDURE barN IS BEGIN + -- 1000 lines of code + END barn; +END; +``` + +**This rule has the following properties:** + +|Name|Default Value|Description|Multivalued| +|----|-------------|-----------|-----------| +|topscore||Top score value|no| +|minimum||Minimum reporting threshold|no| +|sigma||Sigma value|no| + +**Use this rule by referencing it:** +``` xml +<rule ref="category/plsql/design.xml/ExcessiveTypeLength" /> +``` + +## NcssMethodCount + +**Since:** PMD 5.1 + +**Priority:** Medium (3) + +This rule uses the NCSS (Non-Commenting Source Statements) algorithm to determine the number of lines +of code for a given method. NCSS ignores comments, and counts actual statements. Using this algorithm, +lines of code that are split are counted as one. + +**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.plsql.rule.design.NcssMethodCountRule](https://github.com/pmd/pmd/blob/master/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/rule/design/NcssMethodCountRule.java) + +**Example(s):** + +``` sql +CREATE OR REPLACE PACKAGE BODY AS + FUNCTION methd RETURN INTEGER IS + BEGIN + RETURN 1;; + END; +END; +``` + +**This rule has the following properties:** + +|Name|Default Value|Description|Multivalued| +|----|-------------|-----------|-----------| +|topscore||Top score value|no| +|minimum||Minimum reporting threshold|no| +|sigma||Sigma value|no| + +**Use this rule by referencing it:** +``` xml +<rule ref="category/plsql/design.xml/NcssMethodCount" /> +``` + +## NcssObjectCount + +**Since:** PMD 5.1 + +**Priority:** Medium (3) + +This rule uses the NCSS (Non-Commenting Source Statements) algorithm to determine the number of lines +of code for a given Oracle object. NCSS ignores comments, and counts actual statements. Using this algorithm, +lines of code that are split are counted as one. + +**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.plsql.rule.design.NcssObjectCountRule](https://github.com/pmd/pmd/blob/master/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/rule/design/NcssObjectCountRule.java) + +**Example(s):** + +``` sql +CREATE OR REPLACE PACKAGE pkg_ + PROCEDURE Foo IS + BEGIN + --this class only has 6 NCSS lines + super(); + super(); + END; +} +``` + +**This rule has the following properties:** + +|Name|Default Value|Description|Multivalued| +|----|-------------|-----------|-----------| +|topscore||Top score value|no| +|minimum||Minimum reporting threshold|no| +|sigma||Sigma value|no| + +**Use this rule by referencing it:** +``` xml +<rule ref="category/plsql/design.xml/NcssObjectCount" /> +``` + +## NPathComplexity + +**Since:** PMD 5.1 + +**Priority:** Medium (3) + +The NPath complexity of a method is the number of acyclic execution paths through that method. +A threshold of 200 is generally considered the point where measures should be taken to reduce +complexity and increase readability. + +**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.plsql.rule.design.NPathComplexityRule](https://github.com/pmd/pmd/blob/master/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/rule/design/NPathComplexityRule.java) + +**Example(s):** + +``` sql +CREATE OR REPLACE +PROCEDURE bar AS BEGIN -- this is something more complex than it needs to be, + if (y) THEN -- it should be broken down into smaller methods or functions + for j IN 0 .. j-1 LOOP + if (j > r) THEN + doSomething; + while (f < 5 ) LOOP + anotherThing; + f := f - 27; + END LOOP; + else + tryThis(); + END IF; + END LOOP; + END IF; + if ( r - n > 45) THEN + while (doMagic) LOOP + findRabbits; + END LOOP; + END IF; + BEGIN + doSomethingDangerous(); + EXCEPTION WHEN FooException THEN + makeAmends; + BEGIN + dontDoItAgain; + EXCEPTION + WHEN OTHERS THEN + log_problem; + END; + END; +END; +``` + +**This rule has the following properties:** + +|Name|Default Value|Description|Multivalued| +|----|-------------|-----------|-----------| +|topscore||Top score value|no| +|minimum||Minimum reporting threshold|no| +|sigma||Sigma value|no| + +**Use this rule by referencing it:** +``` xml +<rule ref="category/plsql/design.xml/NPathComplexity" /> +``` + +## TooManyFields + +**Since:** PMD 5.1 + +**Priority:** Medium (3) + +Classes that have too many fields can become unwieldy and could be redesigned to have fewer fields, +possibly through grouping related fields in new objects. For example, a class with individual +city/state/zip fields could park them within a single Address field. + +**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.plsql.rule.design.TooManyFieldsRule](https://github.com/pmd/pmd/blob/master/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/rule/design/TooManyFieldsRule.java) + +**Example(s):** + +``` sql +CREATE OR REPLACE PACKAGE pkg_too_many_fields AS + C_CHAR_A CONSTANT CHAR(1 CHAR) := 'A'; + C_CHAR_B CONSTANT CHAR(1 CHAR) := 'B'; + ... + C_CHAR_Z CONSTANT CHAR(1 CHAR) := 'Z'; +END pkg_too_many_fields; +``` + +**This rule has the following properties:** + +|Name|Default Value|Description|Multivalued| +|----|-------------|-----------|-----------| +|maxfields|15|Max allowable fields|no| + +**Use this rule by referencing it:** +``` xml +<rule ref="category/plsql/design.xml/TooManyFields" /> +``` + +## TooManyMethods + +**Since:** PMD 5.1 + +**Priority:** Medium (3) + +A package or type with too many methods is probably a good suspect for refactoring, in order to reduce its complexity and find a way to +have more fine grained objects. + +**This rule is defined by the following XPath expression:** +``` xpath +//node() + [ ( + local-name(.) = 'PackageSpecification' + or + local-name(.) = 'TypeSpecification' + ) + and + ( + count(/descendant::ProgramUnit[ + not ( + starts-with(@Image,'get') + or + starts-with(@Image,'set') + or + starts-with(@Image,'is') + ) + ] + ) + + + count(/descendant::TypeMethod[ + not ( + starts-with(@Image,'get') + or + starts-with(@Image,'set') + or + starts-with(@Image,'is') + ) + ] + ) + ) > $maxmethods + ] +``` + +**This rule has the following properties:** + +|Name|Default Value|Description|Multivalued| +|----|-------------|-----------|-----------| +|maxmethods|1|The method count reporting threshold|no| + +**Use this rule by referencing it:** +``` xml +<rule ref="category/plsql/design.xml/TooManyMethods" /> +``` + diff --git a/docs/pages/pmd/rules/plsql/errorprone.md b/docs/pages/pmd/rules/plsql/errorprone.md new file mode 100644 index 00000000000..17b048571b6 --- /dev/null +++ b/docs/pages/pmd/rules/plsql/errorprone.md @@ -0,0 +1,150 @@ +--- +title: Error Prone +summary: Rules to detect constructs that are either broken, extremely confusing or prone to runtime errors. +permalink: pmd_rules_plsql_errorprone.html +folder: pmd/rules/plsql +sidebaractiveurl: /pmd_rules_plsql.html +editmepath: ../pmd-plsql/src/main/resources/category/plsql/errorprone.xml +keywords: Error Prone, TO_DATE_TO_CHAR, TO_DATEWithoutDateFormat, TO_TIMESTAMPWithoutDateFormat +language: PLSQL +--- +## TO_DATE_TO_CHAR + +**Since:** PMD 5.1 + +**Priority:** Medium (3) + +TO_DATE(TO_CHAR(date-variable)) used to remove time component - use TRUNC(date-variable) + +**This rule is defined by the following XPath expression:** +``` xpath +//PrimaryExpression + [PrimaryPrefix/Name/@Image='TO_DATE'] + [count(PrimarySuffix/Arguments/ArgumentList/Argument) = 1] + [.//PrimaryExpression + [PrimaryPrefix/Name/@Image='TO_CHAR'] + [count(PrimarySuffix/Arguments/ArgumentList/Argument) = 1] + ] +``` + +**Example(s):** + +``` sql +CREATE OR REPLACE PACKAGE BODY date_utilities +IS + +-- Take single parameter, relying on current default NLS date format +FUNCTION strip_time (p_date IN DATE) RETURN DATE +IS +BEGIN + RETURN TO_DATE(TO_CHAR(p_date)); +END strip_time; + + +END date_utilities; +/ +``` + +**Use this rule by referencing it:** +``` xml +<rule ref="category/plsql/errorprone.xml/TO_DATE_TO_CHAR" /> +``` + +## TO_DATEWithoutDateFormat + +**Since:** PMD 5.1 + +**Priority:** Medium (3) + +TO_DATE without date format- use TO_DATE(expression, date-format) + +**This rule is defined by the following XPath expression:** +``` xpath +//PrimaryExpression[PrimaryPrefix/Name/@Image='TO_DATE' and count(PrimarySuffix/Arguments/ArgumentList/Argument) = 1 ] +``` + +**Example(s):** + +``` sql +CREATE OR REPLACE PACKAGE BODY date_utilities +IS + +-- Take single parameter, relying on current default NLS date format +FUNCTION to_date_single_parameter (p_date_string IN VARCHAR2) RETURN DATE +IS +BEGIN + RETURN TO_DATE(p_date_string); +END to_date_single_parameter ; + +-- Take 2 parameters, using an explicit date format string +FUNCTION to_date_two_parameters (p_date_string IN VARCHAR2, p_format_mask IN VARCHAR2) RETURN DATE +IS +BEGIN + TO_DATE(p_date_string, p_date_format); +END to_date_two_parameters; + +-- Take 3 parameters, using an explicit date format string and an explicit language +FUNCTION to_date_three_parameters (p_date_string IN VARCHAR2, p_format_mask IN VARCHAR2, p_nls_language VARCHAR2 ) RETURN DATE +IS +BEGIN + TO_DATE(p_date_string, p_format_mask, p_nls_language); +END to_date_three_parameters; + +END date_utilities; +/ +``` + +**Use this rule by referencing it:** +``` xml +<rule ref="category/plsql/errorprone.xml/TO_DATEWithoutDateFormat" /> +``` + +## TO_TIMESTAMPWithoutDateFormat + +**Since:** PMD 5.1 + +**Priority:** Medium (3) + +TO_TIMESTAMP without date format- use TO_TIMESTAMP(expression, date-format) + +**This rule is defined by the following XPath expression:** +``` xpath +//PrimaryExpression[PrimaryPrefix/Name/@Image='TO_TIMESTAMP' and count(PrimarySuffix/Arguments/ArgumentList/Argument) = 1 ] +``` + +**Example(s):** + +``` sql +CREATE OR REPLACE PACKAGE BODY date_utilities +IS + +-- Take single parameter, relying on current default NLS date format +FUNCTION to_timestamp_single_parameter (p_date_string IN VARCHAR2) RETURN DATE +IS +BEGIN + RETURN TO_TIMESTAMP(p_date_string); +END to_timestamp_single_parameter; + +-- Take 2 parameters, using an explicit date format string +FUNCTION to_timestamp_two_parameters (p_date_string IN VARCHAR2, p_format_mask IN VARCHAR2) RETURN DATE +IS +BEGIN + TO_TIMESTAMP(p_date_string, p_date_format); +END to_timestamp_two_parameters; + +-- Take 3 parameters, using an explicit date format string and an explicit language +FUNCTION to_timestamp_three_parameters (p_date_string IN VARCHAR2, p_format_mask IN VARCHAR2, p_nls_language VARCHAR2 ) RETURN DATE +IS +BEGIN + TO_TIMESTAMP(p_date_string, p_format_mask, p_nls_language); +END to_timestamp_three_parameters; + +END date_utilities; +/ +``` + +**Use this rule by referencing it:** +``` xml +<rule ref="category/plsql/errorprone.xml/TO_TIMESTAMPWithoutDateFormat" /> +``` + diff --git a/docs/pages/pmd/rules/plsql/strictsyntax.md b/docs/pages/pmd/rules/plsql/strictsyntax.md deleted file mode 100644 index 6e33ee8efa1..00000000000 --- a/docs/pages/pmd/rules/plsql/strictsyntax.md +++ /dev/null @@ -1,49 +0,0 @@ ---- -title: Strict Syntax -summary: The Strict Syntax ruleset contains rules that highlight invalid plsql syntax, which works, but should be avoided. -permalink: pmd_rules_plsql_strictsyntax.html -folder: pmd/rules/plsql -sidebaractiveurl: /pmd_rules_plsql.html -editmepath: ../pmd-plsql/src/main/resources/rulesets/plsql/strictsyntax.xml -keywords: Strict Syntax, MisplacedPragma ---- -## MisplacedPragma - -**Since:** PMD 5.5.2 - -**Priority:** Medium (3) - -Oracle states that the PRAQMA AUTONOMOUS_TRANSACTION must be in the declaration block, -but the code does not complain, when being compiled on the 11g DB. -https://docs.oracle.com/cd/B28359_01/appdev.111/b28370/static.htm#BABIIHBJ - -``` -//ProgramUnit/Pragma -``` - -**Example(s):** - -``` sql -create or replace package inline_pragma_error is - -end; -/ - -create or replace package body inline_pragma_error is - procedure do_transaction(p_input_token in varchar(200)) is - PRAGMA AUTONOMOUS_TRANSACTION; /* this is correct place for PRAGMA */ - begin - PRAGMA AUTONOMOUS_TRANSACTION; /* this is the wrong place for PRAGMA -> violation */ - /* do something */ - COMMIT; - end do_transaction; - -end inline_pragma_error; -/ -``` - -**Use this rule by referencing it:** -``` xml -<rule ref="rulesets/plsql/strictsyntax.xml/MisplacedPragma" /> -``` - diff --git a/docs/pages/pmd/rules/pom.md b/docs/pages/pmd/rules/pom.md index acd194c3abf..0b5e8f692fc 100644 --- a/docs/pages/pmd/rules/pom.md +++ b/docs/pages/pmd/rules/pom.md @@ -1,13 +1,26 @@ --- title: Maven POM Rules +tags: [rule_references, pom] +summary: Index of all built-in rules available for Maven POM +language_name: Maven POM permalink: pmd_rules_pom.html folder: pmd/rules --- -List of rulesets and rules contained in each ruleset. +## Error Prone -* [Basic POM](pmd_rules_pom_basic.html): The Basic POM Ruleset contains a collection of good practices regarding Maven's POM files. +{% include callout.html content="Rules to detect constructs that are either broken, extremely confusing or prone to runtime errors." %} + +* [InvalidDependencyTypes](pmd_rules_pom_errorprone.html#invaliddependencytypes): If you use an invalid dependency type in the dependency management section, Maven doesn't fail. I... +* [ProjectVersionAsDependencyVersion](pmd_rules_pom_errorprone.html#projectversionasdependencyversion): Using that expression in dependency declarations seems like a shortcut, but it can go wrong.By fa... + +## Additional rulesets + +* Basic POM (`rulesets/pom/basic.xml`): + + <span style="border-radius: 0.25em; color: #fff; padding: 0.2em 0.6em 0.3em; display: inline; background-color: #d9534f; font-size: 75%;">Deprecated</span> This ruleset is for backwards compatibility. + + It contains the following rules: + + [InvalidDependencyTypes](pmd_rules_pom_errorprone.html#invaliddependencytypes), [ProjectVersionAsDependencyVersion](pmd_rules_pom_errorprone.html#projectversionasdependencyversion) -## Basic POM -* [InvalidDependencyTypes](pmd_rules_pom_basic.html#invaliddependencytypes): While Maven will not failed if you use an invalid type for a dependency in thedependency manageme... -* [ProjectVersionAsDependencyVersion](pmd_rules_pom_basic.html#projectversionasdependencyversion): Using that expression in dependency declarations seems like a shortcut, but it can go wrong.By fa... diff --git a/docs/pages/pmd/rules/pom/basic.md b/docs/pages/pmd/rules/pom/basic.md deleted file mode 100644 index c00904d2de3..00000000000 --- a/docs/pages/pmd/rules/pom/basic.md +++ /dev/null @@ -1,76 +0,0 @@ ---- -title: Basic POM -summary: The Basic POM Ruleset contains a collection of good practices regarding Maven's POM files. -permalink: pmd_rules_pom_basic.html -folder: pmd/rules/pom -sidebaractiveurl: /pmd_rules_pom.html -editmepath: ../pmd-xml/src/main/resources/rulesets/pom/basic.xml -keywords: Basic POM, ProjectVersionAsDependencyVersion, InvalidDependencyTypes ---- -## InvalidDependencyTypes - -**Since:** PMD 5.4 - -**Priority:** Medium (3) - -While Maven will not failed if you use an invalid type for a dependency in the -dependency management section, it will not also uses the dependency. - -``` -//dependencyManagement/dependency/type/text[not(contains('pom, jar, maven-plugin, ejb, war, ear, rar, par',@Image))] -``` - -**Example(s):** - -``` xml -<project...> - ... - <dependencyManagement> - ... - <dependency> - <groupId>org.jboss.arquillian</groupId> - <artifactId>arquillian-bom</artifactId> - <version>${arquillian.version}</version> - <type>bom</type> <!-- not a valid type ! 'pom' is ! --> - <scope>import</scope> - </dependency> - ... - </dependencyManagement> -</project> -``` - -**Use this rule by referencing it:** -``` xml -<rule ref="rulesets/pom/basic.xml/InvalidDependencyTypes" /> -``` - -## ProjectVersionAsDependencyVersion - -**Since:** PMD 5.4 - -**Priority:** Medium (3) - -Using that expression in dependency declarations seems like a shortcut, but it can go wrong. -By far the most common problem is the use of 6.0.0-SNAPSHOT in a BOM or parent POM. - -``` -//dependency/version/text[contains(@Image,'{project.version}')] -``` - -**Example(s):** - -``` xml -<project...> - ... - <dependency> - ... - <version>${project.dependency}</version> - </dependency> -</project> -``` - -**Use this rule by referencing it:** -``` xml -<rule ref="rulesets/pom/basic.xml/ProjectVersionAsDependencyVersion" /> -``` - diff --git a/docs/pages/pmd/rules/pom/errorprone.md b/docs/pages/pmd/rules/pom/errorprone.md new file mode 100644 index 00000000000..7dff1d6ec5e --- /dev/null +++ b/docs/pages/pmd/rules/pom/errorprone.md @@ -0,0 +1,93 @@ +--- +title: Error Prone +summary: Rules to detect constructs that are either broken, extremely confusing or prone to runtime errors. +permalink: pmd_rules_pom_errorprone.html +folder: pmd/rules/pom +sidebaractiveurl: /pmd_rules_pom.html +editmepath: ../pmd-xml/src/main/resources/category/pom/errorprone.xml +keywords: Error Prone, InvalidDependencyTypes, ProjectVersionAsDependencyVersion +language: Maven POM +--- +## InvalidDependencyTypes + +**Since:** PMD 5.4 + +**Priority:** Medium (3) + +If you use an invalid dependency type in the dependency management section, Maven doesn't fail. Instead, +the entry is just ignored, which might have the effect, that the wrong version of the dependency is used. + +The following types are considered valid: pom, jar, maven-plugin, ejb, war, ear, rar, par. + +**This rule is defined by the following XPath expression:** +``` xpath +//dependencyManagement/dependency/type/text[not(@Image = $validTypes)] +``` + +**Example(s):** + +``` xml +<project...> + ... + <dependencyManagement> + ... + <dependency> + <groupId>org.jboss.arquillian</groupId> + <artifactId>arquillian-bom</artifactId> + <version>${arquillian.version}</version> + <type>bom</type> <!-- not a valid type ! 'pom' is ! --> + <scope>import</scope> + </dependency> + ... + </dependencyManagement> +</project> +``` + +**This rule has the following properties:** + +|Name|Default Value|Description|Multivalued| +|----|-------------|-----------|-----------| +|validTypes|pom , jar , maven-plugin , ejb , war , ear , rar , par|Set of valid types.|yes. Delimiter is ','.| + +**Use this rule by referencing it:** +``` xml +<rule ref="category/pom/errorprone.xml/InvalidDependencyTypes" /> +``` + +## ProjectVersionAsDependencyVersion + +**Since:** PMD 5.4 + +**Priority:** Medium (3) + +Using that expression in dependency declarations seems like a shortcut, but it can go wrong. +By far the most common problem is the use of ${project.version} in a BOM or parent POM. + +**This rule is defined by the following XPath expression:** +``` xpath +//dependencies/dependency + [contains(version/text/@Image,'{project.version}')] + [ + (/project/parent/groupId and groupId/text/@Image != /project/parent/groupId/text/@Image) + or + (/project/groupId and groupId/text/@Image != /project/groupId/text/@Image) + ]/version +``` + +**Example(s):** + +``` xml +<project...> + ... + <dependency> + ... + <version>${project.dependency}</version> + </dependency> +</project> +``` + +**Use this rule by referencing it:** +``` xml +<rule ref="category/pom/errorprone.xml/ProjectVersionAsDependencyVersion" /> +``` + diff --git a/docs/pages/pmd/rules/vf.md b/docs/pages/pmd/rules/vf.md index 95b6f091563..0e0ad6a889e 100644 --- a/docs/pages/pmd/rules/vf.md +++ b/docs/pages/pmd/rules/vf.md @@ -1,13 +1,26 @@ --- title: Salesforce VisualForce Rules +tags: [rule_references, vf] +summary: Index of all built-in rules available for Salesforce VisualForce +language_name: Salesforce VisualForce permalink: pmd_rules_vf.html folder: pmd/rules --- -List of rulesets and rules contained in each ruleset. +## Security -* [Basic VF](pmd_rules_vf_security.html): Rules concerning basic VF guidelines. +{% include callout.html content="Rules that flag potential security flaws." %} -## Basic VF * [VfCsrf](pmd_rules_vf_security.html#vfcsrf): Avoid calling VF action upon page load as the action becomes vulnerable to CSRF. * [VfUnescapeEl](pmd_rules_vf_security.html#vfunescapeel): Avoid unescaped user controlled content in EL as it results in XSS. +## Additional rulesets + +* Basic VF (`rulesets/vf/security.xml`): + + <span style="border-radius: 0.25em; color: #fff; padding: 0.2em 0.6em 0.3em; display: inline; background-color: #d9534f; font-size: 75%;">Deprecated</span> This ruleset is for backwards compatibility. + + It contains the following rules: + + [VfCsrf](pmd_rules_vf_security.html#vfcsrf), [VfUnescapeEl](pmd_rules_vf_security.html#vfunescapeel) + + diff --git a/docs/pages/pmd/rules/vf/security.md b/docs/pages/pmd/rules/vf/security.md index 1ed3b4febd2..28a4b5a6bfa 100644 --- a/docs/pages/pmd/rules/vf/security.md +++ b/docs/pages/pmd/rules/vf/security.md @@ -1,11 +1,12 @@ --- -title: Basic VF -summary: Rules concerning basic VF guidelines. +title: Security +summary: Rules that flag potential security flaws. permalink: pmd_rules_vf_security.html folder: pmd/rules/vf sidebaractiveurl: /pmd_rules_vf.html -editmepath: ../pmd-visualforce/src/main/resources/rulesets/vf/security.xml -keywords: Basic VF, VfUnescapeEl, VfCsrf +editmepath: ../pmd-visualforce/src/main/resources/category/vf/security.xml +keywords: Security, VfCsrf, VfUnescapeEl +language: Salesforce VisualForce --- ## VfCsrf @@ -25,7 +26,7 @@ Avoid calling VF action upon page load as the action becomes vulnerable to CSRF. **Use this rule by referencing it:** ``` xml -<rule ref="rulesets/vf/security.xml/VfCsrf" /> +<rule ref="category/vf/security.xml/VfCsrf" /> ``` ## VfUnescapeEl @@ -46,6 +47,6 @@ Avoid unescaped user controlled content in EL as it results in XSS. **Use this rule by referencing it:** ``` xml -<rule ref="rulesets/vf/security.xml/VfUnescapeEl" /> +<rule ref="category/vf/security.xml/VfUnescapeEl" /> ``` diff --git a/docs/pages/pmd/rules/vm.md b/docs/pages/pmd/rules/vm.md index 88f95f9bee7..10be2fe45be 100644 --- a/docs/pages/pmd/rules/vm.md +++ b/docs/pages/pmd/rules/vm.md @@ -1,20 +1,43 @@ --- title: VM Rules +tags: [rule_references, vm] +summary: Index of all built-in rules available for VM +language_name: VM permalink: pmd_rules_vm.html folder: pmd/rules --- -List of rulesets and rules contained in each ruleset. - -* [Basic Velocity](pmd_rules_vm_basic.html): The Basic Velocity ruleset contains basic rules for Apache Velocity pages. - -## Basic Velocity -* [AvoidDeeplyNestedIfStmts](pmd_rules_vm_basic.html#avoiddeeplynestedifstmts): Avoid creating deeply nested if-then statements since they are harder to read and error-prone to ... -* [AvoidReassigningParameters](pmd_rules_vm_basic.html#avoidreassigningparameters): Reassigning values to incoming parameters is not recommended. Use temporary local variables inst... -* [CollapsibleIfStatements](pmd_rules_vm_basic.html#collapsibleifstatements): Sometimes two consecutive 'if' statements can be consolidated by separating their conditions with... -* [EmptyForeachStmt](pmd_rules_vm_basic.html#emptyforeachstmt): Empty foreach statements should be deleted. -* [EmptyIfStmt](pmd_rules_vm_basic.html#emptyifstmt): Empty if statements should be deleted. -* [ExcessiveTemplateLength](pmd_rules_vm_basic.html#excessivetemplatelength): The template is too long. It should be broken up into smaller pieces. -* [NoInlineJavaScript](pmd_rules_vm_basic.html#noinlinejavascript): Avoid inline JavaScript. Import .js files instead. -* [NoInlineStyles](pmd_rules_vm_basic.html#noinlinestyles): Avoid inline styles. Use css classes instead. -* [UnusedMacroParameter](pmd_rules_vm_basic.html#unusedmacroparameter): Avoid unused macro parameters. They should be deleted. +## Best Practices + +{% include callout.html content="Rules which enforce generally accepted best practices." %} + +* [AvoidReassigningParameters](pmd_rules_vm_bestpractices.html#avoidreassigningparameters): Reassigning values to incoming parameters is not recommended. Use temporary local variables inst... +* [UnusedMacroParameter](pmd_rules_vm_bestpractices.html#unusedmacroparameter): Avoid unused macro parameters. They should be deleted. + +## Design + +{% include callout.html content="Rules that help you discover design issues." %} + +* [AvoidDeeplyNestedIfStmts](pmd_rules_vm_design.html#avoiddeeplynestedifstmts): Avoid creating deeply nested if-then statements since they are harder to read and error-prone to ... +* [CollapsibleIfStatements](pmd_rules_vm_design.html#collapsibleifstatements): Sometimes two consecutive 'if' statements can be consolidated by separating their conditions with... +* [ExcessiveTemplateLength](pmd_rules_vm_design.html#excessivetemplatelength): The template is too long. It should be broken up into smaller pieces. +* [NoInlineJavaScript](pmd_rules_vm_design.html#noinlinejavascript): Avoid inline JavaScript. Import .js files instead. +* [NoInlineStyles](pmd_rules_vm_design.html#noinlinestyles): Avoid inline styles. Use css classes instead. + +## Error Prone + +{% include callout.html content="Rules to detect constructs that are either broken, extremely confusing or prone to runtime errors." %} + +* [EmptyForeachStmt](pmd_rules_vm_errorprone.html#emptyforeachstmt): Empty foreach statements should be deleted. +* [EmptyIfStmt](pmd_rules_vm_errorprone.html#emptyifstmt): Empty if statements should be deleted. + +## Additional rulesets + +* Basic Velocity (`rulesets/vm/basic.xml`): + + <span style="border-radius: 0.25em; color: #fff; padding: 0.2em 0.6em 0.3em; display: inline; background-color: #d9534f; font-size: 75%;">Deprecated</span> This ruleset is for backwards compatibility. + + It contains the following rules: + + [AvoidDeeplyNestedIfStmts](pmd_rules_vm_design.html#avoiddeeplynestedifstmts), [AvoidReassigningParameters](pmd_rules_vm_bestpractices.html#avoidreassigningparameters), [CollapsibleIfStatements](pmd_rules_vm_design.html#collapsibleifstatements), [EmptyForeachStmt](pmd_rules_vm_errorprone.html#emptyforeachstmt), [EmptyIfStmt](pmd_rules_vm_errorprone.html#emptyifstmt), [ExcessiveTemplateLength](pmd_rules_vm_design.html#excessivetemplatelength), [NoInlineJavaScript](pmd_rules_vm_design.html#noinlinejavascript), [NoInlineStyles](pmd_rules_vm_design.html#noinlinestyles), [UnusedMacroParameter](pmd_rules_vm_bestpractices.html#unusedmacroparameter) + diff --git a/docs/pages/pmd/rules/vm/basic.md b/docs/pages/pmd/rules/vm/basic.md deleted file mode 100644 index 2cdf621585e..00000000000 --- a/docs/pages/pmd/rules/vm/basic.md +++ /dev/null @@ -1,160 +0,0 @@ ---- -title: Basic Velocity -summary: The Basic Velocity ruleset contains basic rules for Apache Velocity pages. -permalink: pmd_rules_vm_basic.html -folder: pmd/rules/vm -sidebaractiveurl: /pmd_rules_vm.html -editmepath: ../pmd-vm/src/main/resources/rulesets/vm/basic.xml -keywords: Basic Velocity, AvoidDeeplyNestedIfStmts, CollapsibleIfStatements, ExcessiveTemplateLength, AvoidReassigningParameters, EmptyIfStmt, EmptyForeachStmt, UnusedMacroParameter, NoInlineJavaScript, NoInlineStyles ---- -## AvoidDeeplyNestedIfStmts - -**Since:** PMD 5.1 - -**Priority:** Medium (3) - -Avoid creating deeply nested if-then statements since they are harder to read and error-prone to maintain. - -**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.vm.rule.basic.AvoidDeeplyNestedIfStmtsRule](https://github.com/pmd/pmd/blob/master/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/rule/basic/AvoidDeeplyNestedIfStmtsRule.java) - -**This rule has the following properties:** - -|Name|Default Value|Description| -|----|-------------|-----------| -|problemDepth|3|The if statement depth reporting threshold| - -**Use this rule by referencing it:** -``` xml -<rule ref="rulesets/vm/basic.xml/AvoidDeeplyNestedIfStmts" /> -``` - -## AvoidReassigningParameters - -**Since:** PMD 5.1 - -**Priority:** Medium High (2) - -Reassigning values to incoming parameters is not recommended. Use temporary local variables instead. - -**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.vm.rule.basic.AvoidReassigningParametersRule](https://github.com/pmd/pmd/blob/master/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/rule/basic/AvoidReassigningParametersRule.java) - -**Use this rule by referencing it:** -``` xml -<rule ref="rulesets/vm/basic.xml/AvoidReassigningParameters" /> -``` - -## CollapsibleIfStatements - -**Since:** PMD 5.1 - -**Priority:** Medium (3) - -Sometimes two consecutive 'if' statements can be consolidated by separating their conditions with a boolean short-circuit operator. - -**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.vm.rule.basic.CollapsibleIfStatementsRule](https://github.com/pmd/pmd/blob/master/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/rule/basic/CollapsibleIfStatementsRule.java) - -**Use this rule by referencing it:** -``` xml -<rule ref="rulesets/vm/basic.xml/CollapsibleIfStatements" /> -``` - -## EmptyForeachStmt - -**Since:** PMD 5.1 - -**Priority:** Medium High (2) - -Empty foreach statements should be deleted. - -**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.vm.rule.basic.EmptyForeachStmtRule](https://github.com/pmd/pmd/blob/master/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/rule/basic/EmptyForeachStmtRule.java) - -**Use this rule by referencing it:** -``` xml -<rule ref="rulesets/vm/basic.xml/EmptyForeachStmt" /> -``` - -## EmptyIfStmt - -**Since:** PMD 5.1 - -**Priority:** Medium High (2) - -Empty if statements should be deleted. - -**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.vm.rule.basic.EmptyIfStmtRule](https://github.com/pmd/pmd/blob/master/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/rule/basic/EmptyIfStmtRule.java) - -**Use this rule by referencing it:** -``` xml -<rule ref="rulesets/vm/basic.xml/EmptyIfStmt" /> -``` - -## ExcessiveTemplateLength - -**Since:** PMD 5.1 - -**Priority:** Medium (3) - -The template is too long. It should be broken up into smaller pieces. - -**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.vm.rule.basic.ExcessiveTemplateLengthRule](https://github.com/pmd/pmd/blob/master/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/rule/basic/ExcessiveTemplateLengthRule.java) - -**This rule has the following properties:** - -|Name|Default Value|Description| -|----|-------------|-----------| -|topscore||Top score value| -|minimum||Minimum reporting threshold| -|sigma||Sigma value| - -**Use this rule by referencing it:** -``` xml -<rule ref="rulesets/vm/basic.xml/ExcessiveTemplateLength" /> -``` - -## NoInlineJavaScript - -**Since:** PMD 5.1 - -**Priority:** Medium High (2) - -Avoid inline JavaScript. Import .js files instead. - -**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.vm.rule.basic.NoInlineJavaScriptRule](https://github.com/pmd/pmd/blob/master/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/rule/basic/NoInlineJavaScriptRule.java) - -**Use this rule by referencing it:** -``` xml -<rule ref="rulesets/vm/basic.xml/NoInlineJavaScript" /> -``` - -## NoInlineStyles - -**Since:** PMD 5.1 - -**Priority:** Medium High (2) - -Avoid inline styles. Use css classes instead. - -``` -//Text[matches(@literal, "<[^>]+\s[sS][tT][yY][lL][eE]\s*=")] -``` - -**Use this rule by referencing it:** -``` xml -<rule ref="rulesets/vm/basic.xml/NoInlineStyles" /> -``` - -## UnusedMacroParameter - -**Since:** PMD 5.1 - -**Priority:** Medium High (2) - -Avoid unused macro parameters. They should be deleted. - -**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.vm.rule.basic.UnusedMacroParameterRule](https://github.com/pmd/pmd/blob/master/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/rule/basic/UnusedMacroParameterRule.java) - -**Use this rule by referencing it:** -``` xml -<rule ref="rulesets/vm/basic.xml/UnusedMacroParameter" /> -``` - diff --git a/docs/pages/pmd/rules/vm/bestpractices.md b/docs/pages/pmd/rules/vm/bestpractices.md new file mode 100644 index 00000000000..6823e639a4e --- /dev/null +++ b/docs/pages/pmd/rules/vm/bestpractices.md @@ -0,0 +1,40 @@ +--- +title: Best Practices +summary: Rules which enforce generally accepted best practices. +permalink: pmd_rules_vm_bestpractices.html +folder: pmd/rules/vm +sidebaractiveurl: /pmd_rules_vm.html +editmepath: ../pmd-vm/src/main/resources/category/vm/bestpractices.xml +keywords: Best Practices, AvoidReassigningParameters, UnusedMacroParameter +language: VM +--- +## AvoidReassigningParameters + +**Since:** PMD 5.1 + +**Priority:** Medium High (2) + +Reassigning values to incoming parameters is not recommended. Use temporary local variables instead. + +**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.vm.rule.bestpractices.AvoidReassigningParametersRule](https://github.com/pmd/pmd/blob/master/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/rule/bestpractices/AvoidReassigningParametersRule.java) + +**Use this rule by referencing it:** +``` xml +<rule ref="category/vm/bestpractices.xml/AvoidReassigningParameters" /> +``` + +## UnusedMacroParameter + +**Since:** PMD 5.1 + +**Priority:** Medium High (2) + +Avoid unused macro parameters. They should be deleted. + +**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.vm.rule.bestpractices.UnusedMacroParameterRule](https://github.com/pmd/pmd/blob/master/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/rule/bestpractices/UnusedMacroParameterRule.java) + +**Use this rule by referencing it:** +``` xml +<rule ref="category/vm/bestpractices.xml/UnusedMacroParameter" /> +``` + diff --git a/docs/pages/pmd/rules/vm/design.md b/docs/pages/pmd/rules/vm/design.md new file mode 100644 index 00000000000..089de4f9267 --- /dev/null +++ b/docs/pages/pmd/rules/vm/design.md @@ -0,0 +1,102 @@ +--- +title: Design +summary: Rules that help you discover design issues. +permalink: pmd_rules_vm_design.html +folder: pmd/rules/vm +sidebaractiveurl: /pmd_rules_vm.html +editmepath: ../pmd-vm/src/main/resources/category/vm/design.xml +keywords: Design, AvoidDeeplyNestedIfStmts, CollapsibleIfStatements, ExcessiveTemplateLength, NoInlineJavaScript, NoInlineStyles +language: VM +--- +## AvoidDeeplyNestedIfStmts + +**Since:** PMD 5.1 + +**Priority:** Medium (3) + +Avoid creating deeply nested if-then statements since they are harder to read and error-prone to maintain. + +**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.vm.rule.design.AvoidDeeplyNestedIfStmtsRule](https://github.com/pmd/pmd/blob/master/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/rule/design/AvoidDeeplyNestedIfStmtsRule.java) + +**This rule has the following properties:** + +|Name|Default Value|Description|Multivalued| +|----|-------------|-----------|-----------| +|problemDepth|3|The if statement depth reporting threshold|no| + +**Use this rule by referencing it:** +``` xml +<rule ref="category/vm/design.xml/AvoidDeeplyNestedIfStmts" /> +``` + +## CollapsibleIfStatements + +**Since:** PMD 5.1 + +**Priority:** Medium (3) + +Sometimes two consecutive 'if' statements can be consolidated by separating their conditions with a boolean short-circuit operator. + +**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.vm.rule.design.CollapsibleIfStatementsRule](https://github.com/pmd/pmd/blob/master/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/rule/design/CollapsibleIfStatementsRule.java) + +**Use this rule by referencing it:** +``` xml +<rule ref="category/vm/design.xml/CollapsibleIfStatements" /> +``` + +## ExcessiveTemplateLength + +**Since:** PMD 5.1 + +**Priority:** Medium (3) + +The template is too long. It should be broken up into smaller pieces. + +**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.vm.rule.design.ExcessiveTemplateLengthRule](https://github.com/pmd/pmd/blob/master/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/rule/design/ExcessiveTemplateLengthRule.java) + +**This rule has the following properties:** + +|Name|Default Value|Description|Multivalued| +|----|-------------|-----------|-----------| +|topscore||Top score value|no| +|minimum||Minimum reporting threshold|no| +|sigma||Sigma value|no| + +**Use this rule by referencing it:** +``` xml +<rule ref="category/vm/design.xml/ExcessiveTemplateLength" /> +``` + +## NoInlineJavaScript + +**Since:** PMD 5.1 + +**Priority:** Medium High (2) + +Avoid inline JavaScript. Import .js files instead. + +**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.vm.rule.design.NoInlineJavaScriptRule](https://github.com/pmd/pmd/blob/master/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/rule/design/NoInlineJavaScriptRule.java) + +**Use this rule by referencing it:** +``` xml +<rule ref="category/vm/design.xml/NoInlineJavaScript" /> +``` + +## NoInlineStyles + +**Since:** PMD 5.1 + +**Priority:** Medium High (2) + +Avoid inline styles. Use css classes instead. + +**This rule is defined by the following XPath expression:** +``` xpath +//Text[matches(@literal, "<[^>]+\s[sS][tT][yY][lL][eE]\s*=")] +``` + +**Use this rule by referencing it:** +``` xml +<rule ref="category/vm/design.xml/NoInlineStyles" /> +``` + diff --git a/docs/pages/pmd/rules/vm/errorprone.md b/docs/pages/pmd/rules/vm/errorprone.md new file mode 100644 index 00000000000..8fdce71143e --- /dev/null +++ b/docs/pages/pmd/rules/vm/errorprone.md @@ -0,0 +1,40 @@ +--- +title: Error Prone +summary: Rules to detect constructs that are either broken, extremely confusing or prone to runtime errors. +permalink: pmd_rules_vm_errorprone.html +folder: pmd/rules/vm +sidebaractiveurl: /pmd_rules_vm.html +editmepath: ../pmd-vm/src/main/resources/category/vm/errorprone.xml +keywords: Error Prone, EmptyForeachStmt, EmptyIfStmt +language: VM +--- +## EmptyForeachStmt + +**Since:** PMD 5.1 + +**Priority:** Medium High (2) + +Empty foreach statements should be deleted. + +**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.vm.rule.errorprone.EmptyForeachStmtRule](https://github.com/pmd/pmd/blob/master/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/rule/errorprone/EmptyForeachStmtRule.java) + +**Use this rule by referencing it:** +``` xml +<rule ref="category/vm/errorprone.xml/EmptyForeachStmt" /> +``` + +## EmptyIfStmt + +**Since:** PMD 5.1 + +**Priority:** Medium High (2) + +Empty if statements should be deleted. + +**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.vm.rule.errorprone.EmptyIfStmtRule](https://github.com/pmd/pmd/blob/master/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/rule/errorprone/EmptyIfStmtRule.java) + +**Use this rule by referencing it:** +``` xml +<rule ref="category/vm/errorprone.xml/EmptyIfStmt" /> +``` + diff --git a/docs/pages/pmd/rules/xml.md b/docs/pages/pmd/rules/xml.md index 104b0bbf0c1..4c3ca2ec0a9 100644 --- a/docs/pages/pmd/rules/xml.md +++ b/docs/pages/pmd/rules/xml.md @@ -1,12 +1,25 @@ --- title: XML Rules +tags: [rule_references, xml] +summary: Index of all built-in rules available for XML +language_name: XML permalink: pmd_rules_xml.html folder: pmd/rules --- -List of rulesets and rules contained in each ruleset. +## Error Prone -* [Basic XML](pmd_rules_xml_basic.html): The Basic XML Ruleset contains a collection of good practices which everyone should follow. +{% include callout.html content="Rules to detect constructs that are either broken, extremely confusing or prone to runtime errors." %} + +* [MistypedCDATASection](pmd_rules_xml_errorprone.html#mistypedcdatasection): An XML CDATA section begins with a <!CDATA[ marker, which has only one [, and ends with a ]]> mar... + +## Additional rulesets + +* Basic XML (`rulesets/xml/basic.xml`): + + <span style="border-radius: 0.25em; color: #fff; padding: 0.2em 0.6em 0.3em; display: inline; background-color: #d9534f; font-size: 75%;">Deprecated</span> This ruleset is for backwards compatibility. + + It contains the following rules: + + [MistypedCDATASection](pmd_rules_xml_errorprone.html#mistypedcdatasection) -## Basic XML -* [MistypedCDATASection](pmd_rules_xml_basic.html#mistypedcdatasection): An XML CDATA section begins with a <!CDATA[ marker, which has only one [, and ends with a ]]> mar... diff --git a/docs/pages/pmd/rules/xml/basic.md b/docs/pages/pmd/rules/xml/basic.md deleted file mode 100644 index f1537675594..00000000000 --- a/docs/pages/pmd/rules/xml/basic.md +++ /dev/null @@ -1,32 +0,0 @@ ---- -title: Basic XML -summary: The Basic XML Ruleset contains a collection of good practices which everyone should follow. -permalink: pmd_rules_xml_basic.html -folder: pmd/rules/xml -sidebaractiveurl: /pmd_rules_xml.html -editmepath: ../pmd-xml/src/main/resources/rulesets/xml/basic.xml -keywords: Basic XML, MistypedCDATASection ---- -## MistypedCDATASection - -**Since:** PMD 5.0 - -**Priority:** Medium (3) - -An XML CDATA section begins with a <!CDATA[ marker, which has only one [, and ends with a ]]> marker, which has only two ]. - -``` -//cdata-section[starts-with(@Image,'[') or ends-with(@Image,']')] -``` - -**Example(s):** - -``` xml -An extra [ looks like <!CDATA[[]]>, and an extra ] looks like <!CDATA[]]]>. -``` - -**Use this rule by referencing it:** -``` xml -<rule ref="rulesets/xml/basic.xml/MistypedCDATASection" /> -``` - diff --git a/docs/pages/pmd/rules/xml/errorprone.md b/docs/pages/pmd/rules/xml/errorprone.md new file mode 100644 index 00000000000..01e61d95392 --- /dev/null +++ b/docs/pages/pmd/rules/xml/errorprone.md @@ -0,0 +1,34 @@ +--- +title: Error Prone +summary: Rules to detect constructs that are either broken, extremely confusing or prone to runtime errors. +permalink: pmd_rules_xml_errorprone.html +folder: pmd/rules/xml +sidebaractiveurl: /pmd_rules_xml.html +editmepath: ../pmd-xml/src/main/resources/category/xml/errorprone.xml +keywords: Error Prone, MistypedCDATASection +language: XML +--- +## MistypedCDATASection + +**Since:** PMD 5.0 + +**Priority:** Medium (3) + +An XML CDATA section begins with a <!CDATA[ marker, which has only one [, and ends with a ]]> marker, which has only two ]. + +**This rule is defined by the following XPath expression:** +``` xpath +//cdata-section[starts-with(@Image,'[') or ends-with(@Image,']')] +``` + +**Example(s):** + +``` xml +An extra [ looks like <!CDATA[[]]>, and an extra ] looks like <!CDATA[]]]>. +``` + +**Use this rule by referencing it:** +``` xml +<rule ref="category/xml/errorprone.xml/MistypedCDATASection" /> +``` + diff --git a/docs/pages/pmd/rules/xsl.md b/docs/pages/pmd/rules/xsl.md index ebd281d09b4..ffaf5e0b2d5 100644 --- a/docs/pages/pmd/rules/xsl.md +++ b/docs/pages/pmd/rules/xsl.md @@ -1,13 +1,31 @@ --- title: XSL Rules +tags: [rule_references, xsl] +summary: Index of all built-in rules available for XSL +language_name: XSL permalink: pmd_rules_xsl.html folder: pmd/rules --- -List of rulesets and rules contained in each ruleset. +## Code Style -* [XPath in XSL](pmd_rules_xsl_xpath.html): This ruleset regroups a collection of good practices regarding XPath querying and functions inside an XSL. +{% include callout.html content="Rules which enforce a specific coding style." %} + +* [UseConcatOnce](pmd_rules_xsl_codestyle.html#useconcatonce): The XPath concat() functions accepts as many arguments as required so you can have"concat($a,'b',... + +## Performance + +{% include callout.html content="Rules that flag suboptimal code." %} + +* [AvoidAxisNavigation](pmd_rules_xsl_performance.html#avoidaxisnavigation): Avoid using the 'following' or 'preceeding' axes whenever possible, as these can cutthrough 100% ... + +## Additional rulesets + +* XPath in XSL (`rulesets/xsl/xpath.xml`): + + <span style="border-radius: 0.25em; color: #fff; padding: 0.2em 0.6em 0.3em; display: inline; background-color: #d9534f; font-size: 75%;">Deprecated</span> This ruleset is for backwards compatibility. + + It contains the following rules: + + [AvoidAxisNavigation](pmd_rules_xsl_performance.html#avoidaxisnavigation), [UseConcatOnce](pmd_rules_xsl_codestyle.html#useconcatonce) -## XPath in XSL -* [AvoidAxisNavigation](pmd_rules_xsl_xpath.html#avoidaxisnavigation): Avoid using the 'following' or 'preceeding' axes whenever possible, as these can cutthrough 100% ... -* [UseConcatOnce](pmd_rules_xsl_xpath.html#useconcatonce): The XPath concat() functions accepts as many arguments as required so you can have"concat($a,'b',... diff --git a/docs/pages/pmd/rules/xsl/codestyle.md b/docs/pages/pmd/rules/xsl/codestyle.md new file mode 100644 index 00000000000..8e66e2fabf1 --- /dev/null +++ b/docs/pages/pmd/rules/xsl/codestyle.md @@ -0,0 +1,36 @@ +--- +title: Code Style +summary: Rules which enforce a specific coding style. +permalink: pmd_rules_xsl_codestyle.html +folder: pmd/rules/xsl +sidebaractiveurl: /pmd_rules_xsl.html +editmepath: ../pmd-xml/src/main/resources/category/xsl/codestyle.xml +keywords: Code Style, UseConcatOnce +language: XSL +--- +## UseConcatOnce + +**Since:** PMD 5.0 + +**Priority:** Medium (3) + +The XPath concat() functions accepts as many arguments as required so you can have +"concat($a,'b',$c)" rather than "concat($a,concat('b',$c)". + +**This rule is defined by the following XPath expression:** +``` xpath +//node()[contains(substring-after(@select,'concat'),'concat')] +``` + +**Example(s):** + +``` xsl +<xsl:variable name="var" select="concat("Welcome",concat("to you ",$name))"/> +<xsl:variable name="var" select="concat("Welcome","to you ",$name))"> +``` + +**Use this rule by referencing it:** +``` xml +<rule ref="category/xsl/codestyle.xml/UseConcatOnce" /> +``` + diff --git a/docs/pages/pmd/rules/xsl/performance.md b/docs/pages/pmd/rules/xsl/performance.md new file mode 100644 index 00000000000..24adc757e8c --- /dev/null +++ b/docs/pages/pmd/rules/xsl/performance.md @@ -0,0 +1,56 @@ +--- +title: Performance +summary: Rules that flag suboptimal code. +permalink: pmd_rules_xsl_performance.html +folder: pmd/rules/xsl +sidebaractiveurl: /pmd_rules_xsl.html +editmepath: ../pmd-xml/src/main/resources/category/xsl/performance.xml +keywords: Performance, AvoidAxisNavigation +language: XSL +--- +## AvoidAxisNavigation + +**Since:** PMD 5.0 + +**Priority:** Medium (3) + +Avoid using the 'following' or 'preceeding' axes whenever possible, as these can cut +through 100% of the document in the worst case. Also, try to avoid using 'descendant' +or 'descendant-self' axes, as if you're at the top of the Document, it necessarily means +cutting through 100% of the document. + +**This rule is defined by the following XPath expression:** +``` xpath +//node()[ + contains(@select,'preceeding::') + or + contains(@select,'following::') + or + contains(@select,'descendant::') + or + contains(@select,'descendant-self::') + or ( + ($checkSelfDescendantAbreviation = 'true' ) + and + contains(@select,'//') + ) +] +``` + +**Example(s):** + +``` xsl +<xsl:variable name="var" select="//item/descendant::child"/> +``` + +**This rule has the following properties:** + +|Name|Default Value|Description|Multivalued| +|----|-------------|-----------|-----------| +|checkSelfDescendantAbreviation|false|descendant::self abreviation, '//', will also trigger this rule.|no| + +**Use this rule by referencing it:** +``` xml +<rule ref="category/xsl/performance.xml/AvoidAxisNavigation" /> +``` + diff --git a/docs/pages/pmd/rules/xsl/xpath.md b/docs/pages/pmd/rules/xsl/xpath.md deleted file mode 100644 index eda6752a0ea..00000000000 --- a/docs/pages/pmd/rules/xsl/xpath.md +++ /dev/null @@ -1,79 +0,0 @@ ---- -title: XPath in XSL -summary: This ruleset regroups a collection of good practices regarding XPath querying and functions inside an XSL. -permalink: pmd_rules_xsl_xpath.html -folder: pmd/rules/xsl -sidebaractiveurl: /pmd_rules_xsl.html -editmepath: ../pmd-xml/src/main/resources/rulesets/xsl/xpath.xml -keywords: XPath in XSL, UseConcatOnce, AvoidAxisNavigation ---- -## AvoidAxisNavigation - -**Since:** PMD 5.0 - -**Priority:** Medium (3) - -Avoid using the 'following' or 'preceeding' axes whenever possible, as these can cut -through 100% of the document in the worst case. Also, try to avoid using 'descendant' -or 'descendant-self' axes, as if you're at the top of the Document, it necessarily means -cutting through 100% of the document. - -``` -//node()[ - contains(@select,'preceeding::') - or - contains(@select,'following::') - or - contains(@select,'descendant::') - or - contains(@select,'descendant-self::') - or ( - ($checkSelfDescendantAbreviation = 'true' ) - and - contains(@select,'//') - ) -] -``` - -**Example(s):** - -``` xsl -<xsl:variable name="var" select="//item/descendant::child"/> -``` - -**This rule has the following properties:** - -|Name|Default Value|Description| -|----|-------------|-----------| -|checkSelfDescendantAbreviation|false|descendant::self abreviation, '//', will also trigger this rule.| - -**Use this rule by referencing it:** -``` xml -<rule ref="rulesets/xsl/xpath.xml/AvoidAxisNavigation" /> -``` - -## UseConcatOnce - -**Since:** PMD 5.0 - -**Priority:** Medium (3) - -The XPath concat() functions accepts as many arguments as required so you can have -"concat($a,'b',$c)" rather than "concat($a,concat('b',$c)". - -``` -//node()[contains(substring-after(@select,'concat'),'concat')] -``` - -**Example(s):** - -``` xsl -<xsl:variable name="var" select="concat("Welcome",concat("to you ",$name))"/> -<xsl:variable name="var" select="concat("Welcome","to you ",$name))"> -``` - -**Use this rule by referencing it:** -``` xml -<rule ref="rulesets/xsl/xpath.xml/UseConcatOnce" /> -``` - diff --git a/docs/pages/pmd/userdocs/best_practices.md b/docs/pages/pmd/userdocs/best_practices.md index 6e59d9391ac..5c6aad01586 100644 --- a/docs/pages/pmd/userdocs/best_practices.md +++ b/docs/pages/pmd/userdocs/best_practices.md @@ -1,24 +1,32 @@ --- title: Best Practices +tags: [userdocs] permalink: pmd_userdocs_best_practices.html author: Tom Copeland <tom@infoether.com> +last_updated: November 2017 --- ## Choose the rules that are right for you -Running every ruleset will result in a huge number of rule violations, most of which will be unimportant. +Running every existing rule will result in a huge number of rule violations, most of which will be unimportant. Having to sort through a thousand line report to find the few you're really interested in takes all the fun out of things. -Instead, start with some of the obvious rulesets - just run [unusedcode](/pmd_rules_java.html#unused-code) and fix any unused locals and fields. -Then, run [empty](/pmd_rules_java.html#empty-code) and fix all the empty `if` statements and such-like. After that, take [unnecessary](/pmd_rules_java.html#unnecessary) -and fix these violations. Then, run [basic](/pmd_rules_java.html#basic) and fix the remaining violations. -Then peruse the [design](/pmd_rules_java.html#design) and [controversial](/pmd_rules_java.html#controversial) rulesets and use the ones -you like [via a custom ruleset](/pmd_userdocs_understanding_rulesets.html). +Instead, start with some selected specific rules, e.g. the rules that detect unused code from +the category [Best Practices](pmd_rules_java_bestpractices.html) and fix any unused locals and fields. + +Then, run rules, that detect empty `if` statements and such-like. You can find these rules in the category +[Error Prone](pmd_rules_java_errorprone.html). + +After that, look at all the categories and select the rules, that are useful for your project. +You can find an overview of the rules on the [Rule Index](pmd_rules_java.html). + +Use the rules you like [via a custom ruleset](pmd_userdocs_understanding_rulesets.html). ## PMD rules are not set in stone -Generally, pick the ones you like, and ignore or [suppress](/pmd_userdocs_suppressing.html) the warnings you don't like. It's just a tool. +Generally, pick the ones you like, and ignore or [suppress](pmd_userdocs_suppressing.html) +the warnings you don't like. It's just a tool. ## PMD IDE plugins are nice diff --git a/docs/pages/pmd/userdocs/cli_reference.md b/docs/pages/pmd/userdocs/cli_reference.md new file mode 100644 index 00000000000..3a6b2df8e22 --- /dev/null +++ b/docs/pages/pmd/userdocs/cli_reference.md @@ -0,0 +1,252 @@ +--- +title: PMD CLI reference +summary: "Full reference for PMD's command-line interface, including options, output formats and supported languages" +tags: [userdocs] +keywords: [command, line, options, help, formats, renderers] +permalink: pmd_userdocs_cli_reference.html +author: Tom Copeland <tom@infoether.com>, Xavier Le Vourch <xlv@users.sourceforge.net>, Juan Martín Sotuyo Dodero <juansotuyo@gmail.com> +--- + + +## Options + +The tool comes with a rather extensive help text, simply running with `-help`! + +<table> + <tr> + <th>Option</th> + <th>Description</th> + <th>Default value</th> + <th>Applies to</th> + </tr> + + {% include custom/cli_option_row.html options="-rulesets,-R" + option_arg="refs" + description="Comma-separated list of ruleset or rule references." + required="yes" + %} + {% include custom/cli_option_row.html options="-dir,-d" + option_arg="path" + description="Root directory for the analyzed sources." + required="yes" + %} + {% include custom/cli_option_row.html options="-format,-f" + option_arg="format" + description="Output format of the analysis report. The available formats + are described [here](#available-report-formats)." + default="text" + %} + <tr><td/><td/><td/><td/></tr> + {% include custom/cli_option_row.html options="-auxclasspath" + option_arg="cp" + description="Specifies the classpath for libraries used by the source code. + This is used to resolve types in source files. Alternatively, a `file://` URL + to a text file containing path elements on consecutive lines can be specified." + languages="Java" + %} + {% include custom/cli_option_row.html options="-benchmark,-b" + description="Enables benchmark mode, which outputs a benchmark report upon completion. + The report is sent to standard error." + default="false" + %} + {% include custom/cli_option_row.html options="-cache" + option_arg="filepath" + description="Specify the location of the cache file for incremental analysis. + This should be the full path to the file, including the desired file name (not just the parent directory). + If the file doesn't exist, it will be created on the first run. The file will be overwritten on each run + with the most up-to-date rule violations. + This can greatly improve analysis performance and is **highly recommended**." + %} + {% include custom/cli_option_row.html options="-debug,-verbose,-D,-V" + description="Debug mode. Prints more log output." + default="false" + %} + {% include custom/cli_option_row.html options="-encoding,-e" + option_arg="charset" + description="Specifies the character set encoding of the source code files PMD is reading. + The valid values are the standard character sets of `java.nio.charset.Charset`." + default="UTF-8" + %} + {% include custom/cli_option_row.html options="-failOnViolation,--failOnViolation" + option_arg="bool" + description="Specifies whether PMD exits with non-zero status if violations are found. + By default PMD exits with status 4 if violations are found. + Disable this feature with `-failOnViolation false` to exit with 0 instead and just output the report." + default="true" + %} + {% include custom/cli_option_row.html options="-filelist" + option_arg="filepath" + description="Path to file containing a comma delimited list of files to analyze. + If this is given, then you don't need to provide `-dir`." + %} + {% include custom/cli_option_row.html options="-ignorelist" + option_arg="filepath" + description="Path to file containing a comma delimited list of files to ignore. + This option can be combined with `-dir` and `-filelist`. + This ignore list takes precedence over any files in the filelist." + %} + {% include custom/cli_option_row.html options="-help,-h,-H" + description="Display help on usage." + default="false" + %} + {% include custom/cli_option_row.html options="-language,-l" + option_arg="lang" + description="Specify the language PMD should use." + %} + {% include custom/cli_option_row.html options="-minimumpriority,-min" + option_arg="num" + description="Rule priority threshold; rules with lower priority than configured here won't be used." + default="5" + %} + {% include custom/cli_option_row.html options="-norulesetcompatibility" + description='Disables the ruleset compatibility filter. The filter is active by default and tries to automatically "fix" old ruleset files with old rule names' + default="false" + %} + {% include custom/cli_option_row.html options="-no-cache" + description="Explicitly disables incremental analysis. This switch turns off suggestions to use Incremental Analysis, + and causes the `-cache` option to be discarded if it is provided." + default="false" + %} + {% include custom/cli_option_row.html options="-property,-P" + option_arg="name>=<value" + description="Specifies a property for the report renderer. The option can be specified several times." + default="[]" + %} + {% include custom/cli_option_row.html options="-reportfile,-r" + option_arg="path" + description="Path to a file in which the report output will be sent. By default the report is printed on standard output." + %} + {% include custom/cli_option_row.html options="-shortnames" + description="Prints shortened filenames in the report." + default="false" + %} + {% include custom/cli_option_row.html options="-showsuppressed" + description="Causes the suppressed rule violations to be added to the report." + default="false" + %} + {% include custom/cli_option_row.html options="-stress,-S" + description="Performs a stress test." + default="false" + %} + {% include custom/cli_option_row.html options="-suppressmarker" + option_arg="marker" + description="Specifies the comment token that marks lines which PMD should ignore." + default="NOPMD" + %} + {% include custom/cli_option_row.html options="-threads,-t" + option_arg="num" + description="Sets the number of threads used by PMD. + Set threads to `0` to disable multi-threading processing." + default="1" + %} + {% include custom/cli_option_row.html options="-uri,-u" + option_arg="uri" + description="Database URI for sources. If this is given, then you don't need to provide `-dir`." + languages="PLSQL" + %} + {% include custom/cli_option_row.html options="-version,-v" + option_arg="version" + description="Specify the version of a language PMD should use." + %} +</table> + +## Exit Status + +Please note that if PMD detects any violations, it will exit with status 4 (since 5.3). +This behavior has been introduced to ease PMD integration into scripts or hooks, such as SVN hooks. + +<table> +<tr><td>0</td><td>Everything is fine, no violations found</td></tr> +<tr><td>1</td><td>Couldn't understand command-line parameters or PMD exited with an exception</td></tr> +<tr><td>4</td><td>At least one violation has been detected, unless <code>-failOnViolation false</code> is set.</td></tr> +</table> + + +## Supported Languages + +* [apex](pmd_rules_apex.html) (Salesforce Apex) +* [java](pmd_rules_java.html) +* [ecmascript](pmd_rules_javascript.html) (JavaScript) +* [jsp](pmd_rules_jsp.html) +* [plsql](pmd_rules_plsql.html) +* [vf](pmd_rules_vf.html) (Salesforce VisualForce) +* [vm](pmd_rules_vm.html) (Apache Velocity) +* [xml and xsl](/pmd_rules_xml.html) + + +## Available Report Formats + +PMD comes with many different renderers. +The mnemonics in bold are used to select them on the command line, as +arguments to the `-format` option. Some formats accept *properties*, +which can be specified with the `-property` option on the command-line. + +* **codeclimate**: Renderer for Code Climate JSON format. + +* **csv**: Comma-separated values tabular format. + + Properties: + + * problem: Include problem column. Default: true. + * package: Include package column. Default: true. + * file: Include file column. Default: true. + * priority: Include priority column. Default: true. + * line: Include line column. Default: true. + * desc: Include description column. Default: true. + * ruleSet: Include Rule set column. Default: true. + * rule: Include Rule column. Default: true. + +* **emacs**: GNU Emacs integration. + +* **html**: HTML format. + + Properties: + + * linePrefix: Prefix for line number anchor in the source file. + * linkPrefix: Path to HTML source. + +* **ideaj**: IntelliJ IDEA integration. + + Properties: + + * classAndMethodName: Class and method name, pass `.method` when processing a directory. + * sourcePath: + * fileName: + +* **summaryhtml**: Summary HTML format. + + Properties: + + * linePrefix: Prefix for line number anchor in the source file. + * linkPrefix: Path to HTML source. + +* **text**: Text format. + +* **textcolor**: Text format, with color support (requires ANSI console support, e.g. xterm, rxvt, etc.). + + Properties: + + * color: Enables colors with anything other than `false` or `0`. Default: yes. + +* **textpad**: TextPad integration. + +* **vbhtml**: Vladimir Bossicard HTML format. + +* **xml**: XML format. + + Properties: + + * encoding: XML encoding format, defaults to UTF-8. + +* **xslt**: XML with a XSL transformation applied. + + Properties: + + * encoding: XML encoding format, defaults to UTF-8. + * xsltFilename: The XSLT file name. + +* **yahtml**: Yet Another HTML format. + + Properties: + + * outputDir: Output directory. diff --git a/docs/pages/pmd/userdocs/configuring_rules.md b/docs/pages/pmd/userdocs/configuring_rules.md new file mode 100644 index 00000000000..8db986cf1cf --- /dev/null +++ b/docs/pages/pmd/userdocs/configuring_rules.md @@ -0,0 +1,65 @@ +--- +title: Configuring rules +short_title: Configuring rules +keywords: [property, properties, message, priority] +tags: [userdocs, getting_started] +summary: "Learn how to configure your rules directly from the ruleset XML." +last_updated: May 2018 (6.4.0) +permalink: pmd_userdocs_configuring_rules.html +author: Hooper Bloob <hooperbloob@users.sourceforge.net>, Romain Pelisse <rpelisse@users.sourceforge.net>, Clément Fournier <clement.fournier76@gmail.com> +--- + +## Message and priority overriding + +You can change a rule's **message** by specifying a `message` +attribute on the rule element. This will override the previous +value and change the message the rule will print on the report. + +Similarly, the **priority** of a rule can be changed via a nested +element. Using priority, you can deactivate some rules based on a +minimum priority threshold (set using the `-min` CLI option). +Priority is an integer ranging from 1 to 5, with 1 being the highest +priority. + + +Putting things together, the following rule reference lowers the priority +of EmptyCatchBlock to 5, such that e.g. using the `-min 4` CLI parameters +will cause the rule to be ignored. + +```xml +<rule ref="category/java/errorprone.xml/EmptyCatchBlock" + message="Empty catch blocks should be avoided" > + <priority>5</priority> +</rule> +``` + + +## Rule properties + +Properties make it easy to customise the behaviour of a rule directly from the xml. They come in several types, which correspond to the type of their values. For example, NPathComplexity declares a property "reportLevel", with an integer value type, and which corresponds to the threshold above which a method will be reported. If you believe that its default value of 200 is too high, you could lower it to e.g. 150 in the following way: + +```xml +<rule ref="category/java/design.xml/NPathComplexity"> + <properties> + <property name="reportLevel">150</property> + </properties> +</rule> +``` + +Properties are assigned a value with a `property` element, which should mention the name of a property as an attribute. The value of the property can be specified either in the content of the element, like above, or in the `value` attribute, e.g. + +```xml +<property name="reportLevel" value="150"/> +``` + +All property assignements must be enclosed in a `properties` element, which is itself inside a `rule` element. + +{%include tip.html content="The properties of a rule are documented with the rule, e.g. [here](pmd_rules_java_design.html#npathcomplexity) for NPathComplexity. Note that **assigning a value to a property that does not exist throws an error!**" %} + +Some properties take multiple values (a list), in which case you can provide them all by delimiting them with a delimiter character. It is usually a pipe ('\|'), or a comma (',') for numeric properties, e.g. +```xml + <property name="legalCollectionTypes" + value="java.util.ArrayList|java.util.Vector|java.util.HashMap"/> +``` + +These properties are referred to as **multivalued properties** in this documentation. diff --git a/docs/pages/pmd/userdocs/cpd.md b/docs/pages/pmd/userdocs/cpd.md index e75b3e42721..41ed9e6bb8d 100644 --- a/docs/pages/pmd/userdocs/cpd.md +++ b/docs/pages/pmd/userdocs/cpd.md @@ -1,6 +1,7 @@ --- -title: Finding duplicated code -summary: Or how to find copied and pasted code +title: Finding duplicated code with CPD +tags: [cpd, userdocs] +summary: "Learn how to use CPD, the copy-paste detector shipped with PMD." permalink: pmd_userdocs_cpd.html author: Tom Copeland <tom@infoether.com> --- @@ -8,178 +9,137 @@ author: Tom Copeland <tom@infoether.com> ## Overview Duplicate code can be hard to find, especially in a large project. -But PMD's Copy/Paste Detector (CPD) can find it for you! -CPD has been through three major incarnations: +But PMD's **Copy/Paste Detector (CPD)** can find it for you! -* First we wrote it using a variant of Michael Wise's Greedy String Tiling algorithm (our variant is described - [here](http://www.onjava.com/pub/a/onjava/2003/03/12/pmd_cpd.html)). - -* Then it was completely rewritten by Brian Ewins using the - [Burrows-Wheeler transform](http://dogma.net/markn/articles/bwt/bwt.htm). - -* Finally, it was rewritten by Steve Hawkins to use the - [Karp-Rabin](http://www.nist.gov/dads/HTML/karpRabin.html) string matching algorithm. +CPD works with Java, JSP, C, C++, C#, Fortran and PHP code and [some more languages](#supported-languages). +It can be used via [command-line](#cli-usage), or via an [Ant task](#ant-task). +It can also be run with Maven by using the `cpd-check` goal on the [Maven PMD Plugin](pmd_userdocs_tools_maven.html). -Each rewrite made it much faster, and now it can process the JDK 1.4 java.* packages in about 4 seconds -(on my workstation, at least). - -Note that CPD works with Java, JSP, C, C++, C#, Fortran and PHP code and some more languages. For the -full list, see below [Supported Languages](#supported-languages). Your own language is missing? See how to add it [here](/pmd_devdocs_adding_new_cpd_language.html). -CPD is included with PMD, which you can download [here](http://sourceforge.net/projects/pmd/files/pmd/). -## Command line usage +### Why should you care about duplicates? + +It's certainly important to know where to get CPD, and how to call it, but it's worth stepping back for a moment and asking yourself why you should care about this, being the occurrence of duplicate code blocks. -### Windows +Assuming duplicated blocks of code are supposed to do the same thing, any refactoring, even simple, must be duplicated too -- which is unrewarding grunt work, and puts pressure on the developer to find every place in which to perform the refactoring. Automated tools like CPD can help with that to some extent. -CPD comes with its own starter batch file: `cpd.bat`. It's located in the `bin` subdirectory in the PMD -binary distribution zip-file. Let's assume, you are in this directory, then you can start CPD this way: +However, failure to keep the code in sync may mean automated tools will no longer recognise these blocks as duplicates. This means the task of finding duplicates to keep them in sync when doing subsequent refactorings can no longer be entrusted to an automated tool -- adding more burden on the maintainer. Segments of code initially supposed to do the same thing may grow apart undetected upon further refactoring. - cpd.bat --minimum-tokens 100 --files c:\temp\src\java +Now, if the code may never change in the future, then this is not a problem. -The options "minimum-tokens" and "files" are the two required options; there are more options, see below. +Otherwise, the most viable solution is to not duplicate. If the duplicates are already there, then they should be refactored out. We thus advise developers to use CPD to **help remove duplicates**, not to help keep duplicates in sync. +### Refactoring duplicates -### Linux +Once you have located some duplicates, several refactoring strategies may apply depending of the scope and extent of the duplication. Here's a quick summary: -For Linux, there is since PMD 5.0 a combined start script for all command line tools. This includes CPD. -The start script is called `run.sh` and is located in the `bin` subdirectory in the PMD binary distribution -zip-file. Let's assume, you are in this directory, then you can start CPD this way: +* If the duplication is local to a method or single class: + * Extract a local variable if the duplicated logic is not prohibitively long + * Extract the duplicated logic into a private method +* If the duplication occurs in siblings within a class hierarchy: + * Extract a method and pull it up in the class hierarchy, along with common fields + * Use the [Template Method](https://sourcemaking.com/design_patterns/template_method) design pattern +* If the duplication occurs consistently in unrelated hierarchies: + * Introduce a common ancestor to those class hierarchies - ./run.sh cpd --minimum-tokens 100 --files /tmp/src/java +Novice as much as advanced readers may want to [read on on Refactoring Guru](https://refactoring.guru/smells/duplicate-code) for more in-depth strategies, use cases and explanations. -The options "minimum-tokens" and "files" are the two required options; there are more options, see below. +## CLI Usage -### Options +### CLI options reference <table> <tr> <th>Option</th> <th>Description</th> - <th>Required</th> - <th>Applies for language</th> - </tr> - <tr> - <td>--minimum-tokens</td> - <td>The minimum token length which should be reported as a duplicate.</td> - <td>yes</td> - <td></td> - </tr> - <tr> - <td>--files</td> - <td>List of files and directories to process</td> - <td>yes</td> - <td></td> - </tr> - <tr> - <td>--filelist</td> - <td>Path to file containing a comma delimited list of files to analyze. If this is given, then you don't need to provide `--files`.</td> - <td>no</td> - <td></td> - </tr> - <tr> - <td>--language</td> - <td>Sources code language. Default value is `java`</td> - <td>no</td> - <td></td> - </tr> - <tr> - <td>--encoding</td> - <td>Character encoding to use when processing files</td> - <td>no</td> - <td></td> - </tr> - <tr> - <td>--skip-duplicate-files</td> - <td>Ignore multiple copies of files of the same name and length in comparison.</td> - <td>no</td> - <td></td> - </tr> - <tr> - <td>--exclude</td> - <td>Files to be excluded from CPD check</td> - <td>no</td> - <td></td> - </tr> - <tr> - <td>--non-recursive</td> - <td>Don't scan subdirectiories</td> - <td>no</td> - <td></td> - </tr> - <tr> - <td>--skip-lexical-errors</td> - <td>Skip files which can't be tokenized due to invalid characters instead of aborting CPD</td> - <td>no</td> - <td></td> - </tr> - <tr> - <td>--format</td> - <td>Report format. Default value is `text`.</td> - <td>no</td> - <td></td> - </tr> - <tr> - <td>--failOnViolation {true|false}</td> - <td>By default CPD exits with status 4 if code duplications are found. - Disable this option with '--failOnViolation false' to exit with 0 instead and just write the report.</td> - <td>no</td> - <td></td> - </tr> - <tr> - <td>--ignore-literals</td> - <td>Ignore number values and string contents when comparing text</td> - <td>no</td> - <td>java</td> - </tr> - <tr> - <td>--ignore-identifiers</td> - <td>Ignore constant and variable names when comparing text</td> - <td>no</td> - <td>java</td> - </tr> - <tr> - <td>--ignore-annotations</td> - <td>Ignore language annotations when comparing text</td> - <td>no</td> - <td>java</td> - </tr> - <tr> - <td>--ignore-usings</td> - <td>Ignore using directives in C# when comparing text</td> - <td>no</td> - <td>C#</td> - </tr> - <tr> - <td>--no-skip-blocks</td> - <td>Do not skip code blocks marked with --skip-blocks-pattern (e.g. #if 0 until #endif)</td> - <td>no</td> - <td>cpp</td> - </tr> - <tr> - <td>--skip-blocks-pattern</td> - <td> - Pattern to find the blocks to skip. Start and End pattern separated by |. - Default is `#if 0|#endif`. - </td> - <td>no</td> - <td>cpp</td> - </tr> - <tr> - <td>--uri</td> - <td>URI to process</td> - <td>no</td> - <td>plsql</td> - </tr> - <tr> - <td>--help / -h</td> - <td>Print help text</td> - <td>no</td> - <td></td> + <th>Default</th> + <th>Applies to</th> </tr> + {% include custom/cli_option_row.html options="--minimum-tokens" + description="The minimum token length which should be reported as a duplicate." + required="yes" + %} + {% include custom/cli_option_row.html options="--files" + description="List of files and directories to process" + required="yes" + %} + {% include custom/cli_option_row.html options="--filelist" + description="Path to file containing a comma delimited list of files to analyze. If this is given, then you don't need to provide `--files`." + %} + {% include custom/cli_option_row.html options="--language" + description="Sources code language." + default="java" + %} + {% include custom/cli_option_row.html options="--encoding" + description="Character encoding to use when processing files. If not specified, CPD uses the system default encoding." + %} + {% include custom/cli_option_row.html options="--skip-duplicate-files" + description="Ignore multiple copies of files of the same name and length in comparison." + default="false" + %} + {% include custom/cli_option_row.html options="--exclude" + description="Files to be excluded from CPD check" + %} + {% include custom/cli_option_row.html options="--non-recursive" + description="Don't scan subdirectories" + default="false" + %} + {% include custom/cli_option_row.html options="--skip-lexical-errors" + description="Skip files which can't be tokenized due to invalid characters instead of aborting CPD" + default="false" + %} + {% include custom/cli_option_row.html options="--format" + description="Report format." + default="text" + %} + {% include custom/cli_option_row.html options="--failOnViolation" + option_arg="bool" + description="By default CPD exits with status 4 if code duplications are found. + Disable this option with `--failOnViolation false` to exit with 0 instead and just write the report." + default="true" + %} + {% include custom/cli_option_row.html options="--ignore-literals" + description="Ignore number values and string contents when comparing text" + default="false" + languages="Java" + %} + {% include custom/cli_option_row.html options="--ignore-identifiers" + description="Ignore constant and variable names when comparing text" + default="false" + languages="Java" + %} + {% include custom/cli_option_row.html options="--ignore-annotations" + description="Ignore language annotations when comparing text" + default="false" + languages="Java" + %} + {% include custom/cli_option_row.html options="--ignore-usings" + description="Ignore `using` directives in C# when comparing text" + default="false" + languages="C#" + %} + {% include custom/cli_option_row.html options="--no-skip-blocks" + description="Do not skip code blocks matched by `--skip-blocks-pattern`" + default="false" + languages="C++" + %} + {% include custom/cli_option_row.html options="--skip-blocks-pattern" + description="Pattern to find the blocks to skip. It is a string property and contains of two parts, + separated by `|`. The first part is the start pattern, the second part is the ending pattern." + default="#if 0|#endif" + languages="C++" + %} + {% include custom/cli_option_row.html options="--uri" + description="URI to process" + languages="PLSQL" + %} + {% include custom/cli_option_row.html options="--help,-h" + default="false" + description="Print help text" + %} </table> ### Examples @@ -241,26 +201,30 @@ This behavior has been introduced to ease CPD integration into scripts or hooks, </table> -### Supported Languages +## Supported Languages -* cs -* cpp -* ecmascript (JavaScript) -* fortran -* go -* java -* jsp -* matlab -* objectivec -* php -* plsql -* python -* ruby -* scala -* swift +* Apex +* C# +* C/C++ +* EcmaScript (JavaScript) +* Fortran +* Go +* Groovy +* Java +* Jsp +* Matlab +* Objective-C +* Perl +* PHP +* PL/SQL +* Python +* Ruby +* Scala +* Swift +* Visualforce -### Available formats +## Available report formats * text : Default format * xml @@ -273,6 +237,7 @@ This behavior has been introduced to ease CPD integration into scripts or hooks, Andy Glover wrote an Ant task for CPD; here's how to use it: +```xml <target name="cpd"> <taskdef name="cpd" classname="net.sourceforge.pmd.cpd.CPDTask" /> <cpd minimumTokenCount="100" outputFile="/home/tom/cpd.txt"> @@ -281,128 +246,87 @@ Andy Glover wrote an Ant task for CPD; here's how to use it: </fileset> </cpd> </target> +``` + +<!-- TODO avoid duplicating the descriptions! --> + +### Attribute reference -<table border="1" cellpadding="2" cellspacing="0"> - <tr> - <td valign="top"><b>Attribute</b></td> - <td valign="top"><b>Description</b></td> - <td valign="top"><b>Applies for language</b></td> - <td align="center" valign="top"><b>Required</b></td> - </tr> - <tr> - <td valign="top">encoding</td> - <td valign="top"> - The character set encoding (e.g., UTF-8) to use when reading the source code files, but also when - producing the report. A piece of warning, even if you set properly the encoding value, - let's say to UTF-8, but you are running CPD encoded with CP1252, you may end up with not UTF-8 file. - Indeed, CPD copy piece of source code in its report directly, therefore, the source files - keep their encoding.<br /> - If not specified, CPD uses the system default encoding. - </td> - <td valign="top"></td> - <td valign="top" align="center">No</td> - </tr> - <tr> - <td valign="top">format</td> - <td valign="top">The format of the report (e.g. `csv`, `text`, `xml`); defaults to `text`.</td> - <td valign="top"></td> - <td valign="top" align="center">No</td> - </tr> - <tr> - <td valign="top">ignoreLiterals</td> - <td valign="top"> - if `true`, CPD ignores literal - value differences when evaluating a duplicate block. This means that `foo=42;` and `foo=43;` - will be seen as equivalent. You may want to run PMD with this option off to start with and - then switch it on to see what it turns up; defaults to `false`. - </td> - <td valign="top">java</td> - <td valign="top" align="center">No</td> - </tr> - <tr> - <td valign="top">ignoreIdentifiers</td> - <td valign="top"> - Similar to `ignoreLiterals` but for identifiers; i.e., variable names, methods names, - and so forth; defaults to `false`. - </td> - <td valign="top">java</td> - <td valign="top" align="center">No</td> - </tr> - <tr> - <td valign="top">ignoreAnnotations</td> - <td valign="top"> - Ignore annotations. More and more modern frameworks use annotations on classes and methods, - which can be very redundant and trigger CPD matches. With J2EE (CDI, Transaction Handling, etc) - and Spring (everything) annotations become very redundant. Often classes or methods have the - same 5-6 lines of annotations. This causes false positives; defaults to `false`. - </td> - <td valign="top">java</td> - <td valign="top" align="center">No</td> - </tr> - <tr> - <td valign="top">ignoreUsings</td> - <td valign="top"> - Ignore using directives in C#. - </td> - <td valign="top">C#</td> - <td valign="top" align="center">No</td> - </tr> - <tr> - <td valign="top">skipDuplicateFiles</td> - <td valign="top"> - Ignore multiple copies of files of the same name and length in comparison; defaults to `false`. - </td> - <td valign="top"></td> - <td valign="top" align="center">No</td> - </tr> - <tr> - <td valign="top">skipLexicalErrors</td> - <td valign="top"> - Skip files which can't be tokenized due to invalid characters instead of aborting CPD; defaults to `false`. - </td> - <td valign="top"></td> - <td valign="top" align="center">No</td> - </tr> - <tr> - <td valign="top">skipBlocks</td> - <td valign="top"> - Enables or disabled skipping of blocks like a pre-processor; defaults to `true`. - See also option skipBlocksPattern. - </td> - <td valign="top">cpp</td> - <td valign="top">No</td> - </tr> - <tr> - <td valign="top">skipBlocksPattern</td> - <td valign="top"> - Configures the pattern, to find the blocks to skip. It is a string property and contains of two parts, - separated by `|`. The first part is the start pattern, the second part is the ending pattern. - The default value is `#if 0|#endif`. - </td> - <td valign="top">cpp</td> - <td valign="top">no</td> - </tr> - <tr> - <td valign="top">language</td> - <td valign="top"> - Flag to select the appropriate language (e.g. `c`, `cpp`, `cs`, `java`, `jsp`, `php`, `ruby`, `fortran` - `ecmascript`, and `plsql`); defaults to `java`. - </td> - <td valign="top"></td> - <td valign="top" align="center">No</td> - </tr> - <tr> - <td valign="top">minimumtokencount</td> - <td valign="top">A positive integer indicating the minimum duplicate size.</td> - <td valign="top"></td> - <td valign="top" align="center">Yes</td> - </tr> - <tr> - <td valign="top">outputfile</td> - <td valign="top">The destination file for the report. If not specified the console will be used instead.</td> - <td valign="top"></td> - <td valign="top" align="center">No</td> - </tr> +<table> + <tr> + <th>Attribute</th> + <th>Description</th> + <th>Default</th> + <th>Applies to</th> + </tr> + {% include custom/cli_option_row.html options="minimumtokencount" + description="A positive integer indicating the minimum duplicate size." + required="yes" + %} + {% include custom/cli_option_row.html options="encoding" + description="The character set encoding (e.g., UTF-8) to use when reading the source code files, but also when + producing the report. A piece of warning, even if you set properly the encoding value, + let's say to UTF-8, but you are running CPD encoded with CP1252, you may end up with not UTF-8 file. + Indeed, CPD copy piece of source code in its report directly, therefore, the source files + keep their encoding.<br /> + If not specified, CPD uses the system default encoding." + %} + {% include custom/cli_option_row.html options="format" + description="The format of the report (e.g. `csv`, `text`, `xml`)." + default="text" + %} + {% include custom/cli_option_row.html options="ignoreLiterals" + description="if `true`, CPD ignores literal value differences when evaluating a duplicate + block. This means that `foo=42;` and `foo=43;` will be seen as equivalent. You may want + to run PMD with this option off to start with and then switch it on to see what it turns up." + default="false" + languages="Java" + %} + {% include custom/cli_option_row.html options="ignoreIdentifiers" + description="Similar to `ignoreLiterals` but for identifiers; i.e., variable names, methods names, and so forth." + default="false" + languages="Java" + %} + {% include custom/cli_option_row.html options="ignoreAnnotations" + description="Ignore annotations. More and more modern frameworks use annotations on classes and methods, + which can be very redundant and trigger CPD matches. With J2EE (CDI, Transaction Handling, etc) + and Spring (everything) annotations become very redundant. Often classes or methods have the + same 5-6 lines of annotations. This causes false positives." + default="false" + languages="Java" + %} + {% include custom/cli_option_row.html options="ignoreUsings" + description="Ignore using directives in C#." + default="false" + languages="C#" + %} + {% include custom/cli_option_row.html options="skipDuplicateFiles" + description="Ignore multiple copies of files of the same name and length in comparison." + default="false" + %} + {% include custom/cli_option_row.html options="skipLexicalErrors" + description="Skip files which can't be tokenized due to invalid characters instead of aborting CPD." + default="false" + %} + {% include custom/cli_option_row.html options="skipBlocks" + description="Enables or disabled skipping of blocks like a pre-processor. See also option skipBlocksPattern." + default="true" + languages="C++" + %} + {% include custom/cli_option_row.html options="skipBlocksPattern" + description="Configures the pattern, to find the blocks to skip. It is a string property and contains of two parts, + separated by `|`. The first part is the start pattern, the second part is the ending pattern." + default="#if 0|#endif" + languages="C++" + %} + {% include custom/cli_option_row.html options="language" + description="Flag to select the appropriate language (e.g. `c`, `cpp`, `cs`, `java`, `jsp`, `php`, `ruby`, `fortran` + `ecmascript`, and `plsql`)." + default="java" + %} + {% include custom/cli_option_row.html options="outputfile" + description="The destination file for the report. If not specified the console will be used instead." + %} </table> Also, you can get verbose output from this task by running ant with the `-v` flag; i.e.: @@ -412,7 +336,9 @@ Also, you can get verbose output from this task by running ant with the `-v` fla Also, you can get an HTML report from CPD by using the XSLT script in pmd/etc/xslt/cpdhtml.xslt. Just run the CPD task as usual and right after it invoke the Ant XSLT script like this: +```xml <xslt in="cpd.xml" style="etc/xslt/cpdhtml.xslt" out="cpd.html" /> +``` ## GUI @@ -433,8 +359,10 @@ Here's a screenshot of CPD after running on the JDK 8 java.lang package: ## Suppression -Arbitrary blocks of code can be ignored through comments on **Java** by including the keywords `CPD-OFF` and `CPD-ON`. +Arbitrary blocks of code can be ignored through comments on **Java**, **C/C++**, **Javascript**, **Matlab**, +**Objective-C**, **PL/SQL** and **Python** by including the keywords `CPD-OFF` and `CPD-ON`. +```java public Object someParameterizedFactoryMethod(int x) throws Exception { // some unignored code @@ -452,7 +380,7 @@ Arbitrary blocks of code can be ignored through comments on **Java** by includin // further code will *not* be ignored } - +``` Additionally, **Java** allows to toggle suppression by adding the annotations **`@SuppressWarnings("CPD-START")`** and **`@SuppressWarnings("CPD-END")`** @@ -461,6 +389,7 @@ all code within will be ignored by CPD. This approach however, is limited to the locations were `@SuppressWarnings` is accepted. It's legacy and the new comment's based approch should be favored. +```java //enable suppression @SuppressWarnings("CPD-START") public Object someParameterizedFactoryMethod(int x) throws Exception { @@ -470,7 +399,19 @@ It's legacy and the new comment's based approch should be favored. @SuppressWarnings("CPD-END) public void nextMethod() { } - +``` Other languages currently have no support to suppress CPD reports. In the future, the comment based approach will be extended to those of them that can support it. + +## Credits +CPD has been through three major incarnations: + +* First we wrote it using a variant of Michael Wise's Greedy String Tiling algorithm (our variant is described + [here](http://www.onjava.com/pub/a/onjava/2003/03/12/pmd_cpd.html)). + +* Then it was completely rewritten by Brian Ewins using the + [Burrows-Wheeler transform](http://dogma.net/markn/articles/bwt/bwt.htm). + +* Finally, it was rewritten by Steve Hawkins to use the + [Karp-Rabin](http://www.nist.gov/dads/HTML/karpRabin.html) string matching algorithm. diff --git a/docs/pages/pmd/userdocs/extending/defining_properties.md b/docs/pages/pmd/userdocs/extending/defining_properties.md new file mode 100644 index 00000000000..dfa69e98c04 --- /dev/null +++ b/docs/pages/pmd/userdocs/extending/defining_properties.md @@ -0,0 +1,173 @@ +--- +title: Defining rule properties +short_title: Defining rule properties +tags: [extending, userdocs] +summary: "Learn how to define your own properties both for Java and XPath rules." +last_updated: December 2017 (6.0.0) +permalink: pmd_userdocs_extending_defining_properties.html +author: Hooper Bloob <hooperbloob@users.sourceforge.net>, Romain Pelisse <rpelisse@users.sourceforge.net>, Clément Fournier <clement.fournier76@gmail.com> +--- + + +## Defining properties + +If you're a rule developer, you may want to think about what would be useful for a user of your rule to parameterise. It could be a numeric report level, a boolean flag changing the behaviour of your rule... PMD ships with many types of properties ready to use! + +### Overview of properties + +The basic thing you need to do as a developer is to define a **property descriptor** and declare that your rule uses it. A property descriptor defines a number of attributes for your property: +* Its *name*, with which the user will refer to your property; +* Its *description*, for documentation purposes; +* Its *default value* + +Don't worry, all of these attributes can be specified in a single Java statement (or xml element for XPath rules). + +Without further ado, here is the list of available (single-value) properties: + +|Class name|Value type| +|----------|----------| +|IntegerProperty | int +|DoubleProperty | double +|FloatProperty | float +|LongProperty | long +|EnumeratedProperty\<*E*\>| *E* +|StringProperty|String +|BooleanProperty|boolean +|CharacterProperty|char +|FileProperty|java.io.File +|MethodProperty|java.lang.reflect.Method +|TypeProperty|java.lang.Class\<?\> +|RegexProperty|java.util.regex.Pattern + +Each of these is complemented by a multivalued variant, whose name ends with "MultiProperty", and which returns a list of values, e.g. + +|Class name|Value type| +|----------|----------| +|LongMultiProperty | List\<Long\> +|EnumeratedMultiProperty\<*E*\>| List\<*E*\> + +Note that RegexProperty doesn't have a multivalued variant, since the delimiters could be part of a specific value. + +### For Java rules + +The procedure to define a property is quite straightforward: +* Create a property descriptor of the type you want, using its builder; +* Call `definePropertyDescriptor(<your descriptor>)` in the rule's noarg constructor. + +You can then retrieve the value of the property at any time using `getProperty(<your descriptor>)`. + +#### Creating a descriptor + +From version 6.0.0 on, properties can be built using specific **builders**. For example, to build a string property, you'd call +```java +StringProperty.named("myProperty") + .desc("This is my property") + .defaultValue("foo") + .build(); +``` + +This is fairly more readable than a constructor call, but keep in mind the description and the default value are not optional. + +{%include note.html content="The constructors may be deprecated in a future release, so please use the builders instead." %} + +For **numeric properties**, you'd add a call to `range` to define the range of acceptable values, e.g. +```java +IntegerProperty.named("myIntProperty") + .desc("This is my property") + .defaultValue(3) + .range(0, 100) + .build(); +``` + +**Enumerated properties** are a bit less straightforward to define, though they are arguably more powerful. These properties don't have a specific value type, instead, you can choose any type of value, provided the values are from a closed set. To make that actionable, you give string labels to each of the acceptable values, and the user will provide one of those labels as a value in the XML. The property will give you back the associated value, not the label. Here's an example: +```java +static Map<String, ModeStrategy> map = new HashMap<>(); + +static { + map.put("easyMode", new EasyStrategy()); + map.put("hardMode", new HardStrategy()); +} + +static EnumeratedProperty<ModeStrategy> modeProperty + = EnumeratedProperty.<ModeStrategy>named("modeProperty") + .desc("This is my property") + .defaultValue(new EasyStrategy()) + .mappings(map) + .type(ModeStrategy.class) + .build(); +``` + +Note that you're required to fill in the type of the values too, using `type()`. + +#### Example + +You can see an example of properties used in a PMD rule [here](https://github.com/pmd/pmd/blob/ac2ff0f6af8d16f739584ba8d00b7ea1a6311ccc/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/complexity/AvoidDeeplyNestedIfStmtsRule.java#L17). +There are several things to notice here: +* The property descriptor is declared `static final`, which should generally be the case, as descriptors are immutable and can be shared between instances of the same rule; +* The property is declared using `definePropertyDescriptor` *in the constructor*, which ensures the property gets recognised by PMD; +* The value of the property is *not retrieved in the constructor*, but in one of the `visit` methods (typically on the highest node in the tree, since the property doesn't change). + + + +### For XPath rules + +XPath rules can also define their own properties. To do so, you must add a `property` element in the `properties` element of your rule, which **declares the `type` attribute**. This attribute conditions what type the underlying property has, and can have the following values: + +| `type` attribute | Property type| +|----------|----------| +|Integer|IntegerProperty +|Double | DoubleProperty +|Float|FloatProperty +|Long| LongProperty +|String|StringProperty +|Character|CharacterProperty +|Boolean|BooleanProperty +|Class|TypeProperty +|Regex|RegexProperty + +Note that enumerated properties are not available in XPath rules (yet?). + +Properties defined in XPath also *must* declare the `description` attribute. Numeric properties also expect the `min` and `max` attributes. Here are a few examples to sum it up: + +```xml +<property name="stringProp" type="Boolean" value="true" description="A BooleanProperty."/> +<property name="intProp" type="Integer" value="3" min="1" max="20" description="An IntegerProperty."/> +``` + +You can then use the property in XPath with the syntax `$propertyName`, for example: + +```xml +<rule name="MyXpathRule" ...> + <properties> + <property name="maxStatements" type="Integer" value="10" min="1" max="40" + description="Max number of statements per method"/> + <property name="xpath"> + <![CDATA[ + //MethodDeclaration/Block[count(//BlockStatement) > $maxStatements] + ]]></property> + </properties> +</rule> +``` + +#### Multivalued properties + +Multivalued properties are also allowed and their `type` attribute has the form `List[Boolean]` or `List[Character]`, with every above type allowed. These properties **require XPath 2.0** to work properly, and make use of the **sequence datatype** provided by that language. You thus need to set the `version` property to `2.0` to use them. Properties can also declare the `delimiter` attribute. + + + +```xml +<rule name="MyXpathRule" ...> + <properties> + <property name="version" value="2.0" /> + <property name="intProp" type="List[Integer]" value="1,2,5" description="An IntegerMultiProperty." /> + <property name="reportedIdentifiers" type="List[String]" value="foo$bar" delimiter="$" + description="A StringMultiProperty." /> + <property name="xpath"> + <![CDATA[ + //VariableDeclaratorId[@Image = $reportedIdentifiers] + ]]></property> + </properties> +</rule> +``` + +Notice that in the example above, `@Image = $reportedIdentifiers` doesn't test `@Image` for equality with the whole sequence `('foo', 'bar')`, it tests whether the sequence *contains* `@Image`. That is, the above rule will report all variables named `foo` or `bar`. All other XPath 2.0 [functions operating on sequences](https://www.w3.org/TR/xpath-functions/#sequence-functions) are supported. diff --git a/docs/pages/pmd/devdocs/metrics_howto.md b/docs/pages/pmd/userdocs/extending/metrics_howto.md similarity index 90% rename from docs/pages/pmd/devdocs/metrics_howto.md rename to docs/pages/pmd/userdocs/extending/metrics_howto.md index 8c7045e062a..5ccabe03cdb 100644 --- a/docs/pages/pmd/devdocs/metrics_howto.md +++ b/docs/pages/pmd/userdocs/extending/metrics_howto.md @@ -1,24 +1,17 @@ --- -title: Using code metrics in custom rules -tags: [customizing] -summary: "PMD was recently enhanced with the ability to compute code metrics on Java and Apex source (the so-called +title: Using and defining code metrics for custom rules +tags: [extending, userdocs, metrics] +summary: "Since version 6.0.0, PMD is enhanced with the ability to compute code metrics on Java and Apex source (the so-called Metrics Framework). This framework provides developers with a straightforward interface to use code metrics in their rules, and to extend the framework with their own custom metrics." -last_updated: July 20, 2017 -permalink: pmd_devdocs_metrics_howto.html +last_updated: December 18, 2017 +permalink: pmd_userdocs_extending_metrics_howto.html author: Clément Fournier <clement.fournier76@gmail.com> --- -# Using code metrics in custom rules ## Using the metrics framework -{%include note.html content="Using the metrics framework is for now restricted to Java rules (with plans to support -XPath rules later)." %} - -To use the metrics framework in a custom rule, the first thing to do would be to **enable metrics by adding the -`metrics="true"` attribute** to your rule's XML element. - -{%include note.html content="The following explains how to use the Java metrics framework. The Apex framework +{%include note.html content="The following explains how to use the Java metrics framework. The Apex framework differs only by the name of its classes." %} In PMD's Metrics framework, a metric is an operation that can be carried out on nodes of a certain type and produces @@ -57,8 +50,6 @@ or `ConstructorDeclaration`. ## For Java Rules -First, similarly to XPath rules, you should add the `metrics="true"` attribute to your rule's XML element. - The static façade class `JavaMetrics` is the single entry point to compute metrics in the Java framework. This class provides the method `get` and its overloads. The following sections describes the interface of this class. @@ -107,7 +98,7 @@ public Object visit(ASTMethodDeclaration method, Object data) { ### Metric options Some metrics define options that can be used to slightly modify the computation. You'll typically see these options -gathered inside an enum in the implementation class of the metric, for example `CycloMetric.CycloOptions`. They're +gathered inside an enum in the implementation class of the metric, for example `CycloMetric.CycloOption`. They're also documented on the [index of metrics](pmd_java_metrics_index.html). To use options with a metric, you must first bundle them into a `MetricOptions` object. `MetricOptions` provides the @@ -126,7 +117,7 @@ public Object visit(ASTMethodDeclaration method, Object data) { The version of `MetricOptions.ofOptions` using a collection is useful when you're building a `MetricOptions` from eg the value of an `EnumeratedMultiProperty`, which gives users control of the options they use. See -[CyclomaticComplexityRule]( https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/rule/CyclomaticComplexityRule.java) +[CyclomaticComplexityRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/CyclomaticComplexityRule.java#L35) for an example usage. ### Result options @@ -153,17 +144,18 @@ option too. The following is a sample code for a rule reporting methods with a cyclomatic complexity over 10 and classes with a total cyclo over 50. A metric option can be user-configured with a rule property. More complete examples can be found in -[CyclomaticComplexityRule]( https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/rule/CyclomaticComplexityRule.java), -[NcssCountRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/rule/NcssCountRule.java), -or [GodClassRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/GodClassRule.java). +[CyclomaticComplexityRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/CyclomaticComplexityRule.java#L35), +[NcssCountRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/NcssCountRule.java#L30), +or [GodClassRule](https://github.com/pmd/pmd/blob/master/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/GodClassRule.java#L24). ```java public class CycloRule extends AbstractJavaMetricsRule { public static final BooleanProperty COUNT_BOOLEAN_PATHS - = new BooleanProperty("countBooleanPaths", "Count boolean paths", - true, 0f); + = BooleanProperty.named("countBooleanPaths") + .desc("Count boolean paths") + .defaultValue(true).build(); private static final MetricOptions options; @@ -275,9 +267,10 @@ concrete node types you can target with class and operation metrics, by language Language | Java | Apex | -----------|------|------| -Operation declaration|`ASTMethodOrConstructorDeclaration`<br/>>: `ASTMethodDeclaration`, `ASTConstructorDeclaration`| `ASTMethod` +Operation declaration|`ASTMethodOrConstructorDeclaration`<br/>>: `ASTMethodDeclaration`, `ASTConstructorDeclaration`| `ASTMethod`* Type declaration|`ASTAnyTypeDeclaration` >: `ASTEnumDeclaration`, <br> `ASTAnnotationDeclaration`, `ASTClassOrInterfaceDeclaration`| `ASTUserClassOrInterface` >: `ASTUserClass`, `ASTUserInterface` +*Apex method metrics are also applied to triggers by default (see [#771](https://github.com/pmd/pmd/pull/771)). Finer capability checking is not available out of the box for now. What if you don't want such a generalisation? The `supports` method lets you define a predicate to check that the node is supported by your metric. For example, @@ -300,5 +293,5 @@ classes. Here's the default behaviour by language and type of metric: Language | Java | Apex | -----------|------|------| -Operation metrics| supports constructors and non abstract methods| supports any non abstract method except `<init>`, `<clinit>`, and `clone` +Operation metrics| supports constructors and non abstract methods| supports any non abstract method (including triggers), except `<init>`, `<clinit>`, and `clone` Type declaration|supports classes and enums|supports classes diff --git a/docs/pages/pmd/devdocs/rule_guidelines.md b/docs/pages/pmd/userdocs/extending/rule_guidelines.md similarity index 93% rename from docs/pages/pmd/devdocs/rule_guidelines.md rename to docs/pages/pmd/userdocs/extending/rule_guidelines.md index 791830bcc37..91f03f6dba9 100644 --- a/docs/pages/pmd/devdocs/rule_guidelines.md +++ b/docs/pages/pmd/userdocs/extending/rule_guidelines.md @@ -1,9 +1,9 @@ --- -title: PMD Rule Guidelines -tags: [customizing] +title: Rule guidelines +tags: [extending, userdocs] summary: Rule Guidelines last_updated: July 3, 2016 -permalink: pmd_devdocs_rule_guidelines.html +permalink: pmd_userdocs_extending_rule_guidelines.html author: Xavier Le Vourch, Ryan Gustafson, Romain Pelisse --- @@ -51,8 +51,8 @@ See [Test Framework](pmd_devdocs_testing.html) for the general documentation ### … for a rule I want to submit (in a patch) -Figure out the ruleset to which you want to the rule. Then add your rule to the appropriate test class for -the ruleset and add the XML test data in the correct xml subpackage. +Figure out the category to which you want to the rule. Then add your rule to the appropriate test class for +the category and add the XML test data in the correct xml subpackage. ### … for something too specific, that I won’t be able to submit diff --git a/docs/pages/pmd/userdocs/extending/testing.md b/docs/pages/pmd/userdocs/extending/testing.md new file mode 100644 index 00000000000..d7ac79032c8 --- /dev/null +++ b/docs/pages/pmd/userdocs/extending/testing.md @@ -0,0 +1,280 @@ +--- +title: Testing your rules +tags: [extending, userdocs] +summary: "Learn how to use PMD's simple test framework for unit testing rules." +permalink: pmd_userdocs_extending_testing.html +last_updated: November 2018 +author: Andreas Dangel <andreas.dangel@adangel.org> +--- + +## Introduction + +Good rules have tests. At least a positive test case - a code example, that triggers the rule and reports +a violation - and a negative test case - a code example, that doesn't trigger the rule - should be created. +Of course, the more tests, the better the rule is verified. If the rule is more complex or defines properties, +with which the behavior can be modified, then these different cases can also be tested. + +And if there is a bug fix for a rule, be it a false positive or a false negative case, it should be accompanied +with an additional test case, so that the bug is not accidentally reintroduced later on. + +## How it works + +PMD's built-in rules are organized in rulesets, where all rules belonging to the same category are placed +in a single ruleset, such as "category/java/bestpractices.xml". +Each category-ruleset has a single abstract base test class, from which the individual test classes inherit. +We have one test class per rule, which executes all test cases for a single rule. The actual test cases are +stored in separate XML files, for each rule a separate file is used. + +All the test classes inherit from `net.sourceforge.pmd.testframework.PmdRuleTst`, +which provides the seamless integration with JUnit. This base class determines the language, the category name +and the rule name from the concrete test class. It then searches the test code on its own. +E.g. the individual rule test class +`net.sourceforge.pmd.lang.java.rule.bestpractices.AbstractClassWithoutAbstractMethodTest` tests the +rule with the name "AbstractClassWithoutAbstractMethod", which is in the category "bestpractices" for the +language "java". + +The test code (see below [Test XML Reference](#test-xml-reference)) describes the test case completely with +the expected behavior like number of expected rule violations, where the violations are expected, and so on. + +When you are running the test class in your IDE (e.g. Eclipse or IntelliJ IDEA) you can also select a single +test case and just execute this one. + +## Where to place the test code + +The `PmdRuleTst` class searches the XML file, that describes the test cases for a certain rule +using the following convention: +The XML file is a test resource, so it is searched in the tree under `src/test/resources`. + +The sub package `xml` of the test class's package should contain a file with the same name as the rule's name +which is under test. + +For example, to test the rule "AbstractClassWithoutAbstractMethod", the fully qualified test class is: + + net.sourceforge.pmd.lang.java.rule.bestpractices.AbstractClassWithoutAbstractMethodTest + +The test code for the rule can be found in the file: + + src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/AbstractClassWithoutAbstractMethod.xml + +In general, the class name and file name pattern for the test class and data is this: + + net.sourceforge.pmd.lang.<Language Terse Name>.rule.<Category Name>.<Rule Name>Test + src/test/resources/net/sourceforge/pmd/lang/<Language Terse Name>/rule/<Category Name>/xml/<Rule Name>.xml + +{%include tip.html content="This convention allows you to quickly find the test cases for a given rule: +Just search in the project for a file `<RuleName>.xml`. Search for a class `<Rule Name>Test` to find the +unit test class for the given rule." %} + +{%include note.html content="If you want to use the test framework with a different package structure, +see [Using the test framework externally](#using-the-test-framework-externally)." %} + +## Simple example + +### Test Class: AbstractClassWithoutAbstractMethodTest + +This class inherits from `PmdRuleTst` and is located in the package "bestpractices", since the rule +belongs to the category "Best Practices": + +``` java +package net.sourceforge.pmd.lang.java.rule.bestpractices; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class AbstractClassWithoutAbstractMethodTest extends PmdRuleTst { + // no additional unit tests +} +``` + +{%include note.html content="You can also add additionally standard JUnit test methods annotated with `@Test` to +this test class." %} + +### Test Data: AvoidBranchingStatementAsLastInLoop.xml + +This is a stripped down example which just contains two test cases. + +``` xml +<?xml version="1.0" encoding="UTF-8"?> +<test-data + xmlns="http://pmd.sourceforge.net/rule-tests" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://pmd.sourceforge.net/rule-tests https://pmd.sourceforge.io/rule-tests_1_0_0.xsd"> + <test-code> + <description>concrete class</description> + <expected-problems>0</expected-problems> + <code><![CDATA[ +public class Foo {} + ]]></code> + </test-code> + <test-code> + <description>failure case</description> + <expected-problems>1</expected-problems> + <expected-linenumbers>1</expected-linenumbers> + <code><![CDATA[ +public abstract class Foo {} + ]]></code> + </test-code> +</test-data> +``` + +Each test case is in an own `<test-code>` element. The first defines 0 expected problems, means this code doesn't +trigger the rule. The second test case expects 1 problem. Since the rule violations also report the exact AST node, +you can verify the line number, too. + +## Test XML Reference + +The root element is `<test-data>`. It can contain one or more `<test-code>` and `<code-fragment>` elements. +Each `<test-code>` element defines a single test case. `<code-fragment>` elements are used to share code snippets +between different test cases. + +{%include note.html content="The XML schema is available at [rule-tests.xsd](https://github.com/pmd/pmd/blob/master/pmd-test/src/main/resources/rule-tests_1_0_0.xsd)." %} + +### `<test-code>` attributes + +The `<test-code>` elements understands three optional attributes: + +* **reinitializeRule**: By default, it's `true`, so each test case starts with a fresh instantiated rule. Set it + to `false` to reproduce cases, where the previous run has influences. + +* **regressionTest**: By default, it's `true`. Set it to `false`, to ignore and skip a test case. + +* **useAuxClasspath**: By default, it's `true`. Set it to `false` to reproduce issues which only + appear without type resolution. + +### `<test-code>` children + +* **`<description>`**: Short description of the test case. This will be the JUnit test name in the report. + If applicable, this description should contain a reference to the bug number, this test case reproduces. + +* **`<rule-property>`**: Optional rule properties, if the rule is configurable. Just add multiple elements, to + set multiple properties for one test case. For an example, see below. + +* **`<expected-problems>`**: The the raw number of expected rule violations, that this rule is expected to report. + For false-positive test cases, this is always "0". For false-negative test cases, it can be any positive number. + +* **`<expected-linenumbers>`**: Optional element. It's a comma separated list of line numbers. + If there are rule violations reported, then this allows you to + assert the line numbers. Useful if multiple violations should be detected and to be sure that + false positives and negatives don't erase each other. + +* **`<expected-messages>`**: Optional element, with `<message>` elements as children. + Can be used to validate the correct error message, e.g. if the error message references a variable name. + +* **`<code>`**: Either the `<code>` element or the `<code-ref>` element is required. It provides the actual code + snippet on which the rule is executed. The code itself is usually wrapped in a "CDATA" section, so that no + further XML escapes (entity references such as &lt;) are necessary. + +* **`<code-ref id=...>`**: Alternative to `<code>`. References a `<code-fragment>` defined earlier in the file. + This allows you to share the same code snippet with several test cases. The attribute `id` must match the + id of the references code fragment. + +* **`<source-type>`**: Optional element that specifies the source code language. This defines the parser that + is used for parsing the code snippet. If not given, **java** is used as default. + +### `<code-fragment>` + +The code fragment has just one required attribute: **id**. This is used to reference it via a `<code-ref>` element +inside a `<test-code>`. Similar like the `<code>` element, the content of `<code-fragment>` is usually wrapped +in a "CDATA" section, so that no further XML escapes (entity references such as &lt;) are necessary. + +### Complete XML example + +``` xml +<?xml version="1.0" encoding="UTF-8"?> +<test-data + xmlns="http://pmd.sourceforge.net/rule-tests" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://pmd.sourceforge.net/rule-tests https://pmd.sourceforge.io/rule-tests_1_0_0.xsd"> + + <test-code reinitializeRule="true" regressionTest="true" useAuxClasspath="true"> + <description>Just a description, will be used as the test name for JUnit in the reports</description> + <rule-property name="somePropName">propValue</rule-property> <!-- optional --> + <expected-problems>2</expected-problems> + <expected-linenumbers>5,14</expected-linenumbers> <!-- optional --> + <expected-messages> <!-- optional --> + <message>Violation message 1</message> + <message>Violation message 2</message> + </expected-messages> + <code><![CDATA[ + public class ConsistentReturn { + public Boolean foo() { + } + } + ]]></code> + <source-type>apex</source-type> <!-- optional --> + </test-code> + + <code-fragment id="codeSnippet1"><![CDATA[ + public class ConsistentReturn { + public Boolean foo() { + } + } + ]]></code-fragment> + <test-code> + <description>test case using a code fragment</description> + <expected-problems>0</expected-problems> + <code-ref id="codeSnippet1"/> + </test-code> + </test-data> +``` + +## Using the test framework externally + +It is also possible to use the test framework for custom rules developed outside the PMD source base. +Therefore you just need to reference the dependency `net.sourceforge.pmd:pmd-test`. + +For maven, you can use this snippet: + + <dependency> + <groupId>net.sourceforge.pmd</groupId> + <artifactId>pmd-test</artifactId> + <version>{{site.pmd.version}}</version> + <scope>test</scope> + </dependency> + +Then proceed as described earlier: create your test class, create your test cases and run the unit test. + +There is one difference however: Since your package structure is probably different, you'll need to register +the rule test manually, as SimpleAggregatorTst will fail to determine it correctly from the package and class names: + +``` java +package com.example.pmd.rules; + +import net.sourceforge.pmd.testframework.SimpleAggregatorTst; + +public class CustomRuleTest extends SimpleAggregatorTst { + @Override + public void setUp() { + addRule("com/example/pmd/ruleset.xml", "CustomRule"); + } +} +``` + +This will then search for a rule named "CustomRule" in the ruleset, that is located in "src/main/resources" under +the path "com/example/pmd/ruleset.xml". + +The test data should be placed in an xml file located in "src/test/resources" under the path +"com/example/pmd/rules/xml/CustomRule.xml". + +## How the test framework is implemented + +The framework uses a custom JUnit test runner under the hood, among a couple of utility classes: + +* `PmdRuleTst`: This is the base class for tests in PMD's code base. It is a subclass of `RuleTst` and just + contains the logic to determine the test resources based on the test class name. + +* `SimpleAggregatorTst`: This is a more generic base class for the test classes and defines + the custom JUnit test runner. It doesn't register any test cases on its own. + It itself is a subclass of `RuleTst`. + +* `RuleTst`: contains the logic to parse the XML files and provide a list of `TestDescriptor`s. Each test descriptor + describes a single test case. It also contains the logic to execute such a test descriptor and assert the results. + +* `PMDTestRunner`: A custom JUnit test runner, that combines two separate test runners: The custom `RuleTestRunner` + and the standard `JUnit4` test runner. This combination allows you to add additional standard unit test methods + annotated with `@Test` to your test class. + + *Note:* Since the test class is executed through two test runners, it is actually instantiated twice. Be aware + of this, if you do any initialization in the constructor. Also, the static hooks `@BeforeClass` and `@AfterClass` + will be executed twice. + +* `RuleTestRunner`: This test runner executes the test descriptors with the help of `RuleTst`. diff --git a/docs/pages/pmd/devdocs/writing_pmd_rules.md b/docs/pages/pmd/userdocs/extending/writing_pmd_rules.md similarity index 76% rename from docs/pages/pmd/devdocs/writing_pmd_rules.md rename to docs/pages/pmd/userdocs/extending/writing_pmd_rules.md index a6ec0ea8937..f19dec13bd3 100644 --- a/docs/pages/pmd/devdocs/writing_pmd_rules.md +++ b/docs/pages/pmd/userdocs/extending/writing_pmd_rules.md @@ -1,9 +1,9 @@ --- -title: PMD Writing a Custom Rule -tags: [customizing] -summary: Writing a Custom Rule for PMD +title: Writing a custom rule +tags: [extending, userdocs] +summary: "Learn how to write a custom rule for PMD" last_updated: July 3, 2016 -permalink: pmd_devdocs_writing_pmd_rules.html +permalink: pmd_userdocs_extending_writing_pmd_rules.html author: Tom Copeland <tomcopeland@users.sourceforge.net> --- @@ -72,7 +72,7 @@ CompilationUnit You can generate this yourself by: -* Run the batch file bin/designer.bat +* Run the batch file `bin/designer.bat` * Paste the code into the left text area and click the “Go†button * Note that there’s another panel and a textfield to test out XPath expressions; more on that later. * Here’s a screenshot: {% include image.html file="devdocs/designer_screenshot.png" alt="Designer Screenshot" %} @@ -154,7 +154,7 @@ We stuck a `println()` in there for now so we can see when our rule gets hit. ## Put the WhileLoopsMustUseBracesRule rule in a ruleset file -Now our rule is written - at least, the shell of it is - and now we need to tell PMD about it. We need to add it to a ruleset XML file. Look at `pmd-java/src/main/resources/rulesets/java/basic.xml`; it’s got lots of rule definitions in it. Copy and paste one of these rules into a new ruleset - call it `mycustomrules.xml` or something. Then fill in the elements and attributes: +Now our rule is written - at least, the shell of it is - and now we need to tell PMD about it. We need to add it to a ruleset XML file. Look at `pmd-java/src/main/resources/category/java/bestpractices.xml`; it’s got lots of rule definitions in it. Copy and paste one of these rules into a new ruleset - call it `mycustomrules.xml` or something. Then fill in the elements and attributes: * name - WhileLoopsMustUseBracesRule * message - Use braces for while loops @@ -169,7 +169,7 @@ The whole ruleset file should look something like this: <ruleset name="My custom rules" xmlns="http://pmd.sourceforge.net/ruleset/2.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 http://pmd.sourceforge.net/ruleset_2_0_0.xsd"> + xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd"> <rule name="WhileLoopsMustUseBracesRule" message="Avoid using 'while' statements without curly braces" class="WhileLoopsMustUseBracesRule"> @@ -250,71 +250,23 @@ Note that access modifiers are held as attributes, so, for example, finds all private fields. You can see the code that determines all the attributes [here](pmd-core/xref/net/sourceforge/pmd/lang/ast/xpath/AttributeAxisIterator.html) -Thanks to Miguel Griffa for writing a longer [XPath tutorial](xpathruletutorial.html). - -## I want to implement a rule that analyze more than the class! - -An obvious limitation of the previous mechanism is the “class-centric†focus of the rule. How can you implement a rule that checks stuff across the all source code? Let’s take a dummy example. Let’s say you want to implement a rule that count how many Expression Node you have in your source code (told you, it was a dummy example :) ). - -You realize quite simply. You just have to add static field to the RulesContext, as an attribute, and uses Rule.start() and Rule.end() hook to initialized and finalize your rule’s implementation: - -```java -package net.sourceforge.pmd.rules; - -import java.util.concurrent.atomic.AtomicLong; - -import net.sourceforge.pmd.RuleContext; -import net.sourceforge.pmd.lang.java.rule.AbstractJavaRule; -import net.sourceforge.pmd.lang.java.ast.ASTExpression; - -public class CountRule extends AbstractJavaRule { - - private static final String COUNT = "count"; - - @Override - public void start(RuleContext ctx) { - ctx.setAttribute(COUNT, new AtomicLong()); - super.start(ctx); - } - - @Override - public Object visit(ASTExpression node, Object data) { - // How many Expression nodes are there in all files parsed! I must know! - RuleContext ctx = (RuleContext)data; - AtomicLong total = (AtomicLong)ctx.getAttribute(COUNT); - total.incrementAndGet(); - return super.visit(node, data); - } - - @Override - public void end(RuleContext ctx) { - AtomicLong total = (AtomicLong)ctx.getAttribute(COUNT); - addViolation(ctx, null, new Object[] { total }); - ctx.removeAttribute(COUNT); - super.end(ctx); - } -} -``` - -As you can see in this example, the method start will be call the first time the rule is going to be used, so you can initialize properly your rule here. Once the rule will have finished to parses the source code, the method end() will be invoke you can assert there if, or not, your rule has been violated. - ->Note that the example logs a violation **without** a proper classname. This is not really a good idea. Indeed, a lot of aggregating tools that PMD (Such as [XRadar](http://xradar.sourceforge.net), or [Sonar](http://www.sonarsource.com/)) probably uses this kind of meta data on their aggregation processes. So, when implements such a rule, always try to find a way to add classname to the violation report. +More information about writing XPath rules is [available here](pmd_userdocs_extending_writing_xpath_rules.html). ## I need some kind of Type Resolution for my rule! ### Inside an XPath query -PMD XPath syntax includes now a new function called `typeof` which determines if a node (ClassOrInterfaceType only right now) is of the provided type. It also scans the type’s hierarchy, so if you extend a class it will also find this out. +PMD's XPath extensions include two functions called `typeIs` and `typeIsExactly`, +which determine if a node is of a specific type (either any subtype or exactly, +respectively). -Here a an example of use, inside an XPath Query: +Here a an example of use, inside an XPath query: -```xpath -//ClassOrInterfaceDeclaration[ - //ClassOrInterfaceType[typeof(@Image, 'junit.framework.TestCase','TestCase')] -] +```ruby +//ClassOrInterfaceDeclaration/ExtendsList/ClassOrInterfaceType[typeIs('junit.framework.TestCase')] ``` -This query will match on such source code: +This query will for instance match the following class declaration: ```java import junit.framework.TestCase; @@ -322,6 +274,16 @@ import junit.framework.TestCase; public class Foo extends TestCase { } ``` +It will also match against classes which extend a *subtype* of `junit.framework.TestCase`, +i.e. a base class itself extending `TestCase` transitively. If you don't want this behaviour, +then use `typeIsExactly` instead of `typeIs`. + +Checking against an array type is possible with the double bracket syntax. +An array type is denoted by just appending `[]` to the fully qualified class name +of the component type. These can be repeated for arrays of arrays +(e.g. `byte[][]` or `java.lang.String[]`). + + ### With Java code Below an other sample of use of type resolution inside a java code: diff --git a/docs/pages/pmd/devdocs/writing_xpath_rules.md b/docs/pages/pmd/userdocs/extending/writing_xpath_rules.md similarity index 96% rename from docs/pages/pmd/devdocs/writing_xpath_rules.md rename to docs/pages/pmd/userdocs/extending/writing_xpath_rules.md index ccbc1982c18..39d8bdb802e 100644 --- a/docs/pages/pmd/devdocs/writing_xpath_rules.md +++ b/docs/pages/pmd/userdocs/extending/writing_xpath_rules.md @@ -1,9 +1,9 @@ --- -title: Writing XPath Rules -tags: [customizing] +title: Writing XPath rules +tags: [extending, userdocs] summary: "Writing XPath rules for PMD" last_updated: July 3, 2016 -permalink: pmd_devdocs_writing_xpath_rules.html +permalink: pmd_userdocs_extending_writing_xpath_rules.html author: Miguel Griffa <mikkey@users.sourceforge.net> --- @@ -160,9 +160,9 @@ the example code and give your rule a useful name and message. <?xml version="1.0"?> <ruleset name="Custom Rules" - xmlns="http://pmd.sourceforge.net/ruleset/3.0.0" + xmlns="http://pmd.sourceforge.net/ruleset/2.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/3.0.0 http://pmd.sourceforge.net/ruleset_3_0_0.xsd"> + xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd"> <description> Custom rules </description> diff --git a/docs/pages/pmd/userdocs/getting_started.md b/docs/pages/pmd/userdocs/getting_started.md deleted file mode 100644 index 21398decad6..00000000000 --- a/docs/pages/pmd/userdocs/getting_started.md +++ /dev/null @@ -1,345 +0,0 @@ ---- -title: Getting Started -permalink: pmd_userdocs_getting_started.html -author: Tom Copeland <tom@infoether.com>, Xavier Le Vourch <xlv@users.sourceforge.net> ---- - -## How to install PMD and CPD - -### Windows - -Requirements: - -* [Java JRE](http://www.oracle.com/technetwork/java/javase/downloads/index.html) 1.7 or higher -* [Winzip](http://winzip.com) or the free [7-zip](http://www.7-zip.org/) - -Download the latest binary distribution from [the github releases page](https://github.com/pmd/pmd/releases). - -Unzip it into any directory, i.e., c:\pmd\ - -### Linux / Unix - -Requirements: - -* [Java JRE](http://www.oracle.com/technetwork/java/javase/downloads/index.html) 1.7 or higher -* The Unix "zip" utility [InfoZip](http://www.info-zip.org/pub/infozip/) - -Download the latest binary distribution from [the github releases page](https://github.com/pmd/pmd/releases). - -Unzip it into any directory: - - [tom@hal tmp]$ unzip -q pmd-bin-{{site.pmd.version}}.zip - [tom@hal tmp]$ ls -l - total 4640 - drwxrwxr-x 5 tom tom 4096 Apr 17 16:38 pmd-bin-{{site.pmd.version}} - -rw-rw-r-- 1 tom tom 4733312 Jun 9 15:44 pmd-bin-{{site.pmd.version}}.zip - [tom@hal tmp]$ - - -Note that the PMD binary distribution file contains both [PMD](#running-pmd-via-command-line) and [CPD](/pmd_userdocs_cpd.html). - - -## Running PMD via command line - -### On Linux and other UNIX based operating system... - -PMD comes with several command line utilities. Previously, each of them had its own start up script, but this has been -greatly simplified since PMD 5.0. ... at least for Unix systems. There is now only one script, called "run.sh", inside -the `bin/` directory of PMD distribution. - -The first argument is the name of the utility you want to execute ('pmd', 'designer', ...) and the other arguments are -specific to the utility used. - - $ ./bin/run.sh pmd -d ../../../src/main/java/ -f text -R rulesets/java/basic.xml -version 1.7 -language java - .../src/main/java/net/sourceforge/pmd/RuleSet.java:123 These nested if statements could be combined - .../src/main/java/net/sourceforge/pmd/RuleSet.java:231 Useless parentheses. - .../src/main/java/net/sourceforge/pmd/RuleSet.java:232 Useless parentheses. - .../src/main/java/net/sourceforge/pmd/RuleSet.java:357 These nested if statements could be combined - .../src/main/java/net/sourceforge/pmd/RuleSetWriter.java:66 Avoid empty catch blocks - .../src/main/java/net/sourceforge/pmd/RuleSetWriter.java:269 Useless parentheses. - -Type "./run.sh pmd -d \[filename\|jar or zip file containing source code\|directory] -f \[report format] -R \[ruleset file]", i.e: - - /home/user/tmp/pmd-bin-{{site.pmd.version}}/pmd/bin>./run.sh pmd -d /home/user/data/pmd/pmd/test-data/Unused1.java -f xml -R rulesets/java/unusedcode.xml - <?xml version="1.0"?><pmd> - <file name="/home/user/data/pmd/pmd/test-data/Unused1.java"> - <violation line="5" rule="UnusedLocalVariable"> - Avoid unused local variables such as 'fr' - </violation> - </file></pmd> - - /home/user/tmp/pmd-bin-{{site.pmd.version}}/pmd/bin> - - -### Basic usage for Windows - -You can find PMD's starter batch file `pmd.bat` in the `bin` subdirectory. - -Type "pmd -d \[filename\|jar or zip file containing source code\|directory] -f \[report format] -R \[ruleset file]", i.e: - - C:\tmp\pmd-bin-{{site.pmd.version}}<\pmd\bin>pmd -d c:\data\pmd\pmd\test-data\Unused1.java -f xml -R rulesets/java/unusedcode.xml - <?xml version="1.0"?><pmd> - <file name="c:\data\pmd\pmd\test-data\Unused1.java"> - <violation line="5" rule="UnusedLocalVariable"> - Avoid unused local variables such as 'fr' - </violation> - </file></pmd> - - C:\tmp\pmd-bin-{{site.pmd.version}}\pmd\bin> - -You can pass a file name, a directory name, or a jar or zip file name containing Java source code to PMD. - -Also, the PMD binary distribution includes the ruleset files -inside the jar file - even though the "rulesets/java/unusedcode.xml" parameter -above looks like a filesystem reference, it's really being used by a getResourceAsStream() call -to load it out of the PMD jar file. - -### Options - -The tool comes with a rather extensive help text, simply running with `-help`! - -<table> - <tr> - <th>Option</th> - <th>Description</th> - <th>Required</th> - <th>Applies for language</th> - </tr> - <tr> - <td>-rulesets / -R</td> - <td>Comma separated list of ruleset names to use</td> - <td>yes</td> - <td></td> - </tr> - <tr> - <td>-dir / -d</td> - <td>Root directory for sources</td> - <td>yes</td> - <td></td> - </tr> - <tr> - <td>-format / -f</td> - <td>Report format type. Default format is `text`.</td> - <td>no</td> - <td></td> - </tr> - <tr> - <td>-auxclasspath</td> - <td> - Specifies the classpath for libraries used by the source code. This is used by the type resolution. - Alternatively a `file://` URL to a text file containing path elements on consecutive lines can be - specified. - </td> - <td>no</td> - <td></td> - </tr> - <tr> - <td>-uri / -u</td> - <td>Database URI for sources. If this is given, then you don't need to provide `-dir`.</td> - <td>no</td> - <td>plsql</td> - </tr> - <tr> - <td>-filelist</td> - <td>Path to file containing a comma delimited list of files to analyze. If this is given, then you don't need to provide `-dir`.</td> - <td>no</td> - <td></td> - </tr> - <tr> - <td>-debug / -verbose / -D / -V</td> - <td>Debug mode. Prints more log output.</td> - <td>no</td> - <td></td> - </tr> - <tr> - <td>-help / -h / -H</td> - <td>Display help on usage.</td> - <td>no</td> - <td></td> - </tr> - <tr> - <td>-encoding / -e</td> - <td>Specifies the character set encoding of the source code files PMD is reading (i.e. UTF-8). Default is `UTF-8`.</td> - <td>no</td> - <td></td> - </tr> - <tr> - <td>-threads / -t</td> - <td>Sets the number of threads used by PMD. Default is `1`. Set threads to '0' to disable multi-threading processing.</td> - <td>no</td> - <td></td> - </tr> - <tr> - <td>-benchmark / -b</td> - <td>Benchmark mode - output a benchmark report upon completion; defaults to System.err</td> - <td>no</td> - <td></td> - </tr> - <tr> - <td>-stress / -S</td> - <td>Performs a stress test.</td> - <td>no</td> - <td></td> - </tr> - <tr> - <td>-shortnames</td> - <td>Prints shortened filenames in the report.</td> - <td>no</td> - <td></td> - </tr> - <tr> - <td>-showsuppressed</td> - <td>Report should show suppressed rule violations.</td> - <td>no</td> - <td></td> - </tr> - <tr> - <td>-suppressmarker</td> - <td>Specifies the string that marks the line which PMD should ignore; default is `NOPMD`.</td> - <td>no</td> - <td></td> - </tr> - <tr> - <td>-minimumpriority / -min</td> - <td>Rule priority threshold; rules with lower priority than configured here won't be used. Default is `5` - which is the lowest priority.</td> - <td>no</td> - <td></td> - </tr> - <tr> - <td>-property / -P</td> - <td>`{name}={value}`: Define a property for a report format.</td> - <td>no</td> - <td></td> - </tr> - <tr> - <td>-reportfile / -r</td> - <td>Send report output to a file; default to System.out</td> - <td>no</td> - <td></td> - </tr> - <tr> - <td>-version / -v</td> - <td>Specify version of a language PMD should use.</td> - <td>no</td> - <td></td> - </tr> - <tr> - <td>-language / -l</td> - <td>Specify a language PMD should use.</td> - <td>no</td> - <td></td> - </tr> - <tr> - <td>-failOnViolation {true|false}</td> - <td>By default PMD exits with status 4 if violations are found. - Disable this option with '-failOnViolation false' to exit with 0 instead and just write the report. - </td> - <td>no</td> - <td></td> - </tr> - <tr> - <td>-cache</td> - <td>Specify a location for the analysis cache file to use. - This can greatly improve analysis performance and is <b>highly recommended</b>.</td> - <td>no</td> - <td></td> - </tr> -</table> - - -### Exit Status - -Please note that if PMD detects any violations, it will exit with status 4 (since 5.3). -This behavior has been introduced to ease PMD integration into scripts or hooks, such as SVN hooks. - -<table> -<tr><td>0</td><td>Everything is fine, now violations found</td></tr> -<tr><td>1</td><td>Couldn't understand command line parameters or PMD exited with an exception</td></tr> -<tr><td>4</td><td>At least one violation has been detected unless '-failOnViolation false' is set.</td></tr> -</table> - - -### Supported Languages - -* [apex](/pmd_rules_apex.html) -* [java](/pmd_rules_java.html) -* [ecmascript](/pmd_rules_javascript.html) (JavaScript) -* [jsp](/pmd_rules_jsp.html) -* [plsql](/pmd_rules_plsql.html) -* [vm](/pmd_rules_vm.html) (Apache Velocity) -* [xml and xsl](/pmd_rules_xml.html) - - -### Available Report Formats / Renderers - -PMD comes with many different renderer types: - -* **codeclimate**: Renderer for Code Climate JSON format. - -* **csv**: Comma-separated values tabular format. - - Properties: - - * problem: Include problem column. Default: true. - * package: Include package column. Default: true. - * file: Include file column. Default: true. - * priority: Include priority column. Default: true. - * line: Include line column. Default: true. - * desc: Include description column. Default: true. - * ruleSet: Include Rule set column. Default: true. - * rule: Include Rule column. Default: true. - -* **emacs**: GNU Emacs integration. - -* **html**: HTML format. - - Properties: - - * linePrefix: Prefix for line number anchor in the source file. - * linkPrefix: Path to HTML source. - -* **ideaj**: IntelliJ IDEA integration. - - Properties: - - * classAndMethodName: Class and method name, pass '.method' when processing a directory. - * sourcePath: - * fileName: - -* **summaryhtml**: Summary HTML format. - - Properties: - - * linePrefix: Prefix for line number anchor in the source file. - * linkPrefix: Path to HTML source. - -* **text**: Text format. - -* **textcolor**: Text format, with color support (requires ANSI console support, e.g. xterm, rxvt, etc.). - - Properties: - - * color: Enables colors with anything other than 'false' or '0'. Default: yes. - -* **textpad**: TextPad integration. - -* **vbhtml**: Vladimir Bossicard HTML format. - -* **xml**: XML format. - - Properties: - - * encoding: XML encoding format, defaults to UTF-8. - -* **xslt**: XML with a XSL transformation applied. - - Properties: - - * encoding: XML encoding format, defaults to UTF-8. - * xsltFilename: The XSLT file name. - -* **yahtml**: Yet Another HTML format. - - Properties: - - * outputDir: Output directory. diff --git a/docs/pages/pmd/userdocs/help.md b/docs/pages/pmd/userdocs/help.md deleted file mode 100644 index 8ef0a175033..00000000000 --- a/docs/pages/pmd/userdocs/help.md +++ /dev/null @@ -1,8 +0,0 @@ ---- -title: Getting Help -sidebar: pmd_sidebar -permalink: pmd_userdocs_help.html -folder: pmd/userdocs ---- - -Asking questions, reporting bugs, ... diff --git a/docs/pages/pmd/userdocs/incremental_analysis.md b/docs/pages/pmd/userdocs/incremental_analysis.md new file mode 100644 index 00000000000..8aa117b5a2f --- /dev/null +++ b/docs/pages/pmd/userdocs/incremental_analysis.md @@ -0,0 +1,34 @@ +--- +title: Incremental Analysis +keywords: [pmd, options, command, incremental, analysis, performance] +tags: [userdocs] +summary: "Explains how to use incremental analysis to speed up analysis" +permalink: pmd_userdocs_incremental_analysis.html +sidebar: pmd_sidebar +--- + +Ever since PMD 5.6.0, PMD has been able to perform Incremental Analysis. + +When performing Incremental Analysis for the first time, PMD will cache analysis data and results. +This allows subsequent analysis to only look into those files that are new / have changed. For +a typical development environment, where you only change a few files at a time, this can reduce +analysis time dramatically. + +The generated report will be *exactly the same* as it would if running without incremental analysis. +Files included in the final report will reflect exactly those files in your filesystem. Even if +untouched, files with violations will be listed with full detail. Therefore, its usage is higly recommended. + + +### Enabling incremental analysis + +Incremental analysis is enabled automatically once a location to store the cache has been defined. +From command-line that is done through the [`-cache`](pmd_userdocs_cli_reference.html#cache) argument, but support for the feature is +available for tools integrating PMD such as [Ant](pmd_userdocs_tools_ant.html), +[Maven](pmd_userdocs_tools_maven.html), and Gradle. + + +### Disabling incremental analysis + +By default, PMD will suggest to use an analysis cache by logging a warning. +If you'd like to disable this warning, or ignore the analysis cache for a +few runs, you can use the [`-no-cache`](pmd_userdocs_cli_reference.html#no-cache) switch. diff --git a/docs/pages/pmd/userdocs/installation.md b/docs/pages/pmd/userdocs/installation.md new file mode 100644 index 00000000000..44715742661 --- /dev/null +++ b/docs/pages/pmd/userdocs/installation.md @@ -0,0 +1,166 @@ +--- +title: Installation and basic CLI usage +keywords: [pmd, cpd, options, command, auxclasspath] +tags: [getting_started, userdocs] +summary: "Sums up the first steps to set up a CLI installation and get started using PMD" +permalink: pmd_userdocs_installation.html +sidebar: pmd_sidebar +--- + +## How to install PMD and CPD + +### Requirements + +* [Java JRE](http://www.oracle.com/technetwork/java/javase/downloads/index.html) 1.7 or higher +* A zip archiver, e.g.: + + * For Windows: [Winzip](http://winzip.com) or the free [7-zip](http://www.7-zip.org/) + * For Linux / Unix: [InfoZip](http://www.info-zip.org/pub/infozip/) + +{% include note.html content="For executing the Designer (./run.sh designer) using [OpenJDK](http://jdk.java.net) or Java 11, you need additionally [OpenJFX](http://jdk.java.net). Download it, extract it and set the environment variable JAVAFX_HOME." %} + + +### Installation + +PMD is distributed as a zip archive, which includes both [PMD](#running-pmd-via-command-line) and [CPD](/pmd_userdocs_cpd.html). +You can download the latest binary distribution from [the github releases page](https://github.com/pmd/pmd/releases). + +Unzip it into any directory, optionally add the `bin` subdirectory in your `PATH`, and you're good to go! + + + +## Running PMD via command line + +{% include callout.html type="primary" + content="PMD comes with several command line utilities, like CPD, the rule designer or PMD itself. + On Unix, you can run any of them using the script `run.sh`, located inside the `bin/` + directory of the PMD distribution. The first argument is the name of the utility you want + to execute ('pmd', 'designer', ...), e.g. PMD is launched via `run.sh pmd`. The rest of + the arguments are specific to the utility used.<br/><br/> + On Windows, each utility has its own startup script, e.g. `pmd.bat`, `cpd.bat`." %} + +The PMD command (`pmd.bat` or `run.sh pmd`) requires two options: + +* `-d <path>`: path to the sources to analyse. This can be a file name, a directory, or a jar or zip file containing the +sources. +* `-R <path>`: the ruleset file you want to use. PMD uses xml configuration files, called *rulesets*, which specify +which rules to execute on your sources. You can also run a single rule by referencing it using its *category* and +name (more details [here](pmd_userdocs_making_rulesets.html#referencing-a-single-rule)). For example, you can check for unnecessary +modifiers on Java sources with `-R category/java/codestyle.xml/UnnecessaryModifier`. + +{% include note.html + content="At the moment the formerly provided rulesets (eg `rulesets/java/basic.xml`) are deprecated, + though you can still use them. PMD includes a quickstart ruleset for some languages (currently, Java) + as base configurations, which you can reference as e.g. `rulesets/java/quickstart.xml`. You're strongly + encouraged to [create your own ruleset](pmd_userdocs_making_rulesets.html) from the start though." %} + +Additionally, the following options, are specified most of the time even though they're not required: +* `-f <format>`: report format. PMD supports many report formats out of the box. You may want to start with the basic +`text` format (default) or `xml` format. The supported formats are [documented here](pmd_userdocs_cli_reference.html#available-report-formats). +* `-auxclasspath <classpath>`: class path containing the compiled class files of the analysed Java sources, if any. + Setting this up correctly allows PMD to do much deeper analysis using reflection. Some rules, such as [MissingOverride](pmd_rules_java_bestpractices.html#missingoverride), + require it to function properly. + +{%include tip.html content="A full CLI reference, including report formats, is available under [PMD CLI Reference](pmd_userdocs_cli_reference.html)" %} + + + +### Sample usage + + The following shows a sample run of PMD with the `text` format: + + +<div class="text-left"> + <ul class="nav nav-tabs" role="tablist"> + <li role="presentation" class="active"><a href="#linux" aria-controls="linux / unix" role="tab" data-toggle="tab">Linux / Unix</a></li> + <li role="presentation"><a href="#windows" aria-controls="windows" role="tab" data-toggle="tab">Windows</a></li> + </ul> + + <div class="tab-content"> + <div role="tabpanel" class="tab-pane active" id="linux"> +<figure class="highlight"><pre><code class="language-bash" data-lang="bash"><span class="gp">~ $ </span><span class="s2">cd</span> ~/bin/pmd-bin-{{site.pmd.version}}/bin +<span class="gp">~/.../bin $ </span><span class="s2">./run.sh</span> pmd -d ../../../src/main/java/ -f text -R rulesets/java/basic.xml + + .../src/main/java/com/me/RuleSet.java:123 These nested if statements could be combined + .../src/main/java/com/me/RuleSet.java:231 Useless parentheses. + .../src/main/java/com/me/RuleSet.java:232 Useless parentheses. + .../src/main/java/com/me/RuleSet.java:357 These nested if statements could be combined + .../src/main/java/com/me/RuleSetWriter.java:66 Avoid empty catch blocks</code></pre></figure> + </div> + <div role="tabpanel" class="tab-pane" id="windows"> +<figure class="highlight"><pre><code class="language-bash" data-lang="bash"><span class="gp">C:\ > </span><span class="s2">cd</span> C:\pmd-bin-{{site.pmd.version}}\bin +<span class="gp">C:\...\bin > </span><span class="s2">.\pmd.bat</span> -d ..\..\src\main\java\ -f text -R rulesets/java/basic.xml + + .../src/main/java/com/me/RuleSet.java:123 These nested if statements could be combined + .../src/main/java/com/me/RuleSet.java:231 Useless parentheses. + .../src/main/java/com/me/RuleSet.java:232 Useless parentheses. + .../src/main/java/com/me/RuleSet.java:357 These nested if statements could be combined + .../src/main/java/com/me/RuleSetWriter.java:66 Avoid empty catch blocks</code></pre></figure> + </div> + </div> +</div> + + +## Running CPD via command line + +{% include note.html + content="CPD supports Java, JSP, C, C++, C#, Fortran and PHP source code, among other languages. + For the full list, see [Supported Languages](pmd_userdocs_cpd#supported-languages)." %} + +Like for PMD, CPD is started on Unix by `run.sh cpd` and on Windows by `cpd.bat`. + +There are two required parameters: +* `--files <path>`: path to the sources to analyse. This can be a file name, a + directory or a jar or zip file containing the sources. +* `--minimum-tokens <number>`: the minimum token length which should be reported as a duplicate. + +{% include tip.html + content="CPD's command-line reference, Ant task usage, and many examples are documented in the + [CPD documentation page](pmd_userdocs_cpd.html)" %} + +### Sample usage + + The following shows a sample run of CPD with the `text` format: + + +<div class="text-left"> + <ul class="nav nav-tabs" role="tablist"> + <li role="presentation" class="active"><a href="#cpd-linux" aria-controls="linux / unix" role="tab" data-toggle="tab">Linux / Unix</a></li> + <li role="presentation"><a href="#cpd-windows" aria-controls="windows" role="tab" data-toggle="tab">Windows</a></li> + </ul> + + <div class="tab-content"> + <div role="tabpanel" class="tab-pane active" id="cpd-linux"> +<figure class="highlight"><pre><code class="language-bash" data-lang="bash"><span class="gp">~ $ </span><span class="s2">cd</span> ~/bin/pmd-bin-{{site.pmd.version}}/bin +<span class="gp">~/.../bin $ </span><span class="s2">./run.sh</span> cpd --minimum-tokens 100 --files /home/me/src + + Found a 7 line (110 tokens) duplication in the following files: + Starting at line 579 of /home/me/src/test/java/foo/FooTypeTest.java + Starting at line 586 of /home/me/src/test/java/foo/FooTypeTest.java + + assertEquals(Boolean.TYPE, expressions.get(index++).getType()); + assertEquals(Boolean.TYPE, expressions.get(index++).getType()); + assertEquals(Boolean.TYPE, expressions.get(index++).getType()); + assertEquals(Boolean.TYPE, expressions.get(index++).getType()); + assertEquals(Boolean.TYPE, expressions.get(index++).getType()); + assertEquals(Boolean.TYPE, expressions.get(index++).getType()); + assertEquals(Boolean.TYPE, expressions.get(index++).getType());</code></pre></figure> + </div> + <div role="tabpanel" class="tab-pane" id="cpd-windows"> +<figure class="highlight"><pre><code class="language-bash" data-lang="bash"><span class="gp">C:\ > </span><span class="s2">cd</span> C:\pmd-bin-{{site.pmd.version}}\bin +<span class="gp">C:\...\bin > </span><span class="s2">.\cpd.bat</span> --minimum-tokens 100 --files c:\temp\src + + Found a 7 line (110 tokens) duplication in the following files: + Starting at line 579 of c:\temp\src\test\java\foo\FooTypeTest.java + Starting at line 586 of c:\temp\src\test\java\foo\FooTypeTest.java + + assertEquals(Boolean.TYPE, expressions.get(index++).getType()); + assertEquals(Boolean.TYPE, expressions.get(index++).getType()); + assertEquals(Boolean.TYPE, expressions.get(index++).getType()); + assertEquals(Boolean.TYPE, expressions.get(index++).getType()); + assertEquals(Boolean.TYPE, expressions.get(index++).getType()); + assertEquals(Boolean.TYPE, expressions.get(index++).getType()); + assertEquals(Boolean.TYPE, expressions.get(index++).getType());</code></pre></figure> + </div> + </div> +</div> diff --git a/docs/pages/pmd/userdocs/making_rulesets.md b/docs/pages/pmd/userdocs/making_rulesets.md new file mode 100644 index 00000000000..272ce53ebc3 --- /dev/null +++ b/docs/pages/pmd/userdocs/making_rulesets.md @@ -0,0 +1,110 @@ +--- +title: Making rulesets +keywords: [rulesets, reference, rule, exclude, include, pattern, filter] +tags: [getting_started, userdocs] +summary: Making Custom Rulesets for PMD +last_updated: November 2017 +summary: "A ruleset is an XML configuration file, which describes a collection of rules to be executed + in a PMD run. PMD includes built-in rulesets to run quick analyses with a default configuration, but + users are encouraged to make their own rulesets from the start, because they allow for so much + configurability. This page walk you through the creation of a ruleset and the multiple configuration + features offered by rulesets." +last_updated: May 2018 (6.4.0) +permalink: pmd_userdocs_making_rulesets.html +author: Tom Copeland <tomcopeland@users.sourceforge.net>, Clément Fournier <clement.fournier76@gmail.com> +--- + +## Creating a ruleset + +The first step is to create a new empty ruleset. You can use the following template: + +``` xml +<?xml version="1.0"?> + +<ruleset name="Custom Rules" + xmlns="http://pmd.sourceforge.net/ruleset/2.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd"> + + <description> + My custom rules + </description> + + + <!-- Your rules will come here --> + +</ruleset> +``` + +### Referencing a single rule + +<!-- TODO this could be better explained, eg first explain how a ruleset reference works, then rule reference, then go on showing single rule & bulk addition, then include/exclude patterns --> + +To use the built-in rules PMD provides, you need to add some *references* to them. Here's a +basic rule reference: + +```xml + <rule ref="category/java/errorprone.xml/EmptyCatchBlock" /> +``` + +Adding that element into the `ruleset` element adds the rule [EmptyCatchBlock](pmd_rules_java_errorprone.html#emptycatchblock) +to your ruleset. This is a Java rule, so it will be executed on every Java file PMD encounters in +its search space. + +How to read the `ref` attribute? + +* `category/java/errorprone.xml` is a reference to the Java category `errorprone`. Since PMD 6.0.0, + all PMD built-in rules are sorted in one of eight categories, which are consistent across languages: + + 1. **Best Practices**: These are rules which enforce generally accepted best practices.<br/> + 2. **Code Style**: These rules enforce a specific coding style.<br/> + 3. **Design**: Rules that help you discover design issues.<br/> + 4. **Documentation**: These rules are related to code documentation.<br/> + 5. **Error Prone**: Rules to detect constructs that are either broken, extremely confusing or prone to runtime errors.<br/> + 6. **Multithreading**: These are rules that flag issues when dealing with multiple threads of execution.<br/> + 7. **Performance**: Rules that flag suboptimal code.<br/> + 8. **Security**: Rules that flag potential security flaws." + +{% include tip.html content="You can discover the available rules by language and category [from this page](tag_rule_references.html)" %} + + +* `EmptyCatchBlock` is simply the name of the rule. If there were no rule with that name within the specified + category, then PMD would fail before starting the analysis. + +#### [Configuring individual rules](pmd_userdocs_configuring_rules.html) + +### Bulk-adding rules + +You can also reference rules in bulk by referencing a complete category or ruleset, possibly excluding certain rules, like in the following: + +```xml + <rule ref="category/java/codestyle.xml"> + <exclude name="WhileLoopsMustUseBraces"/> + <exclude name="IfElseStmtsMustUseBraces"/> + </rule> +``` + +Here, the `ref` attribute references a whole category. You can also use a file system path or classpath relative path. In any case, the path must address an accessible ruleset XML file. + +{% include note.html content="Path separators in the source file path are normalized to be the `/` character within PMD, so the same ruleset can be used on multiple platforms transparently." %} + +### Filtering the processed files + +You can exclude some files from being processed by a ruleset using **exclude patterns**, with an optional overridding **include pattern**. A file will be excluded from processing *when there is a matching exclude pattern, but no matching include pattern*. This exclude/include technique works regardless of how PMD is used (e.g. command line, IDE, Ant), making it easier to keep application of your PMD rules consistent throughout your environment. Here is an example: + +```xml +<?xml version="1.0"?> +<ruleset name="myruleset" + xmlns="http://pmd.sourceforge.net/ruleset/2.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd"> + <description>My ruleset</description> + + <exclude-pattern>.*/some/package/.*</exclude-pattern> + <exclude-pattern>.*/some/other/package/FunkyClassNamePrefix.*</exclude-pattern> + <include-pattern>.*/some/package/ButNotThisClass.*</include-pattern> + + <!-- Rules here ... --> + +</ruleset> +``` diff --git a/docs/pages/pmd/userdocs/suppressing.md b/docs/pages/pmd/userdocs/suppressing.md deleted file mode 100644 index d9de8589c7b..00000000000 --- a/docs/pages/pmd/userdocs/suppressing.md +++ /dev/null @@ -1,165 +0,0 @@ ---- -title: Suppressing warnings -permalink: pmd_userdocs_suppressing.html -author: Tom Copeland <tom@infoether.com> ---- - -PMD provides several methods by which Rule violations can be suppressed. -Follow these steps to help you determine which expression method works best -for you: - -1. Is the thing you need to suppress universally appealing to other - users of PMD, or is it a false positive? Can you modify the Rule to - support this specific suppression via a configuration property, or to - fix the false positive? If you can do this, then please do so, and - submit a patch back to the PMD project. Since PMD is built by users - for users, your help would be greatly appreciated by everyone. If you - cannot... - -2. Can you use Annotations or the NOPMD marker to work around your - particular issue on a case by case basis? If not... - -3. Can a regular expression matching the violation message work - around your particular issue? If not... - -4. Can a XPath query on the violation node work around your particular - issue? If not... - -5. Your last and final option is to see the first point about - changing the Rule, but you do not need to submit a patch back to the - PMD project. - -If you need to modify the Rule, see [How to write a rule](/pmd_devdocs_writing_pmd_rules.html). -Otherwise, the other suppression methods are explained in the following sections. - -## Annotations - -You can use a JDK 1.5 annotation to suppress PMD warnings, like this: - - // This will suppress all the PMD warnings in this class - @SuppressWarnings("PMD") - public class Bar { - void bar() { - int foo; - } - } - -Or you can suppress one rule with an annotation like this: - - // This will suppress UnusedLocalVariable warnings in this class - @SuppressWarnings("PMD.UnusedLocalVariable") - public class Bar { - void bar() { - int foo; - } - } - -PMD also obeys the JDK annotation @SuppressWarnings("unused"), which will apply to all rules in the unused ruleset. - - // This will suppress UnusedLocalVariable and UnusedPrivateMethod warnings in this class - @SuppressWarnings("unused") - public class Bar { - void bar() { - int foo; - } - private void foobar(){} - } - - -## NOPMD - -Alternatively, you can tell PMD to ignore a specific line by using the "NOPMD" marker, like this: - - public class Bar { - // 'bar' is accessed by a native method, so we want to suppress warnings for it - private int bar; //NOPMD - } - -You can use whatever text string you want to suppress warnings, for example, here's -how to use TURN\_OFF\_WARNINGS as the suppressor: - - $ cat Foo.java - public class Foo { - void bar() { - int x = 2; // TURN_OFF_WARNINGS - } - } - - $ ./run.sh pmd -d Foo.java -f text -R java-unusedcode -suppressmarker TURN_OFF_WARNINGS - No problems found! - UnusedLocalVariable rule violation suppressed by //NOPMD in /home/tom/pmd/pmd/bin/Foo.java - -Note that PMD expects the //NOPMD marker to be on the same line as the violation. So, for -example, if you want to suppress an "empty if statement" warning, you'll need to place it on -the line containing the "if" keyword, e.g.: - - $ cat ~/tmp/Foo.java - public class Foo { - void bar() { - int x = 42; - if (x > 5) { // NOPMD - } - } - } - $ java net.sourceforge.pmd.PMD -d ~/tmp/Foo.java -f text -R java-basic - No problems found! - $ - -A message placed after the NOPMD marker will get placed in the report, e.g.: - - public class Foo { - void bar() { - try { - bar(); - } catch (FileNotFoundException e) {} // NOPMD - this surely will never happen - } - } - -## Violation Suppress Regex - -If a particular Rule does not provide a property to customize behavior -sufficiently, you can fall back to using the global 'violationSuppressRegex' -property. This property defines a regular expression to match against the -message of the violation. If the regular expression matches, -then the violation will be suppressed. - -When using a Rule reference in a RuleSet XML, you can customize the -Rule by adding the 'violationSuppressRegex' property. For example, to -suppress reporting specifically named parameters which are unused: - - - <rule ref="rulesets/java/unusedcode.xml/UnusedFormalParameter"> - <properties> - <property name="violationSuppressRegex" value=".*'mySpecialParameterName'.*"/> - </properties> - </rule> - -Note for message based suppression to work, you must know who to write -a regular expression that matches the message of violations you wish to -suppress. Regular expressions are explained in the JavaDoc for standard -Java class java.util.regex.Pattern. - -## Violation Suppress XPath - -If a particular Rule does not provide a property to customize behavior -sufficiently, you can fall back to using the global 'violationSuppressXPath' -property. This property defines an XPath query to be executed using the -violation node as the starting point. If the XPath query matches anything, -then the violation will be suppressed. - -When using a Rule reference in a RuleSet XML, you can customize the -Rule by adding the 'violationSuppressXPath' property. For example, to -suppress reporting specifically typed parameters which are unused: - - <rule ref="rulesets/java/unusedcode.xml/UnusedFormalParameter"> - <properties> - <property name="violationSuppressXPath" value=".[typeof('java.lang.String')]"/> - </properties> - </rule> - -Note for XPath based suppression to work, you must know how to write -an XPath query that matches the AST structure of the nodes of the -violations you wish to suppress. XPath queries are explained in -[XPath Rule tutorial](/pmd_devdocs_writing_xpath_rules.html). - -Suggestions? Comments? Post them [here](https://github.com/pmd/pmd/issues). Thanks! diff --git a/docs/pages/pmd/userdocs/suppressing_warnings.md b/docs/pages/pmd/userdocs/suppressing_warnings.md new file mode 100644 index 00000000000..5b19ccab1ca --- /dev/null +++ b/docs/pages/pmd/userdocs/suppressing_warnings.md @@ -0,0 +1,206 @@ +--- +title: Suppressing warnings +keywords: [suppressing, warnings, suppresswarnings, nopmd, violationSuppressXPath, violationSuppressRegex] +tags: [userdocs] +summary: "Learn how to suppress some rule violations, from the source code using annotations or comments, or globally from the ruleset" +permalink: pmd_userdocs_suppressing_warnings.html +author: Tom Copeland <tom@infoether.com> +--- + +PMD provides several methods by which Rule violations can be suppressed. +Follow these steps to help you determine which expression method works best +for you: + +1. Is the thing you need to suppress universally appealing to other + users of PMD, or is it a false positive? Can you modify the Rule to + support this specific suppression via a configuration property, or to + fix the false positive? If you can do this, then please do so, and + submit a patch back to the PMD project. Since PMD is built by users + for users, your help would be greatly appreciated by everyone. If you + cannot... + +2. Can you use Annotations or the NOPMD marker to work around your + particular issue on a case by case basis? If not... + +3. Can a regular expression matching the violation message work + around your particular issue? If not... + +4. Can a XPath query on the violation node work around your particular + issue? If not... + +5. Your last and final option is to see the first point about + changing the Rule, but you do not need to submit a patch back to the + PMD project. + +If you need to modify the Rule, see [How to write a rule](pmd_devdocs_writing_pmd_rules.html). +Otherwise, the other suppression methods are explained in the following sections. + +## Annotations + +When using Java 1.5 or later, you can use annotations to suppress PMD warnings, like this: + +```java +// This will suppress all the PMD warnings in this class +@SuppressWarnings("PMD") +public class Bar { + void bar() { + int foo; + } +} +``` + +When using Apex make sure to use single quotes instead of double quotes + +```java +// This will suppress all the PMD warnings in this class +@SuppressWarnings('PMD') +``` + + +Or you can suppress one rule with an annotation like this: + +```java +// This will suppress UnusedLocalVariable warnings in this class +@SuppressWarnings("PMD.UnusedLocalVariable") +public class Bar { + void bar() { + int foo; + } +} +``` + +Multiple rules can be suppressed by providing multiple values, ie: + +```java +@SuppressWarnings({"PMD.UnusedLocalVariable", "PMD.UnusedPrivateMethod"}) +``` + +For Apex, the syntax for this is slightly different: + +```java +@SuppressWarnings('PMD.UnusedLocalVariable, PMD.UnusedPrivateMethod') +``` + +PMD Java also obeys the JDK annotation @SuppressWarnings("unused"), which will apply to all rules in the unused ruleset. + +```java +// This will suppress UnusedLocalVariable and UnusedPrivateMethod warnings in this class +@SuppressWarnings("unused") +public class Bar { + void bar() { + int foo; + } + private void foobar(){} +} +``` + +## NOPMD comment + +Alternatively, you can tell PMD to ignore a specific line by using the "NOPMD" marker in a comment, like this: + +```java +public class Bar { + // 'bar' is accessed by a native method, so we want to suppress warnings for it + private int bar; //NOPMD +} +``` + +You can use whatever text string you want to suppress warnings, by using the `-suppressmarker` CLI option. +For example, here's how to use `TURN_OFF_WARNINGS` as the suppressor: + +```java +$ cat Foo.java +public class Foo { + void bar() { + int x = 2; // TURN_OFF_WARNINGS + } +} + +$ ./run.sh pmd -d Foo.java -f text -R java-unusedcode -suppressmarker TURN_OFF_WARNINGS +No problems found! +UnusedLocalVariable rule violation suppressed by //NOPMD in /home/tom/pmd/pmd/bin/Foo.java +``` +Note that PMD expects the //NOPMD marker to be on the same line as the violation. So, for +example, if you want to suppress an "empty `if` statement" warning, you'll need to place it on +the line containing the `if` keyword, e.g.: + +```java +$ cat ~/tmp/Foo.java +public class Foo { + void bar() { + int x = 42; + if (x > 5) { // NOPMD + } + } +} +$ java net.sourceforge.pmd.PMD -d ~/tmp/Foo.java -f text -R java-basic +No problems found! +``` + +A message placed after the NOPMD marker will get placed in the report, e.g.: + +```java +public class Foo { + void bar() { + try { + bar(); + } catch (FileNotFoundException e) {} // NOPMD - this surely will never happen + } +} +``` + +## XPath and Regex message suppression + +If a particular rule consistently reports a warning in a particular context, you can fall +back to disabling the rule for all these contexts using one of two rule properties +that are defined on every rule. Depending on what you're after, you can suppress +violations for **specific messages** using regular expressions, or **specific nodes** +using an XPath expression. + + +### The property `violationSuppressRegex` + +This property defines a regular expression to match against the +message of the violation. If the regular expression matches, +then the violation will be suppressed. + +For example, to suppress reporting specifically named parameters which +are unused: + +```xml +<rule ref="rulesets/java/unusedcode.xml/UnusedFormalParameter"> + <properties> + <property name="violationSuppressRegex" value=".*'mySpecialParameterName'.*"/> + </properties> +</rule> +``` + +Note for message based suppression to work, you must know how to write +a regular expression that matches the message of violations you wish to +suppress. Regular expressions are explained in the JavaDoc for standard +Java class java.util.regex.Pattern. + +This technique of course relies on how the rule's message is implemented. +Some rules always report the same message, in which case this property is +of no use. + +### The property `violationSuppressXPath` + +This property defines an XPath query to be executed *using the +violation node as the starting point*. If the XPath query matches anything, +then the violation will be suppressed. + +For example, to suppress reporting specifically typed parameters which are unused: + +```xml +<rule ref="rulesets/java/unusedcode.xml/UnusedFormalParameter"> + <properties> + <property name="violationSuppressXPath" value=".[typeIs('java.lang.String')]"/> + </properties> +</rule> +``` + +Note for XPath based suppression to work, you must know how to write +an XPath query that matches the AST structure of the nodes of the +violations you wish to suppress. XPath queries are explained in +[XPath Rule tutorial](pmd_userdocs_extending_writing_xpath_rules.html). diff --git a/docs/pages/pmd/userdocs/tools/ant.md b/docs/pages/pmd/userdocs/tools/ant.md index eb2ff5f42e7..d85c588782b 100644 --- a/docs/pages/pmd/userdocs/tools/ant.md +++ b/docs/pages/pmd/userdocs/tools/ant.md @@ -1,5 +1,6 @@ --- title: Ant Task Usage +tags: [userdocs, tools] permalink: pmd_userdocs_tools_ant.html author: > David Dixon-Peugh <dpeugh@users.sourceforge.net>, @@ -76,8 +77,16 @@ Runs a set of static code analysis rules on some source code files and generates <td>cacheLocation</td> <td> The location of the analysis cache file to be used. - The cache can greatly improve analysis time without loosing analysis quality. - <b>It's use is strongly recommended.</b> + Setting this property enables Incremental Analysis, which can greatly improve analysis time without loosing analysis quality. + <b>Its use is strongly recommended.</b> + </td> + <td>No</td> + </tr> + <tr> + <td>noCache</td> + <td> + Setting this property to true disables Incremental Analysis, even if <i>cacheLocation</i> is provided. + You can use this to explicitly turn off suggestions to use incremental analysis, or for testing purposes. </td> <td>No</td> </tr> @@ -151,8 +160,7 @@ and the associated version (1.5, 1.6,...) The specific version of a language to be used for parsing is selected via the `sourceLanguage` nested element. Possible values are: - <sourceLanguage name="cpp" version=""/> - <sourceLanguage name="fortran" version=""/> + <sourceLanguage name="apex" version=""/> <sourceLanguage name="ecmascript" version="3"/> <sourceLanguage name="java" version="1.3"/> <sourceLanguage name="java" version="1.4"/> @@ -160,12 +168,13 @@ nested element. Possible values are: <sourceLanguage name="java" version="1.6"/> <sourceLanguage name="java" version="1.7"/> <sourceLanguage name="java" version="1.8"/> + <sourceLanguage name="java" version="9"/> <sourceLanguage name="jsp" version=""/> - <sourceLanguage name="php" version=""/> - <sourceLanguage name="ruby" version=""/> + <sourceLanguage name="pom" version=""/> <sourceLanguage name="plsql" version=""/> <sourceLanguage name="xsl" version=""/> <sourceLanguage name="xml" version=""/> + <sourceLanguage name="vf" version=""/> <sourceLanguage name="vm" version=""/> ### Postprocessing the report file with XSLT diff --git a/docs/pages/pmd/userdocs/ci.md b/docs/pages/pmd/userdocs/tools/ci.md similarity index 91% rename from docs/pages/pmd/userdocs/ci.md rename to docs/pages/pmd/userdocs/tools/ci.md index c84cff056af..5f2c6ea616b 100644 --- a/docs/pages/pmd/userdocs/ci.md +++ b/docs/pages/pmd/userdocs/tools/ci.md @@ -1,6 +1,7 @@ --- title: Continuous Integrations plugins -permalink: pmd_userdocs_ci.html +tags: [userdocs, tools] +permalink: pmd_userdocs_tools_ci.html author: Romain PELISSE <belaran@gmail.com> --- diff --git a/docs/pages/pmd/userdocs/tools/maven.md b/docs/pages/pmd/userdocs/tools/maven.md index 06057b1b2d0..1e184ba2046 100644 --- a/docs/pages/pmd/userdocs/tools/maven.md +++ b/docs/pages/pmd/userdocs/tools/maven.md @@ -1,5 +1,6 @@ --- title: Maven PMD Plugin +tags: [userdocs, tools] permalink: /pmd_userdocs_tools_maven.html last_updated: August 2017 author: > @@ -112,6 +113,20 @@ writing a ruleset can be found [here](pmd_userdocs_understanding_rulesets.html). Note that if you include other rulesets in your own rulesets, you have to be sure that the plugin will be able to resolve those other ruleset references. +#### Enabling Incremental Analysis + +When using the Maven PMD plugin 3.8 or later along with PMD 5.6.0 or later, you can enable incremental analysis to +speed up PMD's execution while retaining the quality of the analysis. You can additionally customize where the cache is stored:: + + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-pmd-plugin</artifactId> + <configuration> + <analysisCache>true</analysisCache> <!-- enable incremental analysis --> + <analysisCacheLocation>${project.build.directory}/pmd/pmd.cache</analysisCacheLocation> <!-- Optional: points to this location by default --> + </configuration> + </plugin> + #### Other configurations The Maven PMD plugin allows you to configure CPD, targetJDK, and the use of XRef to link @@ -139,7 +154,7 @@ Maven plugin will use and benefit from the latest bugfixes and enhancements: ``` xml <project> <properties> - <pmdVersion>...choose your version...</version> + <pmdVersion>...choose your version...</pmdVersion> </properties> ... <build> diff --git a/docs/pages/pmd/userdocs/tools.md b/docs/pages/pmd/userdocs/tools/tools.md similarity index 95% rename from docs/pages/pmd/userdocs/tools.md rename to docs/pages/pmd/userdocs/tools/tools.md index c76c5be90b3..37b9e32943f 100644 --- a/docs/pages/pmd/userdocs/tools.md +++ b/docs/pages/pmd/userdocs/tools/tools.md @@ -1,14 +1,24 @@ --- title: Tools / Integrations +tags: [userdocs, tools] + permalink: pmd_userdocs_tools.html author: David Dixon-Peugh <dpeugh@users.sourceforge.net> --- -* IDE plugins -* [Continuous Integrations plugins](/pmd_userdocs_ci.html) -* GUIs +## Automated Code Review + +### Codacy + +[Codacy](https://www.codacy.com/) automates code reviews and monitors code quality on every commit and pull request. +It gives visibility into the technical debt and it can track code style and security issues, code coverage, code duplication, cyclomatic complexity and enforce best practices. +Codacy is static analysis without the hassle. +With Codacy you have PMDJava analysis out-of-the-box, and it is free for open source projects. +* Homepage: [https://www.codacy.com/](https://www.codacy.com/) +* Source code: [https://github.com/codacy/codacy-pmdjava](https://github.com/codacy/codacy-pmdjava) +* Maintainer: Codacy ## IDE Integrations @@ -29,13 +39,6 @@ author: David Dixon-Peugh <dpeugh@users.sourceforge.net> <td><a href="http://tomcopeland.blogs.com/">Tom Copeland</a></td> </tr> - <tr> - <td><a href="#codacy">Codacy</a></td> - <td></td> - <td><a class="externalLink" href="https://github.com/codacy/codacy-pmdjava">github: codacy/codacy-pmdjava</a></td> - <td>Codacy</td> - </tr> - <tr> <td><a href="#code-guide">CodeGuide</a></td> <td></td> @@ -49,6 +52,13 @@ author: David Dixon-Peugh <dpeugh@users.sourceforge.net> <td><a href="https://github.com/pmd/pmd-eclipse-plugin">github: pmd/pmd-eclipse</a></td> <td>Philippe Herlin</td> </tr> + + <tr> + <td>qa-Eclipse</td> + <td></td> + <td><a href="https://github.com/ChristianWulf/qa-eclipse-plugin">qa-Eclipse</a></td> + <td>Christian Wulf</td> + </tr> <tr> <td>eclipse-pmd</td> @@ -158,12 +168,6 @@ the [PMDExtension jar file](http://sourceforge.net/projects/pmd/files/pmd-bluej/ and place it in your `bluej/lib/extensions/` directory. -### Codacy - -Although it is not an IDE, with [Codacy](https://www.codacy.com/) you have PMDJava analysis out-of-the-box, -and it is free for open source projects. - - ### Code Guide Here's how to set up PMD with Omnicore's CodeGuide: diff --git a/docs/pages/pmd/userdocs/understanding_rulesets.md b/docs/pages/pmd/userdocs/understanding_rulesets.md deleted file mode 100644 index cf9ea363c6e..00000000000 --- a/docs/pages/pmd/userdocs/understanding_rulesets.md +++ /dev/null @@ -1,69 +0,0 @@ ---- -title: Understanding Rulesets -permalink: pmd_userdocs_understanding_rulesets.html -summary: Rulesets are collections of rules -last_updated: September 2017 ---- - -There are two major use cases: - -1. When defining a new rule, the rule needs to be defined in a ruleset. PMD's built-in rules - are defined in specific rulesets from which the rule reference documentation is generated, - see [Java Rules](pmd_rules_java.html) for an example. - - Similar rules are grouped together into the same ruleset, like the [Java Braces Ruleset](pmd_rules_java_braces.html) - which contains rules that all deal with missing braces. - -2. When executing PMD you need to tell, which rules should be executed. You could directly point to the - built-in rulesets, but then you might be overwhelmed by the found violations. As described - in [Best Practices](pmd_userdocs_best_practices.html), it's better to define an own custom ruleset. - - With an own custom ruleset, you can: - - * Select the rules, that should be executed - * Adjust the rule properties to exactly meet your needs - -## Create a custom ruleset - -You start by creating a new XML file with the following contents: - -``` xml -<?xml version="1.0"?> - -<ruleset name="Custom Rules" - xmlns="http://pmd.sourceforge.net/ruleset/3.0.0" - xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/3.0.0 http://pmd.sourceforge.net/ruleset_3_0_0.xsd"> - <description> -Custom rules - </description> - -</ruleset> -``` - -Now start to add rule by **referencing** them. Let's say, you want to start with finding -[Empty Catch Blocks](pmd_rules_java_empty.html#emptycatchblock). Then you'd add the following -rule reference inside the `ruleset` elements: - -```xml - <rule ref="rulesets/java/empty.xml/EmptyCatchBlock" /> -``` - -## Adjusting rule properties - -If you want to be less strict with empty catch blocks, you could define that an exception variable name -of `ignored` will not raise a violation. Therefore you would reference the rule **and** define -the appropriate property value: - -```xml - <rule ref="rulesets/java/empty.xml/EmptyCatchBlock"> - <properties> - <property name="allowExceptionNameRegex"> - <value>^ignored$</value> - </property> - </properties> - </rule> -``` - - -{% include note.html content="More information about rulesets can be found on [Making Rulesets](pmd_devdocs_making_rulesets.html)." %} diff --git a/docs/pages/release_notes.md b/docs/pages/release_notes.md index c5dbb293eca..5baf27eb744 100644 --- a/docs/pages/release_notes.md +++ b/docs/pages/release_notes.md @@ -4,299 +4,46 @@ permalink: pmd_release_notes.html keywords: changelog, release notes --- -## ????? - 6.0.0-SNAPSHOT +## {{ site.pmd.date }} - {{ site.pmd.version }} -The PMD team is pleased to announce PMD 6.0.0. +The PMD team is pleased to announce PMD {{ site.pmd.version }}. -This is a major release. +This is a {{ site.pmd.release_type }} release. -### Table Of Contents - -* [New and noteworthy](#new-and-noteworthy) - * [Revamped Apex CPD](#revamped-apex-cpd) - * [Java Type Resolution](#java-type-resolution) - * [Metrics Framework](#metrics-framework) - * [Error Reporting](#error-reporting) - * [Java Symbol Table](#java-symbol-table) - * [Apex Parser Update](#apex-parser-update) - * [New Rules](#new-rules) - * [Modified Rules](#modified-rules) - * [Deprecated Rules](#deprecated-rules) - * [Removed Rules](#removed-rules) -* [Fixed Issues](#fixed-issues) -* [API Changes](#api-changes) -* [External Contributions](#external-contributions) +{% tocmaker is_release_notes_processor %} ### New and noteworthy -#### Revamped Apex CPD - -We are now using the Apex Jorje Lexer to tokenize Apex code for CPD. This change means: - -* All comments are now ignored for CPD. This is consistent with how other languages such as Java and Groovy work. -* Tokenization honors the language specification, which improves accuracy. - -CPD will therefore have less false positives and false negatives. - -#### Java Type Resolution - -As part of Google Summer of Code 2017, [Bendegúz Nagy](https://github.com/WinterGrascph)'s work on type resolution for Java continues. -For this release he has extended support for method calls for both instance and static methods. - -Method shadowing and overloading are supported, as are varargs. However, the selection of the target method upon the presence -of generics and type inference is still work in progress. Expect it in forecoming releases. - -As for fields, the basic support was in place for release 5.8.0, but has now been expanded to support static fields. - -#### Metrics Framework - -As part of Google Summer of Code 2017, [Clément Fournier](https://github.com/oowekyala) is continuing his work -on the new metrics framework for object-oriented metrics. - -There are already a couple of metrics (e.g. ATFD, WMC, Cyclo, LoC) implemented. More metrics are planned. -Based on those metrics, rules like "GodClass" detection can be implemented more easily. - -The Metrics framework has been abstracted and is available in `pmd-core` for other languages. With this -PMD release, the metrics framework is supported for both Java and Apex. - -#### Error Reporting - -A number of improvements on error reporting have taken place, meaning changes to some of the report formats. - -Also of note, the xml report now provides a XML Schema definition, allowing easier parsing and validation. - -##### Processing Errors - -Processing errors can now provide not only the message previously included on some reports, but also a full stacktrace. -This will allow better error reports when providing feedback to the PMD team and help in debugging issues. - -The report formats providing full stacktrace of errors are: - -* html -* summaryhtml -* textcolor -* vbhtml -* xml - -##### Configuration Errors - -For a long time reports have been notified of configuration errors on rules, but they have remained hidden. -On a push to make these more evident to users, and help them get the best results out of PMD, we have started -to include them on the reports. - -So far, only reports that include processing errors are showing configuration errors. In other words, the report formats -providing configuration error reporting are: - -* csv -* html -* summaryhtml -* text -* textcolor -* vbhtml -* xml - -As we move forward we will be able to detect and report more configuration errors (ie: incomplete `auxclasspath`) -and include them to such reports. - #### New Rules -* The rule `NcssCount` (ruleset `java-codesize`) replaces the three rules "NcssConstructorCount", "NcssMethodCount", - and "NcssTypeCount". The new rule uses the metrics framework to achieve the same. It has two properties, to - define the report level for method and class sizes separately. Constructors and methods are considered the same. - -* The new rule `ForLoopCanBeForeach` (ruleset `java-migration`) helps to identify those for-loops that can - be safely refactored into for-each-loops available since java 1.5. - -#### Modified Rules - -* The rule `UnnecessaryFinalModifier` (ruleset `java-unnecessarycode`) has been revamped to detect more cases. - It will now flag anonymous class' methods marked as final (can't be overridden, so it's pointless), along with - final methods overridden / defined within enum instances. - -* The rule `UnnecessaryParentheses` (ruleset `java-controversial`) has been merged into `UselessParentheses` - (ruleset `java-unnecessary`). The rule covers all scenarios previously covered by either rule. - -* The rule `UncommentedEmptyConstructor` (ruleset `java-design`) will now ignore empty constructors annotated with - `javax.inject.Inject`. - -* The rule `AbstractClassWithoutAnyMethod` (ruleset `java-design`) will now ignore classes annotated with - `com.google.auto.value.AutoValue`. - -* The rule `GodClass` (ruleset `java-design`) has been revamped to use the new metrics framework. - -#### Deprecated Rules - -* The rules `NcssConstructorCount`, `NcssMethodCount`, and `NcssTypeCount` (ruleset `java-codesize`) have been - deprecated. They will be replaced by the new rule `NcssCount` in the same ruleset. - -#### Removed Rules - -* The deprecated rule `UseSingleton` has been removed from the ruleset `java-design`. The rule has been renamed - long time ago to `UseUtilityClass`. - -#### Java Symbol Table - -A [bug in symbol table](https://github.com/pmd/pmd/pull/549/commits/0958621ca884a8002012fc7738308c8dfc24b97c) prevented -the symbol table analysis to properly match primitive arrays types. The issue [affected the `java-unsedcode/UnusedPrivateMethod`](https://github.com/pmd/pmd/issues/521) -rule, but other rules may now produce improved results as consequence of this fix. - -#### Apex Parser Update - -The Apex parser version was bumped, from `1.0-sfdc-187` to `1.0-sfdc-224`. This update let us take full advantage -of the latest improvements from Salesforce, but introduces some breaking changes: - -* `BlockStatements` are now created for all control structures, even if no brace is used. We have therefore added - a `hasCurlyBrace` method to differentiate between both scenarios. -* New AST node types are available. In particular `CastExpression`, `ConstructorPreamble`, `IllegalStoreExpression`, - `MethodBlockStatement`, `Modifier`, `MultiStatement`, `NestedExpression`, `NestedStoreExpression`, - `NewKeyValueObjectExpression` and `StatementExecuted` -* Some nodes have been removed. Such is the case of `TestNode`, `DottedExpression` and `NewNameValueObjectExpression` - (replaced by `NewKeyValueObjectExpression`) - -All existing rules have been updated to reflect these changes. If you have custom rules, be sure to update them. +* The new Java rule {% rule "java/codestyle/UseUnderscoresInNumericLiterals" %} (`java-codestyle`) + verifies that numeric literals over a given length (4 chars by default, but configurable) are using + underscores every 3 digits for readability. The rule only applies to Java 7+ codebases. ### Fixed Issues +* all + * [#1284](https://github.com/pmd/pmd/issues/1284): \[doc] Keep record of every currently deprecated API * all - * [#532](https://github.com/pmd/pmd/issues/532): \[core] security concerns on URL-based rulesets - * [#538](https://github.com/pmd/pmd/issues/538): \[core] Provide an XML Schema for XML reports - * [#600](https://github.com/pmd/pmd/issues/600): \[core] Nullpointer while creating cache File -* apex - * [#488](https://github.com/pmd/pmd/pull/488): \[apex] Use Apex lexer for CPD - * [#489](https://github.com/pmd/pmd/pull/489): \[apex] Update Apex compiler - * [#500](https://github.com/pmd/pmd/issues/500): \[apex] Running through CLI shows jorje optimization messages -* cpp - * [#448](https://github.com/pmd/pmd/issues/448): \[cpp] Write custom CharStream to handle continuation characters -* java - * [#1513](https://sourceforge.net/p/pmd/bugs/1513/): \[java] Remove deprecated rule UseSingleton - * [#487](https://github.com/pmd/pmd/pull/487): \[java] Fix typeresolution for anonymous extending object - * [#496](https://github.com/pmd/pmd/issues/496): \[java] processing error on generics inherited from enclosing class - * [#527](https://github.com/pmd/pmd/issues/527): \[java] Lombok getter annotation on enum is not recognized correctly -* java-basic - * [#565](https://github.com/pmd/pmd/pull/565): \[java] False negative on DontCallThreadRun when extending Thread -* java-comments - * [#536](https://github.com/pmd/pmd/issues/536): \[java] CommentDefaultAccessModifierRule ignores constructors -* java-controversial - * [#388](https://github.com/pmd/pmd/issues/388): \[java] controversial.AvoidLiteralsInIfCondition 0.0 false positive - * [#408](https://github.com/pmd/pmd/issues/408): \[java] DFA not analyzing asserts - * [#537](https://github.com/pmd/pmd/issues/537): \[java] UnnecessaryParentheses fails to detect obvious scenario -* java-design - * [#357](https://github.com/pmd/pmd/issues/357): \[java] UncommentedEmptyConstructor consider annotations on Constructor - * [#438](https://github.com/pmd/pmd/issues/438): \[java] Relax AbstractClassWithoutAnyMethod when class is annotated by @AutoValue - * [#590](https://github.com/pmd/pmd/issues/590): \[java] False positive on MissingStaticMethodInNonInstantiatableClass -* java-sunsecure - * [#468](https://github.com/pmd/pmd/issues/468): \[java] ArrayIsStoredDirectly false positive -* java-unusedcode - * [#521](https://github.com/pmd/pmd/issues/521): \[java] UnusedPrivateMethod returns false positives with primitive data type in map argument -* java-unnecessarycode - * [#412](https://github.com/pmd/pmd/issues/412): \[java] java-unnecessarycode/UnnecessaryFinalModifier missing cases + * [#1318](https://github.com/pmd/pmd/issues/1318): \[test] Kotlin DSL to ease test writing + * [#1341](https://github.com/pmd/pmd/issues/1341): \[doc] Documentation Error with Regex Properties +* java-codestyle + * [#1232](https://github.com/pmd/pmd/issues/1232): \[java] Detector for large numbers not separated by _ + * [#1372](https://github.com/pmd/pmd/issues/1372): \[java] false positive for UselessQualifiedThis ### API Changes -* The class `net.sourceforge.pmd.lang.dfa.NodeType` has been converted to an enum. - All node types are enum members now instead of int constants. The names for node types are retained. - -* The properties API (rule and report properties) have been revamped to be fully typesafe. This is everything - around `net.sourceforge.pmd.PropertyDescriptor`. - -* The rule classes `net.sourceforge.pmd.lang.apex.rule.apexunit.ApexUnitTestClassShouldHaveAsserts` - and `net.sourceforge.pmd.lang.apex.rule.apexunit.ApexUnitTestShouldNotUseSeeAllDataTrue` have been - renamed to `ApexUnitTestClassShouldHaveAssertsRule` and `ApexUnitTestShouldNotUseSeeAllDataTrueRule`, - respectively. This is to comply with the naming convention, that each rule class should be suffixed with "Rule". - This change has no impact on custom rulesets, since the rule names themselves didn't change. - -* The never implemented method `PMD.processFiles(PMDConfiguration, RuleSetFactory, Collection<File>, RuleContext, ProgressMonitor)` along with the interface `ProgressMonitor` has been removed. - -* All APIs deprecated in older versions are now removed. This includes: - * `Renderer.getPropertyDefinitions` - * `AbstractRenderer.defineProperty(String, String)` - * `AbstractRenderer.propertyDefinitions` - * `ReportListener` - * `Report.addListener(ReportListener)` - * `SynchronizedReportListener` - * `CPDConfiguration.CPDConfiguration(int, Language, String)` - * `CPDConfiguration.getRendererFromString(String)` - * `StreamUtil` - * `StringUtil.appendXmlEscaped(StringBuilder, String)` - * `StringUtil.htmlEncode(String)` - - -* Several methods in `net.sourceforge.pmd.util.CollectionUtil` have been deprecated and will be removed in PMD 7.0.0. In particular: - * `CollectionUtil.addWithoutDuplicates(T[], T)` - * `CollectionUtil.addWithoutDuplicates(T[], T[])` - * `CollectionUtil.areSemanticEquals(T[], T[])` - * `CollectionUtil.areEqual(Object, Object)` - * `CollectionUtil.arraysAreEqual(Object, Object)` - * `CollectionUtil.valuesAreTransitivelyEqual(Object[], Object[])` +* The implementation of the adapters for the XPath engines Saxon and Jaxen (package `net.sourceforge.pmd.lang.ast.xpath`) + are now deprecated. They'll be moved to an internal package come 7.0.0. Only `Attribute` remains public API. +### External Contributions -* Several methods in `net.sourceforge.pmd.util.StringUtil` have been deprecated and will be removed in PMD 7.0.0. In particular: - * `StringUtil.startsWithAny(String, String[])` - * `StringUtil.isNotEmpty(String)` - * `StringUtil.isEmpty(String)` - * `StringUtil.isMissing(String)` - * `StringUtil.areSemanticEquals(String, String)` - * `StringUtil.replaceString(String, String, String)` - * `StringUtil.replaceString(String, char, String)` - * `StringUtil.substringsOf(String, char)` - * `StringUtil.substringsOf(String, String)` - * `StringUtil.asStringOn(StringBuffer, Iterator, String)` - * `StringUtil.asStringOn(StringBuilder, Object[], String)` - * `StringUtil.lpad(String, int)` +* [#1384](https://github.com/pmd/pmd/pull/1384): \[java] New Rule - UseUnderscoresInNumericLiterals - [RajeshR](https://github.com/rajeshggwp) +* [#1424](https://github.com/pmd/pmd/pull/1424): \[doc] #1341 Updating Regex Values in default Value Property - [avishvat](https://github.com/vishva007) +* [#1428](https://github.com/pmd/pmd/pull/1428): \[core] Upgrading JCommander from 1.48 to 1.72 - [Thunderforge](https://github.com/Thunderforge) +* [#1430](https://github.com/pmd/pmd/pull/1430): \[doc] Who really knows regex? - [Dem Pilafian](https://github.com/dpilafian) -* The class `net.sourceforge.pmd.lang.java.typeresolution.typedefinition.JavaTypeDefinition` is now abstract, and has been enhanced - to provide several new methods. +{% endtocmaker %} -### External Contributions +{% include note.html content="The release notes of previous versions are available [here](pmd_release_notes_old.html)" %} -* [#420](https://github.com/pmd/pmd/pull/420): \[java] Fix UR anomaly in assert statements - [Clément Fournier](https://github.com/oowekyala) -* [#482](https://github.com/pmd/pmd/pull/482): \[java] Metrics testing framework + improved capabilities for metrics - [Clément Fournier](https://github.com/oowekyala) -* [#484](https://github.com/pmd/pmd/pull/484): \[core] Changed linux usage to a more unix like path - [patriksevallius](https://github.com/patriksevallius) -* [#486](https://github.com/pmd/pmd/pull/486): \[java] Add basic method typeresolution - [Bendegúz Nagy](https://github.com/WinterGrascph) -* [#492](https://github.com/pmd/pmd/pull/492): \[java] Typeresolution for overloaded methods - [Bendegúz Nagy](https://github.com/WinterGrascph) -* [#495](https://github.com/pmd/pmd/pull/495): \[core] Custom rule reinitialization code - [Clément Fournier](https://github.com/oowekyala) -* [#479](https://github.com/pmd/pmd/pull/479): \[core] Typesafe and immutable properties - [Clément Fournier](https://github.com/oowekyala) -* [#499](https://github.com/pmd/pmd/pull/499): \[java] Metrics memoization tests - [Clément Fournier](https://github.com/oowekyala) -* [#501](https://github.com/pmd/pmd/pull/501): \[java] Add support for most specific vararg method type resolution - [Bendegúz Nagy](https://github.com/WinterGrascph) -* [#502](https://github.com/pmd/pmd/pull/502): \[java] Add support for static field type resolution - [Bendegúz Nagy](https://github.com/WinterGrascph) -* [#505](https://github.com/pmd/pmd/pull/505): \[java] Followup on metrics - [Clément Fournier](https://github.com/oowekyala) -* [#506](https://github.com/pmd/pmd/pull/506): \[java] Add reduction rules to type inference - [Bendegúz Nagy](https://github.com/WinterGrascph) -* [#511](https://github.com/pmd/pmd/pull/511): \[core] Prepare abstraction of the metrics framework - [Clément Fournier](https://github.com/oowekyala) -* [#512](https://github.com/pmd/pmd/pull/512): \[java] Add incorporation to type inference - [Bendegúz Nagy](https://github.com/WinterGrascph) -* [#513](https://github.com/pmd/pmd/pull/513): \[java] Fix for maximally specific method selection - [Bendegúz Nagy](https://github.com/WinterGrascph) -* [#514](https://github.com/pmd/pmd/pull/514): \[java] Add static method type resolution - [Bendegúz Nagy](https://github.com/WinterGrascph) -* [#517](https://github.com/pmd/pmd/pull/517): \[doc] Metrics documentation - [Clément Fournier](https://github.com/oowekyala) -* [#518](https://github.com/pmd/pmd/pull/518): \[core] Properties refactoring: factorized enumerated property - [Clément Fournier](https://github.com/oowekyala) -* [#523](https://github.com/pmd/pmd/pull/523): \[java] Npath complexity metric and rule - [Clément Fournier](https://github.com/oowekyala) -* [#524](https://github.com/pmd/pmd/pull/524): \[java] Add support for explicit type arguments with method invocation - [Bendegúz Nagy](https://github.com/WinterGrascph) -* [#525](https://github.com/pmd/pmd/pull/525): \[core] Fix line ending and not ignored files issues - [Matias Comercio](https://github.com/MatiasComercio) -* [#528](https://github.com/pmd/pmd/pull/528): \[core] Fix typo - [Ayoub Kaanich](https://github.com/kayoub5) -* [#529](https://github.com/pmd/pmd/pull/529): \[java] Abstracted the Java metrics framework - [Clément Fournier](https://github.com/oowekyala) -* [#530](https://github.com/pmd/pmd/pull/530): \[java] Fix issue #527: Lombok getter annotation on enum is not recognized correctly - [Clément Fournier](https://github.com/oowekyala) -* [#533](https://github.com/pmd/pmd/pull/533): \[core] improve error message - [Dennis Kieselhorst](https://github.com/deki) -* [#535](https://github.com/pmd/pmd/pull/535): \[apex] Fix broken Apex visitor adapter - [Clément Fournier](https://github.com/oowekyala) -* [#542](https://github.com/pmd/pmd/pull/542): \[java] Metrics abstraction - [Clément Fournier](https://github.com/oowekyala) -* [#545](https://github.com/pmd/pmd/pull/545): \[apex] Apex metrics framework - [Clément Fournier](https://github.com/oowekyala) -* [#548](https://github.com/pmd/pmd/pull/548): \[java] Metrics documentation - [Clément Fournier](https://github.com/oowekyala) -* [#550](https://github.com/pmd/pmd/pull/550): \[java] Add basic resolution to type inference - [Bendegúz Nagy](https://github.com/WinterGrascph) -* [#553](https://github.com/pmd/pmd/pull/553): \[java] Refactored ParserTst into a static utility class + add getSourceFromClass - [Clément Fournier](https://github.com/oowekyala) -* [#554](https://github.com/pmd/pmd/pull/554): \[java] Fix #537: UnnecessaryParentheses fails to detect obvious scenario - [Clément Fournier](https://github.com/oowekyala) -* [#555](https://github.com/pmd/pmd/pull/555): \[java] Changed metrics/CyclomaticComplexityRule to use WMC when reporting classes - [Clément Fournier](https://github.com/oowekyala) -* [#556](https://github.com/pmd/pmd/pull/556): \[java] Fix #357: UncommentedEmptyConstructor consider annotations on Constructor - [Clément Fournier](https://github.com/oowekyala) -* [#557](https://github.com/pmd/pmd/pull/557): \[java] Fix NPath metric not counting ternaries correctly - [Clément Fournier](https://github.com/oowekyala) -* [#563](https://github.com/pmd/pmd/pull/563): \[java] Add support for basic method type inference for strict invocation - [Bendegúz Nagy](https://github.com/WinterGrascph) -* [#566](https://github.com/pmd/pmd/pull/566): \[java] New rule in migrating ruleset: ForLoopCanBeForeach - [Clément Fournier](https://github.com/oowekyala) -* [#567](https://github.com/pmd/pmd/pull/567): \[java] Last API change for metrics (metric options) - [Clément Fournier](https://github.com/oowekyala) -* [#570](https://github.com/pmd/pmd/pull/570): \[java] Model lower, upper and intersection types - [Bendegúz Nagy](https://github.com/WinterGrascph) -* [#573](https://github.com/pmd/pmd/pull/573): \[java] Data class rule - [Clément Fournier](https://github.com/oowekyala) -* [#576](https://github.com/pmd/pmd/pull/576): \[doc]\[java] Add hint for Guava users in InefficientEmptyStringCheck - [mmoehring](https://github.com/mmoehring) -* [#578](https://github.com/pmd/pmd/pull/578): \[java] Refactored god class rule - [Clément Fournier](https://github.com/oowekyala) -* [#579](https://github.com/pmd/pmd/pull/579): \[java] Update parsing to produce upper and lower bounds - [Bendegúz Nagy](https://github.com/WinterGrascph) -* [#580](https://github.com/pmd/pmd/pull/580): \[core] Add AbstractMetric to topple the class hierarchy of metrics - [Clément Fournier](https://github.com/oowekyala) -* [#581](https://github.com/pmd/pmd/pull/581): \[java] Relax AbstractClassWithoutAnyMethod when class is annotated by @AutoValue - [Niklas Baudy](https://github.com/vanniktech) -* [#583](https://github.com/pmd/pmd/pull/583): \[java] Documentation about writing metrics - [Clément Fournier](https://github.com/oowekyala) -* [#585](https://github.com/pmd/pmd/pull/585): \[java] Moved NcssCountRule to codesize.xml - [Clément Fournier](https://github.com/oowekyala) -* [#587](https://github.com/pmd/pmd/pull/587): \[core] Properties refactoring: Move static constants of ValueParser to class ValueParserConstants - [Clément Fournier](https://github.com/oowekyala) -* [#588](https://github.com/pmd/pmd/pull/588): \[java] XPath function to compute metrics - [Clément Fournier](https://github.com/oowekyala) -* [#598](https://github.com/pmd/pmd/pull/598): \[java] Fix #388: controversial.AvoidLiteralsInIfCondition 0.0 false positive - [Clément Fournier](https://github.com/oowekyala) -* [#620](https://github.com/pmd/pmd/pull/620): \[core] Moved properties to n.s.pmd.properties - [Clément Fournier](https://github.com/oowekyala) diff --git a/docs/pages/release_notes_old.md b/docs/pages/release_notes_old.md index 4278cd080db..d7110b64736 100644 --- a/docs/pages/release_notes_old.md +++ b/docs/pages/release_notes_old.md @@ -3,8 +3,1743 @@ title: Old Release Notes permalink: pmd_release_notes_old.html --- -Previous versions of PMD can be downloaded here: -http://sourceforge.net/projects/pmd/files/pmd/ +Previous versions of PMD can be downloaded here: https://github.com/pmd/pmd/releases + +## 28-October-2018 - 6.9.0 + +The PMD team is pleased to announce PMD 6.9.0. + +This is a minor release. + +### Table Of Contents + +* [New and noteworthy](#new-and-noteworthy) + * [Improved Golang CPD Support](#improved-golang-cpd-support) + * [New Rules](#new-rules) +* [Fixed Issues](#fixed-issues) +* [API Changes](#api-changes) +* [External Contributions](#external-contributions) + +### New and noteworthy + +#### Improved Golang CPD Support + +Thanks to the work of [ITBA](https://www.itba.edu.ar/) students [Matías Fraga](https://github.com/matifraga), +[Tomi De Lucca](https://github.com/tomidelucca) and [Lucas Soncini](https://github.com/lsoncini), +Golang is now backed by a proper Antlr Grammar. This means CPD is now better at detecting duplicates, +as comments are recognized as such and ignored. + +#### New Rules + +* The new PLSQL rule [`CodeFormat`](https://pmd.github.io/pmd-6.9.0/pmd_rules_plsql_codestyle.html#codeformat) (`plsql-codestyle`) verifies that + PLSQL code is properly formatted. It checks e.g. for correct indentation in select statements and verifies + that each parameter is defined on a separate line. + +### Fixed Issues + +* all + * [#649](https://github.com/pmd/pmd/issues/649): \[core] Exclude specific files from command line + * [#1272](https://github.com/pmd/pmd/issues/1272): \[core] Could not find or load main class when using symlinked run.sh + * [#1377](https://github.com/pmd/pmd/issues/1377): \[core] LanguageRegistry uses default class loader when invoking ServiceLocator + * [#1394](https://github.com/pmd/pmd/issues/1394): \[doc] How to configure "-cache <path>" + * [#1412](https://github.com/pmd/pmd/issues/1412): \[doc] Broken link to adding new cpd language documentation +* apex + * [#1396](https://github.com/pmd/pmd/issues/1396): \[apex] ClassCastException caused by Javadoc +* java + * [#1330](https://github.com/pmd/pmd/issues/1330): \[java] PMD crashes with java.lang.ClassFormatError: Absent Code attribute in method that is not native or abstract in class file javax/xml/ws/Service +* java-bestpractices + * [#1202](https://github.com/pmd/pmd/issues/1202): \[java] GuardLogStatement: "There is log block not surrounded by if" doesn't sound right + * [#1209](https://github.com/pmd/pmd/issues/1209): \[java] UnusedImports false positive for static import with package-private method usage + * [#1343](https://github.com/pmd/pmd/issues/1343): \[java] Update CommentDefaultAccessModifierRule to extend AbstractIgnoredAnnotationRule + * [#1365](https://github.com/pmd/pmd/issues/1365): \[java] JUnitTestsShouldIncludeAssert false positive + * [#1404](https://github.com/pmd/pmd/issues/1404): \[java] UnusedImports false positive with static ondemand import with method call +* java-codestyle + * [#1199](https://github.com/pmd/pmd/issues/1199): \[java] UnnecessaryFullyQualifiedName doesn't flag same package FQCNs + * [#1356](https://github.com/pmd/pmd/issues/1356): \[java] UnnecessaryModifier wrong message public-\>static +* java-design + * [#1369](https://github.com/pmd/pmd/issues/1369): \[java] Processing error (ClassCastException) if a TYPE\_USE annotation is used on a base class in the "extends" clause +* jsp + * [#1402](https://github.com/pmd/pmd/issues/1402): \[jsp] JspTokenManager has a problem about jsp scriptlet +* documentation + * [#1349](https://github.com/pmd/pmd/pull/1349): \[doc] Provide some explanation for WHY duplicate code is bad, like mutations + +### API Changes + +* PMD has a new CLI option `-ignorelist`. With that, you can provide a file containing a comma-delimit list of files, + that should be excluded during analysis. The ignorelist is applied after the files have been selected + via `-dir` or `-filelist`, which means, if the file is in both lists, then it will be ignored. + Note: there is no corresponding option for the Ant task, since the feature is already available via + Ant's FileSet include/exclude filters. + +### External Contributions + +* [#1338](https://github.com/pmd/pmd/pull/1338): \[core] \[cpd] Generalize ANTLR tokens preparing support for ANTLR token filter - [Matías Fraga](https://github.com/matifraga) and [Tomi De Lucca](https://github.com/tomidelucca) +* [#1361](https://github.com/pmd/pmd/pull/1361): \[doc] Update cpd.md with information about risks - [David M. Karr](https://github.com/davidmichaelkarr) +* [#1366](https://github.com/pmd/pmd/pull/1366): \[java] Static Modifier on Internal Interface pmd #1356 - [avishvat](https://github.com/vishva007) +* [#1368](https://github.com/pmd/pmd/pull/1368): \[doc] Updated outdated note in the building documentation. - [Maikel Steneker](https://github.com/maikelsteneker) +* [#1374](https://github.com/pmd/pmd/pull/1374): \[java] Simplify check for 'Test' annotation in JUnitTestsShouldIncludeAssertRule. - [Will Winder](https://github.com/winder) +* [#1375](https://github.com/pmd/pmd/pull/1375): \[java] Add missing null check AbstractJavaAnnotatableNode - [Will Winder](https://github.com/winder) +* [#1376](https://github.com/pmd/pmd/pull/1376): \[all] Upgrading Apache Commons IO from 2.4 to 2.6 - [Thunderforge](https://github.com/Thunderforge) +* [#1378](https://github.com/pmd/pmd/pull/1378): \[all] Upgrading Apache Commons Lang 3 from 3.7 to 3.8.1 - [Thunderforge](https://github.com/Thunderforge) +* [#1382](https://github.com/pmd/pmd/pull/1382): \[all] Replacing deprecated IO methods with ones that specify a charset - [Thunderforge](https://github.com/Thunderforge) +* [#1383](https://github.com/pmd/pmd/pull/1383): \[java] Improved message for GuardLogStatement rule - [Felix Lampe](https://github.com/fblampe) +* [#1386](https://github.com/pmd/pmd/pull/1386): \[go] \[cpd] Add CPD support for Antlr based grammar on Golang - [Matías Fraga](https://github.com/matifraga) +* [#1398](https://github.com/pmd/pmd/pull/1398): \[all] Upgrading SLF4J from 1.7.12 to 1.7.25 - [Thunderforge](https://github.com/Thunderforge) +* [#1400](https://github.com/pmd/pmd/pull/1400): \[java] Fix Issue 1343: Update CommentDefaultAccessModifierRule - [CrazyUnderdog](https://github.com/CrazyUnderdog) +* [#1401](https://github.com/pmd/pmd/pull/1401): \[all] Replacing IOUtils.closeQuietly(foo) with try-with-resources statements - [Thunderforge](https://github.com/Thunderforge) +* [#1406](https://github.com/pmd/pmd/pull/1406): \[jsp] Fix issue 1402: JspTokenManager has a problem about jsp scriptlet - [JustPRV](https://github.com/JustPRV) +* [#1411](https://github.com/pmd/pmd/pull/1411): \[core] Add ignore file path functionality - [Jon Moroney](https://github.com/darakian) +* [#1414](https://github.com/pmd/pmd/pull/1414): \[doc] Fix broken link. Fixes #1412 - [Johan Hammar](https://github.com/johanhammar) + + +## 30-September-2018 - 6.8.0 + +The PMD team is pleased to announce PMD 6.8.0. + +This is a minor release. + +### Table Of Contents + +* [New and noteworthy](#new-and-noteworthy) + * [Drawing a line between private and public API](#drawing-a-line-between-private-and-public-api) + * [`.internal` packages and `@InternalApi` annotation](#`.internal`-packages-and-`@internalapi`-annotation) + * [`@ReservedSubclassing`](#`@reservedsubclassing`) + * [`@Experimental`](#`@experimental`) + * [`@Deprecated`](#`@deprecated`) + * [The transition](#the-transition) + * [Quickstart Ruleset](#quickstart-ruleset) + * [New Rules](#new-rules) + * [Modified Rules](#modified-rules) + * [PLSQL](#plsql) +* [Fixed Issues](#fixed-issues) +* [API Changes](#api-changes) +* [External Contributions](#external-contributions) + +### New and noteworthy + +#### Drawing a line between private and public API + +Until now, all released public members and types were implicitly considered part +of PMD's public API, including inheritance-specific members (protected members, abstract methods). +We have maintained those APIs with the goal to preserve full binary compatibility between minor releases, +only breaking those APIs infrequently, for major releases. + +In order to allow PMD to move forward at a faster pace, this implicit contract will +be invalidated with PMD 7.0.0. We now introduce more fine-grained distinctions between +the type of compatibility support we guarantee for our libraries, and ways to make +them explicit to clients of PMD. + +###### `.internal` packages and `@InternalApi` annotation + +*Internal API* is meant for use *only* by the main PMD codebase. Internal types and methods +may be modified in any way, or even removed, at any time. + +Any API in a package that contains an `.internal` segment is considered internal. +The `@InternalApi` annotation will be used for APIs that have to live outside of +these packages, e.g. methods of a public type that shouldn't be used outside of PMD (again, +these can be removed anytime). + +###### `@ReservedSubclassing` + +Types marked with the `@ReservedSubclassing` annotation are only meant to be subclassed +by classes within PMD. As such, we may add new abstract methods, or remove protected methods, +at any time. All published public members remain supported. The annotation is *not* inherited, which +means a reserved interface doesn't prevent its implementors to be subclassed. + +###### `@Experimental` + +APIs marked with the `@Experimental` annotation at the class or method level are subject to change. +They can be modified in any way, or even removed, at any time. You should not use or rely + on them in any production code. They are purely to allow broad testing and feedback. + +###### `@Deprecated` + +APIs marked with the `@Deprecated` annotation at the class or method level will remain supported +until the next major release but it is recommended to stop using them. + +###### The transition + +*All currently supported APIs will remain so until 7.0.0*. All APIs that are to be moved to +`.internal` packages or hidden will be tagged `@InternalApi` before that major release, and +the breaking API changes will be performed in 7.0.0. + + +#### Quickstart Ruleset + +PMD 6.8.0 provides a first quickstart ruleset for Java, which you can use as a base ruleset to get your +custom ruleset started. You can reference it with `rulesets/java/quickstart.xml`. +You are strongly encouraged to [create your own ruleset](https://pmd.github.io/pmd-6.7.0/pmd_userdocs_making_rulesets.html) +though. + +The quickstart ruleset has the intention, to be useful out-of-the-box for many projects. Therefore it +references only rules, that are most likely to apply everywhere. + +Any feedback would be greatly appreciated. + + +#### New Rules + +* The new Apex rule [`ApexDoc`](https://pmd.github.io/pmd-6.8.0/pmd_rules_apex_documentation.html#apexdoc) (`apex-documentation`) + enforces the inclusion of ApexDoc on classes, interfaces, properties and methods; as well as some + sanity rules for such docs (no missing parameters, parameters' order, and return value). By default, + method overrides and test classes are allowed to not include ApexDoc. + + +#### Modified Rules + +* The rule [`MissingSerialVersionUID`](https://pmd.github.io/pmd-6.8.0/pmd_rules_java_errorprone.html#missingserialversionuid) (`java-errorprone`) has been modified + in order to recognize also missing `serialVersionUID` fields in abstract classes, if they are serializable. + Each individual class in the inheritance chain needs an own serialVersionUID field. See also [Should an abstract class have a serialVersionUID](https://stackoverflow.com/questions/893259/should-an-abstract-class-have-a-serialversionuid). + This change might lead to additional violations in existing code bases. + + +#### PLSQL + +The grammar for PLSQL has been revamped in order to fully parse `SELECT INTO`, `UPDATE`, and `DELETE` +statements. Previously such statements have been simply skipped ahead, now PMD is parsing them, giving access +to the individual parts of a SELECT-statement, such as the Where-Clause. This might produce new parsing errors +where PMD previously could successfully parse PLSQL code. If this happens, please report a new [issue](https://github.com/pmd/pmd/issues/new) to get this problem fixed. + + +### Fixed Issues + +* apex-bestpractices + * [#1348](https://github.com/pmd/pmd/issues/1348): \[apex] AvoidGlobalModifierRule gives warning even when its a webservice - false positive +* java-codestyle + * [#1329](https://github.com/pmd/pmd/issues/1329): \[java] FieldNamingConventions: false positive in serializable class with serialVersionUID + * [#1334](https://github.com/pmd/pmd/issues/1334): \[java] LinguisticNaming should support AtomicBooleans +* java-errorprone + * [#1350](https://github.com/pmd/pmd/issues/1350): \[java] MissingSerialVersionUID false-positive on interfaces + * [#1352](https://github.com/pmd/pmd/issues/1352): \[java] MissingSerialVersionUID false-negative with abstract classes +* java-performance + * [#1325](https://github.com/pmd/pmd/issues/1325): \[java] False positive in ConsecutiveLiteralAppends +* plsql + * [#1279](https://github.com/pmd/pmd/pull/1279): \[plsql] Support for SELECT INTO + + +### API Changes + +* A couple of methods and fields in `net.sourceforge.pmd.properties.AbstractPropertySource` have been + deprecated, as they are replaced by already existing functionality or expose internal implementation + details: `propertyDescriptors`, `propertyValuesByDescriptor`, + `copyPropertyDescriptors()`, `copyPropertyValues()`, `ignoredProperties()`, `usesDefaultValues()`, + `useDefaultValueFor()`. + +* Some methods in `net.sourceforge.pmd.properties.PropertySource` have been deprecated as well: + `usesDefaultValues()`, `useDefaultValueFor()`, `ignoredProperties()`. + +* The class `net.sourceforge.pmd.lang.rule.AbstractDelegateRule` has been deprecated and will + be removed with PMD 7.0.0. It is internally only in use by RuleReference. + +* The default constructor of `net.sourceforge.pmd.lang.rule.RuleReference` has been deprecated + and will be removed with PMD 7.0.0. RuleReferences should only be created by providing a Rule and + a RuleSetReference. Furthermore the following methods are deprecated: `setRuleReference()`, + `hasOverriddenProperty()`, `usesDefaultValues()`, `useDefaultValueFor()`. + + +### External Contributions + +* [#1309](https://github.com/pmd/pmd/pull/1309): \[core] \[CPD] Decouple Antlr Tokenizer implementation from any CPD language supported with Antlr - [Matías Fraga](https://github.com/matifraga) +* [#1314](https://github.com/pmd/pmd/pull/1314): \[apex] Add validation of ApexDoc comments - [Jeff Hube](https://github.com/jeffhube) +* [#1339](https://github.com/pmd/pmd/pull/1339): \[ci] Improve danger message - [BBG](https://github.com/djydewang) +* [#1340](https://github.com/pmd/pmd/pull/1340): \[java] Derive correct classname for non-public non-classes - [kris-scheibe](https://github.com/kris-scheibe) +* [#1357](https://github.com/pmd/pmd/pull/1357): \[doc] Improve Codacy description - [Daniel Reigada](https://github.com/DReigada) + + +## 02-September-2018 - 6.7.0 + +The PMD team is pleased to announce PMD 6.7.0. + +This is a minor release. + +### Table Of Contents + +* [New and noteworthy](#new-and-noteworthy) + * [Modified Rules](#modified-rules) + * [New Rules](#new-rules) + * [Deprecated Rules](#deprecated-rules) +* [Fixed Issues](#fixed-issues) +* [API Changes](#api-changes) +* [External Contributions](#external-contributions) + +### New and noteworthy + +#### Modified Rules + +* The Java rule {% rule java/bestpractices/OneDeclarationPerLine %} (`java-bestpractices`) has been revamped to + consider not only local variable declarations, but field declarations too. + +#### New Rules + +* 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 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. + +* The new Apex rule {% rule apex/codestyle/OneDeclarationPerLine %} (`apex-codestyle`) enforces declaring a + single field / variable per line; or per statement if the `strictMode` property is set. + It's an Apex equivalent of the already existing Java rule of the same name. + +#### Deprecated Rules + +* The Java rules {% rule java/codestyle/VariableNamingConventions %}, {% rule java/codestyle/MIsLeadingVariableName %}, + {% rule java/codestyle/SuspiciousConstantFieldName %}, and {% rule java/codestyle/AvoidPrefixingMethodParameters %} are + now deprecated, and will be removed with version 7.0.0. They are replaced by the more general + {% rule java/codestyle/FieldNamingConventions %}, {% rule java/codestyle/FormalParameterNamingConventions %}, and + {% rule java/codestyle/LocalVariableNamingConventions %}. + +### Fixed Issues + +* 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 + * [#1317](https://github.com/pmd/pmd/issues/1317): \[ci] Coveralls hasn't built the project since June 25th +* 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 + * [#1323](https://github.com/pmd/pmd/issues/1323): \[java] AvoidUsingHardCodedIP ignores match pattern + * [#1327](https://github.com/pmd/pmd/pull/1327): \[java] AvoidUsingHardCodedIP false positive for ":bee" +* java-codestyle + * [#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 + * [#1291](https://github.com/pmd/pmd/issues/1291): \[java] InvalidSlf4jMessageFormat false positive: too many arguments with string concatenation operator + * [#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 + * [#681](https://github.com/pmd/pmd/issues/681): \[plsql] Parse error with Cursor For Loop + +### API Changes + +* 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. + +* The nodes RUNSIGNEDSHIFT and RSIGNEDSHIFT are deprecated and will be removed from the AST with PMD 7.0.0. + These represented the operator of ShiftExpression in two cases out of three, but they're not needed and + make ShiftExpression inconsistent. The operator of a ShiftExpression is now accessible through + ShiftExpression#getOperator. + +### External Contributions + +* [#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) +* [#1315](https://github.com/pmd/pmd/pull/1315): \[apex] Add OneDeclarationPerStatement rule - [Jeff Hube](https://github.com/jeffhube) + + +## 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. + +This is a minor release. + +### Table Of Contents + +* [New and noteworthy](#new-and-noteworthy) + * [New Rules](#new-rules) + * [Modified Rules](#modified-rules) +* [Fixed Issues](#fixed-issues) +* [API Changes](#api-changes) +* [External Contributions](#external-contributions) + +### New and noteworthy + +#### New Rules + +* The new Apex rule [`AvoidNonExistentAnnotations`](pmd_rules_apex_errorprone.html#avoidnonexistentannotations) (`apex-errorprone`) + detects usages non-officially supported annotations. Apex supported non existent annotations for legacy reasons. + In the future, use of such non-existent annotations could result in broken Apex code that will not compile. + A full list of supported annotations can be found [here](https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_classes_annotation.htm) + +#### Modified Rules + +* The Java rule [UnnecessaryModifier](pmd_rules_java_codestyle.html#unnecessarymodifier) (`java-codestyle`) + now detects enum constrcutors with explicit `private` modifier. The rule now produces better error messages + letting you know exactly which modifiers are redundant at each declaration. + +### Fixed Issues +* all + * [#1119](https://github.com/pmd/pmd/issues/1119): \[doc] Make the landing page of the documentation website more useful + * [#1168](https://github.com/pmd/pmd/issues/1168): \[core] xml renderer schema definitions (#538) break included xslt files + * [#1173](https://github.com/pmd/pmd/issues/1173): \[core] Some characters in CPD are not shown correctly. + * [#1193](https://github.com/pmd/pmd/issues/1193): \[core] Designer doesn't start with run.sh +* ecmascript + * [#861](https://github.com/pmd/pmd/issues/861): \[ecmascript] InnaccurateNumericLiteral false positive with hex literals +* java + * [#1074](https://github.com/pmd/pmd/issues/1074): \[java] MissingOverrideRule exception when analyzing PMD under Java 9 + * [#1174](https://github.com/pmd/pmd/issues/1174): \[java] CommentUtil.multiLinesIn() could lead to StringIndexOutOfBoundsException +* java-bestpractices + * [#651](https://github.com/pmd/pmd/issues/651): \[java] SwitchStmtsShouldHaveDefault should be aware of enum types + * [#869](https://github.com/pmd/pmd/issues/869): \[java] GuardLogStatement false positive on return statements and Math.log +* java-codestyle + * [#667](https://github.com/pmd/pmd/issues/667): \[java] Make AtLeastOneConstructor Lombok-aware + * [#1154](https://github.com/pmd/pmd/pull/1154): \[java] CommentDefaultAccessModifierRule FP with nested enums + * [#1158](https://github.com/pmd/pmd/issues/1158): \[java] Fix IdenticalCatchBranches false positive + * [#1186](https://github.com/pmd/pmd/issues/1186): \[java] UnnecessaryFullyQualifiedName doesn't detect java.lang FQ names as violations +* java-design + * [#1200](https://github.com/pmd/pmd/issues/1200): \[java] New default NcssCount method report level is drastically reduced from values of deprecated NcssMethodCount and NcssTypeCount +* xml + * [#715](https://github.com/pmd/pmd/issues/715): \[xml] ProjectVersionAsDependencyVersion false positive + +### API Changes + +* The utility class `net.sourceforge.pmd.lang.java.ast.CommentUtil` has been deprecated and will be removed + with PMD 7.0.0. Its methods have been intended to parse javadoc tags. A more useful solution will be added + around the AST node `FormalComment`, which contains as children `JavadocElement` nodes, which in + turn provide access to the `JavadocTag`. + + All comment AST nodes (`FormalComment`, `MultiLineComment`, `SingleLineComment`) have a new method + `getFilteredComment()` which provide access to the comment text without the leading `/*` markers. + +* The method `AbstractCommentRule.tagsIndicesIn()` has been deprecated and will be removed with + PMD 7.0.0. It is not very useful, since it doesn't extract the information + in a useful way. You would still need check, which tags have been found, and with which + data they might be accompanied. + +### External Contributions + +* [#836](https://github.com/pmd/pmd/pull/836): \[apex] Add a rule to prevent use of non-existent annotations - [anand13s](https://github.com/anand13s) +* [#1159](https://github.com/pmd/pmd/pull/1159): \[ui] Allow to setup the auxclasspath in the designer - [Akshat Bahety](https://github.com/akshatbahety) +* [#1169](https://github.com/pmd/pmd/pull/1169): \[core] Update stylesheets with a default namespace - [Matthew Duggan](https://github.com/mduggan) +* [#1183](https://github.com/pmd/pmd/pull/1183): \[java] fixed typos in rule remediation - [Jake Hemmerle](https://github.com/jakehemmerle) +* [#1206](https://github.com/pmd/pmd/pull/1206): \[java] Recommend StringBuilder next to StringBuffer - [krichter722](https://github.com/krichter722) + + +## 29-May-2018 - 6.4.0 + +The PMD team is pleased to announce PMD 6.4.0. + +This is a minor release. + +### Table Of Contents + +* [New and noteworthy](#new-and-noteworthy) + * [Java 10 Support](#java-10-support) + * [XPath Type Resolution Functions](#xpath-type-resolution-functions) + * [New Rules](#new-rules) + * [Modified Rules](#modified-rules) +* [Fixed Issues](#fixed-issues) +* [API Changes](#api-changes) +* [External Contributions](#external-contributions) + +### New and noteworthy + +#### Java 10 Support + +PMD is now able to understand local-variable type inference as introduced by Java 10. +Simple type resolution features are available, e.g. the type of the variable `s` is inferred +correctly as `String`: + + var s = "Java 10"; + +#### XPath Type Resolution Functions + +For some time now PMD has supported Type Resolution, and exposed this functionality to XPath rules for the Java language +with the `typeof` function. This function however had a number of shortcomings: + +* It would take a first arg with the name to match if types couldn't be resolved. In all cases this was `@Image` + but was still required. +* It required 2 separate arguments for the Fully Qualified Class Name and the simple name of the class against + which to test. +* If only the Fully Qualified Class Name was provided, no simple name check was performed (not documented, + but abused on some rules to "fix" some false positives). + +In this release we are deprecating `typeof` in favor of a simpler `typeIs` function, which behaves exactly as the +old `typeof` when given all 3 arguments. + +`typeIs` receives a single parameter, which is the fully qualified name of the class to test against. + +So, calls such as: + +```ruby +//ClassOrInterfaceType[typeof(@Image, 'junit.framework.TestCase', 'TestCase')] +``` + +can now we expressed much more concisely as: + +```ruby +//ClassOrInterfaceType[typeIs('junit.framework.TestCase')] +``` + +With this change, we also allow to check against array types by just appending `[]` to the fully qualified class name. +These can be repeated for arrays of arrays (e.g. `byte[][]` or `java.lang.String[]`). + +Additionally, we introduce the companion function `typeIsExactly`, that receives the same parameters as `typeIs`, +but checks for exact type matches, without considering the type hierarchy. That is, the test +`typeIsExactly('junit.framework.TestCase')` will match only if the context node is an instance of `TestCase`, but +not if it's an instance of a subclass of `TestCase`. Be aware then, that using that method with abstract types will +never match. + +#### New Rules + +* The new Java rule [`HardCodedCryptoKey`](pmd_rules_java_security.html#hardcodedcryptokey) (`java-security`) + detects hard coded keys used for encryption. It is recommended to store keys outside of the source code. + +* The new Java rule [`IdenticalCatchBranches`](pmd_rules_java_codestyle.html#identicalcatchbranches) (`java-codestyle`) + finds catch blocks, + that catch different exception but perform the same exception handling and thus can be collapsed into a + multi-catch try statement. + +#### Modified Rules + +* The Java rule [JUnit4TestShouldUseTestAnnotation](pmd_rules_java_bestpractices.html#junit4testshouldusetestannotation) (`java-bestpractices`) + has a new parameter "testClassPattern". It is used to distinguish test classes from other classes and + avoid false positives. By default, any class, that has "Test" in its name, is considered a test class. + +* The Java rule [CommentDefaultAccessModifier](pmd_rules_java_codestyle.html#commentdefaultaccessmodifier) (`java-codestyle`) + allows now by default the comment "`/* package */` in addition to "`/* default */`. This behavior can + still be adjusted by setting the property `regex`. + +### Fixed Issues + +* all + * [#1018](https://github.com/pmd/pmd/issues/1018): \[java] Performance degradation of 250% between 6.1.0 and 6.2.0 + * [#1145](https://github.com/pmd/pmd/issues/1145): \[core] JCommander's help text for option -min is wrong +* java + * [#672](https://github.com/pmd/pmd/issues/672): \[java] Support exact type matches for type resolution from XPath + * [#743](https://github.com/pmd/pmd/issues/743): \[java] Prepare for Java 10 + * [#1077](https://github.com/pmd/pmd/issues/1077): \[java] Analyzing enum with lambda passed in constructor fails with "The enclosing scope must exist." + * [#1115](https://github.com/pmd/pmd/issues/1115): \[java] Simplify xpath typeof syntax + * [#1131](https://github.com/pmd/pmd/issues/1131): \[java] java.lang.ClassFormatError: Absent Code attribute in method that is not native or abstract in class file javax/faces/application/FacesMessage$Severity +* java-bestpractices + * [#527](https://github.com/pmd/pmd/issues/572): \[java] False Alarm of JUnit4TestShouldUseTestAnnotation on Predicates + * [#1063](https://github.com/pmd/pmd/issues/1063): \[java] MissingOverride is triggered in illegal places +* java-codestyle + * [#720](https://github.com/pmd/pmd/issues/720): \[java] ShortVariable should whitelist lambdas + * [#955](https://github.com/pmd/pmd/issues/955): \[java] Detect identical catch statements + * [#1114](https://github.com/pmd/pmd/issues/1114): \[java] Star import overwritten by explicit import is not correctly handled + * [#1064](https://github.com/pmd/pmd/issues/1064): \[java] ClassNamingConventions suggests to add Util suffix for simple exception wrappers + * [#1065](https://github.com/pmd/pmd/issues/1065): \[java] ClassNamingConventions shouldn't prohibit numbers in class names + * [#1067](https://github.com/pmd/pmd/issues/1067): \[java] \[6.3.0] PrematureDeclaration false-positive + * [#1096](https://github.com/pmd/pmd/issues/1096): \[java] ClassNamingConventions is too ambitious on finding utility classes +* java-design + * [#824](https://github.com/pmd/pmd/issues/824): \[java] UseUtilityClass false positive when extending + * [#1021](https://github.com/pmd/pmd/issues/1021): \[java] False positive for `DoNotExtendJavaLangError` + * [#1097](https://github.com/pmd/pmd/pull/1097): \[java] False negative in AvoidThrowingRawExceptionTypes +* java-performance + * [#1051](https://github.com/pmd/pmd/issues/1051): \[java] ConsecutiveAppendsShouldReuse false-negative + * [#1098](https://github.com/pmd/pmd/pull/1098): \[java] Simplify LongInstantiation, IntegerInstantiation, ByteInstantiation, and ShortInstantiation using type resolution + * [#1125](https://github.com/pmd/pmd/issues/1125): \[java] Improve message of InefficientEmptyStringCheck for String.trim().isEmpty() +* doc + * [#999](https://github.com/pmd/pmd/issues/999): \[doc] Add a header before the XPath expression in rules + * [#1082](https://github.com/pmd/pmd/issues/1082): \[doc] Multifile analysis doc is invalid +* vf-security + * [#1100](https://github.com/pmd/pmd/issues/1100): \[vf] URLENCODE is ignored as valid escape method + +### API Changes + +* The following classes in package `net.sourceforge.pmd.benchmark` have been deprecated: `Benchmark`, `Benchmarker`, + `BenchmarkReport`, `BenchmarkResult`, `RuleDuration`, `StringBuilderCR` and `TextReport`. Their API is not supported anymore + and is disconnected from the internals of PMD. Use the newer API based around `TimeTracker` instead, which can be found + in the same package. +* The class `net.sourceforge.pmd.lang.java.xpath.TypeOfFunction` has been deprecated. Use the newer `TypeIsFunction` in the same package. +* The `typeof` methdos in `net.sourceforge.pmd.lang.java.xpath.JavaFunctions` have been deprecated. + Use the newer `typeIs` method in the same class instead.. +* The methods `isA`, `isEither` and `isNeither` of `net.sourceforge.pmd.lang.java.typeresolution.TypeHelper`. + Use the new `isExactlyAny` and `isExactlyNone` methods in the same class instead. + + +### External Contributions + +* [#966](https://github.com/pmd/pmd/pull/966): \[java] Issue #955: add new rule to detect identical catch statement - [Clément Fournier](https://github.com/oowekyala) and [BBG](https://github.com/djydewang) +* [#1046](https://github.com/pmd/pmd/pull/1046): \[java] New security rule for finding hard-coded keys used for cryptographic operations - [Sergey Gorbaty](https://github.com/sgorbaty) +* [#1101](https://github.com/pmd/pmd/pull/1101): \[java] Fixes false positive for `DoNotExtendJavaLangError` - [Akshat Bahety](https://github.com/akshatbahety) +* [#1106](https://github.com/pmd/pmd/pull/1106): \[vf] URLENCODE is ignored as valid escape method - [Robert Sösemann](https://github.com/rsoesemann) +* [#1126](https://github.com/pmd/pmd/pull/1126): \[java] Improve implementation hint in InefficientEmptyStringCheck - [krichter722](https://github.com/krichter722) +* [#1129](https://github.com/pmd/pmd/pull/1129): \[java] Adjust InefficientEmptyStringCheck documentation - [krichter722](https://github.com/krichter722) +* [#1137](https://github.com/pmd/pmd/pull/1137): \[ui] Removes the need for RefreshAST - [Akshat Bahety](https://github.com/akshatbahety) + + + +## 29-April-2018 - 6.3.0 + +The PMD team is pleased to announce PMD 6.3.0. + +This is a minor release. + +### Table Of Contents + +* [New and noteworthy](#new-and-noteworthy) + * [Tree Traversal Revision](#tree-traversal-revision) + * [Naming Rules Enhancements](#naming-rules-enhancements) + * [CPD Suppression](#cpd-suppression) + * [Swift 4.1 Support](#swift-41-support) + * [New Rules](#new-rules) + * [Modified Rules](#modified-rules) + * [Deprecated Rules](#deprecated-rules) +* [Fixed Issues](#fixed-issues) +* [External Contributions](#external-contributions) + +### New and noteworthy + +#### Tree Traversal Revision + +As described in [#904](https://github.com/pmd/pmd/issues/904), when searching for child nodes of the AST methods +such as `hasDescendantOfType`, `getFirstDescendantOfType` and `findDescendantsOfType` were found to behave inconsistently, +not all of them honoring find boundaries; that is, nodes that define a self-contained entity which should be considered separately +(think of lambdas, nested classes, anonymous classes, etc.). We have modified these methods to ensure all of them honor +find boundaries. + +This change implies several false positives / unexpected results +(ie: `ASTBlockStatement` falsely returning `true` to `isAllocation()`) +have been fixed; and lots of searches are now restricted to smaller search areas, which improves performance +(depending on the project, we have measured up to 10% improvements during Type Resolution, Symbol Table analysis, +and some rules' application). + +#### Naming Rules Enhancements + +* [ClassNamingConventions](pmd_rules_java_codestyle.html#classnamingconventions) (`java-codestyle`) + has been enhanced to allow granular configuration of naming + conventions for different kinds of type declarations (eg enum or abstract + class). Each kind of declaration can use its own naming convention + using a regex property. See the rule's documentation for more info about + configuration and default conventions. + +* [MethodNamingConventions](pmd_rules_java_codestyle.html#methodnamingconventions) (`java-codestyle`) + has been enhanced in the same way. + +#### CPD Suppression + +Back in PMD 5.6.0 we introduced the ability to suppress CPD warnings in Java using comments, by +including `CPD-OFF` (to start ignoring code), or `CPD-ON` (to resume analysis) during CPD execution. +This has proved to be much more flexible and versatile than the old annotation-based approach, +and has since been the preferred way to suppress CPD warnings. + +On this occasion, we are extending support for comment-based suppressions to many other languages: + +* C/C++ +* Ecmascript / Javascript +* Matlab +* Objective-C +* PL/SQL +* Python + +So for instance, in Python we could now do: + +```python +class BaseHandler(object): + def __init__(self): + # some unignored code + + # tell cpd to start ignoring code - CPD-OFF + + # mission critical code, manually loop unroll + GoDoSomethingAwesome(x + x / 2); + GoDoSomethingAwesome(x + x / 2); + GoDoSomethingAwesome(x + x / 2); + GoDoSomethingAwesome(x + x / 2); + GoDoSomethingAwesome(x + x / 2); + GoDoSomethingAwesome(x + x / 2); + + # resume CPD analysis - CPD-ON + + # further code will *not* be ignored +``` + +Other languages are equivalent. + +#### Swift 4.1 Support + +Thanks to major contributions from [kenji21](https://github.com/kenji21) the Swift grammar has been updated to +support Swift 4.1. This is a major update, since the old grammar was quite dated, and we are sure all iOS +developers will enjoy it. + +Unfortunately, this change is not compatible. The grammar elements that have been removed (ie: the keywords `__FILE__`, +`__LINE__`, `__COLUMN__` and `__FUNCTION__`) are no longer supported. We don't usually introduce such +drastic / breaking changes in minor releases, however, given that the whole Swift ecosystem pushes hard towards +always using the latest versions, and that Swift needs all code and libraries to be currently compiling against +the same Swift version, we felt strongly this change was both safe and necessary to be shipped as soon as possible. +We had great feedback from the community during the process but if you have a legitimate use case for older Swift +versions, please let us know [on our Issue Tracker](https://github.com/pmd/pmd/issues). + +#### New Rules + +* The new Java rule [InsecureCryptoIv](pmd_rules_java_security.html#insecurecryptoiv) (`java-security`) + detects hard coded initialization vectors used in cryptographic operations. It is recommended to use + a randomly generated IV. + +#### Modified Rules + +* The Java rule [UnnecessaryConstructor](pmd_rules_java_codestyle.html#unnecessaryconstructor) (`java-codestyle`) + has been rewritten as a Java rule (previously it was a XPath-based rule). It supports a new property + `ignoredAnnotations` and ignores by default empty constructors, + that are annotated with `javax.inject.Inject`. Additionally, it detects now also unnecessary private constructors + in enums. + +* The property `checkNativeMethods` of the Java rule [MethodNamingConventions](pmd_rules_java_codestyle.html#methodnamingconventions) (`java-codestyle`) + is now deprecated, as it is now superseded by `nativePattern`. Support for that property will be maintained until + 7.0.0. + +* The Java rule [ControlStatementBraces](pmd_rules_java_codestyle.html#controlstatementbraces) (`java-codestyle`) + supports a new boolean property `checkSingleIfStmt`. When unset, the rule won't report `if` statements which lack + braces, if the statement is not part of an `if ... else if` chain. This property defaults to true. + +#### Deprecated Rules + +* The Java rule [AbstractNaming](pmd_rules_java_codestyle.html#abstractnaming) (`java-codestyle`) is deprecated + in favour of [ClassNamingConventions](pmd_rules_java_codestyle.html#classnamingconventions). + See [Naming rules enhancements](#naming-rules-enhancements). + +### Fixed Issues + +* all + * [#695](https://github.com/pmd/pmd/issues/695): \[core] Extend comment-based suppression to all JavaCC languages + * [#988](https://github.com/pmd/pmd/issues/988): \[core] FileNotFoundException for missing classes directory with analysis cache enabled + * [#1036](https://github.com/pmd/pmd/issues/1036): \[core] Non-XML output breaks XML-based CLI integrations +* apex-errorprone + * [#776](https://github.com/pmd/pmd/issues/776): \[apex] AvoidHardcodingId false positives +* documentation + * [#994](https://github.com/pmd/pmd/issues/994): \[doc] Delete duplicate page contributing.md on the website + * [#1057](https://github.com/pmd/pmd/issues/1057): \[doc] Documentation of ignoredAnnotations property is misleading +* java + * [#894](https://github.com/pmd/pmd/issues/894): \[java] Maven PMD plugin fails to process some files without any explanation + * [#899](https://github.com/pmd/pmd/issues/899): \[java] JavaTypeDefinitionSimple.toString can cause NPEs + * [#1020](https://github.com/pmd/pmd/issues/1020): \[java] The CyclomaticComplexity rule runs forever in 6.2.0 + * [#1030](https://github.com/pmd/pmd/pull/1030): \[java] NoClassDefFoundError when analyzing PMD with PMD + * [#1061](https://github.com/pmd/pmd/issues/1061): \[java] Update ASM to handle Java 10 bytecode +* java-bestpractices + * [#370](https://github.com/pmd/pmd/issues/370): \[java] GuardLogStatementJavaUtil not considering lambdas + * [#558](https://github.com/pmd/pmd/issues/558): \[java] ProperLogger Warnings for enums + * [#719](https://github.com/pmd/pmd/issues/719): \[java] Unused Code: Java 8 receiver parameter with an internal class + * [#1009](https://github.com/pmd/pmd/issues/1009): \[java] JUnitAssertionsShouldIncludeMessage - False positive with assertEquals and JUnit5 +* java-codestyle + * [#1003](https://github.com/pmd/pmd/issues/1003): \[java] UnnecessaryConstructor triggered on required empty constructor (Dagger @Inject) + * [#1023](https://github.com/pmd/pmd/issues/1023): \[java] False positive for useless parenthesis + * [#1004](https://github.com/pmd/pmd/issues/1004): \[java] ControlStatementBraces is missing checkIfStmt property +* java-design + * [#1056](https://github.com/pmd/pmd/issues/1056): \[java] Property ignoredAnnotations does not work for SingularField and ImmutableField +* java-errorprone + * [#629](https://github.com/pmd/pmd/issues/629): \[java] NullAssignment false positive + * [#816](https://github.com/pmd/pmd/issues/816): \[java] SingleMethodSingleton false positives with inner classes +* java-performance + * [#586](https://github.com/pmd/pmd/issues/586): \[java] AvoidUsingShortType erroneously triggered on overrides of 3rd party methods +* swift + * [#678](https://github.com/pmd/pmd/issues/678): \[swift]\[cpd] Exception when running for Swift 4 code (KeyPath) + +### External Contributions + +* [#778](https://github.com/pmd/pmd/pull/778): \[swift] Support Swift 4 grammar - [kenji21](https://github.com/kenji21) +* [#1002](https://github.com/pmd/pmd/pull/1002): \[doc] Delete duplicate page contributing.md on the website - [Ishan Srivastava](https://github.com/ishanSrt) +* [#1008](https://github.com/pmd/pmd/pull/1008): \[core] DOC: fix closing tag for <pmdVersion> - [stonio](https://github.com/stonio) +* [#1010](https://github.com/pmd/pmd/pull/1010): \[java] UnnecessaryConstructor triggered on required empty constructor (Dagger @Inject) - [BBG](https://github.com/djydewang) +* [#1012](https://github.com/pmd/pmd/pull/1012): \[java] JUnitAssertionsShouldIncludeMessage - False positive with assertEquals and JUnit5 - [BBG](https://github.com/djydewang) +* [#1024](https://github.com/pmd/pmd/pull/1024): \[java] Issue 558: Properlogger for enums - [Utku Cuhadaroglu](https://github.com/utkuc) +* [#1041](https://github.com/pmd/pmd/pull/1041): \[java] Make BasicProjectMemoizer thread safe. - [bergander](https://github.com/bergander) +* [#1042](https://github.com/pmd/pmd/pull/1042): \[java] New security rule: report usage of hard coded IV in crypto operations - [Sergey Gorbaty](https://github.com/sgorbaty) +* [#1044](https://github.com/pmd/pmd/pull/1044): \[java] Fix for issue #816 - [Akshat Bahety](https://github.com/akshatbahety) +* [#1048](https://github.com/pmd/pmd/pull/1048): \[core] Make MultiThreadProcessor more space efficient - [Gonzalo Exequiel Ibars Ingman](https://github.com/gibarsin) +* [#1062](https://github.com/pmd/pmd/pull/1062): \[core] Update ASM to version 6.1.1 - [Austin Shalit](https://github.com/AustinShalit) + + +## 26-March-2018 - 6.2.0 + +The PMD team is pleased to announce PMD 6.2.0. + +This is a minor release. + +### Table Of Contents + +* [New and noteworthy](#new-and-noteworthy) + * [Ecmascript (JavaScript)](#ecmascript-javascript) + * [Disable Incremental Analysis](#disable-incremental-analysis) + * [New Rules](#new-rules) + * [Modified Rules](#modified-rules) +* [Fixed Issues](#fixed-issues) +* [API Changes](#api-changes) +* [External Contributions](#external-contributions) + +### New and noteworthy + +#### Ecmascript (JavaScript) + +The [Rhino Library](https://github.com/mozilla/rhino) has been upgraded from version 1.7.7 to version 1.7.7.2. + +Detailed changes for changed in Rhino can be found: +* [For 1.7.7.2](https://github.com/mozilla/rhino/blob/master/RELEASE-NOTES.md#rhino-1772) +* [For 1.7.7.1](https://github.com/mozilla/rhino/blob/master/RELEASE-NOTES.md#rhino-1771) + +Both are bugfixing releases. + +#### Disable Incremental Analysis + +Some time ago, we added support for [Incremental Analysis](pmd_userdocs_getting_started.html). On PMD 6.0.0, we +started to add warns when not using it, as we strongly believe it's a great improvement to our user's experience as +analysis time is greatly reduced; and in the future we plan to have it enabled by default. However, we realize some +scenarios don't benefit from it (ie: CI jobs), and having the warning logged can be noisy and cause confusion. + +To this end, we have added a new flag to allow you to explicitly disable incremental analysis. On CLI, this is +the new `-no-cache` flag. On Ant, there is a `noCache` attribute for the `<pmd>` task. + +On both scenarios, disabling the cache takes precedence over setting a cache location. + +#### New Rules + +* The new Java rule [`MissingOverride`](pmd_rules_java_bestpractices.html#missingoverride) + (category `bestpractices`) detects overridden and implemented methods, which are not marked with the + `@Override` annotation. Annotating overridden methods with `@Override` ensures at compile time that + the method really overrides one, which helps refactoring and clarifies intent. + +* The new Java rule [`UnnecessaryAnnotationValueElement`](pmd_rules_java_codestyle.html#unnecessaryannotationvalueelement) + (category `codestyle`) detects annotations with a single element (`value`) that explicitely names it. + That is, doing `@SuppressWarnings(value = "unchecked")` would be flagged in favor of + `@SuppressWarnings("unchecked")`. + +* The new Java rule [`ControlStatementBraces`](pmd_rules_java_codestyle.html#controlstatementbraces) + (category `codestyle`) enforces the presence of braces on control statements where they are optional. + Properties allow to customize which statements are required to have braces. This rule replaces the now + deprecated rules `WhileLoopMustUseBraces`, `ForLoopMustUseBraces`, `IfStmtMustUseBraces`, and + `IfElseStmtMustUseBraces`. More than covering the use cases of those rules, this rule also supports + `do ... while` statements and `case` labels of `switch` statements (disabled by default). + +#### Modified Rules + +* The Java rule `CommentContentRule` (`java-documentation`) previously had the property `wordsAreRegex`. But this + property never had been implemented and is removed now. + +* The Java rule `UnusedPrivateField` (`java-bestpractices`) now has a new `ignoredAnnotations` property + that allows to configure annotations that imply the field should be ignored. By default `@java.lang.Deprecated` + and `@javafx.fxml.FXML` are ignored. + +* The Java rule `UnusedPrivateMethod` (`java-bestpractices`) now has a new `ignoredAnnotations` property + that allows to configure annotations that imply the method should be ignored. By default `@java.lang.Deprecated` + is ignored. + +* The Java rule `ImmutableField` (`java-design`) now has a new `ignoredAnnotations` property + that allows to configure annotations that imply the method should be ignored. By default several `lombok` + annotations are ignored + +* The Java rule `SingularField` (`java-design`) now has a new `ignoredAnnotations` property + that allows to configure annotations that imply the method should be ignored. By default several `lombok` + annotations are ignored + +#### Deprecated Rules + +* The Java rules `WhileLoopMustUseBraces`, `ForLoopMustUseBraces`, `IfStmtMustUseBraces`, and `IfElseStmtMustUseBraces` + are deprecated. They will be replaced by the new rule `ControlStatementBraces`, in the category `codestyle`. + +### Fixed Issues + +* all + * [#928](https://github.com/pmd/pmd/issues/928): \[core] PMD build failure on Windows +* java-bestpracrtices + * [#907](https://github.com/pmd/pmd/issues/907): \[java] UnusedPrivateField false-positive with @FXML + * [#963](https://github.com/pmd/pmd/issues/965): \[java] ArrayIsStoredDirectly not triggered from variadic functions +* java-codestyle + * [#974](https://github.com/pmd/pmd/issues/974): \[java] Merge \*StmtMustUseBraces rules + * [#983](https://github.com/pmd/pmd/issues/983): \[java] Detect annotations with single value element +* java-design + * [#832](https://github.com/pmd/pmd/issues/832): \[java] AvoidThrowingNullPointerException documentation suggestion + * [#837](https://github.com/pmd/pmd/issues/837): \[java] CFGs of declared but not called lambdas are treated as parts of an enclosing method's CFG + * [#839](https://github.com/pmd/pmd/issues/839): \[java] SignatureDeclareThrowsException's IgnoreJUnitCompletely property not honored for constructors + * [#968](https://github.com/pmd/pmd/issues/968): \[java] UseUtilityClassRule reports false positive with lombok NoArgsConstructor +* documentation + * [#978](https://github.com/pmd/pmd/issues/978): \[core] Broken link in CONTRIBUTING.md + * [#992](https://github.com/pmd/pmd/issues/992): \[core] Include info about rule doc generation in "Writing Documentation" md page + +### API Changes + +* A new CLI switch, `-no-cache`, disables incremental analysis and the related suggestion. This overrides the + `-cache` option. The corresponding Ant task parameter is `noCache`. + +* The static method `PMDParameters.transformParametersIntoConfiguration(PMDParameters)` is now deprecated, + for removal in 7.0.0. The new instance method `PMDParameters.toConfiguration()` replaces it. + +* The method `ASTConstructorDeclaration.getParameters()` has been deprecated in favor of the new method + `getFormalParameters()`. This method is available for both `ASTConstructorDeclaration` and + `ASTMethodDeclaration`. + +### External Contributions + +* [#941](https://github.com/pmd/pmd/pull/941): \[java] Use char notation to represent a character to improve performance - [reudismam](https://github.com/reudismam) +* [#943](https://github.com/pmd/pmd/pull/943): \[java] UnusedPrivateField false-positive with @FXML - [BBG](https://github.com/djydewang) +* [#951](https://github.com/pmd/pmd/pull/951): \[java] Add ignoredAnnotations property to unusedPrivateMethod rule - [BBG](https://github.com/djydewang) +* [#952](https://github.com/pmd/pmd/pull/952): \[java] SignatureDeclareThrowsException's IgnoreJUnitCompletely property not honored for constructors - [BBG](https://github.com/djydewang) +* [#958](https://github.com/pmd/pmd/pull/958): \[java] Refactor how we ignore annotated elements in rules - [BBG](https://github.com/djydewang) +* [#965](https://github.com/pmd/pmd/pull/965): \[java] Make Varargs trigger ArrayIsStoredDirectly - [Stephen](https://github.com/pmd/pmd/issues/907) +* [#967](https://github.com/pmd/pmd/pull/967): \[doc] Issue 959: fixed broken link to XPath Rule Tutorial - [Andrey Mochalov](https://github.com/epidemia) +* [#969](https://github.com/pmd/pmd/pull/969): \[java] Issue 968 Add logic to handle lombok private constructors with utility classes - [Kirk Clemens](https://github.com/clem0110) +* [#970](https://github.com/pmd/pmd/pull/970): \[java] Fixed inefficient use of keySet iterator instead of entrySet iterator - [Andrey Mochalov](https://github.com/epidemia) +* [#984](https://github.com/pmd/pmd/pull/984): \[java] issue983 Add new UnnecessaryAnnotationValueElement rule - [Kirk Clemens](https://github.com/clem0110) +* [#989](https://github.com/pmd/pmd/pull/989): \[core] Update Contribute.md to close Issue #978 - [Bolarinwa Saheed Olayemi](https://github.com/refactormyself) +* [#990](https://github.com/pmd/pmd/pull/990): \[java] Updated Doc on AvoidThrowingNullPointerException to close Issue #832 - [Bolarinwa Saheed Olayemi](https://github.com/refactormyself) +* [#993](https://github.com/pmd/pmd/pull/993): \[core] Update writing_documentation.md to fix Issue #992 - [Bolarinwa Saheed Olayemi](https://github.com/refactormyself) + + +## 25-February-2018 - 6.1.0 + +The PMD team is pleased to announce PMD 6.1.0. + +This is a minor release. + +### Table Of Contents + +* [New and noteworthy](#new-and-noteworthy) + * [Designer UI](#designer-ui) +* [Fixed Issues](#fixed-issues) +* [API Changes](#api-changes) + * [Changes to the Node interface](#changes-to-the-node-interface) + * [Changes to CPD renderers](#changes-to-cpd-renderers) +* [External Contributions](#external-contributions) + +### New and noteworthy + +#### Designer UI + +The Designer now supports configuring properties for XPath based rule development. +The Designer is still under development and any feedback is welcome. + +You can start the designer via `run.sh designer` or `designer.bat`. + +### Fixed Issues + +* all + * [#569](https://github.com/pmd/pmd/issues/569): \[core] XPath support requires specific toString implementations + * [#795](https://github.com/pmd/pmd/issues/795): \[cpd] java.lang.OutOfMemoryError + * [#848](https://github.com/pmd/pmd/issues/848): \[doc] Test failures when building pmd-doc under Windows + * [#872](https://github.com/pmd/pmd/issues/872): \[core] NullPointerException at FileDataSource.glomName() + * [#854](https://github.com/pmd/pmd/issues/854): \[ci] Use Java9 for building PMD +* doc + * [#791](https://github.com/pmd/pmd/issues/791): \[doc] Documentation site reorganisation + * [#891](https://github.com/pmd/pmd/issues/891): \[doc] Apex @SuppressWarnings should use single quotes instead of double quotes + * [#909](https://github.com/pmd/pmd/issues/909): \[doc] Please add new PMD Eclipse Plugin to tool integration section +* java + * [#825](https://github.com/pmd/pmd/issues/825): \[java] Excessive\*Length ignores too much + * [#888](https://github.com/pmd/pmd/issues/888): \[java] ParseException occurs with valid '<>' in Java 1.8 mode + * [#920](https://github.com/pmd/pmd/pull/920): \[java] Update valid identifiers in grammar +* java-bestpractices + * [#784](https://github.com/pmd/pmd/issues/784): \[java] ForLoopCanBeForeach false-positive + * [#925](https://github.com/pmd/pmd/issues/925): \[java] UnusedImports false positive for static import +* java-design + * [#855](https://github.com/pmd/pmd/issues/855): \[java] ImmutableField false-positive with lambdas +* java-documentation + * [#877](https://github.com/pmd/pmd/issues/877): \[java] CommentRequired valid rule configuration causes PMD error +* java-errorprone + * [#885](https://github.com/pmd/pmd/issues/885): \[java] CompareObjectsWithEqualsRule trigger by enum1 != enum2 +* java-performance + * [#541](https://github.com/pmd/pmd/issues/541): \[java] ConsecutiveLiteralAppends with types other than string +* scala + * [#853](https://github.com/pmd/pmd/issues/853): \[scala] Upgrade scala version to support Java 9 +* xml + * [#739](https://github.com/pmd/pmd/issues/739): \[xml] IllegalAccessException when accessing attribute using Saxon on JRE 9 + + +### API Changes + +#### Changes to the Node interface + +The method `getXPathNodeName` is added to the `Node` interface, which removes the +use of the `toString` of a node to get its XPath element name (see [#569](https://github.com/pmd/pmd/issues/569)). +A default implementation is provided in `AbstractNode`, to stay compatible +with existing implementors. + +The `toString` method of a Node is not changed for the time being, and still produces +the name of the XPath node. That behaviour may however change in future major releases, +e.g. to produce a more useful message for debugging. + +#### Changes to CPD renderers + +The interface `net.sourceforge.pmd.cpd.Renderer` has been deprecated. A new interface `net.sourceforge.pmd.cpd.renderer.CPDRenderer` +has been introduced to replace it. The main difference is that the new interface is meant to render directly to a `java.io.Writer` +rather than to a String. This allows to greatly reduce the memory footprint of CPD, as on large projects, with many duplications, +it was causing `OutOfMemoryError`s (see [#795](https://github.com/pmd/pmd/issues/795)). + +`net.sourceforge.pmd.cpd.FileReporter` has also been deprecated as part of this change, as it's no longer needed. + +### External Contributions + +* [#790](https://github.com/pmd/pmd/pull/790): \[java] Added some comments for JDK 9 - [Tobias Weimer](https://github.com/tweimer) +* [#803](https://github.com/pmd/pmd/pull/803): \[doc] Added SpotBugs as successor of FindBugs - [Tobias Weimer](https://github.com/tweimer) +* [#828](https://github.com/pmd/pmd/pull/828): \[core] Add operations to manipulate a document - [Gonzalo Ibars Ingman](https://github.com/gibarsin) +* [#830](https://github.com/pmd/pmd/pull/830): \[java] UseArraysAsList: Description added - [Tobias Weimer](https://github.com/tweimer) +* [#845](https://github.com/pmd/pmd/pull/845): \[java] Fix false negative PreserveStackTrace on string concatenation - [Alberto Fernández](https://github.com/albfernandez) +* [#868](https://github.com/pmd/pmd/pull/868): \[core] Improve XPath documentation && make small refactors - [Gonzalo Ibars Ingman](https://github.com/gibarsin) +* [#875](https://github.com/pmd/pmd/pull/875): \[core] Support shortnames when using filelist - [John Zhang](https://github.com/johnjiabinzhang) +* [#886](https://github.com/pmd/pmd/pull/886): \[java] Fix #885 - [Matias Comercio](https://github.com/MatiasComercio) +* [#900](https://github.com/pmd/pmd/pull/900): \[core] Use the isEmpty method instead of comparing the value of size() to 0 - [reudismam](https://github.com/reudismam) +* [#914](https://github.com/pmd/pmd/pull/914): \[doc] Apex @SuppressWarnings documentation updated - [Akshat Bahety](https://github.com/akshatbahety) +* [#918](https://github.com/pmd/pmd/pull/918): \[doc] Add qa-eclipse as new tool - [Akshat Bahety](https://github.com/akshatbahety) +* [#927](https://github.com/pmd/pmd/pull/927): \[java]\[doc] Fix example of AbstractClassWithoutAnyMethod - [Kazuma Watanabe](https://github.com/wata727) + + +## 21-January-2018 - 6.0.1 + +The PMD team is pleased to announce PMD 6.0.1. + +This is a bug fixing release. + +### Table Of Contents + +* [Additional information about the new introduced rule categories](#additional-information-about-the-new-introduced-rule-categories) +* [Fixed Issues](#fixed-issues) +* [API Changes](#api-changes) +* [External Contributions](#external-contributions) + +### Additional information about the new introduced rule categories + +With the release of PMD 6.0.0, all rules have been sorted into one of the following eight categories: + +1. **Best Practices**: These are rules which enforce generally accepted best practices. +2. **Code Style**: These rules enforce a specific coding style. +3. **Design**: Rules that help you discover design issues. +4. **Documentation**: These rules are related to code documentation. +5. **Error Prone**: Rules to detect constructs that are either broken, extremely confusing or prone to runtime errors. +6. **Multithreading**: These are rules that flag issues when dealing with multiple threads of execution. +7. **Performance**: Rules that flag suboptimal code. +8. **Security**: Rules that flag potential security flaws. + +Please note, that not every category in every language may have a rule. There might be categories with no +rules at all, such as `category/java/security.xml`, which has currently no rules. +There are even languages, which only have rules of one category (e.g. `category/xml/errorprone.xml`). + +You can find the information about available rules in the generated rule documentation, available +at <https://pmd.github.io/6.0.1/>. + +In order to help migrate to the new category scheme, the new name for the old, deprecated rule names will +be logged as a warning. See [PR #865](https://github.com/pmd/pmd/pull/865). Please note, that the deprecated +rule names will keep working throughout PMD 6. You can upgrade to PMD 6 without the immediate need +to migrate your current ruleset. That backwards compatibility will be maintained until PMD 7.0.0 is released. + +### Fixed Issues + +* all + * [#842](https://github.com/pmd/pmd/issues/842): \[core] Use correct java bootclasspath for compiling +* apex-errorprone + * [#792](https://github.com/pmd/pmd/issues/792): \[apex] AvoidDirectAccessTriggerMap incorrectly detects array access in classes +* apex-security + * [#788](https://github.com/pmd/pmd/issues/788): \[apex] Method chaining breaks ApexCRUDViolation +* doc + * [#782](https://github.com/pmd/pmd/issues/782): \[doc] Wrong information in the Release Notes about the Security ruleset + * [#794](https://github.com/pmd/pmd/issues/794): \[doc] Broken documentation links for 6.0.0 +* java + * [#793](https://github.com/pmd/pmd/issues/793): \[java] Parser error with private method in nested classes in interfaces + * [#814](https://github.com/pmd/pmd/issues/814): \[java] UnsupportedClassVersionError is failure instead of a warning + * [#831](https://github.com/pmd/pmd/issues/831): \[java] StackOverflow in JavaTypeDefinitionSimple.toString +* java-bestpractices + * [#783](https://github.com/pmd/pmd/issues/783): \[java] GuardLogStatement regression + * [#800](https://github.com/pmd/pmd/issues/800): \[java] ForLoopCanBeForeach NPE when looping on `this` object +* java-codestyle + * [#817](https://github.com/pmd/pmd/issues/817): \[java] UnnecessaryModifierRule crashes on valid code +* java-design + * [#785](https://github.com/pmd/pmd/issues/785): \[java] NPE in DataClass rule + * [#812](https://github.com/pmd/pmd/issues/812): \[java] Exception applying rule DataClass + * [#827](https://github.com/pmd/pmd/issues/827): \[java] GodClass crashes with java.lang.NullPointerException +* java-performance + * [#841](https://github.com/pmd/pmd/issues/841): \[java] InsufficientStringBufferDeclaration NumberFormatException +* java-typeresolution + * [#866](https://github.com/pmd/pmd/issues/866): \[java] rulesets/java/typeresolution.xml lists non-existent rules + +### API Changes + +* The constant `net.sourceforge.pmd.PMD.VERSION` has been deprecated and will be removed with PMD 7.0.0. + Please use `net.sourceforge.pmd.PMDVersion.VERSION` instead. + +### External Contributions + +* [#796](https://github.com/pmd/pmd/pull/796): \[apex] AvoidDirectAccessTriggerMap incorrectly detects array access in classes - [Robert Sösemann](https://github.com/up2go-rsoesemann) +* [#799](https://github.com/pmd/pmd/pull/799): \[apex] Method chaining breaks ApexCRUDViolation - [Robert Sösemann](https://github.com/up2go-rsoesemann) + + +## 15-December-2017 - 6.0.0 + +The PMD team is pleased to announce PMD 6.0.0. + +This is a major release. + +### Table Of Contents + +* [New and noteworthy](#new-and-noteworthy) + * [New Rule Designer](#new-rule-designer) + * [Java 9 support](#java-9-support) + * [Revamped Apex CPD](#revamped-apex-cpd) + * [Java Type Resolution](#java-type-resolution) + * [Metrics Framework](#metrics-framework) + * [Error Reporting](#error-reporting) + * [Apex Rule Suppression](#apex-rule-suppression) + * [Rule Categories](#rule-categories) + * [New Rules](#new-rules) + * [Modified Rules](#modified-rules) + * [Deprecated Rules](#deprecated-rules) + * [Removed Rules](#removed-rules) + * [Java Symbol Table](#java-symbol-table) + * [Apex Parser Update](#apex-parser-update) + * [Incremental Analysis](#incremental-analysis) + * [Rule and Report Properties](#rule-and-report-properties) +* [Fixed Issues](#fixed-issues) +* [API Changes](#api-changes) +* [External Contributions](#external-contributions) + +### New and noteworthy + +#### New Rule Designer + +Thanks to [Clément Fournier](https://github.com/oowekyala), we now have a new rule designer GUI, which +is based on JavaFX. It replaces the old designer and can be started via + +* `bin/run.sh designer` (on Unix-like platform such as Linux and Mac OS X) +* `bin\designer.bat` (on Windows) + +Note: At least Java8 is required for the designer. The old designer is still available +as `designerold` but will be removed with the next major release. + +#### Java 9 support + +The Java grammar has been updated to support analyzing Java 9 projects: + +* private methods in interfaces are possible +* The underscore "\_" is considered an invalid identifier +* Diamond operator for anonymous classes +* The module declarations in `module-info.java` can be parsed +* Concise try-with-resources statements are supported + +Java 9 support is enabled by default. You can switch back to an older java version +via the command line, e.g. `-language java -version 1.8`. + +#### Revamped Apex CPD + +We are now using the Apex Jorje Lexer to tokenize Apex code for CPD. This change means: + +* All comments are now ignored for CPD. This is consistent with how other languages such as Java and Groovy work. +* Tokenization honors the language specification, which improves accuracy. + +CPD will therefore have less false positives and false negatives. + +#### Java Type Resolution + +As part of Google Summer of Code 2017, [Bendegúz Nagy](https://github.com/WinterGrascph) worked on type resolution +for Java. For this release he has extended support for method calls for both instance and static methods. + +Method shadowing and overloading are supported, as are varargs. However, the selection of the target method upon +the presence of generics and type inference is still work in progress. Expect it in forecoming releases. + +As for fields, the basic support was in place for release 5.8.0, but has now been expanded to support static fields. + +#### Metrics Framework + +As part of Google Summer of Code 2017, [Clément Fournier](https://github.com/oowekyala) is worked +on the new metrics framework for object-oriented metrics. + +There are already a couple of metrics (e.g. ATFD, WMC, Cyclo, LoC) implemented. More metrics are planned. +Based on those metrics, rules like "GodClass" detection could be implemented more easily. +The following rules benefit from the metrics framework: NcssCount (java), NPathComplexity (java), +CyclomaticComplexity (both java and apex). + +The Metrics framework has been abstracted and is available in `pmd-core` for other languages. With this +PMD release, the metrics framework is supported for both Java and Apex. + +#### Error Reporting + +A number of improvements on error reporting have taken place, meaning changes to some of the report formats. + +Also of note, the xml report now provides a XML Schema definition, allowing easier parsing and validation. + +##### Processing Errors + +Processing errors can now provide not only the message previously included on some reports, but also a full stacktrace. +This will allow better error reports when providing feedback to the PMD team and help in debugging issues. + +The report formats providing full stacktrace of errors are: + +* html +* summaryhtml +* textcolor +* vbhtml +* xml + +##### Configuration Errors + +For a long time reports have been notified of configuration errors on rules, but they have remained hidden. +On a push to make these more evident to users, and help them get the best results out of PMD, we have started +to include them on the reports. + +So far, only reports that include processing errors are showing configuration errors. In other words, the report formats +providing configuration error reporting are: + +* csv +* html +* summaryhtml +* text +* textcolor +* vbhtml +* xml + +As we move forward we will be able to detect and report more configuration errors (ie: incomplete `auxclasspath`) +and include them to such reports. + +#### Apex Rule Suppression + +Apex violations can now be suppressed very similarly to how it's done in Java, by making use of a +`@SuppressWarnings` annotation. + +Supported syntax includes: + +``` +@SupressWarnings('PMD') // to supress all Apex rules +@SupressWarnings('all') // to supress all Apex rules +@SupressWarnings('PMD.ARuleName') // to supress only the rule named ARuleName +@SupressWarnings('PMD.ARuleName, PMD.AnotherRuleName') // to supress only the rule named ARuleName or AnotherRuleName +``` + +Notice this last scenario is slightly different to the Java syntax. This is due to differences in the Apex grammar for annotations. + +#### Rule Categories + +All built-in rules have been sorted into one of eight categories: + +1. **Best Practices**: These are rules which enforce generally accepted best practices. +2. **Code Style**: These rules enforce a specific coding style. +3. **Design**: Rules that help you discover design issues. +4. **Documentation**: These rules are related to code documentation. +5. **Error Prone**: Rules to detect constructs that are either broken, extremely confusing or prone to runtime errors. +6. **Multithreading**: These are rules that flag issues when dealing with multiple threads of execution. +7. **Performance**: Rules that flag suboptimal code. +8. **Security**: Rules that flag potential security flaws. + +These categories help you to find rules and figure out the relevance and impact for your project. + +All rules have been moved accordingly, e.g. the rule "JumbledIncrementer", which was previously defined in the +ruleset "java-basic" has now been moved to the "Error Prone" category. The new rule reference to be used is +`<rule ref="category/java/errorprone.xml/JumbledIncrementer"/>`. + +The old rulesets like "java-basic" are still kept for backwards-compatibility but will be removed eventually. +The rule reference documentation has been updated to reflect these changes. + +#### New Rules + +* The new Java rule `NcssCount` (category `design`) replaces the three rules "NcssConstructorCount", "NcssMethodCount", + and "NcssTypeCount". The new rule uses the metrics framework to achieve the same. It has two properties, to + define the report level for method and class sizes separately. Constructors and methods are considered the same. + +* The new Java rule `DoNotExtendJavaLangThrowable` (category `errorprone`) is a companion for the + `java-strictexception.xml/DoNotExtendJavaLangError`, detecting direct extensions of `java.lang.Throwable`. + +* The new Java rule `ForLoopCanBeForeach` (category `errorprone`) helps to identify those for-loops that can + be safely refactored into for-each-loops available since java 1.5. + +* The new Java rule `AvoidFileStream` (category `performance`) helps to identify code relying on `FileInputStream` / `FileOutputStream` + which, by using a finalizer, produces extra / unnecessary overhead to garbage collection, and should be replaced with + `Files.newInputStream` / `Files.newOutputStream` available since java 1.7. + +* The new Java rule `DataClass` (category `design`) detects simple data-holders without behaviour. This might indicate + that the behaviour is scattered elsewhere and the data class exposes the internal data structure, + which breaks encapsulation. + +* The new Apex rule `AvoidDirectAccessTriggerMap` (category `errorprone`) helps to identify direct array access to triggers, + which can produce bugs by either accessing non-existing indexes, or leaving them out. You should use for-each-loops + instead. + +* The new Apex rule `AvoidHardcodingId` (category `errorprone`) detects hardcoded strings that look like identifiers + and flags them. Record IDs change between environments, meaning hardcoded ids are bound to fail under a different + setup. + +* The new Apex rule `CyclomaticComplexity` (category `design`) detects overly complex classes and methods. The + report threshold can be configured separately for classes and methods. + +* A whole bunch of new rules has been added to Apex. They all fit into the category `errorprone`. + The 5 rules are migrated for Apex from the equivalent Java rules and include: + * `EmptyCatchBlock` to detect catch blocks completely ignoring exceptions. + * `EmptyIfStmt` for if blocks with no content, that can be safely removed. + * `EmptyTryOrFinallyBlock` for empty try / finally blocks that can be safely removed. + * `EmptyWhileStmt` for empty while loops that can be safely removed. + * `EmptyStatementBlock` for empty code blocks that can be safely removed. + +* The new Apex rule `AvoidSoslInLoops` (category `performance`) is the companion of the old + `AvoidSoqlInLoops` rule, flagging SOSL (Salesforce Object Search Language) queries when within + loops, to avoid governor issues, and hitting the database too often. + +#### Modified Rules + +* The Java rule `UnnecessaryFinalModifier` (category `codestyle`, former ruleset `java-unnecessarycode`) + has been merged into the rule `UnnecessaryModifier`. As part of this, the rule has been revamped to detect more cases. + It will now flag anonymous class' methods marked as final (can't be overridden, so it's pointless), along with + final methods overridden / defined within enum instances. It will also flag `final` modifiers on try-with-resources. + +* The Java rule `UnnecessaryParentheses` (category `codestyle`, former ruleset `java-controversial`) + has been merged into `UselessParentheses` (category `codestyle`, former ruleset `java-unnecessary`). + The rule covers all scenarios previously covered by either rule. + +* The Java rule `UncommentedEmptyConstructor` (category `documentation`, former ruleset `java-design`) + will now ignore empty constructors annotated with `javax.inject.Inject`. + +* The Java rule `AbstractClassWithoutAnyMethod` (category `bestpractices`, former ruleset `java-design`) + will now ignore classes annotated with `com.google.auto.value.AutoValue`. + +* The Java rule `GodClass` (category `design', former ruleset `java-design`) has been revamped to use + the new metrics framework. + +* The Java rule `LooseCoupling` (category `bestpractices`, former ruleset `java-coupling`) has been + replaced by the typeresolution-based implementation. + +* The Java rule `CloneMethodMustImplementCloneable` (category `errorprone`, former ruleset `java-clone`) + has been replaced by the typeresolution-based + implementation and is now able to detect cases if a class implements or extends a Cloneable class/interface. + +* The Java rule `UnusedImports` (category `bestpractices`, former ruleset `java-imports`) has been + replaced by the typeresolution-based + implementation and is now able to detect unused on-demand imports. + +* The Java rule `SignatureDeclareThrowsException` (category `design`, former ruleset 'java-strictexception') + has been replaced by the + typeresolution-based implementation. It has a new property `IgnoreJUnitCompletely`, which allows all + methods in a JUnit testcase to throw exceptions. + +* The Java rule `NPathComplexity` (category `design`, former ruleset `java-codesize`) has been revamped + to use the new metrics framework. + Its report threshold can be configured via the property `reportLevel`, which replaces the now + deprecated property `minimum`. + +* The Java rule `CyclomaticComplexity` (category `design`, former ruleset `java-codesize`) has been + revamped to use the new metrics framework. + Its report threshold can be configured via the properties `classReportLevel` and `methodReportLevel` separately. + The old property `reportLevel`, which configured the level for both total class and method complexity, + is deprecated. + +* The Java rule `CommentRequired` (category `documentation`, former ruleset `java-comments`) + has been revamped to include 2 new properties: + * `accessorCommentRequirement` to specify documentation requirements for getters and setters (default to `ignored`) + * `methodWithOverrideCommentRequirement` to specify documentation requirements for methods annotated with `@Override` (default to `ignored`) + +* The Java rule `EmptyCatchBlock` (category `errorprone`, former ruleset `java-empty`) has been changed to ignore + exceptions named `ignore` or `expected` by default. You can still override this behaviour by setting the `allowExceptionNameRegex` property. + +* The Java rule `OptimizableToArrayCall` (category `performance`, former ruleset `design`) has been + modified to fit for the current JVM implementations: It basically detects now the opposite and suggests to + use `Collection.toArray(new E[0])` with a zero-sized array. + See [Arrays of Wisdom of the Ancients](https://shipilev.net/blog/2016/arrays-wisdom-ancients/). + +#### Deprecated Rules + +* The Java rules `NcssConstructorCount`, `NcssMethodCount`, and `NcssTypeCount` (ruleset `java-codesize`) have been + deprecated. They will be replaced by the new rule `NcssCount` in the category `design`. + +* The Java rule `LooseCoupling` in ruleset `java-typeresolution` is deprecated. Use the rule with the same name + from category `bestpractices` instead. + +* The Java rule `CloneMethodMustImplementCloneable` in ruleset `java-typeresolution` is deprecated. Use the rule with + the same name from category `errorprone` instead. + +* The Java rule `UnusedImports` in ruleset `java-typeresolution` is deprecated. Use the rule with + the same name from category `bestpractices` instead. + +* The Java rule `SignatureDeclareThrowsException` in ruleset `java-typeresolution` is deprecated. Use the rule + with the same name from category `design` instead. + +* The Java rule `EmptyStaticInitializer` in ruleset `java-empty` is deprecated. Use the rule `EmptyInitializer` + from the category `errorprone`, which covers both static and non-static empty initializers.` + +* The Java rules `GuardDebugLogging` (ruleset `java-logging-jakarta-commons`) and `GuardLogStatementJavaUtil` + (ruleset `java-logging-java`) have been deprecated. Use the rule `GuardLogStatement` from the + category `bestpractices`, which covers all cases regardless of the logging framework. + +#### Removed Rules + +* The deprecated Java rule `UseSingleton` has been removed from the ruleset `java-design`. The rule has been renamed + long time ago to `UseUtilityClass` (category `design`). + +#### Java Symbol Table + +A [bug in symbol table](https://github.com/pmd/pmd/pull/549/commits/0958621ca884a8002012fc7738308c8dfc24b97c) prevented +the symbol table analysis to properly match primitive arrays types. The issue [affected the `java-unsedcode/UnusedPrivateMethod`](https://github.com/pmd/pmd/issues/521) +rule, but other rules may now produce improved results as consequence of this fix. + +#### Apex Parser Update + +The Apex parser version was bumped, from `1.0-sfdc-187` to `210-SNAPSHOT`. This update let us take full advantage +of the latest improvements from Salesforce, but introduces some breaking changes: + +* `BlockStatements` are now created for all control structures, even if no brace is used. We have therefore added + a `hasCurlyBrace` method to differentiate between both scenarios. +* New AST node types are available. In particular `CastExpression`, `ConstructorPreamble`, `IllegalStoreExpression`, + `MethodBlockStatement`, `Modifier`, `MultiStatement`, `NestedExpression`, `NestedStoreExpression`, + `NewKeyValueObjectExpression` and `StatementExecuted` +* Some nodes have been removed. Such is the case of `TestNode`, `DottedExpression` and `NewNameValueObjectExpression` + (replaced by `NewKeyValueObjectExpression`) + +All existing rules have been updated to reflect these changes. If you have custom rules, be sure to update them. + +For more info about the included Apex parser, see the new pmd module "pmd-apex-jorje", which packages and provides +the parser as a binary. + +#### Incremental Analysis + +The incremental analysis feature first introduced in PMD 5.6.0 has been enhanced. A few minor issues have been fixed, +and several improvements have been performed to make it more accurate. + +The cache will now detect changes to the JARs referenced in the `auxclasspath` instead of simply looking at their paths +and order. This means that if you are referencing a JAR you are overwriting in some way, the incremental analysis can +now detect it and invalidate it's cache to avoid false reports. + +Similarly, any changes to the execution classpath of PMD will invalidate the cache. This means that if you have custom +rules packaged in a jar, any changes to it will invalidate the cache automatically. + +We have also improved logging on the analysis code, allowing better insight into how the cache is performing, +under debug / verbose builds you can even see individual hits / misses to the cache (and the reason for any miss!) + +Finally, as this feature keeps maturing, we are gently pushing this forward. If not using incremental analysis, +a warning will now be produced suggesting users to adopt it for better performance. + +#### Rule and Report Properties + +The implementation around the properties support for rule properties and report properties has been revamped +to be fully typesafe. Along with that change, the support classes have been moved into an own +package `net.sourceforge.pmd.properties`. While there is no change necessary in the ruleset XML files, +when using/setting values for rules, there are adjustments necessary when declaring properties in Java-implemented +rules. + +Rule properties can be declared both for Java based rules and XPath rules. +This is now very well documented in [Working with properties](pmd_devdocs_working_with_properties.html). + +With PMD 6.0.0, multivalued properties are now also possible with XPath rules. + +### Fixed Issues + +* all + * [#394](https://github.com/pmd/pmd/issues/394): \[core] PMD exclude rules are failing with IllegalArgumentException with non-default minimumPriority + * [#532](https://github.com/pmd/pmd/issues/532): \[core] security concerns on URL-based rulesets + * [#538](https://github.com/pmd/pmd/issues/538): \[core] Provide an XML Schema for XML reports + * [#600](https://github.com/pmd/pmd/issues/600): \[core] Nullpointer while creating cache File + * [#604](https://github.com/pmd/pmd/issues/604): \[core] Incremental analysis should detect changes to jars in classpath + * [#608](https://github.com/pmd/pmd/issues/608): \[core] Add DEBUG log when applying incremental analysis + * [#618](https://github.com/pmd/pmd/issues/618): \[core] Incremental Analysis doesn't close file correctly on Windows upon a cache hit + * [#643](https://github.com/pmd/pmd/issues/643): \[core] PMD Properties (dev-properties) breaks markup on CodeClimateRenderer + * [#680](https://github.com/pmd/pmd/pull/680): \[core] Isolate classloaders for runtime and auxclasspath + * [#762](https://github.com/pmd/pmd/issues/762): \[core] Remove method and file property from available property descriptors for XPath rules + * [#763](https://github.com/pmd/pmd/issues/763): \[core] Turn property descriptor util into an enum and enrich its interface +* apex + * [#265](https://github.com/pmd/pmd/issues/265): \[apex] Make Rule suppression work + * [#488](https://github.com/pmd/pmd/pull/488): \[apex] Use Apex lexer for CPD + * [#489](https://github.com/pmd/pmd/pull/489): \[apex] Update Apex compiler + * [#500](https://github.com/pmd/pmd/issues/500): \[apex] Running through CLI shows jorje optimization messages + * [#605](https://github.com/pmd/pmd/issues/605): \[apex] java.lang.NoClassDefFoundError in the latest build + * [#637](https://github.com/pmd/pmd/issues/637): \[apex] Avoid SOSL in loops + * [#760](https://github.com/pmd/pmd/issues/760): \[apex] EmptyStatementBlock complains about missing rather than empty block + * [#766](https://github.com/pmd/pmd/issues/766): \[apex] Replace old Jorje parser with new one + * [#768](https://github.com/pmd/pmd/issues/768): \[apex] java.lang.NullPointerException from PMD +* cpp + * [#448](https://github.com/pmd/pmd/issues/448): \[cpp] Write custom CharStream to handle continuation characters +* java + * [#1454](https://sourceforge.net/p/pmd/bugs/1454/): \[java] OptimizableToArrayCall is outdated and invalid in current JVMs + * [#1513](https://sourceforge.net/p/pmd/bugs/1513/): \[java] Remove deprecated rule UseSingleton + * [#328](https://github.com/pmd/pmd/issues/328): \[java] java.lang.ClassFormatError: Absent Code attribute in method that is not native or abstract in class file javax/servlet/jsp/PageContext + * [#487](https://github.com/pmd/pmd/pull/487): \[java] Fix typeresolution for anonymous extending object + * [#496](https://github.com/pmd/pmd/issues/496): \[java] processing error on generics inherited from enclosing class + * [#510](https://github.com/pmd/pmd/issues/510): \[java] Typeresolution fails on a simple primary when the source is loaded from a class literal + * [#527](https://github.com/pmd/pmd/issues/527): \[java] Lombok getter annotation on enum is not recognized correctly + * [#534](https://github.com/pmd/pmd/issues/534): \[java] NPE in MethodTypeResolution for static methods + * [#603](https://github.com/pmd/pmd/issues/603): \[core] incremental analysis should invalidate upon Java rule plugin changes + * [#650](https://github.com/pmd/pmd/issues/650): \[java] ProcesingError analyzing code under 5.8.1 + * [#732](https://github.com/pmd/pmd/issues/732): \[java] LinkageError with aux classpath +* java-basic + * [#565](https://github.com/pmd/pmd/pull/565): \[java] False negative on DontCallThreadRun when extending Thread +* java-comments + * [#396](https://github.com/pmd/pmd/issues/396): \[java] CommentRequired: add properties to ignore @Override method and getters / setters + * [#536](https://github.com/pmd/pmd/issues/536): \[java] CommentDefaultAccessModifierRule ignores constructors +* java-controversial + * [#388](https://github.com/pmd/pmd/issues/388): \[java] controversial.AvoidLiteralsInIfCondition 0.0 false positive + * [#408](https://github.com/pmd/pmd/issues/408): \[java] DFA not analyzing asserts + * [#537](https://github.com/pmd/pmd/issues/537): \[java] UnnecessaryParentheses fails to detect obvious scenario +* java-design + * [#357](https://github.com/pmd/pmd/issues/357): \[java] UncommentedEmptyConstructor consider annotations on Constructor + * [#438](https://github.com/pmd/pmd/issues/438): \[java] Relax AbstractClassWithoutAnyMethod when class is annotated by @AutoValue + * [#590](https://github.com/pmd/pmd/issues/590): \[java] False positive on MissingStaticMethodInNonInstantiatableClass +* java-logging + * [#457](https://github.com/pmd/pmd/issues/457): \[java] Merge all log guarding rules + * [#721](https://github.com/pmd/pmd/issues/721): \[java] NPE in PMD 5.8.1 InvalidSlf4jMessageFormat +* java-sunsecure + * [#468](https://github.com/pmd/pmd/issues/468): \[java] ArrayIsStoredDirectly false positive +* java-unusedcode + * [#521](https://github.com/pmd/pmd/issues/521): \[java] UnusedPrivateMethod returns false positives with primitive data type in map argument +* java-unnecessarycode + * [#412](https://github.com/pmd/pmd/issues/412): \[java] java-unnecessarycode/UnnecessaryFinalModifier missing cases + * [#676](https://github.com/pmd/pmd/issues/676): \[java] java-unnecessarycode/UnnecessaryFinalModifier on try-with-resources + +### API Changes + +* The class `net.sourceforge.pmd.lang.dfa.NodeType` has been converted to an enum. + All node types are enum members now instead of int constants. The names for node types are retained. + +* The *Properties API* (rule and report properties) has been revamped to be fully typesafe. This is everything + around `net.sourceforge.pmd.properties.PropertyDescriptor`. + + Note: All classes related to properties have been moved into the package `net.sourceforge.pmd.properties`. + +* The rule classes `net.sourceforge.pmd.lang.apex.rule.apexunit.ApexUnitTestClassShouldHaveAsserts` + and `net.sourceforge.pmd.lang.apex.rule.apexunit.ApexUnitTestShouldNotUseSeeAllDataTrue` have been + renamed to `ApexUnitTestClassShouldHaveAssertsRule` and `ApexUnitTestShouldNotUseSeeAllDataTrueRule`, + respectively. This is to comply with the naming convention, that each rule class should be suffixed with "Rule". + + This change has no impact on custom rulesets, since the rule names themselves didn't change. + +* The never implemented method `PMD.processFiles(PMDConfiguration, RuleSetFactory, Collection<File>, RuleContext, ProgressMonitor)` along with the interface `ProgressMonitor` has been removed. + +* The method `PMD.setupReport(RuleSets, RuleContext, String)` is gone. It was used to report dysfunctional + rules. But PMD does this now automatically before processing the files, so there is no need for this + method anymore. + +* All APIs deprecated in older versions are now removed. This includes: + * `Renderer.getPropertyDefinitions` + * `AbstractRenderer.defineProperty(String, String)` + * `AbstractRenderer.propertyDefinitions` + * `ReportListener` + * `Report.addListener(ReportListener)` + * `SynchronizedReportListener` + * `CPDConfiguration.CPDConfiguration(int, Language, String)` + * `CPDConfiguration.getRendererFromString(String)` + * `StreamUtil` + * `StringUtil.appendXmlEscaped(StringBuilder, String)` + * `StringUtil.htmlEncode(String)` + + +* Several methods in `net.sourceforge.pmd.util.CollectionUtil` have been deprecated and will be removed in PMD 7.0.0. In particular: + * `CollectionUtil.addWithoutDuplicates(T[], T)` + * `CollectionUtil.addWithoutDuplicates(T[], T[])` + * `CollectionUtil.areSemanticEquals(T[], T[])` + * `CollectionUtil.areEqual(Object, Object)` + * `CollectionUtil.arraysAreEqual(Object, Object)` + * `CollectionUtil.valuesAreTransitivelyEqual(Object[], Object[])` + + +* Several methods in `net.sourceforge.pmd.util.StringUtil` have been deprecated and will be removed in PMD 7.0.0. In particular: + * `StringUtil.startsWithAny(String, String[])` + * `StringUtil.isNotEmpty(String)` + * `StringUtil.isEmpty(String)` + * `StringUtil.isMissing(String)` + * `StringUtil.areSemanticEquals(String, String)` + * `StringUtil.replaceString(String, String, String)` + * `StringUtil.replaceString(String, char, String)` + * `StringUtil.substringsOf(String, char)` + * `StringUtil.substringsOf(String, String)` + * `StringUtil.asStringOn(StringBuffer, Iterator, String)` + * `StringUtil.asStringOn(StringBuilder, Object[], String)` + * `StringUtil.lpad(String, int)` + +* The class `net.sourceforge.pmd.lang.java.typeresolution.typedefinition.JavaTypeDefinition` is now abstract, and has been enhanced + to provide several new methods. + +* The constructor of `net.sourceforge.pmd.RuleSetFactory`, which took a `ClassLoader` is deprecated. + Please use the alternative constructor with the `net.sourceforge.pmd.util.ResourceLoader` instead. + +* The following GUI related classes have been deprecated and will be removed in PMD 7.0.0. + The tool "bgastviewer", that could be started via the script `bgastviewer.bat` or `run.sh bgastviewer` is + deprecated, too, and will be removed in PMD 7.0.0. + Both the "old designer" and "bgastviewer" are replaced by the [New Rule Designer](#new-rule-designer). + * `net.sourceforge.pmd.util.designer.CodeEditorTextPane` + * `net.sourceforge.pmd.util.designer.CreateXMLRulePanel` + * `net.sourceforge.pmd.util.designer.Designer` + * `net.sourceforge.pmd.util.designer.DFAPanel` + * `net.sourceforge.pmd.util.designer.LineGetter` + * `net.sourceforge.pmd.util.viewer.Viewer` + * `net.sourceforge.pmd.util.viewer.gui.ActionCommands` + * `net.sourceforge.pmd.util.viewer.gui.ASTPanel` + * `net.sourceforge.pmd.util.viewer.gui.EvaluationResultsPanel` + * `net.sourceforge.pmd.util.viewer.gui.MainFrame` + * `net.sourceforge.pmd.util.viewer.gui.ParseExceptionHandler` + * `net.sourceforge.pmd.util.viewer.gui.SourceCodePanel` + * `net.sourceforge.pmd.util.viewer.gui.XPathPanel` + * `net.sourceforge.pmd.util.viewer.gui.menu.ASTNodePopupMenu` + * `net.sourceforge.pmd.util.viewer.gui.menu.AttributesSubMenu` + * `net.sourceforge.pmd.util.viewer.gui.menu.SimpleNodeSubMenu` + * `net.sourceforge.pmd.util.viewer.gui.menu.XPathFragmentAddingItem` + * `net.sourceforge.pmd.util.viewer.model.ASTModel` + * `net.sourceforge.pmd.util.viewer.model.AttributeToolkit` + * `net.sourceforge.pmd.util.viewer.model.SimpleNodeTreeNodeAdapter` + * `net.sourceforge.pmd.util.viewer.model.ViewerModel` + * `net.sourceforge.pmd.util.viewer.model.ViewerModelEvent` + * `net.sourceforge.pmd.util.viewer.model.ViewerModelListener` + * `net.sourceforge.pmd.util.viewer.util.NLS` + +* The following methods in `net.sourceforge.pmd.Rule` have been deprecated and will be removed in PMD 7.0.0. + All methods are replaced by their bean-like counterparts + * `void setUsesDFA()`. Use `void setDfa(boolean)` instead. + * `boolean usesDFA()`. Use `boolean isDfa()` instead. + * `void setUsesTypeResolution()`. Use `void setTypeResolution(boolean)` instead. + * `boolean usesTypeResolution()`. Use `boolean isTypeResolution()` instead. + * `void setUsesMultifile()`. Use `void setMultifile(boolean)` instead. + * `boolean usesMultifile()`. Use `boolean isMultifile()` instead. + * `boolean usesRuleChain()`. Use `boolean isRuleChain()` instead. + +### External Contributions + +* [#287](https://github.com/pmd/pmd/pull/287): \[apex] Make Rule suppression work - [Robert Sösemann](https://github.com/up2go-rsoesemann) +* [#420](https://github.com/pmd/pmd/pull/420): \[java] Fix UR anomaly in assert statements - [Clément Fournier](https://github.com/oowekyala) +* [#482](https://github.com/pmd/pmd/pull/482): \[java] Metrics testing framework + improved capabilities for metrics - [Clément Fournier](https://github.com/oowekyala) +* [#484](https://github.com/pmd/pmd/pull/484): \[core] Changed linux usage to a more unix like path - [patriksevallius](https://github.com/patriksevallius) +* [#486](https://github.com/pmd/pmd/pull/486): \[java] Add basic method typeresolution - [Bendegúz Nagy](https://github.com/WinterGrascph) +* [#492](https://github.com/pmd/pmd/pull/492): \[java] Typeresolution for overloaded methods - [Bendegúz Nagy](https://github.com/WinterGrascph) +* [#495](https://github.com/pmd/pmd/pull/495): \[core] Custom rule reinitialization code - [Clément Fournier](https://github.com/oowekyala) +* [#479](https://github.com/pmd/pmd/pull/479): \[core] Typesafe and immutable properties - [Clément Fournier](https://github.com/oowekyala) +* [#499](https://github.com/pmd/pmd/pull/499): \[java] Metrics memoization tests - [Clément Fournier](https://github.com/oowekyala) +* [#501](https://github.com/pmd/pmd/pull/501): \[java] Add support for most specific vararg method type resolution - [Bendegúz Nagy](https://github.com/WinterGrascph) +* [#502](https://github.com/pmd/pmd/pull/502): \[java] Add support for static field type resolution - [Bendegúz Nagy](https://github.com/WinterGrascph) +* [#505](https://github.com/pmd/pmd/pull/505): \[java] Followup on metrics - [Clément Fournier](https://github.com/oowekyala) +* [#506](https://github.com/pmd/pmd/pull/506): \[java] Add reduction rules to type inference - [Bendegúz Nagy](https://github.com/WinterGrascph) +* [#511](https://github.com/pmd/pmd/pull/511): \[core] Prepare abstraction of the metrics framework - [Clément Fournier](https://github.com/oowekyala) +* [#512](https://github.com/pmd/pmd/pull/512): \[java] Add incorporation to type inference - [Bendegúz Nagy](https://github.com/WinterGrascph) +* [#513](https://github.com/pmd/pmd/pull/513): \[java] Fix for maximally specific method selection - [Bendegúz Nagy](https://github.com/WinterGrascph) +* [#514](https://github.com/pmd/pmd/pull/514): \[java] Add static method type resolution - [Bendegúz Nagy](https://github.com/WinterGrascph) +* [#517](https://github.com/pmd/pmd/pull/517): \[doc] Metrics documentation - [Clément Fournier](https://github.com/oowekyala) +* [#518](https://github.com/pmd/pmd/pull/518): \[core] Properties refactoring: factorized enumerated property - [Clément Fournier](https://github.com/oowekyala) +* [#523](https://github.com/pmd/pmd/pull/523): \[java] Npath complexity metric and rule - [Clément Fournier](https://github.com/oowekyala) +* [#524](https://github.com/pmd/pmd/pull/524): \[java] Add support for explicit type arguments with method invocation - [Bendegúz Nagy](https://github.com/WinterGrascph) +* [#525](https://github.com/pmd/pmd/pull/525): \[core] Fix line ending and not ignored files issues - [Matias Comercio](https://github.com/MatiasComercio) +* [#528](https://github.com/pmd/pmd/pull/528): \[core] Fix typo - [Ayoub Kaanich](https://github.com/kayoub5) +* [#529](https://github.com/pmd/pmd/pull/529): \[java] Abstracted the Java metrics framework - [Clément Fournier](https://github.com/oowekyala) +* [#530](https://github.com/pmd/pmd/pull/530): \[java] Fix issue #527: Lombok getter annotation on enum is not recognized correctly - [Clément Fournier](https://github.com/oowekyala) +* [#533](https://github.com/pmd/pmd/pull/533): \[core] improve error message - [Dennis Kieselhorst](https://github.com/deki) +* [#535](https://github.com/pmd/pmd/pull/535): \[apex] Fix broken Apex visitor adapter - [Clément Fournier](https://github.com/oowekyala) +* [#542](https://github.com/pmd/pmd/pull/542): \[java] Metrics abstraction - [Clément Fournier](https://github.com/oowekyala) +* [#545](https://github.com/pmd/pmd/pull/545): \[apex] Apex metrics framework - [Clément Fournier](https://github.com/oowekyala) +* [#548](https://github.com/pmd/pmd/pull/548): \[java] Metrics documentation - [Clément Fournier](https://github.com/oowekyala) +* [#550](https://github.com/pmd/pmd/pull/550): \[java] Add basic resolution to type inference - [Bendegúz Nagy](https://github.com/WinterGrascph) +* [#553](https://github.com/pmd/pmd/pull/553): \[java] Refactored ParserTst into a static utility class + add getSourceFromClass - [Clément Fournier](https://github.com/oowekyala) +* [#554](https://github.com/pmd/pmd/pull/554): \[java] Fix #537: UnnecessaryParentheses fails to detect obvious scenario - [Clément Fournier](https://github.com/oowekyala) +* [#555](https://github.com/pmd/pmd/pull/555): \[java] Changed metrics/CyclomaticComplexityRule to use WMC when reporting classes - [Clément Fournier](https://github.com/oowekyala) +* [#556](https://github.com/pmd/pmd/pull/556): \[java] Fix #357: UncommentedEmptyConstructor consider annotations on Constructor - [Clément Fournier](https://github.com/oowekyala) +* [#557](https://github.com/pmd/pmd/pull/557): \[java] Fix NPath metric not counting ternaries correctly - [Clément Fournier](https://github.com/oowekyala) +* [#563](https://github.com/pmd/pmd/pull/563): \[java] Add support for basic method type inference for strict invocation - [Bendegúz Nagy](https://github.com/WinterGrascph) +* [#566](https://github.com/pmd/pmd/pull/566): \[java] New rule in migrating ruleset: ForLoopCanBeForeach - [Clément Fournier](https://github.com/oowekyala) +* [#567](https://github.com/pmd/pmd/pull/567): \[java] Last API change for metrics (metric options) - [Clément Fournier](https://github.com/oowekyala) +* [#570](https://github.com/pmd/pmd/pull/570): \[java] Model lower, upper and intersection types - [Bendegúz Nagy](https://github.com/WinterGrascph) +* [#573](https://github.com/pmd/pmd/pull/573): \[java] Data class rule - [Clément Fournier](https://github.com/oowekyala) +* [#576](https://github.com/pmd/pmd/pull/576): \[doc]\[java] Add hint for Guava users in InefficientEmptyStringCheck - [mmoehring](https://github.com/mmoehring) +* [#578](https://github.com/pmd/pmd/pull/578): \[java] Refactored god class rule - [Clément Fournier](https://github.com/oowekyala) +* [#579](https://github.com/pmd/pmd/pull/579): \[java] Update parsing to produce upper and lower bounds - [Bendegúz Nagy](https://github.com/WinterGrascph) +* [#580](https://github.com/pmd/pmd/pull/580): \[core] Add AbstractMetric to topple the class hierarchy of metrics - [Clément Fournier](https://github.com/oowekyala) +* [#581](https://github.com/pmd/pmd/pull/581): \[java] Relax AbstractClassWithoutAnyMethod when class is annotated by @AutoValue - [Niklas Baudy](https://github.com/vanniktech) +* [#583](https://github.com/pmd/pmd/pull/583): \[java] Documentation about writing metrics - [Clément Fournier](https://github.com/oowekyala) +* [#585](https://github.com/pmd/pmd/pull/585): \[java] Moved NcssCountRule to codesize.xml - [Clément Fournier](https://github.com/oowekyala) +* [#587](https://github.com/pmd/pmd/pull/587): \[core] Properties refactoring: Move static constants of ValueParser to class ValueParserConstants - [Clément Fournier](https://github.com/oowekyala) +* [#588](https://github.com/pmd/pmd/pull/588): \[java] XPath function to compute metrics - [Clément Fournier](https://github.com/oowekyala) +* [#598](https://github.com/pmd/pmd/pull/598): \[java] Fix #388: controversial.AvoidLiteralsInIfCondition 0.0 false positive - [Clément Fournier](https://github.com/oowekyala) +* [#602](https://github.com/pmd/pmd/pull/602): \[java] \[apex] Separate multifile analysis from metrics - [Clément Fournier](https://github.com/oowekyala) +* [#620](https://github.com/pmd/pmd/pull/620): \[core] Moved properties to n.s.pmd.properties - [Clément Fournier](https://github.com/oowekyala) +* [#625](https://github.com/pmd/pmd/pull/625): \[apex] empty code ruleset for apex - [Jan Aertgeerts](https://github.com/JAertgeerts) +* [#632](https://github.com/pmd/pmd/pull/632): \[apex] Add AvoidDirectAccessTriggerMap rule to the style set - [Jan Aertgeerts](https://github.com/JAertgeerts) +* [#644](https://github.com/pmd/pmd/pull/644): \[core] Prevent internal dev-properties from being displayed on CodeClimate renderer - [Filipe Esperandio](https://github.com/filipesperandio) +* [#660](https://github.com/pmd/pmd/pull/660): \[apex] avoid sosl in loops - [Jan Aertgeerts](https://github.com/JAertgeerts) +* [#661](https://github.com/pmd/pmd/pull/661): \[apex] avoid hardcoding id's - [Jan Aertgeerts](https://github.com/JAertgeerts) +* [#666](https://github.com/pmd/pmd/pull/666): \[java] Add DoNotExtendJavaLangThrowable rule - [Robert Painsi](https://github.com/robertpainsi) +* [#668](https://github.com/pmd/pmd/pull/668): \[core] Fix javadoc warnings on pmd-core - [Clément Fournier](https://github.com/oowekyala) +* [#669](https://github.com/pmd/pmd/pull/669): \[core] Builder pattern for properties - [Clément Fournier](https://github.com/oowekyala) +* [#675](https://github.com/pmd/pmd/pull/675): \[java] Fix in Java grammar: Try with final resource node error - [Gonzalo Ibars Ingman](https://github.com/gibarsin) +* [#679](https://github.com/pmd/pmd/pull/679): \[core] Token scheme generalization - [Gonzalo Ibars Ingman](https://github.com/gibarsin) +* [#694](https://github.com/pmd/pmd/pull/694): \[core] Add minor fixes to root pom - [Matias Comercio](https://github.com/MatiasComercio) +* [#696](https://github.com/pmd/pmd/pull/696): \[core] Add remove operation over nodes - [Matias Comercio](https://github.com/MatiasComercio) +* [#711](https://github.com/pmd/pmd/pull/711): \[ui] New rule designer - [Clément Fournier](https://github.com/oowekyala) +* [#722](https://github.com/pmd/pmd/pull/722): \[java] Move NPathComplexity from metrics to design - [Clément Fournier](https://github.com/oowekyala) +* [#723](https://github.com/pmd/pmd/pull/723): \[core] Rule factory refactoring - [Clément Fournier](https://github.com/oowekyala) +* [#726](https://github.com/pmd/pmd/pull/726): \[java] Fix issue #721 (NPE in InvalidSlf4jMessageFormat) - [Clément Fournier](https://github.com/oowekyala) +* [#727](https://github.com/pmd/pmd/pull/727): \[core] Fix #725: numeric property descriptors now check their default value - [Clément Fournier](https://github.com/oowekyala) +* [#733](https://github.com/pmd/pmd/pull/733): \[java] Some improvements to CommentRequired - [Clément Fournier](https://github.com/oowekyala) +* [#734](https://github.com/pmd/pmd/pull/734): \[java] Move CyclomaticComplexity from metrics to design - [Clément Fournier](https://github.com/oowekyala) +* [#736](https://github.com/pmd/pmd/pull/736): \[core] Make Saxon support multi valued XPath properties - [Clément Fournier](https://github.com/oowekyala) +* [#737](https://github.com/pmd/pmd/pull/737): \[doc] Fix NPathComplexity documentation bad rendering - [Clément Fournier](https://github.com/oowekyala) +* [#744](https://github.com/pmd/pmd/pull/744): \[doc] Added Apex to supported languages - [MichaÅ‚ KuliÅ„ski](https://github.com/coola) +* [#746](https://github.com/pmd/pmd/pull/746): \[doc] Fix typo in incremental analysis log message - [Clément Fournier](https://github.com/oowekyala) +* [#749](https://github.com/pmd/pmd/pull/749): \[doc] Update the documentation for properties - [Clément Fournier](https://github.com/oowekyala) +* [#758](https://github.com/pmd/pmd/pull/758): \[core] Expose the full mapping from property type id to property extractor - [Clément Fournier](https://github.com/oowekyala) +* [#764](https://github.com/pmd/pmd/pull/764): \[core] Prevent method and file property use in XPath rules - [Clément Fournier](https://github.com/oowekyala) +* [#771](https://github.com/pmd/pmd/pull/771): \[apex] Fix Apex metrics framework failing on triggers, refs #768 - [Clément Fournier](https://github.com/oowekyala) +* [#774](https://github.com/pmd/pmd/pull/774): \[java] Avoid using FileInput/Output - see JDK-8080225 - [Chas Honton](https://github.com/chonton) ## 01-July-2017 - 5.8.1 diff --git a/docs/pages/tags/tag_collaboration.md b/docs/pages/tags/tag_collaboration.md deleted file mode 100644 index bec986c4ad7..00000000000 --- a/docs/pages/tags/tag_collaboration.md +++ /dev/null @@ -1,11 +0,0 @@ ---- -title: "Collaboration pages" -tagName: collaboration -search: exclude -permalink: tag_collaboration.html -sidebar: mydoc_sidebar -folder: tags ---- -{% include taglogic.html %} - -{% include links.html %} diff --git a/docs/pages/tags/tag_content_types.md b/docs/pages/tags/tag_content_types.md deleted file mode 100644 index fe87eaab59b..00000000000 --- a/docs/pages/tags/tag_content_types.md +++ /dev/null @@ -1,11 +0,0 @@ ---- -title: "Content types pages" -tagName: content_types -search: exclude -permalink: tag_content_types.html -sidebar: mydoc_sidebar -folder: tags ---- -{% include taglogic.html %} - -{% include links.html %} diff --git a/docs/pages/tags/tag_customizing.md b/docs/pages/tags/tag_customizing.md deleted file mode 100644 index fe1426428ed..00000000000 --- a/docs/pages/tags/tag_customizing.md +++ /dev/null @@ -1,11 +0,0 @@ ---- -title: "Customizing PMD Pages" -tagName: customizing -search: exclude -permalink: tag_customizing.html -sidebar: pmd_sidebar -folder: tags ---- -{% include taglogic.html %} - -{% include links.html %} diff --git a/docs/pages/tags/tag_devdocs.md b/docs/pages/tags/tag_devdocs.md new file mode 100644 index 00000000000..87aa8aa5411 --- /dev/null +++ b/docs/pages/tags/tag_devdocs.md @@ -0,0 +1,11 @@ +--- +title: "Developer and contributor documentation" +tagName: devdocs +search: exclude +permalink: tag_devdocs.html +sidebar: pmd_sidebar +folder: tags +--- +{% include taglogic.html %} + +{% include links.html %} diff --git a/docs/pages/tags/tag_extending.md b/docs/pages/tags/tag_extending.md new file mode 100644 index 00000000000..28291803daf --- /dev/null +++ b/docs/pages/tags/tag_extending.md @@ -0,0 +1,9 @@ +--- +title: "Extending PMD" +tagName: extending +search: exclude +permalink: tag_extending.html +sidebar: pmd_sidebar +folder: tags +--- +{% include taglogic.html %} diff --git a/docs/pages/tags/tag_formatting.md b/docs/pages/tags/tag_formatting.md deleted file mode 100644 index 83325023378..00000000000 --- a/docs/pages/tags/tag_formatting.md +++ /dev/null @@ -1,11 +0,0 @@ ---- -title: "Formatting pages" -tagName: formatting -search: exclude -permalink: tag_formatting.html -sidebar: mydoc_sidebar -folder: tags ---- -{% include taglogic.html %} - -{% include links.html %} diff --git a/docs/pages/tags/tag_languages.md b/docs/pages/tags/tag_languages.md index 5cd56b1795d..886d150173d 100644 --- a/docs/pages/tags/tag_languages.md +++ b/docs/pages/tags/tag_languages.md @@ -3,7 +3,7 @@ title: "Supported Lanugages" tagName: languages search: exclude permalink: tag_languages.html -sidebar: mydoc_sidebar +sidebar: pmd_sidebar folder: tags --- {% include taglogic.html %} diff --git a/docs/pages/tags/tag_metrics.md b/docs/pages/tags/tag_metrics.md new file mode 100644 index 00000000000..f189486a617 --- /dev/null +++ b/docs/pages/tags/tag_metrics.md @@ -0,0 +1,9 @@ +--- +title: "Code metrics" +tagName: metrics +search: exclude +permalink: tag_metrics.html +sidebar: pmd_sidebar +folder: tags +--- +{% include taglogic.html %} diff --git a/docs/pages/tags/tag_mobile.md b/docs/pages/tags/tag_mobile.md deleted file mode 100644 index 4f35333a27a..00000000000 --- a/docs/pages/tags/tag_mobile.md +++ /dev/null @@ -1,11 +0,0 @@ ---- -title: "Mobile Pages" -search: exclude -tagName: mobile -permalink: tag_mobile.html -sidebar: mydoc_sidebar -folder: tags ---- -{% include taglogic.html %} - -{% include links.html %} diff --git a/docs/pages/tags/tag_navigation.md b/docs/pages/tags/tag_navigation.md deleted file mode 100644 index 480d087b8e6..00000000000 --- a/docs/pages/tags/tag_navigation.md +++ /dev/null @@ -1,11 +0,0 @@ ---- -title: "Navigation pages" -tagName: navigation -search: exclude -permalink: tag_navigation.html -sidebar: mydoc_sidebar -folder: tags ---- -{% include taglogic.html %} - -{% include links.html %} diff --git a/docs/pages/tags/tag_news.md b/docs/pages/tags/tag_news.md deleted file mode 100644 index 3dc9414239d..00000000000 --- a/docs/pages/tags/tag_news.md +++ /dev/null @@ -1,11 +0,0 @@ ---- -title: "News" -tagName: news -search: exclude -permalink: tag_news.html -sidebar: mydoc_sidebar -folder: tags ---- -{% include taglogic.html %} - -{% include links.html %} diff --git a/docs/pages/tags/tag_publishing.md b/docs/pages/tags/tag_publishing.md deleted file mode 100644 index 5d948f0e2f4..00000000000 --- a/docs/pages/tags/tag_publishing.md +++ /dev/null @@ -1,11 +0,0 @@ ---- -title: "Publishing pages" -tagName: publishing -search: exclude -permalink: tag_publishing.html -sidebar: mydoc_sidebar -folder: tags ---- -{% include taglogic.html %} - -{% include links.html %} diff --git a/docs/pages/tags/tag_release_notes.md b/docs/pages/tags/tag_release_notes.md index 8d0685a831c..2804c0ff307 100644 --- a/docs/pages/tags/tag_release_notes.md +++ b/docs/pages/tags/tag_release_notes.md @@ -3,7 +3,7 @@ title: "Release Notes Pages" tagName: release_notes search: exclude permalink: tag_release_notes.html -sidebar: mydoc_sidebar +sidebar: pmd_sidebar folder: tags --- {% include taglogic.html %} diff --git a/docs/pages/tags/tag_rule_references.md b/docs/pages/tags/tag_rule_references.md new file mode 100644 index 00000000000..2d0ca25b18c --- /dev/null +++ b/docs/pages/tags/tag_rule_references.md @@ -0,0 +1,11 @@ +--- +title: "Rule references" +tagName: rule_references +search: exclude +permalink: tag_rule_references.html +sidebar: pmd_sidebar +folder: tags +--- +{% include taglogic.html %} + +{% include links.html %} diff --git a/docs/pages/tags/tag_single_sourcing.md b/docs/pages/tags/tag_single_sourcing.md deleted file mode 100644 index ae181e154ef..00000000000 --- a/docs/pages/tags/tag_single_sourcing.md +++ /dev/null @@ -1,11 +0,0 @@ ---- -title: "Single sourcing pages" -tagName: single_sourcing -search: exclude -permalink: tag_single_sourcing.html -sidebar: mydoc_sidebar -folder: tags ---- -{% include taglogic.html %} - -{% include links.html %} diff --git a/docs/pages/tags/tag_special_layouts.md b/docs/pages/tags/tag_special_layouts.md deleted file mode 100644 index b010362e967..00000000000 --- a/docs/pages/tags/tag_special_layouts.md +++ /dev/null @@ -1,12 +0,0 @@ ---- -title: "Special layout pages" -tagName: special_layouts -search: exclude -permalink: tag_special_layouts.html -sidebar: mydoc_sidebar -folder: tags ---- - -{% include taglogic.html %} - -{% include links.html %} diff --git a/docs/pages/tags/tag_tools.md b/docs/pages/tags/tag_tools.md new file mode 100644 index 00000000000..de8532dc7a5 --- /dev/null +++ b/docs/pages/tags/tag_tools.md @@ -0,0 +1,11 @@ +--- +title: "Tools and integrations" +tagName: tools +search: exclude +permalink: tag_tools.html +sidebar: pmd_sidebar +folder: tags +--- +{% include taglogic.html %} + +{% include links.html %} diff --git a/docs/pages/tags/tag_userdocs.md b/docs/pages/tags/tag_userdocs.md new file mode 100644 index 00000000000..6d7ffcb3fa1 --- /dev/null +++ b/docs/pages/tags/tag_userdocs.md @@ -0,0 +1,8 @@ +--- +title: "User documentation" +tagName: userdocs +search: exclude +permalink: tag_userdocs.html +sidebar: pmd_sidebar +--- +{% include taglogic.html %} diff --git a/docs/search.json b/docs/search.json index a7f7dd0fe10..3a9de069120 100644 --- a/docs/search.json +++ b/docs/search.json @@ -7,6 +7,20 @@ search: exclude [ {% for page in site.pages %} {% unless page.search == "exclude" %} + +{% if page.permalink contains "pmd_rules_" and page.keywords %} +{% assign rules = page.keywords | split: ", " %} +{% for rule in rules %} +{ +"title": "{{ rule | escape }} ({{page.language}}, {{page.title}})", +"tags": "{{ page.tags }}", +"keywords": "{{rule}}", +"url": "{{ page.url | remove: "/"}}#{{ rule | downcase }}", +"summary": "{{page.summary | strip }}" +} +{% unless forloop.last %},{% endunless %} +{% endfor %} +{% else %} { "title": "{{ page.title | escape }}", "tags": "{{ page.tags }}", @@ -14,6 +28,9 @@ search: exclude "url": "{{ page.url | remove: "/"}}", "summary": "{{page.summary | strip }}" } +{% endif %} + + {% unless forloop.last and site.posts.size < 1 %},{% endunless %} {% endunless %} {% endfor %} diff --git a/docs/update.sh b/docs/update.sh deleted file mode 100755 index 42a3bece578..00000000000 --- a/docs/update.sh +++ /dev/null @@ -1,4 +0,0 @@ -git add . -git status -git commit -m "content update" -git push \ No newline at end of file diff --git a/example-toolchains.xml b/example-toolchains.xml deleted file mode 100644 index ff0b1d5c954..00000000000 --- a/example-toolchains.xml +++ /dev/null @@ -1,28 +0,0 @@ -<?xml version="1.0" encoding="UTF8"?> -<toolchains> - <!-- place this file in ${user.home}/.m2/toolchains.xml --> - <!-- adjust the paths to jdkhome --> - - <toolchain> - <type>jdk</type> - <provides> - <version>1.7</version> - </provides> - <configuration> - <jdkHome>/path/to/jdk/1.7</jdkHome> <!-- Linux --> - <jdkHome>/Library/Java/JavaVirtualMachines/jdk1.7.0_80.jdk/Contents/Home</jdkHome> <!-- MacOSX --> - <jdkHome>C:\\java\\edition\\jdk1.7.0_80</jdkHome> <!-- Windows --> - </configuration> - </toolchain> - <toolchain> - <type>jdk</type> - <provides> - <version>1.8</version> - </provides> - <configuration> - <jdkHome>/path/to/jdk/1.8</jdkHome> <!-- Linux --> - <jdkHome>/Library/Java/JavaVirtualMachines/jdk1.8.0_131.jdk/Contents/Home</jdkHome> <!-- MacOSX --> - <jdkHome>C:\\java\\edition\\jdk1.8.0_131</jdkHome> <!-- Windows --> - </configuration> - </toolchain> -</toolchains> \ No newline at end of file diff --git a/mvnw b/mvnw index 5bf251c0774..e96ccd5fbbb 100755 --- a/mvnw +++ b/mvnw @@ -108,7 +108,7 @@ if $cygwin ; then CLASSPATH=`cygpath --path --unix "$CLASSPATH"` fi -# For Migwn, ensure paths are in UNIX format before anything is touched +# For Mingw, ensure paths are in UNIX format before anything is touched if $mingw ; then [ -n "$M2_HOME" ] && M2_HOME="`(cd "$M2_HOME"; pwd)`" @@ -201,7 +201,9 @@ if [ -z "$BASE_DIR" ]; then fi export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} -echo $MAVEN_PROJECTBASEDIR +if [ "$MVNW_VERBOSE" = true ]; then + echo $MAVEN_PROJECTBASEDIR +fi MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" # For Cygwin, switch paths to Windows format before running java diff --git a/mvnw.cmd b/mvnw.cmd index 019bd74d766..6a6eec39baf 100755 --- a/mvnw.cmd +++ b/mvnw.cmd @@ -35,6 +35,8 @@ @REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' @echo off +@REM set title of command window +title %0 @REM enable echoing my setting MAVEN_BATCH_ECHO to 'on' @if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% diff --git a/pmd-apex-jorje/pom.xml b/pmd-apex-jorje/pom.xml new file mode 100644 index 00000000000..e4db1c5e79d --- /dev/null +++ b/pmd-apex-jorje/pom.xml @@ -0,0 +1,138 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + <artifactId>pmd-apex-jorje</artifactId> + <name>PMD Apex Jorje Parser Library</name> + <packaging>pom</packaging> + + <parent> + <groupId>net.sourceforge.pmd</groupId> + <artifactId>pmd</artifactId> + <version>6.10.0-SNAPSHOT</version> + </parent> + + <properties> + <java.version>8</java.version> + <!-- Workaround for https://youtrack.jetbrains.com/issue/IDEA-188690 --> + <maven.compiler.source>1.${java.version}</maven.compiler.source> + <maven.compiler.target>1.${java.version}</maven.compiler.target> + <apex.jorje.version>2017-11-17</apex.jorje.version> + </properties> + + <build> + <plugins> + <plugin> + <groupId>org.codehaus.mojo</groupId> + <artifactId>build-helper-maven-plugin</artifactId> + <version>3.0.0</version> + <executions> + <execution> + <id>attach-apex-jorje</id> + <phase>package</phase> + <goals> + <goal>attach-artifact</goal> + </goals> + <configuration> + <artifacts> + <artifact> + <file>${basedir}/repo/apex/apex-jorje-lsp-minimized/${apex.jorje.version}/apex-jorje-lsp-minimized-${apex.jorje.version}.jar</file> + <type>jar</type> + <classifier>lib</classifier> + </artifact> + </artifacts> + </configuration> + </execution> + </executions> + </plugin> + </plugins> + </build> + <dependencies> + <!-- transitive dependencies of apex-jorje --> + <dependency> + <groupId>cglib</groupId> + <artifactId>cglib</artifactId> + <version>3.2.0</version> + </dependency> + <dependency> + <groupId>ch.qos.logback</groupId> + <artifactId>logback-classic</artifactId> + <version>1.1.7</version> + </dependency> + <dependency> + <groupId>ch.qos.logback</groupId> + <artifactId>logback-core</artifactId> + <version>1.1.7</version> + </dependency> + <dependency> + <groupId>com.google.code.findbugs</groupId> + <artifactId>jsr305</artifactId> + <version>3.0.1</version> + </dependency> + <dependency> + <groupId>com.google.code.gson</groupId> + <artifactId>gson</artifactId> + <version>2.7</version> + </dependency> + <dependency> + <groupId>com.google.guava</groupId> + <artifactId>guava</artifactId> + <version>22.0</version> + </dependency> + <dependency> + <groupId>org.antlr</groupId> + <artifactId>antlr-runtime</artifactId> + <version>3.5.2</version> + </dependency> + <dependency> + <groupId>org.apache.commons</groupId> + <artifactId>commons-lang3</artifactId> + <version>3.0</version> + </dependency> + <dependency> + <groupId>org.eclipse.xtend</groupId> + <artifactId>org.eclipse.xtend.lib</artifactId> + <version>2.10.0</version> + </dependency> + <dependency> + <groupId>org.eclipse.xtend</groupId> + <artifactId>org.eclipse.xtend.lib.macro</artifactId> + <version>2.10.0</version> + </dependency> + <dependency> + <groupId>org.eclipse.xtext</groupId> + <artifactId>org.eclipse.xtext.xbase.lib</artifactId> + <version>2.10.0</version> + </dependency> + <dependency> + <groupId>org.openjdk.jol</groupId> + <artifactId>jol-core</artifactId> + <version>0.4</version> + </dependency> + <dependency> + <groupId>org.slf4j</groupId> + <artifactId>slf4j-api</artifactId> + <version>1.7.20</version> + </dependency> + <dependency> + <groupId>org.yaml</groupId> + <artifactId>snakeyaml</artifactId> + <version>1.17</version> + </dependency> + <dependency> + <groupId>aopalliance</groupId> + <artifactId>aopalliance</artifactId> + <version>1.0</version> + </dependency> + <dependency> + <groupId>javax.inject</groupId> + <artifactId>javax.inject</artifactId> + <version>1</version> + </dependency> + <dependency> + <groupId>org.ow2.asm</groupId> + <artifactId>asm</artifactId> + <version>5.0.3</version> + <scope>runtime</scope> + </dependency> + </dependencies> +</project> diff --git a/pmd-apex-jorje/repo/README.md b/pmd-apex-jorje/repo/README.md new file mode 100644 index 00000000000..1b3caee2c5a --- /dev/null +++ b/pmd-apex-jorje/repo/README.md @@ -0,0 +1,17 @@ +# Local Maven Repo for the Apex Jorje Parser library + +You can download the needed libraries from: +<https://github.com/forcedotcom/salesforcedx-vscode/tree/develop/packages/salesforcedx-vscode-apex/out> + +Apex Reference: +<https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_reference.htm> + +<https://resources.docs.salesforce.com/sfdc/pdf/salesforce_apex_language_reference.pdf> + +In order to download the jar file, minimize it and add a the jar files to the local repo, use the following script: + + ./create-local-repo.sh + +For the PMD 6.0.0 Release, the versions from +<https://github.com/forcedotcom/salesforcedx-vscode/tree/54b127925eebfcfd14cd9988018438ba49ec3d43/packages/salesforcedx-vscode-apex/out> +have been taken. diff --git a/pmd-apex-jorje/repo/apex/apex-jorje-lsp-minimized/2017-11-17/apex-jorje-lsp-minimized-2017-11-17.jar b/pmd-apex-jorje/repo/apex/apex-jorje-lsp-minimized/2017-11-17/apex-jorje-lsp-minimized-2017-11-17.jar new file mode 100644 index 00000000000..c764affbd1e Binary files /dev/null and b/pmd-apex-jorje/repo/apex/apex-jorje-lsp-minimized/2017-11-17/apex-jorje-lsp-minimized-2017-11-17.jar differ diff --git a/pmd-apex-jorje/repo/apex/apex-jorje-lsp-minimized/2017-11-17/apex-jorje-lsp-minimized-2017-11-17.pom b/pmd-apex-jorje/repo/apex/apex-jorje-lsp-minimized/2017-11-17/apex-jorje-lsp-minimized-2017-11-17.pom new file mode 100644 index 00000000000..9a7ae9a0985 --- /dev/null +++ b/pmd-apex-jorje/repo/apex/apex-jorje-lsp-minimized/2017-11-17/apex-jorje-lsp-minimized-2017-11-17.pom @@ -0,0 +1,9 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> + <modelVersion>4.0.0</modelVersion> + <groupId>apex</groupId> + <artifactId>apex-jorje-lsp-minimized</artifactId> + <version>2017-11-17</version> + <description>POM was created from install:install-file</description> +</project> diff --git a/pmd-apex-jorje/repo/apex/apex-jorje-lsp-minimized/maven-metadata-local.xml b/pmd-apex-jorje/repo/apex/apex-jorje-lsp-minimized/maven-metadata-local.xml new file mode 100644 index 00000000000..cadab749b5b --- /dev/null +++ b/pmd-apex-jorje/repo/apex/apex-jorje-lsp-minimized/maven-metadata-local.xml @@ -0,0 +1,12 @@ +<?xml version="1.0" encoding="UTF-8"?> +<metadata> + <groupId>apex</groupId> + <artifactId>apex-jorje-lsp-minimized</artifactId> + <versioning> + <release>2017-11-17</release> + <versions> + <version>2017-11-17</version> + </versions> + <lastUpdated>20171204195258</lastUpdated> + </versioning> +</metadata> diff --git a/pmd-apex-jorje/repo/create-local-repo.sh b/pmd-apex-jorje/repo/create-local-repo.sh new file mode 100755 index 00000000000..c2933453e32 --- /dev/null +++ b/pmd-apex-jorje/repo/create-local-repo.sh @@ -0,0 +1,52 @@ +#!/bin/bash + +URL=https://raw.githubusercontent.com/forcedotcom/salesforcedx-vscode/54b127925eebfcfd14cd9988018438ba49ec3d43/packages/salesforcedx-vscode-apex/out/apex-jorje-lsp.jar +VERSION=2017-11-17 +FILENAME=apex-jorje-lsp-${VERSION}.jar +FILENAME_MINIMIZED=apex-jorje-lsp-minimized-${VERSION}.jar + + +function install() { + mvn install:install-file -Dfile=${FILENAME_MINIMIZED} \ + -DgroupId=apex \ + -DartifactId=apex-jorje-lsp-minimized \ + -Dversion=${VERSION} \ + -Dpackaging=jar \ + -DlocalRepositoryPath=. +} + +function download() { + curl -o $FILENAME $URL +} + + +function minimize() { + unzip -d temp ${FILENAME} + pushd temp + find . -not -path "." \ + -and -not -path ".." \ + -and -not -path "./apex*" \ + -and -not -path "./StandardApex*" \ + -and -not -path "./messages*" \ + -and -not -path "./com" \ + -and -not -path "./com/google" \ + -and -not -path "./com/google/common*" \ + -print0 | xargs -0 rm -rf + popd + jar --create --file ${FILENAME_MINIMIZED} -C temp/ . + rm -rf temp +} + +function cleanup() { + rm ${FILENAME} + rm ${FILENAME_MINIMIZED} +} + + +download +minimize +install +cleanup + + + diff --git a/pmd-apex/pom.xml b/pmd-apex/pom.xml index 6d833d697bb..33fa5a9ee44 100644 --- a/pmd-apex/pom.xml +++ b/pmd-apex/pom.xml @@ -1,6 +1,5 @@ <?xml version="1.0" encoding="UTF-8"?> -<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <artifactId>pmd-apex</artifactId> <name>PMD Apex</name> @@ -8,12 +7,14 @@ <parent> <groupId>net.sourceforge.pmd</groupId> <artifactId>pmd</artifactId> - <version>6.0.0-SNAPSHOT</version> + <version>6.10.0-SNAPSHOT</version> </parent> <properties> - <config.basedir>${basedir}/../pmd-core</config.basedir> - <java.version>1.8</java.version> + <java.version>8</java.version> + <!-- Workaround for https://youtrack.jetbrains.com/issue/IDEA-188690 --> + <maven.compiler.source>1.${java.version}</maven.compiler.source> + <maven.compiler.target>1.${java.version}</maven.compiler.target> </properties> <build> @@ -45,10 +46,8 @@ <target> <echo>PMD specific tasks: cleaning generated markdown</echo> <delete quiet="true"> - <fileset dir="${basedir}/src/site/markdown/rules/" - includes="**/*.md" /> - <fileset dir="${basedir}/src/site/markdown/" - includes="mergedruleset.xml" /> + <fileset dir="${basedir}/src/site/markdown/rules/" includes="**/*.md" /> + <fileset dir="${basedir}/src/site/markdown/" includes="mergedruleset.xml" /> <fileset dir="${basedir}/src/site/" includes="site.xml" /> </delete> </target> @@ -59,43 +58,6 @@ </execution> </executions> </plugin> - - <plugin> - <groupId>org.apache.maven.plugins</groupId> - <artifactId>maven-shade-plugin</artifactId> - <executions> - <execution> - <phase>package</phase> - <goals> - <goal>shade</goal> - </goals> - <configuration> - <artifactSet> - <includes> - <include>apex</include> - </includes> - <excludes> - <exclude>${project.groupId}</exclude> - </excludes> - </artifactSet> - <relocations> - <!-- apex uses an older version of asm, hence we need to shaded it away --> - <relocation> - <pattern>org.objectweb.asm</pattern> - <shadedPattern>shaded.org.objectweb.asm</shadedPattern> - </relocation> - <!-- apex uses an older version of jackson, hence we need to shaded it away --> - <relocation> - <pattern>com.fasterxml.jackson</pattern> - <shadedPattern>shaded.com.fasterxml.jackson</shadedPattern> - </relocation> - </relocations> - <shadedArtifactAttached>true</shadedArtifactAttached> - <shadedClassifierName>apex-jorje-shaded</shadedClassifierName> - </configuration> - </execution> - </executions> - </plugin> </plugins> </build> <dependencies> @@ -105,68 +67,43 @@ </dependency> <dependency> - <groupId>net.sourceforge.pmd</groupId> - <artifactId>pmd-test</artifactId> - <scope>test</scope> + <groupId>${project.groupId}</groupId> + <artifactId>pmd-apex-jorje</artifactId> + <version>${project.version}</version> + <classifier>lib</classifier> </dependency> <dependency> - <groupId>apex</groupId> - <artifactId>apex-jorje-data</artifactId> - <version>1.0-sfdc-224-SNAPSHOT-3083815933</version> + <groupId>${project.groupId}</groupId> + <artifactId>pmd-apex-jorje</artifactId> + <version>${project.version}</version> + <type>pom</type> </dependency> <dependency> - <groupId>apex</groupId> - <artifactId>apex-jorje-ide</artifactId> - <version>1.0-sfdc-224-SNAPSHOT-3083815933</version> + <groupId>commons-io</groupId> + <artifactId>commons-io</artifactId> </dependency> - <dependency> - <groupId>apex</groupId> - <artifactId>apex-jorje-parser</artifactId> - <version>1.0-sfdc-224-SNAPSHOT-3083815933</version> - </dependency> <dependency> - <groupId>apex</groupId> - <artifactId>apex-jorje-semantic</artifactId> - <version>1.0-sfdc-224-SNAPSHOT-3083815933</version> + <groupId>net.sourceforge.saxon</groupId> + <artifactId>saxon</artifactId> </dependency> - <dependency> - <groupId>apex</groupId> - <artifactId>apex-jorje-services</artifactId> - <version>1.0-sfdc-224-SNAPSHOT-3083815933</version> - </dependency> - - <dependency> - <groupId>apex</groupId> - <artifactId>apex-jorje-tools</artifactId> - <version>1.0-sfdc-224-SNAPSHOT-3083815933</version> - </dependency> <dependency> - <groupId>com.google.guava</groupId> - <artifactId>guava</artifactId> - <version>22.0</version> + <groupId>junit</groupId> + <artifactId>junit</artifactId> + <scope>test</scope> </dependency> - <dependency> - <groupId>org.antlr</groupId> - <artifactId>antlr-runtime</artifactId> - <version>3.5.2</version> + <groupId>net.sourceforge.pmd</groupId> + <artifactId>pmd-test</artifactId> + <scope>test</scope> </dependency> - </dependencies> - <repositories> - <repository> - <id>local-apex-repo</id> - <url>file://${basedir}/repo</url> - </repository> - </repositories> - <profiles> <profile> <id>designer</id> diff --git a/pmd-apex/repo/README.md b/pmd-apex/repo/README.md deleted file mode 100644 index 6922be92467..00000000000 --- a/pmd-apex/repo/README.md +++ /dev/null @@ -1,56 +0,0 @@ -# Local Maven Repo for the Apex Jorje Parser library - -You can download the needed libraries from: -<https://github.com/forcedotcom/idecore/tree/master/com.salesforce.ide.apex.core/lib> - -Apex Reference: -<https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_reference.htm> - -In order to add a the jar files to the local repo, use the following commands: - - mvn install:install-file -Dfile=apex-jorje-data-1.0-sfdc-224-SNAPSHOT.jar \ - -DgroupId=apex \ - -DartifactId=apex-jorje-data \ - -Dversion=1.0-sfdc-224-SNAPSHOT \ - -Dpackaging=jar \ - -DlocalRepositoryPath=./repo - - mvn install:install-file -Dfile=apex-jorje-ide-1.0-sfdc-224-SNAPSHOT.jar \ - -DgroupId=apex \ - -DartifactId=apex-jorje-ide \ - -Dversion=1.0-sfdc-224-SNAPSHOT-3083815933 \ - -Dpackaging=jar \ - -DlocalRepositoryPath=./repo - - mvn install:install-file -Dfile=apex-jorje-parser-1.0-sfdc-224-SNAPSHOT.jar \ - -DgroupId=apex \ - -DartifactId=apex-jorje-parser \ - -Dversion=1.0-sfdc-224-SNAPSHOT-3083815933 \ - -Dpackaging=jar \ - -DlocalRepositoryPath=./repo - - mvn install:install-file -Dfile=apex-jorje-semantic-1.0-sfdc-224-SNAPSHOT.jar \ - -DgroupId=apex \ - -DartifactId=apex-jorje-semantic \ - -Dversion=1.0-sfdc-224-SNAPSHOT-3083815933 \ - -Dpackaging=jar \ - -DlocalRepositoryPath=./repo - - mvn install:install-file -Dfile=apex-jorje-services-1.0-sfdc-224-SNAPSHOT.jar \ - -DgroupId=apex \ - -DartifactId=apex-jorje-services \ - -Dversion=1.0-sfdc-224-SNAPSHOT-3083815933 \ - -Dpackaging=jar \ - -DlocalRepositoryPath=./repo - - mvn install:install-file -Dfile=apex-jorje-tools-1.0-sfdc-224-SNAPSHOT.jar \ - -DgroupId=apex \ - -DartifactId=apex-jorje-tools \ - -Dversion=1.0-sfdc-224-SNAPSHOT-3083815933 \ - -Dpackaging=jar \ - -DlocalRepositoryPath=./repo - - -For the PMD 6.0.0 Release, the versions from -<https://github.com/forcedotcom/idecore/tree/3083815933c2d015d03417986f57bd25786d58ce/com.salesforce.ide.apex.core/lib> -have been taken. diff --git a/pmd-apex/repo/apex/apex-jorje-data/1.0-sfdc-224-SNAPSHOT-3083815933/apex-jorje-data-1.0-sfdc-224-SNAPSHOT-3083815933.jar b/pmd-apex/repo/apex/apex-jorje-data/1.0-sfdc-224-SNAPSHOT-3083815933/apex-jorje-data-1.0-sfdc-224-SNAPSHOT-3083815933.jar deleted file mode 100644 index 0dc242ad4a2..00000000000 Binary files a/pmd-apex/repo/apex/apex-jorje-data/1.0-sfdc-224-SNAPSHOT-3083815933/apex-jorje-data-1.0-sfdc-224-SNAPSHOT-3083815933.jar and /dev/null differ diff --git a/pmd-apex/repo/apex/apex-jorje-data/1.0-sfdc-224-SNAPSHOT-3083815933/apex-jorje-data-1.0-sfdc-224-SNAPSHOT-3083815933.pom b/pmd-apex/repo/apex/apex-jorje-data/1.0-sfdc-224-SNAPSHOT-3083815933/apex-jorje-data-1.0-sfdc-224-SNAPSHOT-3083815933.pom deleted file mode 100644 index 058d3840bc3..00000000000 --- a/pmd-apex/repo/apex/apex-jorje-data/1.0-sfdc-224-SNAPSHOT-3083815933/apex-jorje-data-1.0-sfdc-224-SNAPSHOT-3083815933.pom +++ /dev/null @@ -1,9 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0" - xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> - <modelVersion>4.0.0</modelVersion> - <groupId>apex</groupId> - <artifactId>apex-jorje-data</artifactId> - <version>1.0-sfdc-224-SNAPSHOT-3083815933</version> - <description>POM was created from install:install-file</description> -</project> diff --git a/pmd-apex/repo/apex/apex-jorje-data/maven-metadata-local.xml b/pmd-apex/repo/apex/apex-jorje-data/maven-metadata-local.xml deleted file mode 100644 index 6422123f774..00000000000 --- a/pmd-apex/repo/apex/apex-jorje-data/maven-metadata-local.xml +++ /dev/null @@ -1,12 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<metadata> - <groupId>apex</groupId> - <artifactId>apex-jorje-data</artifactId> - <versioning> - <release>1.0-sfdc-224-SNAPSHOT-3083815933</release> - <versions> - <version>1.0-sfdc-224-SNAPSHOT-3083815933</version> - </versions> - <lastUpdated>20170629193627</lastUpdated> - </versioning> -</metadata> diff --git a/pmd-apex/repo/apex/apex-jorje-ide/1.0-sfdc-224-SNAPSHOT-3083815933/apex-jorje-ide-1.0-sfdc-224-SNAPSHOT-3083815933.jar b/pmd-apex/repo/apex/apex-jorje-ide/1.0-sfdc-224-SNAPSHOT-3083815933/apex-jorje-ide-1.0-sfdc-224-SNAPSHOT-3083815933.jar deleted file mode 100644 index 49d659f7017..00000000000 Binary files a/pmd-apex/repo/apex/apex-jorje-ide/1.0-sfdc-224-SNAPSHOT-3083815933/apex-jorje-ide-1.0-sfdc-224-SNAPSHOT-3083815933.jar and /dev/null differ diff --git a/pmd-apex/repo/apex/apex-jorje-ide/1.0-sfdc-224-SNAPSHOT-3083815933/apex-jorje-ide-1.0-sfdc-224-SNAPSHOT-3083815933.pom b/pmd-apex/repo/apex/apex-jorje-ide/1.0-sfdc-224-SNAPSHOT-3083815933/apex-jorje-ide-1.0-sfdc-224-SNAPSHOT-3083815933.pom deleted file mode 100644 index 7de5f2f2b84..00000000000 --- a/pmd-apex/repo/apex/apex-jorje-ide/1.0-sfdc-224-SNAPSHOT-3083815933/apex-jorje-ide-1.0-sfdc-224-SNAPSHOT-3083815933.pom +++ /dev/null @@ -1,9 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0" - xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> - <modelVersion>4.0.0</modelVersion> - <groupId>apex</groupId> - <artifactId>apex-jorje-ide</artifactId> - <version>1.0-sfdc-224-SNAPSHOT-3083815933</version> - <description>POM was created from install:install-file</description> -</project> diff --git a/pmd-apex/repo/apex/apex-jorje-ide/maven-metadata-local.xml b/pmd-apex/repo/apex/apex-jorje-ide/maven-metadata-local.xml deleted file mode 100644 index 190fe95a19c..00000000000 --- a/pmd-apex/repo/apex/apex-jorje-ide/maven-metadata-local.xml +++ /dev/null @@ -1,12 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<metadata> - <groupId>apex</groupId> - <artifactId>apex-jorje-ide</artifactId> - <versioning> - <release>1.0-sfdc-224-SNAPSHOT-3083815933</release> - <versions> - <version>1.0-sfdc-224-SNAPSHOT-3083815933</version> - </versions> - <lastUpdated>20170629193403</lastUpdated> - </versioning> -</metadata> diff --git a/pmd-apex/repo/apex/apex-jorje-parser/1.0-sfdc-224-SNAPSHOT-3083815933/apex-jorje-parser-1.0-sfdc-224-SNAPSHOT-3083815933.jar b/pmd-apex/repo/apex/apex-jorje-parser/1.0-sfdc-224-SNAPSHOT-3083815933/apex-jorje-parser-1.0-sfdc-224-SNAPSHOT-3083815933.jar deleted file mode 100644 index 04f8c8daa4c..00000000000 Binary files a/pmd-apex/repo/apex/apex-jorje-parser/1.0-sfdc-224-SNAPSHOT-3083815933/apex-jorje-parser-1.0-sfdc-224-SNAPSHOT-3083815933.jar and /dev/null differ diff --git a/pmd-apex/repo/apex/apex-jorje-parser/1.0-sfdc-224-SNAPSHOT-3083815933/apex-jorje-parser-1.0-sfdc-224-SNAPSHOT-3083815933.pom b/pmd-apex/repo/apex/apex-jorje-parser/1.0-sfdc-224-SNAPSHOT-3083815933/apex-jorje-parser-1.0-sfdc-224-SNAPSHOT-3083815933.pom deleted file mode 100644 index 5556dfc70ee..00000000000 --- a/pmd-apex/repo/apex/apex-jorje-parser/1.0-sfdc-224-SNAPSHOT-3083815933/apex-jorje-parser-1.0-sfdc-224-SNAPSHOT-3083815933.pom +++ /dev/null @@ -1,9 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0" - xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> - <modelVersion>4.0.0</modelVersion> - <groupId>apex</groupId> - <artifactId>apex-jorje-parser</artifactId> - <version>1.0-sfdc-224-SNAPSHOT-3083815933</version> - <description>POM was created from install:install-file</description> -</project> diff --git a/pmd-apex/repo/apex/apex-jorje-parser/maven-metadata-local.xml b/pmd-apex/repo/apex/apex-jorje-parser/maven-metadata-local.xml deleted file mode 100644 index 206bde25a30..00000000000 --- a/pmd-apex/repo/apex/apex-jorje-parser/maven-metadata-local.xml +++ /dev/null @@ -1,12 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<metadata> - <groupId>apex</groupId> - <artifactId>apex-jorje-parser</artifactId> - <versioning> - <release>1.0-sfdc-224-SNAPSHOT-3083815933</release> - <versions> - <version>1.0-sfdc-224-SNAPSHOT-3083815933</version> - </versions> - <lastUpdated>20170629193645</lastUpdated> - </versioning> -</metadata> diff --git a/pmd-apex/repo/apex/apex-jorje-semantic/1.0-sfdc-224-SNAPSHOT-3083815933/apex-jorje-semantic-1.0-sfdc-224-SNAPSHOT-3083815933.jar b/pmd-apex/repo/apex/apex-jorje-semantic/1.0-sfdc-224-SNAPSHOT-3083815933/apex-jorje-semantic-1.0-sfdc-224-SNAPSHOT-3083815933.jar deleted file mode 100644 index 9c047a30f3c..00000000000 Binary files a/pmd-apex/repo/apex/apex-jorje-semantic/1.0-sfdc-224-SNAPSHOT-3083815933/apex-jorje-semantic-1.0-sfdc-224-SNAPSHOT-3083815933.jar and /dev/null differ diff --git a/pmd-apex/repo/apex/apex-jorje-semantic/1.0-sfdc-224-SNAPSHOT-3083815933/apex-jorje-semantic-1.0-sfdc-224-SNAPSHOT-3083815933.pom b/pmd-apex/repo/apex/apex-jorje-semantic/1.0-sfdc-224-SNAPSHOT-3083815933/apex-jorje-semantic-1.0-sfdc-224-SNAPSHOT-3083815933.pom deleted file mode 100644 index 042bbb58cb1..00000000000 --- a/pmd-apex/repo/apex/apex-jorje-semantic/1.0-sfdc-224-SNAPSHOT-3083815933/apex-jorje-semantic-1.0-sfdc-224-SNAPSHOT-3083815933.pom +++ /dev/null @@ -1,9 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0" - xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> - <modelVersion>4.0.0</modelVersion> - <groupId>apex</groupId> - <artifactId>apex-jorje-semantic</artifactId> - <version>1.0-sfdc-224-SNAPSHOT-3083815933</version> - <description>POM was created from install:install-file</description> -</project> diff --git a/pmd-apex/repo/apex/apex-jorje-semantic/maven-metadata-local.xml b/pmd-apex/repo/apex/apex-jorje-semantic/maven-metadata-local.xml deleted file mode 100644 index 512c049b0e3..00000000000 --- a/pmd-apex/repo/apex/apex-jorje-semantic/maven-metadata-local.xml +++ /dev/null @@ -1,12 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<metadata> - <groupId>apex</groupId> - <artifactId>apex-jorje-semantic</artifactId> - <versioning> - <release>1.0-sfdc-224-SNAPSHOT-3083815933</release> - <versions> - <version>1.0-sfdc-224-SNAPSHOT-3083815933</version> - </versions> - <lastUpdated>20170629193659</lastUpdated> - </versioning> -</metadata> diff --git a/pmd-apex/repo/apex/apex-jorje-services/1.0-sfdc-224-SNAPSHOT-3083815933/apex-jorje-services-1.0-sfdc-224-SNAPSHOT-3083815933.jar b/pmd-apex/repo/apex/apex-jorje-services/1.0-sfdc-224-SNAPSHOT-3083815933/apex-jorje-services-1.0-sfdc-224-SNAPSHOT-3083815933.jar deleted file mode 100644 index a4b83d818b0..00000000000 Binary files a/pmd-apex/repo/apex/apex-jorje-services/1.0-sfdc-224-SNAPSHOT-3083815933/apex-jorje-services-1.0-sfdc-224-SNAPSHOT-3083815933.jar and /dev/null differ diff --git a/pmd-apex/repo/apex/apex-jorje-services/1.0-sfdc-224-SNAPSHOT-3083815933/apex-jorje-services-1.0-sfdc-224-SNAPSHOT-3083815933.pom b/pmd-apex/repo/apex/apex-jorje-services/1.0-sfdc-224-SNAPSHOT-3083815933/apex-jorje-services-1.0-sfdc-224-SNAPSHOT-3083815933.pom deleted file mode 100644 index 4f67f8b8b7d..00000000000 --- a/pmd-apex/repo/apex/apex-jorje-services/1.0-sfdc-224-SNAPSHOT-3083815933/apex-jorje-services-1.0-sfdc-224-SNAPSHOT-3083815933.pom +++ /dev/null @@ -1,9 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0" - xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> - <modelVersion>4.0.0</modelVersion> - <groupId>apex</groupId> - <artifactId>apex-jorje-services</artifactId> - <version>1.0-sfdc-224-SNAPSHOT-3083815933</version> - <description>POM was created from install:install-file</description> -</project> diff --git a/pmd-apex/repo/apex/apex-jorje-services/maven-metadata-local.xml b/pmd-apex/repo/apex/apex-jorje-services/maven-metadata-local.xml deleted file mode 100644 index a8bc5aea710..00000000000 --- a/pmd-apex/repo/apex/apex-jorje-services/maven-metadata-local.xml +++ /dev/null @@ -1,12 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<metadata> - <groupId>apex</groupId> - <artifactId>apex-jorje-services</artifactId> - <versioning> - <release>1.0-sfdc-224-SNAPSHOT-3083815933</release> - <versions> - <version>1.0-sfdc-224-SNAPSHOT-3083815933</version> - </versions> - <lastUpdated>20170629193717</lastUpdated> - </versioning> -</metadata> diff --git a/pmd-apex/repo/apex/apex-jorje-tools/1.0-sfdc-224-SNAPSHOT-3083815933/apex-jorje-tools-1.0-sfdc-224-SNAPSHOT-3083815933.jar b/pmd-apex/repo/apex/apex-jorje-tools/1.0-sfdc-224-SNAPSHOT-3083815933/apex-jorje-tools-1.0-sfdc-224-SNAPSHOT-3083815933.jar deleted file mode 100644 index 3f858966c15..00000000000 Binary files a/pmd-apex/repo/apex/apex-jorje-tools/1.0-sfdc-224-SNAPSHOT-3083815933/apex-jorje-tools-1.0-sfdc-224-SNAPSHOT-3083815933.jar and /dev/null differ diff --git a/pmd-apex/repo/apex/apex-jorje-tools/1.0-sfdc-224-SNAPSHOT-3083815933/apex-jorje-tools-1.0-sfdc-224-SNAPSHOT-3083815933.pom b/pmd-apex/repo/apex/apex-jorje-tools/1.0-sfdc-224-SNAPSHOT-3083815933/apex-jorje-tools-1.0-sfdc-224-SNAPSHOT-3083815933.pom deleted file mode 100644 index 3830873343a..00000000000 --- a/pmd-apex/repo/apex/apex-jorje-tools/1.0-sfdc-224-SNAPSHOT-3083815933/apex-jorje-tools-1.0-sfdc-224-SNAPSHOT-3083815933.pom +++ /dev/null @@ -1,9 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0" - xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> - <modelVersion>4.0.0</modelVersion> - <groupId>apex</groupId> - <artifactId>apex-jorje-tools</artifactId> - <version>1.0-sfdc-224-SNAPSHOT-3083815933</version> - <description>POM was created from install:install-file</description> -</project> diff --git a/pmd-apex/repo/apex/apex-jorje-tools/maven-metadata-local.xml b/pmd-apex/repo/apex/apex-jorje-tools/maven-metadata-local.xml deleted file mode 100644 index 0c2c47f514e..00000000000 --- a/pmd-apex/repo/apex/apex-jorje-tools/maven-metadata-local.xml +++ /dev/null @@ -1,12 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<metadata> - <groupId>apex</groupId> - <artifactId>apex-jorje-tools</artifactId> - <versioning> - <release>1.0-sfdc-224-SNAPSHOT-3083815933</release> - <versions> - <version>1.0-sfdc-224-SNAPSHOT-3083815933</version> - </versions> - <lastUpdated>20170629193730</lastUpdated> - </versioning> -</metadata> diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/cpd/ApexLanguage.java b/pmd-apex/src/main/java/net/sourceforge/pmd/cpd/ApexLanguage.java index ef6f6ce7902..0bb7bd70148 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/cpd/ApexLanguage.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/cpd/ApexLanguage.java @@ -17,6 +17,7 @@ public ApexLanguage(Properties properties) { setProperties(properties); } + @Override public final void setProperties(Properties properties) { ApexTokenizer tokenizer = (ApexTokenizer) getTokenizer(); tokenizer.setProperties(properties); diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/cpd/ApexTokenizer.java b/pmd-apex/src/main/java/net/sourceforge/pmd/cpd/ApexTokenizer.java index f83cb65cfad..c872489c025 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/cpd/ApexTokenizer.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/cpd/ApexTokenizer.java @@ -40,6 +40,7 @@ public void tokenize(SourceCode sourceCode, Tokens tokenEntries) { ANTLRStringStream ass = new ANTLRStringStream(code.toString()); ApexLexer lexer = new ApexLexer(ass) { + @Override public void emitErrorMessage(String msg) { throw new TokenMgrError(msg, TokenMgrError.LEXICAL_ERROR); } diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ApexHandler.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ApexHandler.java index 92dd3d03032..6984ff1a36a 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ApexHandler.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ApexHandler.java @@ -13,27 +13,25 @@ import net.sourceforge.pmd.lang.XPathHandler; import net.sourceforge.pmd.lang.apex.ast.ApexNode; import net.sourceforge.pmd.lang.apex.ast.DumpFacade; -import net.sourceforge.pmd.lang.apex.metrics.ApexMetricsVisitorFacade; +import net.sourceforge.pmd.lang.apex.multifile.ApexMultifileVisitorFacade; import net.sourceforge.pmd.lang.apex.rule.ApexRuleViolationFactory; -import net.sourceforge.pmd.lang.ast.Node; -import net.sourceforge.pmd.lang.ast.xpath.AbstractASTXPathHandler; +import net.sourceforge.pmd.lang.ast.xpath.DefaultASTXPathHandler; import net.sourceforge.pmd.lang.rule.RuleViolationFactory; -import net.sf.saxon.sxpath.IndependentContext; - public class ApexHandler extends AbstractLanguageVersionHandler { @Override - public XPathHandler getXPathHandler() { - return new AbstractASTXPathHandler() { - public void initialize() { - } + public VisitorStarter getMultifileFacade() { + return rootNode -> new ApexMultifileVisitorFacade().initializeWith((ApexNode<?>) rootNode); + } - public void initialize(IndependentContext context) { - } - }; + + @Override + public XPathHandler getXPathHandler() { + return new DefaultASTXPathHandler(); } + @Override public RuleViolationFactory getRuleViolationFactory() { return ApexRuleViolationFactory.INSTANCE; } @@ -43,26 +41,14 @@ public ParserOptions getDefaultParserOptions() { return new ApexParserOptions(); } + @Override public Parser getParser(ParserOptions parserOptions) { return new ApexParser(parserOptions); } @Override public VisitorStarter getDumpFacade(Writer writer, String prefix, boolean recurse) { - return new VisitorStarter() { - public void start(Node rootNode) { - new DumpFacade().initializeWith(writer, prefix, recurse, (ApexNode<?>) rootNode); - } - }; + return rootNode -> new DumpFacade().initializeWith(writer, prefix, recurse, (ApexNode<?>) rootNode); } - @Override - public VisitorStarter getMetricsVisitorFacade() { - return new VisitorStarter() { - @Override - public void start(Node rootNode) { - new ApexMetricsVisitorFacade().initializeWith((ApexNode<?>) rootNode); - } - }; - } } diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ApexParser.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ApexParser.java index 63e0d529a74..f18cb518f49 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ApexParser.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ApexParser.java @@ -29,14 +29,17 @@ public TokenManager createTokenManager(Reader source) { return null; } + @Override public boolean canParse() { return true; } + @Override public Node parse(String fileName, Reader source) throws ParseException { return apexParser.parse(source); } + @Override public Map<Integer, String> getSuppressMap() { return apexParser.getSuppressMap(); } diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ApexParserOptions.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ApexParserOptions.java index 7f81184afac..a660af6eef7 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ApexParserOptions.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ApexParserOptions.java @@ -5,29 +5,10 @@ package net.sourceforge.pmd.lang.apex; import net.sourceforge.pmd.lang.ParserOptions; -import net.sourceforge.pmd.util.StringUtil; public class ApexParserOptions extends ParserOptions { - @Override - public int hashCode() { - final int prime = 31; - int result = super.hashCode(); - result = prime * result + (1237); - result = prime * result + (1237); - result = prime * result; - return result; - } + // empty class for now, since we don't have extra options for Apex + // Once you add something here, make sure to override hashCode and equals - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (obj == null || getClass() != obj.getClass()) { - return false; - } - final ApexParserOptions that = (ApexParserOptions) obj; - return StringUtil.isSame(this.suppressMarker, that.suppressMarker, false, false, false); - } } diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTAnnotation.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTAnnotation.java index fc049c2afd4..1ba073df4bc 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTAnnotation.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTAnnotation.java @@ -4,6 +4,12 @@ package net.sourceforge.pmd.lang.apex.ast; +import java.util.Arrays; +import java.util.Set; +import java.util.TreeSet; + +import net.sourceforge.pmd.Rule; + import apex.jorje.semantic.ast.modifier.Annotation; public class ASTAnnotation extends AbstractApexNode<Annotation> { @@ -12,7 +18,33 @@ public ASTAnnotation(Annotation annotation) { super(annotation); } + @Override public Object jjtAccept(ApexParserVisitor visitor, Object data) { return visitor.visit(this, data); } + + @Override + public String getImage() { + return node.getType().getApexName(); + } + + public boolean suppresses(Rule rule) { + final String ruleAnno = "PMD." + rule.getName(); + + if (hasImageEqualTo("SuppressWarnings")) { + for (ASTAnnotationParameter param : findChildrenOfType(ASTAnnotationParameter.class)) { + String image = param.getImage(); + + if (image != null) { + Set<String> paramValues = new TreeSet<String>(String.CASE_INSENSITIVE_ORDER); + paramValues.addAll(Arrays.asList(image.replaceAll("\\s+", "").split(","))); + if (paramValues.contains("PMD") || paramValues.contains(ruleAnno) || paramValues.contains("all")) { + return true; + } + } + } + } + + return false; + } } diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTAnnotationParameter.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTAnnotationParameter.java index c60f1f6b4fb..be17773e78c 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTAnnotationParameter.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTAnnotationParameter.java @@ -12,7 +12,16 @@ public ASTAnnotationParameter(AnnotationParameter annotationParameter) { super(annotationParameter); } + @Override public Object jjtAccept(ApexParserVisitor visitor, Object data) { return visitor.visit(this, data); } + + @Override + public String getImage() { + if (node.getValue() != null) { + return node.getValueAsString(); + } + return null; + } } diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTAnonymousClass.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTAnonymousClass.java index f75ed7e14ae..4f63c004b14 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTAnonymousClass.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTAnonymousClass.java @@ -12,6 +12,7 @@ public ASTAnonymousClass(AnonymousClass anonymousClass) { super(anonymousClass); } + @Override public Object jjtAccept(ApexParserVisitor visitor, Object data) { return visitor.visit(this, data); } diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTArrayLoadExpression.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTArrayLoadExpression.java index ad6686daa83..d5f938bb6ef 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTArrayLoadExpression.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTArrayLoadExpression.java @@ -12,6 +12,7 @@ public ASTArrayLoadExpression(ArrayLoadExpression arrayLoadExpression) { super(arrayLoadExpression); } + @Override public Object jjtAccept(ApexParserVisitor visitor, Object data) { return visitor.visit(this, data); } diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTArrayStoreExpression.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTArrayStoreExpression.java index c35a7adc4cc..0585b121f2f 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTArrayStoreExpression.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTArrayStoreExpression.java @@ -12,6 +12,7 @@ public ASTArrayStoreExpression(ArrayStoreExpression arrayStoreExpression) { super(arrayStoreExpression); } + @Override public Object jjtAccept(ApexParserVisitor visitor, Object data) { return visitor.visit(this, data); } diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTAssignmentExpression.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTAssignmentExpression.java index 7d6d2bc00f0..29f466692ba 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTAssignmentExpression.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTAssignmentExpression.java @@ -12,6 +12,7 @@ public ASTAssignmentExpression(AssignmentExpression assignmentExpression) { super(assignmentExpression); } + @Override public Object jjtAccept(ApexParserVisitor visitor, Object data) { return visitor.visit(this, data); } diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTBinaryExpression.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTBinaryExpression.java index 577140ef483..3d7e85027f1 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTBinaryExpression.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTBinaryExpression.java @@ -12,6 +12,7 @@ public ASTBinaryExpression(BinaryExpression binaryExpression) { super(binaryExpression); } + @Override public Object jjtAccept(ApexParserVisitor visitor, Object data) { return visitor.visit(this, data); } diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTBindExpressions.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTBindExpressions.java index 3357b3409d9..7501f3ab8f2 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTBindExpressions.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTBindExpressions.java @@ -12,6 +12,7 @@ public ASTBindExpressions(BindExpressions bindExpressions) { super(bindExpressions); } + @Override public Object jjtAccept(ApexParserVisitor visitor, Object data) { return visitor.visit(this, data); } diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTBlockStatement.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTBlockStatement.java index 522d9489c48..ab16315554c 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTBlockStatement.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTBlockStatement.java @@ -13,6 +13,7 @@ public ASTBlockStatement(BlockStatement blockStatement) { super(blockStatement); } + @Override public Object jjtAccept(ApexParserVisitor visitor, Object data) { return visitor.visit(this, data); } @@ -30,7 +31,7 @@ protected void handleSourceCode(final String source) { // check, whether the this block statement really begins with a curly brace // unfortunately, for-loop and if-statements always contain a block statement, // regardless whether curly braces where present or not. - char firstChar = source.charAt(node.getLoc().startIndex); + char firstChar = source.charAt(node.getLoc().getStartIndex()); curlyBrace = firstChar == '{'; } } diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTBooleanExpression.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTBooleanExpression.java index b60935098bc..f2e44dbc4aa 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTBooleanExpression.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTBooleanExpression.java @@ -12,6 +12,7 @@ public ASTBooleanExpression(BooleanExpression booleanExpression) { super(booleanExpression); } + @Override public Object jjtAccept(ApexParserVisitor visitor, Object data) { return visitor.visit(this, data); } diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTBreakStatement.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTBreakStatement.java index 66f38758da7..90b947d51b6 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTBreakStatement.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTBreakStatement.java @@ -12,6 +12,7 @@ public ASTBreakStatement(BreakStatement breakStatement) { super(breakStatement); } + @Override public Object jjtAccept(ApexParserVisitor visitor, Object data) { return visitor.visit(this, data); } diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTBridgeMethodCreator.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTBridgeMethodCreator.java index 32f5d80641b..29be1c20c01 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTBridgeMethodCreator.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTBridgeMethodCreator.java @@ -12,6 +12,7 @@ public ASTBridgeMethodCreator(BridgeMethodCreator bridgeMethodCreator) { super(bridgeMethodCreator); } + @Override public Object jjtAccept(ApexParserVisitor visitor, Object data) { return visitor.visit(this, data); } diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTCastExpression.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTCastExpression.java index 74f52d243bc..1a534a6eaca 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTCastExpression.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTCastExpression.java @@ -12,6 +12,7 @@ public ASTCastExpression(CastExpression node) { super(node); } + @Override public Object jjtAccept(ApexParserVisitor visitor, Object data) { return visitor.visit(this, data); } diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTCatchBlockStatement.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTCatchBlockStatement.java index c00239bc0a3..14f4929dee2 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTCatchBlockStatement.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTCatchBlockStatement.java @@ -12,6 +12,7 @@ public ASTCatchBlockStatement(CatchBlockStatement catchBlockStatement) { super(catchBlockStatement); } + @Override public Object jjtAccept(ApexParserVisitor visitor, Object data) { return visitor.visit(this, data); } diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTClassRefExpression.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTClassRefExpression.java index bc0669637ee..92f4c5a51b4 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTClassRefExpression.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTClassRefExpression.java @@ -12,6 +12,7 @@ public ASTClassRefExpression(ClassRefExpression classRefExpression) { super(classRefExpression); } + @Override public Object jjtAccept(ApexParserVisitor visitor, Object data) { return visitor.visit(this, data); } diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTConstructorPreamble.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTConstructorPreamble.java index 41163d83845..3f532db8d6c 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTConstructorPreamble.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTConstructorPreamble.java @@ -12,6 +12,7 @@ public ASTConstructorPreamble(ConstructorPreamble node) { super(node); } + @Override public Object jjtAccept(ApexParserVisitor visitor, Object data) { return visitor.visit(this, data); } diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTConstructorPreambleStatement.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTConstructorPreambleStatement.java index 89e9ec8d666..e8613c28443 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTConstructorPreambleStatement.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTConstructorPreambleStatement.java @@ -12,6 +12,7 @@ public ASTConstructorPreambleStatement(ConstructorPreambleStatement constructorP super(constructorPreambleStatement); } + @Override public Object jjtAccept(ApexParserVisitor visitor, Object data) { return visitor.visit(this, data); } diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTContinueStatement.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTContinueStatement.java index 36138e8412f..9a5a80aeab2 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTContinueStatement.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTContinueStatement.java @@ -12,6 +12,7 @@ public ASTContinueStatement(ContinueStatement continueStatement) { super(continueStatement); } + @Override public Object jjtAccept(ApexParserVisitor visitor, Object data) { return visitor.visit(this, data); } diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTDmlDeleteStatement.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTDmlDeleteStatement.java index 3ff1a195a7d..d876498594d 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTDmlDeleteStatement.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTDmlDeleteStatement.java @@ -12,6 +12,7 @@ public ASTDmlDeleteStatement(DmlDeleteStatement dmlDeleteStatement) { super(dmlDeleteStatement); } + @Override public Object jjtAccept(ApexParserVisitor visitor, Object data) { return visitor.visit(this, data); } diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTDmlInsertStatement.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTDmlInsertStatement.java index 2589985cb26..2b818a94f42 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTDmlInsertStatement.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTDmlInsertStatement.java @@ -12,6 +12,7 @@ public ASTDmlInsertStatement(DmlInsertStatement dmlInsertStatement) { super(dmlInsertStatement); } + @Override public Object jjtAccept(ApexParserVisitor visitor, Object data) { return visitor.visit(this, data); } diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTDmlMergeStatement.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTDmlMergeStatement.java index 41de98a930e..7e1bebb4335 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTDmlMergeStatement.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTDmlMergeStatement.java @@ -12,6 +12,7 @@ public ASTDmlMergeStatement(DmlMergeStatement dmlMergeStatement) { super(dmlMergeStatement); } + @Override public Object jjtAccept(ApexParserVisitor visitor, Object data) { return visitor.visit(this, data); } diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTDmlUndeleteStatement.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTDmlUndeleteStatement.java index 94396689e1b..6e91324bd70 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTDmlUndeleteStatement.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTDmlUndeleteStatement.java @@ -12,6 +12,7 @@ public ASTDmlUndeleteStatement(DmlUndeleteStatement dmlUndeleteStatement) { super(dmlUndeleteStatement); } + @Override public Object jjtAccept(ApexParserVisitor visitor, Object data) { return visitor.visit(this, data); } diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTDmlUpdateStatement.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTDmlUpdateStatement.java index 9f6b11bf1d3..587a5365429 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTDmlUpdateStatement.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTDmlUpdateStatement.java @@ -12,6 +12,7 @@ public ASTDmlUpdateStatement(DmlUpdateStatement dmlUpdateStatement) { super(dmlUpdateStatement); } + @Override public Object jjtAccept(ApexParserVisitor visitor, Object data) { return visitor.visit(this, data); } diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTDmlUpsertStatement.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTDmlUpsertStatement.java index 4496376d8c0..8f7512ec31e 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTDmlUpsertStatement.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTDmlUpsertStatement.java @@ -12,6 +12,7 @@ public ASTDmlUpsertStatement(DmlUpsertStatement dmlUpsertStatement) { super(dmlUpsertStatement); } + @Override public Object jjtAccept(ApexParserVisitor visitor, Object data) { return visitor.visit(this, data); } diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTDoLoopStatement.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTDoLoopStatement.java index a6cdc9aeb81..6ac37c8bbe0 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTDoLoopStatement.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTDoLoopStatement.java @@ -12,6 +12,7 @@ public ASTDoLoopStatement(DoLoopStatement doLoopStatement) { super(doLoopStatement); } + @Override public Object jjtAccept(ApexParserVisitor visitor, Object data) { return visitor.visit(this, data); } diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTExpression.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTExpression.java index 8914bcdf142..690ae31f5a8 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTExpression.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTExpression.java @@ -12,6 +12,7 @@ public ASTExpression(Expression expression) { super(expression); } + @Override public Object jjtAccept(ApexParserVisitor visitor, Object data) { return visitor.visit(this, data); } diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTExpressionStatement.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTExpressionStatement.java index ceb6f59c408..b23a76b050a 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTExpressionStatement.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTExpressionStatement.java @@ -12,7 +12,31 @@ public ASTExpressionStatement(ExpressionStatement expressionStatement) { super(expressionStatement); } + @Override public Object jjtAccept(ApexParserVisitor visitor, Object data) { return visitor.visit(this, data); } + + private int beginColumnDiff = -1; + + @Override + public int getBeginColumn() { + if (beginColumnDiff > -1) { + return super.getBeginColumn() - beginColumnDiff; + } + + if (jjtGetNumChildren() > 0 && jjtGetChild(0) instanceof ASTMethodCallExpression) { + ASTMethodCallExpression methodCallExpression = (ASTMethodCallExpression) jjtGetChild(0); + + int fullLength = methodCallExpression.getFullMethodName().length(); + int nameLength = methodCallExpression.getMethodName().length(); + if (fullLength > nameLength) { + beginColumnDiff = fullLength - nameLength; + } else { + beginColumnDiff = 0; + } + } + + return super.getBeginColumn() - beginColumnDiff; + } } diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTField.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTField.java index 96812c19128..e362a6d3af2 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTField.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTField.java @@ -4,14 +4,17 @@ package net.sourceforge.pmd.lang.apex.ast; +import net.sourceforge.pmd.Rule; + import apex.jorje.semantic.ast.member.Field; -public class ASTField extends AbstractApexNode<Field> { +public class ASTField extends AbstractApexNode<Field> implements CanSuppressWarnings { public ASTField(Field field) { super(field); } + @Override public Object jjtAccept(ApexParserVisitor visitor, Object data) { return visitor.visit(this, data); } @@ -20,4 +23,16 @@ public Object jjtAccept(ApexParserVisitor visitor, Object data) { public String getImage() { return node.getFieldInfo().getName(); } + + @Override + public boolean hasSuppressWarningsAnnotationFor(Rule rule) { + for (ASTModifierNode modifier : findChildrenOfType(ASTModifierNode.class)) { + for (ASTAnnotation a : modifier.findChildrenOfType(ASTAnnotation.class)) { + if (a.suppresses(rule)) { + return true; + } + } + } + return false; + } } diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTFieldDeclaration.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTFieldDeclaration.java index 298e337dc09..9b84e465128 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTFieldDeclaration.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTFieldDeclaration.java @@ -12,6 +12,7 @@ public ASTFieldDeclaration(FieldDeclaration fieldDeclaration) { super(fieldDeclaration); } + @Override public Object jjtAccept(ApexParserVisitor visitor, Object data) { return visitor.visit(this, data); } diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTFieldDeclarationStatements.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTFieldDeclarationStatements.java index f9c8fe5242d..56ff04c40a4 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTFieldDeclarationStatements.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTFieldDeclarationStatements.java @@ -4,15 +4,31 @@ package net.sourceforge.pmd.lang.apex.ast; +import net.sourceforge.pmd.Rule; + import apex.jorje.semantic.ast.statement.FieldDeclarationStatements; -public class ASTFieldDeclarationStatements extends AbstractApexNode<FieldDeclarationStatements> { +public class ASTFieldDeclarationStatements extends AbstractApexNode<FieldDeclarationStatements> + implements CanSuppressWarnings { public ASTFieldDeclarationStatements(FieldDeclarationStatements fieldDeclarationStatements) { super(fieldDeclarationStatements); } + @Override public Object jjtAccept(ApexParserVisitor visitor, Object data) { return visitor.visit(this, data); } + + @Override + public boolean hasSuppressWarningsAnnotationFor(Rule rule) { + for (ASTModifierNode modifier : findChildrenOfType(ASTModifierNode.class)) { + for (ASTAnnotation a : modifier.findChildrenOfType(ASTAnnotation.class)) { + if (a.suppresses(rule)) { + return true; + } + } + } + return false; + } } diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTForEachStatement.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTForEachStatement.java index 839b9b84273..5036d368dbf 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTForEachStatement.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTForEachStatement.java @@ -4,7 +4,7 @@ package net.sourceforge.pmd.lang.apex.ast; -import apex.jorje.semantic.ast.statement.ForEachStatement; +import apex.jorje.semantic.ast.statement.foreachstatement.ForEachStatement; public class ASTForEachStatement extends AbstractApexNode<ForEachStatement> { @@ -12,6 +12,7 @@ public ASTForEachStatement(ForEachStatement forEachStatement) { super(forEachStatement); } + @Override public Object jjtAccept(ApexParserVisitor visitor, Object data) { return visitor.visit(this, data); } diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTForLoopStatement.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTForLoopStatement.java index 8f9ed065125..8fb8b9610fa 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTForLoopStatement.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTForLoopStatement.java @@ -12,6 +12,7 @@ public ASTForLoopStatement(ForLoopStatement forLoopStatement) { super(forLoopStatement); } + @Override public Object jjtAccept(ApexParserVisitor visitor, Object data) { return visitor.visit(this, data); } diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTFormalComment.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTFormalComment.java new file mode 100644 index 00000000000..a59df839e13 --- /dev/null +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTFormalComment.java @@ -0,0 +1,23 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.apex.ast; + +public class ASTFormalComment extends AbstractApexNodeBase { + private String token; + + public ASTFormalComment(String token) { + super(ASTFormalComment.class); + this.token = token; + } + + @Override + public Object jjtAccept(ApexParserVisitor visitor, Object data) { + return visitor.visit(this, data); + } + + public String getToken() { + return token; + } +} diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTIfBlockStatement.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTIfBlockStatement.java index 2198f303294..e2f633e6d26 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTIfBlockStatement.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTIfBlockStatement.java @@ -12,6 +12,7 @@ public ASTIfBlockStatement(IfBlockStatement ifBlockStatement) { super(ifBlockStatement); } + @Override public Object jjtAccept(ApexParserVisitor visitor, Object data) { return visitor.visit(this, data); } diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTIfElseBlockStatement.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTIfElseBlockStatement.java index 88a4143c9cd..92571c5cad3 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTIfElseBlockStatement.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTIfElseBlockStatement.java @@ -12,6 +12,7 @@ public ASTIfElseBlockStatement(IfElseBlockStatement ifElseBlockStatement) { super(ifElseBlockStatement); } + @Override public Object jjtAccept(ApexParserVisitor visitor, Object data) { return visitor.visit(this, data); } diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTIllegalStoreExpression.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTIllegalStoreExpression.java index be7d283a1ca..29f70170373 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTIllegalStoreExpression.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTIllegalStoreExpression.java @@ -12,6 +12,7 @@ public ASTIllegalStoreExpression(IllegalStoreExpression node) { super(node); } + @Override public Object jjtAccept(ApexParserVisitor visitor, Object data) { return visitor.visit(this, data); } diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTInstanceOfExpression.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTInstanceOfExpression.java index 4ddf7411334..f1e94593cdc 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTInstanceOfExpression.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTInstanceOfExpression.java @@ -12,6 +12,7 @@ public ASTInstanceOfExpression(InstanceOfExpression instanceOfExpression) { super(instanceOfExpression); } + @Override public Object jjtAccept(ApexParserVisitor visitor, Object data) { return visitor.visit(this, data); } diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTJavaMethodCallExpression.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTJavaMethodCallExpression.java index 679ec6eb079..ba8c2b56f0a 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTJavaMethodCallExpression.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTJavaMethodCallExpression.java @@ -12,6 +12,7 @@ public ASTJavaMethodCallExpression(JavaMethodCallExpression javaMethodCallExpres super(javaMethodCallExpression); } + @Override public Object jjtAccept(ApexParserVisitor visitor, Object data) { return visitor.visit(this, data); } diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTJavaVariableExpression.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTJavaVariableExpression.java index 84f91da4f11..78c3f14e2d8 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTJavaVariableExpression.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTJavaVariableExpression.java @@ -12,6 +12,7 @@ public ASTJavaVariableExpression(JavaVariableExpression javaVariableExpression) super(javaVariableExpression); } + @Override public Object jjtAccept(ApexParserVisitor visitor, Object data) { return visitor.visit(this, data); } diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTLiteralExpression.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTLiteralExpression.java index 7d19485275d..19e7440fac9 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTLiteralExpression.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTLiteralExpression.java @@ -12,6 +12,7 @@ public ASTLiteralExpression(LiteralExpression literalExpression) { super(literalExpression); } + @Override public Object jjtAccept(ApexParserVisitor visitor, Object data) { return visitor.visit(this, data); } diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTMapEntryNode.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTMapEntryNode.java index 1f29b0b3aaf..e5518792af2 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTMapEntryNode.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTMapEntryNode.java @@ -12,6 +12,7 @@ public ASTMapEntryNode(MapEntryNode mapEntryNode) { super(mapEntryNode); } + @Override public Object jjtAccept(ApexParserVisitor visitor, Object data) { return visitor.visit(this, data); } diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTMethod.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTMethod.java index 50019156644..6aa19802613 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTMethod.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTMethod.java @@ -4,24 +4,27 @@ package net.sourceforge.pmd.lang.apex.ast; +import net.sourceforge.pmd.Rule; import net.sourceforge.pmd.lang.apex.metrics.signature.ApexOperationSignature; import net.sourceforge.pmd.lang.ast.SignedNode; import apex.jorje.semantic.ast.member.Method; -public class ASTMethod extends AbstractApexNode<Method> implements ApexQualifiableNode, SignedNode<ASTMethod> { +public class ASTMethod extends AbstractApexNode<Method> implements ApexQualifiableNode, + SignedNode<ASTMethod>, CanSuppressWarnings { public ASTMethod(Method method) { super(method); } + @Override public Object jjtAccept(ApexParserVisitor visitor, Object data) { return visitor.visit(this, data); } @Override public String getImage() { - return node.getMethodInfo().getIdentifier().value; + return node.getMethodInfo().getCanonicalName(); } @Override @@ -44,7 +47,6 @@ public int getEndColumn() { return super.getEndColumn(); } - @Override public ApexQualifiedName getQualifiedName() { return ApexQualifiedName.ofMethod(this); @@ -55,4 +57,16 @@ public ApexQualifiedName getQualifiedName() { public ApexOperationSignature getSignature() { return ApexOperationSignature.of(this); } + + @Override + public boolean hasSuppressWarningsAnnotationFor(Rule rule) { + for (ASTModifierNode modifier : findChildrenOfType(ASTModifierNode.class)) { + for (ASTAnnotation a : modifier.findChildrenOfType(ASTAnnotation.class)) { + if (a.suppresses(rule)) { + return true; + } + } + } + return false; + } } diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTMethodBlockStatement.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTMethodBlockStatement.java index 0890667eba9..98459d46343 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTMethodBlockStatement.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTMethodBlockStatement.java @@ -12,6 +12,7 @@ public ASTMethodBlockStatement(MethodBlockStatement node) { super(node); } + @Override public Object jjtAccept(ApexParserVisitor visitor, Object data) { return visitor.visit(this, data); } diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTMethodCallExpression.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTMethodCallExpression.java index 77f485a5768..b15ccfd1ec1 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTMethodCallExpression.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTMethodCallExpression.java @@ -4,6 +4,9 @@ package net.sourceforge.pmd.lang.apex.ast; +import java.util.Iterator; + +import apex.jorje.data.Identifier; import apex.jorje.semantic.ast.expression.MethodCallExpression; public class ASTMethodCallExpression extends AbstractApexNode<MethodCallExpression> { @@ -12,6 +15,7 @@ public ASTMethodCallExpression(MethodCallExpression methodCallExpression) { super(methodCallExpression); } + @Override public Object jjtAccept(ApexParserVisitor visitor, Object data) { return visitor.visit(this, data); } @@ -22,10 +26,10 @@ public String getMethodName() { public String getFullMethodName() { final String methodName = getMethodName(); - String typeName = ""; - if (!getNode().getReferenceExpression().getJadtIdentifiers().isEmpty()) { - typeName = getNode().getReferenceExpression().getJadtIdentifiers().get(0).value + "."; + StringBuilder typeName = new StringBuilder(); + for (Iterator<Identifier> it = getNode().getReferenceContext().getNames().iterator(); it.hasNext();) { + typeName.append(it.next().getValue()).append('.'); } - return typeName + methodName; + return typeName.toString() + methodName; } } diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTModifier.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTModifier.java index 58a788c4461..bed065951a4 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTModifier.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTModifier.java @@ -12,6 +12,7 @@ public ASTModifier(Modifier node) { super(node); } + @Override public Object jjtAccept(ApexParserVisitor visitor, Object data) { return visitor.visit(this, data); } diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTModifierNode.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTModifierNode.java index 1dee4943de6..811cd33f66d 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTModifierNode.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTModifierNode.java @@ -12,6 +12,7 @@ public ASTModifierNode(ModifierNode modifierNode) { super(modifierNode); } + @Override public Object jjtAccept(ApexParserVisitor visitor, Object data) { return visitor.visit(this, data); } diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTModifierOrAnnotation.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTModifierOrAnnotation.java index c374dec55b7..56e200334d5 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTModifierOrAnnotation.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTModifierOrAnnotation.java @@ -12,6 +12,7 @@ public ASTModifierOrAnnotation(ModifierOrAnnotation modifierOrAnnotation) { super(modifierOrAnnotation); } + @Override public Object jjtAccept(ApexParserVisitor visitor, Object data) { return visitor.visit(this, data); } diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTMultiStatement.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTMultiStatement.java index b4632dcc525..2b5f7e1d0e6 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTMultiStatement.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTMultiStatement.java @@ -12,6 +12,7 @@ public ASTMultiStatement(MultiStatement node) { super(node); } + @Override public Object jjtAccept(ApexParserVisitor visitor, Object data) { return visitor.visit(this, data); } diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTNestedExpression.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTNestedExpression.java index 2d07059de20..90428807028 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTNestedExpression.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTNestedExpression.java @@ -12,6 +12,7 @@ public ASTNestedExpression(NestedExpression node) { super(node); } + @Override public Object jjtAccept(ApexParserVisitor visitor, Object data) { return visitor.visit(this, data); } diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTNestedStoreExpression.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTNestedStoreExpression.java index acdaab383bb..22fc1c9f968 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTNestedStoreExpression.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTNestedStoreExpression.java @@ -12,6 +12,7 @@ public ASTNestedStoreExpression(NestedStoreExpression node) { super(node); } + @Override public Object jjtAccept(ApexParserVisitor visitor, Object data) { return visitor.visit(this, data); } diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTNewKeyValueObjectExpression.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTNewKeyValueObjectExpression.java index 6b7c51295dc..60269d4510b 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTNewKeyValueObjectExpression.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTNewKeyValueObjectExpression.java @@ -12,6 +12,7 @@ public ASTNewKeyValueObjectExpression(NewKeyValueObjectExpression node) { super(node); } + @Override public Object jjtAccept(ApexParserVisitor visitor, Object data) { return visitor.visit(this, data); } diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTNewListInitExpression.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTNewListInitExpression.java index d8e63732d23..7ee5f8ef5bc 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTNewListInitExpression.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTNewListInitExpression.java @@ -12,6 +12,7 @@ public ASTNewListInitExpression(NewListInitExpression newListInitExpression) { super(newListInitExpression); } + @Override public Object jjtAccept(ApexParserVisitor visitor, Object data) { return visitor.visit(this, data); } diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTNewListLiteralExpression.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTNewListLiteralExpression.java index f54dbf4adb0..35d2f73e81b 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTNewListLiteralExpression.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTNewListLiteralExpression.java @@ -12,6 +12,7 @@ public ASTNewListLiteralExpression(NewListLiteralExpression newListLiteralExpres super(newListLiteralExpression); } + @Override public Object jjtAccept(ApexParserVisitor visitor, Object data) { return visitor.visit(this, data); } diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTNewMapInitExpression.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTNewMapInitExpression.java index b97080fc8f7..3b3e7eaca14 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTNewMapInitExpression.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTNewMapInitExpression.java @@ -12,6 +12,7 @@ public ASTNewMapInitExpression(NewMapInitExpression newMapInitExpression) { super(newMapInitExpression); } + @Override public Object jjtAccept(ApexParserVisitor visitor, Object data) { return visitor.visit(this, data); } diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTNewMapLiteralExpression.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTNewMapLiteralExpression.java index 406f3c046ab..77cac52fa00 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTNewMapLiteralExpression.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTNewMapLiteralExpression.java @@ -12,6 +12,7 @@ public ASTNewMapLiteralExpression(NewMapLiteralExpression newMapLiteralExpressio super(newMapLiteralExpression); } + @Override public Object jjtAccept(ApexParserVisitor visitor, Object data) { return visitor.visit(this, data); } diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTNewObjectExpression.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTNewObjectExpression.java index 510e79b6c9f..7195c58afc2 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTNewObjectExpression.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTNewObjectExpression.java @@ -12,6 +12,7 @@ public ASTNewObjectExpression(NewObjectExpression newObjectExpression) { super(newObjectExpression); } + @Override public Object jjtAccept(ApexParserVisitor visitor, Object data) { return visitor.visit(this, data); } diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTNewSetInitExpression.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTNewSetInitExpression.java index 6f412d21803..130a0603fd4 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTNewSetInitExpression.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTNewSetInitExpression.java @@ -12,6 +12,7 @@ public ASTNewSetInitExpression(NewSetInitExpression newSetInitExpression) { super(newSetInitExpression); } + @Override public Object jjtAccept(ApexParserVisitor visitor, Object data) { return visitor.visit(this, data); } diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTNewSetLiteralExpression.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTNewSetLiteralExpression.java index 6e07c93260f..43517f85c6d 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTNewSetLiteralExpression.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTNewSetLiteralExpression.java @@ -12,6 +12,7 @@ public ASTNewSetLiteralExpression(NewSetLiteralExpression newSetLiteralExpressio super(newSetLiteralExpression); } + @Override public Object jjtAccept(ApexParserVisitor visitor, Object data) { return visitor.visit(this, data); } diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTPackageVersionExpression.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTPackageVersionExpression.java index 2ad57df462f..33133633706 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTPackageVersionExpression.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTPackageVersionExpression.java @@ -12,6 +12,7 @@ public ASTPackageVersionExpression(PackageVersionExpression packageVersionExpres super(packageVersionExpression); } + @Override public Object jjtAccept(ApexParserVisitor visitor, Object data) { return visitor.visit(this, data); } diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTParameter.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTParameter.java index 21eb2309497..b861bc1472c 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTParameter.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTParameter.java @@ -4,20 +4,35 @@ package net.sourceforge.pmd.lang.apex.ast; +import net.sourceforge.pmd.Rule; + import apex.jorje.semantic.ast.member.Parameter; -public class ASTParameter extends AbstractApexNode<Parameter> { +public class ASTParameter extends AbstractApexNode<Parameter> implements CanSuppressWarnings { public ASTParameter(Parameter parameter) { super(parameter); } + @Override public Object jjtAccept(ApexParserVisitor visitor, Object data) { return visitor.visit(this, data); } @Override public String getImage() { - return node.getName().value; + return node.getName().getValue(); + } + + @Override + public boolean hasSuppressWarningsAnnotationFor(Rule rule) { + for (ASTModifierNode modifier : findChildrenOfType(ASTModifierNode.class)) { + for (ASTAnnotation a : modifier.findChildrenOfType(ASTAnnotation.class)) { + if (a.suppresses(rule)) { + return true; + } + } + } + return false; } } diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTPostfixExpression.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTPostfixExpression.java index 19fe85bb4cf..4d2bd2b017c 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTPostfixExpression.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTPostfixExpression.java @@ -12,6 +12,7 @@ public ASTPostfixExpression(PostfixExpression postfixExpression) { super(postfixExpression); } + @Override public Object jjtAccept(ApexParserVisitor visitor, Object data) { return visitor.visit(this, data); } diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTPrefixExpression.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTPrefixExpression.java index 707970e767e..11db2fb4a30 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTPrefixExpression.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTPrefixExpression.java @@ -12,6 +12,7 @@ public ASTPrefixExpression(PrefixExpression prefixExpression) { super(prefixExpression); } + @Override public Object jjtAccept(ApexParserVisitor visitor, Object data) { return visitor.visit(this, data); } diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTProperty.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTProperty.java index a64e976c9a5..a0bec8ba906 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTProperty.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTProperty.java @@ -12,6 +12,7 @@ public ASTProperty(Property property) { super(property); } + @Override public Object jjtAccept(ApexParserVisitor visitor, Object data) { return visitor.visit(this, data); } diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTReferenceExpression.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTReferenceExpression.java index 8287ab39afd..32e91d26bbd 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTReferenceExpression.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTReferenceExpression.java @@ -12,6 +12,7 @@ public ASTReferenceExpression(ReferenceExpression referenceExpression) { super(referenceExpression); } + @Override public Object jjtAccept(ApexParserVisitor visitor, Object data) { return visitor.visit(this, data); } diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTReturnStatement.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTReturnStatement.java index 004e735b16d..dfe345408ed 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTReturnStatement.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTReturnStatement.java @@ -12,6 +12,7 @@ public ASTReturnStatement(ReturnStatement returnStatement) { super(returnStatement); } + @Override public Object jjtAccept(ApexParserVisitor visitor, Object data) { return visitor.visit(this, data); } diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTRunAsBlockStatement.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTRunAsBlockStatement.java index e108b99e658..ff2b84eef4b 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTRunAsBlockStatement.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTRunAsBlockStatement.java @@ -12,6 +12,7 @@ public ASTRunAsBlockStatement(RunAsBlockStatement runAsBlockStatement) { super(runAsBlockStatement); } + @Override public Object jjtAccept(ApexParserVisitor visitor, Object data) { return visitor.visit(this, data); } diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTSoqlExpression.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTSoqlExpression.java index 52c2f7355b8..b05e5dcf7c1 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTSoqlExpression.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTSoqlExpression.java @@ -12,6 +12,7 @@ public ASTSoqlExpression(SoqlExpression soqlExpression) { super(soqlExpression); } + @Override public Object jjtAccept(ApexParserVisitor visitor, Object data) { return visitor.visit(this, data); } diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTSoslExpression.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTSoslExpression.java index f029ecd490a..b773d346aaa 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTSoslExpression.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTSoslExpression.java @@ -12,6 +12,7 @@ public ASTSoslExpression(SoslExpression soslExpression) { super(soslExpression); } + @Override public Object jjtAccept(ApexParserVisitor visitor, Object data) { return visitor.visit(this, data); } diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTStandardCondition.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTStandardCondition.java index f01164cf428..9260962ef9d 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTStandardCondition.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTStandardCondition.java @@ -12,6 +12,7 @@ public ASTStandardCondition(StandardCondition standardCondition) { super(standardCondition); } + @Override public Object jjtAccept(ApexParserVisitor visitor, Object data) { return visitor.visit(this, data); } diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTStatement.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTStatement.java index f30541ebea3..9fe6e7dd1c4 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTStatement.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTStatement.java @@ -12,6 +12,7 @@ public ASTStatement(Statement statement) { super(statement); } + @Override public Object jjtAccept(ApexParserVisitor visitor, Object data) { return visitor.visit(this, data); } diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTStatementExecuted.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTStatementExecuted.java index 3307ddca4b5..0e4b865065f 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTStatementExecuted.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTStatementExecuted.java @@ -12,6 +12,7 @@ public ASTStatementExecuted(StatementExecuted node) { super(node); } + @Override public Object jjtAccept(ApexParserVisitor visitor, Object data) { return visitor.visit(this, data); } diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTSuperMethodCallExpression.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTSuperMethodCallExpression.java index 689e201e7d9..1dfbd47f46c 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTSuperMethodCallExpression.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTSuperMethodCallExpression.java @@ -12,6 +12,7 @@ public ASTSuperMethodCallExpression(SuperMethodCallExpression superMethodCallExp super(superMethodCallExpression); } + @Override public Object jjtAccept(ApexParserVisitor visitor, Object data) { return visitor.visit(this, data); } diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTSuperVariableExpression.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTSuperVariableExpression.java index 65abf340278..2b13cb7171d 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTSuperVariableExpression.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTSuperVariableExpression.java @@ -12,6 +12,7 @@ public ASTSuperVariableExpression(SuperVariableExpression superVariableExpressio super(superVariableExpression); } + @Override public Object jjtAccept(ApexParserVisitor visitor, Object data) { return visitor.visit(this, data); } diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTTernaryExpression.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTTernaryExpression.java index 8f6beae7656..3aa5622d528 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTTernaryExpression.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTTernaryExpression.java @@ -12,6 +12,7 @@ public ASTTernaryExpression(TernaryExpression ternaryExpression) { super(ternaryExpression); } + @Override public Object jjtAccept(ApexParserVisitor visitor, Object data) { return visitor.visit(this, data); } diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTThisMethodCallExpression.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTThisMethodCallExpression.java index 6ec6115930b..b3f1194c2ef 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTThisMethodCallExpression.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTThisMethodCallExpression.java @@ -12,6 +12,7 @@ public ASTThisMethodCallExpression(ThisMethodCallExpression thisMethodCallExpres super(thisMethodCallExpression); } + @Override public Object jjtAccept(ApexParserVisitor visitor, Object data) { return visitor.visit(this, data); } diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTThisVariableExpression.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTThisVariableExpression.java index 1a054880c1c..d28e79a9493 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTThisVariableExpression.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTThisVariableExpression.java @@ -12,6 +12,7 @@ public ASTThisVariableExpression(ThisVariableExpression thisVariableExpression) super(thisVariableExpression); } + @Override public Object jjtAccept(ApexParserVisitor visitor, Object data) { return visitor.visit(this, data); } diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTThrowStatement.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTThrowStatement.java index 85a60624ca2..00c977f9cad 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTThrowStatement.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTThrowStatement.java @@ -12,6 +12,7 @@ public ASTThrowStatement(ThrowStatement throwStatement) { super(throwStatement); } + @Override public Object jjtAccept(ApexParserVisitor visitor, Object data) { return visitor.visit(this, data); } diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTTriggerVariableExpression.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTTriggerVariableExpression.java index 8d1ec224ccb..45260316cc1 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTTriggerVariableExpression.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTTriggerVariableExpression.java @@ -12,6 +12,7 @@ public ASTTriggerVariableExpression(TriggerVariableExpression triggerVariableExp super(triggerVariableExpression); } + @Override public Object jjtAccept(ApexParserVisitor visitor, Object data) { return visitor.visit(this, data); } diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTTryCatchFinallyBlockStatement.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTTryCatchFinallyBlockStatement.java index 79d17b94ae9..07d8e8e83f5 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTTryCatchFinallyBlockStatement.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTTryCatchFinallyBlockStatement.java @@ -12,6 +12,7 @@ public ASTTryCatchFinallyBlockStatement(TryCatchFinallyBlockStatement tryCatchFi super(tryCatchFinallyBlockStatement); } + @Override public Object jjtAccept(ApexParserVisitor visitor, Object data) { return visitor.visit(this, data); } diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTUserClass.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTUserClass.java index c506ce13c7e..8e8594e13e7 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTUserClass.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTUserClass.java @@ -6,19 +6,22 @@ import java.lang.reflect.Field; -import apex.jorje.data.ast.Identifier; +import net.sourceforge.pmd.Rule; + +import apex.jorje.data.Identifier; import apex.jorje.semantic.ast.compilation.UserClass; -public class ASTUserClass extends ApexRootNode<UserClass> implements ASTUserClassOrInterface<UserClass> { +public class ASTUserClass extends ApexRootNode<UserClass> implements ASTUserClassOrInterface<UserClass>, + CanSuppressWarnings { private ApexQualifiedName qname; - public ASTUserClass(UserClass userClass) { super(userClass); } + @Override public Object jjtAccept(ApexParserVisitor visitor, Object data) { return visitor.visit(this, data); } @@ -30,14 +33,12 @@ public String getImage() { Field field = node.getClass().getDeclaredField("name"); field.setAccessible(true); Identifier name = (Identifier) field.get(node); - return name.value; - } catch (Exception e) { - e.printStackTrace(); + return name.getValue(); + } catch (NoSuchFieldException | IllegalArgumentException | IllegalAccessException e) { + throw new RuntimeException(e); } - return super.getImage(); } - @Override public ApexQualifiedName getQualifiedName() { if (qname == null) { @@ -59,4 +60,16 @@ public ApexQualifiedName getQualifiedName() { public TypeKind getTypeKind() { return TypeKind.CLASS; } + + @Override + public boolean hasSuppressWarningsAnnotationFor(Rule rule) { + for (ASTModifierNode modifier : findChildrenOfType(ASTModifierNode.class)) { + for (ASTAnnotation a : modifier.findChildrenOfType(ASTAnnotation.class)) { + if (a.suppresses(rule)) { + return true; + } + } + } + return false; + } } diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTUserClassMethods.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTUserClassMethods.java index da2161c13b3..0146a2d3c6d 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTUserClassMethods.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTUserClassMethods.java @@ -12,6 +12,7 @@ public ASTUserClassMethods(UserClassMethods userClassMethods) { super(userClassMethods); } + @Override public Object jjtAccept(ApexParserVisitor visitor, Object data) { return visitor.visit(this, data); } diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTUserEnum.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTUserEnum.java index 39588a4cfef..9e05252dafa 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTUserEnum.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTUserEnum.java @@ -12,6 +12,7 @@ public ASTUserEnum(UserEnum userEnum) { super(userEnum); } + @Override public Object jjtAccept(ApexParserVisitor visitor, Object data) { return visitor.visit(this, data); } diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTUserExceptionMethods.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTUserExceptionMethods.java index 522e882187e..25306a1e5c1 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTUserExceptionMethods.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTUserExceptionMethods.java @@ -12,6 +12,7 @@ public ASTUserExceptionMethods(UserExceptionMethods userExceptionMethods) { super(userExceptionMethods); } + @Override public Object jjtAccept(ApexParserVisitor visitor, Object data) { return visitor.visit(this, data); } diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTUserInterface.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTUserInterface.java index f9b8a181b69..bf7995bd264 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTUserInterface.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTUserInterface.java @@ -6,10 +6,13 @@ import java.lang.reflect.Field; -import apex.jorje.data.ast.Identifier; +import net.sourceforge.pmd.Rule; + +import apex.jorje.data.Identifier; import apex.jorje.semantic.ast.compilation.UserInterface; -public class ASTUserInterface extends ApexRootNode<UserInterface> implements ASTUserClassOrInterface<UserInterface> { +public class ASTUserInterface extends ApexRootNode<UserInterface> implements ASTUserClassOrInterface<UserInterface>, + CanSuppressWarnings { private ApexQualifiedName qname; @@ -17,6 +20,7 @@ public ASTUserInterface(UserInterface userInterface) { super(userInterface); } + @Override public Object jjtAccept(ApexParserVisitor visitor, Object data) { return visitor.visit(this, data); } @@ -27,20 +31,17 @@ public String getImage() { Field field = node.getClass().getDeclaredField("name"); field.setAccessible(true); Identifier name = (Identifier) field.get(node); - return name.value; - } catch (Exception e) { - e.printStackTrace(); + return name.getValue(); + } catch (NoSuchFieldException | IllegalArgumentException | IllegalAccessException e) { + throw new RuntimeException(e); } - return super.getImage(); } - @Override public TypeKind getTypeKind() { return TypeKind.INTERFACE; } - @Override public ApexQualifiedName getQualifiedName() { if (qname == null) { @@ -56,4 +57,16 @@ public ApexQualifiedName getQualifiedName() { return qname; } + + @Override + public boolean hasSuppressWarningsAnnotationFor(Rule rule) { + for (ASTModifierNode modifier : findChildrenOfType(ASTModifierNode.class)) { + for (ASTAnnotation a : modifier.findChildrenOfType(ASTAnnotation.class)) { + if (a.suppresses(rule)) { + return true; + } + } + } + return false; + } } diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTUserTrigger.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTUserTrigger.java index b42192b1972..3c9f5b1bef8 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTUserTrigger.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTUserTrigger.java @@ -6,7 +6,7 @@ import java.lang.reflect.Field; -import apex.jorje.data.ast.Identifier; +import apex.jorje.data.Identifier; import apex.jorje.semantic.ast.compilation.UserTrigger; public class ASTUserTrigger extends ApexRootNode<UserTrigger> { @@ -15,6 +15,7 @@ public ASTUserTrigger(UserTrigger userTrigger) { super(userTrigger); } + @Override public Object jjtAccept(ApexParserVisitor visitor, Object data) { return visitor.visit(this, data); } @@ -25,10 +26,9 @@ public String getImage() { Field field = node.getClass().getDeclaredField("name"); field.setAccessible(true); Identifier name = (Identifier) field.get(node); - return name.value; - } catch (Exception e) { - e.printStackTrace(); + return name.getValue(); + } catch (NoSuchFieldException | IllegalArgumentException | IllegalAccessException e) { + throw new RuntimeException(e); } - return super.getImage(); } } diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTVariableDeclaration.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTVariableDeclaration.java index e6ca034e98c..684e55b32ff 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTVariableDeclaration.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTVariableDeclaration.java @@ -4,14 +4,17 @@ package net.sourceforge.pmd.lang.apex.ast; +import net.sourceforge.pmd.Rule; + import apex.jorje.semantic.ast.statement.VariableDeclaration; -public class ASTVariableDeclaration extends AbstractApexNode<VariableDeclaration> { +public class ASTVariableDeclaration extends AbstractApexNode<VariableDeclaration> implements CanSuppressWarnings { public ASTVariableDeclaration(VariableDeclaration variableDeclaration) { super(variableDeclaration); } + @Override public Object jjtAccept(ApexParserVisitor visitor, Object data) { return visitor.visit(this, data); } @@ -20,4 +23,18 @@ public Object jjtAccept(ApexParserVisitor visitor, Object data) { public String getImage() { return node.getLocalInfo().getName(); } + + @Override + public boolean hasSuppressWarningsAnnotationFor(Rule rule) { + ASTVariableDeclarationStatements parent = (ASTVariableDeclarationStatements) jjtGetParent(); + + for (ASTModifierNode modifier : parent.findChildrenOfType(ASTModifierNode.class)) { + for (ASTAnnotation a : modifier.findChildrenOfType(ASTAnnotation.class)) { + if (a.suppresses(rule)) { + return true; + } + } + } + return false; + } } diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTVariableDeclarationStatements.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTVariableDeclarationStatements.java index 89473603977..0a5a9b31559 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTVariableDeclarationStatements.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTVariableDeclarationStatements.java @@ -12,6 +12,7 @@ public ASTVariableDeclarationStatements(VariableDeclarationStatements variableDe super(variableDeclarationStatements); } + @Override public Object jjtAccept(ApexParserVisitor visitor, Object data) { return visitor.visit(this, data); } diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTVariableExpression.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTVariableExpression.java index 11061d46205..19f41508863 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTVariableExpression.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTVariableExpression.java @@ -12,6 +12,7 @@ public ASTVariableExpression(VariableExpression variableExpression) { super(variableExpression); } + @Override public Object jjtAccept(ApexParserVisitor visitor, Object data) { return visitor.visit(this, data); } diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTWhileLoopStatement.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTWhileLoopStatement.java index 09bf0ad7e20..f24d2854f8b 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTWhileLoopStatement.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ASTWhileLoopStatement.java @@ -12,6 +12,7 @@ public ASTWhileLoopStatement(WhileLoopStatement whileLoopStatement) { super(whileLoopStatement); } + @Override public Object jjtAccept(ApexParserVisitor visitor, Object data) { return visitor.visit(this, data); } diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/AbstractApexNode.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/AbstractApexNode.java index d0e62947c13..bc18a8d05df 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/AbstractApexNode.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/AbstractApexNode.java @@ -4,8 +4,6 @@ package net.sourceforge.pmd.lang.apex.ast; -import net.sourceforge.pmd.lang.ast.AbstractNode; -import net.sourceforge.pmd.lang.ast.Node; import net.sourceforge.pmd.lang.ast.SourceCodePositioner; import apex.jorje.data.Location; @@ -13,12 +11,12 @@ import apex.jorje.semantic.ast.AstNode; import apex.jorje.semantic.exception.UnexpectedCodePathException; -public abstract class AbstractApexNode<T extends AstNode> extends AbstractNode implements ApexNode<T> { +public abstract class AbstractApexNode<T extends AstNode> extends AbstractApexNodeBase implements ApexNode<T> { protected final T node; public AbstractApexNode(T node) { - super(node.getClass().hashCode()); + super(node.getClass()); this.node = node; } @@ -28,20 +26,7 @@ void calculateLineNumbers(SourceCodePositioner positioner) { } Location loc = node.getLoc(); - int startOffset = loc.startIndex; - int endOffset = loc.endIndex; - // end column will be interpreted as inclusive, while endOffset/endIndex - // is exclusive - endOffset -= 1; - - this.beginLine = positioner.lineNumberFromOffset(startOffset); - this.beginColumn = positioner.columnFromOffset(this.beginLine, startOffset); - this.endLine = positioner.lineNumberFromOffset(endOffset); - this.endColumn = positioner.columnFromOffset(this.endLine, endOffset); - - if (this.endColumn < 0) { - this.endColumn = 0; - } + calculateLineNumbers(positioner, loc.getStartIndex(), loc.getEndIndex()); } protected void handleSourceCode(String source) { @@ -49,68 +34,6 @@ protected void handleSourceCode(String source) { } @Override - public int getBeginLine() { - if (this.beginLine > 0) { - return this.beginLine; - } - Node parent = jjtGetParent(); - if (parent != null) { - return parent.getBeginLine(); - } - throw new RuntimeException("Unable to determine beginning line of Node."); - } - - @Override - public int getBeginColumn() { - if (this.beginColumn > 0) { - return this.beginColumn; - } - Node parent = jjtGetParent(); - if (parent != null) { - return parent.getBeginColumn(); - } - throw new RuntimeException("Unable to determine beginning column of Node."); - } - - @Override - public int getEndLine() { - if (this.endLine > 0) { - return this.endLine; - } - Node parent = jjtGetParent(); - if (parent != null) { - return parent.getEndLine(); - } - throw new RuntimeException("Unable to determine ending line of Node."); - } - - @Override - public int getEndColumn() { - if (this.endColumn > 0) { - return this.endColumn; - } - Node parent = jjtGetParent(); - if (parent != null) { - return parent.getEndColumn(); - } - throw new RuntimeException("Unable to determine ending column of Node."); - } - - /** - * Accept the visitor. * - */ - public Object childrenAccept(ApexParserVisitor visitor, Object data) { - if (children != null) { - for (int i = 0; i < children.length; ++i) { - @SuppressWarnings("unchecked") - // we know that the children here are all ApexNodes - ApexNode<T> apexNode = (ApexNode<T>) children[i]; - apexNode.jjtAccept(visitor, data); - } - } - return data; - } - public T getNode() { return node; } @@ -130,11 +53,6 @@ protected boolean hasRealLoc() { } } - @Override - public String toString() { - return this.getClass().getSimpleName().replaceFirst("^AST", ""); - } - public String getLocation() { if (hasRealLoc()) { return String.valueOf(node.getLoc()); diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/AbstractApexNodeBase.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/AbstractApexNodeBase.java new file mode 100644 index 00000000000..8ba32fe4a72 --- /dev/null +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/AbstractApexNodeBase.java @@ -0,0 +1,103 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.apex.ast; + +import net.sourceforge.pmd.lang.ast.AbstractNode; +import net.sourceforge.pmd.lang.ast.Node; +import net.sourceforge.pmd.lang.ast.SourceCodePositioner; + +public abstract class AbstractApexNodeBase extends AbstractNode { + + public AbstractApexNodeBase(Class<?> klass) { + super(klass.hashCode()); + } + + /* package */ void calculateLineNumbers(SourceCodePositioner positioner, int startOffset, int endOffset) { + // end column will be interpreted as inclusive, while endOffset/endIndex + // is exclusive + endOffset -= 1; + + this.beginLine = positioner.lineNumberFromOffset(startOffset); + this.beginColumn = positioner.columnFromOffset(this.beginLine, startOffset); + this.endLine = positioner.lineNumberFromOffset(endOffset); + this.endColumn = positioner.columnFromOffset(this.endLine, endOffset); + + if (this.endColumn < 0) { + this.endColumn = 0; + } + } + + /** + * Accept the visitor. * + */ + public abstract Object jjtAccept(ApexParserVisitor visitor, Object data); + + /** + * Accept the visitor. * + */ + public Object childrenAccept(ApexParserVisitor visitor, Object data) { + if (children != null) { + for (int i = 0; i < children.length; ++i) { + // we know that the children here are all ApexNodes + AbstractApexNodeBase apexNode = (AbstractApexNodeBase) children[i]; + apexNode.jjtAccept(visitor, data); + } + } + return data; + } + + @Override + public int getBeginLine() { + if (this.beginLine > 0) { + return this.beginLine; + } + Node parent = jjtGetParent(); + if (parent != null) { + return parent.getBeginLine(); + } + throw new RuntimeException("Unable to determine beginning line of Node."); + } + + @Override + public int getBeginColumn() { + if (this.beginColumn > 0) { + return this.beginColumn; + } + Node parent = jjtGetParent(); + if (parent != null) { + return parent.getBeginColumn(); + } + throw new RuntimeException("Unable to determine beginning column of Node."); + } + + @Override + public int getEndLine() { + if (this.endLine > 0) { + return this.endLine; + } + Node parent = jjtGetParent(); + if (parent != null) { + return parent.getEndLine(); + } + throw new RuntimeException("Unable to determine ending line of Node."); + } + + @Override + public int getEndColumn() { + if (this.endColumn > 0) { + return this.endColumn; + } + Node parent = jjtGetParent(); + if (parent != null) { + return parent.getEndColumn(); + } + throw new RuntimeException("Unable to determine ending column of Node."); + } + + @Override + public final String getXPathNodeName() { + return this.getClass().getSimpleName().replaceFirst("^AST", ""); + } +} diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ApexParser.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ApexParser.java index b71f438e122..3cbd386de20 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ApexParser.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ApexParser.java @@ -15,6 +15,7 @@ import net.sourceforge.pmd.lang.apex.ApexParserOptions; import net.sourceforge.pmd.lang.ast.ParseException; +import apex.jorje.data.Locations; import apex.jorje.semantic.ast.compilation.Compilation; import apex.jorje.semantic.ast.compilation.UserClass; import apex.jorje.semantic.ast.compilation.UserEnum; @@ -27,24 +28,19 @@ public class ApexParser { protected final ApexParserOptions parserOptions; private Map<Integer, String> suppressMap; - private String suppressMarker = "NOPMD"; public ApexParser(ApexParserOptions parserOptions) { ApexJorjeLogging.disableLogging(); this.parserOptions = parserOptions; - - if (parserOptions.getSuppressMarker() != null) { - suppressMarker = parserOptions.getSuppressMarker(); - } } public Compilation parseApex(final String sourceCode) throws ParseException { TopLevelVisitor visitor = new TopLevelVisitor(); + Locations.useIndexFactory(); CompilerService.INSTANCE.visitAstFromString(sourceCode, visitor); - Compilation astRoot = visitor.getTopLevel(); - return astRoot; + return visitor.getTopLevel(); } public ApexNode<Compilation> parse(final Reader reader) { @@ -58,8 +54,7 @@ public ApexNode<Compilation> parse(final Reader reader) { throw new ParseException("Couldn't parse the source - there is not root node - Syntax Error??"); } - ApexNode<Compilation> tree = treeBuilder.build(astRoot); - return tree; + return treeBuilder.build(astRoot); } catch (IOException e) { throw new ParseException(e); } diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ApexParserVisitor.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ApexParserVisitor.java index 6a20eb582aa..b662e828657 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ApexParserVisitor.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ApexParserVisitor.java @@ -5,10 +5,17 @@ package net.sourceforge.pmd.lang.apex.ast; public interface ApexParserVisitor { + Object visit(AbstractApexNodeBase node, Object data); + + /** + * @deprecated This visit method will be removed with PMD 7.0.0. Use {@link #visit(AbstractApexNodeBase, Object)} + * instead. This method would not visit all nodes, e.g. ASTFormalComment would not be covered. + */ + @Deprecated // will be removed with PMD 7.0.0 Object visit(ApexNode<?> node, Object data); Object visit(ASTAnnotation node, Object data); - + Object visit(ASTAnnotationParameter node, Object data); Object visit(ASTAnonymousClass node, Object data); @@ -38,7 +45,7 @@ public interface ApexParserVisitor { Object visit(ASTClassRefExpression node, Object data); Object visit(ASTConstructorPreamble node, Object data); - + Object visit(ASTConstructorPreambleStatement node, Object data); Object visit(ASTContinueStatement node, Object data); @@ -67,6 +74,8 @@ public interface ApexParserVisitor { Object visit(ASTFieldDeclarationStatements node, Object data); + Object visit(ASTFormalComment node, Object data); + Object visit(ASTForEachStatement node, Object data); Object visit(ASTForLoopStatement node, Object data); diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ApexParserVisitorAdapter.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ApexParserVisitorAdapter.java index 639b88ec879..1680151c232 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ApexParserVisitorAdapter.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ApexParserVisitorAdapter.java @@ -6,447 +6,462 @@ public class ApexParserVisitorAdapter implements ApexParserVisitor { @Override - public Object visit(ApexNode<?> node, Object data) { + public Object visit(AbstractApexNodeBase node, Object data) { return node.childrenAccept(this, data); } + /** + * @deprecated This visit method will be removed with PMD 7.0.0. Use {@link #visit(AbstractApexNodeBase, Object)} + * instead. This method would not visit all nodes, e.g. ASTFormalComment would not be covered. + */ + @Deprecated // will be removed with PMD 7.0.0 + @Override + public Object visit(ApexNode<?> node, Object data) { + return visit((AbstractApexNodeBase) node, data); + } + @Override public Object visit(ASTMethod node, Object data) { - return visit((ApexNode<?>) node, data); + return visit((AbstractApexNodeBase) node, data); } @Override public Object visit(ASTUserClass node, Object data) { - return visit((ApexNode<?>) node, data); + return visit((AbstractApexNodeBase) node, data); } @Override public Object visit(ASTModifierNode node, Object data) { - return visit((ApexNode<?>) node, data); + return visit((AbstractApexNodeBase) node, data); } @Override public Object visit(ASTParameter node, Object data) { - return visit((ApexNode<?>) node, data); + return visit((AbstractApexNodeBase) node, data); } @Override public Object visit(ASTBlockStatement node, Object data) { - return visit((ApexNode<?>) node, data); + return visit((AbstractApexNodeBase) node, data); } @Override public Object visit(ASTUserClassMethods node, Object data) { - return visit((ApexNode<?>) node, data); + return visit((AbstractApexNodeBase) node, data); } @Override public Object visit(ASTBridgeMethodCreator node, Object data) { - return visit((ApexNode<?>) node, data); + return visit((AbstractApexNodeBase) node, data); } @Override public Object visit(ASTReturnStatement node, Object data) { - return visit((ApexNode<?>) node, data); + return visit((AbstractApexNodeBase) node, data); } @Override public Object visit(ASTLiteralExpression node, Object data) { - return visit((ApexNode<?>) node, data); + return visit((AbstractApexNodeBase) node, data); } @Override public Object visit(ASTConstructorPreambleStatement node, Object data) { - return visit((ApexNode<?>) node, data); + return visit((AbstractApexNodeBase) node, data); } @Override public Object visit(ASTUserInterface node, Object data) { - return visit((ApexNode<?>) node, data); + return visit((AbstractApexNodeBase) node, data); } @Override public Object visit(ASTUserEnum node, Object data) { - return visit((ApexNode<?>) node, data); + return visit((AbstractApexNodeBase) node, data); } @Override public Object visit(ASTFieldDeclaration node, Object data) { - return visit((ApexNode<?>) node, data); + return visit((AbstractApexNodeBase) node, data); } @Override public Object visit(ASTWhileLoopStatement node, Object data) { - return visit((ApexNode<?>) node, data); + return visit((AbstractApexNodeBase) node, data); } @Override public Object visit(ASTTryCatchFinallyBlockStatement node, Object data) { - return visit((ApexNode<?>) node, data); + return visit((AbstractApexNodeBase) node, data); } @Override public Object visit(ASTForLoopStatement node, Object data) { - return visit((ApexNode<?>) node, data); + return visit((AbstractApexNodeBase) node, data); } @Override public Object visit(ASTIfElseBlockStatement node, Object data) { - return visit((ApexNode<?>) node, data); + return visit((AbstractApexNodeBase) node, data); } @Override public Object visit(ASTIfBlockStatement node, Object data) { - return visit((ApexNode<?>) node, data); + return visit((AbstractApexNodeBase) node, data); } @Override public Object visit(ASTForEachStatement node, Object data) { - return visit((ApexNode<?>) node, data); + return visit((AbstractApexNodeBase) node, data); } @Override public Object visit(ASTField node, Object data) { - return visit((ApexNode<?>) node, data); + return visit((AbstractApexNodeBase) node, data); } @Override public Object visit(ASTBreakStatement node, Object data) { - return visit((ApexNode<?>) node, data); + return visit((AbstractApexNodeBase) node, data); } @Override public Object visit(ASTThrowStatement node, Object data) { - return visit((ApexNode<?>) node, data); + return visit((AbstractApexNodeBase) node, data); } @Override public Object visit(ASTDoLoopStatement node, Object data) { - return visit((ApexNode<?>) node, data); + return visit((AbstractApexNodeBase) node, data); } @Override public Object visit(ASTTernaryExpression node, Object data) { - return visit((ApexNode<?>) node, data); + return visit((AbstractApexNodeBase) node, data); } @Override public Object visit(ASTSoqlExpression node, Object data) { - return visit((ApexNode<?>) node, data); + return visit((AbstractApexNodeBase) node, data); } @Override public Object visit(ASTBooleanExpression node, Object data) { - return visit((ApexNode<?>) node, data); + return visit((AbstractApexNodeBase) node, data); } @Override public Object visit(ASTAnnotation node, Object data) { - return visit((ApexNode<?>) node, data); + return visit((AbstractApexNodeBase) node, data); } @Override public Object visit(ASTAnonymousClass node, Object data) { - return visit((ApexNode<?>) node, data); + return visit((AbstractApexNodeBase) node, data); } @Override public Object visit(ASTArrayLoadExpression node, Object data) { - return visit((ApexNode<?>) node, data); + return visit((AbstractApexNodeBase) node, data); } @Override public Object visit(ASTArrayStoreExpression node, Object data) { - return visit((ApexNode<?>) node, data); + return visit((AbstractApexNodeBase) node, data); } @Override public Object visit(ASTAssignmentExpression node, Object data) { - return visit((ApexNode<?>) node, data); + return visit((AbstractApexNodeBase) node, data); } @Override public Object visit(ASTBinaryExpression node, Object data) { - return visit((ApexNode<?>) node, data); + return visit((AbstractApexNodeBase) node, data); } @Override public Object visit(ASTBindExpressions node, Object data) { - return visit((ApexNode<?>) node, data); + return visit((AbstractApexNodeBase) node, data); } @Override public Object visit(ASTCatchBlockStatement node, Object data) { - return visit((ApexNode<?>) node, data); + return visit((AbstractApexNodeBase) node, data); } @Override public Object visit(ASTClassRefExpression node, Object data) { - return visit((ApexNode<?>) node, data); + return visit((AbstractApexNodeBase) node, data); } @Override public Object visit(ASTContinueStatement node, Object data) { - return visit((ApexNode<?>) node, data); + return visit((AbstractApexNodeBase) node, data); } @Override public Object visit(ASTDmlDeleteStatement node, Object data) { - return visit((ApexNode<?>) node, data); + return visit((AbstractApexNodeBase) node, data); } @Override public Object visit(ASTDmlInsertStatement node, Object data) { - return visit((ApexNode<?>) node, data); + return visit((AbstractApexNodeBase) node, data); } @Override public Object visit(ASTDmlMergeStatement node, Object data) { - return visit((ApexNode<?>) node, data); + return visit((AbstractApexNodeBase) node, data); } @Override public Object visit(ASTDmlUndeleteStatement node, Object data) { - return visit((ApexNode<?>) node, data); + return visit((AbstractApexNodeBase) node, data); } @Override public Object visit(ASTDmlUpdateStatement node, Object data) { - return visit((ApexNode<?>) node, data); + return visit((AbstractApexNodeBase) node, data); } @Override public Object visit(ASTDmlUpsertStatement node, Object data) { - return visit((ApexNode<?>) node, data); + return visit((AbstractApexNodeBase) node, data); } @Override public Object visit(ASTExpression node, Object data) { - return visit((ApexNode<?>) node, data); + return visit((AbstractApexNodeBase) node, data); } @Override public Object visit(ASTExpressionStatement node, Object data) { - return visit((ApexNode<?>) node, data); + return visit((AbstractApexNodeBase) node, data); } @Override public Object visit(ASTFieldDeclarationStatements node, Object data) { - return visit((ApexNode<?>) node, data); + return visit((AbstractApexNodeBase) node, data); } @Override public Object visit(ASTInstanceOfExpression node, Object data) { - return visit((ApexNode<?>) node, data); + return visit((AbstractApexNodeBase) node, data); } @Override public Object visit(ASTJavaMethodCallExpression node, Object data) { - return visit((ApexNode<?>) node, data); + return visit((AbstractApexNodeBase) node, data); } @Override public Object visit(ASTJavaVariableExpression node, Object data) { - return visit((ApexNode<?>) node, data); + return visit((AbstractApexNodeBase) node, data); } @Override public Object visit(ASTMapEntryNode node, Object data) { - return visit((ApexNode<?>) node, data); + return visit((AbstractApexNodeBase) node, data); } @Override public Object visit(ASTMethodCallExpression node, Object data) { - return visit((ApexNode<?>) node, data); + return visit((AbstractApexNodeBase) node, data); } @Override public Object visit(ASTModifierOrAnnotation node, Object data) { - return visit((ApexNode<?>) node, data); + return visit((AbstractApexNodeBase) node, data); } @Override public Object visit(ASTNewListInitExpression node, Object data) { - return visit((ApexNode<?>) node, data); + return visit((AbstractApexNodeBase) node, data); } @Override public Object visit(ASTNewListLiteralExpression node, Object data) { - return visit((ApexNode<?>) node, data); + return visit((AbstractApexNodeBase) node, data); } @Override public Object visit(ASTNewMapInitExpression node, Object data) { - return visit((ApexNode<?>) node, data); + return visit((AbstractApexNodeBase) node, data); } @Override public Object visit(ASTNewMapLiteralExpression node, Object data) { - return visit((ApexNode<?>) node, data); + return visit((AbstractApexNodeBase) node, data); } @Override public Object visit(ASTNewObjectExpression node, Object data) { - return visit((ApexNode<?>) node, data); + return visit((AbstractApexNodeBase) node, data); } @Override public Object visit(ASTNewSetInitExpression node, Object data) { - return visit((ApexNode<?>) node, data); + return visit((AbstractApexNodeBase) node, data); } @Override public Object visit(ASTNewSetLiteralExpression node, Object data) { - return visit((ApexNode<?>) node, data); + return visit((AbstractApexNodeBase) node, data); } @Override public Object visit(ASTPackageVersionExpression node, Object data) { - return visit((ApexNode<?>) node, data); + return visit((AbstractApexNodeBase) node, data); } @Override public Object visit(ASTPostfixExpression node, Object data) { - return visit((ApexNode<?>) node, data); + return visit((AbstractApexNodeBase) node, data); } @Override public Object visit(ASTPrefixExpression node, Object data) { - return visit((ApexNode<?>) node, data); + return visit((AbstractApexNodeBase) node, data); } @Override public Object visit(ASTProperty node, Object data) { - return visit((ApexNode<?>) node, data); + return visit((AbstractApexNodeBase) node, data); } @Override public Object visit(ASTReferenceExpression node, Object data) { - return visit((ApexNode<?>) node, data); + return visit((AbstractApexNodeBase) node, data); } @Override public Object visit(ASTRunAsBlockStatement node, Object data) { - return visit((ApexNode<?>) node, data); + return visit((AbstractApexNodeBase) node, data); } @Override public Object visit(ASTSoslExpression node, Object data) { - return visit((ApexNode<?>) node, data); + return visit((AbstractApexNodeBase) node, data); } @Override public Object visit(ASTStandardCondition node, Object data) { - return visit((ApexNode<?>) node, data); + return visit((AbstractApexNodeBase) node, data); } @Override public Object visit(ASTStatement node, Object data) { - return visit((ApexNode<?>) node, data); + return visit((AbstractApexNodeBase) node, data); } @Override public Object visit(ASTSuperMethodCallExpression node, Object data) { - return visit((ApexNode<?>) node, data); + return visit((AbstractApexNodeBase) node, data); } @Override public Object visit(ASTSuperVariableExpression node, Object data) { - return visit((ApexNode<?>) node, data); + return visit((AbstractApexNodeBase) node, data); } @Override public Object visit(ASTThisMethodCallExpression node, Object data) { - return visit((ApexNode<?>) node, data); + return visit((AbstractApexNodeBase) node, data); } @Override public Object visit(ASTThisVariableExpression node, Object data) { - return visit((ApexNode<?>) node, data); + return visit((AbstractApexNodeBase) node, data); } @Override public Object visit(ASTTriggerVariableExpression node, Object data) { - return visit((ApexNode<?>) node, data); + return visit((AbstractApexNodeBase) node, data); } @Override public Object visit(ASTUserExceptionMethods node, Object data) { - return visit((ApexNode<?>) node, data); + return visit((AbstractApexNodeBase) node, data); } @Override public Object visit(ASTUserTrigger node, Object data) { - return visit((ApexNode<?>) node, data); + return visit((AbstractApexNodeBase) node, data); } @Override public Object visit(ASTVariableDeclaration node, Object data) { - return visit((ApexNode<?>) node, data); + return visit((AbstractApexNodeBase) node, data); } @Override public Object visit(ASTVariableDeclarationStatements node, Object data) { - return visit((ApexNode<?>) node, data); + return visit((AbstractApexNodeBase) node, data); } @Override public Object visit(ASTVariableExpression node, Object data) { - return visit((ApexNode<?>) node, data); + return visit((AbstractApexNodeBase) node, data); } @Override public Object visit(ASTAnnotationParameter node, Object data) { - return visit((ApexNode<?>) node, data); + return visit((AbstractApexNodeBase) node, data); } @Override public Object visit(ASTCastExpression node, Object data) { - return visit((ApexNode<?>) node, data); + return visit((AbstractApexNodeBase) node, data); } @Override public Object visit(ASTConstructorPreamble node, Object data) { - return visit((ApexNode<?>) node, data); + return visit((AbstractApexNodeBase) node, data); } @Override public Object visit(ASTIllegalStoreExpression node, Object data) { - return visit((ApexNode<?>) node, data); + return visit((AbstractApexNodeBase) node, data); } @Override public Object visit(ASTMethodBlockStatement node, Object data) { - return visit((ApexNode<?>) node, data); + return visit((AbstractApexNodeBase) node, data); } @Override public Object visit(ASTModifier node, Object data) { - return visit((ApexNode<?>) node, data); + return visit((AbstractApexNodeBase) node, data); } @Override public Object visit(ASTMultiStatement node, Object data) { - return visit((ApexNode<?>) node, data); + return visit((AbstractApexNodeBase) node, data); } @Override public Object visit(ASTNestedExpression node, Object data) { - return visit((ApexNode<?>) node, data); + return visit((AbstractApexNodeBase) node, data); } @Override public Object visit(ASTNestedStoreExpression node, Object data) { - return visit((ApexNode<?>) node, data); + return visit((AbstractApexNodeBase) node, data); } @Override public Object visit(ASTNewKeyValueObjectExpression node, Object data) { - return visit((ApexNode<?>) node, data); + return visit((AbstractApexNodeBase) node, data); } @Override public Object visit(ASTStatementExecuted node, Object data) { - return visit((ApexNode<?>) node, data); + return visit((AbstractApexNodeBase) node, data); + } + + @Override + public Object visit(ASTFormalComment node, Object data) { + return visit((AbstractApexNodeBase) node, data); } } diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ApexParserVisitorReducedAdapter.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ApexParserVisitorReducedAdapter.java index f989a95bde1..16d9329dd46 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ApexParserVisitorReducedAdapter.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ApexParserVisitorReducedAdapter.java @@ -23,7 +23,7 @@ public final Object visit(ASTUserClass node, Object data) { public Object visit(ASTUserClassOrInterface<?> node, Object data) { - return visit((ApexNode<?>) node, data); + return visit((AbstractApexNodeBase) node, data); } } diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ApexQualifiedName.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ApexQualifiedName.java index f65605d0a3c..0757b833f6c 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ApexQualifiedName.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ApexQualifiedName.java @@ -14,12 +14,13 @@ import apex.jorje.semantic.symbol.type.TypeInfo; + /** * Qualified name of an apex class or method. * * @author Clément Fournier */ -public class ApexQualifiedName implements QualifiedName { +public final class ApexQualifiedName implements QualifiedName { private final String nameSpace; @@ -106,9 +107,9 @@ public int hashCode() { @Override public boolean equals(Object obj) { return obj instanceof ApexQualifiedName - && Objects.deepEquals(classes, ((ApexQualifiedName) obj).classes) - && Objects.equals(operation, ((ApexQualifiedName) obj).operation) - && Objects.equals(nameSpace, ((ApexQualifiedName) obj).nameSpace); + && Objects.deepEquals(classes, ((ApexQualifiedName) obj).classes) + && Objects.equals(operation, ((ApexQualifiedName) obj).operation) + && Objects.equals(nameSpace, ((ApexQualifiedName) obj).nameSpace); } @@ -154,7 +155,7 @@ private static String getOperationString(ASTMethod node) { List<TypeInfo> paramTypes = node.getNode().getMethodInfo().getParameterTypes(); - if (paramTypes.size() > 0) { + if (!paramTypes.isEmpty()) { sb.append(paramTypes.get(0).getApexName()); for (int i = 1; i < paramTypes.size(); i++) { @@ -170,8 +171,18 @@ private static String getOperationString(ASTMethod node) { static ApexQualifiedName ofMethod(ASTMethod node) { - ApexQualifiedName parent = node.getFirstParentOfType(ASTUserClassOrInterface.class).getQualifiedName(); + ASTUserClassOrInterface<?> parent = node.getFirstParentOfType(ASTUserClassOrInterface.class); + if (parent == null) { + ASTUserTrigger trigger = node.getFirstParentOfType(ASTUserTrigger.class); + String ns = trigger.getNode().getDefiningType().getNamespace().toString(); + String targetObj = trigger.getNode().getTargetName().get(0).getValue(); + + return new ApexQualifiedName(StringUtils.isEmpty(ns) ? "c" : ns, new String[]{"trigger", targetObj}, trigger.getImage()); // uses a reserved word as a class name to prevent clashes - return new ApexQualifiedName(parent.nameSpace, parent.classes, getOperationString(node)); + } else { + ApexQualifiedName baseName = parent.getQualifiedName(); + + return new ApexQualifiedName(baseName.nameSpace, baseName.classes, getOperationString(node)); + } } } diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ApexTreeBuilder.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ApexTreeBuilder.java index 21e0c6e96d9..e55005d186a 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ApexTreeBuilder.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/ApexTreeBuilder.java @@ -6,13 +6,20 @@ import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; +import java.util.ArrayList; import java.util.HashMap; +import java.util.List; +import java.util.ListIterator; import java.util.Map; import java.util.Stack; +import org.antlr.runtime.ANTLRStringStream; +import org.antlr.runtime.Token; + import net.sourceforge.pmd.lang.ast.Node; import net.sourceforge.pmd.lang.ast.SourceCodePositioner; +import apex.jorje.parser.impl.ApexLexer; import apex.jorje.semantic.ast.AstNode; import apex.jorje.semantic.ast.compilation.AnonymousClass; import apex.jorje.semantic.ast.compilation.ConstructorPreamble; @@ -87,7 +94,6 @@ import apex.jorje.semantic.ast.statement.ExpressionStatement; import apex.jorje.semantic.ast.statement.FieldDeclaration; import apex.jorje.semantic.ast.statement.FieldDeclarationStatements; -import apex.jorje.semantic.ast.statement.ForEachStatement; import apex.jorje.semantic.ast.statement.ForLoopStatement; import apex.jorje.semantic.ast.statement.IfBlockStatement; import apex.jorje.semantic.ast.statement.IfElseBlockStatement; @@ -102,6 +108,7 @@ import apex.jorje.semantic.ast.statement.VariableDeclaration; import apex.jorje.semantic.ast.statement.VariableDeclarationStatements; import apex.jorje.semantic.ast.statement.WhileLoopStatement; +import apex.jorje.semantic.ast.statement.foreachstatement.ForEachStatement; import apex.jorje.semantic.ast.visitor.AdditionalPassScope; import apex.jorje.semantic.ast.visitor.AstVisitor; import apex.jorje.semantic.exception.Errors; @@ -216,17 +223,19 @@ private static <T extends AstNode> void register(Class<T> nodeType, Class<? exte // The Apex nodes with children to build. private Stack<AstNode> parents = new Stack<>(); + + private AdditionalPassScope scope = new AdditionalPassScope(Errors.createErrors()); private final SourceCodePositioner sourceCodePositioner; private final String sourceCode; + private ListIterator<TokenLocation> apexDocTokenLocations; public ApexTreeBuilder(String sourceCode) { this.sourceCode = sourceCode; sourceCodePositioner = new SourceCodePositioner(sourceCode); + apexDocTokenLocations = buildApexDocTokenLocations(sourceCode).listIterator(); } - AdditionalPassScope scope = new AdditionalPassScope(new Errors()); - static <T extends AstNode> AbstractApexNode<T> createNodeAdapter(T node) { try { @SuppressWarnings("unchecked") @@ -271,6 +280,78 @@ public <T extends AstNode> ApexNode<T> build(T astNode) { return node; } + private void buildFormalComment(AstNode node) { + if (parents.peek() == node) { + ApexNode<?> parent = (ApexNode<?>) nodes.peek(); + TokenLocation tokenLocation = getApexDocTokenLocation(getApexDocIndex(parent)); + if (tokenLocation != null) { + ASTFormalComment comment = new ASTFormalComment(tokenLocation.token); + comment.calculateLineNumbers(sourceCodePositioner, tokenLocation.index, + tokenLocation.index + tokenLocation.token.length()); + parent.jjtAddChild(comment, 0); + comment.jjtSetParent(parent); + } + } + } + + private int getApexDocIndex(ApexNode<?> node) { + final int index = node.getNode().getLoc().getStartIndex(); + return sourceCode.lastIndexOf('\n', index); + } + + private TokenLocation getApexDocTokenLocation(int index) { + TokenLocation last = null; + while (apexDocTokenLocations.hasNext()) { + final TokenLocation location = apexDocTokenLocations.next(); + if (location.index >= index) { + // rollback, the next token corresponds to a different node + apexDocTokenLocations.previous(); + + if (last != null) { + return last; + } + return null; + } + last = location; + } + return last; + } + + private static List<TokenLocation> buildApexDocTokenLocations(String source) { + ANTLRStringStream stream = new ANTLRStringStream(source); + ApexLexer lexer = new ApexLexer(stream); + + ArrayList<TokenLocation> tokenLocations = new ArrayList<>(); + int startIndex = 0; + Token token = lexer.nextToken(); + int endIndex = lexer.getCharIndex(); + while (token.getType() != Token.EOF) { + if (token.getType() == ApexLexer.BLOCK_COMMENT) { + + // Filter only block comments starting with "/**" + if (token.getText().startsWith("/**")) { + tokenLocations.add(new TokenLocation(startIndex, token.getText())); + } + } + // TODO : Check other non-doc comments and tokens of type ApexLexer.EOL_COMMENT for "NOPMD" suppressions + startIndex = endIndex; + token = lexer.nextToken(); + endIndex = lexer.getCharIndex(); + } + + return tokenLocations; + } + + private static class TokenLocation { + int index; + String token; + + TokenLocation(int index, String token) { + this.index = index; + this.token = token; + } + } + private boolean visit(AstNode node) { if (parents.peek() == node) { return true; @@ -287,7 +368,9 @@ public boolean visit(UserEnum node, AdditionalPassScope scope) { @Override public boolean visit(UserInterface node, AdditionalPassScope scope) { - return visit(node); + final boolean ret = visit(node); + buildFormalComment(node); + return ret; } @Override @@ -572,7 +655,9 @@ public boolean visit(TryCatchFinallyBlockStatement node, AdditionalPassScope sco @Override public boolean visit(Property node, AdditionalPassScope scope) { - return visit(node); + final boolean ret = visit(node); + buildFormalComment(node); + return ret; } @Override @@ -637,12 +722,16 @@ public boolean visit(ThisVariableExpression node, AdditionalPassScope scope) { @Override public boolean visit(UserClass node, AdditionalPassScope scope) { - return visit(node); + final boolean ret = visit(node); + buildFormalComment(node); + return ret; } @Override public boolean visit(Method node, AdditionalPassScope scope) { - return visit(node); + final boolean ret = visit(node); + buildFormalComment(node); + return ret; } @Override diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/CanSuppressWarnings.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/CanSuppressWarnings.java new file mode 100644 index 00000000000..cc6249d6b0f --- /dev/null +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/CanSuppressWarnings.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.apex.ast; + +import net.sourceforge.pmd.Rule; + +public interface CanSuppressWarnings { + boolean hasSuppressWarningsAnnotationFor(Rule rule); +} diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/CompilerService.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/CompilerService.java index 825fe3305f6..f115f87a782 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/CompilerService.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/CompilerService.java @@ -29,9 +29,9 @@ * Central point for interfacing with the compiler. Based on <a href= * "https://github.com/forcedotcom/idecore/blob/master/com.salesforce.ide.apex.core/src/com/salesforce/ide/apex/internal/core/CompilerService.java" * > CompilerService</a> but with Eclipse dependencies removed. - * + * * @author nchen - * + * */ public class CompilerService { public static final CompilerService INSTANCE = new CompilerService(); @@ -41,7 +41,7 @@ public class CompilerService { /** * Configure a compiler with the default configurations: - * + * * @param symbolProvider * EmptySymbolProvider, doesn't provide any symbols that are not * part of source. @@ -56,7 +56,7 @@ public class CompilerService { /** * Configure a compiler with the following configurations: - * + * * @param symbolProvider * A way to retrieve symbols, where symbols are names of types. * @param accessEvaluator @@ -84,11 +84,10 @@ public ApexCompiler visitAstsFromStrings(List<String> sources, AstVisitor<Additi List<SourceFile> sourceFiles = sources.stream().map(s -> SourceFile.builder().setBody(s).build()) .collect(Collectors.toList()); CompilationInput compilationUnit = createCompilationInput(sourceFiles, visitor); - return compile(compilationUnit, visitor, compilerStage); + return compile(compilationUnit, compilerStage); } - private ApexCompiler compile(CompilationInput compilationInput, AstVisitor<AdditionalPassScope> visitor, - CompilerStage compilerStage) { + private ApexCompiler compile(CompilationInput compilationInput, CompilerStage compilerStage) { ApexCompiler compiler = ApexCompiler.builder().setInput(compilationInput).build(); compiler.compile(compilerStage); callAdditionalPassVisitor(compiler); @@ -107,7 +106,7 @@ private CompilationInput createCompilationInput(List<SourceFile> sourceFiles, * don't have all the parts for yet in the offline compiler. Rather than * stop all work on that, we bypass it so that we can still do useful things * like find all your types, find all your methods, etc. - * + * */ @SuppressWarnings("unchecked") private void callAdditionalPassVisitor(ApexCompiler compiler) { @@ -125,6 +124,7 @@ private void callAdditionalPassVisitor(ApexCompiler compiler) { operation.invoke(compilerContext, unit); } } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e) { + throw new RuntimeException(e); } } } diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/DumpFacade.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/DumpFacade.java index acaf2813b92..f4b0e4efb9a 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/DumpFacade.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/DumpFacade.java @@ -49,7 +49,7 @@ private void dump(ApexNode<?> node, String prefix) { writer.print(prefix); // 2) JJT Name of the Node - writer.print(node.toString()); + writer.print(node.getXPathNodeName()); // // If there are any additional details, then: diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/EmptySymbolProvider.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/EmptySymbolProvider.java index ea008d20e3f..3a01f8925b2 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/EmptySymbolProvider.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/EmptySymbolProvider.java @@ -29,11 +29,11 @@ /** * @author jspagnola */ -public class EmptySymbolProvider implements SymbolProvider { +public final class EmptySymbolProvider implements SymbolProvider { private static final EmptySymbolProvider INSTANCE = new EmptySymbolProvider(); - EmptySymbolProvider() { + private EmptySymbolProvider() { } public static EmptySymbolProvider get() { @@ -76,4 +76,9 @@ public boolean hasLabelField(final TypeInfo referencingType, final Namespace nam public String getQuickAction(TypeInfo arg0, String arg1, String arg2) { return null; } + + @Override + public TypeInfo getAggregateResultType(TypeInfo arg0) { + return null; + } } diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/TestAccessEvaluator.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/TestAccessEvaluator.java index 92e8eec5282..c321ab8ce66 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/TestAccessEvaluator.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/TestAccessEvaluator.java @@ -65,6 +65,7 @@ public class TestAccessEvaluator implements AccessEvaluator { private boolean isReallyRunningTests; private boolean hasApexGenericTypes; private boolean hasRemoteActionPerm; + private boolean hasPersonAccountApiAvailable; public TestAccessEvaluator() { validPageVersions = HashMultimap.create(); @@ -77,6 +78,7 @@ public TestAccessEvaluator() { globalComponents = new HashSet<>(); typesWithConnectApiDeserializers = new HashSet<>(); hasRemoteActionPerm = true; + hasPersonAccountApiAvailable = true; } @Override @@ -89,6 +91,11 @@ public boolean hasPermissionForPermGuard(final Namespace referencingNamespace, f return allowedPermGuards.contains(new AllowedPermGuard(referencingNamespace, orgPerm)); } + @Override + public boolean hasPersonAccountApiAvailable() { + return hasPersonAccountApiAvailable; + } + @Override public boolean hasPrivateApi() { return hasPrivateApi; @@ -105,7 +112,7 @@ public boolean hasInternalSfdc() { } @Override - public boolean isTrustedApplication() { + public boolean isTrustedApplication(TypeInfo arg0) { return isTrustedApplication; } diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/TestQueryValidators.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/TestQueryValidators.java index 96ee968a92a..5396c5bb340 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/TestQueryValidators.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/ast/TestQueryValidators.java @@ -34,6 +34,7 @@ * * @author jspagnola */ +@SuppressWarnings("PMD.MissingStaticMethodInNonInstantiatableClass") // this class provides utility classes public final class TestQueryValidators { private TestQueryValidators() { diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/AbstractApexMetric.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/AbstractApexMetric.java index 3c5bc125723..0b8c5c95941 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/AbstractApexMetric.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/AbstractApexMetric.java @@ -15,8 +15,4 @@ public abstract class AbstractApexMetric<N extends Node> extends AbstractMetric<N> { - protected ApexSignatureMatcher getSignatureMatcher() { - return ApexMetrics.getFacade().getProjectMirror(); - } - } diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/ApexMetricsFacade.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/ApexMetricsFacade.java index 8c22a7e8464..0bcd271ab97 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/ApexMetricsFacade.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/ApexMetricsFacade.java @@ -11,31 +11,20 @@ /** * Backs the static façade. + * * @author Clément Fournier */ public class ApexMetricsFacade extends AbstractMetricsFacade<ASTUserClassOrInterface<?>, ASTMethod> { - private final ApexProjectMirror projectMirror = new ApexProjectMirror(); private final ApexProjectMemoizer memoizer = new ApexProjectMemoizer(); /** Resets the entire project mirror. Used for tests. */ void reset() { - projectMirror.reset(); memoizer.reset(); } - /** - * Gets the project mirror. - * - * @return The project mirror - */ - public ApexProjectMirror getProjectMirror() { - return projectMirror; - } - - @Override protected MetricsComputer<ASTUserClassOrInterface<?>, ASTMethod> getLanguageSpecificComputer() { return ApexMetricsComputer.INSTANCE; diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/ApexMetricsVisitor.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/ApexMetricsVisitor.java deleted file mode 100644 index cfa0b8e3058..00000000000 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/ApexMetricsVisitor.java +++ /dev/null @@ -1,50 +0,0 @@ -/** - * BSD-style license; for more info see http://pmd.sourceforge.net/license.html - */ - -package net.sourceforge.pmd.lang.apex.metrics; - -import java.util.Stack; - -import net.sourceforge.pmd.lang.apex.ast.ASTMethod; -import net.sourceforge.pmd.lang.apex.ast.ASTUserClassOrInterface; -import net.sourceforge.pmd.lang.apex.ast.ApexParserVisitorReducedAdapter; - -/** - * @author Clément Fournier - */ -public class ApexMetricsVisitor extends ApexParserVisitorReducedAdapter { - - private final ApexProjectMemoizer memoizer; - private final ApexProjectMirror mirror; - - private final Stack<ApexClassStats> stack = new Stack<>(); - - - public ApexMetricsVisitor(ApexProjectMemoizer memoizer, ApexProjectMirror mirror) { - this.memoizer = memoizer; - this.mirror = mirror; - } - - @Override - public Object visit(ASTUserClassOrInterface<?> node, Object data) { - memoizer.addClassMemoizer(node.getQualifiedName()); - stack.push(mirror.getClassStats(node.getQualifiedName(), true)); - super.visit(node, data); - stack.pop(); - - return data; - } - - - - - @Override - public Object visit(ASTMethod node, Object data) { - memoizer.addOperationMemoizer(node.getQualifiedName()); - - stack.peek().addOperation(node.getQualifiedName().getOperation(), node.getSignature()); - return data; - } - -} diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/ApexMetricsVisitorFacade.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/ApexMetricsVisitorFacade.java deleted file mode 100644 index 78dbcd6870c..00000000000 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/ApexMetricsVisitorFacade.java +++ /dev/null @@ -1,23 +0,0 @@ -/** - * BSD-style license; for more info see http://pmd.sourceforge.net/license.html - */ - -package net.sourceforge.pmd.lang.apex.metrics; - -import net.sourceforge.pmd.lang.apex.ast.ApexNode; -import net.sourceforge.pmd.lang.apex.ast.ApexParserVisitorAdapter; - -/** - * @author Clément Fournier - */ -public class ApexMetricsVisitorFacade extends ApexParserVisitorAdapter { - - - public void initializeWith(ApexNode<?> rootNode) { - ApexMetricsFacade facade = ApexMetrics.getFacade(); - ApexMetricsVisitor visitor = new ApexMetricsVisitor(facade.getLanguageSpecificProjectMemoizer(), - facade.getProjectMirror()); - rootNode.jjtAccept(visitor, null); - } - -} diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/rule/CyclomaticComplexityRule.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/rule/CyclomaticComplexityRule.java deleted file mode 100644 index 0fce961dd8f..00000000000 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/rule/CyclomaticComplexityRule.java +++ /dev/null @@ -1,81 +0,0 @@ -/** - * BSD-style license; for more info see http://pmd.sourceforge.net/license.html - */ - -package net.sourceforge.pmd.lang.apex.metrics.rule; - - -import java.util.Stack; - -import net.sourceforge.pmd.lang.apex.ast.ASTMethod; -import net.sourceforge.pmd.lang.apex.ast.ASTUserClass; -import net.sourceforge.pmd.lang.apex.metrics.ApexMetrics; -import net.sourceforge.pmd.lang.apex.metrics.api.ApexClassMetricKey; -import net.sourceforge.pmd.lang.apex.metrics.api.ApexOperationMetricKey; -import net.sourceforge.pmd.lang.apex.rule.AbstractApexRule; -import net.sourceforge.pmd.lang.metrics.ResultOption; -import net.sourceforge.pmd.properties.IntegerProperty; - -/** - * Cyclomatic complexity rule using metrics. Uses Wmc to report classes (the Java rule will be updated as well in an - * upcoming PR) - * - * @author Clément Fournier - */ -public class CyclomaticComplexityRule extends AbstractApexRule { - - private static final IntegerProperty CLASS_LEVEL_DESCRIPTOR = new IntegerProperty( - "classReportLevel", "Total class complexity reporting threshold", 1, 200, 40, 1.0f); - - private static final IntegerProperty METHOD_LEVEL_DESCRIPTOR = new IntegerProperty( - "methodReportLevel", "Cyclomatic complexity reporting threshold", 1, 30, 10, 1.0f); - - Stack<String> classNames = new Stack<>(); - - - public CyclomaticComplexityRule() { - definePropertyDescriptor(CLASS_LEVEL_DESCRIPTOR); - definePropertyDescriptor(METHOD_LEVEL_DESCRIPTOR); - } - - - @Override - public Object visit(ASTUserClass node, Object data) { - - classNames.push(node.getImage()); - super.visit(node, data); - classNames.pop(); - - if (ApexClassMetricKey.WMC.supports(node)) { - int classWmc = (int) ApexMetrics.get(ApexClassMetricKey.WMC, node); - - if (classWmc >= getProperty(CLASS_LEVEL_DESCRIPTOR)) { - int classHighest = (int) ApexMetrics.get(ApexOperationMetricKey.CYCLO, node, ResultOption.HIGHEST); - - String[] messageParams = {"class", - node.getImage(), - " total", - classWmc + " (highest " + classHighest + ")", }; - - addViolation(data, node, messageParams); - } - } - return data; - } - - - @Override - public final Object visit(ASTMethod node, Object data) { - - int cyclo = (int) ApexMetrics.get(ApexOperationMetricKey.CYCLO, node); - if (cyclo >= getProperty(METHOD_LEVEL_DESCRIPTOR)) { - addViolation(data, node, new String[] {node.getImage().equals(classNames.peek()) ? "constructor" : "method", - node.getQualifiedName().getOperation(), - "", - "" + cyclo, }); - } - - return data; - } - -} diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/signature/ApexOperationSigMask.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/signature/ApexOperationSigMask.java index 38230937428..feb8e6f5bca 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/signature/ApexOperationSigMask.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/signature/ApexOperationSigMask.java @@ -18,10 +18,6 @@ public class ApexOperationSigMask { private Set<Visibility> visMask = EnumSet.allOf(Visibility.class); - public ApexOperationSigMask() { - } - - /** * Sets the mask to cover all visibilities. */ diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/ApexClassStats.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/multifile/ApexClassStats.java similarity index 95% rename from pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/ApexClassStats.java rename to pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/multifile/ApexClassStats.java index 6619dafec93..0f96f0aa349 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/ApexClassStats.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/multifile/ApexClassStats.java @@ -2,7 +2,7 @@ * BSD-style license; for more info see http://pmd.sourceforge.net/license.html */ -package net.sourceforge.pmd.lang.apex.metrics; +package net.sourceforge.pmd.lang.apex.multifile; import java.util.HashMap; import java.util.HashSet; diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/multifile/ApexMultifileVisitor.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/multifile/ApexMultifileVisitor.java new file mode 100644 index 00000000000..d617a193886 --- /dev/null +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/multifile/ApexMultifileVisitor.java @@ -0,0 +1,44 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.apex.multifile; + +import java.util.Stack; + +import net.sourceforge.pmd.lang.apex.ast.ASTMethod; +import net.sourceforge.pmd.lang.apex.ast.ASTUserClassOrInterface; +import net.sourceforge.pmd.lang.apex.ast.ApexParserVisitorReducedAdapter; + +/** + * @author Clément Fournier + */ +public class ApexMultifileVisitor extends ApexParserVisitorReducedAdapter { + + private final ApexProjectMirror mirror; + + private final Stack<ApexClassStats> stack = new Stack<>(); + + + public ApexMultifileVisitor(ApexProjectMirror mirror) { + this.mirror = mirror; + } + + + @Override + public Object visit(ASTUserClassOrInterface<?> node, Object data) { + stack.push(mirror.getClassStats(node.getQualifiedName(), true)); + super.visit(node, data); + stack.pop(); + + return data; + } + + + @Override + public Object visit(ASTMethod node, Object data) { + stack.peek().addOperation(node.getQualifiedName().getOperation(), node.getSignature()); + return data; + } + +} diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/multifile/ApexMultifileVisitorFacade.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/multifile/ApexMultifileVisitorFacade.java new file mode 100644 index 00000000000..dfea0a69f3d --- /dev/null +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/multifile/ApexMultifileVisitorFacade.java @@ -0,0 +1,20 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.apex.multifile; + +import net.sourceforge.pmd.lang.apex.ast.ApexNode; +import net.sourceforge.pmd.lang.apex.ast.ApexParserVisitorAdapter; + +/** + * @author Clément Fournier + */ +public class ApexMultifileVisitorFacade extends ApexParserVisitorAdapter { + + public void initializeWith(ApexNode<?> rootNode) { + ApexMultifileVisitor visitor = new ApexMultifileVisitor(ApexProjectMirror.INSTANCE); + rootNode.jjtAccept(visitor, null); + } + +} diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/ApexProjectMirror.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/multifile/ApexProjectMirror.java similarity index 79% rename from pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/ApexProjectMirror.java rename to pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/multifile/ApexProjectMirror.java index a747db733bc..3283b6de556 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/metrics/ApexProjectMirror.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/multifile/ApexProjectMirror.java @@ -2,12 +2,13 @@ * BSD-style license; for more info see http://pmd.sourceforge.net/license.html */ -package net.sourceforge.pmd.lang.apex.metrics; +package net.sourceforge.pmd.lang.apex.multifile; import java.util.HashMap; import java.util.Map; import net.sourceforge.pmd.lang.apex.ast.ApexQualifiedName; +import net.sourceforge.pmd.lang.apex.metrics.ApexSignatureMatcher; import net.sourceforge.pmd.lang.apex.metrics.signature.ApexOperationSigMask; /** @@ -15,10 +16,15 @@ * * @author Clément Fournier */ -public class ApexProjectMirror implements ApexSignatureMatcher { +final class ApexProjectMirror implements ApexSignatureMatcher { + + static final ApexProjectMirror INSTANCE = new ApexProjectMirror(); private final Map<ApexQualifiedName, ApexClassStats> classes = new HashMap<>(); + private ApexProjectMirror() { + } + void reset() { classes.clear(); diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/AbstractApexRule.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/AbstractApexRule.java index 0984252fb20..9a139bfd281 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/AbstractApexRule.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/AbstractApexRule.java @@ -43,6 +43,7 @@ import net.sourceforge.pmd.lang.apex.ast.ASTFieldDeclarationStatements; import net.sourceforge.pmd.lang.apex.ast.ASTForEachStatement; import net.sourceforge.pmd.lang.apex.ast.ASTForLoopStatement; +import net.sourceforge.pmd.lang.apex.ast.ASTFormalComment; import net.sourceforge.pmd.lang.apex.ast.ASTIfBlockStatement; import net.sourceforge.pmd.lang.apex.ast.ASTIfElseBlockStatement; import net.sourceforge.pmd.lang.apex.ast.ASTIllegalStoreExpression; @@ -99,6 +100,7 @@ import net.sourceforge.pmd.lang.apex.ast.ASTVariableDeclarationStatements; import net.sourceforge.pmd.lang.apex.ast.ASTVariableExpression; import net.sourceforge.pmd.lang.apex.ast.ASTWhileLoopStatement; +import net.sourceforge.pmd.lang.apex.ast.AbstractApexNodeBase; import net.sourceforge.pmd.lang.apex.ast.ApexNode; import net.sourceforge.pmd.lang.apex.ast.ApexParserVisitor; import net.sourceforge.pmd.lang.ast.Node; @@ -121,6 +123,7 @@ public ParserOptions getParserOptions() { return new ApexParserOptions(); } + @Override public void apply(List<? extends Node> nodes, RuleContext ctx) { visitAll(nodes, ctx); } @@ -138,448 +141,463 @@ protected void visitAll(List<? extends Node> nodes, RuleContext ctx) { } @Override - public Object visit(ApexNode<?> node, Object data) { + public Object visit(AbstractApexNodeBase node, Object data) { node.childrenAccept(this, data); return null; } + /** + * @deprecated This visit method will be removed with PMD 7.0.0. Use {@link #visit(AbstractApexNodeBase, Object)} + * instead. This method would not visit all nodes, e.g. ASTFormalComment would not be covered. + */ + @Deprecated // will be removed with PMD 7.0.0 + @Override + public Object visit(ApexNode<?> node, Object data) { + return visit((AbstractApexNodeBase) node, data); + } + @Override public Object visit(ASTMethod node, Object data) { - return visit((ApexNode<?>) node, data); + return visit((AbstractApexNodeBase) node, data); } @Override public Object visit(ASTUserClass node, Object data) { - return visit((ApexNode<?>) node, data); + return visit((AbstractApexNodeBase) node, data); } @Override public Object visit(ASTModifierNode node, Object data) { - return visit((ApexNode<?>) node, data); + return visit((AbstractApexNodeBase) node, data); } @Override public Object visit(ASTParameter node, Object data) { - return visit((ApexNode<?>) node, data); + return visit((AbstractApexNodeBase) node, data); } @Override public Object visit(ASTBlockStatement node, Object data) { - return visit((ApexNode<?>) node, data); + return visit((AbstractApexNodeBase) node, data); } @Override public Object visit(ASTUserClassMethods node, Object data) { - return visit((ApexNode<?>) node, data); + return visit((AbstractApexNodeBase) node, data); } @Override public Object visit(ASTBridgeMethodCreator node, Object data) { - return visit((ApexNode<?>) node, data); + return visit((AbstractApexNodeBase) node, data); } @Override public Object visit(ASTReturnStatement node, Object data) { - return visit((ApexNode<?>) node, data); + return visit((AbstractApexNodeBase) node, data); } @Override public Object visit(ASTLiteralExpression node, Object data) { - return visit((ApexNode<?>) node, data); + return visit((AbstractApexNodeBase) node, data); } @Override public Object visit(ASTConstructorPreambleStatement node, Object data) { - return visit((ApexNode<?>) node, data); + return visit((AbstractApexNodeBase) node, data); } @Override public Object visit(ASTUserInterface node, Object data) { - return visit((ApexNode<?>) node, data); + return visit((AbstractApexNodeBase) node, data); } @Override public Object visit(ASTUserEnum node, Object data) { - return visit((ApexNode<?>) node, data); + return visit((AbstractApexNodeBase) node, data); } @Override public Object visit(ASTFieldDeclaration node, Object data) { - return visit((ApexNode<?>) node, data); + return visit((AbstractApexNodeBase) node, data); } @Override public Object visit(ASTWhileLoopStatement node, Object data) { - return visit((ApexNode<?>) node, data); + return visit((AbstractApexNodeBase) node, data); } @Override public Object visit(ASTTryCatchFinallyBlockStatement node, Object data) { - return visit((ApexNode<?>) node, data); + return visit((AbstractApexNodeBase) node, data); } @Override public Object visit(ASTForLoopStatement node, Object data) { - return visit((ApexNode<?>) node, data); + return visit((AbstractApexNodeBase) node, data); } @Override public Object visit(ASTIfElseBlockStatement node, Object data) { - return visit((ApexNode<?>) node, data); + return visit((AbstractApexNodeBase) node, data); } @Override public Object visit(ASTIfBlockStatement node, Object data) { - return visit((ApexNode<?>) node, data); + return visit((AbstractApexNodeBase) node, data); } @Override public Object visit(ASTForEachStatement node, Object data) { - return visit((ApexNode<?>) node, data); + return visit((AbstractApexNodeBase) node, data); } @Override public Object visit(ASTField node, Object data) { - return visit((ApexNode<?>) node, data); + return visit((AbstractApexNodeBase) node, data); } @Override public Object visit(ASTBreakStatement node, Object data) { - return visit((ApexNode<?>) node, data); + return visit((AbstractApexNodeBase) node, data); } @Override public Object visit(ASTThrowStatement node, Object data) { - return visit((ApexNode<?>) node, data); + return visit((AbstractApexNodeBase) node, data); } @Override public Object visit(ASTDoLoopStatement node, Object data) { - return visit((ApexNode<?>) node, data); + return visit((AbstractApexNodeBase) node, data); } @Override public Object visit(ASTTernaryExpression node, Object data) { - return visit((ApexNode<?>) node, data); + return visit((AbstractApexNodeBase) node, data); } @Override public Object visit(ASTSoqlExpression node, Object data) { - return visit((ApexNode<?>) node, data); + return visit((AbstractApexNodeBase) node, data); } @Override public Object visit(ASTBooleanExpression node, Object data) { - return visit((ApexNode<?>) node, data); + return visit((AbstractApexNodeBase) node, data); } @Override public Object visit(ASTAnnotation node, Object data) { - return visit((ApexNode<?>) node, data); + return visit((AbstractApexNodeBase) node, data); } @Override public Object visit(ASTAnonymousClass node, Object data) { - return visit((ApexNode<?>) node, data); + return visit((AbstractApexNodeBase) node, data); } @Override public Object visit(ASTArrayLoadExpression node, Object data) { - return visit((ApexNode<?>) node, data); + return visit((AbstractApexNodeBase) node, data); } @Override public Object visit(ASTArrayStoreExpression node, Object data) { - return visit((ApexNode<?>) node, data); + return visit((AbstractApexNodeBase) node, data); } @Override public Object visit(ASTAssignmentExpression node, Object data) { - return visit((ApexNode<?>) node, data); + return visit((AbstractApexNodeBase) node, data); } @Override public Object visit(ASTBinaryExpression node, Object data) { - return visit((ApexNode<?>) node, data); + return visit((AbstractApexNodeBase) node, data); } @Override public Object visit(ASTBindExpressions node, Object data) { - return visit((ApexNode<?>) node, data); + return visit((AbstractApexNodeBase) node, data); } @Override public Object visit(ASTCatchBlockStatement node, Object data) { - return visit((ApexNode<?>) node, data); + return visit((AbstractApexNodeBase) node, data); } @Override public Object visit(ASTClassRefExpression node, Object data) { - return visit((ApexNode<?>) node, data); + return visit((AbstractApexNodeBase) node, data); } @Override public Object visit(ASTContinueStatement node, Object data) { - return visit((ApexNode<?>) node, data); + return visit((AbstractApexNodeBase) node, data); } @Override public Object visit(ASTDmlDeleteStatement node, Object data) { - return visit((ApexNode<?>) node, data); + return visit((AbstractApexNodeBase) node, data); } @Override public Object visit(ASTDmlInsertStatement node, Object data) { - return visit((ApexNode<?>) node, data); + return visit((AbstractApexNodeBase) node, data); } @Override public Object visit(ASTDmlMergeStatement node, Object data) { - return visit((ApexNode<?>) node, data); + return visit((AbstractApexNodeBase) node, data); } @Override public Object visit(ASTDmlUndeleteStatement node, Object data) { - return visit((ApexNode<?>) node, data); + return visit((AbstractApexNodeBase) node, data); } @Override public Object visit(ASTDmlUpdateStatement node, Object data) { - return visit((ApexNode<?>) node, data); + return visit((AbstractApexNodeBase) node, data); } @Override public Object visit(ASTDmlUpsertStatement node, Object data) { - return visit((ApexNode<?>) node, data); + return visit((AbstractApexNodeBase) node, data); } @Override public Object visit(ASTExpression node, Object data) { - return visit((ApexNode<?>) node, data); + return visit((AbstractApexNodeBase) node, data); } @Override public Object visit(ASTExpressionStatement node, Object data) { - return visit((ApexNode<?>) node, data); + return visit((AbstractApexNodeBase) node, data); } @Override public Object visit(ASTFieldDeclarationStatements node, Object data) { - return visit((ApexNode<?>) node, data); + return visit((AbstractApexNodeBase) node, data); } @Override public Object visit(ASTInstanceOfExpression node, Object data) { - return visit((ApexNode<?>) node, data); + return visit((AbstractApexNodeBase) node, data); } @Override public Object visit(ASTJavaMethodCallExpression node, Object data) { - return visit((ApexNode<?>) node, data); + return visit((AbstractApexNodeBase) node, data); } @Override public Object visit(ASTJavaVariableExpression node, Object data) { - return visit((ApexNode<?>) node, data); + return visit((AbstractApexNodeBase) node, data); } @Override public Object visit(ASTMapEntryNode node, Object data) { - return visit((ApexNode<?>) node, data); + return visit((AbstractApexNodeBase) node, data); } @Override public Object visit(ASTMethodCallExpression node, Object data) { - return visit((ApexNode<?>) node, data); + return visit((AbstractApexNodeBase) node, data); } @Override public Object visit(ASTModifierOrAnnotation node, Object data) { - return visit((ApexNode<?>) node, data); + return visit((AbstractApexNodeBase) node, data); } @Override public Object visit(ASTNewListInitExpression node, Object data) { - return visit((ApexNode<?>) node, data); + return visit((AbstractApexNodeBase) node, data); } @Override public Object visit(ASTNewListLiteralExpression node, Object data) { - return visit((ApexNode<?>) node, data); + return visit((AbstractApexNodeBase) node, data); } @Override public Object visit(ASTNewMapInitExpression node, Object data) { - return visit((ApexNode<?>) node, data); + return visit((AbstractApexNodeBase) node, data); } @Override public Object visit(ASTNewMapLiteralExpression node, Object data) { - return visit((ApexNode<?>) node, data); + return visit((AbstractApexNodeBase) node, data); } @Override public Object visit(ASTNewObjectExpression node, Object data) { - return visit((ApexNode<?>) node, data); + return visit((AbstractApexNodeBase) node, data); } @Override public Object visit(ASTNewSetInitExpression node, Object data) { - return visit((ApexNode<?>) node, data); + return visit((AbstractApexNodeBase) node, data); } @Override public Object visit(ASTNewSetLiteralExpression node, Object data) { - return visit((ApexNode<?>) node, data); + return visit((AbstractApexNodeBase) node, data); } @Override public Object visit(ASTPackageVersionExpression node, Object data) { - return visit((ApexNode<?>) node, data); + return visit((AbstractApexNodeBase) node, data); } @Override public Object visit(ASTPostfixExpression node, Object data) { - return visit((ApexNode<?>) node, data); + return visit((AbstractApexNodeBase) node, data); } @Override public Object visit(ASTPrefixExpression node, Object data) { - return visit((ApexNode<?>) node, data); + return visit((AbstractApexNodeBase) node, data); } @Override public Object visit(ASTProperty node, Object data) { - return visit((ApexNode<?>) node, data); + return visit((AbstractApexNodeBase) node, data); } @Override public Object visit(ASTReferenceExpression node, Object data) { - return visit((ApexNode<?>) node, data); + return visit((AbstractApexNodeBase) node, data); } @Override public Object visit(ASTRunAsBlockStatement node, Object data) { - return visit((ApexNode<?>) node, data); + return visit((AbstractApexNodeBase) node, data); } @Override public Object visit(ASTSoslExpression node, Object data) { - return visit((ApexNode<?>) node, data); + return visit((AbstractApexNodeBase) node, data); } @Override public Object visit(ASTStandardCondition node, Object data) { - return visit((ApexNode<?>) node, data); + return visit((AbstractApexNodeBase) node, data); } @Override public Object visit(ASTStatement node, Object data) { - return visit((ApexNode<?>) node, data); + return visit((AbstractApexNodeBase) node, data); } @Override public Object visit(ASTSuperMethodCallExpression node, Object data) { - return visit((ApexNode<?>) node, data); + return visit((AbstractApexNodeBase) node, data); } @Override public Object visit(ASTSuperVariableExpression node, Object data) { - return visit((ApexNode<?>) node, data); + return visit((AbstractApexNodeBase) node, data); } @Override public Object visit(ASTThisMethodCallExpression node, Object data) { - return visit((ApexNode<?>) node, data); + return visit((AbstractApexNodeBase) node, data); } @Override public Object visit(ASTThisVariableExpression node, Object data) { - return visit((ApexNode<?>) node, data); + return visit((AbstractApexNodeBase) node, data); } @Override public Object visit(ASTTriggerVariableExpression node, Object data) { - return visit((ApexNode<?>) node, data); + return visit((AbstractApexNodeBase) node, data); } @Override public Object visit(ASTUserExceptionMethods node, Object data) { - return visit((ApexNode<?>) node, data); + return visit((AbstractApexNodeBase) node, data); } @Override public Object visit(ASTUserTrigger node, Object data) { - return visit((ApexNode<?>) node, data); + return visit((AbstractApexNodeBase) node, data); } @Override public Object visit(ASTVariableDeclaration node, Object data) { - return visit((ApexNode<?>) node, data); + return visit((AbstractApexNodeBase) node, data); } @Override public Object visit(ASTVariableDeclarationStatements node, Object data) { - return visit((ApexNode<?>) node, data); + return visit((AbstractApexNodeBase) node, data); } @Override public Object visit(ASTVariableExpression node, Object data) { - return visit((ApexNode<?>) node, data); + return visit((AbstractApexNodeBase) node, data); } @Override public Object visit(ASTAnnotationParameter node, Object data) { - return visit((ApexNode<?>) node, data); + return visit((AbstractApexNodeBase) node, data); } @Override public Object visit(ASTCastExpression node, Object data) { - return visit((ApexNode<?>) node, data); + return visit((AbstractApexNodeBase) node, data); } @Override public Object visit(ASTConstructorPreamble node, Object data) { - return visit((ApexNode<?>) node, data); + return visit((AbstractApexNodeBase) node, data); } @Override public Object visit(ASTIllegalStoreExpression node, Object data) { - return visit((ApexNode<?>) node, data); + return visit((AbstractApexNodeBase) node, data); } @Override public Object visit(ASTMethodBlockStatement node, Object data) { - return visit((ApexNode<?>) node, data); + return visit((AbstractApexNodeBase) node, data); } @Override public Object visit(ASTModifier node, Object data) { - return visit((ApexNode<?>) node, data); + return visit((AbstractApexNodeBase) node, data); } @Override public Object visit(ASTMultiStatement node, Object data) { - return visit((ApexNode<?>) node, data); + return visit((AbstractApexNodeBase) node, data); } @Override public Object visit(ASTNestedExpression node, Object data) { - return visit((ApexNode<?>) node, data); + return visit((AbstractApexNodeBase) node, data); } @Override public Object visit(ASTNestedStoreExpression node, Object data) { - return visit((ApexNode<?>) node, data); + return visit((AbstractApexNodeBase) node, data); } @Override public Object visit(ASTNewKeyValueObjectExpression node, Object data) { - return visit((ApexNode<?>) node, data); + return visit((AbstractApexNodeBase) node, data); } @Override public Object visit(ASTStatementExecuted node, Object data) { - return visit((ApexNode<?>) node, data); + return visit((AbstractApexNodeBase) node, data); + } + + @Override + public Object visit(ASTFormalComment node, Object data) { + return visit((AbstractApexNodeBase) node, data); } } diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/apexunit/AbstractApexUnitTestRule.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/AbstractApexUnitTestRule.java similarity index 88% rename from pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/apexunit/AbstractApexUnitTestRule.java rename to pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/AbstractApexUnitTestRule.java index d74b34ef045..974954d0b22 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/apexunit/AbstractApexUnitTestRule.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/AbstractApexUnitTestRule.java @@ -2,12 +2,11 @@ * BSD-style license; for more info see http://pmd.sourceforge.net/license.html */ -package net.sourceforge.pmd.lang.apex.rule.apexunit; +package net.sourceforge.pmd.lang.apex.rule; import net.sourceforge.pmd.lang.apex.ast.ASTModifierNode; import net.sourceforge.pmd.lang.apex.ast.ASTUserClass; import net.sourceforge.pmd.lang.apex.ast.ApexNode; -import net.sourceforge.pmd.lang.apex.rule.AbstractApexRule; import apex.jorje.services.Version; @@ -37,7 +36,7 @@ public Object visit(final ASTUserClass node, final Object data) { return super.visit(node, data); } - boolean isTestMethodOrClass(final ApexNode<?> node) { + protected boolean isTestMethodOrClass(final ApexNode<?> node) { final ASTModifierNode modifierNode = node.getFirstChildOfType(ASTModifierNode.class); return modifierNode != null && modifierNode.getNode().getModifiers().isTest(); } diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/AbstractStatisticalApexRule.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/AbstractStatisticalApexRule.java index facfe0f7e59..054bea94c93 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/AbstractStatisticalApexRule.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/AbstractStatisticalApexRule.java @@ -16,12 +16,14 @@ public abstract class AbstractStatisticalApexRule extends AbstractApexRule imple private final StatisticalRuleHelper helper = new StatisticalRuleHelper(this); + @Override public void addDataPoint(DataPoint point) { helper.addDataPoint(point); } + @Override public Object[] getViolationParameters(DataPoint point) { - return null; + return new Object[0]; } @Override diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/ApexRuleChainVisitor.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/ApexRuleChainVisitor.java index b3f906bbc77..5abce5d8ac9 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/ApexRuleChainVisitor.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/ApexRuleChainVisitor.java @@ -18,6 +18,7 @@ public class ApexRuleChainVisitor extends AbstractRuleChainVisitor { + @Override protected void indexNodes(List<Node> nodes, RuleContext ctx) { Stack<Node> stack = new Stack<>(); stack.addAll(nodes); @@ -33,6 +34,7 @@ protected void indexNodes(List<Node> nodes, RuleContext ctx) { } } + @Override protected void visit(Rule rule, Node node, RuleContext ctx) { if (rule instanceof XPathRule) { ((XPathRule) rule).evaluate(node, ctx); diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/ApexRuleViolation.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/ApexRuleViolation.java new file mode 100644 index 00000000000..fd44350f5d9 --- /dev/null +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/ApexRuleViolation.java @@ -0,0 +1,68 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.apex.rule; + +import net.sourceforge.pmd.Rule; +import net.sourceforge.pmd.RuleContext; +import net.sourceforge.pmd.lang.apex.ast.CanSuppressWarnings; +import net.sourceforge.pmd.lang.ast.Node; +import net.sourceforge.pmd.lang.rule.ParametricRuleViolation; + +/** + * This is an Apex RuleViolation. It knows how to try to extract the following + * extra information from the violation node: + * <ul> + * <li>Package name</li> + * <li>Class name</li> + * <li>Method name</li> + * <li>Variable name</li> + * <li>Suppression indicator</li> + * </ul> + * @param <T> + */ +@SuppressWarnings("PMD.UseUtilityClass") // we inherit non-static methods... +public class ApexRuleViolation<T> extends ParametricRuleViolation<Node> { + + public ApexRuleViolation(Rule rule, RuleContext ctx, Node node, String message, int beginLine, int endLine) { + this(rule, ctx, node, message); + + setLines(beginLine, endLine); + } + + public ApexRuleViolation(Rule rule, RuleContext ctx, Node node, String message) { + super(rule, ctx, node, message); + + if (node != null) { + if (!suppressed) { + suppressed = isSupressed(node, getRule()); + } + } + } + + /** + * Check for suppression on this node, on parents, and on contained types + * for ASTCompilationUnit + * + * @param node + */ + public static boolean isSupressed(Node node, Rule rule) { + boolean result = suppresses(node, rule); + + if (!result) { + Node parent = node.jjtGetParent(); + while (!result && parent != null) { + result = suppresses(parent, rule); + parent = parent.jjtGetParent(); + } + } + + return result; + } + + private static boolean suppresses(final Node node, Rule rule) { + return node instanceof CanSuppressWarnings + && ((CanSuppressWarnings) node).hasSuppressWarningsAnnotationFor(rule); + } +} diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/ApexRuleViolationFactory.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/ApexRuleViolationFactory.java index 30fa90df4aa..1fd6ed544ae 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/ApexRuleViolationFactory.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/ApexRuleViolationFactory.java @@ -7,10 +7,8 @@ import net.sourceforge.pmd.Rule; import net.sourceforge.pmd.RuleContext; import net.sourceforge.pmd.RuleViolation; -import net.sourceforge.pmd.lang.apex.ast.ApexNode; import net.sourceforge.pmd.lang.ast.Node; import net.sourceforge.pmd.lang.rule.AbstractRuleViolationFactory; -import net.sourceforge.pmd.lang.rule.ParametricRuleViolation; public final class ApexRuleViolationFactory extends AbstractRuleViolationFactory { @@ -22,11 +20,13 @@ private ApexRuleViolationFactory() { @SuppressWarnings("rawtypes") @Override protected RuleViolation createRuleViolation(Rule rule, RuleContext ruleContext, Node node, String message) { - return new ParametricRuleViolation<>(rule, ruleContext, (ApexNode) node, message); + return new ApexRuleViolation<>(rule, ruleContext, node, message); } + @Override + @SuppressWarnings("rawtypes") protected RuleViolation createRuleViolation(Rule rule, RuleContext ruleContext, Node node, String message, int beginLine, int endLine) { - return null; // FIXME + return new ApexRuleViolation(rule, ruleContext, node, message, beginLine, endLine); } } diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/apexunit/ApexUnitTestClassShouldHaveAssertsRule.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/bestpractices/ApexUnitTestClassShouldHaveAssertsRule.java similarity index 91% rename from pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/apexunit/ApexUnitTestClassShouldHaveAssertsRule.java rename to pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/bestpractices/ApexUnitTestClassShouldHaveAssertsRule.java index 4ee76cab83a..76e6023fd6b 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/apexunit/ApexUnitTestClassShouldHaveAssertsRule.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/bestpractices/ApexUnitTestClassShouldHaveAssertsRule.java @@ -2,11 +2,12 @@ * BSD-style license; for more info see http://pmd.sourceforge.net/license.html */ -package net.sourceforge.pmd.lang.apex.rule.apexunit; +package net.sourceforge.pmd.lang.apex.rule.bestpractices; import java.util.ArrayList; import java.util.HashSet; import java.util.List; +import java.util.Locale; import java.util.Set; import net.sourceforge.pmd.lang.apex.ast.ASTBlockStatement; @@ -14,6 +15,7 @@ import net.sourceforge.pmd.lang.apex.ast.ASTMethodCallExpression; import net.sourceforge.pmd.lang.apex.ast.ASTStatement; import net.sourceforge.pmd.lang.apex.ast.ApexNode; +import net.sourceforge.pmd.lang.apex.rule.AbstractApexUnitTestRule; /** * Apex unit tests should have System.assert methods in them @@ -50,7 +52,7 @@ private Object checkForAssertStatements(ApexNode<?> node, Object data) { boolean isAssertFound = false; for (final ASTMethodCallExpression methodCallExpression : methodCalls) { - if (ASSERT_METHODS.contains(methodCallExpression.getFullMethodName().toLowerCase())) { + if (ASSERT_METHODS.contains(methodCallExpression.getFullMethodName().toLowerCase(Locale.ROOT))) { isAssertFound = true; break; } diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/apexunit/ApexUnitTestShouldNotUseSeeAllDataTrueRule.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/bestpractices/ApexUnitTestShouldNotUseSeeAllDataTrueRule.java similarity index 81% rename from pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/apexunit/ApexUnitTestShouldNotUseSeeAllDataTrueRule.java rename to pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/bestpractices/ApexUnitTestShouldNotUseSeeAllDataTrueRule.java index 23137470c9d..38788da4666 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/apexunit/ApexUnitTestShouldNotUseSeeAllDataTrueRule.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/bestpractices/ApexUnitTestShouldNotUseSeeAllDataTrueRule.java @@ -2,17 +2,19 @@ * BSD-style license; for more info see http://pmd.sourceforge.net/license.html */ -package net.sourceforge.pmd.lang.apex.rule.apexunit; +package net.sourceforge.pmd.lang.apex.rule.bestpractices; import net.sourceforge.pmd.lang.apex.ast.ASTMethod; import net.sourceforge.pmd.lang.apex.ast.ASTModifierNode; import net.sourceforge.pmd.lang.apex.ast.ASTUserClass; import net.sourceforge.pmd.lang.apex.ast.ApexNode; +import net.sourceforge.pmd.lang.apex.rule.AbstractApexUnitTestRule; import apex.jorje.semantic.ast.modifier.Annotation; import apex.jorje.semantic.ast.modifier.AnnotationParameter; import apex.jorje.semantic.ast.modifier.ModifierOrAnnotation; import apex.jorje.semantic.symbol.type.AnnotationTypeInfos; +import apex.jorje.semantic.symbol.type.ModifierOrAnnotationTypeInfo; import apex.jorje.semantic.symbol.type.TypeInfoEquivalence; import apex.jorje.services.Version; @@ -54,9 +56,10 @@ private Object checkForSeeAllData(final ApexNode<?> node, final Object data) { final ASTModifierNode modifierNode = node.getFirstChildOfType(ASTModifierNode.class); if (modifierNode != null) { - for (final ModifierOrAnnotation modifierOrAnnotation : modifierNode.getNode().getModifiers().allNodes()) { + for (final ModifierOrAnnotationTypeInfo modifierOrAnnotationTypeInfo : modifierNode.getNode().getModifiers().all()) { + ModifierOrAnnotation modifierOrAnnotation = modifierNode.getNode().getModifiers().get(modifierOrAnnotationTypeInfo); if (modifierOrAnnotation instanceof Annotation && TypeInfoEquivalence - .isEquivalent(modifierOrAnnotation.getType(), AnnotationTypeInfos.IS_TEST)) { + .isEquivalent(modifierOrAnnotationTypeInfo, AnnotationTypeInfos.IS_TEST)) { final Annotation annotation = (Annotation) modifierOrAnnotation; final AnnotationParameter parameter = annotation.getParameter("seeAllData"); diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/bestpractices/AvoidGlobalModifierRule.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/bestpractices/AvoidGlobalModifierRule.java new file mode 100644 index 00000000000..9d583e7f0c8 --- /dev/null +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/bestpractices/AvoidGlobalModifierRule.java @@ -0,0 +1,75 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.apex.rule.bestpractices; + +import java.util.List; + +import net.sourceforge.pmd.lang.apex.ast.ASTAnnotation; +import net.sourceforge.pmd.lang.apex.ast.ASTMethod; +import net.sourceforge.pmd.lang.apex.ast.ASTModifierNode; +import net.sourceforge.pmd.lang.apex.ast.ASTUserClass; +import net.sourceforge.pmd.lang.apex.ast.ASTUserInterface; +import net.sourceforge.pmd.lang.apex.ast.ApexNode; +import net.sourceforge.pmd.lang.apex.rule.AbstractApexRule; + +import apex.jorje.semantic.symbol.type.ModifierTypeInfos; + +public class AvoidGlobalModifierRule extends AbstractApexRule { + + public AvoidGlobalModifierRule() { + setProperty(CODECLIMATE_CATEGORIES, "Style"); + setProperty(CODECLIMATE_REMEDIATION_MULTIPLIER, 100); + setProperty(CODECLIMATE_BLOCK_HIGHLIGHTING, false); + } + + @Override + public Object visit(ASTUserClass node, Object data) { + return checkForGlobal(node, data); + } + + @Override + public Object visit(ASTUserInterface node, Object data) { + return checkForGlobal(node, data); + } + + private Object checkForGlobal(ApexNode<?> node, Object data) { + ASTModifierNode modifierNode = node.getFirstChildOfType(ASTModifierNode.class); + + if (isGlobal(modifierNode) && !hasRestAnnotation(modifierNode) && !hasWebServices(node)) { + addViolation(data, node); + } + + return data; + } + + private boolean hasWebServices(ApexNode<?> node) { + List<ASTMethod> methods = node.findChildrenOfType(ASTMethod.class); + for (ASTMethod method : methods) { + ASTModifierNode methodModifier = method.getFirstChildOfType(ASTModifierNode.class); + if (isWebService(methodModifier)) { + return true; + } + } + return false; + } + + private boolean isWebService(ASTModifierNode modifierNode) { + return modifierNode != null && modifierNode.getNode().getModifiers().has(ModifierTypeInfos.WEB_SERVICE); + } + + private boolean isGlobal(ASTModifierNode modifierNode) { + return modifierNode != null && modifierNode.getNode().getModifiers().has(ModifierTypeInfos.GLOBAL); + } + + private boolean hasRestAnnotation(ASTModifierNode modifierNode) { + List<ASTAnnotation> annotations = modifierNode.findChildrenOfType(ASTAnnotation.class); + for (ASTAnnotation annotation : annotations) { + if (annotation.hasImageEqualTo("RestResource")) { + return true; + } + } + return false; + } +} diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/style/AvoidLogicInTriggerRule.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/bestpractices/AvoidLogicInTriggerRule.java similarity index 93% rename from pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/style/AvoidLogicInTriggerRule.java rename to pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/bestpractices/AvoidLogicInTriggerRule.java index c282ca9e68b..3d74db85c07 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/style/AvoidLogicInTriggerRule.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/bestpractices/AvoidLogicInTriggerRule.java @@ -2,7 +2,7 @@ * BSD-style license; for more info see http://pmd.sourceforge.net/license.html */ -package net.sourceforge.pmd.lang.apex.rule.style; +package net.sourceforge.pmd.lang.apex.rule.bestpractices; import java.util.List; diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/codestyle/ClassNamingConventionsRule.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/codestyle/ClassNamingConventionsRule.java new file mode 100644 index 00000000000..433acc7fe30 --- /dev/null +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/codestyle/ClassNamingConventionsRule.java @@ -0,0 +1,35 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.apex.rule.codestyle; + +import net.sourceforge.pmd.lang.apex.ast.ASTUserClass; +import net.sourceforge.pmd.lang.apex.ast.ASTUserInterface; +import net.sourceforge.pmd.lang.apex.rule.AbstractApexRule; + +public class ClassNamingConventionsRule extends AbstractApexRule { + + public ClassNamingConventionsRule() { + setProperty(CODECLIMATE_CATEGORIES, "Style"); + // Note: x10 as Apex has not automatic refactoring + setProperty(CODECLIMATE_REMEDIATION_MULTIPLIER, 5); + setProperty(CODECLIMATE_BLOCK_HIGHLIGHTING, false); + } + + @Override + public Object visit(ASTUserClass node, Object data) { + if (Character.isLowerCase(node.getImage().charAt(0))) { + addViolation(data, node); + } + return data; + } + + @Override + public Object visit(ASTUserInterface node, Object data) { + if (Character.isLowerCase(node.getImage().charAt(0))) { + addViolation(data, node); + } + return data; + } +} diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/codestyle/MethodNamingConventionsRule.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/codestyle/MethodNamingConventionsRule.java new file mode 100644 index 00000000000..7192e57900d --- /dev/null +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/codestyle/MethodNamingConventionsRule.java @@ -0,0 +1,56 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.apex.rule.codestyle; + +import static apex.jorje.semantic.symbol.type.ModifierTypeInfos.OVERRIDE; + +import net.sourceforge.pmd.lang.apex.ast.ASTMethod; +import net.sourceforge.pmd.lang.apex.ast.ASTProperty; +import net.sourceforge.pmd.lang.apex.ast.ASTUserClass; +import net.sourceforge.pmd.lang.apex.rule.AbstractApexRule; + +public class MethodNamingConventionsRule extends AbstractApexRule { + + public MethodNamingConventionsRule() { + setProperty(CODECLIMATE_CATEGORIES, "Style"); + // Note: x10 as Apex has not automatic refactoring + setProperty(CODECLIMATE_REMEDIATION_MULTIPLIER, 1); + setProperty(CODECLIMATE_BLOCK_HIGHLIGHTING, false); + } + + @Override + public Object visit(ASTUserClass node, Object data) { + return super.visit(node, data); + } + + @Override + public Object visit(ASTMethod node, Object data) { + if (isOverriddenMethod(node) || isPropertyAccessor(node) || isConstructor(node)) { + return data; + } + + String methodName = node.getImage(); + + if (Character.isUpperCase(methodName.charAt(0))) { + addViolationWithMessage(data, node, "Method names should not start with capital letters"); + } + if (methodName.indexOf('_') >= 0) { + addViolationWithMessage(data, node, "Method names should not contain underscores"); + } + return data; + } + + private boolean isOverriddenMethod(ASTMethod node) { + return node.getNode().getModifiers().has(OVERRIDE); + } + + private boolean isPropertyAccessor(ASTMethod node) { + return !node.getParentsOfType(ASTProperty.class).isEmpty(); + } + + private boolean isConstructor(ASTMethod node) { + return node.getNode().getMethodInfo().isConstructor(); + } +} diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/codestyle/VariableNamingConventionsRule.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/codestyle/VariableNamingConventionsRule.java new file mode 100644 index 00000000000..ec189d0ebca --- /dev/null +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/codestyle/VariableNamingConventionsRule.java @@ -0,0 +1,229 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.apex.rule.codestyle; + +import static apex.jorje.semantic.symbol.type.ModifierTypeInfos.FINAL; +import static apex.jorje.semantic.symbol.type.ModifierTypeInfos.STATIC; + +import java.util.List; +import java.util.Locale; + +import net.sourceforge.pmd.lang.apex.ast.ASTField; +import net.sourceforge.pmd.lang.apex.ast.ASTParameter; +import net.sourceforge.pmd.lang.apex.ast.ASTUserClass; +import net.sourceforge.pmd.lang.apex.ast.ASTUserInterface; +import net.sourceforge.pmd.lang.apex.ast.ASTVariableDeclaration; +import net.sourceforge.pmd.lang.apex.ast.ApexNode; +import net.sourceforge.pmd.lang.apex.rule.AbstractApexRule; +import net.sourceforge.pmd.properties.BooleanProperty; +import net.sourceforge.pmd.properties.PropertyDescriptor; +import net.sourceforge.pmd.properties.StringMultiProperty; + +public class VariableNamingConventionsRule extends AbstractApexRule { + + private boolean checkMembers; + private boolean checkLocals; + private boolean checkParameters; + private List<String> staticPrefixes; + private List<String> staticSuffixes; + private List<String> memberPrefixes; + private List<String> memberSuffixes; + private List<String> localPrefixes; + private List<String> localSuffixes; + private List<String> parameterPrefixes; + private List<String> parameterSuffixes; + + private static final BooleanProperty CHECK_MEMBERS_DESCRIPTOR = new BooleanProperty("checkMembers", + "Check member variables", true, 1.0f); + + private static final BooleanProperty CHECK_LOCALS_DESCRIPTOR = new BooleanProperty("checkLocals", + "Check local variables", true, 2.0f); + + private static final BooleanProperty CHECK_PARAMETERS_DESCRIPTOR = new BooleanProperty("checkParameters", + "Check constructor and method parameter variables", true, 3.0f); + + private static final StringMultiProperty STATIC_PREFIXES_DESCRIPTOR = new StringMultiProperty("staticPrefix", + "Static variable prefixes", new String[] { "" }, 4.0f, ','); + + private static final StringMultiProperty STATIC_SUFFIXES_DESCRIPTOR = new StringMultiProperty("staticSuffix", + "Static variable suffixes", new String[] { "" }, 5.0f, ','); + + private static final StringMultiProperty MEMBER_PREFIXES_DESCRIPTOR = new StringMultiProperty("memberPrefix", + "Member variable prefixes", new String[] { "" }, 6.0f, ','); + + private static final StringMultiProperty MEMBER_SUFFIXES_DESCRIPTOR = new StringMultiProperty("memberSuffix", + "Member variable suffixes", new String[] { "" }, 7.0f, ','); + + private static final StringMultiProperty LOCAL_PREFIXES_DESCRIPTOR = new StringMultiProperty("localPrefix", + "Local variable prefixes", new String[] { "" }, 8.0f, ','); + + private static final StringMultiProperty LOCAL_SUFFIXES_DESCRIPTOR = new StringMultiProperty("localSuffix", + "Local variable suffixes", new String[] { "" }, 9.0f, ','); + + private static final StringMultiProperty PARAMETER_PREFIXES_DESCRIPTOR = new StringMultiProperty("parameterPrefix", + "Method parameter variable prefixes", new String[] { "" }, 10.0f, ','); + + private static final StringMultiProperty PARAMETER_SUFFIXES_DESCRIPTOR = new StringMultiProperty("parameterSuffix", + "Method parameter variable suffixes", new String[] { "" }, 11.0f, ','); + + public VariableNamingConventionsRule() { + definePropertyDescriptor(CHECK_MEMBERS_DESCRIPTOR); + definePropertyDescriptor(CHECK_LOCALS_DESCRIPTOR); + definePropertyDescriptor(CHECK_PARAMETERS_DESCRIPTOR); + definePropertyDescriptor(STATIC_PREFIXES_DESCRIPTOR); + definePropertyDescriptor(STATIC_SUFFIXES_DESCRIPTOR); + definePropertyDescriptor(MEMBER_PREFIXES_DESCRIPTOR); + definePropertyDescriptor(MEMBER_SUFFIXES_DESCRIPTOR); + definePropertyDescriptor(LOCAL_PREFIXES_DESCRIPTOR); + definePropertyDescriptor(LOCAL_SUFFIXES_DESCRIPTOR); + definePropertyDescriptor(PARAMETER_PREFIXES_DESCRIPTOR); + definePropertyDescriptor(PARAMETER_SUFFIXES_DESCRIPTOR); + + setProperty(CODECLIMATE_CATEGORIES, "Style"); + // Note: x10 as Apex has not automatic refactoring + setProperty(CODECLIMATE_REMEDIATION_MULTIPLIER, 5); + setProperty(CODECLIMATE_BLOCK_HIGHLIGHTING, false); + } + + @Override + public Object visit(ASTUserClass node, Object data) { + init(); + return super.visit(node, data); + } + + @Override + public Object visit(ASTUserInterface node, Object data) { + init(); + return super.visit(node, data); + } + + protected void init() { + checkMembers = getProperty(CHECK_MEMBERS_DESCRIPTOR); + checkLocals = getProperty(CHECK_LOCALS_DESCRIPTOR); + checkParameters = getProperty(CHECK_PARAMETERS_DESCRIPTOR); + staticPrefixes = getProperty(STATIC_PREFIXES_DESCRIPTOR); + staticSuffixes = getProperty(STATIC_SUFFIXES_DESCRIPTOR); + memberPrefixes = getProperty(MEMBER_PREFIXES_DESCRIPTOR); + memberSuffixes = getProperty(MEMBER_SUFFIXES_DESCRIPTOR); + localPrefixes = getProperty(LOCAL_PREFIXES_DESCRIPTOR); + localSuffixes = getProperty(LOCAL_SUFFIXES_DESCRIPTOR); + parameterPrefixes = getProperty(PARAMETER_PREFIXES_DESCRIPTOR); + parameterSuffixes = getProperty(PARAMETER_SUFFIXES_DESCRIPTOR); + } + + @Override + public Object visit(ASTField node, Object data) { + if (!checkMembers) { + return data; + } + boolean isStatic = node.getNode().getFieldInfo().getModifiers().has(STATIC); + boolean isFinal = node.getNode().getFieldInfo().getModifiers().has(FINAL); + + return checkName(isStatic ? staticPrefixes : memberPrefixes, isStatic ? staticSuffixes : memberSuffixes, node, + isStatic, isFinal, data); + } + + @Override + public Object visit(ASTVariableDeclaration node, Object data) { + + if (!checkLocals) { + return data; + } + + boolean isFinal = node.getNode().getLocalInfo().getModifiers().has(FINAL); + return checkName(localPrefixes, localSuffixes, node, false, isFinal, data); + } + + @Override + public Object visit(ASTParameter node, Object data) { + if (!checkParameters) { + return data; + } + + boolean isFinal = node.getNode().getModifierInfo().has(FINAL); + return checkName(parameterPrefixes, parameterSuffixes, node, false, isFinal, data); + } + + private Object checkName(List<String> prefixes, List<String> suffixes, ApexNode<?> node, boolean isStatic, boolean isFinal, + Object data) { + + String varName = node.getImage(); + + // Skip on null (with exception classes) and serialVersionUID + if (varName == null || "serialVersionUID".equals(varName)) { + return data; + } + + // Static finals should be uppercase + if (isStatic && isFinal) { + if (!varName.equals(varName.toUpperCase(Locale.ROOT))) { + addViolationWithMessage(data, node, + "Variables that are final and static should be all capitals, ''{0}'' is not all capitals.", + new Object[] { varName }); + } + return data; + } else if (!isFinal) { + String normalizedVarName = normalizeVariableName(varName, prefixes, suffixes); + + if (normalizedVarName.indexOf('_') >= 0) { + addViolationWithMessage(data, node, + "Only variables that are final should contain underscores (except for underscores in standard prefix/suffix), ''{0}'' is not final.", + new Object[] { varName }); + } + if (Character.isUpperCase(varName.charAt(0))) { + addViolationWithMessage(data, node, + "Variables should start with a lowercase character, ''{0}'' starts with uppercase character.", + new Object[] { varName }); + } + } + return data; + } + + private String normalizeVariableName(String varName, List<String> prefixes, List<String> suffixes) { + return stripSuffix(stripPrefix(varName, prefixes), suffixes); + } + + private String stripSuffix(String varName, List<String> suffixes) { + if (suffixes != null) { + for (String suffix : suffixes) { + if (varName.endsWith(suffix)) { + varName = varName.substring(0, varName.length() - suffix.length()); + break; + } + } + } + return varName; + } + + private String stripPrefix(String varName, List<String> prefixes) { + if (prefixes != null) { + for (String prefix : prefixes) { + if (varName.startsWith(prefix)) { + return varName.substring(prefix.length()); + } + } + } + return varName; + } + + public boolean hasPrefixesOrSuffixes() { + + for (PropertyDescriptor<?> desc : getPropertyDescriptors()) { + if (desc instanceof StringMultiProperty) { + List<String> values = getProperty((StringMultiProperty) desc); + if (!values.isEmpty()) { + return true; + } + } + } + return false; + } + + @Override + public String dysfunctionReason() { + return hasPrefixesOrSuffixes() ? null : "No prefixes or suffixes specified"; + } + +} diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/complexity/AbstractNcssCountRule.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/complexity/AbstractNcssCountRule.java deleted file mode 100644 index e1bcf2b59af..00000000000 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/complexity/AbstractNcssCountRule.java +++ /dev/null @@ -1,164 +0,0 @@ -/** - * BSD-style license; for more info see http://pmd.sourceforge.net/license.html - */ - -package net.sourceforge.pmd.lang.apex.rule.complexity; - -import net.sourceforge.pmd.lang.apex.ast.ASTBreakStatement; -import net.sourceforge.pmd.lang.apex.ast.ASTContinueStatement; -import net.sourceforge.pmd.lang.apex.ast.ASTDoLoopStatement; -import net.sourceforge.pmd.lang.apex.ast.ASTForEachStatement; -import net.sourceforge.pmd.lang.apex.ast.ASTForLoopStatement; -import net.sourceforge.pmd.lang.apex.ast.ASTIfBlockStatement; -import net.sourceforge.pmd.lang.apex.ast.ASTIfElseBlockStatement; -import net.sourceforge.pmd.lang.apex.ast.ASTMethodCallExpression; -import net.sourceforge.pmd.lang.apex.ast.ASTReturnStatement; -import net.sourceforge.pmd.lang.apex.ast.ASTStatement; -import net.sourceforge.pmd.lang.apex.ast.ASTThrowStatement; -import net.sourceforge.pmd.lang.apex.ast.ASTTryCatchFinallyBlockStatement; -import net.sourceforge.pmd.lang.apex.ast.ASTWhileLoopStatement; -import net.sourceforge.pmd.lang.apex.ast.ApexNode; -import net.sourceforge.pmd.lang.apex.rule.AbstractStatisticalApexRule; -import net.sourceforge.pmd.lang.ast.Node; -import net.sourceforge.pmd.stat.DataPoint; -import net.sourceforge.pmd.util.NumericConstants; - -/** - * Abstract superclass for NCSS counting methods. Counts tokens according to - * <a href="http://www.kclee.de/clemens/java/javancss/">JavaNCSS rules</a>. - * - * @author ported from Java original of Jason Bennett - */ -public abstract class AbstractNcssCountRule extends AbstractStatisticalApexRule { - - private Class<?> nodeClass; - - /** - * Count the nodes of the given type using NCSS rules. - * - * @param nodeClass - * class of node to count - */ - protected AbstractNcssCountRule(Class<?> nodeClass) { - this.nodeClass = nodeClass; - - setProperty(MINIMUM_DESCRIPTOR, 1000d); - setProperty(CODECLIMATE_CATEGORIES, "Complexity"); - setProperty(CODECLIMATE_REMEDIATION_MULTIPLIER, 100); - setProperty(CODECLIMATE_BLOCK_HIGHLIGHTING, false); - } - - @Override - public Object visit(ApexNode<?> node, Object data) { - int numNodes = 0; - - for (int i = 0; i < node.jjtGetNumChildren(); i++) { - ApexNode<?> n = (ApexNode<?>) node.jjtGetChild(i); - Integer treeSize = (Integer) n.jjtAccept(this, data); - numNodes += treeSize.intValue(); - } - - if (this.nodeClass.isInstance(node)) { - // Add 1 to account for base node - numNodes++; - DataPoint point = new DataPoint(); - point.setNode(node); - point.setScore(1.0 * numNodes); - point.setMessage(getMessage()); - addDataPoint(point); - } - - return Integer.valueOf(numNodes); - } - - /** - * Count the number of children of the given node. Adds one to count the - * node itself. - * - * @param node - * node having children counted - * @param data - * node data - * @return count of the number of children of the node, plus one - */ - protected Integer countNodeChildren(Node node, Object data) { - Integer nodeCount; - int lineCount = 0; - for (int i = 0; i < node.jjtGetNumChildren(); i++) { - nodeCount = (Integer) ((ApexNode<?>) node.jjtGetChild(i)).jjtAccept(this, data); - lineCount += nodeCount.intValue(); - } - return ++lineCount; - } - - @Override - public Object visit(ASTForLoopStatement node, Object data) { - return countNodeChildren(node, data); - } - - @Override - public Object visit(ASTForEachStatement node, Object data) { - return countNodeChildren(node, data); - } - - @Override - public Object visit(ASTDoLoopStatement node, Object data) { - return countNodeChildren(node, data); - } - - @Override - public Object visit(ASTIfBlockStatement node, Object data) { - - Integer lineCount = countNodeChildren(node, data); - return lineCount; - } - - @Override - public Object visit(ASTIfElseBlockStatement node, Object data) { - - Integer lineCount = countNodeChildren(node, data); - lineCount++; - - return lineCount; - } - - @Override - public Object visit(ASTWhileLoopStatement node, Object data) { - return countNodeChildren(node, data); - } - - @Override - public Object visit(ASTBreakStatement node, Object data) { - return NumericConstants.ONE; - } - - @Override - public Object visit(ASTTryCatchFinallyBlockStatement node, Object data) { - return countNodeChildren(node, data); - } - - @Override - public Object visit(ASTContinueStatement node, Object data) { - return NumericConstants.ONE; - } - - @Override - public Object visit(ASTReturnStatement node, Object data) { - return countNodeChildren(node, data); - } - - @Override - public Object visit(ASTThrowStatement node, Object data) { - return countNodeChildren(node, data); - } - - @Override - public Object visit(ASTStatement node, Object data) { - return NumericConstants.ONE; - } - - @Override - public Object visit(ASTMethodCallExpression node, Object data) { - return NumericConstants.ONE; - } -} diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/complexity/AvoidDeeplyNestedIfStmtsRule.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/complexity/AvoidDeeplyNestedIfStmtsRule.java deleted file mode 100644 index 8c657d2f44b..00000000000 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/complexity/AvoidDeeplyNestedIfStmtsRule.java +++ /dev/null @@ -1,47 +0,0 @@ -/** - * BSD-style license; for more info see http://pmd.sourceforge.net/license.html - */ - -package net.sourceforge.pmd.lang.apex.rule.complexity; - -import net.sourceforge.pmd.lang.apex.ast.ASTIfBlockStatement; -import net.sourceforge.pmd.lang.apex.ast.ASTUserClass; -import net.sourceforge.pmd.lang.apex.rule.AbstractApexRule; -import net.sourceforge.pmd.properties.IntegerProperty; - -public class AvoidDeeplyNestedIfStmtsRule extends AbstractApexRule { - - private int depth; - private int depthLimit; - - private static final IntegerProperty PROBLEM_DEPTH_DESCRIPTOR = new IntegerProperty("problemDepth", - "The if statement depth reporting threshold", 1, 25, 3, 1.0f); - - public AvoidDeeplyNestedIfStmtsRule() { - definePropertyDescriptor(PROBLEM_DEPTH_DESCRIPTOR); - - setProperty(CODECLIMATE_CATEGORIES, "Complexity"); - // Note: Remedy needs better OO design and therefore high effort - setProperty(CODECLIMATE_REMEDIATION_MULTIPLIER, 200); - setProperty(CODECLIMATE_BLOCK_HIGHLIGHTING, false); - } - - public Object visit(ASTUserClass node, Object data) { - depth = 0; - depthLimit = getProperty(PROBLEM_DEPTH_DESCRIPTOR); - - return super.visit(node, data); - } - - public Object visit(ASTIfBlockStatement node, Object data) { - depth++; - - super.visit(node, data); - if (depth == depthLimit) { - addViolation(data, node); - } - depth--; - - return data; - } -} diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/complexity/ExcessiveClassLengthRule.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/complexity/ExcessiveClassLengthRule.java deleted file mode 100644 index 66961ef5341..00000000000 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/complexity/ExcessiveClassLengthRule.java +++ /dev/null @@ -1,33 +0,0 @@ -/** - * BSD-style license; for more info see http://pmd.sourceforge.net/license.html - */ - -package net.sourceforge.pmd.lang.apex.rule.complexity; - -import static apex.jorje.semantic.symbol.type.AnnotationTypeInfos.IS_TEST; - -import net.sourceforge.pmd.lang.apex.ast.ASTUserClass; - -/** - * This rule detects when a class exceeds a certain threshold. i.e. if a class - * has more than 1000 lines of code. - */ -public class ExcessiveClassLengthRule extends ExcessiveLengthRule { - public ExcessiveClassLengthRule() { - super(ASTUserClass.class); - setProperty(MINIMUM_DESCRIPTOR, 1000d); - - setProperty(CODECLIMATE_CATEGORIES, "Complexity"); - setProperty(CODECLIMATE_REMEDIATION_MULTIPLIER, 150); - setProperty(CODECLIMATE_BLOCK_HIGHLIGHTING, false); - } - - @Override - public Object visit(ASTUserClass node, Object data) { - if (node.getNode().getModifiers().getModifiers().not(IS_TEST)) { - return super.visit(node, data); - } - - return data; - } -} diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/complexity/ExcessiveParameterListRule.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/complexity/ExcessiveParameterListRule.java deleted file mode 100644 index 08f6abd87b9..00000000000 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/complexity/ExcessiveParameterListRule.java +++ /dev/null @@ -1,28 +0,0 @@ -/** - * BSD-style license; for more info see http://pmd.sourceforge.net/license.html - */ - -package net.sourceforge.pmd.lang.apex.rule.complexity; - -import net.sourceforge.pmd.lang.apex.ast.ASTMethod; -import net.sourceforge.pmd.lang.apex.ast.ASTParameter; -import net.sourceforge.pmd.util.NumericConstants; - -/** - * This rule detects an abnormally long parameter list. Note: This counts Nodes, - * and not necessarily parameters, so the numbers may not match up. (But - * topcount and sigma should work.) - */ -public class ExcessiveParameterListRule extends ExcessiveNodeCountRule { - public ExcessiveParameterListRule() { - super(ASTMethod.class); - setProperty(MINIMUM_DESCRIPTOR, 4d); - setProperty(CODECLIMATE_CATEGORIES, "Complexity"); - setProperty(CODECLIMATE_REMEDIATION_MULTIPLIER, 50); - setProperty(CODECLIMATE_BLOCK_HIGHLIGHTING, false); - } - - public Object visit(ASTParameter node, Object data) { - return NumericConstants.ONE; - } -} diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/complexity/ExcessivePublicCountRule.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/complexity/ExcessivePublicCountRule.java deleted file mode 100644 index 1da2781e3ea..00000000000 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/complexity/ExcessivePublicCountRule.java +++ /dev/null @@ -1,52 +0,0 @@ -/** - * BSD-style license; for more info see http://pmd.sourceforge.net/license.html - */ - -package net.sourceforge.pmd.lang.apex.rule.complexity; - -import static apex.jorje.semantic.symbol.type.ModifierTypeInfos.PUBLIC; -import static apex.jorje.semantic.symbol.type.ModifierTypeInfos.STATIC; - -import net.sourceforge.pmd.lang.apex.ast.ASTFieldDeclarationStatements; -import net.sourceforge.pmd.lang.apex.ast.ASTMethod; -import net.sourceforge.pmd.lang.apex.ast.ASTUserClass; -import net.sourceforge.pmd.util.NumericConstants; - -/** - * Rule attempts to count all public methods and public attributes - * defined in a class. - * - * <p>If a class has a high number of public operations, it might be wise - * to consider whether it would be appropriate to divide it into - * subclasses.</p> - * - * <p>A large proportion of public members and operations means the class - * has high potential to be affected by external classes. Futhermore, - * increased effort will be required to thoroughly test the class.</p> - * - * @author ported from Java original of aglover - */ -public class ExcessivePublicCountRule extends ExcessiveNodeCountRule { - - public ExcessivePublicCountRule() { - super(ASTUserClass.class); - setProperty(MINIMUM_DESCRIPTOR, 20d); - setProperty(CODECLIMATE_CATEGORIES, "Complexity"); - setProperty(CODECLIMATE_REMEDIATION_MULTIPLIER, 150); - setProperty(CODECLIMATE_BLOCK_HIGHLIGHTING, false); - } - - public Object visit(ASTMethod node, Object data) { - if (node.getNode().getModifiers().has(PUBLIC) && !node.getImage().matches("<clinit>|<init>|clone")) { - return NumericConstants.ONE; - } - return NumericConstants.ZERO; - } - - public Object visit(ASTFieldDeclarationStatements node, Object data) { - if (node.getNode().getModifiers().has(PUBLIC) && !node.getNode().getModifiers().has(STATIC)) { - return NumericConstants.ONE; - } - return NumericConstants.ZERO; - } -} diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/complexity/NcssConstructorCountRule.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/complexity/NcssConstructorCountRule.java deleted file mode 100644 index 912e7688b94..00000000000 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/complexity/NcssConstructorCountRule.java +++ /dev/null @@ -1,43 +0,0 @@ -/** - * BSD-style license; for more info see http://pmd.sourceforge.net/license.html - */ - -package net.sourceforge.pmd.lang.apex.rule.complexity; - -import net.sourceforge.pmd.lang.apex.ast.ASTMethod; -import net.sourceforge.pmd.stat.DataPoint; -import net.sourceforge.pmd.util.NumericConstants; - -/** - * Non-commented source statement counter for constructors. - * - * @author ported from Java original by Jason Bennett - */ -public class NcssConstructorCountRule extends AbstractNcssCountRule { - - /** - * Count constructor declarations. This includes any explicit super() calls. - */ - public NcssConstructorCountRule() { - super(ASTMethod.class); - setProperty(MINIMUM_DESCRIPTOR, 20d); - setProperty(CODECLIMATE_CATEGORIES, "Complexity"); - setProperty(CODECLIMATE_REMEDIATION_MULTIPLIER, 50); - setProperty(CODECLIMATE_BLOCK_HIGHLIGHTING, false); - } - - @Override - public Object visit(ASTMethod node, Object data) { - if (node.getNode().getMethodInfo().isConstructor()) { - return super.visit(node, data); - } - - return NumericConstants.ZERO; - } - - @Override - public Object[] getViolationParameters(DataPoint point) { - // TODO need to put class name or constructor ID in string - return new String[] { String.valueOf((int) point.getScore()) }; - } -} diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/complexity/NcssMethodCountRule.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/complexity/NcssMethodCountRule.java deleted file mode 100644 index 19a3261ee38..00000000000 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/complexity/NcssMethodCountRule.java +++ /dev/null @@ -1,43 +0,0 @@ -/** - * BSD-style license; for more info see http://pmd.sourceforge.net/license.html - */ - -package net.sourceforge.pmd.lang.apex.rule.complexity; - -import net.sourceforge.pmd.lang.apex.ast.ASTMethod; -import net.sourceforge.pmd.stat.DataPoint; -import net.sourceforge.pmd.util.NumericConstants; - -/** - * Non-commented source statement counter for methods. - * - * @author ported from Java original of Jason Bennett - */ -public class NcssMethodCountRule extends AbstractNcssCountRule { - - /** - * Count the size of all non-constructor methods. - */ - public NcssMethodCountRule() { - super(ASTMethod.class); - setProperty(MINIMUM_DESCRIPTOR, 40d); - setProperty(CODECLIMATE_CATEGORIES, "Complexity"); - setProperty(CODECLIMATE_REMEDIATION_MULTIPLIER, 50); - setProperty(CODECLIMATE_BLOCK_HIGHLIGHTING, false); - } - - @Override - public Object visit(ASTMethod node, Object data) { - if (!node.getNode().getMethodInfo().isConstructor()) { - return super.visit(node, data); - } - - return NumericConstants.ZERO; - } - - @Override - public Object[] getViolationParameters(DataPoint point) { - return new String[] { ((ASTMethod) point.getNode()).getNode().getMethodInfo().getName(), - String.valueOf((int) point.getScore()), }; - } -} diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/complexity/NcssTypeCountRule.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/complexity/NcssTypeCountRule.java deleted file mode 100644 index 1d1fb9489ec..00000000000 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/complexity/NcssTypeCountRule.java +++ /dev/null @@ -1,77 +0,0 @@ -/** - * BSD-style license; for more info see http://pmd.sourceforge.net/license.html - */ - -package net.sourceforge.pmd.lang.apex.rule.complexity; - -import net.sourceforge.pmd.lang.apex.ast.ASTFieldDeclaration; -import net.sourceforge.pmd.lang.apex.ast.ASTMethod; -import net.sourceforge.pmd.lang.apex.ast.ASTUserClass; -import net.sourceforge.pmd.lang.apex.ast.ASTUserEnum; -import net.sourceforge.pmd.lang.apex.ast.ASTUserInterface; -import net.sourceforge.pmd.stat.DataPoint; -import net.sourceforge.pmd.util.NumericConstants; - -/** - * Non-commented source statement counter for type declarations. - * - * @author ported from Java original of Jason Bennett - */ -public class NcssTypeCountRule extends AbstractNcssCountRule { - - /** - * Count type declarations. This includes classes as well as enums and - * annotations. - */ - public NcssTypeCountRule() { - super(ASTUserClass.class); - setProperty(MINIMUM_DESCRIPTOR, 500d); - setProperty(CODECLIMATE_CATEGORIES, "Complexity"); - setProperty(CODECLIMATE_REMEDIATION_MULTIPLIER, 250); - setProperty(CODECLIMATE_BLOCK_HIGHLIGHTING, false); - } - - @Override - public Object visit(ASTUserClass node, Object data) { - - if (!node.hasDecendantOfAnyType(ASTUserClass.class)) { - return super.visit(node, data); - } - - return countNodeChildren(node, data); - } - - @Override - public Object visit(ASTUserInterface node, Object data) { - - if (!node.hasDecendantOfAnyType(ASTUserClass.class)) { - return super.visit(node, data); - } - - return countNodeChildren(node, data); - } - - @Override - public Object visit(ASTUserEnum node, Object data) { - return countNodeChildren(node, data); - } - - @Override - public Object visit(ASTMethod node, Object data) { - if (!node.getImage().matches("<clinit>|<init>|clone")) { - return countNodeChildren(node, data); - } - - return NumericConstants.ZERO; - } - - @Override - public Object visit(ASTFieldDeclaration node, Object data) { - return NumericConstants.ONE; - } - - @Override - public Object[] getViolationParameters(DataPoint point) { - return new String[] { String.valueOf((int) point.getScore()) }; - } -} diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/complexity/StdCyclomaticComplexityRule.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/complexity/StdCyclomaticComplexityRule.java deleted file mode 100644 index 2eabcf788a2..00000000000 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/complexity/StdCyclomaticComplexityRule.java +++ /dev/null @@ -1,214 +0,0 @@ -/** - * BSD-style license; for more info see http://pmd.sourceforge.net/license.html - */ - -package net.sourceforge.pmd.lang.apex.rule.complexity; - -import java.util.Stack; - -import net.sourceforge.pmd.lang.apex.ast.ASTBooleanExpression; -import net.sourceforge.pmd.lang.apex.ast.ASTDoLoopStatement; -import net.sourceforge.pmd.lang.apex.ast.ASTForEachStatement; -import net.sourceforge.pmd.lang.apex.ast.ASTForLoopStatement; -import net.sourceforge.pmd.lang.apex.ast.ASTIfBlockStatement; -import net.sourceforge.pmd.lang.apex.ast.ASTMethod; -import net.sourceforge.pmd.lang.apex.ast.ASTTernaryExpression; -import net.sourceforge.pmd.lang.apex.ast.ASTTryCatchFinallyBlockStatement; -import net.sourceforge.pmd.lang.apex.ast.ASTUserClass; -import net.sourceforge.pmd.lang.apex.ast.ASTUserEnum; -import net.sourceforge.pmd.lang.apex.ast.ASTUserInterface; -import net.sourceforge.pmd.lang.apex.ast.ASTUserTrigger; -import net.sourceforge.pmd.lang.apex.ast.ASTWhileLoopStatement; -import net.sourceforge.pmd.lang.apex.rule.AbstractApexRule; -import net.sourceforge.pmd.lang.ast.Node; -import net.sourceforge.pmd.properties.BooleanProperty; -import net.sourceforge.pmd.properties.IntegerProperty; - -/** - * Implements the standard cyclomatic complexity rule - * <p> - * Standard rules: +1 for each decision point, but not including boolean - * operators unlike CyclomaticComplexityRule. - * - * @author ported on Java version of Alan Hohn, based on work by Donald A. - * Leckie - * - * @since June 18, 2014 - */ -public class StdCyclomaticComplexityRule extends AbstractApexRule { - - public static final IntegerProperty REPORT_LEVEL_DESCRIPTOR = new IntegerProperty("reportLevel", - "Cyclomatic Complexity reporting threshold", 1, 30, 10, 1.0f); - - public static final BooleanProperty SHOW_CLASSES_COMPLEXITY_DESCRIPTOR = new BooleanProperty( - "showClassesComplexity", "Add class average violations to the report", true, 2.0f); - - public static final BooleanProperty SHOW_METHODS_COMPLEXITY_DESCRIPTOR = new BooleanProperty( - "showMethodsComplexity", "Add method average violations to the report", true, 3.0f); - - private int reportLevel; - private boolean showClassesComplexity = true; - private boolean showMethodsComplexity = true; - - protected static class Entry { - private int decisionPoints = 1; - public int highestDecisionPoints; - public int methodCount; - - private Entry(Node node) { - } - - public void bumpDecisionPoints() { - decisionPoints++; - } - - public void bumpDecisionPoints(int size) { - decisionPoints += size; - } - - public int getComplexityAverage() { - return (double) methodCount == 0 ? 1 : (int) Math.rint((double) decisionPoints / (double) methodCount); - } - } - - protected Stack<Entry> entryStack = new Stack<>(); - - public StdCyclomaticComplexityRule() { - definePropertyDescriptor(REPORT_LEVEL_DESCRIPTOR); - definePropertyDescriptor(SHOW_CLASSES_COMPLEXITY_DESCRIPTOR); - definePropertyDescriptor(SHOW_METHODS_COMPLEXITY_DESCRIPTOR); - - setProperty(CODECLIMATE_CATEGORIES, "Complexity"); - setProperty(CODECLIMATE_REMEDIATION_MULTIPLIER, 250); - setProperty(CODECLIMATE_BLOCK_HIGHLIGHTING, false); - } - - @Override - public Object visit(ASTUserClass node, Object data) { - reportLevel = getProperty(REPORT_LEVEL_DESCRIPTOR); - showClassesComplexity = getProperty(SHOW_CLASSES_COMPLEXITY_DESCRIPTOR); - showMethodsComplexity = getProperty(SHOW_METHODS_COMPLEXITY_DESCRIPTOR); - entryStack.push(new Entry(node)); - super.visit(node, data); - Entry classEntry = entryStack.pop(); - if (showClassesComplexity) { - if (classEntry.getComplexityAverage() >= reportLevel || classEntry.highestDecisionPoints >= reportLevel) { - addViolation(data, node, new String[] { "class", node.getImage(), - classEntry.getComplexityAverage() + " (Highest = " + classEntry.highestDecisionPoints + ')', }); - } - } - return data; - } - - @Override - public Object visit(ASTUserTrigger node, Object data) { - reportLevel = getProperty(REPORT_LEVEL_DESCRIPTOR); - showClassesComplexity = getProperty(SHOW_CLASSES_COMPLEXITY_DESCRIPTOR); - showMethodsComplexity = getProperty(SHOW_METHODS_COMPLEXITY_DESCRIPTOR); - entryStack.push(new Entry(node)); - super.visit(node, data); - Entry classEntry = entryStack.pop(); - if (showClassesComplexity) { - if (classEntry.getComplexityAverage() >= reportLevel || classEntry.highestDecisionPoints >= reportLevel) { - addViolation(data, node, new String[] { "trigger", node.getImage(), - classEntry.getComplexityAverage() + " (Highest = " + classEntry.highestDecisionPoints + ')', }); - } - } - return data; - } - - @Override - public Object visit(ASTUserInterface node, Object data) { - return data; - } - - @Override - public Object visit(ASTUserEnum node, Object data) { - entryStack.push(new Entry(node)); - super.visit(node, data); - Entry classEntry = entryStack.pop(); - if (classEntry.getComplexityAverage() >= reportLevel || classEntry.highestDecisionPoints >= reportLevel) { - addViolation(data, node, new String[] { "class", node.getImage(), - classEntry.getComplexityAverage() + "(Highest = " + classEntry.highestDecisionPoints + ')', }); - } - return data; - } - - @Override - public Object visit(ASTMethod node, Object data) { - if (!node.getImage().matches("<clinit>|<init>|clone")) { - entryStack.push(new Entry(node)); - super.visit(node, data); - Entry methodEntry = entryStack.pop(); - int methodDecisionPoints = methodEntry.decisionPoints; - Entry classEntry = entryStack.peek(); - classEntry.methodCount++; - classEntry.bumpDecisionPoints(methodDecisionPoints); - - if (methodDecisionPoints > classEntry.highestDecisionPoints) { - classEntry.highestDecisionPoints = methodDecisionPoints; - } - - if (showMethodsComplexity && methodEntry.decisionPoints >= reportLevel) { - String methodType = (node.getNode().getMethodInfo().isConstructor()) ? "constructor" : "method"; - addViolation(data, node, - new String[] { methodType, node.getImage(), String.valueOf(methodEntry.decisionPoints) }); - } - } - return data; - } - - @Override - public Object visit(ASTIfBlockStatement node, Object data) { - entryStack.peek().bumpDecisionPoints(); - super.visit(node, data); - return data; - } - - @Override - public Object visit(ASTTryCatchFinallyBlockStatement node, Object data) { - entryStack.peek().bumpDecisionPoints(); - super.visit(node, data); - return data; - } - - @Override - public Object visit(ASTForLoopStatement node, Object data) { - entryStack.peek().bumpDecisionPoints(); - super.visit(node, data); - return data; - } - - @Override - public Object visit(ASTForEachStatement node, Object data) { - entryStack.peek().bumpDecisionPoints(); - super.visit(node, data); - return data; - } - - @Override - public Object visit(ASTWhileLoopStatement node, Object data) { - entryStack.peek().bumpDecisionPoints(); - super.visit(node, data); - return data; - } - - @Override - public Object visit(ASTDoLoopStatement node, Object data) { - entryStack.peek().bumpDecisionPoints(); - super.visit(node, data); - return data; - } - - @Override - public Object visit(ASTTernaryExpression node, Object data) { - entryStack.peek().bumpDecisionPoints(); - super.visit(node, data); - return data; - } - - @Override - public Object visit(ASTBooleanExpression node, Object data) { - return data; - } -} diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/complexity/TooManyFieldsRule.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/complexity/TooManyFieldsRule.java deleted file mode 100644 index d26e0521f32..00000000000 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/complexity/TooManyFieldsRule.java +++ /dev/null @@ -1,77 +0,0 @@ -/** - * BSD-style license; for more info see http://pmd.sourceforge.net/license.html - */ - -package net.sourceforge.pmd.lang.apex.rule.complexity; - -import static apex.jorje.semantic.symbol.type.ModifierTypeInfos.FINAL; -import static apex.jorje.semantic.symbol.type.ModifierTypeInfos.STATIC; - -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import net.sourceforge.pmd.lang.apex.ast.ASTField; -import net.sourceforge.pmd.lang.apex.ast.ASTUserClass; -import net.sourceforge.pmd.lang.apex.rule.AbstractApexRule; -import net.sourceforge.pmd.lang.ast.Node; -import net.sourceforge.pmd.properties.IntegerProperty; -import net.sourceforge.pmd.util.NumericConstants; - -public class TooManyFieldsRule extends AbstractApexRule { - - private static final int DEFAULT_MAXFIELDS = 15; - - private Map<String, Integer> stats; - private Map<String, ASTUserClass> nodes; - - private static final IntegerProperty MAX_FIELDS_DESCRIPTOR = new IntegerProperty("maxfields", - "Max allowable fields", 1, 300, DEFAULT_MAXFIELDS, 1.0f); - - public TooManyFieldsRule() { - definePropertyDescriptor(MAX_FIELDS_DESCRIPTOR); - - setProperty(CODECLIMATE_CATEGORIES, "Complexity"); - setProperty(CODECLIMATE_REMEDIATION_MULTIPLIER, 200); - setProperty(CODECLIMATE_BLOCK_HIGHLIGHTING, false); - } - - @Override - public Object visit(ASTUserClass node, Object data) { - - int maxFields = getProperty(MAX_FIELDS_DESCRIPTOR); - - stats = new HashMap<>(5); - nodes = new HashMap<>(5); - - List<ASTField> l = node.findDescendantsOfType(ASTField.class); - - for (ASTField fd : l) { - if (fd.getNode().getModifierInfo().all(FINAL, STATIC)) { - continue; - } - ASTUserClass clazz = fd.getFirstParentOfType(ASTUserClass.class); - if (clazz != null) { - bumpCounterFor(clazz); - } - } - for (Map.Entry<String, Integer> entry : stats.entrySet()) { - int val = entry.getValue(); - Node n = nodes.get(entry.getKey()); - if (val > maxFields) { - addViolation(data, n); - } - } - return data; - } - - private void bumpCounterFor(ASTUserClass clazz) { - String key = clazz.getImage(); - if (!stats.containsKey(key)) { - stats.put(key, NumericConstants.ZERO); - nodes.put(key, clazz); - } - Integer i = Integer.valueOf(stats.get(key) + 1); - stats.put(key, i); - } -} diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/design/AbstractNcssCountRule.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/design/AbstractNcssCountRule.java new file mode 100644 index 00000000000..80612b84e16 --- /dev/null +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/design/AbstractNcssCountRule.java @@ -0,0 +1,162 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.apex.rule.design; + +import net.sourceforge.pmd.lang.apex.ast.ASTBreakStatement; +import net.sourceforge.pmd.lang.apex.ast.ASTContinueStatement; +import net.sourceforge.pmd.lang.apex.ast.ASTDoLoopStatement; +import net.sourceforge.pmd.lang.apex.ast.ASTForEachStatement; +import net.sourceforge.pmd.lang.apex.ast.ASTForLoopStatement; +import net.sourceforge.pmd.lang.apex.ast.ASTIfBlockStatement; +import net.sourceforge.pmd.lang.apex.ast.ASTIfElseBlockStatement; +import net.sourceforge.pmd.lang.apex.ast.ASTMethodCallExpression; +import net.sourceforge.pmd.lang.apex.ast.ASTReturnStatement; +import net.sourceforge.pmd.lang.apex.ast.ASTStatement; +import net.sourceforge.pmd.lang.apex.ast.ASTThrowStatement; +import net.sourceforge.pmd.lang.apex.ast.ASTTryCatchFinallyBlockStatement; +import net.sourceforge.pmd.lang.apex.ast.ASTWhileLoopStatement; +import net.sourceforge.pmd.lang.apex.ast.AbstractApexNodeBase; +import net.sourceforge.pmd.lang.apex.rule.AbstractStatisticalApexRule; +import net.sourceforge.pmd.lang.ast.Node; +import net.sourceforge.pmd.stat.DataPoint; +import net.sourceforge.pmd.util.NumericConstants; + +/** + * Abstract superclass for NCSS counting methods. Counts tokens according to + * <a href="http://www.kclee.de/clemens/java/javancss/">JavaNCSS rules</a>. + * + * @author ported from Java original of Jason Bennett + */ +public abstract class AbstractNcssCountRule extends AbstractStatisticalApexRule { + + private Class<?> nodeClass; + + /** + * Count the nodes of the given type using NCSS rules. + * + * @param nodeClass + * class of node to count + */ + protected AbstractNcssCountRule(Class<?> nodeClass) { + this.nodeClass = nodeClass; + + setProperty(MINIMUM_DESCRIPTOR, 1000d); + setProperty(CODECLIMATE_CATEGORIES, "Complexity"); + setProperty(CODECLIMATE_REMEDIATION_MULTIPLIER, 100); + setProperty(CODECLIMATE_BLOCK_HIGHLIGHTING, false); + } + + @Override + public Object visit(AbstractApexNodeBase node, Object data) { + int numNodes = 0; + + for (int i = 0; i < node.jjtGetNumChildren(); i++) { + AbstractApexNodeBase n = (AbstractApexNodeBase) node.jjtGetChild(i); + Integer treeSize = (Integer) n.jjtAccept(this, data); + numNodes += treeSize.intValue(); + } + + if (this.nodeClass.isInstance(node)) { + // Add 1 to account for base node + numNodes++; + DataPoint point = new DataPoint(); + point.setNode(node); + point.setScore(1.0 * numNodes); + point.setMessage(getMessage()); + addDataPoint(point); + } + + return Integer.valueOf(numNodes); + } + + /** + * Count the number of children of the given node. Adds one to count the + * node itself. + * + * @param node + * node having children counted + * @param data + * node data + * @return count of the number of children of the node, plus one + */ + protected Integer countNodeChildren(Node node, Object data) { + Integer nodeCount; + int lineCount = 0; + for (int i = 0; i < node.jjtGetNumChildren(); i++) { + nodeCount = (Integer) ((AbstractApexNodeBase) node.jjtGetChild(i)).jjtAccept(this, data); + lineCount += nodeCount.intValue(); + } + return ++lineCount; + } + + @Override + public Object visit(ASTForLoopStatement node, Object data) { + return countNodeChildren(node, data); + } + + @Override + public Object visit(ASTForEachStatement node, Object data) { + return countNodeChildren(node, data); + } + + @Override + public Object visit(ASTDoLoopStatement node, Object data) { + return countNodeChildren(node, data); + } + + @Override + public Object visit(ASTIfBlockStatement node, Object data) { + return countNodeChildren(node, data); + } + + @Override + public Object visit(ASTIfElseBlockStatement node, Object data) { + + Integer lineCount = countNodeChildren(node, data); + lineCount++; + + return lineCount; + } + + @Override + public Object visit(ASTWhileLoopStatement node, Object data) { + return countNodeChildren(node, data); + } + + @Override + public Object visit(ASTBreakStatement node, Object data) { + return NumericConstants.ONE; + } + + @Override + public Object visit(ASTTryCatchFinallyBlockStatement node, Object data) { + return countNodeChildren(node, data); + } + + @Override + public Object visit(ASTContinueStatement node, Object data) { + return NumericConstants.ONE; + } + + @Override + public Object visit(ASTReturnStatement node, Object data) { + return countNodeChildren(node, data); + } + + @Override + public Object visit(ASTThrowStatement node, Object data) { + return countNodeChildren(node, data); + } + + @Override + public Object visit(ASTStatement node, Object data) { + return NumericConstants.ONE; + } + + @Override + public Object visit(ASTMethodCallExpression node, Object data) { + return NumericConstants.ONE; + } +} diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/design/AvoidDeeplyNestedIfStmtsRule.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/design/AvoidDeeplyNestedIfStmtsRule.java new file mode 100644 index 00000000000..dc9e4d52d2d --- /dev/null +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/design/AvoidDeeplyNestedIfStmtsRule.java @@ -0,0 +1,51 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.apex.rule.design; + +import net.sourceforge.pmd.lang.apex.ast.ASTIfBlockStatement; +import net.sourceforge.pmd.lang.apex.ast.ASTUserClass; +import net.sourceforge.pmd.lang.apex.rule.AbstractApexRule; +import net.sourceforge.pmd.properties.IntegerProperty; + +public class AvoidDeeplyNestedIfStmtsRule extends AbstractApexRule { + + private int depth; + private int depthLimit; + + private static final IntegerProperty PROBLEM_DEPTH_DESCRIPTOR + = IntegerProperty.named("problemDepth") + .desc("The if statement depth reporting threshold") + .range(1, 25).defaultValue(3).uiOrder(1.0f).build(); + + public AvoidDeeplyNestedIfStmtsRule() { + definePropertyDescriptor(PROBLEM_DEPTH_DESCRIPTOR); + + setProperty(CODECLIMATE_CATEGORIES, "Complexity"); + // Note: Remedy needs better OO design and therefore high effort + setProperty(CODECLIMATE_REMEDIATION_MULTIPLIER, 200); + setProperty(CODECLIMATE_BLOCK_HIGHLIGHTING, false); + } + + @Override + public Object visit(ASTUserClass node, Object data) { + depth = 0; + depthLimit = getProperty(PROBLEM_DEPTH_DESCRIPTOR); + + return super.visit(node, data); + } + + @Override + public Object visit(ASTIfBlockStatement node, Object data) { + depth++; + + super.visit(node, data); + if (depth == depthLimit) { + addViolation(data, node); + } + depth--; + + return data; + } +} diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/design/CyclomaticComplexityRule.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/design/CyclomaticComplexityRule.java new file mode 100644 index 00000000000..08b6c567c5a --- /dev/null +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/design/CyclomaticComplexityRule.java @@ -0,0 +1,104 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.apex.rule.design; + + +import java.util.Stack; + +import net.sourceforge.pmd.lang.apex.ast.ASTMethod; +import net.sourceforge.pmd.lang.apex.ast.ASTUserClass; +import net.sourceforge.pmd.lang.apex.ast.ASTUserTrigger; +import net.sourceforge.pmd.lang.apex.metrics.ApexMetrics; +import net.sourceforge.pmd.lang.apex.metrics.api.ApexClassMetricKey; +import net.sourceforge.pmd.lang.apex.metrics.api.ApexOperationMetricKey; +import net.sourceforge.pmd.lang.apex.rule.AbstractApexRule; +import net.sourceforge.pmd.lang.metrics.ResultOption; +import net.sourceforge.pmd.properties.IntegerProperty; + + +/** + * Cyclomatic complexity rule using metrics. Uses Wmc to report classes. + * + * @author Clément Fournier + */ +public class CyclomaticComplexityRule extends AbstractApexRule { + + private static final IntegerProperty CLASS_LEVEL_DESCRIPTOR + = IntegerProperty.named("classReportLevel") + .desc("Total class complexity reporting threshold") + .range(1, 200) + .defaultValue(40) + .uiOrder(1.0f).build(); + + private static final IntegerProperty METHOD_LEVEL_DESCRIPTOR + = IntegerProperty.named("methodReportLevel") + .desc("Cyclomatic complexity reporting threshold") + .range(1, 30) + .defaultValue(10) + .uiOrder(2.0f).build(); + + private Stack<String> classNames = new Stack<>(); + private boolean inTrigger; + + + public CyclomaticComplexityRule() { + definePropertyDescriptor(CLASS_LEVEL_DESCRIPTOR); + definePropertyDescriptor(METHOD_LEVEL_DESCRIPTOR); + } + + + @Override + public Object visit(ASTUserTrigger node, Object data) { + inTrigger = true; + super.visit(node, data); + inTrigger = false; + return data; + } + + + @Override + public Object visit(ASTUserClass node, Object data) { + + classNames.push(node.getImage()); + super.visit(node, data); + classNames.pop(); + + if (ApexClassMetricKey.WMC.supports(node)) { + int classWmc = (int) ApexMetrics.get(ApexClassMetricKey.WMC, node); + + if (classWmc >= getProperty(CLASS_LEVEL_DESCRIPTOR)) { + int classHighest = (int) ApexMetrics.get(ApexOperationMetricKey.CYCLO, node, ResultOption.HIGHEST); + + String[] messageParams = {"class", + node.getImage(), + " total", + classWmc + " (highest " + classHighest + ")", }; + + addViolation(data, node, messageParams); + } + } + return data; + } + + + @Override + public final Object visit(ASTMethod node, Object data) { + + int cyclo = (int) ApexMetrics.get(ApexOperationMetricKey.CYCLO, node); + if (cyclo >= getProperty(METHOD_LEVEL_DESCRIPTOR)) { + String opType = inTrigger ? "trigger" + : node.getImage().equals(classNames.peek()) ? "constructor" + : "method"; + + addViolation(data, node, new String[]{opType, + node.getQualifiedName().getOperation(), + "", + "" + cyclo, }); + } + + return data; + } + +} diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/design/ExcessiveClassLengthRule.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/design/ExcessiveClassLengthRule.java new file mode 100644 index 00000000000..5bd4156c04b --- /dev/null +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/design/ExcessiveClassLengthRule.java @@ -0,0 +1,33 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.apex.rule.design; + +import static apex.jorje.semantic.symbol.type.AnnotationTypeInfos.IS_TEST; + +import net.sourceforge.pmd.lang.apex.ast.ASTUserClass; + +/** + * This rule detects when a class exceeds a certain threshold. i.e. if a class + * has more than 1000 lines of code. + */ +public class ExcessiveClassLengthRule extends ExcessiveLengthRule { + public ExcessiveClassLengthRule() { + super(ASTUserClass.class); + setProperty(MINIMUM_DESCRIPTOR, 1000d); + + setProperty(CODECLIMATE_CATEGORIES, "Complexity"); + setProperty(CODECLIMATE_REMEDIATION_MULTIPLIER, 150); + setProperty(CODECLIMATE_BLOCK_HIGHLIGHTING, false); + } + + @Override + public Object visit(ASTUserClass node, Object data) { + if (node.getNode().getModifiers().getModifiers().not(IS_TEST)) { + return super.visit(node, data); + } + + return data; + } +} diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/complexity/ExcessiveLengthRule.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/design/ExcessiveLengthRule.java similarity index 80% rename from pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/complexity/ExcessiveLengthRule.java rename to pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/design/ExcessiveLengthRule.java index 61930b833a6..75a03280422 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/complexity/ExcessiveLengthRule.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/design/ExcessiveLengthRule.java @@ -2,9 +2,9 @@ * BSD-style license; for more info see http://pmd.sourceforge.net/license.html */ -package net.sourceforge.pmd.lang.apex.rule.complexity; +package net.sourceforge.pmd.lang.apex.rule.design; -import net.sourceforge.pmd.lang.apex.ast.ApexNode; +import net.sourceforge.pmd.lang.apex.ast.AbstractApexNodeBase; import net.sourceforge.pmd.lang.apex.rule.AbstractStatisticalApexRule; import net.sourceforge.pmd.stat.DataPoint; @@ -16,7 +16,7 @@ public ExcessiveLengthRule(Class<?> nodeClass) { } @Override - public Object visit(ApexNode<?> node, Object data) { + public Object visit(AbstractApexNodeBase node, Object data) { if (nodeClass.isInstance(node)) { DataPoint point = new DataPoint(); point.setNode(node); diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/complexity/ExcessiveNodeCountRule.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/design/ExcessiveNodeCountRule.java similarity index 82% rename from pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/complexity/ExcessiveNodeCountRule.java rename to pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/design/ExcessiveNodeCountRule.java index 5a348f0e497..5819e105256 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/complexity/ExcessiveNodeCountRule.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/design/ExcessiveNodeCountRule.java @@ -2,9 +2,9 @@ * BSD-style license; for more info see http://pmd.sourceforge.net/license.html */ -package net.sourceforge.pmd.lang.apex.rule.complexity; +package net.sourceforge.pmd.lang.apex.rule.design; -import net.sourceforge.pmd.lang.apex.ast.ApexNode; +import net.sourceforge.pmd.lang.apex.ast.AbstractApexNodeBase; import net.sourceforge.pmd.lang.apex.rule.AbstractStatisticalApexRule; import net.sourceforge.pmd.stat.DataPoint; @@ -35,11 +35,11 @@ public ExcessiveNodeCountRule(Class<?> nodeClass) { } @Override - public Object visit(ApexNode<?> node, Object data) { + public Object visit(AbstractApexNodeBase node, Object data) { int numNodes = 0; for (int i = 0; i < node.jjtGetNumChildren(); i++) { - Integer treeSize = (Integer) ((ApexNode<?>) node.jjtGetChild(i)).jjtAccept(this, data); + Integer treeSize = (Integer) ((AbstractApexNodeBase) node.jjtGetChild(i)).jjtAccept(this, data); numNodes += treeSize; } diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/design/ExcessiveParameterListRule.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/design/ExcessiveParameterListRule.java new file mode 100644 index 00000000000..f3239586831 --- /dev/null +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/design/ExcessiveParameterListRule.java @@ -0,0 +1,29 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.apex.rule.design; + +import net.sourceforge.pmd.lang.apex.ast.ASTMethod; +import net.sourceforge.pmd.lang.apex.ast.ASTParameter; +import net.sourceforge.pmd.util.NumericConstants; + +/** + * This rule detects an abnormally long parameter list. Note: This counts Nodes, + * and not necessarily parameters, so the numbers may not match up. (But + * topcount and sigma should work.) + */ +public class ExcessiveParameterListRule extends ExcessiveNodeCountRule { + public ExcessiveParameterListRule() { + super(ASTMethod.class); + setProperty(MINIMUM_DESCRIPTOR, 4d); + setProperty(CODECLIMATE_CATEGORIES, "Complexity"); + setProperty(CODECLIMATE_REMEDIATION_MULTIPLIER, 50); + setProperty(CODECLIMATE_BLOCK_HIGHLIGHTING, false); + } + + @Override + public Object visit(ASTParameter node, Object data) { + return NumericConstants.ONE; + } +} diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/design/ExcessivePublicCountRule.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/design/ExcessivePublicCountRule.java new file mode 100644 index 00000000000..8c8be1c6981 --- /dev/null +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/design/ExcessivePublicCountRule.java @@ -0,0 +1,54 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.apex.rule.design; + +import static apex.jorje.semantic.symbol.type.ModifierTypeInfos.PUBLIC; +import static apex.jorje.semantic.symbol.type.ModifierTypeInfos.STATIC; + +import net.sourceforge.pmd.lang.apex.ast.ASTFieldDeclarationStatements; +import net.sourceforge.pmd.lang.apex.ast.ASTMethod; +import net.sourceforge.pmd.lang.apex.ast.ASTUserClass; +import net.sourceforge.pmd.util.NumericConstants; + +/** + * Rule attempts to count all public methods and public attributes + * defined in a class. + * + * <p>If a class has a high number of public operations, it might be wise + * to consider whether it would be appropriate to divide it into + * subclasses.</p> + * + * <p>A large proportion of public members and operations means the class + * has high potential to be affected by external classes. Futhermore, + * increased effort will be required to thoroughly test the class.</p> + * + * @author ported from Java original of aglover + */ +public class ExcessivePublicCountRule extends ExcessiveNodeCountRule { + + public ExcessivePublicCountRule() { + super(ASTUserClass.class); + setProperty(MINIMUM_DESCRIPTOR, 20d); + setProperty(CODECLIMATE_CATEGORIES, "Complexity"); + setProperty(CODECLIMATE_REMEDIATION_MULTIPLIER, 150); + setProperty(CODECLIMATE_BLOCK_HIGHLIGHTING, false); + } + + @Override + public Object visit(ASTMethod node, Object data) { + if (node.getNode().getModifiers().has(PUBLIC) && !node.getImage().matches("<clinit>|<init>|clone")) { + return NumericConstants.ONE; + } + return NumericConstants.ZERO; + } + + @Override + public Object visit(ASTFieldDeclarationStatements node, Object data) { + if (node.getNode().getModifiers().has(PUBLIC) && !node.getNode().getModifiers().has(STATIC)) { + return NumericConstants.ONE; + } + return NumericConstants.ZERO; + } +} diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/design/NcssConstructorCountRule.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/design/NcssConstructorCountRule.java new file mode 100644 index 00000000000..f9ba66be189 --- /dev/null +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/design/NcssConstructorCountRule.java @@ -0,0 +1,43 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.apex.rule.design; + +import net.sourceforge.pmd.lang.apex.ast.ASTMethod; +import net.sourceforge.pmd.stat.DataPoint; +import net.sourceforge.pmd.util.NumericConstants; + +/** + * Non-commented source statement counter for constructors. + * + * @author ported from Java original by Jason Bennett + */ +public class NcssConstructorCountRule extends AbstractNcssCountRule { + + /** + * Count constructor declarations. This includes any explicit super() calls. + */ + public NcssConstructorCountRule() { + super(ASTMethod.class); + setProperty(MINIMUM_DESCRIPTOR, 20d); + setProperty(CODECLIMATE_CATEGORIES, "Complexity"); + setProperty(CODECLIMATE_REMEDIATION_MULTIPLIER, 50); + setProperty(CODECLIMATE_BLOCK_HIGHLIGHTING, false); + } + + @Override + public Object visit(ASTMethod node, Object data) { + if (node.getNode().getMethodInfo().isConstructor()) { + return super.visit(node, data); + } + + return NumericConstants.ZERO; + } + + @Override + public Object[] getViolationParameters(DataPoint point) { + // TODO need to put class name or constructor ID in string + return new String[] { String.valueOf((int) point.getScore()) }; + } +} diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/design/NcssMethodCountRule.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/design/NcssMethodCountRule.java new file mode 100644 index 00000000000..0604ecfa073 --- /dev/null +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/design/NcssMethodCountRule.java @@ -0,0 +1,43 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.apex.rule.design; + +import net.sourceforge.pmd.lang.apex.ast.ASTMethod; +import net.sourceforge.pmd.stat.DataPoint; +import net.sourceforge.pmd.util.NumericConstants; + +/** + * Non-commented source statement counter for methods. + * + * @author ported from Java original of Jason Bennett + */ +public class NcssMethodCountRule extends AbstractNcssCountRule { + + /** + * Count the size of all non-constructor methods. + */ + public NcssMethodCountRule() { + super(ASTMethod.class); + setProperty(MINIMUM_DESCRIPTOR, 40d); + setProperty(CODECLIMATE_CATEGORIES, "Complexity"); + setProperty(CODECLIMATE_REMEDIATION_MULTIPLIER, 50); + setProperty(CODECLIMATE_BLOCK_HIGHLIGHTING, false); + } + + @Override + public Object visit(ASTMethod node, Object data) { + if (!node.getNode().getMethodInfo().isConstructor()) { + return super.visit(node, data); + } + + return NumericConstants.ZERO; + } + + @Override + public Object[] getViolationParameters(DataPoint point) { + return new String[] { ((ASTMethod) point.getNode()).getNode().getMethodInfo().getName(), + String.valueOf((int) point.getScore()), }; + } +} diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/design/NcssTypeCountRule.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/design/NcssTypeCountRule.java new file mode 100644 index 00000000000..578a9f6dd41 --- /dev/null +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/design/NcssTypeCountRule.java @@ -0,0 +1,77 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.apex.rule.design; + +import net.sourceforge.pmd.lang.apex.ast.ASTFieldDeclaration; +import net.sourceforge.pmd.lang.apex.ast.ASTMethod; +import net.sourceforge.pmd.lang.apex.ast.ASTUserClass; +import net.sourceforge.pmd.lang.apex.ast.ASTUserEnum; +import net.sourceforge.pmd.lang.apex.ast.ASTUserInterface; +import net.sourceforge.pmd.stat.DataPoint; +import net.sourceforge.pmd.util.NumericConstants; + +/** + * Non-commented source statement counter for type declarations. + * + * @author ported from Java original of Jason Bennett + */ +public class NcssTypeCountRule extends AbstractNcssCountRule { + + /** + * Count type declarations. This includes classes as well as enums and + * annotations. + */ + public NcssTypeCountRule() { + super(ASTUserClass.class); + setProperty(MINIMUM_DESCRIPTOR, 500d); + setProperty(CODECLIMATE_CATEGORIES, "Complexity"); + setProperty(CODECLIMATE_REMEDIATION_MULTIPLIER, 250); + setProperty(CODECLIMATE_BLOCK_HIGHLIGHTING, false); + } + + @Override + public Object visit(ASTUserClass node, Object data) { + + if (!node.hasDescendantOfAnyType(ASTUserClass.class)) { + return super.visit(node, data); + } + + return countNodeChildren(node, data); + } + + @Override + public Object visit(ASTUserInterface node, Object data) { + + if (!node.hasDescendantOfAnyType(ASTUserClass.class)) { + return super.visit(node, data); + } + + return countNodeChildren(node, data); + } + + @Override + public Object visit(ASTUserEnum node, Object data) { + return countNodeChildren(node, data); + } + + @Override + public Object visit(ASTMethod node, Object data) { + if (!node.getImage().matches("<clinit>|<init>|clone")) { + return countNodeChildren(node, data); + } + + return NumericConstants.ZERO; + } + + @Override + public Object visit(ASTFieldDeclaration node, Object data) { + return NumericConstants.ONE; + } + + @Override + public Object[] getViolationParameters(DataPoint point) { + return new String[] { String.valueOf((int) point.getScore()) }; + } +} diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/design/StdCyclomaticComplexityRule.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/design/StdCyclomaticComplexityRule.java new file mode 100644 index 00000000000..323dd086ffc --- /dev/null +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/design/StdCyclomaticComplexityRule.java @@ -0,0 +1,215 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.apex.rule.design; + +import java.util.Stack; + +import net.sourceforge.pmd.lang.apex.ast.ASTBooleanExpression; +import net.sourceforge.pmd.lang.apex.ast.ASTDoLoopStatement; +import net.sourceforge.pmd.lang.apex.ast.ASTForEachStatement; +import net.sourceforge.pmd.lang.apex.ast.ASTForLoopStatement; +import net.sourceforge.pmd.lang.apex.ast.ASTIfBlockStatement; +import net.sourceforge.pmd.lang.apex.ast.ASTMethod; +import net.sourceforge.pmd.lang.apex.ast.ASTTernaryExpression; +import net.sourceforge.pmd.lang.apex.ast.ASTTryCatchFinallyBlockStatement; +import net.sourceforge.pmd.lang.apex.ast.ASTUserClass; +import net.sourceforge.pmd.lang.apex.ast.ASTUserEnum; +import net.sourceforge.pmd.lang.apex.ast.ASTUserInterface; +import net.sourceforge.pmd.lang.apex.ast.ASTUserTrigger; +import net.sourceforge.pmd.lang.apex.ast.ASTWhileLoopStatement; +import net.sourceforge.pmd.lang.apex.rule.AbstractApexRule; +import net.sourceforge.pmd.properties.BooleanProperty; +import net.sourceforge.pmd.properties.IntegerProperty; + +/** + * Implements the standard cyclomatic complexity rule + * <p> + * Standard rules: +1 for each decision point, but not including boolean + * operators unlike CyclomaticComplexityRule. + * + * @author ported on Java version of Alan Hohn, based on work by Donald A. + * Leckie + * + * @since June 18, 2014 + */ +public class StdCyclomaticComplexityRule extends AbstractApexRule { + + public static final IntegerProperty REPORT_LEVEL_DESCRIPTOR + = IntegerProperty.named("reportLevel") + .desc("Cyclomatic Complexity reporting threshold") + .range(1, 30).defaultValue(10).uiOrder(1.0f).build(); + + public static final BooleanProperty SHOW_CLASSES_COMPLEXITY_DESCRIPTOR = new BooleanProperty( + "showClassesComplexity", "Add class average violations to the report", true, 2.0f); + + public static final BooleanProperty SHOW_METHODS_COMPLEXITY_DESCRIPTOR = new BooleanProperty( + "showMethodsComplexity", "Add method average violations to the report", true, 3.0f); + + private int reportLevel; + private boolean showClassesComplexity = true; + private boolean showMethodsComplexity = true; + + protected static class Entry { + private int decisionPoints = 1; + public int highestDecisionPoints; + public int methodCount; + + private Entry() { + } + + public void bumpDecisionPoints() { + decisionPoints++; + } + + public void bumpDecisionPoints(int size) { + decisionPoints += size; + } + + public int getComplexityAverage() { + return (double) methodCount == 0 ? 1 : (int) Math.rint((double) decisionPoints / (double) methodCount); + } + } + + protected Stack<Entry> entryStack = new Stack<>(); + + public StdCyclomaticComplexityRule() { + definePropertyDescriptor(REPORT_LEVEL_DESCRIPTOR); + definePropertyDescriptor(SHOW_CLASSES_COMPLEXITY_DESCRIPTOR); + definePropertyDescriptor(SHOW_METHODS_COMPLEXITY_DESCRIPTOR); + + setProperty(CODECLIMATE_CATEGORIES, "Complexity"); + setProperty(CODECLIMATE_REMEDIATION_MULTIPLIER, 250); + setProperty(CODECLIMATE_BLOCK_HIGHLIGHTING, false); + } + + @Override + public Object visit(ASTUserClass node, Object data) { + reportLevel = getProperty(REPORT_LEVEL_DESCRIPTOR); + showClassesComplexity = getProperty(SHOW_CLASSES_COMPLEXITY_DESCRIPTOR); + showMethodsComplexity = getProperty(SHOW_METHODS_COMPLEXITY_DESCRIPTOR); + entryStack.push(new Entry()); + super.visit(node, data); + Entry classEntry = entryStack.pop(); + if (showClassesComplexity) { + if (classEntry.getComplexityAverage() >= reportLevel || classEntry.highestDecisionPoints >= reportLevel) { + addViolation(data, node, new String[] { "class", node.getImage(), + classEntry.getComplexityAverage() + " (Highest = " + classEntry.highestDecisionPoints + ')', }); + } + } + return data; + } + + @Override + public Object visit(ASTUserTrigger node, Object data) { + reportLevel = getProperty(REPORT_LEVEL_DESCRIPTOR); + showClassesComplexity = getProperty(SHOW_CLASSES_COMPLEXITY_DESCRIPTOR); + showMethodsComplexity = getProperty(SHOW_METHODS_COMPLEXITY_DESCRIPTOR); + entryStack.push(new Entry()); + super.visit(node, data); + Entry classEntry = entryStack.pop(); + if (showClassesComplexity) { + if (classEntry.getComplexityAverage() >= reportLevel || classEntry.highestDecisionPoints >= reportLevel) { + addViolation(data, node, new String[] { "trigger", node.getImage(), + classEntry.getComplexityAverage() + " (Highest = " + classEntry.highestDecisionPoints + ')', }); + } + } + return data; + } + + @Override + public Object visit(ASTUserInterface node, Object data) { + return data; + } + + @Override + public Object visit(ASTUserEnum node, Object data) { + entryStack.push(new Entry()); + super.visit(node, data); + Entry classEntry = entryStack.pop(); + if (classEntry.getComplexityAverage() >= reportLevel || classEntry.highestDecisionPoints >= reportLevel) { + addViolation(data, node, new String[] { "class", node.getImage(), + classEntry.getComplexityAverage() + "(Highest = " + classEntry.highestDecisionPoints + ')', }); + } + return data; + } + + @Override + public Object visit(ASTMethod node, Object data) { + if (!node.getImage().matches("<clinit>|<init>|clone")) { + entryStack.push(new Entry()); + super.visit(node, data); + Entry methodEntry = entryStack.pop(); + int methodDecisionPoints = methodEntry.decisionPoints; + Entry classEntry = entryStack.peek(); + classEntry.methodCount++; + classEntry.bumpDecisionPoints(methodDecisionPoints); + + if (methodDecisionPoints > classEntry.highestDecisionPoints) { + classEntry.highestDecisionPoints = methodDecisionPoints; + } + + if (showMethodsComplexity && methodEntry.decisionPoints >= reportLevel) { + String methodType = node.getNode().getMethodInfo().isConstructor() ? "constructor" : "method"; + addViolation(data, node, + new String[] { methodType, node.getImage(), String.valueOf(methodEntry.decisionPoints) }); + } + } + return data; + } + + @Override + public Object visit(ASTIfBlockStatement node, Object data) { + entryStack.peek().bumpDecisionPoints(); + super.visit(node, data); + return data; + } + + @Override + public Object visit(ASTTryCatchFinallyBlockStatement node, Object data) { + entryStack.peek().bumpDecisionPoints(); + super.visit(node, data); + return data; + } + + @Override + public Object visit(ASTForLoopStatement node, Object data) { + entryStack.peek().bumpDecisionPoints(); + super.visit(node, data); + return data; + } + + @Override + public Object visit(ASTForEachStatement node, Object data) { + entryStack.peek().bumpDecisionPoints(); + super.visit(node, data); + return data; + } + + @Override + public Object visit(ASTWhileLoopStatement node, Object data) { + entryStack.peek().bumpDecisionPoints(); + super.visit(node, data); + return data; + } + + @Override + public Object visit(ASTDoLoopStatement node, Object data) { + entryStack.peek().bumpDecisionPoints(); + super.visit(node, data); + return data; + } + + @Override + public Object visit(ASTTernaryExpression node, Object data) { + entryStack.peek().bumpDecisionPoints(); + super.visit(node, data); + return data; + } + + @Override + public Object visit(ASTBooleanExpression node, Object data) { + return data; + } +} diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/design/TooManyFieldsRule.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/design/TooManyFieldsRule.java new file mode 100644 index 00000000000..58ac9101898 --- /dev/null +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/design/TooManyFieldsRule.java @@ -0,0 +1,77 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.apex.rule.design; + +import static apex.jorje.semantic.symbol.type.ModifierTypeInfos.FINAL; +import static apex.jorje.semantic.symbol.type.ModifierTypeInfos.STATIC; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import net.sourceforge.pmd.lang.apex.ast.ASTField; +import net.sourceforge.pmd.lang.apex.ast.ASTUserClass; +import net.sourceforge.pmd.lang.apex.rule.AbstractApexRule; +import net.sourceforge.pmd.lang.ast.Node; +import net.sourceforge.pmd.properties.IntegerProperty; +import net.sourceforge.pmd.util.NumericConstants; + +public class TooManyFieldsRule extends AbstractApexRule { + + private static final int DEFAULT_MAXFIELDS = 15; + + private Map<String, Integer> stats; + private Map<String, ASTUserClass> nodes; + + private static final IntegerProperty MAX_FIELDS_DESCRIPTOR = new IntegerProperty("maxfields", + "Max allowable fields", 1, 300, DEFAULT_MAXFIELDS, 1.0f); + + public TooManyFieldsRule() { + definePropertyDescriptor(MAX_FIELDS_DESCRIPTOR); + + setProperty(CODECLIMATE_CATEGORIES, "Complexity"); + setProperty(CODECLIMATE_REMEDIATION_MULTIPLIER, 200); + setProperty(CODECLIMATE_BLOCK_HIGHLIGHTING, false); + } + + @Override + public Object visit(ASTUserClass node, Object data) { + + int maxFields = getProperty(MAX_FIELDS_DESCRIPTOR); + + stats = new HashMap<>(5); + nodes = new HashMap<>(5); + + List<ASTField> l = node.findDescendantsOfType(ASTField.class); + + for (ASTField fd : l) { + if (fd.getNode().getModifierInfo().all(FINAL, STATIC)) { + continue; + } + ASTUserClass clazz = fd.getFirstParentOfType(ASTUserClass.class); + if (clazz != null) { + bumpCounterFor(clazz); + } + } + for (Map.Entry<String, Integer> entry : stats.entrySet()) { + int val = entry.getValue(); + Node n = nodes.get(entry.getKey()); + if (val > maxFields) { + addViolation(data, n); + } + } + return data; + } + + private void bumpCounterFor(ASTUserClass clazz) { + String key = clazz.getImage(); + if (!stats.containsKey(key)) { + stats.put(key, NumericConstants.ZERO); + nodes.put(key, clazz); + } + Integer i = Integer.valueOf(stats.get(key) + 1); + stats.put(key, i); + } +} diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/documentation/ApexDocRule.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/documentation/ApexDocRule.java new file mode 100644 index 00000000000..d50277fd236 --- /dev/null +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/documentation/ApexDocRule.java @@ -0,0 +1,180 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.apex.rule.documentation; + +import static apex.jorje.semantic.symbol.type.ModifierTypeInfos.GLOBAL; +import static apex.jorje.semantic.symbol.type.ModifierTypeInfos.OVERRIDE; + +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +import net.sourceforge.pmd.lang.apex.ast.ASTAnnotation; +import net.sourceforge.pmd.lang.apex.ast.ASTFormalComment; +import net.sourceforge.pmd.lang.apex.ast.ASTMethod; +import net.sourceforge.pmd.lang.apex.ast.ASTModifierNode; +import net.sourceforge.pmd.lang.apex.ast.ASTProperty; +import net.sourceforge.pmd.lang.apex.ast.ASTUserClass; +import net.sourceforge.pmd.lang.apex.ast.ASTUserInterface; +import net.sourceforge.pmd.lang.apex.ast.ApexNode; +import net.sourceforge.pmd.lang.apex.rule.AbstractApexRule; + +import apex.jorje.data.Locations; +import apex.jorje.semantic.ast.modifier.ModifierGroup; + +public class ApexDocRule extends AbstractApexRule { + private static final Pattern DESCRIPTION_PATTERN = Pattern.compile("@description\\s"); + private static final Pattern RETURN_PATTERN = Pattern.compile("@return\\s"); + private static final Pattern PARAM_PATTERN = Pattern.compile("@param\\s+(\\w+)\\s"); + + private static final String MISSING_COMMENT_MESSAGE = "Missing ApexDoc comment"; + private static final String MISSING_DESCRIPTION_MESSAGE = "Missing ApexDoc @description"; + private static final String MISSING_RETURN_MESSAGE = "Missing ApexDoc @return"; + private static final String UNEXPECTED_RETURN_MESSAGE = "Unexpected ApexDoc @return"; + private static final String MISMATCHED_PARAM_MESSAGE = "Missing or mismatched ApexDoc @param"; + + public ApexDocRule() { + addRuleChainVisit(ASTUserClass.class); + addRuleChainVisit(ASTUserInterface.class); + addRuleChainVisit(ASTMethod.class); + addRuleChainVisit(ASTProperty.class); + } + + @Override + public Object visit(ASTUserClass node, Object data) { + handleClassOrInterface(node, data); + return data; + } + + @Override + public Object visit(ASTUserInterface node, Object data) { + handleClassOrInterface(node, data); + return data; + } + + @Override + public Object visit(ASTMethod node, Object data) { + if (node.jjtGetParent() instanceof ASTProperty) { + // Skip property methods, doc is required on the property itself + return data; + } + + ApexDocComment comment = getApexDocComment(node); + if (comment == null) { + if (shouldHaveApexDocs(node)) { + addViolationWithMessage(data, node, MISSING_COMMENT_MESSAGE); + } + } else { + if (!comment.hasDescription) { + addViolationWithMessage(data, node, MISSING_DESCRIPTION_MESSAGE); + } + + String returnType = node.getNode().getReturnTypeRef().toString(); + boolean shouldHaveReturn = !(returnType.isEmpty() || "void".equalsIgnoreCase(returnType)); + if (comment.hasReturn != shouldHaveReturn) { + if (shouldHaveReturn) { + addViolationWithMessage(data, node, MISSING_RETURN_MESSAGE); + } else { + addViolationWithMessage(data, node, UNEXPECTED_RETURN_MESSAGE); + } + } + + // Collect parameter names in order + final List<String> params = node.getNode().getMethodInfo().getParameters() + .stream().map(p -> p.getName().getValue()).collect(Collectors.toList()); + + if (!comment.params.equals(params)) { + addViolationWithMessage(data, node, MISMATCHED_PARAM_MESSAGE); + } + } + + return data; + } + + @Override + public Object visit(ASTProperty node, Object data) { + ApexDocComment comment = getApexDocComment(node); + if (comment == null) { + if (shouldHaveApexDocs(node)) { + addViolationWithMessage(data, node, MISSING_COMMENT_MESSAGE); + } + } else { + if (!comment.hasDescription) { + addViolationWithMessage(data, node, MISSING_DESCRIPTION_MESSAGE); + } + } + + return data; + } + + private void handleClassOrInterface(ApexNode<?> node, Object data) { + ApexDocComment comment = getApexDocComment(node); + if (comment == null) { + if (shouldHaveApexDocs(node)) { + addViolationWithMessage(data, node, MISSING_COMMENT_MESSAGE); + } + } else { + if (!comment.hasDescription) { + addViolationWithMessage(data, node, MISSING_DESCRIPTION_MESSAGE); + } + } + } + + private boolean shouldHaveApexDocs(ApexNode<?> node) { + if (node.getNode().getLoc() == Locations.NONE) { + return false; + } + + // is this a test? + for (final ASTAnnotation annotation : node.findDescendantsOfType(ASTAnnotation.class)) { + if (annotation.getImage().equals("IsTest")) { + return false; + } + } + + ASTModifierNode modifier = node.getFirstChildOfType(ASTModifierNode.class); + if (modifier != null) { + boolean isPublic = modifier.isPublic(); + ModifierGroup modifierGroup = modifier.getNode().getModifiers(); + boolean isGlobal = modifierGroup.has(GLOBAL); + boolean isOverride = modifierGroup.has(OVERRIDE); + return (isPublic || isGlobal) && !isOverride; + } + return false; + } + + private ApexDocComment getApexDocComment(ApexNode<?> node) { + ASTFormalComment comment = node.getFirstChildOfType(ASTFormalComment.class); + if (comment != null) { + String token = comment.getToken(); + + boolean hasDescription = DESCRIPTION_PATTERN.matcher(token).find(); + boolean hasReturn = RETURN_PATTERN.matcher(token).find(); + + ArrayList<String> params = new ArrayList<>(); + Matcher paramMatcher = PARAM_PATTERN.matcher(token); + while (paramMatcher.find()) { + params.add(paramMatcher.group(1)); + } + + return new ApexDocComment(hasDescription, hasReturn, params); + } + return null; + } + + private static class ApexDocComment { + boolean hasDescription; + boolean hasReturn; + List<String> params; + + ApexDocComment(boolean hasDescription, boolean hasReturn, List<String> params) { + this.hasDescription = hasDescription; + this.hasReturn = hasReturn; + this.params = params; + } + } +} diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/errorprone/AvoidHardcodingIdRule.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/errorprone/AvoidHardcodingIdRule.java new file mode 100644 index 00000000000..248784713dc --- /dev/null +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/errorprone/AvoidHardcodingIdRule.java @@ -0,0 +1,79 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.apex.rule.errorprone; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.regex.Pattern; + +import net.sourceforge.pmd.lang.apex.ast.ASTLiteralExpression; +import net.sourceforge.pmd.lang.apex.rule.AbstractApexRule; + +public class AvoidHardcodingIdRule extends AbstractApexRule { + private static final Pattern PATTERN = Pattern.compile("^[a-zA-Z0-9]{5}0[a-zA-Z0-9]{9}([a-zA-Z0-5]{3})?$"); + private static final Map<String, Character> CHECKSUM_LOOKUP; + + static { + final Map<String, Character> lookup = new HashMap<>(); + final char[] chartable = "ABCDEFGHIJKLMNOPQRSTUVWXYZ012345".toCharArray(); + + for (int i = 0; i < chartable.length; i++) { + lookup.put(String.format("%5s", Integer.toBinaryString(i)).replace(' ', '0'), chartable[i]); + } + + CHECKSUM_LOOKUP = Collections.unmodifiableMap(lookup); + } + + public AvoidHardcodingIdRule() { + setProperty(CODECLIMATE_CATEGORIES, "Style"); + setProperty(CODECLIMATE_REMEDIATION_MULTIPLIER, 100); + setProperty(CODECLIMATE_BLOCK_HIGHLIGHTING, false); + + addRuleChainVisit(ASTLiteralExpression.class); + } + + @Override + public Object visit(ASTLiteralExpression node, Object data) { + Object o = node.getNode().getLiteral(); + if (o instanceof String) { + String literal = (String) o; + if (PATTERN.matcher(literal).matches()) { + // 18-digit ids are just 15 digit ids + checksums, validate it or it's not an id + if (literal.length() == 18 && !validateChecksum(literal)) { + return data; + } + addViolation(data, node); + } + } + return data; + } + + /* + * ID validation - sources: + * https://stackoverflow.com/questions/9742913/validating-a-salesforce-id#answer-29299786 + * https://gist.github.com/jeriley/36b29f7c46527af4532aaf092c90dd56 + */ + private boolean validateChecksum(String literal) { + final String part1 = literal.substring(0, 5); + final String part2 = literal.substring(5, 10); + final String part3 = literal.substring(10, 15); + + final char checksum1 = checksum(part1); + final char checksum2 = checksum(part2); + final char checksum3 = checksum(part3); + + return literal.charAt(15) == checksum1 && literal.charAt(16) == checksum2 + && literal.charAt(17) == checksum3; + } + + private char checksum(String part) { + final StringBuilder sb = new StringBuilder(5); + for (int i = 4; i >= 0; i--) { + sb.append(Character.isUpperCase(part.charAt(i)) ? '1' : '0'); + } + return CHECKSUM_LOOKUP.get(sb.toString()); + } +} diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/errorprone/AvoidNonExistentAnnotationsRule.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/errorprone/AvoidNonExistentAnnotationsRule.java new file mode 100644 index 00000000000..11eeb52b2bd --- /dev/null +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/errorprone/AvoidNonExistentAnnotationsRule.java @@ -0,0 +1,106 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.apex.rule.errorprone; + +import java.util.HashSet; +import java.util.Set; + +import net.sourceforge.pmd.lang.apex.ast.ASTField; +import net.sourceforge.pmd.lang.apex.ast.ASTMethod; +import net.sourceforge.pmd.lang.apex.ast.ASTProperty; +import net.sourceforge.pmd.lang.apex.ast.ASTUserClass; +import net.sourceforge.pmd.lang.apex.ast.ASTUserEnum; +import net.sourceforge.pmd.lang.apex.ast.ASTUserInterface; +import net.sourceforge.pmd.lang.apex.ast.AbstractApexNode; +import net.sourceforge.pmd.lang.apex.rule.AbstractApexRule; + +import apex.jorje.semantic.ast.compilation.UserClass; +import apex.jorje.semantic.ast.compilation.UserEnum; +import apex.jorje.semantic.ast.compilation.UserInterface; +import apex.jorje.semantic.ast.member.Field; +import apex.jorje.semantic.ast.member.Method; +import apex.jorje.semantic.ast.member.Property; +import apex.jorje.semantic.ast.modifier.Annotation; +import apex.jorje.semantic.ast.modifier.ModifierNode; +import apex.jorje.semantic.symbol.type.AnnotationTypeInfos; +import apex.jorje.semantic.symbol.type.StandardAnnotationTypeInfo; + +/** + * Apex supported non existent annotations for legacy reasons. + * In the future, use of such non-existent annotations could result in broken apex code that will not compile. + * This will prevent users of garbage annotations from being able to use legitimate annotations added to apex in the future. + * A full list of supported annotations can be found at https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_classes_annotation.htm + * + * @author a.subramanian + */ +public class AvoidNonExistentAnnotationsRule extends AbstractApexRule { + + private static final Set<StandardAnnotationTypeInfo> SUPPORTED_APEX_ANNOTATIONS = getSupportedApexAnnotations(); + + @Override + public Object visit(final ASTUserClass node, final Object data) { + final UserClass userClass = node.getNode(); + checkForNonExistentAnnotation(node, userClass.getModifiers(), data); + return super.visit(node, data); + } + + @Override + public final Object visit(ASTUserInterface node, final Object data) { + final UserInterface userInterface = node.getNode(); + checkForNonExistentAnnotation(node, userInterface.getModifiers(), data); + return super.visit(node, data); + } + + @Override + public Object visit(final ASTUserEnum node, final Object data) { + final UserEnum userEnum = node.getNode(); + checkForNonExistentAnnotation(node, userEnum.getModifiers(), data); + return super.visit(node, data); + } + + @Override + public Object visit(final ASTMethod node, final Object data) { + final Method method = node.getNode(); + return checkForNonExistentAnnotation(node, method.getModifiersNode(), data); + } + + @Override + public Object visit(final ASTProperty node, final Object data) { + final Property property = node.getNode(); + // may have nested methods, don't visit children + return checkForNonExistentAnnotation(node, property.getModifiersNode(), data); + } + + @Override + public Object visit(final ASTField node, final Object data) { + final Field field = node.getNode(); + return checkForNonExistentAnnotation(node, field.getModifiers(), data); + } + + private Object checkForNonExistentAnnotation(final AbstractApexNode<?> node, final ModifierNode modifierNode, final Object data) { + for (final Annotation annotation : modifierNode.getModifiers().getAnnotations()) { + if (!SUPPORTED_APEX_ANNOTATIONS.contains(annotation.getType())) { + addViolationWithMessage(data, node, "Use of non existent annotations will lead to broken Apex code which will not compile in the future."); + } + } + return data; + } + + private static Set<StandardAnnotationTypeInfo> getSupportedApexAnnotations() { + final java.lang.reflect.Field[] fields = AnnotationTypeInfos.class.getFields(); + final Set<StandardAnnotationTypeInfo> annotationTypeInfos = new HashSet<>(); + for (final java.lang.reflect.Field field : fields) { + if (field.getType().isAssignableFrom(StandardAnnotationTypeInfo.class)) { + field.setAccessible(true); + try { + annotationTypeInfos.add((StandardAnnotationTypeInfo) field.get(null)); + } catch (final Exception illegalAccessException) { + continue; + } + } + } + return annotationTypeInfos; + } +} diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/errorprone/MethodWithSameNameAsEnclosingClassRule.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/errorprone/MethodWithSameNameAsEnclosingClassRule.java new file mode 100644 index 00000000000..835b8575c0f --- /dev/null +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/errorprone/MethodWithSameNameAsEnclosingClassRule.java @@ -0,0 +1,38 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.apex.rule.errorprone; + +import java.util.List; + +import net.sourceforge.pmd.lang.apex.ast.ASTMethod; +import net.sourceforge.pmd.lang.apex.ast.ASTUserClass; +import net.sourceforge.pmd.lang.apex.rule.AbstractApexRule; + +public class MethodWithSameNameAsEnclosingClassRule extends AbstractApexRule { + + public MethodWithSameNameAsEnclosingClassRule() { + setProperty(CODECLIMATE_CATEGORIES, "Style"); + // Note: x10 as Apex has not automatic refactoring + setProperty(CODECLIMATE_REMEDIATION_MULTIPLIER, 50); + setProperty(CODECLIMATE_BLOCK_HIGHLIGHTING, false); + } + + @Override + public Object visit(ASTUserClass node, Object data) { + String className = node.getImage(); + + List<ASTMethod> methods = node.findDescendantsOfType(ASTMethod.class); + + for (ASTMethod m : methods) { + String methodName = m.getImage(); + + if (!m.getNode().getMethodInfo().isConstructor() && methodName.equalsIgnoreCase(className)) { + addViolation(data, m); + } + } + + return super.visit(node, data); + } +} diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/performance/AvoidSoslInLoopsRule.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/performance/AvoidSoslInLoopsRule.java new file mode 100644 index 00000000000..3d16b1871e3 --- /dev/null +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/performance/AvoidSoslInLoopsRule.java @@ -0,0 +1,55 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.apex.rule.performance; + +import net.sourceforge.pmd.lang.apex.ast.ASTDoLoopStatement; +import net.sourceforge.pmd.lang.apex.ast.ASTForEachStatement; +import net.sourceforge.pmd.lang.apex.ast.ASTForLoopStatement; +import net.sourceforge.pmd.lang.apex.ast.ASTReturnStatement; +import net.sourceforge.pmd.lang.apex.ast.ASTSoslExpression; +import net.sourceforge.pmd.lang.apex.ast.ASTWhileLoopStatement; +import net.sourceforge.pmd.lang.apex.rule.AbstractApexRule; +import net.sourceforge.pmd.lang.ast.Node; + +public class AvoidSoslInLoopsRule extends AbstractApexRule { + + public AvoidSoslInLoopsRule() { + setProperty(CODECLIMATE_CATEGORIES, "Performance"); + // Note: Often more complicated as just moving the SOSL a few lines. + // Involves Maps... + setProperty(CODECLIMATE_REMEDIATION_MULTIPLIER, 150); + setProperty(CODECLIMATE_BLOCK_HIGHLIGHTING, false); + } + + @Override + public Object visit(ASTSoslExpression node, Object data) { + if (insideLoop(node) && parentNotReturn(node) && parentNotForEach(node)) { + addViolation(data, node); + } + return data; + } + + private boolean parentNotReturn(ASTSoslExpression node) { + return !(node.jjtGetParent() instanceof ASTReturnStatement); + } + + private boolean parentNotForEach(ASTSoslExpression node) { + return !(node.jjtGetParent() instanceof ASTForEachStatement); + } + + private boolean insideLoop(ASTSoslExpression node) { + Node n = node.jjtGetParent(); + + while (n != null) { + if (n instanceof ASTDoLoopStatement || n instanceof ASTWhileLoopStatement + || n instanceof ASTForLoopStatement || n instanceof ASTForEachStatement) { + return true; + } + n = n.jjtGetParent(); + } + + return false; + } +} diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/security/ApexCRUDViolationRule.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/security/ApexCRUDViolationRule.java index 0cc1d463410..7e208cd8cf0 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/security/ApexCRUDViolationRule.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/security/ApexCRUDViolationRule.java @@ -4,14 +4,14 @@ package net.sourceforge.pmd.lang.apex.rule.security; -import java.lang.reflect.Field; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; -import java.util.Optional; +import java.util.Locale; +import java.util.Map; import java.util.Set; import java.util.WeakHashMap; import java.util.regex.Matcher; @@ -44,10 +44,11 @@ import net.sourceforge.pmd.lang.apex.rule.AbstractApexRule; import net.sourceforge.pmd.lang.ast.Node; -import apex.jorje.data.ast.Identifier; +import apex.jorje.data.Identifier; import apex.jorje.data.ast.TypeRef; -import apex.jorje.data.ast.TypeRef.ArrayTypeRef; -import apex.jorje.data.ast.TypeRef.ClassTypeRef; +import apex.jorje.data.ast.TypeRefs.ArrayTypeRef; +import apex.jorje.data.ast.TypeRefs.ClassTypeRef; +import com.google.common.base.Objects; import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.ListMultimap; @@ -62,10 +63,11 @@ public class ApexCRUDViolationRule extends AbstractApexRule { private static final Pattern SELECT_FROM_PATTERN = Pattern.compile("[\\S|\\s]+?FROM[\\s]+?(\\w+)", Pattern.CASE_INSENSITIVE); - private final HashMap<String, String> varToTypeMapping = new HashMap<>(); + private final Map<String, String> varToTypeMapping = new HashMap<>(); private final ListMultimap<String, String> typeToDMLOperationMapping = ArrayListMultimap.create(); - private final HashMap<String, String> checkedTypeToDMLOperationViaESAPI = new HashMap<>(); - private final WeakHashMap<String, ASTMethod> classMethods = new WeakHashMap<>(); + private final Map<String, String> checkedTypeToDMLOperationViaESAPI = new HashMap<>(); + private final Map<String, ASTMethod> classMethods = new WeakHashMap<>(); + private String className; private static final String IS_CREATEABLE = "isCreateable"; private static final String IS_DELETABLE = "isDeletable"; @@ -100,6 +102,8 @@ public Object visit(ASTUserClass node, Object data) { return data; // stops all the rules } + className = node.getImage(); + for (ASTMethod n : node.findDescendantsOfType(ASTMethod.class)) { StringBuilder sb = new StringBuilder().append(n.getNode().getDefiningType().getApexName()).append(":") .append(n.getNode().getMethodInfo().getCanonicalName()).append(":") @@ -107,8 +111,7 @@ public Object visit(ASTUserClass node, Object data) { classMethods.put(sb.toString(), n); } - node.childrenAccept(this, data); - return data; + return super.visit(node, data); } @Override @@ -176,42 +179,28 @@ public Object visit(final ASTVariableDeclaration node, Object data) { public Object visit(final ASTFieldDeclaration node, Object data) { ASTFieldDeclarationStatements field = node.getFirstParentOfType(ASTFieldDeclarationStatements.class); if (field != null) { - try { - TypeRef a = field.getNode().getTypeName(); - Field classNameField = a.getClass().getDeclaredField("className"); - Field typeArgsField = a.getClass().getDeclaredField("typeArguments"); - classNameField.setAccessible(true); - typeArgsField.setAccessible(true); - - if (classNameField.get(a) instanceof ArrayList<?>) { - @SuppressWarnings("unchecked") - ArrayList<Identifier> innerField = (ArrayList<Identifier>) classNameField.get(a); - if (!innerField.isEmpty()) { - StringBuffer sb = new StringBuffer(); - for (Identifier id : innerField) { - sb.append(id.value).append("."); - } - sb.deleteCharAt(sb.length() - 1); - - switch (sb.toString().toLowerCase()) { - case "list": - case "map": - if (typeArgsField.get(a) instanceof Optional<?>) { - addParametersToMapping(node, a, typeArgsField); - } - break; - default: - varToTypeMapping.put(Helper.getFQVariableName(node), getSimpleType(sb.toString())); - break; - } + TypeRef a = field.getNode().getTypeName(); + List<Identifier> names = a.getNames(); + List<TypeRef> typeArgs = a.getTypeArguments(); + + if (!names.isEmpty()) { + StringBuffer sb = new StringBuffer(); + for (Identifier id : names) { + sb.append(id.getValue()).append("."); + } + sb.deleteCharAt(sb.length() - 1); - } + switch (sb.toString().toLowerCase(Locale.ROOT)) { + case "list": + case "map": + addParametersToMapping(node, typeArgs); + break; + default: + varToTypeMapping.put(Helper.getFQVariableName(node), getSimpleType(sb.toString())); + break; } - } catch (NoSuchFieldException | IllegalArgumentException | IllegalAccessException | SecurityException e) { - e.printStackTrace(); } - } final ASTSoqlExpression soql = node.getFirstChildOfType(ASTSoqlExpression.class); if (soql != null) { @@ -222,31 +211,25 @@ public Object visit(final ASTFieldDeclaration node, Object data) { } - private void addParametersToMapping(final ASTFieldDeclaration node, TypeRef a, Field typeArgsField) - throws IllegalAccessException { - Optional<?> optionalContainer = (Optional<?>) typeArgsField.get(a); - if (optionalContainer.isPresent()) { - ArrayList<?> inner = (ArrayList<?>) optionalContainer.get(); - for (int i = 0; i < inner.size(); i++) { - if (inner.get(i) instanceof ClassTypeRef) { - innerAddParametrizedClassToMapping(node, (ClassTypeRef) inner.get(i)); - } - if (inner.get(i) instanceof ArrayTypeRef) { - ArrayTypeRef atr = (ArrayTypeRef) inner.get(i); - if (atr.heldType instanceof ClassTypeRef) { - innerAddParametrizedClassToMapping(node, (ClassTypeRef) atr.heldType); - } + private void addParametersToMapping(final ASTFieldDeclaration node, List<TypeRef> typeArgs) { + for (int i = 0; i < typeArgs.size(); i++) { + if (typeArgs.get(i) instanceof ClassTypeRef) { + innerAddParametrizedClassToMapping(node, (ClassTypeRef) typeArgs.get(i)); + } + if (typeArgs.get(i) instanceof ArrayTypeRef) { + ArrayTypeRef atr = (ArrayTypeRef) typeArgs.get(i); + if (atr.getHeldType() instanceof ClassTypeRef) { + innerAddParametrizedClassToMapping(node, (ClassTypeRef) atr.getHeldType()); } - } } } private void innerAddParametrizedClassToMapping(final ASTFieldDeclaration node, final ClassTypeRef innerClassRef) { - List<Identifier> ids = innerClassRef.className; + List<Identifier> ids = innerClassRef.getNames(); StringBuffer argType = new StringBuffer(); for (Identifier id : ids) { - argType.append(id.value).append("."); + argType.append(id.getValue()).append("."); } argType.deleteCharAt(argType.length() - 1); addVariableToMapping(Helper.getFQVariableName(node), argType.toString()); @@ -263,12 +246,13 @@ public Object visit(final ASTReturnStatement node, Object data) { } private void addVariableToMapping(final String variableName, final String type) { - switch (type.toLowerCase()) { + switch (type.toLowerCase(Locale.ROOT)) { case "list": case "map": - return; + break; default: varToTypeMapping.put(variableName, getSimpleType(type)); + break; } } @@ -303,7 +287,7 @@ private void collectCRUDMethodLevelChecks(final ASTMethodCallExpression node) { return; } - List<Identifier> a = ref.getNode().getJadtIdentifiers(); + List<Identifier> a = ref.getNode().getNames(); if (!a.isEmpty()) { extractObjectAndFields(a, method, node.getNode().getDefiningType().getApexName()); } else { @@ -342,8 +326,8 @@ private void collectCRUDMethodLevelChecks(final ASTMethodCallExpression node) { private boolean isLastMethodName(final ASTMethodCallExpression methodNode, final String className, final String methodName) { final ASTReferenceExpression reference = methodNode.getFirstChildOfType(ASTReferenceExpression.class); - if (reference.getNode().getJadtIdentifiers().size() > 0) { - if (reference.getNode().getJadtIdentifiers().get(reference.getNode().getJadtIdentifiers().size() - 1).value + if (reference != null && reference.getNode().getNames().size() > 0) { + if (reference.getNode().getNames().get(reference.getNode().getNames().size() - 1).getValue() .equalsIgnoreCase(className) && Helper.isMethodName(methodNode, methodName)) { return true; } @@ -354,16 +338,16 @@ private boolean isLastMethodName(final ASTMethodCallExpression methodNode, final private String getType(final ASTMethodCallExpression methodNode) { final ASTReferenceExpression reference = methodNode.getFirstChildOfType(ASTReferenceExpression.class); - if (reference.getNode().getJadtIdentifiers().size() > 0) { + if (reference.getNode().getNames().size() > 0) { return new StringBuilder().append(reference.getNode().getDefiningType().getApexName()).append(":") - .append(reference.getNode().getJadtIdentifiers().get(0).value).toString(); + .append(reference.getNode().getNames().get(0).getValue()).toString(); } return ""; } private void extractObjectAndFields(final List<Identifier> listIdentifiers, final String method, final String definingType) { - final List<String> strings = listIdentifiers.stream().map(id -> id.value).collect(Collectors.toList()); + final List<String> strings = listIdentifiers.stream().map(id -> id.getValue()).collect(Collectors.toList()); int flsIndex = Collections.lastIndexOfSubList(strings, Arrays.asList(RESERVED_KEYS_FLS)); if (flsIndex != -1) { @@ -375,7 +359,7 @@ private void extractObjectAndFields(final List<Identifier> listIdentifiers, fina } private void checkForCRUD(final AbstractApexNode<?> node, final Object data, final String crudMethod) { - final HashSet<ASTMethodCallExpression> prevCalls = getPreviousMethodCalls(node); + final Set<ASTMethodCallExpression> prevCalls = getPreviousMethodCalls(node); for (ASTMethodCallExpression prevCall : prevCalls) { collectCRUDMethodLevelChecks(prevCall); } @@ -383,8 +367,8 @@ private void checkForCRUD(final AbstractApexNode<?> node, final Object data, fin final ASTMethod wrappingMethod = node.getFirstParentOfType(ASTMethod.class); final ASTUserClass wrappingClass = node.getFirstParentOfType(ASTUserClass.class); - if ((wrappingClass != null && Helper.isTestMethodOrClass(wrappingClass)) - || (wrappingMethod != null && Helper.isTestMethodOrClass(wrappingMethod))) { + if (wrappingClass != null && Helper.isTestMethodOrClass(wrappingClass) + || wrappingMethod != null && Helper.isTestMethodOrClass(wrappingMethod)) { return; } @@ -406,14 +390,14 @@ private void checkForCRUD(final AbstractApexNode<?> node, final Object data, fin } } - private HashSet<ASTMethodCallExpression> getPreviousMethodCalls(final AbstractApexNode<?> self) { - final HashSet<ASTMethodCallExpression> innerMethodCalls = new HashSet<>(); + private Set<ASTMethodCallExpression> getPreviousMethodCalls(final AbstractApexNode<?> self) { + final Set<ASTMethodCallExpression> innerMethodCalls = new HashSet<>(); final ASTMethod outerMethod = self.getFirstParentOfType(ASTMethod.class); if (outerMethod != null) { final ASTBlockStatement blockStatement = outerMethod.getFirstChildOfType(ASTBlockStatement.class); recursivelyEvaluateCRUDMethodCalls(self, innerMethodCalls, blockStatement); - final List<ASTMethod> constructorMethods = findConstructorlMethods(self); + final List<ASTMethod> constructorMethods = findConstructorlMethods(); for (ASTMethod method : constructorMethods) { innerMethodCalls.addAll(method.findDescendantsOfType(ASTMethodCallExpression.class)); } @@ -426,7 +410,7 @@ private HashSet<ASTMethodCallExpression> getPreviousMethodCalls(final AbstractAp } private void recursivelyEvaluateCRUDMethodCalls(final AbstractApexNode<?> self, - final HashSet<ASTMethodCallExpression> innerMethodCalls, final ASTBlockStatement blockStatement) { + final Set<ASTMethodCallExpression> innerMethodCalls, final ASTBlockStatement blockStatement) { if (blockStatement != null) { int numberOfStatements = blockStatement.jjtGetNumChildren(); for (int i = 0; i < numberOfStatements; i++) { @@ -440,7 +424,7 @@ private void recursivelyEvaluateCRUDMethodCalls(final AbstractApexNode<?> self, } AbstractApexNode<?> match = n.getFirstDescendantOfType(self.getClass()); - if (match == self) { + if (Objects.equal(match, self)) { break; } ASTMethodCallExpression methodCall = n.getFirstDescendantOfType(ASTMethodCallExpression.class); @@ -453,9 +437,9 @@ private void recursivelyEvaluateCRUDMethodCalls(final AbstractApexNode<?> self, } private void mapCallToMethodDecl(final AbstractApexNode<?> self, - final HashSet<ASTMethodCallExpression> innerMethodCalls, final List<ASTMethodCallExpression> nodes) { + final Set<ASTMethodCallExpression> innerMethodCalls, final List<ASTMethodCallExpression> nodes) { for (ASTMethodCallExpression node : nodes) { - if (node == self) { + if (Objects.equal(node, self)) { break; } @@ -467,10 +451,11 @@ private void mapCallToMethodDecl(final AbstractApexNode<?> self, } } - private List<ASTMethod> findConstructorlMethods(final AbstractApexNode<?> node) { + private List<ASTMethod> findConstructorlMethods() { final ArrayList<ASTMethod> ret = new ArrayList<>(); final Set<String> constructors = classMethods.keySet().stream() - .filter(p -> (p.contains("<init>") || p.contains("<clinit>"))).collect(Collectors.toSet()); + .filter(p -> p.contains("<init>") || p.contains("<clinit>") + || p.startsWith(className + ":" + className + ":")).collect(Collectors.toSet()); for (String c : constructors) { ret.add(classMethods.get(c)); @@ -504,10 +489,10 @@ private void extractObjectTypeFromESAPI(final ASTMethodCallExpression node, fina if (var != null) { final ASTReferenceExpression reference = var.getFirstChildOfType(ASTReferenceExpression.class); if (reference != null) { - List<Identifier> identifiers = reference.getNode().getJadtIdentifiers(); + List<Identifier> identifiers = reference.getNode().getNames(); if (identifiers.size() == 1) { StringBuilder sb = new StringBuilder().append(node.getNode().getDefiningType().getApexName()) - .append(":").append(identifiers.get(0).value); + .append(":").append(identifiers.get(0).getValue()); checkedTypeToDMLOperationViaESAPI.put(sb.toString(), dmlOperation); } @@ -547,7 +532,7 @@ private void checkForAccessibility(final ASTSoqlExpression node, Object data) { final boolean isCount = node.getNode().getCanonicalQuery().startsWith("SELECT COUNT()"); final Set<String> typesFromSOQL = getTypesFromSOQLQuery(node); - final HashSet<ASTMethodCallExpression> prevCalls = getPreviousMethodCalls(node); + final Set<ASTMethodCallExpression> prevCalls = getPreviousMethodCalls(node); for (ASTMethodCallExpression prevCall : prevCalls) { collectCRUDMethodLevelChecks(prevCall); } @@ -558,8 +543,9 @@ private void checkForAccessibility(final ASTSoqlExpression node, Object data) { final ASTMethod wrappingMethod = node.getFirstParentOfType(ASTMethod.class); final ASTUserClass wrappingClass = node.getFirstParentOfType(ASTUserClass.class); - if (isCount || (wrappingClass != null && Helper.isTestMethodOrClass(wrappingClass)) - || (wrappingMethod != null && Helper.isTestMethodOrClass(wrappingMethod))) { + if (isCount + || wrappingClass != null && Helper.isTestMethodOrClass(wrappingClass) + || wrappingMethod != null && Helper.isTestMethodOrClass(wrappingMethod)) { return; } @@ -646,6 +632,6 @@ private boolean isMethodAGetter(final ASTMethod method) { .matcher(method.getNode().getMethodInfo().getEmitSignature().getReturnType().getApexName()).matches(); final boolean noParams = method.findChildrenOfType(ASTParameter.class).isEmpty(); - return (startsWithGet && noParams && !voidOrString); + return startsWithGet && noParams && !voidOrString; } } diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/security/ApexCSRFRule.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/security/ApexCSRFRule.java index d4aff9c94d8..0d24dc782c3 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/security/ApexCSRFRule.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/security/ApexCSRFRule.java @@ -6,7 +6,6 @@ import net.sourceforge.pmd.lang.apex.ast.ASTMethod; import net.sourceforge.pmd.lang.apex.ast.ASTUserClass; -import net.sourceforge.pmd.lang.apex.ast.ApexNode; import net.sourceforge.pmd.lang.apex.rule.AbstractApexRule; /** @@ -31,7 +30,7 @@ public Object visit(ASTUserClass node, Object data) { return data; // stops all the rules } - return visit((ApexNode<?>) node, data); + return super.visit(node, data); } @Override diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/security/ApexDangerousMethodsRule.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/security/ApexDangerousMethodsRule.java index c3726a8b893..91d9a6dfe4a 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/security/ApexDangerousMethodsRule.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/security/ApexDangerousMethodsRule.java @@ -6,6 +6,7 @@ import java.util.HashSet; import java.util.List; +import java.util.Set; import java.util.regex.Pattern; import net.sourceforge.pmd.lang.apex.ast.ASTField; @@ -19,8 +20,8 @@ * Flags dangerous method calls, e.g. FinancialForce * Configuration.disableTriggerCRUDSecurity() or System.debug with sensitive * input - * - * + * + * * @author sergey.gorbaty * */ @@ -35,7 +36,7 @@ public class ApexDangerousMethodsRule extends AbstractApexRule { private static final String SYSTEM = "System"; private static final String DEBUG = "debug"; - private final HashSet<String> whiteListedVariables = new HashSet<>(); + private final Set<String> whiteListedVariables = new HashSet<>(); public ApexDangerousMethodsRule() { super.addRuleChainVisit(ASTUserClass.class); @@ -45,6 +46,7 @@ public ApexDangerousMethodsRule() { } + @Override public Object visit(ASTUserClass node, Object data) { if (Helper.isTestMethodOrClass(node)) { return data; @@ -89,7 +91,7 @@ private void collectBenignVariables(ASTUserClass node) { private void validateParameters(ASTMethodCallExpression methodCall, Object data) { List<ASTVariableExpression> variables = methodCall.findDescendantsOfType(ASTVariableExpression.class); for (ASTVariableExpression var : variables) { - if (REGEXP.matcher(var.getNode().getIdentifier().value).matches()) { + if (REGEXP.matcher(var.getNode().getIdentifier().getValue()).matches()) { if (!whiteListedVariables.contains(Helper.getFQVariableName(var))) { addViolation(data, methodCall); } diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/security/ApexInsecureEndpointRule.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/security/ApexInsecureEndpointRule.java index 847c6ce4cd9..aff2755b474 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/security/ApexInsecureEndpointRule.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/security/ApexInsecureEndpointRule.java @@ -39,23 +39,23 @@ public ApexInsecureEndpointRule() { @Override public Object visit(ASTAssignmentExpression node, Object data) { - findInsecureEndpoints(node, data); + findInsecureEndpoints(node); return data; } @Override public Object visit(ASTVariableDeclaration node, Object data) { - findInsecureEndpoints(node, data); + findInsecureEndpoints(node); return data; } @Override public Object visit(ASTFieldDeclaration node, Object data) { - findInsecureEndpoints(node, data); + findInsecureEndpoints(node); return data; } - private void findInsecureEndpoints(AbstractApexNode<?> node, Object data) { + private void findInsecureEndpoints(AbstractApexNode<?> node) { ASTVariableExpression variableNode = node.getFirstChildOfType(ASTVariableExpression.class); findInnerInsecureEndpoints(node, variableNode); diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/security/ApexOpenRedirectRule.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/security/ApexOpenRedirectRule.java index 7a3e25e35bc..3485828004a 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/security/ApexOpenRedirectRule.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/security/ApexOpenRedirectRule.java @@ -22,8 +22,8 @@ import net.sourceforge.pmd.lang.apex.ast.ApexNode; import net.sourceforge.pmd.lang.apex.rule.AbstractApexRule; -import apex.jorje.data.ast.Identifier; -import apex.jorje.data.ast.TypeRef.ClassTypeRef; +import apex.jorje.data.Identifier; +import apex.jorje.data.ast.TypeRefs.ClassTypeRef; import apex.jorje.semantic.symbol.member.variable.StandardFieldInfo; /** @@ -121,7 +121,7 @@ private void findSafeLiterals(AbstractApexNode<?> node) { } catch (NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException e) { - // preventing exceptions from this code + throw new RuntimeException(e); } } } @@ -160,9 +160,9 @@ private void checkNewObjects(ASTNewObjectExpression node, Object data) { } ClassTypeRef classRef = (ClassTypeRef) node.getNode().getTypeRef(); - Identifier identifier = classRef.className.get(0); + Identifier identifier = classRef.getNames().get(0); - if (identifier.value.equalsIgnoreCase(PAGEREFERENCE)) { + if (identifier.getValue().equalsIgnoreCase(PAGEREFERENCE)) { getObjectValue(node, data); } } diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/security/ApexSOQLInjectionRule.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/security/ApexSOQLInjectionRule.java index 94a46f32114..ced42d6b7e7 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/security/ApexSOQLInjectionRule.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/security/ApexSOQLInjectionRule.java @@ -7,6 +7,9 @@ import java.util.HashMap; import java.util.HashSet; import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Set; import java.util.regex.Pattern; import net.sourceforge.pmd.lang.apex.ast.ASTAssignmentExpression; @@ -45,8 +48,8 @@ public class ApexSOQLInjectionRule extends AbstractApexRule { private static final String DATABASE = "Database"; private static final String QUERY = "query"; private static final Pattern SELECT_PATTERN = Pattern.compile("^select[\\s]+?.*?$", Pattern.CASE_INSENSITIVE); - private final HashSet<String> safeVariables = new HashSet<>(); - private final HashMap<String, Boolean> selectContainingVariables = new HashMap<>(); + private final Set<String> safeVariables = new HashSet<>(); + private final Map<String, Boolean> selectContainingVariables = new HashMap<>(); public ApexSOQLInjectionRule() { setProperty(CODECLIMATE_CATEGORIES, "Security"); @@ -106,7 +109,7 @@ public Object visit(ASTUserClass node, Object data) { private void findSafeVariablesInSignature(ASTMethod m) { List<Parameter> parameters = m.getNode().getMethodInfo().getParameters(); for (Parameter p : parameters) { - switch (p.getType().getApexName().toLowerCase()) { + switch (p.getType().getApexName().toLowerCase(Locale.ROOT)) { case ID: case INTEGER: case BOOLEAN: @@ -157,7 +160,7 @@ private void findSanitizedVariables(AbstractApexNode<?> node) { if (node instanceof ASTVariableDeclaration) { VariableDeclaration o = (VariableDeclaration) node.getNode(); - switch (o.getLocalInfo().getType().getApexName().toLowerCase()) { + switch (o.getLocalInfo().getType().getApexName().toLowerCase(Locale.ROOT)) { case INTEGER: case ID: case BOOLEAN: diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/security/ApexSuggestUsingNamedCredRule.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/security/ApexSuggestUsingNamedCredRule.java index 234a023d168..ed17d500025 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/security/ApexSuggestUsingNamedCredRule.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/security/ApexSuggestUsingNamedCredRule.java @@ -67,7 +67,7 @@ public Object visit(ASTUserClass node, Object data) { private void findFieldLiterals(final ASTField fDecl) { Object f = fDecl.getNode().getFieldInfo().getValue(); - if (f != null && f instanceof String) { + if (f instanceof String) { final String fieldValue = (String) f; if (AUTHORIZATION.equalsIgnoreCase(fieldValue)) { listOfAuthorizationVariables.add(Helper.getFQVariableName(fDecl)); diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/security/ApexXSSFromURLParamRule.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/security/ApexXSSFromURLParamRule.java index 73db6e61112..1a2e588442a 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/security/ApexXSSFromURLParamRule.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/security/ApexXSSFromURLParamRule.java @@ -18,7 +18,6 @@ import net.sourceforge.pmd.lang.apex.ast.ASTVariableDeclaration; import net.sourceforge.pmd.lang.apex.ast.ASTVariableExpression; import net.sourceforge.pmd.lang.apex.ast.AbstractApexNode; -import net.sourceforge.pmd.lang.apex.ast.ApexNode; import net.sourceforge.pmd.lang.apex.rule.AbstractApexRule; /** @@ -61,7 +60,7 @@ public Object visit(ASTUserClass node, Object data) { return data; // stops all the rules } - return visit((ApexNode<?>) node, data); + return super.visit(node, data); } @Override diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/security/Helper.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/security/Helper.java index 9a266138f5a..4aae2678930 100644 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/security/Helper.java +++ b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/security/Helper.java @@ -4,9 +4,9 @@ package net.sourceforge.pmd.lang.apex.rule.security; -import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.Locale; import net.sourceforge.pmd.lang.apex.ast.ASTDmlDeleteStatement; import net.sourceforge.pmd.lang.apex.ast.ASTDmlInsertStatement; @@ -27,9 +27,8 @@ import net.sourceforge.pmd.lang.apex.ast.ASTVariableExpression; import net.sourceforge.pmd.lang.apex.ast.ApexNode; -import apex.jorje.data.ast.Identifier; +import apex.jorje.data.Identifier; import apex.jorje.data.ast.TypeRef; -import apex.jorje.data.ast.TypeRef.ClassTypeRef; import apex.jorje.semantic.ast.expression.MethodCallExpression; import apex.jorje.semantic.ast.expression.NewKeyValueObjectExpression; import apex.jorje.semantic.ast.expression.VariableExpression; @@ -45,7 +44,7 @@ * */ public final class Helper { - protected static final String ANY_METHOD = "*"; + static final String ANY_METHOD = "*"; private Helper() { throw new AssertionError("Can't instantiate helper classes"); @@ -60,22 +59,14 @@ static boolean isTestMethodOrClass(final ApexNode<?> node) { } final String className = node.getNode().getDefiningType().getApexName(); - if (className.endsWith("Test")) { - return true; - } - - return false; + return className.endsWith("Test"); } static boolean foundAnySOQLorSOSL(final ApexNode<?> node) { final List<ASTSoqlExpression> dmlSoqlExpression = node.findDescendantsOfType(ASTSoqlExpression.class); final List<ASTSoslExpression> dmlSoslExpression = node.findDescendantsOfType(ASTSoslExpression.class); - if (dmlSoqlExpression.isEmpty() && dmlSoslExpression.isEmpty()) { - return false; - } - - return true; + return !dmlSoqlExpression.isEmpty() || !dmlSoslExpression.isEmpty(); } /** @@ -95,27 +86,17 @@ static boolean foundAnyDML(final ApexNode<?> node) { final List<ASTDmlInsertStatement> dmlInsertStatement = node.findDescendantsOfType(ASTDmlInsertStatement.class); final List<ASTDmlDeleteStatement> dmlDeleteStatement = node.findDescendantsOfType(ASTDmlDeleteStatement.class); - if (dmlUpsertStatement.isEmpty() && dmlUpdateStatement.isEmpty() && dmlUndeleteStatement.isEmpty() - && dmlMergeStatement.isEmpty() && dmlInsertStatement.isEmpty() && dmlDeleteStatement.isEmpty()) { - return false; - } - - return true; + return !dmlUpsertStatement.isEmpty() || !dmlUpdateStatement.isEmpty() || !dmlUndeleteStatement.isEmpty() + || !dmlMergeStatement.isEmpty() || !dmlInsertStatement.isEmpty() || !dmlDeleteStatement.isEmpty(); } static boolean isMethodName(final ASTMethodCallExpression methodNode, final String className, final String methodName) { final ASTReferenceExpression reference = methodNode.getFirstChildOfType(ASTReferenceExpression.class); - if (reference.getNode().getJadtIdentifiers().size() == 1) { - if (reference.getNode().getJadtIdentifiers().get(0).value.equalsIgnoreCase(className)) { - if (methodName.equals(ANY_METHOD) || isMethodName(methodNode, methodName)) { - return true; - } - } - } - - return false; + return reference != null && reference.getNode().getNames().size() == 1 + && reference.getNode().getNames().get(0).getValue().equalsIgnoreCase(className) + && (methodName.equals(ANY_METHOD) || isMethodName(methodNode, methodName)); } static boolean isMethodName(final ASTMethodCallExpression m, final String methodName) { @@ -152,14 +133,14 @@ static String getFQVariableName(final ASTVariableExpression variable) { final ASTReferenceExpression ref = variable.getFirstChildOfType(ASTReferenceExpression.class); String objectName = ""; if (ref != null) { - if (ref.getNode().getJadtIdentifiers().size() == 1) { - objectName = ref.getNode().getJadtIdentifiers().get(0).value + "."; + if (ref.getNode().getNames().size() == 1) { + objectName = ref.getNode().getNames().get(0).getValue() + "."; } } VariableExpression n = variable.getNode(); StringBuilder sb = new StringBuilder().append(n.getDefiningType().getApexName()).append(":").append(objectName) - .append(n.getIdentifier().value); + .append(n.getIdentifier().getValue()); return sb.toString(); } @@ -192,9 +173,10 @@ static String getFQVariableName(final ASTFieldDeclaration variable) { java.lang.reflect.Field f = n.getClass().getDeclaredField("name"); f.setAccessible(true); Identifier nameField = (Identifier) f.get(n); - name = nameField.value; + name = nameField.getValue(); } catch (NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException e) { + throw new RuntimeException(e); } StringBuilder sb = new StringBuilder().append(n.getDefiningType().getApexName()).append(":").append(name); @@ -203,15 +185,8 @@ static String getFQVariableName(final ASTFieldDeclaration variable) { static String getFQVariableName(final ASTNewKeyValueObjectExpression variable) { NewKeyValueObjectExpression n = variable.getNode(); - String objType = ""; - try { - // no other way to get this field, Apex Jorje does not expose it - java.lang.reflect.Field f = n.getClass().getDeclaredField("typeRef"); - f.setAccessible(true); - ClassTypeRef hiddenField = (ClassTypeRef) f.get(n); - objType = hiddenField.className.get(0).value; - } catch (NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException e) { - } + TypeRef typeRef = n.getTypeRef(); + String objType = typeRef.getNames().get(0).getValue(); StringBuilder sb = new StringBuilder().append(n.getDefiningType().getApexName()).append(":").append(objType); return sb.toString(); @@ -219,19 +194,11 @@ static String getFQVariableName(final ASTNewKeyValueObjectExpression variable) { static boolean isSystemLevelClass(ASTUserClass node) { List<TypeRef> interfaces = node.getNode().getDefiningType().getCodeUnitDetails().getInterfaceTypeRefs(); - for (TypeRef intObject : interfaces) { - try { - java.lang.reflect.Field f = intObject.getClass().getDeclaredField("className"); - f.setAccessible(true); - @SuppressWarnings("unchecked") - ArrayList<Identifier> ids = (ArrayList<Identifier>) f.get(intObject); - if (isWhitelisted(ids)) { - return true; - } - } catch (NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException e) { + for (TypeRef intObject : interfaces) { + if (isWhitelisted(intObject.getNames())) { + return true; } - } return false; @@ -241,14 +208,14 @@ private static boolean isWhitelisted(List<Identifier> ids) { StringBuffer sb = new StringBuffer(); for (int i = 0; i < ids.size(); i++) { - sb.append(ids.get(i).value); + sb.append(ids.get(i).getValue()); - if (i != (ids.size() - 1)) { + if (i != ids.size() - 1) { sb.append("."); } } - switch (sb.toString().toLowerCase()) { + switch (sb.toString().toLowerCase(Locale.ROOT)) { case "queueable": case "database.batchable": case "installhandler": @@ -260,8 +227,8 @@ private static boolean isWhitelisted(List<Identifier> ids) { } public static String getFQVariableName(Parameter p) { - StringBuffer sb = new StringBuffer(); - sb.append(p.getDefiningType()).append(":").append(p.getName().value); + StringBuffer sb = new StringBuffer(); + sb.append(p.getDefiningType()).append(":").append(p.getName().getValue()); return sb.toString(); } diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/style/AvoidGlobalModifierRule.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/style/AvoidGlobalModifierRule.java deleted file mode 100644 index 46045bd4979..00000000000 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/style/AvoidGlobalModifierRule.java +++ /dev/null @@ -1,42 +0,0 @@ -/** - * BSD-style license; for more info see http://pmd.sourceforge.net/license.html - */ - -package net.sourceforge.pmd.lang.apex.rule.style; - -import static apex.jorje.semantic.symbol.type.ModifierTypeInfos.GLOBAL; - -import net.sourceforge.pmd.lang.apex.ast.ASTModifierNode; -import net.sourceforge.pmd.lang.apex.ast.ASTUserClass; -import net.sourceforge.pmd.lang.apex.ast.ASTUserInterface; -import net.sourceforge.pmd.lang.apex.ast.ApexNode; -import net.sourceforge.pmd.lang.apex.rule.AbstractApexRule; - -public class AvoidGlobalModifierRule extends AbstractApexRule { - - public AvoidGlobalModifierRule() { - setProperty(CODECLIMATE_CATEGORIES, "Style"); - setProperty(CODECLIMATE_REMEDIATION_MULTIPLIER, 100); - setProperty(CODECLIMATE_BLOCK_HIGHLIGHTING, false); - } - - @Override - public Object visit(ASTUserClass node, Object data) { - return checkForGlobal(node, data); - } - - @Override - public Object visit(ASTUserInterface node, Object data) { - return checkForGlobal(node, data); - } - - private Object checkForGlobal(ApexNode<?> node, Object data) { - ASTModifierNode modifierNode = node.getFirstChildOfType(ASTModifierNode.class); - - if (modifierNode != null && modifierNode.getNode().getModifiers().has(GLOBAL)) { - addViolation(data, node); - } - - return data; - } -} diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/style/ClassNamingConventionsRule.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/style/ClassNamingConventionsRule.java deleted file mode 100644 index 8a9a1dfaa36..00000000000 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/style/ClassNamingConventionsRule.java +++ /dev/null @@ -1,33 +0,0 @@ -/** - * BSD-style license; for more info see http://pmd.sourceforge.net/license.html - */ - -package net.sourceforge.pmd.lang.apex.rule.style; - -import net.sourceforge.pmd.lang.apex.ast.ASTUserClass; -import net.sourceforge.pmd.lang.apex.ast.ASTUserInterface; -import net.sourceforge.pmd.lang.apex.rule.AbstractApexRule; - -public class ClassNamingConventionsRule extends AbstractApexRule { - - public ClassNamingConventionsRule() { - setProperty(CODECLIMATE_CATEGORIES, "Style"); - // Note: x10 as Apex has not automatic refactoring - setProperty(CODECLIMATE_REMEDIATION_MULTIPLIER, 5); - setProperty(CODECLIMATE_BLOCK_HIGHLIGHTING, false); - } - - public Object visit(ASTUserClass node, Object data) { - if (Character.isLowerCase(node.getImage().charAt(0))) { - addViolation(data, node); - } - return data; - } - - public Object visit(ASTUserInterface node, Object data) { - if (Character.isLowerCase(node.getImage().charAt(0))) { - addViolation(data, node); - } - return data; - } -} diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/style/MethodNamingConventionsRule.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/style/MethodNamingConventionsRule.java deleted file mode 100644 index 1d251a6fdf7..00000000000 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/style/MethodNamingConventionsRule.java +++ /dev/null @@ -1,54 +0,0 @@ -/** - * BSD-style license; for more info see http://pmd.sourceforge.net/license.html - */ - -package net.sourceforge.pmd.lang.apex.rule.style; - -import static apex.jorje.semantic.symbol.type.ModifierTypeInfos.OVERRIDE; - -import net.sourceforge.pmd.lang.apex.ast.ASTMethod; -import net.sourceforge.pmd.lang.apex.ast.ASTProperty; -import net.sourceforge.pmd.lang.apex.ast.ASTUserClass; -import net.sourceforge.pmd.lang.apex.rule.AbstractApexRule; - -public class MethodNamingConventionsRule extends AbstractApexRule { - - public MethodNamingConventionsRule() { - setProperty(CODECLIMATE_CATEGORIES, "Style"); - // Note: x10 as Apex has not automatic refactoring - setProperty(CODECLIMATE_REMEDIATION_MULTIPLIER, 1); - setProperty(CODECLIMATE_BLOCK_HIGHLIGHTING, false); - } - - public Object visit(ASTUserClass node, Object data) { - return super.visit(node, data); - } - - public Object visit(ASTMethod node, Object data) { - if (isOverriddenMethod(node) || isPropertyAccessor(node) || isConstructor(node)) { - return data; - } - - String methodName = node.getImage(); - - if (Character.isUpperCase(methodName.charAt(0))) { - addViolationWithMessage(data, node, "Method names should not start with capital letters"); - } - if (methodName.indexOf('_') >= 0) { - addViolationWithMessage(data, node, "Method names should not contain underscores"); - } - return data; - } - - private boolean isOverriddenMethod(ASTMethod node) { - return node.getNode().getModifiers().has(OVERRIDE); - } - - private boolean isPropertyAccessor(ASTMethod node) { - return (node.getParentsOfType(ASTProperty.class).size() > 0); - } - - private boolean isConstructor(ASTMethod node) { - return (node.getNode().getMethodInfo().isConstructor()); - } -} diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/style/MethodWithSameNameAsEnclosingClassRule.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/style/MethodWithSameNameAsEnclosingClassRule.java deleted file mode 100644 index 1cbc197aacb..00000000000 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/style/MethodWithSameNameAsEnclosingClassRule.java +++ /dev/null @@ -1,38 +0,0 @@ -/** - * BSD-style license; for more info see http://pmd.sourceforge.net/license.html - */ - -package net.sourceforge.pmd.lang.apex.rule.style; - -import java.util.List; - -import net.sourceforge.pmd.lang.apex.ast.ASTMethod; -import net.sourceforge.pmd.lang.apex.ast.ASTUserClass; -import net.sourceforge.pmd.lang.apex.rule.AbstractApexRule; - -public class MethodWithSameNameAsEnclosingClassRule extends AbstractApexRule { - - public MethodWithSameNameAsEnclosingClassRule() { - setProperty(CODECLIMATE_CATEGORIES, "Style"); - // Note: x10 as Apex has not automatic refactoring - setProperty(CODECLIMATE_REMEDIATION_MULTIPLIER, 50); - setProperty(CODECLIMATE_BLOCK_HIGHLIGHTING, false); - } - - @Override - public Object visit(ASTUserClass node, Object data) { - String className = node.getImage(); - - List<ASTMethod> methods = node.findDescendantsOfType(ASTMethod.class); - - for (ASTMethod m : methods) { - String methodName = m.getImage(); - - if (!m.getNode().getMethodInfo().isConstructor() && methodName.equalsIgnoreCase(className)) { - addViolation(data, m); - } - } - - return super.visit(node, data); - } -} diff --git a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/style/VariableNamingConventionsRule.java b/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/style/VariableNamingConventionsRule.java deleted file mode 100644 index 38252a66f62..00000000000 --- a/pmd-apex/src/main/java/net/sourceforge/pmd/lang/apex/rule/style/VariableNamingConventionsRule.java +++ /dev/null @@ -1,222 +0,0 @@ -/** - * BSD-style license; for more info see http://pmd.sourceforge.net/license.html - */ - -package net.sourceforge.pmd.lang.apex.rule.style; - -import static apex.jorje.semantic.symbol.type.ModifierTypeInfos.FINAL; -import static apex.jorje.semantic.symbol.type.ModifierTypeInfos.STATIC; - -import java.util.List; - -import net.sourceforge.pmd.lang.apex.ast.ASTField; -import net.sourceforge.pmd.lang.apex.ast.ASTParameter; -import net.sourceforge.pmd.lang.apex.ast.ASTUserClass; -import net.sourceforge.pmd.lang.apex.ast.ASTUserInterface; -import net.sourceforge.pmd.lang.apex.ast.ASTVariableDeclaration; -import net.sourceforge.pmd.lang.apex.ast.ApexNode; -import net.sourceforge.pmd.lang.apex.rule.AbstractApexRule; -import net.sourceforge.pmd.properties.BooleanProperty; -import net.sourceforge.pmd.properties.PropertyDescriptor; -import net.sourceforge.pmd.properties.StringMultiProperty; - -public class VariableNamingConventionsRule extends AbstractApexRule { - - private boolean checkMembers; - private boolean checkLocals; - private boolean checkParameters; - private List<String> staticPrefixes; - private List<String> staticSuffixes; - private List<String> memberPrefixes; - private List<String> memberSuffixes; - private List<String> localPrefixes; - private List<String> localSuffixes; - private List<String> parameterPrefixes; - private List<String> parameterSuffixes; - - private static final BooleanProperty CHECK_MEMBERS_DESCRIPTOR = new BooleanProperty("checkMembers", - "Check member variables", true, 1.0f); - - private static final BooleanProperty CHECK_LOCALS_DESCRIPTOR = new BooleanProperty("checkLocals", - "Check local variables", true, 2.0f); - - private static final BooleanProperty CHECK_PARAMETERS_DESCRIPTOR = new BooleanProperty("checkParameters", - "Check constructor and method parameter variables", true, 3.0f); - - private static final StringMultiProperty STATIC_PREFIXES_DESCRIPTOR = new StringMultiProperty("staticPrefix", - "Static variable prefixes", new String[] { "" }, 4.0f, ','); - - private static final StringMultiProperty STATIC_SUFFIXES_DESCRIPTOR = new StringMultiProperty("staticSuffix", - "Static variable suffixes", new String[] { "" }, 5.0f, ','); - - private static final StringMultiProperty MEMBER_PREFIXES_DESCRIPTOR = new StringMultiProperty("memberPrefix", - "Member variable prefixes", new String[] { "" }, 6.0f, ','); - - private static final StringMultiProperty MEMBER_SUFFIXES_DESCRIPTOR = new StringMultiProperty("memberSuffix", - "Member variable suffixes", new String[] { "" }, 7.0f, ','); - - private static final StringMultiProperty LOCAL_PREFIXES_DESCRIPTOR = new StringMultiProperty("localPrefix", - "Local variable prefixes", new String[] { "" }, 8.0f, ','); - - private static final StringMultiProperty LOCAL_SUFFIXES_DESCRIPTOR = new StringMultiProperty("localSuffix", - "Local variable suffixes", new String[] { "" }, 9.0f, ','); - - private static final StringMultiProperty PARAMETER_PREFIXES_DESCRIPTOR = new StringMultiProperty("parameterPrefix", - "Method parameter variable prefixes", new String[] { "" }, 10.0f, ','); - - private static final StringMultiProperty PARAMETER_SUFFIXES_DESCRIPTOR = new StringMultiProperty("parameterSuffix", - "Method parameter variable suffixes", new String[] { "" }, 11.0f, ','); - - public VariableNamingConventionsRule() { - definePropertyDescriptor(CHECK_MEMBERS_DESCRIPTOR); - definePropertyDescriptor(CHECK_LOCALS_DESCRIPTOR); - definePropertyDescriptor(CHECK_PARAMETERS_DESCRIPTOR); - definePropertyDescriptor(STATIC_PREFIXES_DESCRIPTOR); - definePropertyDescriptor(STATIC_SUFFIXES_DESCRIPTOR); - definePropertyDescriptor(MEMBER_PREFIXES_DESCRIPTOR); - definePropertyDescriptor(MEMBER_SUFFIXES_DESCRIPTOR); - definePropertyDescriptor(LOCAL_PREFIXES_DESCRIPTOR); - definePropertyDescriptor(LOCAL_SUFFIXES_DESCRIPTOR); - definePropertyDescriptor(PARAMETER_PREFIXES_DESCRIPTOR); - definePropertyDescriptor(PARAMETER_SUFFIXES_DESCRIPTOR); - - setProperty(CODECLIMATE_CATEGORIES, "Style"); - // Note: x10 as Apex has not automatic refactoring - setProperty(CODECLIMATE_REMEDIATION_MULTIPLIER, 5); - setProperty(CODECLIMATE_BLOCK_HIGHLIGHTING, false); - } - - public Object visit(ASTUserClass node, Object data) { - init(); - return super.visit(node, data); - } - - public Object visit(ASTUserInterface node, Object data) { - init(); - return super.visit(node, data); - } - - protected void init() { - checkMembers = getProperty(CHECK_MEMBERS_DESCRIPTOR); - checkLocals = getProperty(CHECK_LOCALS_DESCRIPTOR); - checkParameters = getProperty(CHECK_PARAMETERS_DESCRIPTOR); - staticPrefixes = getProperty(STATIC_PREFIXES_DESCRIPTOR); - staticSuffixes = getProperty(STATIC_SUFFIXES_DESCRIPTOR); - memberPrefixes = getProperty(MEMBER_PREFIXES_DESCRIPTOR); - memberSuffixes = getProperty(MEMBER_SUFFIXES_DESCRIPTOR); - localPrefixes = getProperty(LOCAL_PREFIXES_DESCRIPTOR); - localSuffixes = getProperty(LOCAL_SUFFIXES_DESCRIPTOR); - parameterPrefixes = getProperty(PARAMETER_PREFIXES_DESCRIPTOR); - parameterSuffixes = getProperty(PARAMETER_SUFFIXES_DESCRIPTOR); - } - - public Object visit(ASTField node, Object data) { - if (!checkMembers) { - return data; - } - boolean isStatic = node.getNode().getFieldInfo().getModifiers().has(STATIC); - boolean isFinal = node.getNode().getFieldInfo().getModifiers().has(FINAL); - - return checkName(isStatic ? staticPrefixes : memberPrefixes, isStatic ? staticSuffixes : memberSuffixes, node, - isStatic, isFinal, data); - } - - public Object visit(ASTVariableDeclaration node, Object data) { - - if (!checkLocals) { - return data; - } - - boolean isFinal = node.getNode().getLocalInfo().getModifiers().has(FINAL); - return checkName(localPrefixes, localSuffixes, node, false, isFinal, data); - } - - public Object visit(ASTParameter node, Object data) { - if (!checkParameters) { - return data; - } - - boolean isFinal = node.getNode().getModifierInfo().has(FINAL); - return checkName(parameterPrefixes, parameterSuffixes, node, false, isFinal, data); - } - - private Object checkName(List<String> prefixes, List<String> suffixes, ApexNode<?> node, boolean isStatic, boolean isFinal, - Object data) { - - String varName = node.getImage(); - - // Skip on null (with exception classes) and serialVersionUID - if (varName == null || "serialVersionUID".equals(varName)) { - return data; - } - - // Static finals should be uppercase - if (isStatic && isFinal) { - if (!varName.equals(varName.toUpperCase())) { - addViolationWithMessage(data, node, - "Variables that are final and static should be all capitals, ''{0}'' is not all capitals.", - new Object[] { varName }); - } - return data; - } else if (!isFinal) { - String normalizedVarName = normalizeVariableName(varName, prefixes, suffixes); - - if (normalizedVarName.indexOf('_') >= 0) { - addViolationWithMessage(data, node, - "Only variables that are final should contain underscores (except for underscores in standard prefix/suffix), ''{0}'' is not final.", - new Object[] { varName }); - } - if (Character.isUpperCase(varName.charAt(0))) { - addViolationWithMessage(data, node, - "Variables should start with a lowercase character, ''{0}'' starts with uppercase character.", - new Object[] { varName }); - } - } - return data; - } - - private String normalizeVariableName(String varName, List<String> prefixes, List<String> suffixes) { - return stripSuffix(stripPrefix(varName, prefixes), suffixes); - } - - private String stripSuffix(String varName, List<String> suffixes) { - if (suffixes != null) { - for (String suffix : suffixes) { - if (varName.endsWith(suffix)) { - varName = varName.substring(0, varName.length() - suffix.length()); - break; - } - } - } - return varName; - } - - private String stripPrefix(String varName, List<String> prefixes) { - if (prefixes != null) { - for (String prefix : prefixes) { - if (varName.startsWith(prefix)) { - return varName.substring(prefix.length()); - } - } - } - return varName; - } - - public boolean hasPrefixesOrSuffixes() { - - for (PropertyDescriptor<?> desc : getPropertyDescriptors()) { - if (desc instanceof StringMultiProperty) { - List<String> values = getProperty((StringMultiProperty) desc); - if (!values.isEmpty()) { - return true; - } - } - } - return false; - } - - public String dysfunctionReason() { - return hasPrefixesOrSuffixes() ? null : "No prefixes or suffixes specified"; - } - -} diff --git a/pmd-apex/src/main/resources/category/apex/bestpractices.xml b/pmd-apex/src/main/resources/category/apex/bestpractices.xml new file mode 100644 index 00000000000..1ac782d8b18 --- /dev/null +++ b/pmd-apex/src/main/resources/category/apex/bestpractices.xml @@ -0,0 +1,112 @@ +<?xml version="1.0"?> + +<ruleset name="Best Practices" + xmlns="http://pmd.sourceforge.net/ruleset/2.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd"> + + <description> +Rules which enforce generally accepted best practices. + </description> + + <rule name="ApexUnitTestClassShouldHaveAsserts" + since="5.5.1" + message="Apex unit tests should System.assert() or assertEquals() or assertNotEquals()" + class="net.sourceforge.pmd.lang.apex.rule.bestpractices.ApexUnitTestClassShouldHaveAssertsRule" + externalInfoUrl="${pmd.website.baseurl}/pmd_rules_apex_bestpractices.html#apexunittestclassshouldhaveasserts"> + <description> +Apex unit tests should include at least one assertion. This makes the tests more robust, and using assert +with messages provide the developer a clearer idea of what the test does. + </description> + <priority>3</priority> + <example> +<![CDATA[ +@isTest +public class Foo { + public static testMethod void testSomething() { + Account a = null; + // This is better than having a NullPointerException + // System.assertNotEquals(a, null, 'account not found'); + a.toString(); + } +} +]]> + </example> + </rule> + + <rule name="ApexUnitTestShouldNotUseSeeAllDataTrue" + since="5.5.1" + message="Apex unit tests should not use @isTest(seeAllData = true)" + class="net.sourceforge.pmd.lang.apex.rule.bestpractices.ApexUnitTestShouldNotUseSeeAllDataTrueRule" + externalInfoUrl="${pmd.website.baseurl}/pmd_rules_apex_bestpractices.html#apexunittestshouldnotuseseealldatatrue"> + <description> +Apex unit tests should not use @isTest(seeAllData=true) because it opens up the existing database data for unexpected modification by tests. + </description> + <priority>3</priority> + <example> +<![CDATA[ +@isTest(seeAllData = true) +public class Foo { + public static testMethod void testSomething() { + Account a = null; + // This is better than having a NullPointerException + // System.assertNotEquals(a, null, 'account not found'); + a.toString(); + } +} +]]> + </example> + </rule> + + <rule name="AvoidGlobalModifier" + since="5.5.0" + message="Avoid using global modifier" + class="net.sourceforge.pmd.lang.apex.rule.bestpractices.AvoidGlobalModifierRule" + externalInfoUrl="${pmd.website.baseurl}/pmd_rules_apex_bestpractices.html#avoidglobalmodifier"> + <description> +Global classes should be avoided (especially in managed packages) as they can never be deleted or changed in signature. Always check twice if something needs to be global. +Many interfaces (e.g. Batch) required global modifiers in the past but don't require this anymore. Don't lock yourself in. + </description> + <priority>3</priority> + <example> +<![CDATA[ +global class Unchangeable { + global UndeletableType unchangable(UndeletableType param) { + // ... + } +} +]]> + </example> + </rule> + + <rule name="AvoidLogicInTrigger" + since="5.5.0" + message="Avoid logic in triggers" + class="net.sourceforge.pmd.lang.apex.rule.bestpractices.AvoidLogicInTriggerRule" + externalInfoUrl="${pmd.website.baseurl}/pmd_rules_apex_bestpractices.html#avoidlogicintrigger"> + <description> +As triggers do not allow methods like regular classes they are less flexible and suited to apply good encapsulation style. +Therefore delegate the triggers work to a regular class (often called Trigger handler class). + +See more here: https://developer.salesforce.com/page/Trigger_Frameworks_and_Apex_Trigger_Best_Practices + </description> + <priority>3</priority> + <example> +<![CDATA[ +trigger Accounts on Account (before insert, before update, before delete, after insert, after update, after delete, after undelete) { + for(Account acc : Trigger.new) { + if(Trigger.isInsert) { + // ... + } + + // ... + + if(Trigger.isDelete) { + // ... + } + } +} +]]> + </example> + </rule> +</ruleset> diff --git a/pmd-apex/src/main/resources/category/apex/categories.properties b/pmd-apex/src/main/resources/category/apex/categories.properties new file mode 100644 index 00000000000..4c45115d8f3 --- /dev/null +++ b/pmd-apex/src/main/resources/category/apex/categories.properties @@ -0,0 +1,17 @@ +# +# BSD-style license; for more info see http://pmd.sourceforge.net/license.html +# + +rulesets.filenames=\ + category/apex/bestpractices.xml,\ + category/apex/codestyle.xml,\ + category/apex/design.xml,\ + category/apex/errorprone.xml,\ + category/apex/performance.xml,\ + category/apex/security.xml + +# +# categories with no rules yet +# +#category/apex/documentation.xml +#category/apex/multithreading.xml diff --git a/pmd-apex/src/main/resources/category/apex/codestyle.xml b/pmd-apex/src/main/resources/category/apex/codestyle.xml new file mode 100644 index 00000000000..ef53ac90d0a --- /dev/null +++ b/pmd-apex/src/main/resources/category/apex/codestyle.xml @@ -0,0 +1,248 @@ +<?xml version="1.0"?> + +<ruleset name="Code Style" + xmlns="http://pmd.sourceforge.net/ruleset/2.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd"> + + <description> +Rules which enforce a specific coding style. + </description> + + <rule name="ClassNamingConventions" + since="5.5.0" + message="Class names should begin with an uppercase character" + class="net.sourceforge.pmd.lang.apex.rule.codestyle.ClassNamingConventionsRule" + externalInfoUrl="${pmd.website.baseurl}/pmd_rules_apex_codestyle.html#classnamingconventions"> + <description> +Class names should always begin with an upper case character. + </description> + <priority>1</priority> + <example> +<![CDATA[ +public class Foo {} +]]> + </example> + </rule> + + <rule name="IfElseStmtsMustUseBraces" + language="apex" + since="5.6.0" + message="Avoid using 'if...else' statements without curly braces" + class="net.sourceforge.pmd.lang.apex.rule.ApexXPathRule" + externalInfoUrl="${pmd.website.baseurl}/pmd_rules_apex_codestyle.html#ifelsestmtsmustusebraces"> + <description> +Avoid using if..else statements without using surrounding braces. If the code formatting +or indentation is lost then it becomes difficult to separate the code being controlled +from the rest. + </description> + <priority>3</priority> + <properties> + <property name="xpath"> + <value> +<![CDATA[ +//IfBlockStatement/BlockStatement[@CurlyBrace='false'][count(child::*) > 0] +| +//IfElseBlockStatement/BlockStatement[@CurlyBrace='false'][count(child::*) > 0] +]]> + </value> + </property> + </properties> + <example> +<![CDATA[ +// this is OK +if (foo) x++; + +// but this is not +if (foo) + x = x+1; +else + x = x-1; +]]> + </example> + </rule> + + <rule name="IfStmtsMustUseBraces" + language="apex" + since="5.6.0" + message="Avoid using if statements without curly braces" + class="net.sourceforge.pmd.lang.apex.rule.ApexXPathRule" + externalInfoUrl="${pmd.website.baseurl}/pmd_rules_apex_codestyle.html#ifstmtsmustusebraces"> + <description> +Avoid using if statements without using braces to surround the code block. If the code +formatting or indentation is lost then it becomes difficult to separate the code being +controlled from the rest. + </description> + <priority>3</priority> + <properties> + <property name="xpath"> + <value> +<![CDATA[ +//IfBlockStatement/BlockStatement[@CurlyBrace='false'] +]]> + </value> + </property> + </properties> + <example> + <![CDATA[ +if (foo) // not recommended + x++; + +if (foo) { // preferred approach + x++; +} +]]> + </example> + </rule> + + <rule name="ForLoopsMustUseBraces" + language="apex" + since="5.6.0" + message="Avoid using 'for' statements without curly braces" + class="net.sourceforge.pmd.lang.apex.rule.ApexXPathRule" + externalInfoUrl="${pmd.website.baseurl}/pmd_rules_apex_codestyle.html#forloopsmustusebraces"> + <description> +Avoid using 'for' statements without using surrounding braces. If the code formatting or +indentation is lost then it becomes difficult to separate the code being controlled +from the rest. + </description> + <priority>3</priority> + <properties> + <property name="xpath"> + <value> +<![CDATA[ +//ForLoopStatement/BlockStatement[@CurlyBrace='false'] +| +//ForEachStatement/BlockStatement[@CurlyBrace='false'] +]]> + </value> + </property> + </properties> + <example> +<![CDATA[ +for (int i = 0; i < 42; i++) // not recommended + foo(); + +for (int i = 0; i < 42; i++) { // preferred approach + foo(); +} +]]> + </example> + </rule> + + <rule name="MethodNamingConventions" + since="5.5.0" + message="Method name does not begin with a lower case character." + class="net.sourceforge.pmd.lang.apex.rule.codestyle.MethodNamingConventionsRule" + externalInfoUrl="${pmd.website.baseurl}/pmd_rules_apex_codestyle.html#methodnamingconventions"> + <description> +Method names should always begin with a lower case character, and should not contain underscores. + </description> + <priority>1</priority> + <example> +<![CDATA[ +public class Foo { + public void fooStuff() { + } +} +]]> + </example> + </rule> + + <rule name="OneDeclarationPerLine" + since="6.7.0" + message="Use one statement for each line, it enhances code readability." + class="net.sourceforge.pmd.lang.apex.rule.ApexXPathRule" + externalInfoUrl="${pmd.website.baseurl}/pmd_rules_apex_codestyle.html#onedeclarationperline"> + <description> +Apex allows the use of several variables declaration of the same type on one line. However, it +can lead to quite messy code. This rule looks for several declarations on the same line. + </description> + <priority>1</priority> + <properties> + <property name="xpath"> + <value> +<![CDATA[ +//VariableDeclarationStatements + [count(VariableDeclaration) > 1] + [$strictMode or count(distinct-values(VariableDeclaration/@BeginLine)) != count(VariableDeclaration)] +| +//FieldDeclarationStatements + [count(FieldDeclaration) > 1] + [$strictMode or count(distinct-values(FieldDeclaration/VariableExpression/@BeginLine)) != count(FieldDeclaration/VariableExpression)] +]]> + </value> + </property> + <property name="version" value="2.0"/> + <property name="strictMode" type="Boolean" value="false" + description="If true, mark combined declaration even if the declarations are on separate lines."/> + </properties> + <example> +<![CDATA[ +Integer a, b; // not recommended + +Integer a, + b; // ok by default, can be flagged setting the strictMode property + +Integer a; // preferred approach +Integer b; +]]> + </example> + </rule> + + <rule name="VariableNamingConventions" + since="5.5.0" + message="{0} variable {1} should begin with {2}" + class="net.sourceforge.pmd.lang.apex.rule.codestyle.VariableNamingConventionsRule" + externalInfoUrl="${pmd.website.baseurl}/pmd_rules_apex_codestyle.html#variablenamingconventions"> + <description> +A variable naming conventions rule - customize this to your liking. Currently, it +checks for final variables that should be fully capitalized and non-final variables +that should not include underscores. + </description> + <priority>1</priority> + <example> +<![CDATA[ +public class Foo { + public static final Integer MY_NUM = 0; + public String myTest = ''; + DataModule dmTest = new DataModule(); +} +]]> + </example> + </rule> + + <rule name="WhileLoopsMustUseBraces" + language="apex" + since="5.6.0" + message="Avoid using 'while' statements without curly braces" + class="net.sourceforge.pmd.lang.apex.rule.ApexXPathRule" + externalInfoUrl="${pmd.website.baseurl}/pmd_rules_apex_codestyle.html#whileloopsmustusebraces"> + <description> +Avoid using 'while' statements without using braces to surround the code block. If the code +formatting or indentation is lost then it becomes difficult to separate the code being +controlled from the rest. + </description> + <priority>3</priority> + <properties> + <property name="xpath"> + <value> +<![CDATA[ +//WhileLoopStatement/BlockStatement[@CurlyBrace='false'] +]]> + </value> + </property> + </properties> + <example> +<![CDATA[ +while (true) // not recommended + x++; + +while (true) { // preferred approach + x++; +} +]]> + </example> + </rule> + +</ruleset> diff --git a/pmd-apex/src/main/resources/category/apex/design.xml b/pmd-apex/src/main/resources/category/apex/design.xml new file mode 100644 index 00000000000..4ae03203820 --- /dev/null +++ b/pmd-apex/src/main/resources/category/apex/design.xml @@ -0,0 +1,342 @@ +<?xml version="1.0"?> + +<ruleset name="Design" + xmlns="http://pmd.sourceforge.net/ruleset/2.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd"> + + <description> +Rules that help you discover design issues. + </description> + + <rule name="AvoidDeeplyNestedIfStmts" + since="5.5.0" + message="Deeply nested if..then statements are hard to read" + class="net.sourceforge.pmd.lang.apex.rule.design.AvoidDeeplyNestedIfStmtsRule" + externalInfoUrl="${pmd.website.baseurl}/pmd_rules_apex_design.html#avoiddeeplynestedifstmts"> + <description> +Avoid creating deeply nested if-then statements since they are harder to read and error-prone to maintain. + </description> + <priority>3</priority> + <example> +<![CDATA[ +public class Foo { + public void bar(Integer x, Integer y, Integer z) { + if (x>y) { + if (y>z) { + if (z==x) { + // !! too deep + } + } + } + } +} +]]> + </example> + </rule> + + <rule name="CyclomaticComplexity" + message="The {0} ''{1}'' has a{2} cyclomatic complexity of {3}." + since="6.0.0" + class="net.sourceforge.pmd.lang.apex.rule.design.CyclomaticComplexityRule" + externalInfoUrl="${pmd.website.baseurl}/pmd_rules_apex_design.html#cyclomaticcomplexity"> + <description> +The complexity of methods directly affects maintenance costs and readability. Concentrating too much decisional logic +in a single method makes its behaviour hard to read and change. + +Cyclomatic complexity assesses the complexity of a method by counting the number of decision points in a method, +plus one for the method entry. Decision points are places where the control flow jumps to another place in the +program. As such, they include all control flow statements, such as 'if', 'while', 'for', and 'case'. + +Generally, numbers ranging from 1-4 denote low complexity, 5-7 denote moderate complexity, 8-10 denote +high complexity, and 11+ is very high complexity. By default, this rule reports methods with a complexity >= 10. +Additionnally, classes with many methods of moderate complexity get reported as well once the total of their +methods' complexities reaches 40, even if none of the methods was directly reported. + +Reported methods should be broken down into several smaller methods. Reported classes should probably be broken down +into subcomponents. + </description> + <priority>3</priority> + <example> +<![CDATA[ +public class Complicated { + public void example() { // This method has a cyclomatic complexity of 12 + int x = 0, y = 1, z = 2, t = 2; + boolean a = false, b = true, c = false, d = true; + if (a && b || b && d) { + if (y == z) { + x = 2; + } else if (y == t && !d) { + x = 2; + } else { + x = 2; + } + } else if (c && d) { + while (z < y) { + x = 2; + } + } else { + for (int n = 0; n < t; n++) { + x = 2; + } + } + } +} +]]> + </example> + </rule> + + <rule name="ExcessiveClassLength" + since="5.5.0" + message="Avoid really long classes." + class="net.sourceforge.pmd.lang.apex.rule.design.ExcessiveClassLengthRule" + externalInfoUrl="${pmd.website.baseurl}/pmd_rules_apex_design.html#excessiveclasslength"> + <description> +Excessive class file lengths are usually indications that the class may be burdened with excessive +responsibilities that could be provided by external classes or functions. In breaking these methods +apart the code becomes more managable and ripe for reuse. + </description> + <priority>3</priority> + <example> +<![CDATA[ +public class Foo { + public void bar1() { + // 1000 lines of code + } + public void bar2() { + // 1000 lines of code + } + public void bar3() { + // 1000 lines of code + } + public void barN() { + // 1000 lines of code + } +} +]]> + </example> + </rule> + + <rule name="ExcessiveParameterList" + since="5.5.0" + message="Avoid long parameter lists." + class="net.sourceforge.pmd.lang.apex.rule.design.ExcessiveParameterListRule" + externalInfoUrl="${pmd.website.baseurl}/pmd_rules_apex_design.html#excessiveparameterlist"> + <description> +Methods with numerous parameters are a challenge to maintain, especially if most of them share the +same datatype. These situations usually denote the need for new objects to wrap the numerous parameters. + </description> + <priority>3</priority> + <example> +<![CDATA[ +// too many arguments liable to be mixed up +public void addPerson(int birthYear, int birthMonth, int birthDate, int height, int weight, int ssn) { + // ... +} +// preferred approach +public void addPerson(Date birthdate, BodyMeasurements measurements, int ssn) { + // ... +} +]]> + </example> + </rule> + + <rule name="ExcessivePublicCount" + since="5.5.0" + message="This class has a bunch of public methods and attributes" + class="net.sourceforge.pmd.lang.apex.rule.design.ExcessivePublicCountRule" + externalInfoUrl="${pmd.website.baseurl}/pmd_rules_apex_design.html#excessivepubliccount"> + <description> +Classes with large numbers of public methods and attributes require disproportionate testing efforts +since combinational side effects grow rapidly and increase risk. Refactoring these classes into +smaller ones not only increases testability and reliability but also allows new variations to be +developed easily. + </description> + <priority>3</priority> + <example> +<![CDATA[ +public class Foo { + public String value; + public Bar something; + public Variable var; + // [... more more public attributes ...] + + public void doWork() {} + public void doMoreWork() {} + public void doWorkAgain() {} + // [... more more public methods ...] +} +]]> + </example> + </rule> + + <rule name="NcssConstructorCount" + since="5.5.0" + message="The constructor has an NCSS line count of {0}" + class="net.sourceforge.pmd.lang.apex.rule.design.NcssConstructorCountRule" + externalInfoUrl="${pmd.website.baseurl}/pmd_rules_apex_design.html#ncssconstructorcount"> + <description> +This rule uses the NCSS (Non-Commenting Source Statements) algorithm to determine the number of lines +of code for a given constructor. NCSS ignores comments, and counts actual statements. Using this algorithm, +lines of code that are split are counted as one. + </description> + <priority>3</priority> + <example> +<![CDATA[ +public class Foo extends Bar { + //this constructor only has 1 NCSS lines + public Foo() { + super(); + + + + + super.foo(); +} +} +]]> + </example> + </rule> + + <rule name="NcssMethodCount" + since="5.5.0" + message="The method ''{0}()'' has an NCSS line count of {1}" + class="net.sourceforge.pmd.lang.apex.rule.design.NcssMethodCountRule" + externalInfoUrl="${pmd.website.baseurl}/pmd_rules_apex_design.html#ncssmethodcount"> + <description> +This rule uses the NCSS (Non-Commenting Source Statements) algorithm to determine the number of lines +of code for a given method. NCSS ignores comments, and counts actual statements. Using this algorithm, +lines of code that are split are counted as one. + </description> + <priority>3</priority> + <example> +<![CDATA[ +public class Foo extends Bar { + //this method only has 1 NCSS lines + public Integer methd() { + super.methd(); + + + + return 1; + } +} +]]> + </example> + </rule> + + <rule name="NcssTypeCount" + since="5.5.0" + message="The type has an NCSS line count of {0}" + class="net.sourceforge.pmd.lang.apex.rule.design.NcssTypeCountRule" + externalInfoUrl="${pmd.website.baseurl}/pmd_rules_apex_design.html#ncsstypecount"> + <description> +This rule uses the NCSS (Non-Commenting Source Statements) algorithm to determine the number of lines +of code for a given type. NCSS ignores comments, and counts actual statements. Using this algorithm, +lines of code that are split are counted as one. + </description> + <priority>3</priority> + <example> +<![CDATA[ +//this class only has 6 NCSS lines +public class Foo extends Bar { + public Foo() { + super(); + + + + + + super.foo(); + } +} +]]> + </example> + </rule> + + <rule name="StdCyclomaticComplexity" + since="5.5.0" + message="The {0} ''{1}'' has a Standard Cyclomatic Complexity of {2}." + class="net.sourceforge.pmd.lang.apex.rule.design.StdCyclomaticComplexityRule" + externalInfoUrl="${pmd.website.baseurl}/pmd_rules_apex_design.html#stdcyclomaticcomplexity"> + <description> +Complexity directly affects maintenance costs is determined by the number of decision points in a method +plus one for the method entry. The decision points include 'if', 'while', 'for', and 'case labels' calls. +Generally, numbers ranging from 1-4 denote low complexity, 5-7 denote moderate complexity, 8-10 denote +high complexity, and 11+ is very high complexity. + </description> + <priority>3</priority> + <example> +<![CDATA[ +// This has a Cyclomatic Complexity = 12 +public class Foo { +1 public void example() { +2 if (a == b || (c == d && e == f)) { +3 if (a1 == b1) { + fiddle(); +4 } else if a2 == b2) { + fiddle(); + } else { + fiddle(); + } +5 } else if (c == d) { +6 while (c == d) { + fiddle(); + } +7 } else if (e == f) { +8 for (int n = 0; n < h; n++) { + fiddle(); + } + } else { + switch (z) { +9 case 1: + fiddle(); + break; +10 case 2: + fiddle(); + break; +11 case 3: + fiddle(); + break; +12 default: + fiddle(); + break; + } + } +} +]]> + </example> + </rule> + + <rule name="TooManyFields" + since="5.5.0" + message="Too many fields" + class="net.sourceforge.pmd.lang.apex.rule.design.TooManyFieldsRule" + externalInfoUrl="${pmd.website.baseurl}/pmd_rules_apex_design.html#toomanyfields"> + <description> +Classes that have too many fields can become unwieldy and could be redesigned to have fewer fields, +possibly through grouping related fields in new objects. For example, a class with individual +city/state/zip fields could park them within a single Address field. + </description> + <priority>3</priority> + <example> +<![CDATA[ +public class Person { + // too many separate fields + int birthYear; + int birthMonth; + int birthDate; + float height; + float weight; +} + +public class Person { + // this is more manageable + Date birthDate; + BodyMeasurements measurements; +} +]]> + </example> + </rule> + +</ruleset> diff --git a/pmd-apex/src/main/resources/category/apex/documentation.xml b/pmd-apex/src/main/resources/category/apex/documentation.xml new file mode 100644 index 00000000000..cafcf10d529 --- /dev/null +++ b/pmd-apex/src/main/resources/category/apex/documentation.xml @@ -0,0 +1,47 @@ +<?xml version="1.0"?> + +<ruleset name="Documentation" + xmlns="http://pmd.sourceforge.net/ruleset/2.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd"> + + <description> +Rules that are related to code documentation. + </description> + + <rule name="ApexDoc" + since="6.8.0" + message="ApexDoc comment is missing or incorrect" + class="net.sourceforge.pmd.lang.apex.rule.documentation.ApexDocRule" + externalInfoUrl="${pmd.website.baseurl}/pmd_rules_apex_documentation.html#apexdoc"> + <description> +This rule validates that: + +* ApexDoc comments are present for classes, methods, and properties that are public or global, excluding +overrides and test classes (as well as the contents of test classes). +* ApexDoc comments should contain @description. +* ApexDoc comments on non-void, non-constructor methods should contain @return. +* ApexDoc comments on void or constructor methods should not contain @return. +* ApexDoc comments on methods with parameters should contain @param for each parameter, in the same +order as the method signature. + +Method overrides and tests are both exempted from having ApexDoc. + </description> + <priority>3</priority> + <example> +<![CDATA[ +/** + * @description Hello World + */ +public class HelloWorld { + /** + * @description Bar + * @return Bar + */ + public Object bar() { return null; } +} +]]> + </example> + </rule> + +</ruleset> diff --git a/pmd-apex/src/main/resources/category/apex/errorprone.xml b/pmd-apex/src/main/resources/category/apex/errorprone.xml new file mode 100644 index 00000000000..6eaa36f7f55 --- /dev/null +++ b/pmd-apex/src/main/resources/category/apex/errorprone.xml @@ -0,0 +1,291 @@ +<?xml version="1.0"?> + +<ruleset name="Error Prone" + xmlns="http://pmd.sourceforge.net/ruleset/2.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd"> + + <description> +Rules to detect constructs that are either broken, extremely confusing or prone to runtime errors. + </description> + + <rule name="AvoidDirectAccessTriggerMap" + since="6.0.0" + message="Avoid directly accessing Trigger.old and Trigger.new" + class="net.sourceforge.pmd.lang.apex.rule.ApexXPathRule" + externalInfoUrl="${pmd.website.baseurl}/pmd_rules_apex_errorprone.html#avoiddirectaccesstriggermap"> + <description> +Avoid directly accessing Trigger.old and Trigger.new as it can lead to a bug. Triggers should be bulkified and iterate through the map to handle the actions for each item separately. + </description> + <priority>3</priority> + <properties> + <property name="xpath"> + <value> +<![CDATA[ +//ArrayLoadExpression[TriggerVariableExpression and LiteralExpression] +]]> + </value> + </property> + </properties> + <example> +<![CDATA[ +trigger AccountTrigger on Account (before insert, before update) { + Account a = Trigger.new[0]; //Bad: Accessing the trigger array directly is not recommended. + + foreach ( Account a : Trigger.new ){ + //Good: Iterate through the trigger.new array instead. + } +} +]]> + </example> + </rule> + + <rule name="AvoidHardcodingId" + since="6.0.0" + message="Hardcoding Id's is bound to break when changing environments." + class="net.sourceforge.pmd.lang.apex.rule.errorprone.AvoidHardcodingIdRule" + externalInfoUrl="${pmd.website.baseurl}/pmd_rules_apex_errorprone.html#avoidhardcodingid"> + <description> +When deploying Apex code between sandbox and production environments, or installing Force.com AppExchange packages, +it is essential to avoid hardcoding IDs in the Apex code. By doing so, if the record IDs change between environments, +the logic can dynamically identify the proper data to operate against and not fail. + </description> + <priority>3</priority> + <example> +<![CDATA[ +public without sharing class Foo { + void foo() { + //Error - hardcoded the record type id + if(a.RecordTypeId == '012500000009WAr'){ + //do some logic here..... + } else if(a.RecordTypeId == '0123000000095Km'){ + //do some logic here for a different record type... + } + } +} +]]> + </example> + </rule> + + <rule name="EmptyCatchBlock" + language="apex" + since="6.0.0" + message="Avoid empty catch blocks" + class="net.sourceforge.pmd.lang.apex.rule.ApexXPathRule" + externalInfoUrl="${pmd.website.baseurl}/pmd_rules_apex_errorprone.html#emptycatchblock"> + <description> +Empty Catch Block finds instances where an exception is caught, but nothing is done. +In most circumstances, this swallows an exception which should either be acted on +or reported. + </description> + <priority>3</priority> + <properties> + <property name="xpath"> + <value> +<![CDATA[ +//CatchBlockStatement[./BlockStatement[count(*) = 0]] +]]> + </value> + </property> + </properties> + <example> +<![CDATA[ +public void doSomething() { + ... + try { + insert accounts; + } catch (DmlException dmle) { + // not good + } +} +]]> + </example> + </rule> + + <rule name="EmptyIfStmt" + language="apex" + since="6.0.0" + message="Avoid empty 'if' statements" + class="net.sourceforge.pmd.lang.apex.rule.ApexXPathRule" + externalInfoUrl="${pmd.website.baseurl}/pmd_rules_apex_errorprone.html#emptyifstmt"> + <description> +Empty If Statement finds instances where a condition is checked but nothing is done about it. + </description> + <priority>3</priority> + <properties> + <property name="xpath"> + <value> +<![CDATA[ +//IfBlockStatement + [BlockStatement[count(*) = 0]] +]]> + </value> + </property> + </properties> + <example> +<![CDATA[ +public class Foo { + public void bar(Integer x) { + if (x == 0) { + // empty! + } + } +} +]]> + </example> + </rule> + + <rule name="EmptyStatementBlock" + language="apex" + since="6.0.0" + message="Avoid empty block statements." + class="net.sourceforge.pmd.lang.apex.rule.ApexXPathRule" + externalInfoUrl="${pmd.website.baseurl}/pmd_rules_apex_errorprone.html#emptystatementblock"> + <description> +Empty block statements serve no purpose and should be removed. + </description> + <priority>3</priority> + <properties> + <property name="xpath"> + <value> +<![CDATA[ +//Method/ModifierNode[@Abstract!='true' and ../BlockStatement[count(*) = 0]] +| //Method/BlockStatement//BlockStatement[count(*) = 0 and @Location != parent::*/@Location] +]]> + </value> + </property> + </properties> + <example> +<![CDATA[ +public class Foo { + + private int _bar; + + public void setBar(int bar) { + // empty + } + +} +]]> + </example> + </rule> + + <rule name="EmptyTryOrFinallyBlock" + language="apex" + since="6.0.0" + message="Avoid empty try or finally blocks" + class="net.sourceforge.pmd.lang.apex.rule.ApexXPathRule" + externalInfoUrl="${pmd.website.baseurl}/pmd_rules_apex_errorprone.html#emptytryorfinallyblock"> + <description> +Avoid empty try or finally blocks - what's the point? + </description> + <priority>3</priority> + <properties> + <property name="xpath"> + <value> +<![CDATA[ +//TryCatchFinallyBlockStatement[./BlockStatement[count(*) = 0]] +]]> + </value> + </property> + </properties> + <example> +<![CDATA[ +public class Foo { + public void bar() { + try { + // empty ! + } catch (Exception e) { + e.printStackTrace(); + } + } +} + +public class Foo { + public void bar() { + try { + int x=2; + } finally { + // empty! + } + } +} +]]> + </example> + </rule> + + <rule name="EmptyWhileStmt" + language="apex" + since="6.0.0" + message="Avoid empty 'while' statements" + class="net.sourceforge.pmd.lang.apex.rule.ApexXPathRule" + externalInfoUrl="${pmd.website.baseurl}/pmd_rules_apex_errorprone.html#emptywhilestmt"> + <description> +Empty While Statement finds all instances where a while statement does nothing. +If it is a timing loop, then you should use Thread.sleep() for it; if it is +a while loop that does a lot in the exit expression, rewrite it to make it clearer. + </description> + <priority>3</priority> + <properties> + <property name="xpath"> + <value> +<![CDATA[ +//WhileLoopStatement[./BlockStatement[count(*) = 0]] +]]> + </value> + </property> + </properties> + <example> +<![CDATA[ +public void bar(Integer a, Integer b) { + while (a == b) { + // empty! + } +} +]]> + </example> + </rule> + + <rule name="MethodWithSameNameAsEnclosingClass" + since="5.5.0" + message="Classes should not have non-constructor methods with the same name as the class" + class="net.sourceforge.pmd.lang.apex.rule.errorprone.MethodWithSameNameAsEnclosingClassRule" + externalInfoUrl="${pmd.website.baseurl}/pmd_rules_apex_errorprone.html#methodwithsamenameasenclosingclass"> + <description> +Non-constructor methods should not have the same name as the enclosing class. + </description> + <priority>3</priority> + <example> +<![CDATA[ +public class MyClass { + // this is OK because it is a constructor + public MyClass() {} + // this is bad because it is a method + public void MyClass() {} +} +]]> + </example> + </rule> + + <rule name="AvoidNonExistentAnnotations" + since="6.5.0" + message="Use of non existent annotations will lead to broken Apex code which will not compile in the future." + class="net.sourceforge.pmd.lang.apex.rule.errorprone.AvoidNonExistentAnnotationsRule" + externalInfoUrl="${pmd.website.baseurl}/pmd_rules_apex_errorprone.html#avoidnonexistentannotations"> + <description> + Apex supported non existent annotations for legacy reasons. + In the future, use of such non-existent annotations could result in broken apex code that will not compile. + This will prevent users of garbage annotations from being able to use legitimate annotations added to Apex in the future. + A full list of supported annotations can be found at https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_classes_annotation.htm + </description> + <priority>3</priority> + <example> + <![CDATA[@NonExistentAnnotation public class ClassWithNonexistentAnnotation { + @NonExistentAnnotation public void methodWithNonExistentAnnotation() { + // ... + } +} +]]> + </example> + </rule> +</ruleset> diff --git a/pmd-apex/src/main/resources/category/apex/multithreading.xml b/pmd-apex/src/main/resources/category/apex/multithreading.xml new file mode 100644 index 00000000000..88c22dfc8bc --- /dev/null +++ b/pmd-apex/src/main/resources/category/apex/multithreading.xml @@ -0,0 +1,11 @@ +<?xml version="1.0"?> + +<ruleset name="Multithreading" + xmlns="http://pmd.sourceforge.net/ruleset/2.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd"> + + <description> +Rules that flag issues when dealing with multiple threads of execution. + </description> +</ruleset> diff --git a/pmd-apex/src/main/resources/category/apex/performance.xml b/pmd-apex/src/main/resources/category/apex/performance.xml new file mode 100644 index 00000000000..01c4191bf81 --- /dev/null +++ b/pmd-apex/src/main/resources/category/apex/performance.xml @@ -0,0 +1,80 @@ +<?xml version="1.0"?> + +<ruleset name="Performance" + xmlns="http://pmd.sourceforge.net/ruleset/2.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd"> + + <description> +Rules that flag suboptimal code. + </description> + + <rule name="AvoidDmlStatementsInLoops" + since="5.5.0" + message="Avoid DML statements inside loops" + class="net.sourceforge.pmd.lang.apex.rule.performance.AvoidDmlStatementsInLoopsRule" + externalInfoUrl="${pmd.website.baseurl}/pmd_rules_apex_performance.html#avoiddmlstatementsinloops"> + <description> +Avoid DML statements inside loops to avoid hitting the DML governor limit. Instead, try to batch up the data into a list and invoke your DML once on that list of data outside the loop. + </description> + <priority>3</priority> + <example> +<![CDATA[ +public class Something { + public void foo() { + for (Integer i = 0; i < 151; i++) { + Account account; + // ... + insert account; + } + } +} +]]> + </example> + </rule> + + <rule name="AvoidSoqlInLoops" + since="5.5.0" + message="Avoid Soql queries inside loops" + class="net.sourceforge.pmd.lang.apex.rule.performance.AvoidSoqlInLoopsRule" + externalInfoUrl="${pmd.website.baseurl}/pmd_rules_apex_performance.html#avoidsoqlinloops"> + <description> +New objects created within loops should be checked to see if they can created outside them and reused. + </description> + <priority>3</priority> + <example> +<![CDATA[ +public class Something { + public static void main( String as[] ) { + for (Integer i = 0; i < 10; i++) { + List<Account> accounts = [SELECT Id FROM Account]; + } + } +} +]]> + </example> + </rule> + + <rule name="AvoidSoslInLoops" + since="6.0.0" + message="Avoid Sosl queries inside loops" + class="net.sourceforge.pmd.lang.apex.rule.performance.AvoidSoslInLoopsRule" + externalInfoUrl="${pmd.website.baseurl}/pmd_rules_apex_performance.html#avoidsoslinloops"> + <description> +Sosl calls within loops can cause governor limit exceptions. + </description> + <priority>3</priority> + <example> +<![CDATA[ +public class Something { + public static void main( String as[] ) { + for (Integer i = 0; i < 10; i++) { + List<List<SObject>> searchList = [FIND 'map*' IN ALL FIELDS RETURNING Account (Id, Name), Contact, Opportunity, Lead]; + } + } +} +]]> + </example> + </rule> + +</ruleset> diff --git a/pmd-apex/src/main/resources/category/apex/security.xml b/pmd-apex/src/main/resources/category/apex/security.xml new file mode 100644 index 00000000000..1c60c72bb8b --- /dev/null +++ b/pmd-apex/src/main/resources/category/apex/security.xml @@ -0,0 +1,274 @@ +<?xml version="1.0"?> + +<ruleset name="Security" + xmlns="http://pmd.sourceforge.net/ruleset/2.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd"> + + <description> +Rules that flag potential security flaws. + </description> + + <rule name="ApexBadCrypto" + since="5.5.3" + message="Apex classes should use random IV/key" + class="net.sourceforge.pmd.lang.apex.rule.security.ApexBadCryptoRule" + externalInfoUrl="${pmd.website.baseurl}/pmd_rules_apex_security.html#apexbadcrypto"> + <description> +The rule makes sure you are using randomly generated IVs and keys for `Crypto` calls. +Hard-wiring these values greatly compromises the security of encrypted data. + </description> + <priority>3</priority> + <example> +<![CDATA[ +public without sharing class Foo { + Blob hardCodedIV = Blob.valueOf('Hardcoded IV 123'); + Blob hardCodedKey = Blob.valueOf('0000000000000000'); + Blob data = Blob.valueOf('Data to be encrypted'); + Blob encrypted = Crypto.encrypt('AES128', hardCodedKey, hardCodedIV, data); +} +]]> + </example> + </rule> + + <rule name="ApexCRUDViolation" + since="5.5.3" + message="Validate CRUD permission before SOQL/DML operation" + class="net.sourceforge.pmd.lang.apex.rule.security.ApexCRUDViolationRule" + externalInfoUrl="${pmd.website.baseurl}/pmd_rules_apex_security.html#apexcrudviolation"> + <description> +The rule validates you are checking for access permissions before a SOQL/SOSL/DML operation. +Since Apex runs in system mode not having proper permissions checks results in escalation of +privilege and may produce runtime errors. This check forces you to handle such scenarios. + </description> + <priority>3</priority> + <example> +<![CDATA[ +public class Foo { + public Contact foo(String status, String ID) { + Contact c = [SELECT Status__c FROM Contact WHERE Id=:ID]; + + // Make sure we can update the database before even trying + if (!Schema.sObjectType.Contact.fields.Name.isUpdateable()) { + return null; + } + + c.Status__c = status; + update c; + return c; + } +} +]]> + </example> + </rule> + + <rule name="ApexCSRF" + since="5.5.3" + message="Avoid making DML operations in Apex class constructor/init method" + class="net.sourceforge.pmd.lang.apex.rule.security.ApexCSRFRule" + externalInfoUrl="${pmd.website.baseurl}/pmd_rules_apex_security.html#apexcsrf"> + <description> +Check to avoid making DML operations in Apex class constructor/init method. This prevents +modification of the database just by accessing a page. + </description> + <priority>3</priority> + <example> +<![CDATA[ +public class Foo { + public init() { + insert data; + } + + public Foo() { + insert data; + } +} +]]> + </example> + </rule> + + <rule name="ApexDangerousMethods" + since="5.5.3" + message="Calling potentially dangerous method" + class="net.sourceforge.pmd.lang.apex.rule.security.ApexDangerousMethodsRule" + externalInfoUrl="${pmd.website.baseurl}/pmd_rules_apex_security.html#apexdangerousmethods"> + <description> +Checks against calling dangerous methods. + +For the time being, it reports: + +* Against `FinancialForce`'s `Configuration.disableTriggerCRUDSecurity()`. Disabling CRUD security +opens the door to several attacks and requires manual validation, which is unreliable. +* Calling `System.debug` passing sensitive data as parameter, which could lead to exposure +of private data. + </description> + <priority>3</priority> + <example> +<![CDATA[ +public class Foo { + public Foo() { + Configuration.disableTriggerCRUDSecurity(); + } +} +]]> + </example> + </rule> + + <rule name="ApexInsecureEndpoint" + since="5.5.3" + message="Apex callouts should use encrypted communication channels" + class="net.sourceforge.pmd.lang.apex.rule.security.ApexInsecureEndpointRule" + externalInfoUrl="${pmd.website.baseurl}/pmd_rules_apex_security.html#apexinsecureendpoint"> + <description> +Checks against accessing endpoints under plain **http**. You should always use +**https** for security. + </description> + <priority>3</priority> + <example> +<![CDATA[ +public without sharing class Foo { + void foo() { + HttpRequest req = new HttpRequest(); + req.setEndpoint('http://localhost:com'); + } +} +]]> + </example> + </rule> + + <rule name="ApexOpenRedirect" + since="5.5.3" + message="Apex classes should safely redirect to a known location" + class="net.sourceforge.pmd.lang.apex.rule.security.ApexOpenRedirectRule" + externalInfoUrl="${pmd.website.baseurl}/pmd_rules_apex_security.html#apexopenredirect"> + <description> +Checks against redirects to user-controlled locations. This prevents attackers from +redirecting users to phishing sites. + </description> + <priority>3</priority> + <example> +<![CDATA[ +public without sharing class Foo { + String unsafeLocation = ApexPage.getCurrentPage().getParameters.get('url_param'); + PageReference page() { + return new PageReference(unsafeLocation); + } +} +]]> + </example> + </rule> + + <rule name="ApexSharingViolations" + since="5.5.3" + message="Apex classes should declare a sharing model if DML or SOQL/SOSL is used" + class="net.sourceforge.pmd.lang.apex.rule.security.ApexSharingViolationsRule" + externalInfoUrl="${pmd.website.baseurl}/pmd_rules_apex_security.html#apexsharingviolations"> + <description> +Detect classes declared without explicit sharing mode if DML methods are used. This +forces the developer to take access restrictions into account before modifying objects. + </description> + <priority>3</priority> + <example> +<![CDATA[ +public without sharing class Foo { + // DML operation here +} +]]> + </example> + </rule> + + <rule name="ApexSOQLInjection" + since="5.5.3" + message="Avoid untrusted/unescaped variables in DML query" + class="net.sourceforge.pmd.lang.apex.rule.security.ApexSOQLInjectionRule" + externalInfoUrl="${pmd.website.baseurl}/pmd_rules_apex_security.html#apexsoqlinjection"> + <description> +Detects the usage of untrusted / unescaped variables in DML queries. + </description> + <priority>3</priority> + <example> +<![CDATA[ +public class Foo { + public void test1(String t1) { + Database.query('SELECT Id FROM Account' + t1); + } +} +]]> + </example> + </rule> + + <rule name="ApexSuggestUsingNamedCred" + since="5.5.3" + message="Suggest named credentials for authentication" + class="net.sourceforge.pmd.lang.apex.rule.security.ApexSuggestUsingNamedCredRule" + externalInfoUrl="${pmd.website.baseurl}/pmd_rules_apex_security.html#apexsuggestusingnamedcred"> + <description> +Detects hardcoded credentials used in requests to an endpoint. + +You should refrain from hardcoding credentials: + * They are hard to mantain by being mixed in application code + * Particularly hard to update them when used from different classes + * Granting a developer access to the codebase means granting knowledge + of credentials, keeping a two-level access is not possible. + * Using different credentials for different environments is troublesome + and error-prone. + +Instead, you should use *Named Credentials* and a callout endpoint. + +For more information, you can check [this](https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_callouts_named_credentials.htm) + </description> + <priority>3</priority> + <example> +<![CDATA[ +public class Foo { + public void foo(String username, String password) { + Blob headerValue = Blob.valueOf(username + ':' + password); + String authorizationHeader = 'BASIC ' + EncodingUtil.base64Encode(headerValue); + req.setHeader('Authorization', authorizationHeader); + } +} +]]> + </example> + </rule> + + <rule name="ApexXSSFromEscapeFalse" + since="5.5.3" + message="Apex classes should escape Strings in error messages" + class="net.sourceforge.pmd.lang.apex.rule.security.ApexXSSFromEscapeFalseRule" + externalInfoUrl="${pmd.website.baseurl}/pmd_rules_apex_security.html#apexxssfromescapefalse"> + <description> +Reports on calls to `addError` with disabled escaping. The message passed to `addError` +will be displayed directly to the user in the UI, making it prime ground for XSS +attacks if unescaped. + </description> + <priority>3</priority> + <example> +<![CDATA[ +public without sharing class Foo { + Trigger.new[0].addError(vulnerableHTMLGoesHere, false); +} +]]> + </example> + </rule> + + <rule name="ApexXSSFromURLParam" + since="5.5.3" + message="Apex classes should escape/sanitize Strings obtained from URL parameters" + class="net.sourceforge.pmd.lang.apex.rule.security.ApexXSSFromURLParamRule" + externalInfoUrl="${pmd.website.baseurl}/pmd_rules_apex_security.html#apexxssfromurlparam"> + <description> +Makes sure that all values obtained from URL parameters are properly escaped / sanitized +to avoid XSS attacks. + </description> + <priority>3</priority> + <example> +<![CDATA[ +public without sharing class Foo { + String unescapedstring = ApexPage.getCurrentPage().getParameters.get('url_param'); + String usedLater = unescapedstring; +} +]]> + </example> + </rule> + +</ruleset> diff --git a/pmd-apex/src/main/resources/rulesets/apex/apexunit.xml b/pmd-apex/src/main/resources/rulesets/apex/apexunit.xml index 50c11851043..efcca12aaef 100644 --- a/pmd-apex/src/main/resources/rulesets/apex/apexunit.xml +++ b/pmd-apex/src/main/resources/rulesets/apex/apexunit.xml @@ -3,56 +3,12 @@ <ruleset name="ApexUnit" xmlns="http://pmd.sourceforge.net/ruleset/2.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 http://pmd.sourceforge.net/ruleset_2_0_0.xsd"> + xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd"> <description> These rules deal with different problems that can occur with Apex unit tests. </description> - <rule name="ApexUnitTestClassShouldHaveAsserts" - since="5.5.1" - message="Apex unit tests should System.assert() or assertEquals() or assertNotEquals()" - class="net.sourceforge.pmd.lang.apex.rule.apexunit.ApexUnitTestClassShouldHaveAssertsRule" - externalInfoUrl="${pmd.website.baseurl}/pmd_rules_apex_apexunit.html#apexunittestclassshouldhaveasserts"> - <description> - Apex unit tests should include at least one assertion. This makes the tests more robust, and using assert - with messages provide the developer a clearer idea of what the test does. - </description> - <priority>3</priority> - <example> - <![CDATA[ -@isTest -public class Foo { - public static testMethod void testSomething() { - Account a = null; - // This is better than having a NullPointerException - // System.assertNotEquals(a, null, 'account not found'); - a.toString(); - } -} - ]]> - </example> - </rule> - <rule name="ApexUnitTestShouldNotUseSeeAllDataTrue" - since="5.5.1" - message="Apex unit tests should not use @isTest(seeAllData = true)" - class="net.sourceforge.pmd.lang.apex.rule.apexunit.ApexUnitTestShouldNotUseSeeAllDataTrueRule" - externalInfoUrl="${pmd.website.baseurl}/pmd_rules_apex_apexunit.html#apexunittestshouldnotuseseealldatatrue"> - <description> - Apex unit tests should not use @isTest(seeAllData=true) because it opens up the existing database data for unexpected modification by tests. - </description> - <priority>3</priority> - <example> - <![CDATA[ -@isTest(seeAllData = true) -public class Foo { - public static testMethod void testSomething() { - Account a = null; - // This is better than having a NullPointerException - // System.assertNotEquals(a, null, 'account not found'); - a.toString(); - } -} - ]]> - </example> - </rule> + <rule ref="category/apex/bestpractices.xml/ApexUnitTestClassShouldHaveAsserts" deprecated="true" /> + <rule ref="category/apex/bestpractices.xml/ApexUnitTestShouldNotUseSeeAllDataTrue" deprecated="true" /> + </ruleset> diff --git a/pmd-apex/src/main/resources/rulesets/apex/braces.xml b/pmd-apex/src/main/resources/rulesets/apex/braces.xml index adba480da39..10a8a93bc6f 100644 --- a/pmd-apex/src/main/resources/rulesets/apex/braces.xml +++ b/pmd-apex/src/main/resources/rulesets/apex/braces.xml @@ -3,147 +3,14 @@ <ruleset name="Braces" xmlns="http://pmd.sourceforge.net/ruleset/2.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 http://pmd.sourceforge.net/ruleset_2_0_0.xsd"> + xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd"> <description> The Braces ruleset contains rules regarding the use and placement of braces. </description> - <rule name="IfStmtsMustUseBraces" - language="apex" - since="5.6.0" - message="Avoid using if statements without curly braces" - class="net.sourceforge.pmd.lang.apex.rule.ApexXPathRule" - externalInfoUrl="${pmd.website.baseurl}/pmd_rules_apex_braces.html#ifstmtsmustusebraces"> - <description> -Avoid using if statements without using braces to surround the code block. If the code -formatting or indentation is lost then it becomes difficult to separate the code being -controlled from the rest. - </description> - <priority>3</priority> - <properties> - <property name="xpath"> - <value> -<![CDATA[ -//IfBlockStatement/BlockStatement[@CurlyBrace='false'] -]]> - </value> - </property> - </properties> - <example> - <![CDATA[ -if (foo) // not recommended - x++; - -if (foo) { // preferred approach - x++; -} -]]> - </example> - </rule> - - <rule name="WhileLoopsMustUseBraces" - language="apex" - since="5.6.0" - message="Avoid using 'while' statements without curly braces" - class="net.sourceforge.pmd.lang.apex.rule.ApexXPathRule" - externalInfoUrl="${pmd.website.baseurl}/pmd_rules_apex_braces.html#whileloopsmustusebraces"> - <description> -Avoid using 'while' statements without using braces to surround the code block. If the code -formatting or indentation is lost then it becomes difficult to separate the code being -controlled from the rest. - </description> - <priority>3</priority> - <properties> - <property name="xpath"> - <value> -<![CDATA[ -//WhileLoopStatement/BlockStatement[@CurlyBrace='false'] -]]> - </value> - </property> - </properties> - <example> -<![CDATA[ -while (true) // not recommended - x++; - -while (true) { // preferred approach - x++; -} -]]> - </example> - </rule> - - <rule name="IfElseStmtsMustUseBraces" - language="apex" - since="5.6.0" - message="Avoid using 'if...else' statements without curly braces" - class="net.sourceforge.pmd.lang.apex.rule.ApexXPathRule" - externalInfoUrl="${pmd.website.baseurl}/pmd_rules_apex_braces.html#ifelsestmtsmustusebraces"> - <description> -Avoid using if..else statements without using surrounding braces. If the code formatting -or indentation is lost then it becomes difficult to separate the code being controlled -from the rest. - </description> - <priority>3</priority> - <properties> - <property name="xpath"> - <value> -<![CDATA[ -//IfBlockStatement/BlockStatement[@CurlyBrace='false'][count(child::*) > 0] -| -//IfElseBlockStatement/BlockStatement[@CurlyBrace='false'][count(child::*) > 0] -]]> - </value> - </property> - </properties> - <example> -<![CDATA[ -// this is OK -if (foo) x++; - -// but this is not -if (foo) - x = x+1; -else - x = x-1; -]]> - </example> - </rule> - - <rule name="ForLoopsMustUseBraces" - language="apex" - since="5.6.0" - message="Avoid using 'for' statements without curly braces" - class="net.sourceforge.pmd.lang.apex.rule.ApexXPathRule" - externalInfoUrl="${pmd.website.baseurl}/pmd_rules_apex_braces.html#forloopsmustusebraces"> - <description> -Avoid using 'for' statements without using surrounding braces. If the code formatting or -indentation is lost then it becomes difficult to separate the code being controlled -from the rest. - </description> - <priority>3</priority> - <properties> - <property name="xpath"> - <value> -<![CDATA[ -//ForLoopStatement/BlockStatement[@CurlyBrace='false'] -| -//ForEachStatement/BlockStatement[@CurlyBrace='false'] -]]> - </value> - </property> - </properties> - <example> -<![CDATA[ -for (int i = 0; i < 42; i++) // not recommended - foo(); - -for (int i = 0; i < 42; i++) { // preferred approach - foo(); -} -]]> - </example> - </rule> + <rule ref="category/apex/codestyle.xml/ForLoopsMustUseBraces" deprecated="true" /> + <rule ref="category/apex/codestyle.xml/IfElseStmtsMustUseBraces" deprecated="true" /> + <rule ref="category/apex/codestyle.xml/IfStmtsMustUseBraces" deprecated="true" /> + <rule ref="category/apex/codestyle.xml/WhileLoopsMustUseBraces" deprecated="true" /> </ruleset> diff --git a/pmd-apex/src/main/resources/rulesets/apex/complexity.xml b/pmd-apex/src/main/resources/rulesets/apex/complexity.xml index 881b89fa199..594a7bd626b 100644 --- a/pmd-apex/src/main/resources/rulesets/apex/complexity.xml +++ b/pmd-apex/src/main/resources/rulesets/apex/complexity.xml @@ -2,289 +2,20 @@ <ruleset name="Complexity" xmlns="http://pmd.sourceforge.net/ruleset/2.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 http://pmd.sourceforge.net/ruleset_2_0_0.xsd"> + xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd"> <description> The Complexity ruleset contains rules that find problems related to code size or complexity. </description> - <rule name="AvoidDeeplyNestedIfStmts" - since="5.5.0" - message="Deeply nested if..then statements are hard to read" - class="net.sourceforge.pmd.lang.apex.rule.complexity.AvoidDeeplyNestedIfStmtsRule" - externalInfoUrl="${pmd.website.baseurl}/pmd_rules_apex_complexity.html#avoiddeeplynestedifstmts"> - <description> -Avoid creating deeply nested if-then statements since they are harder to read and error-prone to maintain. - </description> - <priority>3</priority> - <example> -<![CDATA[ -public class Foo { - public void bar(Integer x, Integer y, Integer z) { - if (x>y) { - if (y>z) { - if (z==x) { - // !! too deep - } - } - } - } -} -]]> - </example> - </rule> - - <rule name="ExcessiveParameterList" - since="5.5.0" - message="Avoid long parameter lists." - class="net.sourceforge.pmd.lang.apex.rule.complexity.ExcessiveParameterListRule" - externalInfoUrl="${pmd.website.baseurl}/pmd_rules_apex_complexity.html#excessiveparameterlist"> - <description> -Methods with numerous parameters are a challenge to maintain, especially if most of them share the -same datatype. These situations usually denote the need for new objects to wrap the numerous parameters. - </description> - <priority>3</priority> - <example> -<![CDATA[ -// too many arguments liable to be mixed up -public void addPerson(int birthYear, int birthMonth, int birthDate, int height, int weight, int ssn) { - // ... -} -// preferred approach -public void addPerson(Date birthdate, BodyMeasurements measurements, int ssn) { - // ... -} -]]> - </example> - </rule> - - <rule name="ExcessiveClassLength" - since="5.5.0" - message="Avoid really long classes." - class="net.sourceforge.pmd.lang.apex.rule.complexity.ExcessiveClassLengthRule" - externalInfoUrl="${pmd.website.baseurl}/pmd_rules_apex_complexity.html#excessiveclasslength"> - <description> -Excessive class file lengths are usually indications that the class may be burdened with excessive -responsibilities that could be provided by external classes or functions. In breaking these methods -apart the code becomes more managable and ripe for reuse. - </description> - <priority>3</priority> - <example> -<![CDATA[ -public class Foo { - public void bar1() { - // 1000 lines of code - } - public void bar2() { - // 1000 lines of code - } - public void bar3() { - // 1000 lines of code - } - public void barN() { - // 1000 lines of code - } -} -]]> - </example> - </rule> - - <rule name="NcssMethodCount" - since="5.5.0" - message="The method ''{0}()'' has an NCSS line count of {1}" - class="net.sourceforge.pmd.lang.apex.rule.complexity.NcssMethodCountRule" - externalInfoUrl="${pmd.website.baseurl}/pmd_rules_apex_complexity.html#ncssmethodcount"> - <description> -This rule uses the NCSS (Non-Commenting Source Statements) algorithm to determine the number of lines -of code for a given method. NCSS ignores comments, and counts actual statements. Using this algorithm, -lines of code that are split are counted as one. - </description> - <priority>3</priority> - <example> -<![CDATA[ -public class Foo extends Bar { - //this method only has 1 NCSS lines - public Integer methd() { - super.methd(); - - - - return 1; - } -} -]]> - </example> - </rule> - - <rule name="NcssTypeCount" - since="5.5.0" - message="The type has an NCSS line count of {0}" - class="net.sourceforge.pmd.lang.apex.rule.complexity.NcssTypeCountRule" - externalInfoUrl="${pmd.website.baseurl}/pmd_rules_apex_complexity.html#ncsstypecount"> - <description> -This rule uses the NCSS (Non-Commenting Source Statements) algorithm to determine the number of lines -of code for a given type. NCSS ignores comments, and counts actual statements. Using this algorithm, -lines of code that are split are counted as one. - </description> - <priority>3</priority> - <example> -<![CDATA[ -//this class only has 6 NCSS lines -public class Foo extends Bar { - public Foo() { - super(); - - - - - - super.foo(); - } -} -]]> - </example> - </rule> - - <rule name="NcssConstructorCount" - since="5.5.0" - message="The constructor has an NCSS line count of {0}" - class="net.sourceforge.pmd.lang.apex.rule.complexity.NcssConstructorCountRule" - externalInfoUrl="${pmd.website.baseurl}/pmd_rules_apex_complexity.html#ncssconstructorcount"> - <description> -This rule uses the NCSS (Non-Commenting Source Statements) algorithm to determine the number of lines -of code for a given constructor. NCSS ignores comments, and counts actual statements. Using this algorithm, -lines of code that are split are counted as one. - </description> - <priority>3</priority> - <example> -<![CDATA[ -public class Foo extends Bar { - //this constructor only has 1 NCSS lines - public Foo() { - super(); - - - - - super.foo(); -} -} -]]> - </example> - </rule> - - <rule name="StdCyclomaticComplexity" - since="5.5.0" - message="The {0} ''{1}'' has a Standard Cyclomatic Complexity of {2}." - class="net.sourceforge.pmd.lang.apex.rule.complexity.StdCyclomaticComplexityRule" - externalInfoUrl="${pmd.website.baseurl}/pmd_rules_apex_complexity.html#stdcyclomaticcomplexity"> - <description> -Complexity directly affects maintenance costs is determined by the number of decision points in a method -plus one for the method entry. The decision points include 'if', 'while', 'for', and 'case labels' calls. -Generally, numbers ranging from 1-4 denote low complexity, 5-7 denote moderate complexity, 8-10 denote -high complexity, and 11+ is very high complexity. - </description> - <priority>3</priority> - <example> -<![CDATA[ -// This has a Cyclomatic Complexity = 12 -public class Foo { -1 public void example() { -2 if (a == b || (c == d && e == f)) { -3 if (a1 == b1) { - fiddle(); -4 } else if a2 == b2) { - fiddle(); - } else { - fiddle(); - } -5 } else if (c == d) { -6 while (c == d) { - fiddle(); - } -7 } else if (e == f) { -8 for (int n = 0; n < h; n++) { - fiddle(); - } - } else { - switch (z) { -9 case 1: - fiddle(); - break; -10 case 2: - fiddle(); - break; -11 case 3: - fiddle(); - break; -12 default: - fiddle(); - break; - } - } -} -]]> - </example> - </rule> - - <rule name="TooManyFields" - since="5.5.0" - message="Too many fields" - class="net.sourceforge.pmd.lang.apex.rule.complexity.TooManyFieldsRule" - externalInfoUrl="${pmd.website.baseurl}/pmd_rules_apex_complexity.html#toomanyfields"> - <description> -Classes that have too many fields can become unwieldy and could be redesigned to have fewer fields, -possibly through grouping related fields in new objects. For example, a class with individual -city/state/zip fields could park them within a single Address field. - </description> - <priority>3</priority> - <example> -<![CDATA[ -public class Person { - // too many separate fields - int birthYear; - int birthMonth; - int birthDate; - float height; - float weight; -} - -public class Person { - // this is more manageable - Date birthDate; - BodyMeasurements measurements; -} -]]> - </example> - </rule> - - <rule name="ExcessivePublicCount" - since="5.5.0" - message="This class has a bunch of public methods and attributes" - class="net.sourceforge.pmd.lang.apex.rule.complexity.ExcessivePublicCountRule" - externalInfoUrl="${pmd.website.baseurl}/pmd_rules_apex_complexity.html#excessivepubliccount"> - <description> -Classes with large numbers of public methods and attributes require disproportionate testing efforts -since combinational side effects grow rapidly and increase risk. Refactoring these classes into -smaller ones not only increases testability and reliability but also allows new variations to be -developed easily. - </description> - <priority>3</priority> - <example> -<![CDATA[ -public class Foo { - public String value; - public Bar something; - public Variable var; - // [... more more public attributes ...] - - public void doWork() {} - public void doMoreWork() {} - public void doWorkAgain() {} - // [... more more public methods ...] -} -]]> - </example> - </rule> + <rule ref="category/apex/design.xml/AvoidDeeplyNestedIfStmts" deprecated="true" /> + <rule ref="category/apex/design.xml/ExcessiveClassLength" deprecated="true" /> + <rule ref="category/apex/design.xml/ExcessiveParameterList" deprecated="true" /> + <rule ref="category/apex/design.xml/ExcessivePublicCount" deprecated="true" /> + <rule ref="category/apex/design.xml/NcssConstructorCount" deprecated="true" /> + <rule ref="category/apex/design.xml/NcssMethodCount" deprecated="true" /> + <rule ref="category/apex/design.xml/NcssTypeCount" deprecated="true" /> + <rule ref="category/apex/design.xml/StdCyclomaticComplexity" deprecated="true" /> + <rule ref="category/apex/design.xml/TooManyFields" deprecated="true" /> </ruleset> diff --git a/pmd-apex/src/main/resources/rulesets/apex/empty.xml b/pmd-apex/src/main/resources/rulesets/apex/empty.xml new file mode 100644 index 00000000000..73dad3a46ec --- /dev/null +++ b/pmd-apex/src/main/resources/rulesets/apex/empty.xml @@ -0,0 +1,18 @@ +<?xml version="1.0"?> + +<ruleset name="Empty Code" + xmlns="http://pmd.sourceforge.net/ruleset/2.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd"> + <description> +The Empty Code ruleset contains rules that find empty statements of any kind (empty method, +empty block statement, empty try or catch block,...). + </description> + + <rule ref="category/apex/errorprone.xml/EmptyCatchBlock" deprecated="true" /> + <rule ref="category/apex/errorprone.xml/EmptyIfStmt" deprecated="true" /> + <rule ref="category/apex/errorprone.xml/EmptyStatementBlock" deprecated="true" /> + <rule ref="category/apex/errorprone.xml/EmptyTryOrFinallyBlock" deprecated="true" /> + <rule ref="category/apex/errorprone.xml/EmptyWhileStmt" deprecated="true" /> + +</ruleset> diff --git a/pmd-apex/src/main/resources/rulesets/apex/metrics.xml b/pmd-apex/src/main/resources/rulesets/apex/metrics.xml index 99452747339..f8ee4a72547 100644 --- a/pmd-apex/src/main/resources/rulesets/apex/metrics.xml +++ b/pmd-apex/src/main/resources/rulesets/apex/metrics.xml @@ -1,66 +1,13 @@ <?xml version="1.0"?> <ruleset name="Metrics temporary ruleset" - xmlns="http://pmd.sourceforge.net/ruleset/3.0.0" + xmlns="http://pmd.sourceforge.net/ruleset/2.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/3.0.0 http://pmd.sourceforge.net/ruleset_3_0_0.xsd"> + xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd"> <description> These are rules which use the Metrics Framework to calculate metrics. </description> - <rule name="CyclomaticComplexity" - message="The {0} ''{1}'' has a{2} cyclomatic complexity of {3}." - since="6.0.0" - class="net.sourceforge.pmd.lang.apex.metrics.rule.CyclomaticComplexityRule" - metrics="true" - externalInfoUrl="${pmd.website.baseurl}/pmd_rules_apex_metrics.html#cyclomaticcomplexity"> - <description> - <![CDATA[ -The complexity of methods directly affects maintenance costs and readability. Concentrating too much decisional logic -in a single method makes its behaviour hard to read and change. - -Cyclomatic complexity assesses the complexity of a method by counting the number of decision points in a method, -plus one for the method entry. Decision points are places where the control flow jumps to another place in the -program. As such, they include all control flow statements, such as 'if', 'while', 'for', and 'case'. - -Generally, numbers ranging from 1-4 denote low complexity, 5-7 denote moderate complexity, 8-10 denote -high complexity, and 11+ is very high complexity. By default, this rule reports methods with a complexity >= 10. -Additionnally, classes with many methods of moderate complexity get reported as well once the total of their -methods' complexities reaches 40, even if none of the methods was directly reported. - -Reported methods should be broken down into several smaller methods. Reported classes should probably be broken down -into subcomponents. - ]]> - </description> - <priority>3</priority> - <example> - <![CDATA[ -public class Complicated { - public void example() { // This method has a cyclomatic complexity of 12 - int x = 0, y = 1, z = 2, t = 2; - boolean a = false, b = true, c = false, d = true; - if (a && b || b && d) { - if (y == z) { - x = 2; - } else if (y == t && !d) { - x = 2; - } else { - x = 2; - } - } else if (c && d) { - while (z < y) { - x = 2; - } - } else { - for (int n = 0; n < t; n++) { - x = 2; - } - } - } -} -]]> - </example> - </rule> - + <rule ref="category/apex/design.xml/CyclomaticComplexity" deprecated="true" /> </ruleset> diff --git a/pmd-apex/src/main/resources/rulesets/apex/performance.xml b/pmd-apex/src/main/resources/rulesets/apex/performance.xml index 89e47f739c8..347ca6bcbca 100644 --- a/pmd-apex/src/main/resources/rulesets/apex/performance.xml +++ b/pmd-apex/src/main/resources/rulesets/apex/performance.xml @@ -3,55 +3,13 @@ <ruleset name="Performance" xmlns="http://pmd.sourceforge.net/ruleset/2.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 http://pmd.sourceforge.net/ruleset_2_0_0.xsd"> + xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd"> <description> The Performance ruleset contains a collection of good practices which should be followed. </description> - <rule name="AvoidSoqlInLoops" - since="5.5.0" - message="Avoid Soql queries inside loops" - class="net.sourceforge.pmd.lang.apex.rule.performance.AvoidSoqlInLoopsRule" - externalInfoUrl="${pmd.website.baseurl}/pmd_rules_apex_performance.html#avoidsoqlinloops"> - <description> -New objects created within loops should be checked to see if they can created outside them and reused. - </description> - <priority>3</priority> - <example> -<![CDATA[ -public class Something { - public static void main( String as[] ) { - for (Integer i = 0; i < 10; i++) { - List<Account> accounts = [SELECT Id FROM Account]; - } - } -} -]]> - </example> - </rule> - - <rule name="AvoidDmlStatementsInLoops" - since="5.5.0" - message="Avoid DML statements inside loops" - class="net.sourceforge.pmd.lang.apex.rule.performance.AvoidDmlStatementsInLoopsRule" - externalInfoUrl="${pmd.website.baseurl}/pmd_rules_apex_performance.html#avoiddmlstatementsinloops"> - <description> -Avoid DML statements inside loops to avoid hitting the DML governor limit. Instead, try to batch up the data into a list and invoke your DML once on that list of data outside the loop. - </description> - <priority>3</priority> - <example> -<![CDATA[ -public class Something { - public void foo() { - for (Integer i = 0; i < 151; i++) { - Account account; - // ... - insert account; - } - } -} -]]> - </example> - </rule> + <rule ref="category/apex/performance.xml/AvoidDmlStatementsInLoops" deprecated="true" /> + <rule ref="category/apex/performance.xml/AvoidSoqlInLoops" deprecated="true" /> + <rule ref="category/apex/performance.xml/AvoidSoslInLoops" deprecated="true" /> </ruleset> diff --git a/pmd-apex/src/main/resources/rulesets/apex/ruleset.xml b/pmd-apex/src/main/resources/rulesets/apex/ruleset.xml index 768ecd89942..993f33a7724 100644 --- a/pmd-apex/src/main/resources/rulesets/apex/ruleset.xml +++ b/pmd-apex/src/main/resources/rulesets/apex/ruleset.xml @@ -1,9 +1,9 @@ <?xml version="1.0" encoding="UTF-8"?> -<ruleset xmlns="http://pmd.sourceforge.net/ruleset/2.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" name="Default ruleset used by the CodeClimate Engine for Salesforce.com Apex" xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 http://pmd.sourceforge.net/ruleset_2_0_0.xsd"> +<ruleset xmlns="http://pmd.sourceforge.net/ruleset/2.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" name="Default ruleset used by the CodeClimate Engine for Salesforce.com Apex" xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd"> <description>Default ruleset used by the Code Climate Engine for Salesforce.com Apex</description> - + <!-- COMPLEXITY --> - <rule ref="rulesets/apex/complexity.xml/ExcessiveClassLength" message="Avoid really long classes (lines of code)"> + <rule ref="category/apex/design.xml/ExcessiveClassLength" message="Avoid really long classes (lines of code)"> <priority>3</priority> <properties> <property name="minimum" value="1000" /> @@ -13,7 +13,7 @@ <property name="cc_block_highlighting" value="false" /> </properties> </rule> - <rule ref="rulesets/apex/complexity.xml/ExcessiveParameterList" message="Avoid long parameter lists"> + <rule ref="category/apex/design.xml/ExcessiveParameterList" message="Avoid long parameter lists"> <priority>3</priority> <properties> <property name="minimum" value="4" /> @@ -23,7 +23,7 @@ <property name="cc_block_highlighting" value="false" /> </properties> </rule> - <rule ref="rulesets/apex/complexity.xml/ExcessivePublicCount" message="This class has too many public methods and attributes"> + <rule ref="category/apex/design.xml/ExcessivePublicCount" message="This class has too many public methods and attributes"> <priority>3</priority> <properties> <property name="minimum" value="25" /> @@ -33,7 +33,7 @@ <property name="cc_block_highlighting" value="false" /> </properties> </rule> - <rule ref="rulesets/apex/complexity.xml/NcssConstructorCount" message="The constructor has an NCSS line count of {0}"> + <rule ref="category/apex/design.xml/NcssConstructorCount" message="The constructor has an NCSS line count of {0}"> <priority>3</priority> <properties> <property name="minimum" value="20" /> @@ -43,7 +43,7 @@ <property name="cc_block_highlighting" value="false" /> </properties> </rule> - <rule ref="rulesets/apex/complexity.xml/NcssMethodCount" message="The method {0}() has an NCSS line count of {1}"> + <rule ref="category/apex/design.xml/NcssMethodCount" message="The method {0}() has an NCSS line count of {1}"> <priority>3</priority> <properties> <property name="minimum" value="60" /> @@ -53,7 +53,7 @@ <property name="cc_block_highlighting" value="false" /> </properties> </rule> - <rule ref="rulesets/apex/complexity.xml/NcssTypeCount" message="The type has an NCSS line count of {0}"> + <rule ref="category/apex/design.xml/NcssTypeCount" message="The type has an NCSS line count of {0}"> <priority>3</priority> <properties> <property name="minimum" value="700" /> @@ -63,7 +63,7 @@ <property name="cc_block_highlighting" value="false" /> </properties> </rule> - <rule ref="rulesets/apex/complexity.xml/StdCyclomaticComplexity" message="The {0} ''{1}'' has a Standard Cyclomatic Complexity of {2}."> + <rule ref="category/apex/design.xml/StdCyclomaticComplexity" message="The {0} ''{1}'' has a Standard Cyclomatic Complexity of {2}."> <priority>3</priority> <properties> <property name="reportLevel" value="10" /> @@ -73,7 +73,7 @@ <property name="cc_block_highlighting" value="false" /> </properties> </rule> - <rule ref="rulesets/apex/complexity.xml/TooManyFields" message="Too many fields"> + <rule ref="category/apex/design.xml/TooManyFields" message="Too many fields"> <priority>3</priority> <properties> <property name="maxfields" value="20" /> @@ -83,7 +83,7 @@ <property name="cc_block_highlighting" value="false" /> </properties> </rule> - <rule ref="rulesets/apex/complexity.xml/AvoidDeeplyNestedIfStmts" message="Deeply nested if..else statements are hard to read"> + <rule ref="category/apex/design.xml/AvoidDeeplyNestedIfStmts" message="Deeply nested if..else statements are hard to read"> <priority>3</priority> <properties> <property name="problemDepth" value="4" /> @@ -93,9 +93,27 @@ <property name="cc_block_highlighting" value="false" /> </properties> </rule> - + <rule ref="category/apex/design.xml/CyclomaticComplexity"> + <priority>3</priority> + <properties> + <!-- relevant for Code Climate output only --> + <property name="cc_categories" value="Complexity" /> + <property name="cc_remediation_points_multiplier" value="200" /> + <property name="cc_block_highlighting" value="false" /> + </properties> + </rule> + <!-- PERFORMANCE --> - <rule ref="rulesets/apex/performance.xml/AvoidSoqlInLoops" message="Avoid Soql queries inside loops"> + <rule ref="category/apex/performance.xml/AvoidSoqlInLoops" message="Avoid Soql queries inside loops"> + <priority>3</priority> + <properties> + <!-- relevant for Code Climate output only --> + <property name="cc_categories" value="Performance" /> + <property name="cc_remediation_points_multiplier" value="150" /> + <property name="cc_block_highlighting" value="false" /> + </properties> + </rule> + <rule ref="category/apex/performance.xml/AvoidSoslInLoops" message="Avoid Sosl queries inside loops"> <priority>3</priority> <properties> <!-- relevant for Code Climate output only --> @@ -104,7 +122,7 @@ <property name="cc_block_highlighting" value="false" /> </properties> </rule> - <rule ref="rulesets/apex/performance.xml/AvoidDmlStatementsInLoops" message="Avoid DML Statements inside loops"> + <rule ref="category/apex/performance.xml/AvoidDmlStatementsInLoops" message="Avoid DML Statements inside loops"> <priority>3</priority> <properties> <!-- relevant for Code Climate output only --> @@ -113,7 +131,16 @@ <property name="cc_block_highlighting" value="false" /> </properties> </rule> - <rule ref="rulesets/apex/style.xml/AvoidLogicInTrigger" message="Avoid logic in triggers"> + <rule ref="category/apex/errorprone.xml/AvoidDirectAccessTriggerMap" message="Avoid directly accessing Trigger.old and Trigger.new"> + <priority>3</priority> + <properties> + <!-- relevant for Code Climate output only --> + <property name="cc_categories" value="Performance" /> + <property name="cc_remediation_points_multiplier" value="150" /> + <property name="cc_block_highlighting" value="false" /> + </properties> + </rule> + <rule ref="category/apex/bestpractices.xml/AvoidLogicInTrigger" message="Avoid logic in triggers"> <priority>3</priority> <properties> <!-- relevant for Code Climate output only --> @@ -122,7 +149,7 @@ <property name="cc_block_highlighting" value="false" /> </properties> </rule> - <rule ref="rulesets/apex/style.xml/AvoidGlobalModifier" message="Avoid using global modifier"> + <rule ref="category/apex/bestpractices.xml/AvoidGlobalModifier" message="Avoid using global modifier"> <priority>3</priority> <properties> <!-- relevant for Code Climate output only --> @@ -131,8 +158,26 @@ <property name="cc_block_highlighting" value="false" /> </properties> </rule> + <rule ref="category/apex/errorprone.xml/AvoidNonExistentAnnotations"> + <properties> + <!-- relevant for Code Climate output only --> + <property name="cc_categories" value="Style" /> + <property name="cc_remediation_points_multiplier" value="100" /> + <property name="cc_block_highlighting" value="false" /> + </properties> + </rule> + <rule ref="category/apex/errorprone.xml/AvoidHardcodingId" message="Avoid hardcoding ID's"> + <priority>3</priority> + <properties> + <!-- relevant for Code Climate output only --> + <property name="cc_categories" value="Security"/> + <property name="cc_remediation_points_multiplier" value="20"/> + <property name="cc_block_highlighting" value="false"/> + </properties> + </rule> + <!-- NAMING --> - <rule ref="rulesets/apex/style.xml/ClassNamingConventions" message="Class names should begin with an uppercase character"> + <rule ref="category/apex/codestyle.xml/ClassNamingConventions" message="Class names should begin with an uppercase character"> <priority>3</priority> <properties> <!-- relevant for Code Climate output only --> @@ -141,7 +186,7 @@ <property name="cc_block_highlighting" value="false" /> </properties> </rule> - <rule ref="rulesets/apex/style.xml/MethodNamingConventions" message="Method name does not begin with a lower case character."> + <rule ref="category/apex/codestyle.xml/MethodNamingConventions" message="Method name does not begin with a lower case character."> <priority>3</priority> <properties> <!-- relevant for Code Climate output only --> @@ -150,7 +195,7 @@ <property name="cc_block_highlighting" value="false" /> </properties> </rule> - <rule ref="rulesets/apex/style.xml/MethodWithSameNameAsEnclosingClass" message="Classes should not have non-constructor methods with the same name as the class"> + <rule ref="category/apex/errorprone.xml/MethodWithSameNameAsEnclosingClass" message="Classes should not have non-constructor methods with the same name as the class"> <priority>3</priority> <properties> <!-- relevant for Code Climate output only --> @@ -159,7 +204,7 @@ <property name="cc_block_highlighting" value="false" /> </properties> </rule> - <rule ref="rulesets/apex/style.xml/VariableNamingConventions" message="{0} variable {1} should begin with {2}"> + <rule ref="category/apex/codestyle.xml/VariableNamingConventions" message="{0} variable {1} should begin with {2}"> <priority>3</priority> <properties> <!-- relevant for Code Climate output only --> @@ -168,8 +213,9 @@ <property name="cc_block_highlighting" value="false" /> </properties> </rule> + <!-- TESTS --> - <rule ref="rulesets/apex/apexunit.xml/ApexUnitTestClassShouldHaveAsserts" message="Apex unit test classes should have at least one System.assert() or assertEquals() or AssertNotEquals() call"> + <rule ref="category/apex/bestpractices.xml/ApexUnitTestClassShouldHaveAsserts" message="Apex unit test classes should have at least one System.assert() or assertEquals() or AssertNotEquals() call"> <priority>3</priority> <properties> <!-- relevant for Code Climate output only --> @@ -178,7 +224,7 @@ <property name="cc_block_highlighting" value="false" /> </properties> </rule> - <rule ref="rulesets/apex/apexunit.xml/ApexUnitTestShouldNotUseSeeAllDataTrue" message="@isTest(seeAllData=true) should not be used in Apex unit tests because it opens up the existing database data for unexpected modification by tests"> + <rule ref="category/apex/bestpractices.xml/ApexUnitTestShouldNotUseSeeAllDataTrue" message="@isTest(seeAllData=true) should not be used in Apex unit tests because it opens up the existing database data for unexpected modification by tests"> <priority>3</priority> <properties> <!-- relevant for Code Climate output only --> @@ -187,8 +233,9 @@ <property name="cc_block_highlighting" value="false" /> </properties> </rule> + <!-- SECURITY --> - <rule ref="rulesets/apex/security.xml/ApexSharingViolations" message="Apex classes should declare a sharing model if DML or SOQL is used"> + <rule ref="category/apex/security.xml/ApexSharingViolations" message="Apex classes should declare a sharing model if DML or SOQL is used"> <priority>3</priority> <properties> <!-- relevant for Code Climate output only --> @@ -197,7 +244,7 @@ <property name="cc_block_highlighting" value="false" /> </properties> </rule> - <rule ref="rulesets/apex/security.xml/ApexInsecureEndpoint" message="Apex callouts should use encrypted communication channels"> + <rule ref="category/apex/security.xml/ApexInsecureEndpoint" message="Apex callouts should use encrypted communication channels"> <priority>3</priority> <properties> <!-- relevant for Code Climate output only --> @@ -206,7 +253,7 @@ <property name="cc_block_highlighting" value="false" /> </properties> </rule> - <rule ref="rulesets/apex/security.xml/ApexCSRF" message="Avoid making DML operations in Apex class constructor/init method"> + <rule ref="category/apex/security.xml/ApexCSRF" message="Avoid making DML operations in Apex class constructor/init method"> <priority>3</priority> <properties> <!-- relevant for Code Climate output only --> @@ -215,7 +262,7 @@ <property name="cc_block_highlighting" value="false" /> </properties> </rule> - <rule ref="rulesets/apex/security.xml/ApexOpenRedirect" message="Apex classes should safely redirect to a known location"> + <rule ref="category/apex/security.xml/ApexOpenRedirect" message="Apex classes should safely redirect to a known location"> <priority>3</priority> <properties> <!-- relevant for Code Climate output only --> @@ -224,7 +271,7 @@ <property name="cc_block_highlighting" value="false" /> </properties> </rule> - <rule ref="rulesets/apex/security.xml/ApexSOQLInjection" message="Apex classes should escape variables merged in DML query"> + <rule ref="category/apex/security.xml/ApexSOQLInjection" message="Apex classes should escape variables merged in DML query"> <priority>3</priority> <properties> <!-- relevant for Code Climate output only --> @@ -233,7 +280,7 @@ <property name="cc_block_highlighting" value="false" /> </properties> </rule> - <rule ref="rulesets/apex/security.xml/ApexXSSFromURLParam" message="Apex classes should escape Strings obtained from URL parameters"> + <rule ref="category/apex/security.xml/ApexXSSFromURLParam" message="Apex classes should escape Strings obtained from URL parameters"> <priority>3</priority> <properties> <!-- relevant for Code Climate output only --> @@ -242,7 +289,7 @@ <property name="cc_block_highlighting" value="false" /> </properties> </rule> - <rule ref="rulesets/apex/security.xml/ApexXSSFromEscapeFalse" message="Apex classes should escape addError strings"> + <rule ref="category/apex/security.xml/ApexXSSFromEscapeFalse" message="Apex classes should escape addError strings"> <priority>3</priority> <properties> <!-- relevant for Code Climate output only --> @@ -251,7 +298,7 @@ <property name="cc_block_highlighting" value="false" /> </properties> </rule> - <rule ref="rulesets/apex/security.xml/ApexBadCrypto" message="Apex Crypto should use random IV/key"> + <rule ref="category/apex/security.xml/ApexBadCrypto" message="Apex Crypto should use random IV/key"> <priority>3</priority> <properties> <!-- relevant for Code Climate output only --> @@ -260,7 +307,7 @@ <property name="cc_block_highlighting" value="false" /> </properties> </rule> - <rule ref="rulesets/apex/security.xml/ApexCRUDViolation" message="Validate CRUD permission before SOQL/DML operation"> + <rule ref="category/apex/security.xml/ApexCRUDViolation" message="Validate CRUD permission before SOQL/DML operation"> <priority>3</priority> <properties> <!-- relevant for Code Climate output only --> @@ -269,7 +316,7 @@ <property name="cc_block_highlighting" value="false"/> </properties> </rule> - <rule ref="rulesets/apex/security.xml/ApexDangerousMethods" message="Calling potentially dangerous method"> + <rule ref="category/apex/security.xml/ApexDangerousMethods" message="Calling potentially dangerous method"> <priority>3</priority> <properties> <!-- relevant for Code Climate output only --> @@ -277,8 +324,8 @@ <property name="cc_remediation_points_multiplier" value="50"/> <property name="cc_block_highlighting" value="false"/> </properties> - </rule> - <rule ref="rulesets/apex/security.xml/ApexSuggestUsingNamedCred" message="Consider using named credentials for authenticated callouts"> + </rule> + <rule ref="category/apex/security.xml/ApexSuggestUsingNamedCred" message="Consider using named credentials for authenticated callouts"> <priority>3</priority> <properties> <!-- relevant for Code Climate output only --> @@ -287,41 +334,111 @@ <property name="cc_block_highlighting" value="false"/> </properties> </rule> + <!-- BRACES --> - <rule ref="rulesets/apex/braces.xml/IfStmtsMustUseBraces" message="Avoid using if statements without curly braces"> - <priority>3</priority> - <properties> - <!-- relevant for Code Climate output only --> - <property name="cc_categories" value="Style" /> - <property name="cc_remediation_points_multiplier" value="5" /> - <property name="cc_block_highlighting" value="false" /> - </properties> - </rule> - <rule ref="rulesets/apex/braces.xml/WhileLoopsMustUseBraces" message="Avoid using 'while' statements without curly braces"> - <priority>3</priority> - <properties> - <!-- relevant for Code Climate output only --> - <property name="cc_categories" value="Style" /> - <property name="cc_remediation_points_multiplier" value="5" /> - <property name="cc_block_highlighting" value="false" /> - </properties> - </rule> - <rule ref="rulesets/apex/braces.xml/IfElseStmtsMustUseBraces" message="Avoid using 'if...else' statements without curly braces"> - <priority>3</priority> - <properties> - <!-- relevant for Code Climate output only --> - <property name="cc_categories" value="Style" /> - <property name="cc_remediation_points_multiplier" value="5" /> - <property name="cc_block_highlighting" value="false" /> - </properties> - </rule> - <rule ref="rulesets/apex/braces.xml/ForLoopsMustUseBraces" message="Avoid using 'for' statements without curly braces"> - <priority>3</priority> - <properties> - <!-- relevant for Code Climate output only --> - <property name="cc_categories" value="Style" /> - <property name="cc_remediation_points_multiplier" value="5" /> - <property name="cc_block_highlighting" value="false" /> - </properties> - </rule> -</ruleset> \ No newline at end of file + <rule ref="category/apex/codestyle.xml/IfStmtsMustUseBraces" message="Avoid using if statements without curly braces"> + <priority>3</priority> + <properties> + <!-- relevant for Code Climate output only --> + <property name="cc_categories" value="Style" /> + <property name="cc_remediation_points_multiplier" value="5" /> + <property name="cc_block_highlighting" value="false" /> + </properties> + </rule> + <rule ref="category/apex/codestyle.xml/WhileLoopsMustUseBraces" message="Avoid using 'while' statements without curly braces"> + <priority>3</priority> + <properties> + <!-- relevant for Code Climate output only --> + <property name="cc_categories" value="Style" /> + <property name="cc_remediation_points_multiplier" value="5" /> + <property name="cc_block_highlighting" value="false" /> + </properties> + </rule> + <rule ref="category/apex/codestyle.xml/IfElseStmtsMustUseBraces" message="Avoid using 'if...else' statements without curly braces"> + <priority>3</priority> + <properties> + <!-- relevant for Code Climate output only --> + <property name="cc_categories" value="Style" /> + <property name="cc_remediation_points_multiplier" value="5" /> + <property name="cc_block_highlighting" value="false" /> + </properties> + </rule> + <rule ref="category/apex/codestyle.xml/ForLoopsMustUseBraces" message="Avoid using 'for' statements without curly braces"> + <priority>3</priority> + <properties> + <!-- relevant for Code Climate output only --> + <property name="cc_categories" value="Style" /> + <property name="cc_remediation_points_multiplier" value="5" /> + <property name="cc_block_highlighting" value="false" /> + </properties> + </rule> + + <!-- EMPTY --> + <rule ref="category/apex/errorprone.xml/EmptyCatchBlock" message="Avoid empty catch blocks"> + <priority>3</priority> + <properties> + <!-- relevant for Code Climate output only --> + <property name="cc_categories" value="Style" /> + <property name="cc_remediation_points_multiplier" value="5" /> + <property name="cc_block_highlighting" value="false" /> + </properties> + </rule> + <rule ref="category/apex/errorprone.xml/EmptyIfStmt" message="Avoid empty 'if' statements"> + <priority>3</priority> + <properties> + <!-- relevant for Code Climate output only --> + <property name="cc_categories" value="Style" /> + <property name="cc_remediation_points_multiplier" value="5" /> + <property name="cc_block_highlighting" value="false" /> + </properties> + </rule> + <rule ref="category/apex/errorprone.xml/EmptyWhileStmt" message="Avoid empty 'while' statements"> + <priority>3</priority> + <properties> + <!-- relevant for Code Climate output only --> + <property name="cc_categories" value="Style" /> + <property name="cc_remediation_points_multiplier" value="5" /> + <property name="cc_block_highlighting" value="false" /> + </properties> + </rule> + <rule ref="category/apex/errorprone.xml/EmptyTryOrFinallyBlock" message="Avoid empty try or finally blocks"> + <priority>3</priority> + <properties> + <!-- relevant for Code Climate output only --> + <property name="cc_categories" value="Style" /> + <property name="cc_remediation_points_multiplier" value="5" /> + <property name="cc_block_highlighting" value="false" /> + </properties> + </rule> + <rule ref="category/apex/errorprone.xml/EmptyStatementBlock" message="Avoid empty block statements."> + <priority>3</priority> + <properties> + <!-- relevant for Code Climate output only --> + <property name="cc_categories" value="Style" /> + <property name="cc_remediation_points_multiplier" value="5" /> + <property name="cc_block_highlighting" value="false" /> + </properties> + </rule> + + <!-- STYLE --> + <rule ref="category/apex/codestyle.xml/OneDeclarationPerLine"> + <priority>3</priority> + <properties> + <!-- relevant for Code Climate output only --> + <property name="cc_categories" value="Style" /> + <property name="cc_remediation_points_multiplier" value="5" /> + <property name="cc_block_highlighting" value="false" /> + </properties> + </rule> + + <!-- DOCUMENTATION --> + <rule ref="category/apex/documentation.xml/ApexDoc" message="Document classes, methods, and properties that are public or global."> + <priority>3</priority> + <properties> + <!-- relevant for Code Climate output only --> + <property name="cc_categories" value="Style" /> + <property name="cc_remediation_points_multiplier" value="50" /> + <property name="cc_block_highlighting" value="false" /> + </properties> + </rule> +</ruleset> diff --git a/pmd-apex/src/main/resources/rulesets/apex/rulesets.properties b/pmd-apex/src/main/resources/rulesets/apex/rulesets.properties index 879f524f1dd..dab0624aee6 100644 --- a/pmd-apex/src/main/resources/rulesets/apex/rulesets.properties +++ b/pmd-apex/src/main/resources/rulesets/apex/rulesets.properties @@ -3,9 +3,15 @@ # rulesets.filenames=\ - rulesets/apex/apexunit.xml,\ - rulesets/apex/braces.xml,\ - rulesets/apex/complexity.xml,\ - rulesets/apex/performance.xml,\ - rulesets/apex/security.xml,\ - rulesets/apex/style.xml + category/apex/bestpractices.xml,\ + category/apex/codestyle.xml,\ + category/apex/design.xml,\ + category/apex/documentation.xml,\ + category/apex/errorprone.xml,\ + category/apex/performance.xml,\ + category/apex/security.xml + +# +# categories with no rules yet +# +#category/apex/multithreading.xml diff --git a/pmd-apex/src/main/resources/rulesets/apex/security.xml b/pmd-apex/src/main/resources/rulesets/apex/security.xml index 8f111016aca..998dc4e034f 100644 --- a/pmd-apex/src/main/resources/rulesets/apex/security.xml +++ b/pmd-apex/src/main/resources/rulesets/apex/security.xml @@ -2,274 +2,22 @@ <ruleset name="Security" xmlns="http://pmd.sourceforge.net/ruleset/2.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 http://pmd.sourceforge.net/ruleset_2_0_0.xsd"> + xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd"> <description> These rules deal with different security problems that can occur within Apex. </description> - <rule name="ApexSharingViolations" - since="5.5.3" - message="Apex classes should declare a sharing model if DML or SOQL/SOSL is used" - class="net.sourceforge.pmd.lang.apex.rule.security.ApexSharingViolationsRule" - externalInfoUrl="${pmd.website.baseurl}/pmd_rules_apex_security.html#apexsharingviolations"> - <description> -Detect classes declared without explicit sharing mode if DML methods are used. This -forces the developer to take access restrictions into account before modifying objects. - </description> - <priority>3</priority> - <example> -<![CDATA[ -public without sharing class Foo { - // DML operation here -} -]]> - </example> - </rule> - - <rule name="ApexOpenRedirect" - since="5.5.3" - message="Apex classes should safely redirect to a known location" - class="net.sourceforge.pmd.lang.apex.rule.security.ApexOpenRedirectRule" - externalInfoUrl="${pmd.website.baseurl}/pmd_rules_apex_security.html#apexopenredirect"> - <description> -Checks against redirects to user-controlled locations. This prevents attackers from -redirecting users to phishing sites. - </description> - <priority>3</priority> - <example> -<![CDATA[ -public without sharing class Foo { - String unsafeLocation = ApexPage.getCurrentPage().getParameters.get('url_param'); - PageReference page() { - return new PageReference(unsafeLocation); - } -} -]]> - </example> - </rule> - - <rule name="ApexInsecureEndpoint" - since="5.5.3" - message="Apex callouts should use encrypted communication channels" - class="net.sourceforge.pmd.lang.apex.rule.security.ApexInsecureEndpointRule" - externalInfoUrl="${pmd.website.baseurl}/pmd_rules_apex_security.html#apexinsecureendpoint"> - <description> -Checks against accessing endpoints under plain **http**. You should always use -**https** for security. - </description> - <priority>3</priority> - <example> -<![CDATA[ -public without sharing class Foo { - void foo() { - HttpRequest req = new HttpRequest(); - req.setEndpoint('http://localhost:com'); - } -} -]]> - </example> - </rule> - - <rule name="ApexXSSFromURLParam" - since="5.5.3" - message="Apex classes should escape/sanitize Strings obtained from URL parameters" - class="net.sourceforge.pmd.lang.apex.rule.security.ApexXSSFromURLParamRule" - externalInfoUrl="${pmd.website.baseurl}/pmd_rules_apex_security.html#apexxssfromurlparam"> - <description> -Makes sure that all values obtained from URL parameters are properly escaped / sanitized -to avoid XSS attacks. - </description> - <priority>3</priority> - <example> -<![CDATA[ -public without sharing class Foo { - String unescapedstring = ApexPage.getCurrentPage().getParameters.get('url_param'); - String usedLater = unescapedstring; -} -]]> - </example> - </rule> - - <rule name="ApexXSSFromEscapeFalse" - since="5.5.3" - message="Apex classes should escape Strings in error messages" - class="net.sourceforge.pmd.lang.apex.rule.security.ApexXSSFromEscapeFalseRule" - externalInfoUrl="${pmd.website.baseurl}/pmd_rules_apex_security.html#apexxssfromescapefalse"> - <description> -Reports on calls to `addError` with disabled escaping. The message passed to `addError` -will be displayed directly to the user in the UI, making it prime ground for XSS -attacks if unescaped. - </description> - <priority>3</priority> - <example> -<![CDATA[ -public without sharing class Foo { - Trigger.new[0].addError(vulnerableHTMLGoesHere, false); -} -]]> - </example> - </rule> - - <rule name="ApexBadCrypto" - since="5.5.3" - message="Apex classes should use random IV/key" - class="net.sourceforge.pmd.lang.apex.rule.security.ApexBadCryptoRule" - externalInfoUrl="${pmd.website.baseurl}/pmd_rules_apex_security.html#apexbadcrypto"> - <description> -The rule makes sure you are using randomly generated IVs and keys for `Crypto` calls. -Hard-wiring these values greatly compromises the security of encrypted data. - </description> - <priority>3</priority> - <example> -<![CDATA[ -public without sharing class Foo { - Blob hardCodedIV = Blob.valueOf('Hardcoded IV 123'); - Blob hardCodedKey = Blob.valueOf('0000000000000000'); - Blob data = Blob.valueOf('Data to be encrypted'); - Blob encrypted = Crypto.encrypt('AES128', hardCodedKey, hardCodedIV, data); -} -]]> - </example> - </rule> - - <rule name="ApexCSRF" - since="5.5.3" - message="Avoid making DML operations in Apex class constructor/init method" - class="net.sourceforge.pmd.lang.apex.rule.security.ApexCSRFRule" - externalInfoUrl="${pmd.website.baseurl}/pmd_rules_apex_security.html#apexcsrf"> - <description> -Check to avoid making DML operations in Apex class constructor/init method. This prevents -modification of the database just by accessing a page. - </description> - <priority>3</priority> - <example> -<![CDATA[ -public class Foo { - public init() { - insert data; - } - - public Foo() { - insert data; - } -} -]]> - </example> - </rule> - - <rule name="ApexSOQLInjection" - since="5.5.3" - message="Avoid untrusted/unescaped variables in DML query" - class="net.sourceforge.pmd.lang.apex.rule.security.ApexSOQLInjectionRule" - externalInfoUrl="${pmd.website.baseurl}/pmd_rules_apex_security.html#apexsoqlinjection"> - <description> -Detects the usage of untrusted / unescaped variables in DML queries. - </description> - <priority>3</priority> - <example> -<![CDATA[ -public class Foo { - public void test1(String t1) { - Database.query('SELECT Id FROM Account' + t1); - } -} -]]> - </example> - </rule> - - <rule name="ApexCRUDViolation" - since="5.5.3" - message="Validate CRUD permission before SOQL/DML operation" - class="net.sourceforge.pmd.lang.apex.rule.security.ApexCRUDViolationRule" - externalInfoUrl="${pmd.website.baseurl}/pmd_rules_apex_security.html#apexcrudviolation"> - <description> -The rule validates you are checking for access permissions before a SOQL/SOSL/DML operation. -Since Apex runs in system mode not having proper permissions checks results in escalation of -privilege and may produce runtime errors. This check forces you to handle such scenarios. - </description> - <priority>3</priority> - <example> -<![CDATA[ -public class Foo { - public Contact foo(String status, String ID) { - Contact c = [SELECT Status__c FROM Contact WHERE Id=:ID]; - - // Make sure we can update the database before even trying - if (!Schema.sObjectType.Contact.fields.Name.isUpdateable()) { - return null; - } - - c.Status__c = status; - update c; - return c; - } -} -]]> - </example> - </rule> - - <rule name="ApexDangerousMethods" - since="5.5.3" - message="Calling potentially dangerous method" - class="net.sourceforge.pmd.lang.apex.rule.security.ApexDangerousMethodsRule" - externalInfoUrl="${pmd.website.baseurl}/pmd_rules_apex_security.html#apexdangerousmethods"> - <description><![CDATA[ -Checks against calling dangerous methods. - -For the time being, it reports: - -* Against `FinancialForce`'s `Configuration.disableTriggerCRUDSecurity()`. Disabling CRUD security -opens the door to several attacks and requires manual validation, which is unreliable. -* Calling `System.debug` passing sensitive data as parameter, which could lead to exposure -of private data. -]]> - </description> - <priority>3</priority> - <example> -<![CDATA[ -public class Foo { - public Foo() { - Configuration.disableTriggerCRUDSecurity(); - } -} -]]> - </example> - </rule> - - <rule name="ApexSuggestUsingNamedCred" - since="5.5.3" - message="Suggest named credentials for authentication" - class="net.sourceforge.pmd.lang.apex.rule.security.ApexSuggestUsingNamedCredRule" - externalInfoUrl="${pmd.website.baseurl}/pmd_rules_apex_security.html#apexsuggestusingnamedcred"> - <description><![CDATA[ -Detects hardcoded credentials used in requests to an endpoint. - -You should refrain from hardcoding credentials: - * They are hard to mantain by being mixed in application code - * Particularly hard to update them when used from different classes - * Granting a developer access to the codebase means granting knowledge - of credentials, keeping a two-level access is not possible. - * Using different credentials for different environments is troublesome - and error-prone. - -Instead, you should use *Named Credentials* and a callout endpoint. - -For more information, you can check [this](https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_callouts_named_credentials.htm) -]]> - </description> - <priority>3</priority> - <example> -<![CDATA[ -public class Foo { - public void foo(String username, String password) { - Blob headerValue = Blob.valueOf(username + ':' + password); - String authorizationHeader = 'BASIC ' + EncodingUtil.base64Encode(headerValue); - req.setHeader('Authorization', authorizationHeader); - } -} - ]]> - </example> - </rule> + <rule ref="category/apex/security.xml/ApexBadCrypto" deprecated="true" /> + <rule ref="category/apex/security.xml/ApexCRUDViolation" deprecated="true" /> + <rule ref="category/apex/security.xml/ApexCSRF" deprecated="true" /> + <rule ref="category/apex/security.xml/ApexDangerousMethods" deprecated="true" /> + <rule ref="category/apex/security.xml/ApexInsecureEndpoint" deprecated="true" /> + <rule ref="category/apex/security.xml/ApexOpenRedirect" deprecated="true" /> + <rule ref="category/apex/security.xml/ApexSharingViolations" deprecated="true" /> + <rule ref="category/apex/security.xml/ApexSOQLInjection" deprecated="true" /> + <rule ref="category/apex/security.xml/ApexSuggestUsingNamedCred" deprecated="true" /> + <rule ref="category/apex/security.xml/ApexXSSFromEscapeFalse" deprecated="true" /> + <rule ref="category/apex/security.xml/ApexXSSFromURLParam" deprecated="true" /> </ruleset> diff --git a/pmd-apex/src/main/resources/rulesets/apex/style.xml b/pmd-apex/src/main/resources/rulesets/apex/style.xml index 7c86b03a410..00f1c198a28 100644 --- a/pmd-apex/src/main/resources/rulesets/apex/style.xml +++ b/pmd-apex/src/main/resources/rulesets/apex/style.xml @@ -3,139 +3,20 @@ <ruleset name="Style" xmlns="http://pmd.sourceforge.net/ruleset/2.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 http://pmd.sourceforge.net/ruleset_2_0_0.xsd"> + xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd"> <description> The Style Ruleset contains rules regarding preferred usage of names and identifiers. </description> - <rule name="VariableNamingConventions" - since="5.5.0" - message="{0} variable {1} should begin with {2}" - class="net.sourceforge.pmd.lang.apex.rule.style.VariableNamingConventionsRule" - externalInfoUrl="${pmd.website.baseurl}/pmd_rules_apex_style.html#variablenamingconventions"> - <description> -A variable naming conventions rule - customize this to your liking. Currently, it -checks for final variables that should be fully capitalized and non-final variables -that should not include underscores. - </description> - <priority>1</priority> - <example> -<![CDATA[ -public class Foo { - public static final Integer MY_NUM = 0; - public String myTest = ''; - DataModule dmTest = new DataModule(); -} -]]> - </example> - </rule> + <rule ref="category/apex/codestyle.xml/ClassNamingConventions" deprecated="true" /> + <rule ref="category/apex/codestyle.xml/MethodNamingConventions" deprecated="true" /> + <rule ref="category/apex/codestyle.xml/VariableNamingConventions" deprecated="true" /> - <rule name="MethodNamingConventions" - since="5.5.0" - message="Method name does not begin with a lower case character." - class="net.sourceforge.pmd.lang.apex.rule.style.MethodNamingConventionsRule" - externalInfoUrl="${pmd.website.baseurl}/pmd_rules_apex_style.html#methodnamingconventions"> - <description> -Method names should always begin with a lower case character, and should not contain underscores. - </description> - <priority>1</priority> - <example> -<![CDATA[ -public class Foo { - public void fooStuff() { - } -} -]]> - </example> - </rule> + <rule ref="category/apex/errorprone.xml/AvoidDirectAccessTriggerMap" deprecated="true" /> + <rule ref="category/apex/errorprone.xml/AvoidHardcodingId" deprecated="true" /> + <rule ref="category/apex/errorprone.xml/MethodWithSameNameAsEnclosingClass" deprecated="true" /> - <rule name="ClassNamingConventions" - since="5.5.0" - message="Class names should begin with an uppercase character" - class="net.sourceforge.pmd.lang.apex.rule.style.ClassNamingConventionsRule" - externalInfoUrl="${pmd.website.baseurl}/pmd_rules_apex_style.html#classnamingconventions"> - <description> -Class names should always begin with an upper case character. - </description> - <priority>1</priority> - <example> -<![CDATA[ -public class Foo {} -]]> - </example> - </rule> - - <rule name="MethodWithSameNameAsEnclosingClass" - since="5.5.0" - message="Classes should not have non-constructor methods with the same name as the class" - class="net.sourceforge.pmd.lang.apex.rule.style.MethodWithSameNameAsEnclosingClassRule" - externalInfoUrl="${pmd.website.baseurl}/pmd_rules_apex_style.html#methodwithsamenameasenclosingclass"> - <description> -Non-constructor methods should not have the same name as the enclosing class. - </description> - <priority>3</priority> - <example> -<![CDATA[ -public class MyClass { - // this is OK because it is a constructor - public MyClass() {} - // this is bad because it is a method - public void MyClass() {} -} -]]> - </example> - </rule> - - <rule name="AvoidLogicInTrigger" - since="5.5.0" - message="Avoid logic in triggers" - class="net.sourceforge.pmd.lang.apex.rule.style.AvoidLogicInTriggerRule" - externalInfoUrl="${pmd.website.baseurl}/pmd_rules_apex_style.html#avoidlogicintrigger"> - <description> -As triggers do not allow methods like regular classes they are less flexible and suited to apply good encapsulation style. -Therefore delegate the triggers work to a regular class (often called Trigger handler class). - -See more here: https://developer.salesforce.com/page/Trigger_Frameworks_and_Apex_Trigger_Best_Practices - </description> - <priority>3</priority> - <example> -<![CDATA[ -trigger Accounts on Account (before insert, before update, before delete, after insert, after update, after delete, after undelete) { - for(Account acc : Trigger.new) { - if(Trigger.isInsert) { - // ... - } - - // ... - - if(Trigger.isDelete) { - // ... - } - } -} -]]> - </example> - </rule> - - <rule name="AvoidGlobalModifier" - since="5.5.0" - message="Avoid using global modifier" - class="net.sourceforge.pmd.lang.apex.rule.style.AvoidGlobalModifierRule" - externalInfoUrl="${pmd.website.baseurl}/pmd_rules_apex_style.html#avoidglobalmodifier"> - <description> -Global classes should be avoided (especially in managed packages) as they can never be deleted or changed in signature. Always check twice if something needs to be global. -Many interfaces (e.g. Batch) required global modifiers in the past but don't require this anymore. Don't lock yourself in. - </description> - <priority>3</priority> - <example> -<![CDATA[ -global class Unchangeable { - global UndeletableType unchangable(UndeletableType param) { - // ... - } -} -]]> - </example> - </rule> + <rule ref="category/apex/bestpractices.xml/AvoidGlobalModifier" deprecated="true" /> + <rule ref="category/apex/bestpractices.xml/AvoidLogicInTrigger" deprecated="true" /> </ruleset> diff --git a/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/FooRule.java b/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/FooRule.java new file mode 100644 index 00000000000..19332eb45c6 --- /dev/null +++ b/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/FooRule.java @@ -0,0 +1,58 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.apex; + +import net.sourceforge.pmd.lang.apex.ast.ASTField; +import net.sourceforge.pmd.lang.apex.ast.ASTParameter; +import net.sourceforge.pmd.lang.apex.ast.ASTUserClass; +import net.sourceforge.pmd.lang.apex.ast.ASTVariableDeclaration; +import net.sourceforge.pmd.lang.apex.rule.AbstractApexRule; + +/** + * Sample rule that detect any node with an image of "Foo". Used for testing. + */ +public class FooRule extends AbstractApexRule { + + public FooRule() { + setMessage("No Foo allowed"); + } + + @Override + public Object visit(ASTUserClass c, Object ctx) { + if (c.getImage().equalsIgnoreCase("Foo")) { + addViolation(ctx, c); + } + return super.visit(c, ctx); + } + + @Override + public Object visit(ASTVariableDeclaration c, Object ctx) { + if (c.getImage().equalsIgnoreCase("Foo")) { + addViolation(ctx, c); + } + return super.visit(c, ctx); + } + + @Override + public Object visit(ASTField c, Object ctx) { + if (c.getImage().equalsIgnoreCase("Foo")) { + addViolation(ctx, c); + } + return super.visit(c, ctx); + } + + @Override + public Object visit(ASTParameter c, Object ctx) { + if (c.getImage().equalsIgnoreCase("Foo")) { + addViolation(ctx, c); + } + return super.visit(c, ctx); + } + + @Override + public String getName() { + return "NoFoo"; + } +} diff --git a/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/SuppressWarningsTest.java b/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/SuppressWarningsTest.java new file mode 100644 index 00000000000..a61670f4d19 --- /dev/null +++ b/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/SuppressWarningsTest.java @@ -0,0 +1,184 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.apex; + +import static org.junit.Assert.assertEquals; + +import org.junit.Test; + +import net.sourceforge.pmd.PMD; +import net.sourceforge.pmd.Report; +import net.sourceforge.pmd.lang.LanguageRegistry; +import net.sourceforge.pmd.lang.apex.ast.ASTUserClass; +import net.sourceforge.pmd.lang.apex.rule.AbstractApexRule; +import net.sourceforge.pmd.testframework.RuleTst; + +public class SuppressWarningsTest extends RuleTst { + + private static class BarRule extends AbstractApexRule { + @Override + public Object visit(ASTUserClass clazz, Object ctx) { + if (clazz.getImage().equalsIgnoreCase("bar")) { + addViolation(ctx, clazz); + } + return super.visit(clazz, ctx); + } + + @Override + public String getName() { + return "NoBar"; + } + } + + @Test + public void testClassLevelSuppression() { + Report rpt = new Report(); + runTestFromString(TEST1, new FooRule(), rpt, + LanguageRegistry.getLanguage(ApexLanguageModule.NAME).getVersion("35")); + assertEquals(0, rpt.size()); + runTestFromString(TEST2, new FooRule(), rpt, + LanguageRegistry.getLanguage(ApexLanguageModule.NAME).getVersion("35")); + assertEquals(0, rpt.size()); + } + + @Test + public void testInheritedSuppression() { + Report rpt = new Report(); + runTestFromString(TEST3, new FooRule(), rpt, + LanguageRegistry.getLanguage(ApexLanguageModule.NAME).getVersion("35")); + assertEquals(0, rpt.size()); + } + + @Test + public void testMethodLevelSuppression() { + Report rpt = new Report(); + runTestFromString(TEST4, new FooRule(), rpt, + LanguageRegistry.getLanguage(ApexLanguageModule.NAME).getVersion("35")); + assertEquals(1, rpt.size()); + } + + @Test + public void testConstructorLevelSuppression() { + Report rpt = new Report(); + runTestFromString(TEST5, new FooRule(), rpt, + LanguageRegistry.getLanguage(ApexLanguageModule.NAME).getVersion("35")); + assertEquals(0, rpt.size()); + } + + @Test + public void testFieldLevelSuppression() { + Report rpt = new Report(); + runTestFromString(TEST6, new FooRule(), rpt, + LanguageRegistry.getLanguage(ApexLanguageModule.NAME).getVersion("35")); + assertEquals(1, rpt.size()); + } + + @Test + public void testParameterLevelSuppression() { + Report rpt = new Report(); + runTestFromString(TEST7, new FooRule(), rpt, + LanguageRegistry.getLanguage(ApexLanguageModule.NAME).getVersion("35")); + assertEquals(1, rpt.size()); + } + + @Test + public void testLocalVariableLevelSuppression() { + Report rpt = new Report(); + runTestFromString(TEST8, new FooRule(), rpt, + LanguageRegistry.getLanguage(ApexLanguageModule.NAME).getVersion("35")); + assertEquals(1, rpt.size()); + } + + @Test + public void testSpecificSuppression() { + Report rpt = new Report(); + runTestFromString(TEST9, new FooRule(), rpt, + LanguageRegistry.getLanguage(ApexLanguageModule.NAME).getVersion("35")); + assertEquals(1, rpt.size()); + } + + @Test + public void testSpecificSuppressionMulitpleValues() { + Report rpt = new Report(); + runTestFromString(TEST9_MULTIPLE_VALUES, new FooRule(), rpt, + LanguageRegistry.getLanguage(ApexLanguageModule.NAME).getVersion("35")); + assertEquals(0, rpt.size()); + } + + @Test + public void testNoSuppressionBlank() { + Report rpt = new Report(); + runTestFromString(TEST10, new FooRule(), rpt, + LanguageRegistry.getLanguage(ApexLanguageModule.NAME).getVersion("35")); + assertEquals(2, rpt.size()); + } + + @Test + public void testNoSuppressionSomethingElseS() { + Report rpt = new Report(); + runTestFromString(TEST11, new FooRule(), rpt, + LanguageRegistry.getLanguage(ApexLanguageModule.NAME).getVersion("35")); + assertEquals(2, rpt.size()); + } + + @Test + public void testSuppressAll() { + Report rpt = new Report(); + runTestFromString(TEST12, new FooRule(), rpt, + LanguageRegistry.getLanguage(ApexLanguageModule.NAME).getVersion("35")); + assertEquals(0, rpt.size()); + } + + @Test + public void testSpecificSuppressionAtTopLevel() { + Report rpt = new Report(); + runTestFromString(TEST13, new BarRule(), rpt, + LanguageRegistry.getLanguage(ApexLanguageModule.NAME).getVersion("35")); + assertEquals(0, rpt.size()); + } + + private static final String TEST1 = "@SuppressWarnings('PMD')" + PMD.EOL + "public class Foo {}"; + + private static final String TEST2 = "@SuppressWarnings('PMD')" + PMD.EOL + "public class Foo {" + PMD.EOL + + " void bar() {" + PMD.EOL + " Integer foo;" + PMD.EOL + " }" + PMD.EOL + "}"; + + private static final String TEST3 = "public class Baz {" + PMD.EOL + " @SuppressWarnings('PMD')" + PMD.EOL + + " public class Bar {" + PMD.EOL + " void bar() {" + PMD.EOL + " Integer foo;" + PMD.EOL + " }" + PMD.EOL + + " }" + PMD.EOL + "}"; + + private static final String TEST4 = "public class Foo {" + PMD.EOL + " @SuppressWarnings('PMD')" + PMD.EOL + + " void bar() {" + PMD.EOL + " Integer foo;" + PMD.EOL + " }" + PMD.EOL + "}"; + + private static final String TEST5 = "public class Bar {" + PMD.EOL + " @SuppressWarnings('PMD')" + PMD.EOL + + " public Bar() {" + PMD.EOL + " Integer foo;" + PMD.EOL + " }" + PMD.EOL + "}"; + + private static final String TEST6 = "public class Bar {" + PMD.EOL + " @SuppressWarnings('PMD')" + PMD.EOL + + " Integer foo;" + PMD.EOL + " void bar() {" + PMD.EOL + " Integer foo;" + PMD.EOL + " }" + PMD.EOL + "}"; + + private static final String TEST7 = "public class Bar {" + PMD.EOL + " Integer foo;" + PMD.EOL + + " void bar(@SuppressWarnings('PMD') Integer foo) {}" + PMD.EOL + "}"; + + private static final String TEST8 = "public class Bar {" + PMD.EOL + " Integer foo;" + PMD.EOL + " void bar() {" + + PMD.EOL + " @SuppressWarnings('PMD') Integer foo;" + PMD.EOL + " }" + PMD.EOL + "}"; + + private static final String TEST9 = "public class Bar {" + PMD.EOL + " Integer foo;" + PMD.EOL + " void bar() {" + + PMD.EOL + " @SuppressWarnings('PMD.NoFoo') Integer foo;" + PMD.EOL + " }" + PMD.EOL + "}"; + + private static final String TEST9_MULTIPLE_VALUES = "@SuppressWarnings('PMD.NoFoo, PMD.NoBar')" + + PMD.EOL + "public class Bar {" + PMD.EOL + " Integer foo;" + PMD.EOL + " void bar() {" + PMD.EOL + + " Integer foo;" + PMD.EOL + " }" + PMD.EOL + "}"; + + private static final String TEST10 = "public class Bar {" + PMD.EOL + " Integer foo;" + PMD.EOL + " void bar() {" + + PMD.EOL + " @SuppressWarnings('') Integer foo;" + PMD.EOL + " }" + PMD.EOL + "}"; + + private static final String TEST11 = "public class Bar {" + PMD.EOL + " Integer foo;" + PMD.EOL + " void bar() {" + + PMD.EOL + " @SuppressWarnings('SomethingElse') Integer foo;" + PMD.EOL + " }" + PMD.EOL + "}"; + + private static final String TEST12 = "public class Bar {" + PMD.EOL + " @SuppressWarnings('all') Integer foo;" + + PMD.EOL + "}"; + + private static final String TEST13 = "@SuppressWarnings('PMD.NoBar')" + PMD.EOL + "public class Bar {" + PMD.EOL + + "}"; +} diff --git a/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/ast/ApexParserTest.java b/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/ast/ApexParserTest.java index 1e4498b82f2..c1fdd406df9 100644 --- a/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/ast/ApexParserTest.java +++ b/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/ast/ApexParserTest.java @@ -6,8 +6,10 @@ import static net.sourceforge.pmd.lang.apex.ast.ApexParserTestHelpers.parse; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; import java.io.File; +import java.nio.charset.StandardCharsets; import java.util.List; import org.apache.commons.io.FileUtils; @@ -76,6 +78,7 @@ private void assertLineNumbersForTestCode(ApexNode<Compilation> rootNode) { // BlockStatement - the whole method body Node blockStatement = method1.jjtGetChild(1); + assertTrue(((ASTBlockStatement) blockStatement).hasCurlyBrace()); assertPosition(blockStatement, 2, 27, 5, 5); // the expression ("System.out...") @@ -111,7 +114,7 @@ public void parsesRealWorldClasses() throws Exception { for (File file : fList) { if (file.isFile() && file.getName().endsWith(".cls")) { - String sourceCode = FileUtils.readFileToString(file); + String sourceCode = FileUtils.readFileToString(file, StandardCharsets.UTF_8); ApexNode<Compilation> rootNode = parse(sourceCode); Assert.assertNotNull(rootNode); } @@ -125,12 +128,13 @@ public void parsesRealWorldClasses() throws Exception { */ @Test public void stackOverflowDuringClassParsing() throws Exception { - String source = IOUtils.toString(ApexParserTest.class.getResourceAsStream("StackOverflowClass.cls")); + String source = IOUtils.toString(ApexParserTest.class.getResourceAsStream("StackOverflowClass.cls"), + StandardCharsets.UTF_8); ApexNode<Compilation> rootNode = parse(source); Assert.assertNotNull(rootNode); int count = visitPosition(rootNode, 0); - Assert.assertEquals(489, count); + Assert.assertEquals(427, count); } private int visitPosition(Node node, int count) { diff --git a/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/ast/ApexQualifiedNameTest.java b/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/ast/ApexQualifiedNameTest.java index a85430f8fdf..4bb620c6549 100644 --- a/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/ast/ApexQualifiedNameTest.java +++ b/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/ast/ApexQualifiedNameTest.java @@ -15,6 +15,7 @@ import apex.jorje.semantic.ast.compilation.Compilation; + /** * @author Clément Fournier */ @@ -69,9 +70,9 @@ public void testMethodWithArguments() { @Test public void testOverLoads() { ApexNode<Compilation> root = ApexParserTestHelpers.parse("public class Foo { " - + "String foo(String h) {} " - + "String foo(int c) {}" - + "String foo(Foo c) {}}"); + + "String foo(String h) {} " + + "String foo(int c) {}" + + "String foo(Foo c) {}}"); List<ASTMethod> methods = root.findDescendantsOfType(ASTMethod.class); @@ -83,4 +84,17 @@ public void testOverLoads() { } } } + + + @Test + public void testTrigger() { + ApexNode<Compilation> root = ApexParserTestHelpers.parse("trigger myAccountTrigger on Account (before insert, before update) {}"); + + + List<ASTMethod> methods = root.findDescendantsOfType(ASTMethod.class); + + for (ASTMethod m : methods) { + assertEquals("c__trigger.Account#myAccountTrigger", m.getQualifiedName().toString()); + } + } } diff --git a/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/metrics/ApexMetricsVisitorTest.java b/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/metrics/ApexMetricsVisitorTest.java deleted file mode 100644 index b16f610ddb8..00000000000 --- a/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/metrics/ApexMetricsVisitorTest.java +++ /dev/null @@ -1,69 +0,0 @@ -/** - * BSD-style license; for more info see http://pmd.sourceforge.net/license.html - */ - -package net.sourceforge.pmd.lang.apex.metrics; - -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; - -import java.io.IOException; - -import org.apache.commons.io.IOUtils; -import org.junit.Test; - -import net.sourceforge.pmd.lang.LanguageRegistry; -import net.sourceforge.pmd.lang.LanguageVersionHandler; -import net.sourceforge.pmd.lang.apex.ApexLanguageModule; -import net.sourceforge.pmd.lang.apex.ast.ASTMethod; -import net.sourceforge.pmd.lang.apex.ast.ApexNode; -import net.sourceforge.pmd.lang.apex.ast.ApexParserTest; -import net.sourceforge.pmd.lang.apex.ast.ApexParserTestHelpers; -import net.sourceforge.pmd.lang.apex.ast.ApexParserVisitorAdapter; -import net.sourceforge.pmd.lang.apex.metrics.signature.ApexOperationSigMask; - -import apex.jorje.semantic.ast.compilation.Compilation; - -/** - * @author Clément Fournier - */ -public class ApexMetricsVisitorTest extends ApexParserTest { - - @Test - public void testProjectMirrorNotNull() { - assertNotNull(ApexMetrics.getFacade().getProjectMirror()); - } - - - @Test - public void testOperationsAreThere() throws IOException { - ApexNode<Compilation> acu = parseAndVisitForString( - IOUtils.toString(ApexMetricsVisitorTest.class.getResourceAsStream("MetadataDeployController.cls"))); - - final ApexSignatureMatcher toplevel = ApexMetrics.getFacade().getProjectMirror(); - - final ApexOperationSigMask opMask = new ApexOperationSigMask(); - - // We could parse qnames from string but probably simpler to do that - acu.jjtAccept(new ApexParserVisitorAdapter() { - @Override - public Object visit(ASTMethod node, Object data) { - if (!node.getImage().matches("(<clinit>|<init>|clone)")) { - assertTrue(toplevel.hasMatchingSig(node.getQualifiedName(), opMask)); - } - - return data; - } - }, null); - } - - - static ApexNode<Compilation> parseAndVisitForString(String source) { - LanguageVersionHandler languageVersionHandler = LanguageRegistry.getLanguage(ApexLanguageModule.NAME) - .getDefaultVersion().getLanguageVersionHandler(); - ApexNode<Compilation> acu = ApexParserTestHelpers.parse(source); - languageVersionHandler.getSymbolFacade().start(acu); - languageVersionHandler.getMetricsVisitorFacade().start(acu); - return acu; - } -} diff --git a/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/metrics/ApexProjectMirrorTest.java b/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/metrics/ApexProjectMirrorTest.java index 8534858d5db..9c78229005a 100644 --- a/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/metrics/ApexProjectMirrorTest.java +++ b/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/metrics/ApexProjectMirrorTest.java @@ -4,11 +4,11 @@ package net.sourceforge.pmd.lang.apex.metrics; -import static net.sourceforge.pmd.lang.apex.metrics.ApexMetricsVisitorTest.parseAndVisitForString; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotEquals; import java.io.IOException; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.List; import java.util.Random; @@ -16,13 +16,18 @@ import org.apache.commons.io.IOUtils; import org.junit.Test; +import net.sourceforge.pmd.lang.LanguageRegistry; +import net.sourceforge.pmd.lang.LanguageVersionHandler; +import net.sourceforge.pmd.lang.apex.ApexLanguageModule; import net.sourceforge.pmd.lang.apex.ast.ASTMethod; import net.sourceforge.pmd.lang.apex.ast.ASTUserClass; import net.sourceforge.pmd.lang.apex.ast.ASTUserClassOrInterface; import net.sourceforge.pmd.lang.apex.ast.ApexNode; +import net.sourceforge.pmd.lang.apex.ast.ApexParserTestHelpers; import net.sourceforge.pmd.lang.apex.ast.ApexParserVisitorAdapter; import net.sourceforge.pmd.lang.apex.metrics.impl.AbstractApexClassMetric; import net.sourceforge.pmd.lang.apex.metrics.impl.AbstractApexOperationMetric; +import net.sourceforge.pmd.lang.apex.multifile.ApexMultifileVisitorTest; import net.sourceforge.pmd.lang.metrics.MetricKey; import net.sourceforge.pmd.lang.metrics.MetricKeyUtil; import net.sourceforge.pmd.lang.metrics.MetricMemoizer; @@ -43,7 +48,8 @@ public class ApexProjectMirrorTest { static { try { acu = parseAndVisitForString( - IOUtils.toString(ApexMetricsVisitorTest.class.getResourceAsStream("MetadataDeployController.cls"))); + IOUtils.toString(ApexMultifileVisitorTest.class.getResourceAsStream("MetadataDeployController.cls"), + StandardCharsets.UTF_8)); } catch (IOException ioe) { // Should definitely not happen } @@ -104,6 +110,14 @@ public Object visit(ASTUserClass node, Object data) { } + static ApexNode<Compilation> parseAndVisitForString(String source) { + LanguageVersionHandler languageVersionHandler = LanguageRegistry.getLanguage(ApexLanguageModule.NAME) + .getDefaultVersion().getLanguageVersionHandler(); + ApexNode<Compilation> acu = ApexParserTestHelpers.parse(source); + languageVersionHandler.getSymbolFacade().start(acu); + return acu; + } + private class RandomOperationMetric extends AbstractApexOperationMetric { private Random random = new Random(); @@ -126,4 +140,5 @@ public double computeFor(ASTUserClassOrInterface<?> node, MetricOptions options) } } + } diff --git a/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/multifile/ApexMultifileVisitorTest.java b/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/multifile/ApexMultifileVisitorTest.java new file mode 100644 index 00000000000..2add9a6c3be --- /dev/null +++ b/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/multifile/ApexMultifileVisitorTest.java @@ -0,0 +1,72 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.apex.multifile; + +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; + +import org.apache.commons.io.IOUtils; +import org.junit.Test; + +import net.sourceforge.pmd.lang.LanguageRegistry; +import net.sourceforge.pmd.lang.LanguageVersionHandler; +import net.sourceforge.pmd.lang.apex.ApexLanguageModule; +import net.sourceforge.pmd.lang.apex.ast.ASTMethod; +import net.sourceforge.pmd.lang.apex.ast.ApexNode; +import net.sourceforge.pmd.lang.apex.ast.ApexParserTest; +import net.sourceforge.pmd.lang.apex.ast.ApexParserTestHelpers; +import net.sourceforge.pmd.lang.apex.ast.ApexParserVisitorAdapter; +import net.sourceforge.pmd.lang.apex.metrics.ApexSignatureMatcher; +import net.sourceforge.pmd.lang.apex.metrics.signature.ApexOperationSigMask; + +import apex.jorje.semantic.ast.compilation.Compilation; + +/** + * @author Clément Fournier + */ +public class ApexMultifileVisitorTest extends ApexParserTest { + + @Test + public void testProjectMirrorNotNull() { + assertNotNull(ApexProjectMirror.INSTANCE); + } + + + @Test + public void testOperationsAreThere() throws IOException { + ApexNode<Compilation> acu = parseAndVisitForString( + IOUtils.toString(ApexMultifileVisitorTest.class.getResourceAsStream("MetadataDeployController.cls"), + StandardCharsets.UTF_8)); + + final ApexSignatureMatcher toplevel = ApexProjectMirror.INSTANCE; + + final ApexOperationSigMask opMask = new ApexOperationSigMask(); + + // We could parse qnames from string but probably simpler to do that + acu.jjtAccept(new ApexParserVisitorAdapter() { + @Override + public Object visit(ASTMethod node, Object data) { + if (!node.getImage().matches("(<clinit>|<init>|clone)")) { + assertTrue(toplevel.hasMatchingSig(node.getQualifiedName(), opMask)); + } + + return data; + } + }, null); + } + + + static ApexNode<Compilation> parseAndVisitForString(String source) { + LanguageVersionHandler languageVersionHandler = LanguageRegistry.getLanguage(ApexLanguageModule.NAME) + .getDefaultVersion().getLanguageVersionHandler(); + ApexNode<Compilation> acu = ApexParserTestHelpers.parse(source); + languageVersionHandler.getSymbolFacade().start(acu); + languageVersionHandler.getMultifileFacade().start(acu); + return acu; + } +} diff --git a/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/rule/apexunit/ApexUnitRulesTest.java b/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/rule/apexunit/ApexUnitRulesTest.java deleted file mode 100644 index 8b2222df8ec..00000000000 --- a/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/rule/apexunit/ApexUnitRulesTest.java +++ /dev/null @@ -1,21 +0,0 @@ -/** - * BSD-style license; for more info see http://pmd.sourceforge.net/license.html - */ - -package net.sourceforge.pmd.lang.apex.rule.apexunit; - -import net.sourceforge.pmd.testframework.SimpleAggregatorTst; - -/** - * @author a.subramanian - */ -public class ApexUnitRulesTest extends SimpleAggregatorTst { - - private static final String RULESET = "apex-apexunit"; - - @Override - public void setUp() { - addRule(RULESET, "ApexUnitTestClassShouldHaveAsserts"); - addRule(RULESET, "ApexUnitTestShouldNotUseSeeAllDataTrue"); - } -} diff --git a/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/rule/bestpractices/ApexUnitTestClassShouldHaveAssertsTest.java b/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/rule/bestpractices/ApexUnitTestClassShouldHaveAssertsTest.java new file mode 100644 index 00000000000..5c3d68226e0 --- /dev/null +++ b/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/rule/bestpractices/ApexUnitTestClassShouldHaveAssertsTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.apex.rule.bestpractices; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class ApexUnitTestClassShouldHaveAssertsTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/rule/bestpractices/ApexUnitTestShouldNotUseSeeAllDataTrueTest.java b/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/rule/bestpractices/ApexUnitTestShouldNotUseSeeAllDataTrueTest.java new file mode 100644 index 00000000000..00084bc52e6 --- /dev/null +++ b/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/rule/bestpractices/ApexUnitTestShouldNotUseSeeAllDataTrueTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.apex.rule.bestpractices; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class ApexUnitTestShouldNotUseSeeAllDataTrueTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/rule/bestpractices/AvoidGlobalModifierTest.java b/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/rule/bestpractices/AvoidGlobalModifierTest.java new file mode 100644 index 00000000000..6a1780bfc9d --- /dev/null +++ b/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/rule/bestpractices/AvoidGlobalModifierTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.apex.rule.bestpractices; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class AvoidGlobalModifierTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/rule/bestpractices/AvoidLogicInTriggerTest.java b/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/rule/bestpractices/AvoidLogicInTriggerTest.java new file mode 100644 index 00000000000..08cfa53ada6 --- /dev/null +++ b/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/rule/bestpractices/AvoidLogicInTriggerTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.apex.rule.bestpractices; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class AvoidLogicInTriggerTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/rule/braces/BracesRulesTest.java b/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/rule/braces/BracesRulesTest.java deleted file mode 100644 index 09cbb1cc89d..00000000000 --- a/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/rule/braces/BracesRulesTest.java +++ /dev/null @@ -1,20 +0,0 @@ -/** - * BSD-style license; for more info see http://pmd.sourceforge.net/license.html - */ - -package net.sourceforge.pmd.lang.apex.rule.braces; - -import net.sourceforge.pmd.testframework.SimpleAggregatorTst; - -public class BracesRulesTest extends SimpleAggregatorTst { - - private static final String RULESET = "apex-braces"; - - @Override - public void setUp() { - addRule(RULESET, "ForLoopsMustUseBraces"); - addRule(RULESET, "IfElseStmtsMustUseBraces"); - addRule(RULESET, "IfStmtsMustUseBraces"); - addRule(RULESET, "WhileLoopsMustUseBraces"); - } -} diff --git a/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/rule/codestyle/ClassNamingConventionsTest.java b/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/rule/codestyle/ClassNamingConventionsTest.java new file mode 100644 index 00000000000..6e44c82f667 --- /dev/null +++ b/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/rule/codestyle/ClassNamingConventionsTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.apex.rule.codestyle; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class ClassNamingConventionsTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/rule/codestyle/ForLoopsMustUseBracesTest.java b/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/rule/codestyle/ForLoopsMustUseBracesTest.java new file mode 100644 index 00000000000..32e64fa7c83 --- /dev/null +++ b/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/rule/codestyle/ForLoopsMustUseBracesTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.apex.rule.codestyle; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class ForLoopsMustUseBracesTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/rule/codestyle/IfElseStmtsMustUseBracesTest.java b/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/rule/codestyle/IfElseStmtsMustUseBracesTest.java new file mode 100644 index 00000000000..8c118c38d7b --- /dev/null +++ b/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/rule/codestyle/IfElseStmtsMustUseBracesTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.apex.rule.codestyle; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class IfElseStmtsMustUseBracesTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/rule/codestyle/IfStmtsMustUseBracesTest.java b/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/rule/codestyle/IfStmtsMustUseBracesTest.java new file mode 100644 index 00000000000..049d251fbd7 --- /dev/null +++ b/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/rule/codestyle/IfStmtsMustUseBracesTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.apex.rule.codestyle; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class IfStmtsMustUseBracesTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/rule/codestyle/MethodNamingConventionsTest.java b/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/rule/codestyle/MethodNamingConventionsTest.java new file mode 100644 index 00000000000..2be02e5acbf --- /dev/null +++ b/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/rule/codestyle/MethodNamingConventionsTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.apex.rule.codestyle; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class MethodNamingConventionsTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/rule/codestyle/OneDeclarationPerLineTest.java b/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/rule/codestyle/OneDeclarationPerLineTest.java new file mode 100644 index 00000000000..fa97125829b --- /dev/null +++ b/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/rule/codestyle/OneDeclarationPerLineTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.apex.rule.codestyle; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class OneDeclarationPerLineTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/rule/codestyle/VariableNamingConventionsTest.java b/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/rule/codestyle/VariableNamingConventionsTest.java new file mode 100644 index 00000000000..dbca24d0313 --- /dev/null +++ b/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/rule/codestyle/VariableNamingConventionsTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.apex.rule.codestyle; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class VariableNamingConventionsTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/rule/codestyle/WhileLoopsMustUseBracesTest.java b/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/rule/codestyle/WhileLoopsMustUseBracesTest.java new file mode 100644 index 00000000000..e5a005e7262 --- /dev/null +++ b/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/rule/codestyle/WhileLoopsMustUseBracesTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.apex.rule.codestyle; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class WhileLoopsMustUseBracesTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/rule/complexity/ComplexityRulesTest.java b/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/rule/complexity/ComplexityRulesTest.java deleted file mode 100644 index 21f66d18802..00000000000 --- a/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/rule/complexity/ComplexityRulesTest.java +++ /dev/null @@ -1,25 +0,0 @@ -/** - * BSD-style license; for more info see http://pmd.sourceforge.net/license.html - */ - -package net.sourceforge.pmd.lang.apex.rule.complexity; - -import net.sourceforge.pmd.testframework.SimpleAggregatorTst; - -public class ComplexityRulesTest extends SimpleAggregatorTst { - - private static final String RULESET = "apex-complexity"; - - @Override - public void setUp() { - addRule(RULESET, "AvoidDeeplyNestedIfStmts"); - addRule(RULESET, "ExcessiveClassLength"); - addRule(RULESET, "ExcessiveParameterList"); - addRule(RULESET, "ExcessivePublicCount"); - addRule(RULESET, "NcssConstructorCount"); - addRule(RULESET, "NcssMethodCount"); - addRule(RULESET, "NcssTypeCount"); - addRule(RULESET, "StdCyclomaticComplexity"); - addRule(RULESET, "TooManyFields"); - } -} diff --git a/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/rule/complexity/StdCyclomaticComplexityRuleTest.java b/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/rule/complexity/StdCyclomaticComplexityRuleTest.java deleted file mode 100644 index 7aadd87c141..00000000000 --- a/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/rule/complexity/StdCyclomaticComplexityRuleTest.java +++ /dev/null @@ -1,43 +0,0 @@ -/** - * BSD-style license; for more info see http://pmd.sourceforge.net/license.html - */ - -package net.sourceforge.pmd.lang.apex.rule.complexity; - -import java.io.StringReader; -import java.util.Arrays; - -import org.junit.Assert; -import org.junit.Test; - -import net.sourceforge.pmd.RuleContext; -import net.sourceforge.pmd.lang.LanguageRegistry; -import net.sourceforge.pmd.lang.LanguageVersion; -import net.sourceforge.pmd.lang.Parser; -import net.sourceforge.pmd.lang.ParserOptions; -import net.sourceforge.pmd.lang.apex.ApexLanguageModule; -import net.sourceforge.pmd.lang.ast.Node; - -public class StdCyclomaticComplexityRuleTest { - /** - * Make sure the entry stack is empty, if show classes complexity is - * disabled. - * - * @see <a href="https://sourceforge.net/p/pmd/bugs/1501/">bug #1501</a> - */ - @Test - public void entryStackMustBeEmpty() { - StdCyclomaticComplexityRule rule = new StdCyclomaticComplexityRule(); - rule.setProperty(StdCyclomaticComplexityRule.SHOW_CLASSES_COMPLEXITY_DESCRIPTOR, Boolean.FALSE); - - RuleContext ctx = new RuleContext(); - LanguageVersion javaLanguageVersion = LanguageRegistry.getLanguage(ApexLanguageModule.NAME).getDefaultVersion(); - ParserOptions parserOptions = javaLanguageVersion.getLanguageVersionHandler().getDefaultParserOptions(); - Parser parser = javaLanguageVersion.getLanguageVersionHandler().getParser(parserOptions); - Node node = parser.parse("test", new StringReader("public class SampleClass {}")); - - rule.apply(Arrays.asList(node), ctx); - - Assert.assertTrue(rule.entryStack.isEmpty()); - } -} diff --git a/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/rule/design/AvoidDeeplyNestedIfStmtsTest.java b/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/rule/design/AvoidDeeplyNestedIfStmtsTest.java new file mode 100644 index 00000000000..abf3e78e0cb --- /dev/null +++ b/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/rule/design/AvoidDeeplyNestedIfStmtsTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.apex.rule.design; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class AvoidDeeplyNestedIfStmtsTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/rule/design/CyclomaticComplexityTest.java b/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/rule/design/CyclomaticComplexityTest.java new file mode 100644 index 00000000000..d6673508874 --- /dev/null +++ b/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/rule/design/CyclomaticComplexityTest.java @@ -0,0 +1,17 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.apex.rule.design; + +import net.sourceforge.pmd.Rule; +import net.sourceforge.pmd.lang.apex.metrics.ApexMetricsHook; +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class CyclomaticComplexityTest extends PmdRuleTst { + @Override + protected Rule reinitializeRule(Rule rule) { + ApexMetricsHook.reset(); + return super.reinitializeRule(rule); + } +} diff --git a/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/rule/design/ExcessiveClassLengthTest.java b/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/rule/design/ExcessiveClassLengthTest.java new file mode 100644 index 00000000000..5cde5c180c3 --- /dev/null +++ b/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/rule/design/ExcessiveClassLengthTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.apex.rule.design; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class ExcessiveClassLengthTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/rule/design/ExcessiveParameterListTest.java b/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/rule/design/ExcessiveParameterListTest.java new file mode 100644 index 00000000000..d2263e88b6e --- /dev/null +++ b/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/rule/design/ExcessiveParameterListTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.apex.rule.design; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class ExcessiveParameterListTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/rule/design/ExcessivePublicCountTest.java b/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/rule/design/ExcessivePublicCountTest.java new file mode 100644 index 00000000000..20a52344b15 --- /dev/null +++ b/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/rule/design/ExcessivePublicCountTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.apex.rule.design; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class ExcessivePublicCountTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/rule/design/NcssConstructorCountTest.java b/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/rule/design/NcssConstructorCountTest.java new file mode 100644 index 00000000000..ec837f8518a --- /dev/null +++ b/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/rule/design/NcssConstructorCountTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.apex.rule.design; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class NcssConstructorCountTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/rule/design/NcssMethodCountTest.java b/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/rule/design/NcssMethodCountTest.java new file mode 100644 index 00000000000..8f9b35c018a --- /dev/null +++ b/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/rule/design/NcssMethodCountTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.apex.rule.design; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class NcssMethodCountTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/rule/design/NcssTypeCountTest.java b/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/rule/design/NcssTypeCountTest.java new file mode 100644 index 00000000000..3416a142c96 --- /dev/null +++ b/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/rule/design/NcssTypeCountTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.apex.rule.design; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class NcssTypeCountTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/rule/design/StdCyclomaticComplexityTest.java b/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/rule/design/StdCyclomaticComplexityTest.java new file mode 100644 index 00000000000..421d5c45e56 --- /dev/null +++ b/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/rule/design/StdCyclomaticComplexityTest.java @@ -0,0 +1,44 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.apex.rule.design; + +import java.io.StringReader; +import java.util.Arrays; + +import org.junit.Assert; +import org.junit.Test; + +import net.sourceforge.pmd.RuleContext; +import net.sourceforge.pmd.lang.LanguageRegistry; +import net.sourceforge.pmd.lang.LanguageVersion; +import net.sourceforge.pmd.lang.Parser; +import net.sourceforge.pmd.lang.ParserOptions; +import net.sourceforge.pmd.lang.apex.ApexLanguageModule; +import net.sourceforge.pmd.lang.ast.Node; +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class StdCyclomaticComplexityTest extends PmdRuleTst { + /** + * Make sure the entry stack is empty, if show classes complexity is + * disabled. + * + * @see <a href="https://sourceforge.net/p/pmd/bugs/1501/">bug #1501</a> + */ + @Test + public void entryStackMustBeEmpty() { + StdCyclomaticComplexityRule rule = new StdCyclomaticComplexityRule(); + rule.setProperty(StdCyclomaticComplexityRule.SHOW_CLASSES_COMPLEXITY_DESCRIPTOR, Boolean.FALSE); + + RuleContext ctx = new RuleContext(); + LanguageVersion javaLanguageVersion = LanguageRegistry.getLanguage(ApexLanguageModule.NAME).getDefaultVersion(); + ParserOptions parserOptions = javaLanguageVersion.getLanguageVersionHandler().getDefaultParserOptions(); + Parser parser = javaLanguageVersion.getLanguageVersionHandler().getParser(parserOptions); + Node node = parser.parse("test", new StringReader("public class SampleClass {}")); + + rule.apply(Arrays.asList(node), ctx); + + Assert.assertTrue(rule.entryStack.isEmpty()); + } +} diff --git a/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/rule/design/TooManyFieldsTest.java b/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/rule/design/TooManyFieldsTest.java new file mode 100644 index 00000000000..71becae7c6d --- /dev/null +++ b/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/rule/design/TooManyFieldsTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.apex.rule.design; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class TooManyFieldsTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/rule/documentation/ApexDocTest.java b/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/rule/documentation/ApexDocTest.java new file mode 100644 index 00000000000..93273a11ad9 --- /dev/null +++ b/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/rule/documentation/ApexDocTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.apex.rule.documentation; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class ApexDocTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/rule/errorprone/AvoidDirectAccessTriggerMapTest.java b/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/rule/errorprone/AvoidDirectAccessTriggerMapTest.java new file mode 100644 index 00000000000..fbe6cd530e1 --- /dev/null +++ b/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/rule/errorprone/AvoidDirectAccessTriggerMapTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.apex.rule.errorprone; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class AvoidDirectAccessTriggerMapTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/rule/errorprone/AvoidHardcodingIdTest.java b/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/rule/errorprone/AvoidHardcodingIdTest.java new file mode 100644 index 00000000000..51f261aec03 --- /dev/null +++ b/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/rule/errorprone/AvoidHardcodingIdTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.apex.rule.errorprone; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class AvoidHardcodingIdTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/rule/errorprone/AvoidNonExistentAnnotationsTest.java b/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/rule/errorprone/AvoidNonExistentAnnotationsTest.java new file mode 100644 index 00000000000..45139b9d0ab --- /dev/null +++ b/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/rule/errorprone/AvoidNonExistentAnnotationsTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.apex.rule.errorprone; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class AvoidNonExistentAnnotationsTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/rule/errorprone/EmptyCatchBlockTest.java b/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/rule/errorprone/EmptyCatchBlockTest.java new file mode 100644 index 00000000000..a6c0dc0cead --- /dev/null +++ b/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/rule/errorprone/EmptyCatchBlockTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.apex.rule.errorprone; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class EmptyCatchBlockTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/rule/errorprone/EmptyIfStmtTest.java b/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/rule/errorprone/EmptyIfStmtTest.java new file mode 100644 index 00000000000..cc8f0a982e0 --- /dev/null +++ b/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/rule/errorprone/EmptyIfStmtTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.apex.rule.errorprone; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class EmptyIfStmtTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/rule/errorprone/EmptyStatementBlockTest.java b/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/rule/errorprone/EmptyStatementBlockTest.java new file mode 100644 index 00000000000..7a39e1aba51 --- /dev/null +++ b/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/rule/errorprone/EmptyStatementBlockTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.apex.rule.errorprone; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class EmptyStatementBlockTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/rule/errorprone/EmptyTryOrFinallyBlockTest.java b/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/rule/errorprone/EmptyTryOrFinallyBlockTest.java new file mode 100644 index 00000000000..d2d24d91afd --- /dev/null +++ b/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/rule/errorprone/EmptyTryOrFinallyBlockTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.apex.rule.errorprone; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class EmptyTryOrFinallyBlockTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/rule/errorprone/EmptyWhileStmtTest.java b/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/rule/errorprone/EmptyWhileStmtTest.java new file mode 100644 index 00000000000..3a98d7a9cda --- /dev/null +++ b/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/rule/errorprone/EmptyWhileStmtTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.apex.rule.errorprone; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class EmptyWhileStmtTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/rule/errorprone/MethodWithSameNameAsEnclosingClassTest.java b/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/rule/errorprone/MethodWithSameNameAsEnclosingClassTest.java new file mode 100644 index 00000000000..87e1ce64319 --- /dev/null +++ b/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/rule/errorprone/MethodWithSameNameAsEnclosingClassTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.apex.rule.errorprone; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class MethodWithSameNameAsEnclosingClassTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/rule/metrics/MetricsRulesTest.java b/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/rule/metrics/MetricsRulesTest.java deleted file mode 100644 index ec2fae8091d..00000000000 --- a/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/rule/metrics/MetricsRulesTest.java +++ /dev/null @@ -1,31 +0,0 @@ -/** - * BSD-style license; for more info see http://pmd.sourceforge.net/license.html - */ - -package net.sourceforge.pmd.lang.apex.rule.metrics; - -import net.sourceforge.pmd.Rule; -import net.sourceforge.pmd.lang.apex.metrics.ApexMetricsHook; -import net.sourceforge.pmd.testframework.SimpleAggregatorTst; - -/** - * @author Clément Fournier - */ -public class MetricsRulesTest extends SimpleAggregatorTst { - - - private static final String RULESET = "apex-metrics"; - - - @Override - protected Rule reinitializeRule(Rule rule) { - ApexMetricsHook.reset(); - return super.reinitializeRule(rule); - } - - - @Override - public void setUp() { - addRule(RULESET, "CyclomaticComplexity"); - } -} diff --git a/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/rule/performance/AvoidDmlStatementsInLoopsTest.java b/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/rule/performance/AvoidDmlStatementsInLoopsTest.java new file mode 100644 index 00000000000..e63930e585a --- /dev/null +++ b/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/rule/performance/AvoidDmlStatementsInLoopsTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.apex.rule.performance; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class AvoidDmlStatementsInLoopsTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/rule/performance/AvoidSoqlInLoopsTest.java b/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/rule/performance/AvoidSoqlInLoopsTest.java new file mode 100644 index 00000000000..cec9d93bf47 --- /dev/null +++ b/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/rule/performance/AvoidSoqlInLoopsTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.apex.rule.performance; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class AvoidSoqlInLoopsTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/rule/performance/AvoidSoslInLoopsTest.java b/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/rule/performance/AvoidSoslInLoopsTest.java new file mode 100644 index 00000000000..ce3a33a7c89 --- /dev/null +++ b/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/rule/performance/AvoidSoslInLoopsTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.apex.rule.performance; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class AvoidSoslInLoopsTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/rule/performance/PerformanceRulesTest.java b/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/rule/performance/PerformanceRulesTest.java deleted file mode 100644 index c89531d0916..00000000000 --- a/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/rule/performance/PerformanceRulesTest.java +++ /dev/null @@ -1,18 +0,0 @@ -/** - * BSD-style license; for more info see http://pmd.sourceforge.net/license.html - */ - -package net.sourceforge.pmd.lang.apex.rule.performance; - -import net.sourceforge.pmd.testframework.SimpleAggregatorTst; - -public class PerformanceRulesTest extends SimpleAggregatorTst { - - private static final String RULESET = "apex-performance"; - - @Override - public void setUp() { - addRule(RULESET, "AvoidSoqlInLoops"); - addRule(RULESET, "AvoidDmlStatementsInLoops"); - } -} diff --git a/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/rule/security/ApexBadCryptoTest.java b/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/rule/security/ApexBadCryptoTest.java new file mode 100644 index 00000000000..05134ff78c5 --- /dev/null +++ b/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/rule/security/ApexBadCryptoTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.apex.rule.security; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class ApexBadCryptoTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/rule/security/ApexCRUDViolationTest.java b/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/rule/security/ApexCRUDViolationTest.java new file mode 100644 index 00000000000..f041ac9bb06 --- /dev/null +++ b/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/rule/security/ApexCRUDViolationTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.apex.rule.security; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class ApexCRUDViolationTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/rule/security/ApexCSRFTest.java b/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/rule/security/ApexCSRFTest.java new file mode 100644 index 00000000000..e81fb45180c --- /dev/null +++ b/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/rule/security/ApexCSRFTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.apex.rule.security; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class ApexCSRFTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/rule/security/ApexDangerousMethodsTest.java b/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/rule/security/ApexDangerousMethodsTest.java new file mode 100644 index 00000000000..74fe589b3d7 --- /dev/null +++ b/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/rule/security/ApexDangerousMethodsTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.apex.rule.security; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class ApexDangerousMethodsTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/rule/security/ApexInsecureEndpointTest.java b/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/rule/security/ApexInsecureEndpointTest.java new file mode 100644 index 00000000000..c118cbb916e --- /dev/null +++ b/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/rule/security/ApexInsecureEndpointTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.apex.rule.security; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class ApexInsecureEndpointTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/rule/security/ApexOpenRedirectTest.java b/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/rule/security/ApexOpenRedirectTest.java new file mode 100644 index 00000000000..5cf72744fa4 --- /dev/null +++ b/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/rule/security/ApexOpenRedirectTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.apex.rule.security; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class ApexOpenRedirectTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/rule/security/ApexSOQLInjectionTest.java b/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/rule/security/ApexSOQLInjectionTest.java new file mode 100644 index 00000000000..a9a619d3567 --- /dev/null +++ b/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/rule/security/ApexSOQLInjectionTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.apex.rule.security; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class ApexSOQLInjectionTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/rule/security/ApexSharingViolationsTest.java b/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/rule/security/ApexSharingViolationsTest.java new file mode 100644 index 00000000000..e083382550d --- /dev/null +++ b/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/rule/security/ApexSharingViolationsTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.apex.rule.security; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class ApexSharingViolationsTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/rule/security/ApexSuggestUsingNamedCredTest.java b/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/rule/security/ApexSuggestUsingNamedCredTest.java new file mode 100644 index 00000000000..231f15313e3 --- /dev/null +++ b/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/rule/security/ApexSuggestUsingNamedCredTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.apex.rule.security; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class ApexSuggestUsingNamedCredTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/rule/security/ApexXSSFromEscapeFalseTest.java b/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/rule/security/ApexXSSFromEscapeFalseTest.java new file mode 100644 index 00000000000..9acbd35642f --- /dev/null +++ b/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/rule/security/ApexXSSFromEscapeFalseTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.apex.rule.security; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class ApexXSSFromEscapeFalseTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/rule/security/ApexXSSFromURLParamTest.java b/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/rule/security/ApexXSSFromURLParamTest.java new file mode 100644 index 00000000000..a0f8bdc72d3 --- /dev/null +++ b/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/rule/security/ApexXSSFromURLParamTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.apex.rule.security; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class ApexXSSFromURLParamTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/rule/security/SecurityRulesTest.java b/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/rule/security/SecurityRulesTest.java deleted file mode 100644 index 703f112d862..00000000000 --- a/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/rule/security/SecurityRulesTest.java +++ /dev/null @@ -1,27 +0,0 @@ -/** - * BSD-style license; for more info see http://pmd.sourceforge.net/license.html - */ - -package net.sourceforge.pmd.lang.apex.rule.security; - -import net.sourceforge.pmd.testframework.SimpleAggregatorTst; - -public class SecurityRulesTest extends SimpleAggregatorTst { - - private static final String RULESET = "apex-security"; - - @Override - public void setUp() { - addRule(RULESET, "ApexBadCrypto"); - addRule(RULESET, "ApexXSSFromEscapeFalse"); - addRule(RULESET, "ApexXSSFromURLParam"); - addRule(RULESET, "ApexCSRF"); - addRule(RULESET, "ApexOpenRedirect"); - addRule(RULESET, "ApexSOQLInjection"); - addRule(RULESET, "ApexSharingViolations"); - addRule(RULESET, "ApexInsecureEndpoint"); - addRule(RULESET, "ApexCRUDViolation"); - addRule(RULESET, "ApexDangerousMethods"); - addRule(RULESET, "ApexSuggestUsingNamedCred"); - } -} diff --git a/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/rule/style/StyleRulesTest.java b/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/rule/style/StyleRulesTest.java deleted file mode 100644 index f16234d462a..00000000000 --- a/pmd-apex/src/test/java/net/sourceforge/pmd/lang/apex/rule/style/StyleRulesTest.java +++ /dev/null @@ -1,23 +0,0 @@ -/** - * BSD-style license; for more info see http://pmd.sourceforge.net/license.html - */ - -package net.sourceforge.pmd.lang.apex.rule.style; - -import net.sourceforge.pmd.testframework.SimpleAggregatorTst; - -public class StyleRulesTest extends SimpleAggregatorTst { - - private static final String RULESET = "apex-style"; - - @Override - public void setUp() { - addRule(RULESET, "AvoidGlobalModifier"); - addRule(RULESET, "AvoidLogicInTrigger"); - addRule(RULESET, "ClassNamingConventions"); - addRule(RULESET, "MethodNamingConventions"); - addRule(RULESET, "VariableNamingConventions"); - addRule(RULESET, "MethodWithSameNameAsEnclosingClass"); - - } -} diff --git a/pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/metrics/MetadataDeployController.cls b/pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/multifile/MetadataDeployController.cls similarity index 100% rename from pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/metrics/MetadataDeployController.cls rename to pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/multifile/MetadataDeployController.cls diff --git a/pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/rule/apexunit/xml/ApexUnitTestClassShouldHaveAsserts.xml b/pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/rule/bestpractices/xml/ApexUnitTestClassShouldHaveAsserts.xml similarity index 100% rename from pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/rule/apexunit/xml/ApexUnitTestClassShouldHaveAsserts.xml rename to pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/rule/bestpractices/xml/ApexUnitTestClassShouldHaveAsserts.xml diff --git a/pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/rule/apexunit/xml/ApexUnitTestShouldNotUseSeeAllDataTrue.xml b/pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/rule/bestpractices/xml/ApexUnitTestShouldNotUseSeeAllDataTrue.xml similarity index 100% rename from pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/rule/apexunit/xml/ApexUnitTestShouldNotUseSeeAllDataTrue.xml rename to pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/rule/bestpractices/xml/ApexUnitTestShouldNotUseSeeAllDataTrue.xml diff --git a/pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/rule/bestpractices/xml/AvoidGlobalModifier.xml b/pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/rule/bestpractices/xml/AvoidGlobalModifier.xml new file mode 100644 index 00000000000..28f5acf78cc --- /dev/null +++ b/pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/rule/bestpractices/xml/AvoidGlobalModifier.xml @@ -0,0 +1,91 @@ +<?xml version="1.0" encoding="UTF-8"?> + +<test-data + xmlns="http://pmd.sourceforge.net/rule-tests" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://pmd.sourceforge.net/rule-tests http://pmd.sourceforge.net/rule-tests_1_0_0.xsd"> + + <test-code> + <description>Global class</description> + <expected-problems>1</expected-problems> + <code><![CDATA[ +global class Foo { + +} + ]]></code> + </test-code> + + <test-code> + <description>Global interface</description> + <expected-problems>1</expected-problems> + <code><![CDATA[ +global interface Foo { + +} + ]]></code> + </test-code> + + <test-code> + <description>Global method</description> + <expected-problems>1</expected-problems> + <code><![CDATA[ +global class Foo { + global Integer bar() { + + } +} + ]]></code> + </test-code> + + <test-code> + <description>Global inner interface</description> + <expected-problems>1</expected-problems> + <code><![CDATA[ +global class Foo { + global interface Bar { { + + } +} + ]]></code> + </test-code> + + <test-code> + <description>#1348 [apex] AvoidGlobalModifierRule gives warning even when its a REST webservice - false positive</description> + <expected-problems>0</expected-problems> + <code><![CDATA[ +@RestResource(urlMapping = '/partners/submit/*') +global class Generic_LoanCreation { + @HttpPost + global static void createLoanApplication() { + } +} + ]]></code> + </test-code> + + <test-code> + <description>#1348 [apex] AvoidGlobalModifierRule gives warning even when its a SOAP webservice - false positive</description> + <expected-problems>0</expected-problems> + <code><![CDATA[ +global class Generic_LoanCreation { + webservice static void createLoanApplication() { + } +} + ]]></code> + </test-code> + + <test-code> + <description>Simple public and non-global class</description> + <expected-problems>0</expected-problems> + <code><![CDATA[ +public class myOuterClass { + // Additional myOuterClass code here + class myInnerClass { + // myInnerClass code here + } + + void myMethod() { + } +} + ]]></code> + </test-code> +</test-data> diff --git a/pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/rule/style/xml/AvoidLogicInTrigger.xml b/pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/rule/bestpractices/xml/AvoidLogicInTrigger.xml similarity index 100% rename from pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/rule/style/xml/AvoidLogicInTrigger.xml rename to pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/rule/bestpractices/xml/AvoidLogicInTrigger.xml diff --git a/pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/rule/style/xml/ClassNamingConventions.xml b/pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/rule/codestyle/xml/ClassNamingConventions.xml similarity index 100% rename from pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/rule/style/xml/ClassNamingConventions.xml rename to pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/rule/codestyle/xml/ClassNamingConventions.xml diff --git a/pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/rule/braces/xml/ForLoopsMustUseBraces.xml b/pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/rule/codestyle/xml/ForLoopsMustUseBraces.xml similarity index 100% rename from pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/rule/braces/xml/ForLoopsMustUseBraces.xml rename to pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/rule/codestyle/xml/ForLoopsMustUseBraces.xml diff --git a/pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/rule/braces/xml/IfElseStmtsMustUseBraces.xml b/pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/rule/codestyle/xml/IfElseStmtsMustUseBraces.xml similarity index 100% rename from pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/rule/braces/xml/IfElseStmtsMustUseBraces.xml rename to pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/rule/codestyle/xml/IfElseStmtsMustUseBraces.xml diff --git a/pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/rule/braces/xml/IfStmtsMustUseBraces.xml b/pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/rule/codestyle/xml/IfStmtsMustUseBraces.xml similarity index 100% rename from pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/rule/braces/xml/IfStmtsMustUseBraces.xml rename to pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/rule/codestyle/xml/IfStmtsMustUseBraces.xml diff --git a/pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/rule/style/xml/MethodNamingConventions.xml b/pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/rule/codestyle/xml/MethodNamingConventions.xml similarity index 100% rename from pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/rule/style/xml/MethodNamingConventions.xml rename to pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/rule/codestyle/xml/MethodNamingConventions.xml diff --git a/pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/rule/codestyle/xml/OneDeclarationPerLine.xml b/pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/rule/codestyle/xml/OneDeclarationPerLine.xml new file mode 100644 index 00000000000..7a1a36a987f --- /dev/null +++ b/pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/rule/codestyle/xml/OneDeclarationPerLine.xml @@ -0,0 +1,95 @@ +<?xml version="1.0" encoding="UTF-8"?> + +<test-data + xmlns="http://pmd.sourceforge.net/rule-tests" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://pmd.sourceforge.net/rule-tests http://pmd.sourceforge.net/rule-tests_1_0_0.xsd"> + + <test-code> + <description>one field declaration per statement</description> + <expected-problems>1</expected-problems> + <code><![CDATA[ +public class Foo { + Integer a, b; +} + ]]></code> + </test-code> + + <test-code> + <description>one variable declaration per statement</description> + <expected-problems>1</expected-problems> + <code><![CDATA[ +public class Foo { + void bar() { + Integer a, b; + } +} + ]]></code> + </test-code> + + + <test-code> + <description>all is well</description> + <expected-problems>0</expected-problems> + <code><![CDATA[ +public class Foo { + Integer a; + Integer b; + void bar() { + Integer c; + Integer d; + } +} + ]]></code> + </test-code> + + <test-code> + <description>one variable declaration per line, ok by default</description> + <expected-problems>0</expected-problems> + <code><![CDATA[ +public class Foo { + void bar() { + Integer a, + b; + } +} + ]]></code> + </test-code> + + <test-code> + <description>one field declaration per line, ok by default</description> + <expected-problems>0</expected-problems> + <code><![CDATA[ +public class Foo { + Integer a, + b; +} + ]]></code> + </test-code> + + <test-code> + <description>one variable declaration per line, not ok when strictMode</description> + <rule-property name="strictMode">true</rule-property> + <expected-problems>1</expected-problems> + <code><![CDATA[ +public class Foo { + void bar() { + Integer a, + b; + } +} + ]]></code> + </test-code> + + <test-code> + <description>one field declaration per line, not ok when strictMode</description> + <rule-property name="strictMode">true</rule-property> + <expected-problems>1</expected-problems> + <code><![CDATA[ +public class Foo { + Integer a, + b; +} + ]]></code> + </test-code> +</test-data> diff --git a/pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/rule/style/xml/VariableNamingConventions.xml b/pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/rule/codestyle/xml/VariableNamingConventions.xml similarity index 100% rename from pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/rule/style/xml/VariableNamingConventions.xml rename to pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/rule/codestyle/xml/VariableNamingConventions.xml diff --git a/pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/rule/braces/xml/WhileLoopsMustUseBraces.xml b/pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/rule/codestyle/xml/WhileLoopsMustUseBraces.xml similarity index 100% rename from pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/rule/braces/xml/WhileLoopsMustUseBraces.xml rename to pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/rule/codestyle/xml/WhileLoopsMustUseBraces.xml diff --git a/pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/rule/complexity/xml/ExcessiveClassLength.xml b/pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/rule/complexity/xml/ExcessiveClassLength.xml deleted file mode 100644 index 1b4dd59884d..00000000000 --- a/pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/rule/complexity/xml/ExcessiveClassLength.xml +++ /dev/null @@ -1,74 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> - -<test-data - xmlns="http://pmd.sourceforge.net/rule-tests" - xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:schemaLocation="http://pmd.sourceforge.net/rule-tests http://pmd.sourceforge.net/rule-tests_1_0_0.xsd"> - - <test-code> - <description>short</description> - <rule-property name="minimum">10</rule-property> - <expected-problems>0</expected-problems> - <code><![CDATA[ -public class Foo { - public void foo() { - Integer x; - } -} - ]]></code> - </test-code> - - <code-fragment id="long"><![CDATA[ -public class Foo { - public void bar() { - bar(); - bar(); - bar(); - bar(); - bar(); - bar(); - bar(); - bar(); - bar(); - bar(); - } -} - ]]></code-fragment> - - <test-code> - <description>long</description> - <rule-property name="minimum">10</rule-property> - <expected-problems>1</expected-problems> - <code-ref id="long"/> - </test-code> - - <test-code> - <description>long class - changed minimum</description> - <rule-property name="minimum">2000</rule-property> - <expected-problems>0</expected-problems> - <code-ref id="long"/> - </test-code> - - <test-code> - <description>ignore test classes</description> - <expected-problems>0</expected-problems> - <code><![CDATA[ -@isTest -public class Foo { - public void bar() { - bar(); - bar(); - bar(); - bar(); - bar(); - bar(); - bar(); - bar(); - bar(); - bar(); - } -} - ]]></code> - </test-code> - -</test-data> \ No newline at end of file diff --git a/pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/rule/complexity/xml/ExcessiveParameterList.xml b/pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/rule/complexity/xml/ExcessiveParameterList.xml deleted file mode 100644 index 7620be0f625..00000000000 --- a/pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/rule/complexity/xml/ExcessiveParameterList.xml +++ /dev/null @@ -1,31 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> - -<test-data - xmlns="http://pmd.sourceforge.net/rule-tests" - xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:schemaLocation="http://pmd.sourceforge.net/rule-tests http://pmd.sourceforge.net/rule-tests_1_0_0.xsd"> - - <test-code> - <description>short</description> - <rule-property name="minimum">9</rule-property> - <expected-problems>0</expected-problems> - <code><![CDATA[ -public class Foo { - public void foo() {} -} - ]]></code> - </test-code> - - <test-code> - <description>long</description> - <rule-property name="minimum">9</rule-property> - <expected-problems>1</expected-problems> - <code><![CDATA[ -public class Foo { - public void foo(Integer p01, Integer p02, Integer p03, Integer p04, Integer p05, Integer p06, Integer p07, Integer p08, Integer p09, Integer p10 ) { } - public void bar(Integer p01, Integer p02, Integer p03, Integer p04, Integer p05 ) { } -} - ]]></code> - </test-code> - -</test-data> diff --git a/pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/rule/complexity/xml/AvoidDeeplyNestedIfStmts.xml b/pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/rule/design/xml/AvoidDeeplyNestedIfStmts.xml similarity index 100% rename from pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/rule/complexity/xml/AvoidDeeplyNestedIfStmts.xml rename to pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/rule/design/xml/AvoidDeeplyNestedIfStmts.xml diff --git a/pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/rule/design/xml/CyclomaticComplexity.xml b/pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/rule/design/xml/CyclomaticComplexity.xml new file mode 100644 index 00000000000..c1c8ec239de --- /dev/null +++ b/pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/rule/design/xml/CyclomaticComplexity.xml @@ -0,0 +1,302 @@ +<?xml version="1.0" encoding="UTF-8"?> +<test-data + xmlns="http://pmd.sourceforge.net/rule-tests" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://pmd.sourceforge.net/rule-tests http://pmd.sourceforge.net/rule-tests_1_0_0.xsd"> + + <code-fragment id="full-example"> + <![CDATA[ + public class Complicated { + public void example() { + int x = 0, y = 1, z = 2, t = 2; + boolean a = false, b = true, c = false, d = true; + if (a && b || b && d) { + if (y == z) { + x = 2; + } else if (y == t && !d) { + x = 2; + } else { + x = 2; + } + } else if (c && d) { + while (z < y) { + x = 2; + } + } else if (a && !b) { + for (int n = 0; n < t; n++) { + x = 2; + } + } else { + x = 2; + } + } + } + ]]> + </code-fragment> + + <test-code> + <description>Simple method</description> + <rule-property name="classReportLevel">1</rule-property> + <rule-property name="methodReportLevel">1</rule-property> + <expected-problems>2</expected-problems> + <expected-messages> + <message>The class 'Foo' has a total cyclomatic complexity of 1 (highest 1).</message> + <message>The method 'foo()' has a cyclomatic complexity of 1.</message> + </expected-messages> + <code> + <![CDATA[ + public class Foo { + public void foo() {} + } + ]]> + </code> + </test-code> + + <test-code> + <description>testLessComplicatedThanReportLevel</description> + <expected-problems>0</expected-problems> + <code> + <![CDATA[ + public class Foo { + public void foo() {} + } + ]]> + </code> + </test-code> + + <test-code> + <description>Complicated method</description> + <expected-problems>1</expected-problems> + <expected-messages> + <message>The method 'example()' has a cyclomatic complexity of 14.</message> + </expected-messages> + <code-ref id="full-example"/> + </test-code> + + <test-code> + <description>Constructor</description> + <rule-property name="methodReportLevel">1</rule-property> + <expected-problems>1</expected-problems> + <expected-messages> + <message>The constructor 'Foo()' has a cyclomatic complexity of 1.</message> + </expected-messages> + <code> + <![CDATA[ + public class Foo { + public Foo() {} + } + ]]> + </code> + </test-code> + + <test-code> + <description>Test class report level</description> + <rule-property name="classReportLevel">14</rule-property> + <rule-property name="methodReportLevel">999</rule-property> + <expected-problems>1</expected-problems> + <code-ref id="full-example"/> + </test-code> + + <test-code> + <description>Test method report level</description> + <rule-property name="classReportLevel">999</rule-property> + <rule-property name="methodReportLevel">14</rule-property> + <expected-problems>1</expected-problems> + <code-ref id="full-example"/> + </test-code> + + <code-fragment id="constructor-violation"> + <![CDATA[ + public class Test { + public Test() { + if (a == 1) { + if (b == 2) { + System.out.println("b"); + } else if (b == 1) { + } + } else { + } + } + } + ]]> + </code-fragment> + + <test-code> + <description>#984 Cyclomatic complexity should treat constructors like methods: 1 - reportMethods=true</description> + <rule-property name="methodReportLevel">1</rule-property> + <expected-problems>1</expected-problems> + <code-ref id="constructor-violation"/> + </test-code> + + <test-code> + <description>#984 Cyclomatic complexity should treat constructors like methods: 2 -reportMethods=false</description> + <rule-property name="methodReportLevel">999</rule-property> + <expected-problems>0</expected-problems> + <code-ref id="constructor-violation"/> + </test-code> + + <code-fragment id="manyBooleanOps"> + <![CDATA[ + class Foo { + void foo(){ + int x=0, y=1; + boolean a, b; + if (x > 2 || y < 4) { + while (x++ < 10 && !(y-- < 0)); + } else if (a && b || x < 4) { + return; + } + } + } + ]]> + </code-fragment> + + <test-code> + <description>Standard Cyclo should count boolean paths</description> + <rule-property name="methodReportLevel">2</rule-property> + <expected-problems>1</expected-problems> + <expected-messages> + <message>The method 'foo()' has a cyclomatic complexity of 8.</message> + </expected-messages> + <code-ref id="manyBooleanOps"/> + </test-code> + + <code-fragment id="many-small-methods"> + <![CDATA[ + public class Complicated { + int x = 0, y = 1, z = 2, t = 2; + boolean a = false, b = true, c = false, d = true; + public void example1() { + if (a && b || b && d) { + if (y == z) { + x = 2; + } else if (y == t && !d) { + x = 2; + } else { + x = 2; + } + } + } + public void example2() { + if (c && d) { + while (z < y) { + x = 2; + } + } + } + public void example12() { + if (a && b || b && d) { + if (y == z) { + x = 2; + } else if (y == t && !d) { + x = 2; + } else { + x = 2; + } + } + } + public void example22() { + if (c && d) { + while (z < y) { + x = 2; + } + } + } + public void example3() { + if (a && !b) { + for (int n = 0; n < t; n++) { + x = 2; + } + } + } + public void example32() { + if (a && !b) { + for (int n = 0; n < t; n++) { + x = 2; + } + } + } + public void exception() { + try { + int k = 0; + k++; + } catch (IOException ioe) { + ioe.printStackTrace(); + throw new Exception("surprise", ioe); + } catch (Exception e) { + // do nothing + } + } + public void exception2() { + try { + int k = 0; + k++; + } catch (IOException ioe) { + ioe.printStackTrace(); + throw new Exception("surprise", ioe); + } catch (Exception e) { + // do nothing + } + } + } + ]]> + </code-fragment> + + <test-code> + <!-- The class should get reported even though none of its methods get reported. --> + <description>Test many unreported methods</description> + <expected-problems>1</expected-problems> + <expected-messages> + <message>The class 'Complicated' has a total cyclomatic complexity of 40 (highest 8).</message> + </expected-messages> + <code-ref id="many-small-methods"/> + </test-code> + + + <code-fragment id="trigger"> + <![CDATA[ + trigger CaseAssignLevel on CaseAssignLevel__c (after delete, after insert, after undelete, after update, before delete, before insert, before update) { + + if(Trigger.isBefore) { + if(Trigger.isInsert || Trigger.isUpdate) { + CaseAssignLevel_tr.doIsNotTrigger(Trigger.new); + } + } + + + + if(Trigger.isBefore && !CaseAssignLevel_tr.isNotTrigger) { + if(Trigger.isInsert || Trigger.isUpdate) { + CaseAssignLevel_tr.doCreateKeyValue(Trigger.new, Trigger.oldMap); + } + } + + + else if(Trigger.isAfter && !CaseAssignLevel_tr.isNotTrigger) { + if(Trigger.isInsert || Trigger.isUpdate) { + + } + } + + } + ]]> + </code-fragment> + + <test-code> + <description>#768 NPE caused by exception in ApexQualifiedName.ofMethod because of trigger</description> + <expected-problems>1</expected-problems> + <expected-messages> + <message>The trigger 'CaseAssignLevel' has a cyclomatic complexity of 12.</message> + </expected-messages> + <code-ref id="trigger"/> + </test-code> + + <test-code> + <description>#768 NPE caused by exception in ApexQualifiedName.ofMethod because of trigger</description> + <rule-property name="methodReportLevel">13</rule-property> + <expected-problems>0</expected-problems> + <code-ref id="trigger"/> + </test-code> + + +</test-data> diff --git a/pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/rule/design/xml/ExcessiveClassLength.xml b/pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/rule/design/xml/ExcessiveClassLength.xml new file mode 100644 index 00000000000..ffb61e51f54 --- /dev/null +++ b/pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/rule/design/xml/ExcessiveClassLength.xml @@ -0,0 +1,95 @@ +<?xml version="1.0" encoding="UTF-8"?> + +<test-data + xmlns="http://pmd.sourceforge.net/rule-tests" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://pmd.sourceforge.net/rule-tests http://pmd.sourceforge.net/rule-tests_1_0_0.xsd"> + + <test-code> + <description>short</description> + <rule-property name="minimum">10</rule-property> + <expected-problems>0</expected-problems> + <code><![CDATA[ +public class Foo { + public void foo() { + Integer x; + } +} + ]]></code> + </test-code> + + <code-fragment id="long"><![CDATA[ +public class Foo { + public void bar() { + bar(); + bar(); + bar(); + bar(); + bar(); + bar(); + bar(); + bar(); + bar(); + bar(); + } +} + ]]></code-fragment> + + <test-code> + <description>long</description> + <rule-property name="minimum">10</rule-property> + <expected-problems>1</expected-problems> + <code-ref id="long"/> + </test-code> + + <test-code> + <description>long class - changed minimum</description> + <rule-property name="minimum">2000</rule-property> + <expected-problems>0</expected-problems> + <code-ref id="long"/> + </test-code> + + <test-code> + <description>ignore test classes</description> + <expected-problems>0</expected-problems> + <code><![CDATA[ +@isTest +public class Foo { + public void bar() { + bar(); + bar(); + bar(); + bar(); + bar(); + bar(); + bar(); + bar(); + bar(); + bar(); + } +} + ]]></code> + </test-code> + + <test-code> + <description>#1396 Public method with javadoc</description> + <expected-problems>0</expected-problems> + <code><![CDATA[ +/** + * Class comment + */ +public class SomeClass { + + /** + * Comment + */ + public void doSomething() { + System.debug("hello world"); + } + + /** Field comment */ + private String field; +} + ]]></code> + </test-code> +</test-data> \ No newline at end of file diff --git a/pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/rule/design/xml/ExcessiveParameterList.xml b/pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/rule/design/xml/ExcessiveParameterList.xml new file mode 100644 index 00000000000..7b3b14d2528 --- /dev/null +++ b/pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/rule/design/xml/ExcessiveParameterList.xml @@ -0,0 +1,53 @@ +<?xml version="1.0" encoding="UTF-8"?> + +<test-data + xmlns="http://pmd.sourceforge.net/rule-tests" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://pmd.sourceforge.net/rule-tests http://pmd.sourceforge.net/rule-tests_1_0_0.xsd"> + + <test-code> + <description>short</description> + <rule-property name="minimum">9</rule-property> + <expected-problems>0</expected-problems> + <code><![CDATA[ +public class Foo { + public void foo() {} +} + ]]></code> + </test-code> + + <test-code> + <description>long</description> + <rule-property name="minimum">9</rule-property> + <expected-problems>1</expected-problems> + <code><![CDATA[ +public class Foo { + public void foo(Integer p01, Integer p02, Integer p03, Integer p04, Integer p05, Integer p06, Integer p07, Integer p08, Integer p09, Integer p10 ) { } + public void bar(Integer p01, Integer p02, Integer p03, Integer p04, Integer p05 ) { } +} + ]]></code> + </test-code> + + <test-code> + <description>#1396 Public method with javadoc</description> + <expected-problems>0</expected-problems> + <code><![CDATA[ +/** + * Class comment + */ +public class SomeClass { + + /** + * Comment + */ + public void doSomething() { + System.debug("hello world"); + } + + /** Field comment */ + private String field; +} + ]]></code> + </test-code> + +</test-data> diff --git a/pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/rule/complexity/xml/ExcessivePublicCount.xml b/pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/rule/design/xml/ExcessivePublicCount.xml similarity index 86% rename from pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/rule/complexity/xml/ExcessivePublicCount.xml rename to pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/rule/design/xml/ExcessivePublicCount.xml index 7df32abc0d8..91c1856bcac 100644 --- a/pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/rule/complexity/xml/ExcessivePublicCount.xml +++ b/pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/rule/design/xml/ExcessivePublicCount.xml @@ -92,5 +92,26 @@ public class Foo { } ]]></code> </test-code> - + + <test-code> + <description>#1396 Class with javadoc</description> + <expected-problems>0</expected-problems> + <code><![CDATA[ +/** + * Class comment + */ +public class SomeClass { + + /** + * Comment + */ + public void doSomething() { + System.debug("hello world"); + } + + /** Field comment */ + private String field; +} + ]]></code> + </test-code> </test-data> diff --git a/pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/rule/complexity/xml/NcssConstructorCount.xml b/pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/rule/design/xml/NcssConstructorCount.xml similarity index 84% rename from pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/rule/complexity/xml/NcssConstructorCount.xml rename to pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/rule/design/xml/NcssConstructorCount.xml index 200bc11340b..e0a43206477 100644 --- a/pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/rule/complexity/xml/NcssConstructorCount.xml +++ b/pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/rule/design/xml/NcssConstructorCount.xml @@ -109,5 +109,32 @@ public class Foo { } ]]></code> </test-code> - + + <test-code> + <description>#1396 Constructor with comment</description> + <expected-problems>0</expected-problems> + <code><![CDATA[ +/** + * Class comment + */ +public class SomeClass { + + /** + * Constructor comment + */ + public SomeClass() { + } + + /** + * Comment + */ + public void doSomething() { + System.debug("hello world"); + } + + /** Field comment */ + private String field; +} + ]]></code> + </test-code> </test-data> diff --git a/pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/rule/complexity/xml/NcssMethodCount.xml b/pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/rule/design/xml/NcssMethodCount.xml similarity index 89% rename from pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/rule/complexity/xml/NcssMethodCount.xml rename to pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/rule/design/xml/NcssMethodCount.xml index 6b92b0f3e57..58e523add47 100644 --- a/pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/rule/complexity/xml/NcssMethodCount.xml +++ b/pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/rule/design/xml/NcssMethodCount.xml @@ -130,4 +130,25 @@ private class AcceptanceTests_Test { } </code> </test-code> + + <test-code> + <description>#1396 Method with comment</description> + <expected-problems>0</expected-problems> + <code><![CDATA[ +/** + * Class comment + */ +public class SomeClass { + /** + * Comment + */ + public void doSomething() { + System.debug("hello world"); + } + + /** Field comment */ + private String field; +} + ]]></code> + </test-code> </test-data> diff --git a/pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/rule/complexity/xml/NcssTypeCount.xml b/pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/rule/design/xml/NcssTypeCount.xml similarity index 87% rename from pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/rule/complexity/xml/NcssTypeCount.xml rename to pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/rule/design/xml/NcssTypeCount.xml index 347644b2336..55692690aaa 100644 --- a/pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/rule/complexity/xml/NcssTypeCount.xml +++ b/pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/rule/design/xml/NcssTypeCount.xml @@ -105,5 +105,26 @@ private class AcceptanceTests_Test { } } ]]></code> - </test-code> + </test-code> + + <test-code> + <description>#1396 Method with comment</description> + <expected-problems>0</expected-problems> + <code><![CDATA[ +/** + * Class comment + */ +public class SomeClass { + /** + * Comment + */ + public void doSomething() { + System.debug("hello world"); + } + + /** Field comment */ + private String field; +} + ]]></code> + </test-code> </test-data> diff --git a/pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/rule/complexity/xml/StdCyclomaticComplexity.xml b/pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/rule/design/xml/StdCyclomaticComplexity.xml similarity index 100% rename from pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/rule/complexity/xml/StdCyclomaticComplexity.xml rename to pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/rule/design/xml/StdCyclomaticComplexity.xml diff --git a/pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/rule/complexity/xml/TooManyFields.xml b/pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/rule/design/xml/TooManyFields.xml similarity index 100% rename from pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/rule/complexity/xml/TooManyFields.xml rename to pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/rule/design/xml/TooManyFields.xml diff --git a/pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/rule/documentation/xml/ApexDoc.xml b/pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/rule/documentation/xml/ApexDoc.xml new file mode 100644 index 00000000000..0e890cd2741 --- /dev/null +++ b/pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/rule/documentation/xml/ApexDoc.xml @@ -0,0 +1,401 @@ +<?xml version="1.0" encoding="UTF-8"?> + +<test-data + xmlns="http://pmd.sourceforge.net/rule-tests" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://pmd.sourceforge.net/rule-tests http://pmd.sourceforge.net/rule-tests_1_0_0.xsd"> + + <test-code> + <description>comment should start with two asterisks</description> + <expected-problems>1</expected-problems> + <code><![CDATA[ +/* + * @description Foo + */ +public class Foo { } + ]]></code> + </test-code> + + <test-code> + <description>public test class does not need comment</description> + <expected-problems>0</expected-problems> + <code><![CDATA[ +@isTest +public class Foo { } + ]]></code> + </test-code> + + <test-code> + <description>public class should have comment</description> + <expected-problems>1</expected-problems> + <code><![CDATA[ +public class Foo { } + ]]></code> + </test-code> + + <test-code> + <description>global class should have comment</description> + <expected-problems>1</expected-problems> + <code><![CDATA[ +global class Foo { } + ]]></code> + </test-code> + + <test-code> + <description>private class does not need comment</description> + <expected-problems>0</expected-problems> + <code><![CDATA[ +private class Foo { } + ]]></code> + </test-code> + + <test-code> + <description>class comment should have description</description> + <expected-problems>1</expected-problems> + <code><![CDATA[ +/** + */ +public class Foo { } + ]]></code> + </test-code> + + <test-code> + <description>correct class comment</description> + <expected-problems>0</expected-problems> + <code><![CDATA[ +/** + * @description Foo + */ +public class Foo { } + ]]></code> + </test-code> + + <test-code> + <description>correct class comment with annotation</description> + <expected-problems>0</expected-problems> + <code><![CDATA[ +/** + * @description Foo + */ +@Deprecated +public class Foo { } + ]]></code> + </test-code> + + <test-code> + <description>public interface should have comment</description> + <expected-problems>1</expected-problems> + <code><![CDATA[ +public interface Foo { } + ]]></code> + </test-code> + + <test-code> + <description>global interface should have comment</description> + <expected-problems>1</expected-problems> + <code><![CDATA[ +global interface Foo { } + ]]></code> + </test-code> + + <test-code> + <description>private interface does not need comment</description> + <expected-problems>0</expected-problems> + <code><![CDATA[ +private interface Foo { } + ]]></code> + </test-code> + + <test-code> + <description>interface comment should have description</description> + <expected-problems>1</expected-problems> + <code><![CDATA[ +/** + */ +public interface Foo { } + ]]></code> + </test-code> + + <test-code> + <description>correct interface comment</description> + <expected-problems>0</expected-problems> + <code><![CDATA[ +/** + * @description Foo + */ +public interface Foo { } + ]]></code> + </test-code> + + <test-code> + <description>correct interface comment with annotation</description> + <expected-problems>0</expected-problems> + <code><![CDATA[ +/** + * @description Foo + */ +@Deprecated +public interface Foo { } + ]]></code> + </test-code> + + <test-code> + <description>public method should have comment</description> + <expected-problems>1</expected-problems> + <code><![CDATA[ +/** + * @description Foo + */ +public class Foo { + public void bar() { } +} + ]]></code> + </test-code> + + <test-code> + <description>global method should have comment</description> + <expected-problems>1</expected-problems> + <code><![CDATA[ +/** + * @description Foo + */ +public class Foo { + global void bar() { } +} + ]]></code> + </test-code> + + <test-code> + <description>public override method does not need comment</description> + <expected-problems>0</expected-problems> + <code><![CDATA[ +/** + * @description Foo + */ +public class Foo { + public override void bar() { } +} + ]]></code> + </test-code> + + <test-code> + <description>method comment should have description</description> + <expected-problems>1</expected-problems> + <code><![CDATA[ +/** + * @description Foo + */ +public class Foo { + /** + */ + public void bar() { } +} + ]]></code> + </test-code> + + <test-code> + <description>non-void method comment should have return</description> + <expected-problems>1</expected-problems> + <code><![CDATA[ +/** + * @description Foo + */ +public class Foo { + /** + * @description Bar + */ + public Object bar() { return null; } +} + ]]></code> + </test-code> + + <test-code> + <description>constructor comment does not need return</description> + <expected-problems>0</expected-problems> + <code><![CDATA[ +/** + * @description Foo + */ +public class Foo { + /** + * @description Foo + */ + public Foo() { } +} + ]]></code> + </test-code> + + <test-code> + <description>method with param comment should have param</description> + <expected-problems>1</expected-problems> + <code><![CDATA[ +/** + * @description Foo + */ +public class Foo { + /** + * @description Bar + */ + public void bar(String foo) { } +} + ]]></code> + </test-code> + + <test-code> + <description>params should match method</description> + <expected-problems>1</expected-problems> + <code><![CDATA[ +/** + * @description Foo + */ +public class Foo { + /** + * @description Bar + * @param foo1 Foo + */ + public void bar(String foo) { } +} + ]]></code> + </test-code> + + <test-code> + <description>correct void method comment</description> + <expected-problems>0</expected-problems> + <code><![CDATA[ +/** + * @description Foo + */ +public class Foo { + /** + * @description Bar + */ + public void bar() { } +} + ]]></code> + </test-code> + + <test-code> + <description>correct non-void method comment</description> + <expected-problems>0</expected-problems> + <code><![CDATA[ +/** + * @description Foo + */ +public class Foo { + /** + * @description Bar + * @return Bar + */ + public Object bar() { return null; } +} + ]]></code> + </test-code> + + <test-code> + <description>correct method with params comment</description> + <expected-problems>0</expected-problems> + <code><![CDATA[ +/** + * @description Foo + */ +public class Foo { + /** + * @description Bar + * @param foo1 Foo1 + * @param foo2 Foo2 + */ + public void bar(String foo1, String foo2) { return null; } +} + ]]></code> + </test-code> + + <test-code> + <description>correct method comment with annotation</description> + <expected-problems>0</expected-problems> + <code><![CDATA[ +/** + * @description Foo + */ +public class Foo { + /** + * @description Bar + */ + @Deprecated + public void bar() { } +} + ]]></code> + </test-code> + + <test-code> + <description>public property should have comment</description> + <expected-problems>1</expected-problems> + <code><![CDATA[ +/** + * @description Foo + */ +public class Foo { + public Object Bar { get; set; } +} + ]]></code> +</test-code> + + <test-code> + <description>global property should have comment</description> + <expected-problems>1</expected-problems> + <code><![CDATA[ +/** + * @description Foo + */ +public class Foo { + global Object Bar { get; set; } +} + ]]></code> + </test-code> + + <test-code> + <description>property comment should have description</description> + <expected-problems>1</expected-problems> + <code><![CDATA[ +/** + * @description Foo + */ +public class Foo { + public Object Bar { get; set; } +} + ]]></code> + </test-code> + + <test-code> + <description>correct property comment</description> + <expected-problems>0</expected-problems> + <code><![CDATA[ +/** + * @description Foo + */ +public class Foo { + /** + * @description Bar + */ + public Object Bar { get; set; } +} + ]]></code> + </test-code> + + <test-code> + <description>correct property comment with annotation</description> + <expected-problems>0</expected-problems> + <code><![CDATA[ +/** + * @description Foo + */ +public class Foo { + /** + * @description Bar + */ + @Deprecated + public Object Bar { get; set; } +} + ]]></code> + </test-code> + +</test-data> diff --git a/pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/rule/errorprone/xml/AvoidDirectAccessTriggerMap.xml b/pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/rule/errorprone/xml/AvoidDirectAccessTriggerMap.xml new file mode 100644 index 00000000000..49d7898859a --- /dev/null +++ b/pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/rule/errorprone/xml/AvoidDirectAccessTriggerMap.xml @@ -0,0 +1,44 @@ +<?xml version="1.0" encoding="UTF-8"?> + +<test-data + xmlns="http://pmd.sourceforge.net/rule-tests" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://pmd.sourceforge.net/rule-tests http://pmd.sourceforge.net/rule-tests_1_0_0.xsd"> + + <test-code> + <description>Directly accessing the Trigger.old map.</description> + <expected-problems>1</expected-problems> + <code><![CDATA[ +trigger AccountTrigger on Account (before insert, before update) { + Account a = Trigger.new[0]; //Bad: Accessing the trigger array directly is not recommended. +} + ]]></code> + </test-code> + + <test-code> + <description>Looping through map, best practice</description> + <expected-problems>0</expected-problems> + <code><![CDATA[ +trigger AccountTrigger on Account (before insert, before update) { + foreach ( Account a : Trigger.new ){ + //Good: Iterate through the trigger.new array instead. + system.debug(a); + } +} + ]]></code> + </test-code> + + <test-code> + <description>Regression issue #792</description> + <expected-problems>0</expected-problems> + <code><![CDATA[ +class NotATrigger { + void foo(){ + Account[] accounts = opportunity.Accounts[0]; + } +} + + ]]></code> + </test-code> + +</test-data> diff --git a/pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/rule/errorprone/xml/AvoidHardcodingId.xml b/pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/rule/errorprone/xml/AvoidHardcodingId.xml new file mode 100644 index 00000000000..d69a71476f5 --- /dev/null +++ b/pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/rule/errorprone/xml/AvoidHardcodingId.xml @@ -0,0 +1,94 @@ +<?xml version="1.0" encoding="UTF-8"?> + +<test-data + xmlns="http://pmd.sourceforge.net/rule-tests" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://pmd.sourceforge.net/rule-tests http://pmd.sourceforge.net/rule-tests_1_0_0.xsd"> + + <test-code> + <description>Non compliant scenario: Hardcoded Id</description> + <expected-problems>1</expected-problems> + <code><![CDATA[ +public class Foo { + public void method (ID newRecordID) { + for (Account current : listOfcounts){ + if (current.getId == 'a002400000RG1nyAAD') { + } + } + method('3266sd35435sd6a'); + } +} + ]]></code> + </test-code> + + <test-code> + <description>Compliant scenario, getting ID dynamically</description> + <expected-problems>0</expected-problems> + <code><![CDATA[ +public class Foo { + public void method (ID newRecordID) { + for (Account current : listOfcounts){ + if (current.getId == newRecordID) { + + } + } + + otherMethod(newRecordID); + } +} + ]]></code> + </test-code> + + <test-code> + <description>Test for random string combinations</description> + <expected-problems>0</expected-problems> + <code><![CDATA[ +public class Foo { + void foo() { + return 'jatuatzbtazi124'; + } +} + ]]></code> + </test-code> + + <test-code> + <description>Test for random string combinations - more than 15, less than 18 digits</description> + <expected-problems>0</expected-problems> + <code><![CDATA[ +public class Foo { + void foo() { + return 'jatua0tzbtazi1243'; + } +} + ]]></code> + </test-code> + + <test-code> + <description>Test for random string combinations - checksum doesn't match</description> + <expected-problems>0</expected-problems> + <code><![CDATA[ +public class Foo { + void foo() { + return '001A0000006Vm9uIAE'; + } +} + ]]></code> + </test-code> + + <test-code> + <description>[apex] AvoidHardcodingId false positives #776</description> + <expected-problems>0</expected-problems> + <code><![CDATA[ +public class Foo { + void foo() { + // this is a false positive, we can't say, whether it's a salesforce id or not + @SuppressWarnings('PMD.AvoidHardcodingId') + String IMEI__c = '359040082913024'; + + // now the 6th character is non-0, definitive not a salesforce id + String IMEI2__c = '359041082913024'; + } +} + ]]></code> + </test-code> +</test-data> diff --git a/pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/rule/errorprone/xml/AvoidNonExistentAnnotations.xml b/pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/rule/errorprone/xml/AvoidNonExistentAnnotations.xml new file mode 100644 index 00000000000..b07f4e9a581 --- /dev/null +++ b/pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/rule/errorprone/xml/AvoidNonExistentAnnotations.xml @@ -0,0 +1,72 @@ +<?xml version="1.0" encoding="UTF-8"?> + +<test-data + xmlns="http://pmd.sourceforge.net/rule-tests" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://pmd.sourceforge.net/rule-tests http://pmd.sourceforge.net/rule-tests_1_0_0.xsd"> + + <test-code> + <description>Class with nonexistent annotation</description> + <expected-problems>1</expected-problems> + <code><![CDATA[ +@NonExistentStupidity public class Foo { + +} + ]]></code> + </test-code> + + <test-code> + <description>Interface with nonexistent annotation</description> + <expected-problems>1</expected-problems> + <code><![CDATA[ +@NonExistentStupidity public interface Foo { + +} + ]]></code> + </test-code> + + <test-code> + <description>Method with non existent annotation</description> + <expected-problems>1</expected-problems> + <code><![CDATA[ +public class Foo { + @NonExistentStupidity public Integer bar() { + } +} + ]]></code> + </test-code> + + <test-code> + <description>Property with non existent annotation</description> + <expected-problems>1</expected-problems> + <code><![CDATA[ +public class Foo { + @NonExistentStupidity public Integer myProp {get; set;} +} + ]]></code> + </test-code> + + <test-code> + <description>Field with non existent annotation</description> + <expected-problems>1</expected-problems> + <code><![CDATA[ +public class Foo { + @NonExistentStupidity public Integer myField; +} + ]]></code> + </test-code> + + <test-code> + <description>Valid annotations are not flagged</description> + <expected-problems>0</expected-problems> + <code><![CDATA[ +@IsTest +public class Foo { + @Future + @Deprecated + public Integer bar() { + } +} + ]]></code> + </test-code> +</test-data> diff --git a/pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/rule/errorprone/xml/EmptyCatchBlock.xml b/pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/rule/errorprone/xml/EmptyCatchBlock.xml new file mode 100644 index 00000000000..2a827911476 --- /dev/null +++ b/pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/rule/errorprone/xml/EmptyCatchBlock.xml @@ -0,0 +1,40 @@ +<?xml version="1.0" encoding="UTF-8"?> +<test-data + xmlns="http://pmd.sourceforge.net/rule-tests" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://pmd.sourceforge.net/rule-tests http://pmd.sourceforge.net/rule-tests_1_0_0.xsd"> + <test-code> + <description><![CDATA[ +Failure case: Empty Catch Block + ]]></description> + <expected-problems>1</expected-problems> + <code><![CDATA[ +public class Foo { + public void bar() { + try { + system.debug(1); + } + catch(Exception e) { + } + } +} + ]]></code> + </test-code> + <test-code> + <description><![CDATA[ +Success case: Empty Catch Block + ]]></description> + <expected-problems>0</expected-problems> + <code><![CDATA[ +public class Foo { + public void foo() { + try { + system.debug(1); + } catch(Exception e) { + system.debug(e); + } + } +} + ]]></code> + </test-code> +</test-data> diff --git a/pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/rule/errorprone/xml/EmptyIfStmt.xml b/pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/rule/errorprone/xml/EmptyIfStmt.xml new file mode 100644 index 00000000000..00b5ba05600 --- /dev/null +++ b/pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/rule/errorprone/xml/EmptyIfStmt.xml @@ -0,0 +1,35 @@ +<?xml version="1.0" encoding="UTF-8"?> +<test-data + xmlns="http://pmd.sourceforge.net/rule-tests" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://pmd.sourceforge.net/rule-tests http://pmd.sourceforge.net/rule-tests_1_0_0.xsd"> + <test-code> + <description><![CDATA[ +Failure Case: Empty If Statement + ]]></description> + <expected-problems>1</expected-problems> + <code><![CDATA[ +public class Foo { + public void foo() { + if(true) { + } + } +} + ]]></code> + </test-code> + <test-code> + <description><![CDATA[ +Success Case: Empty If statement + ]]></description> + <expected-problems>0</expected-problems> + <code><![CDATA[ +public class Foo { + public void foo() { + if(true) { + system.debug(true); + } + } +} + ]]></code> + </test-code> +</test-data> diff --git a/pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/rule/errorprone/xml/EmptyStatementBlock.xml b/pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/rule/errorprone/xml/EmptyStatementBlock.xml new file mode 100644 index 00000000000..c80d2bf9cc4 --- /dev/null +++ b/pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/rule/errorprone/xml/EmptyStatementBlock.xml @@ -0,0 +1,65 @@ +<?xml version="1.0" encoding="UTF-8"?> +<test-data + xmlns="http://pmd.sourceforge.net/rule-tests" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://pmd.sourceforge.net/rule-tests http://pmd.sourceforge.net/rule-tests_1_0_0.xsd"> + <test-code> + <description><![CDATA[ +Failure case: Empty Statement Block + ]]></description> + <expected-problems>1</expected-problems> + <code><![CDATA[ +public class Foo { + public void foo() { + } + + public abstract void bar() {} +} + ]]></code> + </test-code> + <test-code> + <description><![CDATA[ +Success case: Empty Statement Block + ]]></description> + <expected-problems>0</expected-problems> + <code><![CDATA[ +public class Foo { + public void foo() { + system.debug(1); + } + public abstract void bar() {} +} + ]]></code> + </test-code> + <test-code> + <description><![CDATA[ +#760 - false positive on non existing else block + ]]></description> + <expected-problems>0</expected-problems> + <code><![CDATA[ +public class Foo { + public void foo() { + if (something) { + system.debug(1); + } + } +} + ]]></code> + </test-code> + <test-code> + <description><![CDATA[ +#760 - is reported on existing else block + ]]></description> + <expected-problems>1</expected-problems> + <code><![CDATA[ +public class Foo { + public void foo() { + if (something) { + system.debug(1); + } else { + } + } +} + ]]></code> + </test-code> +</test-data> diff --git a/pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/rule/errorprone/xml/EmptyTryOrFinallyBlock.xml b/pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/rule/errorprone/xml/EmptyTryOrFinallyBlock.xml new file mode 100644 index 00000000000..74700f4dd10 --- /dev/null +++ b/pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/rule/errorprone/xml/EmptyTryOrFinallyBlock.xml @@ -0,0 +1,78 @@ +<?xml version="1.0" encoding="UTF-8"?> +<test-data + xmlns="http://pmd.sourceforge.net/rule-tests" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://pmd.sourceforge.net/rule-tests http://pmd.sourceforge.net/rule-tests_1_0_0.xsd"> + <test-code> + <description><![CDATA[ +Failure Case: Empty Try Block + ]]></description> + <expected-problems>1</expected-problems> + <code><![CDATA[ +public class Foo { + void foo() { + try { + } + catch(Exception e) { + system.debug(e); + } + } +} + ]]></code> + </test-code> + <test-code> + <description><![CDATA[ +Success Case: Empty Try Block + ]]></description> + <expected-problems>0</expected-problems> + <code><![CDATA[ +public class Foo { + void foo() { + try { + insert account; + } catch(Exception e) { + system.debug(e); + } + } +} + ]]></code> + </test-code> + <test-code> + <description><![CDATA[ +Failure Case: Empty Finally Block + ]]></description> + <expected-problems>1</expected-problems> + <code><![CDATA[ +public class Foo { + void foo() { + try { + insert account; + } catch(Exception e) { + // Generic exception handling code + system.debug(e); + } finally { + } + } +} + ]]></code> + </test-code> + <test-code> + <description><![CDATA[ +Success Case: Empty Finally Block + ]]></description> + <expected-problems>0</expected-problems> + <code><![CDATA[ +public class Foo { + void foo() { + try { + insert account; + } catch(Exception e) { + system.debug(e); + } finally { + someAction(); + } + } +} + ]]></code> + </test-code> +</test-data> diff --git a/pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/rule/errorprone/xml/EmptyWhileStmt.xml b/pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/rule/errorprone/xml/EmptyWhileStmt.xml new file mode 100644 index 00000000000..85c98fee7bc --- /dev/null +++ b/pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/rule/errorprone/xml/EmptyWhileStmt.xml @@ -0,0 +1,39 @@ +<?xml version="1.0" encoding="UTF-8"?> +<test-data + xmlns="http://pmd.sourceforge.net/rule-tests" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://pmd.sourceforge.net/rule-tests http://pmd.sourceforge.net/rule-tests_1_0_0.xsd"> + <test-code> + <description><![CDATA[ +Failure Case: Empty While Statement + ]]></description> + <expected-problems>1</expected-problems> + <code><![CDATA[ +public class Foo { + void foo() { + Integer count = 1; + while (count < 11) { + } + } +} + ]]></code> + </test-code> + <test-code> + <description><![CDATA[ +Success Case: Empty While Statement + ]]></description> + <expected-problems>0</expected-problems> + <code><![CDATA[ +public class Foo { + void foo() { + Integer count = 1; + + while (count < 11) { + System.debug(count); + count++; + } + } +} + ]]></code> + </test-code> +</test-data> diff --git a/pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/rule/style/xml/MethodWithSameNameAsEnclosingClass.xml b/pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/rule/errorprone/xml/MethodWithSameNameAsEnclosingClass.xml similarity index 100% rename from pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/rule/style/xml/MethodWithSameNameAsEnclosingClass.xml rename to pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/rule/errorprone/xml/MethodWithSameNameAsEnclosingClass.xml diff --git a/pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/rule/metrics/xml/CyclomaticComplexity.xml b/pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/rule/metrics/xml/CyclomaticComplexity.xml deleted file mode 100644 index 8fa77671376..00000000000 --- a/pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/rule/metrics/xml/CyclomaticComplexity.xml +++ /dev/null @@ -1,255 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<test-data - xmlns="http://pmd.sourceforge.net/rule-tests" - xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:schemaLocation="http://pmd.sourceforge.net/rule-tests http://pmd.sourceforge.net/rule-tests_1_0_0.xsd"> - - <code-fragment id="full-example"> - <![CDATA[ - public class Complicated { - public void example() { - int x = 0, y = 1, z = 2, t = 2; - boolean a = false, b = true, c = false, d = true; - if (a && b || b && d) { - if (y == z) { - x = 2; - } else if (y == t && !d) { - x = 2; - } else { - x = 2; - } - } else if (c && d) { - while (z < y) { - x = 2; - } - } else if (a && !b) { - for (int n = 0; n < t; n++) { - x = 2; - } - } else { - x = 2; - } - } - } - ]]> - </code-fragment> - - <test-code> - <description>Simple method</description> - <rule-property name="classReportLevel">1</rule-property> - <rule-property name="methodReportLevel">1</rule-property> - <expected-problems>2</expected-problems> - <expected-messages> - <message>The class 'Foo' has a total cyclomatic complexity of 1 (highest 1).</message> - <message>The method 'foo()' has a cyclomatic complexity of 1.</message> - </expected-messages> - <code> - <![CDATA[ - public class Foo { - public void foo() {} - } - ]]> - </code> - </test-code> - - <test-code> - <description>testLessComplicatedThanReportLevel</description> - <expected-problems>0</expected-problems> - <code> - <![CDATA[ - public class Foo { - public void foo() {} - } - ]]> - </code> - </test-code> - - <test-code> - <description>Complicated method</description> - <expected-problems>1</expected-problems> - <expected-messages> - <message>The method 'example()' has a cyclomatic complexity of 14.</message> - </expected-messages> - <code-ref id="full-example"/> - </test-code> - - <test-code> - <description>Constructor</description> - <rule-property name="methodReportLevel">1</rule-property> - <expected-problems>1</expected-problems> - <expected-messages> - <message>The constructor 'Foo()' has a cyclomatic complexity of 1.</message> - </expected-messages> - <code> - <![CDATA[ - public class Foo { - public Foo() {} - } - ]]> - </code> - </test-code> - - <test-code> - <description>Test class report level</description> - <rule-property name="classReportLevel">14</rule-property> - <rule-property name="methodReportLevel">999</rule-property> - <expected-problems>1</expected-problems> - <code-ref id="full-example"/> - </test-code> - - <test-code> - <description>Test method report level</description> - <rule-property name="classReportLevel">999</rule-property> - <rule-property name="methodReportLevel">14</rule-property> - <expected-problems>1</expected-problems> - <code-ref id="full-example"/> - </test-code> - - <code-fragment id="constructor-violation"> - <![CDATA[ - public class Test { - public Test() { - if (a == 1) { - if (b == 2) { - System.out.println("b"); - } else if (b == 1) { - } - } else { - } - } - } - ]]> - </code-fragment> - - <test-code> - <description>#984 Cyclomatic complexity should treat constructors like methods: 1 - reportMethods=true</description> - <rule-property name="methodReportLevel">1</rule-property> - <expected-problems>1</expected-problems> - <code-ref id="constructor-violation"/> - </test-code> - - <test-code> - <description>#984 Cyclomatic complexity should treat constructors like methods: 2 -reportMethods=false</description> - <rule-property name="methodReportLevel">999</rule-property> - <expected-problems>0</expected-problems> - <code-ref id="constructor-violation"/> - </test-code> - - <code-fragment id="manyBooleanOps"> - <![CDATA[ - class Foo { - void foo(){ - int x=0, y=1; - boolean a, b; - if (x > 2 || y < 4) { - while (x++ < 10 && !(y-- < 0)); - } else if (a && b || x < 4) { - return; - } - } - } - ]]> - </code-fragment> - - <test-code> - <description>Standard Cyclo should count boolean paths</description> - <rule-property name="methodReportLevel">2</rule-property> - <expected-problems>1</expected-problems> - <expected-messages> - <message>The method 'foo()' has a cyclomatic complexity of 8.</message> - </expected-messages> - <code-ref id="manyBooleanOps"/> - </test-code> - - <code-fragment id="many-small-methods"> - <![CDATA[ - public class Complicated { - int x = 0, y = 1, z = 2, t = 2; - boolean a = false, b = true, c = false, d = true; - public void example1() { - if (a && b || b && d) { - if (y == z) { - x = 2; - } else if (y == t && !d) { - x = 2; - } else { - x = 2; - } - } - } - public void example2() { - if (c && d) { - while (z < y) { - x = 2; - } - } - } - public void example12() { - if (a && b || b && d) { - if (y == z) { - x = 2; - } else if (y == t && !d) { - x = 2; - } else { - x = 2; - } - } - } - public void example22() { - if (c && d) { - while (z < y) { - x = 2; - } - } - } - public void example3() { - if (a && !b) { - for (int n = 0; n < t; n++) { - x = 2; - } - } - } - public void example32() { - if (a && !b) { - for (int n = 0; n < t; n++) { - x = 2; - } - } - } - public void exception() { - try { - int k = 0; - k++; - } catch (IOException ioe) { - ioe.printStackTrace(); - throw new Exception("surprise", ioe); - } catch (Exception e) { - // do nothing - } - } - public void exception2() { - try { - int k = 0; - k++; - } catch (IOException ioe) { - ioe.printStackTrace(); - throw new Exception("surprise", ioe); - } catch (Exception e) { - // do nothing - } - } - } - ]]> - </code-fragment> - - <test-code> - <!-- The class should get reported even though none of its methods get reported. --> - <description>Test many unreported methods</description> - <expected-problems>1</expected-problems> - <expected-messages> - <message>The class 'Complicated' has a total cyclomatic complexity of 40 (highest 8).</message> - </expected-messages> - <code-ref id="many-small-methods"/> - </test-code> - -</test-data> \ No newline at end of file diff --git a/pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/rule/performance/xml/AvoidSoslInLoops.xml b/pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/rule/performance/xml/AvoidSoslInLoops.xml new file mode 100644 index 00000000000..19aa716baa9 --- /dev/null +++ b/pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/rule/performance/xml/AvoidSoslInLoops.xml @@ -0,0 +1,107 @@ +<?xml version="1.0" encoding="UTF-8"?> + +<test-data + xmlns="http://pmd.sourceforge.net/rule-tests" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://pmd.sourceforge.net/rule-tests http://pmd.sourceforge.net/rule-tests_1_0_0.xsd"> + + <test-code> + <description>Problematic Sosl in for each</description> + <expected-problems>1</expected-problems> + <code><![CDATA[ +public class Foo { + public void test1() { + for(Integer i : new List<Integer>{1,2}) { + List<List<SObject>> searchList = [FIND 'map*' IN ALL FIELDS RETURNING Account (Id, Name), Contact, Opportunity, Lead]; + } + } +} + ]]></code> + </test-code> + + <test-code> + <description>Problematic Sosl in for loop</description> + <expected-problems>1</expected-problems> + <code><![CDATA[ +public class Foo { + public void test1() { + for(;;) { + List<List<SObject>> searchList = [FIND 'map*' IN ALL FIELDS RETURNING Account (Id, Name), Contact, Opportunity, Lead]; + } + } +} + ]]></code> + </test-code> + + <test-code> + <description>Problematic Sosl in While loop</description> + <expected-problems>1</expected-problems> + <code><![CDATA[ +public class Foo { + public void test1() { + while(true) { + List<List<SObject>> searchList = [FIND 'map*' IN ALL FIELDS RETURNING Account (Id, Name), Contact, Opportunity, Lead]; + } + } +} + ]]></code> + </test-code> + + <test-code> + <description>Problematic Sosl in do loop</description> + <expected-problems>1</expected-problems> + <code><![CDATA[ +public class Foo { + public void test1() { + do{ + List<List<SObject>> searchList = [FIND 'map*' IN ALL FIELDS RETURNING Account (Id, Name), Contact, Opportunity, Lead]; + }while(true) ; + } +} + ]]></code> + </test-code> + + <test-code> + <description>Multiple problematic Sosl expressions</description> + <expected-problems>2</expected-problems> + <code><![CDATA[ +public class Foo { + public void test1() { + do{ + List<List<SObject>> searchList = [FIND 'map*' IN ALL FIELDS RETURNING Account (Id, Name), Contact, Opportunity, Lead]; + List<List<SObject>> searchList = [FIND 'map*' IN ALL FIELDS RETURNING Account (Id, Name)]; + }while(true) ; + } +} + ]]></code> + </test-code> + + <test-code> + <description>Return Sosl is even ok in loop</description> + <expected-problems>0</expected-problems> + <code><![CDATA[ +public class Foo { + public List<Account> test1() { + for(;;) { + return [FIND 'map*' IN ALL FIELDS RETURNING Account (Id, Name), Contact, Opportunity, Lead]; + } + } +} + ]]></code> + </test-code> + + <test-code> + <description>#29 SOSL For Loops should not throw an Avoid Sosl queries inside loops issue</description> + <expected-problems>0</expected-problems> + <code><![CDATA[ +public class Foo { + public void test1() { + for(List<sObject> a : [FIND 'map*' IN ALL FIELDS RETURNING Account (Id, Name), Contact, Opportunity]) { + + } + } +} + ]]></code> + </test-code> + +</test-data> diff --git a/pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/rule/security/xml/ApexCRUDViolation.xml b/pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/rule/security/xml/ApexCRUDViolation.xml index ce3933613c7..2c7a4768b6a 100644 --- a/pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/rule/security/xml/ApexCRUDViolation.xml +++ b/pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/rule/security/xml/ApexCRUDViolation.xml @@ -812,6 +812,18 @@ public class Foo { public list<A.B[]> bar; public map<Id,A.B[]> baz; } +]]></code> + </test-code> + + <test-code> + <description>Regression issue #799 detection</description> + <expected-problems>0</expected-problems> + <code><![CDATA[ +public class Foo { + void chainedMethods() { + firstMethod().lastMethod(); + } +} ]]></code> </test-code> </test-data> diff --git a/pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/rule/style/xml/AvoidGlobalModifier.xml b/pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/rule/style/xml/AvoidGlobalModifier.xml deleted file mode 100644 index ead264bc979..00000000000 --- a/pmd-apex/src/test/resources/net/sourceforge/pmd/lang/apex/rule/style/xml/AvoidGlobalModifier.xml +++ /dev/null @@ -1,52 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> - -<test-data - xmlns="http://pmd.sourceforge.net/rule-tests" - xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:schemaLocation="http://pmd.sourceforge.net/rule-tests http://pmd.sourceforge.net/rule-tests_1_0_0.xsd"> - - <test-code> - <description>Global class</description> - <expected-problems>1</expected-problems> - <code><![CDATA[ -global class Foo { - -} - ]]></code> - </test-code> - - <test-code> - <description>Global interface</description> - <expected-problems>1</expected-problems> - <code><![CDATA[ -global interface Foo { - -} - ]]></code> - </test-code> - - <test-code> - <description>Global method</description> - <expected-problems>1</expected-problems> - <code><![CDATA[ -global class Foo { - global Integer bar() { - - } -} - ]]></code> - </test-code> - - <test-code> - <description>Global inner interface</description> - <expected-problems>1</expected-problems> - <code><![CDATA[ -global class Foo { - global interface Bar { { - - } -} - ]]></code> - </test-code> - -</test-data> diff --git a/pmd-apex/src/test/resources/rulesets/apex/metrics_test.xml b/pmd-apex/src/test/resources/rulesets/apex/metrics_test.xml index 9864f7e3f65..b9980f75716 100644 --- a/pmd-apex/src/test/resources/rulesets/apex/metrics_test.xml +++ b/pmd-apex/src/test/resources/rulesets/apex/metrics_test.xml @@ -1,9 +1,9 @@ <?xml version="1.0"?> <ruleset name="Metrics testing ruleset" - xmlns="http://pmd.sourceforge.net/ruleset/3.0.0" + xmlns="http://pmd.sourceforge.net/ruleset/2.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/3.0.0 http://pmd.sourceforge.net/ruleset_3_0_0.xsd"> + xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd"> <description> Metrics testing ruleset. Each metric is tested through a dummy rule. @@ -11,14 +11,12 @@ <rule name="CycloTest" message = "''{0}'' has value {1}." - class="net.sourceforge.pmd.lang.apex.metrics.impl.CycloTestRule" - metrics="true"> + class="net.sourceforge.pmd.lang.apex.metrics.impl.CycloTestRule"> </rule> <rule name="WmcTest" message = "''{0}'' has value {1}." - class="net.sourceforge.pmd.lang.apex.metrics.impl.WmcTestRule" - metrics="true"> + class="net.sourceforge.pmd.lang.apex.metrics.impl.WmcTestRule"> </rule> </ruleset> diff --git a/pmd-core/etc/xslt/corley-pmd-report.xslt b/pmd-core/etc/xslt/corley-pmd-report.xslt index 5c42a7d2093..181ac61108a 100644 --- a/pmd-core/etc/xslt/corley-pmd-report.xslt +++ b/pmd-core/etc/xslt/corley-pmd-report.xslt @@ -1,6 +1,6 @@ <?xml version="1.0" encoding="UTF-8"?> <!-- $Header$ --> -<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> +<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xpath-default-namespace="http://pmd.sourceforge.net/report/2.0.0" version="2.0"> <xsl:output method="html" encoding="UTF-8" doctype-public="-//W3C//DTD HTML 4.01 Transitional//EN" doctype-system="http://www.w3.org/TR/html4/loose.dtd" indent="yes"/> diff --git a/pmd-core/etc/xslt/cpdhtml.xslt b/pmd-core/etc/xslt/cpdhtml.xslt index 90126772310..e894a8e12bb 100644 --- a/pmd-core/etc/xslt/cpdhtml.xslt +++ b/pmd-core/etc/xslt/cpdhtml.xslt @@ -1,7 +1,7 @@ <?xml version="1.0" encoding="UTF-8"?> <!-- Stylesheet to turn the XML output of CPD into a nice-looking HTML page --> <!-- $Id$ --> -<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> +<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xpath-default-namespace="http://pmd.sourceforge.net/report/2.0.0" version="2.0"> <xsl:output method="html" encoding="UTF-8" doctype-public="-//W3C//DTD HTML 4.01 Transitional//EN" doctype-system="http://www.w3.org/TR/html4/loose.dtd" indent="yes"/> diff --git a/pmd-core/etc/xslt/junit-txt-output.xsl b/pmd-core/etc/xslt/junit-txt-output.xsl index 54cd2ad794f..757d0d1da87 100644 --- a/pmd-core/etc/xslt/junit-txt-output.xsl +++ b/pmd-core/etc/xslt/junit-txt-output.xsl @@ -1,4 +1,4 @@ -<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> +<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xpath-default-namespace="http://pmd.sourceforge.net/report/2.0.0" version="2.0"> <xsl:output method="text"/> <xsl:template match="testsuites"> diff --git a/pmd-core/etc/xslt/only-prio1.xslt b/pmd-core/etc/xslt/only-prio1.xslt index 92b335a8b97..1aa26eb5b9e 100644 --- a/pmd-core/etc/xslt/only-prio1.xslt +++ b/pmd-core/etc/xslt/only-prio1.xslt @@ -1,6 +1,6 @@ <?xml version="1.0" encoding="UTF-8"?> <!-- $Header$ --> -<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> +<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xpath-default-namespace="http://pmd.sourceforge.net/report/2.0.0" version="2.0"> <xsl:output method="html" encoding="UTF-8" doctype-public="-//W3C//DTD HTML 4.01 Transitional//EN" doctype-system="http://www.w3.org/TR/html4/loose.dtd" indent="yes"/> diff --git a/pmd-core/etc/xslt/only-prio2.xslt b/pmd-core/etc/xslt/only-prio2.xslt index bb6dd03b4a8..bf71ecf4caa 100644 --- a/pmd-core/etc/xslt/only-prio2.xslt +++ b/pmd-core/etc/xslt/only-prio2.xslt @@ -1,6 +1,6 @@ <?xml version="1.0" encoding="UTF-8"?> <!-- $Header$ --> -<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> +<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xpath-default-namespace="http://pmd.sourceforge.net/report/2.0.0" version="2.0"> <xsl:output method="html" encoding="UTF-8" doctype-public="-//W3C//DTD HTML 4.01 Transitional//EN" doctype-system="http://www.w3.org/TR/html4/loose.dtd" indent="yes"/> diff --git a/pmd-core/etc/xslt/only-prio3.xslt b/pmd-core/etc/xslt/only-prio3.xslt index 445c4ac868b..6548a9c54bc 100644 --- a/pmd-core/etc/xslt/only-prio3.xslt +++ b/pmd-core/etc/xslt/only-prio3.xslt @@ -1,6 +1,6 @@ <?xml version="1.0" encoding="UTF-8"?> <!-- $Header$ --> -<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> +<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xpath-default-namespace="http://pmd.sourceforge.net/report/2.0.0" version="2.0"> <xsl:output method="html" encoding="UTF-8" doctype-public="-//W3C//DTD HTML 4.01 Transitional//EN" doctype-system="http://www.w3.org/TR/html4/loose.dtd" indent="yes"/> diff --git a/pmd-core/etc/xslt/only-prio4.xslt b/pmd-core/etc/xslt/only-prio4.xslt index 9a60c27642e..e26c2650b99 100644 --- a/pmd-core/etc/xslt/only-prio4.xslt +++ b/pmd-core/etc/xslt/only-prio4.xslt @@ -1,6 +1,6 @@ <?xml version="1.0" encoding="UTF-8"?> <!-- $Header$ --> -<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> +<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xpath-default-namespace="http://pmd.sourceforge.net/report/2.0.0" version="2.0"> <xsl:output method="html" encoding="UTF-8" doctype-public="-//W3C//DTD HTML 4.01 Transitional//EN" doctype-system="http://www.w3.org/TR/html4/loose.dtd" indent="yes"/> diff --git a/pmd-core/etc/xslt/only-prio5.xslt b/pmd-core/etc/xslt/only-prio5.xslt index 80452064247..5e9937cc86d 100644 --- a/pmd-core/etc/xslt/only-prio5.xslt +++ b/pmd-core/etc/xslt/only-prio5.xslt @@ -1,6 +1,6 @@ <?xml version="1.0" encoding="UTF-8"?> <!-- $Header$ --> -<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> +<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xpath-default-namespace="http://pmd.sourceforge.net/report/2.0.0" version="2.0"> <xsl:output method="html" encoding="UTF-8" doctype-public="-//W3C//DTD HTML 4.01 Transitional//EN" doctype-system="http://www.w3.org/TR/html4/loose.dtd" indent="yes"/> diff --git a/pmd-core/etc/xslt/pmd-report-per-class.xslt b/pmd-core/etc/xslt/pmd-report-per-class.xslt index 741c631a32a..68218eea33e 100644 --- a/pmd-core/etc/xslt/pmd-report-per-class.xslt +++ b/pmd-core/etc/xslt/pmd-report-per-class.xslt @@ -1,6 +1,6 @@ <?xml version="1.0" encoding="UTF-8"?> <!-- $Header$ --> -<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> +<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xpath-default-namespace="http://pmd.sourceforge.net/report/2.0.0" version="2.0"> <xsl:output method="html" encoding="UTF-8" doctype-public="-//W3C//DTD HTML 4.01 Transitional//EN" doctype-system="http://www.w3.org/TR/html4/loose.dtd" indent="yes"/> diff --git a/pmd-core/etc/xslt/pmd-report.xslt b/pmd-core/etc/xslt/pmd-report.xslt index d3096a918a7..8a37c85d3bf 100644 --- a/pmd-core/etc/xslt/pmd-report.xslt +++ b/pmd-core/etc/xslt/pmd-report.xslt @@ -1,6 +1,6 @@ <?xml version="1.0" encoding="UTF-8"?> -<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> +<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xpath-default-namespace="http://pmd.sourceforge.net/report/2.0.0" version="2.0"> <xsl:variable name="cvsweb">http://doc.ece.uci.edu/cvs/viewcvs.cgi/Zen/packages/src/</xsl:variable> @@ -13,13 +13,13 @@ table.details tr th { font-weight: bold; text-align:left; background:#a6caf0; } table.details tr td { background:#eeeee0; } table.summary tr th { font-weight: bold; text-align:left; background:#a6caf0; } - table.summary tr td { background:#eeeee0; text-align:center;} + table.summary tr td { background:#eeeee0; text-align:center;} .p1 { background:#FF9999; } .p2 { background:#FFCC66; } .p3 { background:#FFFF99; } .p4 { background:#99FF99; } - .p5 { background:#9999FF; } - + .p5 { background:#9999FF; } + </style> </head> <body> @@ -34,7 +34,7 @@ <th>Priority 2</th> <th>Priority 3</th> <th>Priority 4</th> - <th>Priority 5</th> + <th>Priority 5</th> </tr> <tr> <td><xsl:value-of select="count(//file)"/></td> @@ -43,7 +43,7 @@ <td><div class="p2"><xsl:value-of select="count(//violation[@priority = 2])"/></div></td> <td><div class="p3"><xsl:value-of select="count(//violation[@priority = 3])"/></div></td> <td><div class="p4"><xsl:value-of select="count(//violation[@priority = 4])"/></div></td> - <td><div class="p5"><xsl:value-of select="count(//violation[@priority = 5])"/></div></td> + <td><div class="p5"><xsl:value-of select="count(//violation[@priority = 5])"/></div></td> </tr> </table> <hr/> @@ -56,14 +56,14 @@ <th>Begin Line</th> <th align="left">Description</th> </tr> - + <xsl:for-each select="violation"> <tr> <td style="padding: 3px" align="right"><a><xsl:attribute name="href"><xsl:value-of select="$cvsweb"/><xsl:value-of select="$filename"/>?annotate=HEAD#<xsl:value-of disable-output-escaping="yes" select="@beginline"/></xsl:attribute><xsl:value-of disable-output-escaping="yes" select="@beginline"/></a></td> <td style="padding: 3px" align="left" width="100%"><xsl:value-of disable-output-escaping="yes" select="."/></td> </tr> </xsl:for-each> - + </table> <br/> </xsl:for-each> diff --git a/pmd-core/etc/xslt/pmd-xmlfilter-prio1.xsl b/pmd-core/etc/xslt/pmd-xmlfilter-prio1.xsl index d758c7b372e..92c057bf75a 100644 --- a/pmd-core/etc/xslt/pmd-xmlfilter-prio1.xsl +++ b/pmd-core/etc/xslt/pmd-xmlfilter-prio1.xsl @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="UTF-8"?> -<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" > +<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xpath-default-namespace="http://pmd.sourceforge.net/report/2.0.0" version="2.0"> <xsl:output method="xml"/> <xsl:template match="node() | @*"> <xsl:copy> diff --git a/pmd-core/etc/xslt/pmd-xmlfilter-prio2.xsl b/pmd-core/etc/xslt/pmd-xmlfilter-prio2.xsl index 64151365157..8887278f3eb 100644 --- a/pmd-core/etc/xslt/pmd-xmlfilter-prio2.xsl +++ b/pmd-core/etc/xslt/pmd-xmlfilter-prio2.xsl @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="UTF-8"?> -<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" > +<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xpath-default-namespace="http://pmd.sourceforge.net/report/2.0.0" version="2.0"> <xsl:output method="xml"/> <xsl:template match="node() | @*"> <xsl:copy> diff --git a/pmd-core/etc/xslt/pmd-xmlfilter-prio3.xsl b/pmd-core/etc/xslt/pmd-xmlfilter-prio3.xsl index b37e001375d..fb3441da835 100644 --- a/pmd-core/etc/xslt/pmd-xmlfilter-prio3.xsl +++ b/pmd-core/etc/xslt/pmd-xmlfilter-prio3.xsl @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="UTF-8"?> -<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" > +<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xpath-default-namespace="http://pmd.sourceforge.net/report/2.0.0" version="2.0"> <xsl:output method="xml"/> <xsl:template match="node() | @*"> <xsl:copy> diff --git a/pmd-core/etc/xslt/pmd-xmlfilter-prio4.xsl b/pmd-core/etc/xslt/pmd-xmlfilter-prio4.xsl index 91c0e825ea1..76de4311150 100644 --- a/pmd-core/etc/xslt/pmd-xmlfilter-prio4.xsl +++ b/pmd-core/etc/xslt/pmd-xmlfilter-prio4.xsl @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="UTF-8"?> -<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" > +<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xpath-default-namespace="http://pmd.sourceforge.net/report/2.0.0" version="2.0"> <xsl:output method="xml"/> <xsl:template match="node() | @*"> <xsl:copy> diff --git a/pmd-core/etc/xslt/pmd-xmlfilter-prio5.xsl b/pmd-core/etc/xslt/pmd-xmlfilter-prio5.xsl index e8ca43b453c..09df4562c44 100644 --- a/pmd-core/etc/xslt/pmd-xmlfilter-prio5.xsl +++ b/pmd-core/etc/xslt/pmd-xmlfilter-prio5.xsl @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="UTF-8"?> -<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" > +<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xpath-default-namespace="http://pmd.sourceforge.net/report/2.0.0" version="2.0"> <xsl:output method="xml"/> <xsl:template match="node() | @*"> <xsl:copy> diff --git a/pmd-core/etc/xslt/pmd-xmlfilter-upto-prio2.xsl b/pmd-core/etc/xslt/pmd-xmlfilter-upto-prio2.xsl index 11ea0d8c992..aaad9197844 100644 --- a/pmd-core/etc/xslt/pmd-xmlfilter-upto-prio2.xsl +++ b/pmd-core/etc/xslt/pmd-xmlfilter-upto-prio2.xsl @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="UTF-8"?> -<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" > +<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xpath-default-namespace="http://pmd.sourceforge.net/report/2.0.0" version="2.0"> <xsl:output method="xml"/> <xsl:template match="node() | @*"> <xsl:copy> diff --git a/pmd-core/etc/xslt/pmd-xmlfilter-upto-prio3.xsl b/pmd-core/etc/xslt/pmd-xmlfilter-upto-prio3.xsl index a64fac7db99..28db995f0ea 100644 --- a/pmd-core/etc/xslt/pmd-xmlfilter-upto-prio3.xsl +++ b/pmd-core/etc/xslt/pmd-xmlfilter-upto-prio3.xsl @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="UTF-8"?> -<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" > +<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xpath-default-namespace="http://pmd.sourceforge.net/report/2.0.0" version="2.0"> <xsl:output method="xml"/> <xsl:template match="node() | @*"> <xsl:copy> diff --git a/pmd-core/etc/xslt/pmd-xmlfilter-upto-prio4.xsl b/pmd-core/etc/xslt/pmd-xmlfilter-upto-prio4.xsl index 9d256d3bd41..f497c45e021 100644 --- a/pmd-core/etc/xslt/pmd-xmlfilter-upto-prio4.xsl +++ b/pmd-core/etc/xslt/pmd-xmlfilter-upto-prio4.xsl @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="UTF-8"?> -<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" > +<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xpath-default-namespace="http://pmd.sourceforge.net/report/2.0.0" version="2.0"> <xsl:output method="xml"/> <xsl:template match="node() | @*"> <xsl:copy> diff --git a/pmd-core/etc/xslt/wz-pmd-report.xslt b/pmd-core/etc/xslt/wz-pmd-report.xslt index 8511ac37ce3..6abe9af9cfe 100644 --- a/pmd-core/etc/xslt/wz-pmd-report.xslt +++ b/pmd-core/etc/xslt/wz-pmd-report.xslt @@ -1,6 +1,6 @@ <?xml version="1.0" encoding="UTF-8"?> <!-- $Header$ --> -<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> +<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xpath-default-namespace="http://pmd.sourceforge.net/report/2.0.0" version="2.0"> <xsl:output method="html" encoding="UTF-8" doctype-public="-//W3C//DTD HTML 4.01 Transitional//EN" doctype-system="http://www.w3.org/TR/html4/loose.dtd" indent="yes"/> diff --git a/pmd-core/pom.xml b/pmd-core/pom.xml index 9262c561106..60d278817ab 100644 --- a/pmd-core/pom.xml +++ b/pmd-core/pom.xml @@ -7,13 +7,9 @@ <parent> <groupId>net.sourceforge.pmd</groupId> <artifactId>pmd</artifactId> - <version>6.0.0-SNAPSHOT</version> + <version>6.10.0-SNAPSHOT</version> </parent> - <properties> - <config.basedir>${basedir}</config.basedir> - </properties> - <build> <plugins> <plugin> @@ -64,6 +60,34 @@ <suppressionsLocation>pmd-core-checkstyle-suppressions.xml</suppressionsLocation> </configuration> </plugin> + + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-shade-plugin</artifactId> + <configuration> + <artifactSet> + <includes>jaxen:jaxen</includes> + </artifactSet> + <filters> + <filter> + <artifact>jaxen:jaxen</artifact> + <includes> + <include>org/jaxen/**</include> + </includes> + <excludes> + <exclude>org/w3c/dom/**</exclude> + </excludes> + </filter> + </filters> + </configuration> + <executions> + <execution> + <goals> + <goal>shade</goal> + </goals> + </execution> + </executions> + </plugin> </plugins> </build> <dependencies> @@ -72,7 +96,10 @@ <artifactId>ant</artifactId> <scope>provided</scope> </dependency> - + <dependency> + <groupId>org.antlr</groupId> + <artifactId>antlr4-runtime</artifactId> + </dependency> <dependency> <groupId>com.beust</groupId> <artifactId>jcommander</artifactId> @@ -84,6 +111,15 @@ <dependency> <groupId>jaxen</groupId> <artifactId>jaxen</artifactId> + <!-- + Jaxen is optionally, so that it doesn't get inherited by default. + Jaxen is shaded into pmd-core (without org.w3c.dom.UserDataHandler). + Adding Jaxen again as a dependency would reintroduce the duplicated + org.w3c.dom.UserDataHandler interface. + See https://github.com/pmd/pmd/issues/1074 + and https://github.com/pmd/pmd/pull/1201 + --> + <optional>true</optional> </dependency> <dependency> <groupId>net.java.dev.javacc</groupId> @@ -125,6 +161,16 @@ <artifactId>junit</artifactId> <scope>test</scope> </dependency> + <dependency> + <groupId>org.hamcrest</groupId> + <artifactId>hamcrest-library</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>pl.pragmatists</groupId> + <artifactId>JUnitParams</artifactId> + <scope>test</scope> + </dependency> <dependency> <groupId>org.apache.ant</groupId> <artifactId>ant-testutil</artifactId> @@ -141,4 +187,45 @@ <scope>test</scope> </dependency> </dependencies> + + <profiles> + <profile> + <id>m2e</id> + <!-- This profile is only activated when building in Eclipse with m2e --> + <activation> + <property> + <name>m2e.version</name> + </property> + </activation> + <dependencies> + <!-- + the maven-shade-plugin runs during package phase, + but inside eclipse, this phase is not executed during development. + Therefore this profile adds back the jaxen dependency in eclipse. + --> + <dependency> + <groupId>jaxen</groupId> + <artifactId>jaxen</artifactId> + <optional>false</optional> + </dependency> + </dependencies> + </profile> + <profile> + <id>idea</id> + <!-- This profile is only activated when building in IntelliJ --> + <!-- It serves the same purpose as the above for eclipse--> + <activation> + <property> + <name>idea.maven.embedder.version</name> + </property> + </activation> + <dependencies> + <dependency> + <groupId>jaxen</groupId> + <artifactId>jaxen</artifactId> + <optional>false</optional> + </dependency> + </dependencies> + </profile> + </profiles> </project> diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/AbstractConfiguration.java b/pmd-core/src/main/java/net/sourceforge/pmd/AbstractConfiguration.java index bededb0f956..671cf74da13 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/AbstractConfiguration.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/AbstractConfiguration.java @@ -4,6 +4,8 @@ package net.sourceforge.pmd; +import java.nio.charset.Charset; + /** * Base configuration class for both PMD and CPD. * @@ -11,7 +13,7 @@ */ public abstract class AbstractConfiguration { - private String sourceEncoding = System.getProperty("file.encoding"); + private Charset sourceEncoding = Charset.forName(System.getProperty("file.encoding")); private boolean debug; /** @@ -26,7 +28,7 @@ protected AbstractConfiguration() { * * @return The character encoding. */ - public String getSourceEncoding() { + public Charset getSourceEncoding() { return sourceEncoding; } @@ -37,7 +39,7 @@ public String getSourceEncoding() { * The character encoding. */ public void setSourceEncoding(String sourceEncoding) { - this.sourceEncoding = sourceEncoding; + this.sourceEncoding = Charset.forName(sourceEncoding); } /** diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/PMD.java b/pmd-core/src/main/java/net/sourceforge/pmd/PMD.java index b6ba7ec6b74..7372f159060 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/PMD.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/PMD.java @@ -6,7 +6,8 @@ import java.io.File; import java.io.IOException; -import java.io.InputStream; +import java.io.OutputStreamWriter; +import java.io.Writer; import java.net.URISyntaxException; import java.sql.SQLException; import java.util.ArrayList; @@ -14,18 +15,19 @@ import java.util.Comparator; import java.util.HashSet; import java.util.List; -import java.util.Properties; import java.util.Set; import java.util.concurrent.atomic.AtomicInteger; -import java.util.logging.Handler; +import java.util.logging.ConsoleHandler; import java.util.logging.Level; import java.util.logging.Logger; -import org.apache.commons.io.IOUtils; - -import net.sourceforge.pmd.benchmark.Benchmark; -import net.sourceforge.pmd.benchmark.Benchmarker; -import net.sourceforge.pmd.benchmark.TextReport; +import net.sourceforge.pmd.benchmark.TextTimingReportRenderer; +import net.sourceforge.pmd.benchmark.TimeTracker; +import net.sourceforge.pmd.benchmark.TimedOperation; +import net.sourceforge.pmd.benchmark.TimedOperationCategory; +import net.sourceforge.pmd.benchmark.TimingReport; +import net.sourceforge.pmd.benchmark.TimingReportRenderer; +import net.sourceforge.pmd.cache.NoopAnalysisCache; import net.sourceforge.pmd.cli.PMDCommandLineInterface; import net.sourceforge.pmd.cli.PMDParameters; import net.sourceforge.pmd.lang.Language; @@ -42,12 +44,12 @@ import net.sourceforge.pmd.util.ClasspathClassLoader; import net.sourceforge.pmd.util.FileUtil; import net.sourceforge.pmd.util.IOUtil; +import net.sourceforge.pmd.util.ResourceLoader; import net.sourceforge.pmd.util.database.DBMSMetadata; import net.sourceforge.pmd.util.database.DBURI; import net.sourceforge.pmd.util.database.SourceObject; import net.sourceforge.pmd.util.datasource.DataSource; import net.sourceforge.pmd.util.datasource.ReaderDataSource; -import net.sourceforge.pmd.util.log.ConsoleLogHandler; import net.sourceforge.pmd.util.log.ScopedLogHandlersManager; /** @@ -78,8 +80,10 @@ public class PMD { /** * Constant that contains always the current version of PMD. + * @deprecated Use {@link PMDVersion#VERSION} instead. */ - public static final String VERSION; + @Deprecated // to be removed with PMD 7.0.0. + public static final String VERSION = PMDVersion.VERSION; /** * Create a PMD instance using a default Configuration. Changes to the @@ -168,54 +172,6 @@ public static Parser parserFor(LanguageVersion languageVersion, PMDConfiguration return languageVersionHandler.getParser(options); } - /** - * Create a report, filter out any defective rules, and keep a record of - * them. - * - * @param rs - * the rules - * @param ctx - * the rule context - * @param fileName - * the filename of the source file, which should appear in the - * report - * @return the Report - */ - public static Report setupReport(RuleSets rs, RuleContext ctx, String fileName) { - - Set<Rule> brokenRules = removeBrokenRules(rs); - Report report = Report.createReport(ctx, fileName); - - for (Rule rule : brokenRules) { - report.addConfigError(new Report.ConfigurationError(rule, rule.dysfunctionReason())); - } - - return report; - } - - /** - * Remove and return the misconfigured rules from the rulesets and log them - * for good measure. - * - * @param ruleSets - * RuleSets - * @return Set<Rule> - */ - private static Set<Rule> removeBrokenRules(RuleSets ruleSets) { - - Set<Rule> brokenRules = new HashSet<>(); - ruleSets.removeDysfunctionalRules(brokenRules); - - for (Rule rule : brokenRules) { - if (LOG.isLoggable(Level.WARNING)) { - LOG.log(Level.WARNING, - "Removed misconfigured rule: " + rule.getName() + " cause: " + rule.dysfunctionReason()); - } - } - - return brokenRules; - } - /** * Get the runtime configuration. The configuration can be modified to * affect how PMD behaves. @@ -246,7 +202,7 @@ public SourceCodeProcessor getSourceCodeProcessor() { public static int doPMD(PMDConfiguration configuration) { // Load the RuleSets - RuleSetFactory ruleSetFactory = RulesetsFactoryUtils.getRulesetFactory(configuration); + RuleSetFactory ruleSetFactory = RulesetsFactoryUtils.getRulesetFactory(configuration, new ResourceLoader()); RuleSets ruleSets = RulesetsFactoryUtils.getRuleSetsWithBenchmark(configuration.getRuleSets(), ruleSetFactory); if (ruleSets == null) { return 0; @@ -255,35 +211,40 @@ public static int doPMD(PMDConfiguration configuration) { Set<Language> languages = getApplicableLanguages(configuration, ruleSets); List<DataSource> files = getApplicableFiles(configuration, languages); - long reportStart = System.nanoTime(); try { - Renderer renderer = configuration.createRenderer(); - List<Renderer> renderers = Collections.singletonList(renderer); - - renderer.setWriter(IOUtil.createWriter(configuration.getReportFile())); - renderer.start(); - - Benchmarker.mark(Benchmark.Reporting, System.nanoTime() - reportStart, 0); + Renderer renderer; + List<Renderer> renderers; + try (TimedOperation to = TimeTracker.startOperation(TimedOperationCategory.REPORTING)) { + renderer = configuration.createRenderer(); + renderers = Collections.singletonList(renderer); + + renderer.setWriter(IOUtil.createWriter(configuration.getReportFile())); + renderer.start(); + } RuleContext ctx = new RuleContext(); final AtomicInteger violations = new AtomicInteger(0); ctx.getReport().addListener(new ThreadSafeReportListener() { @Override public void ruleViolationAdded(RuleViolation ruleViolation) { - violations.incrementAndGet(); + violations.getAndIncrement(); } @Override public void metricAdded(Metric metric) { + // ignored - not needed for counting violations } }); - processFiles(configuration, ruleSetFactory, files, ctx, renderers); + try (TimedOperation to = TimeTracker.startOperation(TimedOperationCategory.FILE_PROCESSING)) { + processFiles(configuration, ruleSetFactory, files, ctx, renderers); + } - reportStart = System.nanoTime(); - renderer.end(); - renderer.flush(); - return violations.get(); + try (TimedOperation rto = TimeTracker.startOperation(TimedOperationCategory.REPORTING)) { + renderer.end(); + renderer.flush(); + return violations.get(); + } } catch (Exception e) { String message = e.getMessage(); if (message != null) { @@ -295,8 +256,6 @@ public void metricAdded(Metric metric) { LOG.info(PMDCommandLineInterface.buildUsageText()); return 0; } finally { - Benchmarker.mark(Benchmark.Reporting, System.nanoTime() - reportStart, 0); - /* * Make sure it's our own classloader before attempting to close it.... * Maven + Jacoco provide us with a cloaseable classloader that if closed @@ -344,6 +303,14 @@ public static RuleContext newRuleContext(String sourceCodeFilename, File sourceC public static void processFiles(final PMDConfiguration configuration, final RuleSetFactory ruleSetFactory, final List<DataSource> files, final RuleContext ctx, final List<Renderer> renderers) { + if (!configuration.isIgnoreIncrementalAnalysis() + && configuration.getAnalysisCache() instanceof NoopAnalysisCache + && LOG.isLoggable(Level.WARNING)) { + final String version = PMDVersion.isUnknown() || PMDVersion.isSnapshot() ? "latest" : "pmd-" + PMDVersion.VERSION; + LOG.warning("This analysis could be faster, please consider using Incremental Analysis: " + + "https://pmd.github.io/" + version + "/pmd_userdocs_incremental_analysis.html"); + } + sortFiles(configuration, files); // Make sure the cache is listening for analysis results @@ -395,11 +362,9 @@ public int compare(DataSource left, DataSource right) { * @return List of {@link DataSource} of files */ public static List<DataSource> getApplicableFiles(PMDConfiguration configuration, Set<Language> languages) { - long startFiles = System.nanoTime(); - List<DataSource> files = internalGetApplicableFiles(configuration, languages); - long endFiles = System.nanoTime(); - Benchmarker.mark(Benchmark.CollectFiles, endFiles - startFiles, 0); - return files; + try (TimedOperation to = TimeTracker.startOperation(TimedOperationCategory.COLLECT_FILES)) { + return internalGetApplicableFiles(configuration, languages); + } } private static List<DataSource> internalGetApplicableFiles(PMDConfiguration configuration, @@ -440,6 +405,23 @@ private static List<DataSource> internalGetApplicableFiles(PMDConfiguration conf } } + + if (null != configuration.getIgnoreFilePath()) { + String ignoreFilePath = configuration.getIgnoreFilePath(); + File file = new File(ignoreFilePath); + try { + if (!file.exists()) { + LOG.log(Level.SEVERE, "Problem with Ignore File Path", ignoreFilePath); + throw new RuntimeException("Problem with Ignore File Path: " + ignoreFilePath); + } else { + String filePaths = FileUtil.readFilelist(new File(ignoreFilePath)); + files.removeAll(FileUtil.collectFiles(filePaths, fileSelector)); + } + } catch (IOException ex) { + LOG.log(Level.SEVERE, "Problem with Ignore File", ex); + throw new RuntimeException("Problem with Ignore File Path: " + ignoreFilePath, ex); + } + } return files; } @@ -483,14 +465,17 @@ public static void main(String[] args) { * violations found. */ public static int run(String[] args) { - int status = 0; - long start = System.nanoTime(); final PMDParameters params = PMDCommandLineInterface.extractParameters(new PMDParameters(), args, "pmd"); - final PMDConfiguration configuration = PMDParameters.transformParametersIntoConfiguration(params); + + if (params.isBenchmark()) { + TimeTracker.startGlobalTracking(); + } + + int status = 0; + final PMDConfiguration configuration = params.toConfiguration(); final Level logLevel = params.isDebug() ? Level.FINER : Level.INFO; - final Handler logHandler = new ConsoleLogHandler(); - final ScopedLogHandlersManager logHandlerManager = new ScopedLogHandlersManager(logLevel, logHandler); + final ScopedLogHandlersManager logHandlerManager = new ScopedLogHandlersManager(logLevel, new ConsoleHandler()); final Level oldLogLevel = LOG.getLevel(); // Need to do this, since the static logger has already been initialized // at this point @@ -511,40 +496,21 @@ public static int run(String[] args) { } finally { logHandlerManager.close(); LOG.setLevel(oldLogLevel); + if (params.isBenchmark()) { - long end = System.nanoTime(); - Benchmarker.mark(Benchmark.TotalPMD, end - start, 0); + final TimingReport timingReport = TimeTracker.stopGlobalTracking(); // TODO get specified report format from config - TextReport report = new TextReport(); - - report.generate(Benchmarker.values(), System.err); + final TimingReportRenderer renderer = new TextTimingReportRenderer(); + try { + // Don't close this writer, we don't want to close stderr + final Writer writer = new OutputStreamWriter(System.err); + renderer.render(timingReport, writer); + } catch (final IOException e) { + System.err.println(e.getMessage()); + } } } return status; } - - /** - * Determines the version from maven's generated pom.properties file. - */ - static { - String pmdVersion = null; - InputStream stream = PMD.class - .getResourceAsStream("/META-INF/maven/net.sourceforge.pmd/pmd-core/pom.properties"); - if (stream != null) { - try { - Properties properties = new Properties(); - properties.load(stream); - pmdVersion = properties.getProperty("version"); - } catch (IOException e) { - LOG.log(Level.FINE, "Couldn't determine version of PMD", e); - } finally { - IOUtils.closeQuietly(stream); - } - } - if (pmdVersion == null) { - pmdVersion = "unknown"; - } - VERSION = pmdVersion; - } } diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/PMDConfiguration.java b/pmd-core/src/main/java/net/sourceforge/pmd/PMDConfiguration.java index c59ed57c779..dd179c05bcf 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/PMDConfiguration.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/PMDConfiguration.java @@ -80,7 +80,6 @@ * </ul> */ public class PMDConfiguration extends AbstractConfiguration { - // General behavior options private String suppressMarker = PMD.SUPPRESS_MARKER; private int threads = Runtime.getRuntime().availableProcessors(); @@ -93,6 +92,7 @@ public class PMDConfiguration extends AbstractConfiguration { private String inputPaths; private String inputUri; private String inputFilePath; + private String ignoreFilePath; private boolean ruleSetFactoryCompatibilityEnabled = true; // Reporting options @@ -106,6 +106,7 @@ public class PMDConfiguration extends AbstractConfiguration { private boolean stressTest; private boolean benchmark; private AnalysisCache analysisCache = new NoopAnalysisCache(); + private boolean ignoreIncrementalAnalysis; /** * Get the suppress marker. This is the source level marker used to indicate @@ -315,6 +316,10 @@ public String getInputFilePath() { return inputFilePath; } + public String getIgnoreFilePath() { + return ignoreFilePath; + } + /** * The input file path points to a single file, which contains a * comma-separated list of source file names to process. @@ -326,6 +331,17 @@ public void setInputFilePath(String inputFilePath) { this.inputFilePath = inputFilePath; } + /** + * The input file path points to a single file, which contains a + * comma-separated list of source file names to ignore. + * + * @param ignoreFilePath + * path to the file + */ + public void setIgnoreFilePath(String ignoreFilePath) { + this.ignoreFilePath = ignoreFilePath; + } + /** * Get the input URI to process for source code objects. * @@ -552,8 +568,7 @@ public boolean isRuleSetFactoryCompatibilityEnabled() { /** * Sets the rule set factory compatibility feature enabled/disabled. * - * @param ruleSetFactoryCompatibilityEnabled - * <code>true</code> if the feature should be enabled + * @param ruleSetFactoryCompatibilityEnabled {@code true} if the feature should be enabled * * @see RuleSetFactoryCompatibility */ @@ -567,21 +582,27 @@ public void setRuleSetFactoryCompatibilityEnabled(boolean ruleSetFactoryCompatib * @return The currently used analysis cache. Never null. */ public AnalysisCache getAnalysisCache() { + // Make sure we are not null + if (analysisCache == null || isIgnoreIncrementalAnalysis() && !(analysisCache instanceof NoopAnalysisCache)) { + // sets a noop cache + setAnalysisCache(new NoopAnalysisCache()); + } + return analysisCache; } /** * Sets the analysis cache to be used. Setting a - * value of <code>null</code> will cause a Noop AnalysisCache to be used. - * + * value of {@code null} will cause a Noop AnalysisCache to be used. + * If incremental analysis was explicitly disabled ({@link #isIgnoreIncrementalAnalysis()}), + * then this method is a noop. + * * @param cache The analysis cache to be used. */ public void setAnalysisCache(final AnalysisCache cache) { - if (cache == null) { - analysisCache = new NoopAnalysisCache(); - } else { - analysisCache = cache; - } + // the doc says it's a noop if incremental analysis was disabled, + // but it's actually the getter that enforces that + this.analysisCache = cache == null ? new NoopAnalysisCache() : cache; } /** @@ -591,10 +612,32 @@ public void setAnalysisCache(final AnalysisCache cache) { * @param cacheLocation The location of the analysis cache to be used. */ public void setAnalysisCacheLocation(final String cacheLocation) { - if (cacheLocation == null) { - setAnalysisCache(new NoopAnalysisCache()); - } else { - setAnalysisCache(new FileAnalysisCache(new File(cacheLocation))); - } + setAnalysisCache(cacheLocation == null + ? new NoopAnalysisCache() + : new FileAnalysisCache(new File(cacheLocation))); + } + + + /** + * Sets whether the user has explicitly disabled incremental analysis or not. + * If so, incremental analysis is not used, and all suggestions to use it are + * disabled. The analysis cached location is ignored, even if it's specified. + * + * @param noCache Whether to ignore incremental analysis or not + */ + public void setIgnoreIncrementalAnalysis(boolean noCache) { + // see #getAnalysisCache for the implementation. + this.ignoreIncrementalAnalysis = noCache; + } + + + /** + * Returns whether incremental analysis was explicitly disabled by the user + * or not. + * + * @return {@code true} if incremental analysis is explicitly disabled + */ + public boolean isIgnoreIncrementalAnalysis() { + return ignoreIncrementalAnalysis; } } diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/PMDVersion.java b/pmd-core/src/main/java/net/sourceforge/pmd/PMDVersion.java new file mode 100644 index 00000000000..3e50b27392a --- /dev/null +++ b/pmd-core/src/main/java/net/sourceforge/pmd/PMDVersion.java @@ -0,0 +1,79 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Properties; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * Stores the current PMD version and provides utility methods around it. + */ +public final class PMDVersion { + + private static final Logger LOG = Logger.getLogger(PMDVersion.class.getName()); + + /** + * Constant that contains always the current version of PMD. + */ + public static final String VERSION; + + private static final String UNKNOWN_VERSION = "unknown"; + + /** + * Determines the version from maven's generated pom.properties file. + */ + static { + String pmdVersion = UNKNOWN_VERSION; + try (InputStream stream = PMDVersion.class.getResourceAsStream("/META-INF/maven/net.sourceforge.pmd/pmd-core/pom.properties")) { + if (stream != null) { + final Properties properties = new Properties(); + properties.load(stream); + pmdVersion = properties.getProperty("version"); + } + } catch (final IOException e) { + LOG.log(Level.FINE, "Couldn't determine version of PMD", e); + } + + VERSION = pmdVersion; + } + + private PMDVersion() { + throw new AssertionError("Can't instantiate utility classes"); + } + + /** + * Retrieves the next major release to be expected. + * Useful when logging deprecation messages to indicate when support will be removed. + * + * @return The next major release to be expected. + */ + public static String getNextMajorRelease() { + if (isUnknown()) { + return UNKNOWN_VERSION; + } + + final int major = Integer.parseInt(VERSION.split("\\.")[0]); + return (major + 1) + ".0.0"; + } + + /** + * Checks if the current version is unknown. + * @return True if an unknown version, false otherwise + */ + public static boolean isUnknown() { + return UNKNOWN_VERSION.equals(VERSION); + } + + /** + * Checks if the current version is a snapshot. + * @return True if a snapshot release, false otherwise + */ + public static boolean isSnapshot() { + return VERSION.endsWith("-SNAPSHOT"); + } +} diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/Report.java b/pmd-core/src/main/java/net/sourceforge/pmd/Report.java index 0ed214c74a7..ff2a29a6720 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/Report.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/Report.java @@ -169,10 +169,9 @@ public String getDetail() { error.printStackTrace(writer); return stringWriter.toString(); } catch (IOException e) { - // can never happen when using StringWriter + // IOException on close - should never happen when using StringWriter + throw new RuntimeException(e); } - - return null; } public String getFile() { diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/Rule.java b/pmd-core/src/main/java/net/sourceforge/pmd/Rule.java index 222b1da10ff..3fc13e727cb 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/Rule.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/Rule.java @@ -112,6 +112,7 @@ public interface Rule extends PropertySource { * * @return the name */ + @Override String getName(); /** @@ -257,54 +258,107 @@ public interface Rule extends PropertySource { /** * Sets whether this Rule uses Data Flow Analysis. + * @deprecated Use {@link #setDfa(boolean)} instead. */ - // FUTURE Use JavaBean conventions for boolean attributes + @Deprecated // To be removed in PMD 7.0.0 void setUsesDFA(); + /** + * Sets whether this Rule uses Data Flow Analysis. + */ + void setDfa(boolean isDfa); + /** * Gets whether this Rule uses Data Flow Analysis. * * @return <code>true</code> if Data Flow Analysis is used. + * @deprecated Use {@link #isDfa()} instead. */ - // FUTURE Use JavaBean conventions for boolean attributes + @Deprecated // To be removed in PMD 7.0.0 boolean usesDFA(); + /** + * Gets whether this Rule uses Data Flow Analysis. + * + * @return <code>true</code> if Data Flow Analysis is used. + */ + boolean isDfa(); + /** * Sets whether this Rule uses Type Resolution. + * @deprecated Use {@link #setTypeResolution(boolean)} instead. */ - // FUTURE Use JavaBean conventions for boolean attributes + @Deprecated // To be removed in PMD 7.0.0 void setUsesTypeResolution(); + /** + * Sets whether this Rule uses Type Resolution. + */ + void setTypeResolution(boolean usingTypeResolution); + /** * Gets whether this Rule uses Type Resolution. * * @return <code>true</code> if Type Resolution is used. + * + * @deprecated Use {@link #isTypeResolution()} instead */ - // FUTURE Use JavaBean conventions for boolean attributes + @Deprecated // To be removed in PMD 7.0.0 boolean usesTypeResolution(); /** - * Sets whether this Rule uses Object-Oriented Metrics. + * Gets whether this Rule uses Type Resolution. + * + * @return <code>true</code> if Type Resolution is used. + */ + boolean isTypeResolution(); + + /** + * Sets whether this Rule uses multi-file analysis. + * @deprecated use {@link #setMultifile(boolean)} instead. + */ + @Deprecated // To be removed in PMD 7.0.0 + void setUsesMultifile(); + + /** + * Sets whether this Rule uses multi-file analysis. + */ + void setMultifile(boolean multifile); + + /** + * Gets whether this Rule uses multi-file analysis. + * + * @return <code>true</code> if the multi file analysis is used. + * + * @deprecated Use {@link #isMultifile()} instead. */ - // FUTURE Use JavaBean conventions for boolean attributes - void setUsesMetrics(); + @Deprecated // To be removed in PMD 7.0.0 + boolean usesMultifile(); /** - * Gets whether this Rule uses Object-Oriented Metrics. + * Gets whether this Rule uses multi-file analysis. * - * @return <code>true</code> if the Metrics Framework is used. + * @return <code>true</code> if the multi file analysis is used. */ - // FUTURE Use JavaBean conventions for boolean attributes - boolean usesMetrics(); + boolean isMultifile(); /** * Gets whether this Rule uses the RuleChain. * * @return <code>true</code> if RuleChain is used. + * + * @deprecated USe {@link #isRuleChain()} instead. */ - // FUTURE Use JavaBean conventions for boolean attributes + @Deprecated // To be removed in PMD 7.0.0 boolean usesRuleChain(); + /** + * Gets whether this Rule uses the RuleChain. + * + * @return <code>true</code> if RuleChain is used. + */ + boolean isRuleChain(); + /** * Gets the collection of AST node names visited by the Rule on the * RuleChain. diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/RuleSet.java b/pmd-core/src/main/java/net/sourceforge/pmd/RuleSet.java index 1fbefe1d9f5..71d5bc24a5a 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/RuleSet.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/RuleSet.java @@ -18,8 +18,9 @@ import org.apache.commons.lang3.StringUtils; -import net.sourceforge.pmd.benchmark.Benchmark; -import net.sourceforge.pmd.benchmark.Benchmarker; +import net.sourceforge.pmd.benchmark.TimeTracker; +import net.sourceforge.pmd.benchmark.TimedOperation; +import net.sourceforge.pmd.benchmark.TimedOperationCategory; import net.sourceforge.pmd.cache.ChecksumAware; import net.sourceforge.pmd.lang.Language; import net.sourceforge.pmd.lang.LanguageVersion; @@ -57,12 +58,9 @@ public class RuleSet implements ChecksumAware { /** * Creates a new RuleSet with the given checksum. - * - * @param checksum - * A checksum of the ruleset, should change only if the ruleset - * was configured differently - * @param rules - * The rules to be applied as part of this ruleset + * + * @param builder + * A rule set builder. */ private RuleSet(final RuleSetBuilder builder) { checksum = builder.checksum; @@ -122,15 +120,26 @@ public RuleSet(final RuleSet rs) { * Add a new rule to this ruleset. Note that this method does not check * for duplicates. * - * @param rule + * @param newRule * the rule to be added * @return The same builder, for a fluid programming interface */ - public RuleSetBuilder addRule(final Rule rule) { - if (rule == null) { + public RuleSetBuilder addRule(final Rule newRule) { + if (newRule == null) { throw new IllegalArgumentException(MISSING_RULE); } - rules.add(rule); + + // check for duplicates - adding more than one rule with the same name will + // be problematic - see #RuleSet.getRuleByName(String) + for (Rule rule : rules) { + if (rule.getName().equals(newRule.getName()) && rule.getLanguage() == newRule.getLanguage()) { + LOG.warning("The rule with name " + newRule.getName() + " is duplicated. " + + "Future versions of PMD will reject to load such rulesets."); + break; + } + } + + rules.add(newRule); return this; } @@ -163,15 +172,22 @@ public RuleSetBuilder addRuleReplaceIfExists(final Rule rule) { * same language was added before, so that the existent rule * configuration won't be overridden. * - * @param rule + * @param ruleOrRef * the new rule to add * @return The same builder, for a fluid programming interface */ - public RuleSetBuilder addRuleIfNotExists(final Rule rule) { - if (rule == null) { + public RuleSetBuilder addRuleIfNotExists(final Rule ruleOrRef) { + if (ruleOrRef == null) { throw new IllegalArgumentException(MISSING_RULE); } + // resolve the underlying rule, to avoid adding duplicated rules + // if the rule has been renamed/merged and moved at the same time + Rule rule = ruleOrRef; + while (rule instanceof RuleReference) { + rule = ((RuleReference) rule).getRule(); + } + boolean exists = false; for (final Rule r : rules) { if (r.getName().equals(rule.getName()) && r.getLanguage() == rule.getLanguage()) { @@ -180,7 +196,7 @@ public RuleSetBuilder addRuleIfNotExists(final Rule rule) { } } if (!exists) { - addRule(rule); + addRule(ruleOrRef); } return this; } @@ -207,9 +223,7 @@ public RuleSetBuilder addRuleByReference(final String ruleSetFileName, final Rul ruleReference = (RuleReference) rule; } else { final RuleSetReference ruleSetReference = new RuleSetReference(ruleSetFileName); - ruleReference = new RuleReference(); - ruleReference.setRule(rule); - ruleReference.setRuleSetReference(ruleSetReference); + ruleReference = new RuleReference(rule, ruleSetReference); } rules.add(ruleReference); return this; @@ -387,6 +401,17 @@ public String getName() { public RuleSet build() { return new RuleSet(this); } + + public void filterRulesByPriority(RulePriority minimumPriority) { + Iterator<Rule> iterator = rules.iterator(); + while (iterator.hasNext()) { + Rule rule = iterator.next(); + if (rule.getPriority().compareTo(minimumPriority) > 0) { + LOG.fine("Removing rule " + rule.getName() + " due to priority: " + rule.getPriority() + " required: " + minimumPriority); + iterator.remove(); + } + } + } } /** @@ -441,7 +466,7 @@ public Rule getRuleByName(String ruleName) { * <code>false</code> otherwise */ public boolean applies(File file) { - return file != null ? filter.filter(file) : true; + return file == null || filter.filter(file); } /** @@ -466,23 +491,24 @@ public void start(RuleContext ctx) { * the current context */ public void apply(List<? extends Node> acuList, RuleContext ctx) { - long start = System.nanoTime(); - for (Rule rule : rules) { - try { - if (!rule.usesRuleChain() && applies(rule, ctx.getLanguageVersion())) { - rule.apply(acuList, ctx); - long end = System.nanoTime(); - Benchmarker.mark(Benchmark.Rule, rule.getName(), end - start, 1); - start = end; - } - } catch (RuntimeException e) { - if (ctx.isIgnoreExceptions()) { - if (LOG.isLoggable(Level.WARNING)) { - LOG.log(Level.WARNING, "Exception applying rule " + rule.getName() + " on file " - + ctx.getSourceCodeFilename() + ", continuing with next rule", e); + try (TimedOperation to = TimeTracker.startOperation(TimedOperationCategory.RULE)) { + for (Rule rule : rules) { + if (!rule.isRuleChain() && applies(rule, ctx.getLanguageVersion())) { + + try (TimedOperation rto = TimeTracker.startOperation(TimedOperationCategory.RULE, rule.getName())) { + rule.apply(acuList, ctx); + } 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; + } } - } else { - throw e; } } } @@ -580,7 +606,7 @@ public List<String> getIncludePatterns() { */ public boolean usesDFA(Language language) { for (Rule r : rules) { - if (r.getLanguage().equals(language) && r.usesDFA()) { + if (r.getLanguage().equals(language) && r.isDfa()) { return true; } } @@ -597,23 +623,26 @@ public boolean usesDFA(Language language) { */ public boolean usesTypeResolution(Language language) { for (Rule r : rules) { - if (r.getLanguage().equals(language) && r.usesTypeResolution()) { + if (r.getLanguage().equals(language) && r.isTypeResolution()) { return true; } } return false; } + /** - * Does any Rule for the given Language use the Metrics Framework? + * Does any Rule for the given Language use multi-file analysis? + * + * @param language + * The Language. * - * @param language The Language. - * @return <code>true</code> if a Rule for the Language uses the Metrics - * Framework, <code>false</code> otherwise. + * @return {@code true} if a Rule for the Language uses multi file analysis, + * {@code false} otherwise. */ - public boolean usesMetrics(Language language) { + public boolean usesMultifile(Language language) { for (Rule r : rules) { - if (r.getLanguage().equals(language) && r.usesMetrics()) { + if (r.getLanguage().equals(language) && r.isMultifile()) { return true; } } diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/RuleSetFactory.java b/pmd-core/src/main/java/net/sourceforge/pmd/RuleSetFactory.java index 70fac15de70..ec47ae5a545 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/RuleSetFactory.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/RuleSetFactory.java @@ -4,17 +4,13 @@ package net.sourceforge.pmd; -import static net.sourceforge.pmd.properties.PropertyDescriptorField.DEFAULT_VALUE; - import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.Collection; -import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; -import java.util.Map; import java.util.Properties; import java.util.Set; import java.util.logging.Level; @@ -36,22 +32,17 @@ import net.sourceforge.pmd.RuleSet.RuleSetBuilder; import net.sourceforge.pmd.lang.Language; import net.sourceforge.pmd.lang.LanguageRegistry; -import net.sourceforge.pmd.lang.LanguageVersion; import net.sourceforge.pmd.lang.rule.MockRule; import net.sourceforge.pmd.lang.rule.RuleReference; import net.sourceforge.pmd.lang.rule.XPathRule; -import net.sourceforge.pmd.properties.AbstractPropertyDescriptorFactory; -import net.sourceforge.pmd.properties.PropertyDescriptor; -import net.sourceforge.pmd.properties.PropertyDescriptorFactory; -import net.sourceforge.pmd.properties.PropertyDescriptorField; -import net.sourceforge.pmd.properties.PropertyDescriptorUtil; +import net.sourceforge.pmd.rules.RuleFactory; import net.sourceforge.pmd.util.ResourceLoader; /** * RuleSetFactory is responsible for creating RuleSet instances from XML - * content. By default Rules will be loaded using the ClassLoader for this - * class, using the {@link RulePriority#LOW} priority, with Rule deprecation - * warnings off. By default, the ruleset compatibility filter is active, too. + * content. By default Rules will be loaded using the {@link RulePriority#LOW} priority, + * with Rule deprecation warnings off. + * By default, the ruleset compatibility filter is active, too. * See {@link RuleSetFactoryCompatibility}. */ public class RuleSetFactory { @@ -61,22 +52,29 @@ public class RuleSetFactory { private static final String DESCRIPTION = "description"; private static final String UNEXPECTED_ELEMENT = "Unexpected element <"; private static final String PRIORITY = "priority"; - private static final String FOR_RULE = "' for Rule "; - private static final String MESSAGE = "message"; - private static final String EXTERNAL_INFO_URL = "externalInfoUrl"; - private final ClassLoader classLoader; + private final ResourceLoader resourceLoader; private final RulePriority minimumPriority; private final boolean warnDeprecated; private final RuleSetFactoryCompatibility compatibilityFilter; public RuleSetFactory() { - this(RuleSetFactory.class.getClassLoader(), RulePriority.LOW, false, true); + this(new ResourceLoader(), RulePriority.LOW, false, true); } + /** + * @deprecated Use {@link #RuleSetFactory(ResourceLoader, RulePriority, boolean, boolean)} with + * {@link ResourceLoader} instead of a {@link ClassLoader}. + */ + @Deprecated // to be removed with PMD 7.0.0. public RuleSetFactory(final ClassLoader classLoader, final RulePriority minimumPriority, final boolean warnDeprecated, final boolean enableCompatibility) { - this.classLoader = classLoader; + this(new ResourceLoader(classLoader), minimumPriority, warnDeprecated, enableCompatibility); + } + + public RuleSetFactory(final ResourceLoader resourceLoader, final RulePriority minimumPriority, + final boolean warnDeprecated, final boolean enableCompatibility) { + this.resourceLoader = resourceLoader; this.minimumPriority = minimumPriority; this.warnDeprecated = warnDeprecated; @@ -97,7 +95,7 @@ public RuleSetFactory(final ClassLoader classLoader, final RulePriority minimumP * factory. */ public RuleSetFactory(final RuleSetFactory factory, final boolean warnDeprecated) { - this(factory.classLoader, factory.minimumPriority, warnDeprecated, factory.compatibilityFilter != null); + this(factory.resourceLoader, factory.minimumPriority, warnDeprecated, factory.compatibilityFilter != null); } /** @@ -112,7 +110,7 @@ public RuleSetFactory(final RuleSetFactory factory, final boolean warnDeprecated /** * Returns an Iterator of RuleSet objects loaded from descriptions from the - * "rulesets.properties" resource for each Language with Rule support. + * "categories.properties" resource for each Language with Rule support. * * @return An Iterator of RuleSet objects. * @@ -124,8 +122,8 @@ public Iterator<RuleSet> getRegisteredRuleSets() throws RuleSetNotFoundException List<RuleSetReferenceId> ruleSetReferenceIds = new ArrayList<>(); for (Language language : LanguageRegistry.findWithRuleSupport()) { Properties props = new Properties(); - rulesetsProperties = "rulesets/" + language.getTerseName() + "/rulesets.properties"; - try (InputStream inputStream = ResourceLoader.loadResourceAsStream(rulesetsProperties);) { + rulesetsProperties = "category/" + language.getTerseName() + "/categories.properties"; + try (InputStream inputStream = resourceLoader.loadClassPathResourceAsStreamOrThrow(rulesetsProperties)) { props.load(inputStream); } String rulesetFilenames = props.getProperty("rulesets.filenames"); @@ -134,7 +132,7 @@ public Iterator<RuleSet> getRegisteredRuleSets() throws RuleSetNotFoundException return createRuleSets(ruleSetReferenceIds).getRuleSetsIterator(); } catch (IOException ioe) { throw new RuntimeException("Couldn't find " + rulesetsProperties - + "; please ensure that the rulesets directory is on the classpath. The current classpath is: " + + "; please ensure that the directory is on the classpath. The current classpath is: " + System.getProperty("java.class.path")); } } @@ -143,7 +141,7 @@ public Iterator<RuleSet> getRegisteredRuleSets() throws RuleSetNotFoundException * Create a RuleSets from a comma separated list of RuleSet reference IDs. * This is a convenience method which calls * {@link RuleSetReferenceId#parse(String)}, and then calls - * {@link #createRuleSets(List)}. The currently configured ClassLoader is + * {@link #createRuleSets(List)}. The currently configured ResourceLoader is * used. * * @param referenceString @@ -158,7 +156,7 @@ public RuleSets createRuleSets(String referenceString) throws RuleSetNotFoundExc /** * Create a RuleSets from a list of RuleSetReferenceIds. The currently - * configured ClassLoader is used. + * configured ResourceLoader is used. * * @param ruleSetReferenceIds * The List of RuleSetReferenceId of the RuleSets to create. @@ -180,7 +178,7 @@ public RuleSets createRuleSets(List<RuleSetReferenceId> ruleSetReferenceIds) thr * convenience method which calls {@link RuleSetReferenceId#parse(String)}, * gets the first item in the List, and then calls * {@link #createRuleSet(RuleSetReferenceId)}. The currently configured - * ClassLoader is used. + * ResourceLoader is used. * * @param referenceString * A comma separated list of RuleSet reference IDs. @@ -192,14 +190,14 @@ public RuleSet createRuleSet(String referenceString) throws RuleSetNotFoundExcep List<RuleSetReferenceId> references = RuleSetReferenceId.parse(referenceString); if (references.isEmpty()) { throw new RuleSetNotFoundException( - "No RuleSetReferenceId can be parsed from the string: <" + referenceString + ">"); + "No RuleSetReferenceId can be parsed from the string: <" + referenceString + '>'); } return createRuleSet(references.get(0)); } /** * Create a RuleSet from a RuleSetReferenceId. Priority filtering is ignored - * when loading a single Rule. The currently configured ClassLoader is used. + * when loading a single Rule. The currently configured ResourceLoader is used. * * @param ruleSetReferenceId * The RuleSetReferenceId of the RuleSet to create. @@ -291,7 +289,7 @@ public RuleSet createSingleRuleRuleSet(final Rule rule) { /** * Create a Rule from a RuleSet created from a file name resource. The - * currently configured ClassLoader is used. + * currently configured ResourceLoader is used. * <p> * Any Rules in the RuleSet other than the one being created, are _not_ * created. Deprecated rules are _not_ ignored, so that they can be @@ -329,7 +327,7 @@ private Rule createRule(RuleSetReferenceId ruleSetReferenceId, boolean withDepre private RuleSet parseRuleSetNode(RuleSetReferenceId ruleSetReferenceId, boolean withDeprecatedRuleReferences) throws RuleSetNotFoundException { try (CheckedInputStream inputStream = new CheckedInputStream( - ruleSetReferenceId.getInputStream(this.classLoader), new Adler32());) { + ruleSetReferenceId.getInputStream(resourceLoader), new Adler32());) { if (!ruleSetReferenceId.isExternal()) { throw new IllegalArgumentException( "Cannot parse a RuleSet from a non-external reference: <" + ruleSetReferenceId + ">."); @@ -379,6 +377,8 @@ private RuleSet parseRuleSetNode(RuleSetReferenceId ruleSetReferenceId, boolean ruleSetBuilder.withDescription("Missing description"); } + ruleSetBuilder.filterRulesByPriority(minimumPriority); + return ruleSetBuilder.build(); } catch (ClassNotFoundException cnfe) { return classNotFoundProblem(cnfe); @@ -426,6 +426,7 @@ private DocumentBuilder createDocumentBuilder() throws ParserConfigurationExcept dbf.setExpandEntityReferences(false); } catch (final ParserConfigurationException e) { // an unsupported feature... too bad, but won't fail execution due to this + LOG.log(Level.WARNING, "Ignored unsupported XML Parser Feature for parsing rulesets", e); } return dbf.newDocumentBuilder(); @@ -455,7 +456,7 @@ private void parseRuleNode(RuleSetReferenceId ruleSetReferenceId, RuleSetBuilder Element ruleElement = (Element) ruleNode; String ref = ruleElement.getAttribute("ref"); if (ref.endsWith("xml")) { - parseRuleSetReferenceNode(ruleSetReferenceId, ruleSetBuilder, ruleElement, ref); + parseRuleSetReferenceNode(ruleSetBuilder, ruleElement, ref); } else if (StringUtils.isBlank(ref)) { parseSingleRuleNode(ruleSetReferenceId, ruleSetBuilder, ruleNode); } else { @@ -469,8 +470,6 @@ private void parseRuleNode(RuleSetReferenceId ruleSetReferenceId, RuleSetBuilder * explicitly excluded, below the minimum priority threshold for this * RuleSetFactory, or which are deprecated. * - * @param ruleSetReferenceId - * The RuleSetReferenceId of the RuleSet being parsed. * @param ruleSetBuilder * The RuleSet being constructed. * @param ruleElement @@ -478,8 +477,8 @@ private void parseRuleNode(RuleSetReferenceId ruleSetReferenceId, RuleSetBuilder * @param ref * The RuleSet reference. */ - private void parseRuleSetReferenceNode(RuleSetReferenceId ruleSetReferenceId, RuleSetBuilder ruleSetBuilder, - Element ruleElement, String ref) throws RuleSetNotFoundException { + private void parseRuleSetReferenceNode(RuleSetBuilder ruleSetBuilder, Element ruleElement, String ref) + throws RuleSetNotFoundException { String priority = null; NodeList childNodes = ruleElement.getChildNodes(); Set<String> excludedRulesCheck = new HashSet<>(); @@ -495,22 +494,44 @@ private void parseRuleSetReferenceNode(RuleSetReferenceId ruleSetReferenceId, Ru } final RuleSetReference ruleSetReference = new RuleSetReference(ref, true, excludedRulesCheck); - RuleSetFactory ruleSetFactory = new RuleSetFactory(this, warnDeprecated); + // load the ruleset with minimum priority low, so that we get all rules, to be able to exclude any rule + // minimum priority will be applied again, before constructing the final ruleset + RuleSetFactory ruleSetFactory = new RuleSetFactory(resourceLoader, RulePriority.LOW, warnDeprecated, this.compatibilityFilter != null); RuleSet otherRuleSet = ruleSetFactory.createRuleSet(RuleSetReferenceId.parse(ref).get(0)); + List<RuleReference> potentialRules = new ArrayList<>(); + int countDeprecated = 0; for (Rule rule : otherRuleSet.getRules()) { excludedRulesCheck.remove(rule.getName()); - if (!ruleSetReference.getExcludes().contains(rule.getName()) && !rule.isDeprecated()) { - RuleReference ruleReference = new RuleReference(); - ruleReference.setRuleSetReference(ruleSetReference); - ruleReference.setRule(rule); - ruleSetBuilder.addRuleIfNotExists(ruleReference); - + if (!ruleSetReference.getExcludes().contains(rule.getName())) { + RuleReference ruleReference = new RuleReference(rule, ruleSetReference); // override the priority if (priority != null) { ruleReference.setPriority(RulePriority.valueOf(Integer.parseInt(priority))); } + + if (rule.isDeprecated()) { + countDeprecated++; + } + potentialRules.add(ruleReference); } } + + boolean rulesetDeprecated = false; + if (!potentialRules.isEmpty() && potentialRules.size() == countDeprecated) { + // all rules in the ruleset have been deprecated - the ruleset itself is considered to be deprecated + rulesetDeprecated = true; + LOG.warning("The RuleSet " + ref + " has been deprecated and will be removed in PMD " + PMDVersion.getNextMajorRelease()); + } + + for (RuleReference r : potentialRules) { + if (rulesetDeprecated || !r.getRule().isDeprecated()) { + // add the rule, if either the ruleset itself is deprecated (then we add all rules) + // or if the rule is not deprecated (in that case, the ruleset might contain deprecated as well + // as valid references to rules) + ruleSetBuilder.addRuleIfNotExists(r); + } + } + if (!excludedRulesCheck.isEmpty()) { throw new IllegalArgumentException( "Unable to exclude rules " + excludedRulesCheck + "; perhaps the rule name is mispelled?"); @@ -538,117 +559,12 @@ private void parseSingleRuleNode(RuleSetReferenceId ruleSetReferenceId, RuleSetB && !isRuleName(ruleElement, ruleSetReferenceId.getRuleName())) { return; } - - String attribute = ruleElement.getAttribute("class"); - if (attribute == null || "".equals(attribute)) { - throw new IllegalArgumentException("The 'class' field of rule can't be null, nor empty."); - } - Rule rule = (Rule) classLoader.loadClass(attribute).newInstance(); - rule.setName(ruleElement.getAttribute("name")); - - if (ruleElement.hasAttribute("language")) { - String languageName = ruleElement.getAttribute("language"); - Language language = LanguageRegistry.findLanguageByTerseName(languageName); - if (language == null) { - throw new IllegalArgumentException("Unknown Language '" + languageName + FOR_RULE + rule.getName() - + ", supported Languages are " - + LanguageRegistry.commaSeparatedTerseNamesForLanguage(LanguageRegistry.findWithRuleSupport())); - } - rule.setLanguage(language); - } - - Language language = rule.getLanguage(); - if (language == null) { - throw new IllegalArgumentException( - "Rule " + rule.getName() + " does not have a Language; missing 'language' attribute?"); - } - - if (ruleElement.hasAttribute("minimumLanguageVersion")) { - String minimumLanguageVersionName = ruleElement.getAttribute("minimumLanguageVersion"); - LanguageVersion minimumLanguageVersion = language.getVersion(minimumLanguageVersionName); - if (minimumLanguageVersion == null) { - throw new IllegalArgumentException("Unknown minimum Language Version '" + minimumLanguageVersionName - + "' for Language '" + language.getTerseName() + FOR_RULE + rule.getName() - + "; supported Language Versions are: " - + LanguageRegistry.commaSeparatedTerseNamesForLanguageVersion(language.getVersions())); - } - rule.setMinimumLanguageVersion(minimumLanguageVersion); - } - - if (ruleElement.hasAttribute("maximumLanguageVersion")) { - String maximumLanguageVersionName = ruleElement.getAttribute("maximumLanguageVersion"); - LanguageVersion maximumLanguageVersion = language.getVersion(maximumLanguageVersionName); - if (maximumLanguageVersion == null) { - throw new IllegalArgumentException("Unknown maximum Language Version '" + maximumLanguageVersionName - + "' for Language '" + language.getTerseName() + FOR_RULE + rule.getName() - + "; supported Language Versions are: " - + LanguageRegistry.commaSeparatedTerseNamesForLanguageVersion(language.getVersions())); - } - rule.setMaximumLanguageVersion(maximumLanguageVersion); - } - - if (rule.getMinimumLanguageVersion() != null && rule.getMaximumLanguageVersion() != null) { - throw new IllegalArgumentException( - "The minimum Language Version '" + rule.getMinimumLanguageVersion().getTerseName() - + "' must be prior to the maximum Language Version '" - + rule.getMaximumLanguageVersion().getTerseName() + FOR_RULE + rule.getName() - + "; perhaps swap them around?"); - } - - String since = ruleElement.getAttribute("since"); - if (StringUtils.isNotBlank(since)) { - rule.setSince(since); - } - rule.setMessage(ruleElement.getAttribute(MESSAGE)); + Rule rule = new RuleFactory().buildRule(ruleElement); rule.setRuleSetName(ruleSetBuilder.getName()); - rule.setExternalInfoUrl(ruleElement.getAttribute(EXTERNAL_INFO_URL)); - - if (hasAttributeSetTrue(ruleElement, "deprecated")) { - rule.setDeprecated(true); - } - - if (hasAttributeSetTrue(ruleElement, "dfa")) { - rule.setUsesDFA(); - } - if (hasAttributeSetTrue(ruleElement, "typeResolution")) { - rule.setUsesTypeResolution(); - } - - if (hasAttributeSetTrue(ruleElement, "metrics")) { - rule.setUsesMetrics(); - } - - final NodeList nodeList = ruleElement.getChildNodes(); - for (int i = 0; i < nodeList.getLength(); i++) { - Node node = nodeList.item(i); - if (node.getNodeType() != Node.ELEMENT_NODE) { - continue; - } - String nodeName = node.getNodeName(); - if (DESCRIPTION.equals(nodeName)) { - rule.setDescription(parseTextNode(node)); - } else if ("example".equals(nodeName)) { - rule.addExample(parseTextNode(node)); - } else if (PRIORITY.equals(nodeName)) { - rule.setPriority(RulePriority.valueOf(Integer.parseInt(parseTextNode(node).trim()))); - } else if ("properties".equals(nodeName)) { - parsePropertiesNode(rule, node); - } else { - throw new IllegalArgumentException(UNEXPECTED_ELEMENT + nodeName - + "> encountered as child of <rule> element for Rule " + rule.getName()); - } - - } - if (StringUtils.isNotBlank(ruleSetReferenceId.getRuleName()) - || rule.getPriority().compareTo(minimumPriority) <= 0) { - ruleSetBuilder.addRule(rule); - } + ruleSetBuilder.addRule(rule); } - private static boolean hasAttributeSetTrue(Element element, String attributeId) { - return element.hasAttribute(attributeId) && "true".equalsIgnoreCase(element.getAttribute(attributeId)); - } /** * Parse a rule node as a RuleReference. A RuleReference is a single Rule @@ -678,7 +594,9 @@ private void parseRuleReferenceNode(RuleSetReferenceId ruleSetReferenceId, RuleS return; } - RuleSetFactory ruleSetFactory = new RuleSetFactory(this, warnDeprecated); + // load the ruleset with minimum priority low, so that we get all rules, to be able to exclude any rule + // minimum priority will be applied again, before constructing the final ruleset + RuleSetFactory ruleSetFactory = new RuleSetFactory(resourceLoader, RulePriority.LOW, warnDeprecated, this.compatibilityFilter != null); boolean isSameRuleSet = false; RuleSetReferenceId otherRuleSetReferenceId = RuleSetReferenceId.parse(ref).get(0); @@ -699,67 +617,45 @@ && containsRule(ruleSetReferenceId, otherRuleSetReferenceId.getRuleName())) { if (referencedRule instanceof RuleReference) { RuleReference ruleReference = (RuleReference) referencedRule; if (LOG.isLoggable(Level.WARNING)) { - LOG.warning("Use Rule name " + ruleReference.getRuleSetReference().getRuleSetFileName() + "/" + LOG.warning("Use Rule name " + ruleReference.getRuleSetReference().getRuleSetFileName() + '/' + ruleReference.getOriginalName() + " instead of the deprecated Rule name " + otherRuleSetReferenceId - + ". Future versions of PMD will remove support for this deprecated Rule name usage."); + + ". PMD " + PMDVersion.getNextMajorRelease() + + " will remove support for this deprecated Rule name usage."); } } else if (referencedRule instanceof MockRule) { if (LOG.isLoggable(Level.WARNING)) { LOG.warning("Discontinue using Rule name " + otherRuleSetReferenceId + " as it has been removed from PMD and no longer functions." - + " Future versions of PMD will remove support for this Rule."); + + " PMD " + PMDVersion.getNextMajorRelease() + + " will remove support for this Rule."); } } else { if (LOG.isLoggable(Level.WARNING)) { LOG.warning("Discontinue using Rule name " + otherRuleSetReferenceId + " as it is scheduled for removal from PMD." - + " Future versions of PMD will remove support for this Rule."); + + " PMD " + PMDVersion.getNextMajorRelease() + + " will remove support for this Rule."); } } } RuleSetReference ruleSetReference = new RuleSetReference(otherRuleSetReferenceId.getRuleSetFileName(), false); - RuleReference ruleReference = new RuleReference(); - ruleReference.setRuleSetReference(ruleSetReference); - ruleReference.setRule(referencedRule); + RuleReference ruleReference = new RuleFactory().decorateRule(referencedRule, ruleSetReference, ruleElement); - if (ruleElement.hasAttribute("deprecated")) { - ruleReference.setDeprecated(Boolean.parseBoolean(ruleElement.getAttribute("deprecated"))); - } - if (ruleElement.hasAttribute("name")) { - ruleReference.setName(ruleElement.getAttribute("name")); - } - if (ruleElement.hasAttribute(MESSAGE)) { - ruleReference.setMessage(ruleElement.getAttribute(MESSAGE)); - } - if (ruleElement.hasAttribute(EXTERNAL_INFO_URL)) { - ruleReference.setExternalInfoUrl(ruleElement.getAttribute(EXTERNAL_INFO_URL)); - } - for (int i = 0; i < ruleElement.getChildNodes().getLength(); i++) { - Node node = ruleElement.getChildNodes().item(i); - if (node.getNodeType() == Node.ELEMENT_NODE) { - if (node.getNodeName().equals(DESCRIPTION)) { - ruleReference.setDescription(parseTextNode(node)); - } else if (node.getNodeName().equals("example")) { - ruleReference.addExample(parseTextNode(node)); - } else if (node.getNodeName().equals(PRIORITY)) { - ruleReference.setPriority(RulePriority.valueOf(Integer.parseInt(parseTextNode(node)))); - } else if (node.getNodeName().equals("properties")) { - parsePropertiesNode(ruleReference, node); - } else { - throw new IllegalArgumentException(UNEXPECTED_ELEMENT + node.getNodeName() - + "> encountered as child of <rule> element for Rule " + ruleReference.getName()); - } + if (warnDeprecated && ruleReference.isDeprecated()) { + if (LOG.isLoggable(Level.WARNING)) { + LOG.warning("Use Rule name " + ruleReference.getRuleSetReference().getRuleSetFileName() + '/' + + ruleReference.getOriginalName() + " instead of the deprecated Rule name " + + ruleSetReferenceId.getRuleSetFileName() + '/' + ruleReference.getName() + + ". PMD " + PMDVersion.getNextMajorRelease() + + " will remove support for this deprecated Rule name usage."); } } - if (StringUtils.isNotBlank(ruleSetReferenceId.getRuleName()) - || referencedRule.getPriority().compareTo(minimumPriority) <= 0) { - if (withDeprecatedRuleReferences || !isSameRuleSet || !ruleReference.isDeprecated()) { - ruleSetBuilder.addRuleReplaceIfExists(ruleReference); - } + if (withDeprecatedRuleReferences || !isSameRuleSet || !ruleReference.isDeprecated()) { + ruleSetBuilder.addRuleReplaceIfExists(ruleReference); } } @@ -774,7 +670,7 @@ && containsRule(ruleSetReferenceId, otherRuleSetReferenceId.getRuleName())) { */ private boolean containsRule(RuleSetReferenceId ruleSetReferenceId, String ruleName) { boolean found = false; - try (InputStream ruleSet = ruleSetReferenceId.getInputStream(classLoader)) { + try (InputStream ruleSet = ruleSetReferenceId.getInputStream(resourceLoader)) { DocumentBuilder builder = createDocumentBuilder(); Document document = builder.parse(ruleSet); Element ruleSetElement = document.getDocumentElement(); @@ -798,112 +694,6 @@ private static boolean isElementNode(Node node, String name) { return node.getNodeType() == Node.ELEMENT_NODE && node.getNodeName().equals(name); } - /** - * Parse a properties node. - * - * @param rule - * The Rule to which the properties should be added. - * @param propertiesNode - * Must be a properties element node. - */ - private static void parsePropertiesNode(Rule rule, Node propertiesNode) { - for (int i = 0; i < propertiesNode.getChildNodes().getLength(); i++) { - Node node = propertiesNode.getChildNodes().item(i); - if (isElementNode(node, "property")) { - parsePropertyNodeBR(rule, node); - } - } - } - - private static String valueFrom(Node parentNode) { - - final NodeList nodeList = parentNode.getChildNodes(); - - for (int i = 0; i < nodeList.getLength(); i++) { - Node node = nodeList.item(i); - if (isElementNode(node, "value")) { - return parseTextNode(node); - } - } - return null; - } - - - /** - * Sets the value of a property. - * - * @param rule The rule which has the property - * @param desc The property descriptor - * @param strValue The string value of the property, converted to a T - * @param <T> The type of values of the property descriptor - */ - private static <T> void setValue(Rule rule, PropertyDescriptor<T> desc, String strValue) { - T realValue = desc.valueFrom(strValue); - rule.setProperty(desc, realValue); - } - - - /** - * Parse a property node. - * - * @param rule The Rule to which the property should be added. - * @param propertyNode Must be a property element node. - */ - private static void parsePropertyNodeBR(Rule rule, Node propertyNode) { - - Element propertyElement = (Element) propertyNode; - String typeId = propertyElement.getAttribute(PropertyDescriptorField.TYPE.attributeName()); - String strValue = propertyElement.getAttribute(DEFAULT_VALUE.attributeName()); - if (StringUtils.isBlank(strValue)) { - strValue = valueFrom(propertyElement); - } - - // Setting of existing property, or defining a new property? - if (StringUtils.isBlank(typeId)) { - String name = propertyElement.getAttribute(PropertyDescriptorField.NAME.attributeName()); - - PropertyDescriptor<?> propertyDescriptor = rule.getPropertyDescriptor(name); - if (propertyDescriptor == null) { - throw new IllegalArgumentException( - "Cannot set non-existant property '" + name + "' on Rule " + rule.getName()); - } else { - setValue(rule, propertyDescriptor, strValue); - } - return; - } - - PropertyDescriptorFactory<?> pdFactory = PropertyDescriptorUtil.factoryFor(typeId); - if (pdFactory == null) { - throw new RuntimeException("No property descriptor factory for type: " + typeId); - } - - Set<PropertyDescriptorField> valueKeys = pdFactory.expectableFields(); - Map<PropertyDescriptorField, String> values = new HashMap<>(valueKeys.size()); - - // populate a map of values for an individual descriptor - for (PropertyDescriptorField field : valueKeys) { - String valueStr = propertyElement.getAttribute(field.attributeName()); - if (valueStr != null) { - values.put(field, valueStr); - } - } - - if (StringUtils.isBlank(values.get(DEFAULT_VALUE))) { - NodeList children = propertyElement.getElementsByTagName(DEFAULT_VALUE.attributeName()); - if (children.getLength() == 1) { - values.put(DEFAULT_VALUE, children.item(0).getTextContent()); - } else { - throw new RuntimeException("No value defined!"); - } - } - - // casting is not pretty but prevents the interface from having this method - PropertyDescriptor<?> desc = ((AbstractPropertyDescriptorFactory<?>) pdFactory).createExternalWith(values); - - rule.definePropertyDescriptor(desc); - setValue(rule, desc, strValue); - } - /** * Parse a String from a textually type node. * diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/RuleSetFactoryCompatibility.java b/pmd-core/src/main/java/net/sourceforge/pmd/RuleSetFactoryCompatibility.java index af39be8cf4b..c16057024d2 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/RuleSetFactoryCompatibility.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/RuleSetFactoryCompatibility.java @@ -71,7 +71,17 @@ public RuleSetFactoryCompatibility() { // PMD 6.0.0 addFilterRuleMoved("java", "controversial", "unnecessary", "UnnecessaryParentheses"); addFilterRuleRenamed("java", "unnecessary", "UnnecessaryParentheses", "UselessParentheses"); - + addFilterRuleMoved("java", "typeresolution", "coupling", "LooseCoupling"); + addFilterRuleMoved("java", "typeresolution", "clone", "CloneMethodMustImplementCloneable"); + addFilterRuleMoved("java", "typeresolution", "imports", "UnusedImports"); + addFilterRuleMoved("java", "typeresolution", "strictexception", "SignatureDeclareThrowsException"); + addFilterRuleRenamed("java", "naming", "MisleadingVariableName", "MIsLeadingVariableName"); + addFilterRuleRenamed("java", "unnecessary", "UnnecessaryFinalModifier", "UnnecessaryModifier"); + addFilterRuleRenamed("java", "empty", "EmptyStaticInitializer", "EmptyInitializer"); + // GuardLogStatementJavaUtil moved and renamed... + addFilterRuleMoved("java", "logging-java", "logging-jakarta-commons", "GuardLogStatementJavaUtil"); + addFilterRuleRenamed("java", "logging-jakarta-commons", "GuardLogStatementJavaUtil", "GuardLogStatement"); + addFilterRuleRenamed("java", "logging-jakarta-commons", "GuardDebugLogging", "GuardLogStatement"); } void addFilterRuleRenamed(String language, String ruleset, String oldName, String newName) { @@ -105,8 +115,8 @@ public Reader filterRuleSetFile(InputStream stream) throws IOException { return new StringReader(ruleset); } - private String applyAllFilters(String in) { - String result = in; + private String applyAllFilters(String ruleset) { + String result = ruleset; for (RuleSetFilter filter : filters) { result = filter.apply(result); } @@ -184,9 +194,9 @@ public static RuleSetFilter ruleRemoved(String language, String ruleset, String return filter; } - String apply(String in) { - String result = in; - Matcher matcher = refPattern.matcher(in); + String apply(String ruleset) { + String result = ruleset; + Matcher matcher = refPattern.matcher(ruleset); if (matcher.find()) { result = matcher.replaceAll(replacement); diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/RuleSetReferenceId.java b/pmd-core/src/main/java/net/sourceforge/pmd/RuleSetReferenceId.java index 74a685de99f..e5ee6c47cce 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/RuleSetReferenceId.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/RuleSetReferenceId.java @@ -12,7 +12,6 @@ import java.util.ArrayList; import java.util.List; -import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.StringUtils; import net.sourceforge.pmd.util.ResourceLoader; @@ -199,7 +198,7 @@ public RuleSetReferenceId(final String id, final RuleSetReferenceId externalRule ruleName = null; allRules = true; } else { - external = externalRuleSetReferenceId != null ? externalRuleSetReferenceId.isExternal() : false; + external = externalRuleSetReferenceId != null && externalRuleSetReferenceId.isExternal(); ruleSetFileName = externalRuleSetReferenceId != null ? externalRuleSetReferenceId.getRuleSetFileName() : null; ruleName = id; @@ -224,18 +223,13 @@ public RuleSetReferenceId(final String id, final RuleSetReferenceId externalRule * @return <code>true</code> if the ruleset could be loaded, * <code>false</code> otherwise. */ - private boolean checkRulesetExists(String name) { + private boolean checkRulesetExists(final String name) { boolean resourceFound = false; if (name != null) { - try { - InputStream resource = ResourceLoader.loadResourceAsStream(name, - RuleSetReferenceId.class.getClassLoader()); - if (resource != null) { - resourceFound = true; - IOUtils.closeQuietly(resource); - } - } catch (RuleSetNotFoundException e) { - resourceFound = false; + try (InputStream resource = new ResourceLoader().loadClassPathResourceAsStreamOrThrow(name)) { + resourceFound = true; + } catch (Exception ignored) { + // ignored } } return resourceFound; @@ -257,7 +251,7 @@ private String resolveBuiltInRuleset(final String name) { int index = name.indexOf('-'); if (index >= 0) { // Standard short name - result = "rulesets/" + name.substring(0, index) + "/" + name.substring(index + 1) + ".xml"; + result = "rulesets/" + name.substring(0, index) + '/' + name.substring(index + 1) + ".xml"; } else { // A release RuleSet? if (name.matches("[0-9]+.*")) { @@ -299,17 +293,14 @@ private static boolean isHttpUrl(String name) { return false; } - if (stripped.startsWith("http://") || stripped.startsWith("https://")) { - return true; - } - - return false; + return stripped.startsWith("http://") || stripped.startsWith("https://"); } private static boolean isValidUrl(String name) { if (isHttpUrl(name)) { String url = StringUtils.strip(name); try { + // FIXME : Do we really need to perform a request? if it's a url we should treat it as one even if the server is down HttpURLConnection connection = (HttpURLConnection) new URL(url).openConnection(); connection.setRequestMethod("HEAD"); connection.setConnectTimeout(ResourceLoader.TIMEOUT); @@ -393,22 +384,21 @@ public String getRuleName() { } /** - * Try to load the RuleSet resource with the specified ClassLoader. Multiple + * Try to load the RuleSet resource with the specified ResourceLoader. Multiple * attempts to get independent InputStream instances may be made, so * subclasses must ensure they support this behavior. Delegates to an * external RuleSetReferenceId if there is one associated with this * instance. * - * @param classLoader - * The ClassLoader to use. + * @param rl The {@link ResourceLoader} to use. * @return An InputStream to that resource. * @throws RuleSetNotFoundException * if unable to find a resource. */ - public InputStream getInputStream(ClassLoader classLoader) throws RuleSetNotFoundException { + public InputStream getInputStream(final ResourceLoader rl) throws RuleSetNotFoundException { if (externalRuleSetReferenceId == null) { InputStream in = StringUtils.isBlank(ruleSetFileName) ? null - : ResourceLoader.loadResourceAsStream(ruleSetFileName, classLoader); + : rl.loadResourceAsStream(ruleSetFileName); if (in == null) { throw new RuleSetNotFoundException("Can't find resource '" + ruleSetFileName + "' for rule '" + ruleName + "'" + ". Make sure the resource is a valid file or URL and is on the CLASSPATH. " @@ -416,7 +406,7 @@ public InputStream getInputStream(ClassLoader classLoader) throws RuleSetNotFoun } return in; } else { - return externalRuleSetReferenceId.getInputStream(classLoader); + return externalRuleSetReferenceId.getInputStream(rl); } } @@ -434,7 +424,7 @@ public String toString() { if (allRules) { return ruleSetFileName; } else { - return ruleSetFileName + "/" + ruleName; + return ruleSetFileName + '/' + ruleName; } } else { diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/RuleSetWriter.java b/pmd-core/src/main/java/net/sourceforge/pmd/RuleSetWriter.java index deae16d873c..70f6f23b41b 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/RuleSetWriter.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/RuleSetWriter.java @@ -9,6 +9,8 @@ import java.util.List; import java.util.Map; import java.util.Set; +import java.util.logging.Level; +import java.util.logging.Logger; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.FactoryConfigurationError; @@ -34,21 +36,21 @@ import net.sourceforge.pmd.lang.rule.XPathRule; import net.sourceforge.pmd.properties.PropertyDescriptor; import net.sourceforge.pmd.properties.PropertyDescriptorField; -import net.sourceforge.pmd.properties.PropertyDescriptorUtil; +import net.sourceforge.pmd.properties.PropertyTypeId; /** * This class represents a way to serialize a RuleSet to an XML configuration * file. */ public class RuleSetWriter { + private static final Logger LOG = Logger.getLogger(RuleSetWriter.class.getName()); public static final String RULESET_2_0_0_NS_URI = "http://pmd.sourceforge.net/ruleset/2.0.0"; - public static final String RULESET_3_0_0_NS_URI = "http://pmd.sourceforge.net/ruleset/3.0.0"; /** * @deprecated use {@link #RULESET_2_0_0_NS_URI} instead */ - @Deprecated + @Deprecated // To be removed in PMD 7.0.0 public static final String RULESET_NS_URI = RULESET_2_0_0_NS_URI; private final OutputStream outputStream; @@ -79,6 +81,7 @@ public void write(RuleSet ruleSet) { transformerFactory.setAttribute("indent-number", 3); } catch (IllegalArgumentException iae) { // ignore it, specific to one parser + LOG.log(Level.FINE, "Couldn't set indentation", iae); } Transformer transformer = transformerFactory.newTransformer(); transformer.setOutputProperty(OutputKeys.METHOD, "xml"); @@ -99,10 +102,10 @@ public void write(RuleSet ruleSet) { } private Element createRuleSetElement(RuleSet ruleSet) { - Element ruleSetElement = document.createElementNS(RULESET_3_0_0_NS_URI, "ruleset"); + Element ruleSetElement = document.createElementNS(RULESET_2_0_0_NS_URI, "ruleset"); ruleSetElement.setAttribute("xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance"); ruleSetElement.setAttributeNS("http://www.w3.org/2001/XMLSchema-instance", "xsi:schemaLocation", - RULESET_3_0_0_NS_URI + " http://pmd.sourceforge.net/ruleset_3_0_0.xsd"); + RULESET_2_0_0_NS_URI + " https://pmd.sourceforge.io/ruleset_2_0_0.xsd"); ruleSetElement.setAttribute("name", ruleSet.getName()); Element descriptionElement = createDescriptionElement(ruleSet.getDescription()); @@ -139,11 +142,11 @@ private Element createIncludePatternElement(String includePattern) { } private Element createRuleElement() { - return document.createElementNS(RULESET_3_0_0_NS_URI, "rule"); + return document.createElementNS(RULESET_2_0_0_NS_URI, "rule"); } private Element createExcludeElement(String exclude) { - Element element = document.createElementNS(RULESET_3_0_0_NS_URI, "exclude"); + Element element = document.createElementNS(RULESET_2_0_0_NS_URI, "exclude"); element.setAttribute("name", exclude); return element; } @@ -157,7 +160,7 @@ private Element createPriorityElement(RulePriority priority) { } private Element createPropertiesElement() { - return document.createElementNS(RULESET_3_0_0_NS_URI, "properties"); + return document.createElementNS(RULESET_2_0_0_NS_URI, "properties"); } private Element createRuleElement(Rule rule) { @@ -167,8 +170,7 @@ private Element createRuleElement(Rule rule) { if (ruleSetReference.isAllRules()) { if (!ruleSetFileNames.contains(ruleSetReference.getRuleSetFileName())) { ruleSetFileNames.add(ruleSetReference.getRuleSetFileName()); - Element ruleSetReferenceElement = createRuleSetReferenceElement(ruleSetReference); - return ruleSetReferenceElement; + return createRuleSetReferenceElement(ruleSetReference); } else { return null; } @@ -178,7 +180,7 @@ private Element createRuleElement(Rule rule) { LanguageVersion maximumLanguageVersion = ruleReference.getOverriddenMaximumLanguageVersion(); Boolean deprecated = ruleReference.isOverriddenDeprecated(); String name = ruleReference.getOverriddenName(); - String ref = ruleReference.getRuleSetReference().getRuleSetFileName() + "/" + String ref = ruleReference.getRuleSetReference().getRuleSetFileName() + '/' + ruleReference.getRule().getName(); String message = ruleReference.getOverriddenMessage(); String externalInfoUrl = ruleReference.getOverriddenExternalInfoUrl(); @@ -197,7 +199,7 @@ private Element createRuleElement(Rule rule) { return createSingleRuleElement(rule instanceof ImmutableLanguage ? null : rule.getLanguage(), rule.getMinimumLanguageVersion(), rule.getMaximumLanguageVersion(), rule.isDeprecated(), rule.getName(), rule.getSince(), null, rule.getMessage(), rule.getExternalInfoUrl(), - rule.getRuleClass(), rule.usesDFA(), rule.usesTypeResolution(), rule.usesMetrics(), + rule.getRuleClass(), rule.isDfa(), rule.isTypeResolution(), rule.isMultifile(), rule.getDescription(), rule.getPriority(), rule.getPropertyDescriptors(), rule.getPropertiesByPropertyDescriptor(), rule.getExamples()); @@ -213,7 +215,7 @@ private void setIfNonNull(Object value, Element target, String id) { private Element createSingleRuleElement(Language language, LanguageVersion minimumLanguageVersion, LanguageVersion maximumLanguageVersion, Boolean deprecated, String name, String since, String ref, String message, String externalInfoUrl, String clazz, Boolean dfa, Boolean typeResolution, - Boolean metrics, + Boolean multifile, // NOPMD: TODO multifile String description, RulePriority priority, List<PropertyDescriptor<?>> propertyDescriptors, Map<PropertyDescriptor<?>, Object> propertiesByPropertyDescriptor, List<String> examples) { Element ruleElement = createRuleElement(); @@ -236,7 +238,7 @@ private Element createSingleRuleElement(Language language, LanguageVersion minim setIfNonNull(externalInfoUrl, ruleElement, "externalInfoUrl"); setIfNonNull(dfa, ruleElement, "dfa"); setIfNonNull(typeResolution, ruleElement, "typeResolution"); - setIfNonNull(metrics, ruleElement, "metrics"); + //TODO multifile: setIfNonNull(multifile, ruleElement, "multifile"); if (description != null) { Element descriptionElement = createDescriptionElement(description); @@ -330,7 +332,7 @@ private Element createPropertiesElement(List<PropertyDescriptor<?>> propertyDesc } private Element createPropertyValueElement(PropertyDescriptor propertyDescriptor, Object value) { - Element propertyElement = document.createElementNS(RULESET_3_0_0_NS_URI, "property"); + Element propertyElement = document.createElementNS(RULESET_2_0_0_NS_URI, "property"); propertyElement.setAttribute("name", propertyDescriptor.name()); String valueString = propertyDescriptor.asDelimitedString(value); if (XPathRule.XPATH_DESCRIPTOR.equals(propertyDescriptor)) { @@ -349,7 +351,7 @@ private Element createPropertyDefinitionElementBR(PropertyDescriptor<?> property final Element propertyElement = createPropertyValueElement(propertyDescriptor, propertyDescriptor.defaultValue()); propertyElement.setAttribute(PropertyDescriptorField.TYPE.attributeName(), - PropertyDescriptorUtil.typeIdFor(propertyDescriptor.type(), + PropertyTypeId.typeIdFor(propertyDescriptor.type(), propertyDescriptor.isMultiValue())); Map<PropertyDescriptorField, String> propertyValuesById = propertyDescriptor.attributeValuesById(); @@ -361,14 +363,14 @@ private Element createPropertyDefinitionElementBR(PropertyDescriptor<?> property } private Element createTextElement(String name, String value) { - Element element = document.createElementNS(RULESET_3_0_0_NS_URI, name); + Element element = document.createElementNS(RULESET_2_0_0_NS_URI, name); Text text = document.createTextNode(value); element.appendChild(text); return element; } private Element createCDATASectionElement(String name, String value) { - Element element = document.createElementNS(RULESET_3_0_0_NS_URI, name); + Element element = document.createElementNS(RULESET_2_0_0_NS_URI, name); CDATASection cdataSection = document.createCDATASection(value); element.appendChild(cdataSection); return element; diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/RuleSets.java b/pmd-core/src/main/java/net/sourceforge/pmd/RuleSets.java index c28f323a6ce..90168308586 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/RuleSets.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/RuleSets.java @@ -35,8 +35,9 @@ public class RuleSets { * Public constructor. */ public RuleSets() { + // default constructor } - + /** * Copy constructor. Deep copies RuleSets. * @param ruleSets The RuleSets to copy. @@ -54,7 +55,6 @@ public RuleSets(final RuleSets ruleSets) { * the RuleSet */ public RuleSets(RuleSet ruleSet) { - this(); addRuleSet(ruleSet); } @@ -77,7 +77,7 @@ public void addRuleSet(RuleSet ruleSet) { * @return RuleSet[] */ public RuleSet[] getAllRuleSets() { - return ruleSets.toArray(new RuleSet[ruleSets.size()]); + return ruleSets.toArray(new RuleSet[0]); } public Iterator<RuleSet> getRuleSetsIterator() { @@ -220,15 +220,17 @@ public boolean usesTypeResolution(Language language) { } /** - * Does any Rule for the given Language use the Metrics Framework? + * Does any Rule for the given Language use multi-file analysis? + * + * @param language + * The Language. * - * @param language The Language. - * @return <code>true</code> if a Rule for the Language uses the Metrics - * Framework, <code>false</code> otherwise. + * @return {@code true} if a Rule for the Language uses multi file analysis, + * {@code false} otherwise. */ - public boolean usesMetrics(Language language) { + public boolean usesMultifile(Language language) { for (RuleSet ruleSet : ruleSets) { - if (ruleSet.usesMetrics(language)) { + if (ruleSet.usesMultifile(language)) { return true; } } diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/RulesetsFactoryUtils.java b/pmd-core/src/main/java/net/sourceforge/pmd/RulesetsFactoryUtils.java index 2b0857a8cca..e13384111a8 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/RulesetsFactoryUtils.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/RulesetsFactoryUtils.java @@ -7,8 +7,10 @@ import java.util.logging.Level; import java.util.logging.Logger; -import net.sourceforge.pmd.benchmark.Benchmark; -import net.sourceforge.pmd.benchmark.Benchmarker; +import net.sourceforge.pmd.benchmark.TimeTracker; +import net.sourceforge.pmd.benchmark.TimedOperation; +import net.sourceforge.pmd.benchmark.TimedOperationCategory; +import net.sourceforge.pmd.util.ResourceLoader; public final class RulesetsFactoryUtils { @@ -36,7 +38,7 @@ public static RuleSets getRuleSets(String rulesets, RuleSetFactory factory) { ruleSets = factory.createRuleSets(rulesets); printRuleNamesInDebug(ruleSets); if (ruleSets.ruleCount() == 0) { - String msg = "No rules found. Maybe you mispelled a rule name? (" + rulesets + ")"; + String msg = "No rules found. Maybe you mispelled a rule name? (" + rulesets + ')'; LOG.log(Level.SEVERE, msg); throw new IllegalArgumentException(msg); } @@ -61,19 +63,14 @@ public static RuleSets getRuleSets(String rulesets, RuleSetFactory factory) { * a ruleset couldn't be found. */ public static RuleSets getRuleSetsWithBenchmark(String rulesets, RuleSetFactory factory) { - long loadRuleStart = System.nanoTime(); - RuleSets ruleSets = null; - try { - ruleSets = getRuleSets(rulesets, factory); - } finally { - long endLoadRules = System.nanoTime(); - Benchmarker.mark(Benchmark.LoadRules, endLoadRules - loadRuleStart, 0); + try (TimedOperation to = TimeTracker.startOperation(TimedOperationCategory.LOAD_RULES)) { + return getRuleSets(rulesets, factory); } - return ruleSets; } - public static RuleSetFactory getRulesetFactory(final PMDConfiguration configuration) { - return new RuleSetFactory(configuration.getClassLoader(), configuration.getMinimumPriority(), true, + public static RuleSetFactory getRulesetFactory(final PMDConfiguration configuration, + final ResourceLoader resourceLoader) { + return new RuleSetFactory(resourceLoader, configuration.getMinimumPriority(), true, configuration.isRuleSetFactoryCompatibilityEnabled()); } diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/SourceCodeProcessor.java b/pmd-core/src/main/java/net/sourceforge/pmd/SourceCodeProcessor.java index e361f742093..ddc0f200ec8 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/SourceCodeProcessor.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/SourceCodeProcessor.java @@ -4,17 +4,16 @@ package net.sourceforge.pmd; +import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.Reader; -import java.io.UnsupportedEncodingException; import java.util.Collections; import java.util.List; -import org.apache.commons.io.IOUtils; - -import net.sourceforge.pmd.benchmark.Benchmark; -import net.sourceforge.pmd.benchmark.Benchmarker; +import net.sourceforge.pmd.benchmark.TimeTracker; +import net.sourceforge.pmd.benchmark.TimedOperation; +import net.sourceforge.pmd.benchmark.TimedOperationCategory; import net.sourceforge.pmd.lang.Language; import net.sourceforge.pmd.lang.LanguageVersion; import net.sourceforge.pmd.lang.LanguageVersionHandler; @@ -48,10 +47,10 @@ public SourceCodeProcessor(PMDConfiguration configuration) { * @see #processSourceCode(Reader, RuleSets, RuleContext) */ public void processSourceCode(InputStream sourceCode, RuleSets ruleSets, RuleContext ctx) throws PMDException { - try { - processSourceCode(new InputStreamReader(sourceCode, configuration.getSourceEncoding()), ruleSets, ctx); - } catch (UnsupportedEncodingException uee) { - throw new PMDException("Unsupported encoding exception: " + uee.getMessage()); + try (Reader streamReader = new InputStreamReader(sourceCode, configuration.getSourceEncoding())) { + processSourceCode(streamReader, ruleSets, ctx); + } catch (IOException e) { + throw new PMDException("IO exception: " + e.getMessage(), e); } } @@ -102,26 +101,29 @@ public void processSourceCode(Reader sourceCode, RuleSets ruleSets, RuleContext configuration.getAnalysisCache().analysisFailed(ctx.getSourceCodeFile()); throw new PMDException("Error while processing " + ctx.getSourceCodeFilename(), e); } finally { - IOUtils.closeQuietly(sourceCode); ruleSets.end(ctx); } } } private Node parse(RuleContext ctx, Reader sourceCode, Parser parser) { - long start = System.nanoTime(); - Node rootNode = parser.parse(ctx.getSourceCodeFilename(), sourceCode); - ctx.getReport().suppress(parser.getSuppressMap()); - long end = System.nanoTime(); - Benchmarker.mark(Benchmark.Parser, end - start, 0); - return rootNode; + try (TimedOperation to = TimeTracker.startOperation(TimedOperationCategory.PARSER)) { + Node rootNode = parser.parse(ctx.getSourceCodeFilename(), sourceCode); + ctx.getReport().suppress(parser.getSuppressMap()); + return rootNode; + } } private void symbolFacade(Node rootNode, LanguageVersionHandler languageVersionHandler) { - long start = System.nanoTime(); - languageVersionHandler.getSymbolFacade(configuration.getClassLoader()).start(rootNode); - long end = System.nanoTime(); - Benchmarker.mark(Benchmark.SymbolTable, end - start, 0); + try (TimedOperation to = TimeTracker.startOperation(TimedOperationCategory.SYMBOL_TABLE)) { + languageVersionHandler.getSymbolFacade(configuration.getClassLoader()).start(rootNode); + } + } + + private void resolveQualifiedNames(Node rootNode, LanguageVersionHandler handler) { + try (TimedOperation to = TimeTracker.startOperation(TimedOperationCategory.QUALIFIED_NAME_RESOLUTION)) { + handler.getQualifiedNameResolutionFacade(configuration.getClassLoader()).start(rootNode); + } } // private ParserOptions getParserOptions(final LanguageVersionHandler @@ -135,11 +137,10 @@ private void symbolFacade(Node rootNode, LanguageVersionHandler languageVersionH private void usesDFA(LanguageVersion languageVersion, Node rootNode, RuleSets ruleSets, Language language) { if (ruleSets.usesDFA(language)) { - long start = System.nanoTime(); - VisitorStarter dataFlowFacade = languageVersion.getLanguageVersionHandler().getDataFlowFacade(); - dataFlowFacade.start(rootNode); - long end = System.nanoTime(); - Benchmarker.mark(Benchmark.DFA, end - start, 0); + try (TimedOperation to = TimeTracker.startOperation(TimedOperationCategory.DFA)) { + VisitorStarter dataFlowFacade = languageVersion.getLanguageVersionHandler().getDataFlowFacade(); + dataFlowFacade.start(rootNode); + } } } @@ -147,37 +148,37 @@ private void usesTypeResolution(LanguageVersion languageVersion, Node rootNode, Language language) { if (ruleSets.usesTypeResolution(language)) { - long start = System.nanoTime(); - languageVersion.getLanguageVersionHandler().getTypeResolutionFacade(configuration.getClassLoader()) - .start(rootNode); - long end = System.nanoTime(); - Benchmarker.mark(Benchmark.TypeResolution, end - start, 0); + try (TimedOperation to = TimeTracker.startOperation(TimedOperationCategory.TYPE_RESOLUTION)) { + languageVersion.getLanguageVersionHandler().getTypeResolutionFacade(configuration.getClassLoader()) + .start(rootNode); + } } } - private void usesMetrics(LanguageVersion languageVersion, Node rootNode, RuleSets ruleSets, - Language language) { - if (ruleSets.usesMetrics(language)) { - long start = System.nanoTime(); - languageVersion.getLanguageVersionHandler().getMetricsVisitorFacade() - .start(rootNode); - long end = System.nanoTime(); - Benchmarker.mark(Benchmark.MetricsVisitor, end - start, 0); + private void usesMultifile(Node rootNode, LanguageVersionHandler languageVersionHandler, RuleSets ruleSets, + Language language) { + + if (ruleSets.usesMultifile(language)) { + try (TimedOperation to = TimeTracker.startOperation(TimedOperationCategory.MULTIFILE_ANALYSIS)) { + languageVersionHandler.getMultifileFacade().start(rootNode); + } } } + private void processSource(Reader sourceCode, RuleSets ruleSets, RuleContext ctx) { LanguageVersion languageVersion = ctx.getLanguageVersion(); LanguageVersionHandler languageVersionHandler = languageVersion.getLanguageVersionHandler(); Parser parser = PMD.parserFor(languageVersion, configuration); Node rootNode = parse(ctx, sourceCode, parser); + resolveQualifiedNames(rootNode, languageVersionHandler); symbolFacade(rootNode, languageVersionHandler); Language language = languageVersion.getLanguage(); usesDFA(languageVersion, rootNode, ruleSets, language); usesTypeResolution(languageVersion, rootNode, ruleSets, language); - usesMetrics(languageVersion, rootNode, ruleSets, language); + usesMultifile(rootNode, languageVersionHandler, ruleSets, language); List<Node> acus = Collections.singletonList(rootNode); ruleSets.apply(acus, ctx, language); diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/annotation/Experimental.java b/pmd-core/src/main/java/net/sourceforge/pmd/annotation/Experimental.java new file mode 100644 index 00000000000..82a838e616f --- /dev/null +++ b/pmd-core/src/main/java/net/sourceforge/pmd/annotation/Experimental.java @@ -0,0 +1,18 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.annotation; + +import java.lang.annotation.Documented; + + +/** + * Indicates the feature is in experimental state: its existence, signature or behavior + * might change without warning from one release to the next. + * + * @since 6.7.0 + */ +@Documented +public @interface Experimental { +} diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/annotation/InternalApi.java b/pmd-core/src/main/java/net/sourceforge/pmd/annotation/InternalApi.java new file mode 100644 index 00000000000..fbcef903503 --- /dev/null +++ b/pmd-core/src/main/java/net/sourceforge/pmd/annotation/InternalApi.java @@ -0,0 +1,25 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.annotation; + +import java.lang.annotation.Documented; + + +/** + * Tags API members that are not publicly supported API. + * Such members may be removed, renamed, moved, or otherwise + * broken at any time and should not be relied upon outside + * of the main PMD codebase. + * + * <p>Members and types tagged with this annotation will remain + * supported until 7.0.0, after which some will be moved to internal + * packages, or will see their visibility reduced. + * + * @since 6.7.0 + */ +// NOTE: use @Deprecated with this annotation to raise a compiler warning until 7.0.0 +@Documented +public @interface InternalApi { +} diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/annotation/ReservedSubclassing.java b/pmd-core/src/main/java/net/sourceforge/pmd/annotation/ReservedSubclassing.java new file mode 100644 index 00000000000..47f10de80cc --- /dev/null +++ b/pmd-core/src/main/java/net/sourceforge/pmd/annotation/ReservedSubclassing.java @@ -0,0 +1,37 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Target; + + +/** + * Indicates that subclassing this type is not publicly + * supported API. Abstract methods may be added or removed + * at any time, which could break binary compatibility with + * existing implementors. Protected methods are also part of + * the private API of this type. + * + * <p>The API that is not inheritance-specific (unless {@linkplain InternalApi noted otherwise}, + * all public members), is still public API and will remain binary- + * compatible between major releases. + * + * <p>Types tagged with this annotation will remain supported + * until 7.0.0, at which point no guarantees will be maintained + * about the stability of the inheritance hierarchy for external + * clients. + * + * <p>This should be used for example for base rule classes that + * are meant to be used in PMD only, or for AST-related interfaces + * and abstract classes. + * + * @since 6.7.0 + */ +@Target(ElementType.TYPE) +@Documented +public @interface ReservedSubclassing { +} diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/ant/Formatter.java b/pmd-core/src/main/java/net/sourceforge/pmd/ant/Formatter.java index b1224fbed60..140de7ef82c 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/ant/Formatter.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/ant/Formatter.java @@ -206,9 +206,9 @@ private static String getConsoleEncoding() { if (res instanceof Charset) { return ((Charset) res).name(); } - } catch (NoSuchFieldException e) { + } catch (NoSuchFieldException ignored) { // fall-through - } catch (IllegalAccessException e) { + } catch (IllegalAccessException ignored) { // fall-through } return getNativeConsoleEncoding(); @@ -224,11 +224,11 @@ private static String getNativeConsoleEncoding() { if (res instanceof String) { return (String) res; } - } catch (NoSuchMethodException e) { + } catch (NoSuchMethodException ignored) { // fall-through - } catch (InvocationTargetException e) { + } catch (InvocationTargetException ignored) { // fall-through - } catch (IllegalAccessException e) { + } catch (IllegalAccessException ignored) { // fall-through } return null; diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/ant/PMDTask.java b/pmd-core/src/main/java/net/sourceforge/pmd/ant/PMDTask.java index 85241b290f3..378b8396e2c 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/ant/PMDTask.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/ant/PMDTask.java @@ -36,6 +36,7 @@ public class PMDTask extends Task { private String failuresPropertyName; private SourceLanguage sourceLanguage; private String cacheLocation; + private boolean noCache; private final Collection<RuleSetWrapper> nestedRules = new ArrayList<>(); @Override @@ -254,4 +255,13 @@ public String getCacheLocation() { public void setCacheLocation(String cacheLocation) { this.cacheLocation = cacheLocation; } + + + public boolean isNoCache() { + return noCache; + } + + public void setNoCache(boolean noCache) { + this.noCache = noCache; + } } diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/ant/internal/PMDTaskImpl.java b/pmd-core/src/main/java/net/sourceforge/pmd/ant/internal/PMDTaskImpl.java index b76a0682b39..e7d34c5bdd6 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/ant/internal/PMDTaskImpl.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/ant/internal/PMDTaskImpl.java @@ -12,10 +12,7 @@ import java.util.LinkedList; import java.util.List; import java.util.concurrent.atomic.AtomicInteger; -import java.util.logging.Handler; -import java.util.logging.Level; -import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.StringUtils; import org.apache.tools.ant.AntClassLoader; import org.apache.tools.ant.BuildException; @@ -42,7 +39,9 @@ import net.sourceforge.pmd.lang.LanguageVersion; import net.sourceforge.pmd.renderers.AbstractRenderer; import net.sourceforge.pmd.renderers.Renderer; +import net.sourceforge.pmd.util.ClasspathClassLoader; import net.sourceforge.pmd.util.IOUtil; +import net.sourceforge.pmd.util.ResourceLoader; import net.sourceforge.pmd.util.datasource.DataSource; import net.sourceforge.pmd.util.datasource.FileDataSource; import net.sourceforge.pmd.util.log.AntLogHandler; @@ -79,13 +78,14 @@ public PMDTaskImpl(PMDTask task) { this.failuresPropertyName = task.getFailuresPropertyName(); configuration.setMinimumPriority(RulePriority.valueOf(task.getMinimumPriority())); configuration.setAnalysisCacheLocation(task.getCacheLocation()); + configuration.setIgnoreIncrementalAnalysis(task.isNoCache()); SourceLanguage version = task.getSourceLanguage(); if (version != null) { LanguageVersion languageVersion = LanguageRegistry - .findLanguageVersionByTerseName(version.getName() + " " + version.getVersion()); + .findLanguageVersionByTerseName(version.getName() + ' ' + version.getVersion()); if (languageVersion == null) { - throw new BuildException("The following language is not supported:" + version + "."); + throw new BuildException("The following language is not supported:" + version + '.'); } configuration.setDefaultLanguageVersion(languageVersion); } @@ -101,9 +101,11 @@ public PMDTaskImpl(PMDTask task) { private void doTask() { setupClassLoader(); - + // Setup RuleSetFactory and validate RuleSets - RuleSetFactory ruleSetFactory = RulesetsFactoryUtils.getRulesetFactory(configuration); + final ResourceLoader rl = setupResourceLoader(); + RuleSetFactory ruleSetFactory = RulesetsFactoryUtils.getRulesetFactory(configuration, rl); + try { // This is just used to validate and display rules. Each thread will create its own ruleset String ruleSets = configuration.getRuleSets(); @@ -208,6 +210,27 @@ public String defaultFileExtension() { } } + private ResourceLoader setupResourceLoader() { + if (classpath == null) { + classpath = new Path(project); + } + + /* + * 'basedir' is added to the path to make sure that relative paths such + * as "<ruleset>resources/custom_ruleset.xml</ruleset>" still work when + * ant is invoked from a different directory using "-f" + */ + classpath.add(new Path(null, project.getBaseDir().toString())); + + project.log("Using the AntClassLoader: " + classpath, Project.MSG_VERBOSE); + // must be true, otherwise you'll get ClassCastExceptions as classes + // are loaded twice + // and exist in multiple class loaders + final boolean parentFirst = true; + return new ResourceLoader(new AntClassLoader(Thread.currentThread().getContextClassLoader(), + project, classpath, parentFirst)); + } + private void handleError(RuleContext ctx, Report errorReport, RuntimeException pmde) { pmde.printStackTrace(); @@ -216,12 +239,13 @@ private void handleError(RuleContext ctx, Report errorReport, RuntimeException p Throwable cause = pmde.getCause(); if (cause != null) { - StringWriter strWriter = new StringWriter(); - PrintWriter printWriter = new PrintWriter(strWriter); - cause.printStackTrace(printWriter); - project.log(strWriter.toString(), Project.MSG_VERBOSE); - IOUtils.closeQuietly(printWriter); - + try (StringWriter strWriter = new StringWriter(); + PrintWriter printWriter = new PrintWriter(strWriter)) { + cause.printStackTrace(printWriter); + project.log(strWriter.toString(), Project.MSG_VERBOSE); + } catch (IOException e) { + project.log("Error while closing stream", e, Project.MSG_ERR); + } if (StringUtils.isNotBlank(cause.getMessage())) { project.log(cause.getMessage(), Project.MSG_VERBOSE); } @@ -234,24 +258,6 @@ private void handleError(RuleContext ctx, Report errorReport, RuntimeException p } private void setupClassLoader() { - if (classpath == null) { - classpath = new Path(project); - } - /* - * 'basedir' is added to the path to make sure that relative paths such - * as "<ruleset>resources/custom_ruleset.xml</ruleset>" still work when - * ant is invoked from a different directory using "-f" - */ - classpath.add(new Path(null, project.getBaseDir().toString())); - - project.log("Using the AntClassLoader: " + classpath, Project.MSG_VERBOSE); - // must be true, otherwise you'll get ClassCastExceptions as classes - // are loaded twice - // and exist in multiple class loaders - boolean parentFirst = true; - configuration.setClassLoader( - new AntClassLoader(Thread.currentThread().getContextClassLoader(), project, classpath, parentFirst)); - try { if (auxClasspath != null) { project.log("Using auxclasspath: " + auxClasspath, Project.MSG_VERBOSE); @@ -263,13 +269,17 @@ private void setupClassLoader() { } public void execute() throws BuildException { - final Handler antLogHandler = new AntLogHandler(project); - final ScopedLogHandlersManager logManager = new ScopedLogHandlersManager(Level.FINEST, antLogHandler); + final AntLogHandler antLogHandler = new AntLogHandler(project); + final ScopedLogHandlersManager logManager = new ScopedLogHandlersManager(antLogHandler.getAntLogLevel(), antLogHandler); try { doTask(); } finally { logManager.close(); - IOUtil.tryCloseClassLoader(configuration.getClassLoader()); + // only close the classloader, if it is ours. Otherwise we end up with class not found + // exceptions + if (configuration.getClassLoader() instanceof ClasspathClassLoader) { + IOUtil.tryCloseClassLoader(configuration.getClassLoader()); + } } } diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/benchmark/Benchmark.java b/pmd-core/src/main/java/net/sourceforge/pmd/benchmark/Benchmark.java index ac37b993e59..7cf422214a6 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/benchmark/Benchmark.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/benchmark/Benchmark.java @@ -5,32 +5,37 @@ package net.sourceforge.pmd.benchmark; /** + * Represents an execution phase for benchmarking purposes. * * @author Brian Remedios */ +@Deprecated public enum Benchmark { - Rule(0, null), - RuleChainRule(1, null), - CollectFiles(2, "Collect files"), - LoadRules(3, "Load rules"), - Parser(4, "Parser"), - SymbolTable(5, "Symbol table"), - DFA(6, "DFA"), - TypeResolution(7, "Type resolution"), - MetricsVisitor(8, "Metrics"), - RuleChainVisit(9, "RuleChain visit"), - Reporting(10, "Reporting"), - RuleTotal(11, "Rule total"), - RuleChainTotal(12, "Rule chain rule total"), - MeasuredTotal(13, "Measured total"), - NonMeasuredTotal(14, "Non-measured total"), - TotalPMD(15, "Total PMD"); + // The constants must be sorted in execution order, + // the index is derived from the ordinal of the constant + Rule(null), + RuleChainRule(null), + CollectFiles("Collect files"), + LoadRules("Load rules"), + Parser("Parser"), + QualifiedNameResolution("Qualified name resolution"), + SymbolTable("Symbol table"), + DFA("DFA"), + TypeResolution("Type resolution"), + RuleChainVisit("RuleChain visit"), + Multifile("Multifile analysis"), + Reporting("Reporting"), + RuleTotal("Rule total"), + RuleChainTotal("Rule chain rule total"), + MeasuredTotal("Measured total"), + NonMeasuredTotal("Non-measured total"), + TotalPMD("Total PMD"); - public final int index; + public final int index = ordinal(); public final String name; - Benchmark(int idx, String theName) { - index = idx; + + Benchmark(String theName) { name = theName; } } diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/benchmark/BenchmarkReport.java b/pmd-core/src/main/java/net/sourceforge/pmd/benchmark/BenchmarkReport.java index 49f021cf40a..c0cdb123b07 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/benchmark/BenchmarkReport.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/benchmark/BenchmarkReport.java @@ -12,19 +12,20 @@ * * @author Brian Remedios */ +@Deprecated public interface BenchmarkReport { /** * - * @param stressResults - * @param out + * @param stressResults the durations from the stress test run + * @param stream the report is written into this stream */ - void generate(Set<RuleDuration> stressResults, PrintStream out); + void generate(Set<RuleDuration> stressResults, PrintStream stream); /** * * @param benchmarksByName - * @param out + * @param stream the report is written into this stream */ - void generate(Map<String, BenchmarkResult> benchmarksByName, PrintStream out); + void generate(Map<String, BenchmarkResult> benchmarksByName, PrintStream stream); } diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/benchmark/BenchmarkResult.java b/pmd-core/src/main/java/net/sourceforge/pmd/benchmark/BenchmarkResult.java index 80ec47df53f..5f929eda3fe 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/benchmark/BenchmarkResult.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/benchmark/BenchmarkResult.java @@ -4,6 +4,7 @@ package net.sourceforge.pmd.benchmark; +@Deprecated class BenchmarkResult implements Comparable<BenchmarkResult> { public final Benchmark type; @@ -37,10 +38,9 @@ public void update(long time, long count) { @Override public int compareTo(BenchmarkResult benchmarkResult) { - int cmp = type.index - benchmarkResult.type.index; + int cmp = Integer.compare(type.index, benchmarkResult.type.index); if (cmp == 0) { - long delta = this.time - benchmarkResult.time; - cmp = delta > 0 ? 1 : (delta < 0 ? -1 : 0); + cmp = Long.compare(this.time, benchmarkResult.time); } return cmp; } diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/benchmark/Benchmarker.java b/pmd-core/src/main/java/net/sourceforge/pmd/benchmark/Benchmarker.java index 3f60278e59a..d200d351a5a 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/benchmark/Benchmarker.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/benchmark/Benchmarker.java @@ -4,9 +4,10 @@ package net.sourceforge.pmd.benchmark; +import java.io.BufferedInputStream; import java.io.IOException; +import java.io.InputStream; import java.io.InputStreamReader; -import java.io.Reader; import java.util.HashMap; import java.util.Iterator; import java.util.List; @@ -14,7 +15,6 @@ import java.util.Set; import java.util.TreeSet; -import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.StringUtils; import net.sourceforge.pmd.PMD; @@ -36,10 +36,10 @@ import net.sourceforge.pmd.util.datasource.DataSource; /** - * - * + * @deprecated use {@link TimeTracker} instead */ -public class Benchmarker { +@Deprecated +public final class Benchmarker { private static final Map<String, BenchmarkResult> BENCHMARKS_BY_NAME = new HashMap<>(); @@ -144,11 +144,8 @@ private static void parseStress(Parser parser, List<DataSource> dataSources, boo long start = System.currentTimeMillis(); for (DataSource dataSource : dataSources) { - InputStreamReader reader = new InputStreamReader(dataSource.getInputStream()); - try { + try (InputStreamReader reader = new InputStreamReader(dataSource.getInputStream())) { parser.parse(dataSource.getNiceFileName(false, null), reader); - } finally { - IOUtils.closeQuietly(reader); } } @@ -191,12 +188,9 @@ private static void stress(LanguageVersion languageVersion, RuleSet ruleSet, Lis RuleContext ctx = new RuleContext(); long start = System.currentTimeMillis(); for (DataSource dataSource : dataSources) { - Reader reader = new InputStreamReader(dataSource.getInputStream()); - try { + try (InputStream stream = new BufferedInputStream(dataSource.getInputStream())) { ctx.setSourceCodeFilename(dataSource.getNiceFileName(false, null)); - new SourceCodeProcessor(config).processSourceCode(reader, ruleSets, ctx); - } finally { - IOUtils.closeQuietly(reader); + new SourceCodeProcessor(config).processSourceCode(stream, ruleSets, ctx); } } long end = System.currentTimeMillis(); diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/benchmark/RuleDuration.java b/pmd-core/src/main/java/net/sourceforge/pmd/benchmark/RuleDuration.java index 417ee3d41af..35e2e6affa9 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/benchmark/RuleDuration.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/benchmark/RuleDuration.java @@ -6,6 +6,7 @@ import net.sourceforge.pmd.Rule; +@Deprecated public class RuleDuration implements Comparable<RuleDuration> { public Rule rule; diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/benchmark/StringBuilderCR.java b/pmd-core/src/main/java/net/sourceforge/pmd/benchmark/StringBuilderCR.java index a07840f606f..36cab0443ea 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/benchmark/StringBuilderCR.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/benchmark/StringBuilderCR.java @@ -10,6 +10,7 @@ * * @author Brian Remedios */ +@Deprecated public class StringBuilderCR { private final String cr; diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/benchmark/TextReport.java b/pmd-core/src/main/java/net/sourceforge/pmd/benchmark/TextReport.java index 9bc1c0b27ac..24881067906 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/benchmark/TextReport.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/benchmark/TextReport.java @@ -20,25 +20,19 @@ * * */ +@Deprecated public class TextReport implements BenchmarkReport { private static final int TIME_COLUMN = 48; private static final int NAME_COLUMN_WIDTH = 50; private static final int VALUE_COLUMN_WIDTH = 8; - /** - * - * @param stressResults the durations from the stress test run - * @param out - * PrintStream - * @see BenchmarkReport#generate(Set, PrintStream) - */ @Override - public void generate(Set<RuleDuration> stressResults, PrintStream out) { + public void generate(Set<RuleDuration> stressResults, PrintStream stream) { - out.println("========================================================="); - out.println("Rule\t\t\t\t\t\tTime in ms"); - out.println("========================================================="); + stream.println("========================================================="); + stream.println("Rule\t\t\t\t\t\tTime in ms"); + stream.println("========================================================="); for (RuleDuration result : stressResults) { StringBuilder buffer = new StringBuilder(result.rule.getName()); @@ -46,25 +40,18 @@ public void generate(Set<RuleDuration> stressResults, PrintStream out) { buffer.append(' '); } buffer.append(result.time); - out.println(out.toString()); + stream.println(stream.toString()); } - out.println("========================================================="); + stream.println("========================================================="); } public void report(Map<String, BenchmarkResult> benchmarksByName) { generate(benchmarksByName, System.out); } - /** - * - * @param benchmarksByName - * @param out - * PrintStream - * @see BenchmarkReport#generate(Map, PrintStream) - */ @Override - public void generate(Map<String, BenchmarkResult> benchmarksByName, PrintStream out) { + public void generate(Map<String, BenchmarkResult> benchmarksByName, PrintStream stream) { List<BenchmarkResult> results = new ArrayList<>(benchmarksByName.values()); @@ -153,7 +140,7 @@ public void generate(Map<String, BenchmarkResult> benchmarksByName, PrintStream buf.appendLn(buf2.toString()); } - out.print(buf.toString()); + stream.print(buf.toString()); } /** diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/benchmark/TextTimingReportRenderer.java b/pmd-core/src/main/java/net/sourceforge/pmd/benchmark/TextTimingReportRenderer.java new file mode 100644 index 00000000000..6aa88bc486a --- /dev/null +++ b/pmd-core/src/main/java/net/sourceforge/pmd/benchmark/TextTimingReportRenderer.java @@ -0,0 +1,144 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.benchmark; + +import java.io.IOException; +import java.io.Writer; +import java.text.MessageFormat; +import java.util.Comparator; +import java.util.Map; +import java.util.Map.Entry; +import java.util.TreeSet; + +import org.apache.commons.lang3.StringUtils; + +import net.sourceforge.pmd.PMD; +import net.sourceforge.pmd.benchmark.TimeTracker.TimedResult; + +/** + * A text based renderer for {@link TimingReport}. + * @author Juan Martín Sotuyo Dodero + */ +public class TextTimingReportRenderer implements TimingReportRenderer { + + private static final String TIME_FORMAT = "{0,number,0.0000}"; + private static final String CUSTOM_COUNTER_FORMAT = "{0,number,###,###,###}"; + + private static final int LABEL_COLUMN_WIDTH = 50; + private static final int TIME_COLUMN_WIDTH = 12; + private static final int SELF_TIME_COLUMN_WIDTH = 17; + private static final int CALL_COLUMN_WIDTH = 9; + private static final int COUNTER_COLUMN_WIDTH = 12; + + private static final int COLUMNS = LABEL_COLUMN_WIDTH + TIME_COLUMN_WIDTH + + SELF_TIME_COLUMN_WIDTH + CALL_COLUMN_WIDTH + COUNTER_COLUMN_WIDTH; + + @Override + public void render(final TimingReport report, final Writer writer) throws IOException { + for (final TimedOperationCategory category : TimedOperationCategory.values()) { + final Map<String, TimedResult> labeledMeasurements = report.getLabeledMeasurements(category); + if (!labeledMeasurements.isEmpty()) { + renderCategoryMeasurements(category, labeledMeasurements, writer); + } + } + + renderHeader("Summary", writer); + + for (final TimedOperationCategory category : TimedOperationCategory.values()) { + final TimedResult timedResult = report.getUnlabeledMeasurements(category); + if (timedResult != null) { + renderMeasurement(category.displayName(), timedResult, writer); + } + } + + writer.write(PMD.EOL); + renderHeader("Total", writer); + + writer.write(StringUtils.rightPad("Wall Clock Time", LABEL_COLUMN_WIDTH)); + final String wallClockTime = MessageFormat.format(TIME_FORMAT, report.getWallClockMillis() / 1000.0); + writer.write(StringUtils.leftPad(wallClockTime, TIME_COLUMN_WIDTH)); + writer.write(PMD.EOL); + + writer.flush(); + } + + private void renderMeasurement(final String label, final TimedResult timedResult, + final Writer writer) throws IOException { + writer.write(StringUtils.rightPad(label, LABEL_COLUMN_WIDTH)); + + final String time = MessageFormat.format(TIME_FORMAT, timedResult.totalTimeNanos.get() / 1000000000.0); + writer.write(StringUtils.leftPad(time, TIME_COLUMN_WIDTH)); + + final String selfTime = MessageFormat.format(TIME_FORMAT, timedResult.selfTimeNanos.get() / 1000000000.0); + writer.write(StringUtils.leftPad(selfTime, SELF_TIME_COLUMN_WIDTH)); + + if (timedResult.callCount.get() > 0) { + final String callCount = MessageFormat.format(CUSTOM_COUNTER_FORMAT, timedResult.callCount.get()); + writer.write(StringUtils.leftPad(callCount, CALL_COLUMN_WIDTH)); + + if (timedResult.extraDataCounter.get() > 0) { + final String counter = MessageFormat.format(CUSTOM_COUNTER_FORMAT, timedResult.extraDataCounter.get()); + writer.write(StringUtils.leftPad(counter, COUNTER_COLUMN_WIDTH)); + } + } + + writer.write(PMD.EOL); + } + + private void renderCategoryMeasurements(final TimedOperationCategory category, + final Map<String, TimedResult> labeledMeasurements, final Writer writer) throws IOException { + renderHeader(category.displayName(), writer); + + final TimedResult grandTotal = new TimedResult(); + final TreeSet<Map.Entry<String, TimedResult>> sortedKeySet = new TreeSet<>( + new Comparator<Map.Entry<String, TimedResult>>() { + @Override + public int compare(final Entry<String, TimedResult> o1, final Entry<String, TimedResult> o2) { + return Long.compare(o1.getValue().selfTimeNanos.get(), o2.getValue().selfTimeNanos.get()); + } + }); + sortedKeySet.addAll(labeledMeasurements.entrySet()); + + for (final Map.Entry<String, TimedResult> entry : sortedKeySet) { + renderMeasurement(entry.getKey(), entry.getValue(), writer); + grandTotal.mergeTimes(entry.getValue()); + } + + writer.write(PMD.EOL); + renderMeasurement("Total " + category.displayName(), grandTotal, writer); + writer.write(PMD.EOL); + } + + private void renderHeader(final String displayName, final Writer writer) throws IOException { + final StringBuilder sb = new StringBuilder(COLUMNS) + .append(displayName); + + // Make sure we have an even-length string + if (displayName.length() % 2 == 1) { + sb.append(' '); + } + + // Surround with <<< and >>> + sb.insert(0, "<<< ").append(" >>>"); + + // Create the ruler + while (sb.length() < COLUMNS) { + sb.insert(0, '-').append('-'); + } + + writer.write(sb.toString()); + writer.write(PMD.EOL); + + // Write table titles + writer.write(StringUtils.rightPad("Label", LABEL_COLUMN_WIDTH)); + writer.write(StringUtils.leftPad("Time (secs)", TIME_COLUMN_WIDTH)); + writer.write(StringUtils.leftPad("Self Time (secs)", SELF_TIME_COLUMN_WIDTH)); + writer.write(StringUtils.leftPad("# Calls", CALL_COLUMN_WIDTH)); + writer.write(StringUtils.leftPad("Counter", COUNTER_COLUMN_WIDTH)); + writer.write(PMD.EOL); + writer.write(PMD.EOL); + } + +} diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/benchmark/TimeTracker.java b/pmd-core/src/main/java/net/sourceforge/pmd/benchmark/TimeTracker.java new file mode 100644 index 00000000000..8bf202fa86c --- /dev/null +++ b/pmd-core/src/main/java/net/sourceforge/pmd/benchmark/TimeTracker.java @@ -0,0 +1,286 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.benchmark; + +import java.util.Collections; +import java.util.LinkedList; +import java.util.Objects; +import java.util.Queue; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; + +/** + * A time tracker class to measure time spent on different sections of PMD analysis. + * The class is thread-aware, allowing to differentiate CPU and wall clock time. + * + * @author Juan Martín Sotuyo Dodero + */ +public final class TimeTracker { + + private static boolean trackTime = false; + private static long wallClockStartMillis = -1; + private static final ThreadLocal<Queue<TimerEntry>> TIMER_ENTRIES; + private static final ConcurrentMap<TimedOperationKey, TimedResult> ACCUMULATED_RESULTS = new ConcurrentHashMap<>(); + private static final TimedOperation NOOP_TIMED_OPERATION = new TimedOperation() { + + @Override + public void close() { + // noop + } + + @Override + public void close(final int count) { + // noop + } + }; + + static { + TIMER_ENTRIES = new ThreadLocal<Queue<TimerEntry>>() { + @Override + protected Queue<TimerEntry> initialValue() { + return Collections.asLifoQueue(new LinkedList<TimerEntry>()); + } + }; + } + + private TimeTracker() { + throw new AssertionError("Can't instantiate utility class"); + } + + /** + * Starts global tracking. Allows tracking operations to take place and starts the wall clock. + * Must be called once PMD starts if tracking is desired, no tracking will be performed otherwise. + */ + public static void startGlobalTracking() { + wallClockStartMillis = System.currentTimeMillis(); + trackTime = true; + ACCUMULATED_RESULTS.clear(); // just in case + initThread(); // init main thread + } + + /** + * Stops global tracking. Stops the wall clock. All further operations will be treated as NOOP. + * @return The timed data obtained through the run. + */ + public static TimingReport stopGlobalTracking() { + if (!trackTime) { + return null; + } + + finishThread(); // finish the main thread + trackTime = false; + + // Fix UNACCOUNTED metric (total time is meaningless as is call count) + final TimedResult unaccountedResult = ACCUMULATED_RESULTS.get( + new TimedOperationKey(TimedOperationCategory.UNACCOUNTED, null)); + unaccountedResult.totalTimeNanos.set(unaccountedResult.selfTimeNanos.get()); + unaccountedResult.callCount.set(0); + + return new TimingReport(System.currentTimeMillis() - wallClockStartMillis, ACCUMULATED_RESULTS); + } + + /** + * Initialize a thread, starting to track it's own time. + */ + public static void initThread() { + if (!trackTime) { + return; + } + + startOperation(TimedOperationCategory.UNACCOUNTED); + } + + /** + * Finishes tracking a thread. + */ + public static void finishThread() { + if (!trackTime) { + return; + } + + finishOperation(0); + + // clean up thread-locals in multithread analysis + if (TIMER_ENTRIES.get().isEmpty()) { + TIMER_ENTRIES.remove(); + } + } + + /** + * Starts tracking an operation. + * @param category The category under which to track the operation. + * @return The current timed operation being tracked. + */ + public static TimedOperation startOperation(final TimedOperationCategory category) { + return startOperation(category, null); + } + + /** + * Starts tracking an operation. + * @param category The category under which to track the operation. + * @param label A label to be added to the category. Allows to differentiate measures within a single category. + * @return The current timed operation being tracked. + */ + public static TimedOperation startOperation(final TimedOperationCategory category, final String label) { + if (!trackTime) { + return NOOP_TIMED_OPERATION; + } + + TIMER_ENTRIES.get().add(new TimerEntry(category, label)); + return new TimedOperationImpl(); + } + + /** + * Finishes tracking an operation. + * @param extraDataCounter An optional additional data counter to track along the measurements. + * Users are free to track any extra value they want (ie: number of analyzed nodes, + * iterations in a loop, etc.) + */ + /* default */ static void finishOperation(final long extraDataCounter) { + if (!trackTime) { + return; + } + + final Queue<TimerEntry> queue = TIMER_ENTRIES.get(); + final TimerEntry timerEntry = queue.remove(); + + // Compute if absent + TimedResult result = ACCUMULATED_RESULTS.get(timerEntry.operation); + if (result == null) { + ACCUMULATED_RESULTS.putIfAbsent(timerEntry.operation, new TimedResult()); + result = ACCUMULATED_RESULTS.get(timerEntry.operation); + } + + // Update counters and let next element on the stack ignore the time we spent + final long delta = result.accumulate(timerEntry, extraDataCounter); + if (!queue.isEmpty()) { + queue.peek().inNestedOperationsNanos += delta; + } + } + + /** + * An entry in the open timers queue. Defines an operation that has started and hasn't finished yet. + */ + private static class TimerEntry { + /* package */ final TimedOperationKey operation; + /* package */ final long start; + /* package */ long inNestedOperationsNanos = 0; + + /* package */ TimerEntry(final TimedOperationCategory category, final String label) { + this.operation = new TimedOperationKey(category, label); + this.start = System.nanoTime(); + } + + @Override + public String toString() { + return "TimerEntry for " + operation; + } + } + + /** + * Aggregate results measured so far for a given category + label. + */ + /* package */ static class TimedResult { + /* package */ AtomicLong totalTimeNanos = new AtomicLong(); + /* package */ AtomicLong selfTimeNanos = new AtomicLong(); + /* package */ AtomicInteger callCount = new AtomicInteger(); + /* package */ AtomicLong extraDataCounter = new AtomicLong(); + + /** + * Adds a new {@link TimerEntry} to the results. + * @param timerEntry The entry to be added + * @param extraData Any extra data counter to be added + * @return The delta time transcurred since the {@link TimerEntry} began in nanos. + */ + /* package */ long accumulate(final TimerEntry timerEntry, final long extraData) { + final long delta = System.nanoTime() - timerEntry.start; + + totalTimeNanos.getAndAdd(delta); + selfTimeNanos.getAndAdd(delta - timerEntry.inNestedOperationsNanos); + callCount.getAndIncrement(); + extraDataCounter.getAndAdd(extraData); + + return delta; + } + + /** + * Merges the times (and only the times) from another {@link TimedResult} into self. + * @param timedResult The {@link TimedResult} to merge + */ + /* package */ void mergeTimes(final TimedResult timedResult) { + totalTimeNanos.getAndAdd(timedResult.totalTimeNanos.get()); + selfTimeNanos.getAndAdd(timedResult.selfTimeNanos.get()); + } + } + + /** + * A unique identifier for a timed operation + */ + /* package */ static class TimedOperationKey { + /* package */ final TimedOperationCategory category; + /* package */ final String label; + + /* package */ TimedOperationKey(final TimedOperationCategory category, final String label) { + this.category = category; + this.label = label; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((category == null) ? 0 : category.hashCode()); + result = prime * result + ((label == null) ? 0 : label.hashCode()); + return result; + } + + @Override + public boolean equals(final Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + TimedOperationKey other = (TimedOperationKey) obj; + if (category != other.category) { + return false; + } + return Objects.equals(label, other.label); + } + + @Override + public String toString() { + return "TimedOperationKey [category=" + category + ", label=" + label + "]"; + } + } + + /** + * A standard timed operation implementation. + */ + private static class TimedOperationImpl implements TimedOperation { + private boolean closed = false; + + @Override + public void close() { + close(0); + } + + @Override + public void close(int extraDataCounter) { + if (closed) { + return; + } + + closed = true; + TimeTracker.finishOperation(extraDataCounter); + } + } +} diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/benchmark/TimedOperation.java b/pmd-core/src/main/java/net/sourceforge/pmd/benchmark/TimedOperation.java new file mode 100644 index 00000000000..20978cdf49f --- /dev/null +++ b/pmd-core/src/main/java/net/sourceforge/pmd/benchmark/TimedOperation.java @@ -0,0 +1,25 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.benchmark; + +/** + * Describes a timed operation. It's {@link AutoCloseable}, for ease of use. + */ +public interface TimedOperation extends AutoCloseable { + + /** + * Stops tracking if not already stopped. + */ + @Override + void close(); + + /** + * Stops tracking if not already stopped. + * @param extraDataCounter An optional additional data counter to track along the measurements. + * Users are free to track any extra value they want (ie: number of analyzed nodes, + * iterations in a loop, etc.) + */ + void close(int extraDataCounter); +} diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/benchmark/TimedOperationCategory.java b/pmd-core/src/main/java/net/sourceforge/pmd/benchmark/TimedOperationCategory.java new file mode 100644 index 00000000000..5876a7f77b6 --- /dev/null +++ b/pmd-core/src/main/java/net/sourceforge/pmd/benchmark/TimedOperationCategory.java @@ -0,0 +1,39 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.benchmark; + +import java.util.Locale; + +import org.apache.commons.lang3.StringUtils; + +/** + * @author Juan Martín Sotuyo Dodero + */ +public enum TimedOperationCategory { + RULE, + RULECHAIN_RULE, + COLLECT_FILES, + LOAD_RULES, + PARSER, + QUALIFIED_NAME_RESOLUTION, + SYMBOL_TABLE, + DFA, + TYPE_RESOLUTION, + RULECHAIN_VISIT, + MULTIFILE_ANALYSIS, + REPORTING, + FILE_PROCESSING, + UNACCOUNTED; + + public String displayName() { + final String[] parts = name().toLowerCase(Locale.getDefault()).split("_"); + final StringBuilder sb = new StringBuilder(); + for (final String part : parts) { + sb.append(StringUtils.capitalize(part)).append(' '); + } + sb.setLength(sb.length() - 1); // remove the final space + return sb.toString(); + } +} diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/benchmark/TimingReport.java b/pmd-core/src/main/java/net/sourceforge/pmd/benchmark/TimingReport.java new file mode 100644 index 00000000000..f8fed28d931 --- /dev/null +++ b/pmd-core/src/main/java/net/sourceforge/pmd/benchmark/TimingReport.java @@ -0,0 +1,54 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.benchmark; + +import java.util.HashMap; +import java.util.Map; + +import net.sourceforge.pmd.benchmark.TimeTracker.TimedOperationKey; +import net.sourceforge.pmd.benchmark.TimeTracker.TimedResult; + +/** + * A report on timing data obtained through the execution of PMD + * @author Juan Martín Sotuyo Dodero + */ +public class TimingReport { + + private final long wallClockMillis; + private final Map<TimedOperationKey, TimedResult> results; + + /* package */ TimingReport(final long wallClockMillis, final Map<TimedOperationKey, TimedResult> accumulatedResults) { + this.wallClockMillis = wallClockMillis; + results = accumulatedResults; + } + + public Map<String, TimedResult> getLabeledMeasurements(final TimedOperationCategory category) { + final Map<String, TimedResult> ret = new HashMap<>(); + + for (final Map.Entry<TimedOperationKey, TimedResult> entry : results.entrySet()) { + final TimedOperationKey timedOperation = entry.getKey(); + if (timedOperation.category == category && timedOperation.label != null) { + ret.put(timedOperation.label, entry.getValue()); + } + } + + return ret; + } + + public TimedResult getUnlabeledMeasurements(final TimedOperationCategory category) { + for (final Map.Entry<TimedOperationKey, TimedResult> entry : results.entrySet()) { + final TimedOperationKey timedOperation = entry.getKey(); + if (timedOperation.category == category && timedOperation.label == null) { + return entry.getValue(); + } + } + + return null; + } + + public long getWallClockMillis() { + return wallClockMillis; + } +} diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/benchmark/TimingReportRenderer.java b/pmd-core/src/main/java/net/sourceforge/pmd/benchmark/TimingReportRenderer.java new file mode 100644 index 00000000000..8bd8818c266 --- /dev/null +++ b/pmd-core/src/main/java/net/sourceforge/pmd/benchmark/TimingReportRenderer.java @@ -0,0 +1,23 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.benchmark; + +import java.io.IOException; +import java.io.Writer; + +/** + * Defines a renderer for {@link TimingReport}. + * @author Juan Martín Sotuyo Dodero + */ +public interface TimingReportRenderer { + + /** + * Renders the given report into the given writer. + * @param report The report data to render + * @param writer The writer on which to render + * @throws IOException if the write operation fails + */ + void render(TimingReport report, Writer writer) throws IOException; +} diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/cache/AbstractAnalysisCache.java b/pmd-core/src/main/java/net/sourceforge/pmd/cache/AbstractAnalysisCache.java index 6b128275cca..9a0fa6e5086 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/cache/AbstractAnalysisCache.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/cache/AbstractAnalysisCache.java @@ -5,15 +5,30 @@ package net.sourceforge.pmd.cache; import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.net.URL; import java.net.URLClassLoader; -import java.util.Arrays; +import java.nio.file.FileVisitOption; +import java.nio.file.FileVisitResult; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.SimpleFileVisitor; +import java.nio.file.attribute.BasicFileAttributes; +import java.util.ArrayList; import java.util.Collections; +import java.util.EnumSet; import java.util.List; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; +import java.util.logging.Level; import java.util.logging.Logger; +import java.util.zip.Adler32; +import java.util.zip.CheckedInputStream; -import net.sourceforge.pmd.PMD; +import org.apache.commons.io.IOUtils; + +import net.sourceforge.pmd.PMDVersion; import net.sourceforge.pmd.Rule; import net.sourceforge.pmd.RuleSets; import net.sourceforge.pmd.RuleViolation; @@ -28,15 +43,16 @@ public abstract class AbstractAnalysisCache implements AnalysisCache { protected final String pmdVersion; protected final ConcurrentMap<String, AnalysisResult> fileResultsCache; protected final ConcurrentMap<String, AnalysisResult> updatedResultsCache; - protected long rulesetChecksum; - protected long classpathChecksum; protected final CachedRuleMapper ruleMapper = new CachedRuleMapper(); - + protected long rulesetChecksum; + protected long auxClassPathChecksum; + protected long executionClassPathChecksum; + /** * Creates a new empty cache */ public AbstractAnalysisCache() { - pmdVersion = PMD.VERSION; + pmdVersion = PMDVersion.VERSION; fileResultsCache = new ConcurrentHashMap<>(); updatedResultsCache = new ConcurrentHashMap<>(); } @@ -46,16 +62,24 @@ public boolean isUpToDate(final File sourceFile) { // There is a new file being analyzed, prepare entry in updated cache final AnalysisResult updatedResult = new AnalysisResult(sourceFile); updatedResultsCache.put(sourceFile.getPath(), updatedResult); - + // Now check the old cache final AnalysisResult analysisResult = fileResultsCache.get(sourceFile.getPath()); - - if (analysisResult == null) { - // new file, need to analyze it - return false; + + // is this a known file? has it changed? + final boolean result = analysisResult != null + && analysisResult.getFileChecksum() == updatedResult.getFileChecksum(); + + if (LOG.isLoggable(Level.FINE)) { + if (result) { + LOG.fine("Incremental Analysis cache HIT"); + } else { + LOG.fine("Incremental Analysis cache MISS - " + + (analysisResult != null ? "file changed" : "no previous result found")); + } } - - return analysisResult.getFileChecksum() == updatedResult.getFileChecksum(); + + return result; } @Override @@ -75,32 +99,46 @@ public void analysisFailed(final File sourceFile) { updatedResultsCache.remove(sourceFile.getPath()); } + + /** + * Returns true if the cache exists. If so, normal cache validity checks + * will be performed. Otherwise, the cache is necessarily invalid (e.g. on a first run). + */ + protected abstract boolean cacheExists(); + + @Override - public void checkValidity(final RuleSets ruleSets, final ClassLoader classLoader) { - boolean cacheIsValid = true; + public void checkValidity(final RuleSets ruleSets, final ClassLoader auxclassPathClassLoader) { + boolean cacheIsValid = cacheExists(); - if (ruleSets.getChecksum() != rulesetChecksum) { + if (cacheIsValid && ruleSets.getChecksum() != rulesetChecksum) { LOG.info("Analysis cache invalidated, rulesets changed."); cacheIsValid = false; } - final long classLoaderChecksum; - if (classLoader instanceof URLClassLoader) { - final URLClassLoader urlClassLoader = (URLClassLoader) classLoader; - classLoaderChecksum = Arrays.hashCode(urlClassLoader.getURLs()); - - if (cacheIsValid && classLoaderChecksum != classpathChecksum) { + final long currentAuxClassPathChecksum; + if (auxclassPathClassLoader instanceof URLClassLoader) { + final URLClassLoader urlClassLoader = (URLClassLoader) auxclassPathClassLoader; + currentAuxClassPathChecksum = computeClassPathHash(urlClassLoader.getURLs()); + + if (cacheIsValid && currentAuxClassPathChecksum != auxClassPathChecksum) { // Do we even care? for (final Rule r : ruleSets.getAllRules()) { - if (r.usesDFA() || r.usesTypeResolution()) { - LOG.info("Analysis cache invalidated, classpath changed."); + if (r.isDfa() || r.isTypeResolution()) { + LOG.info("Analysis cache invalidated, auxclasspath changed."); cacheIsValid = false; break; } } } } else { - classLoaderChecksum = 0; + currentAuxClassPathChecksum = 0; + } + + final long currentExecutionClassPathChecksum = computeClassPathHash(getClassPathEntries()); + if (cacheIsValid && currentExecutionClassPathChecksum != executionClassPathChecksum) { + LOG.info("Analysis cache invalidated, execution classpath changed."); + cacheIsValid = false; } if (!cacheIsValid) { @@ -110,14 +148,66 @@ public void checkValidity(final RuleSets ruleSets, final ClassLoader classLoader // Update the local checksums rulesetChecksum = ruleSets.getChecksum(); - classpathChecksum = classLoaderChecksum; + auxClassPathChecksum = currentAuxClassPathChecksum; + executionClassPathChecksum = currentExecutionClassPathChecksum; ruleMapper.initialize(ruleSets); } + private URL[] getClassPathEntries() { + final String classpath = System.getProperty("java.class.path"); + final String[] classpathEntries = classpath.split(File.pathSeparator); + final List<URL> entries = new ArrayList<>(); + + try { + for (final String entry : classpathEntries) { + final File f = new File(entry); + if (f.isFile()) { + entries.add(f.toURI().toURL()); + } else { + Files.walkFileTree(f.toPath(), EnumSet.of(FileVisitOption.FOLLOW_LINKS), Integer.MAX_VALUE, + new SimpleFileVisitor<Path>() { + @Override + public FileVisitResult visitFile(final Path file, + final BasicFileAttributes attrs) throws IOException { + if (!attrs.isSymbolicLink()) { // Broken link that can't be followed + entries.add(file.toUri().toURL()); + } + return FileVisitResult.CONTINUE; + } + }); + } + } + } catch (final IOException e) { + LOG.log(Level.SEVERE, "Incremental analysis can't check execution classpath contents", e); + throw new RuntimeException(e); + } + + return entries.toArray(new URL[0]); + } + + private long computeClassPathHash(final URL... classpathEntry) { + final Adler32 adler32 = new Adler32(); + for (final URL url : classpathEntry) { + try (CheckedInputStream inputStream = new CheckedInputStream(url.openStream(), adler32)) { + // Just read it, the CheckedInputStream will update the checksum on it's own + while (IOUtils.skip(inputStream, Long.MAX_VALUE) == Long.MAX_VALUE) { + // just loop + } + } catch (final FileNotFoundException ignored) { + LOG.warning("Auxclasspath entry " + url.toString() + " doesn't exist, ignoring it"); + } catch (final IOException e) { + // Can this even happen? + LOG.log(Level.SEVERE, "Incremental analysis can't check auxclasspath contents", e); + throw new RuntimeException(e); + } + } + return adler32.getValue(); + } + @Override public void ruleViolationAdded(final RuleViolation ruleViolation) { final AnalysisResult analysisResult = updatedResultsCache.get(ruleViolation.getFilename()); - + analysisResult.addViolation(ruleViolation); } diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/cache/AnalysisCache.java b/pmd-core/src/main/java/net/sourceforge/pmd/cache/AnalysisCache.java index c2632d74c21..001e3acb6d2 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/cache/AnalysisCache.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/cache/AnalysisCache.java @@ -13,16 +13,22 @@ /** * An analysis cache for incremental analysis. + * Simultaneously manages the old version of the cache, + * and the new, most up-to-date violation cache. */ public interface AnalysisCache extends ThreadSafeReportListener { /** - * Persist the analysis results on whatever means is used by the cache + * Persists the updated analysis results on whatever medium is used by the cache. */ void persist(); /** - * Check if a given file is up to date in the cache and can be skipped from analysis + * Checks if a given file is up to date in the cache and can be skipped from analysis. + * Regardless of the return value of this method, each call adds the parameter to the + * updated cache, which allows {@link #ruleViolationAdded(RuleViolation)} to add a rule + * violation to the file. TODO is this really best behaviour? This side-effects seems counter-intuitive. + * * @param sourceFile The file to check in the cache * @return True if the cache is a hit, false otherwise */ @@ -36,15 +42,19 @@ public interface AnalysisCache extends ThreadSafeReportListener { List<RuleViolation> getCachedViolations(File sourceFile); /** - * Notifies the cache that analysis of the given file has failed and should not be cached + * Notifies the cache that analysis of the given file has failed and should not be cached. * @param sourceFile The file whose analysis failed */ void analysisFailed(File sourceFile); /** * Checks if the cache is valid for the configured rulesets and class loader. + * If the provided rulesets and classpath don't match those of the cache, the + * cache is invalidated. This needs to be called before analysis, as it + * conditions the good behaviour of {@link #isUpToDate(File)}. + * * @param ruleSets The rulesets configured for this analysis. - * @param classLoader The class loader configured for this analysis. + * @param auxclassPathClassLoader The class loader for auxclasspath configured for this analysis. */ - void checkValidity(RuleSets ruleSets, ClassLoader classLoader); + void checkValidity(RuleSets ruleSets, ClassLoader auxclassPathClassLoader); } diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/cache/AnalysisResult.java b/pmd-core/src/main/java/net/sourceforge/pmd/cache/AnalysisResult.java index 5475556b481..4e4569bedeb 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/cache/AnalysisResult.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/cache/AnalysisResult.java @@ -38,15 +38,15 @@ public AnalysisResult(final File sourceFile) { private static long computeFileChecksum(final File sourceFile) { try ( CheckedInputStream stream = new CheckedInputStream( - new BufferedInputStream(new FileInputStream(sourceFile)), new Adler32()); + new BufferedInputStream(new FileInputStream(sourceFile)), new Adler32()); ) { // Just read it, the CheckedInputStream will update the checksum on it's own IOUtils.skipFully(stream, sourceFile.length()); return stream.getChecksum().getValue(); - } catch (final IOException e) { + } catch (final IOException ignored) { // We don't really care, if it's unreadable - // the analysis will fail and report the error on it's own + // the analysis will fail and report the error on it's own since the checksum won't match } return 0; diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/cache/FileAnalysisCache.java b/pmd-core/src/main/java/net/sourceforge/pmd/cache/FileAnalysisCache.java index 450f9701e50..84065947154 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/cache/FileAnalysisCache.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/cache/FileAnalysisCache.java @@ -17,7 +17,7 @@ import java.util.List; import java.util.Map; -import net.sourceforge.pmd.PMD; +import net.sourceforge.pmd.PMDVersion; import net.sourceforge.pmd.RuleViolation; /** @@ -26,7 +26,7 @@ public class FileAnalysisCache extends AbstractAnalysisCache { private final File cacheFile; - + /** * Creates a new cache backed by the given file, and attempts to load pre-existing data from it. * @param cache The file on which to store analysis cache @@ -43,25 +43,26 @@ public FileAnalysisCache(final File cache) { * @param cacheFile The file which backs the file analysis cache. */ private void loadFromFile(final File cacheFile) { - if (cacheFile.exists()) { + if (cacheExists()) { try ( DataInputStream inputStream = new DataInputStream( new BufferedInputStream(new FileInputStream(cacheFile))); ) { final String cacheVersion = inputStream.readUTF(); - - if (PMD.VERSION.equals(cacheVersion)) { + + if (PMDVersion.VERSION.equals(cacheVersion)) { // Cache seems valid, load the rest - + // Get checksums rulesetChecksum = inputStream.readLong(); - classpathChecksum = inputStream.readLong(); - + auxClassPathChecksum = inputStream.readLong(); + executionClassPathChecksum = inputStream.readLong(); + // Cached results while (inputStream.available() > 0) { final String fileName = inputStream.readUTF(); final long checksum = inputStream.readLong(); - + final int countViolations = inputStream.readInt(); final List<RuleViolation> violations = new ArrayList<>(countViolations); for (int i = 0; i < countViolations; i++) { @@ -70,21 +71,33 @@ private void loadFromFile(final File cacheFile) { fileResultsCache.put(fileName, new AnalysisResult(checksum, violations)); } + + LOG.info("Analysis cache loaded"); } else { LOG.info("Analysis cache invalidated, PMD version changed."); } } catch (final EOFException e) { LOG.warning("Cache file " + cacheFile.getPath() + " is malformed, will not be used for current analysis"); } catch (final IOException e) { - LOG.severe("Could not load analysis cache to file. " + e.getMessage()); + LOG.severe("Could not load analysis cache from file. " + e.getMessage()); } + } else if (cacheFile.isDirectory()) { + LOG.severe("The configured cache location must be the path to a file, but is a directory."); } } @Override public void persist() { + + if (cacheFile.isDirectory()) { + LOG.severe("Cannot persist the cache, the given path points to a directory."); + return; + } + + boolean cacheFileShouldBeCreated = !cacheFile.exists(); + // Create directories missing along the way - if (!cacheFile.exists()) { + if (cacheFileShouldBeCreated) { final File parentFile = cacheFile.getAbsoluteFile().getParentFile(); if (parentFile != null && !parentFile.exists()) { parentFile.mkdirs(); @@ -93,26 +106,38 @@ public void persist() { try ( DataOutputStream outputStream = new DataOutputStream( - new BufferedOutputStream(new FileOutputStream(cacheFile))); + new BufferedOutputStream(new FileOutputStream(cacheFile))) ) { outputStream.writeUTF(pmdVersion); - + outputStream.writeLong(rulesetChecksum); - outputStream.writeLong(classpathChecksum); - + outputStream.writeLong(auxClassPathChecksum); + outputStream.writeLong(executionClassPathChecksum); + for (final Map.Entry<String, AnalysisResult> resultEntry : updatedResultsCache.entrySet()) { final List<RuleViolation> violations = resultEntry.getValue().getViolations(); outputStream.writeUTF(resultEntry.getKey()); outputStream.writeLong(resultEntry.getValue().getFileChecksum()); - + outputStream.writeInt(violations.size()); for (final RuleViolation rv : violations) { CachedRuleViolation.storeToStream(outputStream, rv); } } + if (cacheFileShouldBeCreated) { + LOG.info("Analysis cache created"); + } else { + LOG.info("Analysis cache updated"); + } } catch (final IOException e) { LOG.severe("Could not persist analysis cache to file. " + e.getMessage()); } } + + + @Override + protected boolean cacheExists() { + return cacheFile.exists() && cacheFile.isFile() && cacheFile.length() > 0; + } } diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/cache/NoopAnalysisCache.java b/pmd-core/src/main/java/net/sourceforge/pmd/cache/NoopAnalysisCache.java index b723ccc4c84..bbab1088ac8 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/cache/NoopAnalysisCache.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/cache/NoopAnalysisCache.java @@ -16,7 +16,7 @@ * A NOOP analysis cache. Easier / safer than null-checking. */ public class NoopAnalysisCache implements AnalysisCache { - + @Override public void ruleViolationAdded(final RuleViolation ruleViolation) { // noop diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/cli/PMDCommandLineInterface.java b/pmd-core/src/main/java/net/sourceforge/pmd/cli/PMDCommandLineInterface.java index ca00f441c43..03fe697a36e 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/cli/PMDCommandLineInterface.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/cli/PMDCommandLineInterface.java @@ -7,6 +7,7 @@ import java.util.Properties; import net.sourceforge.pmd.PMD; +import net.sourceforge.pmd.PMDVersion; import net.sourceforge.pmd.lang.LanguageRegistry; import net.sourceforge.pmd.properties.PropertyDescriptor; import net.sourceforge.pmd.renderers.Renderer; @@ -19,7 +20,7 @@ * @author Romain Pelisse <belaran@gmail.com> * */ -public class PMDCommandLineInterface { +public final class PMDCommandLineInterface { public static final String PROG_NAME = "pmd"; @@ -93,7 +94,7 @@ private static String getExamples() { private static String getWindowsLaunchCmd() { final String WINDOWS_PROMPT = "C:\\>"; - final String launchCmd = "pmd-bin-" + PMD.VERSION + "\\bin\\pmd.bat"; + final String launchCmd = "pmd-bin-" + PMDVersion.VERSION + "\\bin\\pmd.bat"; return WINDOWS_PROMPT + launchCmd; } @@ -101,25 +102,19 @@ private static String getWindowsExample() { final String launchCmd = getWindowsLaunchCmd(); final String WINDOWS_PATH_TO_CODE = "c:\\my\\source\\code "; - return "For example on windows: " + PMD.EOL + launchCmd + " -dir " + WINDOWS_PATH_TO_CODE - + "-format text -R java-unusedcode,java-imports -version 1.5 -language java -debug" + PMD.EOL - + launchCmd + " -dir " + WINDOWS_PATH_TO_CODE - + "-f xml -rulesets java-basic,java-design -encoding UTF-8" + PMD.EOL + launchCmd + " -d " - + WINDOWS_PATH_TO_CODE + "-rulesets java-typeresolution -auxclasspath commons-collections.jar;derby.jar" - + PMD.EOL + launchCmd + " -d " + WINDOWS_PATH_TO_CODE - + "-f html -R java-typeresolution -auxclasspath file:///C:/my/classpathfile" + PMD.EOL + PMD.EOL; + return "For example on windows: " + PMD.EOL + + launchCmd + " -dir " + WINDOWS_PATH_TO_CODE + "-format text -R rulesets/java/quickstart.xml -version 1.5 -language java -debug" + PMD.EOL + + launchCmd + " -dir " + WINDOWS_PATH_TO_CODE + "-f xml -rulesets rulesets/java/quickstart.xml,category/java/codestyle.xml -encoding UTF-8" + PMD.EOL + + launchCmd + " -d " + WINDOWS_PATH_TO_CODE + "-rulesets rulesets/java/quickstart.xml -auxclasspath lib\\commons-collections.jar;lib\\derby.jar" + PMD.EOL + + launchCmd + " -d " + WINDOWS_PATH_TO_CODE + "-f html -R rulesets/java/quickstart.xml -auxclasspath file:///C:/my/classpathfile" + PMD.EOL + PMD.EOL; } private static String getUnixExample() { - final String UNIX_PROMPT = "$ "; - final String launchCmd = "pmd-bin-" + PMD.VERSION + "/bin/run.sh pmd"; - return "For example on *nix: " + PMD.EOL + UNIX_PROMPT + launchCmd - + " -dir /home/workspace/src/main/java/code -f html -rulesets java-basic,java-design" + PMD.EOL - + UNIX_PROMPT + launchCmd - + " -d ./src/main/java/code -f xslt -R java-basic,java-design -property xsltFilename=my-own.xsl" - + PMD.EOL + UNIX_PROMPT + launchCmd - + " -d ./src/main/java/code -f html -R java-typeresolution -auxclasspath commons-collections.jar:derby.jar" - + PMD.EOL; + final String launchCmd = "$ pmd-bin-" + PMDVersion.VERSION + "/bin/run.sh pmd"; + return "For example on *nix: " + PMD.EOL + + launchCmd + " -dir /home/workspace/src/main/java/code -f html -rulesets rulesets/java/quickstart.xml,category/java/codestyle.xml" + PMD.EOL + + launchCmd + " -d ./src/main/java/code -R rulesets/java/quickstart.xml -f xslt -property xsltFilename=my-own.xsl" + PMD.EOL + + launchCmd + " -d ./src/main/java/code -f html -R rulesets/java/quickstart.xml -auxclasspath commons-collections.jar:derby.jar" + PMD.EOL; } private static String supportedVersions() { @@ -138,7 +133,7 @@ public static void main(String[] args) { } public static String jarName() { - return "pmd-" + PMD.VERSION + ".jar"; + return "pmd-" + PMDVersion.VERSION + ".jar"; } private static String getReports() { @@ -183,7 +178,7 @@ private static boolean isExitAfterRunSet() { if (noExit == null) { noExit = System.getProperty(NO_EXIT_AFTER_RUN); } - return (noExit == null ? true : false); + return noExit == null; } private static void setStatusCode(int statusCode) { diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/cli/PMDParameters.java b/pmd-core/src/main/java/net/sourceforge/pmd/cli/PMDParameters.java index acf68c391c1..218a40923de 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/cli/PMDParameters.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/cli/PMDParameters.java @@ -15,6 +15,7 @@ import net.sourceforge.pmd.lang.LanguageVersion; import com.beust.jcommander.IStringConverter; +import com.beust.jcommander.IValueValidator; import com.beust.jcommander.Parameter; import com.beust.jcommander.ParameterException; import com.beust.jcommander.validators.PositiveInteger; @@ -25,16 +26,18 @@ public class PMDParameters { required = true) private String rulesets; - @Parameter(names = { "-uri", "-u" }, description = "Database URI for sources.", required = false) + @Parameter(names = { "-uri", "-u" }, description = "Database URI for sources.") private String uri; - @Parameter(names = { "-dir", "-d" }, description = "Root directory for sources.", required = false) + @Parameter(names = { "-dir", "-d" }, description = "Root directory for sources.") private String sourceDir; - @Parameter(names = { "-filelist" }, description = "Path to a file containing a list of files to analyze.", - required = false) + @Parameter(names = "-filelist", description = "Path to a file containing a list of files to analyze.") private String fileListPath; + @Parameter(names = "-ignorelist", description = "Path to a file containing a list of files to ignore.") + private String ignoreListPath; + @Parameter(names = { "-format", "-f" }, description = "Report format type.") private String format = "text"; // Enhance to support other usage @@ -50,7 +53,7 @@ public class PMDParameters { @Parameter(names = { "-threads", "-t" }, description = "Sets the number of threads used by PMD.", validateWith = PositiveInteger.class) - private Integer threads = 1; + private int threads = 1; @Parameter(names = { "-benchmark", "-b" }, description = "Benchmark mode - output a benchmark report upon completion; default to System.err.") @@ -66,13 +69,14 @@ public class PMDParameters { private boolean showsuppressed = false; @Parameter(names = "-suppressmarker", - description = "Specifies the string that marks the a line which PMD should ignore; default is NOPMD.") + description = "Specifies the string that marks a line which PMD should ignore; default is NOPMD.") private String suppressmarker = "NOPMD"; @Parameter(names = { "-minimumpriority", "-min" }, - description = "Rule priority threshold; rules with lower priority than configured here won't be used. Default is '5' which is the lowest priority.", - converter = RulePriorityConverter.class) - private RulePriority minimumPriority = RulePriority.LOW; + description = "Rule priority threshold; rules with lower priority than configured here won't be used. " + + "Valid values are integers between 1 and 5 (inclusive), with 5 being the lowest priority.", + validateValueWith = RulePriorityValidator.class) + private int minimumPriority = RulePriority.LOW.getPriority(); @Parameter(names = { "-property", "-P" }, description = "{name}={value}: Define a property for the report format.", converter = PropertyConverter.class) @@ -98,10 +102,17 @@ public class PMDParameters { @Parameter(names = "-norulesetcompatibility", description = "Disable the ruleset compatibility filter. The filter is active by default and tries automatically 'fix' old ruleset files with old rule names") private boolean noRuleSetCompatibility = false; - - @Parameter(names = "-cache", description = "Specify the location of the cache file for incremental analysis.") + + @Parameter(names = "-cache", arity = 1, + description = "Specify the location of the cache file for incremental analysis. " + + "This should be the full path to the file, including the desired file name (not just the parent directory). " + + "If the file doesn't exist, it will be created on the first run. The file will be overwritten on each run " + + "with the most up-to-date rule violations.") private String cacheLocation = null; + @Parameter(names = "-no-cache", description = "Explicitly disable incremental analysis. The '-cache' option is ignored if this switch is present in the command line.") + private boolean noCache = false; + // this has to be a public static class, so that JCommander can use it! public static class PropertyConverter implements IStringConverter<Properties> { @@ -122,7 +133,20 @@ public Properties convert(String value) { } } + // this has to be a public static class, so that JCommander can use it! + public static class RulePriorityValidator implements IValueValidator<Integer> { + + @Override + public void validate(String name, Integer value) throws ParameterException { + if (value < 1 || value > 5) { + throw new ParameterException("Priority values can only be integer value, between 1 and 5," + value + " is not valid"); + } + } + } + + /** @deprecated Will be removed in 7.0.0 */ + @Deprecated public static class RulePriorityConverter implements IStringConverter<RulePriority> { public int validate(String value) throws ParameterException { @@ -140,45 +164,70 @@ public RulePriority convert(String value) { } } - public static PMDConfiguration transformParametersIntoConfiguration(PMDParameters params) { - if (null == params.getSourceDir() && null == params.getUri() && null == params.getFileListPath()) { + + /** + * Converts these parameters into a configuration. + * + * @return A new PMDConfiguration corresponding to these parameters + * + * @throws IllegalArgumentException if the parameters are inconsistent or incomplete + */ + public PMDConfiguration toConfiguration() { + if (null == this.getSourceDir() && null == this.getUri() && null == this.getFileListPath()) { throw new IllegalArgumentException( "Please provide a parameter for source root directory (-dir or -d), database URI (-uri or -u), or file list path (-filelist)."); } PMDConfiguration configuration = new PMDConfiguration(); - configuration.setInputPaths(params.getSourceDir()); - configuration.setInputFilePath(params.getFileListPath()); - configuration.setInputUri(params.getUri()); - configuration.setReportFormat(params.getFormat()); - configuration.setBenchmark(params.isBenchmark()); - configuration.setDebug(params.isDebug()); - configuration.setMinimumPriority(params.getMinimumPriority()); - configuration.setReportFile(params.getReportfile()); - configuration.setReportProperties(params.getProperties()); - configuration.setReportShortNames(params.isShortnames()); - configuration.setRuleSets(params.getRulesets()); - configuration.setRuleSetFactoryCompatibilityEnabled(!params.noRuleSetCompatibility); - configuration.setShowSuppressedViolations(params.isShowsuppressed()); - configuration.setSourceEncoding(params.getEncoding()); - configuration.setStressTest(params.isStress()); - configuration.setSuppressMarker(params.getSuppressmarker()); - configuration.setThreads(params.getThreads()); - configuration.setFailOnViolation(params.isFailOnViolation()); - configuration.setAnalysisCacheLocation(params.cacheLocation); + configuration.setInputPaths(this.getSourceDir()); + configuration.setInputFilePath(this.getFileListPath()); + configuration.setIgnoreFilePath(this.getIgnoreListPath()); + configuration.setInputUri(this.getUri()); + configuration.setReportFormat(this.getFormat()); + configuration.setBenchmark(this.isBenchmark()); + configuration.setDebug(this.isDebug()); + configuration.setMinimumPriority(this.getMinimumPriority()); + configuration.setReportFile(this.getReportfile()); + configuration.setReportProperties(this.getProperties()); + configuration.setReportShortNames(this.isShortnames()); + configuration.setRuleSets(this.getRulesets()); + configuration.setRuleSetFactoryCompatibilityEnabled(!this.noRuleSetCompatibility); + configuration.setShowSuppressedViolations(this.isShowsuppressed()); + configuration.setSourceEncoding(this.getEncoding()); + configuration.setStressTest(this.isStress()); + configuration.setSuppressMarker(this.getSuppressmarker()); + configuration.setThreads(this.getThreads()); + configuration.setFailOnViolation(this.isFailOnViolation()); + configuration.setAnalysisCacheLocation(this.cacheLocation); + configuration.setIgnoreIncrementalAnalysis(this.isIgnoreIncrementalAnalysis()); LanguageVersion languageVersion = LanguageRegistry - .findLanguageVersionByTerseName(params.getLanguage() + " " + params.getVersion()); + .findLanguageVersionByTerseName(this.getLanguage() + ' ' + this.getVersion()); if (languageVersion != null) { configuration.getLanguageVersionDiscoverer().setDefaultLanguageVersion(languageVersion); } try { - configuration.prependClasspath(params.getAuxclasspath()); + configuration.prependClasspath(this.getAuxclasspath()); } catch (IOException e) { throw new IllegalArgumentException("Invalid auxiliary classpath: " + e.getMessage(), e); } return configuration; } + + public boolean isIgnoreIncrementalAnalysis() { + return noCache; + } + + + /** + * {@link #toConfiguration()}. + * @deprecated To be removed in 7.0.0. Use the instance method {@link #toConfiguration()}. + */ + @Deprecated + public static PMDConfiguration transformParametersIntoConfiguration(PMDParameters params) { + return params.toConfiguration(); + } + public boolean isDebug() { return debug; } @@ -216,7 +265,7 @@ public String getSuppressmarker() { } public RulePriority getMinimumPriority() { - return minimumPriority; + return RulePriority.valueOf(minimumPriority); } public Properties getProperties() { @@ -258,6 +307,10 @@ public String getFileListPath() { return fileListPath; } + public String getIgnoreListPath() { + return ignoreListPath; + } + public String getFormat() { return format; } diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/cpd/AbstractTokenizer.java b/pmd-core/src/main/java/net/sourceforge/pmd/cpd/AbstractTokenizer.java index 296deb83479..7309fd2f4c7 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/cpd/AbstractTokenizer.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/cpd/AbstractTokenizer.java @@ -5,6 +5,7 @@ package net.sourceforge.pmd.cpd; import java.util.List; +import java.util.Locale; /** * @@ -51,7 +52,7 @@ public void tokenize(SourceCode tokens, Tokens tokenEntries) { loc = getTokenFromLine(token, loc); if (token.length() > 0 && !isIgnorableString(token.toString())) { if (downcaseString) { - token = new StringBuilder(token.toString().toLowerCase()); + token = new StringBuilder(token.toString().toLowerCase(Locale.ROOT)); } // need to re-think how to link this // if ( CPD.debugEnable ) { diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/cpd/AntlrTokenizer.java b/pmd-core/src/main/java/net/sourceforge/pmd/cpd/AntlrTokenizer.java new file mode 100644 index 00000000000..aad0d2379ab --- /dev/null +++ b/pmd-core/src/main/java/net/sourceforge/pmd/cpd/AntlrTokenizer.java @@ -0,0 +1,54 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.cpd; + +import org.antlr.v4.runtime.CharStream; +import org.antlr.v4.runtime.CharStreams; +import org.antlr.v4.runtime.Token; + +import net.sourceforge.pmd.cpd.token.AntlrToken; +import net.sourceforge.pmd.lang.antlr.AntlrTokenManager; +import net.sourceforge.pmd.lang.ast.TokenMgrError; + +/** + * Generic implementation of a {@link Tokenizer} useful to any Antlr grammar. + */ +public abstract class AntlrTokenizer implements Tokenizer { + + protected abstract AntlrTokenManager getLexerForSource(SourceCode sourceCode); + + @Override + public void tokenize(final SourceCode sourceCode, final Tokens tokenEntries) { + + AntlrTokenManager tokenManager = getLexerForSource(sourceCode); + + try { + AntlrToken token = (AntlrToken) tokenManager.getNextToken(); + + while (token.getType() != Token.EOF) { + if (!token.isHidden()) { + final TokenEntry tokenEntry = + new TokenEntry(token.getImage(), tokenManager.getFileName(), token.getBeginLine()); + + tokenEntries.add(tokenEntry); + } + token = (AntlrToken) tokenManager.getNextToken(); + } + } catch (final AntlrTokenManager.ANTLRSyntaxError err) { + // Wrap exceptions of the ANTLR tokenizer in a TokenMgrError, so they are correctly handled + // when CPD is executed with the '--skipLexicalErrors' command line option + throw new TokenMgrError("Lexical error in file " + tokenManager.getFileName() + " at line " + + err.getLine() + ", column " + err.getColumn() + ". Encountered: " + err.getMessage(), + TokenMgrError.LEXICAL_ERROR); + } finally { + tokenEntries.add(TokenEntry.getEOF()); + } + } + + /* default */ static CharStream getCharStreamFromSourceCode(final SourceCode sourceCode) { + StringBuilder buffer = sourceCode.getCodeBuffer(); + return CharStreams.fromString(buffer.toString()); + } +} diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/cpd/AnyTokenizer.java b/pmd-core/src/main/java/net/sourceforge/pmd/cpd/AnyTokenizer.java index 8983f90bfee..2e9745bb8e5 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/cpd/AnyTokenizer.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/cpd/AnyTokenizer.java @@ -6,11 +6,9 @@ import java.io.BufferedReader; import java.io.CharArrayReader; -import java.util.NoSuchElementException; +import java.io.IOException; import java.util.StringTokenizer; -import org.apache.commons.io.IOUtils; - /** * This class does a best-guess try-anything tokenization. * @@ -22,31 +20,24 @@ public class AnyTokenizer implements Tokenizer { @Override public void tokenize(SourceCode sourceCode, Tokens tokenEntries) { StringBuilder sb = sourceCode.getCodeBuffer(); - BufferedReader reader = new BufferedReader(new CharArrayReader(sb.toString().toCharArray())); - try { + try (BufferedReader reader = new BufferedReader(new CharArrayReader(sb.toString().toCharArray()))) { int lineNumber = 1; String line = reader.readLine(); while (line != null) { StringTokenizer tokenizer = new StringTokenizer(line, TOKENS, true); - try { + while (tokenizer.hasMoreTokens()) { String token = tokenizer.nextToken(); - while (token != null) { - if (!" ".equals(token) && !"\t".equals(token)) { - tokenEntries.add(new TokenEntry(token, sourceCode.getFileName(), lineNumber)); - } - token = tokenizer.nextToken(); + if (!" ".equals(token) && !"\t".equals(token)) { + tokenEntries.add(new TokenEntry(token, sourceCode.getFileName(), lineNumber)); } - } catch (NoSuchElementException ex) { - // done with tokens } // advance iteration variables line = reader.readLine(); lineNumber++; } - } catch (Exception ex) { - ex.printStackTrace(); + } catch (IOException ignored) { + ignored.printStackTrace(); } finally { - IOUtils.closeQuietly(reader); tokenEntries.add(TokenEntry.getEOF()); } } diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/cpd/CPDCommandLineInterface.java b/pmd-core/src/main/java/net/sourceforge/pmd/cpd/CPDCommandLineInterface.java index ffb4bc1d190..5a2f9d80fa8 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/cpd/CPDCommandLineInterface.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/cpd/CPDCommandLineInterface.java @@ -4,9 +4,11 @@ package net.sourceforge.pmd.cpd; +import java.io.BufferedWriter; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; +import java.io.OutputStreamWriter; import java.net.URISyntaxException; import java.util.ArrayList; import java.util.Arrays; @@ -14,13 +16,14 @@ import java.util.logging.Logger; import net.sourceforge.pmd.PMD; +import net.sourceforge.pmd.PMDVersion; import net.sourceforge.pmd.util.FileUtil; import net.sourceforge.pmd.util.database.DBURI; import com.beust.jcommander.JCommander; import com.beust.jcommander.ParameterException; -public class CPDCommandLineInterface { +public final class CPDCommandLineInterface { private static final Logger LOGGER = Logger.getLogger(CPDCommandLineInterface.class.getName()); private static final int DUPLICATE_CODE_FOUND = 4; @@ -46,7 +49,7 @@ private static boolean isExitAfterRunSet() { if (noExit == null) { noExit = System.getProperty(NO_EXIT_AFTER_RUN); } - return (noExit == null ? true : false); + return noExit == null; } private static void setStatusCode(int statusCode) { @@ -83,7 +86,12 @@ public static void main(String[] args) { addSourceFilesToCPD(cpd, arguments); cpd.go(); - System.out.println(arguments.getRenderer().render(cpd.getMatches())); + if (arguments.getCPDRenderer() == null) { + // legacy writer + System.out.println(arguments.getRenderer().render(cpd.getMatches())); + } else { + arguments.getCPDRenderer().render(cpd.getMatches(), new BufferedWriter(new OutputStreamWriter(System.out))); + } if (cpd.getMatches().hasNext()) { if (arguments.isFailOnViolation()) { setStatusCodeOrExit(DUPLICATE_CODE_FOUND); @@ -93,7 +101,7 @@ public static void main(String[] args) { } else { setStatusCodeOrExit(0); } - } catch (RuntimeException e) { + } catch (IOException | RuntimeException e) { e.printStackTrace(); setStatusCodeOrExit(ERROR_STATUS); } @@ -177,12 +185,12 @@ private static void addSourceURIToCPD(String uri, CPD cpd) { public static String buildUsageText() { String helpText = " For example on Windows:" + PMD.EOL; - helpText += " C:\\>" + "pmd-bin-" + PMD.VERSION + "\\bin\\cpd.bat" + helpText += " C:\\>" + "pmd-bin-" + PMDVersion.VERSION + "\\bin\\cpd.bat" + " --minimum-tokens 100 --files c:\\jdk18\\src\\java" + PMD.EOL; helpText += PMD.EOL; helpText += " For example on *nix:" + PMD.EOL; - helpText += " $ " + "pmd-bin-" + PMD.VERSION + "/bin/run.sh cpd" + helpText += " $ " + "pmd-bin-" + PMDVersion.VERSION + "/bin/run.sh cpd" + " --minimum-tokens 100 --files /path/to/java/code" + PMD.EOL; helpText += PMD.EOL; diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/cpd/CPDConfiguration.java b/pmd-core/src/main/java/net/sourceforge/pmd/cpd/CPDConfiguration.java index 1f9b4179123..ad5d8cee585 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/cpd/CPDConfiguration.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/cpd/CPDConfiguration.java @@ -21,6 +21,7 @@ import java.util.Set; import net.sourceforge.pmd.AbstractConfiguration; +import net.sourceforge.pmd.cpd.renderer.CPDRenderer; import net.sourceforge.pmd.util.FileFinder; import net.sourceforge.pmd.util.FileUtil; @@ -36,10 +37,17 @@ public class CPDConfiguration extends AbstractConfiguration { public static final String DEFAULT_LANGUAGE = "java"; - public static final String DEFAULT_RENDERER = "text"; - private static final Map<String, Class<? extends Renderer>> RENDERERS = new HashMap<>(); + private static final Map<String, Class<? extends CPDRenderer>> RENDERERS = new HashMap<>(); + + static { + RENDERERS.put(DEFAULT_RENDERER, SimpleRenderer.class); + RENDERERS.put("xml", XMLRenderer.class); + RENDERERS.put("csv", CSVRenderer.class); + RENDERERS.put("csv_with_linecount_per_file", CSVWithLinecountPerFileRenderer.class); + RENDERERS.put("vs", VSRenderer.class); + } @Parameter(names = "--language", description = "Sources code language. Default value is " + DEFAULT_LANGUAGE, required = false, converter = LanguageConverter.class) @@ -61,7 +69,10 @@ public class CPDConfiguration extends AbstractConfiguration { * The actual renderer. constructed by using the {@link #rendererName}. This * property is only valid after {@link #postContruct()} has been called! */ + @Deprecated private Renderer renderer; + + private CPDRenderer cpdRenderer; private String encoding; @@ -133,9 +144,6 @@ public Language convert(String languageString) { } } - public CPDConfiguration() { - } - @Parameter(names = "--encoding", description = "Character encoding to use when processing files", required = false) public void setEncoding(String encoding) { this.encoding = encoding; @@ -143,7 +151,7 @@ public void setEncoding(String encoding) { } public SourceCode sourceCodeFor(File file) { - return new SourceCode(new SourceCode.FileCodeLoader(file, getSourceEncoding())); + return new SourceCode(new SourceCode.FileCodeLoader(file, getSourceEncoding().name())); } public SourceCode sourceCodeFor(Reader reader, String sourceCodeName) { @@ -151,31 +159,33 @@ public SourceCode sourceCodeFor(Reader reader, String sourceCodeName) { } public void postContruct() { - if (this.getLanguage() == null) { - this.setLanguage(CPDConfiguration.getLanguageFromString(DEFAULT_LANGUAGE)); + if (getLanguage() == null) { + setLanguage(CPDConfiguration.getLanguageFromString(DEFAULT_LANGUAGE)); } - if (this.getRendererName() == null) { - this.setRendererName(DEFAULT_RENDERER); + if (getRendererName() == null) { + setRendererName(DEFAULT_RENDERER); } - if (this.getRenderer() == null) { - this.setRenderer(getRendererFromString(getRendererName(), this.getEncoding())); + if (getRenderer() == null && getCPDRenderer() == null) { + try { + setCPDRenderer(getCPDRendererFromString(getRendererName(), getEncoding())); + } catch (ClassCastException e) { + // The renderer class configured is not using the new CPDRenderer interface... + setRenderer(getRendererFromString(getRendererName(), getEncoding())); + } } } - static { - RENDERERS.put(DEFAULT_RENDERER, SimpleRenderer.class); - RENDERERS.put("xml", XMLRenderer.class); - RENDERERS.put("csv", CSVRenderer.class); - RENDERERS.put("csv_with_linecount_per_file", CSVWithLinecountPerFileRenderer.class); - RENDERERS.put("vs", VSRenderer.class); - } - + /** + * @deprecated Use {@link #getCPDRendererFromString(String, String)} instead + */ + @Deprecated public static Renderer getRendererFromString(String name, String encoding) { String clazzname = name; if (clazzname == null || "".equals(clazzname)) { clazzname = DEFAULT_RENDERER; } - Class<? extends Renderer> clazz = RENDERERS.get(clazzname.toLowerCase(Locale.ROOT)); + @SuppressWarnings("unchecked") // Safe, all standard implementations implement both interfaces + Class<? extends Renderer> clazz = (Class<? extends Renderer>) RENDERERS.get(clazzname.toLowerCase(Locale.ROOT)); if (clazz == null) { try { clazz = Class.forName(clazzname).asSubclass(Renderer.class); @@ -193,8 +203,32 @@ public static Renderer getRendererFromString(String name, String encoding) { return new SimpleRenderer(); } } + + public static CPDRenderer getCPDRendererFromString(String name, String encoding) { + String clazzname = name; + if (clazzname == null || "".equals(clazzname)) { + clazzname = DEFAULT_RENDERER; + } + Class<? extends CPDRenderer> clazz = RENDERERS.get(clazzname.toLowerCase(Locale.ROOT)); + if (clazz == null) { + try { + clazz = Class.forName(clazzname).asSubclass(CPDRenderer.class); + } catch (ClassNotFoundException e) { + System.err.println("Can't find class '" + name + "', defaulting to SimpleRenderer."); + clazz = SimpleRenderer.class; + } + } + try { + CPDRenderer renderer = clazz.getDeclaredConstructor().newInstance(); + setRendererEncoding(renderer, encoding); + return renderer; + } catch (Exception e) { + System.err.println("Couldn't instantiate renderer, defaulting to SimpleRenderer: " + e); + return new SimpleRenderer(); + } + } - private static void setRendererEncoding(Renderer renderer, String encoding) + private static void setRendererEncoding(Object renderer, String encoding) throws IllegalAccessException, InvocationTargetException { try { PropertyDescriptor encodingProperty = new PropertyDescriptor("encoding", renderer.getClass()); @@ -202,7 +236,7 @@ private static void setRendererEncoding(Renderer renderer, String encoding) if (method != null) { method.invoke(renderer, encoding); } - } catch (IntrospectionException e) { + } catch (IntrospectionException ignored) { // ignored - maybe this renderer doesn't have a encoding property } } @@ -276,9 +310,17 @@ public void setRendererName(String rendererName) { this.rendererName = rendererName; } + /** + * @deprecated Use {@link #getCPDRenderer()} instead + */ + @Deprecated public Renderer getRenderer() { return renderer; } + + public CPDRenderer getCPDRenderer() { + return cpdRenderer; + } public Tokenizer tokenizer() { if (language == null) { @@ -309,7 +351,7 @@ public FilenameFilter filenameFilter() { } } - FilenameFilter filter = new FilenameFilter() { + return new FilenameFilter() { @Override public boolean accept(File dir, String name) { File f = new File(dir, name); @@ -320,11 +362,21 @@ public boolean accept(File dir, String name) { return languageFilter.accept(dir, name); } }; - return filter; } + /** + * @deprecated Use {@link #setCPDRenderer(CPDRenderer)} instead + * @param renderer + */ + @Deprecated public void setRenderer(Renderer renderer) { this.renderer = renderer; + this.cpdRenderer = null; + } + + public void setCPDRenderer(CPDRenderer renderer) { + this.cpdRenderer = renderer; + this.renderer = null; } public boolean isIgnoreLiterals() { diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/cpd/CPDNullListener.java b/pmd-core/src/main/java/net/sourceforge/pmd/cpd/CPDNullListener.java index dd0a132bf10..64b6060166b 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/cpd/CPDNullListener.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/cpd/CPDNullListener.java @@ -9,9 +9,11 @@ public class CPDNullListener implements CPDListener { @Override public void addedFile(int fileCount, File file) { + // does nothing - override it if necessary } @Override public void phaseUpdate(int phase) { + // does nothing - override it if necessary } } diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/cpd/CPDTask.java b/pmd-core/src/main/java/net/sourceforge/pmd/cpd/CPDTask.java index 46322d96abc..abf9f6e7076 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/cpd/CPDTask.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/cpd/CPDTask.java @@ -4,8 +4,13 @@ package net.sourceforge.pmd.cpd; +import java.io.BufferedWriter; import java.io.File; +import java.io.FileOutputStream; import java.io.IOException; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.Writer; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -18,6 +23,8 @@ import org.apache.tools.ant.types.EnumeratedAttribute; import org.apache.tools.ant.types.FileSet; +import net.sourceforge.pmd.cpd.renderer.CPDRenderer; + /** * CPDTask * @@ -123,16 +130,28 @@ private void report(CPD cpd) throws ReportException { if (!cpd.getMatches().hasNext()) { log("No duplicates over " + minimumTokenCount + " tokens found", Project.MSG_INFO); } - Renderer renderer = createRenderer(); - FileReporter reporter; - if (outputFile == null) { - reporter = new FileReporter(encoding); - } else if (outputFile.isAbsolute()) { - reporter = new FileReporter(outputFile, encoding); - } else { - reporter = new FileReporter(new File(getProject().getBaseDir(), outputFile.toString()), encoding); + CPDRenderer renderer = createRenderer(); + + try { + final OutputStream os; + if (outputFile == null) { + os = System.out; + } else if (outputFile.isAbsolute()) { + os = new FileOutputStream(outputFile); + } else { + os = new FileOutputStream(new File(getProject().getBaseDir(), outputFile.toString())); + } + + if (encoding == null) { + encoding = System.getProperty("file.encoding"); + } + + try (Writer writer = new BufferedWriter(new OutputStreamWriter(os, encoding))) { + renderer.render(cpd.getMatches(), writer); + } + } catch (IOException ioe) { + throw new ReportException(ioe); } - reporter.report(renderer.render(cpd.getMatches())); } private void tokenizeFiles(CPD cpd) throws IOException { @@ -155,7 +174,7 @@ private long analyzeCode(CPD cpd) { return stop - start; } - private Renderer createRenderer() { + private CPDRenderer createRenderer() { if (format.equals(TEXT_FORMAT)) { return new SimpleRenderer(); } else if (format.equals(CSV_FORMAT)) { diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/cpd/CSVRenderer.java b/pmd-core/src/main/java/net/sourceforge/pmd/cpd/CSVRenderer.java index 197b39c6cd8..78260b43599 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/cpd/CSVRenderer.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/cpd/CSVRenderer.java @@ -4,16 +4,20 @@ package net.sourceforge.pmd.cpd; +import java.io.IOException; +import java.io.StringWriter; +import java.io.Writer; import java.util.Iterator; import org.apache.commons.lang3.StringEscapeUtils; import net.sourceforge.pmd.PMD; +import net.sourceforge.pmd.cpd.renderer.CPDRenderer; -public class CSVRenderer implements Renderer { +public class CSVRenderer implements Renderer, CPDRenderer { - private char separator; - private boolean lineCountPerFile; + private final char separator; + private final boolean lineCountPerFile; public static final char DEFAULT_SEPARATOR = ','; public static final boolean DEFAULT_LINECOUNTPERFILE = false; @@ -37,34 +41,44 @@ public CSVRenderer(char separatorChar, boolean lineCountPerFile) { @Override public String render(Iterator<Match> matches) { - StringBuilder csv = new StringBuilder(1000); + StringWriter writer = new StringWriter(1000); + try { + render(matches, writer); + } catch (IOException ignored) { + // Not really possible with a StringWriter + } + return writer.toString(); + } + @Override + public void render(Iterator<Match> matches, Writer writer) throws IOException { if (!lineCountPerFile) { - csv.append("lines").append(separator); + writer.append("lines").append(separator); } - csv.append("tokens").append(separator).append("occurrences").append(PMD.EOL); + writer.append("tokens").append(separator).append("occurrences").append(PMD.EOL); while (matches.hasNext()) { Match match = matches.next(); if (!lineCountPerFile) { - csv.append(match.getLineCount()).append(separator); + writer.append(String.valueOf(match.getLineCount())).append(separator); } - csv.append(match.getTokenCount()).append(separator).append(match.getMarkCount()).append(separator); + writer.append(String.valueOf(match.getTokenCount())).append(separator) + .append(String.valueOf(match.getMarkCount())).append(separator); for (Iterator<Mark> marks = match.iterator(); marks.hasNext();) { Mark mark = marks.next(); - csv.append(mark.getBeginLine()).append(separator); + writer.append(String.valueOf(mark.getBeginLine())).append(separator); if (lineCountPerFile) { - csv.append(mark.getLineCount()).append(separator); + writer.append(String.valueOf(mark.getLineCount())).append(separator); } - csv.append(StringEscapeUtils.escapeCsv(mark.getFilename())); + writer.append(StringEscapeUtils.escapeCsv(mark.getFilename())); if (marks.hasNext()) { - csv.append(separator); + writer.append(separator); } } - csv.append(PMD.EOL); + writer.append(PMD.EOL); } - return csv.toString(); + writer.flush(); } } diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/cpd/FileReporter.java b/pmd-core/src/main/java/net/sourceforge/pmd/cpd/FileReporter.java index 75de5b14d5e..033171a34ce 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/cpd/FileReporter.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/cpd/FileReporter.java @@ -6,17 +6,20 @@ import java.io.BufferedWriter; import java.io.File; +import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.io.Writer; -import org.apache.commons.io.IOUtils; +import net.sourceforge.pmd.cpd.renderer.CPDRenderer; /** * @author Philippe T'Seyen + * @deprecated {@link CPDRenderer} directly renders to a Writer */ +@Deprecated // to be removed with 7.0.0 public class FileReporter { private File reportFile; private String encoding; @@ -35,22 +38,14 @@ public FileReporter(File reportFile, String encoding) { } public void report(String content) throws ReportException { - try { - Writer writer = null; - try { - OutputStream outputStream; - if (reportFile == null) { - outputStream = System.out; - } else { - outputStream = new FileOutputStream(reportFile); - } - writer = new BufferedWriter(new OutputStreamWriter(outputStream, encoding)); - writer.write(content); - } finally { - IOUtils.closeQuietly(writer); - } + try (Writer writer = new BufferedWriter(new OutputStreamWriter(getOutputStream(), encoding))) { + writer.write(content); } catch (IOException ioe) { throw new ReportException(ioe); } } + + private OutputStream getOutputStream() throws FileNotFoundException { + return reportFile == null ? System.out : new FileOutputStream(reportFile); + } } diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/cpd/GUI.java b/pmd-core/src/main/java/net/sourceforge/pmd/cpd/GUI.java index 7cfbaec817b..c6e7c4727b8 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/cpd/GUI.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/cpd/GUI.java @@ -21,6 +21,7 @@ import java.io.FileOutputStream; import java.io.IOException; import java.io.PrintWriter; +import java.io.Writer; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; @@ -31,7 +32,6 @@ import java.util.Map; import java.util.Properties; import java.util.Set; - import javax.swing.AbstractButton; import javax.swing.BorderFactory; import javax.swing.JButton; @@ -58,16 +58,15 @@ import javax.swing.Timer; import javax.swing.event.ListSelectionEvent; import javax.swing.event.ListSelectionListener; -import javax.swing.event.TableModelListener; +import javax.swing.table.AbstractTableModel; import javax.swing.table.DefaultTableCellRenderer; import javax.swing.table.JTableHeader; import javax.swing.table.TableColumn; import javax.swing.table.TableColumnModel; import javax.swing.table.TableModel; -import org.apache.commons.io.IOUtils; - -import net.sourceforge.pmd.PMD; +import net.sourceforge.pmd.PMDVersion; +import net.sourceforge.pmd.cpd.renderer.CPDRenderer; public class GUI implements CPDListener { @@ -75,25 +74,25 @@ public class GUI implements CPDListener { // String render(Iterator<Match> items); // } - private static final Object[][] RENDERER_SETS = new Object[][] { { "Text", new Renderer() { + private static final Object[][] RENDERER_SETS = new Object[][] { { "Text", new CPDRenderer() { @Override - public String render(Iterator<Match> items) { - return new SimpleRenderer().render(items); + public void render(Iterator<Match> items, Writer writer) throws IOException { + new SimpleRenderer().render(items, writer); } - }, }, { "XML", new Renderer() { + }, }, { "XML", new CPDRenderer() { @Override - public String render(Iterator<Match> items) { - return new XMLRenderer().render(items); + public void render(Iterator<Match> items, Writer writer) throws IOException { + new XMLRenderer().render(items, writer); } - }, }, { "CSV (comma)", new Renderer() { + }, }, { "CSV (comma)", new CPDRenderer() { @Override - public String render(Iterator<Match> items) { - return new CSVRenderer(',').render(items); + public void render(Iterator<Match> items, Writer writer) throws IOException { + new CSVRenderer(',').render(items, writer); } - }, }, { "CSV (tab)", new Renderer() { + }, }, { "CSV (tab)", new CPDRenderer() { @Override - public String render(Iterator<Match> items) { - return new CSVRenderer('\t').render(items); + public void render(Iterator<Match> items, Writer writer) throws IOException { + new CSVRenderer('\t').render(items, writer); } }, }, }; @@ -139,7 +138,7 @@ public Language languageFor(Properties p) { @Override public String[] extensions() { List<String> exts = lang.getExtensions(); - return exts.toArray(new String[exts.size()]); + return exts.toArray(new String[0]); } @Override @@ -254,9 +253,9 @@ public void run() { private class SaveListener implements ActionListener { - final Renderer renderer; + final CPDRenderer renderer; - SaveListener(Renderer theRenderer) { + SaveListener(CPDRenderer theRenderer) { renderer = theRenderer; } @@ -270,16 +269,12 @@ public void actionPerformed(ActionEvent evt) { } if (!f.canWrite()) { - PrintWriter pw = null; - try { - pw = new PrintWriter(new FileOutputStream(f)); - pw.write(renderer.render(matches.iterator())); + try (PrintWriter pw = new PrintWriter(new FileOutputStream(f))) { + renderer.render(matches.iterator(), pw); pw.flush(); JOptionPane.showMessageDialog(frame, "Saved " + matches.size() + " matches"); } catch (IOException e) { error("Couldn't save file" + f.getAbsolutePath(), e); - } finally { - IOUtils.closeQuietly(pw); } } else { error("Could not write to file " + f.getAbsolutePath(), null); @@ -356,13 +351,13 @@ private void addSaveOptionsTo(JMenu menu) { for (int i = 0; i < RENDERER_SETS.length; i++) { saveItem = new JMenuItem("Save as " + RENDERER_SETS[i][0]); - saveItem.addActionListener(new SaveListener((Renderer) RENDERER_SETS[i][1])); + saveItem.addActionListener(new SaveListener((CPDRenderer) RENDERER_SETS[i][1])); menu.add(saveItem); } } public GUI() { - frame = new JFrame("PMD Duplicate Code Detector (v " + PMD.VERSION + ')'); + frame = new JFrame("PMD Duplicate Code Detector (v " + PMDVersion.VERSION + ')'); timeField.setEditable(false); @@ -636,7 +631,7 @@ private String setLabelFor(Match match) { int separatorPos = sourceId.lastIndexOf(File.separatorChar); label = "..." + sourceId.substring(separatorPos); } else { - label = "(" + sourceIDs.size() + " separate files)"; + label = String.format("(%d separate files)", sourceIDs.size()); } match.setLabel(label); @@ -725,7 +720,7 @@ private Timer createTimer() { final long start = System.currentTimeMillis(); - Timer t = new Timer(1000, new ActionListener() { + return new Timer(1000, new ActionListener() { @Override public void actionPerformed(ActionEvent e) { long now = System.currentTimeMillis(); @@ -736,7 +731,6 @@ public void actionPerformed(ActionEvent e) { timeField.setText(formatTime(minutes, seconds)); } }); - return t; } private static String formatTime(long minutes, long seconds) { @@ -753,21 +747,21 @@ private static String formatTime(long minutes, long seconds) { return sb.toString(); } - private interface SortingTableModel<E> extends TableModel { - int sortColumn(); + private abstract class SortingTableModel<E> extends AbstractTableModel { + abstract int sortColumn(); - void sortColumn(int column); + abstract void sortColumn(int column); - boolean sortDescending(); + abstract boolean sortDescending(); - void sortDescending(boolean flag); + abstract void sortDescending(boolean flag); - void sort(Comparator<E> comparator); + abstract void sort(Comparator<E> comparator); } private TableModel tableModelFrom(final List<Match> items) { - TableModel model = new SortingTableModel<Match>() { + return new SortingTableModel<Match>() { private int sortColumn; private boolean sortDescending; @@ -809,23 +803,11 @@ public Class<?> getColumnClass(int columnIndex) { return Object.class; } - @Override - public void setValueAt(Object aValue, int rowIndex, int columnIndex) { - } - @Override public String getColumnName(int i) { return matchColumns[i].label(); } - @Override - public void addTableModelListener(TableModelListener l) { - } - - @Override - public void removeTableModelListener(TableModelListener l) { - } - @Override public int sortColumn() { return sortColumn; @@ -854,8 +836,6 @@ public void sort(Comparator<Match> comparator) { } } }; - - return model; } private void sortOnColumn(int columnIndex) { diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/cpd/GridBagHelper.java b/pmd-core/src/main/java/net/sourceforge/pmd/cpd/GridBagHelper.java index f1aa601fd2e..393cf43eb73 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/cpd/GridBagHelper.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/cpd/GridBagHelper.java @@ -10,7 +10,6 @@ import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.Insets; - import javax.swing.JLabel; import javax.swing.SwingConstants; diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/cpd/LanguageFactory.java b/pmd-core/src/main/java/net/sourceforge/pmd/cpd/LanguageFactory.java index 66559a53268..336a49a00c5 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/cpd/LanguageFactory.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/cpd/LanguageFactory.java @@ -72,7 +72,7 @@ public static Language createLanguage(String language, Properties properties) { if (BY_EXTENSION.equals(language)) { implementation = instance.getLanguageByExtension(properties.getProperty(EXTENSION)); } else { - implementation = instance.languages.get(instance.languageAliases(language).toLowerCase()); + implementation = instance.languages.get(instance.languageAliases(language).toLowerCase(Locale.ROOT)); } if (implementation == null) { // No proper implementation diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/cpd/Renderer.java b/pmd-core/src/main/java/net/sourceforge/pmd/cpd/Renderer.java index 200dc040351..1c5f85c31f2 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/cpd/Renderer.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/cpd/Renderer.java @@ -6,9 +6,13 @@ import java.util.Iterator; +import net.sourceforge.pmd.cpd.renderer.CPDRenderer; + /** * @author Philippe T'Seyen + * @deprecated Use {@link CPDRenderer} instead */ +@Deprecated // to be removed with 7.0.0 public interface Renderer { String render(Iterator<Match> matches); } diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/cpd/SimpleRenderer.java b/pmd-core/src/main/java/net/sourceforge/pmd/cpd/SimpleRenderer.java index dc2d4ef38b3..9ccef7f4f18 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/cpd/SimpleRenderer.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/cpd/SimpleRenderer.java @@ -4,12 +4,16 @@ package net.sourceforge.pmd.cpd; +import java.io.IOException; +import java.io.StringWriter; +import java.io.Writer; import java.util.Iterator; import net.sourceforge.pmd.PMD; +import net.sourceforge.pmd.cpd.renderer.CPDRenderer; import net.sourceforge.pmd.util.StringUtil; -public class SimpleRenderer implements Renderer { +public class SimpleRenderer implements Renderer, CPDRenderer { private String separator; private boolean trimLeadingWhitespace; @@ -29,53 +33,59 @@ public SimpleRenderer(String theSeparator) { separator = theSeparator; } - private void renderOn(StringBuilder rpt, Match match) { + private void renderOn(Writer writer, Match match) throws IOException { - rpt.append("Found a ").append(match.getLineCount()).append(" line (").append(match.getTokenCount()) + writer.append("Found a ").append(String.valueOf(match.getLineCount())).append(" line (").append(String.valueOf(match.getTokenCount())) .append(" tokens) duplication in the following files: ").append(PMD.EOL); for (Iterator<Mark> occurrences = match.iterator(); occurrences.hasNext();) { Mark mark = occurrences.next(); - rpt.append("Starting at line ").append(mark.getBeginLine()).append(" of ").append(mark.getFilename()) + writer.append("Starting at line ").append(String.valueOf(mark.getBeginLine())).append(" of ").append(mark.getFilename()) .append(PMD.EOL); } - rpt.append(PMD.EOL); // add a line to separate the source from the desc - // above + writer.append(PMD.EOL); // add a line to separate the source from the desc above String source = match.getSourceCodeSlice(); if (trimLeadingWhitespace) { - String[] lines = source.split("[" + PMD.EOL + "]"); + String[] lines = source.split('[' + PMD.EOL + ']'); int trimDepth = StringUtil.maxCommonLeadingWhitespaceForAll(lines); if (trimDepth > 0) { lines = StringUtil.trimStartOn(lines, trimDepth); } for (int i = 0; i < lines.length; i++) { - rpt.append(lines[i]).append(PMD.EOL); + writer.append(lines[i]).append(PMD.EOL); } return; } - rpt.append(source).append(PMD.EOL); + writer.append(source).append(PMD.EOL); } @Override public String render(Iterator<Match> matches) { + StringWriter writer = new StringWriter(300); + try { + render(matches, writer); + } catch (IOException ignored) { + // Not really possible with a StringWriter + } + return writer.toString(); + } - StringBuilder rpt = new StringBuilder(300); - + @Override + public void render(Iterator<Match> matches, Writer writer) throws IOException { if (matches.hasNext()) { - renderOn(rpt, matches.next()); + renderOn(writer, matches.next()); } Match match; while (matches.hasNext()) { match = matches.next(); - rpt.append(separator).append(PMD.EOL); - renderOn(rpt, match); - + writer.append(separator).append(PMD.EOL); + renderOn(writer, match); } - return rpt.toString(); + writer.flush(); } } diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/cpd/VSRenderer.java b/pmd-core/src/main/java/net/sourceforge/pmd/cpd/VSRenderer.java index 8e80a783b78..f34622e2f0f 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/cpd/VSRenderer.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/cpd/VSRenderer.java @@ -4,28 +4,40 @@ package net.sourceforge.pmd.cpd; +import java.io.IOException; +import java.io.StringWriter; +import java.io.Writer; import java.util.Iterator; import net.sourceforge.pmd.PMD; +import net.sourceforge.pmd.cpd.renderer.CPDRenderer; -public class VSRenderer implements Renderer { +public class VSRenderer implements Renderer, CPDRenderer { @Override public String render(Iterator<Match> matches) { + StringWriter writer = new StringWriter(300); + try { + render(matches, writer); + } catch (IOException ignored) { + // Not really possible with a StringWriter + } + return writer.toString(); + } - StringBuilder buffer = new StringBuilder(300); - + @Override + public void render(Iterator<Match> matches, Writer writer) throws IOException { for (Match match; matches.hasNext();) { match = matches.next(); Mark mark; for (Iterator<Mark> iterator = match.iterator(); iterator.hasNext();) { mark = iterator.next(); - buffer.append(mark.getFilename()); - buffer.append('(').append(mark.getBeginLine()).append("):"); - buffer.append(" Between lines " + mark.getBeginLine() + " and " + writer.append(mark.getFilename()) + .append('(').append(String.valueOf(mark.getBeginLine())).append("):") + .append(" Between lines " + mark.getBeginLine() + " and " + (mark.getBeginLine() + match.getLineCount()) + PMD.EOL); } } - return buffer.toString(); + writer.flush(); } } diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/cpd/XMLRenderer.java b/pmd-core/src/main/java/net/sourceforge/pmd/cpd/XMLRenderer.java index 14c28f458c0..bea6819f9c8 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/cpd/XMLRenderer.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/cpd/XMLRenderer.java @@ -4,9 +4,10 @@ package net.sourceforge.pmd.cpd; +import java.io.IOException; import java.io.StringWriter; +import java.io.Writer; import java.util.Iterator; - import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; @@ -20,12 +21,14 @@ import org.w3c.dom.Document; import org.w3c.dom.Element; +import net.sourceforge.pmd.cpd.renderer.CPDRenderer; + /** * @author Philippe T'Seyen - original implementation * @author Romain Pelisse - javax.xml implementation * */ -public final class XMLRenderer implements Renderer { +public final class XMLRenderer implements Renderer, CPDRenderer { private String encoding; @@ -69,7 +72,7 @@ private Document createDocument() { } } - private String xmlDocToString(Document doc) { + private void dumpDocToWriter(Document doc, Writer writer) { try { TransformerFactory tf = TransformerFactory.newInstance(); Transformer transformer = tf.newTransformer(); @@ -77,9 +80,7 @@ private String xmlDocToString(Document doc) { transformer.setOutputProperty(OutputKeys.ENCODING, encoding); transformer.setOutputProperty(OutputKeys.INDENT, "yes"); transformer.setOutputProperty(OutputKeys.CDATA_SECTION_ELEMENTS, "codefragment"); - StringWriter writer = new StringWriter(); transformer.transform(new DOMSource(doc), new StreamResult(writer)); - return writer.toString(); } catch (TransformerException e) { throw new IllegalStateException(e); } @@ -87,6 +88,17 @@ private String xmlDocToString(Document doc) { @Override public String render(Iterator<Match> matches) { + StringWriter writer = new StringWriter(); + try { + render(matches, writer); + } catch (IOException ignored) { + // Not really possible with a StringWriter + } + return writer.toString(); + } + + @Override + public void render(Iterator<Match> matches, Writer writer) throws IOException { Document doc = createDocument(); Element root = doc.createElement("pmd-cpd"); doc.appendChild(root); @@ -97,7 +109,8 @@ public String render(Iterator<Match> matches) { root.appendChild(addCodeSnippet(doc, addFilesToDuplicationElement(doc, createDuplicationElement(doc, match), match), match)); } - return xmlDocToString(doc); + dumpDocToWriter(doc, writer); + writer.flush(); } private Element addFilesToDuplicationElement(Document doc, Element duplication, Match match) { diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/cpd/renderer/CPDRenderer.java b/pmd-core/src/main/java/net/sourceforge/pmd/cpd/renderer/CPDRenderer.java new file mode 100644 index 00000000000..4f3b601f04f --- /dev/null +++ b/pmd-core/src/main/java/net/sourceforge/pmd/cpd/renderer/CPDRenderer.java @@ -0,0 +1,15 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.cpd.renderer; + +import java.io.IOException; +import java.io.Writer; +import java.util.Iterator; + +import net.sourceforge.pmd.cpd.Match; + +public interface CPDRenderer { + void render(Iterator<Match> matches, Writer writer) throws IOException; +} diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/cpd/token/AntlrToken.java b/pmd-core/src/main/java/net/sourceforge/pmd/cpd/token/AntlrToken.java new file mode 100644 index 00000000000..6df0fd1e503 --- /dev/null +++ b/pmd-core/src/main/java/net/sourceforge/pmd/cpd/token/AntlrToken.java @@ -0,0 +1,74 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.cpd.token; + +import org.antlr.v4.runtime.Lexer; +import org.antlr.v4.runtime.Token; + +import net.sourceforge.pmd.lang.ast.GenericToken; + +/** + * Generic Antlr representation of a token. + */ +public class AntlrToken implements GenericToken { + + private final Token token; + private final AntlrToken previousComment; + + /** + * Constructor + * + * @param token The antlr token implementation + * @param previousComment The previous comment + */ + public AntlrToken(final Token token, final AntlrToken previousComment) { + this.token = token; + this.previousComment = previousComment; + } + + @Override + public GenericToken getNext() { + // Antlr implementation does not require this + return null; + } + + @Override + public GenericToken getPreviousComment() { + return previousComment; + } + + @Override + public String getImage() { + return token.getText(); + } + + @Override + public int getBeginLine() { + return token.getLine(); + } + + @Override + public int getEndLine() { + return token.getLine(); + } + + @Override + public int getBeginColumn() { + return token.getCharPositionInLine(); + } + + @Override + public int getEndColumn() { + return token.getCharPositionInLine() + token.getStopIndex() - token.getStartIndex(); + } + + public int getType() { + return token.getType(); + } + + public boolean isHidden() { + return token.getChannel() == Lexer.HIDDEN; + } +} diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/cpd/token/JavaCCTokenFilter.java b/pmd-core/src/main/java/net/sourceforge/pmd/cpd/token/JavaCCTokenFilter.java new file mode 100644 index 00000000000..0b1cd53b4e0 --- /dev/null +++ b/pmd-core/src/main/java/net/sourceforge/pmd/cpd/token/JavaCCTokenFilter.java @@ -0,0 +1,84 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.cpd.token; + +import net.sourceforge.pmd.lang.TokenManager; +import net.sourceforge.pmd.lang.ast.GenericToken; + +/** + * A generic filter for JavaCC-based token managers that allows to use comments + * to enable / disable analysis of parts of the stream + */ +public class JavaCCTokenFilter implements TokenFilter { + + private final TokenManager tokenManager; + private boolean discardingSuppressing; + + /** + * Creates a new JavaCCTokenFilter + * @param tokenManager The token manager from which to retrieve tokens to be filtered + */ + public JavaCCTokenFilter(final TokenManager tokenManager) { + this.tokenManager = tokenManager; + } + + @Override + public final GenericToken getNextToken() { + GenericToken currentToken = (GenericToken) tokenManager.getNextToken(); + while (!currentToken.getImage().isEmpty()) { + analyzeToken(currentToken); + processCPDSuppression(currentToken); + + if (!isDiscarding()) { + return currentToken; + } + + currentToken = (GenericToken) tokenManager.getNextToken(); + } + + return null; + } + + private boolean isDiscarding() { + return discardingSuppressing || isLanguageSpecificDiscarding(); + } + + private void processCPDSuppression(final GenericToken currentToken) { + // Check if a comment is altering the suppression state + GenericToken comment = currentToken.getPreviousComment(); + while (comment != null) { + if (comment.getImage().contains("CPD-OFF")) { + discardingSuppressing = true; + break; + } + if (comment.getImage().contains("CPD-ON")) { + discardingSuppressing = false; + break; + } + comment = comment.getPreviousComment(); + } + } + + /** + * Extension point for subclasses to indicate tokens are to be filtered. + * + * @return True if tokens should be filtered, false otherwise + */ + protected boolean isLanguageSpecificDiscarding() { + return false; + } + + /** + * Extension point for subclasses to analyze all tokens (before filtering) + * and update internal status to decide on custom discard rules. + * + * @param currentToken The token to be analyzed + * @see #isLanguageSpecificDiscarding() + */ + protected void analyzeToken(final GenericToken currentToken) { + // noop + } + +} diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/cpd/token/TokenFilter.java b/pmd-core/src/main/java/net/sourceforge/pmd/cpd/token/TokenFilter.java new file mode 100644 index 00000000000..965db0cbfdd --- /dev/null +++ b/pmd-core/src/main/java/net/sourceforge/pmd/cpd/token/TokenFilter.java @@ -0,0 +1,19 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.cpd.token; + +import net.sourceforge.pmd.lang.ast.GenericToken; + +/** + * Defines filter to be applied to the token stream during CPD analysis + */ +public interface TokenFilter { + + /** + * Retrieves the next token to pass the filter + * @return The next token to pass the filter, or null if the end of the stream was reached + */ + GenericToken getNextToken(); +} diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/dcd/ClassLoaderUtil.java b/pmd-core/src/main/java/net/sourceforge/pmd/dcd/ClassLoaderUtil.java index 21204cb355c..9534861216e 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/dcd/ClassLoaderUtil.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/dcd/ClassLoaderUtil.java @@ -12,7 +12,7 @@ * ClassLoader utilities. Useful for extracting additional details from a class * hierarchy beyond the basic standard Java Reflection APIs. */ -public class ClassLoaderUtil { +public final class ClassLoaderUtil { public static final String CLINIT = "<clinit>"; @@ -56,8 +56,8 @@ private static Field myGetField(Class<?> type, String name) throws NoSuchFieldEx for (Class<?> superInterface : type.getInterfaces()) { try { return myGetField(superInterface, name); - } catch (NoSuchFieldException e2) { - // Okay + } catch (NoSuchFieldException ignored) { + // Ignored, we'll try the super class next } } // Try the super classes @@ -99,8 +99,8 @@ private static Method myGetMethod(Class<?> type, String name, Class<?>... parame // + type.getSuperclass()); return myGetMethod(type.getSuperclass(), name, parameterTypes); } - } catch (NoSuchMethodException e2) { - // Okay + } catch (NoSuchMethodException ignored) { + // Ignored, we'll try the interfaces next } // Try the super interfaces for (Class<?> superInterface : type.getInterfaces()) { @@ -108,8 +108,8 @@ private static Method myGetMethod(Class<?> type, String name, Class<?>... parame // System.out.println("Checking super interface: " // + superInterface); return myGetMethod(superInterface, name, parameterTypes); - } catch (NoSuchMethodException e3) { - // Okay + } catch (NoSuchMethodException ignored) { + // Ignored, fall through to the last line, were we throw } } throw new NoSuchMethodException(type.getName() + '.' + getMethodSignature(name, parameterTypes)); @@ -153,7 +153,8 @@ public static boolean isOverridenMethod(Class<?> clazz, Method method, boolean c clazz.getDeclaredMethod(method.getName(), method.getParameterTypes()); return true; } - } catch (NoSuchMethodException e) { + } catch (NoSuchMethodException ignored) { + // Ignored, checking super class and interfaces next } // Check super class if (clazz.getSuperclass() != null) { diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/dcd/DCD.java b/pmd-core/src/main/java/net/sourceforge/pmd/dcd/DCD.java index eddc9a266e5..a4a0de4a8d6 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/dcd/DCD.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/dcd/DCD.java @@ -46,7 +46,7 @@ * * @author Ryan Gustafson <ryan.gustafson@gmail.com> */ -public class DCD { +public final class DCD { // // TODO Implement the direct users, indirect users, and dead code // candidate sets. Use the pmd.util.filter.Filter APIs. Need to come up diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/dcd/asm/TypeSignatureVisitor.java b/pmd-core/src/main/java/net/sourceforge/pmd/dcd/asm/TypeSignatureVisitor.java index 1d34c106a87..0d95e544c67 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/dcd/asm/TypeSignatureVisitor.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/dcd/asm/TypeSignatureVisitor.java @@ -94,7 +94,7 @@ public Class<?>[] getMethodParameterTypes() { throw new RuntimeException(); } if (parameterTypes != null) { - return parameterTypes.toArray(new Class<?>[parameterTypes.size()]); + return parameterTypes.toArray(new Class<?>[0]); } else { return null; } diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/dcd/graph/ConstructorNode.java b/pmd-core/src/main/java/net/sourceforge/pmd/dcd/graph/ConstructorNode.java index 7dd9c2badad..59df0b43919 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/dcd/graph/ConstructorNode.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/dcd/graph/ConstructorNode.java @@ -15,7 +15,6 @@ /** * Represents a Class Constructor in a UsageGraph. */ -@SuppressWarnings("PMD.OverrideBothEqualsAndHashcode") public class ConstructorNode extends MemberNode<ConstructorNode, Constructor<?>> { private WeakReference<Constructor<?>> constructorReference; @@ -80,21 +79,4 @@ public int compareTo(ConstructorNode that) { } return cmp; } - - @Override - public boolean equals(Object obj) { - if (obj instanceof ConstructorNode) { - ConstructorNode that = (ConstructorNode) obj; - return super.equals(that); - } - return false; - } - - /* (non-Javadoc) - * @see net.sourceforge.pmd.dcd.graph.MemberNode#hashCode() - */ - @Override - public int hashCode() { - return super.hashCode(); - } } diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/dcd/graph/FieldNode.java b/pmd-core/src/main/java/net/sourceforge/pmd/dcd/graph/FieldNode.java index 1944e4baf61..ef03a917a50 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/dcd/graph/FieldNode.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/dcd/graph/FieldNode.java @@ -12,7 +12,6 @@ /** * Represents a Class Field in a UsageGraph. */ -@SuppressWarnings("PMD.OverrideBothEqualsAndHashcode") public class FieldNode extends MemberNode<FieldNode, Field> { private WeakReference<Field> fieldReference; @@ -36,21 +35,4 @@ public Field getMember() { public int compareTo(FieldNode that) { return this.name.compareTo(that.name); } - - @Override - public boolean equals(Object obj) { - if (obj instanceof FieldNode) { - FieldNode that = (FieldNode) obj; - return super.equals(that); - } - return false; - } - - /* (non-Javadoc) - * @see net.sourceforge.pmd.dcd.graph.MemberNode#hashCode() - */ - @Override - public int hashCode() { - return super.hashCode(); - } } diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/dcd/graph/MemberNode.java b/pmd-core/src/main/java/net/sourceforge/pmd/dcd/graph/MemberNode.java index 8f85abe9917..31dc259f140 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/dcd/graph/MemberNode.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/dcd/graph/MemberNode.java @@ -24,8 +24,6 @@ public abstract class MemberNode<S extends MemberNode<S, T>, T extends Member> private List<MemberNode> users; - private Object decoration; - public MemberNode(ClassNode classNode, String name, String desc) { this.classNode = classNode; this.name = name; @@ -81,7 +79,7 @@ public List<MemberNode> getUsers() { @Override public String toString() { - return name + " " + desc; + return name + ' ' + desc; } public String toStringLong() { @@ -89,6 +87,7 @@ public String toStringLong() { } @SuppressWarnings("PMD.SuspiciousEqualsMethodName") + @Deprecated // To be removed with PMD 7.0.0 public boolean equals(S that) { return equals(that.name, that.desc); } diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/dcd/graph/MethodNode.java b/pmd-core/src/main/java/net/sourceforge/pmd/dcd/graph/MethodNode.java index 8559bb762c5..86e7e0587a8 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/dcd/graph/MethodNode.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/dcd/graph/MethodNode.java @@ -15,7 +15,6 @@ /** * Represents a Class Method in a UsageGraph. */ -@SuppressWarnings("PMD.OverrideBothEqualsAndHashcode") public class MethodNode extends MemberNode<MethodNode, Method> { private WeakReference<Method> methodReference; @@ -58,21 +57,4 @@ public int compareTo(MethodNode that) { } return cmp; } - - @Override - public boolean equals(Object obj) { - if (obj instanceof MethodNode) { - MethodNode that = (MethodNode) obj; - return super.equals(that); - } - return false; - } - - /* (non-Javadoc) - * @see net.sourceforge.pmd.dcd.graph.MemberNode#hashCode() - */ - @Override - public int hashCode() { - return super.hashCode(); - } } diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/dcd/graph/UsageGraphBuilder.java b/pmd-core/src/main/java/net/sourceforge/pmd/dcd/graph/UsageGraphBuilder.java index 954e7acee03..2af82d3c2fa 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/dcd/graph/UsageGraphBuilder.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/dcd/graph/UsageGraphBuilder.java @@ -10,7 +10,6 @@ import java.util.Arrays; import java.util.List; -import org.apache.commons.io.IOUtils; import org.objectweb.asm.AnnotationVisitor; import org.objectweb.asm.Attribute; import org.objectweb.asm.ClassReader; @@ -52,13 +51,10 @@ public void index(String name) { if (classFilter.filter(className)) { if (!usageGraph.isClass(className)) { usageGraph.defineClass(className); - InputStream inputStream = this.getClass().getClassLoader() - .getResourceAsStream(classResourceName + ".class"); - ClassReader classReader = new ClassReader(inputStream); - try { + try (InputStream inputStream = this.getClass().getClassLoader() + .getResourceAsStream(classResourceName + ".class")) { + ClassReader classReader = new ClassReader(inputStream); classReader.accept(getNewClassVisitor(), 0); - } finally { - IOUtils.closeQuietly(inputStream); } } } diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/document/DeleteDocumentOperation.java b/pmd-core/src/main/java/net/sourceforge/pmd/document/DeleteDocumentOperation.java new file mode 100644 index 00000000000..006bdb13763 --- /dev/null +++ b/pmd-core/src/main/java/net/sourceforge/pmd/document/DeleteDocumentOperation.java @@ -0,0 +1,17 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.document; + +public class DeleteDocumentOperation extends DocumentOperation { + + public DeleteDocumentOperation(final int beginLine, final int endLine, final int beginColumn, final int endColumn) { + super(beginLine, endLine, beginColumn, endColumn); + } + + @Override + public void apply(final Document document) { + document.delete(getRegionByLine()); + } +} diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/document/Document.java b/pmd-core/src/main/java/net/sourceforge/pmd/document/Document.java new file mode 100644 index 00000000000..01b26bd03ce --- /dev/null +++ b/pmd-core/src/main/java/net/sourceforge/pmd/document/Document.java @@ -0,0 +1,35 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.document; + +/** + * Represents a file which contains programming code that will be fixed. + */ +public interface Document { + + /** + * Insert a text at a line at the position/column specified. If there is any text to the right of the insertion, + * that text is shifted by the length of the text to insert, which means that it is not replaced. + * @param beginLine the line in which to insert the text + * @param beginColumn the position in the line in which to insert the text + * @param textToInsert the text to be added + */ + void insert(int beginLine, int beginColumn, String textToInsert); + + /** + * Replace a specific region in the document which contains text by another text, which not necessarily is the same + * length as the region's one. + * @param regionByOffset the region in which a text will be inserted to replace the current document's contents + * @param textToReplace the text to insert + */ + void replace(RegionByLine regionByOffset, String textToReplace); + + /** + * Delete a region in the document, removing all text which contains it. If there is any text to the right of this + * region, it will be shifted to the left by the length of the region to delete. + * @param regionByOffset the region in which to erase all the text + */ + void delete(RegionByLine regionByOffset); +} diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/document/DocumentFile.java b/pmd-core/src/main/java/net/sourceforge/pmd/document/DocumentFile.java new file mode 100644 index 00000000000..1aba2c795b2 --- /dev/null +++ b/pmd-core/src/main/java/net/sourceforge/pmd/document/DocumentFile.java @@ -0,0 +1,170 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.document; + +import static java.util.Objects.requireNonNull; + +import java.io.BufferedReader; +import java.io.Closeable; +import java.io.File; +import java.io.IOException; +import java.io.Writer; +import java.nio.charset.Charset; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardCopyOption; +import java.util.ArrayList; +import java.util.List; +import java.util.Scanner; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * Implementation that handles a Document as a file in the filesystem and receives operations in a sorted manner + * (i.e. the regions are sorted). This improves the efficiency of reading the file by only scanning it once while + * operations are applied, until an instance of this document is closed. + */ +public class DocumentFile implements Document, Closeable { + + private static final Logger LOG = Logger.getLogger(DocumentFile.class.getName()); + + private List<Integer> lineToOffset = new ArrayList<>(); + + private final Path filePath; + private final BufferedReader reader; + private int currentPosition = 0; + + private final Path temporaryPath = Files.createTempFile("pmd-", ".tmp"); + private final Writer writer; + + public DocumentFile(final File file, final Charset charset) throws IOException { + reader = Files.newBufferedReader(requireNonNull(file).toPath(), requireNonNull(charset)); + writer = Files.newBufferedWriter(temporaryPath, charset); + this.filePath = file.toPath(); + mapLinesToOffsets(); + } + + private void mapLinesToOffsets() throws IOException { + try (Scanner scanner = new Scanner(filePath)) { + int currentGlobalOffset = 0; + + while (scanner.hasNextLine()) { + lineToOffset.add(currentGlobalOffset); + currentGlobalOffset += getLineLengthWithLineSeparator(scanner); + } + } + } + + /** + * Sums the line length without the line separation and the characters which matched the line separation pattern + * @param scanner the scanner from which to read the line's length + * @return the length of the line with the line separator. + */ + private int getLineLengthWithLineSeparator(final Scanner scanner) { + int lineLength = scanner.nextLine().length(); + final String lineSeparationMatch = scanner.match().group(1); + + if (lineSeparationMatch != null) { + lineLength += lineSeparationMatch.length(); + } + + return lineLength; + } + + @Override + public void insert(int beginLine, int beginColumn, final String textToInsert) { + try { + tryToInsertIntoFile(beginLine, beginColumn, textToInsert); + } catch (final IOException e) { + LOG.log(Level.WARNING, "An exception occurred when inserting into file " + filePath); + } + } + + private void tryToInsertIntoFile(int beginLine, int beginColumn, final String textToInsert) throws IOException { + final int offset = mapToOffset(beginLine, beginColumn); + writeUntilOffsetReached(offset); + writer.write(textToInsert); + } + + private int mapToOffset(final int line, final int column) { + return lineToOffset.get(line) + column; + } + + /** + * Write characters between the current offset until the next offset to be read + * @param nextOffsetToRead the position in which the reader will stop reading + * @throws IOException if an I/O error occurs + */ + private void writeUntilOffsetReached(final int nextOffsetToRead) throws IOException { + if (nextOffsetToRead < currentPosition) { + throw new IllegalStateException(); + } + final char[] bufferToCopy = new char[nextOffsetToRead - currentPosition]; + reader.read(bufferToCopy); + writer.write(bufferToCopy); + currentPosition = nextOffsetToRead; + } + + @Override + public void replace(final RegionByLine regionByLine, final String textToReplace) { + try { + tryToReplaceInFile(mapToRegionByOffset(regionByLine), textToReplace); + } catch (final IOException e) { + LOG.log(Level.WARNING, "An exception occurred when replacing in file " + filePath.toAbsolutePath()); + } + } + + private RegionByOffset mapToRegionByOffset(final RegionByLine regionByLine) { + final int startOffset = mapToOffset(regionByLine.getBeginLine(), regionByLine.getBeginColumn()); + final int endOffset = mapToOffset(regionByLine.getEndLine(), regionByLine.getEndColumn()); + + return new RegionByOffsetImp(startOffset, endOffset - startOffset); + } + + private void tryToReplaceInFile(final RegionByOffset regionByOffset, final String textToReplace) throws IOException { + writeUntilOffsetReached(regionByOffset.getOffset()); + reader.skip(regionByOffset.getLength()); + currentPosition = regionByOffset.getOffsetAfterEnding(); + writer.write(textToReplace); + } + + @Override + public void delete(final RegionByLine regionByOffset) { + try { + tryToDeleteFromFile(mapToRegionByOffset(regionByOffset)); + } catch (final IOException e) { + LOG.log(Level.WARNING, "An exception occurred when deleting from file " + filePath.toAbsolutePath()); + } + } + + private void tryToDeleteFromFile(final RegionByOffset regionByOffset) throws IOException { + writeUntilOffsetReached(regionByOffset.getOffset()); + reader.skip(regionByOffset.getLength()); + currentPosition = regionByOffset.getOffsetAfterEnding(); + } + + @Override + public void close() throws IOException { + if (reader.ready()) { + writeUntilEOF(); + } + reader.close(); + writer.close(); + + Files.move(temporaryPath, filePath, StandardCopyOption.REPLACE_EXISTING); + } + + private void writeUntilEOF() throws IOException { + String line; + + while ((line = reader.readLine()) != null) { + writer.write(line); + } + } + + /* package-private */ List<Integer> getLineToOffset() { + return lineToOffset; + } +} diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/document/DocumentOperation.java b/pmd-core/src/main/java/net/sourceforge/pmd/document/DocumentOperation.java new file mode 100644 index 00000000000..2f5a7893462 --- /dev/null +++ b/pmd-core/src/main/java/net/sourceforge/pmd/document/DocumentOperation.java @@ -0,0 +1,31 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.document; + +/** + * Represents an operation in a document which will be managed by + * {@link DocumentOperationsApplierForNonOverlappingRegions}. + */ +public abstract class DocumentOperation { + + /** + * The region to which this operations belongs + */ + private final RegionByLine regionByLine; + + public DocumentOperation(final int beginLine, final int endLine, final int beginColumn, final int endColumn) { + regionByLine = new RegionByLineImp(beginLine, endLine, beginColumn, endColumn); + } + + /** + * Apply this operation to the specified document + * @param document the document to which apply the operation + */ + public abstract void apply(Document document); + + public RegionByLine getRegionByLine() { + return regionByLine; + } +} diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/document/DocumentOperationsApplierForNonOverlappingRegions.java b/pmd-core/src/main/java/net/sourceforge/pmd/document/DocumentOperationsApplierForNonOverlappingRegions.java new file mode 100644 index 00000000000..2e747ece062 --- /dev/null +++ b/pmd-core/src/main/java/net/sourceforge/pmd/document/DocumentOperationsApplierForNonOverlappingRegions.java @@ -0,0 +1,102 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.document; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.Objects; + +public class DocumentOperationsApplierForNonOverlappingRegions { + + private static final Comparator<DocumentOperation> COMPARATOR = new DocumentOperationNonOverlappingRegionsComparator(); + + private final Document document; + private final List<DocumentOperation> operations; + + private boolean applied; + + public DocumentOperationsApplierForNonOverlappingRegions(final Document document) { + this.document = Objects.requireNonNull(document); + operations = new ArrayList<>(); + applied = false; + } + + public void addDocumentOperation(DocumentOperation documentOperation) { + assertOperationsHaveNotBeenApplied(); + + final int index = getIndexForDocumentOperation(Objects.requireNonNull(documentOperation)); + operations.add(index, documentOperation); + } + + private void assertOperationsHaveNotBeenApplied() { + if (applied) { + throw new IllegalStateException("Document operations have already been applied to the document"); + } + } + + private int getIndexForDocumentOperation(final DocumentOperation documentOperation) { + int potentialIndex = Collections.binarySearch(operations, documentOperation, COMPARATOR); + + if (potentialIndex < 0) { + return ~potentialIndex; + } + + final int lastIndex = operations.size() - 1; + while (potentialIndex < lastIndex && areSiblingsEqual(potentialIndex)) { + potentialIndex++; + } + return potentialIndex + 1; + } + + private boolean areSiblingsEqual(final int index) { + return COMPARATOR.compare(operations.get(index), operations.get(index + 1)) == 0; + } + + public void apply() { + assertOperationsHaveNotBeenApplied(); + applied = true; + + for (final DocumentOperation operation : operations) { + operation.apply(document); + } + } + + private static class DocumentOperationNonOverlappingRegionsComparator implements Comparator<DocumentOperation> { + + @Override + public int compare(final DocumentOperation o1, final DocumentOperation o2) { + final RegionByLine r1 = Objects.requireNonNull(o1).getRegionByLine(); + final RegionByLine r2 = Objects.requireNonNull(o2).getRegionByLine(); + + final int comparison; + if (operationsStartAtTheSameOffsetAndHaveZeroLength(r1, r2)) { + comparison = 0; + } else if (doesFirstRegionEndBeforeSecondRegionBegins(r1, r2)) { + comparison = -1; + } else if (doesFirstRegionEndBeforeSecondRegionBegins(r2, r1)) { + comparison = 1; + } else { + throw new IllegalArgumentException("Regions between document operations overlap, " + r1.toString() + "\n" + r2.toString()); + } + return comparison; + } + + private boolean operationsStartAtTheSameOffsetAndHaveZeroLength(final RegionByLine r1, final RegionByLine r2) { + return r1.getBeginLine() == r2.getBeginLine() && r1.getBeginColumn() == r2.getBeginColumn() + && r1.getBeginLine() == r1.getEndLine() && r1.getBeginColumn() == r1.getEndColumn(); + } + + private boolean doesFirstRegionEndBeforeSecondRegionBegins(final RegionByLine r1, final RegionByLine r2) { + if (r1.getEndLine() < r2.getBeginLine()) { + return true; + } else if (r1.getEndLine() == r2.getBeginLine()) { + return r1.getEndColumn() <= r2.getBeginColumn(); + } + return false; + } + } +} diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/document/InsertDocumentOperation.java b/pmd-core/src/main/java/net/sourceforge/pmd/document/InsertDocumentOperation.java new file mode 100644 index 00000000000..6982abbf407 --- /dev/null +++ b/pmd-core/src/main/java/net/sourceforge/pmd/document/InsertDocumentOperation.java @@ -0,0 +1,26 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.document; + +import static java.util.Objects.requireNonNull; + +/** + * Represents an insert operation in a {@link Document}. + */ +public class InsertDocumentOperation extends DocumentOperation { + + private final String textToInsert; + + public InsertDocumentOperation(final int beginLine, final int beginColumn, final String textToInsert) { + super(beginLine, beginLine, beginColumn, beginColumn); + this.textToInsert = requireNonNull(textToInsert); + } + + @Override + public void apply(final Document document) { + final RegionByLine regionByLine = getRegionByLine(); + document.insert(regionByLine.getBeginLine(), regionByLine.getBeginColumn(), textToInsert); + } +} diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/document/RegionByLine.java b/pmd-core/src/main/java/net/sourceforge/pmd/document/RegionByLine.java new file mode 100644 index 00000000000..f421255806d --- /dev/null +++ b/pmd-core/src/main/java/net/sourceforge/pmd/document/RegionByLine.java @@ -0,0 +1,19 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.document; + +/** + * Represents a region in a {@link Document} with the tuple (beginLine, endLine, beginColumn, endColumn). + */ +public interface RegionByLine { + + int getBeginLine(); + + int getEndLine(); + + int getBeginColumn(); + + int getEndColumn(); +} diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/document/RegionByLineImp.java b/pmd-core/src/main/java/net/sourceforge/pmd/document/RegionByLineImp.java new file mode 100644 index 00000000000..2f9f002a639 --- /dev/null +++ b/pmd-core/src/main/java/net/sourceforge/pmd/document/RegionByLineImp.java @@ -0,0 +1,68 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.document; + +/** + * Immutable implementation of the {@link RegionByLine} interface. + */ +public class RegionByLineImp implements RegionByLine { + + private final int beginLine; + private final int endLine; + private final int beginColumn; + private final int endColumn; + + public RegionByLineImp(final int beginLine, final int endLine, final int beginColumn, final int endColumn) { + this.beginLine = requireNonNegative(beginLine); + this.endLine = requireNonNegative(endLine); + this.beginColumn = requireNonNegative(beginColumn); + this.endColumn = requireNonNegative(endColumn); + + requireLinesCorrectlyOrdered(); + } + + private void requireLinesCorrectlyOrdered() { + if (beginLine > endLine) { + throw new IllegalArgumentException("endLine must be equal or greater than beginLine"); + } + } + + private static int requireNonNegative(final int value) { + if (value < 0) { + throw new IllegalArgumentException("parameter must be non-negative"); + } + return value; + } + + @Override + public int getBeginLine() { + return beginLine; + } + + @Override + public int getEndLine() { + return endLine; + } + + @Override + public int getBeginColumn() { + return beginColumn; + } + + @Override + public int getEndColumn() { + return endColumn; + } + + @Override + public String toString() { + return "RegionByLineImp{" + + "beginLine=" + beginLine + + ", endLine=" + endLine + + ", beginColumn=" + beginColumn + + ", endColumn=" + endColumn + + '}'; + } +} diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/document/RegionByOffset.java b/pmd-core/src/main/java/net/sourceforge/pmd/document/RegionByOffset.java new file mode 100644 index 00000000000..57c73c2d4e0 --- /dev/null +++ b/pmd-core/src/main/java/net/sourceforge/pmd/document/RegionByOffset.java @@ -0,0 +1,17 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.document; + +/** + * Represents a region in a {@link Document} with the tuple (offset, length). + */ +public interface RegionByOffset { + + int getOffset(); + + int getLength(); + + int getOffsetAfterEnding(); +} diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/document/RegionByOffsetImp.java b/pmd-core/src/main/java/net/sourceforge/pmd/document/RegionByOffsetImp.java new file mode 100644 index 00000000000..1fd8c8ea4e0 --- /dev/null +++ b/pmd-core/src/main/java/net/sourceforge/pmd/document/RegionByOffsetImp.java @@ -0,0 +1,42 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.document; + +/** + * Immutable implementation of the {@link RegionByOffset} interface. + */ +public class RegionByOffsetImp implements RegionByOffset { + private final int offset; + private final int length; + private final int offsetAfterEnding; + + public RegionByOffsetImp(final int offset, final int length) { + this.offset = requireNonNegative(offset); + this.length = requireNonNegative(length); + offsetAfterEnding = offset + length; + } + + private static int requireNonNegative(final int value) { + if (value < 0) { + throw new IllegalArgumentException(); + } + return value; + } + + @Override + public int getOffset() { + return offset; + } + + @Override + public int getLength() { + return length; + } + + @Override + public int getOffsetAfterEnding() { + return offsetAfterEnding; + } +} diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/document/ReplaceDocumentOperation.java b/pmd-core/src/main/java/net/sourceforge/pmd/document/ReplaceDocumentOperation.java new file mode 100644 index 00000000000..b7fe5b594ae --- /dev/null +++ b/pmd-core/src/main/java/net/sourceforge/pmd/document/ReplaceDocumentOperation.java @@ -0,0 +1,22 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.document; + +import static java.util.Objects.requireNonNull; + +public class ReplaceDocumentOperation extends DocumentOperation { + + private final String textToReplace; + + public ReplaceDocumentOperation(final int beginLine, final int endLine, final int beginColumn, final int endColumn, final String textToReplace) { + super(beginLine, endLine, beginColumn, endColumn); + this.textToReplace = requireNonNull(textToReplace); + } + + @Override + public void apply(final Document document) { + document.replace(getRegionByLine(), textToReplace); + } +} diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/lang/AbstractLanguageVersionHandler.java b/pmd-core/src/main/java/net/sourceforge/pmd/lang/AbstractLanguageVersionHandler.java index 948f2c7cb9f..400be33c401 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/lang/AbstractLanguageVersionHandler.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/lang/AbstractLanguageVersionHandler.java @@ -51,15 +51,22 @@ public VisitorStarter getTypeResolutionFacade(ClassLoader classLoader) { } @Override - public VisitorStarter getMetricsVisitorFacade() { + public VisitorStarter getDumpFacade(final Writer writer, final String prefix, final boolean recurse) { return VisitorStarter.DUMMY; } @Override - public VisitorStarter getDumpFacade(final Writer writer, final String prefix, final boolean recurse) { + public VisitorStarter getMultifileFacade() { + return VisitorStarter.DUMMY; + } + + + @Override + public VisitorStarter getQualifiedNameResolutionFacade(ClassLoader classLoader) { return VisitorStarter.DUMMY; } + @Override public DFAGraphRule getDFAGraphRule() { return null; diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/lang/BaseLanguageModule.java b/pmd-core/src/main/java/net/sourceforge/pmd/lang/BaseLanguageModule.java index 8b7827c2470..d02b956ddae 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/lang/BaseLanguageModule.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/lang/BaseLanguageModule.java @@ -99,7 +99,7 @@ public LanguageVersion getDefaultVersion() { @Override public String toString() { - return "LanguageModule:" + name + "(" + this.getClass().getSimpleName() + ")"; + return "LanguageModule:" + name + '(' + this.getClass().getSimpleName() + ')'; } @Override diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/lang/LanguageFilenameFilter.java b/pmd-core/src/main/java/net/sourceforge/pmd/lang/LanguageFilenameFilter.java index 7a7ca021699..6635ac66633 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/lang/LanguageFilenameFilter.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/lang/LanguageFilenameFilter.java @@ -8,6 +8,7 @@ import java.io.FilenameFilter; import java.util.Collections; import java.util.List; +import java.util.Locale; import java.util.Set; /** @@ -52,7 +53,7 @@ public boolean accept(File dir, String name) { return false; } - String extension = name.substring(1 + lastDotIndex).toUpperCase(); + String extension = name.substring(1 + lastDotIndex).toUpperCase(Locale.ROOT); for (Language language : languages) { for (String ext : language.getExtensions()) { if (extension.equalsIgnoreCase(ext)) { diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/lang/LanguageRegistry.java b/pmd-core/src/main/java/net/sourceforge/pmd/lang/LanguageRegistry.java index 593cf65aed8..029953ae15f 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/lang/LanguageRegistry.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/lang/LanguageRegistry.java @@ -14,6 +14,8 @@ import java.util.Map; import java.util.ServiceLoader; +import org.apache.commons.lang3.StringUtils; + /** * Created by christoferdutz on 20.09.14. */ @@ -25,7 +27,8 @@ public final class LanguageRegistry { private LanguageRegistry() { List<Language> languagesList = new ArrayList<>(); - ServiceLoader<Language> languageLoader = ServiceLoader.load(Language.class); + // Use current class' classloader instead of the threads context classloader, see https://github.com/pmd/pmd/issues/1377 + ServiceLoader<Language> languageLoader = ServiceLoader.load(Language.class, getClass().getClassLoader()); Iterator<Language> iterator = languageLoader.iterator(); while (iterator.hasNext()) { try { @@ -90,7 +93,7 @@ public static LanguageVersion findLanguageVersionByTerseName(String terseNameAnd String version; String terseName; if (terseNameAndVersion.contains(" ")) { - version = terseNameAndVersion.substring(terseNameAndVersion.lastIndexOf(' ') + 1); + version = StringUtils.trimToNull(terseNameAndVersion.substring(terseNameAndVersion.lastIndexOf(' ') + 1)); terseName = terseNameAndVersion.substring(0, terseNameAndVersion.lastIndexOf(' ')); } else { version = null; @@ -120,9 +123,7 @@ public static List<Language> findByExtension(String extension) { public static List<LanguageVersion> findAllVersions() { List<LanguageVersion> versions = new ArrayList<>(); for (Language language : getLanguages()) { - for (LanguageVersion languageVersion : language.getVersions()) { - versions.add(languageVersion); - } + versions.addAll(language.getVersions()); } return versions; } diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/lang/LanguageVersionHandler.java b/pmd-core/src/main/java/net/sourceforge/pmd/lang/LanguageVersionHandler.java index 238e711a665..8a536a56165 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/lang/LanguageVersionHandler.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/lang/LanguageVersionHandler.java @@ -79,21 +79,33 @@ public interface LanguageVersionHandler { VisitorStarter getTypeResolutionFacade(ClassLoader classLoader); /** - * Get the Metrics Framework visitor. + * Get the DumpFacade. * + * @param writer + * The writer to dump to. * @return VisitorStarter */ - VisitorStarter getMetricsVisitorFacade(); + VisitorStarter getDumpFacade(Writer writer, String prefix, boolean recurse); /** - * Get the DumpFacade. + * Gets the visitor that performs multifile data gathering. * - * @param writer - * The writer to dump to. - * @return VisitorStarter + * @return The visitor starter */ - VisitorStarter getDumpFacade(Writer writer, String prefix, boolean recurse); + VisitorStarter getMultifileFacade(); + + + /** + * Gets the visitor that populates the qualified names of the + * nodes. + * + * @param classLoader The classloader to use to resolve the types of type qualified names + * + * @return The visitor starter + */ + VisitorStarter getQualifiedNameResolutionFacade(ClassLoader classLoader); + DFAGraphRule getDFAGraphRule(); } diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/lang/TokenManager.java b/pmd-core/src/main/java/net/sourceforge/pmd/lang/TokenManager.java index 6673d13a68e..e0e67c78cc1 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/lang/TokenManager.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/lang/TokenManager.java @@ -8,6 +8,7 @@ * Common interface for interacting with parser Token Managers. */ public interface TokenManager { + // TODO : Change the return to GenericToken in 7.0.0 - maybe even use generics TokenManager<T extends GenericToken> Object getNextToken(); void setFileName(String fileName); diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/lang/XPathHandler.java b/pmd-core/src/main/java/net/sourceforge/pmd/lang/XPathHandler.java index c6407e1a207..95728185d74 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/lang/XPathHandler.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/lang/XPathHandler.java @@ -6,6 +6,7 @@ import org.jaxen.Navigator; +import net.sourceforge.pmd.annotation.InternalApi; import net.sourceforge.pmd.lang.xpath.Initializer; import net.sf.saxon.sxpath.IndependentContext; @@ -14,15 +15,19 @@ * Interface for performing Language specific XPath handling, such as * initialization and navigation. */ +@InternalApi +@Deprecated public interface XPathHandler { XPathHandler DUMMY = new XPathHandler() { @Override public void initialize() { + // empty handler - does nothing } @Override public void initialize(IndependentContext context) { + // empty handler - does nothing } @Override @@ -46,6 +51,9 @@ public Navigator getNavigator() { /** * Get a Jaxen Navigator for this Language. May return <code>null</code> if * there is no Jaxen Navigation for this language. + * + * @deprecated Support for Jaxen will be removed come 7.0.0 */ + @Deprecated Navigator getNavigator(); } diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/lang/antlr/AntlrTokenManager.java b/pmd-core/src/main/java/net/sourceforge/pmd/lang/antlr/AntlrTokenManager.java new file mode 100644 index 00000000000..0bf95b33362 --- /dev/null +++ b/pmd-core/src/main/java/net/sourceforge/pmd/lang/antlr/AntlrTokenManager.java @@ -0,0 +1,87 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.antlr; + +import org.antlr.v4.runtime.BaseErrorListener; +import org.antlr.v4.runtime.Lexer; +import org.antlr.v4.runtime.RecognitionException; +import org.antlr.v4.runtime.Recognizer; + +import net.sourceforge.pmd.cpd.token.AntlrToken; +import net.sourceforge.pmd.lang.TokenManager; + +/** + * Generic token manager implementation for all Antlr lexers. + */ +public class AntlrTokenManager implements TokenManager { + private final Lexer lexer; + private String fileName; + private AntlrToken previousToken; + + /** + * Constructor + * + * @param lexer The lexer + * @param fileName The file name + */ + public AntlrTokenManager(final Lexer lexer, final String fileName) { + this.lexer = lexer; + this.fileName = fileName; + resetListeners(); + } + + @Override + public Object getNextToken() { + final AntlrToken previousComment = previousToken != null && previousToken.isHidden() ? previousToken : null; + final AntlrToken currentToken = new AntlrToken(lexer.nextToken(), previousComment); + previousToken = currentToken; + + return currentToken; + } + + @Override + public void setFileName(String fileName) { + this.fileName = fileName; + } + + public String getFileName() { + return fileName; + } + + private void resetListeners() { + lexer.removeErrorListeners(); + lexer.addErrorListener(new ErrorHandler()); + } + + private static class ErrorHandler extends BaseErrorListener { + + @Override + public void syntaxError(final Recognizer<?, ?> recognizer, final Object offendingSymbol, final int line, + final int charPositionInLine, final String msg, final RecognitionException ex) { + throw new ANTLRSyntaxError(msg, line, charPositionInLine, ex); + } + } + + public static class ANTLRSyntaxError extends RuntimeException { + private static final long serialVersionUID = 1L; + private final int line; + private final int column; + + /* default */ ANTLRSyntaxError(final String msg, final int line, final int column, + final RecognitionException cause) { + super(msg, cause); + this.line = line; + this.column = column; + } + + public int getLine() { + return line; + } + + public int getColumn() { + return column; + } + } +} diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/AbstractNode.java b/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/AbstractNode.java index f7bbd8e178d..e1985d4d5bc 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/AbstractNode.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/AbstractNode.java @@ -7,22 +7,33 @@ import java.util.ArrayList; import java.util.Iterator; import java.util.List; - +import java.util.Objects; +import java.util.logging.Logger; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; +import org.apache.commons.lang3.ArrayUtils; import org.jaxen.BaseXPath; import org.jaxen.JaxenException; import org.w3c.dom.Document; import org.w3c.dom.Element; +import net.sourceforge.pmd.PMDVersion; import net.sourceforge.pmd.lang.ast.xpath.Attribute; +import net.sourceforge.pmd.lang.ast.xpath.AttributeAxisIterator; import net.sourceforge.pmd.lang.ast.xpath.DocumentNavigator; import net.sourceforge.pmd.lang.dfa.DataFlowNode; + +/** + * Base class for all implementations of the Node interface. + */ public abstract class AbstractNode implements Node { + private static final Logger LOG = Logger.getLogger(AbstractNode.class.getName()); + + protected Node parent; protected Node[] children; protected int childIndex; @@ -113,13 +124,6 @@ public int jjtGetId() { return id; } - /** - * Subclasses should implement this method to return a name usable with - * XPathRule for evaluating Element Names. - */ - @Override - public abstract String toString(); - @Override public String getImage() { return image; @@ -132,7 +136,7 @@ public void setImage(String image) { @Override public boolean hasImageEqualTo(String image) { - return this.getImage() != null && this.getImage().equals(image); + return Objects.equals(this.getImage(), image); } @Override @@ -218,7 +222,7 @@ public <T> T getFirstParentOfType(Class<T> parentType) { while (parentNode != null && !parentType.isInstance(parentNode)) { parentNode = parentNode.jjtGetParent(); } - return (T) parentNode; + return parentType.cast(parentNode); } @@ -228,21 +232,41 @@ public <T> List<T> getParentsOfType(Class<T> parentType) { Node parentNode = jjtGetParent(); while (parentNode != null) { if (parentType.isInstance(parentNode)) { - parents.add((T) parentNode); + parents.add(parentType.cast(parentNode)); } parentNode = parentNode.jjtGetParent(); } return parents; } + @SafeVarargs + @Override + public final <T> T getFirstParentOfAnyType(Class<? extends T>... parentTypes) { + Node parentNode = jjtGetParent(); + while (parentNode != null) { + for (Class<? extends T> c : parentTypes) { + if (c.isInstance(parentNode)) { + return c.cast(parentNode); + } + } + parentNode = parentNode.jjtGetParent(); + } + return null; + } @Override public <T> List<T> findDescendantsOfType(Class<T> targetType) { List<T> list = new ArrayList<>(); - findDescendantsOfType(this, targetType, list, true); + findDescendantsOfType(this, targetType, list, false); return list; } + // TODO : Add to Node interface in 7.0.0 + public <T> List<T> findDescendantsOfType(final Class<T> targetType, final boolean crossBoundaries) { + final List<T> list = new ArrayList<>(); + findDescendantsOfType(this, targetType, list, crossBoundaries); + return list; + } @Override public <T> void findDescendantsOfType(Class<T> targetType, List<T> results, boolean crossBoundaries) { @@ -252,18 +276,15 @@ public <T> void findDescendantsOfType(Class<T> targetType, List<T> results, bool private static <T> void findDescendantsOfType(Node node, Class<T> targetType, List<T> results, boolean crossFindBoundaries) { - if (!crossFindBoundaries && node.isFindBoundary()) { - return; - } - - int n = node.jjtGetNumChildren(); - for (int i = 0; i < n; i++) { + for (int i = 0; i < node.jjtGetNumChildren(); i++) { Node child = node.jjtGetChild(i); - if (child.getClass() == targetType) { - results.add((T) child); + if (targetType.isAssignableFrom(child.getClass())) { + results.add(targetType.cast(child)); } - findDescendantsOfType(child, targetType, results, crossFindBoundaries); + if (crossFindBoundaries || !child.isFindBoundary()) { + findDescendantsOfType(child, targetType, results, crossFindBoundaries); + } } } @@ -271,11 +292,10 @@ private static <T> void findDescendantsOfType(Node node, Class<T> targetType, Li @Override public <T> List<T> findChildrenOfType(Class<T> targetType) { List<T> list = new ArrayList<>(); - int n = jjtGetNumChildren(); - for (int i = 0; i < n; i++) { + for (int i = 0; i < jjtGetNumChildren(); i++) { Node child = jjtGetChild(i); if (targetType.isInstance(child)) { - list.add((T) child); + list.add(targetType.cast(child)); } } return list; @@ -332,23 +352,26 @@ public <T> T getFirstChildOfType(Class<T> childType) { int n = jjtGetNumChildren(); for (int i = 0; i < n; i++) { Node child = jjtGetChild(i); - if (child.getClass() == childType) { - return (T) child; + if (childType.isInstance(child)) { + return childType.cast(child); } } return null; } - private static <T> T getFirstDescendantOfType(Class<T> descendantType, Node node) { - int n = node.jjtGetNumChildren(); + + private static <T> T getFirstDescendantOfType(final Class<T> descendantType, final Node node) { + final int n = node.jjtGetNumChildren(); for (int i = 0; i < n; i++) { Node n1 = node.jjtGetChild(i); - if (n1.getClass() == descendantType) { - return (T) n1; + if (descendantType.isAssignableFrom(n1.getClass())) { + return descendantType.cast(n1); } - T n2 = getFirstDescendantOfType(descendantType, n1); - if (n2 != null) { - return n2; + if (!n1.isFindBoundary()) { + final T n2 = getFirstDescendantOfType(descendantType, n1); + if (n2 != null) { + return n2; + } } } return null; @@ -360,12 +383,31 @@ public final <T> boolean hasDescendantOfType(Class<T> type) { return getFirstDescendantOfType(type) != null; } + /** + * Returns true if this node has a descendant of any type among the provided types. + * + * @param types Types to test * - * @param types - * @return boolean + * @deprecated Use {@link #hasDescendantOfAnyType(Class[])} */ + @Deprecated public final boolean hasDecendantOfAnyType(Class<?>... types) { + return hasDescendantOfAnyType(types); + } + + + /** + * Returns true if this node has a descendant of any type among the provided types. + * + * @param types Types to test + */ + public final boolean hasDescendantOfAnyType(Class<?>... types) { + // TODO consider implementing that with a single traversal! + // hasDescendantOfType could then be a special case of this one + // But to really share implementations, getFirstDescendantOfType's + // internal helper could have to give up some type safety to rely + // instead on a getFirstDescendantOfAnyType, then cast to the correct type for (Class<?> type : types) { if (hasDescendantOfType(type)) { return true; @@ -418,4 +460,66 @@ public GenericToken jjtGetLastToken() { public void jjtSetLastToken(GenericToken token) { this.lastToken = token; } + + @Override + public void remove() { + // Detach current node of its parent, if any + final Node parent = jjtGetParent(); + if (parent != null) { + parent.removeChildAtIndex(jjtGetChildIndex()); + jjtSetParent(null); + } + + // TODO [autofix]: Notify action for handling text edition + } + + @Override + public void removeChildAtIndex(final int childIndex) { + if (0 <= childIndex && childIndex < jjtGetNumChildren()) { + // Remove the child at the given index + children = ArrayUtils.remove(children, childIndex); + // Update the remaining & left-shifted children indexes + for (int i = childIndex; i < jjtGetNumChildren(); i++) { + jjtGetChild(i).jjtSetChildIndex(i); + } + } + } + + + /** + * {@inheritDoc} + * + * <p>This default implementation adds compatibility with the previous + * way to get the xpath node name, which used {@link Object#toString()}. + * + * <p>Please override it. It may be removed in a future major version. + */ + @Override + // @Deprecated // FUTURE 7.0.0 make abstract + public String getXPathNodeName() { + LOG.warning("getXPathNodeName should be overriden in classes derived from AbstractNode. " + + "The implementation is provided for compatibility with existing implementors," + + "but could be declared abstract as soon as release " + PMDVersion.getNextMajorRelease() + + "."); + return toString(); + } + + + /** + * + * + * @deprecated The equivalence between toString and a node's name could be broken as soon as release 7.0.0. + * Use getXPathNodeName for that purpose. The use for debugging purposes is not deprecated. + */ + @Deprecated + @Override + public String toString() { + return getXPathNodeName(); + } + + + @Override + public Iterator<Attribute> getXPathAttributesIterator() { + return new AttributeAxisIterator(this); + } } diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/GenericToken.java b/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/GenericToken.java index 8750e01ae4a..18798da8688 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/GenericToken.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/GenericToken.java @@ -4,6 +4,51 @@ package net.sourceforge.pmd.lang.ast; -public class GenericToken { +/** + * Represents a language-independent token such as constants, values language reserved keywords, or comments. + */ +public interface GenericToken { + + /** + * Obtain the next generic token according to the input stream which generated the instance of this token. + * @return the next generic token if it exists; null if it does not exist + */ + GenericToken getNext(); + + /** + * Obtain a comment-type token which, according to the input stream which generated the instance of this token, + * precedes this instance token and succeeds the previous generic token (if there is any). + * @return the comment-type token if it exists; null if it does not exist + */ + GenericToken getPreviousComment(); + + /** + * Gets the token's text. + * @return the token's text + */ + String getImage(); + + /** + * Gets the line where the token's region begins + * @return a non-negative integer containing the begin line + */ + int getBeginLine(); + + /** + * Gets the line where the token's region ends + * @return a non-negative integer containing the end line + */ + int getEndLine(); + + /** + * Gets the column offset from the start of the begin line where the token's region begins + * @return a non-negative integer containing the begin column + */ + int getBeginColumn(); + /** + * Gets the column offset from the start of the end line where the token's region ends + * @return a non-negative integer containing the begin column + */ + int getEndColumn(); } diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/Node.java b/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/Node.java index 1461eba2d2e..dfac6c1d078 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/Node.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/Node.java @@ -5,11 +5,13 @@ package net.sourceforge.pmd.lang.ast; +import java.util.Iterator; import java.util.List; import org.jaxen.JaxenException; import org.w3c.dom.Document; +import net.sourceforge.pmd.lang.ast.xpath.Attribute; import net.sourceforge.pmd.lang.dfa.DataFlowNode; /** @@ -84,16 +86,27 @@ public interface Node { Node jjtGetChild(int index); /** - * Return the number of children the node has. + * Returns the number of children the node has. */ int jjtGetNumChildren(); int jjtGetId(); + + /** + * Returns a string token, usually filled-in by the parser, which describes some textual + * characteristic of this node. This is usually an identifier, but you should check that + * using the Designer. On most nodes though, this method returns {@code null}. + */ String getImage(); void setImage(String image); + /** + * Returns true if this node's image is equal to the given string. + * + * @param image The image to check + */ boolean hasImageEqualTo(String image); int getBeginLine(); @@ -108,11 +121,19 @@ public interface Node { void setDataFlowNode(DataFlowNode dataFlowNode); + + /** + * Returns true if this node is considered a boundary by traversal methods. + * Traversal methods such as {@link #getFirstDescendantOfType(Class)} don't + * look past such boundaries by default, which is usually the expected thing + * to do. For example, in Java, lambdas and nested classes are considered + * find boundaries. + */ boolean isFindBoundary(); /** - * Returns the n-th parent or null if there are not {@code n} ancestors + * Returns the n-th parent or null if there are less than {@code n} ancestors. * * @param n how many ancestors to iterate over. * @@ -137,7 +158,8 @@ public interface Node { /** * Traverses up the tree to find all of the parent instances of type - * parentType or one of its subclasses. + * parentType or one of its subclasses. The nodes are ordered + * deepest-first. * * @param parentType Class literal of the type you want to find * @param <T> The type you want to find @@ -146,6 +168,18 @@ public interface Node { */ <T> List<T> getParentsOfType(Class<T> parentType); + + /** + * Gets the first parent that's an instance of any of the given types. + * + * @param parentTypes Types to look for + * @param <T> Most specific common type of the parameters + * + * @return The first parent with a matching type. Returns null if there + * is no such parent + */ + <T> T getFirstParentOfAnyType(Class<? extends T>... parentTypes); + /** * Traverses the children to find all the instances of type childType or * one of its subclasses. @@ -162,7 +196,7 @@ public interface Node { /** * Traverses down the tree to find all the descendant instances of type - * descendantType. + * descendantType without crossing find boundaries. * * @param targetType * class which you want to find. @@ -199,7 +233,7 @@ public interface Node { /** * Traverses down the tree to find the first descendant instance of type - * descendantType. + * descendantType without crossing find boundaries. * * @param descendantType * class which you want to find. @@ -209,7 +243,7 @@ public interface Node { <T> T getFirstDescendantOfType(Class<T> descendantType); /** - * Finds if this node contains a descendant of the given type. + * Finds if this node contains a descendant of the given type without crossing find boundaries. * * @param type * the node type to search @@ -255,12 +289,12 @@ public interface Node { /** * Set the user data associated with this node. - * <p> - * PMD itself will never set user data onto a node. Nor should any Rule + * + * <p>PMD itself will never set user data onto a node. Nor should any Rule * implementation, as the AST nodes are shared between concurrently * executing Rules (i.e. it is <strong>not</strong> thread-safe). - * <p> - * This API is most useful for external applications looking to leverage + * + * <p>This API is most useful for external applications looking to leverage * PMD's robust support for AST structures, in which case application * specific annotations on the AST nodes can be quite useful. * @@ -268,4 +302,36 @@ public interface Node { * The data to set on this node. */ void setUserData(Object userData); + + /** + * Remove the current node from its parent. + */ + void remove(); + + /** + * This method tells the node to remove the child node at the given index from the node's list of + * children, if any; if not, no changes are done. + * + * @param childIndex + * The index of the child to be removed + */ + void removeChildAtIndex(int childIndex); + + + /** + * Gets the name of the node that is used to match it with XPath queries. + * + * @return The XPath node name + */ + String getXPathNodeName(); + + + /** + * Returns an iterator enumerating all the attributes that are available + * from XPath for this node. + * + * @return An attribute iterator for this node + */ + Iterator<Attribute> getXPathAttributesIterator(); + } diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/QualifiedName.java b/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/QualifiedName.java index 2f46a186134..69d5f386ab8 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/QualifiedName.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/QualifiedName.java @@ -5,9 +5,11 @@ package net.sourceforge.pmd.lang.ast; /** - * Basic interface for qualified names. That's used in the metrics framework to refer unambiguously to operations or - * classes. Language specific pmd modules should have at most one implementation of this interface, to allow safe - * downcasting from QualifiedName to e.g. JavaQualifiedName. + * Basic interface for qualified names usable in the metrics framework. + * Qualified names identify unambiguously operations and classes across + * the analysed project. Language specific pmd modules should have at + * most one implementation of this interface, to allow safe downcasting + * from QualifiedName to e.g. JavaQualifiedName. * * @author Clément Fournier */ diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/xpath/AbstractASTXPathHandler.java b/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/xpath/AbstractASTXPathHandler.java index 4c9fad1fdf7..f4c1e8ddbe9 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/xpath/AbstractASTXPathHandler.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/xpath/AbstractASTXPathHandler.java @@ -6,11 +6,15 @@ import org.jaxen.Navigator; +import net.sourceforge.pmd.annotation.InternalApi; import net.sourceforge.pmd.lang.Language; import net.sourceforge.pmd.lang.XPathHandler; import net.sf.saxon.sxpath.IndependentContext; + +@Deprecated +@InternalApi public abstract class AbstractASTXPathHandler implements XPathHandler { @Override diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/xpath/Attribute.java b/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/xpath/Attribute.java index 07a0eaa3ccc..6f57f00dabe 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/xpath/Attribute.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/xpath/Attribute.java @@ -6,27 +6,43 @@ import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.logging.Level; +import java.util.logging.Logger; import net.sourceforge.pmd.lang.ast.Node; /** + * Represents an XPath attribute of a specific node. + * Attributes know their name, the node they wrap, + * and have access to their value. + * * @author daniels */ public class Attribute { + + private static final Logger LOG = Logger.getLogger(Attribute.class.getName()); + private static final ConcurrentMap<String, Boolean> DETECTED_DEPRECATED_ATTRIBUTES = new ConcurrentHashMap<>(); + private static final Object[] EMPTY_OBJ_ARRAY = new Object[0]; + private Node parent; private String name; private Method method; private Object value; private String stringValue; + /** Creates a new attribute belonging to the given node using its accessor. */ public Attribute(Node parent, String name, Method m) { this.parent = parent; this.name = name; this.method = m; } + + /** Creates a new attribute belonging to the given node using its string value. */ public Attribute(Node parent, String name, String value) { this.parent = parent; this.name = name; @@ -34,17 +50,33 @@ public Attribute(Node parent, String name, String value) { this.stringValue = value; } + + public String getName() { + return name; + } + + + public Node getParent() { + return parent; + } + public Object getValue() { if (value != null) { return value; } + + if (method.isAnnotationPresent(Deprecated.class) && LOG.isLoggable(Level.WARNING) + && DETECTED_DEPRECATED_ATTRIBUTES.putIfAbsent(getLoggableAttributeName(), Boolean.TRUE) == null) { + // this message needs to be kept in sync with PMDCoverageTest + LOG.warning("Use of deprecated attribute '" + getLoggableAttributeName() + "' in XPath query"); + } + // this lazy loading reduces calls to Method.invoke() by about 90% try { - return method.invoke(parent, EMPTY_OBJ_ARRAY); - } catch (IllegalAccessException iae) { + value = method.invoke(parent, EMPTY_OBJ_ARRAY); + return value; + } catch (IllegalAccessException | InvocationTargetException iae) { iae.printStackTrace(); - } catch (InvocationTargetException ite) { - ite.printStackTrace(); } return null; } @@ -57,24 +89,16 @@ public String getStringValue() { if (this.value == null) { v = getValue(); } - if (v == null) { - stringValue = ""; - } else { - stringValue = String.valueOf(v); - } + stringValue = v == null ? "" : String.valueOf(v); return stringValue; } - public String getName() { - return name; - } - - public Node getParent() { - return parent; + private String getLoggableAttributeName() { + return parent.getXPathNodeName() + "/@" + name; } @Override public String toString() { - return name + ":" + getValue() + ":" + parent; + return name + ':' + getValue() + ':' + parent; } } diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/xpath/AttributeAxisIterator.java b/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/xpath/AttributeAxisIterator.java index c6e05d20aea..75807ecd716 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/xpath/AttributeAxisIterator.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/xpath/AttributeAxisIterator.java @@ -6,55 +6,54 @@ import java.lang.reflect.Method; import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; import java.util.Iterator; import java.util.List; +import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; +import net.sourceforge.pmd.annotation.InternalApi; import net.sourceforge.pmd.lang.ast.Node; -public class AttributeAxisIterator implements Iterator<Attribute> { - - private static class MethodWrapper { - public Method method; - public String name; - MethodWrapper(Method m) { - this.method = m; - this.name = truncateMethodName(m.getName()); - } +/** + * Explores an AST node reflectively to iterate over its XPath + * attributes. This is the default way the attributes of a node + * are made accessible to XPath rules, and defines an important + * piece of PMD's XPath support. + * + * @deprecated Use {@link Node#getXPathAttributesIterator()} + */ +@Deprecated +@InternalApi +public class AttributeAxisIterator implements Iterator<Attribute> { - private String truncateMethodName(String n) { - // about 70% of the methods start with 'get', so this case goes - // first - if (n.startsWith("get")) { - return n.substring("get".length()); - } - if (n.startsWith("is")) { - return n.substring("is".length()); - } - if (n.startsWith("has")) { - return n.substring("has".length()); - } - if (n.startsWith("uses")) { - return n.substring("uses".length()); - } + /** Caches the precomputed attribute accessors of a given class. */ + private static final ConcurrentMap<Class<?>, MethodWrapper[]> METHOD_CACHE = new ConcurrentHashMap<>(); - return n; - } - } + /* Constants used to determine which methods are accessors */ + private static final Set<Class<?>> CONSIDERED_RETURN_TYPES + = new HashSet<>(Arrays.<Class<?>>asList(Integer.TYPE, Boolean.TYPE, Double.TYPE, String.class, Long.TYPE, Character.TYPE, Float.TYPE)); + private static final Set<String> FILTERED_OUT_NAMES + = new HashSet<>(Arrays.asList("toString", "getClass", "getXPathNodeName", "getTypeNameNode", "hashCode", "getImportedNameNode", "getScope")); + /* Iteration variables */ private Attribute currObj; private MethodWrapper[] methodWrappers; private int position; private Node node; - private static ConcurrentMap<Class<?>, MethodWrapper[]> methodCache = - new ConcurrentHashMap<Class<?>, MethodWrapper[]>(); + /** + * Creates a new iterator that enumerates the attributes of the given node. + * Note: if you want to access the attributes of a node, don't use this directly, + * use instead the overridable {@link Node#getXPathAttributesIterator()}. + */ public AttributeAxisIterator(Node contextNode) { this.node = contextNode; - if (!methodCache.containsKey(contextNode.getClass())) { + if (!METHOD_CACHE.containsKey(contextNode.getClass())) { Method[] preFilter = contextNode.getClass().getMethods(); List<MethodWrapper> postFilter = new ArrayList<>(); for (Method element : preFilter) { @@ -62,17 +61,35 @@ public AttributeAxisIterator(Node contextNode) { postFilter.add(new MethodWrapper(element)); } } - methodCache.putIfAbsent(contextNode.getClass(), postFilter.toArray(new MethodWrapper[postFilter.size()])); + METHOD_CACHE.putIfAbsent(contextNode.getClass(), postFilter.toArray(new MethodWrapper[0])); } - this.methodWrappers = methodCache.get(contextNode.getClass()); + this.methodWrappers = METHOD_CACHE.get(contextNode.getClass()); this.position = 0; this.currObj = getNextAttribute(); } + + /** + * Returns whether the given method is an attribute accessor, + * in which case a corresponding Attribute will be added to + * the iterator. + * + * @param method The method to test + */ + protected boolean isAttributeAccessor(Method method) { + String methodName = method.getName(); + + return CONSIDERED_RETURN_TYPES.contains(method.getReturnType()) + && method.getParameterTypes().length == 0 + && !methodName.startsWith("jjt") + && !FILTERED_OUT_NAMES.contains(methodName); + } + + @Override public Attribute next() { - if (currObj == null) { + if (!hasNext()) { throw new IndexOutOfBoundsException(); } Attribute ret = currObj; @@ -80,16 +97,19 @@ public Attribute next() { return ret; } + @Override public boolean hasNext() { return currObj != null; } + @Override public void remove() { throw new UnsupportedOperationException(); } + private Attribute getNextAttribute() { if (methodWrappers == null || position == methodWrappers.length) { return null; @@ -98,17 +118,45 @@ private Attribute getNextAttribute() { return new Attribute(node, m.name, m.method); } - protected boolean isAttributeAccessor(Method method) { - String methodName = method.getName(); - boolean deprecated = method.getAnnotation(Deprecated.class) != null; - - return !deprecated - && (Integer.TYPE == method.getReturnType() || Boolean.TYPE == method.getReturnType() - || Double.TYPE == method.getReturnType() || String.class == method.getReturnType()) - && method.getParameterTypes().length == 0 && Void.TYPE != method.getReturnType() - && !methodName.startsWith("jjt") && !"toString".equals(methodName) && !"getScope".equals(methodName) - && !"getClass".equals(methodName) && !"getTypeNameNode".equals(methodName) - && !"getImportedNameNode".equals(methodName) && !"hashCode".equals(methodName); + /** + * Associates an attribute accessor with the XPath-accessible + * name of the attribute. This is used to avoid recomputing + * the name of the attribute for each attribute (it's only done + * once and put inside the {@link #METHOD_CACHE}). + */ + private static class MethodWrapper { + public Method method; + public String name; + + + MethodWrapper(Method m) { + this.method = m; + this.name = truncateMethodName(m.getName()); + } + + + /** + * This method produces the actual XPath name of an attribute + * from the name of its accessor. + */ + private String truncateMethodName(String n) { + // about 70% of the methods start with 'get', so this case goes + // first + if (n.startsWith("get")) { + return n.substring("get".length()); + } + if (n.startsWith("is")) { + return n.substring("is".length()); + } + if (n.startsWith("has")) { + return n.substring("has".length()); + } + if (n.startsWith("uses")) { + return n.substring("uses".length()); + } + + return n; + } } } diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/xpath/AttributeNode.java b/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/xpath/AttributeNode.java index f34343f1336..6fead638864 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/xpath/AttributeNode.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/xpath/AttributeNode.java @@ -6,11 +6,16 @@ import java.util.Iterator; + /** * This interface can be used by an AST node to indicate it can directly provide * access to it's attributes, versus having them be determined via * introspection. + * + * @deprecated See {@link net.sourceforge.pmd.lang.ast.Node#getXPathAttributesIterator()}. + * Will be removed in 7.0.0 */ +@Deprecated public interface AttributeNode { Iterator<Attribute> getAttributeIterator(); } diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/xpath/DefaultASTXPathHandler.java b/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/xpath/DefaultASTXPathHandler.java new file mode 100644 index 00000000000..974d426e807 --- /dev/null +++ b/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/xpath/DefaultASTXPathHandler.java @@ -0,0 +1,24 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.ast.xpath; + +import net.sourceforge.pmd.annotation.InternalApi; + +import net.sf.saxon.sxpath.IndependentContext; + + +@Deprecated +@InternalApi +public class DefaultASTXPathHandler extends AbstractASTXPathHandler { + @Override + public void initialize() { + // override if needed + } + + @Override + public void initialize(IndependentContext context) { + // override if needed + } +} diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/xpath/DocumentNavigator.java b/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/xpath/DocumentNavigator.java index 3adf8ea3e5a..4f90c2cac71 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/xpath/DocumentNavigator.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/xpath/DocumentNavigator.java @@ -11,12 +11,15 @@ import org.jaxen.XPath; import org.jaxen.util.SingleObjectIterator; +import net.sourceforge.pmd.annotation.InternalApi; import net.sourceforge.pmd.lang.ast.Node; import net.sourceforge.pmd.lang.ast.RootNode; /** * @author daniels */ +@Deprecated +@InternalApi public class DocumentNavigator extends DefaultNavigator { private static final Iterator<Node> EMPTY_ITERATOR = new ArrayList<Node>().iterator(); @@ -48,7 +51,7 @@ public String getCommentStringValue(Object arg0) { @Override public String getElementName(Object node) { - return node.toString(); + return ((Node) node).getXPathNodeName(); } @Override @@ -135,11 +138,7 @@ public Object getParentNode(Object arg0) { @Override public Iterator<Attribute> getAttributeAxisIterator(Object arg0) { - if (arg0 instanceof AttributeNode) { - return ((AttributeNode) arg0).getAttributeIterator(); - } else { - return new AttributeAxisIterator((Node) arg0); - } + return ((Node) arg0).getXPathAttributesIterator(); } /** diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/xpath/NodeIterator.java b/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/xpath/NodeIterator.java index 7f1cb1867ac..c442baa8edf 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/xpath/NodeIterator.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/xpath/NodeIterator.java @@ -7,16 +7,22 @@ import java.util.Iterator; import java.util.NoSuchElementException; +import net.sourceforge.pmd.annotation.InternalApi; import net.sourceforge.pmd.lang.ast.Node; /** + * Base class for node iterators used to implement XPath axis + * iterators for Jaxen. + * * @author daniels */ +@Deprecated +@InternalApi public abstract class NodeIterator implements Iterator<Node> { private Node node; - public NodeIterator(Node contextNode) { + protected NodeIterator(Node contextNode) { this.node = getFirstNode(contextNode); } diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/xpath/saxon/AbstractNodeInfo.java b/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/xpath/saxon/AbstractNodeInfo.java index 3c33db0368e..1e7c8e0825f 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/xpath/saxon/AbstractNodeInfo.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/xpath/saxon/AbstractNodeInfo.java @@ -4,6 +4,8 @@ package net.sourceforge.pmd.lang.ast.xpath.saxon; +import net.sourceforge.pmd.annotation.InternalApi; + import net.sf.saxon.Configuration; import net.sf.saxon.event.Receiver; import net.sf.saxon.om.Axis; @@ -27,82 +29,54 @@ * useful implementations, such as {@link #iterateAxis(byte, NodeTest)} and * {@link #isSameNodeInfo(NodeInfo)}. */ +@Deprecated +@InternalApi public class AbstractNodeInfo implements VirtualNode, SiblingCountingNode { - /** - * {@inheritDoc} - */ @Override public String getSystemId() { throw createUnsupportedOperationException("Source.getSystemId()"); } - /** - * {@inheritDoc} - */ @Override public void setSystemId(String systemId) { throw createUnsupportedOperationException("Source.setSystemId(String)"); } - /** - * {@inheritDoc} - */ @Override public String getStringValue() { throw createUnsupportedOperationException("ValueRepresentation.getStringValue()"); } - /** - * {@inheritDoc} - */ @Override public CharSequence getStringValueCS() { throw createUnsupportedOperationException("ValueRepresentation.getStringValueCS()"); } - /** - * {@inheritDoc} - */ @Override public SequenceIterator getTypedValue() throws XPathException { throw createUnsupportedOperationException("Item.getTypedValue()"); } - /** - * {@inheritDoc} - */ @Override public Object getUnderlyingNode() { throw createUnsupportedOperationException("VirtualNode.getUnderlyingNode()"); } - /** - * {@inheritDoc} - */ @Override public int getSiblingPosition() { throw createUnsupportedOperationException("SiblingCountingNode.getSiblingPosition()"); } - /** - * {@inheritDoc} - */ @Override public Value atomize() throws XPathException { throw createUnsupportedOperationException("NodeInfo.atomize()"); } - /** - * {@inheritDoc} - */ @Override public int compareOrder(NodeInfo other) { throw createUnsupportedOperationException("NodeInfo.compareOrder(NodeInfo)"); } - /** - * {@inheritDoc} - */ @Override public void copy(Receiver receiver, int whichNamespaces, boolean copyAnnotations, int locationId) throws XPathException { @@ -135,57 +109,36 @@ public int hashCode() { } } - /** - * {@inheritDoc} - */ @Override public void generateId(FastStringBuffer buffer) { throw createUnsupportedOperationException("NodeInfo.generateId(FastStringBuffer)"); } - /** - * {@inheritDoc} - */ @Override public String getAttributeValue(int fingerprint) { throw createUnsupportedOperationException("NodeInfo.getAttributeValue(int)"); } - /** - * {@inheritDoc} - */ @Override public String getBaseURI() { throw createUnsupportedOperationException("NodeInfo.getBaseURI()"); } - /** - * {@inheritDoc} - */ @Override public int getColumnNumber() { throw createUnsupportedOperationException("NodeInfo.getColumnNumber()"); } - /** - * {@inheritDoc} - */ @Override public Configuration getConfiguration() { throw createUnsupportedOperationException("NodeInfo.getConfiguration()"); } - /** - * {@inheritDoc} - */ @Override public int[] getDeclaredNamespaces(int[] buffer) { throw createUnsupportedOperationException("NodeInfo.getDeclaredNamespaces(int[])"); } - /** - * {@inheritDoc} - */ @Override public String getDisplayName() { throw createUnsupportedOperationException("NodeInfo.getDisplayName()"); @@ -201,129 +154,81 @@ public int getDocumentNumber() { return 0; } - /** - * {@inheritDoc} - */ @Override public DocumentInfo getDocumentRoot() { throw createUnsupportedOperationException("NodeInfo.getDocumentRoot()"); } - /** - * {@inheritDoc} - */ @Override public int getFingerprint() { throw createUnsupportedOperationException("NodeInfo.getFingerprint()"); } - /** - * {@inheritDoc} - */ @Override public int getLineNumber() { throw createUnsupportedOperationException("NodeInfo.getLineNumber()"); } - /** - * {@inheritDoc} - */ @Override public String getLocalPart() { throw createUnsupportedOperationException("NodeInfo.getLocalPart()"); } - /** - * {@inheritDoc} - */ @Override public int getNameCode() { throw createUnsupportedOperationException("NodeInfo.getNameCode()"); } - /** - * {@inheritDoc} - */ @Override public NamePool getNamePool() { throw createUnsupportedOperationException("NodeInfo.getNamePool()"); } - /** - * {@inheritDoc} - */ @Override public int getNodeKind() { throw createUnsupportedOperationException("NodeInfo.getNodeKind()"); } - /** - * {@inheritDoc} - */ @Override public NodeInfo getParent() { throw createUnsupportedOperationException("NodeInfo.getParent()"); } - /** - * {@inheritDoc} - */ @Override public String getPrefix() { throw createUnsupportedOperationException("NodeInfo.getPrefix()"); } - /** - * {@inheritDoc} - */ @Override public NodeInfo getRoot() { throw createUnsupportedOperationException("NodeInfo.getRoot()"); } - /** - * {@inheritDoc} - */ @Override public int getTypeAnnotation() { throw createUnsupportedOperationException("NodeInfo.getTypeAnnotation()"); } - /** - * {@inheritDoc} - */ @Override public String getURI() { throw createUnsupportedOperationException("NodeInfo.getURI()"); } - /** - * {@inheritDoc} - */ @Override public boolean hasChildNodes() { throw createUnsupportedOperationException("NodeInfo.hasChildNodes()"); } - /** - * {@inheritDoc} - */ @Override public boolean isId() { throw createUnsupportedOperationException("NodeInfo.isId()"); } - /** - * {@inheritDoc} - */ @Override public boolean isIdref() { throw createUnsupportedOperationException("NodeInfo.isIdref()"); } - /** - * {@inheritDoc} - */ @Override public boolean isNilled() { throw createUnsupportedOperationException("NodeInfo.isNilled()"); @@ -340,9 +245,6 @@ public boolean isSameNodeInfo(NodeInfo other) { return this.equals(other); } - /** - * {@inheritDoc} - */ @Override public AxisIterator iterateAxis(byte axisNumber) { throw createUnsupportedOperationException( diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/xpath/saxon/AttributeAxisIterator.java b/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/xpath/saxon/AttributeAxisIterator.java index b56cc1ac0d8..9a0e23baf16 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/xpath/saxon/AttributeAxisIterator.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/xpath/saxon/AttributeAxisIterator.java @@ -4,18 +4,24 @@ package net.sourceforge.pmd.lang.ast.xpath.saxon; +import java.util.Iterator; + +import net.sourceforge.pmd.annotation.InternalApi; import net.sourceforge.pmd.lang.ast.xpath.Attribute; import net.sf.saxon.om.Navigator; import net.sf.saxon.om.SequenceIterator; /** - * This is an Attribute axis iterator. + * An adapter over our {@link net.sourceforge.pmd.lang.ast.xpath.AttributeAxisIterator} + * for the Saxon model. */ +@Deprecated +@InternalApi public class AttributeAxisIterator extends Navigator.BaseEnumeration { protected final ElementNode startNodeInfo; - protected final net.sourceforge.pmd.lang.ast.xpath.AttributeAxisIterator iterator; + protected final Iterator<Attribute> iterator; /** * Create an iterator over the Attribute axis for the given ElementNode. @@ -24,20 +30,14 @@ public class AttributeAxisIterator extends Navigator.BaseEnumeration { */ public AttributeAxisIterator(ElementNode startNodeInfo) { this.startNodeInfo = startNodeInfo; - this.iterator = new net.sourceforge.pmd.lang.ast.xpath.AttributeAxisIterator(startNodeInfo.node); + this.iterator = startNodeInfo.node.getXPathAttributesIterator(); } - /** - * {@inheritDoc} - */ @Override public SequenceIterator getAnother() { return new AttributeAxisIterator(startNodeInfo); } - /** - * {@inheritDoc} - */ @Override public void advance() { if (this.iterator.hasNext()) { diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/xpath/saxon/AttributeNode.java b/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/xpath/saxon/AttributeNode.java index 8b859f1c384..fc36cf6b243 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/xpath/saxon/AttributeNode.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/xpath/saxon/AttributeNode.java @@ -4,26 +4,34 @@ package net.sourceforge.pmd.lang.ast.xpath.saxon; +import net.sourceforge.pmd.annotation.InternalApi; import net.sourceforge.pmd.lang.ast.xpath.Attribute; +import net.sourceforge.pmd.lang.rule.xpath.SaxonXPathRuleQuery; import net.sf.saxon.om.NodeInfo; import net.sf.saxon.om.SequenceIterator; import net.sf.saxon.trans.XPathException; import net.sf.saxon.type.Type; -import net.sf.saxon.value.BooleanValue; -import net.sf.saxon.value.EmptySequence; -import net.sf.saxon.value.Int64Value; -import net.sf.saxon.value.StringValue; import net.sf.saxon.value.Value; /** * A Saxon OM Attribute node for an AST Node Attribute. + * Belongs to an {@link ElementNode}, and wraps an + * {@link Attribute}. */ +@Deprecated +@InternalApi public class AttributeNode extends AbstractNodeInfo { protected final Attribute attribute; protected final int id; protected Value value; + + /** + * Creates a new AttributeNode from a PMD Attribute. + * + * @param id The index within the attribute order + */ public AttributeNode(Attribute attribute, int id) { this.attribute = attribute; this.id = id; @@ -45,23 +53,9 @@ public String getURI() { } @Override - public Value atomize() throws XPathException { + public Value atomize() { if (value == null) { - Object v = attribute.getValue(); - // TODO Need to handle the full range of types, is there something - // Saxon can do to help? - if (v instanceof String) { - value = new StringValue((String) v); - } else if (v instanceof Boolean) { - value = BooleanValue.get(((Boolean) v).booleanValue()); - } else if (v instanceof Integer) { - value = Int64Value.makeIntegerValue((Integer) v); - } else if (v == null) { - value = EmptySequence.getInstance(); - } else { - throw new RuntimeException( - "Unable to create ValueRepresentaton for attribute value: " + v + " of type " + v.getClass()); - } + value = SaxonXPathRuleQuery.getAtomicRepresentation(attribute.getValue()); } return value; } diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/xpath/saxon/DocumentNode.java b/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/xpath/saxon/DocumentNode.java index a98308857b5..f91718c8164 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/xpath/saxon/DocumentNode.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/xpath/saxon/DocumentNode.java @@ -8,6 +8,7 @@ import java.util.Iterator; import java.util.Map; +import net.sourceforge.pmd.annotation.InternalApi; import net.sourceforge.pmd.lang.ast.Node; import net.sf.saxon.om.Axis; @@ -21,6 +22,8 @@ /** * A Saxon OM Document node for an AST Node. */ +@Deprecated +@InternalApi public class DocumentNode extends AbstractNodeInfo implements DocumentInfo { /** @@ -46,25 +49,16 @@ public DocumentNode(Node node) { this.rootNode = new ElementNode(this, new IdGenerator(), null, node, -1); } - /** - * {@inheritDoc} - */ @Override public String[] getUnparsedEntity(String name) { throw createUnsupportedOperationException("DocumentInfo.getUnparsedEntity(String)"); } - /** - * {@inheritDoc} - */ @Override public Iterator getUnparsedEntityNames() { throw createUnsupportedOperationException("DocumentInfo.getUnparsedEntityNames()"); } - /** - * {@inheritDoc} - */ @Override public NodeInfo selectID(String id) { throw createUnsupportedOperationException("DocumentInfo.selectID(String)"); diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/xpath/saxon/ElementNode.java b/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/xpath/saxon/ElementNode.java index dc407538a13..60960f4f4b9 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/xpath/saxon/ElementNode.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/xpath/saxon/ElementNode.java @@ -4,6 +4,7 @@ package net.sourceforge.pmd.lang.ast.xpath.saxon; +import net.sourceforge.pmd.annotation.InternalApi; import net.sourceforge.pmd.lang.ast.Node; import net.sf.saxon.om.Axis; @@ -19,6 +20,8 @@ /** * A Saxon OM Element type node for an AST Node. */ +@Deprecated +@InternalApi public class ElementNode extends AbstractNodeInfo { protected final DocumentNode document; @@ -83,7 +86,7 @@ public DocumentInfo getDocumentRoot() { @Override public String getLocalPart() { - return node.toString(); + return node.getXPathNodeName(); } @Override diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/xpath/saxon/IdGenerator.java b/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/xpath/saxon/IdGenerator.java index a0147294211..afb0886811d 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/xpath/saxon/IdGenerator.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/xpath/saxon/IdGenerator.java @@ -4,9 +4,14 @@ package net.sourceforge.pmd.lang.ast.xpath.saxon; +import net.sourceforge.pmd.annotation.InternalApi; + + /** * This class is used to generate unique IDs for nodes. */ +@Deprecated +@InternalApi public class IdGenerator { private int id; diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/lang/dfa/StackObject.java b/pmd-core/src/main/java/net/sourceforge/pmd/lang/dfa/StackObject.java index 78605c7f957..271e8984fb5 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/lang/dfa/StackObject.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/lang/dfa/StackObject.java @@ -24,7 +24,6 @@ public NodeType getType() { @Override public String toString() { - return ("StackObject: type=" + type + ", node=" + node.toString()); - + return "StackObject: type=" + type + ", node=" + node.toString(); } } diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/lang/dfa/VariableAccess.java b/pmd-core/src/main/java/net/sourceforge/pmd/lang/dfa/VariableAccess.java index dca5da4676a..26761385301 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/lang/dfa/VariableAccess.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/lang/dfa/VariableAccess.java @@ -51,13 +51,13 @@ public String getVariableName() { @Override public String toString() { if (isDefinition()) { - return "Definition(" + variableName + ")"; + return "Definition(" + variableName + ')'; } if (isReference()) { - return "Reference(" + variableName + ")"; + return "Reference(" + variableName + ')'; } if (isUndefinition()) { - return "Undefinition(" + variableName + ")"; + return "Undefinition(" + variableName + ')'; } throw new RuntimeException("Access type was never set"); } diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/lang/dfa/pathfinder/CurrentPath.java b/pmd-core/src/main/java/net/sourceforge/pmd/lang/dfa/pathfinder/CurrentPath.java index e23e3c20823..86827e2ff09 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/lang/dfa/pathfinder/CurrentPath.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/lang/dfa/pathfinder/CurrentPath.java @@ -68,7 +68,7 @@ public DataFlowNode getDoBranchNodeFromFirstDoStatement() { } public boolean isEndNode() { - return this.getLast().getChildren().size() == 0; + return this.getLast().getChildren().isEmpty(); // return inode instanceof StartOrEndDataFlowNode; } 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 0c22b3d49bc..bcf88664053 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 @@ 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<AbstractReportNode> 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 747be9e52ae..eb6ec4d5881 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 24d2e9ac5ae..8ba7c1ba4c8 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 a7d664eb7ea..61ab8eb4ee4 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 @@ -9,7 +9,6 @@ import java.io.FileWriter; import java.io.IOException; -import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.StringUtils; import net.sourceforge.pmd.PMD; @@ -25,6 +24,7 @@ * * @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") @@ -44,9 +44,9 @@ public ReportHTMLPrintVisitor(String baseDir) { * Writes the buffer to file. */ private void write(String filename, StringBuilder buf) throws IOException { - BufferedWriter bw = new BufferedWriter(new FileWriter(new File(baseDir + FILE_SEPARATOR + filename))); - bw.write(buf.toString(), 0, buf.length()); - IOUtils.closeQuietly(bw); + try (BufferedWriter bw = new BufferedWriter(new FileWriter(new File(baseDir + FILE_SEPARATOR + filename)))) { + bw.write(buf.toString(), 0, buf.length()); + } } /** 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 18d58771d19..988a29e21dc 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 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<RuleViolation> { 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 6c9adf413b7..0794187d13d 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 184d7d701cc..4b0c7cb6830 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 @@ 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/metrics/BasicProjectMemoizer.java b/pmd-core/src/main/java/net/sourceforge/pmd/lang/metrics/BasicProjectMemoizer.java index c6d9ff7dcff..e5b6a7463dc 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/lang/metrics/BasicProjectMemoizer.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/lang/metrics/BasicProjectMemoizer.java @@ -4,8 +4,8 @@ package net.sourceforge.pmd.lang.metrics; -import java.util.HashMap; import java.util.Map; +import java.util.WeakHashMap; import net.sourceforge.pmd.lang.ast.QualifiableNode; import net.sourceforge.pmd.lang.ast.QualifiedName; @@ -14,17 +14,22 @@ * Simple implementation of a project memoizer. Memoizers are accessible in constant time, provided the QualifiedName's * hashCode is well distributed. * + * <p>This implementation takes care of recollecting irrelevant memoizers by storing them in {@link WeakHashMap}. + * * @param <T> Type of type declaration nodes of the language * @param <O> Type of operation declaration nodes of the language * * @author Clément Fournier * @since 6.0.0 */ -public abstract class BasicProjectMemoizer<T extends QualifiableNode, O extends QualifiableNode> implements - ProjectMemoizer<T, O> { +public abstract class BasicProjectMemoizer<T extends QualifiableNode, O extends QualifiableNode> + implements ProjectMemoizer<T, O> { + + private Map<QualifiedName, MetricMemoizer<T>> classes = new WeakHashMap<>(); + private Map<QualifiedName, MetricMemoizer<O>> operations = new WeakHashMap<>(); - private Map<QualifiedName, MetricMemoizer<T>> classes = new HashMap<>(); - private Map<QualifiedName, MetricMemoizer<O>> operations = new HashMap<>(); + private final Object classesSynchronizer = new Object(); + private final Object operationsSynchronizer = new Object(); /** Clears all memoizers. Used for tests. */ public void reset() { @@ -33,26 +38,26 @@ public void reset() { } - @Override - public void addClassMemoizer(QualifiedName qname) { - classes.put(qname, new BasicMetricMemoizer<T>()); - } - - - @Override - public void addOperationMemoizer(QualifiedName qname) { - operations.put(qname, new BasicMetricMemoizer<O>()); - } - - @Override public MetricMemoizer<O> getOperationMemoizer(QualifiedName qname) { + synchronized (operationsSynchronizer) { + if (!operations.containsKey(qname)) { + operations.put(qname, new BasicMetricMemoizer<O>()); + } + } + return operations.get(qname); } @Override public MetricMemoizer<T> getClassMemoizer(QualifiedName qname) { + synchronized (classesSynchronizer) { + if (!classes.containsKey(qname)) { + classes.put(qname, new BasicMetricMemoizer<T>()); + } + } + return classes.get(qname); } } diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/lang/metrics/MetricKeyUtil.java b/pmd-core/src/main/java/net/sourceforge/pmd/lang/metrics/MetricKeyUtil.java index d5391b3ae0d..1abceb534de 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/lang/metrics/MetricKeyUtil.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/lang/metrics/MetricKeyUtil.java @@ -14,7 +14,7 @@ * @author Clément Fournier * @since 6.0.0 */ -public class MetricKeyUtil { +public final class MetricKeyUtil { private MetricKeyUtil() { @@ -22,7 +22,7 @@ private MetricKeyUtil() { /** - * Creates a new metric key holding a metric which can be computed on a class. + * Creates a new metric key from its metric and name. * * @param name The name of the metric * @param metric The metric to use diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/lang/metrics/MetricOptions.java b/pmd-core/src/main/java/net/sourceforge/pmd/lang/metrics/MetricOptions.java index 536a7db0232..033fb2c3611 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/lang/metrics/MetricOptions.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/lang/metrics/MetricOptions.java @@ -158,7 +158,7 @@ void addAll(Collection<? extends MetricOption> options) { MetricOptions build() { - if (opts.size() == 0) { + if (opts.isEmpty()) { return emptyOptions(); } diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/lang/metrics/ParameterizedMetricKey.java b/pmd-core/src/main/java/net/sourceforge/pmd/lang/metrics/ParameterizedMetricKey.java index f916a07af57..e0a8dbb7997 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/lang/metrics/ParameterizedMetricKey.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/lang/metrics/ParameterizedMetricKey.java @@ -63,6 +63,7 @@ public int hashCode() { * * @return An instance of parameterized metric key corresponding to the parameters */ + @SuppressWarnings("PMD.SingletonClassReturningNewInstance") public static <N extends Node> ParameterizedMetricKey<N> getInstance(MetricKey<N> key, MetricOptions options) { ParameterizedMetricKey<N> tmp = new ParameterizedMetricKey<>(key, options); if (!POOL.containsKey(tmp)) { diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/lang/metrics/ProjectMemoizer.java b/pmd-core/src/main/java/net/sourceforge/pmd/lang/metrics/ProjectMemoizer.java index 51d4c76b552..04651b2a326 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/lang/metrics/ProjectMemoizer.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/lang/metrics/ProjectMemoizer.java @@ -11,6 +11,9 @@ * Object storing the memoizers of the analysed project. This object should ideally be kept separate from the * SignatureMatcher if there is one. A base implementation is available, see {@link BasicProjectMemoizer}. * + * <p>Memoizers need not be all kept, in fact, only those who refer to operations or classes defined in + * the analysed file are still relevant. + * * @param <T> Type of type declaration nodes of the language * @param <O> Type of operation declaration nodes of the language * @@ -38,21 +41,4 @@ public interface ProjectMemoizer<T extends QualifiableNode, O extends Qualifiabl */ MetricMemoizer<T> getClassMemoizer(QualifiedName qname); - - /** - * Adds a memoizer for the class identified by this qualified name. - * - * @param qname The qualified name of the class - */ - void addClassMemoizer(QualifiedName qname); - - - /** - * Adds a memoizer for the operation identified by this qualified name. - * - * @param qname The qualified name of the operations - */ - void addOperationMemoizer(QualifiedName qname); - - } diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/lang/rule/AbstractDelegateRule.java b/pmd-core/src/main/java/net/sourceforge/pmd/lang/rule/AbstractDelegateRule.java index e6ad0e95fd4..062f3756d48 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/lang/rule/AbstractDelegateRule.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/lang/rule/AbstractDelegateRule.java @@ -21,7 +21,11 @@ /** * Base class for Rule implementations which delegate to another Rule instance. + * + * @deprecated This is only relevant to {@link RuleReference}, but prevents sharing the implementation + * of {@link net.sourceforge.pmd.properties.AbstractPropertySource}. Will be removed in 7.0.0 */ +@Deprecated public abstract class AbstractDelegateRule implements Rule { private Rule rule; @@ -30,6 +34,13 @@ public Rule getRule() { return rule; } + + /** + * @deprecated This will be removed in 7.0.0 + * I mark it specially deprecated because it's inherited by rule reference, + * even though a RuleReference has no business setting its rule after construction + */ + @Deprecated public void setRule(Rule rule) { this.rule = rule; } @@ -213,6 +224,10 @@ public <V> void setProperty(MultiValuePropertyDescriptor<V> propertyDescriptor, rule.setProperty(propertyDescriptor, values); } + @Override + public boolean isPropertyOverridden(PropertyDescriptor<?> propertyDescriptor) { + return rule.isPropertyOverridden(propertyDescriptor); + } @Override public Map<PropertyDescriptor<?>, Object> getPropertiesByPropertyDescriptor() { @@ -220,38 +235,80 @@ public Map<PropertyDescriptor<?>, Object> getPropertiesByPropertyDescriptor() { } @Override + @Deprecated // To be removed in PMD 7.0.0 public void setUsesDFA() { - rule.setUsesDFA(); + rule.setDfa(true); } @Override + public void setDfa(boolean isDfa) { + rule.setDfa(isDfa); + } + + @Override + @Deprecated // To be removed in PMD 7.0.0 public boolean usesDFA() { - return rule.usesDFA(); + return rule.isDfa(); + } + + @Override + public boolean isDfa() { + return rule.isDfa(); } @Override + @Deprecated // To be removed in PMD 7.0.0 public void setUsesTypeResolution() { - rule.setUsesTypeResolution(); + rule.setTypeResolution(true); + } + + @Override + public void setTypeResolution(boolean usingTypeResolution) { + rule.setTypeResolution(usingTypeResolution); } @Override + @Deprecated // To be removed in PMD 7.0.0 public boolean usesTypeResolution() { - return rule.usesTypeResolution(); + return rule.isTypeResolution(); + } + + @Override + public boolean isTypeResolution() { + return rule.isTypeResolution(); + } + + @Override + @Deprecated // To be removed in PMD 7.0.0 + public void setUsesMultifile() { + rule.setMultifile(true); } @Override - public void setUsesMetrics() { - rule.setUsesMetrics(); + public void setMultifile(boolean multifile) { + rule.setMultifile(multifile); } @Override - public boolean usesMetrics() { - return rule.usesMetrics(); + @Deprecated // To be removed in PMD 7.0.0 + public boolean usesMultifile() { + return rule.isMultifile(); } @Override + public boolean isMultifile() { + return rule.isMultifile(); + } + + @Override + @Deprecated // To be removed in PMD 7.0.0 public boolean usesRuleChain() { - return rule.usesRuleChain(); + return rule.isRuleChain(); + } + + @Override + public boolean isRuleChain() { + return rule.isRuleChain(); } @Override diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/lang/rule/AbstractRule.java b/pmd-core/src/main/java/net/sourceforge/pmd/lang/rule/AbstractRule.java index 7b06b9bcd30..c2614de7106 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/lang/rule/AbstractRule.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/lang/rule/AbstractRule.java @@ -40,7 +40,7 @@ public abstract class AbstractRule extends AbstractPropertySource implements Rul private RulePriority priority = RulePriority.LOW; private boolean usesDFA; private boolean usesTypeResolution; - private boolean usesMetrics; + private boolean usesMultifile; private List<String> ruleChainVisits = new ArrayList<>(); public AbstractRule() { @@ -48,6 +48,15 @@ public AbstractRule() { definePropertyDescriptor(Rule.VIOLATION_SUPPRESS_XPATH_DESCRIPTOR); } + @Override + protected String getPropertySourceType() { + return "rule"; + } + + /** + * @deprecated Use {@link #deepCopy()} to create verbatim copies of rules. + */ + @Deprecated public void deepCopyValuesTo(AbstractRule otherRule) { otherRule.language = language; otherRule.minimumLanguageVersion = minimumLanguageVersion; @@ -62,11 +71,11 @@ public void deepCopyValuesTo(AbstractRule otherRule) { otherRule.examples = copyExamples(); otherRule.externalInfoUrl = externalInfoUrl; otherRule.priority = priority; - otherRule.propertyDescriptors = copyPropertyDescriptors(); + otherRule.propertyDescriptors = new ArrayList<>(getPropertyDescriptors()); otherRule.propertyValuesByDescriptor = copyPropertyValues(); otherRule.usesDFA = usesDFA; otherRule.usesTypeResolution = usesTypeResolution; - otherRule.usesMetrics = usesMetrics; + otherRule.usesMultifile = usesMultifile; otherRule.ruleChainVisits = copyRuleChainVisits(); } @@ -78,17 +87,11 @@ private List<String> copyRuleChainVisits() { return new ArrayList<>(ruleChainVisits); } - /** - * @see Rule#getLanguage() - */ @Override public Language getLanguage() { return language; } - /** - * @see Rule#setLanguage(net.sourceforge.pmd.lang.Language) - */ @Override public void setLanguage(Language language) { if (this.language != null && this instanceof ImmutableLanguage && !this.language.equals(language)) { @@ -98,194 +101,122 @@ public void setLanguage(Language language) { this.language = language; } - /** - * @see Rule#getMinimumLanguageVersion() - */ @Override public LanguageVersion getMinimumLanguageVersion() { return minimumLanguageVersion; } - /** - * @see Rule#setMinimumLanguageVersion(net.sourceforge.pmd.lang.LanguageVersion) - */ @Override public void setMinimumLanguageVersion(LanguageVersion minimumLanguageVersion) { this.minimumLanguageVersion = minimumLanguageVersion; } - /** - * @see Rule#getMaximumLanguageVersion() - */ @Override public LanguageVersion getMaximumLanguageVersion() { return maximumLanguageVersion; } - /** - * @see Rule#setMaximumLanguageVersion(net.sourceforge.pmd.lang.LanguageVersion) - */ @Override public void setMaximumLanguageVersion(LanguageVersion maximumLanguageVersion) { this.maximumLanguageVersion = maximumLanguageVersion; } - /** - * @see Rule#isDeprecated() - */ @Override public boolean isDeprecated() { return deprecated; } - /** - * @see Rule#setDeprecated(boolean) - */ @Override public void setDeprecated(boolean deprecated) { this.deprecated = deprecated; } - /** - * @see Rule#getName() - */ @Override public String getName() { return name; } - /** - * @see Rule#setName(String) - */ @Override public void setName(String name) { this.name = name; } - /** - * @see Rule#getSince() - */ @Override public String getSince() { return since; } - /** - * @see Rule#setSince(String) - */ @Override public void setSince(String since) { this.since = since; } - /** - * @see Rule#getRuleClass() - */ @Override public String getRuleClass() { return ruleClass; } - /** - * @see Rule#setRuleClass(String) - */ @Override public void setRuleClass(String ruleClass) { this.ruleClass = ruleClass; } - /** - * @see Rule#getRuleSetName() - */ @Override public String getRuleSetName() { return ruleSetName; } - /** - * @see Rule#setRuleSetName(String) - */ @Override public void setRuleSetName(String ruleSetName) { this.ruleSetName = ruleSetName; } - /** - * @see Rule#getMessage() - */ @Override public String getMessage() { return message; } - /** - * @see Rule#setMessage(String) - */ @Override public void setMessage(String message) { this.message = message; } - /** - * @see Rule#getDescription() - */ @Override public String getDescription() { return description; } - /** - * @see Rule#setDescription(String) - */ @Override public void setDescription(String description) { this.description = description; } - /** - * @see Rule#getExamples() - */ @Override public List<String> getExamples() { // TODO Needs to be externally immutable return examples; } - /** - * @see Rule#addExample(String) - */ @Override public void addExample(String example) { examples.add(example); } - /** - * @see Rule#getExternalInfoUrl() - */ @Override public String getExternalInfoUrl() { return externalInfoUrl; } - /** - * @see Rule#setExternalInfoUrl(String) - */ @Override public void setExternalInfoUrl(String externalInfoUrl) { this.externalInfoUrl = externalInfoUrl; } - /** - * @see Rule#getPriority() - */ @Override public RulePriority getPriority() { return priority; } - /** - * @see Rule#setPriority(RulePriority) - */ @Override public void setPriority(RulePriority priority) { this.priority = priority; @@ -302,74 +233,88 @@ public ParserOptions getParserOptions() { return new ParserOptions(); } - /** - * @see Rule#setUsesDFA() - */ @Override + @Deprecated // To be removed in PMD 7.0.0 public void setUsesDFA() { - usesDFA = true; + setDfa(true); + } + + @Override + public void setDfa(boolean isDfa) { + usesDFA = isDfa; } - /** - * @see Rule#usesDFA() - */ @Override + @Deprecated // To be removed in PMD 7.0.0 public boolean usesDFA() { + return isDfa(); + } + + @Override + public boolean isDfa() { return usesDFA; } - /** - * @see Rule#setUsesTypeResolution() - */ @Override + @Deprecated // To be removed in PMD 7.0.0 public void setUsesTypeResolution() { - usesTypeResolution = true; + setTypeResolution(true); + } + + @Override + public void setTypeResolution(boolean usingTypeResolution) { + usesTypeResolution = usingTypeResolution; } - /** - * @see Rule#usesTypeResolution() - */ @Override + @Deprecated // To be removed in PMD 7.0.0 public boolean usesTypeResolution() { + return isTypeResolution(); + } + + @Override + public boolean isTypeResolution() { return usesTypeResolution; } - /** - * @see Rule#setUsesMetrics() - */ @Override - public void setUsesMetrics() { - usesMetrics = true; + @Deprecated // To be removed in PMD 7.0.0 + public void setUsesMultifile() { + setMultifile(true); } - /** - * @see Rule#usesMetrics() - */ @Override - public boolean usesMetrics() { - return usesMetrics; + public void setMultifile(boolean multifile) { + usesMultifile = multifile; } + @Override + @Deprecated // To be removed in PMD 7.0.0 + public boolean usesMultifile() { + return isMultifile(); + } - /** - * @see Rule#usesRuleChain() - */ @Override + public boolean isMultifile() { + return usesMultifile; + } + + @Override + @Deprecated // To be removed in PMD 7.0.0 public boolean usesRuleChain() { + return isRuleChain(); + } + + @Override + public boolean isRuleChain() { return !getRuleChainVisits().isEmpty(); } - /** - * @see Rule#getRuleChainVisits() - */ @Override public List<String> getRuleChainVisits() { return ruleChainVisits; } - /** - * @see Rule#addRuleChainVisit(Class) - */ @Override public void addRuleChainVisit(Class<? extends Node> nodeClass) { if (!nodeClass.getSimpleName().startsWith("AST")) { @@ -378,9 +323,6 @@ public void addRuleChainVisit(Class<? extends Node> nodeClass) { addRuleChainVisit(nodeClass.getSimpleName().substring("AST".length())); } - /** - * @see Rule#addRuleChainVisit(String) - */ @Override public void addRuleChainVisit(String astNodeName) { if (!ruleChainVisits.contains(astNodeName)) { @@ -388,17 +330,11 @@ public void addRuleChainVisit(String astNodeName) { } } - /** - * @see Rule#start(RuleContext) - */ @Override public void start(RuleContext ctx) { // Override as needed } - /** - * @see Rule#end(RuleContext) - */ @Override public void end(RuleContext ctx) { // Override as needed @@ -494,9 +430,6 @@ public boolean equals(Object o) { return equality; } - /** - * @see #equals(Object) - */ @Override public int hashCode() { Object propertyValues = getPropertiesByPropertyDescriptor(); @@ -510,8 +443,9 @@ public Rule deepCopy() { Rule rule = null; try { rule = getClass().newInstance(); - } catch (InstantiationException | IllegalAccessException e) { + } catch (InstantiationException | IllegalAccessException ignored) { // Can't happen... we already have an instance + throw new RuntimeException(ignored); // in case it happens anyway, something is really wrong... } rule.setName(getName()); rule.setLanguage(getLanguage()); @@ -521,22 +455,17 @@ public Rule deepCopy() { rule.setMessage(getMessage()); rule.setRuleSetName(getRuleSetName()); rule.setExternalInfoUrl(getExternalInfoUrl()); - if (usesDFA()) { - rule.setUsesDFA(); - } - if (usesTypeResolution()) { - rule.setUsesTypeResolution(); - } - if (usesMetrics()) { - rule.setUsesMetrics(); - } + rule.setDfa(isDfa()); + rule.setTypeResolution(isTypeResolution()); + rule.setMultifile(isMultifile()); rule.setDescription(getDescription()); for (final String example : getExamples()) { rule.addExample(example); } rule.setPriority(getPriority()); for (final PropertyDescriptor<?> prop : getPropertyDescriptors()) { - if (!rule.hasDescriptor(prop)) { + // define the descriptor only if it doesn't yet exist + if (rule.getPropertyDescriptor(prop.name()) == null) { rule.definePropertyDescriptor(prop); // Property descriptors are immutable, and can be freely shared } 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 176e1f7c351..bb3c5fb3d81 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,12 +12,16 @@ 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; -import net.sourceforge.pmd.benchmark.Benchmark; -import net.sourceforge.pmd.benchmark.Benchmarker; +import net.sourceforge.pmd.benchmark.TimeTracker; +import net.sourceforge.pmd.benchmark.TimedOperation; +import net.sourceforge.pmd.benchmark.TimedOperationCategory; import net.sourceforge.pmd.lang.ast.Node; /** @@ -26,6 +30,8 @@ * 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. @@ -59,41 +65,52 @@ public void visitAll(List<Node> nodes, RuleContext ctx) { // Perform a visitation of the AST to index nodes which need visiting by // type - long start = System.nanoTime(); - indexNodes(nodes, ctx); - long end = System.nanoTime(); - Benchmarker.mark(Benchmark.RuleChainVisit, end - start, 1); + try (TimedOperation to = TimeTracker.startOperation(TimedOperationCategory.RULECHAIN_VISIT)) { + indexNodes(nodes, ctx); + } // For each RuleSet, only if this source file applies - for (Map.Entry<RuleSet, List<Rule>> entry : ruleSetRules.entrySet()) { - RuleSet ruleSet = entry.getKey(); - if (!ruleSet.applies(ctx.getSourceCodeFile())) { - continue; - } - - // For each rule, allow it to visit the nodes it desires - start = System.nanoTime(); - for (Rule rule : entry.getValue()) { - int visits = 0; - if (!RuleSet.applies(rule, ctx.getLanguageVersion())) { + try (TimedOperation to = TimeTracker.startOperation(TimedOperationCategory.RULECHAIN_RULE)) { + for (Map.Entry<RuleSet, List<Rule>> entry : ruleSetRules.entrySet()) { + RuleSet ruleSet = entry.getKey(); + if (!ruleSet.applies(ctx.getSourceCodeFile())) { continue; } - final List<String> nodeNames = rule.getRuleChainVisits(); - for (int j = 0; j < nodeNames.size(); j++) { - List<Node> ns = nodeNameToNodes.get(nodeNames.get(j)); - for (Node node : ns) { - // Visit with underlying Rule, not the RuleReference - Rule actualRule = rule; - while (actualRule instanceof RuleReference) { - actualRule = ((RuleReference) actualRule).getRule(); + + // For each rule, allow it to visit the nodes it desires + for (Rule rule : entry.getValue()) { + int visits = 0; + if (!RuleSet.applies(rule, ctx.getLanguageVersion())) { + continue; + } + try (TimedOperation rcto = TimeTracker.startOperation(TimedOperationCategory.RULECHAIN_RULE, rule.getName())) { + final List<String> nodeNames = rule.getRuleChainVisits(); + for (int j = 0; j < nodeNames.size(); j++) { + List<Node> ns = nodeNameToNodes.get(nodeNames.get(j)); + for (Node node : ns) { + // Visit with underlying Rule, not the RuleReference + Rule actualRule = rule; + while (actualRule instanceof RuleReference) { + actualRule = ((RuleReference) actualRule).getRule(); + } + visit(actualRule, node, ctx); + } + 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; } - visit(actualRule, node, ctx); } - visits += ns.size(); } - end = System.nanoTime(); - Benchmarker.mark(Benchmark.RuleChainRule, rule.getName(), end - start, visits); - start = end; } } } @@ -112,7 +129,7 @@ public void visitAll(List<Node> nodes, RuleContext ctx) { * Index a single node for visitation by rules. */ protected void indexNode(Node node) { - List<Node> nodes = nodeNameToNodes.get(node.toString()); + List<Node> nodes = nodeNameToNodes.get(node.getXPathNodeName()); if (nodes != null) { nodes.add(node); } @@ -138,7 +155,7 @@ protected void initialize() { Map.Entry<RuleSet, List<Rule>> entry = entryIterator.next(); for (Iterator<Rule> ruleIterator = entry.getValue().iterator(); ruleIterator.hasNext();) { Rule rule = ruleIterator.next(); - if (rule.usesRuleChain()) { + if (rule.isRuleChain()) { visitedNodes.addAll(rule.getRuleChainVisits()); } else { // Drop rules which do not participate in the rule chain. diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/lang/rule/ImportWrapper.java b/pmd-core/src/main/java/net/sourceforge/pmd/lang/rule/ImportWrapper.java index adee83c7936..69b7e2e2297 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/lang/rule/ImportWrapper.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/lang/rule/ImportWrapper.java @@ -6,6 +6,7 @@ import java.lang.reflect.Field; import java.lang.reflect.Method; +import java.lang.reflect.Modifier; import java.util.HashSet; import java.util.Set; @@ -35,6 +36,18 @@ public ImportWrapper(String fullname, String name, Node node, Class<?> type, boo for (Field f : type.getFields()) { allDemands.add(f.getName()); } + // also consider static fields, that are not public + for (Field f : type.getDeclaredFields()) { + if (Modifier.isStatic(f.getModifiers())) { + allDemands.add(f.getName()); + } + } + // and methods, too + for (Method m : type.getDeclaredMethods()) { + if (Modifier.isStatic(m.getModifiers())) { + allDemands.add(m.getName()); + } + } } } @@ -101,6 +114,6 @@ public boolean isStaticOnDemand() { @Override public String toString() { - return "Import[name=" + name + ",fullname=" + fullname + ",static*=" + isStaticDemand + "]"; + return "Import[name=" + name + ",fullname=" + fullname + ",static*=" + isStaticDemand + ']'; } } diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/lang/rule/MockRule.java b/pmd-core/src/main/java/net/sourceforge/pmd/lang/rule/MockRule.java index f363ba3e037..8c0e15ea9d3 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/lang/rule/MockRule.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/lang/rule/MockRule.java @@ -23,7 +23,7 @@ public class MockRule extends AbstractRule { public MockRule() { super(); setLanguage(LanguageRegistry.getLanguage("Dummy")); - definePropertyDescriptor(new IntegerProperty("testIntProperty", "testIntProperty", 0, 100, 1, 0)); + definePropertyDescriptor(IntegerProperty.named("testIntProperty").desc("testIntProperty").range(0, 100).defaultValue(1).uiOrder(0).build()); } public MockRule(String name, String description, String message, String ruleSetName, RulePriority priority) { @@ -41,5 +41,6 @@ public MockRule(String name, String description, String message, String ruleSetN @Override public void apply(List<? extends Node> nodes, RuleContext ctx) { + // the mock rule does nothing. Usually you would start here to analyze the AST. } } diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/lang/rule/RuleReference.java b/pmd-core/src/main/java/net/sourceforge/pmd/lang/rule/RuleReference.java index e387ce154fc..15b2609f41e 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/lang/rule/RuleReference.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/lang/rule/RuleReference.java @@ -5,12 +5,12 @@ package net.sourceforge.pmd.lang.rule; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Objects; import net.sourceforge.pmd.Rule; import net.sourceforge.pmd.RulePriority; @@ -43,25 +43,59 @@ public class RuleReference extends AbstractDelegateRule { private RulePriority priority; private RuleSetReference ruleSetReference; - private static final List<PropertyDescriptor<?>> EMPTY_DESCRIPTORS = Collections.emptyList(); + /** + * @deprecated to be removed with PMD 7.0.0. when creating a rule reference, always + * provide the rule and the ruleset, see the constructor RuleReference(Rule, RuleSetReference) + */ + @Deprecated public RuleReference() { + // default constructor } + /** + * Create a new reference to the given rule. + * + * @param theRule the referenced rule + * @param theRuleSetReference the rule set, where the rule is defined + */ public RuleReference(Rule theRule, RuleSetReference theRuleSetReference) { setRule(theRule); ruleSetReference = theRuleSetReference; } + + /** copy constructor */ + private RuleReference(RuleReference ref) { + + this.language = ref.language; + this.minimumLanguageVersion = ref.minimumLanguageVersion; + this.maximumLanguageVersion = ref.maximumLanguageVersion; + this.deprecated = ref.deprecated; + this.name = ref.name; + this.propertyDescriptors = ref.propertyDescriptors; + this.propertyValues = ref.propertyValues == null ? null : new HashMap<>(ref.propertyValues); + this.message = ref.message; + this.description = ref.description; + this.examples = ref.examples == null ? null : new ArrayList<>(ref.examples); + this.externalInfoUrl = ref.externalInfoUrl; + this.priority = ref.priority; + this.ruleSetReference = ref.ruleSetReference; + + this.setRule(ref.getRule().deepCopy()); + } + public Language getOverriddenLanguage() { return language; } + // FIXME should we really allow overriding the language of a rule? + // I don't see any case where this wouldn't just make the rule fail during execution @Override public void setLanguage(Language language) { // Only override if different than current value, or if already // overridden. - if (!isSame(language, super.getLanguage()) || this.language != null) { + if (!Objects.equals(language, super.getLanguage()) || this.language != null) { this.language = language; super.setLanguage(language); } @@ -75,7 +109,7 @@ public LanguageVersion getOverriddenMinimumLanguageVersion() { public void setMinimumLanguageVersion(LanguageVersion minimumLanguageVersion) { // Only override if different than current value, or if already // overridden. - if (!isSame(minimumLanguageVersion, super.getMinimumLanguageVersion()) || this.minimumLanguageVersion != null) { + if (!Objects.equals(minimumLanguageVersion, super.getMinimumLanguageVersion()) || this.minimumLanguageVersion != null) { this.minimumLanguageVersion = minimumLanguageVersion; super.setMinimumLanguageVersion(minimumLanguageVersion); } @@ -89,7 +123,7 @@ public LanguageVersion getOverriddenMaximumLanguageVersion() { public void setMaximumLanguageVersion(LanguageVersion maximumLanguageVersion) { // Only override if different than current value, or if already // overridden. - if (!isSame(maximumLanguageVersion, super.getMaximumLanguageVersion()) || this.maximumLanguageVersion != null) { + if (!Objects.equals(maximumLanguageVersion, super.getMaximumLanguageVersion()) || this.maximumLanguageVersion != null) { this.maximumLanguageVersion = maximumLanguageVersion; super.setMaximumLanguageVersion(maximumLanguageVersion); } @@ -101,7 +135,7 @@ public Boolean isOverriddenDeprecated() { @Override public boolean isDeprecated() { - return deprecated != null && deprecated.booleanValue(); + return deprecated != null && deprecated; } @Override @@ -170,6 +204,10 @@ public List<String> getOverriddenExamples() { @Override public void addExample(String example) { + // TODO Intuitively, if some examples are overridden (even with empty value), then + // I think we should discard the previous ones. If the rule needs new examples, + // then the previous ones are not relevant. + // TODO Meaningful override of examples is hard, because they are merely // a list of strings. How does one indicate override of a particular // value? Via index? Rule.setExample(int, String)? But the XML format @@ -220,9 +258,11 @@ public void setPriority(RulePriority priority) { } } - public List<PropertyDescriptor<?>> getOverriddenPropertyDescriptors() { - return propertyDescriptors == null ? EMPTY_DESCRIPTORS : propertyDescriptors; + @Override + public List<PropertyDescriptor<?>> getOverriddenPropertyDescriptors() { + return propertyDescriptors == null ? Collections.<PropertyDescriptor<?>>emptyList() + : new ArrayList<>(propertyDescriptors); } @Override @@ -237,14 +277,16 @@ public void definePropertyDescriptor(PropertyDescriptor<?> propertyDescriptor) t propertyDescriptors.add(propertyDescriptor); } + + @Override public Map<PropertyDescriptor<?>, Object> getOverriddenPropertiesByPropertyDescriptor() { - return propertyValues; + return propertyValues == null ? new HashMap<PropertyDescriptor<?>, Object>() : new HashMap<>(propertyValues); } @Override public <T> void setProperty(PropertyDescriptor<T> propertyDescriptor, T value) { // Only override if different than current value. - if (!isSame(super.getProperty(propertyDescriptor), value)) { + if (!Objects.equals(super.getProperty(propertyDescriptor), value)) { if (propertyValues == null) { propertyValues = new HashMap<>(); } @@ -254,14 +296,15 @@ public <T> void setProperty(PropertyDescriptor<T> propertyDescriptor, T value) { } - - - - public RuleSetReference getRuleSetReference() { return ruleSetReference; } + + /** + * @deprecated There's no use in setting the ruleset reference after construction + */ + @Deprecated public void setRuleSetReference(RuleSetReference ruleSetReference) { this.ruleSetReference = ruleSetReference; } @@ -270,19 +313,6 @@ private static boolean isSame(String s1, String s2) { return StringUtil.isSame(s1, s2, true, false, true); } - @SuppressWarnings("PMD.CompareObjectsWithEquals") - private static boolean isSame(Object o1, Object o2) { - if (o1 instanceof Object[] && o2 instanceof Object[]) { - return isSame((Object[]) o1, (Object[]) o2); - } - return o1 == o2 || o1 != null && o2 != null && o1.equals(o2); - } - - @SuppressWarnings("PMD.UnusedNullCheckInEquals") - // TODO: fix UnusedNullCheckInEquals rule for Arrays - private static boolean isSame(Object[] a1, Object[] a2) { - return a1 == a2 || a1 != null && a2 != null && Arrays.equals(a1, a2); - } private static boolean contains(Collection<String> collection, String s1) { for (String s2 : collection) { @@ -299,11 +329,21 @@ public boolean hasDescriptor(PropertyDescriptor<?> descriptor) { || super.hasDescriptor(descriptor); } + /** + * @deprecated Use {@link #isPropertyOverridden(PropertyDescriptor)} instead + */ + @Deprecated public boolean hasOverriddenProperty(PropertyDescriptor<?> descriptor) { + return isPropertyOverridden(descriptor); + } + + @Override + public boolean isPropertyOverridden(PropertyDescriptor<?> descriptor) { return propertyValues != null && propertyValues.containsKey(descriptor); } @Override + @Deprecated public boolean usesDefaultValues() { List<PropertyDescriptor<?>> descriptors = getOverriddenPropertyDescriptors(); @@ -312,19 +352,16 @@ public boolean usesDefaultValues() { } for (PropertyDescriptor<?> desc : descriptors) { - if (!isSame(desc.defaultValue(), getProperty(desc))) { + if (!Objects.equals(desc.defaultValue(), getProperty(desc))) { return false; } } - if (!getRule().usesDefaultValues()) { - return false; - } - - return true; + return getRule().usesDefaultValues(); } @Override + @Deprecated public void useDefaultValueFor(PropertyDescriptor<?> desc) { // not sure if we should go all the way through to the real thing? @@ -343,28 +380,6 @@ public void useDefaultValueFor(PropertyDescriptor<?> desc) { @Override public Rule deepCopy() { - RuleReference rule = null; - try { - rule = getClass().newInstance(); - } catch (InstantiationException | IllegalAccessException e) { - // Can't happen... we already have an instance - } - rule.setRule(this.getRule().deepCopy()); - - rule.language = language; - rule.minimumLanguageVersion = minimumLanguageVersion; - rule.maximumLanguageVersion = maximumLanguageVersion; - rule.deprecated = deprecated; - rule.name = name; - rule.propertyDescriptors = propertyDescriptors; - rule.propertyValues = propertyValues == null ? null : new HashMap<>(propertyValues); - rule.message = message; - rule.description = description; - rule.examples = examples == null ? null : new ArrayList<>(examples); - rule.externalInfoUrl = externalInfoUrl; - rule.priority = priority; - rule.ruleSetReference = ruleSetReference; - - return rule; + return new RuleReference(this); } } diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/lang/rule/XPathRule.java b/pmd-core/src/main/java/net/sourceforge/pmd/lang/rule/XPathRule.java index a2d4852f70e..f990dfab74a 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/lang/rule/XPathRule.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/lang/rule/XPathRule.java @@ -21,17 +21,20 @@ import net.sourceforge.pmd.lang.rule.xpath.SaxonXPathRuleQuery; import net.sourceforge.pmd.lang.rule.xpath.XPathRuleQuery; import net.sourceforge.pmd.properties.EnumeratedProperty; -import net.sourceforge.pmd.properties.PropertySource; import net.sourceforge.pmd.properties.StringProperty; /** * Rule that tries to match an XPath expression against a DOM view of an AST. * - * <p>This rule needs a "xpath" property value in order to function. + * <p>This rule needs a "xpath" property value in order to function.</p> */ public class XPathRule extends AbstractRule { - public static final StringProperty XPATH_DESCRIPTOR = new StringProperty("xpath", "XPath expression", "", 1.0f); + public static final StringProperty XPATH_DESCRIPTOR = StringProperty.named("xpath") + .desc("XPath expression") + .defaultValue("") + .uiOrder(1.0f) + .build(); private static final Map<String, String> XPATH_VERSIONS; @@ -43,29 +46,45 @@ public class XPathRule extends AbstractRule { XPATH_VERSIONS = Collections.unmodifiableMap(tmp); } + public static final EnumeratedProperty<String> VERSION_DESCRIPTOR = EnumeratedProperty.<String>named("version") + .desc("XPath specification version") + .mappings(XPATH_VERSIONS) + .defaultValue(XPATH_1_0) + .type(String.class) + .uiOrder(2.0f) + .build(); - - public static final EnumeratedProperty<String> VERSION_DESCRIPTOR - = new EnumeratedProperty<>("version", - "XPath specification version", XPATH_VERSIONS, XPATH_1_0, String.class, 2.0f); - + /** + * This is initialized only once when calling {@link #evaluate(Node, RuleContext)} or {@link #getRuleChainVisits()}. + */ private XPathRuleQuery xpathRuleQuery; + /** + * Creates a new XPathRule without the corresponding XPath query. + */ public XPathRule() { definePropertyDescriptor(XPATH_DESCRIPTOR); definePropertyDescriptor(VERSION_DESCRIPTOR); } - public XPathRule(String xPath) { + /** + * Creates a new XPathRule and associates the XPath query. + */ + public XPathRule(final String xPath) { this(); setXPath(xPath); } - public void setXPath(String xPath) { + /** + * Sets the XPath to query against the desired nodes in {@link #apply(List, RuleContext)}. + * + * @param xPath the XPath query + */ + public void setXPath(final String xPath) { setProperty(XPathRule.XPATH_DESCRIPTOR, xPath); } - public void setVersion(String version) { + public void setVersion(final String version) { setProperty(XPathRule.VERSION_DESCRIPTOR, version); } @@ -80,28 +99,55 @@ public void apply(List<? extends Node> nodes, RuleContext ctx) { } /** - * Evaluate the XPath query with the AST node. All matches are reported as - * violations. + * Evaluate the XPath query with the AST node. All matches are reported as violations. * - * @param node - * The Node that to be checked. - * @param data - * The RuleContext. + * @param node The Node that to be checked. + * @param data The RuleContext. */ - public void evaluate(Node node, RuleContext data) { - init(); - List<Node> nodes = xpathRuleQuery.evaluate(node, data); - if (nodes != null) { - for (Node n : nodes) { - addViolation(data, n, n.getImage()); - } + public void evaluate(final Node node, final RuleContext data) { + if (xPathRuleQueryNeedsInitialization()) { + initXPathRuleQuery(); } + List<Node> nodesWithViolation = xpathRuleQuery.evaluate(node, data); + for (Node nodeWithViolation : nodesWithViolation) { + addViolation(data, nodeWithViolation, nodeWithViolation.getImage()); + } + } + + /** + * Initializes {@link #xpathRuleQuery} iff {@link #xPathRuleQueryNeedsInitialization()} is true. To select the + * engine in which the query will be run it looks at the XPath version. + */ + private void initXPathRuleQuery() { + String xpath = getProperty(XPATH_DESCRIPTOR); + String version = getProperty(VERSION_DESCRIPTOR); + + initRuleQueryBasedOnVersion(version); + + xpathRuleQuery.setXPath(xpath); + xpathRuleQuery.setVersion(version); + xpathRuleQuery.setProperties(getPropertiesByPropertyDescriptor()); + } + + /** + * Checks if the {@link #xpathRuleQuery} is null and therefore requires initialization. + * + * @return true if {@link #xpathRuleQuery} is null + */ + private boolean xPathRuleQueryNeedsInitialization() { + return xpathRuleQuery == null; + } + + private void initRuleQueryBasedOnVersion(final String version) { + xpathRuleQuery = XPATH_1_0.equals(version) ? new JaxenXPathRuleQuery() : new SaxonXPathRuleQuery(); } @Override public List<String> getRuleChainVisits() { - if (init()) { + if (xPathRuleQueryNeedsInitialization()) { + initXPathRuleQuery(); + for (String nodeName : xpathRuleQuery.getRuleChainVisits()) { super.addRuleChainVisit(nodeName); } @@ -109,32 +155,12 @@ public List<String> getRuleChainVisits() { return super.getRuleChainVisits(); } - private boolean init() { - if (xpathRuleQuery == null) { - String xpath = getProperty(XPATH_DESCRIPTOR); - String version = getProperty(VERSION_DESCRIPTOR); - if (XPATH_1_0.equals(version)) { - xpathRuleQuery = new JaxenXPathRuleQuery(); - } else { - xpathRuleQuery = new SaxonXPathRuleQuery(); - } - xpathRuleQuery.setXPath(xpath); - xpathRuleQuery.setVersion(version); - xpathRuleQuery.setProperties(this.getPropertiesByPropertyDescriptor()); - return true; - } - return false; - } - - public boolean hasXPathExpression() { - return StringUtils.isNotBlank(getProperty(XPATH_DESCRIPTOR)); - } - - /** - * @see PropertySource#dysfunctionReason() - */ @Override public String dysfunctionReason() { return hasXPathExpression() ? null : "Missing xPath expression"; } + + private boolean hasXPathExpression() { + return StringUtils.isNotBlank(getProperty(XPATH_DESCRIPTOR)); + } } diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/lang/rule/xpath/AbstractXPathRuleQuery.java b/pmd-core/src/main/java/net/sourceforge/pmd/lang/rule/xpath/AbstractXPathRuleQuery.java index ef39feeaac2..69c6d0afdf3 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/lang/rule/xpath/AbstractXPathRuleQuery.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/lang/rule/xpath/AbstractXPathRuleQuery.java @@ -37,17 +37,11 @@ public abstract class AbstractXPathRuleQuery implements XPathRuleQuery { */ protected final List<String> ruleChainVisits = new ArrayList<>(); - /** - * {@inheritDoc} - */ @Override - public void setXPath(String xpath) { + public void setXPath(final String xpath) { this.xpath = xpath; } - /** - * {@inheritDoc} - */ @Override public void setVersion(String version) throws UnsupportedOperationException { if (!isSupportedVersion(version)) { @@ -68,25 +62,16 @@ public void setVersion(String version) throws UnsupportedOperationException { */ protected abstract boolean isSupportedVersion(String version); - /** - * {@inheritDoc} - */ @Override public void setProperties(Map<PropertyDescriptor<?>, Object> properties) { this.properties = properties; } - /** - * {@inheritDoc} - */ @Override public List<String> getRuleChainVisits() { return ruleChainVisits; } - /** - * {@inheritDoc} - */ @Override public abstract List<Node> evaluate(Node node, RuleContext data); } diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/lang/rule/xpath/JaxenXPathRuleQuery.java b/pmd-core/src/main/java/net/sourceforge/pmd/lang/rule/xpath/JaxenXPathRuleQuery.java index 1faf9470426..1badb00042f 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/lang/rule/xpath/JaxenXPathRuleQuery.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/lang/rule/xpath/JaxenXPathRuleQuery.java @@ -8,7 +8,6 @@ import java.util.ArrayList; import java.util.Deque; import java.util.HashMap; -import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Map.Entry; @@ -52,117 +51,133 @@ private enum InitializationStatus { private static final String AST_ROOT = "_AST_ROOT_"; - /** - * {@inheritDoc} - */ @Override public boolean isSupportedVersion(String version) { return XPATH_1_0.equals(version); } - /** - * {@inheritDoc} - */ @Override - @SuppressWarnings("unchecked") - public List<Node> evaluate(Node node, RuleContext data) { - List<Node> results = new ArrayList<>(); + public List<Node> evaluate(final Node node, final RuleContext data) { + final List<Node> results = new ArrayList<>(); + try { - initializeXPathExpression( - data.getLanguageVersion().getLanguageVersionHandler().getXPathHandler().getNavigator()); - List<XPath> xpaths = nodeNameToXPaths.get(node.toString()); - if (xpaths == null) { - xpaths = nodeNameToXPaths.get(AST_ROOT); - } - for (XPath xpath : xpaths) { - List<Node> nodes = xpath.selectNodes(node); - results.addAll(nodes); + initializeExpressionIfStatusIsNoneOrPartial(data.getLanguageVersion().getLanguageVersionHandler().getXPathHandler().getNavigator()); + + List<XPath> xPaths = getXPathsForNodeOrDefault(node.getXPathNodeName()); + for (XPath xpath : xPaths) { + @SuppressWarnings("unchecked") + final List<Node> matchedNodes = xpath.selectNodes(node); + results.addAll(matchedNodes); } - } catch (JaxenException ex) { - throw new RuntimeException(ex); + } catch (final JaxenException e) { + throw new RuntimeException(e); } return results; } /** - * {@inheritDoc} + * Get the XPath queries associated with the node name. If there are none, the XPath queries for the {@link #AST_ROOT} + * are obtained. + * + * @param nodeName the id of the node + * @return the list of XPath queries that match the node name */ + private List<XPath> getXPathsForNodeOrDefault(final String nodeName) { + List<XPath> xPaths = nodeNameToXPaths.get(nodeName); + if (xPaths == null) { + xPaths = nodeNameToXPaths.get(AST_ROOT); + } + return xPaths; + } + @Override public List<String> getRuleChainVisits() { try { // No Navigator available in this context - initializeXPathExpression(null); + initializeExpressionIfStatusIsNoneOrPartial(null); return super.getRuleChainVisits(); - } catch (JaxenException ex) { + } catch (final JaxenException ex) { throw new RuntimeException(ex); } } + /** + * + * @param navigator the navigator which is required to be non-null if the {@link #initializationStatus} is PARTIAL. + * @throws JaxenException + */ @SuppressWarnings("unchecked") - private void initializeXPathExpression(Navigator navigator) throws JaxenException { + private void initializeExpressionIfStatusIsNoneOrPartial(final Navigator navigator) throws JaxenException { if (initializationStatus == InitializationStatus.FULL) { return; - } else if (initializationStatus == InitializationStatus.PARTIAL && navigator == null) { - if (LOG.isLoggable(Level.SEVERE)) { - LOG.severe("XPathRule is not initialized because no navigator was provided. " - + "Please make sure to implement getXPathHandler in the handler of the language. " - + "See also AbstractLanguageVersionHandler."); - } + } + if (initializationStatus == InitializationStatus.PARTIAL && navigator == null) { + LOG.severe("XPathRule is not initialized because no navigator was provided. " + + "Please make sure to implement getXPathHandler in the handler of the language. " + + "See also AbstractLanguageVersionHandler."); return; } + initializeXPathExpression(navigator); + } + + private void initializeXPathExpression(final Navigator navigator) throws JaxenException { + /* + Attempt to use the RuleChain with this XPath query. - // - // Attempt to use the RuleChain with this XPath query. To do so, the - // queries - // should generally look like //TypeA or //TypeA | //TypeB. We will look - // at the - // parsed XPath AST using the Jaxen APIs to make this determination. - // If the query is not exactly what we are looking for, do not use the - // RuleChain. - // + To do so, the queries should generally look like //TypeA or //TypeA | //TypeB. We will look at the parsed XPath + AST using the Jaxen APIs to make this determination. + + If the query is not exactly what we are looking for, do not use the + RuleChain. + */ nodeNameToXPaths = new HashMap<>(); - BaseXPath originalXPath = createXPath(xpath, navigator); - indexXPath(originalXPath, AST_ROOT); + final BaseXPath originalXPath = createXPath(xpath, navigator); + addQueryToNode(originalXPath, AST_ROOT); boolean useRuleChain = true; - Deque<Expr> pending = new ArrayDeque<>(); + final Deque<Expr> pending = new ArrayDeque<>(); pending.push(originalXPath.getRootExpr()); while (!pending.isEmpty()) { - Expr node = pending.pop(); + final Expr node = pending.pop(); // Need to prove we can handle this part of the query boolean valid = false; // Must be a LocationPath... that is something like //Type if (node instanceof LocationPath) { - LocationPath locationPath = (LocationPath) node; + final LocationPath locationPath = (LocationPath) node; if (locationPath.isAbsolute()) { // Should be at least two steps - List<Step> steps = locationPath.getSteps(); + @SuppressWarnings("unchecked") + final List<Step> steps = locationPath.getSteps(); + if (steps.size() >= 2) { - Step step1 = steps.get(0); - Step step2 = steps.get(1); + final Step step1 = steps.get(0); + final Step step2 = steps.get(1); // First step should be an AllNodeStep using the // descendant or self axis if (step1 instanceof AllNodeStep - && ((AllNodeStep) step1).getAxis() == Axis.DESCENDANT_OR_SELF) { + && step1.getAxis() == Axis.DESCENDANT_OR_SELF) { // Second step should be a NameStep using the child // axis. - if (step2 instanceof NameStep && ((NameStep) step2).getAxis() == Axis.CHILD) { + if (step2 instanceof NameStep && step2.getAxis() == Axis.CHILD) { // Construct a new expression that is // appropriate for RuleChain use - XPathFactory xpathFactory = new DefaultXPathFactory(); + final XPathFactory xpathFactory = new DefaultXPathFactory(); // Instead of an absolute location path, we'll // be using a relative path - LocationPath relativeLocationPath = xpathFactory.createRelativeLocationPath(); + final LocationPath relativeLocationPath = xpathFactory.createRelativeLocationPath(); // The first step will be along the self axis - Step allNodeStep = xpathFactory.createAllNodeStep(Axis.SELF); + final Step allNodeStep = xpathFactory.createAllNodeStep(Axis.SELF); // Retain all predicates from the original name // step - for (Iterator<Predicate> i = step2.getPredicates().iterator(); i.hasNext();) { - allNodeStep.addPredicate(i.next()); + @SuppressWarnings("unchecked") + final List<Predicate> predicates = step2.getPredicates(); + + for (Predicate predicate : predicates) { + allNodeStep.addPredicate(predicate); } relativeLocationPath.addStep(allNodeStep); @@ -172,8 +187,8 @@ private void initializeXPathExpression(Navigator navigator) throws JaxenExceptio relativeLocationPath.addStep(steps.get(i)); } - BaseXPath xpath = createXPath(relativeLocationPath.getText(), navigator); - indexXPath(xpath, ((NameStep) step2).getLocalName()); + final BaseXPath xpath = createXPath(relativeLocationPath.getText(), navigator); + addQueryToNode(xpath, ((NameStep) step2).getLocalName()); valid = true; } } @@ -200,9 +215,9 @@ private void initializeXPathExpression(Navigator navigator) throws JaxenExceptio } else { // Use original XPath if we cannot use the RuleChain nodeNameToXPaths.clear(); - indexXPath(originalXPath, AST_ROOT); + addQueryToNode(originalXPath, AST_ROOT); if (LOG.isLoggable(Level.FINE)) { - LOG.log(Level.FINE, "Unable to use RuleChain for for XPath: " + xpath); + LOG.log(Level.FINE, "Unable to use RuleChain for XPath: " + xpath); } } @@ -213,27 +228,32 @@ private void initializeXPathExpression(Navigator navigator) throws JaxenExceptio } else { this.initializationStatus = InitializationStatus.FULL; } - } - private void indexXPath(XPath xpath, String nodeName) { - List<XPath> xpaths = nodeNameToXPaths.get(nodeName); - if (xpaths == null) { - xpaths = new ArrayList<>(); - nodeNameToXPaths.put(nodeName, xpaths); + /** + * Relates an XPath query to a node by adding the query to the {@link #nodeNameToXPaths}. + * + * @param xPath the query to do over a node + * @param nodeName the node on which to do the query + */ + private void addQueryToNode(final XPath xPath, final String nodeName) { + List<XPath> xPathsForNode = nodeNameToXPaths.get(nodeName); + if (xPathsForNode == null) { + xPathsForNode = new ArrayList<>(); + nodeNameToXPaths.put(nodeName, xPathsForNode); } - xpaths.add(xpath); + xPathsForNode.add(xPath); } - private BaseXPath createXPath(String xpathQueryString, Navigator navigator) throws JaxenException { + private BaseXPath createXPath(final String xpathQueryString, final Navigator navigator) throws JaxenException { + final BaseXPath xpath = new BaseXPath(xpathQueryString, navigator); - BaseXPath xpath = new BaseXPath(xpathQueryString, navigator); if (properties.size() > 1) { - SimpleVariableContext vc = new SimpleVariableContext(); + final SimpleVariableContext vc = new SimpleVariableContext(); for (Entry<PropertyDescriptor<?>, Object> e : properties.entrySet()) { - String propName = e.getKey().name(); + final String propName = e.getKey().name(); if (!"xpath".equals(propName)) { - Object value = e.getValue(); + final Object value = e.getValue(); vc.setVariableValue(propName, value != null ? value.toString() : null); } } diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/lang/rule/xpath/SaxonXPathRuleQuery.java b/pmd-core/src/main/java/net/sourceforge/pmd/lang/rule/xpath/SaxonXPathRuleQuery.java index 042ead7aa08..ddbe17615b7 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/lang/rule/xpath/SaxonXPathRuleQuery.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/lang/rule/xpath/SaxonXPathRuleQuery.java @@ -8,18 +8,16 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.regex.Pattern; import net.sourceforge.pmd.RuleContext; import net.sourceforge.pmd.lang.ast.Node; import net.sourceforge.pmd.lang.ast.xpath.saxon.DocumentNode; import net.sourceforge.pmd.lang.ast.xpath.saxon.ElementNode; import net.sourceforge.pmd.lang.xpath.Initializer; -import net.sourceforge.pmd.properties.BooleanProperty; -import net.sourceforge.pmd.properties.EnumeratedProperty; -import net.sourceforge.pmd.properties.IntegerProperty; import net.sourceforge.pmd.properties.PropertyDescriptor; -import net.sourceforge.pmd.properties.StringProperty; +import net.sf.saxon.om.Item; import net.sf.saxon.om.ValueRepresentation; import net.sf.saxon.sxpath.AbstractStaticContext; import net.sf.saxon.sxpath.IndependentContext; @@ -29,108 +27,131 @@ import net.sf.saxon.sxpath.XPathStaticContext; import net.sf.saxon.sxpath.XPathVariable; import net.sf.saxon.trans.XPathException; +import net.sf.saxon.value.AtomicValue; +import net.sf.saxon.value.BigIntegerValue; import net.sf.saxon.value.BooleanValue; +import net.sf.saxon.value.DoubleValue; +import net.sf.saxon.value.EmptySequence; +import net.sf.saxon.value.FloatValue; import net.sf.saxon.value.Int64Value; +import net.sf.saxon.value.SequenceExtent; import net.sf.saxon.value.StringValue; +import net.sf.saxon.value.UntypedAtomicValue; /** * This is a Saxon based XPathRule query. */ public class SaxonXPathRuleQuery extends AbstractXPathRuleQuery { - // Mapping from Node name to applicable XPath queries - private XPathExpression xpathExpression; - private List<XPathVariable> xpathVariables; - private static final int MAX_CACHE_SIZE = 20; private static final Map<Node, DocumentNode> CACHE = new LinkedHashMap<Node, DocumentNode>(MAX_CACHE_SIZE) { private static final long serialVersionUID = -7653916493967142443L; + @Override protected boolean removeEldestEntry(final Map.Entry<Node, DocumentNode> eldest) { return size() > MAX_CACHE_SIZE; } }; /** - * {@inheritDoc} + * Representation of an XPath query, created at {@link #initializeXPathExpression()} using {@link #xpath}. + */ + private XPathExpression xpathExpression; + + /** + * Holds the static context later used to match the variables in the dynamic context in + * {@link #createDynamicContext(ElementNode)}. Created at {@link #initializeXPathExpression()} + * using the properties descriptors in {@link #properties}. */ + private List<XPathVariable> xpathVariables; + @Override public boolean isSupportedVersion(String version) { return XPATH_1_0_COMPATIBILITY.equals(version) || XPATH_2_0.equals(version); } - /** - * {@inheritDoc} - */ @Override @SuppressWarnings("unchecked") - public List<Node> evaluate(Node node, RuleContext data) { + public List<Node> evaluate(final Node node, final RuleContext data) { initializeXPathExpression(); - List<Node> results = new ArrayList<>(); try { - // Get the DocumentNode for the AST - DocumentNode documentNode = getDocumentNode(node); - - // Get the corresponding ElementNode for this node. - ElementNode rootElementNode = documentNode.nodeToElementNode.get(node); - - // Create a dynamic context for this node - XPathDynamicContext xpathDynamicContext = xpathExpression.createDynamicContext(rootElementNode); - - // Set variable values on the dynamic context - for (XPathVariable xpathVariable : xpathVariables) { - String name = xpathVariable.getVariableQName().getLocalName(); - for (Map.Entry<PropertyDescriptor<?>, Object> entry : super.properties.entrySet()) { - if (name.equals(entry.getKey().name())) { - PropertyDescriptor<?> propertyDescriptor = entry.getKey(); - Object value = entry.getValue(); - ValueRepresentation valueRepresentation; - - // TODO Need to handle null values? - // TODO Need to handle more PropertyDescriptors, is - // there an easy factory in Saxon we can use for this? - if (propertyDescriptor instanceof StringProperty) { - valueRepresentation = new StringValue((String) value); - } else if (propertyDescriptor instanceof BooleanProperty) { - valueRepresentation = BooleanValue.get((Boolean) value); - } else if (propertyDescriptor instanceof IntegerProperty) { - valueRepresentation = Int64Value.makeIntegerValue((Integer) value); - } else if (propertyDescriptor instanceof EnumeratedProperty) { - if (value instanceof String) { - valueRepresentation = new StringValue((String) value); - } else { - throw new RuntimeException( - "Unable to create ValueRepresentaton for non-String EnumeratedProperty value: " - + value); - } - } else { - throw new RuntimeException("Unable to create ValueRepresentaton for PropertyDescriptor: " - + propertyDescriptor); - } - xpathDynamicContext.setVariable(xpathVariable, valueRepresentation); - } - } - } + final DocumentNode documentNode = getDocumentNodeForRootNode(node); - List<ElementNode> nodes = xpathExpression.evaluate(xpathDynamicContext); - for (ElementNode elementNode : nodes) { + // Map AST Node -> Saxon Node + final ElementNode rootElementNode = documentNode.nodeToElementNode.get(node); + + final XPathDynamicContext xpathDynamicContext = createDynamicContext(rootElementNode); + final List<ElementNode> nodes = xpathExpression.evaluate(xpathDynamicContext); + + /* + Map List of Saxon Nodes -> List of AST Nodes, which were detected to match the XPath expression + (i.e. violation found) + */ + final List<Node> results = new ArrayList<>(); + for (final ElementNode elementNode : nodes) { results.add((Node) elementNode.getUnderlyingNode()); } - } catch (XPathException e) { + return results; + } catch (final XPathException e) { throw new RuntimeException(super.xpath + " had problem: " + e.getMessage(), e); } - return results; } - private DocumentNode getDocumentNode(Node node) { - // Get the root AST node - Node root = node; - while (root.jjtGetParent() != null) { - root = root.jjtGetParent(); + /** + * Attempt to create a dynamic context on which to evaluate the {@link #xpathExpression}. + * + * @param elementNode the node on which to create the context; generally this node is the root node of the Saxon + * Tree + * @return the dynamic context on which to run the query + * @throws XPathException if the supplied value does not conform to the required type of the + * variable, when setting up the dynamic context; or if the supplied value contains a node that does not belong to + * this Configuration (or another Configuration that shares the same namePool) + */ + private XPathDynamicContext createDynamicContext(final ElementNode elementNode) throws XPathException { + final XPathDynamicContext dynamicContext = xpathExpression.createDynamicContext(elementNode); + + // Set variable values on the dynamic context + for (final XPathVariable xpathVariable : xpathVariables) { + final String variableName = xpathVariable.getVariableQName().getLocalName(); + for (final Map.Entry<PropertyDescriptor<?>, Object> entry : super.properties.entrySet()) { + if (variableName.equals(entry.getKey().name())) { + final ValueRepresentation valueRepresentation = getRepresentation(entry.getKey(), entry.getValue()); + dynamicContext.setVariable(xpathVariable, valueRepresentation); + } + } } + return dynamicContext; + } + + + private ValueRepresentation getRepresentation(final PropertyDescriptor<?> descriptor, final Object value) { + if (descriptor.isMultiValue()) { + final List<?> val = (List<?>) value; + if (val.isEmpty()) { + return EmptySequence.getInstance(); + } + final Item[] converted = new Item[val.size()]; + for (int i = 0; i < val.size(); i++) { + converted[i] = getAtomicRepresentation(val.get(i)); + } + return new SequenceExtent(converted); + } else { + return getAtomicRepresentation(value); + } + } + + /** + * Gets the DocumentNode representation for the whole AST in which the node is, that is, if the node is not the root + * of the AST, then the AST is traversed all the way up until the root node is found. If the DocumentNode was + * cached because this method was previously called, then a new DocumentNode will not be instanced. + * + * @param node the node from which the root node will be looked for. + * @return the DocumentNode representing the whole AST + */ + private DocumentNode getDocumentNodeForRootNode(final Node node) { + final Node root = getRootNode(node); - // Cache DocumentNode trees, so that different XPath queries can re-use them. DocumentNode documentNode; synchronized (CACHE) { documentNode = CACHE.get(root); @@ -142,13 +163,30 @@ private DocumentNode getDocumentNode(Node node) { return documentNode; } + /** + * Traverse the AST until the root node is found. + * + * @param node the node from where to start traversing the tree + * @return the root node + */ + private Node getRootNode(final Node node) { + Node root = node; + while (root.jjtGetParent() != null) { + root = root.jjtGetParent(); + } + return root; + } + + /** + * Initialize the {@link #xpathExpression} and the {@link #xpathVariables}. + */ private void initializeXPathExpression() { if (xpathExpression != null) { return; } try { - XPathEvaluator xpathEvaluator = new XPathEvaluator(); - XPathStaticContext xpathStaticContext = xpathEvaluator.getStaticContext(); + final XPathEvaluator xpathEvaluator = new XPathEvaluator(); + final XPathStaticContext xpathStaticContext = xpathEvaluator.getStaticContext(); // Enable XPath 1.0 compatibility if (XPATH_1_0_COMPATIBILITY.equals(version)) { @@ -158,14 +196,16 @@ private void initializeXPathExpression() { // Register PMD functions Initializer.initialize((IndependentContext) xpathStaticContext); - // Create XPathVariables for later use. It is a Saxon quirk that - // XPathVariables must be defined on the static context, and - // reused later to associate an actual value on the dynamic context. + /* + Create XPathVariables for later use. It is a Saxon quirk that XPathVariables must be defined on the + static context, and reused later to associate an actual value on the dynamic context creation, in + createDynamicContext(ElementNode). + */ xpathVariables = new ArrayList<>(); - for (PropertyDescriptor<?> propertyDescriptor : super.properties.keySet()) { - String name = propertyDescriptor.name(); + for (final PropertyDescriptor<?> propertyDescriptor : super.properties.keySet()) { + final String name = propertyDescriptor.name(); if (!"xpath".equals(name)) { - XPathVariable xpathVariable = xpathStaticContext.declareVariable(null, name); + final XPathVariable xpathVariable = xpathStaticContext.declareVariable(null, name); xpathVariables.add(xpathVariable); } } @@ -174,8 +214,48 @@ private void initializeXPathExpression() { // an approach which used Jaxen's stuff, but that only works for // 1.0 compatibility mode. Rather do it right instead of kludging. xpathExpression = xpathEvaluator.createExpression(super.xpath); - } catch (XPathException e) { + } catch (final XPathException e) { throw new RuntimeException(e); } } + + + /** + * Gets the Saxon representation of the parameter, if its type corresponds + * to an XPath 2.0 atomic datatype. + * + * @param value The value to convert + * + * @return The converted AtomicValue + */ + public static AtomicValue getAtomicRepresentation(final Object value) { + + /* + FUTURE When supported, we should consider refactor this implementation to use Pattern Matching + (see http://openjdk.java.net/jeps/305) so that it looks clearer. + */ + if (value == null) { + return UntypedAtomicValue.ZERO_LENGTH_UNTYPED; + + } else if (value instanceof String) { + return new StringValue((String) value); + } else if (value instanceof Boolean) { + return BooleanValue.get((Boolean) value); + } else if (value instanceof Integer) { + return Int64Value.makeIntegerValue((Integer) value); + } else if (value instanceof Long) { + return new BigIntegerValue((Long) value); + } else if (value instanceof Double) { + return new DoubleValue((Double) value); + } else if (value instanceof Character) { + return new StringValue(value.toString()); + } else if (value instanceof Float) { + return new FloatValue((Float) value); + } else if (value instanceof Pattern) { + return new StringValue(String.valueOf(value)); + } else { + // We could maybe use UntypedAtomicValue + throw new RuntimeException("Unable to create ValueRepresentation for value of type: " + value.getClass()); + } + } } diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/lang/rule/xpath/XPathRuleQuery.java b/pmd-core/src/main/java/net/sourceforge/pmd/lang/rule/xpath/XPathRuleQuery.java index 6a2109cdfe5..dedd90deab9 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/lang/rule/xpath/XPathRuleQuery.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/lang/rule/xpath/XPathRuleQuery.java @@ -14,12 +14,14 @@ /** * This interface captures the logic needed by XPathRule to implement an XPath * based query on an AST Node. + * * <p> * Implementations of this class do not need to be thread-safe, but they will be * reused to query against different AST Nodes. Therefore, internal state should * be maintained in a fashion consistent with reuse. Further, implementations * are recommended to manage internal state that is invariant over AST Nodes in * a fashion which facilities high performance (e.g. caching). + * </p> */ public interface XPathRuleQuery { diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/lang/xpath/Initializer.java b/pmd-core/src/main/java/net/sourceforge/pmd/lang/xpath/Initializer.java index bb69fd43f88..c73ef828a7b 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/lang/xpath/Initializer.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/lang/xpath/Initializer.java @@ -4,6 +4,7 @@ package net.sourceforge.pmd.lang.xpath; +import net.sourceforge.pmd.annotation.InternalApi; import net.sourceforge.pmd.lang.Language; import net.sourceforge.pmd.lang.LanguageRegistry; import net.sourceforge.pmd.lang.LanguageVersion; @@ -16,8 +17,12 @@ * initialization. For example, initializing custom Jaxen Functions. * Initialization should be performed before any XPath related operations are * performed. + * + * @deprecated Is internal API */ -public class Initializer { +@InternalApi +@Deprecated +public final class Initializer { private Initializer() { } diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/lang/xpath/MatchesFunction.java b/pmd-core/src/main/java/net/sourceforge/pmd/lang/xpath/MatchesFunction.java index 67493ca9c50..252ed9bdece 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/lang/xpath/MatchesFunction.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/lang/xpath/MatchesFunction.java @@ -14,9 +14,12 @@ import org.jaxen.SimpleFunctionContext; import org.jaxen.XPathFunctionContext; +import net.sourceforge.pmd.annotation.InternalApi; import net.sourceforge.pmd.lang.ast.xpath.Attribute; // FIXME Can this function be extended to work on non-AST attributes? +@InternalApi +@Deprecated public class MatchesFunction implements Function { public static void registerSelfInSimpleContext() { diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/lang/xpath/PMDFunctions.java b/pmd-core/src/main/java/net/sourceforge/pmd/lang/xpath/PMDFunctions.java index 2619c3e1e5d..5a26e7b75c8 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/lang/xpath/PMDFunctions.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/lang/xpath/PMDFunctions.java @@ -4,7 +4,12 @@ package net.sourceforge.pmd.lang.xpath; -public class PMDFunctions { +import net.sourceforge.pmd.annotation.InternalApi; + + +@InternalApi +@Deprecated +public final class PMDFunctions { private PMDFunctions() { } public static boolean matches(String s, String pattern1) { diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/processor/AbstractPMDProcessor.java b/pmd-core/src/main/java/net/sourceforge/pmd/processor/AbstractPMDProcessor.java index b16491a7593..412b8e0aab4 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/processor/AbstractPMDProcessor.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/processor/AbstractPMDProcessor.java @@ -5,17 +5,23 @@ package net.sourceforge.pmd.processor; import java.io.IOException; +import java.util.HashSet; import java.util.List; +import java.util.Set; +import java.util.logging.Level; +import java.util.logging.Logger; import net.sourceforge.pmd.PMDConfiguration; import net.sourceforge.pmd.Report; +import net.sourceforge.pmd.Rule; import net.sourceforge.pmd.RuleContext; import net.sourceforge.pmd.RuleSetFactory; import net.sourceforge.pmd.RuleSets; import net.sourceforge.pmd.RulesetsFactoryUtils; import net.sourceforge.pmd.SourceCodeProcessor; -import net.sourceforge.pmd.benchmark.Benchmark; -import net.sourceforge.pmd.benchmark.Benchmarker; +import net.sourceforge.pmd.benchmark.TimeTracker; +import net.sourceforge.pmd.benchmark.TimedOperation; +import net.sourceforge.pmd.benchmark.TimedOperationCategory; import net.sourceforge.pmd.renderers.Renderer; import net.sourceforge.pmd.util.datasource.DataSource; @@ -25,6 +31,8 @@ */ public abstract class AbstractPMDProcessor { + private static final Logger LOG = Logger.getLogger(AbstractPMDProcessor.class.getName()); + protected final PMDConfiguration configuration; public AbstractPMDProcessor(PMDConfiguration configuration) { @@ -33,16 +41,12 @@ public AbstractPMDProcessor(PMDConfiguration configuration) { public void renderReports(final List<Renderer> renderers, final Report report) { - long start = System.nanoTime(); - - try { + try (TimedOperation to = TimeTracker.startOperation(TimedOperationCategory.REPORTING)) { for (Renderer r : renderers) { r.renderFileReport(report); } - long end = System.nanoTime(); - Benchmarker.mark(Benchmark.Reporting, end - start, 1); } catch (IOException ioe) { - + throw new RuntimeException(ioe); } } @@ -56,26 +60,58 @@ protected String filenameFrom(DataSource dataSource) { * not</strong> be used by different threads. Each thread must create its * own copy of the rules. * - * @param factory + * @param factory The factory used to create the configured rule sets + * @param report The base report on which to report any configuration errors * @return the rules within a rulesets */ - protected RuleSets createRuleSets(RuleSetFactory factory) { - return RulesetsFactoryUtils.getRuleSets(configuration.getRuleSets(), factory); + protected RuleSets createRuleSets(RuleSetFactory factory, Report report) { + final RuleSets rs = RulesetsFactoryUtils.getRuleSets(configuration.getRuleSets(), factory); + + final Set<Rule> brokenRules = removeBrokenRules(rs); + for (final Rule rule : brokenRules) { + report.addConfigError(new Report.ConfigurationError(rule, rule.dysfunctionReason())); + } + + return rs; + } + + /** + * Remove and return the misconfigured rules from the rulesets and log them + * for good measure. + * + * @param ruleSets RuleSets to prune of broken rules. + * @return Set<Rule> + */ + private Set<Rule> removeBrokenRules(final RuleSets ruleSets) { + final Set<Rule> brokenRules = new HashSet<>(); + ruleSets.removeDysfunctionalRules(brokenRules); + + for (final Rule rule : brokenRules) { + if (LOG.isLoggable(Level.WARNING)) { + LOG.log(Level.WARNING, + "Removed misconfigured rule: " + rule.getName() + " cause: " + rule.dysfunctionReason()); + } + } + + return brokenRules; } public void processFiles(RuleSetFactory ruleSetFactory, List<DataSource> files, RuleContext ctx, List<Renderer> renderers) { - RuleSets rs = createRuleSets(ruleSetFactory); + RuleSets rs = createRuleSets(ruleSetFactory, ctx.getReport()); configuration.getAnalysisCache().checkValidity(rs, configuration.getClassLoader()); SourceCodeProcessor processor = new SourceCodeProcessor(configuration); for (DataSource dataSource : files) { String niceFileName = filenameFrom(dataSource); - runAnalysis(new PmdRunnable(configuration, dataSource, niceFileName, renderers, - ctx, ruleSetFactory, processor)); + runAnalysis(new PmdRunnable(dataSource, niceFileName, renderers, ctx, rs, processor)); } + // render base report first - general errors + renderReports(renderers, ctx.getReport()); + + // then add analysis results per file collectReports(renderers); } diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/processor/MultiThreadProcessor.java b/pmd-core/src/main/java/net/sourceforge/pmd/processor/MultiThreadProcessor.java index 68a68b6f716..f398a90db8a 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/processor/MultiThreadProcessor.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/processor/MultiThreadProcessor.java @@ -4,57 +4,51 @@ package net.sourceforge.pmd.processor; -import java.util.ArrayList; import java.util.List; import java.util.concurrent.CompletionService; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorCompletionService; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; -import java.util.concurrent.Future; -import java.util.concurrent.ThreadFactory; import net.sourceforge.pmd.PMDConfiguration; import net.sourceforge.pmd.Report; import net.sourceforge.pmd.renderers.Renderer; + /** * @author Romain Pelisse <belaran@gmail.com> - * */ public class MultiThreadProcessor extends AbstractPMDProcessor { + private final ExecutorService executor; + private final CompletionService<Report> completionService; - private ThreadFactory factory; - private ExecutorService executor; - private CompletionService<Report> completionService; - private List<Future<Report>> tasks = new ArrayList<>(); + private long submittedTasks = 0L; public MultiThreadProcessor(final PMDConfiguration configuration) { super(configuration); - factory = new PmdThreadFactory(); - executor = Executors.newFixedThreadPool(configuration.getThreads(), factory); + executor = Executors.newFixedThreadPool(configuration.getThreads(), new PmdThreadFactory()); completionService = new ExecutorCompletionService<>(executor); } @Override protected void runAnalysis(PmdRunnable runnable) { - // multi-threaded execution, dispatch analysis to worker threads - tasks.add(completionService.submit(runnable)); + completionService.submit(runnable); + submittedTasks++; } @Override protected void collectReports(List<Renderer> renderers) { - // Collect result analysis, waiting for termination if needed try { - for (int i = 0; i < tasks.size(); i++) { + for (int i = 0; i < submittedTasks; i++) { final Report report = completionService.take().get(); super.renderReports(renderers, report); } - } catch (InterruptedException ie) { + } catch (final InterruptedException ie) { Thread.currentThread().interrupt(); - } catch (ExecutionException ee) { - Throwable t = ee.getCause(); + } catch (final ExecutionException ee) { + final Throwable t = ee.getCause(); if (t instanceof RuntimeException) { throw (RuntimeException) t; } else if (t instanceof Error) { diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/processor/PmdRunnable.java b/pmd-core/src/main/java/net/sourceforge/pmd/processor/PmdRunnable.java index 4b416f8e9db..0adf3a8a4b4 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/processor/PmdRunnable.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/processor/PmdRunnable.java @@ -12,15 +12,12 @@ import java.util.logging.Level; import java.util.logging.Logger; -import net.sourceforge.pmd.PMD; -import net.sourceforge.pmd.PMDConfiguration; import net.sourceforge.pmd.PMDException; import net.sourceforge.pmd.Report; import net.sourceforge.pmd.RuleContext; -import net.sourceforge.pmd.RuleSetFactory; -import net.sourceforge.pmd.RuleSetNotFoundException; import net.sourceforge.pmd.RuleSets; import net.sourceforge.pmd.SourceCodeProcessor; +import net.sourceforge.pmd.benchmark.TimeTracker; import net.sourceforge.pmd.renderers.Renderer; import net.sourceforge.pmd.util.datasource.DataSource; @@ -30,23 +27,20 @@ public class PmdRunnable implements Callable<Report> { private static final ThreadLocal<ThreadContext> LOCAL_THREAD_CONTEXT = new ThreadLocal<>(); - private final PMDConfiguration configuration; private final DataSource dataSource; private final String fileName; private final List<Renderer> renderers; private final RuleContext ruleContext; - private final RuleSetFactory ruleSetFactory; + private final RuleSets ruleSets; private final SourceCodeProcessor sourceCodeProcessor; - public PmdRunnable(PMDConfiguration configuration, DataSource dataSource, String fileName, - List<Renderer> renderers, RuleContext ruleContext, RuleSetFactory ruleSetFactory, - SourceCodeProcessor sourceCodeProcessor) { - this.configuration = configuration; + public PmdRunnable(DataSource dataSource, String fileName, List<Renderer> renderers, + RuleContext ruleContext, RuleSets ruleSets, SourceCodeProcessor sourceCodeProcessor) { + this.ruleSets = ruleSets; this.dataSource = dataSource; this.fileName = fileName; this.renderers = renderers; this.ruleContext = ruleContext; - this.ruleSetFactory = ruleSetFactory; this.sourceCodeProcessor = sourceCodeProcessor; } @@ -62,23 +56,15 @@ private void addError(Report report, Exception e, String errorMessage) { @Override public Report call() { + TimeTracker.initThread(); + ThreadContext tc = LOCAL_THREAD_CONTEXT.get(); if (tc == null) { - try { - tc = new ThreadContext(ruleSetFactory.createRuleSets(configuration.getRuleSets()), - new RuleContext(ruleContext)); - } catch (RuleSetNotFoundException e) { - throw new RuntimeException(e); - } + tc = new ThreadContext(new RuleSets(ruleSets), new RuleContext(ruleContext)); LOCAL_THREAD_CONTEXT.set(tc); } - /* - * FIXME : This creates ConfigErrors for dysfunctional rules **per-thread**. - * We need rulesets to be copy-constructable and have them cleaned-up only once - * before analysis starts, reducing this to Report.createReport(tc.ruleContext, fileName); - */ - Report report = PMD.setupReport(tc.ruleSets, tc.ruleContext, fileName); + Report report = Report.createReport(tc.ruleContext, fileName); if (LOG.isLoggable(Level.FINE)) { LOG.fine("Processing " + tc.ruleContext.getSourceCodeFilename()); @@ -87,8 +73,7 @@ public Report call() { r.startFileAnalysis(dataSource); } - try { - InputStream stream = new BufferedInputStream(dataSource.getInputStream()); + try (InputStream stream = new BufferedInputStream(dataSource.getInputStream())) { tc.ruleContext.setLanguageVersion(null); sourceCodeProcessor.processSourceCode(stream, tc.ruleSets, tc.ruleContext); } catch (PMDException pmde) { @@ -99,6 +84,8 @@ public Report call() { addError(report, re, "RuntimeException during processing of " + fileName); } + TimeTracker.finishThread(); + return report; } diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/properties/AbstractMultiNumericProperty.java b/pmd-core/src/main/java/net/sourceforge/pmd/properties/AbstractMultiNumericProperty.java index 2d9bd655bd5..b72bb6b4cde 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/properties/AbstractMultiNumericProperty.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/properties/AbstractMultiNumericProperty.java @@ -9,12 +9,14 @@ import net.sourceforge.pmd.properties.modules.NumericPropertyModule; + /** * Base class for multi-valued numeric properties. * * @param <T> The type of number * * @author Brian Remedios + * @author Clément Fournier * @version Refactored June 2017 (6.0.0) */ /* default */ abstract class AbstractMultiNumericProperty<T extends Number> extends AbstractMultiValueProperty<T> @@ -41,6 +43,9 @@ super(theName, theDescription, theDefault, theUIOrder, isDefinedExternally); module = new NumericPropertyModule<>(lower, upper); + for (T num : theDefault) { + module.checkNumber(num); + } } diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/properties/AbstractMultiPackagedProperty.java b/pmd-core/src/main/java/net/sourceforge/pmd/properties/AbstractMultiPackagedProperty.java index a39cf3a7b46..a9183adcae9 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/properties/AbstractMultiPackagedProperty.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/properties/AbstractMultiPackagedProperty.java @@ -9,16 +9,18 @@ import net.sourceforge.pmd.properties.modules.PackagedPropertyModule; + /** * Multi-valued property restricting the type of its values to some packages. * * @param <T> The type of the values * * @author Brian Remedios + * @author Clément Fournier * @version Refactored June 2017 (6.0.0) */ /* default */ abstract class AbstractMultiPackagedProperty<T> extends AbstractMultiValueProperty<T> - implements PackagedPropertyDescriptor<List<T>> { + implements PackagedPropertyDescriptor<List<T>> { protected final PackagedPropertyModule<T> module; diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/properties/AbstractMultiValueProperty.java b/pmd-core/src/main/java/net/sourceforge/pmd/properties/AbstractMultiValueProperty.java index f016f17c3c1..514ed200033 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/properties/AbstractMultiValueProperty.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/properties/AbstractMultiValueProperty.java @@ -14,6 +14,7 @@ import net.sourceforge.pmd.Rule; + /** * Multi-valued property. * @@ -23,7 +24,7 @@ * @version 6.0.0 */ /* default */ abstract class AbstractMultiValueProperty<V> extends AbstractProperty<List<V>> - implements MultiValuePropertyDescriptor<V> { + implements MultiValuePropertyDescriptor<V> { /** The default value. */ @@ -118,6 +119,7 @@ private boolean defaultHasNullValue() { * * @return A string representation of the default value. */ + @Override protected String defaultAsString() { return asDelimitedString(defaultValue(), multiValueDelimiter()); } diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/properties/AbstractNumericProperty.java b/pmd-core/src/main/java/net/sourceforge/pmd/properties/AbstractNumericProperty.java index d942e02d60a..9c6c42f9d36 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/properties/AbstractNumericProperty.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/properties/AbstractNumericProperty.java @@ -8,17 +8,18 @@ import net.sourceforge.pmd.properties.modules.NumericPropertyModule; + /** - * Maintains a pair of boundary limit values between which all values managed by - * the subclasses must fit. + * Maintains a pair of boundary limit values between which all values managed by the subclasses must fit. * * @param <T> The type of value. * * @author Brian Remedios + * @author Clément Fournier * @version Refactored June 2017 (6.0.0) */ /* default */ abstract class AbstractNumericProperty<T extends Number> extends AbstractSingleValueProperty<T> - implements NumericPropertyDescriptor<T> { + implements NumericPropertyDescriptor<T> { private final NumericPropertyModule<T> module; @@ -42,8 +43,10 @@ protected AbstractNumericProperty(String theName, String theDescription, T lower super(theName, theDescription, theDefault, theUIOrder, isDefinedExternally); module = new NumericPropertyModule<>(lower, upper); - - + if (theDefault == null) { + return; // TODO: remove me when you scrap StatisticalRule (see pull #727) + } + module.checkNumber(theDefault); } diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/properties/AbstractPackagedProperty.java b/pmd-core/src/main/java/net/sourceforge/pmd/properties/AbstractPackagedProperty.java index d9aafbff697..3d498026a16 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/properties/AbstractPackagedProperty.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/properties/AbstractPackagedProperty.java @@ -8,17 +8,19 @@ import net.sourceforge.pmd.properties.modules.PackagedPropertyModule; + /** - * Property which restricts the type of its values to some packages. If - * the legalPackageNames value is set to null then no restrictions are made. + * Property which restricts the type of its values to some packages. If the legalPackageNames value is set to null then + * no restrictions are made. * * @param <T> The type of the values * * @author Brian Remedios + * @author Clément Fournier * @version Refactored June 2017 (6.0.0) */ /* default */ abstract class AbstractPackagedProperty<T> extends AbstractSingleValueProperty<T> - implements PackagedPropertyDescriptor<T> { + implements PackagedPropertyDescriptor<T> { protected final PackagedPropertyModule<T> module; diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/properties/AbstractProperty.java b/pmd-core/src/main/java/net/sourceforge/pmd/properties/AbstractProperty.java index 2b1650b651f..ab1815ddefe 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/properties/AbstractProperty.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/properties/AbstractProperty.java @@ -13,12 +13,14 @@ import org.apache.commons.lang3.StringUtils; + /** * Abstract class for properties. * * @param <T> The type of the property's value. This is a list type for multi-valued properties * * @author Brian Remedios + * @author Clément Fournier * @version Refactored June 2017 (6.0.0) */ /* default */ abstract class AbstractProperty<T> implements PropertyDescriptor<T> { @@ -50,14 +52,6 @@ protected AbstractProperty(String theName, String theDescription, float theUIOrd } - private static String checkNotEmpty(String arg, PropertyDescriptorField argId) throws IllegalArgumentException { - if (StringUtils.isBlank(arg)) { - throw new IllegalArgumentException("Property attribute '" + argId + "' cannot be null or blank"); - } - return arg; - } - - @Override public String description() { return description; @@ -106,9 +100,9 @@ public int hashCode() { @Override public String toString() { - return "[PropertyDescriptor: name=" + name() + "," - + " type=" + (isMultiValue() ? "List<" + type() + ">" : type()) + "," - + " value=" + defaultValue() + "]"; + return "[PropertyDescriptor: name=" + name() + ',' + + " type=" + (isMultiValue() ? "List<" + type() + '>' : type()) + ',' + + " value=" + defaultValue() + ']'; } @@ -127,8 +121,8 @@ public final Map<PropertyDescriptorField, String> attributeValuesById() { /** - * Adds this property's attributes to the map. Subclasses can override this to add more - * {@link PropertyDescriptorField}. + * Adds this property's attributes to the map. Subclasses can override this to add more {@link + * PropertyDescriptorField}. * * @param attributes The map to fill */ @@ -153,4 +147,12 @@ public boolean isDefinedExternally() { } + private static String checkNotEmpty(String arg, PropertyDescriptorField argId) throws IllegalArgumentException { + if (StringUtils.isBlank(arg)) { + throw new IllegalArgumentException("Property attribute '" + argId + "' cannot be null or blank"); + } + return arg; + } + + } diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/properties/AbstractPropertyDescriptorFactory.java b/pmd-core/src/main/java/net/sourceforge/pmd/properties/AbstractPropertyDescriptorFactory.java deleted file mode 100755 index e4fb024153c..00000000000 --- a/pmd-core/src/main/java/net/sourceforge/pmd/properties/AbstractPropertyDescriptorFactory.java +++ /dev/null @@ -1,312 +0,0 @@ -/** - * BSD-style license; for more info see http://pmd.sourceforge.net/license.html - */ - -package net.sourceforge.pmd.properties; - -import static net.sourceforge.pmd.properties.PropertyDescriptor.CORE_EXPECTED_FIELDS; -import static net.sourceforge.pmd.properties.PropertyDescriptorField.DEFAULT_VALUE; -import static net.sourceforge.pmd.properties.PropertyDescriptorField.DELIMITER; -import static net.sourceforge.pmd.properties.PropertyDescriptorField.DESCRIPTION; -import static net.sourceforge.pmd.properties.PropertyDescriptorField.LEGAL_PACKAGES; -import static net.sourceforge.pmd.properties.PropertyDescriptorField.MAX; -import static net.sourceforge.pmd.properties.PropertyDescriptorField.MIN; -import static net.sourceforge.pmd.properties.PropertyDescriptorField.NAME; - -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; -import java.util.Map.Entry; -import java.util.Set; - -import org.apache.commons.lang3.StringUtils; - -/** - * Basic implementation of a property descriptor factory. - * - * @param <T> The type of values property descriptor returned by this factory. This can be a list. - * - * @author Brian Remedios - */ -public abstract class AbstractPropertyDescriptorFactory<T> implements PropertyDescriptorFactory<T> { - - private final Class<?> valueType; - - /** - * Denote the identifiers of the expected fields paired with booleans - * denoting whether they are required or not. - */ - private final Map<PropertyDescriptorField, Boolean> expectedFields; - - - public AbstractPropertyDescriptorFactory(Class<?> theValueType) { - valueType = theValueType; - expectedFields = CORE_EXPECTED_FIELDS; - } - - - public AbstractPropertyDescriptorFactory(Class<?> theValueType, Map<PropertyDescriptorField, Boolean> additionalFieldTypesByKey) { - - valueType = theValueType; - if (additionalFieldTypesByKey == null) { - expectedFields = CORE_EXPECTED_FIELDS; - return; - } - Map<PropertyDescriptorField, Boolean> temp - = new HashMap<>(CORE_EXPECTED_FIELDS.size() + additionalFieldTypesByKey.size()); - temp.putAll(CORE_EXPECTED_FIELDS); - temp.putAll(additionalFieldTypesByKey); - - expectedFields = Collections.unmodifiableMap(temp); - } - - - @Override - public Class<?> valueType() { - return valueType; - } - - - @Override - public Set<PropertyDescriptorField> expectableFields() { - return Collections.unmodifiableSet(expectedFields.keySet()); - } - - - /** - * Retrieves the name of the descriptor from the map. - * - * @param valuesById Map of attributes - * - * @return The name, which is null if none is specified - */ - protected String nameIn(Map<PropertyDescriptorField, String> valuesById) { - return valuesById.get(NAME); - } - - - /** - * Retrieves the description from the map. - * - * @param valuesById Map of attributes - * - * @return The description, which is null if none is specified - */ - protected String descriptionIn(Map<PropertyDescriptorField, String> valuesById) { - return valuesById.get(DESCRIPTION); - } - - - /** - * Retrieves the default value from the map. - * - * @param valuesById Map of attributes - * - * @return The default value - * - * @throws RuntimeException if the default value is null, empty, or missing - */ - protected String defaultValueIn(Map<PropertyDescriptorField, String> valuesById) { - String deft = valuesById.get(DEFAULT_VALUE); - if (isValueMissing(deft)) { - throw new RuntimeException("Default value was null, empty, or missing"); - } - return deft; - } - - - @Override - public final PropertyDescriptor<T> createWith(Map<PropertyDescriptorField, String> valuesById) { - checkRequiredFields(valuesById); - return createWith(valuesById, false); - } - - - /** Checks whether all required fields are present in the map. */ - private void checkRequiredFields(Map<PropertyDescriptorField, String> valuesById) { - for (Entry<PropertyDescriptorField, Boolean> entry : expectedFields.entrySet()) { - if (entry.getValue() && isValueMissing(valuesById.get(entry.getKey()))) { - throw new RuntimeException("Missing required value for key: " + entry.getKey()); - } - } - } - - - /** - * Creates a new property descriptor specifying whether the descriptor is externally defined or not. This is - * meant to be implemented by subclasses. - * - * @param valuesById The map of values - * @param isExternallyDefined Whether the descriptor is externally defined - * - * @return A new and initialized {@link PropertyDescriptor} - */ - protected abstract PropertyDescriptor<T> createWith(Map<PropertyDescriptorField, String> valuesById, boolean isExternallyDefined); - - - /** - * Checks if the value is considered as missing or not. Some properties support whitespace values, hence the - * check. By default this does not support it. The factory can override this method to change the predicate. - * - * @param value The value to check - * - * @return True if the value must be considered missing, false otherwise - */ - protected boolean isValueMissing(String value) { - return StringUtils.isBlank(value); - } - - - /** - * Creates a new property descriptor which was defined externally. - * - * @param valuesById The map of values - * - * @return A new and initialized {@link PropertyDescriptor} - * - * @see PropertyDescriptor#isDefinedExternally() - */ - public final PropertyDescriptor<T> createExternalWith(Map<PropertyDescriptorField, String> valuesById) { - checkRequiredFields(valuesById); - return createWith(valuesById, true); - } - - - /** - * Gets the labels for enumerated properties, returns a string array of length 0 if none are specified. - * - * @param valuesById Map of attributes - * - * @return An array containing the labels - */ - protected static String[] labelsIn(Map<PropertyDescriptorField, String> valuesById) { - return StringUtils.split(valuesById.get(PropertyDescriptorField.LABELS), - MultiValuePropertyDescriptor.DEFAULT_DELIMITER); - } - - - // For enumerated properties - protected static Object[] choicesIn(Map<PropertyDescriptorField, String> valuesById) { - throw new UnsupportedOperationException(); // TODO: find a way to extract an arbitrary object from a string - // Maybe reason enough to only allow enums... - } - - - // For enumerated properties - protected static int indexIn(Map<PropertyDescriptorField, String> valuesById) { - throw new UnsupportedOperationException(); // TODO - } - - - // For enumerated properties - protected static Class<Object> classIn(Map<PropertyDescriptorField, String> valuesById) { - throw new UnsupportedOperationException(); // TODO - } - - - // For enumerated properties - protected static int[] indicesIn(Map<PropertyDescriptorField, String> valuesById) { - throw new UnsupportedOperationException(); // TODO - } - - - /** - * Finds the delimiter in the map, taking {@link MultiValuePropertyDescriptor#DEFAULT_DELIMITER} if none is - * mentioned. - * - * @param valuesById Map of attributes - * - * @return The delimiter or the default - */ - protected static char delimiterIn(Map<PropertyDescriptorField, String> valuesById) { - return delimiterIn(valuesById, MultiValuePropertyDescriptor.DEFAULT_DELIMITER); - } - - - /** - * Finds the delimiter in the map, taking the specified default delimiter if none is specified. - * - * @param valuesById Map of attributes - * @param defaultDelimiter The default delimiter to take - * - * @return The delimiter or the default - * - * @throws RuntimeException If the delimiter is present but is more than 1 character - */ - protected static char delimiterIn(Map<PropertyDescriptorField, String> valuesById, char defaultDelimiter) { - String characterStr = ""; - if (valuesById.containsKey(DELIMITER)) { - characterStr = valuesById.get(DELIMITER).trim(); - } - - if (StringUtils.isBlank(characterStr)) { - return defaultDelimiter; - } - - if (characterStr.length() != 1) { - throw new RuntimeException("Ambiguous delimiter character, must have length 1: \"" + characterStr + "\""); - } - return characterStr.charAt(0); - } - - - /** - * Retrieves the minimum and maximum values from the map. - * - * @param valuesById Map of attributes - * - * @return An array of 2 string, min at the left, max at the right - * - * @throws RuntimeException If one of them is missing - */ - protected static String[] minMaxFrom(Map<PropertyDescriptorField, String> valuesById) { - String min = minValueIn(valuesById); - String max = maxValueIn(valuesById); - if (StringUtils.isBlank(min) || StringUtils.isBlank(max)) { - throw new RuntimeException("min and max values must be specified"); - } - return new String[] {min, max}; - } - - - private static String minValueIn(Map<PropertyDescriptorField, String> valuesById) { - return valuesById.get(MIN); - } - - - private static String maxValueIn(Map<PropertyDescriptorField, String> valuesById) { - return valuesById.get(MAX); - } - - - protected static String[] legalPackageNamesIn(Map<PropertyDescriptorField, String> valuesById, char delimiter) { - String names = valuesById.get(LEGAL_PACKAGES); - if (StringUtils.isBlank(names)) { - return null; - } - return StringUtils.split(names, delimiter); - } - - - /** - * Returns a map describing which fields are required to build a property using this factory. The parameters are - * added to the {@link PropertyDescriptor#CORE_EXPECTED_FIELDS}, which are required for all descriptors. - * - * @param otherKeys Additional keys - * @param otherValues Whether the corresponding keys are required or not - * - * @return The complete map of expected fields. - */ - static Map<PropertyDescriptorField, Boolean> expectedFieldTypesWith(PropertyDescriptorField[] otherKeys, - Boolean[] otherValues) { - Map<PropertyDescriptorField, Boolean> largerMap = new HashMap<>( - otherKeys.length + CORE_EXPECTED_FIELDS.size()); - largerMap.putAll(CORE_EXPECTED_FIELDS); - for (int i = 0; i < otherKeys.length; i++) { - largerMap.put(otherKeys[i], otherValues[i]); - } - return largerMap; - } - - -} diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/properties/AbstractPropertySource.java b/pmd-core/src/main/java/net/sourceforge/pmd/properties/AbstractPropertySource.java index 318ec1ca2b5..2212750a7db 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/properties/AbstractPropertySource.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/properties/AbstractPropertySource.java @@ -15,17 +15,45 @@ import net.sourceforge.pmd.util.CollectionUtil; + /** - * Base class for objects which can be configured through properties. Rules and - * Reports are such objects. + * Base class for {@link PropertySource}. * * @author Brian Remedios */ public abstract class AbstractPropertySource implements PropertySource { - /** The list of known properties that can be configured. */ + // setProperty should probably be hidden from Rule implementations, they have no business using that + // The apex rules that do that could be refactored to do it in the XML + + // TODO RuleReference should extend this class + // This would avoid duplicating the implementation between Rule and RuleReference, + // which should use exactly the same mechanism to override properties (XML). + + // BUT to do that RuleReference should not extend AbstractDelegateRule + // Indeed, AbstractDelegateRule has no business having this overriding logic built-in, + // it would break its contract. For all deeds and purposes that class should be removed. + + // TODO 7.0.0 these fields should be made private final + + /** + * The list of known properties that can be configured. + * + * @deprecated Will be made private final + */ + @Deprecated protected List<PropertyDescriptor<?>> propertyDescriptors = new ArrayList<>(); - /** The values for each property. */ + + /** + * The values for each property that were overridden here. + * Default property values are not contained in this map. + * In other words, if this map doesn't contain a descriptor + * which is in {@link #propertyDescriptors}, then it's assumed + * to have a default value. + * + * @deprecated Will be made private final + */ + @Deprecated protected Map<PropertyDescriptor<?>, Object> propertyValuesByDescriptor = new HashMap<>(); @@ -33,7 +61,9 @@ public abstract class AbstractPropertySource implements PropertySource { * Creates a copied list of the property descriptors and returns it. * * @return a copy of the property descriptors. + * @deprecated Just use {@link #getPropertyDescriptors()} */ + @Deprecated protected List<PropertyDescriptor<?>> copyPropertyDescriptors() { return new ArrayList<>(propertyDescriptors); } @@ -43,13 +73,16 @@ protected List<PropertyDescriptor<?>> copyPropertyDescriptors() { * Creates a copied map of the values of the properties and returns it. * * @return a copy of the values + * + * @deprecated Just use {@link #getPropertiesByPropertyDescriptor()} or {@link #getOverriddenPropertiesByPropertyDescriptor()} */ + @Deprecated protected Map<PropertyDescriptor<?>, Object> copyPropertyValues() { return new HashMap<>(propertyValuesByDescriptor); } - @Override + @Deprecated public Set<PropertyDescriptor<?>> ignoredProperties() { return Collections.emptySet(); } @@ -58,11 +91,10 @@ public Set<PropertyDescriptor<?>> ignoredProperties() { @Override public void definePropertyDescriptor(PropertyDescriptor<?> propertyDescriptor) { // Check to ensure the property does not already exist. - for (PropertyDescriptor<?> descriptor : propertyDescriptors) { - if (descriptor.name().equals(propertyDescriptor.name())) { - throw new IllegalArgumentException("There is already a PropertyDescriptor with name '" - + propertyDescriptor.name() + "' defined on Rule " + getName() + "."); - } + if (getPropertyDescriptor(propertyDescriptor.name()) != null) { + throw new IllegalArgumentException("There is already a PropertyDescriptor with name '" + + propertyDescriptor.name() + "' defined on " + getPropertySourceType() + " " + getName() + "."); + } propertyDescriptors.add(propertyDescriptor); // Sort in UI order @@ -70,14 +102,7 @@ public void definePropertyDescriptor(PropertyDescriptor<?> propertyDescriptor) { } - /** - * Gets the name of the property source. This is e.g. the rule name or the - * report name. - * - * @return the name - */ - public abstract String getName(); - + protected abstract String getPropertySourceType(); @Override public PropertyDescriptor<?> getPropertyDescriptor(String name) { @@ -92,18 +117,19 @@ public PropertyDescriptor<?> getPropertyDescriptor(String name) { @Override public boolean hasDescriptor(PropertyDescriptor<?> descriptor) { + return propertyDescriptors.contains(descriptor); + } - if (propertyValuesByDescriptor.isEmpty()) { - propertyValuesByDescriptor = getPropertiesByPropertyDescriptor(); - } - return propertyValuesByDescriptor.containsKey(descriptor); + @Override + public final List<PropertyDescriptor<?>> getOverriddenPropertyDescriptors() { + return new ArrayList<>(propertyValuesByDescriptor.keySet()); } @Override public List<PropertyDescriptor<?>> getPropertyDescriptors() { - return propertyDescriptors; + return Collections.unmodifiableList(propertyDescriptors); } @@ -120,12 +146,17 @@ public <T> T getProperty(PropertyDescriptor<T> propertyDescriptor) { } + @Override + public boolean isPropertyOverridden(PropertyDescriptor<?> propertyDescriptor) { + return propertyValuesByDescriptor.containsKey(propertyDescriptor); + } + + @Override public <T> void setProperty(PropertyDescriptor<T> propertyDescriptor, T value) { checkValidPropertyDescriptor(propertyDescriptor); if (value instanceof List) { propertyValuesByDescriptor.put(propertyDescriptor, Collections.unmodifiableList((List) value)); - } else { propertyValuesByDescriptor.put(propertyDescriptor, value); } @@ -145,13 +176,18 @@ public <V> void setProperty(MultiValuePropertyDescriptor<V> propertyDescriptor, * @param propertyDescriptor The property descriptor to check */ private void checkValidPropertyDescriptor(PropertyDescriptor<?> propertyDescriptor) { - if (!propertyDescriptors.contains(propertyDescriptor)) { - throw new IllegalArgumentException( - "Property descriptor not defined for Rule " + getName() + ": " + propertyDescriptor); + if (!hasDescriptor(propertyDescriptor)) { + throw new IllegalArgumentException("Property descriptor not defined for " + getPropertySourceType() + " " + getName() + ": " + propertyDescriptor); } } + @Override + public final Map<PropertyDescriptor<?>, Object> getOverriddenPropertiesByPropertyDescriptor() { + return new HashMap<>(propertyValuesByDescriptor); + } + + @Override public Map<PropertyDescriptor<?>, Object> getPropertiesByPropertyDescriptor() { if (propertyDescriptors.isEmpty()) { @@ -169,11 +205,12 @@ public Map<PropertyDescriptor<?>, Object> getPropertiesByPropertyDescriptor() { } } - return propertiesByPropertyDescriptor; + return Collections.unmodifiableMap(propertiesByPropertyDescriptor); } @Override + @Deprecated public boolean usesDefaultValues() { Map<PropertyDescriptor<?>, Object> valuesByProperty = getPropertiesByPropertyDescriptor(); @@ -192,11 +229,13 @@ public boolean usesDefaultValues() { @Override + @Deprecated public void useDefaultValueFor(PropertyDescriptor<?> desc) { propertyValuesByDescriptor.remove(desc); } + // todo Java 8 move up to interface @Override public String dysfunctionReason() { return null; diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/properties/AbstractSingleValueProperty.java b/pmd-core/src/main/java/net/sourceforge/pmd/properties/AbstractSingleValueProperty.java index 40effa179c1..0864cbaf171 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/properties/AbstractSingleValueProperty.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/properties/AbstractSingleValueProperty.java @@ -6,6 +6,7 @@ import net.sourceforge.pmd.Rule; + /** * Single value property. * @@ -14,7 +15,7 @@ * @author Clément Fournier */ /* default */ abstract class AbstractSingleValueProperty<T> extends AbstractProperty<T> - implements SingleValuePropertyDescriptor<T> { + implements SingleValuePropertyDescriptor<T> { /** Default value. */ private T defaultValue; @@ -23,10 +24,11 @@ /** * Creates a single value property. * - * @param theName Name of the property - * @param theDescription Description - * @param theUIOrder UI order - * @param theDefault Default value + * @param theName Name of the property + * @param theDescription Description + * @param theUIOrder UI order + * @param theDefault Default value + * @param isDefinedExternally Whether the property is defined in the XML (by a XPath rule) or not * * @throws IllegalArgumentException If name or description are empty, or UI order is negative. */ @@ -85,12 +87,10 @@ public String errorFor(T value) { } - private String typeErrorFor(T value) { // TODO:cf consider subtypes? - + private String typeErrorFor(T value) { if (value != null && !type().isAssignableFrom(value.getClass())) { return value + " is not an instance of " + type(); } - return null; } diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/properties/BooleanMultiProperty.java b/pmd-core/src/main/java/net/sourceforge/pmd/properties/BooleanMultiProperty.java index a7f3fc69a82..bb13cac5759 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/properties/BooleanMultiProperty.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/properties/BooleanMultiProperty.java @@ -8,7 +8,10 @@ import java.util.Arrays; import java.util.List; -import java.util.Map; + +import net.sourceforge.pmd.properties.builders.MultiValuePropertyBuilder; +import net.sourceforge.pmd.properties.builders.PropertyDescriptorBuilderConversionWrapper; + /** * Defines a property type that supports multiple Boolean values. @@ -17,20 +20,6 @@ */ public final class BooleanMultiProperty extends AbstractMultiValueProperty<Boolean> { - /** Factory. */ - public static final PropertyDescriptorFactory<List<Boolean>> FACTORY // @formatter:off - = new MultiValuePropertyDescriptorFactory<Boolean>(Boolean.class) { - @Override - public BooleanMultiProperty createWith(Map<PropertyDescriptorField, String> valuesById, boolean isDefinedExternally) { - char delimiter = delimiterIn(valuesById); - return new BooleanMultiProperty(nameIn(valuesById), - descriptionIn(valuesById), - ValueParserConstants.parsePrimitives(defaultValueIn(valuesById), delimiter, BOOLEAN_PARSER), - 0f, - isDefinedExternally); - } - }; // @formatter:on - /** * Constructor using an array of defaults. @@ -76,4 +65,32 @@ public Class<Boolean> type() { return Boolean.class; } + + static PropertyDescriptorBuilderConversionWrapper.MultiValue<Boolean, BooleanMultiPBuilder> extractor() { + return new PropertyDescriptorBuilderConversionWrapper.MultiValue<Boolean, BooleanMultiPBuilder>(Boolean.class, ValueParserConstants.BOOLEAN_PARSER) { + @Override + protected BooleanMultiPBuilder newBuilder(String name) { + return new BooleanMultiPBuilder(name); + } + }; + } + + + public static BooleanMultiPBuilder named(String name) { + return new BooleanMultiPBuilder(name); + } + + + public static final class BooleanMultiPBuilder extends MultiValuePropertyBuilder<Boolean, BooleanMultiPBuilder> { + private BooleanMultiPBuilder(String name) { + super(name); + } + + + @Override + public BooleanMultiProperty build() { + return new BooleanMultiProperty(this.name, this.description, this.defaultValues, this.uiOrder, isDefinedInXML); + } + } + } diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/properties/BooleanProperty.java b/pmd-core/src/main/java/net/sourceforge/pmd/properties/BooleanProperty.java index 78876146fb5..acac8d414a6 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/properties/BooleanProperty.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/properties/BooleanProperty.java @@ -6,7 +6,9 @@ import static net.sourceforge.pmd.properties.ValueParserConstants.BOOLEAN_PARSER; -import java.util.Map; +import net.sourceforge.pmd.properties.builders.PropertyDescriptorBuilderConversionWrapper; +import net.sourceforge.pmd.properties.builders.SingleValuePropertyBuilder; + /** * Defines a property type that supports single Boolean values. @@ -16,23 +18,8 @@ */ public final class BooleanProperty extends AbstractSingleValueProperty<Boolean> { - /** Factory. */ - public static final PropertyDescriptorFactory<Boolean> FACTORY // @formatter:off - = new SingleValuePropertyDescriptorFactory<Boolean>(Boolean.class) { - @Override - public BooleanProperty createWith(Map<PropertyDescriptorField, String> valuesById, boolean isDefinedExternally) { - return new BooleanProperty(nameIn(valuesById), - descriptionIn(valuesById), - BOOLEAN_PARSER.valueOf(defaultValueIn(valuesById)), - 0f, - isDefinedExternally); - } - }; // @formatter:on - - /** - * Constructor for BooleanProperty limited to a single value. Converts - * default argument string into a boolean. + * Constructor for BooleanProperty limited to a single value. Converts default argument string into a boolean. * * @param theName Name * @param theDescription Description @@ -75,4 +62,33 @@ public Class<Boolean> type() { public Boolean createFrom(String propertyString) throws IllegalArgumentException { return BOOLEAN_PARSER.valueOf(propertyString); } + + + static PropertyDescriptorBuilderConversionWrapper.SingleValue<Boolean, BooleanPBuilder> extractor() { + return new PropertyDescriptorBuilderConversionWrapper.SingleValue<Boolean, BooleanPBuilder>(Boolean.class, ValueParserConstants.BOOLEAN_PARSER) { + @Override + protected BooleanPBuilder newBuilder(String name) { + return new BooleanPBuilder(name); + } + }; + } + + + public static BooleanPBuilder named(String name) { + return new BooleanPBuilder(name); + } + + + public static final class BooleanPBuilder extends SingleValuePropertyBuilder<Boolean, BooleanPBuilder> { + private BooleanPBuilder(String name) { + super(name); + } + + + @Override + public BooleanProperty build() { + return new BooleanProperty(this.name, this.description, this.defaultValue, this.uiOrder, this.isDefinedInXML); + } + } + } diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/properties/CharacterMultiProperty.java b/pmd-core/src/main/java/net/sourceforge/pmd/properties/CharacterMultiProperty.java index 1eb0e2c252d..e94c3840e84 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/properties/CharacterMultiProperty.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/properties/CharacterMultiProperty.java @@ -7,10 +7,13 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; -import java.util.Map; import org.apache.commons.lang3.StringUtils; +import net.sourceforge.pmd.properties.builders.MultiValuePropertyBuilder; +import net.sourceforge.pmd.properties.builders.PropertyDescriptorBuilderConversionWrapper; + + /** * Multi-valued character property. * @@ -19,27 +22,6 @@ */ public final class CharacterMultiProperty extends AbstractMultiValueProperty<Character> { - /** Factory. */ - public static final PropertyDescriptorFactory<List<Character>> FACTORY // @formatter:off - = new MultiValuePropertyDescriptorFactory<Character>(Character.class) { - - @Override - protected boolean isValueMissing(String value) { - return StringUtils.isEmpty(value); - } - - @Override - public CharacterMultiProperty createWith(Map<PropertyDescriptorField, String> valuesById, boolean isDefinedExternally) { - char delimiter = delimiterIn(valuesById); - return new CharacterMultiProperty(nameIn(valuesById), - descriptionIn(valuesById), - ValueParserConstants.parsePrimitives(defaultValueIn(valuesById), delimiter, ValueParserConstants.CHARACTER_PARSER), - 0.0f, - delimiter, - isDefinedExternally); - } - }; // @formatter:on - /** * Constructor using an array of defaults. @@ -111,4 +93,33 @@ public List<Character> valueFrom(String valueString) throws IllegalArgumentExcep return chars; } + + static PropertyDescriptorBuilderConversionWrapper.MultiValue<Character, CharacterMultiPBuilder> extractor() { + return new PropertyDescriptorBuilderConversionWrapper.MultiValue<Character, CharacterMultiPBuilder>(Character.class, ValueParserConstants.CHARACTER_PARSER) { + @Override + protected CharacterMultiPBuilder newBuilder(String name) { + return new CharacterMultiPBuilder(name); + } + }; + } + + + public static CharacterMultiPBuilder named(String name) { + return new CharacterMultiPBuilder(name); + } + + + public static final class CharacterMultiPBuilder extends MultiValuePropertyBuilder<Character, CharacterMultiPBuilder> { + private CharacterMultiPBuilder(String name) { + super(name); + } + + + @Override + public CharacterMultiProperty build() { + return new CharacterMultiProperty(this.name, this.description, this.defaultValues, this.uiOrder, multiValueDelimiter, isDefinedInXML); + } + } + + } diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/properties/CharacterProperty.java b/pmd-core/src/main/java/net/sourceforge/pmd/properties/CharacterProperty.java index d91b65a3ce5..ba90f2b3f34 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/properties/CharacterProperty.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/properties/CharacterProperty.java @@ -6,9 +6,9 @@ import static net.sourceforge.pmd.properties.ValueParserConstants.CHARACTER_PARSER; -import java.util.Map; +import net.sourceforge.pmd.properties.builders.PropertyDescriptorBuilderConversionWrapper; +import net.sourceforge.pmd.properties.builders.SingleValuePropertyBuilder; -import org.apache.commons.lang3.StringUtils; /** * Defines a property type that supports single Character values. @@ -18,26 +18,6 @@ */ public final class CharacterProperty extends AbstractSingleValueProperty<Character> { - public static final PropertyDescriptorFactory<Character> FACTORY // @formatter:off - = new SingleValuePropertyDescriptorFactory<Character>(Character.class) { - - @Override - protected boolean isValueMissing(String value) { - return StringUtils.isEmpty(value); - } - - @Override - public CharacterProperty createWith(Map<PropertyDescriptorField, String> valuesById, boolean isDefinedExternally) { - return new CharacterProperty(nameIn(valuesById), - descriptionIn(valuesById), - defaultValueIn(valuesById) == null ? null - : defaultValueIn(valuesById).charAt(0), - 0f, - isDefinedExternally); - } - }; // @formatter:on - - /** * Constructor for CharacterProperty. * @@ -60,20 +40,6 @@ private CharacterProperty(String theName, String theDescription, Character theDe } - /** - * Parses a String into a Character. - * - * @param charStr String to parse - * - * @return Parsed Character - * - * @throws IllegalArgumentException if the String doesn't have length 1 - */ - public static Character charFrom(String charStr) { - return CHARACTER_PARSER.valueOf(charStr); - } - - /** * Constructor. * @@ -98,4 +64,45 @@ public Character createFrom(String valueString) throws IllegalArgumentException return charFrom(valueString); } + + /** + * Parses a String into a Character. + * + * @param charStr String to parse + * + * @return Parsed Character + * @throws IllegalArgumentException if the String doesn't have length 1 + */ + public static Character charFrom(String charStr) { + return CHARACTER_PARSER.valueOf(charStr); + } + + + static PropertyDescriptorBuilderConversionWrapper.SingleValue<Character, CharacterPBuilder> extractor() { + return new PropertyDescriptorBuilderConversionWrapper.SingleValue<Character, CharacterPBuilder>(Character.class, ValueParserConstants.CHARACTER_PARSER) { + @Override + protected CharacterPBuilder newBuilder(String name) { + return new CharacterPBuilder(name); + } + }; + } + + + public static CharacterPBuilder named(String name) { + return new CharacterPBuilder(name); + } + + + public static final class CharacterPBuilder extends SingleValuePropertyBuilder<Character, CharacterPBuilder> { + private CharacterPBuilder(String name) { + super(name); + } + + + @Override + public CharacterProperty build() { + return new CharacterProperty(this.name, this.description, this.defaultValue, this.uiOrder, isDefinedInXML); + } + } + } diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/properties/DoubleMultiProperty.java b/pmd-core/src/main/java/net/sourceforge/pmd/properties/DoubleMultiProperty.java index dd48d54cb1b..5bfabaf259f 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/properties/DoubleMultiProperty.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/properties/DoubleMultiProperty.java @@ -4,11 +4,12 @@ package net.sourceforge.pmd.properties; -import static net.sourceforge.pmd.properties.ValueParserConstants.DOUBLE_PARSER; - import java.util.Arrays; import java.util.List; -import java.util.Map; + +import net.sourceforge.pmd.properties.builders.MultiNumericPropertyBuilder; +import net.sourceforge.pmd.properties.builders.PropertyDescriptorBuilderConversionWrapper; + /** * Multi-valued double property. @@ -18,27 +19,6 @@ */ public final class DoubleMultiProperty extends AbstractMultiNumericProperty<Double> { - /** Factory. */ - public static final PropertyDescriptorFactory<List<Double>> FACTORY // @formatter:off - = new MultiValuePropertyDescriptorFactory<Double>(Double.class, NUMBER_FIELD_TYPES_BY_KEY) { - @Override - public DoubleMultiProperty createWith(Map<PropertyDescriptorField, String> valuesById, boolean isDefinedExternally) { - String[] minMax = minMaxFrom(valuesById); - char delimiter = delimiterIn(valuesById, DEFAULT_NUMERIC_DELIMITER); - List<Double> defaultValues - = ValueParserConstants.parsePrimitives(defaultValueIn(valuesById), delimiter, DOUBLE_PARSER); - - return new DoubleMultiProperty(nameIn(valuesById), - descriptionIn(valuesById), - DOUBLE_PARSER.valueOf(minMax[0]), - DOUBLE_PARSER.valueOf(minMax[1]), - defaultValues, - 0f, - isDefinedExternally); - } - }; // @formatter:on - - /** * Constructor using an array of defaults. * @@ -49,7 +29,7 @@ public DoubleMultiProperty createWith(Map<PropertyDescriptorField, String> value * @param defaultValues Array of defaults * @param theUIOrder UI order * - * @throws IllegalArgumentException if min > max or one of the defaults is not between the bounds + * @throws IllegalArgumentException if {@literal min > max} or one of the defaults is not between the bounds */ public DoubleMultiProperty(String theName, String theDescription, Double min, Double max, Double[] defaultValues, float theUIOrder) { @@ -74,7 +54,7 @@ private DoubleMultiProperty(String theName, String theDescription, Double min, D * @param defaultValues List of defaults * @param theUIOrder UI order * - * @throws IllegalArgumentException if min > max or one of the defaults is not between the bounds + * @throws IllegalArgumentException if {@literal min > max} or one of the defaults is not between the bounds */ public DoubleMultiProperty(String theName, String theDescription, Double min, Double max, List<Double> defaultValues, float theUIOrder) { @@ -93,4 +73,32 @@ protected Double createFrom(String value) { return Double.valueOf(value); } + + static PropertyDescriptorBuilderConversionWrapper.MultiValue.Numeric<Double, DoubleMultiPBuilder> extractor() { + return new PropertyDescriptorBuilderConversionWrapper.MultiValue.Numeric<Double, DoubleMultiPBuilder>(Double.class, ValueParserConstants.DOUBLE_PARSER) { + @Override + protected DoubleMultiPBuilder newBuilder(String name) { + return new DoubleMultiPBuilder(name); + } + }; + } + + + public static DoubleMultiPBuilder named(String name) { + return new DoubleMultiPBuilder(name); + } + + + public static final class DoubleMultiPBuilder extends MultiNumericPropertyBuilder<Double, DoubleMultiPBuilder> { + private DoubleMultiPBuilder(String name) { + super(name); + } + + + @Override + public DoubleMultiProperty build() { + return new DoubleMultiProperty(name, description, lowerLimit, upperLimit, defaultValues, uiOrder, isDefinedInXML); + } + } + } diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/properties/DoubleProperty.java b/pmd-core/src/main/java/net/sourceforge/pmd/properties/DoubleProperty.java index 5a0d374745f..ce635c43668 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/properties/DoubleProperty.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/properties/DoubleProperty.java @@ -6,34 +6,18 @@ import static net.sourceforge.pmd.properties.ValueParserConstants.DOUBLE_PARSER; -import java.util.Map; +import net.sourceforge.pmd.properties.builders.PropertyDescriptorBuilderConversionWrapper; +import net.sourceforge.pmd.properties.builders.SingleNumericPropertyBuilder; + /** - * Defines a property type that support single double-type property values - * within an upper and lower boundary. + * Defines a property type that support single double-type property values within an upper and lower boundary. * * @author Brian Remedios * @version Refactored June 2017 (6.0.0) */ public final class DoubleProperty extends AbstractNumericProperty<Double> { - /** Factory. */ - public static final PropertyDescriptorFactory<Double> FACTORY // @formatter:off - = new SingleValuePropertyDescriptorFactory<Double>(Double.class, NUMBER_FIELD_TYPES_BY_KEY) { - @Override - public DoubleProperty createWith(Map<PropertyDescriptorField, String> valuesById, - boolean isDefinedExternally) { - final String[] minMax = minMaxFrom(valuesById); - return new DoubleProperty(nameIn(valuesById), - descriptionIn(valuesById), - doubleFrom(minMax[0]), - doubleFrom(minMax[1]), - doubleFrom(defaultValueIn(valuesById)), - 0f, - isDefinedExternally); - } - }; // @formatter:on - /** * Constructor for DoubleProperty that limits itself to a single value within the specified limits. Converts string @@ -46,7 +30,7 @@ public DoubleProperty createWith(Map<PropertyDescriptorField, String> valuesById * @param defaultStr Default value * @param theUIOrder UI order * - * @throws IllegalArgumentException if min > max or one of the defaults is not between the bounds + * @throws IllegalArgumentException if {@literal min > max} or one of the defaults is not between the bounds * @deprecated will be removed in 7.0.0 */ public DoubleProperty(String theName, String theDescription, String minStr, String maxStr, String defaultStr, @@ -62,18 +46,6 @@ private DoubleProperty(String theName, String theDescription, Double min, Double } - /** - * Parses a String into a Double. - * - * @param numberString String to parse - * - * @return Parsed Double - */ - private static Double doubleFrom(String numberString) { - return DOUBLE_PARSER.valueOf(numberString); - } - - /** * Constructor that limits itself to a single value within the specified limits. * @@ -84,7 +56,7 @@ private static Double doubleFrom(String numberString) { * @param theDefault Default value * @param theUIOrder UI order * - * @throws IllegalArgumentException if min > max or one of the defaults is not between the bounds + * @throws IllegalArgumentException if {@literal min > max} or one of the defaults is not between the bounds */ public DoubleProperty(String theName, String theDescription, Double min, Double max, Double theDefault, float theUIOrder) { @@ -102,4 +74,45 @@ public Class<Double> type() { protected Double createFrom(String value) { return doubleFrom(value); } + + + /** + * Parses a String into a Double. + * + * @param numberString String to parse + * + * @return Parsed Double + */ + private static Double doubleFrom(String numberString) { + return DOUBLE_PARSER.valueOf(numberString); + } + + + static PropertyDescriptorBuilderConversionWrapper.SingleValue.Numeric<Double, DoublePBuilder> extractor() { + return new PropertyDescriptorBuilderConversionWrapper.SingleValue.Numeric<Double, DoublePBuilder>(Double.class, ValueParserConstants.DOUBLE_PARSER) { + @Override + protected DoublePBuilder newBuilder(String name) { + return new DoublePBuilder(name); + } + }; + } + + + public static DoublePBuilder named(String name) { + return new DoublePBuilder(name); + } + + + public static final class DoublePBuilder extends SingleNumericPropertyBuilder<Double, DoublePBuilder> { + private DoublePBuilder(String name) { + super(name); + } + + + @Override + public DoubleProperty build() { + return new DoubleProperty(name, description, lowerLimit, upperLimit, defaultValue, uiOrder, isDefinedInXML); + } + } + } diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/properties/EnumeratedMultiProperty.java b/pmd-core/src/main/java/net/sourceforge/pmd/properties/EnumeratedMultiProperty.java index 06ea880080d..fd4ed5530b6 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/properties/EnumeratedMultiProperty.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/properties/EnumeratedMultiProperty.java @@ -8,9 +8,11 @@ import java.util.List; import java.util.Map; +import net.sourceforge.pmd.properties.builders.MultiValuePropertyBuilder; import net.sourceforge.pmd.properties.modules.EnumeratedPropertyModule; import net.sourceforge.pmd.util.CollectionUtil; + /** * Multi-valued property which can take only a fixed set of values of any type, then selected via String labels. The * mappings method returns the set of mappings between the labels and their values. @@ -24,21 +26,6 @@ public final class EnumeratedMultiProperty<E> extends AbstractMultiValueProperty<E> implements EnumeratedPropertyDescriptor<E, List<E>> { - /** Factory. */ - public static final PropertyDescriptorFactory<List<Object>> FACTORY // @formatter:off - = new MultiValuePropertyDescriptorFactory<Object>(Object.class) { // TODO:cf is Object the right type? - @Override - public EnumeratedMultiProperty createWith(Map<PropertyDescriptorField, String> valuesById, boolean isDefinedExternally) { - Object[] choices = choicesIn(valuesById); - return new EnumeratedMultiProperty<>(nameIn(valuesById), - descriptionIn(valuesById), - CollectionUtil.mapFrom(labelsIn(valuesById), choices), - selection(indicesIn(valuesById), choices), - classIn(valuesById), - 0f, - isDefinedExternally); - } - }; // @formatter:on private final EnumeratedPropertyModule<E> module; @@ -56,13 +43,13 @@ public EnumeratedMultiProperty createWith(Map<PropertyDescriptorField, String> v * @param theUIOrder UI order * * @deprecated Use {@link #EnumeratedMultiProperty(String, String, Map, List, Class, float)}. Will be removed in - * 7.0.0 + * 7.0.0 */ @Deprecated public EnumeratedMultiProperty(String theName, String theDescription, String[] theLabels, E[] theChoices, int[] choiceIndices, Class<E> valueType, float theUIOrder) { this(theName, theDescription, CollectionUtil.mapFrom(theLabels, theChoices), - selection(choiceIndices, theChoices), valueType, theUIOrder, false); + selection(choiceIndices, theChoices), valueType, theUIOrder, false); } @@ -78,13 +65,13 @@ public EnumeratedMultiProperty(String theName, String theDescription, String[] t * @param theUIOrder UI order * * @deprecated Use {@link #EnumeratedMultiProperty(String, String, Map, List, Class, float)}. Will be removed in - * 7.0.0 + * 7.0.0 */ @Deprecated public EnumeratedMultiProperty(String theName, String theDescription, String[] theLabels, E[] theChoices, int[] choiceIndices, float theUIOrder) { this(theName, theDescription, CollectionUtil.mapFrom(theLabels, theChoices), - selection(choiceIndices, theChoices), null, theUIOrder, false); + selection(choiceIndices, theChoices), null, theUIOrder, false); } @@ -168,4 +155,44 @@ private static <E> List<E> selection(int[] choiceIndices, E[] theChoices) { return selected; } + + public static <E> EnumMultiPBuilder<E> named(String name) { + return new EnumMultiPBuilder<>(name); + } + + + public static final class EnumMultiPBuilder<E> extends MultiValuePropertyBuilder<E, EnumMultiPBuilder<E>> { + + private Class<E> valueType; + private Map<String, E> mappings; + + + private EnumMultiPBuilder(String name) { + super(name); + } + + public EnumMultiPBuilder<E> type(Class<E> type) { + this.valueType = type; + return this; + } + + /** + * Sets the key-value mappings. + * + * @param map A map of label to value + * + * @return The same builder + */ + public EnumMultiPBuilder<E> mappings(Map<String, E> map) { + this.mappings = map; + return this; + } + + + @Override + public EnumeratedMultiProperty<E> build() { + return new EnumeratedMultiProperty<>(this.name, this.description, mappings, this.defaultValues, valueType, this.uiOrder, isDefinedInXML); + } + } + } diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/properties/EnumeratedProperty.java b/pmd-core/src/main/java/net/sourceforge/pmd/properties/EnumeratedProperty.java index ccaab99d773..d22cf554874 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/properties/EnumeratedProperty.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/properties/EnumeratedProperty.java @@ -4,17 +4,18 @@ package net.sourceforge.pmd.properties; -import java.util.Enumeration; import java.util.Map; +import net.sourceforge.pmd.properties.builders.SingleValuePropertyBuilder; import net.sourceforge.pmd.properties.modules.EnumeratedPropertyModule; import net.sourceforge.pmd.util.CollectionUtil; + /** * Property which can take only a fixed set of values of any type, then selected via String labels. The mappings method * returns the set of mappings between the labels and their values. * - * <p>This property currently doesn't support serialization and cannot be defined in a ruleset file.z + * <p>This property currently doesn't support serialization and cannot be defined in a ruleset file.z </p> * * @param <E> Type of the values * @@ -23,26 +24,7 @@ * @version Refactored June 2017 (6.0.0) */ public final class EnumeratedProperty<E> extends AbstractSingleValueProperty<E> - implements EnumeratedPropertyDescriptor<E, E> { - - /** Factory. */ - public static final PropertyDescriptorFactory<? extends Enumeration> FACTORY // @formatter:off - = new SingleValuePropertyDescriptorFactory<Enumeration>(Enumeration.class) { // TODO:cf Enumeration? Object? - - @Override - public EnumeratedProperty createWith(Map<PropertyDescriptorField, String> valuesById, boolean isDefinedExternally) { - Map<String, Object> labelsToChoices = CollectionUtil.mapFrom(labelsIn(valuesById), // this is not implemented - choicesIn(valuesById)); // ditto - return new EnumeratedProperty<>(nameIn(valuesById), - descriptionIn(valuesById), - labelsToChoices, - choicesIn(valuesById)[indexIn(valuesById)], - classIn(valuesById), - 0f, - isDefinedExternally); - } - }; // @formatter:on - + implements EnumeratedPropertyDescriptor<E, E> { private final EnumeratedPropertyModule<E> module; @@ -65,7 +47,7 @@ public EnumeratedProperty createWith(Map<PropertyDescriptorField, String> values public EnumeratedProperty(String theName, String theDescription, String[] theLabels, E[] theChoices, int defaultIndex, Class<E> valueType, float theUIOrder) { this(theName, theDescription, CollectionUtil.mapFrom(theLabels, theChoices), - theChoices[defaultIndex], valueType, theUIOrder, false); + theChoices[defaultIndex], valueType, theUIOrder, false); } @@ -86,7 +68,7 @@ public EnumeratedProperty(String theName, String theDescription, String[] theLab public EnumeratedProperty(String theName, String theDescription, String[] theLabels, E[] theChoices, int defaultIndex, float theUIOrder) { this(theName, theDescription, CollectionUtil.mapFrom(theLabels, theChoices), - theChoices[defaultIndex], null, theUIOrder, false); + theChoices[defaultIndex], null, theUIOrder, false); } @@ -146,4 +128,34 @@ public Map<String, E> mappings() { } + public static <E> EnumPBuilder<E> named(String name) { + return new EnumPBuilder<>(name); + } + + public static final class EnumPBuilder<E> extends SingleValuePropertyBuilder<E, EnumPBuilder<E>> { + + private Class<E> valueType; + private Map<String, E> mappings; + + + private EnumPBuilder(String name) { + super(name); + } + + public EnumPBuilder<E> type(Class<E> type) { + this.valueType = type; + return this; + } + + public EnumPBuilder<E> mappings(Map<String, E> map) { + this.mappings = map; + return this; + } + + + @Override + public EnumeratedProperty<E> build() { + return new EnumeratedProperty<>(this.name, this.description, mappings, this.defaultValue, valueType, this.uiOrder, isDefinedInXML); + } + } } diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/properties/EnumeratedPropertyDescriptor.java b/pmd-core/src/main/java/net/sourceforge/pmd/properties/EnumeratedPropertyDescriptor.java index 9a4b4a36bea..0736db2c4d8 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/properties/EnumeratedPropertyDescriptor.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/properties/EnumeratedPropertyDescriptor.java @@ -6,6 +6,7 @@ import java.util.Map; + /** * Interface defining an enumerated property descriptor. * diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/properties/ExpectedFieldsBuilder.java b/pmd-core/src/main/java/net/sourceforge/pmd/properties/ExpectedFieldsBuilder.java deleted file mode 100644 index f5dac458c44..00000000000 --- a/pmd-core/src/main/java/net/sourceforge/pmd/properties/ExpectedFieldsBuilder.java +++ /dev/null @@ -1,57 +0,0 @@ -/** - * BSD-style license; for more info see http://pmd.sourceforge.net/license.html - */ - -package net.sourceforge.pmd.properties; - -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; - -/** Builder for an expected fields map. */ -public final class ExpectedFieldsBuilder { - - private final Map<PropertyDescriptorField, Boolean> requiredFields = new HashMap<>(); - - - private ExpectedFieldsBuilder() { - - } - - - /** - * Adds a mapping of field/ required to the map. - * - * @param field The field to expect - * @param isRequired Whether it's required or not - * - * @return This instance, so that we have a fluent interface - */ - public ExpectedFieldsBuilder put(PropertyDescriptorField field, boolean isRequired) { - requiredFields.put(field, isRequired); - return this; - } - - - /** - * Gets an immutable map containing the fields we've put here. - * - * @return The map of field/ isRequired mappings - */ - public Map<PropertyDescriptorField, Boolean> build() { - return Collections.unmodifiableMap(requiredFields); - } - - - /** - * Gets a builder for a required fields map. - * - * @return A builder - * - * @see ExpectedFieldsBuilder - */ - public static ExpectedFieldsBuilder instance() { - return new ExpectedFieldsBuilder(); - } - -} diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/properties/FileProperty.java b/pmd-core/src/main/java/net/sourceforge/pmd/properties/FileProperty.java index d8d7bc7611a..57209296258 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/properties/FileProperty.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/properties/FileProperty.java @@ -5,10 +5,13 @@ package net.sourceforge.pmd.properties; import java.io.File; -import java.util.Map; import org.apache.commons.lang3.StringUtils; +import net.sourceforge.pmd.properties.builders.PropertyDescriptorBuilderConversionWrapper; +import net.sourceforge.pmd.properties.builders.SinglePackagedPropertyBuilder; + + /** * Property taking a File object as its value. * @@ -17,19 +20,6 @@ */ public final class FileProperty extends AbstractSingleValueProperty<File> { - /** Factory. */ - public static final PropertyDescriptorFactory<File> FACTORY // @formatter:off - = new SingleValuePropertyDescriptorFactory<File>(File.class) { - @Override - public FileProperty createWith(Map<PropertyDescriptorField, String> valuesById, boolean isDefinedExternally) { - return new FileProperty(nameIn(valuesById), - descriptionIn(valuesById), - null, - 0f, - isDefinedExternally); - } - }; // @formatter:on - /** * Constructor for file property. @@ -60,4 +50,34 @@ public Class<File> type() { public File createFrom(String propertyString) { return StringUtils.isBlank(propertyString) ? null : new File(propertyString); } + + + static PropertyDescriptorBuilderConversionWrapper.SingleValue<File, FilePBuilder> extractor() { + return new PropertyDescriptorBuilderConversionWrapper.SingleValue<File, FilePBuilder>(File.class, ValueParserConstants.FILE_PARSER) { + @Override + protected FilePBuilder newBuilder(String name) { + return new FilePBuilder(name); + } + }; + } + + + public static FilePBuilder named(String name) { + return new FilePBuilder(name); + } + + + public static final class FilePBuilder extends SinglePackagedPropertyBuilder<File, FilePBuilder> { + private FilePBuilder(String name) { + super(name); + } + + + @Override + public FileProperty build() { + return new FileProperty(name, description, defaultValue, uiOrder, isDefinedInXML); + } + } + + } diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/properties/FloatMultiProperty.java b/pmd-core/src/main/java/net/sourceforge/pmd/properties/FloatMultiProperty.java index c5b60f3ecb2..4c06a0f107d 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/properties/FloatMultiProperty.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/properties/FloatMultiProperty.java @@ -4,11 +4,12 @@ package net.sourceforge.pmd.properties; -import static net.sourceforge.pmd.properties.ValueParserConstants.FLOAT_PARSER; - import java.util.Arrays; import java.util.List; -import java.util.Map; + +import net.sourceforge.pmd.properties.builders.MultiNumericPropertyBuilder; +import net.sourceforge.pmd.properties.builders.PropertyDescriptorBuilderConversionWrapper; + /** * Multi-valued float property. @@ -18,25 +19,6 @@ */ public final class FloatMultiProperty extends AbstractMultiNumericProperty<Float> { - /** Factory. */ - public static final PropertyDescriptorFactory<List<Float>> FACTORY // @formatter:off - = new MultiValuePropertyDescriptorFactory<Float>(Float.class, NUMBER_FIELD_TYPES_BY_KEY) { - @Override - public FloatMultiProperty createWith(Map<PropertyDescriptorField, String> valuesById, - boolean isDefinedExternally) { - String[] minMax = minMaxFrom(valuesById); - char delimiter = delimiterIn(valuesById, DEFAULT_NUMERIC_DELIMITER); - List<Float> defaultValues = ValueParserConstants.parsePrimitives(defaultValueIn(valuesById), delimiter, FLOAT_PARSER); - return new FloatMultiProperty(nameIn(valuesById), - descriptionIn(valuesById), - FLOAT_PARSER.valueOf(minMax[0]), - FLOAT_PARSER.valueOf(minMax[1]), - defaultValues, - 0f, - isDefinedExternally); - } - }; // @formatter:on - /** * Constructor using an array of defaults. @@ -48,7 +30,7 @@ public FloatMultiProperty createWith(Map<PropertyDescriptorField, String> values * @param defaultValues Array of defaults * @param theUIOrder UI order * - * @throws IllegalArgumentException if min > max or one of the defaults is not between the bounds + * @throws IllegalArgumentException if {@literal min > max} or one of the defaults is not between the bounds */ public FloatMultiProperty(String theName, String theDescription, Float min, Float max, Float[] defaultValues, float theUIOrder) { @@ -73,7 +55,7 @@ private FloatMultiProperty(String theName, String theDescription, Float min, Flo * @param defaultValues List of defaults * @param theUIOrder UI order * - * @throws IllegalArgumentException if min > max or one of the defaults is not between the bounds + * @throws IllegalArgumentException if {@literal min > max} or one of the defaults is not between the bounds */ public FloatMultiProperty(String theName, String theDescription, Float min, Float max, List<Float> defaultValues, float theUIOrder) { @@ -91,4 +73,34 @@ public Class<Float> type() { protected Float createFrom(String value) { return Float.valueOf(value); } + + + static PropertyDescriptorBuilderConversionWrapper.MultiValue.Numeric<Float, FloatMultiPBuilder> extractor() { + return new PropertyDescriptorBuilderConversionWrapper.MultiValue.Numeric<Float, FloatMultiPBuilder>(Float.class, ValueParserConstants.FLOAT_PARSER) { + @Override + protected FloatMultiPBuilder newBuilder(String name) { + return new FloatMultiPBuilder(name); + } + }; + } + + + public static FloatMultiPBuilder named(String name) { + return new FloatMultiPBuilder(name); + } + + + public static final class FloatMultiPBuilder extends MultiNumericPropertyBuilder<Float, FloatMultiPBuilder> { + private FloatMultiPBuilder(String name) { + super(name); + } + + + @Override + public FloatMultiProperty build() { + return new FloatMultiProperty(name, description, lowerLimit, upperLimit, defaultValues, uiOrder, isDefinedInXML); + } + } + + } diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/properties/FloatProperty.java b/pmd-core/src/main/java/net/sourceforge/pmd/properties/FloatProperty.java index 17ecfa812c8..a3ce5de9a18 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/properties/FloatProperty.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/properties/FloatProperty.java @@ -6,32 +6,17 @@ import static net.sourceforge.pmd.properties.ValueParserConstants.FLOAT_PARSER; -import java.util.Map; +import net.sourceforge.pmd.properties.builders.PropertyDescriptorBuilderConversionWrapper; +import net.sourceforge.pmd.properties.builders.SingleNumericPropertyBuilder; + /** - * Defines a property type that supports single float property values within an - * upper and lower boundary. + * Defines a property type that supports single float property values within an upper and lower boundary. * * @author Brian Remedios */ public final class FloatProperty extends AbstractNumericProperty<Float> { - /** Factory. */ - public static final PropertyDescriptorFactory<Float> FACTORY // @formatter:off - = new SingleValuePropertyDescriptorFactory<Float>(Float.class, NUMBER_FIELD_TYPES_BY_KEY) { - @Override - public FloatProperty createWith(Map<PropertyDescriptorField, String> valuesById, boolean isDefinedExternally) { - final String[] minMax = minMaxFrom(valuesById); - return new FloatProperty(nameIn(valuesById), - descriptionIn(valuesById), - FLOAT_PARSER.valueOf(minMax[0]), - FLOAT_PARSER.valueOf(minMax[1]), - FLOAT_PARSER.valueOf(defaultValueIn(valuesById)), - 0f, - isDefinedExternally); - } - }; // @formatter:on - /** * Constructor for FloatProperty that limits itself to a single value within the specified limits. Converts string @@ -44,13 +29,13 @@ public FloatProperty createWith(Map<PropertyDescriptorField, String> valuesById, * @param defaultStr Default value * @param theUIOrder UI order * - * @throws IllegalArgumentException if min > max or one of the defaults is not between the bounds + * @throws IllegalArgumentException if {@literal min > max} or one of the defaults is not between the bounds * @deprecated will be removed in 7.0.0 */ public FloatProperty(String theName, String theDescription, String minStr, String maxStr, String defaultStr, float theUIOrder) { this(theName, theDescription, FLOAT_PARSER.valueOf(minStr), - FLOAT_PARSER.valueOf(maxStr), FLOAT_PARSER.valueOf(defaultStr), theUIOrder, false); + FLOAT_PARSER.valueOf(maxStr), FLOAT_PARSER.valueOf(defaultStr), theUIOrder, false); } @@ -71,7 +56,7 @@ private FloatProperty(String theName, String theDescription, Float min, Float ma * @param theDefault Default value * @param theUIOrder UI order * - * @throws IllegalArgumentException if min > max or one of the defaults is not between the bounds + * @throws IllegalArgumentException if {@literal min > max} or one of the defaults is not between the bounds */ public FloatProperty(String theName, String theDescription, Float min, Float max, Float theDefault, float theUIOrder) { @@ -91,4 +76,32 @@ protected Float createFrom(String value) { } + static PropertyDescriptorBuilderConversionWrapper.SingleValue.Numeric<Float, FloatPBuilder> extractor() { + return new PropertyDescriptorBuilderConversionWrapper.SingleValue.Numeric<Float, FloatPBuilder>(Float.class, ValueParserConstants.FLOAT_PARSER) { + @Override + protected FloatPBuilder newBuilder(String name) { + return new FloatPBuilder(name); + } + }; + } + + + public static FloatPBuilder named(String name) { + return new FloatPBuilder(name); + } + + + public static final class FloatPBuilder extends SingleNumericPropertyBuilder<Float, FloatPBuilder> { + private FloatPBuilder(String name) { + super(name); + } + + + @Override + public FloatProperty build() { + return new FloatProperty(name, description, lowerLimit, upperLimit, defaultValue, uiOrder, isDefinedInXML); + } + } + + } diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/properties/IntegerMultiProperty.java b/pmd-core/src/main/java/net/sourceforge/pmd/properties/IntegerMultiProperty.java index 59e9ba298a0..3aa5781651f 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/properties/IntegerMultiProperty.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/properties/IntegerMultiProperty.java @@ -4,11 +4,12 @@ package net.sourceforge.pmd.properties; -import static net.sourceforge.pmd.properties.ValueParserConstants.INTEGER_PARSER; - import java.util.Arrays; import java.util.List; -import java.util.Map; + +import net.sourceforge.pmd.properties.builders.MultiNumericPropertyBuilder; +import net.sourceforge.pmd.properties.builders.PropertyDescriptorBuilderConversionWrapper; + /** * Multi-valued integer property. @@ -18,25 +19,6 @@ */ public final class IntegerMultiProperty extends AbstractMultiNumericProperty<Integer> { - /** Factory. */ - public static final PropertyDescriptorFactory<List<Integer>> FACTORY // @formatter:off - = new MultiValuePropertyDescriptorFactory<Integer>(Integer.class, NUMBER_FIELD_TYPES_BY_KEY) { - @Override - public IntegerMultiProperty createWith(Map<PropertyDescriptorField, String> valuesById, boolean - isDefinedExternally) { - String[] minMax = minMaxFrom(valuesById); - char delimiter = delimiterIn(valuesById, DEFAULT_NUMERIC_DELIMITER); - List<Integer> defaultValues = ValueParserConstants.parsePrimitives(defaultValueIn(valuesById), delimiter, INTEGER_PARSER); - return new IntegerMultiProperty(nameIn(valuesById), - descriptionIn(valuesById), - INTEGER_PARSER.valueOf(minMax[0]), - INTEGER_PARSER.valueOf(minMax[1]), - defaultValues, - 0f, - isDefinedExternally); - } - }; // @formatter:on - /** * Constructor using an array of defaults. @@ -48,7 +30,7 @@ public IntegerMultiProperty createWith(Map<PropertyDescriptorField, String> valu * @param defaultValues Array of defaults * @param theUIOrder UI order * - * @throws IllegalArgumentException if min > max or one of the defaults is not between the bounds + * @throws IllegalArgumentException if {@literal min > max} or one of the defaults is not between the bounds */ public IntegerMultiProperty(String theName, String theDescription, Integer min, Integer max, Integer[] defaultValues, float theUIOrder) { @@ -74,7 +56,7 @@ private IntegerMultiProperty(String theName, String theDescription, Integer min, * @param defaultValues List of defaults * @param theUIOrder UI order * - * @throws IllegalArgumentException if min > max or one of the defaults is not between the bounds + * @throws IllegalArgumentException if {@literal min > max} or one of the defaults is not between the bounds */ public IntegerMultiProperty(String theName, String theDescription, Integer min, Integer max, List<Integer> defaultValues, float theUIOrder) { @@ -93,4 +75,32 @@ public Class<Integer> type() { protected Integer createFrom(String toParse) { return Integer.valueOf(toParse); } + + + static PropertyDescriptorBuilderConversionWrapper.MultiValue.Numeric<Integer, IntegerMultiPBuilder> extractor() { + return new PropertyDescriptorBuilderConversionWrapper.MultiValue.Numeric<Integer, IntegerMultiPBuilder>(Integer.class, ValueParserConstants.INTEGER_PARSER) { + @Override + protected IntegerMultiPBuilder newBuilder(String name) { + return new IntegerMultiPBuilder(name); + } + }; + } + + + public static IntegerMultiPBuilder named(String name) { + return new IntegerMultiPBuilder(name); + } + + + public static final class IntegerMultiPBuilder extends MultiNumericPropertyBuilder<Integer, IntegerMultiPBuilder> { + private IntegerMultiPBuilder(String name) { + super(name); + } + + + @Override + public IntegerMultiProperty build() { + return new IntegerMultiProperty(name, description, lowerLimit, upperLimit, defaultValues, uiOrder, isDefinedInXML); + } + } } diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/properties/IntegerProperty.java b/pmd-core/src/main/java/net/sourceforge/pmd/properties/IntegerProperty.java index bc03221c334..6508fa3fbe8 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/properties/IntegerProperty.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/properties/IntegerProperty.java @@ -6,33 +6,17 @@ import static net.sourceforge.pmd.properties.ValueParserConstants.INTEGER_PARSER; -import java.util.Map; +import net.sourceforge.pmd.properties.builders.PropertyDescriptorBuilderConversionWrapper; +import net.sourceforge.pmd.properties.builders.SingleNumericPropertyBuilder; + /** - * Defines a datatype that supports single Integer property values within an - * upper and lower boundary. + * Defines a datatype that supports single Integer property values within an upper and lower boundary. * * @author Brian Remedios */ public final class IntegerProperty extends AbstractNumericProperty<Integer> { - /** Factory. */ - public static final PropertyDescriptorFactory<Integer> FACTORY // @formatter:off - = new SingleValuePropertyDescriptorFactory<Integer>(Integer.class, NUMBER_FIELD_TYPES_BY_KEY) { - - @Override - public IntegerProperty createWith(Map<PropertyDescriptorField, String> valuesById, boolean isDefinedExternally) { - final String[] minMax = minMaxFrom(valuesById); - return new IntegerProperty(nameIn(valuesById), - descriptionIn(valuesById), - INTEGER_PARSER.valueOf(minMax[0]), - INTEGER_PARSER.valueOf(minMax[1]), - INTEGER_PARSER.valueOf(defaultValueIn(valuesById)), - 0f, - isDefinedExternally); - } - }; // @formatter:on - /** * Constructor that limits itself to a single value within the specified limits. @@ -44,7 +28,7 @@ public IntegerProperty createWith(Map<PropertyDescriptorField, String> valuesByI * @param theDefault Default value * @param theUIOrder UI order * - * @throws IllegalArgumentException if min > max or one of the defaults is not between the bounds + * @throws IllegalArgumentException if {@literal min > max} or one of the defaults is not between the bounds */ public IntegerProperty(String theName, String theDescription, Integer min, Integer max, Integer theDefault, float theUIOrder) { @@ -71,4 +55,31 @@ protected Integer createFrom(String value) { } + static PropertyDescriptorBuilderConversionWrapper.SingleValue.Numeric<Integer, IntegerPBuilder> extractor() { + return new PropertyDescriptorBuilderConversionWrapper.SingleValue.Numeric<Integer, IntegerPBuilder>(Integer.class, ValueParserConstants.INTEGER_PARSER) { + @Override + protected IntegerPBuilder newBuilder(String name) { + return new IntegerPBuilder(name); + } + }; + } + + + public static IntegerPBuilder named(String name) { + return new IntegerPBuilder(name); + } + + + public static final class IntegerPBuilder extends SingleNumericPropertyBuilder<Integer, IntegerPBuilder> { + private IntegerPBuilder(String name) { + super(name); + } + + + @Override + public IntegerProperty build() { + return new IntegerProperty(name, description, lowerLimit, upperLimit, defaultValue, uiOrder, isDefinedInXML); + } + } + } diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/properties/LongMultiProperty.java b/pmd-core/src/main/java/net/sourceforge/pmd/properties/LongMultiProperty.java index cbc49746006..eb07bf17bcd 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/properties/LongMultiProperty.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/properties/LongMultiProperty.java @@ -4,11 +4,12 @@ package net.sourceforge.pmd.properties; -import static net.sourceforge.pmd.properties.ValueParserConstants.LONG_PARSER; - import java.util.Arrays; import java.util.List; -import java.util.Map; + +import net.sourceforge.pmd.properties.builders.MultiNumericPropertyBuilder; +import net.sourceforge.pmd.properties.builders.PropertyDescriptorBuilderConversionWrapper; + /** * Multi-valued long property. @@ -18,24 +19,6 @@ */ public final class LongMultiProperty extends AbstractMultiNumericProperty<Long> { - /** Factory. */ - public static final PropertyDescriptorFactory<List<Long>> FACTORY // @formatter:off - = new MultiValuePropertyDescriptorFactory<Long>(Long.class, NUMBER_FIELD_TYPES_BY_KEY) { - @Override - public LongMultiProperty createWith(Map<PropertyDescriptorField, String> valuesById, boolean isDefinedExternally) { - String[] minMax = minMaxFrom(valuesById); - char delimiter = delimiterIn(valuesById, DEFAULT_NUMERIC_DELIMITER); - List<Long> defaultValues = ValueParserConstants.parsePrimitives(defaultValueIn(valuesById), delimiter, LONG_PARSER); - return new LongMultiProperty(nameIn(valuesById), - descriptionIn(valuesById), - LONG_PARSER.valueOf(minMax[0]), - LONG_PARSER.valueOf(minMax[1]), - defaultValues, - 0f, - isDefinedExternally); - } - }; // @formatter:on - /** * Constructor using an array of defaults. @@ -47,7 +30,7 @@ public LongMultiProperty createWith(Map<PropertyDescriptorField, String> valuesB * @param defaultValues Array of defaults * @param theUIOrder UI order * - * @throws IllegalArgumentException if min > max or one of the defaults is not between the bounds + * @throws IllegalArgumentException if {@literal min > max} or one of the defaults is not between the bounds */ public LongMultiProperty(String theName, String theDescription, Long min, Long max, Long[] defaultValues, float theUIOrder) { @@ -72,7 +55,7 @@ private LongMultiProperty(String theName, String theDescription, Long min, Long * @param defaultValues List of defaults * @param theUIOrder UI order * - * @throws IllegalArgumentException if min > max or one of the defaults is not between the bounds + * @throws IllegalArgumentException if {@literal min > max} or one of the defaults is not between the bounds */ public LongMultiProperty(String theName, String theDescription, Long min, Long max, List<Long> defaultValues, float theUIOrder) { @@ -91,4 +74,36 @@ protected Long createFrom(String value) { return Long.valueOf(value); } + + static PropertyDescriptorBuilderConversionWrapper.MultiValue.Numeric<Long, LongMultiPBuilder> extractor() { + return new PropertyDescriptorBuilderConversionWrapper.MultiValue.Numeric<Long, LongMultiPBuilder>(Long.class, ValueParserConstants.LONG_PARSER) { + @Override + protected LongMultiPBuilder newBuilder(String name) { + return new LongMultiPBuilder(name); + } + }; + } + + + public static LongMultiPBuilder named(String name) { + return new LongMultiPBuilder(name); + } + + + public static final class LongMultiPBuilder + extends MultiNumericPropertyBuilder<Long, LongMultiPBuilder> { + + protected LongMultiPBuilder(String name) { + super(name); + } + + + @Override + public LongMultiProperty build() { + return new LongMultiProperty(name, description, lowerLimit, upperLimit, + defaultValues, uiOrder, isDefinedInXML); + } + } + + } diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/properties/LongProperty.java b/pmd-core/src/main/java/net/sourceforge/pmd/properties/LongProperty.java index 144123953e4..020dc50f3e3 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/properties/LongProperty.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/properties/LongProperty.java @@ -4,35 +4,19 @@ package net.sourceforge.pmd.properties; -import static net.sourceforge.pmd.properties.ValueParserConstants.LONG_PARSER; +import net.sourceforge.pmd.properties.builders.PropertyDescriptorBuilderConversionWrapper; +import net.sourceforge.pmd.properties.builders.SingleNumericPropertyBuilder; -import java.util.Map; /** * Single valued long property. * * @author Brian Remedios + * @author Clément Fournier * @version Refactored June 2017 (6.0.0) */ public final class LongProperty extends AbstractNumericProperty<Long> { - /** Factory. */ - public static final PropertyDescriptorFactory<Long> FACTORY // @formatter:off - = new SingleValuePropertyDescriptorFactory<Long>(Long.class, NUMBER_FIELD_TYPES_BY_KEY) { - @Override - public LongProperty createWith(Map<PropertyDescriptorField, String> valuesById, boolean isDefinedExternally) { - final String[] minMax = minMaxFrom(valuesById); - return new LongProperty(nameIn(valuesById), - descriptionIn(valuesById), - LONG_PARSER.valueOf(minMax[0]), - LONG_PARSER.valueOf(minMax[1]), - LONG_PARSER.valueOf(defaultValueIn(valuesById)), - 0f, - isDefinedExternally); - } - }; - // @formatter:on - /** * Constructor for LongProperty that limits itself to a single value within the specified limits. Converts string @@ -45,13 +29,13 @@ public LongProperty createWith(Map<PropertyDescriptorField, String> valuesById, * @param defaultStr Default value * @param theUIOrder UI order * - * @throws IllegalArgumentException if min > max or one of the defaults is not between the bounds + * @throws IllegalArgumentException if {@literal min > max} or one of the defaults is not between the bounds * @deprecated will be removed in 7.0.0 */ public LongProperty(String theName, String theDescription, String minStr, String maxStr, String defaultStr, float theUIOrder) { this(theName, theDescription, Long.valueOf(minStr), Long.valueOf(maxStr), - Long.valueOf(defaultStr), theUIOrder, false); + Long.valueOf(defaultStr), theUIOrder, false); } @@ -72,7 +56,7 @@ private LongProperty(String theName, String theDescription, Long min, Long max, * @param theDefault Default value * @param theUIOrder UI order * - * @throws IllegalArgumentException if min > max or one of the defaults is not between the bounds + * @throws IllegalArgumentException if {@literal min > max} or one of the defaults is not between the bounds */ public LongProperty(String theName, String theDescription, Long min, Long max, Long theDefault, float theUIOrder) { this(theName, theDescription, min, max, theDefault, theUIOrder, false); @@ -90,4 +74,32 @@ protected Long createFrom(String toParse) { return Long.valueOf(toParse); } + + static PropertyDescriptorBuilderConversionWrapper.SingleValue.Numeric<Long, LongPBuilder> extractor() { + return new PropertyDescriptorBuilderConversionWrapper.SingleValue.Numeric<Long, LongPBuilder>(Long.class, ValueParserConstants.LONG_PARSER) { + @Override + protected LongPBuilder newBuilder(String name) { + return new LongPBuilder(name); + } + }; + } + + + public static LongPBuilder named(String name) { + return new LongPBuilder(name); + } + + + public static final class LongPBuilder extends SingleNumericPropertyBuilder<Long, LongPBuilder> { + private LongPBuilder(String name) { + super(name); + } + + + @Override + public LongProperty build() { + return new LongProperty(name, description, lowerLimit, upperLimit, defaultValue, uiOrder, isDefinedInXML); + } + } + } diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/properties/MethodMultiProperty.java b/pmd-core/src/main/java/net/sourceforge/pmd/properties/MethodMultiProperty.java index 68c11a59364..bac24a7efbe 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/properties/MethodMultiProperty.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/properties/MethodMultiProperty.java @@ -9,39 +9,23 @@ import java.lang.reflect.Method; import java.util.Arrays; import java.util.List; -import java.util.Map; +import net.sourceforge.pmd.properties.builders.MultiPackagedPropertyBuilder; +import net.sourceforge.pmd.properties.builders.PropertyDescriptorBuilderConversionWrapper; import net.sourceforge.pmd.properties.modules.MethodPropertyModule; + /** - * Defines a property type that can specify multiple methods to use as part of a - * rule. + * Defines a property type that can specify multiple methods to use as part of a rule. * - * Rule developers can limit the rules to those within designated packages per - * the 'legalPackages' argument in the constructor which can be an array of - * partial package names, i.e., ["java.lang", "com.mycompany" ]. + * Rule developers can limit the rules to those within designated packages per the 'legalPackages' argument in the + * constructor which can be an array of partial package names, i.e., ["java.lang", "com.mycompany" ]. * * @author Brian Remedios * @version Refactored June 2017 (6.0.0) */ public final class MethodMultiProperty extends AbstractMultiPackagedProperty<Method> { - /** Factory. */ - public static final PropertyDescriptorFactory<List<Method>> FACTORY // @formatter:off - = new MultiValuePropertyDescriptorFactory<Method>(Method.class, PACKAGED_FIELD_TYPES_BY_KEY) { - @Override - public MethodMultiProperty createWith(Map<PropertyDescriptorField, String> valuesById, boolean isDefinedExternally) { - char delimiter = delimiterIn(valuesById, MULTI_VALUE_DELIMITER); - return new MethodMultiProperty(nameIn(valuesById), - descriptionIn(valuesById), - methodsFrom(defaultValueIn(valuesById)), - legalPackageNamesIn(valuesById, delimiter), - 0f, - isDefinedExternally); - } - - }; // @formatter:on - /** * Constructor for MethodMultiProperty using an array of defaults. @@ -79,7 +63,7 @@ public MethodMultiProperty(String theName, String theDescription, List<Method> t private MethodMultiProperty(String theName, String theDescription, List<Method> theDefaults, String[] legalPackageNames, float theUIOrder, boolean isDefinedExternally) { super(theName, theDescription, theDefaults, theUIOrder, isDefinedExternally, - new MethodPropertyModule(legalPackageNames, theDefaults)); + new MethodPropertyModule(legalPackageNames, theDefaults)); } @@ -98,14 +82,9 @@ private MethodMultiProperty(String theName, String theDescription, List<Method> public MethodMultiProperty(String theName, String theDescription, String methodDefaults, String[] legalPackageNames, float theUIOrder) { this(theName, theDescription, - methodsFrom(methodDefaults), - legalPackageNames, theUIOrder, - false); - } - - - private static List<Method> methodsFrom(String valueString) { - return ValueParserConstants.parsePrimitives(valueString, MULTI_VALUE_DELIMITER, METHOD_PARSER); + methodsFrom(methodDefaults), + legalPackageNames, theUIOrder, + false); } @@ -131,4 +110,38 @@ public Class<Method> type() { public List<Method> valueFrom(String valueString) throws IllegalArgumentException { return methodsFrom(valueString); } + + + private static List<Method> methodsFrom(String valueString) { + return ValueParserConstants.parsePrimitives(valueString, MULTI_VALUE_DELIMITER, METHOD_PARSER); + } + + + static PropertyDescriptorBuilderConversionWrapper.MultiValue.Packaged<Method, MethodMultiPBuilder> extractor() { + return new PropertyDescriptorBuilderConversionWrapper.MultiValue.Packaged<Method, MethodMultiPBuilder>(Method.class, ValueParserConstants.METHOD_PARSER) { + @Override + protected MethodMultiPBuilder newBuilder(String name) { + return new MethodMultiPBuilder(name); + } + }; + } + + + public static MethodMultiPBuilder named(String name) { + return new MethodMultiPBuilder(name); + } + + + public static final class MethodMultiPBuilder extends MultiPackagedPropertyBuilder<Method, MethodMultiPBuilder> { + private MethodMultiPBuilder(String name) { + super(name); + } + + + @Override + public MethodMultiProperty build() { + return new MethodMultiProperty(name, description, defaultValues, legalPackageNames, uiOrder, isDefinedInXML); + } + } + } diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/properties/MethodProperty.java b/pmd-core/src/main/java/net/sourceforge/pmd/properties/MethodProperty.java index fe92aa88fcc..66cc0bed814 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/properties/MethodProperty.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/properties/MethodProperty.java @@ -9,38 +9,23 @@ import java.lang.reflect.Method; import java.util.Collections; -import java.util.Map; +import net.sourceforge.pmd.properties.builders.PropertyDescriptorBuilderConversionWrapper; +import net.sourceforge.pmd.properties.builders.SinglePackagedPropertyBuilder; import net.sourceforge.pmd.properties.modules.MethodPropertyModule; + /** - * Defines a property type that can specify a single method to use as part of a - * rule. + * Defines a property type that can specify a single method to use as part of a rule. * - * <p>Rule developers can limit the rules to those within designated packages per - * the 'legalPackages' argument in the constructor which can be an array of - * partial package names, i.e., ["java.lang", "com.mycompany" ].</p> + * <p>Rule developers can limit the rules to those within designated packages per the 'legalPackages' argument in the + * constructor which can be an array of partial package names, i.e., ["java.lang", "com.mycompany" ].</p> * * @author Brian Remedios * @version Refactored June 2017 (6.0.0) */ public final class MethodProperty extends AbstractPackagedProperty<Method> { - /** Factory. */ - public static final PropertyDescriptorFactory<Method> FACTORY // @formatter:off - = new SingleValuePropertyDescriptorFactory<Method>(Method.class, PACKAGED_FIELD_TYPES_BY_KEY) { - @Override - public MethodProperty createWith(Map<PropertyDescriptorField, String> valuesById, boolean isDefinedExternally) { - char delimiter = delimiterIn(valuesById); - return new MethodProperty(nameIn(valuesById), - descriptionIn(valuesById), - METHOD_PARSER.valueOf(defaultValueIn(valuesById)), - legalPackageNamesIn(valuesById, delimiter), - 0f, - isDefinedExternally); - } - }; // @formatter:on - /** * Constructor for MethodProperty. @@ -61,7 +46,7 @@ public MethodProperty(String theName, String theDescription, Method theDefault, private MethodProperty(String theName, String theDescription, Method theDefault, String[] legalPackageNames, float theUIOrder, boolean isDefinedExternally) { super(theName, theDescription, theDefault, theUIOrder, isDefinedExternally, - new MethodPropertyModule(legalPackageNames, Collections.singletonList(theDefault))); + new MethodPropertyModule(legalPackageNames, Collections.singletonList(theDefault))); } @@ -79,7 +64,7 @@ private MethodProperty(String theName, String theDescription, Method theDefault, public MethodProperty(String theName, String theDescription, String defaultMethodStr, String[] legalPackageNames, float theUIOrder) { this(theName, theDescription, METHOD_PARSER.valueOf(defaultMethodStr), - legalPackageNames, theUIOrder, false); + legalPackageNames, theUIOrder, false); } @@ -101,4 +86,32 @@ public Method createFrom(String valueString) throws IllegalArgumentException { } + static PropertyDescriptorBuilderConversionWrapper.SingleValue.Packaged<Method, MethodPBuilder> extractor() { + return new PropertyDescriptorBuilderConversionWrapper.SingleValue.Packaged<Method, MethodPBuilder>(Method.class, ValueParserConstants.METHOD_PARSER) { + @Override + protected MethodPBuilder newBuilder(String name) { + return new MethodPBuilder(name); + } + }; + } + + + public static MethodPBuilder named(String name) { + return new MethodPBuilder(name); + } + + + public static final class MethodPBuilder extends SinglePackagedPropertyBuilder<Method, MethodPBuilder> { + private MethodPBuilder(String name) { + super(name); + } + + + @Override + public MethodProperty build() { + return new MethodProperty(name, description, defaultValue, legalPackageNames, uiOrder, isDefinedInXML); + } + } + + } diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/properties/MultiValuePropertyDescriptor.java b/pmd-core/src/main/java/net/sourceforge/pmd/properties/MultiValuePropertyDescriptor.java index 4d047a0b140..6d70fd44a79 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/properties/MultiValuePropertyDescriptor.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/properties/MultiValuePropertyDescriptor.java @@ -6,6 +6,7 @@ import java.util.List; + /** * Specializes property descriptors for multi valued descriptors. For this type of property, the return value of the * {@link #type()} method must be the class literal of the type parameter of this interface, which is the type of the diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/properties/MultiValuePropertyDescriptorFactory.java b/pmd-core/src/main/java/net/sourceforge/pmd/properties/MultiValuePropertyDescriptorFactory.java deleted file mode 100644 index 08693bd6107..00000000000 --- a/pmd-core/src/main/java/net/sourceforge/pmd/properties/MultiValuePropertyDescriptorFactory.java +++ /dev/null @@ -1,40 +0,0 @@ -/** - * BSD-style license; for more info see http://pmd.sourceforge.net/license.html - */ - -package net.sourceforge.pmd.properties; - -import java.util.List; -import java.util.Map; - -/** - * Concrete implementation of a property descriptor factory for multi valued properties. - * - * @param <T> Type of the property - * - * @author Clément Fournier - * @since 6.0.0 - */ -public abstract class MultiValuePropertyDescriptorFactory<T> extends AbstractPropertyDescriptorFactory<List<T>> { - - public MultiValuePropertyDescriptorFactory(Class<T> theValueType) { - super(theValueType); - } - - - public MultiValuePropertyDescriptorFactory(Class<T> theValueType, - Map<PropertyDescriptorField, Boolean> additionalFieldTypesByKey) { - super(theValueType, additionalFieldTypesByKey); - } - - - @Override - protected abstract MultiValuePropertyDescriptor<T> createWith(Map<PropertyDescriptorField, String> valuesById, - boolean isDefinedExternally); - - - @Override - public boolean isMultiValue() { - return true; - } -} diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/properties/NumericPropertyDescriptor.java b/pmd-core/src/main/java/net/sourceforge/pmd/properties/NumericPropertyDescriptor.java index 82cac5e9d5b..99959662a97 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/properties/NumericPropertyDescriptor.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/properties/NumericPropertyDescriptor.java @@ -4,25 +4,16 @@ package net.sourceforge.pmd.properties; -import static net.sourceforge.pmd.properties.PropertyDescriptorField.MAX; -import static net.sourceforge.pmd.properties.PropertyDescriptorField.MIN; - -import java.util.Map; - /** - * Defines a descriptor type whose instance values are required to lie within - * specified upper and lower limits. + * Defines a descriptor type whose instance values are required to lie within specified upper and lower limits. * * @param <T> type of the property value * * @author Brian Remedios + * @author Clément Fournier */ public interface NumericPropertyDescriptor<T> extends PropertyDescriptor<T> { - Map<PropertyDescriptorField, Boolean> NUMBER_FIELD_TYPES_BY_KEY - = ExpectedFieldsBuilder.instance().put(MIN, true).put(MAX, true).build(); - - /** * Returns the maximum value that instances of the property can have. * diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/properties/PackagedPropertyDescriptor.java b/pmd-core/src/main/java/net/sourceforge/pmd/properties/PackagedPropertyDescriptor.java index a58d21e4e0e..bfc42fc402f 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/properties/PackagedPropertyDescriptor.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/properties/PackagedPropertyDescriptor.java @@ -4,10 +4,6 @@ package net.sourceforge.pmd.properties; -import static net.sourceforge.pmd.properties.PropertyDescriptorField.LEGAL_PACKAGES; - -import java.util.Map; - /** * Defines a property descriptor type whose values can be described by qualified names and thus restricted to only some * packages. These typically use values such as {@link Class} and {@link java.lang.reflect.Method}. @@ -18,11 +14,6 @@ */ public interface PackagedPropertyDescriptor<T> extends PropertyDescriptor<T> { - /** Required additional fields. */ - Map<PropertyDescriptorField, Boolean> PACKAGED_FIELD_TYPES_BY_KEY - = AbstractPropertyDescriptorFactory.expectedFieldTypesWith(new PropertyDescriptorField[] {LEGAL_PACKAGES}, - new Boolean[] {false}); - /** Delimiter used to separate package names. */ char PACKAGE_NAME_DELIMITER = ' '; /** Delimiter used to separate multiple values if this descriptor is multi valued. */ diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/properties/PropertyDescriptor.java b/pmd-core/src/main/java/net/sourceforge/pmd/properties/PropertyDescriptor.java index d154888eb8b..09d501e01cb 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/properties/PropertyDescriptor.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/properties/PropertyDescriptor.java @@ -4,50 +4,33 @@ package net.sourceforge.pmd.properties; -import static net.sourceforge.pmd.properties.PropertyDescriptorField.DEFAULT_VALUE; -import static net.sourceforge.pmd.properties.PropertyDescriptorField.DELIMITER; -import static net.sourceforge.pmd.properties.PropertyDescriptorField.DESCRIPTION; -import static net.sourceforge.pmd.properties.PropertyDescriptorField.NAME; - import java.util.Map; import net.sourceforge.pmd.Rule; import net.sourceforge.pmd.RuleSetWriter; + /** - * Property value descriptor that defines the use & requirements for setting - * property values for use within PMD and any associated GUIs. While concrete - * descriptor instances are static and immutable they provide validation, + * Property value descriptor that defines the use & requirements for setting property values for use within PMD and + * any associated GUIs. While concrete descriptor instances are static and immutable they provide validation, * serialization, and default values for any specific datatypes. * - * <p>This interface is primarily specialized according to whether the property is - * multi-valued or single-valued, see {@link SingleValuePropertyDescriptor} and - * {@link MultiValuePropertyDescriptor}. + * <p>This interface is primarily specialized according to whether the property is multi-valued or single-valued, see + * {@link SingleValuePropertyDescriptor} and {@link MultiValuePropertyDescriptor}. * - * <p>Several interfaces further specialize the behaviour of descriptors to accommodate - * specific types of descriptors, see {@link NumericPropertyDescriptor} and - * {@link EnumeratedPropertyDescriptor}. + * <p>Several interfaces further specialize the behaviour of descriptors to accommodate specific types of descriptors, + * see {@link NumericPropertyDescriptor} and {@link EnumeratedPropertyDescriptor}. * * @param <T> type of the property's value. This is a list type for multi-valued properties. * * @author Brian Remedios + * @author Clément Fournier * @version Refactored June 2017 (6.0.0) */ public interface PropertyDescriptor<T> extends Comparable<PropertyDescriptor<?>> { - /** Default expected fields. Unmodifiable. */ - Map<PropertyDescriptorField, Boolean> CORE_EXPECTED_FIELDS - = ExpectedFieldsBuilder.instance() - .put(NAME, true) - .put(DESCRIPTION, true) - .put(DEFAULT_VALUE, true) - .put(DELIMITER, false) - .build(); - - /** - * The name of the property without spaces as it serves as the key into the - * property map. + * The name of the property without spaces as it serves as the key into the property map. * * @return String */ @@ -55,8 +38,7 @@ public interface PropertyDescriptor<T> extends Comparable<PropertyDescriptor<?>> /** - * Describes the property and the role it plays within the rule it is - * specified for. Could be used in a tooltip. + * Describes the property and the role it plays within the rule it is specified for. Could be used in a tooltip. * * @return String */ @@ -64,8 +46,8 @@ public interface PropertyDescriptor<T> extends Comparable<PropertyDescriptor<?>> /** - * Denotes the value datatype. For multi value properties, this is not the - * List class but the list's component class. + * Denotes the value datatype. For multi value properties, this is not the List class but the list's component + * class. * * @return Class literal of the value type */ @@ -75,10 +57,9 @@ public interface PropertyDescriptor<T> extends Comparable<PropertyDescriptor<?>> /** * Returns whether the property is multi-valued, i.e. an array of strings, * - * <p>As unary property rule properties will return a value of one, you must - * use the get/setProperty accessors when working with the actual values. - * When working with multi-value properties then the get/setProperties - * accessors must be used.</p> + * <p>As unary property rule properties will return a value of one, you must use the get/setProperty accessors when + * working with the actual values. When working with multi-value properties then the get/setProperties accessors + * must be used.</p> * * @return boolean */ @@ -86,8 +67,7 @@ public interface PropertyDescriptor<T> extends Comparable<PropertyDescriptor<?>> /** - * Default value to use when the user hasn't specified one or when they wish - * to revert to a known-good state. + * Default value to use when the user hasn't specified one or when they wish to revert to a known-good state. * * @return Object */ @@ -95,8 +75,8 @@ public interface PropertyDescriptor<T> extends Comparable<PropertyDescriptor<?>> /** - * Validation function that returns a diagnostic error message for a sample - * property value. Returns null if the value is acceptable. + * Validation function that returns a diagnostic error message for a sample property value. Returns null if the + * value is acceptable. * * @param value The value to check. * @@ -106,15 +86,12 @@ public interface PropertyDescriptor<T> extends Comparable<PropertyDescriptor<?>> /** - * Denotes the relative order the property field should occupy if we are - * using an auto-generated UI to display and edit property values. If the - * value returned has a non-zero fractional part then this is can be used to - * place adjacent fields on the same row. + * Denotes the relative order the property field should occupy if we are using an auto-generated UI to display and + * edit property values. If the value returned has a non-zero fractional part then this is can be used to place + * adjacent fields on the same row. * - * <p>Example:<br> - * name -> 0.0 description 1.0 minValue -> 2.0 maxValue -> 2.1 - * </p> - * ..would have their fields placed like:<br> + * <p>Example:<br> name -> 0.0 description 1.0 minValue -> 2.0 maxValue -> 2.1 </p> ..would have their + * fields placed like:<br> * * <code>name: [ ] description: [ ] minimum: [ ] maximum: [ ]</code> * @@ -129,15 +106,13 @@ public interface PropertyDescriptor<T> extends Comparable<PropertyDescriptor<?>> * @param propertyString The string to parse * * @return The value represented by the string - * * @throws IllegalArgumentException if the given string cannot be parsed */ T valueFrom(String propertyString) throws IllegalArgumentException; /** - * Formats the object onto a string suitable for storage within the property - * map. + * Formats the object onto a string suitable for storage within the property map. * * @param value Object * @@ -147,8 +122,8 @@ public interface PropertyDescriptor<T> extends Comparable<PropertyDescriptor<?>> /** - * A convenience method that returns an error string if the rule holds onto - * a property value that has a problem. Returns null otherwise. + * A convenience method that returns an error string if the rule holds onto a property value that has a problem. + * Returns null otherwise. * * @param rule Rule * @@ -158,9 +133,8 @@ public interface PropertyDescriptor<T> extends Comparable<PropertyDescriptor<?>> /** - * If the datatype is a String then return the preferred number of rows to - * allocate in the text widget, returns a value of one for all other types. - * Useful for multi-line XPATH editors. + * If the datatype is a String then return the preferred number of rows to allocate in the text widget, returns a + * value of one for all other types. Useful for multi-line XPATH editors. * * @return int */ @@ -168,8 +142,7 @@ public interface PropertyDescriptor<T> extends Comparable<PropertyDescriptor<?>> /** - * Returns a map representing all the property attributes of the receiver in - * string form. + * Returns a map representing all the property attributes of the receiver in string form. * * @return map */ @@ -177,10 +150,9 @@ public interface PropertyDescriptor<T> extends Comparable<PropertyDescriptor<?>> /** - * True if this descriptor was defined in the ruleset xml. This precision is - * necessary for the {@link RuleSetWriter} to write out the property correctly: - * if it was defined externally, then its definition must be written out, otherwise - * only its value. + * True if this descriptor was defined in the ruleset xml. This precision is necessary for the {@link RuleSetWriter} + * to write out the property correctly: if it was defined externally, then its definition must be written out, + * otherwise only its value. * * @return True if the descriptor was defined in xml */ diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/properties/PropertyDescriptorFactory.java b/pmd-core/src/main/java/net/sourceforge/pmd/properties/PropertyDescriptorFactory.java deleted file mode 100755 index a21517322ee..00000000000 --- a/pmd-core/src/main/java/net/sourceforge/pmd/properties/PropertyDescriptorFactory.java +++ /dev/null @@ -1,56 +0,0 @@ -/** - * BSD-style license; for more info see http://pmd.sourceforge.net/license.html - */ - -package net.sourceforge.pmd.properties; - -import java.util.Map; -import java.util.Set; - -/** - * A factory to create {@link PropertyDescriptor}s based on a map of values. - * - * @param <T> The type of values property descriptor returned by this factory. This can be a list. - * - * @author Brian Remedios - * @version Refactored July 2017 (6.0.0) - */ -public interface PropertyDescriptorFactory<T> { - - /** - * The type of the value of the {@link PropertyDescriptor} created by this - * factory. - * - * @return The type of the value. - */ - Class<?> valueType(); - - - /** - * Returns true if the built property descriptor is multi-valued. - * - * @return True if the built property descriptor is multi-valued. - */ - boolean isMultiValue(); - - - /** - * Denote the identifiers of all fields that contribute to building - * this descriptor. Control of the required fields is performed - * inside the factory. - * - * @return A set of field identifiers - */ - Set<PropertyDescriptorField> expectableFields(); - - - /** - * Create a property descriptor of the appropriate type using the values - * provided. - * - * @param valuesById the map of values - * - * @return A new and initialized {@link PropertyDescriptor} - */ - PropertyDescriptor<T> createWith(Map<PropertyDescriptorField, String> valuesById); -} diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/properties/PropertyDescriptorField.java b/pmd-core/src/main/java/net/sourceforge/pmd/properties/PropertyDescriptorField.java index 745771bdb27..e1d3f1dd09c 100755 --- a/pmd-core/src/main/java/net/sourceforge/pmd/properties/PropertyDescriptorField.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/properties/PropertyDescriptorField.java @@ -4,15 +4,18 @@ package net.sourceforge.pmd.properties; +import java.util.Objects; + import net.sourceforge.pmd.RuleSetFactory; + /** - * Field names for parsing the properties out of the ruleset xml files. These are intended to be used as the keys to - * a map of fields to values. Most property descriptors can be built directly from such a map using their factory. + * Field names for parsing the properties out of the ruleset xml files. These are intended to be used as the keys to a + * map of fields to values. Most property descriptors can be built directly from such a map using their factory. * * @author Brian Remedios * @see RuleSetFactory - * @see PropertyDescriptorUtil + * @see PropertyTypeId */ public enum PropertyDescriptorField { @@ -22,6 +25,8 @@ public enum PropertyDescriptorField { NAME("name"), /** The description of the property. */ DESCRIPTION("description"), + /** The UI order. */ + UI_ORDER("uiOrder"), /** The default value. */ DEFAULT_VALUE("value"), /** For multi-valued properties, this defines the delimiter of the single values. */ @@ -30,10 +35,7 @@ public enum PropertyDescriptorField { MIN("min"), /** The maximum allowed value for numeric properties. */ MAX("max"), - /** - * To limit the range of valid values, package names. - * @see PackagedPro - */ + /** To limit the range of valid values, package names. */ LEGAL_PACKAGES("legalPackages"), /** Labels for enumerated properties. */ LABELS("labels"), @@ -65,4 +67,14 @@ public String toString() { return attributeName(); } + + public static PropertyDescriptorField getConstant(String name) { + for (PropertyDescriptorField f : values()) { + if (Objects.equals(f.attributeName, name)) { + return f; + } + } + return null; + } + } diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/properties/PropertyDescriptorUtil.java b/pmd-core/src/main/java/net/sourceforge/pmd/properties/PropertyDescriptorUtil.java deleted file mode 100755 index afc0dea3f65..00000000000 --- a/pmd-core/src/main/java/net/sourceforge/pmd/properties/PropertyDescriptorUtil.java +++ /dev/null @@ -1,89 +0,0 @@ -/** - * BSD-style license; for more info see http://pmd.sourceforge.net/license.html - */ - -package net.sourceforge.pmd.properties; - -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; - -/** - * Utility class allowing to find the factory of a specific type of descriptor. That's used to define descriptors in - * the xml, eg for xpath rules. - * - * @author Brian Remedios - */ -public final class PropertyDescriptorUtil { - - private static final Map<String, PropertyDescriptorFactory<?>> DESCRIPTOR_FACTORIES_BY_TYPE; - - - static { - Map<String, PropertyDescriptorFactory<?>> temp = new HashMap<>(18); - temp.put("Boolean", BooleanProperty.FACTORY); - temp.put("List<Boolean>", BooleanMultiProperty.FACTORY); - - temp.put("String", StringProperty.FACTORY); - temp.put("List<String>", StringMultiProperty.FACTORY); - temp.put("Character", CharacterProperty.FACTORY); - temp.put("List<Character>", CharacterMultiProperty.FACTORY); - - - temp.put("Integer", IntegerProperty.FACTORY); - temp.put("List<Integer>", IntegerMultiProperty.FACTORY); - temp.put("Long", LongProperty.FACTORY); - temp.put("List<Long>", LongMultiProperty.FACTORY); - temp.put("Float", FloatProperty.FACTORY); - temp.put("List<Float>", FloatMultiProperty.FACTORY); - temp.put("Double", DoubleProperty.FACTORY); - temp.put("List<Double>", DoubleMultiProperty.FACTORY); - // temp.put("Enum", EnumeratedProperty.FACTORY); // TODO:cf implement that - // temp.put("List<Enum>", EnumeratedMultiProperty.FACTORY); - - temp.put("Class", TypeProperty.FACTORY); - temp.put("List<Class>", TypeMultiProperty.FACTORY); - temp.put("Method", MethodProperty.FACTORY); - temp.put("List<Method>", MethodMultiProperty.FACTORY); - - temp.put("File", FileProperty.FACTORY); - - DESCRIPTOR_FACTORIES_BY_TYPE = Collections.unmodifiableMap(temp); - } - - - private PropertyDescriptorUtil() { } - - - /** - * Gets the factory for the descriptor identified by the string id. - * - * @param typeId The identifier of the type - * - * @return The factory used to build new instances of a descriptor - */ - public static PropertyDescriptorFactory<?> factoryFor(String typeId) { - return DESCRIPTOR_FACTORIES_BY_TYPE.get(typeId); - } - - - /** - * Gets the string representation of this type, as it should be given when defining a descriptor in the xml. - * - * @param valueType The type to look for - * @param multiValue Whether the descriptor is multivalued or not - * - * @return The type id - */ - public static String typeIdFor(Class<?> valueType, boolean multiValue) { - - for (Map.Entry<String, PropertyDescriptorFactory<?>> entry : DESCRIPTOR_FACTORIES_BY_TYPE.entrySet()) { - if (entry.getValue().valueType() == valueType && entry.getValue().isMultiValue() == multiValue) { - return entry.getKey(); - } - } - return null; - } - - -} diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/properties/PropertySource.java b/pmd-core/src/main/java/net/sourceforge/pmd/properties/PropertySource.java index 2f7af354d0f..7b24644164a 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/properties/PropertySource.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/properties/PropertySource.java @@ -8,16 +8,26 @@ import java.util.Map; import java.util.Set; + /** - * Any entity that manages a list of properties is a {@link PropertySource}. - * These are e.g. Rules and Renderers. + * Entity that manages a list of properties. Properties are described by + * {@linkplain PropertyDescriptor property descriptors}. A property source + * maintains a mapping of property descriptors to property values. The value + * of a property can be set by {@link #setProperty(PropertyDescriptor, Object)}. + * If the property wasn't set with this method, then the property is assumed + * to take a default value, which is specified by {@linkplain PropertyDescriptor#defaultValue() the property descriptor}. + * + * <p>Bad configuration of the properties may be reported by {@link #dysfunctionReason()}. + * + * <p>Notable instances of this interface are {@linkplain net.sourceforge.pmd.Rule rules} and + * {@linkplain net.sourceforge.pmd.renderers.Renderer renderers}. * * @author Brian Remedios */ public interface PropertySource { /** - * Define a new property via a PropertyDescriptor. + * Defines a new property. Properties must be defined before they can be set a value. * * @param propertyDescriptor The property descriptor. * @@ -26,6 +36,15 @@ public interface PropertySource { void definePropertyDescriptor(PropertyDescriptor<?> propertyDescriptor) throws IllegalArgumentException; + /** + * Gets the name of this property source. This is e.g. the name + * of the rule or renderer. + * + * @return The name + */ + String getName(); + + /** * Get the PropertyDescriptor for the given property name. * @@ -37,8 +56,8 @@ public interface PropertySource { /** - * Get the PropertyDescriptors for all defined properties. The properties - * are returned sorted by UI order. + * Get the descriptors of all defined properties. + * The properties are returned sorted by UI order. * * @return The PropertyDescriptors in UI order. */ @@ -46,7 +65,16 @@ public interface PropertySource { /** - * Get the typed value for the given property. Multi valued properties return immutable lists. + * Returns a modifiable list of the property descriptors + * that don't use default values. + * + * @return The descriptors that don't use default values + */ + List<PropertyDescriptor<?>> getOverriddenPropertyDescriptors(); + + /** + * Get the typed value for the given property. + * Multi valued properties return immutable lists. * * @param <T> The underlying type of the property descriptor. * @param propertyDescriptor The property descriptor. @@ -57,7 +85,18 @@ public interface PropertySource { /** - * Set the property value specified (will be type-checked) + * Returns true if the given property has been set to a value + * somewhere in the XML. + * + * @param propertyDescriptor The descriptor + * + * @return True if the property has been set + */ + boolean isPropertyOverridden(PropertyDescriptor<?> propertyDescriptor); + + /** + * Set the property value specified. This is also referred to as "overriding" + * the (default) value of a property. * * @param <T> The underlying type of the property descriptor. * @param propertyDescriptor The property descriptor. @@ -68,6 +107,7 @@ public interface PropertySource { /** * Sets the value of a multi value property descriptor with a variable number of arguments. + * This is also referred to as "overriding" the (default) value of a property. * * @param propertyDescriptor The property descriptor for which to add a value * @param values Values @@ -77,20 +117,36 @@ public interface PropertySource { /** - * Returns all the current property values for the receiver or an immutable - * empty map if none are specified. + * Returns an unmodifiable map of descriptors to property values + * for the current receiver. The returned map has an entry for + * every defined descriptor ({@link #getPropertyDescriptors()}), + * if they were not specified explicitly, then default values are + * used. * - * @return all current property values or a empty map. + * @return An unmodifiable map of descriptors to property values */ Map<PropertyDescriptor<?>, Object> getPropertiesByPropertyDescriptor(); /** - * Returns whether this Rule has the specified PropertyDescriptor. + * Returns a modifiable map of the property descriptors + * that don't use default values, to their overridden value. + * Modifications on the returned map don't affect this property + * source. + * + * @return The descriptors that don't use default values + */ + Map<PropertyDescriptor<?>, Object> getOverriddenPropertiesByPropertyDescriptor(); + + + /** + * Returns whether the specified property is defined on this property source, + * in which case it can be set or retrieved with {@link #getProperty(PropertyDescriptor)} + * and {@link #setProperty(PropertyDescriptor, Object)}. * - * @param descriptor The PropertyDescriptor for which to check. + * @param descriptor The descriptor to look for * - * @return boolean <code>true</code> if the descriptor is present, <code>false</code> otherwise. + * @return {@code true} if the descriptor is defined, {@code false} otherwise. */ boolean hasDescriptor(PropertyDescriptor<?> descriptor); @@ -99,33 +155,39 @@ public interface PropertySource { * Returns whether this Rule uses default values for properties. * * @return boolean <code>true</code> if the properties all have default values, <code>false</code> otherwise. + * + * @deprecated Has no real utility, will be removed by 7.0.0 */ + @Deprecated boolean usesDefaultValues(); /** - * Clears out any user-specified value for the property allowing it to use - * the default value in the descriptor. + * Clears out any user-specified value for the property allowing it to use the default value in the descriptor. * * @param desc the property to clear out + * + * @deprecated Has no real utility, and the name is confusing, will be removed by 7.0.0 */ + @Deprecated void useDefaultValueFor(PropertyDescriptor<?> desc); /** - * Return the properties that are effectively ignored due to the - * configuration of the rule and values held by other properties. This can - * be used to disable corresponding widgets in a UI. + * Return the properties that are effectively ignored due to the configuration of the rule and values held by other + * properties. This can be used to disable corresponding widgets in a UI. * * @return the properties that are ignored + * @deprecated Has no real utility, will be removed by 7.0.0 */ + @Deprecated Set<PropertyDescriptor<?>> ignoredProperties(); /** - * Returns a description of why the receiver may be dysfunctional. Usually - * due to missing property values or some kind of conflict between values. - * Returns null if the receiver is ok. + * Returns a description of why the receiver may be dysfunctional. + * Usually due to missing property values or some kind of conflict + * between values. Returns null if the receiver is ok. * * @return String */ diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/properties/PropertyTypeId.java b/pmd-core/src/main/java/net/sourceforge/pmd/properties/PropertyTypeId.java new file mode 100644 index 00000000000..dfe151feada --- /dev/null +++ b/pmd-core/src/main/java/net/sourceforge/pmd/properties/PropertyTypeId.java @@ -0,0 +1,204 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.properties; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import net.sourceforge.pmd.properties.builders.PropertyDescriptorBuilderConversionWrapper; +import net.sourceforge.pmd.properties.builders.PropertyDescriptorExternalBuilder; + + +/** + * Enumerates the properties that can be built from the XML. Defining a property in + * the XML requires the {@code type} attribute, which acts as a mnemonic for the type + * of the property that should be built. The mapping between the values of this attribute + * and the concrete property that is built is encoded in the constants of this enum. + * + * @author Clément Fournier + * @see PropertyDescriptorExternalBuilder + * @since 6.0.0 + */ +public enum PropertyTypeId { + BOOLEAN("Boolean", BooleanProperty.extractor(), ValueParserConstants.BOOLEAN_PARSER), + BOOLEAN_LIST("List[Boolean]", BooleanMultiProperty.extractor(), ValueParserConstants.BOOLEAN_PARSER), + + STRING("String", StringProperty.extractor(), ValueParserConstants.STRING_PARSER), + STRING_LIST("List[String]", StringMultiProperty.extractor(), ValueParserConstants.STRING_PARSER), + CHARACTER("Character", CharacterProperty.extractor(), ValueParserConstants.CHARACTER_PARSER), + CHARACTER_LIST("List[Character]", CharacterMultiProperty.extractor(), ValueParserConstants.CHARACTER_PARSER), + + REGEX("Regex", RegexProperty.extractor(), ValueParserConstants.REGEX_PARSER), + + INTEGER("Integer", IntegerProperty.extractor(), ValueParserConstants.INTEGER_PARSER), + INTEGER_LIST("List[Integer]", IntegerMultiProperty.extractor(), ValueParserConstants.INTEGER_PARSER), + LONG("Long", LongProperty.extractor(), ValueParserConstants.LONG_PARSER), + LONG_LIST("List[Long]", LongMultiProperty.extractor(), ValueParserConstants.LONG_PARSER), + FLOAT("Float", FloatProperty.extractor(), ValueParserConstants.FLOAT_PARSER), + FLOAT_LIST("List[Float]", FloatMultiProperty.extractor(), ValueParserConstants.FLOAT_PARSER), + DOUBLE("Double", DoubleProperty.extractor(), ValueParserConstants.DOUBLE_PARSER), + DOUBLE_LIST("List[Double]", DoubleMultiProperty.extractor(), ValueParserConstants.DOUBLE_PARSER), + // ENUM("Enum", EnumeratedProperty.FACTORY), // TODO:cf we need new syntax in the xml to support that + // ENUM_LIST("List[Enum]", EnumeratedMultiProperty.FACTORY), + + CLASS("Class", TypeProperty.extractor(), ValueParserConstants.CLASS_PARSER), + CLASS_LIST("List[Class]", TypeMultiProperty.extractor(), ValueParserConstants.CLASS_PARSER); + + + private static final Map<String, PropertyTypeId> CONSTANTS_BY_MNEMONIC; + private final String stringId; + private final PropertyDescriptorExternalBuilder<?> factory; + private final ValueParser<?> valueParser; + + static { + Map<String, PropertyTypeId> temp = new HashMap<>(); + for (PropertyTypeId id : values()) { + temp.put(id.stringId, id); + } + CONSTANTS_BY_MNEMONIC = Collections.unmodifiableMap(temp); + } + + + PropertyTypeId(String id, PropertyDescriptorExternalBuilder<?> factory, ValueParser<?> valueParser) { + this.stringId = id; + this.factory = factory; + this.valueParser = valueParser; + } + + + /** + * Gets the value of the type attribute represented by this constant. + * + * @return The string id + */ + public String getStringId() { + return stringId; + } + + + /** + * Gets the factory associated to the type id, that can build the + * property from strings extracted from the XML. + * + * @return The factory + */ + public PropertyDescriptorExternalBuilder<?> getFactory() { + return factory; + } + + + /** + * Returns true if the property corresponding to this factory is numeric, + * which means it can be safely cast to a {@link NumericPropertyDescriptor}. + * + * @return whether the property is numeric + */ + public boolean isPropertyNumeric() { + return factory instanceof PropertyDescriptorBuilderConversionWrapper.SingleValue.Numeric + || factory instanceof PropertyDescriptorBuilderConversionWrapper.MultiValue.Numeric; + } + + + /** + * Returns true if the property corresponding to this factory is packaged, + * which means it can be safely cast to a {@link PackagedPropertyDescriptor}. + * + * @return whether the property is packaged + */ + public boolean isPropertyPackaged() { + return factory instanceof PropertyDescriptorBuilderConversionWrapper.SingleValue.Packaged + || factory instanceof PropertyDescriptorBuilderConversionWrapper.MultiValue.Packaged; + } + + + /** + * Returns true if the property corresponding to this factory takes + * lists of values as its value. + * + * @return whether the property is multivalue + */ + public boolean isPropertyMultivalue() { + return factory.isMultiValue(); + } + + + /** + * Returns the value type of the property corresponding to this factory. + * This is the component type of the list if the property is multivalued. + * + * @return The value type of the property + */ + public Class<?> propertyValueType() { + return factory.valueType(); + } + + + /** + * Gets the object used to parse the values of this property from a string. + * If the property is multivalued, the parser only parses individual components + * of the list. A list parser can be obtained with {@link ValueParserConstants#multi(ValueParser, char)}. + * + * @return The value parser + */ + public ValueParser<?> getValueParser() { + return valueParser; + } + + + /** + * Returns the full mappings from type ids to enum constants. + * + * @return The full mapping. + */ + public static Map<String, PropertyTypeId> typeIdsToConstants() { + return CONSTANTS_BY_MNEMONIC; + } + + + /** + * Gets the factory for the descriptor identified by the string id. + * + * @param stringId The identifier of the type + * + * @return The factory used to build new instances of a descriptor + */ + public static PropertyDescriptorExternalBuilder<?> factoryFor(String stringId) { + PropertyTypeId cons = CONSTANTS_BY_MNEMONIC.get(stringId); + return cons == null ? null : cons.factory; + } + + + /** + * Gets the enum constant corresponding to the given mnemonic. + * + * @param stringId A mnemonic for the property type + * + * @return A PropertyTypeId + */ + public static PropertyTypeId lookupMnemonic(String stringId) { + return CONSTANTS_BY_MNEMONIC.get(stringId); + } + + + /** + * Gets the string representation of this type, as it should be given + * when defining a descriptor in the xml. + * + * @param valueType The type to look for + * @param multiValue Whether the descriptor is multivalued or not + * + * @return The string id + */ + public static String typeIdFor(Class<?> valueType, boolean multiValue) { + for (Map.Entry<String, PropertyTypeId> entry : CONSTANTS_BY_MNEMONIC.entrySet()) { + if (entry.getValue().propertyValueType() == valueType + && entry.getValue().isPropertyMultivalue() == multiValue) { + return entry.getKey(); + } + } + return null; + } +} diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/properties/RegexProperty.java b/pmd-core/src/main/java/net/sourceforge/pmd/properties/RegexProperty.java new file mode 100644 index 00000000000..c7559693b91 --- /dev/null +++ b/pmd-core/src/main/java/net/sourceforge/pmd/properties/RegexProperty.java @@ -0,0 +1,87 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.properties; + +import java.util.regex.Pattern; + +import net.sourceforge.pmd.properties.builders.PropertyDescriptorBuilderConversionWrapper; +import net.sourceforge.pmd.properties.builders.PropertyDescriptorBuilderConversionWrapper.SingleValue; +import net.sourceforge.pmd.properties.builders.SingleValuePropertyBuilder; + + +/** + * Property which has a regex pattern as a value. This property has no multi-valued + * variant, since it would be ambiguous whether the delimiters are part of the regex + * or not. + * + * @author Clément Fournier + * @since 6.2.0 + */ +public final class RegexProperty extends AbstractSingleValueProperty<Pattern> { + + RegexProperty(String theName, String theDescription, Pattern theDefault, float theUIOrder, boolean isDefinedExternally) { + super(theName, theDescription, theDefault, theUIOrder, isDefinedExternally); + } + + + @Override + protected Pattern createFrom(String toParse) { + return Pattern.compile(toParse); + } + + + @Override + public Class<Pattern> type() { + return Pattern.class; + } + + + static SingleValue<Pattern, RegexPBuilder> extractor() { + return new PropertyDescriptorBuilderConversionWrapper.SingleValue<Pattern, RegexPBuilder>(Pattern.class, ValueParserConstants.REGEX_PARSER) { + @Override + protected RegexPBuilder newBuilder(String name) { + return new RegexPBuilder(name); + } + }; + } + + + /** + * Creates a new builder for a regex property. + * + * @param name The name of the property + * + * @return A new builder + */ + public static RegexPBuilder named(String name) { + return new RegexPBuilder(name); + } + + /** Builder for a {@link RegexProperty}. */ + public static final class RegexPBuilder extends SingleValuePropertyBuilder<Pattern, RegexProperty.RegexPBuilder> { + private RegexPBuilder(String name) { + super(name); + } + + + /** + * Specify a default pattern for the property. + * The argument must be a valid regex pattern. + * + * @param val Regex pattern + * + * @return The same builder + */ + public RegexPBuilder defaultValue(String val) { + return super.defaultValue(Pattern.compile(val)); + } + + + @Override + public RegexProperty build() { + return new RegexProperty(this.name, this.description, this.defaultValue, this.uiOrder, isDefinedInXML); + } + } +} diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/properties/SingleValuePropertyDescriptorFactory.java b/pmd-core/src/main/java/net/sourceforge/pmd/properties/SingleValuePropertyDescriptorFactory.java deleted file mode 100644 index 78967158ef1..00000000000 --- a/pmd-core/src/main/java/net/sourceforge/pmd/properties/SingleValuePropertyDescriptorFactory.java +++ /dev/null @@ -1,39 +0,0 @@ -/** - * BSD-style license; for more info see http://pmd.sourceforge.net/license.html - */ - -package net.sourceforge.pmd.properties; - -import java.util.Map; - -/** - * Concrete implementation of a property descriptor factory for single valued properties. - * - * @param <T> Type of the property - * - * @author Clément Fournier - * @since 6.0.0 - */ -public abstract class SingleValuePropertyDescriptorFactory<T> extends AbstractPropertyDescriptorFactory<T> { - - /* default */ SingleValuePropertyDescriptorFactory(Class<T> theValueType) { - super(theValueType); - } - - - /* default */ SingleValuePropertyDescriptorFactory(Class<T> theValueType, - Map<PropertyDescriptorField, Boolean> additionalFieldTypesByKey) { - super(theValueType, additionalFieldTypesByKey); - } - - - @Override - public abstract SingleValuePropertyDescriptor<T> createWith(Map<PropertyDescriptorField, String> valuesById, - boolean isDefinedExternally); - - - @Override - public final boolean isMultiValue() { - return false; - } -} diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/properties/StringMultiProperty.java b/pmd-core/src/main/java/net/sourceforge/pmd/properties/StringMultiProperty.java index 4e6f945708f..538c0abc200 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/properties/StringMultiProperty.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/properties/StringMultiProperty.java @@ -4,44 +4,24 @@ package net.sourceforge.pmd.properties; -import static net.sourceforge.pmd.properties.ValueParserConstants.STRING_PARSER; - import java.util.Arrays; import java.util.List; -import java.util.Map; import org.apache.commons.lang3.StringUtils; +import net.sourceforge.pmd.properties.builders.MultiValuePropertyBuilder; +import net.sourceforge.pmd.properties.builders.PropertyDescriptorBuilderConversionWrapper; + + /** - * Defines a datatype that supports multiple String values. Note that all - * strings must be filtered by the delimiter character. + * Defines a datatype that supports multiple String values. Note that all strings must be filtered by the delimiter + * character. * * @author Brian Remedios * @version Refactored June 2017 (6.0.0) */ public final class StringMultiProperty extends AbstractMultiValueProperty<String> { - /** Factory. */ - public static final PropertyDescriptorFactory<List<String>> FACTORY // @formatter:off - = new MultiValuePropertyDescriptorFactory<String>(String.class) { - - @Override - protected boolean isValueMissing(String value) { - return StringUtils.isEmpty(value); - } - - @Override - public StringMultiProperty createWith(Map<PropertyDescriptorField, String> valuesById, boolean isDefinedExternally) { - char delimiter = delimiterIn(valuesById); - return new StringMultiProperty(nameIn(valuesById), - descriptionIn(valuesById), - ValueParserConstants.parsePrimitives(defaultValueIn(valuesById), delimiter, STRING_PARSER), - 0.0f, - delimiter, - isDefinedExternally); - } - }; // @formatter:on - /** * Constructor using an array of defaults. @@ -88,28 +68,6 @@ private StringMultiProperty(String theName, String theDescription, List<String> } - /** - * Checks if the values are valid. - * - * @param defaultValue The default value - * @param delim The delimiter - * - * @throws IllegalArgumentException if one value contains the delimiter - */ - private static void checkDefaults(List<String> defaultValue, char delim) { - - if (defaultValue == null) { - return; - } - - for (String aDefaultValue : defaultValue) { - if (aDefaultValue.indexOf(delim) >= 0) { - throw new IllegalArgumentException("Cannot include the delimiter in the set of defaults"); - } - } - } - - @Override public Class<String> type() { return String.class; @@ -155,4 +113,54 @@ private boolean containsDelimiter(String value) { protected String createFrom(String toParse) { return toParse; } + + + /** + * Checks if the values are valid. + * + * @param defaultValue The default value + * @param delim The delimiter + * + * @throws IllegalArgumentException if one value contains the delimiter + */ + private static void checkDefaults(List<String> defaultValue, char delim) { + + if (defaultValue == null) { + return; + } + + for (String aDefaultValue : defaultValue) { + if (aDefaultValue.indexOf(delim) >= 0) { + throw new IllegalArgumentException("Cannot include the delimiter in the set of defaults"); + } + } + } + + + static PropertyDescriptorBuilderConversionWrapper.MultiValue<String, StringMultiPBuilder> extractor() { + return new PropertyDescriptorBuilderConversionWrapper.MultiValue<String, StringMultiPBuilder>(String.class, ValueParserConstants.STRING_PARSER) { + @Override + protected StringMultiPBuilder newBuilder(String name) { + return new StringMultiPBuilder(name); + } + }; + } + + + public static StringMultiPBuilder named(String name) { + return new StringMultiPBuilder(name); + } + + + public static final class StringMultiPBuilder extends MultiValuePropertyBuilder<String, StringMultiPBuilder> { + private StringMultiPBuilder(String name) { + super(name); + } + + + @Override + public StringMultiProperty build() { + return new StringMultiProperty(this.name, this.description, this.defaultValues, this.uiOrder, this.multiValueDelimiter, isDefinedInXML); + } + } } diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/properties/StringProperty.java b/pmd-core/src/main/java/net/sourceforge/pmd/properties/StringProperty.java index 6b751fdfcc9..0fe2652845c 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/properties/StringProperty.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/properties/StringProperty.java @@ -4,9 +4,9 @@ package net.sourceforge.pmd.properties; -import java.util.Map; +import net.sourceforge.pmd.properties.builders.PropertyDescriptorBuilderConversionWrapper; +import net.sourceforge.pmd.properties.builders.SingleValuePropertyBuilder; -import org.apache.commons.lang3.StringUtils; /** * Defines a datatype that supports single String values. @@ -16,26 +16,6 @@ */ public final class StringProperty extends AbstractSingleValueProperty<String> { - /** Factory. */ - public static final PropertyDescriptorFactory<String> FACTORY // @formatter:off - = new SingleValuePropertyDescriptorFactory<String>(String.class) { - - @Override - protected boolean isValueMissing(String value) { - return StringUtils.isEmpty(value); - } - - @Override - public StringProperty createWith(Map<PropertyDescriptorField, String> valuesById, boolean isDefinedExternally) { - return new StringProperty(nameIn(valuesById), - descriptionIn(valuesById), - defaultValueIn(valuesById), - 0f, - isDefinedExternally); - } - }; // @formatter:on - - /** * Constructor. * @@ -66,4 +46,32 @@ public Class<String> type() { public String createFrom(String valueString) { return valueString; } + + + static PropertyDescriptorBuilderConversionWrapper.SingleValue<String, StringPBuilder> extractor() { + return new PropertyDescriptorBuilderConversionWrapper.SingleValue<String, StringPBuilder>(String.class, ValueParserConstants.STRING_PARSER) { + @Override + protected StringPBuilder newBuilder(String name) { + return new StringPBuilder(name); + } + }; + } + + + public static StringPBuilder named(String name) { + return new StringPBuilder(name); + } + + + public static final class StringPBuilder extends SingleValuePropertyBuilder<String, StringPBuilder> { + private StringPBuilder(String name) { + super(name); + } + + + @Override + public StringProperty build() { + return new StringProperty(this.name, this.description, this.defaultValue, this.uiOrder, isDefinedInXML); + } + } } diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/properties/TypeMultiProperty.java b/pmd-core/src/main/java/net/sourceforge/pmd/properties/TypeMultiProperty.java index aa832efff95..5e53397e4a2 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/properties/TypeMultiProperty.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/properties/TypeMultiProperty.java @@ -5,13 +5,14 @@ package net.sourceforge.pmd.properties; import java.util.List; -import java.util.Map; +import net.sourceforge.pmd.properties.builders.MultiPackagedPropertyBuilder; +import net.sourceforge.pmd.properties.builders.PropertyDescriptorBuilderConversionWrapper; import net.sourceforge.pmd.properties.modules.TypePropertyModule; + /** - * Defines a property that supports multiple class types, even for primitive - * values! + * Defines a property that supports multiple class types, even for primitive values! * * TODO - untested for array types * @@ -19,20 +20,6 @@ */ public final class TypeMultiProperty extends AbstractMultiPackagedProperty<Class> { - /** Factory. */ - public static final PropertyDescriptorFactory<List<Class>> FACTORY // @formatter:off - = new MultiValuePropertyDescriptorFactory<Class>(Class.class, PACKAGED_FIELD_TYPES_BY_KEY) { - @Override - public TypeMultiProperty createWith(Map<PropertyDescriptorField, String> valuesById, boolean isDefinedExternally) { - char delimiter = delimiterIn(valuesById); - return new TypeMultiProperty(nameIn(valuesById), - descriptionIn(valuesById), - defaultValueIn(valuesById), - legalPackageNamesIn(valuesById, delimiter), - 0f); - } - }; // @formatter:on - /** * Constructor for TypeProperty. @@ -56,7 +43,7 @@ public TypeMultiProperty(String theName, String theDescription, List<Class> theD private TypeMultiProperty(String theName, String theDescription, List<Class> theTypeDefaults, String[] legalPackageNames, float theUIOrder, boolean isDefinedExternally) { super(theName, theDescription, theTypeDefaults, theUIOrder, isDefinedExternally, - new TypePropertyModule(legalPackageNames, theTypeDefaults)); + new TypePropertyModule(legalPackageNames, theTypeDefaults)); } @@ -74,14 +61,9 @@ private TypeMultiProperty(String theName, String theDescription, List<Class> the public TypeMultiProperty(String theName, String theDescription, String theTypeDefaults, String[] legalPackageNames, float theUIOrder) { this(theName, theDescription, typesFrom(theTypeDefaults), - legalPackageNames, - theUIOrder, false); - - } - + legalPackageNames, + theUIOrder, false); - private static List<Class> typesFrom(String valueString) { - return ValueParserConstants.parsePrimitives(valueString, MULTI_VALUE_DELIMITER, ValueParserConstants.CLASS_PARSER); } @@ -107,4 +89,38 @@ protected Class createFrom(String toParse) { public List<Class> valueFrom(String valueString) { return typesFrom(valueString); } + + + private static List<Class> typesFrom(String valueString) { + return ValueParserConstants.parsePrimitives(valueString, MULTI_VALUE_DELIMITER, ValueParserConstants.CLASS_PARSER); + } + + + public static PropertyDescriptorBuilderConversionWrapper.MultiValue.Packaged<Class, TypeMultiPBuilder> extractor() { + return new PropertyDescriptorBuilderConversionWrapper.MultiValue.Packaged<Class, TypeMultiPBuilder>(Class.class, ValueParserConstants.CLASS_PARSER) { + @Override + protected TypeMultiPBuilder newBuilder(String name) { + return new TypeMultiPBuilder(name); + } + }; + } + + + public static TypeMultiPBuilder named(String name) { + return new TypeMultiPBuilder(name); + } + + + public static final class TypeMultiPBuilder extends MultiPackagedPropertyBuilder<Class, TypeMultiPBuilder> { + + private TypeMultiPBuilder(String name) { + super(name); + } + + + @Override + public TypeMultiProperty build() { + return new TypeMultiProperty(name, description, defaultValues, legalPackageNames, uiOrder, isDefinedInXML); + } + } } diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/properties/TypeProperty.java b/pmd-core/src/main/java/net/sourceforge/pmd/properties/TypeProperty.java index fc5d577297a..a7d2ef2898b 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/properties/TypeProperty.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/properties/TypeProperty.java @@ -5,13 +5,14 @@ package net.sourceforge.pmd.properties; import java.util.Collections; -import java.util.Map; +import net.sourceforge.pmd.properties.builders.PropertyDescriptorBuilderConversionWrapper; +import net.sourceforge.pmd.properties.builders.SinglePackagedPropertyBuilder; import net.sourceforge.pmd.properties.modules.TypePropertyModule; + /** - * Defines a property that supports single class types, even for primitive - * values! + * Defines a property that supports single class types, even for primitive values! * * TODO - untested for array types * @@ -19,22 +20,6 @@ */ public final class TypeProperty extends AbstractPackagedProperty<Class> { - /** Factory. */ - public static final PropertyDescriptorFactory<Class> FACTORY // @formatter:off - = new SingleValuePropertyDescriptorFactory<Class>(Class.class, PACKAGED_FIELD_TYPES_BY_KEY) { - @Override - public TypeProperty createWith(Map<PropertyDescriptorField, String> valuesById, boolean isDefinedExternally) { - char delimiter = delimiterIn(valuesById); - return new TypeProperty(nameIn(valuesById), - descriptionIn(valuesById), - ValueParserConstants.CLASS_PARSER.valueOf(defaultValueIn(valuesById)), - legalPackageNamesIn(valuesById, delimiter), - 0f, - isDefinedExternally); - } - }; // @formatter:on - - /** * Constructor for TypeProperty using a string as default value. * @@ -57,7 +42,7 @@ public TypeProperty(String theName, String theDescription, String defaultTypeStr private TypeProperty(String theName, String theDescription, Class<?> theDefault, String[] legalPackageNames, float theUIOrder, boolean isDefinedExternally) { super(theName, theDescription, theDefault, theUIOrder, isDefinedExternally, - new TypePropertyModule(legalPackageNames, Collections.<Class>singletonList(theDefault))); + new TypePropertyModule(legalPackageNames, Collections.<Class>singletonList(theDefault))); } @@ -92,4 +77,33 @@ protected String asString(Class value) { public Class<?> createFrom(String valueString) { return ValueParserConstants.CLASS_PARSER.valueOf(valueString); } + + + static PropertyDescriptorBuilderConversionWrapper.SingleValue.Packaged<Class, TypePBuilder> extractor() { + return new PropertyDescriptorBuilderConversionWrapper.SingleValue.Packaged<Class, TypePBuilder>(Class.class, ValueParserConstants.CLASS_PARSER) { + @Override + protected TypePBuilder newBuilder(String name) { + return new TypePBuilder(name); + } + }; + } + + + public static TypePBuilder named(String name) { + return new TypePBuilder(name); + } + + + public static final class TypePBuilder extends SinglePackagedPropertyBuilder<Class, TypePBuilder> { + private TypePBuilder(String name) { + super(name); + } + + + @Override + public TypeProperty build() { + return new TypeProperty(name, description, defaultValue, legalPackageNames, uiOrder, isDefinedInXML); + } + } + } diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/properties/ValueParser.java b/pmd-core/src/main/java/net/sourceforge/pmd/properties/ValueParser.java index 4224006c6cd..4ce4acdcad1 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/properties/ValueParser.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/properties/ValueParser.java @@ -18,7 +18,6 @@ public interface ValueParser<U> { * @param value The string to parse * * @return The primitive found - * * @throws IllegalArgumentException if the value couldn't be parsed */ U valueOf(String value) throws IllegalArgumentException; diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/properties/ValueParserConstants.java b/pmd-core/src/main/java/net/sourceforge/pmd/properties/ValueParserConstants.java index c7a426c2121..b4f60a7f6e7 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/properties/ValueParserConstants.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/properties/ValueParserConstants.java @@ -9,114 +9,27 @@ import static net.sourceforge.pmd.properties.modules.MethodPropertyModule.METHOD_ARG_DELIMITER; import static net.sourceforge.pmd.properties.modules.MethodPropertyModule.METHOD_GROUP_DELIMITERS; +import java.io.File; import java.lang.reflect.Array; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.List; +import java.util.regex.Pattern; import org.apache.commons.lang3.StringUtils; import net.sourceforge.pmd.util.ClassUtil; + /** * @author Clément Fournier * @since 6.0.0 */ -//TODO: make package-private when we move everything to n.s.pmd.properties public final class ValueParserConstants { - /** Extracts characters. */ - static final ValueParser<Character> CHARACTER_PARSER = new ValueParser<Character>() { - @Override - public Character valueOf(String value) { - if (value == null || value.length() != 1) { - throw new IllegalArgumentException("missing/ambiguous character value"); - } - return value.charAt(0); - } - }; - - - /** Extracts strings. That's a dummy used to return a list in StringMultiProperty. */ - static final ValueParser<String> STRING_PARSER = new ValueParser<String>() { - @Override - public String valueOf(String value) { - return value; - } - }; - - - /** Extracts integers. */ - static final ValueParser<Integer> INTEGER_PARSER = new ValueParser<Integer>() { - @Override - public Integer valueOf(String value) { - return Integer.valueOf(value); - } - }; - - - /** Extracts booleans. */ - static final ValueParser<Boolean> BOOLEAN_PARSER = new ValueParser<Boolean>() { - @Override - public Boolean valueOf(String value) { - return Boolean.valueOf(value); - } - }; - - - /** Extracts floats. */ - static final ValueParser<Float> FLOAT_PARSER = new ValueParser<Float>() { - @Override - public Float valueOf(String value) { - return Float.valueOf(value); - } - }; - - - /** Extracts longs. */ - static final ValueParser<Long> LONG_PARSER = new ValueParser<Long>() { - @Override - public Long valueOf(String value) { - return Long.valueOf(value); - } - }; - - - /** Extracts doubles. */ - static final ValueParser<Double> DOUBLE_PARSER = new ValueParser<Double>() { - @Override - public Double valueOf(String value) { - return Double.valueOf(value); - } - }; - - - /** Extract classes. */ - static final ValueParser<Class> CLASS_PARSER = new ValueParser<Class>() { - @Override - public Class valueOf(String value) throws IllegalArgumentException { - if (StringUtils.isBlank(value)) { - return null; - } - - Class<?> cls = ClassUtil.getTypeFor(value); - if (cls != null) { - return cls; - } - - try { - return Class.forName(value); - } catch (ClassNotFoundException ex) { - throw new IllegalArgumentException(value); - } - } - }; - - /** Extracts methods. */ - // TODO: make package-private when we move everything to n.s.pmd.properties - public static final ValueParser<Method> METHOD_PARSER = new ValueParser<Method>() { + static final ValueParser<Method> METHOD_PARSER = new ValueParser<Method>() { @Override public Method valueOf(String value) throws IllegalArgumentException { return methodFrom(value, CLASS_METHOD_DELIMITER, METHOD_ARG_DELIMITER); @@ -223,6 +136,94 @@ private Class<?> typeFor(String typeName) { } }; + /** Extracts characters. */ + static final ValueParser<Character> CHARACTER_PARSER = new ValueParser<Character>() { + @Override + public Character valueOf(String value) { + if (value == null || value.length() != 1) { + throw new IllegalArgumentException("missing/ambiguous character value"); + } + return value.charAt(0); + } + }; + /** Extracts strings. That's a dummy used to return a list in StringMultiProperty. */ + static final ValueParser<String> STRING_PARSER = new ValueParser<String>() { + @Override + public String valueOf(String value) { + return value; + } + }; + /** Extracts integers. */ + static final ValueParser<Integer> INTEGER_PARSER = new ValueParser<Integer>() { + @Override + public Integer valueOf(String value) { + return Integer.valueOf(value); + } + }; + /** Extracts booleans. */ + static final ValueParser<Boolean> BOOLEAN_PARSER = new ValueParser<Boolean>() { + @Override + public Boolean valueOf(String value) { + return Boolean.valueOf(value); + } + }; + /** Extracts floats. */ + static final ValueParser<Float> FLOAT_PARSER = new ValueParser<Float>() { + @Override + public Float valueOf(String value) { + return Float.valueOf(value); + } + }; + /** Extracts longs. */ + static final ValueParser<Long> LONG_PARSER = new ValueParser<Long>() { + @Override + public Long valueOf(String value) { + return Long.valueOf(value); + } + }; + /** Extracts doubles. */ + static final ValueParser<Double> DOUBLE_PARSER = new ValueParser<Double>() { + @Override + public Double valueOf(String value) { + return Double.valueOf(value); + } + }; + /** Extracts files */ + static final ValueParser<File> FILE_PARSER = new ValueParser<File>() { + @Override + public File valueOf(String value) throws IllegalArgumentException { + return new File(value); + } + }; + + /** Compiles a regex. */ + static final ValueParser<Pattern> REGEX_PARSER = new ValueParser<Pattern>() { + @Override + public Pattern valueOf(String value) throws IllegalArgumentException { + return Pattern.compile(value); + } + }; + + /** Extract classes. */ + static final ValueParser<Class> CLASS_PARSER = new ValueParser<Class>() { + @Override + public Class valueOf(String value) throws IllegalArgumentException { + if (StringUtils.isBlank(value)) { + return null; + } + + Class<?> cls = ClassUtil.getTypeFor(value); + if (cls != null) { + return cls; + } + + try { + return Class.forName(value); + } catch (ClassNotFoundException ex) { + throw new IllegalArgumentException(value); + } + } + }; private ValueParserConstants() { @@ -230,6 +231,25 @@ private ValueParserConstants() { } + /** + * Returns a value parser parsing lists of values of type U. + * + * @param parser Parser used to parse a single value + * @param delimiter Char delimiting values + * @param <U> Element type of the target list + * + * @return A list of values + */ + public static <U> ValueParser<List<U>> multi(final ValueParser<U> parser, final char delimiter) { + return new ValueParser<List<U>>() { + @Override + public List<U> valueOf(String value) throws IllegalArgumentException { + return parsePrimitives(value, delimiter, parser); + } + }; + } + + /** * Parses a string into a list of values of type {@literal <U>}. * @@ -242,7 +262,7 @@ private ValueParserConstants() { */ // FUTURE 1.8 : use java.util.function.Function<String, U> in place of ValueParser<U>, // replace ValueParser constants with static functions - public static <U> List<U> parsePrimitives(String toParse, char delimiter, ValueParser<U> extractor) { + static <U> List<U> parsePrimitives(String toParse, char delimiter, ValueParser<U> extractor) { String[] values = StringUtils.split(toParse, delimiter); List<U> result = new ArrayList<>(); for (String s : values) { diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/properties/builders/MultiNumericPropertyBuilder.java b/pmd-core/src/main/java/net/sourceforge/pmd/properties/builders/MultiNumericPropertyBuilder.java new file mode 100644 index 00000000000..8bcc2340c38 --- /dev/null +++ b/pmd-core/src/main/java/net/sourceforge/pmd/properties/builders/MultiNumericPropertyBuilder.java @@ -0,0 +1,45 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.properties.builders; + +import net.sourceforge.pmd.properties.MultiValuePropertyDescriptor; + + +/** + * For multi-value numeric properties. + * + * @param <V> Element type of the list + * @param <T> Concrete type of the underlying builder + */ +public abstract class MultiNumericPropertyBuilder<V, T extends MultiNumericPropertyBuilder<V, T>> + extends MultiValuePropertyBuilder<V, T> { + + + protected V lowerLimit; + protected V upperLimit; + + + protected MultiNumericPropertyBuilder(String name) { + super(name); + multiValueDelimiter = MultiValuePropertyDescriptor.DEFAULT_NUMERIC_DELIMITER; + } + + + /** + * Specify the range of acceptable values. + * + * @param min Lower bound, inclusive + * @param max Upper bound, inclusive + * + * @return The same builder + */ + @SuppressWarnings("unchecked") + public T range(V min, V max) { + this.lowerLimit = min; + this.upperLimit = max; + return (T) this; + } + +} diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/properties/builders/MultiPackagedPropertyBuilder.java b/pmd-core/src/main/java/net/sourceforge/pmd/properties/builders/MultiPackagedPropertyBuilder.java new file mode 100644 index 00000000000..aad9f61895f --- /dev/null +++ b/pmd-core/src/main/java/net/sourceforge/pmd/properties/builders/MultiPackagedPropertyBuilder.java @@ -0,0 +1,33 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.properties.builders; + +import java.util.Arrays; + + +/** + * @author Clément Fournier + * @since 6.0.0 + */ +public abstract class MultiPackagedPropertyBuilder<V, T extends MultiPackagedPropertyBuilder<V, T>> + extends MultiValuePropertyBuilder<V, T> { + + protected String[] legalPackageNames; + + + protected MultiPackagedPropertyBuilder(String name) { + super(name); + } + + + @SuppressWarnings("unchecked") + public T legalPackages(String[] packs) { + if (packs != null) { + this.legalPackageNames = Arrays.copyOf(packs, packs.length); + } + return (T) this; + } + +} diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/properties/builders/MultiValuePropertyBuilder.java b/pmd-core/src/main/java/net/sourceforge/pmd/properties/builders/MultiValuePropertyBuilder.java new file mode 100644 index 00000000000..6405dd57701 --- /dev/null +++ b/pmd-core/src/main/java/net/sourceforge/pmd/properties/builders/MultiValuePropertyBuilder.java @@ -0,0 +1,74 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.properties.builders; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; + +import net.sourceforge.pmd.properties.MultiValuePropertyDescriptor; + + +/** + * For multi-value properties. + * + * @param <V> Element type of the list + * @param <T> Concrete type of the underlying builder + */ +public abstract class MultiValuePropertyBuilder<V, T extends MultiValuePropertyBuilder<V, T>> + extends PropertyDescriptorBuilder<List<V>, T> { + + protected List<V> defaultValues; + protected char multiValueDelimiter = MultiValuePropertyDescriptor.DEFAULT_DELIMITER; + + + protected MultiValuePropertyBuilder(String name) { + super(name); + } + + + /** + * Specify a default value. + * + * @param val List of values + * + * @return The same builder + */ + @SuppressWarnings("unchecked") + public T defaultValues(Collection<? extends V> val) { + this.defaultValues = new ArrayList<>(val); + return (T) this; + } + + + /** + * Specify default values. + * + * @param val List of values + * + * @return The same builder + */ + @SuppressWarnings("unchecked") + public T defaultValues(V... val) { + this.defaultValues = Arrays.asList(val); + return (T) this; + } + + + /** + * Specify a delimiter character. By default it's {@link MultiValuePropertyDescriptor#DEFAULT_DELIMITER}, or {@link + * MultiValuePropertyDescriptor#DEFAULT_NUMERIC_DELIMITER} for numeric properties. + * + * @param delim Delimiter + * + * @return The same builder + */ + @SuppressWarnings("unchecked") + public T delim(char delim) { + this.multiValueDelimiter = delim; + return (T) this; + } +} diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/properties/builders/PropertyDescriptorBuilder.java b/pmd-core/src/main/java/net/sourceforge/pmd/properties/builders/PropertyDescriptorBuilder.java new file mode 100644 index 00000000000..784fe1ff0fd --- /dev/null +++ b/pmd-core/src/main/java/net/sourceforge/pmd/properties/builders/PropertyDescriptorBuilder.java @@ -0,0 +1,85 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.properties.builders; + +import org.apache.commons.lang3.StringUtils; + +import net.sourceforge.pmd.properties.PropertyDescriptor; + + +/** + * Base class for property builders. + * + * @param <E> Value type of the built descriptor + * @param <T> Concrete type of this builder instance. Removes code duplication at the expense of a few unchecked casts. + * Everything goes well if this parameter's value is correctly set. + * + * @author Clément Fournier + * @since 6.0.0 + */ +public abstract class PropertyDescriptorBuilder<E, T extends PropertyDescriptorBuilder<E, T>> { + + protected String name; + protected String description; + protected float uiOrder = 0f; + protected boolean isDefinedInXML = false; + + + protected PropertyDescriptorBuilder(String name) { + if (StringUtils.isBlank(name)) { + throw new IllegalArgumentException("Name must be provided"); + } + this.name = name; + } + + + /** + * Specify the description of the property. + * + * @param desc The description + * + * @return The same builder + */ + @SuppressWarnings("unchecked") + public T desc(String desc) { + if (StringUtils.isBlank(desc)) { + throw new IllegalArgumentException("Description must be provided"); + } + this.description = desc; + return (T) this; + } + + + /** + * Specify the UI order of the property. + * + * @param f The UI order + * + * @return The same builder + */ + @SuppressWarnings("unchecked") + public T uiOrder(float f) { + this.uiOrder = f; + return (T) this; + } + + + /** + * Builds the descriptor and returns it. + * + * @return The built descriptor + * @throws IllegalArgumentException if parameters are incorrect + */ + public abstract PropertyDescriptor<E> build(); + + + /** + * Returns the name of the property to be built. + */ + public String getName() { + return name; + } + +} diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/properties/builders/PropertyDescriptorBuilderConversionWrapper.java b/pmd-core/src/main/java/net/sourceforge/pmd/properties/builders/PropertyDescriptorBuilderConversionWrapper.java new file mode 100644 index 00000000000..caf9beb9d61 --- /dev/null +++ b/pmd-core/src/main/java/net/sourceforge/pmd/properties/builders/PropertyDescriptorBuilderConversionWrapper.java @@ -0,0 +1,259 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.properties.builders; + + +import static net.sourceforge.pmd.properties.PropertyDescriptorField.DELIMITER; +import static net.sourceforge.pmd.properties.PropertyDescriptorField.LEGAL_PACKAGES; + +import java.util.List; +import java.util.Map; + +import org.apache.commons.lang3.StringUtils; + +import net.sourceforge.pmd.properties.MultiValuePropertyDescriptor; +import net.sourceforge.pmd.properties.PropertyDescriptor; +import net.sourceforge.pmd.properties.PropertyDescriptorField; +import net.sourceforge.pmd.properties.ValueParser; +import net.sourceforge.pmd.properties.ValueParserConstants; + + +/** + * Wraps a property builder and maps its inputs from strings to the target types of the descriptor. + * + * @param <E> Value type of the descriptor + * @param <T> Concrete type of the underlying builder + * + * @author Clément Fournier + * @since 6.0.0 + */ +public abstract class PropertyDescriptorBuilderConversionWrapper<E, T extends PropertyDescriptorBuilder<E, T>> + implements PropertyDescriptorExternalBuilder<E> { + + private final Class<?> valueType; + + + protected PropertyDescriptorBuilderConversionWrapper(Class<?> valueType) { + this.valueType = valueType; + } + + + /** Populates the builder with extracted fields. To be overridden. */ + protected void populate(T builder, Map<PropertyDescriptorField, String> fields) { + builder.desc(fields.get(PropertyDescriptorField.DESCRIPTION)); + if (fields.containsKey(PropertyDescriptorField.UI_ORDER)) { + builder.uiOrder(Float.parseFloat(fields.get(PropertyDescriptorField.UI_ORDER))); + } + } + + + @Override + public abstract boolean isMultiValue(); + + + @Override + public Class<?> valueType() { + return valueType; + } + + + protected abstract T newBuilder(String name); // FUTURE 1.8: use a Supplier constructor parameter + + + @Override + public PropertyDescriptor<E> build(Map<PropertyDescriptorField, String> fields) { + T builder = newBuilder(fields.get(PropertyDescriptorField.NAME)); + populate(builder, fields); + builder.isDefinedInXML = true; + return builder.build(); + } + + + protected static String[] legalPackageNamesIn(Map<PropertyDescriptorField, String> valuesById, char delimiter) { + String names = valuesById.get(LEGAL_PACKAGES); + return StringUtils.isBlank(names) ? null : StringUtils.split(names, delimiter); + } + + + private static char delimiterIn(Map<PropertyDescriptorField, String> valuesById, char defalt) { + String characterStr = ""; + if (valuesById.containsKey(DELIMITER)) { + characterStr = valuesById.get(DELIMITER).trim(); + } + + if (StringUtils.isBlank(characterStr)) { + return defalt; + } + + if (characterStr.length() != 1) { + throw new RuntimeException("Ambiguous delimiter character, must have length 1: \"" + characterStr + "\""); + } + return characterStr.charAt(0); + } + + + /** + * For multi-value properties. + * + * @param <V> Element type of the list + * @param <T> Concrete type of the underlying builder + */ + public abstract static class MultiValue<V, T extends MultiValuePropertyBuilder<V, T>> + extends PropertyDescriptorBuilderConversionWrapper<List<V>, T> { + + protected final ValueParser<V> parser; + + + protected MultiValue(Class<V> valueType, ValueParser<V> parser) { + super(valueType); + this.parser = parser; + } + + + @Override + protected void populate(T builder, Map<PropertyDescriptorField, String> fields) { + super.populate(builder, fields); + char delim = delimiterIn(fields, builder.multiValueDelimiter); + builder.delim(delim).defaultValues(ValueParserConstants.multi(parser, delim) + .valueOf(fields.get(PropertyDescriptorField.DEFAULT_VALUE))); + } + + + @Override + public boolean isMultiValue() { + return true; + } + + + /** + * For multi-value numeric properties. + * + * @param <V> Element type of the list + * @param <T> Concrete type of the underlying builder + */ + public abstract static class Numeric<V, T extends MultiNumericPropertyBuilder<V, T>> + extends MultiValue<V, T> { + + protected Numeric(Class<V> valueType, ValueParser<V> parser) { + super(valueType, parser); + } + + + @Override + protected void populate(T builder, Map<PropertyDescriptorField, String> fields) { + super.populate(builder, fields); + V min = parser.valueOf(fields.get(PropertyDescriptorField.MIN)); + V max = parser.valueOf(fields.get(PropertyDescriptorField.MAX)); + builder.range(min, max); + } + } + + + /** + * For single-value packaged properties. + * + * @param <V> Element type of the list + * @param <T> Concrete type of the underlying builder + */ + public abstract static class Packaged<V, T extends MultiPackagedPropertyBuilder<V, T>> + extends MultiValue<V, T> { + + protected Packaged(Class<V> valueType, ValueParser<V> parser) { + super(valueType, parser); + } + + + @Override + protected void populate(T builder, Map<PropertyDescriptorField, String> fields) { + super.populate(builder, fields); + builder.legalPackages(legalPackageNamesIn(fields, PropertyDescriptorBuilderConversionWrapper.delimiterIn(fields, + MultiValuePropertyDescriptor.DEFAULT_DELIMITER))); + } + } + + } + + + /** + * For single-value properties. + * + * @param <E> Value type of the property + * @param <T> Concrete type of the underlying builder + */ + public abstract static class SingleValue<E, T extends SingleValuePropertyBuilder<E, T>> + extends PropertyDescriptorBuilderConversionWrapper<E, T> { + + protected final ValueParser<E> parser; + + + protected SingleValue(Class<E> valueType, ValueParser<E> parser) { + super(valueType); + this.parser = parser; + } + + + @Override + protected void populate(T builder, Map<PropertyDescriptorField, String> fields) { + super.populate(builder, fields); + builder.defaultValue(parser.valueOf(fields.get(PropertyDescriptorField.DEFAULT_VALUE))); + } + + + @Override + public boolean isMultiValue() { + return false; + } + + + /** + * For single-value numeric properties. + * + * @param <V> Element type of the list + * @param <T> Concrete type of the underlying builder + */ + public abstract static class Numeric<V, T extends SingleNumericPropertyBuilder<V, T>> + extends SingleValue<V, T> { + + protected Numeric(Class<V> valueType, ValueParser<V> parser) { + super(valueType, parser); + } + + + @Override + protected void populate(T builder, Map<PropertyDescriptorField, String> fields) { + super.populate(builder, fields); + V min = parser.valueOf(fields.get(PropertyDescriptorField.MIN)); + V max = parser.valueOf(fields.get(PropertyDescriptorField.MAX)); + builder.range(min, max); + } + } + + + /** + * For single-value packaged properties. + * + * @param <E> Element type of the list + * @param <T> Concrete type of the underlying builder + */ + public abstract static class Packaged<E, T extends SinglePackagedPropertyBuilder<E, T>> + extends SingleValue<E, T> { + + protected Packaged(Class<E> valueType, ValueParser<E> parser) { + super(valueType, parser); + } + + + @Override + protected void populate(T builder, Map<PropertyDescriptorField, String> fields) { + super.populate(builder, fields); + builder.legalPackageNames(legalPackageNamesIn(fields, PropertyDescriptorBuilderConversionWrapper.delimiterIn(fields, + MultiValuePropertyDescriptor.DEFAULT_DELIMITER))); + } + } + + + } + +} diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/properties/builders/PropertyDescriptorExternalBuilder.java b/pmd-core/src/main/java/net/sourceforge/pmd/properties/builders/PropertyDescriptorExternalBuilder.java new file mode 100644 index 00000000000..16ded671411 --- /dev/null +++ b/pmd-core/src/main/java/net/sourceforge/pmd/properties/builders/PropertyDescriptorExternalBuilder.java @@ -0,0 +1,48 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.properties.builders; + +import java.util.Map; + +import net.sourceforge.pmd.properties.PropertyDescriptor; +import net.sourceforge.pmd.properties.PropertyDescriptorField; + + +/** + * Builds properties from a map of key value pairs, eg extracted from an XML element. + * + * @param <E> The type of values. + * + * @author Clément Fournier + * @since 6.0.0 + */ +public interface PropertyDescriptorExternalBuilder<E> { + + + /** + * Whether this descriptor is multi-valued. + * + * @return True if this descriptor is multi-valued + */ + boolean isMultiValue(); + + + /** + * Type of the values of the descriptor, or component type if this descriptor is multi-valued. + * + * @return Type of the values + */ + Class<?> valueType(); + + + /** + * Builds a descriptor. The descriptor returned is tagged as built externally. + * + * @param fields Key value pairs + * + * @return A builder + */ + PropertyDescriptor<E> build(Map<PropertyDescriptorField, String> fields); +} diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/properties/builders/SingleNumericPropertyBuilder.java b/pmd-core/src/main/java/net/sourceforge/pmd/properties/builders/SingleNumericPropertyBuilder.java new file mode 100644 index 00000000000..4e4d450aed6 --- /dev/null +++ b/pmd-core/src/main/java/net/sourceforge/pmd/properties/builders/SingleNumericPropertyBuilder.java @@ -0,0 +1,39 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.properties.builders; + +/** + * @author Clément Fournier + * @since 6.0.0 + */ +public abstract class SingleNumericPropertyBuilder<V, T extends SingleNumericPropertyBuilder<V, T>> + extends SingleValuePropertyBuilder<V, T> { + + + protected V lowerLimit; + protected V upperLimit; + + + public SingleNumericPropertyBuilder(String name) { + super(name); + } + + + /** + * Specify the range of acceptable values. + * + * @param min Lower bound + * @param max Upper bound + * + * @return The same builder + */ + @SuppressWarnings("unchecked") + public T range(V min, V max) { + this.lowerLimit = min; + this.upperLimit = max; + return (T) this; + } + +} diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/properties/builders/SinglePackagedPropertyBuilder.java b/pmd-core/src/main/java/net/sourceforge/pmd/properties/builders/SinglePackagedPropertyBuilder.java new file mode 100644 index 00000000000..2f7219d14c3 --- /dev/null +++ b/pmd-core/src/main/java/net/sourceforge/pmd/properties/builders/SinglePackagedPropertyBuilder.java @@ -0,0 +1,57 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.properties.builders; + +import java.util.Arrays; +import java.util.Collection; + + +/** + * @author Clément Fournier + * @since 6.0.0 + */ +public abstract class SinglePackagedPropertyBuilder<V, T extends SinglePackagedPropertyBuilder<V, T>> + extends SingleValuePropertyBuilder<V, T> { + + protected String[] legalPackageNames; + + + public SinglePackagedPropertyBuilder(String name) { + super(name); + } + + + /** + * Specify the allowed package prefixes. + * + * @param packs The package prefixes + * + * @return The same builder + */ + @SuppressWarnings("unchecked") + public T legalPackageNames(String... packs) { + if (packs != null) { + this.legalPackageNames = Arrays.copyOf(packs, packs.length); + } + return (T) this; + } + + + /** + * Specify the allowed package prefixes. + * + * @param packs The package prefixes + * + * @return The same builder + */ + @SuppressWarnings("unchecked") + public T legalPackageNames(Collection<String> packs) { + if (packs != null) { + this.legalPackageNames = packs.toArray(new String[0]); + } + return (T) this; + } + +} diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/properties/builders/SingleValuePropertyBuilder.java b/pmd-core/src/main/java/net/sourceforge/pmd/properties/builders/SingleValuePropertyBuilder.java new file mode 100644 index 00000000000..4c8edbf11bb --- /dev/null +++ b/pmd-core/src/main/java/net/sourceforge/pmd/properties/builders/SingleValuePropertyBuilder.java @@ -0,0 +1,38 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.properties.builders; + +/** + * For single-value property descriptors. + * + * @param <E> Value type of the built descriptor + * @param <T> Concrete type of this builder instance. + */ +public abstract class SingleValuePropertyBuilder<E, T extends SingleValuePropertyBuilder<E, T>> + extends PropertyDescriptorBuilder<E, T> { + + protected E defaultValue; + + + protected SingleValuePropertyBuilder(String name) { + super(name); + } + + + /** + * Specify a default value. + * + * @param val Value + * + * @return The same builder + */ + @SuppressWarnings("unchecked") + public T defaultValue(E val) { + this.defaultValue = val; + return (T) this; + } + + +} diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/properties/modules/EnumeratedPropertyModule.java b/pmd-core/src/main/java/net/sourceforge/pmd/properties/modules/EnumeratedPropertyModule.java index f1c7d8afe3e..ff78fa232bf 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/properties/modules/EnumeratedPropertyModule.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/properties/modules/EnumeratedPropertyModule.java @@ -9,6 +9,7 @@ import net.sourceforge.pmd.util.CollectionUtil; + /** * Factorises common functionality for enumerated properties. * diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/properties/modules/MethodPropertyModule.java b/pmd-core/src/main/java/net/sourceforge/pmd/properties/modules/MethodPropertyModule.java index 2dbd0afe6ff..c2b1019055a 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/properties/modules/MethodPropertyModule.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/properties/modules/MethodPropertyModule.java @@ -10,6 +10,7 @@ import net.sourceforge.pmd.util.ClassUtil; + /** * Factorises common functionality for method properties. * diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/properties/modules/NumericPropertyModule.java b/pmd-core/src/main/java/net/sourceforge/pmd/properties/modules/NumericPropertyModule.java index 46c5455738d..e37286f9a11 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/properties/modules/NumericPropertyModule.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/properties/modules/NumericPropertyModule.java @@ -11,6 +11,7 @@ import net.sourceforge.pmd.properties.PropertyDescriptorField; + /** * Common utilities for implementations of numeric property descriptors. * @@ -32,12 +33,10 @@ public NumericPropertyModule(T lowerLimit, T upperLimit) { if (lowerLimit.doubleValue() > upperLimit.doubleValue()) { throw new IllegalArgumentException("Lower limit cannot be greater than the upper limit"); } - - } - private void checkNumber(T number) { + public void checkNumber(T number) { String error = valueErrorFor(number); if (error != null) { throw new IllegalArgumentException(error); @@ -61,19 +60,6 @@ public String valueErrorFor(T value) { } - /** - * Returns a string representing the range defined by the two bounds. - * - * @param low Lower bound - * @param up Upper bound - * - * @return String - */ - private static String rangeString(Number low, Number up) { - return "(" + low + " -> " + up + ")"; - } - - public T getLowerLimit() { return lowerLimit; } @@ -90,4 +76,17 @@ public void addAttributesTo(Map<PropertyDescriptorField, String> attributes) { } + /** + * Returns a string representing the range defined by the two bounds. + * + * @param low Lower bound + * @param up Upper bound + * + * @return String + */ + private static String rangeString(Number low, Number up) { + return "(" + low + " -> " + up + ")"; + } + + } diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/properties/modules/PackagedPropertyModule.java b/pmd-core/src/main/java/net/sourceforge/pmd/properties/modules/PackagedPropertyModule.java index 06bf77db145..a9c29a0ea6a 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/properties/modules/PackagedPropertyModule.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/properties/modules/PackagedPropertyModule.java @@ -18,6 +18,7 @@ import net.sourceforge.pmd.properties.PropertyDescriptorField; + /** * Factorises common functionality for packaged properties. * @@ -55,7 +56,7 @@ private void checkValidPackages(String[] legalNamePrefixes) throws IllegalArgume for (String name : legalNamePrefixes) { if (name == null) { throw new IllegalArgumentException("Null is not allowed in the legal package names:" - + Arrays.toString(legalNamePrefixes)); + + Arrays.toString(legalNamePrefixes)); } else if (!PACKAGE_NAME_PATTERN.matcher(name).matches()) { throw new IllegalArgumentException("One name is not a package: '" + name + "'"); diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/properties/modules/TypePropertyModule.java b/pmd-core/src/main/java/net/sourceforge/pmd/properties/modules/TypePropertyModule.java index 0758b86c20e..183357552ea 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/properties/modules/TypePropertyModule.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/properties/modules/TypePropertyModule.java @@ -6,6 +6,7 @@ import java.util.List; + /** * Factorises common functionality for type properties. * diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/renderers/AbstractAccumulatingRenderer.java b/pmd-core/src/main/java/net/sourceforge/pmd/renderers/AbstractAccumulatingRenderer.java index b12e0803b4e..5ffb4e8fd56 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/renderers/AbstractAccumulatingRenderer.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/renderers/AbstractAccumulatingRenderer.java @@ -32,24 +32,16 @@ public AbstractAccumulatingRenderer(String name, String description) { super(name, description); } - /** - * {@inheritDoc} - */ @Override public void start() throws IOException { report = new Report(); } - /** - * {@inheritDoc} - */ @Override public void startFileAnalysis(DataSource dataSource) { + // does nothing - override if necessary } - /** - * {@inheritDoc} - */ @Override public void renderFileReport(Report report) throws IOException { this.report.merge(report); diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/renderers/AbstractIncrementingRenderer.java b/pmd-core/src/main/java/net/sourceforge/pmd/renderers/AbstractIncrementingRenderer.java index c3dd54918f4..6e60d9d1198 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/renderers/AbstractIncrementingRenderer.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/renderers/AbstractIncrementingRenderer.java @@ -45,23 +45,16 @@ public AbstractIncrementingRenderer(String name, String description) { super(name, description); } - /** - * {@inheritDoc} - */ @Override public void start() throws IOException { + // does nothing - override if necessary } - /** - * {@inheritDoc} - */ @Override public void startFileAnalysis(DataSource dataSource) { + // does nothing - override if necessary } - /** - * {@inheritDoc} - */ @Override public void renderFileReport(Report report) throws IOException { Iterator<RuleViolation> violations = report.iterator(); @@ -92,10 +85,8 @@ public void renderFileReport(Report report) throws IOException { */ public abstract void renderFileViolations(Iterator<RuleViolation> violations) throws IOException; - /** - * {@inheritDoc} - */ @Override public void end() throws IOException { + // does nothing - override if necessary } } diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/renderers/AbstractRenderer.java b/pmd-core/src/main/java/net/sourceforge/pmd/renderers/AbstractRenderer.java index 965f0e3ccbc..33a76124950 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/renderers/AbstractRenderer.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/renderers/AbstractRenderer.java @@ -27,65 +27,46 @@ public AbstractRenderer(String name, String description) { this.description = description; } - /** - * {@inheritDoc} - */ + @Override + protected String getPropertySourceType() { + return "renderer"; + } + @Override public String getName() { return name; } - /** - * {@inheritDoc} - */ @Override public void setName(String name) { this.name = name; } - /** - * {@inheritDoc} - */ @Override public String getDescription() { return description; } - /** - * {@inheritDoc} - */ @Override public void setDescription(String description) { this.description = description; } - /** - * {@inheritDoc} - */ @Override public boolean isShowSuppressedViolations() { return showSuppressedViolations; } - /** - * {@inheritDoc} - */ @Override public void setShowSuppressedViolations(boolean showSuppressedViolations) { this.showSuppressedViolations = showSuppressedViolations; } - /** - * {@inheritDoc} - */ @Override public void setWriter(Writer writer) { this.writer = writer; } - /** - * {@inheritDoc} - */ @Override public Writer getWriter() { return writer; diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/renderers/CSVRenderer.java b/pmd-core/src/main/java/net/sourceforge/pmd/renderers/CSVRenderer.java index 5dcf3de6914..8c3ef261c2c 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/renderers/CSVRenderer.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/renderers/CSVRenderer.java @@ -132,9 +132,6 @@ private CSVWriter<RuleViolation> csvWriter() { return csvWriter; } - /** - * {@inheritDoc} - */ @Override public void start() throws IOException { csvWriter().writeTitles(getWriter()); @@ -145,9 +142,6 @@ public String defaultFileExtension() { return "csv"; } - /** - * {@inheritDoc} - */ @Override public void renderFileViolations(Iterator<RuleViolation> violations) throws IOException { csvWriter().writeData(getWriter(), violations); diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/renderers/CodeClimateIssue.java b/pmd-core/src/main/java/net/sourceforge/pmd/renderers/CodeClimateIssue.java index aec04f9ab15..7d66f5ba0d4 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/renderers/CodeClimateIssue.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/renderers/CodeClimateIssue.java @@ -11,7 +11,7 @@ * (https://github.com/codeclimate/spec/blob/master/SPEC.md#issues) */ public class CodeClimateIssue { - public final String type = "issue"; + public String type; public String check_name; // SUPPRESS CHECKSTYLE underscore is required per codeclimate format public String description; public Content content; @@ -20,6 +20,10 @@ public class CodeClimateIssue { public String severity; public int remediation_points; // SUPPRESS CHECKSTYLE underscore is required per codeclimate format + public CodeClimateIssue() { + type = "issue"; // the default type for PMD violations when reporting as code climate + } + /** * Location structure */ diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/renderers/CodeClimateRenderer.java b/pmd-core/src/main/java/net/sourceforge/pmd/renderers/CodeClimateRenderer.java index 7765310c80e..ba7675e504b 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/renderers/CodeClimateRenderer.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/renderers/CodeClimateRenderer.java @@ -17,6 +17,7 @@ import org.apache.commons.lang3.StringUtils; import net.sourceforge.pmd.PMD; +import net.sourceforge.pmd.PMDVersion; import net.sourceforge.pmd.Rule; import net.sourceforge.pmd.RuleViolation; import net.sourceforge.pmd.properties.PropertyDescriptor; @@ -35,25 +36,22 @@ public class CodeClimateRenderer extends AbstractIncrementingRenderer { // Note: required by https://github.com/codeclimate/spec/blob/master/SPEC.md protected static final String NULL_CHARACTER = "\u0000"; - private final String pmdDeveloperUrl; + protected static final List<String> INTERNAL_DEV_PROPERTIES = Arrays.asList("version", "xpath"); + private static final String PMD_PROPERTIES_URL = getPmdPropertiesURL(); private Rule rule; public CodeClimateRenderer() { super(NAME, "Code Climate integration."); - pmdDeveloperUrl = getPmdDeveloperURL(); } - private static String getPmdDeveloperURL() { - String url = "http://pmd.github.io/pmd-" + PMD.VERSION + "/customizing/pmd-developer.html"; - if (PMD.VERSION.contains("SNAPSHOT") || "unknown".equals(PMD.VERSION)) { - url = "http://pmd.sourceforge.net/snapshot/customizing/pmd-developer.html"; + private static String getPmdPropertiesURL() { + String url = "https://pmd.github.io/pmd-" + PMDVersion.VERSION + "/pmd_devdocs_working_with_properties.html"; + if (PMDVersion.isSnapshot() || PMDVersion.isUnknown()) { + url = "https://pmd.github.io/latest/pmd_devdocs_working_with_properties.html"; } return url; } - /** - * {@inheritDoc} - */ @Override public void renderFileViolations(Iterator<RuleViolation> violations) throws IOException { Writer writer = getWriter(); @@ -165,11 +163,16 @@ private <T> String getBody() { } if (!rule.getPropertyDescriptors().isEmpty()) { - result += "\\n\\n### [PMD properties](" + pmdDeveloperUrl + ")\\n\\n"; + result += "\\n\\n### [PMD properties](" + PMD_PROPERTIES_URL + ")\\n\\n"; result += "Name | Value | Description\\n"; result += "--- | --- | ---\\n"; for (PropertyDescriptor<?> property : rule.getPropertyDescriptors()) { + String propertyName = property.name().replaceAll("\\_", "\\\\_"); + if (INTERNAL_DEV_PROPERTIES.contains(propertyName)) { + continue; + } + @SuppressWarnings("unchecked") PropertyDescriptor<T> typed = (PropertyDescriptor<T>) property; T value = rule.getProperty(typed); @@ -179,10 +182,7 @@ private <T> String getBody() { } propertyValue = propertyValue.replaceAll("(\n|\r\n|\r)", "\\\\n"); - String porpertyName = property.name(); - porpertyName = porpertyName.replaceAll("\\_", "\\\\_"); - - result += porpertyName + " | " + propertyValue + " | " + property.description() + "\\n"; + result += propertyName + " | " + propertyValue + " | " + property.description() + "\\n"; } } return cleaned(result); diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/renderers/EmacsRenderer.java b/pmd-core/src/main/java/net/sourceforge/pmd/renderers/EmacsRenderer.java index 388b1565b35..3a00d2161ed 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/renderers/EmacsRenderer.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/renderers/EmacsRenderer.java @@ -28,9 +28,6 @@ public String defaultFileExtension() { return "emacs"; } - /** - * {@inheritDoc} - */ @Override public void renderFileViolations(Iterator<RuleViolation> violations) throws IOException { Writer writer = getWriter(); diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/renderers/EmptyRenderer.java b/pmd-core/src/main/java/net/sourceforge/pmd/renderers/EmptyRenderer.java index 5a2c60b16f9..bf1207f7e12 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/renderers/EmptyRenderer.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/renderers/EmptyRenderer.java @@ -26,17 +26,21 @@ public String defaultFileExtension() { @Override public void start() throws IOException { + // deliberately does nothing } @Override public void startFileAnalysis(DataSource dataSource) { + // deliberately does nothing } @Override public void renderFileReport(Report report) throws IOException { + // deliberately does nothing } @Override public void end() throws IOException { + // deliberately does nothing } } diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/renderers/HTMLRenderer.java b/pmd-core/src/main/java/net/sourceforge/pmd/renderers/HTMLRenderer.java index 3733396f127..ecebba5dd5e 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/renderers/HTMLRenderer.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/renderers/HTMLRenderer.java @@ -75,9 +75,6 @@ public void renderBody(Writer writer, Report report) throws IOException { glomConfigurationErrors(writer, configErrors); } - /** - * {@inheritDoc} - */ @Override public void start() throws IOException { Writer writer = getWriter(); @@ -88,18 +85,12 @@ public void start() throws IOException { + "<th>#</th><th>File</th><th>Line</th><th>Problem</th></tr>" + PMD.EOL); } - /** - * {@inheritDoc} - */ @Override public void renderFileViolations(Iterator<RuleViolation> violations) throws IOException { Writer writer = getWriter(); glomRuleViolations(writer, violations); } - /** - * {@inheritDoc} - */ @Override public void end() throws IOException { Writer writer = getWriter(); diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/renderers/IDEAJRenderer.java b/pmd-core/src/main/java/net/sourceforge/pmd/renderers/IDEAJRenderer.java index 96563962b6e..f6cddabd647 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/renderers/IDEAJRenderer.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/renderers/IDEAJRenderer.java @@ -45,9 +45,6 @@ public String defaultFileExtension() { return "txt"; } - /** - * {@inheritDoc} - */ @Override public void renderFileViolations(Iterator<RuleViolation> violations) throws IOException { classAndMethodName = getProperty(CLASS_AND_METHOD_NAME); diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/renderers/Renderer.java b/pmd-core/src/main/java/net/sourceforge/pmd/renderers/Renderer.java index 4e82bb43dc8..c033f26f5b9 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/renderers/Renderer.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/renderers/Renderer.java @@ -40,6 +40,7 @@ public interface Renderer extends PropertySource { * * @return The name of the Renderer. */ + @Override String getName(); /** diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/renderers/RendererFactory.java b/pmd-core/src/main/java/net/sourceforge/pmd/renderers/RendererFactory.java index cee09d0118a..48631ab33f9 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/renderers/RendererFactory.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/renderers/RendererFactory.java @@ -21,7 +21,7 @@ * * @see Renderer */ -public class RendererFactory { +public final class RendererFactory { private static final Logger LOG = Logger.getLogger(RendererFactory.class.getName()); @@ -129,8 +129,8 @@ private static Constructor<? extends Renderer> getRendererConstructor(Class<? ex if (!Modifier.isPublic(constructor.getModifiers())) { constructor = null; } - } catch (NoSuchMethodException e) { - // Ok + } catch (NoSuchMethodException ignored) { + // Ignored, we'll check default constructor next } // 2) No-arg constructor? @@ -139,8 +139,8 @@ private static Constructor<? extends Renderer> getRendererConstructor(Class<? ex if (!Modifier.isPublic(constructor.getModifiers())) { constructor = null; } - } catch (NoSuchMethodException e2) { - // Ok + } catch (NoSuchMethodException ignored) { + // Ignored, we'll eventually throw an exception, if there is no constructor } if (constructor == null) { diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/renderers/SummaryHTMLRenderer.java b/pmd-core/src/main/java/net/sourceforge/pmd/renderers/SummaryHTMLRenderer.java index 38340759cb8..0b5c907a7d4 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/renderers/SummaryHTMLRenderer.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/renderers/SummaryHTMLRenderer.java @@ -31,9 +31,6 @@ public String defaultFileExtension() { return "html"; } - /** - * {@inheritDoc} - */ @Override public void end() throws IOException { writer.write("<html><head><title>PMD" + PMD.EOL); diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/renderers/TextColorRenderer.java b/pmd-core/src/main/java/net/sourceforge/pmd/renderers/TextColorRenderer.java index f3002017cdb..f28df9dab36 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/renderers/TextColorRenderer.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/renderers/TextColorRenderer.java @@ -13,8 +13,6 @@ import java.util.Iterator; import java.util.Map; -import org.apache.commons.io.IOUtils; - import net.sourceforge.pmd.PMD; import net.sourceforge.pmd.Report; import net.sourceforge.pmd.RuleViolation; @@ -109,9 +107,6 @@ private boolean isPropertyEnabled(String property) { return property != null && !("0".equals(property) || "false".equalsIgnoreCase(property)); } - /** - * {@inheritDoc} - */ @Override public void end() throws IOException { StringBuilder buf = new StringBuilder(500); @@ -194,17 +189,13 @@ public void end() throws IOException { */ private String getLine(String sourceFile, int line) { String code = null; - BufferedReader br = null; - try { - br = new BufferedReader(getReader(sourceFile)); + try (BufferedReader br = new BufferedReader(getReader(sourceFile))) { for (int i = 0; line > i; i++) { String txt = br.readLine(); code = txt == null ? "" : txt.trim(); } } catch (IOException ioErr) { ioErr.printStackTrace(); - } finally { - IOUtils.closeQuietly(br); } return code; } diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/renderers/TextPadRenderer.java b/pmd-core/src/main/java/net/sourceforge/pmd/renderers/TextPadRenderer.java index 79843a2dc7b..ad6f51f4319 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/renderers/TextPadRenderer.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/renderers/TextPadRenderer.java @@ -49,9 +49,6 @@ public String defaultFileExtension() { return "txt"; } - /** - * {@inheritDoc} - */ @Override public void renderFileViolations(Iterator violations) throws IOException { Writer writer = getWriter(); diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/renderers/TextRenderer.java b/pmd-core/src/main/java/net/sourceforge/pmd/renderers/TextRenderer.java index 372d013092c..aaf03fdb115 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/renderers/TextRenderer.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/renderers/TextRenderer.java @@ -28,16 +28,6 @@ public String defaultFileExtension() { return "txt"; } - /** - * {@inheritDoc} - */ - @Override - public void start() throws IOException { - } - - /** - * {@inheritDoc} - */ @Override public void renderFileViolations(Iterator violations) throws IOException { Writer writer = getWriter(); @@ -53,9 +43,6 @@ public void renderFileViolations(Iterator violations) throws IOEx } } - /** - * {@inheritDoc} - */ @Override public void end() throws IOException { Writer writer = getWriter(); diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/renderers/VBHTMLRenderer.java b/pmd-core/src/main/java/net/sourceforge/pmd/renderers/VBHTMLRenderer.java index 2af6da261a0..3c944a331ae 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/renderers/VBHTMLRenderer.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/renderers/VBHTMLRenderer.java @@ -30,17 +30,11 @@ public String defaultFileExtension() { return "vb.html"; } - /** - * {@inheritDoc} - */ @Override public void start() throws IOException { getWriter().write(header()); } - /** - * {@inheritDoc} - */ @Override public void renderFileViolations(Iterator violations) throws IOException { if (!violations.hasNext()) { @@ -87,9 +81,6 @@ public void renderFileViolations(Iterator violations) throws IOEx } } - /** - * {@inheritDoc} - */ @Override public void end() throws IOException { Writer writer = getWriter(); diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/renderers/XMLRenderer.java b/pmd-core/src/main/java/net/sourceforge/pmd/renderers/XMLRenderer.java index 7dd7de67687..52495551701 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/renderers/XMLRenderer.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/renderers/XMLRenderer.java @@ -11,6 +11,7 @@ import java.util.Iterator; import net.sourceforge.pmd.PMD; +import net.sourceforge.pmd.PMDVersion; import net.sourceforge.pmd.Report; import net.sourceforge.pmd.RuleViolation; import net.sourceforge.pmd.properties.StringProperty; @@ -42,9 +43,6 @@ public String defaultFileExtension() { return "xml"; } - /** - * {@inheritDoc} - */ @Override public void start() throws IOException { String encoding = getProperty(ENCODING); @@ -63,9 +61,6 @@ public void start() throws IOException { writer.write(buf.toString()); } - /** - * {@inheritDoc} - */ @Override public void renderFileViolations(Iterator violations) throws IOException { Writer writer = getWriter(); @@ -118,9 +113,6 @@ public void renderFileViolations(Iterator violations) throws IOEx } } - /** - * {@inheritDoc} - */ @Override public void end() throws IOException { Writer writer = getWriter(); @@ -181,7 +173,7 @@ private void createVersionAttr(StringBuilder buffer) { buffer.append(" reportNodesByPackage = new TreeMap<>(); + public YAHTMLRenderer() { // YA = Yet Another? super(NAME, "Yet Another HTML format."); @@ -31,15 +41,211 @@ public String defaultFileExtension() { return "html"; } - /** - * {@inheritDoc} - */ + 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(value) + .append("
+ + + +
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 00000000000..df94031c6ab --- /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 00000000000..65b0d3a738c --- /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-core/src/test/resources/net/sourceforge/pmd/rulesets/foo-project/foo-rules b/pmd-core/src/test/resources/net/sourceforge/pmd/rulesets/foo-project/foo-rules index 1b8a8f8a158..5a12247fc77 100644 --- a/pmd-core/src/test/resources/net/sourceforge/pmd/rulesets/foo-project/foo-rules +++ b/pmd-core/src/test/resources/net/sourceforge/pmd/rulesets/foo-project/foo-rules @@ -2,7 +2,7 @@ + xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd"> + + \ No newline at end of file diff --git a/pmd-core/src/test/resources/net/sourceforge/pmd/rulesets/ruleset-minimum-priority-exclusion.xml b/pmd-core/src/test/resources/net/sourceforge/pmd/rulesets/ruleset-minimum-priority-exclusion.xml new file mode 100644 index 00000000000..a0569f07f25 --- /dev/null +++ b/pmd-core/src/test/resources/net/sourceforge/pmd/rulesets/ruleset-minimum-priority-exclusion.xml @@ -0,0 +1,15 @@ + + + + + Ruleset used by test RuleSetFactoryTest + + + + + + + + + \ No newline at end of file diff --git a/pmd-core/src/test/resources/net/sourceforge/pmd/rulesets/ruleset-minimum-priority.xml b/pmd-core/src/test/resources/net/sourceforge/pmd/rulesets/ruleset-minimum-priority.xml new file mode 100644 index 00000000000..ec45ae89664 --- /dev/null +++ b/pmd-core/src/test/resources/net/sourceforge/pmd/rulesets/ruleset-minimum-priority.xml @@ -0,0 +1,17 @@ + + + + + Ruleset used by test RuleSetFactoryTest + + + + + + + + 5 + + + \ No newline at end of file diff --git a/pmd-core/src/test/resources/net/sourceforge/pmd/xml/j2ee.xml b/pmd-core/src/test/resources/net/sourceforge/pmd/xml/j2ee.xml index 2e2fd383af2..f158927f952 100644 --- a/pmd-core/src/test/resources/net/sourceforge/pmd/xml/j2ee.xml +++ b/pmd-core/src/test/resources/net/sourceforge/pmd/xml/j2ee.xml @@ -3,7 +3,7 @@ + xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd"> These are rules for J2EE diff --git a/pmd-core/src/test/resources/rulesets/MultiThreadProcessorTest/basic.xml b/pmd-core/src/test/resources/rulesets/MultiThreadProcessorTest/basic.xml index 499b6a45964..71b65b500e1 100644 --- a/pmd-core/src/test/resources/rulesets/MultiThreadProcessorTest/basic.xml +++ b/pmd-core/src/test/resources/rulesets/MultiThreadProcessorTest/basic.xml @@ -1,6 +1,6 @@ + xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd"> Ruleset used by test RuleSetReferenceIdTest diff --git a/pmd-core/src/test/resources/rulesets/MultiThreadProcessorTest/dysfunctional.xml b/pmd-core/src/test/resources/rulesets/MultiThreadProcessorTest/dysfunctional.xml new file mode 100644 index 00000000000..7ee4f275f95 --- /dev/null +++ b/pmd-core/src/test/resources/rulesets/MultiThreadProcessorTest/dysfunctional.xml @@ -0,0 +1,15 @@ + + + + + Ruleset used by test MultiThreadProcessorTest + + + + Dysfunctional + 3 + + + \ No newline at end of file diff --git a/pmd-core/src/test/resources/rulesets/dummy/basic.xml b/pmd-core/src/test/resources/rulesets/dummy/basic.xml index 0a141832a22..46654539eec 100644 --- a/pmd-core/src/test/resources/rulesets/dummy/basic.xml +++ b/pmd-core/src/test/resources/rulesets/dummy/basic.xml @@ -1,6 +1,6 @@ + xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd"> Ruleset used by test RuleSetReferenceIdTest @@ -32,5 +32,5 @@ Just for test ]]> -
" +
\ No newline at end of file diff --git a/pmd-core/src/test/resources/rulesets/dummy/deprecated.xml b/pmd-core/src/test/resources/rulesets/dummy/deprecated.xml new file mode 100644 index 00000000000..dd04f0fb84b --- /dev/null +++ b/pmd-core/src/test/resources/rulesets/dummy/deprecated.xml @@ -0,0 +1,15 @@ + + + + + Ruleset used by test RuleSetReferenceIdTest + + + + + + + + + \ No newline at end of file diff --git a/pmd-core/src/test/resources/rulesets/dummy/empty-ruleset.xml b/pmd-core/src/test/resources/rulesets/dummy/empty-ruleset.xml new file mode 100644 index 00000000000..b57e43ad946 --- /dev/null +++ b/pmd-core/src/test/resources/rulesets/dummy/empty-ruleset.xml @@ -0,0 +1,12 @@ + + + + + Empty Ruleset which contains no rules. + Although it is empty, it should not be marked as deprecated, just because it has no rules (yet). + + + + + \ No newline at end of file diff --git a/pmd-core/src/test/resources/rulesets/dummy/unusedcode.xml b/pmd-core/src/test/resources/rulesets/dummy/unusedcode.xml index e852f349721..fd0b7670dc5 100644 --- a/pmd-core/src/test/resources/rulesets/dummy/unusedcode.xml +++ b/pmd-core/src/test/resources/rulesets/dummy/unusedcode.xml @@ -1,6 +1,6 @@ + xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd"> Ruleset used by test RuleSetReferenceIdTest diff --git a/pmd-core/src/test/resources/rulesets/dummy2/basic.xml b/pmd-core/src/test/resources/rulesets/dummy2/basic.xml index ecd1644795a..b03bdf0b500 100644 --- a/pmd-core/src/test/resources/rulesets/dummy2/basic.xml +++ b/pmd-core/src/test/resources/rulesets/dummy2/basic.xml @@ -1,6 +1,6 @@ + xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd"> Ruleset used by test RuleSetReferenceIdTest diff --git a/pmd-cpp/etc/grammar/cpp.jj b/pmd-cpp/etc/grammar/cpp.jj index 3f65d72a8e9..a39effe9827 100644 --- a/pmd-cpp/etc/grammar/cpp.jj +++ b/pmd-cpp/etc/grammar/cpp.jj @@ -129,10 +129,9 @@ public final class CppParser { return sym.IsCtor(GetFullyScopedName()); } } - PARSER_END(CppParser) -SKIP : +SKIP: { " " | @@ -143,38 +142,28 @@ SKIP : "\r\n" | "\n" -| - "//" : IN_LINE_COMMENT -| - "/*" : IN_COMMENT | "#" : PREPROCESSOR_OUTPUT } - SKIP: -{ - "\n" : DEFAULT -} + SPECIAL_TOKEN: +{ } - MORE: -{ - < ~[] > -} +MORE: +{ "/*" : IN_MULTI_LINE_COMMENT } - SKIP: -{ "*/" : DEFAULT } + SPECIAL_TOKEN: +{ : DEFAULT } - MORE: + MORE: { < ~[] > } - SKIP: -{ "*/" : PREPROCESSOR_OUTPUT } + SPECIAL_TOKEN: +{ : PREPROCESSOR_OUTPUT } SKIP: { "\n" : DEFAULT - | "/*" : IN_PREPROCESSOR_OUTPUT_COMMENT - | "//" : IN_LINE_COMMENT } MORE: @@ -183,6 +172,8 @@ SKIP : | "\\\r\n" | + "/*": IN_PREPROCESSOR_OUTPUT_COMMENT + | < ~[] > } diff --git a/pmd-cpp/pom.xml b/pmd-cpp/pom.xml index 8448a6db021..178aa43ae44 100644 --- a/pmd-cpp/pom.xml +++ b/pmd-cpp/pom.xml @@ -7,13 +7,9 @@ net.sourceforge.pmd pmd - 6.0.0-SNAPSHOT + 6.10.0-SNAPSHOT - - ${basedir}/../pmd-core - - @@ -73,6 +69,10 @@ net.sourceforge.pmd pmd-core + + commons-io + commons-io + junit diff --git a/pmd-cpp/src/main/ant/alljavacc.xml b/pmd-cpp/src/main/ant/alljavacc.xml index 4066ee3a6fc..9347d126e4a 100644 --- a/pmd-cpp/src/main/ant/alljavacc.xml +++ b/pmd-cpp/src/main/ant/alljavacc.xml @@ -40,6 +40,57 @@ + + + public class Token implements java.io.Serializable + + + + + + public Token specialToken; + + + 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 e4462cba84f..ad27da15360 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-cpp/src/main/java/net/sourceforge/pmd/cpd/CPPTokenizer.java b/pmd-cpp/src/main/java/net/sourceforge/pmd/cpd/CPPTokenizer.java index 3a9b7b6fd23..b81450012b9 100644 --- a/pmd-cpp/src/main/java/net/sourceforge/pmd/cpd/CPPTokenizer.java +++ b/pmd-cpp/src/main/java/net/sourceforge/pmd/cpd/CPPTokenizer.java @@ -10,15 +10,14 @@ import java.io.StringReader; import java.util.Properties; -import org.apache.commons.io.IOUtils; - import net.sourceforge.pmd.PMD; +import net.sourceforge.pmd.cpd.token.JavaCCTokenFilter; +import net.sourceforge.pmd.cpd.token.TokenFilter; import net.sourceforge.pmd.lang.LanguageRegistry; import net.sourceforge.pmd.lang.LanguageVersionHandler; -import net.sourceforge.pmd.lang.TokenManager; +import net.sourceforge.pmd.lang.ast.GenericToken; import net.sourceforge.pmd.lang.ast.TokenMgrError; import net.sourceforge.pmd.lang.cpp.CppLanguageModule; -import net.sourceforge.pmd.lang.cpp.ast.Token; import net.sourceforge.pmd.util.IOUtil; /** @@ -55,32 +54,24 @@ public void setProperties(Properties properties) { @Override public void tokenize(SourceCode sourceCode, Tokens tokenEntries) { StringBuilder buffer = sourceCode.getCodeBuffer(); - Reader reader = null; - try { + try (Reader reader = IOUtil.skipBOM(new StringReader(maybeSkipBlocks(buffer.toString())))) { LanguageVersionHandler languageVersionHandler = LanguageRegistry.getLanguage(CppLanguageModule.NAME) .getDefaultVersion().getLanguageVersionHandler(); - reader = new StringReader(maybeSkipBlocks(buffer.toString())); - reader = IOUtil.skipBOM(reader); - TokenManager tokenManager = languageVersionHandler - .getParser(languageVersionHandler.getDefaultParserOptions()) - .getTokenManager(sourceCode.getFileName(), reader); - Token currentToken = (Token) tokenManager.getNextToken(); - while (currentToken.image.length() > 0) { - tokenEntries.add(new TokenEntry(currentToken.image, sourceCode.getFileName(), currentToken.beginLine)); - currentToken = (Token) tokenManager.getNextToken(); + final TokenFilter tokenFilter = new JavaCCTokenFilter( + languageVersionHandler.getParser(languageVersionHandler.getDefaultParserOptions()) + .getTokenManager(sourceCode.getFileName(), reader)); + + GenericToken currentToken = tokenFilter.getNextToken(); + while (currentToken != null) { + tokenEntries.add(new TokenEntry(currentToken.getImage(), sourceCode.getFileName(), currentToken.getBeginLine())); + currentToken = tokenFilter.getNextToken(); } tokenEntries.add(TokenEntry.getEOF()); System.err.println("Added " + sourceCode.getFileName()); - } catch (TokenMgrError err) { + } catch (TokenMgrError | IOException err) { err.printStackTrace(); System.err.println("Skipping " + sourceCode.getFileName() + " due to parse error"); tokenEntries.add(TokenEntry.getEOF()); - } catch (IOException e) { - e.printStackTrace(); - System.err.println("Skipping " + sourceCode.getFileName() + " due to parse error"); - tokenEntries.add(TokenEntry.getEOF()); - } finally { - IOUtils.closeQuietly(reader); } } diff --git a/pmd-cpp/src/main/java/net/sourceforge/pmd/lang/cpp/CppTokenManager.java b/pmd-cpp/src/main/java/net/sourceforge/pmd/lang/cpp/CppTokenManager.java index b22607777f0..83aa097c390 100644 --- a/pmd-cpp/src/main/java/net/sourceforge/pmd/lang/cpp/CppTokenManager.java +++ b/pmd-cpp/src/main/java/net/sourceforge/pmd/lang/cpp/CppTokenManager.java @@ -25,6 +25,7 @@ public CppTokenManager(Reader source) { tokenManager = new CppParserTokenManager(new CppCharStream(source)); } + @Override public Object getNextToken() { return tokenManager.getNextToken(); } diff --git a/pmd-cpp/src/test/java/net/sourceforge/pmd/cpd/CPPTokenizerTest.java b/pmd-cpp/src/test/java/net/sourceforge/pmd/cpd/CPPTokenizerTest.java index cc185a627f8..fb8fbd3360b 100644 --- a/pmd-cpp/src/test/java/net/sourceforge/pmd/cpd/CPPTokenizerTest.java +++ b/pmd-cpp/src/test/java/net/sourceforge/pmd/cpd/CPPTokenizerTest.java @@ -5,8 +5,10 @@ package net.sourceforge.pmd.cpd; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotSame; import static org.junit.Assert.assertTrue; +import java.nio.charset.StandardCharsets; import java.util.Properties; import org.apache.commons.io.IOUtils; @@ -19,7 +21,7 @@ public class CPPTokenizerTest { @Test public void testUTFwithBOM() { Tokens tokens = parse("\ufeffint start()\n{ int ret = 1;\nreturn ret;\n}\n"); - assertTrue(TokenEntry.getEOF() != tokens.getTokens().get(0)); + assertNotSame(TokenEntry.getEOF(), tokens.getTokens().get(0)); assertEquals(15, tokens.size()); } @@ -29,9 +31,19 @@ public void testUnicodeSupport() { + "int main()\n" + "{\n" + " std::string text(\"ąęćśźńó\");\n" + " std::cout << text;\n" + " return 0;\n" + "}\n"; Tokens tokens = parse(code); - assertTrue(TokenEntry.getEOF() != tokens.getTokens().get(0)); + assertNotSame(TokenEntry.getEOF(), tokens.getTokens().get(0)); assertEquals(24, tokens.size()); } + + @Test + public void testIgnoreBetweenSpecialComments() { + String code = "#include \n" + "#include \n" + "\n" + "// CPD-OFF\n" + + "int main()\n" + "{\n" + " std::string text(\"ąęćśźńó\");\n" + " std::cout << text;\n" + + " return 0;\n" + "// CPD-ON\n" + "}\n"; + Tokens tokens = parse(code); + assertNotSame(TokenEntry.getEOF(), tokens.getTokens().get(0)); + assertEquals(2, tokens.size()); // "}" + EOF + } @Test public void testMultiLineMacros() { @@ -56,21 +68,21 @@ public void testWideCharacters() { @Test public void testTokenizerWithSkipBlocks() throws Exception { - String test = IOUtils.toString(CPPTokenizerTest.class.getResourceAsStream("cpp/cpp_with_asm.cpp")); + String test = IOUtils.toString(CPPTokenizerTest.class.getResourceAsStream("cpp/cpp_with_asm.cpp"), StandardCharsets.UTF_8); Tokens tokens = parse(test, true); assertEquals(19, tokens.size()); } @Test public void testTokenizerWithSkipBlocksPattern() throws Exception { - String test = IOUtils.toString(CPPTokenizerTest.class.getResourceAsStream("cpp/cpp_with_asm.cpp")); + String test = IOUtils.toString(CPPTokenizerTest.class.getResourceAsStream("cpp/cpp_with_asm.cpp"), StandardCharsets.UTF_8); Tokens tokens = parse(test, true, "#if debug|#endif"); assertEquals(31, tokens.size()); } @Test public void testTokenizerWithoutSkipBlocks() throws Exception { - String test = IOUtils.toString(CPPTokenizerTest.class.getResourceAsStream("cpp/cpp_with_asm.cpp")); + String test = IOUtils.toString(CPPTokenizerTest.class.getResourceAsStream("cpp/cpp_with_asm.cpp"), StandardCharsets.UTF_8); Tokens tokens = parse(test, false); assertEquals(37, tokens.size()); } diff --git a/pmd-cs/pom.xml b/pmd-cs/pom.xml index 4b0a3e8f7fc..b4b5306ec8b 100644 --- a/pmd-cs/pom.xml +++ b/pmd-cs/pom.xml @@ -7,13 +7,9 @@ net.sourceforge.pmd pmd - 6.0.0-SNAPSHOT + 6.10.0-SNAPSHOT - - ${basedir}/../pmd-core - - @@ -32,7 +28,20 @@ net.sourceforge.pmd pmd-core + + commons-io + commons-io + + + org.apache.commons + commons-lang3 + + + junit + junit + test + net.sourceforge.pmd pmd-test diff --git a/pmd-cs/src/main/java/net/sourceforge/pmd/cpd/CsLanguage.java b/pmd-cs/src/main/java/net/sourceforge/pmd/cpd/CsLanguage.java index 2bf957a1ad7..e54edcddbec 100644 --- a/pmd-cs/src/main/java/net/sourceforge/pmd/cpd/CsLanguage.java +++ b/pmd-cs/src/main/java/net/sourceforge/pmd/cpd/CsLanguage.java @@ -20,6 +20,7 @@ public CsLanguage(Properties properties) { setProperties(properties); } + @Override public final void setProperties(Properties properties) { CsTokenizer tokenizer = (CsTokenizer) getTokenizer(); tokenizer.setProperties(properties); diff --git a/pmd-cs/src/main/java/net/sourceforge/pmd/cpd/CsTokenizer.java b/pmd-cs/src/main/java/net/sourceforge/pmd/cpd/CsTokenizer.java index 1f595003f3f..c3e4f6e6229 100644 --- a/pmd-cs/src/main/java/net/sourceforge/pmd/cpd/CsTokenizer.java +++ b/pmd-cs/src/main/java/net/sourceforge/pmd/cpd/CsTokenizer.java @@ -11,7 +11,6 @@ import java.io.PushbackReader; import java.util.Properties; -import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.RandomStringUtils; /** @@ -31,41 +30,43 @@ public void setProperties(Properties properties) { @Override public void tokenize(SourceCode sourceCode, Tokens tokenEntries) { - Tokenizer tokenizer = new Tokenizer(sourceCode.getCodeBuffer().toString()); - Token token = tokenizer.getNextToken(); - - while (!token.equals(Token.EOF)) { - Token lookAhead = tokenizer.getNextToken(); - - // Ignore using directives - // Only using directives should be ignored, because these are used - // to import namespaces - // - // Using directive: 'using System.Math;' - // Using statement: 'using (Font font1 = new Font(..)) { .. }' - if (ignoreUsings && "using".equals(token.image) && !"(".equals(lookAhead.image)) { - // We replace the 'using' token by a random token, because it - // should not be part of - // any duplication block. When we omit it from the token stream, - // there is a change that - // we get a duplication block that starts before the 'using' - // directives and ends afterwards. - String randomTokenText = RandomStringUtils.randomAlphanumeric(20); - - token = new Token(randomTokenText, token.lineNumber); - // Skip all other tokens of the using directive to prevent a - // partial matching - while (!";".equals(lookAhead.image) && !lookAhead.equals(Token.EOF)) { - lookAhead = tokenizer.getNextToken(); + try (Tokenizer tokenizer = new Tokenizer(sourceCode.getCodeBuffer().toString())) { + Token token = tokenizer.getNextToken(); + + while (!token.equals(Token.EOF)) { + Token lookAhead = tokenizer.getNextToken(); + + // Ignore using directives + // Only using directives should be ignored, because these are used + // to import namespaces + // + // Using directive: 'using System.Math;' + // Using statement: 'using (Font font1 = new Font(..)) { .. }' + if (ignoreUsings && "using".equals(token.image) && !"(".equals(lookAhead.image)) { + // We replace the 'using' token by a random token, because it + // should not be part of + // any duplication block. When we omit it from the token stream, + // there is a change that + // we get a duplication block that starts before the 'using' + // directives and ends afterwards. + String randomTokenText = RandomStringUtils.randomAlphanumeric(20); + + token = new Token(randomTokenText, token.lineNumber); + // Skip all other tokens of the using directive to prevent a + // partial matching + while (!";".equals(lookAhead.image) && !lookAhead.equals(Token.EOF)) { + lookAhead = tokenizer.getNextToken(); + } } + if (!";".equals(token.image)) { + tokenEntries.add(new TokenEntry(token.image, sourceCode.getFileName(), token.lineNumber)); + } + token = lookAhead; } - if (!";".equals(token.image)) { - tokenEntries.add(new TokenEntry(token.image, sourceCode.getFileName(), token.lineNumber)); - } - token = lookAhead; + tokenEntries.add(TokenEntry.getEOF()); + } catch (IOException e) { + e.printStackTrace(); } - tokenEntries.add(TokenEntry.getEOF()); - IOUtils.closeQuietly(tokenizer); } public void setIgnoreUsings(boolean ignoreUsings) { diff --git a/pmd-dist/pom.xml b/pmd-dist/pom.xml index 7c56cf059d0..7aa2c57082c 100644 --- a/pmd-dist/pom.xml +++ b/pmd-dist/pom.xml @@ -8,13 +8,9 @@ net.sourceforge.pmd pmd - 6.0.0-SNAPSHOT + 6.10.0-SNAPSHOT - - ${basedir}/../pmd-core - - @@ -156,6 +152,11 @@ pmd-ruby ${project.version} + + net.sourceforge.pmd + pmd-scala + ${project.version} + net.sourceforge.pmd pmd-swift @@ -172,6 +173,17 @@ ${project.version} + + + commons-io + commons-io + + + + org.apache.commons + commons-lang3 + + junit junit @@ -180,7 +192,7 @@ org.apache.commons commons-compress - 1.13 + 1.18 test @@ -197,12 +209,6 @@ pmd-apex ${project.version} - - net.sourceforge.pmd - pmd-apex - ${project.version} - apex-jorje-shaded - net.sourceforge.pmd pmd-ui @@ -210,23 +216,5 @@ - - jdk9-disabled - - !1.9 - - - - - net.sourceforge.pmd - pmd-scala - ${project.version} - - - diff --git a/pmd-dist/src/main/assembly/bin.xml b/pmd-dist/src/main/assembly/bin.xml index 153f25e2b95..2b44241aa97 100644 --- a/pmd-dist/src/main/assembly/bin.xml +++ b/pmd-dist/src/main/assembly/bin.xml @@ -52,10 +52,6 @@ 0755 0644 false - - - apex:* - diff --git a/pmd-dist/src/main/scripts/designer.bat b/pmd-dist/src/main/scripts/designer.bat index 21353704597..60a4fecc634 100644 --- a/pmd-dist/src/main/scripts/designer.bat +++ b/pmd-dist/src/main/scripts/designer.bat @@ -1,6 +1,51 @@ @echo off set TOPDIR=%~dp0.. set OPTS= -set MAIN_CLASS=net.sourceforge.pmd.util.designer.Designer +set MAIN_CLASS=net.sourceforge.pmd.util.fxdesigner.DesignerStarter -java -classpath "%TOPDIR%\lib\*" %OPTS% %MAIN_CLASS% %* + +:: sets the jver variable to the java version, eg 901 for 9.0.1+x or 180 for 1.8.0_171-b11 +:: sets the jvendor variable to either java (oracle) or openjdk +for /f tokens^=1^,3^,4^,5^ delims^=.-_+^"^ %%j in ('java -version 2^>^&1 ^| find "version"') do ( + set jvendor=%%j + if %%l EQU ea ( + set /A "jver=%%k00" + ) else ( + set /A jver=%%k%%l%%m + ) +) + +Set "jreopts=" +:: oracle java 9 and 10 has javafx included as a module +if /I "%jvendor%" EQU "java" ( + if %jver% GEQ 900 ( + if %jver% LSS 1100 ( + :: enable reflection + Set jreopts=--add-opens javafx.controls/javafx.scene.control.skin=ALL-UNNAMED + ) + ) +) + +set "_needjfxlib=0" +if /I "%jvendor%" EQU "openjdk" set _needjfxlib=1 +if /I "%jvendor%" EQU "java" ( + if %jver% GEQ 1100 set _needjfxlib=1 +) +if %_needjfxlib% EQU 1 ( + if %jver% LSS 1000 ( + echo For openjfx at least java 10 is required. + pause + exit + ) + if [%JAVAFX_HOME%] EQU [] ( + echo The environment variable JAVAFX_HOME is missing. + pause + exit + ) + set "classpath=%TOPDIR%\lib\*;%JAVAFX_HOME%\lib\*" +) else ( + set "classpath=%TOPDIR%\lib\*" +) + + +java %jreopts% -classpath "%classpath%" %OPTS% %MAIN_CLASS% %* diff --git a/pmd-dist/src/main/scripts/designerold.bat b/pmd-dist/src/main/scripts/designerold.bat new file mode 100644 index 00000000000..21353704597 --- /dev/null +++ b/pmd-dist/src/main/scripts/designerold.bat @@ -0,0 +1,6 @@ +@echo off +set TOPDIR=%~dp0.. +set OPTS= +set MAIN_CLASS=net.sourceforge.pmd.util.designer.Designer + +java -classpath "%TOPDIR%\lib\*" %OPTS% %MAIN_CLASS% %* diff --git a/pmd-dist/src/main/scripts/run.sh b/pmd-dist/src/main/scripts/run.sh index fc05262f32c..79e4ef609e0 100755 --- a/pmd-dist/src/main/scripts/run.sh +++ b/pmd-dist/src/main/scripts/run.sh @@ -10,7 +10,7 @@ usage() { } valid_app_options () { - echo "pmd, cpd, cpdgui, designer, bgastviewer" + echo "pmd, cpd, cpdgui, designer, bgastviewer, designerold" } is_cygwin() { @@ -29,6 +29,7 @@ cygwin_paths() { # For Cygwin, switch paths to Windows format before running java if ${cygwin} ; then [ -n "${JAVA_HOME}" ] && JAVA_HOME=$(cygpath --windows "${JAVA_HOME}") + [ -n "${JAVAFX_HOME}" ] && JAVAFX_HOME=$(cygpath --windows "${JAVAFX_HOME}") [ -n "${DIRECTORY}" ] && DIRECTORY=$(cygpath --windows "${DIRECTORY}") classpath=$(cygpath --path --windows "${classpath}") fi @@ -38,6 +39,7 @@ convert_cygwin_vars() { # If cygwin, convert to Unix form before manipulating if ${cygwin} ; then [ -n "${JAVA_HOME}" ] && JAVA_HOME=$(cygpath --unix "${JAVA_HOME}") + [ -n "${JAVAFX_HOME}" ] && JAVAFX_HOME=$(cygpath --unix "${JAVAFX_HOME}") [ -n "${CLASSPATH}" ] && CLASSPATH=$(cygpath --path --unix "${CLASSPATH}") fi } @@ -59,7 +61,13 @@ java_heapsize_settings() { set_lib_dir() { if [ -z ${LIB_DIR} ]; then - local script_dir=$(dirname "${0}") + # Allow for symlinks to this script + if [ -L $0 ]; then + local script_real_loc=$(readlink "$0") + else + local script_real_loc=$0 + fi + local script_dir=$(dirname "${script_real_loc}") local cwd="${PWD}" cd "${script_dir}/../lib" @@ -74,6 +82,91 @@ check_lib_dir() { fi } +function script_exit() { + echo $1 >&2 + exit 1 +} + +determine_java_version() { + local 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 + readonly 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 + }') + # java_vendor is either java (oracle) or openjdk + readonly java_vendor=$(echo $full_ver | sed -n -e 's/^\(.*\) version .*$/\1/p') +} + +jre_specific_vm_options() { + if [ "${APPNAME}" = "designer" ] + then + options="" + + if [ "$java_ver" -ge 80 ] && [ "$java_ver" -lt 90 ] + then + # no options needed for java8. + options="" + elif [ "$java_ver" -ge 90 ] && [ "$java_ver" -lt 110 ] && [ "$java_vendor" = "java" ] + then + # java9 and java10 from oracle contain javafx as a module + # open internal module of javafx to reflection (for our TreeViewWrapper) + options="--add-opens javafx.controls/javafx.scene.control.skin=ALL-UNNAMED" + # The rest here is for RichtextFX + options+=" --add-opens javafx.graphics/javafx.scene.text=ALL-UNNAMED" + options+=" --add-opens javafx.graphics/com.sun.javafx.scene.text=ALL-UNNAMED" + options+=" --add-opens javafx.graphics/com.sun.javafx.text=ALL-UNNAMED" + options+=" --add-opens javafx.graphics/com.sun.javafx.geom=ALL-UNNAMED" + # Warn of remaining illegal accesses + options+=" --illegal-access=warn" + elif [ "$java_vendor" = "openjdk" ] || ( [ "$java_vendor" = "java" ] && [ "$java_ver" -ge 110 ] ) + then + # openjdk and java11 from oracle onwards do not contain javafx directly + # there are no extra options either - javafx will be added to the classpath without modules + options="" + fi + + echo $options + else + echo "" + fi +} + +function add_pmd_classpath() { + if [ -n "$classpath" ]; then + classpath="$classpath:${LIB_DIR}/*" + else + classpath="${LIB_DIR}/*" + fi +} + +function add_openjfx_classpath() { + if [ "${APPNAME}" = "designer" ] + then + if [ "$java_vendor" = "openjdk" ] && [ "$java_ver" -lt 100 ] + then + script_exit "For openjfx at least java 10 is required" + elif [ "$java_vendor" = "openjdk" ] || ( [ "$java_vendor" = "java" ] && [ "$java_ver" -ge 110 ] ) + then + # openjfx is required for openjdk builds and oracle java 11 or later + if [ -z "${JAVAFX_HOME}" ] + then + script_exit "The environment variable JAVAFX_HOME is missing." + else + if [ -n "$classpath" ]; then + classpath="$classpath:${JAVAFX_HOME}/lib/*" + else + classpath="${JAVAFX_HOME}/lib/*" + fi + fi + fi + fi +} + readonly APPNAME="${1}" if [ -z "${APPNAME}" ]; then usage @@ -89,6 +182,9 @@ case "${APPNAME}" in readonly CLASSNAME="net.sourceforge.pmd.cpd.CPD" ;; "designer") + readonly CLASSNAME="net.sourceforge.pmd.util.fxdesigner.DesignerStarter" + ;; + "designerold") readonly CLASSNAME="net.sourceforge.pmd.util.designer.Designer" ;; "bgastviewer") @@ -111,18 +207,12 @@ convert_cygwin_vars classpath=$CLASSPATH -cd "${CWD}" - -for jarfile in "${LIB_DIR}"/*.jar; do - if [ -n "$classpath" ]; then - classpath=$classpath:$jarfile - else - classpath=$jarfile - fi -done +add_pmd_classpath +determine_java_version +add_openjfx_classpath cygwin_paths java_heapsize_settings -java ${HEAPSIZE} -cp "${classpath}" "${CLASSNAME}" "$@" +java ${HEAPSIZE} $(jre_specific_vm_options) -cp "${classpath}" "${CLASSNAME}" "$@" diff --git a/pmd-dist/src/test/java/net/sourceforge/pmd/it/BinaryDistributionIT.java b/pmd-dist/src/test/java/net/sourceforge/pmd/it/BinaryDistributionIT.java index 0796d4d907c..c9c52d291ee 100644 --- a/pmd-dist/src/test/java/net/sourceforge/pmd/it/BinaryDistributionIT.java +++ b/pmd-dist/src/test/java/net/sourceforge/pmd/it/BinaryDistributionIT.java @@ -21,12 +21,12 @@ import org.junit.BeforeClass; import org.junit.Test; -import net.sourceforge.pmd.PMD; +import net.sourceforge.pmd.PMDVersion; public class BinaryDistributionIT { private static File getBinaryDistribution() { - return new File(".", "target/pmd-bin-" + PMD.VERSION + ".zip"); + return new File(".", "target/pmd-bin-" + PMDVersion.VERSION + ".zip"); } /** @@ -57,13 +57,13 @@ public void testFileExistence() { private Set getExpectedFileNames() { Set result = new HashSet<>(); - String basedir = "pmd-bin-" + PMD.VERSION + "/"; + String basedir = "pmd-bin-" + PMDVersion.VERSION + "/"; result.add(basedir); result.add(basedir + "bin/run.sh"); result.add(basedir + "bin/pmd.bat"); result.add(basedir + "bin/cpd.bat"); - result.add(basedir + "lib/pmd-core-" + PMD.VERSION + ".jar"); - result.add(basedir + "lib/pmd-java-" + PMD.VERSION + ".jar"); + result.add(basedir + "lib/pmd-core-" + PMDVersion.VERSION + ".jar"); + result.add(basedir + "lib/pmd-java-" + PMDVersion.VERSION + ".jar"); return result; } @@ -93,11 +93,11 @@ public void runPMD() throws Exception { result = PMDExecutor.runPMD(tempDir, "-h"); result.assertExecutionResult(1, "apex, ecmascript, java, jsp, plsql, pom, vf, vm, wsdl, xml, xsl"); - result = PMDExecutor.runPMDRules(tempDir, srcDir, "java-basic"); + result = PMDExecutor.runPMDRules(tempDir, srcDir, "src/test/resources/rulesets/sample-ruleset.xml"); result.assertExecutionResult(4, "JumbledIncrementer.java:8:"); - result = PMDExecutor.runPMDRules(tempDir, srcDir, "java-design"); - result.assertExecutionResult(0, ""); + result = PMDExecutor.runPMDRules(tempDir, srcDir, "rulesets/java/quickstart.xml"); + result.assertExecutionResult(4, ""); } @Test @@ -107,6 +107,7 @@ public void runCPD() throws Exception { ExecutionResult result; result = CpdExecutor.runCpd(tempDir, "-h"); + result.assertExecutionResult(1, "Supported languages: [apex, cpp, cs, ecmascript, fortran, go, groovy, java, jsp, matlab, objectivec, perl, php, plsql, python, ruby, scala, swift, vf]"); result = CpdExecutor.runCpd(tempDir, "--minimum-tokens", "10", "--format", "text", "--files", srcDir); diff --git a/pmd-dist/src/test/java/net/sourceforge/pmd/it/CpdExecutor.java b/pmd-dist/src/test/java/net/sourceforge/pmd/it/CpdExecutor.java index ee6a3e617c0..42e80430ac5 100644 --- a/pmd-dist/src/test/java/net/sourceforge/pmd/it/CpdExecutor.java +++ b/pmd-dist/src/test/java/net/sourceforge/pmd/it/CpdExecutor.java @@ -4,13 +4,14 @@ package net.sourceforge.pmd.it; +import java.nio.charset.StandardCharsets; import java.nio.file.Path; import java.util.Arrays; import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.SystemUtils; -import net.sourceforge.pmd.PMD; +import net.sourceforge.pmd.PMDVersion; /** * Executes CPD from command line. Deals with the differences, when CPD is run on Windows or on Linux. @@ -25,24 +26,24 @@ private CpdExecutor() { } private static ExecutionResult runCpdUnix(Path tempDir, String ... arguments) throws Exception { - String cmd = tempDir.resolve(PMD_BIN_PREFIX + PMD.VERSION + "/bin/run.sh").toAbsolutePath().toString(); + String cmd = tempDir.resolve(PMD_BIN_PREFIX + PMDVersion.VERSION + "/bin/run.sh").toAbsolutePath().toString(); ProcessBuilder pb = new ProcessBuilder(cmd, "cpd"); pb.command().addAll(Arrays.asList(arguments)); pb.redirectErrorStream(true); Process process = pb.start(); - String output = IOUtils.toString(process.getInputStream()); + String output = IOUtils.toString(process.getInputStream(), StandardCharsets.UTF_8); int result = process.waitFor(); return new ExecutionResult(result, output); } private static ExecutionResult runCpdWindows(Path tempDir, String ... arguments) throws Exception { - String cmd = tempDir.resolve(PMD_BIN_PREFIX + PMD.VERSION + "/bin/cpd.bat").toAbsolutePath().toString(); + String cmd = tempDir.resolve(PMD_BIN_PREFIX + PMDVersion.VERSION + "/bin/cpd.bat").toAbsolutePath().toString(); ProcessBuilder pb = new ProcessBuilder(cmd); pb.command().addAll(Arrays.asList(arguments)); pb.redirectErrorStream(true); Process process = pb.start(); - String output = IOUtils.toString(process.getInputStream()); + String output = IOUtils.toString(process.getInputStream(), StandardCharsets.UTF_8); int result = process.waitFor(); return new ExecutionResult(result, output); diff --git a/pmd-dist/src/test/java/net/sourceforge/pmd/it/PMDExecutor.java b/pmd-dist/src/test/java/net/sourceforge/pmd/it/PMDExecutor.java index 309dc9d1149..1e127a1f0fb 100644 --- a/pmd-dist/src/test/java/net/sourceforge/pmd/it/PMDExecutor.java +++ b/pmd-dist/src/test/java/net/sourceforge/pmd/it/PMDExecutor.java @@ -4,13 +4,14 @@ package net.sourceforge.pmd.it; +import java.nio.charset.StandardCharsets; import java.nio.file.Path; import java.util.Arrays; import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.SystemUtils; -import net.sourceforge.pmd.PMD; +import net.sourceforge.pmd.PMDVersion; /** * Executes PMD from command line. Deals with the differences, when PMD is run on Windows or on Linux. @@ -29,24 +30,24 @@ private PMDExecutor() { } private static ExecutionResult runPMDUnix(Path tempDir, String ... arguments) throws Exception { - String cmd = tempDir.resolve(PMD_BIN_PREFIX + PMD.VERSION + "/bin/run.sh").toAbsolutePath().toString(); + String cmd = tempDir.resolve(PMD_BIN_PREFIX + PMDVersion.VERSION + "/bin/run.sh").toAbsolutePath().toString(); ProcessBuilder pb = new ProcessBuilder(cmd, "pmd"); pb.command().addAll(Arrays.asList(arguments)); pb.redirectErrorStream(true); Process process = pb.start(); - String output = IOUtils.toString(process.getInputStream()); + String output = IOUtils.toString(process.getInputStream(), StandardCharsets.UTF_8); int result = process.waitFor(); return new ExecutionResult(result, output); } private static ExecutionResult runPMDWindows(Path tempDir, String ... arguments) throws Exception { - String cmd = tempDir.resolve(PMD_BIN_PREFIX + PMD.VERSION + "/bin/pmd.bat").toAbsolutePath().toString(); + String cmd = tempDir.resolve(PMD_BIN_PREFIX + PMDVersion.VERSION + "/bin/pmd.bat").toAbsolutePath().toString(); ProcessBuilder pb = new ProcessBuilder(cmd); pb.command().addAll(Arrays.asList(arguments)); pb.redirectErrorStream(true); Process process = pb.start(); - String output = IOUtils.toString(process.getInputStream()); + String output = IOUtils.toString(process.getInputStream(), StandardCharsets.UTF_8); int result = process.waitFor(); return new ExecutionResult(result, output); diff --git a/pmd-dist/src/test/java/net/sourceforge/pmd/it/SourceDistributionIT.java b/pmd-dist/src/test/java/net/sourceforge/pmd/it/SourceDistributionIT.java index 76a36363e47..0564cb04332 100644 --- a/pmd-dist/src/test/java/net/sourceforge/pmd/it/SourceDistributionIT.java +++ b/pmd-dist/src/test/java/net/sourceforge/pmd/it/SourceDistributionIT.java @@ -10,11 +10,11 @@ import org.junit.Test; -import net.sourceforge.pmd.PMD; +import net.sourceforge.pmd.PMDVersion; public class SourceDistributionIT { private File getSourceDistribution() { - return new File(".", "target/pmd-src-" + PMD.VERSION + ".zip"); + return new File(".", "target/pmd-src-" + PMDVersion.VERSION + ".zip"); } @Test diff --git a/pmd-dist/src/test/resources/rulesets/sample-ruleset.xml b/pmd-dist/src/test/resources/rulesets/sample-ruleset.xml new file mode 100644 index 00000000000..a03e6aaea96 --- /dev/null +++ b/pmd-dist/src/test/resources/rulesets/sample-ruleset.xml @@ -0,0 +1,10 @@ + + + Sample Ruleset for Integration Test + + + + diff --git a/pmd-doc/pom.xml b/pmd-doc/pom.xml index 23c78952a14..a1474717919 100644 --- a/pmd-doc/pom.xml +++ b/pmd-doc/pom.xml @@ -8,12 +8,14 @@ net.sourceforge.pmd pmd - 6.0.0-SNAPSHOT + 6.10.0-SNAPSHOT - 1.8 - ${basedir}/../pmd-core + 8 + + 1.${java.version} + 1.${java.version} @@ -45,6 +47,25 @@ net.sourceforge.pmd pmd-dist ${project.version} + runtime + + + net.sourceforge.pmd + pmd-core + ${project.version} + + + commons-io + commons-io + + + org.apache.commons + commons-lang3 + + + org.yaml + snakeyaml + 1.19 diff --git a/pmd-doc/src/main/java/net/sourceforge/pmd/docs/GenerateRuleDocsCmd.java b/pmd-doc/src/main/java/net/sourceforge/pmd/docs/GenerateRuleDocsCmd.java index 64f04bc962e..e58f1d72a72 100644 --- a/pmd-doc/src/main/java/net/sourceforge/pmd/docs/GenerateRuleDocsCmd.java +++ b/pmd-doc/src/main/java/net/sourceforge/pmd/docs/GenerateRuleDocsCmd.java @@ -4,15 +4,26 @@ package net.sourceforge.pmd.docs; +import java.io.File; +import java.io.IOException; import java.nio.file.FileSystems; +import java.nio.file.FileVisitResult; +import java.nio.file.Files; import java.nio.file.Path; +import java.nio.file.SimpleFileVisitor; +import java.nio.file.attribute.BasicFileAttributes; +import java.util.ArrayList; import java.util.Iterator; +import java.util.List; +import java.util.regex.Pattern; + +import org.apache.commons.io.FilenameUtils; import net.sourceforge.pmd.RuleSet; import net.sourceforge.pmd.RuleSetFactory; import net.sourceforge.pmd.RuleSetNotFoundException; -public class GenerateRuleDocsCmd { +public final class GenerateRuleDocsCmd { private GenerateRuleDocsCmd() { // Utility class } @@ -21,12 +32,36 @@ public static void main(String[] args) throws RuleSetNotFoundException { long start = System.currentTimeMillis(); Path output = FileSystems.getDefault().getPath(args[0]).resolve("..").toAbsolutePath().normalize(); System.out.println("Generating docs into " + output); - RuleDocGenerator generator = new RuleDocGenerator(new DefaultFileWriter(), output); RuleSetFactory ruleSetFactory = new RuleSetFactory(); Iterator registeredRuleSets = ruleSetFactory.getRegisteredRuleSets(); + List additionalRulesets = findAdditionalRulesets(output); + + RuleDocGenerator generator = new RuleDocGenerator(new DefaultFileWriter(), output); + generator.generate(registeredRuleSets, additionalRulesets); - generator.generate(registeredRuleSets); System.out.println("Generated docs in " + (System.currentTimeMillis() - start) + " ms"); } + + public static List findAdditionalRulesets(Path basePath) { + try { + List additionalRulesets = new ArrayList<>(); + Pattern rulesetPattern = Pattern.compile("^.+" + Pattern.quote(File.separator) + "pmd-\\w+" + + Pattern.quote(FilenameUtils.normalize("/src/main/resources/rulesets/")) + + "\\w+" + Pattern.quote(File.separator) + "\\w+.xml$"); + Files.walkFileTree(basePath, new SimpleFileVisitor() { + @Override + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { + if (rulesetPattern.matcher(file.toString()).matches()) { + additionalRulesets.add(file.toString()); + } + + return FileVisitResult.CONTINUE; + } + }); + return additionalRulesets; + } catch (IOException e) { + throw new RuntimeException(e); + } + } } diff --git a/pmd-doc/src/main/java/net/sourceforge/pmd/docs/RuleDocGenerator.java b/pmd-doc/src/main/java/net/sourceforge/pmd/docs/RuleDocGenerator.java index d0800c8b47d..aac961c1688 100644 --- a/pmd-doc/src/main/java/net/sourceforge/pmd/docs/RuleDocGenerator.java +++ b/pmd-doc/src/main/java/net/sourceforge/pmd/docs/RuleDocGenerator.java @@ -12,6 +12,7 @@ import java.nio.file.SimpleFileVisitor; import java.nio.file.attribute.BasicFileAttributes; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Comparator; @@ -22,19 +23,29 @@ import java.util.Locale; import java.util.Map; import java.util.Objects; +import java.util.SortedMap; +import java.util.TreeMap; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import org.apache.commons.io.FilenameUtils; import org.apache.commons.lang3.StringUtils; import net.sourceforge.pmd.Rule; import net.sourceforge.pmd.RuleSet; +import net.sourceforge.pmd.RuleSetFactory; import net.sourceforge.pmd.RuleSetNotFoundException; import net.sourceforge.pmd.lang.Language; import net.sourceforge.pmd.lang.rule.RuleReference; import net.sourceforge.pmd.lang.rule.XPathRule; +import net.sourceforge.pmd.properties.MultiValuePropertyDescriptor; import net.sourceforge.pmd.properties.PropertyDescriptor; public class RuleDocGenerator { + private static final Logger LOG = Logger.getLogger(RuleDocGenerator.class.getName()); + private static final String LANGUAGE_INDEX_FILENAME_PATTERN = "docs/pages/pmd/rules/${language.tersename}.md"; private static final String LANGUAGE_INDEX_PERMALINK_PATTERN = "pmd_rules_${language.tersename}.html"; private static final String RULESET_INDEX_FILENAME_PATTERN = "docs/pages/pmd/rules/${language.tersename}/${ruleset.name}.md"; @@ -42,13 +53,11 @@ public class RuleDocGenerator { private static final String DEPRECATION_LABEL_SMALL = "Deprecated "; private static final String DEPRECATION_LABEL = "Deprecated "; + private static final String DEPRECATED_RULE_PROPERTY_MARKER = "deprecated!"; private static final String GITHUB_SOURCE_LINK = "https://github.com/pmd/pmd/blob/master/"; - private final Path root; - private final FileWriter writer; - - /** Maintains mapping from pmd terse language name to rouge highlighter language */ + /** Maintains mapping from pmd terse language name to rouge highlighter language. */ private static final Map LANGUAGE_HIGHLIGHT_MAPPER = new HashMap<>(); static { @@ -58,9 +67,12 @@ public class RuleDocGenerator { LANGUAGE_HIGHLIGHT_MAPPER.put("plsql", "sql"); } + private final Path root; + private final FileWriter writer; + public RuleDocGenerator(FileWriter writer, Path root) { - this.root = Objects.requireNonNull(root, "Root directory must be provided"); this.writer = Objects.requireNonNull(writer, "A file writer must be provided"); + this.root = Objects.requireNonNull(root, "Root directory must be provided"); Path docsDir = root.resolve("docs"); if (!Files.exists(docsDir) || !Files.isDirectory(docsDir)) { @@ -68,24 +80,56 @@ public RuleDocGenerator(FileWriter writer, Path root) { } } - public void generate(Iterator rulesets) { + public void generate(Iterator registeredRulesets, List additionalRulesets) { Map> sortedRulesets; + Map> sortedAdditionalRulesets; try { - sortedRulesets = sortRulesets(rulesets); - generateLanguageIndex(sortedRulesets); + sortedRulesets = sortRulesets(registeredRulesets); + sortedAdditionalRulesets = sortRulesets(resolveAdditionalRulesets(additionalRulesets)); + generateLanguageIndex(sortedRulesets, sortedAdditionalRulesets); generateRuleSetIndex(sortedRulesets); + generateSidebar(sortedRulesets); + } catch (RuleSetNotFoundException | IOException e) { throw new RuntimeException(e); } } + private void generateSidebar(Map> sortedRulesets) throws IOException { + SidebarGenerator generator = new SidebarGenerator(writer, root); + generator.generateSidebar(sortedRulesets); + } + + private Iterator resolveAdditionalRulesets(List additionalRulesets) throws RuleSetNotFoundException { + if (additionalRulesets == null) { + return Collections.emptyIterator(); + } + + List rulesets = new ArrayList<>(); + RuleSetFactory ruleSetFactory = new RuleSetFactory(); + for (String filename : additionalRulesets) { + try { + // do not take rulesets from pmd-test or pmd-core + if (!filename.contains("pmd-test") && !filename.contains("pmd-core")) { + rulesets.add(ruleSetFactory.createRuleSet(filename)); + } else { + LOG.fine("Ignoring ruleset " + filename); + } + } catch (IllegalArgumentException e) { + // ignore rulesets, we can't read + LOG.log(Level.WARNING, "ruleset file " + filename + " ignored (" + e.getMessage() + ")", e); + } + } + return rulesets.iterator(); + } + private Path getAbsoluteOutputPath(String filename) { return root.resolve(FilenameUtils.normalize(filename)); } private Map> sortRulesets(Iterator rulesets) throws RuleSetNotFoundException { - Map> rulesetsByLanguage = new HashMap<>(); + SortedMap> rulesetsByLanguage = new TreeMap<>(); while (rulesets.hasNext()) { RuleSet ruleset = rulesets.next(); @@ -126,10 +170,11 @@ private static Language getRuleSetLanguage(RuleSet ruleset) { /** * Writes for each language an index file, which lists the rulesets, the rules * and links to the ruleset pages. - * @param rulesets all rulesets + * @param rulesets all registered/built-in rulesets + * @param sortedAdditionalRulesets additional rulesets * @throws IOException */ - private void generateLanguageIndex(Map> rulesets) throws IOException { + private void generateLanguageIndex(Map> rulesets, Map> sortedAdditionalRulesets) throws IOException { for (Map.Entry> entry : rulesets.entrySet()) { String languageTersename = entry.getKey().getTerseName(); String filename = LANGUAGE_INDEX_FILENAME_PATTERN @@ -139,28 +184,23 @@ private void generateLanguageIndex(Map> rulesets) throws List lines = new LinkedList<>(); lines.add("---"); lines.add("title: " + entry.getKey().getName() + " Rules"); + lines.add("tags: [rule_references, " + languageTersename + "]"); + lines.add("summary: Index of all built-in rules available for " + entry.getKey().getName()); + lines.add("language_name: " + entry.getKey().getName()); lines.add("permalink: " + LANGUAGE_INDEX_PERMALINK_PATTERN.replace("${language.tersename}", languageTersename)); lines.add("folder: pmd/rules"); lines.add("---"); - lines.add("List of rulesets and rules contained in each ruleset."); - lines.add(""); - - for (RuleSet ruleset : entry.getValue()) { - String link = RULESET_INDEX_PERMALINK_PATTERN - .replace("${language.tersename}", languageTersename) - .replace("${ruleset.name}", getRuleSetFilename(ruleset)); - lines.add("* [" + ruleset.getName() + "](" + link + "): " + getRuleSetDescriptionSingleLine(ruleset)); - } - lines.add(""); - for (RuleSet ruleset : entry.getValue()) { lines.add("## " + ruleset.getName()); + lines.add(""); + lines.add("{% include callout.html content=\"" + getRuleSetDescriptionSingleLine(ruleset).replaceAll("\"", "'") + "\" %}"); + lines.add(""); for (Rule rule : getSortedRules(ruleset)) { String link = RULESET_INDEX_PERMALINK_PATTERN .replace("${language.tersename}", languageTersename) - .replace("${ruleset.name}", getRuleSetFilename(ruleset)); + .replace("${ruleset.name}", RuleSetUtils.getRuleSetFilename(ruleset)); if (rule instanceof RuleReference) { RuleReference ref = (RuleReference) rule; if (ruleset.getFileName().equals(ref.getRuleSetReference().getRuleSetFileName())) { @@ -173,7 +213,7 @@ private void generateLanguageIndex(Map> rulesets) throws // rule moved to another ruleset... String otherLink = RULESET_INDEX_PERMALINK_PATTERN .replace("${language.tersename}", languageTersename) - .replace("${ruleset.name}", getRuleSetFilename(ref.getRuleSetReference().getRuleSetFileName())); + .replace("${ruleset.name}", RuleSetUtils.getRuleSetFilename(ref.getRuleSetReference().getRuleSetFileName())); lines.add("* [" + rule.getName() + "](" + link + "#" + rule.getName().toLowerCase(Locale.ROOT) + "): " + DEPRECATION_LABEL_SMALL + "The rule has been moved to another ruleset. Use instead " @@ -189,6 +229,58 @@ private void generateLanguageIndex(Map> rulesets) throws lines.add(""); } + List additionalRulesetsForLanguage = sortedAdditionalRulesets.get(entry.getKey()); + if (additionalRulesetsForLanguage != null) { + lines.add("## Additional rulesets"); + lines.add(""); + + for (RuleSet ruleset : additionalRulesetsForLanguage) { + boolean deprecated = RuleSetUtils.isRuleSetDeprecated(ruleset); + + String rulesetName = ruleset.getName() + " (`" + RuleSetUtils.getRuleSetClasspath(ruleset) + "`)"; + + if (!deprecated) { + lines.add("* " + rulesetName + ":"); + lines.add(""); + lines.add(" " + getRuleSetDescriptionSingleLine(ruleset)); + lines.add(""); + } else { + lines.add("* " + rulesetName + ":"); + lines.add(""); + lines.add(" " + DEPRECATION_LABEL_SMALL + " This ruleset is for backwards compatibility."); + lines.add(""); + } + + lines.add(" It contains the following rules:"); + lines.add(""); + StringBuilder rules = new StringBuilder(); + for (Rule rule : getSortedRules(ruleset)) { + if (rules.length() == 0) { + rules.append(" "); + } else { + rules.append(", "); + } + + Rule resolvedRule = RuleSetUtils.resolveRuleReferences(rule); + if (resolvedRule instanceof RuleReference) { + // Note: deprecated rulesets contain by definition only rule references + RuleReference ref = (RuleReference) resolvedRule; + String otherLink = RULESET_INDEX_PERMALINK_PATTERN + .replace("${language.tersename}", languageTersename) + .replace("${ruleset.name}", RuleSetUtils.getRuleSetFilename(ref.getRuleSetReference().getRuleSetFileName())); + + rules.append("[").append(ref.getName()).append("]("); + rules.append(otherLink).append("#").append(ref.getRule().getName().toLowerCase(Locale.ROOT)).append(")"); + } else { + rules.append(rule.getName()); + } + } + lines.add(rules.toString()); + lines.add(""); + } + lines.add(""); + } + System.out.println("Generated " + path); writer.write(path, lines); } @@ -207,20 +299,6 @@ private static String getShortRuleDescription(Rule rule) { .replaceAll("`", "'") .replaceAll("\\*", "")), 100); } - - /** - * Gets the sanitized base name of the ruleset. - * For some reason, the filename might contain some newlines, which are removed. - * @param ruleset - * @return - */ - private static String getRuleSetFilename(RuleSet ruleset) { - return getRuleSetFilename(ruleset.getFileName()); - } - - private static String getRuleSetFilename(String rulesetFileName) { - return FilenameUtils.getBaseName(StringUtils.chomp(rulesetFileName)); - } private static String getRuleSetDescriptionSingleLine(RuleSet ruleset) { String description = ruleset.getDescription(); @@ -229,6 +307,10 @@ private static String getRuleSetDescriptionSingleLine(RuleSet ruleset) { return description; } + private static List toLines(String s) { + return Arrays.asList(s.split("\r\n|\n")); + } + /** * Generates for each ruleset a page. The page contains the details for each rule. * @@ -237,9 +319,11 @@ private static String getRuleSetDescriptionSingleLine(RuleSet ruleset) { */ private void generateRuleSetIndex(Map> rulesets) throws IOException { for (Map.Entry> entry : rulesets.entrySet()) { - String languageTersename = entry.getKey().getTerseName(); + Language language = entry.getKey(); + String languageTersename = language.getTerseName(); + String languageName = language.getName(); for (RuleSet ruleset : entry.getValue()) { - String rulesetFilename = getRuleSetFilename(ruleset); + String rulesetFilename = RuleSetUtils.getRuleSetFilename(ruleset); String filename = RULESET_INDEX_FILENAME_PATTERN .replace("${language.tersename}", languageTersename) .replace("${ruleset.name}", rulesetFilename); @@ -259,6 +343,7 @@ private void generateRuleSetIndex(Map> rulesets) throws lines.add("sidebaractiveurl: /" + LANGUAGE_INDEX_PERMALINK_PATTERN.replace("${language.tersename}", languageTersename)); lines.add("editmepath: ../" + getRuleSetSourceFilepath(ruleset)); lines.add("keywords: " + getRuleSetKeywords(ruleset)); + lines.add("language: " + languageName); lines.add("---"); for (Rule rule : getSortedRules(ruleset)) { @@ -278,7 +363,7 @@ private void generateRuleSetIndex(Map> rulesets) throws // rule moved to another ruleset String otherLink = RULESET_INDEX_PERMALINK_PATTERN .replace("${language.tersename}", languageTersename) - .replace("${ruleset.name}", getRuleSetFilename(ref.getRuleSetReference().getRuleSetFileName())); + .replace("${ruleset.name}", RuleSetUtils.getRuleSetFilename(ref.getRuleSetReference().getRuleSetFileName())); lines.add(DEPRECATION_LABEL); lines.add(""); lines.add("The rule has been moved to another ruleset. Use instead: [" @@ -304,28 +389,28 @@ private void generateRuleSetIndex(Map> rulesets) throws lines.add(""); } - lines.add(StringUtils.stripToEmpty(rule.getDescription())); + lines.addAll(toLines(stripIndentation(rule.getDescription()))); lines.add(""); if (rule instanceof XPathRule || rule instanceof RuleReference && ((RuleReference) rule).getRule() instanceof XPathRule) { + lines.add("**This rule is defined by the following XPath expression:**"); + lines.add("``` xpath"); + lines.addAll(toLines(StringUtils.stripToEmpty(rule.getProperty(XPathRule.XPATH_DESCRIPTOR)))); lines.add("```"); - lines.add(StringUtils.stripToEmpty(rule.getProperty(XPathRule.XPATH_DESCRIPTOR))); - lines.add("```"); - lines.add(""); } else { lines.add("**This rule is defined by the following Java class:** " + "[" + rule.getRuleClass() + "](" + GITHUB_SOURCE_LINK + getRuleClassSourceFilepath(rule.getRuleClass()) + ")"); - lines.add(""); } + lines.add(""); if (!rule.getExamples().isEmpty()) { lines.add("**Example(s):**"); lines.add(""); for (String example : rule.getExamples()) { lines.add("``` " + mapLanguageForHighlighting(languageTersename)); - lines.add(StringUtils.stripToEmpty(example)); + lines.addAll(toLines(StringUtils.stripToEmpty(example))); lines.add("```"); lines.add(""); } @@ -341,12 +426,52 @@ private void generateRuleSetIndex(Map> rulesets) throws if (!properties.isEmpty()) { lines.add("**This rule has the following properties:**"); lines.add(""); - lines.add("|Name|Default Value|Description|"); - lines.add("|----|-------------|-----------|"); + lines.add("|Name|Default Value|Description|Multivalued|"); + lines.add("|----|-------------|-----------|-----------|"); for (PropertyDescriptor propertyDescriptor : properties) { + String description = propertyDescriptor.description(); + if (description != null && description.toLowerCase(Locale.ROOT).startsWith(DEPRECATED_RULE_PROPERTY_MARKER)) { + description = DEPRECATION_LABEL_SMALL + + description.substring(DEPRECATED_RULE_PROPERTY_MARKER.length()); + } + + String defaultValue = ""; + if (propertyDescriptor.defaultValue() != null) { + if (propertyDescriptor.isMultiValue()) { + @SuppressWarnings("unchecked") // multi valued properties are using a List + MultiValuePropertyDescriptor> multiPropertyDescriptor = (MultiValuePropertyDescriptor>) propertyDescriptor; + defaultValue = multiPropertyDescriptor.asDelimitedString(multiPropertyDescriptor.defaultValue()); + + // surround the delimiter with spaces, so that the browser can wrap + // the value nicely + defaultValue = defaultValue.replaceAll(Pattern.quote( + String.valueOf(multiPropertyDescriptor.multiValueDelimiter())), + " " + multiPropertyDescriptor.multiValueDelimiter() + " "); + } else { + defaultValue = String.valueOf(propertyDescriptor.defaultValue()); + } + } + + defaultValue = defaultValue.replace("\\", "\\\\") + .replace("*", "\\*") + .replace("_", "\\_") + .replace("~", "\\~") + .replace("[", "\\[") + .replace("]", "\\]") + .replace("|", "\\|"); + + String multiValued = "no"; + if (propertyDescriptor.isMultiValue()) { + MultiValuePropertyDescriptor multiValuePropertyDescriptor = + (MultiValuePropertyDescriptor) propertyDescriptor; + multiValued = "yes. Delimiter is '" + + multiValuePropertyDescriptor.multiValueDelimiter() + "'."; + } + lines.add("|" + propertyDescriptor.name() - + "|" + (propertyDescriptor.defaultValue() != null ? String.valueOf(propertyDescriptor.defaultValue()) : "") - + "|" + propertyDescriptor.description() + + "|" + defaultValue + + "|" + description + + "|" + multiValued.replace("|", "\\|") + "|"); } lines.add(""); @@ -354,7 +479,7 @@ private void generateRuleSetIndex(Map> rulesets) throws lines.add("**Use this rule by referencing it:**"); lines.add("``` xml"); - lines.add(""); + lines.add(""); lines.add("```"); lines.add(""); } @@ -365,6 +490,38 @@ private void generateRuleSetIndex(Map> rulesets) throws } } + private static String stripIndentation(String description) { + if (description == null || description.isEmpty()) { + return ""; + } + + String stripped = StringUtils.stripStart(description, "\n\r"); + stripped = StringUtils.stripEnd(stripped, "\n\r "); + + int indentation = 0; + int strLen = stripped.length(); + while (Character.isWhitespace(stripped.charAt(indentation)) && indentation < strLen) { + indentation++; + } + + String[] lines = stripped.split("\\n"); + String prefix = StringUtils.repeat(' ', indentation); + StringBuilder result = new StringBuilder(stripped.length()); + + if (StringUtils.isNotEmpty(prefix)) { + for (int i = 0; i < lines.length; i++) { + String line = lines[i]; + if (i > 0) { + result.append(StringUtils.LF); + } + result.append(StringUtils.removeStart(line, prefix)); + } + } else { + result.append(stripped); + } + return result.toString(); + } + /** * Simply maps PMD languages to rouge languages * @@ -425,14 +582,17 @@ public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IO if (!foundPathResult.isEmpty()) { Path foundPath = foundPathResult.get(0); foundPath = root.relativize(foundPath); - return foundPath.toString(); + // Note: the path is normalized to unix path separators, so that the editme link + // uses forward slashes + return FilenameUtils.normalize(foundPath.toString(), true); } return StringUtils.chomp(ruleset.getFileName()); } private String getRuleClassSourceFilepath(String ruleClass) throws IOException { - final String relativeSourceFilename = ruleClass.replaceAll("\\.", File.separator) + ".java"; + final String relativeSourceFilename = ruleClass.replaceAll("\\.", Matcher.quoteReplacement(File.separator)) + + ".java"; final List foundPathResult = new LinkedList<>(); Files.walkFileTree(root, new SimpleFileVisitor() { diff --git a/pmd-doc/src/main/java/net/sourceforge/pmd/docs/RuleSetUtils.java b/pmd-doc/src/main/java/net/sourceforge/pmd/docs/RuleSetUtils.java new file mode 100644 index 00000000000..f928fe1de63 --- /dev/null +++ b/pmd-doc/src/main/java/net/sourceforge/pmd/docs/RuleSetUtils.java @@ -0,0 +1,81 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.docs; + +import org.apache.commons.io.FilenameUtils; +import org.apache.commons.lang3.StringUtils; + +import net.sourceforge.pmd.Rule; +import net.sourceforge.pmd.RuleSet; +import net.sourceforge.pmd.lang.rule.RuleReference; + +public final class RuleSetUtils { + + private RuleSetUtils() { + // Utility class + } + + /** + * Gets the sanitized base name of the ruleset. + * For some reason, the filename might contain some newlines, which are removed. + * @param ruleset + * @return + */ + public static String getRuleSetFilename(RuleSet ruleset) { + return getRuleSetFilename(ruleset.getFileName()); + } + + public static String getRuleSetFilename(String rulesetFileName) { + return FilenameUtils.getBaseName(StringUtils.chomp(rulesetFileName)); + } + + /** + * A ruleset is considered deprecated, if it only contains rule references + * and all rule references are deprecated. + * + * @param ruleset + * @return + */ + public static boolean isRuleSetDeprecated(RuleSet ruleset) { + boolean result = true; + for (Rule rule : ruleset.getRules()) { + if (!(rule instanceof RuleReference) || !rule.isDeprecated()) { + result = false; + break; + } + } + return result; + } + + public static String getRuleSetClasspath(RuleSet ruleset) { + final String RESOURCES_PATH = "/resources/"; + String filename = FilenameUtils.normalize(StringUtils.chomp(ruleset.getFileName()), true); + int startIndex = filename.lastIndexOf(RESOURCES_PATH); + if (startIndex > -1) { + return filename.substring(startIndex + RESOURCES_PATH.length()); + } else { + return filename; + } + } + + /** + * Recursively resolves rule references until the last reference. + * The last reference is returned. + * If the given rule not a reference, the rule is returned. + * + * @param rule + * @return + */ + public static Rule resolveRuleReferences(Rule rule) { + Rule result = rule; + Rule ref = rule; + while (ref instanceof RuleReference) { + // remember the last reference + result = ref; + ref = ((RuleReference) ref).getRule(); + } + return result; + } +} diff --git a/pmd-doc/src/main/java/net/sourceforge/pmd/docs/SidebarGenerator.java b/pmd-doc/src/main/java/net/sourceforge/pmd/docs/SidebarGenerator.java new file mode 100644 index 00000000000..44b158f2a53 --- /dev/null +++ b/pmd-doc/src/main/java/net/sourceforge/pmd/docs/SidebarGenerator.java @@ -0,0 +1,107 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.docs; + +import java.io.IOException; +import java.io.Reader; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +import org.apache.commons.lang3.SystemUtils; +import org.yaml.snakeyaml.DumperOptions; +import org.yaml.snakeyaml.DumperOptions.FlowStyle; +import org.yaml.snakeyaml.DumperOptions.LineBreak; +import org.yaml.snakeyaml.Yaml; + +import net.sourceforge.pmd.RuleSet; +import net.sourceforge.pmd.lang.Language; + +public class SidebarGenerator { + private static final String SIDEBAR_YML = "docs/_data/sidebars/pmd_sidebar.yml"; + + private final FileWriter writer; + private final Path sidebarPath; + + public SidebarGenerator(FileWriter writer, Path basePath) { + this.writer = Objects.requireNonNull(writer, "A file writer must be provided"); + this.sidebarPath = Objects.requireNonNull(basePath, "A base directory must be provided").resolve(SIDEBAR_YML); + } + + @SuppressWarnings("unchecked") + private Map extractRuleReference(Map sidebar) { + List> entries = (List>) sidebar.get("entries"); + Map entry = entries.get(0); + List> folders = (List>) entry.get("folders"); + return folders.get(3); + } + + public void generateSidebar(Map> sortedRulesets) throws IOException { + Map sidebar = loadSidebar(); + Map ruleReference = extractRuleReference(sidebar); + ruleReference.put("folderitems", generateRuleReferenceSection(sortedRulesets)); + writeSidebar(sidebar); + } + + List> generateRuleReferenceSection(Map> sortedRulesets) { + List> newFolderItems = new ArrayList<>(); + for (Map.Entry> entry : sortedRulesets.entrySet()) { + Map newFolderItem = new LinkedHashMap<>(); + newFolderItem.put("title", null); + newFolderItem.put("output", "web, pdf"); + + Map subfolder = new LinkedHashMap<>(); + newFolderItem.put("subfolders", Arrays.asList(subfolder)); + + subfolder.put("title", entry.getKey().getName() + " Rules"); + subfolder.put("output", "web, pdf"); + List> subfolderitems = new ArrayList<>(); + subfolder.put("subfolderitems", subfolderitems); + + Map ruleIndexSubfolderItem = new LinkedHashMap<>(); + ruleIndexSubfolderItem.put("title", "Index"); + ruleIndexSubfolderItem.put("output", "web, pdf"); + ruleIndexSubfolderItem.put("url", "/pmd_rules_" + entry.getKey().getTerseName() + ".html"); + subfolderitems.add(ruleIndexSubfolderItem); + + for (RuleSet ruleset : entry.getValue()) { + Map subfolderitem = new LinkedHashMap<>(); + subfolderitem.put("title", ruleset.getName()); + subfolderitem.put("output", "web, pdf"); + subfolderitem.put("url", "/pmd_rules_" + entry.getKey().getTerseName() + "_" + RuleSetUtils.getRuleSetFilename(ruleset) + ".html"); + subfolderitems.add(subfolderitem); + } + + newFolderItems.add(newFolderItem); + } + return newFolderItems; + } + + public Map loadSidebar() throws IOException { + try (Reader reader = Files.newBufferedReader(sidebarPath, StandardCharsets.UTF_8)) { + Yaml yaml = new Yaml(); + @SuppressWarnings("unchecked") + Map sidebar = (Map) yaml.load(reader); + return sidebar; + } + } + + public void writeSidebar(Map sidebar) throws IOException { + DumperOptions options = new DumperOptions(); + options.setDefaultFlowStyle(FlowStyle.BLOCK); + if (SystemUtils.IS_OS_WINDOWS) { + options.setLineBreak(LineBreak.WIN); + } + Yaml yaml = new Yaml(options); + writer.write(sidebarPath, Arrays.asList(yaml.dump(sidebar))); + System.out.println("Generated " + sidebarPath); + } +} diff --git a/pmd-doc/src/test/java/net/sourceforge/pmd/docs/RuleDocGeneratorTest.java b/pmd-doc/src/test/java/net/sourceforge/pmd/docs/RuleDocGeneratorTest.java index 43e64bb1d60..20288456774 100644 --- a/pmd-doc/src/test/java/net/sourceforge/pmd/docs/RuleDocGeneratorTest.java +++ b/pmd-doc/src/test/java/net/sourceforge/pmd/docs/RuleDocGeneratorTest.java @@ -8,10 +8,16 @@ import static org.junit.Assert.assertTrue; import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.FileVisitResult; import java.nio.file.Files; import java.nio.file.Path; +import java.nio.file.SimpleFileVisitor; +import java.nio.file.attribute.BasicFileAttributes; import java.util.Arrays; +import java.util.List; +import org.apache.commons.io.FilenameUtils; import org.apache.commons.io.IOUtils; import org.junit.After; import org.junit.Before; @@ -32,33 +38,61 @@ public void setup() throws IOException { writer.reset(); root = Files.createTempDirectory("pmd-ruledocgenerator-test"); - Files.createDirectory(root.resolve("docs")); + Files.createDirectories(root.resolve("docs/_data/sidebars")); + List mockedSidebar = Arrays.asList( + "entries:", + "- title: sidebar", + " folders:", + " - title: 1", + " - title: 2", + " - title: 3", + " - title: Rules"); + Files.write(root.resolve("docs/_data/sidebars/pmd_sidebar.yml"), mockedSidebar); } @After public void cleanup() throws IOException { - Files.delete(root.resolve("docs")); - Files.delete(root); + Files.walkFileTree(root, new SimpleFileVisitor() { + @Override + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { + Files.delete(file); + return FileVisitResult.CONTINUE; + } + + @Override + public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException { + Files.delete(dir); + return FileVisitResult.CONTINUE; + } + }); } @Test public void testSingleRuleset() throws RuleSetNotFoundException, IOException { RuleDocGenerator generator = new RuleDocGenerator(writer, root); - + RuleSetFactory rsf = new RuleSetFactory(); RuleSet ruleset = rsf.createRuleSet("rulesets/ruledoctest/sample.xml"); - - generator.generate(Arrays.asList(ruleset).iterator()); - assertEquals(2, writer.getData().size()); + generator.generate(Arrays.asList(ruleset).iterator(), + Arrays.asList( + "rulesets/ruledoctest/sample-deprecated.xml", + "rulesets/ruledoctest/other-ruleset.xml")); + + assertEquals(3, writer.getData().size()); FileEntry languageIndex = writer.getData().get(0); - assertTrue(languageIndex.getFilename().endsWith("docs/pages/pmd/rules/java.md")); - assertEquals(IOUtils.toString(RuleDocGeneratorTest.class.getResourceAsStream("/expected/java.md")), + assertTrue(FilenameUtils.normalize(languageIndex.getFilename(), true).endsWith("docs/pages/pmd/rules/java.md")); + assertEquals(IOUtils.toString(RuleDocGeneratorTest.class.getResourceAsStream("/expected/java.md"), StandardCharsets.UTF_8), languageIndex.getContent()); - + FileEntry ruleSetIndex = writer.getData().get(1); - assertTrue(ruleSetIndex.getFilename().endsWith("docs/pages/pmd/rules/java/sample.md")); - assertEquals(IOUtils.toString(RuleDocGeneratorTest.class.getResourceAsStream("/expected/sample.md")), + assertTrue(FilenameUtils.normalize(ruleSetIndex.getFilename(), true).endsWith("docs/pages/pmd/rules/java/sample.md")); + assertEquals(IOUtils.toString(RuleDocGeneratorTest.class.getResourceAsStream("/expected/sample.md"), StandardCharsets.UTF_8), ruleSetIndex.getContent()); + + FileEntry sidebar = writer.getData().get(2); + assertTrue(FilenameUtils.normalize(sidebar.getFilename(), true).endsWith("docs/_data/sidebars/pmd_sidebar.yml")); + assertEquals(IOUtils.toString(RuleDocGeneratorTest.class.getResourceAsStream("/expected/pmd_sidebar.yml"), StandardCharsets.UTF_8), + sidebar.getContent()); } } diff --git a/pmd-doc/src/test/java/net/sourceforge/pmd/docs/RuleSetResolverTest.java b/pmd-doc/src/test/java/net/sourceforge/pmd/docs/RuleSetResolverTest.java new file mode 100644 index 00000000000..f6fa28da160 --- /dev/null +++ b/pmd-doc/src/test/java/net/sourceforge/pmd/docs/RuleSetResolverTest.java @@ -0,0 +1,58 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.docs; + +import static org.junit.Assert.fail; + +import java.nio.file.FileSystems; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import org.apache.commons.io.FilenameUtils; +import org.junit.Test; + +import net.sourceforge.pmd.RuleSetFactory; +import net.sourceforge.pmd.RuleSetNotFoundException; + +public class RuleSetResolverTest { + + private static List excludedRulesets = new ArrayList<>(); + + static { + excludedRulesets.add(FilenameUtils.normalize("pmd-test/src/main/resources/rulesets/dummy/basic.xml")); + } + + @Test + public void resolveAllRulesets() { + Path basePath = FileSystems.getDefault().getPath(".").resolve("..").toAbsolutePath().normalize(); + List additionalRulesets = GenerateRuleDocsCmd.findAdditionalRulesets(basePath); + + filterRuleSets(additionalRulesets); + + RuleSetFactory ruleSetFactory = new RuleSetFactory(); + for (String filename : additionalRulesets) { + try { + ruleSetFactory.createRuleSet(filename); + } catch (RuntimeException | RuleSetNotFoundException e) { + fail("Couldn't load ruleset " + filename + ": " + e.getMessage()); + } + } + } + + private void filterRuleSets(List additionalRulesets) { + Iterator it = additionalRulesets.iterator(); + while (it.hasNext()) { + String filename = it.next(); + for (String exclusion : excludedRulesets) { + if (filename.endsWith(exclusion)) { + it.remove(); + break; + } + } + } + } +} diff --git a/pmd-doc/src/test/java/net/sourceforge/pmd/docs/SidebarGeneratorTest.java b/pmd-doc/src/test/java/net/sourceforge/pmd/docs/SidebarGeneratorTest.java new file mode 100644 index 00000000000..b3d96b4e190 --- /dev/null +++ b/pmd-doc/src/test/java/net/sourceforge/pmd/docs/SidebarGeneratorTest.java @@ -0,0 +1,60 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.docs; + +import static org.junit.Assert.assertEquals; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.FileSystems; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.apache.commons.io.IOUtils; +import org.apache.commons.lang3.SystemUtils; +import org.junit.Before; +import org.junit.Test; +import org.yaml.snakeyaml.DumperOptions; +import org.yaml.snakeyaml.DumperOptions.FlowStyle; +import org.yaml.snakeyaml.DumperOptions.LineBreak; +import org.yaml.snakeyaml.Yaml; + +import net.sourceforge.pmd.RuleSet; +import net.sourceforge.pmd.RuleSetFactory; +import net.sourceforge.pmd.lang.Language; +import net.sourceforge.pmd.lang.LanguageRegistry; + +public class SidebarGeneratorTest { + private MockedFileWriter writer = new MockedFileWriter(); + + @Before + public void setup() { + writer.reset(); + } + + @Test + public void testSidebar() throws IOException { + Map> rulesets = new HashMap<>(); + RuleSet ruleSet1 = new RuleSetFactory().createNewRuleSet("test", "test", "bestpractices.xml", Collections.emptyList(), Collections.emptyList(), Collections.emptyList()); + RuleSet ruleSet2 = new RuleSetFactory().createNewRuleSet("test2", "test", "codestyle.xml", Collections.emptyList(), Collections.emptyList(), Collections.emptyList()); + rulesets.put(LanguageRegistry.findLanguageByTerseName("java"), Arrays.asList(ruleSet1, ruleSet2)); + rulesets.put(LanguageRegistry.findLanguageByTerseName("ecmascript"), Arrays.asList(ruleSet1)); + + SidebarGenerator generator = new SidebarGenerator(writer, FileSystems.getDefault().getPath("..")); + List> result = generator.generateRuleReferenceSection(rulesets); + + DumperOptions options = new DumperOptions(); + options.setDefaultFlowStyle(FlowStyle.BLOCK); + if (SystemUtils.IS_OS_WINDOWS) { + options.setLineBreak(LineBreak.WIN); + } + String yaml = new Yaml(options).dump(result); + + assertEquals(IOUtils.toString(SidebarGeneratorTest.class.getResourceAsStream("sidebar.yml"), StandardCharsets.UTF_8), yaml); + } +} diff --git a/pmd-doc/src/test/resources/expected/java.md b/pmd-doc/src/test/resources/expected/java.md index 8ddbfc08d7d..5fcdf1f6fc5 100644 --- a/pmd-doc/src/test/resources/expected/java.md +++ b/pmd-doc/src/test/resources/expected/java.md @@ -1,15 +1,36 @@ --- title: Java Rules +tags: [rule_references, java] +summary: Index of all built-in rules available for Java +language_name: Java permalink: pmd_rules_java.html folder: pmd/rules --- -List of rulesets and rules contained in each ruleset. +## Sample -* [Sample](pmd_rules_java_sample.html): Sample ruleset to test rule doc generation. +{% include callout.html content="Sample ruleset to test rule doc generation." %} -## Sample * [DeprecatedSample](pmd_rules_java_sample.html#deprecatedsample): Deprecated Just some description of a deprecated rule. * [JumbledIncrementer](pmd_rules_java_sample.html#jumbledincrementer): Avoid jumbled loop incrementers - its usually a mistake, and is confusing even if intentional. -* [MovedRule](pmd_rules_java_sample.html#movedrule): Deprecated The rule has been moved to another ruleset. Use instead [JumbledIncrementer](pmd_rules_java_basic.html#jumbledincrementer). +* [MovedRule](pmd_rules_java_sample.html#movedrule): Deprecated The rule has been moved to another ruleset. Use instead [JumbledIncrementer](pmd_rules_java_sample2.html#jumbledincrementer). * [OverrideBothEqualsAndHashcode](pmd_rules_java_sample.html#overridebothequalsandhashcode): Override both 'public boolean Object.equals(Object other)', and 'public int Object.hashCode()', o... * [RenamedRule](pmd_rules_java_sample.html#renamedrule): Deprecated The rule has been renamed. Use instead [JumbledIncrementer](pmd_rules_java_sample.html#jumbledincrementer). + +## Additional rulesets + +* Other ruleset (`rulesets/ruledoctest/other-ruleset.xml`): + + Ruleset which serves a specific use case, such as Getting Started. + + It contains the following rules: + + [JumbledIncrementer](pmd_rules_java_sample.html#jumbledincrementer), [OverrideBothEqualsAndHashcode](pmd_rules_java_sample.html#overridebothequalsandhashcode) + +* Sample Deprecated (`rulesets/ruledoctest/sample-deprecated.xml`): + + Deprecated This ruleset is for backwards compatibility. + + It contains the following rules: + + [JumbledIncrementer](pmd_rules_java_sample.html#jumbledincrementer), [OldNameOfJumbledIncrementer](pmd_rules_java_sample.html#jumbledincrementer), [OverrideBothEqualsAndHashcode](pmd_rules_java_sample.html#overridebothequalsandhashcode) + diff --git a/pmd-doc/src/test/resources/expected/pmd_sidebar.yml b/pmd-doc/src/test/resources/expected/pmd_sidebar.yml new file mode 100644 index 00000000000..3a062ae0586 --- /dev/null +++ b/pmd-doc/src/test/resources/expected/pmd_sidebar.yml @@ -0,0 +1,20 @@ +entries: +- title: sidebar + folders: + - title: 1 + - title: 2 + - title: 3 + - title: Rules + folderitems: + - title: null + output: web, pdf + subfolders: + - title: Java Rules + output: web, pdf + subfolderitems: + - title: Index + output: web, pdf + url: /pmd_rules_java.html + - title: Sample + output: web, pdf + url: /pmd_rules_java_sample.html diff --git a/pmd-doc/src/test/resources/expected/sample.md b/pmd-doc/src/test/resources/expected/sample.md index c1119aab3e3..49d2ddccb95 100644 --- a/pmd-doc/src/test/resources/expected/sample.md +++ b/pmd-doc/src/test/resources/expected/sample.md @@ -6,6 +6,7 @@ folder: pmd/rules/java sidebaractiveurl: /pmd_rules_java.html editmepath: ../rulesets/ruledoctest/sample.xml keywords: Sample, OverrideBothEqualsAndHashcode, JumbledIncrementer, DeprecatedSample, RenamedRule, MovedRule +language: Java --- ## DeprecatedSample @@ -17,13 +18,14 @@ keywords: Sample, OverrideBothEqualsAndHashcode, JumbledIncrementer, DeprecatedS Just some description of a deprecated rule. -``` +**This rule is defined by the following XPath expression:** +``` xpath //ForStatement ``` **Use this rule by referencing it:** ``` xml - + ``` ## JumbledIncrementer @@ -34,7 +36,8 @@ Just some description of a deprecated rule. Avoid jumbled loop incrementers - its usually a mistake, and is confusing even if intentional. -``` +**This rule is defined by the following XPath expression:** +``` xpath //ForStatement [ ForUpdate/StatementExpressionList/StatementExpression/PostfixExpression/PrimaryExpression/PrimaryPrefix/Name/@Image @@ -59,20 +62,29 @@ public class JumbledIncrementerRule1 { **This rule has the following properties:** -|Name|Default Value|Description| -|----|-------------|-----------| -|sampleAdditionalProperty|the value|This is a additional property for tests| +|Name|Default Value|Description|Multivalued| +|----|-------------|-----------|-----------| +|sampleAdditionalProperty|the value|This is a additional property for tests|no| +|sampleMultiStringProperty|Value1 \| Value2|Test property with multiple strings|yes. Delimiter is '\|'.| +|sampleDeprecatedProperty|test|Deprecated This is a sample deprecated property for tests|no| +|sampleRegexProperty1|\\/\\\*\\s+(default\|package)\\s+\\\*\\/|The property is of type regex|no| +|sampleRegexProperty2|\[a-z\]\*|The property is of type regex|no| +|sampleRegexProperty3|\\s+|The property is of type regex|no| +|sampleRegexProperty4|\_dd\_|The property is of type regex|no| +|sampleRegexProperty5|\[0-9\]{1,3}|The property is of type regex|no| +|sampleRegexProperty6|\\b|The property is of type regex|no| +|sampleRegexProperty7|\\n|The property is of type regex|no| **Use this rule by referencing it:** ``` xml - + ``` ## MovedRule Deprecated -The rule has been moved to another ruleset. Use instead: [JumbledIncrementer](pmd_rules_java_basic.html#jumbledincrementer) +The rule has been moved to another ruleset. Use instead: [JumbledIncrementer](pmd_rules_java_sample2.html#jumbledincrementer) **Since:** PMD 1.0 @@ -80,7 +92,8 @@ The rule has been moved to another ruleset. Use instead: [JumbledIncrementer](pm Avoid jumbled loop incrementers - its usually a mistake, and is confusing even if intentional. -``` +**This rule is defined by the following XPath expression:** +``` xpath //ForStatement [ ForUpdate/StatementExpressionList/StatementExpression/PostfixExpression/PrimaryExpression/PrimaryPrefix/Name/@Image @@ -105,7 +118,7 @@ public class JumbledIncrementerRule1 { **Use this rule by referencing it:** ``` xml - + ``` ## OverrideBothEqualsAndHashcode @@ -120,7 +133,13 @@ Override both `public boolean Object.equals(Object other)`, and `public int Obje Even if you are inheriting a `hashCode()` from a parent class, consider implementing hashCode and explicitly delegating to your superclass. -**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.basic.OverrideBothEqualsAndHashcodeRule](https://github.com/pmd/pmd/blob/master/net/sourceforge/pmd/lang/java/rule/basic/OverrideBothEqualsAndHashcodeRule.java) +Second paragraph. + + Code sample + +Third paragraph. + +**This rule is defined by the following Java class:** [net.sourceforge.pmd.lang.java.rule.errorprone.OverrideBothEqualsAndHashcodeRule](https://github.com/pmd/pmd/blob/master/net/sourceforge/pmd/lang/java/rule/errorprone/OverrideBothEqualsAndHashcodeRule.java) **Example(s):** @@ -149,7 +168,7 @@ public class Foo { // perfect, both methods provided **Use this rule by referencing it:** ``` xml - + ``` ## RenamedRule @@ -164,7 +183,8 @@ This rule has been renamed. Use instead: [JumbledIncrementer](#jumbledincremente Avoid jumbled loop incrementers - its usually a mistake, and is confusing even if intentional. -``` +**This rule is defined by the following XPath expression:** +``` xpath //ForStatement [ ForUpdate/StatementExpressionList/StatementExpression/PostfixExpression/PrimaryExpression/PrimaryPrefix/Name/@Image @@ -189,11 +209,20 @@ public class JumbledIncrementerRule1 { **This rule has the following properties:** -|Name|Default Value|Description| -|----|-------------|-----------| -|sampleAdditionalProperty|the value|This is a additional property for tests| +|Name|Default Value|Description|Multivalued| +|----|-------------|-----------|-----------| +|sampleAdditionalProperty|the value|This is a additional property for tests|no| +|sampleMultiStringProperty|Value1 \| Value2|Test property with multiple strings|yes. Delimiter is '\|'.| +|sampleDeprecatedProperty|test|Deprecated This is a sample deprecated property for tests|no| +|sampleRegexProperty1|\\/\\\*\\s+(default\|package)\\s+\\\*\\/|The property is of type regex|no| +|sampleRegexProperty2|\[a-z\]\*|The property is of type regex|no| +|sampleRegexProperty3|\\s+|The property is of type regex|no| +|sampleRegexProperty4|\_dd\_|The property is of type regex|no| +|sampleRegexProperty5|\[0-9\]{1,3}|The property is of type regex|no| +|sampleRegexProperty6|\\b|The property is of type regex|no| +|sampleRegexProperty7|\\n|The property is of type regex|no| **Use this rule by referencing it:** ``` xml - + ``` diff --git a/pmd-doc/src/test/resources/net/sourceforge/pmd/docs/sidebar.yml b/pmd-doc/src/test/resources/net/sourceforge/pmd/docs/sidebar.yml new file mode 100644 index 00000000000..39d149abebd --- /dev/null +++ b/pmd-doc/src/test/resources/net/sourceforge/pmd/docs/sidebar.yml @@ -0,0 +1,27 @@ +- title: null + output: web, pdf + subfolders: + - title: Java Rules + output: web, pdf + subfolderitems: + - title: Index + output: web, pdf + url: /pmd_rules_java.html + - title: test + output: web, pdf + url: /pmd_rules_java_bestpractices.html + - title: test2 + output: web, pdf + url: /pmd_rules_java_codestyle.html +- title: null + output: web, pdf + subfolders: + - title: Ecmascript Rules + output: web, pdf + subfolderitems: + - title: Index + output: web, pdf + url: /pmd_rules_ecmascript.html + - title: test + output: web, pdf + url: /pmd_rules_ecmascript_bestpractices.html diff --git a/pmd-doc/src/test/resources/rulesets/ruledoctest/other-ruleset.xml b/pmd-doc/src/test/resources/rulesets/ruledoctest/other-ruleset.xml new file mode 100644 index 00000000000..9b278ee3cd1 --- /dev/null +++ b/pmd-doc/src/test/resources/rulesets/ruledoctest/other-ruleset.xml @@ -0,0 +1,14 @@ + + + + +Ruleset which serves a specific use case, such as Getting Started. + + + + + + diff --git a/pmd-doc/src/test/resources/rulesets/ruledoctest/sample-deprecated.xml b/pmd-doc/src/test/resources/rulesets/ruledoctest/sample-deprecated.xml new file mode 100644 index 00000000000..f9832d1428f --- /dev/null +++ b/pmd-doc/src/test/resources/rulesets/ruledoctest/sample-deprecated.xml @@ -0,0 +1,17 @@ + + + + +Sample ruleset which only contains deprecated rule references. + + + + + + + + + diff --git a/pmd-doc/src/test/resources/rulesets/ruledoctest/sample.xml b/pmd-doc/src/test/resources/rulesets/ruledoctest/sample.xml index 2f3b5c876c3..ec8a02c9b73 100644 --- a/pmd-doc/src/test/resources/rulesets/ruledoctest/sample.xml +++ b/pmd-doc/src/test/resources/rulesets/ruledoctest/sample.xml @@ -3,7 +3,7 @@ + xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd"> Sample ruleset to test rule doc generation. @@ -12,13 +12,19 @@ Sample ruleset to test rule doc generation. language="java" since="0.4" message="Ensure you override both equals() and hashCode()" - class="net.sourceforge.pmd.lang.java.rule.basic.OverrideBothEqualsAndHashcodeRule" + class="net.sourceforge.pmd.lang.java.rule.errorprone.OverrideBothEqualsAndHashcodeRule" externalInfoUrl="${pmd.website.baseurl}/pmd_rules_java_sample.html#overridebothequalsandhashcode" minimumLanguageVersion="1.5"> -Override both `public boolean Object.equals(Object other)`, and `public int Object.hashCode()`, or override neither. -Even if you are inheriting a `hashCode()` from a parent class, consider implementing hashCode and explicitly -delegating to your superclass. + Override both `public boolean Object.equals(Object other)`, and `public int Object.hashCode()`, or override neither. + Even if you are inheriting a `hashCode()` from a parent class, consider implementing hashCode and explicitly + delegating to your superclass. + + Second paragraph. + + Code sample + + Third paragraph. 3 @@ -71,6 +77,15 @@ Avoid jumbled loop incrementers - its usually a mistake, and is confusing even i + + + + + + + + + - + diff --git a/pmd-doc/src/test/resources/rulesets/ruledoctest/sample2.xml b/pmd-doc/src/test/resources/rulesets/ruledoctest/sample2.xml new file mode 100644 index 00000000000..88f5e15c02d --- /dev/null +++ b/pmd-doc/src/test/resources/rulesets/ruledoctest/sample2.xml @@ -0,0 +1,50 @@ + + + + +Sample ruleset to test rule doc generation. + + + + +Avoid jumbled loop incrementers - its usually a mistake, and is confusing even if intentional. + + 3 + + + + + + + + + + + + + diff --git a/pmd-fortran/pom.xml b/pmd-fortran/pom.xml index 012cc06fb4a..7bb53ef1f5f 100644 --- a/pmd-fortran/pom.xml +++ b/pmd-fortran/pom.xml @@ -7,13 +7,9 @@ net.sourceforge.pmd pmd - 6.0.0-SNAPSHOT + 6.10.0-SNAPSHOT - - ${basedir}/../pmd-core - - @@ -32,7 +28,15 @@ net.sourceforge.pmd pmd-core + + commons-io + commons-io + + + junit + junit + net.sourceforge.pmd pmd-test diff --git a/pmd-go/pom.xml b/pmd-go/pom.xml index 47a7c9d06be..b75d7784554 100644 --- a/pmd-go/pom.xml +++ b/pmd-go/pom.xml @@ -7,15 +7,15 @@ net.sourceforge.pmd pmd - 6.0.0-SNAPSHOT + 6.10.0-SNAPSHOT - - ${basedir}/../pmd-core - - + + org.antlr + antlr4-maven-plugin + maven-resources-plugin @@ -28,6 +28,10 @@ + + org.antlr + antlr4-runtime + net.sourceforge.pmd pmd-core diff --git a/pmd-go/src/main/antlr4/net/sourceforge/pmd/lang/go/antlr4/Golang.g4 b/pmd-go/src/main/antlr4/net/sourceforge/pmd/lang/go/antlr4/Golang.g4 new file mode 100644 index 00000000000..ec54cbac43e --- /dev/null +++ b/pmd-go/src/main/antlr4/net/sourceforge/pmd/lang/go/antlr4/Golang.g4 @@ -0,0 +1,1317 @@ +/* + [The "BSD licence"] + Copyright (c) 2017 Sasa Coh, Michał Błotniak + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + 3. The name of the author may not be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ +/** + * A Go grammar for ANTLR 4 derived from the Go Language Specification + * https://golang.org/ref/spec + * + */ +grammar Golang; + +@parser::members { + + /** + * Returns {@code true} iff on the current index of the parser's + * token stream a token exists on the {@code HIDDEN} channel which + * either is a line terminator, or is a multi line comment that + * contains a line terminator. + * + * @return {@code true} iff on the current index of the parser's + * token stream a token exists on the {@code HIDDEN} channel which + * either is a line terminator, or is a multi line comment that + * contains a line terminator. + */ + private boolean lineTerminatorAhead() { + // Get the token ahead of the current index. + int possibleIndexEosToken = this.getCurrentToken().getTokenIndex() - 1; + Token ahead = _input.get(possibleIndexEosToken); + if (ahead.getChannel() != Lexer.HIDDEN) { + // We're only interested in tokens on the HIDDEN channel. + return false; + } + + if (ahead.getType() == TERMINATOR) { + // There is definitely a line terminator ahead. + return true; + } + + if (ahead.getType() == WS) { + // Get the token ahead of the current whitespaces. + possibleIndexEosToken = this.getCurrentToken().getTokenIndex() - 2; + ahead = _input.get(possibleIndexEosToken); + } + + // Get the token's text and type. + String text = ahead.getText(); + int type = ahead.getType(); + + // Check if the token is, or contains a line terminator. + return (type == COMMENT && (text.contains("\r") || text.contains("\n"))) || + (type == TERMINATOR); + } + + /** + * Returns {@code true} if no line terminator exists between the specified + * token offset and the prior one on the {@code HIDDEN} channel. + * + * @return {@code true} if no line terminator exists between the specified + * token offset and the prior one on the {@code HIDDEN} channel. + */ + private boolean noTerminatorBetween(int tokenOffset) { + BufferedTokenStream stream = (BufferedTokenStream)_input; + List tokens = stream.getHiddenTokensToLeft(stream.LT(tokenOffset).getTokenIndex()); + + if (tokens == null) { + return true; + } + + for (Token token : tokens) { + if (token.getText().contains("\n")) + return false; + } + + return true; + } + + /** + * Returns {@code true} if no line terminator exists after any encounterd + * parameters beyond the specified token offset and the next on the + * {@code HIDDEN} channel. + * + * @return {@code true} if no line terminator exists after any encounterd + * parameters beyond the specified token offset and the next on the + * {@code HIDDEN} channel. + */ + private boolean noTerminatorAfterParams(int tokenOffset) { + BufferedTokenStream stream = (BufferedTokenStream)_input; + int leftParams = 1; + int rightParams = 0; + String value; + + if (stream.LT(tokenOffset).getText().equals("(")) { + // Scan past parameters + while (leftParams != rightParams) { + tokenOffset++; + value = stream.LT(tokenOffset).getText(); + + if (value.equals("(")) { + leftParams++; + } + else if (value.equals(")")) { + rightParams++; + } + } + + tokenOffset++; + return noTerminatorBetween(tokenOffset); + } + + return true; + } +} + +@lexer::members { + + // The most recently produced token. + private Token lastToken = null; + + /** + * Return the next token from the character stream and records this last + * token in case it resides on the default channel. This recorded token + * is used to determine when the lexer could possibly match a regex + * literal. + * + * @return the next token from the character stream. + */ + @Override + public Token nextToken() { + + // Get the next token. + Token next = super.nextToken(); + + if (next.getChannel() == Token.DEFAULT_CHANNEL) { + // Keep track of the last token on the default channel. + this.lastToken = next; + } + + return next; + } +} + +//SourceFile = PackageClause ";" { ImportDecl ";" } { TopLevelDecl ";" } . +sourceFile + : packageClause eos ( importDecl eos )* ( topLevelDecl eos )* + ; + +//PackageClause = "package" PackageName . +//PackageName = identifier . +packageClause + : 'package' IDENTIFIER + ; + +importDecl + : 'import' ( importSpec | '(' ( importSpec eos )* ')' ) + ; + +importSpec + : ( '.' | IDENTIFIER )? importPath + ; + +importPath + : STRING_LIT + ; + +//TopLevelDecl = Declaration | FunctionDecl | MethodDecl . +topLevelDecl + : declaration + | functionDecl + | methodDecl + ; + +//Declaration = ConstDecl | TypeDecl | VarDecl . +declaration + : constDecl + | typeDecl + | varDecl + ; + + +//ConstDecl = "const" ( ConstSpec | "(" { ConstSpec ";" } ")" ) . +constDecl + : 'const' ( constSpec | '(' ( constSpec eos )* ')' ) + ; + +//ConstSpec = IdentifierList [ [ Type ] "=" ExpressionList ] . +constSpec + : identifierList ( type? '=' expressionList )? + ; + +// +//IdentifierList = identifier { "," identifier } . +identifierList + : IDENTIFIER ( ',' IDENTIFIER )* + ; + +//ExpressionList = Expression { "," Expression } . +expressionList + : expression ( ',' expression )* + ; + +//TypeDecl = "type" ( TypeSpec | "(" { TypeSpec ";" } ")" ) . +typeDecl + : 'type' ( typeSpec | '(' ( typeSpec eos )* ')' ) + ; + +//TypeSpec = identifier Type . +typeSpec + : IDENTIFIER type + ; + + +// Function declarations + +//FunctionDecl = "func" FunctionName ( Function | Signature ) . +//FunctionName = identifier . +//Function = Signature FunctionBody . +//FunctionBody = Block . +functionDecl + : 'func' IDENTIFIER ( function | signature ) + ; + +function + : signature block + ; + +//MethodDecl = "func" Receiver MethodName ( Function | Signature ) . +//Receiver = Parameters . +methodDecl + : 'func' receiver IDENTIFIER ( function | signature ) + ; + +receiver + : parameters + ; + +//VarDecl = "var" ( VarSpec | "(" { VarSpec ";" } ")" ) . +//VarSpec = IdentifierList ( Type [ "=" ExpressionList ] | "=" ExpressionList ) . +varDecl + : 'var' ( varSpec | '(' ( varSpec eos )* ')' ) + ; + +varSpec + : identifierList ( type ( '=' expressionList )? | '=' expressionList ) + ; + + +//Block = "{" StatementList "}" . +block + : '{' statementList '}' + ; + +//StatementList = { Statement ";" } . +statementList + : ( statement eos )* + ; + +statement + : declaration + | labeledStmt + | simpleStmt + | goStmt + | returnStmt + | breakStmt + | continueStmt + | gotoStmt + | fallthroughStmt + | block + | ifStmt + | switchStmt + | selectStmt + | forStmt + | deferStmt + ; + +//SimpleStmt = EmptyStmt | ExpressionStmt | SendStmt | IncDecStmt | Assignment | ShortVarDecl . +simpleStmt + : sendStmt + | expressionStmt + | incDecStmt + | assignment + | shortVarDecl + | emptyStmt + ; + +//ExpressionStmt = Expression . +expressionStmt + : expression + ; + +//SendStmt = Channel "<-" Expression . +//Channel = Expression . +sendStmt + : expression '<-' expression + ; + +//IncDecStmt = Expression ( "++" | "--" ) . +incDecStmt + : expression ( '++' | '--' ) + ; + +//Assignment = ExpressionList assign_op ExpressionList . +assignment + : expressionList assign_op expressionList + ; + +//assign_op = [ add_op | mul_op ] "=" . +assign_op + : ('+' | '-' | '|' | '^' | '*' | '/' | '%' | '<<' | '>>' | '&' | '&^')? '=' + ; + + +//ShortVarDecl = IdentifierList ":=" ExpressionList . +shortVarDecl + : identifierList ':=' expressionList + ; + +emptyStmt + : ';' + ; + +//LabeledStmt = Label ":" Statement . +//Label = identifier . +labeledStmt + : IDENTIFIER ':' statement + ; + +//ReturnStmt = "return" [ ExpressionList ] . +returnStmt + : 'return' expressionList? + ; + +//BreakStmt = "break" [ Label ] . +breakStmt + : 'break' IDENTIFIER? + ; + +//ContinueStmt = "continue" [ Label ] . +continueStmt + : 'continue' IDENTIFIER? + ; + +//GotoStmt = "goto" Label . +gotoStmt + : 'goto' IDENTIFIER + ; + +//FallthroughStmt = "fallthrough" . +fallthroughStmt + : 'fallthrough' + ; + +//DeferStmt = "defer" Expression . +deferStmt + : 'defer' expression + ; + +//IfStmt = "if" [ SimpleStmt ";" ] Expression Block [ "else" ( IfStmt | Block ) ] . +ifStmt + : 'if' (simpleStmt ';')? expression block ( 'else' ( ifStmt | block ) )? + ; + +//SwitchStmt = ExprSwitchStmt | TypeSwitchStmt . +switchStmt + : exprSwitchStmt | typeSwitchStmt + ; + +//ExprSwitchStmt = "switch" [ SimpleStmt ";" ] [ Expression ] "{" { ExprCaseClause } "}" . +//ExprCaseClause = ExprSwitchCase ":" StatementList . +//ExprSwitchCase = "case" ExpressionList | "default" . +exprSwitchStmt + : 'switch' ( simpleStmt ';' )? expression? '{' exprCaseClause* '}' + ; + +exprCaseClause + : exprSwitchCase ':' statementList + ; + +exprSwitchCase + : 'case' expressionList | 'default' + ; + +//TypeSwitchStmt = "switch" [ SimpleStmt ";" ] TypeSwitchGuard "{" { TypeCaseClause } "}" . +//TypeSwitchGuard = [ identifier ":=" ] PrimaryExpr "." "(" "type" ")" . +//TypeCaseClause = TypeSwitchCase ":" StatementList . +//TypeSwitchCase = "case" TypeList | "default" . +//TypeList = Type { "," Type } . +typeSwitchStmt + : 'switch' ( simpleStmt ';' )? typeSwitchGuard '{' typeCaseClause* '}' + ; +typeSwitchGuard + : ( IDENTIFIER ':=' )? primaryExpr '.' '(' 'type' ')' + ; +typeCaseClause + : typeSwitchCase ':' statementList + ; +typeSwitchCase + : 'case' typeList | 'default' + ; +typeList + : type ( ',' type )* + ; + + +//SelectStmt = "select" "{" { CommClause } "}" . +//CommClause = CommCase ":" StatementList . +//CommCase = "case" ( SendStmt | RecvStmt ) | "default" . +//RecvStmt = [ ExpressionList "=" | IdentifierList ":=" ] RecvExpr . +//RecvExpr = Expression . +selectStmt + : 'select' '{' commClause* '}' + ; +commClause + : commCase ':' statementList + ; +commCase + : 'case' ( sendStmt | recvStmt ) | 'default' + ; +recvStmt + : ( expressionList '=' | identifierList ':=' )? expression + ; + +//ForStmt = "for" [ Condition | ForClause | RangeClause ] Block . +//Condition = Expression . +forStmt + : 'for' ( expression | forClause | rangeClause )? block + ; + +//ForClause = [ InitStmt ] ";" [ Condition ] ";" [ PostStmt ] . +//InitStmt = SimpleStmt . +//PostStmt = SimpleStmt . +forClause + : simpleStmt? ';' expression? ';' simpleStmt? + ; + + +//RangeClause = [ ExpressionList "=" | IdentifierList ":=" ] "range" Expression . +rangeClause + : (expressionList '=' | identifierList ':=' )? 'range' expression + ; + +//GoStmt = "go" Expression . +goStmt + : 'go' expression + ; + +//Type = TypeName | TypeLit | "(" Type ")" . +type + : typeName + | typeLit + | '(' type ')' + ; + +//TypeName = identifier | QualifiedIdent . +typeName + : IDENTIFIER + | qualifiedIdent + ; + +//TypeLit = ArrayType | StructType | PointerType | FunctionType | InterfaceType | +// SliceType | MapType | ChannelType . +typeLit + : arrayType + | structType + | pointerType + | functionType + | interfaceType + | sliceType + | mapType + | channelType + ; + + +arrayType + : '[' arrayLength ']' elementType + ; + +arrayLength + : expression + ; + +elementType + : type + ; + +//PointerType = "*" BaseType . +//BaseType = Type . +pointerType + : '*' type + ; + +//InterfaceType = "interface" "{" { MethodSpec ";" } "}" . +//MethodSpec = MethodName Signature | InterfaceTypeName . +//MethodName = identifier . +//InterfaceTypeName = TypeName . +interfaceType + : 'interface' '{' ( methodSpec eos )* '}' + ; + +//SliceType = "[" "]" ElementType . +sliceType + : '[' ']' elementType + ; + +//MapType = "map" "[" KeyType "]" ElementType . +//KeyType = Type . +mapType + : 'map' '[' type ']' elementType + ; + +//ChannelType = ( "chan" | "chan" "<-" | "<-" "chan" ) ElementType . +channelType + : ( 'chan' | 'chan' '<-' | '<-' 'chan' ) elementType + ; + +methodSpec + : {noTerminatorAfterParams(2)}? IDENTIFIER parameters result + | typeName + | IDENTIFIER parameters + ; + + +//FunctionType = "func" Signature . +//Signature = Parameters [ Result ] . +//Result = Parameters | Type . +//Parameters = "(" [ ParameterList [ "," ] ] ")" . +//ParameterList = ParameterDecl { "," ParameterDecl } . +//ParameterDecl = [ IdentifierList ] [ "..." ] Type . +functionType + : 'func' signature + ; + +signature + : {noTerminatorAfterParams(1)}? parameters result + | parameters + ; + +result + : parameters + | type + ; + +parameters + : '(' ( parameterList ','? )? ')' + ; + +parameterList + : parameterDecl ( ',' parameterDecl )* + ; + +parameterDecl + : identifierList? '...'? type + ; + +///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Operands + +//Operand = Literal | OperandName | MethodExpr | "(" Expression ")" . +//Literal = BasicLit | CompositeLit | FunctionLit . +//BasicLit = int_lit | float_lit | imaginary_lit | rune_lit | string_lit . +//OperandName = identifier | QualifiedIdent. + +operand + : literal + | operandName + | methodExpr + | '(' expression ')' + ; + +literal + : basicLit + | compositeLit + | functionLit + ; + +basicLit + : INT_LIT + | FLOAT_LIT + | IMAGINARY_LIT + | RUNE_LIT + | STRING_LIT + ; + +operandName + : IDENTIFIER + | qualifiedIdent + ; + +//QualifiedIdent = PackageName "." identifier . +qualifiedIdent + : IDENTIFIER '.' IDENTIFIER + ; + +//CompositeLit = LiteralType LiteralValue . +//LiteralType = StructType | ArrayType | "[" "..." "]" ElementType | +// SliceType | MapType | TypeName . +//LiteralValue = "{" [ ElementList [ "," ] ] "}" . +//ElementList = KeyedElement { "," KeyedElement } . +//KeyedElement = [ Key ":" ] Element . +//Key = FieldName | Expression | LiteralValue . +//FieldName = identifier . +//Element = Expression | LiteralValue . + +compositeLit + : literalType literalValue + ; + +literalType + : structType + | arrayType + | '[' '...' ']' elementType + | sliceType + | mapType + | typeName + ; + +literalValue + : '{' ( elementList ','? )? '}' + ; + +elementList + : keyedElement (',' keyedElement)* + ; + +keyedElement + : (key ':')? element + ; + +key + : IDENTIFIER + | expression + | literalValue + ; + +element + : expression + | literalValue + ; + +//StructType = "struct" "{" { FieldDecl ";" } "}" . +//FieldDecl = (IdentifierList Type | AnonymousField) [ Tag ] . +//AnonymousField = [ "*" ] TypeName . +//Tag = string_lit . +structType + : 'struct' '{' ( fieldDecl eos )* '}' + ; + +fieldDecl + : ({noTerminatorBetween(2)}? identifierList type | anonymousField) STRING_LIT? + ; + +anonymousField + : '*'? typeName + ; + +//FunctionLit = "func" Function . +functionLit + : 'func' function + ; + +//PrimaryExpr = +// Operand | +// Conversion | +// PrimaryExpr Selector | +// PrimaryExpr Index | +// PrimaryExpr Slice | +// PrimaryExpr TypeAssertion | +// PrimaryExpr Arguments . +// +//Selector = "." identifier . +//Index = "[" Expression "]" . +//Slice = "[" ( [ Expression ] ":" [ Expression ] ) | +// ( [ Expression ] ":" Expression ":" Expression ) +// "]" . +//TypeAssertion = "." "(" Type ")" . +//Arguments = "(" [ ( ExpressionList | Type [ "," ExpressionList ] ) [ "..." ] [ "," ] ] ")" . + +primaryExpr + : operand + | conversion + | primaryExpr selector + | primaryExpr index + | primaryExpr slice + | primaryExpr typeAssertion + | primaryExpr arguments + ; + +selector + : '.' IDENTIFIER + ; + +index + : '[' expression ']' + ; + +slice + : '[' (( expression? ':' expression? ) | ( expression? ':' expression ':' expression )) ']' + ; + +typeAssertion + : '.' '(' type ')' + ; + +arguments + : '(' ( ( expressionList | type ( ',' expressionList )? ) '...'? ','? )? ')' + ; + +//MethodExpr = ReceiverType "." MethodName . +//ReceiverType = TypeName | "(" "*" TypeName ")" | "(" ReceiverType ")" . +methodExpr + : receiverType '.' IDENTIFIER + ; + +receiverType + : typeName + | '(' '*' typeName ')' + | '(' receiverType ')' + ; + +//Expression = UnaryExpr | Expression binary_op Expression . +//UnaryExpr = PrimaryExpr | unary_op UnaryExpr . + +expression + : unaryExpr +// | expression BINARY_OP expression + | expression ('||' | '&&' | '==' | '!=' | '<' | '<=' | '>' | '>=' | '+' | '-' | '|' | '^' | '*' | '/' | '%' | '<<' | '>>' | '&' | '&^') expression + ; + +unaryExpr + : primaryExpr + | ('+'|'-'|'!'|'^'|'*'|'&'|'<-') unaryExpr + ; + +//Conversion = Type "(" Expression [ "," ] ")" . +conversion + : type '(' expression ','? ')' + ; + +eos + : ';' + | EOF + | {lineTerminatorAhead()}? + | {_input.LT(1).getText().equals("}") }? + ; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// LEXER + + +// Identifiers +//identifier = letter { letter | unicode_digit } . +IDENTIFIER + : LETTER ( LETTER | UNICODE_DIGIT )* + ; + +// Keywords +KEYWORD + : 'break' + | 'default' + | 'func' + | 'interface' + | 'select' + | 'case' + | 'defer' + | 'go' + | 'map' + | 'struct' + | 'chan' + | 'else' + | 'goto' + | 'package' + | 'switch' + | 'const' + | 'fallthrough' + | 'if' + | 'range' + | 'type' + | 'continue' + | 'for' + | 'import' + | 'return' + | 'var' + ; + + +// Operators + +//binary_op = "||" | "&&" | rel_op | add_op | mul_op . +BINARY_OP + : '||' | '&&' | REL_OP | ADD_OP | MUL_OP + ; + +//rel_op = "==" | "!=" | "<" | "<=" | ">" | ">=" . +fragment REL_OP + : '==' + | '!=' + | '<' + | '<=' + | '>' + | '>=' + ; + +//add_op = "+" | "-" | "|" | "^" . +fragment ADD_OP + : '+' + | '-' + | '|' + | '^' + ; + +//mul_op = "*" | "/" | "%" | "<<" | ">>" | "&" | "&^" . +fragment MUL_OP + : '*' + | '/' + | '%' + | '<<' + | '>>' + | '&' + | '&^' + ; + +//unary_op = "+" | "-" | "!" | "^" | "*" | "&" | "<-" . +fragment UNARY_OP + : '+' + | '-' + | '!' + | '^' + | '*' + | '&' + | '<-' + ; + + +// Integer literals + +//int_lit = decimal_lit | octal_lit | hex_lit . +INT_LIT + : DECIMAL_LIT + | OCTAL_LIT + | HEX_LIT + ; + +//decimal_lit = ( "1" … "9" ) { decimal_digit } . +fragment DECIMAL_LIT + : [1-9] DECIMAL_DIGIT* + ; + +//octal_lit = "0" { octal_digit } . +fragment OCTAL_LIT + : '0' OCTAL_DIGIT* + ; + +//hex_lit = "0" ( "x" | "X" ) hex_digit { hex_digit } . +fragment HEX_LIT + : '0' ( 'x' | 'X' ) HEX_DIGIT+ + ; + + +// Floating-point literals + +//float_lit = decimals "." [ decimals ] [ exponent ] | +// decimals exponent | +// "." decimals [ exponent ] . +FLOAT_LIT + : DECIMALS '.' DECIMALS? EXPONENT? + | DECIMALS EXPONENT + | '.' DECIMALS EXPONENT? + ; + +//decimals = decimal_digit { decimal_digit } . +fragment DECIMALS + : DECIMAL_DIGIT+ + ; + +//exponent = ( "e" | "E" ) [ "+" | "-" ] decimals . +fragment EXPONENT + : ( 'e' | 'E' ) ( '+' | '-' )? DECIMALS + ; + +// Imaginary literals +//imaginary_lit = (decimals | float_lit) "i" . +IMAGINARY_LIT + : (DECIMALS | FLOAT_LIT) 'i' + ; + + +// Rune literals + +//rune_lit = "'" ( unicode_value | byte_value ) "'" . +RUNE_LIT + : '\'' ( UNICODE_VALUE | BYTE_VALUE ) '\'' + ; + +//unicode_value = unicode_char | little_u_value | big_u_value | escaped_char . +fragment UNICODE_VALUE + : UNICODE_CHAR + | LITTLE_U_VALUE + | BIG_U_VALUE + | ESCAPED_CHAR + ; + +//byte_value = octal_byte_value | hex_byte_value . +fragment BYTE_VALUE + : OCTAL_BYTE_VALUE | HEX_BYTE_VALUE + ; + +//octal_byte_value = `\` octal_digit octal_digit octal_digit . +fragment OCTAL_BYTE_VALUE + : '\\' OCTAL_DIGIT OCTAL_DIGIT OCTAL_DIGIT + ; + +//hex_byte_value = `\` "x" hex_digit hex_digit . +fragment HEX_BYTE_VALUE + : '\\' 'x' HEX_DIGIT HEX_DIGIT + ; + +//little_u_value = `\` "u" hex_digit hex_digit hex_digit hex_digit . +// hex_digit hex_digit hex_digit hex_digit . +LITTLE_U_VALUE + : '\\u' HEX_DIGIT HEX_DIGIT HEX_DIGIT HEX_DIGIT + ; + +//big_u_value = `\` "U" hex_digit hex_digit hex_digit hex_digit +BIG_U_VALUE + : '\\U' HEX_DIGIT HEX_DIGIT HEX_DIGIT HEX_DIGIT HEX_DIGIT HEX_DIGIT HEX_DIGIT HEX_DIGIT + ; + +//escaped_char = `\` ( "a" | "b" | "f" | "n" | "r" | "t" | "v" | `\` | "'" | `"` ) . +fragment ESCAPED_CHAR + : '\\' ( 'a' | 'b' | 'f' | 'n' | 'r' | 't' | 'v' | '\\' | '\'' | '"' ) + ; + + +// String literals + +//string_lit = raw_string_lit | interpreted_string_lit . +STRING_LIT + : RAW_STRING_LIT + | INTERPRETED_STRING_LIT + ; + +//raw_string_lit = "`" { unicode_char | newline } "`" . +fragment RAW_STRING_LIT + : '`' ( UNICODE_CHAR | NEWLINE | [~`] )*? '`' + ; + +//interpreted_string_lit = `"` { unicode_value | byte_value } `"` . +fragment INTERPRETED_STRING_LIT + : '"' ( '\\"' | UNICODE_VALUE | BYTE_VALUE )*? '"' + ; + + +// +// Source code representation +// + +//letter = unicode_letter | "_" . +fragment LETTER + : UNICODE_LETTER + | '_' + ; + +//decimal_digit = "0" … "9" . +fragment DECIMAL_DIGIT + : [0-9] + ; + +//octal_digit = "0" … "7" . +fragment OCTAL_DIGIT + : [0-7] + ; + +//hex_digit = "0" … "9" | "A" … "F" | "a" … "f" . +fragment HEX_DIGIT + : [0-9a-fA-F] + ; + +//newline = /* the Unicode code point U+000A */ . +fragment NEWLINE + : [\u000A] + ; + +//unicode_char = /* an arbitrary Unicode code point except newline */ . +fragment UNICODE_CHAR : ~[\u000A] ; + +//unicode_digit = /* a Unicode code point classified as "Number, decimal digit" */ . +fragment UNICODE_DIGIT + : [\u0030-\u0039] + | [\u0660-\u0669] + | [\u06F0-\u06F9] + | [\u0966-\u096F] + | [\u09E6-\u09EF] + | [\u0A66-\u0A6F] + | [\u0AE6-\u0AEF] + | [\u0B66-\u0B6F] + | [\u0BE7-\u0BEF] + | [\u0C66-\u0C6F] + | [\u0CE6-\u0CEF] + | [\u0D66-\u0D6F] + | [\u0E50-\u0E59] + | [\u0ED0-\u0ED9] + | [\u0F20-\u0F29] + | [\u1040-\u1049] + | [\u1369-\u1371] + | [\u17E0-\u17E9] + | [\u1810-\u1819] + | [\uFF10-\uFF19] + ; + +//unicode_letter = /* a Unicode code point classified as "Letter" */ . +fragment UNICODE_LETTER + : [\u0041-\u005A] + | [\u0061-\u007A] + | [\u00AA] + | [\u00B5] + | [\u00BA] + | [\u00C0-\u00D6] + | [\u00D8-\u00F6] + | [\u00F8-\u021F] + | [\u0222-\u0233] + | [\u0250-\u02AD] + | [\u02B0-\u02B8] + | [\u02BB-\u02C1] + | [\u02D0-\u02D1] + | [\u02E0-\u02E4] + | [\u02EE] + | [\u037A] + | [\u0386] + | [\u0388-\u038A] + | [\u038C] + | [\u038E-\u03A1] + | [\u03A3-\u03CE] + | [\u03D0-\u03D7] + | [\u03DA-\u03F3] + | [\u0400-\u0481] + | [\u048C-\u04C4] + | [\u04C7-\u04C8] + | [\u04CB-\u04CC] + | [\u04D0-\u04F5] + | [\u04F8-\u04F9] + | [\u0531-\u0556] + | [\u0559] + | [\u0561-\u0587] + | [\u05D0-\u05EA] + | [\u05F0-\u05F2] + | [\u0621-\u063A] + | [\u0640-\u064A] + | [\u0671-\u06D3] + | [\u06D5] + | [\u06E5-\u06E6] + | [\u06FA-\u06FC] + | [\u0710] + | [\u0712-\u072C] + | [\u0780-\u07A5] + | [\u0905-\u0939] + | [\u093D] + | [\u0950] + | [\u0958-\u0961] + | [\u0985-\u098C] + | [\u098F-\u0990] + | [\u0993-\u09A8] + | [\u09AA-\u09B0] + | [\u09B2] + | [\u09B6-\u09B9] + | [\u09DC-\u09DD] + | [\u09DF-\u09E1] + | [\u09F0-\u09F1] + | [\u0A05-\u0A0A] + | [\u0A0F-\u0A10] + | [\u0A13-\u0A28] + | [\u0A2A-\u0A30] + | [\u0A32-\u0A33] + | [\u0A35-\u0A36] + | [\u0A38-\u0A39] + | [\u0A59-\u0A5C] + | [\u0A5E] + | [\u0A72-\u0A74] + | [\u0A85-\u0A8B] + | [\u0A8D] + | [\u0A8F-\u0A91] + | [\u0A93-\u0AA8] + | [\u0AAA-\u0AB0] + | [\u0AB2-\u0AB3] + | [\u0AB5-\u0AB9] + | [\u0ABD] + | [\u0AD0] + | [\u0AE0] + | [\u0B05-\u0B0C] + | [\u0B0F-\u0B10] + | [\u0B13-\u0B28] + | [\u0B2A-\u0B30] + | [\u0B32-\u0B33] + | [\u0B36-\u0B39] + | [\u0B3D] + | [\u0B5C-\u0B5D] + | [\u0B5F-\u0B61] + | [\u0B85-\u0B8A] + | [\u0B8E-\u0B90] + | [\u0B92-\u0B95] + | [\u0B99-\u0B9A] + | [\u0B9C] + | [\u0B9E-\u0B9F] + | [\u0BA3-\u0BA4] + | [\u0BA8-\u0BAA] + | [\u0BAE-\u0BB5] + | [\u0BB7-\u0BB9] + | [\u0C05-\u0C0C] + | [\u0C0E-\u0C10] + | [\u0C12-\u0C28] + | [\u0C2A-\u0C33] + | [\u0C35-\u0C39] + | [\u0C60-\u0C61] + | [\u0C85-\u0C8C] + | [\u0C8E-\u0C90] + | [\u0C92-\u0CA8] + | [\u0CAA-\u0CB3] + | [\u0CB5-\u0CB9] + | [\u0CDE] + | [\u0CE0-\u0CE1] + | [\u0D05-\u0D0C] + | [\u0D0E-\u0D10] + | [\u0D12-\u0D28] + | [\u0D2A-\u0D39] + | [\u0D60-\u0D61] + | [\u0D85-\u0D96] + | [\u0D9A-\u0DB1] + | [\u0DB3-\u0DBB] + | [\u0DBD] + | [\u0DC0-\u0DC6] + | [\u0E01-\u0E30] + | [\u0E32-\u0E33] + | [\u0E40-\u0E46] + | [\u0E81-\u0E82] + | [\u0E84] + | [\u0E87-\u0E88] + | [\u0E8A] + | [\u0E8D] + | [\u0E94-\u0E97] + | [\u0E99-\u0E9F] + | [\u0EA1-\u0EA3] + | [\u0EA5] + | [\u0EA7] + | [\u0EAA-\u0EAB] + | [\u0EAD-\u0EB0] + | [\u0EB2-\u0EB3] + | [\u0EBD-\u0EC4] + | [\u0EC6] + | [\u0EDC-\u0EDD] + | [\u0F00] + | [\u0F40-\u0F6A] + | [\u0F88-\u0F8B] + | [\u1000-\u1021] + | [\u1023-\u1027] + | [\u1029-\u102A] + | [\u1050-\u1055] + | [\u10A0-\u10C5] + | [\u10D0-\u10F6] + | [\u1100-\u1159] + | [\u115F-\u11A2] + | [\u11A8-\u11F9] + | [\u1200-\u1206] + | [\u1208-\u1246] + | [\u1248] + | [\u124A-\u124D] + | [\u1250-\u1256] + | [\u1258] + | [\u125A-\u125D] + | [\u1260-\u1286] + | [\u1288] + | [\u128A-\u128D] + | [\u1290-\u12AE] + | [\u12B0] + | [\u12B2-\u12B5] + | [\u12B8-\u12BE] + | [\u12C0] + | [\u12C2-\u12C5] + | [\u12C8-\u12CE] + | [\u12D0-\u12D6] + | [\u12D8-\u12EE] + | [\u12F0-\u130E] + | [\u1310] + | [\u1312-\u1315] + | [\u1318-\u131E] + | [\u1320-\u1346] + | [\u1348-\u135A] + | [\u13A0-\u13B0] + | [\u13B1-\u13F4] + | [\u1401-\u1676] + | [\u1681-\u169A] + | [\u16A0-\u16EA] + | [\u1780-\u17B3] + | [\u1820-\u1877] + | [\u1880-\u18A8] + | [\u1E00-\u1E9B] + | [\u1EA0-\u1EE0] + | [\u1EE1-\u1EF9] + | [\u1F00-\u1F15] + | [\u1F18-\u1F1D] + | [\u1F20-\u1F39] + | [\u1F3A-\u1F45] + | [\u1F48-\u1F4D] + | [\u1F50-\u1F57] + | [\u1F59] + | [\u1F5B] + | [\u1F5D] + | [\u1F5F-\u1F7D] + | [\u1F80-\u1FB4] + | [\u1FB6-\u1FBC] + | [\u1FBE] + | [\u1FC2-\u1FC4] + | [\u1FC6-\u1FCC] + | [\u1FD0-\u1FD3] + | [\u1FD6-\u1FDB] + | [\u1FE0-\u1FEC] + | [\u1FF2-\u1FF4] + | [\u1FF6-\u1FFC] + | [\u207F] + | [\u2102] + | [\u2107] + | [\u210A-\u2113] + | [\u2115] + | [\u2119-\u211D] + | [\u2124] + | [\u2126] + | [\u2128] + | [\u212A-\u212D] + | [\u212F-\u2131] + | [\u2133-\u2139] + | [\u2160-\u2183] + | [\u3005-\u3007] + | [\u3021-\u3029] + | [\u3031-\u3035] + | [\u3038-\u303A] + | [\u3041-\u3094] + | [\u309D-\u309E] + | [\u30A1-\u30FA] + | [\u30FC-\u30FE] + | [\u3105-\u312C] + | [\u3131-\u318E] + | [\u31A0-\u31B7] + | [\u3400] + | [\u4DB5] + | [\u4E00] + | [\u9FA5] + | [\uA000-\uA48C] + | [\uAC00] + | [\uD7A3] + | [\uF900-\uFA2D] + | [\uFB00-\uFB06] + | [\uFB13-\uFB17] + | [\uFB1D] + | [\uFB1F-\uFB28] + | [\uFB2A-\uFB36] + | [\uFB38-\uFB3C] + | [\uFB3E] + | [\uFB40-\uFB41] + | [\uFB43-\uFB44] + | [\uFB46-\uFBB1] + | [\uFBD3-\uFD3D] + | [\uFD50-\uFD8F] + | [\uFD92-\uFDC7] + | [\uFDF0-\uFDFB] + | [\uFE70-\uFE72] + | [\uFE74] + | [\uFE76-\uFEFC] + | [\uFF21-\uFF3A] + | [\uFF41-\uFF5A] + | [\uFF66-\uFFBE] + | [\uFFC2-\uFFC7] + | [\uFFCA-\uFFCF] + | [\uFFD2-\uFFD7] + | [\uFFDA-\uFFDC] + ; + +// +// Whitespace and comments +// + +WS : [ \t]+ -> channel(HIDDEN) + ; + +COMMENT + : '/*' .*? '*/' -> channel(HIDDEN) + ; + +TERMINATOR + : [\r\n]+ -> channel(HIDDEN) + ; + + +LINE_COMMENT + : '//' ~[\r\n]* -> skip + ; diff --git a/pmd-go/src/main/java/net/sourceforge/pmd/cpd/GoTokenizer.java b/pmd-go/src/main/java/net/sourceforge/pmd/cpd/GoTokenizer.java index 33b59758b87..2d46bbf82bf 100644 --- a/pmd-go/src/main/java/net/sourceforge/pmd/cpd/GoTokenizer.java +++ b/pmd-go/src/main/java/net/sourceforge/pmd/cpd/GoTokenizer.java @@ -4,29 +4,19 @@ package net.sourceforge.pmd.cpd; -import java.util.ArrayList; +import org.antlr.v4.runtime.CharStream; + +import net.sourceforge.pmd.lang.antlr.AntlrTokenManager; +import net.sourceforge.pmd.lang.go.antlr4.GolangLexer; /** - * Implements a tokenizer for the Go Language. - * - * @author oinume@gmail.com + * The Go tokenizer. */ -public class GoTokenizer extends AbstractTokenizer { - - /** - * Creates a new {@link GoTokenizer} - */ - public GoTokenizer() { - // setting markers for "string" in Go - this.stringToken = new ArrayList(); - this.stringToken.add("\""); - this.stringToken.add("`"); - - // setting markers for 'ignorable character' in Go - this.ignorableCharacter = new ArrayList(); - this.ignorableCharacter.add(";"); +public class GoTokenizer extends AntlrTokenizer { - // setting markers for 'ignorable string' in Go - this.ignorableStmt = new ArrayList(); + @Override + protected AntlrTokenManager getLexerForSource(SourceCode sourceCode) { + CharStream charStream = AntlrTokenizer.getCharStreamFromSourceCode(sourceCode); + return new AntlrTokenManager(new GolangLexer(charStream), sourceCode.getFileName()); } } diff --git a/pmd-go/src/main/java/net/sourceforge/pmd/lang/go/GoLanguageModule.java b/pmd-go/src/main/java/net/sourceforge/pmd/lang/go/GoLanguageModule.java new file mode 100644 index 00000000000..9c35be150d5 --- /dev/null +++ b/pmd-go/src/main/java/net/sourceforge/pmd/lang/go/GoLanguageModule.java @@ -0,0 +1,26 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.go; + +import net.sourceforge.pmd.lang.BaseLanguageModule; + +/** + * Language Module for Go. + */ +public class GoLanguageModule extends BaseLanguageModule { + + /** The name. */ + public static final String NAME = "Golang"; + /** The terse name. */ + public static final String TERSE_NAME = "go"; + + /** + * Create a new instance of Golang Language Module. + */ + public GoLanguageModule() { + super(NAME, null, TERSE_NAME, null, "go"); + addVersion("1", null, true); + } +} diff --git a/pmd-go/src/main/resources/META-INF/services/net.sourceforge.pmd.lang.Language b/pmd-go/src/main/resources/META-INF/services/net.sourceforge.pmd.lang.Language new file mode 100644 index 00000000000..bb8223d299f --- /dev/null +++ b/pmd-go/src/main/resources/META-INF/services/net.sourceforge.pmd.lang.Language @@ -0,0 +1 @@ +net.sourceforge.pmd.lang.go.GoLanguageModule diff --git a/pmd-go/src/test/java/net/sourceforge/pmd/LanguageVersionTest.java b/pmd-go/src/test/java/net/sourceforge/pmd/LanguageVersionTest.java new file mode 100644 index 00000000000..89f73cdca0a --- /dev/null +++ b/pmd-go/src/test/java/net/sourceforge/pmd/LanguageVersionTest.java @@ -0,0 +1,27 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd; + +import java.util.Arrays; +import java.util.Collection; + +import org.junit.runners.Parameterized.Parameters; + +import net.sourceforge.pmd.lang.LanguageRegistry; +import net.sourceforge.pmd.lang.LanguageVersion; +import net.sourceforge.pmd.lang.go.GoLanguageModule; + +public class LanguageVersionTest extends AbstractLanguageVersionTest { + + public LanguageVersionTest(String name, String terseName, String version, LanguageVersion expected) { + super(name, terseName, version, expected); + } + + @Parameters + public static Collection data() { + return Arrays.asList(new Object[][] { { GoLanguageModule.NAME, GoLanguageModule.TERSE_NAME, "1", + LanguageRegistry.getLanguage(GoLanguageModule.NAME).getDefaultVersion(), }, }); + } +} diff --git a/pmd-go/src/test/java/net/sourceforge/pmd/cpd/GoTokenizerTest.java b/pmd-go/src/test/java/net/sourceforge/pmd/cpd/GoTokenizerTest.java new file mode 100644 index 00000000000..7e34d939813 --- /dev/null +++ b/pmd-go/src/test/java/net/sourceforge/pmd/cpd/GoTokenizerTest.java @@ -0,0 +1,36 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.cpd; + +import java.io.IOException; + +import org.apache.commons.io.IOUtils; +import org.junit.Before; +import org.junit.Test; + +import net.sourceforge.pmd.testframework.AbstractTokenizerTest; + +public class GoTokenizerTest extends AbstractTokenizerTest { + + private static final String FILENAME = "btrfs.go"; + + @Before + @Override + public void buildTokenizer() throws IOException { + this.tokenizer = new GoTokenizer(); + this.sourceCode = new SourceCode(new SourceCode.StringCodeLoader(this.getSampleCode(), FILENAME)); + } + + @Override + public String getSampleCode() throws IOException { + return IOUtils.toString(GoTokenizer.class.getResourceAsStream(FILENAME)); + } + + @Test + public void tokenizeTest() throws IOException { + this.expectedTokenCount = 3517; + super.tokenizeTest(); + } +} diff --git a/pmd-go/src/test/resources/net/sourceforge/pmd/cpd/btrfs.go b/pmd-go/src/test/resources/net/sourceforge/pmd/cpd/btrfs.go new file mode 100644 index 00000000000..b8c0434bdcb --- /dev/null +++ b/pmd-go/src/test/resources/net/sourceforge/pmd/cpd/btrfs.go @@ -0,0 +1,692 @@ +// Downloaded on 2018/10/14 from https://github.com/kolyshkin/moby/blob/master/daemon/graphdriver/btrfs/btrfs.go +// +// btfrs.go +// btrs +// + +// +build linux + +package btrfs // import "github.com/docker/docker/daemon/graphdriver/btrfs" + +/* +#include +#include +#include +#include + +static void set_name_btrfs_ioctl_vol_args_v2(struct btrfs_ioctl_vol_args_v2* btrfs_struct, const char* value) { + snprintf(btrfs_struct->name, BTRFS_SUBVOL_NAME_MAX, "%s", value); +} +*/ +import "C" + +import ( + "fmt" + "io/ioutil" + "math" + "os" + "path" + "path/filepath" + "strconv" + "strings" + "sync" + "unsafe" + + "github.com/docker/docker/daemon/graphdriver" + "github.com/docker/docker/pkg/containerfs" + "github.com/docker/docker/pkg/idtools" + "github.com/docker/docker/pkg/mount" + "github.com/docker/docker/pkg/parsers" + "github.com/docker/docker/pkg/system" + "github.com/docker/go-units" + "github.com/opencontainers/selinux/go-selinux/label" + "github.com/pkg/errors" + "github.com/sirupsen/logrus" + "golang.org/x/sys/unix" +) + +func init() { + graphdriver.Register("btrfs", Init) +} + +type btrfsOptions struct { + minSpace uint64 + size uint64 +} + +// Init returns a new BTRFS driver. +// An error is returned if BTRFS is not supported. +func Init(home string, options []string, uidMaps, gidMaps []idtools.IDMap) (graphdriver.Driver, error) { + + // Perform feature detection on /var/lib/docker/btrfs if it's an existing directory. + // This covers situations where /var/lib/docker/btrfs is a mount, and on a different + // filesystem than /var/lib/docker. + // If the path does not exist, fall back to using /var/lib/docker for feature detection. + testdir := home + if _, err := os.Stat(testdir); os.IsNotExist(err) { + testdir = filepath.Dir(testdir) + } + + fsMagic, err := graphdriver.GetFSMagic(testdir) + if err != nil { + return nil, err + } + + if fsMagic != graphdriver.FsMagicBtrfs { + return nil, graphdriver.ErrPrerequisites + } + + rootUID, rootGID, err := idtools.GetRootUIDGID(uidMaps, gidMaps) + if err != nil { + return nil, err + } + if err := idtools.MkdirAllAndChown(home, 0700, idtools.Identity{UID: rootUID, GID: rootGID}); err != nil { + return nil, err + } + + opt, userDiskQuota, err := parseOptions(options) + if err != nil { + return nil, err + } + + // For some reason shared mount propagation between a container + // and the host does not work for btrfs, and a remedy is to bind + // mount graphdriver home to itself (even without changing the + // propagation mode). + err = mount.MakeMount(home) + if err != nil { + return nil, errors.Wrapf(err, "failed to make %s a mount", home) + } + + driver := &Driver{ + home: home, + uidMaps: uidMaps, + gidMaps: gidMaps, + options: opt, + } + + if userDiskQuota { + if err := driver.subvolEnableQuota(); err != nil { + return nil, err + } + } + + return graphdriver.NewNaiveDiffDriver(driver, uidMaps, gidMaps), nil +} + +func parseOptions(opt []string) (btrfsOptions, bool, error) { + var options btrfsOptions + userDiskQuota := false + for _, option := range opt { + key, val, err := parsers.ParseKeyValueOpt(option) + if err != nil { + return options, userDiskQuota, err + } + key = strings.ToLower(key) + switch key { + case "btrfs.min_space": + minSpace, err := units.RAMInBytes(val) + if err != nil { + return options, userDiskQuota, err + } + userDiskQuota = true + options.minSpace = uint64(minSpace) + default: + return options, userDiskQuota, fmt.Errorf("Unknown option %s", key) + } + } + return options, userDiskQuota, nil +} + +// Driver contains information about the filesystem mounted. +type Driver struct { + //root of the file system + home string + uidMaps []idtools.IDMap + gidMaps []idtools.IDMap + options btrfsOptions + quotaEnabled bool + once sync.Once +} + +// String prints the name of the driver (btrfs). +func (d *Driver) String() string { + return "btrfs" +} + +// Status returns current driver information in a two dimensional string array. +// Output contains "Build Version" and "Library Version" of the btrfs libraries used. +// Version information can be used to check compatibility with your kernel. +func (d *Driver) Status() [][2]string { + status := [][2]string{} + if bv := btrfsBuildVersion(); bv != "-" { + status = append(status, [2]string{"Build Version", bv}) + } + if lv := btrfsLibVersion(); lv != -1 { + status = append(status, [2]string{"Library Version", fmt.Sprintf("%d", lv)}) + } + return status +} + +// GetMetadata returns empty metadata for this driver. +func (d *Driver) GetMetadata(id string) (map[string]string, error) { + return nil, nil +} + +// Cleanup unmounts the home directory. +func (d *Driver) Cleanup() error { + err := d.subvolDisableQuota() + umountErr := mount.Unmount(d.home) + + // in case we have two errors, prefer the one from disableQuota() + if err != nil { + return err + } + + if umountErr != nil { + return errors.Wrapf(umountErr, "error unmounting %s", d.home) + } + + return nil +} + +func free(p *C.char) { + C.free(unsafe.Pointer(p)) +} + +func openDir(path string) (*C.DIR, error) { + Cpath := C.CString(path) + defer free(Cpath) + + dir := C.opendir(Cpath) + if dir == nil { + return nil, fmt.Errorf("Can't open dir") + } + return dir, nil +} + +func closeDir(dir *C.DIR) { + if dir != nil { + C.closedir(dir) + } +} + +func getDirFd(dir *C.DIR) uintptr { + return uintptr(C.dirfd(dir)) +} + +func subvolCreate(path, name string) error { + dir, err := openDir(path) + if err != nil { + return err + } + defer closeDir(dir) + + var args C.struct_btrfs_ioctl_vol_args + for i, c := range []byte(name) { + args.name[i] = C.char(c) + } + + _, _, errno := unix.Syscall(unix.SYS_IOCTL, getDirFd(dir), C.BTRFS_IOC_SUBVOL_CREATE, + uintptr(unsafe.Pointer(&args))) + if errno != 0 { + return fmt.Errorf("Failed to create btrfs subvolume: %v", errno.Error()) + } + return nil +} + +func subvolSnapshot(src, dest, name string) error { + srcDir, err := openDir(src) + if err != nil { + return err + } + defer closeDir(srcDir) + + destDir, err := openDir(dest) + if err != nil { + return err + } + defer closeDir(destDir) + + var args C.struct_btrfs_ioctl_vol_args_v2 + args.fd = C.__s64(getDirFd(srcDir)) + + var cs = C.CString(name) + C.set_name_btrfs_ioctl_vol_args_v2(&args, cs) + C.free(unsafe.Pointer(cs)) + + _, _, errno := unix.Syscall(unix.SYS_IOCTL, getDirFd(destDir), C.BTRFS_IOC_SNAP_CREATE_V2, + uintptr(unsafe.Pointer(&args))) + if errno != 0 { + return fmt.Errorf("Failed to create btrfs snapshot: %v", errno.Error()) + } + return nil +} + +func isSubvolume(p string) (bool, error) { + var bufStat unix.Stat_t + if err := unix.Lstat(p, &bufStat); err != nil { + return false, err + } + + // return true if it is a btrfs subvolume + return bufStat.Ino == C.BTRFS_FIRST_FREE_OBJECTID, nil +} + +func subvolDelete(dirpath, name string, quotaEnabled bool) error { + dir, err := openDir(dirpath) + if err != nil { + return err + } + defer closeDir(dir) + fullPath := path.Join(dirpath, name) + + var args C.struct_btrfs_ioctl_vol_args + + // walk the btrfs subvolumes + walkSubvolumes := func(p string, f os.FileInfo, err error) error { + if err != nil { + if os.IsNotExist(err) && p != fullPath { + // missing most likely because the path was a subvolume that got removed in the previous iteration + // since it's gone anyway, we don't care + return nil + } + return fmt.Errorf("error walking subvolumes: %v", err) + } + // we want to check children only so skip itself + // it will be removed after the filepath walk anyways + if f.IsDir() && p != fullPath { + sv, err := isSubvolume(p) + if err != nil { + return fmt.Errorf("Failed to test if %s is a btrfs subvolume: %v", p, err) + } + if sv { + if err := subvolDelete(path.Dir(p), f.Name(), quotaEnabled); err != nil { + return fmt.Errorf("Failed to destroy btrfs child subvolume (%s) of parent (%s): %v", p, dirpath, err) + } + } + } + return nil + } + if err := filepath.Walk(path.Join(dirpath, name), walkSubvolumes); err != nil { + return fmt.Errorf("Recursively walking subvolumes for %s failed: %v", dirpath, err) + } + + if quotaEnabled { + if qgroupid, err := subvolLookupQgroup(fullPath); err == nil { + var args C.struct_btrfs_ioctl_qgroup_create_args + args.qgroupid = C.__u64(qgroupid) + + _, _, errno := unix.Syscall(unix.SYS_IOCTL, getDirFd(dir), C.BTRFS_IOC_QGROUP_CREATE, + uintptr(unsafe.Pointer(&args))) + if errno != 0 { + logrus.WithField("storage-driver", "btrfs").Errorf("Failed to delete btrfs qgroup %v for %s: %v", qgroupid, fullPath, errno.Error()) + } + } else { + logrus.WithField("storage-driver", "btrfs").Errorf("Failed to lookup btrfs qgroup for %s: %v", fullPath, err.Error()) + } + } + + // all subvolumes have been removed + // now remove the one originally passed in + for i, c := range []byte(name) { + args.name[i] = C.char(c) + } + _, _, errno := unix.Syscall(unix.SYS_IOCTL, getDirFd(dir), C.BTRFS_IOC_SNAP_DESTROY, + uintptr(unsafe.Pointer(&args))) + if errno != 0 { + return fmt.Errorf("Failed to destroy btrfs snapshot %s for %s: %v", dirpath, name, errno.Error()) + } + return nil +} + +func (d *Driver) updateQuotaStatus() { + d.once.Do(func() { + if !d.quotaEnabled { + // In case quotaEnabled is not set, check qgroup and update quotaEnabled as needed + if err := subvolQgroupStatus(d.home); err != nil { + // quota is still not enabled + return + } + d.quotaEnabled = true + } + }) +} + +func (d *Driver) subvolEnableQuota() error { + d.updateQuotaStatus() + + if d.quotaEnabled { + return nil + } + + dir, err := openDir(d.home) + if err != nil { + return err + } + defer closeDir(dir) + + var args C.struct_btrfs_ioctl_quota_ctl_args + args.cmd = C.BTRFS_QUOTA_CTL_ENABLE + _, _, errno := unix.Syscall(unix.SYS_IOCTL, getDirFd(dir), C.BTRFS_IOC_QUOTA_CTL, + uintptr(unsafe.Pointer(&args))) + if errno != 0 { + return fmt.Errorf("Failed to enable btrfs quota for %s: %v", dir, errno.Error()) + } + + d.quotaEnabled = true + + return nil +} + +func (d *Driver) subvolDisableQuota() error { + d.updateQuotaStatus() + + if !d.quotaEnabled { + return nil + } + + dir, err := openDir(d.home) + if err != nil { + return err + } + defer closeDir(dir) + + var args C.struct_btrfs_ioctl_quota_ctl_args + args.cmd = C.BTRFS_QUOTA_CTL_DISABLE + _, _, errno := unix.Syscall(unix.SYS_IOCTL, getDirFd(dir), C.BTRFS_IOC_QUOTA_CTL, + uintptr(unsafe.Pointer(&args))) + if errno != 0 { + return fmt.Errorf("Failed to disable btrfs quota for %s: %v", dir, errno.Error()) + } + + d.quotaEnabled = false + + return nil +} + +func (d *Driver) subvolRescanQuota() error { + d.updateQuotaStatus() + + if !d.quotaEnabled { + return nil + } + + dir, err := openDir(d.home) + if err != nil { + return err + } + defer closeDir(dir) + + var args C.struct_btrfs_ioctl_quota_rescan_args + _, _, errno := unix.Syscall(unix.SYS_IOCTL, getDirFd(dir), C.BTRFS_IOC_QUOTA_RESCAN_WAIT, + uintptr(unsafe.Pointer(&args))) + if errno != 0 { + return fmt.Errorf("Failed to rescan btrfs quota for %s: %v", dir, errno.Error()) + } + + return nil +} + +func subvolLimitQgroup(path string, size uint64) error { + dir, err := openDir(path) + if err != nil { + return err + } + defer closeDir(dir) + + var args C.struct_btrfs_ioctl_qgroup_limit_args + args.lim.max_referenced = C.__u64(size) + args.lim.flags = C.BTRFS_QGROUP_LIMIT_MAX_RFER + _, _, errno := unix.Syscall(unix.SYS_IOCTL, getDirFd(dir), C.BTRFS_IOC_QGROUP_LIMIT, + uintptr(unsafe.Pointer(&args))) + if errno != 0 { + return fmt.Errorf("Failed to limit qgroup for %s: %v", dir, errno.Error()) + } + + return nil +} + +// subvolQgroupStatus performs a BTRFS_IOC_TREE_SEARCH on the root path +// with search key of BTRFS_QGROUP_STATUS_KEY. +// In case qgroup is enabled, the retuned key type will match BTRFS_QGROUP_STATUS_KEY. +// For more details please see https://github.com/kdave/btrfs-progs/blob/v4.9/qgroup.c#L1035 +func subvolQgroupStatus(path string) error { + dir, err := openDir(path) + if err != nil { + return err + } + defer closeDir(dir) + + var args C.struct_btrfs_ioctl_search_args + args.key.tree_id = C.BTRFS_QUOTA_TREE_OBJECTID + args.key.min_type = C.BTRFS_QGROUP_STATUS_KEY + args.key.max_type = C.BTRFS_QGROUP_STATUS_KEY + args.key.max_objectid = C.__u64(math.MaxUint64) + args.key.max_offset = C.__u64(math.MaxUint64) + args.key.max_transid = C.__u64(math.MaxUint64) + args.key.nr_items = 4096 + + _, _, errno := unix.Syscall(unix.SYS_IOCTL, getDirFd(dir), C.BTRFS_IOC_TREE_SEARCH, + uintptr(unsafe.Pointer(&args))) + if errno != 0 { + return fmt.Errorf("Failed to search qgroup for %s: %v", path, errno.Error()) + } + sh := (*C.struct_btrfs_ioctl_search_header)(unsafe.Pointer(&args.buf)) + if sh._type != C.BTRFS_QGROUP_STATUS_KEY { + return fmt.Errorf("Invalid qgroup search header type for %s: %v", path, sh._type) + } + return nil +} + +func subvolLookupQgroup(path string) (uint64, error) { + dir, err := openDir(path) + if err != nil { + return 0, err + } + defer closeDir(dir) + + var args C.struct_btrfs_ioctl_ino_lookup_args + args.objectid = C.BTRFS_FIRST_FREE_OBJECTID + + _, _, errno := unix.Syscall(unix.SYS_IOCTL, getDirFd(dir), C.BTRFS_IOC_INO_LOOKUP, + uintptr(unsafe.Pointer(&args))) + if errno != 0 { + return 0, fmt.Errorf("Failed to lookup qgroup for %s: %v", dir, errno.Error()) + } + if args.treeid == 0 { + return 0, fmt.Errorf("Invalid qgroup id for %s: 0", dir) + } + + return uint64(args.treeid), nil +} + +func (d *Driver) subvolumesDir() string { + return path.Join(d.home, "subvolumes") +} + +func (d *Driver) subvolumesDirID(id string) string { + return path.Join(d.subvolumesDir(), id) +} + +func (d *Driver) quotasDir() string { + return path.Join(d.home, "quotas") +} + +func (d *Driver) quotasDirID(id string) string { + return path.Join(d.quotasDir(), id) +} + +// CreateReadWrite creates a layer that is writable for use as a container +// file system. +func (d *Driver) CreateReadWrite(id, parent string, opts *graphdriver.CreateOpts) error { + return d.Create(id, parent, opts) +} + +// Create the filesystem with given id. +func (d *Driver) Create(id, parent string, opts *graphdriver.CreateOpts) error { + quotas := path.Join(d.home, "quotas") + subvolumes := path.Join(d.home, "subvolumes") + rootUID, rootGID, err := idtools.GetRootUIDGID(d.uidMaps, d.gidMaps) + if err != nil { + return err + } + if err := idtools.MkdirAllAndChown(subvolumes, 0700, idtools.Identity{UID: rootUID, GID: rootGID}); err != nil { + return err + } + if parent == "" { + if err := subvolCreate(subvolumes, id); err != nil { + return err + } + } else { + parentDir := d.subvolumesDirID(parent) + st, err := os.Stat(parentDir) + if err != nil { + return err + } + if !st.IsDir() { + return fmt.Errorf("%s: not a directory", parentDir) + } + if err := subvolSnapshot(parentDir, subvolumes, id); err != nil { + return err + } + } + + var storageOpt map[string]string + if opts != nil { + storageOpt = opts.StorageOpt + } + + if _, ok := storageOpt["size"]; ok { + driver := &Driver{} + if err := d.parseStorageOpt(storageOpt, driver); err != nil { + return err + } + + if err := d.setStorageSize(path.Join(subvolumes, id), driver); err != nil { + return err + } + if err := idtools.MkdirAllAndChown(quotas, 0700, idtools.Identity{UID: rootUID, GID: rootGID}); err != nil { + return err + } + if err := ioutil.WriteFile(path.Join(quotas, id), []byte(fmt.Sprint(driver.options.size)), 0644); err != nil { + return err + } + } + + // if we have a remapped root (user namespaces enabled), change the created snapshot + // dir ownership to match + if rootUID != 0 || rootGID != 0 { + if err := os.Chown(path.Join(subvolumes, id), rootUID, rootGID); err != nil { + return err + } + } + + mountLabel := "" + if opts != nil { + mountLabel = opts.MountLabel + } + + return label.Relabel(path.Join(subvolumes, id), mountLabel, false) +} + +// Parse btrfs storage options +func (d *Driver) parseStorageOpt(storageOpt map[string]string, driver *Driver) error { + // Read size to change the subvolume disk quota per container + for key, val := range storageOpt { + key := strings.ToLower(key) + switch key { + case "size": + size, err := units.RAMInBytes(val) + if err != nil { + return err + } + driver.options.size = uint64(size) + default: + return fmt.Errorf("Unknown option %s", key) + } + } + + return nil +} + +// Set btrfs storage size +func (d *Driver) setStorageSize(dir string, driver *Driver) error { + if driver.options.size <= 0 { + return fmt.Errorf("btrfs: invalid storage size: %s", units.HumanSize(float64(driver.options.size))) + } + if d.options.minSpace > 0 && driver.options.size < d.options.minSpace { + return fmt.Errorf("btrfs: storage size cannot be less than %s", units.HumanSize(float64(d.options.minSpace))) + } + if err := d.subvolEnableQuota(); err != nil { + return err + } + return subvolLimitQgroup(dir, driver.options.size) +} + +// Remove the filesystem with given id. +func (d *Driver) Remove(id string) error { + dir := d.subvolumesDirID(id) + if _, err := os.Stat(dir); err != nil { + return err + } + quotasDir := d.quotasDirID(id) + if _, err := os.Stat(quotasDir); err == nil { + if err := os.Remove(quotasDir); err != nil { + return err + } + } else if !os.IsNotExist(err) { + return err + } + + // Call updateQuotaStatus() to invoke status update + d.updateQuotaStatus() + + if err := subvolDelete(d.subvolumesDir(), id, d.quotaEnabled); err != nil { + return err + } + if err := system.EnsureRemoveAll(dir); err != nil { + return err + } + return d.subvolRescanQuota() +} + +// Get the requested filesystem id. +func (d *Driver) Get(id, mountLabel string) (containerfs.ContainerFS, error) { + dir := d.subvolumesDirID(id) + st, err := os.Stat(dir) + if err != nil { + return nil, err + } + + if !st.IsDir() { + return nil, fmt.Errorf("%s: not a directory", dir) + } + + if quota, err := ioutil.ReadFile(d.quotasDirID(id)); err == nil { + if size, err := strconv.ParseUint(string(quota), 10, 64); err == nil && size >= d.options.minSpace { + if err := d.subvolEnableQuota(); err != nil { + return nil, err + } + if err := subvolLimitQgroup(dir, size); err != nil { + return nil, err + } + } + } + + return containerfs.NewLocalContainerFS(dir), nil +} + +// Put is not implemented for BTRFS as there is no cleanup required for the id. +func (d *Driver) Put(id string) error { + // Get() creates no runtime resources (like e.g. mounts) + // so this doesn't need to do anything. + return nil +} + +// Exists checks if the id exists in the filesystem. +func (d *Driver) Exists(id string) bool { + dir := d.subvolumesDirID(id) + _, err := os.Stat(dir) + return err == nil +} diff --git a/pmd-groovy/pom.xml b/pmd-groovy/pom.xml index e1bb0e888c5..fd5c4e8cb19 100644 --- a/pmd-groovy/pom.xml +++ b/pmd-groovy/pom.xml @@ -7,13 +7,9 @@ net.sourceforge.pmd pmd - 6.0.0-SNAPSHOT + 6.10.0-SNAPSHOT - - ${basedir}/../pmd-core - - @@ -33,6 +29,10 @@ org.codehaus.groovy groovy + + commons-io + commons-io + net.sourceforge.pmd diff --git a/pmd-groovy/src/main/java/net/sourceforge/pmd/cpd/GroovyTokenizer.java b/pmd-groovy/src/main/java/net/sourceforge/pmd/cpd/GroovyTokenizer.java index 34eec4ef873..470c3b1cad7 100644 --- a/pmd-groovy/src/main/java/net/sourceforge/pmd/cpd/GroovyTokenizer.java +++ b/pmd-groovy/src/main/java/net/sourceforge/pmd/cpd/GroovyTokenizer.java @@ -4,7 +4,8 @@ package net.sourceforge.pmd.cpd; -import org.apache.commons.io.IOUtils; +import java.io.StringReader; + import org.codehaus.groovy.antlr.parser.GroovyLexer; import net.sourceforge.pmd.lang.ast.TokenMgrError; @@ -22,7 +23,7 @@ public class GroovyTokenizer implements Tokenizer { public void tokenize(SourceCode sourceCode, Tokens tokenEntries) { StringBuilder buffer = sourceCode.getCodeBuffer(); - GroovyLexer lexer = new GroovyLexer(IOUtils.toInputStream(buffer.toString())); + GroovyLexer lexer = new GroovyLexer(new StringReader(buffer.toString())); TokenStream tokenStream = lexer.plumb(); try { diff --git a/pmd-groovy/src/test/java/net/sourceforge/pmd/cpd/GroovyTokenizerTest.java b/pmd-groovy/src/test/java/net/sourceforge/pmd/cpd/GroovyTokenizerTest.java index 8c4e840354a..200fecfb800 100644 --- a/pmd-groovy/src/test/java/net/sourceforge/pmd/cpd/GroovyTokenizerTest.java +++ b/pmd-groovy/src/test/java/net/sourceforge/pmd/cpd/GroovyTokenizerTest.java @@ -5,6 +5,7 @@ package net.sourceforge.pmd.cpd; import java.io.IOException; +import java.nio.charset.StandardCharsets; import org.apache.commons.io.IOUtils; import org.junit.Before; @@ -25,7 +26,7 @@ public void buildTokenizer() throws IOException { @Override public String getSampleCode() throws IOException { - return IOUtils.toString(GroovyTokenizer.class.getResourceAsStream(FILENAME)); + return IOUtils.toString(GroovyTokenizer.class.getResourceAsStream(FILENAME), StandardCharsets.UTF_8); } @Test diff --git a/pmd-java/etc/grammar/Java.jjt b/pmd-java/etc/grammar/Java.jjt index 688acd1cc1e..991335622fb 100644 --- a/pmd-java/etc/grammar/Java.jjt +++ b/pmd-java/etc/grammar/Java.jjt @@ -1,4 +1,22 @@ /** + * Add support for Java 10 Local Variable Type Inference + * See #743. In Java 10 mode, "var" as local variable type is handled special. + * Andreas Dangel 04/2018 + *==================================================================== + * Fixes #888 [java] ParseException occurs with valid '<>' in Java 1.8 mode + * Juan Martin Sotuyo Dodero 01/2018 + *==================================================================== + * Fixes #793 [java] Parser error with private method in nested classes in interfaces + * Andreas Dangel 12/2017 + *==================================================================== + * Add support for Java 9 changes: + * Private interface methods are only allowed with java9. + * A single underscore "_" is an invalid identifier in java9. + * Diamond operator for anonymous classes is only allowed with java9. + * Add support for module-info.java. + * Allow more concise try-with-resources statements with java9. + * Andreas Dangel 09/2017 + *==================================================================== * Add support for new Java 8 annotation locations. * Bugs #414, #415, #417 * @Snap252 06/2017 @@ -319,16 +337,57 @@ public class JavaParser { throwParseException("Cannot use explicit receiver parameters when running in JDK inferior to 1.8 mode!"); } } + private void checkForBadAnonymousDiamondUsage() { + if (jdkVersion < 9) { + ASTAllocationExpression node = (ASTAllocationExpression)jjtree.peekNode(); + ASTTypeArguments types = node.getFirstChildOfType(ASTClassOrInterfaceType.class).getFirstChildOfType(ASTTypeArguments.class); + if (node.isAnonymousClass() && types != null && types.isDiamond()) { + throwParseException("Cannot use '<>' with anonymous inner classes when running in JDK inferior to 9 mode!"); + } + } + } + /** + * Keeps track whether we are dealing with an interface or not. Needed since the tree is + * is not fully constructed yet, when we check for private interface methods. + * The flag is updated, if entering ClassOrInterfaceDeclaration and reset when leaving. + * The flag is also reset, if entering a anonymous inner class or enums. + */ + private boolean inInterface = false; + private void checkForBadPrivateInterfaceMethod(ASTMethodDeclaration node) { + if (jdkVersion < 9 && inInterface && node.isPrivate()) { + throwParseException("Cannot use private interface methods when running in JDK inferior to 9 mode!"); + } + } + private void checkForBadIdentifier(String image) { + if (jdkVersion >= 9 && "_".equals(image)) { + throwParseException("With JDK 9, '_' is a keyword, and may not be used as an identifier!"); + } + } + private void checkForBadModuleUsage() { + if (jdkVersion < 9) { + throwParseException("Cannot use module declaration when running in JDK inferior to 9 mode!"); + } + } + private void checkForBadConciseTryWithResourcesUsage() { + if (jdkVersion < 9) { + throwParseException("Cannot use concise try-with-resources when running in JDK inferior to 9 mode!"); + } + } + private void checkForBadTypeIdentifierUsage(String image) { + if (jdkVersion >= 10 && "var".equals(image)) { + throwParseException("With JDK 10, 'var' is a restricted local variable type and cannot be used for type declarations!"); + } + } // This is a semantic LOOKAHEAD to determine if we're dealing with an assert // Note that this can't be replaced with a syntactic lookahead // since "assert" isn't a string literal token private boolean isNextTokenAnAssert() { - boolean res = getToken(1).image.equals("assert"); - if (res && jdkVersion <= 3 && getToken(2).image.equals("(")) { - res = false; + if (jdkVersion <= 3) { + return false; } - return res; + + return getToken(1).image.equals("assert"); } private boolean isPrecededByComment(Token tok) { @@ -342,6 +401,14 @@ public class JavaParser { return res; } + /** + * Semantic lookahead to check if the next identifier is a + * specific restricted keyword. + */ + private boolean isKeyword(String keyword) { + return getToken(1).kind == IDENTIFIER && getToken(1).image.equals(keyword); + } + public Map getSuppressMap() { return token_source.getSuppressMap(); } @@ -462,6 +529,27 @@ TOKEN : | < STRICTFP: "strictfp" > } + +/* Restricted Keywords */ +// Note: These are commented out, since these keywords +// can still be used as identifiers. +// see isKeyword() semantic lookup +/* +TOKEN : +{ + < OPEN: "open" > +| < MODULE: "module" > +| < REQUIRES: "requires" > +| < TRANSITIVE: "transitive" > +| < EXPORTS: "exports" > +| < OPENS: "opens" > +| < TO: "to" > +| < USES: "uses" > +| < PROVIDES: "provides" > +| < WITH: "with" > +} +*/ + /* LITERALS */ TOKEN : @@ -528,7 +616,7 @@ TOKEN : < IDENTIFIER: ()* > | < #LETTER: - [ // all chars for which Character.isIdentifierStart is true + [ // all chars for which Character.isJavaIdentifierStart is true "$", "A"-"Z", "_", @@ -539,55 +627,67 @@ TOKEN : "\u00ba", "\u00c0"-"\u00d6", "\u00d8"-"\u00f6", - "\u00f8"-"\u021f", - "\u0222"-"\u0233", - "\u0250"-"\u02ad", - "\u02b0"-"\u02b8", - "\u02bb"-"\u02c1", - "\u02d0"-"\u02d1", + "\u00f8"-"\u02c1", + "\u02c6"-"\u02d1", "\u02e0"-"\u02e4", + "\u02ec", "\u02ee", - "\u037a", + "\u0370"-"\u0374", + "\u0376"-"\u0377", + "\u037a"-"\u037d", + "\u037f", "\u0386", "\u0388"-"\u038a", "\u038c", "\u038e"-"\u03a1", - "\u03a3"-"\u03ce", - "\u03d0"-"\u03d7", - "\u03da"-"\u03f3", - "\u0400"-"\u0481", - "\u048c"-"\u04c4", - "\u04c7"-"\u04c8", - "\u04cb"-"\u04cc", - "\u04d0"-"\u04f5", - "\u04f8"-"\u04f9", + "\u03a3"-"\u03f5", + "\u03f7"-"\u0481", + "\u048a"-"\u052f", "\u0531"-"\u0556", "\u0559", "\u0561"-"\u0587", + "\u058f", "\u05d0"-"\u05ea", "\u05f0"-"\u05f2", - "\u0621"-"\u063a", - "\u0640"-"\u064a", + "\u060b", + "\u0620"-"\u064a", + "\u066e"-"\u066f", "\u0671"-"\u06d3", "\u06d5", "\u06e5"-"\u06e6", + "\u06ee"-"\u06ef", "\u06fa"-"\u06fc", + "\u06ff", "\u0710", - "\u0712"-"\u072c", - "\u0780"-"\u07a5", - "\u0905"-"\u0939", + "\u0712"-"\u072f", + "\u074d"-"\u07a5", + "\u07b1", + "\u07ca"-"\u07ea", + "\u07f4"-"\u07f5", + "\u07fa", + "\u0800"-"\u0815", + "\u081a", + "\u0824", + "\u0828", + "\u0840"-"\u0858", + "\u08a0"-"\u08b4", + "\u0904"-"\u0939", "\u093d", "\u0950", "\u0958"-"\u0961", + "\u0971"-"\u0980", "\u0985"-"\u098c", "\u098f"-"\u0990", "\u0993"-"\u09a8", "\u09aa"-"\u09b0", "\u09b2", "\u09b6"-"\u09b9", + "\u09bd", + "\u09ce", "\u09dc"-"\u09dd", "\u09df"-"\u09e1", "\u09f0"-"\u09f3", + "\u09fb", "\u0a05"-"\u0a0a", "\u0a0f"-"\u0a10", "\u0a13"-"\u0a28", @@ -598,8 +698,7 @@ TOKEN : "\u0a59"-"\u0a5c", "\u0a5e", "\u0a72"-"\u0a74", - "\u0a85"-"\u0a8b", - "\u0a8d", + "\u0a85"-"\u0a8d", "\u0a8f"-"\u0a91", "\u0a93"-"\u0aa8", "\u0aaa"-"\u0ab0", @@ -607,16 +706,20 @@ TOKEN : "\u0ab5"-"\u0ab9", "\u0abd", "\u0ad0", - "\u0ae0", + "\u0ae0"-"\u0ae1", + "\u0af1", + "\u0af9", "\u0b05"-"\u0b0c", "\u0b0f"-"\u0b10", "\u0b13"-"\u0b28", "\u0b2a"-"\u0b30", "\u0b32"-"\u0b33", - "\u0b36"-"\u0b39", + "\u0b35"-"\u0b39", "\u0b3d", "\u0b5c"-"\u0b5d", "\u0b5f"-"\u0b61", + "\u0b71", + "\u0b83", "\u0b85"-"\u0b8a", "\u0b8e"-"\u0b90", "\u0b92"-"\u0b95", @@ -625,26 +728,32 @@ TOKEN : "\u0b9e"-"\u0b9f", "\u0ba3"-"\u0ba4", "\u0ba8"-"\u0baa", - "\u0bae"-"\u0bb5", - "\u0bb7"-"\u0bb9", + "\u0bae"-"\u0bb9", + "\u0bd0", + "\u0bf9", "\u0c05"-"\u0c0c", "\u0c0e"-"\u0c10", "\u0c12"-"\u0c28", - "\u0c2a"-"\u0c33", - "\u0c35"-"\u0c39", + "\u0c2a"-"\u0c39", + "\u0c3d", + "\u0c58"-"\u0c5a", "\u0c60"-"\u0c61", "\u0c85"-"\u0c8c", "\u0c8e"-"\u0c90", "\u0c92"-"\u0ca8", "\u0caa"-"\u0cb3", "\u0cb5"-"\u0cb9", + "\u0cbd", "\u0cde", "\u0ce0"-"\u0ce1", + "\u0cf1"-"\u0cf2", "\u0d05"-"\u0d0c", "\u0d0e"-"\u0d10", - "\u0d12"-"\u0d28", - "\u0d2a"-"\u0d39", - "\u0d60"-"\u0d61", + "\u0d12"-"\u0d3a", + "\u0d3d", + "\u0d4e", + "\u0d5f"-"\u0d61", + "\u0d7a"-"\u0d7f", "\u0d85"-"\u0d96", "\u0d9a"-"\u0db1", "\u0db3"-"\u0dbb", @@ -669,57 +778,82 @@ TOKEN : "\u0ebd", "\u0ec0"-"\u0ec4", "\u0ec6", - "\u0edc"-"\u0edd", + "\u0edc"-"\u0edf", "\u0f00", "\u0f40"-"\u0f47", - "\u0f49"-"\u0f6a", - "\u0f88"-"\u0f8b", - "\u1000"-"\u1021", - "\u1023"-"\u1027", - "\u1029"-"\u102a", + "\u0f49"-"\u0f6c", + "\u0f88"-"\u0f8c", + "\u1000"-"\u102a", + "\u103f", "\u1050"-"\u1055", + "\u105a"-"\u105d", + "\u1061", + "\u1065"-"\u1066", + "\u106e"-"\u1070", + "\u1075"-"\u1081", + "\u108e", "\u10a0"-"\u10c5", - "\u10d0"-"\u10f6", - "\u1100"-"\u1159", - "\u115f"-"\u11a2", - "\u11a8"-"\u11f9", - "\u1200"-"\u1206", - "\u1208"-"\u1246", - "\u1248", + "\u10c7", + "\u10cd", + "\u10d0"-"\u10fa", + "\u10fc"-"\u1248", "\u124a"-"\u124d", "\u1250"-"\u1256", "\u1258", "\u125a"-"\u125d", - "\u1260"-"\u1286", - "\u1288", + "\u1260"-"\u1288", "\u128a"-"\u128d", - "\u1290"-"\u12ae", - "\u12b0", + "\u1290"-"\u12b0", "\u12b2"-"\u12b5", "\u12b8"-"\u12be", "\u12c0", "\u12c2"-"\u12c5", - "\u12c8"-"\u12ce", - "\u12d0"-"\u12d6", - "\u12d8"-"\u12ee", - "\u12f0"-"\u130e", - "\u1310", + "\u12c8"-"\u12d6", + "\u12d8"-"\u1310", "\u1312"-"\u1315", - "\u1318"-"\u131e", - "\u1320"-"\u1346", - "\u1348"-"\u135a", - "\u13a0"-"\u13f4", + "\u1318"-"\u135a", + "\u1380"-"\u138f", + "\u13a0"-"\u13f5", + "\u13f8"-"\u13fd", "\u1401"-"\u166c", - "\u166f"-"\u1676", + "\u166f"-"\u167f", "\u1681"-"\u169a", "\u16a0"-"\u16ea", + "\u16ee"-"\u16f8", + "\u1700"-"\u170c", + "\u170e"-"\u1711", + "\u1720"-"\u1731", + "\u1740"-"\u1751", + "\u1760"-"\u176c", + "\u176e"-"\u1770", "\u1780"-"\u17b3", - "\u17db", + "\u17d7", + "\u17db"-"\u17dc", "\u1820"-"\u1877", "\u1880"-"\u18a8", - "\u1e00"-"\u1e9b", - "\u1ea0"-"\u1ef9", - "\u1f00"-"\u1f15", + "\u18aa", + "\u18b0"-"\u18f5", + "\u1900"-"\u191e", + "\u1950"-"\u196d", + "\u1970"-"\u1974", + "\u1980"-"\u19ab", + "\u19b0"-"\u19c9", + "\u1a00"-"\u1a16", + "\u1a20"-"\u1a54", + "\u1aa7", + "\u1b05"-"\u1b33", + "\u1b45"-"\u1b4b", + "\u1b83"-"\u1ba0", + "\u1bae"-"\u1baf", + "\u1bba"-"\u1be5", + "\u1c00"-"\u1c23", + "\u1c4d"-"\u1c4f", + "\u1c5a"-"\u1c7d", + "\u1ce9"-"\u1cec", + "\u1cee"-"\u1cf1", + "\u1cf5"-"\u1cf6", + "\u1d00"-"\u1dbf", + "\u1e00"-"\u1f15", "\u1f18"-"\u1f1d", "\u1f20"-"\u1f45", "\u1f48"-"\u1f4d", @@ -739,8 +873,11 @@ TOKEN : "\u1ff2"-"\u1ff4", "\u1ff6"-"\u1ffc", "\u203f"-"\u2040", + "\u2054", + "\u2071", "\u207f", - "\u20a0"-"\u20af", + "\u2090"-"\u209c", + "\u20a0"-"\u20be", "\u2102", "\u2107", "\u210a"-"\u2113", @@ -750,24 +887,102 @@ TOKEN : "\u2126", "\u2128", "\u212a"-"\u212d", - "\u212f"-"\u2131", - "\u2133"-"\u2139", - "\u2160"-"\u2183", + "\u212f"-"\u2139", + "\u213c"-"\u213f", + "\u2145"-"\u2149", + "\u214e", + "\u2160"-"\u2188", + "\u2c00"-"\u2c2e", + "\u2c30"-"\u2c5e", + "\u2c60"-"\u2ce4", + "\u2ceb"-"\u2cee", + "\u2cf2"-"\u2cf3", + "\u2d00"-"\u2d25", + "\u2d27", + "\u2d2d", + "\u2d30"-"\u2d67", + "\u2d6f", + "\u2d80"-"\u2d96", + "\u2da0"-"\u2da6", + "\u2da8"-"\u2dae", + "\u2db0"-"\u2db6", + "\u2db8"-"\u2dbe", + "\u2dc0"-"\u2dc6", + "\u2dc8"-"\u2dce", + "\u2dd0"-"\u2dd6", + "\u2dd8"-"\u2dde", + "\u2e2f", "\u3005"-"\u3007", "\u3021"-"\u3029", "\u3031"-"\u3035", - "\u3038"-"\u303a", - "\u3041"-"\u3094", - "\u309d"-"\u309e", - "\u30a1"-"\u30fe", - "\u3105"-"\u312c", + "\u3038"-"\u303c", + "\u3041"-"\u3096", + "\u309d"-"\u309f", + "\u30a1"-"\u30fa", + "\u30fc"-"\u30ff", + "\u3105"-"\u312d", "\u3131"-"\u318e", - "\u31a0"-"\u31b7", + "\u31a0"-"\u31ba", + "\u31f0"-"\u31ff", "\u3400"-"\u4db5", - "\u4e00"-"\u9fa5", + "\u4e00"-"\u9fd5", "\ua000"-"\ua48c", + "\ua4d0"-"\ua4fd", + "\ua500"-"\ua60c", + "\ua610"-"\ua61f", + "\ua62a"-"\ua62b", + "\ua640"-"\ua66e", + "\ua67f"-"\ua69d", + "\ua6a0"-"\ua6ef", + "\ua717"-"\ua71f", + "\ua722"-"\ua788", + "\ua78b"-"\ua7ad", + "\ua7b0"-"\ua7b7", + "\ua7f7"-"\ua801", + "\ua803"-"\ua805", + "\ua807"-"\ua80a", + "\ua80c"-"\ua822", + "\ua838", + "\ua840"-"\ua873", + "\ua882"-"\ua8b3", + "\ua8f2"-"\ua8f7", + "\ua8fb", + "\ua8fd", + "\ua90a"-"\ua925", + "\ua930"-"\ua946", + "\ua960"-"\ua97c", + "\ua984"-"\ua9b2", + "\ua9cf", + "\ua9e0"-"\ua9e4", + "\ua9e6"-"\ua9ef", + "\ua9fa"-"\ua9fe", + "\uaa00"-"\uaa28", + "\uaa40"-"\uaa42", + "\uaa44"-"\uaa4b", + "\uaa60"-"\uaa76", + "\uaa7a", + "\uaa7e"-"\uaaaf", + "\uaab1", + "\uaab5"-"\uaab6", + "\uaab9"-"\uaabd", + "\uaac0", + "\uaac2", + "\uaadb"-"\uaadd", + "\uaae0"-"\uaaea", + "\uaaf2"-"\uaaf4", + "\uab01"-"\uab06", + "\uab09"-"\uab0e", + "\uab11"-"\uab16", + "\uab20"-"\uab26", + "\uab28"-"\uab2e", + "\uab30"-"\uab5a", + "\uab5c"-"\uab65", + "\uab70"-"\uabe2", "\uac00"-"\ud7a3", - "\uf900"-"\ufa2d", + "\ud7b0"-"\ud7c6", + "\ud7cb"-"\ud7fb", + "\uf900"-"\ufa6d", + "\ufa70"-"\ufad9", "\ufb00"-"\ufb06", "\ufb13"-"\ufb17", "\ufb1d", @@ -781,18 +996,17 @@ TOKEN : "\ufbd3"-"\ufd3d", "\ufd50"-"\ufd8f", "\ufd92"-"\ufdc7", - "\ufdf0"-"\ufdfb", + "\ufdf0"-"\ufdfc", "\ufe33"-"\ufe34", "\ufe4d"-"\ufe4f", "\ufe69", - "\ufe70"-"\ufe72", - "\ufe74", + "\ufe70"-"\ufe74", "\ufe76"-"\ufefc", "\uff04", "\uff21"-"\uff3a", "\uff3f", "\uff41"-"\uff5a", - "\uff65"-"\uffbe", + "\uff66"-"\uffbe", "\uffc2"-"\uffc7", "\uffca"-"\uffcf", "\uffd2"-"\uffd7", @@ -803,7 +1017,7 @@ TOKEN : > | < #PART_LETTER: - [ // all chars for which Character.isIdentifierPart is true + [ // all chars for which Character.isJavaIdentifierPart is true "\u0000"-"\u0008", "\u000e"-"\u001b", "$", @@ -814,79 +1028,74 @@ TOKEN : "\u007f"-"\u009f", "\u00a2"-"\u00a5", "\u00aa", + "\u00ad", "\u00b5", "\u00ba", "\u00c0"-"\u00d6", "\u00d8"-"\u00f6", - "\u00f8"-"\u021f", - "\u0222"-"\u0233", - "\u0250"-"\u02ad", - "\u02b0"-"\u02b8", - "\u02bb"-"\u02c1", - "\u02d0"-"\u02d1", + "\u00f8"-"\u02c1", + "\u02c6"-"\u02d1", "\u02e0"-"\u02e4", + "\u02ec", "\u02ee", - "\u0300"-"\u034e", - "\u0360"-"\u0362", - "\u037a", + "\u0300"-"\u0374", + "\u0376"-"\u0377", + "\u037a"-"\u037d", + "\u037f", "\u0386", "\u0388"-"\u038a", "\u038c", "\u038e"-"\u03a1", - "\u03a3"-"\u03ce", - "\u03d0"-"\u03d7", - "\u03da"-"\u03f3", - "\u0400"-"\u0481", - "\u0483"-"\u0486", - "\u048c"-"\u04c4", - "\u04c7"-"\u04c8", - "\u04cb"-"\u04cc", - "\u04d0"-"\u04f5", - "\u04f8"-"\u04f9", + "\u03a3"-"\u03f5", + "\u03f7"-"\u0481", + "\u0483"-"\u0487", + "\u048a"-"\u052f", "\u0531"-"\u0556", "\u0559", "\u0561"-"\u0587", - "\u0591"-"\u05a1", - "\u05a3"-"\u05b9", - "\u05bb"-"\u05bd", + "\u058f", + "\u0591"-"\u05bd", "\u05bf", "\u05c1"-"\u05c2", - "\u05c4", + "\u05c4"-"\u05c5", + "\u05c7", "\u05d0"-"\u05ea", "\u05f0"-"\u05f2", - "\u0621"-"\u063a", - "\u0640"-"\u0655", - "\u0660"-"\u0669", - "\u0670"-"\u06d3", - "\u06d5"-"\u06dc", + "\u0600"-"\u0605", + "\u060b", + "\u0610"-"\u061a", + "\u061c", + "\u0620"-"\u0669", + "\u066e"-"\u06d3", + "\u06d5"-"\u06dd", "\u06df"-"\u06e8", - "\u06ea"-"\u06ed", - "\u06f0"-"\u06fc", - "\u070f"-"\u072c", - "\u0730"-"\u074a", - "\u0780"-"\u07b0", - "\u0901"-"\u0903", - "\u0905"-"\u0939", - "\u093c"-"\u094d", - "\u0950"-"\u0954", - "\u0958"-"\u0963", + "\u06ea"-"\u06fc", + "\u06ff", + "\u070f"-"\u074a", + "\u074d"-"\u07b1", + "\u07c0"-"\u07f5", + "\u07fa", + "\u0800"-"\u082d", + "\u0840"-"\u085b", + "\u08a0"-"\u08b4", + "\u08e3"-"\u0963", "\u0966"-"\u096f", - "\u0981"-"\u0983", + "\u0971"-"\u0983", "\u0985"-"\u098c", "\u098f"-"\u0990", "\u0993"-"\u09a8", "\u09aa"-"\u09b0", "\u09b2", "\u09b6"-"\u09b9", - "\u09bc", - "\u09be"-"\u09c4", + "\u09bc"-"\u09c4", "\u09c7"-"\u09c8", - "\u09cb"-"\u09cd", + "\u09cb"-"\u09ce", "\u09d7", "\u09dc"-"\u09dd", "\u09df"-"\u09e3", "\u09e6"-"\u09f3", - "\u0a02", + "\u09fb", + "\u0a01"-"\u0a03", "\u0a05"-"\u0a0a", "\u0a0f"-"\u0a10", "\u0a13"-"\u0a28", @@ -898,12 +1107,12 @@ TOKEN : "\u0a3e"-"\u0a42", "\u0a47"-"\u0a48", "\u0a4b"-"\u0a4d", + "\u0a51", "\u0a59"-"\u0a5c", "\u0a5e", - "\u0a66"-"\u0a74", + "\u0a66"-"\u0a75", "\u0a81"-"\u0a83", - "\u0a85"-"\u0a8b", - "\u0a8d", + "\u0a85"-"\u0a8d", "\u0a8f"-"\u0a91", "\u0a93"-"\u0aa8", "\u0aaa"-"\u0ab0", @@ -913,22 +1122,25 @@ TOKEN : "\u0ac7"-"\u0ac9", "\u0acb"-"\u0acd", "\u0ad0", - "\u0ae0", + "\u0ae0"-"\u0ae3", "\u0ae6"-"\u0aef", + "\u0af1", + "\u0af9", "\u0b01"-"\u0b03", "\u0b05"-"\u0b0c", "\u0b0f"-"\u0b10", "\u0b13"-"\u0b28", "\u0b2a"-"\u0b30", "\u0b32"-"\u0b33", - "\u0b36"-"\u0b39", - "\u0b3c"-"\u0b43", + "\u0b35"-"\u0b39", + "\u0b3c"-"\u0b44", "\u0b47"-"\u0b48", "\u0b4b"-"\u0b4d", "\u0b56"-"\u0b57", "\u0b5c"-"\u0b5d", - "\u0b5f"-"\u0b61", + "\u0b5f"-"\u0b63", "\u0b66"-"\u0b6f", + "\u0b71", "\u0b82"-"\u0b83", "\u0b85"-"\u0b8a", "\u0b8e"-"\u0b90", @@ -938,49 +1150,51 @@ TOKEN : "\u0b9e"-"\u0b9f", "\u0ba3"-"\u0ba4", "\u0ba8"-"\u0baa", - "\u0bae"-"\u0bb5", - "\u0bb7"-"\u0bb9", + "\u0bae"-"\u0bb9", "\u0bbe"-"\u0bc2", "\u0bc6"-"\u0bc8", "\u0bca"-"\u0bcd", + "\u0bd0", "\u0bd7", - "\u0be7"-"\u0bef", - "\u0c01"-"\u0c03", + "\u0be6"-"\u0bef", + "\u0bf9", + "\u0c00"-"\u0c03", "\u0c05"-"\u0c0c", "\u0c0e"-"\u0c10", "\u0c12"-"\u0c28", - "\u0c2a"-"\u0c33", - "\u0c35"-"\u0c39", - "\u0c3e"-"\u0c44", + "\u0c2a"-"\u0c39", + "\u0c3d"-"\u0c44", "\u0c46"-"\u0c48", "\u0c4a"-"\u0c4d", "\u0c55"-"\u0c56", - "\u0c60"-"\u0c61", + "\u0c58"-"\u0c5a", + "\u0c60"-"\u0c63", "\u0c66"-"\u0c6f", - "\u0c82"-"\u0c83", + "\u0c81"-"\u0c83", "\u0c85"-"\u0c8c", "\u0c8e"-"\u0c90", "\u0c92"-"\u0ca8", "\u0caa"-"\u0cb3", "\u0cb5"-"\u0cb9", - "\u0cbe"-"\u0cc4", + "\u0cbc"-"\u0cc4", "\u0cc6"-"\u0cc8", "\u0cca"-"\u0ccd", "\u0cd5"-"\u0cd6", "\u0cde", - "\u0ce0"-"\u0ce1", + "\u0ce0"-"\u0ce3", "\u0ce6"-"\u0cef", - "\u0d02"-"\u0d03", + "\u0cf1"-"\u0cf2", + "\u0d01"-"\u0d03", "\u0d05"-"\u0d0c", "\u0d0e"-"\u0d10", - "\u0d12"-"\u0d28", - "\u0d2a"-"\u0d39", - "\u0d3e"-"\u0d43", + "\u0d12"-"\u0d3a", + "\u0d3d"-"\u0d44", "\u0d46"-"\u0d48", - "\u0d4a"-"\u0d4d", + "\u0d4a"-"\u0d4e", "\u0d57", - "\u0d60"-"\u0d61", + "\u0d5f"-"\u0d63", "\u0d66"-"\u0d6f", + "\u0d7a"-"\u0d7f", "\u0d82"-"\u0d83", "\u0d85"-"\u0d96", "\u0d9a"-"\u0db1", @@ -991,6 +1205,7 @@ TOKEN : "\u0dcf"-"\u0dd4", "\u0dd6", "\u0dd8"-"\u0ddf", + "\u0de6"-"\u0def", "\u0df2"-"\u0df3", "\u0e01"-"\u0e3a", "\u0e3f"-"\u0e4e", @@ -1012,7 +1227,7 @@ TOKEN : "\u0ec6", "\u0ec8"-"\u0ecd", "\u0ed0"-"\u0ed9", - "\u0edc"-"\u0edd", + "\u0edc"-"\u0edf", "\u0f00", "\u0f18"-"\u0f19", "\u0f20"-"\u0f29", @@ -1020,65 +1235,85 @@ TOKEN : "\u0f37", "\u0f39", "\u0f3e"-"\u0f47", - "\u0f49"-"\u0f6a", + "\u0f49"-"\u0f6c", "\u0f71"-"\u0f84", - "\u0f86"-"\u0f8b", - "\u0f90"-"\u0f97", + "\u0f86"-"\u0f97", "\u0f99"-"\u0fbc", "\u0fc6", - "\u1000"-"\u1021", - "\u1023"-"\u1027", - "\u1029"-"\u102a", - "\u102c"-"\u1032", - "\u1036"-"\u1039", - "\u1040"-"\u1049", - "\u1050"-"\u1059", + "\u1000"-"\u1049", + "\u1050"-"\u109d", "\u10a0"-"\u10c5", - "\u10d0"-"\u10f6", - "\u1100"-"\u1159", - "\u115f"-"\u11a2", - "\u11a8"-"\u11f9", - "\u1200"-"\u1206", - "\u1208"-"\u1246", - "\u1248", + "\u10c7", + "\u10cd", + "\u10d0"-"\u10fa", + "\u10fc"-"\u1248", "\u124a"-"\u124d", "\u1250"-"\u1256", "\u1258", "\u125a"-"\u125d", - "\u1260"-"\u1286", - "\u1288", + "\u1260"-"\u1288", "\u128a"-"\u128d", - "\u1290"-"\u12ae", - "\u12b0", + "\u1290"-"\u12b0", "\u12b2"-"\u12b5", "\u12b8"-"\u12be", "\u12c0", "\u12c2"-"\u12c5", - "\u12c8"-"\u12ce", - "\u12d0"-"\u12d6", - "\u12d8"-"\u12ee", - "\u12f0"-"\u130e", - "\u1310", + "\u12c8"-"\u12d6", + "\u12d8"-"\u1310", "\u1312"-"\u1315", - "\u1318"-"\u131e", - "\u1320"-"\u1346", - "\u1348"-"\u135a", - "\u1369"-"\u1371", - "\u13a0"-"\u13f4", + "\u1318"-"\u135a", + "\u135d"-"\u135f", + "\u1380"-"\u138f", + "\u13a0"-"\u13f5", + "\u13f8"-"\u13fd", "\u1401"-"\u166c", - "\u166f"-"\u1676", + "\u166f"-"\u167f", "\u1681"-"\u169a", "\u16a0"-"\u16ea", + "\u16ee"-"\u16f8", + "\u1700"-"\u170c", + "\u170e"-"\u1714", + "\u1720"-"\u1734", + "\u1740"-"\u1753", + "\u1760"-"\u176c", + "\u176e"-"\u1770", + "\u1772"-"\u1773", "\u1780"-"\u17d3", - "\u17db", + "\u17d7", + "\u17db"-"\u17dd", "\u17e0"-"\u17e9", "\u180b"-"\u180e", "\u1810"-"\u1819", "\u1820"-"\u1877", - "\u1880"-"\u18a9", - "\u1e00"-"\u1e9b", - "\u1ea0"-"\u1ef9", - "\u1f00"-"\u1f15", + "\u1880"-"\u18aa", + "\u18b0"-"\u18f5", + "\u1900"-"\u191e", + "\u1920"-"\u192b", + "\u1930"-"\u193b", + "\u1946"-"\u196d", + "\u1970"-"\u1974", + "\u1980"-"\u19ab", + "\u19b0"-"\u19c9", + "\u19d0"-"\u19d9", + "\u1a00"-"\u1a1b", + "\u1a20"-"\u1a5e", + "\u1a60"-"\u1a7c", + "\u1a7f"-"\u1a89", + "\u1a90"-"\u1a99", + "\u1aa7", + "\u1ab0"-"\u1abd", + "\u1b00"-"\u1b4b", + "\u1b50"-"\u1b59", + "\u1b6b"-"\u1b73", + "\u1b80"-"\u1bf3", + "\u1c00"-"\u1c37", + "\u1c40"-"\u1c49", + "\u1c4d"-"\u1c7d", + "\u1cd0"-"\u1cd2", + "\u1cd4"-"\u1cf6", + "\u1cf8"-"\u1cf9", + "\u1d00"-"\u1df5", + "\u1dfc"-"\u1f15", "\u1f18"-"\u1f1d", "\u1f20"-"\u1f45", "\u1f48"-"\u1f4d", @@ -1097,14 +1332,19 @@ TOKEN : "\u1fe0"-"\u1fec", "\u1ff2"-"\u1ff4", "\u1ff6"-"\u1ffc", - "\u200c"-"\u200f", + "\u200b"-"\u200f", "\u202a"-"\u202e", "\u203f"-"\u2040", - "\u206a"-"\u206f", + "\u2054", + "\u2060"-"\u2064", + "\u2066"-"\u206f", + "\u2071", "\u207f", - "\u20a0"-"\u20af", + "\u2090"-"\u209c", + "\u20a0"-"\u20be", "\u20d0"-"\u20dc", "\u20e1", + "\u20e5"-"\u20f0", "\u2102", "\u2107", "\u210a"-"\u2113", @@ -1114,25 +1354,94 @@ TOKEN : "\u2126", "\u2128", "\u212a"-"\u212d", - "\u212f"-"\u2131", - "\u2133"-"\u2139", - "\u2160"-"\u2183", + "\u212f"-"\u2139", + "\u213c"-"\u213f", + "\u2145"-"\u2149", + "\u214e", + "\u2160"-"\u2188", + "\u2c00"-"\u2c2e", + "\u2c30"-"\u2c5e", + "\u2c60"-"\u2ce4", + "\u2ceb"-"\u2cf3", + "\u2d00"-"\u2d25", + "\u2d27", + "\u2d2d", + "\u2d30"-"\u2d67", + "\u2d6f", + "\u2d7f"-"\u2d96", + "\u2da0"-"\u2da6", + "\u2da8"-"\u2dae", + "\u2db0"-"\u2db6", + "\u2db8"-"\u2dbe", + "\u2dc0"-"\u2dc6", + "\u2dc8"-"\u2dce", + "\u2dd0"-"\u2dd6", + "\u2dd8"-"\u2dde", + "\u2de0"-"\u2dff", + "\u2e2f", "\u3005"-"\u3007", "\u3021"-"\u302f", "\u3031"-"\u3035", - "\u3038"-"\u303a", - "\u3041"-"\u3094", + "\u3038"-"\u303c", + "\u3041"-"\u3096", "\u3099"-"\u309a", - "\u309d"-"\u309e", - "\u30a1"-"\u30fe", - "\u3105"-"\u312c", + "\u309d"-"\u309f", + "\u30a1"-"\u30fa", + "\u30fc"-"\u30ff", + "\u3105"-"\u312d", "\u3131"-"\u318e", - "\u31a0"-"\u31b7", + "\u31a0"-"\u31ba", + "\u31f0"-"\u31ff", "\u3400"-"\u4db5", - "\u4e00"-"\u9fa5", + "\u4e00"-"\u9fd5", "\ua000"-"\ua48c", + "\ua4d0"-"\ua4fd", + "\ua500"-"\ua60c", + "\ua610"-"\ua62b", + "\ua640"-"\ua66f", + "\ua674"-"\ua67d", + "\ua67f"-"\ua6f1", + "\ua717"-"\ua71f", + "\ua722"-"\ua788", + "\ua78b"-"\ua7ad", + "\ua7b0"-"\ua7b7", + "\ua7f7"-"\ua827", + "\ua838", + "\ua840"-"\ua873", + "\ua880"-"\ua8c4", + "\ua8d0"-"\ua8d9", + "\ua8e0"-"\ua8f7", + "\ua8fb", + "\ua8fd", + "\ua900"-"\ua92d", + "\ua930"-"\ua953", + "\ua960"-"\ua97c", + "\ua980"-"\ua9c0", + "\ua9cf"-"\ua9d9", + "\ua9e0"-"\ua9fe", + "\uaa00"-"\uaa36", + "\uaa40"-"\uaa4d", + "\uaa50"-"\uaa59", + "\uaa60"-"\uaa76", + "\uaa7a"-"\uaac2", + "\uaadb"-"\uaadd", + "\uaae0"-"\uaaef", + "\uaaf2"-"\uaaf6", + "\uab01"-"\uab06", + "\uab09"-"\uab0e", + "\uab11"-"\uab16", + "\uab20"-"\uab26", + "\uab28"-"\uab2e", + "\uab30"-"\uab5a", + "\uab5c"-"\uab65", + "\uab70"-"\uabea", + "\uabec"-"\uabed", + "\uabf0"-"\uabf9", "\uac00"-"\ud7a3", - "\uf900"-"\ufa2d", + "\ud7b0"-"\ud7c6", + "\ud7cb"-"\ud7fb", + "\uf900"-"\ufa6d", + "\ufa70"-"\ufad9", "\ufb00"-"\ufb06", "\ufb13"-"\ufb17", "\ufb1d"-"\ufb28", @@ -1145,13 +1454,13 @@ TOKEN : "\ufbd3"-"\ufd3d", "\ufd50"-"\ufd8f", "\ufd92"-"\ufdc7", - "\ufdf0"-"\ufdfb", - "\ufe20"-"\ufe23", + "\ufdf0"-"\ufdfc", + "\ufe00"-"\ufe0f", + "\ufe20"-"\ufe2f", "\ufe33"-"\ufe34", "\ufe4d"-"\ufe4f", "\ufe69", - "\ufe70"-"\ufe72", - "\ufe74", + "\ufe70"-"\ufe74", "\ufe76"-"\ufefc", "\ufeff", "\uff04", @@ -1159,7 +1468,7 @@ TOKEN : "\uff21"-"\uff3a", "\uff3f", "\uff41"-"\uff5a", - "\uff65"-"\uffbe", + "\uff66"-"\uffbe", "\uffc2"-"\uffc7", "\uffca"-"\uffcf", "\uffd2"-"\uffd7", @@ -1214,6 +1523,7 @@ TOKEN : | < XOR: "^" > | < REM: "%" > | < LSHIFT: "<<" > +// Notice the absence of >> or >>>, to not conflict with generics | < PLUSASSIGN: "+=" > | < MINUSASSIGN: "-=" > | < STARASSIGN: "*=" > @@ -1263,7 +1573,8 @@ ASTCompilationUnit CompilationUnit() : { [ LOOKAHEAD( ( Annotation() )* "package" ) PackageDeclaration() ( EmptyStatement() )* ] ( ImportDeclaration() ( EmptyStatement() )* )* - ( TypeDeclaration() ( EmptyStatement() )* )* + ( LOOKAHEAD(2) TypeDeclaration() ( EmptyStatement() )* )* + [ LOOKAHEAD({isKeyword("open") || isKeyword("module") || getToken(1).kind == AT}) ModuleDeclaration() ( EmptyStatement() )* ] ( < "\u001a" > )? ( < "~[]" > )? @@ -1330,7 +1641,7 @@ void TypeDeclaration(): ( ClassOrInterfaceDeclaration(modifiers) | - EnumDeclaration(modifiers) + LOOKAHEAD({isKeyword("enum")}) EnumDeclaration(modifiers) | AnnotationTypeDeclaration(modifiers) ) @@ -1338,17 +1649,20 @@ void TypeDeclaration(): void ClassOrInterfaceDeclaration(int modifiers): { -Token t = null; -jjtThis.setModifiers(modifiers); + Token t = null; + jjtThis.setModifiers(modifiers); + boolean inInterfaceOld = inInterface; + inInterface = false; } { - - ( /* See note about this optional final modifier in BlockStatement */ ["final"|"abstract"] "class" | "interface" { jjtThis.setInterface(); } ) - t= { jjtThis.setImage(t.image); } + ( /* See note about this optional final modifier in BlockStatement */ + ["final"|"abstract"] "class" | "interface" { jjtThis.setInterface(); inInterface = true; } ) + t= { checkForBadTypeIdentifierUsage(t.image); jjtThis.setImage(t.image); } [ TypeParameters() ] [ ExtendsList() ] [ ImplementsList() ] ClassOrInterfaceBody() + { inInterface = inInterfaceOld; } // always restore the flag after leaving the node } void ExtendsList(): @@ -1356,15 +1670,15 @@ void ExtendsList(): boolean extendsMoreThanOne = false; } { - "extends" (Annotation() {checkForBadTypeAnnotations();})* ClassOrInterfaceType() - ( "," (Annotation() {checkForBadTypeAnnotations();})* ClassOrInterfaceType() { extendsMoreThanOne = true; } )* + "extends" (TypeAnnotation())* ClassOrInterfaceType() + ( "," (TypeAnnotation())* ClassOrInterfaceType() { extendsMoreThanOne = true; } )* } void ImplementsList(): {} { - "implements" (Annotation() {checkForBadTypeAnnotations();})* ClassOrInterfaceType() - ( "," (Annotation() {checkForBadTypeAnnotations();})* ClassOrInterfaceType() )* + "implements" (TypeAnnotation())* ClassOrInterfaceType() + ( "," (TypeAnnotation())* ClassOrInterfaceType() )* } void EnumDeclaration(int modifiers): @@ -1375,7 +1689,7 @@ jjtThis.setModifiers(modifiers); } { t = { - if (!t.image.equals("enum")) { + if (!"enum".equals(t.image)) { throw new ParseException("ERROR: expecting enum"); } @@ -1383,19 +1697,24 @@ jjtThis.setModifiers(modifiers); throw new ParseException("ERROR: Can't use enum as a keyword in pre-JDK 1.5 target"); } } - t= {jjtThis.setImage(t.image);} + t= {checkForBadTypeIdentifierUsage(t.image); jjtThis.setImage(t.image);} [ ImplementsList() ] EnumBody() } void EnumBody(): -{} +{ + boolean inInterfaceOld = inInterface; + inInterface = false; +} { "{" [( Annotation() )* EnumConstant() ( LOOKAHEAD(2) "," ( Annotation() )* EnumConstant() )* ] [ "," ] [ ";" ( ClassOrInterfaceBodyDeclaration() )* ] "}" + + { inInterface = inInterfaceOld; } // always restore the flag after leaving the node } void EnumConstant(): @@ -1413,14 +1732,14 @@ void TypeParameters(): void TypeParameter(): {Token t;} { - (Annotation() {checkForBadTypeAnnotations();})* + (TypeAnnotation())* t= {jjtThis.setImage(t.image);} [ TypeBound() ] } void TypeBound(): {} { - "extends" (Annotation() {checkForBadTypeAnnotations();})* ClassOrInterfaceType() ( "&" (Annotation() {checkForBadTypeAnnotations();})* ClassOrInterfaceType() )* + "extends" (TypeAnnotation())* ClassOrInterfaceType() ( "&" (TypeAnnotation())* ClassOrInterfaceType() )* } void ClassOrInterfaceBody(): @@ -1436,7 +1755,7 @@ void ClassOrInterfaceBodyDeclaration(): { LOOKAHEAD(["static"] "{" ) Initializer() | modifiers = Modifiers() ( LOOKAHEAD(3) ClassOrInterfaceDeclaration(modifiers) - | LOOKAHEAD(3) EnumDeclaration(modifiers) + | LOOKAHEAD({isKeyword("enum")}) EnumDeclaration(modifiers) | LOOKAHEAD( [ TypeParameters() ] "(" ) ConstructorDeclaration(modifiers) | LOOKAHEAD( Type() ( "[" "]" )* ( "," | "=" | ";" ) ) FieldDeclaration(modifiers) | LOOKAHEAD(2) MethodDeclaration(modifiers) @@ -1446,6 +1765,7 @@ void ClassOrInterfaceBodyDeclaration(): ";" } + void FieldDeclaration(int modifiers) : {jjtThis.setModifiers(modifiers);} { @@ -1471,6 +1791,7 @@ void VariableDeclaratorId() : { checkForBadAssertUsage(image, "a variable name"); checkForBadEnumUsage(image, "a variable name"); + checkForBadIdentifier(image); jjtThis.setImage( image ); } } @@ -1489,10 +1810,13 @@ void ArrayInitializer() : } void MethodDeclaration(int modifiers) : -{jjtThis.setModifiers(modifiers);} +{ + jjtThis.setModifiers(modifiers); + { checkForBadPrivateInterfaceMethod(jjtThis); } +} { [ TypeParameters() ] - (Annotation() {checkForBadTypeAnnotations();})* ResultType() MethodDeclarator() [ "throws" NameList() ] + (TypeAnnotation())* ResultType() MethodDeclarator() [ "throws" NameList() ] ( Block() | ";" ) } @@ -1556,21 +1880,27 @@ void Initializer() : /* * Type, name and expression syntax follows. + * Type is the same as "UnannType" in JLS + * + * See https://docs.oracle.com/javase/specs/jls/se10/html/jls-8.html#jls-UnannType */ void Type(): -{} { - LOOKAHEAD(2) ReferenceType() - | - PrimitiveType() + Token t; +} +{ + LOOKAHEAD(2) ReferenceType() + | PrimitiveType() } void ReferenceType(): {} { - PrimitiveType() (Annotation() {checkForBadTypeAnnotations();})* ( LOOKAHEAD(2) "[" "]" { jjtThis.bumpArrayDepth(); })+ + // The grammar here is mildly wrong, the annotations can be before each [] + // This will wait for #997 + PrimitiveType() (TypeAnnotation())* ( LOOKAHEAD(2) "[" "]" { jjtThis.bumpArrayDepth(); })+ | - ( ClassOrInterfaceType()) (Annotation() {checkForBadTypeAnnotations();})* ( LOOKAHEAD(2) "[" "]" { jjtThis.bumpArrayDepth(); })* + ( ClassOrInterfaceType()) (TypeAnnotation())* ( LOOKAHEAD(2) "[" "]" { jjtThis.bumpArrayDepth(); })* } void ClassOrInterfaceType(): @@ -1597,13 +1927,13 @@ void TypeArguments(): void TypeArgument(): {} { - (Annotation() {checkForBadTypeAnnotations();})* (ReferenceType() | "?" [ WildcardBounds() ]) + (TypeAnnotation())* (ReferenceType() | "?" [ WildcardBounds() ]) } void WildcardBounds(): {} { - ("extends" | "super") (Annotation() {checkForBadTypeAnnotations();})* ReferenceType() + ("extends" | "super") (TypeAnnotation())* ReferenceType() } void PrimitiveType() : @@ -1649,8 +1979,8 @@ void Name() : void NameList() : {} { - (Annotation() {checkForBadTypeAnnotations();})* Name() - ( "," (Annotation() {checkForBadTypeAnnotations();})* Name() + (TypeAnnotation())* Name() + ( "," (TypeAnnotation())* Name() )* } @@ -1669,6 +1999,11 @@ void Expression() : * a primary expression. Consider adding a semantic predicate to work * around this. */ + // It also allows lambda expressions in many more contexts as allowed by the JLS. + // Lambda expressions are not a PrimaryExpression in the JLS, instead they're + // separated from the AssignmentExpression production. Their use is restricted + // to method and constructor argument, cast operand, and the RHS of assignments. + // https://docs.oracle.com/javase/specs/jls/se9/html/jls-15.html#jls-15.27 {} { ConditionalExpression() @@ -1694,10 +2029,12 @@ void AssignmentOperator() : | "|=" {jjtThis.setImage("|="); jjtThis.setCompound();} } +// TODO Setting isTernary is unnecessary, since the node is only pushed on the stack if there is at least one child, +// ie if it's a ternary void ConditionalExpression() #ConditionalExpression(>1) : {} { - ConditionalOrExpression() [ LOOKAHEAD(2) "?" {jjtThis.setTernary();} Expression() ":" ConditionalExpression() ] + ConditionalOrExpression() [ LOOKAHEAD(2) "?" Expression() ":" ConditionalExpression() ] } void ConditionalOrExpression() #ConditionalOrExpression(>1): @@ -1760,8 +2097,8 @@ void ShiftExpression() #ShiftExpression(>1): AdditiveExpression() ( LOOKAHEAD(2) ( "<<" { jjtThis.setImage("<<");} - | RSIGNEDSHIFT() - | RUNSIGNEDSHIFT() + | RSIGNEDSHIFT() { jjtThis.setImage(">>"); } + | RUNSIGNEDSHIFT() { jjtThis.setImage(">>>"); } ) AdditiveExpression() )* } @@ -1823,8 +2160,8 @@ void CastExpression() : { LOOKAHEAD( "(" (Annotation())* PrimitiveType() ")" - ) "(" (Annotation() {checkForBadTypeAnnotations();})* Type() ")" UnaryExpression() -| "(" (Annotation() {checkForBadTypeAnnotations();})* Type() ( "&" {checkForBadIntersectionTypesInCasts(); jjtThis.setIntersectionTypes(true);} ReferenceType() )* ")" UnaryExpressionNotPlusMinus() + ) "(" (TypeAnnotation())* Type() ")" UnaryExpression() +| "(" (TypeAnnotation())* Type() ( "&" {checkForBadIntersectionTypesInCasts(); jjtThis.setIntersectionTypes(true);} ReferenceType() )* ")" UnaryExpressionNotPlusMinus() } void PrimaryExpression() : @@ -1869,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" @@ -1921,16 +2286,22 @@ void ArgumentList() : void AllocationExpression(): {} { - "new" (Annotation() {checkForBadTypeAnnotations();})* + "new" (TypeAnnotation())* (LOOKAHEAD(2) PrimitiveType() ArrayDimsAndInits() | - ClassOrInterfaceType() [ TypeArguments() ] + ClassOrInterfaceType() ( ArrayDimsAndInits() | - Arguments() [ ClassOrInterfaceBody() ] + Arguments() + [ + { boolean inInterfaceOld = inInterface; inInterface = false; /* a anonymous class is not a interface */ } + ClassOrInterfaceBody() + { inInterface = inInterfaceOld; } // always restore the flag after leaving the node + ] ) + { checkForBadAnonymousDiamondUsage(); } ) } @@ -1943,9 +2314,9 @@ void ArrayDimsAndInits() : { LOOKAHEAD(2) - ( LOOKAHEAD(2) (Annotation() {checkForBadTypeAnnotations();})* "[" Expression() "]" )+ ( LOOKAHEAD(2) "[" "]" )* + ( LOOKAHEAD(2) (TypeAnnotation())* "[" Expression() "]" {jjtThis.bumpArrayDepth();})+ ( LOOKAHEAD(2) "[" "]" {jjtThis.bumpArrayDepth();} )* | - ( "[" "]" )+ ArrayInitializer() + ( "[" "]" {jjtThis.bumpArrayDepth();})+ ArrayInitializer() } @@ -2007,15 +2378,25 @@ void BlockStatement(): LOOKAHEAD( (Annotation())* ["final"|"abstract"] "class") (Annotation())* ClassOrInterfaceDeclaration(0) } +/* + * See https://docs.oracle.com/javase/specs/jls/se10/html/jls-14.html#jls-14.4 + */ void LocalVariableDeclaration() : {} { ( "final" {jjtThis.setFinal(true);} | Annotation() )* - Type() + LocalVariableType() VariableDeclarator() ( "," VariableDeclarator() )* } +void LocalVariableType() #void : +{} +{ + LOOKAHEAD( { jdkVersion >= 10 && isKeyword("var") } ) + | Type() +} + void EmptyStatement() : {} { @@ -2059,7 +2440,6 @@ void IfStatement() : {} { "if" "(" Expression() ")" Statement() [ LOOKAHEAD(1) "else" {jjtThis.setHasElse();} Statement() ] -{} } void WhileStatement() : @@ -2172,11 +2552,9 @@ void Resources() : void Resource() : {} { - ( "final" | Annotation() )* - Type() - VariableDeclaratorId() - "=" - Expression() + LOOKAHEAD(2) ( ( "final" {jjtThis.setFinal(true);} | Annotation() )* LocalVariableType() VariableDeclaratorId() "=" Expression() ) + | + Name() {checkForBadConciseTryWithResourcesUsage();} } void CatchStatement() : @@ -2207,7 +2585,7 @@ void AssertStatement() : * type declaration syntax with generics clean */ -void RUNSIGNEDSHIFT(): +void RUNSIGNEDSHIFT(): // TODO 7.0.0 make #void {} { ( LOOKAHEAD({ getToken(1).kind == GT && @@ -2216,7 +2594,7 @@ void RUNSIGNEDSHIFT(): ) } -void RSIGNEDSHIFT(): +void RSIGNEDSHIFT(): // TODO 7.0.0 make #void {} { ( LOOKAHEAD({ getToken(1).kind == GT && @@ -2285,6 +2663,15 @@ void MemberValueArrayInitializer(): "{" (MemberValue() ( LOOKAHEAD(2) "," MemberValue() )* [ "," ])? "}" } +/* + * We use that ghost production to factorise the check for JDK >= 1.8. + */ +void TypeAnnotation() #void: +{} +{ + Annotation() {checkForBadTypeAnnotations();} +} + /* Annotation Types. */ @@ -2294,7 +2681,13 @@ Token t; jjtThis.setModifiers(modifiers); } { - "@" "interface" t= {checkForBadAnnotationUsage();jjtThis.setImage(t.image);} AnnotationTypeBody() + "@" "interface" t= + { + checkForBadAnnotationUsage(); + checkForBadTypeIdentifierUsage(t.image); + jjtThis.setImage(t.image); + } + AnnotationTypeBody() } void AnnotationTypeBody(): @@ -2341,3 +2734,41 @@ void DefaultValue(): { "default" MemberValue() } + +void ModuleDeclaration(): +{ + StringBuilder s = new StringBuilder(); + Token t; + checkForBadModuleUsage(); +} +{ + ( Annotation() )* [LOOKAHEAD({isKeyword("open")}) {jjtThis.setOpen(true);}] LOOKAHEAD({isKeyword("module")}) + t= { s.append(t.image); } + ( "." t= { s.append('.').append(t.image); } )* { jjtThis.setImage(s.toString()); } + "{" (ModuleDirective())* "}" +} + +void ModuleDirective(): +{} +{ + ( LOOKAHEAD({isKeyword("requires")}) { jjtThis.setType(ASTModuleDirective.DirectiveType.REQUIRES); } + (LOOKAHEAD({isKeyword("transitive")}) { jjtThis.setRequiresModifier(ASTModuleDirective.RequiresModifier.TRANSITIVE); } | + "static" { jjtThis.setRequiresModifier(ASTModuleDirective.RequiresModifier.STATIC); } )? + ModuleName() ";" ) + | ( LOOKAHEAD({isKeyword("exports")}) { jjtThis.setType(ASTModuleDirective.DirectiveType.EXPORTS); } Name() [ LOOKAHEAD({isKeyword("to")}) ModuleName() ("," ModuleName())*] ";" ) + | ( LOOKAHEAD({isKeyword("opens")}) { jjtThis.setType(ASTModuleDirective.DirectiveType.OPENS); } Name() [ LOOKAHEAD({isKeyword("to")}) ModuleName() ("," ModuleName())*] ";" ) + | ( LOOKAHEAD({isKeyword("uses")}) { jjtThis.setType(ASTModuleDirective.DirectiveType.USES); } Name() ";" ) + | ( LOOKAHEAD({isKeyword("provides")}) { jjtThis.setType(ASTModuleDirective.DirectiveType.PROVIDES); } Name() LOOKAHEAD({isKeyword("with")}) Name() ("," Name() )* ";" ) +} + +// Similar to Name() +void ModuleName(): +{ + StringBuilder s = new StringBuilder(); + Token t; +} +{ + t= { s.append(t.image); } + ( "." t= {s.append('.').append(t.image);} )* + {jjtThis.setImage(s.toString());} +} diff --git a/pmd-java/pom.xml b/pmd-java/pom.xml index 0480b7f79e4..e212339c441 100644 --- a/pmd-java/pom.xml +++ b/pmd-java/pom.xml @@ -7,13 +7,9 @@ net.sourceforge.pmd pmd - 6.0.0-SNAPSHOT + 6.10.0-SNAPSHOT - - ${basedir}/../pmd-core - - @@ -43,6 +39,11 @@ + + kotlin-maven-plugin + org.jetbrains.kotlin + + org.apache.maven.plugins maven-antrun-plugin @@ -111,10 +112,6 @@ - - jaxen - jaxen - net.java.dev.javacc javacc @@ -131,6 +128,14 @@ org.ow2.asm asm + + commons-io + commons-io + + + org.apache.commons + commons-lang3 + net.sourceforge.saxon @@ -139,6 +144,13 @@ runtime + + + + net.sourceforge.pmd + pmd-lang-test + test + net.sourceforge.pmd pmd-test @@ -149,10 +161,20 @@ slf4j-api test + + junit + junit + test + com.github.stefanbirkner system-rules test + + org.apache.ant + ant-testutil + test + diff --git a/pmd-java/src/main/ant/alljavacc.xml b/pmd-java/src/main/ant/alljavacc.xml index 45c05dac704..e1a80620f17 100644 --- a/pmd-java/src/main/ant/alljavacc.xml +++ b/pmd-java/src/main/ant/alljavacc.xml @@ -60,6 +60,7 @@ public class]]> + } ]]> - - public class Token - - + + public class Token implements java.io.Serializable + + + + + + public Token specialToken; + + diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/cpd/JavaLanguage.java b/pmd-java/src/main/java/net/sourceforge/pmd/cpd/JavaLanguage.java index 2f79767bd80..0ff3638c99d 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/cpd/JavaLanguage.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/cpd/JavaLanguage.java @@ -16,6 +16,7 @@ public JavaLanguage(Properties properties) { setProperties(properties); } + @Override public final void setProperties(Properties properties) { JavaTokenizer tokenizer = (JavaTokenizer) getTokenizer(); tokenizer.setProperties(properties); diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/cpd/JavaTokenizer.java b/pmd-java/src/main/java/net/sourceforge/pmd/cpd/JavaTokenizer.java index 0b62ca6adec..4ed923a31a3 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/cpd/JavaTokenizer.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/cpd/JavaTokenizer.java @@ -9,9 +9,11 @@ import java.util.LinkedList; import java.util.Properties; +import net.sourceforge.pmd.cpd.token.JavaCCTokenFilter; import net.sourceforge.pmd.lang.LanguageRegistry; import net.sourceforge.pmd.lang.LanguageVersionHandler; import net.sourceforge.pmd.lang.TokenManager; +import net.sourceforge.pmd.lang.ast.GenericToken; import net.sourceforge.pmd.lang.java.JavaLanguageModule; import net.sourceforge.pmd.lang.java.ast.JavaParserConstants; import net.sourceforge.pmd.lang.java.ast.Token; @@ -31,34 +33,30 @@ public void setProperties(Properties properties) { ignoreIdentifiers = Boolean.parseBoolean(properties.getProperty(IGNORE_IDENTIFIERS, "false")); } + @Override public void tokenize(SourceCode sourceCode, Tokens tokenEntries) { - StringBuilder stringBuilder = sourceCode.getCodeBuffer(); - - // Note that Java version is irrelevant for tokenizing - LanguageVersionHandler languageVersionHandler = LanguageRegistry.getLanguage(JavaLanguageModule.NAME) - .getVersion("1.4").getLanguageVersionHandler(); - String fileName = sourceCode.getFileName(); - TokenManager tokenMgr = languageVersionHandler.getParser(languageVersionHandler.getDefaultParserOptions()) - .getTokenManager(fileName, new StringReader(stringBuilder.toString())); - Token currentToken = (Token) tokenMgr.getNextToken(); - - TokenDiscarder discarder = new TokenDiscarder(ignoreAnnotations); - ConstructorDetector constructorDetector = new ConstructorDetector(ignoreIdentifiers); - - while (currentToken.image.length() > 0) { - discarder.updateState(currentToken); - - if (discarder.isDiscarding()) { - currentToken = (Token) tokenMgr.getNextToken(); - continue; - } + final String fileName = sourceCode.getFileName(); + final JavaTokenFilter tokenFilter = createTokenFilter(sourceCode); + final ConstructorDetector constructorDetector = new ConstructorDetector(ignoreIdentifiers); + Token currentToken = (Token) tokenFilter.getNextToken(); + while (currentToken != null) { processToken(tokenEntries, fileName, currentToken, constructorDetector); - currentToken = (Token) tokenMgr.getNextToken(); + currentToken = (Token) tokenFilter.getNextToken(); } tokenEntries.add(TokenEntry.getEOF()); } + private JavaTokenFilter createTokenFilter(final SourceCode sourceCode) { + final StringBuilder stringBuilder = sourceCode.getCodeBuffer(); + // Note that Java version is irrelevant for tokenizing + final LanguageVersionHandler languageVersionHandler = LanguageRegistry.getLanguage(JavaLanguageModule.NAME) + .getVersion("1.4").getLanguageVersionHandler(); + final TokenManager tokenMgr = languageVersionHandler.getParser(languageVersionHandler.getDefaultParserOptions()) + .getTokenManager(sourceCode.getFileName(), new StringReader(stringBuilder.toString())); + return new JavaTokenFilter(tokenMgr, ignoreAnnotations); + } + private void processToken(Tokens tokenEntries, String fileName, Token currentToken, ConstructorDetector constructorDetector) { String image = currentToken.image; @@ -93,15 +91,14 @@ public void setIgnoreAnnotations(boolean ignoreAnnotations) { } /** - * The {@link TokenDiscarder} consumes token by token and maintains state. - * It can detect, whether the current token belongs to an annotation and - * whether the current token should be discarded by CPD. + * The {@link JavaTokenFilter} extends the {@link JavaCCTokenFilter} to discard + * Java-specific tokens. *

* By default, it discards semicolons, package and import statements, and - * enables CPD suppression. Optionally, all annotations can be ignored, too. + * enables annotation-based CPD suppression. Optionally, all annotations can be ignored, too. *

*/ - private static class TokenDiscarder { + private static class JavaTokenFilter extends JavaCCTokenFilter { private boolean isAnnotation = false; private boolean nextTokenEndsAnnotation = false; private int annotationStack = 0; @@ -112,22 +109,24 @@ private static class TokenDiscarder { private boolean discardingAnnotations = false; private boolean ignoreAnnotations = false; - TokenDiscarder(boolean ignoreAnnotations) { + JavaTokenFilter(final TokenManager tokenManager, final boolean ignoreAnnotations) { + super(tokenManager); this.ignoreAnnotations = ignoreAnnotations; } - public void updateState(Token currentToken) { - detectAnnotations(currentToken); + @Override + protected void analyzeToken(final GenericToken currentToken) { + detectAnnotations((Token) currentToken); - skipSemicolon(currentToken); - skipPackageAndImport(currentToken); - skipCPDSuppression(currentToken); + skipSemicolon((Token) currentToken); + skipPackageAndImport((Token) currentToken); + skipAnnotationSuppression((Token) currentToken); if (ignoreAnnotations) { skipAnnotations(); } } - private void skipPackageAndImport(Token currentToken) { + private void skipPackageAndImport(final Token currentToken) { if (currentToken.kind == JavaParserConstants.PACKAGE || currentToken.kind == JavaParserConstants.IMPORT) { discardingKeywords = true; } else if (discardingKeywords && currentToken.kind == JavaParserConstants.SEMICOLON) { @@ -135,7 +134,7 @@ private void skipPackageAndImport(Token currentToken) { } } - private void skipSemicolon(Token currentToken) { + private void skipSemicolon(final Token currentToken) { if (currentToken.kind == JavaParserConstants.SEMICOLON) { discardingSemicolon = true; } else if (discardingSemicolon && currentToken.kind != JavaParserConstants.SEMICOLON) { @@ -143,21 +142,7 @@ private void skipSemicolon(Token currentToken) { } } - private void skipCPDSuppression(Token currentToken) { - // Check if a comment is altering the suppression state - Token st = currentToken.specialToken; - while (st != null) { - if (st.image.contains("CPD-OFF")) { - discardingSuppressing = true; - break; - } - if (st.image.contains("CPD-ON")) { - discardingSuppressing = false; - break; - } - st = st.specialToken; - } - + private void skipAnnotationSuppression(final Token currentToken) { // if processing an annotation, look for a CPD-START or CPD-END if (isAnnotation) { if (!discardingSuppressing && currentToken.kind == JavaParserConstants.STRING_LITERAL @@ -178,10 +163,10 @@ private void skipAnnotations() { } } - public boolean isDiscarding() { - boolean result = discardingSemicolon || discardingKeywords || discardingAnnotations + @Override + protected boolean isLanguageSpecificDiscarding() { + return discardingSemicolon || discardingKeywords || discardingAnnotations || discardingSuppressing; - return result; } private void detectAnnotations(Token currentToken) { diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/AbstractJavaHandler.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/AbstractJavaHandler.java index 6d9be6c0b07..725a4aae034 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/AbstractJavaHandler.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/AbstractJavaHandler.java @@ -12,20 +12,23 @@ import net.sourceforge.pmd.lang.VisitorStarter; import net.sourceforge.pmd.lang.XPathHandler; import net.sourceforge.pmd.lang.ast.Node; -import net.sourceforge.pmd.lang.ast.xpath.AbstractASTXPathHandler; +import net.sourceforge.pmd.lang.ast.xpath.DefaultASTXPathHandler; import net.sourceforge.pmd.lang.dfa.DFAGraphRule; import net.sourceforge.pmd.lang.java.ast.ASTCompilationUnit; import net.sourceforge.pmd.lang.java.ast.DumpFacade; import net.sourceforge.pmd.lang.java.ast.JavaNode; import net.sourceforge.pmd.lang.java.dfa.DataFlowFacade; import net.sourceforge.pmd.lang.java.dfa.JavaDFAGraphRule; -import net.sourceforge.pmd.lang.java.metrics.JavaMetricsVisitorFacade; +import net.sourceforge.pmd.lang.java.multifile.MultifileVisitorFacade; +import net.sourceforge.pmd.lang.java.qname.QualifiedNameResolver; import net.sourceforge.pmd.lang.java.rule.JavaRuleViolationFactory; import net.sourceforge.pmd.lang.java.symboltable.SymbolFacade; import net.sourceforge.pmd.lang.java.typeresolution.TypeResolutionFacade; import net.sourceforge.pmd.lang.java.xpath.GetCommentOnFunction; import net.sourceforge.pmd.lang.java.xpath.JavaFunctions; import net.sourceforge.pmd.lang.java.xpath.MetricFunction; +import net.sourceforge.pmd.lang.java.xpath.TypeIsExactlyFunction; +import net.sourceforge.pmd.lang.java.xpath.TypeIsFunction; import net.sourceforge.pmd.lang.java.xpath.TypeOfFunction; import net.sourceforge.pmd.lang.rule.RuleViolationFactory; @@ -46,19 +49,24 @@ public DataFlowHandler getDataFlowHandler() { @Override public XPathHandler getXPathHandler() { - return new AbstractASTXPathHandler() { + return new DefaultASTXPathHandler() { + @Override public void initialize() { TypeOfFunction.registerSelfInSimpleContext(); GetCommentOnFunction.registerSelfInSimpleContext(); MetricFunction.registerSelfInSimpleContext(); + TypeIsFunction.registerSelfInSimpleContext(); + TypeIsExactlyFunction.registerSelfInSimpleContext(); } + @Override public void initialize(IndependentContext context) { super.initialize(context, LanguageRegistry.getLanguage(JavaLanguageModule.NAME), JavaFunctions.class); } }; } + @Override public RuleViolationFactory getRuleViolationFactory() { return JavaRuleViolationFactory.INSTANCE; } @@ -66,6 +74,7 @@ public RuleViolationFactory getRuleViolationFactory() { @Override public VisitorStarter getDataFlowFacade() { return new VisitorStarter() { + @Override public void start(Node rootNode) { new DataFlowFacade().initializeWith(getDataFlowHandler(), (ASTCompilationUnit) rootNode); } @@ -75,6 +84,7 @@ public void start(Node rootNode) { @Override public VisitorStarter getSymbolFacade() { return new VisitorStarter() { + @Override public void start(Node rootNode) { new SymbolFacade().initializeWith(null, (ASTCompilationUnit) rootNode); } @@ -84,6 +94,7 @@ public void start(Node rootNode) { @Override public VisitorStarter getSymbolFacade(final ClassLoader classLoader) { return new VisitorStarter() { + @Override public void start(Node rootNode) { new SymbolFacade().initializeWith(classLoader, (ASTCompilationUnit) rootNode); } @@ -93,6 +104,7 @@ public void start(Node rootNode) { @Override public VisitorStarter getTypeResolutionFacade(final ClassLoader classLoader) { return new VisitorStarter() { + @Override public void start(Node rootNode) { new TypeResolutionFacade().initializeWith(classLoader, (ASTCompilationUnit) rootNode); } @@ -100,24 +112,37 @@ public void start(Node rootNode) { } @Override - public VisitorStarter getMetricsVisitorFacade() { + public VisitorStarter getDumpFacade(final Writer writer, final String prefix, final boolean recurse) { return new VisitorStarter() { @Override public void start(Node rootNode) { - new JavaMetricsVisitorFacade().initializeWith((ASTCompilationUnit) rootNode); + new DumpFacade().initializeWith(writer, prefix, recurse, (JavaNode) rootNode); } }; } @Override - public VisitorStarter getDumpFacade(final Writer writer, final String prefix, final boolean recurse) { + public VisitorStarter getMultifileFacade() { + return new VisitorStarter() { + @Override + public void start(Node rootNode) { + new MultifileVisitorFacade().initializeWith((ASTCompilationUnit) rootNode); + } + }; + } + + + @Override + public VisitorStarter getQualifiedNameResolutionFacade(final ClassLoader classLoader) { return new VisitorStarter() { + @Override public void start(Node rootNode) { - new DumpFacade().initializeWith(writer, prefix, recurse, (JavaNode) rootNode); + new QualifiedNameResolver().initializeWith(classLoader, (ASTCompilationUnit) rootNode); } }; } + @Override public DFAGraphRule getDFAGraphRule() { return new JavaDFAGraphRule(); diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/AbstractJavaParser.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/AbstractJavaParser.java index a34e113b7fe..d1537603700 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/AbstractJavaParser.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/AbstractJavaParser.java @@ -48,15 +48,18 @@ protected JavaParser createJavaParser(Reader source) throws ParseException { return parser; } + @Override public boolean canParse() { return true; } + @Override public Node parse(String fileName, Reader source) throws ParseException { AbstractTokenManager.setFileName(fileName); return createJavaParser(source).CompilationUnit(); } + @Override public Map getSuppressMap() { return parser.getSuppressMap(); } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/Java13Handler.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/Java13Handler.java deleted file mode 100644 index 14322623d3f..00000000000 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/Java13Handler.java +++ /dev/null @@ -1,15 +0,0 @@ -/** - * BSD-style license; for more info see http://pmd.sourceforge.net/license.html - */ - -package net.sourceforge.pmd.lang.java; - -import net.sourceforge.pmd.lang.Parser; -import net.sourceforge.pmd.lang.ParserOptions; - -public class Java13Handler extends AbstractJavaHandler { - - public Parser getParser(ParserOptions parserOptions) { - return new Java13Parser(parserOptions); - } -} diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/Java13Parser.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/Java13Parser.java deleted file mode 100644 index cdcaec1e70c..00000000000 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/Java13Parser.java +++ /dev/null @@ -1,30 +0,0 @@ -/** - * BSD-style license; for more info see http://pmd.sourceforge.net/license.html - */ - -package net.sourceforge.pmd.lang.java; - -import java.io.Reader; - -import net.sourceforge.pmd.lang.ParserOptions; -import net.sourceforge.pmd.lang.java.ast.JavaParser; -import net.sourceforge.pmd.lang.java.ast.ParseException; - -/** - * Adapter for the JavaParser, using Java 1.3 grammar. - * - * @author Pieter_Van_Raemdonck - Application Engineers NV/SA - www.ae.be - */ -public class Java13Parser extends AbstractJavaParser { - - public Java13Parser(ParserOptions parserOptions) { - super(parserOptions); - } - - @Override - protected JavaParser createJavaParser(Reader source) throws ParseException { - JavaParser javaParser = super.createJavaParser(source); - javaParser.setJdkVersion(3); - return javaParser; - } -} diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/Java14Handler.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/Java14Handler.java deleted file mode 100644 index 189ef56f2e1..00000000000 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/Java14Handler.java +++ /dev/null @@ -1,15 +0,0 @@ -/** - * BSD-style license; for more info see http://pmd.sourceforge.net/license.html - */ - -package net.sourceforge.pmd.lang.java; - -import net.sourceforge.pmd.lang.Parser; -import net.sourceforge.pmd.lang.ParserOptions; - -public class Java14Handler extends AbstractJavaHandler { - - public Parser getParser(ParserOptions parserOptions) { - return new Java14Parser(parserOptions); - } -} diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/Java14Parser.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/Java14Parser.java deleted file mode 100644 index caf2b4de7f4..00000000000 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/Java14Parser.java +++ /dev/null @@ -1,30 +0,0 @@ -/** - * BSD-style license; for more info see http://pmd.sourceforge.net/license.html - */ - -package net.sourceforge.pmd.lang.java; - -import java.io.Reader; - -import net.sourceforge.pmd.lang.ParserOptions; -import net.sourceforge.pmd.lang.java.ast.JavaParser; -import net.sourceforge.pmd.lang.java.ast.ParseException; - -/** - * Adapter for the JavaParser, using Java 1.4 grammar. - * - * @author Pieter_Van_Raemdonck - Application Engineers NV/SA - www.ae.be - */ -public class Java14Parser extends AbstractJavaParser { - - public Java14Parser(ParserOptions parserOptions) { - super(parserOptions); - } - - @Override - protected JavaParser createJavaParser(Reader source) throws ParseException { - JavaParser javaParser = super.createJavaParser(source); - javaParser.setJdkVersion(4); - return javaParser; - } -} diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/Java15Handler.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/Java15Handler.java deleted file mode 100644 index 6e858d77c46..00000000000 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/Java15Handler.java +++ /dev/null @@ -1,15 +0,0 @@ -/** - * BSD-style license; for more info see http://pmd.sourceforge.net/license.html - */ - -package net.sourceforge.pmd.lang.java; - -import net.sourceforge.pmd.lang.Parser; -import net.sourceforge.pmd.lang.ParserOptions; - -public class Java15Handler extends AbstractJavaHandler { - - public Parser getParser(ParserOptions parserOptions) { - return new Java15Parser(parserOptions); - } -} diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/Java15Parser.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/Java15Parser.java deleted file mode 100644 index fe5e687c5a8..00000000000 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/Java15Parser.java +++ /dev/null @@ -1,30 +0,0 @@ -/** - * BSD-style license; for more info see http://pmd.sourceforge.net/license.html - */ - -package net.sourceforge.pmd.lang.java; - -import java.io.Reader; - -import net.sourceforge.pmd.lang.ParserOptions; -import net.sourceforge.pmd.lang.java.ast.JavaParser; -import net.sourceforge.pmd.lang.java.ast.ParseException; - -/** - * Adapter for the JavaParser, using Java 1.5 grammar. - * - * @author Pieter_Van_Raemdonck - Application Engineers NV/SA - www.ae.be - */ -public class Java15Parser extends AbstractJavaParser { - - public Java15Parser(ParserOptions parserOptions) { - super(parserOptions); - } - - @Override - protected JavaParser createJavaParser(Reader source) throws ParseException { - JavaParser javaParser = super.createJavaParser(source); - javaParser.setJdkVersion(5); - return javaParser; - } -} diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/Java16Handler.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/Java16Handler.java deleted file mode 100644 index 5f207e5ed07..00000000000 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/Java16Handler.java +++ /dev/null @@ -1,15 +0,0 @@ -/** - * BSD-style license; for more info see http://pmd.sourceforge.net/license.html - */ - -package net.sourceforge.pmd.lang.java; - -import net.sourceforge.pmd.lang.Parser; -import net.sourceforge.pmd.lang.ParserOptions; - -public class Java16Handler extends AbstractJavaHandler { - - public Parser getParser(ParserOptions parserOptions) { - return new Java16Parser(parserOptions); - } -} diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/Java16Parser.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/Java16Parser.java deleted file mode 100644 index 3f3dc4b0ad2..00000000000 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/Java16Parser.java +++ /dev/null @@ -1,28 +0,0 @@ -/** - * BSD-style license; for more info see http://pmd.sourceforge.net/license.html - */ - -package net.sourceforge.pmd.lang.java; - -import java.io.Reader; - -import net.sourceforge.pmd.lang.ParserOptions; -import net.sourceforge.pmd.lang.java.ast.JavaParser; -import net.sourceforge.pmd.lang.java.ast.ParseException; - -/** - * Adapter for the JavaParser, using Java 1.6 grammar. - */ -public class Java16Parser extends AbstractJavaParser { - - public Java16Parser(ParserOptions parserOptions) { - super(parserOptions); - } - - @Override - protected JavaParser createJavaParser(Reader source) throws ParseException { - JavaParser javaParser = super.createJavaParser(source); - javaParser.setJdkVersion(6); - return javaParser; - } -} diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/Java17Handler.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/Java17Handler.java deleted file mode 100644 index 3f9f292b61d..00000000000 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/Java17Handler.java +++ /dev/null @@ -1,15 +0,0 @@ -/** - * BSD-style license; for more info see http://pmd.sourceforge.net/license.html - */ - -package net.sourceforge.pmd.lang.java; - -import net.sourceforge.pmd.lang.Parser; -import net.sourceforge.pmd.lang.ParserOptions; - -public class Java17Handler extends AbstractJavaHandler { - - public Parser getParser(ParserOptions parserOptions) { - return new Java17Parser(parserOptions); - } -} diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/Java17Parser.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/Java17Parser.java deleted file mode 100644 index e85f7c0e94d..00000000000 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/Java17Parser.java +++ /dev/null @@ -1,28 +0,0 @@ -/** - * BSD-style license; for more info see http://pmd.sourceforge.net/license.html - */ - -package net.sourceforge.pmd.lang.java; - -import java.io.Reader; - -import net.sourceforge.pmd.lang.ParserOptions; -import net.sourceforge.pmd.lang.java.ast.JavaParser; -import net.sourceforge.pmd.lang.java.ast.ParseException; - -/** - * Adapter for the JavaParser, using Java 1.7 grammar. - */ -public class Java17Parser extends AbstractJavaParser { - - public Java17Parser(ParserOptions parserOptions) { - super(parserOptions); - } - - @Override - protected JavaParser createJavaParser(Reader source) throws ParseException { - JavaParser javaParser = super.createJavaParser(source); - javaParser.setJdkVersion(7); - return javaParser; - } -} diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/Java18Handler.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/Java18Handler.java deleted file mode 100644 index a049d743bbb..00000000000 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/Java18Handler.java +++ /dev/null @@ -1,15 +0,0 @@ -/** - * BSD-style license; for more info see http://pmd.sourceforge.net/license.html - */ - -package net.sourceforge.pmd.lang.java; - -import net.sourceforge.pmd.lang.Parser; -import net.sourceforge.pmd.lang.ParserOptions; - -public class Java18Handler extends AbstractJavaHandler { - - public Parser getParser(ParserOptions parserOptions) { - return new Java18Parser(parserOptions); - } -} diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/Java18Parser.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/Java18Parser.java deleted file mode 100644 index c98214b154f..00000000000 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/Java18Parser.java +++ /dev/null @@ -1,28 +0,0 @@ -/** - * BSD-style license; for more info see http://pmd.sourceforge.net/license.html - */ - -package net.sourceforge.pmd.lang.java; - -import java.io.Reader; - -import net.sourceforge.pmd.lang.ParserOptions; -import net.sourceforge.pmd.lang.java.ast.JavaParser; -import net.sourceforge.pmd.lang.java.ast.ParseException; - -/** - * Adapter for the JavaParser, using Java 1.8 grammar. - */ -public class Java18Parser extends AbstractJavaParser { - - public Java18Parser(ParserOptions parserOptions) { - super(parserOptions); - } - - @Override - protected JavaParser createJavaParser(Reader source) throws ParseException { - JavaParser javaParser = super.createJavaParser(source); - javaParser.setJdkVersion(8); - return javaParser; - } -} diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/JavaDataFlowHandler.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/JavaDataFlowHandler.java index 347aa2010c0..cccfad16845 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/JavaDataFlowHandler.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/JavaDataFlowHandler.java @@ -13,10 +13,12 @@ import net.sourceforge.pmd.lang.java.dfa.JavaDataFlowNode; public class JavaDataFlowHandler implements DataFlowHandler { + @Override public DataFlowNode createDataFlowNode(List dataFlow, Node node) { return new JavaDataFlowNode(dataFlow, node); } + @Override public Class getLabelStatementNodeClass() { return ASTLabeledStatement.class; } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/JavaLanguageHandler.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/JavaLanguageHandler.java new file mode 100644 index 00000000000..ff594dcd7e6 --- /dev/null +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/JavaLanguageHandler.java @@ -0,0 +1,21 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java; + +import net.sourceforge.pmd.lang.Parser; +import net.sourceforge.pmd.lang.ParserOptions; + +public class JavaLanguageHandler extends AbstractJavaHandler { + private final int jdkVersion; + + public JavaLanguageHandler(int jdkVersion) { + this.jdkVersion = jdkVersion; + } + + @Override + public Parser getParser(ParserOptions parserOptions) { + return new JavaLanguageParser(jdkVersion, parserOptions); + } +} 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 60a5c43d106..f5fb17220a5 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 @@ -17,12 +17,15 @@ public class JavaLanguageModule extends BaseLanguageModule { public JavaLanguageModule() { super(NAME, null, TERSE_NAME, JavaRuleChainVisitor.class, "java"); - addVersion("1.3", new Java13Handler(), false); - addVersion("1.4", new Java14Handler(), false); - addVersion("1.5", new Java15Handler(), false); - addVersion("1.6", new Java16Handler(), false); - addVersion("1.7", new Java17Handler(), false); - addVersion("1.8", new Java18Handler(), true); + addVersion("1.3", new JavaLanguageHandler(3), false); + addVersion("1.4", new JavaLanguageHandler(4), false); + addVersion("1.5", new JavaLanguageHandler(5), false); + addVersion("1.6", new JavaLanguageHandler(6), false); + 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), false); + addVersion("11", new JavaLanguageHandler(11), true); } } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/JavaLanguageParser.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/JavaLanguageParser.java new file mode 100644 index 00000000000..0cdb9c3c6d8 --- /dev/null +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/JavaLanguageParser.java @@ -0,0 +1,33 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java; + +import java.io.Reader; + +import net.sourceforge.pmd.lang.ParserOptions; +import net.sourceforge.pmd.lang.java.ast.JavaParser; +import net.sourceforge.pmd.lang.java.ast.ParseException; + +/** + * Adapter for the JavaParser, using the specified grammar version. + * + * @author Pieter_Van_Raemdonck - Application Engineers NV/SA - www.ae.be + * @author Andreas Dangel + */ +public class JavaLanguageParser extends AbstractJavaParser { + private final int jdkVersion; + + public JavaLanguageParser(int jdkVersion, ParserOptions parserOptions) { + super(parserOptions); + this.jdkVersion = jdkVersion; + } + + @Override + protected JavaParser createJavaParser(Reader source) throws ParseException { + JavaParser javaParser = super.createJavaParser(source); + javaParser.setJdkVersion(jdkVersion); + return javaParser; + } +} diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/JavaTokenManager.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/JavaTokenManager.java index 7b4eb24a6e4..8b579d270a5 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/JavaTokenManager.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/JavaTokenManager.java @@ -20,10 +20,12 @@ public JavaTokenManager(Reader source) { tokenManager = new JavaParserTokenManager(new JavaCharStream(source)); } + @Override public Object getNextToken() { return tokenManager.getNextToken(); } + @Override public void setFileName(String fileName) { tokenManager.setFileName(fileName); } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTAdditiveExpression.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTAdditiveExpression.java index 5c62af94616..2e7d65ba2c3 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTAdditiveExpression.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTAdditiveExpression.java @@ -5,6 +5,20 @@ package net.sourceforge.pmd.lang.java.ast; +/** + * Represents an addition operation on two or more values, or string concatenation. + * This has a precedence greater than {@link ASTShiftExpression}, and lower + * than {@link ASTMultiplicativeExpression}. + * + *

Note that the children of this node are not necessarily {@link ASTMultiplicativeExpression}, + * rather, they are expressions with an operator precedence greater or equal to MultiplicativeExpression. + * + *

+ *
+ * AdditiveExpression ::= {@linkplain ASTMultiplicativeExpression MultiplicativeExpression} ( ( "+" | "-" ) {@linkplain ASTMultiplicativeExpression MultiplicativeExpression} )+
+ *
+ * 
+ */ public class ASTAdditiveExpression extends AbstractJavaTypeNode { public ASTAdditiveExpression(int id) { super(id); @@ -14,11 +28,16 @@ public ASTAdditiveExpression(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 image of the operator, i.e. "+" or "-". + */ + public String getOperator() { + return getImage(); + } } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTAllocationExpression.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTAllocationExpression.java index fbf7293a617..3ea7ef08ec2 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTAllocationExpression.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTAllocationExpression.java @@ -5,7 +5,13 @@ package net.sourceforge.pmd.lang.java.ast; -public class ASTAllocationExpression extends AbstractJavaTypeNode { +import net.sourceforge.pmd.lang.java.qname.JavaTypeQualifiedName; + + +public class ASTAllocationExpression extends AbstractJavaTypeNode implements JavaQualifiableNode { + + private JavaTypeQualifiedName qualifiedName; + public ASTAllocationExpression(int id) { super(id); } @@ -17,7 +23,39 @@ public ASTAllocationExpression(JavaParser p, int id) { /** * Accept the visitor. * */ + @Override public Object jjtAccept(JavaParserVisitor visitor, Object data) { return visitor.visit(this, data); } + + /** + * Returns true if this expression defines a body, + * which is compiled to an anonymous class. If this + * method returns false, then {@link #getQualifiedName()} + * returns {@code null}. + */ + public boolean isAnonymousClass() { + if (jjtGetNumChildren() > 1) { + // check the last child + return jjtGetChild(jjtGetNumChildren() - 1) instanceof ASTClassOrInterfaceBody; + } + return false; + } + + /** + * Gets the qualified name of the anonymous class + * declared by this node, or null if this node + * doesn't declare any. + * + * @see #isAnonymousClass() + */ + @Override + public JavaTypeQualifiedName getQualifiedName() { + return qualifiedName; + } + + public void setQualifiedName(JavaTypeQualifiedName qname) { + this.qualifiedName = qname; + } + } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTAndExpression.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTAndExpression.java index 586d27d5c4d..7ef18ae1e49 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTAndExpression.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTAndExpression.java @@ -5,6 +5,21 @@ package net.sourceforge.pmd.lang.java.ast; +/** + * Represents a non-shortcut boolean AND-expression. + * This has a precedence greater than {@link ASTExclusiveOrExpression}, + * and lower than {@link ASTEqualityExpression}. + * + *

Note that the children of this node are not necessarily {@link ASTEqualityExpression}, + * rather, they are expressions with an operator precedence greater or equal to EqualityExpression. + * + * + *

+ *
+ * AndExpression ::=  {@linkplain ASTEqualityExpression EqualityExpression} ( "&" {@linkplain ASTEqualityExpression EqualityExpression} )+
+ *
+ * 
+ */ public class ASTAndExpression extends AbstractJavaTypeNode { public ASTAndExpression(int id) { super(id); @@ -14,9 +29,7 @@ public ASTAndExpression(JavaParser p, int id) { super(p, id); } - /** - * Accept the visitor. * - */ + @Override public Object jjtAccept(JavaParserVisitor visitor, Object data) { return visitor.visit(this, data); } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTAnnotation.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTAnnotation.java index ea742fa096c..4c083b8c7fe 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTAnnotation.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTAnnotation.java @@ -9,15 +9,27 @@ import java.util.List; import net.sourceforge.pmd.Rule; -import net.sourceforge.pmd.lang.ast.Node; -public class ASTAnnotation extends AbstractJavaNode { - private static List unusedRules = Arrays.asList(new String[] { "UnusedPrivateField", "UnusedLocalVariable", - "UnusedPrivateMethod", "UnusedFormalParameter", }); +/** + * Represents an annotation. This node has three possible children, + * that correspond to specific syntactic variants. + * + *
+ *
+ * Annotation ::= {@linkplain ASTNormalAnnotation NormalAnnotation}
+ *              | {@linkplain ASTSingleMemberAnnotation SingleMemberAnnotation}
+ *              | {@linkplain ASTMarkerAnnotation MarkerAnnotation}
+ *
+ * 
+ * + */ +public class ASTAnnotation extends AbstractJavaTypeNode { + + private static final List UNUSED_RULES + = Arrays.asList("UnusedPrivateField", "UnusedLocalVariable", "UnusedPrivateMethod", "UnusedFormalParameter"); - private static List serialRules = Arrays - .asList(new String[] { "BeanMembersShouldSerialize", "MissingSerialVersionUID" }); + private static final List SERIAL_RULES = Arrays.asList("BeanMembersShouldSerialize", "MissingSerialVersionUID"); public ASTAnnotation(int id) { super(id); @@ -27,43 +39,66 @@ public ASTAnnotation(JavaParser p, int id) { super(p, id); } + + /** + * Returns the name of the annotation as it is used, + * eg {@code java.lang.Override} or {@code Override}. + */ + public String getAnnotationName() { + return jjtGetChild(0).jjtGetChild(0).getImage(); + } + + // @formatter:off + /** + * Returns true if this annotation suppresses the given rule. + * The suppression annotation is {@link SuppressWarnings}. + * This method returns true if this annotation is a SuppressWarnings, + * and if the set of suppressed warnings ({@link SuppressWarnings#value()}) + * contains at least one of those: + *
    + *
  • "PMD" (suppresses all rules); + *
  • "PMD.rulename", where rulename is the name of the given rule; + *
  • "all" (conventional value to suppress all warnings). + *
+ * + *

Additionnally, the following values suppress a specific set of rules: + *

    + *
  • {@code "unused"}: suppresses rules like UnusedLocalVariable or UnusedPrivateField; + *
  • {@code "serial"}: suppresses BeanMembersShouldSerialize and MissingSerialVersionUID; + *
+ * + * @param rule The rule for which to check for suppression + * + * @return True if this annotation suppresses the given rule + */ + // @formatter:on public boolean suppresses(Rule rule) { - final String ruleAnno = "\"PMD." + rule.getName() + "\""; - - if (jjtGetChild(0) instanceof ASTSingleMemberAnnotation) { - ASTSingleMemberAnnotation n = (ASTSingleMemberAnnotation) jjtGetChild(0); - return checkAnnototation(n, ruleAnno, rule); - } else if (jjtGetChild(0) instanceof ASTNormalAnnotation) { - ASTNormalAnnotation n = (ASTNormalAnnotation) jjtGetChild(0); - return checkAnnototation(n, ruleAnno, rule); + + if (jjtGetChild(0) instanceof ASTMarkerAnnotation) { + return false; } - return false; - } - private boolean checkAnnototation(Node n, String ruleAnno, Rule rule) { - if (n.jjtGetChild(0) instanceof ASTName) { - ASTName annName = (ASTName) n.jjtGetChild(0); - - if ("SuppressWarnings".equals(annName.getImage()) - || "java.lang.SuppressWarnings".equals(annName.getImage())) { - List nodes = n.findDescendantsOfType(ASTLiteral.class); - for (ASTLiteral element : nodes) { - if (element.hasImageEqualTo("\"PMD\"") || element.hasImageEqualTo(ruleAnno) - // Check for standard annotations values - || element.hasImageEqualTo("\"all\"") - || element.hasImageEqualTo("\"serial\"") && serialRules.contains(rule.getName()) - || element.hasImageEqualTo("\"unused\"") && unusedRules.contains(rule.getName())) { - return true; - } + // if (SuppressWarnings.class.equals(getType())) { // typeres is not always on + if (isSuppressWarnings()) { + for (ASTLiteral element : findDescendantsOfType(ASTLiteral.class)) { + if (element.hasImageEqualTo("\"PMD\"") || element.hasImageEqualTo("\"PMD." + rule.getName() + "\"") + // Check for standard annotations values + || element.hasImageEqualTo("\"all\"") + || element.hasImageEqualTo("\"serial\"") && SERIAL_RULES.contains(rule.getName()) + || element.hasImageEqualTo("\"unused\"") && UNUSED_RULES.contains(rule.getName())) { + return true; } } } + return false; } - /** - * Accept the visitor. - */ + + private boolean isSuppressWarnings() { + return "SuppressWarnings".equals(getAnnotationName()) || "java.lang.SuppressWarnings".equals(getAnnotationName()); + } + @Override public Object jjtAccept(JavaParserVisitor visitor, Object data) { return visitor.visit(this, data); diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTAnnotationMethodDeclaration.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTAnnotationMethodDeclaration.java index 4681413195f..9b59f1d688b 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTAnnotationMethodDeclaration.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTAnnotationMethodDeclaration.java @@ -6,7 +6,7 @@ package net.sourceforge.pmd.lang.java.ast; -public class ASTAnnotationMethodDeclaration extends AbstractJavaAccessNode { +public class ASTAnnotationMethodDeclaration extends AbstractMethodLikeNode { public ASTAnnotationMethodDeclaration(int id) { super(id); } @@ -16,9 +16,16 @@ public ASTAnnotationMethodDeclaration(JavaParser p, int id) { } /** Accept the visitor. **/ + @Override public Object jjtAccept(JavaParserVisitor visitor, Object data) { return visitor.visit(this, data); } + + + @Override + public MethodLikeKind getKind() { + return MethodLikeKind.METHOD; + } } /* * JavaCC - OriginalChecksum=f6dd440446f8aa5c9c191ae760080ee0 (do not edit this diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTAnnotationTypeBody.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTAnnotationTypeBody.java index d6da45cc3c7..cf98a6be870 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTAnnotationTypeBody.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTAnnotationTypeBody.java @@ -17,6 +17,7 @@ public ASTAnnotationTypeBody(JavaParser p, int id) { /** * Accept the visitor. * */ + @Override public Object jjtAccept(JavaParserVisitor visitor, Object data) { return visitor.visit(this, data); } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTAnnotationTypeDeclaration.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTAnnotationTypeDeclaration.java index 88ca621ac69..77d33ded7dc 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTAnnotationTypeDeclaration.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTAnnotationTypeDeclaration.java @@ -7,9 +7,8 @@ import java.util.List; -public class ASTAnnotationTypeDeclaration extends AbstractJavaAccessTypeNode implements ASTAnyTypeDeclaration { +public class ASTAnnotationTypeDeclaration extends AbstractAnyTypeDeclaration { - private JavaQualifiedName qualifiedName; public ASTAnnotationTypeDeclaration(int id) { super(id); @@ -27,26 +26,6 @@ public Object jjtAccept(JavaParserVisitor visitor, Object data) { return visitor.visit(this, data); } - public boolean isNested() { - return jjtGetParent() instanceof ASTClassOrInterfaceBodyDeclaration - || jjtGetParent() instanceof ASTAnnotationTypeMemberDeclaration; - } - - @Override - public JavaQualifiedName getQualifiedName() { - if (qualifiedName == null) { - if (isNested()) { - ASTAnyTypeDeclaration parent = this.getFirstParentOfType(ASTAnyTypeDeclaration.class); - JavaQualifiedName parentQN = parent.getQualifiedName(); - qualifiedName = JavaQualifiedName.ofNestedClass(parentQN, this.getImage()); - return qualifiedName; - } - - qualifiedName = JavaQualifiedName.ofOuterClass(this); - } - - return qualifiedName; - } @Override public TypeKind getTypeKind() { diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTAnnotationTypeMemberDeclaration.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTAnnotationTypeMemberDeclaration.java index 313da57d783..328f06ec887 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTAnnotationTypeMemberDeclaration.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTAnnotationTypeMemberDeclaration.java @@ -5,7 +5,7 @@ package net.sourceforge.pmd.lang.java.ast; -public class ASTAnnotationTypeMemberDeclaration extends AbstractJavaNode implements ASTAnyTypeBodyDeclaration { +public class ASTAnnotationTypeMemberDeclaration extends AbstractTypeBodyDeclaration { public ASTAnnotationTypeMemberDeclaration(int id) { super(id); } @@ -17,6 +17,7 @@ public ASTAnnotationTypeMemberDeclaration(JavaParser p, int id) { /** * Accept the visitor. * */ + @Override public Object jjtAccept(JavaParserVisitor visitor, Object data) { return visitor.visit(this, data); } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTAnyTypeBodyDeclaration.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTAnyTypeBodyDeclaration.java index 251b9bbd326..c2a8c56445e 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTAnyTypeBodyDeclaration.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTAnyTypeBodyDeclaration.java @@ -10,4 +10,47 @@ * @author Clément Fournier */ public interface ASTAnyTypeBodyDeclaration extends JavaNode { + + + /** + * Returns the child of this declaration, + * which can be cast to a more specific node + * type using {@link #getKind()} as a cue. + * + *

Returns null if this is an empty declaration, + * that is, a single semicolon. + */ + JavaNode getDeclarationNode(); + + /** + * Gets the kind of declaration this node contains. + * This is a cue for the node type the child of this + * declaration can be cast to. + */ + DeclarationKind getKind(); + + /** Kind of declaration. */ + enum DeclarationKind { + /** See {@link ASTInitializer}. */ + INITIALIZER, + /** See {@link ASTConstructorDeclaration}. */ + CONSTRUCTOR, + /** See {@link ASTMethodDeclaration}. */ + METHOD, + /** See {@link ASTFieldDeclaration}. */ + FIELD, + /** See {@link ASTAnnotationMethodDeclaration}. */ + ANNOTATION_METHOD, + /** See {@link ASTClassOrInterfaceDeclaration}. */ + CLASS, + /** See {@link ASTEnumDeclaration}. */ + ENUM, + /** See {@link ASTClassOrInterfaceDeclaration}. */ + INTERFACE, + /** See {@link ASTAnnotationTypeDeclaration}. */ + ANNOTATION, + /** No child, {@link #getDeclarationNode()} will return null. */ + EMPTY + } + } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTAnyTypeDeclaration.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTAnyTypeDeclaration.java index a7c999de974..124c8e18d26 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTAnyTypeDeclaration.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTAnyTypeDeclaration.java @@ -5,13 +5,17 @@ package net.sourceforge.pmd.lang.java.ast; import java.util.List; +import java.util.Locale; + +import net.sourceforge.pmd.lang.java.qname.JavaTypeQualifiedName; + /** - * Groups enums, classes and interface declarations. + * Groups enum, class, annotation and interface declarations. * * @author Clément Fournier */ -public interface ASTAnyTypeDeclaration extends JavaQualifiableNode, AccessNode, JavaNode { +public interface ASTAnyTypeDeclaration extends TypeNode, JavaQualifiableNode, AccessNode, JavaNode { /** * Finds the type kind of this declaration. @@ -29,11 +33,26 @@ public interface ASTAnyTypeDeclaration extends JavaQualifiableNode, AccessNode, List getDeclarations(); + + @Override + JavaTypeQualifiedName getQualifiedName(); + + + /** + * Returns true if this type declaration is nested inside an interface, class or annotation. + */ + boolean isNested(); + /** * The kind of type this node declares. */ enum TypeKind { - CLASS, INTERFACE, ENUM, ANNOTATION + CLASS, INTERFACE, ENUM, ANNOTATION; + + + public String getPrintableName() { + return name().toLowerCase(Locale.ROOT); + } } } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTArgumentList.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTArgumentList.java index 8b4f6fbfbb1..cc2faea91ae 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTArgumentList.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTArgumentList.java @@ -17,6 +17,7 @@ public ASTArgumentList(JavaParser p, int id) { /** * Accept the visitor. * */ + @Override public Object jjtAccept(JavaParserVisitor visitor, Object data) { return visitor.visit(this, data); } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTArguments.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTArguments.java index d0898b5ea43..a2edc5094fd 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTArguments.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTArguments.java @@ -24,6 +24,7 @@ public int getArgumentCount() { /** * Accept the visitor. * */ + @Override public Object jjtAccept(JavaParserVisitor visitor, Object data) { return visitor.visit(this, data); } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTArrayDimsAndInits.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTArrayDimsAndInits.java index e3986bc2d1d..de1035272b2 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTArrayDimsAndInits.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTArrayDimsAndInits.java @@ -5,7 +5,9 @@ package net.sourceforge.pmd.lang.java.ast; -public class ASTArrayDimsAndInits extends AbstractJavaNode { +public class ASTArrayDimsAndInits extends AbstractJavaNode implements Dimensionable { + private int arrayDepth; + public ASTArrayDimsAndInits(int id) { super(id); } @@ -17,7 +19,22 @@ public ASTArrayDimsAndInits(JavaParser p, int id) { /** * Accept the visitor. * */ + @Override public Object jjtAccept(JavaParserVisitor visitor, Object data) { return visitor.visit(this, data); } + + public void bumpArrayDepth() { + arrayDepth++; + } + + @Override + public int getArrayDepth() { + return arrayDepth; + } + + @Override + public boolean isArray() { + return arrayDepth > 0; // should always be true... + } } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTArrayInitializer.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTArrayInitializer.java index 274b6ece084..d4f28d307c1 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTArrayInitializer.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTArrayInitializer.java @@ -17,6 +17,7 @@ public ASTArrayInitializer(JavaParser p, int id) { /** * Accept the visitor. * */ + @Override public Object jjtAccept(JavaParserVisitor visitor, Object data) { return visitor.visit(this, data); } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTAssertStatement.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTAssertStatement.java index 5ab7c6e0b6a..063a3455e69 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTAssertStatement.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTAssertStatement.java @@ -5,6 +5,15 @@ package net.sourceforge.pmd.lang.java.ast; +/** + * Represents an {@code assert} statement. + * + *

+ *
+ * AssertStatement ::= "assert" {@linkplain ASTExpression Expression} ( ":" {@linkplain ASTExpression Expression} )? ";"
+ *
+ * 
+ */ public class ASTAssertStatement extends AbstractJavaNode { public ASTAssertStatement(int id) { super(id); @@ -14,10 +23,36 @@ public ASTAssertStatement(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 expression tested by this assert statement. + */ + public ASTExpression getGuardExpressionNode() { + return (ASTExpression) jjtGetChild(0); + } + + + /** + * Returns true if this assert statement has a "detail message" + * expression. In that case, {@link #getDetailMessageNode()} doesn't + * return null. + */ + public boolean hasDetailMessage() { + return jjtGetNumChildren() == 2; + } + + + /** + * Returns the expression that corresponds to the detail message, + * i.e. the expression after the colon, if it's present. + */ + public ASTExpression getDetailMessageNode() { + return hasDetailMessage() ? (ASTExpression) jjtGetChild(1) : null; + } + } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTAssignmentOperator.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTAssignmentOperator.java index 14ae47a9b8e..7d7dcf4b590 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTAssignmentOperator.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTAssignmentOperator.java @@ -5,6 +5,15 @@ package net.sourceforge.pmd.lang.java.ast; +/** + * Represents an assignment operator in an {@linkplain ASTExpression assignment expression}. + * + *
+ *
+ *  AssignmentOperator ::= "=" | "*=" | "/=" | "%=" | "+=" | "-=" | "<<=" | ">>=" | ">>>=" | "&=" | "^=" | "|="
+ *
+ * 
+ */ public class ASTAssignmentOperator extends AbstractJavaNode { private boolean isCompound; @@ -16,6 +25,7 @@ public ASTAssignmentOperator(JavaParser p, int id) { super(p, id); } + // TODO this could be determined from the image of the operator, no need to set it in the parser... public void setCompound() { isCompound = true; } @@ -24,9 +34,6 @@ public boolean isCompound() { return this.isCompound; } - /** - * Accept the visitor. * - */ @Override public Object jjtAccept(JavaParserVisitor visitor, Object data) { return visitor.visit(this, data); diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTBlockStatement.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTBlockStatement.java index 5ff61240eda..751adccf055 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTBlockStatement.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTBlockStatement.java @@ -17,6 +17,7 @@ public ASTBlockStatement(JavaParser p, int id) { /** * Accept the visitor. * */ + @Override public Object jjtAccept(JavaParserVisitor visitor, Object data) { return visitor.visit(this, data); } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTBreakStatement.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTBreakStatement.java index 61ca9c4fd63..1076b9a8034 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTBreakStatement.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTBreakStatement.java @@ -17,6 +17,7 @@ public ASTBreakStatement(JavaParser p, int id) { /** * Accept the visitor. * */ + @Override public Object jjtAccept(JavaParserVisitor visitor, Object data) { return visitor.visit(this, data); } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTCastExpression.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTCastExpression.java index d432ba5d302..1191ffa9281 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTCastExpression.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTCastExpression.java @@ -27,6 +27,7 @@ public boolean hasIntersectionTypes() { /** * Accept the visitor. * */ + @Override public Object jjtAccept(JavaParserVisitor visitor, Object data) { return visitor.visit(this, data); } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTCatchStatement.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTCatchStatement.java index 1b578434a0e..200a2bbc513 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTCatchStatement.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTCatchStatement.java @@ -5,6 +5,16 @@ package net.sourceforge.pmd.lang.java.ast; +import java.util.ArrayList; +import java.util.List; + + +/** + * Catch statement node. + *
+ *      "catch" "(" FormalParameter ")" Block
+ * 
+ */ public class ASTCatchStatement extends AbstractJavaNode { public ASTCatchStatement(int id) { super(id); @@ -14,10 +24,64 @@ public ASTCatchStatement(JavaParser p, int id) { super(p, id); } - /** - * Accept the visitor. * - */ + @Override public Object jjtAccept(JavaParserVisitor visitor, Object data) { return visitor.visit(this, data); } + + + /** + * Returns true if this node is a multi-catch statement, + * that is, it catches several unrelated exception types + * at the same time. Such a block can be declared like the + * following for example: + * + *

{@code catch (IllegalStateException | IllegalArgumentException e) {}} + * + * @return True if this node is a multi-catch statement + */ + public boolean isMulticatchStatement() { + return getCaughtExceptionTypeNodes().size() > 1; // the list is parsed multiple times... + } + + + /** + * Returns the Block node of this catch branch. + */ + public ASTBlock getBlock() { + return getFirstChildOfType(ASTBlock.class); + } + + /** + * Returns the list of type nodes denoting the exception types + * caught by this catch block. The returned list has at least + * one element. + */ + public List getCaughtExceptionTypeNodes() { + // maybe cache the list + return getFirstChildOfType(ASTFormalParameter.class).findChildrenOfType(ASTType.class); + } + + + /** + * Returns the list of exception types caught by this catch block. + * Any of these can be null, if they couldn't be resolved. This can + * happen if the auxclasspath is not correctly set. + */ + @SuppressWarnings("unchecked") + public List> getCaughtExceptionTypes() { + List> result = new ArrayList<>(); + for (ASTType type : getCaughtExceptionTypeNodes()) { + result.add((Class) type.getType()); + } + return result; + } + + /** + * Returns exception name caught by this catch block. + */ + public String getExceptionName() { + return getFirstDescendantOfType(ASTVariableDeclaratorId.class).getImage(); + } + } 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 a42b3b6ac0a..838ee1b1b8c 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); @@ -17,6 +27,7 @@ public ASTClassOrInterfaceBody(JavaParser p, int id) { /** * Accept the visitor. */ + @Override public Object jjtAccept(JavaParserVisitor visitor, Object data) { return visitor.visit(this, data); } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTClassOrInterfaceBodyDeclaration.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTClassOrInterfaceBodyDeclaration.java index 0e1bc3608a8..7d850bf70e8 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTClassOrInterfaceBodyDeclaration.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTClassOrInterfaceBodyDeclaration.java @@ -7,7 +7,7 @@ import net.sourceforge.pmd.Rule; -public class ASTClassOrInterfaceBodyDeclaration extends AbstractJavaNode implements CanSuppressWarnings, ASTAnyTypeBodyDeclaration { +public class ASTClassOrInterfaceBodyDeclaration extends AbstractTypeBodyDeclaration implements CanSuppressWarnings, ASTAnyTypeBodyDeclaration { public ASTClassOrInterfaceBodyDeclaration(int id) { super(id); @@ -22,6 +22,7 @@ public boolean isFindBoundary() { return isAnonymousInnerClass(); } + @Override public boolean hasSuppressWarningsAnnotationFor(Rule rule) { for (int i = 0; i < jjtGetNumChildren(); i++) { if (jjtGetChild(i) instanceof ASTAnnotation) { @@ -37,6 +38,7 @@ public boolean hasSuppressWarningsAnnotationFor(Rule rule) { /** * Accept the visitor. * */ + @Override public Object jjtAccept(JavaParserVisitor visitor, Object data) { return visitor.visit(this, data); } 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 9e09fe341c3..9f1ed15a26b 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 @@ -5,12 +5,34 @@ package net.sourceforge.pmd.lang.java.ast; +import java.util.Collections; import java.util.List; -public class ASTClassOrInterfaceDeclaration extends AbstractJavaAccessTypeNode implements ASTAnyTypeDeclaration { +import net.sourceforge.pmd.lang.ast.Node; +import net.sourceforge.pmd.util.CollectionUtil; + + +/** + * Represents class and interface declarations. This is a {@linkplain Node#isFindBoundary() find boundary} + * for tree traversal methods. + * + *
+ *
+ * ClassOrInterfaceDeclaration ::= ( "class" | "interface" )
+ *                                 <IDENTIFIER>
+ *                                 {@linkplain ASTTypeParameters TypeParameters}?
+ *                                 {@linkplain ASTExtendsList ExtendsList}?
+ *                                 {@linkplain ASTImplementsList ImplementsList}?
+ *                                 {@linkplain ASTClassOrInterfaceBody ClassOrInterfaceBody}
+ * 
+ * + */ +public class ASTClassOrInterfaceDeclaration extends AbstractAnyTypeDeclaration { + + private boolean isLocal; + private boolean isLocalComputed; // guard for lazy evaluation of isLocal() private boolean isInterface; - private JavaQualifiedName qualifiedName; public ASTClassOrInterfaceDeclaration(int id) { super(id); @@ -22,7 +44,7 @@ public ASTClassOrInterfaceDeclaration(JavaParser p, int id) { @Override public boolean isFindBoundary() { - return isNested(); + return isNested() || isLocal(); } /** @@ -33,9 +55,31 @@ public Object jjtAccept(JavaParserVisitor visitor, Object data) { return visitor.visit(this, data); } - public boolean isNested() { - return jjtGetParent() instanceof ASTClassOrInterfaceBodyDeclaration - || jjtGetParent() instanceof ASTAnnotationTypeMemberDeclaration; + + /** + * Returns true if the class is declared inside a block other + * than the body of another class, or the top level. + */ + public boolean isLocal() { + if (!isLocalComputed) { + Node current = jjtGetParent(); + while (current != null) { + if (current instanceof ASTAnyTypeDeclaration) { + isLocal = false; + break; + } else if (current instanceof ASTMethodOrConstructorDeclaration + || current instanceof ASTInitializer) { + isLocal = true; + break; + } + current = current.jjtGetParent(); + } + if (current == null) { + isLocal = false; + } + isLocalComputed = true; + } + return isLocal; } public boolean isInterface() { @@ -46,22 +90,6 @@ public void setInterface() { this.isInterface = true; } - @Override - public JavaQualifiedName getQualifiedName() { - if (qualifiedName == null) { - if (isNested()) { - ASTClassOrInterfaceDeclaration parent = this.getFirstParentOfType(ASTClassOrInterfaceDeclaration.class); - JavaQualifiedName parentQN = parent.getQualifiedName(); - qualifiedName = JavaQualifiedName.ofNestedClass(parentQN, this.getImage()); - return qualifiedName; - } - - qualifiedName = JavaQualifiedName.ofOuterClass(this); - } - - return qualifiedName; - } - @Override public TypeKind getTypeKind() { return isInterface() ? TypeKind.INTERFACE : TypeKind.CLASS; @@ -73,4 +101,37 @@ public List getDeclarations() { return getFirstChildOfType(ASTClassOrInterfaceBody.class) .findChildrenOfType(ASTAnyTypeBodyDeclaration.class); } + + + /** + * Returns the superclass type node if this node is a class + * declaration and explicitly declares an {@code extends} + * clause. Superinterfaces of an interface are not considered. + * + *

Returns {@code null} otherwise. + */ + public ASTClassOrInterfaceType getSuperClassTypeNode() { + if (isInterface()) { + return null; + } + + ASTExtendsList extendsList = getFirstChildOfType(ASTExtendsList.class); + return extendsList == null ? null : extendsList.iterator().next(); + } + + + /** + * Returns the interfaces implemented by this class, or + * extended by this interface. Returns an empty list if + * none is specified. + */ + public List getSuperInterfacesTypeNodes() { + + Iterable it = isInterface() + ? getFirstChildOfType(ASTExtendsList.class) + : getFirstChildOfType(ASTImplementsList.class); + + return it == null ? Collections.emptyList() : CollectionUtil.toList(it.iterator()); + } + } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTClassOrInterfaceType.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTClassOrInterfaceType.java index f1a14808594..43f3697f28d 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTClassOrInterfaceType.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTClassOrInterfaceType.java @@ -5,8 +5,19 @@ package net.sourceforge.pmd.lang.java.ast; -import java.util.List; +import net.sourceforge.pmd.lang.ast.Node; + +/** + * Represents a class or interface type, possibly parameterised with type arguments. + * + *

+ *
+ * ClassOrInterfaceType ::= <IDENTIFIER> {@linkplain ASTTypeArguments TypeArguments}? ( "." <IDENTIFIER>  {@linkplain ASTTypeArguments TypeArguments}? )*
+ *
+ * 
+ * + */ public class ASTClassOrInterfaceType extends AbstractJavaTypeNode { public ASTClassOrInterfaceType(int id) { super(id); @@ -16,9 +27,7 @@ public ASTClassOrInterfaceType(JavaParser p, int id) { super(p, id); } - /** - * Accept the visitor. * - */ + @Override public Object jjtAccept(JavaParserVisitor visitor, Object data) { return visitor.visit(this, data); } @@ -33,14 +42,12 @@ public Object jjtAccept(JavaParserVisitor visitor, Object data) { */ public boolean isReferenceToClassSameCompilationUnit() { ASTCompilationUnit root = getFirstParentOfType(ASTCompilationUnit.class); - List classes = root.findDescendantsOfType(ASTClassOrInterfaceDeclaration.class); - for (ASTClassOrInterfaceDeclaration c : classes) { + for (ASTClassOrInterfaceDeclaration c : root.findDescendantsOfType(ASTClassOrInterfaceDeclaration.class, true)) { if (c.hasImageEqualTo(getImage())) { return true; } } - List enums = root.findDescendantsOfType(ASTEnumDeclaration.class); - for (ASTEnumDeclaration e : enums) { + for (ASTEnumDeclaration e : root.findDescendantsOfType(ASTEnumDeclaration.class, true)) { if (e.hasImageEqualTo(getImage())) { return true; } @@ -49,6 +56,22 @@ public boolean isReferenceToClassSameCompilationUnit() { } public boolean isAnonymousClass() { - return jjtGetParent().hasDescendantOfType(ASTClassOrInterfaceBody.class); + return jjtGetParent().getFirstChildOfType(ASTClassOrInterfaceBody.class) != null; + } + + public boolean isArray() { + Node p = jjtGetParent(); + if (p instanceof ASTReferenceType) { + return ((ASTReferenceType) p).isArray(); + } + return false; + } + + public int getArrayDepth() { + Node p = jjtGetParent(); + if (p instanceof ASTReferenceType) { + return ((ASTReferenceType) p).getArrayDepth(); + } + return 0; } } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTConditionalAndExpression.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTConditionalAndExpression.java index 838f722b585..600f1b2fe4f 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTConditionalAndExpression.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTConditionalAndExpression.java @@ -5,18 +5,32 @@ package net.sourceforge.pmd.lang.java.ast; +/** + * Represents a boolean AND-expression. This has a precedence greater than {@link ASTConditionalOrExpression}, + * and lower than {@link ASTInclusiveOrExpression}. + * + *

Note that the children of this node are not necessarily {@link ASTInclusiveOrExpression}, + * rather, they are expressions with an operator precedence greater or equal to InclusiveOrExpression. + * + * + *

+ *
+ * ConditionalAndExpression ::=  {@linkplain ASTInclusiveOrExpression InclusiveOrExpression} ( "&&" {@linkplain ASTInclusiveOrExpression InclusiveOrExpression} )+
+ *
+ * 
+ */ public class ASTConditionalAndExpression extends AbstractJavaTypeNode { public ASTConditionalAndExpression(int id) { super(id); } + public ASTConditionalAndExpression(JavaParser p, int id) { super(p, id); } - /** - * Accept the visitor. * - */ + + @Override public Object jjtAccept(JavaParserVisitor visitor, Object data) { return visitor.visit(this, data); } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTConditionalExpression.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTConditionalExpression.java index 633644586d0..10611ea0647 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTConditionalExpression.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTConditionalExpression.java @@ -5,9 +5,26 @@ package net.sourceforge.pmd.lang.java.ast; +import net.sourceforge.pmd.lang.ast.Node; + + +/** + * Represents a conditional expression, aka ternary expression. This operation has + * a greater precedence as {@linkplain ASTExpression assignment expressions}, + * and lower as {@link ASTConditionalOrExpression}. + * + *

Note that the children of this node are not necessarily {@link ASTConditionalOrExpression}, + * rather, they are expressions with an operator precedence greater or equal to ConditionalOrExpression. + * + *

+ *
+ * ConditionalExpression ::= {@linkplain ASTConditionalOrExpression ConditionalOrExpression} "?"  {@linkplain ASTExpression Expression} ":" {@linkplain ASTConditionalExpression ConditionalExpression}
+ *
+ * 
+ * + */ public class ASTConditionalExpression extends AbstractJavaTypeNode { - private boolean isTernary; public ASTConditionalExpression(int id) { super(id); @@ -17,17 +34,54 @@ public ASTConditionalExpression(JavaParser p, int id) { super(p, id); } + + /** + * @deprecated To be removed in 7.0.0 + */ + @Deprecated public void setTernary() { - isTernary = true; + // noop } + + /** + * This method always returns true. + * + * @deprecated To be removed in 7.0.0 + */ + @Deprecated public boolean isTernary() { - return this.isTernary; + return true; + } + + + /** + * Returns the node that represents the guard of this conditional. + * That is the expression before the '?'. + */ + public Node getGuardExpressionNode() { + return jjtGetChild(0); + } + + + /** + * Returns the node that represents the expression that will be evaluated + * if the guard evaluates to true. + */ + public ASTExpression getTrueAlternative() { + return (ASTExpression) jjtGetChild(1); } + /** - * Accept the visitor. * + * Returns the node that represents the expression that will be evaluated + * if the guard evaluates to false. */ + public Node getFalseAlternative() { + return jjtGetChild(2); + } + + @Override public Object jjtAccept(JavaParserVisitor visitor, Object data) { return visitor.visit(this, data); diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTConditionalOrExpression.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTConditionalOrExpression.java index 91e3109d0cb..b32ae06d7c2 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTConditionalOrExpression.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTConditionalOrExpression.java @@ -5,6 +5,20 @@ package net.sourceforge.pmd.lang.java.ast; +/** + * Represents a boolean OR-expression. This has a precedence greater than {@link ASTConditionalExpression}, + * and lower than {@link ASTConditionalAndExpression}. + * + *

Note that the children of this node are not necessarily {@link ASTConditionalAndExpression}, + * rather, they are expressions with an operator precedence greater or equal to ConditionalAndExpression. + * + * + *

+ *
+ * ConditionalOrExpression ::=  {@linkplain ASTConditionalAndExpression ConditionalAndExpression} ( "||" {@linkplain ASTConditionalAndExpression ConditionalAndExpression} )+
+ *
+ * 
+ */ public class ASTConditionalOrExpression extends AbstractJavaTypeNode { public ASTConditionalOrExpression(int id) { super(id); @@ -14,9 +28,7 @@ public ASTConditionalOrExpression(JavaParser p, int id) { super(p, id); } - /** - * Accept the visitor. * - */ + @Override public Object jjtAccept(JavaParserVisitor visitor, Object data) { return visitor.visit(this, data); } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTConstructorDeclaration.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTConstructorDeclaration.java index 666f75bbdfe..5ab9b36724d 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTConstructorDeclaration.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTConstructorDeclaration.java @@ -5,12 +5,10 @@ package net.sourceforge.pmd.lang.java.ast; -import net.sourceforge.pmd.lang.java.metrics.signature.JavaOperationSignature; -public class ASTConstructorDeclaration extends AbstractJavaAccessNode implements ASTMethodOrConstructorDeclaration { +public class ASTConstructorDeclaration extends AbstractMethodOrConstructorDeclaration { private boolean containsComment; - private JavaQualifiedName qualifiedName; public ASTConstructorDeclaration(int id) { super(id); @@ -20,12 +18,10 @@ public ASTConstructorDeclaration(JavaParser p, int id) { super(p, id); } - public ASTFormalParameters getParameters() { - return (ASTFormalParameters) (jjtGetChild(0) instanceof ASTFormalParameters ? jjtGetChild(0) : jjtGetChild(1)); - } - public int getParameterCount() { - return getParameters().getParameterCount(); + @Override + public MethodLikeKind getKind() { + return MethodLikeKind.CONSTRUCTOR; } /** @@ -44,16 +40,20 @@ public void setContainsComment() { this.containsComment = true; } - @Override - public JavaQualifiedName getQualifiedName() { - if (qualifiedName == null) { - qualifiedName = JavaQualifiedName.ofOperation(this); - } - return qualifiedName; + /** + * @deprecated to be removed with PMD 7.0.0 - use getFormalParameters() instead + */ + @Deprecated + public ASTFormalParameters getParameters() { + return getFormalParameters(); } - @Override - public JavaOperationSignature getSignature() { - return JavaOperationSignature.buildFor(this); + public int getParameterCount() { + return getFormalParameters().getParameterCount(); + } + + //@Override // enable this with PMD 7.0.0 - see interface ASTMethodOrConstructorDeclaration + public ASTFormalParameters getFormalParameters() { + return getFirstChildOfType(ASTFormalParameters.class); } } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTContinueStatement.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTContinueStatement.java index 8f19f95f456..05d96f2b714 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTContinueStatement.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTContinueStatement.java @@ -17,6 +17,7 @@ public ASTContinueStatement(JavaParser p, int id) { /** * Accept the visitor. * */ + @Override public Object jjtAccept(JavaParserVisitor visitor, Object data) { return visitor.visit(this, data); } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTDefaultValue.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTDefaultValue.java index c12cab1400c..c5bb40c2af0 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTDefaultValue.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTDefaultValue.java @@ -17,6 +17,7 @@ public ASTDefaultValue(JavaParser p, int id) { /** * Accept the visitor. * */ + @Override public Object jjtAccept(JavaParserVisitor visitor, Object data) { return visitor.visit(this, data); } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTDoStatement.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTDoStatement.java index 7fd631e46e2..6412bb68244 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTDoStatement.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTDoStatement.java @@ -17,6 +17,7 @@ public ASTDoStatement(JavaParser p, int id) { /** * Accept the visitor. * */ + @Override public Object jjtAccept(JavaParserVisitor visitor, Object data) { return visitor.visit(this, data); } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTEmptyStatement.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTEmptyStatement.java index 0f064c2a63d..1260eeba114 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTEmptyStatement.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTEmptyStatement.java @@ -17,6 +17,7 @@ public ASTEmptyStatement(JavaParser p, int id) { /** * Accept the visitor. * */ + @Override public Object jjtAccept(JavaParserVisitor visitor, Object data) { return visitor.visit(this, data); } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTEnumBody.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTEnumBody.java index ec9f5024ea1..a722f661031 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTEnumBody.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTEnumBody.java @@ -17,6 +17,7 @@ public ASTEnumBody(JavaParser p, int id) { /** * Accept the visitor. * */ + @Override public Object jjtAccept(JavaParserVisitor visitor, Object data) { return visitor.visit(this, data); } 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 3d7b4a3b61e..30c6337921c 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 @@ -5,7 +5,24 @@ package net.sourceforge.pmd.lang.java.ast; -public class ASTEnumConstant extends AbstractJavaNode { +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; + public ASTEnumConstant(int id) { super(id); } @@ -17,7 +34,38 @@ public ASTEnumConstant(JavaParser p, int id) { /** * Accept the visitor. * */ + @Override public Object jjtAccept(JavaParserVisitor visitor, Object data) { return visitor.visit(this, data); } + + + /** + * Gets the qualified name of the anonymous class + * declared by this node, or null if this node + * doesn't declare any. + * + * @see #isAnonymousClass() + */ + @Override + public JavaTypeQualifiedName getQualifiedName() { + return qualifiedName; + } + + + public void setQualifiedName(JavaTypeQualifiedName qname) { + this.qualifiedName = qname; + } + + + /** + * Returns true if this enum constant defines a body, + * which is compiled like an anonymous class. If this + * method returns false, then {@link #getQualifiedName()} + * returns {@code null}. + */ + public boolean isAnonymousClass() { + return getFirstChildOfType(ASTClassOrInterfaceBody.class) != null; + } + } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTEnumDeclaration.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTEnumDeclaration.java index 0ea51d4b3ee..bc22a661257 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTEnumDeclaration.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTEnumDeclaration.java @@ -7,9 +7,8 @@ import java.util.List; -public class ASTEnumDeclaration extends AbstractJavaAccessTypeNode implements ASTAnyTypeDeclaration { +public class ASTEnumDeclaration extends AbstractAnyTypeDeclaration { - private JavaQualifiedName qualifiedName; public ASTEnumDeclaration(int id) { super(id); @@ -19,30 +18,11 @@ public ASTEnumDeclaration(JavaParser p, int id) { super(p, id); } + @Override public Object jjtAccept(JavaParserVisitor visitor, Object data) { return visitor.visit(this, data); } - public boolean isNested() { - return jjtGetParent() instanceof ASTClassOrInterfaceBodyDeclaration - || jjtGetParent() instanceof ASTAnnotationTypeMemberDeclaration; - } - - @Override - public JavaQualifiedName getQualifiedName() { - if (qualifiedName == null) { - if (isNested()) { - ASTAnyTypeDeclaration parent = this.getFirstParentOfType(ASTAnyTypeDeclaration.class); - JavaQualifiedName parentQN = parent.getQualifiedName(); - qualifiedName = JavaQualifiedName.ofNestedClass(parentQN, this.getImage()); - return qualifiedName; - } - - qualifiedName = JavaQualifiedName.ofOuterClass(this); - } - - return qualifiedName; - } @Override public TypeKind getTypeKind() { diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTEqualityExpression.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTEqualityExpression.java index c5686deb4c5..b1bfead6484 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTEqualityExpression.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTEqualityExpression.java @@ -5,6 +5,21 @@ package net.sourceforge.pmd.lang.java.ast; +/** + * Represents an identity test between two values or more values. + * This has a precedence greater than {@link ASTAndExpression}, + * and lower than {@link ASTInstanceOfExpression} and {@link ASTRelationalExpression}. + * + *

Note that the children of this node are not necessarily {@link ASTInstanceOfExpression}, + * rather, they are expressions with an operator precedence greater or equal to InstanceOfExpression. + * + * + *

+ *
+ * EqualityExpression ::=  {@linkplain ASTInstanceOfExpression InstanceOfExpression}  ( ( "==" | "!=" ) {@linkplain ASTInstanceOfExpression InstanceOfExpression}  )+
+ *
+ * 
+ */ public class ASTEqualityExpression extends AbstractJavaTypeNode { public ASTEqualityExpression(int id) { super(id); @@ -14,11 +29,17 @@ public ASTEqualityExpression(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 image of the operator, i.e. "==" or "!=". + */ + public String getOperator() { + return getImage(); + } + } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTExclusiveOrExpression.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTExclusiveOrExpression.java index 26c870eff6e..52afcd4f974 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTExclusiveOrExpression.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTExclusiveOrExpression.java @@ -5,6 +5,20 @@ package net.sourceforge.pmd.lang.java.ast; +/** + * Represents a boolean XOR-expression. This has a precedence greater than {@link ASTInclusiveOrExpression}, + * and lower than {@link ASTAndExpression}. + * + *

Note that the children of this node are not necessarily {@link ASTAndExpression}, + * rather, they are expressions with an operator precedence greater or equal to AndExpression. + * + * + *

+ *
+ * ExclusiveOrExpression ::=  {@linkplain ASTAndExpression AndExpression} ( "^" {@linkplain ASTAndExpression AndExpression} )+
+ *
+ * 
+ */ public class ASTExclusiveOrExpression extends AbstractJavaTypeNode { public ASTExclusiveOrExpression(int id) { super(id); @@ -14,9 +28,7 @@ public ASTExclusiveOrExpression(JavaParser p, int id) { super(p, id); } - /** - * Accept the visitor. * - */ + @Override public Object jjtAccept(JavaParserVisitor visitor, Object data) { return visitor.visit(this, data); } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTExpression.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTExpression.java index 37b3f49ab41..eb2e539fffe 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTExpression.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTExpression.java @@ -5,6 +5,18 @@ package net.sourceforge.pmd.lang.java.ast; +/** + * Represents an expression, in the most general sense. + * This corresponds roughly to the AssignmentExpression + * of the JLS. One difference though, is that this production + * also matches lambda expressions, contrary to the JLS. + * + *
+ *
+ * Expression ::= {@linkplain ASTConditionalExpression ConditionalExpression} ( {@linkplain ASTAssignmentOperator AssignmentOperator} Expression )?
+ *
+ * 
+ */ public class ASTExpression extends AbstractJavaTypeNode { public ASTExpression(int id) { super(id); @@ -17,6 +29,7 @@ public ASTExpression(JavaParser p, int id) { /** * Accept the visitor. * */ + @Override public Object jjtAccept(JavaParserVisitor visitor, Object data) { return visitor.visit(this, data); } @@ -40,12 +53,9 @@ public boolean isStandAlonePrimitive() { ASTLiteral literal = primaryPrefix.getFirstChildOfType(ASTLiteral.class); - if (literal == null || literal.isStringLiteral() - || (literal.jjtGetNumChildren() != 0 && literal.jjtGetChild(0) instanceof ASTNullLiteral)) { - return false; - } - + // if it is not a string literal and not a null, then it is one of // byte, short, char, int, long, float, double, boolean - return true; + return literal != null && !literal.isStringLiteral() + && (literal.jjtGetNumChildren() == 0 || !(literal.jjtGetChild(0) instanceof ASTNullLiteral)); } } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTExtendsList.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTExtendsList.java index 7b416a34667..fdcbd9147ab 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTExtendsList.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTExtendsList.java @@ -5,7 +5,20 @@ package net.sourceforge.pmd.lang.java.ast; -public class ASTExtendsList extends AbstractJavaNode { +import java.util.Iterator; + + +/** + * Represents the {@code extends} clause of a class or interface declaration. + * If the parent is an interface declaration, then these types are all interface + * types. Otherwise, then this list contains exactly one element. + * + *
+ *  ExtendsList ::= "extends" (TypeAnnotation)* ClassOrInterfaceType
+ *                ( "," (TypeAnnotation)* ClassOrInterfaceType )*
+ * 
+ */ +public class ASTExtendsList extends AbstractJavaNode implements Iterable { public ASTExtendsList(int id) { super(id); } @@ -17,7 +30,14 @@ public ASTExtendsList(JavaParser p, int id) { /** * Accept the visitor. * */ + @Override public Object jjtAccept(JavaParserVisitor visitor, Object data) { return visitor.visit(this, data); } + + + @Override // TODO this doesn't preserve the annotations. + public Iterator iterator() { + return new NodeChildrenIterator<>(this, ASTClassOrInterfaceType.class); + } } 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 82c8eb37c6d..15e396118da 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,38 @@ package net.sourceforge.pmd.lang.java.ast; +import java.util.Iterator; + import net.sourceforge.pmd.lang.ast.SignedNode; -import net.sourceforge.pmd.lang.java.metrics.signature.JavaFieldSignature; +import net.sourceforge.pmd.lang.java.multifile.signature.JavaFieldSignature; + + +/** + * 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 { -public class ASTFieldDeclaration extends AbstractJavaAccessTypeNode implements Dimensionable, SignedNode { + private JavaFieldSignature signature; public ASTFieldDeclaration(int id) { @@ -88,24 +116,23 @@ public boolean isProtected() { } public boolean isAnnotationMember() { - if (jjtGetParent().jjtGetParent() instanceof ASTAnnotationTypeBody) { - return true; - } - return false; + return getNthParent(2) instanceof ASTAnnotationTypeBody; } public boolean isInterfaceMember() { - if (jjtGetParent().jjtGetParent() instanceof ASTEnumBody) { + if (getNthParent(2) instanceof ASTEnumBody) { return false; } ASTClassOrInterfaceDeclaration n = getFirstParentOfType(ASTClassOrInterfaceDeclaration.class); return n != null && n.isInterface(); } + @Override public boolean isArray() { return checkType() + checkDecl() > 0; } + @Override public int getArrayDepth() { if (!isArray()) { return 0; @@ -145,6 +172,21 @@ public String getVariableName() { @Override public JavaFieldSignature getSignature() { - return JavaFieldSignature.buildFor(this); + if (signature == null) { + signature = JavaFieldSignature.buildFor(this); + } + + 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/ASTFinallyStatement.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTFinallyStatement.java index 4455daae613..afeb95910cf 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTFinallyStatement.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTFinallyStatement.java @@ -17,6 +17,7 @@ public ASTFinallyStatement(JavaParser p, int id) { /** * Accept the visitor. * */ + @Override public Object jjtAccept(JavaParserVisitor visitor, Object data) { return visitor.visit(this, data); } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTForInit.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTForInit.java index 0f19bcdc655..99ed7f1dda3 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTForInit.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTForInit.java @@ -17,6 +17,7 @@ public ASTForInit(JavaParser p, int id) { /** * Accept the visitor. * */ + @Override public Object jjtAccept(JavaParserVisitor visitor, Object data) { return visitor.visit(this, data); } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTForStatement.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTForStatement.java index f6af4988337..61aef7c1423 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTForStatement.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTForStatement.java @@ -5,6 +5,19 @@ package net.sourceforge.pmd.lang.java.ast; +/** + * Represents a {@code for}-loop, or a foreach loop. + * + *
+ *
+ * ForStatement ::= "for" "(" {@linkplain ASTLocalVariableDeclaration LocalVariableDeclaration} ":" {@linkplain ASTExpression Expression} ")" {@linkplain ASTStatement Statement}
+ *                | "for" "(" {@linkplain ASTForInit ForInit}? ";" {@linkplain ASTExpression Expression}? ";" {@linkplain ASTForUpdate ForUpdate}? ")" {@linkplain ASTStatement Statement}
+ *
+ * 
+ * + */ +// TODO this should be split into two different nodes, otherwise +// we can't enrich the API without returning null half the time public class ASTForStatement extends AbstractJavaNode { public ASTForStatement(int id) { super(id); @@ -14,10 +27,26 @@ public ASTForStatement(JavaParser p, int id) { super(p, id); } - /** - * Accept the visitor. * - */ + @Override public Object jjtAccept(JavaParserVisitor visitor, Object data) { return visitor.visit(this, data); } + + + /** + * Returns true if this node represents a foreach loop. + */ + public boolean isForeach() { + return jjtGetChild(0) instanceof ASTLocalVariableDeclaration; + } + + + /** + * Returns the statement that represents the body of this + * loop. + */ + public ASTStatement getBody() { + return (ASTStatement) jjtGetChild(jjtGetNumChildren() - 1); + } + } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTForUpdate.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTForUpdate.java index ebb3b0fab37..4a7dd02a649 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTForUpdate.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTForUpdate.java @@ -17,6 +17,7 @@ public ASTForUpdate(JavaParser p, int id) { /** * Accept the visitor. * */ + @Override public Object jjtAccept(JavaParserVisitor visitor, Object data) { return visitor.visit(this, data); } 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 cb9b449a8a0..b2d5420c4c0 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 @@ -6,8 +6,20 @@ package net.sourceforge.pmd.lang.java.ast; import net.sourceforge.pmd.Rule; +import net.sourceforge.pmd.lang.java.typeresolution.typedefinition.JavaTypeDefinition; -public class ASTFormalParameter extends AbstractJavaAccessNode implements Dimensionable, CanSuppressWarnings { + +/** + * Formal parameter node. Used in the {@link ASTFormalParameters} + * 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
+ * 
+ */ +public class ASTFormalParameter extends AbstractJavaAccessTypeNode implements Dimensionable, CanSuppressWarnings { private boolean isVarargs; @@ -19,16 +31,47 @@ public ASTFormalParameter(JavaParser p, int id) { super(p, id); } + + /** + * @deprecated Will be made private in 7.0.0 + */ + // Should be made package-private + @Deprecated public void setVarargs() { isVarargs = true; } + + /** + * Returns true if this node is a varargs parameter. + */ public boolean isVarargs() { return isVarargs; } + + /** + * Returns true if this node is the explicit receiver parameter, + * e.g. in + * + *
+     * class Foo {
+     *   abstract void foo(@Bar Foo this);
+     * }
+     * 
+ */ public boolean isExplicitReceiverParameter() { - return getDecl().isExplicitReceiverParameter(); + 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 @@ -36,22 +79,34 @@ public Object jjtAccept(JavaParserVisitor visitor, Object data) { return visitor.visit(this, data); } + + /** + * Returns the declarator ID of this formal parameter. + */ + public ASTVariableDeclaratorId getVariableDeclaratorId() { + return getFirstChildOfType(ASTVariableDeclaratorId.class); + } + @Override public boolean hasSuppressWarningsAnnotationFor(Rule rule) { - for (int i = 0; i < jjtGetNumChildren(); i++) { - if (jjtGetChild(i) instanceof ASTAnnotation) { - ASTAnnotation a = (ASTAnnotation) jjtGetChild(i); - if (a.suppresses(rule)) { - return true; - } + for (ASTAnnotation a : findChildrenOfType(ASTAnnotation.class)) { + if (a.suppresses(rule)) { + return true; } } return false; } + + /** + * Returns true if this formal parameter is of an array type. + * This includes varargs parameters. + */ @Override public boolean isArray() { - return checkType() + checkDecl() > 0; + return isVarargs() + || getTypeNode() != null && getTypeNode().isArray() + || getVariableDeclaratorId().isArray(); } @Override @@ -59,34 +114,65 @@ public int getArrayDepth() { if (!isArray()) { return 0; } - return checkType() + checkDecl(); + return getTypeNode().getArrayDepth() + getVariableDeclaratorId().getArrayDepth() + (isVarargs() ? 1 : 0); } + + /** + * Returns the type node of this formal parameter. + * The type of that node is not necessarily the type + * of the parameter itself, see {@link ASTVariableDeclaratorId#getType()}. + * + *

In particular, the type of the returned node + * doesn't take into account whether this formal + * parameter is varargs or not. + */ public ASTType getTypeNode() { - for (int i = 0; i < jjtGetNumChildren(); i++) { - if (jjtGetChild(i) instanceof ASTType) { - return (ASTType) jjtGetChild(i); - } - } - throw new IllegalStateException("ASTType not found"); + return getFirstChildOfType(ASTType.class); } - private int checkType() { - return getTypeNode().getArrayDepth(); - } + /** + * @deprecated use {@link #getVariableDeclaratorId()} + */ + @Deprecated protected ASTVariableDeclaratorId getDecl() { - try { - return (ASTVariableDeclaratorId) jjtGetChild(jjtGetNumChildren() - 1); - } catch (ClassCastException c) { - System.out.println( - "CLASS CAST: " + this.getBeginLine() + ":" + this.getBeginColumn() + " " + this.toString()); - return null; - } + return getVariableDeclaratorId(); + } + + /** + * Returns the type of this formal parameter. That type + * is exactly that of the variable declarator id, + * which means that the declarator id's type takes into + * account whether this parameter is varargs or not. + */ + @Override + public Class getType() { + return getVariableDeclaratorId().getType(); + } + + + @Override + public JavaTypeDefinition getTypeDefinition() { + return getVariableDeclaratorId().getTypeDefinition(); } - private int checkDecl() { - return getDecl().getArrayDepth(); + + /** + * Noop, the type of this node is defined by the type + * of the declarator id. + */ + @Override + public void setTypeDefinition(JavaTypeDefinition type) { + // see javadoc } + /** + * Noop, the type of this node is defined by the type + * of the declarator id. + */ + @Override + public void setType(Class type) { + // see javadoc + } } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTFormalParameters.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTFormalParameters.java index 1661263b33b..57cc052d535 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTFormalParameters.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTFormalParameters.java @@ -5,7 +5,11 @@ package net.sourceforge.pmd.lang.java.ast; -public class ASTFormalParameters extends AbstractJavaNode { +import java.util.Iterator; +import java.util.List; + + +public class ASTFormalParameters extends AbstractJavaNode implements Iterable { public ASTFormalParameters(int id) { super(id); } @@ -15,13 +19,22 @@ public ASTFormalParameters(JavaParser p, int id) { } public int getParameterCount() { - return jjtGetNumChildren(); + final List parameters = findChildrenOfType(ASTFormalParameter.class); + return !parameters.isEmpty() && parameters.get(0).isExplicitReceiverParameter() + ? parameters.size() - 1 : parameters.size(); } /** * Accept the visitor. * */ + @Override public Object jjtAccept(JavaParserVisitor visitor, Object data) { return visitor.visit(this, data); } + + + @Override + public Iterator iterator() { + return new NodeChildrenIterator<>(this, ASTFormalParameter.class); + } } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTImplementsList.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTImplementsList.java index fca9ac8966c..3be6bf7b2f5 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTImplementsList.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTImplementsList.java @@ -5,7 +5,18 @@ package net.sourceforge.pmd.lang.java.ast; -public class ASTImplementsList extends AbstractJavaNode { +import java.util.Iterator; + + +/** + * Represents the {@code implements} clause of a class declaration. + * + *

+ *  ExtendsList ::= "implements" (TypeAnnotation)* ClassOrInterfaceType
+ *                ( "," (TypeAnnotation)* ClassOrInterfaceType )*
+ * 
+ */ +public class ASTImplementsList extends AbstractJavaNode implements Iterable { public ASTImplementsList(int id) { super(id); } @@ -17,7 +28,14 @@ public ASTImplementsList(JavaParser p, int id) { /** * Accept the visitor. * */ + @Override public Object jjtAccept(JavaParserVisitor visitor, Object data) { return visitor.visit(this, data); } + + + @Override + public Iterator iterator() { + return new NodeChildrenIterator<>(this, ASTClassOrInterfaceType.class); + } } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTImportDeclaration.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTImportDeclaration.java index 13ab743c195..326105a7df0 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTImportDeclaration.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTImportDeclaration.java @@ -5,6 +5,25 @@ package net.sourceforge.pmd.lang.java.ast; +/** + * Represents an import declaration in a Java file. + * + *
+ *
+ * ImportDeclaration ::= "import" "static"? {@linkplain ASTName Name} ( "." "*" )? ";"
+ *
+ * 
+ * + * @see JLS 7.5 + * + */ +// TODO should this really be a type node? +// E.g. for on-demand imports, what's the type of this node? There's no type name, just a package name +// for on-demand static imports? +// for static imports of a field? the type of the field or the type of the enclosing type? +// for static imports of a method? +// I don't think we can work out a spec without surprising corner cases, and #1207 will abstract +// things away anyway, so I think we should make it a regular node public class ASTImportDeclaration extends AbstractJavaTypeNode { private boolean isImportOnDemand; @@ -19,18 +38,47 @@ public ASTImportDeclaration(JavaParser p, int id) { super(p, id); } + + /** + * @deprecated Will be made private with 7.0.0 + */ + @Deprecated public void setImportOnDemand() { isImportOnDemand = true; } + + // @formatter:off + /** + * Returns true if this is an import-on-demand declaration, + * aka "wildcard import". + * + *
    + *
  • If this is a static import, then the imported names are those + * of the accessible static members of the named type; + *
  • Otherwise, the imported names are the names of the accessible types + * of the named type or named package. + *
+ */ + // @formatter:on public boolean isImportOnDemand() { return isImportOnDemand; } + + /** + * @deprecated Will be made private with 7.0.0 + */ + @Deprecated public void setStatic() { isStatic = true; } + + /** + * Returns true if this is a static import. If this import is not on-demand, + * {@link #getImportedSimpleName()} returns the name of the imported member. + */ public boolean isStatic() { return isStatic; } @@ -41,10 +89,35 @@ public ASTName getImportedNameNode() { return (ASTName) jjtGetChild(0); } + + /** + * Returns the full name of the import. For on-demand imports, this is the name without + * the final dot and asterisk. + */ public String getImportedName() { - return ((ASTName) jjtGetChild(0)).getImage(); + return jjtGetChild(0).getImage(); } + + /** + * Returns the simple name of the type or method imported by this declaration. + * For on-demand imports, returns {@code null}. + */ + public String getImportedSimpleName() { + if (isImportOnDemand) { + return null; + } + + String importName = getImportedName(); + return importName.substring(importName.lastIndexOf('.') + 1); + } + + + /** + * Returns the "package" prefix of the imported name. For type imports, including on-demand + * imports, this is really the package name of the imported type(s). For static imports, + * this is actually the qualified name of the enclosing type, including the type name. + */ public String getPackageName() { String importName = getImportedName(); if (isImportOnDemand) { @@ -57,9 +130,6 @@ public String getPackageName() { return importName.substring(0, lastDot); } - /** - * Accept the visitor. * - */ @Override public Object jjtAccept(JavaParserVisitor visitor, Object data) { return visitor.visit(this, data); @@ -69,6 +139,15 @@ public void setPackage(Package packge) { this.pkg = packge; } + + /** + * Returns the {@link Package} instance representing the package of the + * type or method imported by this declaration. This may be null if the + * auxclasspath is not correctly set, as this method depends on correct + * type resolution. + */ + // TODO deprecate? This is only used in a test. I don't think it's really + // useful and it gives work to ClassTypeResolver. public Package getPackage() { return this.pkg; } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTInclusiveOrExpression.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTInclusiveOrExpression.java index e1391e819b1..73b3921df68 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTInclusiveOrExpression.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTInclusiveOrExpression.java @@ -5,18 +5,33 @@ package net.sourceforge.pmd.lang.java.ast; +/** + * Represents a non-shortcut boolean OR-expression. This has a precedence + * greater than {@link ASTConditionalAndExpression}, and lower than + * {@link ASTExclusiveOrExpression}. + * + *

Note that the children of this node are not necessarily {@link ASTExclusiveOrExpression}, + * rather, they are expressions with an operator precedence greater or equal to ExclusiveOrExpression. + * + * + *

+ *
+ * InclusiveOrExpression ::=  {@linkplain ASTExclusiveOrExpression ExclusiveOrExpression} ( "|" {@linkplain ASTExclusiveOrExpression ExclusiveOrExpression} )+
+ *
+ * 
+ */ public class ASTInclusiveOrExpression extends AbstractJavaTypeNode { public ASTInclusiveOrExpression(int id) { super(id); } + public ASTInclusiveOrExpression(JavaParser p, int id) { super(p, id); } - /** - * Accept the visitor. * - */ + + @Override public Object jjtAccept(JavaParserVisitor visitor, Object data) { return visitor.visit(this, data); } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTInstanceOfExpression.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTInstanceOfExpression.java index 672088a7c49..89afd1fcbfc 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTInstanceOfExpression.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTInstanceOfExpression.java @@ -5,19 +5,42 @@ package net.sourceforge.pmd.lang.java.ast; +/** + * Represents a type test on an object. This has a precedence greater than {@link ASTEqualityExpression}, + * and lower than {@link ASTShiftExpression}. This has the same precedence as a {@link ASTRelationalExpression}. + * + *

Note that the children of this node are not necessarily {@link ASTRelationalExpression}, + * rather, they are expressions with an operator precedence greater or equal to RelationalExpression. + * + * + *

+ *
+ * InstanceOfExpression ::=  {@linkplain ASTShiftExpression ShiftExpression} "instanceof" {@linkplain ASTType Type}
+ *
+ * 
+ */ public class ASTInstanceOfExpression extends AbstractJavaTypeNode { public ASTInstanceOfExpression(int id) { super(id); } + public ASTInstanceOfExpression(JavaParser p, int id) { super(p, id); } - /** - * Accept the visitor. * - */ + + @Override public Object jjtAccept(JavaParserVisitor visitor, Object data) { return visitor.visit(this, data); } + + + /** + * Gets the type against which the expression is tested. + */ + public ASTType getTypeNode() { + return (ASTType) jjtGetChild(1); + } + } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTLabeledStatement.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTLabeledStatement.java index 5ae2bb92e72..17ad7e804b4 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTLabeledStatement.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTLabeledStatement.java @@ -14,6 +14,7 @@ public ASTLabeledStatement(JavaParser p, int id) { super(p, id); } + @Override public Object jjtAccept(JavaParserVisitor visitor, Object data) { return visitor.visit(this, data); } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTLambdaExpression.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTLambdaExpression.java index 89d42efa674..0b35ac03166 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTLambdaExpression.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTLambdaExpression.java @@ -6,24 +6,33 @@ package net.sourceforge.pmd.lang.java.ast; -public class ASTLambdaExpression extends AbstractJavaAccessNode { +public class ASTLambdaExpression extends AbstractMethodLikeNode { public ASTLambdaExpression(int id) { super(id); } + public ASTLambdaExpression(JavaParser p, int id) { super(p, id); } - + + @Override public boolean isFindBoundary() { return true; } + /** Accept the visitor. **/ + @Override public Object jjtAccept(JavaParserVisitor visitor, Object data) { return visitor.visit(this, data); } + + @Override + public MethodLikeKind getKind() { + return MethodLikeKind.LAMBDA; + } } /* * JavaCC - OriginalChecksum=e706de031abe9a22c368b7cb52802f1b (do not edit this 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 964dc2789b6..2a187323906 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,8 @@ package net.sourceforge.pmd.lang.java.ast; +import java.math.BigInteger; +import java.util.Locale; import java.util.regex.Pattern; public class ASTLiteral extends AbstractJavaTypeNode { @@ -53,7 +55,7 @@ public boolean isIntLiteral() { /** * Checks whether this literal is a long integer. - * + * * @return true if this literal is a long */ public boolean isLongLiteral() { @@ -83,7 +85,7 @@ public boolean isFloatLiteral() { /** * Checks whether this literal describes a double. - * + * * @return true if this literal is a double. */ public boolean isDoubleLiteral() { @@ -97,6 +99,72 @@ public boolean isDoubleLiteral() { return false; } + private String stripIntValue() { + String image = getImage().toLowerCase(Locale.ROOT).replaceAll("_", ""); + + boolean isNegative = false; + if (image.charAt(0) == '-') { + isNegative = true; + image = image.substring(1); + } + + if (image.endsWith("l")) { + image = image.substring(0, image.length() - 1); + } + + // ignore base prefix if any + if (image.charAt(0) == '0' && image.length() > 1) { + if (image.charAt(1) == 'x' || image.charAt(1) == 'b') { + image = image.substring(2); + } else { + image = image.substring(1); + } + } + + if (isNegative) { + return "-" + image; + } + return image; + } + + private String stripFloatValue() { + return getImage().toLowerCase(Locale.ROOT).replaceAll("_", ""); + } + + private int getIntBase() { + final String image = getImage().toLowerCase(Locale.ROOT); + final int offset = image.charAt(0) == '-' ? 1 : 0; + if (image.startsWith("0x", offset)) { + return 16; + } + if (image.startsWith("0b", offset)) { + return 2; + } + if (image.startsWith("0", offset) && image.length() > 1) { + return 8; + } + return 10; + } + + public int getValueAsInt() { + // the downcast allows to parse 0x80000000+ numbers as negative instead of a NumberFormatException + return (int) getValueAsLong(); + } + + public long getValueAsLong() { + // 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() { + return Float.parseFloat(stripFloatValue()); + } + + public double getValueAsDouble() { + return Double.parseDouble(stripFloatValue()); + } + public void setCharLiteral() { this.isChar = true; } 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 44e6b1901f3..d0089bdb326 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 @@ -5,9 +5,26 @@ package net.sourceforge.pmd.lang.java.ast; +import java.util.Iterator; + import net.sourceforge.pmd.Rule; -public class ASTLocalVariableDeclaration extends AbstractJavaAccessNode implements Dimensionable, CanSuppressWarnings { + +/** + * Represents a local variable declaration. This is a {@linkplain ASTBlockStatement block statement}, + * but the node is also used in {@linkplain ASTForInit for-loop initialisers} and + * {@linkplain ASTForStatement foreach statements}. + * + *

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} )*
+ *
+ * 
+ */ +public class ASTLocalVariableDeclaration extends AbstractJavaAccessNode implements Dimensionable, CanSuppressWarnings, Iterable { public ASTLocalVariableDeclaration(int id) { super(id); @@ -17,14 +34,12 @@ public ASTLocalVariableDeclaration(JavaParser p, int id) { super(p, id); } - /** - * Accept the visitor. * - */ @Override public Object jjtAccept(JavaParserVisitor visitor, Object data) { return visitor.visit(this, data); } + @Override public boolean hasSuppressWarningsAnnotationFor(Rule rule) { for (int i = 0; i < jjtGetNumChildren(); i++) { if (jjtGetChild(i) instanceof ASTAnnotation) { @@ -37,42 +52,66 @@ public boolean hasSuppressWarningsAnnotationFor(Rule rule) { return false; } + /** + * If true, this local variable declaration represents a declaration, + * which makes use of local variable type inference, e.g. java10 "var". + * You can receive the inferred type via {@link ASTVariableDeclarator#getType()}. + * + * @see ASTVariableDeclaratorId#isTypeInferred() + */ + public boolean isTypeInferred() { + return getTypeNode() == null; + } + + @Override + // TODO deprecate public boolean isArray() { - return checkType() + checkDecl() > 0; + return getArrayDepth() > 0; } + @Override + // TODO deprecate public int getArrayDepth() { - return checkType() + checkDecl(); + return getArrayDimensionOnType() + getArrayDimensionOnDeclaratorId(); } + /** + * Gets the type node for this variable declaration statement. + * With Java10 and local variable type inference, there might be + * no type node at all. + * @return The type node or null + * @see #isTypeInferred() + */ public ASTType getTypeNode() { - for (int i = 0; i < jjtGetNumChildren(); i++) { - if (jjtGetChild(i) instanceof ASTType) { - return (ASTType) jjtGetChild(i); - } - } - throw new IllegalStateException("ASTType not found"); + return getFirstChildOfType(ASTType.class); } - private int checkType() { - return getTypeNode().getArrayDepth(); + private int getArrayDimensionOnType() { + ASTType typeNode = getTypeNode(); + if (typeNode != null) { + return typeNode.getArrayDepth(); + } + return 0; } private ASTVariableDeclaratorId getDecl() { return (ASTVariableDeclaratorId) jjtGetChild(jjtGetNumChildren() - 1).jjtGetChild(0); } - private int checkDecl() { + private int getArrayDimensionOnDeclaratorId() { return getDecl().getArrayDepth(); } /** - * Gets the variable name of this field. This method searches the first + * Gets the variable name of this declaration. This method searches the first * VariableDeclartorId node and returns it's image or null if * the child node is not found. * * @return a String representing the name of the variable */ + // TODO deprecate. + // It would be nice to have a way to inform XPath users of the intended replacement + // for a deprecated attribute. We may use another annotation for that. public String getVariableName() { ASTVariableDeclaratorId decl = getFirstDescendantOfType(ASTVariableDeclaratorId.class); if (decl != null) { @@ -80,4 +119,14 @@ public String getVariableName() { } return null; } + + + /** + * Returns an iterator over the ids of the variables + * 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/ASTMarkerAnnotation.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTMarkerAnnotation.java index 9d7066612bc..c86b20ac6c6 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTMarkerAnnotation.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTMarkerAnnotation.java @@ -5,19 +5,49 @@ package net.sourceforge.pmd.lang.java.ast; +/** + * Represents an annotation with no declared member, e.g. {@code @Override}. + * + *
+ *
+ * MarkerAnnotation ::= "@" {@linkplain ASTAnnotation Name}
+ *
+ * 
+ * + * @see ASTSingleMemberAnnotation + * @see ASTNormalAnnotation + */ public class ASTMarkerAnnotation extends AbstractJavaTypeNode { + public ASTMarkerAnnotation(int id) { super(id); } + public ASTMarkerAnnotation(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 annotation as it is used, + * eg {@code java.lang.Override} or {@code Override}. + */ + public String getAnnotationName() { + return jjtGetChild(0).getImage(); + } + + + @Override + public ASTAnnotation jjtGetParent() { + return (ASTAnnotation) super.jjtGetParent(); + } + + } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTMemberSelector.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTMemberSelector.java index 867dcd320ea..c1ee155d1b8 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTMemberSelector.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTMemberSelector.java @@ -17,6 +17,7 @@ public ASTMemberSelector(JavaParser p, int id) { /** * Accept the visitor. * */ + @Override public Object jjtAccept(JavaParserVisitor visitor, Object data) { return visitor.visit(this, data); } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTMemberValue.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTMemberValue.java index 03c8ec74c38..307a9903d2b 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTMemberValue.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTMemberValue.java @@ -5,18 +5,31 @@ package net.sourceforge.pmd.lang.java.ast; +/** + * Represents the value of a member of an annotation. + * This can appear in a {@linkplain ASTMemberValuePair member-value pair}, + * or in a {@linkplain ASTSingleMemberAnnotation single-member annotation}. + * + *
+ *
+ * MemberValue ::= {@linkplain ASTAnnotation Annotation}
+ *               | {@linkplain ASTMemberValueArrayInitializer MemberValueArrayInitializer}
+ *               | < any expression, excluding assignment expressions and lambda expressions >
+ *
+ * 
+ */ public class ASTMemberValue extends AbstractJavaNode { public ASTMemberValue(int id) { super(id); } + public ASTMemberValue(JavaParser p, int id) { super(p, id); } - /** - * Accept the visitor. * - */ + + @Override public Object jjtAccept(JavaParserVisitor visitor, Object data) { return visitor.visit(this, data); } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTMemberValueArrayInitializer.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTMemberValueArrayInitializer.java index 1ae9aaf9b6a..20852e3aa52 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTMemberValueArrayInitializer.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTMemberValueArrayInitializer.java @@ -5,7 +5,22 @@ package net.sourceforge.pmd.lang.java.ast; -public class ASTMemberValueArrayInitializer extends AbstractJavaNode { + +import java.util.Iterator; + + +/** + * Represents an array of member values in an annotation {@linkplain ASTMemberValue member value}. + * + *
+ *
+ * MemberValueArrayInitializer ::= "{" ( {@linkplain ASTMemberValue MemberValue} ( "," {@linkplain ASTMemberValue MemberValue} )*  ","? )? "}"
+ *
+ * 
+ * + * + */ +public class ASTMemberValueArrayInitializer extends AbstractJavaNode implements Iterable { public ASTMemberValueArrayInitializer(int id) { super(id); } @@ -14,10 +29,14 @@ public ASTMemberValueArrayInitializer(JavaParser p, int id) { super(p, id); } - /** - * Accept the visitor. * - */ + @Override public Object jjtAccept(JavaParserVisitor visitor, Object data) { return visitor.visit(this, data); } + + + @Override + public Iterator iterator() { + return new NodeChildrenIterator<>(this, ASTMemberValue.class); + } } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTMemberValuePair.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTMemberValuePair.java index 6676dcab24d..4d1bb0aa3d3 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTMemberValuePair.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTMemberValuePair.java @@ -5,18 +5,49 @@ package net.sourceforge.pmd.lang.java.ast; +/** + * Represents a single member-value pair in an annotation. + * + *
+ *
+ * MemberValuePair ::=  <IDENTIFIER> "=" {@linkplain ASTMemberValue MemberValue}
+ *
+ * 
+ */ public class ASTMemberValuePair extends AbstractJavaNode { public ASTMemberValuePair(int id) { super(id); } + public ASTMemberValuePair(JavaParser p, int id) { super(p, id); } + /** - * Accept the visitor. * + * Returns the name of the member set by this pair. */ + public String getMemberName() { + return getImage(); + } + + + /** + * Returns the value of the member set by this pair. + */ + public ASTMemberValue getMemberValue() { + return (ASTMemberValue) jjtGetChild(0); + } + + + @Override + public ASTMemberValuePairs jjtGetParent() { + return (ASTMemberValuePairs) super.jjtGetParent(); + } + + + @Override public Object jjtAccept(JavaParserVisitor visitor, Object data) { return visitor.visit(this, data); } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTMemberValuePairs.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTMemberValuePairs.java index 7b26e3e78a5..6fffd2aa3ae 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTMemberValuePairs.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTMemberValuePairs.java @@ -5,19 +5,49 @@ package net.sourceforge.pmd.lang.java.ast; -public class ASTMemberValuePairs extends AbstractJavaNode { +import java.util.Iterator; + + +/** + * Represents a list of member values in an {@linkplain ASTNormalAnnotation annotation}. + * + *
+ *
+ *  MemberValuePairs ::= {@linkplain ASTMemberValuePair MemberValuePair} ( "," {@linkplain ASTMemberValuePair MemberValuePair} )*
+ *
+ * 
+ */ +public class ASTMemberValuePairs extends AbstractJavaNode implements Iterable { public ASTMemberValuePairs(int id) { super(id); } + public ASTMemberValuePairs(JavaParser p, int id) { super(p, id); } - /** - * Accept the visitor. * - */ + + @Override public Object jjtAccept(JavaParserVisitor visitor, Object data) { return visitor.visit(this, data); } + + + @Override + public ASTMemberValuePair jjtGetChild(int index) { + return (ASTMemberValuePair) super.jjtGetChild(index); + } + + + @Override + public ASTNormalAnnotation jjtGetParent() { + return (ASTNormalAnnotation) super.jjtGetParent(); + } + + + @Override + public Iterator iterator() { + return new NodeChildrenIterator<>(this, ASTMemberValuePair.class); + } } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTMethodDeclaration.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTMethodDeclaration.java index f9e93ac83ad..6f7909bcea5 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTMethodDeclaration.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTMethodDeclaration.java @@ -7,11 +7,17 @@ import net.sourceforge.pmd.lang.ast.Node; import net.sourceforge.pmd.lang.dfa.DFAGraphMethod; -import net.sourceforge.pmd.lang.java.metrics.signature.JavaOperationSignature; -public class ASTMethodDeclaration extends AbstractJavaAccessNode implements DFAGraphMethod, ASTMethodOrConstructorDeclaration { - private JavaQualifiedName qualifiedName; +/** + * Method declaration node. + * + *
+ * MethodDeclaration := [ TypeParameters() ] (TypeAnnotation())* ResultType() MethodDeclarator() [ "throws" NameList() ] ( Block() | ";" )
+ * 
+ * + */ +public class ASTMethodDeclaration extends AbstractMethodOrConstructorDeclaration implements DFAGraphMethod { public ASTMethodDeclaration(int id) { @@ -22,111 +28,130 @@ public ASTMethodDeclaration(JavaParser p, int id) { super(p, id); } - /** - * Accept the visitor. * - */ @Override public Object jjtAccept(JavaParserVisitor visitor, Object data) { return visitor.visit(this, data); } /** - * Gets the name of the method. - * - * @return a String representing the name of the method + * Returns the simple name of the method. */ public String getMethodName() { - ASTMethodDeclarator md = getFirstChildOfType(ASTMethodDeclarator.class); - if (md != null) { - return md.getImage(); - } - return null; + return getFirstChildOfType(ASTMethodDeclarator.class).getImage(); } + + @Override public String getName() { return getMethodName(); } + + /** + * Returns true if this method is explicitly modified by + * the {@code public} modifier. + */ public boolean isSyntacticallyPublic() { return super.isPublic(); } + + /** + * Returns true if this method is explicitly modified by + * the {@code abstract} modifier. + */ public boolean isSyntacticallyAbstract() { return super.isAbstract(); } + + /** + * Returns true if this method has public visibility. + * Non-private interface members are implicitly public, + * whether they declare the {@code public} modifier or + * not. + */ @Override public boolean isPublic() { - if (isInterfaceMember()) { - return true; - } - return super.isPublic(); + // interface methods are public by default, but could be private since java9 + return isInterfaceMember() && !isPrivate() || super.isPublic(); } + + /** + * Returns true if this method is abstract, so doesn't + * declare a body. Interface members are + * implicitly abstract, whether they declare the + * {@code abstract} modifier or not. + */ @Override public boolean isAbstract() { - if (isInterfaceMember()) { - return true; - } - return super.isAbstract(); + return isInterfaceMember() || super.isAbstract(); } + /** + * Returns true if this method declaration is a member of an interface type. + */ public boolean isInterfaceMember() { - ASTClassOrInterfaceDeclaration clz = getFirstParentOfType(ASTClassOrInterfaceDeclaration.class); - return clz != null && clz.isInterface(); + // for a real class/interface the 3rd parent is a ClassOrInterfaceDeclaration, + // for anonymous classes, the parent is e.g. a AllocationExpression + Node potentialTypeDeclaration = getNthParent(3); + + return potentialTypeDeclaration instanceof ASTClassOrInterfaceDeclaration + && ((ASTClassOrInterfaceDeclaration) potentialTypeDeclaration).isInterface(); } + + /** + * Returns true if the result type of this method is {@code void}. + */ public boolean isVoid() { return getResultType().isVoid(); } + + /** + * Returns the result type node of the method. + */ public ASTResultType getResultType() { return getFirstChildOfType(ASTResultType.class); } + + /** + * Returns the block defined by this method, or + * null if the method is abstract. + */ public ASTBlock getBlock() { - for (int i = 0; i < jjtGetNumChildren(); i++) { - Node n = jjtGetChild(i); - if (n instanceof ASTBlock) { - return (ASTBlock) n; - } - } - return null; + return getFirstChildOfType(ASTBlock.class); } + + /** + * Returns the exception names listed in the {@code throws} clause + * of this method declaration, or null if there are none. + */ public ASTNameList getThrows() { - int declaratorIndex = -1; - for (int i = 0; i < jjtGetNumChildren(); i++) { - Node child = jjtGetChild(i); - if (child instanceof ASTMethodDeclarator) { - declaratorIndex = i; - break; - } - } - // the throws declaration is immediately followed by the - // MethodDeclarator - if (jjtGetNumChildren() > declaratorIndex + 1) { - Node n = jjtGetChild(declaratorIndex + 1); - if (n instanceof ASTNameList) { - return (ASTNameList) n; - } - } - return null; + return getFirstChildOfType(ASTNameList.class); } @Override - public JavaQualifiedName getQualifiedName() { - if (qualifiedName == null) { - qualifiedName = JavaQualifiedName.ofOperation(this); - } - return qualifiedName; + public MethodLikeKind getKind() { + return MethodLikeKind.METHOD; } + //@Override // enable this with PMD 7.0.0 - see interface ASTMethodOrConstructorDeclaration + public ASTFormalParameters getFormalParameters() { + return getFirstChildOfType(ASTMethodDeclarator.class).getFirstChildOfType(ASTFormalParameters.class); + } - @Override - public JavaOperationSignature getSignature() { - return JavaOperationSignature.buildFor(this); + + /** + * Returns the method declarator. Never null. + */ + public ASTMethodDeclarator getMethodDeclarator() { + return getFirstChildOfType(ASTMethodDeclarator.class); } } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTMethodDeclarator.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTMethodDeclarator.java index 2d524c9c94a..61459b2a5a8 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTMethodDeclarator.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTMethodDeclarator.java @@ -15,12 +15,13 @@ public ASTMethodDeclarator(JavaParser p, int id) { } public int getParameterCount() { - return this.jjtGetChild(0).jjtGetNumChildren(); + return getFirstChildOfType(ASTFormalParameters.class).getParameterCount(); } /** * Accept the visitor. * */ + @Override public Object jjtAccept(JavaParserVisitor visitor, Object data) { return visitor.visit(this, data); } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTMethodOrConstructorDeclaration.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTMethodOrConstructorDeclaration.java index b141d01e2b3..d501dcad969 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTMethodOrConstructorDeclaration.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTMethodOrConstructorDeclaration.java @@ -5,18 +5,26 @@ package net.sourceforge.pmd.lang.java.ast; import net.sourceforge.pmd.lang.ast.SignedNode; -import net.sourceforge.pmd.lang.java.metrics.signature.JavaOperationSignature; +import net.sourceforge.pmd.lang.java.multifile.signature.JavaOperationSignature; + /** + * Groups method and constructor declarations under a common type. + * * @author Clément Fournier + * @see MethodLikeNode + * @since 5.8.1 */ -public interface ASTMethodOrConstructorDeclaration extends - SignedNode, - JavaQualifiableNode, - AccessNode, - JavaNode { - +public interface ASTMethodOrConstructorDeclaration extends MethodLikeNode, SignedNode { @Override JavaOperationSignature getSignature(); + // + // Enable this with PMD 7.0.0 + // + ///** + // * Returns the formal parameters node of this method or constructor. + // */ + //ASTFormalParameters getFormalParameters(); + } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTMethodReference.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTMethodReference.java index 23f1e41a2e1..fe6067f6b75 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTMethodReference.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTMethodReference.java @@ -16,6 +16,7 @@ public ASTMethodReference(JavaParser p, int id) { } /** Accept the visitor. **/ + @Override public Object jjtAccept(JavaParserVisitor visitor, Object data) { return visitor.visit(this, data); } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTModuleDeclaration.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTModuleDeclaration.java new file mode 100644 index 00000000000..e5c551c780b --- /dev/null +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTModuleDeclaration.java @@ -0,0 +1,34 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ +/* Generated By:JJTree: Do not edit this line. ASTModuleDeclaration.java Version 4.3 */ +/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=true,VISITOR=true,TRACK_TOKENS=true,NODE_PREFIX=AST,NODE_EXTENDS=,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ + +package net.sourceforge.pmd.lang.java.ast; + +public class ASTModuleDeclaration extends AbstractJavaNode { + private boolean open; + + public ASTModuleDeclaration(int id) { + super(id); + } + + public ASTModuleDeclaration(JavaParser p, int id) { + super(p, id); + } + + /** Accept the visitor. **/ + @Override + public Object jjtAccept(JavaParserVisitor visitor, Object data) { + return visitor.visit(this, data); + } + + public void setOpen(boolean open) { + this.open = open; + } + + public boolean isOpen() { + return open; + } +} +/* JavaCC - OriginalChecksum=752bbec72a6d0d96c4c69a2d08c73614 (do not edit this line) */ diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTModuleDirective.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTModuleDirective.java new file mode 100644 index 00000000000..71be3a8e360 --- /dev/null +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTModuleDirective.java @@ -0,0 +1,55 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ +/* Generated By:JJTree: Do not edit this line. ASTModuleDirective.java Version 4.3 */ +/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=true,VISITOR=true,TRACK_TOKENS=true,NODE_PREFIX=AST,NODE_EXTENDS=,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ + +package net.sourceforge.pmd.lang.java.ast; + +public class ASTModuleDirective extends AbstractJavaNode { + public enum DirectiveType { + REQUIRES, EXPORTS, OPENS, USES, PROVIDES; + } + + public enum RequiresModifier { + STATIC, TRANSITIVE; + } + + private DirectiveType type; + + private RequiresModifier requiresModifier; + + public ASTModuleDirective(int id) { + super(id); + } + + public ASTModuleDirective(JavaParser p, int id) { + super(p, id); + } + + /** Accept the visitor. **/ + @Override + public Object jjtAccept(JavaParserVisitor visitor, Object data) { + return visitor.visit(this, data); + } + + public void setType(DirectiveType type) { + this.type = type; + } + + public String getType() { + return String.valueOf(type); + } + + public void setRequiresModifier(RequiresModifier requiresModifier) { + this.requiresModifier = requiresModifier; + } + + public String getRequiresModifier() { + return requiresModifier == null ? null : requiresModifier.name(); + } +} +/* + * JavaCC - OriginalChecksum=93c74930e5df0269e81ce18b4efa6378 (do not edit this + * line) + */ diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTModuleName.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTModuleName.java new file mode 100644 index 00000000000..810b6c5861a --- /dev/null +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTModuleName.java @@ -0,0 +1,28 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +/* Generated By:JJTree: Do not edit this line. ASTModuleName.java Version 4.3 */ +/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=true,VISITOR=true,TRACK_TOKENS=true,NODE_PREFIX=AST,NODE_EXTENDS=,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ + +package net.sourceforge.pmd.lang.java.ast; + +public class ASTModuleName extends AbstractJavaNode { + public ASTModuleName(int id) { + super(id); + } + + public ASTModuleName(JavaParser p, int id) { + super(p, id); + } + + /** Accept the visitor. **/ + @Override + public Object jjtAccept(JavaParserVisitor visitor, Object data) { + return visitor.visit(this, data); + } +} +/* + * JavaCC - OriginalChecksum=7be9235079394543d4574d840ebb5235 (do not edit this + * line) + */ diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTMultiplicativeExpression.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTMultiplicativeExpression.java index 8392ce9e438..f01da959c69 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTMultiplicativeExpression.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTMultiplicativeExpression.java @@ -5,6 +5,20 @@ package net.sourceforge.pmd.lang.java.ast; +/** + * Represents a multiplication, division, or modulo operation on + * two or more values. This has a precedence greater than {@link ASTAdditiveExpression}, + * and lower than {@linkplain ASTUnaryExpression UnaryExpression}. + * + *

Note that the children of this node are not necessarily {@link ASTUnaryExpression}s, + * rather, they are expressions with an operator precedence greater or equal to UnaryExpression. + * + *

+ *
+ * MultiplicativeExpression ::= {@linkplain ASTUnaryExpression UnaryExpression} ( ( "*" | "/" | "%" ) {@linkplain ASTUnaryExpression UnaryExpression} )+
+ *
+ * 
+ */ public class ASTMultiplicativeExpression extends AbstractJavaTypeNode { public ASTMultiplicativeExpression(int id) { super(id); @@ -14,10 +28,16 @@ public ASTMultiplicativeExpression(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 image of the operator, i.e. "*", "/" or "%". + */ + public String getOperator() { + return getImage(); + } } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTNameList.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTNameList.java index c97d1346b33..f2e2c36b695 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTNameList.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTNameList.java @@ -17,6 +17,7 @@ public ASTNameList(JavaParser p, int id) { /** * Accept the visitor. * */ + @Override public Object jjtAccept(JavaParserVisitor visitor, Object data) { return visitor.visit(this, data); } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTNormalAnnotation.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTNormalAnnotation.java index acd60791b9b..971885de74d 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTNormalAnnotation.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTNormalAnnotation.java @@ -5,19 +5,47 @@ package net.sourceforge.pmd.lang.java.ast; +/** + * Represents an annotation that with a parenthesized list + * of key-value pairs (possibly empty). + * + *
+ *
+ * NormalAnnotation ::=  "@" {@linkplain ASTName Name} "(" {@linkplain ASTMemberValuePairs MemberValuePairs}? ")"
+ *
+ * 
+ * + * @see ASTSingleMemberAnnotation + * @see ASTMarkerAnnotation + */ public class ASTNormalAnnotation extends AbstractJavaTypeNode { public ASTNormalAnnotation(int id) { super(id); } + public ASTNormalAnnotation(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 annotation as it is used, + * eg {@code java.lang.Override} or {@code Override}. + */ + public String getAnnotationName() { + return jjtGetChild(0).getImage(); + } + + + @Override + public ASTAnnotation jjtGetParent() { + return (ASTAnnotation) super.jjtGetParent(); + } } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTNullLiteral.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTNullLiteral.java index b8a02253b42..1256c1a4e95 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTNullLiteral.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTNullLiteral.java @@ -17,6 +17,7 @@ public ASTNullLiteral(JavaParser p, int id) { /** * Accept the visitor. * */ + @Override public Object jjtAccept(JavaParserVisitor visitor, Object data) { return visitor.visit(this, data); } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTPackageDeclaration.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTPackageDeclaration.java index e4a74862544..2cba1c63ee2 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTPackageDeclaration.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTPackageDeclaration.java @@ -5,7 +5,7 @@ package net.sourceforge.pmd.lang.java.ast; -public class ASTPackageDeclaration extends AbstractJavaNode { +public class ASTPackageDeclaration extends AbstractJavaAnnotatableNode { public ASTPackageDeclaration(int id) { super(id); } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTPostfixExpression.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTPostfixExpression.java index 52da10a709e..f0dd5042372 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTPostfixExpression.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTPostfixExpression.java @@ -5,6 +5,17 @@ package net.sourceforge.pmd.lang.java.ast; +/** + * Represents a unary postfix operation on a value. + * This is one of the {@linkplain ASTUnaryExpression PrefixExpression} + * and has the same precedence. + * + *
+ *
+ * PostfixExpression ::= {@linkplain ASTPrimaryExpression PrimaryExpression} ( "++" | "--" )
+ *
+ * 
+ */ public class ASTPostfixExpression extends AbstractJavaTypeNode { public ASTPostfixExpression(int id) { @@ -15,11 +26,17 @@ public ASTPostfixExpression(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 image of this unary operator, i.e. "++" or "--". + */ + public String getOperator() { + return getImage(); + } + } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTPreDecrementExpression.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTPreDecrementExpression.java index 51b3c221c8d..ae7dc717e4f 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTPreDecrementExpression.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTPreDecrementExpression.java @@ -5,6 +5,17 @@ package net.sourceforge.pmd.lang.java.ast; +/** + * Represents a pre-decrement expression on a variable. + * This has the same precedence as {@linkplain ASTUnaryExpression UnaryExpression} + * and the like. + * + *
+ *
+ * PreDecrementExpression ::= "--" {@linkplain ASTPrimaryExpression PrimaryExpression}
+ *
+ * 
+ */ public class ASTPreDecrementExpression extends AbstractJavaTypeNode { public ASTPreDecrementExpression(int id) { super(id); @@ -14,9 +25,7 @@ public ASTPreDecrementExpression(JavaParser p, int id) { super(p, id); } - /** - * Accept the visitor. * - */ + @Override public Object jjtAccept(JavaParserVisitor visitor, Object data) { return visitor.visit(this, data); } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTPreIncrementExpression.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTPreIncrementExpression.java index e82287d93d1..0b2cae8132a 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTPreIncrementExpression.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTPreIncrementExpression.java @@ -5,6 +5,17 @@ package net.sourceforge.pmd.lang.java.ast; +/** + * Represents a pre-increment expression on a variable. + * This has the same precedence as {@linkplain ASTUnaryExpression UnaryExpression} + * and the like. + * + *
+ *
+ * PreIncrementExpression ::= "++" {@linkplain ASTPrimaryExpression PrimaryExpression}
+ *
+ * 
+ */ public class ASTPreIncrementExpression extends AbstractJavaTypeNode { public ASTPreIncrementExpression(int id) { super(id); @@ -14,9 +25,7 @@ public ASTPreIncrementExpression(JavaParser p, int id) { super(p, id); } - /** - * Accept the visitor. * - */ + @Override public Object jjtAccept(JavaParserVisitor visitor, Object data) { return visitor.visit(this, data); } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTPrimaryExpression.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTPrimaryExpression.java index 2b027318b82..92d645f0ff0 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTPrimaryExpression.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTPrimaryExpression.java @@ -17,6 +17,7 @@ public ASTPrimaryExpression(JavaParser p, int id) { /** * Accept the visitor. * */ + @Override public Object jjtAccept(JavaParserVisitor visitor, Object data) { return visitor.visit(this, data); } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTPrimarySuffix.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTPrimarySuffix.java index 7223999f7c9..dda473d8b12 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTPrimarySuffix.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTPrimarySuffix.java @@ -38,7 +38,7 @@ public boolean isArguments() { * Get the number of arguments for this primary suffix. One should call * {@link #isArguments()} to see if there are arguments. If this method is * called when there are no arguments it returns -1. - * + * * @return A non-negative argument number when there are arguments, * -1 otherwise. */ diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTPrimitiveType.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTPrimitiveType.java index 08bf88a52b7..c6eb62bd013 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTPrimitiveType.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTPrimitiveType.java @@ -5,6 +5,15 @@ package net.sourceforge.pmd.lang.java.ast; +/** + * Represents a primitive type. + * + *
+ *
+ * PrimitiveType ::= "boolean" | "char" | "byte" | "short" | "int" | "long" | "float" | "double"
+ *
+ * 
+ */ public class ASTPrimitiveType extends AbstractJavaTypeNode implements Dimensionable { private int arrayDepth; diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTRSIGNEDSHIFT.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTRSIGNEDSHIFT.java index 76c56123c94..4e287e8fad3 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTRSIGNEDSHIFT.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTRSIGNEDSHIFT.java @@ -5,6 +5,10 @@ package net.sourceforge.pmd.lang.java.ast; +/** + * @deprecated Will be removed in 7.0.0. Use {@link ASTShiftExpression#getOperator()} + */ +@Deprecated public class ASTRSIGNEDSHIFT extends AbstractJavaNode { public ASTRSIGNEDSHIFT(int id) { super(id); @@ -17,6 +21,7 @@ public ASTRSIGNEDSHIFT(JavaParser p, int id) { /** * Accept the visitor. * */ + @Override public Object jjtAccept(JavaParserVisitor visitor, Object data) { return visitor.visit(this, data); } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTRUNSIGNEDSHIFT.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTRUNSIGNEDSHIFT.java index 8314a4a795c..9e2568014d9 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTRUNSIGNEDSHIFT.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTRUNSIGNEDSHIFT.java @@ -5,6 +5,10 @@ package net.sourceforge.pmd.lang.java.ast; +/** + * @deprecated Will be removed in 7.0.0. Use {@link ASTShiftExpression#getOperator()} + */ +@Deprecated public class ASTRUNSIGNEDSHIFT extends AbstractJavaNode { public ASTRUNSIGNEDSHIFT(int id) { super(id); @@ -17,6 +21,7 @@ public ASTRUNSIGNEDSHIFT(JavaParser p, int id) { /** * Accept the visitor. * */ + @Override public Object jjtAccept(JavaParserVisitor visitor, Object data) { return visitor.visit(this, data); } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTReferenceType.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTReferenceType.java index 6adcc1369a7..b5f27149726 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTReferenceType.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTReferenceType.java @@ -5,6 +5,18 @@ package net.sourceforge.pmd.lang.java.ast; +/** + * Represents a reference type, i.e. a {@linkplain ASTClassOrInterfaceType class or interface type}, + * or an array type. + * + *
+ *
+ *  ReferenceType ::= {@linkplain ASTPrimitiveType PrimitiveType} {@linkplain ASTAnnotation Annotation}* ( "[" "]" )+
+ *                  | {@linkplain ASTClassOrInterfaceType ClassOrInterfaceType} {@linkplain ASTAnnotation Annotation}* ( "[" "]" )*
+ *
+ * 
+ * + */ public class ASTReferenceType extends AbstractJavaTypeNode implements Dimensionable { private int arrayDepth; diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTRelationalExpression.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTRelationalExpression.java index a60f0c149ec..43d84c36807 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTRelationalExpression.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTRelationalExpression.java @@ -5,6 +5,20 @@ package net.sourceforge.pmd.lang.java.ast; +/** + * Represents a comparison on two numeric values. This has a precedence greater than {@link ASTEqualityExpression}, + * and lower than {@link ASTShiftExpression}. This has the same precedence as a {@link ASTInstanceOfExpression}. + * + *

Note that the children of this node are not necessarily {@link ASTShiftExpression}, + * rather, they are expressions with an operator precedence greater or equal to ShiftExpression. + * + * + *

+ *
+ * RelationalExpression ::=  {@linkplain ASTShiftExpression ShiftExpression} ( ( "<" | ">" | "<=" | ">=" ) {@linkplain ASTShiftExpression ShiftExpression} )+
+ *
+ * 
+ */ public class ASTRelationalExpression extends AbstractJavaTypeNode { public ASTRelationalExpression(int id) { super(id); @@ -14,9 +28,7 @@ public ASTRelationalExpression(JavaParser p, int id) { super(p, id); } - /** - * Accept the visitor. * - */ + @Override public Object jjtAccept(JavaParserVisitor visitor, Object data) { return visitor.visit(this, data); } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTResource.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTResource.java index 89bc36899df..dc4b381423c 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTResource.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTResource.java @@ -6,8 +6,6 @@ package net.sourceforge.pmd.lang.java.ast; -import net.sourceforge.pmd.lang.ast.Node; - public class ASTResource extends ASTFormalParameter { public ASTResource(int id) { super(id); @@ -18,20 +16,10 @@ public ASTResource(JavaParser p, int id) { } /** Accept the visitor. **/ + @Override public Object jjtAccept(JavaParserVisitor visitor, Object data) { return visitor.visit(this, data); } - - @Override - protected ASTVariableDeclaratorId getDecl() { - for (int i = 0; i < jjtGetNumChildren(); i++) { - Node n = jjtGetChild(i); - if (n instanceof ASTVariableDeclaratorId) { - return (ASTVariableDeclaratorId) n; - } - } - return null; - } } /* * JavaCC - OriginalChecksum=92734fc70bba91fd9422150dbf87d5c4 (do not edit this diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTResourceSpecification.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTResourceSpecification.java index 7c9105459fa..f0610899dbe 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTResourceSpecification.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTResourceSpecification.java @@ -16,6 +16,7 @@ public ASTResourceSpecification(JavaParser p, int id) { } /** Accept the visitor. **/ + @Override public Object jjtAccept(JavaParserVisitor visitor, Object data) { return visitor.visit(this, data); } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTResources.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTResources.java index 079a2552d96..e93f3217949 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTResources.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTResources.java @@ -16,6 +16,7 @@ public ASTResources(JavaParser p, int id) { } /** Accept the visitor. **/ + @Override public Object jjtAccept(JavaParserVisitor visitor, Object data) { return visitor.visit(this, data); } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTResultType.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTResultType.java index 798105bb388..23120326054 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTResultType.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTResultType.java @@ -25,6 +25,7 @@ public boolean isVoid() { /** * Accept the visitor. * */ + @Override public Object jjtAccept(JavaParserVisitor visitor, Object data) { return visitor.visit(this, data); } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTReturnStatement.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTReturnStatement.java index 6f9d0f6d5cb..9ee9af2d8fb 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTReturnStatement.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTReturnStatement.java @@ -17,6 +17,7 @@ public ASTReturnStatement(JavaParser p, int id) { /** * Accept the visitor. * */ + @Override public Object jjtAccept(JavaParserVisitor visitor, Object data) { return visitor.visit(this, data); } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTShiftExpression.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTShiftExpression.java index 425db2b5950..bff49953937 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTShiftExpression.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTShiftExpression.java @@ -5,6 +5,21 @@ package net.sourceforge.pmd.lang.java.ast; +/** + * Represents a shift expression on a numeric value. This has a precedence greater than {@link ASTRelationalExpression}, + * and lower than {@link ASTAdditiveExpression}. + * + *

Note that the children of this node are not necessarily {@link ASTAdditiveExpression}, + * rather, they are expressions with an operator precedence greater or equal to AdditiveExpression. + * + * + *

+ *
+ * ShiftExpression ::=  {@linkplain ASTAdditiveExpression AdditiveExpression} ( ( "<<"  | {@linkplain ASTRSIGNEDSHIFT RSIGNEDSHIFT} | {@linkplain ASTRUNSIGNEDSHIFT RUNSIGNEDSHIFT} ) {@linkplain ASTAdditiveExpression AdditiveExpression} )+
+ *
+ * 
+ */ +// TODO we could merge the productions for ASTRSIGNEDSHIFT and ASTRUNSIGNEDSHIFT into this node using a #void production that sets the image of the parent public class ASTShiftExpression extends AbstractJavaTypeNode { public ASTShiftExpression(int id) { super(id); @@ -14,10 +29,17 @@ public ASTShiftExpression(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 image of the operator, i.e. "<<", ">>", or ">>>". + */ + public String getOperator() { + return getImage(); + } + } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTSingleMemberAnnotation.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTSingleMemberAnnotation.java index 4ef0a2541a5..c1b57d12f2b 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTSingleMemberAnnotation.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTSingleMemberAnnotation.java @@ -5,6 +5,18 @@ package net.sourceforge.pmd.lang.java.ast; +/** + * Represents an annotation using the shorthand syntax for the default member. + * + *
+ *
+ * SingleMemberAnnotation ::=  "@"  {@linkplain ASTName Name} "(" {@linkplain ASTMemberValue MemberValue} ")"
+ *
+ * 
+ * + * @see ASTMarkerAnnotation + * @see ASTNormalAnnotation + */ public class ASTSingleMemberAnnotation extends AbstractJavaTypeNode { public ASTSingleMemberAnnotation(int id) { super(id); @@ -14,10 +26,32 @@ public ASTSingleMemberAnnotation(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 value of the default member + * set by this annotation. + */ + public ASTMemberValue getMemberValue() { + return (ASTMemberValue) jjtGetChild(1); + } + + + /** + * Returns the name of the annotation as it is used, + * eg {@code java.lang.Override} or {@code Override}. + */ + public String getAnnotationName() { + return jjtGetChild(0).getImage(); + } + + + @Override + public ASTAnnotation jjtGetParent() { + return (ASTAnnotation) super.jjtGetParent(); + } } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTStatement.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTStatement.java index bb4ba883098..e48b71d27df 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTStatement.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTStatement.java @@ -17,6 +17,7 @@ public ASTStatement(JavaParser p, int id) { /** * Accept the visitor. * */ + @Override public Object jjtAccept(JavaParserVisitor visitor, Object data) { return visitor.visit(this, data); } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTStatementExpression.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTStatementExpression.java index 4eeac3bace5..a656219ba9a 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTStatementExpression.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTStatementExpression.java @@ -17,6 +17,7 @@ public ASTStatementExpression(JavaParser p, int id) { /** * Accept the visitor. * */ + @Override public Object jjtAccept(JavaParserVisitor visitor, Object data) { return visitor.visit(this, data); } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTStatementExpressionList.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTStatementExpressionList.java index fab8b21039f..3ab98ecee14 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTStatementExpressionList.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTStatementExpressionList.java @@ -17,6 +17,7 @@ public ASTStatementExpressionList(JavaParser p, int id) { /** * Accept the visitor. * */ + @Override public Object jjtAccept(JavaParserVisitor visitor, Object data) { return visitor.visit(this, data); } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTSwitchLabel.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTSwitchLabel.java index ee63b947627..64f1c56a84c 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTSwitchLabel.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTSwitchLabel.java @@ -5,6 +5,15 @@ package net.sourceforge.pmd.lang.java.ast; +/** + * Represents either a {@code case} or {@code default} label inside + * a {@linkplain ASTSwitchStatement switch statement}. + * + *
+ * SwitchLabel ::=  "case" {@linkplain ASTExpression Expression} ":"
+ *                | "default" ":"
+ * 
+ */ public class ASTSwitchLabel extends AbstractJavaNode { private boolean isDefault; diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTSwitchStatement.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTSwitchStatement.java index fc5e69d5254..e9ad7c7b018 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTSwitchStatement.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTSwitchStatement.java @@ -5,7 +5,22 @@ package net.sourceforge.pmd.lang.java.ast; -public class ASTSwitchStatement extends AbstractJavaNode { +import java.util.Iterator; +import java.util.Set; + +import org.apache.commons.lang3.EnumUtils; + + +/** + * Represents a {@code switch} statement. + * + *
+ *    SwitchStatement ::= "switch" "(" {@linkplain ASTExpression Expression} ")" "{"
+ *                        ( {@linkplain ASTSwitchLabel SwitchLabel} {@linkplain ASTBlockStatement BlockStatement}* )*
+ *                        "}"
+ * 
+ */ +public class ASTSwitchStatement extends AbstractJavaNode implements Iterable { public ASTSwitchStatement(int id) { super(id); } @@ -14,10 +29,70 @@ public ASTSwitchStatement(JavaParser p, int id) { super(p, id); } - /** - * Accept the visitor. * - */ + + @Override public Object jjtAccept(JavaParserVisitor visitor, Object data) { return visitor.visit(this, data); } + + + /** + * Returns true if this switch has a {@code default} case. + */ + public boolean hasDefaultCase() { + for (ASTSwitchLabel label : this) { + if (label.isDefault()) { + return true; + } + } + return false; + } + + + /** + * Gets the expression tested by this switch. + * This is the expression between the parentheses. + */ + public ASTExpression getTestedExpression() { + return (ASTExpression) jjtGetChild(0); + } + + + /** + * Returns true if this switch statement tests an expression + * having an enum type and all the constants of this type + * are covered by a switch case. Returns false if the type of + * the tested expression could not be resolved. + */ + public boolean isExhaustiveEnumSwitch() { + ASTExpression expression = getTestedExpression(); + + if (expression.getType() == null) { + return false; + } + + if (Enum.class.isAssignableFrom(expression.getType())) { + + @SuppressWarnings("unchecked") + Set constantNames = EnumUtils.getEnumMap((Class) expression.getType()).keySet(); + + for (ASTSwitchLabel label : this) { + // since this is an enum switch, the labels are necessarily + // the simple name of some enum constant. + + constantNames.remove(label.getFirstDescendantOfType(ASTName.class).getImage()); + + } + + return constantNames.isEmpty(); + } + + return false; + } + + + @Override + public Iterator iterator() { + return new NodeChildrenIterator<>(this, ASTSwitchLabel.class); + } } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTSynchronizedStatement.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTSynchronizedStatement.java index 7a1fbce1ae3..71dc35564df 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTSynchronizedStatement.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTSynchronizedStatement.java @@ -17,6 +17,7 @@ public ASTSynchronizedStatement(JavaParser p, int id) { /** * Accept the visitor. * */ + @Override public Object jjtAccept(JavaParserVisitor visitor, Object data) { return visitor.visit(this, data); } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTTryStatement.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTTryStatement.java index 5ff97724deb..53e3325c0c4 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTTryStatement.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTTryStatement.java @@ -5,6 +5,15 @@ package net.sourceforge.pmd.lang.java.ast; +import java.util.List; + + +/** + * Try statement node. + *
+ * TryStatement ::= "try" ( ResourceSpecification )? Block ( CatchStatement )* [ FinallyStatement ]
+ * 
+ */ public class ASTTryStatement extends AbstractJavaNode { public ASTTryStatement(int id) { @@ -15,30 +24,47 @@ public ASTTryStatement(JavaParser p, int id) { super(p, id); } - /** - * Accept the visitor. * - */ + + @Override public Object jjtAccept(JavaParserVisitor visitor, Object data) { return visitor.visit(this, data); } + + /** + * Returns true if this node is a try-with-resources, in which case it + * has a ResourceSpecification child node. + */ + public boolean isTryWithResources() { + return getFirstChildOfType(ASTResourceSpecification.class) != null; + } + + + /** + * Returns the catch statement nodes of this try statement. + * If there are none, returns an empty list. + */ + public List getCatchStatements() { + return findChildrenOfType(ASTCatchStatement.class); + } + + + /** + * Returns true if this try statement has a {@code finally} statement, + * in which case {@link #getFinally()} won't return {@code null}. + */ public boolean hasFinally() { - for (int i = 0; i < this.jjtGetNumChildren(); i++) { - if (jjtGetChild(i) instanceof ASTFinallyStatement) { - return true; - } - } - return false; + return getFirstChildOfType(ASTFinallyStatement.class) != null; } + + /** + * Returns the {@code finally} statement of this try statement, if any. + * + * @return The finally statement, or null if there is none + */ public ASTFinallyStatement getFinally() { - for (int i = 0; i < this.jjtGetNumChildren(); i++) { - if (jjtGetChild(i) instanceof ASTFinallyStatement) { - return (ASTFinallyStatement) jjtGetChild(i); - } - } - throw new RuntimeException( - "ASTTryStatement.getFinally called but this try stmt doesn't contain a finally block"); + return getFirstChildOfType(ASTFinallyStatement.class); } } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTType.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTType.java index bdd26c4f597..00d4e72ef37 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTType.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTType.java @@ -5,6 +5,17 @@ package net.sourceforge.pmd.lang.java.ast; +/** + * Represents a type reference. + * + *
+ *
+ * Type ::= {@linkplain ASTReferenceType ReferenceType} | {@linkplain ASTPrimitiveType PrimitiveType}
+ *
+ * 
+ * + * Note: it is not exactly the same the "UnnanType" defined in JLS. + */ public class ASTType extends AbstractJavaTypeNode { public ASTType(int id) { super(id); @@ -35,12 +46,10 @@ public int getArrayDepth() { && (jjtGetChild(0) instanceof ASTReferenceType || jjtGetChild(0) instanceof ASTPrimitiveType)) { return ((Dimensionable) jjtGetChild(0)).getArrayDepth(); } - throw new RuntimeException("ASTType.getArrayDepth called, but first child (of " + jjtGetNumChildren() - + " total children) is neither a primitive nor a reference type."); + return 0; // this is not an array } public boolean isArray() { return getArrayDepth() > 0; } - } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTTypeArgument.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTTypeArgument.java index 7b0ffb8ffd1..c17fff01fef 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTTypeArgument.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTTypeArgument.java @@ -5,6 +5,17 @@ package net.sourceforge.pmd.lang.java.ast; + +/** + * Represents a single type argument in a {@linkplain ASTTypeArguments type arguments list}. + * + *
+ *
+ * TypeArgument ::= ( {@linkplain ASTAnnotation Annotation} )* ( {@linkplain ASTReferenceType ReferenceType} | "?" {@linkplain ASTWildcardBounds WildcardBounds}? )
+ *
+ * 
+ */ +// TODO should implement Annotatable when we use can use Java 8 mixins instead of an abstract class public class ASTTypeArgument extends AbstractJavaTypeNode { public ASTTypeArgument(int id) { super(id); @@ -14,9 +25,25 @@ public ASTTypeArgument(JavaParser p, int id) { super(p, id); } + + /** + * Returns true if this node is a wildcard argument (bounded or not). + */ + public boolean isWildcard() { + return getTypeNode() == null; + } + + /** - * Accept the visitor. * + * Returns the type node of this type argument. + * Returns {@code null} if this is a wildcard argument. */ + public ASTReferenceType getTypeNode() { + return getFirstChildOfType(ASTReferenceType.class); + } + + + @Override public Object jjtAccept(JavaParserVisitor visitor, Object data) { return visitor.visit(this, data); } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTTypeArguments.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTTypeArguments.java index 0367669904f..e669576daf2 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTTypeArguments.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTTypeArguments.java @@ -5,7 +5,20 @@ package net.sourceforge.pmd.lang.java.ast; -public class ASTTypeArguments extends AbstractJavaNode { +import java.util.Iterator; + + +/** + * Represents a list of type arguments. This is different from {@linkplain ASTTypeParameters type parameters}! + * + *
+ *
+ *  TypeArguments ::= "<" {@linkplain ASTTypeArgument TypeArgument} ( "," {@linkplain ASTTypeArgument TypeArgument} )* ">"
+ *                  | "<" ">"
+ * 
+ * + */ +public class ASTTypeArguments extends AbstractJavaNode implements Iterable { public ASTTypeArguments(int id) { super(id); } @@ -14,10 +27,23 @@ public ASTTypeArguments(JavaParser p, int id) { super(p, id); } - /** - * Accept the visitor. * - */ + @Override public Object jjtAccept(JavaParserVisitor visitor, Object data) { return visitor.visit(this, data); } + + + /** + * Returns true if this is a diamond, that is, the + * actual type arguments are inferred. + */ + public boolean isDiamond() { + return jjtGetNumChildren() == 0; + } + + + @Override + public Iterator iterator() { + return new NodeChildrenIterator<>(this, ASTTypeArgument.class); + } } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTTypeBound.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTTypeBound.java index 3c0a5bf8453..e9d0cfaa0f7 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTTypeBound.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTTypeBound.java @@ -5,18 +5,43 @@ package net.sourceforge.pmd.lang.java.ast; +import java.util.List; + + +/** + * Represents a type bound on a {@linkplain ASTTypeParameter type parameter}. + * Type bounds specify the type of the type variable to which they apply as + * an intersection type. + * The first bound type is a class or interface type, while the additional + * bounds are necessarily interface types. + * + *
+ *
+ * TypeBound ::= "extends" {@linkplain ASTAnnotation Annotation}* {@linkplain ASTClassOrInterfaceType ClassOrInterfaceType} ( "&" {@linkplain ASTAnnotation Annotation}* {@linkplain ASTClassOrInterfaceType ClassOrInterfaceType} )*
+ *
+ * 
+ */ public class ASTTypeBound extends AbstractJavaTypeNode { public ASTTypeBound(int id) { super(id); } + public ASTTypeBound(JavaParser p, int id) { super(p, id); } + /** - * Accept the visitor. * + * Returns a list with the type bounds of this node. + * The returned list has at least one element. */ + public List getBoundTypeNodes() { + return findChildrenOfType(ASTClassOrInterfaceType.class); + } + + + @Override public Object jjtAccept(JavaParserVisitor visitor, Object data) { return visitor.visit(this, data); } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTTypeDeclaration.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTTypeDeclaration.java index 399825499e8..2db090fc2f8 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTTypeDeclaration.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTTypeDeclaration.java @@ -16,6 +16,7 @@ public ASTTypeDeclaration(JavaParser p, int id) { super(p, id); } + @Override public boolean hasSuppressWarningsAnnotationFor(Rule rule) { for (int i = 0; i < jjtGetNumChildren(); i++) { if (jjtGetChild(i) instanceof ASTAnnotation) { @@ -31,6 +32,7 @@ public boolean hasSuppressWarningsAnnotationFor(Rule rule) { /** * Accept the visitor. * */ + @Override public Object jjtAccept(JavaParserVisitor visitor, Object data) { return visitor.visit(this, data); } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTTypeParameter.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTTypeParameter.java index 6cd0ba9b37f..4b96b52262d 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTTypeParameter.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTTypeParameter.java @@ -5,18 +5,58 @@ package net.sourceforge.pmd.lang.java.ast; + +/** + * Represents a type parameter declaration of a method, constructor, class or interface declaration. + * + *
+ *
+ * TypeParameter ::= {@linkplain ASTAnnotation Annotation}* <IDENTIFIER> {@linkplain ASTTypeBound TypeBound}?
+ *
+ * 
+ * + * @see JLS + */ +// TODO should implement Annotatable when we use can use Java 8 mixins instead of an abstract class public class ASTTypeParameter extends AbstractJavaTypeNode { public ASTTypeParameter(int id) { super(id); } + public ASTTypeParameter(JavaParser p, int id) { super(p, id); } + + /** + * Returns the name of the type variable introduced by this declaration. + */ + public String getParameterName() { + return getImage(); + } + + /** - * Accept the visitor. * + * Returns true if this type parameter is bounded, + * in which case {@link #getTypeBoundNode()} doesn't + * return {@code null}. */ + public final boolean hasTypeBound() { + return getTypeBoundNode() != null; + } + + + /** + * Returns the type bound node of this parameter, + * or null if it is not bounded. + */ + public final ASTTypeBound getTypeBoundNode() { + return getFirstChildOfType(ASTTypeBound.class); + } + + + @Override public Object jjtAccept(JavaParserVisitor visitor, Object data) { return visitor.visit(this, data); } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTTypeParameters.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTTypeParameters.java index c9583efa90d..c63d37f0bbd 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTTypeParameters.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTTypeParameters.java @@ -5,7 +5,22 @@ package net.sourceforge.pmd.lang.java.ast; -public class ASTTypeParameters extends AbstractJavaNode { + +import java.util.Iterator; + + +/** + * Represents a list of type parameters. + * + *
+ *
+ * TypeParameters ::= "<" {@linkplain ASTTypeParameter TypeParameter} ( "," {@linkplain ASTTypeParameter TypeParameter} )* ">"
+ *
+ * 
+ * + * + */ +public class ASTTypeParameters extends AbstractJavaNode implements Iterable { public ASTTypeParameters(int id) { super(id); } @@ -14,10 +29,14 @@ public ASTTypeParameters(JavaParser p, int id) { super(p, id); } - /** - * Accept the visitor. * - */ + @Override public Object jjtAccept(JavaParserVisitor visitor, Object data) { return visitor.visit(this, data); } + + + @Override + public Iterator iterator() { + return new NodeChildrenIterator<>(this, ASTTypeParameter.class); + } } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTUnaryExpression.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTUnaryExpression.java index 6cf4a2a95aa..e2ab159afda 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTUnaryExpression.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTUnaryExpression.java @@ -5,6 +5,26 @@ package net.sourceforge.pmd.lang.java.ast; + +/** + * Represents a unary prefix operation on a value. + * This has a precedence greater than {@link ASTMultiplicativeExpression}. + * + *

UnaryExpression has the same precedence as {@linkplain ASTPreIncrementExpression PreIncrementExpression}, + * {@linkplain ASTPreDecrementExpression PreDecrementExpression} and + * {@linkplain ASTUnaryExpressionNotPlusMinus UnaryExpressionNotPlusMinus}. + * + *

Note that the child of this node is not necessarily a UnaryExpression, + * rather, it can be an expression with an operator precedence greater or equal + * to a UnaryExpression. + * + * + *

+ *
+ * UnaryExpression ::= ( "+" | "-" ) UnaryExpression
+ *
+ * 
+ */ public class ASTUnaryExpression extends AbstractJavaTypeNode { public ASTUnaryExpression(int id) { super(id); @@ -14,11 +34,17 @@ public ASTUnaryExpression(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 image of this unary operator, i.e. "+" or "-". + */ + public String getOperator() { + return getImage(); + } + } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTUnaryExpressionNotPlusMinus.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTUnaryExpressionNotPlusMinus.java index e55416a10bd..8b9c9c359bb 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTUnaryExpressionNotPlusMinus.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTUnaryExpressionNotPlusMinus.java @@ -5,6 +5,20 @@ package net.sourceforge.pmd.lang.java.ast; +/** + * Represents a boolean negation or bitwise inverse operation. + * This has the same precedence as {@linkplain ASTUnaryExpression UnaryExpression} + * and the like. + * + *

Note that the child of this node is not necessarily an {@link ASTUnaryExpression}, + * rather, it can be an expression with an operator precedence greater or equal to a UnaryExpression. + * + *

+ *
+ * UnaryExpressionNotPlusMinus ::=  ( "~" | "!" ) {@linkplain ASTUnaryExpression UnaryExpression}
+ *
+ * 
+ */ public class ASTUnaryExpressionNotPlusMinus extends AbstractJavaTypeNode { public ASTUnaryExpressionNotPlusMinus(int id) { super(id); @@ -14,11 +28,18 @@ public ASTUnaryExpressionNotPlusMinus(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 image of this unary operator, i.e. "~" or "!". + */ + public String getOperator() { + return getImage(); + } + + } 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 dc12212ff12..7d28205df63 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,19 +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 445a59d0462..5a549e59775 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 @@ -11,7 +11,30 @@ import net.sourceforge.pmd.lang.java.symboltable.VariableNameDeclaration; import net.sourceforge.pmd.lang.symboltable.NameOccurrence; -public class ASTVariableDeclaratorId extends AbstractJavaTypeNode { +// @formatter:off +/** + * Represents an identifier in the context of variable or parameter declarations (not their use in + * expressions). Such a node declares a name in the scope it's defined in, and can occur in the following + * contexts: + * + *
    + *
  • Field declarations; + *
  • Local variable declarations; + *
  • Method, constructor and lambda parameter declarations; + *
  • Method and constructor explicit receiver parameter declarations; + *
  • Exception parameter declarations occurring in catch clauses; + *
  • Resource declarations occurring in try-with-resources statements. + *
+ * + *

Since this node conventionally represents the declared variable in PMD, our symbol table + * populates it with a {@link VariableNameDeclaration}, and its usages can be accessed through + * the method {@link #getUsages()}. + * + *

Type resolution assigns the type of the variable to this node. See {@link #getType()}'s + * documentation for the contract of this method. + */ +// @formatter:on +public class ASTVariableDeclaratorId extends AbstractJavaTypeNode implements Dimensionable { private int arrayDepth; private VariableNameDeclaration nameDeclaration; @@ -25,9 +48,6 @@ public ASTVariableDeclaratorId(JavaParser p, int id) { super(p, id); } - /** - * Accept the visitor. * - */ @Override public Object jjtAccept(JavaParserVisitor visitor, Object data) { return visitor.visit(this, data); @@ -45,52 +65,204 @@ public List getUsages() { return getScope().getDeclarations(VariableNameDeclaration.class).get(nameDeclaration); } + // TODO Dimensionable will be deprecated + public void bumpArrayDepth() { arrayDepth++; } + @Override public int getArrayDepth() { return arrayDepth; } + @Override public boolean isArray() { return arrayDepth > 0; } + + /** + * Returns true if this nodes declares an exception parameter in + * a {@code catch} statement. + */ public boolean isExceptionBlockParameter() { - return jjtGetParent().jjtGetParent() instanceof ASTTryStatement; + return jjtGetParent().jjtGetParent() instanceof ASTCatchStatement; + } + + + /** + * Returns true if this node declares a formal parameter for a method + * declaration or a lambda expression. In particular, returns false + * if the node is a receiver parameter (see {@link #isExplicitReceiverParameter()}). + */ + public boolean isFormalParameter() { + return jjtGetParent() instanceof ASTFormalParameter && !isExceptionBlockParameter() && !isResourceDeclaration() + || isLambdaParamWithNoType(); + } + + + /** + * Returns true if this node declares a local variable. + */ + public boolean isLocalVariable() { + return getNthParent(2) instanceof ASTLocalVariableDeclaration; + } + + + /** + * Returns true if this node declares a formal parameter for + * a lambda expression. In that case, the type of this parameter + * is not necessarily inferred, see {@link #isTypeInferred()}. + */ + public boolean isLambdaParameter() { + return isLambdaParamWithNoType() || jjtGetParent() instanceof ASTFormalParameter && getNthParent(3) instanceof ASTLambdaExpression; + } + + + private boolean isLambdaParamWithNoType() { + return jjtGetParent() instanceof ASTLambdaExpression; + } + + + /** + * Returns true if this node declares a field. + */ + public boolean isField() { + return getNthParent(2) instanceof ASTFieldDeclaration; + } + + + /** + * Returns the name of the variable. + */ + public String getVariableName() { + return getImage(); + } + + + /** + * Returns true if the variable declared by this node is declared final. + * Doesn't account for the "effectively-final" nuance. Resource + * declarations are implicitly final. + */ + public boolean isFinal() { + if (isResourceDeclaration()) { + // this is implicit even if "final" is not explicitly declared. + return true; + } else if (isLambdaParamWithNoType()) { + return false; + } + + if (jjtGetParent() instanceof ASTFormalParameter) { + // This accounts for exception parameters too for now + return ((ASTFormalParameter) jjtGetParent()).isFinal(); + } + + Node grandpa = getNthParent(2); + + if (grandpa instanceof ASTLocalVariableDeclaration) { + return ((ASTLocalVariableDeclaration) grandpa).isFinal(); + } else if (grandpa instanceof ASTFieldDeclaration) { + return ((ASTFieldDeclaration) grandpa).isFinal(); + } + + throw new IllegalStateException("All cases should be handled"); } + + /** + * @deprecated Will be made private with 7.0.0 + */ + @Deprecated public void setExplicitReceiverParameter() { explicitReceiverParameter = true; } + + /** + * Returns true if this node is a receiver parameter for a method or constructor + * declaration. The receiver parameter has the name {@code this}, and must be declared + * at the beginning of the parameter list. Its only purpose is to annotate + * the type of the object on which the method call is issued. It was introduced + * in Java 8. + */ public boolean isExplicitReceiverParameter() { + // TODO this could be inferred from the image tbh return explicitReceiverParameter; } - public Node getTypeNameNode() { - if (jjtGetParent() instanceof ASTFormalParameter) { - return findTypeNameNode(jjtGetParent()); - } else if (jjtGetParent() instanceof ASTLambdaExpression) { - // lambda expression with lax types. The type is inferred... - return null; - } else if (jjtGetParent().jjtGetParent() instanceof ASTLocalVariableDeclaration - || jjtGetParent().jjtGetParent() instanceof ASTFieldDeclaration) { - return findTypeNameNode(jjtGetParent().jjtGetParent()); + + /** + * Returns true if this declarator id declares a resource in a try-with-resources statement. + */ + public boolean isResourceDeclaration() { + return jjtGetParent() instanceof ASTResource; + } + + + /** + * Returns true if the declared variable's type is inferred by + * the compiler. In Java 8, this can happen if it's in a formal + * parameter of a lambda with an inferred type (e.g. {@code (a, b) -> a + b}). + * Since Java 10, the type of local variables can be inferred + * too, e.g. {@code var i = 2;}. + * + *

This method returns true for declarator IDs in those contexts, + * in which case {@link #getTypeNode()} returns {@code null}, + * since the type node is absent. + */ + public boolean isTypeInferred() { + return isLambdaParamWithNoType() || isLocalVariableTypeInferred() || isLambdaTypeInferred(); + } + + + private boolean isLocalVariableTypeInferred() { + if (isResourceDeclaration()) { + // covers "var" in try-with-resources + return jjtGetParent().getFirstChildOfType(ASTType.class) == null; + } else if (getNthParent(2) instanceof ASTLocalVariableDeclaration) { + // covers "var" as local variables and in for statements + return getNthParent(2).getFirstChildOfType(ASTType.class) == null; } - return null; + + return false; + } + + private boolean isLambdaTypeInferred() { + return getNthParent(3) instanceof ASTLambdaExpression + && jjtGetParent().getFirstChildOfType(ASTType.class) == null; } /** - * Determines the type node of this variable id. - * - * @return the type node or null if there is no explicit type. + * 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 + * type. + */ + // TODO unreliable, not typesafe and not useful, should be deprecated + public Node getTypeNameNode() { + ASTType type = getTypeNode(); + return type == null ? null : getTypeNode().jjtGetChild(0); + } + + + /** + * Determines the type node of this variable id, that is, the type node + * belonging to the variable declaration of this node (either a + * FormalParameter, LocalVariableDeclaration or FieldDeclaration). + * + *

The type of the returned node is not necessarily the type of this + * node. See {@link #getType()} for an explanation. + * + * @return the type node, or {@code null} if there is no explicit type, + * e.g. if {@link #isTypeInferred()} returns true. */ public ASTType getTypeNode() { if (jjtGetParent() instanceof ASTFormalParameter) { + // ASTResource is a subclass of ASTFormal parameter for now but this will change + // and this will need to be corrected here, see #998 return ((ASTFormalParameter) jjtGetParent()).getTypeNode(); - } else if (jjtGetParent() instanceof ASTLambdaExpression) { + } else if (isTypeInferred()) { // lambda expression with lax types. The type is inferred... return null; } else { @@ -102,13 +274,33 @@ public ASTType getTypeNode() { return null; } - private Node findTypeNameNode(Node node) { - int i = 0; - while (node.jjtGetChild(i) instanceof ASTAnnotation) { - // skip annotations - i++; - } - ASTType typeNode = (ASTType) node.jjtGetChild(i); - return typeNode.jjtGetChild(0); + // @formatter:off + /** + * Returns the type of the declared variable. The type of a declarator ID is + *

    + *
  • 1. not necessarily the same as the type written out at the + * start of the declaration, e.g. {@code int a[];} + *
  • 2. not necessarily the same as the types of other variables + * declared in the same statement, e.g. {@code int a[], b;}. + *
+ * + *

These are consequences of Java's allowing programmers to + * declare additional pairs of brackets on declarator ids. The type + * of the node returned by {@link #getTypeNode()} doesn't take into + * account those additional array dimensions, whereas this node's + * type takes into account the total number of dimensions, i.e. + * those declared on this node plus those declared on the type node. + * + *

The returned type also takes into account whether this variable + * is a varargs formal parameter. + * + *

The type of the declarator ID is thus always the real type of + * the variable. + */ + // @formatter:on + @Override + @SuppressWarnings("PMD.UselessOverridingMethod") + public Class getType() { + return super.getType(); } } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTVariableInitializer.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTVariableInitializer.java index a10b3197d32..0c431640181 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTVariableInitializer.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTVariableInitializer.java @@ -17,6 +17,7 @@ public ASTVariableInitializer(JavaParser p, int id) { /** * Accept the visitor. * */ + @Override public Object jjtAccept(JavaParserVisitor visitor, Object data) { return visitor.visit(this, data); } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTWhileStatement.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTWhileStatement.java index ee7891ada10..57d3850d977 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTWhileStatement.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTWhileStatement.java @@ -17,6 +17,7 @@ public ASTWhileStatement(JavaParser p, int id) { /** * Accept the visitor. * */ + @Override public Object jjtAccept(JavaParserVisitor visitor, Object data) { return visitor.visit(this, data); } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTWildcardBounds.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTWildcardBounds.java index 5c0aa5a72b9..ffc01d08d6b 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTWildcardBounds.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/ASTWildcardBounds.java @@ -5,18 +5,54 @@ package net.sourceforge.pmd.lang.java.ast; +/** + * Represents a type bound on a wildcard {@linkplain ASTTypeArgument type argument}. + * + *

+ *
+ * WildcardBounds ::=  ( "extends" | "super" ) ( {@linkplain ASTAnnotation Annotation} )* {@linkplain ASTReferenceType ReferenceType}
+ *
+ * 
+ */ public class ASTWildcardBounds extends AbstractJavaTypeNode { public ASTWildcardBounds(int id) { super(id); } + public ASTWildcardBounds(JavaParser p, int id) { super(p, id); } + + /** + * Returns true if this is an upper type bound, e.g. + * in {@code }. + */ + public boolean isUpperBound() { + return jjtGetFirstToken().toString().equals("extends"); + } + + /** - * Accept the visitor. * + * Returns true if this is a lower type bound, e.g. + * in {@code }. */ + public boolean isLowerBound() { + return !isUpperBound(); + } + + + /** + * Returns the type node representing the bound, e.g. + * the {@code Node} in {@code }. + */ + public ASTReferenceType getTypeBoundNode() { + return getFirstChildOfType(ASTReferenceType.class); + } + + + @Override public Object jjtAccept(JavaParserVisitor visitor, Object data) { return visitor.visit(this, data); } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/AbstractAnyTypeDeclaration.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/AbstractAnyTypeDeclaration.java new file mode 100644 index 00000000000..2d56c46e39b --- /dev/null +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/AbstractAnyTypeDeclaration.java @@ -0,0 +1,88 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.ast; + +import net.sourceforge.pmd.lang.ast.Node; +import net.sourceforge.pmd.lang.java.qname.JavaTypeQualifiedName; +import net.sourceforge.pmd.lang.java.typeresolution.typedefinition.JavaTypeDefinition; + + +/** + * Abstract class for type declarations nodes. + */ +public abstract class AbstractAnyTypeDeclaration extends AbstractJavaAccessTypeNode implements ASTAnyTypeDeclaration { + + private JavaTypeQualifiedName qualifiedName; + + + AbstractAnyTypeDeclaration(int i) { + super(i); + } + + + AbstractAnyTypeDeclaration(JavaParser parser, int i) { + super(parser, i); + } + + + @Override + public final boolean isNested() { + return jjtGetParent() instanceof ASTClassOrInterfaceBodyDeclaration + || jjtGetParent() instanceof ASTAnnotationTypeMemberDeclaration; + } + + + /** + * Returns true if the enclosing type of this type declaration + * is any of the given kinds. If this declaration is a top-level + * declaration, returns false. This won't consider anonymous classes + * until #905 is tackled. TODO 7.0.0 + * + * @param kinds Kinds to test + */ + // TODO 7.0.0 move that up to ASTAnyTypeDeclaration + public final boolean enclosingTypeIsA(TypeKind... kinds) { + + ASTAnyTypeDeclaration parent = getEnclosingTypeDeclaration(); + if (parent == null) { + return false; + } + + for (TypeKind k : kinds) { + if (parent.getTypeKind() == k) { + return true; + } + } + + return false; + } + + + /** + * Returns the enclosing type of this type, if it is nested. + * Otherwise returns null. This won't consider anonymous classes + * until #905 is tackled. TODO 7.0.0 + */ + public final ASTAnyTypeDeclaration getEnclosingTypeDeclaration() { + if (!isNested()) { + return null; + } + Node parent = getNthParent(3); + + return parent instanceof ASTAnyTypeDeclaration ? (ASTAnyTypeDeclaration) parent : null; + } + + @Override + public final JavaTypeQualifiedName getQualifiedName() { + return qualifiedName; + } + + + public void setQualifiedName(JavaTypeQualifiedName qualifiedName) { + this.qualifiedName = qualifiedName; + this.typeDefinition = JavaTypeDefinition.forClass(qualifiedName.getType()); + } +} + diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/AbstractJavaAccessNode.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/AbstractJavaAccessNode.java index a63b5141597..da3063423ef 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/AbstractJavaAccessNode.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/AbstractJavaAccessNode.java @@ -4,7 +4,7 @@ package net.sourceforge.pmd.lang.java.ast; -public abstract class AbstractJavaAccessNode extends AbstractJavaNode implements AccessNode { +public abstract class AbstractJavaAccessNode extends AbstractJavaAnnotatableNode implements AccessNode { private int modifiers; @@ -16,112 +16,136 @@ public AbstractJavaAccessNode(JavaParser parser, int i) { super(parser, i); } + @Override public int getModifiers() { return this.modifiers; } + @Override public void setModifiers(int modifiers) { this.modifiers = modifiers; } + @Override public boolean isPublic() { return isModifier(PUBLIC); } + @Override public void setPublic(boolean isPublic) { setModifier(isPublic, PUBLIC); } + @Override public boolean isProtected() { return isModifier(PROTECTED); } + @Override public void setProtected(boolean isProtected) { setModifier(isProtected, PROTECTED); } + @Override public boolean isPrivate() { return isModifier(PRIVATE); } + @Override public void setPrivate(boolean isPrivate) { setModifier(isPrivate, PRIVATE); } + @Override public boolean isAbstract() { return isModifier(ABSTRACT); } + @Override public void setAbstract(boolean isAbstract) { setModifier(isAbstract, ABSTRACT); } + @Override public boolean isStatic() { return isModifier(STATIC); } + @Override public void setStatic(boolean isStatic) { setModifier(isStatic, STATIC); } + @Override public boolean isFinal() { return isModifier(FINAL); } + @Override public void setFinal(boolean isFinal) { setModifier(isFinal, FINAL); } + @Override public boolean isSynchronized() { return isModifier(SYNCHRONIZED); } + @Override public void setSynchronized(boolean isSynchronized) { setModifier(isSynchronized, SYNCHRONIZED); } + @Override public boolean isNative() { return isModifier(NATIVE); } + @Override public void setNative(boolean isNative) { setModifier(isNative, NATIVE); } + @Override public boolean isTransient() { return isModifier(TRANSIENT); } + @Override public void setTransient(boolean isTransient) { setModifier(isTransient, TRANSIENT); } + @Override public boolean isVolatile() { return isModifier(VOLATILE); } + @Override public void setVolatile(boolean isVolative) { setModifier(isVolative, VOLATILE); } + @Override public boolean isStrictfp() { return isModifier(STRICTFP); } + @Override public void setStrictfp(boolean isStrictfp) { setModifier(isStrictfp, STRICTFP); } + @Override public boolean isDefault() { return isModifier(DEFAULT); } + @Override public void setDefault(boolean isDefault) { setModifier(isDefault, DEFAULT); } - // TODO: fix the rule - around binary expressions the parentheses are needed... - @SuppressWarnings("PMD.UselessParentheses") private boolean isModifier(int mask) { return (modifiers & mask) == mask; } @@ -134,7 +158,9 @@ private void setModifier(boolean enable, int mask) { } } + @Override public boolean isPackagePrivate() { return !isPrivate() && !isPublic() && !isProtected(); } } + diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/AbstractJavaAccessTypeNode.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/AbstractJavaAccessTypeNode.java index 72895afefc0..220a3c13dde 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/AbstractJavaAccessTypeNode.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/AbstractJavaAccessTypeNode.java @@ -7,7 +7,10 @@ import net.sourceforge.pmd.lang.java.typeresolution.typedefinition.JavaTypeDefinition; public abstract class AbstractJavaAccessTypeNode extends AbstractJavaAccessNode implements TypeNode { - private JavaTypeDefinition typeDefinition; + /** + * Type definition, used to get the type of the node. + */ + protected JavaTypeDefinition typeDefinition; public AbstractJavaAccessTypeNode(int i) { super(i); diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/AbstractJavaAnnotatableNode.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/AbstractJavaAnnotatableNode.java new file mode 100644 index 00000000000..36ae852ba26 --- /dev/null +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/AbstractJavaAnnotatableNode.java @@ -0,0 +1,53 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.ast; + +import java.util.Collection; +import java.util.List; + +import net.sourceforge.pmd.lang.java.typeresolution.TypeHelper; + +abstract class AbstractJavaAnnotatableNode extends AbstractJavaNode implements Annotatable { + + AbstractJavaAnnotatableNode(int i) { + super(i); + } + + AbstractJavaAnnotatableNode(JavaParser parser, int i) { + super(parser, i); + } + + @Override + public List getDeclaredAnnotations() { + return this.jjtGetParent().findChildrenOfType(ASTAnnotation.class); + } + + @Override + public ASTAnnotation getAnnotation(String annotQualifiedName) { + List annotations = getDeclaredAnnotations(); + for (ASTAnnotation annotation : annotations) { + ASTName name = annotation.getFirstDescendantOfType(ASTName.class); + if (name != null && TypeHelper.isA(name, annotQualifiedName)) { + return annotation; + } + } + return null; + } + + @Override + public boolean isAnnotationPresent(String annotQualifiedName) { + return getAnnotation(annotQualifiedName) != null; + } + + @Override + public boolean isAnyAnnotationPresent(Collection annotQualifiedNames) { + for (String annotQualifiedName : annotQualifiedNames) { + if (isAnnotationPresent(annotQualifiedName)) { + return true; + } + } + return false; + } +} diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/AbstractJavaNode.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/AbstractJavaNode.java index ab6130ea2fc..acf4106d455 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/AbstractJavaNode.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/AbstractJavaNode.java @@ -22,6 +22,7 @@ public AbstractJavaNode(JavaParser parser, int id) { this.parser = parser; } + @Override public void jjtOpen() { if (beginLine == -1 && parser.token.next != null) { beginLine = parser.token.next.beginLine; @@ -29,6 +30,7 @@ public void jjtOpen() { } } + @Override public void jjtClose() { if (beginLine == -1 && (children == null || children.length == 0)) { beginColumn = parser.token.beginColumn; @@ -43,6 +45,7 @@ public void jjtClose() { /** * Accept the visitor. * */ + @Override public Object jjtAccept(JavaParserVisitor visitor, Object data) { return visitor.visit(this, data); } @@ -50,6 +53,7 @@ public Object jjtAccept(JavaParserVisitor visitor, Object data) { /** * Accept the visitor. * */ + @Override public Object childrenAccept(JavaParserVisitor visitor, Object data) { if (children != null) { for (int i = 0; i < children.length; ++i) { @@ -59,6 +63,7 @@ public Object childrenAccept(JavaParserVisitor visitor, Object data) { return data; } + @Override public Scope getScope() { if (scope == null) { return ((JavaNode) parent).getScope(); @@ -66,6 +71,7 @@ public Scope getScope() { return scope; } + @Override public void setScope(Scope scope) { this.scope = scope; } @@ -78,7 +84,10 @@ public Comment comment() { return comment; } - public String toString() { + + + @Override + public final String getXPathNodeName() { return JavaParserTreeConstants.jjtNodeName[id]; } } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/AbstractJavaTypeNode.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/AbstractJavaTypeNode.java index 3bb3eb148cb..8ade1689e3b 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/AbstractJavaTypeNode.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/AbstractJavaTypeNode.java @@ -25,11 +25,7 @@ public AbstractJavaTypeNode(JavaParser p, int i) { @Override public Class getType() { - if (typeDefinition != null) { - return typeDefinition.getType(); - } - - return null; + return typeDefinition == null ? null : typeDefinition.getType(); } @Override diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/AbstractMethodLikeNode.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/AbstractMethodLikeNode.java new file mode 100644 index 00000000000..6e60b8639b5 --- /dev/null +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/AbstractMethodLikeNode.java @@ -0,0 +1,34 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.ast; + +import net.sourceforge.pmd.lang.java.qname.JavaOperationQualifiedName; + + +public abstract class AbstractMethodLikeNode extends AbstractJavaAccessNode implements MethodLikeNode { + private JavaOperationQualifiedName qualifiedName; + + + AbstractMethodLikeNode(int i) { + super(i); + } + + + AbstractMethodLikeNode(JavaParser parser, int i) { + super(parser, i); + } + + + public void setQualifiedName(JavaOperationQualifiedName qualifiedName) { + this.qualifiedName = qualifiedName; + } + + + @Override + public JavaOperationQualifiedName getQualifiedName() { + return qualifiedName; + } + +} diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/AbstractMethodOrConstructorDeclaration.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/AbstractMethodOrConstructorDeclaration.java new file mode 100644 index 00000000000..5eba9d6c090 --- /dev/null +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/AbstractMethodOrConstructorDeclaration.java @@ -0,0 +1,33 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.ast; + +import net.sourceforge.pmd.lang.java.multifile.signature.JavaOperationSignature; + + +public abstract class AbstractMethodOrConstructorDeclaration extends AbstractMethodLikeNode implements ASTMethodOrConstructorDeclaration { + + private JavaOperationSignature signature; + + + AbstractMethodOrConstructorDeclaration(int i) { + super(i); + } + + + AbstractMethodOrConstructorDeclaration(JavaParser parser, int i) { + super(parser, i); + } + + + @Override + public JavaOperationSignature getSignature() { + if (signature == null) { + signature = JavaOperationSignature.buildFor(this); + } + + return signature; + } +} diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/AbstractTypeBodyDeclaration.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/AbstractTypeBodyDeclaration.java new file mode 100644 index 00000000000..92eedb762a5 --- /dev/null +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/AbstractTypeBodyDeclaration.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 net.sourceforge.pmd.lang.java.ast.ASTAnyTypeBodyDeclaration.DeclarationKind.ANNOTATION; +import static net.sourceforge.pmd.lang.java.ast.ASTAnyTypeBodyDeclaration.DeclarationKind.ANNOTATION_METHOD; +import static net.sourceforge.pmd.lang.java.ast.ASTAnyTypeBodyDeclaration.DeclarationKind.CLASS; +import static net.sourceforge.pmd.lang.java.ast.ASTAnyTypeBodyDeclaration.DeclarationKind.CONSTRUCTOR; +import static net.sourceforge.pmd.lang.java.ast.ASTAnyTypeBodyDeclaration.DeclarationKind.EMPTY; +import static net.sourceforge.pmd.lang.java.ast.ASTAnyTypeBodyDeclaration.DeclarationKind.ENUM; +import static net.sourceforge.pmd.lang.java.ast.ASTAnyTypeBodyDeclaration.DeclarationKind.FIELD; +import static net.sourceforge.pmd.lang.java.ast.ASTAnyTypeBodyDeclaration.DeclarationKind.INITIALIZER; +import static net.sourceforge.pmd.lang.java.ast.ASTAnyTypeBodyDeclaration.DeclarationKind.INTERFACE; +import static net.sourceforge.pmd.lang.java.ast.ASTAnyTypeBodyDeclaration.DeclarationKind.METHOD; + + +/** + * @author Clément Fournier + * @since 6.2.0 + */ +abstract class AbstractTypeBodyDeclaration extends AbstractJavaNode implements ASTAnyTypeBodyDeclaration { + + private DeclarationKind kind; + + AbstractTypeBodyDeclaration(int id) { + super(id); + } + + + AbstractTypeBodyDeclaration(JavaParser p, int id) { + super(p, id); + } + + + @Override + public JavaNode getDeclarationNode() { + if (jjtGetNumChildren() == 0) { + return null; + } + + // skips the annotations + AccessNode node = getFirstChildOfType(AccessNode.class); + if (node == null) { + return getFirstChildOfType(ASTInitializer.class); + } + + return (JavaNode) node; + } + + + private DeclarationKind determineKind() { + if (jjtGetNumChildren() == 0) { + return EMPTY; + } + + JavaNode node = getDeclarationNode(); + + if (node instanceof ASTInitializer) { + return INITIALIZER; + } else if (node instanceof ASTConstructorDeclaration) { + return CONSTRUCTOR; + } else if (node instanceof ASTMethodDeclaration) { + return METHOD; + } else if (node instanceof ASTAnnotationMethodDeclaration) { + return ANNOTATION_METHOD; + } else if (node instanceof ASTFieldDeclaration) { + return FIELD; + } else if (node instanceof ASTClassOrInterfaceDeclaration) { + return ((ASTClassOrInterfaceDeclaration) node).isInterface() ? INTERFACE : CLASS; + } else if (node instanceof ASTAnnotationTypeDeclaration) { + return ANNOTATION; + } else if (node instanceof ASTEnumDeclaration) { + return ENUM; + } + + throw new IllegalStateException("Declaration node types should all be known"); + } + + @Override + public DeclarationKind getKind() { + if (kind == null) { + kind = determineKind(); + } + + return kind; + } +} diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/AccessNode.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/AccessNode.java index 6fb95bbfdf8..1f233512dfc 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/AccessNode.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/AccessNode.java @@ -6,7 +6,6 @@ import net.sourceforge.pmd.lang.ast.Node; -// FUTURE Remove non JavaBean setters /** * This interface captures Java access modifiers. */ diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/Annotatable.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/Annotatable.java new file mode 100644 index 00000000000..e85027e5fdc --- /dev/null +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/Annotatable.java @@ -0,0 +1,48 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.ast; + +import java.util.Collection; +import java.util.List; + +/** + * The interface use to mark nodes that can be annotated. + */ +public interface Annotatable extends JavaNode { + + /** + * Get all annotations present on this node. + * + * @return all annotations present on this node. + */ + List getDeclaredAnnotations(); + + /** + * Get specific annotaion on this node. + * + * @param annotQualifiedName + * qulified name of the annotation. + * @return ASTAnnotaion node if the annotation is present on this node, else null + */ + ASTAnnotation getAnnotation(String annotQualifiedName); + + /** + * Checks whether any annotation is present on this node. + * + * @param annotQualifiedNames + * collection that cotains qulified name of annotations. + * @return true if any annotation is present on this node, else false + */ + boolean isAnyAnnotationPresent(Collection annotQualifiedNames); + + /** + * Checks whether the annotation is present on this node. + * + * @param annotQualifiedName + * qulified name of the annotation. + * @return true if the annotation is present on this node, else false + */ + boolean isAnnotationPresent(String annotQualifiedName); +} diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/Comment.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/Comment.java index 26479840e2c..da668e6d2f1 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/Comment.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/Comment.java @@ -5,44 +5,105 @@ package net.sourceforge.pmd.lang.java.ast; import java.util.ArrayList; -import java.util.Collection; -import java.util.Map; +import java.util.Collections; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import org.apache.commons.lang3.StringUtils; + +import net.sourceforge.pmd.PMD; import net.sourceforge.pmd.lang.ast.AbstractNode; -import net.sourceforge.pmd.lang.ast.Node; -import net.sourceforge.pmd.lang.java.javadoc.JavadocTag; public abstract class Comment extends AbstractNode { + // single regex, that captures: the start of a multi-line comment (/**|/*), the start of a single line comment (//) + // or the start of line within a multine comment (*). It removes the end of the comment (*/) if existing. + private static final Pattern COMMENT_LINE_COMBINED = Pattern.compile("^(?://|/\\*\\*?|\\*)?(.*?)(?:\\*/|/)?$"); + + // Same as "\\R" - but \\R is only available with java8+ + static final Pattern NEWLINES_PATTERN = Pattern.compile("\\u000D\\u000A|[\\u000A\\u000B\\u000C\\u000D\\u0085\\u2028\\u2029]"); protected Comment(Token t) { super(-1, t.beginLine, t.endLine, t.beginColumn, t.endColumn); setImage(t.image); - if (t.image.startsWith("/**")) { - findJavadocs(t.image); - } } + @Override public String toString() { return getImage(); } - private void findJavadocs(String commentText) { + /** + * Filters the comment by removing the leading comment marker (like {@code *}) of each line + * as well as the start markers ({@code //}, {@code /*} or {@code /**} + * and the end markers (*/). + * Also leading and trailing empty lines are removed. + * + * @return the filtered comment + */ + public String getFilteredComment() { + List lines = multiLinesIn(); + lines = trim(lines); + return StringUtils.join(lines, PMD.EOL); + } + + /** + * Removes the leading comment marker (like {@code *}) of each line + * of the comment as well as the start marker ({@code //}, {@code /*} or {@code /**} + * and the end markers (*/). + * + * @param comment the raw comment + * @return List of lines of the comments + */ + private List multiLinesIn() { + String[] lines = NEWLINES_PATTERN.split(getImage()); + List filteredLines = new ArrayList<>(lines.length); - Collection kids = new ArrayList<>(); + for (String rawLine : lines) { + String line = rawLine.trim(); - Map tags = CommentUtil.javadocTagsIn(commentText); - for (Map.Entry entry : tags.entrySet()) { - JavadocTag tag = JavadocTag.tagFor(entry.getKey()); - if (tag == null) { - continue; + Matcher allMatcher = COMMENT_LINE_COMBINED.matcher(line); + if (allMatcher.matches()) { + filteredLines.add(allMatcher.group(1).trim()); } - kids.add(new JavadocElement(getBeginLine(), getBeginLine(), - // TODO valid? - entry.getValue() + 1, entry.getValue() + tag.label.length() + 1, tag)); } - children = kids.toArray(new Node[kids.size()]); + return filteredLines; } + /** + * Similar to the String.trim() function, this one removes the leading and + * trailing empty/blank lines from the line list. + * + * @param lines the list of lines, which might contain empty lines + * @return the lines without leading or trailing blank lines. + */ + // note: this is only package private, since it is used by CommentUtil. Once CommentUtil is gone, this + // can be private + static List trim(List lines) { + if (lines == null) { + return Collections.emptyList(); + } + + List result = new ArrayList<>(lines.size()); + List tempList = new ArrayList<>(); + boolean foundFirstNonEmptyLine = false; + for (String line : lines) { + if (StringUtils.isNotBlank(line)) { + // new non-empty line: add all previous empty lines occurred before + result.addAll(tempList); + tempList.clear(); + result.add(line); + + foundFirstNonEmptyLine = true; + } else { + if (foundFirstNonEmptyLine) { + // add the empty line to a temporary list first + tempList.add(line); + } + } + } + return result; + } } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/CommentUtil.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/CommentUtil.java index 06aaf9ecbd0..2bca005f2c5 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/CommentUtil.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/CommentUtil.java @@ -4,9 +4,7 @@ package net.sourceforge.pmd.lang.java.ast; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; +import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -15,18 +13,41 @@ import org.apache.commons.lang3.StringUtils; +import net.sourceforge.pmd.lang.java.javadoc.JavadocTag; + +/** + * + * @deprecated This utility class is deprecated and will be removed with PMD 7.0.0. + * Its methods have been intended to parse javadoc tags. + * A more useful solution will be added around the AST node {@link FormalComment}, + * which contains as children {@link JavadocElement} nodes, which in + * turn provide access to the {@link JavadocTag}. + */ +@Deprecated // will be remove with PMD 7.0.0 public final class CommentUtil { - private static final String CR = "\n"; private static final Pattern JAVADOC_TAG = Pattern.compile("@[A-Za-z0-9]+"); - private static final Map JAVADOC_CACHE = new HashMap<>(); private CommentUtil() { } + /** + * Gets the next word (characters until next whitespace, punctuation, + * or anything that is not a letter or digit) at the given position. + * + * @param text the complete text + * @param position the position, at which the word starts + * @return the word + * + * @deprecated This method is deprecated and will be removed with PMD 7.0.0. + * This method has been intended to parse javadoc tags. + * A more useful solution will be added around the AST node {@link FormalComment}, + * which contains as children {@link JavadocElement} nodes, which in + * turn provide access to the {@link JavadocTag}. + */ + @Deprecated // will be removed with PMD 7.0.0 public static String wordAfter(String text, int position) { - - if (position >= text.length()) { + if (text == null || position >= text.length()) { return null; } int newposition = position + 1; @@ -40,11 +61,28 @@ public static String wordAfter(String text, int position) { return text.substring(newposition, end); } + /** + * Gets the remaining line after a specific position. + * + * @param text the complete text + * @param position the position from which the comment should be returned + * @return the part of the text + * + * @deprecated This method is deprecated and will be removed with PMD 7.0.0. + * This method has been intended to parse javadoc tags. + * A more useful solution will be added around the AST node {@link FormalComment}, + * which contains as children {@link JavadocElement} nodes, which in + * turn provide access to the {@link JavadocTag}. + */ + @Deprecated // will be removed with PMD 7.0.0 public static String javadocContentAfter(String text, int position) { + if (text == null || position > text.length()) { + return null; + } int endPos = text.indexOf('\n', position); if (endPos < 0) { - return null; + endPos = text.length(); } if (StringUtils.isNotBlank(text.substring(position, endPos))) { @@ -65,108 +103,69 @@ public static String javadocContentAfter(String text, int position) { return null; } + /** + * Finds all the javadoc tags in the (formal) comment. + * Returns a map from javadoc tag to index position. + * + *

Note: If a tag is used multiple times, the last occurrence is returned. + * + * @param comment the raw comment + * @return mapping of javadoc tag to index position + * + * @deprecated This method is deprecated and will be removed with PMD 7.0.0. + * This method has been intended to parse javadoc tags. + * A more useful solution will be added around the AST node {@link FormalComment}, + * which contains as children {@link JavadocElement} nodes, which in + * turn provide access to the {@link JavadocTag}. + */ + @Deprecated // will be removed with PMD 7.0.0 public static Map javadocTagsIn(String comment) { - Matcher m = JAVADOC_TAG.matcher(comment); - Map tags = null; - while (m.find()) { - if (tags == null) { - tags = new HashMap<>(); - } - String match = comment.substring(m.start() + 1, m.end()); - String tag = JAVADOC_CACHE.get(match); - if (tag == null) { - JAVADOC_CACHE.put(match, match); + Map tags = new HashMap<>(); + + if (comment != null) { + Matcher m = JAVADOC_TAG.matcher(comment); + while (m.find()) { + String match = comment.substring(m.start() + 1, m.end()); + tags.put(match, m.start()); } - tags.put(tag, m.start()); - } - if (tags == null) { - return Collections.emptyMap(); } + return tags; } + /** + * Removes the leading comment marker (like {@code *}) of each line + * of the comment as well as the start marker ({@code //}, {@code /*} or {@code /**} + * and the end markers (*/). + * + * @param comment the raw comment + * @return List of lines of the comments + * + * @deprecated This method will be removed with PMD 7.0.0. + * It has been replaced by {@link Comment#getFilteredComment()}. + */ + @Deprecated // will be removed with PMD 7.0.0 public static List multiLinesIn(String comment) { - - String[] lines = comment.split(CR); - List filteredLines = new ArrayList<>(lines.length); - - for (String rawLine : lines) { - String line = rawLine.trim(); - - if (line.startsWith("//")) { - filteredLines.add(line.substring(2)); - continue; - } - - if (line.endsWith("*/")) { - int end = line.length() - 2; - int start = line.startsWith("/**") ? 3 : line.startsWith("/*") ? 2 : 0; - filteredLines.add(line.substring(start, end)); - continue; - } - - if (line.charAt(0) == '*') { - filteredLines.add(line.substring(1)); - continue; - } - - if (line.startsWith("/**")) { - filteredLines.add(line.substring(3)); - continue; - } - - if (line.startsWith("/*")) { - filteredLines.add(line.substring(2)); - continue; - } - - filteredLines.add(line); - } - - return filteredLines; + // temporary createa a Multiline Comment Node + Token t = new Token(); + t.image = comment; + MultiLineComment node = new MultiLineComment(t); + return Arrays.asList(Comment.NEWLINES_PATTERN.split(node.getFilteredComment())); } /** * Similar to the String.trim() function, this one removes the leading and * trailing empty/blank lines from the line list. * - * @param lines + * @param lines the list of lines, which might contain empty lines + * @return the lines without leading or trailing blank lines. + * + * @deprecated This method will be removed with PMD 7.0.0. + * It is not needed anymore, since {@link Comment#getFilteredComment()} + * returns already the filtered and trimmed comment text. */ + @Deprecated // will be removed with PMD 7.0.0 public static List trim(List lines) { - - int firstNonEmpty = 0; - for (; firstNonEmpty < lines.size(); firstNonEmpty++) { - if (StringUtils.isNotBlank(lines.get(firstNonEmpty))) { - break; - } - } - - // all of them empty? - if (firstNonEmpty == lines.size()) { - return Collections.emptyList(); - } - - int lastNonEmpty = lines.size() - 1; - for (; lastNonEmpty > 0; lastNonEmpty--) { - if (StringUtils.isNotBlank(lines.get(lastNonEmpty))) { - break; - } - } - - List filtered = new ArrayList<>(); - for (int i = firstNonEmpty; i < lastNonEmpty; i++) { - filtered.add(lines.get(i)); - } - - return filtered; - } - - public static void main(String[] args) { - - Collection tags = javadocTagsIn(args[0]).keySet(); - - for (String tag : tags) { - System.out.println(tag); - } + return Comment.trim(lines); } } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/DumpFacade.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/DumpFacade.java index 66fef23b389..8d6917d5a51 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/DumpFacade.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/DumpFacade.java @@ -45,7 +45,7 @@ private void dump(JavaNode node, String prefix) { writer.print(prefix); // 2) JJT Name of the Node - writer.print(node.toString()); + writer.print(node.getXPathNodeName()); // // If there are any additional details, then: @@ -122,9 +122,7 @@ private void dump(JavaNode node, String prefix) { extras.add("nested"); } } else if (node instanceof ASTConditionalExpression) { - if (((ASTConditionalExpression) node).isTernary()) { - extras.add("ternary"); - } + extras.add("ternary"); } else if (node instanceof ASTConstructorDeclaration) { extras.add(String.valueOf(((ASTConstructorDeclaration) node).getParameterCount())); if (((ASTConstructorDeclaration) node).containsComment()) { @@ -192,6 +190,12 @@ private void dump(JavaNode node, String prefix) { if (((ASTTryStatement) node).hasFinally()) { extras.add("has finally"); } + } else if (node instanceof ASTModuleDirective) { + ASTModuleDirective directive = (ASTModuleDirective) node; + extras.add(directive.getType()); + if (directive.getRequiresModifier() != null) { + extras.add(directive.getRequiresModifier()); + } } // Output image and extras diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/FormalComment.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/FormalComment.java index 18542c0e5f2..57ae282a559 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/FormalComment.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/FormalComment.java @@ -4,10 +4,43 @@ package net.sourceforge.pmd.lang.java.ast; +import java.util.ArrayList; +import java.util.Collection; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import net.sourceforge.pmd.lang.ast.Node; +import net.sourceforge.pmd.lang.java.javadoc.JavadocTag; + public class FormalComment extends Comment { + private static final Pattern JAVADOC_TAG = Pattern.compile("@([A-Za-z0-9]+)"); + public FormalComment(Token t) { super(t); + + findJavadocs(); } + @Override + public String getXPathNodeName() { + return "FormalComment"; + } + + private void findJavadocs() { + Collection kids = new ArrayList<>(); + + Matcher javadocTagMatcher = JAVADOC_TAG.matcher(getFilteredComment()); + while (javadocTagMatcher.find()) { + JavadocTag tag = JavadocTag.tagFor(javadocTagMatcher.group(1)); + int tagStartIndex = javadocTagMatcher.start(1); + if (tag != null) { + kids.add(new JavadocElement(getBeginLine(), getBeginLine(), + // TODO valid? + tagStartIndex, tagStartIndex + tag.label.length() + 1, tag)); + } + } + + children = kids.toArray(new Node[0]); + } } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/JavaNode.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/JavaNode.java index e19c8c66230..453cb648040 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/JavaNode.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/JavaNode.java @@ -19,6 +19,7 @@ public interface JavaNode extends ScopedNode { */ Object childrenAccept(JavaParserVisitor visitor, Object data); + @Override Scope getScope(); void setScope(Scope scope); diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/JavaParserControllessVisitorAdapter.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/JavaParserControllessVisitorAdapter.java index ce75d10b067..aec2b1e1536 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/JavaParserControllessVisitorAdapter.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/JavaParserControllessVisitorAdapter.java @@ -17,5 +17,5 @@ public class JavaParserControllessVisitorAdapter extends JavaParserVisitorAdapte public Object visit(JavaNode node, Object data) { return data; } - + } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/JavaParserDecoratedVisitor.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/JavaParserDecoratedVisitor.java index a7d4c7d8229..0e55523479f 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/JavaParserDecoratedVisitor.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/JavaParserDecoratedVisitor.java @@ -252,14 +252,22 @@ public Object visit(ASTDefaultValue node, Object data) { } + /** + * @deprecated Will be removed in 7.0.0. Use {@link ASTShiftExpression#getOperator()} + */ @Override + @Deprecated public Object visit(ASTRUNSIGNEDSHIFT node, Object data) { visitor.visit(node, data); return visit((JavaNode) node, data); } + /** + * @deprecated Will be removed in 7.0.0. Use {@link ASTShiftExpression#getOperator()} + */ @Override + @Deprecated public Object visit(ASTRSIGNEDSHIFT node, Object data) { visitor.visit(node, data); return visit((JavaNode) node, data); @@ -838,4 +846,23 @@ public Object visit(ASTMethodReference node, Object data) { visitor.visit(node, data); return visit((JavaNode) node, data); } + + + @Override + public Object visit(ASTModuleDeclaration node, Object data) { + visitor.visit(node, data); + return visit((JavaNode) node, data); + } + + @Override + public Object visit(ASTModuleDirective node, Object data) { + visitor.visit(node, data); + return visit((JavaNode) node, data); + } + + @Override + public Object visit(ASTModuleName node, Object data) { + visitor.visit(node, data); + return visit((JavaNode) node, data); + } } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/JavaParserVisitorAdapter.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/JavaParserVisitorAdapter.java index e42836fa573..210b09c0b45 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/JavaParserVisitorAdapter.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/JavaParserVisitorAdapter.java @@ -6,459 +6,598 @@ public class JavaParserVisitorAdapter implements JavaParserVisitor { + @Override public Object visit(JavaNode node, Object data) { return node.childrenAccept(this, data); } + @Override public Object visit(ASTExtendsList node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTClassOrInterfaceDeclaration node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTImplementsList node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTTypeParameters node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTMemberSelector node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTTypeParameter node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTTypeBound node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTClassOrInterfaceBody node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTClassOrInterfaceBodyDeclaration node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTEnumBody node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTEnumConstant node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTReferenceType node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTClassOrInterfaceType node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTTypeArguments node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTTypeArgument node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTWildcardBounds node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTAnnotation node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTNormalAnnotation node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTMarkerAnnotation node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTSingleMemberAnnotation node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTMemberValuePairs node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTMemberValuePair node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTMemberValue node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTMemberValueArrayInitializer node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTAnnotationTypeDeclaration node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTAnnotationTypeBody node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTAnnotationTypeMemberDeclaration node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTAnnotationMethodDeclaration node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTDefaultValue node, Object data) { return visit((JavaNode) node, data); } + + /** + * @deprecated Will be removed in 7.0.0. Use {@link ASTShiftExpression#getOperator()} + */ + @Override + @Deprecated public Object visit(ASTRUNSIGNEDSHIFT node, Object data) { return visit((JavaNode) node, data); } + + /** + * @deprecated Will be removed in 7.0.0. Use {@link ASTShiftExpression#getOperator()} + */ + @Override + @Deprecated public Object visit(ASTRSIGNEDSHIFT node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTCompilationUnit node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTEnumDeclaration node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTAssertStatement node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTPackageDeclaration node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTImportDeclaration node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTTypeDeclaration node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTFieldDeclaration node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTVariableDeclarator node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTVariableDeclaratorId node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTVariableInitializer node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTArrayInitializer node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTMethodDeclaration node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTMethodDeclarator node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTFormalParameters node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTFormalParameter node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTConstructorDeclaration node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTExplicitConstructorInvocation node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTInitializer node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTType node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTPrimitiveType node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTResultType node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTName node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTNameList node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTExpression node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTAssignmentOperator node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTConditionalExpression node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTConditionalOrExpression node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTConditionalAndExpression node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTInclusiveOrExpression node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTExclusiveOrExpression node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTAndExpression node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTEqualityExpression node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTInstanceOfExpression node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTRelationalExpression node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTShiftExpression node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTAdditiveExpression node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTMultiplicativeExpression node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTUnaryExpression node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTPreIncrementExpression node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTPreDecrementExpression node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTUnaryExpressionNotPlusMinus node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTPostfixExpression node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTCastExpression node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTPrimaryExpression node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTPrimaryPrefix node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTPrimarySuffix node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTLiteral node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTBooleanLiteral node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTNullLiteral node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTArguments node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTArgumentList node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTAllocationExpression node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTArrayDimsAndInits node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTStatement node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTLabeledStatement node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTBlock node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTBlockStatement node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTLocalVariableDeclaration node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTEmptyStatement node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTStatementExpression node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTSwitchStatement node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTSwitchLabel node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTIfStatement node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTWhileStatement node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTDoStatement node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTForStatement node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTForInit node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTStatementExpressionList node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTForUpdate node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTBreakStatement node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTContinueStatement node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTReturnStatement node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTThrowStatement node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTSynchronizedStatement node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTTryStatement node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTResourceSpecification node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTResources node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTResource node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTFinallyStatement node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTCatchStatement node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTLambdaExpression node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTMethodReference node, Object data) { return visit((JavaNode) node, data); } + + @Override + public Object visit(ASTModuleDeclaration node, Object data) { + return visit((JavaNode) node, data); + } + + @Override + public Object visit(ASTModuleDirective node, Object data) { + return visit((JavaNode) node, data); + } + + @Override + public Object visit(ASTModuleName node, Object data) { + return visit((JavaNode) node, data); + } } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/JavaParserVisitorDecorator.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/JavaParserVisitorDecorator.java index 386d519c548..7451c09a300 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/JavaParserVisitorDecorator.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/JavaParserVisitorDecorator.java @@ -14,11 +14,6 @@ public class JavaParserVisitorDecorator implements JavaParserControllessVisitor private JavaParserVisitor visitor; - - public JavaParserVisitorDecorator() { - } - - public void setBase(JavaParserControllessVisitor base) { visitor = base; } @@ -204,13 +199,21 @@ public Object visit(ASTDefaultValue node, Object data) { } + /** + * @deprecated Will be removed in 7.0.0. Use {@link ASTShiftExpression#getOperator()} + */ @Override + @Deprecated public Object visit(ASTRUNSIGNEDSHIFT node, Object data) { return visitor.visit(node, data); } + /** + * @deprecated Will be removed in 7.0.0. Use {@link ASTShiftExpression#getOperator()} + */ @Override + @Deprecated public Object visit(ASTRSIGNEDSHIFT node, Object data) { return visitor.visit(node, data); } @@ -706,4 +709,22 @@ public Object visit(ASTLambdaExpression node, Object data) { public Object visit(ASTMethodReference node, Object data) { return visitor.visit(node, data); } + + + @Override + public Object visit(ASTModuleDeclaration node, Object data) { + return visitor.visit(node, data); + } + + + @Override + public Object visit(ASTModuleDirective node, Object data) { + return visitor.visit(node, data); + } + + + @Override + public Object visit(ASTModuleName node, Object data) { + return visitor.visit(node, data); + } } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/JavaParserVisitorReducedAdapter.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/JavaParserVisitorReducedAdapter.java index 90eb457089d..a33423ac6a0 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/JavaParserVisitorReducedAdapter.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/JavaParserVisitorReducedAdapter.java @@ -47,6 +47,17 @@ public Object visit(ASTConstructorDeclaration node, Object data) { public Object visit(ASTMethodOrConstructorDeclaration node, Object data) { + return visit((MethodLikeNode) node, data); + } + + + @Override + public Object visit(ASTLambdaExpression node, Object data) { + return visit((MethodLikeNode) node, data); + } + + + public Object visit(MethodLikeNode node, Object data) { return visit((JavaNode) node, data); } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/JavaQualifiableNode.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/JavaQualifiableNode.java index 2f8362f7986..ded0119277c 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/JavaQualifiableNode.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/JavaQualifiableNode.java @@ -6,6 +6,7 @@ import net.sourceforge.pmd.lang.ast.QualifiableNode; + /** * Java nodes that can be described with a qualified name. * @@ -18,5 +19,6 @@ public interface JavaQualifiableNode extends QualifiableNode { * * @return A qualified name. */ + @Override JavaQualifiedName getQualifiedName(); } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/JavaQualifiedName.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/JavaQualifiedName.java index 843fb567e8d..b17aaac3686 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/JavaQualifiedName.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/JavaQualifiedName.java @@ -4,297 +4,192 @@ package net.sourceforge.pmd.lang.java.ast; -import java.util.Arrays; -import java.util.regex.Matcher; -import java.util.regex.Pattern; +import java.util.Objects; import net.sourceforge.pmd.lang.ast.QualifiedName; +import net.sourceforge.pmd.lang.java.qname.JavaOperationQualifiedName; +import net.sourceforge.pmd.lang.java.qname.JavaTypeQualifiedName; +import net.sourceforge.pmd.lang.java.qname.QualifiedNameFactory; + /** - * Represents Qualified Names for use within the java metrics framework. + * Unambiguous identifier for a java method or class. This implementation + * approaches the qualified name format found in stack traces for example, + * using a custom format specification (see {@link QualifiedNameFactory#ofString(String)}). + * + *

Instances of this class are immutable. They can be obtained from the + * factory methods of {@link QualifiedNameFactory}, or from + * {@link JavaQualifiableNode#getQualifiedName()} on AST nodes that support it. + * + *

Class qualified names follow the binary name spec. + * + *

Method qualified names don't follow a specification but allow to + * distinguish overloads of the same method, using parameter types and order. + * + * @see JavaTypeQualifiedName + * @see JavaOperationQualifiedName + * + * @since 5.8.1 + * @author Clément Fournier */ -public final class JavaQualifiedName implements QualifiedName { +public abstract class JavaQualifiedName implements QualifiedName { - /** - * Pattern specifying the format. - * - *

{@code ((\w+\.)+|\.)((\w+)(\$\w+)*)(#(\w+)\(((\w+)(, \w+)*)?\))?} - */ - public static final Pattern FORMAT = Pattern.compile("((\\w+\\.)+|\\.)((\\w+)(\\$\\w+)*)(#(\\w+)\\(((\\w+)(, \\w+)*)?\\))?"); + // toString cache + private String toString; + private int hashCode; - private String[] packages = null; // unnamed package - private String[] classes = new String[1]; - private String operation = null; - private JavaQualifiedName() { - } + @Override + public abstract JavaTypeQualifiedName getClassName(); /** - * Builds the qualified name of a method declaration. - * - * @param node The method declaration node + * Returns true if this qualified name identifies a + * local class. * - * @return The qualified name of the node + * @deprecated Use {@link JavaTypeQualifiedName#isLocalClass()}. Will be removed in 7.0.0 */ - /* default */ static JavaQualifiedName ofOperation(ASTMethodDeclaration node) { - JavaQualifiedName parentQname = node.getFirstParentOfType(ASTAnyTypeDeclaration.class) - .getQualifiedName(); - - return ofOperation(parentQname, - node.getMethodName(), - node.getFirstDescendantOfType(ASTFormalParameters.class)); + @Deprecated + public boolean isLocalClass() { + return getClassName().isLocalClass(); } /** - * Builds the qualified name of a constructor declaration. - * - * @param node The constructor declaration node + * Get the simple name of the class. * - * @return The qualified name of the node + * @deprecated Use {@link JavaTypeQualifiedName#getClassSimpleName()}. Will be removed in 7.0.0 */ - /* default */ static JavaQualifiedName ofOperation(ASTConstructorDeclaration node) { - ASTAnyTypeDeclaration parent = node.getFirstParentOfType(ASTAnyTypeDeclaration.class); - - return ofOperation(parent.getQualifiedName(), - parent.getImage(), - node.getFirstDescendantOfType(ASTFormalParameters.class)); + @Deprecated + public String getClassSimpleName() { + return getClassName().getClassSimpleName(); } - /** Factorises the functionality of makeOperationof() */ - private static JavaQualifiedName ofOperation(JavaQualifiedName parent, String opName, ASTFormalParameters params) { - JavaQualifiedName qname = new JavaQualifiedName(); - - qname.packages = parent.packages; - qname.classes = parent.classes; - qname.operation = getOperationName(opName, params); - - return qname; + /** + * Returns true if the class represented by this + * qualified name is in the unnamed package. + * + * @deprecated Use {@link JavaTypeQualifiedName#isUnnamedPackage()}. Will be removed in 7.0.0 + */ + @Deprecated + public boolean isUnnamedPackage() { + return getClassName().isUnnamedPackage(); } /** - * Builds the qualified name of a nested class using the qualified name of its immediate parent. + * Returns the operation specific part of the name. It + * identifies an operation in its namespace. Returns + * {@code null} if {@link #isOperation()} returns false. * - * @param parent The qname of the immediate parent - * @param className The name of the class + * @deprecated Use {@link JavaOperationQualifiedName#getOperation()}. Will be removed in 7.0.0 * - * @return The qualified name of the nested class + * @return The operation string, or {@code null}. */ - /* default */ static JavaQualifiedName ofNestedClass(JavaQualifiedName parent, String className) { - JavaQualifiedName qname = new JavaQualifiedName(); - qname.packages = parent.packages; - if (parent.classes[0] != null) { - qname.classes = Arrays.copyOf(parent.classes, parent.classes.length + 1); - qname.classes[parent.classes.length] = className; - } else { - qname.classes[0] = className; - } - - return qname; + @Deprecated + public String getOperation() { + return null; // overridden by JOperationQName } /** - * Builds the qualified name of an outer (not nested) class. - * - * @param node The class node + * Returns the packages in order. * - * @return The qualified name of the node + * @deprecated Use {@link JavaTypeQualifiedName#getPackageList()} ()}. Will be removed in 7.0.0 */ - /* default */ static JavaQualifiedName ofOuterClass(ASTAnyTypeDeclaration node) { - ASTPackageDeclaration pkg = node.getFirstParentOfType(ASTCompilationUnit.class) - .getFirstChildOfType(ASTPackageDeclaration.class); - - JavaQualifiedName qname = new JavaQualifiedName(); - qname.packages = pkg == null ? null : pkg.getPackageNameImage().split("\\."); - qname.classes[0] = node.getImage(); - - return qname; + @Deprecated + public String[] getPackages() { + return getClassName().getPackageList().toArray(new String[0]); } - // Might be useful with type resolution - public static JavaQualifiedName ofClass(Class clazz) { - throw new UnsupportedOperationException(); - } - /** - * Parses a qualified name given in the format defined for this implementation. The format - * is specified by a regex pattern (see {@link JavaQualifiedName#FORMAT}). Examples: + * Returns the classes in order. * - *

{@code com.company.MyClass$Nested#myMethod(String, int)} - *

    - *
  • Packages are separated by full stops; - *
  • Nested classes are separated by a dollar symbol; - *
  • The optional method suffix is separated from the class with a hashtag; - *
  • Method arguments are separated by a comma and a single space. - *
- * - *

{@code .MyClass$Nested} - *

    - *
  • A class in the unnamed package is preceded by a single full stop. - *
- * - * @param name The name to parse. - * - * @return A qualified name instance corresponding to the parsed string. + * @deprecated Use {{@link JavaTypeQualifiedName#getClassList()}. Will be removed in 7.0.0 */ - public static JavaQualifiedName ofString(String name) { - JavaQualifiedName qname = new JavaQualifiedName(); - - Matcher matcher = FORMAT.matcher(name); - - if (!matcher.matches()) { - return null; - } - - qname.packages = ".".equals(matcher.group(1)) ? null : matcher.group(1).split("\\."); - qname.classes = matcher.group(3).split("\\$"); - qname.operation = matcher.group(6) == null ? null : matcher.group(6).substring(1); - - return qname; + @Deprecated + public String[] getClasses() { + return getClassName().getClassList().toArray(new String[0]); } - /** Returns a normalized method name (not Java-canonical!). */ - private static String getOperationName(String methodName, ASTFormalParameters params) { - - StringBuilder sb = new StringBuilder(); - sb.append(methodName); - sb.append('('); - - int last = params.getParameterCount() - 1; - for (int i = 0; i < last; i++) { - // append type image of param - sb.append(params.jjtGetChild(i).getFirstDescendantOfType(ASTType.class).getTypeImage()); - sb.append(", "); + @Override + public final boolean equals(Object o) { + if (this == o) { + return true; } - - if (last > -1) { - sb.append(params.jjtGetChild(last).getFirstDescendantOfType(ASTType.class).getTypeImage()); + if (o == null || getClass() != o.getClass()) { + return false; } - - sb.append(')'); - - return sb.toString(); + JavaQualifiedName that = (JavaQualifiedName) o; + return Objects.equals(toString(), that.toString()) + && structurallyEquals(that); } - @Override - public boolean isClass() { - return classes[0] != null && operation == null; - } + /** + * Returns true if the given qname is identical to this qname. + * Performs a structural comparison. Used in the implementation + * of {@link #equals(Object)} after taking shortcuts. + * + * @param qname The other comparand. Can always be casted down + * to the subclass type in which this method is overridden + */ + protected abstract boolean structurallyEquals(JavaQualifiedName qname); @Override - public boolean isOperation() { - return operation != null; + public final int hashCode() { + if (hashCode == 0) { + hashCode = buildHashCode(); + } + return hashCode; } /** - * Returns the packages. This is specific to Java's package structure. - * - * @return The packages. + * Computes the hashcode. Called once, then cached. + * Since QualifiedNames are mostly used as the keys + * of a map, caching the hashcode makes sense. */ - public String[] getPackages() { - return packages; - } + protected abstract int buildHashCode(); /** - * Returns the class specific part of the name. It identifies a class in the namespace it's declared in. If the - * class is nested inside another, then the array returned contains all enclosing classes in order. - * - * @return The class names array. + * Returns the string representation of this qualified + * name. The representation follows the format defined + * for {@link QualifiedNameFactory#ofString(String)}. */ - public String[] getClasses() { - return classes; + @Override + public final String toString() { + // lazy evaluated + if (toString == null) { + toString = buildToString(); + } + return toString; } /** - * Returns the operation specific part of the name. It identifies an operation in its namespace. - * - * @return The operation string. + * @deprecated Use {@link QualifiedNameFactory#ofString(String)}. Will be removed in 7.0.0 */ - public String getOperation() { - return operation; - } - - - @Override - public JavaQualifiedName getClassName() { - if (isClass()) { - return this; - } - - JavaQualifiedName qname = new JavaQualifiedName(); - qname.classes = this.classes; - qname.packages = this.packages; - return qname; + @Deprecated + public static JavaQualifiedName ofString(String name) { + return QualifiedNameFactory.ofString(name); } - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - - JavaQualifiedName that = (JavaQualifiedName) o; - - // Probably incorrect - comparing Object[] arrays with Arrays.equals - if (!Arrays.equals(packages, that.packages)) { - return false; - } - // Probably incorrect - comparing Object[] arrays with Arrays.equals - if (!Arrays.equals(classes, that.classes)) { - return false; - } - return operation != null ? operation.equals(that.operation) : that.operation == null; + /** + * @deprecated Use {@link QualifiedNameFactory#ofClass(Class)}. Will be removed in 7.0.0 + */ + @Deprecated + public static JavaQualifiedName ofClass(Class clazz) { + return QualifiedNameFactory.ofClass(clazz); } - @Override - public int hashCode() { - int result = Arrays.hashCode(packages); - result = 31 * result + Arrays.hashCode(classes); - result = 31 * result + (operation != null ? operation.hashCode() : 0); - return result; - } - @Override - public String toString() { - StringBuilder sb = new StringBuilder(); - - if (packages != null) { - int last = packages.length - 1; - for (int i = 0; i < last; i++) { - sb.append(packages[i]); - sb.append('.'); - } - - sb.append(packages[last]); - } - sb.append('.'); // this dot is there even if package is null - - int last = classes.length - 1; - for (int i = 0; i < last; i++) { - sb.append(classes[i]); - sb.append('$'); - } - - sb.append(classes[last]); - - if (operation != null) { - sb.append('#'); - sb.append(operation); - } - - return sb.toString(); - } + /** + * Construct the toString once. Called only once per instance + */ + protected abstract String buildToString(); } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/JavadocElement.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/JavadocElement.java index 4ea05c21936..54f489499fa 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/JavadocElement.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/JavadocElement.java @@ -21,8 +21,11 @@ public JavadocTag tag() { return tag; } + + + @Override - public String toString() { + public String getXPathNodeName() { return tag.label + " : " + tag.description; } } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/MethodLikeNode.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/MethodLikeNode.java new file mode 100644 index 00000000000..0d77e9c59a5 --- /dev/null +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/MethodLikeNode.java @@ -0,0 +1,47 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.ast; + +import java.util.Locale; + +import net.sourceforge.pmd.lang.java.qname.JavaOperationQualifiedName; + + +/** + * Groups method, constructor and lambda declarations under a common type. + * + * @author Clément Fournier + * @since 6.1.0 + */ +public interface MethodLikeNode extends AccessNode, JavaQualifiableNode, JavaNode { + + /** + * Returns a token indicating whether this node is a lambda + * expression or a method or constructor declaration. Can + * be used to downcast safely to a subinterface or an + * implementing class. + * + * @return The kind of method-like + */ + MethodLikeKind getKind(); + + + @Override + JavaOperationQualifiedName getQualifiedName(); + + + /** Kind of method-like. */ + enum MethodLikeKind { + METHOD, + CONSTRUCTOR, + LAMBDA; + + public String getPrintableName() { + return name().toLowerCase(Locale.ROOT); + } + } + + +} diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/MultiLineComment.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/MultiLineComment.java index 5f447ca4001..99302ed5bf3 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/MultiLineComment.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/MultiLineComment.java @@ -10,4 +10,10 @@ public MultiLineComment(Token t) { super(t); } + + @Override + public String getXPathNodeName() { + return "MultiLineComment"; + } + } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/NodeChildrenIterator.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/NodeChildrenIterator.java new file mode 100644 index 00000000000..4b6a5260596 --- /dev/null +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/NodeChildrenIterator.java @@ -0,0 +1,60 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.ast; + +import java.util.Iterator; + +import net.sourceforge.pmd.lang.ast.Node; + + +/** + * Iterates over the children of a node that are of a specific type. + * + * @author Clément Fournier + * @since 6.3.0 + */ +class NodeChildrenIterator implements Iterator { + + private final Node parent; + private final Class targetChildType; + private int i = 0; + + + NodeChildrenIterator(Node parent, Class targetChildType) { + this.parent = parent; + this.targetChildType = targetChildType; + } + + + private void moveToNext() { + while (i < parent.jjtGetNumChildren() && !(targetChildType.isInstance(parent.jjtGetChild(i)))) { + i++; + } + } + + + @Override + public boolean hasNext() { + moveToNext(); + return i < parent.jjtGetNumChildren(); + } + + + @Override + public T next() { + moveToNext(); + + @SuppressWarnings("unchecked") + T t = (T) parent.jjtGetChild(i++); + return t; + } + + + @Override + public void remove() { + throw new UnsupportedOperationException(); + } + +} diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/SingleLineComment.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/SingleLineComment.java index ef4646510fa..2c730bb08da 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/SingleLineComment.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/SingleLineComment.java @@ -10,4 +10,9 @@ public SingleLineComment(Token t) { super(t); } + + @Override + public String getXPathNodeName() { + return "SingleLineComment"; + } } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/package-info.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/package-info.java new file mode 100644 index 00000000000..bd306f8570d --- /dev/null +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/ast/package-info.java @@ -0,0 +1,8 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +/** + * Contains the classes and interfaces modelling the Java AST. + */ +package net.sourceforge.pmd.lang.java.ast; diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/dfa/DataFlowFacade.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/dfa/DataFlowFacade.java index 6353d8ed4f7..4068b26e12d 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/dfa/DataFlowFacade.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/dfa/DataFlowFacade.java @@ -27,12 +27,14 @@ public void initializeWith(DataFlowHandler dataFlowHandler, ASTCompilationUnit n node.jjtAccept(this, null); } + @Override public Object visit(ASTMethodDeclaration node, Object data) { sbf.buildDataFlowFor(node); vav.compute(node); return data; } + @Override public Object visit(ASTConstructorDeclaration node, Object data) { sbf.buildDataFlowFor(node); vav.compute(node); diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/dfa/JavaDFAGraphRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/dfa/JavaDFAGraphRule.java index 7ab1ea5cd5b..f8eb752fdf1 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/dfa/JavaDFAGraphRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/dfa/JavaDFAGraphRule.java @@ -18,9 +18,10 @@ public class JavaDFAGraphRule extends AbstractJavaRule implements DFAGraphRule { private List methods; public JavaDFAGraphRule() { - super.setUsesDFA(); + super.setDfa(true); } + @Override public List getMethods() { return this.methods; } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/dfa/JavaDataFlowNode.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/dfa/JavaDataFlowNode.java index aee3c7d5eaf..76c3f0a0544 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/dfa/JavaDataFlowNode.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/dfa/JavaDataFlowNode.java @@ -18,6 +18,7 @@ public JavaDataFlowNode(List dataFlow, Node node) { super(dataFlow, node); } + @Override public String toString() { String res = "DataFlowNode: line " + this.getLine() + ", "; if (node instanceof ASTMethodDeclaration || node instanceof ASTConstructorDeclaration) { diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/javadoc/JavadocTag.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/javadoc/JavadocTag.java index 1ecae011930..79f599dec37 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/javadoc/JavadocTag.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/javadoc/JavadocTag.java @@ -24,7 +24,7 @@ public final class JavadocTag { "Indicates that an item is a member of the deprecated API"); public static final JavadocTag PARAM = new JavadocTag("param", " "); public static final JavadocTag THROWS = new JavadocTag("throws", " "); - public static final JavadocTag RETURN = new JavadocTag("returns", " "); + public static final JavadocTag RETURN = new JavadocTag("return", " "); public static final JavadocTag SEE = new JavadocTag("see", " "); /* public static final JavadocTag POST = new JavadocTag("post", " "); diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/AbstractJavaMetric.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/AbstractJavaMetric.java index 9c6809bbcde..b287e0400e3 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/AbstractJavaMetric.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/AbstractJavaMetric.java @@ -4,18 +4,12 @@ package net.sourceforge.pmd.lang.java.metrics; -import java.util.ArrayList; -import java.util.List; - import net.sourceforge.pmd.lang.ast.Node; -import net.sourceforge.pmd.lang.java.ast.ASTMethodOrConstructorDeclaration; -import net.sourceforge.pmd.lang.java.ast.JavaQualifiedName; import net.sourceforge.pmd.lang.metrics.AbstractMetric; /** - * Base class for metrics. Metric objects encapsulate the computational logic required to compute a metric from a - * PackageStats and node. They're stateless. + * Base class for metrics. Metric objects encapsulate the computational logic required to compute a metric from a node. * * @param Type of node the metric can be computed on * @@ -23,23 +17,4 @@ */ public abstract class AbstractJavaMetric extends AbstractMetric { - - protected List findAllCalls(ASTMethodOrConstructorDeclaration node) { - List result = new ArrayList<>(); - // TODO:cf findAllCalls - // Needs TypeRes - // Find the qualified names of all methods called in that method's block - return result; - } - - - /** - * Gives access to a signature matcher to metrics. They can use it to perform signature matching. - * - * @return A signature matcher - */ - protected static JavaSignatureMatcher getSignatureMatcher() { - return JavaMetrics.getFacade().getTopLevelPackageStats(); - } - } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/ClassStats.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/ClassStats.java deleted file mode 100644 index 902bfa22424..00000000000 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/ClassStats.java +++ /dev/null @@ -1,138 +0,0 @@ -/** - * BSD-style license; for more info see http://pmd.sourceforge.net/license.html - */ - -package net.sourceforge.pmd.lang.java.metrics; - -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; - -import net.sourceforge.pmd.lang.java.ast.JavaQualifiedName; -import net.sourceforge.pmd.lang.java.metrics.signature.JavaFieldSigMask; -import net.sourceforge.pmd.lang.java.metrics.signature.JavaFieldSignature; -import net.sourceforge.pmd.lang.java.metrics.signature.JavaOperationSigMask; -import net.sourceforge.pmd.lang.java.metrics.signature.JavaOperationSignature; - -/** - * Statistics about a class, enum, interface, or annotation. Stores information about the contained members and their - * signatures, and memoizes the results of the class metrics computed on the corresponding node. - * - *

This class does not provide methods to operate directly on its nested classes, but only on itself. To operate on a - * nested class, retrieve the correct ClassStats with {@link PackageStats#getClassStats(JavaQualifiedName, boolean)} - * then use the methods of ClassStats. Note that at this level, entities of the data structure do not manipulate - * QualifiedNames anymore, only Strings. - * - * @author Clément Fournier - */ -/* default */ class ClassStats { - - private Map> operations = new HashMap<>(); - private Map> fields = new HashMap<>(); - private Map nestedClasses = new HashMap<>(); - - // References to the hierarchy - // TODO:cf useful? - // private String superclass; - // private List subclasses; - - - /** - * Finds a ClassStats in the direct children of this class. This can only be a directly nested class, for example in - * the following snippet, A can get B and B can get C but A cannot get C without asking B. - *

-     * {@code
-     * class MyClass { // ClassStats A
-     *   class MyNested { // ClassStats B
-     *     class MyDeeplyNested { // ClassStats C
-     *     }
-     *   }
-     * }
-     * }
-     * 
- * - * @param className Name of the nested class - * @param createIfNotFound Create the requested ClassStats if missing - * - * @return The new ClassStats or the one that was found. Can return null if createIfNotFound is unset - */ - /* default */ ClassStats getNestedClassStats(String className, boolean createIfNotFound) { - if (createIfNotFound && !nestedClasses.containsKey(className)) { - nestedClasses.put(className, new ClassStats()); - } - return nestedClasses.get(className); - } - - - /** - * Adds an operation to the class. - * - * @param name The name of the operation - * @param sig The signature of the operation - */ - /* default */ void addOperation(String name, JavaOperationSignature sig) { - if (!operations.containsKey(sig)) { - operations.put(sig, new HashSet()); - } - operations.get(sig).add(name); - } - - - /** - * Adds a field to the class. - * - * @param name The name of the field - * @param sig The signature of the field - */ - /* default */ void addField(String name, JavaFieldSignature sig) { - if (!fields.containsKey(sig)) { - fields.put(sig, new HashSet()); - } - fields.get(sig).add(name); - } - - - /** - * Checks whether the class declares an operation by the name given which is covered by the signature mask. - * - * @param name The name of the operation to look for - * @param mask The mask covering accepted signatures - * - * @return True if the class declares an operation by the name given which is covered by the signature mask, false - * otherwise - */ - /* default */ boolean hasMatchingSig(String name, JavaOperationSigMask mask) { - // Indexing on signatures optimises this type of request - for (JavaOperationSignature sig : operations.keySet()) { - if (mask.covers(sig)) { - if (operations.get(sig).contains(name)) { - return true; - } - } - } - return false; - } - - - /** - * Checks whether the class declares a field by the name given which is covered by the signature mask. - * - * @param name The name of the field to look for - * @param mask The mask covering accepted signatures - * - * @return True if the class declares a field by the name given which is covered by the signature mask, false - * otherwise - */ - /* default */ boolean hasMatchingSig(String name, JavaFieldSigMask mask) { - for (JavaFieldSignature sig : fields.keySet()) { - if (mask.covers(sig)) { - if (fields.get(sig).contains(name)) { - return true; - } - } - } - return false; - } - -} diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/JavaMetrics.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/JavaMetrics.java index 607703b5904..5e889e04394 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/JavaMetrics.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/JavaMetrics.java @@ -7,6 +7,7 @@ import net.sourceforge.pmd.lang.java.ast.ASTAnyTypeDeclaration; import net.sourceforge.pmd.lang.java.ast.ASTMethodOrConstructorDeclaration; +import net.sourceforge.pmd.lang.java.ast.MethodLikeNode; import net.sourceforge.pmd.lang.metrics.MetricKey; import net.sourceforge.pmd.lang.metrics.MetricOptions; import net.sourceforge.pmd.lang.metrics.ResultOption; @@ -79,11 +80,31 @@ public static double get(MetricKey key, ASTAnyTypeDeclara * * @return The value of the metric, or {@code Double.NaN} if the value couldn't be computed */ - public static double get(MetricKey key, ASTMethodOrConstructorDeclaration node) { + public static double get(MetricKey key, MethodLikeNode node) { return FACADE.computeForOperation(key, node, MetricOptions.emptyOptions()); } + /** + * @see #get(MetricKey, MethodLikeNode) + * @deprecated Provided here for backwards binary compatibility with {@link #get(MetricKey, MethodLikeNode)}. + * Please explicitly link your code to that method and recompile your code. Will be remove with 7.0.0 + */ + public static double get(MetricKey key, ASTMethodOrConstructorDeclaration node) { + return get(key, (MethodLikeNode) node); + } + + + /** + * @see #get(MetricKey, MethodLikeNode, MetricOptions) + * @deprecated Provided here for backwards binary compatibility with {@link #get(MetricKey, MethodLikeNode, MetricOptions)}. + * Please explicitly link your code to that method and recompile your code. Will be remove with 7.0.0 + */ + @Deprecated + public static double get(MetricKey key, ASTMethodOrConstructorDeclaration node, MetricOptions options) { + return get(key, (MethodLikeNode) node, options); + } + /** * Computes a metric identified by its key on a operation AST node. * @@ -93,8 +114,7 @@ public static double get(MetricKey key, ASTMe * * @return The value of the metric, or {@code Double.NaN} if the value couldn't be computed */ - public static double get(MetricKey key, ASTMethodOrConstructorDeclaration node, - MetricOptions options) { + public static double get(MetricKey key, MethodLikeNode node, MetricOptions options) { return FACADE.computeForOperation(key, node, options); } @@ -107,10 +127,9 @@ public static double get(MetricKey key, ASTMe * @param node The node on which to compute the metric * @param resultOption The result option to use * - * @return The value of the metric, or {@code Double.NaN} if the value couldn't be computed or {@code option} is - * {@code null} + * @return The value of the metric, or {@code Double.NaN} if the value couldn't be computed */ - public static double get(MetricKey key, ASTAnyTypeDeclaration node, ResultOption resultOption) { + public static double get(MetricKey key, ASTAnyTypeDeclaration node, ResultOption resultOption) { return FACADE.computeWithResultOption(key, node, MetricOptions.emptyOptions(), resultOption); } @@ -124,10 +143,9 @@ public static double get(MetricKey key, ASTAn * @param resultOption The result option to use * @param options The version of the metric * - * @return The value of the metric, or {@code Double.NaN} if the value couldn't be computed or {@code option} is - * {@code null} + * @return The value of the metric, or {@code Double.NaN} if the value couldn't be computed */ - public static double get(MetricKey key, ASTAnyTypeDeclaration node, + public static double get(MetricKey key, ASTAnyTypeDeclaration node, MetricOptions options, ResultOption resultOption) { return FACADE.computeWithResultOption(key, node, options, resultOption); } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/JavaMetricsComputer.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/JavaMetricsComputer.java index 63287a7d8a1..b96d66b93a0 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/JavaMetricsComputer.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/JavaMetricsComputer.java @@ -10,6 +10,7 @@ import net.sourceforge.pmd.lang.java.ast.ASTAnyTypeBodyDeclaration; import net.sourceforge.pmd.lang.java.ast.ASTAnyTypeDeclaration; import net.sourceforge.pmd.lang.java.ast.ASTMethodOrConstructorDeclaration; +import net.sourceforge.pmd.lang.java.ast.MethodLikeNode; import net.sourceforge.pmd.lang.metrics.AbstractMetricsComputer; /** @@ -17,24 +18,23 @@ * * @author Clément Fournier */ -public class JavaMetricsComputer extends AbstractMetricsComputer { +public final class JavaMetricsComputer extends AbstractMetricsComputer { static final JavaMetricsComputer INSTANCE = new JavaMetricsComputer(); private JavaMetricsComputer() { - } - + // TODO: doesn't consider lambdas @Override - protected List findOperations(ASTAnyTypeDeclaration node) { + protected List findOperations(ASTAnyTypeDeclaration node) { - List operations = new ArrayList<>(); + List operations = new ArrayList<>(); for (ASTAnyTypeBodyDeclaration decl : node.getDeclarations()) { if (decl.jjtGetNumChildren() > 0 && decl.jjtGetChild(0) instanceof ASTMethodOrConstructorDeclaration) { - operations.add((ASTMethodOrConstructorDeclaration) decl.jjtGetChild(0)); + operations.add((MethodLikeNode) decl.jjtGetChild(0)); } } return operations; diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/JavaMetricsFacade.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/JavaMetricsFacade.java index ce79002be34..6ddd2e1df9c 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/JavaMetricsFacade.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/JavaMetricsFacade.java @@ -5,7 +5,7 @@ package net.sourceforge.pmd.lang.java.metrics; import net.sourceforge.pmd.lang.java.ast.ASTAnyTypeDeclaration; -import net.sourceforge.pmd.lang.java.ast.ASTMethodOrConstructorDeclaration; +import net.sourceforge.pmd.lang.java.ast.MethodLikeNode; import net.sourceforge.pmd.lang.metrics.AbstractMetricsFacade; import net.sourceforge.pmd.lang.metrics.MetricsComputer; @@ -14,29 +14,17 @@ * * @author Clément Fournier */ -class JavaMetricsFacade extends AbstractMetricsFacade { +class JavaMetricsFacade extends AbstractMetricsFacade { - private final PackageStats topLevelPackageStats = new PackageStats(); private final JavaProjectMemoizer memoizer = new JavaProjectMemoizer(); /** Resets the entire data structure. Used for tests. */ void reset() { - topLevelPackageStats.reset(); memoizer.reset(); } - /** - * Gets the top level package stats of this façade. - * - * @return The top level package stats - */ - PackageStats getTopLevelPackageStats() { - return topLevelPackageStats; - } - - @Override public JavaProjectMemoizer getLanguageSpecificProjectMemoizer() { return memoizer; @@ -44,7 +32,7 @@ public JavaProjectMemoizer getLanguageSpecificProjectMemoizer() { @Override - protected MetricsComputer getLanguageSpecificComputer() { + protected MetricsComputer getLanguageSpecificComputer() { return JavaMetricsComputer.INSTANCE; } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/JavaMetricsVisitor.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/JavaMetricsVisitor.java deleted file mode 100644 index 107c19ea3db..00000000000 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/JavaMetricsVisitor.java +++ /dev/null @@ -1,60 +0,0 @@ -/** - * BSD-style license; for more info see http://pmd.sourceforge.net/license.html - */ - -package net.sourceforge.pmd.lang.java.metrics; - -import java.util.Stack; - -import net.sourceforge.pmd.lang.java.ast.ASTAnyTypeDeclaration; -import net.sourceforge.pmd.lang.java.ast.ASTFieldDeclaration; -import net.sourceforge.pmd.lang.java.ast.ASTMethodOrConstructorDeclaration; -import net.sourceforge.pmd.lang.java.ast.JavaParserVisitorReducedAdapter; - -/** - * Visitor for the metrics framework, that fills a {@link PackageStats} object with the signatures of operations and - * fields it encounters. - * - * @author Clément Fournier - */ -class JavaMetricsVisitor extends JavaParserVisitorReducedAdapter { - - private final Stack stack = new Stack<>(); - private final PackageStats toplevel; - private final JavaProjectMemoizer memoizer; - - - JavaMetricsVisitor(PackageStats toplevel, JavaProjectMemoizer memoizer) { - this.toplevel = toplevel; - this.memoizer = memoizer; - } - - - @Override - public Object visit(ASTAnyTypeDeclaration node, Object data) { - memoizer.addClassMemoizer(node.getQualifiedName()); - - stack.push(toplevel.getClassStats(node.getQualifiedName(), true)); - super.visit(node, data); - stack.pop(); - - return data; - } - - - @Override - public Object visit(ASTMethodOrConstructorDeclaration node, Object data) { - memoizer.addOperationMemoizer(node.getQualifiedName()); - - stack.peek().addOperation(node.getQualifiedName().getOperation(), node.getSignature()); - return super.visit(node, data); - } - - - @Override - public Object visit(ASTFieldDeclaration node, Object data) { - stack.peek().addField(node.getVariableName(), node.getSignature()); - return data; // end recursion - } - -} diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/JavaMetricsVisitorFacade.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/JavaMetricsVisitorFacade.java deleted file mode 100644 index 9ae8ea0501f..00000000000 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/JavaMetricsVisitorFacade.java +++ /dev/null @@ -1,24 +0,0 @@ -/** - * BSD-style license; for more info see http://pmd.sourceforge.net/license.html - */ - -package net.sourceforge.pmd.lang.java.metrics; - -import net.sourceforge.pmd.lang.java.ast.ASTCompilationUnit; -import net.sourceforge.pmd.lang.java.ast.JavaParserVisitorAdapter; - -/** - * Wraps the visitor. - * - * @author Clément Fournier - */ -public class JavaMetricsVisitorFacade extends JavaParserVisitorAdapter { - - public void initializeWith(ASTCompilationUnit rootNode) { - JavaMetricsFacade facade = JavaMetrics.getFacade(); - JavaMetricsVisitor visitor = new JavaMetricsVisitor(facade.getTopLevelPackageStats(), - facade.getLanguageSpecificProjectMemoizer()); - rootNode.jjtAccept(visitor, null); - } - -} diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/JavaProjectMemoizer.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/JavaProjectMemoizer.java index c85c1a2f92b..0d30549a51b 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/JavaProjectMemoizer.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/JavaProjectMemoizer.java @@ -5,7 +5,7 @@ package net.sourceforge.pmd.lang.java.metrics; import net.sourceforge.pmd.lang.java.ast.ASTAnyTypeDeclaration; -import net.sourceforge.pmd.lang.java.ast.ASTMethodOrConstructorDeclaration; +import net.sourceforge.pmd.lang.java.ast.MethodLikeNode; import net.sourceforge.pmd.lang.metrics.BasicProjectMemoizer; /** @@ -13,6 +13,6 @@ * * @author Clément Fournier */ -class JavaProjectMemoizer extends BasicProjectMemoizer { +class JavaProjectMemoizer extends BasicProjectMemoizer { } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/JavaSignatureMatcher.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/JavaSignatureMatcher.java deleted file mode 100644 index 25dbe29028f..00000000000 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/JavaSignatureMatcher.java +++ /dev/null @@ -1,42 +0,0 @@ -/** - * BSD-style license; for more info see http://pmd.sourceforge.net/license.html - */ - -package net.sourceforge.pmd.lang.java.metrics; - -import net.sourceforge.pmd.lang.java.ast.JavaQualifiedName; -import net.sourceforge.pmd.lang.java.metrics.signature.JavaFieldSigMask; -import net.sourceforge.pmd.lang.java.metrics.signature.JavaOperationSigMask; - -/** - * Gathers the methods that PackageStats should make available to metrics during the computation. - * - * @author Clément Fournier - */ -public interface JavaSignatureMatcher { - - /** - * Returns true if the signature of the operation designated by the qualified name is covered by the mask. - * - * @param qname The operation to test - * @param sigMask The signature mask to use - * - * @return True if the signature of the operation designated by the qualified name is covered by the mask - */ - boolean hasMatchingSig(JavaQualifiedName qname, JavaOperationSigMask sigMask); - - - /** - * Returns true if the signature of the field designated by its name and the qualified name of its class is covered - * by the mask. - * - * @param qname The class of the field - * @param fieldName The name of the field - * @param sigMask The signature mask to use - * - * @return True if the signature of the field is covered by the mask - */ - boolean hasMatchingSig(JavaQualifiedName qname, String fieldName, JavaFieldSigMask sigMask); - - -} diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/PackageStats.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/PackageStats.java deleted file mode 100644 index f89bc3e8849..00000000000 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/PackageStats.java +++ /dev/null @@ -1,128 +0,0 @@ -/** - * BSD-style license; for more info see http://pmd.sourceforge.net/license.html - */ - -package net.sourceforge.pmd.lang.java.metrics; - -import java.util.HashMap; -import java.util.Map; - -import net.sourceforge.pmd.lang.java.ast.JavaQualifiedName; -import net.sourceforge.pmd.lang.java.metrics.signature.JavaFieldSigMask; -import net.sourceforge.pmd.lang.java.metrics.signature.JavaOperationSigMask; - - -/** - * Statistics about a package. This recursive data structure mirrors the package structure of the analysed project and - * stores information about the classes and subpackages it contains. This object provides signature matching - * utilities to metrics. - * - * @author Clément Fournier - * @see ClassStats - */ -public final class PackageStats implements JavaSignatureMatcher { - - private final Map subPackages = new HashMap<>(); - private final Map classes = new HashMap<>(); - - - /** - * Default constructor. - */ - /* default */ PackageStats() { - - } - - - /** - * Resets the entire data structure. - */ - /* default */ void reset() { - subPackages.clear(); - classes.clear(); - } - - - /** - * Gets the ClassStats corresponding to the named resource. The class can be nested. If the createIfNotFound - * parameter is set, the method also creates the hierarchy if it doesn't exist. - * - * @param qname The qualified name of the class - * @param createIfNotFound Create hierarchy if missing - * - * @return The new ClassStats, or the one that was found. Can return null only if createIfNotFound is unset - */ - /* default */ ClassStats getClassStats(JavaQualifiedName qname, boolean createIfNotFound) { - PackageStats container = getSubPackage(qname, createIfNotFound); - - if (container == null) { - return null; - } - - String topClassName = qname.getClasses()[0]; - if (createIfNotFound && classes.get(topClassName) == null) { - classes.put(topClassName, new ClassStats()); - } - - ClassStats next = classes.get(topClassName); - - if (next == null) { - return null; - } - - String[] nameClasses = qname.getClasses(); - - for (int i = 1; i < nameClasses.length && next != null; i++) { - // Delegate search for nested classes to ClassStats - next = next.getNestedClassStats(nameClasses[i], createIfNotFound); - } - - return next; - } - - - /** - * Returns the deepest PackageStats that contains the named resource. If the second parameter is set, creates the - * missing PackageStats along the way. - * - * @param qname The qualified name of the resource - * @param createIfNotFound If set to true, the hierarch is created if missing - * - * @return The deepest package that contains this resource. Can only return null if createIfNotFound is unset - */ - private PackageStats getSubPackage(JavaQualifiedName qname, boolean createIfNotFound) { - if (qname.getPackages() == null) { - return this; // the toplevel - } - - String[] packagePath = qname.getPackages(); - PackageStats next = this; - - for (int i = 0; i < packagePath.length && next != null; i++) { - if (createIfNotFound && next.subPackages.get(packagePath[i]) == null) { - next.subPackages.put(packagePath[i], new PackageStats()); - } - - next = next.subPackages.get(packagePath[i]); - } - - return next; - } - - - @Override - public boolean hasMatchingSig(JavaQualifiedName qname, JavaOperationSigMask sigMask) { - ClassStats clazz = getClassStats(qname, false); - - return clazz != null && clazz.hasMatchingSig(qname.getOperation(), sigMask); - } - - - @Override - public boolean hasMatchingSig(JavaQualifiedName qname, String fieldName, JavaFieldSigMask sigMask) { - ClassStats clazz = getClassStats(qname, false); - - return clazz != null && clazz.hasMatchingSig(fieldName, sigMask); - } - -} diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/api/JavaOperationMetric.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/api/JavaOperationMetric.java index 20260855792..656cb658e82 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/api/JavaOperationMetric.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/api/JavaOperationMetric.java @@ -4,7 +4,7 @@ package net.sourceforge.pmd.lang.java.metrics.api; -import net.sourceforge.pmd.lang.java.ast.ASTMethodOrConstructorDeclaration; +import net.sourceforge.pmd.lang.java.ast.MethodLikeNode; import net.sourceforge.pmd.lang.metrics.Metric; /** @@ -12,7 +12,7 @@ * * @author Clément Fournier */ -public interface JavaOperationMetric extends Metric { +public interface JavaOperationMetric extends Metric { } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/api/JavaOperationMetricKey.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/api/JavaOperationMetricKey.java index 152a3bf4d93..80ae084b6f7 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/api/JavaOperationMetricKey.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/api/JavaOperationMetricKey.java @@ -5,6 +5,7 @@ package net.sourceforge.pmd.lang.java.metrics.api; import net.sourceforge.pmd.lang.java.ast.ASTMethodOrConstructorDeclaration; +import net.sourceforge.pmd.lang.java.ast.MethodLikeNode; import net.sourceforge.pmd.lang.java.metrics.impl.AtfdMetric.AtfdOperationMetric; import net.sourceforge.pmd.lang.java.metrics.impl.CycloMetric; import net.sourceforge.pmd.lang.java.metrics.impl.LocMetric.LocOperationMetric; @@ -12,10 +13,11 @@ import net.sourceforge.pmd.lang.java.metrics.impl.NpathMetric; import net.sourceforge.pmd.lang.metrics.MetricKey; + /** * Keys identifying standard operation metrics. */ -public enum JavaOperationMetricKey implements MetricKey { +public enum JavaOperationMetricKey implements MetricKey { /** * Access to Foreign Data. @@ -69,9 +71,18 @@ public JavaOperationMetric getCalculator() { @Override - public boolean supports(ASTMethodOrConstructorDeclaration node) { + public boolean supports(MethodLikeNode node) { return calculator.supports(node); } + /** + * @see #supports(MethodLikeNode) + * @deprecated Provided here for backwards binary compatibility with {@link #supports(MethodLikeNode)}. + * Please explicitly link your code to that method and recompile your code. Will be remove with 7.0.0 + */ + @Deprecated + public boolean supports(ASTMethodOrConstructorDeclaration node) { + return this.supports((MethodLikeNode) node); + } } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/impl/AbstractJavaClassMetric.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/impl/AbstractJavaClassMetric.java index 045b7a8759c..c37494ec22b 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/impl/AbstractJavaClassMetric.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/impl/AbstractJavaClassMetric.java @@ -15,8 +15,9 @@ import net.sourceforge.pmd.lang.java.ast.ASTMethodOrConstructorDeclaration; import net.sourceforge.pmd.lang.java.metrics.AbstractJavaMetric; import net.sourceforge.pmd.lang.java.metrics.api.JavaClassMetric; -import net.sourceforge.pmd.lang.java.metrics.signature.JavaFieldSigMask; -import net.sourceforge.pmd.lang.java.metrics.signature.JavaOperationSigMask; +import net.sourceforge.pmd.lang.java.multifile.signature.JavaFieldSigMask; +import net.sourceforge.pmd.lang.java.multifile.signature.JavaOperationSigMask; + /** * Base class for class metrics. @@ -34,6 +35,7 @@ public abstract class AbstractJavaClassMetric extends AbstractJavaMetric List getDeclarationsOfType(ASTAnyTypeDeclaration nod List decls = node.getDeclarations(); for (ASTAnyTypeBodyDeclaration decl : decls) { - if (tClass.isInstance(decl.jjtGetChild(0))) { + if (decl.jjtGetNumChildren() > 0 && tClass.isInstance(decl.jjtGetChild(0))) { result.add(tClass.cast(decl.jjtGetChild(0))); } } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/impl/AbstractJavaOperationMetric.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/impl/AbstractJavaOperationMetric.java index 90403d55e0f..f9eed4f242e 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/impl/AbstractJavaOperationMetric.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/impl/AbstractJavaOperationMetric.java @@ -5,16 +5,19 @@ package net.sourceforge.pmd.lang.java.metrics.impl; import net.sourceforge.pmd.lang.java.ast.ASTMethodOrConstructorDeclaration; +import net.sourceforge.pmd.lang.java.ast.MethodLikeNode; import net.sourceforge.pmd.lang.java.metrics.AbstractJavaMetric; import net.sourceforge.pmd.lang.java.metrics.api.JavaOperationMetric; + /** - * Base class for operation metrics. + * Base class for operation metrics. Can be applied on method and constructor declarations, and + * lambda expressions. * * @author Clément Fournier */ -public abstract class AbstractJavaOperationMetric extends AbstractJavaMetric - implements JavaOperationMetric { +public abstract class AbstractJavaOperationMetric extends AbstractJavaMetric + implements JavaOperationMetric { /** * Returns true if the metric can be computed on this operation. By default, abstract operations are filtered out. @@ -23,7 +26,19 @@ public abstract class AbstractJavaOperationMetric extends AbstractJavaMetric opts = options.getOptions(); - JavaParserDecoratedVisitor visitor = new JavaParserDecoratedVisitor(CycloBaseVisitor.INSTANCE); + JavaParserDecoratedVisitor visitor = new JavaParserDecoratedVisitor(CycloBaseVisitor.INSTANCE) { // TODO decorators are unmaintainable, change that someday + // stops the visit when stumbling on a lambda or class decl + @Override + public Object visit(JavaNode localNode, Object data) { + return localNode.isFindBoundary() && !localNode.equals(node) ? data : super.visit(localNode, data); // TODO generalize that to other metrics + } + }; if (opts.contains(CycloOption.CONSIDER_ASSERT)) { visitor.decorateWith(new CycloAssertAwareDecorator()); @@ -59,7 +66,7 @@ public double computeFor(ASTMethodOrConstructorDeclaration node, MetricOptions o * * @return The number of paths through the expression */ - public static int booleanExpressionComplexity(ASTExpression expr) { + public static int booleanExpressionComplexity(Node expr) { if (expr == null) { return 0; } @@ -69,6 +76,10 @@ public static int booleanExpressionComplexity(ASTExpression expr) { int complexity = 0; + if (expr instanceof ASTConditionalOrExpression || expr instanceof ASTConditionalAndExpression) { + complexity++; + } + for (ASTConditionalOrExpression element : orNodes) { complexity += element.jjtGetNumChildren() - 1; } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/impl/LocMetric.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/impl/LocMetric.java index 79324a9ed9b..bcca1bd558e 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/impl/LocMetric.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/impl/LocMetric.java @@ -5,7 +5,7 @@ package net.sourceforge.pmd.lang.java.metrics.impl; import net.sourceforge.pmd.lang.java.ast.ASTAnyTypeDeclaration; -import net.sourceforge.pmd.lang.java.ast.ASTMethodOrConstructorDeclaration; +import net.sourceforge.pmd.lang.java.ast.MethodLikeNode; import net.sourceforge.pmd.lang.metrics.MetricOptions; /** @@ -21,13 +21,13 @@ public final class LocMetric { public static final class LocOperationMetric extends AbstractJavaOperationMetric { @Override - public boolean supports(ASTMethodOrConstructorDeclaration node) { + public boolean supports(MethodLikeNode node) { return true; } @Override - public double computeFor(ASTMethodOrConstructorDeclaration node, MetricOptions options) { + public double computeFor(MethodLikeNode node, MetricOptions options) { return 1 + node.getEndLine() - node.getBeginLine(); } } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/impl/NcssMetric.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/impl/NcssMetric.java index 841a4e0c1d2..99a904393fa 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/impl/NcssMetric.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/impl/NcssMetric.java @@ -9,8 +9,8 @@ import org.apache.commons.lang3.mutable.MutableInt; import net.sourceforge.pmd.lang.java.ast.ASTAnyTypeDeclaration; -import net.sourceforge.pmd.lang.java.ast.ASTMethodOrConstructorDeclaration; import net.sourceforge.pmd.lang.java.ast.JavaParserDecoratedVisitor; +import net.sourceforge.pmd.lang.java.ast.MethodLikeNode; import net.sourceforge.pmd.lang.java.metrics.impl.visitors.NcssBaseVisitor; import net.sourceforge.pmd.lang.java.metrics.impl.visitors.NcssCountImportsDecorator; import net.sourceforge.pmd.lang.metrics.MetricOption; @@ -73,13 +73,13 @@ public double computeFor(ASTAnyTypeDeclaration node, MetricOptions version) { public static final class NcssOperationMetric extends AbstractJavaOperationMetric { @Override - public boolean supports(ASTMethodOrConstructorDeclaration node) { + public boolean supports(MethodLikeNode node) { return true; } @Override - public double computeFor(ASTMethodOrConstructorDeclaration node, MetricOptions version) { + public double computeFor(MethodLikeNode node, MetricOptions version) { Set options = version.getOptions(); JavaParserDecoratedVisitor visitor = new JavaParserDecoratedVisitor(NcssBaseVisitor.INSTANCE); diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/impl/NoamMetric.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/impl/NoamMetric.java index c94237d35c2..057e74a2b08 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/impl/NoamMetric.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/impl/NoamMetric.java @@ -6,9 +6,9 @@ import net.sourceforge.pmd.lang.java.ast.ASTAnyTypeDeclaration; import net.sourceforge.pmd.lang.java.ast.ASTAnyTypeDeclaration.TypeKind; -import net.sourceforge.pmd.lang.java.metrics.signature.JavaOperationSigMask; -import net.sourceforge.pmd.lang.java.metrics.signature.JavaOperationSignature.Role; -import net.sourceforge.pmd.lang.java.metrics.signature.JavaSignature.Visibility; +import net.sourceforge.pmd.lang.java.multifile.signature.JavaOperationSigMask; +import net.sourceforge.pmd.lang.java.multifile.signature.JavaOperationSignature.Role; +import net.sourceforge.pmd.lang.java.multifile.signature.JavaSignature.Visibility; import net.sourceforge.pmd.lang.metrics.MetricOptions; /** diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/impl/NopaMetric.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/impl/NopaMetric.java index e19fa2761b4..9dc5d140987 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/impl/NopaMetric.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/impl/NopaMetric.java @@ -6,8 +6,8 @@ import net.sourceforge.pmd.lang.java.ast.ASTAnyTypeDeclaration; import net.sourceforge.pmd.lang.java.ast.ASTAnyTypeDeclaration.TypeKind; -import net.sourceforge.pmd.lang.java.metrics.signature.JavaFieldSigMask; -import net.sourceforge.pmd.lang.java.metrics.signature.JavaSignature.Visibility; +import net.sourceforge.pmd.lang.java.multifile.signature.JavaFieldSigMask; +import net.sourceforge.pmd.lang.java.multifile.signature.JavaSignature.Visibility; import net.sourceforge.pmd.lang.metrics.MetricOptions; /** diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/impl/NpathMetric.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/impl/NpathMetric.java index 634fcc6e264..6b96127c957 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/impl/NpathMetric.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/impl/NpathMetric.java @@ -5,7 +5,7 @@ package net.sourceforge.pmd.lang.java.metrics.impl; -import net.sourceforge.pmd.lang.java.ast.ASTMethodOrConstructorDeclaration; +import net.sourceforge.pmd.lang.java.ast.MethodLikeNode; import net.sourceforge.pmd.lang.java.metrics.impl.visitors.NpathBaseVisitor; import net.sourceforge.pmd.lang.metrics.MetricOptions; @@ -18,7 +18,7 @@ public class NpathMetric extends AbstractJavaOperationMetric { @Override - public double computeFor(ASTMethodOrConstructorDeclaration node, MetricOptions options) { + public double computeFor(MethodLikeNode node, MetricOptions options) { return (Integer) node.jjtAccept(NpathBaseVisitor.INSTANCE, null); } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/impl/TccMetric.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/impl/TccMetric.java index 5823e27b560..6405f242df7 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/impl/TccMetric.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/impl/TccMetric.java @@ -11,7 +11,7 @@ import java.util.Set; import net.sourceforge.pmd.lang.java.ast.ASTAnyTypeDeclaration; -import net.sourceforge.pmd.lang.java.metrics.impl.visitors.TccMethodPairVisitor; +import net.sourceforge.pmd.lang.java.metrics.impl.visitors.TccAttributeAccessCollector; import net.sourceforge.pmd.lang.metrics.MetricOptions; /** @@ -25,8 +25,7 @@ public class TccMetric extends AbstractJavaClassMetric { @Override public double computeFor(ASTAnyTypeDeclaration node, MetricOptions options) { - @SuppressWarnings("unchecked") - Map> usagesByMethod = (Map>) node.jjtAccept(new TccMethodPairVisitor(), null); + Map> usagesByMethod = new TccAttributeAccessCollector(node).start(); int numPairs = numMethodsRelatedByAttributeAccess(usagesByMethod); int maxPairs = maxMethodPairs(usagesByMethod.size()); diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/impl/WocMetric.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/impl/WocMetric.java index b3be183fdf1..c42f258e72f 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/impl/WocMetric.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/impl/WocMetric.java @@ -6,9 +6,9 @@ import net.sourceforge.pmd.lang.java.ast.ASTAnyTypeDeclaration; import net.sourceforge.pmd.lang.java.ast.ASTAnyTypeDeclaration.TypeKind; -import net.sourceforge.pmd.lang.java.metrics.signature.JavaOperationSigMask; -import net.sourceforge.pmd.lang.java.metrics.signature.JavaOperationSignature.Role; -import net.sourceforge.pmd.lang.java.metrics.signature.JavaSignature.Visibility; +import net.sourceforge.pmd.lang.java.multifile.signature.JavaOperationSigMask; +import net.sourceforge.pmd.lang.java.multifile.signature.JavaOperationSignature.Role; +import net.sourceforge.pmd.lang.java.multifile.signature.JavaSignature.Visibility; import net.sourceforge.pmd.lang.metrics.MetricOptions; /** diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/impl/visitors/AtfdBaseVisitor.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/impl/visitors/AtfdBaseVisitor.java index 77b32dd102e..34873640c19 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/impl/visitors/AtfdBaseVisitor.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/impl/visitors/AtfdBaseVisitor.java @@ -6,6 +6,7 @@ import java.util.List; +import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.mutable.MutableInt; import net.sourceforge.pmd.lang.java.ast.ASTAllocationExpression; @@ -15,7 +16,6 @@ import net.sourceforge.pmd.lang.java.ast.ASTPrimaryPrefix; import net.sourceforge.pmd.lang.java.ast.ASTPrimarySuffix; import net.sourceforge.pmd.lang.java.ast.JavaParserVisitorAdapter; -import net.sourceforge.pmd.util.StringUtil; /** * Computes Atfd. @@ -41,7 +41,7 @@ private boolean isForeignGetterSetterCall(ASTPrimaryExpression node) { String methodOrAttributeName = getMethodOrAttributeName(node); - return methodOrAttributeName != null && StringUtil.startsWithAny(methodOrAttributeName, "get", "is", "set"); + return methodOrAttributeName != null && StringUtils.startsWithAny(methodOrAttributeName, "get", "is", "set"); } @@ -63,7 +63,7 @@ private boolean isForeignAttributeOrMethod(ASTPrimaryExpression node) { result = false; } else if (nameImage == null && node.getFirstDescendantOfType(ASTPrimaryPrefix.class).usesThisModifier()) { result = false; - } else if (nameImage == null && node.hasDecendantOfAnyType(ASTLiteral.class, ASTAllocationExpression.class)) { + } else if (nameImage == null && node.hasDescendantOfAnyType(ASTLiteral.class, ASTAllocationExpression.class)) { result = false; } else { result = true; @@ -103,7 +103,7 @@ private String getMethodOrAttributeName(ASTPrimaryExpression node) { private boolean isAttributeAccess(ASTPrimaryExpression node) { - return node.findDescendantsOfType(ASTPrimarySuffix.class).isEmpty(); + return !node.hasDescendantOfType(ASTPrimarySuffix.class); } } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/impl/visitors/CycloBaseVisitor.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/impl/visitors/CycloBaseVisitor.java index 007ffe94249..47ef3aa1f55 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/impl/visitors/CycloBaseVisitor.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/impl/visitors/CycloBaseVisitor.java @@ -30,10 +30,6 @@ public class CycloBaseVisitor extends JavaParserControllessVisitorAdapter { /** Instance. */ public static final CycloBaseVisitor INSTANCE = new CycloBaseVisitor(); - protected CycloBaseVisitor() { - - } - @Override public Object visit(ASTSwitchStatement node, Object data) { int childCount = node.jjtGetNumChildren(); @@ -59,10 +55,8 @@ public Object visit(ASTSwitchStatement node, Object data) { @Override public Object visit(ASTConditionalExpression node, Object data) { - if (node.isTernary()) { - ((MutableInt) data).increment(); - super.visit(node, data); - } + ((MutableInt) data).increment(); + super.visit(node, data); return data; } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/impl/visitors/CycloPathAwareDecorator.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/impl/visitors/CycloPathAwareDecorator.java index 36f50b6968b..25d87ebbeed 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/impl/visitors/CycloPathAwareDecorator.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/impl/visitors/CycloPathAwareDecorator.java @@ -18,7 +18,6 @@ import net.sourceforge.pmd.lang.java.ast.JavaNode; import net.sourceforge.pmd.lang.java.ast.JavaParserVisitorDecorator; import net.sourceforge.pmd.lang.java.metrics.impl.CycloMetric; -import net.sourceforge.pmd.lang.java.rule.codesize.NPathComplexityRule; /** * Decorator which counts the complexity of boolean expressions for Cyclo. @@ -43,6 +42,10 @@ public Object visit(ASTIfStatement node, Object data) { public Object visit(ASTForStatement node, Object data) { super.visit(node, data); + if (node.isForeach()) { + return data; + } + int boolCompFor = CycloMetric.booleanExpressionComplexity(node.getFirstDescendantOfType(ASTExpression.class)); ((MutableInt) data).add(boolCompFor); return data; @@ -93,10 +96,9 @@ public Object visit(ASTWhileStatement node, Object data) { public Object visit(ASTConditionalExpression node, Object data) { super.visit(node, data); - if (node.isTernary()) { - int boolCompTern = NPathComplexityRule.sumExpressionComplexity(node.getFirstChildOfType(ASTExpression.class)); - ((MutableInt) data).add(1 + boolCompTern); - } + int boolCompTern = CycloMetric.booleanExpressionComplexity(node.getGuardExpressionNode()); + ((MutableInt) data).add(boolCompTern); + return data; } @@ -104,11 +106,13 @@ public Object visit(ASTConditionalExpression node, Object data) { @Override public Object visit(ASTAssertStatement node, Object data) { int base = ((MutableInt) data).getValue(); + // This is precisely the problem with decorators + // The control flow is completely obscured and information is spread out, for no real benefit super.visit(node, data); boolean isAssertAware = base < ((MutableInt) data).getValue(); if (isAssertAware) { - int boolCompAssert = CycloMetric.booleanExpressionComplexity(node.getFirstChildOfType(ASTExpression.class)); + int boolCompAssert = CycloMetric.booleanExpressionComplexity(node.getGuardExpressionNode()); ((MutableInt) data).add(boolCompAssert); } return data; diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/impl/visitors/NcssBaseVisitor.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/impl/visitors/NcssBaseVisitor.java index ac07057af62..2348a9a41eb 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/impl/visitors/NcssBaseVisitor.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/impl/visitors/NcssBaseVisitor.java @@ -46,12 +46,6 @@ public class NcssBaseVisitor extends JavaParserControllessVisitorAdapter { /** Instance. */ public static final NcssBaseVisitor INSTANCE = new NcssBaseVisitor(); - - protected NcssBaseVisitor() { - - } - - @Override public Object visit(ASTClassOrInterfaceDeclaration node, Object data) { ((MutableInt) data).increment(); 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 ab877458d81..328d64b23b9 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 @@ -33,17 +33,22 @@ public class NpathBaseVisitor extends JavaParserVisitorReducedAdapter { /** Instance. */ public static final NpathBaseVisitor INSTANCE = new NpathBaseVisitor(); - protected NpathBaseVisitor() { - - } - /* Multiplies the complexity of the children of this node. */ private int multiplyChildrenComplexities(JavaNode node, Object data) { int product = 1; 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; @@ -56,7 +61,16 @@ private int sumChildrenComplexities(JavaNode node, Object data) { 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; @@ -85,7 +99,7 @@ public Object visit(ASTIfStatement node, Object data) { 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)); @@ -99,7 +113,7 @@ public Object visit(ASTWhileStatement node, Object data) { 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; } @@ -111,7 +125,7 @@ public Object visit(ASTDoStatement node, Object data) { 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; } @@ -123,7 +137,7 @@ public Object visit(ASTForStatement node, Object data) { 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; } @@ -146,7 +160,7 @@ public Object visit(ASTReturnStatement node, Object data) { boolCompReturn += conditionalExpressionComplexity; } - return (boolCompReturn > 0) ? boolCompReturn : 1; + return boolCompReturn > 0 ? boolCompReturn : 1; } @@ -166,7 +180,7 @@ public Object visit(ASTSwitchStatement node, Object data) { npath += caseRange; caseRange = 1; } else { - Integer complexity = (Integer) n.jjtAccept(this, data); + int complexity = (int) n.jjtAccept(this, data); caseRange *= complexity; } } @@ -180,14 +194,11 @@ public Object visit(ASTSwitchStatement node, Object data) { public Object visit(ASTConditionalExpression node, Object data) { // bool comp of guard clause + complexity of last two children (= total - 1) - if (node.isTernary()) { - ASTExpression wrapper = new ASTExpression(Integer.MAX_VALUE); - wrapper.jjtAddChild(node.jjtGetChild(0), 0); - int boolCompTernary = CycloMetric.booleanExpressionComplexity(wrapper); + ASTExpression wrapper = new ASTExpression(Integer.MAX_VALUE); + wrapper.jjtAddChild(node.jjtGetChild(0), 0); + int boolCompTernary = CycloMetric.booleanExpressionComplexity(wrapper); - return boolCompTernary + sumChildrenComplexities(node, data) - 1; - } - return 1; + return boolCompTernary + sumChildrenComplexities(node, data) - 1; } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/impl/visitors/TccAttributeAccessCollector.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/impl/visitors/TccAttributeAccessCollector.java new file mode 100644 index 00000000000..06cf13cf3f7 --- /dev/null +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/impl/visitors/TccAttributeAccessCollector.java @@ -0,0 +1,164 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.metrics.impl.visitors; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; + +import net.sourceforge.pmd.lang.java.ast.ASTAnyTypeDeclaration; +import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceDeclaration; +import net.sourceforge.pmd.lang.java.ast.ASTConstructorDeclaration; +import net.sourceforge.pmd.lang.java.ast.ASTMethodDeclaration; +import net.sourceforge.pmd.lang.java.ast.ASTName; +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.JavaParserVisitorReducedAdapter; +import net.sourceforge.pmd.lang.java.symboltable.ClassScope; +import net.sourceforge.pmd.lang.java.symboltable.VariableNameDeclaration; +import net.sourceforge.pmd.lang.symboltable.Scope; + + +/** + * Returns the map of method names to the set of local attributes accessed when visiting a class. + * + * @author Clément Fournier + * @since 6.0.0 + */ +public class TccAttributeAccessCollector extends JavaParserVisitorReducedAdapter { + + private final ASTAnyTypeDeclaration exploredClass; + + + /** The name of the current method. */ + private String currentMethodName; + + private Map> methodAttributeAccess; + + + public TccAttributeAccessCollector(ASTAnyTypeDeclaration exploredClass) { + this.exploredClass = exploredClass; + } + + + /** + * Collects the attribute accesses by method into a map. + */ + @SuppressWarnings("unchecked") + public Map> start() { + return (Map>) this.visit(exploredClass, new HashMap>()); + } + + + @Override + public Object visit(ASTAnyTypeDeclaration node, Object data) { + if (Objects.equals(node, exploredClass)) { + methodAttributeAccess = new HashMap<>(); + super.visit(node, data); + } else if (node instanceof ASTClassOrInterfaceDeclaration + && ((ASTClassOrInterfaceDeclaration) node).isLocal()) { + super.visit(node, data); + } + return methodAttributeAccess; + } + + + @Override + public Object visit(ASTMethodDeclaration node, Object data) { + + if (!node.isAbstract()) { + if (node.getFirstParentOfType(ASTAnyTypeDeclaration.class) == exploredClass) { + currentMethodName = node.getQualifiedName().getOperation(); + methodAttributeAccess.put(currentMethodName, new HashSet()); + + super.visit(node, data); + currentMethodName = null; + } else { + super.visit(node, data); + } + } + + return null; + } + + + @Override + public Object visit(ASTConstructorDeclaration node, Object data) { + return data; // we're only looking for method pairs + } + + + /** + * The primary expression node is used to detect access + * to attributes and method calls. If the access is not for a + * foreign class, then the {@link #methodAttributeAccess} + * map is updated for the current method. + */ + @Override + public Object visit(ASTPrimaryExpression node, Object data) { + if (currentMethodName != null) { + Set methodAccess = methodAttributeAccess.get(currentMethodName); + String variableName = getVariableName(node); + if (isLocalAttributeAccess(variableName, node.getScope())) { + methodAccess.add(variableName); + } + } + + + return super.visit(node, data); + } + + private String getVariableName(ASTPrimaryExpression node) { + ASTPrimaryPrefix prefix = node.getFirstDescendantOfType(ASTPrimaryPrefix.class); + + if (prefix.usesThisModifier()) { + List suffixes = node.findChildrenOfType(ASTPrimarySuffix.class); + if (suffixes.size() > 1) { + if (!suffixes.get(1).isArguments()) { // not a method call + return suffixes.get(0).getImage(); + } + } + } + + ASTName name = prefix.getFirstDescendantOfType(ASTName.class); + + String variableName = null; + + if (name != null) { + int dotIndex = name.getImage().indexOf("."); + if (dotIndex == -1) { + variableName = name.getImage(); + } else { + variableName = name.getImage().substring(0, dotIndex); + } + } + + return variableName; + } + + + private boolean isLocalAttributeAccess(String varName, Scope scope) { + Scope currentScope = scope; + + while (currentScope != null) { + for (VariableNameDeclaration decl : currentScope.getDeclarations(VariableNameDeclaration.class).keySet()) { + if (decl.getImage().equals(varName)) { + if (currentScope instanceof ClassScope + && ((ClassScope) currentScope).getClassDeclaration().getNode() == exploredClass) { + return true; + } + } + } + currentScope = currentScope.getParent(); // WARNING doesn't consider inherited fields or static imports + } + + return false; + } + +} diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/impl/visitors/TccMethodPairVisitor.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/impl/visitors/TccMethodPairVisitor.java deleted file mode 100644 index e804812656b..00000000000 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/impl/visitors/TccMethodPairVisitor.java +++ /dev/null @@ -1,121 +0,0 @@ -/** - * BSD-style license; for more info see http://pmd.sourceforge.net/license.html - */ - -package net.sourceforge.pmd.lang.java.metrics.impl.visitors; - -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; -import java.util.Stack; - -import net.sourceforge.pmd.lang.java.ast.ASTAnyTypeDeclaration; -import net.sourceforge.pmd.lang.java.ast.ASTMethodDeclaration; -import net.sourceforge.pmd.lang.java.ast.ASTName; -import net.sourceforge.pmd.lang.java.ast.ASTPrimaryExpression; -import net.sourceforge.pmd.lang.java.ast.ASTPrimaryPrefix; -import net.sourceforge.pmd.lang.java.ast.JavaParserVisitorReducedAdapter; -import net.sourceforge.pmd.lang.java.symboltable.ClassScope; -import net.sourceforge.pmd.lang.java.symboltable.VariableNameDeclaration; -import net.sourceforge.pmd.lang.symboltable.Scope; - -/** - * Returns the map of method names to the set local attributes accessed when visiting a class. - * - * @author Clément Fournier - * @since 6.0.0 - */ -public class TccMethodPairVisitor extends JavaParserVisitorReducedAdapter { - - /** - * Collects for each method of the current class, which local attributes are accessed. - */ - Stack>> methodAttributeAccess = new Stack<>(); - - /** The name of the current method. */ - private String currentMethodName; - - - @Override - public Object visit(ASTAnyTypeDeclaration node, Object data) { - methodAttributeAccess.push(new HashMap>()); - super.visit(node, data); - - methodAttributeAccess.peek().remove(null); - return methodAttributeAccess.pop(); - } - - - @Override - public Object visit(ASTMethodDeclaration node, Object data) { - - if (!node.isAbstract()) { - currentMethodName = node.getQualifiedName().getOperation(); - methodAttributeAccess.peek().put(currentMethodName, new HashSet()); - - super.visit(node, data); - - currentMethodName = null; - } - - return null; - } - - - /** - * The primary expression node is used to detect access to attributes and method calls. If the access is not for a - * foreign class, then the {@link #methodAttributeAccess} map is updated for the current method. - */ - @Override - public Object visit(ASTPrimaryExpression node, Object data) { - if (currentMethodName != null) { - Set methodAccess = methodAttributeAccess.peek().get(currentMethodName); - String variableName = getVariableName(node); - if (isLocalAttributeAccess(variableName, node.getScope())) { - methodAccess.add(variableName); - } - } - - - return super.visit(node, data); - } - - - private String getVariableName(ASTPrimaryExpression node) { - ASTPrimaryPrefix prefix = node.getFirstDescendantOfType(ASTPrimaryPrefix.class); - ASTName name = prefix.getFirstDescendantOfType(ASTName.class); - - String variableName = null; - - if (name != null) { - int dotIndex = name.getImage().indexOf("."); - if (dotIndex == -1) { - variableName = name.getImage(); - } else { - variableName = name.getImage().substring(0, dotIndex); - } - } - - return variableName; - } - - - private boolean isLocalAttributeAccess(String varName, Scope scope) { - Scope currentScope = scope; - - while (currentScope != null) { - for (VariableNameDeclaration decl : currentScope.getDeclarations(VariableNameDeclaration.class).keySet()) { - if (decl.getImage().equals(varName)) { - if (currentScope instanceof ClassScope) { - return true; - } - } - } - currentScope = currentScope.getParent(); // WARNING doesn't consider inherited fields or static imports - } - - return false; - } - -} diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/rule/CyclomaticComplexityRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/rule/CyclomaticComplexityRule.java deleted file mode 100644 index 9cc50b70a48..00000000000 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/rule/CyclomaticComplexityRule.java +++ /dev/null @@ -1,113 +0,0 @@ -/** - * BSD-style license; for more info see http://pmd.sourceforge.net/license.html - */ - -package net.sourceforge.pmd.lang.java.metrics.rule; - -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; - -import net.sourceforge.pmd.lang.java.ast.ASTAnyTypeDeclaration; -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.metrics.JavaMetrics; -import net.sourceforge.pmd.lang.java.metrics.api.JavaClassMetricKey; -import net.sourceforge.pmd.lang.java.metrics.api.JavaOperationMetricKey; -import net.sourceforge.pmd.lang.java.metrics.impl.CycloMetric.CycloOption; -import net.sourceforge.pmd.lang.java.rule.AbstractJavaMetricsRule; -import net.sourceforge.pmd.lang.metrics.MetricOptions; -import net.sourceforge.pmd.lang.metrics.ResultOption; -import net.sourceforge.pmd.properties.EnumeratedMultiProperty; -import net.sourceforge.pmd.properties.IntegerProperty; - -/** - * Cyclomatic complexity rule using metrics. - * - * @author Clément Fournier - */ -public class CyclomaticComplexityRule extends AbstractJavaMetricsRule { - - private static final IntegerProperty CLASS_LEVEL_DESCRIPTOR = new IntegerProperty( - "classReportLevel", "Total class complexity reporting threshold", 1, 600, 80, 1.0f); - - private static final IntegerProperty METHOD_LEVEL_DESCRIPTOR = new IntegerProperty( - "methodReportLevel", "Cyclomatic complexity reporting threshold", 1, 50, 10, 1.0f); - - private static final Map OPTION_MAP; - - - static { - OPTION_MAP = new HashMap<>(); - OPTION_MAP.put(CycloOption.IGNORE_BOOLEAN_PATHS.valueName(), CycloOption.IGNORE_BOOLEAN_PATHS); - OPTION_MAP.put(CycloOption.CONSIDER_ASSERT.valueName(), CycloOption.CONSIDER_ASSERT); - } - - - private static final EnumeratedMultiProperty CYCLO_OPTIONS_DESCRIPTOR = new EnumeratedMultiProperty<>( - "cycloOptions", "Choose options for the computation of Cyclo", - OPTION_MAP, Collections.emptyList(), CycloOption.class, 3.0f); - - private int methodReportLevel; - private int classReportLevel; - private MetricOptions cycloOptions; - - - public CyclomaticComplexityRule() { - definePropertyDescriptor(CLASS_LEVEL_DESCRIPTOR); - definePropertyDescriptor(METHOD_LEVEL_DESCRIPTOR); - definePropertyDescriptor(CYCLO_OPTIONS_DESCRIPTOR); - } - - - @Override - public Object visit(ASTCompilationUnit node, Object data) { - methodReportLevel = getProperty(METHOD_LEVEL_DESCRIPTOR); - classReportLevel = getProperty(CLASS_LEVEL_DESCRIPTOR); - cycloOptions = MetricOptions.ofOptions(getProperty(CYCLO_OPTIONS_DESCRIPTOR)); - - - super.visit(node, data); - return data; - } - - - @Override - public Object visit(ASTAnyTypeDeclaration node, Object data) { - - super.visit(node, data); - - if (JavaClassMetricKey.WMC.supports(node)) { - int classWmc = (int) JavaMetrics.get(JavaClassMetricKey.WMC, node, cycloOptions); - - if (classWmc >= classReportLevel) { - int classHighest = (int) JavaMetrics.get(JavaOperationMetricKey.CYCLO, node, cycloOptions, ResultOption.HIGHEST); - - String[] messageParams = {node.getTypeKind().name().toLowerCase(), - node.getImage(), - " total", - classWmc + " (highest " + classHighest + ")", }; - - addViolation(data, node, messageParams); - } - } - return data; - } - - - @Override - public final Object visit(ASTMethodOrConstructorDeclaration node, Object data) { - - int cyclo = (int) JavaMetrics.get(JavaOperationMetricKey.CYCLO, node, cycloOptions); - if (cyclo >= methodReportLevel) { - addViolation(data, node, new String[] {node instanceof ASTMethodDeclaration ? "method" : "constructor", - node.getQualifiedName().getOperation(), - "", - "" + cyclo, }); - } - - return data; - } - -} diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/rule/NPathComplexityRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/rule/NPathComplexityRule.java deleted file mode 100644 index 7973b4dd504..00000000000 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/rule/NPathComplexityRule.java +++ /dev/null @@ -1,53 +0,0 @@ -/** - * BSD-style license; for more info see http://pmd.sourceforge.net/license.html - */ - -package net.sourceforge.pmd.lang.java.metrics.rule; - -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.metrics.JavaMetrics; -import net.sourceforge.pmd.lang.java.metrics.api.JavaOperationMetricKey; -import net.sourceforge.pmd.lang.java.rule.AbstractJavaMetricsRule; -import net.sourceforge.pmd.properties.IntegerProperty; - -/** - * Simple n-path complexity rule. - * - * @author Clément Fournier - */ -public class NPathComplexityRule extends AbstractJavaMetricsRule { - - private static final IntegerProperty REPORT_LEVEL_DESCRIPTOR = new IntegerProperty( - "reportLevel", "N-Path Complexity reporting threshold", 1, 30, 200, 1.0f); - - - private static int reportLevel = 200; - - - public NPathComplexityRule() { - definePropertyDescriptor(REPORT_LEVEL_DESCRIPTOR); - } - - - @Override - public Object visit(ASTCompilationUnit node, Object data) { - reportLevel = getProperty(REPORT_LEVEL_DESCRIPTOR); - - super.visit(node, data); - return data; - } - - - @Override - public final Object visit(ASTMethodOrConstructorDeclaration node, Object data) { - int npath = (int) JavaMetrics.get(JavaOperationMetricKey.NPATH, node); - if (npath >= reportLevel) { - addViolation(data, node, new String[] {node instanceof ASTMethodDeclaration ? "method" : "constructor", - node.getQualifiedName().getOperation(), "" + npath, }); - } - - return data; - } -} diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/signature/JavaFieldSigMask.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/signature/JavaFieldSigMask.java deleted file mode 100644 index b3844825ad9..00000000000 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/signature/JavaFieldSigMask.java +++ /dev/null @@ -1,39 +0,0 @@ -/** - * BSD-style license; for more info see http://pmd.sourceforge.net/license.html - */ - -package net.sourceforge.pmd.lang.java.metrics.signature; - -/** - * Signature mask for a field. Newly created masks cover any field. - * - * @author Clément Fournier - */ -public final class JavaFieldSigMask extends JavaSigMask { - - private boolean coverFinal = true; - private boolean coverStatic = true; - - - public JavaFieldSigMask() { - super(); - } - - - /** Include final fields?. */ - public void coverFinal(boolean coverFinal) { - this.coverFinal = coverFinal; - } - - - /** Include static fields?. */ - public void coverStatic(boolean coverStatic) { - this.coverStatic = coverStatic; - } - - - @Override - public boolean covers(JavaFieldSignature sig) { - return super.covers(sig) && (coverFinal || !sig.isFinal) && (coverStatic || !sig.isStatic); - } -} diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/multifile/ClassMirror.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/multifile/ClassMirror.java new file mode 100644 index 00000000000..77748ab9492 --- /dev/null +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/multifile/ClassMirror.java @@ -0,0 +1,60 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.multifile; + +import net.sourceforge.pmd.lang.java.multifile.signature.JavaFieldSigMask; +import net.sourceforge.pmd.lang.java.multifile.signature.JavaOperationSigMask; + +/** + * Represents a class for signature matching. + * + * @author Clément Fournier + * @since 6.0.0 + */ +interface ClassMirror { + + /** + * Returns true if the signature of the operation designated by the qualified name is covered by the mask. + * + * @param opName The operation to test + * @param sigMask The signature mask to use + * + * @return True if the signature of the operation designated by the qualified name is covered by the mask + */ + boolean hasMatchingOpSig(String opName, JavaOperationSigMask sigMask); + + + /** + * Returns true if the signature of the field designated by its name and the qualified name of its class is covered + * by the mask. + * + * @param fieldName The name of the field + * @param sigMask The signature mask to use + * + * @return True if the signature of the field is covered by the mask + */ + boolean hasMatchingFieldSig(String fieldName, JavaFieldSigMask sigMask); + + + /** + * Returns the number of operations matching the signature mask in the class. + * + * @param sigMask Signature mask + * + * @return The number of operations matching the signature mask + */ + int countMatchingOpSigs(JavaOperationSigMask sigMask); + + + /** + * Returns the number of fields matching the signature mask in the class. + * + * @param sigMask Signature mask + * + * @return The number of fields matching the signature mask + */ + int countMatchingFieldSigs(JavaFieldSigMask sigMask); + +} diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/multifile/ClassStats.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/multifile/ClassStats.java new file mode 100644 index 00000000000..b7aba0d141f --- /dev/null +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/multifile/ClassStats.java @@ -0,0 +1,152 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.multifile; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; + +import net.sourceforge.pmd.lang.java.ast.JavaQualifiedName; +import net.sourceforge.pmd.lang.java.multifile.signature.JavaFieldSigMask; +import net.sourceforge.pmd.lang.java.multifile.signature.JavaFieldSignature; +import net.sourceforge.pmd.lang.java.multifile.signature.JavaOperationSigMask; +import net.sourceforge.pmd.lang.java.multifile.signature.JavaOperationSignature; + +/** + * Statistics about a class, enum, interface, or annotation. Stores information about the contained members and their + * signatures, and memoizes the results of the class metrics computed on the corresponding node. + * + *

This class does not provide methods to operate directly on its nested classes, but only on itself. To operate on a + * nested class, retrieve the correct ClassStats with {@link PackageStats#getClassStats(JavaQualifiedName, boolean)} + * then use the methods of ClassStats. Note that at this level, entities of the data structure do not manipulate + * QualifiedNames anymore, only Strings. + * + * @author Clément Fournier + * @since 6.0.0 + */ +final class ClassStats implements ClassMirror { + + private Map> operations = new HashMap<>(); + private Map> fields = new HashMap<>(); + private Map nestedClasses = new HashMap<>(); + + // References to the hierarchy + // TODO:cf useful? + // private String superclass; + // private List subclasses; + + + /** + * Finds a ClassStats in the direct children of this class. This can only be a directly nested class, for example in + * the following snippet, A can get B and B can get C but A cannot get C without asking B. + *

+     * {@code
+     * class MyClass { // ClassStats A
+     *   class MyNested { // ClassStats B
+     *     class MyDeeplyNested { // ClassStats C
+     *     }
+     *   }
+     * }
+     * }
+     * 
+ * + * @param className Name of the nested class + * @param createIfNotFound Create the requested ClassStats if missing + * + * @return The new ClassStats or the one that was found. Can return null if createIfNotFound is unset + */ + ClassStats getNestedClassStats(String className, boolean createIfNotFound) { + if (createIfNotFound && !nestedClasses.containsKey(className)) { + nestedClasses.put(className, new ClassStats()); + } + return nestedClasses.get(className); + } + + + /** + * Adds an operation to the class. + * + * @param name The name of the operation + * @param sig The signature of the operation + */ + void addOperation(String name, JavaOperationSignature sig) { + if (!operations.containsKey(sig)) { + operations.put(sig, new HashSet()); + } + operations.get(sig).add(name); + } + + + /** + * Adds a field to the class. + * + * @param name The name of the field + * @param sig The signature of the field + */ + void addField(String name, JavaFieldSignature sig) { + if (!fields.containsKey(sig)) { + fields.put(sig, new HashSet()); + } + fields.get(sig).add(name); + } + + + @Override + public boolean hasMatchingOpSig(String name, JavaOperationSigMask mask) { + // Indexing on signatures optimises this type of request + for (Entry> entry : operations.entrySet()) { + if (mask.covers(entry.getKey())) { + if (entry.getValue().contains(name)) { + return true; + } + } + } + return false; + } + + + @Override + public boolean hasMatchingFieldSig(String name, JavaFieldSigMask mask) { + for (Entry> entry : fields.entrySet()) { + if (mask.covers(entry.getKey())) { + if (entry.getValue().contains(name)) { + return true; + } + } + } + return false; + } + + + @Override + public int countMatchingOpSigs(JavaOperationSigMask sigMask) { + int sum = 0; + + for (Entry> e : operations.entrySet()) { + if (sigMask.covers(e.getKey())) { + sum += e.getValue().size(); + } + } + + return sum; + } + + + @Override + public int countMatchingFieldSigs(JavaFieldSigMask sigMask) { + int sum = 0; + + for (Entry> e : fields.entrySet()) { + if (sigMask.covers(e.getKey())) { + sum += e.getValue().size(); + } + } + + return sum; + } + +} diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/multifile/MultifileVisitor.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/multifile/MultifileVisitor.java new file mode 100644 index 00000000000..40f328f1b02 --- /dev/null +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/multifile/MultifileVisitor.java @@ -0,0 +1,55 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.multifile; + +import java.util.Stack; + +import net.sourceforge.pmd.lang.java.ast.ASTAnyTypeDeclaration; +import net.sourceforge.pmd.lang.java.ast.ASTFieldDeclaration; +import net.sourceforge.pmd.lang.java.ast.ASTMethodOrConstructorDeclaration; +import net.sourceforge.pmd.lang.java.ast.JavaParserVisitorReducedAdapter; + +/** + * Fills the PackageStats. + * + * @author Clément Fournier + * @since 6.0.0 + */ +public class MultifileVisitor extends JavaParserVisitorReducedAdapter { + + private final Stack stack = new Stack<>(); + private final PackageStats toplevel; + + + MultifileVisitor(PackageStats toplevel) { + this.toplevel = toplevel; + } + + + @Override + public Object visit(ASTAnyTypeDeclaration node, Object data) { + stack.push(toplevel.getClassStats(node.getQualifiedName(), true)); + super.visit(node, data); + stack.pop(); + + return data; + } + + + @Override + public Object visit(ASTMethodOrConstructorDeclaration node, Object data) { + stack.peek().addOperation(node.getQualifiedName().getOperation(), node.getSignature()); + return super.visit(node, data); + } + + + @Override + public Object visit(ASTFieldDeclaration node, Object data) { + stack.peek().addField(node.getVariableName(), node.getSignature()); + return data; // end recursion + } + + +} diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/multifile/MultifileVisitorFacade.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/multifile/MultifileVisitorFacade.java new file mode 100644 index 00000000000..2a27b388718 --- /dev/null +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/multifile/MultifileVisitorFacade.java @@ -0,0 +1,23 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.multifile; + +import net.sourceforge.pmd.lang.java.ast.ASTCompilationUnit; +import net.sourceforge.pmd.lang.java.ast.JavaParserVisitorAdapter; + +/** + * @author Clément Fournier + * @since 6.0.0 + */ +public final class MultifileVisitorFacade extends JavaParserVisitorAdapter { + + public void initializeWith(ASTCompilationUnit rootNode) { + PackageStats projectMirror = PackageStats.INSTANCE; + MultifileVisitor visitor = new MultifileVisitor(projectMirror); + rootNode.jjtAccept(visitor, null); + } + + +} diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/multifile/PackageStats.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/multifile/PackageStats.java new file mode 100644 index 00000000000..1799b31a4bc --- /dev/null +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/multifile/PackageStats.java @@ -0,0 +1,143 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.multifile; + +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import net.sourceforge.pmd.lang.java.multifile.signature.JavaFieldSigMask; +import net.sourceforge.pmd.lang.java.multifile.signature.JavaOperationSigMask; +import net.sourceforge.pmd.lang.java.qname.JavaOperationQualifiedName; +import net.sourceforge.pmd.lang.java.qname.JavaTypeQualifiedName; + + +/** + * Statistics about a package. This recursive data structure mirrors the package structure of the analysed project and + * stores information about the classes and subpackages it contains. This object provides signature matching utilities + * to metrics. + * + * @author Clément Fournier + * @see ClassStats + * @since 6.0.0 + */ +final class PackageStats implements ProjectMirror { + + static final PackageStats INSTANCE = new PackageStats(); + + private final Map subPackages = new HashMap<>(); + private final Map classes = new HashMap<>(); + + + /** + * Default constructor. + */ + private PackageStats() { + } + + + /** + * Resets the entire data structure. + */ + /* default */ void reset() { + subPackages.clear(); + classes.clear(); + } + + + /** + * Gets the ClassStats corresponding to the named resource. The class can be nested. If the createIfNotFound + * parameter is set, the method also creates the hierarchy if it doesn't exist. + * + * @param qname The qualified name of the class + * @param createIfNotFound Create hierarchy if missing + * + * @return The new ClassStats, or the one that was found. Can return null only if createIfNotFound is unset + */ + /* default */ ClassStats getClassStats(JavaTypeQualifiedName qname, boolean createIfNotFound) { + PackageStats container = getSubPackage(qname, createIfNotFound); + + if (container == null) { + return null; + } + + String topClassName = qname.getClassList().get(0); + if (createIfNotFound && container.classes.get(topClassName) == null) { + container.classes.put(topClassName, new ClassStats()); + } + + ClassStats next = container.classes.get(topClassName); + + if (next == null) { + return null; + } + + Iterator it = qname.getClassList().iterator(); + if (it.hasNext()) { + it.next(); + } + + while (it.hasNext() && next != null) { + // Delegate search for nested classes to ClassStats + next = next.getNestedClassStats(it.next(), createIfNotFound); + } + + return next; + } + + + /** + * Returns the deepest PackageStats that contains the named resource. If the second parameter is set, creates the + * missing PackageStats along the way. + * + * @param qname The qualified name of the resource + * @param createIfNotFound If set to true, the hierarch is created if missing + * + * @return The deepest package that contains this resource. Can only return null if createIfNotFound is unset + */ + private PackageStats getSubPackage(JavaTypeQualifiedName qname, boolean createIfNotFound) { + if (qname.getPackageList().isEmpty()) { + return this; // the toplevel + } + + List packagePath = qname.getPackageList(); + PackageStats next = this; + + for (Iterator it = packagePath.iterator(); it.hasNext() && next != null;) { + String currentPackage = it.next(); + if (createIfNotFound && next.subPackages.get(currentPackage) == null) { + next.subPackages.put(currentPackage, new PackageStats()); + } + + next = next.subPackages.get(currentPackage); + } + + return next; + } + + + @Override + public boolean hasMatchingSig(JavaOperationQualifiedName qname, JavaOperationSigMask sigMask) { + ClassStats clazz = getClassStats(qname.getClassName(), false); + + return clazz != null && clazz.hasMatchingOpSig(qname.getOperation(), sigMask); + } + + + @Override + public boolean hasMatchingSig(JavaTypeQualifiedName qname, String fieldName, JavaFieldSigMask sigMask) { + ClassStats clazz = getClassStats(qname, false); + + return clazz != null && clazz.hasMatchingFieldSig(fieldName, sigMask); + } + + + @Override + public ClassMirror getClassMirror(JavaTypeQualifiedName className) { + return getClassStats(className, false); + } + +} diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/multifile/ProjectMirror.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/multifile/ProjectMirror.java new file mode 100644 index 00000000000..8f92344e871 --- /dev/null +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/multifile/ProjectMirror.java @@ -0,0 +1,53 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.multifile; + +import net.sourceforge.pmd.lang.java.multifile.signature.JavaFieldSigMask; +import net.sourceforge.pmd.lang.java.multifile.signature.JavaOperationSigMask; +import net.sourceforge.pmd.lang.java.qname.JavaOperationQualifiedName; +import net.sourceforge.pmd.lang.java.qname.JavaTypeQualifiedName; + +/** + * Represents the analysed project to provide all rules with info about other classes. + * + * @author Clément Fournier + * @since 6.0.0 + */ +interface ProjectMirror { + + /** + * Returns true if the signature of the operation designated by the qualified name is covered by the mask. + * + * @param qname The operation to test + * @param sigMask The signature mask to use + * + * @return True if the signature of the operation designated by the qualified name is covered by the mask + */ + boolean hasMatchingSig(JavaOperationQualifiedName qname, JavaOperationSigMask sigMask); + + + /** + * Returns true if the signature of the field designated by its name and the qualified name of its class is covered + * by the mask. + * + * @param qname The class of the field + * @param fieldName The name of the field + * @param sigMask The signature mask to use + * + * @return True if the signature of the field is covered by the mask + */ + boolean hasMatchingSig(JavaTypeQualifiedName qname, String fieldName, JavaFieldSigMask sigMask); + + + /** + * Gets the class mirror corresponding to the qualified name. + * + * @param className The qualified name of the class. + * + * @return The class mirror + */ + ClassMirror getClassMirror(JavaTypeQualifiedName className); + +} diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/multifile/signature/JavaFieldSigMask.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/multifile/signature/JavaFieldSigMask.java new file mode 100644 index 00000000000..f6649490d17 --- /dev/null +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/multifile/signature/JavaFieldSigMask.java @@ -0,0 +1,53 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.multifile.signature; + +/** + * Signature mask for a field. Newly created masks cover any field. + * + * @author Clément Fournier + */ +public final class JavaFieldSigMask extends JavaSigMask { + + private boolean coverFinal = true; + private boolean coverStatic = true; + + /** Include final fields?. */ + public void coverFinal(boolean coverFinal) { + this.coverFinal = coverFinal; + } + + + public void coverFinal() { + this.coverFinal = true; + } + + + public void forbidFinal() { + this.coverFinal = false; + } + + + /** Include static fields?. */ + public void coverStatic(boolean coverStatic) { + this.coverStatic = coverStatic; + } + + + public void coverStatic() { + this.coverStatic = true; + } + + + public void forbidStatic() { + this.coverStatic = false; + } + + + @Override + public boolean covers(JavaFieldSignature sig) { + return super.covers(sig) && (coverFinal || !sig.isFinal) && (coverStatic || !sig.isStatic); + } +} diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/signature/JavaFieldSignature.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/multifile/signature/JavaFieldSignature.java similarity index 85% rename from pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/signature/JavaFieldSignature.java rename to pmd-java/src/main/java/net/sourceforge/pmd/lang/java/multifile/signature/JavaFieldSignature.java index 4f1144fe8ae..83b8f1bd2fa 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/signature/JavaFieldSignature.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/multifile/signature/JavaFieldSignature.java @@ -2,7 +2,7 @@ * BSD-style license; for more info see http://pmd.sourceforge.net/license.html */ -package net.sourceforge.pmd.lang.java.metrics.signature; +package net.sourceforge.pmd.lang.java.multifile.signature; import java.util.HashMap; import java.util.Map; @@ -29,6 +29,16 @@ private JavaFieldSignature(Visibility visibility, boolean isStatic, boolean isFi } + @Override + public String toString() { + return "JavaFieldSignature{" + + "isStatic=" + isStatic + + ", isFinal=" + isFinal + + ", visibility=" + visibility + + '}'; + } + + @Override public boolean equals(Object o) { return this == o; diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/signature/JavaOperationSigMask.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/multifile/signature/JavaOperationSigMask.java similarity index 84% rename from pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/signature/JavaOperationSigMask.java rename to pmd-java/src/main/java/net/sourceforge/pmd/lang/java/multifile/signature/JavaOperationSigMask.java index 4dbe77025ef..ecd9bb79ae7 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/signature/JavaOperationSigMask.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/multifile/signature/JavaOperationSigMask.java @@ -2,13 +2,13 @@ * BSD-style license; for more info see http://pmd.sourceforge.net/license.html */ -package net.sourceforge.pmd.lang.java.metrics.signature; +package net.sourceforge.pmd.lang.java.multifile.signature; import java.util.Arrays; import java.util.EnumSet; import java.util.Set; -import net.sourceforge.pmd.lang.java.metrics.signature.JavaOperationSignature.Role; +import net.sourceforge.pmd.lang.java.multifile.signature.JavaOperationSignature.Role; /** * Signature mask for an operation. Newly created masks cover any operation that is not abstract. @@ -60,6 +60,16 @@ public void coverAbstract(boolean coverAbstract) { } + public void coverAbstract() { + this.coverAbstract = true; + } + + + public void forbidAbstract() { + this.coverAbstract = false; + } + + @Override public boolean covers(JavaOperationSignature sig) { return super.covers(sig) && roleMask.contains(sig.role) && (coverAbstract diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/signature/JavaOperationSignature.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/multifile/signature/JavaOperationSignature.java similarity index 94% rename from pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/signature/JavaOperationSignature.java rename to pmd-java/src/main/java/net/sourceforge/pmd/lang/java/multifile/signature/JavaOperationSignature.java index 2a6d615d8e0..45afbc061e8 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/signature/JavaOperationSignature.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/multifile/signature/JavaOperationSignature.java @@ -2,12 +2,13 @@ * BSD-style license; for more info see http://pmd.sourceforge.net/license.html */ -package net.sourceforge.pmd.lang.java.metrics.signature; +package net.sourceforge.pmd.lang.java.multifile.signature; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -29,7 +30,7 @@ */ public final class JavaOperationSignature extends JavaSignature { - private static final Map POOL = new HashMap<>(); + private static final Map POOL = new ConcurrentHashMap<>(); public final Role role; public final boolean isAbstract; @@ -53,6 +54,16 @@ public int hashCode() { } + @Override + public String toString() { + return "JavaOperationSignature{" + + "role=" + role + + ", isAbstract=" + isAbstract + + ", visibility=" + visibility + + '}'; + } + + /** Used internally by the pooler. */ private static int code(Visibility visibility, Role role, boolean isAbstract) { return visibility.hashCode() * 31 + role.hashCode() * 2 + (isAbstract ? 1 : 0); diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/signature/JavaSigMask.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/multifile/signature/JavaSigMask.java similarity index 90% rename from pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/signature/JavaSigMask.java rename to pmd-java/src/main/java/net/sourceforge/pmd/lang/java/multifile/signature/JavaSigMask.java index cc50cf8f505..54e51c40690 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/signature/JavaSigMask.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/multifile/signature/JavaSigMask.java @@ -2,13 +2,13 @@ * BSD-style license; for more info see http://pmd.sourceforge.net/license.html */ -package net.sourceforge.pmd.lang.java.metrics.signature; +package net.sourceforge.pmd.lang.java.multifile.signature; import java.util.Arrays; import java.util.EnumSet; import java.util.Set; -import net.sourceforge.pmd.lang.java.metrics.signature.JavaSignature.Visibility; +import net.sourceforge.pmd.lang.java.multifile.signature.JavaSignature.Visibility; import net.sourceforge.pmd.lang.metrics.SigMask; /** diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/signature/JavaSignature.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/multifile/signature/JavaSignature.java similarity index 96% rename from pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/signature/JavaSignature.java rename to pmd-java/src/main/java/net/sourceforge/pmd/lang/java/multifile/signature/JavaSignature.java index 093a611461e..c3dd860493b 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/signature/JavaSignature.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/multifile/signature/JavaSignature.java @@ -2,7 +2,7 @@ * BSD-style license; for more info see http://pmd.sourceforge.net/license.html */ -package net.sourceforge.pmd.lang.java.metrics.signature; +package net.sourceforge.pmd.lang.java.multifile.signature; import net.sourceforge.pmd.lang.ast.SignedNode; import net.sourceforge.pmd.lang.java.ast.AccessNode; diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/qname/ImmutableList.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/qname/ImmutableList.java new file mode 100644 index 00000000000..bd7b33a2ce3 --- /dev/null +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/qname/ImmutableList.java @@ -0,0 +1,676 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.qname; + +import java.lang.ref.SoftReference; +import java.util.AbstractMap.SimpleImmutableEntry; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.ListIterator; +import java.util.Map.Entry; +import java.util.Objects; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import net.sourceforge.pmd.lang.java.ast.JavaQualifiedName; + + +/** + * Classical immutable linked list representing an ordered collection. + * This implementation has constant-time prepend, size, head and tail + * operations. Most other operations are linear-time, but caching helps + * reduce the overhead of some common operations (reverse). Allows + * structural sharing of the tail, which makes many operations constant- + * or zero-memory cost. + * + *

This implementation is designed for {@link JavaQualifiedName}, to + * represent package and class hierarchies. It is not meant as a general- + * purpose implementation. + * + * @param Element type + * + * @author Clément Fournier + * @since 6.1.0 + */ +interface ImmutableList extends List { + /** + * Adds an element at the beginning of this list. + * + * @param elem the element to prepend. + * + * @return a list which contains {@code elem} as first element and + * which continues with this list. + */ + ImmutableList prepend(E elem); + + + /** + * Size of this list. + */ + @Override + int size(); + + + /** + * Returns true if this list has no elements. + */ + @Override + boolean isEmpty(); + + + /** + * Returns the first element of this list. + * + * @throws IndexOutOfBoundsException If this list is empty + */ + E head(); + + + /** + * Returns the list of the elements after the first. + * + * @throws IndexOutOfBoundsException If this list is empty + */ + ImmutableList tail(); + + + /** + * Returns the ith element of the list. + * + * @param i Index of the element + * + * @throws IndexOutOfBoundsException if the index is out of bounds + */ + @Override + E get(int i); + + + /** + * Returns a list with the same elements as this one, but ordered in reverse.. + */ + ImmutableList reverse(); + + + /** + * Returns a list of pairs, result of zipping the two lists together. + * Eg {@code List(1,2,3).zip(List("a", "b", "c")) == List((1,"a"), (2,"b"), (3,"c"))}. + * + *

The resulting list has the length of the shortest input list. + * Trailing elements of the longer list are discarded. + * + * @param right List providing the right pair elements + * @param Type of the right list + * + * @return A list of pairs + */ + ImmutableList> zip(ImmutableList right); + + + /** + * Returns an iterator over the elements of this list. + * It iterates from the head to the last element. + */ + @Override + Iterator iterator(); + + + /** + * Returns a mutable list containing the same elements as + * this list. Changes made to the returned collection do not + * affect this list. + * + * @return A list with the same elements as this list + */ + List toList(); + + + /** + * Companion class providing factory methods for immutable lists. + * Since we do not offer a general-purpose implementation, these + * factories are package private. + */ + final class ListFactory { + /** Empty list instance. */ + private static final Nil NIL = new Nil<>(); + + + private ListFactory() { + + } + + + /** + * Splits this string around matches of the given regular expression. + * This method works as if by invoking the {@link String#split(String)} + * method on both arguments, and converting the resulting array to a list. + * IMPORTANT: the returned list's head points to the last match. + * + * @param input input string on which to match + * @param regex the delimiting regular expression + * + * @return A list of strings computed by splitting the input string + * around matches of the given regular expression + * + * @see String#split(String) + */ + static ImmutableList split(String input, String regex) { + return split(input, regex, 0); + } + + + /** + * Splits this string around matches of the given regular expression. + * This method works as if by invoking the {@link String#split(String, int)} + * method on the arguments, and converting the resulting array to a list. + * IMPORTANT: The returned list's head points to the last match. + * + * @param input input string on which to match + * @param regex the delimiting regular expression + * @param limit maximum number of matches + * + * @return A list of strings computed by splitting the input string + * around matches of the given regular expression + * + * @see String#split(String, int) + */ + static ImmutableList split(String input, String regex, int limit) { + // this was adapted from Pattern.split + int index = 0; + boolean matchLimited = limit > 0; + ImmutableList matchList = emptyList(); + Matcher m = Pattern.compile(regex).matcher(input); + + // Add segments before each match found + while (m.find()) { + if (!matchLimited || matchList.size() < limit - 1) { + if (index == 0 && index == m.start() && m.start() == m.end()) { + // no empty leading substring included for zero-width match + // at the beginning of the input char sequence. + continue; + } + String match = input.subSequence(index, m.start()).toString(); + matchList = matchList.prepend(match); + index = m.end(); + } else if (matchList.size() == limit - 1) { // last one + String match = input.subSequence(index, + input.length()).toString(); + matchList = matchList.prepend(match); + index = m.end(); + } + } + + // If no match was found, return the input + if (index == 0) { + return make(input); + } + + // Add remaining segment + if (!matchLimited || matchList.size() < limit) { + matchList = matchList.prepend(input.subSequence(index, input.length()).toString()); + } + + // Eliminate the trailing empty strings + if (limit == 0) { + while (!matchList.isEmpty() && matchList.head().equals("")) { + matchList = matchList.tail(); + } + } + return matchList; + } + + + /** + * Returns an empty immutable list. + * + * @param The argument type + * + * @return An empty list + */ + @SuppressWarnings("unchecked") + static ImmutableList emptyList() { + return (ImmutableList) NIL; + } + + + /** + * Returns an empty list. In all cases behaves like {@link #emptyList()}, + * but is an overload of {@link #make(Object[])} for convenience. + * + * @param Element type + * + * @return An empty list + */ + static ImmutableList make() { + return emptyList(); + } + + + private static ImmutableList fromArray(E[] arr) { + ImmutableList cur = emptyList(); + if (arr != null) { + for (E item : arr) { + cur = cur.prepend(item); + } + } + return cur; + } + + + /** + * Creates and returns a list containing the given elements. + * The returned list's head is the first parameter. + * + * @param elems Elements of the list in order + * @param Element type + * + * @return A list containing the given elements. + */ + @SafeVarargs + static ImmutableList make(E... elems) { + return fromArray(elems); + } + + + /** + * Abstract implementation. + * + * @param Element type + */ + private abstract static class AbstractImmutableList implements ImmutableList { + + @Override + public boolean add(E e) { + throw new UnsupportedOperationException(); + } + + + @Override + public boolean addAll(Collection c) { + throw new UnsupportedOperationException(); + } + + + @Override + public boolean addAll(int index, Collection c) { + throw new UnsupportedOperationException(); + } + + + @Override + public void add(int index, E element) { + throw new UnsupportedOperationException(); + } + + + @Override + public boolean remove(Object o) { + throw new UnsupportedOperationException(); + } + + + @Override + public boolean removeAll(Collection c) { + throw new UnsupportedOperationException(); + } + + + @Override + public E remove(int index) { + throw new UnsupportedOperationException(); + } + + + @Override + public boolean retainAll(Collection c) { + throw new UnsupportedOperationException(); + } + + + @Override + public void clear() { + throw new UnsupportedOperationException(); + } + + + @Override + public ListIterator listIterator() { + throw new UnsupportedOperationException(); + } + + + @Override + public ListIterator listIterator(int index) { + throw new UnsupportedOperationException(); + } + + @Override + public T[] toArray(T[] a) { + return toList().toArray(a); + } + + + @Override + public Object[] toArray() { + return toList().toArray(); + } + + @Override + public final AbstractImmutableList prepend(E elem) { + return new ListNode<>(elem, this); + } + + + @Override + public E set(int index, E element) { + throw new UnsupportedOperationException(); + } + + + @Override + public List subList(int fromIndex, int toIndex) { + throw new UnsupportedOperationException(); + } + + + @Override + public final ImmutableList> zip(ImmutableList right) { + Iterator thisIt = this.iterator(); + Iterator rightIt = right.iterator(); + ImmutableList> result = emptyList(); + while (thisIt.hasNext() && rightIt.hasNext()) { + result = result.prepend(new SimpleImmutableEntry<>(thisIt.next(), rightIt.next())); + } + return result.reverse(); + } + } + + /** + * Empty list. Uses reference equality since there's only one instance around. + * + * @param Element type + */ + private static class Nil extends AbstractImmutableList { + + /** + * You should not use that, use {@link #emptyList()}. + */ + Nil() { + // There should only be one instance of that + } + + @Override + public Iterator iterator() { + return Collections.emptyIterator(); + } + + + @Override + public E get(int i) { + throw new IndexOutOfBoundsException("Empty list!"); + } + + + @Override + public int size() { + return 0; + } + + + @Override + public boolean isEmpty() { + return true; + } + + + @Override + public E head() { + throw new IndexOutOfBoundsException("Empty list!"); + } + + + @Override + public ImmutableList tail() { + throw new IndexOutOfBoundsException("Empty list!"); + } + + + @Override + public ImmutableList reverse() { + return this; + } + + + @Override + public boolean contains(Object item) { + return false; + } + + + @Override + public boolean containsAll(Collection c) { + return false; + } + + + @Override + public int indexOf(Object o) { + return -1; + } + + + @Override + public int lastIndexOf(Object o) { + return -1; + } + + + @Override + public String toString() { + return "List()"; + } + + + @Override + public List toList() { + return Collections.emptyList(); + } + } + + /** + * Non-empty list. + * + * @param Element type + */ + private static class ListNode extends AbstractImmutableList { + private final E head; + private final ImmutableList tail; + private final int size; + private SoftReference> reverseCache; + + + private ListNode(E head, AbstractImmutableList tail) { + this.head = head; + this.tail = tail; + this.size = tail.isEmpty() ? 1 : tail.size() + 1; + } + + + @Override + public int size() { + return size; + } + + + @Override + public boolean isEmpty() { + return false; + } + + + @Override + public E head() { + return head; + } + + + @Override + public ImmutableList tail() { + return tail; + } + + + @Override + public E get(int i) { + if (i < 0 || i > size()) { + throw new IndexOutOfBoundsException(); + } + + return i == 0 ? head() : tail().get(i - 1); + } + + + @Override + public Iterator iterator() { + return new NodeIterator<>(this); + } + + + @Override + public ImmutableList reverse() { + if (reverseCache == null || reverseCache.get() == null) { + ImmutableList rev = buildReverse(); + ((ListNode) rev).reverseCache = new SoftReference>(this); + reverseCache = new SoftReference<>(rev); + } + + return reverseCache.get(); + } + + + private ImmutableList buildReverse() { + ImmutableList cur = emptyList(); + for (E item : this) { + cur = cur.prepend(item); + } + return cur; + } + + + @Override + public List toList() { + List result = new ArrayList<>(size()); + for (E item : this) { + result.add(item); + } + return result; + } + + + @Override + public boolean contains(Object o) { + return Objects.equals(head(), o) || tail.contains(o); + } + + + @Override + public boolean containsAll(Collection c) { + for (Object o : c) { + if (!contains(o)) { + return false; + } + } + return true; + } + + + @Override + public int indexOf(Object o) { + int i = 0; + for (E e : this) { + if (Objects.equals(e, o)) { + return i; + } + i++; + } + return -1; + } + + + @Override + public int lastIndexOf(Object o) { + int i = reverse().indexOf(o); + return i < 0 ? i : size() - i - 1; + } + + + @Override + public String toString() { + if (size() == 1) { + return "List(" + head() + ")"; + } + + StringBuilder sb = new StringBuilder("List(").append(head()); + for (E elem : tail()) { + sb.append(", ").append(elem); + } + return sb.append(")").toString(); + } + + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + ListNode listNode = (ListNode) o; + return size == listNode.size + && Objects.equals(head, listNode.head) + && Objects.equals(tail, listNode.tail); + } + + + @Override + public int hashCode() { + return Objects.hash(head, tail, size); + } + + + private static class NodeIterator implements Iterator { + ImmutableList current; + + + NodeIterator(ImmutableList start) { + current = start; + } + + + @Override + public boolean hasNext() { + return !current.isEmpty(); + } + + + @Override + public E next() { + E head = current.head(); + current = current.tail(); + return head; + } + + + @Override + public void remove() { + throw new UnsupportedOperationException(); + } + } + } + } +} diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/qname/JavaOperationQualifiedName.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/qname/JavaOperationQualifiedName.java new file mode 100644 index 00000000000..02d23360aaf --- /dev/null +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/qname/JavaOperationQualifiedName.java @@ -0,0 +1,94 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.qname; + +import java.util.Objects; + +import net.sourceforge.pmd.lang.java.ast.JavaQualifiedName; + + +/** + * Specializes JavaQualifiedName for operations. + * + * @author Clément Fournier + * @since 6.1.0 + */ +public final class JavaOperationQualifiedName extends JavaQualifiedName { + + + private final JavaTypeQualifiedName parent; + private final String operation; + private final boolean isLambda; + + + JavaOperationQualifiedName(JavaTypeQualifiedName parent, String operation, boolean isLambda) { + Objects.requireNonNull(operation); + Objects.requireNonNull(parent); + + this.parent = parent; + this.operation = operation; + this.isLambda = isLambda; + } + + + @Override + public JavaTypeQualifiedName getClassName() { + return parent; + } + + + @Override + public boolean isOperation() { + return true; + } + + + @Override + public boolean isClass() { + return false; + } + + + /** + * Returns true if this qualified name identifies a lambda expression. + */ + public boolean isLambda() { + return isLambda; + } + + + /** + * Returns the operation specific part of the name. It + * identifies an operation in its namespace. + * + * @return The operation string. + */ + @Override + public String getOperation() { + return operation; + } + + + + @Override + protected boolean structurallyEquals(JavaQualifiedName qname) { + JavaOperationQualifiedName that = (JavaOperationQualifiedName) qname; + return isLambda == that.isLambda + && this.operation.equals(that.operation) + && this.parent.equals(that.parent); + } + + + @Override + protected int buildHashCode() { + return parent.hashCode() * 31 + Objects.hash(isLambda, operation); + } + + @Override + protected String buildToString() { + return parent.toString() + "#" + operation; + } +} + 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 new file mode 100644 index 00000000000..2f02863f046 --- /dev/null +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/qname/JavaTypeQualifiedName.java @@ -0,0 +1,241 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.qname; + +import java.util.List; +import java.util.Map.Entry; +import java.util.Objects; + +import org.apache.commons.lang3.StringUtils; + +import net.sourceforge.pmd.lang.java.ast.JavaQualifiedName; + + +/** + * Specialises {@link JavaQualifiedName} for type names. + * + * @author Clément Fournier + * @since 6.1.0 + */ +public final class JavaTypeQualifiedName extends JavaQualifiedName { + + /** Local index value for when the class is not local. */ + static final int NOTLOCAL_PLACEHOLDER = -1; + + // since we prepend each time, these lists are in the reversed order (innermost elem first). + // we use ImmutableList.reverse() to get them in their usual, user-friendly order + + private final ImmutableList packages; // unnamed package == Nil + private final ImmutableList classes; + + /** + * Local indices of the parents and of this class, in order. + * They can be zipped with the {@link #classes} list. + * + *

If a class is not local, its local index is {@link #NOTLOCAL_PLACEHOLDER}. + */ + private final ImmutableList localIndices; + + private Class representedType; + private boolean typeLoaded; + + private final ClassLoader classLoader; + + JavaTypeQualifiedName(ImmutableList packages, ImmutableList classes, ImmutableList localIndices, ClassLoader classLoader) { + Objects.requireNonNull(packages); + Objects.requireNonNull(classes); + Objects.requireNonNull(localIndices); + + if (classes.isEmpty() || localIndices.size() != classes.size()) { + throw new IllegalArgumentException("Error building a type qualified name"); + } + + this.packages = packages; + this.classes = classes; + this.localIndices = localIndices; + + this.classLoader = classLoader; // classLoader may be null + } + + + @Override + public JavaTypeQualifiedName getClassName() { + return this; + } + + + @Override + protected boolean structurallyEquals(JavaQualifiedName qname) { + JavaTypeQualifiedName that = (JavaTypeQualifiedName) qname; + return this.packages.equals(that.packages) + && this.classes.equals(that.classes) + && this.localIndices.equals(that.localIndices); + } + + + @Override + protected int buildHashCode() { + return Objects.hash(packages, classes, localIndices); + } + + + @Override + public boolean isClass() { + return true; + } + + + @Override + public boolean isOperation() { + return false; + } + + + /** + * Returns true if this qualified name identifies a + * local class. + */ + @Override + public boolean isLocalClass() { + return localIndices.head() != NOTLOCAL_PLACEHOLDER; + } + + + /** + * Returns true if this qualified name identifies an + * anonymous class. + */ + public boolean isAnonymousClass() { + return !isLocalClass() && StringUtils.isNumeric(getClassSimpleName()); + } + + + /** + * Get the simple name of the class. + */ + @Override + public String getClassSimpleName() { + return classes.head(); + } + + + /** + * Returns true if the class represented by this + * qualified name is in the unnamed package. + */ + @Override + public boolean isUnnamedPackage() { + return packages.isEmpty(); + } + + + /** + * Returns the packages in outer-to-inner order. This + * is specific to Java's package structure. If the + * outer class is in the unnamed package, returns an + * empty list. + * + *

{@literal @NotNull} + * + * @return The packages. + */ + public List getPackageList() { + return packages.reverse(); + } + + + /** + * Returns the class specific part of the name. It + * identifies a class in the namespace it's declared + * in. If the class is nested inside another, then + * the list returned contains all enclosing classes + * in order, from outermost to innermost. + * + *

{@literal @NotNull} + * + * @return The class names. + */ + public List getClassList() { + return classes.reverse(); + } + + + /** + * Gets the Class instance identified by this qualified name. + * + * @return A class instance, or null if the classloader threw a {@link ClassNotFoundException} + * or {@link LinkageError} while trying to load the class. + */ + public Class getType() { + synchronized (this) { + if (!typeLoaded) { + typeLoaded = true; + try { + representedType = loadType(); + } catch (ClassNotFoundException | LinkageError e) { + representedType = null; + //TODO: report missing/broken type in auxclasspath + } + } + return representedType; + } + } + + + /** + * Gets the Class instance identified by this qualified name. + * + * @return A class instance + * + * @throws ClassNotFoundException if the class is not found + */ + private Class loadType() throws ClassNotFoundException { + if (classLoader != null) { + // hence why the toString should follow binary name specification + return classLoader.loadClass(getBinaryName()); + } + return null; + } + + + /** + * Returns the binary name of the type identified by this qualified name. + * The binary name can be used to load a {@link Class} using a {@link ClassLoader}. + * Contrary to this method, {@link #toString()} is not guaranteed to return + * a binary name. For most purposes, you should avoid using this method + * directly and use {@link #getType()} instead. Just don't build a + * dependency on the toString if you want a binary name. + * + * @return The binary name of the type identified by this qualified name + */ + public String getBinaryName() { + return toString(); + } + + + @Override + protected String buildToString() { + StringBuilder sb = new StringBuilder(); + + for (String aPackage : packages.reverse()) { + sb.append(aPackage).append('.'); + } + + // this in the normal order + ImmutableList reversed = classes.reverse(); + sb.append(reversed.head()); + for (Entry classAndLocalIdx : reversed.tail().zip(localIndices.reverse().tail())) { + sb.append('$'); + + if (classAndLocalIdx.getValue() != JavaTypeQualifiedName.NOTLOCAL_PLACEHOLDER) { + sb.append(classAndLocalIdx.getValue()); + } + + sb.append(classAndLocalIdx.getKey()); + } + + return sb.toString(); + } +} 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 new file mode 100644 index 00000000000..b57e5b25499 --- /dev/null +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/qname/QualifiedNameFactory.java @@ -0,0 +1,184 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.qname; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.apache.commons.lang3.StringUtils; + +import net.sourceforge.pmd.lang.java.ast.JavaQualifiedName; +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 + */ +public final class QualifiedNameFactory { + + /** Operation part of a lambda. */ + private static final String LAMBDA_PATTERN = "lambda\\$(\\w++)?\\$\\d++"; + private static final Pattern COMPILED_LAMBDA_PATTERN = Pattern.compile(LAMBDA_PATTERN); + + + /** + * Pattern specifying the format. + * + *

+     *     ((\w++\.)*)              # packages
+     *     (                        # classes
+     *       (\w++)                 # primary class
+     *       (
+     *         \$                   # separator
+     *         \d*+                 # optional local/anonymous class index
+     *         (\D\w*+)?            # regular class name, absent for anonymous class
+     *       )*
+     *     )
+     *     (                        # optional operation suffix
+     *       \#
+     *       (
+     *         lambda\$(\w++)\$\d++ # name of a lambda
+     *       |
+     *         (\w++)               # method name
+     *         \(
+     *         (                    # parameters
+     *           (\w++)
+     *           (,\040\w++)*       # \040 is a space
+     *         )?
+     *         \)
+     *       )
+     *     )?
+     * 
+ */ + private static final Pattern FORMAT = Pattern.compile("((\\w++\\.)*) # packages\n" // don't forget to edit the javadoc upon change + + "( # classes\n" + + " (\\w++) # primary class\n" + + " (" + + " \\$ # separator\n" + + " \\d*+ # optional local/anonymous class index\n" + + " ([a-zA-Z]\\w*+)? # regular class name, absent for anonymous class\n" + + " )*" + + ")" + + "( # optional operation suffix\n" + + " \\#" + + " (" + + " " + LAMBDA_PATTERN + " # name of a lambda\n" + + " | " + + " (\\w++) # method name\n" + + " \\(" + + " ( # parameters\n" + + " (\\w++)" + + " (,\\040\\w++)* # \040 is a space\n" + + " )?" + + " \\)" + + " )" + + ")?", Pattern.COMMENTS); + // indices of interesting groups in the regex + private static final int PACKAGES_GROUP_INDEX = 1; + private static final int CLASSES_GROUP_INDEX = 3; + private static final int OPERATION_GROUP_INDEX = 7; + + private static final Pattern LOCAL_INDEX_PATTERN = Pattern.compile("(\\d+)(\\D\\w+)"); + + + private QualifiedNameFactory() { + + } + + + /** + * Gets the qualified name of a class. + * + * @param clazz Class object + * + * @return The qualified name of the class, or null if the class is null + */ + public static JavaTypeQualifiedName ofClass(Class clazz) { + if (clazz == null) { + return null; + } + + String name = clazz.getName(); + if (name.indexOf('.') < 0) { + name = '.' + name; // unnamed package, marked by a full stop. See ofString's format below + } + + // 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()); + } + + + /** + * Parses a qualified name given in the format defined for this implementation. The format + * is specified by a regex pattern (see {@link #FORMAT}). Examples: + * + *

{@code com.company.MyClass$Nested#myMethod(String, int)} + *

    + *
  • Packages are separated by full stops; + *
  • Nested classes are separated by a dollar symbol; + *
  • The optional method suffix is separated from the class with a hashtag; + *
  • Method arguments are separated by a comma and a single space. + *
+ * + *

{@code MyClass$Nested} + *

    + *
  • The qualified name of a class in the unnamed package starts with the class name. + *
+ * + *

{@code com.foo.Class$1LocalClass} + *

    + *
  • A local class' qualified name is assigned an index which identifies it within the scope + * of its enclosing class. The index is displayed after the separating dollar symbol. + *
+ * + * @param name The name to parse. + * + * @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()) { + return null; + } + + ImmutableList packages = StringUtils.isBlank(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(); + + ImmutableList indexAndClasses = ListFactory.split(matcher.group(CLASSES_GROUP_INDEX), "\\$"); + ImmutableList localIndices = ListFactory.emptyList(); + ImmutableList classes = ListFactory.emptyList(); + + // iterates right to left + for (String clazz : indexAndClasses.reverse()) { + Matcher localIndexMatcher = LOCAL_INDEX_PATTERN.matcher(clazz); + if (localIndexMatcher.matches()) { // anonymous classes don't match, because there needs to be at least one non-digit + localIndices = localIndices.prepend(Integer.parseInt(localIndexMatcher.group(1))); + classes = classes.prepend(localIndexMatcher.group(2)); + } else { + localIndices = localIndices.prepend(JavaTypeQualifiedName.NOTLOCAL_PLACEHOLDER); + classes = classes.prepend(clazz); + } + } + + 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 new file mode 100644 index 00000000000..d7f7226a6a6 --- /dev/null +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/qname/QualifiedNameResolver.java @@ -0,0 +1,459 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.qname; + +import static net.sourceforge.pmd.lang.java.qname.JavaTypeQualifiedName.NOTLOCAL_PLACEHOLDER; + +import java.util.HashMap; +import java.util.Map; +import java.util.Stack; +import java.util.concurrent.ConcurrentHashMap; + +import org.apache.commons.lang3.mutable.MutableInt; + +import net.sourceforge.pmd.lang.ast.Node; +import net.sourceforge.pmd.lang.java.ast.ASTAllocationExpression; +import net.sourceforge.pmd.lang.java.ast.ASTAnyTypeDeclaration; +import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceDeclaration; +import net.sourceforge.pmd.lang.java.ast.ASTCompilationUnit; +import net.sourceforge.pmd.lang.java.ast.ASTConstructorDeclaration; +import net.sourceforge.pmd.lang.java.ast.ASTEnumConstant; +import net.sourceforge.pmd.lang.java.ast.ASTFieldDeclaration; +import net.sourceforge.pmd.lang.java.ast.ASTFormalParameter; +import net.sourceforge.pmd.lang.java.ast.ASTFormalParameters; +import net.sourceforge.pmd.lang.java.ast.ASTInitializer; +import net.sourceforge.pmd.lang.java.ast.ASTLambdaExpression; +import net.sourceforge.pmd.lang.java.ast.ASTMethodDeclaration; +import net.sourceforge.pmd.lang.java.ast.ASTPackageDeclaration; +import net.sourceforge.pmd.lang.java.ast.AbstractAnyTypeDeclaration; +import net.sourceforge.pmd.lang.java.ast.JavaParserVisitorReducedAdapter; +import net.sourceforge.pmd.lang.java.ast.JavaQualifiableNode; +import net.sourceforge.pmd.lang.java.ast.MethodLikeNode; +import net.sourceforge.pmd.lang.java.qname.ImmutableList.ListFactory; +import net.sourceforge.pmd.lang.java.typeresolution.PMDASMClassLoader; + + +/** + * Populates {@link JavaQualifiableNode} instances with their qualified names. + * + * @author Clément Fournier + * @since 6.1.0 + */ +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 static final Map> FOUND_PACKAGES = new ConcurrentHashMap<>(128); + + // The following stacks stack some counter of the + // visited classes. A new entry is pushed when + // we enter a new class, and popped when we get + // out + + /** + * The top of the stack is the map of local class names + * to the count of local classes with that name declared + * in the current class. + */ + private final Stack> currentLocalIndices = new Stack<>(); + + /** + * The top of the stack is the current count of + * anonymous classes of the currently visited class. + */ + private final Stack anonymousCounters = new Stack<>(); + + private final Stack lambdaCounters = new Stack<>(); + + private final Stack innermostEnclosingTypeName = new Stack<>(); + + /** + * Package list of the current file. + * The qualified names of classes and methods declared + * in this compilation unit share this list, because they're + * declared in the same package. + * + * The head of this list is the name of the + * innermost package. + */ + private ImmutableList packages; + + /** + * Local indices of the currently visited class. + * + * The head of this list is the local index of the + * innermost class. + */ + private ImmutableList localIndices; + + /** + * Class names of the currently visited class. + * For outer classes, this list has one name. + * For a nested class, names are prepended to + * this list from outermost to innermost. + * + * The head of this list is the name of the + * innermost class. + */ + private ImmutableList classNames; + + /** + * The classloader that must be used to load classes for resolving types, + * e.g. for qualified names. + * This is the auxclasspath. + */ + private ClassLoader classLoader; + + /** + * Initialises the visitor and starts it. + * @param classLoader The classloader that will be used by type qualified names + * to load their type. + * @param rootNode The root hierarchy + */ + public void initializeWith(ClassLoader classLoader, ASTCompilationUnit rootNode) { + this.classLoader = PMDASMClassLoader.getInstance(classLoader); + rootNode.jjtAccept(this, null); + } + + + @Override + public Object visit(ASTCompilationUnit node, Object data) { + + // update the package list + packages = getPackageList(node.getFirstChildOfType(ASTPackageDeclaration.class)); + + // reset other variables + localIndices = ListFactory.emptyList(); + classNames = ListFactory.emptyList(); + anonymousCounters.clear(); + currentLocalIndices.clear(); + + return super.visit(node, data); + } + + + /** + * Returns the immutable list representation of this package name. + * The list's tail is shared with all packages that start with + * the same prefix. + * + * @param pack The package declaration, may be null + */ + private ImmutableList getPackageList(ASTPackageDeclaration pack) { + if (pack == null) { + return ListFactory.emptyList(); + } + + final String image = pack.getPackageNameImage(); + ImmutableList fullExisting = FOUND_PACKAGES.get(image); + + if (fullExisting != null) { + return fullExisting; + } + + // else we'll have to look for the longest prefix currently known + // and complete it with remaining packages + + final String[] allPacks = image.split("\\."); + ImmutableList longestPrefix = getLongestPackagePrefix(image, allPacks.length); + StringBuilder prefixImage = new StringBuilder(); + for (String p : longestPrefix) { + prefixImage.append(p); + } + + for (int i = longestPrefix.size(); i < allPacks.length; i++) { + longestPrefix = longestPrefix.prepend(allPacks[i]); + prefixImage.append(allPacks[i]); + FOUND_PACKAGES.put(prefixImage.toString(), longestPrefix); + } + + return longestPrefix; + } + + + /** + * Returns the longest list of package names contained in the cache + * that is a prefix of the given string. This method proceeds recursively, + * trimming one package name at each iteration and checking if the remaining + * prefix is already cached. + * + * @param acc Accumulator, initially the full package name + * @param i Index indicating the remaining number of packages, initially + * the total number of packages in the package name + */ + private ImmutableList getLongestPackagePrefix(String acc, int i) { + ImmutableList prefix = FOUND_PACKAGES.get(acc); + if (prefix != null) { + return prefix; + } + + if (i == 1) { // no prefix known, return early because there's no more '.' in acc + return ListFactory.emptyList(); + } + + return getLongestPackagePrefix(acc.substring(0, acc.lastIndexOf('.')), i - 1); + } + + + @Override + public Object visit(ASTAnyTypeDeclaration node, Object data) { + int localIndex = NOTLOCAL_PLACEHOLDER; + if (node instanceof ASTClassOrInterfaceDeclaration + && ((ASTClassOrInterfaceDeclaration) node).isLocal()) { + + localIndex = getNextIndexFromHistogram(currentLocalIndices.peek(), node.getImage(), 1); + } + + updateClassContext(node.getImage(), localIndex); + + ((AbstractAnyTypeDeclaration) node).setQualifiedName(contextClassQName()); + + super.visit(node, data); + + // go back to previous context + rollbackClassContext(); + + return data; + } + + + @Override + public Object visit(ASTAllocationExpression node, Object data) { + if (!node.isAnonymousClass()) { + return super.visit(node, data); + } + + updateContextForAnonymousClass(); + node.setQualifiedName(contextClassQName()); + + super.visit(node, data); + rollbackClassContext(); + + return data; + } + + + @Override + public Object visit(ASTEnumConstant node, Object data) { + if (!node.isAnonymousClass()) { + return super.visit(node, data); + } + + updateContextForAnonymousClass(); + node.setQualifiedName(contextClassQName()); + + super.visit(node, data); + rollbackClassContext(); + + return data; + } + + + @Override + public Object visit(ASTMethodDeclaration node, Object data) { + String opname = getOperationName(node.getMethodName(), node.getFirstDescendantOfType(ASTFormalParameters.class)); + node.setQualifiedName(contextOperationQName(opname, false)); + return super.visit(node, data); + } + + + @Override + public Object visit(ASTConstructorDeclaration node, Object data) { + String opname = getOperationName(classNames.head(), node.getFirstDescendantOfType(ASTFormalParameters.class)); + node.setQualifiedName(contextOperationQName(opname, false)); + return super.visit(node, data); + } + + // @formatter:off + /** + * Populates the qualified name of a lambda expression. The + * qualified name of a lambda is made up: + *
    + *
  • Of the qualified name of the innermost enclosing + * type (considering anonymous classes too);
  • + *
  • The operation string is composed of the following + * segments, separated with a dollar ({@literal $}) symbol: + *
      + *
    • The {@code lambda} keyword;
    • + *
    • A keyword identifying the scope the lambda + * was declared in. It can be: + *
        + *
      • {@code new}, if the lambda is declared in an + * instance initializer, or a constructor, or in the + * initializer of an instance field of an outer or + * nested class
      • + *
      • {@code static}, if the lambda is declared in a + * static initializer, or in the initializer of a + * static field (including interface constants),
      • + *
      • {@code null}, if the lambda is declared inside + * another lambda,
      • + *
      • The innermost enclosing type's simple name, if the + * lambda is declared in the field initializer of a local + * class,
      • + *
      • The innermost enclosing method's name, if the + * lambda is declared inside a method,
      • + *
      • Nothing (empty string), if the lambda is declared + * in the initializer of the field of an anonymous class;
      • + *
      + *
    • + *
    • A numeric index, unique for each lambda declared + * within the same type declaration.
    • + *
    + *
  • + *
+ * + *

The operation string of a lambda does not contain any formal parameters. + * + *

This specification was worked out from stack traces. The precise order in + * which the numeric index is assigned does not conform to the way javac assigns + * them. Doing that could allow us to retrieve the Method instance associated + * with the lambda. TODO + * + *

See + * this stackoverflow answer for more info about how lambdas are compiled. + * + * @param node Lambda expression node + */ + // @formatter:on + @Override + public Object visit(ASTLambdaExpression node, Object data) { + + String opname = "lambda$" + findLambdaScopeNameSegment(node) + + "$" + lambdaCounters.peek().getAndIncrement(); + + node.setQualifiedName(contextOperationQName(opname, true)); + return super.visit(node, data); + } + + + private void updateContextForAnonymousClass() { + updateClassContext("" + anonymousCounters.peek().incrementAndGet(), NOTLOCAL_PLACEHOLDER); + } + + + /** Pushes a new context for an inner class. */ + private void updateClassContext(String className, int localIndex) { + localIndices = localIndices.prepend(localIndex); + classNames = classNames.prepend(className); + anonymousCounters.push(new MutableInt(0)); + lambdaCounters.push(new MutableInt(0)); + currentLocalIndices.push(new HashMap()); + innermostEnclosingTypeName.push(contextClassQName()); + } + + + /** Rollback the context to the state of the enclosing class. */ + private void rollbackClassContext() { + localIndices = localIndices.tail(); + classNames = classNames.tail(); + anonymousCounters.pop(); + lambdaCounters.pop(); + currentLocalIndices.pop(); + innermostEnclosingTypeName.pop(); + } + + /** Creates a new class qname from the current context (fields). */ + private JavaTypeQualifiedName contextClassQName() { + return new JavaTypeQualifiedName(packages, classNames, localIndices, classLoader); + } + + + /** Creates a new operation qname, using the current context for the class part. */ + private JavaOperationQualifiedName contextOperationQName(String op, boolean isLambda) { + return new JavaOperationQualifiedName(innermostEnclosingTypeName.peek(), op, isLambda); + } + + + private String findLambdaScopeNameSegment(ASTLambdaExpression node) { + Node parent = node.jjtGetParent(); + while (parent != null + && !(parent instanceof ASTFieldDeclaration) + && !(parent instanceof ASTEnumConstant) + && !(parent instanceof ASTInitializer) + && !(parent instanceof MethodLikeNode)) { + parent = parent.jjtGetParent(); + } + + if (parent == null) { + throw new IllegalStateException("The enclosing scope must exist."); + } + + if (parent instanceof ASTInitializer) { + return ((ASTInitializer) parent).isStatic() ? "static" : "new"; + } else if (parent instanceof ASTConstructorDeclaration) { + return "new"; + } else if (parent instanceof ASTLambdaExpression) { + return "null"; + } else if (parent instanceof ASTEnumConstant) { + return "static"; + } else if (parent instanceof ASTFieldDeclaration) { + ASTFieldDeclaration field = (ASTFieldDeclaration) parent; + if (field.isStatic() || field.isInterfaceMember()) { + return "static"; + } + if (innermostEnclosingTypeName.peek().isAnonymousClass()) { + return ""; + } else if (innermostEnclosingTypeName.peek().isLocalClass()) { + return classNames.head(); + } else { // other type + return "new"; + } + } else { // ASTMethodDeclaration + return ((ASTMethodDeclaration) parent).getMethodName(); + } + } + + + /** Returns a normalized method name (not Java-canonical!). */ + private static String getOperationName(String methodName, ASTFormalParameters params) { + + StringBuilder sb = new StringBuilder(); + sb.append(methodName); + sb.append('('); + + boolean first = true; + for (ASTFormalParameter param : params) { + if (!first) { + sb.append(", "); + } + first = false; + + sb.append(param.getTypeNode().getTypeImage()); + if (param.isVarargs()) { + sb.append("..."); + } + } + + sb.append(')'); + + return sb.toString(); + } + + + /** + * Gets the next available index based on a key and a histogram (map of keys to int counters). + * If the key doesn't exist, we add a new entry with the startIndex. + * + *

Used for lambda and anonymous class counters + * + * @param histogram The histogram map + * @param key The key to access + * @param startIndex First index given out when the key doesn't exist + * + * @return The next free index + */ + private static int getNextIndexFromHistogram(Map histogram, T key, int startIndex) { + Integer count = histogram.get(key); + if (count == null) { + histogram.put(key, startIndex); + return startIndex; + } else { + histogram.put(key, count + 1); + return count + 1; + } + } + +} diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/AbstractIgnoredAnnotationRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/AbstractIgnoredAnnotationRule.java new file mode 100644 index 00000000000..37e6691da50 --- /dev/null +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/AbstractIgnoredAnnotationRule.java @@ -0,0 +1,40 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule; + +import java.util.Collection; +import java.util.Collections; + +import net.sourceforge.pmd.lang.java.ast.Annotatable; +import net.sourceforge.pmd.properties.StringMultiProperty; + +public abstract class AbstractIgnoredAnnotationRule extends AbstractJavaRule { + + private final StringMultiProperty ignoredAnnotationsDescriptor + = StringMultiProperty.named("ignoredAnnotations") + .desc("Fully qualified names of the annotation types that should be ignored by this rule") + .defaultValues(defaultSuppressionAnnotations()) + .build(); + + protected Collection defaultSuppressionAnnotations() { + return Collections.emptyList(); + } + + protected AbstractIgnoredAnnotationRule() { + definePropertyDescriptor(ignoredAnnotationsDescriptor); + } + + + /** + * Checks whether any annotation in ignoredAnnotationsDescriptor is present on the node. + * + * @param node + * the node to check + * @return true if the annotation has been found, otherwise false + */ + protected boolean hasIgnoredAnnotation(Annotatable node) { + return node.isAnyAnnotationPresent(getProperty(ignoredAnnotationsDescriptor)); + } +} diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/AbstractJUnitRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/AbstractJUnitRule.java new file mode 100644 index 00000000000..4f729a28303 --- /dev/null +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/AbstractJUnitRule.java @@ -0,0 +1,151 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule; + +import java.util.List; + +import net.sourceforge.pmd.lang.ast.Node; +import net.sourceforge.pmd.lang.java.ast.ASTAnnotation; +import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceDeclaration; +import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceType; +import net.sourceforge.pmd.lang.java.ast.ASTCompilationUnit; +import net.sourceforge.pmd.lang.java.ast.ASTExtendsList; +import net.sourceforge.pmd.lang.java.ast.ASTImportDeclaration; +import net.sourceforge.pmd.lang.java.ast.ASTMethodDeclaration; +import net.sourceforge.pmd.lang.java.ast.ASTName; +import net.sourceforge.pmd.lang.java.ast.TypeNode; +import net.sourceforge.pmd.lang.java.typeresolution.TypeHelper; + +public abstract class AbstractJUnitRule extends AbstractJavaRule { + + protected static final String JUNIT3_CLASS_NAME = "junit.framework.TestCase"; + protected static final String JUNIT4_CLASS_NAME = "org.junit.Test"; + protected static final String JUNIT5_CLASS_NAME = "org.junit.jupiter.api.Test"; + + private boolean isJUnit3Class; + private boolean isJUnit4Class; + private boolean isJUnit5Class; + + @Override + public Object visit(ASTCompilationUnit node, Object data) { + + isJUnit3Class = false; + isJUnit4Class = false; + isJUnit5Class = false; + + isJUnit3Class = isJUnit3Class(node); + isJUnit4Class = isJUnit4Class(node); + isJUnit5Class = isJUnit5Class(node); + + if (isJUnit4Class && isJUnit5Class) { + isJUnit4Class &= hasImports(node, JUNIT4_CLASS_NAME); + isJUnit5Class &= hasImports(node, JUNIT5_CLASS_NAME); + } + + if (!isTestNgClass(node) && (isJUnit3Class || isJUnit4Class || isJUnit5Class)) { + return super.visit(node, data); + } + return data; + } + + private boolean isTestNgClass(ASTCompilationUnit node) { + List imports = node.findDescendantsOfType(ASTImportDeclaration.class); + for (ASTImportDeclaration i : imports) { + if (i.getImportedName() != null && i.getImportedName().startsWith("org.testng")) { + return true; + } + } + return false; + } + + public boolean isJUnitMethod(ASTMethodDeclaration method, Object data) { + if (method.isAbstract() || method.isNative() || method.isStatic()) { + return false; // skip various inapplicable method variations + } + + if (!isJUnit5Class && !method.isPublic()) { + // junit5 class doesn't require test methods to be public anymore + return false; + } + + boolean result = false; + result |= isJUnit3Method(method); + result |= isJUnit4Method(method); + result |= isJUnit5Method(method); + return result; + } + + private boolean isJUnit4Method(ASTMethodDeclaration method) { + return isJUnit4Class && doesNodeContainJUnitAnnotation(method.jjtGetParent(), JUNIT4_CLASS_NAME); + } + + private boolean isJUnit5Method(ASTMethodDeclaration method) { + return isJUnit5Class && doesNodeContainJUnitAnnotation(method.jjtGetParent(), JUNIT5_CLASS_NAME); + } + + private boolean isJUnit3Method(ASTMethodDeclaration method) { + return isJUnit3Class && method.isVoid() && method.getMethodName().startsWith("test"); + } + + private boolean isJUnit3Class(ASTCompilationUnit node) { + ASTClassOrInterfaceDeclaration cid = node.getFirstDescendantOfType(ASTClassOrInterfaceDeclaration.class); + if (cid == null) { + return false; + } + + if (node.getType() != null && TypeHelper.isA(node, JUNIT3_CLASS_NAME)) { + return true; + } else if (node.getType() == null) { + ASTExtendsList extendsList = cid.getFirstChildOfType(ASTExtendsList.class); + if (extendsList == null) { + return false; + } + if (((ASTClassOrInterfaceType) extendsList.jjtGetChild(0)).getImage().endsWith("TestCase")) { + return true; + } + String className = cid.getImage(); + return className.endsWith("Test"); + } else if (hasImports(node, JUNIT3_CLASS_NAME)) { + return cid.getImage().endsWith("Test"); + } + return false; + } + + private boolean isJUnit4Class(ASTCompilationUnit node) { + return doesNodeContainJUnitAnnotation(node, JUNIT4_CLASS_NAME); + } + + private boolean isJUnit5Class(ASTCompilationUnit node) { + return doesNodeContainJUnitAnnotation(node, JUNIT5_CLASS_NAME); + } + + private boolean doesNodeContainJUnitAnnotation(Node node, String annotationTypeClassName) { + List annotations = node.findDescendantsOfType(ASTAnnotation.class); + for (ASTAnnotation annotation : annotations) { + Node annotationTypeNode = annotation.jjtGetChild(0); + TypeNode annotationType = (TypeNode) annotationTypeNode; + if (annotationType.getType() == null) { + ASTName name = annotationTypeNode.getFirstChildOfType(ASTName.class); + if (name != null && (name.hasImageEqualTo("Test") || name.hasImageEqualTo(annotationTypeClassName))) { + return true; + } + } else if (TypeHelper.isA(annotationType, annotationTypeClassName)) { + return true; + } + } + return false; + } + + private boolean hasImports(ASTCompilationUnit cu, String className) { + List imports = cu.findDescendantsOfType(ASTImportDeclaration.class); + for (ASTImportDeclaration importDeclaration : imports) { + ASTName name = importDeclaration.getFirstChildOfType(ASTName.class); + if (name != null && name.hasImageEqualTo(className)) { + return true; + } + } + return false; + } +} diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/AbstractJavaMetricsRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/AbstractJavaMetricsRule.java index 45c84d22410..692a3b5d955 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/AbstractJavaMetricsRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/AbstractJavaMetricsRule.java @@ -9,9 +9,12 @@ import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceDeclaration; import net.sourceforge.pmd.lang.java.ast.ASTConstructorDeclaration; import net.sourceforge.pmd.lang.java.ast.ASTEnumDeclaration; +import net.sourceforge.pmd.lang.java.ast.ASTLambdaExpression; import net.sourceforge.pmd.lang.java.ast.ASTMethodDeclaration; import net.sourceforge.pmd.lang.java.ast.ASTMethodOrConstructorDeclaration; import net.sourceforge.pmd.lang.java.ast.JavaNode; +import net.sourceforge.pmd.lang.java.ast.MethodLikeNode; + /** * Java Rule with convenient visit methods to e.g. treat contructors and methods the same. @@ -54,10 +57,19 @@ public final Object visit(ASTConstructorDeclaration node, Object data) { return visit((ASTMethodOrConstructorDeclaration) node, data); } + @Override + public final Object visit(ASTLambdaExpression node, Object data) { + return visit((MethodLikeNode) node, data); + } + public Object visit(ASTMethodOrConstructorDeclaration node, Object data) { - return visit((JavaNode) node, data); + return visit((MethodLikeNode) node, data); } + public Object visit(MethodLikeNode node, Object data) { + return visit((JavaNode) node, data); + } + } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/AbstractJavaRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/AbstractJavaRule.java index 637ef9fd081..0e17201d7ab 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/AbstractJavaRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/AbstractJavaRule.java @@ -19,17 +19,25 @@ public abstract class AbstractJavaRule extends AbstractRule implements JavaParse public AbstractJavaRule() { super.setLanguage(LanguageRegistry.getLanguage(JavaLanguageModule.NAME)); // Enable Type Resolution on Java Rules by default - super.setUsesTypeResolution(); + super.setTypeResolution(true); } + @Override public void apply(List nodes, RuleContext ctx) { visitAll(nodes, ctx); } protected void visitAll(List nodes, RuleContext ctx) { for (Object element : nodes) { - ASTCompilationUnit node = (ASTCompilationUnit) element; - visit(node, ctx); + /* + It is important to note that we are assuming that all nodes here are of type Compilation Unit, + but our caller method may be called with any type of node, and that's why we need to check the kind + of instance of each element + */ + if (element instanceof ASTCompilationUnit) { + ASTCompilationUnit node = (ASTCompilationUnit) element; + visit(node, ctx); + } } } @@ -71,460 +79,599 @@ protected boolean isSuppressed(Node node) { // Due to Java single inheritance, it preferred to extend from the more // complex Rule base class instead of from relatively simple Visitor. // + @Override public Object visit(JavaNode node, Object data) { node.childrenAccept(this, data); return null; } + @Override public Object visit(ASTExtendsList node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTClassOrInterfaceDeclaration node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTImplementsList node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTTypeParameters node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTMemberSelector node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTTypeParameter node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTTypeBound node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTClassOrInterfaceBody node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTClassOrInterfaceBodyDeclaration node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTEnumBody node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTEnumConstant node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTReferenceType node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTClassOrInterfaceType node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTTypeArguments node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTTypeArgument node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTWildcardBounds node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTAnnotation node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTNormalAnnotation node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTMarkerAnnotation node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTSingleMemberAnnotation node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTMemberValuePairs node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTMemberValuePair node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTMemberValue node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTMemberValueArrayInitializer node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTAnnotationTypeDeclaration node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTAnnotationTypeBody node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTAnnotationTypeMemberDeclaration node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTAnnotationMethodDeclaration node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTDefaultValue node, Object data) { return visit((JavaNode) node, data); } + + /** + * @deprecated Will be removed in 7.0.0. Use {@link ASTShiftExpression#getOperator()} + */ + @Override + @Deprecated public Object visit(ASTRUNSIGNEDSHIFT node, Object data) { return visit((JavaNode) node, data); } + + /** + * @deprecated Will be removed in 7.0.0. Use {@link ASTShiftExpression#getOperator()} + */ + @Override + @Deprecated public Object visit(ASTRSIGNEDSHIFT node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTCompilationUnit node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTEnumDeclaration node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTAssertStatement node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTPackageDeclaration node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTImportDeclaration node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTTypeDeclaration node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTFieldDeclaration node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTVariableDeclarator node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTVariableDeclaratorId node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTVariableInitializer node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTArrayInitializer node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTMethodDeclaration node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTMethodDeclarator node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTFormalParameters node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTFormalParameter node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTConstructorDeclaration node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTExplicitConstructorInvocation node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTInitializer node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTType node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTPrimitiveType node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTResultType node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTName node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTNameList node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTExpression node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTAssignmentOperator node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTConditionalExpression node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTConditionalOrExpression node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTConditionalAndExpression node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTInclusiveOrExpression node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTExclusiveOrExpression node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTAndExpression node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTEqualityExpression node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTInstanceOfExpression node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTRelationalExpression node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTShiftExpression node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTAdditiveExpression node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTMultiplicativeExpression node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTUnaryExpression node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTPreIncrementExpression node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTPreDecrementExpression node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTUnaryExpressionNotPlusMinus node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTPostfixExpression node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTCastExpression node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTPrimaryExpression node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTPrimaryPrefix node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTPrimarySuffix node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTLiteral node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTBooleanLiteral node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTNullLiteral node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTArguments node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTArgumentList node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTAllocationExpression node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTArrayDimsAndInits node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTStatement node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTLabeledStatement node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTBlock node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTBlockStatement node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTLocalVariableDeclaration node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTEmptyStatement node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTStatementExpression node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTSwitchStatement node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTSwitchLabel node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTIfStatement node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTWhileStatement node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTDoStatement node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTForStatement node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTForInit node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTStatementExpressionList node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTForUpdate node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTBreakStatement node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTContinueStatement node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTReturnStatement node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTThrowStatement node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTSynchronizedStatement node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTTryStatement node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTFinallyStatement node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTCatchStatement node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTResourceSpecification node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTResources node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTResource node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTLambdaExpression node, Object data) { return visit((JavaNode) node, data); } + @Override public Object visit(ASTMethodReference node, Object data) { return visit((JavaNode) node, data); } + + @Override + public Object visit(ASTModuleDeclaration node, Object data) { + return visit((JavaNode) node, data); + } + + @Override + public Object visit(ASTModuleDirective node, Object data) { + return visit((JavaNode) node, data); + } + + @Override + public Object visit(ASTModuleName node, Object data) { + return visit((JavaNode) node, data); + } } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/AbstractLombokAwareRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/AbstractLombokAwareRule.java index 178838664a0..710f374aee6 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/AbstractLombokAwareRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/AbstractLombokAwareRule.java @@ -4,6 +4,7 @@ package net.sourceforge.pmd.lang.java.rule; +import java.util.Collection; import java.util.HashSet; import java.util.List; import java.util.Set; @@ -15,6 +16,8 @@ import net.sourceforge.pmd.lang.java.ast.ASTEnumDeclaration; import net.sourceforge.pmd.lang.java.ast.ASTImportDeclaration; import net.sourceforge.pmd.lang.java.ast.ASTName; +import net.sourceforge.pmd.lang.java.ast.Annotatable; + /** * Base class for rules, that should ignore classes/fields that are annotated @@ -22,7 +25,7 @@ * * @author Andreas Dangel */ -public class AbstractLombokAwareRule extends AbstractJavaRule { +public class AbstractLombokAwareRule extends AbstractIgnoredAnnotationRule { private boolean lombokImported = false; private boolean classHasLombokAnnotation = false; @@ -30,13 +33,19 @@ public class AbstractLombokAwareRule extends AbstractJavaRule { private static final Set LOMBOK_ANNOTATIONS = new HashSet<>(); static { - LOMBOK_ANNOTATIONS.add("Data"); - LOMBOK_ANNOTATIONS.add("Getter"); - LOMBOK_ANNOTATIONS.add("Setter"); - LOMBOK_ANNOTATIONS.add("Value"); - LOMBOK_ANNOTATIONS.add("RequiredArgsConstructor"); - LOMBOK_ANNOTATIONS.add("AllArgsConstructor"); - LOMBOK_ANNOTATIONS.add("Builder"); + LOMBOK_ANNOTATIONS.add("lombok.Data"); + LOMBOK_ANNOTATIONS.add("lombok.Getter"); + LOMBOK_ANNOTATIONS.add("lombok.Setter"); + LOMBOK_ANNOTATIONS.add("lombok.Value"); + LOMBOK_ANNOTATIONS.add("lombok.RequiredArgsConstructor"); + LOMBOK_ANNOTATIONS.add("lombok.AllArgsConstructor"); + LOMBOK_ANNOTATIONS.add("lombok.NoArgsConstructor"); + LOMBOK_ANNOTATIONS.add("lombok.Builder"); + } + + @Override + protected Collection defaultSuppressionAnnotations() { + return LOMBOK_ANNOTATIONS; } @Override @@ -79,13 +88,15 @@ protected boolean hasClassLombokAnnotation() { } /** + * @deprecated As of release 6.2.0, replaced by {@link #hasLombokAnnotation(Annotatable)} * Checks whether the given node is annotated with any lombok annotation. * The node can be any node, e.g. class declaration or field declaration. - * + * * @param node * the node to check * @return true if a lombok annotation has been found */ + @Deprecated protected boolean hasLombokAnnotation(Node node) { boolean result = false; Node parent = node.jjtGetParent(); @@ -110,4 +121,16 @@ protected boolean hasLombokAnnotation(Node node) { } return result; } + + /** + * Checks whether the given node is annotated with any lombok annotation. + * The node should be annotateable. + * + * @param node + * the Annotatable node to check + * @return true if a lombok annotation has been found + */ + protected boolean hasLombokAnnotation(Annotatable node) { + return node.isAnyAnnotationPresent(LOMBOK_ANNOTATIONS); + } } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/AbstractStatisticalJavaRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/AbstractStatisticalJavaRule.java index ae43ab56d7b..d4b65dbd1b3 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/AbstractStatisticalJavaRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/AbstractStatisticalJavaRule.java @@ -16,12 +16,14 @@ public abstract class AbstractStatisticalJavaRule extends AbstractJavaRule imple private final StatisticalRuleHelper helper = new StatisticalRuleHelper(this); + @Override public void addDataPoint(DataPoint point) { helper.addDataPoint(point); } + @Override public Object[] getViolationParameters(DataPoint point) { - return null; + return new Object[0]; } @Override diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/JavaRuleChainVisitor.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/JavaRuleChainVisitor.java index cfe6ab43c06..386335289a9 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/JavaRuleChainVisitor.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/JavaRuleChainVisitor.java @@ -18,10 +18,12 @@ public class JavaRuleChainVisitor extends AbstractRuleChainVisitor { + @Override protected void indexNodes(List nodes, RuleContext ctx) { JavaParserVisitor javaParserVistor = new JavaParserVisitorAdapter() { // Perform a visitation of the AST to index nodes which need // visiting by type + @Override public Object visit(JavaNode node, Object data) { indexNode(node); return super.visit(node, data); @@ -33,6 +35,7 @@ public Object visit(JavaNode node, Object data) { } } + @Override protected void visit(Rule rule, Node node, RuleContext ctx) { // Rule better either be a JavaParserVisitor, or a XPathRule if (rule instanceof XPathRule) { diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/JavaRuleViolation.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/JavaRuleViolation.java index be8622ae03f..35b78142098 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/JavaRuleViolation.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/JavaRuleViolation.java @@ -9,13 +9,13 @@ import net.sourceforge.pmd.Rule; import net.sourceforge.pmd.RuleContext; import net.sourceforge.pmd.lang.ast.Node; -import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceDeclaration; import net.sourceforge.pmd.lang.java.ast.ASTCompilationUnit; import net.sourceforge.pmd.lang.java.ast.ASTFieldDeclaration; import net.sourceforge.pmd.lang.java.ast.ASTFormalParameter; import net.sourceforge.pmd.lang.java.ast.ASTLocalVariableDeclaration; import net.sourceforge.pmd.lang.java.ast.ASTVariableDeclarator; import net.sourceforge.pmd.lang.java.ast.ASTVariableDeclaratorId; +import net.sourceforge.pmd.lang.java.ast.AbstractAnyTypeDeclaration; import net.sourceforge.pmd.lang.java.ast.AccessNode; import net.sourceforge.pmd.lang.java.ast.CanSuppressWarnings; import net.sourceforge.pmd.lang.java.ast.JavaNode; @@ -97,7 +97,7 @@ public static boolean isSupressed(Node node, Rule rule) { private void setClassNameFrom(JavaNode node) { String qualifiedName = null; - for (ASTClassOrInterfaceDeclaration parent : node.getParentsOfType(ASTClassOrInterfaceDeclaration.class)) { + for (AbstractAnyTypeDeclaration parent : node.getParentsOfType(AbstractAnyTypeDeclaration.class)) { String clsName = parent.getScope().getEnclosingScope(ClassScope.class).getClassName(); if (qualifiedName == null) { qualifiedName = clsName; diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/StringConcatenationRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/StringConcatenationRule.java index e495d858a53..a0c9f511954 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/StringConcatenationRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/StringConcatenationRule.java @@ -11,6 +11,7 @@ //FUTURE This is not referenced by any RuleSet? public class StringConcatenationRule extends AbstractJavaRule { + @Override public Object visit(ASTForStatement node, Object data) { Node forLoopStmt = null; for (int i = 0; i < 4; i++) { diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/UselessAssignment.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/UselessAssignment.java index 02fbdffc706..61b7a1950b6 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/UselessAssignment.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/UselessAssignment.java @@ -21,6 +21,7 @@ public class UselessAssignment extends AbstractJavaRule implements Executable { private RuleContext rc; + @Override public Object visit(ASTMethodDeclaration node, Object data) { this.rc = (RuleContext) data; @@ -48,11 +49,13 @@ private static class Usage { this.node = node; } + @Override public String toString() { return "accessType = " + accessType + ", line = " + node.getLine(); } } + @Override public void execute(CurrentPath path) { Map hash = new HashMap<>(); // System.out.println("path size is " + path.size()); diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/basic/AvoidUsingOctalValuesRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/basic/AvoidUsingOctalValuesRule.java deleted file mode 100644 index cd1fdf58646..00000000000 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/basic/AvoidUsingOctalValuesRule.java +++ /dev/null @@ -1,37 +0,0 @@ -/** - * BSD-style license; for more info see http://pmd.sourceforge.net/license.html - */ - -package net.sourceforge.pmd.lang.java.rule.basic; - -import java.util.regex.Pattern; - -import net.sourceforge.pmd.lang.java.ast.ASTLiteral; -import net.sourceforge.pmd.lang.java.rule.AbstractJavaRule; -import net.sourceforge.pmd.properties.BooleanProperty; - -public class AvoidUsingOctalValuesRule extends AbstractJavaRule { - - public static final Pattern OCTAL_PATTERN = Pattern.compile("0[0-7]{2,}[lL]?"); - - public static final Pattern STRICT_OCTAL_PATTERN = Pattern.compile("0[0-7]+[lL]?"); - - private static final BooleanProperty STRICT_METHODS_DESCRIPTOR = new BooleanProperty("strict", - "Detect violations between 00 and 07", false, 1.0f); - - public AvoidUsingOctalValuesRule() { - definePropertyDescriptor(STRICT_METHODS_DESCRIPTOR); - } - - public Object visit(ASTLiteral node, Object data) { - boolean strict = getProperty(STRICT_METHODS_DESCRIPTOR); - Pattern p = strict ? STRICT_OCTAL_PATTERN : OCTAL_PATTERN; - - String img = node.getImage(); - if (img != null && p.matcher(img).matches()) { - addViolation(data, node); - } - - return data; - } -} diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/sunsecure/AbstractSunSecureRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/AbstractSunSecureRule.java similarity index 96% rename from pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/sunsecure/AbstractSunSecureRule.java rename to pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/AbstractSunSecureRule.java index c731f56afcc..1e44a3959ff 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/sunsecure/AbstractSunSecureRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/AbstractSunSecureRule.java @@ -2,7 +2,7 @@ * BSD-style license; for more info see http://pmd.sourceforge.net/license.html */ -package net.sourceforge.pmd.lang.java.rule.sunsecure; +package net.sourceforge.pmd.lang.java.rule.bestpractices; import java.util.List; @@ -88,8 +88,7 @@ private boolean hasTernaryNullCheck(ASTReturnStatement ret) { } private boolean hasTernaryCondition(ASTReturnStatement ret) { - ASTConditionalExpression condition = ret.getFirstDescendantOfType(ASTConditionalExpression.class); - return condition != null && condition.isTernary(); + return null != ret.getFirstDescendantOfType(ASTConditionalExpression.class); } /** diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/AccessorClassGenerationRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/AccessorClassGenerationRule.java similarity index 98% rename from pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/AccessorClassGenerationRule.java rename to pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/AccessorClassGenerationRule.java index cad0f0472b4..84d5a59947e 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/AccessorClassGenerationRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/AccessorClassGenerationRule.java @@ -2,7 +2,7 @@ * BSD-style license; for more info see http://pmd.sourceforge.net/license.html */ -package net.sourceforge.pmd.lang.java.rule.design; +package net.sourceforge.pmd.lang.java.rule.bestpractices; import java.util.ArrayList; import java.util.HashMap; diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/AccessorMethodGenerationRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/AccessorMethodGenerationRule.java similarity index 97% rename from pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/AccessorMethodGenerationRule.java rename to pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/AccessorMethodGenerationRule.java index b982a917848..56cad28680a 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/AccessorMethodGenerationRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/AccessorMethodGenerationRule.java @@ -2,7 +2,7 @@ * BSD-style license; for more info see http://pmd.sourceforge.net/license.html */ -package net.sourceforge.pmd.lang.java.rule.design; +package net.sourceforge.pmd.lang.java.rule.bestpractices; import java.util.List; import java.util.Map; @@ -22,23 +22,24 @@ public class AccessorMethodGenerationRule extends AbstractJavaRule { + @Override public Object visit(final ASTCompilationUnit node, final Object data) { final SourceFileScope file = node.getScope().getEnclosingScope(SourceFileScope.class); analyzeScope(file, data); - + return data; // Stop tree navigation } private void analyzeScope(final AbstractJavaScope file, final Object data) { for (final ClassNameDeclaration classDecl : file.getDeclarations(ClassNameDeclaration.class).keySet()) { final ClassScope classScope = (ClassScope) classDecl.getScope(); - + // Check fields for (final Map.Entry> varDecl : classScope.getVariableDeclarations().entrySet()) { final ASTFieldDeclaration field = varDecl.getKey().getNode().getFirstParentOfType(ASTFieldDeclaration.class); analyzeMember(field, varDecl.getValue(), classScope, data); } - + // Check methods for (final Map.Entry> methodDecl : classScope.getMethodDeclarations().entrySet()) { final ASTMethodDeclaration method = methodDecl.getKey().getNode().getFirstParentOfType(ASTMethodDeclaration.class); @@ -49,7 +50,7 @@ private void analyzeScope(final AbstractJavaScope file, final Object data) { analyzeScope(classScope, data); } } - + public void analyzeMember(final AbstractJavaAccessNode node, final List occurrences, final ClassScope classScope, final Object data) { if (!node.isPrivate()) { diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/sunsecure/ArrayIsStoredDirectlyRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/ArrayIsStoredDirectlyRule.java similarity index 91% rename from pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/sunsecure/ArrayIsStoredDirectlyRule.java rename to pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/ArrayIsStoredDirectlyRule.java index 9330a1830ad..cccdf3a6617 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/sunsecure/ArrayIsStoredDirectlyRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/ArrayIsStoredDirectlyRule.java @@ -2,7 +2,7 @@ * BSD-style license; for more info see http://pmd.sourceforge.net/license.html */ -package net.sourceforge.pmd.lang.java.rule.sunsecure; +package net.sourceforge.pmd.lang.java.rule.bestpractices; import java.util.ArrayList; import java.util.List; @@ -44,12 +44,10 @@ public Object visit(ASTClassOrInterfaceDeclaration node, Object data) { @Override public Object visit(ASTConstructorDeclaration node, Object data) { ASTFormalParameter[] arrs = getArrays(node.getParameters()); - if (arrs != null) { - // TODO check if one of these arrays is stored in a non local - // variable - List bs = node.findDescendantsOfType(ASTBlockStatement.class); - checkAll(data, arrs, bs); - } + // TODO check if one of these arrays is stored in a non local + // variable + List bs = node.findDescendantsOfType(ASTBlockStatement.class); + checkAll(data, arrs, bs); return data; } @@ -57,9 +55,7 @@ public Object visit(ASTConstructorDeclaration node, Object data) { public Object visit(ASTMethodDeclaration node, Object data) { final ASTFormalParameters params = node.getFirstDescendantOfType(ASTFormalParameters.class); ASTFormalParameter[] arrs = getArrays(params); - if (arrs != null) { - checkAll(data, arrs, node.findDescendantsOfType(ASTBlockStatement.class)); - } + checkAll(data, arrs, node.findDescendantsOfType(ASTBlockStatement.class)); return data; } @@ -156,13 +152,13 @@ private ASTFormalParameter[] getArrays(ASTFormalParameters params) { if (l != null && !l.isEmpty()) { List l2 = new ArrayList<>(); for (ASTFormalParameter fp : l) { - if (fp.isArray()) { + if (fp.isArray() || fp.isVarargs()) { l2.add(fp); } } - return l2.toArray(new ASTFormalParameter[l2.size()]); + return l2.toArray(new ASTFormalParameter[0]); } - return null; + return new ASTFormalParameter[0]; } } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/AvoidReassigningParametersRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/AvoidReassigningParametersRule.java new file mode 100644 index 00000000000..d355f156929 --- /dev/null +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/AvoidReassigningParametersRule.java @@ -0,0 +1,58 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.bestpractices; + +import java.util.List; +import java.util.Map; + +import net.sourceforge.pmd.lang.java.ast.ASTConstructorDeclaration; +import net.sourceforge.pmd.lang.java.ast.ASTMethodDeclarator; +import net.sourceforge.pmd.lang.java.rule.AbstractJavaRule; +import net.sourceforge.pmd.lang.java.symboltable.JavaNameOccurrence; +import net.sourceforge.pmd.lang.java.symboltable.VariableNameDeclaration; +import net.sourceforge.pmd.lang.symboltable.NameOccurrence; + +public class AvoidReassigningParametersRule extends AbstractJavaRule { + + @Override + public Object visit(ASTMethodDeclarator node, Object data) { + Map> params = node.getScope() + .getDeclarations(VariableNameDeclaration.class); + this.lookForViolation(params, data); + return super.visit(node, data); + } + + private void lookForViolation(Map> params, Object data) { + for (Map.Entry> entry : params.entrySet()) { + VariableNameDeclaration decl = entry.getKey(); + List usages = entry.getValue(); + + // Only look for formal parameters + if (!decl.getDeclaratorId().isFormalParameter()) { + continue; + } + + for (NameOccurrence occ : usages) { + JavaNameOccurrence jocc = (JavaNameOccurrence) occ; + if ((jocc.isOnLeftHandSide() || jocc.isSelfAssignment()) + && jocc.getNameForWhichThisIsAQualifier() == null && !jocc.useThisOrSuper() && !decl.isVarargs() + && (!decl.isArray() + || jocc.getLocation().jjtGetParent().jjtGetParent().jjtGetNumChildren() == 1)) { + // not an array or no primary suffix to access the array + // values + addViolation(data, decl.getNode(), decl.getImage()); + } + } + } + } + + @Override + public Object visit(ASTConstructorDeclaration node, Object data) { + Map> params = node.getScope() + .getDeclarations(VariableNameDeclaration.class); + this.lookForViolation(params, data); + return super.visit(node, data); + } +} diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/basic/AvoidUsingHardCodedIPRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/AvoidUsingHardCodedIPRule.java similarity index 96% rename from pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/basic/AvoidUsingHardCodedIPRule.java rename to pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/AvoidUsingHardCodedIPRule.java index 8cb8291fa5d..6e51e2b5ad8 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/basic/AvoidUsingHardCodedIPRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/AvoidUsingHardCodedIPRule.java @@ -2,7 +2,7 @@ * BSD-style license; for more info see http://pmd.sourceforge.net/license.html */ -package net.sourceforge.pmd.lang.java.rule.basic; +package net.sourceforge.pmd.lang.java.rule.bestpractices; import java.util.Arrays; import java.util.Collections; @@ -11,11 +11,12 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; +import org.apache.commons.lang3.StringUtils; + import net.sourceforge.pmd.lang.java.ast.ASTCompilationUnit; import net.sourceforge.pmd.lang.java.ast.ASTLiteral; import net.sourceforge.pmd.lang.java.rule.AbstractJavaRule; import net.sourceforge.pmd.properties.EnumeratedMultiProperty; -import net.sourceforge.pmd.properties.PropertySource; public class AvoidUsingHardCodedIPRule extends AbstractJavaRule { @@ -135,8 +136,9 @@ protected boolean isIPv6(final char firstChar, String s, final boolean checkIPv6 // Quick check before using Regular Expression // 1) At least 3 characters // 2) 1st must be a Hex number or a : (colon) - // 3) Must contain at least 1 : (colon) - if (s.length() < 3 || !(isHexCharacter(firstChar) || firstChar == ':') || s.indexOf(':') < 0) { + // 3) Must contain at least 2 colons (:) + if (s.length() < 3 || !(isHexCharacter(firstChar) || firstChar == ':') + || StringUtils.countMatches(s, ':') < 2) { return false; } @@ -214,9 +216,7 @@ public boolean hasChosenAddressTypes() { return getProperty(CHECK_ADDRESS_TYPES_DESCRIPTOR).size() > 0; } - /** - * @see PropertySource#dysfunctionReason() - */ + @Override public String dysfunctionReason() { return hasChosenAddressTypes() ? null : "No address types specified"; diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/basic/CheckResultSetRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/CheckResultSetRule.java similarity index 92% rename from pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/basic/CheckResultSetRule.java rename to pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/CheckResultSetRule.java index e9f6b14bc2b..fdc79937e05 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/basic/CheckResultSetRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/CheckResultSetRule.java @@ -2,7 +2,7 @@ * BSD-style license; for more info see http://pmd.sourceforge.net/license.html */ -package net.sourceforge.pmd.lang.java.rule.basic; +package net.sourceforge.pmd.lang.java.rule.bestpractices; import java.util.HashMap; import java.util.HashSet; @@ -48,8 +48,10 @@ public Object visit(ASTMethodDeclaration node, Object data) { @Override public Object visit(ASTLocalVariableDeclaration node, Object data) { - ASTClassOrInterfaceType type = node.getFirstChildOfType(ASTType.class) - .getFirstDescendantOfType(ASTClassOrInterfaceType.class); + ASTClassOrInterfaceType type = null; + if (!node.isTypeInferred()) { + type = node.getFirstChildOfType(ASTType.class).getFirstDescendantOfType(ASTClassOrInterfaceType.class); + } if (type != null && (type.getType() != null && "java.sql.ResultSet".equals(type.getType().getName()) || "ResultSet".equals(type.getImage()))) { ASTVariableDeclarator declarator = node.getFirstChildOfType(ASTVariableDeclarator.class); diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/migrating/ForLoopCanBeForeachRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/ForLoopCanBeForeachRule.java similarity index 87% rename from pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/migrating/ForLoopCanBeForeachRule.java rename to pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/ForLoopCanBeForeachRule.java index 017ca45353e..aa722b2e44c 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/migrating/ForLoopCanBeForeachRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/ForLoopCanBeForeachRule.java @@ -2,12 +2,13 @@ * BSD-style license; for more info see http://pmd.sourceforge.net/license.html */ -package net.sourceforge.pmd.lang.java.rule.migrating; +package net.sourceforge.pmd.lang.java.rule.bestpractices; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Map.Entry; +import java.util.Objects; import org.jaxen.JaxenException; @@ -16,6 +17,7 @@ import net.sourceforge.pmd.lang.java.ast.ASTForInit; import net.sourceforge.pmd.lang.java.ast.ASTForStatement; import net.sourceforge.pmd.lang.java.ast.ASTForUpdate; +import net.sourceforge.pmd.lang.java.ast.ASTLocalVariableDeclaration; import net.sourceforge.pmd.lang.java.ast.ASTName; import net.sourceforge.pmd.lang.java.ast.ASTPrimaryPrefix; import net.sourceforge.pmd.lang.java.ast.ASTPrimarySuffix; @@ -28,6 +30,7 @@ import net.sourceforge.pmd.lang.java.typeresolution.TypeHelper; import net.sourceforge.pmd.lang.symboltable.NameOccurrence; import net.sourceforge.pmd.lang.symboltable.Scope; +import net.sourceforge.pmd.lang.symboltable.ScopedNode; /** * @author Clément Fournier @@ -35,6 +38,10 @@ */ public class ForLoopCanBeForeachRule extends AbstractJavaRule { + public ForLoopCanBeForeachRule() { + addRuleChainVisit(ASTForStatement.class); + } + @Override public Object visit(ASTForStatement node, Object data) { @@ -43,20 +50,20 @@ public Object visit(ASTForStatement node, Object data) { final ASTExpression guardCondition = node.getFirstChildOfType(ASTExpression.class); if (init == null && update == null || guardCondition == null) { - return super.visit(node, data); + return data; } Entry> indexDecl = getIndexVarDeclaration(init, update); if (indexDecl == null) { - return super.visit(node, data); + return data; } List occurrences = indexDecl.getValue(); VariableNameDeclaration index = indexDecl.getKey(); - if (TypeHelper.isA(index, Iterator.class)) { + if (TypeHelper.isExactlyAny(index, Iterator.class)) { Entry> iterableInfo = getIterableDeclOfIteratorLoop(index, node.getScope()); if (iterableInfo != null && isReplaceableIteratorLoop(indexDecl, guardCondition, iterableInfo, node)) { @@ -67,7 +74,7 @@ public Object visit(ASTForStatement node, Object data) { if (occurrences == null || !"int".equals(index.getTypeImage()) || !indexStartsAtZero(index)) { - return super.visit(node, data); + return data; } @@ -76,14 +83,14 @@ public Object visit(ASTForStatement node, Object data) { if (!isForUpdateSimpleEnough(update, itName) || iterableName == null) { - return super.visit(node, data); + return data; } Entry> iterableInfo = findDeclaration(iterableName, node.getScope()); VariableNameDeclaration iterableDeclaration = iterableInfo == null ? null : iterableInfo.getKey(); if (iterableDeclaration == null) { - return super.visit(node, data); + return data; } if (iterableDeclaration.isArray() && isReplaceableArrayLoop(node, occurrences, iterableDeclaration)) { @@ -94,23 +101,33 @@ && isReplaceableListLoop(node, occurrences, iterableDeclaration)) { addViolation(data, node); } - return super.visit(node, data); + return data; } - /* Finds the declaration of the index variable and its occurrences */ + /* Finds the declaration of the index variable and its occurrences, null to abort */ private Entry> getIndexVarDeclaration(ASTForInit init, ASTForUpdate update) { if (init == null) { return guessIndexVarFromUpdate(update); } + ASTLocalVariableDeclaration decl = init.getFirstChildOfType(ASTLocalVariableDeclaration.class); + if (decl == null) { + return null; + } + + int numDeclaredVars = decl.findChildrenOfType(ASTVariableDeclarator.class).size(); + if (numDeclaredVars > 1) { + return null; // will abort in the calling function + } + Map> decls = init.getScope().getDeclarations(VariableNameDeclaration.class); Entry> indexVarAndOccurrences = null; for (Entry> e : decls.entrySet()) { ASTForInit declInit = e.getKey().getNode().getFirstParentOfType(ASTForInit.class); - if (declInit == init) { + if (Objects.equals(declInit, init)) { indexVarAndOccurrences = e; break; } @@ -157,7 +174,7 @@ private String getSimpleForUpdateXpath(String itName) { + "/PrimaryExpression" + "/PrimaryPrefix" + "/Name" - + (itName == null ? "" : ("[@Image='" + itName + "']")); + + (itName == null ? "" : "[@Image='" + itName + "']"); } @@ -240,8 +257,13 @@ private Entry> getIterableDeclOfIt return null; } - String name = initializer.getFirstDescendantOfType(ASTName.class) - .getImage(); + ASTName nameNode = initializer.getFirstDescendantOfType(ASTName.class); + if (nameNode == null) { + // TODO : This can happen if we are calling a local / statically imported method that returns the iterable - currently unhandled + return null; + } + + String name = nameNode.getImage(); int dotIndex = name.indexOf('.'); if (dotIndex > 0) { @@ -318,8 +340,10 @@ private boolean occurenceIsListGet(NameOccurrence occ, String listName) { Node prefix = suffix.jjtGetParent().jjtGetChild(0); - if (!(prefix instanceof ASTPrimaryPrefix) && prefix.jjtGetNumChildren() != 1 - && !(prefix.jjtGetChild(0) instanceof ASTName)) { + if (!(prefix instanceof ASTPrimaryPrefix) || prefix.jjtGetNumChildren() != 1 + || !(prefix.jjtGetChild(0) instanceof ASTName)) { + // it's either not a primary prefix, doesn't have children (can happen with this./super.) + // or first child is not a name return false; } @@ -376,13 +400,14 @@ private boolean isReplaceableIteratorLoop(Entry isTraceEnabled + * debug -> isDebugEnabled + * info -> isInfoEnabled + * warn -> isWarnEnabled + * error -> isErrorEnabled + * + * + * java util: + * log(Level.FINE) -> isLoggable + * finest -> isLoggable + * finer -> isLoggable + * fine -> isLoggable + * info -> isLoggable + * warning -> isLoggable + * severe -> isLoggable + */ + private static final StringMultiProperty LOG_LEVELS = new StringMultiProperty("logLevels", "LogLevels to guard", + new String[] {"trace", "debug", "info", "warn", "error", + "log", "finest", "finer", "fine", "info", "warning", "severe", }, 1.0f, ','); + + private static final StringMultiProperty GUARD_METHODS = new StringMultiProperty("guardsMethods", + "method use to guard the log statement", + new String[] {"isTraceEnabled", "isDebugEnabled", "isInfoEnabled", "isWarnEnabled", "isErrorEnabled", + "isLoggable", }, 2.0f, ','); + + private Map guardStmtByLogLevel = new HashMap<>(12); + + /* + * java util methods, that need special handling, e.g. they require an argument, which + * determines the log level + */ + private static final String JAVA_UTIL_LOG_METHOD = "log"; + private static final String JAVA_UTIL_LOG_GUARD_METHOD = "isLoggable"; + + public GuardLogStatementRule() { + definePropertyDescriptor(LOG_LEVELS); + definePropertyDescriptor(GUARD_METHODS); + } + + @Override + public Object visit(ASTCompilationUnit unit, Object data) { + extractProperties(); + return super.visit(unit, data); + } + + @Override + public Object visit(ASTClassOrInterfaceDeclaration node, Object data) { + if (node.isInterface()) { + return data; // don't consider interfaces + } + return super.visit(node, data); + } + + @Override + public Object visit(ASTStatementExpression node, Object data) { + if (node.jjtGetNumChildren() < 1 || !(node.jjtGetChild(0) instanceof ASTPrimaryExpression)) { + // only consider primary expressions + return node; + } + + ASTPrimaryExpression primary = (ASTPrimaryExpression) node.jjtGetChild(0); + if (primary.jjtGetNumChildren() >= 2 && primary.jjtGetChild(0) instanceof ASTPrimaryPrefix) { + ASTPrimaryPrefix prefix = (ASTPrimaryPrefix) primary.jjtGetChild(0); + String methodCall = getMethodCallName(prefix); + String logLevel = getLogLevelName(primary, methodCall); + + if (guardStmtByLogLevel.containsKey(methodCall) && logLevel != null + && primary.jjtGetChild(1) instanceof ASTPrimarySuffix + && primary.jjtGetChild(1).hasDescendantOfType(ASTAdditiveExpression.class)) { + + if (!hasGuard(primary, methodCall, logLevel)) { + super.addViolation(data, node); + } + } + } + return super.visit(node, data); + } + + private boolean hasGuard(ASTPrimaryExpression node, String methodCall, String logLevel) { + ASTIfStatement ifStatement = node.getFirstParentOfType(ASTIfStatement.class); + if (ifStatement == null) { + return false; + } + + // an if statement always has an expression + ASTExpression expr = ifStatement.getFirstChildOfType(ASTExpression.class); + List guardCalls = expr.findDescendantsOfType(ASTPrimaryPrefix.class); + if (guardCalls.isEmpty()) { + return false; + } + + boolean foundGuard = false; + // check all conditions in the if expression + for (ASTPrimaryPrefix guardCall : guardCalls) { + if (guardCall.jjtGetNumChildren() < 1 + || guardCall.jjtGetChild(0).getImage() == null) { + continue; + } + + String guardMethodCall = getLastPartOfName(guardCall.jjtGetChild(0)); + boolean guardMethodCallMatches = guardStmtByLogLevel.get(methodCall).contains(guardMethodCall); + boolean hasArguments = guardCall.jjtGetParent().hasDescendantOfType(ASTArgumentList.class); + + if (guardMethodCallMatches && !JAVA_UTIL_LOG_GUARD_METHOD.equals(guardMethodCall)) { + // simple case: guard method without the need to check arguments found + foundGuard = true; + } else if (guardMethodCallMatches && hasArguments) { + // java.util.logging: guard method with argument. Verify the log level + String guardArgLogLevel = getLogLevelName(guardCall.jjtGetParent(), guardMethodCall); + foundGuard = logLevel.equals(guardArgLogLevel); + } + + if (foundGuard) { + break; + } + } + + return foundGuard; + } + + /** + * Extracts the method name of the method call. + * @param prefix the method call + * @return the name of the called method + */ + private String getMethodCallName(ASTPrimaryPrefix prefix) { + String result = ""; + if (prefix.jjtGetNumChildren() == 1 && prefix.jjtGetChild(0) instanceof ASTName) { + result = getLastPartOfName(prefix.jjtGetChild(0)); + } + return result; + } + + private String getLastPartOfName(Node name) { + String result = ""; + if (name != null) { + result = name.getImage(); + } + int dotIndex = result.lastIndexOf('.'); + if (dotIndex > -1 && result.length() > dotIndex + 1) { + result = result.substring(dotIndex + 1); + } + return result; + } + + /** + * Gets the first child, first grand child, ... of the given types. + * The children must follow the given order of types + * + * @param root the node from where to start the search + * @param childrenTypes the list of types + * @param should match the last type of childrenType, otherwise you'll get a ClassCastException + * @return the found child node or null + */ + @SafeVarargs + private static N getFirstChild(Node root, Class ... childrenTypes) { + Node current = root; + for (Class clazz : childrenTypes) { + Node child = current.getFirstChildOfType(clazz); + if (child != null) { + current = child; + } else { + return null; + } + } + @SuppressWarnings("unchecked") + N result = (N) current; + return result; + } + + /** + * Determines the log level, that is used. It is either the called method name + * itself or - in case java util logging is used, then it is the first argument of + * the method call (if it exists). + * + * @param node the method call + * @param methodCallName the called method name previously determined + * @return the log level or null if it could not be determined + */ + private String getLogLevelName(Node node, String methodCallName) { + if (!JAVA_UTIL_LOG_METHOD.equals(methodCallName) && !JAVA_UTIL_LOG_GUARD_METHOD.equals(methodCallName)) { + return methodCallName; + } + + String logLevel = null; + ASTPrimarySuffix suffix = node.getFirstDescendantOfType(ASTPrimarySuffix.class); + if (suffix != null) { + ASTArgumentList argumentList = suffix.getFirstDescendantOfType(ASTArgumentList.class); + if (argumentList != null && argumentList.jjtGetNumChildren() > 0) { + // at least one argument - the log level. If the method call is "log", then a message might follow + ASTName name = GuardLogStatementRule.getFirstChild(argumentList.jjtGetChild(0), + ASTPrimaryExpression.class, ASTPrimaryPrefix.class, ASTName.class); + String lastPart = getLastPartOfName(name); + lastPart = lastPart.toLowerCase(Locale.ROOT); + if (!lastPart.isEmpty()) { + logLevel = lastPart; + } + } + } + + return logLevel; + } + + private void extractProperties() { + if (guardStmtByLogLevel.isEmpty()) { + + List logLevels = new ArrayList<>(super.getProperty(LOG_LEVELS)); + List guardMethods = new ArrayList<>(super.getProperty(GUARD_METHODS)); + + if (guardMethods.isEmpty() && !logLevels.isEmpty()) { + throw new IllegalArgumentException("Can't specify logLevels without specifying guardMethods."); + } + if (logLevels.size() > guardMethods.size()) { + // reuse the last guardMethod for the remaining log levels + int needed = logLevels.size() - guardMethods.size(); + String lastGuard = guardMethods.get(guardMethods.size() - 1); + for (int i = 0; i < needed; i++) { + guardMethods.add(lastGuard); + } + } + if (logLevels.size() != guardMethods.size()) { + throw new IllegalArgumentException("For each logLevel a guardMethod must be specified."); + } + + buildGuardStatementMap(logLevels, guardMethods); + } + } + + private void buildGuardStatementMap(List logLevels, List guardMethods) { + for (int i = 0; i < logLevels.size(); i++) { + String logLevel = logLevels.get(i); + if (guardStmtByLogLevel.containsKey(logLevel)) { + String combinedGuard = guardStmtByLogLevel.get(logLevel); + combinedGuard += "|" + guardMethods.get(i); + guardStmtByLogLevel.put(logLevel, combinedGuard); + } else { + guardStmtByLogLevel.put(logLevel, guardMethods.get(i)); + } + } + } +} diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/JUnitAssertionsShouldIncludeMessageRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/JUnitAssertionsShouldIncludeMessageRule.java new file mode 100644 index 00000000000..065f8968bba --- /dev/null +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/JUnitAssertionsShouldIncludeMessageRule.java @@ -0,0 +1,93 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.bestpractices; + +import java.util.ArrayList; +import java.util.List; + +import net.sourceforge.pmd.lang.java.ast.ASTArguments; +import net.sourceforge.pmd.lang.java.ast.ASTExpression; +import net.sourceforge.pmd.lang.java.ast.ASTName; +import net.sourceforge.pmd.lang.java.ast.ASTPrimaryExpression; +import net.sourceforge.pmd.lang.java.ast.ASTPrimaryPrefix; +import net.sourceforge.pmd.lang.java.rule.AbstractJUnitRule; + +public class JUnitAssertionsShouldIncludeMessageRule extends AbstractJUnitRule { + + private class AssertionCall { + private final int argumentsCount; + private final String assertionName; + + AssertionCall(String assertionName, int argumentsCount) { + this.argumentsCount = argumentsCount; + this.assertionName = assertionName; + } + + public void check(Object ctx, ASTArguments node) { + if (node.getArgumentCount() == argumentsCount + && node.getNthParent(2) instanceof ASTPrimaryExpression) { + ASTPrimaryPrefix primaryPrefix = node.getNthParent(2).getFirstChildOfType(ASTPrimaryPrefix.class); + + if (primaryPrefix != null) { + ASTName name = primaryPrefix.getFirstChildOfType(ASTName.class); + + if (name != null && name.hasImageEqualTo(this.assertionName)) { + if (isException(node)) { + return; + } + JUnitAssertionsShouldIncludeMessageRule.this.addViolation(ctx, name); + } + } + } + } + + protected boolean isException(ASTArguments node) { + return false; + } + } + + private List checks = new ArrayList<>(); + + public JUnitAssertionsShouldIncludeMessageRule() { + checks.add(new AssertionCall("assertArrayEquals", 2)); + checks.add(new AssertionCall("assertEquals", 2)); + checks.add(new AssertionCall("assertFalse", 1)); + checks.add(new AssertionCall("assertNotNull", 1)); + checks.add(new AssertionCall("assertNotSame", 2)); + checks.add(new AssertionCall("assertNull", 1)); + checks.add(new AssertionCall("assertSame", 2)); + checks.add(new AssertionCall("assertThat", 2)); + checks.add(new AssertionCall("assertTrue", 1)); + checks.add(new AssertionCall("fail", 0)); + + checks.add(new AssertionCall("assertEquals", 3) { + @Override + protected boolean isException(ASTArguments node) { + List arguments = node.findDescendantsOfType(ASTExpression.class); + boolean isExceptionJunit4 = isStringTypeOrNull(arguments.get(0)); + boolean isExceptionJunit5 = isStringTypeOrNull(arguments.get(2)); + + return isExceptionJunit4 || isExceptionJunit5; + } + }); + } + + @Override + public Object visit(ASTArguments node, Object data) { + for (AssertionCall call : checks) { + call.check(data, node); + } + return super.visit(node, data); + } + + /** + * @param node + * the node to check + * @return {@code true} if node's type is String or null, otherwise {@code false} + */ + private boolean isStringTypeOrNull(ASTExpression node) { + return node.getType() == String.class || node.getType() == null; + } +} diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/junit/JUnitTestsShouldIncludeAssertRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/JUnitTestsShouldIncludeAssertRule.java similarity index 93% rename from pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/junit/JUnitTestsShouldIncludeAssertRule.java rename to pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/JUnitTestsShouldIncludeAssertRule.java index fd9678d0acc..071b76840f7 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/junit/JUnitTestsShouldIncludeAssertRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/JUnitTestsShouldIncludeAssertRule.java @@ -2,7 +2,7 @@ * BSD-style license; for more info see http://pmd.sourceforge.net/license.html */ -package net.sourceforge.pmd.lang.java.rule.junit; +package net.sourceforge.pmd.lang.java.rule.bestpractices; import java.util.HashMap; import java.util.List; @@ -19,6 +19,8 @@ import net.sourceforge.pmd.lang.java.ast.ASTPrimaryExpression; import net.sourceforge.pmd.lang.java.ast.ASTReferenceType; import net.sourceforge.pmd.lang.java.ast.ASTStatementExpression; +import net.sourceforge.pmd.lang.java.rule.AbstractJUnitRule; +import net.sourceforge.pmd.lang.java.typeresolution.TypeHelper; import net.sourceforge.pmd.lang.symboltable.NameDeclaration; import net.sourceforge.pmd.lang.symboltable.NameOccurrence; import net.sourceforge.pmd.lang.symboltable.Scope; @@ -78,8 +80,8 @@ private Map> getRuleAnnotatedExpectedExceptions(Sco Map> result = new HashMap<>(); Map> decls = classScope.getDeclarations(); - for (NameDeclaration decl : decls.keySet()) { - Node parent = decl.getNode().jjtGetParent().jjtGetParent().jjtGetParent(); + for (Map.Entry> entry : decls.entrySet()) { + Node parent = entry.getKey().getNode().jjtGetParent().jjtGetParent().jjtGetParent(); if (parent.hasDescendantOfType(ASTMarkerAnnotation.class) && parent.getFirstChildOfType(ASTFieldDeclaration.class) != null) { String annot = parent.getFirstDescendantOfType(ASTMarkerAnnotation.class).jjtGetChild(0).getImage(); @@ -91,7 +93,7 @@ private Map> getRuleAnnotatedExpectedExceptions(Sco if (!"ExpectedException".equals(type.jjtGetChild(0).getImage())) { continue; } - result.put(decl.getName(), decls.get(decl)); + result.put(entry.getKey().getName(), entry.getValue()); } } return result; @@ -104,8 +106,7 @@ private boolean isExpectAnnotated(Node methodParent) { List annotations = methodParent.findDescendantsOfType(ASTNormalAnnotation.class); for (ASTNormalAnnotation annotation : annotations) { ASTName name = annotation.getFirstChildOfType(ASTName.class); - if (name != null && ("Test".equals(name.getImage()) - || name.getType() != null && name.getType().equals(JUNIT4_CLASS))) { + if (name != null && TypeHelper.isA(name, JUNIT4_CLASS_NAME)) { List memberValues = annotation.findDescendantsOfType(ASTMemberValuePair.class); for (ASTMemberValuePair pair : memberValues) { if ("expected".equals(pair.getImage())) { diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/migrating/JUnitUseExpectedRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/JUnitUseExpectedRule.java similarity index 97% rename from pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/migrating/JUnitUseExpectedRule.java rename to pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/JUnitUseExpectedRule.java index 261f5526071..d41e170a7aa 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/migrating/JUnitUseExpectedRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/JUnitUseExpectedRule.java @@ -2,7 +2,7 @@ * BSD-style license; for more info see http://pmd.sourceforge.net/license.html */ -package net.sourceforge.pmd.lang.java.rule.migrating; +package net.sourceforge.pmd.lang.java.rule.bestpractices; import java.util.ArrayList; import java.util.List; @@ -17,7 +17,7 @@ import net.sourceforge.pmd.lang.java.ast.ASTName; import net.sourceforge.pmd.lang.java.ast.ASTThrowStatement; import net.sourceforge.pmd.lang.java.ast.ASTTryStatement; -import net.sourceforge.pmd.lang.java.rule.junit.AbstractJUnitRule; +import net.sourceforge.pmd.lang.java.rule.AbstractJUnitRule; /** * This rule finds code like this: diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/coupling/LooseCouplingRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/LooseCouplingRule.java similarity index 82% rename from pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/coupling/LooseCouplingRule.java rename to pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/LooseCouplingRule.java index 5000de0f74b..1ef3e21c3a4 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/coupling/LooseCouplingRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/LooseCouplingRule.java @@ -2,7 +2,7 @@ * BSD-style license; for more info see http://pmd.sourceforge.net/license.html */ -package net.sourceforge.pmd.lang.java.rule.coupling; +package net.sourceforge.pmd.lang.java.rule.bestpractices; import net.sourceforge.pmd.lang.ast.Node; import net.sourceforge.pmd.lang.java.ast.ASTAnnotation; @@ -29,15 +29,17 @@ public class LooseCouplingRule extends AbstractJavaRule { // "java.util.TreeMap", "java.util.Vector" // }); + @Override public Object visit(ASTClassOrInterfaceType node, Object data) { if (methodHasOverride(node)) { return data; } Node parent = node.getNthParent(3); - String typeName = node.getImage(); - if (CollectionUtil.isCollectionType(typeName, false) && (parent instanceof ASTFieldDeclaration - || parent instanceof ASTFormalParameter || parent instanceof ASTResultType)) { - addViolation(data, node, typeName); + Class clazzType = node.getType(); + boolean isType = CollectionUtil.isCollectionType(clazzType, false); + if (isType && (parent instanceof ASTFieldDeclaration || parent instanceof ASTFormalParameter + || parent instanceof ASTResultType)) { + addViolation(data, node, node.getImage()); } return data; } @@ -48,7 +50,7 @@ private boolean methodHasOverride(Node node) { ASTMarkerAnnotation marker = method.getFirstDescendantOfType(ASTMarkerAnnotation.class); if (marker != null && marker.getFirstChildOfType(ASTName.class) != null) { ASTName name = marker.getFirstChildOfType(ASTName.class); - if ("Override".equals(name.getImage())) { + if (name.getType() == Override.class) { return true; } } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/sunsecure/MethodReturnsInternalArrayRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/MethodReturnsInternalArrayRule.java similarity index 99% rename from pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/sunsecure/MethodReturnsInternalArrayRule.java rename to pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/MethodReturnsInternalArrayRule.java index e6338ae84b6..71170722eb1 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/sunsecure/MethodReturnsInternalArrayRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/MethodReturnsInternalArrayRule.java @@ -2,7 +2,7 @@ * BSD-style license; for more info see http://pmd.sourceforge.net/license.html */ -package net.sourceforge.pmd.lang.java.rule.sunsecure; +package net.sourceforge.pmd.lang.java.rule.bestpractices; import java.util.List; 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 new file mode 100644 index 00000000000..3b97daa27d9 --- /dev/null +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/MissingOverrideRule.java @@ -0,0 +1,381 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.bestpractices; + +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import java.util.Stack; +import java.util.logging.Logger; + +import net.sourceforge.pmd.lang.java.ast.ASTAllocationExpression; +import net.sourceforge.pmd.lang.java.ast.ASTAnnotation; +import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceDeclaration; +import net.sourceforge.pmd.lang.java.ast.ASTCompilationUnit; +import net.sourceforge.pmd.lang.java.ast.ASTEnumConstant; +import net.sourceforge.pmd.lang.java.ast.ASTEnumDeclaration; +import net.sourceforge.pmd.lang.java.ast.ASTFormalParameter; +import net.sourceforge.pmd.lang.java.ast.ASTFormalParameters; +import net.sourceforge.pmd.lang.java.ast.ASTMethodDeclaration; +import net.sourceforge.pmd.lang.java.rule.AbstractJavaRule; + + +/** + * Flags missing @Override annotations. + * + * @author Clément Fournier + * @since 6.2.0 + */ +public class MissingOverrideRule extends AbstractJavaRule { + + private static final Logger LOG = Logger.getLogger(MissingOverrideRule.class.getName()); + private final Stack currentLookup = new Stack<>(); + + @Override + public Object visit(ASTCompilationUnit node, Object data) { + currentLookup.clear(); + return super.visit(node, data); + } + + + @Override + public Object visit(ASTClassOrInterfaceDeclaration node, Object data) { + currentLookup.push(getMethodLookup(node.getType())); + super.visit(node, data); + currentLookup.pop(); + + return data; + } + + @Override + public Object visit(ASTEnumDeclaration node, Object data) { + currentLookup.push(getMethodLookup(node.getType())); + super.visit(node, data); + currentLookup.pop(); + + return data; + } + + + + @Override + public Object visit(ASTAllocationExpression node, Object data) { + if (node.isAnonymousClass()) { + currentLookup.push(getMethodLookup(node.getType())); + } + super.visit(node, data); + + if (node.isAnonymousClass()) { + currentLookup.pop(); + } + + return data; + } + + + @Override + public Object visit(ASTEnumConstant node, Object data) { + // FIXME, ASTEnumConstant needs typeres support! + // if (node.isAnonymousClass()) { + // currentExploredClass.push(node.getType()); + // } + super.visit(node, data); + + // if (node.isAnonymousClass()) { + // currentExploredClass.pop(); + // } + + return data; + } + + + /** + * Returns a map of method name to methods with the same name (overloads). + * The map contains a MethodWrapper for all methods declared in this class. + * + * @param exploredType Type to explore + */ + private MethodLookup getMethodLookup(Class exploredType) { + if (exploredType == null) { + return null; + } + + 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); + } + + return new MethodLookup(result, overridden); + } catch (final LinkageError e) { + // we may have an incomplete auxclasspath + return null; + } + } + + + /** + * Returns the set of methods declared in this type that are overridden. + * + * @param exploredType The type to explore + */ + private Set overriddenMethods(Class exploredType) { + return overriddenMethodsRec(exploredType, true, new HashSet<>(Arrays.asList(exploredType.getDeclaredMethods())), new HashSet(), new HashSet>(), false); + } + + + private Set overriddenMethodsRec(Class exploredType, boolean skip, Set candidates, Set result, Set> alreadyExplored, boolean onlyPublic) { + + if (candidates.isEmpty() || alreadyExplored.contains(exploredType)) { + return result; + } + + alreadyExplored.add(exploredType); + + if (!skip) { + Set toRemove = new HashSet<>(); + for (Method dm : exploredType.getDeclaredMethods()) { + if (onlyPublic && !Modifier.isPublic(dm.getModifiers()) + || Modifier.isPrivate(dm.getModifiers()) + || Modifier.isStatic(dm.getModifiers())) { + continue; + } + + for (Method cand : candidates) { + if (Modifier.isPrivate(dm.getModifiers()) || Modifier.isStatic(dm.getModifiers())) { + continue; + } + + if (cand.getName().equals(dm.getName()) && Arrays.equals(cand.getParameterTypes(), dm.getParameterTypes())) { + // cand is overriden + result.add(cand); + toRemove.add(cand); + // Several methods are eligible, because of return type covariance + // We could do away with adding only the first one to the result, + // but then the other would stay in the candidates and we'd explore + // the rest of the tree unnecessarily + } + } + candidates.removeAll(toRemove); // no need to look for it elsewhere + } + } + + if (candidates.isEmpty()) { + return result; + } + + Class superClass = exploredType.getSuperclass(); + if (superClass != null) { + overriddenMethodsRec(superClass, false, candidates, result, alreadyExplored, false); + } + + for (Class iface : exploredType.getInterfaces()) { + overriddenMethodsRec(iface, false, candidates, result, alreadyExplored, false); + } + + if (exploredType.isInterface() && exploredType.getInterfaces().length == 0) { + // implicit public object member declarations + if (!alreadyExplored.contains(Object.class)) { + overriddenMethodsRec(Object.class, false, candidates, result, alreadyExplored, true); + } + } + + return result; + } + + + @Override + public Object visit(ASTMethodDeclaration node, Object data) { + if (currentLookup.peek() == null) { + return super.visit(node, data); + } + + for (ASTAnnotation annot : node.getDeclaredAnnotations()) { + if (Override.class.equals(annot.getType())) { + // we assume the compiler has already checked it, so it's correct + return super.visit(node, data); + } + } + + try { + boolean overridden = currentLookup.peek().isOverridden(node.getName(), node.getFormalParameters()); + if (overridden) { + addViolation(data, node, new Object[]{node.getQualifiedName().getOperation()}); + } + } catch (NoSuchMethodException e) { + // may happen in the body of an enum constant, + // because the method lookup used is the one of + // the parent class. + LOG.warning("MissingOverride encountered unexpected method " + node.getMethodName()); + // throw new RuntimeException(e); // uncomment when enum constants are handled by typeres + } + return super.visit(node, data); + } + + + + private static class MethodLookup { + + // method name, parameter count, methods + private final Map>> map; + private final Set overridden; + + + private MethodLookup(Map>> map, Set overridden) { + this.map = map; + this.overridden = overridden; + + for (Map> overloadSet : map.values()) { + for (Entry> sameParamCountMethods : overloadSet.entrySet()) { + // bridges have the same name and param count as the bridged method + resolveBridges(sameParamCountMethods.getValue()); + } + } + } + + + private void resolveBridges(List overloads) { + if (overloads.size() <= 1) { + return; + } + + // partition the overloads + List bridges = new ArrayList<>(); + List notBridges = new ArrayList<>(); + + for (Method m : overloads) { + if (m.isBridge()) { + bridges.add(m); + } else { + notBridges.add(m); + } + } + + if (bridges.isEmpty()) { + return; + } + + // each bridge necessarily calls another non-bridge method, which is overridden + + if (notBridges.size() == bridges.size()) { + // This is a good heuristic, though not perfect. + + // Most of the time, bridges is one-to-one to notBridges, and we can safely assume that + // all non-bridge methods are overridden, since they need a bridge method + + // This chokes on overloads which don't override a previous definition, in which case there's no + // generated bridge for that overload. Short of statically analysing type parameters, or reading + // the bytecode to find the delegation call, we have no way to find out which of the overloads is + // overridden. An example of that is in RuleViolationComparator: there's one bridge + // compare(Object, Object) for the overload compare(RV, RV), but we can't know because there's + // another overload, compare(String, String) which is equally eligible + + // It's also possible that several bridges are generated for the same method, when the method + // was already redefined several times with a different bound. An example of that is in + // PropertyDescriptorConversionWrapper.SingleValue.Packaged and similar subclasses: each of the + // levels of the hierarchy redefine the original method with a tighter bound on the type parameter. + // Three bridges are generated for populate() on concrete builder classes. + + // The two situations could happen together, and if they balance out, that gives a false positive + // with the current test (size are equal). If they don't balance out, then we don't report anything, + // which is why this test seems the safest. + + // Depending on the real-world frequency of those two situations happening together, we may rather + // use notBridges.size() <= bridges.size(), to remove FNs caused by additional bridges. + + overridden.addAll(notBridges); + } + + overloads.removeAll(bridges); // better prune the candidate overloads anyway + } + + + + + + private List getMethods(String name, int paramCount) throws NoSuchMethodException { + Map> overloads = map.get(name); + if (overloads == null) { + throw new NoSuchMethodException(name); + } + + List methods = overloads.get(paramCount); + if (methods == null || methods.isEmpty()) { + throw new NoSuchMethodException(name); + } + return methods; + } + + /** + * Tries to determine if the method with the given name and parameter count is overridden + * + * @return True or false + * + * @throws NoSuchMethodException if no method is registered with this name and paramcount, which is a bug + */ + boolean isOverridden(String name, ASTFormalParameters params) throws NoSuchMethodException { + List methods = getMethods(name, params.getParameterCount()); + + if (methods.size() == 1) { // only one method with this name and parameter count, we can conclude + return overridden.contains(methods.get(0)); + } else { // several overloads with same name and count, cannot be determined without comparing parameters + Class[] paramTypes = getParameterTypes(params); + if (paramTypes == null) { + return false; + } + for (Method m : methods) { + if (Arrays.equals(m.getParameterTypes(), paramTypes)) { + // we found our overload + return overridden.contains(m); + } + } + return false; + } + } + + + private static Class[] getParameterTypes(ASTFormalParameters params) { + Class[] paramTypes = new Class[params.getParameterCount()]; + int i = 0; + for (ASTFormalParameter p : params) { + Class pType = p.getType(); + if (pType == null) { + // fail, couldn't resolve one parameter + return null; + } + + paramTypes[i++] = pType; + } + return paramTypes; + } + + + } + + +} + + + diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/PreserveStackTraceRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/PreserveStackTraceRule.java similarity index 85% rename from pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/PreserveStackTraceRule.java rename to pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/PreserveStackTraceRule.java index 76bbe28f3b9..31293bd7950 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/PreserveStackTraceRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/PreserveStackTraceRule.java @@ -2,13 +2,16 @@ * BSD-style license; for more info see http://pmd.sourceforge.net/license.html */ -package net.sourceforge.pmd.lang.java.rule.design; +package net.sourceforge.pmd.lang.java.rule.bestpractices; +import java.util.ArrayList; import java.util.List; import java.util.Map; +import java.util.Objects; import net.sourceforge.pmd.RuleContext; import net.sourceforge.pmd.lang.ast.Node; +import net.sourceforge.pmd.lang.java.ast.ASTAdditiveExpression; import net.sourceforge.pmd.lang.java.ast.ASTAllocationExpression; import net.sourceforge.pmd.lang.java.ast.ASTArgumentList; import net.sourceforge.pmd.lang.java.ast.ASTCastExpression; @@ -137,16 +140,38 @@ private boolean isInitCauseCalled(String target, List occurrence private boolean checkForTargetUsage(String target, Node baseNode) { boolean match = false; if (target != null && baseNode != null) { - List nameNodes = baseNode.findDescendantsOfType(ASTName.class); + // TODO : use Node.findDescendantsOfType(ASTName.class, true) on 7.0.0 + List nameNodes = new ArrayList<>(); + baseNode.findDescendantsOfType(ASTName.class, nameNodes, true); for (ASTName nameNode : nameNodes) { if (target.equals(nameNode.getImage())) { - match = true; - break; + boolean isPartOfStringConcatenation = isStringConcat(nameNode, baseNode); + if (!isPartOfStringConcatenation) { + match = true; + break; + } } } } return match; } + + /** + * Checks whether the given childNode is part of an additive expression (String concatenation) limiting search to base Node. + * @param childNode + * @param baseNode + * @return + */ + private boolean isStringConcat(Node childNode, Node baseNode) { + Node currentNode = childNode; + while (!Objects.equals(currentNode, baseNode)) { + currentNode = currentNode.jjtGetParent(); + if (currentNode instanceof ASTAdditiveExpression) { + return true; + } + } + return false; + } private void ck(Object data, String target, ASTThrowStatement throwStatement, Node baseNode) { if (!checkForTargetUsage(target, baseNode)) { diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/unusedcode/UnusedFormalParameterRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/UnusedFormalParameterRule.java similarity index 93% rename from pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/unusedcode/UnusedFormalParameterRule.java rename to pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/UnusedFormalParameterRule.java index 0ba95f91d91..df8473acbfb 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/unusedcode/UnusedFormalParameterRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/UnusedFormalParameterRule.java @@ -2,7 +2,7 @@ * BSD-style license; for more info see http://pmd.sourceforge.net/license.html */ -package net.sourceforge.pmd.lang.java.rule.unusedcode; +package net.sourceforge.pmd.lang.java.rule.bestpractices; import java.io.InvalidObjectException; import java.io.ObjectInputStream; @@ -19,6 +19,7 @@ import net.sourceforge.pmd.lang.java.ast.ASTName; import net.sourceforge.pmd.lang.java.ast.ASTNameList; import net.sourceforge.pmd.lang.java.ast.ASTType; +import net.sourceforge.pmd.lang.java.ast.ASTVariableDeclaratorId; import net.sourceforge.pmd.lang.java.ast.JavaNode; import net.sourceforge.pmd.lang.java.rule.AbstractJavaRule; import net.sourceforge.pmd.lang.java.symboltable.JavaNameOccurrence; @@ -35,11 +36,13 @@ public UnusedFormalParameterRule() { definePropertyDescriptor(CHECKALL_DESCRIPTOR); } + @Override public Object visit(ASTConstructorDeclaration node, Object data) { check(node, data); return data; } + @Override public Object visit(ASTMethodDeclaration node, Object data) { if (!node.isPrivate() && !getProperty(CHECKALL_DESCRIPTOR)) { return data; @@ -85,6 +88,12 @@ private void check(Node node, Object data) { .getDeclarations(VariableNameDeclaration.class); for (Map.Entry> entry : vars.entrySet()) { VariableNameDeclaration nameDecl = entry.getKey(); + + ASTVariableDeclaratorId declNode = nameDecl.getDeclaratorId(); + if (!declNode.isFormalParameter() || declNode.isExplicitReceiverParameter()) { + continue; + } + if (actuallyUsed(nameDecl, entry.getValue())) { continue; } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/UnusedImportsRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/UnusedImportsRule.java new file mode 100644 index 00000000000..29516d08624 --- /dev/null +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/UnusedImportsRule.java @@ -0,0 +1,213 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.bestpractices; + +import java.util.HashSet; +import java.util.Iterator; +import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import net.sourceforge.pmd.lang.ast.Node; +import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceType; +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.Comment; +import net.sourceforge.pmd.lang.java.ast.DummyJavaNode; +import net.sourceforge.pmd.lang.java.ast.FormalComment; +import net.sourceforge.pmd.lang.java.ast.TypeNode; +import net.sourceforge.pmd.lang.java.rule.AbstractJavaRule; +import net.sourceforge.pmd.lang.rule.ImportWrapper; + +public class UnusedImportsRule extends AbstractJavaRule { + + protected Set imports = new HashSet<>(); + + /* + * Patterns to match the following constructs: + * + * @see package.class#member(param, param) label {@linkplain + * package.class#member(param, param) label} {@link + * package.class#member(param, param) label} {@link package.class#field} + * {@value package.class#field} + * + * @throws package.class label + */ + private static final Pattern SEE_PATTERN = Pattern + .compile("@see\\s+(\\p{Alpha}\\w*)(?:#\\w*(?:\\(([\\w\\s,]*)\\))?)?"); + + private static final Pattern LINK_PATTERNS = Pattern + .compile("\\{@link(?:plain)?\\s+(\\p{Alpha}\\w*)(?:#\\w*(?:\\(([.\\w\\s,]*)\\))?)?[\\s\\}]"); + + private static final Pattern VALUE_PATTERN = Pattern.compile("\\{@value\\s+(\\p{Alpha}\\w*)[\\s#\\}]"); + + private static final Pattern THROWS_PATTERN = Pattern.compile("@throws\\s+(\\p{Alpha}\\w*)"); + + private static final Pattern[] PATTERNS = { SEE_PATTERN, LINK_PATTERNS, VALUE_PATTERN, THROWS_PATTERN }; + + @Override + public Object visit(ASTCompilationUnit node, Object data) { + imports.clear(); + super.visit(node, data); + visitComments(node); + + /* + * special handling for Bug 2606609 : False "UnusedImports" positive in + * package-info.java package annotations are processed before the import + * clauses so they need to be examined again later on. + */ + if (node.jjtGetNumChildren() > 0 && node.jjtGetChild(0) instanceof ASTPackageDeclaration) { + visit((ASTPackageDeclaration) node.jjtGetChild(0), data); + } + for (ImportWrapper wrapper : imports) { + addViolation(data, wrapper.getNode(), wrapper.getFullName()); + } + return data; + } + + private void visitComments(ASTCompilationUnit node) { + if (imports.isEmpty()) { + return; + } + for (Comment comment : node.getComments()) { + if (!(comment instanceof FormalComment)) { + continue; + } + for (Pattern p : PATTERNS) { + Matcher m = p.matcher(comment.getImage()); + while (m.find()) { + String s = m.group(1); + imports.remove(new ImportWrapper(s, s, new DummyJavaNode(-1))); + + if (m.groupCount() > 1) { + s = m.group(2); + if (s != null) { + String[] params = s.split("\\s*,\\s*"); + for (String param : params) { + final int firstDot = param.indexOf('.'); + final String expectedImportName; + if (firstDot == -1) { + expectedImportName = param; + } else { + expectedImportName = param.substring(0, firstDot); + } + imports.remove(new ImportWrapper(param, expectedImportName, new DummyJavaNode(-1))); + } + } + } + + if (imports.isEmpty()) { + return; + } + } + } + } + } + + @Override + public Object visit(ASTImportDeclaration node, Object data) { + if (node.isImportOnDemand()) { + ASTName importedType = (ASTName) node.jjtGetChild(0); + imports.add(new ImportWrapper(importedType.getImage(), null, node, node.getType(), node.isStatic())); + } else { + if (!node.isImportOnDemand()) { + ASTName importedType = (ASTName) node.jjtGetChild(0); + String className; + if (isQualifiedName(importedType)) { + int lastDot = importedType.getImage().lastIndexOf('.') + 1; + className = importedType.getImage().substring(lastDot); + } else { + className = importedType.getImage(); + } + imports.add(new ImportWrapper(importedType.getImage(), className, node)); + } + } + return data; + } + + @Override + public Object visit(ASTClassOrInterfaceType node, Object data) { + check(node); + return super.visit(node, data); + } + + @Override + public Object visit(ASTName node, Object data) { + check(node); + return data; + } + + protected void check(Node node) { + if (imports.isEmpty()) { + return; + } + ImportWrapper candidate = getImportWrapper(node); + Iterator it = imports.iterator(); + while (it.hasNext()) { + ImportWrapper i = it.next(); + if (i.matches(candidate)) { + it.remove(); + return; + } + } + if (TypeNode.class.isAssignableFrom(node.getClass()) && ((TypeNode) node).getType() != null) { + Class c = ((TypeNode) node).getType(); + if (c.getPackage() != null) { + candidate = new ImportWrapper(c.getPackage().getName(), null); + if (imports.contains(candidate)) { + imports.remove(candidate); + } + } + } + } + + protected ImportWrapper getImportWrapper(Node node) { + String fullName = node.getImage(); + + String name; + if (!isQualifiedName(node)) { + name = node.getImage(); + } else { + // ASTName could be: MyClass.MyConstant + // name -> MyClass + // fullName -> MyClass.MyConstant + name = node.getImage().substring(0, node.getImage().indexOf('.')); + if (isMethodCall(node)) { + // ASTName could be: MyClass.MyConstant.method(a, b) + // name -> MyClass + // fullName -> MyClass.MyConstant + fullName = node.getImage().substring(0, node.getImage().lastIndexOf('.')); + } + } + + return new ImportWrapper(fullName, name); + } + + private boolean isMethodCall(Node node) { + // PrimaryExpression + // PrimaryPrefix + // Name + // PrimarySuffix + + if (node.jjtGetParent() instanceof ASTPrimaryPrefix && node.getNthParent(2) instanceof ASTPrimaryExpression) { + Node primaryPrefix = node.jjtGetParent(); + Node expression = primaryPrefix.jjtGetParent(); + + boolean hasNextSibling = expression.jjtGetNumChildren() > primaryPrefix.jjtGetChildIndex() + 1; + if (hasNextSibling) { + Node nextSibling = expression.jjtGetChild(primaryPrefix.jjtGetChildIndex() + 1); + if (nextSibling instanceof ASTPrimarySuffix) { + return true; + } + } + } + return false; + } +} diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/unusedcode/UnusedLocalVariableRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/UnusedLocalVariableRule.java similarity index 96% rename from pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/unusedcode/UnusedLocalVariableRule.java rename to pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/UnusedLocalVariableRule.java index c8a791f2e2e..d98e5253804 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/unusedcode/UnusedLocalVariableRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/UnusedLocalVariableRule.java @@ -2,7 +2,7 @@ * BSD-style license; for more info see http://pmd.sourceforge.net/license.html */ -package net.sourceforge.pmd.lang.java.rule.unusedcode; +package net.sourceforge.pmd.lang.java.rule.bestpractices; import java.util.List; @@ -19,6 +19,7 @@ public UnusedLocalVariableRule() { addRuleChainVisit(ASTLocalVariableDeclaration.class); } + @Override public Object visit(ASTLocalVariableDeclaration decl, Object data) { for (int i = 0; i < decl.jjtGetNumChildren(); i++) { if (!(decl.jjtGetChild(i) instanceof ASTVariableDeclarator)) { diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/unusedcode/UnusedPrivateFieldRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/UnusedPrivateFieldRule.java similarity index 83% rename from pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/unusedcode/UnusedPrivateFieldRule.java rename to pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/UnusedPrivateFieldRule.java index 6d0a883630d..394acd77a81 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/unusedcode/UnusedPrivateFieldRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/UnusedPrivateFieldRule.java @@ -2,9 +2,10 @@ * BSD-style license; for more info see http://pmd.sourceforge.net/license.html */ -package net.sourceforge.pmd.lang.java.rule.unusedcode; +package net.sourceforge.pmd.lang.java.rule.bestpractices; import java.util.ArrayList; +import java.util.Collection; import java.util.List; import java.util.Map; @@ -17,7 +18,9 @@ import net.sourceforge.pmd.lang.java.ast.ASTName; import net.sourceforge.pmd.lang.java.ast.ASTPrimaryPrefix; import net.sourceforge.pmd.lang.java.ast.ASTPrimarySuffix; +import net.sourceforge.pmd.lang.java.ast.AbstractJavaNode; import net.sourceforge.pmd.lang.java.ast.AccessNode; +import net.sourceforge.pmd.lang.java.ast.Annotatable; import net.sourceforge.pmd.lang.java.ast.JavaNode; import net.sourceforge.pmd.lang.java.rule.AbstractLombokAwareRule; import net.sourceforge.pmd.lang.java.symboltable.JavaNameOccurrence; @@ -27,6 +30,15 @@ public class UnusedPrivateFieldRule extends AbstractLombokAwareRule { + @Override + protected Collection defaultSuppressionAnnotations() { + Collection defaultValues = new ArrayList<>(); + defaultValues.addAll(super.defaultSuppressionAnnotations()); + defaultValues.add("java.lang.Deprecated"); + defaultValues.add("javafx.fxml.FXML"); + return defaultValues; + } + @Override public Object visit(ASTClassOrInterfaceDeclaration node, Object data) { boolean classHasLombok = hasLombokAnnotation(node); @@ -37,7 +49,7 @@ public Object visit(ASTClassOrInterfaceDeclaration node, Object data) { VariableNameDeclaration decl = entry.getKey(); AccessNode accessNodeParent = decl.getAccessNodeParent(); if (!accessNodeParent.isPrivate() || isOK(decl.getImage()) || classHasLombok - || hasLombokAnnotation(accessNodeParent)) { + || hasIgnoredAnnotation((Annotatable) accessNodeParent)) { continue; } if (!actuallyUsed(entry.getValue())) { @@ -79,20 +91,18 @@ private boolean usedInOuter(NameDeclaration decl, JavaNode body) { List classOrInterfaceBodyDeclarations = body .findChildrenOfType(ASTClassOrInterfaceBodyDeclaration.class); List enumConstants = body.findChildrenOfType(ASTEnumConstant.class); - List nodes = new ArrayList<>(); + List nodes = new ArrayList<>(); nodes.addAll(classOrInterfaceBodyDeclarations); nodes.addAll(enumConstants); - for (JavaNode node : nodes) { - List primarySuffixes = node.findDescendantsOfType(ASTPrimarySuffix.class); - for (ASTPrimarySuffix primarySuffix : primarySuffixes) { + for (AbstractJavaNode node : nodes) { + for (ASTPrimarySuffix primarySuffix : node.findDescendantsOfType(ASTPrimarySuffix.class, true)) { if (decl.getImage().equals(primarySuffix.getImage())) { return true; // No violation } } - List primaryPrefixes = node.findDescendantsOfType(ASTPrimaryPrefix.class); - for (ASTPrimaryPrefix primaryPrefix : primaryPrefixes) { + for (ASTPrimaryPrefix primaryPrefix : node.findDescendantsOfType(ASTPrimaryPrefix.class, true)) { ASTName name = primaryPrefix.getFirstDescendantOfType(ASTName.class); if (name != null) { diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/unusedcode/UnusedPrivateMethodRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/UnusedPrivateMethodRule.java similarity index 87% rename from pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/unusedcode/UnusedPrivateMethodRule.java rename to pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/UnusedPrivateMethodRule.java index dab394e9138..8cc74f19aaf 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/unusedcode/UnusedPrivateMethodRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/UnusedPrivateMethodRule.java @@ -2,8 +2,10 @@ * BSD-style license; for more info see http://pmd.sourceforge.net/license.html */ -package net.sourceforge.pmd.lang.java.rule.unusedcode; +package net.sourceforge.pmd.lang.java.rule.bestpractices; +import java.util.Collection; +import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Map; @@ -16,7 +18,8 @@ import net.sourceforge.pmd.lang.java.ast.ASTMethodDeclaration; import net.sourceforge.pmd.lang.java.ast.ASTMethodDeclarator; import net.sourceforge.pmd.lang.java.ast.AccessNode; -import net.sourceforge.pmd.lang.java.rule.AbstractJavaRule; +import net.sourceforge.pmd.lang.java.ast.Annotatable; +import net.sourceforge.pmd.lang.java.rule.AbstractIgnoredAnnotationRule; import net.sourceforge.pmd.lang.java.symboltable.ClassScope; import net.sourceforge.pmd.lang.java.symboltable.MethodNameDeclaration; import net.sourceforge.pmd.lang.symboltable.NameDeclaration; @@ -26,17 +29,23 @@ * This rule detects private methods, that are not used and can therefore be * deleted. */ -public class UnusedPrivateMethodRule extends AbstractJavaRule { +public class UnusedPrivateMethodRule extends AbstractIgnoredAnnotationRule { + + @Override + protected Collection defaultSuppressionAnnotations() { + return Collections.singletonList("java.lang.Deprecated"); + } /** * Visit each method declaration. - * + * * @param node * the method declaration * @param data * data - rule context * @return data */ + @Override public Object visit(ASTClassOrInterfaceDeclaration node, Object data) { if (node.isInterface()) { return data; @@ -46,7 +55,7 @@ public Object visit(ASTClassOrInterfaceDeclaration node, Object data) { .getMethodDeclarations(); for (MethodNameDeclaration mnd : findUnique(methods)) { List occs = methods.get(mnd); - if (!privateAndNotExcluded(mnd)) { + if (!privateAndNotExcluded(mnd) || hasIgnoredAnnotation((Annotatable) mnd.getNode().jjtGetParent())) { continue; } if (occs.isEmpty()) { diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/UseCollectionIsEmptyRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/UseCollectionIsEmptyRule.java similarity index 97% rename from pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/UseCollectionIsEmptyRule.java rename to pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/UseCollectionIsEmptyRule.java index 9c906b8288f..923bc2c730a 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/UseCollectionIsEmptyRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/bestpractices/UseCollectionIsEmptyRule.java @@ -2,7 +2,7 @@ * BSD-style license; for more info see http://pmd.sourceforge.net/license.html */ -package net.sourceforge.pmd.lang.java.rule.design; +package net.sourceforge.pmd.lang.java.rule.bestpractices; import java.util.Arrays; import java.util.HashMap; @@ -26,22 +26,24 @@ /** * Detect structures like "foo.size() == 0" and suggest replacing them with * foo.isEmpty(). Will also find != 0 (replaceable with !isEmpty()). - * + * * @author Jason Bennett */ public class UseCollectionIsEmptyRule extends AbstractInefficientZeroCheck { + @Override public boolean appliesToClassName(String name) { return CollectionUtil.isCollectionType(name, true); } /** * Determine if we're dealing with .size method - * + * * @param occ * The name occurrence * @return true if it's .size, else false */ + @Override public boolean isTargetMethod(JavaNameOccurrence occ) { if (occ.getNameForWhichThisIsAQualifier() != null) { if (occ.getLocation().getImage().endsWith(".size")) { diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codesize/AbstractNcssCountRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codesize/AbstractNcssCountRule.java deleted file mode 100644 index b00e6cbb915..00000000000 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codesize/AbstractNcssCountRule.java +++ /dev/null @@ -1,199 +0,0 @@ -/** - * BSD-style license; for more info see http://pmd.sourceforge.net/license.html - */ - -package net.sourceforge.pmd.lang.java.rule.codesize; - -import net.sourceforge.pmd.lang.ast.Node; -import net.sourceforge.pmd.lang.java.ast.ASTBreakStatement; -import net.sourceforge.pmd.lang.java.ast.ASTCatchStatement; -import net.sourceforge.pmd.lang.java.ast.ASTContinueStatement; -import net.sourceforge.pmd.lang.java.ast.ASTDoStatement; -import net.sourceforge.pmd.lang.java.ast.ASTFinallyStatement; -import net.sourceforge.pmd.lang.java.ast.ASTForInit; -import net.sourceforge.pmd.lang.java.ast.ASTForStatement; -import net.sourceforge.pmd.lang.java.ast.ASTIfStatement; -import net.sourceforge.pmd.lang.java.ast.ASTLabeledStatement; -import net.sourceforge.pmd.lang.java.ast.ASTLocalVariableDeclaration; -import net.sourceforge.pmd.lang.java.ast.ASTReturnStatement; -import net.sourceforge.pmd.lang.java.ast.ASTStatementExpression; -import net.sourceforge.pmd.lang.java.ast.ASTStatementExpressionList; -import net.sourceforge.pmd.lang.java.ast.ASTSwitchLabel; -import net.sourceforge.pmd.lang.java.ast.ASTSwitchStatement; -import net.sourceforge.pmd.lang.java.ast.ASTSynchronizedStatement; -import net.sourceforge.pmd.lang.java.ast.ASTThrowStatement; -import net.sourceforge.pmd.lang.java.ast.ASTWhileStatement; -import net.sourceforge.pmd.lang.java.ast.JavaNode; -import net.sourceforge.pmd.lang.java.rule.AbstractStatisticalJavaRule; -import net.sourceforge.pmd.stat.DataPoint; -import net.sourceforge.pmd.util.NumericConstants; - -/** - * Abstract superclass for NCSS counting methods. Counts tokens according to - * JavaNCSS rules. - * - * @author Jason Bennett - */ -public abstract class AbstractNcssCountRule extends AbstractStatisticalJavaRule { - - private Class nodeClass; - - /** - * Count the nodes of the given type using NCSS rules. - * - * @param nodeClass - * class of node to count - */ - protected AbstractNcssCountRule(Class nodeClass) { - this.nodeClass = nodeClass; - } - - @Override - public Object visit(JavaNode node, Object data) { - int numNodes = 0; - - for (int i = 0; i < node.jjtGetNumChildren(); i++) { - JavaNode n = (JavaNode) node.jjtGetChild(i); - Integer treeSize = (Integer) n.jjtAccept(this, data); - numNodes += treeSize.intValue(); - } - - if (this.nodeClass.isInstance(node)) { - // Add 1 to account for base node - numNodes++; - DataPoint point = new DataPoint(); - point.setNode(node); - point.setScore(1.0 * numNodes); - point.setMessage(getMessage()); - addDataPoint(point); - } - - return Integer.valueOf(numNodes); - } - - /** - * Count the number of children of the given Java node. Adds one to count - * the node itself. - * - * @param node - * java node having children counted - * @param data - * node data - * @return count of the number of children of the node, plus one - */ - protected Integer countNodeChildren(Node node, Object data) { - Integer nodeCount = null; - int lineCount = 0; - for (int i = 0; i < node.jjtGetNumChildren(); i++) { - nodeCount = (Integer) ((JavaNode) node.jjtGetChild(i)).jjtAccept(this, data); - lineCount += nodeCount.intValue(); - } - return ++lineCount; - } - - @Override - public Object visit(ASTForStatement node, Object data) { - return countNodeChildren(node, data); - } - - @Override - public Object visit(ASTDoStatement node, Object data) { - return countNodeChildren(node, data); - } - - @Override - public Object visit(ASTIfStatement node, Object data) { - - Integer lineCount = countNodeChildren(node, data); - - if (node.hasElse()) { - lineCount++; - } - - return lineCount; - } - - @Override - public Object visit(ASTWhileStatement node, Object data) { - return countNodeChildren(node, data); - } - - @Override - public Object visit(ASTBreakStatement node, Object data) { - return NumericConstants.ONE; - } - - @Override - public Object visit(ASTCatchStatement node, Object data) { - return countNodeChildren(node, data); - } - - @Override - public Object visit(ASTContinueStatement node, Object data) { - return NumericConstants.ONE; - } - - @Override - public Object visit(ASTFinallyStatement node, Object data) { - return countNodeChildren(node, data); - } - - @Override - public Object visit(ASTReturnStatement node, Object data) { - return countNodeChildren(node, data); - } - - @Override - public Object visit(ASTSwitchStatement node, Object data) { - return countNodeChildren(node, data); - } - - @Override - public Object visit(ASTSynchronizedStatement node, Object data) { - return countNodeChildren(node, data); - } - - @Override - public Object visit(ASTThrowStatement node, Object data) { - return NumericConstants.ONE; - } - - @Override - public Object visit(ASTStatementExpression node, Object data) { - - // "For" update expressions do not count as separate lines of code - if (node.jjtGetParent() instanceof ASTStatementExpressionList) { - return NumericConstants.ZERO; - } - - return NumericConstants.ONE; - } - - @Override - public Object visit(ASTLabeledStatement node, Object data) { - return countNodeChildren(node, data); - } - - @Override - public Object visit(ASTLocalVariableDeclaration node, Object data) { - - // "For" init declarations do not count as separate lines of code - if (node.jjtGetParent() instanceof ASTForInit) { - return NumericConstants.ZERO; - } - - /* - * This will count variables declared on the same line as separate NCSS - * counts. This violates JavaNCSS standards, but I'm not convinced - * that's a bad thing here. - */ - - return countNodeChildren(node, data); - } - - @Override - public Object visit(ASTSwitchLabel node, Object data) { - return countNodeChildren(node, data); - } - -} diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codesize/CyclomaticComplexityRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codesize/CyclomaticComplexityRule.java deleted file mode 100644 index 63d521b93a5..00000000000 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codesize/CyclomaticComplexityRule.java +++ /dev/null @@ -1,81 +0,0 @@ -/** - * BSD-style license; for more info see http://pmd.sourceforge.net/license.html - */ - -package net.sourceforge.pmd.lang.java.rule.codesize; - -import net.sourceforge.pmd.lang.java.ast.ASTConditionalExpression; -import net.sourceforge.pmd.lang.java.ast.ASTDoStatement; -import net.sourceforge.pmd.lang.java.ast.ASTExpression; -import net.sourceforge.pmd.lang.java.ast.ASTForStatement; -import net.sourceforge.pmd.lang.java.ast.ASTIfStatement; -import net.sourceforge.pmd.lang.java.ast.ASTSwitchStatement; -import net.sourceforge.pmd.lang.java.ast.ASTWhileStatement; - -/** - * @author Donald A. Leckie, - * - * @version $Revision: 5956 $, $Date: 2008-04-04 04:59:25 -0500 (Fri, 04 Apr - * 2008) $ - * @since January 14, 2003 - */ -public class CyclomaticComplexityRule extends StdCyclomaticComplexityRule { - - @Override - public Object visit(ASTIfStatement node, Object data) { - super.visit(node, data); - - int boolCompIf = NPathComplexityRule.sumExpressionComplexity(node.getFirstChildOfType(ASTExpression.class)); - entryStack.peek().bumpDecisionPoints(boolCompIf); - return data; - } - - @Override - public Object visit(ASTForStatement node, Object data) { - super.visit(node, data); - - int boolCompFor = NPathComplexityRule - .sumExpressionComplexity(node.getFirstDescendantOfType(ASTExpression.class)); - entryStack.peek().bumpDecisionPoints(boolCompFor); - return data; - } - - @Override - public Object visit(ASTDoStatement node, Object data) { - super.visit(node, data); - - int boolCompDo = NPathComplexityRule.sumExpressionComplexity(node.getFirstChildOfType(ASTExpression.class)); - entryStack.peek().bumpDecisionPoints(boolCompDo); - return data; - } - - @Override - public Object visit(ASTSwitchStatement node, Object data) { - super.visit(node, data); - - int boolCompSwitch = NPathComplexityRule.sumExpressionComplexity(node.getFirstChildOfType(ASTExpression.class)); - entryStack.peek().bumpDecisionPoints(boolCompSwitch); - return data; - } - - @Override - public Object visit(ASTWhileStatement node, Object data) { - super.visit(node, data); - - int boolCompWhile = NPathComplexityRule.sumExpressionComplexity(node.getFirstChildOfType(ASTExpression.class)); - entryStack.peek().bumpDecisionPoints(boolCompWhile); - return data; - } - - @Override - public Object visit(ASTConditionalExpression node, Object data) { - super.visit(node, data); - - if (node.isTernary()) { - int boolCompTern = NPathComplexityRule - .sumExpressionComplexity(node.getFirstChildOfType(ASTExpression.class)); - entryStack.peek().bumpDecisionPoints(boolCompTern); - } - return data; - } -} diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codesize/ExcessiveClassLengthRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codesize/ExcessiveClassLengthRule.java deleted file mode 100644 index d5861074a15..00000000000 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codesize/ExcessiveClassLengthRule.java +++ /dev/null @@ -1,19 +0,0 @@ -/** - * BSD-style license; for more info see http://pmd.sourceforge.net/license.html - */ - -package net.sourceforge.pmd.lang.java.rule.codesize; - -import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceDeclaration; -import net.sourceforge.pmd.lang.java.rule.design.ExcessiveLengthRule; - -/** - * This rule detects when a class exceeds a certain threshold. i.e. if a class - * has more than 1000 lines of code. - */ -public class ExcessiveClassLengthRule extends ExcessiveLengthRule { - public ExcessiveClassLengthRule() { - super(ASTClassOrInterfaceDeclaration.class); - setProperty(MINIMUM_DESCRIPTOR, 1000d); - } -} diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codesize/ExcessiveMethodLengthRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codesize/ExcessiveMethodLengthRule.java deleted file mode 100644 index fbfad1d8c34..00000000000 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codesize/ExcessiveMethodLengthRule.java +++ /dev/null @@ -1,19 +0,0 @@ -/** - * BSD-style license; for more info see http://pmd.sourceforge.net/license.html - */ - -package net.sourceforge.pmd.lang.java.rule.codesize; - -import net.sourceforge.pmd.lang.java.ast.ASTMethodDeclaration; -import net.sourceforge.pmd.lang.java.rule.design.ExcessiveLengthRule; - -/** - * This rule detects when a method exceeds a certain threshold. i.e. if a method - * has more than x lines of code. - */ -public class ExcessiveMethodLengthRule extends ExcessiveLengthRule { - public ExcessiveMethodLengthRule() { - super(ASTMethodDeclaration.class); - setProperty(MINIMUM_DESCRIPTOR, 100d); - } -} diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codesize/ExcessiveParameterListRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codesize/ExcessiveParameterListRule.java deleted file mode 100644 index acdbda3cf66..00000000000 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codesize/ExcessiveParameterListRule.java +++ /dev/null @@ -1,27 +0,0 @@ -/** - * BSD-style license; for more info see http://pmd.sourceforge.net/license.html - */ - -package net.sourceforge.pmd.lang.java.rule.codesize; - -import net.sourceforge.pmd.lang.java.ast.ASTFormalParameter; -import net.sourceforge.pmd.lang.java.ast.ASTFormalParameters; -import net.sourceforge.pmd.lang.java.rule.design.ExcessiveNodeCountRule; -import net.sourceforge.pmd.util.NumericConstants; - -/** - * This rule detects an abnormally long parameter list. Note: This counts Nodes, - * and not necessarily parameters, so the numbers may not match up. (But - * topcount and sigma should work.) - */ -public class ExcessiveParameterListRule extends ExcessiveNodeCountRule { - public ExcessiveParameterListRule() { - super(ASTFormalParameters.class); - setProperty(MINIMUM_DESCRIPTOR, 10d); - } - - // Count these nodes, but no others. - public Object visit(ASTFormalParameter node, Object data) { - return NumericConstants.ONE; - } -} diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codesize/ExcessivePublicCountRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codesize/ExcessivePublicCountRule.java deleted file mode 100644 index 7c09337d4e1..00000000000 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codesize/ExcessivePublicCountRule.java +++ /dev/null @@ -1,67 +0,0 @@ -/** - * BSD-style license; for more info see http://pmd.sourceforge.net/license.html - */ - -package net.sourceforge.pmd.lang.java.rule.codesize; - -import net.sourceforge.pmd.lang.java.ast.ASTCompilationUnit; -import net.sourceforge.pmd.lang.java.ast.ASTFieldDeclaration; -import net.sourceforge.pmd.lang.java.ast.ASTMethodDeclarator; -import net.sourceforge.pmd.lang.java.ast.AccessNode; -import net.sourceforge.pmd.lang.java.rule.design.ExcessiveNodeCountRule; -import net.sourceforge.pmd.util.NumericConstants; - -/** - * Rule attempts to count all public methods and public attributes - * defined in a class. - * - *

If a class has a high number of public operations, it might be wise - * to consider whether it would be appropriate to divide it into - * subclasses.

- * - *

A large proportion of public members and operations means the class - * has high potential to be affected by external classes. Futhermore, - * increased effort will be required to thoroughly test the class. - *

- * - * @author aglover - */ -public class ExcessivePublicCountRule extends ExcessiveNodeCountRule { - - public ExcessivePublicCountRule() { - super(ASTCompilationUnit.class); - setProperty(MINIMUM_DESCRIPTOR, 45d); - } - - /** - * Method counts ONLY public methods. - */ - public Object visit(ASTMethodDeclarator node, Object data) { - return this.getTallyOnAccessType((AccessNode) node.jjtGetParent()); - } - - /** - * Method counts ONLY public class attributes which are not PUBLIC and - * static- these usually represent constants.... - */ - public Object visit(ASTFieldDeclaration node, Object data) { - if (node.isFinal() && node.isStatic()) { - return NumericConstants.ZERO; - } - return this.getTallyOnAccessType(node); - } - - /** - * Method counts a node if it is public - * - * @param node - * The access node. - * @return Integer 1 if node is public 0 otherwise - */ - private Integer getTallyOnAccessType(AccessNode node) { - if (node.isPublic()) { - return NumericConstants.ONE; - } - return NumericConstants.ZERO; - } -} diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codesize/NPathComplexityRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codesize/NPathComplexityRule.java deleted file mode 100644 index e18210cf749..00000000000 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codesize/NPathComplexityRule.java +++ /dev/null @@ -1,265 +0,0 @@ -/** - * BSD-style license; for more info see http://pmd.sourceforge.net/license.html - */ - -package net.sourceforge.pmd.lang.java.rule.codesize; - -import java.util.ArrayList; -import java.util.List; - -import net.sourceforge.pmd.lang.java.ast.ASTConditionalAndExpression; -import net.sourceforge.pmd.lang.java.ast.ASTConditionalExpression; -import net.sourceforge.pmd.lang.java.ast.ASTConditionalOrExpression; -import net.sourceforge.pmd.lang.java.ast.ASTDoStatement; -import net.sourceforge.pmd.lang.java.ast.ASTExpression; -import net.sourceforge.pmd.lang.java.ast.ASTForStatement; -import net.sourceforge.pmd.lang.java.ast.ASTIfStatement; -import net.sourceforge.pmd.lang.java.ast.ASTMethodDeclaration; -import net.sourceforge.pmd.lang.java.ast.ASTReturnStatement; -import net.sourceforge.pmd.lang.java.ast.ASTStatement; -import net.sourceforge.pmd.lang.java.ast.ASTSwitchLabel; -import net.sourceforge.pmd.lang.java.ast.ASTSwitchStatement; -import net.sourceforge.pmd.lang.java.ast.ASTTryStatement; -import net.sourceforge.pmd.lang.java.ast.ASTWhileStatement; -import net.sourceforge.pmd.lang.java.ast.JavaNode; -import net.sourceforge.pmd.lang.java.rule.AbstractStatisticalJavaRule; -import net.sourceforge.pmd.stat.DataPoint; -import net.sourceforge.pmd.util.NumericConstants; - -/** - * NPath complexity is a measurement of the acyclic execution paths through a - * function. See Nejmeh, Communications of the ACM Feb 1988 pp 188-200. - * - * @author Jason Bennett - */ -public class NPathComplexityRule extends AbstractStatisticalJavaRule { - - public NPathComplexityRule() { - super(); - setProperty(MINIMUM_DESCRIPTOR, 200d); - } - - private int complexityMultipleOf(JavaNode node, int npathStart, Object data) { - - int npath = npathStart; - JavaNode n; - - for (int i = 0; i < node.jjtGetNumChildren(); i++) { - n = (JavaNode) node.jjtGetChild(i); - npath *= (Integer) n.jjtAccept(this, data); - } - - return npath; - } - - private int complexitySumOf(JavaNode node, int npathStart, Object data) { - - int npath = npathStart; - JavaNode n; - - for (int i = 0; i < node.jjtGetNumChildren(); i++) { - n = (JavaNode) node.jjtGetChild(i); - npath += (Integer) n.jjtAccept(this, data); - } - - return npath; - } - - @Override - public Object visit(ASTMethodDeclaration node, Object data) { - int npath = complexityMultipleOf(node, 1, data); - - DataPoint point = new DataPoint(); - point.setNode(node); - point.setScore(1.0 * npath); - point.setMessage(getMessage()); - addDataPoint(point); - - return Integer.valueOf(npath); - } - - @Override - public Object visit(JavaNode node, Object data) { - int npath = complexityMultipleOf(node, 1, data); - return Integer.valueOf(npath); - } - - @Override - public Object visit(ASTIfStatement node, Object data) { - // (npath of if + npath of else (or 1) + bool_comp of if) * npath of - // next - - List statementChildren = new ArrayList<>(); - for (int i = 0; i < node.jjtGetNumChildren(); i++) { - if (node.jjtGetChild(i).getClass() == ASTStatement.class) { - statementChildren.add((JavaNode) node.jjtGetChild(i)); - } - } - - if (statementChildren.isEmpty() || statementChildren.size() == 1 && node.hasElse() - || statementChildren.size() != 1 && !node.hasElse()) { - throw new IllegalStateException("If node has wrong number of children"); - } - - // add path for not taking if - int complexity = 0; - if (!node.hasElse()) { - complexity++; - } - - for (JavaNode element : statementChildren) { - complexity += (Integer) element.jjtAccept(this, data); - } - - int boolCompIf = sumExpressionComplexity(node.getFirstChildOfType(ASTExpression.class)); - return Integer.valueOf(boolCompIf + complexity); - } - - @Override - public Object visit(ASTWhileStatement node, Object data) { - // (npath of while + bool_comp of while + 1) * npath of next - - int boolCompWhile = sumExpressionComplexity(node.getFirstChildOfType(ASTExpression.class)); - - Integer nPathWhile = (Integer) ((JavaNode) node.getFirstChildOfType(ASTStatement.class)).jjtAccept(this, data); - - return Integer.valueOf(boolCompWhile + nPathWhile + 1); - } - - @Override - public Object visit(ASTDoStatement node, Object data) { - // (npath of do + bool_comp of do + 1) * npath of next - - int boolCompDo = sumExpressionComplexity(node.getFirstChildOfType(ASTExpression.class)); - - Integer nPathDo = (Integer) ((JavaNode) node.getFirstChildOfType(ASTStatement.class)).jjtAccept(this, data); - - return Integer.valueOf(boolCompDo + nPathDo + 1); - } - - @Override - public Object visit(ASTForStatement node, Object data) { - // (npath of for + bool_comp of for + 1) * npath of next - - int boolCompFor = sumExpressionComplexity(node.getFirstDescendantOfType(ASTExpression.class)); - - Integer nPathFor = (Integer) ((JavaNode) node.getFirstChildOfType(ASTStatement.class)).jjtAccept(this, data); - - return Integer.valueOf(boolCompFor + nPathFor + 1); - } - - @Override - public Object visit(ASTReturnStatement node, Object data) { - // return statements are valued at 1, or the value of the boolean - // expression - - ASTExpression expr = node.getFirstChildOfType(ASTExpression.class); - - if (expr == null) { - return NumericConstants.ONE; - } - - int boolCompReturn = sumExpressionComplexity(expr); - int conditionalExpressionComplexity = complexityMultipleOf(expr, 1, data); - - if (conditionalExpressionComplexity > 1) { - boolCompReturn += conditionalExpressionComplexity; - } - - if (boolCompReturn > 0) { - return Integer.valueOf(boolCompReturn); - } - return NumericConstants.ONE; - } - - @Override - public Object visit(ASTSwitchStatement node, Object data) { - // bool_comp of switch + sum(npath(case_range)) - - int boolCompSwitch = sumExpressionComplexity(node.getFirstChildOfType(ASTExpression.class)); - - int npath = 0; - int caseRange = 0; - for (int i = 0; i < node.jjtGetNumChildren(); i++) { - JavaNode n = (JavaNode) node.jjtGetChild(i); - - // Fall-through labels count as 1 for complexity - if (n instanceof ASTSwitchLabel) { - npath += caseRange; - caseRange = 1; - } else { - Integer complexity = (Integer) n.jjtAccept(this, data); - caseRange *= complexity; - } - } - // add in npath of last label - npath += caseRange; - return Integer.valueOf(boolCompSwitch + npath); - } - - @Override - public Object visit(ASTTryStatement node, Object data) { - /* - * This scenario was not addressed by the original paper. Based on the - * principles outlined in the paper, as well as the Checkstyle NPath - * implementation, this code will add the complexity of the try to the - * complexities of the catch and finally blocks. - */ - int npath = complexitySumOf(node, 0, data); - - return Integer.valueOf(npath); - - } - - @Override - public Object visit(ASTConditionalExpression node, Object data) { - if (node.isTernary()) { - int npath = complexitySumOf(node, 0, data); - - npath += 2; - return Integer.valueOf(npath); - } - return NumericConstants.ONE; - } - - /** - * Calculate the boolean complexity of the given expression. NPath boolean - * complexity is the sum of && and || tokens. This is calculated by summing - * the number of children of the &&'s (minus one) and the children of the - * ||'s (minus one). - * - *

Note that this calculation applies to Cyclomatic Complexity as well.

- * - * @param expr - * control structure expression - * @return complexity of the boolean expression - */ - public static int sumExpressionComplexity(ASTExpression expr) { - if (expr == null) { - return 0; - } - - List andNodes = expr.findDescendantsOfType(ASTConditionalAndExpression.class); - List orNodes = expr.findDescendantsOfType(ASTConditionalOrExpression.class); - - int children = 0; - - for (ASTConditionalOrExpression element : orNodes) { - children += element.jjtGetNumChildren(); - children--; - } - - for (ASTConditionalAndExpression element : andNodes) { - children += element.jjtGetNumChildren(); - children--; - } - - return children; - } - - @Override - public Object[] getViolationParameters(DataPoint point) { - return new String[] { ((ASTMethodDeclaration) point.getNode()).getMethodName(), - String.valueOf((int) point.getScore()), }; - } -} diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codesize/NcssConstructorCountRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codesize/NcssConstructorCountRule.java deleted file mode 100644 index c6ddbf7a3e1..00000000000 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codesize/NcssConstructorCountRule.java +++ /dev/null @@ -1,37 +0,0 @@ -/** - * BSD-style license; for more info see http://pmd.sourceforge.net/license.html - */ - -package net.sourceforge.pmd.lang.java.rule.codesize; - -import net.sourceforge.pmd.lang.java.ast.ASTConstructorDeclaration; -import net.sourceforge.pmd.lang.java.ast.ASTExplicitConstructorInvocation; -import net.sourceforge.pmd.stat.DataPoint; -import net.sourceforge.pmd.util.NumericConstants; - -/** - * Non-commented source statement counter for constructors. - * - * @author Jason Bennett - */ -public class NcssConstructorCountRule extends AbstractNcssCountRule { - - /** - * Count constructor declarations. This includes any explicit super() calls. - */ - public NcssConstructorCountRule() { - super(ASTConstructorDeclaration.class); - setProperty(MINIMUM_DESCRIPTOR, 100d); - } - - public Object visit(ASTExplicitConstructorInvocation node, Object data) { - return NumericConstants.ONE; - } - - @Override - public Object[] getViolationParameters(DataPoint point) { - // TODO need to put class name or constructor ID in string - return new String[] { String.valueOf(((ASTConstructorDeclaration) point.getNode()).getParameterCount()), - String.valueOf((int) point.getScore()), }; - } -} diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codesize/NcssMethodCountRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codesize/NcssMethodCountRule.java deleted file mode 100644 index 38a55733484..00000000000 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codesize/NcssMethodCountRule.java +++ /dev/null @@ -1,35 +0,0 @@ -/** - * BSD-style license; for more info see http://pmd.sourceforge.net/license.html - */ - -package net.sourceforge.pmd.lang.java.rule.codesize; - -import net.sourceforge.pmd.lang.java.ast.ASTMethodDeclaration; -import net.sourceforge.pmd.stat.DataPoint; - -/** - * Non-commented source statement counter for methods. - * - * @author Jason Bennett - */ -public class NcssMethodCountRule extends AbstractNcssCountRule { - - /** - * Count the size of all non-constructor methods. - */ - public NcssMethodCountRule() { - super(ASTMethodDeclaration.class); - setProperty(MINIMUM_DESCRIPTOR, 100d); - } - - @Override - public Object visit(ASTMethodDeclaration node, Object data) { - return super.visit(node, data); - } - - @Override - public Object[] getViolationParameters(DataPoint point) { - return new String[] { ((ASTMethodDeclaration) point.getNode()).getMethodName(), - String.valueOf((int) point.getScore()), }; - } -} diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codesize/NcssTypeCountRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codesize/NcssTypeCountRule.java deleted file mode 100644 index b2a26eab06f..00000000000 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codesize/NcssTypeCountRule.java +++ /dev/null @@ -1,87 +0,0 @@ -/** - * BSD-style license; for more info see http://pmd.sourceforge.net/license.html - */ - -package net.sourceforge.pmd.lang.java.rule.codesize; - -import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceDeclaration; -import net.sourceforge.pmd.lang.java.ast.ASTConstructorDeclaration; -import net.sourceforge.pmd.lang.java.ast.ASTEnumDeclaration; -import net.sourceforge.pmd.lang.java.ast.ASTExplicitConstructorInvocation; -import net.sourceforge.pmd.lang.java.ast.ASTFieldDeclaration; -import net.sourceforge.pmd.lang.java.ast.ASTInitializer; -import net.sourceforge.pmd.lang.java.ast.ASTMethodDeclaration; -import net.sourceforge.pmd.lang.java.ast.ASTTypeDeclaration; -import net.sourceforge.pmd.stat.DataPoint; -import net.sourceforge.pmd.util.NumericConstants; - -/** - * Non-commented source statement counter for type declarations. - * - * @author Jason Bennett - */ -public class NcssTypeCountRule extends AbstractNcssCountRule { - - /** - * Count type declarations. This includes classes as well as enums and - * annotations. - */ - public NcssTypeCountRule() { - super(ASTTypeDeclaration.class); - setProperty(MINIMUM_DESCRIPTOR, 1500d); - } - - @Override - public Object visit(ASTClassOrInterfaceDeclaration node, Object data) { - - if (!node.isNested()) { - return super.visit(node, data); - } - - return countNodeChildren(node, data); - } - - @Override - public Object visit(ASTConstructorDeclaration node, Object data) { - return countNodeChildren(node, data); - } - - @Override - public Object visit(ASTExplicitConstructorInvocation node, Object data) { - return NumericConstants.ONE; - } - - @Override - public Object visit(ASTEnumDeclaration node, Object data) { - /* - * If the enum is a type in and of itself, don't count its declaration - * twice. - */ - if (node.jjtGetParent() instanceof ASTTypeDeclaration) { - Integer nodeCount = countNodeChildren(node, data); - int count = nodeCount.intValue() - 1; - return Integer.valueOf(count); - } - return countNodeChildren(node, data); - } - - @Override - public Object visit(ASTMethodDeclaration node, Object data) { - return countNodeChildren(node, data); - } - - @Override - public Object visit(ASTInitializer node, Object data) { - return countNodeChildren(node, data); - } - - @Override - public Object visit(ASTFieldDeclaration node, Object data) { - return NumericConstants.ONE; - } - - @Override - public Object[] getViolationParameters(DataPoint point) { - return new String[] { String.valueOf((int) point.getScore()) }; - } -} diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codesize/StdCyclomaticComplexityRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codesize/StdCyclomaticComplexityRule.java deleted file mode 100644 index dde262300db..00000000000 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codesize/StdCyclomaticComplexityRule.java +++ /dev/null @@ -1,246 +0,0 @@ -/** - * BSD-style license; for more info see http://pmd.sourceforge.net/license.html - */ - -package net.sourceforge.pmd.lang.java.rule.codesize; - -import java.util.ArrayDeque; -import java.util.Deque; - -import net.sourceforge.pmd.lang.ast.Node; -import net.sourceforge.pmd.lang.java.ast.ASTBlockStatement; -import net.sourceforge.pmd.lang.java.ast.ASTCatchStatement; -import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceDeclaration; -import net.sourceforge.pmd.lang.java.ast.ASTCompilationUnit; -import net.sourceforge.pmd.lang.java.ast.ASTConditionalExpression; -import net.sourceforge.pmd.lang.java.ast.ASTConstructorDeclaration; -import net.sourceforge.pmd.lang.java.ast.ASTDoStatement; -import net.sourceforge.pmd.lang.java.ast.ASTEnumDeclaration; -import net.sourceforge.pmd.lang.java.ast.ASTForStatement; -import net.sourceforge.pmd.lang.java.ast.ASTIfStatement; -import net.sourceforge.pmd.lang.java.ast.ASTMethodDeclaration; -import net.sourceforge.pmd.lang.java.ast.ASTMethodDeclarator; -import net.sourceforge.pmd.lang.java.ast.ASTSwitchLabel; -import net.sourceforge.pmd.lang.java.ast.ASTSwitchStatement; -import net.sourceforge.pmd.lang.java.ast.ASTWhileStatement; -import net.sourceforge.pmd.lang.java.rule.AbstractJavaRule; -import net.sourceforge.pmd.properties.BooleanProperty; -import net.sourceforge.pmd.properties.IntegerProperty; - -/** - * Implements the standard cyclomatic complexity rule - *

- * Standard rules: +1 for each decision point, including case statements but not - * including boolean operators unlike CyclomaticComplexityRule. - * - * @author Alan Hohn, based on work by Donald A. Leckie - * - * @since June 18, 2014 - */ -public class StdCyclomaticComplexityRule extends AbstractJavaRule { - - public static final IntegerProperty REPORT_LEVEL_DESCRIPTOR = new IntegerProperty("reportLevel", - "Cyclomatic Complexity reporting threshold", 1, 30, 10, 1.0f); - - public static final BooleanProperty SHOW_CLASSES_COMPLEXITY_DESCRIPTOR = new BooleanProperty( - "showClassesComplexity", "Add class average violations to the report", true, 2.0f); - - public static final BooleanProperty SHOW_METHODS_COMPLEXITY_DESCRIPTOR = new BooleanProperty( - "showMethodsComplexity", "Add method average violations to the report", true, 3.0f); - - private int reportLevel; - private boolean showClassesComplexity = true; - private boolean showMethodsComplexity = true; - - protected static class Entry { - private Node node; - private int decisionPoints = 1; - public int highestDecisionPoints; - public int methodCount; - - private Entry(Node node) { - this.node = node; - } - - public void bumpDecisionPoints() { - decisionPoints++; - } - - public void bumpDecisionPoints(int size) { - decisionPoints += size; - } - - public int getComplexityAverage() { - return (double) methodCount == 0 ? 1 : (int) Math.rint((double) decisionPoints / (double) methodCount); - } - } - - protected Deque entryStack = new ArrayDeque<>(); - - public StdCyclomaticComplexityRule() { - definePropertyDescriptor(REPORT_LEVEL_DESCRIPTOR); - definePropertyDescriptor(SHOW_CLASSES_COMPLEXITY_DESCRIPTOR); - definePropertyDescriptor(SHOW_METHODS_COMPLEXITY_DESCRIPTOR); - } - - @Override - public Object visit(ASTCompilationUnit node, Object data) { - reportLevel = getProperty(REPORT_LEVEL_DESCRIPTOR); - showClassesComplexity = getProperty(SHOW_CLASSES_COMPLEXITY_DESCRIPTOR); - showMethodsComplexity = getProperty(SHOW_METHODS_COMPLEXITY_DESCRIPTOR); - super.visit(node, data); - return data; - } - - @Override - public Object visit(ASTIfStatement node, Object data) { - entryStack.peek().bumpDecisionPoints(); - super.visit(node, data); - return data; - } - - @Override - public Object visit(ASTCatchStatement node, Object data) { - entryStack.peek().bumpDecisionPoints(); - super.visit(node, data); - return data; - } - - @Override - public Object visit(ASTForStatement node, Object data) { - entryStack.peek().bumpDecisionPoints(); - super.visit(node, data); - return data; - } - - @Override - public Object visit(ASTDoStatement node, Object data) { - entryStack.peek().bumpDecisionPoints(); - super.visit(node, data); - return data; - } - - @Override - public Object visit(ASTSwitchStatement node, Object data) { - Entry entry = entryStack.peek(); - - int childCount = node.jjtGetNumChildren(); - int lastIndex = childCount - 1; - for (int n = 0; n < lastIndex; n++) { - Node childNode = node.jjtGetChild(n); - if (childNode instanceof ASTSwitchLabel) { - // default is generally not considered a decision (same as - // "else") - ASTSwitchLabel sl = (ASTSwitchLabel) childNode; - if (!sl.isDefault()) { - childNode = node.jjtGetChild(n + 1); - if (childNode instanceof ASTBlockStatement) { - entry.bumpDecisionPoints(); - } - } - } - } - super.visit(node, data); - return data; - } - - @Override - public Object visit(ASTWhileStatement node, Object data) { - entryStack.peek().bumpDecisionPoints(); - super.visit(node, data); - return data; - } - - @Override - public Object visit(ASTConditionalExpression node, Object data) { - if (node.isTernary()) { - entryStack.peek().bumpDecisionPoints(); - super.visit(node, data); - } - return data; - } - - @Override - public Object visit(ASTClassOrInterfaceDeclaration node, Object data) { - if (node.isInterface()) { - return data; - } - - entryStack.push(new Entry(node)); - super.visit(node, data); - Entry classEntry = entryStack.pop(); - if (showClassesComplexity) { - if (classEntry.getComplexityAverage() >= reportLevel || classEntry.highestDecisionPoints >= reportLevel) { - addViolation(data, node, new String[] { "class", node.getImage(), - classEntry.getComplexityAverage() + " (Highest = " + classEntry.highestDecisionPoints + ')', }); - } - } - return data; - } - - @Override - public Object visit(ASTMethodDeclaration node, Object data) { - entryStack.push(new Entry(node)); - super.visit(node, data); - Entry methodEntry = entryStack.pop(); - if (!isSuppressed(node)) { - int methodDecisionPoints = methodEntry.decisionPoints; - Entry classEntry = entryStack.peek(); - classEntry.methodCount++; - classEntry.bumpDecisionPoints(methodDecisionPoints); - - if (methodDecisionPoints > classEntry.highestDecisionPoints) { - classEntry.highestDecisionPoints = methodDecisionPoints; - } - - ASTMethodDeclarator methodDeclarator = null; - for (int n = 0; n < node.jjtGetNumChildren(); n++) { - Node childNode = node.jjtGetChild(n); - if (childNode instanceof ASTMethodDeclarator) { - methodDeclarator = (ASTMethodDeclarator) childNode; - break; - } - } - - if (showMethodsComplexity && methodEntry.decisionPoints >= reportLevel) { - addViolation(data, node, - new String[] { "method", methodDeclarator == null ? "" : methodDeclarator.getImage(), - String.valueOf(methodEntry.decisionPoints), }); - } - } - return data; - } - - @Override - public Object visit(ASTEnumDeclaration node, Object data) { - entryStack.push(new Entry(node)); - super.visit(node, data); - Entry classEntry = entryStack.pop(); - if (classEntry.getComplexityAverage() >= reportLevel || classEntry.highestDecisionPoints >= reportLevel) { - addViolation(data, node, new String[] { "class", node.getImage(), - classEntry.getComplexityAverage() + "(Highest = " + classEntry.highestDecisionPoints + ')', }); - } - return data; - } - - @Override - public Object visit(ASTConstructorDeclaration node, Object data) { - entryStack.push(new Entry(node)); - super.visit(node, data); - Entry constructorEntry = entryStack.pop(); - if (!isSuppressed(node)) { - int constructorDecisionPointCount = constructorEntry.decisionPoints; - Entry classEntry = entryStack.peek(); - classEntry.methodCount++; - classEntry.decisionPoints += constructorDecisionPointCount; - if (constructorDecisionPointCount > classEntry.highestDecisionPoints) { - classEntry.highestDecisionPoints = constructorDecisionPointCount; - } - if (showMethodsComplexity && constructorEntry.decisionPoints >= reportLevel) { - addViolation(data, node, new String[] { "constructor", classEntry.node.getImage(), - String.valueOf(constructorDecisionPointCount), }); - } - } - return data; - } -} diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codesize/TooManyFieldsRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codesize/TooManyFieldsRule.java deleted file mode 100644 index e730de0072f..00000000000 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codesize/TooManyFieldsRule.java +++ /dev/null @@ -1,71 +0,0 @@ -/** - * BSD-style license; for more info see http://pmd.sourceforge.net/license.html - */ - -package net.sourceforge.pmd.lang.java.rule.codesize; - -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import net.sourceforge.pmd.lang.ast.Node; -import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceDeclaration; -import net.sourceforge.pmd.lang.java.ast.ASTCompilationUnit; -import net.sourceforge.pmd.lang.java.ast.ASTFieldDeclaration; -import net.sourceforge.pmd.lang.java.rule.AbstractJavaRule; -import net.sourceforge.pmd.properties.IntegerProperty; -import net.sourceforge.pmd.util.NumericConstants; - -public class TooManyFieldsRule extends AbstractJavaRule { - - private static final int DEFAULT_MAXFIELDS = 15; - - private Map stats; - private Map nodes; - - private static final IntegerProperty MAX_FIELDS_DESCRIPTOR = new IntegerProperty("maxfields", - "Max allowable fields", 1, 300, DEFAULT_MAXFIELDS, 1.0f); - - public TooManyFieldsRule() { - definePropertyDescriptor(MAX_FIELDS_DESCRIPTOR); - } - - @Override - public Object visit(ASTCompilationUnit node, Object data) { - - int maxFields = getProperty(MAX_FIELDS_DESCRIPTOR); - - stats = new HashMap<>(5); - nodes = new HashMap<>(5); - - List l = node.findDescendantsOfType(ASTFieldDeclaration.class); - - for (ASTFieldDeclaration fd : l) { - if (fd.isFinal() && fd.isStatic()) { - continue; - } - ASTClassOrInterfaceDeclaration clazz = fd.getFirstParentOfType(ASTClassOrInterfaceDeclaration.class); - if (clazz != null && !clazz.isInterface()) { - bumpCounterFor(clazz); - } - } - for (String k : stats.keySet()) { - int val = stats.get(k); - Node n = nodes.get(k); - if (val > maxFields) { - addViolation(data, n); - } - } - return data; - } - - private void bumpCounterFor(ASTClassOrInterfaceDeclaration clazz) { - String key = clazz.getImage(); - if (!stats.containsKey(key)) { - stats.put(key, NumericConstants.ZERO); - nodes.put(key, clazz); - } - Integer i = Integer.valueOf(stats.get(key) + 1); - stats.put(key, i); - } -} 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 new file mode 100644 index 00000000000..e53f3a9c233 --- /dev/null +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codestyle/AbstractNamingConventionRule.java @@ -0,0 +1,68 @@ +/** + * 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.JavaNode; +import net.sourceforge.pmd.lang.java.rule.AbstractJavaRule; +import net.sourceforge.pmd.properties.PropertyDescriptor; +import net.sourceforge.pmd.properties.RegexProperty; +import net.sourceforge.pmd.properties.RegexProperty.RegexPBuilder; +import net.sourceforge.pmd.util.StringUtil; + + +/** + * Base class for naming conventions rule. Not public API, but + * used to uniformize eg property names between our rules. + * + *

Protected methods may leak API because concrete classes + * are not final so they're package private instead + * + * @author Clément Fournier + * @since 6.5.0 + */ +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]*"; + + /** The argument is interpreted as the display name, and is converted to camel case to get the property name. */ + RegexPBuilder defaultProp(String displayName) { + return defaultProp(StringUtil.toCamelCase(displayName, true), displayName); + } + + /** Returns a pre-filled builder with the given name and display name (for the description). */ + RegexPBuilder defaultProp(String name, String displayName) { + return RegexProperty.named(name + "Pattern") + .desc("Regex which applies to " + displayName.trim() + " names") + .defaultValue(defaultConvention()); + } + + /** Default regex string for this kind of entities. */ + abstract String defaultConvention(); + + + /** Generic "kind" of node, eg "static method" or "utility class". */ + abstract String kindDisplayName(T node, PropertyDescriptor descriptor); + + /** Extracts the name that should be pattern matched. */ + String nameExtractor(T node) { + return node.getImage(); + } + + + void checkMatches(T node, PropertyDescriptor regex, Object data) { + String name = nameExtractor(node); + if (!getProperty(regex).matcher(name).matches()) { + addViolation(data, node, new Object[]{ + kindDisplayName(node, regex), + name, + getProperty(regex).toString(), + }); + } + } + +} diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codestyle/AtLeastOneConstructorRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codestyle/AtLeastOneConstructorRule.java new file mode 100644 index 00000000000..2983c4eaad0 --- /dev/null +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codestyle/AtLeastOneConstructorRule.java @@ -0,0 +1,74 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.codestyle; + +import java.util.Arrays; +import java.util.Collection; + +import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceDeclaration; +import net.sourceforge.pmd.lang.java.ast.ASTConstructorDeclaration; +import net.sourceforge.pmd.lang.java.ast.ASTFieldDeclaration; +import net.sourceforge.pmd.lang.java.ast.ASTMethodDeclaration; +import net.sourceforge.pmd.lang.java.rule.AbstractIgnoredAnnotationRule; +import net.sourceforge.pmd.lang.java.rule.design.UseUtilityClassRule; + +/** + * This rule detects non-static classes with no constructors; + * requiring even the default constructor to be explicit. + * It ignores classes with solely static methods, + * use {@link UseUtilityClassRule} to flag those. + */ +public class AtLeastOneConstructorRule extends AbstractIgnoredAnnotationRule { + + public AtLeastOneConstructorRule() { + addRuleChainVisit(ASTClassOrInterfaceDeclaration.class); + } + + @Override + protected Collection defaultSuppressionAnnotations() { + return Arrays.asList("lombok.Data", + "lombok.Value", + "lombok.Builder", + "lombok.NoArgsConstructor", + "lombok.RequiredArgsConstructor", + "lombok.AllArgsConstructorAtLeastOneConstructor"); + } + + @Override + public Object visit(final ASTClassOrInterfaceDeclaration node, final Object data) { + // Ignore interfaces / static classes / classes that have a constructor / classes ignored through annotations + if (node.isInterface() || node.isStatic() || node.hasDescendantOfType(ASTConstructorDeclaration.class) + || hasIgnoredAnnotation(node)) { + return data; + } + + boolean atLeastOneMember = false; + + // Do we have any non-static methods? + for (final ASTMethodDeclaration m : node.findDescendantsOfType(ASTMethodDeclaration.class)) { + if (!m.isStatic()) { + addViolation(data, node); + return data; + } + atLeastOneMember = true; + } + + // .. or fields? + for (final ASTFieldDeclaration f : node.findDescendantsOfType(ASTFieldDeclaration.class)) { + if (!f.isStatic()) { + addViolation(data, node); + return data; + } + atLeastOneMember = true; + } + + // Class has no declared members + if (!atLeastOneMember) { + addViolation(data, node); + } + + return data; + } +} diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/naming/AvoidDollarSignsRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codestyle/AvoidDollarSignsRule.java similarity index 92% rename from pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/naming/AvoidDollarSignsRule.java rename to pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codestyle/AvoidDollarSignsRule.java index b53b907b32a..84aeb53a1fb 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/naming/AvoidDollarSignsRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codestyle/AvoidDollarSignsRule.java @@ -2,7 +2,7 @@ * BSD-style license; for more info see http://pmd.sourceforge.net/license.html */ -package net.sourceforge.pmd.lang.java.rule.naming; +package net.sourceforge.pmd.lang.java.rule.codestyle; import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceDeclaration; import net.sourceforge.pmd.lang.java.ast.ASTMethodDeclarator; @@ -11,6 +11,7 @@ public class AvoidDollarSignsRule extends AbstractJavaRule { + @Override public Object visit(ASTClassOrInterfaceDeclaration node, Object data) { if (node.getImage().indexOf('$') != -1) { addViolation(data, node); @@ -19,6 +20,7 @@ public Object visit(ASTClassOrInterfaceDeclaration node, Object data) { return super.visit(node, data); } + @Override public Object visit(ASTVariableDeclaratorId node, Object data) { if (node.getImage().indexOf('$') != -1) { addViolation(data, node); @@ -27,6 +29,7 @@ public Object visit(ASTVariableDeclaratorId node, Object data) { return super.visit(node, data); } + @Override public Object visit(ASTMethodDeclarator node, Object data) { if (node.getImage().indexOf('$') != -1) { addViolation(data, node); diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codestyle/ClassNamingConventionsRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codestyle/ClassNamingConventionsRule.java new file mode 100644 index 00000000000..4914f88c3ae --- /dev/null +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codestyle/ClassNamingConventionsRule.java @@ -0,0 +1,153 @@ +/** + * 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.ASTAnnotationTypeDeclaration; +import net.sourceforge.pmd.lang.java.ast.ASTAnyTypeBodyDeclaration; +import net.sourceforge.pmd.lang.java.ast.ASTAnyTypeBodyDeclaration.DeclarationKind; +import net.sourceforge.pmd.lang.java.ast.ASTAnyTypeDeclaration; +import net.sourceforge.pmd.lang.java.ast.ASTAnyTypeDeclaration.TypeKind; +import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceDeclaration; +import net.sourceforge.pmd.lang.java.ast.ASTEnumDeclaration; +import net.sourceforge.pmd.lang.java.ast.ASTInitializer; +import net.sourceforge.pmd.lang.java.ast.ASTMethodDeclaration; +import net.sourceforge.pmd.lang.java.ast.AccessNode; +import net.sourceforge.pmd.properties.PropertyDescriptor; +import net.sourceforge.pmd.properties.RegexProperty; + + +/** + * Configurable naming conventions for type declarations. + */ +public class ClassNamingConventionsRule extends AbstractNamingConventionRule { + + private final RegexProperty classRegex = defaultProp("class", "concrete class").build(); + private final RegexProperty abstractClassRegex = defaultProp("abstract class").build(); + private final RegexProperty interfaceRegex = defaultProp("interface").build(); + private final RegexProperty enumerationRegex = defaultProp("enum").build(); + private final RegexProperty annotationRegex = defaultProp("annotation").build(); + private final RegexProperty utilityClassRegex = defaultProp("utility class").defaultValue("[A-Z][a-zA-Z0-9]+(Utils?|Helper)").build(); + + + public ClassNamingConventionsRule() { + definePropertyDescriptor(classRegex); + definePropertyDescriptor(abstractClassRegex); + definePropertyDescriptor(interfaceRegex); + definePropertyDescriptor(enumerationRegex); + definePropertyDescriptor(annotationRegex); + definePropertyDescriptor(utilityClassRegex); + + addRuleChainVisit(ASTClassOrInterfaceDeclaration.class); + addRuleChainVisit(ASTEnumDeclaration.class); + addRuleChainVisit(ASTAnnotationTypeDeclaration.class); + } + + + // This could probably be moved to ClassOrInterfaceDeclaration + // to share the implementation and be used from XPath + private boolean isUtilityClass(ASTAnyTypeDeclaration node) { + if (node.getTypeKind() != TypeKind.CLASS) { + return false; + } + + ASTClassOrInterfaceDeclaration classNode = (ASTClassOrInterfaceDeclaration) node; + + // A class with a superclass or interfaces should not be considered + if (classNode.getSuperClassTypeNode() != null + || !classNode.getSuperInterfacesTypeNodes().isEmpty()) { + return false; + } + + // A class without declarations shouldn't be reported + boolean hasAny = false; + + for (ASTAnyTypeBodyDeclaration decl : classNode.getDeclarations()) { + switch (decl.getKind()) { + case FIELD: + case METHOD: + hasAny = isNonPrivate(decl) && !isMainMethod(decl); + if (!((AccessNode) decl.getDeclarationNode()).isStatic()) { + return false; + } + break; + + case INITIALIZER: + if (!((ASTInitializer) decl.getDeclarationNode()).isStatic()) { + return false; + } + break; + + default: + break; + } + } + + return hasAny; + } + + private boolean isNonPrivate(ASTAnyTypeBodyDeclaration decl) { + return !((AccessNode) decl.getDeclarationNode()).isPrivate(); + } + + + private boolean isMainMethod(ASTAnyTypeBodyDeclaration bodyDeclaration) { + if (DeclarationKind.METHOD != bodyDeclaration.getKind()) { + return false; + } + + ASTMethodDeclaration decl = (ASTMethodDeclaration) bodyDeclaration.getDeclarationNode(); + + return decl.isStatic() + && "main".equals(decl.getMethodName()) + && decl.getResultType().isVoid() + && decl.getFormalParameters().getParameterCount() == 1 + && String[].class.equals(decl.getFormalParameters().iterator().next().getType()); + } + + + @Override + public Object visit(ASTClassOrInterfaceDeclaration node, Object data) { + + if (node.isAbstract()) { + checkMatches(node, abstractClassRegex, data); + } else if (isUtilityClass(node)) { + checkMatches(node, utilityClassRegex, data); + } else if (node.isInterface()) { + checkMatches(node, interfaceRegex, data); + } else { + checkMatches(node, classRegex, data); + } + + return data; + } + + + @Override + public Object visit(ASTEnumDeclaration node, Object data) { + checkMatches(node, enumerationRegex, data); + return data; + } + + + @Override + public Object visit(ASTAnnotationTypeDeclaration node, Object data) { + checkMatches(node, annotationRegex, data); + return data; + } + + + @Override + String defaultConvention() { + return PASCAL_CASE; + } + + + @Override + String kindDisplayName(ASTAnyTypeDeclaration node, PropertyDescriptor descriptor) { + return isUtilityClass(node) ? "utility class" : node.getTypeKind().getPrintableName(); + } +} diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codestyle/CommentDefaultAccessModifierRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codestyle/CommentDefaultAccessModifierRule.java new file mode 100644 index 00000000000..604de9f33fd --- /dev/null +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codestyle/CommentDefaultAccessModifierRule.java @@ -0,0 +1,121 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.codestyle; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import net.sourceforge.pmd.lang.java.ast.ASTAnyTypeDeclaration; +import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceDeclaration; +import net.sourceforge.pmd.lang.java.ast.ASTCompilationUnit; +import net.sourceforge.pmd.lang.java.ast.ASTConstructorDeclaration; +import net.sourceforge.pmd.lang.java.ast.ASTFieldDeclaration; +import net.sourceforge.pmd.lang.java.ast.ASTMethodDeclaration; +import net.sourceforge.pmd.lang.java.ast.ASTMethodDeclarator; +import net.sourceforge.pmd.lang.java.ast.ASTVariableDeclaratorId; +import net.sourceforge.pmd.lang.java.ast.AbstractAnyTypeDeclaration; +import net.sourceforge.pmd.lang.java.ast.AbstractJavaAccessNode; +import net.sourceforge.pmd.lang.java.ast.Comment; +import net.sourceforge.pmd.lang.java.rule.AbstractIgnoredAnnotationRule; +import net.sourceforge.pmd.properties.RegexProperty; + +/** + * Check for Methods, Fields and Nested Classes that have a default access + * modifier + * This rule ignores all nodes annotated with @VisibleForTesting by default. + * Use the ignoredAnnotationsDescriptor property to customize the ignored rules. + * + * @author Damián Techeira + */ +public class CommentDefaultAccessModifierRule extends AbstractIgnoredAnnotationRule { + + private static final RegexProperty REGEX_DESCRIPTOR = RegexProperty.named("regex") + .desc("Regular expression").defaultValue("\\/\\*\\s+(default|package)\\s+\\*\\/").uiOrder(1.0f).build(); + private static final String MESSAGE = "To avoid mistakes add a comment " + + "at the beginning of the %s %s if you want a default access modifier"; + private final Set interestingLineNumberComments = new HashSet(); + + public CommentDefaultAccessModifierRule() { + definePropertyDescriptor(REGEX_DESCRIPTOR); + } + + @Override + protected Collection defaultSuppressionAnnotations() { + Collection ignoredStrings = new ArrayList<>(); + ignoredStrings.add("com.google.common.annotations.VisibleForTesting"); + ignoredStrings.add("android.support.annotation.VisibleForTesting"); + return ignoredStrings; + } + + @Override + public Object visit(final ASTCompilationUnit node, final Object data) { + interestingLineNumberComments.clear(); + final List comments = node.getComments(); + for (final Comment comment : comments) { + if (getProperty(REGEX_DESCRIPTOR).matcher(comment.getImage()).matches()) { + interestingLineNumberComments.add(comment.getBeginLine()); + } + } + return super.visit(node, data); + } + + @Override + public Object visit(final ASTMethodDeclaration decl, final Object data) { + if (shouldReport(decl)) { + addViolationWithMessage(data, decl, + String.format(MESSAGE, decl.getFirstChildOfType(ASTMethodDeclarator.class).getImage(), "method")); + } + return super.visit(decl, data); + } + + @Override + public Object visit(final ASTFieldDeclaration decl, final Object data) { + if (shouldReport(decl)) { + addViolationWithMessage(data, decl, String.format(MESSAGE, + decl.getFirstDescendantOfType(ASTVariableDeclaratorId.class).getImage(), "field")); + } + return super.visit(decl, data); + } + + @Override + public Object visit(final ASTClassOrInterfaceDeclaration decl, final Object data) { + // check for nested classes + if (decl.isNested() && shouldReport(decl)) { + addViolationWithMessage(data, decl, String.format(MESSAGE, decl.getImage(), "nested class")); + } + return super.visit(decl, data); + } + + @Override + public Object visit(final ASTConstructorDeclaration decl, Object data) { + if (shouldReport(decl)) { + addViolationWithMessage(data, decl, String.format(MESSAGE, decl.getImage(), "constructor")); + } + return super.visit(decl, data); + } + + private boolean shouldReport(final AbstractJavaAccessNode decl) { + final AbstractAnyTypeDeclaration parentClassOrInterface = decl + .getFirstParentOfType(AbstractAnyTypeDeclaration.class); + + boolean isConcreteClass = parentClassOrInterface.getTypeKind() == ASTAnyTypeDeclaration.TypeKind.CLASS; + boolean isEnumConstructor = parentClassOrInterface.getTypeKind() == ASTAnyTypeDeclaration.TypeKind.ENUM + && decl instanceof ASTConstructorDeclaration; + + // ignore if it's an Interface / Annotation / Enum constructor + return isConcreteClass && !isEnumConstructor + // check if the field/method/nested class has a default access + // modifier + && decl.isPackagePrivate() + // if is a default access modifier check if there is a comment + // in this line + && !interestingLineNumberComments.contains(decl.getBeginLine()) + // that it is not annotated with e.g. @VisibleForTesting + && !hasIgnoredAnnotation(decl); + } +} diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/ConfusingTernaryRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codestyle/ConfusingTernaryRule.java similarity index 98% rename from pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/ConfusingTernaryRule.java rename to pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codestyle/ConfusingTernaryRule.java index 082d4c29406..8eb681819b0 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/ConfusingTernaryRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codestyle/ConfusingTernaryRule.java @@ -2,7 +2,7 @@ * BSD-style license; for more info see http://pmd.sourceforge.net/license.html */ -package net.sourceforge.pmd.lang.java.rule.design; +package net.sourceforge.pmd.lang.java.rule.codestyle; import net.sourceforge.pmd.lang.ast.Node; import net.sourceforge.pmd.lang.java.ast.ASTConditionalAndExpression; @@ -20,9 +20,9 @@ /** * if (x != y) { diff(); } else { same(); } and
* (!x ? diff() : same()); - * + * *

XPath can handle the easy cases, e.g.:

- * + * *
  *    //IfStatement[
  *      Statement[2]
@@ -30,18 +30,18 @@
  *        EqualityExpression[@Image="!="] or
  *        UnaryExpressionNotPlusMinus[@Image="!"]]]
  * 
- * + * *

But "&&" and "||" are difficult, since we need a match for all * children instead of just one. This can be done by using a double-negative, * e.g.:

- * + * *
  *    not(*[not(matchme)])
  * 
- * + * *

Still, XPath is unable to handle arbitrarily nested cases, since it lacks * recursion, e.g.:

- * + * *
  * if (((x != !y)) || !(x)) {
  *     diff();
@@ -59,6 +59,7 @@ public ConfusingTernaryRule() {
         definePropertyDescriptor(ignoreElseIfProperty);
     }
 
+    @Override
     public Object visit(ASTIfStatement node, Object data) {
         // look for "if (match) ..; else .."
         if (node.jjtGetNumChildren() == 3) {
@@ -78,6 +79,7 @@ public Object visit(ASTIfStatement node, Object data) {
         return super.visit(node, data);
     }
 
+    @Override
     public Object visit(ASTConditionalExpression node, Object data) {
         // look for "match ? .. : .."
         if (node.jjtGetNumChildren() > 0) {
diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/imports/DontImportJavaLangRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codestyle/DontImportJavaLangRule.java
similarity index 94%
rename from pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/imports/DontImportJavaLangRule.java
rename to pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codestyle/DontImportJavaLangRule.java
index 38d264b72a7..dc9a1a01a6f 100644
--- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/imports/DontImportJavaLangRule.java
+++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codestyle/DontImportJavaLangRule.java
@@ -2,7 +2,7 @@
  * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
  */
 
-package net.sourceforge.pmd.lang.java.rule.imports;
+package net.sourceforge.pmd.lang.java.rule.codestyle;
 
 import net.sourceforge.pmd.lang.java.ast.ASTImportDeclaration;
 import net.sourceforge.pmd.lang.java.rule.AbstractJavaRule;
diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/imports/DuplicateImportsRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codestyle/DuplicateImportsRule.java
similarity index 93%
rename from pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/imports/DuplicateImportsRule.java
rename to pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codestyle/DuplicateImportsRule.java
index 55ead86d57a..28963d75571 100644
--- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/imports/DuplicateImportsRule.java
+++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codestyle/DuplicateImportsRule.java
@@ -2,7 +2,7 @@
  * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
  */
 
-package net.sourceforge.pmd.lang.java.rule.imports;
+package net.sourceforge.pmd.lang.java.rule.codestyle;
 
 import java.lang.reflect.Method;
 import java.lang.reflect.Modifier;
@@ -19,6 +19,7 @@ public class DuplicateImportsRule extends AbstractJavaRule {
     private Set singleTypeImports;
     private Set importOnDemandImports;
 
+    @Override
     public Object visit(ASTCompilationUnit node, Object data) {
         singleTypeImports = new HashSet<>();
         importOnDemandImports = new HashSet<>();
@@ -49,7 +50,7 @@ public Object visit(ASTCompilationUnit node, Object data) {
     /**
      * Check whether this seemingly duplicate import is actually a
      * disambiguation import.
-     * 
+     *
      * Example: import java.awt.*; import java.util.*; import java.util.List;
      * //Needed because java.awt.List exists
      */
@@ -79,13 +80,11 @@ private boolean isDisambiguationImport(ASTCompilationUnit node, String singleTyp
         }
 
         String fullyQualifiedClassName = "java.lang." + singleTypeName;
-        if (node.getClassTypeResolver().classNameExists(fullyQualifiedClassName)) {
-            return true; // Class exists in another imported package
-        }
-
-        return false; // This really is a duplicate import
+        // Class might exist in another imported package
+        return node.getClassTypeResolver().classNameExists(fullyQualifiedClassName);
     }
 
+    @Override
     public Object visit(ASTImportDeclaration node, Object data) {
         ImportWrapper wrapper = new ImportWrapper(node.getImportedName(), node.getImportedName(),
                 node.getImportedNameNode(), node.isStatic() && node.isImportOnDemand());
diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/FieldDeclarationsShouldBeAtStartOfClassRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codestyle/FieldDeclarationsShouldBeAtStartOfClassRule.java
similarity index 98%
rename from pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/FieldDeclarationsShouldBeAtStartOfClassRule.java
rename to pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codestyle/FieldDeclarationsShouldBeAtStartOfClassRule.java
index a38b055e323..3b173e06a47 100644
--- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/FieldDeclarationsShouldBeAtStartOfClassRule.java
+++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codestyle/FieldDeclarationsShouldBeAtStartOfClassRule.java
@@ -2,7 +2,7 @@
  * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
  */
 
-package net.sourceforge.pmd.lang.java.rule.design;
+package net.sourceforge.pmd.lang.java.rule.codestyle;
 
 import net.sourceforge.pmd.lang.ast.Node;
 import net.sourceforge.pmd.lang.java.ast.ASTAnnotation;
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 00000000000..0cfc4f2e5a6
--- /dev/null
+++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codestyle/FieldNamingConventionsRule.java
@@ -0,0 +1,112 @@
+/**
+ * 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;
+import net.sourceforge.pmd.properties.StringMultiProperty;
+
+
+/**
+ * Configurable naming conventions for field declarations.
+ *
+ * @author Clément Fournier
+ * @since 6.7.0
+ */
+public class FieldNamingConventionsRule extends AbstractNamingConventionRule {
+    // TODO we need a more powerful scheme to match some fields, e.g. include modifiers/type
+    // We could define a new property, but specifying property values as a single string doesn't scale
+    private static final StringMultiProperty EXCLUDED_NAMES = StringMultiProperty.named("exclusions")
+                                                                                 .desc("Names of fields to whitelist.")
+                                                                                 .defaultValues("serialVersionUID")
+                                                                                 .build();
+
+
+    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);
+        definePropertyDescriptor(EXCLUDED_NAMES);
+
+        addRuleChainVisit(ASTFieldDeclaration.class);
+        addRuleChainVisit(ASTEnumConstant.class);
+    }
+
+
+    @Override
+    public Object visit(ASTFieldDeclaration node, Object data) {
+        for (ASTVariableDeclaratorId id : node) {
+            if (getProperty(EXCLUDED_NAMES).contains(id.getVariableName())) {
+                continue;
+            }
+
+            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/FormalParameterNamingConventionsRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codestyle/FormalParameterNamingConventionsRule.java
new file mode 100644
index 00000000000..ac748895a6f
--- /dev/null
+++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codestyle/FormalParameterNamingConventionsRule.java
@@ -0,0 +1,72 @@
+/**
+ * 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.ASTVariableDeclaratorId;
+import net.sourceforge.pmd.properties.PropertyDescriptor;
+import net.sourceforge.pmd.properties.RegexProperty;
+
+
+/**
+ * Enforces a naming convention for lambda and method parameters.
+ *
+ * @author Clément Fournier
+ * @since 6.6.0
+ */
+public final class FormalParameterNamingConventionsRule extends AbstractNamingConventionRule {
+
+    // These are not exhaustive, but are chosen to be the most useful, for a start
+
+
+    private final RegexProperty formalParamRegex = defaultProp("methodParameter", "formal parameter").build();
+    private final RegexProperty finalFormalParamRegex = defaultProp("finalMethodParameter", "final formal parameter").build();
+
+    private final RegexProperty lambdaParamRegex = defaultProp("lambdaParameter", "inferred-type lambda parameter").build();
+    private final RegexProperty explicitLambdaParamRegex = defaultProp("explicitLambdaParameter", "explicitly-typed lambda parameter").build();
+
+
+    public FormalParameterNamingConventionsRule() {
+        definePropertyDescriptor(formalParamRegex);
+        definePropertyDescriptor(finalFormalParamRegex);
+        definePropertyDescriptor(lambdaParamRegex);
+        definePropertyDescriptor(explicitLambdaParamRegex);
+
+        addRuleChainVisit(ASTVariableDeclaratorId.class);
+    }
+
+
+    @Override
+    public Object visit(ASTVariableDeclaratorId node, Object data) {
+
+        if (node.isLambdaParameter()) {
+            checkMatches(node, node.isTypeInferred() ? lambdaParamRegex : explicitLambdaParamRegex, data);
+        } else if (node.isFormalParameter()) {
+            checkMatches(node, node.isFinal() ? finalFormalParamRegex : formalParamRegex, data);
+        }
+
+        return data;
+    }
+
+
+    @Override
+    String defaultConvention() {
+        return CAMEL_CASE;
+    }
+
+
+    @Override
+    String kindDisplayName(ASTVariableDeclaratorId node, PropertyDescriptor descriptor) {
+        if (node.isLambdaParameter()) {
+            return node.isTypeInferred() ? "lambda parameter" : "explicitly-typed lambda parameter";
+        } else if (node.isFormalParameter()) { // necessarily a method parameter here
+            return node.isFinal() ? "final method parameter" : "method parameter";
+        }
+
+        throw new UnsupportedOperationException("This rule doesn't handle this case");
+    }
+}
diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codestyle/IdenticalCatchBranchesRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codestyle/IdenticalCatchBranchesRule.java
new file mode 100644
index 00000000000..ba10b3f3c75
--- /dev/null
+++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codestyle/IdenticalCatchBranchesRule.java
@@ -0,0 +1,185 @@
+/**
+ * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
+ */
+
+package net.sourceforge.pmd.lang.java.rule.codestyle;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+
+import net.sourceforge.pmd.lang.ast.Node;
+import net.sourceforge.pmd.lang.java.ast.ASTCatchStatement;
+import net.sourceforge.pmd.lang.java.ast.ASTName;
+import net.sourceforge.pmd.lang.java.ast.ASTPrimaryPrefix;
+import net.sourceforge.pmd.lang.java.ast.ASTPrimarySuffix;
+import net.sourceforge.pmd.lang.java.ast.ASTTryStatement;
+import net.sourceforge.pmd.lang.java.ast.ASTType;
+import net.sourceforge.pmd.lang.java.rule.AbstractJavaRule;
+
+
+/**
+ * Flags identical catch branches, which can be collapsed into a multi-catch.
+ *
+ * @author Clément Fournier
+ * @since 6.4.0
+ */
+public class IdenticalCatchBranchesRule extends AbstractJavaRule {
+
+
+    private boolean areEquivalent(ASTCatchStatement st1, ASTCatchStatement st2) {
+        return hasSameSubTree(st1.getBlock(), st2.getBlock(), st1.getExceptionName(), st2.getExceptionName());
+    }
+
+
+    /** groups catch statements by equivalence class, according to the equivalence {@link #areEquivalent(ASTCatchStatement, ASTCatchStatement)}. */
+    private Set> equivalenceClasses(List catches) {
+        Set> result = new HashSet<>(catches.size());
+        for (ASTCatchStatement stmt : catches) {
+            if (result.isEmpty()) {
+                result.add(newEquivClass(stmt));
+                continue;
+            }
+
+            boolean isNewClass = true;
+            for (List equivClass : result) {
+                if (areEquivalent(stmt, equivClass.get(0))) {
+                    equivClass.add(stmt);
+                    isNewClass = false;
+                    break;
+                }
+            }
+
+            if (isNewClass) {
+                result.add(newEquivClass(stmt));
+            }
+        }
+
+        return result;
+    }
+
+
+    private List newEquivClass(ASTCatchStatement stmt) {
+        // Each equivalence class is sorted by document order
+        List result = new ArrayList<>(2);
+        result.add(stmt);
+        return result;
+    }
+
+
+    // Gets the representation of the set of catch statements as a single multicatch
+    private String getCaughtExceptionsAsString(ASTCatchStatement stmt) {
+
+        StringBuilder sb = new StringBuilder();
+
+        final String delim = " | ";
+        for (ASTType type : stmt.getCaughtExceptionTypeNodes()) {
+            sb.append(type.getTypeImage()).append(delim);
+        }
+
+        // remove the last delimiter
+        sb.replace(sb.length() - 3, sb.length(), "");
+        return sb.toString();
+    }
+
+
+    @Override
+    public Object visit(ASTTryStatement node, Object data) {
+
+        List catchStatements = node.getCatchStatements();
+        Set> equivClasses = equivalenceClasses(catchStatements);
+
+        for (List identicalStmts : equivClasses) {
+            if (identicalStmts.size() > 1) {
+                String identicalBranchName = getCaughtExceptionsAsString(identicalStmts.get(0));
+
+                // By convention, lower catch blocks are collapsed into the highest one
+                // The first node of the equivalence class is thus the block that should be transformed
+                for (int i = 1; i < identicalStmts.size(); i++) {
+                    addViolation(data, identicalStmts.get(i), new String[]{identicalBranchName, });
+                }
+            }
+        }
+
+        return super.visit(node, data);
+    }
+
+
+    /**
+     * Checks whether two nodes define the same subtree,
+     * up to the renaming of one local variable.
+     *
+     * @param node1          the first node to check
+     * @param node2          the second node to check
+     * @param exceptionName1 the first exception variable name
+     * @param exceptionName2 the second exception variable name
+     */
+    private boolean hasSameSubTree(Node node1, Node node2, String exceptionName1, String exceptionName2) {
+        if (node1 == null && node2 == null) {
+            return true;
+        } else if (node1 == null || node2 == null) {
+            return false;
+        }
+
+        //numbers of child node are different
+        if (node1.jjtGetNumChildren() != node2.jjtGetNumChildren()) {
+            return false;
+        }
+
+        for (int num = 0; num < node1.jjtGetNumChildren(); num++) {
+
+            if (!basicEquivalence(node1.jjtGetChild(num), node2.jjtGetChild(num), exceptionName1, exceptionName2)) {
+                return false;
+            }
+
+            //subtree of nodes are different
+            if (!hasSameSubTree(node1.jjtGetChild(num), node2.jjtGetChild(num),
+                exceptionName1, exceptionName2)) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+
+    // no subtree comparison
+    private boolean basicEquivalence(Node node1, Node node2, String varName1, String varName2) {
+        // Nodes must have the same type
+        if (node1.getClass() != node2.getClass()) {
+            return false;
+        }
+
+        String image1 = node1.getImage();
+        String image2 = node2.getImage();
+
+        // image of nodes must be the same
+        return Objects.equals(image1, image2)
+                // or must be references to the variable we allow to interchange
+                || Objects.equals(image1, varName1) && Objects.equals(image2, varName2)
+                // which means we must filter out method references.
+                && isNoMethodName(node1) && isNoMethodName(node2);
+
+    }
+
+
+    private boolean isNoMethodName(Node name) {
+
+        if (name instanceof ASTName
+                && (name.jjtGetParent() instanceof ASTPrimaryPrefix || name.jjtGetParent() instanceof ASTPrimarySuffix)) {
+
+            Node prefixOrSuffix = name.jjtGetParent();
+
+            if (prefixOrSuffix.jjtGetParent().jjtGetNumChildren() > 1 + prefixOrSuffix.jjtGetChildIndex()) {
+                // there's one next sibling
+
+                Node next = prefixOrSuffix.jjtGetParent().jjtGetChild(prefixOrSuffix.jjtGetChildIndex() + 1);
+                if (next instanceof ASTPrimarySuffix) {
+                    return !((ASTPrimarySuffix) next).isArguments();
+                }
+            }
+        }
+        return true;
+    }
+}
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 00000000000..8af4d19a9cb
--- /dev/null
+++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codestyle/LinguisticNamingRule.java
@@ -0,0 +1,209 @@
+/**
+ * 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 java.util.Locale;
+
+import org.apache.commons.lang3.StringUtils;
+
+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.lang.java.typeresolution.TypeHelper;
+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 the configured prefix (see transformMethodNames property).")
+            .uiOrder(4.0f).build();
+    private static final BooleanProperty CHECK_TRANSFORM_METHODS = BooleanProperty.named("checkTransformMethods")
+            .defaultValue(false)
+            .desc("Check return type of methods which contain the configured infix in their name (see transformMethodNames property).")
+            .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 StringMultiProperty TRANSFORM_METHOD_NAMES_PROPERTY = StringMultiProperty
+            .named("transformMethodNames").defaultValues("to", "as")
+            .desc("The prefixes and infixes that indicate a transform method.").uiOrder(6.0f).build();
+
+    private static final BooleanProperty CHECK_FIELDS = BooleanProperty.named("checkFields").defaultValue(true)
+            .desc("Check field names and types for inconsistent naming.").uiOrder(7.0f).build();
+    private static final BooleanProperty CHECK_VARIABLES = BooleanProperty.named("checkVariables").defaultValue(true)
+            .desc("Check local variable names and types for inconsistent naming.").uiOrder(8.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(9.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(TRANSFORM_METHOD_NAMES_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();
+        List prefixes = getProperty(TRANSFORM_METHOD_NAMES_PROPERTY);
+        String[] splitMethodName = StringUtils.splitByCharacterTypeCamelCase(nameOfMethod);
+        if (resultType.isVoid() && splitMethodName.length > 0
+                && prefixes.contains(splitMethodName[0].toLowerCase(Locale.ROOT))) {
+            // "To" or any other configured prefix found
+            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();
+        List infixes = getProperty(TRANSFORM_METHOD_NAMES_PROPERTY);
+        for (String infix : infixes) {
+            if (resultType.isVoid() && containsWord(nameOfMethod, StringUtils.capitalize(infix))) {
+                // "To" or any other configured infix in the middle somewhere
+                addViolationWithMessage(data, node, "Linguistics Antipattern - The transform method ''{0}'' should not return void linguistically",
+                        new Object[] { nameOfMethod });
+                // the first violation is sufficient - it is still the same method we are analyzing here
+                break;
+            }
+        }
+    }
+
+    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 boolean isBooleanType(ASTType node) {
+        return "boolean".equalsIgnoreCase(node.getTypeImage()) || TypeHelper.isA(node, "java.util.concurrent.atomic.AtomicBoolean");
+    }
+
+    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) && !isBooleanType(t)) {
+                    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(ASTType typeNode, ASTVariableDeclarator node, Object data) {
+        for (String prefix : getProperty(BOOLEAN_FIELD_PREFIXES_PROPERTY)) {
+            if (hasPrefix(node.getName(), prefix) && !isBooleanType(typeNode)) {
+                addViolationWithMessage(data, node, "Linguistics Antipattern - The field ''{0}'' indicates linguistically it is a boolean, but it is ''{1}''",
+                        new Object[] { node.getName(), typeNode.getTypeImage() });
+            }
+        }
+    }
+
+    private void checkVariable(ASTType typeNode, ASTVariableDeclarator node, Object data) {
+        for (String prefix : getProperty(BOOLEAN_FIELD_PREFIXES_PROPERTY)) {
+            if (hasPrefix(node.getName(), prefix) && !isBooleanType(typeNode)) {
+                addViolationWithMessage(data, node, "Linguistics Antipattern - The variable ''{0}'' indicates linguistically it is a boolean, but it is ''{1}''",
+                        new Object[] { node.getName(), typeNode.getTypeImage() });
+            }
+        }
+    }
+
+    @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, 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, 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/optimizations/LocalVariableCouldBeFinalRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codestyle/LocalVariableCouldBeFinalRule.java
similarity index 88%
rename from pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/optimizations/LocalVariableCouldBeFinalRule.java
rename to pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codestyle/LocalVariableCouldBeFinalRule.java
index f948d60f736..7bf3bb424ae 100644
--- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/optimizations/LocalVariableCouldBeFinalRule.java
+++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codestyle/LocalVariableCouldBeFinalRule.java
@@ -2,18 +2,20 @@
  * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
  */
 
-package net.sourceforge.pmd.lang.java.rule.optimizations;
+package net.sourceforge.pmd.lang.java.rule.codestyle;
 
 import java.util.List;
 import java.util.Map;
 
 import net.sourceforge.pmd.lang.java.ast.ASTLocalVariableDeclaration;
+import net.sourceforge.pmd.lang.java.rule.performance.AbstractOptimizationRule;
 import net.sourceforge.pmd.lang.java.symboltable.VariableNameDeclaration;
 import net.sourceforge.pmd.lang.symboltable.NameOccurrence;
 import net.sourceforge.pmd.lang.symboltable.Scope;
 
 public class LocalVariableCouldBeFinalRule extends AbstractOptimizationRule {
 
+    @Override
     public Object visit(ASTLocalVariableDeclaration node, Object data) {
         if (node.isFinal()) {
             return data;
diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codestyle/LocalVariableNamingConventionsRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codestyle/LocalVariableNamingConventionsRule.java
new file mode 100644
index 00000000000..d90c77512cc
--- /dev/null
+++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codestyle/LocalVariableNamingConventionsRule.java
@@ -0,0 +1,69 @@
+/**
+ * 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.ASTVariableDeclaratorId;
+import net.sourceforge.pmd.properties.PropertyDescriptor;
+import net.sourceforge.pmd.properties.RegexProperty;
+
+
+/**
+ * Enforces a naming convention for local variables and other locally scoped variables.
+ *
+ * @author Clément Fournier
+ * @since 6.6.0
+ */
+public final class LocalVariableNamingConventionsRule extends AbstractNamingConventionRule {
+
+    // These are not exhaustive, but are chosen to be the most useful, for a start
+
+    private final RegexProperty localVarRegex = defaultProp("localVar", "non-final local variable").build();
+    private final RegexProperty finalVarRegex = defaultProp("finalVar", "final local variable").build();
+
+    private final RegexProperty exceptionBlockParameterRegex = defaultProp("catchParameter", "exception block parameter").build();
+
+
+    public LocalVariableNamingConventionsRule() {
+        definePropertyDescriptor(localVarRegex);
+        definePropertyDescriptor(finalVarRegex);
+        definePropertyDescriptor(exceptionBlockParameterRegex);
+
+        addRuleChainVisit(ASTVariableDeclaratorId.class);
+    }
+
+
+
+    @Override
+    public Object visit(ASTVariableDeclaratorId node, Object data) {
+
+        if (node.isExceptionBlockParameter()) {
+            checkMatches(node, exceptionBlockParameterRegex, data);
+        } else if (node.isLocalVariable()) {
+            checkMatches(node, node.isFinal() ? finalVarRegex : localVarRegex, data);
+        }
+
+        return data;
+    }
+
+
+    @Override
+    String defaultConvention() {
+        return CAMEL_CASE;
+    }
+
+
+    @Override
+    String kindDisplayName(ASTVariableDeclaratorId node, PropertyDescriptor descriptor) {
+        if (node.isExceptionBlockParameter()) {
+            return "exception block parameter";
+        } else if (node.isLocalVariable()) {
+            return node.isFinal() ? "final local variable" : "local variable";
+        }
+
+        throw new UnsupportedOperationException("This rule doesn't handle this case");
+    }
+}
diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/optimizations/MethodArgumentCouldBeFinalRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codestyle/MethodArgumentCouldBeFinalRule.java
similarity index 92%
rename from pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/optimizations/MethodArgumentCouldBeFinalRule.java
rename to pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codestyle/MethodArgumentCouldBeFinalRule.java
index 3e153c10b76..0d11dcf72d5 100644
--- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/optimizations/MethodArgumentCouldBeFinalRule.java
+++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codestyle/MethodArgumentCouldBeFinalRule.java
@@ -2,7 +2,7 @@
  * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
  */
 
-package net.sourceforge.pmd.lang.java.rule.optimizations;
+package net.sourceforge.pmd.lang.java.rule.codestyle;
 
 import java.util.List;
 import java.util.Map;
@@ -11,6 +11,7 @@
 import net.sourceforge.pmd.lang.java.ast.ASTFormalParameter;
 import net.sourceforge.pmd.lang.java.ast.ASTMethodDeclaration;
 import net.sourceforge.pmd.lang.java.ast.AccessNode;
+import net.sourceforge.pmd.lang.java.rule.performance.AbstractOptimizationRule;
 import net.sourceforge.pmd.lang.java.symboltable.VariableNameDeclaration;
 import net.sourceforge.pmd.lang.symboltable.NameOccurrence;
 import net.sourceforge.pmd.lang.symboltable.Scope;
diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codestyle/MethodNamingConventionsRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codestyle/MethodNamingConventionsRule.java
new file mode 100644
index 00000000000..75de6f59def
--- /dev/null
+++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codestyle/MethodNamingConventionsRule.java
@@ -0,0 +1,127 @@
+/**
+ * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
+ */
+
+package net.sourceforge.pmd.lang.java.rule.codestyle;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.regex.Pattern;
+
+import net.sourceforge.pmd.lang.ast.Node;
+import net.sourceforge.pmd.lang.java.ast.ASTAllocationExpression;
+import net.sourceforge.pmd.lang.java.ast.ASTAnyTypeDeclaration;
+import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceDeclaration;
+import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceType;
+import net.sourceforge.pmd.lang.java.ast.ASTEnumConstant;
+import net.sourceforge.pmd.lang.java.ast.ASTMethodDeclaration;
+import net.sourceforge.pmd.lang.java.typeresolution.TypeHelper;
+import net.sourceforge.pmd.properties.BooleanProperty;
+import net.sourceforge.pmd.properties.PropertyDescriptor;
+import net.sourceforge.pmd.properties.RegexProperty;
+import net.sourceforge.pmd.properties.RegexProperty.RegexPBuilder;
+
+
+public class MethodNamingConventionsRule extends AbstractNamingConventionRule {
+
+    private static final Map DESCRIPTOR_TO_DISPLAY_NAME = new HashMap<>();
+
+    @Deprecated
+    private static final BooleanProperty CHECK_NATIVE_METHODS_DESCRIPTOR = new BooleanProperty("checkNativeMethods",
+                                                                                               "deprecated! Check native methods", true, 1.0f);
+
+
+    private final RegexProperty instanceRegex = defaultProp("", "instance").build();
+    private final RegexProperty staticRegex = defaultProp("static").build();
+    private final RegexProperty nativeRegex = defaultProp("native").build();
+    private final RegexProperty junit3Regex = defaultProp("JUnit 3 test").defaultValue("test[A-Z0-9][a-zA-Z0-9]*").build();
+    private final RegexProperty junit4Regex = defaultProp("JUnit 4 test").build();
+
+
+    public MethodNamingConventionsRule() {
+        definePropertyDescriptor(CHECK_NATIVE_METHODS_DESCRIPTOR);
+
+        definePropertyDescriptor(instanceRegex);
+        definePropertyDescriptor(staticRegex);
+        definePropertyDescriptor(nativeRegex);
+        definePropertyDescriptor(junit3Regex);
+        definePropertyDescriptor(junit4Regex);
+    }
+
+    private boolean isJunit4Test(ASTMethodDeclaration node) {
+        return node.isAnnotationPresent("org.junit.Test");
+    }
+
+
+    private boolean isJunit3Test(ASTMethodDeclaration node) {
+        if (!node.getMethodName().startsWith("test")) {
+            return false;
+        }
+
+        // Considers anonymous classes, TODO with #905 this will be easier
+        Node parent = node.getFirstParentOfAnyType(ASTEnumConstant.class, ASTAllocationExpression.class, ASTAnyTypeDeclaration.class);
+
+        if (!(parent instanceof ASTClassOrInterfaceDeclaration) || ((ASTClassOrInterfaceDeclaration) parent).isInterface()) {
+            return false;
+        }
+
+        ASTClassOrInterfaceType superClass = ((ASTClassOrInterfaceDeclaration) parent).getSuperClassTypeNode();
+
+        return superClass != null && TypeHelper.isA(superClass, "junit.framework.TestCase");
+    }
+
+
+    @Override
+    public Object visit(ASTMethodDeclaration node, Object data) {
+
+        if (node.isAnnotationPresent("java.lang.Override")) {
+            return super.visit(node, data);
+        }
+
+        if (node.isNative()) {
+            if (getProperty(CHECK_NATIVE_METHODS_DESCRIPTOR)) {
+                checkMatches(node, nativeRegex, data);
+            } else {
+                return super.visit(node, data);
+            }
+        } else if (node.isStatic()) {
+            checkMatches(node, staticRegex, data);
+        } else if (isJunit4Test(node)) {
+            checkMatches(node, junit4Regex, data);
+        } else if (isJunit3Test(node)) {
+            checkMatches(node, junit3Regex, data);
+        } else {
+            checkMatches(node, instanceRegex, data);
+        }
+
+        return super.visit(node, data);
+    }
+
+
+    @Override
+    String defaultConvention() {
+        return CAMEL_CASE;
+    }
+
+
+    @Override
+    String nameExtractor(ASTMethodDeclaration node) {
+        return node.getMethodName();
+    }
+
+    @Override
+    RegexPBuilder defaultProp(String name, String displayName) {
+        String display = (displayName + " method").trim();
+        RegexPBuilder prop = super.defaultProp(name.isEmpty() ? "method" : name, display);
+
+        DESCRIPTOR_TO_DISPLAY_NAME.put(prop.getName(), display);
+
+        return prop;
+    }
+
+
+    @Override
+    String kindDisplayName(ASTMethodDeclaration node, PropertyDescriptor descriptor) {
+        return DESCRIPTOR_TO_DISPLAY_NAME.get(descriptor.name());
+    }
+}
diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codestyle/OnlyOneReturnRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codestyle/OnlyOneReturnRule.java
new file mode 100644
index 00000000000..3421f616ce6
--- /dev/null
+++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codestyle/OnlyOneReturnRule.java
@@ -0,0 +1,46 @@
+/**
+ * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
+ */
+
+package net.sourceforge.pmd.lang.java.rule.codestyle;
+
+import java.util.Iterator;
+import java.util.List;
+
+import net.sourceforge.pmd.lang.ast.Node;
+import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceDeclaration;
+import net.sourceforge.pmd.lang.java.ast.ASTMethodDeclaration;
+import net.sourceforge.pmd.lang.java.ast.ASTReturnStatement;
+import net.sourceforge.pmd.lang.java.rule.AbstractJavaRule;
+
+public class OnlyOneReturnRule extends AbstractJavaRule {
+
+    @Override
+    public Object visit(ASTClassOrInterfaceDeclaration node, Object data) {
+        if (node.isInterface()) {
+            return data;
+        }
+        return super.visit(node, data);
+    }
+
+    @Override
+    public Object visit(ASTMethodDeclaration node, Object data) {
+        if (node.isAbstract()) {
+            return data;
+        }
+
+        List returnNodes = node.findDescendantsOfType(ASTReturnStatement.class);
+
+        if (returnNodes.size() > 1) {
+            for (Iterator i = returnNodes.iterator(); i.hasNext();) {
+                Node problem = i.next();
+                // skip the last one, it's OK
+                if (!i.hasNext()) {
+                    continue;
+                }
+                addViolation(data, problem);
+            }
+        }
+        return data;
+    }
+}
diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codestyle/PrematureDeclarationRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codestyle/PrematureDeclarationRule.java
new file mode 100644
index 00000000000..291a6de34b7
--- /dev/null
+++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codestyle/PrematureDeclarationRule.java
@@ -0,0 +1,110 @@
+/**
+ * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
+ */
+
+package net.sourceforge.pmd.lang.java.rule.codestyle;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import net.sourceforge.pmd.lang.ast.Node;
+import net.sourceforge.pmd.lang.java.ast.ASTBlockStatement;
+import net.sourceforge.pmd.lang.java.ast.ASTForInit;
+import net.sourceforge.pmd.lang.java.ast.ASTLocalVariableDeclaration;
+import net.sourceforge.pmd.lang.java.ast.ASTName;
+import net.sourceforge.pmd.lang.java.ast.ASTReturnStatement;
+import net.sourceforge.pmd.lang.java.ast.ASTThrowStatement;
+import net.sourceforge.pmd.lang.java.rule.AbstractJavaRule;
+
+/**
+ * Checks for variables in methods that are defined before they are really
+ * needed. A reference is deemed to be premature if it is created ahead of a
+ * block of code that doesn't use it that also has the ability to return or
+ * throw an exception.
+ * 
+ * @author Brian Remedios
+ */
+public class PrematureDeclarationRule extends AbstractJavaRule {
+
+
+    @Override
+    public Object visit(ASTLocalVariableDeclaration node, Object data) {
+
+        // is it part of a for-loop declaration?
+        if (node.jjtGetParent() instanceof ASTForInit) {
+            // yes, those don't count
+            return super.visit(node, data);
+        }
+
+        for (ASTBlockStatement block : statementsAfter(node)) {
+            if (hasReferencesIn(block, node.getVariableName())) {
+                break;
+            }
+            
+            if (hasExit(block)) {
+                addViolation(data, node);
+                break;
+            }
+        }
+
+        return super.visit(node, data);
+    }
+
+
+    /**
+     * Returns whether the block contains a return call or throws an exception.
+     * Exclude blocks that have these things as part of an inner class.
+     */
+    private boolean hasExit(ASTBlockStatement block) {
+        return block.hasDescendantOfAnyType(ASTThrowStatement.class, ASTReturnStatement.class);
+    }
+
+
+    /**
+     * Returns whether the variable is mentioned within the statement or not.
+     */
+    private static boolean hasReferencesIn(ASTBlockStatement block, String varName) {
+
+        // allow for closures on the var
+        for (ASTName name : block.findDescendantsOfType(ASTName.class, true)) {
+            if (isReference(varName, name.getImage())) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+
+    /**
+     * Return whether the shortName is part of the compound name by itself or as
+     * a method call receiver.
+     */
+    private static boolean isReference(String shortName, String compoundName) {
+        int dotPos = compoundName.indexOf('.');
+
+        return dotPos < 0 ? shortName.equals(compoundName) : shortName.equals(compoundName.substring(0, dotPos));
+    }
+
+
+    /**
+     * Returns all the block statements following the given local var declaration.
+     */
+    private static List statementsAfter(ASTLocalVariableDeclaration node) {
+
+        Node blockOrSwitch = node.jjtGetParent().jjtGetParent();
+
+        int count = blockOrSwitch.jjtGetNumChildren();
+        int start = node.jjtGetParent().jjtGetChildIndex() + 1;
+
+        List nextBlocks = new ArrayList<>(count - start);
+
+        for (int i = start; i < count; i++) {
+            Node maybeBlock = blockOrSwitch.jjtGetChild(i);
+            if (maybeBlock instanceof ASTBlockStatement) {
+                nextBlocks.add((ASTBlockStatement) maybeBlock);
+            }
+        }
+
+        return nextBlocks;
+    }
+}
diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codestyle/UnnecessaryAnnotationValueElementRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codestyle/UnnecessaryAnnotationValueElementRule.java
new file mode 100644
index 00000000000..f1294bd3196
--- /dev/null
+++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codestyle/UnnecessaryAnnotationValueElementRule.java
@@ -0,0 +1,35 @@
+/**
+ * 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.ASTAnnotation;
+import net.sourceforge.pmd.lang.java.ast.ASTMemberValuePair;
+import net.sourceforge.pmd.lang.java.rule.AbstractJavaRule;
+
+/**
+ * @author Kirk Clemens
+ * @since 6.2.0
+ */
+public class UnnecessaryAnnotationValueElementRule extends AbstractJavaRule {
+
+    public UnnecessaryAnnotationValueElementRule() {
+        addRuleChainVisit(ASTAnnotation.class);
+    }
+
+    @Override
+    public Object visit(ASTAnnotation node, Object data) {
+
+        final List annotationProperties = node.findDescendantsOfType(ASTMemberValuePair.class);
+        // all that needs to be done is check to if there's a single property in the annotation and if if that property is value
+        // then it's a violation and it should be resolved.
+        if (annotationProperties.size() == 1 && "value".equals(annotationProperties.get(0).getImage())) {
+            addViolation(data, node);
+        }
+
+        return data;
+    }
+}
diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codestyle/UnnecessaryConstructorRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codestyle/UnnecessaryConstructorRule.java
new file mode 100644
index 00000000000..c9c8ad1e40b
--- /dev/null
+++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codestyle/UnnecessaryConstructorRule.java
@@ -0,0 +1,102 @@
+/**
+ * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
+ */
+
+package net.sourceforge.pmd.lang.java.rule.codestyle;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+
+import net.sourceforge.pmd.lang.ast.Node;
+import net.sourceforge.pmd.lang.java.ast.ASTBlockStatement;
+import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceDeclaration;
+import net.sourceforge.pmd.lang.java.ast.ASTConstructorDeclaration;
+import net.sourceforge.pmd.lang.java.ast.ASTEnumDeclaration;
+import net.sourceforge.pmd.lang.java.ast.ASTExplicitConstructorInvocation;
+import net.sourceforge.pmd.lang.java.ast.ASTNameList;
+import net.sourceforge.pmd.lang.java.rule.AbstractIgnoredAnnotationRule;
+
+/**
+ * This rule detects when a constructor is not necessary;
+ * i.e., when there is only one constructor, it’s public, has an empty body,
+ * and takes no arguments.
+ */
+public class UnnecessaryConstructorRule extends AbstractIgnoredAnnotationRule {
+
+    @Override
+    protected Collection defaultSuppressionAnnotations() {
+        return Collections.singletonList("javax.inject.Inject");
+    }
+
+    @Override
+    public Object visit(ASTClassOrInterfaceDeclaration node, Object data) {
+
+        ASTConstructorDeclaration cons = node.getFirstDescendantOfType(ASTConstructorDeclaration.class);
+        if (isExplicitDefaultConstructor(node)
+            && haveSameAccessModifier(node, cons)) {
+            addViolation(data, cons);
+        }
+
+        return super.visit(node, data);
+    }
+
+    @Override
+    public Object visit(ASTEnumDeclaration node, Object data) {
+
+        ASTConstructorDeclaration cons = node.getFirstDescendantOfType(ASTConstructorDeclaration.class);
+        if (isExplicitDefaultConstructor(node) && cons.isPrivate()) {
+            addViolation(data, cons);
+        }
+
+        return super.visit(node, data);
+    }
+
+    /**
+     * Returns {@code true} if the node has only one {@link ASTConstructorDeclaration}
+     * child node and the constructor has empty body or simply invokes the superclass
+     * constructor with no arguments.
+     *
+     * @param node the node to check
+     */
+    private boolean isExplicitDefaultConstructor(Node node) {
+
+        List nodes
+            = node.findDescendantsOfType(ASTConstructorDeclaration.class);
+
+        if (nodes.size() != 1) {
+            return false;
+        }
+
+        ASTConstructorDeclaration cdnode = nodes.get(0);
+
+        return cdnode.getParameterCount() == 0 && !hasIgnoredAnnotation(cdnode)
+            && !cdnode.hasDescendantOfType(ASTBlockStatement.class) && !cdnode.hasDescendantOfType(ASTNameList.class)
+            && hasDefaultConstructorInvocation(cdnode);
+    }
+
+    /**
+     * Returns {@code true} if the constructor simply invokes superclass constructor
+     * with no arguments or doesn't invoke any constructor, otherwise {@code false}.
+     *
+     * @param cons the node to check
+     */
+    private boolean hasDefaultConstructorInvocation(ASTConstructorDeclaration cons) {
+        ASTExplicitConstructorInvocation inv = cons.getFirstChildOfType(ASTExplicitConstructorInvocation.class);
+        return inv == null || inv.isSuper() && inv.getArgumentCount() == 0;
+    }
+
+    /**
+     * Returns {@code true} if access modifier of construtor is same as class's,
+     * otherwise {@code false}.
+     *
+     * @param node the class declaration node
+     * @param cons the constructor declaration node
+     */
+    private boolean haveSameAccessModifier(ASTClassOrInterfaceDeclaration node, ASTConstructorDeclaration cons) {
+        return node.isPrivate() && cons.isPrivate()
+            || node.isProtected() && cons.isProtected()
+            || node.isPublic() && cons.isPublic()
+            || node.isPackagePrivate() && cons.isPackagePrivate();
+    }
+}
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
new file mode 100644
index 00000000000..9353548a71b
--- /dev/null
+++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codestyle/UnnecessaryFullyQualifiedNameRule.java
@@ -0,0 +1,328 @@
+/**
+ * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
+ */
+
+package net.sourceforge.pmd.lang.java.rule.codestyle;
+
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+
+import org.apache.commons.lang3.StringUtils;
+
+import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceType;
+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;
+
+public class UnnecessaryFullyQualifiedNameRule extends AbstractJavaRule {
+
+    private List imports = new ArrayList<>();
+    private String currentPackage;
+
+    public UnnecessaryFullyQualifiedNameRule() {
+        super.addRuleChainVisit(ASTCompilationUnit.class);
+        super.addRuleChainVisit(ASTPackageDeclaration.class);
+        super.addRuleChainVisit(ASTImportDeclaration.class);
+        super.addRuleChainVisit(ASTClassOrInterfaceType.class);
+        super.addRuleChainVisit(ASTName.class);
+    }
+
+    @Override
+    public Object visit(ASTCompilationUnit node, Object data) {
+        imports.clear();
+        currentPackage = null;
+        return data;
+    }
+
+    @Override
+    public Object visit(ASTPackageDeclaration node, Object data) {
+        currentPackage = node.getPackageNameImage();
+        return data;
+    }
+    
+    @Override
+    public Object visit(ASTImportDeclaration node, Object data) {
+        imports.add(node);
+        return data;
+    }
+
+    @Override
+    public Object visit(ASTClassOrInterfaceType node, Object data) {
+        // This name has no qualification, it can't be unnecessarily qualified
+        if (node.getImage().indexOf('.') < 0) {
+            return data;
+        }
+        checkImports(node, data);
+        return data;
+    }
+
+    @Override
+    public Object visit(ASTName node, Object data) {
+        if (!(node.jjtGetParent() instanceof ASTImportDeclaration)
+                && !(node.jjtGetParent() instanceof ASTPackageDeclaration)) {
+            // This name has no qualification, it can't be unnecessarily qualified
+            if (node.getImage().indexOf('.') < 0) {
+                return data;
+            }
+            checkImports(node, data);
+        }
+        return data;
+    }
+
+
+    /**
+     * Returns true if the name could be imported by this declaration.
+     * The name must be fully qualified, the import is either on-demand
+     * or static, that is its {@link ASTImportDeclaration#getImportedName()}
+     * is the enclosing package or type name of the imported type or static member.
+     */
+    private boolean declarationMatches(ASTImportDeclaration decl, String name) {
+        return name.startsWith(decl.getImportedName())
+                && 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<>();
+
+        // Find all "matching" import declarations
+        for (ASTImportDeclaration importDeclaration : imports) {
+            if (!importDeclaration.isImportOnDemand()) {
+                // Exact match of imported class
+                if (name.equals(importDeclaration.getImportedName())) {
+                    matches.add(importDeclaration);
+                    continue;
+                }
+            }
+            // On demand import exactly matches the package of the type
+            // Or match of static method call on imported class
+            if (declarationMatches(importDeclaration, name)) {
+                matches.add(importDeclaration);
+            }
+        }
+
+        // If there is no direct match, consider if we match the tail end of a
+        // direct static import, but also a static method on a class import.
+        // For example:
+        //
+        // import java.util.Arrays;
+        // import static java.util.Arrays.asList;
+        // static {
+        // List list1 = Arrays.asList("foo"); // Array class name not needed!
+        // List list2 = asList("foo"); // Preferred, used static import
+        // }
+        //
+        // Or: The usage of a FQN is correct, if there is another import with the same class.
+        // Example
+        // import foo.String;
+        // static {
+        // java.lang.String s = "a";
+        // }
+        if (matches.isEmpty()) {
+            for (ASTImportDeclaration importDeclaration : imports) {
+                String[] importParts = importDeclaration.getImportedName().split("\\.");
+                String[] nameParts = name.split("\\.");
+                if (importDeclaration.isStatic()) {
+                    if (importDeclaration.isImportOnDemand()) {
+                        // Name class part matches class part of static import?
+                        if (nameParts[nameParts.length - 2].equals(importParts[importParts.length - 1])) {
+                            matches.add(importDeclaration);
+                        }
+                    } else {
+                        // 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);
+                        }
+                    }
+                } else if (!importDeclaration.isImportOnDemand()) {
+                    // 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);
+                    }
+                }
+            }
+        }
+
+        if (matches.isEmpty()) {
+            if (isJavaLangImplicit(node)) {
+                addViolation(data, node, new Object[] { node.getImage(), "java.lang.*", "implicit "});
+            } else if (isSamePackage(node)) {
+                addViolation(data, node, new Object[] { node.getImage(), currentPackage + ".*", "same package "});
+            }
+        } else {
+            ASTImportDeclaration firstMatch = findFirstMatch(matches);
+
+            // Could this done to avoid a conflict?
+            if (!isAvoidingConflict(node, name, firstMatch)) {
+                String importStr = firstMatch.getImportedName() + (firstMatch.isImportOnDemand() ? ".*" : "");
+                String type = firstMatch.isStatic() ? "static " : "";
+
+                addViolation(data, node, new Object[] { node.getImage(), importStr, type });
+            }
+        }
+    }
+
+    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 isSamePackage(AbstractJavaTypeNode node) {
+        String name = node.getImage();
+        return name.substring(0, name.lastIndexOf('.')).equals(currentPackage);
+    }
+    
+    private boolean isJavaLangImplicit(AbstractJavaTypeNode node) {
+        String name = node.getImage();
+        boolean isJavaLang = name != null && name.startsWith("java.lang.");
+
+        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() // 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.*
+            return StringUtils.countMatches(name, '.') == 2;
+        }
+        return false;
+    }
+
+    private boolean isAvoidingConflict(final AbstractJavaTypeNode node, final String name,
+            final ASTImportDeclaration firstMatch) {
+        // is it a conflict between different imports?
+        if (firstMatch.isImportOnDemand() && firstMatch.isStatic()) {
+            final String methodCalled = name.substring(name.indexOf('.') + 1);
+
+            // Is there any other static import conflictive?
+            for (final ASTImportDeclaration importDeclaration : imports) {
+                if (!Objects.equals(importDeclaration, firstMatch) && importDeclaration.isStatic()) {
+                    if (declarationMatches(firstMatch, importDeclaration.getImportedName())) {
+                        // A conflict against the same class is not an excuse,
+                        // ie:
+                        // import java.util.Arrays;
+                        // import static java.util.Arrays.asList;
+                        continue;
+                    }
+
+                    if (importDeclaration.isImportOnDemand()) {
+                        // We need type resolution to make sure there is a
+                        // conflicting method
+                        if (importDeclaration.getType() != null) {
+                            for (final Method m : importDeclaration.getType().getMethods()) {
+                                if (m.getName().equals(methodCalled)) {
+                                    return true;
+                                }
+                            }
+                        }
+                    } else if (importDeclaration.getImportedName().endsWith(methodCalled)) {
+                        return true;
+                    }
+                }
+            }
+        }
+
+        final String unqualifiedName = name.substring(name.lastIndexOf('.') + 1);
+        final int unqualifiedNameLength = unqualifiedName.length();
+
+        // There could be a conflict between an import on demand and another import, e.g.
+        // import One.*;
+        // import Two.Problem;
+        // Where One.Problem is a legitimate qualification
+        if (firstMatch.isImportOnDemand() && !firstMatch.isStatic()) {
+            for (ASTImportDeclaration importDeclaration : imports) {
+                if (importDeclaration != firstMatch     // NOPMD
+                        && !importDeclaration.isStatic()
+                        && !importDeclaration.isImportOnDemand()) {
+
+                    // Duplicate imports are legal
+                    if (!importDeclaration.getPackageName().equals(firstMatch.getPackageName())
+                            && importDeclaration.getImportedSimpleName().equals(unqualifiedName)) {
+                        return true;
+                    }
+                }
+            }
+        }
+
+        // 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()) {
+            // 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();
+        for (final String qualified : qualifiedTypes) {
+            int fullLength = qualified.length();
+            if (qualified.endsWith(unqualifiedName)
+                    && (fullLength == unqualifiedNameLength || qualified.charAt(fullLength - unqualifiedNameLength - 1) == '.')) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+}
diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/UnnecessaryLocalBeforeReturnRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codestyle/UnnecessaryLocalBeforeReturnRule.java
similarity index 91%
rename from pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/UnnecessaryLocalBeforeReturnRule.java
rename to pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codestyle/UnnecessaryLocalBeforeReturnRule.java
index 57d9fde4ad4..86b7f6ca5fe 100644
--- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/UnnecessaryLocalBeforeReturnRule.java
+++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codestyle/UnnecessaryLocalBeforeReturnRule.java
@@ -2,11 +2,12 @@
  * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
  */
 
-package net.sourceforge.pmd.lang.java.rule.design;
+package net.sourceforge.pmd.lang.java.rule.codestyle;
 
 import java.util.List;
 import java.util.Map;
 
+import net.sourceforge.pmd.lang.ast.Node;
 import net.sourceforge.pmd.lang.java.ast.ASTAnnotation;
 import net.sourceforge.pmd.lang.java.ast.ASTBlockStatement;
 import net.sourceforge.pmd.lang.java.ast.ASTExpression;
@@ -62,6 +63,10 @@ public Object visit(ASTReturnStatement rtn, Object data) {
                 .getDeclarations(VariableNameDeclaration.class);
         for (Map.Entry> entry : vars.entrySet()) {
             VariableNameDeclaration variableDeclaration = entry.getKey();
+            if (variableDeclaration.getDeclaratorId().isFormalParameter()) {
+                continue;
+            }
+            
             List usages = entry.getValue();
 
             if (usages.size() == 1) { // If there is more than 1 usage, then it's not only returned
@@ -99,6 +104,12 @@ private boolean statementsBeforeReturn(VariableNameDeclaration variableDeclarati
         return false;
     }
 
+    // TODO : should node define isAfter / isBefore helper methods for Nodes?
+    private static boolean isAfter(Node n1, Node n2) {
+        return n1.getBeginLine() > n2.getBeginLine()
+                || n1.getBeginLine() == n2.getBeginLine() && n1.getBeginColumn() >= n2.getEndColumn();
+    }
+
     private boolean isInitDataModifiedAfterInit(final VariableNameDeclaration variableDeclaration,
             final ASTReturnStatement rtn) {
         final ASTVariableInitializer initializer = variableDeclaration.getAccessNodeParent()
@@ -118,12 +129,7 @@ private boolean isInitDataModifiedAfterInit(final VariableNameDeclaration variab
                             for (final NameOccurrence occ : entry.getValue()) {
                                 final ScopedNode location = occ.getLocation();
                                 // Is it used after initializing our "unnecessary" local but before the return statement?
-                                // TODO : should node define isAfter / isBefore helper methods?
-                                if ((location.getBeginLine() > initializer.getEndLine()
-                                        || (location.getBeginLine() == initializer.getEndLine() && location.getBeginColumn() >= initializer.getEndColumn()))
-                                        && (location.getEndLine() < rtn.getBeginLine()
-                                                || (location.getEndLine() == rtn.getBeginLine()
-                                                        && location.getEndColumn() <= rtn.getEndColumn()))) {
+                                if (isAfter(location, initializer) && isAfter(rtn, location)) {
                                     return true;
                                 }
                             }
diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codestyle/UnnecessaryModifierRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codestyle/UnnecessaryModifierRule.java
new file mode 100644
index 00000000000..1f894e5052b
--- /dev/null
+++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codestyle/UnnecessaryModifierRule.java
@@ -0,0 +1,293 @@
+/**
+ * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
+ */
+
+package net.sourceforge.pmd.lang.java.rule.codestyle;
+
+import java.util.EnumSet;
+import java.util.Locale;
+import java.util.Set;
+
+import org.apache.commons.lang3.StringUtils;
+
+import net.sourceforge.pmd.lang.ast.Node;
+import net.sourceforge.pmd.lang.java.ast.ASTAllocationExpression;
+import net.sourceforge.pmd.lang.java.ast.ASTAnnotationMethodDeclaration;
+import net.sourceforge.pmd.lang.java.ast.ASTAnnotationTypeDeclaration;
+import net.sourceforge.pmd.lang.java.ast.ASTAnyTypeDeclaration;
+import net.sourceforge.pmd.lang.java.ast.ASTAnyTypeDeclaration.TypeKind;
+import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceDeclaration;
+import net.sourceforge.pmd.lang.java.ast.ASTConstructorDeclaration;
+import net.sourceforge.pmd.lang.java.ast.ASTEnumBody;
+import net.sourceforge.pmd.lang.java.ast.ASTEnumConstant;
+import net.sourceforge.pmd.lang.java.ast.ASTEnumDeclaration;
+import net.sourceforge.pmd.lang.java.ast.ASTFieldDeclaration;
+import net.sourceforge.pmd.lang.java.ast.ASTMethodDeclaration;
+import net.sourceforge.pmd.lang.java.ast.ASTMethodOrConstructorDeclaration;
+import net.sourceforge.pmd.lang.java.ast.ASTResource;
+import net.sourceforge.pmd.lang.java.ast.AccessNode;
+import net.sourceforge.pmd.lang.java.ast.MethodLikeNode;
+import net.sourceforge.pmd.lang.java.rule.AbstractJavaRule;
+
+
+public class UnnecessaryModifierRule extends AbstractJavaRule {
+
+
+    public UnnecessaryModifierRule() {
+        addRuleChainVisit(ASTEnumDeclaration.class);
+        addRuleChainVisit(ASTAnnotationTypeDeclaration.class);
+        addRuleChainVisit(ASTClassOrInterfaceDeclaration.class);
+        addRuleChainVisit(ASTMethodDeclaration.class);
+        addRuleChainVisit(ASTResource.class);
+        addRuleChainVisit(ASTFieldDeclaration.class);
+        addRuleChainVisit(ASTAnnotationMethodDeclaration.class);
+        addRuleChainVisit(ASTConstructorDeclaration.class);
+    }
+
+
+    private void reportUnnecessaryModifiers(Object data, Node node,
+                                            Modifier unnecessaryModifier, String explanation) {
+        reportUnnecessaryModifiers(data, node, EnumSet.of(unnecessaryModifier), explanation);
+    }
+
+
+    private void reportUnnecessaryModifiers(Object data, Node node,
+                                            Set unnecessaryModifiers, String explanation) {
+        if (unnecessaryModifiers.isEmpty()) {
+            return;
+        }
+        super.addViolation(data, node, new String[]{
+                formatUnnecessaryModifiers(unnecessaryModifiers),
+                getPrintableNodeKind(node),
+                getNodeName(node),
+                explanation.isEmpty() ? "" : ": " + explanation,
+        });
+    }
+
+
+    // TODO this should probably make it into a PrettyPrintingUtil or something.
+    private String getNodeName(Node node) {
+        // constructors are differentiated by their parameters, while we only use method name for methods
+        if (node instanceof ASTMethodDeclaration) {
+            return ((ASTMethodDeclaration) node).getMethodName();
+        } else if (node instanceof ASTMethodOrConstructorDeclaration) {
+            // constructors are differentiated by their parameters, while we only use method name for methods
+            return ((ASTConstructorDeclaration) node).getQualifiedName().getOperation();
+        } else if (node instanceof ASTFieldDeclaration) {
+            return ((ASTFieldDeclaration) node).getVariableName();
+        } else if (node instanceof ASTResource) {
+            return ((ASTResource) node).getVariableDeclaratorId().getImage();
+        } else {
+            return node.getImage();
+        }
+    }
+
+
+    // TODO same here
+    private String getPrintableNodeKind(Node node) {
+        if (node instanceof ASTAnyTypeDeclaration) {
+            return ((ASTAnyTypeDeclaration) node).getTypeKind().getPrintableName();
+        } else if (node instanceof MethodLikeNode) {
+            return ((MethodLikeNode) node).getKind().getPrintableName();
+        } else if (node instanceof ASTFieldDeclaration) {
+            return "field";
+        } else if (node instanceof ASTResource) {
+            return "resource specification";
+        }
+        throw new UnsupportedOperationException("Node " + node + " is unaccounted for");
+    }
+
+
+    private String formatUnnecessaryModifiers(Set set) {
+        // prints in the standard modifier order (sorted by enum constant ordinal),
+        // regardless of the actual order in which we checked
+        return (set.size() > 1 ? "s" : "") + " '" + StringUtils.join(set, " ") + "'";
+    }
+
+
+    @Override
+    public Object visit(ASTEnumDeclaration node, Object data) {
+
+        if (node.isPublic()) {
+            checkDeclarationInInterfaceType(data, node, EnumSet.of(Modifier.PUBLIC));
+        }
+
+        if (node.isStatic()) {
+            // a static enum
+            reportUnnecessaryModifiers(data, node, Modifier.STATIC, "nested enums are implicitly static");
+        }
+
+        return data;
+    }
+
+
+    @Override
+    public Object visit(ASTAnnotationTypeDeclaration node, Object data) {
+        if (node.isAbstract()) {
+            // may have several violations, with different explanations
+            reportUnnecessaryModifiers(data, node, Modifier.ABSTRACT, "annotations types are implicitly abstract");
+
+        }
+
+
+        if (!node.isNested()) {
+            return data;
+        }
+
+        // a public annotation within an interface or annotation
+        if (node.isPublic() && node.enclosingTypeIsA(TypeKind.INTERFACE, TypeKind.ANNOTATION)) {
+            reportUnnecessaryModifiers(data, node, Modifier.PUBLIC, "members of " + getPrintableNodeKind(node.getEnclosingTypeDeclaration()) + " types are implicitly public");
+        }
+
+        if (node.isStatic()) {
+            // a static annotation
+            reportUnnecessaryModifiers(data, node, Modifier.STATIC, "nested annotation types are implicitly static");
+        }
+
+        return data;
+    }
+
+
+    @Override
+    public Object visit(ASTClassOrInterfaceDeclaration node, Object data) {
+
+        if (node.isInterface() && node.isAbstract()) {
+            // an abstract interface
+            reportUnnecessaryModifiers(data, node, Modifier.ABSTRACT, "interface types are implicitly abstract");
+        }
+
+        if (!node.isNested()) {
+            return data;
+        }
+
+        boolean isParentInterfaceOrAnnotation = node.enclosingTypeIsA(TypeKind.INTERFACE, TypeKind.ANNOTATION);
+
+        // a public class or interface within an interface or annotation
+        if (node.isPublic() && isParentInterfaceOrAnnotation) {
+            reportUnnecessaryModifiers(data, node, Modifier.PUBLIC, "members of " + getPrintableNodeKind(node.getEnclosingTypeDeclaration()) + " types are implicitly public");
+        }
+
+        if (node.isStatic()) {
+            if (node.isInterface()) {
+                // a static interface
+                reportUnnecessaryModifiers(data, node, Modifier.STATIC, "member interfaces are implicitly static");
+            } else if (isParentInterfaceOrAnnotation) {
+                // a type nested within an interface
+                reportUnnecessaryModifiers(data, node, Modifier.STATIC, "types nested within an interface type are implicitly static");
+            }
+        }
+
+        return data;
+    }
+
+    @Override
+    public Object visit(final ASTMethodDeclaration node, Object data) {
+        Set unnecessary = EnumSet.noneOf(Modifier.class);
+
+        if (node.isSyntacticallyPublic()) {
+            unnecessary.add(Modifier.PUBLIC);
+        }
+        if (node.isSyntacticallyAbstract()) {
+            unnecessary.add(Modifier.ABSTRACT);
+        }
+
+        checkDeclarationInInterfaceType(data, node, unnecessary);
+
+        if (node.isFinal()) {
+            // If the method is annotated by @SafeVarargs then it's ok
+            if (!isSafeVarargs(node)) {
+                if (node.isPrivate()) {
+                    reportUnnecessaryModifiers(data, node, Modifier.FINAL, "private methods cannot be overridden");
+                } else {
+                    final Node n = node.getNthParent(3);
+                    // A final method of an anonymous class / enum constant. Neither can be extended / overridden
+                    if (n instanceof ASTAllocationExpression || n instanceof ASTEnumConstant) {
+                        reportUnnecessaryModifiers(data, node, Modifier.FINAL, "an anonymous class cannot be extended");
+                    } else if (n instanceof ASTClassOrInterfaceDeclaration && ((AccessNode) n).isFinal()) {
+                        reportUnnecessaryModifiers(data, node, Modifier.FINAL, "the method is already in a final class");
+                    }
+                }
+            }
+        }
+
+        return data;
+    }
+
+    @Override
+    public Object visit(final ASTResource node, final Object data) {
+        if (node.isFinal()) {
+            reportUnnecessaryModifiers(data, node, Modifier.FINAL, "resource specifications are implicitly final");
+        }
+
+        return data;
+    }
+
+    @Override
+    public Object visit(ASTFieldDeclaration node, Object data) {
+        Set unnecessary = EnumSet.noneOf(Modifier.class);
+        if (node.isSyntacticallyPublic()) {
+            unnecessary.add(Modifier.PUBLIC);
+        }
+        if (node.isSyntacticallyStatic()) {
+            unnecessary.add(Modifier.STATIC);
+        }
+        if (node.isSyntacticallyFinal()) {
+            unnecessary.add(Modifier.FINAL);
+        }
+
+        checkDeclarationInInterfaceType(data, node, unnecessary);
+        return data;
+    }
+
+    @Override
+    public Object visit(ASTAnnotationMethodDeclaration node, Object data) {
+        Set unnecessary = EnumSet.noneOf(Modifier.class);
+        if (node.isPublic()) {
+            unnecessary.add(Modifier.PUBLIC);
+        }
+        if (node.isAbstract()) {
+            unnecessary.add(Modifier.ABSTRACT);
+        }
+        checkDeclarationInInterfaceType(data, node, unnecessary);
+        return data;
+    }
+
+    @Override
+    public Object visit(ASTConstructorDeclaration node, Object data) {
+        if (node.getNthParent(2) instanceof ASTEnumBody) {
+            if (node.isPrivate()) {
+                reportUnnecessaryModifiers(data, node, Modifier.PRIVATE, "enum constructors are implicitly private");
+            }
+        }
+        return data;
+    }
+
+
+    private boolean isSafeVarargs(final ASTMethodDeclaration node) {
+        return node.isAnnotationPresent(SafeVarargs.class.getName());
+    }
+
+
+    private void checkDeclarationInInterfaceType(Object data, Node fieldOrMethod, Set unnecessary) {
+        // third ancestor could be an AllocationExpression
+        // if this is a method in an anonymous inner class
+        Node parent = fieldOrMethod.jjtGetParent().jjtGetParent().jjtGetParent();
+        if (parent instanceof ASTAnnotationTypeDeclaration
+                || parent instanceof ASTClassOrInterfaceDeclaration
+                && ((ASTClassOrInterfaceDeclaration) parent).isInterface()) {
+            reportUnnecessaryModifiers(data, fieldOrMethod, unnecessary, "the " + getPrintableNodeKind(fieldOrMethod) + " is declared in an " + getPrintableNodeKind(parent) + " type");
+        }
+    }
+
+
+    private enum Modifier {
+        PUBLIC, PRIVATE, PROTECTED, STATIC, FINAL, ABSTRACT;
+
+
+        @Override
+        public String toString() {
+            return name().toLowerCase(Locale.ROOT);
+        }
+    }
+
+}
diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/unnecessary/UnnecessaryReturnRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codestyle/UnnecessaryReturnRule.java
similarity index 92%
rename from pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/unnecessary/UnnecessaryReturnRule.java
rename to pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codestyle/UnnecessaryReturnRule.java
index e709b866d99..dfc18aeb883 100644
--- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/unnecessary/UnnecessaryReturnRule.java
+++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codestyle/UnnecessaryReturnRule.java
@@ -2,7 +2,7 @@
  * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
  */
 
-package net.sourceforge.pmd.lang.java.rule.unnecessary;
+package net.sourceforge.pmd.lang.java.rule.codestyle;
 
 import net.sourceforge.pmd.lang.java.ast.ASTBlock;
 import net.sourceforge.pmd.lang.java.ast.ASTBlockStatement;
@@ -13,6 +13,7 @@
 
 public class UnnecessaryReturnRule extends AbstractJavaRule {
 
+    @Override
     public Object visit(ASTMethodDeclaration node, Object data) {
 
         if (node.getResultType().isVoid()) {
@@ -21,6 +22,7 @@ public Object visit(ASTMethodDeclaration node, Object data) {
         return data;
     }
 
+    @Override
     public Object visit(ASTReturnStatement node, Object data) {
         if (node.jjtGetParent() instanceof ASTStatement && node.getNthParent(2) instanceof ASTBlockStatement
                 && node.getNthParent(3) instanceof ASTBlock && node.getNthParent(4) instanceof ASTMethodDeclaration) {
diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codestyle/VariableNamingConventionsRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codestyle/VariableNamingConventionsRule.java
new file mode 100644
index 00000000000..3e79516e91e
--- /dev/null
+++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codestyle/VariableNamingConventionsRule.java
@@ -0,0 +1,254 @@
+/**
+ * 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 java.util.Locale;
+
+import net.sourceforge.pmd.lang.ast.Node;
+import net.sourceforge.pmd.lang.java.ast.ASTAnnotationTypeDeclaration;
+import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceDeclaration;
+import net.sourceforge.pmd.lang.java.ast.ASTCompilationUnit;
+import net.sourceforge.pmd.lang.java.ast.ASTFieldDeclaration;
+import net.sourceforge.pmd.lang.java.ast.ASTFormalParameter;
+import net.sourceforge.pmd.lang.java.ast.ASTFormalParameters;
+import net.sourceforge.pmd.lang.java.ast.ASTLocalVariableDeclaration;
+import net.sourceforge.pmd.lang.java.ast.ASTMethodDeclaration;
+import net.sourceforge.pmd.lang.java.ast.ASTVariableDeclarator;
+import net.sourceforge.pmd.lang.java.ast.ASTVariableDeclaratorId;
+import net.sourceforge.pmd.lang.java.rule.AbstractJavaRule;
+import net.sourceforge.pmd.properties.BooleanProperty;
+import net.sourceforge.pmd.properties.PropertyDescriptor;
+import net.sourceforge.pmd.properties.StringMultiProperty;
+
+@Deprecated
+public class VariableNamingConventionsRule extends AbstractJavaRule {
+
+    private boolean checkMembers;
+    private boolean checkLocals;
+    private boolean checkParameters;
+    private boolean checkNativeMethodParameters;
+    private List staticPrefixes;
+    private List staticSuffixes;
+    private List memberPrefixes;
+    private List memberSuffixes;
+    private List localPrefixes;
+    private List localSuffixes;
+    private List parameterPrefixes;
+    private List parameterSuffixes;
+
+    private static final BooleanProperty CHECK_MEMBERS_DESCRIPTOR = new BooleanProperty("checkMembers",
+            "Check member variables", true, 1.0f);
+
+    private static final BooleanProperty CHECK_LOCALS_DESCRIPTOR = new BooleanProperty("checkLocals",
+            "Check local variables", true, 2.0f);
+
+    private static final BooleanProperty CHECK_PARAMETERS_DESCRIPTOR = new BooleanProperty("checkParameters",
+            "Check constructor and method parameter variables", true, 3.0f);
+
+    private static final BooleanProperty CHECK_NATIVE_METHOD_PARAMETERS_DESCRIPTOR = new BooleanProperty(
+            "checkNativeMethodParameters", "Check method parameter of native methods", true, 3.5f);
+
+    private static final StringMultiProperty STATIC_PREFIXES_DESCRIPTOR = new StringMultiProperty("staticPrefix",
+            "Static variable prefixes", new String[] { "" }, 4.0f, ',');
+
+    private static final StringMultiProperty STATIC_SUFFIXES_DESCRIPTOR = new StringMultiProperty("staticSuffix",
+            "Static variable suffixes", new String[] { "" }, 5.0f, ',');
+
+    private static final StringMultiProperty MEMBER_PREFIXES_DESCRIPTOR = new StringMultiProperty("memberPrefix",
+            "Member variable prefixes", new String[] { "" }, 6.0f, ',');
+
+    private static final StringMultiProperty MEMBER_SUFFIXES_DESCRIPTOR = new StringMultiProperty("memberSuffix",
+            "Member variable suffixes", new String[] { "" }, 7.0f, ',');
+
+    private static final StringMultiProperty LOCAL_PREFIXES_DESCRIPTOR = new StringMultiProperty("localPrefix",
+            "Local variable prefixes", new String[] { "" }, 8.0f, ',');
+
+    private static final StringMultiProperty LOCAL_SUFFIXES_DESCRIPTOR = new StringMultiProperty("localSuffix",
+            "Local variable suffixes", new String[] { "" }, 9.0f, ',');
+
+    private static final StringMultiProperty PARAMETER_PREFIXES_DESCRIPTOR = new StringMultiProperty("parameterPrefix",
+            "Method parameter variable prefixes", new String[] { "" }, 10.0f, ',');
+
+    private static final StringMultiProperty PARAMETER_SUFFIXES_DESCRIPTOR = new StringMultiProperty("parameterSuffix",
+            "Method parameter variable suffixes", new String[] { "" }, 11.0f, ',');
+
+    public VariableNamingConventionsRule() {
+        definePropertyDescriptor(CHECK_MEMBERS_DESCRIPTOR);
+        definePropertyDescriptor(CHECK_LOCALS_DESCRIPTOR);
+        definePropertyDescriptor(CHECK_PARAMETERS_DESCRIPTOR);
+        definePropertyDescriptor(CHECK_NATIVE_METHOD_PARAMETERS_DESCRIPTOR);
+        definePropertyDescriptor(STATIC_PREFIXES_DESCRIPTOR);
+        definePropertyDescriptor(STATIC_SUFFIXES_DESCRIPTOR);
+        definePropertyDescriptor(MEMBER_PREFIXES_DESCRIPTOR);
+        definePropertyDescriptor(MEMBER_SUFFIXES_DESCRIPTOR);
+        definePropertyDescriptor(LOCAL_PREFIXES_DESCRIPTOR);
+        definePropertyDescriptor(LOCAL_SUFFIXES_DESCRIPTOR);
+        definePropertyDescriptor(PARAMETER_PREFIXES_DESCRIPTOR);
+        definePropertyDescriptor(PARAMETER_SUFFIXES_DESCRIPTOR);
+    }
+
+    @Override
+    public Object visit(ASTCompilationUnit node, Object data) {
+        init();
+        return super.visit(node, data);
+    }
+
+    protected void init() {
+        checkMembers = getProperty(CHECK_MEMBERS_DESCRIPTOR);
+        checkLocals = getProperty(CHECK_LOCALS_DESCRIPTOR);
+        checkParameters = getProperty(CHECK_PARAMETERS_DESCRIPTOR);
+        checkNativeMethodParameters = getProperty(CHECK_NATIVE_METHOD_PARAMETERS_DESCRIPTOR);
+        staticPrefixes = getProperty(STATIC_PREFIXES_DESCRIPTOR);
+        staticSuffixes = getProperty(STATIC_SUFFIXES_DESCRIPTOR);
+        memberPrefixes = getProperty(MEMBER_PREFIXES_DESCRIPTOR);
+        memberSuffixes = getProperty(MEMBER_SUFFIXES_DESCRIPTOR);
+        localPrefixes = getProperty(LOCAL_PREFIXES_DESCRIPTOR);
+        localSuffixes = getProperty(LOCAL_SUFFIXES_DESCRIPTOR);
+        parameterPrefixes = getProperty(PARAMETER_PREFIXES_DESCRIPTOR);
+        parameterSuffixes = getProperty(PARAMETER_SUFFIXES_DESCRIPTOR);
+    }
+
+    @Override
+    public Object visit(ASTFieldDeclaration node, Object data) {
+        if (!checkMembers) {
+            return data;
+        }
+        boolean isStatic = node.isStatic();
+        boolean isFinal = node.isFinal();
+
+        Node type = node.jjtGetParent().jjtGetParent().jjtGetParent();
+        // Anything from an interface is necessarily static and final
+        // Anything inside an annotation type is also static and final
+        if (type instanceof ASTClassOrInterfaceDeclaration && ((ASTClassOrInterfaceDeclaration) type).isInterface()
+                || type instanceof ASTAnnotationTypeDeclaration) {
+            isStatic = true;
+            isFinal = true;
+        }
+        return checkVariableDeclarators(node.isStatic() ? staticPrefixes : memberPrefixes,
+                isStatic ? staticSuffixes : memberSuffixes, node, isStatic, isFinal, data);
+    }
+
+    @Override
+    public Object visit(ASTLocalVariableDeclaration node, Object data) {
+        if (!checkLocals) {
+            return data;
+        }
+        return checkVariableDeclarators(localPrefixes, localSuffixes, node, false, node.isFinal(), data);
+    }
+
+    @Override
+    public Object visit(ASTFormalParameters node, Object data) {
+        if (!checkParameters) {
+            return data;
+        }
+        ASTMethodDeclaration methodDeclaration = node.getFirstParentOfType(ASTMethodDeclaration.class);
+        if (!checkNativeMethodParameters && methodDeclaration.isNative()) {
+            return data;
+        }
+
+        for (ASTFormalParameter formalParameter : node.findChildrenOfType(ASTFormalParameter.class)) {
+            for (ASTVariableDeclaratorId variableDeclaratorId : formalParameter
+                    .findChildrenOfType(ASTVariableDeclaratorId.class)) {
+                checkVariableDeclaratorId(parameterPrefixes, parameterSuffixes, false, formalParameter.isFinal(),
+                        variableDeclaratorId, data);
+            }
+        }
+        return data;
+    }
+
+    private Object checkVariableDeclarators(List prefixes, List suffixes, Node root, boolean isStatic,
+            boolean isFinal, Object data) {
+        for (ASTVariableDeclarator variableDeclarator : root.findChildrenOfType(ASTVariableDeclarator.class)) {
+            for (ASTVariableDeclaratorId variableDeclaratorId : variableDeclarator
+                    .findChildrenOfType(ASTVariableDeclaratorId.class)) {
+                checkVariableDeclaratorId(prefixes, suffixes, isStatic, isFinal, variableDeclaratorId, data);
+            }
+        }
+        return data;
+    }
+
+    private Object checkVariableDeclaratorId(List prefixes, List suffixes, boolean isStatic,
+            boolean isFinal, ASTVariableDeclaratorId variableDeclaratorId, Object data) {
+
+        // Get the variable name
+        String varName = variableDeclaratorId.getImage();
+
+        // Skip serialVersionUID
+        if ("serialVersionUID".equals(varName)) {
+            return data;
+        }
+
+        // Static finals should be uppercase
+        if (isStatic && isFinal) {
+            if (!varName.equals(varName.toUpperCase(Locale.ROOT))) {
+                addViolationWithMessage(data, variableDeclaratorId,
+                        "Variables that are final and static should be all capitals, ''{0}'' is not all capitals.",
+                        new Object[] { varName });
+            }
+            return data;
+        } else if (!isFinal) {
+            String normalizedVarName = normalizeVariableName(varName, prefixes, suffixes);
+
+            if (normalizedVarName.indexOf('_') >= 0) {
+                addViolationWithMessage(data, variableDeclaratorId,
+                        "Only variables that are final should contain underscores (except for underscores in standard prefix/suffix), ''{0}'' is not final.",
+                        new Object[] { varName });
+            }
+            if (Character.isUpperCase(varName.charAt(0))) {
+                addViolationWithMessage(data, variableDeclaratorId,
+                        "Variables should start with a lowercase character, ''{0}'' starts with uppercase character.",
+                        new Object[] { varName });
+            }
+        }
+        return data;
+    }
+
+    private String normalizeVariableName(String varName, List prefixes, List suffixes) {
+        return stripSuffix(stripPrefix(varName, prefixes), suffixes);
+    }
+
+    private String stripSuffix(String varName, List suffixes) {
+        if (suffixes != null) {
+            for (String suffix : suffixes) {
+                if (varName.endsWith(suffix)) {
+                    varName = varName.substring(0, varName.length() - suffix.length());
+                    break;
+                }
+            }
+        }
+        return varName;
+    }
+
+    private String stripPrefix(String varName, List prefixes) {
+        if (prefixes != null) {
+            for (String prefix : prefixes) {
+                if (varName.startsWith(prefix)) {
+                    return varName.substring(prefix.length());
+                }
+            }
+        }
+        return varName;
+    }
+
+    public boolean hasPrefixesOrSuffixes() {
+
+        for (PropertyDescriptor desc : getPropertyDescriptors()) {
+            if (desc instanceof StringMultiProperty) {
+                List values = getProperty((StringMultiProperty) desc);
+                if (!values.isEmpty()) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    @Override
+    public String dysfunctionReason() {
+        return hasPrefixesOrSuffixes() ? null : "No prefixes or suffixes specified";
+    }
+
+}
diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/comments/AbstractCommentRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/comments/AbstractCommentRule.java
deleted file mode 100644
index 46b30a7ce1b..00000000000
--- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/comments/AbstractCommentRule.java
+++ /dev/null
@@ -1,231 +0,0 @@
-/**
- * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
- */
-
-package net.sourceforge.pmd.lang.java.rule.comments;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-import java.util.Map.Entry;
-import java.util.SortedMap;
-import java.util.TreeMap;
-
-import org.apache.commons.lang3.StringUtils;
-
-import net.sourceforge.pmd.lang.ast.Node;
-import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceBody;
-import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceDeclaration;
-import net.sourceforge.pmd.lang.java.ast.ASTCompilationUnit;
-import net.sourceforge.pmd.lang.java.ast.ASTConstructorDeclaration;
-import net.sourceforge.pmd.lang.java.ast.ASTEnumDeclaration;
-import net.sourceforge.pmd.lang.java.ast.ASTFieldDeclaration;
-import net.sourceforge.pmd.lang.java.ast.ASTMethodDeclaration;
-import net.sourceforge.pmd.lang.java.ast.AbstractJavaAccessNode;
-import net.sourceforge.pmd.lang.java.ast.AbstractJavaAccessTypeNode;
-import net.sourceforge.pmd.lang.java.ast.Comment;
-import net.sourceforge.pmd.lang.java.ast.FormalComment;
-import net.sourceforge.pmd.lang.java.ast.MultiLineComment;
-import net.sourceforge.pmd.lang.java.ast.SingleLineComment;
-import net.sourceforge.pmd.lang.java.rule.AbstractJavaRule;
-
-/**
- * 
- * @author Brian Remedios
- */
-public abstract class AbstractCommentRule extends AbstractJavaRule {
-
-    protected AbstractCommentRule() {
-    }
-
-    protected List tagsIndicesIn(String comments) {
-
-        int atPos = comments.indexOf('@');
-        if (atPos < 0) {
-            return Collections.emptyList();
-        }
-
-        List ints = new ArrayList<>();
-        ints.add(atPos);
-
-        atPos = comments.indexOf('@', atPos + 1);
-        while (atPos >= 0) {
-            ints.add(atPos);
-            atPos = comments.indexOf('@', atPos + 1);
-        }
-
-        return ints;
-    }
-
-    protected String filteredCommentIn(Comment comment) {
-
-        String trimmed = comment.getImage().trim();
-
-        if (comment instanceof SingleLineComment) {
-            return singleLineIn(trimmed);
-        }
-        if (comment instanceof MultiLineComment) {
-            return multiLinesIn(trimmed);
-        }
-        if (comment instanceof FormalComment) {
-            return formalLinesIn(trimmed);
-        }
-
-        return trimmed; // should never reach here
-    }
-
-    private String singleLineIn(String comment) {
-
-        if (comment.startsWith("//")) {
-            return comment.substring(2);
-        }
-
-        return comment;
-    }
-
-    private static String asSingleString(List lines) {
-
-        StringBuilder sb = new StringBuilder();
-        for (String line : lines) {
-            if (StringUtils.isBlank(line)) {
-                continue;
-            }
-            sb.append(line).append('\n');
-        }
-
-        return sb.toString().trim();
-    }
-
-    private static String multiLinesIn(String comment) {
-
-        String[] lines = comment.split("\n");
-        List filteredLines = new ArrayList<>(lines.length);
-
-        for (String rawLine : lines) {
-            String line = rawLine.trim();
-
-            if (line.endsWith("*/")) {
-                int end = line.length() - 2;
-                int start = line.startsWith("/*") ? 2 : 0;
-                filteredLines.add(line.substring(start, end));
-                continue;
-            }
-
-            if (line.length() > 0 && line.charAt(0) == '*') {
-                filteredLines.add(line.substring(1));
-                continue;
-            }
-
-            if (line.startsWith("/*")) {
-                filteredLines.add(line.substring(2));
-                continue;
-            }
-
-        }
-
-        return asSingleString(filteredLines);
-    }
-
-    private String formalLinesIn(String comment) {
-
-        String[] lines = comment.split("\n");
-        List filteredLines = new ArrayList<>(lines.length);
-
-        for (String origLine : lines) {
-            String line = origLine.trim();
-
-            if (line.endsWith("*/")) {
-                filteredLines.add(line.substring(0, line.length() - 2));
-                continue;
-            }
-
-            if (line.length() > 0 && line.charAt(0) == '*') {
-                filteredLines.add(line.substring(1));
-                continue;
-            }
-            if (line.startsWith("/**")) {
-                filteredLines.add(line.substring(3));
-                continue;
-            }
-
-        }
-
-        return asSingleString(filteredLines);
-    }
-
-    protected void assignCommentsToDeclarations(ASTCompilationUnit cUnit) {
-
-        SortedMap itemsByLineNumber = orderedCommentsAndDeclarations(cUnit);
-        FormalComment lastComment = null;
-        AbstractJavaAccessNode lastNode = null;
-
-        for (Entry entry : itemsByLineNumber.entrySet()) {
-            Node value = entry.getValue();
-
-            if (value instanceof AbstractJavaAccessNode) {
-                AbstractJavaAccessNode node = (AbstractJavaAccessNode) value;
-
-                // maybe the last comment is within the last node
-                if (lastComment != null && isCommentNotWithin(lastComment, lastNode, node)
-                        && isCommentBefore(lastComment, node)) {
-                    node.comment(lastComment);
-                    lastComment = null;
-                }
-                if (!(node instanceof AbstractJavaAccessTypeNode)) {
-                    lastNode = node;
-                }
-            } else if (value instanceof FormalComment) {
-                lastComment = (FormalComment) value;
-            }
-        }
-    }
-
-    private boolean isCommentNotWithin(FormalComment n1, Node n2, Node node) {
-        if (n1 == null || n2 == null || node == null) {
-            return true;
-        }
-        boolean isNotWithinNode2 = !(n1.getEndLine() < n2.getEndLine()
-                || n1.getEndLine() == n2.getEndLine() && n1.getEndColumn() < n2.getEndColumn());
-        boolean isNotSameClass = node.getFirstParentOfType(ASTClassOrInterfaceBody.class) != n2
-                .getFirstParentOfType(ASTClassOrInterfaceBody.class);
-        boolean isNodeWithinNode2 = (node.getEndLine() < n2.getEndLine()
-                || node.getEndLine() == n2.getEndLine() && node.getEndColumn() < n2.getEndColumn());
-        return isNotWithinNode2 || isNotSameClass || isNodeWithinNode2;
-    }
-
-    private boolean isCommentBefore(FormalComment n1, Node n2) {
-        return n1.getEndLine() < n2.getBeginLine()
-                || n1.getEndLine() == n2.getBeginLine() && n1.getEndColumn() < n2.getBeginColumn();
-    }
-
-    protected SortedMap orderedCommentsAndDeclarations(ASTCompilationUnit cUnit) {
-
-        SortedMap itemsByLineNumber = new TreeMap<>();
-
-        List packageDecl = cUnit
-                .findDescendantsOfType(ASTClassOrInterfaceDeclaration.class);
-        addDeclarations(itemsByLineNumber, packageDecl);
-
-        addDeclarations(itemsByLineNumber, cUnit.getComments());
-
-        List fields = cUnit.findDescendantsOfType(ASTFieldDeclaration.class);
-        addDeclarations(itemsByLineNumber, fields);
-
-        List methods = cUnit.findDescendantsOfType(ASTMethodDeclaration.class);
-        addDeclarations(itemsByLineNumber, methods);
-
-        List constructors = cUnit.findDescendantsOfType(ASTConstructorDeclaration.class);
-        addDeclarations(itemsByLineNumber, constructors);
-
-        List enumDecl = cUnit.findDescendantsOfType(ASTEnumDeclaration.class);
-        addDeclarations(itemsByLineNumber, enumDecl);
-
-        return itemsByLineNumber;
-    }
-
-    private void addDeclarations(SortedMap map, List nodes) {
-        for (Node node : nodes) {
-            map.put((node.getBeginLine() << 16) + node.getBeginColumn(), node);
-        }
-    }
-}
diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/comments/CommentDefaultAccessModifierRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/comments/CommentDefaultAccessModifierRule.java
deleted file mode 100644
index 2e674edd644..00000000000
--- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/comments/CommentDefaultAccessModifierRule.java
+++ /dev/null
@@ -1,133 +0,0 @@
-/**
- * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
- */
-
-package net.sourceforge.pmd.lang.java.rule.comments;
-
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-
-import net.sourceforge.pmd.lang.java.ast.ASTAnnotation;
-import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceBodyDeclaration;
-import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceDeclaration;
-import net.sourceforge.pmd.lang.java.ast.ASTCompilationUnit;
-import net.sourceforge.pmd.lang.java.ast.ASTConstructorDeclaration;
-import net.sourceforge.pmd.lang.java.ast.ASTFieldDeclaration;
-import net.sourceforge.pmd.lang.java.ast.ASTMethodDeclaration;
-import net.sourceforge.pmd.lang.java.ast.ASTMethodDeclarator;
-import net.sourceforge.pmd.lang.java.ast.ASTName;
-import net.sourceforge.pmd.lang.java.ast.ASTVariableDeclaratorId;
-import net.sourceforge.pmd.lang.java.ast.AbstractJavaAccessNode;
-import net.sourceforge.pmd.lang.java.ast.Comment;
-import net.sourceforge.pmd.properties.StringProperty;
-
-/**
- * Check for Methods, Fields and Nested Classes that have a default access
- * modifier
- *
- * @author Damián Techeira
- */
-public class CommentDefaultAccessModifierRule extends AbstractCommentRule {
-
-    private static final StringProperty REGEX_DESCRIPTOR = new StringProperty("regex", "Regular expression", "", 1.0f);
-    private static final String MESSAGE = "To avoid mistakes add a comment "
-            + "at the beginning of the %s %s if you want a default access modifier";
-    private final Set interestingLineNumberComments = new HashSet();
-
-    public CommentDefaultAccessModifierRule() {
-        definePropertyDescriptor(REGEX_DESCRIPTOR);
-    }
-
-    public CommentDefaultAccessModifierRule(final String regex) {
-        this();
-        setRegex(regex);
-    }
-
-    public void setRegex(final String regex) {
-        setProperty(CommentDefaultAccessModifierRule.REGEX_DESCRIPTOR, regex);
-    }
-
-    @Override
-    public Object visit(final ASTCompilationUnit node, final Object data) {
-        interestingLineNumberComments.clear();
-        final List comments = node.getComments();
-        for (final Comment comment : comments) {
-            if (comment.getImage().matches(getProperty(REGEX_DESCRIPTOR).trim())) {
-                interestingLineNumberComments.add(comment.getBeginLine());
-            }
-        }
-        return super.visit(node, data);
-    }
-
-    @Override
-    public Object visit(final ASTMethodDeclaration decl, final Object data) {
-        if (shouldReport(decl)) {
-            addViolationWithMessage(data, decl,
-                    String.format(MESSAGE, decl.getFirstChildOfType(ASTMethodDeclarator.class).getImage(), "method"));
-        }
-        return super.visit(decl, data);
-    }
-
-    @Override
-    public Object visit(final ASTFieldDeclaration decl, final Object data) {
-        if (shouldReport(decl)) {
-            addViolationWithMessage(data, decl, String.format(MESSAGE,
-                    decl.getFirstDescendantOfType(ASTVariableDeclaratorId.class).getImage(), "field"));
-        }
-        return super.visit(decl, data);
-    }
-
-    @Override
-    public Object visit(final ASTClassOrInterfaceDeclaration decl, final Object data) {
-        // check for nested classes
-        if (decl.isNested() && shouldReport(decl)) {
-            addViolationWithMessage(data, decl, String.format(MESSAGE, decl.getImage(), "nested class"));
-        }
-        return super.visit(decl, data);
-    }
-
-    @Override
-    public Object visit(final ASTConstructorDeclaration decl, Object data) {
-        if (shouldReport(decl)) {
-            addViolationWithMessage(data, decl, String.format(MESSAGE, decl.getImage(), "constructor"));
-        }
-        return super.visit(decl, data);
-    }
-
-    private boolean shouldReport(final AbstractJavaAccessNode decl) {
-        List parentClassOrInterface = decl
-                .getParentsOfType(ASTClassOrInterfaceDeclaration.class);
-        // ignore if is a Interface
-        return (!parentClassOrInterface.isEmpty() && !parentClassOrInterface.get(0).isInterface())
-                // check if the field/method/nested class has a default access
-                // modifier
-                && decl.isPackagePrivate()
-                // if is a default access modifier check if there is a comment
-                // in this line
-                && !interestingLineNumberComments.contains(decl.getBeginLine())
-                // that it is not annotated with @VisibleForTesting
-                && hasNoVisibleForTestingAnnotation(decl);
-    }
-
-    private boolean hasNoVisibleForTestingAnnotation(AbstractJavaAccessNode decl) {
-        boolean result = true;
-        ASTClassOrInterfaceBodyDeclaration parent = decl.getFirstParentOfType(ASTClassOrInterfaceBodyDeclaration.class);
-        if (parent != null) {
-            List annotations = parent.findChildrenOfType(ASTAnnotation.class);
-            for (ASTAnnotation annotation : annotations) {
-                List names = annotation.findDescendantsOfType(ASTName.class);
-                for (ASTName name : names) {
-                    if (name.hasImageEqualTo("VisibleForTesting")) {
-                        result = false;
-                        break;
-                    }
-                }
-                if (result == false) {
-                    break;
-                }
-            }
-        }
-        return result;
-    }
-}
diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/comments/CommentRequiredRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/comments/CommentRequiredRule.java
deleted file mode 100644
index b9fdeb0bdee..00000000000
--- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/comments/CommentRequiredRule.java
+++ /dev/null
@@ -1,272 +0,0 @@
-/**
- * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
- */
-
-package net.sourceforge.pmd.lang.java.rule.comments;
-
-import java.util.Arrays;
-
-import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceDeclaration;
-import net.sourceforge.pmd.lang.java.ast.ASTCompilationUnit;
-import net.sourceforge.pmd.lang.java.ast.ASTConstructorDeclaration;
-import net.sourceforge.pmd.lang.java.ast.ASTEnumDeclaration;
-import net.sourceforge.pmd.lang.java.ast.ASTFieldDeclaration;
-import net.sourceforge.pmd.lang.java.ast.ASTMethodDeclaration;
-import net.sourceforge.pmd.lang.java.ast.AbstractJavaAccessNode;
-import net.sourceforge.pmd.properties.EnumeratedProperty;
-import net.sourceforge.pmd.properties.PropertySource;
-
-/**
- * @author Brian Remedios
- */
-public class CommentRequiredRule extends AbstractCommentRule {
-
-    enum CommentRequirement {
-        Required("Required"), Ignored("Ignored"), Unwanted("Unwanted");
-
-        private final String label;
-
-        CommentRequirement(String theLabel) {
-            label = theLabel;
-        }
-
-        public static String[] labels() {
-            String[] labels = new String[values().length];
-            int i = 0;
-            for (CommentRequirement requirement : values()) {
-                labels[i++] = requirement.label;
-            }
-            return labels;
-        }
-    }
-
-    public static final EnumeratedProperty HEADER_CMT_REQUIREMENT_DESCRIPTOR = new EnumeratedProperty<>(
-            "headerCommentRequirement",
-            "Header comments. Possible values: " + Arrays.toString(CommentRequirement.values()),
-            CommentRequirement.labels(), CommentRequirement.values(), 0, CommentRequirement.class, 1.0f);
-
-    public static final EnumeratedProperty FIELD_CMT_REQUIREMENT_DESCRIPTOR = new EnumeratedProperty<>(
-            "fieldCommentRequirement",
-            "Field comments. Possible values: " + Arrays.toString(CommentRequirement.values()),
-            CommentRequirement.labels(), CommentRequirement.values(), 0, CommentRequirement.class, 2.0f);
-
-    public static final EnumeratedProperty PUB_METHOD_CMT_REQUIREMENT_DESCRIPTOR = new EnumeratedProperty<>(
-            "publicMethodCommentRequirement",
-            "Public method and constructor comments. Possible values: " + Arrays.toString(CommentRequirement.values()),
-            CommentRequirement.labels(), CommentRequirement.values(), 0, CommentRequirement.class, 3.0f);
-
-    public static final EnumeratedProperty PROT_METHOD_CMT_REQUIREMENT_DESCRIPTOR = new EnumeratedProperty<>(
-            "protectedMethodCommentRequirement",
-            "Protected method constructor comments. Possible values: " + Arrays.toString(CommentRequirement.values()),
-            CommentRequirement.labels(), CommentRequirement.values(), 0, CommentRequirement.class, 4.0f);
-
-    public static final EnumeratedProperty ENUM_CMT_REQUIREMENT_DESCRIPTOR = new EnumeratedProperty<>(
-            "enumCommentRequirement", "Enum comments. Possible values: " + Arrays.toString(CommentRequirement.values()),
-            CommentRequirement.labels(), CommentRequirement.values(), 0, CommentRequirement.class, 5.0f);
-
-    public static final EnumeratedProperty SERIAL_VERSION_UID_CMT_REQUIREMENT_DESCRIPTOR = new EnumeratedProperty<>(
-        "serialVersionUIDCommentRequired",
-        "serial version UID commts. Possible values: " + Arrays.toString(CommentRequirement.values()),
-        CommentRequirement.labels(), CommentRequirement.values(), 1, CommentRequirement.class, 6.0f);
-
-    public CommentRequiredRule() {
-        definePropertyDescriptor(HEADER_CMT_REQUIREMENT_DESCRIPTOR);
-        definePropertyDescriptor(FIELD_CMT_REQUIREMENT_DESCRIPTOR);
-        definePropertyDescriptor(PUB_METHOD_CMT_REQUIREMENT_DESCRIPTOR);
-        definePropertyDescriptor(PROT_METHOD_CMT_REQUIREMENT_DESCRIPTOR);
-        definePropertyDescriptor(ENUM_CMT_REQUIREMENT_DESCRIPTOR);
-        definePropertyDescriptor(SERIAL_VERSION_UID_CMT_REQUIREMENT_DESCRIPTOR);
-    }
-
-    private CommentRequirement getCommentRequirement(String label) {
-        if (CommentRequirement.Ignored.label.equals(label)) {
-            return CommentRequirement.Ignored;
-        } else if (CommentRequirement.Required.label.equals(label)) {
-            return CommentRequirement.Required;
-        } else if (CommentRequirement.Unwanted.label.equals(label)) {
-            return CommentRequirement.Unwanted;
-        } else {
-            return null;
-        }
-    }
-
-    @Override
-    public Object visit(ASTClassOrInterfaceDeclaration decl, Object data) {
-        CommentRequirement headerRequirement = getCommentRequirement(
-                getProperty(HEADER_CMT_REQUIREMENT_DESCRIPTOR).toString());
-
-        if (headerRequirement != CommentRequirement.Ignored) {
-            if (headerRequirement == CommentRequirement.Required) {
-                if (decl.comment() == null) {
-                    addViolationWithMessage(data, decl,
-                            HEADER_CMT_REQUIREMENT_DESCRIPTOR.name() + " " + CommentRequirement.Required,
-                            decl.getBeginLine(), decl.getEndLine());
-                }
-            } else {
-                if (decl.comment() != null) {
-                    addViolationWithMessage(data, decl,
-                            HEADER_CMT_REQUIREMENT_DESCRIPTOR.name() + " " + CommentRequirement.Unwanted,
-                            decl.getBeginLine(), decl.getEndLine());
-                }
-            }
-        }
-
-        return super.visit(decl, data);
-    }
-
-    @Override
-    public Object visit(ASTConstructorDeclaration decl, Object data) {
-        checkComment(decl, data);
-        return super.visit(decl, data);
-    }
-
-    @Override
-    public Object visit(ASTMethodDeclaration decl, Object data) {
-        checkComment(decl, data);
-        return super.visit(decl, data);
-    }
-
-    private void checkComment(AbstractJavaAccessNode decl, Object data) {
-        CommentRequirement pubMethodRequirement = getCommentRequirement(
-                getProperty(PUB_METHOD_CMT_REQUIREMENT_DESCRIPTOR).toString());
-        CommentRequirement protMethodRequirement = getCommentRequirement(
-                getProperty(PROT_METHOD_CMT_REQUIREMENT_DESCRIPTOR).toString());
-
-        if (decl.isPublic()) {
-            if (pubMethodRequirement != CommentRequirement.Ignored) {
-                if (pubMethodRequirement == CommentRequirement.Required) {
-                    if (decl.comment() == null) {
-                        addViolationWithMessage(data, decl,
-                                PUB_METHOD_CMT_REQUIREMENT_DESCRIPTOR.name() + " " + CommentRequirement.Required,
-                                decl.getBeginLine(), decl.getEndLine());
-                    }
-                } else {
-                    if (decl.comment() != null) {
-                        addViolationWithMessage(data, decl,
-                                PUB_METHOD_CMT_REQUIREMENT_DESCRIPTOR.name() + " " + CommentRequirement.Unwanted,
-                                decl.getBeginLine(), decl.getEndLine());
-                    }
-                }
-            }
-        } else if (decl.isProtected()) {
-            if (protMethodRequirement != CommentRequirement.Ignored) {
-                if (protMethodRequirement == CommentRequirement.Required) {
-                    if (decl.comment() == null) {
-                        addViolationWithMessage(data, decl,
-                                PROT_METHOD_CMT_REQUIREMENT_DESCRIPTOR.name() + " " + CommentRequirement.Required,
-                                decl.getBeginLine(), decl.getEndLine());
-                    }
-                } else {
-                    if (decl.comment() != null) {
-                        addViolationWithMessage(data, decl,
-                                PROT_METHOD_CMT_REQUIREMENT_DESCRIPTOR.name() + " " + CommentRequirement.Unwanted,
-                                decl.getBeginLine(), decl.getEndLine());
-                    }
-                }
-            }
-        }
-    }
-
-    @Override
-    public Object visit(ASTFieldDeclaration decl, Object data) {
-        CommentRequirement fieldRequirement = getCommentRequirement(
-                getProperty(FIELD_CMT_REQUIREMENT_DESCRIPTOR).toString());
-
-        if (fieldRequirement != CommentRequirement.Ignored) {
-            if (isSerialVersionUID(decl)) {
-                checkSerialVersionUID(decl, data, fieldRequirement);
-            } else if (fieldRequirement == CommentRequirement.Required) {
-                if (decl.comment() == null) {
-                    addViolationWithMessage(data, decl,
-                            FIELD_CMT_REQUIREMENT_DESCRIPTOR.name() + " " + CommentRequirement.Required,
-                            decl.getBeginLine(), decl.getEndLine());
-                }
-            } else {
-                if (decl.comment() != null) {
-                    addViolationWithMessage(data, decl,
-                            FIELD_CMT_REQUIREMENT_DESCRIPTOR.name() + " " + CommentRequirement.Unwanted,
-                            decl.getBeginLine(), decl.getEndLine());
-                }
-            }
-        }
-
-        return super.visit(decl, data);
-    }
-
-    private void checkSerialVersionUID(ASTFieldDeclaration decl, Object data, CommentRequirement fieldRequirement) {
-        CommentRequirement serialVersionUIDReq = getCommentRequirement(
-                getProperty(SERIAL_VERSION_UID_CMT_REQUIREMENT_DESCRIPTOR).toString());
-        if (serialVersionUIDReq != CommentRequirement.Ignored) {
-            if (fieldRequirement == CommentRequirement.Required) {
-                if (decl.comment() == null) {
-                    addViolationWithMessage(data, decl,
-                            SERIAL_VERSION_UID_CMT_REQUIREMENT_DESCRIPTOR.name() + " " + CommentRequirement.Required,
-                            decl.getBeginLine(), decl.getEndLine());
-                }
-            } else {
-                if (decl.comment() != null) {
-                    addViolationWithMessage(data, decl,
-                            SERIAL_VERSION_UID_CMT_REQUIREMENT_DESCRIPTOR.name() + " " + CommentRequirement.Unwanted,
-                            decl.getBeginLine(), decl.getEndLine());
-                }
-            }
-        }
-    }
-
-    private boolean isSerialVersionUID(ASTFieldDeclaration field) {
-        if ("serialVersionUID".equals(field.getVariableName()) && field.isStatic() && field.isFinal()
-                && field.getType() == long.class) {
-            return true;
-        }
-        return false;
-    }
-
-    @Override
-    public Object visit(ASTEnumDeclaration decl, Object data) {
-
-        CommentRequirement enumRequirement = getCommentRequirement(
-                getProperty(ENUM_CMT_REQUIREMENT_DESCRIPTOR).toString());
-
-        if (enumRequirement != CommentRequirement.Ignored) {
-            if (enumRequirement == CommentRequirement.Required) {
-                if (decl.comment() == null) {
-                    addViolationWithMessage(data, decl,
-                            ENUM_CMT_REQUIREMENT_DESCRIPTOR.name() + " " + CommentRequirement.Required,
-                            decl.getBeginLine(), decl.getEndLine());
-                }
-            } else {
-                if (decl.comment() != null) {
-                    addViolationWithMessage(data, decl,
-                            ENUM_CMT_REQUIREMENT_DESCRIPTOR.name() + " " + CommentRequirement.Unwanted,
-                            decl.getBeginLine(), decl.getEndLine());
-                }
-            }
-        }
-
-        return super.visit(decl, data);
-    }
-
-    @Override
-    public Object visit(ASTCompilationUnit cUnit, Object data) {
-        assignCommentsToDeclarations(cUnit);
-
-        return super.visit(cUnit, data);
-    }
-
-    public boolean allCommentsAreIgnored() {
-
-        return getProperty(HEADER_CMT_REQUIREMENT_DESCRIPTOR) == CommentRequirement.Ignored
-                && getProperty(FIELD_CMT_REQUIREMENT_DESCRIPTOR) == CommentRequirement.Ignored
-                && getProperty(PUB_METHOD_CMT_REQUIREMENT_DESCRIPTOR) == CommentRequirement.Ignored
-                && getProperty(PROT_METHOD_CMT_REQUIREMENT_DESCRIPTOR) == CommentRequirement.Ignored
-                && getProperty(SERIAL_VERSION_UID_CMT_REQUIREMENT_DESCRIPTOR) == CommentRequirement.Ignored;
-    }
-
-    /**
-     * @see PropertySource#dysfunctionReason()
-     */
-    @Override
-    public String dysfunctionReason() {
-        return allCommentsAreIgnored() ? "All comment types are ignored" : null;
-    }
-}
diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/comments/JavadocRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/comments/JavadocRule.java
deleted file mode 100644
index 581d0060385..00000000000
--- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/comments/JavadocRule.java
+++ /dev/null
@@ -1,9 +0,0 @@
-/**
- * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
- */
-
-package net.sourceforge.pmd.lang.java.rule.comments;
-
-public class JavadocRule extends AbstractCommentRule {
-
-}
diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/controversial/NullAssignmentRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/controversial/NullAssignmentRule.java
deleted file mode 100644
index 439f3fc3ebe..00000000000
--- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/controversial/NullAssignmentRule.java
+++ /dev/null
@@ -1,61 +0,0 @@
-/**
- * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
- */
-
-package net.sourceforge.pmd.lang.java.rule.controversial;
-
-import net.sourceforge.pmd.lang.java.ast.ASTAssignmentOperator;
-import net.sourceforge.pmd.lang.java.ast.ASTConditionalExpression;
-import net.sourceforge.pmd.lang.java.ast.ASTEqualityExpression;
-import net.sourceforge.pmd.lang.java.ast.ASTExpression;
-import net.sourceforge.pmd.lang.java.ast.ASTName;
-import net.sourceforge.pmd.lang.java.ast.ASTNullLiteral;
-import net.sourceforge.pmd.lang.java.ast.ASTStatementExpression;
-import net.sourceforge.pmd.lang.java.ast.AccessNode;
-import net.sourceforge.pmd.lang.java.rule.AbstractJavaRule;
-import net.sourceforge.pmd.lang.java.symboltable.VariableNameDeclaration;
-
-// TODO - should check that this is not the first assignment.  e.g., this is OK:
-// Object x;
-// x = null;
-public class NullAssignmentRule extends AbstractJavaRule {
-
-    @Override
-    public Object visit(ASTNullLiteral node, Object data) {
-
-        if (node.getNthParent(5) instanceof ASTStatementExpression) {
-            ASTStatementExpression n = (ASTStatementExpression) node.getNthParent(5);
-
-            if (isAssignmentToFinalField(n)) {
-                return data;
-            }
-
-            if (n.jjtGetNumChildren() > 2 && n.jjtGetChild(1) instanceof ASTAssignmentOperator) {
-                addViolation(data, node);
-            }
-        } else if (node.getNthParent(4) instanceof ASTConditionalExpression) {
-            // "false" expression of ternary
-            if (isBadTernary((ASTConditionalExpression) node.getNthParent(4))) {
-                addViolation(data, node);
-            }
-        } else if (node.getNthParent(5) instanceof ASTConditionalExpression
-                && node.getNthParent(4) instanceof ASTExpression) {
-            // "true" expression of ternary
-            if (isBadTernary((ASTConditionalExpression) node.getNthParent(5))) {
-                addViolation(data, node);
-            }
-        }
-
-        return data;
-    }
-
-    private boolean isAssignmentToFinalField(ASTStatementExpression n) {
-        ASTName name = n.getFirstDescendantOfType(ASTName.class);
-        return name != null && name.getNameDeclaration() instanceof VariableNameDeclaration
-                && ((AccessNode) ((VariableNameDeclaration) name.getNameDeclaration()).getAccessNodeParent()).isFinal();
-    }
-
-    private boolean isBadTernary(ASTConditionalExpression n) {
-        return n.isTernary() && !(n.jjtGetChild(0) instanceof ASTEqualityExpression);
-    }
-}
diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/controversial/OnlyOneReturnRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/controversial/OnlyOneReturnRule.java
deleted file mode 100644
index 754680c2bda..00000000000
--- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/controversial/OnlyOneReturnRule.java
+++ /dev/null
@@ -1,68 +0,0 @@
-/**
- * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
- */
-
-package net.sourceforge.pmd.lang.java.rule.controversial;
-
-import java.util.ArrayList;
-import java.util.Iterator;
-import java.util.List;
-
-import net.sourceforge.pmd.lang.ast.Node;
-import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceDeclaration;
-import net.sourceforge.pmd.lang.java.ast.ASTLambdaExpression;
-import net.sourceforge.pmd.lang.java.ast.ASTMethodDeclaration;
-import net.sourceforge.pmd.lang.java.ast.ASTReturnStatement;
-import net.sourceforge.pmd.lang.java.rule.AbstractJavaRule;
-
-public class OnlyOneReturnRule extends AbstractJavaRule {
-
-    @Override
-    public Object visit(ASTClassOrInterfaceDeclaration node, Object data) {
-        if (node.isInterface()) {
-            return data;
-        }
-        return super.visit(node, data);
-    }
-
-    @Override
-    public Object visit(ASTMethodDeclaration node, Object data) {
-        if (node.isAbstract()) {
-            return data;
-        }
-
-        List returnNodes = new ArrayList<>();
-        node.findDescendantsOfType(ASTReturnStatement.class, returnNodes, false);
-        returnNodes = filterLambdaExpressions(returnNodes);
-
-        if (returnNodes.size() > 1) {
-            for (Iterator i = returnNodes.iterator(); i.hasNext();) {
-                Node problem = i.next();
-                // skip the last one, it's OK
-                if (!i.hasNext()) {
-                    continue;
-                }
-                addViolation(data, problem);
-            }
-        }
-        return data;
-    }
-
-    /**
-     * Checks whether the return statement is inside a lambda expression, and if
-     * so, this return statement is removed.
-     * 
-     * @param returnNodes
-     *            all the return statements inside the method
-     * @return all return statements, that are NOT within a lambda expression.
-     */
-    private List filterLambdaExpressions(List returnNodes) {
-        List filtered = new ArrayList<>();
-        for (ASTReturnStatement ret : returnNodes) {
-            if (ret.getFirstParentOfType(ASTLambdaExpression.class) == null) {
-                filtered.add(ret);
-            }
-        }
-        return filtered;
-    }
-}
diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/AbstractNcssCountRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/AbstractNcssCountRule.java
new file mode 100644
index 00000000000..06c6469b976
--- /dev/null
+++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/AbstractNcssCountRule.java
@@ -0,0 +1,199 @@
+/**
+ * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
+ */
+
+package net.sourceforge.pmd.lang.java.rule.design;
+
+import net.sourceforge.pmd.lang.ast.Node;
+import net.sourceforge.pmd.lang.java.ast.ASTBreakStatement;
+import net.sourceforge.pmd.lang.java.ast.ASTCatchStatement;
+import net.sourceforge.pmd.lang.java.ast.ASTContinueStatement;
+import net.sourceforge.pmd.lang.java.ast.ASTDoStatement;
+import net.sourceforge.pmd.lang.java.ast.ASTFinallyStatement;
+import net.sourceforge.pmd.lang.java.ast.ASTForInit;
+import net.sourceforge.pmd.lang.java.ast.ASTForStatement;
+import net.sourceforge.pmd.lang.java.ast.ASTIfStatement;
+import net.sourceforge.pmd.lang.java.ast.ASTLabeledStatement;
+import net.sourceforge.pmd.lang.java.ast.ASTLocalVariableDeclaration;
+import net.sourceforge.pmd.lang.java.ast.ASTReturnStatement;
+import net.sourceforge.pmd.lang.java.ast.ASTStatementExpression;
+import net.sourceforge.pmd.lang.java.ast.ASTStatementExpressionList;
+import net.sourceforge.pmd.lang.java.ast.ASTSwitchLabel;
+import net.sourceforge.pmd.lang.java.ast.ASTSwitchStatement;
+import net.sourceforge.pmd.lang.java.ast.ASTSynchronizedStatement;
+import net.sourceforge.pmd.lang.java.ast.ASTThrowStatement;
+import net.sourceforge.pmd.lang.java.ast.ASTWhileStatement;
+import net.sourceforge.pmd.lang.java.ast.JavaNode;
+import net.sourceforge.pmd.lang.java.rule.AbstractStatisticalJavaRule;
+import net.sourceforge.pmd.stat.DataPoint;
+import net.sourceforge.pmd.util.NumericConstants;
+
+/**
+ * Abstract superclass for NCSS counting methods. Counts tokens according to
+ * JavaNCSS rules.
+ * 
+ * @author Jason Bennett
+ */
+public abstract class AbstractNcssCountRule extends AbstractStatisticalJavaRule {
+
+    private Class nodeClass;
+
+    /**
+     * Count the nodes of the given type using NCSS rules.
+     * 
+     * @param nodeClass
+     *            class of node to count
+     */
+    protected AbstractNcssCountRule(Class nodeClass) {
+        this.nodeClass = nodeClass;
+    }
+
+    @Override
+    public Object visit(JavaNode node, Object data) {
+        int numNodes = 0;
+
+        for (int i = 0; i < node.jjtGetNumChildren(); i++) {
+            JavaNode n = (JavaNode) node.jjtGetChild(i);
+            Integer treeSize = (Integer) n.jjtAccept(this, data);
+            numNodes += treeSize.intValue();
+        }
+
+        if (this.nodeClass.isInstance(node)) {
+            // Add 1 to account for base node
+            numNodes++;
+            DataPoint point = new DataPoint();
+            point.setNode(node);
+            point.setScore(1.0 * numNodes);
+            point.setMessage(getMessage());
+            addDataPoint(point);
+        }
+
+        return Integer.valueOf(numNodes);
+    }
+
+    /**
+     * Count the number of children of the given Java node. Adds one to count
+     * the node itself.
+     * 
+     * @param node
+     *            java node having children counted
+     * @param data
+     *            node data
+     * @return count of the number of children of the node, plus one
+     */
+    protected Integer countNodeChildren(Node node, Object data) {
+        Integer nodeCount = null;
+        int lineCount = 0;
+        for (int i = 0; i < node.jjtGetNumChildren(); i++) {
+            nodeCount = (Integer) ((JavaNode) node.jjtGetChild(i)).jjtAccept(this, data);
+            lineCount += nodeCount.intValue();
+        }
+        return ++lineCount;
+    }
+
+    @Override
+    public Object visit(ASTForStatement node, Object data) {
+        return countNodeChildren(node, data);
+    }
+
+    @Override
+    public Object visit(ASTDoStatement node, Object data) {
+        return countNodeChildren(node, data);
+    }
+
+    @Override
+    public Object visit(ASTIfStatement node, Object data) {
+
+        Integer lineCount = countNodeChildren(node, data);
+
+        if (node.hasElse()) {
+            lineCount++;
+        }
+
+        return lineCount;
+    }
+
+    @Override
+    public Object visit(ASTWhileStatement node, Object data) {
+        return countNodeChildren(node, data);
+    }
+
+    @Override
+    public Object visit(ASTBreakStatement node, Object data) {
+        return NumericConstants.ONE;
+    }
+
+    @Override
+    public Object visit(ASTCatchStatement node, Object data) {
+        return countNodeChildren(node, data);
+    }
+
+    @Override
+    public Object visit(ASTContinueStatement node, Object data) {
+        return NumericConstants.ONE;
+    }
+
+    @Override
+    public Object visit(ASTFinallyStatement node, Object data) {
+        return countNodeChildren(node, data);
+    }
+
+    @Override
+    public Object visit(ASTReturnStatement node, Object data) {
+        return countNodeChildren(node, data);
+    }
+
+    @Override
+    public Object visit(ASTSwitchStatement node, Object data) {
+        return countNodeChildren(node, data);
+    }
+
+    @Override
+    public Object visit(ASTSynchronizedStatement node, Object data) {
+        return countNodeChildren(node, data);
+    }
+
+    @Override
+    public Object visit(ASTThrowStatement node, Object data) {
+        return NumericConstants.ONE;
+    }
+
+    @Override
+    public Object visit(ASTStatementExpression node, Object data) {
+
+        // "For" update expressions do not count as separate lines of code
+        if (node.jjtGetParent() instanceof ASTStatementExpressionList) {
+            return NumericConstants.ZERO;
+        }
+
+        return NumericConstants.ONE;
+    }
+
+    @Override
+    public Object visit(ASTLabeledStatement node, Object data) {
+        return countNodeChildren(node, data);
+    }
+
+    @Override
+    public Object visit(ASTLocalVariableDeclaration node, Object data) {
+
+        // "For" init declarations do not count as separate lines of code
+        if (node.jjtGetParent() instanceof ASTForInit) {
+            return NumericConstants.ZERO;
+        }
+
+        /*
+         * This will count variables declared on the same line as separate NCSS
+         * counts. This violates JavaNCSS standards, but I'm not convinced
+         * that's a bad thing here.
+         */
+
+        return countNodeChildren(node, data);
+    }
+
+    @Override
+    public Object visit(ASTSwitchLabel node, Object data) {
+        return countNodeChildren(node, data);
+    }
+
+}
diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/AvoidDeeplyNestedIfStmtsRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/AvoidDeeplyNestedIfStmtsRule.java
index 973b6a56aa8..49b50123013 100644
--- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/AvoidDeeplyNestedIfStmtsRule.java
+++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/AvoidDeeplyNestedIfStmtsRule.java
@@ -14,19 +14,23 @@ public class AvoidDeeplyNestedIfStmtsRule extends AbstractJavaRule {
     private int depth;
     private int depthLimit;
 
-    private static final IntegerProperty PROBLEM_DEPTH_DESCRIPTOR = new IntegerProperty("problemDepth",
-            "The if statement depth reporting threshold", 1, 25, 3, 1.0f);
+    private static final IntegerProperty PROBLEM_DEPTH_DESCRIPTOR
+            = IntegerProperty.named("problemDepth")
+                             .desc("The if statement depth reporting threshold")
+                             .range(1, 25).defaultValue(3).uiOrder(1.0f).build();
 
     public AvoidDeeplyNestedIfStmtsRule() {
         definePropertyDescriptor(PROBLEM_DEPTH_DESCRIPTOR);
     }
 
+    @Override
     public Object visit(ASTCompilationUnit node, Object data) {
         depth = 0;
         depthLimit = getProperty(PROBLEM_DEPTH_DESCRIPTOR);
         return super.visit(node, data);
     }
 
+    @Override
     public Object visit(ASTIfStatement node, Object data) {
         if (!node.hasElse()) {
             depth++;
diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/AvoidReassigningParametersRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/AvoidReassigningParametersRule.java
deleted file mode 100644
index 21614ccd540..00000000000
--- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/AvoidReassigningParametersRule.java
+++ /dev/null
@@ -1,52 +0,0 @@
-/**
- * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
- */
-
-package net.sourceforge.pmd.lang.java.rule.design;
-
-import java.util.List;
-import java.util.Map;
-
-import net.sourceforge.pmd.lang.java.ast.ASTConstructorDeclaration;
-import net.sourceforge.pmd.lang.java.ast.ASTMethodDeclarator;
-import net.sourceforge.pmd.lang.java.rule.AbstractJavaRule;
-import net.sourceforge.pmd.lang.java.symboltable.JavaNameOccurrence;
-import net.sourceforge.pmd.lang.java.symboltable.VariableNameDeclaration;
-import net.sourceforge.pmd.lang.symboltable.NameOccurrence;
-
-public class AvoidReassigningParametersRule extends AbstractJavaRule {
-
-    @Override
-    public Object visit(ASTMethodDeclarator node, Object data) {
-        Map> params = node.getScope()
-                .getDeclarations(VariableNameDeclaration.class);
-        this.lookForViolation(params, data);
-        return super.visit(node, data);
-    }
-
-    private void lookForViolation(Map> params, Object data) {
-        for (Map.Entry> entry : params.entrySet()) {
-            VariableNameDeclaration decl = entry.getKey();
-            List usages = entry.getValue();
-            for (NameOccurrence occ : usages) {
-                JavaNameOccurrence jocc = (JavaNameOccurrence) occ;
-                if ((jocc.isOnLeftHandSide() || jocc.isSelfAssignment())
-                        && jocc.getNameForWhichThisIsAQualifier() == null && !jocc.useThisOrSuper() && !decl.isVarargs()
-                        && (!decl.isArray()
-                                || jocc.getLocation().jjtGetParent().jjtGetParent().jjtGetNumChildren() == 1)) {
-                    // not an array or no primary suffix to access the array
-                    // values
-                    addViolation(data, decl.getNode(), decl.getImage());
-                }
-            }
-        }
-    }
-
-    @Override
-    public Object visit(ASTConstructorDeclaration node, Object data) {
-        Map> params = node.getScope()
-                .getDeclarations(VariableNameDeclaration.class);
-        this.lookForViolation(params, data);
-        return super.visit(node, data);
-    }
-}
diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/coupling/CouplingBetweenObjectsRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/CouplingBetweenObjectsRule.java
similarity index 95%
rename from pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/coupling/CouplingBetweenObjectsRule.java
rename to pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/CouplingBetweenObjectsRule.java
index 00124a484ac..ba4ec721c05 100644
--- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/coupling/CouplingBetweenObjectsRule.java
+++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/CouplingBetweenObjectsRule.java
@@ -2,7 +2,7 @@
  * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
  */
 
-package net.sourceforge.pmd.lang.java.rule.coupling;
+package net.sourceforge.pmd.lang.java.rule.design;
 
 import java.util.HashSet;
 import java.util.Set;
@@ -36,8 +36,10 @@ public class CouplingBetweenObjectsRule extends AbstractJavaRule {
     private int couplingCount;
     private Set typesFoundSoFar;
 
-    private static final IntegerProperty THRESHOLD_DESCRIPTOR = new IntegerProperty("threshold",
-            "Unique type reporting threshold", 2, 100, 20, 1.0f);
+    private static final IntegerProperty THRESHOLD_DESCRIPTOR 
+            = IntegerProperty.named("threshold")
+                             .desc("Unique type reporting threshold")
+                             .range(2, 100).defaultValue(20).uiOrder(1.0f).build();
 
     public CouplingBetweenObjectsRule() {
         definePropertyDescriptor(THRESHOLD_DESCRIPTOR);
diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/CyclomaticComplexityRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/CyclomaticComplexityRule.java
new file mode 100644
index 00000000000..339ac0aec3c
--- /dev/null
+++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/CyclomaticComplexityRule.java
@@ -0,0 +1,154 @@
+/**
+ * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
+ */
+
+package net.sourceforge.pmd.lang.java.rule.design;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.logging.Logger;
+
+import net.sourceforge.pmd.lang.java.ast.ASTAnyTypeDeclaration;
+import net.sourceforge.pmd.lang.java.ast.ASTCompilationUnit;
+import net.sourceforge.pmd.lang.java.ast.MethodLikeNode;
+import net.sourceforge.pmd.lang.java.metrics.JavaMetrics;
+import net.sourceforge.pmd.lang.java.metrics.api.JavaClassMetricKey;
+import net.sourceforge.pmd.lang.java.metrics.api.JavaOperationMetricKey;
+import net.sourceforge.pmd.lang.java.metrics.impl.CycloMetric;
+import net.sourceforge.pmd.lang.java.metrics.impl.CycloMetric.CycloOption;
+import net.sourceforge.pmd.lang.java.rule.AbstractJavaMetricsRule;
+import net.sourceforge.pmd.lang.metrics.MetricOptions;
+import net.sourceforge.pmd.lang.metrics.ResultOption;
+import net.sourceforge.pmd.properties.EnumeratedMultiProperty;
+import net.sourceforge.pmd.properties.IntegerProperty;
+
+
+/**
+ * Cyclomatic complexity rule using metrics.
+ *
+ * @author Clément Fournier, based on work by Alan Hohn and Donald A. Leckie
+ * @see CycloMetric
+ * @version 6.0.0
+ */
+public class CyclomaticComplexityRule extends AbstractJavaMetricsRule {
+    
+    private static final Logger LOG = Logger.getLogger(CyclomaticComplexityRule.class.getName());
+
+    // Deprecated, kept for backwards compatibility (6.0.0)
+    @Deprecated
+    private static final IntegerProperty REPORT_LEVEL_DESCRIPTOR
+        = IntegerProperty.named("reportLevel")
+                         .desc("Deprecated! Cyclomatic Complexity reporting threshold")
+                         .range(1, 30).defaultValue(10).uiOrder(1.0f).build();
+
+
+    private static final IntegerProperty CLASS_LEVEL_DESCRIPTOR
+        = IntegerProperty.named("classReportLevel")
+                         .desc("Total class complexity reporting threshold")
+                         .range(1, 600).defaultValue(80).uiOrder(1.0f).build();
+
+    private static final IntegerProperty METHOD_LEVEL_DESCRIPTOR
+        = IntegerProperty.named("methodReportLevel")
+                         .desc("Cyclomatic complexity reporting threshold")
+                         .range(1, 50).defaultValue(10).uiOrder(1.0f).build();
+
+    private static final Map OPTION_MAP;
+    
+    static {
+        OPTION_MAP = new HashMap<>();
+        OPTION_MAP.put(CycloOption.IGNORE_BOOLEAN_PATHS.valueName(), CycloOption.IGNORE_BOOLEAN_PATHS);
+        OPTION_MAP.put(CycloOption.CONSIDER_ASSERT.valueName(), CycloOption.CONSIDER_ASSERT);
+    }
+    
+    private static final EnumeratedMultiProperty CYCLO_OPTIONS_DESCRIPTOR
+        = EnumeratedMultiProperty.named("cycloOptions").type(CycloOption.class)
+                                                                    .desc("Choose options for the computation of Cyclo")
+                                                                    .mappings(OPTION_MAP)
+                                                                    .defaultValues(Collections.emptyList())
+                                                                    .uiOrder(3.0f).build();
+    private int methodReportLevel;
+    private int classReportLevel;
+    private MetricOptions cycloOptions;
+
+
+    public CyclomaticComplexityRule() {
+        definePropertyDescriptor(CLASS_LEVEL_DESCRIPTOR);
+        definePropertyDescriptor(METHOD_LEVEL_DESCRIPTOR);
+        definePropertyDescriptor(CYCLO_OPTIONS_DESCRIPTOR);
+        definePropertyDescriptor(REPORT_LEVEL_DESCRIPTOR);
+    }
+
+    // Kept for backwards compatibility // TODO remove the property sometime
+    private void assignReportLevelsCompat() {
+        int methodLevel = getProperty(METHOD_LEVEL_DESCRIPTOR);
+        int classLevel = getProperty(CLASS_LEVEL_DESCRIPTOR);
+        int commonLevel = getProperty(REPORT_LEVEL_DESCRIPTOR);
+        
+        if (methodLevel == METHOD_LEVEL_DESCRIPTOR.defaultValue()
+            && classLevel == CLASS_LEVEL_DESCRIPTOR.defaultValue()
+            && commonLevel != REPORT_LEVEL_DESCRIPTOR.defaultValue()) {
+            LOG.warning("Rule CyclomaticComplexity uses deprecated property 'reportLevel'. " 
+                        + "Future versions of PMD will remove support for this property. " 
+                        + "Please use 'methodReportLevel' and 'classReportLevel' instead!");
+            methodLevel = commonLevel;
+            classLevel = commonLevel * 8;
+        }
+        
+        methodReportLevel = methodLevel;
+        classReportLevel = classLevel;
+    }
+
+    @Override
+    public Object visit(ASTCompilationUnit node, Object data) {
+        // methodReportLevel = getProperty(METHOD_LEVEL_DESCRIPTOR);
+        // classReportLevel = getProperty(CLASS_LEVEL_DESCRIPTOR);
+        assignReportLevelsCompat();
+        
+        cycloOptions = MetricOptions.ofOptions(getProperty(CYCLO_OPTIONS_DESCRIPTOR));
+
+
+        super.visit(node, data);
+        return data;
+    }
+
+
+    @Override
+    public Object visit(ASTAnyTypeDeclaration node, Object data) {
+
+        super.visit(node, data);
+
+        if (JavaClassMetricKey.WMC.supports(node)) {
+            int classWmc = (int) JavaMetrics.get(JavaClassMetricKey.WMC, node, cycloOptions);
+
+            if (classWmc >= classReportLevel) {
+                int classHighest = (int) JavaMetrics.get(JavaOperationMetricKey.CYCLO, node, cycloOptions, ResultOption.HIGHEST);
+
+                String[] messageParams = {node.getTypeKind().getPrintableName(),
+                                          node.getImage(),
+                                          " total",
+                                          classWmc + " (highest " + classHighest + ")", };
+
+                addViolation(data, node, messageParams);
+            }
+        }
+        return data;
+    }
+
+
+    @Override
+    public final Object visit(MethodLikeNode node, Object data) {
+
+        int cyclo = (int) JavaMetrics.get(JavaOperationMetricKey.CYCLO, node, cycloOptions);
+        if (cyclo >= methodReportLevel) {
+
+            addViolation(data, node, new String[]{node.getKind().getPrintableName(),
+                                                  node.getQualifiedName().getOperation(),
+                                                  "",
+                                                  "" + cyclo, });
+        }
+
+        return data;
+    }
+
+}
diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/rule/DataClassRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/DataClassRule.java
similarity index 97%
rename from pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/rule/DataClassRule.java
rename to pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/DataClassRule.java
index a8aaf78146c..02333794116 100644
--- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/metrics/rule/DataClassRule.java
+++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/DataClassRule.java
@@ -2,7 +2,7 @@
  * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
  */
 
-package net.sourceforge.pmd.lang.java.metrics.rule;
+package net.sourceforge.pmd.lang.java.rule.design;
 
 import net.sourceforge.pmd.lang.java.ast.ASTAnyTypeDeclaration;
 import net.sourceforge.pmd.lang.java.metrics.JavaMetrics;
diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/strictexception/ExceptionAsFlowControlRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/ExceptionAsFlowControlRule.java
similarity index 96%
rename from pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/strictexception/ExceptionAsFlowControlRule.java
rename to pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/ExceptionAsFlowControlRule.java
index 231a4a04f5a..b1405a45a48 100644
--- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/strictexception/ExceptionAsFlowControlRule.java
+++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/ExceptionAsFlowControlRule.java
@@ -2,7 +2,7 @@
  * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
  */
 
-package net.sourceforge.pmd.lang.java.rule.strictexception;
+package net.sourceforge.pmd.lang.java.rule.design;
 
 import java.util.List;
 
diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/ExcessiveClassLengthRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/ExcessiveClassLengthRule.java
new file mode 100644
index 00000000000..766dc252c6a
--- /dev/null
+++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/ExcessiveClassLengthRule.java
@@ -0,0 +1,18 @@
+/**
+ * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
+ */
+
+package net.sourceforge.pmd.lang.java.rule.design;
+
+import net.sourceforge.pmd.lang.java.ast.ASTAnyTypeDeclaration;
+
+/**
+ * This rule detects when a class exceeds a certain threshold. i.e. if a class
+ * has more than 1000 lines of code.
+ */
+public class ExcessiveClassLengthRule extends ExcessiveLengthRule {
+    public ExcessiveClassLengthRule() {
+        super(ASTAnyTypeDeclaration.class);
+        setProperty(MINIMUM_DESCRIPTOR, 1000d);
+    }
+}
diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/coupling/ExcessiveImportsRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/ExcessiveImportsRule.java
similarity index 89%
rename from pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/coupling/ExcessiveImportsRule.java
rename to pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/ExcessiveImportsRule.java
index 02c995bf81a..0b1baa38651 100644
--- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/coupling/ExcessiveImportsRule.java
+++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/ExcessiveImportsRule.java
@@ -2,11 +2,10 @@
  * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
  */
 
-package net.sourceforge.pmd.lang.java.rule.coupling;
+package net.sourceforge.pmd.lang.java.rule.design;
 
 import net.sourceforge.pmd.lang.java.ast.ASTCompilationUnit;
 import net.sourceforge.pmd.lang.java.ast.ASTImportDeclaration;
-import net.sourceforge.pmd.lang.java.rule.design.ExcessiveNodeCountRule;
 import net.sourceforge.pmd.util.NumericConstants;
 
 /**
@@ -32,6 +31,7 @@ public ExcessiveImportsRule() {
      * @param data
      * @return Object
      */
+    @Override
     public Object visit(ASTImportDeclaration node, Object data) {
         return NumericConstants.ONE;
     }
diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/ExcessiveMethodLengthRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/ExcessiveMethodLengthRule.java
new file mode 100644
index 00000000000..86c654ec709
--- /dev/null
+++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/ExcessiveMethodLengthRule.java
@@ -0,0 +1,19 @@
+/**
+ * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
+ */
+
+package net.sourceforge.pmd.lang.java.rule.design;
+
+import net.sourceforge.pmd.lang.java.ast.ASTMethodOrConstructorDeclaration;
+
+
+/**
+ * This rule detects when a method exceeds a certain threshold. i.e. if a method
+ * has more than x lines of code.
+ */
+public class ExcessiveMethodLengthRule extends ExcessiveLengthRule {
+    public ExcessiveMethodLengthRule() {
+        super(ASTMethodOrConstructorDeclaration.class);
+        setProperty(MINIMUM_DESCRIPTOR, 100d);
+    }
+}
diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/ExcessiveParameterListRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/ExcessiveParameterListRule.java
new file mode 100644
index 00000000000..2895186887c
--- /dev/null
+++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/ExcessiveParameterListRule.java
@@ -0,0 +1,27 @@
+/**
+ * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
+ */
+
+package net.sourceforge.pmd.lang.java.rule.design;
+
+import net.sourceforge.pmd.lang.java.ast.ASTFormalParameter;
+import net.sourceforge.pmd.lang.java.ast.ASTFormalParameters;
+import net.sourceforge.pmd.util.NumericConstants;
+
+/**
+ * This rule detects an abnormally long parameter list. Note: This counts Nodes,
+ * and not necessarily parameters, so the numbers may not match up. (But
+ * topcount and sigma should work.)
+ */
+public class ExcessiveParameterListRule extends ExcessiveNodeCountRule {
+    public ExcessiveParameterListRule() {
+        super(ASTFormalParameters.class);
+        setProperty(MINIMUM_DESCRIPTOR, 10d);
+    }
+
+    // Count these nodes, but no others.
+    @Override
+    public Object visit(ASTFormalParameter node, Object data) {
+        return NumericConstants.ONE;
+    }
+}
diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/ExcessivePublicCountRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/ExcessivePublicCountRule.java
new file mode 100644
index 00000000000..be8c345239e
--- /dev/null
+++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/ExcessivePublicCountRule.java
@@ -0,0 +1,68 @@
+/**
+ * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
+ */
+
+package net.sourceforge.pmd.lang.java.rule.design;
+
+import net.sourceforge.pmd.lang.java.ast.ASTCompilationUnit;
+import net.sourceforge.pmd.lang.java.ast.ASTFieldDeclaration;
+import net.sourceforge.pmd.lang.java.ast.ASTMethodDeclarator;
+import net.sourceforge.pmd.lang.java.ast.AccessNode;
+import net.sourceforge.pmd.util.NumericConstants;
+
+/**
+ * Rule attempts to count all public methods and public attributes
+ * defined in a class.
+ *
+ * 

If a class has a high number of public operations, it might be wise + * to consider whether it would be appropriate to divide it into + * subclasses.

+ * + *

A large proportion of public members and operations means the class + * has high potential to be affected by external classes. Futhermore, + * increased effort will be required to thoroughly test the class. + *

+ * + * @author aglover + */ +public class ExcessivePublicCountRule extends ExcessiveNodeCountRule { + + public ExcessivePublicCountRule() { + super(ASTCompilationUnit.class); + setProperty(MINIMUM_DESCRIPTOR, 45d); + } + + /** + * Method counts ONLY public methods. + */ + @Override + public Object visit(ASTMethodDeclarator node, Object data) { + return this.getTallyOnAccessType((AccessNode) node.jjtGetParent()); + } + + /** + * Method counts ONLY public class attributes which are not PUBLIC and + * static- these usually represent constants.... + */ + @Override + public Object visit(ASTFieldDeclaration node, Object data) { + if (node.isFinal() && node.isStatic()) { + return NumericConstants.ZERO; + } + return this.getTallyOnAccessType(node); + } + + /** + * Method counts a node if it is public + * + * @param node + * The access node. + * @return Integer 1 if node is public 0 otherwise + */ + private Integer getTallyOnAccessType(AccessNode node) { + if (node.isPublic()) { + return NumericConstants.ONE; + } + return NumericConstants.ZERO; + } +} diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/ImmutableFieldRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/ImmutableFieldRule.java index 2d982f1569b..d409c66d909 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/ImmutableFieldRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/ImmutableFieldRule.java @@ -4,7 +4,6 @@ package net.sourceforge.pmd.lang.java.rule.design; -import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Map; @@ -24,6 +23,7 @@ import net.sourceforge.pmd.lang.java.ast.ASTVariableInitializer; import net.sourceforge.pmd.lang.java.ast.ASTWhileStatement; import net.sourceforge.pmd.lang.java.ast.AccessNode; +import net.sourceforge.pmd.lang.java.ast.Annotatable; import net.sourceforge.pmd.lang.java.rule.AbstractLombokAwareRule; import net.sourceforge.pmd.lang.java.symboltable.JavaNameOccurrence; import net.sourceforge.pmd.lang.java.symboltable.VariableNameDeclaration; @@ -40,7 +40,7 @@ private enum FieldImmutabilityType { /** Variable is not changed outside the constructor. */ IMMUTABLE, /** Variable is only written during declaration, if at all. */ - CHECKDECL; + CHECKDECL } @Override @@ -55,7 +55,8 @@ public Object visit(ASTClassOrInterfaceDeclaration node, Object data) { AccessNode accessNodeParent = field.getAccessNodeParent(); if (accessNodeParent.isStatic() || !accessNodeParent.isPrivate() || accessNodeParent.isFinal() || accessNodeParent.isVolatile() - || hasClassLombokAnnotation()) { + || hasClassLombokAnnotation() + || hasIgnoredAnnotation((Annotatable) accessNodeParent)) { continue; } @@ -71,14 +72,14 @@ public Object visit(ASTClassOrInterfaceDeclaration node, Object data) { } private boolean initializedWhenDeclared(VariableNameDeclaration field) { - return ((Node) field.getAccessNodeParent()).hasDescendantOfType(ASTVariableInitializer.class); + return field.getAccessNodeParent().hasDescendantOfType(ASTVariableInitializer.class); } private FieldImmutabilityType initializedInConstructor(List usages, Set allConstructors) { FieldImmutabilityType result = FieldImmutabilityType.MUTABLE; int methodInitCount = 0; int lambdaUsage = 0; - Set consSet = new HashSet<>(); + Set consSet = new HashSet<>(); // set of constructors accessing the field for (NameOccurrence occ : usages) { JavaNameOccurrence jocc = (JavaNameOccurrence) occ; if (jocc.isOnLeftHandSide() || jocc.isSelfAssignment()) { @@ -98,6 +99,8 @@ private FieldImmutabilityType initializedInConstructor(List usag } if (inAnonymousInnerClass(node)) { methodInitCount++; + } else if (node.getFirstParentOfType(ASTLambdaExpression.class) != null) { + lambdaUsage++; } else { consSet.add(constructor); } @@ -134,9 +137,7 @@ private boolean inAnonymousInnerClass(Node node) { } private List findAllConstructors(ASTClassOrInterfaceDeclaration node) { - List cons = new ArrayList<>(); - node.getFirstChildOfType(ASTClassOrInterfaceBody.class).findDescendantsOfType(ASTConstructorDeclaration.class, - cons, false); - return cons; + return node.getFirstChildOfType(ASTClassOrInterfaceBody.class) + .findDescendantsOfType(ASTConstructorDeclaration.class); } } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/coupling/LawOfDemeterRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/LawOfDemeterRule.java similarity index 99% rename from pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/coupling/LawOfDemeterRule.java rename to pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/LawOfDemeterRule.java index 5d8c4c43623..cb0d15b00a5 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/coupling/LawOfDemeterRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/LawOfDemeterRule.java @@ -2,12 +2,13 @@ * BSD-style license; for more info see http://pmd.sourceforge.net/license.html */ -package net.sourceforge.pmd.lang.java.rule.coupling; +package net.sourceforge.pmd.lang.java.rule.design; import java.util.ArrayList; import java.util.Collections; import java.util.Iterator; import java.util.List; +import java.util.Locale; import java.util.Set; import net.sourceforge.pmd.RuleContext; @@ -383,7 +384,7 @@ private boolean isFactory(ASTVariableDeclarator declarator) { boolean factory = false; List names = declarator.findDescendantsOfType(ASTName.class); for (ASTName name : names) { - if (name.getImage().toLowerCase().contains("factory")) { + if (name.getImage().toLowerCase(Locale.ROOT).contains("factory")) { factory = true; break; } @@ -440,6 +441,7 @@ public String toString() { + forLoop; } + @Override public int compareTo(Assignment o) { return o.line - line; } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/coupling/LoosePackageCouplingRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/LoosePackageCouplingRule.java similarity index 98% rename from pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/coupling/LoosePackageCouplingRule.java rename to pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/LoosePackageCouplingRule.java index c8598015fc2..6631d68531f 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/coupling/LoosePackageCouplingRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/LoosePackageCouplingRule.java @@ -2,7 +2,7 @@ * BSD-style license; for more info see http://pmd.sourceforge.net/license.html */ -package net.sourceforge.pmd.lang.java.rule.coupling; +package net.sourceforge.pmd.lang.java.rule.design; import java.util.ArrayList; import java.util.Collections; diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codesize/ModifiedCyclomaticComplexityRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/ModifiedCyclomaticComplexityRule.java similarity index 92% rename from pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codesize/ModifiedCyclomaticComplexityRule.java rename to pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/ModifiedCyclomaticComplexityRule.java index a04b198d714..92d70d285ab 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codesize/ModifiedCyclomaticComplexityRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/ModifiedCyclomaticComplexityRule.java @@ -2,7 +2,7 @@ * BSD-style license; for more info see http://pmd.sourceforge.net/license.html */ -package net.sourceforge.pmd.lang.java.rule.codesize; +package net.sourceforge.pmd.lang.java.rule.design; import net.sourceforge.pmd.lang.java.ast.ASTSwitchStatement; import net.sourceforge.pmd.lang.java.ast.JavaNode; @@ -17,6 +17,7 @@ * * @since June 18, 2014 */ +@Deprecated public class ModifiedCyclomaticComplexityRule extends StdCyclomaticComplexityRule { @Override 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 new file mode 100644 index 00000000000..59891ef4615 --- /dev/null +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/NPathComplexityRule.java @@ -0,0 +1,81 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.design; + +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; +import net.sourceforge.pmd.properties.DoubleProperty; +import net.sourceforge.pmd.properties.IntegerProperty; + + +/** + * Simple n-path complexity rule. + * + * @author Clément Fournier + * @author Jason Bennett + */ +public class NPathComplexityRule extends AbstractJavaMetricsRule { + + private static final Logger LOG = Logger.getLogger(NPathComplexityRule.class.getName()); + + @Deprecated + private static final DoubleProperty MINIMUM_DESCRIPTOR + = DoubleProperty.named("minimum").desc("Deprecated! Minimum reporting threshold") + .range(0d, 2000d).defaultValue(200d).uiOrder(2.0f).build(); + + + private static final IntegerProperty REPORT_LEVEL_DESCRIPTOR + = IntegerProperty.named("reportLevel").desc("N-Path Complexity reporting threshold") + .range(1, 2000).defaultValue(200).uiOrder(1.0f).build(); + + + private int reportLevel = 200; + + + public NPathComplexityRule() { + definePropertyDescriptor(REPORT_LEVEL_DESCRIPTOR); + definePropertyDescriptor(MINIMUM_DESCRIPTOR); + } + + + @Override + public Object visit(ASTCompilationUnit node, Object data) { + reportLevel = getReportLevel(); + + super.visit(node, data); + return data; + } + + + private int getReportLevel() { + double oldProp = getProperty(MINIMUM_DESCRIPTOR); + if (oldProp != MINIMUM_DESCRIPTOR.defaultValue()) { + LOG.warning("Rule NPathComplexity uses deprecated property 'minimum'. Future versions of PMD will remove support for this property. Please use 'reportLevel' instead!"); + return (int) oldProp; + } + + return getProperty(REPORT_LEVEL_DESCRIPTOR); + } + + + @Override + public final Object visit(ASTMethodOrConstructorDeclaration node, Object data) { + 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, }); + } + + return data; + } +} + diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/NcssConstructorCountRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/NcssConstructorCountRule.java new file mode 100644 index 00000000000..5da4c24f8f6 --- /dev/null +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/NcssConstructorCountRule.java @@ -0,0 +1,39 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.design; + +import net.sourceforge.pmd.lang.java.ast.ASTConstructorDeclaration; +import net.sourceforge.pmd.lang.java.ast.ASTExplicitConstructorInvocation; +import net.sourceforge.pmd.stat.DataPoint; +import net.sourceforge.pmd.util.NumericConstants; + +/** + * Non-commented source statement counter for constructors. + * + * @author Jason Bennett + */ +@Deprecated +public class NcssConstructorCountRule extends AbstractNcssCountRule { + + /** + * Count constructor declarations. This includes any explicit super() calls. + */ + public NcssConstructorCountRule() { + super(ASTConstructorDeclaration.class); + setProperty(MINIMUM_DESCRIPTOR, 100d); + } + + @Override + public Object visit(ASTExplicitConstructorInvocation node, Object data) { + return NumericConstants.ONE; + } + + @Override + public Object[] getViolationParameters(DataPoint point) { + // TODO need to put class name or constructor ID in string + return new String[] { String.valueOf(((ASTConstructorDeclaration) point.getNode()).getParameterCount()), + String.valueOf((int) point.getScore()), }; + } +} diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codesize/NcssCountRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/NcssCountRule.java similarity index 85% rename from pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codesize/NcssCountRule.java rename to pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/NcssCountRule.java index 74b6af7c5e9..c2eff8a4234 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/codesize/NcssCountRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/NcssCountRule.java @@ -2,10 +2,11 @@ * BSD-style license; for more info see http://pmd.sourceforge.net/license.html */ -package net.sourceforge.pmd.lang.java.rule.codesize; +package net.sourceforge.pmd.lang.java.rule.design; import java.util.Collections; import java.util.HashMap; +import java.util.Locale; import java.util.Map; import net.sourceforge.pmd.lang.java.ast.ASTAnyTypeDeclaration; @@ -30,11 +31,19 @@ public final class NcssCountRule extends AbstractJavaMetricsRule { - private static final IntegerProperty METHOD_REPORT_LEVEL_DESCRIPTOR = new IntegerProperty( - "methodReportLevel", "Metric reporting threshold for methods", 1, 60, 12, 1.0f); + private static final IntegerProperty METHOD_REPORT_LEVEL_DESCRIPTOR = + IntegerProperty.named("methodReportLevel") + .desc("NCSS reporting threshold for methods") + .range(1, 2000) + .defaultValue(60) + .build(); - private static final IntegerProperty CLASS_REPORT_LEVEL_DESCRIPTOR = new IntegerProperty( - "classReportLevel", "Metric reporting threshold for classes", 1, 1000, 250, 1.0f); + private static final IntegerProperty CLASS_REPORT_LEVEL_DESCRIPTOR = + IntegerProperty.named("classReportLevel") + .desc("NCSS reporting threshold for classes") + .range(1, 20000) + .defaultValue(1500) + .build(); private static final Map OPTION_MAP; @@ -83,7 +92,7 @@ public Object visit(ASTAnyTypeDeclaration node, Object data) { int classHighest = (int) JavaMetrics.get(JavaOperationMetricKey.NCSS, node, ncssOptions, ResultOption.HIGHEST); if (classSize >= classReportLevel) { - String[] messageParams = {node.getTypeKind().name().toLowerCase(), + String[] messageParams = {node.getTypeKind().name().toLowerCase(Locale.ROOT), node.getImage(), classSize + " (Highest = " + classHighest + ")", }; diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/NcssMethodCountRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/NcssMethodCountRule.java new file mode 100644 index 00000000000..b9314040934 --- /dev/null +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/NcssMethodCountRule.java @@ -0,0 +1,36 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.design; + +import net.sourceforge.pmd.lang.java.ast.ASTMethodDeclaration; +import net.sourceforge.pmd.stat.DataPoint; + +/** + * Non-commented source statement counter for methods. + * + * @author Jason Bennett + */ +@Deprecated +public class NcssMethodCountRule extends AbstractNcssCountRule { + + /** + * Count the size of all non-constructor methods. + */ + public NcssMethodCountRule() { + super(ASTMethodDeclaration.class); + setProperty(MINIMUM_DESCRIPTOR, 100d); + } + + @Override + public Object visit(ASTMethodDeclaration node, Object data) { + return super.visit(node, data); + } + + @Override + public Object[] getViolationParameters(DataPoint point) { + return new String[] { ((ASTMethodDeclaration) point.getNode()).getMethodName(), + String.valueOf((int) point.getScore()), }; + } +} diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/NcssTypeCountRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/NcssTypeCountRule.java new file mode 100644 index 00000000000..11e547284ee --- /dev/null +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/NcssTypeCountRule.java @@ -0,0 +1,88 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.design; + +import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceDeclaration; +import net.sourceforge.pmd.lang.java.ast.ASTConstructorDeclaration; +import net.sourceforge.pmd.lang.java.ast.ASTEnumDeclaration; +import net.sourceforge.pmd.lang.java.ast.ASTExplicitConstructorInvocation; +import net.sourceforge.pmd.lang.java.ast.ASTFieldDeclaration; +import net.sourceforge.pmd.lang.java.ast.ASTInitializer; +import net.sourceforge.pmd.lang.java.ast.ASTMethodDeclaration; +import net.sourceforge.pmd.lang.java.ast.ASTTypeDeclaration; +import net.sourceforge.pmd.stat.DataPoint; +import net.sourceforge.pmd.util.NumericConstants; + +/** + * Non-commented source statement counter for type declarations. + * + * @author Jason Bennett + */ +@Deprecated +public class NcssTypeCountRule extends AbstractNcssCountRule { + + /** + * Count type declarations. This includes classes as well as enums and + * annotations. + */ + public NcssTypeCountRule() { + super(ASTTypeDeclaration.class); + setProperty(MINIMUM_DESCRIPTOR, 1500d); + } + + @Override + public Object visit(ASTClassOrInterfaceDeclaration node, Object data) { + + if (!node.isNested()) { + return super.visit(node, data); + } + + return countNodeChildren(node, data); + } + + @Override + public Object visit(ASTConstructorDeclaration node, Object data) { + return countNodeChildren(node, data); + } + + @Override + public Object visit(ASTExplicitConstructorInvocation node, Object data) { + return NumericConstants.ONE; + } + + @Override + public Object visit(ASTEnumDeclaration node, Object data) { + /* + * If the enum is a type in and of itself, don't count its declaration + * twice. + */ + if (node.jjtGetParent() instanceof ASTTypeDeclaration) { + Integer nodeCount = countNodeChildren(node, data); + int count = nodeCount.intValue() - 1; + return Integer.valueOf(count); + } + return countNodeChildren(node, data); + } + + @Override + public Object visit(ASTMethodDeclaration node, Object data) { + return countNodeChildren(node, data); + } + + @Override + public Object visit(ASTInitializer node, Object data) { + return countNodeChildren(node, data); + } + + @Override + public Object visit(ASTFieldDeclaration node, Object data) { + return NumericConstants.ONE; + } + + @Override + public Object[] getViolationParameters(DataPoint point) { + return new String[] { String.valueOf((int) point.getScore()) }; + } +} diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/PositionalIteratorRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/PositionalIteratorRule.java index 933bcf4c637..09aa1069523 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/PositionalIteratorRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/PositionalIteratorRule.java @@ -14,6 +14,7 @@ public class PositionalIteratorRule extends AbstractJavaRule { + @Override public Object visit(ASTWhileStatement node, Object data) { if (hasNameAsChild(node.jjtGetChild(0))) { String exprName = getName(node.jjtGetChild(0)); diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/SignatureDeclareThrowsExceptionRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/SignatureDeclareThrowsExceptionRule.java new file mode 100644 index 00000000000..43ec9ff20b3 --- /dev/null +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/SignatureDeclareThrowsExceptionRule.java @@ -0,0 +1,208 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.design; + +import java.util.Collections; +import java.util.List; + +import net.sourceforge.pmd.lang.ast.Node; +import net.sourceforge.pmd.lang.java.ast.ASTAnnotation; +import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceDeclaration; +import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceType; +import net.sourceforge.pmd.lang.java.ast.ASTCompilationUnit; +import net.sourceforge.pmd.lang.java.ast.ASTConstructorDeclaration; +import net.sourceforge.pmd.lang.java.ast.ASTImportDeclaration; +import net.sourceforge.pmd.lang.java.ast.ASTMethodDeclaration; +import net.sourceforge.pmd.lang.java.ast.ASTName; +import net.sourceforge.pmd.lang.java.ast.ASTNameList; +import net.sourceforge.pmd.lang.java.rule.AbstractJavaRule; +import net.sourceforge.pmd.properties.BooleanProperty; + +/** + * A method/constructor shouldn't explicitly throw java.lang.Exception, since it + * is unclear which exceptions that can be thrown from the methods. It might be + * difficult to document and understand such vague interfaces. Use either a class + * derived from RuntimeException or a checked exception. + * + *

This rule uses PMD's type resolution facilities, and can detect + * if the class implements or extends TestCase class + * + * @author Trond Andersen + * @version 1.0 + * @since 1.2 + */ + +public class SignatureDeclareThrowsExceptionRule extends AbstractJavaRule { + + private static final BooleanProperty IGNORE_JUNIT_COMPLETELY_DESCRIPTOR = new BooleanProperty( + "IgnoreJUnitCompletely", "Allow all methods in a JUnit testcase to throw Exceptions", false, 1.0f); + + // Set to true when the class is determined to be a JUnit testcase + private boolean junitImported = false; + + public SignatureDeclareThrowsExceptionRule() { + definePropertyDescriptor(IGNORE_JUNIT_COMPLETELY_DESCRIPTOR); + } + + @Override + public Object visit(ASTCompilationUnit node, Object o) { + junitImported = false; + return super.visit(node, o); + } + + @Override + public Object visit(ASTClassOrInterfaceDeclaration node, Object data) { + if (junitImported) { + return super.visit(node, data); + } + + for (final ASTClassOrInterfaceType type : node.getSuperInterfacesTypeNodes()) { + if (isJUnitTest(type)) { + junitImported = true; + return super.visit(node, data); + } + } + + ASTClassOrInterfaceType type = node.getSuperClassTypeNode(); + if (type != null && isJUnitTest(type)) { + junitImported = true; + return super.visit(node, data); + } + + return super.visit(node, data); + } + + private boolean isJUnitTest(ASTClassOrInterfaceType type) { + Class clazz = type.getType(); + if (clazz == null) { + if ("junit.framework.Test".equals(type.getImage())) { + return true; + } + } else if (isJUnitTest(clazz)) { + return true; + } else { + while (clazz != null && !Object.class.equals(clazz)) { + for (Class intf : clazz.getInterfaces()) { + if (isJUnitTest(intf)) { + return true; + } + } + clazz = clazz.getSuperclass(); + } + } + return false; + } + + private boolean isJUnitTest(Class clazz) { + return clazz.getName().equals("junit.framework.Test"); + } + + @Override + public Object visit(ASTImportDeclaration node, Object o) { + if (node.getImportedName().indexOf("junit") != -1) { + junitImported = true; + } + return super.visit(node, o); + } + + @Override + public Object visit(ASTMethodDeclaration methodDeclaration, Object o) { + if (junitImported && isAllowedMethod(methodDeclaration)) { + return super.visit(methodDeclaration, o); + } + + if (methodDeclaration.getMethodName().startsWith("test")) { + return super.visit(methodDeclaration, o); + } + + // Ignore overridden methods, the issue should be marked on the method definition + final List methodAnnotations = methodDeclaration.jjtGetParent().findChildrenOfType(ASTAnnotation.class); + for (final ASTAnnotation annotation : methodAnnotations) { + final ASTName annotationName = annotation.getFirstDescendantOfType(ASTName.class); + if (annotationName.hasImageEqualTo("Override") || annotationName.hasImageEqualTo("java.lang.Override")) { + return super.visit(methodDeclaration, o); + } + } + + checkExceptions(methodDeclaration, o); + + return super.visit(methodDeclaration, o); + } + + private boolean isAllowedMethod(ASTMethodDeclaration methodDeclaration) { + if (getProperty(IGNORE_JUNIT_COMPLETELY_DESCRIPTOR)) { + return true; + } else { + return methodDeclaration.getMethodName().equals("setUp") + || methodDeclaration.getMethodName().equals("tearDown"); + } + } + + @Override + public Object visit(ASTConstructorDeclaration constructorDeclaration, Object o) { + if (junitImported && getProperty(IGNORE_JUNIT_COMPLETELY_DESCRIPTOR)) { + return super.visit(constructorDeclaration, o); + } + + checkExceptions(constructorDeclaration, o); + return super.visit(constructorDeclaration, o); + } + + /** + * Search the list of thrown exceptions for Exception + */ + private void checkExceptions(Node method, Object o) { + List exceptionList = Collections.emptyList(); + ASTNameList nameList = method.getFirstChildOfType(ASTNameList.class); + if (nameList != null) { + exceptionList = nameList.findDescendantsOfType(ASTName.class); + } + if (!exceptionList.isEmpty()) { + evaluateExceptions(exceptionList, o); + } + } + + /** + * Checks all exceptions for possible violation on the exception + * declaration. + * + * @param exceptionList + * containing all exception for declaration + * @param context + */ + private void evaluateExceptions(List exceptionList, Object context) { + for (ASTName exception : exceptionList) { + if (hasDeclaredExceptionInSignature(exception)) { + addViolation(context, exception); + } + } + } + + /** + * Checks if the given value is defined as Exception and the + * parent is either a method or constructor declaration. + * + * @param exception + * to evaluate + * @return true if Exception is declared and has proper parents + */ + private boolean hasDeclaredExceptionInSignature(ASTName exception) { + return exception.hasImageEqualTo("Exception") && isParentSignatureDeclaration(exception); + } + + /** + * Checks if the given exception is declared in the method or constructor + * signature. + * + * @param exception + * to evaluate + * @return true if parent node is either a method or constructor declaration + */ + private boolean isParentSignatureDeclaration(ASTName exception) { + Node parent = exception.jjtGetParent().jjtGetParent(); + return parent instanceof ASTMethodDeclaration || parent instanceof ASTConstructorDeclaration; + } + +} diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/SimplifyBooleanReturnsRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/SimplifyBooleanReturnsRule.java index b6390905fa0..be31cd3bcab 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/SimplifyBooleanReturnsRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/SimplifyBooleanReturnsRule.java @@ -17,6 +17,7 @@ public class SimplifyBooleanReturnsRule extends AbstractJavaRule { + @Override public Object visit(ASTMethodDeclaration node, Object data) { // only boolean methods should be inspected ASTResultType r = node.getResultType(); @@ -34,6 +35,7 @@ public Object visit(ASTMethodDeclaration node, Object data) { return data; } + @Override public Object visit(ASTIfStatement node, Object data) { // that's the case: if..then..return; return; if (!node.hasElse() && isIfJustReturnsBoolean(node) && isJustReturnsBooleanAfter(node)) { @@ -154,7 +156,7 @@ && terminatesInBooleanLiteral(node.jjtGetChild(2).jjtGetChild(0))) { /** * Checks, whether there is a statement after the given if statement, and if * so, whether this is just a return boolean statement. - * + * * @param node * the if statement * @return @@ -173,7 +175,7 @@ private boolean isJustReturnsBooleanAfter(ASTIfStatement ifNode) { /** * Checks whether the given ifstatement just returns a boolean in the if * clause. - * + * * @param node * the if statement * @return diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/SingleMethodSingletonRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/SingleMethodSingletonRule.java deleted file mode 100644 index 8a4fb3c43f5..00000000000 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/SingleMethodSingletonRule.java +++ /dev/null @@ -1,38 +0,0 @@ -/** - * BSD-style license; for more info see http://pmd.sourceforge.net/license.html - */ - -package net.sourceforge.pmd.lang.java.rule.design; - -import java.util.HashSet; -import java.util.Set; - -import net.sourceforge.pmd.lang.java.ast.ASTCompilationUnit; -import net.sourceforge.pmd.lang.java.ast.ASTMethodDeclaration; -import net.sourceforge.pmd.lang.java.rule.AbstractJavaRule; - -public class SingleMethodSingletonRule extends AbstractJavaRule { - - private Set methodset = new HashSet(); - - @Override - public Object visit(ASTCompilationUnit node, Object data) { - methodset.clear(); - return super.visit(node, data); - } - - @Override - public Object visit(ASTMethodDeclaration node, Object data) { - if (node.getResultType().isVoid()) { - return super.visit(node, data); - } - - if ("getInstance".equals(node.getMethodName())) { - if (!methodset.add(node.getMethodName())) { - addViolation(data, node); - } - } - - return super.visit(node, data); - } -} diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/SingularFieldRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/SingularFieldRule.java index 6d0d4e08372..0914ecc49a2 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/SingularFieldRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/SingularFieldRule.java @@ -4,7 +4,6 @@ package net.sourceforge.pmd.lang.java.rule.design; -import java.util.ArrayList; import java.util.List; import net.sourceforge.pmd.lang.ast.Node; @@ -52,7 +51,7 @@ public Object visit(ASTFieldDeclaration node, Object data) { boolean checkInnerClasses = getProperty(CHECK_INNER_CLASSES); boolean disallowNotAssignment = getProperty(DISALLOW_NOT_ASSIGNMENT); - if (node.isPrivate() && !node.isStatic() && !hasClassLombokAnnotation() && !hasLombokAnnotation(node)) { + if (node.isPrivate() && !node.isStatic() && !hasClassLombokAnnotation() && !hasIgnoredAnnotation(node)) { for (ASTVariableDeclarator declarator : node.findChildrenOfType(ASTVariableDeclarator.class)) { ASTVariableDeclaratorId declaration = (ASTVariableDeclaratorId) declarator.jjtGetChild(0); List usages = declaration.getUsages(); @@ -161,8 +160,7 @@ public Object visit(ASTFieldDeclaration node, Object data) { private boolean isInAssignment(Node potentialStatement) { if (potentialStatement instanceof ASTStatementExpression) { ASTStatementExpression statement = (ASTStatementExpression) potentialStatement; - List assignments = new ArrayList<>(); - statement.findDescendantsOfType(ASTAssignmentOperator.class, assignments, false); + List assignments = statement.findDescendantsOfType(ASTAssignmentOperator.class); return !assignments.isEmpty() && "=".equals(assignments.get(0).getImage()); } else { return false; diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/StdCyclomaticComplexityRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/StdCyclomaticComplexityRule.java new file mode 100644 index 00000000000..65703cd2040 --- /dev/null +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/StdCyclomaticComplexityRule.java @@ -0,0 +1,247 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.design; + +import java.util.ArrayDeque; +import java.util.Deque; + +import net.sourceforge.pmd.lang.ast.Node; +import net.sourceforge.pmd.lang.java.ast.ASTBlockStatement; +import net.sourceforge.pmd.lang.java.ast.ASTCatchStatement; +import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceDeclaration; +import net.sourceforge.pmd.lang.java.ast.ASTCompilationUnit; +import net.sourceforge.pmd.lang.java.ast.ASTConditionalExpression; +import net.sourceforge.pmd.lang.java.ast.ASTConstructorDeclaration; +import net.sourceforge.pmd.lang.java.ast.ASTDoStatement; +import net.sourceforge.pmd.lang.java.ast.ASTEnumDeclaration; +import net.sourceforge.pmd.lang.java.ast.ASTForStatement; +import net.sourceforge.pmd.lang.java.ast.ASTIfStatement; +import net.sourceforge.pmd.lang.java.ast.ASTMethodDeclaration; +import net.sourceforge.pmd.lang.java.ast.ASTMethodDeclarator; +import net.sourceforge.pmd.lang.java.ast.ASTSwitchLabel; +import net.sourceforge.pmd.lang.java.ast.ASTSwitchStatement; +import net.sourceforge.pmd.lang.java.ast.ASTWhileStatement; +import net.sourceforge.pmd.lang.java.rule.AbstractJavaRule; +import net.sourceforge.pmd.properties.BooleanProperty; +import net.sourceforge.pmd.properties.IntegerProperty; + +/** + * Implements the standard cyclomatic complexity rule + *

+ * Standard rules: +1 for each decision point, including case statements but not + * including boolean operators unlike CyclomaticComplexityRule. + * + * @author Alan Hohn, based on work by Donald A. Leckie + * + * @since June 18, 2014 + */ +@Deprecated +public class StdCyclomaticComplexityRule extends AbstractJavaRule { + + public static final IntegerProperty REPORT_LEVEL_DESCRIPTOR + = IntegerProperty.named("reportLevel") + .desc("Cyclomatic Complexity reporting threshold") + .range(1, 30).defaultValue(10).uiOrder(1.0f).build(); + + public static final BooleanProperty SHOW_CLASSES_COMPLEXITY_DESCRIPTOR = new BooleanProperty( + "showClassesComplexity", "Add class average violations to the report", true, 2.0f); + + public static final BooleanProperty SHOW_METHODS_COMPLEXITY_DESCRIPTOR = new BooleanProperty( + "showMethodsComplexity", "Add method average violations to the report", true, 3.0f); + + private int reportLevel; + private boolean showClassesComplexity = true; + private boolean showMethodsComplexity = true; + + protected static class Entry { + private Node node; + private int decisionPoints = 1; + public int highestDecisionPoints; + public int methodCount; + + private Entry(Node node) { + this.node = node; + } + + public void bumpDecisionPoints() { + decisionPoints++; + } + + public void bumpDecisionPoints(int size) { + decisionPoints += size; + } + + public int getComplexityAverage() { + return (double) methodCount == 0 ? 1 : (int) Math.rint((double) decisionPoints / (double) methodCount); + } + } + + protected Deque entryStack = new ArrayDeque<>(); + + public StdCyclomaticComplexityRule() { + definePropertyDescriptor(REPORT_LEVEL_DESCRIPTOR); + definePropertyDescriptor(SHOW_CLASSES_COMPLEXITY_DESCRIPTOR); + definePropertyDescriptor(SHOW_METHODS_COMPLEXITY_DESCRIPTOR); + } + + @Override + public Object visit(ASTCompilationUnit node, Object data) { + reportLevel = getProperty(REPORT_LEVEL_DESCRIPTOR); + showClassesComplexity = getProperty(SHOW_CLASSES_COMPLEXITY_DESCRIPTOR); + showMethodsComplexity = getProperty(SHOW_METHODS_COMPLEXITY_DESCRIPTOR); + super.visit(node, data); + return data; + } + + @Override + public Object visit(ASTIfStatement node, Object data) { + entryStack.peek().bumpDecisionPoints(); + super.visit(node, data); + return data; + } + + @Override + public Object visit(ASTCatchStatement node, Object data) { + entryStack.peek().bumpDecisionPoints(); + super.visit(node, data); + return data; + } + + @Override + public Object visit(ASTForStatement node, Object data) { + entryStack.peek().bumpDecisionPoints(); + super.visit(node, data); + return data; + } + + @Override + public Object visit(ASTDoStatement node, Object data) { + entryStack.peek().bumpDecisionPoints(); + super.visit(node, data); + return data; + } + + @Override + public Object visit(ASTSwitchStatement node, Object data) { + Entry entry = entryStack.peek(); + + int childCount = node.jjtGetNumChildren(); + int lastIndex = childCount - 1; + for (int n = 0; n < lastIndex; n++) { + Node childNode = node.jjtGetChild(n); + if (childNode instanceof ASTSwitchLabel) { + // default is generally not considered a decision (same as + // "else") + ASTSwitchLabel sl = (ASTSwitchLabel) childNode; + if (!sl.isDefault()) { + childNode = node.jjtGetChild(n + 1); + if (childNode instanceof ASTBlockStatement) { + entry.bumpDecisionPoints(); + } + } + } + } + super.visit(node, data); + return data; + } + + @Override + public Object visit(ASTWhileStatement node, Object data) { + entryStack.peek().bumpDecisionPoints(); + super.visit(node, data); + return data; + } + + @Override + public Object visit(ASTConditionalExpression node, Object data) { + entryStack.peek().bumpDecisionPoints(); + super.visit(node, data); + return data; + } + + @Override + public Object visit(ASTClassOrInterfaceDeclaration node, Object data) { + if (node.isInterface()) { + return data; + } + + entryStack.push(new Entry(node)); + super.visit(node, data); + Entry classEntry = entryStack.pop(); + if (showClassesComplexity) { + if (classEntry.getComplexityAverage() >= reportLevel || classEntry.highestDecisionPoints >= reportLevel) { + addViolation(data, node, new String[] { "class", node.getImage(), + classEntry.getComplexityAverage() + " (Highest = " + classEntry.highestDecisionPoints + ')', }); + } + } + return data; + } + + @Override + public Object visit(ASTMethodDeclaration node, Object data) { + entryStack.push(new Entry(node)); + super.visit(node, data); + Entry methodEntry = entryStack.pop(); + if (!isSuppressed(node)) { + int methodDecisionPoints = methodEntry.decisionPoints; + Entry classEntry = entryStack.peek(); + classEntry.methodCount++; + classEntry.bumpDecisionPoints(methodDecisionPoints); + + if (methodDecisionPoints > classEntry.highestDecisionPoints) { + classEntry.highestDecisionPoints = methodDecisionPoints; + } + + ASTMethodDeclarator methodDeclarator = null; + for (int n = 0; n < node.jjtGetNumChildren(); n++) { + Node childNode = node.jjtGetChild(n); + if (childNode instanceof ASTMethodDeclarator) { + methodDeclarator = (ASTMethodDeclarator) childNode; + break; + } + } + + if (showMethodsComplexity && methodEntry.decisionPoints >= reportLevel) { + addViolation(data, node, + new String[] { "method", methodDeclarator == null ? "" : methodDeclarator.getImage(), + String.valueOf(methodEntry.decisionPoints), }); + } + } + return data; + } + + @Override + public Object visit(ASTEnumDeclaration node, Object data) { + entryStack.push(new Entry(node)); + super.visit(node, data); + Entry classEntry = entryStack.pop(); + if (classEntry.getComplexityAverage() >= reportLevel || classEntry.highestDecisionPoints >= reportLevel) { + addViolation(data, node, new String[] { "class", node.getImage(), + classEntry.getComplexityAverage() + "(Highest = " + classEntry.highestDecisionPoints + ')', }); + } + return data; + } + + @Override + public Object visit(ASTConstructorDeclaration node, Object data) { + entryStack.push(new Entry(node)); + super.visit(node, data); + Entry constructorEntry = entryStack.pop(); + if (!isSuppressed(node)) { + int constructorDecisionPointCount = constructorEntry.decisionPoints; + Entry classEntry = entryStack.peek(); + classEntry.methodCount++; + classEntry.decisionPoints += constructorDecisionPointCount; + if (constructorDecisionPointCount > classEntry.highestDecisionPoints) { + classEntry.highestDecisionPoints = constructorDecisionPointCount; + } + if (showMethodsComplexity && constructorEntry.decisionPoints >= reportLevel) { + addViolation(data, node, new String[] { "constructor", classEntry.node.getImage(), + String.valueOf(constructorDecisionPointCount), }); + } + } + return data; + } +} diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/SwitchDensityRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/SwitchDensityRule.java index 0f07c0a8264..3299c585d16 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/SwitchDensityRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/SwitchDensityRule.java @@ -55,6 +55,7 @@ public SwitchDensityRule() { setProperty(MINIMUM_DESCRIPTOR, 10d); } + @Override public Object visit(ASTSwitchStatement node, Object data) { SwitchDensity oldData = null; @@ -79,6 +80,7 @@ public Object visit(ASTSwitchStatement node, Object data) { return oldData; } + @Override public Object visit(ASTStatement statement, Object data) { if (data instanceof SwitchDensity) { ((SwitchDensity) data).addStatement(); @@ -89,6 +91,7 @@ public Object visit(ASTStatement statement, Object data) { return data; } + @Override public Object visit(ASTSwitchLabel switchLabel, Object data) { if (data instanceof SwitchDensity) { ((SwitchDensity) data).addSwitchLabel(); diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/TooManyFieldsRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/TooManyFieldsRule.java new file mode 100644 index 00000000000..b0fc794f689 --- /dev/null +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/TooManyFieldsRule.java @@ -0,0 +1,46 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.design; + +import java.util.List; + +import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceDeclaration; +import net.sourceforge.pmd.lang.java.ast.ASTFieldDeclaration; +import net.sourceforge.pmd.lang.java.rule.AbstractJavaRule; +import net.sourceforge.pmd.properties.IntegerProperty; + +public class TooManyFieldsRule extends AbstractJavaRule { + + private static final int DEFAULT_MAXFIELDS = 15; + + private static final IntegerProperty MAX_FIELDS_DESCRIPTOR = new IntegerProperty("maxfields", + "Max allowable fields", 1, 300, DEFAULT_MAXFIELDS, 1.0f); + + public TooManyFieldsRule() { + definePropertyDescriptor(MAX_FIELDS_DESCRIPTOR); + addRuleChainVisit(ASTClassOrInterfaceDeclaration.class); + } + + @Override + public Object visit(ASTClassOrInterfaceDeclaration node, Object data) { + final int maxFields = getProperty(MAX_FIELDS_DESCRIPTOR); + int counter = 0; + + final List l = node.findDescendantsOfType(ASTFieldDeclaration.class); + + for (ASTFieldDeclaration fd : l) { + if (fd.isFinal() && fd.isStatic()) { + continue; + } + counter++; + } + + if (counter > maxFields) { + addViolation(data, node); + } + + return data; + } +} diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/UseUtilityClassRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/UseUtilityClassRule.java index 00012eb06ce..6273d61bd0e 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/UseUtilityClassRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/UseUtilityClassRule.java @@ -4,27 +4,39 @@ package net.sourceforge.pmd.lang.java.rule.design; +import java.util.List; + import net.sourceforge.pmd.lang.ast.Node; import net.sourceforge.pmd.lang.java.ast.ASTAnnotation; import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceBody; import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceDeclaration; import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceType; import net.sourceforge.pmd.lang.java.ast.ASTConstructorDeclaration; -import net.sourceforge.pmd.lang.java.ast.ASTExtendsList; import net.sourceforge.pmd.lang.java.ast.ASTFieldDeclaration; +import net.sourceforge.pmd.lang.java.ast.ASTMemberValuePair; import net.sourceforge.pmd.lang.java.ast.ASTMethodDeclaration; +import net.sourceforge.pmd.lang.java.ast.ASTName; import net.sourceforge.pmd.lang.java.ast.ASTResultType; import net.sourceforge.pmd.lang.java.rule.AbstractJavaRule; public class UseUtilityClassRule extends AbstractJavaRule { + public UseUtilityClassRule() { + addRuleChainVisit(ASTClassOrInterfaceBody.class); + } + @Override public Object visit(ASTClassOrInterfaceBody decl, Object data) { if (decl.jjtGetParent() instanceof ASTClassOrInterfaceDeclaration) { ASTClassOrInterfaceDeclaration parent = (ASTClassOrInterfaceDeclaration) decl.jjtGetParent(); - if (parent.isAbstract() || parent.isInterface() || isExceptionType(parent)) { - return super.visit(decl, data); + if (parent.isAbstract() || parent.isInterface() || parent.getSuperClassTypeNode() != null) { + return data; } + + if (isOkUsingLombok(parent)) { + return data; + } + int i = decl.jjtGetNumChildren(); int methodCount = 0; boolean isOK = false; @@ -63,14 +75,40 @@ public Object visit(ASTClassOrInterfaceBody decl, Object data) { break; } } - } } if (!isOK && methodCount > 0) { addViolation(data, decl); } } - return super.visit(decl, data); + return data; + } + + private boolean isOkUsingLombok(ASTClassOrInterfaceDeclaration parent) { + // check if there's a lombok no arg private constructor, if so skip the rest of the rules + ASTAnnotation annotation = parent.getAnnotation("lombok.NoArgsConstructor"); + + if (annotation != null) { + + List memberValuePairs = annotation.findDescendantsOfType(ASTMemberValuePair.class); + + for (ASTMemberValuePair memberValuePair : memberValuePairs) { + // to set the access level of a constructor in lombok, you set the access property on the annotation + if ("access".equals(memberValuePair.getImage())) { + List names = memberValuePair.findDescendantsOfType(ASTName.class); + + for (ASTName name : names) { + // check to see if the value of the member value pair ends PRIVATE. This is from the AccessLevel enum in Lombok + if (name.getImage().endsWith("PRIVATE")) { + // if the constructor is found and the accesslevel is private no need to check anything else + return true; + } + } + } + } + } + + return false; } private Node skipAnnotations(Node p) { @@ -81,19 +119,4 @@ private Node skipAnnotations(Node p) { } return n; } - - private boolean isExceptionType(ASTClassOrInterfaceDeclaration parent) { - ASTExtendsList extendsList = parent.getFirstChildOfType(ASTExtendsList.class); - if (extendsList != null) { - ASTClassOrInterfaceType superClass = extendsList.getFirstChildOfType(ASTClassOrInterfaceType.class); - if (superClass.getType() != null && Throwable.class.isAssignableFrom(superClass.getType())) { - return true; - } - if (superClass.getType() == null && superClass.getImage().endsWith("Exception")) { - return true; - } - } - return false; - } - } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/unnecessary/UselessOverridingMethodRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/UselessOverridingMethodRule.java similarity index 99% rename from pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/unnecessary/UselessOverridingMethodRule.java rename to pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/UselessOverridingMethodRule.java index 9c819e50a87..87e2127a956 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/unnecessary/UselessOverridingMethodRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/UselessOverridingMethodRule.java @@ -2,7 +2,7 @@ * BSD-style license; for more info see http://pmd.sourceforge.net/license.html */ -package net.sourceforge.pmd.lang.java.rule.unnecessary; +package net.sourceforge.pmd.lang.java.rule.design; import java.util.ArrayList; import java.util.List; diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/documentation/AbstractCommentRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/documentation/AbstractCommentRule.java new file mode 100644 index 00000000000..b93c080e7a8 --- /dev/null +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/documentation/AbstractCommentRule.java @@ -0,0 +1,131 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.documentation; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.SortedMap; +import java.util.TreeMap; + +import net.sourceforge.pmd.lang.ast.Node; +import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceBody; +import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceDeclaration; +import net.sourceforge.pmd.lang.java.ast.ASTCompilationUnit; +import net.sourceforge.pmd.lang.java.ast.ASTConstructorDeclaration; +import net.sourceforge.pmd.lang.java.ast.ASTEnumDeclaration; +import net.sourceforge.pmd.lang.java.ast.ASTFieldDeclaration; +import net.sourceforge.pmd.lang.java.ast.ASTMethodDeclaration; +import net.sourceforge.pmd.lang.java.ast.AbstractJavaAccessNode; +import net.sourceforge.pmd.lang.java.ast.AbstractJavaAccessTypeNode; +import net.sourceforge.pmd.lang.java.ast.Comment; +import net.sourceforge.pmd.lang.java.ast.CommentUtil; +import net.sourceforge.pmd.lang.java.ast.FormalComment; +import net.sourceforge.pmd.lang.java.ast.JavadocElement; +import net.sourceforge.pmd.lang.java.javadoc.JavadocTag; +import net.sourceforge.pmd.lang.java.rule.AbstractJavaRule; + +/** + * + * @author Brian Remedios + */ +public abstract class AbstractCommentRule extends AbstractJavaRule { + + /** + * Returns a list of indices of javadoc tag occurrences in the comment. + * + *

Note: if the same tag occurs multiple times, only the last occurrence is returned. + * + * @param comments the complete comment text + * @return list of indices. + * + * @deprecated This method is deprecated and will be removed with PMD 7.0.0. + * It is not very useful, since it doesn't extract the information + * in a useful way. You would still need check, which tags have been found, and with which + * data they might be accompanied. + * A more useful solution will be added around the AST node {@link FormalComment}, + * which contains as children {@link JavadocElement} nodes, which in + * turn provide access to the {@link JavadocTag}. + */ + @Deprecated // the method will be removed with PMD 7.0.0 + protected List tagsIndicesIn(String comments) { + Map tags = CommentUtil.javadocTagsIn(comments); + return new ArrayList<>(tags.values()); + } + + protected String filteredCommentIn(Comment comment) { + return comment.getFilteredComment(); + } + + protected void assignCommentsToDeclarations(ASTCompilationUnit cUnit) { + + SortedMap itemsByLineNumber = orderedCommentsAndDeclarations(cUnit); + FormalComment lastComment = null; + AbstractJavaAccessNode lastNode = null; + + for (Entry entry : itemsByLineNumber.entrySet()) { + Node value = entry.getValue(); + + if (value instanceof AbstractJavaAccessNode) { + AbstractJavaAccessNode node = (AbstractJavaAccessNode) value; + + // maybe the last comment is within the last node + if (lastComment != null && isCommentNotWithin(lastComment, lastNode, node) + && isCommentBefore(lastComment, node)) { + node.comment(lastComment); + lastComment = null; + } + if (!(node instanceof AbstractJavaAccessTypeNode)) { + lastNode = node; + } + } else if (value instanceof FormalComment) { + lastComment = (FormalComment) value; + } + } + } + + private boolean isCommentNotWithin(FormalComment n1, Node n2, Node node) { + if (n1 == null || n2 == null || node == null) { + return true; + } + boolean isNotWithinNode2 = !(n1.getEndLine() < n2.getEndLine() + || n1.getEndLine() == n2.getEndLine() && n1.getEndColumn() < n2.getEndColumn()); + boolean isNotSameClass = node.getFirstParentOfType(ASTClassOrInterfaceBody.class) != n2 + .getFirstParentOfType(ASTClassOrInterfaceBody.class); + boolean isNodeWithinNode2 = node.getEndLine() < n2.getEndLine() + || node.getEndLine() == n2.getEndLine() && node.getEndColumn() < n2.getEndColumn(); + return isNotWithinNode2 || isNotSameClass || isNodeWithinNode2; + } + + private boolean isCommentBefore(FormalComment n1, Node n2) { + return n1.getEndLine() < n2.getBeginLine() + || n1.getEndLine() == n2.getBeginLine() && n1.getEndColumn() < n2.getBeginColumn(); + } + + protected SortedMap orderedCommentsAndDeclarations(ASTCompilationUnit cUnit) { + SortedMap itemsByLineNumber = new TreeMap<>(); + + addDeclarations(itemsByLineNumber, cUnit.findDescendantsOfType(ASTClassOrInterfaceDeclaration.class, true)); + + addDeclarations(itemsByLineNumber, cUnit.getComments()); + + addDeclarations(itemsByLineNumber, cUnit.findDescendantsOfType(ASTFieldDeclaration.class, true)); + + addDeclarations(itemsByLineNumber, cUnit.findDescendantsOfType(ASTMethodDeclaration.class, true)); + + addDeclarations(itemsByLineNumber, cUnit.findDescendantsOfType(ASTConstructorDeclaration.class, true)); + + addDeclarations(itemsByLineNumber, cUnit.findDescendantsOfType(ASTEnumDeclaration.class, true)); + + return itemsByLineNumber; + } + + private void addDeclarations(SortedMap map, List nodes) { + for (Node node : nodes) { + map.put((node.getBeginLine() << 16) + node.getBeginColumn(), node); + } + } +} diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/comments/CodeInCommentsRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/documentation/CodeInCommentsRule.java similarity index 86% rename from pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/comments/CodeInCommentsRule.java rename to pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/documentation/CodeInCommentsRule.java index a9fb3ce76ab..e7e37d5a1d3 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/comments/CodeInCommentsRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/documentation/CodeInCommentsRule.java @@ -2,7 +2,7 @@ * BSD-style license; for more info see http://pmd.sourceforge.net/license.html */ -package net.sourceforge.pmd.lang.java.rule.comments; +package net.sourceforge.pmd.lang.java.rule.documentation; /** * diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/comments/CommentContentRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/documentation/CommentContentRule.java similarity index 86% rename from pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/comments/CommentContentRule.java rename to pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/documentation/CommentContentRule.java index dc796639ed8..bdbbcf55764 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/comments/CommentContentRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/documentation/CommentContentRule.java @@ -2,12 +2,13 @@ * BSD-style license; for more info see http://pmd.sourceforge.net/license.html */ -package net.sourceforge.pmd.lang.java.rule.comments; +package net.sourceforge.pmd.lang.java.rule.documentation; import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; import java.util.List; +import java.util.Locale; import java.util.Set; import org.apache.commons.lang3.StringUtils; @@ -31,16 +32,12 @@ public class CommentContentRule extends AbstractCommentRule { private boolean caseSensitive; - private boolean wordsAreRegex; private List originalBadWords; private List currentBadWords; // FIXME need some better defaults (or none?) private static final String[] BAD_WORDS = {"idiot", "jerk" }; - public static final BooleanProperty WORDS_ARE_REGEX_DESCRIPTOR = new BooleanProperty("wordsAreRegex", - "Use regular expressions", false, 1.0f); - // ignored when property above == True public static final BooleanProperty CASE_SENSITIVE_DESCRIPTOR = new BooleanProperty("caseSensitive", "Case sensitive", false, 2.0f); @@ -56,7 +53,6 @@ public class CommentContentRule extends AbstractCommentRule { } public CommentContentRule() { - definePropertyDescriptor(WORDS_ARE_REGEX_DESCRIPTOR); definePropertyDescriptor(CASE_SENSITIVE_DESCRIPTOR); definePropertyDescriptor(DISSALLOWED_TERMS_DESCRIPTOR); } @@ -66,7 +62,6 @@ public CommentContentRule() { */ @Override public void start(RuleContext ctx) { - wordsAreRegex = getProperty(WORDS_ARE_REGEX_DESCRIPTOR); originalBadWords = getProperty(DISSALLOWED_TERMS_DESCRIPTOR); caseSensitive = getProperty(CASE_SENSITIVE_DESCRIPTOR); if (caseSensitive) { @@ -74,17 +69,11 @@ public void start(RuleContext ctx) { } else { currentBadWords = new ArrayList<>(); for (String badWord : originalBadWords) { - currentBadWords.add(badWord.toUpperCase()); + currentBadWords.add(badWord.toUpperCase(Locale.ROOT)); } } } - @Override - public Set> ignoredProperties() { - return getProperty(WORDS_ARE_REGEX_DESCRIPTOR) ? NON_REGEX_PROPERTIES - : Collections.>emptySet(); - } - /** * . * @see Rule#end(RuleContext) @@ -106,7 +95,7 @@ private List illegalTermsIn(Comment comment) { } if (!caseSensitive) { - commentText = commentText.toUpperCase(); + commentText = commentText.toUpperCase(Locale.ROOT); } List foundWords = new ArrayList<>(); diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/documentation/CommentRequiredRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/documentation/CommentRequiredRule.java new file mode 100644 index 00000000000..77b4870feb5 --- /dev/null +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/documentation/CommentRequiredRule.java @@ -0,0 +1,253 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.documentation; + + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; + +import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceDeclaration; +import net.sourceforge.pmd.lang.java.ast.ASTCompilationUnit; +import net.sourceforge.pmd.lang.java.ast.ASTConstructorDeclaration; +import net.sourceforge.pmd.lang.java.ast.ASTEnumDeclaration; +import net.sourceforge.pmd.lang.java.ast.ASTFieldDeclaration; +import net.sourceforge.pmd.lang.java.ast.ASTMarkerAnnotation; +import net.sourceforge.pmd.lang.java.ast.ASTMethodDeclaration; +import net.sourceforge.pmd.lang.java.ast.ASTName; +import net.sourceforge.pmd.lang.java.ast.AbstractJavaAccessNode; +import net.sourceforge.pmd.lang.java.ast.AbstractJavaNode; +import net.sourceforge.pmd.lang.java.multifile.signature.JavaOperationSignature; +import net.sourceforge.pmd.properties.EnumeratedProperty; +import net.sourceforge.pmd.properties.EnumeratedProperty.EnumPBuilder; + + +/** + * @author Brian Remedios + */ +public class CommentRequiredRule extends AbstractCommentRule { + + // Used to pretty print a message + private static final Map DESCRIPTOR_NAME_TO_COMMENT_TYPE = new HashMap<>(); + + + private static final EnumeratedProperty ACCESSOR_CMT_DESCRIPTOR + = requirementPropertyBuilder("accessorCommentRequirement", "Comments on getters and setters\"") + .defaultValue(CommentRequirement.Ignored).build(); + private static final EnumeratedProperty OVERRIDE_CMT_DESCRIPTOR + = requirementPropertyBuilder("methodWithOverrideCommentRequirement", "Comments on @Override methods") + .defaultValue(CommentRequirement.Ignored).build(); + private static final EnumeratedProperty HEADER_CMT_REQUIREMENT_DESCRIPTOR + = requirementPropertyBuilder("headerCommentRequirement", "Header comments").uiOrder(1.0f).build(); + private static final EnumeratedProperty FIELD_CMT_REQUIREMENT_DESCRIPTOR + = requirementPropertyBuilder("fieldCommentRequirement", "Field comments").uiOrder(2.0f).build(); + private static final EnumeratedProperty PUB_METHOD_CMT_REQUIREMENT_DESCRIPTOR + = requirementPropertyBuilder("publicMethodCommentRequirement", "Public method and constructor comments") + .uiOrder(3.0f).build(); + private static final EnumeratedProperty PROT_METHOD_CMT_REQUIREMENT_DESCRIPTOR + = requirementPropertyBuilder("protectedMethodCommentRequirement", "Protected method constructor comments") + .uiOrder(4.0f).build(); + private static final EnumeratedProperty ENUM_CMT_REQUIREMENT_DESCRIPTOR + = requirementPropertyBuilder("enumCommentRequirement", "Enum comments").uiOrder(5.0f).build(); + private static final EnumeratedProperty SERIAL_VERSION_UID_CMT_REQUIREMENT_DESCRIPTOR + = requirementPropertyBuilder("serialVersionUIDCommentRequired", "Serial version UID comments") + .defaultValue(CommentRequirement.Ignored).uiOrder(6.0f).build(); + + + public CommentRequiredRule() { + definePropertyDescriptor(OVERRIDE_CMT_DESCRIPTOR); + definePropertyDescriptor(ACCESSOR_CMT_DESCRIPTOR); + definePropertyDescriptor(HEADER_CMT_REQUIREMENT_DESCRIPTOR); + definePropertyDescriptor(FIELD_CMT_REQUIREMENT_DESCRIPTOR); + definePropertyDescriptor(PUB_METHOD_CMT_REQUIREMENT_DESCRIPTOR); + definePropertyDescriptor(PROT_METHOD_CMT_REQUIREMENT_DESCRIPTOR); + definePropertyDescriptor(ENUM_CMT_REQUIREMENT_DESCRIPTOR); + definePropertyDescriptor(SERIAL_VERSION_UID_CMT_REQUIREMENT_DESCRIPTOR); + } + + + private void checkCommentMeetsRequirement(Object data, AbstractJavaNode node, + EnumeratedProperty descriptor) { + switch (getProperty(descriptor)) { + case Ignored: + break; + case Required: + if (node.comment() == null) { + commentRequiredViolation(data, node, descriptor); + } + break; + case Unwanted: + if (node.comment() != null) { + commentRequiredViolation(data, node, descriptor); + } + break; + default: + break; + } + } + + + // Adds a violation + private void commentRequiredViolation(Object data, AbstractJavaNode node, + EnumeratedProperty descriptor) { + + + addViolationWithMessage(data, node, + DESCRIPTOR_NAME_TO_COMMENT_TYPE.get(descriptor.name()) + + " are " + + getProperty(descriptor).label.toLowerCase(Locale.ROOT)); + } + + + @Override + public Object visit(ASTClassOrInterfaceDeclaration decl, Object data) { + checkCommentMeetsRequirement(data, decl, HEADER_CMT_REQUIREMENT_DESCRIPTOR); + return super.visit(decl, data); + } + + + @Override + public Object visit(ASTConstructorDeclaration decl, Object data) { + checkMethodOrConstructorComment(decl, data); + return super.visit(decl, data); + } + + + @Override + public Object visit(ASTMethodDeclaration decl, Object data) { + if (isAnnotatedOverride(decl)) { + checkCommentMeetsRequirement(data, decl, OVERRIDE_CMT_DESCRIPTOR); + } else if (decl.getSignature().role == JavaOperationSignature.Role.GETTER_OR_SETTER) { + checkCommentMeetsRequirement(data, decl, ACCESSOR_CMT_DESCRIPTOR); + } else { + checkMethodOrConstructorComment(decl, data); + } + return super.visit(decl, data); + } + + + private void checkMethodOrConstructorComment(AbstractJavaAccessNode decl, Object data) { + if (decl.isPublic()) { + checkCommentMeetsRequirement(data, decl, PUB_METHOD_CMT_REQUIREMENT_DESCRIPTOR); + } else if (decl.isProtected()) { + checkCommentMeetsRequirement(data, decl, PROT_METHOD_CMT_REQUIREMENT_DESCRIPTOR); + } + } + + + private boolean isAnnotatedOverride(ASTMethodDeclaration decl) { + List annotations = decl.jjtGetParent().findDescendantsOfType(ASTMarkerAnnotation.class); + for (ASTMarkerAnnotation ann : annotations) { // TODO consider making a method to get the annotations of a method + if (ann.getFirstChildOfType(ASTName.class).getImage().equals("Override")) { + return true; + } + } + return false; + } + + + @Override + public Object visit(ASTFieldDeclaration decl, Object data) { + if (isSerialVersionUID(decl)) { + checkCommentMeetsRequirement(data, decl, SERIAL_VERSION_UID_CMT_REQUIREMENT_DESCRIPTOR); + } else { + checkCommentMeetsRequirement(data, decl, FIELD_CMT_REQUIREMENT_DESCRIPTOR); + } + + return super.visit(decl, data); + } + + + private boolean isSerialVersionUID(ASTFieldDeclaration field) { + return "serialVersionUID".equals(field.getVariableName()) + && field.isStatic() + && field.isFinal() + && field.getType() == long.class; + } + + + @Override + public Object visit(ASTEnumDeclaration decl, Object data) { + checkCommentMeetsRequirement(data, decl, ENUM_CMT_REQUIREMENT_DESCRIPTOR); + return super.visit(decl, data); + } + + + @Override + public Object visit(ASTCompilationUnit cUnit, Object data) { + assignCommentsToDeclarations(cUnit); + return super.visit(cUnit, data); + } + + private boolean allCommentsAreIgnored() { + + return getProperty(OVERRIDE_CMT_DESCRIPTOR) == CommentRequirement.Ignored + && getProperty(ACCESSOR_CMT_DESCRIPTOR) == CommentRequirement.Ignored + && getProperty(HEADER_CMT_REQUIREMENT_DESCRIPTOR) == CommentRequirement.Ignored + && getProperty(FIELD_CMT_REQUIREMENT_DESCRIPTOR) == CommentRequirement.Ignored + && getProperty(PUB_METHOD_CMT_REQUIREMENT_DESCRIPTOR) == CommentRequirement.Ignored + && getProperty(PROT_METHOD_CMT_REQUIREMENT_DESCRIPTOR) == CommentRequirement.Ignored + && getProperty(ENUM_CMT_REQUIREMENT_DESCRIPTOR) == CommentRequirement.Ignored + && getProperty(SERIAL_VERSION_UID_CMT_REQUIREMENT_DESCRIPTOR) == CommentRequirement.Ignored; + } + + @Override + public String dysfunctionReason() { + return allCommentsAreIgnored() ? "All comment types are ignored" : null; + } + + private enum CommentRequirement { + Required("Required"), Ignored("Ignored"), Unwanted("Unwanted"); + + private static final List LABELS = buildValueLabels(); + private static final Map MAPPINGS; + private final String label; + + static { + Map tmp = new HashMap<>(); + for (CommentRequirement r : values()) { + tmp.put(r.label, r); + } + MAPPINGS = Collections.unmodifiableMap(tmp); + } + + CommentRequirement(String theLabel) { + label = theLabel; + } + + + private static List buildValueLabels() { + List labels = new ArrayList<>(values().length); + for (CommentRequirement r : values()) { + labels.add(r.label); + } + return Collections.unmodifiableList(labels); + } + + + public static List labels() { + return LABELS; + } + + + public static Map mappings() { + return MAPPINGS; + } + } + + + // pre-filled builder + private static EnumPBuilder requirementPropertyBuilder(String name, String commentType) { + DESCRIPTOR_NAME_TO_COMMENT_TYPE.put(name, commentType); + return EnumeratedProperty.named(name) + .desc(commentType + ". Possible values: " + CommentRequirement.labels()) + .mappings(CommentRequirement.mappings()) + .defaultValue(CommentRequirement.Required) + .type(CommentRequirement.class); + } +} diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/comments/CommentSizeRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/documentation/CommentSizeRule.java similarity index 84% rename from pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/comments/CommentSizeRule.java rename to pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/documentation/CommentSizeRule.java index eeeb7cdd0d1..4133699f0d0 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/comments/CommentSizeRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/documentation/CommentSizeRule.java @@ -2,7 +2,7 @@ * BSD-style license; for more info see http://pmd.sourceforge.net/license.html */ -package net.sourceforge.pmd.lang.java.rule.comments; +package net.sourceforge.pmd.lang.java.rule.documentation; import java.util.ArrayList; import java.util.List; @@ -21,9 +21,15 @@ */ public class CommentSizeRule extends AbstractCommentRule { - public static final IntegerProperty MAX_LINES = new IntegerProperty("maxLines", "Maximum lines", 2, 200, 6, 2.0f); - public static final IntegerProperty MAX_LINE_LENGTH = new IntegerProperty("maxLineLength", "Maximum line length", 1, - 200, 80, 2.0f); + public static final IntegerProperty MAX_LINES + = IntegerProperty.named("maxLines") + .desc("Maximum lines") + .range(2, 200).defaultValue(6).uiOrder(2.0f).build(); + + public static final IntegerProperty MAX_LINE_LENGTH + = IntegerProperty.named("maxLineLength") + .desc("Maximum line length") + .range(1, 200).defaultValue(80).uiOrder(2.0f).build(); private static final String CR = "\n"; diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/comments/HeaderCommentsRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/documentation/HeaderCommentsRule.java similarity index 97% rename from pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/comments/HeaderCommentsRule.java rename to pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/documentation/HeaderCommentsRule.java index e4ffc2c52e4..064e9c2f8ad 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/comments/HeaderCommentsRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/documentation/HeaderCommentsRule.java @@ -2,7 +2,7 @@ * BSD-style license; for more info see http://pmd.sourceforge.net/license.html */ -package net.sourceforge.pmd.lang.java.rule.comments; +package net.sourceforge.pmd.lang.java.rule.documentation; import net.sourceforge.pmd.lang.java.ast.ASTCompilationUnit; import net.sourceforge.pmd.properties.EnumeratedProperty; diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/documentation/JavadocRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/documentation/JavadocRule.java new file mode 100644 index 00000000000..e25c8a1f232 --- /dev/null +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/documentation/JavadocRule.java @@ -0,0 +1,9 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.documentation; + +public class JavadocRule extends AbstractCommentRule { + +} diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/controversial/AssignmentInOperandRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/AssignmentInOperandRule.java similarity index 91% rename from pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/controversial/AssignmentInOperandRule.java rename to pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/AssignmentInOperandRule.java index ad7cc4c2226..783b1c7cedb 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/controversial/AssignmentInOperandRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/AssignmentInOperandRule.java @@ -2,7 +2,7 @@ * BSD-style license; for more info see http://pmd.sourceforge.net/license.html */ -package net.sourceforge.pmd.lang.java.rule.controversial; +package net.sourceforge.pmd.lang.java.rule.errorprone; import net.sourceforge.pmd.lang.ast.Node; import net.sourceforge.pmd.lang.java.ast.ASTAssignmentOperator; @@ -53,8 +53,8 @@ public Object visit(ASTExpression node, Object data) { && !getProperty(ALLOW_FOR_DESCRIPTOR)) && (node.hasDescendantOfType(ASTAssignmentOperator.class) || !getProperty(ALLOW_INCREMENT_DECREMENT_DESCRIPTOR) - && (node.hasDecendantOfAnyType(ASTPreIncrementExpression.class, - ASTPreDecrementExpression.class, ASTPostfixExpression.class)))) { + && (node.hasDescendantOfAnyType(ASTPreIncrementExpression.class, + ASTPreDecrementExpression.class, ASTPostfixExpression.class)))) { addViolation(data, node); return data; diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/AssignmentToNonFinalStaticRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/AssignmentToNonFinalStaticRule.java similarity index 94% rename from pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/AssignmentToNonFinalStaticRule.java rename to pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/AssignmentToNonFinalStaticRule.java index 572cdc567e4..4870e31778e 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/AssignmentToNonFinalStaticRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/AssignmentToNonFinalStaticRule.java @@ -2,7 +2,7 @@ * BSD-style license; for more info see http://pmd.sourceforge.net/license.html */ -package net.sourceforge.pmd.lang.java.rule.design; +package net.sourceforge.pmd.lang.java.rule.errorprone; import java.util.List; import java.util.Map; @@ -22,12 +22,13 @@ */ public class AssignmentToNonFinalStaticRule extends AbstractJavaRule { + @Override public Object visit(ASTClassOrInterfaceDeclaration node, Object data) { Map> vars = node.getScope() .getDeclarations(VariableNameDeclaration.class); for (Map.Entry> entry : vars.entrySet()) { VariableNameDeclaration decl = entry.getKey(); - AccessNode accessNodeParent = (AccessNode) decl.getAccessNodeParent(); + AccessNode accessNodeParent = decl.getAccessNodeParent(); if (!accessNodeParent.isStatic() || accessNodeParent.isFinal()) { continue; } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/basic/AvoidBranchingStatementAsLastInLoopRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/AvoidBranchingStatementAsLastInLoopRule.java similarity index 94% rename from pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/basic/AvoidBranchingStatementAsLastInLoopRule.java rename to pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/AvoidBranchingStatementAsLastInLoopRule.java index 19185198816..4a897421bc9 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/basic/AvoidBranchingStatementAsLastInLoopRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/AvoidBranchingStatementAsLastInLoopRule.java @@ -2,7 +2,7 @@ * BSD-style license; for more info see http://pmd.sourceforge.net/license.html */ -package net.sourceforge.pmd.lang.java.rule.basic; +package net.sourceforge.pmd.lang.java.rule.errorprone; import java.util.Arrays; import java.util.Collections; @@ -118,7 +118,7 @@ public String dysfunctionReason() { public boolean checksNothing() { - return getProperty(CHECK_BREAK_LOOP_TYPES).size() == 0 && getProperty(CHECK_CONTINUE_LOOP_TYPES).size() == 0 - && getProperty(CHECK_RETURN_LOOP_TYPES).size() == 0; + return getProperty(CHECK_BREAK_LOOP_TYPES).isEmpty() && getProperty(CHECK_CONTINUE_LOOP_TYPES).isEmpty() + && getProperty(CHECK_RETURN_LOOP_TYPES).isEmpty(); } } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/finalizers/AvoidCallingFinalizeRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/AvoidCallingFinalizeRule.java similarity index 95% rename from pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/finalizers/AvoidCallingFinalizeRule.java rename to pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/AvoidCallingFinalizeRule.java index 76585fd3c00..28aa3b36a10 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/finalizers/AvoidCallingFinalizeRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/AvoidCallingFinalizeRule.java @@ -2,7 +2,7 @@ * BSD-style license; for more info see http://pmd.sourceforge.net/license.html */ -package net.sourceforge.pmd.lang.java.rule.finalizers; +package net.sourceforge.pmd.lang.java.rule.errorprone; import java.util.HashSet; import java.util.List; @@ -20,11 +20,13 @@ public class AvoidCallingFinalizeRule extends AbstractJavaRule { private Set checked = new HashSet<>(); + @Override public Object visit(ASTCompilationUnit acu, Object ctx) { checked.clear(); return super.visit(acu, ctx); } + @Override public Object visit(ASTName name, Object ctx) { if (name.getImage() == null || !name.getImage().endsWith("finalize")) { return ctx; @@ -36,6 +38,7 @@ public Object visit(ASTName name, Object ctx) { return ctx; } + @Override public Object visit(ASTPrimaryPrefix pp, Object ctx) { List primarySuffixes = pp.jjtGetParent().findChildrenOfType(ASTPrimarySuffix.class); ASTPrimarySuffix firstSuffix = null; diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/strictexception/AvoidCatchingThrowableRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/AvoidCatchingThrowableRule.java similarity index 94% rename from pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/strictexception/AvoidCatchingThrowableRule.java rename to pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/AvoidCatchingThrowableRule.java index 4b56f54d319..a0012de8893 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/strictexception/AvoidCatchingThrowableRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/AvoidCatchingThrowableRule.java @@ -2,7 +2,7 @@ * BSD-style license; for more info see http://pmd.sourceforge.net/license.html */ -package net.sourceforge.pmd.lang.java.rule.strictexception; +package net.sourceforge.pmd.lang.java.rule.errorprone; import net.sourceforge.pmd.lang.java.ast.ASTCatchStatement; import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceType; diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/strings/AvoidDuplicateLiteralsRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/AvoidDuplicateLiteralsRule.java similarity index 94% rename from pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/strings/AvoidDuplicateLiteralsRule.java rename to pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/AvoidDuplicateLiteralsRule.java index c5f31cf4a6e..9fde3df4d51 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/strings/AvoidDuplicateLiteralsRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/AvoidDuplicateLiteralsRule.java @@ -2,7 +2,7 @@ * BSD-style license; for more info see http://pmd.sourceforge.net/license.html */ -package net.sourceforge.pmd.lang.java.rule.strings; +package net.sourceforge.pmd.lang.java.rule.errorprone; import java.io.BufferedReader; import java.io.File; @@ -17,7 +17,6 @@ import java.util.Map; import java.util.Set; -import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.StringUtils; import net.sourceforge.pmd.lang.java.ast.ASTAnnotation; @@ -33,8 +32,10 @@ public class AvoidDuplicateLiteralsRule extends AbstractJavaRule { - public static final IntegerProperty THRESHOLD_DESCRIPTOR = new IntegerProperty("maxDuplicateLiterals", - "Max duplicate literals", 1, 20, 4, 1.0f); + public static final IntegerProperty THRESHOLD_DESCRIPTOR + = IntegerProperty.named("maxDuplicateLiterals") + .desc("Max duplicate literals") + .range(1, 20).defaultValue(4).uiOrder(1.0f).build(); public static final IntegerProperty MINIMUM_LENGTH_DESCRIPTOR = new IntegerProperty("minimumLength", "Minimum string length to check", 1, Integer.MAX_VALUE, 3, 1.5f); @@ -114,17 +115,13 @@ public Object visit(ASTCompilationUnit node, Object data) { exceptions = p.parse(getProperty(EXCEPTION_LIST_DESCRIPTOR)); } else if (getProperty(EXCEPTION_FILE_DESCRIPTOR) != null) { exceptions = new HashSet<>(); - LineNumberReader reader = null; - try { - reader = getLineReader(); + try (LineNumberReader reader = getLineReader()) { String line; while ((line = reader.readLine()) != null) { exceptions.add(line); } } catch (IOException ioe) { - ioe.printStackTrace(); - } finally { - IOUtils.closeQuietly(reader); + throw new RuntimeException(ioe); } } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/naming/AvoidFieldNameMatchingMethodNameRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/AvoidFieldNameMatchingMethodNameRule.java similarity index 93% rename from pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/naming/AvoidFieldNameMatchingMethodNameRule.java rename to pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/AvoidFieldNameMatchingMethodNameRule.java index 51fdc1fc15c..77fe17fc1e0 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/naming/AvoidFieldNameMatchingMethodNameRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/AvoidFieldNameMatchingMethodNameRule.java @@ -2,11 +2,12 @@ * BSD-style license; for more info see http://pmd.sourceforge.net/license.html */ -package net.sourceforge.pmd.lang.java.rule.naming; +package net.sourceforge.pmd.lang.java.rule.errorprone; import java.util.ArrayList; import java.util.HashSet; import java.util.List; +import java.util.Locale; import java.util.Set; import net.sourceforge.pmd.lang.ast.Node; @@ -40,11 +41,11 @@ public Object visit(ASTClassOrInterfaceBody node, Object data) { if (child instanceof ASTFieldDeclaration) { fields.add((ASTFieldDeclaration) child); } else if (child instanceof ASTMethodDeclaration) { - methodNames.add(((ASTMethodDeclaration) child).getMethodName().toLowerCase()); + methodNames.add(((ASTMethodDeclaration) child).getMethodName().toLowerCase(Locale.ROOT)); } } for (ASTFieldDeclaration field : fields) { - String varName = field.getVariableName().toLowerCase(); + String varName = field.getVariableName().toLowerCase(Locale.ROOT); if (methodNames.contains(varName)) { addViolation(data, field, field.getVariableName()); } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/naming/AvoidFieldNameMatchingTypeNameRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/AvoidFieldNameMatchingTypeNameRule.java similarity index 91% rename from pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/naming/AvoidFieldNameMatchingTypeNameRule.java rename to pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/AvoidFieldNameMatchingTypeNameRule.java index b5577160468..ca96b63ebb2 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/naming/AvoidFieldNameMatchingTypeNameRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/AvoidFieldNameMatchingTypeNameRule.java @@ -2,7 +2,7 @@ * BSD-style license; for more info see http://pmd.sourceforge.net/license.html */ -package net.sourceforge.pmd.lang.java.rule.naming; +package net.sourceforge.pmd.lang.java.rule.errorprone; import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceDeclaration; import net.sourceforge.pmd.lang.java.ast.ASTFieldDeclaration; @@ -10,6 +10,7 @@ public class AvoidFieldNameMatchingTypeNameRule extends AbstractJavaRule { + @Override public Object visit(ASTClassOrInterfaceDeclaration node, Object data) { if (node.isInterface()) { return data; @@ -17,6 +18,7 @@ public Object visit(ASTClassOrInterfaceDeclaration node, Object data) { return super.visit(node, data); } + @Override public Object visit(ASTFieldDeclaration node, Object data) { ASTClassOrInterfaceDeclaration cl = node.getFirstParentOfType(ASTClassOrInterfaceDeclaration.class); if (cl != null && node.getVariableName().equalsIgnoreCase(cl.getImage())) { diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/basic/AvoidMultipleUnaryOperatorsRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/AvoidMultipleUnaryOperatorsRule.java similarity index 98% rename from pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/basic/AvoidMultipleUnaryOperatorsRule.java rename to pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/AvoidMultipleUnaryOperatorsRule.java index 54f528e45ee..cf983969c54 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/basic/AvoidMultipleUnaryOperatorsRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/AvoidMultipleUnaryOperatorsRule.java @@ -2,7 +2,7 @@ * BSD-style license; for more info see http://pmd.sourceforge.net/license.html */ -package net.sourceforge.pmd.lang.java.rule.basic; +package net.sourceforge.pmd.lang.java.rule.errorprone; import net.sourceforge.pmd.lang.ast.Node; import net.sourceforge.pmd.lang.java.ast.ASTExpression; diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/AvoidUsingOctalValuesRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/AvoidUsingOctalValuesRule.java new file mode 100644 index 00000000000..d4c2fd551c8 --- /dev/null +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/AvoidUsingOctalValuesRule.java @@ -0,0 +1,41 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.errorprone; + +import java.util.regex.Pattern; + +import net.sourceforge.pmd.lang.java.ast.ASTLiteral; +import net.sourceforge.pmd.lang.java.rule.AbstractJavaRule; +import net.sourceforge.pmd.properties.BooleanProperty; + +public class AvoidUsingOctalValuesRule extends AbstractJavaRule { + + public static final Pattern OCTAL_PATTERN = Pattern.compile("0[0-7]{2,}[lL]?"); + + public static final Pattern STRICT_OCTAL_PATTERN = Pattern.compile("0[0-7]+[lL]?"); + + private static final BooleanProperty STRICT_METHODS_DESCRIPTOR = BooleanProperty.named("strict") + .desc("Detect violations between 00 and 07") + .defaultValue(false) + .uiOrder(1.0f).build(); + + + public AvoidUsingOctalValuesRule() { + definePropertyDescriptor(STRICT_METHODS_DESCRIPTOR); + } + + @Override + public Object visit(ASTLiteral node, Object data) { + boolean strict = getProperty(STRICT_METHODS_DESCRIPTOR); + Pattern p = strict ? STRICT_OCTAL_PATTERN : OCTAL_PATTERN; + + String img = node.getImage(); + if (img != null && p.matcher(img).matches()) { + addViolation(data, node); + } + + return data; + } +} diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/javabeans/BeanMembersShouldSerializeRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/BeanMembersShouldSerializeRule.java similarity index 90% rename from pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/javabeans/BeanMembersShouldSerializeRule.java rename to pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/BeanMembersShouldSerializeRule.java index ba4a8b09e81..cf2785145e9 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/javabeans/BeanMembersShouldSerializeRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/BeanMembersShouldSerializeRule.java @@ -2,11 +2,12 @@ * BSD-style license; for more info see http://pmd.sourceforge.net/license.html */ -package net.sourceforge.pmd.lang.java.rule.javabeans; +package net.sourceforge.pmd.lang.java.rule.errorprone; import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.Locale; import java.util.Map; import net.sourceforge.pmd.lang.ast.Node; @@ -74,13 +75,14 @@ public Object visit(ASTClassOrInterfaceDeclaration node, Object data) { Map> vars = node.getScope() .getDeclarations(VariableNameDeclaration.class); - for (VariableNameDeclaration decl : vars.keySet()) { + for (Map.Entry> entry : vars.entrySet()) { + VariableNameDeclaration decl = entry.getKey(); AccessNode accessNodeParent = decl.getAccessNodeParent(); - if (vars.get(decl).isEmpty() || accessNodeParent.isTransient() || accessNodeParent.isStatic()) { + if (entry.getValue().isEmpty() || accessNodeParent.isTransient() || accessNodeParent.isStatic()) { continue; } String varName = trimIfPrefix(decl.getImage()); - varName = varName.substring(0, 1).toUpperCase() + varName.substring(1, varName.length()); + varName = varName.substring(0, 1).toUpperCase(Locale.ROOT) + varName.substring(1, varName.length()); boolean hasGetMethod = Arrays.binarySearch(methNameArray, "get" + varName) >= 0 || Arrays.binarySearch(methNameArray, "is" + varName) >= 0; boolean hasSetMethod = Arrays.binarySearch(methNameArray, "set" + varName) >= 0; diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/basic/BrokenNullCheckRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/BrokenNullCheckRule.java similarity index 99% rename from pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/basic/BrokenNullCheckRule.java rename to pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/BrokenNullCheckRule.java index 6c68ae98c18..c38d246257f 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/basic/BrokenNullCheckRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/BrokenNullCheckRule.java @@ -2,7 +2,7 @@ * BSD-style license; for more info see http://pmd.sourceforge.net/license.html */ -package net.sourceforge.pmd.lang.java.rule.basic; +package net.sourceforge.pmd.lang.java.rule.errorprone; import java.util.ArrayList; import java.util.List; diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/basic/CheckSkipResultRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/CheckSkipResultRule.java similarity index 98% rename from pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/basic/CheckSkipResultRule.java rename to pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/CheckSkipResultRule.java index f433df46dcc..138b8ca7386 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/basic/CheckSkipResultRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/CheckSkipResultRule.java @@ -2,7 +2,7 @@ * BSD-style license; for more info see http://pmd.sourceforge.net/license.html */ -package net.sourceforge.pmd.lang.java.rule.basic; +package net.sourceforge.pmd.lang.java.rule.errorprone; import java.io.InputStream; diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/CloneMethodMustImplementCloneableRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/CloneMethodMustImplementCloneableRule.java new file mode 100644 index 00000000000..e528fac6ac8 --- /dev/null +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/CloneMethodMustImplementCloneableRule.java @@ -0,0 +1,168 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.errorprone; + +import java.util.HashSet; +import java.util.List; +import java.util.Objects; +import java.util.Set; + +import net.sourceforge.pmd.lang.ast.Node; +import net.sourceforge.pmd.lang.java.ast.ASTBlock; +import net.sourceforge.pmd.lang.java.ast.ASTBlockStatement; +import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceDeclaration; +import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceType; +import net.sourceforge.pmd.lang.java.ast.ASTCompilationUnit; +import net.sourceforge.pmd.lang.java.ast.ASTExtendsList; +import net.sourceforge.pmd.lang.java.ast.ASTImplementsList; +import net.sourceforge.pmd.lang.java.ast.ASTMethodDeclaration; +import net.sourceforge.pmd.lang.java.ast.ASTMethodDeclarator; +import net.sourceforge.pmd.lang.java.rule.AbstractJavaRule; + +/** + * The method clone() should only be implemented if the class implements the + * Cloneable interface with the exception of a final method that only throws + * CloneNotSupportedException. This version uses PMD's type resolution + * facilities, and can detect if the class implements or extends a Cloneable + * class + * + * @author acaplan + */ +public class CloneMethodMustImplementCloneableRule extends AbstractJavaRule { + + @Override + public Object visit(final ASTClassOrInterfaceDeclaration node, final Object data) { + if (extendsOrImplementsCloneable(node)) { + return data; + } + return super.visit(node, data); + } + + private boolean extendsOrImplementsCloneable(final ASTClassOrInterfaceDeclaration node) { + if (node.getType() != null) { + return Cloneable.class.isAssignableFrom(node.getType()); + } + + // From this point on, this is a best effort, the auxclasspath is incomplete. + + // TODO : Should we really care about this? + // Shouldn't the type resolver / symbol table report missing classes and the user + // know results are dependent on running under proper arguments? + final ASTImplementsList impl = node.getFirstChildOfType(ASTImplementsList.class); + if (impl != null) { + for (int ix = 0; ix < impl.jjtGetNumChildren(); ix++) { + final Node child = impl.jjtGetChild(ix); + + if (child.getClass() != ASTClassOrInterfaceType.class) { + continue; + } + + final ASTClassOrInterfaceType type = (ASTClassOrInterfaceType) child; + if (type.getType() == null) { + if ("Cloneable".equals(type.getImage())) { + return true; + } + } else if (Cloneable.class.isAssignableFrom(type.getType())) { + return true; + } + } + } + + if (node.jjtGetNumChildren() != 0 && node.jjtGetChild(0) instanceof ASTExtendsList) { + final ASTClassOrInterfaceType type = (ASTClassOrInterfaceType) node.jjtGetChild(0).jjtGetChild(0); + final Class clazz = type.getType(); + if (clazz != null) { + return Cloneable.class.isAssignableFrom(clazz); + } + } + + return false; + } + + @Override + public Object visit(final ASTMethodDeclaration node, final Object data) { + // Is this a clone method? + final ASTMethodDeclarator methodDeclarator = node.getFirstChildOfType(ASTMethodDeclarator.class); + if (!isCloneMethod(methodDeclarator)) { + return data; + } + + // Is the clone method just throwing CloneNotSupportedException? + final ASTClassOrInterfaceDeclaration classOrInterface = node.getFirstParentOfType(ASTClassOrInterfaceDeclaration.class); + if (classOrInterface != null && //Don't analyze enums, which cannot subclass clone() + (node.isFinal() || classOrInterface.isFinal())) { + if (node.findDescendantsOfType(ASTBlock.class).size() == 1) { + final List blocks = node.findDescendantsOfType(ASTBlockStatement.class); + if (blocks.size() == 1) { + final ASTBlockStatement block = blocks.get(0); + final ASTClassOrInterfaceType type = block.getFirstDescendantOfType(ASTClassOrInterfaceType.class); + if (type != null && type.getType() != null && type.getNthParent(9).equals(node) + && type.getType().equals(CloneNotSupportedException.class)) { + return data; + } else if (type != null && type.getType() == null + && "CloneNotSupportedException".equals(type.getImage())) { + return data; + } + } + } + } + + // TODO : Should we really care about this? It can only happen with an incomplete auxclasspath + if (classOrInterface != null && classOrInterface.getType() == null) { + // Now check other whether implemented or extended classes are defined inside the same file + final Set classesNames = determineTopLevelCloneableClasses(classOrInterface); + + final ASTImplementsList implementsList = classOrInterface.getFirstChildOfType(ASTImplementsList.class); + if (implementsList != null) { + final List types = implementsList.findChildrenOfType(ASTClassOrInterfaceType.class); + for (final ASTClassOrInterfaceType t : types) { + if (classesNames.contains(t.getImage())) { + return data; + } + } + } + + final ASTExtendsList extendsList = classOrInterface.getFirstChildOfType(ASTExtendsList.class); + if (extendsList != null) { + final ASTClassOrInterfaceType type = extendsList.getFirstChildOfType(ASTClassOrInterfaceType.class); + if (classesNames.contains(type.getImage())) { + return data; + } + } + } + + // Nothing can save us now + addViolation(data, node); + return data; + } + + /** + * Determines all the class/interface declarations inside this compilation + * unit, which implement Cloneable + * + * @param currentClass + * the node of the class, that is currently analyzed (inside this + * compilation unit) + * @return a Set of class/interface names + */ + private Set determineTopLevelCloneableClasses(final ASTClassOrInterfaceDeclaration currentClass) { + final List classes = currentClass.getFirstParentOfType(ASTCompilationUnit.class) + .findDescendantsOfType(ASTClassOrInterfaceDeclaration.class); + final Set classesNames = new HashSet(); + for (final ASTClassOrInterfaceDeclaration c : classes) { + if (!Objects.equals(c, currentClass) && extendsOrImplementsCloneable(c)) { + classesNames.add(c.getImage()); + } + } + return classesNames; + } + + public boolean isCloneMethod(final ASTMethodDeclarator method) { + if (!"clone".equals(method.getImage())) { + return false; + } + return method.getParameterCount() == 0; + } +} diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/CloseResourceRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/CloseResourceRule.java similarity index 98% rename from pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/CloseResourceRule.java rename to pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/CloseResourceRule.java index c05e8746c75..8fc6b8a1bbe 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/CloseResourceRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/CloseResourceRule.java @@ -2,7 +2,7 @@ * BSD-style license; for more info see http://pmd.sourceforge.net/license.html */ -package net.sourceforge.pmd.lang.java.rule.design; +package net.sourceforge.pmd.lang.java.rule.errorprone; import java.util.ArrayList; import java.util.HashSet; @@ -104,13 +104,13 @@ private static String toSimpleType(String fullyQualifiedClassName) { @Override public Object visit(ASTConstructorDeclaration node, Object data) { checkForResources(node, data); - return data; + return super.visit(node, data); } @Override public Object visit(ASTMethodDeclaration node, Object data) { checkForResources(node, data); - return data; + return super.visit(node, data); } private void checkForResources(Node node, Object data) { @@ -121,7 +121,7 @@ private void checkForResources(Node node, Object data) { for (ASTLocalVariableDeclaration var : vars) { ASTType type = var.getTypeNode(); - if (type.jjtGetChild(0) instanceof ASTReferenceType) { + if (type != null && type.jjtGetChild(0) instanceof ASTReferenceType) { ASTReferenceType ref = (ASTReferenceType) type.jjtGetChild(0); if (ref.jjtGetChild(0) instanceof ASTClassOrInterfaceType) { ASTClassOrInterfaceType clazz = (ASTClassOrInterfaceType) ref.jjtGetChild(0); @@ -367,7 +367,7 @@ private boolean nullCheckIfCondition(ASTBlock enclosingBlock, Node node, String + " [PrimaryExpression/PrimaryPrefix/Literal/NullLiteral]"); return !nodes.isEmpty(); } catch (JaxenException e) { - // no boolean literals or other condition + throw new RuntimeException(e); } } return true; diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/CompareObjectsWithEqualsRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/CompareObjectsWithEqualsRule.java similarity index 93% rename from pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/CompareObjectsWithEqualsRule.java rename to pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/CompareObjectsWithEqualsRule.java index 277c68a9db7..9b75c5f9b42 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/CompareObjectsWithEqualsRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/CompareObjectsWithEqualsRule.java @@ -2,7 +2,7 @@ * BSD-style license; for more info see http://pmd.sourceforge.net/license.html */ -package net.sourceforge.pmd.lang.java.rule.design; +package net.sourceforge.pmd.lang.java.rule.errorprone; import net.sourceforge.pmd.lang.ast.Node; import net.sourceforge.pmd.lang.java.ast.ASTAllocationExpression; @@ -23,7 +23,7 @@ private boolean hasName(Node n) { /** * Indicate whether this node is allocating a new object. - * + * * @param n * node that might be allocating a new object * @return true if child 0 is an AllocationExpression @@ -33,6 +33,7 @@ private boolean isAllocation(Node n) { && n.jjtGetParent().jjtGetNumChildren() == 1; } + @Override public Object visit(ASTEqualityExpression node, Object data) { Node c0 = node.jjtGetChild(0).jjtGetChild(0); Node c1 = node.jjtGetChild(1).jjtGetChild(0); @@ -84,7 +85,9 @@ public Object visit(ASTEqualityExpression node, Object data) { ASTReferenceType type1 = ((Node) nd1.getAccessNodeParent()) .getFirstDescendantOfType(ASTReferenceType.class); // skip, if it is an enum - if (type0.getType() != null && type0.getType().equals(type1.getType()) && type0.getType().isEnum()) { + if (type0.getType() != null && type0.getType().equals(type1.getType()) + // It may be a custom enum class or an explicit Enum class usage + && (type0.getType().isEnum() || type0.getType() == java.lang.Enum.class)) { return data; } @@ -98,7 +101,7 @@ public Object visit(ASTEqualityExpression node, Object data) { /** * Checks whether the given node contains a qualified name, consisting of * one ASTPrimaryPrefix and one or more ASTPrimarySuffix nodes. - * + * * @param node * the node * @return true if it is a qualified name diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/ConstructorCallsOverridableMethodRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/ConstructorCallsOverridableMethodRule.java similarity index 99% rename from pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/ConstructorCallsOverridableMethodRule.java rename to pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/ConstructorCallsOverridableMethodRule.java index 465ca59a25e..1de99a9493b 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/ConstructorCallsOverridableMethodRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/ConstructorCallsOverridableMethodRule.java @@ -2,7 +2,7 @@ * BSD-style license; for more info see http://pmd.sourceforge.net/license.html */ -package net.sourceforge.pmd.lang.java.rule.design; +package net.sourceforge.pmd.lang.java.rule.errorprone; import java.util.ArrayList; import java.util.Collections; @@ -596,7 +596,7 @@ private static class EvalPackage { public List calledConstructors; public Map> allPrivateConstructorsOfClass; - EvalPackage() { + private EvalPackage() { } EvalPackage(String className) { diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/controversial/DaaRuleViolation.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/DaaRuleViolation.java similarity index 93% rename from pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/controversial/DaaRuleViolation.java rename to pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/DaaRuleViolation.java index af20b0b4b7f..40dfc8b9777 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/controversial/DaaRuleViolation.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/DaaRuleViolation.java @@ -2,7 +2,7 @@ * BSD-style license; for more info see http://pmd.sourceforge.net/license.html */ -package net.sourceforge.pmd.lang.java.rule.controversial; +package net.sourceforge.pmd.lang.java.rule.errorprone; import net.sourceforge.pmd.Rule; import net.sourceforge.pmd.RuleContext; @@ -13,7 +13,7 @@ /** * The RuleViolation is extended by the VariableName. The VariableName is * required for showing what variable produces the UR DD or DU anomaly. - * + * * @author Sven Jacob * @author Brian Remedios */ @@ -30,6 +30,7 @@ public DaaRuleViolation(Rule rule, RuleContext ctx, Node node, String type, Stri this.type = type; } + @Override public String getVariableName() { return variableName; } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/controversial/DataflowAnomalyAnalysisRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/DataflowAnomalyAnalysisRule.java similarity index 88% rename from pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/controversial/DataflowAnomalyAnalysisRule.java rename to pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/DataflowAnomalyAnalysisRule.java index 4308a72cb50..ab529036a7b 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/controversial/DataflowAnomalyAnalysisRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/DataflowAnomalyAnalysisRule.java @@ -2,7 +2,7 @@ * BSD-style license; for more info see http://pmd.sourceforge.net/license.html */ -package net.sourceforge.pmd.lang.java.rule.controversial; +package net.sourceforge.pmd.lang.java.rule.errorprone; import java.text.MessageFormat; import java.util.ArrayList; @@ -29,17 +29,23 @@ * @author Sven Jacob */ public class DataflowAnomalyAnalysisRule extends AbstractJavaRule implements Executable { + private static final IntegerProperty MAX_PATH_DESCRIPTOR + = IntegerProperty.named("maxPaths") + .desc("Maximum number of checked paths per method. A lower value will increase the performance of the rule but may decrease anomalies found.") + .range(100, 8000) + .defaultValue(1000) + .uiOrder(1.0f).build(); + private static final IntegerProperty MAX_VIOLATIONS_DESCRIPTOR + = IntegerProperty.named("maxViolations") + .desc("Maximum number of anomalies per class") + .range(1, 2000) + .defaultValue(100) + .uiOrder(2.0f).build(); private RuleContext rc; private List daaRuleViolations; private int maxRuleViolations; private int currentRuleViolationCount; - private static final IntegerProperty MAX_PATH_DESCRIPTOR = new IntegerProperty("maxPaths", - "Maximum number of checked paths per method. A lower value will increase the performance of the rule but may decrease anomalies found.", - 100, 8000, 1000, 1.0f); - - private static final IntegerProperty MAX_VIOLATIONS_DESCRIPTOR = new IntegerProperty("maxViolations", - "Maximum number of anomalies per class", 1, 2000, 100, 2.0f); private static class Usage { public int accessType; @@ -50,6 +56,7 @@ private static class Usage { this.node = node; } + @Override public String toString() { return "accessType = " + accessType + ", line = " + node.getLine(); } @@ -60,12 +67,14 @@ public DataflowAnomalyAnalysisRule() { definePropertyDescriptor(MAX_VIOLATIONS_DESCRIPTOR); } + @Override public Object visit(ASTClassOrInterfaceDeclaration node, Object data) { maxRuleViolations = getProperty(MAX_VIOLATIONS_DESCRIPTOR); currentRuleViolationCount = 0; return super.visit(node, data); } + @Override public Object visit(ASTMethodDeclaration methodDeclaration, Object data) { rc = (RuleContext) data; daaRuleViolations = new ArrayList<>(); @@ -79,6 +88,7 @@ public Object visit(ASTMethodDeclaration methodDeclaration, Object data) { return data; } + @Override public void execute(CurrentPath path) { if (maxNumberOfViolationsReached()) { @@ -143,7 +153,7 @@ private void addDaaViolation(Object data, Node node, String type, String var, in /** * Maximum number of violations was already reached? - * + * * @return true if the maximum number of violations was * reached, false otherwise. */ @@ -154,7 +164,7 @@ private boolean maxNumberOfViolationsReached() { /** * Checks if a violation already exists. This is needed because on the * different paths same anomalies can occur. - * + * * @param type * @param var * @param startLine diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/controversial/DontImportSunRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/DontImportSunRule.java similarity index 88% rename from pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/controversial/DontImportSunRule.java rename to pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/DontImportSunRule.java index 98117e7cc56..f7b42f7d598 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/controversial/DontImportSunRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/DontImportSunRule.java @@ -2,13 +2,14 @@ * BSD-style license; for more info see http://pmd.sourceforge.net/license.html */ -package net.sourceforge.pmd.lang.java.rule.controversial; +package net.sourceforge.pmd.lang.java.rule.errorprone; import net.sourceforge.pmd.lang.java.ast.ASTImportDeclaration; import net.sourceforge.pmd.lang.java.rule.AbstractJavaRule; public class DontImportSunRule extends AbstractJavaRule { + @Override public Object visit(ASTImportDeclaration node, Object data) { String img = node.jjtGetChild(0).getImage(); if (img.startsWith("sun.") && !img.startsWith("sun.misc.Signal")) { diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/IdempotentOperationsRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/IdempotentOperationsRule.java similarity index 98% rename from pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/IdempotentOperationsRule.java rename to pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/IdempotentOperationsRule.java index 8656433bdb2..40a0f038271 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/IdempotentOperationsRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/IdempotentOperationsRule.java @@ -2,7 +2,7 @@ * BSD-style license; for more info see http://pmd.sourceforge.net/license.html */ -package net.sourceforge.pmd.lang.java.rule.design; +package net.sourceforge.pmd.lang.java.rule.errorprone; import java.util.List; diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/imports/ImportFromSamePackageRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/ImportFromSamePackageRule.java similarity index 92% rename from pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/imports/ImportFromSamePackageRule.java rename to pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/ImportFromSamePackageRule.java index eef11f30673..6323acfaced 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/imports/ImportFromSamePackageRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/ImportFromSamePackageRule.java @@ -2,7 +2,7 @@ * BSD-style license; for more info see http://pmd.sourceforge.net/license.html */ -package net.sourceforge.pmd.lang.java.rule.imports; +package net.sourceforge.pmd.lang.java.rule.errorprone; import org.apache.commons.lang3.StringUtils; @@ -12,6 +12,7 @@ public class ImportFromSamePackageRule extends AbstractJavaRule { + @Override public Object visit(ASTImportDeclaration importDecl, Object data) { String packageName = importDecl.getScope().getEnclosingScope(SourceFileScope.class).getPackageName(); diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/InvalidSlf4jMessageFormatRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/InvalidSlf4jMessageFormatRule.java new file mode 100644 index 00000000000..55d90ff4740 --- /dev/null +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/InvalidSlf4jMessageFormatRule.java @@ -0,0 +1,224 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.errorprone; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.apache.commons.lang3.StringUtils; +import org.jaxen.JaxenException; + +import net.sourceforge.pmd.lang.ast.Node; +import net.sourceforge.pmd.lang.java.ast.ASTArgumentList; +import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceBody; +import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceType; +import net.sourceforge.pmd.lang.java.ast.ASTExpression; +import net.sourceforge.pmd.lang.java.ast.ASTFieldDeclaration; +import net.sourceforge.pmd.lang.java.ast.ASTLiteral; +import net.sourceforge.pmd.lang.java.ast.ASTMethodOrConstructorDeclaration; +import net.sourceforge.pmd.lang.java.ast.ASTName; +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.ASTVariableDeclarator; +import net.sourceforge.pmd.lang.java.ast.ASTVariableDeclaratorId; +import net.sourceforge.pmd.lang.java.ast.ASTVariableInitializer; +import net.sourceforge.pmd.lang.java.rule.AbstractJavaRule; +import net.sourceforge.pmd.lang.java.symboltable.VariableNameDeclaration; +import net.sourceforge.pmd.lang.java.typeresolution.TypeHelper; +import net.sourceforge.pmd.lang.symboltable.NameDeclaration; + +public class InvalidSlf4jMessageFormatRule extends AbstractJavaRule { + private static final Logger LOG = Logger.getLogger(InvalidSlf4jMessageFormatRule.class.getName()); + + private static final Set LOGGER_LEVELS; + private static final String LOGGER_CLASS = "org.slf4j.Logger"; + + static { + LOGGER_LEVELS = Collections + .unmodifiableSet(new HashSet(Arrays.asList("trace", "debug", "info", "warn", "error"))); + } + + public InvalidSlf4jMessageFormatRule() { + addRuleChainVisit(ASTName.class); + } + + @Override + public Object visit(final ASTName node, final Object data) { + final NameDeclaration nameDeclaration = node.getNameDeclaration(); + // ignore imports or methods + if (!(nameDeclaration instanceof VariableNameDeclaration)) { + return data; + } + + // ignore non slf4j logger + Class type = ((VariableNameDeclaration) nameDeclaration).getType(); + if (type == null || !type.getName().equals(LOGGER_CLASS)) { + return data; + } + + // get the node that contains the logger + final ASTPrimaryExpression parentNode = node.getFirstParentOfType(ASTPrimaryExpression.class); + + // get the log level + final String method = parentNode.getFirstChildOfType(ASTPrimaryPrefix.class).getFirstChildOfType(ASTName.class) + .getImage().replace(nameDeclaration.getImage() + ".", ""); + + // ignore if not a log level + if (!LOGGER_LEVELS.contains(method)) { + return data; + } + + // find the arguments + final List argumentList = parentNode.getFirstChildOfType(ASTPrimarySuffix.class) + .getFirstDescendantOfType(ASTArgumentList.class).findChildrenOfType(ASTExpression.class); + + // remove the message parameter + final ASTExpression messageParam = argumentList.remove(0); + final int expectedArguments = expectedArguments(messageParam); + + if (expectedArguments == 0) { + // ignore if we are not expecting arguments to format the message + // or if we couldn't analyze the message parameter + return data; + } + + // Remove throwable param, since it is shown separately. + // But only, if it is not used as a placeholder argument + if (argumentList.size() > expectedArguments) { + removeThrowableParam(argumentList); + } + + if (argumentList.size() < expectedArguments) { + addViolationWithMessage(data, node, + "Missing arguments," + getExpectedMessage(argumentList, expectedArguments)); + } else if (argumentList.size() > expectedArguments) { + addViolationWithMessage(data, node, + "Too many arguments," + getExpectedMessage(argumentList, expectedArguments)); + } + + return data; + } + + private boolean isNewThrowable(ASTPrimaryExpression last) { + // in case a new exception is created or the exception class is + // mentioned. + ASTClassOrInterfaceType classOrInterface = last.getFirstDescendantOfType(ASTClassOrInterfaceType.class); + return classOrInterface != null && classOrInterface.getType() != null + && TypeHelper.isA(classOrInterface, Throwable.class); + } + + private boolean hasTypeThrowable(ASTPrimaryExpression last) { + // if the type could be determined already + return last.getType() != null && TypeHelper.isA(last, Throwable.class); + } + + private boolean isReferencingThrowable(ASTPrimaryExpression last) { + // check the variable type, if there is a reference by name + ASTName variable = last.getFirstDescendantOfType(ASTName.class); + if (variable != null && variable.getNameDeclaration() != null + && variable.getNameDeclaration() instanceof VariableNameDeclaration) { + VariableNameDeclaration declaration = (VariableNameDeclaration) variable.getNameDeclaration(); + if (declaration.getType() != null && Throwable.class.isAssignableFrom(declaration.getType())) { + return true; + } + // convention: Exception type names should end with Exception + if (declaration.getTypeImage() != null && declaration.getTypeImage().endsWith("Exception")) { + return true; + } + } + return false; + } + + private void removeThrowableParam(final List params) { + // Throwable parameters are the last one in the list, if any. + if (params.isEmpty()) { + return; + } + int lastIndex = params.size() - 1; + ASTPrimaryExpression last = params.get(lastIndex).getFirstDescendantOfType(ASTPrimaryExpression.class); + + if (isNewThrowable(last) || hasTypeThrowable(last) || isReferencingThrowable(last)) { + params.remove(lastIndex); + } + } + + private String getExpectedMessage(final List params, final int expectedArguments) { + return " expected " + expectedArguments + (expectedArguments > 1 ? " arguments " : " argument ") + "but have " + + params.size(); + } + + private int expectedArguments(final ASTExpression node) { + int count = 0; + // look if the logger have a literal message + if (node.getFirstDescendantOfType(ASTLiteral.class) != null) { + count = countPlaceholders(node); + } else if (node.getFirstDescendantOfType(ASTName.class) != null) { + final String variableName = node.getFirstDescendantOfType(ASTName.class).getImage(); + // look if the message is defined locally + final List localVariables = node + .getFirstParentOfType(ASTMethodOrConstructorDeclaration.class) + .findDescendantsOfType(ASTVariableDeclarator.class); + count = getAmountOfExpectedArguments(variableName, localVariables); + + if (count == 0) { + // look if the message is defined in a field + final List fieldlist = node.getFirstParentOfType(ASTClassOrInterfaceBody.class) + .findDescendantsOfType(ASTFieldDeclaration.class); + // only look for ASTVariableDeclarator that are Fields + final List fields = new ArrayList(fieldlist.size()); + for (final ASTFieldDeclaration astFieldDeclaration : fieldlist) { + fields.add(astFieldDeclaration.getFirstChildOfType(ASTVariableDeclarator.class)); + } + count = getAmountOfExpectedArguments(variableName, fields); + } + } + return count; + } + + private int getAmountOfExpectedArguments(final String variableName, final List variables) { + for (final ASTVariableDeclarator astVariableDeclarator : variables) { + if (astVariableDeclarator.getFirstChildOfType(ASTVariableDeclaratorId.class).getImage() + .equals(variableName)) { + ASTVariableInitializer variableInitializer = astVariableDeclarator + .getFirstDescendantOfType(ASTVariableInitializer.class); + ASTExpression expression = null; + if (variableInitializer != null) { + expression = variableInitializer.getFirstChildOfType(ASTExpression.class); + } + if (expression != null) { + return countPlaceholders(expression); + } + } + } + return 0; + } + + private int countPlaceholders(final ASTExpression node) { + // zero means, no placeholders, or we could not analyze the message parameter + int result = 0; + + try { + List literals = node + .findChildNodesWithXPath( + "AdditiveExpression/PrimaryExpression/PrimaryPrefix/Literal[@StringLiteral='true']" + + "|PrimaryExpression/PrimaryPrefix/Literal[@StringLiteral='true']"); + // if there are multiple literals, we just assume, they are concatenated + // together... + for (Node stringLiteral : literals) { + result += StringUtils.countMatches(stringLiteral.getImage(), "{}"); + } + } catch (JaxenException e) { + LOG.log(Level.FINE, "Could not determine literals", e); + } + return result; + } +} diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/MethodWithSameNameAsEnclosingClassRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/MethodWithSameNameAsEnclosingClassRule.java new file mode 100644 index 00000000000..9b470c462a1 --- /dev/null +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/MethodWithSameNameAsEnclosingClassRule.java @@ -0,0 +1,25 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.errorprone; + +import java.util.List; + +import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceDeclaration; +import net.sourceforge.pmd.lang.java.ast.ASTMethodDeclarator; +import net.sourceforge.pmd.lang.java.rule.AbstractJavaRule; + +public class MethodWithSameNameAsEnclosingClassRule extends AbstractJavaRule { + + @Override + public Object visit(ASTClassOrInterfaceDeclaration node, Object data) { + List methods = node.findDescendantsOfType(ASTMethodDeclarator.class); + for (ASTMethodDeclarator m : methods) { + if (m.hasImageEqualTo(node.getImage())) { + addViolation(data, m); + } + } + return super.visit(node, data); + } +} diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/MoreThanOneLoggerRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/MoreThanOneLoggerRule.java new file mode 100644 index 00000000000..30e201509d4 --- /dev/null +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/MoreThanOneLoggerRule.java @@ -0,0 +1,87 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.errorprone; + +import java.util.Stack; + +import net.sourceforge.pmd.lang.ast.Node; +import net.sourceforge.pmd.lang.java.ast.ASTAnnotationTypeDeclaration; +import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceDeclaration; +import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceType; +import net.sourceforge.pmd.lang.java.ast.ASTEnumDeclaration; +import net.sourceforge.pmd.lang.java.ast.ASTReferenceType; +import net.sourceforge.pmd.lang.java.ast.ASTType; +import net.sourceforge.pmd.lang.java.ast.ASTVariableDeclarator; +import net.sourceforge.pmd.lang.java.ast.JavaNode; +import net.sourceforge.pmd.lang.java.rule.AbstractJavaRule; +import net.sourceforge.pmd.lang.java.typeresolution.TypeHelper; +import net.sourceforge.pmd.util.NumericConstants; + +public class MoreThanOneLoggerRule extends AbstractJavaRule { + + private static final String LOG4J_LOGGER_NAME = "org.apache.log4j.Logger"; + private static final String JAVA_LOGGER_NAME = "java.util.logging.Logger"; + private static final String SLF4J_LOGGER_NAME = "org.slf4j.Logger"; + + private Stack stack = new Stack<>(); + + private Integer count; + + @Override + public Object visit(ASTClassOrInterfaceDeclaration node, Object data) { + return init(node, data); + } + + @Override + public Object visit(ASTEnumDeclaration node, Object data) { + return init(node, data); + } + + @Override + public Object visit(ASTAnnotationTypeDeclaration node, Object data) { + return init(node, data); + } + + private Object init(JavaNode node, Object data) { + stack.push(count); + count = NumericConstants.ZERO; + + node.childrenAccept(this, data); + + if (count > 1) { + addViolation(data, node); + } + count = stack.pop(); + + return data; + } + + @Override + public Object visit(ASTVariableDeclarator node, Object data) { + if (count > 1) { + return super.visit(node, data); + } + ASTType type = node.jjtGetParent().getFirstChildOfType(ASTType.class); + if (type != null) { + Node reftypeNode = type.jjtGetChild(0); + if (reftypeNode instanceof ASTReferenceType) { + Node classOrIntType = reftypeNode.jjtGetChild(0); + if (classOrIntType instanceof ASTClassOrInterfaceType) { + Class clazzType = ((ASTClassOrInterfaceType) classOrIntType).getType(); + if (clazzType != null + && (TypeHelper.isA((ASTClassOrInterfaceType) classOrIntType, LOG4J_LOGGER_NAME) + || TypeHelper.isA((ASTClassOrInterfaceType) classOrIntType, JAVA_LOGGER_NAME) + || TypeHelper.isA((ASTClassOrInterfaceType) classOrIntType, SLF4J_LOGGER_NAME)) + || clazzType == null && "Logger".equals(classOrIntType.getImage())) { + ++count; + } + } + } + } + + return super.visit(node, data); + } + +} diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/NullAssignmentRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/NullAssignmentRule.java new file mode 100644 index 00000000000..fefd1ff59a4 --- /dev/null +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/NullAssignmentRule.java @@ -0,0 +1,77 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.errorprone; + +import net.sourceforge.pmd.lang.java.ast.ASTAssignmentOperator; +import net.sourceforge.pmd.lang.java.ast.ASTBlockStatement; +import net.sourceforge.pmd.lang.java.ast.ASTConditionalExpression; +import net.sourceforge.pmd.lang.java.ast.ASTEqualityExpression; +import net.sourceforge.pmd.lang.java.ast.ASTExpression; +import net.sourceforge.pmd.lang.java.ast.ASTLambdaExpression; +import net.sourceforge.pmd.lang.java.ast.ASTName; +import net.sourceforge.pmd.lang.java.ast.ASTNullLiteral; +import net.sourceforge.pmd.lang.java.ast.ASTReturnStatement; +import net.sourceforge.pmd.lang.java.ast.ASTStatementExpression; +import net.sourceforge.pmd.lang.java.ast.ASTVariableInitializer; +import net.sourceforge.pmd.lang.java.ast.AccessNode; +import net.sourceforge.pmd.lang.java.rule.AbstractJavaRule; +import net.sourceforge.pmd.lang.java.symboltable.VariableNameDeclaration; + +public class NullAssignmentRule extends AbstractJavaRule { + + public NullAssignmentRule() { + addRuleChainVisit(ASTNullLiteral.class); + } + + @Override + public Object visit(ASTNullLiteral node, Object data) { + + if (node.getNthParent(5) instanceof ASTStatementExpression) { + ASTStatementExpression n = (ASTStatementExpression) node.getNthParent(5); + + if (isAssignmentToFinalField(n)) { + return data; + } + + if (n.jjtGetNumChildren() > 2 && n.jjtGetChild(1) instanceof ASTAssignmentOperator) { + addViolation(data, node); + } + } else if (node.getNthParent(4) instanceof ASTConditionalExpression) { + // "false" expression of ternary + if (isBadTernary((ASTConditionalExpression) node.getNthParent(4))) { + addViolation(data, node); + } + } else if (node.getNthParent(5) instanceof ASTConditionalExpression + && node.getNthParent(4) instanceof ASTExpression) { + // "true" expression of ternary + if (isBadTernary((ASTConditionalExpression) node.getNthParent(5))) { + addViolation(data, node); + } + } + + return data; + } + + private boolean isAssignmentToFinalField(ASTStatementExpression n) { + ASTName name = n.getFirstDescendantOfType(ASTName.class); + return name != null && name.getNameDeclaration() instanceof VariableNameDeclaration + && ((AccessNode) ((VariableNameDeclaration) name.getNameDeclaration()).getAccessNodeParent()).isFinal(); + } + + private boolean isBadTernary(ASTConditionalExpression ternary) { + boolean isInitializer = false; + + ASTVariableInitializer variableInitializer = ternary.getFirstParentOfType(ASTVariableInitializer.class); + if (variableInitializer != null) { + ASTBlockStatement statement = ternary.getFirstParentOfType(ASTBlockStatement.class); + isInitializer = statement == variableInitializer.getFirstParentOfType(ASTBlockStatement.class); + } + + return !(ternary.jjtGetChild(0) instanceof ASTEqualityExpression) + && !isInitializer + && !(ternary.getNthParent(2) instanceof ASTReturnStatement) + && !(ternary.getNthParent(2) instanceof ASTLambdaExpression); + } +} diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/basic/OverrideBothEqualsAndHashcodeRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/OverrideBothEqualsAndHashcodeRule.java similarity index 98% rename from pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/basic/OverrideBothEqualsAndHashcodeRule.java rename to pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/OverrideBothEqualsAndHashcodeRule.java index 857c24d2ef9..e30518381e1 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/basic/OverrideBothEqualsAndHashcodeRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/OverrideBothEqualsAndHashcodeRule.java @@ -2,7 +2,7 @@ * BSD-style license; for more info see http://pmd.sourceforge.net/license.html */ -package net.sourceforge.pmd.lang.java.rule.basic; +package net.sourceforge.pmd.lang.java.rule.errorprone; import java.util.List; diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/SingleMethodSingletonRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/SingleMethodSingletonRule.java new file mode 100644 index 00000000000..4308e8652fa --- /dev/null +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/SingleMethodSingletonRule.java @@ -0,0 +1,51 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.errorprone; + + +import java.util.List; + +import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceDeclaration; +import net.sourceforge.pmd.lang.java.ast.ASTMethodDeclaration; +import net.sourceforge.pmd.lang.java.rule.AbstractJavaRule; + +/** + * Returns Checks if the singleton rule is used properly. + */ +public class SingleMethodSingletonRule extends AbstractJavaRule { + + /** + * Checks for getInstance method usage in the same class. + * @param node of ASTCLass + * @param data of Object + * @return Object + * + */ + + + @Override + public Object visit(ASTClassOrInterfaceDeclaration node, Object data) { + + + List methods = node.findDescendantsOfType(ASTMethodDeclaration.class); // Find the name of methods in it + + int count = 0; + for (ASTMethodDeclaration method : methods) { + + if (method.getName().equals("getInstance")) { + count++; + if (count > 1) { + addViolation(data, node); + break; + } + } + + } + + + return super.visit(node, data); + + } +} diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/SingletonClassReturningNewInstanceRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/SingletonClassReturningNewInstanceRule.java similarity index 95% rename from pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/SingletonClassReturningNewInstanceRule.java rename to pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/SingletonClassReturningNewInstanceRule.java index f37b08ae076..10c96c36728 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/SingletonClassReturningNewInstanceRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/SingletonClassReturningNewInstanceRule.java @@ -2,7 +2,7 @@ * BSD-style license; for more info see http://pmd.sourceforge.net/license.html */ -package net.sourceforge.pmd.lang.java.rule.design; +package net.sourceforge.pmd.lang.java.rule.errorprone; import java.util.List; @@ -62,7 +62,7 @@ public Object visit(ASTMethodDeclaration node, Object data) { List astBlockStatements = node.findDescendantsOfType(ASTBlockStatement.class); returnVariableName = getReturnVariableName(node); - if (astBlockStatements.size() != 0) { + if (!astBlockStatements.isEmpty()) { for (ASTBlockStatement blockStatement : astBlockStatements) { if (blockStatement.hasDescendantOfType(ASTLocalVariableDeclaration.class)) { List lVarList = blockStatement @@ -106,7 +106,7 @@ private String getReturnVariableName(ASTMethodDeclaration node) { } private String getNameFromPrimaryPrefix(ASTPrimaryPrefix pp) { - if ((pp.jjtGetNumChildren() == 1) && (pp.jjtGetChild(0) instanceof ASTName)) { + if (pp.jjtGetNumChildren() == 1 && pp.jjtGetChild(0) instanceof ASTName) { return ((ASTName) pp.jjtGetChild(0)).getImage(); } return null; diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/naming/SuspiciousHashcodeMethodNameRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/SuspiciousHashcodeMethodNameRule.java similarity index 95% rename from pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/naming/SuspiciousHashcodeMethodNameRule.java rename to pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/SuspiciousHashcodeMethodNameRule.java index b904a4b529f..48935a3b49c 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/naming/SuspiciousHashcodeMethodNameRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/SuspiciousHashcodeMethodNameRule.java @@ -2,7 +2,7 @@ * BSD-style license; for more info see http://pmd.sourceforge.net/license.html */ -package net.sourceforge.pmd.lang.java.rule.naming; +package net.sourceforge.pmd.lang.java.rule.errorprone; import net.sourceforge.pmd.lang.ast.Node; import net.sourceforge.pmd.lang.java.ast.ASTMethodDeclaration; @@ -13,6 +13,7 @@ public class SuspiciousHashcodeMethodNameRule extends AbstractJavaRule { + @Override public Object visit(ASTMethodDeclaration node, Object data) { /* * original XPath rule was //MethodDeclaration [ResultType diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/controversial/SuspiciousOctalEscapeRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/SuspiciousOctalEscapeRule.java similarity index 98% rename from pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/controversial/SuspiciousOctalEscapeRule.java rename to pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/SuspiciousOctalEscapeRule.java index 3ac5b21bb49..81910bf24c1 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/controversial/SuspiciousOctalEscapeRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/SuspiciousOctalEscapeRule.java @@ -2,7 +2,7 @@ * BSD-style license; for more info see http://pmd.sourceforge.net/license.html */ -package net.sourceforge.pmd.lang.java.rule.controversial; +package net.sourceforge.pmd.lang.java.rule.errorprone; import net.sourceforge.pmd.lang.java.ast.ASTLiteral; import net.sourceforge.pmd.lang.java.rule.AbstractJavaRule; diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/TestClassWithoutTestCasesRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/TestClassWithoutTestCasesRule.java new file mode 100644 index 00000000000..6900c018fcc --- /dev/null +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/TestClassWithoutTestCasesRule.java @@ -0,0 +1,39 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.errorprone; + +import java.util.List; + +import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceDeclaration; +import net.sourceforge.pmd.lang.java.ast.ASTMethodDeclaration; +import net.sourceforge.pmd.lang.java.rule.AbstractJUnitRule; + +public class TestClassWithoutTestCasesRule extends AbstractJUnitRule { + + @Override + public Object visit(ASTClassOrInterfaceDeclaration node, Object data) { + if (node.isAbstract() || node.isInterface() || node.isNested()) { + return data; + } + + List m = node.findDescendantsOfType(ASTMethodDeclaration.class); + boolean testsFound = false; + + if (m != null) { + for (ASTMethodDeclaration md : m) { + if (isJUnitMethod(md, data)) { + testsFound = true; + break; + } + } + } + + if (!testsFound) { + addViolation(data, node); + } + + return data; + } +} diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/strings/UnnecessaryCaseChangeRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/UnnecessaryCaseChangeRule.java similarity index 97% rename from pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/strings/UnnecessaryCaseChangeRule.java rename to pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/UnnecessaryCaseChangeRule.java index 24a954960b2..0ad72b50f0a 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/strings/UnnecessaryCaseChangeRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/UnnecessaryCaseChangeRule.java @@ -2,7 +2,7 @@ * BSD-style license; for more info see http://pmd.sourceforge.net/license.html */ -package net.sourceforge.pmd.lang.java.rule.strings; +package net.sourceforge.pmd.lang.java.rule.errorprone; import net.sourceforge.pmd.lang.ast.Node; import net.sourceforge.pmd.lang.java.ast.ASTName; @@ -13,6 +13,7 @@ public class UnnecessaryCaseChangeRule extends AbstractJavaRule { + @Override public Object visit(ASTPrimaryExpression exp, Object data) { int n = exp.jjtGetNumChildren(); if (n < 4) { diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/unnecessary/UnnecessaryConversionTemporaryRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/UnnecessaryConversionTemporaryRule.java similarity index 96% rename from pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/unnecessary/UnnecessaryConversionTemporaryRule.java rename to pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/UnnecessaryConversionTemporaryRule.java index 2be78660f07..fcd91042e67 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/unnecessary/UnnecessaryConversionTemporaryRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/UnnecessaryConversionTemporaryRule.java @@ -2,7 +2,7 @@ * BSD-style license; for more info see http://pmd.sourceforge.net/license.html */ -package net.sourceforge.pmd.lang.java.rule.unnecessary; +package net.sourceforge.pmd.lang.java.rule.errorprone; import java.util.Set; @@ -22,6 +22,7 @@ public class UnnecessaryConversionTemporaryRule extends AbstractJavaRule { private static final Set PRIMITIVE_WRAPPERS = CollectionUtil .asSet(new String[] { "Integer", "Boolean", "Double", "Long", "Short", "Byte", "Float" }); + @Override public Object visit(ASTPrimaryExpression node, Object data) { if (node.jjtGetNumChildren() == 0 || (node.jjtGetChild(0)).jjtGetNumChildren() == 0 || !(node.jjtGetChild(0).jjtGetChild(0) instanceof ASTAllocationExpression)) { @@ -37,6 +38,7 @@ public Object visit(ASTPrimaryExpression node, Object data) { return data; } + @Override public Object visit(ASTAllocationExpression node, Object data) { if (!inPrimaryExpressionContext || !(node.jjtGetChild(0) instanceof ASTClassOrInterfaceType)) { return super.visit(node, data); @@ -48,6 +50,7 @@ public Object visit(ASTAllocationExpression node, Object data) { return super.visit(node, data); } + @Override public Object visit(ASTPrimarySuffix node, Object data) { if (inPrimaryExpressionContext && usingPrimitiveWrapperAllocation) { if (node.hasImageEqualTo("toString")) { diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/unnecessary/UselessOperationOnImmutableRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/UselessOperationOnImmutableRule.java similarity index 94% rename from pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/unnecessary/UselessOperationOnImmutableRule.java rename to pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/UselessOperationOnImmutableRule.java index a6d86b1d2de..4e33d2f23bf 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/unnecessary/UselessOperationOnImmutableRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/errorprone/UselessOperationOnImmutableRule.java @@ -2,7 +2,7 @@ * BSD-style license; for more info see http://pmd.sourceforge.net/license.html */ -package net.sourceforge.pmd.lang.java.rule.unnecessary; +package net.sourceforge.pmd.lang.java.rule.errorprone; import java.util.HashMap; import java.util.Map; @@ -100,8 +100,10 @@ public Object visit(ASTLocalVariableDeclaration node, Object data) { */ private ASTVariableDeclaratorId getDeclaration(ASTLocalVariableDeclaration node) { ASTType type = node.getTypeNode(); - if (MAP_CLASSES.keySet().contains(type.getTypeImage())) { - return node.getFirstDescendantOfType(ASTVariableDeclaratorId.class); + if (type != null) { + if (MAP_CLASSES.keySet().contains(type.getTypeImage())) { + return node.getFirstDescendantOfType(ASTVariableDeclaratorId.class); + } } return null; } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/imports/UnnecessaryFullyQualifiedNameRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/imports/UnnecessaryFullyQualifiedNameRule.java deleted file mode 100644 index 66cca9967c8..00000000000 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/imports/UnnecessaryFullyQualifiedNameRule.java +++ /dev/null @@ -1,187 +0,0 @@ -/** - * BSD-style license; for more info see http://pmd.sourceforge.net/license.html - */ - -package net.sourceforge.pmd.lang.java.rule.imports; - -import java.lang.reflect.Method; -import java.util.ArrayList; -import java.util.List; -import java.util.Set; - -import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceType; -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.JavaNode; -import net.sourceforge.pmd.lang.java.rule.AbstractJavaRule; -import net.sourceforge.pmd.lang.java.symboltable.SourceFileScope; - -public class UnnecessaryFullyQualifiedNameRule extends AbstractJavaRule { - - private List imports = new ArrayList<>(); - private List matches = new ArrayList<>(); - - public UnnecessaryFullyQualifiedNameRule() { - super.addRuleChainVisit(ASTCompilationUnit.class); - super.addRuleChainVisit(ASTImportDeclaration.class); - super.addRuleChainVisit(ASTClassOrInterfaceType.class); - super.addRuleChainVisit(ASTName.class); - } - - @Override - public Object visit(ASTCompilationUnit node, Object data) { - imports.clear(); - return data; - } - - @Override - public Object visit(ASTImportDeclaration node, Object data) { - imports.add(node); - return data; - } - - @Override - public Object visit(ASTClassOrInterfaceType node, Object data) { - checkImports(node, data); - return data; - } - - @Override - public Object visit(ASTName node, Object data) { - if (!(node.jjtGetParent() instanceof ASTImportDeclaration) - && !(node.jjtGetParent() instanceof ASTPackageDeclaration)) { - checkImports(node, data); - } - return data; - } - - private void checkImports(JavaNode node, Object data) { - String name = node.getImage(); - matches.clear(); - - // Find all "matching" import declarations - for (ASTImportDeclaration importDeclaration : imports) { - if (importDeclaration.isImportOnDemand()) { - // On demand import exactly matches the package of the type - if (name.startsWith(importDeclaration.getImportedName())) { - if (name.lastIndexOf('.') == importDeclaration.getImportedName().length()) { - matches.add(importDeclaration); - continue; - } - } - } else { - // Exact match of imported class - if (name.equals(importDeclaration.getImportedName())) { - matches.add(importDeclaration); - continue; - } - // Match of static method call on imported class - if (name.startsWith(importDeclaration.getImportedName())) { - if (name.lastIndexOf('.') == importDeclaration.getImportedName().length()) { - matches.add(importDeclaration); - continue; - } - } - } - } - - // If there is no direct match, consider if we match the tail end of a - // direct static import, but also a static method on a class import? - // For example: - // - // import java.util.Arrays; - // import static java.util.Arrays.asList; - // static { - // List list1 = Arrays.asList("foo"); // Array class name not needed! - // List list2 = asList("foo"); // Preferred, used static import - // } - if (matches.isEmpty() && name.indexOf('.') >= 0) { - for (ASTImportDeclaration importDeclaration : imports) { - if (importDeclaration.isStatic()) { - String[] importParts = importDeclaration.getImportedName().split("\\."); - String[] nameParts = name.split("\\."); - if (importDeclaration.isImportOnDemand()) { - // Name class part matches class part of static import? - if (nameParts[nameParts.length - 2].equals(importParts[importParts.length - 1])) { - matches.add(importDeclaration); - } - } else { - // Last 2 parts match? - if (nameParts[nameParts.length - 1].equals(importParts[importParts.length - 1]) - && nameParts[nameParts.length - 2].equals(importParts[importParts.length - 2])) { - matches.add(importDeclaration); - } - } - } - } - } - - if (!matches.isEmpty()) { - ASTImportDeclaration firstMatch = matches.get(0); - - // Could this done to avoid a conflict? - if (!isAvoidingConflict(node, name, firstMatch)) { - String importStr = firstMatch.getImportedName() + (firstMatch.isImportOnDemand() ? ".*" : ""); - String type = firstMatch.isStatic() ? "static " : ""; - - addViolation(data, node, new Object[] { node.getImage(), importStr, type }); - } - } - - matches.clear(); - } - - private boolean isAvoidingConflict(final JavaNode node, final String name, - final ASTImportDeclaration firstMatch) { - // is it a conflict between different imports? - if (firstMatch.isImportOnDemand() && firstMatch.isStatic() && name.indexOf('.') != -1) { - final String methodCalled = name.substring(name.indexOf('.') + 1); - - // Is there any other static import conflictive? - for (final ASTImportDeclaration importDeclaration : imports) { - if (importDeclaration != firstMatch && importDeclaration.isStatic()) { - if (importDeclaration.getImportedName().startsWith(firstMatch.getImportedName()) - && importDeclaration.getImportedName().lastIndexOf('.') == firstMatch.getImportedName() - .length()) { - // A conflict against the same class is not an excuse, - // ie: - // import java.util.Arrays; - // import static java.util.Arrays.asList; - continue; - } - - if (importDeclaration.isImportOnDemand()) { - // We need type resolution to make sure there is a - // conflicting method - if (importDeclaration.getType() != null) { - for (final Method m : importDeclaration.getType().getMethods()) { - if (m.getName().equals(methodCalled)) { - return true; - } - } - } - } else if (importDeclaration.getImportedName().endsWith(methodCalled)) { - return true; - } - } - } - } - - // Is it a conflict with a class in the same file? - final String unqualifiedName = name.substring(name.lastIndexOf('.') + 1); - final int unqualifiedNameLength = unqualifiedName.length(); - final Set qualifiedTypes = node.getScope().getEnclosingScope(SourceFileScope.class) - .getQualifiedTypeNames().keySet(); - for (final String qualified : qualifiedTypes) { - int fullLength = qualified.length(); - if (qualified.endsWith(unqualifiedName) - && (fullLength == unqualifiedNameLength || qualified.charAt(fullLength - unqualifiedNameLength - 1) == '.')) { - return true; - } - } - - return false; - } -} diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/imports/UnusedImportsRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/imports/UnusedImportsRule.java deleted file mode 100644 index 419f8962c89..00000000000 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/imports/UnusedImportsRule.java +++ /dev/null @@ -1,158 +0,0 @@ -/** - * BSD-style license; for more info see http://pmd.sourceforge.net/license.html - */ - -package net.sourceforge.pmd.lang.java.rule.imports; - -import java.util.HashSet; -import java.util.Set; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import net.sourceforge.pmd.lang.ast.Node; -import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceType; -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.Comment; -import net.sourceforge.pmd.lang.java.ast.DummyJavaNode; -import net.sourceforge.pmd.lang.java.ast.FormalComment; -import net.sourceforge.pmd.lang.java.rule.AbstractJavaRule; -import net.sourceforge.pmd.lang.rule.ImportWrapper; - -public class UnusedImportsRule extends AbstractJavaRule { - - protected Set imports = new HashSet<>(); - - /* - * Patterns to match the following constructs: - * - * @see package.class#member(param, param) label {@linkplain - * package.class#member(param, param) label} {@link - * package.class#member(param, param) label} {@link package.class#field} - * {@value package.class#field} - * - * @throws package.class label - */ - private static final Pattern SEE_PATTERN = Pattern - .compile("@see\\s+(\\p{Alpha}\\w*)(?:#\\w*(?:\\(([\\w\\s,]*)\\))?)?"); - - private static final Pattern LINK_PATTERNS = Pattern - .compile("\\{@link(?:plain)?\\s+(\\p{Alpha}\\w*)(?:#\\w*(?:\\(([.\\w\\s,]*)\\))?)?[\\s\\}]"); - - private static final Pattern VALUE_PATTERN = Pattern.compile("\\{@value\\s+(\\p{Alpha}\\w*)[\\s#\\}]"); - - private static final Pattern THROWS_PATTERN = Pattern.compile("@throws\\s+(\\p{Alpha}\\w*)"); - - private static final Pattern[] PATTERNS = { SEE_PATTERN, LINK_PATTERNS, VALUE_PATTERN, THROWS_PATTERN }; - - @Override - public Object visit(ASTCompilationUnit node, Object data) { - imports.clear(); - super.visit(node, data); - visitComments(node); - - /* - * special handling for Bug 2606609 : False "UnusedImports" positive in - * package-info.java package annotations are processed before the import - * clauses so they need to be examined again later on. - */ - if (node.jjtGetNumChildren() > 0 && node.jjtGetChild(0) instanceof ASTPackageDeclaration) { - visit((ASTPackageDeclaration) node.jjtGetChild(0), data); - } - for (ImportWrapper wrapper : imports) { - addViolation(data, wrapper.getNode(), wrapper.getFullName()); - } - return data; - } - - private void visitComments(ASTCompilationUnit node) { - if (imports.isEmpty()) { - return; - } - for (Comment comment : node.getComments()) { - if (!(comment instanceof FormalComment)) { - continue; - } - for (Pattern p : PATTERNS) { - Matcher m = p.matcher(comment.getImage()); - while (m.find()) { - String s = m.group(1); - imports.remove(new ImportWrapper(s, s, new DummyJavaNode(-1))); - - if (m.groupCount() > 1) { - s = m.group(2); - if (s != null) { - String[] params = s.split("\\s*,\\s*"); - for (String param : params) { - final int firstDot = param.indexOf('.'); - final String expectedImportName; - if (firstDot == -1) { - expectedImportName = param; - } else { - expectedImportName = param.substring(0, firstDot); - } - imports.remove(new ImportWrapper(param, expectedImportName, new DummyJavaNode(-1))); - } - } - } - - if (imports.isEmpty()) { - return; - } - } - } - } - } - - @Override - public Object visit(ASTImportDeclaration node, Object data) { - if (!node.isImportOnDemand()) { - ASTName importedType = (ASTName) node.jjtGetChild(0); - String className; - if (isQualifiedName(importedType)) { - int lastDot = importedType.getImage().lastIndexOf('.') + 1; - className = importedType.getImage().substring(lastDot); - } else { - className = importedType.getImage(); - } - imports.add(new ImportWrapper(importedType.getImage(), className, node)); - } - - return data; - } - - @Override - public Object visit(ASTClassOrInterfaceType node, Object data) { - check(node); - return super.visit(node, data); - } - - @Override - public Object visit(ASTName node, Object data) { - check(node); - return data; - } - - protected void check(Node node) { - if (imports.isEmpty()) { - return; - } - ImportWrapper candidate = getImportWrapper(node); - if (imports.contains(candidate)) { - imports.remove(candidate); - } - } - - protected ImportWrapper getImportWrapper(Node node) { - String name; - if (!isQualifiedName(node)) { - name = node.getImage(); - } else { - name = node.getImage().substring(0, node.getImage().indexOf('.')); - } - ImportWrapper candidate = new ImportWrapper(node.getImage(), name); - return candidate; - } -} diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/junit/AbstractJUnitRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/junit/AbstractJUnitRule.java deleted file mode 100644 index a804ae06dfa..00000000000 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/junit/AbstractJUnitRule.java +++ /dev/null @@ -1,190 +0,0 @@ -/** - * BSD-style license; for more info see http://pmd.sourceforge.net/license.html - */ - -package net.sourceforge.pmd.lang.java.rule.junit; - -import java.util.List; - -import net.sourceforge.pmd.lang.ast.Node; -import net.sourceforge.pmd.lang.java.ast.ASTAnnotation; -import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceDeclaration; -import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceType; -import net.sourceforge.pmd.lang.java.ast.ASTCompilationUnit; -import net.sourceforge.pmd.lang.java.ast.ASTExtendsList; -import net.sourceforge.pmd.lang.java.ast.ASTImportDeclaration; -import net.sourceforge.pmd.lang.java.ast.ASTMethodDeclaration; -import net.sourceforge.pmd.lang.java.ast.ASTName; -import net.sourceforge.pmd.lang.java.ast.ASTResultType; -import net.sourceforge.pmd.lang.java.ast.ASTTypeParameters; -import net.sourceforge.pmd.lang.java.ast.TypeNode; -import net.sourceforge.pmd.lang.java.rule.AbstractJavaRule; -import net.sourceforge.pmd.lang.java.typeresolution.TypeHelper; - -public abstract class AbstractJUnitRule extends AbstractJavaRule { - - public static final Class JUNIT3_CLASS; - private static final String JUNIT3_CLASS_NAME = "junit.framework.TestCase"; - public static final Class JUNIT4_CLASS; - private static final String JUNIT4_CLASS_NAME = "org.junit.Test"; - public static final Class JUNIT5_CLASS; - private static final String JUNIT5_CLASS_NAME = "org.junit.jupiter.api.Test"; - - private boolean isJUnit3Class; - private boolean isJUnit4Class; - private boolean isJUnit5Class; - - static { - Class c; - - try { - c = Class.forName(JUNIT3_CLASS_NAME); - } catch (ClassNotFoundException | NoClassDefFoundError t) { - c = null; - } - JUNIT3_CLASS = c; - - try { - c = Class.forName(JUNIT4_CLASS_NAME); - } catch (ClassNotFoundException | NoClassDefFoundError t) { - c = null; - } - JUNIT4_CLASS = c; - - try { - c = Class.forName(JUNIT5_CLASS_NAME); - } catch (ClassNotFoundException | NoClassDefFoundError t) { - c = null; - } - JUNIT5_CLASS = c; - } - - @Override - public Object visit(ASTCompilationUnit node, Object data) { - - isJUnit3Class = false; - isJUnit4Class = false; - isJUnit5Class = false; - - isJUnit3Class = isJUnit3Class(node); - if (!isJUnit3Class) { - isJUnit4Class = isJUnit4Class(node); - isJUnit5Class = isJUnit5Class(node); - } - if (isJUnit4Class && isJUnit5Class) { - isJUnit4Class &= hasImports(node, JUNIT4_CLASS_NAME); - isJUnit5Class &= hasImports(node, JUNIT5_CLASS_NAME); - } - - if (!isTestNgClass(node) && (isJUnit3Class || isJUnit4Class || isJUnit5Class)) { - return super.visit(node, data); - } - return data; - } - - private boolean isTestNgClass(ASTCompilationUnit node) { - List imports = node.findDescendantsOfType(ASTImportDeclaration.class); - for (ASTImportDeclaration i : imports) { - if (i.getImportedName() != null && i.getImportedName().startsWith("org.testng")) { - return true; - } - } - return false; - } - - public boolean isJUnitMethod(ASTMethodDeclaration method, Object data) { - if (method.isAbstract() || method.isNative() || method.isStatic()) { - return false; // skip various inapplicable method variations - } - - if (!isJUnit5Class && !method.isPublic()) { - // junit5 class doesn't require test methods to be public anymore - return false; - } - - boolean result = false; - if (isJUnit3Class) { - result = isJUnit3Method(method); - } - - result |= isJUnit4Method(method); - result |= isJUnit5Method(method); - return result; - } - - private boolean isJUnit4Method(ASTMethodDeclaration method) { - return doesNodeContainJUnitAnnotation(method.jjtGetParent(), JUNIT4_CLASS, JUNIT4_CLASS_NAME); - } - - private boolean isJUnit5Method(ASTMethodDeclaration method) { - return doesNodeContainJUnitAnnotation(method.jjtGetParent(), JUNIT5_CLASS, JUNIT5_CLASS_NAME); - } - - private boolean isJUnit3Method(ASTMethodDeclaration method) { - Node node = method.jjtGetChild(0); - if (node instanceof ASTTypeParameters) { - node = method.jjtGetChild(1); - } - return ((ASTResultType) node).isVoid() && method.getMethodName().startsWith("test"); - } - - private boolean isJUnit3Class(ASTCompilationUnit node) { - ASTClassOrInterfaceDeclaration cid = node.getFirstDescendantOfType(ASTClassOrInterfaceDeclaration.class); - if (cid == null) { - return false; - } - - if (node.getType() != null && TypeHelper.isA(node, JUNIT3_CLASS)) { - return true; - } else if (node.getType() == null) { - ASTExtendsList extendsList = cid.getFirstChildOfType(ASTExtendsList.class); - if (extendsList == null) { - return false; - } - if (((ASTClassOrInterfaceType) extendsList.jjtGetChild(0)).getImage().endsWith("TestCase")) { - return true; - } - String className = cid.getImage(); - return className.endsWith("Test"); - } else if (hasImports(node, JUNIT3_CLASS_NAME)) { - return cid.getImage().endsWith("Test"); - } - return false; - } - - private boolean isJUnit4Class(ASTCompilationUnit node) { - return doesNodeContainJUnitAnnotation(node, JUNIT4_CLASS, JUNIT4_CLASS_NAME); - } - - private boolean isJUnit5Class(ASTCompilationUnit node) { - return doesNodeContainJUnitAnnotation(node, JUNIT5_CLASS, JUNIT5_CLASS_NAME); - } - - private boolean doesNodeContainJUnitAnnotation(Node node, Class annotationTypeClass, String annotationTypeClassName) { - List annotations = node.findDescendantsOfType(ASTAnnotation.class); - for (ASTAnnotation annotation : annotations) { - Node annotationTypeNode = annotation.jjtGetChild(0); - TypeNode annotationType = (TypeNode) annotationTypeNode; - if (annotationType.getType() == null) { - ASTName name = annotationTypeNode.getFirstChildOfType(ASTName.class); - if (name != null && (name.hasImageEqualTo("Test") || name.hasImageEqualTo(annotationTypeClassName))) { - return true; - } - } else if (annotationType.getType().equals(annotationTypeClass)) { - return true; - } - } - return false; - } - - private boolean hasImports(ASTCompilationUnit cu, String className) { - List imports = cu.findDescendantsOfType(ASTImportDeclaration.class); - for (ASTImportDeclaration importDeclaration : imports) { - ASTName name = importDeclaration.getFirstChildOfType(ASTName.class); - if (name != null && name.hasImageEqualTo(className)) { - return true; - } - } - return false; - } -} diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/junit/JUnitAssertionsShouldIncludeMessageRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/junit/JUnitAssertionsShouldIncludeMessageRule.java deleted file mode 100644 index 9b02e1a801d..00000000000 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/junit/JUnitAssertionsShouldIncludeMessageRule.java +++ /dev/null @@ -1,79 +0,0 @@ -/** - * BSD-style license; for more info see http://pmd.sourceforge.net/license.html - */ - -package net.sourceforge.pmd.lang.java.rule.junit; - -import java.util.ArrayList; -import java.util.List; - -import net.sourceforge.pmd.lang.java.ast.ASTArguments; -import net.sourceforge.pmd.lang.java.ast.ASTExpression; -import net.sourceforge.pmd.lang.java.ast.ASTName; -import net.sourceforge.pmd.lang.java.ast.ASTPrimaryExpression; -import net.sourceforge.pmd.lang.java.ast.ASTPrimaryPrefix; - -public class JUnitAssertionsShouldIncludeMessageRule extends AbstractJUnitRule { - - private class AssertionCall { - private final int argumentsCount; - private final String assertionName; - - AssertionCall(String assertionName, int argumentsCount) { - this.argumentsCount = argumentsCount; - this.assertionName = assertionName; - } - - public void check(Object ctx, ASTArguments node) { - if (node.getArgumentCount() == argumentsCount - && node.jjtGetParent().jjtGetParent() instanceof ASTPrimaryExpression) { - ASTPrimaryExpression primary = (ASTPrimaryExpression) node.jjtGetParent().jjtGetParent(); - if (primary.jjtGetChild(0) instanceof ASTPrimaryPrefix && primary.jjtGetChild(0).jjtGetNumChildren() > 0 - && primary.jjtGetChild(0).jjtGetChild(0) instanceof ASTName) { - ASTName name = (ASTName) primary.jjtGetChild(0).jjtGetChild(0); - - if (name.hasImageEqualTo(this.assertionName)) { - if (isException(node)) { - return; - } - JUnitAssertionsShouldIncludeMessageRule.this.addViolation(ctx, name); - } - } - } - } - - protected boolean isException(ASTArguments node) { - return false; - } - } - - private List checks = new ArrayList<>(); - - public JUnitAssertionsShouldIncludeMessageRule() { - checks.add(new AssertionCall("assertArrayEquals", 2)); - checks.add(new AssertionCall("assertEquals", 2)); - checks.add(new AssertionCall("assertFalse", 1)); - checks.add(new AssertionCall("assertNotNull", 1)); - checks.add(new AssertionCall("assertNotSame", 2)); - checks.add(new AssertionCall("assertNull", 1)); - checks.add(new AssertionCall("assertSame", 2)); - checks.add(new AssertionCall("assertThat", 2)); - checks.add(new AssertionCall("assertTrue", 1)); - checks.add(new AssertionCall("fail", 0)); - - checks.add(new AssertionCall("assertEquals", 3) { - @Override - protected boolean isException(ASTArguments node) { - ASTExpression firstArgument = node.getFirstDescendantOfType(ASTExpression.class); - return firstArgument.getType() == null || firstArgument.getType() == String.class; - } - }); - } - - public Object visit(ASTArguments node, Object data) { - for (AssertionCall call : checks) { - call.check(data, node); - } - return super.visit(node, data); - } -} diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/junit/TestClassWithoutTestCasesRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/junit/TestClassWithoutTestCasesRule.java deleted file mode 100644 index 6cec9c988c4..00000000000 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/junit/TestClassWithoutTestCasesRule.java +++ /dev/null @@ -1,42 +0,0 @@ -/** - * BSD-style license; for more info see http://pmd.sourceforge.net/license.html - */ - -package net.sourceforge.pmd.lang.java.rule.junit; - -import java.util.List; - -import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceDeclaration; -import net.sourceforge.pmd.lang.java.ast.ASTMethodDeclaration; - -public class TestClassWithoutTestCasesRule extends AbstractJUnitRule { - - @Override - public Object visit(ASTClassOrInterfaceDeclaration node, Object data) { - if (node.isAbstract() || node.isInterface() || node.isNested()) { - return data; - } - - List m = node.findDescendantsOfType(ASTMethodDeclaration.class); - boolean testsFound = false; - - if (m != null) { - for (ASTMethodDeclaration md : m) { - if (!isInInnerClassOrInterface(md) && isJUnitMethod(md, data)) { - testsFound = true; - } - } - } - - if (!testsFound) { - addViolation(data, node); - } - - return data; - } - - private boolean isInInnerClassOrInterface(ASTMethodDeclaration md) { - ASTClassOrInterfaceDeclaration p = md.getFirstParentOfType(ASTClassOrInterfaceDeclaration.class); - return p != null && p.isNested(); - } -} diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/logging/GuardDebugLoggingRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/logging/GuardDebugLoggingRule.java deleted file mode 100644 index d1afdeeb902..00000000000 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/logging/GuardDebugLoggingRule.java +++ /dev/null @@ -1,21 +0,0 @@ -/** - * BSD-style license; for more info see http://pmd.sourceforge.net/license.html - */ - -package net.sourceforge.pmd.lang.java.rule.logging; - -import java.util.HashMap; - -public class GuardDebugLoggingRule extends GuardLogStatementRule { - - public GuardDebugLoggingRule() { - super.guardStmtByLogLevel = new HashMap<>(1); - super.guardStmtByLogLevel.put(".debug", "isDebugEnabled"); - } - - @Override - protected void extractProperties() { - // This rule is not configurable - } - -} diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/logging/GuardLogStatementJavaUtilRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/logging/GuardLogStatementJavaUtilRule.java deleted file mode 100644 index e1eca01b9c1..00000000000 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/logging/GuardLogStatementJavaUtilRule.java +++ /dev/null @@ -1,81 +0,0 @@ -/** - * BSD-style license; for more info see http://pmd.sourceforge.net/license.html - */ - -package net.sourceforge.pmd.lang.java.rule.logging; - -import java.util.List; -import java.util.logging.Level; - -import net.sourceforge.pmd.lang.java.ast.ASTCompilationUnit; -import net.sourceforge.pmd.lang.java.ast.ASTImportDeclaration; - -public class GuardLogStatementJavaUtilRule extends GuardLogStatementRule { - - private static final String GUARD_METHOD_NAME = "isLoggable"; - - private static String extendedXPath = "//PrimaryPrefix[ends-with(Name/@Image, '.log')]\n" - + "[following-sibling::PrimarySuffix\n" - + " [ends-with(.//PrimaryPrefix/Name/@Image, 'LOG_LEVEL_UPPERCASE')]\n" - + " [count(../descendant::AdditiveExpression) > 0]\n" + "]\n" - + "[count(ancestor::IfStatement/Expression/descendant::PrimaryExpression\n" - + " [ends-with(descendant::PrimaryPrefix[1]/Name/@Image,'GUARD')]) = 0\n" + "or\n" - + "count(ancestor::IfStatement/Expression/descendant::PrimaryExpression\n" - + " [ends-with(descendant::PrimaryPrefix[2]/Name/@Image,'LOG_LEVEL_UPPERCASE')]) = 0]"; - - @Override - public Object visit(ASTCompilationUnit unit, Object data) { - if (isSlf4jOrLog4jImported(unit)) { - return data; - } - - String[] logLevels = getProperty(LOG_LEVELS).toArray(new String[0]); // TODO:cf convert to list - String[] guardMethods = getProperty(GUARD_METHODS).toArray(new String[0]); - - if (super.guardStmtByLogLevel.isEmpty() && logLevels.length > 0 && guardMethods.length > 0) { - configureGuards(logLevels, guardMethods); - } else if (super.guardStmtByLogLevel.isEmpty()) { - configureDefaultGuards(); - } - - findViolationForEachLogStatement(unit, data, extendedXPath); - return super.visit(unit, data); - } - - private boolean isSlf4jOrLog4jImported(ASTCompilationUnit unit) { - List imports = unit.findChildrenOfType(ASTImportDeclaration.class); - for (ASTImportDeclaration i : imports) { - if (i.getImportedName().startsWith("org.slf4j") || i.getImportedName().startsWith("org.apache.log4j")) { - return true; - } - } - return false; - } - - private void configureGuards(String[] logLevels, String[] guardMethods) { - String[] methods = guardMethods; - if (methods.length != logLevels.length) { - String firstMethodName = guardMethods[0]; - methods = new String[logLevels.length]; - for (int i = 0; i < logLevels.length; i++) { - methods[i] = firstMethodName; - } - } - for (int i = 0; i < logLevels.length; i++) { - super.guardStmtByLogLevel.put("." + logLevels[i], methods[i]); - } - } - - private void configureDefaultGuards() { - super.guardStmtByLogLevel.put(formatLogLevelString(Level.FINEST), GUARD_METHOD_NAME); - super.guardStmtByLogLevel.put(formatLogLevelString(Level.FINER), GUARD_METHOD_NAME); - super.guardStmtByLogLevel.put(formatLogLevelString(Level.FINE), GUARD_METHOD_NAME); - super.guardStmtByLogLevel.put(formatLogLevelString(Level.INFO), GUARD_METHOD_NAME); - super.guardStmtByLogLevel.put(formatLogLevelString(Level.WARNING), GUARD_METHOD_NAME); - super.guardStmtByLogLevel.put(formatLogLevelString(Level.SEVERE), GUARD_METHOD_NAME); - } - - private String formatLogLevelString(Level logLevel) { - return "." + logLevel.toString().toLowerCase(); - } -} diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/logging/GuardLogStatementRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/logging/GuardLogStatementRule.java deleted file mode 100644 index fcaa170dad3..00000000000 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/logging/GuardLogStatementRule.java +++ /dev/null @@ -1,129 +0,0 @@ -/** - * BSD-style license; for more info see http://pmd.sourceforge.net/license.html - */ - -package net.sourceforge.pmd.lang.java.rule.logging; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; - -import org.jaxen.JaxenException; - -import net.sourceforge.pmd.Rule; -import net.sourceforge.pmd.lang.ast.Node; -import net.sourceforge.pmd.lang.java.ast.ASTCompilationUnit; -import net.sourceforge.pmd.lang.java.rule.optimizations.AbstractOptimizationRule; -import net.sourceforge.pmd.properties.StringMultiProperty; - -/** - * Check that log.debug, log.trace, log.error, etc... statements are guarded by - * some test expression on log.isDebugEnabled() or log.isTraceEnabled(). - * - * @author Romain Pelisse - <belaran@gmail.com> - * @author Heiko Rupp - <hwr@pilhuhn.de> - * @author Tammo van Lessen - provided original XPath expression - * - */ -public class GuardLogStatementRule extends AbstractOptimizationRule implements Rule { - - public static final StringMultiProperty LOG_LEVELS = new StringMultiProperty("logLevels", "LogLevels to guard", - new String[] {}, 1.0f, ','); - - public static final StringMultiProperty GUARD_METHODS = new StringMultiProperty("guardsMethods", - "method use to guard the log statement", new String[] {}, 2.0f, ','); - - protected Map guardStmtByLogLevel = new HashMap<>(5); - - private static final String XPATH_EXPRESSION = "//PrimaryPrefix[ends-with(Name/@Image, 'LOG_LEVEL')]" - + "[count(../descendant::AdditiveExpression) > 0]" - + "[count(ancestor::IfStatement/Expression/descendant::PrimaryExpression[" - + "ends-with(descendant::PrimaryPrefix/Name/@Image,'GUARD')]) = 0]"; - - public GuardLogStatementRule() { - definePropertyDescriptor(LOG_LEVELS); - definePropertyDescriptor(GUARD_METHODS); - } - - @Override - public Object visit(ASTCompilationUnit unit, Object data) { - extractProperties(); - findViolationForEachLogStatement(unit, data, XPATH_EXPRESSION); - return super.visit(unit, data); - } - - protected void findViolationForEachLogStatement(ASTCompilationUnit unit, Object data, String xpathExpression) { - for (Entry entry : guardStmtByLogLevel.entrySet()) { - List nodes = findViolations(unit, entry.getKey(), entry.getValue(), xpathExpression); - for (Node node : nodes) { - super.addViolation(data, node); - } - } - } - - @SuppressWarnings("unchecked") - private List findViolations(ASTCompilationUnit unit, String logLevel, String guard, - String xpathExpression) { - try { - return unit - .findChildNodesWithXPath(xpathExpression.replaceAll("LOG_LEVEL_UPPERCASE", logLevel.toUpperCase()) - .replaceAll("LOG_LEVEL", logLevel).replaceAll("GUARD", guard)); - } catch (JaxenException e) { - e.printStackTrace(); - } - return Collections.EMPTY_LIST; - } - - private void setPropertiesDefaultValues(List logLevels, List guardMethods) { - logLevels.add("trace"); - logLevels.add("debug"); - logLevels.add("info"); - logLevels.add("warn"); - logLevels.add("error"); - - guardMethods.clear(); - guardMethods.add("isTraceEnabled"); - guardMethods.add("isDebugEnabled"); - guardMethods.add("isInfoEnabled"); - guardMethods.add("isWarnEnabled"); - guardMethods.add("isErrorEnabled"); - } - - protected void extractProperties() { - if (guardStmtByLogLevel.isEmpty()) { - - List logLevels = new ArrayList<>(super.getProperty(LOG_LEVELS)); - List guardMethods = new ArrayList<>(super.getProperty(GUARD_METHODS)); - - if (guardMethods.isEmpty() && !logLevels.isEmpty()) { - throw new IllegalArgumentException("Can't specify guardMethods without specifiying logLevels."); - } - - if (logLevels.isEmpty()) { - setPropertiesDefaultValues(logLevels, guardMethods); - } - - buildGuardStatementMap(logLevels, guardMethods); - } - } - - protected void buildGuardStatementMap(List logLevels, List guardMethods) { - for (String logLevel : logLevels) { - boolean found = false; - for (String guardMethod : guardMethods) { - if (!found && guardMethod.toLowerCase().contains(logLevel.toLowerCase())) { - found = true; - guardStmtByLogLevel.put("." + logLevel, guardMethod); - } - } - - if (!found) { - throw new IllegalArgumentException("No guard method associated to the logLevel:" + logLevel - + ". Should be something like 'is" + logLevel + "Enabled'."); - } - } - } -} diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/logging/InvalidSlf4jMessageFormatRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/logging/InvalidSlf4jMessageFormatRule.java deleted file mode 100644 index 4eb61ffcdd9..00000000000 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/logging/InvalidSlf4jMessageFormatRule.java +++ /dev/null @@ -1,198 +0,0 @@ -/** - * BSD-style license; for more info see http://pmd.sourceforge.net/license.html - */ - -package net.sourceforge.pmd.lang.java.rule.logging; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashSet; -import java.util.List; -import java.util.Set; - -import org.apache.commons.lang3.StringUtils; - -import net.sourceforge.pmd.lang.java.ast.ASTArgumentList; -import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceBody; -import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceType; -import net.sourceforge.pmd.lang.java.ast.ASTExpression; -import net.sourceforge.pmd.lang.java.ast.ASTFieldDeclaration; -import net.sourceforge.pmd.lang.java.ast.ASTLiteral; -import net.sourceforge.pmd.lang.java.ast.ASTMethodDeclaration; -import net.sourceforge.pmd.lang.java.ast.ASTName; -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.ASTVariableDeclarator; -import net.sourceforge.pmd.lang.java.ast.ASTVariableDeclaratorId; -import net.sourceforge.pmd.lang.java.ast.AbstractJavaTypeNode; -import net.sourceforge.pmd.lang.java.rule.AbstractJavaRule; -import net.sourceforge.pmd.lang.java.symboltable.VariableNameDeclaration; -import net.sourceforge.pmd.lang.symboltable.NameDeclaration; - -public class InvalidSlf4jMessageFormatRule extends AbstractJavaRule { - - private static final Set LOGGER_LEVELS; - private static final String LOGGER_CLASS = "org.slf4j.Logger"; - - static { - LOGGER_LEVELS = Collections - .unmodifiableSet(new HashSet(Arrays.asList("trace", "debug", "info", "warn", "error"))); - } - - @Override - public Object visit(final ASTName node, final Object data) { - final NameDeclaration nameDeclaration = node.getNameDeclaration(); - // ignore imports or methods - if (nameDeclaration == null || !(nameDeclaration instanceof VariableNameDeclaration)) { - return super.visit(node, data); - } - - // ignore non slf4j logger - Class type = ((VariableNameDeclaration) nameDeclaration).getType(); - if (type == null || !type.getName().equals(LOGGER_CLASS)) { - return super.visit(node, data); - } - - // get the node that contains the logger - final ASTPrimaryExpression parentNode = node.getFirstParentOfType(ASTPrimaryExpression.class); - - // get the log level - final String method = parentNode.getFirstChildOfType(ASTPrimaryPrefix.class).getFirstChildOfType(ASTName.class) - .getImage().replace(nameDeclaration.getImage() + ".", ""); - - // ignore if not a log level - if (!LOGGER_LEVELS.contains(method)) { - return super.visit(node, data); - } - - // find the arguments - final List argumentList = parentNode.getFirstChildOfType(ASTPrimarySuffix.class) - .getFirstDescendantOfType(ASTArgumentList.class).findChildrenOfType(ASTExpression.class); - - // remove the message parameter - final ASTPrimaryExpression messageParam = argumentList.remove(0).getFirstDescendantOfType(ASTPrimaryExpression.class); - final int expectedArguments = expectedArguments(messageParam); - - if (expectedArguments == 0) { - // ignore if we are not expecting arguments to format the message - // or if we couldn't analyze the message parameter - return super.visit(node, data); - } - - // Remove throwable param, since it is shown separately. - // But only, if it is not used as a placeholder argument - if (argumentList.size() > expectedArguments) { - removeThrowableParam(argumentList); - } - - if (argumentList.size() < expectedArguments) { - addViolationWithMessage(data, node, "Missing arguments," + getExpectedMessage(argumentList, expectedArguments)); - } else if (argumentList.size() > expectedArguments) { - addViolationWithMessage(data, node, "Too many arguments," + getExpectedMessage(argumentList, expectedArguments)); - } - - return super.visit(node, data); - } - - private boolean isNewThrowable(ASTPrimaryExpression last) { - // in case a new exception is created or the exception class is - // mentioned. - ASTClassOrInterfaceType classOrInterface = last.getFirstDescendantOfType(ASTClassOrInterfaceType.class); - if (classOrInterface != null && classOrInterface.getType() != null - && Throwable.class.isAssignableFrom(classOrInterface.getType())) { - return true; - } - return false; - } - - private boolean hasTypeThrowable(ASTPrimaryExpression last) { - // if the type could be determined already - if (last.getType() != null && Throwable.class.isAssignableFrom(last.getType())) { - return true; - } - return false; - } - - private boolean isReferencingThrowable(ASTPrimaryExpression last) { - // check the variable type, if there is a reference by name - ASTName variable = last.getFirstDescendantOfType(ASTName.class); - if (variable != null && variable.getNameDeclaration() != null - && variable.getNameDeclaration() instanceof VariableNameDeclaration) { - VariableNameDeclaration declaration = (VariableNameDeclaration) variable.getNameDeclaration(); - if (declaration.getType() != null && Throwable.class.isAssignableFrom(declaration.getType())) { - return true; - } - // convention: Exception type names should end with Exception - if (declaration.getTypeImage() != null && declaration.getTypeImage().endsWith("Exception")) { - return true; - } - } - return false; - } - - private void removeThrowableParam(final List params) { - // Throwable parameters are the last one in the list, if any. - if (params.isEmpty()) { - return; - } - int lastIndex = params.size() - 1; - ASTPrimaryExpression last = params.get(lastIndex).getFirstDescendantOfType(ASTPrimaryExpression.class); - - if (isNewThrowable(last) || hasTypeThrowable(last) || isReferencingThrowable(last)) { - params.remove(lastIndex); - } - } - - private String getExpectedMessage(final List params, final int expectedArguments) { - return " expected " + expectedArguments + (expectedArguments > 1 ? " arguments " : " argument ") + "but have " - + params.size(); - } - - private int expectedArguments(final ASTPrimaryExpression node) { - int count = 0; - // look if the logger have a literal message - if (node.getFirstDescendantOfType(ASTLiteral.class) != null) { - count = countPlaceholders(node); - } else if (node.getFirstDescendantOfType(ASTName.class) != null) { - final String variableName = node.getFirstDescendantOfType(ASTName.class).getImage(); - // look if the message is defined locally - final List localValiables = node.getFirstParentOfType(ASTMethodDeclaration.class) - .findDescendantsOfType(ASTVariableDeclarator.class); - count = getAmountOfExpectedArguments(variableName, localValiables); - - if (count == 0) { - // look if the message is defined in a field - final List fieldlist = node.getFirstParentOfType(ASTClassOrInterfaceBody.class) - .findDescendantsOfType(ASTFieldDeclaration.class); - // only look for ASTVariableDeclarator that are Fields - final List fields = new ArrayList(fieldlist.size()); - for (final ASTFieldDeclaration astFieldDeclaration : fieldlist) { - fields.add(astFieldDeclaration.getFirstChildOfType(ASTVariableDeclarator.class)); - } - count = getAmountOfExpectedArguments(variableName, fields); - } - } - return count; - } - - private int getAmountOfExpectedArguments(final String variableName, final List variables) { - for (final ASTVariableDeclarator astVariableDeclarator : variables) { - if (astVariableDeclarator.getFirstChildOfType(ASTVariableDeclaratorId.class).getImage() - .equals(variableName)) { - return countPlaceholders(astVariableDeclarator); - } - } - return 0; - } - - private int countPlaceholders(final AbstractJavaTypeNode node) { - int result = 0; // zero means, no placeholders, or we could not analyze the message parameter - ASTLiteral stringLiteral = node.getFirstDescendantOfType(ASTLiteral.class); - if (stringLiteral != null) { - result = StringUtils.countMatches(stringLiteral.getImage(), "{}"); - } - return result; - } -} diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/logging/MoreThanOneLoggerRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/logging/MoreThanOneLoggerRule.java deleted file mode 100644 index 20de8dec78c..00000000000 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/logging/MoreThanOneLoggerRule.java +++ /dev/null @@ -1,109 +0,0 @@ -/** - * BSD-style license; for more info see http://pmd.sourceforge.net/license.html - */ - -package net.sourceforge.pmd.lang.java.rule.logging; - -import java.util.Stack; - -import net.sourceforge.pmd.lang.ast.Node; -import net.sourceforge.pmd.lang.java.ast.ASTAnnotationTypeDeclaration; -import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceDeclaration; -import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceType; -import net.sourceforge.pmd.lang.java.ast.ASTEnumDeclaration; -import net.sourceforge.pmd.lang.java.ast.ASTReferenceType; -import net.sourceforge.pmd.lang.java.ast.ASTType; -import net.sourceforge.pmd.lang.java.ast.ASTVariableDeclarator; -import net.sourceforge.pmd.lang.java.ast.JavaNode; -import net.sourceforge.pmd.lang.java.rule.AbstractJavaRule; -import net.sourceforge.pmd.util.NumericConstants; - -public class MoreThanOneLoggerRule extends AbstractJavaRule { - - private static final Class LOG4J_LOGGER; - - private static final Class JAVA_LOGGER; - - private static final Class SLF4J_LOGGER; - - static { - Class c; - try { - c = Class.forName("org.apache.log4j.Logger"); - } catch (Throwable t) { - c = null; - } - LOG4J_LOGGER = c; - try { - c = Class.forName("java.util.logging.Logger"); - } catch (Throwable t) { - c = null; - } - JAVA_LOGGER = c; - try { - c = Class.forName("org.slf4j.Logger"); - } catch (Throwable t) { - c = null; - } - SLF4J_LOGGER = c; - } - - private Stack stack = new Stack<>(); - - private Integer count; - - @Override - public Object visit(ASTClassOrInterfaceDeclaration node, Object data) { - return init(node, data); - } - - @Override - public Object visit(ASTEnumDeclaration node, Object data) { - return init(node, data); - } - - @Override - public Object visit(ASTAnnotationTypeDeclaration node, Object data) { - return init(node, data); - } - - private Object init(JavaNode node, Object data) { - stack.push(count); - count = NumericConstants.ZERO; - - node.childrenAccept(this, data); - - if (count > 1) { - addViolation(data, node); - } - count = stack.pop(); - - return data; - } - - @Override - public Object visit(ASTVariableDeclarator node, Object data) { - if (count > 1) { - return super.visit(node, data); - } - Node type = node.jjtGetParent().getFirstChildOfType(ASTType.class); - if (type != null) { - Node reftypeNode = type.jjtGetChild(0); - if (reftypeNode instanceof ASTReferenceType) { - Node classOrIntType = reftypeNode.jjtGetChild(0); - if (classOrIntType instanceof ASTClassOrInterfaceType) { - Class clazzType = ((ASTClassOrInterfaceType) classOrIntType).getType(); - if (clazzType != null - && (clazzType.equals(LOG4J_LOGGER) || clazzType.equals(JAVA_LOGGER) - || clazzType.equals(SLF4J_LOGGER)) - || clazzType == null && "Logger".equals(classOrIntType.getImage())) { - ++count; - } - } - } - } - - return super.visit(node, data); - } - -} diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/basic/DoubleCheckedLockingRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/multithreading/DoubleCheckedLockingRule.java similarity index 99% rename from pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/basic/DoubleCheckedLockingRule.java rename to pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/multithreading/DoubleCheckedLockingRule.java index 363aa51d277..d3cc0a338a8 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/basic/DoubleCheckedLockingRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/multithreading/DoubleCheckedLockingRule.java @@ -2,7 +2,7 @@ * BSD-style license; for more info see http://pmd.sourceforge.net/license.html */ -package net.sourceforge.pmd.lang.java.rule.basic; +package net.sourceforge.pmd.lang.java.rule.multithreading; import java.util.ArrayList; import java.util.List; diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/NonThreadSafeSingletonRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/multithreading/NonThreadSafeSingletonRule.java similarity index 98% rename from pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/NonThreadSafeSingletonRule.java rename to pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/multithreading/NonThreadSafeSingletonRule.java index 7ff3e54be23..990e8b96b91 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/NonThreadSafeSingletonRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/multithreading/NonThreadSafeSingletonRule.java @@ -2,7 +2,7 @@ * BSD-style license; for more info see http://pmd.sourceforge.net/license.html */ -package net.sourceforge.pmd.lang.java.rule.design; +package net.sourceforge.pmd.lang.java.rule.multithreading; import java.util.HashMap; import java.util.List; diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/UnsynchronizedStaticDateFormatterRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/multithreading/UnsynchronizedStaticDateFormatterRule.java similarity index 97% rename from pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/UnsynchronizedStaticDateFormatterRule.java rename to pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/multithreading/UnsynchronizedStaticDateFormatterRule.java index 691622bab03..9365b6a2337 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/design/UnsynchronizedStaticDateFormatterRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/multithreading/UnsynchronizedStaticDateFormatterRule.java @@ -2,7 +2,7 @@ * BSD-style license; for more info see http://pmd.sourceforge.net/license.html */ -package net.sourceforge.pmd.lang.java.rule.design; +package net.sourceforge.pmd.lang.java.rule.multithreading; import java.util.Set; diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/naming/ClassNamingConventionsRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/naming/ClassNamingConventionsRule.java deleted file mode 100644 index 59015b70c8f..00000000000 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/naming/ClassNamingConventionsRule.java +++ /dev/null @@ -1,18 +0,0 @@ -/** - * BSD-style license; for more info see http://pmd.sourceforge.net/license.html - */ - -package net.sourceforge.pmd.lang.java.rule.naming; - -import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceDeclaration; -import net.sourceforge.pmd.lang.java.rule.AbstractJavaRule; - -public class ClassNamingConventionsRule extends AbstractJavaRule { - - public Object visit(ASTClassOrInterfaceDeclaration node, Object data) { - if (Character.isLowerCase(node.getImage().charAt(0))) { - addViolation(data, node); - } - return data; - } -} diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/naming/MethodNamingConventionsRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/naming/MethodNamingConventionsRule.java deleted file mode 100644 index e6535661897..00000000000 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/naming/MethodNamingConventionsRule.java +++ /dev/null @@ -1,66 +0,0 @@ -/** - * BSD-style license; for more info see http://pmd.sourceforge.net/license.html - */ - -package net.sourceforge.pmd.lang.java.rule.naming; - -import java.util.List; - -import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceBodyDeclaration; -import net.sourceforge.pmd.lang.java.ast.ASTCompilationUnit; -import net.sourceforge.pmd.lang.java.ast.ASTMarkerAnnotation; -import net.sourceforge.pmd.lang.java.ast.ASTMethodDeclaration; -import net.sourceforge.pmd.lang.java.ast.ASTMethodDeclarator; -import net.sourceforge.pmd.lang.java.ast.ASTName; -import net.sourceforge.pmd.lang.java.rule.AbstractJavaRule; -import net.sourceforge.pmd.properties.BooleanProperty; - -public class MethodNamingConventionsRule extends AbstractJavaRule { - - private boolean checkNativeMethods; - - private static final BooleanProperty CHECK_NATIVE_METHODS_DESCRIPTOR = new BooleanProperty("checkNativeMethods", - "Check native methods", true, 1.0f); - - public MethodNamingConventionsRule() { - definePropertyDescriptor(CHECK_NATIVE_METHODS_DESCRIPTOR); - } - - public Object visit(ASTCompilationUnit node, Object data) { - checkNativeMethods = getProperty(CHECK_NATIVE_METHODS_DESCRIPTOR); - return super.visit(node, data); - } - - public Object visit(ASTMethodDeclarator node, Object data) { - if (!checkNativeMethods && node.getFirstParentOfType(ASTMethodDeclaration.class).isNative()) { - return data; - } - - if (isOverriddenMethod(node)) { - return data; - } - - String methodName = node.getImage(); - - if (Character.isUpperCase(methodName.charAt(0))) { - addViolationWithMessage(data, node, "Method names should not start with capital letters"); - } - if (methodName.indexOf('_') >= 0) { - addViolationWithMessage(data, node, "Method names should not contain underscores"); - } - return data; - } - - private boolean isOverriddenMethod(ASTMethodDeclarator node) { - ASTClassOrInterfaceBodyDeclaration declaration = node - .getFirstParentOfType(ASTClassOrInterfaceBodyDeclaration.class); - List annotations = declaration.findDescendantsOfType(ASTMarkerAnnotation.class); - for (ASTMarkerAnnotation ann : annotations) { - ASTName name = ann.getFirstChildOfType(ASTName.class); - if (name != null && name.hasImageEqualTo("Override")) { - return true; - } - } - return false; - } -} diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/naming/MethodWithSameNameAsEnclosingClassRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/naming/MethodWithSameNameAsEnclosingClassRule.java deleted file mode 100644 index 55ef2f548cd..00000000000 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/naming/MethodWithSameNameAsEnclosingClassRule.java +++ /dev/null @@ -1,25 +0,0 @@ -/** - * BSD-style license; for more info see http://pmd.sourceforge.net/license.html - */ - -package net.sourceforge.pmd.lang.java.rule.naming; - -import java.util.List; - -import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceDeclaration; -import net.sourceforge.pmd.lang.java.ast.ASTMethodDeclarator; -import net.sourceforge.pmd.lang.java.rule.AbstractJavaRule; - -public class MethodWithSameNameAsEnclosingClassRule extends AbstractJavaRule { - - @Override - public Object visit(ASTClassOrInterfaceDeclaration node, Object data) { - List methods = node.findDescendantsOfType(ASTMethodDeclarator.class); - for (ASTMethodDeclarator m : methods) { - if (m.hasImageEqualTo(node.getImage())) { - addViolation(data, m); - } - } - return super.visit(node, data); - } -} diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/naming/VariableNamingConventionsRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/naming/VariableNamingConventionsRule.java deleted file mode 100644 index a56efdcadd0..00000000000 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/naming/VariableNamingConventionsRule.java +++ /dev/null @@ -1,247 +0,0 @@ -/** - * BSD-style license; for more info see http://pmd.sourceforge.net/license.html - */ - -package net.sourceforge.pmd.lang.java.rule.naming; - -import java.util.List; - -import net.sourceforge.pmd.lang.ast.Node; -import net.sourceforge.pmd.lang.java.ast.ASTAnnotationTypeDeclaration; -import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceDeclaration; -import net.sourceforge.pmd.lang.java.ast.ASTCompilationUnit; -import net.sourceforge.pmd.lang.java.ast.ASTFieldDeclaration; -import net.sourceforge.pmd.lang.java.ast.ASTFormalParameter; -import net.sourceforge.pmd.lang.java.ast.ASTFormalParameters; -import net.sourceforge.pmd.lang.java.ast.ASTLocalVariableDeclaration; -import net.sourceforge.pmd.lang.java.ast.ASTMethodDeclaration; -import net.sourceforge.pmd.lang.java.ast.ASTVariableDeclarator; -import net.sourceforge.pmd.lang.java.ast.ASTVariableDeclaratorId; -import net.sourceforge.pmd.lang.java.rule.AbstractJavaRule; -import net.sourceforge.pmd.properties.BooleanProperty; -import net.sourceforge.pmd.properties.PropertyDescriptor; -import net.sourceforge.pmd.properties.StringMultiProperty; - -public class VariableNamingConventionsRule extends AbstractJavaRule { - - private boolean checkMembers; - private boolean checkLocals; - private boolean checkParameters; - private boolean checkNativeMethodParameters; - private List staticPrefixes; - private List staticSuffixes; - private List memberPrefixes; - private List memberSuffixes; - private List localPrefixes; - private List localSuffixes; - private List parameterPrefixes; - private List parameterSuffixes; - - private static final BooleanProperty CHECK_MEMBERS_DESCRIPTOR = new BooleanProperty("checkMembers", - "Check member variables", true, 1.0f); - - private static final BooleanProperty CHECK_LOCALS_DESCRIPTOR = new BooleanProperty("checkLocals", - "Check local variables", true, 2.0f); - - private static final BooleanProperty CHECK_PARAMETERS_DESCRIPTOR = new BooleanProperty("checkParameters", - "Check constructor and method parameter variables", true, 3.0f); - - private static final BooleanProperty CHECK_NATIVE_METHOD_PARAMETERS_DESCRIPTOR = new BooleanProperty( - "checkNativeMethodParameters", "Check method parameter of native methods", true, 3.5f); - - private static final StringMultiProperty STATIC_PREFIXES_DESCRIPTOR = new StringMultiProperty("staticPrefix", - "Static variable prefixes", new String[] { "" }, 4.0f, ','); - - private static final StringMultiProperty STATIC_SUFFIXES_DESCRIPTOR = new StringMultiProperty("staticSuffix", - "Static variable suffixes", new String[] { "" }, 5.0f, ','); - - private static final StringMultiProperty MEMBER_PREFIXES_DESCRIPTOR = new StringMultiProperty("memberPrefix", - "Member variable prefixes", new String[] { "" }, 6.0f, ','); - - private static final StringMultiProperty MEMBER_SUFFIXES_DESCRIPTOR = new StringMultiProperty("memberSuffix", - "Member variable suffixes", new String[] { "" }, 7.0f, ','); - - private static final StringMultiProperty LOCAL_PREFIXES_DESCRIPTOR = new StringMultiProperty("localPrefix", - "Local variable prefixes", new String[] { "" }, 8.0f, ','); - - private static final StringMultiProperty LOCAL_SUFFIXES_DESCRIPTOR = new StringMultiProperty("localSuffix", - "Local variable suffixes", new String[] { "" }, 9.0f, ','); - - private static final StringMultiProperty PARAMETER_PREFIXES_DESCRIPTOR = new StringMultiProperty("parameterPrefix", - "Method parameter variable prefixes", new String[] { "" }, 10.0f, ','); - - private static final StringMultiProperty PARAMETER_SUFFIXES_DESCRIPTOR = new StringMultiProperty("parameterSuffix", - "Method parameter variable suffixes", new String[] { "" }, 11.0f, ','); - - public VariableNamingConventionsRule() { - definePropertyDescriptor(CHECK_MEMBERS_DESCRIPTOR); - definePropertyDescriptor(CHECK_LOCALS_DESCRIPTOR); - definePropertyDescriptor(CHECK_PARAMETERS_DESCRIPTOR); - definePropertyDescriptor(CHECK_NATIVE_METHOD_PARAMETERS_DESCRIPTOR); - definePropertyDescriptor(STATIC_PREFIXES_DESCRIPTOR); - definePropertyDescriptor(STATIC_SUFFIXES_DESCRIPTOR); - definePropertyDescriptor(MEMBER_PREFIXES_DESCRIPTOR); - definePropertyDescriptor(MEMBER_SUFFIXES_DESCRIPTOR); - definePropertyDescriptor(LOCAL_PREFIXES_DESCRIPTOR); - definePropertyDescriptor(LOCAL_SUFFIXES_DESCRIPTOR); - definePropertyDescriptor(PARAMETER_PREFIXES_DESCRIPTOR); - definePropertyDescriptor(PARAMETER_SUFFIXES_DESCRIPTOR); - } - - public Object visit(ASTCompilationUnit node, Object data) { - init(); - return super.visit(node, data); - } - - protected void init() { - checkMembers = getProperty(CHECK_MEMBERS_DESCRIPTOR); - checkLocals = getProperty(CHECK_LOCALS_DESCRIPTOR); - checkParameters = getProperty(CHECK_PARAMETERS_DESCRIPTOR); - checkNativeMethodParameters = getProperty(CHECK_NATIVE_METHOD_PARAMETERS_DESCRIPTOR); - staticPrefixes = getProperty(STATIC_PREFIXES_DESCRIPTOR); - staticSuffixes = getProperty(STATIC_SUFFIXES_DESCRIPTOR); - memberPrefixes = getProperty(MEMBER_PREFIXES_DESCRIPTOR); - memberSuffixes = getProperty(MEMBER_SUFFIXES_DESCRIPTOR); - localPrefixes = getProperty(LOCAL_PREFIXES_DESCRIPTOR); - localSuffixes = getProperty(LOCAL_SUFFIXES_DESCRIPTOR); - parameterPrefixes = getProperty(PARAMETER_PREFIXES_DESCRIPTOR); - parameterSuffixes = getProperty(PARAMETER_SUFFIXES_DESCRIPTOR); - } - - public Object visit(ASTFieldDeclaration node, Object data) { - if (!checkMembers) { - return data; - } - boolean isStatic = node.isStatic(); - boolean isFinal = node.isFinal(); - - Node type = node.jjtGetParent().jjtGetParent().jjtGetParent(); - // Anything from an interface is necessarily static and final - // Anything inside an annotation type is also static and final - if (type instanceof ASTClassOrInterfaceDeclaration && ((ASTClassOrInterfaceDeclaration) type).isInterface() - || type instanceof ASTAnnotationTypeDeclaration) { - isStatic = true; - isFinal = true; - } - return checkVariableDeclarators(node.isStatic() ? staticPrefixes : memberPrefixes, - isStatic ? staticSuffixes : memberSuffixes, node, isStatic, isFinal, data); - } - - public Object visit(ASTLocalVariableDeclaration node, Object data) { - if (!checkLocals) { - return data; - } - return checkVariableDeclarators(localPrefixes, localSuffixes, node, false, node.isFinal(), data); - } - - public Object visit(ASTFormalParameters node, Object data) { - if (!checkParameters) { - return data; - } - ASTMethodDeclaration methodDeclaration = node.getFirstParentOfType(ASTMethodDeclaration.class); - if (!checkNativeMethodParameters && methodDeclaration.isNative()) { - return data; - } - - for (ASTFormalParameter formalParameter : node.findChildrenOfType(ASTFormalParameter.class)) { - for (ASTVariableDeclaratorId variableDeclaratorId : formalParameter - .findChildrenOfType(ASTVariableDeclaratorId.class)) { - checkVariableDeclaratorId(parameterPrefixes, parameterSuffixes, node, false, formalParameter.isFinal(), - variableDeclaratorId, data); - } - } - return data; - } - - private Object checkVariableDeclarators(List prefixes, List suffixes, Node root, boolean isStatic, - boolean isFinal, Object data) { - for (ASTVariableDeclarator variableDeclarator : root.findChildrenOfType(ASTVariableDeclarator.class)) { - for (ASTVariableDeclaratorId variableDeclaratorId : variableDeclarator - .findChildrenOfType(ASTVariableDeclaratorId.class)) { - checkVariableDeclaratorId(prefixes, suffixes, root, isStatic, isFinal, variableDeclaratorId, data); - } - } - return data; - } - - private Object checkVariableDeclaratorId(List prefixes, List suffixes, Node root, boolean isStatic, - boolean isFinal, ASTVariableDeclaratorId variableDeclaratorId, Object data) { - - // Get the variable name - String varName = variableDeclaratorId.getImage(); - - // Skip serialVersionUID - if ("serialVersionUID".equals(varName)) { - return data; - } - - // Static finals should be uppercase - if (isStatic && isFinal) { - if (!varName.equals(varName.toUpperCase())) { - addViolationWithMessage(data, variableDeclaratorId, - "Variables that are final and static should be all capitals, ''{0}'' is not all capitals.", - new Object[] { varName }); - } - return data; - } else if (!isFinal) { - String normalizedVarName = normalizeVariableName(varName, prefixes, suffixes); - - if (normalizedVarName.indexOf('_') >= 0) { - addViolationWithMessage(data, variableDeclaratorId, - "Only variables that are final should contain underscores (except for underscores in standard prefix/suffix), ''{0}'' is not final.", - new Object[] { varName }); - } - if (Character.isUpperCase(varName.charAt(0))) { - addViolationWithMessage(data, variableDeclaratorId, - "Variables should start with a lowercase character, ''{0}'' starts with uppercase character.", - new Object[] { varName }); - } - } - return data; - } - - private String normalizeVariableName(String varName, List prefixes, List suffixes) { - return stripSuffix(stripPrefix(varName, prefixes), suffixes); - } - - private String stripSuffix(String varName, List suffixes) { - if (suffixes != null) { - for (String suffix : suffixes) { - if (varName.endsWith(suffix)) { - varName = varName.substring(0, varName.length() - suffix.length()); - break; - } - } - } - return varName; - } - - private String stripPrefix(String varName, List prefixes) { - if (prefixes != null) { - for (String prefix : prefixes) { - if (varName.startsWith(prefix)) { - return varName.substring(prefix.length()); - } - } - } - return varName; - } - - public boolean hasPrefixesOrSuffixes() { - - for (PropertyDescriptor desc : getPropertyDescriptors()) { - if (desc instanceof StringMultiProperty) { - List values = getProperty((StringMultiProperty) desc); - if (!values.isEmpty()) { - return true; - } - } - } - return false; - } - - public String dysfunctionReason() { - return hasPrefixesOrSuffixes() ? null : "No prefixes or suffixes specified"; - } - -} diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/optimizations/PrematureDeclarationRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/optimizations/PrematureDeclarationRule.java deleted file mode 100644 index 04edc4fc141..00000000000 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/optimizations/PrematureDeclarationRule.java +++ /dev/null @@ -1,234 +0,0 @@ -/** - * BSD-style license; for more info see http://pmd.sourceforge.net/license.html - */ - -package net.sourceforge.pmd.lang.java.rule.optimizations; - -import java.util.ArrayList; -import java.util.List; - -import net.sourceforge.pmd.lang.ast.Node; -import net.sourceforge.pmd.lang.java.ast.ASTBlockStatement; -import net.sourceforge.pmd.lang.java.ast.ASTForInit; -import net.sourceforge.pmd.lang.java.ast.ASTLambdaExpression; -import net.sourceforge.pmd.lang.java.ast.ASTLocalVariableDeclaration; -import net.sourceforge.pmd.lang.java.ast.ASTMethodDeclaration; -import net.sourceforge.pmd.lang.java.ast.ASTName; -import net.sourceforge.pmd.lang.java.ast.ASTReturnStatement; -import net.sourceforge.pmd.lang.java.ast.ASTThrowStatement; -import net.sourceforge.pmd.lang.java.ast.ASTVariableDeclarator; -import net.sourceforge.pmd.lang.java.ast.ASTVariableDeclaratorId; -import net.sourceforge.pmd.lang.java.ast.AbstractJavaNode; -import net.sourceforge.pmd.lang.java.rule.AbstractJavaRule; - -/** - * Checks for variables in methods that are defined before they are really - * needed. A reference is deemed to be premature if it is created ahead of a - * block of code that doesn't use it that also has the ability to return or - * throw an exception. - * - * @author Brian Remedios - */ -public class PrematureDeclarationRule extends AbstractJavaRule { - - /** - * - * @param node - * ASTLocalVariableDeclaration - * @param data - * Object - * @return Object - * @see net.sourceforge.pmd.lang.java.ast.JavaParserVisitor#visit(ASTLocalVariableDeclaration, - * Object) - */ - public Object visit(ASTLocalVariableDeclaration node, Object data) { - - // is it part of a for-loop declaration? - if (node.jjtGetParent() instanceof ASTForInit) { - // yes, those don't count - return visit((AbstractJavaNode) node, data); - } - - String varName = varNameIn(node); - - AbstractJavaNode grandparent = (AbstractJavaNode) node.jjtGetParent().jjtGetParent(); - - List nextBlocks = blocksAfter(grandparent, node); - - for (ASTBlockStatement block : nextBlocks) { - if (hasReferencesIn(block, varName) || isLambda(block)) { - break; - } - - if (hasExit(block)) { - addViolation(data, node, varName); - break; - } - } - - return visit((AbstractJavaNode) node, data); - } - - private boolean isLambda(ASTBlockStatement block) { - return block.getFirstParentOfType(ASTLambdaExpression.class) != null; - } - - /** - * Return whether a class of the specified type exists between the node - * argument and the topParent argument. - * - * @param node - * Node - * @param intermediateParentClass - * Class - * @param topParent - * Node - * @return boolean - */ - public static boolean hasAsParentBetween(Node node, Class intermediateParentClass, Node topParent) { - - Node currentParent = node.jjtGetParent(); - - while (!currentParent.equals(topParent)) { - currentParent = currentParent.jjtGetParent(); - if (currentParent.getClass().equals(intermediateParentClass)) { - return true; - } - } - return false; - } - - /** - * Returns whether the block contains a return call or throws an exception. - * Exclude blocks that have these things as part of an inner class. - * - * @param block - * ASTBlockStatement - * @return boolean - */ - @SuppressWarnings({ "rawtypes", "unchecked" }) - private boolean hasExit(ASTBlockStatement block) { - - List exitBlocks = block.findDescendantsOfType(ASTReturnStatement.class); - exitBlocks.addAll(block.findDescendantsOfType(ASTThrowStatement.class)); - - if (exitBlocks.isEmpty()) { - return false; - } - - // now check to see if the ones we have are part of a method on a - // declared inner class - // or part of a lambda expression - boolean result = false; - for (int i = 0; i < exitBlocks.size(); i++) { - Node exitNode = (Node) exitBlocks.get(i); - if (!hasAsParentBetween(exitNode, ASTMethodDeclaration.class, block) - && !hasAsParentBetween(exitNode, ASTLambdaExpression.class, block)) { - result = true; - break; - } - } - - return result; - } - - /** - * Returns whether the variable is mentioned within the statement block or - * not. - * - * @param block - * ASTBlockStatement - * @param varName - * String - * @return boolean - */ - private static boolean hasReferencesIn(ASTBlockStatement block, String varName) { - - List names = block.findDescendantsOfType(ASTName.class); - - for (ASTName name : names) { - if (isReference(varName, name.getImage())) { - return true; - } - } - return false; - } - - /** - * Return whether the shortName is part of the compound name by itself or as - * a method call receiver. - * - * @param shortName - * String - * @param compoundName - * String - * @return boolean - */ - private static boolean isReference(String shortName, String compoundName) { - - int dotPos = compoundName.indexOf('.'); - - return dotPos < 0 ? shortName.equals(compoundName) : shortName.endsWith(compoundName.substring(0, dotPos)); - } - - /** - * Return the name of the variable we just assigned something to. - * - * @param node - * ASTLocalVariableDeclaration - * @return String - */ - private static String varNameIn(ASTLocalVariableDeclaration node) { - ASTVariableDeclarator declarator = node.getFirstChildOfType(ASTVariableDeclarator.class); - return ((ASTVariableDeclaratorId) declarator.jjtGetChild(0)).getImage(); - } - - /** - * Returns the index of the node block in relation to its siblings. - * - * @param block - * SimpleJavaNode - * @param node - * Node - * @return int - */ - private static int indexOf(AbstractJavaNode block, Node node) { - - int count = block.jjtGetNumChildren(); - - for (int i = 0; i < count; i++) { - if (node == block.jjtGetChild(i)) { - return i; - } - } - - return -1; - } - - /** - * Returns all the blocks found right after the node supplied within the its - * current scope. - * - * @param block - * SimpleJavaNode - * @param node - * SimpleNode - * @return List - */ - private static List blocksAfter(AbstractJavaNode block, AbstractJavaNode node) { - - int count = block.jjtGetNumChildren(); - int start = indexOf(block, node.jjtGetParent()) + 1; - - List nextBlocks = new ArrayList<>(count); - - for (int i = start; i < count; i++) { - Node maybeBlock = block.jjtGetChild(i); - if (maybeBlock instanceof ASTBlockStatement) { - nextBlocks.add((ASTBlockStatement) maybeBlock); - } - } - - return nextBlocks; - } -} diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/package-info.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/package-info.java new file mode 100644 index 00000000000..6652b542d41 --- /dev/null +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/package-info.java @@ -0,0 +1,8 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +/** + * Contains the built-in rules bundled with the Java distribution. + */ +package net.sourceforge.pmd.lang.java.rule; diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/optimizations/AbstractOptimizationRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/performance/AbstractOptimizationRule.java similarity index 93% rename from pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/optimizations/AbstractOptimizationRule.java rename to pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/performance/AbstractOptimizationRule.java index c1c47c9118e..320c35ad30e 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/optimizations/AbstractOptimizationRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/performance/AbstractOptimizationRule.java @@ -2,7 +2,7 @@ * BSD-style license; for more info see http://pmd.sourceforge.net/license.html */ -package net.sourceforge.pmd.lang.java.rule.optimizations; +package net.sourceforge.pmd.lang.java.rule.performance; import java.util.List; @@ -19,6 +19,7 @@ */ public class AbstractOptimizationRule extends AbstractJavaRule { + @Override public Object visit(ASTClassOrInterfaceDeclaration node, Object data) { if (node.isInterface()) { return data; diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/strings/AppendCharacterWithCharRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/performance/AppendCharacterWithCharRule.java similarity index 95% rename from pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/strings/AppendCharacterWithCharRule.java rename to pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/performance/AppendCharacterWithCharRule.java index 96dcab93eed..b7335513fff 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/strings/AppendCharacterWithCharRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/performance/AppendCharacterWithCharRule.java @@ -2,7 +2,7 @@ * BSD-style license; for more info see http://pmd.sourceforge.net/license.html */ -package net.sourceforge.pmd.lang.java.rule.strings; +package net.sourceforge.pmd.lang.java.rule.performance; import net.sourceforge.pmd.lang.java.ast.ASTBlockStatement; import net.sourceforge.pmd.lang.java.ast.ASTLiteral; diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/optimizations/AvoidInstantiatingObjectsInLoopsRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/performance/AvoidInstantiatingObjectsInLoopsRule.java similarity index 97% rename from pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/optimizations/AvoidInstantiatingObjectsInLoopsRule.java rename to pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/performance/AvoidInstantiatingObjectsInLoopsRule.java index 3dc355b396c..af7045bf8ec 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/optimizations/AvoidInstantiatingObjectsInLoopsRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/performance/AvoidInstantiatingObjectsInLoopsRule.java @@ -2,7 +2,7 @@ * BSD-style license; for more info see http://pmd.sourceforge.net/license.html */ -package net.sourceforge.pmd.lang.java.rule.optimizations; +package net.sourceforge.pmd.lang.java.rule.performance; import net.sourceforge.pmd.lang.ast.Node; import net.sourceforge.pmd.lang.java.ast.ASTAllocationExpression; diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/basic/BigIntegerInstantiationRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/performance/BigIntegerInstantiationRule.java similarity index 97% rename from pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/basic/BigIntegerInstantiationRule.java rename to pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/performance/BigIntegerInstantiationRule.java index 1eecd844f06..39f2585c1e8 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/basic/BigIntegerInstantiationRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/performance/BigIntegerInstantiationRule.java @@ -2,7 +2,7 @@ * BSD-style license; for more info see http://pmd.sourceforge.net/license.html */ -package net.sourceforge.pmd.lang.java.rule.basic; +package net.sourceforge.pmd.lang.java.rule.performance; import java.math.BigDecimal; import java.math.BigInteger; diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/basic/BooleanInstantiationRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/performance/BooleanInstantiationRule.java similarity index 98% rename from pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/basic/BooleanInstantiationRule.java rename to pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/performance/BooleanInstantiationRule.java index f027e4dfb73..33dc35bc400 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/basic/BooleanInstantiationRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/performance/BooleanInstantiationRule.java @@ -2,7 +2,7 @@ * BSD-style license; for more info see http://pmd.sourceforge.net/license.html */ -package net.sourceforge.pmd.lang.java.rule.basic; +package net.sourceforge.pmd.lang.java.rule.performance; import net.sourceforge.pmd.lang.ast.Node; import net.sourceforge.pmd.lang.java.ast.ASTAllocationExpression; diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/performance/ConsecutiveAppendsShouldReuseRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/performance/ConsecutiveAppendsShouldReuseRule.java new file mode 100644 index 00000000000..db3626e90ce --- /dev/null +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/performance/ConsecutiveAppendsShouldReuseRule.java @@ -0,0 +1,142 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.performance; + +import java.util.List; +import java.util.Map; + +import net.sourceforge.pmd.lang.ast.Node; +import net.sourceforge.pmd.lang.java.ast.ASTBlockStatement; +import net.sourceforge.pmd.lang.java.ast.ASTExpression; +import net.sourceforge.pmd.lang.java.ast.ASTLocalVariableDeclaration; +import net.sourceforge.pmd.lang.java.ast.ASTName; +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.ASTStatement; +import net.sourceforge.pmd.lang.java.ast.ASTStatementExpression; +import net.sourceforge.pmd.lang.java.ast.ASTVariableDeclaratorId; +import net.sourceforge.pmd.lang.java.ast.AbstractJavaNode; +import net.sourceforge.pmd.lang.java.rule.AbstractJavaRule; +import net.sourceforge.pmd.lang.java.symboltable.VariableNameDeclaration; +import net.sourceforge.pmd.lang.java.typeresolution.TypeHelper; +import net.sourceforge.pmd.lang.symboltable.NameOccurrence; + +public class ConsecutiveAppendsShouldReuseRule extends AbstractJavaRule { + + @Override + public Object visit(ASTBlockStatement node, Object data) { + String variable = getVariableAppended(node); + if (variable != null) { + ASTBlockStatement nextSibling = getNextBlockStatementSibling(node); + if (nextSibling != null) { + String nextVariable = getVariableAppended(nextSibling); + if (nextVariable != null && nextVariable.equals(variable)) { + addViolation(data, node); + } + } + } + return super.visit(node, data); + } + + private ASTBlockStatement getNextBlockStatementSibling(Node node) { + Node parent = node.jjtGetParent(); + int childIndex = -1; + for (int i = 0; i < parent.jjtGetNumChildren(); i++) { + if (parent.jjtGetChild(i) == node) { + childIndex = i; + break; + } + } + if (childIndex + 1 < parent.jjtGetNumChildren()) { + Node nextSibling = parent.jjtGetChild(childIndex + 1); + if (nextSibling instanceof ASTBlockStatement) { + return (ASTBlockStatement) nextSibling; + } + } + return null; + } + + private String getVariableAppended(ASTBlockStatement node) { + if (isFirstChild(node, ASTStatement.class)) { + ASTStatement statement = (ASTStatement) node.jjtGetChild(0); + if (isFirstChild(statement, ASTStatementExpression.class)) { + ASTStatementExpression stmtExp = (ASTStatementExpression) statement.jjtGetChild(0); + if (stmtExp.jjtGetNumChildren() == 1) { + ASTPrimaryPrefix primaryPrefix = stmtExp.getFirstDescendantOfType(ASTPrimaryPrefix.class); + if (primaryPrefix != null) { + ASTName name = primaryPrefix.getFirstChildOfType(ASTName.class); + if (name != null) { + String image = name.getImage(); + if (image.endsWith(".append")) { + String variable = image.substring(0, image.indexOf('.')); + if (isAStringBuilderBuffer(primaryPrefix, variable)) { + return variable; + } + } + } + } + } else { + final ASTExpression exp = stmtExp.getFirstDescendantOfType(ASTExpression.class); + if (isFirstChild(exp, ASTPrimaryExpression.class)) { + final ASTPrimarySuffix primarySuffix = ((ASTPrimaryExpression) exp.jjtGetChild(0)) + .getFirstDescendantOfType(ASTPrimarySuffix.class); + if (primarySuffix != null) { + final String name = primarySuffix.getImage(); + if ("append".equals(name)) { + final ASTPrimaryExpression pExp = stmtExp + .getFirstDescendantOfType(ASTPrimaryExpression.class); + if (pExp != null) { + final ASTName astName = stmtExp.getFirstDescendantOfType(ASTName.class); + if (astName != null) { + final String variable = astName.getImage(); + if (isAStringBuilderBuffer(primarySuffix, variable)) { + return variable; + } + } + } + } + } + } + } + } + } else if (isFirstChild(node, ASTLocalVariableDeclaration.class)) { + ASTLocalVariableDeclaration lvd = (ASTLocalVariableDeclaration) node.jjtGetChild(0); + + ASTVariableDeclaratorId vdId = lvd.getFirstDescendantOfType(ASTVariableDeclaratorId.class); + ASTExpression exp = lvd.getFirstDescendantOfType(ASTExpression.class); + + if (exp != null) { + ASTPrimarySuffix primarySuffix = exp.getFirstDescendantOfType(ASTPrimarySuffix.class); + if (primarySuffix != null) { + final String name = primarySuffix.getImage(); + if ("append".equals(name)) { + String variable = vdId.getImage(); + if (isAStringBuilderBuffer(primarySuffix, variable)) { + return variable; + } + } + } + } + } + + return null; + } + + private boolean isAStringBuilderBuffer(AbstractJavaNode node, String name) { + Map> declarations = node.getScope() + .getDeclarations(VariableNameDeclaration.class); + for (VariableNameDeclaration decl : declarations.keySet()) { + if (decl.getName().equals(name) && TypeHelper.isExactlyAny(decl, StringBuilder.class, StringBuffer.class)) { + return true; + } + } + return false; + } + + private boolean isFirstChild(Node node, Class clazz) { + return node.jjtGetNumChildren() == 1 && clazz.isAssignableFrom(node.jjtGetChild(0).getClass()); + } +} diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/strings/ConsecutiveLiteralAppendsRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/performance/ConsecutiveLiteralAppendsRule.java similarity index 88% rename from pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/strings/ConsecutiveLiteralAppendsRule.java rename to pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/performance/ConsecutiveLiteralAppendsRule.java index 52c86a88364..f4f328e6164 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/strings/ConsecutiveLiteralAppendsRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/performance/ConsecutiveLiteralAppendsRule.java @@ -2,8 +2,9 @@ * BSD-style license; for more info see http://pmd.sourceforge.net/license.html */ -package net.sourceforge.pmd.lang.java.rule.strings; +package net.sourceforge.pmd.lang.java.rule.performance; +import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Map; @@ -71,8 +72,10 @@ public class ConsecutiveLiteralAppendsRule extends AbstractJavaRule { BLOCK_PARENTS.add(ASTMethodDeclaration.class); } - private static final IntegerProperty THRESHOLD_DESCRIPTOR = new IntegerProperty("threshold", - "Max consecutive appends", 1, 10, 1, 1.0f); + private static final IntegerProperty THRESHOLD_DESCRIPTOR + = IntegerProperty.named("threshold") + .desc("Max consecutive appends") + .range(1, 10).defaultValue(1).uiOrder(1.0f).build(); private int threshold = 1; @@ -83,7 +86,7 @@ public ConsecutiveLiteralAppendsRule() { @Override public Object visit(ASTVariableDeclaratorId node, Object data) { - if (!isStringBuffer(node)) { + if (!isStringBuilderOrBuffer(node)) { return data; } threshold = getProperty(THRESHOLD_DESCRIPTOR); @@ -94,33 +97,22 @@ public Object visit(ASTVariableDeclaratorId node, Object data) { } Node lastBlock = getFirstParentBlock(node); Node currentBlock = lastBlock; - Map> decls = node.getScope() - .getDeclarations(VariableNameDeclaration.class); Node rootNode = null; // only want the constructor flagged if it's really containing strings if (concurrentCount >= 1) { rootNode = node; } - for (List decl : decls.values()) { - for (NameOccurrence no : decl) { - JavaNameOccurrence jno = (JavaNameOccurrence) no; - Node n = jno.getLocation(); - - // skip the declarations/usages, that deal with a different - // variable - if (!node.getImage().equals(jno.getImage())) { - continue; - } - currentBlock = getFirstParentBlock(n); + List usages = determineUsages(node); - if (!InefficientStringBufferingRule.isInStringBufferOperation(n, 3, "append")) { - if (!jno.isPartOfQualifiedName()) { - checkForViolation(rootNode, data, concurrentCount); - concurrentCount = 0; - } - continue; - } + for (NameOccurrence no : usages) { + JavaNameOccurrence jno = (JavaNameOccurrence) no; + Node n = jno.getLocation(); + + currentBlock = getFirstParentBlock(n); + + if (InefficientStringBufferingRule.isInStringBufferOperation(n, 3, "append")) { + // append method call detected ASTPrimaryExpression s = n.getFirstParentOfType(ASTPrimaryExpression.class); int numChildren = s.jjtGetNumChildren(); for (int jx = 0; jx < numChildren; jx++) { @@ -154,12 +146,32 @@ public Object visit(ASTVariableDeclaratorId node, Object data) { } lastBlock = currentBlock; } + } else if (n.getImage().endsWith(".toString") || n.getImage().endsWith(".length")) { + // ignore toString and length, they do not change affect the content of the sb + } else { + // usage of the stringbuilder variable for any other purpose, including + // calling e.g. delete + checkForViolation(rootNode, data, concurrentCount); + concurrentCount = 0; } } checkForViolation(rootNode, data, concurrentCount); return data; } + private List determineUsages(ASTVariableDeclaratorId node) { + Map> decls = node.getScope() + .getDeclarations(VariableNameDeclaration.class); + for (Map.Entry> entry : decls.entrySet()) { + // find the first variable that matches + if (node.hasImageEqualTo(entry.getKey().getName())) { + return entry.getValue(); + } + } + + return Collections.emptyList(); + } + /** * Determine if the constructor contains (or ends with) a String Literal * @@ -374,10 +386,8 @@ private boolean isAppendingStringLiteral(Node node) { return n instanceof ASTLiteral; } - private static boolean isStringBuffer(ASTVariableDeclaratorId node) { - + private static boolean isStringBuilderOrBuffer(ASTVariableDeclaratorId node) { if (node.getType() != null) { - // return node.getType().equals(StringBuffer.class); return TypeHelper.isEither(node, StringBuffer.class, StringBuilder.class); } Node nn = node.getTypeNameNode(); diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/performance/InefficientEmptyStringCheckRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/performance/InefficientEmptyStringCheckRule.java new file mode 100644 index 00000000000..42af70e828e --- /dev/null +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/performance/InefficientEmptyStringCheckRule.java @@ -0,0 +1,82 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.performance; + +import net.sourceforge.pmd.lang.ast.Node; +import net.sourceforge.pmd.lang.java.ast.ASTPrimaryExpression; +import net.sourceforge.pmd.lang.java.rule.AbstractInefficientZeroCheck; +import net.sourceforge.pmd.lang.java.symboltable.JavaNameOccurrence; + +/** + * This rule finds code which inefficiently determines empty strings. + * + *

+ *

+ * str.trim().length()==0
+ * 
+ * or + *
+ * str.trim().isEmpty()
+ * 
+ * (for the same reason) is quite inefficient as trim() causes a new String to + * be created. A Smarter code to check for an empty string would be: + * + *
+ * private boolean checkTrimEmpty(String str) {
+ *     for(int i = 0; i < str.length(); i++) {
+ *         if(!Character.isWhitespace(str.charAt(i))) {
+ *             return false;
+ *         }
+ *     }
+ *     return true;
+ * }
+ * 
+ * or you can refer to Apache's StringUtils#isBlank + * (in commons-lang), Spring's StringUtils#hasText (in the Spring + * framework) or Google's CharMatcher#whitespace (in Guava) for + * existing implementations (some might include the check for != null). + *

+ * + * @author acaplan + */ +public class InefficientEmptyStringCheckRule extends AbstractInefficientZeroCheck { + + @Override + public boolean isTargetMethod(JavaNameOccurrence occ) { + if (occ.getNameForWhichThisIsAQualifier() != null + && occ.getNameForWhichThisIsAQualifier().getImage().indexOf("trim") != -1) { + Node pExpression = occ.getLocation().jjtGetParent().jjtGetParent(); + if (pExpression.jjtGetNumChildren() > 2 && "length".equals(pExpression.jjtGetChild(2).getImage())) { + return true; + } + } + return false; + } + + @Override + public boolean appliesToClassName(String name) { + return "String".equals(name); + } + + @Override + public Object visit(ASTPrimaryExpression node, Object data) { + + if (node.jjtGetNumChildren() > 3) { + // Check last suffix + if (!"isEmpty".equals(node.jjtGetChild(node.jjtGetNumChildren() - 2).getImage())) { + return data; + } + + Node prevCall = node.jjtGetChild(node.jjtGetNumChildren() - 4); + String target = prevCall.jjtGetNumChildren() > 0 ? prevCall.jjtGetChild(0).getImage() : prevCall.getImage(); + if (target != null && ("trim".equals(target) || target.endsWith(".trim"))) { + addViolation(data, node); + } + } + return data; + } + +} + diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/strings/InefficientStringBufferingRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/performance/InefficientStringBufferingRule.java similarity index 95% rename from pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/strings/InefficientStringBufferingRule.java rename to pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/performance/InefficientStringBufferingRule.java index f132bab6f60..ad00b76fbab 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/strings/InefficientStringBufferingRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/performance/InefficientStringBufferingRule.java @@ -2,7 +2,7 @@ * BSD-style license; for more info see http://pmd.sourceforge.net/license.html */ -package net.sourceforge.pmd.lang.java.rule.strings; +package net.sourceforge.pmd.lang.java.rule.performance; import java.util.Iterator; import java.util.List; @@ -121,10 +121,7 @@ private boolean isStringType(ASTName name) { private boolean isPrimitiveType(ASTName name) { ASTType type = getTypeNode(name); - if (type != null && !type.findChildrenOfType(ASTPrimitiveType.class).isEmpty()) { - return true; - } - return false; + return type != null && !type.findChildrenOfType(ASTPrimitiveType.class).isEmpty(); } private ASTType getTypeNode(ASTName name) { @@ -163,7 +160,7 @@ protected static boolean isInStringBufferOperation(Node node, int length, String if (argList == null || argList.jjtGetNumChildren() > 1) { return false; } - return TypeHelper.isEither((TypedNameDeclaration) n.getNameDeclaration(), StringBuffer.class, + return TypeHelper.isExactlyAny((TypedNameDeclaration) n.getNameDeclaration(), StringBuffer.class, StringBuilder.class); } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/strings/InsufficientStringBufferDeclarationRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/performance/InsufficientStringBufferDeclarationRule.java similarity index 96% rename from pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/strings/InsufficientStringBufferDeclarationRule.java rename to pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/performance/InsufficientStringBufferDeclarationRule.java index 79ff8c9c375..42042816aff 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/strings/InsufficientStringBufferDeclarationRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/performance/InsufficientStringBufferDeclarationRule.java @@ -2,7 +2,7 @@ * BSD-style license; for more info see http://pmd.sourceforge.net/license.html */ -package net.sourceforge.pmd.lang.java.rule.strings; +package net.sourceforge.pmd.lang.java.rule.performance; import java.util.HashMap; import java.util.HashSet; @@ -55,7 +55,7 @@ public class InsufficientStringBufferDeclarationRule extends AbstractJavaRule { @Override public Object visit(ASTVariableDeclaratorId node, Object data) { - if (!TypeHelper.isEither(node.getNameDeclaration(), StringBuffer.class, StringBuilder.class)) { + if (!TypeHelper.isExactlyAny(node.getNameDeclaration(), StringBuffer.class, StringBuilder.class)) { return data; } Node rootNode = node; @@ -199,16 +199,17 @@ private int processNode(Node sn) { anticipatedLength += str.length() - 2; } else if (literal.isCharLiteral()) { anticipatedLength += 1; - } else if (literal.isIntLiteral() && str.startsWith("0x")) { + } else if (literal.isIntLiteral()) { // but only if we are not inside a cast expression Node parentNode = literal.jjtGetParent().jjtGetParent().jjtGetParent(); if (parentNode instanceof ASTCastExpression && ((ASTCastExpression) parentNode).getType() == char.class) { anticipatedLength += 1; } else { + // any number, regardless of the base will be converted to base 10 // e.g. 0xdeadbeef -> will be converted to a // base 10 integer string: 3735928559 - anticipatedLength += String.valueOf(Long.parseLong(str.substring(2), 16)).length(); + anticipatedLength += String.valueOf(literal.getValueAsLong()).length(); } } else { anticipatedLength += str.length(); @@ -273,11 +274,8 @@ private int getConstructorLength(Node node, int constructorLength) { // characters // don't add the constructor's length iConstructorLength = 14 + str.length(); - } else if (literal.isIntLiteral() && str.startsWith("0x")) { - // bug 3516101 - the string could be a hex number - iConstructorLength = Integer.parseInt(str.substring(2), 16); - } else { - iConstructorLength = Integer.parseInt(str); + } else if (literal.isIntLiteral()) { + iConstructorLength = literal.getValueAsInt(); } } else { iConstructorLength = -1; diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/optimizations/RedundantFieldInitializerRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/performance/RedundantFieldInitializerRule.java similarity index 77% rename from pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/optimizations/RedundantFieldInitializerRule.java rename to pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/performance/RedundantFieldInitializerRule.java index 3cee0a6bd4c..0ed7e1915bf 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/optimizations/RedundantFieldInitializerRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/performance/RedundantFieldInitializerRule.java @@ -2,9 +2,7 @@ * BSD-style license; for more info see http://pmd.sourceforge.net/license.html */ -package net.sourceforge.pmd.lang.java.rule.optimizations; - -import java.math.BigInteger; +package net.sourceforge.pmd.lang.java.rule.performance; import net.sourceforge.pmd.lang.ast.Node; import net.sourceforge.pmd.lang.java.ast.ASTBooleanLiteral; @@ -32,6 +30,7 @@ public RedundantFieldInitializerRule() { addRuleChainVisit(ASTFieldDeclaration.class); } + @Override public Object visit(ASTFieldDeclaration fieldDeclaration, Object data) { // Finals can only be initialized once. if (fieldDeclaration.isFinal()) { @@ -83,21 +82,13 @@ public Object visit(ASTFieldDeclaration fieldDeclaration, Object data) { // code. Number value = -1; if (literal.isIntLiteral()) { - value = parseInteger(literal.getImage()); + value = literal.getValueAsInt(); } else if (literal.isLongLiteral()) { - String s = literal.getImage(); - // remove the ending "l" or "L" for long - // values - s = s.substring(0, s.length() - 1); - value = parseInteger(s); + value = literal.getValueAsLong(); } else if (literal.isFloatLiteral()) { - String s = literal.getImage(); - // remove the ending "f" or "F" for float - // values - s = s.substring(0, s.length() - 1); - value = Float.valueOf(s); + value = literal.getValueAsFloat(); } else if (literal.isDoubleLiteral()) { - value = Double.valueOf(literal.getImage()); + value = literal.getValueAsDouble(); } else if (literal.isCharLiteral()) { value = (int) literal.getImage().charAt(1); } @@ -118,17 +109,17 @@ public Object visit(ASTFieldDeclaration fieldDeclaration, Object data) { /** * Checks if a FieldDeclaration is a reference type (includes arrays). The * reference information is in the FieldDeclaration for this example: - * + * *
      * int[] ia1
      * 
- * + * * and in the VariableDeclarator for this example: - * + * *
      * int ia2[];
      * 
- * + * * . * * @param fieldDeclaration @@ -152,27 +143,4 @@ private boolean isRef(ASTFieldDeclaration fieldDeclaration, ASTVariableDeclarato private void addViolation(Object data, ASTVariableDeclarator variableDeclarator) { super.addViolation(data, variableDeclarator, variableDeclarator.jjtGetChild(0).getImage()); } - - private Number parseInteger(String s) { - boolean negative = false; - String number = s; - if (number.charAt(0) == '-') { - negative = true; - number = number.substring(1); - } - BigInteger result; - if (number.startsWith("0x") || number.startsWith("0X")) { - result = new BigInteger(number.substring(2).replaceAll("_", ""), 16); - } else if (number.startsWith("0b") || number.startsWith("0B")) { - result = new BigInteger(number.substring(2).replaceAll("_", ""), 8); - } else if (number.charAt(0) == '0' && number.length() > 1) { - result = new BigInteger(number.substring(1).replaceAll("_", ""), 8); - } else { - result = new BigInteger(number.replaceAll("_", "")); - } - if (negative) { - result = result.negate(); - } - return result; - } } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/strings/StringInstantiationRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/performance/StringInstantiationRule.java similarity index 86% rename from pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/strings/StringInstantiationRule.java rename to pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/performance/StringInstantiationRule.java index 8aa293809fd..8f17a26fca9 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/strings/StringInstantiationRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/performance/StringInstantiationRule.java @@ -2,7 +2,7 @@ * BSD-style license; for more info see http://pmd.sourceforge.net/license.html */ -package net.sourceforge.pmd.lang.java.rule.strings; +package net.sourceforge.pmd.lang.java.rule.performance; import java.util.List; @@ -34,7 +34,7 @@ public Object visit(ASTAllocationExpression node, Object data) { return data; } - if (node.hasDecendantOfAnyType(ASTArrayDimsAndInits.class, ASTAdditiveExpression.class)) { + if (node.hasDescendantOfAnyType(ASTArrayDimsAndInits.class, ASTAdditiveExpression.class)) { return data; } @@ -50,7 +50,7 @@ public Object visit(ASTAllocationExpression node, Object data) { return data; } - if (nd instanceof TypedNameDeclaration && TypeHelper.isA((TypedNameDeclaration) nd, String.class)) { + if (nd instanceof TypedNameDeclaration && TypeHelper.isExactlyAny((TypedNameDeclaration) nd, String.class)) { addViolation(data, node); } return data; diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/strings/StringToStringRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/performance/StringToStringRule.java similarity index 89% rename from pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/strings/StringToStringRule.java rename to pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/performance/StringToStringRule.java index c9f5a7eb16b..95c4e299867 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/strings/StringToStringRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/performance/StringToStringRule.java @@ -2,7 +2,7 @@ * BSD-style license; for more info see http://pmd.sourceforge.net/license.html */ -package net.sourceforge.pmd.lang.java.rule.strings; +package net.sourceforge.pmd.lang.java.rule.performance; import net.sourceforge.pmd.lang.java.ast.ASTMethodReference; import net.sourceforge.pmd.lang.java.ast.ASTName; @@ -16,8 +16,10 @@ public class StringToStringRule extends AbstractJavaRule { + @Override public Object visit(ASTVariableDeclaratorId node, Object data) { - if (!TypeHelper.isA(node.getNameDeclaration(), String.class)) { + if (!TypeHelper.isExactlyAny(node.getNameDeclaration(), String.class) + && !TypeHelper.isExactlyAny(node.getNameDeclaration(), String[].class)) { return data; } boolean isArray = node.isArray(); diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/optimizations/UnnecessaryWrapperObjectCreationRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/performance/UnnecessaryWrapperObjectCreationRule.java similarity index 97% rename from pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/optimizations/UnnecessaryWrapperObjectCreationRule.java rename to pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/performance/UnnecessaryWrapperObjectCreationRule.java index 45011f2ee64..4faf1d28915 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/optimizations/UnnecessaryWrapperObjectCreationRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/performance/UnnecessaryWrapperObjectCreationRule.java @@ -2,7 +2,7 @@ * BSD-style license; for more info see http://pmd.sourceforge.net/license.html */ -package net.sourceforge.pmd.lang.java.rule.optimizations; +package net.sourceforge.pmd.lang.java.rule.performance; import java.util.Set; @@ -25,6 +25,7 @@ public class UnnecessaryWrapperObjectCreationRule extends AbstractJavaRule { private static final Set SUFFIX_SET = CollectionUtil.asSet(new String[] { "toString", "byteValue", "shortValue", "intValue", "longValue", "floatValue", "doubleValue", "charValue", }); + @Override public Object visit(ASTPrimaryPrefix node, Object data) { if (node.jjtGetNumChildren() == 0 || !(node.jjtGetChild(0) instanceof ASTName)) { return super.visit(node, data); diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/strings/UseIndexOfCharRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/performance/UseIndexOfCharRule.java similarity index 89% rename from pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/strings/UseIndexOfCharRule.java rename to pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/performance/UseIndexOfCharRule.java index 4ad541fddb7..69d0f23b4c8 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/strings/UseIndexOfCharRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/performance/UseIndexOfCharRule.java @@ -2,7 +2,7 @@ * BSD-style license; for more info see http://pmd.sourceforge.net/license.html */ -package net.sourceforge.pmd.lang.java.rule.strings; +package net.sourceforge.pmd.lang.java.rule.performance; import net.sourceforge.pmd.lang.ast.Node; import net.sourceforge.pmd.lang.java.ast.ASTLiteral; @@ -17,7 +17,7 @@ public class UseIndexOfCharRule extends AbstractPoorMethodCall { /** * Method targetTypeName. - * + * * @return String */ @Override @@ -27,7 +27,7 @@ protected String targetTypename() { /** * Method methodNames. - * + * * @return String[] */ @Override @@ -35,9 +35,7 @@ protected String[] methodNames() { return METHOD_NAMES; } - /** - * {@inheritDoc} - */ + @Override protected boolean isViolationArgument(Node arg) { return ((ASTLiteral) arg).isSingleCharacterStringLiteral(); } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/optimizations/UseStringBufferForStringAppendsRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/performance/UseStringBufferForStringAppendsRule.java similarity index 92% rename from pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/optimizations/UseStringBufferForStringAppendsRule.java rename to pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/performance/UseStringBufferForStringAppendsRule.java index 1920cee434f..0b04df40543 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/optimizations/UseStringBufferForStringAppendsRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/performance/UseStringBufferForStringAppendsRule.java @@ -2,7 +2,9 @@ * BSD-style license; for more info see http://pmd.sourceforge.net/license.html */ -package net.sourceforge.pmd.lang.java.rule.optimizations; +package net.sourceforge.pmd.lang.java.rule.performance; + +import java.util.Objects; import net.sourceforge.pmd.lang.ast.Node; import net.sourceforge.pmd.lang.java.ast.ASTArgumentList; @@ -48,8 +50,9 @@ public Object visit(ASTVariableDeclaratorId node, Object data) { ASTConditionalExpression conditional = name.getFirstParentOfType(ASTConditionalExpression.class); if (conditional != null) { - Node thirdParent = name.jjtGetParent().jjtGetParent().jjtGetParent(); - if ((thirdParent == conditional || thirdParent.jjtGetParent() == conditional) + Node thirdParent = name.getNthParent(3); + Node fourthParent = name.getNthParent(4); + if ((Objects.equals(thirdParent, conditional) || Objects.equals(fourthParent, conditional)) && conditional.getFirstParentOfType(ASTStatementExpression.class) == statement) { // is used in ternary as only option (not appended to other // string) diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/strings/UseStringBufferLengthRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/performance/UseStringBufferLengthRule.java similarity index 96% rename from pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/strings/UseStringBufferLengthRule.java rename to pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/performance/UseStringBufferLengthRule.java index c30ff9df8f7..2d1c0fc9127 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/strings/UseStringBufferLengthRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/performance/UseStringBufferLengthRule.java @@ -2,7 +2,7 @@ * BSD-style license; for more info see http://pmd.sourceforge.net/license.html */ -package net.sourceforge.pmd.lang.java.rule.strings; +package net.sourceforge.pmd.lang.java.rule.performance; import java.util.HashSet; import java.util.List; @@ -64,7 +64,7 @@ public Object visit(ASTName decl, Object data) { return data; } if (alreadySeen.contains(nd) || !(nd instanceof TypedNameDeclaration) || nd instanceof TypedNameDeclaration - && TypeHelper.isNeither((TypedNameDeclaration) nd, StringBuffer.class, StringBuilder.class)) { + && TypeHelper.isExactlyNone((TypedNameDeclaration) nd, StringBuffer.class, StringBuilder.class)) { return data; } alreadySeen.add(nd); diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/strings/UselessStringValueOfRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/performance/UselessStringValueOfRule.java similarity index 98% rename from pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/strings/UselessStringValueOfRule.java rename to pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/performance/UselessStringValueOfRule.java index 86f2916774a..e54701cfede 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/strings/UselessStringValueOfRule.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/performance/UselessStringValueOfRule.java @@ -2,7 +2,7 @@ * BSD-style license; for more info see http://pmd.sourceforge.net/license.html */ -package net.sourceforge.pmd.lang.java.rule.strings; +package net.sourceforge.pmd.lang.java.rule.performance; import net.sourceforge.pmd.lang.ast.Node; import net.sourceforge.pmd.lang.java.ast.ASTAdditiveExpression; diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/security/HardCodedCryptoKeyRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/security/HardCodedCryptoKeyRule.java new file mode 100644 index 00000000000..33210da0e34 --- /dev/null +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/security/HardCodedCryptoKeyRule.java @@ -0,0 +1,94 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.security; + +import net.sourceforge.pmd.lang.ast.Node; +import net.sourceforge.pmd.lang.java.ast.ASTAllocationExpression; +import net.sourceforge.pmd.lang.java.ast.ASTArgumentList; +import net.sourceforge.pmd.lang.java.ast.ASTArguments; +import net.sourceforge.pmd.lang.java.ast.ASTArrayInitializer; +import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceType; +import net.sourceforge.pmd.lang.java.ast.ASTLiteral; +import net.sourceforge.pmd.lang.java.ast.ASTName; +import net.sourceforge.pmd.lang.java.ast.ASTPrimaryPrefix; +import net.sourceforge.pmd.lang.java.ast.ASTVariableInitializer; +import net.sourceforge.pmd.lang.java.rule.AbstractJavaRule; +import net.sourceforge.pmd.lang.java.symboltable.VariableNameDeclaration; +import net.sourceforge.pmd.lang.java.typeresolution.TypeHelper; + +/** + * Finds hard coded encryption keys that are passed to + * javax.crypto.spec.SecretKeySpec(key, algorithm). + * + * @author sergeygorbaty + * @since 6.4.0 + */ +public class HardCodedCryptoKeyRule extends AbstractJavaRule { + + private static final Class SECRET_KEY_SPEC = javax.crypto.spec.SecretKeySpec.class; + + public HardCodedCryptoKeyRule() { + addRuleChainVisit(ASTAllocationExpression.class); + } + + @Override + public Object visit(ASTAllocationExpression node, Object data) { + ASTClassOrInterfaceType declClassName = node.getFirstChildOfType(ASTClassOrInterfaceType.class); + if (declClassName != null && TypeHelper.isA(declClassName, SECRET_KEY_SPEC)) { + Node firstArgument = null; + + ASTArguments arguments = node.getFirstChildOfType(ASTArguments.class); + if (arguments.getArgumentCount() > 0) { + firstArgument = arguments.getFirstChildOfType(ASTArgumentList.class).jjtGetChild(0); + } + + if (firstArgument != null) { + ASTPrimaryPrefix prefix = firstArgument.getFirstDescendantOfType(ASTPrimaryPrefix.class); + validateProperKeyArgument(data, prefix); + } + } + return data; + } + + /** + * Recursively resolves the argument again, if the variable initializer + * is itself a expression. + * + * Then checks the expression for being a string literal or array + * + * @param data + * @param firstArgumentExpression + */ + private void validateProperKeyArgument(Object data, ASTPrimaryPrefix firstArgumentExpression) { + if (firstArgumentExpression == null) { + return; + } + + // named variable + ASTName namedVar = firstArgumentExpression.getFirstDescendantOfType(ASTName.class); + if (namedVar != null) { + // find where it's declared, if possible + if (namedVar != null && namedVar.getNameDeclaration() instanceof VariableNameDeclaration) { + VariableNameDeclaration varDecl = (VariableNameDeclaration) namedVar.getNameDeclaration(); + ASTVariableInitializer initializer = varDecl.getAccessNodeParent().getFirstDescendantOfType(ASTVariableInitializer.class); + if (initializer != null) { + validateProperKeyArgument(data, initializer.getFirstDescendantOfType(ASTPrimaryPrefix.class)); + } + } + } + + // hard coded array + ASTArrayInitializer arrayInit = firstArgumentExpression.getFirstDescendantOfType(ASTArrayInitializer.class); + if (arrayInit != null) { + addViolation(data, arrayInit); + } + + // string literal + ASTLiteral literal = firstArgumentExpression.getFirstDescendantOfType(ASTLiteral.class); + if (literal != null && literal.isStringLiteral()) { + addViolation(data, literal); + } + } +} diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/security/InsecureCryptoIvRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/security/InsecureCryptoIvRule.java new file mode 100644 index 00000000000..c91b2f26c57 --- /dev/null +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/security/InsecureCryptoIvRule.java @@ -0,0 +1,92 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.security; + +import net.sourceforge.pmd.lang.ast.Node; +import net.sourceforge.pmd.lang.java.ast.ASTAllocationExpression; +import net.sourceforge.pmd.lang.java.ast.ASTArgumentList; +import net.sourceforge.pmd.lang.java.ast.ASTArguments; +import net.sourceforge.pmd.lang.java.ast.ASTArrayInitializer; +import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceType; +import net.sourceforge.pmd.lang.java.ast.ASTLiteral; +import net.sourceforge.pmd.lang.java.ast.ASTName; +import net.sourceforge.pmd.lang.java.ast.ASTPrimaryPrefix; +import net.sourceforge.pmd.lang.java.ast.ASTVariableInitializer; +import net.sourceforge.pmd.lang.java.rule.AbstractJavaRule; +import net.sourceforge.pmd.lang.java.symboltable.VariableNameDeclaration; +import net.sourceforge.pmd.lang.java.typeresolution.TypeHelper; + +/** + * Finds hardcoded static Initialization Vectors vectors used with cryptographic + * operations. + * + * + * //bad: byte[] ivBytes = new byte[] {32, 87, -14, 25, 78, -104, 98, 40}; + * //bad: byte[] ivBytes = "hardcoded".getBytes(); + * //bad: byte[] ivBytes = someString.getBytes(); + * + * + *

{@link javax.crypto.spec.IvParameterSpec} must not be created from a static sources + * + * @author sergeygorbaty + * @since 6.3.0 + * + */ +public class InsecureCryptoIvRule extends AbstractJavaRule { + + public InsecureCryptoIvRule() { + addRuleChainVisit(ASTAllocationExpression.class); + } + + @Override + public Object visit(ASTAllocationExpression node, Object data) { + ASTClassOrInterfaceType declClassName = node.getFirstChildOfType(ASTClassOrInterfaceType.class); + if (declClassName != null && TypeHelper.isA(declClassName, javax.crypto.spec.IvParameterSpec.class)) { + Node firstArgument = null; + + ASTArguments arguments = node.getFirstChildOfType(ASTArguments.class); + if (arguments.getArgumentCount() > 0) { + firstArgument = arguments.getFirstChildOfType(ASTArgumentList.class).jjtGetChild(0); + } + + if (firstArgument != null) { + ASTPrimaryPrefix prefix = firstArgument.getFirstDescendantOfType(ASTPrimaryPrefix.class); + validateProperIv(data, prefix); + } + } + return data; + } + + private void validateProperIv(Object data, ASTPrimaryPrefix firstArgumentExpression) { + if (firstArgumentExpression == null) { + return; + } + + // named variable + ASTName namedVar = firstArgumentExpression.getFirstDescendantOfType(ASTName.class); + if (namedVar != null) { + // find where it's declared, if possible + if (namedVar != null && namedVar.getNameDeclaration() instanceof VariableNameDeclaration) { + VariableNameDeclaration varDecl = (VariableNameDeclaration) namedVar.getNameDeclaration(); + ASTVariableInitializer initializer = varDecl.getAccessNodeParent().getFirstDescendantOfType(ASTVariableInitializer.class); + if (initializer != null) { + validateProperIv(data, initializer.getFirstDescendantOfType(ASTPrimaryPrefix.class)); + } + } + } + + // hard coded array + ASTArrayInitializer arrayInit = firstArgumentExpression.getFirstDescendantOfType(ASTArrayInitializer.class); + if (arrayInit != null) { + addViolation(data, firstArgumentExpression); + } + + // string literal + ASTLiteral literal = firstArgumentExpression.getFirstDescendantOfType(ASTLiteral.class); + if (literal != null && literal.isStringLiteral()) { + addViolation(data, firstArgumentExpression); + } + } +} diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/strictexception/SignatureDeclareThrowsExceptionRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/strictexception/SignatureDeclareThrowsExceptionRule.java deleted file mode 100644 index 8b22268cce0..00000000000 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/strictexception/SignatureDeclareThrowsExceptionRule.java +++ /dev/null @@ -1,126 +0,0 @@ -/** - * BSD-style license; for more info see http://pmd.sourceforge.net/license.html - */ - -package net.sourceforge.pmd.lang.java.rule.strictexception; - -import java.util.Collections; -import java.util.List; - -import net.sourceforge.pmd.lang.ast.Node; -import net.sourceforge.pmd.lang.java.ast.ASTAnnotation; -import net.sourceforge.pmd.lang.java.ast.ASTCompilationUnit; -import net.sourceforge.pmd.lang.java.ast.ASTConstructorDeclaration; -import net.sourceforge.pmd.lang.java.ast.ASTImportDeclaration; -import net.sourceforge.pmd.lang.java.ast.ASTMethodDeclaration; -import net.sourceforge.pmd.lang.java.ast.ASTName; -import net.sourceforge.pmd.lang.java.ast.ASTNameList; -import net.sourceforge.pmd.lang.java.rule.AbstractJavaRule; - -/** - * - * @author Trond Andersen - * @version 1.0 - * @since 1.2 - */ - -public class SignatureDeclareThrowsExceptionRule extends AbstractJavaRule { - - private boolean junitImported; - - @Override - public Object visit(ASTCompilationUnit node, Object o) { - junitImported = false; - return super.visit(node, o); - } - - @Override - public Object visit(ASTImportDeclaration node, Object o) { - if (node.getImportedName().indexOf("junit") != -1) { - junitImported = true; - } - return super.visit(node, o); - } - - @Override - public Object visit(ASTMethodDeclaration methodDeclaration, Object o) { - if ((methodDeclaration.getMethodName().equals("setUp") || methodDeclaration.getMethodName().equals("tearDown")) - && junitImported) { - return super.visit(methodDeclaration, o); - } - - if (methodDeclaration.getMethodName().startsWith("test")) { - return super.visit(methodDeclaration, o); - } - - // Ignore overridden methods, the issue should be marked on the method definition - final List methodAnnotations = methodDeclaration.jjtGetParent().findChildrenOfType(ASTAnnotation.class); - for (final ASTAnnotation annotation : methodAnnotations) { - final ASTName annotationName = annotation.getFirstDescendantOfType(ASTName.class); - if (annotationName.hasImageEqualTo("Override") || annotationName.hasImageEqualTo("java.lang.Override")) { - return super.visit(methodDeclaration, o); - } - } - - List exceptionList = Collections.emptyList(); - ASTNameList nameList = methodDeclaration.getFirstChildOfType(ASTNameList.class); - if (nameList != null) { - exceptionList = nameList.findDescendantsOfType(ASTName.class); - } - if (!exceptionList.isEmpty()) { - evaluateExceptions(exceptionList, o); - } - return super.visit(methodDeclaration, o); - } - - @Override - public Object visit(ASTConstructorDeclaration constructorDeclaration, Object o) { - List exceptionList = constructorDeclaration.findDescendantsOfType(ASTName.class); - if (!exceptionList.isEmpty()) { - evaluateExceptions(exceptionList, o); - } - return super.visit(constructorDeclaration, o); - } - - /** - * Checks all exceptions for possible violation on the exception - * declaration. - * - * @param exceptionList - * containing all exception for declaration - * @param context - */ - private void evaluateExceptions(List exceptionList, Object context) { - for (ASTName exception : exceptionList) { - if (hasDeclaredExceptionInSignature(exception)) { - addViolation(context, exception); - } - } - } - - /** - * Checks if the given value is defined as Exception and the - * parent is either a method or constructor declaration. - * - * @param exception - * to evaluate - * @return true if Exception is declared and has proper parents - */ - private boolean hasDeclaredExceptionInSignature(ASTName exception) { - return exception.hasImageEqualTo("Exception") && isParentSignatureDeclaration(exception); - } - - /** - * Checks if the given exception is declared in the method or constructor - * signature. - * - * @param exception - * to evaluate - * @return true if parent node is either a method or constructor declaration - */ - private boolean isParentSignatureDeclaration(ASTName exception) { - Node parent = exception.jjtGetParent().jjtGetParent(); - return parent instanceof ASTMethodDeclaration || parent instanceof ASTConstructorDeclaration; - } - -} diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/strings/ConsecutiveAppendsShouldReuseRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/strings/ConsecutiveAppendsShouldReuseRule.java deleted file mode 100644 index 7b523184449..00000000000 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/strings/ConsecutiveAppendsShouldReuseRule.java +++ /dev/null @@ -1,173 +0,0 @@ -/** - * BSD-style license; for more info see http://pmd.sourceforge.net/license.html - */ - -package net.sourceforge.pmd.lang.java.rule.strings; - -import java.util.List; -import java.util.Map; - -import net.sourceforge.pmd.lang.ast.Node; -import net.sourceforge.pmd.lang.java.ast.ASTBlockStatement; -import net.sourceforge.pmd.lang.java.ast.ASTExpression; -import net.sourceforge.pmd.lang.java.ast.ASTLocalVariableDeclaration; -import net.sourceforge.pmd.lang.java.ast.ASTName; -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.ASTStatement; -import net.sourceforge.pmd.lang.java.ast.ASTStatementExpression; -import net.sourceforge.pmd.lang.java.ast.ASTVariableDeclaratorId; -import net.sourceforge.pmd.lang.java.ast.AbstractJavaNode; -import net.sourceforge.pmd.lang.java.rule.AbstractJavaRule; -import net.sourceforge.pmd.lang.java.symboltable.VariableNameDeclaration; -import net.sourceforge.pmd.lang.java.typeresolution.TypeHelper; -import net.sourceforge.pmd.lang.symboltable.NameOccurrence; - -/** - * Original rule was written with XPath, but didn't verify whether the two calls - * to append would have been done on the same variable. - * - *

-//BlockStatement[./Statement/StatementExpression//PrimaryPrefix/Name[ends-with(@Image,'.append')]
-                                      [substring-before(@Image, '.') =
-                                         ancestor::Block//LocalVariableDeclaration[./Type//ClassOrInterfaceType[@Image='StringBuffer']]//VariableDeclaratorId/@Image
-                                      ]
-                ]/following-sibling::*[1][./Statement/StatementExpression//PrimaryPrefix/Name[ends-with(@Image,'.append')]
-                                         [substring-before(@Image, '.') = 
-                                             ancestor::Block//LocalVariableDeclaration[./Type//ClassOrInterfaceType[@Image='StringBuffer']]//VariableDeclaratorId/@Image
-                                         ]
-                                      ] 
-|
-//BlockStatement[./Statement/StatementExpression//PrimaryPrefix/Name[ends-with(@Image,'.append')]
-                                      [substring-before(@Image, '.') = 
-                                         ancestor::Block//LocalVariableDeclaration[./Type//ClassOrInterfaceType[@Image='StringBuilder']]//VariableDeclaratorId/@Image
-                                      ]
-                ]/following-sibling::*[1][./Statement/StatementExpression//PrimaryPrefix/Name[ends-with(@Image,'.append')]
-                                         [substring-before(@Image, '.') = 
-                                             ancestor::Block//LocalVariableDeclaration[./Type//ClassOrInterfaceType[@Image='StringBuilder']]//VariableDeclaratorId/@Image
-                                         ]
-                                      ]
- * 
- * 
- * - */ -public class ConsecutiveAppendsShouldReuseRule extends AbstractJavaRule { - - @Override - public Object visit(ASTBlockStatement node, Object data) { - String variable = getVariableAppended(node); - if (variable != null) { - ASTBlockStatement nextSibling = getNextBlockStatementSibling(node); - if (nextSibling != null) { - String nextVariable = getVariableAppended(nextSibling); - if (nextVariable != null && nextVariable.equals(variable)) { - addViolation(data, node); - } - } - } - return super.visit(node, data); - } - - private ASTBlockStatement getNextBlockStatementSibling(Node node) { - Node parent = node.jjtGetParent(); - int childIndex = -1; - for (int i = 0; i < parent.jjtGetNumChildren(); i++) { - if (parent.jjtGetChild(i) == node) { - childIndex = i; - break; - } - } - if (childIndex + 1 < parent.jjtGetNumChildren()) { - Node nextSibling = parent.jjtGetChild(childIndex + 1); - if (nextSibling instanceof ASTBlockStatement) { - return (ASTBlockStatement) nextSibling; - } - } - return null; - } - - private String getVariableAppended(ASTBlockStatement node) { - if (isFirstChild(node, ASTStatement.class)) { - ASTStatement statement = (ASTStatement) node.jjtGetChild(0); - if (isFirstChild(statement, ASTStatementExpression.class)) { - ASTStatementExpression stmtExp = (ASTStatementExpression) statement.jjtGetChild(0); - if (stmtExp.jjtGetNumChildren() == 1) { - ASTPrimaryPrefix primaryPrefix = stmtExp.getFirstDescendantOfType(ASTPrimaryPrefix.class); - if (primaryPrefix != null) { - ASTName name = primaryPrefix.getFirstChildOfType(ASTName.class); - if (name != null) { - String image = name.getImage(); - if (image.endsWith(".append")) { - String variable = image.substring(0, image.indexOf('.')); - if (isAStringBuilderBuffer(primaryPrefix, variable)) { - return variable; - } - } - } - } - } else { - final ASTExpression exp = stmtExp.getFirstDescendantOfType(ASTExpression.class); - if (isFirstChild(exp, ASTPrimaryExpression.class)) { - final ASTPrimarySuffix primarySuffix = ((ASTPrimaryExpression) exp.jjtGetChild(0)) - .getFirstDescendantOfType(ASTPrimarySuffix.class); - if (primarySuffix != null) { - final String name = primarySuffix.getImage(); - if ("append".equals(name)) { - final ASTPrimaryExpression pExp = stmtExp - .getFirstDescendantOfType(ASTPrimaryExpression.class); - if (pExp != null) { - final ASTName astName = stmtExp.getFirstDescendantOfType(ASTName.class); - if (astName != null) { - final String variable = astName.getImage(); - if (isAStringBuilderBuffer(primarySuffix, variable)) { - return variable; - } - } - } - } - } - } - } - } - } else if (isFirstChild(node, ASTLocalVariableDeclaration.class)) { - ASTLocalVariableDeclaration lvd = (ASTLocalVariableDeclaration) node.jjtGetChild(0); - - ASTVariableDeclaratorId vdId = lvd.getFirstDescendantOfType(ASTVariableDeclaratorId.class); - ASTExpression exp = lvd.getFirstDescendantOfType(ASTExpression.class); - - if (exp != null) { - ASTPrimarySuffix primarySuffix = exp.getFirstDescendantOfType(ASTPrimarySuffix.class); - if (primarySuffix != null) { - final String name = primarySuffix.getImage(); - if ("append".equals(name)) { - String variable = vdId.getImage(); - if (isAStringBuilderBuffer(primarySuffix, variable)) { - return variable; - } - } - } - } - } - - return null; - } - - private boolean isAStringBuilderBuffer(AbstractJavaNode node, String name) { - Map> declarations = node.getScope() - .getDeclarations(VariableNameDeclaration.class); - for (VariableNameDeclaration decl : declarations.keySet()) { - if (decl.getName().equals(name) && TypeHelper.isEither(decl, StringBuilder.class, StringBuffer.class)) { - return true; - } - } - return false; - } - - private boolean isFirstChild(Node node, Class clazz) { - if (node.jjtGetNumChildren() == 1 && clazz.isAssignableFrom(node.jjtGetChild(0).getClass())) { - return true; - } - return false; - } -} diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/strings/InefficientEmptyStringCheckRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/strings/InefficientEmptyStringCheckRule.java deleted file mode 100644 index 3e55fc47d64..00000000000 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/strings/InefficientEmptyStringCheckRule.java +++ /dev/null @@ -1,68 +0,0 @@ -/** - * BSD-style license; for more info see http://pmd.sourceforge.net/license.html - */ - -package net.sourceforge.pmd.lang.java.rule.strings; - -import net.sourceforge.pmd.lang.ast.Node; -import net.sourceforge.pmd.lang.java.ast.ASTPrimaryExpression; -import net.sourceforge.pmd.lang.java.rule.AbstractInefficientZeroCheck; -import net.sourceforge.pmd.lang.java.symboltable.JavaNameOccurrence; - -/** - * This rule finds code which inefficiently determines empty strings. This code - * - *
- *         if(str.trim().length()==0){....
- * 
- * - *

- * is quite inefficient as trim() causes a new String to be created. Smarter - * code to check for an empty string would be: - *

- * - *
- * Character.isWhitespace(str.charAt(i));
- * 
- * - * @author acaplan - */ -public class InefficientEmptyStringCheckRule extends AbstractInefficientZeroCheck { - - @Override - public boolean isTargetMethod(JavaNameOccurrence occ) { - if (occ.getNameForWhichThisIsAQualifier() != null - && occ.getNameForWhichThisIsAQualifier().getImage().indexOf("trim") != -1) { - Node pExpression = occ.getLocation().jjtGetParent().jjtGetParent(); - if (pExpression.jjtGetNumChildren() > 2 && "length".equals(pExpression.jjtGetChild(2).getImage())) { - return true; - } - } - return false; - } - - @Override - public boolean appliesToClassName(String name) { - return "String".equals(name); - } - - @Override - public Object visit(ASTPrimaryExpression node, Object data) { - - if (node.jjtGetNumChildren() > 3) { - // Check last suffix - if (!"isEmpty".equals(node.jjtGetChild(node.jjtGetNumChildren() - 2).getImage())) { - return data; - } - - Node prevCall = node.jjtGetChild(node.jjtGetNumChildren() - 4); - String target = prevCall.jjtGetNumChildren() > 0 ? prevCall.jjtGetChild(0).getImage() : prevCall.getImage(); - if (target != null && ("trim".equals(target) || target.endsWith(".trim"))) { - addViolation(data, node); - } - } - return data; - } - -} - diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/unnecessary/UnnecessaryModifierRule.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/unnecessary/UnnecessaryModifierRule.java deleted file mode 100644 index 0d14ede72d1..00000000000 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/rule/unnecessary/UnnecessaryModifierRule.java +++ /dev/null @@ -1,118 +0,0 @@ -/** - * BSD-style license; for more info see http://pmd.sourceforge.net/license.html - */ - -package net.sourceforge.pmd.lang.java.rule.unnecessary; - -import net.sourceforge.pmd.lang.ast.Node; -import net.sourceforge.pmd.lang.java.ast.ASTAnnotationMethodDeclaration; -import net.sourceforge.pmd.lang.java.ast.ASTAnnotationTypeDeclaration; -import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceDeclaration; -import net.sourceforge.pmd.lang.java.ast.ASTEnumDeclaration; -import net.sourceforge.pmd.lang.java.ast.ASTFieldDeclaration; -import net.sourceforge.pmd.lang.java.ast.ASTMethodDeclaration; -import net.sourceforge.pmd.lang.java.rule.AbstractJavaRule; - -public class UnnecessaryModifierRule extends AbstractJavaRule { - - @Override - public Object visit(ASTEnumDeclaration node, Object data) { - if (node.isStatic()) { - // a static enum - addViolation(data, node, getMessage()); - } - - return super.visit(node, data); - } - - public Object visit(ASTAnnotationTypeDeclaration node, Object data) { - if (node.isAbstract()) { - // an abstract annotation - addViolation(data, node, getMessage()); - } - - if (!node.isNested()) { - return super.visit(node, data); - } - - Node parent = node.jjtGetParent().jjtGetParent().jjtGetParent(); - boolean isParentInterfaceOrAnnotation = parent instanceof ASTAnnotationTypeDeclaration - || parent instanceof ASTClassOrInterfaceDeclaration && ((ASTClassOrInterfaceDeclaration) parent).isInterface(); - - // a public annotation within an interface or annotation - if (node.isPublic() && isParentInterfaceOrAnnotation) { - addViolation(data, node, getMessage()); - } - - if (node.isStatic()) { - // a static annotation - addViolation(data, node, getMessage()); - } - - return super.visit(node, data); - } - - public Object visit(ASTClassOrInterfaceDeclaration node, Object data) { - if (node.isInterface() && node.isAbstract()) { - // an abstract interface - addViolation(data, node, getMessage()); - } - - if (!node.isNested()) { - return super.visit(node, data); - } - - Node parent = node.jjtGetParent().jjtGetParent().jjtGetParent(); - boolean isParentInterfaceOrAnnotation = parent instanceof ASTAnnotationTypeDeclaration - || parent instanceof ASTClassOrInterfaceDeclaration && ((ASTClassOrInterfaceDeclaration) parent).isInterface(); - - // a public interface within an interface or annotation - if (node.isInterface() && node.isPublic() && isParentInterfaceOrAnnotation) { - addViolation(data, node, getMessage()); - } - - if (node.isInterface() && node.isStatic()) { - // a static interface - addViolation(data, node, getMessage()); - } - - // a public and/or static class within an interface or annotation - if (!node.isInterface() && (node.isPublic() || node.isStatic()) && isParentInterfaceOrAnnotation) { - addViolation(data, node, getMessage()); - } - - return super.visit(node, data); - } - - public Object visit(ASTMethodDeclaration node, Object data) { - if (node.isSyntacticallyPublic() || node.isSyntacticallyAbstract()) { - check(node, data); - } - return super.visit(node, data); - } - - public Object visit(ASTFieldDeclaration node, Object data) { - if (node.isSyntacticallyPublic() || node.isSyntacticallyStatic() || node.isSyntacticallyFinal()) { - check(node, data); - } - return super.visit(node, data); - } - - public Object visit(ASTAnnotationMethodDeclaration node, Object data) { - if (node.isPublic() || node.isAbstract()) { - check(node, data); - } - return super.visit(node, data); - } - - private void check(Node fieldOrMethod, Object data) { - // third ancestor could be an AllocationExpression - // if this is a method in an anonymous inner class - Node parent = fieldOrMethod.jjtGetParent().jjtGetParent().jjtGetParent(); - if (parent instanceof ASTAnnotationTypeDeclaration - || parent instanceof ASTClassOrInterfaceDeclaration - && ((ASTClassOrInterfaceDeclaration) parent).isInterface()) { - addViolation(data, fieldOrMethod); - } - } -} diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/symboltable/ClassNameDeclaration.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/symboltable/ClassNameDeclaration.java index e68d24a6805..28bf128809b 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/symboltable/ClassNameDeclaration.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/symboltable/ClassNameDeclaration.java @@ -15,6 +15,7 @@ public ClassNameDeclaration(JavaNode node) { super(node); } + @Override public String toString() { if (node instanceof ASTClassOrInterfaceDeclaration) { if (((ASTClassOrInterfaceDeclaration) node).isInterface()) { @@ -31,10 +32,12 @@ public Node getAccessNodeParent() { return node; } + @Override public String getTypeImage() { return ((ASTClassOrInterfaceDeclaration) node).getImage(); } + @Override public Class getType() { return ((ASTClassOrInterfaceDeclaration) node).getType(); } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/symboltable/ClassScope.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/symboltable/ClassScope.java index 1b538970feb..33cb17768c6 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/symboltable/ClassScope.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/symboltable/ClassScope.java @@ -63,6 +63,7 @@ public class ClassScope extends AbstractJavaScope { // FIXME - this breaks given sufficiently nested code private static ThreadLocal anonymousInnerClassCounter = new ThreadLocal() { + @Override protected Integer initialValue() { return Integer.valueOf(1); } @@ -90,7 +91,7 @@ public ClassScope(final String className, final ClassNameDeclaration classNameDe *

FIXME - should have name like Foo$1, not Anonymous$1 to get this working * right, the parent scope needs to be passed in when instantiating a * ClassScope

- * + * * @param classNameDeclaration The declaration of this class, as known to the parent scope. */ public ClassScope(final ClassNameDeclaration classNameDeclaration) { @@ -122,6 +123,7 @@ public Map> getVariableDeclaration return getDeclarations(VariableNameDeclaration.class); } + @Override public Set addNameOccurrence(NameOccurrence occurrence) { JavaNameOccurrence javaOccurrence = (JavaNameOccurrence) occurrence; Set declarations = findVariableHere(javaOccurrence); @@ -131,7 +133,7 @@ public Set addNameOccurrence(NameOccurrence occurrence) { List nameOccurrences = getMethodDeclarations().get(decl); if (nameOccurrences == null) { // TODO may be a class name: Foo.this.super(); - + // search inner classes for (ClassNameDeclaration innerClass : getClassDeclarations().keySet()) { Scope innerClassScope = innerClass.getScope(); @@ -176,6 +178,7 @@ public String getClassName() { return this.className; } + @Override protected Set findVariableHere(JavaNameOccurrence occurrence) { if (occurrence.isThisOrSuper() || className.equals(occurrence.getImage())) { // Reference to ourselves! @@ -322,7 +325,7 @@ private MethodNameDeclaration createBuiltInMethodDeclaration(final String method variableDeclaratorId.setImage("arg" + i); formalParameter.jjtAddChild(variableDeclaratorId, 1); variableDeclaratorId.jjtSetParent(formalParameter); - + ASTType type = new ASTType(JavaParserTreeConstants.JJTTYPE); formalParameter.jjtAddChild(type, 0); type.jjtSetParent(formalParameter); @@ -352,7 +355,7 @@ private MethodNameDeclaration createBuiltInMethodDeclaration(final String method /** * Provide a list of types of the parameters of the given method * declaration. The types are simple type images. - * + * * @param mnd * the method declaration. * @return List of types @@ -369,6 +372,10 @@ private List determineParameterTypes(MethodNameDeclaration Map qualifiedTypeNames = fileScope.getQualifiedTypeNames(); for (ASTFormalParameter p : parameters) { + if (p.isExplicitReceiverParameter()) { + continue; + } + String typeImage = p.getTypeNode().getTypeImage(); // typeImage might be qualified/unqualified. If it refers to a type, // defined in the same toplevel class, @@ -435,7 +442,7 @@ private String findQualifiedName(String typeImage, Set candidates) { * (e.g. because it is itself the result of a method call), the parameter * type is used - so it is assumed, it is of the correct type. This might * cause confusion when methods are overloaded. - * + * * @param occurrence * the method call * @param parameterTypes @@ -659,6 +666,7 @@ private Node getNextSibling(Node current) { return null; } + @Override public String toString() { StringBuilder res = new StringBuilder("ClassScope (").append(className).append("): "); Map> classDeclarations = getClassDeclarations(); @@ -667,10 +675,10 @@ public String toString() { } Map> methodDeclarations = getMethodDeclarations(); if (!methodDeclarations.isEmpty()) { - for (MethodNameDeclaration mnd : methodDeclarations.keySet()) { - res.append(mnd.toString()); - int usages = methodDeclarations.get(mnd).size(); - res.append("(begins at line ").append(mnd.getNode().getBeginLine()).append(", ").append(usages) + for (Map.Entry> entry : methodDeclarations.entrySet()) { + res.append(entry.getKey().toString()); + int usages = entry.getValue().size(); + res.append("(begins at line ").append(entry.getKey().getNode().getBeginLine()).append(", ").append(usages) .append(" usages)"); res.append(", "); } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/symboltable/DeclarationFinderFunction.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/symboltable/DeclarationFinderFunction.java index e3829e3043d..b5132940280 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/symboltable/DeclarationFinderFunction.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/symboltable/DeclarationFinderFunction.java @@ -4,6 +4,7 @@ package net.sourceforge.pmd.lang.java.symboltable; +import net.sourceforge.pmd.lang.java.ast.ASTMethodReference; import net.sourceforge.pmd.lang.symboltable.NameDeclaration; import net.sourceforge.pmd.lang.symboltable.NameOccurrence; import net.sourceforge.pmd.util.SearchFunction; @@ -19,6 +20,11 @@ public DeclarationFinderFunction(NameOccurrence occurrence) { @Override public boolean applyTo(NameDeclaration nameDeclaration) { + // do not match method references + if (occurrence.getLocation() instanceof ASTMethodReference) { + return false; + } + if (isDeclaredBefore(nameDeclaration) && isSameName(nameDeclaration)) { decl = nameDeclaration; return false; diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/symboltable/JavaNameOccurrence.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/symboltable/JavaNameOccurrence.java index 02b1b0d7705..7dcc57fe91f 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/symboltable/JavaNameOccurrence.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/symboltable/JavaNameOccurrence.java @@ -89,8 +89,9 @@ public boolean isOnLeftHandSide() { primaryExpression = location.jjtGetParent().jjtGetParent().jjtGetParent(); } else { throw new RuntimeException( - "Found a NameOccurrence that didn't have an ASTPrimary Expression as parent or grandparent. Parent = " - + location.jjtGetParent() + " and grandparent = " + location.jjtGetParent().jjtGetParent()); + "Found a NameOccurrence (" + location + ") that didn't have an ASTPrimary Expression as parent or grandparent. Parent = " + + location.jjtGetParent() + " and grandparent = " + location.jjtGetParent().jjtGetParent() + + " (location line " + location.getBeginLine() + " col " + location.getBeginColumn() + ")"); } if (isStandAlonePostfix(primaryExpression)) { @@ -109,11 +110,7 @@ public boolean isOnLeftHandSide() { return false; } - if (isCompoundAssignment(primaryExpression)) { - return false; - } - - return true; + return !isCompoundAssignment(primaryExpression); } private boolean isCompoundAssignment(Node primaryExpression) { diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/symboltable/LocalScope.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/symboltable/LocalScope.java index 3e27f8b06b3..47a855ff281 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/symboltable/LocalScope.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/symboltable/LocalScope.java @@ -24,6 +24,7 @@ public Map> getVariableDeclaration return getDeclarations(VariableNameDeclaration.class); } + @Override public Set addNameOccurrence(NameOccurrence occurrence) { JavaNameOccurrence javaOccurrence = (JavaNameOccurrence) occurrence; Set declarations = findVariableHere(javaOccurrence); @@ -40,6 +41,7 @@ public Set addNameOccurrence(NameOccurrence occurrence) { return declarations; } + @Override public void addDeclaration(NameDeclaration nameDecl) { if (!(nameDecl instanceof VariableNameDeclaration || nameDecl instanceof ClassNameDeclaration)) { throw new IllegalArgumentException( @@ -49,6 +51,7 @@ public void addDeclaration(NameDeclaration nameDecl) { super.addDeclaration(nameDecl); } + @Override public Set findVariableHere(JavaNameOccurrence occurrence) { if (occurrence.isThisOrSuper() || occurrence.isMethodOrConstructorInvocation()) { return Collections.emptySet(); @@ -61,6 +64,7 @@ public Set findVariableHere(JavaNameOccurrence occurrence) { return Collections.emptySet(); } + @Override public String toString() { return "LocalScope:" + glomNames(getVariableDeclarations().keySet()); } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/symboltable/MethodScope.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/symboltable/MethodScope.java index a5230fde547..dc79e5e26f4 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/symboltable/MethodScope.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/symboltable/MethodScope.java @@ -13,7 +13,6 @@ import net.sourceforge.pmd.lang.java.ast.ASTConstructorDeclaration; import net.sourceforge.pmd.lang.java.ast.ASTName; import net.sourceforge.pmd.lang.symboltable.Applier; -import net.sourceforge.pmd.lang.symboltable.ImageFinderFunction; import net.sourceforge.pmd.lang.symboltable.NameDeclaration; import net.sourceforge.pmd.lang.symboltable.NameOccurrence; @@ -33,6 +32,7 @@ public Map> getVariableDeclaration return getDeclarations(VariableNameDeclaration.class); } + @Override public Set addNameOccurrence(NameOccurrence occurrence) { JavaNameOccurrence javaOccurrence = (JavaNameOccurrence) occurrence; Set declarations = findVariableHere(javaOccurrence); @@ -48,6 +48,7 @@ public Set addNameOccurrence(NameOccurrence occurrence) { return declarations; } + @Override public void addDeclaration(NameDeclaration variableDecl) { if (!(variableDecl instanceof VariableNameDeclaration || variableDecl instanceof ClassNameDeclaration)) { throw new IllegalArgumentException( @@ -56,11 +57,12 @@ public void addDeclaration(NameDeclaration variableDecl) { super.addDeclaration(variableDecl); } + @Override public Set findVariableHere(JavaNameOccurrence occurrence) { if (occurrence.isThisOrSuper() || occurrence.isMethodOrConstructorInvocation()) { return Collections.emptySet(); } - ImageFinderFunction finder = new ImageFinderFunction(occurrence.getImage()); + DeclarationFinderFunction finder = new DeclarationFinderFunction(occurrence); Applier.apply(finder, getVariableDeclarations().keySet().iterator()); if (finder.getDecl() != null) { return Collections.singleton(finder.getDecl()); @@ -75,6 +77,7 @@ public String getName() { return node.jjtGetChild(1).getImage(); } + @Override public String toString() { return "MethodScope:" + glomNames(getVariableDeclarations().keySet()); } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/symboltable/OccurrenceFinder.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/symboltable/OccurrenceFinder.java index baf14a5c579..37b6b819a57 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/symboltable/OccurrenceFinder.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/symboltable/OccurrenceFinder.java @@ -21,6 +21,7 @@ public class OccurrenceFinder extends JavaParserVisitorAdapter { private final Set additionalDeclarations = new HashSet<>(); + @Override public Object visit(ASTPrimaryExpression node, Object data) { NameFinder nameFinder = new NameFinder(node); diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/symboltable/ScopeAndDeclarationFinder.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/symboltable/ScopeAndDeclarationFinder.java index 7d3d5700220..94b898fbddc 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/symboltable/ScopeAndDeclarationFinder.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/symboltable/ScopeAndDeclarationFinder.java @@ -7,20 +7,15 @@ import java.util.ArrayDeque; import java.util.Deque; -import net.sourceforge.pmd.lang.ast.Node; import net.sourceforge.pmd.lang.java.ast.ASTAnnotationTypeDeclaration; import net.sourceforge.pmd.lang.java.ast.ASTBlock; -import net.sourceforge.pmd.lang.java.ast.ASTBlockStatement; import net.sourceforge.pmd.lang.java.ast.ASTCatchStatement; import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceBody; import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceDeclaration; import net.sourceforge.pmd.lang.java.ast.ASTCompilationUnit; import net.sourceforge.pmd.lang.java.ast.ASTConstructorDeclaration; import net.sourceforge.pmd.lang.java.ast.ASTEnumDeclaration; -import net.sourceforge.pmd.lang.java.ast.ASTFinallyStatement; import net.sourceforge.pmd.lang.java.ast.ASTForStatement; -import net.sourceforge.pmd.lang.java.ast.ASTFormalParameters; -import net.sourceforge.pmd.lang.java.ast.ASTIfStatement; import net.sourceforge.pmd.lang.java.ast.ASTImportDeclaration; import net.sourceforge.pmd.lang.java.ast.ASTLambdaExpression; import net.sourceforge.pmd.lang.java.ast.ASTMethodDeclaration; @@ -28,7 +23,6 @@ import net.sourceforge.pmd.lang.java.ast.ASTPackageDeclaration; import net.sourceforge.pmd.lang.java.ast.ASTSwitchStatement; import net.sourceforge.pmd.lang.java.ast.ASTTryStatement; -import net.sourceforge.pmd.lang.java.ast.ASTTypeParameters; import net.sourceforge.pmd.lang.java.ast.ASTVariableDeclaratorId; import net.sourceforge.pmd.lang.java.ast.AbstractJavaNode; import net.sourceforge.pmd.lang.java.ast.JavaNode; @@ -207,8 +201,18 @@ public Object visit(ASTClassOrInterfaceBody node, Object data) { @Override public Object visit(ASTBlock node, Object data) { - createLocalScope(node); - cont(node); + // top-level blocks for methods should have the same scope as parameters, just skip them + // same applies to catch statements defining exceptions + the catch block, and for-blocks + if (node.jjtGetParent() instanceof ASTMethodDeclaration + || node.jjtGetParent() instanceof ASTConstructorDeclaration + || node.jjtGetParent() instanceof ASTLambdaExpression + || node.jjtGetParent() instanceof ASTCatchStatement + || node.jjtGetParent() instanceof ASTForStatement) { + super.visit(node, null); + } else { + createLocalScope(node); + cont(node); + } return data; } @@ -219,57 +223,10 @@ public Object visit(ASTCatchStatement node, Object data) { return data; } - @Override - public Object visit(ASTFinallyStatement node, Object data) { - createLocalScope(node); - cont(node); - return data; - } - @Override public Object visit(ASTConstructorDeclaration node, Object data) { - /* - * Local variables declared inside the constructor need to be in a - * different scope so special handling is needed - */ createMethodScope(node); - - Scope methodScope = node.getScope(); - - Node formalParameters = node.jjtGetChild(0); - int i = 1; - int n = node.jjtGetNumChildren(); - if (!(formalParameters instanceof ASTFormalParameters)) { - visit((ASTTypeParameters) formalParameters, data); - formalParameters = node.jjtGetChild(1); - i++; - } - visit((ASTFormalParameters) formalParameters, data); - - Scope localScope = null; - for (; i < n; i++) { - JavaNode b = (JavaNode) node.jjtGetChild(i); - if (b instanceof ASTBlockStatement) { - if (localScope == null) { - createLocalScope(node); - localScope = node.getScope(); - } - b.setScope(localScope); - visit(b, data); - } else { - visit(b, data); - } - } - if (localScope != null) { - // pop the local scope - scopes.pop(); - - // reset the correct scope for the constructor - node.setScope(methodScope); - } - // pop the method scope - scopes.pop(); - + cont(node); return data; } @@ -304,13 +261,6 @@ public Object visit(ASTForStatement node, Object data) { return data; } - @Override - public Object visit(ASTIfStatement node, Object data) { - createLocalScope(node); - cont(node); - return data; - } - @Override public Object visit(ASTVariableDeclaratorId node, Object data) { VariableNameDeclaration decl = new VariableNameDeclaration(node); diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/symboltable/SimpleTypedNameDeclaration.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/symboltable/SimpleTypedNameDeclaration.java index 19978187c05..559358d4c33 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/symboltable/SimpleTypedNameDeclaration.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/symboltable/SimpleTypedNameDeclaration.java @@ -5,6 +5,7 @@ package net.sourceforge.pmd.lang.java.symboltable; import java.util.HashSet; +import java.util.Locale; import java.util.Set; /** @@ -150,8 +151,8 @@ private boolean internalEquals(Object obj) { } else if (!typeImage.equals(other.typeImage)) { // consider auto-boxing if (other.typeImage != null) { - String lcType = typeImage.toLowerCase(); - String otherLcType = other.typeImage.toLowerCase(); + String lcType = typeImage.toLowerCase(Locale.ROOT); + String otherLcType = other.typeImage.toLowerCase(Locale.ROOT); if (primitiveTypes.contains(lcType) && primitiveTypes.contains(otherLcType)) { if (lcType.equals(otherLcType)) { return true; diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/symboltable/SourceFileScope.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/symboltable/SourceFileScope.java index 64f22fb6d0e..877231c3de0 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/symboltable/SourceFileScope.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/symboltable/SourceFileScope.java @@ -40,7 +40,7 @@ public SourceFileScope(final ClassLoader classLoader, final String packageImage) /** * Configures the type resolution for the symbol table. - * + * * @param imports * the import declarations */ @@ -62,7 +62,7 @@ public Set getExplicitImports() { * Whether an auxclasspath has been configured or not. This can be used to * enable/disable more detailed symbol table analysis and type resolution * can be used - or to fall back to more simple implementation. - * + * * @return true if the auxclasspath is configured and types can * be resolved reliably. * @see #resolveType(String) @@ -73,7 +73,7 @@ public boolean hasAuxclasspath() { /** * Tries to resolve a class by name. - * + * * @param name * the name of the class * @return the class or null if no class could be found @@ -87,8 +87,8 @@ public String getPackageName() { } /** - * {@inheritDoc} - * + * + * * @throws IllegalArgumentException * if declaration is not a {@link ClassNameDeclaration} */ @@ -103,7 +103,7 @@ public void addDeclaration(NameDeclaration declaration) { /** * Convenience method that casts the declarations to * {@link ClassNameDeclaration}s. - * + * * @see #getDeclarations() * @return all class name declarations */ @@ -111,6 +111,7 @@ public Map> getClassDeclarations() { return getDeclarations(ClassNameDeclaration.class); } + @Override public String toString() { return "SourceFileScope: " + glomNames(getClassDeclarations().keySet()); } @@ -121,6 +122,7 @@ public ClassNameDeclaration findClassNameDeclaration(String name) { return (ClassNameDeclaration) finder.getDecl(); } + @Override protected Set findVariableHere(JavaNameOccurrence occ) { ImageFinderFunction finder = new ImageFinderFunction(occ.getImage()); Applier.apply(finder, getDeclarations().keySet().iterator()); @@ -133,7 +135,7 @@ protected Set findVariableHere(JavaNameOccurrence occ) { /** * Returns a set of all types defined within this source file. This includes * all top-level types and nested types. - * + * * @return set of all types in this source file. */ public Map getQualifiedTypeNames() { diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/symboltable/TypeSet.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/symboltable/TypeSet.java index 0bb46bbe36a..45c284a5690 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/symboltable/TypeSet.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/symboltable/TypeSet.java @@ -36,7 +36,7 @@ public TypeSet() { /** * The {@link TypeSet} provides type resolution for the symbol facade. - * + * * @param classLoader * the class loader to use to search classes (could be an * auxiliary class path) @@ -52,7 +52,7 @@ public TypeSet(ClassLoader classLoader) { /** * Whether the classloader is using the auxclasspath or not. - * + * * @return true if the classloader is using the auxclasspath * feature */ @@ -83,7 +83,7 @@ public interface Resolver { * Checks if the given class could be resolved by this resolver. Notice, * that a resolver's ability to resolve a class does not imply that the * class will actually be found and resolved. - * + * * @param name * the name of the class, might be fully classified or not. * @return whether the class can be resolved @@ -102,7 +102,7 @@ public abstract static class AbstractResolver implements Resolver { /** * Creates a new AbstractResolver that uses the given class loader. - * + * * @param pmdClassLoader * the class loader to use */ @@ -128,7 +128,8 @@ protected Class resolveMaybeInner(final String name, final String fqName) { try { return pmdClassLoader.loadClass(className); } catch (final ClassNotFoundException e) { - // Ignored, can never actually happen + // Ignored, can never actually happen, since we loaded the class at least once before... + throw new RuntimeException(e); // in case it happens anyway } } @@ -144,8 +145,8 @@ protected Class resolveMaybeInner(final String name, final String fqName) { // Update the mapping classNames.put(name, actualClassName); return c; - } catch (final ClassNotFoundException e) { - // Ignored + } catch (final ClassNotFoundException ignored) { + // Ignored, we'll try again with a different class name, assuming inner classes } } @@ -163,6 +164,7 @@ protected Class resolveMaybeInner(final String name, final String fqName) { return null; } + @Override public boolean couldResolve(final String name) { /* * Resolvers based on this one, will attempt to load the class from @@ -181,7 +183,7 @@ public static class ExplicitImportResolver extends AbstractResolver { /** * Creates a new {@link ExplicitImportResolver}. - * + * * @param pmdClassLoader * the class loader to use. * @param importStmts @@ -231,7 +233,7 @@ public static class CurrentPackageResolver extends AbstractResolver { /** * Creates a new {@link CurrentPackageResolver} - * + * * @param pmdClassLoader * the class loader to use * @param pkg @@ -290,7 +292,7 @@ public static class ImplicitImportResolver extends AbstractResolver { /** * Creates a {@link ImplicitImportResolver} - * + * * @param pmdClassLoader * the class loader */ @@ -337,7 +339,7 @@ public static class ImportOnDemandResolver extends AbstractResolver { /** * Creates a {@link ImportOnDemandResolver} - * + * * @param pmdClassLoader * the class loader to use * @param importStmts @@ -366,8 +368,8 @@ public Class resolve(String name) throws ClassNotFoundException { if (pmdClassLoader.couldResolve(fqClassName)) { try { return pmdClassLoader.loadClass(fqClassName); - } catch (ClassNotFoundException e) { - // ignored + } catch (ClassNotFoundException ignored) { + // ignored, we'll throw a custom exception later } } } @@ -405,7 +407,11 @@ public static class PrimitiveTypeResolver implements Resolver { types.put("long", long.class); types.put("boolean", boolean.class); types.put("byte", byte.class); - types.put("short", short.class); + + @SuppressWarnings("PMD.AvoidUsingShortType") // scoping the suppression just for the following statement + Class shortType = short.class; + types.put("short", shortType); + types.put("char", char.class); PRIMITIVE_TYPES = Collections.unmodifiableMap(types); } @@ -449,7 +455,7 @@ public boolean couldResolve(String name) { public static class FullyQualifiedNameResolver extends AbstractResolver { /** * Creates a {@link FullyQualifiedNameResolver} - * + * * @param pmdClassLoader * the class loader to use */ @@ -495,7 +501,7 @@ public String getASTCompilationUnitPackage() { /** * Adds a import to the list of imports - * + * * @param importString * the import to add */ @@ -513,7 +519,7 @@ public Set getExplicitImports() { /** * Resolves a class by its name using all known resolvers. - * + * * @param name * the name of the class, can be a simple name or a fully * qualified name. @@ -530,8 +536,11 @@ public Class findClass(String name) { if (resolver.couldResolve(name)) { try { return resolver.resolve(name); - } catch (ClassNotFoundException cnfe) { + } catch (ClassNotFoundException ignored) { // ignored, maybe another resolver will find the class + } catch (LinkageError le) { + // we found the class, but there is a problem with it (see https://github.com/pmd/pmd/issues/328) + return null; } } } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/symboltable/VariableNameDeclaration.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/symboltable/VariableNameDeclaration.java index b2d71756e79..1a3a226cbf8 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/symboltable/VariableNameDeclaration.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/symboltable/VariableNameDeclaration.java @@ -37,6 +37,16 @@ public boolean isArray() { } } + public int getArrayDepth() { + ASTVariableDeclaratorId astVariableDeclaratorId = (ASTVariableDeclaratorId) node; + ASTType typeNode = astVariableDeclaratorId.getTypeNode(); + if (typeNode != null) { + return ((Dimensionable) typeNode.jjtGetParent()).getArrayDepth(); + } else { + return 0; + } + } + public boolean isVarargs() { ASTVariableDeclaratorId astVariableDeclaratorId = (ASTVariableDeclaratorId) node; ASTFormalParameter parameter = astVariableDeclaratorId.getFirstParentOfType(ASTFormalParameter.class); @@ -44,18 +54,27 @@ public boolean isVarargs() { } public boolean isExceptionBlockParameter() { - return ((ASTVariableDeclaratorId) node).isExceptionBlockParameter(); + return getDeclaratorId().isExceptionBlockParameter(); } + /** + * @deprecated use {@link #isTypeInferred()} + */ + @Deprecated public boolean isLambdaTypelessParameter() { - return getAccessNodeParent() instanceof ASTLambdaExpression; + return isTypeInferred(); + } + + public boolean isTypeInferred() { + return getDeclaratorId().isTypeInferred(); } public boolean isPrimitiveType() { - return !isLambdaTypelessParameter() + return !isTypeInferred() && getAccessNodeParent().getFirstChildOfType(ASTType.class).jjtGetChild(0) instanceof ASTPrimitiveType; } + @Override public String getTypeImage() { TypeNode typeNode = getTypeNode(); if (typeNode != null) { @@ -68,7 +87,7 @@ public String getTypeImage() { * Note that an array of primitive types (int[]) is a reference type. */ public boolean isReferenceType() { - return !isLambdaTypelessParameter() + return !isTypeInferred() && getAccessNodeParent().getFirstChildOfType(ASTType.class).jjtGetChild(0) instanceof ASTReferenceType; } @@ -87,12 +106,13 @@ private TypeNode getTypeNode() { if (isPrimitiveType()) { return (TypeNode) getAccessNodeParent().getFirstChildOfType(ASTType.class).jjtGetChild(0); } - if (!isLambdaTypelessParameter()) { + if (!isTypeInferred()) { return (TypeNode) getAccessNodeParent().getFirstChildOfType(ASTType.class).jjtGetChild(0).jjtGetChild(0); } return null; } + @Override public Class getType() { TypeNode typeNode = getTypeNode(); if (typeNode != null) { diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/typeresolution/ClassTypeResolver.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/typeresolution/ClassTypeResolver.java index f0208ef04dd..7f3c9fb3226 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/typeresolution/ClassTypeResolver.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/typeresolution/ClassTypeResolver.java @@ -22,16 +22,17 @@ import java.util.logging.Logger; import net.sourceforge.pmd.lang.ast.Node; +import net.sourceforge.pmd.lang.ast.QualifiableNode; import net.sourceforge.pmd.lang.java.ast.ASTAdditiveExpression; import net.sourceforge.pmd.lang.java.ast.ASTAllocationExpression; import net.sourceforge.pmd.lang.java.ast.ASTAndExpression; -import net.sourceforge.pmd.lang.java.ast.ASTAnnotationTypeDeclaration; +import net.sourceforge.pmd.lang.java.ast.ASTAnnotation; +import net.sourceforge.pmd.lang.java.ast.ASTAnyTypeDeclaration; import net.sourceforge.pmd.lang.java.ast.ASTArgumentList; import net.sourceforge.pmd.lang.java.ast.ASTArguments; import net.sourceforge.pmd.lang.java.ast.ASTArrayDimsAndInits; import net.sourceforge.pmd.lang.java.ast.ASTBooleanLiteral; import net.sourceforge.pmd.lang.java.ast.ASTCastExpression; -import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceBody; import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceDeclaration; import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceType; import net.sourceforge.pmd.lang.java.ast.ASTCompilationUnit; @@ -39,17 +40,19 @@ import net.sourceforge.pmd.lang.java.ast.ASTConditionalExpression; import net.sourceforge.pmd.lang.java.ast.ASTConditionalOrExpression; import net.sourceforge.pmd.lang.java.ast.ASTConstructorDeclaration; -import net.sourceforge.pmd.lang.java.ast.ASTEnumBody; -import net.sourceforge.pmd.lang.java.ast.ASTEnumDeclaration; +import net.sourceforge.pmd.lang.java.ast.ASTEnumConstant; import net.sourceforge.pmd.lang.java.ast.ASTEqualityExpression; import net.sourceforge.pmd.lang.java.ast.ASTExclusiveOrExpression; import net.sourceforge.pmd.lang.java.ast.ASTExpression; import net.sourceforge.pmd.lang.java.ast.ASTExtendsList; import net.sourceforge.pmd.lang.java.ast.ASTFieldDeclaration; +import net.sourceforge.pmd.lang.java.ast.ASTForStatement; +import net.sourceforge.pmd.lang.java.ast.ASTFormalParameter; import net.sourceforge.pmd.lang.java.ast.ASTImportDeclaration; import net.sourceforge.pmd.lang.java.ast.ASTInclusiveOrExpression; import net.sourceforge.pmd.lang.java.ast.ASTInstanceOfExpression; import net.sourceforge.pmd.lang.java.ast.ASTLiteral; +import net.sourceforge.pmd.lang.java.ast.ASTLocalVariableDeclaration; import net.sourceforge.pmd.lang.java.ast.ASTMarkerAnnotation; import net.sourceforge.pmd.lang.java.ast.ASTMethodDeclaration; import net.sourceforge.pmd.lang.java.ast.ASTMultiplicativeExpression; @@ -65,6 +68,7 @@ import net.sourceforge.pmd.lang.java.ast.ASTPrimitiveType; import net.sourceforge.pmd.lang.java.ast.ASTReferenceType; import net.sourceforge.pmd.lang.java.ast.ASTRelationalExpression; +import net.sourceforge.pmd.lang.java.ast.ASTResource; import net.sourceforge.pmd.lang.java.ast.ASTShiftExpression; import net.sourceforge.pmd.lang.java.ast.ASTSingleMemberAnnotation; import net.sourceforge.pmd.lang.java.ast.ASTStatementExpression; @@ -79,6 +83,7 @@ import net.sourceforge.pmd.lang.java.ast.ASTUnaryExpressionNotPlusMinus; import net.sourceforge.pmd.lang.java.ast.ASTVariableDeclarator; import net.sourceforge.pmd.lang.java.ast.ASTVariableDeclaratorId; +import net.sourceforge.pmd.lang.java.ast.ASTVariableInitializer; import net.sourceforge.pmd.lang.java.ast.ASTWildcardBounds; import net.sourceforge.pmd.lang.java.ast.AbstractJavaTypeNode; import net.sourceforge.pmd.lang.java.ast.JavaNode; @@ -165,16 +170,7 @@ public class ClassTypeResolver extends JavaParserVisitorAdapter { private final PMDASMClassLoader pmdClassLoader; private Map importedClasses; private List importedOnDemand; - private Map anonymousClassMetadata = new HashMap<>(); - private static class AnonymousClassMetadata { - public final String name; - public int anonymousClassCounter; - - AnonymousClassMetadata(final String className) { - this.name = className; - } - } public ClassTypeResolver() { this(ClassTypeResolver.class.getClassLoader()); @@ -203,11 +199,7 @@ public Object visit(ASTCompilationUnit node, Object data) { if (className != null) { populateClassName(node, className); } - } catch (ClassNotFoundException e) { - if (LOG.isLoggable(Level.FINE)) { - LOG.log(Level.FINE, "Could not find class " + className + ", due to: " + e); - } - } catch (NoClassDefFoundError e) { + } catch (ClassNotFoundException | NoClassDefFoundError e) { if (LOG.isLoggable(Level.FINE)) { LOG.log(Level.FINE, "Could not find class " + className + ", due to: " + e); } @@ -259,15 +251,15 @@ public Object visit(ASTClassOrInterfaceType node, Object data) { String typeName = node.getImage(); if (node.isAnonymousClass()) { - final AnonymousClassMetadata parentAnonymousClassMetadata = getParentAnonymousClassMetadata(node); - if (parentAnonymousClassMetadata != null) { - typeName = parentAnonymousClassMetadata.name + "$" + ++parentAnonymousClassMetadata - .anonymousClassCounter; - anonymousClassMetadata.put(node, new AnonymousClassMetadata(typeName)); + QualifiableNode parent = node.getFirstParentOfAnyType(ASTAllocationExpression.class, ASTEnumConstant.class); + + if (parent != null) { + typeName = parent.getQualifiedName().toString(); } } - populateType(node, typeName); + // FIXME, we should discard the array depth on this node, it should only be known to ASTReferenceType (#910) + populateType(node, typeName, node.getArrayDepth()); ASTTypeArguments typeArguments = node.getFirstChildOfType(ASTTypeArguments.class); @@ -283,79 +275,13 @@ public Object visit(ASTClassOrInterfaceType node, Object data) { return data; } - private AnonymousClassMetadata getParentAnonymousClassMetadata(final ASTClassOrInterfaceType node) { - Node parent = node; - do { - parent = parent.jjtGetParent(); - } while (parent != null && !(parent instanceof ASTClassOrInterfaceBody) && !(parent instanceof ASTEnumBody)); - - // TODO : Should never happen, but add this for safety until we are sure to cover all possible scenarios in - // unit testing - if (parent == null) { - return null; - } - - parent = parent.jjtGetParent(); - - TypeNode typedParent; - // The parent may now be an ASTEnumConstant, an ASTAllocationExpression, an ASTEnumDeclaration or an - // ASTClassOrInterfaceDeclaration - if (parent instanceof ASTAllocationExpression) { - typedParent = parent.getFirstChildOfType(ASTClassOrInterfaceType.class); - } else if (parent instanceof ASTClassOrInterfaceDeclaration || parent instanceof ASTEnumDeclaration) { - typedParent = (TypeNode) parent; - } else { - typedParent = parent.getFirstParentOfType(ASTEnumDeclaration.class); - } - - final AnonymousClassMetadata metadata = anonymousClassMetadata.get(typedParent); - if (metadata != null) { - return metadata; - } - - final AnonymousClassMetadata newMetadata; - if (typedParent instanceof ASTClassOrInterfaceType) { - ASTClassOrInterfaceType parentTypeNode = (ASTClassOrInterfaceType) typedParent; - if (parentTypeNode.isAnonymousClass()) { - final AnonymousClassMetadata parentMetadata = getParentAnonymousClassMetadata(parentTypeNode); - newMetadata = new AnonymousClassMetadata(parentMetadata.name + "$" + ++parentMetadata - .anonymousClassCounter); - } else { - newMetadata = new AnonymousClassMetadata(parentTypeNode.getImage()); - } - } else { - newMetadata = new AnonymousClassMetadata(typedParent.getImage()); - } - - anonymousClassMetadata.put(typedParent, newMetadata); - - return newMetadata; - } - - @Override - public Object visit(ASTClassOrInterfaceDeclaration node, Object data) { - populateType(node, node.getImage()); - return super.visit(node, data); - } - - @Override - public Object visit(ASTEnumDeclaration node, Object data) { - populateType(node, node.getImage()); - return super.visit(node, data); - } - - @Override - public Object visit(ASTAnnotationTypeDeclaration node, Object data) { - populateType(node, node.getImage()); - return super.visit(node, data); - } - /** * Set's the node's type to the found Class in the node's name (if there is a class to be found). * * @param node + * * @return The index in the array produced by splitting the node's name by '.', which is not part of the - * class name found. Example: com.package.SomeClass.staicField.otherField, return would be 3 + * class name found. Example: com.package.SomeClass.staticField.otherField, return would be 3 */ private int searchNodeNameForClass(TypeNode node) { // this is the index from which field/method names start in the dotSplitImage array @@ -418,7 +344,7 @@ public Object visit(ASTName node, Object data) { if (node.getType() != null) { // static field or method // node.getType() has been set by the call to searchNodeNameForClass above // node.getType() will have the value equal to the Class found by that method - previousType = JavaTypeDefinition.forClass(node.getType()); + previousType = node.getTypeDefinition(); } else { // non-static field or method if (dotSplitImage.length == 1 && astArguments != null) { // method List methods = getLocalApplicableMethods(node, dotSplitImage[0], @@ -444,11 +370,10 @@ public Object visit(ASTName node, Object data) { if (node.getNameDeclaration() != null && previousType == null // if it's not null, then let other code handle things && node.getNameDeclaration().getNode() instanceof TypeNode) { - // Carry over the type from the declaration - Class nodeType = ((TypeNode) node.getNameDeclaration().getNode()).getType(); - // FIXME : generic classes and class with generic super types could have the wrong type assigned here + // Carry over the type (including generics) from the declaration + JavaTypeDefinition nodeType = ((TypeNode) node.getNameDeclaration().getNode()).getTypeDefinition(); if (nodeType != null) { - node.setType(nodeType); + node.setTypeDefinition(nodeType); return super.visit(node, data); } } @@ -545,14 +470,12 @@ private ASTArguments getSuffixMethodArgs(Node node) { if (prefix instanceof ASTPrimaryPrefix && prefix.jjtGetParent().jjtGetNumChildren() >= 2) { - ASTArguments args = prefix.jjtGetParent().jjtGetChild(1).getFirstChildOfType(ASTArguments.class); - return args; + return prefix.jjtGetParent().jjtGetChild(1).getFirstChildOfType(ASTArguments.class); } return null; } - /** * Searches a JavaTypeDefinition and it's superclasses until a field with name {@code fieldImage} that * is visible from the {@code accessingClass} class. Once it's found, it's possibly generic type is @@ -561,6 +484,7 @@ private ASTArguments getSuffixMethodArgs(Node node) { * @param typeToSearch The type def. to search the field in. * @param fieldImage The simple name of the field. * @param accessingClass The class that is trying to access the field, some Class declared in the current ACU. + * * @return JavaTypeDefinition of the resolved field or null if it could not be found. */ private JavaTypeDefinition getFieldType(JavaTypeDefinition typeToSearch, String fieldImage, Class @@ -573,7 +497,10 @@ private JavaTypeDefinition getFieldType(JavaTypeDefinition typeToSearch, String } } catch (final NoSuchFieldException ignored) { // swallow - } catch (final NoClassDefFoundError e) { + } catch (final LinkageError e) { + if (LOG.isLoggable(Level.WARNING)) { + LOG.log(Level.WARNING, "Error during type resolution due to: " + e); + } // TODO : report a missing class once we start doing that... return null; } @@ -592,6 +519,7 @@ private JavaTypeDefinition getFieldType(JavaTypeDefinition typeToSearch, String * @param scope The scope to start the search from. * @param image The name of the field, local variable or method parameter. * @param accessingClass The Class (which is defined in the current ACU) that is trying to access the field. + * * @return Type def. of the field, or null if it could not be resolved. */ private JavaTypeDefinition getTypeDefinitionOfVariableFromScope(Scope scope, String image, Class @@ -608,7 +536,7 @@ private JavaTypeDefinition getTypeDefinitionOfVariableFromScope(Scope scope, Str ASTType typeNode = entry.getKey().getDeclaratorId().getTypeNode(); if (typeNode == null) { - // TODO : Type is infered, ie, this is a lambda such as (var) -> var.equals(other) + // TODO : Type is inferred, ie, this is a lambda such as (var) -> var.equals(other) or a local var return null; } @@ -635,7 +563,7 @@ private JavaTypeDefinition getTypeDefinitionOfVariableFromScope(Scope scope, Str if (foundTypeDef != null) { // if null, then it's not an inherited field return foundTypeDef; } - } catch (ClassCastException e) { + } catch (ClassCastException ignored) { // if there is an anonymous class, getClassDeclaration().getType() will throw // TODO: maybe there is a better way to handle this, maybe this hides bugs } @@ -678,12 +606,16 @@ public Object visit(ASTVariableDeclarator node, Object data) { @Override public Object visit(ASTVariableDeclaratorId node, Object data) { - if (node == null || node.getNameDeclaration() == null) { + if (node == null || node.isTypeInferred()) { return super.visit(node, data); } - String name = node.getNameDeclaration().getTypeImage(); - if (name != null) { - populateType(node, name); + + // Type common to all declarations in the same statement + JavaTypeDefinition baseType = node.getTypeNode().getTypeDefinition(); + + if (baseType != null) { + // add the dimensions specific to the declarator id + node.setTypeDefinition(baseType.withDimensions(node.getArrayDepth())); } return super.visit(node, data); } @@ -695,10 +627,94 @@ public Object visit(ASTType node, Object data) { return data; } + private void populateVariableDeclaratorFromType(ASTLocalVariableDeclaration node, JavaTypeDefinition typeDefinition) { + // assign this type to VariableDeclarator and VariableDeclaratorId + TypeNode var = node.getFirstChildOfType(ASTVariableDeclarator.class); + if (var != null) { + var.setTypeDefinition(typeDefinition); + var = var.getFirstChildOfType(ASTVariableDeclaratorId.class); + } + if (var != null) { + var.setTypeDefinition(typeDefinition); + } + } + + @Override + public Object visit(ASTLocalVariableDeclaration node, Object data) { + super.visit(node, data); + // resolve "var" types: Upward projection of the type of the initializer expression + ASTType type = node.getTypeNode(); + if (type == null) { + // no type node -> type is inferred + ASTVariableInitializer initializer = node.getFirstDescendantOfType(ASTVariableInitializer.class); + if (initializer != null && initializer.jjtGetChild(0) instanceof ASTExpression) { + // only Expression is allowed, ArrayInitializer is not allowed in combination with "var". + ASTExpression expression = (ASTExpression) initializer.jjtGetChild(0); + populateVariableDeclaratorFromType(node, expression.getTypeDefinition()); + } + } + return data; + } + + @Override + public Object visit(ASTForStatement node, Object data) { + super.visit(node, data); + // resolve potential "var" type + if (node.jjtGetChild(0) instanceof ASTLocalVariableDeclaration) { + ASTLocalVariableDeclaration localVariableDeclaration = (ASTLocalVariableDeclaration) node.jjtGetChild(0); + ASTType type = localVariableDeclaration.getTypeNode(); + if (type == null) { + // no type node -> type is inferred + ASTExpression expression = node.getFirstChildOfType(ASTExpression.class); + if (expression != null && expression.getTypeDefinition() != null) { + // see https://docs.oracle.com/javase/specs/jls/se10/html/jls-14.html#jls-14.14.2 + // if the type is an array, then take the component type + // if the type is Iterable, then take X as type + // if the type is Iterable, take Object as type + JavaTypeDefinition typeDefinitionIterable = expression.getTypeDefinition(); + JavaTypeDefinition typeDefinition = null; + if (typeDefinitionIterable.isArrayType()) { + typeDefinition = typeDefinitionIterable.getComponentType(); + } else if (typeDefinitionIterable.isGeneric() && typeDefinitionIterable.getGenericType(0) != null) { + typeDefinition = typeDefinitionIterable.getGenericType(0); + } else { + typeDefinition = JavaTypeDefinition.forClass(Object.class); + } + populateVariableDeclaratorFromType(localVariableDeclaration, typeDefinition); + } + } + } + return data; + } + + @Override + public Object visit(ASTResource node, Object data) { + super.visit(node, data); + // resolve "var" types: the type of the initializer expression + ASTType type = node.getTypeNode(); + if (type == null) { + // no type node -> type is inferred + ASTExpression initializer = node.getFirstChildOfType(ASTExpression.class); + + if (node.getVariableDeclaratorId() != null) { + node.getVariableDeclaratorId().setTypeDefinition(initializer.getTypeDefinition()); + } + } + return data; + } + @Override public Object visit(ASTReferenceType node, Object data) { super.visit(node, data); rollupTypeUnary(node); + + JavaTypeDefinition elementTypeDef = node.getTypeDefinition(); + if (elementTypeDef != null) { + // FIXME when ClassOrInterfaceType resolves type without dimensions, remove the test here + if (!elementTypeDef.isArrayType()) { + node.setTypeDefinition(elementTypeDef.withDimensions(node.getArrayDepth())); + } + } return data; } @@ -718,11 +734,11 @@ public Object visit(ASTExpression node, Object data) { @Override public Object visit(ASTConditionalExpression node, Object data) { super.visit(node, data); - if (node.isTernary()) { - // TODO Rules for Ternary are complex - } else { - rollupTypeUnary(node); - } + + // TODO Rules for Ternary are complex + + rollupTypeUnary(node); + return data; } @@ -943,6 +959,7 @@ public Object visit(ASTPrimaryExpression primaryNode, Object data) { * Returns the the first Class declaration around the node. * * @param node The node with the enclosing Class declaration. + * * @return The JavaTypeDefinition of the enclosing Class declaration. */ private TypeNode getEnclosingTypeDeclaration(Node node) { @@ -983,6 +1000,7 @@ private Class getEnclosingTypeDeclarationClass(Node node) { * * @param node The node from which to start searching. * @param clazz The type of the enclosing class. + * * @return The TypeDefinition of the superclass. */ private JavaTypeDefinition getSuperClassTypeDefinition(Node node, Class clazz) { @@ -1036,11 +1054,11 @@ public Object visit(ASTTypeArgument node, Object data) { public Object visit(ASTWildcardBounds node, Object data) { super.visit(node, data); - JavaTypeDefinition childType = ((TypeNode) node.jjtGetChild(0)).getTypeDefinition(); + JavaTypeDefinition childType = node.getTypeBoundNode().getTypeDefinition(); - if (node.jjtGetFirstToken().toString().equals("super")) { + if (node.isLowerBound()) { node.setTypeDefinition(JavaTypeDefinition.forClass(LOWER_WILDCARD, childType)); - } else { // equals "extends" + } else { // upper bound node.setTypeDefinition(JavaTypeDefinition.forClass(UPPER_WILDCARD, childType)); } @@ -1055,8 +1073,9 @@ public Object visit(ASTTypeParameters node, Object data) { TypeNode parent = (TypeNode) node.jjtGetParent(); final JavaTypeDefinition[] boundGenerics = new JavaTypeDefinition[node.jjtGetNumChildren()]; - for (int i = 0; i < node.jjtGetNumChildren(); ++i) { - boundGenerics[i] = ((TypeNode) node.jjtGetChild(i)).getTypeDefinition(); + int i = 0; + for (ASTTypeParameter arg : node) { + boundGenerics[i++] = arg.getTypeDefinition(); } parent.setTypeDefinition(JavaTypeDefinition.forClass(parent.getType(), boundGenerics)); @@ -1067,7 +1086,7 @@ public Object visit(ASTTypeParameters node, Object data) { @Override public Object visit(ASTTypeParameter node, Object data) { - if (node.jjtGetNumChildren() == 0) { // type parameter doesn't have declared upper bounds + if (!node.hasTypeBound()) { // type parameter doesn't have declared upper bounds node.setTypeDefinition(JavaTypeDefinition.forClass(UPPER_BOUND, Object.class)); } else { super.visit(node, data); @@ -1081,11 +1100,13 @@ public Object visit(ASTTypeParameter node, Object data) { public Object visit(ASTTypeBound node, Object data) { super.visit(node, data); - // TypeBound will have at least one child - JavaTypeDefinition[] bounds = new JavaTypeDefinition[node.jjtGetNumChildren()]; + List typeNodes = node.getBoundTypeNodes(); - for (int index = 0; index < node.jjtGetNumChildren(); ++index) { - bounds[index] = ((TypeNode) node.jjtGetChild(index)).getTypeDefinition(); + // TypeBound will have at least one child, but maybe more + JavaTypeDefinition[] bounds = new JavaTypeDefinition[typeNodes.size()]; + int i = 0; + for (ASTClassOrInterfaceType bound : typeNodes) { + bounds[i++] = bound.getTypeDefinition(); } node.setTypeDefinition(JavaTypeDefinition.forClass(UPPER_BOUND, bounds)); @@ -1134,38 +1155,12 @@ public Object visit(ASTLiteral node, Object data) { public Object visit(ASTAllocationExpression node, Object data) { super.visit(node, data); - if (node.jjtGetNumChildren() >= 2 && node.jjtGetChild(1) instanceof ASTArrayDimsAndInits - || node.jjtGetNumChildren() >= 3 && node.jjtGetChild(2) instanceof ASTArrayDimsAndInits) { - // - // Classes for Array types cannot be found directly using - // reflection. - // As far as I can tell you have to create an array instance of the - // necessary - // dimensionality, and then ask for the type from the instance. OMFG - // that's ugly. - // - - // TODO Need to create utility method to allow array type creation - // which will use - // caching to avoid repeated object creation. - // TODO Modify Parser to tell us array dimensions count. - // TODO Parser seems to do some work to handle arrays in certain - // case already. - // Examine those to figure out what's going on, make sure _all_ - // array scenarios - // are ultimately covered. Appears to use a Dimensionable interface - // to handle - // only a part of the APIs (not bump), but is implemented several - // times, so - // look at refactoring to eliminate duplication. Dimensionable is - // also used - // on AccessNodes for some scenarios, need to account for that. - // Might be - // missing some TypeNode candidates we can add to the AST and have - // to deal - // with here (e.g. FormalParameter)? Plus some existing usages may - // be - // incorrect. + final ASTArrayDimsAndInits dims = node.getFirstChildOfType(ASTArrayDimsAndInits.class); + if (dims != null) { + final JavaTypeDefinition elementType = ((TypeNode) node.jjtGetChild(0)).getTypeDefinition(); + if (elementType != null) { + node.setTypeDefinition(elementType.withDimensions(dims.getArrayDepth())); + } } else { rollupTypeUnary(node); } @@ -1179,6 +1174,30 @@ public Object visit(ASTStatementExpression node, Object data) { return data; } + + @Override + public Object visit(ASTFormalParameter node, Object data) { + super.visit(node, data); + JavaTypeDefinition varType = node.getVariableDeclaratorId().getTypeDefinition(); + + if (varType != null) { + if (node.isVarargs()) { + // The type of the formal parameter is defined in terms of the type + // of the declarator ID + node.getVariableDeclaratorId().setTypeDefinition(varType.withDimensions(1)); + } + } + return data; + } + + + @Override + public Object visit(ASTAnnotation node, Object data) { + super.visit(node, data); + rollupTypeUnary(node); + return data; + } + @Override public Object visit(ASTNormalAnnotation node, Object data) { super.visit(node, data); @@ -1202,9 +1221,8 @@ public Object visit(ASTSingleMemberAnnotation node, Object data) { // Roll up the type based on type of the first child node. private void rollupTypeUnary(TypeNode typeNode) { - Node node = typeNode; - if (node.jjtGetNumChildren() >= 1) { - Node child = node.jjtGetChild(0); + if (typeNode.jjtGetNumChildren() >= 1) { + Node child = typeNode.jjtGetChild(0); if (child instanceof TypeNode) { typeNode.setTypeDefinition(((TypeNode) child).getTypeDefinition()); } @@ -1272,6 +1290,10 @@ private void rollupTypeBinaryNumericPromotion(TypeNode typeNode) { } private void populateType(TypeNode node, String className) { + populateType(node, className, 0); + } + + private void populateType(TypeNode node, String className, int arrayDimens) { String qualifiedName = className; Class myType = PRIMITIVE_TYPES.get(className); @@ -1291,10 +1313,13 @@ private void populateType(TypeNode node, String className) { myType = pmdClassLoader.loadClass(qualifiedName); } catch (ClassNotFoundException e) { myType = processOnDemand(qualifiedName); - } catch (NoClassDefFoundError e) { - myType = processOnDemand(qualifiedName); } catch (LinkageError e) { - myType = processOnDemand(qualifiedName); + // we found the class, but there is a problem with it (see https://github.com/pmd/pmd/issues/1131) + if (LOG.isLoggable(Level.FINE)) { + LOG.log(Level.FINE, "Tried to load class " + qualifiedName + " from on demand import, " + + "with an incomplete classpath.", e); + } + return; } } } @@ -1304,16 +1329,23 @@ private void populateType(TypeNode node, String className) { + qualifiedName.substring(qualifiedName.lastIndexOf('.') + 1); try { myType = pmdClassLoader.loadClass(qualifiedNameInner); - } catch (Exception e) { - // ignored + } catch (ClassNotFoundException ignored) { + // ignored, we'll try again with a different package name/fqcn + } catch (LinkageError e) { + // we found the class, but there is a problem with it (see https://github.com/pmd/pmd/issues/1131) + if (LOG.isLoggable(Level.FINE)) { + LOG.log(Level.FINE, "Tried to load class " + qualifiedNameInner + " from on demand import, " + + "with an incomplete classpath.", e); + } + return; } } if (myType == null && qualifiedName != null && !qualifiedName.contains(".")) { // try again with java.lang.... try { myType = pmdClassLoader.loadClass("java.lang." + qualifiedName); - } catch (Exception e) { - // ignored + } catch (Exception ignored) { + // ignored, we'll try again with generics } } @@ -1325,7 +1357,8 @@ private void populateType(TypeNode node, String className) { node.setTypeDefinition(parameter.getTypeDefinition()); } } else { - node.setType(myType); + JavaTypeDefinition def = JavaTypeDefinition.forClass(myType); + node.setTypeDefinition(def.withDimensions(arrayDimens)); } } @@ -1363,8 +1396,9 @@ public boolean classNameExists(String fullyQualifiedClassName) { return true; // Class found } catch (ClassNotFoundException e) { return false; - } catch (NoClassDefFoundError e) { - return false; + } catch (LinkageError e2) { + // Class exists, but may be invalid (see https://github.com/pmd/pmd/issues/1131) + return true; } } @@ -1373,31 +1407,45 @@ public Class loadClass(String fullyQualifiedClassName) { return pmdClassLoader.loadClass(fullyQualifiedClassName); } catch (ClassNotFoundException e) { return null; + } catch (LinkageError e2) { + if (LOG.isLoggable(Level.FINE)) { + LOG.log(Level.FINE, "Tried to load class " + fullyQualifiedClassName + " from on demand import, " + + "with an incomplete classpath.", e2); + } + return null; } } private Class processOnDemand(String qualifiedName) { for (String entry : importedOnDemand) { + String fullClassName = entry + "." + qualifiedName; try { - return pmdClassLoader.loadClass(entry + "." + qualifiedName); - } catch (Throwable e) { + return pmdClassLoader.loadClass(fullClassName); + } catch (ClassNotFoundException ignored) { + // ignored + } catch (LinkageError e) { + if (LOG.isLoggable(Level.FINE)) { + LOG.log(Level.FINE, "Tried to load class " + fullClassName + " from on demand import, " + + "with an incomplete classpath.", e); + } } } return null; } private String getClassName(ASTCompilationUnit node) { - ASTClassOrInterfaceDeclaration classDecl = node.getFirstDescendantOfType(ASTClassOrInterfaceDeclaration.class); + ASTAnyTypeDeclaration classDecl = node.getFirstDescendantOfType(ASTAnyTypeDeclaration.class); if (classDecl == null) { - // Happens if this compilation unit only contains an enum + // package-info.java? return null; } + + if (node.declarationsAreInDefaultPackage()) { return classDecl.getImage(); } - ASTPackageDeclaration pkgDecl = node.getPackageDeclaration(); - importedOnDemand.add(pkgDecl.getPackageNameImage()); - return pkgDecl.getPackageNameImage() + "." + classDecl.getImage(); + importedOnDemand.add(node.getPackageDeclaration().getPackageNameImage()); + return classDecl.getQualifiedName().toString(); } /** @@ -1420,7 +1468,7 @@ private void populateImports(ASTCompilationUnit node) { String strName = anImportDeclaration.getImportedName(); String fieldName = strName.substring(strName.lastIndexOf('.') + 1); - Class staticClassWithField = loadClass(strPackage); + Class staticClassWithField = loadClass(strPackage); if (staticClassWithField != null) { JavaTypeDefinition typeDef = getFieldType(JavaTypeDefinition.forClass(staticClassWithField), fieldName, currentAcu.getType()); @@ -1449,9 +1497,9 @@ private void populateImports(ASTCompilationUnit node) { } } + private void populateClassName(ASTCompilationUnit node, String className) throws ClassNotFoundException { node.setType(pmdClassLoader.loadClass(className)); importedClasses.putAll(pmdClassLoader.getImportedClasses(className)); } - } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/typeresolution/MethodType.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/typeresolution/MethodType.java index 2afdf1be1db..1e77b1744b9 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/typeresolution/MethodType.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/typeresolution/MethodType.java @@ -14,7 +14,7 @@ /** * This is really just a POJO. */ -public class MethodType { +public final class MethodType { private final JavaTypeDefinition returnType; private final List argTypes; private final Method method; @@ -84,4 +84,58 @@ public String toString() { public boolean isParameterized() { return returnType != null && argTypes != null; } + + /* (non-Javadoc) + * @see java.lang.Object#hashCode() + */ + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((argTypes == null) ? 0 : argTypes.hashCode()); + // note: only taking the method's name + result = prime * result + ((method == null) ? 0 : method.getName().hashCode()); + result = prime * result + ((returnType == null) ? 0 : returnType.hashCode()); + return result; + } + + /* (non-Javadoc) + * @see java.lang.Object#equals(java.lang.Object) + */ + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + MethodType other = (MethodType) obj; + if (argTypes == null) { + if (other.argTypes != null) { + return false; + } + } else if (!argTypes.equals(other.argTypes)) { + return false; + } + if (method == null) { + if (other.method != null) { + return false; + } + } else if (!method.getName().equals(other.method.getName())) { + // note: only comparing the method's name + return false; + } + if (returnType == null) { + if (other.returnType != null) { + return false; + } + } else if (!returnType.equals(other.returnType)) { + return false; + } + return true; + } } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/typeresolution/MethodTypeResolution.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/typeresolution/MethodTypeResolution.java index aac0250fe59..ff8b6eac0a3 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/typeresolution/MethodTypeResolution.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/typeresolution/MethodTypeResolution.java @@ -13,7 +13,11 @@ import java.lang.reflect.TypeVariable; import java.util.ArrayList; import java.util.Collections; +import java.util.HashMap; import java.util.List; +import java.util.Map; +import java.util.logging.Level; +import java.util.logging.Logger; import net.sourceforge.pmd.lang.ast.Node; import net.sourceforge.pmd.lang.java.ast.ASTArgumentList; @@ -32,23 +36,30 @@ public final class MethodTypeResolution { private MethodTypeResolution() {} + private static final Logger LOG = Logger.getLogger(MethodTypeResolution.class.getName()); + private static final List> PRIMITIVE_SUBTYPE_ORDER; private static final List> BOXED_PRIMITIVE_SUBTYPE_ORDER; + private static final Map, Class> PRIMITIVE_BOXING_RULES; static { - List> primitiveList = new ArrayList<>(); + final List> primitiveList = new ArrayList<>(); + + @SuppressWarnings("PMD.AvoidUsingShortType") // defining a local variable to suppress warnings only for + // the following statement + Class shortType = short.class; primitiveList.add(double.class); primitiveList.add(float.class); primitiveList.add(long.class); primitiveList.add(int.class); - primitiveList.add(short.class); + primitiveList.add(shortType); primitiveList.add(byte.class); primitiveList.add(char.class); // this is here for convenience, not really in order PRIMITIVE_SUBTYPE_ORDER = Collections.unmodifiableList(primitiveList); - List> boxedList = new ArrayList<>(); + final List> boxedList = new ArrayList<>(); boxedList.add(Double.class); boxedList.add(Float.class); @@ -59,40 +70,41 @@ private MethodTypeResolution() {} boxedList.add(Character.class); BOXED_PRIMITIVE_SUBTYPE_ORDER = Collections.unmodifiableList(boxedList); + + final Map, Class> boxingRules = new HashMap<>(); + + boxingRules.put(double.class, Double.class); + boxingRules.put(float.class, Float.class); + boxingRules.put(long.class, Long.class); + boxingRules.put(int.class, Integer.class); + boxingRules.put(shortType, Short.class); + boxingRules.put(byte.class, Byte.class); + boxingRules.put(char.class, Character.class); + boxingRules.put(boolean.class, Boolean.class); + boxingRules.put(void.class, Void.class); + + PRIMITIVE_BOXING_RULES = Collections.unmodifiableMap(boxingRules); } public static boolean checkSubtypeability(MethodType method, MethodType subtypeableMethod) { List subtypeableParams = subtypeableMethod.getParameterTypes(); List methodParams = method.getParameterTypes(); - - if (!method.getMethod().isVarArgs() && !subtypeableMethod.getMethod().isVarArgs()) { + // If we come from third-phase, both are varargs, otherwhise, treat all as fixed-arity + if (!method.getMethod().isVarArgs() || !subtypeableMethod.getMethod().isVarArgs()) { for (int index = 0; index < subtypeableParams.size(); ++index) { if (!isSubtypeable(methodParams.get(index), subtypeableParams.get(index))) { return false; } } - } else if (method.getMethod().isVarArgs() && subtypeableMethod.getMethod().isVarArgs()) { - - if (methodParams.size() < subtypeableParams.size()) { - for (int index = 0; index < subtypeableParams.size(); ++index) { - if (!isSubtypeable(method.getArgTypeIncludingVararg(index), - subtypeableMethod.getArgTypeIncludingVararg(index))) { - return false; - } - } - } else { - for (int index = 0; index < methodParams.size(); ++index) { - if (!isSubtypeable(method.getArgTypeIncludingVararg(index), - subtypeableMethod.getArgTypeIncludingVararg(index))) { - return false; - } + } else { + final int maxSize = Math.max(subtypeableParams.size(), methodParams.size()); + for (int index = 0; index < maxSize; ++index) { + if (!isSubtypeable(method.getArgTypeIncludingVararg(index), + subtypeableMethod.getArgTypeIncludingVararg(index))) { + return false; } } - - } else { - throw new IllegalStateException("These methods can only be vararg at the same time:\n" - + method.toString() + "\n" + subtypeableMethod.toString()); } return true; @@ -106,16 +118,14 @@ public static List selectMethodsFirstPhase(JavaTypeDefinition contex List methodsToSearch, ASTArgumentList argList) { // TODO: check if explicit type arguments are applicable to the type parameter bounds List selectedMethods = new ArrayList<>(); + final int argCount = argList == null ? 0 : argList.jjtGetNumChildren(); outter: for (int methodIndex = 0; methodIndex < methodsToSearch.size(); ++methodIndex) { MethodType methodType = methodsToSearch.get(methodIndex); - if (argList == null) { - selectedMethods.add(methodType); - - // vararg methods are considered fixed arity here - } else if (getArity(methodType.getMethod()) == argList.jjtGetNumChildren()) { + // vararg methods are considered fixed arity here, see 3rd phase + if (getArity(methodType.getMethod()) == argCount) { if (!methodType.isParameterized()) { // https://docs.oracle.com/javase/specs/jls/se8/html/jls-18.html#jls-18.5.1 // ... @@ -125,7 +135,7 @@ public static List selectMethodsFirstPhase(JavaTypeDefinition contex // reference type, or ii) Fi is a primitive type but ei is not a standalone expression of a // primitive type; then the method is not applicable and there is no need to proceed with inference. Class[] methodParameterTypes = methodType.getMethod().getParameterTypes(); - for (int argIndex = 0; argIndex < argList.jjtGetNumChildren(); ++argIndex) { + for (int argIndex = 0; argIndex < argCount; ++argIndex) { if (((ASTExpression) argList.jjtGetChild(argIndex)).isStandAlonePrimitive()) { if (!methodParameterTypes[argIndex].isPrimitive()) { continue outter; // this method is not applicable @@ -136,13 +146,19 @@ public static List selectMethodsFirstPhase(JavaTypeDefinition contex } methodType = parameterizeInvocation(context, methodType.getMethod(), argList); + + // May be null if the method call is not applicable + if (methodType == null) { + continue; + } } + // TODO : Is this needed? parameterizeInvocation already performs inference to check applicability... // check subtypeability of each argument to the corresponding parameter boolean methodIsApplicable = true; // try each arguments if it's subtypeable - for (int argIndex = 0; argIndex < argList.jjtGetNumChildren(); ++argIndex) { + for (int argIndex = 0; argIndex < argCount; ++argIndex) { if (!isSubtypeable(methodType.getParameterTypes().get(argIndex), (ASTExpression) argList.jjtGetChild(argIndex))) { methodIsApplicable = false; @@ -162,7 +178,6 @@ public static List selectMethodsFirstPhase(JavaTypeDefinition contex return selectedMethods; } - public static MethodType parameterizeInvocation(JavaTypeDefinition context, Method method, ASTArgumentList argList) { @@ -174,6 +189,11 @@ public static MethodType parameterizeInvocation(JavaTypeDefinition context, Meth List resolvedTypeParameters = TypeInferenceResolver .inferTypes(produceInitialConstraints(method, argList, variables), initialBounds, variables); + // Is the method applicable? + if (resolvedTypeParameters == null) { + return null; + } + return getTypeDefOfMethod(context, method, resolvedTypeParameters); } @@ -189,7 +209,7 @@ public static List produceInitialConstraints(Method method, ASTArgum int typeParamIndex = -1; if (methodParameters[i] instanceof TypeVariable) { typeParamIndex = JavaTypeDefinition - .getGenericTypeIndex(methodTypeParameters, ((TypeVariable) methodParameters[i]).getName()); + .getGenericTypeIndex(methodTypeParameters, ((TypeVariable) methodParameters[i]).getName()); } if (typeParamIndex != -1) { @@ -229,7 +249,7 @@ public static void produceInitialBounds(Method method, JavaTypeDefinition contex int boundVarIndex = -1; if (bound instanceof TypeVariable) { boundVarIndex = - JavaTypeDefinition.getGenericTypeIndex(typeVariables, ((TypeVariable) bound).getName()); + JavaTypeDefinition.getGenericTypeIndex(typeVariables, ((TypeVariable) bound).getName()); } if (boundVarIndex != -1) { @@ -257,6 +277,7 @@ public static void produceInitialBounds(Method method, JavaTypeDefinition contex public static List selectMethodsSecondPhase(List methodsToSearch, ASTArgumentList argList) { // TODO: check if explicit type arguments are applicable to the type parameter bounds List selectedMethods = new ArrayList<>(); + final int argCount = argList == null ? 0 : argList.jjtGetNumChildren(); for (int methodIndex = 0; methodIndex < methodsToSearch.size(); ++methodIndex) { MethodType methodType = methodsToSearch.get(methodIndex); @@ -264,16 +285,13 @@ public static List selectMethodsSecondPhase(List methods throw new ResolutionFailedException(); } - if (argList == null) { - selectedMethods.add(methodType); - - // vararg methods are considered fixed arity here - } else if (getArity(methodType.getMethod()) == argList.jjtGetNumChildren()) { + // vararg methods are considered fixed arity here, see 3rd phase + if (getArity(methodType.getMethod()) == argCount) { // check method convertability of each argument to the corresponding parameter boolean methodIsApplicable = true; // try each arguments if it's method convertible - for (int argIndex = 0; argIndex < argList.jjtGetNumChildren(); ++argIndex) { + for (int argIndex = 0; argIndex < argCount; ++argIndex) { if (!isMethodConvertible(methodType.getParameterTypes().get(argIndex), (ASTExpression) argList.jjtGetChild(argIndex))) { methodIsApplicable = false; @@ -307,35 +325,41 @@ public static List selectMethodsThirdPhase(List methodsT throw new ResolutionFailedException(); } - if (argList == null) { - selectedMethods.add(methodType); - - // now we consider varargs as not fixed arity - } else { // check subtypeability of each argument to the corresponding parameter + // now we consider varargs as not fixed arity + // if we reach here and the method is not a vararg, then we didn't find a resolution in earlier phases + if (methodType.isVararg()) { // check subtypeability of each argument to the corresponding parameter boolean methodIsApplicable = true; List methodParameters = methodType.getParameterTypes(); JavaTypeDefinition varargComponentType = methodType.getVarargComponentType(); - // try each arguments if it's method convertible - for (int argIndex = 0; argIndex < argList.jjtGetNumChildren(); ++argIndex) { - JavaTypeDefinition parameterType = argIndex < methodParameters.size() - 1 - ? methodParameters.get(argIndex) : varargComponentType; - - if (!isMethodConvertible(parameterType, (ASTExpression) argList.jjtGetChild(argIndex))) { - methodIsApplicable = false; - break; + if (argList == null) { + // There are no arguments, make sure the method has only a vararg + methodIsApplicable = getArity(methodType.getMethod()) == 1; + } else { + // try each arguments if it's method convertible + for (int argIndex = 0; argIndex < argList.jjtGetNumChildren(); ++argIndex) { + JavaTypeDefinition parameterType = argIndex < methodParameters.size() - 1 + ? methodParameters.get(argIndex) : varargComponentType; + + if (!isMethodConvertible(parameterType, (ASTExpression) argList.jjtGetChild(argIndex))) { + methodIsApplicable = false; + break; + } + + // TODO: If k != n, or if k = n and An cannot be converted by method invocation conversion to + // Sn[], then the type which is the erasure (§4.6) of Sn is accessible at the point of invocation. + + // TODO: add unchecked conversion in an else if branch } - - // TODO: If k != n, or if k = n and An cannot be converted by method invocation conversion to - // Sn[], then the type which is the erasure (§4.6) of Sn is accessible at the point of invocation. - - // TODO: add unchecked conversion in an else if branch } if (methodIsApplicable) { selectedMethods.add(methodType); } + } else { + // TODO: Remove check for vararg here, once we can detect and use return types of method calls + LOG.log(Level.FINE, "Method {0} couldn't be resolved", String.valueOf(methodType)); } } @@ -407,14 +431,15 @@ public static MethodType selectAmongMaximallySpecific(MethodType first, MethodTy // the bottom of the section is relevant here, we can't resolve this type // TODO: resolve this - return null; + // we obviously don't know the runtime type. Let's return the first as the most specific + return first; } else { // second one isn't abstract return second; } } else if (second.isAbstract()) { return first; // first isn't abstract, second one is } else { - return null; // TODO: once shadowing and overriding methods is done, add exception back + return first; // TODO: once shadowing and overriding methods is done, add exception back // throw new IllegalStateException("None of the maximally specific methods are abstract.\n" // + first.toString() + "\n" + second.toString()); } @@ -441,16 +466,28 @@ public static List getApplicableMethods(JavaTypeDefinition context, Class contextClass = context.getType(); // search the class - for (Method method : contextClass.getDeclaredMethods()) { - if (isMethodApplicable(method, methodName, argArity, accessingClass, typeArguments)) { - result.add(getTypeDefOfMethod(context, method, typeArguments)); + try { + for (Method method : contextClass.getDeclaredMethods()) { + if (isMethodApplicable(method, methodName, argArity, accessingClass, typeArguments)) { + result.add(getTypeDefOfMethod(context, method, typeArguments)); + } } + } catch (final LinkageError ignored) { + // TODO : This is an incomplete classpath, report the missing class } // search it's supertype if (!contextClass.equals(Object.class)) { - result.addAll(getApplicableMethods(context.resolveTypeDefinition(contextClass.getGenericSuperclass()), - methodName, typeArguments, argArity, accessingClass)); + List inheritedMethods = getApplicableMethods(context.resolveTypeDefinition(contextClass.getGenericSuperclass()), + methodName, typeArguments, argArity, accessingClass); + + // but only add the found methods of the supertype, if they have not been overridden + // TODO: verify whether this simplified overriding detection is good enough and at the correct place + for (MethodType inherited : inheritedMethods) { + if (!result.contains(inherited)) { + result.add(inherited); + } + } } // search it's interfaces @@ -488,21 +525,16 @@ public static MethodType getTypeDefOfMethod(JavaTypeDefinition context, Method m public static boolean isMethodApplicable(Method method, String methodName, int argArity, Class accessingClass, List typeArguments) { - if (method.getName().equals(methodName) // name matches + return method.getName().equals(methodName) // name matches // is visible && isMemberVisibleFromClass(method.getDeclaringClass(), method.getModifiers(), accessingClass) // if method is vararg with arity n, then the invocation's arity >= n - 1 - && (!method.isVarArgs() || (argArity >= getArity(method) - 1)) + && (!method.isVarArgs() || argArity >= getArity(method) - 1) // if the method isn't vararg, then arity matches - && (method.isVarArgs() || (argArity == getArity(method))) + && (method.isVarArgs() || argArity == getArity(method)) // isn't generic or arity of type arguments matches that of parameters && (!isGeneric(method) || typeArguments.isEmpty() - || method.getTypeParameters().length == typeArguments.size())) { - - return true; - } - - return false; + || method.getTypeParameters().length == typeArguments.size()); } @@ -564,6 +596,10 @@ public static int getArity(Method method) { } public static boolean isMethodConvertible(JavaTypeDefinition parameter, ASTExpression argument) { + if (argument.getTypeDefinition() == null) { + LOG.log(Level.FINE, "No type information for node {0}", argument.toString()); + return true; + } return isMethodConvertible(parameter, argument.getTypeDefinition()); } @@ -590,19 +626,17 @@ && isSubtypeable(parameter, // covers unboxing int indexInBoxed = BOXED_PRIMITIVE_SUBTYPE_ORDER.indexOf(argument.getType()); - if (indexInBoxed != -1 // arg is boxed primitive + return indexInBoxed != -1 // arg is boxed primitive && isSubtypeable(parameter, - JavaTypeDefinition.forClass(PRIMITIVE_SUBTYPE_ORDER.get(indexInBoxed)))) { - return true; - } - + JavaTypeDefinition.forClass(PRIMITIVE_SUBTYPE_ORDER.get(indexInBoxed))); // TODO: add raw unchecked conversion part - - return false; } - public static boolean isSubtypeable(JavaTypeDefinition parameter, ASTExpression argument) { + if (argument.getTypeDefinition() == null) { + LOG.log(Level.FINE, "No type information for node {0}", argument.toString()); + return true; + } return isSubtypeable(parameter, argument.getTypeDefinition()); } @@ -636,7 +670,10 @@ public static boolean isSubtypeable(JavaTypeDefinition parameter, JavaTypeDefini // right now we only check if generic arguments are the same // TODO: add support for wildcard types // (future note: can't call subtype as it is recursively, infinite types) - return parameter.equals(argSuper); + //return parameter.equals(argSuper); + + // TODO: this ignores the check for generic types!! + return parameter.getType().equals(argSuper.getType()); } int indexOfParameter = PRIMITIVE_SUBTYPE_ORDER.indexOf(parameter.getType()); @@ -658,11 +695,7 @@ public static boolean isSubtypeable(JavaTypeDefinition parameter, JavaTypeDefini } public static JavaTypeDefinition boxPrimitive(JavaTypeDefinition def) { - if (!def.isPrimitive()) { - return null; - } - - return JavaTypeDefinition.forClass(BOXED_PRIMITIVE_SUBTYPE_ORDER.get(PRIMITIVE_SUBTYPE_ORDER.indexOf(def.getType()))); + return JavaTypeDefinition.forClass(PRIMITIVE_BOXING_RULES.get(def.getType())); } public static List getMethodExplicitTypeArugments(Node node) { diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/typeresolution/PMDASMClassLoader.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/typeresolution/PMDASMClassLoader.java index ad130b8be52..9ecb0d9d3e5 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/typeresolution/PMDASMClassLoader.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/typeresolution/PMDASMClassLoader.java @@ -100,7 +100,7 @@ public boolean couldResolve(String name) { } public synchronized Map getImportedClasses(String name) throws ClassNotFoundException { - if (dontBother.containsValue(name)) { + if (dontBother.containsKey(name)) { throw new ClassNotFoundException(name); } try (InputStream classResource = getResourceAsStream(name.replace('.', '/') + ".class")) { diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/typeresolution/TypeHelper.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/typeresolution/TypeHelper.java index 5be80324ac5..c77263615ae 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/typeresolution/TypeHelper.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/typeresolution/TypeHelper.java @@ -4,10 +4,8 @@ package net.sourceforge.pmd.lang.java.typeresolution; -import java.util.Arrays; -import java.util.List; +import org.apache.commons.lang3.ClassUtils; -import net.sourceforge.pmd.lang.ast.Node; import net.sourceforge.pmd.lang.java.ast.TypeNode; import net.sourceforge.pmd.lang.java.symboltable.TypedNameDeclaration; @@ -17,6 +15,68 @@ private TypeHelper() { // utility class } + /** + * Checks whether the resolved type of the given {@link TypeNode} n is of the type + * given by the clazzName. If the clazzName is on the auxclasspath, then also subclasses + * are considered. + * + * @param n the type node to check + * @param clazzName the class name to compare to + * @return true if type node n is of type clazzName or a subtype of clazzName + */ + public static boolean isA(final TypeNode n, final String clazzName) { + final Class clazz = loadClassWithNodeClassloader(n, clazzName); + + if (clazz != null) { + return isA(n, clazz); + } + + return clazzName.equals(n.getImage()) || clazzName.endsWith("." + n.getImage()); + } + + /** + * Checks whether the resolved type of the given {@link TypeNode} n is exactly of the type + * given by the clazzName. + * + * @param n the type node to check + * @param clazzName the class name to compare to + * @return true if type node n is exactly of type clazzName. + */ + public static boolean isExactlyA(final TypeNode n, final String clazzName) { + final Class clazz = loadClassWithNodeClassloader(n, clazzName); + + if (clazz != null) { + return n.getType() == clazz; + } + + return clazzName.equals(n.getImage()) || clazzName.endsWith("." + n.getImage()); + } + + private static Class loadClassWithNodeClassloader(final TypeNode n, final String clazzName) { + if (n.getType() != null) { + try { + ClassLoader classLoader = n.getType().getClassLoader(); + if (classLoader == null) { + // Using the system classloader then + classLoader = ClassLoader.getSystemClassLoader(); + } + + // If the requested type is in the classpath, using the same classloader should work + return ClassUtils.getClass(classLoader, clazzName); + } catch (final ClassNotFoundException ignored) { + // The requested type is not on the auxclasspath. This might happen, if the type node + // is probed for a specific type (e.g. is is a JUnit5 Test Annotation class). + // Failing to resolve clazzName does not necessarily indicate an incomplete auxclasspath. + } catch (final LinkageError expected) { + // We found the class but it's invalid / incomplete. This may be an incomplete auxclasspath + // if it was a NoClassDefFoundError. TODO : Report it? + } + } + + return null; + } + + /** @see #isA(TypeNode, String) */ public static boolean isA(TypeNode n, Class clazz) { return subclasses(n, clazz); } @@ -25,16 +85,42 @@ public static boolean isEither(TypeNode n, Class class1, Class class2) { return subclasses(n, class1) || subclasses(n, class2); } - public static boolean isA(TypedNameDeclaration vnd, Class clazz) { + public static boolean isExactlyAny(TypedNameDeclaration vnd, Class... clazzes) { Class type = vnd.getType(); - return type != null && type.equals(clazz) || type == null - && (clazz.getSimpleName().equals(vnd.getTypeImage()) || clazz.getName().equals(vnd.getTypeImage())); + for (final Class clazz : clazzes) { + if (type != null && type.equals(clazz) || type == null + && (clazz.getSimpleName().equals(vnd.getTypeImage()) || clazz.getName().equals(vnd.getTypeImage()))) { + return true; + } + } + + return false; + } + + public static boolean isExactlyNone(TypedNameDeclaration vnd, Class... clazzes) { + return !isExactlyAny(vnd, clazzes); + } + + /** + * @deprecated use {@link #isExactlyAny(TypedNameDeclaration, Class...)} + */ + @Deprecated + public static boolean isA(TypedNameDeclaration vnd, Class clazz) { + return isExactlyAny(vnd, clazz); } + /** + * @deprecated use {@link #isExactlyAny(TypedNameDeclaration, Class...)} + */ + @Deprecated public static boolean isEither(TypedNameDeclaration vnd, Class class1, Class class2) { - return isA(vnd, class1) || isA(vnd, class2); + return isExactlyAny(vnd, class1, class2); } + /** + * @deprecated use {@link #isExactlyNone(TypedNameDeclaration, Class...)} + */ + @Deprecated public static boolean isNeither(TypedNameDeclaration vnd, Class class1, Class class2) { return !isA(vnd, class1) && !isA(vnd, class2); } @@ -42,24 +128,9 @@ public static boolean isNeither(TypedNameDeclaration vnd, Class class1, Class public static boolean subclasses(TypeNode n, Class clazz) { Class type = n.getType(); if (type == null) { - return clazz.getSimpleName().equals(((Node) n).getImage()) || clazz.getName().equals(((Node) n).getImage()); + return n.hasImageEqualTo(clazz.getSimpleName()) || n.hasImageEqualTo(clazz.getName()); } - if (type.equals(clazz)) { - return true; - } - - List> implementors = Arrays.asList(type.getInterfaces()); - if (implementors.contains(clazz)) { - return true; - } - Class superC = type.getSuperclass(); - while (superC != null && !superC.equals(Object.class)) { - if (superC.equals(clazz)) { - return true; - } - superC = superC.getSuperclass(); - } - return false; + return clazz.isAssignableFrom(type); } } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/typeresolution/rules/CloneMethodMustImplementCloneable.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/typeresolution/rules/CloneMethodMustImplementCloneable.java deleted file mode 100644 index 9bf9e3603b1..00000000000 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/typeresolution/rules/CloneMethodMustImplementCloneable.java +++ /dev/null @@ -1,172 +0,0 @@ -/** - * BSD-style license; for more info see http://pmd.sourceforge.net/license.html - */ - -package net.sourceforge.pmd.lang.java.typeresolution.rules; - -import java.util.HashSet; -import java.util.List; -import java.util.Set; - -import net.sourceforge.pmd.lang.ast.Node; -import net.sourceforge.pmd.lang.java.ast.ASTBlock; -import net.sourceforge.pmd.lang.java.ast.ASTBlockStatement; -import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceDeclaration; -import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceType; -import net.sourceforge.pmd.lang.java.ast.ASTCompilationUnit; -import net.sourceforge.pmd.lang.java.ast.ASTExtendsList; -import net.sourceforge.pmd.lang.java.ast.ASTFormalParameters; -import net.sourceforge.pmd.lang.java.ast.ASTImplementsList; -import net.sourceforge.pmd.lang.java.ast.ASTMethodDeclaration; -import net.sourceforge.pmd.lang.java.ast.ASTMethodDeclarator; -import net.sourceforge.pmd.lang.java.rule.AbstractJavaRule; - -/** - * The method clone() should only be implemented if the class implements the - * Cloneable interface with the exception of a final method that only throws - * CloneNotSupportedException. This version uses PMD's type resolution - * facilities, and can detect if the class implements or extends a Cloneable - * class - * - * @author acaplan - */ -public class CloneMethodMustImplementCloneable extends AbstractJavaRule { - - @Override - public Object visit(final ASTClassOrInterfaceDeclaration node, final Object data) { - if (extendsOrImplementsCloneable(node)) { - return data; - } - return super.visit(node, data); - } - - private boolean extendsOrImplementsCloneable(final ASTClassOrInterfaceDeclaration node) { - if (node.getType() != null) { - return Cloneable.class.isAssignableFrom(node.getType()); - } - - // From this point on, this is a best effort, the auxclasspath is incomplete. - - // TODO : Should we really care about this? - // Shouldn't the type resolver / symbol table report missing classes and the user - // know results are dependent on running under proper arguments? - final ASTImplementsList impl = node.getFirstChildOfType(ASTImplementsList.class); - if (impl != null) { - for (int ix = 0; ix < impl.jjtGetNumChildren(); ix++) { - final Node child = impl.jjtGetChild(ix); - - if (child.getClass() != ASTClassOrInterfaceType.class) { - continue; - } - - final ASTClassOrInterfaceType type = (ASTClassOrInterfaceType) child; - if (type.getType() == null) { - if ("Cloneable".equals(type.getImage())) { - return true; - } - } else if (Cloneable.class.isAssignableFrom(type.getType())) { - return true; - } - } - } - - if (node.jjtGetNumChildren() != 0 && node.jjtGetChild(0) instanceof ASTExtendsList) { - final ASTClassOrInterfaceType type = (ASTClassOrInterfaceType) node.jjtGetChild(0).jjtGetChild(0); - final Class clazz = type.getType(); - if (clazz != null) { - return Cloneable.class.isAssignableFrom(clazz); - } - } - - return false; - } - - @Override - public Object visit(final ASTMethodDeclaration node, final Object data) { - // Is this a clone method? - final ASTMethodDeclarator methodDeclarator = node.getFirstChildOfType(ASTMethodDeclarator.class); - if (!isCloneMethod(methodDeclarator)) { - return data; - } - - // Is the clone method just throwing CloneNotSupportedException? - final ASTClassOrInterfaceDeclaration classOrInterface = node.getFirstParentOfType(ASTClassOrInterfaceDeclaration.class); - if (classOrInterface != null && //Don't analyze enums, which cannot subclass clone() - (node.isFinal() || classOrInterface.isFinal())) { - if (node.findDescendantsOfType(ASTBlock.class).size() == 1) { - final List blocks = node.findDescendantsOfType(ASTBlockStatement.class); - if (blocks.size() == 1) { - final ASTBlockStatement block = blocks.get(0); - final ASTClassOrInterfaceType type = block.getFirstDescendantOfType(ASTClassOrInterfaceType.class); - if (type != null && type.getType() != null && type.getNthParent(9).equals(node) - && type.getType().equals(CloneNotSupportedException.class)) { - return data; - } else if (type != null && type.getType() == null - && "CloneNotSupportedException".equals(type.getImage())) { - return data; - } - } - } - } - - // TODO : Should we really care about this? It can only happen with an incomplete auxclasspath - if (classOrInterface != null && classOrInterface.getType() == null) { - // Now check other whether implemented or extended classes are defined inside the same file - final Set classesNames = determineTopLevelCloneableClasses(classOrInterface); - - final ASTImplementsList implementsList = classOrInterface.getFirstChildOfType(ASTImplementsList.class); - if (implementsList != null) { - final List types = implementsList.findChildrenOfType(ASTClassOrInterfaceType.class); - for (final ASTClassOrInterfaceType t : types) { - if (classesNames.contains(t.getImage())) { - return data; - } - } - } - - final ASTExtendsList extendsList = classOrInterface.getFirstChildOfType(ASTExtendsList.class); - if (extendsList != null) { - final ASTClassOrInterfaceType type = extendsList.getFirstChildOfType(ASTClassOrInterfaceType.class); - if (classesNames.contains(type.getImage())) { - return data; - } - } - } - - // Nothing can save us now - addViolation(data, node); - return data; - } - - /** - * Determines all the class/interface declarations inside this compilation - * unit, which implement Cloneable - * - * @param currentClass - * the node of the class, that is currently analyzed (inside this - * compilation unit) - * @return a Set of class/interface names - */ - private Set determineTopLevelCloneableClasses(final ASTClassOrInterfaceDeclaration currentClass) { - final List classes = currentClass.getFirstParentOfType(ASTCompilationUnit.class) - .findDescendantsOfType(ASTClassOrInterfaceDeclaration.class); - final Set classesNames = new HashSet(); - for (final ASTClassOrInterfaceDeclaration c : classes) { - if (c != currentClass && extendsOrImplementsCloneable(c)) { - classesNames.add(c.getImage()); - } - } - return classesNames; - } - - public boolean isCloneMethod(final ASTMethodDeclarator node) { - if (!"clone".equals(node.getImage())) { - return false; - } - final int countParams = ((ASTFormalParameters) node.jjtGetChild(0)).jjtGetNumChildren(); - if (countParams != 0) { - return false; - } - return true; - } -} diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/typeresolution/rules/LooseCoupling.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/typeresolution/rules/LooseCoupling.java deleted file mode 100644 index c60c0a1b4fb..00000000000 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/typeresolution/rules/LooseCoupling.java +++ /dev/null @@ -1,52 +0,0 @@ -/** - * BSD-style license; for more info see http://pmd.sourceforge.net/license.html - */ - -package net.sourceforge.pmd.lang.java.typeresolution.rules; - -import net.sourceforge.pmd.lang.ast.Node; -import net.sourceforge.pmd.lang.java.ast.ASTAnnotation; -import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceBodyDeclaration; -import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceType; -import net.sourceforge.pmd.lang.java.ast.ASTFieldDeclaration; -import net.sourceforge.pmd.lang.java.ast.ASTFormalParameter; -import net.sourceforge.pmd.lang.java.ast.ASTMarkerAnnotation; -import net.sourceforge.pmd.lang.java.ast.ASTName; -import net.sourceforge.pmd.lang.java.ast.ASTResultType; -import net.sourceforge.pmd.lang.java.rule.AbstractJavaRule; -import net.sourceforge.pmd.util.CollectionUtil; - -/** - * This is a separate rule, uses the type resolution facade - */ -public class LooseCoupling extends AbstractJavaRule { - - @Override - public Object visit(ASTClassOrInterfaceType node, Object data) { - if (methodHasOverride(node)) { - return data; - } - Node parent = node.getNthParent(3); - Class clazzType = node.getType(); - boolean isType = CollectionUtil.isCollectionType(clazzType, false); - if (isType && (parent instanceof ASTFieldDeclaration || parent instanceof ASTFormalParameter - || parent instanceof ASTResultType)) { - addViolation(data, node, node.getImage()); - } - return data; - } - - private boolean methodHasOverride(Node node) { - ASTClassOrInterfaceBodyDeclaration method = node.getFirstParentOfType(ASTClassOrInterfaceBodyDeclaration.class); - if (method != null && method.jjtGetNumChildren() > 0 && method.jjtGetChild(0) instanceof ASTAnnotation) { - ASTMarkerAnnotation marker = method.getFirstDescendantOfType(ASTMarkerAnnotation.class); - if (marker != null && marker.getFirstChildOfType(ASTName.class) != null) { - ASTName name = marker.getFirstChildOfType(ASTName.class); - if (name.getType() == Override.class) { - return true; - } - } - } - return false; - } -} diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/typeresolution/rules/SignatureDeclareThrowsException.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/typeresolution/rules/SignatureDeclareThrowsException.java deleted file mode 100644 index e63f304285f..00000000000 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/typeresolution/rules/SignatureDeclareThrowsException.java +++ /dev/null @@ -1,195 +0,0 @@ -/** - * BSD-style license; for more info see http://pmd.sourceforge.net/license.html - */ - -package net.sourceforge.pmd.lang.java.typeresolution.rules; - -import java.util.List; - -import net.sourceforge.pmd.lang.ast.Node; -import net.sourceforge.pmd.lang.java.ast.ASTAnnotation; -import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceDeclaration; -import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceType; -import net.sourceforge.pmd.lang.java.ast.ASTConstructorDeclaration; -import net.sourceforge.pmd.lang.java.ast.ASTExtendsList; -import net.sourceforge.pmd.lang.java.ast.ASTImplementsList; -import net.sourceforge.pmd.lang.java.ast.ASTImportDeclaration; -import net.sourceforge.pmd.lang.java.ast.ASTMethodDeclaration; -import net.sourceforge.pmd.lang.java.ast.ASTName; -import net.sourceforge.pmd.lang.java.rule.AbstractJavaRule; -import net.sourceforge.pmd.properties.BooleanProperty; - -/** - * A method/constructor shouldn't explicitly throw java.lang.Exception, since it - * is unclear which exceptions that can be thrown from the methods. It might be - * difficult to document and understand the vague interfaces. Use either a class - * derived from RuntimeException or a checked exception. This version uses PMD's - * type resolution facilities, and can detect if the class implements or extends - * TestCase class - * - * @author Trond Andersen - * @author acaplan - * @author Wouter Zelle - */ -public class SignatureDeclareThrowsException extends AbstractJavaRule { - - private static final BooleanProperty IGNORE_JUNIT_COMPLETELY_DESCRIPTOR = new BooleanProperty( - "IgnoreJUnitCompletely", "Allow all methods in a JUnit testcase to throw Exceptions", false, 1.0f); - - // Set to true when the class is determined to be a JUnit testcase - private boolean junitImported = false; - - public SignatureDeclareThrowsException() { - definePropertyDescriptor(IGNORE_JUNIT_COMPLETELY_DESCRIPTOR); - } - - @Override - public Object visit(ASTClassOrInterfaceDeclaration node, Object data) { - if (junitImported) { - return super.visit(node, data); - } - - ASTImplementsList impl = node.getFirstChildOfType(ASTImplementsList.class); - if (impl != null && impl.jjtGetParent().equals(node)) { - for (int ix = 0; ix < impl.jjtGetNumChildren(); ix++) { - Node child = impl.jjtGetChild(ix); - - if (child.getClass() != ASTClassOrInterfaceType.class) { - continue; - } - - ASTClassOrInterfaceType type = (ASTClassOrInterfaceType) child; - if (isJUnitTest(type)) { - junitImported = true; - return super.visit(node, data); - } - } - } - if (node.jjtGetNumChildren() != 0 && node.jjtGetChild(0) instanceof ASTExtendsList) { - ASTClassOrInterfaceType type = (ASTClassOrInterfaceType) node.jjtGetChild(0).jjtGetChild(0); - if (isJUnitTest(type)) { - junitImported = true; - return super.visit(node, data); - } - } - - return super.visit(node, data); - } - - private boolean isJUnitTest(ASTClassOrInterfaceType type) { - Class clazz = type.getType(); - if (clazz == null) { - if ("junit.framework.Test".equals(type.getImage())) { - return true; - } - } else if (isJUnitTest(clazz)) { - return true; - } else { - while (clazz != null && !Object.class.equals(clazz)) { - for (Class intf : clazz.getInterfaces()) { - if (isJUnitTest(intf)) { - return true; - } - } - clazz = clazz.getSuperclass(); - } - } - return false; - } - - private boolean isJUnitTest(Class clazz) { - return clazz.getName().equals("junit.framework.Test"); - } - - @Override - public Object visit(ASTImportDeclaration node, Object o) { - if (node.getImportedName().indexOf("junit") != -1) { - junitImported = true; - } - return super.visit(node, o); - } - - @Override - public Object visit(ASTMethodDeclaration methodDeclaration, Object o) { - if (junitImported && isAllowedMethod(methodDeclaration)) { - return super.visit(methodDeclaration, o); - } - - // Ignore overridden methods, the issue should be marked on the method definition - final List methodAnnotations = methodDeclaration.jjtGetParent().findChildrenOfType(ASTAnnotation.class); - for (final ASTAnnotation annotation : methodAnnotations) { - final ASTName annotationName = annotation.getFirstDescendantOfType(ASTName.class); - if (annotationName.hasImageEqualTo("Override") || annotationName.hasImageEqualTo("java.lang.Override")) { - return super.visit(methodDeclaration, o); - } - } - - checkExceptions(methodDeclaration, o); - - return super.visit(methodDeclaration, o); - } - - private boolean isAllowedMethod(ASTMethodDeclaration methodDeclaration) { - if (getProperty(IGNORE_JUNIT_COMPLETELY_DESCRIPTOR)) { - return true; - } else { - return methodDeclaration.getMethodName().equals("setUp") - || methodDeclaration.getMethodName().equals("tearDown"); - } - } - - @Override - public Object visit(ASTConstructorDeclaration constructorDeclaration, Object o) { - checkExceptions(constructorDeclaration, o); - - return super.visit(constructorDeclaration, o); - } - - /** - * Search the list of thrown exceptions for Exception - */ - private void checkExceptions(Node method, Object o) { - List exceptionList = method.findDescendantsOfType(ASTName.class); - if (!exceptionList.isEmpty()) { - evaluateExceptions(exceptionList, o); - } - } - - /** - * Checks all exceptions for possible violation on the exception - * declaration. - * - * @param exceptionList - * containing all exception for declaration - * @param context - */ - private void evaluateExceptions(List exceptionList, Object context) { - for (ASTName exception : exceptionList) { - if (hasDeclaredExceptionInSignature(exception)) { - addViolation(context, exception); - } - } - } - - /** - * Checks if the given value is defined as Exception and the - * parent is either a method or constructor declaration. - * - * @param exception - * to evaluate - * @return true if Exception is declared and has proper parents - */ - private boolean hasDeclaredExceptionInSignature(ASTName exception) { - return exception.hasImageEqualTo("Exception") && isParentSignatureDeclaration(exception); - } - - /** - * @param exception - * to evaluate - * @return true if parent node is either a method or constructor declaration - */ - private boolean isParentSignatureDeclaration(ASTName exception) { - Node parent = exception.jjtGetParent().jjtGetParent(); - return parent instanceof ASTMethodDeclaration || parent instanceof ASTConstructorDeclaration; - } -} diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/typeresolution/rules/imports/UnusedImports.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/typeresolution/rules/imports/UnusedImports.java deleted file mode 100644 index 23f7b23ceff..00000000000 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/typeresolution/rules/imports/UnusedImports.java +++ /dev/null @@ -1,53 +0,0 @@ -/** - * BSD-style license; for more info see http://pmd.sourceforge.net/license.html - */ - -package net.sourceforge.pmd.lang.java.typeresolution.rules.imports; - -import java.util.Iterator; - -import net.sourceforge.pmd.lang.ast.Node; -import net.sourceforge.pmd.lang.java.ast.ASTImportDeclaration; -import net.sourceforge.pmd.lang.java.ast.ASTName; -import net.sourceforge.pmd.lang.java.ast.TypeNode; -import net.sourceforge.pmd.lang.java.rule.imports.UnusedImportsRule; -import net.sourceforge.pmd.lang.rule.ImportWrapper; - -public class UnusedImports extends UnusedImportsRule { - - @Override - public Object visit(ASTImportDeclaration node, Object data) { - if (node.isImportOnDemand()) { - ASTName importedType = (ASTName) node.jjtGetChild(0); - imports.add(new ImportWrapper(importedType.getImage(), null, node, node.getType(), node.isStatic())); - } else { - super.visit(node, data); - } - return data; - } - - @Override - protected void check(Node node) { - if (imports.isEmpty()) { - return; - } - ImportWrapper candidate = getImportWrapper(node); - Iterator it = imports.iterator(); - while (it.hasNext()) { - ImportWrapper i = it.next(); - if (i.matches(candidate)) { - it.remove(); - return; - } - } - if (TypeNode.class.isAssignableFrom(node.getClass()) && ((TypeNode) node).getType() != null) { - Class c = ((TypeNode) node).getType(); - if (c.getPackage() != null) { - candidate = new ImportWrapper(c.getPackage().getName(), null); - if (imports.contains(candidate)) { - imports.remove(candidate); - } - } - } - } -} diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/typeresolution/typedefinition/JavaTypeDefinition.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/typeresolution/typedefinition/JavaTypeDefinition.java index 7a40b0c1c79..6c5bc5b3d6c 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/typeresolution/typedefinition/JavaTypeDefinition.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/typeresolution/typedefinition/JavaTypeDefinition.java @@ -12,6 +12,9 @@ import java.util.Map; import java.util.Set; +import org.apache.commons.lang3.ArrayUtils; + + public abstract class JavaTypeDefinition implements TypeDefinition { // contains non-generic and raw EXACT types private static final Map, JavaTypeDefinition> CLASS_EXACT_TYPE_DEF_CACHE = new HashMap<>(); @@ -37,9 +40,13 @@ public static JavaTypeDefinition forClass(TypeDefinitionType type, JavaTypeDefin } case UPPER_BOUND: case UPPER_WILDCARD: - return new JavaTypeDefinitionUpper(type, intersectionTypes); + // In theory, if one of the bounds can't be resolved, then the type is useless. + // Looking at the implementation of JavaTypeDefinitionUpper, it looks like only the + // first bound is used, so we could only check for the first array component. + // But isn't that behaviour weird ? Where are the other bounds useful then ? + return ArrayUtils.contains(intersectionTypes, null) ? null : new JavaTypeDefinitionUpper(type, intersectionTypes); case LOWER_WILDCARD: - return new JavaTypeDefinitionLower(intersectionTypes); + return ArrayUtils.contains(intersectionTypes, null) ? null : new JavaTypeDefinitionLower(intersectionTypes); default: throw new IllegalStateException("Unknow type"); } @@ -62,7 +69,12 @@ public static JavaTypeDefinition forClass(final Class clazz, JavaTypeDefiniti return typeDef; } - final JavaTypeDefinition newDef = new JavaTypeDefinitionSimple(clazz); + final JavaTypeDefinition newDef; + try { + newDef = new JavaTypeDefinitionSimple(clazz); + } catch (final NoClassDefFoundError e) { + return null; // Can happen if a parent class references a class not in classpath + } CLASS_EXACT_TYPE_DEF_CACHE.put(clazz, newDef); @@ -95,7 +107,6 @@ public static int getGenericTypeIndex(TypeVariable[] typeParameters, final St public abstract JavaTypeDefinition resolveTypeDefinition(Type type, Method method, List methodTypeArgs); - public abstract JavaTypeDefinition getComponentType(); public abstract boolean isClassOrInterface(); @@ -109,6 +120,60 @@ public abstract JavaTypeDefinition resolveTypeDefinition(Type type, Method metho public abstract boolean isArrayType(); + + /** + * Gets the component type of this type definition if it + * is an array type. The component type of an array is + * the type is the same type as the array, with one less + * dimension, e.g. the component type of {@code int[][][]} + * is {@code int[][]}. + * + * @return The component type of this array type + * + * @throws IllegalStateException if this definition doesn't identify an array type + * @see #getElementType() + */ + public abstract JavaTypeDefinition getComponentType(); + + + /** + * Gets the element type of this type definition if it + * is an array type. The component type of an array is + * the type is the same type as the array, stripped of + * all array dimensions, e.g. the element type of + * {@code int[][][]} is {@code int}. + * + * @return The element type of this array type, or this + * type definition if {@link #isArrayType()} returns false + * + * @see #getComponentType() + */ + public abstract JavaTypeDefinition getElementType(); + + + // @formatter:off + /** + * Returns the type definition of the array type which + * has the given number of array dimensions, plus the dimensions + * of this type definition. Examples, assuming JavaTypeDefinition + * and Class are interchangeable (== is equality, not identity): + *
    + *
  • {@code int.class.withDimensions(3) == int[][][].class} + *
  • {@code int[].class.withDimensions(1) == int[][].class} + *
  • {@code c.withDimensions(0) == c} + *
  • {@code n > 0 => c.withDimensions(n).getComponentType() == c.withDimensions(n - 1)} + *
+ * + * @param numDimensions Number of dimensions added to this type in + * the resulting array type + * + * @return A new type definition, or this if numDimensions == 0 + * @throws IllegalArgumentException if numDimensions < 0 + */ + // @formatter:on + public abstract JavaTypeDefinition withDimensions(int numDimensions); + + @Override public abstract String toString(); @@ -124,8 +189,10 @@ public abstract JavaTypeDefinition resolveTypeDefinition(Type type, Method metho public abstract Set> getErasedSuperTypeSet(); + /** - * @return true if clazz is generic and had not been parameterized + * Returns true if this type has type parameters and has not been parameterized, + * e.g. {@code List} instead of {@code List}. */ public abstract boolean isRawType(); @@ -158,4 +225,6 @@ public final TypeDefinitionType getDefinitionType() { public abstract JavaTypeDefinition getJavaType(int index); public abstract int getJavaTypeCount(); + + protected abstract String shallowString(); } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/typeresolution/typedefinition/JavaTypeDefinitionSimple.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/typeresolution/typedefinition/JavaTypeDefinitionSimple.java index 8e2e3154eab..1bed9a9f645 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/typeresolution/typedefinition/JavaTypeDefinitionSimple.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/typeresolution/typedefinition/JavaTypeDefinitionSimple.java @@ -9,6 +9,8 @@ import static net.sourceforge.pmd.lang.java.typeresolution.typedefinition.TypeDefinitionType.LOWER_WILDCARD; import static net.sourceforge.pmd.lang.java.typeresolution.typedefinition.TypeDefinitionType.UPPER_WILDCARD; +import java.lang.reflect.Array; +import java.lang.reflect.GenericArrayType; import java.lang.reflect.Method; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; @@ -18,7 +20,10 @@ import java.util.Collections; import java.util.HashSet; import java.util.List; +import java.util.Objects; import java.util.Set; +import java.util.logging.Level; +import java.util.logging.Logger; /* default */ class JavaTypeDefinitionSimple extends JavaTypeDefinition { @@ -30,6 +35,8 @@ private final boolean isRawType; private final JavaTypeDefinition enclosingClass; + private static final Logger LOG = Logger.getLogger(JavaTypeDefinitionSimple.class.getName()); + protected JavaTypeDefinitionSimple(Class clazz, JavaTypeDefinition... boundGenerics) { super(EXACT); this.clazz = clazz; @@ -79,11 +86,11 @@ public boolean isGeneric() { } private JavaTypeDefinition getGenericType(final String parameterName, Method method, - List methodTypeArgumens) { - if (method != null && methodTypeArgumens != null) { + List methodTypeArguments) { + if (method != null && methodTypeArguments != null) { int paramIndex = getGenericTypeIndex(method.getTypeParameters(), parameterName); if (paramIndex != -1) { - return methodTypeArgumens.get(paramIndex); + return methodTypeArguments.get(paramIndex); } } @@ -93,8 +100,8 @@ private JavaTypeDefinition getGenericType(final String parameterName, Method met @Override public JavaTypeDefinition getGenericType(final String parameterName) { for (JavaTypeDefinition currTypeDef = this; currTypeDef != null; - currTypeDef = currTypeDef.getEnclosingClass()) { - + currTypeDef = currTypeDef.getEnclosingClass()) { + int paramIndex = getGenericTypeIndex(currTypeDef.getType().getTypeParameters(), parameterName); if (paramIndex != -1) { return currTypeDef.getGenericType(paramIndex); @@ -104,13 +111,16 @@ public JavaTypeDefinition getGenericType(final String parameterName) { // throw because we could not find parameterName StringBuilder builder = new StringBuilder("No generic parameter by name ").append(parameterName); for (JavaTypeDefinition currTypeDef = this; currTypeDef != null; - currTypeDef = currTypeDef.getEnclosingClass()) { - + currTypeDef = currTypeDef.getEnclosingClass()) { + builder.append("\n on class "); - builder.append(clazz.getSimpleName()); + builder.append(currTypeDef.getType().getSimpleName()); } - throw new IllegalArgumentException(builder.toString()); + LOG.log(Level.FINE, builder.toString()); + // TODO: throw eventually + //throw new IllegalArgumentException(builder.toString()); + return forClass(Object.class); } @Override @@ -127,7 +137,7 @@ public JavaTypeDefinition getGenericType(final int index) { for (int i = genericArgs.size(); i <= index; i++) { genericArgs.add(null); } - + /* * Set a default to circuit-brake any recursions (ie: raw types with no generic info) * Object.class is a right answer in those scenarios @@ -180,13 +190,25 @@ public JavaTypeDefinition resolveTypeDefinition(final Type type, Method method, final Type[] wildcardUpperBounds = ((WildcardType) type).getUpperBounds(); return forClass(UPPER_WILDCARD, resolveTypeDefinition(wildcardUpperBounds[0], method, methodTypeArgs)); } + } else if (type instanceof GenericArrayType) { + JavaTypeDefinition component = resolveTypeDefinition(((GenericArrayType) type).getGenericComponentType(), method, methodTypeArgs); + // TODO: retain the generic types of the array component... + return forClass(Array.newInstance(component.getType(), 0).getClass()); } // TODO : Shall we throw here? return forClass(Object.class); } + + @Override + public boolean isArrayType() { + return clazz.isArray(); + } + + // TODO: are generics okay like this? + @Override public JavaTypeDefinition getComponentType() { Class componentType = getType().getComponentType(); @@ -197,14 +219,39 @@ public JavaTypeDefinition getComponentType() { return forClass(componentType); } + + private Class getElementTypeRec(Class arrayType) { + return arrayType.isArray() ? getElementTypeRec(arrayType.getComponentType()) : arrayType; + } + + + @Override + public JavaTypeDefinition getElementType() { + return isArrayType() ? forClass(getElementTypeRec(getType())) : this; + } + + + @Override + public JavaTypeDefinition withDimensions(int numDimensions) { + if (numDimensions < 0) { + throw new IllegalArgumentException("Negative array dimension"); + } + return numDimensions == 0 + ? this + : forClass(Array.newInstance(getType(), (int[]) Array.newInstance(int.class, numDimensions)).getClass()); + } + + @Override public boolean isClassOrInterface() { return !clazz.isEnum() && !clazz.isPrimitive() && !clazz.isAnnotation() && !clazz.isArray(); } + @Override public boolean isNullType() { return false; } + @Override public boolean isPrimitive() { return clazz.isPrimitive(); } @@ -214,30 +261,51 @@ public boolean equivalent(JavaTypeDefinition def) { return clazz.equals(def.getType()) && getTypeParameterCount() == def.getTypeParameterCount(); } + @Override public boolean hasSameErasureAs(JavaTypeDefinition def) { return clazz == def.getType(); } + @Override public int getTypeParameterCount() { return typeParameterCount; } - public boolean isArrayType() { - return clazz.isArray(); - } @Override public String toString() { + final StringBuilder sb = new StringBuilder("JavaTypeDefinition [clazz=").append(clazz) + .append(", definitionType=").append(getDefinitionType()) + .append(", genericArgs=["); + + // Forcefully resolve all generic types + for (int i = 0; i < genericArgs.size(); i++) { + getGenericType(i); + } + + for (final JavaTypeDefinition jtd : genericArgs) { + sb.append(jtd.shallowString()).append(", "); + } + + if (!genericArgs.isEmpty()) { + sb.replace(sb.length() - 3, sb.length() - 1, ""); // remove last comma + } + + return sb.append("], isGeneric=").append(isGeneric) + .append("]\n").toString(); + } + + @Override + public String shallowString() { return new StringBuilder("JavaTypeDefinition [clazz=").append(clazz) .append(", definitionType=").append(getDefinitionType()) - .append(", genericArgs=").append(genericArgs) .append(", isGeneric=").append(isGeneric) .append("]\n").toString(); } @Override public boolean equals(Object obj) { - if (obj == null || !(obj instanceof JavaTypeDefinitionSimple)) { + if (!(obj instanceof JavaTypeDefinitionSimple)) { return false; } @@ -294,6 +362,7 @@ protected Set getSuperTypeSet(Set destin return destinationSet; } + @Override public Set> getErasedSuperTypeSet() { Set> result = new HashSet<>(); result.add(Object.class); @@ -313,8 +382,10 @@ private static Set> getErasedSuperTypeSet(Class clazz, Set> return destinationSet; } + + @Override public JavaTypeDefinition getAsSuper(Class superClazz) { - if (clazz == superClazz) { // optimize for same class calls + if (Objects.equals(clazz, superClazz)) { // optimize for same class calls return this; } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/typeresolution/typedefinition/JavaTypeDefinitionUpper.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/typeresolution/typedefinition/JavaTypeDefinitionUpper.java index 64b11a48ddb..02f74ca5fb6 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/typeresolution/typedefinition/JavaTypeDefinitionUpper.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/typeresolution/typedefinition/JavaTypeDefinitionUpper.java @@ -6,6 +6,7 @@ import java.lang.reflect.Method; import java.lang.reflect.Type; +import java.util.Arrays; import java.util.List; import java.util.Set; @@ -77,6 +78,19 @@ public JavaTypeDefinition getComponentType() { return firstJavaType().getComponentType(); } + + @Override + public JavaTypeDefinition getElementType() { + return firstJavaType().getElementType(); + } + + + @Override + public JavaTypeDefinition withDimensions(int numDimensions) { + return firstJavaType().withDimensions(numDimensions); + } + + @Override public boolean isClassOrInterface() { return firstJavaType().isClassOrInterface(); @@ -111,15 +125,20 @@ public boolean isArrayType() { public String toString() { StringBuilder builder = new StringBuilder() .append("JavaTypeDefinition ") - .append(getDefinitionType().toString()) + .append(getDefinitionType()) .append(" [") .append(typeList[0]); for (int index = 1; index < typeList.length; ++index) { - builder.append(" && "); - builder.append(typeList[index]); + builder.append(" && ") + .append(typeList[index]); } return builder.append("]").toString(); } + + @Override + protected String shallowString() { + return toString(); + } @Override public boolean equals(Object obj) { @@ -139,18 +158,7 @@ public boolean equals(Object obj) { } // we assume that the typeList list cannot contain duplicates, then indeed, this will prove equality - outer: - for (JavaTypeDefinition intersectionTypeDef : typeList) { - for (JavaTypeDefinition otherIntersectionTypeDef : otherTypeDef.typeList) { - if (intersectionTypeDef.equals(otherIntersectionTypeDef)) { - continue outer; - } - } - - return false; - } - - return true; + return Arrays.deepEquals(typeList, otherTypeDef.typeList); } @Override diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/typeresolution/typeinference/InferenceRuleType.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/typeresolution/typeinference/InferenceRuleType.java index 5fc2d675d48..b88258dc70f 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/typeresolution/typeinference/InferenceRuleType.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/typeresolution/typeinference/InferenceRuleType.java @@ -83,7 +83,9 @@ public List reduce(BoundOrConstraint val) { // A constraint formula of the form ‹S = T›, where S and T are type arguments (§4.5.1), is reduced as // follows: TODO - throw new IllegalStateException("Reduce method is flawed! " + val.toString()); + // TODO: Reduce to false for the time being, reduction is still incomplete + return null; + //throw new IllegalStateException("Reduce method is flawed! " + val.toString()); } }, @@ -128,7 +130,9 @@ public List reduce(BoundOrConstraint val) { // Otherwise, the constraint is reduced according to the form of T: TODO - throw new IllegalStateException("Reduce method is flawed! " + val.toString()); + // TODO: Reduce to false for the time being, reduction is still incomplete + return null; + //throw new IllegalStateException("Reduce method is flawed! " + val.toString()); } }, @@ -220,7 +224,9 @@ public List reduce(BoundOrConstraint val) { // If T is a wildcard of the form ? super T': TODO - throw new IllegalStateException("Reduce method is flawed! " + val.toString()); + // TODO: Reduce to false for the time being, reduction is still incomplete + return null; + //throw new IllegalStateException("Reduce method is flawed! " + val.toString()); } }; @@ -257,7 +263,5 @@ private static Constraint copyConstraint(BoundOrConstraint val, InferenceRuleTyp } } - public List reduce(BoundOrConstraint constraint) { - return null; - } + public abstract List reduce(BoundOrConstraint constraint); } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/typeresolution/typeinference/TypeInferenceResolver.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/typeresolution/typeinference/TypeInferenceResolver.java index 40908b03533..21c5247488f 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/typeresolution/typeinference/TypeInferenceResolver.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/typeresolution/typeinference/TypeInferenceResolver.java @@ -15,6 +15,7 @@ import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Set; import net.sourceforge.pmd.lang.java.typeresolution.MethodTypeResolution; @@ -39,6 +40,11 @@ public static List inferTypes(List constraints, while (!constraints.isEmpty()) { List reduceResult = constraints.remove(constraints.size() - 1).reduce(); + // If null, the types are incompatible + if (reduceResult == null) { + return null; + } + for (BoundOrConstraint boundOrConstraint : reduceResult) { if (boundOrConstraint instanceof Bound) { newBounds.add((Bound) boundOrConstraint); @@ -160,7 +166,7 @@ public static JavaTypeDefinition merge(JavaTypeDefinition first, JavaTypeDefinit } public static Set> getErasedCandidateSet(List erasedSuperTypeSets) { - Set> result = null; + Set> result = new HashSet<>(); if (!erasedSuperTypeSets.isEmpty()) { result = erasedSuperTypeSets.get(0).getErasedSuperTypeSet(); @@ -179,7 +185,7 @@ public static Set> getMinimalErasedCandidateSet(Set> erasedSet outter: for (Class candidate : erasedSet) { for (Class erasedSetMember : erasedSet) { - if (candidate != erasedSetMember + if (!Objects.equals(candidate, erasedSetMember) && MethodTypeResolution.isSubtypeable(candidate, erasedSetMember)) { continue outter; // skip candidate from result set } @@ -271,7 +277,7 @@ public static boolean isProperSubsetOfVariables(List variables, for (Variable unresolvedVariable : variables) { for (Variable dependency : dependencies.get(unresolvedVariable)) { if (!instantiations.containsKey(dependency) - && unresolvedVariable != dependency + && !Objects.equals(unresolvedVariable, dependency) && !boundsHaveAnEqualityBetween(variables, dependency, bounds)) { return false; } @@ -288,8 +294,8 @@ public static boolean boundsHaveAnEqualityBetween(List firstList, Vari for (Bound bound : bounds) { for (Variable first : firstList) { if (bound.ruleType == EQUALITY - && ((bound.leftVariable() == first && bound.rightVariable() == second) - || (bound.leftVariable() == second && bound.rightVariable() == first))) { + && (bound.leftVariable() == first && bound.rightVariable() == second + || bound.leftVariable() == second && bound.rightVariable() == first)) { return true; } } @@ -319,63 +325,66 @@ private static class Combinations implements Iterable> { @Override public Iterator> iterator() { - return new Iterator>() { - private BitSet nextBitSet = new BitSet(n); + return new CombinationsIterator(); + } - { - advanceToNextK(); - } + private class CombinationsIterator implements Iterator> { + private BitSet nextBitSet = new BitSet(n); - @Override - public void remove() { + private CombinationsIterator() { + advanceToNextK(); + } + private void advanceToNextK() { + k++; + if (k > n) { + nextBitSet = null; + } else { + nextBitSet.clear(); + nextBitSet.set(0, k); } + } - private void advanceToNextK() { - if (++k > n) { - nextBitSet = null; - } else { - nextBitSet.clear(); - nextBitSet.set(0, k); - } - } + @Override + public void remove() { + throw new UnsupportedOperationException("remove"); + } - @Override - public boolean hasNext() { - return nextBitSet != null; - } + @Override + public boolean hasNext() { + return nextBitSet != null; + } - @Override - public List next() { - BitSet resultBitSet = (BitSet) nextBitSet.clone(); + @Override + public List next() { + BitSet resultBitSet = (BitSet) nextBitSet.clone(); - int b = nextBitSet.previousClearBit(n - 1); - int b1 = nextBitSet.previousSetBit(b); + int b = nextBitSet.previousClearBit(n - 1); + int b1 = nextBitSet.previousSetBit(b); - if (b1 == -1) { - advanceToNextK(); - } else { - nextBitSet.clear(b1); - nextBitSet.set(b1 + 1, b1 + (n - b) + 1); - nextBitSet.clear(b1 + (n - b) + 1, n); - } + if (b1 == -1) { + advanceToNextK(); + } else { + nextBitSet.clear(b1); + nextBitSet.set(b1 + 1, b1 + (n - b) + 1); + nextBitSet.clear(b1 + (n - b) + 1, n); + } - resultList.clear(); - for (int i = 0; i < n; ++i) { - if (resultBitSet.get(i)) { - resultList.add(permuteThis.get(i)); - } + resultList.clear(); + for (int i = 0; i < n; ++i) { + if (resultBitSet.get(i)) { + resultList.add(permuteThis.get(i)); } - - return unmodifyableViewOfResult; } - }; + + return unmodifyableViewOfResult; + } } } /** - * @return A map of variable -> proper type produced by searching for α = T or T = α bounds + * @return A map of variable -> proper type produced by searching for α = T or T = α bounds */ public static Map getInstantiations(List bounds) { Map result = new HashMap<>(); 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 3aa89bfe94f..dce2ae802c1 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 @@ -10,7 +10,6 @@ import java.util.Map; import org.objectweb.asm.AnnotationVisitor; -import org.objectweb.asm.Attribute; import org.objectweb.asm.ClassVisitor; import org.objectweb.asm.FieldVisitor; import org.objectweb.asm.Label; @@ -37,7 +36,7 @@ public class PMDASMVisitor extends ClassVisitor { public List innerClasses; public PMDASMVisitor(String outerName) { - super(Opcodes.ASM5); + super(Opcodes.ASM7_EXPERIMENTAL); this.outerName = outerName; } @@ -126,10 +125,6 @@ public MethodVisitor visitMethod(int access, String name, String desc, String si return methodVisitor; } - @Override - public void visitSource(String source, String debug) { - } - @Override public void visitInnerClass(String name, String outerName, String innerName, int access) { if (!this.outerName.replace('.', '/').equals(outerName)) { @@ -147,14 +142,6 @@ public void visitInnerClass(String name, String outerName, String innerName, int packages.put(innerName, name.replace('/', '.')); } - @Override - public void visitOuterClass(String owner, String name, String desc) { - } - - @Override - public void visitEnd() { - } - private void addMethodDesc(String desc) { addTypes(desc); addType(Type.getReturnType(desc)); @@ -181,10 +168,6 @@ private void addType(Type t) { } } - @Override - public void visitAttribute(Attribute attr) { - } - /* * Start visitors */ @@ -203,14 +186,6 @@ public AnnotationVisitor visitAnnotation(String desc, boolean visible) { parent.addType(Type.getType(desc)); return parent.annotationVisitor; } - - @Override - public void visitAttribute(Attribute attr) { - } - - @Override - public void visitEnd() { - } } private static class PMDAnnotationVisitor extends AnnotationVisitor { @@ -237,10 +212,6 @@ public AnnotationVisitor visitArray(String name) { return this; } - @Override - public void visitEnd() { - } - @Override public void visit(String name, Object value) { if (value instanceof Type) { @@ -257,10 +228,6 @@ private static class PMDSignatureVisitor extends SignatureVisitor { this.parent = visitor; } - @Override - public void visitFormalTypeParameter(String name) { - } - @Override public SignatureVisitor visitClassBound() { return this; @@ -296,14 +263,6 @@ public SignatureVisitor visitExceptionType() { return this; } - @Override - public void visitBaseType(char descriptor) { - } - - @Override - public void visitTypeVariable(String name) { - } - @Override public SignatureVisitor visitArrayType() { return this; @@ -319,18 +278,10 @@ public void visitInnerClassType(String name) { // parent.parseClassName(name); } - @Override - public void visitTypeArgument() { - } - @Override public SignatureVisitor visitTypeArgument(char wildcard) { return this; } - - @Override - public void visitEnd() { - } } private static class PMDMethodVisitor extends MethodVisitor { @@ -347,11 +298,6 @@ public AnnotationVisitor visitParameterAnnotation(int parameter, String desc, bo return parent.annotationVisitor; } - public AnnotationVisitor visitAnnotation(String name, String desc) { - parent.addType(Type.getType(desc)); - return parent.annotationVisitor; - } - @Override public void visitTypeInsn(int opcode, String desc) { if (desc.charAt(0) == '[') { @@ -399,59 +345,11 @@ public void visitLocalVariable(String name, String desc, String sig, Label start parent.extractSignature(sig); } - @Override - public void visitCode() { - } - - @Override - public void visitFrame(int type, int nLocal, Object[] local, int nStack, Object[] stack) { - } - - @Override - public void visitInsn(int opcode) { - } - - @Override - public void visitIntInsn(int opcode, int operand) { - } - - @Override - public void visitVarInsn(int opcode, int var) { - } - - @Override - public void visitJumpInsn(int opcode, Label label) { - } - - @Override - public void visitLabel(Label label) { - } - - @Override - public void visitIincInsn(int var, int increment) { - } - - @Override - public void visitTableSwitchInsn(int min, int max, Label dflt, Label... labels) { - } - - @Override - public void visitLookupSwitchInsn(Label dflt, int[] keys, Label[] labels) { - } - @Override public void visitTryCatchBlock(Label start, Label end, Label handler, String type) { parent.parseClassName(type); } - @Override - public void visitLineNumber(int line, Label start) { - } - - @Override - public void visitMaxs(int maxStack, int maxLocals) { - } - @Override public AnnotationVisitor visitAnnotationDefault() { return parent.annotationVisitor; @@ -462,14 +360,5 @@ public AnnotationVisitor visitAnnotation(String desc, boolean visible) { parent.addType(Type.getType(desc)); return parent.annotationVisitor; } - - @Override - public void visitEnd() { - } - - @Override - public void visitAttribute(Attribute attr) { - } - } } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/xpath/GetCommentOnFunction.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/xpath/GetCommentOnFunction.java index 2a6ba8bf204..9e79e5328f9 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/xpath/GetCommentOnFunction.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/xpath/GetCommentOnFunction.java @@ -12,6 +12,7 @@ import org.jaxen.SimpleFunctionContext; import org.jaxen.XPathFunctionContext; +import net.sourceforge.pmd.annotation.InternalApi; import net.sourceforge.pmd.lang.ast.AbstractNode; import net.sourceforge.pmd.lang.ast.Node; import net.sourceforge.pmd.lang.java.ast.ASTCompilationUnit; @@ -21,9 +22,11 @@ * The XPath query "//VariableDeclarator[contains(getCommentOn(), * '//password')]" will find all variables declared that are annotated with the * password comment. - * + * * @author Andy Throgmorton */ +@InternalApi +@Deprecated public class GetCommentOnFunction implements Function { public static void registerSelfInSimpleContext() { @@ -32,6 +35,7 @@ public static void registerSelfInSimpleContext() { new GetCommentOnFunction()); } + @Override public Object call(Context context, List args) throws FunctionCallException { if (!args.isEmpty()) { return Boolean.FALSE; diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/xpath/JavaFunctions.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/xpath/JavaFunctions.java index 20b20a21c15..d4242f26e6d 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/xpath/JavaFunctions.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/xpath/JavaFunctions.java @@ -4,6 +4,7 @@ package net.sourceforge.pmd.lang.java.xpath; +import net.sourceforge.pmd.annotation.InternalApi; import net.sourceforge.pmd.lang.ast.Node; import net.sourceforge.pmd.lang.ast.xpath.saxon.ElementNode; @@ -12,22 +13,35 @@ /** * Exposes all Java Language specific functions for Saxon use. */ +@InternalApi +@Deprecated public final class JavaFunctions { private JavaFunctions() { // utility class } - public static boolean typeof(XPathContext context, String nodeTypeName, String fullTypeName) { + @Deprecated + public static boolean typeof(final XPathContext context, final String nodeTypeName, final String fullTypeName) { return typeof(context, nodeTypeName, fullTypeName, null); } - public static boolean typeof(XPathContext context, String nodeTypeName, String fullTypeName, String shortTypeName) { + @Deprecated + public static boolean typeof(final XPathContext context, final String nodeTypeName, + final String fullTypeName, final String shortTypeName) { return TypeOfFunction.typeof((Node) ((ElementNode) context.getContextItem()).getUnderlyingNode(), nodeTypeName, fullTypeName, shortTypeName); } - public static double metric(XPathContext context, String metricKeyName) { + public static double metric(final XPathContext context, final String metricKeyName) { return MetricFunction.getMetric((Node) ((ElementNode) context.getContextItem()).getUnderlyingNode(), metricKeyName); } + + public static boolean typeIs(final XPathContext context, final String fullTypeName) { + return TypeIsFunction.typeIs((Node) ((ElementNode) context.getContextItem()).getUnderlyingNode(), fullTypeName); + } + + public static boolean typeIsExactly(final XPathContext context, final String fullTypeName) { + return TypeIsExactlyFunction.typeIsExactly((Node) ((ElementNode) context.getContextItem()).getUnderlyingNode(), fullTypeName); + } } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/xpath/MetricFunction.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/xpath/MetricFunction.java index d53d88bb87a..29e9c2bb692 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/xpath/MetricFunction.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/xpath/MetricFunction.java @@ -5,6 +5,7 @@ package net.sourceforge.pmd.lang.java.xpath; import java.util.List; +import java.util.Locale; import java.util.Map; import org.apache.commons.lang3.EnumUtils; @@ -14,17 +15,26 @@ import org.jaxen.SimpleFunctionContext; import org.jaxen.XPathFunctionContext; +import net.sourceforge.pmd.annotation.InternalApi; import net.sourceforge.pmd.lang.ast.Node; import net.sourceforge.pmd.lang.java.ast.ASTAnyTypeDeclaration; -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.JavaClassMetricKey; import net.sourceforge.pmd.lang.java.metrics.api.JavaOperationMetricKey; + /** + * Implements the {@code metric()} XPath function. Takes the + * string name of a metric and the context node and returns + * the result if the metric can be computed, otherwise returns + * {@link Double#NaN}. + * * @author Clément Fournier * @since 6.0.0 */ +@InternalApi +@Deprecated public class MetricFunction implements Function { @@ -35,9 +45,7 @@ public class MetricFunction implements Function { @Override public Object call(Context context, List args) throws FunctionCallException { - String metricKeyName = null; - - if (args.size() == 0) { + if (args.isEmpty()) { throw new IllegalArgumentException(badMetricKeyArgMessage()); } @@ -45,9 +53,9 @@ public Object call(Context context, List args) throws FunctionCallException { throw new IllegalArgumentException(badMetricKeyArgMessage()); } - metricKeyName = (String) args.get(0); - + String metricKeyName = (String) args.get(0); Node n = (Node) context.getNodeSet().get(0); + return getMetric(n, metricKeyName); } @@ -75,8 +83,8 @@ public static String badMetricKeyArgMessage() { public static double getMetric(Node n, String metricKeyName) { if (n instanceof ASTAnyTypeDeclaration) { return getClassMetric((ASTAnyTypeDeclaration) n, getClassMetricKey(metricKeyName)); - } else if (n instanceof ASTMethodOrConstructorDeclaration) { - return getOpMetric((ASTMethodOrConstructorDeclaration) n, getOperationMetricKey(metricKeyName)); + } else if (n instanceof MethodLikeNode) { + return getOpMetric((MethodLikeNode) n, getOperationMetricKey(metricKeyName)); } else { throw new IllegalStateException(genericBadNodeMessage()); } @@ -84,7 +92,7 @@ public static double getMetric(Node n, String metricKeyName) { private static JavaClassMetricKey getClassMetricKey(String s) { - String constantName = s.toUpperCase(); + String constantName = s.toUpperCase(Locale.ROOT); if (!CLASS_METRIC_KEY_MAP.containsKey(constantName)) { throw new IllegalArgumentException(badClassMetricKeyMessage()); } @@ -93,7 +101,7 @@ private static JavaClassMetricKey getClassMetricKey(String s) { private static JavaOperationMetricKey getOperationMetricKey(String s) { - String constantName = s.toUpperCase(); + String constantName = s.toUpperCase(Locale.ROOT); if (!OPERATION_METRIC_KEY_MAP.containsKey(constantName)) { throw new IllegalArgumentException(badOperationMetricKeyMessage()); } @@ -101,7 +109,7 @@ private static JavaOperationMetricKey getOperationMetricKey(String s) { } - private static double getOpMetric(ASTMethodOrConstructorDeclaration node, JavaOperationMetricKey key) { + private static double getOpMetric(MethodLikeNode node, JavaOperationMetricKey key) { return JavaMetrics.get(key, node); } diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/xpath/TypeIsExactlyFunction.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/xpath/TypeIsExactlyFunction.java new file mode 100644 index 00000000000..811afd1525c --- /dev/null +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/xpath/TypeIsExactlyFunction.java @@ -0,0 +1,58 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.xpath; + +import java.util.List; + +import org.jaxen.Context; +import org.jaxen.Function; +import org.jaxen.FunctionCallException; +import org.jaxen.SimpleFunctionContext; +import org.jaxen.XPathFunctionContext; + +import net.sourceforge.pmd.annotation.InternalApi; +import net.sourceforge.pmd.lang.ast.Node; +import net.sourceforge.pmd.lang.java.ast.TypeNode; +import net.sourceforge.pmd.lang.java.typeresolution.TypeHelper; + + +@InternalApi +@Deprecated +public class TypeIsExactlyFunction implements Function { + + public static void registerSelfInSimpleContext() { + ((SimpleFunctionContext) XPathFunctionContext.getInstance()).registerFunction(null, "typeIsExactly", + new TypeIsExactlyFunction()); + } + + @Override + public Object call(final Context context, final List args) throws FunctionCallException { + if (args.size() != 1) { + throw new IllegalArgumentException( + "typeIsExactly function takes a single String argument with the fully qualified type name to check against."); + } + final String fullTypeName = (String) args.get(0); + final Node n = (Node) context.getNodeSet().get(0); + + return typeIsExactly(n, fullTypeName); + } + + /** + * Example XPath 1.0: {@code //ClassOrInterfaceType[typeIsExactly('java.lang.String')]} + *

+ * Example XPath 2.0: {@code //ClassOrInterfaceType[pmd-java:typeIsExactly('java.lang.String')]} + * + * @param n The node on which to check for types + * @param fullTypeName The fully qualified name of the class or any supertype + * @return True if the type of the node matches, false otherwise. + */ + public static boolean typeIsExactly(final Node n, final String fullTypeName) { + if (n instanceof TypeNode) { + return TypeHelper.isExactlyA((TypeNode) n, fullTypeName); + } else { + throw new IllegalArgumentException("typeIsExactly function may only be called on a TypeNode."); + } + } +} diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/xpath/TypeIsFunction.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/xpath/TypeIsFunction.java new file mode 100644 index 00000000000..41ac55d5b0e --- /dev/null +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/xpath/TypeIsFunction.java @@ -0,0 +1,58 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.xpath; + +import java.util.List; + +import org.jaxen.Context; +import org.jaxen.Function; +import org.jaxen.FunctionCallException; +import org.jaxen.SimpleFunctionContext; +import org.jaxen.XPathFunctionContext; + +import net.sourceforge.pmd.annotation.InternalApi; +import net.sourceforge.pmd.lang.ast.Node; +import net.sourceforge.pmd.lang.java.ast.TypeNode; +import net.sourceforge.pmd.lang.java.typeresolution.TypeHelper; + + +@InternalApi +@Deprecated +public class TypeIsFunction implements Function { + + public static void registerSelfInSimpleContext() { + ((SimpleFunctionContext) XPathFunctionContext.getInstance()).registerFunction(null, "typeIs", + new TypeIsFunction()); + } + + @Override + public Object call(final Context context, final List args) throws FunctionCallException { + if (args.size() != 1) { + throw new IllegalArgumentException( + "typeIs function takes a single String argument with the fully qualified type name to check against."); + } + final String fullTypeName = (String) args.get(0); + final Node n = (Node) context.getNodeSet().get(0); + + return typeIs(n, fullTypeName); + } + + /** + * Example XPath 1.0: {@code //ClassOrInterfaceType[typeIs('java.lang.String')]} + *

+ * Example XPath 2.0: {@code //ClassOrInterfaceType[pmd-java:typeIs('java.lang.String')]} + * + * @param n The node on which to check for types + * @param fullTypeName The fully qualified name of the class or any supertype + * @return True if the type of the node matches, false otherwise. + */ + public static boolean typeIs(final Node n, final String fullTypeName) { + if (n instanceof TypeNode) { + return TypeHelper.isA((TypeNode) n, fullTypeName); + } else { + throw new IllegalArgumentException("typeIs function may only be called on a TypeNode."); + } + } +} diff --git a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/xpath/TypeOfFunction.java b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/xpath/TypeOfFunction.java index 3901d5f7aa9..2fe6167146c 100644 --- a/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/xpath/TypeOfFunction.java +++ b/pmd-java/src/main/java/net/sourceforge/pmd/lang/java/xpath/TypeOfFunction.java @@ -6,6 +6,7 @@ import java.util.Arrays; import java.util.List; +import java.util.logging.Logger; import org.jaxen.Context; import org.jaxen.Function; @@ -13,18 +14,25 @@ import org.jaxen.SimpleFunctionContext; import org.jaxen.XPathFunctionContext; +import net.sourceforge.pmd.PMDVersion; import net.sourceforge.pmd.lang.ast.Node; import net.sourceforge.pmd.lang.ast.xpath.Attribute; import net.sourceforge.pmd.lang.java.ast.TypeNode; +@Deprecated public class TypeOfFunction implements Function { + private static final Logger LOG = Logger.getLogger(TypeOfFunction.class.getName()); + private static boolean deprecationWarned = false; + public static void registerSelfInSimpleContext() { ((SimpleFunctionContext) XPathFunctionContext.getInstance()).registerFunction(null, "typeof", new TypeOfFunction()); } + @Override public Object call(Context context, List args) throws FunctionCallException { + nagDeprecatedFunction(); String nodeTypeName = null; String fullTypeName = null; @@ -57,8 +65,28 @@ public Object call(Context context, List args) throws FunctionCallException { return typeof(n, nodeTypeName, fullTypeName, shortTypeName); } - // TEST //ClassOrInterfaceType[typeof(@Image, 'java.lang.String')] + private static void nagDeprecatedFunction() { + if (!deprecationWarned) { + deprecationWarned = true; + LOG.warning("The XPath function typeof() is deprecated and will be removed in " + + PMDVersion.getNextMajorRelease() + ". Use typeIs() instead."); + } + } + + /** + * Example XPath 1.0: {@code //ClassOrInterfaceType[typeof(@Image, 'java.lang.String', 'String')]} + *

+ * Example XPath 2.0: {@code //ClassOrInterfaceType[pmd-java:typeof(@Image, 'java.lang.String', 'String')]} + * + * @param n + * @param nodeTypeName Usually the {@code @Image} attribute of the node + * @param fullTypeName The fully qualified name of the class or any supertype + * @param shortTypeName The simple class name, might be null + * @return + */ public static boolean typeof(Node n, String nodeTypeName, String fullTypeName, String shortTypeName) { + nagDeprecatedFunction(); + if (n instanceof TypeNode) { Class type = ((TypeNode) n).getType(); if (type == null) { diff --git a/pmd-java/src/main/resources/category/java/bestpractices.xml b/pmd-java/src/main/resources/category/java/bestpractices.xml new file mode 100644 index 00000000000..5d905a2e4ed --- /dev/null +++ b/pmd-java/src/main/resources/category/java/bestpractices.xml @@ -0,0 +1,1498 @@ + + + + + +Rules which enforce generally accepted best practices. + + + + +The abstract class does not contain any abstract methods. An abstract class suggests +an incomplete implementation, which is to be completed by subclasses implementing the +abstract methods. If the class is intended to be used as a base class only (not to be instantiated +directly) a protected constructor can be provided prevent direct instantiation. + + 3 + + + + + + + + + + + + + + +Instantiation by way of private constructors from outside of the constructor's class often causes the +generation of an accessor. A factory method, or non-privatization of the constructor can eliminate this +situation. The generated class file is actually an interface. It gives the accessing class the ability +to invoke a new hidden package scope constructor that takes the interface as a supplementary parameter. +This turns a private constructor effectively into one with package scope, and is challenging to discern. + + 3 + + + + + + + +When accessing a private field / method from another class, the Java compiler will generate a accessor methods +with package-private visibility. This adds overhead, and to the dex method count on Android. This situation can +be avoided by changing the visibility of the field / method from private to package-private. + + 3 + + + + + + + +Constructors and methods receiving arrays should clone objects and store the copy. +This prevents future changes from the user from affecting the original array. + + 3 + + + + + + + +Avoid printStackTrace(); use a logger call instead. + + 3 + + + + + + + + + + + + + + +Reassigning values to incoming parameters is not recommended. Use temporary local variables instead. + + 2 + + + + + + + +StringBuffers/StringBuilders can grow considerably, and so may become a source of memory leaks +if held within objects with long lifetimes. + + 3 + + + + + + + + + + + + + + +Application with hard-coded IP addresses can become impossible to deploy in some cases. +Externalizing IP adresses is preferable. + + 3 + + + + + + + +Always check the return values of navigation methods (next, previous, first, last) of a ResultSet. +If the value return is 'false', it should be handled properly. + + 3 + + + + + + + +Avoid constants in interfaces. Interfaces should define types, constants are implementation details +better placed in classes or enums. See Effective Java, item 19. + + 3 + + + + + + + + + + + + + + + +By convention, the default label should be the last label in a switch statement. + + 3 + + + + + + + + + + + + + + +Reports loops that can be safely replaced with the foreach syntax. The rule considers loops over +lists, arrays and iterators. A loop is safe to replace if it only uses the index variable to +access an element of the list or array, only has one update statement, and loops through *every* +element of the list or array left to right. + + 3 + + l) { + for (int i = 0; i < l.size(); i++) { // pre Java 1.5 + System.out.println(l.get(i)); + } + + for (String s : l) { // post Java 1.5 + System.out.println(s); + } + } +} +]]> + + + + + +Whenever using a log level, one should check if the loglevel is actually enabled, or +otherwise skip the associate String creation and manipulation. + + 2 + + + + + + + +In JUnit 3, test suites are indicated by the suite() method. In JUnit 4, suites are indicated +through the @RunWith(Suite.class) annotation. + + 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 5 introduced @AfterEach and @AfterAll annotations to execute methods after each test or after all tests in the class, respectively. + + 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 5 introduced @BeforeEach and @BeforeAll annotations to execute methods before each test or before all tests in the class, respectively. + + 3 + + + + + + + + + + + + + + +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 + + + + + + + + + + + + + + + + +JUnit assertions should include an informative message - i.e., use the three-argument version of +assertEquals(), not the two-argument version. + + 3 + + + + + + + +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 + + + + + $maximumAsserts] +]]> + + + + + + + + + + +JUnit tests should include at least one assertion. This makes the tests more robust, and using assert +with messages provide the developer a clearer idea of what the test does. + + 3 + + + + + + + +In JUnit4, use the @Test(expected) annotation to denote tests that should throw exceptions. + + 3 + + + + + + + +The use of implementation types (i.e., HashSet) as object references limits your ability to use alternate +implementations in the future as requirements change. Whenever available, referencing objects +by their interface types (i.e, Set) provides much more flexibility. + + 3 + + list = new ArrayList<>(); + + public HashSet getFoo() { + return new HashSet(); + } + + // preferred approach + private List list = new ArrayList<>(); + + public Set getFoo() { + return new HashSet(); + } +} +]]> + + + + + +Exposing internal arrays to the caller violates object encapsulation since elements can be +removed or replaced outside of the object that owns it. It is safer to return a copy of the array. + + 3 + + + + + + + + + Annotating overridden methods with @Override ensures at compile time that + the method really overrides one, which helps refactoring and clarifies intent. + + 3 + + + + + + + +Java allows the use of several variables declaration of the same type on one line. However, it +can lead to quite messy code. This rule looks for several declarations on the same line. + + 4 + + + + 1] + [$strictMode or count(distinct-values(VariableDeclarator/@BeginLine)) != count(VariableDeclarator)] +| +//FieldDeclaration + [count(VariableDeclarator) > 1] + [$strictMode or count(distinct-values(VariableDeclarator/@BeginLine)) != count(VariableDeclarator)] +]]> + + + + + + + + + + + + +Position literals first in comparisons, if the second argument is null then NullPointerExceptions +can be avoided, they will just return false. + + 3 + + + + + + + + + + + + + + +Position literals first in comparisons, if the second argument is null then NullPointerExceptions +can be avoided, they will just return false. + + 3 + + + + + + + + + + + + + + +Throwing a new exception from a catch block without passing the original exception into the +new exception will cause the original stack trace to be lost making it difficult to debug +effectively. + + 3 + + + + + + + +Consider replacing Enumeration usages with the newer java.util.Iterator + + 3 + + + + + + + + + + + + + + +Consider replacing Hashtable usage with the newer java.util.Map if thread safety is not required. + + 3 + + + //Type/ReferenceType/ClassOrInterfaceType[@Image='Hashtable'] + + + + + + + + + +Consider replacing Vector usages with the newer java.util.ArrayList if expensive thread-safe operations are not required. + + 3 + + + //Type/ReferenceType/ClassOrInterfaceType[@Image='Vector'] + + + + + + + + + +All switch statements should include a default option to catch any unspecified values. + + 3 + + + + + + + + + + + + + +References to System.(out|err).print are usually intended for debugging purposes and can remain in +the codebase even in production code. By using a logger one can enable/disable this behaviour at +will (and by priority) and avoid clogging the Standard out log. + + 2 + + + + + + + + + + + + + + +Avoid passing parameters to methods or constructors without actually referencing them in the method body. + + 3 + + + + + + + +Avoid unused import statements to prevent unwanted dependencies. +This rule will also find unused on demand imports, i.e. import com.foo.*. + + 4 + + + + + + + +Detects when a local variable is declared and/or assigned, but not used. + + 3 + + + + + + + +Detects when a private field is declared and/or assigned a value, but not used. + + 3 + + + + + + + +Unused Private Method detects when a private method is declared but is unused. + + 3 + + + + + + + +This rule detects JUnit assertions in object equality. These assertions should be made by more specific methods, like assertEquals. + + 3 + + + + + + + + + + + + + + +This rule detects JUnit assertions in object references equality. These assertions should be made by +more specific methods, like assertNull, assertNotNull. + + 3 + + + + + + + + + + + + + + +This rule detects JUnit assertions in object references equality. These assertions should be made +by more specific methods, like assertSame, assertNotSame. + + 3 + + + + + + + + + + + + + + +When asserting a value is the same as a literal or Boxed boolean, use assertTrue/assertFalse, instead of assertEquals. + + 3 + + + + + + + + + + + + + + +The isEmpty() method on java.util.Collection is provided to determine if a collection has any elements. +Comparing the value of size() to 0 does not convey intent as well as the isEmpty() method. + + 3 + + + + + + + +Java 5 introduced the varargs parameter declaration for methods and constructors. This syntactic +sugar provides flexibility for users of these methods and constructors, allowing them to avoid +having to deal with the creation of an array. + + 4 + + + + + + + + + + + + + diff --git a/pmd-java/src/main/resources/category/java/categories.properties b/pmd-java/src/main/resources/category/java/categories.properties new file mode 100644 index 00000000000..3189fd3ade9 --- /dev/null +++ b/pmd-java/src/main/resources/category/java/categories.properties @@ -0,0 +1,13 @@ +# +# BSD-style license; for more info see http://pmd.sourceforge.net/license.html +# + +rulesets.filenames=\ + category/java/bestpractices.xml,\ + category/java/codestyle.xml,\ + category/java/design.xml,\ + category/java/documentation.xml,\ + category/java/errorprone.xml,\ + category/java/multithreading.xml,\ + category/java/performance.xml,\ + category/java/security.xml diff --git a/pmd-java/src/main/resources/category/java/codestyle.xml b/pmd-java/src/main/resources/category/java/codestyle.xml new file mode 100644 index 00000000000..b5ded22baf9 --- /dev/null +++ b/pmd-java/src/main/resources/category/java/codestyle.xml @@ -0,0 +1,2108 @@ + + + + + + Rules which enforce a specific coding style. + + + + + Abstract classes should be named 'AbstractXXX'. + + 3 + + + + + + + + + + + + + + + + + + 3 + + + + + + + +Avoid using dollar signs in variable/method/class/interface names. + + 3 + + + + + + + Avoid using final local variables, turn them into fields. + 3 + + + + + + + + + + + + + + +Prefixing parameters by 'in' or 'out' pollutes the name of the parameters and reduces code readability. +To indicate whether or not a parameter will be modify in a method, its better to document method +behavior with Javadoc. + + 4 + + + + + + + + + + + + + + + + + +Do not use protected fields in final classes since they cannot be subclassed. +Clarify your intent by using private or package access modifiers instead. + + 3 + + + + + + + + + + + + + + +Do not use protected methods in most final classes since they cannot be subclassed. This should +only be allowed in final classes that extend other classes with protected methods (whose +visibility cannot be reduced). Clarify your intent by using private or package access modifiers instead. + + 3 + + + + + + + + + + + + + + +Unnecessary reliance on Java Native Interface (JNI) calls directly reduces application portability +and increases the maintenance burden. + + 2 + + + //Name[starts-with(@Image,'System.loadLibrary')] + + + + + + + + + +Methods that return boolean results should be named as predicate statements to denote this. +I.e, 'isReady()', 'hasValues()', 'canCommit()', 'willFail()', etc. Avoid the use of the 'get' +prefix for these methods. + + 4 + + + + + + + + + + + + + + + +It is a good practice to call super() in a constructor. If super() is not called but +another constructor (such as an overloaded constructor) is called, this rule will not report it. + + 3 + + + + 0 ] +/ClassOrInterfaceBody + /ClassOrInterfaceBodyDeclaration + /ConstructorDeclaration[ count (.//ExplicitConstructorInvocation)=0 ] +]]> + + + + + + + + + + + Configurable naming conventions for type declarations. This rule reports + type declarations which do not match the regex that applies to their + specific kind (e.g. enum or interface). Each regex can be configured through + properties. + + By default this rule uses the standard Java naming convention (Pascal case), + and reports utility class names not ending with 'Util'. + + 1 + + + + + + + +To avoid mistakes if we want that a Method, Constructor, Field or Nested class have a default access modifier +we must add a comment at the beginning of it's declaration. +By default the comment must be /* default */ or /* package */, if you want another, you have to provide a regular expression. +This rule ignores by default all cases that have a @VisibleForTesting annotation. Use the +property "ignoredAnnotations" to customize the recognized annotations. + + 3 + + + + + + + +Avoid negation within an "if" expression with an "else" clause. For example, rephrase: +`if (x != y) diff(); else same();` as: `if (x == y) same(); else diff();`. + +Most "if (x != y)" cases without an "else" are often return cases, so consistent use of this +rule makes the code easier to read. Also, this resolves trivial ordering problems, such +as "does the error case go first?" or "does the common case go first?". + + 3 + + + + + + + + Enforce a policy for braces on control statements. It is recommended to use braces on 'if ... else' + statements and loop statements, even if they are optional. This usually makes the code clearer, and + helps prepare the future when you need to add another statement. That said, this rule lets you control + which statements are required to have braces via properties. + + From 6.2.0 on, this rule supersedes WhileLoopMustUseBraces, ForLoopMustUseBraces, IfStmtMustUseBraces, + and IfElseStmtMustUseBraces. + + 3 + + + + + + + + + + + + + 1 + or (some $stmt (: in only the block statements until the next label :) + in following-sibling::BlockStatement except following-sibling::SwitchLabel[1]/following-sibling::BlockStatement + satisfies not($stmt/Statement/Block))] + ]]> + + + + + + + + + +Use explicit scoping instead of accidental usage of default package private level. +The rule allows methods and fields annotated with Guava's @VisibleForTesting. + + 3 + + + + + + + + + + + +Avoid importing anything from the package 'java.lang'. These classes are automatically imported (JLS 7.5.3). + + 4 + + + + + + + +Duplicate or overlapping import statements should be avoided. + + 4 + + + + + + + +Empty or auto-generated methods in an abstract class should be tagged as abstract. This helps to remove their inapproprate +usage by developers who should be implementing their own versions in the concrete subclasses. + + 1 + + + + + + + + + + + + + + No need to explicitly extend Object. + 4 + + + + + + + + + + + + + + +Fields should be declared at the top of the class, before any method declarations, constructors, initializers or inner classes. + + 3 + + + + + + + + + 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 + + + + + + + +Some for loops can be simplified to while loops, this makes them more concise. + + 3 + + + + + + + + + + + + + + +Avoid using 'for' statements without using curly braces. If the code formatting or +indentation is lost then it becomes difficult to separate the code being controlled +from the rest. + + 3 + + + //ForStatement[not(Statement/Block)] + + + + + + + + + + + Configurable naming conventions for formal parameters of methods and lambdas. + This rule reports formal parameters which do not match the regex that applies to their + specific kind (e.g. lambda parameter, or final formal parameter). Each regex can be + configured through properties. + + By default this rule uses the standard Java naming convention (Camel case). + + 1 + + lambda1 = s_str -> { }; + + // lambda parameters with an explicit type can be configured separately + Consumer lambda1 = (String str) -> { }; + + } + + } + ]]> + + + + + +Names for references to generic values should be limited to a single uppercase letter. + + 4 + + + + 1 + or + string:upper-case(@Image) != @Image +] +]]> + + + + + extends BaseDao { + // This is ok... +} + +public interface GenericDao { + // Also this +} + +public interface GenericDao { + // 'e' should be an 'E' +} + +public interface GenericDao { + // 'EF' is not ok. +} +]]> + + + + + + + Identical `catch` branches use up vertical space and increase the complexity of code without + adding functionality. It's better style to collapse identical branches into a single multi-catch + branch. + + 3 + + + + + + + +Avoid using if..else statements without using surrounding braces. If the code formatting +or indentation is lost then it becomes difficult to separate the code being controlled +from the rest. + + 3 + + + + + + + + + + + + + + +Avoid using if statements without using braces to surround the code block. If the code +formatting or indentation is lost then it becomes difficult to separate the code being +controlled from the rest. + + 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). + + 3 + + + + + + + +The Local Home interface of a Session EJB should be suffixed by 'LocalHome'. + + 4 + + + + + + + + + + + + + + +The Local Interface of a Session EJB should be suffixed by 'Local'. + + 4 + + + + + + + + + + + + + + +A local variable assigned only once can be declared final. + + 3 + + + + + + + + Configurable naming conventions for local variable declarations and other locally-scoped + variables. This rule reports variable declarations which do not match the regex that applies to their + specific kind (e.g. final variable, or catch-clause parameter). Each regex can be configured through + properties. + + By default this rule uses the standard Java naming convention (Camel case). + + 1 + + + + + + + +Fields, formal arguments, or local variable names that are too long can make the code difficult to follow. + + 3 + + + + + $minimum] +]]> + + + + + + + + + + +The EJB Specification states that any MessageDrivenBean or SessionBean should be suffixed by 'Bean'. + + 4 + + + + + + + + + + + + + + +A method argument that is never re-assigned within the method can be declared final. + + 3 + + + + + + + + Configurable naming conventions for method declarations. This rule reports + method declarations which do not match the regex that applies to their + specific kind (e.g. JUnit test or native method). Each regex can be + configured through properties. + + By default this rule uses the standard Java naming convention (Camel case). + + 1 + + + + + + + +Detects when a non-field has a name starting with 'm_'. This usually denotes a field and could be confusing. + + 3 + + + + + + + + + + + + + + +Detects when a class or interface does not have a package definition. + + 3 + + + //ClassOrInterfaceDeclaration[count(preceding::PackageDeclaration) = 0] + + + + + + + + + + Since Java 1.7, numeric literals can use underscores to separate digits. This rule enforces that + numeric literals above a certain length use these underscores to increase readability. + + The rule only supports decimal (base 10) literals for now. The acceptable length under which literals + are not required to have underscores is configurable via a property. Even under that length, underscores + that are misplaced (not making groups of 3 digits) are reported. + + 3 + + + + + + + + + + + + + + + + +A method should have only one exit point, and that should be the last statement in the method. + + 3 + + 0) { + return "hey"; // first exit + } + return "hi"; // second exit + } +} +]]> + + + + + +Detects when a package definition contains uppercase characters. + + 3 + + + //PackageDeclaration/Name[lower-case(@Image)!=@Image] + + + + + + + + + +Checks for variables that are defined before they might be used. A reference is deemed to be premature if it is created right before a block of code that doesn't use it that also has the ability to return or throw an exception. + + 3 + + + + + + + +Remote Interface of a Session EJB should not have a suffix. + + 4 + + + + + + + + + + + + + + +A Remote Home interface type of a Session EJB should be suffixed by 'Home'. + + 4 + + + + + + + + + + + + + + +Short Classnames with fewer than e.g. five characters are not recommended. + + 4 + + + + + + + + + + + + + + + +Method names that are very short are not helpful to the reader. + + 3 + + + + + + + + + + + + + + + +Fields, local variables, or parameter names that are very short are not helpful to the reader. + + 3 + + + + + + + + + + + + + + + + +Field names using all uppercase characters - Sun's Java naming conventions indicating constants - should +be declared as final. + + 3 + + + + + + + + + + + + + + +If you overuse the static import feature, it can make your program unreadable and +unmaintainable, polluting its namespace with all the static members you import. +Readers of your code (including you, a few months after you wrote it) will not know +which class a static member comes from (Sun 1.5 Language Guide). + + 3 + + + + + $maximumStaticImports] +]]> + + + + + + + + + + + + Avoid the use of value in annotations when it's the only element. + + 3 + + + + + + + + +This rule detects when a constructor is not necessary; i.e., when there is only one constructor and the +constructor is identical to the default constructor. The default constructor should has same access +modifier as the declaring class. In an enum type, the default constructor is implicitly private. + + 3 + + + + + + + +Import statements allow the use of non-fully qualified names. The use of a fully qualified name +which is covered by an import statement is redundant. Consider using the non-fully qualified name. + + 4 + + + + + + + +Avoid the creation of unnecessary local variables + + 3 + + + + + + + +Fields in interfaces and annotations are automatically `public static final`, and methods are `public abstract`. +Classes, interfaces or annotations nested in an interface or annotation are automatically `public static` +(all nested interfaces and annotations are automatically static). +Nested enums are automatically `static`. +For historical reasons, modifiers which are implied by the context are accepted by the compiler, but are superfluous. + + 3 + + + + + + + +Avoid the use of unnecessary return statements. + + 3 + + + + + + + Useless parentheses should be removed. + 4 + + + +1] + /PrimaryPrefix/Expression + [not(./CastExpression)] + [not(./ConditionalExpression)] + [not(./AdditiveExpression)] + [not(./AssignmentOperator)] +| +//Expression[not(parent::PrimaryPrefix)]/PrimaryExpression[count(*)=1] + /PrimaryPrefix/Expression +| +//Expression/ConditionalAndExpression/PrimaryExpression/PrimaryPrefix/Expression[ + count(*)=1 and + count(./CastExpression)=0 and + count(./EqualityExpression/MultiplicativeExpression)=0 and + count(./ConditionalExpression)=0 and + count(./ConditionalOrExpression)=0] +| +//Expression/ConditionalOrExpression/PrimaryExpression/PrimaryPrefix/Expression[ + count(*)=1 and + not(./CastExpression) and + not(./ConditionalExpression) and + not(./EqualityExpression/MultiplicativeExpression)] +| +//Expression/ConditionalExpression/PrimaryExpression/PrimaryPrefix/Expression[ + count(*)=1 and + not(./CastExpression) and + not(./EqualityExpression)] +| +//Expression/AdditiveExpression[not(./PrimaryExpression/PrimaryPrefix/Literal[@StringLiteral='true'])] + /PrimaryExpression[1]/PrimaryPrefix/Expression[ + count(*)=1 and + not(./CastExpression) and + not(./AdditiveExpression[@Image = '-']) and + not(./ShiftExpression) and + not(./RelationalExpression) and + not(./InstanceOfExpression) and + not(./EqualityExpression) and + not(./AndExpression) and + not(./ExclusiveOrExpression) and + not(./InclusiveOrExpression) and + not(./ConditionalAndExpression) and + not(./ConditionalOrExpression) and + not(./ConditionalExpression)] +| +//Expression/EqualityExpression/PrimaryExpression/PrimaryPrefix/Expression[ + count(*)=1 and + not(./CastExpression) and + not(./AndExpression) and + not(./InclusiveOrExpression) and + not(./ExclusiveOrExpression) and + not(./ConditionalExpression) and + not(./ConditionalAndExpression) and + not(./ConditionalOrExpression) and + not(./EqualityExpression)] +]]> + + + + + + + + + + + Reports qualified this usages in the same class. + + 3 + + + + + + + + + + + + + + +A variable naming conventions rule - customize this to your liking. Currently, it +checks for final variables that should be fully capitalized and non-final variables +that should not include underscores. + + 1 + + + + + + + +Avoid using 'while' statements without using braces to surround the code block. If the code +formatting or indentation is lost then it becomes difficult to separate the code being +controlled from the rest. + + 3 + + + //WhileStatement[not(Statement/Block)] + + + + + + + + diff --git a/pmd-java/src/main/resources/category/java/design.xml b/pmd-java/src/main/resources/category/java/design.xml new file mode 100644 index 00000000000..82a3534b0dc --- /dev/null +++ b/pmd-java/src/main/resources/category/java/design.xml @@ -0,0 +1,1642 @@ + + + + + +Rules that help you discover design issues. + + + + +If an abstract class does not provides any methods, it may be acting as a simple data container +that is not meant to be instantiated. In this case, it is probably better to use a private or +protected constructor in order to prevent instantiation than make the class misleadingly abstract. + + 1 + + + + + + + + + + + + + + +Avoid catching generic exceptions such as NullPointerException, RuntimeException, Exception in try-catch block + + 3 + + + + + + + + + + + + + + +Avoid creating deeply nested if-then statements since they are harder to read and error-prone to maintain. + + 3 + +y) { + if (y>z) { + if (z==x) { + // !! too deep + } + } + } + } +} +]]> + + + + + +Catch blocks that merely rethrow a caught exception only add to code size and runtime complexity. + + 3 + + + + + + + + + + + + + + +Catch blocks that merely rethrow a caught exception wrapped inside a new instance of the same type only add to +code size and runtime complexity. + + 3 + + + + + + + + + + + + + + +*Effective Java, 3rd Edition, Item 72: Favor the use of standard exceptions* +> +>Arguably, every erroneous method invocation boils down to an illegal argument or state, +but other exceptions are standardly used for certain kinds of illegal arguments and states. +If a caller passes null in some parameter for which null values are prohibited, convention dictates that +NullPointerException be thrown rather than IllegalArgumentException. + +To implement that, you are encouraged to use `java.util.Objects.requireNonNull()` +(introduced in Java 1.7). This method is designed primarily for doing parameter +validation in methods and constructors with multiple parameters. + +Your parameter validation could thus look like the following: +``` +public class Foo { + private String exampleValue; + + void setExampleValue(String exampleValue) { + // check, throw and assignment in a single standard call + this.exampleValue = Objects.requireNonNull(exampleValue, "exampleValue must not be null!"); + } + } +``` +]]> + + 1 + + + + + + + + + + + + + + +Avoid throwing certain exception types. Rather than throw a raw RuntimeException, Throwable, +Exception, or Error, use a subclassed exception or error instead. + + 1 + + + + + + + + + + + + + + +A class with only private constructors should be final, unless the private constructor +is invoked by a inner class. + + 1 + + + += 1 ] +[count(./ClassOrInterfaceBody/ClassOrInterfaceBodyDeclaration/ConstructorDeclaration[(@Public = 'true') or (@Protected = 'true') or (@PackagePrivate = 'true')]) = 0 ] +[not(.//ClassOrInterfaceDeclaration)] +]]> + + + + + + + + + + +Sometimes two consecutive 'if' statements can be consolidated by separating their conditions with a boolean short-circuit operator. + + 3 + + + + + + + + + + + + + + +This rule counts the number of unique attributes, local variables, and return types within an object. +A number higher than the specified threshold can indicate a high degree of coupling. + + 3 + + + + + + + = 10. +Additionnally, classes with many methods of moderate complexity get reported as well once the total of their +methods' complexities reaches 80, even if none of the methods was directly reported. + +Reported methods should be broken down into several smaller methods. Reported classes should probably be broken down +into subcomponents.]]> + + 3 + + + + + + + +Data Classes are simple data holders, which reveal most of their state, and +without complex functionality. The lack of functionality may indicate that +their behaviour is defined elsewhere, which is a sign of poor data-behaviour +proximity. By directly exposing their internals, Data Classes break encapsulation, +and therefore reduce the system's maintainability and understandability. Moreover, +classes tend to strongly rely on their data representation, which makes for a brittle +design. + +Refactoring a Data Class should focus on restoring a good data-behaviour proximity. In +most cases, that means moving the operations defined on the data back into the class. +In some other cases it may make sense to remove entirely the class and move the data +into the former client classes. + + 3 + + + + + + + +Errors are system exceptions. Do not extend them. + + 3 + + + + + + + + + + + + + + +Using Exceptions as form of flow control is not recommended as they obscure true exceptions when debugging. +Either add the necessary validation or use an alternate control structure. + + 3 + + + + + + + +Excessive class file lengths are usually indications that the class may be burdened with excessive +responsibilities that could be provided by external classes or functions. In breaking these methods +apart the code becomes more manageable and ripe for reuse. + + 3 + + + + + + + +A high number of imports can indicate a high degree of coupling within an object. This rule +counts the number of unique imports and reports a violation if the count is above the +user-specified threshold. + + 3 + + + + + + + +When methods are excessively long this usually indicates that the method is doing more than its +name/signature might suggest. They also become challenging for others to digest since excessive +scrolling causes readers to lose focus. +Try to reduce the method length by creating helper methods and removing any copy/pasted code. + + 3 + + + + + + + +Methods with numerous parameters are a challenge to maintain, especially if most of them share the +same datatype. These situations usually denote the need for new objects to wrap the numerous parameters. + + 3 + + + + + + + +Classes with large numbers of public methods and attributes require disproportionate testing efforts +since combinational side effects grow rapidly and increase risk. Refactoring these classes into +smaller ones not only increases testability and reliability but also allows new variations to be +developed easily. + + 3 + + + + + + + +If a final field is assigned to a compile-time constant, it could be made static, thus saving overhead +in each object at runtime. + + 3 + + + + + + + + + + + + + + +The God Class rule detects the God Class design flaw using metrics. God classes do too many things, +are very big and overly complex. They should be split apart to be more object-oriented. +The rule uses the detection strategy described in "Object-Oriented Metrics in Practice". +The violations are reported against the entire class. + +See also the references: + +Michele Lanza and Radu Marinescu. Object-Oriented Metrics in Practice: +Using Software Metrics to Characterize, Evaluate, and Improve the Design +of Object-Oriented Systems. Springer, Berlin, 1 edition, October 2006. Page 80. + + 3 + + + + +Identifies private fields whose values never change once they are initialized either in the declaration +of the field or by a constructor. This helps in converting existing classes to becoming immutable ones. + + 3 + + + + + + + +The Law of Demeter is a simple rule, that says "only talk to friends". It helps to reduce coupling between classes +or objects. + +See also the references: + +* Andrew Hunt, David Thomas, and Ward Cunningham. The Pragmatic Programmer. From Journeyman to Master. Addison-Wesley Longman, Amsterdam, October 1999.; +* K.J. Lieberherr and I.M. Holland. Assuring good style for object-oriented programs. Software, IEEE, 6(5):38–48, 1989.; +* <http://www.ccs.neu.edu/home/lieber/LoD.html> +* <http://en.wikipedia.org/wiki/Law_of_Demeter> + + 3 + + + + + + + +Use opposite operator instead of negating the whole expression with a logic complement operator. + + 3 + + + + + + + + += + return false; + } + + return true; +} +]]> + + + + + +Avoid using classes from the configured package hierarchy outside of the package hierarchy, +except when using one of the configured allowed classes. + + 3 + + + + + + + +Complexity directly affects maintenance costs is determined by the number of decision points in a method +plus one for the method entry. The decision points include 'if', 'while', 'for', and 'case labels' calls. +Generally, numbers ranging from 1-4 denote low complexity, 5-7 denote moderate complexity, 8-10 denote +high complexity, and 11+ is very high complexity. Modified complexity treats switch statements as a single +decision point. + + 3 + + + + + + + +This rule uses the NCSS (Non-Commenting Source Statements) algorithm to determine the number of lines +of code for a given constructor. NCSS ignores comments, and counts actual statements. Using this algorithm, +lines of code that are split are counted as one. + + 3 + + + + + + + +This rule uses the NCSS (Non-Commenting Source Statements) metric to determine the number of lines +of code in a class, method or constructor. NCSS ignores comments, blank lines, and only counts actual +statements. For more details on the calculation, see the documentation of +the [NCSS metric](/pmd_java_metrics_index.html#non-commenting-source-statements-ncss). + + 3 + + + + + + + +This rule uses the NCSS (Non-Commenting Source Statements) algorithm to determine the number of lines +of code for a given method. NCSS ignores comments, and counts actual statements. Using this algorithm, +lines of code that are split are counted as one. + + 3 + + + + + + + +This rule uses the NCSS (Non-Commenting Source Statements) algorithm to determine the number of lines +of code for a given type. NCSS ignores comments, and counts actual statements. Using this algorithm, +lines of code that are split are counted as one. + + 3 + + + + + + + +The NPath complexity of a method is the number of acyclic execution paths through that method. +While cyclomatic complexity counts the number of decision points in a method, NPath counts the number of +full paths from the beginning to the end of the block of the method. That metric grows exponentially, as +it multiplies the complexity of statements in the same block. For more details on the calculation, see the +documentation of the [NPath metric](/pmd_java_metrics_index.html#npath-complexity-npath). + +A threshold of 200 is generally considered the point where measures should be taken to reduce +complexity and increase readability. + + 3 + + + + + + + +A method/constructor shouldn't explicitly throw the generic java.lang.Exception, since it +is unclear which exceptions that can be thrown from the methods. It might be +difficult to document and understand such vague interfaces. Use either a class +derived from RuntimeException or a checked exception. + + 3 + + + + + + + + + + 3 + + + + + + + + + + + + + + +Avoid negation in an assertTrue or assertFalse test. + +For example, rephrase: + + assertTrue(!expr); + +as: + + assertFalse(expr); + + + 3 + + + + + + + + + + + + + + +Avoid unnecessary comparisons in boolean expressions, they serve no purpose and impacts readability. + + 3 + + + + + + + + + + + + + + +Avoid unnecessary if-then-else statements when returning a boolean. The result of +the conditional test can be returned instead. + + 3 + + + + + + + +No need to check for null before an instanceof; the instanceof keyword returns false when given a null argument. + + 3 + + + + + + + + + + + + + + +Fields whose scopes are limited to just single methods do not rely on the containing +object to provide them to other methods. They may be better implemented as local variables +within those methods. + + 3 + + + + + + + +Complexity directly affects maintenance costs is determined by the number of decision points in a method +plus one for the method entry. The decision points include 'if', 'while', 'for', and 'case labels' calls. +Generally, numbers ranging from 1-4 denote low complexity, 5-7 denote moderate complexity, 8-10 denote +high complexity, and 11+ is very high complexity. + + 3 + + + + + + + +A high ratio of statements to labels in a switch statement implies that the switch statement +is overloaded. Consider moving the statements into new methods or creating subclasses based +on the switch variable. + + 3 + + + + + + + +Classes that have too many fields can become unwieldy and could be redesigned to have fewer fields, +possibly through grouping related fields in new objects. For example, a class with individual +city/state/zip fields could park them within a single Address field. + + 3 + + + + + + + +A class with too many methods is probably a good suspect for refactoring, in order to reduce its +complexity and find a way to have more fine grained objects. + + 3 + + + + + + $maxmethods + ] +]]> + + + + + + + +The overriding method merely calls the same method defined in a superclass. + + 3 + + + + + + + +When you write a public method, you should be thinking in terms of an API. If your method is public, it means other class +will use it, therefore, you want (or need) to offer a comprehensive and evolutive API. If you pass a lot of information +as a simple series of Strings, you may think of using an Object to represent all those information. You'll get a simpler +API (such as doWork(Workload workload), rather than a tedious series of Strings) and more importantly, if you need at some +point to pass extra data, you'll be able to do so by simply modifying or extending Workload without any modification to +your API. + + 3 + + + + 3 +] +]]> + + + + + + + + + + +For classes that only have static methods, consider making them utility classes. +Note that this doesn't apply to abstract classes, since their subclasses may +well include non-static methods. Also, if you want this class to be a utility class, +remember to add a private constructor to prevent instantiation. +(Note, that this use was known before PMD 5.1.0 as UseSingleton). + + 3 + + + + + + diff --git a/pmd-java/src/main/resources/category/java/documentation.xml b/pmd-java/src/main/resources/category/java/documentation.xml new file mode 100644 index 00000000000..44ca7380037 --- /dev/null +++ b/pmd-java/src/main/resources/category/java/documentation.xml @@ -0,0 +1,144 @@ + + + + + +Rules that are related to code documentation. + + + + +A rule for the politically correct... we don't want to offend anyone. + + 3 + + + + + + + +Denotes whether comments are required (or unwanted) for specific language elements. + + 3 + + + + + + + +Determines whether the dimensions of non-header comments found are within the specified limits. + + 3 + + + + + + + +Uncommented Empty Constructor finds instances where a constructor does not +contain statements, but there is no comment. By explicitly commenting empty +constructors it is easier to distinguish between intentional (commented) +and unintentional empty constructors. + + 3 + + + + + + + + + + + + + + + +Uncommented Empty Method Body finds instances where a method body does not contain +statements, but there is no comment. By explicitly commenting empty method bodies +it is easier to distinguish between intentional (commented) and unintentional +empty methods. + + 3 + + + + + + + + + + + + + \ No newline at end of file diff --git a/pmd-java/src/main/resources/category/java/errorprone.xml b/pmd-java/src/main/resources/category/java/errorprone.xml new file mode 100644 index 00000000000..9c90817ac92 --- /dev/null +++ b/pmd-java/src/main/resources/category/java/errorprone.xml @@ -0,0 +1,3382 @@ + + + + + +Rules to detect constructs that are either broken, extremely confusing or prone to runtime errors. + + + + +Avoid assignments in operands; this can make code more complicated and harder to read. + + 3 + + + + + + + +Identifies a possible unsafe usage of a static field. + + 3 + + + + + + + +Methods such as getDeclaredConstructors(), getDeclaredConstructor(Class[]) and setAccessible(), +as the interface PrivilegedAction, allow for the runtime alteration of variable, class, or +method visibility, even if they are private. This violates the principle of encapsulation. + + 3 + + + + + + + + + + + + + + +Use of the term 'assert' will conflict with newer versions of Java since it is a reserved word. + + 2 + + + //VariableDeclaratorId[@Image='assert'] + + + + + + + + + +Using a branching statement as the last part of a loop may be a bug, and/or is confusing. +Ensure that the usage is not a bug, or consider using another approach. + + 2 + + 25) { + break; + } +} +]]> + + + + + +The method Object.finalize() is called by the garbage collector on an object when garbage collection determines +that there are no more references to the object. It should not be invoked by application logic. + +Note that Oracle has declared Object.finalize() as deprecated since JDK 9. + + 3 + + + + + + + +Code should never throw NullPointerExceptions under normal circumstances. A catch block may hide the +original error, causing other, more subtle problems later on. + + 3 + + + + + + + + + + + + + + +Catching Throwable errors is not recommended since its scope is very broad. It includes runtime issues such as +OutOfMemoryError that should be exposed and managed separately. + + 3 + + + + + + + +One might assume that the result of "new BigDecimal(0.1)" is exactly equal to 0.1, but it is actually +equal to .1000000000000000055511151231257827021181583404541015625. +This is because 0.1 cannot be represented exactly as a double (or as a binary fraction of any finite +length). Thus, the long value that is being passed in to the constructor is not exactly equal to 0.1, +appearances notwithstanding. + +The (String) constructor, on the other hand, is perfectly predictable: 'new BigDecimal("0.1")' is +exactly equal to 0.1, as one would expect. Therefore, it is generally recommended that the +(String) constructor be used in preference to this one. + + 3 + + + + + + + + + + + + + + +Code containing duplicate String literals can usually be improved by declaring the String as a constant field. + + 3 + + + + + + + +Use of the term 'enum' will conflict with newer versions of Java since it is a reserved word. + + 2 + + + //VariableDeclaratorId[@Image='enum'] + + + + + + + + + +It can be confusing to have a field name with the same name as a method. While this is permitted, +having information (field) and actions (method) is not clear naming. Developers versed in +Smalltalk often prefer this approach as the methods denote accessor methods. + + 3 + + + + + + + +It is somewhat confusing to have a field name matching the declaring class name. +This probably means that type and/or field names should be chosen more carefully. + + 3 + + + + + + + +Each caught exception type should be handled in its own catch clause. + + 3 + + + + + + + + + + + + + + +Avoid using hard-coded literals in conditional statements. By declaring them as static variables +or private members with descriptive names maintainability is enhanced. By default, the literals "-1" and "0" are ignored. +More exceptions can be defined with the property "ignoreMagicNumbers". + + 3 + + + + + + + + + + += 0) { } // alternative approach + + if (aDouble > 0.0) {} // magic number 0.0 + if (aDouble >= Double.MIN_VALUE) {} // preferred approach +} +]]> + + + + + +Statements in a catch block that invoke accessors on the exception without using the information +only add to code size. Either remove the invocation, or use the return result. + + 2 + + + + + + + + + + + + + + +The use of multiple unary operators may be problematic, and/or confusing. +Ensure that the intended usage is not a bug, or consider simplifying the expression. + + 2 + + + + + + + +Integer literals should not start with zero since this denotes that the rest of literal will be +interpreted as an octal value. + + 3 + + + + + + + +Avoid equality comparisons with Double.NaN. Due to the implicit lack of representation +precision when comparing floating point numbers these are likely to cause logic errors. + + 3 + + + + + + + + + + + + + + +If a class is a bean, or is referenced by a bean directly or indirectly it needs to be serializable. +Member variables need to be marked as transient, static, or have accessor methods in the class. Marking +variables as transient is the safest and easiest modification. Accessor methods should follow the Java +naming conventions, i.e. for a variable named foo, getFoo() and setFoo() accessor methods should be provided. + + 3 + + + + + + + +The null check is broken since it will throw a NullPointerException itself. +It is likely that you used || instead of && or vice versa. + + 2 + + + + + + + Super should be called at the start of the method + 3 + + + + + + + + + + + + + + +Super should be called at the end of the method + + 3 + + + + + + + + + + + + + + +The skip() method may skip a smaller number of bytes than requested. Check the returned value to find out if it was the case or not. + + 3 + + + + + + + +When deriving an array of a specific class from your Collection, one should provide an array of +the same class as the parameter of the toArray() method. Doing otherwise you will will result +in a ClassCastException. + + 3 + + + + + + + + + + + + + + +The java Manual says "By convention, classes that implement this interface should override +Object.clone (which is protected) with a public method." + + 3 + + + + + + + + + + + + + + +The method clone() should only be implemented if the class implements the Cloneable interface with the exception of +a final method that only throws CloneNotSupportedException. + +The rule can also detect, if the class implements or extends a Cloneable class. + + 3 + + + + + + + +If a class implements cloneable the return type of the method clone() must be the class name. That way, the caller +of the clone method doesn't need to cast the returned clone to the correct type. + +Note: This is only possible with Java 1.5 or higher. + + 3 + + + + + + + + + + + + + + +The method clone() should throw a CloneNotSupportedException. + + 3 + + + + + + + + + + + + + + +Ensure that resources (like Connection, Statement, and ResultSet objects) are always closed after use. + + 3 + + + + + + + +Use equals() to compare object references; avoid comparing them with ==. + + 3 + + + + + + + +Calling overridable methods during construction poses a risk of invoking methods on an incompletely +constructed object and can be difficult to debug. +It may leave the sub-class unable to construct its superclass or forced to replicate the construction +process completely within itself, losing the ability to call super(). If the default constructor +contains a call to an overridable method, the subclass may be completely uninstantiable. Note that +this includes method calls throughout the control flow graph - i.e., if a constructor Foo() calls a +private method bar() that calls a public method buz(), this denotes a problem. + + 1 + + + + + + + The dataflow analysis tracks local definitions, undefinitions and references to variables on different paths on the data flow. +From those informations there can be found various problems. + +1. UR - Anomaly: There is a reference to a variable that was not defined before. This is a bug and leads to an error. +2. DU - Anomaly: A recently defined variable is undefined. These anomalies may appear in normal source text. +3. DD - Anomaly: A recently defined variable is redefined. This is ominous but don't have to be a bug. + + 5 + + dd-anomaly + foo(buz); + buz = 2; +} // buz is undefined when leaving scope -> du-anomaly +]]> + + + + + +Calls to System.gc(), Runtime.getRuntime().gc(), and System.runFinalization() are not advised. Code should have the +same behavior whether the garbage collection is disabled using the option -Xdisableexplicitgc or not. +Moreover, "modern" jvms do a very good job handling garbage collections. If memory usage issues unrelated to memory +leaks develop within an application, it should be dealt with JVM options rather than within the code itself. + + 2 + + + + + + + + + + + + + + +Web applications should not call System.exit(), since only the web container or the +application server should stop the JVM. This rule also checks for the equivalent call Runtime.getRuntime().exit(). + + 3 + + + + + + + + + + + + + + +Extend Exception or RuntimeException instead of Throwable. + + 3 + + + + + + + + + + + + + + +Use Environment.getExternalStorageDirectory() instead of "/sdcard" + + 3 + + + //Literal[starts-with(@Image,'"/sdcard')] + + + + + + + + + +Throwing exceptions within a 'finally' block is confusing since they may mask other exceptions +or code defects. +Note: This is a PMD implementation of the Lint4j rule "A throw in a finally block" + + 4 + + + //FinallyStatement[descendant::ThrowStatement] + + + + + + + + + +Avoid importing anything from the 'sun.*' packages. These packages are not portable and are likely to change. + + 4 + + + + + + + +Don't use floating point for loop indices. If you must use floating point, use double +unless you're certain that float provides enough precision and you have a compelling +performance need (space or time). + + 3 + + + + + + + + + + + + + + +Empty Catch Block finds instances where an exception is caught, but nothing is done. +In most circumstances, this swallows an exception which should either be acted on +or reported. + + 3 + + + + + + + + + + + + + + + + +Empty finalize methods serve no purpose and should be removed. Note that Oracle has declared Object.finalize() as deprecated since JDK 9. + + 3 + + + + + + + + + + + + + + +Empty finally blocks serve no purpose and should be removed. + + 3 + + + + + + + + + + + + + + +Empty If Statement finds instances where a condition is checked but nothing is done about it. + + 3 + + + + + + + + + + + + + + +Empty initializers serve no purpose and should be removed. + + 3 + + + //Initializer/Block[count(*)=0] + + + + + + + + + +Empty block statements serve no purpose and should be removed. + + 3 + + + //BlockStatement/Statement/Block[count(*) = 0] + + + + + + + + + +An empty statement (or a semicolon by itself) that is not used as the sole body of a 'for' +or 'while' loop is probably a bug. It could also be a double semicolon, which has no purpose +and should be removed. + + 3 + + + + + + + + + + + + + + +Empty switch statements serve no purpose and should be removed. + + 3 + + + //SwitchStatement[count(*) = 1] + + + + + + + + + +Empty synchronized blocks serve no purpose and should be removed. + + 3 + + + //SynchronizedStatement/Block[1][count(*) = 0] + + + + + + + + + +Avoid empty try blocks - what's the point? + + 3 + + + + + + + + + + + + + + +Empty While Statement finds all instances where a while statement does nothing. +If it is a timing loop, then you should use Thread.sleep() for it; if it is +a while loop that does a lot in the exit expression, rewrite it to make it clearer. + + 3 + + + + + + + + + + + + + + +Tests for null should not use the equals() method. The '==' operator should be used instead. + + 1 + + + + + + + + + + + + + + +If the finalize() is implemented, its last action should be to call super.finalize. Note that Oracle has declared Object.finalize() as deprecated since JDK 9. + + 3 + + + + + + + + + + + + + + + +If the finalize() is implemented, it should do something besides just calling super.finalize(). Note that Oracle has declared Object.finalize() as deprecated since JDK 9. + + 3 + + + + + + + + + + + + + + +Methods named finalize() should not have parameters. It is confusing and most likely an attempt to +overload Object.finalize(). It will not be called by the VM. + +Note that Oracle has declared Object.finalize() as deprecated since JDK 9. + + 3 + + + +0]] +]]> + + + + + + + + + + +When overriding the finalize(), the new method should be set as protected. If made public, +other classes may invoke it at inappropriate times. + +Note that Oracle has declared Object.finalize() as deprecated since JDK 9. + + 3 + + + + + + + + + + + + + + +Avoid idempotent operations - they have no effect. + + 3 + + + + + + + +There is no need to import a type that lives in the same package. + + 3 + + + + + + + +Avoid instantiating an object just to call getClass() on it; use the .class public member instead. + + 4 + + + + + + + + + + + + + + +Check for messages in slf4j loggers with non matching number of arguments and placeholders. + + 5 + + + + + + + +Avoid jumbled loop incrementers - its usually a mistake, and is confusing even if intentional. + + 3 + + + + + + + + + + + + + +Some JUnit framework methods are easy to misspell. + + 3 + + + + + + + + + + + + + + +The suite() method in a JUnit test needs to be both public and static. + + 3 + + + + + + + + + + + + + + +In most cases, the Logger reference can be declared as static and final. + + 2 + + + + + + + + + + + + + + +Non-constructor methods should not have the same name as the enclosing class. + + 3 + + + + + + + +The null check here is misplaced. If the variable is null a NullPointerException will be thrown. +Either the check is useless (the variable will never be "null") or it is incorrect. + + 3 + + + + + + + + + + + + + + + + + +Switch statements without break or return statements for each case option +may indicate problematic behaviour. Empty cases are ignored as these indicate an intentional fall-through. + + 3 + + + + + + + + + + + + + + +Serializable classes should provide a serialVersionUID field. +The serialVersionUID field is also needed for abstract base classes. Each individual class in the inheritance +chain needs an own serialVersionUID field. See also [Should an abstract class have a serialVersionUID](https://stackoverflow.com/questions/893259/should-an-abstract-class-have-a-serialversionuid). + + 3 + + + + + + + + + + + + + + +A class that has private constructors and does not have any static methods or fields cannot be used. + + 3 + + + + + + + + + + + + + + +Normally only one logger is used in each class. + + 2 + + + + + + + +A non-case label (e.g. a named break/continue label) was present in a switch statement. +This legal, but confusing. It is easy to mix up the case labels and the non-case labels. + + 3 + + + //SwitchStatement//BlockStatement/Statement/LabeledStatement + + + + + + + + + +A non-static initializer block will be called any time a constructor is invoked (just prior to +invoking the constructor). While this is a valid language construct, it is rarely used and is +confusing. + + 3 + + + + + + + + + + + + + + +Assigning a "null" to a variable (outside of its declaration) is usually bad form. Sometimes, this type +of assignment is an indication that the programmer doesn't completely understand what is going on in the code. + +NOTE: This sort of assignment may used in some cases to dereference objects and encourage garbage collection. + + 3 + + + + + + + +Override both public boolean Object.equals(Object other), and public int Object.hashCode(), or override neither. Even if you are inheriting a hashCode() from a parent class, consider implementing hashCode and explicitly delegating to your superclass. + + 3 + + + + + + + +Object clone() should be implemented with super.clone(). + + 2 + + + + 0 +] +]]> + + + + + + + + + + +A logger should normally be defined private static final and be associated with the correct class. +Private final Log log; is also allowed for rare cases where loggers need to be passed around, +with the restriction that the logger needs to be passed into the constructor. + + 3 + + + + + + + + + + + + + + + +For any method that returns an array, it is a better to return an empty array rather than a +null reference. This removes the need for null checking all results and avoids inadvertent +NullPointerExceptions. + + 1 + + + + + + + + + + + + + + +Avoid returning from a finally block, this can discard exceptions. + + 3 + + + //FinallyStatement//ReturnStatement + + + + + + + + + +Be sure to specify a Locale when creating SimpleDateFormat instances to ensure that locale-appropriate +formatting is used. + + 3 + + + + + + + + + + + + + + +Some classes contain overloaded getInstance. The problem with overloaded getInstance methods +is that the instance created using the overloaded method is not cached and so, +for each call and new objects will be created for every invocation. + + 2 + + + + + + + +Some classes contain overloaded getInstance. The problem with overloaded getInstance methods +is that the instance created using the overloaded method is not cached and so, +for each call and new objects will be created for every invocation. + + 2 + + + + + + + +According to the J2EE specification, an EJB should not have any static fields +with write access. However, static read-only fields are allowed. This ensures proper +behavior especially when instances are distributed by the container on several JREs. + + 3 + + + + + + + + + + + + + + +Individual character values provided as initialization arguments will be converted into integers. +This can lead to internal buffer sizes that are larger than expected. Some examples: + +``` +new StringBuffer() // 16 +new StringBuffer(6) // 6 +new StringBuffer("hello world") // 11 + 16 = 27 +new StringBuffer('A') // chr(A) = 65 +new StringBuffer("A") // 1 + 16 = 17 + +new StringBuilder() // 16 +new StringBuilder(6) // 6 +new StringBuilder("hello world") // 11 + 16 = 27 +new StringBuilder('C') // chr(C) = 67 +new StringBuilder("A") // 1 + 16 = 17 +``` + + 4 + + + + + + + + + + + + + + +The method name and parameter number are suspiciously close to equals(Object), which can denote an +intention to override the equals(Object) method. + + 2 + + + + + + + + + + + + + + +The method name and return type are suspiciously close to hashCode(), which may denote an intention +to override the hashCode() method. + + 3 + + + + + + + +A suspicious octal escape sequence was found inside a String literal. +The Java language specification (section 3.10.6) says an octal +escape sequence inside a literal String shall consist of a backslash +followed by: + + OctalDigit | OctalDigit OctalDigit | ZeroToThree OctalDigit OctalDigit + +Any octal escape sequence followed by non-octal digits can be confusing, +e.g. "\038" is interpreted as the octal escape sequence "\03" followed by +the literal character "8". + + 3 + + + + + + + +Test classes end with the suffix Test. Having a non-test class with that name is not a good practice, +since most people will assume it is a test case. Test classes have test methods named testXXX. + + 3 + + + + + + + +Do not use "if" statements whose conditionals are always true or always false. + + 3 + + + + + + + + + + + + + + +A JUnit test assertion with a boolean literal is unnecessary since it always will evaluate to the same thing. +Consider using flow control (in case of assertTrue(false) or similar) or simply removing +statements like assertTrue(true) and assertFalse(false). If you just want a test to halt after finding +an error, use the fail() method and provide an indication message of why it did. + + 3 + + + + + + + + + + + + + + +Using equalsIgnoreCase() is faster than using toUpperCase/toLowerCase().equals() + + 3 + + + + + + + +Avoid the use temporary objects when converting primitives to Strings. Use the static conversion methods +on the wrapper classes instead. + + 3 + + + + + + + +After checking an object reference for null, you should invoke equals() on that object rather than passing it to another object's equals() method. + + 3 + + + + + + + + + + + + + + +To make sure the full stacktrace is printed out, use the logging statement with two arguments: a String and a Throwable. + + 3 + + + + + + + + + + + + + + +Using '==' or '!=' to compare strings only works if intern version is used on both sides. +Use the equals() method instead. + + 3 + + + + + + + + + + + + + + +An operation on an Immutable object (String, BigDecimal or BigInteger) won't change the object itself +since the result of the operation is a new object. Therefore, ignoring the operation result is an error. + + 3 + + + + + + + +When doing String.toLowerCase()/toUpperCase() conversions, use Locales to avoids problems with languages that +have unusual conventions, i.e. Turkish. + + 3 + + + + + + + + + + + + + + +In J2EE, the getClassLoader() method might not work as expected. Use +Thread.currentThread().getContextClassLoader() instead. + + 3 + + + //PrimarySuffix[@Image='getClassLoader'] + + + + + + + + diff --git a/pmd-java/src/main/resources/category/java/multithreading.xml b/pmd-java/src/main/resources/category/java/multithreading.xml new file mode 100644 index 00000000000..e936aedede0 --- /dev/null +++ b/pmd-java/src/main/resources/category/java/multithreading.xml @@ -0,0 +1,363 @@ + + + + + +Rules that flag issues when dealing with multiple threads of execution. + + + + +Method-level synchronization can cause problems when new code is added to the method. +Block-level synchronization helps to ensure that only the code that needs synchronization +gets it. + + 3 + + + //MethodDeclaration[@Synchronized='true'] + + + + + + + + + +Avoid using java.lang.ThreadGroup; although it is intended to be used in a threaded environment +it contains methods that are not thread-safe. + + 3 + + + + + + + + + + + + + + +Use of the keyword 'volatile' is generally used to fine tune a Java application, and therefore, requires +a good expertise of the Java Memory Model. Moreover, its range of action is somewhat misknown. Therefore, +the volatile keyword should not be used for maintenance purpose and portability. + + 2 + + + //FieldDeclaration[contains(@Volatile,'true')] + + + + + + + + + +The J2EE specification explicitly forbids the use of threads. + + 3 + + + //ClassOrInterfaceType[@Image = 'Thread' or @Image = 'Runnable'] + + + + + + + + + +Explicitly calling Thread.run() method will execute in the caller's thread of control. Instead, call Thread.start() for the intended behavior. + + 4 + + + + + + + + + + + + + + +Partially created objects can be returned by the Double Checked Locking pattern when used in Java. +An optimizing JRE may assign a reference to the baz variable before it calls the constructor of the object the +reference points to. + +Note: With Java 5, you can make Double checked locking work, if you declare the variable to be `volatile`. + +For more details refer to: <http://www.javaworld.com/javaworld/jw-02-2001/jw-0209-double.html> +or <http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html> + + 1 + + + + + + + +Non-thread safe singletons can result in bad state changes. Eliminate +static singletons if possible by instantiating the object directly. Static +singletons are usually not needed as only a single instance exists anyway. +Other possible fixes are to synchronize the entire method or to use an +[initialize-on-demand holder class](https://en.wikipedia.org/wiki/Initialization-on-demand_holder_idiom). + +Refrain from using the double-checked locking pattern. The Java Memory Model doesn't +guarantee it to work unless the variable is declared as `volatile`, adding an uneeded +performance penalty. [Reference](http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html) + +See Effective Java, item 48. + + 3 + + + + + + + +SimpleDateFormat instances are not synchronized. Sun recommends using separate format instances +for each thread. If multiple threads must access a static formatter, the formatter must be +synchronized either on method or block level. + + 3 + + + + + + + +Since Java5 brought a new implementation of the Map designed for multi-threaded access, you can +perform efficient map reads without blocking other threads. + + 3 + + + + + + + + + + + + + + +Thread.notify() awakens a thread monitoring the object. If more than one thread is monitoring, then only +one is chosen. The thread chosen is arbitrary; thus its usually safer to call notifyAll() instead. + + 3 + + + + + + + + + + + + + \ No newline at end of file diff --git a/pmd-java/src/main/resources/category/java/performance.xml b/pmd-java/src/main/resources/category/java/performance.xml new file mode 100644 index 00000000000..fa8011af935 --- /dev/null +++ b/pmd-java/src/main/resources/category/java/performance.xml @@ -0,0 +1,998 @@ + + + + + +Rules that flag suboptimal code. + + + + +The conversion of literals to strings by concatenating them with empty strings is inefficient. +It is much better to use one of the type-specific toString() methods instead. + + 3 + + + + + + + + + + + + + + +Avoid concatenating characters as strings in StringBuffer/StringBuilder.append methods. + + 3 + + + + + + + +Instead of manually copying data between two arrays, use the efficient Arrays.copyOf or System.arraycopy method instead. + + 3 + + + + + + + + + + + + + + +The FileInputStream and FileOutputStream classes contains a finalizer method which will cause garbage collection pauses. See [JDK-8080225](https://bugs.openjdk.java.net/browse/JDK-8080225) for details. + +The FileReader and FileWriter constructors instantiate FileInputStream and FileOutputStream, again causing garbage collection issues while finalizer methods are called. + +* Use `Files.newInputStream(Paths.get(fileName))` instead of `new FileInputStream(fileName)`. +* Use `Files.newOutputStream(Paths.get(fileName))` instead of `new FileOutputStream(fileName)`. +* Use `Files.newBufferedReader(Paths.get(fileName))` instead of `new FileReader(fileName)`. +* Use `Files.newBufferedWriter(Paths.get(fileName))` instead of `new FileWriter(fileName)`. + + 1 + + + + + + + + + + + + + + +New objects created within loops should be checked to see if they can created outside them and reused. + + 3 + + + + + + + +Java uses the 'short' type to reduce memory usage, not to optimize calculation. In fact, the JVM does not have any +arithmetic capabilities for the short type: the JVM must convert the short into an int, do the proper calculation +and convert the int back to a short. Thus any storage gains found through use of the 'short' type may be offset by +adverse impacts on performance. + + 1 + + + + + + + + + + + + + + +Don't create instances of already existing BigInteger (BigInteger.ZERO, BigInteger.ONE) and +for Java 1.5 onwards, BigInteger.TEN and BigDecimal (BigDecimal.ZERO, BigDecimal.ONE, BigDecimal.TEN) + + 3 + + + + + + + +Avoid instantiating Boolean objects; you can reference Boolean.TRUE, Boolean.FALSE, or call Boolean.valueOf() instead. +Note that new Boolean() is deprecated since JDK 9 for that reason. + + 2 + + + + + + + +Calling new Byte() causes memory allocation that can be avoided by the static Byte.valueOf(). +It makes use of an internal cache that recycles earlier instances making it more memory efficient. +Note that new Byte() is deprecated since JDK 9 for that reason. + + 2 + + + + + + + + + + + + + + +Consecutive calls to StringBuffer/StringBuilder .append should be chained, reusing the target object. This can improve the performance +by producing a smaller bytecode, reducing overhead and improving inlining. A complete analysis can be found [here](https://github.com/pmd/pmd/issues/202#issuecomment-274349067) + + 3 + + + + + + + +Consecutively calling StringBuffer/StringBuilder.append(...) with literals should be avoided. +Since the literals are constants, they can already be combined into a single String literal and this String +can be appended in a single method call. + + 3 + + + + + + + + + + 3 + + 0) { + doSomething(); + } +} +]]> + + + + + +Avoid concatenating non-literals in a StringBuffer constructor or append() since intermediate buffers will +need to be be created and destroyed by the JVM. + + 3 + + + + + + + +Failing to pre-size a StringBuffer or StringBuilder properly could cause it to re-size many times +during runtime. This rule attempts to determine the total number the characters that are actually +passed into StringBuffer.append(), but represents a best guess "worst case" scenario. An empty +StringBuffer/StringBuilder constructor initializes the object to 16 characters. This default +is assumed if the length of the constructor can not be determined. + + 3 + + + + + + + +Calling new Integer() causes memory allocation that can be avoided by the static Integer.valueOf(). +It makes use of an internal cache that recycles earlier instances making it more memory efficient. +Note that new Integer() is deprecated since JDK 9 for that reason. + + 2 + + + + + + + + + + + + + + +Calling new Long() causes memory allocation that can be avoided by the static Long.valueOf(). +It makes use of an internal cache that recycles earlier instances making it more memory efficient. +Note that new Long() is deprecated since JDK 9 for that reason. + + 2 + + + + + + + + + + + + + + +Calls to a collection's `toArray(E[])` method should specify a target array of zero size. This allows the JVM +to optimize the memory allocation and copying as much as possible. + +Previous versions of this rule (pre PMD 6.0.0) suggested the opposite, but current JVM implementations +perform always better, when they have full control over the target array. And allocation an array via +reflection is nowadays as fast as the direct allocation. + +See also [Arrays of Wisdom of the Ancients](https://shipilev.net/blog/2016/arrays-wisdom-ancients/) + +Note: If you don't need an array of the correct type, then the simple `toArray()` method without an array +is faster, but returns only an array of type `Object[]`. + + 3 + + + + + + + + + foos = getFoos(); + +// much better; this one allows the jvm to allocate an array of the correct size and effectively skip +// the zeroing, since each array element will be overridden anyways +Foo[] fooArray = foos.toArray(new Foo[0]); + +// inefficient, the array needs to be zeroed out by the jvm before it is handed over to the toArray method +Foo[] fooArray = foos.toArray(new Foo[foos.size()]); +]]> + + + + + +Java will initialize fields with known default values so any explicit initialization of those same defaults +is redundant and results in a larger class file (approximately three additional bytecode instructions per field). + + 3 + + + + + + + +Since it passes in a literal of length 1, calls to (string).startsWith can be rewritten using (string).charAt(0) +at the expense of some readability. + + 3 + + + + + + + + + + + + + + +Calling new Short() causes memory allocation that can be avoided by the static Short.valueOf(). +It makes use of an internal cache that recycles earlier instances making it more memory efficient. +Note that new Short() is deprecated since JDK 9 for that reason. + + 2 + + + + + + + + + + + + + + +Avoid instantiating String objects; this is usually unnecessary since they are immutable and can be safely shared. + + 2 + + + + + + + +Avoid calling toString() on objects already known to be string instances; this is unnecessary. + + 3 + + + + + + + +Switch statements are intended to be used to support complex branching behaviour. Using a switch for only a few +cases is ill-advised, since switches are not as easy to understand as if-then statements. In these cases use the +if-then statement to increase code readability. + + 3 + + + + + + + + + + + + + + + +Most wrapper classes provide static conversion methods that avoid the need to create intermediate objects +just to create the primitive forms. Using these avoids the cost of creating objects that also need to be +garbage-collected later. + + 3 + + + + + + + +ArrayList is a much better Collection implementation than Vector if thread-safe operation is not required. + + 3 + + + + 0] + //AllocationExpression/ClassOrInterfaceType + [@Image='Vector' or @Image='java.util.Vector'] +]]> + + + + + + + + + + +(Arrays.asList(...)) if that is inconvenient for you (e.g. because of concurrent access). +]]> + + 3 + + + + + + + + + l= new ArrayList<>(100); + for (int i=0; i< 100; i++) { + l.add(ints[i]); + } + for (int i=0; i< 100; i++) { + l.add(a[i].toString()); // won't trigger the rule + } + } +} +]]> + + + + + +Use String.indexOf(char) when checking for the index of a single character; it executes faster. + + 3 + + + + + + + +No need to call String.valueOf to append to a string; just use the valueOf() argument directly. + + 3 + + + + + + + +The use of the '+=' operator for appending strings causes the JVM to create and use an internal StringBuffer. +If a non-trivial number of these concatenations are being used then the explicit use of a StringBuilder or +threadsafe StringBuffer is recommended to avoid this. + + 3 + + + + + + + +Use StringBuffer.length() to determine StringBuffer length rather than using StringBuffer.toString().equals("") +or StringBuffer.toString().length() == ... + + 3 + + + + + + + + + diff --git a/pmd-java/src/main/resources/category/java/security.xml b/pmd-java/src/main/resources/category/java/security.xml new file mode 100644 index 00000000000..8c81f3b7c88 --- /dev/null +++ b/pmd-java/src/main/resources/category/java/security.xml @@ -0,0 +1,65 @@ + + + + + +Rules that flag potential security flaws. + + + + +Do not use hard coded values for cryptographic operations. Please store keys outside of source code. + + 3 + + + + + + + +Do not use hard coded initialization vector in cryptographic operations. Please use a randomly generated IV. + + 3 + + + + + + diff --git a/pmd-java/src/main/resources/rulesets/java/android.xml b/pmd-java/src/main/resources/rulesets/java/android.xml index 5988065afc6..03462bf230b 100644 --- a/pmd-java/src/main/resources/rulesets/java/android.xml +++ b/pmd-java/src/main/resources/rulesets/java/android.xml @@ -3,122 +3,15 @@ + xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd"> These rules deal with the Android SDK, mostly related to best practices. To get better results, make sure that the auxclasspath is defined for type resolution to work. - - Super should be called at the start of the method - 3 - - - - - - - - - - - - Super should be called at the end of the method - 3 - - - - - - - - - - - - - - Use Environment.getExternalStorageDirectory() instead of "/sdcard" - 3 - - - //Literal[starts-with(@Image,'"/sdcard')] - - - - - - + + + + diff --git a/pmd-java/src/main/resources/rulesets/java/basic.xml b/pmd-java/src/main/resources/rulesets/java/basic.xml index 05709ec878b..f7a216f9fd8 100644 --- a/pmd-java/src/main/resources/rulesets/java/basic.xml +++ b/pmd-java/src/main/resources/rulesets/java/basic.xml @@ -3,846 +3,40 @@ + xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd"> The Basic ruleset contains a collection of good practices which should be followed. - - -Avoid jumbled loop incrementers - its usually a mistake, and is confusing even if intentional. - - 3 - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - -Some for loops can be simplified to while loops, this makes them more concise. - - 3 - - - 1] - [not(LocalVariableDeclaration)] - [not(ForInit)] - [not(ForUpdate)] - [not(Type and Expression and Statement)] -]]> - - - - - - - - - -Override both public boolean Object.equals(Object other), and public int Object.hashCode(), or override neither. Even if you are inheriting a hashCode() from a parent class, consider implementing hashCode and explicitly delegating to your superclass. - - 3 - - - - - - - -Partially created objects can be returned by the Double Checked Locking pattern when used in Java. -An optimizing JRE may assign a reference to the baz variable before it calls the constructor of the object the -reference points to. - -Note: With Java 5, you can make Double checked locking work, if you declare the variable to be `volatile`. - -For more details refer to: <http://www.javaworld.com/javaworld/jw-02-2001/jw-0209-double.html> -or <http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html> - - 1 - - - - - - - -Avoid returning from a finally block, this can discard exceptions. - - 3 - - - - - - - - - - - - - - -Do not use "if" statements whose conditionals are always true or always false. - - 3 - - - - - - - - - - - - - - -Avoid instantiating Boolean objects; you can reference Boolean.TRUE, Boolean.FALSE, or call Boolean.valueOf() instead. - - 2 - - - - - - - -Sometimes two consecutive 'if' statements can be consolidated by separating their conditions with a boolean short-circuit operator. - - 3 - - - - - - - - - - - - - - -When deriving an array of a specific class from your Collection, one should provide an array of -the same class as the parameter of the toArray() method. Doing otherwise you will will result -in a ClassCastException. - - 3 - - - - - - - - - - - - - - - -One might assume that the result of "new BigDecimal(0.1)" is exactly equal to 0.1, but it is actually -equal to .1000000000000000055511151231257827021181583404541015625. -This is because 0.1 cannot be represented exactly as a double (or as a binary fraction of any finite -length). Thus, the long value that is being passed in to the constructor is not exactly equal to 0.1, -appearances notwithstanding. - -The (String) constructor, on the other hand, is perfectly predictable: 'new BigDecimal("0.1")' is -exactly equal to 0.1, as one would expect. Therefore, it is generally recommended that the -(String) constructor be used in preference to this one. - - 3 - - - - - - - - - - - - - - - -The null check here is misplaced. If the variable is null a NullPointerException will be thrown. -Either the check is useless (the variable will never be "null") or it is incorrect. - - 3 - - - - - - - - - - - - - - - - -Avoid using java.lang.ThreadGroup; although it is intended to be used in a threaded environment -it contains methods that are not thread-safe. - - 3 - - - - - - - - - - - - - - -The null check is broken since it will throw a NullPointerException itself. -It is likely that you used || instead of && or vice versa. - - 2 - - - - - - - -Don't create instances of already existing BigInteger (BigInteger.ZERO, BigInteger.ONE) and -for Java 1.5 onwards, BigInteger.TEN and BigDecimal (BigDecimal.ZERO, BigDecimal.ONE, BigDecimal.TEN) - - 3 - - - - - - - - - - 3 - - - - - - - - - - 3 - - - - - - - - - - - - - 3 - - - - - - - - - - 2 - - - - - - - No need to explicitly extend Object. - 4 - - - - - - - - - - - - - - The skip() method may skip a smaller number of bytes than requested. Check the returned value to find out if it was the case or not. - 3 - - - - - - - - - - 2 - - 25) { - break; - } -} - ]]> - - - - - -Explicitly calling Thread.run() method will execute in the caller's thread of control. Instead, call Thread.start() for the intended behavior. - - 4 - - - - - - - - - - - - - - -Don't use floating point for loop indices. If you must use floating point, use double -unless you're certain that float provides enough precision and you have a compelling -performance need (space or time). - - 3 - - - - - - - - - - - - - - - - - 3 - - - - - - - - - - diff --git a/pmd-java/src/main/resources/rulesets/java/braces.xml b/pmd-java/src/main/resources/rulesets/java/braces.xml index aa7be0d3130..e0ce7deec31 100644 --- a/pmd-java/src/main/resources/rulesets/java/braces.xml +++ b/pmd-java/src/main/resources/rulesets/java/braces.xml @@ -3,143 +3,15 @@ + xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd"> The Braces ruleset contains rules regarding the use and placement of braces. - - -Avoid using if statements without using braces to surround the code block. If the code -formatting or indentation is lost then it becomes difficult to separate the code being -controlled from the rest. - - 3 - - - - - - - - - - - - - - -Avoid using 'while' statements without using braces to surround the code block. If the code -formatting or indentation is lost then it becomes difficult to separate the code being -controlled from the rest. - - 3 - - - - - - - - - - - - - - -Avoid using if..else statements without using surrounding braces. If the code formatting -or indentation is lost then it becomes difficult to separate the code being controlled -from the rest. - - 3 - - - - - - - - - - - - - - -Avoid using 'for' statements without using curly braces. If the code formatting or -indentation is lost then it becomes difficult to separate the code being controlled -from the rest. - - 3 - - - - - - - - - - - + + + + + diff --git a/pmd-java/src/main/resources/rulesets/java/clone.xml b/pmd-java/src/main/resources/rulesets/java/clone.xml index 01350d2b5db..f662ff54734 100644 --- a/pmd-java/src/main/resources/rulesets/java/clone.xml +++ b/pmd-java/src/main/resources/rulesets/java/clone.xml @@ -3,215 +3,16 @@ + xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd"> The Clone Implementation ruleset contains a collection of rules that find questionable usages of the clone() method. - - -Object clone() should be implemented with super.clone(). - - 2 - - - - 0 -] - ]]> - - - - - - - + + + + + + - - -The method clone() should throw a CloneNotSupportedException. - - 3 - - - - - - - - - - - - - - -The method clone() should only be implemented if the class implements the Cloneable interface with the exception of a final method that only throws CloneNotSupportedException. - - 3 - - - - - - - - - - - - - - -If a class implements cloneable the return type of the method clone() must be the class name. That way, the caller -of the clone method doesn't need to cast the returned clone to the correct type. - -Note: This is only possible with Java 1.5 or higher. - - 3 - - - - - - - - - - - - - - -The java Manual says "By convention, classes that implement this interface should override -Object.clone (which is protected) with a public method." - - 3 - - - - - - - - - - \ No newline at end of file diff --git a/pmd-java/src/main/resources/rulesets/java/codesize.xml b/pmd-java/src/main/resources/rulesets/java/codesize.xml index 2d4be9bebd0..c4c2b1911ea 100644 --- a/pmd-java/src/main/resources/rulesets/java/codesize.xml +++ b/pmd-java/src/main/resources/rulesets/java/codesize.xml @@ -1,553 +1,28 @@ + xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd"> The Code Size ruleset contains rules that find problems related to code size or complexity. - - -The NPath complexity of a method is the number of acyclic execution paths through that method. -A threshold of 200 is generally considered the point where measures should be taken to reduce -complexity and increase readability. - - 3 - - r) { - doSomething(); - while (f < 5 ) { - anotherThing(); - f -= 27; - } - } else { - tryThis(); - } - } - } - if ( r - n > 45) { - while (doMagic()) { - findRabbits(); - } - } - try { - doSomethingDangerous(); - } catch (Exception ex) { - makeAmends(); - } finally { - dontDoItAgain(); - } -} - ]]> - - - - - -When methods are excessively long this usually indicates that the method is doing more than its -name/signature might suggest. They also become challenging for others to digest since excessive -scrolling causes readers to lose focus. -Try to reduce the method length by creating helper methods and removing any copy/pasted code. - - 3 - - - - - - - - -Methods with numerous parameters are a challenge to maintain, especially if most of them share the -same datatype. These situations usually denote the need for new objects to wrap the numerous parameters. - - 3 - - - - - - - - - -Excessive class file lengths are usually indications that the class may be burdened with excessive -responsibilities that could be provided by external classes or functions. In breaking these methods -apart the code becomes more manageable and ripe for reuse. - - 3 - - - - - - - - -Complexity directly affects maintenance costs is determined by the number of decision points in a method -plus one for the method entry. The decision points include 'if', 'while', 'for', and 'case labels' calls. -Generally, numbers ranging from 1-4 denote low complexity, 5-7 denote moderate complexity, 8-10 denote -high complexity, and 11+ is very high complexity. - - 3 - - - - - - - -Complexity directly affects maintenance costs is determined by the number of decision points in a method -plus one for the method entry. The decision points include 'if', 'while', 'for', and 'case labels' calls. -Generally, numbers ranging from 1-4 denote low complexity, 5-7 denote moderate complexity, 8-10 denote -high complexity, and 11+ is very high complexity. - - 3 - - - - - - - -Complexity directly affects maintenance costs is determined by the number of decision points in a method -plus one for the method entry. The decision points include 'if', 'while', 'for', and 'case labels' calls. -Generally, numbers ranging from 1-4 denote low complexity, 5-7 denote moderate complexity, 8-10 denote -high complexity, and 11+ is very high complexity. Modified complexity treats switch statements as a single -decision point. - - 3 - - - - - - - -Classes with large numbers of public methods and attributes require disproportionate testing efforts -since combinational side effects grow rapidly and increase risk. Refactoring these classes into -smaller ones not only increases testability and reliability but also allows new variations to be -developed easily. - - 3 - - - - - - - -Classes that have too many fields can become unwieldy and could be redesigned to have fewer fields, -possibly through grouping related fields in new objects. For example, a class with individual -city/state/zip fields could park them within a single Address field. - - 3 - - - - - - - -This rule uses the NCSS (Non-Commenting Source Statements) algorithm to determine the number of lines -of code for a given method. NCSS ignores comments, and counts actual statements. Using this algorithm, -lines of code that are split are counted as one. - - 3 - - - - - - - -This rule uses the NCSS (Non-Commenting Source Statements) algorithm to determine the number of lines -of code for a given type. NCSS ignores comments, and counts actual statements. Using this algorithm, -lines of code that are split are counted as one. - - 3 - - - - - - -This rule uses the NCSS (Non-Commenting Source Statements) algorithm to determine the number of lines -of code for a given constructor. NCSS ignores comments, and counts actual statements. Using this algorithm, -lines of code that are split are counted as one. - - 3 - - - - - - - - This rule uses the NCSS (Non-Commenting Source Statements) metric to determine the number of lines - of code in a class, method or constructor. NCSS ignores comments, blank lines, and only counts actual - statements. For more details on the calculation, see the documentation of - the [NCSS metric](/pmd_java_metrics_index.html#non-commenting-source-statements-ncss). - - 3 - - - - - - - -A class with too many methods is probably a good suspect for refactoring, in order to reduce its -complexity and find a way to have more fine grained objects. - - 3 - - - - - - $maxmethods - ] - ]]> - - - - + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/pmd-java/src/main/resources/rulesets/java/comments.xml b/pmd-java/src/main/resources/rulesets/java/comments.xml index 1a45408802c..0655e2081c6 100755 --- a/pmd-java/src/main/resources/rulesets/java/comments.xml +++ b/pmd-java/src/main/resources/rulesets/java/comments.xml @@ -3,123 +3,17 @@ + xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd"> Rules intended to catch errors related to code comments - - -Denotes whether comments are required (or unwanted) for specific language elements. - - 3 - - - - - - - -Determines whether the dimensions of non-header comments found are within the specified limits. - - 3 - - - - + + - - -A rule for the politically correct... we don't want to offend anyone. - - 3 - - - - - - - - To avoid mistakes if we want that a Method, Constructor, Field or Nested class have a default access modifier - we must add a comment at the beginning of it's declaration. - By default the comment must be /* default */, if you want another, you have to provide a regexp. - - 3 - - - - - - - - - - - + + + diff --git a/pmd-java/src/main/resources/rulesets/java/controversial.xml b/pmd-java/src/main/resources/rulesets/java/controversial.xml index 5c59d8f486f..769016eb02e 100644 --- a/pmd-java/src/main/resources/rulesets/java/controversial.xml +++ b/pmd-java/src/main/resources/rulesets/java/controversial.xml @@ -3,794 +3,43 @@ + xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd"> The Controversial ruleset contains rules that, for whatever reason, are considered controversial. They are held here to allow people to include them as they see fit within their custom rulesets. - - -This rule detects when a constructor is not necessary; i.e., when there is only one constructor, -it's public, has an empty body, and takes no arguments. - - 3 - - - - - - - - - - - - - - -Assigning a "null" to a variable (outside of its declaration) is usually bad form. Sometimes, this type -of assignment is an indication that the programmer doesn't completely understand what is going on in the code. - -NOTE: This sort of assignment may used in some cases to dereference objects and encourage garbage collection. - - 3 - - - - - - - -A method should have only one exit point, and that should be the last statement in the method. - - 3 - - 0) { - return "hey"; // first exit - } - return "hi"; // second exit - } -} - ]]> - - - - - -Avoid assignments in operands; this can make code more complicated and harder to read. - - 3 - - - - - - - -Each class should declare at least one constructor. - - 3 - - - - - - - - - - - - - - -Avoid importing anything from the 'sun.*' packages. These packages are not portable and are likely to change. - - 4 - - - - - - - -A suspicious octal escape sequence was found inside a String literal. -The Java language specification (section 3.10.6) says an octal -escape sequence inside a literal String shall consist of a backslash -followed by: - - OctalDigit | OctalDigit OctalDigit | ZeroToThree OctalDigit OctalDigit - -Any octal escape sequence followed by non-octal digits can be confusing, -e.g. "\038" is interpreted as the octal escape sequence "\03" followed by -the literal character "8". - - 3 - - - - - - - -It is a good practice to call super() in a constructor. If super() is not called but -another constructor (such as an overloaded constructor) is called, this rule will not report it. - - 3 - - - - 0 ] -/ClassOrInterfaceBody - /ClassOrInterfaceBodyDeclaration - /ConstructorDeclaration[ count (.//ExplicitConstructorInvocation)=0 ] - ]]> - - - - - - - - - - - - - -Use explicit scoping instead of accidental usage of default package private level. -The rule allows methods and fields annotated with Guava's @VisibleForTesting. - - 3 - - - - - - - - - The dataflow analysis tracks local definitions, undefinitions and references to variables on different paths on the data flow. -From those informations there can be found various problems. - -1. UR - Anomaly: There is a reference to a variable that was not defined before. This is a bug and leads to an error. -2. DU - Anomaly: A recently defined variable is undefined. These anomalies may appear in normal source text. -3. DD - Anomaly: A recently defined variable is redefined. This is ominous but don't have to be a bug. - - 5 - - dd-anomaly - foo(buz); - buz = 2; -} // buz is undefined when leaving scope -> du-anomaly -]]> - - - - - Avoid using final local variables, turn them into fields. - 3 - - - - - - - - - - -Java uses the 'short' type to reduce memory usage, not to optimize calculation. In fact, the JVM does not have any -arithmetic capabilities for the short type: the JVM must convert the short into an int, do the proper calculation -and convert the int back to a short. Thus any storage gains found through use of the 'short' type may be offset by -adverse impacts on performance. - - 1 - - - - - - - - - - - - - - -Use of the keyword 'volatile' is generally used to fine tune a Java application, and therefore, requires -a good expertise of the Java Memory Model. Moreover, its range of action is somewhat misknown. Therefore, -the volatile keyword should not be used for maintenance purpose and portability. - - 2 - - - - - - - - - - - - - - - -Unnecessary reliance on Java Native Interface (JNI) calls directly reduces application portability -and increases the maintenance burden. - - 2 - - - - - - - - - - - - - - - - - 3 - - - - - - - - - - - - - - -Calls to System.gc(), Runtime.getRuntime().gc(), and System.runFinalization() are not advised. Code should have the -same behavior whether the garbage collection is disabled using the option -Xdisableexplicitgc or not. -Moreover, "modern" jvms do a very good job handling garbage collections. If memory usage issues unrelated to memory -leaks develop within an application, it should be dealt with JVM options rather than within the code itself. - - 2 - - - - - - - - - - - - - - -Java allows the use of several variables declaration of the same type on one line. However, it -can lead to quite messy code. This rule looks for several declarations on the same line. - - 4 - - - - 1] - [$strictMode or count(distinct-values(VariableDeclarator/@BeginLine)) != count(VariableDeclarator)] - ]]> - - - - - - - - - - - - - - - 4 - - - - - - - - - - - - - -Avoid using hard-coded literals in conditional statements. By declaring them as static variables -or private members with descriptive names maintainability is enhanced. By default, the literals "-1" and "0" are ignored. -More exceptions can be defined with the property "ignoreMagicNumbers". - - 3 - - - - - - - - - - -= 0) { } // alternative approach - - if (aDouble > 0.0) {} // magic number 0.0 - if (aDouble >= Double.MIN_VALUE) {} // preferred approach -} -]]> - - - - - -When you write a public method, you should be thinking in terms of an API. If your method is public, it means other class -will use it, therefore, you want (or need) to offer a comprehensive and evolutive API. If you pass a lot of information -as a simple series of Strings, you may think of using an Object to represent all those information. You'll get a simpler -API (such as doWork(Workload workload), rather than a tedious series of Strings) and more importantly, if you need at some -point to pass extra data, you'll be able to do so by simply modifying or extending Workload without any modification to -your API. - - 3 - - - - 3 -] -]]> - - - - - - - - - - - -Since Java5 brought a new implementation of the Map designed for multi-threaded access, you can -perform efficient map reads without blocking other threads. - - 3 - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pmd-java/src/main/resources/rulesets/java/coupling.xml b/pmd-java/src/main/resources/rulesets/java/coupling.xml index 43c9a63a02c..43982f3afde 100644 --- a/pmd-java/src/main/resources/rulesets/java/coupling.xml +++ b/pmd-java/src/main/resources/rulesets/java/coupling.xml @@ -3,163 +3,18 @@ + xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd"> Rules which find instances of high or inappropriate coupling between objects and packages. - - -This rule counts the number of unique attributes, local variables, and return types within an object. -A number higher than the specified threshold can indicate a high degree of coupling. - - 3 - - + -public class Foo { - private Blah var1; - private Bar var2; - - //followed by many imports of unique objects - void ObjectC doWork() { - Bardo var55; - ObjectA var44; - ObjectZ var93; - return something; - } -} -]]> - - - - - -A high number of imports can indicate a high degree of coupling within an object. This rule -counts the number of unique imports and reports a violation if the count is above the -user-specified threshold. - - 3 - - - - - - - -The use of implementation types as object references limits your ability to use alternate -implementations in the future as requirements change. Whenever available, referencing objects -by their interface types provides much more flexibility. - - 3 - - list = new ArrayList<>(); - -public HashSet getFoo() { - return new HashSet(); -} - -// preferred approach -private List list = new ArrayList<>(); - -public Set getFoo() { - return new HashSet(); -} -]]> - - - - - -Avoid using classes from the configured package hierarchy outside of the package hierarchy, -except when using one of the configured allowed classes. - - 3 - - - - - - - -The Law of Demeter is a simple rule, that says "only talk to friends". It helps to reduce coupling between classes -or objects. - -See also the references: - -* Andrew Hunt, David Thomas, and Ward Cunningham. The Pragmatic Programmer. From Journeyman to Master. Addison-Wesley Longman, Amsterdam, October 1999.; -* K.J. Lieberherr and I.M. Holland. Assuring good style for object-oriented programs. Software, IEEE, 6(5):38–48, 1989.; -* <http://www.ccs.neu.edu/home/lieber/LoD.html> -* <http://en.wikipedia.org/wiki/Law_of_Demeter> - - 3 - - - - + + + + \ No newline at end of file diff --git a/pmd-java/src/main/resources/rulesets/java/design.xml b/pmd-java/src/main/resources/rulesets/java/design.xml index 31839ccf57e..d9be5f1edae 100644 --- a/pmd-java/src/main/resources/rulesets/java/design.xml +++ b/pmd-java/src/main/resources/rulesets/java/design.xml @@ -1,1976 +1,78 @@ + xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd"> - + The Design ruleset contains rules that flag suboptimal code implementations. Alternate approaches are suggested. - - - - -For classes that only have static methods, consider making them utility classes. -Note that this doesn't apply to abstract classes, since their subclasses may -well include non-static methods. Also, if you want this class to be a utility class, -remember to add a private constructor to prevent instantiation. -(Note, that this use was known before PMD 5.1.0 as UseSingleton). - 3 - - - - - - - -Avoid unnecessary if-then-else statements when returning a boolean. The result of -the conditional test can be returned instead. - - 3 - - - - - - - -Avoid unnecessary comparisons in boolean expressions, they serve no purpose and impacts readability. - - 3 - - - - - - - - - - - - - - -All switch statements should include a default option to catch any unspecified values. - - 3 - - - - - - - - - - - - - - -Avoid creating deeply nested if-then statements since they are harder to read and error-prone to maintain. - - 3 - -y) { - if (y>z) { - if (z==x) { - // !! too deep - } - } - } - } -} -]]> - - - - - -Reassigning values to incoming parameters is not recommended. Use temporary local variables instead. - - 2 - - - - - - - -A high ratio of statements to labels in a switch statement implies that the switch statement -is overloaded. Consider moving the statements into new methods or creating subclasses based -on the switch variable. - - 3 - - - - - - - -Calling overridable methods during construction poses a risk of invoking methods on an incompletely -constructed object and can be difficult to debug. -It may leave the sub-class unable to construct its superclass or forced to replicate the construction -process completely within itself, losing the ability to call super(). If the default constructor -contains a call to an overridable method, the subclass may be completely uninstantiable. Note that -this includes method calls throughout the control flow graph - i.e., if a constructor Foo() calls a -private method bar() that calls a public method buz(), this denotes a problem. - - 1 - - - - - - - -Instantiation by way of private constructors from outside of the constructor's class often causes the -generation of an accessor. A factory method, or non-privatization of the constructor can eliminate this -situation. The generated class file is actually an interface. It gives the accessing class the ability -to invoke a new hidden package scope constructor that takes the interface as a supplementary parameter. -This turns a private constructor effectively into one with package scope, and is challenging to discern. - - 3 - - - - - - - -If a final field is assigned to a compile-time constant, it could be made static, thus saving overhead -in each object at runtime. - - 3 - - - - - - - - - - - - - - - -Ensure that resources (like Connection, Statement, and ResultSet objects) are always closed after use. - - 3 - - - - - - - -A non-static initializer block will be called any time a constructor is invoked (just prior to -invoking the constructor). While this is a valid language construct, it is rarely used and is -confusing. - - 3 - - - - - - - - - - - - - - -By convention, the default label should be the last label in a switch statement. - - 3 - - - - - - - - - - - - - - -A non-case label (e.g. a named break/continue label) was present in a switch statement. -This legal, but confusing. It is easy to mix up the case labels and the non-case labels. - - 3 - - - - - - - - - - - - - - -Calls to a collection's toArray() method should specify target arrays sized to match the size of the -collection. Initial arrays that are too small are discarded in favour of new ones that have to be created -that are the proper size. - - 3 - - - - - - - - - - - - - - - -Avoid equality comparisons with Double.NaN. Due to the implicit lack of representation -precision when comparing floating point numbers these are likely to cause logic errors. - - 3 - - - - - - - - - - - - - - -Tests for null should not use the equals() method. The '==' operator should be used instead. - - 1 - - - - - - - - - - - - - - -Avoid negation within an "if" expression with an "else" clause. For example, rephrase: -`if (x != y) diff(); else same();` as: `if (x == y) same(); else diff();`. - -Most "if (x != y)" cases without an "else" are often return cases, so consistent use of this -rule makes the code easier to read. Also, this resolves trivial ordering problems, such -as "does the error case go first?" or "does the common case go first?". - - 3 - - - - - - - -Avoid instantiating an object just to call getClass() on it; use the .class public member instead. - - 4 - - - - - - - - - - - - - - -Avoid idempotent operations - they have no effect. - - 3 - - - - - - - - -Be sure to specify a Locale when creating SimpleDateFormat instances to ensure that locale-appropriate -formatting is used. - - 3 - - - - - - - - - - - - - - -Identifies private fields whose values never change once they are initialized either in the declaration -of the field or by a constructor. This helps in converting existing classes to becoming immutable ones. - - 3 - - - - - - - - -When doing String.toLowerCase()/toUpperCase() conversions, use Locales to avoids problems with languages that -have unusual conventions, i.e. Turkish. - - 3 - - - - - - - - - - - - - - -Do not use protected fields in final classes since they cannot be subclassed. -Clarify your intent by using private or package access modifiers instead. - - 3 - - - - - - - - - - - - - - -Identifies a possible unsafe usage of a static field. - - 3 - - - - - - - -A class that has private constructors and does not have any static methods or fields cannot be used. - - 3 - - - - - - - - - - - - - - -Method-level synchronization can cause problems when new code is added to the method. -Block-level synchronization helps to ensure that only the code that needs synchronization -gets it. - - 3 - - - - - - - - - - - - - - -Switch statements without break or return statements for each case option -may indicate problematic behaviour. Empty cases are ignored as these indicate an intentional fall-through. - - 3 - - - - - - - - - - - - - - -Thread.notify() awakens a thread monitoring the object. If more than one thread is monitoring, then only -one is chosen. The thread chosen is arbitrary; thus its usually safer to call notifyAll() instead. - - 3 - - - - - - - - - - - - - - -Each caught exception type should be handled in its own catch clause. - - 3 - - - - - - - - - - - - - - -The abstract class does not contain any abstract methods. An abstract class suggests -an incomplete implementation, which is to be completed by subclasses implementing the -abstract methods. If the class is intended to be used as a base class only (not to be instantiated -directly) a protected constructor can be provided prevent direct instantiation. - - 3 - - - - - - - - - - - - - -No need to check for null before an instanceof; the instanceof keyword returns false when given a null argument. - - 3 - - - - - - - - - - - - - - -Use equals() to compare object references; avoid comparing them with ==. - - 3 - - - - - - - -Position literals first in comparisons, if the second argument is null then NullPointerExceptions -can be avoided, they will just return false. - - 3 - - - - - - - - - - - - - - -Position literals first in comparisons, if the second argument is null then NullPointerExceptions -can be avoided, they will just return false. - - 3 - - - - - - - - - - - - - - - -Avoid the creation of unnecessary local variables - - 3 - - - - - - - -Non-thread safe singletons can result in bad state changes. Eliminate -static singletons if possible by instantiating the object directly. Static -singletons are usually not needed as only a single instance exists anyway. -Other possible fixes are to synchronize the entire method or to use an -[initialize-on-demand holder class](https://en.wikipedia.org/wiki/Initialization-on-demand_holder_idiom). - -Refrain from using the double-checked locking pattern. The Java Memory Model doesn't -guarantee it to work unless the variable is declared as `volatile`, adding an uneeded -performance penalty. [Reference](http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html) - -See Effective Java, item 48. - - 3 - - - - - -Some classes contain overloaded getInstance. The problem with overloaded getInstance methods -is that the instance created using the overloaded method is not cached and so, -for each call and new objects will be created for every invocation. - - 2 - - - - - - -Some classes contain overloaded getInstance. The problem with overloaded getInstance methods -is that the instance created using the overloaded method is not cached and so, -for each call and new objects will be created for every invocation. - - 2 - - - - - - -Uncommented Empty Method Body finds instances where a method body does not contain -statements, but there is no comment. By explicitly commenting empty method bodies -it is easier to distinguish between intentional (commented) and unintentional -empty methods. - - 3 - - - - - - - - - - - - - - -Uncommented Empty Constructor finds instances where a constructor does not -contain statements, but there is no comment. By explicitly commenting empty -constructors it is easier to distinguish between intentional (commented) -and unintentional empty constructors. - - 3 - - - - - - - - - - - - - - - -SimpleDateFormat instances are not synchronized. Sun recommends using separate format instances -for each thread. If multiple threads must access a static formatter, the formatter must be -synchronized either on method or block level. - - 3 - - - - - - - -Throwing a new exception from a catch block without passing the original exception into the -new exception will cause the original stack trace to be lost making it difficult to debug -effectively. - - 3 - - - - - - - -The isEmpty() method on java.util.Collection is provided to determine if a collection has any elements. -Comparing the value of size() to 0 does not convey intent as well as the isEmpty() method. - - 3 - - - - - - - -A class with only private constructors should be final, unless the private constructor -is invoked by a inner class. - - 1 - - - = 1 ] -[count(./ClassOrInterfaceBody/ClassOrInterfaceBodyDeclaration/ConstructorDeclaration[(@Public = 'true') or (@Protected = 'true') or (@PackagePrivate = 'true')]) = 0 ] -[not(.//ClassOrInterfaceDeclaration)] - ]]> - - - - - - - - -Empty or auto-generated methods in an abstract class should be tagged as abstract. This helps to remove their inapproprate -usage by developers who should be implementing their own versions in the concrete subclasses. - - 1 - - - - - - - - - - - - - - -Fields whose scopes are limited to just single methods do not rely on the containing -object to provide them to other methods. They may be better implemented as local variables -within those methods. - - 3 - - - - - -For any method that returns an array, it is a better to return an empty array rather than a -null reference. This removes the need for null checking all results and avoids inadvertent -NullPointerExceptions. - - 1 - - - - - - - - - - - - -If an abstract class does not provides any methods, it may be acting as a simple data container -that is not meant to be instantiated. In this case, it is probably better to use a private or -protected constructor in order to prevent instantiation than make the class misleadingly abstract. - - 1 - - - - - - - - - - - - - - -Switch statements are intended to be used to support complex branching behaviour. Using a switch for only a few -cases is ill-advised, since switches are not as easy to understand as if-then statements. In these cases use the -if-then statement to increase code readability. - - 3 - - - - - - - - - - - - - - - -Use opposite operator instead of negating the whole expression with a logic complement operator. - - 3 - - - - - - - - - = - return false; - } - - return true; -} - ]]> - - - - - -Java 5 introduced the varargs parameter declaration for methods and constructors. This syntactic -sugar provides flexibility for users of these methods and constructors, allowing them to avoid -having to deal with the creation of an array. - - 4 - - - - - - - - - - -Fields should be declared at the top of the class, before any method declarations, constructors, initializers or inner classes. - - 3 - - - - - - - -The God Class rule detects the God Class design flaw using metrics. God classes do too many things, -are very big and overly complex. They should be split apart to be more object-oriented. -The rule uses the detection strategy described in "Object-Oriented Metrics in Practice". -The violations are reported against the entire class. - -See also the references: - -Michele Lanza and Radu Marinescu. Object-Oriented Metrics in Practice: -Using Software Metrics to Characterize, Evaluate, and Improve the Design -of Object-Oriented Systems. Springer, Berlin, 1 edition, October 2006. Page 80. - - 3 - - - - -Do not use protected methods in most final classes since they cannot be subclassed. This should -only be allowed in final classes that extend other classes with protected methods (whose -visibility cannot be reduced). Clarify your intent by using private or package access modifiers instead. - - 3 - - - - - - - - - - - - - - -Avoid constants in interfaces. Interfaces should define types, constants are implementation details -better placed in classes or enums. See Effective Java, item 19. - - 3 - - - - - - - - - - - - - - - -When accessing a private field / method from another class, the Java compiler will generate a accessor methods -with package-private visibility. This adds overhead, and to the dex method count on Android. This situation can -be avoided by changing the visibility of the field / method from private to package-private. - - 3 - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pmd-java/src/main/resources/rulesets/java/empty.xml b/pmd-java/src/main/resources/rulesets/java/empty.xml index cf5789214fc..398c70d5b8b 100644 --- a/pmd-java/src/main/resources/rulesets/java/empty.xml +++ b/pmd-java/src/main/resources/rulesets/java/empty.xml @@ -3,389 +3,23 @@ + xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd"> The Empty Code ruleset contains rules that find empty statements of any kind (empty method, empty block statement, empty try or catch block,...). - - -Empty Catch Block finds instances where an exception is caught, but nothing is done. -In most circumstances, this swallows an exception which should either be acted on -or reported. - - 3 - - - - - - - - - - - - - - - - -Empty If Statement finds instances where a condition is checked but nothing is done about it. - - 3 - - - - - - - - - - - - - - -Empty While Statement finds all instances where a while statement does nothing. -If it is a timing loop, then you should use Thread.sleep() for it; if it is -a while loop that does a lot in the exit expression, rewrite it to make it clearer. - - 3 - - - - - - - - - - - - - - - -Avoid empty try blocks - what's the point? - - 3 - - - - - - - - - - - - - - -Empty finally blocks serve no purpose and should be removed. - - 3 - - - - - - - - - - - - - - - -Empty switch statements serve no purpose and should be removed. - - 3 - - - - - - - - - - - - - - -Empty synchronized blocks serve no purpose and should be removed. - - 3 - - - - - - - - - - - - - - - -An empty statement (or a semicolon by itself) that is not used as the sole body of a 'for' -or 'while' loop is probably a bug. It could also be a double semicolon, which has no purpose -and should be removed. - - 3 - - - - - - - - - - - - - - -Empty initializers serve no purpose and should be removed. - - 3 - - - - - - - - - - - - - - -Empty block statements serve no purpose and should be removed. - - 3 - - - - - - - - - - - - - - -An empty static initializer serve no purpose and should be removed. - - 3 - - - - - - - - - - - + + + + + + + + + + + + \ No newline at end of file diff --git a/pmd-java/src/main/resources/rulesets/java/finalizers.xml b/pmd-java/src/main/resources/rulesets/java/finalizers.xml index fe2ed9e0681..af33d58df08 100644 --- a/pmd-java/src/main/resources/rulesets/java/finalizers.xml +++ b/pmd-java/src/main/resources/rulesets/java/finalizers.xml @@ -3,200 +3,18 @@ + xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd"> These rules deal with different problems that can occur with finalizers. - - -Empty finalize methods serve no purpose and should be removed. - - 3 - - - - - - - - - - - + + + + + + + - - -If the finalize() is implemented, it should do something besides just calling super.finalize(). - - 3 - - - - - - - - - - - - - - -Methods named finalize() should not have parameters. It is confusing and most likely an attempt to -overload Object.finalize(). It will not be called by the VM. - - 3 - - - -0]] -]]> - - - - - - - - - - -If the finalize() is implemented, its last action should be to call super.finalize. - - 3 - - - - - - - - - - - - - - - -When overriding the finalize(), the new method should be set as protected. If made public, -other classes may invoke it at inappropriate times. - - 3 - - - - - - - - - - - - - - -The method Object.finalize() is called by the garbage collector on an object when garbage collection determines -that there are no more references to the object. It should not be invoked by application logic. - - 3 - - - - - - \ No newline at end of file + \ No newline at end of file diff --git a/pmd-java/src/main/resources/rulesets/java/imports.xml b/pmd-java/src/main/resources/rulesets/java/imports.xml index db98a901862..9558018e311 100644 --- a/pmd-java/src/main/resources/rulesets/java/imports.xml +++ b/pmd-java/src/main/resources/rulesets/java/imports.xml @@ -3,143 +3,19 @@ + xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd"> These rules deal with different problems that can occur with import statements. - - -Duplicate or overlapping import statements should be avoided. - - 4 - - - - + + - - -Avoid importing anything from the package 'java.lang'. These classes are automatically imported (JLS 7.5.3). - - 4 - - - - - - - -Avoid the use of unused import statements to prevent unwanted dependencies. - - 4 - - - - - - - -There is no need to import a type that lives in the same package. - - 3 - - - - - - - -If you overuse the static import feature, it can make your program unreadable and -unmaintainable, polluting its namespace with all the static members you import. -Readers of your code (including you, a few months after you wrote it) will not know -which class a static member comes from (Sun 1.5 Language Guide). - - 3 - - - - $maximumStaticImports] -]]> - - - - - - - -Import statements allow the use of non-fully qualified names. The use of a fully qualified name -which is covered by an import statement is redundant. Consider using the non-fully qualified name. - - 4 - - + + + + + diff --git a/pmd-java/src/main/resources/rulesets/java/j2ee.xml b/pmd-java/src/main/resources/rulesets/java/j2ee.xml index bc37c578446..6aca6fb3349 100644 --- a/pmd-java/src/main/resources/rulesets/java/j2ee.xml +++ b/pmd-java/src/main/resources/rulesets/java/j2ee.xml @@ -3,351 +3,23 @@ + xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd"> Rules specific to the use of J2EE implementations. - - -In J2EE, the getClassLoader() method might not work as expected. Use -Thread.currentThread().getContextClassLoader() instead. - - 3 - - - - - - - - - - - + + + + - - -The EJB Specification states that any MessageDrivenBean or SessionBean should be suffixed by 'Bean'. - - 4 - - - - - - - + + + + -public class MissingTheProperSuffix implements SessionBean {} // non-standard name - ]]> - - - - - -A Remote Home interface type of a Session EJB should be suffixed by 'Home'. - - 4 - - - - - - - - - - - - - - -The Local Interface of a Session EJB should be suffixed by 'Local'. - - 4 - - - - - - - - - - - - - - -The Local Home interface of a Session EJB should be suffixed by 'LocalHome'. - - 4 - - - - - - - - - - - - - - -Remote Interface of a Session EJB should not have a suffix. - - 4 - - - - - - - - - - - - - - -Web applications should not call System.exit(), since only the web container or the -application server should stop the JVM. This rule also checks for the equivalent call Runtime.getRuntime().exit(). - - 3 - - - - - - - - - - -According to the J2EE specification, an EJB should not have any static fields -with write access. However, static read-only fields are allowed. This ensures proper -behavior especially when instances are distributed by the container on several JREs. - - 3 - - - - - - - - - - -The J2EE specification explicitly forbids the use of threads. - - 3 - - - - - - - - - + diff --git a/pmd-java/src/main/resources/rulesets/java/javabeans.xml b/pmd-java/src/main/resources/rulesets/java/javabeans.xml index 6e7cf7c840d..241bf58d86c 100644 --- a/pmd-java/src/main/resources/rulesets/java/javabeans.xml +++ b/pmd-java/src/main/resources/rulesets/java/javabeans.xml @@ -3,82 +3,14 @@ + xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd"> The JavaBeans Ruleset catches instances of bean rules not being followed. - - - -If a class is a bean, or is referenced by a bean directly or indirectly it needs to be serializable. -Member variables need to be marked as transient, static, or have accessor methods in the class. Marking -variables as transient is the safest and easiest modification. Accessor methods should follow the Java -naming conventions, i.e. for a variable named foo, getFoo() and setFoo() accessor methods should be provided. - - 3 - - - - - - - -Serializable classes should provide a serialVersionUID field. - - 3 - - - - - - - - - - - + + + diff --git a/pmd-java/src/main/resources/rulesets/java/junit.xml b/pmd-java/src/main/resources/rulesets/java/junit.xml index 8529e3975a6..d122e26f815 100644 --- a/pmd-java/src/main/resources/rulesets/java/junit.xml +++ b/pmd-java/src/main/resources/rulesets/java/junit.xml @@ -3,446 +3,24 @@ + xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd"> These rules deal with different problems that can occur with JUnit tests. - - -The suite() method in a JUnit test needs to be both public and static. - - 3 - - - - - - - - - - - - - - -Some JUnit framework methods are easy to misspell. - - 3 - - - - - - - - - - - - - - -JUnit assertions should include an informative message - i.e., use the three-argument version of -assertEquals(), not the two-argument version. - - 3 - - - - - - - -JUnit tests should include at least one assertion. This makes the tests more robust, and using assert -with messages provide the developer a clearer idea of what the test does. - - 3 - - - - - - - -Test classes end with the suffix Test. Having a non-test class with that name is not a good practice, -since most people will assume it is a test case. Test classes have test methods named testXXX. - - 3 - - - - - - - -A JUnit test assertion with a boolean literal is unnecessary since it always will evaluate to the same thing. -Consider using flow control (in case of assertTrue(false) or similar) or simply removing -statements like assertTrue(true) and assertFalse(false). If you just want a test to halt after finding -an error, use the fail() method and provide an indication message of why it did. - - 3 - - - - - - - - - - - - - - -This rule detects JUnit assertions in object equality. These assertions should be made by more specific methods, like assertEquals. - - 3 - - - - - - - - - - - - - - -This rule detects JUnit assertions in object references equality. These assertions should be made -by more specific methods, like assertSame, assertNotSame. - - 3 - - - - - - - - - - - - - - -This rule detects JUnit assertions in object references equality. These assertions should be made by -more specific methods, like assertNull, assertNotNull. - - 3 - - - - - - - - - - - - - - -Avoid negation in an assertTrue or assertFalse test. - -For example, rephrase: - - assertTrue(!expr); - -as: - - assertFalse(expr); - - - 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. -Customize the maximum number of assertions used by this Rule to suit your needs. - - 3 - - - - $maximumAsserts] - ]]> - - - - - - - - - -When asserting a value is the same as a literal or Boxed boolean, use assertTrue/assertFalse, instead of assertEquals. - - 3 - - - - - - - - - - + + + + + + + + + + + + + + + diff --git a/pmd-java/src/main/resources/rulesets/java/logging-jakarta-commons.xml b/pmd-java/src/main/resources/rulesets/java/logging-jakarta-commons.xml index a5e0eae2bf1..9f23f2dfd09 100644 --- a/pmd-java/src/main/resources/rulesets/java/logging-jakarta-commons.xml +++ b/pmd-java/src/main/resources/rulesets/java/logging-jakarta-commons.xml @@ -3,145 +3,17 @@ + xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd"> The Jakarta Commons Logging ruleset contains a collection of rules that find questionable usages of that framework. - - -To make sure the full stacktrace is printed out, use the logging statement with two arguments: a String and a Throwable. - - 3 - - - - - - - + + + - - -A logger should normally be defined private static final and be associated with the correct class. -Private final Log log; is also allowed for rare cases where loggers need to be passed around, -with the restriction that the logger needs to be passed into the constructor. - - 3 - - - - - - - - - - - - - -When log messages are composed by concatenating strings, the whole section should be guarded -by a isDebugEnabled() check to avoid performance and memory issues. - - 3 - - - - - - - -Whenever using a log level, one should check if the loglevel is actually enabled, or -otherwise skip the associate String creation and manipulation. - - 2 - - - - + + + diff --git a/pmd-java/src/main/resources/rulesets/java/logging-java.xml b/pmd-java/src/main/resources/rulesets/java/logging-java.xml index c842516b61f..2bc200b5534 100644 --- a/pmd-java/src/main/resources/rulesets/java/logging-java.xml +++ b/pmd-java/src/main/resources/rulesets/java/logging-java.xml @@ -3,182 +3,20 @@ + xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd"> The Java Logging ruleset contains a collection of rules that find questionable usages of the logger. - - -Normally only one logger is used in each class. - - 2 - - - - + + + + - - -In most cases, the Logger reference can be declared as static and final. - - 2 - - - - - - - - - - - - - - -References to System.(out|err).print are usually intended for debugging purposes and can remain in -the codebase even in production code. By using a logger one can enable/disable this behaviour at -will (and by priority) and avoid clogging the Standard out log. - - 2 - - - - - - - - - - - - - - -Avoid printStackTrace(); use a logger call instead. - - 3 - - - - - - - - - - - - - - -Whenever using a log level, one should check if the loglevel is actually enabled, or -otherwise skip the associate String creation and manipulation. - - 2 - - - - - - - -Check for messages in slf4j loggers with non matching number of arguments and placeholders. - - 5 - - - - + + + + diff --git a/pmd-java/src/main/resources/rulesets/java/metrics.xml b/pmd-java/src/main/resources/rulesets/java/metrics.xml index 3b4fd0fcecf..a19ae5a8e97 100644 --- a/pmd-java/src/main/resources/rulesets/java/metrics.xml +++ b/pmd-java/src/main/resources/rulesets/java/metrics.xml @@ -1,161 +1,15 @@ - + - These are rules which use the Metrics Framework to calculate metrics. +The Metrics ruleset contains rules that flag large / complex code. These rules are alternative implementations to +those already existing in the Design ruleset kept apart until the Metrics framework matures. - - = 10. - Additionnally, classes with many methods of moderate complexity get reported as well once the total of their - methods' complexities reaches 80, even if none of the methods was directly reported. - - Reported methods should be broken down into several smaller methods. Reported classes should probably be broken down - into subcomponents.]]> - - 3 - - - - - - - - The NPath complexity of a method is the number of acyclic execution paths through that method. - While cyclomatic complexity counts the number of decision points in a method, NPath counts the number of - full paths from the beginning to the end of the block of the method. That metric grows exponentially, as - it multiplies the complexity of statements in the same block. For more details on the calculation, see the - documentation of the [NPath metric](/pmd_java_metrics_index.html#npath-complexity-npath). - - A threshold of 200 is generally considered the point where measures should be taken to reduce - complexity and increase readability. - - 3 - - - - - - - - Data Classes are simple data holders, which reveal most of their state, and - without complex functionality. The lack of functionality may indicate that - their behaviour is defined elsewhere, which is a sign of poor data-behaviour - proximity. By directly exposing their internals, Data Classes break encapsulation, - and therefore reduce the system's maintainability and understandability. Moreover, - classes tend to strongly rely on their data representation, which makes for a brittle - design. - - Refactoring a Data Class should focus on restoring a good data-behaviour proximity. In - most cases, that means moving the operations defined on the data back into the class. - In some other cases it may make sense to remove entirely the class and move the data - into the former client classes. - - 3 - - - - - - + + diff --git a/pmd-java/src/main/resources/rulesets/java/migrating.xml b/pmd-java/src/main/resources/rulesets/java/migrating.xml index 672b92fb21b..bc7681960a0 100644 --- a/pmd-java/src/main/resources/rulesets/java/migrating.xml +++ b/pmd-java/src/main/resources/rulesets/java/migrating.xml @@ -3,508 +3,30 @@ + xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd"> Contains rules about migrating from one JDK version to another. Don't use these rules directly, rather, use a wrapper ruleset such as migrating_to_13.xml. - - -Consider replacing Vector usages with the newer java.util.ArrayList if expensive thread-safe operations are not required. - - 3 - - - - - - - - - - - - - - -Consider replacing Hashtable usage with the newer java.util.Map if thread safety is not required. - - 3 - - - - - - - - - - - - - - -Consider replacing Enumeration usages with the newer java.util.Iterator - - 3 - - - - - - - - - - - - - - -Use of the term 'enum' will conflict with newer versions of Java since it is a reserved word. - - 2 - - - - - - - - - - - - - - -Use of the term 'assert' will conflict with newer versions of Java since it is a reserved word. - - 2 - - - - - - - - - - - - - - -Calling new Integer() causes memory allocation that can be avoided by the static Integer.valueOf(). -It makes use of an internal cache that recycles earlier instances making it more memory efficient. - - 2 - - - - - - - - - - - - - - -Calling new Byte() causes memory allocation that can be avoided by the static Byte.valueOf(). -It makes use of an internal cache that recycles earlier instances making it more memory efficient. - - 2 - - - - - - - - - - - - - - -Calling new Short() causes memory allocation that can be avoided by the static Short.valueOf(). -It makes use of an internal cache that recycles earlier instances making it more memory efficient. - - 2 - - - - - - - - - - - - - - -Calling new Long() causes memory allocation that can be avoided by the static Long.valueOf(). -It makes use of an internal cache that recycles earlier instances making it more memory efficient. - - 2 - - - - - - - - - - - - - - - -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 - - 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 - - 3 - - - - - - - - - - - - - - -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. - - 3 - - - - - - - - - - - - - - -In JUnit 3, test suites are indicated by the suite() method. In JUnit 4, suites are indicated -through the @RunWith(Suite.class) annotation. - - 3 - - - - - - - - - - - - - - -In JUnit4, use the @Test(expected) annotation to denote tests that should throw exceptions. - - 3 - - - - - - - - 3 - l) { - for (int i = 0; i < l.size(); i++) { // pre Java 1.5 - System.out.println(l.get(i)); - } - - for (String s : l) { // post Java 1.5 - System.out.println(s); - } - } -} - ]]> - + + + + + + + + + + + + + + + + + + diff --git a/pmd-java/src/main/resources/rulesets/java/migrating_to_13.xml b/pmd-java/src/main/resources/rulesets/java/migrating_to_13.xml index b63719861ff..8d84773244e 100644 --- a/pmd-java/src/main/resources/rulesets/java/migrating_to_13.xml +++ b/pmd-java/src/main/resources/rulesets/java/migrating_to_13.xml @@ -3,17 +3,14 @@ + xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd"> Contains rules for migrating to JDK 1.3 - - - + + + - - - diff --git a/pmd-java/src/main/resources/rulesets/java/migrating_to_14.xml b/pmd-java/src/main/resources/rulesets/java/migrating_to_14.xml index 201ba79afc7..8140a032d9e 100644 --- a/pmd-java/src/main/resources/rulesets/java/migrating_to_14.xml +++ b/pmd-java/src/main/resources/rulesets/java/migrating_to_14.xml @@ -3,12 +3,12 @@ + xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd"> Contains rules for migrating to JDK 1.4 - + diff --git a/pmd-java/src/main/resources/rulesets/java/migrating_to_15.xml b/pmd-java/src/main/resources/rulesets/java/migrating_to_15.xml index 97417efbab6..58f833176f5 100644 --- a/pmd-java/src/main/resources/rulesets/java/migrating_to_15.xml +++ b/pmd-java/src/main/resources/rulesets/java/migrating_to_15.xml @@ -3,18 +3,15 @@ + xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd"> Contains rules for migrating to JDK 1.5 - - - - - + + + + + - - - diff --git a/pmd-java/src/main/resources/rulesets/java/migrating_to_junit4.xml b/pmd-java/src/main/resources/rulesets/java/migrating_to_junit4.xml index e7a79ade1dc..ab9fccf7416 100644 --- a/pmd-java/src/main/resources/rulesets/java/migrating_to_junit4.xml +++ b/pmd-java/src/main/resources/rulesets/java/migrating_to_junit4.xml @@ -3,18 +3,15 @@ + xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd"> Contains rules for migrating to JUnit 4 - - - - - + + + + + - - - diff --git a/pmd-java/src/main/resources/rulesets/java/naming.xml b/pmd-java/src/main/resources/rulesets/java/naming.xml index e9c7b045861..64931996c48 100644 --- a/pmd-java/src/main/resources/rulesets/java/naming.xml +++ b/pmd-java/src/main/resources/rulesets/java/naming.xml @@ -3,597 +3,33 @@ + xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd"> The Naming Ruleset contains rules regarding preferred usage of names and identifiers. - - - -Fields, local variables, or parameter names that are very short are not helpful to the reader. - - 3 - - - - - - - - - - - - - - - -Fields, formal arguments, or local variable names that are too long can make the code difficult to follow. - - 3 - - - - - $minimum] -]]> - - - - - - - - - - -Method names that are very short are not helpful to the reader. - - 3 - - - - - - - - - - - - - - - -A variable naming conventions rule - customize this to your liking. Currently, it -checks for final variables that should be fully capitalized and non-final variables -that should not include underscores. - - 1 - - - - - - - -Method names should always begin with a lower case character, and should not contain underscores. - - 1 - - - - - - - -Class names should always begin with an upper case character. - - 1 - - - - - - - -Abstract classes should be named 'AbstractXXX'. - - 3 - - - - - - - - - - - - - - - -Avoid using dollar signs in variable/method/class/interface names. - - 3 - - - - - - - -Non-constructor methods should not have the same name as the enclosing class. - - 3 - - - - - - - -The method name and return type are suspiciously close to hashCode(), which may denote an intention -to override the hashCode() method. - - 3 - - - - - - - -Field names using all uppercase characters - Sun's Java naming conventions indicating constants - should -be declared as final. - - 3 - - - - - - - - - - - - - - -The method name and parameter number are suspiciously close to equals(Object), which can denote an -intention to override the equals(Object) method. - - 2 - - - - - - - - - - - - - - -It is somewhat confusing to have a field name matching the declaring class name. -This probably means that type and/or field names should be chosen more carefully. - - 3 - - - - - - - -It can be confusing to have a field name with the same name as a method. While this is permitted, -having information (field) and actions (method) is not clear naming. Developers versed in -Smalltalk often prefer this approach as the methods denote accessor methods. - - 3 - - - - - - - -Detects when a class or interface does not have a package definition. - - 3 - - - - - - - - - - - - - - -Detects when a package definition contains uppercase characters. - - 3 - - - - - - - - - - - - - - -Detects when a non-field has a name starting with 'm_'. This usually denotes a field and could be confusing. - - 3 - - - - - - - - - - - - - - -Methods that return boolean results should be named as predicate statements to denote this. -I.e, 'isReady()', 'hasValues()', 'canCommit()', 'willFail()', etc. Avoid the use of the 'get' -prefix for these methods. - - 4 - - - - - - - - - - - - - - - -Short Classnames with fewer than e.g. five characters are not recommended. - - 4 - - - - - - - - - - - - - - - -Names for references to generic values should be limited to a single uppercase letter. - - 4 - - - - 1 - or - string:upper-case(@Image) != @Image -] -]]> - - - - - extends BaseDao { - // This is ok... -} - -public interface GenericDao { - // Also this -} - -public interface GenericDao { - // 'e' should be an 'E' -} - -public interface GenericDao { - // 'EF' is not ok. -} -]]> - - - + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pmd-java/src/main/resources/rulesets/java/optimizations.xml b/pmd-java/src/main/resources/rulesets/java/optimizations.xml index a473d7a6c8c..4439c5385e1 100644 --- a/pmd-java/src/main/resources/rulesets/java/optimizations.xml +++ b/pmd-java/src/main/resources/rulesets/java/optimizations.xml @@ -3,436 +3,24 @@ + xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd"> These rules deal with different optimizations that generally apply to best practices. - - -A local variable assigned only once can be declared final. - - 3 - - - - - - - -A method argument that is never re-assigned within the method can be declared final. - - 3 - - - - - - - -New objects created within loops should be checked to see if they can created outside them and reused. - - 3 - - - - - - - -ArrayList is a much better Collection implementation than Vector if thread-safe operation is not required. - - 3 - - - - 0] - //AllocationExpression/ClassOrInterfaceType - [@Image='Vector' or @Image='java.util.Vector'] -]]> - - - - - - - - - - -Since it passes in a literal of length 1, calls to (string).startsWith can be rewritten using (string).charAt(0) -at the expense of some readability. - - 3 - - - - - - - - - - - - - - -The use of the '+=' operator for appending strings causes the JVM to create and use an internal StringBuffer. -If a non-trivial number of these concatenations are being used then the explicit use of a StringBuilder or -threadsafe StringBuffer is recommended to avoid this. - - 3 - - - - - - - -The java.util.Arrays class has a "asList" method that should be used when you want to create a new List from -an array of objects. It is faster than executing a loop to copy all the elements of the array one by one. - - 3 - - - - - - - - - - - - - - -Instead of manually copying data between two arrays, use the efficient System.arraycopy method instead. - - 3 - - - - - - - - - - - - - - -Most wrapper classes provide static conversion methods that avoid the need to create intermediate objects -just to create the primitive forms. Using these avoids the cost of creating objects that also need to be -garbage-collected later. - - 3 - - - - - - - -The conversion of literals to strings by concatenating them with empty strings is inefficient. -It is much better to use one of the type-specific toString() methods instead. - - 3 - - - - - - - - - - - - - - -Java will initialize fields with known default values so any explicit initialization of those same defaults -is redundant and results in a larger class file (approximately three additional bytecode instructions per field). - - 3 - - - - - - - -Checks for variables that are defined before they might be used. A reference is deemed to be premature if it is created right before a block of code that doesn't use it that also has the ability to return or throw an exception. - - 3 - - - - - - + + + + + + + + + + + + + + \ No newline at end of file diff --git a/pmd-java/src/main/resources/rulesets/java/quickstart.xml b/pmd-java/src/main/resources/rulesets/java/quickstart.xml new file mode 100644 index 00000000000..f2cf5c60b02 --- /dev/null +++ b/pmd-java/src/main/resources/rulesets/java/quickstart.xml @@ -0,0 +1,333 @@ + + + Quickstart configuration of PMD. Includes the rules that are most likely to apply everywhere. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pmd-java/src/main/resources/rulesets/java/rulesets.properties b/pmd-java/src/main/resources/rulesets/java/rulesets.properties index f4b5f1d7f7f..cc92c1fe948 100644 --- a/pmd-java/src/main/resources/rulesets/java/rulesets.properties +++ b/pmd-java/src/main/resources/rulesets/java/rulesets.properties @@ -3,28 +3,13 @@ # rulesets.filenames=\ - rulesets/java/android.xml,\ - rulesets/java/basic.xml,\ - rulesets/java/braces.xml,\ - rulesets/java/clone.xml,\ - rulesets/java/codesize.xml,\ - rulesets/java/comments.xml,\ - rulesets/java/controversial.xml,\ - rulesets/java/coupling.xml,\ - rulesets/java/design.xml,\ - rulesets/java/empty.xml,\ - rulesets/java/finalizers.xml,\ - rulesets/java/imports.xml,\ - rulesets/java/j2ee.xml,\ - rulesets/java/javabeans.xml,\ - rulesets/java/junit.xml,\ - rulesets/java/logging-jakarta-commons.xml,\ - rulesets/java/logging-java.xml,\ - rulesets/java/migrating.xml,\ - rulesets/java/naming.xml,\ - rulesets/java/optimizations.xml,\ - rulesets/java/strictexception.xml,\ - rulesets/java/strings.xml,\ - rulesets/java/sunsecure.xml,\ - rulesets/java/unnecessary.xml,\ - rulesets/java/unusedcode.xml + category/java/bestpractices.xml,\ + category/java/codestyle.xml,\ + category/java/design.xml,\ + category/java/documentation.xml,\ + category/java/errorprone.xml,\ + category/java/multithreading.xml,\ + category/java/performance.xml + +# security doesn't contain any rules yet +# category/java/security.xml diff --git a/pmd-java/src/main/resources/rulesets/java/strictexception.xml b/pmd-java/src/main/resources/rulesets/java/strictexception.xml index 7efab2baf17..1610c48b0d8 100644 --- a/pmd-java/src/main/resources/rulesets/java/strictexception.xml +++ b/pmd-java/src/main/resources/rulesets/java/strictexception.xml @@ -3,413 +3,24 @@ + xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd"> These rules provide some strict guidelines about throwing and catching exceptions. - - -Catching Throwable errors is not recommended since its scope is very broad. It includes runtime issues such as -OutOfMemoryError that should be exposed and managed separately. - - 3 - - - - - - - -Methods that declare the generic Exception as a possible throwable are not very helpful since their -failure modes are unclear. Use a class derived from RuntimeException or a more specific checked exception. - - 3 - - - - - - - -Using Exceptions as form of flow control is not recommended as they obscure true exceptions when debugging. -Either add the necessary validation or use an alternate control structure. - - 3 - - - - - - - -Code should never throw NullPointerExceptions under normal circumstances. A catch block may hide the -original error, causing other, more subtle problems later on. - - 3 - - - - - - - - - - - - - - -Avoid throwing certain exception types. Rather than throw a raw RuntimeException, Throwable, -Exception, or Error, use a subclassed exception or error instead. - - 1 - - - - - - - - - - - - - - -Avoid throwing NullPointerExceptions. These are confusing because most people will assume that the -virtual machine threw it. Consider using an IllegalArgumentException instead; this will be -clearly seen as a programmer-initiated exception. - - 1 - - - - - - - - - - - - - - -Catch blocks that merely rethrow a caught exception only add to code size and runtime complexity. - - 3 - - - - - - - - - - - - - - -Errors are system exceptions. Do not extend them. - - 3 - - - - - - - - - - - - -Throwing exceptions within a 'finally' block is confusing since they may mask other exceptions -or code defects. -Note: This is a PMD implementation of the Lint4j rule "A throw in a finally block" - - 4 - - - - - - - - - - - - - - -Catch blocks that merely rethrow a caught exception wrapped inside a new instance of the same type only add to -code size and runtime complexity. - - 3 - - - - - - - - - - - - - - -Avoid catching generic exceptions such as NullPointerException, RuntimeException, Exception in try-catch block - - 3 - - - - - - - - - - - - - - -Statements in a catch block that invoke accessors on the exception without using the information -only add to code size. Either remove the invocation, or use the return result. - - 2 - - - - - - - - - - - + + + + + + + + + + + + + + diff --git a/pmd-java/src/main/resources/rulesets/java/strings.xml b/pmd-java/src/main/resources/rulesets/java/strings.xml index cafbda75165..75dd092c487 100644 --- a/pmd-java/src/main/resources/rulesets/java/strings.xml +++ b/pmd-java/src/main/resources/rulesets/java/strings.xml @@ -3,402 +3,29 @@ + xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd"> These rules deal with different issues that can arise with manipulation of the String, StringBuffer, or StringBuilder instances. - - -Code containing duplicate String literals can usually be improved by declaring the String as a constant field. - - 3 - - - - - - - -Avoid instantiating String objects; this is usually unnecessary since they are immutable and can be safely shared. - - 2 - - - - - - - -Avoid calling toString() on objects already known to be string instances; this is unnecessary. - - 3 - - - - - - - -Avoid concatenating non-literals in a StringBuffer constructor or append() since intermediate buffers will -need to be be created and destroyed by the JVM. - - 3 - - - - - - - -Using equalsIgnoreCase() is faster than using toUpperCase/toLowerCase().equals() - - 3 - - - - - - - -Use StringBuffer.length() to determine StringBuffer length rather than using StringBuffer.toString().equals("") -or StringBuffer.toString().length() == ... - - 3 - - - - - - - - -Avoid concatenating characters as strings in StringBuffer/StringBuilder.append methods. - - 3 - - - - - - - -Consecutive calls to StringBuffer/StringBuilder .append should be chained, reusing the target object. This can improve the performance -by producing a smaller bytecode, reducing overhead and improving inlining. A complete analysis can be found [here](https://github.com/pmd/pmd/issues/202#issuecomment-274349067) - - 3 - - - - - - - -Consecutively calling StringBuffer/StringBuilder.append with String literals - - 3 - - - - - - - - -Use String.indexOf(char) when checking for the index of a single character; it executes faster. - - 3 - - - - - - - -String.trim().length() is an inefficient way to check if a String is really empty, as it -creates a new String object just to check its size. Consider creating a static function that -loops through a string, checking Character.isWhitespace() on each character and returning -false if a non-whitespace character is found. You can refer to Apache's StringUtils#isBlank (in commons-lang), -Spring's StringUtils#hasText (in the Spring framework) or Google's CharMatcher#whitespace (in Guava) for -existing implementations. - - 3 - - 0) { - doSomething(); - } -} -]]> - - - - - -Failing to pre-size a StringBuffer or StringBuilder properly could cause it to re-size many times -during runtime. This rule attempts to determine the total number the characters that are actually -passed into StringBuffer.append(), but represents a best guess "worst case" scenario. An empty -StringBuffer/StringBuilder constructor initializes the object to 16 characters. This default -is assumed if the length of the constructor can not be determined. - - 3 - - - - - - - -No need to call String.valueOf to append to a string; just use the valueOf() argument directly. - - 3 - - - - - - - - -Individual character values provided as initialization arguments will be converted into integers. -This can lead to internal buffer sizes that are larger than expected. Some examples: - -``` -new StringBuffer() // 16 -new StringBuffer(6) // 6 -new StringBuffer("hello world") // 11 + 16 = 27 -new StringBuffer('A') // chr(A) = 65 -new StringBuffer("A") // 1 + 16 = 17 - -new StringBuilder() // 16 -new StringBuilder(6) // 6 -new StringBuilder("hello world") // 11 + 16 = 27 -new StringBuilder('C') // chr(C) = 67 -new StringBuilder("A") // 1 + 16 = 17 -``` - - 4 - - - - - - - - - - - - - - -Using '==' or '!=' to compare strings only works if intern version is used on both sides. -Use the equals() method instead. - - 3 - - - - - - - - - - - - - - -StringBuffers/StringBuilders can grow considerably, and so may become a source of memory leaks -if held within objects with long lifetimes. - - 3 - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + diff --git a/pmd-java/src/main/resources/rulesets/java/sunsecure.xml b/pmd-java/src/main/resources/rulesets/java/sunsecure.xml index ea97eb88c97..e1f452caebc 100644 --- a/pmd-java/src/main/resources/rulesets/java/sunsecure.xml +++ b/pmd-java/src/main/resources/rulesets/java/sunsecure.xml @@ -3,55 +3,13 @@ + xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd"> These rules check the security guidelines from Sun, published at http://java.sun.com/security/seccodeguide.html#gcg - - -Exposing internal arrays to the caller violates object encapsulation since elements can be -removed or replaced outside of the object that owns it. It is safer to return a copy of the array. - - 3 - - - - - - - -Constructors and methods receiving arrays should clone objects and store the copy. -This prevents future changes from the user from affecting the original array. - - 3 - - - - + + + diff --git a/pmd-java/src/main/resources/rulesets/java/typeresolution.xml b/pmd-java/src/main/resources/rulesets/java/typeresolution.xml index 27fe9c11d3e..c10136bd72b 100644 --- a/pmd-java/src/main/resources/rulesets/java/typeresolution.xml +++ b/pmd-java/src/main/resources/rulesets/java/typeresolution.xml @@ -3,106 +3,16 @@ + xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd"> -These are rules which resolve java Class files for comparison, as opposed to a String +All rules in this ruleset have been moved to other rulesets. Please use the other rules +directly and don't use this ruleset anymore. - - - -Avoid using implementation types (i.e., HashSet); use the interface (i.e, Set) instead - - 3 - - - - - - - -The method clone() should only be implemented if the class implements the Cloneable interface with the exception -of a final method that only throws CloneNotSupportedException. This version uses PMD's type resolution facilities, -and can detect if the class implements or extends a Cloneable class. - - 3 - - - - - - - -Avoid unused import statements. This rule will find unused on demand imports, i.e. import com.foo.*. - - 4 - - - - - - - -It is unclear which exceptions that can be thrown from the methods. -It might be difficult to document and understand the vague interfaces. -Use either a class derived from RuntimeException or a checked exception. - -JUnit classes are excluded. - - 3 - - - - - - - + + + + diff --git a/pmd-java/src/main/resources/rulesets/java/unnecessary.xml b/pmd-java/src/main/resources/rulesets/java/unnecessary.xml index 188c83a1b9a..6479fadef1c 100644 --- a/pmd-java/src/main/resources/rulesets/java/unnecessary.xml +++ b/pmd-java/src/main/resources/rulesets/java/unnecessary.xml @@ -3,386 +3,24 @@ + xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd"> The Unnecessary Ruleset contains a collection of rules for unnecessary code. - - -Avoid the use temporary objects when converting primitives to Strings. Use the static conversion methods -on the wrapper classes instead. - - 3 - - + + + - return Integer.toString(x); // preferred approach -} -]]> - - + + + + - - -Avoid the use of unnecessary return statements. - - 3 - - - - + - - -When a class has the final modifier, all the methods are automatically final and do not need to be -tagged as such. Similarly, methods that can't be overridden (private methods, methods of anonymous classes, -methods of enum instance) do not need to be tagged either. - - 3 - - - - - - - - - - - - - - -The overriding method merely calls the same method defined in a superclass. - - 3 - - - - - - - -An operation on an Immutable object (String, BigDecimal or BigInteger) won't change the object itself -since the result of the operation is a new object. Therefore, ignoring the operation result is an error. - - 3 - - - - - - - -After checking an object reference for null, you should invoke equals() on that object rather than passing it to another object's equals() method. - - 3 - - - - - - - - - - - - - - Useless parentheses should be removed. - 4 - - - - 1] - /PrimaryPrefix/Expression - [not(./CastExpression)] - [not(./ConditionalExpression[@Ternary='true'])] - [not(./AdditiveExpression[//Literal[@StringLiteral='true']])] -| -//Expression[not(parent::PrimaryPrefix)]/PrimaryExpression[count(*)=1] - /PrimaryPrefix/Expression -| -//Expression/ConditionalAndExpression/PrimaryExpression/PrimaryPrefix/Expression[ - count(*)=1 and - count(./CastExpression)=0 and - count(./EqualityExpression/MultiplicativeExpression)=0 and - count(./ConditionalExpression[@Ternary='true'])=0 and - count(./ConditionalOrExpression)=0] -| -//Expression/ConditionalOrExpression/PrimaryExpression/PrimaryPrefix/Expression[ - count(*)=1 and - not(./CastExpression) and - not(./ConditionalExpression[@Ternary='true']) and - not(./EqualityExpression/MultiplicativeExpression)] -| -//Expression/ConditionalExpression/PrimaryExpression/PrimaryPrefix/Expression[ - count(*)=1 and - not(./CastExpression) and - not(./EqualityExpression)] -| -//Expression/AdditiveExpression[not(./PrimaryExpression/PrimaryPrefix/Literal[@StringLiteral='true'])] - /PrimaryExpression[1]/PrimaryPrefix/Expression[ - count(*)=1 and - not(./CastExpression) and - not(./AdditiveExpression[@Image = '-']) and - not(./ShiftExpression) and - not(./RelationalExpression) and - not(./InstanceOfExpression) and - not(./EqualityExpression) and - not(./AndExpression) and - not(./ExclusiveOrExpression) and - not(./InclusiveOrExpression) and - not(./ConditionalAndExpression) and - not(./ConditionalOrExpression) and - not(./ConditionalExpression)] -| -//Expression/EqualityExpression/PrimaryExpression/PrimaryPrefix/Expression[ - count(*)=1 and - not(./CastExpression) and - not(./AndExpression) and - not(./InclusiveOrExpression) and - not(./ExclusiveOrExpression) and - not(./ConditionalExpression) and - not(./ConditionalAndExpression) and - not(./ConditionalOrExpression) and - not(./EqualityExpression)] - ]]> - - - - - - - - - - Look for qualified this usages in the same class. - 3 - - - - - - - - - - - - - - -Fields in interfaces and annotations are automatically `public static final`, and methods are `public abstract`. -Classes, interfaces or annotations nested in an interface or annotation are automatically `public static` -(all nested interfaces and annotations are automatically static). -Nested enums are automatically `static`. -For historical reasons, modifiers which are implied by the context are accepted by the compiler, but are superfluous. - - 3 - - - - + + diff --git a/pmd-java/src/main/resources/rulesets/java/unusedcode.xml b/pmd-java/src/main/resources/rulesets/java/unusedcode.xml index 2e741115728..0b22e86f953 100644 --- a/pmd-java/src/main/resources/rulesets/java/unusedcode.xml +++ b/pmd-java/src/main/resources/rulesets/java/unusedcode.xml @@ -3,94 +3,15 @@ + xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd"> The Unused Code ruleset contains rules that find unused or ineffective code. - - -Detects when a private field is declared and/or assigned a value, but not used. - - 3 - - - - - - - -Detects when a local variable is declared and/or assigned, but not used. - - 3 - - - - - - - -Unused Private Method detects when a private method is declared but is unused. - - 3 - - - - - - - -Avoid passing parameters to methods or constructors without actually referencing them in the method body. - - 3 - - - - + + + + + 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 a7bd84a4844..f2972b14071 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 void testJavaFileUsingDefaults() { File javaFile = new File("/path/to/MyClass.java"); LanguageVersion languageVersion = discoverer.getDefaultLanguageVersionForFile(javaFile); - assertEquals("LanguageVersion must be Java 1.8 !", - LanguageRegistry.getLanguage(JavaLanguageModule.NAME).getVersion("1.8"), languageVersion); + assertEquals("LanguageVersion must be Java 11 !", + LanguageRegistry.getLanguage(JavaLanguageModule.NAME).getVersion("11"), languageVersion); } /** @@ -48,7 +48,7 @@ public void testJavaFileUsing14() { public void testLanguageVersionDiscoverer() { PMDConfiguration configuration = new PMDConfiguration(); LanguageVersionDiscoverer languageVersionDiscoverer = configuration.getLanguageVersionDiscoverer(); - assertEquals("Default Java version", LanguageRegistry.getLanguage(JavaLanguageModule.NAME).getVersion("1.8"), + 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 984a99b706e..1adfc7c185b 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 static Collection data() { 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/RuleSetFactoryTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/RuleSetFactoryTest.java index a761cd4e9ad..cd0d8e16f25 100644 --- a/pmd-java/src/test/java/net/sourceforge/pmd/RuleSetFactoryTest.java +++ b/pmd-java/src/test/java/net/sourceforge/pmd/RuleSetFactoryTest.java @@ -4,7 +4,6 @@ package net.sourceforge.pmd; -import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import org.junit.Test; @@ -20,28 +19,13 @@ public void testExclusionOfUselessParantheses() throws RuleSetNotFoundException "\n" + "\n" + + " xsi:schemaLocation=\"http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd\">\n" + " Custom ruleset for tests\n" - + " \n" + + " \n" + " \n" + " \n" + "\n"); RuleSetFactory ruleSetFactory = new RuleSetFactory(); RuleSet ruleset = ruleSetFactory.createRuleSet(ref); Rule rule = ruleset.getRuleByName("UselessParentheses"); assertNull(rule); } - - /** - * Makes sure that the internal dogfood.xml ruleset is valid and doesn't - * reference any unknown rules. - * - * @throws RuleSetNotFoundException - * if dogfood couldn't be found at all - */ - @Test - public void testDogfoodRuleset() throws RuleSetNotFoundException { - RuleSetReferenceId ref = RuleSetReferenceId.parse("rulesets/internal/dogfood.xml").get(0); - RuleSetFactory ruleSetFactory = new RuleSetFactory(); - RuleSet ruleset = ruleSetFactory.createRuleSet(ref); - assertNotNull(ruleset); - } } diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/ant/PMDTaskTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/ant/PMDTaskTest.java index 3a5097af00c..2857e659580 100644 --- a/pmd-java/src/test/java/net/sourceforge/pmd/ant/PMDTaskTest.java +++ b/pmd-java/src/test/java/net/sourceforge/pmd/ant/PMDTaskTest.java @@ -4,14 +4,15 @@ package net.sourceforge.pmd.ant; -import java.io.File; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + import java.lang.reflect.Field; import java.nio.charset.Charset; import java.util.Locale; import java.util.Objects; import org.apache.commons.io.FileUtils; -import org.junit.Assert; import org.junit.Rule; import org.junit.Test; import org.junit.contrib.java.lang.system.RestoreSystemProperties; @@ -144,17 +145,43 @@ public void testFormatterEncodingWithXML() throws Exception { setDefaultCharset("cp1252"); executeTarget("testFormatterEncodingWithXML"); - String report = FileUtils.readFileToString(new File("target/testFormatterEncodingWithXML-pmd.xml"), "UTF-8"); - Assert.assertTrue(report.contains("unusedVariableWithÃœmlaut")); + String report = FileUtils.readFileToString(currentTempFile(), "UTF-8"); + assertTrue(report.contains("unusedVariableWithÃœmlaut")); } @Test - public void testFormatterEncodingWithXMLConsole() throws Exception { + public void testFormatterEncodingWithXMLConsole() { setDefaultCharset("cp1252"); executeTarget("testFormatterEncodingWithXMLConsole"); String report = buildRule.getOutput(); - Assert.assertTrue(report.startsWith("")); - Assert.assertTrue(report.contains("unusedVariableWithÜmlaut")); + assertTrue(report.startsWith("")); + assertTrue(report.contains("unusedVariableWithÜmlaut")); + } + + @Test + public void testMissingCacheLocation() { + executeTarget("testMissingCacheLocation"); + assertOutputContaining("Avoid really long methods"); + assertContains(buildRule.getLog(), "This analysis could be faster"); + } + + @Test + public void testAnalysisCache() { + executeTarget("testAnalysisCache"); + assertOutputContaining("Avoid really long methods"); + assertDoesntContain(buildRule.getLog(), "This analysis could be faster"); + + assertTrue(currentTempFile().exists()); + } + + + @Test + public void testDisableIncrementalAnalysis() { + executeTarget("testDisableIncrementalAnalysis"); + assertOutputContaining("Avoid really long methods"); + assertDoesntContain(buildRule.getLog(), "This analysis could be faster"); + + assertFalse(currentTempFile().exists()); } } diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/cli/CLITest.java b/pmd-java/src/test/java/net/sourceforge/pmd/cli/CLITest.java index f6845977151..baa91d55adf 100644 --- a/pmd-java/src/test/java/net/sourceforge/pmd/cli/CLITest.java +++ b/pmd-java/src/test/java/net/sourceforge/pmd/cli/CLITest.java @@ -21,25 +21,25 @@ public class CLITest extends BaseCLITest { @Test public void minimalArgs() { - String[] args = { "-d", SOURCE_FOLDER, "-f", "text", "-R", "java-unnecessary,java-design", }; + String[] args = { "-d", SOURCE_FOLDER, "-f", "text", "-R", "category/java/bestpractices.xml,category/java/design.xml", }; runTest(args, "minimalArgs"); } @Test public void minimumPriority() { - String[] args = { "-d", SOURCE_FOLDER, "-f", "text", "-R", "java-design", "-min", "1", }; + String[] args = { "-d", SOURCE_FOLDER, "-f", "text", "-R", "category/java/design.xml", "-min", "1", }; runTest(args, "minimumPriority"); } @Test public void usingDebug() { - String[] args = { "-d", SOURCE_FOLDER, "-f", "text", "-R", "java-design", "-debug", }; + String[] args = { "-d", SOURCE_FOLDER, "-f", "text", "-R", "category/java/design.xml", "-debug", }; runTest(args, "minimalArgsWithDebug"); } @Test public void changeJavaVersion() { - String[] args = { "-d", SOURCE_FOLDER, "-f", "text", "-R", "java-design", "-version", "1.5", "-language", + String[] args = { "-d", SOURCE_FOLDER, "-f", "text", "-R", "category/java/design.xml", "-version", "1.5", "-language", "java", "-debug", }; String resultFilename = runTest(args, "chgJavaVersion"); assertTrue("Invalid Java version", @@ -48,20 +48,20 @@ public void changeJavaVersion() { @Test public void exitStatusNoViolations() { - String[] args = { "-d", SOURCE_FOLDER, "-f", "text", "-R", "java-design", }; + String[] args = { "-d", SOURCE_FOLDER, "-f", "text", "-R", "category/java/design.xml", }; runTest(args, "exitStatusNoViolations"); } @Test public void exitStatusWithViolations() { - String[] args = { "-d", SOURCE_FOLDER, "-f", "text", "-R", "java-empty", }; + String[] args = { "-d", SOURCE_FOLDER, "-f", "text", "-R", "category/java/errorprone.xml", }; String resultFilename = runTest(args, "exitStatusWithViolations", 4); assertTrue(FileUtil.findPatternInFile(new File(resultFilename), "Avoid empty if")); } @Test public void exitStatusWithViolationsAndWithoutFailOnViolations() { - String[] args = { "-d", SOURCE_FOLDER, "-f", "text", "-R", "java-empty", "-failOnViolation", "false", }; + String[] args = { "-d", SOURCE_FOLDER, "-f", "text", "-R", "category/java/errorprone.xml", "-failOnViolation", "false", }; String resultFilename = runTest(args, "exitStatusWithViolationsAndWithoutFailOnViolations", 0); assertTrue(FileUtil.findPatternInFile(new File(resultFilename), "Avoid empty if")); } @@ -71,13 +71,13 @@ public void exitStatusWithViolationsAndWithoutFailOnViolations() { */ @Test public void testWrongRuleset() throws Exception { - String[] args = { "-d", SOURCE_FOLDER, "-f", "text", "-R", "java-designn", }; + String[] args = { "-d", SOURCE_FOLDER, "-f", "text", "-R", "category/java/designn.xml", }; String filename = TEST_OUPUT_DIRECTORY + "testWrongRuleset.txt"; createTestOutputFile(filename); runPMDWith(args); Assert.assertEquals(1, getStatusCode()); assertTrue(FileUtil.findPatternInFile(new File(filename), - "Can't find resource 'null' for rule 'java-designn'." + " Make sure the resource is a valid file")); + "Can't find resource 'category/java/designn.xml' for rule 'null'." + " Make sure the resource is a valid file")); } /** @@ -85,13 +85,13 @@ public void testWrongRuleset() throws Exception { */ @Test public void testWrongRulesetWithRulename() throws Exception { - String[] args = { "-d", SOURCE_FOLDER, "-f", "text", "-R", "java-designn/UseCollectionIsEmpty", }; + String[] args = { "-d", SOURCE_FOLDER, "-f", "text", "-R", "category/java/designn.xml/UseCollectionIsEmpty", }; String filename = TEST_OUPUT_DIRECTORY + "testWrongRuleset.txt"; createTestOutputFile(filename); runPMDWith(args); Assert.assertEquals(1, getStatusCode()); assertTrue(FileUtil.findPatternInFile(new File(filename), - "Can't find resource 'null' for rule " + "'java-designn/UseCollectionIsEmpty'.")); + "Can't find resource 'category/java/designn.xml' for rule " + "'UseCollectionIsEmpty'.")); } /** @@ -99,12 +99,12 @@ public void testWrongRulesetWithRulename() throws Exception { */ @Test public void testWrongRulename() throws Exception { - String[] args = { "-d", SOURCE_FOLDER, "-f", "text", "-R", "java-design/ThisRuleDoesNotExist", }; + String[] args = { "-d", SOURCE_FOLDER, "-f", "text", "-R", "category/java/design.xml/ThisRuleDoesNotExist", }; String filename = TEST_OUPUT_DIRECTORY + "testWrongRuleset.txt"; createTestOutputFile(filename); runPMDWith(args); Assert.assertEquals(1, getStatusCode()); assertTrue(FileUtil.findPatternInFile(new File(filename), Pattern - .quote("No rules found. Maybe you mispelled a rule name?" + " (java-design/ThisRuleDoesNotExist)"))); + .quote("No rules found. Maybe you mispelled a rule name?" + " (category/java/design.xml/ThisRuleDoesNotExist)"))); } } diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/coverage/PMDCoverageTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/coverage/PMDCoverageTest.java index 4e2bba19c32..947d8a099f3 100644 --- a/pmd-java/src/test/java/net/sourceforge/pmd/coverage/PMDCoverageTest.java +++ b/pmd-java/src/test/java/net/sourceforge/pmd/coverage/PMDCoverageTest.java @@ -4,6 +4,8 @@ package net.sourceforge.pmd.coverage; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.fail; import java.io.BufferedReader; @@ -11,19 +13,31 @@ import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; +import org.apache.commons.io.FileUtils; +import org.apache.commons.lang3.StringUtils; +import org.junit.Rule; import org.junit.Test; +import org.junit.contrib.java.lang.system.StandardErrorStreamLog; +import org.junit.contrib.java.lang.system.StandardOutputStreamLog; import net.sourceforge.pmd.PMD; public class PMDCoverageTest { + @Rule + public StandardOutputStreamLog output = new StandardOutputStreamLog(); + + @Rule + public StandardErrorStreamLog errorStream = new StandardErrorStreamLog(); + /** * Test some of the PMD command line options */ @Test public void testPmdOptions() { - runPmd("-d src/main/java/net/sourceforge/pmd/lang/java/rule/design -f text -R rulesets/internal/all-java.xml -version 1.5 -language java -stress -benchmark"); + runPmd("-d src/main/java/net/sourceforge/pmd/lang/java/rule/design -f text -R rulesets/internal/all-java.xml -language java -stress -benchmark"); } /** @@ -39,15 +53,28 @@ private void runPmd(String commandLine) { try { f = File.createTempFile("pmd", ".txt"); int n = args.length; - String[] a = new String[n + 2]; + String[] a = new String[n + 2 + 2]; System.arraycopy(args, 0, a, 0, n); a[n] = "-reportfile"; a[n + 1] = f.getAbsolutePath(); + a[n + 2] = "-threads"; + a[n + 3] = String.valueOf(Runtime.getRuntime().availableProcessors()); args = a; PMD.run(args); - // FIXME: check that output doesn't have parsing errors + assertEquals("Nothing should be output to stdout", 0, output.getLog().length()); + + + assertEquals("No exceptions expected", 0, StringUtils.countMatches(errorStream.getLog(), "Exception applying rule")); + assertFalse("Wrong configuration? Ruleset not found", errorStream.getLog().contains("Ruleset not found")); + assertEquals("No usage of deprecated XPath attributes expected", 0, StringUtils.countMatches(errorStream.getLog(), "Use of deprecated attribute")); + + String report = FileUtils.readFileToString(f, StandardCharsets.UTF_8); + assertEquals("No processing errors expected", 0, StringUtils.countMatches(report, "Error while processing")); + + // we might have explicit examples of parsing errors, so these are maybe false positives + assertEquals("No parsing error expected", 0, StringUtils.countMatches(report, "Error while parsing")); } catch (IOException ioe) { fail("Problem creating temporary file: " + ioe.getLocalizedMessage()); } finally { diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/jaxen/DocumentNavigatorTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/jaxen/DocumentNavigatorTest.java index fe0bbcb6598..b1e070e9a4f 100644 --- a/pmd-java/src/test/java/net/sourceforge/pmd/jaxen/DocumentNavigatorTest.java +++ b/pmd-java/src/test/java/net/sourceforge/pmd/jaxen/DocumentNavigatorTest.java @@ -75,7 +75,7 @@ public Object visit(ASTPrimaryExpression node, Object data) { } @Before - public void setUp() throws Exception { + public void setUp() { try { rule = new TestRule(); runTestFromString(TEST, rule, new Report(), diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/jaxen/RegexpAcceptanceTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/jaxen/RegexpAcceptanceTest.java index 350e3176447..396c87a009b 100644 --- a/pmd-java/src/test/java/net/sourceforge/pmd/jaxen/RegexpAcceptanceTest.java +++ b/pmd-java/src/test/java/net/sourceforge/pmd/jaxen/RegexpAcceptanceTest.java @@ -17,6 +17,11 @@ public class RegexpAcceptanceTest extends SimpleAggregatorTst { private static final String XPATH = "//ClassOrInterfaceDeclaration[matches(@Image, 'F?o')]"; + @Override + protected void setUp() { + // not registering any rule + } + @Test public void testSimple() { Rule r = new XPathRule(XPATH); diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ParserTstUtil.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ParserTstUtil.java index 9a45ef14796..cd3064565d0 100644 --- a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ParserTstUtil.java +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ParserTstUtil.java @@ -11,6 +11,7 @@ import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Proxy; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; @@ -25,6 +26,7 @@ import net.sourceforge.pmd.lang.java.ast.ASTCompilationUnit; import net.sourceforge.pmd.lang.java.ast.JavaParserVisitor; import net.sourceforge.pmd.lang.java.dfa.DataFlowFacade; +import net.sourceforge.pmd.lang.java.qname.QualifiedNameResolver; import net.sourceforge.pmd.lang.java.symboltable.SymbolFacade; public class ParserTstUtil { @@ -65,6 +67,9 @@ public Object invoke(Object proxy, Method method, Object[] params) throws NoSuch } } + // TODO provide a configurable api to choose which visitors to invoke + // it makes no sense + public static Set getNodes(Class clazz, String javaCode) { return getNodes(LanguageRegistry.getLanguage(JavaLanguageModule.NAME).getDefaultVersion(), clazz, javaCode); } @@ -89,10 +94,9 @@ public static List getOrderedNodes(Class clazz, String javaCode) { JavaParserVisitor jpv = (JavaParserVisitor) Proxy.newProxyInstance(JavaParserVisitor.class.getClassLoader(), new Class[] { JavaParserVisitor.class }, coll); jpv.visit(cu, null); - SymbolFacade sf = new SymbolFacade(); - sf.initializeWith(cu); - DataFlowFacade dff = new DataFlowFacade(); - dff.initializeWith(languageVersionHandler.getDataFlowHandler(), cu); + new QualifiedNameResolver().initializeWith(ParserTstUtil.class.getClassLoader(), cu); + new SymbolFacade().initializeWith(cu); + new DataFlowFacade().initializeWith(languageVersionHandler.getDataFlowHandler(), cu); return (List) coll.getCollection(); } @@ -145,6 +149,16 @@ public static ASTCompilationUnit parseJava18(String code) { return parseJava(getLanguageVersionHandler("1.8"), code); } + /** @see #parseJava(LanguageVersionHandler, String) */ + public static ASTCompilationUnit parseJava9(String code) { + return parseJava(getLanguageVersionHandler("9"), code); + } + + /** @see #parseJava(LanguageVersionHandler, String) */ + public static ASTCompilationUnit parseJava10(String code) { + return parseJava(getLanguageVersionHandler("10"), code); + } + /** @see #parseJava(LanguageVersionHandler, String) */ public static ASTCompilationUnit parseJava13(Class source) { return parseJava13(getSourceFromClass(source)); @@ -170,6 +184,16 @@ public static ASTCompilationUnit parseJava18(Class source) { return parseJava18(getSourceFromClass(source)); } + /** @see #parseJava(LanguageVersionHandler, String) */ + public static ASTCompilationUnit parseJava9(Class source) { + return parseJava9(getSourceFromClass(source)); + } + + /** @see #parseJava(LanguageVersionHandler, String) */ + public static ASTCompilationUnit parseJava10(Class source) { + return parseJava10(getSourceFromClass(source)); + } + /** @see #parseJava(LanguageVersionHandler, String) */ public static ASTCompilationUnit parseJavaDefaultVersion(String source) { return parseJava(getDefaultLanguageVersionHandler(), source); @@ -201,6 +225,7 @@ public static LanguageVersionHandler getDefaultLanguageVersionHandler() { public static ASTCompilationUnit parseJava(LanguageVersionHandler languageVersionHandler, String code) { ASTCompilationUnit rootNode = (ASTCompilationUnit) languageVersionHandler .getParser(languageVersionHandler.getDefaultParserOptions()).parse(null, new StringReader(code)); + languageVersionHandler.getQualifiedNameResolutionFacade(ParserTstUtil.class.getClassLoader()).start(rootNode); languageVersionHandler.getSymbolFacade().start(rootNode); return rootNode; } @@ -214,10 +239,23 @@ public static String getSourceFromClass(Class clazz) { } String source; try { - source = IOUtils.toString(is); + source = IOUtils.toString(is, StandardCharsets.UTF_8); } catch (IOException e) { throw new RuntimeException(e); } return source; } + + public static ASTCompilationUnit parseAndTypeResolveJava(String javaVersion, String sourceCode) { + LanguageVersionHandler languageVersionHandler = getLanguageVersionHandler(javaVersion); + ASTCompilationUnit rootNode = (ASTCompilationUnit) languageVersionHandler + .getParser(languageVersionHandler.getDefaultParserOptions()) + .parse(null, new StringReader(sourceCode)); + languageVersionHandler.getQualifiedNameResolutionFacade(ParserTstUtil.class.getClassLoader()).start(rootNode); + languageVersionHandler.getSymbolFacade().start(rootNode); + languageVersionHandler.getDataFlowFacade().start(rootNode); + languageVersionHandler.getTypeResolutionFacade(ParserTstUtil.class.getClassLoader()).start(rootNode); + languageVersionHandler.getMultifileFacade().start(rootNode); + return rootNode; + } } diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/ASTClassOrInterfaceDeclarationTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/ASTClassOrInterfaceDeclarationTest.java new file mode 100644 index 00000000000..1781788e4b2 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/ASTClassOrInterfaceDeclarationTest.java @@ -0,0 +1,76 @@ +/** + * 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.assertFalse; +import static org.junit.Assert.assertTrue; + +import java.util.List; + +import org.junit.Test; + +import net.sourceforge.pmd.lang.java.ParserTstUtil; + + +/** + * @author Clément Fournier + * @since 6.1.0 + */ +public class ASTClassOrInterfaceDeclarationTest { + + private static final String LOCAL_CLASS_IN_METHOD + = "class Foo { void bar() { class Local {}}}"; + + private static final String NESTED_CLASS_IS_NOT_LOCAL + = "class Foo { class Nested {} void bar() {}}"; + + private static final String LOCAL_CLASS_IN_INITIALIZER + = "class Foo { { class Local {} } }"; + + private static final String LOCAL_CHILDREN_ARE_NOT_ALWAYS_LOCAL + = "class Foo { { class Local { class Nested {} void bar() {class Local2 {}}}}}"; + + + @Test + public void testLocalInMethod() { + List classes = ParserTstUtil.getOrderedNodes(ASTClassOrInterfaceDeclaration.class, LOCAL_CLASS_IN_METHOD); + assertTrue(classes.size() == 2); + + assertFalse("Local class false-positive", classes.get(0).isLocal()); + assertTrue("Local class false-negative", classes.get(1).isLocal()); + } + + + @Test + public void testLocalInInitializer() { + List classes = ParserTstUtil.getOrderedNodes(ASTClassOrInterfaceDeclaration.class, LOCAL_CLASS_IN_INITIALIZER); + assertTrue(classes.size() == 2); + + assertFalse("Local class false-positive", classes.get(0).isLocal()); + assertTrue("Local class false-negative", classes.get(1).isLocal()); + } + + + @Test + public void testNestedClassIsNotLocal() { + List classes = ParserTstUtil.getOrderedNodes(ASTClassOrInterfaceDeclaration.class, NESTED_CLASS_IS_NOT_LOCAL); + assertTrue(classes.size() == 2); + + assertFalse("Local class false-positive", classes.get(0).isLocal()); + assertFalse("Local class false-positive", classes.get(1).isLocal()); + } + + + @Test + public void testLocalChildrenAreNotAlwaysLocal() { + List classes = ParserTstUtil.getOrderedNodes(ASTClassOrInterfaceDeclaration.class, LOCAL_CHILDREN_ARE_NOT_ALWAYS_LOCAL); + assertTrue(classes.size() == 4); + + assertFalse("Local class false-positive", classes.get(0).isLocal()); // class Foo + assertTrue("Local class false-negative", classes.get(1).isLocal()); // class Local + assertFalse("Local class false-positive", classes.get(2).isLocal()); // class Nested + assertTrue("Local class false-negative", classes.get(3).isLocal()); // class Local2 + } +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/ASTLiteralTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/ASTLiteralTest.java index f5738b386d2..eb996281753 100644 --- a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/ASTLiteralTest.java +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/ASTLiteralTest.java @@ -59,6 +59,46 @@ public void testIsCharLiteral() { assertTrue((literals.iterator().next()).isCharLiteral()); } + @Test + public void testIntValueParsing() { + ASTLiteral literal = new ASTLiteral(1); + literal.setIntLiteral(); + literal.setImage("1___234"); + literal.testingOnlySetBeginColumn(1); + literal.testingOnlySetEndColumn(7); + assertEquals(1___234, literal.getValueAsInt()); + } + + @Test + public void testIntValueParsingBinary() { + ASTLiteral literal = new ASTLiteral(1); + literal.setIntLiteral(); + literal.setImage("0b0000_0010"); + literal.testingOnlySetBeginColumn(1); + literal.testingOnlySetEndColumn(7); + assertEquals(0b0000_0010, literal.getValueAsInt()); + } + + @Test + public void testIntValueParsingNegativeHexa() { + ASTLiteral literal = new ASTLiteral(1); + literal.setIntLiteral(); + literal.setImage("-0X0000_000f"); + literal.testingOnlySetBeginColumn(1); + literal.testingOnlySetEndColumn(7); + assertEquals(-0X0000_000f, literal.getValueAsInt()); + } + + @Test + public void testFloatValueParsingNegative() { + ASTLiteral literal = new ASTLiteral(1); + literal.setIntLiteral(); + literal.setImage("-3_456.123_456"); + literal.testingOnlySetBeginColumn(1); + literal.testingOnlySetEndColumn(7); + assertEquals(-3_456.123_456f, literal.getValueAsFloat(), 0); + } + @Test public void testStringUnicodeEscapesNotEscaped() { ASTLiteral literal = new ASTLiteral(1); diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/ASTMethodDeclarationTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/ASTMethodDeclarationTest.java index b4c4cb61894..516f7739b74 100644 --- a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/ASTMethodDeclarationTest.java +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/ASTMethodDeclarationTest.java @@ -5,9 +5,13 @@ package net.sourceforge.pmd.lang.java.ast; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; import org.junit.Test; +import net.sourceforge.pmd.lang.java.ParserTstUtil; + public class ASTMethodDeclarationTest { @Test @@ -21,4 +25,20 @@ public void testGetVariableName() { assertEquals("foo", md.getMethodName()); } + + @Test + public void testPrivateInterfaceMethods() { + ASTCompilationUnit node = ParserTstUtil.parseJava9("public interface Foo { private void bar() { } }"); + ASTMethodDeclaration methodDecl = node.getFirstDescendantOfType(ASTMethodDeclaration.class); + assertTrue(methodDecl.isPrivate()); + assertFalse(methodDecl.isPublic()); + } + + @Test + public void testPublicInterfaceMethods() { + ASTCompilationUnit node = ParserTstUtil.parseJava9("public interface Foo { void bar(); }"); + ASTMethodDeclaration methodDecl = node.getFirstDescendantOfType(ASTMethodDeclaration.class); + assertFalse(methodDecl.isPrivate()); + assertTrue(methodDecl.isPublic()); + } } diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/ASTModuleDeclarationTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/ASTModuleDeclarationTest.java new file mode 100644 index 00000000000..60223d57d3b --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/ASTModuleDeclarationTest.java @@ -0,0 +1,79 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.ast; + +import static net.sourceforge.pmd.lang.java.ParserTstUtil.parseJava9; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.List; + +import org.apache.commons.io.IOUtils; +import org.junit.Test; + +public class ASTModuleDeclarationTest { + private static String loadSource(String name) { + try { + return IOUtils.toString(ASTModuleDeclarationTest.class.getResourceAsStream("jdkversiontests/" + name), + StandardCharsets.UTF_8); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + @Test + public final void jdk9ModuleInfo() { + ASTCompilationUnit ast = parseJava9(loadSource("jdk9_module_info.java")); + List modules = ast.findDescendantsOfType(ASTModuleDeclaration.class); + assertEquals(1, modules.size()); + ASTModuleDeclaration module = modules.get(0); + assertTrue(module.isOpen()); + assertEquals("com.example.foo", module.getImage()); + assertEquals(7, module.jjtGetNumChildren()); + List directives = module.findChildrenOfType(ASTModuleDirective.class); + assertEquals(7, directives.size()); + + // requires com.example.foo.http; + assertEquals(ASTModuleDirective.DirectiveType.REQUIRES.name(), directives.get(0).getType()); + assertNull(directives.get(0).getRequiresModifier()); + assertEquals("com.example.foo.http", directives.get(0).getFirstChildOfType(ASTModuleName.class).getImage()); + + // requires java.logging; + assertEquals(ASTModuleDirective.DirectiveType.REQUIRES.name(), directives.get(1).getType()); + assertNull(directives.get(1).getRequiresModifier()); + assertEquals("java.logging", directives.get(1).getFirstChildOfType(ASTModuleName.class).getImage()); + + // requires transitive com.example.foo.network; + assertEquals(ASTModuleDirective.DirectiveType.REQUIRES.name(), directives.get(2).getType()); + assertEquals(ASTModuleDirective.RequiresModifier.TRANSITIVE.name(), directives.get(2).getRequiresModifier()); + assertEquals("com.example.foo.network", directives.get(2).getFirstChildOfType(ASTModuleName.class).getImage()); + + // exports com.example.foo.bar; + assertEquals(ASTModuleDirective.DirectiveType.EXPORTS.name(), directives.get(3).getType()); + assertNull(directives.get(3).getRequiresModifier()); + assertEquals("com.example.foo.bar", directives.get(3).getFirstChildOfType(ASTName.class).getImage()); + + // exports com.example.foo.internal to com.example.foo.probe; + assertEquals(ASTModuleDirective.DirectiveType.EXPORTS.name(), directives.get(4).getType()); + assertNull(directives.get(4).getRequiresModifier()); + assertEquals("com.example.foo.internal", directives.get(4).getFirstChildOfType(ASTName.class).getImage()); + assertEquals("com.example.foo.probe", directives.get(4).getFirstChildOfType(ASTModuleName.class).getImage()); + + // uses com.example.foo.spi.Intf; + assertEquals(ASTModuleDirective.DirectiveType.USES.name(), directives.get(5).getType()); + assertNull(directives.get(5).getRequiresModifier()); + assertEquals("com.example.foo.spi.Intf", directives.get(5).getFirstChildOfType(ASTName.class).getImage()); + + // provides com.example.foo.spi.Intf with com.example.foo.Impl; + assertEquals(ASTModuleDirective.DirectiveType.PROVIDES.name(), directives.get(6).getType()); + assertNull(directives.get(6).getRequiresModifier()); + assertEquals("com.example.foo.spi.Intf", directives.get(6).getFirstChildOfType(ASTName.class).getImage()); + assertEquals("com.example.foo.Impl", directives.get(6).findChildrenOfType(ASTName.class).get(1).getImage()); + } + +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/ASTVariableDeclaratorIdTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/ASTVariableDeclaratorIdTest.java index 299c4348f7f..977ba773bcd 100644 --- a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/ASTVariableDeclaratorIdTest.java +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/ASTVariableDeclaratorIdTest.java @@ -16,14 +16,14 @@ public class ASTVariableDeclaratorIdTest { + + + @Test public void testIsExceptionBlockParameter() { - ASTTryStatement tryNode = new ASTTryStatement(1); - ASTBlock block = new ASTBlock(2); - ASTVariableDeclaratorId v = new ASTVariableDeclaratorId(3); - v.jjtSetParent(block); - block.jjtSetParent(tryNode); - assertTrue(v.isExceptionBlockParameter()); + ASTCompilationUnit acu = getNodes(ASTCompilationUnit.class, EXCEPTION_PARAMETER).iterator().next(); + ASTVariableDeclaratorId id = acu.getFirstDescendantOfType(ASTVariableDeclaratorId.class); + assertTrue(id.isExceptionBlockParameter()); } @Test @@ -47,7 +47,8 @@ public void testAnnotations() { @Test public void testLambdaWithType() throws Exception { ASTCompilationUnit acu = parseJava18(TEST_LAMBDA_WITH_TYPE); - ASTVariableDeclaratorId f = acu.findDescendantsOfType(ASTVariableDeclaratorId.class).get(1); + ASTLambdaExpression lambda = acu.getFirstDescendantOfType(ASTLambdaExpression.class); + ASTVariableDeclaratorId f = lambda.getFirstDescendantOfType(ASTVariableDeclaratorId.class); assertEquals("File", f.getTypeNode().getTypeImage()); assertEquals("File", f.getTypeNameNode().jjtGetChild(0).getImage()); } @@ -55,13 +56,15 @@ public void testLambdaWithType() throws Exception { @Test public void testLambdaWithoutType() throws Exception { ASTCompilationUnit acu = parseJava18(TEST_LAMBDA_WITHOUT_TYPE); - ASTVariableDeclaratorId f = acu.findDescendantsOfType(ASTVariableDeclaratorId.class).get(1); + ASTLambdaExpression lambda = acu.getFirstDescendantOfType(ASTLambdaExpression.class); + ASTVariableDeclaratorId f = lambda.getFirstDescendantOfType(ASTVariableDeclaratorId.class); assertNull(f.getTypeNode()); assertNull(f.getTypeNameNode()); } private static final String TYPE_NAME_NODE = "public class Test {" + PMD.EOL + " private String bar;" + PMD.EOL + "}"; + private static final String EXCEPTION_PARAMETER = "public class Test { { try {} catch(Exception ie) {} } }"; private static final String TEST_ANNOTATIONS = "public class Foo {" + PMD.EOL + " public void bar(@A1 @A2 String s) {}" + PMD.EOL + "}"; private static final String TEST_LAMBDA_WITH_TYPE = "public class Foo {\n" + " public void bar() {\n" diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/CommentTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/CommentTest.java new file mode 100644 index 00000000000..2c586d5a2f9 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/CommentTest.java @@ -0,0 +1,110 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.ast; + +import org.apache.commons.lang3.StringUtils; +import org.junit.Assert; +import org.junit.Test; + +import net.sourceforge.pmd.PMD; + +public class CommentTest { + @Test + public void testMultiLinesInSingleLine() { + String comment = "/* single line. */"; + String filtered = filter(comment); + Assert.assertEquals(1, lineCount(filtered)); + Assert.assertEquals("single line.", filtered); + } + + @Test + public void testMultiLinesInSingleLineSimple() { + String comment = "// single line."; + String filtered = filter(comment); + Assert.assertEquals(1, lineCount(filtered)); + Assert.assertEquals("single line.", filtered); + } + + @Test + public void testMultiLinesInSingleLineFormal() { + String comment = "/** single line. */"; + String filtered = filter(comment); + Assert.assertEquals(1, lineCount(filtered)); + Assert.assertEquals("single line.", filtered); + } + + @Test + public void testMultiLinesInMultiLine() { + String comment = + "/*\n" + + " * line 1\n" + + " * line 2\n" + + " */\n"; + String filtered = filter(comment); + Assert.assertEquals(2, lineCount(filtered)); + Assert.assertEquals("line 1" + PMD.EOL + "line 2", filtered); + } + + @Test + public void testMultiLinesInMultiLineCrLf() { + String comment = + "/*\r\n" + + " * line 1\r\n" + + " * line 2\r\n" + + " */\r\n"; + String filtered = filter(comment); + Assert.assertEquals(2, lineCount(filtered)); + Assert.assertEquals("line 1" + PMD.EOL + "line 2", filtered); + } + + @Test + public void testMultiLinesInMultiLineFormal() { + String comment = + "/**\n" + + " * line 1\n" + + " * line 2\n" + + " */\n"; + String filtered = filter(comment); + Assert.assertEquals(2, lineCount(filtered)); + Assert.assertEquals("line 1" + PMD.EOL + "line 2", filtered); + } + + @Test + public void testMultiLinesInMultiLineFormalCrLf() { + String comment = + "/**\r\n" + + " * line 1\r\n" + + " * line 2\r\n" + + " */\r\n"; + String filtered = filter(comment); + Assert.assertEquals(2, lineCount(filtered)); + Assert.assertEquals("line 1" + PMD.EOL + "line 2", filtered); + } + + @Test + public void testMultiLinesInMultiLineNoAsteriskEmpty() { + String comment = + "/**\n" + + " * line 1\n" + + "line 2\n" + + "\n" + + " */\n"; + String filtered = filter(comment); + Assert.assertEquals(2, lineCount(filtered)); + Assert.assertEquals("line 1" + PMD.EOL + "line 2", filtered); + } + + private String filter(String comment) { + Token t = new Token(); + t.image = comment; + Comment node = new Comment(t) { + }; + return node.getFilteredComment(); + } + + private int lineCount(String filtered) { + return StringUtils.countMatches(filtered, PMD.EOL) + 1; + } +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/CommentUtilTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/CommentUtilTest.java new file mode 100644 index 00000000000..b6b50c5aa7a --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/CommentUtilTest.java @@ -0,0 +1,238 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.ast; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Map; + +import org.junit.Assert; +import org.junit.Test; + + +public class CommentUtilTest { + + @Test + public void testFindJavaDocTags() { + String formalComment = + "/**\n" + + " * @see something\n" + + " * @author Author1\n" + + " * @author Author2\n" + + " * @param parm1 description\n" + + " */\n"; + + Map javadocTagsIn = CommentUtil.javadocTagsIn(formalComment); + Assert.assertEquals(3, javadocTagsIn.size()); + Assert.assertEquals(7, javadocTagsIn.get("see").intValue()); + Assert.assertEquals("@see", formalComment.substring(7, 7 + 4)); + Assert.assertEquals("@author", formalComment.substring(javadocTagsIn.get("author"), + javadocTagsIn.get("author") + "author".length() + 1)); + } + + @Test + public void testFindJavaDocTagsEmpty() { + Map javadocTagsIn = CommentUtil.javadocTagsIn(""); + Assert.assertEquals(0, javadocTagsIn.size()); + } + + @Test + public void testFindJavaDocTagsNull() { + Map javadocTagsIn = CommentUtil.javadocTagsIn(null); + Assert.assertEquals(0, javadocTagsIn.size()); + } + + @Test + public void testMultiLinesInSingleLine() { + String comment = "/* single line. */"; + List lines = CommentUtil.multiLinesIn(comment); + Assert.assertEquals(1, lines.size()); + Assert.assertEquals("single line.", lines.get(0)); + } + + @Test + public void testMultiLinesInSingleLineSimple() { + String comment = "// single line."; + List lines = CommentUtil.multiLinesIn(comment); + Assert.assertEquals(1, lines.size()); + Assert.assertEquals("single line.", lines.get(0)); + } + + @Test + public void testMultiLinesInSingleLineFormal() { + String comment = "/** single line. */"; + List lines = CommentUtil.multiLinesIn(comment); + Assert.assertEquals(1, lines.size()); + Assert.assertEquals("single line.", lines.get(0)); + } + + @Test + public void testMultiLinesInMultiLine() { + String comment = + "/*\n" + + " * line 1\n" + + " * line 2\n" + + " */\n"; + List lines = CommentUtil.multiLinesIn(comment); + Assert.assertEquals(2, lines.size()); + Assert.assertEquals("line 1", lines.get(0)); + Assert.assertEquals("line 2", lines.get(1)); + } + + @Test + public void testMultiLinesInMultiLineCrLf() { + String comment = + "/*\r\n" + + " * line 1\r\n" + + " * line 2\r\n" + + " */\r\n"; + List lines = CommentUtil.multiLinesIn(comment); + Assert.assertEquals(2, lines.size()); + Assert.assertEquals("line 1", lines.get(0)); + Assert.assertEquals("line 2", lines.get(1)); + } + + @Test + public void testMultiLinesInMultiLineFormal() { + String comment = + "/**\n" + + " * line 1\n" + + " * line 2\n" + + " */\n"; + List lines = CommentUtil.multiLinesIn(comment); + Assert.assertEquals(2, lines.size()); + Assert.assertEquals("line 1", lines.get(0)); + Assert.assertEquals("line 2", lines.get(1)); + } + + @Test + public void testMultiLinesInMultiLineFormalCrLf() { + String comment = + "/**\r\n" + + " * line 1\r\n" + + " * line 2\r\n" + + " */\r\n"; + List lines = CommentUtil.multiLinesIn(comment); + Assert.assertEquals(2, lines.size()); + Assert.assertEquals("line 1", lines.get(0)); + Assert.assertEquals("line 2", lines.get(1)); + } + + @Test + public void testMultiLinesInMultiLineNoAsteriskEmpty() { + String comment = + "/**\n" + + " * line 1\n" + + "line 2\n" + + "\n" + + " */\n"; + List lines = CommentUtil.multiLinesIn(comment); + Assert.assertEquals(2, lines.size()); + Assert.assertEquals("line 1", lines.get(0)); + Assert.assertEquals("line 2", lines.get(1)); + } + + @Test + public void testTrim() { + List lines = Arrays.asList("", "a", "", ""); + List trimmed = CommentUtil.trim(lines); + Assert.assertEquals(1, trimmed.size()); + Assert.assertEquals("a", trimmed.get(0)); + } + + @Test + public void testTrimNotMiddle() { + List lines = Arrays.asList("a", "b", "", "c"); + List trimmed = CommentUtil.trim(lines); + Assert.assertEquals(4, trimmed.size()); + Assert.assertEquals("a", trimmed.get(0)); + Assert.assertEquals("b", trimmed.get(1)); + Assert.assertEquals("", trimmed.get(2)); + Assert.assertEquals("c", trimmed.get(3)); + } + + @Test + public void testTrimEmpty() { + List trimmed = CommentUtil.trim(new ArrayList()); + Assert.assertEquals(0, trimmed.size()); + } + + @Test + public void testTrimNull() { + List trimmed = CommentUtil.trim(null); + Assert.assertEquals(0, trimmed.size()); + } + + @Test + public void testWordAfter() { + String wordAfter = CommentUtil.wordAfter("@param param1 Description", "@param".length()); + Assert.assertEquals("param1", wordAfter); + } + + @Test + public void testWordAfterPositionOutOfBounds() { + String wordAfter = CommentUtil.wordAfter("@param param1 Description", Integer.MAX_VALUE); + Assert.assertNull(wordAfter); + } + + @Test + public void testWordAfterNull() { + String wordAfter = CommentUtil.wordAfter(null, 0); + Assert.assertNull(wordAfter); + } + + @Test + public void testJavadocAfter() { + String javadocContentAfter = CommentUtil.javadocContentAfter("@param param1 The Description\n", + "@param param1".length()); + Assert.assertEquals("The Description", javadocContentAfter); + } + + @Test + public void testJavadocAfterOutOfBounds() { + String javadocContentAfter = CommentUtil.javadocContentAfter("@param param1 The Description\n", + Integer.MAX_VALUE); + Assert.assertNull(javadocContentAfter); + } + + @Test + public void testJavadocAfterNull() { + String javadocContentAfter = CommentUtil.javadocContentAfter(null, 0); + Assert.assertNull(javadocContentAfter); + } + + @Test + public void testJavadoc() { + String comment = " /**\n" + + " * Checks if the metric can be computed on the node.\n" + + " *\n" + + " * @param node The node to check\n" + + " *\n" + + " * @return True if the metric can be computed\n" + + " */\n" + + " boolean supports(N node);\n" + + ""; + List lines = CommentUtil.multiLinesIn(comment); + lines = CommentUtil.trim(lines); + + for (String line : lines) { + Map tags = CommentUtil.javadocTagsIn(line); + for (String tag : tags.keySet()) { + int pos = tags.get(tag) + tag.length() + 1; + String wordAfter = CommentUtil.wordAfter(line, pos); + pos = pos + wordAfter.length() + 1; + String description = CommentUtil.javadocContentAfter(line, pos); + if ("param".equals(tag)) { + Assert.assertEquals("node", wordAfter); // the parameter name + Assert.assertEquals("The node to check", description); + } else if ("return".equals(tag)) { + Assert.assertEquals("True", wordAfter); + Assert.assertEquals("if the metric can be computed", description); + } + } + } + } +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/FormalCommentTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/FormalCommentTest.java new file mode 100644 index 00000000000..5f7e29aaa38 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/FormalCommentTest.java @@ -0,0 +1,36 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.ast; + +import org.junit.Assert; +import org.junit.Test; + +public class FormalCommentTest { + + @Test + public void testJavadocTagsAsChildren() { + String comment = " /**\n" + + " * Checks if the metric can be computed on the node.\n" + + " *\n" + + " * @param node The node to check\n" + + " *\n" + + " * @return True if the metric can be computed\n" + + " */\n" + + " boolean supports(N node);\n" + + ""; + + Token token = new Token(); + token.image = comment; + FormalComment commentNode = new FormalComment(token); + + Assert.assertEquals(2, commentNode.jjtGetNumChildren()); + + JavadocElement paramTag = (JavadocElement) commentNode.jjtGetChild(0); + Assert.assertEquals("param", paramTag.tag().label); + + JavadocElement returnTag = (JavadocElement) commentNode.jjtGetChild(1); + Assert.assertEquals("return", returnTag.tag().label); + } +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/JDKVersionTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/JDKVersionTest.java index 3e22d5e3d4f..c93605ddb8d 100644 --- a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/JDKVersionTest.java +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/JDKVersionTest.java @@ -8,9 +8,16 @@ import static net.sourceforge.pmd.lang.java.ParserTstUtil.parseJava14; import static net.sourceforge.pmd.lang.java.ParserTstUtil.parseJava15; import static net.sourceforge.pmd.lang.java.ParserTstUtil.parseJava17; +import static net.sourceforge.pmd.lang.java.ParserTstUtil.parseJava18; +import static net.sourceforge.pmd.lang.java.ParserTstUtil.parseJava9; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; import java.io.IOException; import java.nio.charset.StandardCharsets; +import java.util.List; import org.apache.commons.io.IOUtils; import org.junit.Test; @@ -205,4 +212,84 @@ public final void testMulticatch() { public final void testMulticatchWithAnnotations() { parseJava17(loadSource("jdk17_multicatch_with_annotations.java")); } + + @Test(expected = ParseException.class) + public final void jdk9PrivateInterfaceMethodsInJava18() { + parseJava18(loadSource("jdk9_private_interface_methods.java")); + } + + @Test + public final void testPrivateMethods() { + parseJava18("public class Foo { private void bar() { } }"); + } + + @Test + public final void testNestedPrivateMethods() { + parseJava18("public interface Baz { public static class Foo { private void bar() { } } }"); + } + + @Test + public final void jdk9PrivateInterfaceMethods() { + parseJava9(loadSource("jdk9_private_interface_methods.java")); + } + + @Test + public final void jdk9InvalidIdentifierInJava18() { + parseJava18(loadSource("jdk9_invalid_identifier.java")); + } + + @Test(expected = ParseException.class) + public final void jdk9InvalidIdentifier() { + parseJava9(loadSource("jdk9_invalid_identifier.java")); + } + + @Test(expected = ParseException.class) + public final void jdk9AnonymousDiamondInJava8() { + parseJava18(loadSource("jdk9_anonymous_diamond.java")); + } + + @Test + public final void jdk9AnonymousDiamond() { + parseJava9(loadSource("jdk9_anonymous_diamond.java")); + } + + @Test(expected = ParseException.class) + public final void jdk9ModuleInfoInJava8() { + parseJava18(loadSource("jdk9_module_info.java")); + } + + @Test + public final void jdk9ModuleInfo() { + parseJava9(loadSource("jdk9_module_info.java")); + } + + @Test(expected = ParseException.class) + public final void jdk9TryWithResourcesInJava8() { + parseJava18(loadSource("jdk9_try_with_resources.java")); + } + + @Test + public final void jdk9TryWithResources() { + parseJava9(loadSource("jdk9_try_with_resources.java")); + } + + @Test + public final void jdk7PrivateMethodInnerClassInterface1() { + ASTCompilationUnit acu = parseJava17(loadSource("private_method_in_inner_class_interface1.java")); + List methods = acu.findDescendantsOfType(ASTMethodDeclaration.class, true); + assertEquals(3, methods.size()); + for (ASTMethodDeclaration method : methods) { + assertFalse(method.isInterfaceMember()); + } + } + + @Test + public final void jdk7PrivateMethodInnerClassInterface2() { + try { + ASTCompilationUnit acu = parseJava17(loadSource("private_method_in_inner_class_interface2.java")); + fail("Expected exception"); + } catch (ParseException e) { + assertTrue(e.getMessage().startsWith("Line 19")); + } + } } diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/Java10Test.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/Java10Test.java new file mode 100644 index 00000000000..ecfc1b1d69d --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/Java10Test.java @@ -0,0 +1,185 @@ +/** + * 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 static org.junit.Assert.assertNull; +import static org.junit.Assert.assertSame; + +import java.io.FileInputStream; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; + +import org.apache.commons.io.IOUtils; +import org.junit.Assert; +import org.junit.Test; + +import net.sourceforge.pmd.lang.java.ParserTstUtil; +import net.sourceforge.pmd.lang.java.typeresolution.typedefinition.JavaTypeDefinition; + +public class Java10Test { + + private static String loadSource(String name) { + try { + return IOUtils.toString(Java10Test.class.getResourceAsStream("jdkversiontests/java10/" + name), + StandardCharsets.UTF_8); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + @Test + public void testLocalVarInferenceBeforeJava10() { + // note, it can be parsed, but we'll have a ReferenceType of "var" + ASTCompilationUnit compilationUnit = ParserTstUtil.parseAndTypeResolveJava("9", + loadSource("LocalVariableTypeInference.java")); + + List localVars = compilationUnit.findDescendantsOfType(ASTLocalVariableDeclaration.class); + assertEquals(3, localVars.size()); + + // first: var list = new ArrayList(); + ASTType type = localVars.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()); + // in that case, we don't have a class named "var", so the type will be null + assertNull(classType.getType()); + assertNull(type.getType()); + + // check the type of the variable initializer's expression + ASTExpression initExpression = localVars.get(0) + .getFirstChildOfType(ASTVariableDeclarator.class) + .getFirstChildOfType(ASTVariableInitializer.class) + .getFirstChildOfType(ASTExpression.class); + assertSame("type should be ArrayList", ArrayList.class, initExpression.getType()); + } + + @Test + public void testLocalVarInferenceCanBeParsedJava10() { + ASTCompilationUnit compilationUnit = ParserTstUtil.parseAndTypeResolveJava("10", + loadSource("LocalVariableTypeInference.java")); + List localVars = compilationUnit.findDescendantsOfType(ASTLocalVariableDeclaration.class); + assertEquals(3, localVars.size()); + + // first: var list = new ArrayList(); + assertNull(localVars.get(0).getTypeNode()); + ASTVariableDeclarator varDecl = localVars.get(0).getFirstChildOfType(ASTVariableDeclarator.class); + assertSame("type should be ArrayList", ArrayList.class, varDecl.getType()); + assertEquals("type should be ArrayList", JavaTypeDefinition.forClass(ArrayList.class, JavaTypeDefinition.forClass(String.class)), + varDecl.getTypeDefinition()); + ASTVariableDeclaratorId varId = varDecl.getFirstChildOfType(ASTVariableDeclaratorId.class); + assertEquals("type should be equal", varDecl.getTypeDefinition(), varId.getTypeDefinition()); + + // second: var stream = list.stream(); + assertNull(localVars.get(1).getTypeNode()); + //ASTVariableDeclarator varDecl2 = localVars.get(1).getFirstChildOfType(ASTVariableDeclarator.class); + // TODO: return type of method call is unknown + // assertEquals("type should be Stream", JavaTypeDefinition.forClass(Stream.class, JavaTypeDefinition.forClass(String.class)), + // varDecl2.getTypeDefinition()); + + // third: var s = "Java 10"; + assertNull(localVars.get(2).getTypeNode()); + ASTVariableDeclarator varDecl3 = localVars.get(2).getFirstChildOfType(ASTVariableDeclarator.class); + assertEquals("type should be String", JavaTypeDefinition.forClass(String.class), varDecl3.getTypeDefinition()); + + ASTArgumentList argumentList = compilationUnit.getFirstDescendantOfType(ASTArgumentList.class); + ASTExpression expression3 = argumentList.getFirstChildOfType(ASTExpression.class); + assertEquals("type should be String", JavaTypeDefinition.forClass(String.class), expression3.getTypeDefinition()); + } + + @Test + public void testForLoopWithVar() { + ASTCompilationUnit compilationUnit = ParserTstUtil.parseAndTypeResolveJava("10", + loadSource("LocalVariableTypeInferenceForLoop.java")); + List localVars = compilationUnit.findDescendantsOfType(ASTLocalVariableDeclaration.class); + assertEquals(1, localVars.size()); + + assertNull(localVars.get(0).getTypeNode()); + ASTVariableDeclarator varDecl = localVars.get(0).getFirstChildOfType(ASTVariableDeclarator.class); + assertSame("type should be int", Integer.TYPE, varDecl.getType()); + } + + @Test + public void testForLoopEnhancedWithVar() { + ASTCompilationUnit compilationUnit = ParserTstUtil.parseAndTypeResolveJava("10", + loadSource("LocalVariableTypeInferenceForLoopEnhanced.java")); + List localVars = compilationUnit.findDescendantsOfType(ASTLocalVariableDeclaration.class); + assertEquals(1, localVars.size()); + + assertNull(localVars.get(0).getTypeNode()); + ASTVariableDeclarator varDecl = localVars.get(0).getFirstChildOfType(ASTVariableDeclarator.class); + assertSame("type should be String", String.class, varDecl.getType()); + } + + @Test + public void testForLoopEnhancedWithVar2() { + ASTCompilationUnit compilationUnit = ParserTstUtil.parseAndTypeResolveJava("10", + loadSource("LocalVariableTypeInferenceForLoopEnhanced2.java")); + List localVars = compilationUnit.findDescendantsOfType(ASTLocalVariableDeclaration.class); + assertEquals(4, localVars.size()); + + assertNull(localVars.get(1).getTypeNode()); + ASTVariableDeclarator varDecl2 = localVars.get(1).getFirstChildOfType(ASTVariableDeclarator.class); + assertSame("type should be String", String.class, varDecl2.getType()); + ASTVariableDeclaratorId varId2 = varDecl2.getFirstChildOfType(ASTVariableDeclaratorId.class); + assertSame("type should be String", String.class, varId2.getType()); + + assertNull(localVars.get(3).getTypeNode()); + ASTVariableDeclarator varDecl4 = localVars.get(3).getFirstChildOfType(ASTVariableDeclarator.class); + assertSame("type should be int", Integer.TYPE, varDecl4.getType()); + } + + @Test + public void testTryWithResourcesWithVar() { + ASTCompilationUnit compilationUnit = ParserTstUtil.parseAndTypeResolveJava("10", + loadSource("LocalVariableTypeInferenceTryWithResources.java")); + List resources = compilationUnit.findDescendantsOfType(ASTResource.class); + assertEquals(1, resources.size()); + + assertNull(resources.get(0).getTypeNode()); + ASTVariableDeclaratorId varId = resources.get(0).getVariableDeclaratorId(); + assertSame("type should be FileInputStream", FileInputStream.class, varId.getType()); + } + + @Test + public void testTypeResNullPointer() { + ASTCompilationUnit compilationUnit = ParserTstUtil.parseAndTypeResolveJava("10", + loadSource("LocalVariableTypeInference_typeres.java")); + Assert.assertNotNull(compilationUnit); + } + + @Test + public void testVarAsIdentifier() { + ASTCompilationUnit compilationUnit = ParserTstUtil.parseAndTypeResolveJava("10", + loadSource("LocalVariableTypeInference_varAsIdentifier.java")); + Assert.assertNotNull(compilationUnit); + } + + @Test(expected = ParseException.class) + public void testVarAsTypeIdentifier() { + ParserTstUtil.parseAndTypeResolveJava("10", + loadSource("LocalVariableTypeInference_varAsTypeIdentifier.java")); + } + + @Test(expected = ParseException.class) + public void testVarAsAnnotationName() { + ParserTstUtil.parseAndTypeResolveJava("10", + loadSource("LocalVariableTypeInference_varAsAnnotationName.java")); + } + + @Test(expected = ParseException.class) + public void testVarAsEnumName() { + ParserTstUtil.parseAndTypeResolveJava("10", + loadSource("LocalVariableTypeInference_varAsEnumName.java")); + } +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/JavaQualifiedNameTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/JavaQualifiedNameTest.java new file mode 100644 index 00000000000..4098fa0a88e --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/JavaQualifiedNameTest.java @@ -0,0 +1,691 @@ +/** + * 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.assertFalse; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +import java.util.Arrays; +import java.util.List; + +import org.junit.Test; + +import net.sourceforge.pmd.lang.java.ParserTstUtil; +import net.sourceforge.pmd.lang.java.qname.JavaOperationQualifiedName; +import net.sourceforge.pmd.lang.java.qname.JavaTypeQualifiedName; +import net.sourceforge.pmd.lang.java.qname.QualifiedNameFactory; + + +/** + * @author Clément Fournier + */ +public class JavaQualifiedNameTest { + + private List getNodes(Class nodeType, String source) { + return ParserTstUtil.getOrderedNodes(nodeType, source); + } + + @Test + public void testEmptyPackage() { + final String TEST = "class Foo {}"; + List nodes = getNodes(ASTClassOrInterfaceDeclaration.class, TEST); + for (ASTClassOrInterfaceDeclaration coid : nodes) { + JavaTypeQualifiedName qname = coid.getQualifiedName(); + assertEquals("Foo", qname.toString()); + assertTrue(qname.getPackageList().isEmpty()); + assertTrue(qname.isUnnamedPackage()); + assertEquals(1, qname.getClassList().size()); + } + } + + + @Test + public void testPackage() { + final String TEST = "package foo.bar; class Bzaz{}"; + + List nodes = getNodes(ASTClassOrInterfaceDeclaration.class, + TEST); + for (ASTClassOrInterfaceDeclaration coid : nodes) { + JavaTypeQualifiedName qname = coid.getQualifiedName(); + assertEquals("foo.bar.Bzaz", qname.toString()); + assertEquals(2, qname.getPackageList().size()); + assertEquals(1, qname.getClassList().size()); + } + } + + + @Test + public void testNestedClass() { + final String TEST = "package foo.bar; class Bzaz{ class Bor{ class Foo{}}}"; + + List nodes = getNodes(ASTClassOrInterfaceDeclaration.class, TEST); + + for (ASTClassOrInterfaceDeclaration coid : nodes) { + JavaTypeQualifiedName qname = coid.getQualifiedName(); + switch (coid.getImage()) { + case "Foo": + assertEquals("foo.bar.Bzaz$Bor$Foo", + qname.toString()); + assertEquals(3, qname.getClassList().size()); + break; + default: + break; + } + } + } + + + @Test + public void testNestedEnum() { + final String TEST = "package foo.bar; class Foo { enum Bzaz{HOO;}}"; + + List nodes = getNodes(ASTEnumDeclaration.class, TEST); + + for (ASTEnumDeclaration coid : nodes) { + JavaTypeQualifiedName qname = coid.getQualifiedName(); + assertEquals("foo.bar.Foo$Bzaz", qname.toString()); + assertEquals(2, qname.getPackageList().size()); + assertEquals(2, qname.getClassList().size()); + } + } + + + @Test + public void testEnum() { + final String TEST = "package foo.bar; enum Bzaz{HOO;}"; + + List nodes = getNodes(ASTEnumDeclaration.class, TEST); + + for (ASTEnumDeclaration coid : nodes) { + JavaTypeQualifiedName qname = coid.getQualifiedName(); + assertEquals("foo.bar.Bzaz", qname.toString()); + assertEquals(2, qname.getPackageList().size()); + assertEquals(1, qname.getClassList().size()); + } + } + + + @Test + public void testEnumMethodMember() { + final String TEST = "package foo.bar; enum Bzaz{HOO; void foo(){}}"; + + List nodes = getNodes(ASTMethodDeclaration.class, TEST); + + for (ASTMethodDeclaration coid : nodes) { + JavaOperationQualifiedName qname = coid.getQualifiedName(); + assertEquals("foo.bar.Bzaz#foo()", qname.toString()); + assertEquals(2, qname.getClassName().getPackageList().size()); + assertEquals(1, qname.getClassName().getClassList().size()); + assertEquals("foo()", qname.getOperation()); + } + } + + + @Test + public void testNestedEmptyPackage() { + final String TEST = "class Bzaz{ class Bor{ class Foo{}}}"; + + List nodes = getNodes(ASTClassOrInterfaceDeclaration.class, TEST); + + for (ASTClassOrInterfaceDeclaration coid : nodes) { + JavaTypeQualifiedName qname = coid.getQualifiedName(); + switch (coid.getImage()) { + case "Foo": + assertEquals("Bzaz$Bor$Foo", + qname.toString()); + assertTrue(qname.getPackageList().isEmpty()); + assertTrue(qname.isUnnamedPackage()); + assertEquals(3, qname.getClassList().size()); + break; + default: + break; + } + } + } + + + @Test + public void testMethod() { + final String TEST = "package bar; class Bzaz{ public void foo(){}}"; + + List nodes = getNodes(ASTMethodDeclaration.class, TEST); + + for (ASTMethodDeclaration declaration : nodes) { + JavaOperationQualifiedName qname = declaration.getQualifiedName(); + assertEquals("bar.Bzaz#foo()", qname.toString()); + assertNotNull(qname.getOperation()); + assertEquals("foo()", qname.getOperation()); + + } + } + + + @Test + public void testConstructor() { + final String TEST = "package bar; class Bzaz{ public Bzaz(){}}"; + + List nodes = getNodes(ASTConstructorDeclaration.class, TEST); + + for (ASTConstructorDeclaration declaration : nodes) { + JavaOperationQualifiedName qname = declaration.getQualifiedName(); + assertEquals("bar.Bzaz#Bzaz()", + qname.toString()); + assertNotNull(qname.getOperation()); + assertEquals("Bzaz()", qname.getOperation()); + + } + } + + + @Test + public void testConstructorWithParams() { + final String TEST = "package bar; class Bzaz{ public Bzaz(int j, String k){}}"; + + List nodes = getNodes(ASTConstructorDeclaration.class, TEST); + + for (ASTConstructorDeclaration declaration : nodes) { + JavaOperationQualifiedName qname = declaration.getQualifiedName(); + assertEquals("bar.Bzaz#Bzaz(int, String)", qname.toString()); + assertNotNull(qname.getOperation()); + assertEquals("Bzaz(int, String)", qname.getOperation()); + + } + } + + + @Test + public void testConstructorOverload() { + final String TEST = "package bar; class Bzaz{ public Bzaz(int j) {} public Bzaz(int j, String k){}}"; + + List nodes = getNodes(ASTConstructorDeclaration.class, + TEST); + + ASTConstructorDeclaration[] arr = nodes.toArray(new ASTConstructorDeclaration[2]); + assertNotEquals(arr[0].getQualifiedName(), arr[1].getQualifiedName()); + } + + + @Test + public void testMethodOverload() { + final String TEST = "package bar; class Bzaz{ public void foo(String j) {} " + + "public void foo(int j){} public void foo(double k){}}"; + + List nodes = getNodes(ASTMethodDeclaration.class, TEST); + + ASTMethodDeclaration[] arr = nodes.toArray(new ASTMethodDeclaration[3]); + assertNotEquals(arr[0].getQualifiedName(), arr[1].getQualifiedName()); + assertNotEquals(arr[1].getQualifiedName(), arr[2].getQualifiedName()); + } + + + @Test + public void testParseClass() { + JavaTypeQualifiedName outer = (JavaTypeQualifiedName) QualifiedNameFactory.ofString("foo.bar.Bzaz"); + JavaTypeQualifiedName nested = (JavaTypeQualifiedName) QualifiedNameFactory.ofString("foo.bar.Bzaz$Bolg"); + + assertEquals(1, outer.getClassList().size()); + assertEquals("Bzaz", outer.getClassList().get(0)); + + assertEquals(2, nested.getClassList().size()); + assertEquals("Bzaz", nested.getClassList().get(0)); + assertEquals("Bolg", nested.getClassList().get(1)); + } + + + @Test + public void testParsePackages() { + JavaTypeQualifiedName packs = (JavaTypeQualifiedName) QualifiedNameFactory.ofString("foo.bar.Bzaz$Bolg"); + JavaTypeQualifiedName nopacks = (JavaTypeQualifiedName) QualifiedNameFactory.ofString("Bzaz"); + + assertNotNull(packs.getPackageList()); + assertEquals("foo", packs.getPackageList().get(0)); + assertEquals("bar", packs.getPackageList().get(1)); + + assertTrue(nopacks.getPackageList().isEmpty()); + } + + + @Test + public void testParseOperation() { + JavaOperationQualifiedName noparams = (JavaOperationQualifiedName) QualifiedNameFactory.ofString("foo.bar.Bzaz$Bolg#bar()"); + JavaOperationQualifiedName params = (JavaOperationQualifiedName) QualifiedNameFactory.ofString("foo.bar.Bzaz#bar(String, int)"); + + assertEquals("bar()", noparams.getOperation()); + assertEquals("bar(String, int)", params.getOperation()); + } + + + @Test + public void testParseLocalClasses() { + final String SIMPLE = "foo.bar.Bzaz$1Local"; + final String NESTED = "foo.Bar$1Local$Nested"; + JavaTypeQualifiedName simple = (JavaTypeQualifiedName) QualifiedNameFactory.ofString(SIMPLE); + JavaTypeQualifiedName nested = (JavaTypeQualifiedName) QualifiedNameFactory.ofString(NESTED); + + assertNotNull(simple); + assertTrue(simple.isLocalClass()); + assertFalse(simple.isAnonymousClass()); + assertNotNull(nested); + assertFalse(nested.isLocalClass()); + assertFalse(simple.isAnonymousClass()); + + assertEquals(SIMPLE, simple.toString()); + assertEquals(NESTED, nested.toString()); + + } + + + @Test + public void testParseAnonymousClass() { + final String SIMPLE = "Bzaz$12$13"; + + JavaTypeQualifiedName simple = (JavaTypeQualifiedName) QualifiedNameFactory.ofString(SIMPLE); + + assertNotNull(simple); + assertTrue(simple.isAnonymousClass()); + assertFalse(simple.isLocalClass()); + + assertEquals("12", simple.getClassList().get(1)); + assertEquals("13", simple.getClassList().get(2)); + + assertEquals(SIMPLE, simple.toString()); + } + + @Test + public void testParseLambdaName() { + final String IN_LAMBDA = "foo.bar.Bzaz$1Local#lambda$null$12"; + final String STATIC = "foo.bar.Bzaz#lambda$static$12"; + final String NEW = "foo.bar.Bzaz#lambda$new$1"; + final String IN_METHOD = "Bzaz#lambda$myMethod$4"; + + for (String s : Arrays.asList(IN_LAMBDA, STATIC, NEW, IN_METHOD)) { + JavaOperationQualifiedName qname = (JavaOperationQualifiedName) QualifiedNameFactory.ofString(s); + assertNotNull(qname); + assertTrue(qname.isLambda()); + assertEquals(s, qname.toString()); + assertEquals(qname, QualifiedNameFactory.ofString(qname.toString())); + } + } + + @Test + public void testParseLambdaInEnumConstant() { + final String LAMBA_IN_ENUM_CONSTANT = "package foo; import java.util.function.Function; enum Bar { CONST(e -> e); Bar(Function o) {} }"; + final String QNAME = "foo.Bar#lambda$static$0"; + + ASTLambdaExpression node = getNodes(ASTLambdaExpression.class, LAMBA_IN_ENUM_CONSTANT).get(0); + assertNotNull(node); + + assertEquals(QualifiedNameFactory.ofString(QNAME), node.getQualifiedName()); + } + + + @Test + public void testParseMalformed() { + assertNull(QualifiedNameFactory.ofString(".foo.bar.Bzaz")); + assertNull(QualifiedNameFactory.ofString("foo.bar.")); + assertNull(QualifiedNameFactory.ofString("foo.bar.Bzaz#foo")); + assertNull(QualifiedNameFactory.ofString("foo.bar.Bzaz()")); + assertNull(QualifiedNameFactory.ofString("foo.bar.Bzaz#foo(String,)")); + assertNull(QualifiedNameFactory.ofString("foo.bar.Bzaz#foo(String , int)")); + assertNull(QualifiedNameFactory.ofString("foo.bar.Bzaz#lambda$static$23(String)")); + assertNull(QualifiedNameFactory.ofString("foo.bar.Bzaz#lambda$static$")); + } + + + @Test + public void testSimpleLocalClass() { + final String TEST = "package bar; class Boron { public void foo(String j) { class Local {} } }"; + + List classes + = ParserTstUtil.getOrderedNodes(ASTClassOrInterfaceDeclaration.class, TEST); + + JavaQualifiedName qname = QualifiedNameFactory.ofString("bar.Boron$1Local"); + + assertEquals(qname, classes.get(1).getQualifiedName()); + } + + + @Test + public void testLocalClassNameClash() { + final String TEST = "package bar; class Bzaz{ void foo() { class Local {} } {// initializer\n class Local {}}}"; + + List classes + = ParserTstUtil.getOrderedNodes(ASTClassOrInterfaceDeclaration.class, TEST); + + assertNotEquals(classes.get(1).getQualifiedName(), classes.get(2).getQualifiedName()); + + assertEquals(QualifiedNameFactory.ofString("bar.Bzaz$1Local"), classes.get(1).getQualifiedName()); + assertEquals(QualifiedNameFactory.ofString("bar.Bzaz$2Local"), classes.get(2).getQualifiedName()); + } + + + @Test + public void testLocalClassDeepNesting() { + final String TEST + = "class Bzaz{ void foo() { " + + " class Local { " + + " class Nested {" + + " {" + + " class InnerLocal{}" + + " }" + + " }" + + " }" + + "}}"; + + List classes + = ParserTstUtil.getOrderedNodes(ASTClassOrInterfaceDeclaration.class, TEST); + + assertNotEquals(classes.get(1).getQualifiedName(), classes.get(2).getQualifiedName()); + + assertEquals(QualifiedNameFactory.ofString("Bzaz$1Local"), classes.get(1).getQualifiedName()); + assertEquals(QualifiedNameFactory.ofString("Bzaz$1Local$Nested"), classes.get(2).getQualifiedName()); + assertEquals(QualifiedNameFactory.ofString("Bzaz$1Local$Nested$1InnerLocal"), classes.get(3).getQualifiedName()); + } + + + @Test + public void testAnonymousClass() { + final String TEST + = "class Bzaz{ void foo() { " + + " new Runnable() {" + + " public void run() {}" + + " };" + + "}}"; + + List classes = ParserTstUtil.getOrderedNodes(ASTAllocationExpression.class, TEST); + + assertEquals(QualifiedNameFactory.ofString("Bzaz$1"), classes.get(0).getQualifiedName()); + assertFalse(classes.get(0).getQualifiedName().isLocalClass()); + assertTrue(classes.get(0).getQualifiedName().isAnonymousClass()); + assertTrue("1".equals(classes.get(0).getQualifiedName().getClassSimpleName())); + } + + + @Test + public void testMultipleAnonymousClasses() { + final String TEST + = "class Bzaz{ void foo() { " + + " new Runnable() {" + + " public void run() {}" + + " };" + + " new Runnable() {" + + " public void run() {}" + + " };" + + "}}"; + + List classes = ParserTstUtil.getOrderedNodes(ASTAllocationExpression.class, TEST); + + assertNotEquals(classes.get(0), classes.get(1)); + assertEquals(QualifiedNameFactory.ofString("Bzaz$1"), classes.get(0).getQualifiedName()); + assertEquals(QualifiedNameFactory.ofString("Bzaz$2"), classes.get(1).getQualifiedName()); + } + + + @Test + public void testNestedAnonymousClass() { + final String TEST + = "class Bzaz{ void foo() {" + + " new Runnable() {" + + " public void run() {" + + " new Runnable() {" + + " public void run() {}" + + " };" + + " }" + + " };" + + "}}"; + + List classes = ParserTstUtil.getOrderedNodes(ASTAllocationExpression.class, TEST); + + assertNotEquals(classes.get(0), classes.get(1)); + assertEquals(QualifiedNameFactory.ofString("Bzaz$1"), classes.get(0).getQualifiedName()); + assertEquals(QualifiedNameFactory.ofString("Bzaz$1$1"), classes.get(1).getQualifiedName()); + } + + + @Test + public void testLocalInAnonymousClass() { + final String TEST + = "class Bzaz{ void foo() {" + + " new Runnable() {" + + " public void run() {" + + " class FooRunnable {}" + + " }" + + " };" + + "}}"; + + List classes = ParserTstUtil.getOrderedNodes(ASTClassOrInterfaceDeclaration.class, TEST); + + assertTrue(classes.get(1).isLocal()); + assertEquals(QualifiedNameFactory.ofString("Bzaz$1$1FooRunnable"), classes.get(1).getQualifiedName()); + } + + @Test + public void testLambdaInStaticInitializer() { + final String TEST + = "import java.util.function.*;" + + "class Bzaz{ " + + " static {" + + " Consumer l = s -> {" + + " System.out.println(s);" + + " };" + + " l.accept(\"foo\");" + + " }" + + "}"; + + + List lambdas = ParserTstUtil.getOrderedNodes(ASTLambdaExpression.class, TEST); + + assertEquals(QualifiedNameFactory.ofString("Bzaz#lambda$static$0"), lambdas.get(0).getQualifiedName()); + } + + + @Test + public void testLambdaInInitializerAndConstructor() { + final String TEST + = "import java.util.function.*;" + + "class Bzaz{ " + + " {" + + " Consumer l = s -> {" + + " System.out.println(s);" + + " };" + + " l.accept(\"foo\");" + + " }" + + " public Bzaz() {" + + " Consumer l = s -> {" + + " System.out.println(s);" + + " };" + + " l.accept(\"foo\");" + + " }" + + "}"; + + List lambdas = ParserTstUtil.getOrderedNodes(ASTLambdaExpression.class, TEST); + + assertEquals(QualifiedNameFactory.ofString("Bzaz#lambda$new$0"), lambdas.get(0).getQualifiedName()); + assertEquals(QualifiedNameFactory.ofString("Bzaz#lambda$new$1"), lambdas.get(1).getQualifiedName()); + } + + + @Test + public void testLambdaField() { + final String TEST + = "import java.util.function.*;" + + "public class Bzaz { " + + " Consumer l = s -> {" + + " System.out.println(s);" + + " };" + + " public static Consumer k = s -> {" + + " System.out.println(s);" + + " };" + + "}"; + + List lambdas = ParserTstUtil.getOrderedNodes(ASTLambdaExpression.class, TEST); + + assertEquals(QualifiedNameFactory.ofString("Bzaz#lambda$new$0"), lambdas.get(0).getQualifiedName()); + assertEquals(QualifiedNameFactory.ofString("Bzaz#lambda$static$1"), lambdas.get(1).getQualifiedName()); + } + + + @Test + public void testLambdaInterfaceField() { + final String TEST + = "import java.util.function.*;" + + "public interface Bzaz { " + + " Consumer l = s -> {" + + " System.out.println(s);" + + " };" + + " public static Consumer k = s -> {" + + " System.out.println(s);" + + " };" + + "}"; + + List lambdas = ParserTstUtil.getOrderedNodes(ASTLambdaExpression.class, TEST); + + assertEquals(QualifiedNameFactory.ofString("Bzaz#lambda$static$0"), lambdas.get(0).getQualifiedName()); + assertEquals(QualifiedNameFactory.ofString("Bzaz#lambda$static$1"), lambdas.get(1).getQualifiedName()); + } + + + @Test + public void testLambdaLocalClassField() { + final String TEST + = "import java.util.function.*;" + + "public class Bzaz { " + + " public void boo() {" + + " class Local {" + + " Consumer l = s -> {" + + " System.out.println(s);" + + " };" + + " }" + + " }" + + "}"; + + List lambdas = ParserTstUtil.getOrderedNodes(ASTLambdaExpression.class, TEST); + + assertEquals(QualifiedNameFactory.ofString("Bzaz$1Local#lambda$Local$0"), lambdas.get(0).getQualifiedName()); + } + + + @Test + public void testLambdaAnonymousClassField() { + final String TEST + = "import java.util.function.*;" + + "public class Bzaz { " + + " public void boo() {" + + " new Anonymous() {" + + " Consumer l = s -> {" + + " System.out.println(s);" + + " };" + + " };" + + " }" + + "}"; + + List lambdas = ParserTstUtil.getOrderedNodes(ASTLambdaExpression.class, TEST); + + assertEquals(QualifiedNameFactory.ofString("Bzaz$1#lambda$$0"), lambdas.get(0).getQualifiedName()); + + // This is here because of a bug with the regex parsing, which failed on "Bzaz$1#lambda$$0" + // because the second segment of the lambda name was the empty string + + assertTrue(lambdas.get(0).getQualifiedName().isLambda()); + assertEquals("lambda$$0", lambdas.get(0).getQualifiedName().getOperation()); + assertEquals(2, lambdas.get(0).getQualifiedName().getClassName().getClassList().size()); + } + + + @Test + public void testLambdasInMethod() { + final String TEST + = "import java.util.function.*;" + + "class Bzaz{ " + + " public void bar() {" + + " Consumer l = s -> {" + + " System.out.println(s);" + + " };" + + " l.accept(\"foo\");" + + " }" + + " public void fooBar() {" + + " Consumer l = s -> {" + + " System.out.println(s);" + + " };" + + " l.accept(\"foo\");" + + " }" + + " public void gollum() {" + + " Consumer l = s -> {" + + " System.out.println(s);" + + " };" + + " l.accept(\"foo\");" + + " }" + + "}"; + + List lambdas = ParserTstUtil.getOrderedNodes(ASTLambdaExpression.class, TEST); + + assertEquals(QualifiedNameFactory.ofString("Bzaz#lambda$bar$0"), lambdas.get(0).getQualifiedName()); + assertEquals(QualifiedNameFactory.ofString("Bzaz#lambda$fooBar$1"), lambdas.get(1).getQualifiedName()); + assertEquals(QualifiedNameFactory.ofString("Bzaz#lambda$gollum$2"), lambdas.get(2).getQualifiedName()); + } + + + @Test + public void testLambdaCounterBelongsToClass() { + final String TEST + = "import java.util.function.*;" + + "class Bzaz{ " + + " static {" + + " Consumer l = s -> {" + + " System.out.println(s);" + + " };" + + " l.accept(\"foo\");" + + " }" + + " public Bzaz() {" + + " Consumer l = s -> {" + + " System.out.println(s);" + + " };" + + " l.accept(\"foo\");" + + " }" + + " public void gollum() {" + + " Consumer l = s -> {" + + " System.out.println(s);" + + " };" + + " l.accept(\"foo\");" + + " new Runnable() {" + + " public void run() {" + + " Runnable r = () -> {};" + + " r.run();" + + " }" + + " }.run();" + + " }" + + "}"; + + List lambdas = ParserTstUtil.getOrderedNodes(ASTLambdaExpression.class, TEST); + + assertEquals(QualifiedNameFactory.ofString("Bzaz#lambda$static$0"), lambdas.get(0).getQualifiedName()); + assertEquals(QualifiedNameFactory.ofString("Bzaz#lambda$new$1"), lambdas.get(1).getQualifiedName()); + assertEquals(QualifiedNameFactory.ofString("Bzaz#lambda$gollum$2"), lambdas.get(2).getQualifiedName()); + assertEquals(QualifiedNameFactory.ofString("Bzaz$1#lambda$run$0"), lambdas.get(3).getQualifiedName()); // counter starts over for anon class + + // This is here because of a bug with the regex parsing, which caused "Bzaz$1#lambda$run$0" + // to be parsed as + // * classes == List("Bzaz", "#lambda", "run", "0").reverse() + // * localIndices == List(-1, 1, -1, -1) + // * operation == null + assertTrue(lambdas.get(3).getQualifiedName().isLambda()); + assertEquals("lambda$run$0", lambdas.get(3).getQualifiedName().getOperation()); + assertEquals(2, lambdas.get(3).getQualifiedName().getClassName().getClassList().size()); + } + + + @Test + public void testGetType() { + JavaTypeQualifiedName qname = QualifiedNameFactory.ofClass(ASTAdditiveExpression.class); + assertEquals(qname.getType(), ASTAdditiveExpression.class); + } + + +} + diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/ParserCornersTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/ParserCornersTest.java index 78fb900f1ce..a74222b92c4 100644 --- a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/ParserCornersTest.java +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/ParserCornersTest.java @@ -14,12 +14,18 @@ import java.io.IOException; import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.util.List; +import java.util.Map; import org.apache.commons.io.IOUtils; import org.junit.Assert; import org.junit.Test; import net.sourceforge.pmd.PMD; +import net.sourceforge.pmd.lang.java.ParserTstUtil; +import net.sourceforge.pmd.lang.symboltable.NameDeclaration; +import net.sourceforge.pmd.lang.symboltable.NameOccurrence; public class ParserCornersTest { @@ -35,6 +41,33 @@ public void testInnerOuterClass() { + " TestInnerClassCallsOuterParent.super.toString();\n" + " }\n" + " };\n" + " }\n" + "}\n"); } + + /** + * #888 PMD 6.0.0 can't parse valid <> under 1.8. + */ + @Test + public void testDiamondUsageJava8() { + parseJava18("public class PMDExceptionTest {\n" + + " private Component makeUI() {\n" + + " String[] model = {\"123456\", \"7890\"};\n" + + " JComboBox comboBox = new JComboBox<>(model);\n" + + " comboBox.setEditable(true);\n" + + " comboBox.setEditor(new BasicComboBoxEditor() {\n" + + " private Component editorComponent;\n" + + " @Override public Component getEditorComponent() {\n" + + " if (editorComponent == null) {\n" + + " JTextField tc = (JTextField) super.getEditorComponent();\n" + + " editorComponent = new JLayer<>(tc, new ValidationLayerUI<>());\n" + + " }\n" + + " return editorComponent;\n" + + " }\n" + + " });\n" + + " JPanel p = new JPanel();\n" + + " p.add(comboBox);\n" + + " return p;\n" + + " }\n" + + "}"); + } @Test public final void testGetFirstASTNameImageNull() { @@ -95,8 +128,8 @@ public void testLambdaBug1333() { } @Test - public void testLambdaBug1470() throws Exception { - String code = IOUtils.toString(ParserCornersTest.class.getResourceAsStream("LambdaBug1470.java"), "UTF-8"); + public void testLambdaBug1470() { + String code = readAsString("LambdaBug1470.java"); parseJava18(code); } @@ -130,20 +163,20 @@ public void testMultipleExceptionCatching() { } @Test - public void testBug1429ParseError() throws Exception { - String c = IOUtils.toString(this.getClass().getResourceAsStream("Bug1429.java")); + public void testBug1429ParseError() { + String c = readAsString("Bug1429.java"); parseJava18(c); } @Test - public void testBug1530ParseError() throws Exception { - String c = IOUtils.toString(this.getClass().getResourceAsStream("Bug1530.java")); + public void testBug1530ParseError() { + String c = readAsString("Bug1530.java"); parseJava18(c); } @Test - public void testGitHubBug207() throws Exception { - String c = IOUtils.toString(this.getClass().getResourceAsStream("GitHubBug207.java")); + public void testGitHubBug207() { + String c = readAsString("GitHubBug207.java"); parseJava18(c); } @@ -157,8 +190,8 @@ public void testBug206() throws Exception { } @Test - public void testGitHubBug208ParseError() throws Exception { - String c = IOUtils.toString(this.getClass().getResourceAsStream("GitHubBug208.java")); + public void testGitHubBug208ParseError() { + String c = readAsString("GitHubBug208.java"); parseJava15(c); } @@ -220,14 +253,32 @@ public void testParseEmptyStatements() throws Exception { Assert.assertEquals(ASTEmptyStatement.class, cu3.jjtGetChild(4).getClass()); } + @Test + public void testMethodReferenceConfused() throws Exception { + String code = readAsString("MethodReferenceConfused.java"); + ASTCompilationUnit compilationUnit = ParserTstUtil.parseAndTypeResolveJava("10", code); + Assert.assertNotNull(compilationUnit); + ASTBlock firstBlock = compilationUnit.getFirstDescendantOfType(ASTBlock.class); + Map> declarations = firstBlock.getScope().getDeclarations(); + boolean foundVariable = false; + for (Map.Entry> declaration : declarations.entrySet()) { + String varName = declaration.getKey().getImage(); + if ("someVarNameSameAsMethodReference".equals(varName)) { + foundVariable = true; + Assert.assertTrue("no usages expected", declaration.getValue().isEmpty()); + } else if ("someObject".equals(varName)) { + Assert.assertEquals("1 usage expected", 1, declaration.getValue().size()); + Assert.assertEquals(6, declaration.getValue().get(0).getLocation().getBeginLine()); + } + } + Assert.assertTrue("Test setup wrong - variable 'someVarNameSameAsMethodReference' not found anymore!", foundVariable); + } + private String readAsString(String resource) { - InputStream in = ParserCornersTest.class.getResourceAsStream(resource); - try { - return IOUtils.toString(in); + try (InputStream in = ParserCornersTest.class.getResourceAsStream(resource)) { + return IOUtils.toString(in, StandardCharsets.UTF_8); } catch (IOException e) { throw new RuntimeException(e); - } finally { - IOUtils.closeQuietly(in); } } diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/QualifiedNameTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/QualifiedNameTest.java deleted file mode 100644 index b0284736c8c..00000000000 --- a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/QualifiedNameTest.java +++ /dev/null @@ -1,265 +0,0 @@ -/** - * BSD-style license; for more info see http://pmd.sourceforge.net/license.html - */ - -package net.sourceforge.pmd.lang.java.ast; - -import static net.sourceforge.pmd.lang.java.ParserTstUtil.getNodes; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; - -import java.util.Set; - -import org.junit.Test; - -/** - * @author Clément Fournier - */ -public class QualifiedNameTest { - - - @Test - public void testEmptyPackage() { - final String TEST = "class Foo {}"; - Set nodes = getNodes(ASTClassOrInterfaceDeclaration.class, - TEST); - for (ASTClassOrInterfaceDeclaration coid : nodes) { - JavaQualifiedName qname = coid.getQualifiedName(); - assertEquals(".Foo", qname.toString()); - assertNull(qname.getPackages()); - assertEquals(1, qname.getClasses().length); - assertNull(qname.getOperation()); - } - } - - @Test - public void testPackage() { - final String TEST = "package foo.bar; class Bzaz{}"; - - Set nodes = getNodes(ASTClassOrInterfaceDeclaration.class, - TEST); - for (ASTClassOrInterfaceDeclaration coid : nodes) { - JavaQualifiedName qname = coid.getQualifiedName(); - assertEquals("foo.bar.Bzaz", qname.toString()); - assertEquals(2, qname.getPackages().length); - assertEquals(1, qname.getClasses().length); - assertNull(qname.getOperation()); - } - } - - @Test - public void testNestedClass() { - final String TEST = "package foo.bar; class Bzaz{ class Bor{ class Foo{}}}"; - - - Set nodes = getNodes(ASTClassOrInterfaceDeclaration.class, - TEST); - - for (ASTClassOrInterfaceDeclaration coid : nodes) { - JavaQualifiedName qname = coid.getQualifiedName(); - switch (coid.getImage()) { - case "Foo": - assertEquals("foo.bar.Bzaz$Bor$Foo", - qname.toString()); - assertEquals(3, qname.getClasses().length); - break; - default: - break; - } - } - } - - @Test - public void testNestedEnum() { - final String TEST = "package foo.bar; class Foo { enum Bzaz{HOO;}}"; - - Set nodes = getNodes(ASTEnumDeclaration.class, TEST); - - for (ASTEnumDeclaration coid : nodes) { - JavaQualifiedName qname = coid.getQualifiedName(); - assertEquals("foo.bar.Foo$Bzaz", qname.toString()); - assertEquals(2, qname.getPackages().length); - assertEquals(2, qname.getClasses().length); - assertNull(qname.getOperation()); - } - } - - @Test - public void testEnum() { - final String TEST = "package foo.bar; enum Bzaz{HOO;}"; - - Set nodes = getNodes(ASTEnumDeclaration.class, TEST); - - for (ASTEnumDeclaration coid : nodes) { - JavaQualifiedName qname = coid.getQualifiedName(); - assertEquals("foo.bar.Bzaz", qname.toString()); - assertEquals(2, qname.getPackages().length); - assertEquals(1, qname.getClasses().length); - assertNull(qname.getOperation()); - } - } - - @Test - public void testEnumMethodMember() { - final String TEST = "package foo.bar; enum Bzaz{HOO; void foo(){}}"; - - Set nodes = getNodes(ASTMethodDeclaration.class, TEST); - - for (ASTMethodDeclaration coid : nodes) { - JavaQualifiedName qname = coid.getQualifiedName(); - assertEquals("foo.bar.Bzaz#foo()", qname.toString()); - assertEquals(2, qname.getPackages().length); - assertEquals(1, qname.getClasses().length); - assertEquals("foo()", qname.getOperation()); - } - } - - @Test - public void testNestedEmptyPackage() { - final String TEST = "class Bzaz{ class Bor{ class Foo{}}}"; - - - Set nodes = getNodes(ASTClassOrInterfaceDeclaration.class, - TEST); - - for (ASTClassOrInterfaceDeclaration coid : nodes) { - JavaQualifiedName qname = coid.getQualifiedName(); - switch (coid.getImage()) { - case "Foo": - assertEquals(".Bzaz$Bor$Foo", - qname.toString()); - assertNull(qname.getPackages()); - assertEquals(3, qname.getClasses().length); - break; - default: - break; - } - } - } - - @Test - public void testMethod() { - final String TEST = "package bar; class Bzaz{ public void foo(){}}"; - - - Set nodes = getNodes(ASTMethodDeclaration.class, - TEST); - - for (ASTMethodDeclaration declaration : nodes) { - JavaQualifiedName qname = declaration.getQualifiedName(); - assertEquals("bar.Bzaz#foo()", qname.toString()); - assertNotNull(qname.getOperation()); - assertEquals("foo()", qname.getOperation()); - - } - } - - @Test - public void testConstructor() { - final String TEST = "package bar; class Bzaz{ public Bzaz(){}}"; - - - Set nodes = getNodes(ASTConstructorDeclaration.class, - TEST); - - for (ASTConstructorDeclaration declaration : nodes) { - JavaQualifiedName qname = declaration.getQualifiedName(); - assertEquals("bar.Bzaz#Bzaz()", - qname.toString()); - assertNotNull(qname.getOperation()); - assertEquals("Bzaz()", qname.getOperation()); - - } - } - - @Test - public void testConstructorWithParams() { - final String TEST = "package bar; class Bzaz{ public Bzaz(int j, String k){}}"; - - - Set nodes = getNodes(ASTConstructorDeclaration.class, - TEST); - - for (ASTConstructorDeclaration declaration : nodes) { - JavaQualifiedName qname = declaration.getQualifiedName(); - assertEquals("bar.Bzaz#Bzaz(int, String)", qname.toString()); - assertNotNull(qname.getOperation()); - assertEquals("Bzaz(int, String)", qname.getOperation()); - - } - } - - @Test - public void testConstructorOverload() { - final String TEST = "package bar; class Bzaz{ public Bzaz(int j) {} public Bzaz(int j, String k){}}"; - - Set nodes = getNodes(ASTConstructorDeclaration.class, - TEST); - - ASTConstructorDeclaration[] arr = nodes.toArray(new ASTConstructorDeclaration[2]); - assertNotEquals(arr[0].getQualifiedName(), arr[1].getQualifiedName()); - } - - @Test - public void testMethodOverload() { - final String TEST = "package bar; class Bzaz{ public void foo(String j) {} " - + "public void foo(int j){} public void foo(double k){}}"; - - Set nodes = getNodes(ASTMethodDeclaration.class, TEST); - - ASTMethodDeclaration[] arr = nodes.toArray(new ASTMethodDeclaration[3]); - assertNotEquals(arr[0].getQualifiedName(), arr[1].getQualifiedName()); - assertNotEquals(arr[1].getQualifiedName(), arr[2].getQualifiedName()); - } - - - @Test - public void testParseClass() { - JavaQualifiedName outer = JavaQualifiedName.ofString("foo.bar.Bzaz"); - JavaQualifiedName nested = JavaQualifiedName.ofString("foo.bar.Bzaz$Bolg"); - - assertEquals(1, outer.getClasses().length); - assertEquals("Bzaz", outer.getClasses()[0]); - - assertEquals(2, nested.getClasses().length); - assertEquals("Bzaz", nested.getClasses()[0]); - assertEquals("Bolg", nested.getClasses()[1]); - } - - - @Test - public void testParsePackages() { - JavaQualifiedName packs = JavaQualifiedName.ofString("foo.bar.Bzaz$Bolg"); - JavaQualifiedName nopacks = JavaQualifiedName.ofString(".Bzaz"); - - assertNotNull(packs.getPackages()); - assertEquals("foo", packs.getPackages()[0]); - assertEquals("bar", packs.getPackages()[1]); - - assertNull(nopacks.getPackages()); - } - - @Test - public void testParseOperation() { - JavaQualifiedName noparams = JavaQualifiedName.ofString("foo.bar.Bzaz$Bolg#bar()"); - JavaQualifiedName params = JavaQualifiedName.ofString("foo.bar.Bzaz#bar(String, int)"); - - assertEquals("bar()", noparams.getOperation()); - assertEquals("bar(String, int)", params.getOperation()); - } - - @Test - public void testParseMalformed() { - assertNull(JavaQualifiedName.ofString(".foo.bar.Bzaz")); - assertNull(JavaQualifiedName.ofString("foo.bar.")); - assertNull(JavaQualifiedName.ofString("foo.bar.Bzaz#foo")); - assertNull(JavaQualifiedName.ofString("foo.bar.Bzaz()")); - assertNull(JavaQualifiedName.ofString("foo.bar.Bzaz#foo(String,)")); - assertNull(JavaQualifiedName.ofString("foo.bar.Bzaz#foo(String , int)")); - } - - -} - diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/SimpleNodeTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/SimpleNodeTest.java index 724f2d914ca..e443c9f867f 100644 --- a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/SimpleNodeTest.java +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/ast/SimpleNodeTest.java @@ -13,7 +13,6 @@ import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; -import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.Set; @@ -209,8 +208,7 @@ public void testParentMethods() { @Test public void testContainsNoInner() { ASTCompilationUnit c = getNodes(ASTCompilationUnit.class, CONTAINS_NO_INNER).iterator().next(); - List res = new ArrayList<>(); - c.findDescendantsOfType(ASTFieldDeclaration.class, res, false); + List res = c.findDescendantsOfType(ASTFieldDeclaration.class); assertTrue(res.isEmpty()); /* * String expectedXml = @@ -254,8 +252,7 @@ public void testContainsNoInner() { @Test public void testContainsNoInnerWithAnonInner() { ASTCompilationUnit c = getNodes(ASTCompilationUnit.class, CONTAINS_NO_INNER_WITH_ANON_INNER).iterator().next(); - List res = new ArrayList<>(); - c.findDescendantsOfType(ASTFieldDeclaration.class, res, false); + List res = c.findDescendantsOfType(ASTFieldDeclaration.class); assertTrue(res.isEmpty()); } diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/metrics/JavaMetricsVisitorTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/metrics/JavaMetricsVisitorTest.java deleted file mode 100644 index 7807515397f..00000000000 --- a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/metrics/JavaMetricsVisitorTest.java +++ /dev/null @@ -1,111 +0,0 @@ -/** - * BSD-style license; for more info see http://pmd.sourceforge.net/license.html - */ - -package net.sourceforge.pmd.lang.java.metrics; - -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; - -import org.junit.Test; - -import net.sourceforge.pmd.lang.LanguageVersionHandler; -import net.sourceforge.pmd.lang.java.ParserTstUtil; -import net.sourceforge.pmd.lang.java.ast.ASTCompilationUnit; -import net.sourceforge.pmd.lang.java.ast.ASTMethodDeclaration; -import net.sourceforge.pmd.lang.java.ast.JavaParserVisitorAdapter; -import net.sourceforge.pmd.lang.java.ast.JavaQualifiedName; -import net.sourceforge.pmd.lang.java.metrics.signature.JavaFieldSigMask; -import net.sourceforge.pmd.lang.java.metrics.signature.JavaOperationSigMask; -import net.sourceforge.pmd.lang.java.metrics.signature.JavaOperationSignature.Role; -import net.sourceforge.pmd.lang.java.metrics.signature.JavaSignature.Visibility; -import net.sourceforge.pmd.lang.java.metrics.testdata.MetricsVisitorTestData; - -/** - * Tests of the metrics visitor. - * - * @author Clément Fournier - */ -public class JavaMetricsVisitorTest { - - - @Test - public void testPackageStatsNotNull() { - assertNotNull(JavaMetrics.getFacade().getTopLevelPackageStats()); - } - - - @Test - public void testOperationsAreThere() { - ASTCompilationUnit acu = parseAndVisitForClass(MetricsVisitorTestData.class); - - final JavaSignatureMatcher toplevel = JavaMetrics.getFacade().getTopLevelPackageStats(); - - final JavaOperationSigMask opMask = new JavaOperationSigMask(); - - // We could parse qnames from string but probably simpler to do that - acu.jjtAccept(new JavaParserVisitorAdapter() { - @Override - public Object visit(ASTMethodDeclaration node, Object data) { - assertTrue(toplevel.hasMatchingSig(node.getQualifiedName(), opMask)); - return data; - } - }, null); - } - - - @Test - public void testFieldsAreThere() { - parseAndVisitForClass(MetricsVisitorTestData.class); - - - final JavaSignatureMatcher toplevel = JavaMetrics.getFacade().getTopLevelPackageStats(); - - final JavaFieldSigMask fieldSigMask = new JavaFieldSigMask(); - - JavaQualifiedName clazz = JavaQualifiedName.ofString("net.sourceforge.pmd.lang.java" - + ".metrics.testdata" - + ".MetricsVisitorTestData"); - String[] fieldNames = {"x", "y", "z", "t"}; - Visibility[] visibilities = {Visibility.PUBLIC, Visibility.PRIVATE, Visibility.PROTECTED, Visibility.PACKAGE}; - - for (int i = 0; i < fieldNames.length; i++) { - fieldSigMask.restrictVisibilitiesTo(visibilities[i]); - assertTrue(toplevel.hasMatchingSig(clazz, fieldNames[i], fieldSigMask)); - } - } - - - // this test is probably useless, SignatureTest and SigMaskTest already ensure signatures and sigmask have no - // problem - @Test - public void testStaticOperationsSig() { - parseAndVisitForClass(MetricsVisitorTestData.class); - - final JavaSignatureMatcher toplevel = JavaMetrics.getFacade().getTopLevelPackageStats(); - - final JavaOperationSigMask operationSigMask = new JavaOperationSigMask(); - operationSigMask.restrictRolesTo(Role.STATIC); - - JavaQualifiedName q1 = JavaQualifiedName.ofString("net.sourceforge.pmd.lang.java" - + ".metrics.testdata" - + ".MetricsVisitorTestData#mystatic1()"); - - assertTrue(toplevel.hasMatchingSig(q1, operationSigMask)); - - operationSigMask.coverAllRoles(); - operationSigMask.forbid(Role.STATIC); - - assertFalse(toplevel.hasMatchingSig(q1, operationSigMask)); - } - - - static ASTCompilationUnit parseAndVisitForClass(Class clazz) { - ASTCompilationUnit acu = ParserTstUtil.parseJavaDefaultVersion(clazz); - LanguageVersionHandler handler = ParserTstUtil.getDefaultLanguageVersionHandler(); - handler.getTypeResolutionFacade(JavaMetricsVisitorTest.class.getClassLoader()).start(acu); - handler.getMetricsVisitorFacade().start(acu); - return acu; - } -} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/metrics/PackageStatsTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/metrics/PackageStatsTest.java deleted file mode 100644 index 08ae711e0de..00000000000 --- a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/metrics/PackageStatsTest.java +++ /dev/null @@ -1,91 +0,0 @@ -/** - * BSD-style license; for more info see http://pmd.sourceforge.net/license.html - */ - -package net.sourceforge.pmd.lang.java.metrics; - -import static net.sourceforge.pmd.lang.java.ParserTstUtil.getOrderedNodes; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; - -import org.junit.Before; -import org.junit.Test; - -import net.sourceforge.pmd.lang.java.ast.ASTFieldDeclaration; -import net.sourceforge.pmd.lang.java.ast.ASTMethodDeclaration; -import net.sourceforge.pmd.lang.java.ast.ASTMethodOrConstructorDeclaration; -import net.sourceforge.pmd.lang.java.ast.JavaQualifiedName; -import net.sourceforge.pmd.lang.java.metrics.signature.JavaFieldSigMask; -import net.sourceforge.pmd.lang.java.metrics.signature.JavaFieldSignature; -import net.sourceforge.pmd.lang.java.metrics.signature.JavaOperationSigMask; -import net.sourceforge.pmd.lang.java.metrics.signature.JavaOperationSignature; - -/** - * Tests functionality of PackageStats, so both its ProjectMemoizer and JavaSignatureMatcher interfaces. - * - * @author Clément Fournier - */ -public class PackageStatsTest { - - private PackageStats pack; - - - @Before - public void setUp() { - pack = new PackageStats(); - } - - - @Test - public void testAddClass() { - JavaQualifiedName qname = JavaQualifiedName.ofString("org.foo.Boo"); - - assertNull(pack.getClassStats(qname, false)); - assertNotNull(pack.getClassStats(qname, true)); - - // now it's added, this shouldn't return null - assertNotNull(pack.getClassStats(qname, false)); - } - - - @Test - public void testAddOperation() { - final String TEST = "package org.foo; class Boo{ " - + "public void foo(){}}"; - - ASTMethodOrConstructorDeclaration node = getOrderedNodes(ASTMethodDeclaration.class, TEST).get(0); - - JavaQualifiedName qname = node.getQualifiedName(); - JavaOperationSignature signature = JavaOperationSignature.buildFor(node); - - assertFalse(pack.hasMatchingSig(qname, new JavaOperationSigMask())); - - ClassStats clazz = pack.getClassStats(qname, true); - clazz.addOperation("foo()", signature); - assertTrue(pack.hasMatchingSig(qname, new JavaOperationSigMask())); - } - - - @Test - public void testAddField() { - final String TEST = "package org.foo; class Boo{ " - + "public String bar;}"; - - ASTFieldDeclaration node = getOrderedNodes(ASTFieldDeclaration.class, TEST).get(0); - - JavaQualifiedName qname = JavaQualifiedName.ofString("org.foo.Boo"); - String fieldName = "bar"; - JavaFieldSignature signature = JavaFieldSignature.buildFor(node); - - assertFalse(pack.hasMatchingSig(qname, fieldName, new JavaFieldSigMask())); - - ClassStats clazz = pack.getClassStats(qname, true); - clazz.addField(fieldName, signature); - assertTrue(pack.hasMatchingSig(qname, fieldName, new JavaFieldSigMask())); - } - - - -} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/metrics/ProjectMemoizerTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/metrics/ProjectMemoizerTest.java index 5c8d16374f2..eee8a7152b2 100644 --- a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/metrics/ProjectMemoizerTest.java +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/metrics/ProjectMemoizerTest.java @@ -4,7 +4,7 @@ package net.sourceforge.pmd.lang.java.metrics; -import static net.sourceforge.pmd.lang.java.metrics.JavaMetricsVisitorTest.parseAndVisitForClass; + import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotEquals; @@ -14,10 +14,12 @@ import org.junit.Test; +import net.sourceforge.pmd.lang.java.ParserTstUtil; import net.sourceforge.pmd.lang.java.ast.ASTAnyTypeDeclaration; import net.sourceforge.pmd.lang.java.ast.ASTCompilationUnit; import net.sourceforge.pmd.lang.java.ast.ASTMethodOrConstructorDeclaration; import net.sourceforge.pmd.lang.java.ast.JavaParserVisitorReducedAdapter; +import net.sourceforge.pmd.lang.java.ast.MethodLikeNode; import net.sourceforge.pmd.lang.java.metrics.impl.AbstractJavaClassMetric; import net.sourceforge.pmd.lang.java.metrics.impl.AbstractJavaOperationMetric; import net.sourceforge.pmd.lang.java.metrics.testdata.MetricsVisitorTestData; @@ -32,12 +34,12 @@ public class ProjectMemoizerTest { private MetricKey classMetricKey = MetricKeyUtil.of(null, new RandomClassMetric()); - private MetricKey opMetricKey = MetricKeyUtil.of(null, new RandomOperationMetric()); + private MetricKey opMetricKey = MetricKeyUtil.of(null, new RandomOperationMetric()); @Test public void memoizationTest() { - ASTCompilationUnit acu = parseAndVisitForClass(MetricsVisitorTestData.class); + ASTCompilationUnit acu = ParserTstUtil.parseJavaDefaultVersion(MetricsVisitorTestData.class); List expected = visitWith(acu, true); List real = visitWith(acu, false); @@ -48,7 +50,8 @@ public void memoizationTest() { @Test public void forceMemoizationTest() { - ASTCompilationUnit acu = parseAndVisitForClass(MetricsVisitorTestData.class); + + ASTCompilationUnit acu = ParserTstUtil.parseJavaDefaultVersion(MetricsVisitorTestData.class); List reference = visitWith(acu, true); List real = visitWith(acu, true); @@ -70,7 +73,7 @@ private List visitWith(ASTCompilationUnit acu, final boolean force) { acu.jjtAccept(new JavaParserVisitorReducedAdapter() { @Override public Object visit(ASTMethodOrConstructorDeclaration node, Object data) { - MetricMemoizer op = toplevel.getOperationMemoizer(node.getQualifiedName()); + MetricMemoizer op = toplevel.getOperationMemoizer(node.getQualifiedName()); result.add((int) JavaMetricsComputer.INSTANCE.computeForOperation(opMetricKey, node, force, MetricOptions.emptyOptions(), op)); return super.visit(node, data); @@ -96,7 +99,7 @@ private class RandomOperationMetric extends AbstractJavaOperationMetric { @Override - public double computeFor(ASTMethodOrConstructorDeclaration node, MetricOptions options) { + public double computeFor(MethodLikeNode node, MetricOptions options) { return random.nextInt(); } } diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/metrics/SigMaskTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/metrics/SigMaskTest.java index 279c9ed55c7..ac730d87c4a 100644 --- a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/metrics/SigMaskTest.java +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/metrics/SigMaskTest.java @@ -16,12 +16,12 @@ import net.sourceforge.pmd.lang.java.ast.ASTFieldDeclaration; import net.sourceforge.pmd.lang.java.ast.ASTMethodDeclaration; import net.sourceforge.pmd.lang.java.ast.ASTMethodOrConstructorDeclaration; -import net.sourceforge.pmd.lang.java.metrics.signature.JavaFieldSigMask; -import net.sourceforge.pmd.lang.java.metrics.signature.JavaFieldSignature; -import net.sourceforge.pmd.lang.java.metrics.signature.JavaOperationSigMask; -import net.sourceforge.pmd.lang.java.metrics.signature.JavaOperationSignature; -import net.sourceforge.pmd.lang.java.metrics.signature.JavaOperationSignature.Role; -import net.sourceforge.pmd.lang.java.metrics.signature.JavaSignature.Visibility; +import net.sourceforge.pmd.lang.java.multifile.signature.JavaFieldSigMask; +import net.sourceforge.pmd.lang.java.multifile.signature.JavaFieldSignature; +import net.sourceforge.pmd.lang.java.multifile.signature.JavaOperationSigMask; +import net.sourceforge.pmd.lang.java.multifile.signature.JavaOperationSignature; +import net.sourceforge.pmd.lang.java.multifile.signature.JavaOperationSignature.Role; +import net.sourceforge.pmd.lang.java.multifile.signature.JavaSignature.Visibility; import net.sourceforge.pmd.lang.metrics.SigMask; /** @@ -108,7 +108,7 @@ public void testEmptyFieldMask() { public void testFinalFields() { List nodes = getOrderedNodes(ASTFieldDeclaration.class, TEST_FIELDS); JavaFieldSigMask mask = new JavaFieldSigMask(); - mask.coverFinal(false); + mask.forbidFinal(); for (ASTFieldDeclaration node : nodes) { if (node.isFinal()) { @@ -123,7 +123,7 @@ public void testFinalFields() { public void testStaticFields() { List nodes = getOrderedNodes(ASTFieldDeclaration.class, TEST_FIELDS); JavaFieldSigMask mask = new JavaFieldSigMask(); - mask.coverStatic(false); + mask.forbidStatic(); for (ASTFieldDeclaration node : nodes) { if (node.isStatic()) { @@ -188,7 +188,7 @@ public void testOperationVisibility() { TEST_OPERATIONS); JavaOperationSigMask mask = new JavaOperationSigMask(); - mask.coverAbstract(true); + mask.coverAbstract(); mask.restrictVisibilitiesTo(Visibility.PUBLIC); @@ -237,7 +237,7 @@ public void testOperationRoles() { TEST_OPERATIONS); JavaOperationSigMask mask = new JavaOperationSigMask(); mask.restrictRolesTo(Role.STATIC); - mask.coverAbstract(true); + mask.coverAbstract(); for (ASTMethodOrConstructorDeclaration node : nodes) { if (node.isStatic()) { diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/metrics/SignatureTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/metrics/SignatureTest.java index 47bb966fe83..dab6aa719ec 100644 --- a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/metrics/SignatureTest.java +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/metrics/SignatureTest.java @@ -20,13 +20,13 @@ import net.sourceforge.pmd.lang.java.ast.ASTMethodDeclaration; import net.sourceforge.pmd.lang.java.ast.ASTMethodOrConstructorDeclaration; import net.sourceforge.pmd.lang.java.ast.JavaParserVisitorAdapter; -import net.sourceforge.pmd.lang.java.metrics.signature.JavaFieldSignature; -import net.sourceforge.pmd.lang.java.metrics.signature.JavaOperationSignature; -import net.sourceforge.pmd.lang.java.metrics.signature.JavaOperationSignature.Role; -import net.sourceforge.pmd.lang.java.metrics.signature.JavaSignature; -import net.sourceforge.pmd.lang.java.metrics.signature.JavaSignature.Visibility; import net.sourceforge.pmd.lang.java.metrics.testdata.GetterDetection; import net.sourceforge.pmd.lang.java.metrics.testdata.SetterDetection; +import net.sourceforge.pmd.lang.java.multifile.signature.JavaFieldSignature; +import net.sourceforge.pmd.lang.java.multifile.signature.JavaOperationSignature; +import net.sourceforge.pmd.lang.java.multifile.signature.JavaOperationSignature.Role; +import net.sourceforge.pmd.lang.java.multifile.signature.JavaSignature; +import net.sourceforge.pmd.lang.java.multifile.signature.JavaSignature.Visibility; /** * Test class for {@link JavaSignature} and its subclasses. diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/metrics/impl/AbstractMetricTestRule.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/metrics/impl/AbstractMetricTestRule.java index 6c48f87f4eb..bf24029644f 100644 --- a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/metrics/impl/AbstractMetricTestRule.java +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/metrics/impl/AbstractMetricTestRule.java @@ -11,7 +11,7 @@ import net.sourceforge.pmd.lang.java.ast.ASTAnyTypeDeclaration; import net.sourceforge.pmd.lang.java.ast.ASTCompilationUnit; -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.JavaClassMetricKey; import net.sourceforge.pmd.lang.java.metrics.api.JavaOperationMetricKey; @@ -132,7 +132,7 @@ private String niceDoubleString(double val) { if (val == (int) val) { return String.valueOf((int) val); } else { - return String.format(Locale.ROOT, "%." + 4 + "f", val); + return String.format(Locale.ROOT, "%.4f", val); } } @@ -157,7 +157,7 @@ public Object visit(ASTAnyTypeDeclaration node, Object data) { @Override - public Object visit(ASTMethodOrConstructorDeclaration node, Object data) { + public Object visit(MethodLikeNode node, Object data) { if (opKey != null && reportMethods && opKey.supports(node)) { double methodValue = JavaMetrics.get(opKey, node, metricOptions); if (methodValue >= reportLevel) { @@ -165,6 +165,6 @@ public Object visit(ASTMethodOrConstructorDeclaration node, Object data) { "" + niceDoubleString(methodValue), }); } } - return data; + return super.visit(node, data); } } 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 b99a1c72a5f..5a64ad0efed 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 @@ -43,7 +43,6 @@ private Rule makeXpathRuleFromXPath(String xpath) { rule.setXPath(xpath); rule.setMessage(VIOLATION_MESSAGE); rule.setLanguage(LanguageRegistry.getLanguage(JavaLanguageModule.NAME)); - rule.setUsesMetrics(); return rule; } @@ -54,6 +53,7 @@ private Iterator getViolations(Rule rule, String code) throws PMD 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(); @@ -73,7 +73,7 @@ public void testWellFormedClassMetricRule() throws PMDException { @Test public void testWellFormedOperationMetricRule() throws PMDException { Rule rule = makeXpathRuleFromXPath("//ConstructorDeclaration[metric('CYCLO') > 1]"); - final String code = "class Foo { Foo() {if(true){}} }"; + final String code = "class Goo { Goo() {if(true){}} }"; Iterator violations = getViolations(rule, code); assertTrue(violations.hasNext()); @@ -83,7 +83,7 @@ public void testWellFormedOperationMetricRule() throws PMDException { @Test public void testBadCase() throws PMDException { Rule rule = makeXpathRuleFromXPath("//ConstructorDeclaration[metric('cYclo') > 1]"); - final String code = "class Foo { Foo() {if(true){}} }"; + final String code = "class Hoo { Hoo() {if(true){}} }"; Iterator violations = getViolations(rule, code); assertTrue(violations.hasNext()); @@ -93,7 +93,7 @@ public void testBadCase() throws PMDException { @Test public void testNonexistentMetric() throws Exception { testWithExpectedException("//ConstructorDeclaration[metric('FOOBAR') > 1]", - "class Foo { Foo() {if(true){}} }", + "class Joo { Joo() {if(true){}} }", IllegalArgumentException.class, MetricFunction.badOperationMetricKeyMessage()); } @@ -102,7 +102,7 @@ public void testNonexistentMetric() throws Exception { @Test public void testWrongNodeTypeGeneric() throws Exception { testWithExpectedException("//IfStatement[metric('NCSS') > 1]", - "class Foo { Foo() {if(true){}} }", + "class Koo { Koo() {if(true){}} }", IllegalStateException.class, MetricFunction.genericBadNodeMessage()); } @@ -111,7 +111,7 @@ public void testWrongNodeTypeGeneric() throws Exception { @Test public void testWrongMetricKeyForTypeDeclaration() throws Exception { testWithExpectedException("//EnumDeclaration[metric('CYCLO') > 1]", - "enum Foo { FOO; }", + "enum Loo { FOO; }", IllegalArgumentException.class, MetricFunction.badClassMetricKeyMessage()); } @@ -120,7 +120,7 @@ public void testWrongMetricKeyForTypeDeclaration() throws Exception { @Test public void testWrongMetricKeyForOperationDeclaration() throws Exception { testWithExpectedException("//MethodDeclaration[metric('WMC') > 1]", - "class Foo { void foo() {if(true){}} }", + "class Moo { void foo() {if(true){}} }", IllegalArgumentException.class, MetricFunction.badOperationMetricKeyMessage()); } diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/multifile/ClassStatsTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/multifile/ClassStatsTest.java new file mode 100644 index 00000000000..aed08671d8a --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/multifile/ClassStatsTest.java @@ -0,0 +1,121 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.multifile; + + +import static org.junit.Assert.assertEquals; + +import org.junit.Before; +import org.junit.Ignore; +import org.junit.Test; + +import net.sourceforge.pmd.lang.java.multifile.signature.JavaFieldSigMask; +import net.sourceforge.pmd.lang.java.multifile.signature.JavaOperationSigMask; +import net.sourceforge.pmd.lang.java.multifile.signature.JavaOperationSignature.Role; +import net.sourceforge.pmd.lang.java.multifile.signature.JavaSignature.Visibility; +import net.sourceforge.pmd.lang.java.multifile.testdata.SignatureCountTestData; +import net.sourceforge.pmd.lang.java.qname.QualifiedNameFactory; + +/** + * @author Clément Fournier + */ +public class ClassStatsTest { + + @Before + public void resetMultifile() { + PackageStats.INSTANCE.reset(); + } + + + @Test + @Ignore("Exception in typeresolution visit") + public void testCountOpSigs() { + + JavaMultifileVisitorTest.parseAndVisitForClass(SignatureCountTestData.class); + + final ProjectMirror toplevel = PackageStats.INSTANCE; + + final ClassMirror classMirror = toplevel.getClassMirror(QualifiedNameFactory.ofClass(SignatureCountTestData.class)); + + final FluentOperationSigMask opSigMask = new FluentOperationSigMask(); + + opSigMask.mask.coverAbstract(); + opSigMask.restrictRolesTo(Role.STATIC); + + assertEquals(4, classMirror.countMatchingOpSigs(opSigMask.mask)); + assertEquals(2, classMirror.countMatchingOpSigs(opSigMask.restrictVisibilitiesTo(Visibility.PRIVATE))); + assertEquals(2, classMirror.countMatchingOpSigs(opSigMask.restrictVisibilitiesTo(Visibility.PUBLIC))); + + opSigMask.restrictRolesTo(Role.METHOD).coverAllVisibilities(); + + assertEquals(6, classMirror.countMatchingOpSigs(opSigMask.mask)); + assertEquals(1, classMirror.countMatchingOpSigs(opSigMask.restrictVisibilitiesTo(Visibility.PUBLIC))); + assertEquals(1, classMirror.countMatchingOpSigs(opSigMask.restrictVisibilitiesTo(Visibility.PRIVATE))); + assertEquals(4, classMirror.countMatchingOpSigs(opSigMask.restrictVisibilitiesTo(Visibility.PROTECTED))); + assertEquals(2, classMirror.countMatchingOpSigs(opSigMask.forbidAbstract())); + + opSigMask.restrictRolesTo(Role.GETTER_OR_SETTER).coverAllVisibilities(); + + assertEquals(8, classMirror.countMatchingOpSigs(opSigMask.mask)); + assertEquals(4, classMirror.countMatchingOpSigs(opSigMask.restrictVisibilitiesTo(Visibility.PACKAGE))); + assertEquals(4, classMirror.countMatchingOpSigs(opSigMask.restrictVisibilitiesTo(Visibility.PUBLIC))); + + opSigMask.restrictRolesTo(Role.CONSTRUCTOR).coverAllVisibilities(); + + assertEquals(3, classMirror.countMatchingOpSigs(opSigMask.mask)); + assertEquals(3, classMirror.countMatchingOpSigs(opSigMask.restrictVisibilitiesTo(Visibility.PUBLIC))); + + + FluentFieldSigMask fieldSigMask = new FluentFieldSigMask(); + + assertEquals(6, classMirror.countMatchingFieldSigs(fieldSigMask.mask)); + assertEquals(3, classMirror.countMatchingFieldSigs(fieldSigMask.restrictVisibilitiesTo(Visibility.PUBLIC))); + assertEquals(1, classMirror.countMatchingFieldSigs(fieldSigMask.restrictVisibilitiesTo(Visibility.PROTECTED))); + assertEquals(2, classMirror.countMatchingFieldSigs(fieldSigMask.restrictVisibilitiesTo(Visibility.PRIVATE))); + + fieldSigMask.mask.forbidFinal(); + + assertEquals(0, classMirror.countMatchingFieldSigs(fieldSigMask.restrictVisibilitiesTo(Visibility.PRIVATE))); + + + } + + // Containers to clear up tests + private class FluentOperationSigMask { + + JavaOperationSigMask mask = new JavaOperationSigMask(); + + + JavaOperationSigMask forbidAbstract() { + mask.coverAbstract(false); + return mask; + } + + + JavaOperationSigMask restrictVisibilitiesTo(Visibility... visibilities) { + mask.restrictVisibilitiesTo(visibilities); + return mask; + } + + + JavaOperationSigMask restrictRolesTo(Role... roles) { + mask.restrictRolesTo(roles); + return mask; + } + } + + private class FluentFieldSigMask { + + JavaFieldSigMask mask = new JavaFieldSigMask(); + + + JavaFieldSigMask restrictVisibilitiesTo(Visibility... visibilities) { + mask.restrictVisibilitiesTo(visibilities); + return mask; + } + } + + +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/multifile/JavaMultifileVisitorTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/multifile/JavaMultifileVisitorTest.java new file mode 100644 index 00000000000..28524e29a6f --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/multifile/JavaMultifileVisitorTest.java @@ -0,0 +1,149 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.multifile; + +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import org.junit.Before; +import org.junit.Test; + +import net.sourceforge.pmd.lang.LanguageVersionHandler; +import net.sourceforge.pmd.lang.java.ParserTstUtil; +import net.sourceforge.pmd.lang.java.ast.ASTCompilationUnit; +import net.sourceforge.pmd.lang.java.ast.ASTMethodDeclaration; +import net.sourceforge.pmd.lang.java.ast.JavaParserVisitorAdapter; +import net.sourceforge.pmd.lang.java.ast.JavaQualifiedName; +import net.sourceforge.pmd.lang.java.multifile.signature.JavaFieldSigMask; +import net.sourceforge.pmd.lang.java.multifile.signature.JavaOperationSigMask; +import net.sourceforge.pmd.lang.java.multifile.signature.JavaOperationSignature.Role; +import net.sourceforge.pmd.lang.java.multifile.signature.JavaSignature.Visibility; +import net.sourceforge.pmd.lang.java.multifile.testdata.MultifileVisitorTestData2; +import net.sourceforge.pmd.lang.java.qname.JavaOperationQualifiedName; +import net.sourceforge.pmd.lang.java.qname.JavaTypeQualifiedName; +import net.sourceforge.pmd.lang.java.qname.QualifiedNameFactory; + +/** + * Tests of the multifile visitor. + * + * @author Clément Fournier + */ +public class JavaMultifileVisitorTest { + + + @Test + public void testPackageStatsNotNull() { + assertNotNull(PackageStats.INSTANCE); + } + + + @Before + public void resetMultifile() { + PackageStats.INSTANCE.reset(); + } + + + @Test + public void testOperationsAreThere() { + ASTCompilationUnit acu = parseAndVisitForClass(MultifileVisitorTestData2.class); + + final ProjectMirror toplevel = PackageStats.INSTANCE; + + final JavaOperationSigMask opMask = new JavaOperationSigMask(); + + // We could parse qnames from string but probably simpler to do that + acu.jjtAccept(new JavaParserVisitorAdapter() { + @Override + public Object visit(ASTMethodDeclaration node, Object data) { + assertTrue(toplevel.hasMatchingSig(node.getQualifiedName(), opMask)); + return data; + } + }, null); + } + + + @Test + public void testFieldsAreThere() { + parseAndVisitForClass(MultifileVisitorTestData2.class); + + final ProjectMirror toplevel = PackageStats.INSTANCE; + + final JavaFieldSigMask fieldSigMask = new JavaFieldSigMask(); + + JavaTypeQualifiedName clazz = QualifiedNameFactory.ofClass(MultifileVisitorTestData2.class); + + String[] fieldNames = {"x", "y", "z", "t"}; + Visibility[] visibilities = {Visibility.PUBLIC, Visibility.PRIVATE, Visibility.PROTECTED, Visibility.PACKAGE}; + + for (int i = 0; i < fieldNames.length; i++) { + fieldSigMask.restrictVisibilitiesTo(visibilities[i]); + assertTrue(toplevel.hasMatchingSig(clazz, fieldNames[i], fieldSigMask)); + } + } + + + @Test + public void testBothClassesOperationsAreThere() { + parseAndVisitForClass(MultifileVisitorTestData2.class); + parseAndVisitForClass(MultifileVisitorTestData2.class); + + final ProjectMirror toplevel = PackageStats.INSTANCE; + + final JavaOperationSigMask operationSigMask = new JavaOperationSigMask(); + + JavaQualifiedName clazz = QualifiedNameFactory.ofClass(MultifileVisitorTestData2.class); + JavaQualifiedName clazz2 = QualifiedNameFactory.ofClass(MultifileVisitorTestData2.class); + + String[] opNames = {"getX()", "getY()", "setX(String)", "setY(String)", + "mymethod1()", "mymethod2()", "mystatic1()", + "mystatic2(String)", "mystatic2(String, String)", }; + Role[] roles = {Role.GETTER_OR_SETTER, Role.GETTER_OR_SETTER, Role.GETTER_OR_SETTER, Role.GETTER_OR_SETTER, + Role.METHOD, Role.METHOD, Role.STATIC, Role.STATIC, Role.STATIC, }; + + + for (int i = 0; i < opNames.length; i++) { + operationSigMask.restrictRolesTo(roles[i]); + JavaOperationQualifiedName name1 = (JavaOperationQualifiedName) QualifiedNameFactory.ofString(clazz.toString() + "#" + opNames[i]); + JavaOperationQualifiedName name2 = (JavaOperationQualifiedName) QualifiedNameFactory.ofString(clazz2.toString() + "#" + opNames[i]); + + assertTrue(toplevel.hasMatchingSig(name1, operationSigMask)); + assertTrue(toplevel.hasMatchingSig(name2, operationSigMask)); + } + } + + + @Test + public void testBothClassesFieldsAreThere() { + parseAndVisitForClass(MultifileVisitorTestData2.class); + parseAndVisitForClass(MultifileVisitorTestData2.class); + + final ProjectMirror toplevel = PackageStats.INSTANCE; + + final JavaFieldSigMask fieldSigMask = new JavaFieldSigMask(); + + JavaTypeQualifiedName clazz = QualifiedNameFactory.ofClass(MultifileVisitorTestData2.class); + JavaTypeQualifiedName clazz2 = QualifiedNameFactory.ofClass(MultifileVisitorTestData2.class); + + String[] fieldNames = {"x", "y", "z", "t"}; + Visibility[] visibilities = {Visibility.PUBLIC, Visibility.PRIVATE, Visibility.PROTECTED, Visibility.PACKAGE}; + + + for (int i = 0; i < fieldNames.length; i++) { + fieldSigMask.restrictVisibilitiesTo(visibilities[i]); + assertTrue(toplevel.hasMatchingSig(clazz, fieldNames[i], fieldSigMask)); + assertTrue(toplevel.hasMatchingSig(clazz2, fieldNames[i], fieldSigMask)); + } + } + + + static ASTCompilationUnit parseAndVisitForClass(Class clazz) { + ASTCompilationUnit acu = ParserTstUtil.parseJavaDefaultVersion(clazz); + LanguageVersionHandler handler = ParserTstUtil.getDefaultLanguageVersionHandler(); + handler.getQualifiedNameResolutionFacade(JavaMultifileVisitorTest.class.getClassLoader()).start(acu); + handler.getTypeResolutionFacade(JavaMultifileVisitorTest.class.getClassLoader()).start(acu); + handler.getMultifileFacade().start(acu); + return acu; + } +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/multifile/PackageStatsTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/multifile/PackageStatsTest.java new file mode 100644 index 00000000000..c5d536a6c1a --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/multifile/PackageStatsTest.java @@ -0,0 +1,93 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.multifile; + +import static net.sourceforge.pmd.lang.java.ParserTstUtil.getOrderedNodes; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +import org.junit.Before; +import org.junit.Test; + +import net.sourceforge.pmd.lang.java.ast.ASTFieldDeclaration; +import net.sourceforge.pmd.lang.java.ast.ASTMethodDeclaration; +import net.sourceforge.pmd.lang.java.ast.ASTMethodOrConstructorDeclaration; +import net.sourceforge.pmd.lang.java.multifile.signature.JavaFieldSigMask; +import net.sourceforge.pmd.lang.java.multifile.signature.JavaFieldSignature; +import net.sourceforge.pmd.lang.java.multifile.signature.JavaOperationSigMask; +import net.sourceforge.pmd.lang.java.multifile.signature.JavaOperationSignature; +import net.sourceforge.pmd.lang.java.qname.JavaOperationQualifiedName; +import net.sourceforge.pmd.lang.java.qname.JavaTypeQualifiedName; +import net.sourceforge.pmd.lang.java.qname.QualifiedNameFactory; + +/** + * Tests functionality of PackageStats + * + * @author Clément Fournier + */ +public class PackageStatsTest { + + private PackageStats pack; + + + @Before + public void setUp() { + pack = PackageStats.INSTANCE; + pack.reset(); + } + + + @Test + public void testAddClass() { + JavaTypeQualifiedName qname = (JavaTypeQualifiedName) QualifiedNameFactory.ofString("org.foo.Boo"); + + assertNull(pack.getClassStats(qname, false)); + assertNotNull(pack.getClassStats(qname, true)); + + // now it's added, this shouldn't return null + assertNotNull(pack.getClassStats(qname, false)); + } + + + @Test + public void testAddOperation() { + final String TEST = "package org.foo; class Boo{ " + + "public void foo(){}}"; + + ASTMethodOrConstructorDeclaration node = getOrderedNodes(ASTMethodDeclaration.class, TEST).get(0); + + JavaOperationQualifiedName qname = node.getQualifiedName(); + JavaOperationSignature signature = JavaOperationSignature.buildFor(node); + + assertFalse(pack.hasMatchingSig(qname, new JavaOperationSigMask())); + + ClassStats clazz = pack.getClassStats(qname.getClassName(), true); + clazz.addOperation("foo()", signature); + assertTrue(pack.hasMatchingSig(qname, new JavaOperationSigMask())); + } + + + @Test + public void testAddField() { + final String TEST = "package org.foo; class Boo{ " + + "public String bar;}"; + + ASTFieldDeclaration node = getOrderedNodes(ASTFieldDeclaration.class, TEST).get(0); + + JavaTypeQualifiedName qname = (JavaTypeQualifiedName) QualifiedNameFactory.ofString("org.foo.Boo"); + String fieldName = "bar"; + JavaFieldSignature signature = JavaFieldSignature.buildFor(node); + + assertFalse(pack.hasMatchingSig(qname, fieldName, new JavaFieldSigMask())); + + ClassStats clazz = pack.getClassStats(qname, true); + clazz.addField(fieldName, signature); + assertTrue(pack.hasMatchingSig(qname, fieldName, new JavaFieldSigMask())); + } + + +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/multifile/testdata/MultifileVisitorTestData.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/multifile/testdata/MultifileVisitorTestData.java new file mode 100644 index 00000000000..6924ace69e6 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/multifile/testdata/MultifileVisitorTestData.java @@ -0,0 +1,75 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.multifile.testdata; + +/** + * Test data for the metrics visitor + * + * @author Clément Fournier + */ +public class MultifileVisitorTestData { + + public String x; + private String y; + protected String z; + String t; + + public MultifileVisitorTestData() { + + } + + private MultifileVisitorTestData(String x) { + + } + + public String getX() { + return x; + } + + public String getY() { + return y; + } + + public void setX(String n) { + x = n; + } + + public void setY(String n) { + y = n; + } + + + public static class NestedClass { + + public NestedClass() { + + } + + public void nestedMethod1() { + + } + } + + public void mymethod1() { + + } + + private void mymethod2() { + + } + + protected static void mystatic1() { + + } + + private static void mystatic2(String k) { + + } + + private static void mystatic2(String k, String l) { + + } + +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/multifile/testdata/MultifileVisitorTestData2.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/multifile/testdata/MultifileVisitorTestData2.java new file mode 100644 index 00000000000..3c8b63a6bb0 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/multifile/testdata/MultifileVisitorTestData2.java @@ -0,0 +1,75 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.multifile.testdata; + +/** + * Test data for the metrics visitor + * + * @author Clément Fournier + */ +public class MultifileVisitorTestData2 { + + public String x; + private String y; + protected String z; + String t; + + public MultifileVisitorTestData2() { + + } + + private MultifileVisitorTestData2(String x) { + + } + + public String getX() { + return x; + } + + public String getY() { + return y; + } + + public void setX(String n) { + x = n; + } + + public void setY(String n) { + y = n; + } + + + public static class NestedClass { + + public NestedClass() { + + } + + public void nestedMethod1() { + + } + } + + public void mymethod1() { + + } + + private void mymethod2() { + + } + + protected static void mystatic1() { + + } + + private static void mystatic2(String k) { + + } + + private static void mystatic2(String k, String l) { + + } + +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/multifile/testdata/SignatureCountTestData.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/multifile/testdata/SignatureCountTestData.java new file mode 100644 index 00000000000..2d6d1af385b --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/multifile/testdata/SignatureCountTestData.java @@ -0,0 +1,140 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.multifile.testdata; + +import org.apache.commons.lang3.StringUtils; + +// Fields: +// 2 private static final +// 3 public +// 1 protected + +// Constructors: +// 3 public + +// Getters/ Setters: +// 4 public +// 4 package + +// Static: +// 2 private +// 2 public + +// Methods: +// 1 public +// 1 private +// 2 protected +// 2 protected final +public abstract class SignatureCountTestData { + + private static final int MAX_X = 0; + private static final int MIN_X = 0; + public int x; + public int y; + public int z; + protected int t; + + + public SignatureCountTestData(int x, int y, int z, int t) { + this.x = x; + this.y = y; + this.z = z; + this.t = t; + } + + + public SignatureCountTestData(int x, int y, int z) {} + + + public SignatureCountTestData(int x, int y) {} + + + public int getX() { + return x; + } + + + void setX(int x) { + this.x = x; + } + + + public int getY() { + return y; + } + + + void setY(int y) { + this.y = y; + } + + + public int getZ() { + return z; + } + + + void setZ(int z) { + this.z = z; + } + + + public int getT() { + return t; + } + + + void setT(int t) { + this.t = t; + } + + + public boolean isNotEmpty(String value) { + return !isEmpty(value); + } + + + private boolean isEmpty(String value) { + return StringUtils.isBlank(value); + } + + + protected boolean isMissing(String value) { + return StringUtils.isEmpty(value); + } + + + protected boolean areSemanticEquals(String a, String b) { + return a.equals(b); + } + + + protected abstract String replaceString(String original, String oldString, String newString); + + + @Deprecated + protected abstract void appendXmlEscaped(StringBuilder buf, String src); + + + public static boolean startsWithAny(String text, String... prefixes) { + return false; + } + + + public static boolean isAnyOf(String text, String... tests) { + return false; + } + + + private static String withoutPrefixes(String text, String... prefixes) { + return text; + } + + + private static void appendXmlEscaped(StringBuilder buf, String src, boolean supportUTF8) { + } + + +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/JavaRuleViolationTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/JavaRuleViolationTest.java index 06174cd3ba8..b9c690f12dd 100644 --- a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/JavaRuleViolationTest.java +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/JavaRuleViolationTest.java @@ -63,6 +63,18 @@ public void testMethodName() { assertEquals("bar", violation.getMethodName()); } + /** + * Tests that the enum name is taken correctly from the given node. + */ + @Test + public void testEnumName() { + ASTCompilationUnit ast = parse("enum Foo {FOO; void bar(int x) {} }"); + ASTMethodDeclaration md = ast.getFirstDescendantOfType(ASTMethodDeclaration.class); + final RuleContext context = new RuleContext(); + final JavaRuleViolation violation = new JavaRuleViolation(null, context, md, null); + assertEquals("Foo", violation.getClassName()); + } + /** * Tests that the class name is taken correctly, even if the node is outside * of a class scope, e.g. a import declaration. diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/XPathRuleTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/XPathRuleTest.java index 623e42797aa..077e1b5db73 100644 --- a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/XPathRuleTest.java +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/XPathRuleTest.java @@ -8,13 +8,16 @@ import java.io.StringReader; import java.util.HashMap; +import java.util.Iterator; import java.util.List; import org.junit.Before; import org.junit.Test; import net.sourceforge.pmd.PMD; +import net.sourceforge.pmd.PMDException; import net.sourceforge.pmd.Report; +import net.sourceforge.pmd.Rule; import net.sourceforge.pmd.RuleContext; import net.sourceforge.pmd.RuleSet; import net.sourceforge.pmd.RuleSetFactory; @@ -32,6 +35,7 @@ import net.sourceforge.pmd.lang.rule.xpath.SaxonXPathRuleQuery; import net.sourceforge.pmd.lang.rule.xpath.XPathRuleQuery; import net.sourceforge.pmd.properties.PropertyDescriptor; +import net.sourceforge.pmd.properties.StringMultiProperty; import net.sourceforge.pmd.properties.StringProperty; import net.sourceforge.pmd.testframework.RuleTst; @@ -53,17 +57,36 @@ public void setUp() { public void testPluginname() throws Exception { rule.setXPath("//VariableDeclaratorId[string-length(@Image) < 3]"); rule.setMessage("{0}"); - PMD p = new PMD(); - RuleContext ctx = new RuleContext(); - Report report = new Report(); - ctx.setReport(report); - ctx.setSourceCodeFilename("n/a"); - RuleSet rules = new RuleSetFactory().createSingleRuleRuleSet(rule); - p.getSourceCodeProcessor().processSourceCode(new StringReader(TEST1), new RuleSets(rules), ctx); + Report report = getReportForTestString(rule, TEST1); RuleViolation rv = report.iterator().next(); assertEquals("a", rv.getDescription()); } + + @Test + public void testXPathMultiProperty() throws Exception { + rule.setXPath("//VariableDeclaratorId[@Image=$forbiddenNames]"); + rule.setMessage("Avoid vars"); + rule.setVersion(XPathRuleQuery.XPATH_2_0); + StringMultiProperty varDescriptor + = StringMultiProperty.named("forbiddenNames") + .desc("Forbidden names") + .defaultValues("forbid1", "forbid2") + .delim('$') + .build(); + + rule.definePropertyDescriptor(varDescriptor); + + Report report = getReportForTestString(rule, TEST3); + Iterator rv = report.iterator(); + int i = 0; + for (; rv.hasNext(); ++i) { + rv.next(); + } + assertEquals(2, i); + } + + @Test public void testVariables() throws Exception { rule.setXPath("//VariableDeclaratorId[@Image=$var]"); @@ -71,17 +94,12 @@ public void testVariables() throws Exception { StringProperty varDescriptor = new StringProperty("var", "Test var", null, 1.0f); rule.definePropertyDescriptor(varDescriptor); rule.setProperty(varDescriptor, "fiddle"); - PMD p = new PMD(); - RuleContext ctx = new RuleContext(); - Report report = new Report(); - ctx.setReport(report); - ctx.setSourceCodeFilename("n/a"); - RuleSet rules = new RuleSetFactory().createSingleRuleRuleSet(rule); - p.getSourceCodeProcessor().processSourceCode(new StringReader(TEST2), new RuleSets(rules), ctx); + Report report = getReportForTestString(rule, TEST2); RuleViolation rv = report.iterator().next(); assertEquals(3, rv.getBeginLine()); } + /** * Test for problem reported in bug #1219 PrimarySuffix/@Image does not work * in some cases in xpath 2.0 @@ -168,8 +186,24 @@ public void testFollowingSibling() throws Exception { assertEquals(5, nodes.get(1).getBeginLine()); } + private static Report getReportForTestString(Rule r, String test) throws PMDException { + PMD p = new PMD(); + RuleContext ctx = new RuleContext(); + Report report = new Report(); + ctx.setReport(report); + ctx.setSourceCodeFilename("n/a"); + RuleSet rules = new RuleSetFactory().createSingleRuleRuleSet(r); + p.getSourceCodeProcessor().processSourceCode(new StringReader(test), new RuleSets(rules), ctx); + return report; + } + + private static final String TEST1 = "public class Foo {" + PMD.EOL + " int a;" + PMD.EOL + "}"; private static final String TEST2 = "public class Foo {" + PMD.EOL + " int faddle;" + PMD.EOL + " int fiddle;" + PMD.EOL + "}"; + + + private static final String TEST3 = "public class Foo {" + PMD.EOL + " int forbid1; int forbid2; int forbid1$forbid2;" + PMD.EOL + "}"; + } diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/android/AndroidRulesTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/android/AndroidRulesTest.java deleted file mode 100644 index 099e33e1c9b..00000000000 --- a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/android/AndroidRulesTest.java +++ /dev/null @@ -1,19 +0,0 @@ -/** - * BSD-style license; for more info see http://pmd.sourceforge.net/license.html - */ - -package net.sourceforge.pmd.lang.java.rule.android; - -import net.sourceforge.pmd.testframework.SimpleAggregatorTst; - -public class AndroidRulesTest extends SimpleAggregatorTst { - - private static final String RULESET = "java-android"; - - @Override - public void setUp() { - addRule(RULESET, "CallSuperFirst"); - addRule(RULESET, "CallSuperLast"); - addRule(RULESET, "DoNotHardCodeSDCard"); - } -} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/basic/BasicRulesTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/basic/BasicRulesTest.java deleted file mode 100644 index ab9f1775ad8..00000000000 --- a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/basic/BasicRulesTest.java +++ /dev/null @@ -1,51 +0,0 @@ -/** - * BSD-style license; for more info see http://pmd.sourceforge.net/license.html - */ - -package net.sourceforge.pmd.lang.java.rule.basic; - -import net.sourceforge.pmd.testframework.SimpleAggregatorTst; - -/** - * Rule tests for the basic ruleset - */ -public class BasicRulesTest extends SimpleAggregatorTst { - - private static final String RULESET = "java-basic"; - - @Override - public void setUp() { - addRule(RULESET, "AvoidBranchingStatementAsLastInLoop"); - addRule(RULESET, "AvoidDecimalLiteralsInBigDecimalConstructor"); - addRule(RULESET, "AvoidMultipleUnaryOperators"); - addRule(RULESET, "AvoidThreadGroup"); - addRule(RULESET, "AvoidUsingHardCodedIP"); - // addRule(RULESET, "AvoidUsingHardCodedURL"); - addRule(RULESET, "AvoidUsingOctalValues"); - addRule(RULESET, "BigIntegerInstantiation"); - addRule(RULESET, "BooleanInstantiation"); - addRule(RULESET, "BrokenNullCheck"); - addRule(RULESET, "CheckResultSet"); - addRule(RULESET, "CheckSkipResult"); - addRule(RULESET, "ClassCastExceptionWithToArray"); - addRule(RULESET, "CollapsibleIfStatements"); - addRule(RULESET, "DoubleCheckedLocking"); - addRule(RULESET, "ExtendsObject"); - addRule(RULESET, "ForLoopShouldBeWhileLoop"); - addRule(RULESET, "JumbledIncrementer"); - addRule(RULESET, "MisplacedNullCheck"); - addRule(RULESET, "OverrideBothEqualsAndHashcode"); - addRule(RULESET, "ReturnFromFinallyBlock"); - addRule(RULESET, "DontCallThreadRun"); - addRule(RULESET, "DontUseFloatTypeForLoopIndices"); - addRule(RULESET, "SimplifiedTernary"); - } - - // Used by DontCallThreadRun test cases - public static class TestThread extends Thread { - @Override - public void run() { - System.out.println("test"); - } - } -} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/AbstractClassWithoutAbstractMethodTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/AbstractClassWithoutAbstractMethodTest.java new file mode 100644 index 00000000000..a7ff179f293 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/AbstractClassWithoutAbstractMethodTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.bestpractices; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class AbstractClassWithoutAbstractMethodTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/AccessorClassGenerationTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/AccessorClassGenerationTest.java new file mode 100644 index 00000000000..73faf421b8a --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/AccessorClassGenerationTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.bestpractices; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class AccessorClassGenerationTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/AccessorMethodGenerationTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/AccessorMethodGenerationTest.java new file mode 100644 index 00000000000..678eac60420 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/AccessorMethodGenerationTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.bestpractices; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class AccessorMethodGenerationTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/ArrayIsStoredDirectlyTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/ArrayIsStoredDirectlyTest.java new file mode 100644 index 00000000000..1df344be6ec --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/ArrayIsStoredDirectlyTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.bestpractices; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class ArrayIsStoredDirectlyTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/AvoidPrintStackTraceTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/AvoidPrintStackTraceTest.java new file mode 100644 index 00000000000..0a52f862d1c --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/AvoidPrintStackTraceTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.bestpractices; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class AvoidPrintStackTraceTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/AvoidReassigningParametersTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/AvoidReassigningParametersTest.java new file mode 100644 index 00000000000..906b82d9662 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/AvoidReassigningParametersTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.bestpractices; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class AvoidReassigningParametersTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/AvoidStringBufferFieldTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/AvoidStringBufferFieldTest.java new file mode 100644 index 00000000000..88219b92573 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/AvoidStringBufferFieldTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.bestpractices; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class AvoidStringBufferFieldTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/AvoidUsingHardCodedIPTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/AvoidUsingHardCodedIPTest.java new file mode 100644 index 00000000000..63e4523ca20 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/AvoidUsingHardCodedIPTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.bestpractices; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class AvoidUsingHardCodedIPTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/CheckResultSetTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/CheckResultSetTest.java new file mode 100644 index 00000000000..effe3630b28 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/CheckResultSetTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.bestpractices; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class CheckResultSetTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/ConstantsInInterfaceTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/ConstantsInInterfaceTest.java new file mode 100644 index 00000000000..e72544c3fd9 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/ConstantsInInterfaceTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.bestpractices; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class ConstantsInInterfaceTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/DefaultLabelNotLastInSwitchStmtTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/DefaultLabelNotLastInSwitchStmtTest.java new file mode 100644 index 00000000000..7c8d1c663a1 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/DefaultLabelNotLastInSwitchStmtTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.bestpractices; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class DefaultLabelNotLastInSwitchStmtTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/ForLoopCanBeForeachTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/ForLoopCanBeForeachTest.java new file mode 100644 index 00000000000..74d7399cc5e --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/ForLoopCanBeForeachTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.bestpractices; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class ForLoopCanBeForeachTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/GuardLogStatementTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/GuardLogStatementTest.java new file mode 100644 index 00000000000..02f1628b389 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/GuardLogStatementTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.bestpractices; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class GuardLogStatementTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/JUnit4SuitesShouldUseSuiteAnnotationTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/JUnit4SuitesShouldUseSuiteAnnotationTest.java new file mode 100644 index 00000000000..7c48d95ab2e --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/JUnit4SuitesShouldUseSuiteAnnotationTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.bestpractices; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class JUnit4SuitesShouldUseSuiteAnnotationTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/JUnit4TestShouldUseAfterAnnotationTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/JUnit4TestShouldUseAfterAnnotationTest.java new file mode 100644 index 00000000000..f29e876d5f1 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/JUnit4TestShouldUseAfterAnnotationTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.bestpractices; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class JUnit4TestShouldUseAfterAnnotationTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/JUnit4TestShouldUseBeforeAnnotationTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/JUnit4TestShouldUseBeforeAnnotationTest.java new file mode 100644 index 00000000000..cd3772d6693 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/JUnit4TestShouldUseBeforeAnnotationTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.bestpractices; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class JUnit4TestShouldUseBeforeAnnotationTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/JUnit4TestShouldUseTestAnnotationTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/JUnit4TestShouldUseTestAnnotationTest.java new file mode 100644 index 00000000000..f348ab0b04c --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/JUnit4TestShouldUseTestAnnotationTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.bestpractices; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class JUnit4TestShouldUseTestAnnotationTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/JUnitAssertionsShouldIncludeMessageTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/JUnitAssertionsShouldIncludeMessageTest.java new file mode 100644 index 00000000000..ab7c80a5be7 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/JUnitAssertionsShouldIncludeMessageTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.bestpractices; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class JUnitAssertionsShouldIncludeMessageTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/JUnitTestContainsTooManyAssertsTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/JUnitTestContainsTooManyAssertsTest.java new file mode 100644 index 00000000000..76656a65d03 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/JUnitTestContainsTooManyAssertsTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.bestpractices; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class JUnitTestContainsTooManyAssertsTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/JUnitTestsShouldIncludeAssertTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/JUnitTestsShouldIncludeAssertTest.java new file mode 100644 index 00000000000..373e30c1096 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/JUnitTestsShouldIncludeAssertTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.bestpractices; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class JUnitTestsShouldIncludeAssertTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/JUnitUseExpectedTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/JUnitUseExpectedTest.java new file mode 100644 index 00000000000..6cef9b5e225 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/JUnitUseExpectedTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.bestpractices; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class JUnitUseExpectedTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/LooseCouplingTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/LooseCouplingTest.java new file mode 100644 index 00000000000..25382d7ae0a --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/LooseCouplingTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.bestpractices; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class LooseCouplingTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/MethodReturnsInternalArrayTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/MethodReturnsInternalArrayTest.java new file mode 100644 index 00000000000..0bf67ba0a8b --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/MethodReturnsInternalArrayTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.bestpractices; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class MethodReturnsInternalArrayTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/MissingOverrideTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/MissingOverrideTest.java new file mode 100644 index 00000000000..edd71d4de35 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/MissingOverrideTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.bestpractices; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class MissingOverrideTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/OneDeclarationPerLineTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/OneDeclarationPerLineTest.java new file mode 100644 index 00000000000..822381222e9 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/OneDeclarationPerLineTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.bestpractices; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class OneDeclarationPerLineTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/PositionLiteralsFirstInCaseInsensitiveComparisonsTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/PositionLiteralsFirstInCaseInsensitiveComparisonsTest.java new file mode 100644 index 00000000000..31f9ae44ffd --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/PositionLiteralsFirstInCaseInsensitiveComparisonsTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.bestpractices; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class PositionLiteralsFirstInCaseInsensitiveComparisonsTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/PositionLiteralsFirstInComparisonsTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/PositionLiteralsFirstInComparisonsTest.java new file mode 100644 index 00000000000..4b47c65a99e --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/PositionLiteralsFirstInComparisonsTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.bestpractices; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class PositionLiteralsFirstInComparisonsTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/PreserveStackTraceTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/PreserveStackTraceTest.java new file mode 100644 index 00000000000..f23c1636c25 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/PreserveStackTraceTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.bestpractices; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class PreserveStackTraceTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/ReplaceEnumerationWithIteratorTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/ReplaceEnumerationWithIteratorTest.java new file mode 100644 index 00000000000..fea13ff280d --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/ReplaceEnumerationWithIteratorTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.bestpractices; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class ReplaceEnumerationWithIteratorTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/ReplaceHashtableWithMapTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/ReplaceHashtableWithMapTest.java new file mode 100644 index 00000000000..d1a6337efac --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/ReplaceHashtableWithMapTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.bestpractices; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class ReplaceHashtableWithMapTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/ReplaceVectorWithListTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/ReplaceVectorWithListTest.java new file mode 100644 index 00000000000..d6f5b31eb46 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/ReplaceVectorWithListTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.bestpractices; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class ReplaceVectorWithListTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/SwitchStmtsShouldHaveDefaultTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/SwitchStmtsShouldHaveDefaultTest.java new file mode 100644 index 00000000000..bbec6a1612c --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/SwitchStmtsShouldHaveDefaultTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.bestpractices; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class SwitchStmtsShouldHaveDefaultTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/SystemPrintlnTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/SystemPrintlnTest.java new file mode 100644 index 00000000000..e17f5ee7bed --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/SystemPrintlnTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.bestpractices; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class SystemPrintlnTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/UnusedFormalParameterTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/UnusedFormalParameterTest.java new file mode 100644 index 00000000000..154952c1f59 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/UnusedFormalParameterTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.bestpractices; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class UnusedFormalParameterTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/UnusedImportsTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/UnusedImportsTest.java new file mode 100644 index 00000000000..32b9d11728c --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/UnusedImportsTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.bestpractices; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class UnusedImportsTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/UnusedLocalVariableTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/UnusedLocalVariableTest.java new file mode 100644 index 00000000000..aad6d106c4f --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/UnusedLocalVariableTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.bestpractices; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class UnusedLocalVariableTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/UnusedPrivateFieldTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/UnusedPrivateFieldTest.java new file mode 100644 index 00000000000..d1b7ba90f6b --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/UnusedPrivateFieldTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.bestpractices; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class UnusedPrivateFieldTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/UnusedPrivateMethodTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/UnusedPrivateMethodTest.java new file mode 100644 index 00000000000..5cc101a04fa --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/UnusedPrivateMethodTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.bestpractices; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class UnusedPrivateMethodTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/UseAssertEqualsInsteadOfAssertTrueTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/UseAssertEqualsInsteadOfAssertTrueTest.java new file mode 100644 index 00000000000..4df42d6789d --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/UseAssertEqualsInsteadOfAssertTrueTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.bestpractices; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class UseAssertEqualsInsteadOfAssertTrueTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/UseAssertNullInsteadOfAssertTrueTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/UseAssertNullInsteadOfAssertTrueTest.java new file mode 100644 index 00000000000..a593a9e0ffa --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/UseAssertNullInsteadOfAssertTrueTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.bestpractices; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class UseAssertNullInsteadOfAssertTrueTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/UseAssertSameInsteadOfAssertTrueTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/UseAssertSameInsteadOfAssertTrueTest.java new file mode 100644 index 00000000000..e874a309b15 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/UseAssertSameInsteadOfAssertTrueTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.bestpractices; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class UseAssertSameInsteadOfAssertTrueTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/UseAssertTrueInsteadOfAssertEqualsTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/UseAssertTrueInsteadOfAssertEqualsTest.java new file mode 100644 index 00000000000..cb8ded71bf6 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/UseAssertTrueInsteadOfAssertEqualsTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.bestpractices; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class UseAssertTrueInsteadOfAssertEqualsTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/UseCollectionIsEmptyTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/UseCollectionIsEmptyTest.java new file mode 100644 index 00000000000..f941133e825 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/UseCollectionIsEmptyTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.bestpractices; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class UseCollectionIsEmptyTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/UseVarargsTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/UseVarargsTest.java new file mode 100644 index 00000000000..a40a5617d9d --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/UseVarargsTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.bestpractices; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class UseVarargsTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/missingoverride/AbsClassWithInterface.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/missingoverride/AbsClassWithInterface.java new file mode 100644 index 00000000000..09d691a4a42 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/missingoverride/AbsClassWithInterface.java @@ -0,0 +1,12 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.bestpractices.missingoverride; + +/** + * @author Clément Fournier + * @since 6.2.0 + */ +public abstract class AbsClassWithInterface implements Runnable { +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/missingoverride/AbstractClass.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/missingoverride/AbstractClass.java new file mode 100644 index 00000000000..cf18ad4a39a --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/missingoverride/AbstractClass.java @@ -0,0 +1,26 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.bestpractices.missingoverride; + +/** + * @author Clément Fournier + * @since 6.2.0 + */ +public abstract class AbstractClass { + + Object fun(String s) { + return new Object(); + } + + + public void arrayParams(String dflt, int[] keys, StringBuilder[] labels) { + } + + + public R generic(T t, R r) { + return r; + } + +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/missingoverride/AmbiguousOverload.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/missingoverride/AmbiguousOverload.java new file mode 100644 index 00000000000..e944f6c3a01 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/missingoverride/AmbiguousOverload.java @@ -0,0 +1,28 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.bestpractices.missingoverride; + +import java.util.Comparator; + + +public class AmbiguousOverload implements Comparator { + + + // only one of those overloads is an override, and so there's only one bridge, + // so we can't choose the inherited overload + + + @Override + public int compare(StringBuilder o1, StringBuilder o2) { + return 0; + } + + + public int compare(String s, String s2) { + return 0; + } + + +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/missingoverride/AnonClassExample.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/missingoverride/AnonClassExample.java new file mode 100644 index 00000000000..9435ec213dd --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/missingoverride/AnonClassExample.java @@ -0,0 +1,17 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.bestpractices.missingoverride; + +public class AnonClassExample { + static { + new Thread(new Runnable() { + // missing + public void run() { + + } + }).start(); + } +} + diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/missingoverride/CloneableInterface.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/missingoverride/CloneableInterface.java new file mode 100644 index 00000000000..e9b7b24a4a8 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/missingoverride/CloneableInterface.java @@ -0,0 +1,15 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.bestpractices.missingoverride; + +/** + * @author Clément Fournier + * @since 6.4.0 + */ +interface CloneableInterface extends Cloneable { + // nothing to report, the method is not inherited + // https://docs.oracle.com/javase/specs/jls/se7/html/jls-9.html#jls-9.6.3.4 + CloneableInterface clone() throws CloneNotSupportedException; +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/missingoverride/CloneableInterfaceOverride.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/missingoverride/CloneableInterfaceOverride.java new file mode 100644 index 00000000000..d6b66f2774d --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/missingoverride/CloneableInterfaceOverride.java @@ -0,0 +1,15 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.bestpractices.missingoverride; + +/** + * @author Clément Fournier + * @since 6.4.0 + */ +public interface CloneableInterfaceOverride extends CloneableInterface { + + @Override + CloneableInterface clone() throws CloneNotSupportedException; +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/missingoverride/ConcreteClass.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/missingoverride/ConcreteClass.java new file mode 100644 index 00000000000..8e4605e7652 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/missingoverride/ConcreteClass.java @@ -0,0 +1,16 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.bestpractices.missingoverride; + +/** + * @author Clément Fournier + * @since 6.2.0 + */ +public class ConcreteClass extends AbstractClass { + @Override + Object fun(String s) { + return null; + } +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/missingoverride/ConcreteClassArrayParams.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/missingoverride/ConcreteClassArrayParams.java new file mode 100644 index 00000000000..19d3ff1a49a --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/missingoverride/ConcreteClassArrayParams.java @@ -0,0 +1,18 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.bestpractices.missingoverride; + +public class ConcreteClassArrayParams extends AbstractClass { + @Override + Object fun(String s) { + return null; + } + + + @Override + public void arrayParams(String dflt, int[] keys, StringBuilder[] labels) { + super.arrayParams(dflt, keys, labels); + } +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/missingoverride/ConcreteClassTransitive.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/missingoverride/ConcreteClassTransitive.java new file mode 100644 index 00000000000..64293b5c599 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/missingoverride/ConcreteClassTransitive.java @@ -0,0 +1,16 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.bestpractices.missingoverride; + +/** + * @author Clément Fournier + * @since 6.2.0 + */ +public class ConcreteClassTransitive extends AbsClassWithInterface { + @Override + public void run() { + + } +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/missingoverride/CovariantReturnType.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/missingoverride/CovariantReturnType.java new file mode 100644 index 00000000000..6e977c65c6e --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/missingoverride/CovariantReturnType.java @@ -0,0 +1,13 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.bestpractices.missingoverride; + +public class CovariantReturnType extends AbstractClass { + + @Override + String fun(String s) { + return ""; + } +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/missingoverride/EnumWithAnonClass.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/missingoverride/EnumWithAnonClass.java new file mode 100644 index 00000000000..62268dc3247 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/missingoverride/EnumWithAnonClass.java @@ -0,0 +1,24 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.bestpractices.missingoverride; + +/** + * @author Clément Fournier + * @since 6.2.0 + */ +public enum EnumWithAnonClass { + Foo { + @Override + public String getSomething() { + return null; + } + }; + + + public Object getSomething() { + return null; + } + +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/missingoverride/EnumWithInterfaces.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/missingoverride/EnumWithInterfaces.java new file mode 100644 index 00000000000..f493b8c0974 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/missingoverride/EnumWithInterfaces.java @@ -0,0 +1,34 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.bestpractices.missingoverride; + +import net.sourceforge.pmd.lang.java.ast.ASTAnyTypeDeclaration; +import net.sourceforge.pmd.lang.metrics.Metric; +import net.sourceforge.pmd.lang.metrics.MetricKey; + + +/** + * @author Clément Fournier + * @since 6.2.0 + */ +public enum EnumWithInterfaces implements MetricKey { + Foo { + @Override + public Metric getCalculator() { + return null; + } + }; + + @Override + public Metric getCalculator() { + return null; + } + + + @Override + public boolean supports(ASTAnyTypeDeclaration node) { + return false; + } +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/missingoverride/GenericInterfaceWithOverloads.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/missingoverride/GenericInterfaceWithOverloads.java new file mode 100644 index 00000000000..4ed3999d418 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/missingoverride/GenericInterfaceWithOverloads.java @@ -0,0 +1,25 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.bestpractices.missingoverride; + +import net.sourceforge.pmd.lang.java.ast.ASTCompilationUnit; +import net.sourceforge.pmd.lang.java.ast.ASTImportDeclaration; +import net.sourceforge.pmd.lang.java.ast.ASTPackageDeclaration; + + +public interface GenericInterfaceWithOverloads { + + T visit(ASTCompilationUnit node, T data); + + + T visit(ASTPackageDeclaration node, T data); + + + T multi(ASTImportDeclaration node, T data, R r); + + + T multi(ASTPackageDeclaration node, T data, R r); + +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/missingoverride/GenericWithOverloadsImpl.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/missingoverride/GenericWithOverloadsImpl.java new file mode 100644 index 00000000000..8f7a9004d97 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/missingoverride/GenericWithOverloadsImpl.java @@ -0,0 +1,47 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.bestpractices.missingoverride; + +import net.sourceforge.pmd.lang.java.ast.ASTCompilationUnit; +import net.sourceforge.pmd.lang.java.ast.ASTImportDeclaration; +import net.sourceforge.pmd.lang.java.ast.ASTMethodDeclaration; +import net.sourceforge.pmd.lang.java.ast.ASTPackageDeclaration; + + +public class GenericWithOverloadsImpl implements GenericInterfaceWithOverloads { + + // a bridge method is generated for each of these that implement an interface method + + + @Override + public String visit(ASTCompilationUnit node, String data) { + return null; + } + + + @Override + public String visit(ASTPackageDeclaration node, String data) { + return null; + } + + + @Override + public String multi(ASTImportDeclaration node, String data, Integer integer) { + return null; + } + + + // this one is not overriden, no bridge + public String multi(ASTMethodDeclaration node, String data, Integer integer) { + return null; + } + + + @Override + public String multi(ASTPackageDeclaration node, String data, Integer integer) { + return null; + } + +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/missingoverride/HierarchyWithSeveralBridges.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/missingoverride/HierarchyWithSeveralBridges.java new file mode 100644 index 00000000000..bf5bbfe32de --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/missingoverride/HierarchyWithSeveralBridges.java @@ -0,0 +1,39 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.bestpractices.missingoverride; + +import net.sourceforge.pmd.lang.ast.Node; +import net.sourceforge.pmd.lang.java.ast.ASTType; +import net.sourceforge.pmd.lang.java.ast.AbstractJavaTypeNode; +import net.sourceforge.pmd.lang.java.ast.JavaNode; + +public abstract class HierarchyWithSeveralBridges { + + abstract void foo(T node); + + public abstract static class SubclassOne extends HierarchyWithSeveralBridges { + + @Override + abstract void foo(T node); + } + + public abstract static class SubclassTwo extends SubclassOne { + @Override + void foo(T node) { + + } + } + + + public static class Concrete extends SubclassTwo { + + // bridges: foo(AbstractJavaTypeNode), foo(JavaNode), foo(Node) + + @Override + void foo(ASTType node) { + + } + } +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/missingoverride/InterfaceWithNoSuperClass.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/missingoverride/InterfaceWithNoSuperClass.java new file mode 100644 index 00000000000..0972217af3e --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/missingoverride/InterfaceWithNoSuperClass.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.bestpractices.missingoverride; + +public interface InterfaceWithNoSuperClass { + + @Override + String toString(); +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/missingoverride/Option.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/missingoverride/Option.java new file mode 100644 index 00000000000..bd4c1980328 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/missingoverride/Option.java @@ -0,0 +1,12 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.bestpractices.missingoverride; + +/** + * @author Clément Fournier + * @since 6.2.0 + */ +public class Option { +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/missingoverride/OptionTestCaseOneParam.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/missingoverride/OptionTestCaseOneParam.java new file mode 100644 index 00000000000..968f0003577 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/missingoverride/OptionTestCaseOneParam.java @@ -0,0 +1,12 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.bestpractices.missingoverride; + +/** + * @author Clément Fournier + * @since 6.1.0 + */ +public class OptionTestCaseOneParam { +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/missingoverride/RunnableImpl.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/missingoverride/RunnableImpl.java new file mode 100644 index 00000000000..d631863ffd5 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/missingoverride/RunnableImpl.java @@ -0,0 +1,12 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.bestpractices.missingoverride; + +public class RunnableImpl implements Runnable { + @Override + public void run() { + + } +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/missingoverride/SubclassPrivateNoOverride.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/missingoverride/SubclassPrivateNoOverride.java new file mode 100644 index 00000000000..5cc4bc00add --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/missingoverride/SubclassPrivateNoOverride.java @@ -0,0 +1,17 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.bestpractices.missingoverride; + +/** + * @author Clément Fournier + * @since 6.4.0 + */ +public class SubclassPrivateNoOverride extends SuperclassWithPrivate { + + // No override + public void foo() { + + } +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/missingoverride/SubclassWithGenericMethod.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/missingoverride/SubclassWithGenericMethod.java new file mode 100644 index 00000000000..191b7decc7a --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/missingoverride/SubclassWithGenericMethod.java @@ -0,0 +1,13 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.bestpractices.missingoverride; + +public class SubclassWithGenericMethod extends AbstractClass { + + @Override + public R generic(T t, R r) { + return super.generic(t, r); + } +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/missingoverride/SubclassWithStatic.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/missingoverride/SubclassWithStatic.java new file mode 100644 index 00000000000..4e8a68d9e77 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/missingoverride/SubclassWithStatic.java @@ -0,0 +1,17 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.bestpractices.missingoverride; + +/** + * @author Clément Fournier + * @since 6.4.0 + */ +public class SubclassWithStatic extends SuperclassWithStatic { + + public static void fooBar(String f) { + + } + +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/missingoverride/SuperclassWithPrivate.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/missingoverride/SuperclassWithPrivate.java new file mode 100644 index 00000000000..d2cea858737 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/missingoverride/SuperclassWithPrivate.java @@ -0,0 +1,17 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.bestpractices.missingoverride; + +/** + * @author Clément Fournier + * @since 6.4.0 + */ +public class SuperclassWithPrivate { + + private void foo() { + + } + +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/missingoverride/SuperclassWithStatic.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/missingoverride/SuperclassWithStatic.java new file mode 100644 index 00000000000..d8d2a8d0172 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/missingoverride/SuperclassWithStatic.java @@ -0,0 +1,22 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.bestpractices.missingoverride; + +/** + * @author Clément Fournier + * @since 6.4.0 + */ +public class SuperclassWithStatic { + + protected SuperclassWithStatic() { + + } + + + public static void fooBar(String f) { + + } + +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/switchstmtsshouldhavedefault/SimpleEnum.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/switchstmtsshouldhavedefault/SimpleEnum.java new file mode 100644 index 00000000000..1fa9ce966c0 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/switchstmtsshouldhavedefault/SimpleEnum.java @@ -0,0 +1,15 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.bestpractices.switchstmtsshouldhavedefault; + +/** + * @author Clément Fournier + * @since 6.5.0 + */ +public enum SimpleEnum { + FOO, + BAR, + BZAZ +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/unusedimports/ClassWithConstants.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/unusedimports/ClassWithConstants.java new file mode 100644 index 00000000000..5938d2aa0a8 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/unusedimports/ClassWithConstants.java @@ -0,0 +1,18 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.bestpractices.unusedimports; + +import java.util.Arrays; +import java.util.List; + +public class ClassWithConstants { + + private ClassWithConstants() { + // Utility class + } + + /*package*/ static final List LIST1 = Arrays.asList("A"); + /*package*/ static final List LIST2 = Arrays.asList("B"); +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/unusedimports/ClassWithStringConstants.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/unusedimports/ClassWithStringConstants.java new file mode 100644 index 00000000000..329b6059b35 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/unusedimports/ClassWithStringConstants.java @@ -0,0 +1,15 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.bestpractices.unusedimports; + +public class ClassWithStringConstants { + + private ClassWithStringConstants() { + // Utility class + } + + public static final String CONST1 = "a"; + public static final String CONST2 = "b"; +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/unusedimports/PackagePrivateUtils.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/unusedimports/PackagePrivateUtils.java new file mode 100644 index 00000000000..b9dee82b8c7 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/unusedimports/PackagePrivateUtils.java @@ -0,0 +1,22 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.bestpractices.unusedimports; + +final class PackagePrivateUtils { + private PackagePrivateUtils() { + } + + static int f1(int x) { + return x + 1; + } + + static int f2(int x) { + return x + 1; + } + + static int f3(int x) { + return x + 1; + } +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/unusedimports/PublicUtils.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/unusedimports/PublicUtils.java new file mode 100644 index 00000000000..003aeae7a39 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/unusedimports/PublicUtils.java @@ -0,0 +1,22 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.bestpractices.unusedimports; + +public class PublicUtils { + private PublicUtils() { + } + + public static int g1(int x) { + return x + 1; + } + + public static int g2(int x) { + return x + 1; + } + + public static int g3(int x) { + return x + 1; + } +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/unusedcode/unusedprivatemethod/ClassWithPublicEnum.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/unusedprivatemethod/ClassWithPublicEnum.java similarity index 86% rename from pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/unusedcode/unusedprivatemethod/ClassWithPublicEnum.java rename to pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/unusedprivatemethod/ClassWithPublicEnum.java index 530718c2920..98533fd5d6d 100644 --- a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/unusedcode/unusedprivatemethod/ClassWithPublicEnum.java +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/unusedprivatemethod/ClassWithPublicEnum.java @@ -2,7 +2,7 @@ * BSD-style license; for more info see http://pmd.sourceforge.net/license.html */ -package net.sourceforge.pmd.lang.java.rule.unusedcode.unusedprivatemethod; +package net.sourceforge.pmd.lang.java.rule.bestpractices.unusedprivatemethod; /** * diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/unusedcode/unusedprivatemethod/DashboardGraphInnateFilter.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/unusedprivatemethod/DashboardGraphInnateFilter.java similarity index 75% rename from pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/unusedcode/unusedprivatemethod/DashboardGraphInnateFilter.java rename to pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/unusedprivatemethod/DashboardGraphInnateFilter.java index c42eb563b8f..c2e2f9a7cb9 100644 --- a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/unusedcode/unusedprivatemethod/DashboardGraphInnateFilter.java +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/unusedprivatemethod/DashboardGraphInnateFilter.java @@ -2,7 +2,7 @@ * BSD-style license; for more info see http://pmd.sourceforge.net/license.html */ -package net.sourceforge.pmd.lang.java.rule.unusedcode.unusedprivatemethod; +package net.sourceforge.pmd.lang.java.rule.bestpractices.unusedprivatemethod; /** * Sample class diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/unusedcode/unusedprivatemethod/DashboardInnateFilter.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/unusedprivatemethod/DashboardInnateFilter.java similarity index 76% rename from pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/unusedcode/unusedprivatemethod/DashboardInnateFilter.java rename to pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/unusedprivatemethod/DashboardInnateFilter.java index b021bf7c01d..0b54ce8e03b 100644 --- a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/unusedcode/unusedprivatemethod/DashboardInnateFilter.java +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/bestpractices/unusedprivatemethod/DashboardInnateFilter.java @@ -2,7 +2,7 @@ * BSD-style license; for more info see http://pmd.sourceforge.net/license.html */ -package net.sourceforge.pmd.lang.java.rule.unusedcode.unusedprivatemethod; +package net.sourceforge.pmd.lang.java.rule.bestpractices.unusedprivatemethod; /** * Sample class diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/braces/BracesRulesTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/braces/BracesRulesTest.java deleted file mode 100644 index 1094fce6a98..00000000000 --- a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/braces/BracesRulesTest.java +++ /dev/null @@ -1,20 +0,0 @@ -/** - * BSD-style license; for more info see http://pmd.sourceforge.net/license.html - */ - -package net.sourceforge.pmd.lang.java.rule.braces; - -import net.sourceforge.pmd.testframework.SimpleAggregatorTst; - -public class BracesRulesTest extends SimpleAggregatorTst { - - private static final String RULESET = "java-braces"; - - @Override - public void setUp() { - addRule(RULESET, "ForLoopsMustUseBraces"); - addRule(RULESET, "IfElseStmtsMustUseBraces"); - addRule(RULESET, "IfStmtsMustUseBraces"); - addRule(RULESET, "WhileLoopsMustUseBraces"); - } -} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/clone/CloneRulesTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/clone/CloneRulesTest.java deleted file mode 100644 index 7b404971487..00000000000 --- a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/clone/CloneRulesTest.java +++ /dev/null @@ -1,21 +0,0 @@ -/** - * BSD-style license; for more info see http://pmd.sourceforge.net/license.html - */ - -package net.sourceforge.pmd.lang.java.rule.clone; - -import net.sourceforge.pmd.testframework.SimpleAggregatorTst; - -public class CloneRulesTest extends SimpleAggregatorTst { - - private static final String RULESET = "java-clone"; - - @Override - public void setUp() { - addRule(RULESET, "CloneMethodMustImplementCloneable"); - addRule(RULESET, "CloneThrowsCloneNotSupportedException"); - addRule(RULESET, "ProperCloneImplementation"); - addRule(RULESET, "CloneMethodReturnTypeMustMatchClassName"); - addRule(RULESET, "CloneMethodMustBePublic"); - } -} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codesize/CodesizeRulesTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codesize/CodesizeRulesTest.java deleted file mode 100644 index f87b5913478..00000000000 --- a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codesize/CodesizeRulesTest.java +++ /dev/null @@ -1,30 +0,0 @@ -/** - * BSD-style license; for more info see http://pmd.sourceforge.net/license.html - */ - -package net.sourceforge.pmd.lang.java.rule.codesize; - -import net.sourceforge.pmd.testframework.SimpleAggregatorTst; - -public class CodesizeRulesTest extends SimpleAggregatorTst { - - private static final String RULESET = "java-codesize"; - - @Override - public void setUp() { - addRule(RULESET, "CyclomaticComplexity"); - addRule(RULESET, "ExcessivePublicCount"); - addRule(RULESET, "ExcessiveClassLength"); - addRule(RULESET, "ExcessiveParameterList"); - addRule(RULESET, "ExcessiveMethodLength"); - addRule(RULESET, "ModifiedCyclomaticComplexity"); - addRule(RULESET, "NcssConstructorCount"); - addRule(RULESET, "NcssMethodCount"); - addRule(RULESET, "NcssTypeCount"); - addRule(RULESET, "NcssCount"); - addRule(RULESET, "NPathComplexity"); - addRule(RULESET, "StdCyclomaticComplexity"); - addRule(RULESET, "TooManyFields"); - addRule(RULESET, "TooManyMethods"); - } -} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codesize/StdCyclomaticComplexityRuleTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codesize/StdCyclomaticComplexityRuleTest.java deleted file mode 100644 index 7e33256e330..00000000000 --- a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codesize/StdCyclomaticComplexityRuleTest.java +++ /dev/null @@ -1,44 +0,0 @@ -/** - * BSD-style license; for more info see http://pmd.sourceforge.net/license.html - */ - -package net.sourceforge.pmd.lang.java.rule.codesize; - -import java.io.StringReader; -import java.util.Arrays; - -import org.junit.Assert; -import org.junit.Test; - -import net.sourceforge.pmd.RuleContext; -import net.sourceforge.pmd.lang.LanguageRegistry; -import net.sourceforge.pmd.lang.LanguageVersion; -import net.sourceforge.pmd.lang.Parser; -import net.sourceforge.pmd.lang.ParserOptions; -import net.sourceforge.pmd.lang.ast.Node; -import net.sourceforge.pmd.lang.java.JavaLanguageModule; - -public class StdCyclomaticComplexityRuleTest { - - /** - * Make sure the entry stack is empty, if show classes complexity is - * disabled. - * - * @see bug #1501 - */ - @Test - public void entryStackMustBeEmpty() { - StdCyclomaticComplexityRule rule = new StdCyclomaticComplexityRule(); - rule.setProperty(StdCyclomaticComplexityRule.SHOW_CLASSES_COMPLEXITY_DESCRIPTOR, Boolean.FALSE); - - RuleContext ctx = new RuleContext(); - LanguageVersion javaLanguageVersion = LanguageRegistry.getLanguage(JavaLanguageModule.NAME).getVersion("1.8"); - ParserOptions parserOptions = javaLanguageVersion.getLanguageVersionHandler().getDefaultParserOptions(); - Parser parser = javaLanguageVersion.getLanguageVersionHandler().getParser(parserOptions); - Node node = parser.parse("test", new StringReader("public class SampleClass {}")); - - rule.apply(Arrays.asList(node), ctx); - - Assert.assertTrue(rule.entryStack.isEmpty()); - } -} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codestyle/AbstractNamingTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codestyle/AbstractNamingTest.java new file mode 100644 index 00000000000..5f19656e151 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codestyle/AbstractNamingTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.codestyle; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class AbstractNamingTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codestyle/AtLeastOneConstructorTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codestyle/AtLeastOneConstructorTest.java new file mode 100644 index 00000000000..129fe92fa92 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codestyle/AtLeastOneConstructorTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.codestyle; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class AtLeastOneConstructorTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codestyle/AvoidDollarSignsTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codestyle/AvoidDollarSignsTest.java new file mode 100644 index 00000000000..1d8e87383c4 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codestyle/AvoidDollarSignsTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.codestyle; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class AvoidDollarSignsTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codestyle/AvoidFinalLocalVariableTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codestyle/AvoidFinalLocalVariableTest.java new file mode 100644 index 00000000000..1bddcf45daf --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codestyle/AvoidFinalLocalVariableTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.codestyle; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class AvoidFinalLocalVariableTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codestyle/AvoidPrefixingMethodParametersTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codestyle/AvoidPrefixingMethodParametersTest.java new file mode 100644 index 00000000000..24c2337d7e4 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codestyle/AvoidPrefixingMethodParametersTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.codestyle; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class AvoidPrefixingMethodParametersTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codestyle/AvoidProtectedFieldInFinalClassTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codestyle/AvoidProtectedFieldInFinalClassTest.java new file mode 100644 index 00000000000..1f22c1d6071 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codestyle/AvoidProtectedFieldInFinalClassTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.codestyle; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class AvoidProtectedFieldInFinalClassTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codestyle/AvoidProtectedMethodInFinalClassNotExtendingTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codestyle/AvoidProtectedMethodInFinalClassNotExtendingTest.java new file mode 100644 index 00000000000..61bb110a086 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codestyle/AvoidProtectedMethodInFinalClassNotExtendingTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.codestyle; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class AvoidProtectedMethodInFinalClassNotExtendingTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codestyle/AvoidUsingNativeCodeTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codestyle/AvoidUsingNativeCodeTest.java new file mode 100644 index 00000000000..88867514437 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codestyle/AvoidUsingNativeCodeTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.codestyle; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class AvoidUsingNativeCodeTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codestyle/BooleanGetMethodNameTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codestyle/BooleanGetMethodNameTest.java new file mode 100644 index 00000000000..c6c6734dc8c --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codestyle/BooleanGetMethodNameTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.codestyle; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class BooleanGetMethodNameTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codestyle/CallSuperInConstructorTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codestyle/CallSuperInConstructorTest.java new file mode 100644 index 00000000000..05b5cf0c8c7 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codestyle/CallSuperInConstructorTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.codestyle; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class CallSuperInConstructorTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codestyle/ClassNamingConventionsTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codestyle/ClassNamingConventionsTest.java new file mode 100644 index 00000000000..81605d543f2 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codestyle/ClassNamingConventionsTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.codestyle; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class ClassNamingConventionsTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codestyle/CommentDefaultAccessModifierTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codestyle/CommentDefaultAccessModifierTest.java new file mode 100644 index 00000000000..d0ad862dad4 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codestyle/CommentDefaultAccessModifierTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.codestyle; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class CommentDefaultAccessModifierTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codestyle/ConfusingTernaryTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codestyle/ConfusingTernaryTest.java new file mode 100644 index 00000000000..a023f709213 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codestyle/ConfusingTernaryTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.codestyle; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class ConfusingTernaryTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codestyle/ControlStatementBracesTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codestyle/ControlStatementBracesTest.java new file mode 100644 index 00000000000..c7ff86946e5 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codestyle/ControlStatementBracesTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.codestyle; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class ControlStatementBracesTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codestyle/DefaultPackageTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codestyle/DefaultPackageTest.java new file mode 100644 index 00000000000..19c6c714fdf --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codestyle/DefaultPackageTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.codestyle; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class DefaultPackageTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codestyle/DontImportJavaLangTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codestyle/DontImportJavaLangTest.java new file mode 100644 index 00000000000..5c0060bcba8 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codestyle/DontImportJavaLangTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.codestyle; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class DontImportJavaLangTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codestyle/DuplicateImportsTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codestyle/DuplicateImportsTest.java new file mode 100644 index 00000000000..8dd1705bed1 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codestyle/DuplicateImportsTest.java @@ -0,0 +1,23 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.codestyle; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class DuplicateImportsTest extends PmdRuleTst { + /** + * This is just for testing DuplicateImports for static imports and + * disambiguation. + */ + // Do not delete this method, its needed for a test case + // see: + // /pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/codestyle/xml/DuplicateImports.xml + // #1306 False positive on duplicate when using static imports + public static void assertTrue(String message, boolean condition) { + if (!condition) { + System.out.println(message); + } + } +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codestyle/EmptyMethodInAbstractClassShouldBeAbstractTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codestyle/EmptyMethodInAbstractClassShouldBeAbstractTest.java new file mode 100644 index 00000000000..8d2ca65fdc5 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codestyle/EmptyMethodInAbstractClassShouldBeAbstractTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.codestyle; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class EmptyMethodInAbstractClassShouldBeAbstractTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codestyle/ExtendsObjectTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codestyle/ExtendsObjectTest.java new file mode 100644 index 00000000000..d961107c92d --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codestyle/ExtendsObjectTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.codestyle; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class ExtendsObjectTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codestyle/FieldDeclarationsShouldBeAtStartOfClassTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codestyle/FieldDeclarationsShouldBeAtStartOfClassTest.java new file mode 100644 index 00000000000..c9010b20a0b --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codestyle/FieldDeclarationsShouldBeAtStartOfClassTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.codestyle; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class FieldDeclarationsShouldBeAtStartOfClassTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codestyle/FieldNamingConventionsTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codestyle/FieldNamingConventionsTest.java new file mode 100644 index 00000000000..c2ab5652a21 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codestyle/FieldNamingConventionsTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.codestyle; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class FieldNamingConventionsTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codestyle/ForLoopShouldBeWhileLoopTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codestyle/ForLoopShouldBeWhileLoopTest.java new file mode 100644 index 00000000000..646e03a8539 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codestyle/ForLoopShouldBeWhileLoopTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.codestyle; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class ForLoopShouldBeWhileLoopTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codestyle/ForLoopsMustUseBracesTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codestyle/ForLoopsMustUseBracesTest.java new file mode 100644 index 00000000000..76ce00748ab --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codestyle/ForLoopsMustUseBracesTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.codestyle; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class ForLoopsMustUseBracesTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codestyle/FormalParameterNamingConventionsTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codestyle/FormalParameterNamingConventionsTest.java new file mode 100644 index 00000000000..11ecb73e9a8 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codestyle/FormalParameterNamingConventionsTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.codestyle; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class FormalParameterNamingConventionsTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codestyle/GenericsNamingTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codestyle/GenericsNamingTest.java new file mode 100644 index 00000000000..f7357c25425 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codestyle/GenericsNamingTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.codestyle; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class GenericsNamingTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codestyle/IdenticalCatchBranchesTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codestyle/IdenticalCatchBranchesTest.java new file mode 100644 index 00000000000..da7244d121c --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codestyle/IdenticalCatchBranchesTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.codestyle; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class IdenticalCatchBranchesTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codestyle/IfElseStmtsMustUseBracesTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codestyle/IfElseStmtsMustUseBracesTest.java new file mode 100644 index 00000000000..957035bb8dc --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codestyle/IfElseStmtsMustUseBracesTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.codestyle; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class IfElseStmtsMustUseBracesTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codestyle/IfStmtsMustUseBracesTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codestyle/IfStmtsMustUseBracesTest.java new file mode 100644 index 00000000000..cbbf424654a --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codestyle/IfStmtsMustUseBracesTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.codestyle; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class IfStmtsMustUseBracesTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codestyle/LinguisticNamingTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codestyle/LinguisticNamingTest.java new file mode 100644 index 00000000000..7858ef662aa --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codestyle/LinguisticNamingTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.codestyle; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class LinguisticNamingTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codestyle/LocalHomeNamingConventionTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codestyle/LocalHomeNamingConventionTest.java new file mode 100644 index 00000000000..11d40a17880 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codestyle/LocalHomeNamingConventionTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.codestyle; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class LocalHomeNamingConventionTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codestyle/LocalInterfaceSessionNamingConventionTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codestyle/LocalInterfaceSessionNamingConventionTest.java new file mode 100644 index 00000000000..be1832f299d --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codestyle/LocalInterfaceSessionNamingConventionTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.codestyle; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class LocalInterfaceSessionNamingConventionTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codestyle/LocalVariableCouldBeFinalTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codestyle/LocalVariableCouldBeFinalTest.java new file mode 100644 index 00000000000..d342b03ba9c --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codestyle/LocalVariableCouldBeFinalTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.codestyle; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class LocalVariableCouldBeFinalTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codestyle/LocalVariableNamingConventionsTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codestyle/LocalVariableNamingConventionsTest.java new file mode 100644 index 00000000000..bee83930e84 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codestyle/LocalVariableNamingConventionsTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.codestyle; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class LocalVariableNamingConventionsTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codestyle/LongVariableTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codestyle/LongVariableTest.java new file mode 100644 index 00000000000..793af6fa631 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codestyle/LongVariableTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.codestyle; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class LongVariableTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codestyle/MDBAndSessionBeanNamingConventionTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codestyle/MDBAndSessionBeanNamingConventionTest.java new file mode 100644 index 00000000000..e12899a61d0 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codestyle/MDBAndSessionBeanNamingConventionTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.codestyle; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class MDBAndSessionBeanNamingConventionTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codestyle/MIsLeadingVariableNameTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codestyle/MIsLeadingVariableNameTest.java new file mode 100644 index 00000000000..56db10643a0 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codestyle/MIsLeadingVariableNameTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.codestyle; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class MIsLeadingVariableNameTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codestyle/MethodArgumentCouldBeFinalTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codestyle/MethodArgumentCouldBeFinalTest.java new file mode 100644 index 00000000000..66b362def99 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codestyle/MethodArgumentCouldBeFinalTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.codestyle; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class MethodArgumentCouldBeFinalTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codestyle/MethodNamingConventionsTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codestyle/MethodNamingConventionsTest.java new file mode 100644 index 00000000000..c9f328d06ff --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codestyle/MethodNamingConventionsTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.codestyle; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class MethodNamingConventionsTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codestyle/NoPackageTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codestyle/NoPackageTest.java new file mode 100644 index 00000000000..54b1246e32d --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codestyle/NoPackageTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.codestyle; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class NoPackageTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codestyle/OnlyOneReturnTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codestyle/OnlyOneReturnTest.java new file mode 100644 index 00000000000..d656015f724 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codestyle/OnlyOneReturnTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.codestyle; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class OnlyOneReturnTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codestyle/PackageCaseTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codestyle/PackageCaseTest.java new file mode 100644 index 00000000000..0b59065adb6 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codestyle/PackageCaseTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.codestyle; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class PackageCaseTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codestyle/PrematureDeclarationTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codestyle/PrematureDeclarationTest.java new file mode 100644 index 00000000000..c73a9f97814 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codestyle/PrematureDeclarationTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.codestyle; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class PrematureDeclarationTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codestyle/RemoteInterfaceNamingConventionTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codestyle/RemoteInterfaceNamingConventionTest.java new file mode 100644 index 00000000000..1d0c445aea4 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codestyle/RemoteInterfaceNamingConventionTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.codestyle; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class RemoteInterfaceNamingConventionTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codestyle/RemoteSessionInterfaceNamingConventionTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codestyle/RemoteSessionInterfaceNamingConventionTest.java new file mode 100644 index 00000000000..7e95cb5d1ae --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codestyle/RemoteSessionInterfaceNamingConventionTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.codestyle; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class RemoteSessionInterfaceNamingConventionTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codestyle/ShortClassNameTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codestyle/ShortClassNameTest.java new file mode 100644 index 00000000000..1c4bdc49c6e --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codestyle/ShortClassNameTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.codestyle; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class ShortClassNameTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codestyle/ShortMethodNameTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codestyle/ShortMethodNameTest.java new file mode 100644 index 00000000000..7133947372f --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codestyle/ShortMethodNameTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.codestyle; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class ShortMethodNameTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codestyle/ShortVariableTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codestyle/ShortVariableTest.java new file mode 100644 index 00000000000..2d29ac34426 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codestyle/ShortVariableTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.codestyle; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class ShortVariableTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codestyle/SuspiciousConstantFieldNameTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codestyle/SuspiciousConstantFieldNameTest.java new file mode 100644 index 00000000000..7e8b484c1c7 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codestyle/SuspiciousConstantFieldNameTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.codestyle; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class SuspiciousConstantFieldNameTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codestyle/TooManyStaticImportsTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codestyle/TooManyStaticImportsTest.java new file mode 100644 index 00000000000..43af1eac984 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codestyle/TooManyStaticImportsTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.codestyle; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class TooManyStaticImportsTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codestyle/UnnecessaryAnnotationValueElementTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codestyle/UnnecessaryAnnotationValueElementTest.java new file mode 100644 index 00000000000..1e41e5769d5 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codestyle/UnnecessaryAnnotationValueElementTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.codestyle; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class UnnecessaryAnnotationValueElementTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codestyle/UnnecessaryConstructorTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codestyle/UnnecessaryConstructorTest.java new file mode 100644 index 00000000000..b2cf45becfa --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codestyle/UnnecessaryConstructorTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.codestyle; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class UnnecessaryConstructorTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codestyle/UnnecessaryFullyQualifiedNameTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codestyle/UnnecessaryFullyQualifiedNameTest.java new file mode 100644 index 00000000000..0f3628952af --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codestyle/UnnecessaryFullyQualifiedNameTest.java @@ -0,0 +1,38 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.codestyle; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class UnnecessaryFullyQualifiedNameTest extends PmdRuleTst { + // Do not delete these two enums - it is needed for a test case + // see: + // /pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/codestyle/xml/UnnecessaryFullyQualifiedName.xml + // #1436 UnnecessaryFullyQualifiedName false positive on clashing static + // imports with enums + public enum ENUM1 { + A, B; + } + + public enum ENUM2 { + C, D; + } + + // Do not delete these two classes - it is needed for a test case + // see: /pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/codestyle/xml/UnnecessaryFullyQualifiedName.xml + // #1546 part 1 UnnecessaryFullyQualifiedName doesn't take into consideration conflict resolution + // #1546 part 2 UnnecessaryFullyQualifiedName doesn't take into consideration conflict resolution + public static class PhonyMockito { + public static T mock(Class clazz) { + return null; + } + } + + public static class PhonyPowerMockito { + public static T mock(Class clazz) { + return null; + } + } +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codestyle/UnnecessaryLocalBeforeReturnTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codestyle/UnnecessaryLocalBeforeReturnTest.java new file mode 100644 index 00000000000..73dc6cdac3a --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codestyle/UnnecessaryLocalBeforeReturnTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.codestyle; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class UnnecessaryLocalBeforeReturnTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codestyle/UnnecessaryModifierTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codestyle/UnnecessaryModifierTest.java new file mode 100644 index 00000000000..09774b6a331 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codestyle/UnnecessaryModifierTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.codestyle; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class UnnecessaryModifierTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codestyle/UnnecessaryReturnTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codestyle/UnnecessaryReturnTest.java new file mode 100644 index 00000000000..0a8d3c6bd65 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codestyle/UnnecessaryReturnTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.codestyle; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class UnnecessaryReturnTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codestyle/UseUnderscoresInNumericLiteralsTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codestyle/UseUnderscoresInNumericLiteralsTest.java new file mode 100644 index 00000000000..ecaaba4bf34 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codestyle/UseUnderscoresInNumericLiteralsTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.codestyle; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class UseUnderscoresInNumericLiteralsTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codestyle/UselessParenthesesTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codestyle/UselessParenthesesTest.java new file mode 100644 index 00000000000..14cc66c01f2 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codestyle/UselessParenthesesTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.codestyle; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class UselessParenthesesTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codestyle/UselessQualifiedThisTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codestyle/UselessQualifiedThisTest.java new file mode 100644 index 00000000000..19f7b4bf6ec --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codestyle/UselessQualifiedThisTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.codestyle; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class UselessQualifiedThisTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codestyle/VariableNamingConventionsTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codestyle/VariableNamingConventionsTest.java new file mode 100644 index 00000000000..34e2c13642c --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codestyle/VariableNamingConventionsTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.codestyle; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class VariableNamingConventionsTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codestyle/WhileLoopsMustUseBracesTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codestyle/WhileLoopsMustUseBracesTest.java new file mode 100644 index 00000000000..4aa0611c113 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codestyle/WhileLoopsMustUseBracesTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.codestyle; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class WhileLoopsMustUseBracesTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codestyle/commentdefaultaccessmodifier/OnlyForTesting.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codestyle/commentdefaultaccessmodifier/OnlyForTesting.java new file mode 100644 index 00000000000..6f4a6608ed1 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/codestyle/commentdefaultaccessmodifier/OnlyForTesting.java @@ -0,0 +1,9 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.codestyle.commentdefaultaccessmodifier; + +public @interface OnlyForTesting { + +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/comments/CommentRulesTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/comments/CommentRulesTest.java deleted file mode 100755 index 6ac8b46dd99..00000000000 --- a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/comments/CommentRulesTest.java +++ /dev/null @@ -1,20 +0,0 @@ -/** - * BSD-style license; for more info see http://pmd.sourceforge.net/license.html - */ - -package net.sourceforge.pmd.lang.java.rule.comments; - -import net.sourceforge.pmd.testframework.SimpleAggregatorTst; - -public class CommentRulesTest extends SimpleAggregatorTst { - - private static final String RULESET = "java-comments"; - - @Override - public void setUp() { - addRule(RULESET, "CommentRequired"); - addRule(RULESET, "CommentSize"); - addRule(RULESET, "CommentContent"); - addRule(RULESET, "CommentDefaultAccessModifier"); - } -} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/controversial/ControversialRulesTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/controversial/ControversialRulesTest.java deleted file mode 100644 index f4e29450044..00000000000 --- a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/controversial/ControversialRulesTest.java +++ /dev/null @@ -1,36 +0,0 @@ -/** - * BSD-style license; for more info see http://pmd.sourceforge.net/license.html - */ - -package net.sourceforge.pmd.lang.java.rule.controversial; - -import net.sourceforge.pmd.testframework.SimpleAggregatorTst; - -public class ControversialRulesTest extends SimpleAggregatorTst { - - private static final String RULESET = "java-controversial"; - - @Override - public void setUp() { - addRule(RULESET, "AssignmentInOperand"); - addRule(RULESET, "AvoidLiteralsInIfCondition"); - addRule(RULESET, "AvoidPrefixingMethodParameters"); - addRule(RULESET, "AvoidUsingNativeCode"); - addRule(RULESET, "AvoidUsingShortType"); - addRule(RULESET, "AvoidUsingVolatile"); - addRule(RULESET, "AtLeastOneConstructor"); - addRule(RULESET, "AvoidFinalLocalVariable"); - addRule(RULESET, "CallSuperInConstructor"); - addRule(RULESET, "DataflowAnomalyAnalysis"); - addRule(RULESET, "DefaultPackage"); - addRule(RULESET, "DontImportSun"); - addRule(RULESET, "DoNotCallGarbageCollectionExplicitly"); - addRule(RULESET, "NullAssignment"); - addRule(RULESET, "OnlyOneReturn"); - addRule(RULESET, "OneDeclarationPerLine"); - addRule(RULESET, "SuspiciousOctalEscape"); - addRule(RULESET, "UnnecessaryConstructor"); - addRule(RULESET, "UseConcurrentHashMap"); - addRule(RULESET, "UseObjectForClearerAPI"); - } -} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/coupling/CouplingRulesTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/coupling/CouplingRulesTest.java deleted file mode 100644 index 28cb2f3c5b9..00000000000 --- a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/coupling/CouplingRulesTest.java +++ /dev/null @@ -1,21 +0,0 @@ -/** - * BSD-style license; for more info see http://pmd.sourceforge.net/license.html - */ - -package net.sourceforge.pmd.lang.java.rule.coupling; - -import net.sourceforge.pmd.testframework.SimpleAggregatorTst; - -public class CouplingRulesTest extends SimpleAggregatorTst { - - private static final String RULESET = "java-coupling"; - - @Override - public void setUp() { - addRule(RULESET, "CouplingBetweenObjects"); - addRule(RULESET, "ExcessiveImports"); - addRule(RULESET, "LooseCoupling"); - addRule(RULESET, "LoosePackageCoupling"); - addRule(RULESET, "LawOfDemeter"); - } -} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/design/AbstractClassWithoutAnyMethodTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/design/AbstractClassWithoutAnyMethodTest.java new file mode 100644 index 00000000000..a75049eb195 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/design/AbstractClassWithoutAnyMethodTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.design; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class AbstractClassWithoutAnyMethodTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/design/AvoidCatchingGenericExceptionTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/design/AvoidCatchingGenericExceptionTest.java new file mode 100644 index 00000000000..f299c962c92 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/design/AvoidCatchingGenericExceptionTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.design; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class AvoidCatchingGenericExceptionTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/design/AvoidDeeplyNestedIfStmtsTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/design/AvoidDeeplyNestedIfStmtsTest.java new file mode 100644 index 00000000000..cb42425327b --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/design/AvoidDeeplyNestedIfStmtsTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.design; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class AvoidDeeplyNestedIfStmtsTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/design/AvoidRethrowingExceptionTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/design/AvoidRethrowingExceptionTest.java new file mode 100644 index 00000000000..b5b96015f70 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/design/AvoidRethrowingExceptionTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.design; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class AvoidRethrowingExceptionTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/design/AvoidThrowingNewInstanceOfSameExceptionTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/design/AvoidThrowingNewInstanceOfSameExceptionTest.java new file mode 100644 index 00000000000..042bcfdbf68 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/design/AvoidThrowingNewInstanceOfSameExceptionTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.design; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class AvoidThrowingNewInstanceOfSameExceptionTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/design/AvoidThrowingNullPointerExceptionTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/design/AvoidThrowingNullPointerExceptionTest.java new file mode 100644 index 00000000000..399add80feb --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/design/AvoidThrowingNullPointerExceptionTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.design; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class AvoidThrowingNullPointerExceptionTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/design/AvoidThrowingRawExceptionTypesTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/design/AvoidThrowingRawExceptionTypesTest.java new file mode 100644 index 00000000000..e9370037f6c --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/design/AvoidThrowingRawExceptionTypesTest.java @@ -0,0 +1,25 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.design; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class AvoidThrowingRawExceptionTypesTest extends PmdRuleTst { + public static class Throwable extends java.lang.Throwable { + private static final long serialVersionUID = 1798165250043760600L; + } + + public static class Exception extends java.lang.Throwable { + private static final long serialVersionUID = -2518308549741147689L; + } + + public static class RuntimeException extends java.lang.Throwable { + private static final long serialVersionUID = 6341520923058239682L; + } + + public static class Error extends java.lang.Throwable { + private static final long serialVersionUID = -6965602141393320558L; + } +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/design/ClassWithOnlyPrivateConstructorsShouldBeFinalTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/design/ClassWithOnlyPrivateConstructorsShouldBeFinalTest.java new file mode 100644 index 00000000000..1687261131a --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/design/ClassWithOnlyPrivateConstructorsShouldBeFinalTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.design; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class ClassWithOnlyPrivateConstructorsShouldBeFinalTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/design/CollapsibleIfStatementsTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/design/CollapsibleIfStatementsTest.java new file mode 100644 index 00000000000..f3482168695 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/design/CollapsibleIfStatementsTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.design; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class CollapsibleIfStatementsTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/design/CouplingBetweenObjectsTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/design/CouplingBetweenObjectsTest.java new file mode 100644 index 00000000000..bb0fc00c9ec --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/design/CouplingBetweenObjectsTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.design; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class CouplingBetweenObjectsTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/design/CyclomaticComplexityTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/design/CyclomaticComplexityTest.java new file mode 100644 index 00000000000..2a922b0d8f3 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/design/CyclomaticComplexityTest.java @@ -0,0 +1,17 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.design; + +import net.sourceforge.pmd.Rule; +import net.sourceforge.pmd.lang.java.metrics.MetricsHook; +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class CyclomaticComplexityTest extends PmdRuleTst { + @Override + protected Rule reinitializeRule(Rule rule) { + MetricsHook.reset(); + return rule; + } +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/design/DataClassTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/design/DataClassTest.java new file mode 100644 index 00000000000..044eb744583 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/design/DataClassTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.design; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class DataClassTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/design/DesignRulesTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/design/DesignRulesTest.java deleted file mode 100644 index 46355ca5125..00000000000 --- a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/design/DesignRulesTest.java +++ /dev/null @@ -1,79 +0,0 @@ -/** - * BSD-style license; for more info see http://pmd.sourceforge.net/license.html - */ - -package net.sourceforge.pmd.lang.java.rule.design; - -import net.sourceforge.pmd.testframework.SimpleAggregatorTst; - -/** - * Tests all the rules, that are in the design ruleset. - */ -public class DesignRulesTest extends SimpleAggregatorTst { - - private static final String RULESET = "java-design"; - - @Override - public void setUp() { - addRule(RULESET, "AbstractClassWithoutAbstractMethod"); - addRule(RULESET, "AbstractClassWithoutAnyMethod"); - addRule(RULESET, "AccessorClassGeneration"); - addRule(RULESET, "AccessorMethodGeneration"); - addRule(RULESET, "AssignmentToNonFinalStatic"); - addRule(RULESET, "AvoidDeeplyNestedIfStmts"); - addRule(RULESET, "AvoidInstanceofChecksInCatchClause"); - addRule(RULESET, "AvoidProtectedFieldInFinalClass"); - addRule(RULESET, "AvoidProtectedMethodInFinalClassNotExtending"); - addRule(RULESET, "AvoidReassigningParameters"); - addRule(RULESET, "AvoidSynchronizedAtMethodLevel"); - addRule(RULESET, "BadComparison"); - addRule(RULESET, "ClassWithOnlyPrivateConstructorsShouldBeFinal"); - addRule(RULESET, "CloseResource"); - addRule(RULESET, "CompareObjectsWithEquals"); - addRule(RULESET, "ConfusingTernary"); - addRule(RULESET, "ConstantsInInterface"); - addRule(RULESET, "ConstructorCallsOverridableMethod"); - addRule(RULESET, "DefaultLabelNotLastInSwitchStmt"); - addRule(RULESET, "EmptyMethodInAbstractClassShouldBeAbstract"); - addRule(RULESET, "EqualsNull"); - addRule(RULESET, "FieldDeclarationsShouldBeAtStartOfClass"); - addRule(RULESET, "FinalFieldCouldBeStatic"); - addRule(RULESET, "GodClass"); - addRule(RULESET, "IdempotentOperations"); - addRule(RULESET, "ImmutableField"); - addRule(RULESET, "InstantiationToGetClass"); - addRule(RULESET, "LogicInversion"); - addRule(RULESET, "MissingBreakInSwitch"); - addRule(RULESET, "MissingStaticMethodInNonInstantiatableClass"); - addRule(RULESET, "NonCaseLabelInSwitchStatement"); - addRule(RULESET, "NonStaticInitializer"); - addRule(RULESET, "NonThreadSafeSingleton"); - addRule(RULESET, "OptimizableToArrayCall"); - // addRule(RULESET, "PositionalIteratorRule"); This rule does not yet - // exist - addRule(RULESET, "PositionLiteralsFirstInComparisons"); - addRule(RULESET, "PositionLiteralsFirstInCaseInsensitiveComparisons"); - addRule(RULESET, "PreserveStackTrace"); - addRule(RULESET, "ReturnEmptyArrayRatherThanNull"); - addRule(RULESET, "SimpleDateFormatNeedsLocale"); - addRule(RULESET, "SimplifyBooleanExpressions"); - addRule(RULESET, "SimplifyBooleanReturns"); - addRule(RULESET, "SimplifyConditional"); - addRule(RULESET, "SingleMethodSingleton"); - addRule(RULESET, "SingletonClassReturningNewInstance"); - addRule(RULESET, "SingularField"); - addRule(RULESET, "SwitchDensity"); - addRule(RULESET, "SwitchStmtsShouldHaveDefault"); - addRule(RULESET, "TooFewBranchesForASwitchStatement"); - // addRule(RULESET, "TooManyHttpFilter"); This rule does not yet exist - addRule(RULESET, "UncommentedEmptyConstructor"); - addRule(RULESET, "UncommentedEmptyMethodBody"); - addRule(RULESET, "UnnecessaryLocalBeforeReturn"); - addRule(RULESET, "UnsynchronizedStaticDateFormatter"); - addRule(RULESET, "UseCollectionIsEmpty"); - addRule(RULESET, "UseLocaleWithCaseConversions"); - addRule(RULESET, "UseNotifyAllInsteadOfNotify"); - addRule(RULESET, "UseUtilityClass"); - addRule(RULESET, "UseVarargs"); - } -} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/design/DoNotExtendJavaLangErrorTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/design/DoNotExtendJavaLangErrorTest.java new file mode 100644 index 00000000000..3d2ba33f800 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/design/DoNotExtendJavaLangErrorTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.design; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class DoNotExtendJavaLangErrorTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/design/ExceptionAsFlowControlTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/design/ExceptionAsFlowControlTest.java new file mode 100644 index 00000000000..c99bb7fca3c --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/design/ExceptionAsFlowControlTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.design; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class ExceptionAsFlowControlTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/design/ExcessiveClassLengthTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/design/ExcessiveClassLengthTest.java new file mode 100644 index 00000000000..d2a63dea3b9 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/design/ExcessiveClassLengthTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.design; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class ExcessiveClassLengthTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/design/ExcessiveImportsTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/design/ExcessiveImportsTest.java new file mode 100644 index 00000000000..437c46ab380 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/design/ExcessiveImportsTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.design; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class ExcessiveImportsTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/design/ExcessiveMethodLengthTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/design/ExcessiveMethodLengthTest.java new file mode 100644 index 00000000000..75dc63b795d --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/design/ExcessiveMethodLengthTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.design; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class ExcessiveMethodLengthTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/design/ExcessiveParameterListTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/design/ExcessiveParameterListTest.java new file mode 100644 index 00000000000..4903e4d7824 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/design/ExcessiveParameterListTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.design; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class ExcessiveParameterListTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/design/ExcessivePublicCountTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/design/ExcessivePublicCountTest.java new file mode 100644 index 00000000000..94770564143 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/design/ExcessivePublicCountTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.design; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class ExcessivePublicCountTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/design/FinalFieldCouldBeStaticTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/design/FinalFieldCouldBeStaticTest.java new file mode 100644 index 00000000000..1b2bf96d7ca --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/design/FinalFieldCouldBeStaticTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.design; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class FinalFieldCouldBeStaticTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/design/GodClassTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/design/GodClassTest.java new file mode 100644 index 00000000000..6dbff09d61e --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/design/GodClassTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.design; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class GodClassTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/design/ImmutableFieldTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/design/ImmutableFieldTest.java new file mode 100644 index 00000000000..fddff3df9cb --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/design/ImmutableFieldTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.design; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class ImmutableFieldTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/design/LawOfDemeterTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/design/LawOfDemeterTest.java new file mode 100644 index 00000000000..bd57f84d341 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/design/LawOfDemeterTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.design; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class LawOfDemeterTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/design/LogicInversionTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/design/LogicInversionTest.java new file mode 100644 index 00000000000..38959833e57 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/design/LogicInversionTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.design; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class LogicInversionTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/design/LoosePackageCouplingTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/design/LoosePackageCouplingTest.java new file mode 100644 index 00000000000..e587f4ba1c5 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/design/LoosePackageCouplingTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.design; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class LoosePackageCouplingTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/design/ModifiedCyclomaticComplexityTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/design/ModifiedCyclomaticComplexityTest.java new file mode 100644 index 00000000000..ec388aaf6f2 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/design/ModifiedCyclomaticComplexityTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.design; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class ModifiedCyclomaticComplexityTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/design/NPathComplexityTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/design/NPathComplexityTest.java new file mode 100644 index 00000000000..7cfb3bc81d4 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/design/NPathComplexityTest.java @@ -0,0 +1,17 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.design; + +import net.sourceforge.pmd.Rule; +import net.sourceforge.pmd.lang.java.metrics.MetricsHook; +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class NPathComplexityTest extends PmdRuleTst { + @Override + protected Rule reinitializeRule(Rule rule) { + MetricsHook.reset(); + return rule; + } +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/design/NcssConstructorCountTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/design/NcssConstructorCountTest.java new file mode 100644 index 00000000000..27f0c65d6c7 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/design/NcssConstructorCountTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.design; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class NcssConstructorCountTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/design/NcssCountTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/design/NcssCountTest.java new file mode 100644 index 00000000000..7a70967a7d9 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/design/NcssCountTest.java @@ -0,0 +1,17 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.design; + +import net.sourceforge.pmd.Rule; +import net.sourceforge.pmd.lang.java.metrics.MetricsHook; +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class NcssCountTest extends PmdRuleTst { + @Override + protected Rule reinitializeRule(Rule rule) { + MetricsHook.reset(); + return rule; + } +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/design/NcssMethodCountTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/design/NcssMethodCountTest.java new file mode 100644 index 00000000000..178e3ae0a3d --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/design/NcssMethodCountTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.design; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class NcssMethodCountTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/design/NcssTypeCountTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/design/NcssTypeCountTest.java new file mode 100644 index 00000000000..0645d950158 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/design/NcssTypeCountTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.design; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class NcssTypeCountTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/design/SignatureDeclareThrowsExceptionTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/design/SignatureDeclareThrowsExceptionTest.java new file mode 100644 index 00000000000..f76054932aa --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/design/SignatureDeclareThrowsExceptionTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.design; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class SignatureDeclareThrowsExceptionTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/design/SimplifiedTernaryTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/design/SimplifiedTernaryTest.java new file mode 100644 index 00000000000..c381684027a --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/design/SimplifiedTernaryTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.design; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class SimplifiedTernaryTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/design/SimplifyBooleanAssertionTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/design/SimplifyBooleanAssertionTest.java new file mode 100644 index 00000000000..9d5b97789fd --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/design/SimplifyBooleanAssertionTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.design; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class SimplifyBooleanAssertionTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/design/SimplifyBooleanExpressionsTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/design/SimplifyBooleanExpressionsTest.java new file mode 100644 index 00000000000..ef90b6a888b --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/design/SimplifyBooleanExpressionsTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.design; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class SimplifyBooleanExpressionsTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/design/SimplifyBooleanReturnsTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/design/SimplifyBooleanReturnsTest.java new file mode 100644 index 00000000000..487cc554e48 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/design/SimplifyBooleanReturnsTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.design; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class SimplifyBooleanReturnsTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/design/SimplifyConditionalTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/design/SimplifyConditionalTest.java new file mode 100644 index 00000000000..927ae8d8f85 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/design/SimplifyConditionalTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.design; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class SimplifyConditionalTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/design/SingularFieldTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/design/SingularFieldTest.java new file mode 100644 index 00000000000..98d36df5445 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/design/SingularFieldTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.design; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class SingularFieldTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/design/StdCyclomaticComplexityTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/design/StdCyclomaticComplexityTest.java new file mode 100644 index 00000000000..a3bbd6602f7 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/design/StdCyclomaticComplexityTest.java @@ -0,0 +1,44 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.design; + +import java.io.StringReader; +import java.util.Arrays; + +import org.junit.Assert; +import org.junit.Test; + +import net.sourceforge.pmd.RuleContext; +import net.sourceforge.pmd.lang.LanguageRegistry; +import net.sourceforge.pmd.lang.LanguageVersion; +import net.sourceforge.pmd.lang.Parser; +import net.sourceforge.pmd.lang.ParserOptions; +import net.sourceforge.pmd.lang.ast.Node; +import net.sourceforge.pmd.lang.java.JavaLanguageModule; +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class StdCyclomaticComplexityTest extends PmdRuleTst { + /** + * Make sure the entry stack is empty, if show classes complexity is + * disabled. + * + * @see bug #1501 + */ + @Test + public void entryStackMustBeEmpty() { + StdCyclomaticComplexityRule rule = new StdCyclomaticComplexityRule(); + rule.setProperty(StdCyclomaticComplexityRule.SHOW_CLASSES_COMPLEXITY_DESCRIPTOR, Boolean.FALSE); + + RuleContext ctx = new RuleContext(); + LanguageVersion javaLanguageVersion = LanguageRegistry.getLanguage(JavaLanguageModule.NAME).getVersion("1.8"); + ParserOptions parserOptions = javaLanguageVersion.getLanguageVersionHandler().getDefaultParserOptions(); + Parser parser = javaLanguageVersion.getLanguageVersionHandler().getParser(parserOptions); + Node node = parser.parse("test", new StringReader("public class SampleClass {}")); + + rule.apply(Arrays.asList(node), ctx); + + Assert.assertTrue(rule.entryStack.isEmpty()); + } +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/design/SwitchDensityTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/design/SwitchDensityTest.java new file mode 100644 index 00000000000..0da92cecacc --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/design/SwitchDensityTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.design; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class SwitchDensityTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/design/TooManyFieldsTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/design/TooManyFieldsTest.java new file mode 100644 index 00000000000..12cad4bac7d --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/design/TooManyFieldsTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.design; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class TooManyFieldsTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/design/TooManyMethodsTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/design/TooManyMethodsTest.java new file mode 100644 index 00000000000..c81262b2654 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/design/TooManyMethodsTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.design; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class TooManyMethodsTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/design/UseObjectForClearerAPITest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/design/UseObjectForClearerAPITest.java new file mode 100644 index 00000000000..1cabaee839b --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/design/UseObjectForClearerAPITest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.design; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class UseObjectForClearerAPITest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/design/UseUtilityClassTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/design/UseUtilityClassTest.java new file mode 100644 index 00000000000..36e7942a09a --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/design/UseUtilityClassTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.design; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class UseUtilityClassTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/design/UselessOverridingMethodTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/design/UselessOverridingMethodTest.java new file mode 100644 index 00000000000..47bc7198393 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/design/UselessOverridingMethodTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.design; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class UselessOverridingMethodTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/design/donotextendjavalangerror/Error.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/design/donotextendjavalangerror/Error.java new file mode 100644 index 00000000000..3631156f731 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/design/donotextendjavalangerror/Error.java @@ -0,0 +1,13 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.design.donotextendjavalangerror; + +/** + * @author Akshat Bahety + * @since 6.4.0 + */ +public class Error { + +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/design/signaturedeclarethrowsexception/MyTestCase.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/design/signaturedeclarethrowsexception/MyTestCase.java new file mode 100644 index 00000000000..ae347180624 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/design/signaturedeclarethrowsexception/MyTestCase.java @@ -0,0 +1,19 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.design.signaturedeclarethrowsexception; + +import org.junit.Ignore; + +import junit.framework.TestCase; + +/** + * Warning, this class IS NOT useless. It is used by the some regression tests. + * + * See file: SignatureDeclareThrowsException.xml + */ +@Ignore("not a test case") +public class MyTestCase extends TestCase { + +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/comments/AbstractCommentRuleTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/documentation/AbstractCommentRuleTest.java similarity index 76% rename from pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/comments/AbstractCommentRuleTest.java rename to pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/documentation/AbstractCommentRuleTest.java index 6a59ef17b23..bad0f73ad0a 100644 --- a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/comments/AbstractCommentRuleTest.java +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/documentation/AbstractCommentRuleTest.java @@ -2,7 +2,7 @@ * BSD-style license; for more info see http://pmd.sourceforge.net/license.html */ -package net.sourceforge.pmd.lang.java.rule.comments; +package net.sourceforge.pmd.lang.java.rule.documentation; import static org.junit.Assert.assertEquals; @@ -44,6 +44,24 @@ public void testFilteredCommentIn() { assertEquals("a formal comment with blank lines", filtered); } + @Test + public void testTagsIndicesIn() { + String comment = " /**\n" + + " * Checks if the metric can be computed on the node.\n" + + " *\n" + + " * @param node The node to check\n" + + " *\n" + + " * @return True if the metric can be computed\n" + + " */\n" + + " boolean supports(N node);\n" + + ""; + + List indices = testSubject.tagsIndicesIn(comment); + Assert.assertEquals(2, indices.size()); + Assert.assertEquals(79, indices.get(0).intValue()); + Assert.assertEquals(123, indices.get(1).intValue()); + } + @Test public void testCommentAssignments() { LanguageVersionHandler handler = LanguageRegistry.getLanguage(JavaLanguageModule.NAME).getVersion("1.8") diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/documentation/CommentContentTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/documentation/CommentContentTest.java new file mode 100644 index 00000000000..958a2058743 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/documentation/CommentContentTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.documentation; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class CommentContentTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/documentation/CommentRequiredTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/documentation/CommentRequiredTest.java new file mode 100644 index 00000000000..6b31f2edfe6 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/documentation/CommentRequiredTest.java @@ -0,0 +1,55 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.documentation; + +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; + +import java.util.ArrayList; +import java.util.List; + +import org.junit.Test; + +import net.sourceforge.pmd.Rule; +import net.sourceforge.pmd.properties.PropertyDescriptor; +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class CommentRequiredTest extends PmdRuleTst { + @Test + public void allCommentTypesIgnored() { + CommentRequiredRule rule = new CommentRequiredRule(); + assertNull("By default, the rule should be functional", rule.dysfunctionReason()); + + List> propertyDescriptors = getProperties(rule); + for (PropertyDescriptor property : propertyDescriptors) { + setPropertyValue(rule, property, "Ignored"); + } + + assertNotNull("All properties are ignored, rule should be dysfunctional", rule.dysfunctionReason()); + + // now, try out combinations: only one of the properties is required. + for (PropertyDescriptor property : propertyDescriptors) { + setPropertyValue(rule, property, "Required"); + assertNull("The property " + property.name() + " is set to required, the rule should be functional.", + rule.dysfunctionReason()); + setPropertyValue(rule, property, "Ignored"); + } + } + + private static List> getProperties(Rule rule) { + List> result = new ArrayList<>(); + for (PropertyDescriptor property : rule.getPropertyDescriptors()) { + if (property != Rule.VIOLATION_SUPPRESS_REGEX_DESCRIPTOR + && property != Rule.VIOLATION_SUPPRESS_XPATH_DESCRIPTOR) { + result.add(property); + } + } + return result; + } + + private static void setPropertyValue(Rule rule, PropertyDescriptor property, String value) { + rule.setProperty(property, property.valueFrom(value)); + } +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/documentation/CommentSizeTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/documentation/CommentSizeTest.java new file mode 100644 index 00000000000..51a404bcda3 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/documentation/CommentSizeTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.documentation; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class CommentSizeTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/documentation/UncommentedEmptyConstructorTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/documentation/UncommentedEmptyConstructorTest.java new file mode 100644 index 00000000000..c8753d83bd7 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/documentation/UncommentedEmptyConstructorTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.documentation; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class UncommentedEmptyConstructorTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/documentation/UncommentedEmptyMethodBodyTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/documentation/UncommentedEmptyMethodBodyTest.java new file mode 100644 index 00000000000..f0d9ae47db8 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/documentation/UncommentedEmptyMethodBodyTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.documentation; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class UncommentedEmptyMethodBodyTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/empty/EmptyRulesTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/empty/EmptyRulesTest.java deleted file mode 100644 index 142ec6643b8..00000000000 --- a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/empty/EmptyRulesTest.java +++ /dev/null @@ -1,27 +0,0 @@ -/** - * BSD-style license; for more info see http://pmd.sourceforge.net/license.html - */ - -package net.sourceforge.pmd.lang.java.rule.empty; - -import net.sourceforge.pmd.testframework.SimpleAggregatorTst; - -public class EmptyRulesTest extends SimpleAggregatorTst { - - private static final String RULESET = "java-empty"; - - @Override - public void setUp() { - addRule(RULESET, "EmptyCatchBlock"); - addRule(RULESET, "EmptyFinallyBlock"); - addRule(RULESET, "EmptyIfStmt"); - addRule(RULESET, "EmptyInitializer"); - addRule(RULESET, "EmptyStatementBlock"); - addRule(RULESET, "EmptyStatementNotInLoop"); - addRule(RULESET, "EmptyStaticInitializer"); - addRule(RULESET, "EmptySwitchStatements"); - addRule(RULESET, "EmptySynchronizedBlock"); - addRule(RULESET, "EmptyTryBlock"); - addRule(RULESET, "EmptyWhileStmt"); - } -} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/AssignmentInOperandTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/AssignmentInOperandTest.java new file mode 100644 index 00000000000..3432ef4411b --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/AssignmentInOperandTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.errorprone; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class AssignmentInOperandTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/AssignmentToNonFinalStaticTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/AssignmentToNonFinalStaticTest.java new file mode 100644 index 00000000000..3c1ee83e893 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/AssignmentToNonFinalStaticTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.errorprone; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class AssignmentToNonFinalStaticTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/AvoidAssertAsIdentifierTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/AvoidAssertAsIdentifierTest.java new file mode 100644 index 00000000000..8f1d8d151d1 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/AvoidAssertAsIdentifierTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.errorprone; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class AvoidAssertAsIdentifierTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/AvoidBranchingStatementAsLastInLoopTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/AvoidBranchingStatementAsLastInLoopTest.java new file mode 100644 index 00000000000..3628a5de052 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/AvoidBranchingStatementAsLastInLoopTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.errorprone; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class AvoidBranchingStatementAsLastInLoopTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/AvoidCallingFinalizeTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/AvoidCallingFinalizeTest.java new file mode 100644 index 00000000000..a7e3a8e9e75 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/AvoidCallingFinalizeTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.errorprone; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class AvoidCallingFinalizeTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/AvoidCatchingNPETest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/AvoidCatchingNPETest.java new file mode 100644 index 00000000000..18892011f06 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/AvoidCatchingNPETest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.errorprone; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class AvoidCatchingNPETest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/AvoidCatchingThrowableTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/AvoidCatchingThrowableTest.java new file mode 100644 index 00000000000..f05ef975550 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/AvoidCatchingThrowableTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.errorprone; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class AvoidCatchingThrowableTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/AvoidDecimalLiteralsInBigDecimalConstructorTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/AvoidDecimalLiteralsInBigDecimalConstructorTest.java new file mode 100644 index 00000000000..81d14c30429 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/AvoidDecimalLiteralsInBigDecimalConstructorTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.errorprone; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class AvoidDecimalLiteralsInBigDecimalConstructorTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/AvoidDuplicateLiteralsTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/AvoidDuplicateLiteralsTest.java new file mode 100644 index 00000000000..f6cc10d6450 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/AvoidDuplicateLiteralsTest.java @@ -0,0 +1,53 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.errorprone; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import java.util.Set; + +import org.junit.Test; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class AvoidDuplicateLiteralsTest extends PmdRuleTst { + @Test + public void testStringParserEmptyString() { + AvoidDuplicateLiteralsRule.ExceptionParser p = new AvoidDuplicateLiteralsRule.ExceptionParser(','); + Set res = p.parse(""); + assertTrue(res.isEmpty()); + } + + @Test + public void testStringParserSimple() { + AvoidDuplicateLiteralsRule.ExceptionParser p = new AvoidDuplicateLiteralsRule.ExceptionParser(','); + Set res = p.parse("a,b,c"); + assertEquals(3, res.size()); + assertTrue(res.contains("a")); + assertTrue(res.contains("b")); + assertTrue(res.contains("c")); + } + + @Test + public void testStringParserEscapedChar() { + AvoidDuplicateLiteralsRule.ExceptionParser p = new AvoidDuplicateLiteralsRule.ExceptionParser(','); + Set res = p.parse("a,b,\\,"); + assertEquals(3, res.size()); + assertTrue(res.contains("a")); + assertTrue(res.contains("b")); + assertTrue(res.contains(",")); + } + + @Test + public void testStringParserEscapedEscapedChar() { + AvoidDuplicateLiteralsRule.ExceptionParser p = new AvoidDuplicateLiteralsRule.ExceptionParser(','); + Set res = p.parse("a,b,\\\\"); + assertEquals(3, res.size()); + assertTrue(res.contains("a")); + assertTrue(res.contains("b")); + assertTrue(res.contains("\\")); + } +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/AvoidEnumAsIdentifierTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/AvoidEnumAsIdentifierTest.java new file mode 100644 index 00000000000..0c936de878b --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/AvoidEnumAsIdentifierTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.errorprone; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class AvoidEnumAsIdentifierTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/AvoidFieldNameMatchingMethodNameTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/AvoidFieldNameMatchingMethodNameTest.java new file mode 100644 index 00000000000..e6d75f353b0 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/AvoidFieldNameMatchingMethodNameTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.errorprone; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class AvoidFieldNameMatchingMethodNameTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/AvoidFieldNameMatchingTypeNameTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/AvoidFieldNameMatchingTypeNameTest.java new file mode 100644 index 00000000000..a76a447653a --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/AvoidFieldNameMatchingTypeNameTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.errorprone; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class AvoidFieldNameMatchingTypeNameTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/AvoidInstanceofChecksInCatchClauseTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/AvoidInstanceofChecksInCatchClauseTest.java new file mode 100644 index 00000000000..6742ca059fe --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/AvoidInstanceofChecksInCatchClauseTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.errorprone; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class AvoidInstanceofChecksInCatchClauseTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/AvoidLiteralsInIfConditionTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/AvoidLiteralsInIfConditionTest.java new file mode 100644 index 00000000000..76210189425 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/AvoidLiteralsInIfConditionTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.errorprone; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class AvoidLiteralsInIfConditionTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/AvoidLosingExceptionInformationTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/AvoidLosingExceptionInformationTest.java new file mode 100644 index 00000000000..33f4635cd18 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/AvoidLosingExceptionInformationTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.errorprone; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class AvoidLosingExceptionInformationTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/AvoidMultipleUnaryOperatorsTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/AvoidMultipleUnaryOperatorsTest.java new file mode 100644 index 00000000000..5ac541e0899 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/AvoidMultipleUnaryOperatorsTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.errorprone; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class AvoidMultipleUnaryOperatorsTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/AvoidUsingOctalValuesTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/AvoidUsingOctalValuesTest.java new file mode 100644 index 00000000000..cf4f8736bd1 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/AvoidUsingOctalValuesTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.errorprone; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class AvoidUsingOctalValuesTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/BadComparisonTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/BadComparisonTest.java new file mode 100644 index 00000000000..215f27fd796 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/BadComparisonTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.errorprone; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class BadComparisonTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/BeanMembersShouldSerializeTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/BeanMembersShouldSerializeTest.java new file mode 100644 index 00000000000..4d20fca51f8 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/BeanMembersShouldSerializeTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.errorprone; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class BeanMembersShouldSerializeTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/BrokenNullCheckTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/BrokenNullCheckTest.java new file mode 100644 index 00000000000..e4bc49884ca --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/BrokenNullCheckTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.errorprone; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class BrokenNullCheckTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/CallSuperFirstTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/CallSuperFirstTest.java new file mode 100644 index 00000000000..058ba5c874d --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/CallSuperFirstTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.errorprone; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class CallSuperFirstTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/CallSuperLastTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/CallSuperLastTest.java new file mode 100644 index 00000000000..fce7b262196 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/CallSuperLastTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.errorprone; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class CallSuperLastTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/CheckSkipResultTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/CheckSkipResultTest.java new file mode 100644 index 00000000000..03ff8854110 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/CheckSkipResultTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.errorprone; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class CheckSkipResultTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/ClassCastExceptionWithToArrayTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/ClassCastExceptionWithToArrayTest.java new file mode 100644 index 00000000000..cbc7d414639 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/ClassCastExceptionWithToArrayTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.errorprone; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class ClassCastExceptionWithToArrayTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/CloneMethodMustBePublicTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/CloneMethodMustBePublicTest.java new file mode 100644 index 00000000000..3a929d920e2 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/CloneMethodMustBePublicTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.errorprone; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class CloneMethodMustBePublicTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/CloneMethodMustImplementCloneableTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/CloneMethodMustImplementCloneableTest.java new file mode 100644 index 00000000000..f1f57448702 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/CloneMethodMustImplementCloneableTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.errorprone; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class CloneMethodMustImplementCloneableTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/CloneMethodReturnTypeMustMatchClassNameTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/CloneMethodReturnTypeMustMatchClassNameTest.java new file mode 100644 index 00000000000..3ce93362975 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/CloneMethodReturnTypeMustMatchClassNameTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.errorprone; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class CloneMethodReturnTypeMustMatchClassNameTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/CloneThrowsCloneNotSupportedExceptionTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/CloneThrowsCloneNotSupportedExceptionTest.java new file mode 100644 index 00000000000..e4cd2411f1f --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/CloneThrowsCloneNotSupportedExceptionTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.errorprone; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class CloneThrowsCloneNotSupportedExceptionTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/CloseResourceTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/CloseResourceTest.java new file mode 100644 index 00000000000..a20781ae9cc --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/CloseResourceTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.errorprone; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class CloseResourceTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/CompareObjectsWithEqualsTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/CompareObjectsWithEqualsTest.java new file mode 100644 index 00000000000..46b5b9b8371 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/CompareObjectsWithEqualsTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.errorprone; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class CompareObjectsWithEqualsTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/ConstructorCallsOverridableMethodTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/ConstructorCallsOverridableMethodTest.java new file mode 100644 index 00000000000..d42e18a2d5d --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/ConstructorCallsOverridableMethodTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.errorprone; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class ConstructorCallsOverridableMethodTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/DataflowAnomalyAnalysisTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/DataflowAnomalyAnalysisTest.java new file mode 100644 index 00000000000..16c3ac6ba20 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/DataflowAnomalyAnalysisTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.errorprone; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class DataflowAnomalyAnalysisTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/DoNotCallGarbageCollectionExplicitlyTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/DoNotCallGarbageCollectionExplicitlyTest.java new file mode 100644 index 00000000000..3d3b63a54a8 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/DoNotCallGarbageCollectionExplicitlyTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.errorprone; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class DoNotCallGarbageCollectionExplicitlyTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/DoNotCallSystemExitTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/DoNotCallSystemExitTest.java new file mode 100644 index 00000000000..cfcfd46bb46 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/DoNotCallSystemExitTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.errorprone; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class DoNotCallSystemExitTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/DoNotExtendJavaLangThrowableTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/DoNotExtendJavaLangThrowableTest.java new file mode 100644 index 00000000000..23762dabfb2 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/DoNotExtendJavaLangThrowableTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.errorprone; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class DoNotExtendJavaLangThrowableTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/DoNotHardCodeSDCardTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/DoNotHardCodeSDCardTest.java new file mode 100644 index 00000000000..e312edcf437 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/DoNotHardCodeSDCardTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.errorprone; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class DoNotHardCodeSDCardTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/DoNotThrowExceptionInFinallyTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/DoNotThrowExceptionInFinallyTest.java new file mode 100644 index 00000000000..1fdda55c42f --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/DoNotThrowExceptionInFinallyTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.errorprone; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class DoNotThrowExceptionInFinallyTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/DontImportSunTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/DontImportSunTest.java new file mode 100644 index 00000000000..b0951490c65 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/DontImportSunTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.errorprone; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class DontImportSunTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/DontUseFloatTypeForLoopIndicesTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/DontUseFloatTypeForLoopIndicesTest.java new file mode 100644 index 00000000000..bff2a2c3f3b --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/DontUseFloatTypeForLoopIndicesTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.errorprone; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class DontUseFloatTypeForLoopIndicesTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/EmptyCatchBlockTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/EmptyCatchBlockTest.java new file mode 100644 index 00000000000..53e61e82269 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/EmptyCatchBlockTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.errorprone; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class EmptyCatchBlockTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/EmptyFinalizerTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/EmptyFinalizerTest.java new file mode 100644 index 00000000000..bb81c264a3d --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/EmptyFinalizerTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.errorprone; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class EmptyFinalizerTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/EmptyFinallyBlockTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/EmptyFinallyBlockTest.java new file mode 100644 index 00000000000..f076b33017a --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/EmptyFinallyBlockTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.errorprone; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class EmptyFinallyBlockTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/EmptyIfStmtTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/EmptyIfStmtTest.java new file mode 100644 index 00000000000..bf2bff63cf9 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/EmptyIfStmtTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.errorprone; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class EmptyIfStmtTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/EmptyInitializerTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/EmptyInitializerTest.java new file mode 100644 index 00000000000..32d1abfab99 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/EmptyInitializerTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.errorprone; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class EmptyInitializerTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/EmptyStatementBlockTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/EmptyStatementBlockTest.java new file mode 100644 index 00000000000..2d49e087d93 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/EmptyStatementBlockTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.errorprone; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class EmptyStatementBlockTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/EmptyStatementNotInLoopTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/EmptyStatementNotInLoopTest.java new file mode 100644 index 00000000000..fe4d05a8e8f --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/EmptyStatementNotInLoopTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.errorprone; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class EmptyStatementNotInLoopTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/EmptySwitchStatementsTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/EmptySwitchStatementsTest.java new file mode 100644 index 00000000000..edf5cd3362a --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/EmptySwitchStatementsTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.errorprone; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class EmptySwitchStatementsTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/EmptySynchronizedBlockTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/EmptySynchronizedBlockTest.java new file mode 100644 index 00000000000..a66b28d808b --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/EmptySynchronizedBlockTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.errorprone; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class EmptySynchronizedBlockTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/EmptyTryBlockTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/EmptyTryBlockTest.java new file mode 100644 index 00000000000..3c6e1330b6a --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/EmptyTryBlockTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.errorprone; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class EmptyTryBlockTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/EmptyWhileStmtTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/EmptyWhileStmtTest.java new file mode 100644 index 00000000000..d3700819162 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/EmptyWhileStmtTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.errorprone; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class EmptyWhileStmtTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/EqualsNullTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/EqualsNullTest.java new file mode 100644 index 00000000000..e869a5d6095 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/EqualsNullTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.errorprone; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class EqualsNullTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/FinalizeDoesNotCallSuperFinalizeTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/FinalizeDoesNotCallSuperFinalizeTest.java new file mode 100644 index 00000000000..97017d2b877 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/FinalizeDoesNotCallSuperFinalizeTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.errorprone; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class FinalizeDoesNotCallSuperFinalizeTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/FinalizeOnlyCallsSuperFinalizeTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/FinalizeOnlyCallsSuperFinalizeTest.java new file mode 100644 index 00000000000..af0ab117397 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/FinalizeOnlyCallsSuperFinalizeTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.errorprone; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class FinalizeOnlyCallsSuperFinalizeTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/FinalizeOverloadedTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/FinalizeOverloadedTest.java new file mode 100644 index 00000000000..e123e98a1c6 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/FinalizeOverloadedTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.errorprone; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class FinalizeOverloadedTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/FinalizeShouldBeProtectedTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/FinalizeShouldBeProtectedTest.java new file mode 100644 index 00000000000..73b0ee58fc5 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/FinalizeShouldBeProtectedTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.errorprone; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class FinalizeShouldBeProtectedTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/IdempotentOperationsTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/IdempotentOperationsTest.java new file mode 100644 index 00000000000..6b29749f2fb --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/IdempotentOperationsTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.errorprone; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class IdempotentOperationsTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/ImportFromSamePackageTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/ImportFromSamePackageTest.java new file mode 100644 index 00000000000..111cae739a3 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/ImportFromSamePackageTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.errorprone; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class ImportFromSamePackageTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/InstantiationToGetClassTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/InstantiationToGetClassTest.java new file mode 100644 index 00000000000..f27f820f6fc --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/InstantiationToGetClassTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.errorprone; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class InstantiationToGetClassTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/InvalidSlf4jMessageFormatTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/InvalidSlf4jMessageFormatTest.java new file mode 100644 index 00000000000..6d8bb37721f --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/InvalidSlf4jMessageFormatTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.errorprone; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class InvalidSlf4jMessageFormatTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/JUnitSpellingTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/JUnitSpellingTest.java new file mode 100644 index 00000000000..209d3bb488e --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/JUnitSpellingTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.errorprone; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class JUnitSpellingTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/JUnitStaticSuiteTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/JUnitStaticSuiteTest.java new file mode 100644 index 00000000000..67b9a62bb52 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/JUnitStaticSuiteTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.errorprone; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class JUnitStaticSuiteTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/JumbledIncrementerTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/JumbledIncrementerTest.java new file mode 100644 index 00000000000..90f349f2436 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/JumbledIncrementerTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.errorprone; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class JumbledIncrementerTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/LoggerIsNotStaticFinalTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/LoggerIsNotStaticFinalTest.java new file mode 100644 index 00000000000..c3f990070d4 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/LoggerIsNotStaticFinalTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.errorprone; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class LoggerIsNotStaticFinalTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/MethodWithSameNameAsEnclosingClassTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/MethodWithSameNameAsEnclosingClassTest.java new file mode 100644 index 00000000000..c4060f965c0 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/MethodWithSameNameAsEnclosingClassTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.errorprone; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class MethodWithSameNameAsEnclosingClassTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/MisplacedNullCheckTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/MisplacedNullCheckTest.java new file mode 100644 index 00000000000..371de4f655b --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/MisplacedNullCheckTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.errorprone; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class MisplacedNullCheckTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/MissingBreakInSwitchTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/MissingBreakInSwitchTest.java new file mode 100644 index 00000000000..c78f69909e8 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/MissingBreakInSwitchTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.errorprone; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class MissingBreakInSwitchTest extends PmdRuleTst { + // no additional unit tests +} 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 00000000000..f402d9a7931 --- /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/java/net/sourceforge/pmd/lang/java/rule/errorprone/MissingSerialVersionUIDTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/MissingSerialVersionUIDTest.java new file mode 100644 index 00000000000..b2437fbaf83 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/MissingSerialVersionUIDTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.errorprone; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class MissingSerialVersionUIDTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/MissingStaticMethodInNonInstantiatableClassTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/MissingStaticMethodInNonInstantiatableClassTest.java new file mode 100644 index 00000000000..f3978f34295 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/MissingStaticMethodInNonInstantiatableClassTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.errorprone; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class MissingStaticMethodInNonInstantiatableClassTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/MoreThanOneLoggerTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/MoreThanOneLoggerTest.java new file mode 100644 index 00000000000..2e970f3641b --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/MoreThanOneLoggerTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.errorprone; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class MoreThanOneLoggerTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/MyInterface.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/MyInterface.java new file mode 100644 index 00000000000..b8ae7fb7eb5 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/MyInterface.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; + +/** + * Warning, this class ARE not useless. It is used by the some of regression + * tests. + * See file "CloneMethodMustImplementCloneable.xml" + */ +public interface MyInterface extends Cloneable { + +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/NonCaseLabelInSwitchStatementTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/NonCaseLabelInSwitchStatementTest.java new file mode 100644 index 00000000000..ab4a88a5d8e --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/NonCaseLabelInSwitchStatementTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.errorprone; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class NonCaseLabelInSwitchStatementTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/NonStaticInitializerTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/NonStaticInitializerTest.java new file mode 100644 index 00000000000..f009ce5ab25 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/NonStaticInitializerTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.errorprone; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class NonStaticInitializerTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/NullAssignmentTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/NullAssignmentTest.java new file mode 100644 index 00000000000..3a65b157f26 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/NullAssignmentTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.errorprone; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class NullAssignmentTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/OverrideBothEqualsAndHashcodeTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/OverrideBothEqualsAndHashcodeTest.java new file mode 100644 index 00000000000..32d88b23b1a --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/OverrideBothEqualsAndHashcodeTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.errorprone; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class OverrideBothEqualsAndHashcodeTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/ProperCloneImplementationTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/ProperCloneImplementationTest.java new file mode 100644 index 00000000000..dc40b33138e --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/ProperCloneImplementationTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.errorprone; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class ProperCloneImplementationTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/ProperLoggerTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/ProperLoggerTest.java new file mode 100644 index 00000000000..18f13af50be --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/ProperLoggerTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.errorprone; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class ProperLoggerTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/ReturnEmptyArrayRatherThanNullTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/ReturnEmptyArrayRatherThanNullTest.java new file mode 100644 index 00000000000..116741d89fe --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/ReturnEmptyArrayRatherThanNullTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.errorprone; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class ReturnEmptyArrayRatherThanNullTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/ReturnFromFinallyBlockTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/ReturnFromFinallyBlockTest.java new file mode 100644 index 00000000000..5945a9133fb --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/ReturnFromFinallyBlockTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.errorprone; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class ReturnFromFinallyBlockTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/SimpleDateFormatNeedsLocaleTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/SimpleDateFormatNeedsLocaleTest.java new file mode 100644 index 00000000000..6a8c7b0c7e8 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/SimpleDateFormatNeedsLocaleTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.errorprone; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class SimpleDateFormatNeedsLocaleTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/SingleMethodSingletonTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/SingleMethodSingletonTest.java new file mode 100644 index 00000000000..067a517f944 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/SingleMethodSingletonTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.errorprone; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class SingleMethodSingletonTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/SingletonClassReturningNewInstanceTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/SingletonClassReturningNewInstanceTest.java new file mode 100644 index 00000000000..ca957ed18f4 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/SingletonClassReturningNewInstanceTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.errorprone; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class SingletonClassReturningNewInstanceTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/StaticEJBFieldShouldBeFinalTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/StaticEJBFieldShouldBeFinalTest.java new file mode 100644 index 00000000000..af2f8fcdf24 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/StaticEJBFieldShouldBeFinalTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.errorprone; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class StaticEJBFieldShouldBeFinalTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/StringBufferInstantiationWithCharTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/StringBufferInstantiationWithCharTest.java new file mode 100644 index 00000000000..f4e819b3288 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/StringBufferInstantiationWithCharTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.errorprone; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class StringBufferInstantiationWithCharTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/SuspiciousEqualsMethodNameTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/SuspiciousEqualsMethodNameTest.java new file mode 100644 index 00000000000..7787ade01b0 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/SuspiciousEqualsMethodNameTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.errorprone; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class SuspiciousEqualsMethodNameTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/SuspiciousHashcodeMethodNameTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/SuspiciousHashcodeMethodNameTest.java new file mode 100644 index 00000000000..28d3edba4e1 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/SuspiciousHashcodeMethodNameTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.errorprone; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class SuspiciousHashcodeMethodNameTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/SuspiciousOctalEscapeTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/SuspiciousOctalEscapeTest.java new file mode 100644 index 00000000000..c5549023aae --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/SuspiciousOctalEscapeTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.errorprone; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class SuspiciousOctalEscapeTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/TestClassWithoutTestCasesTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/TestClassWithoutTestCasesTest.java new file mode 100644 index 00000000000..c901a755219 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/TestClassWithoutTestCasesTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.errorprone; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class TestClassWithoutTestCasesTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/UnconditionalIfStatementTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/UnconditionalIfStatementTest.java new file mode 100644 index 00000000000..6af4a7f8e38 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/UnconditionalIfStatementTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.errorprone; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class UnconditionalIfStatementTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/UnnecessaryBooleanAssertionTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/UnnecessaryBooleanAssertionTest.java new file mode 100644 index 00000000000..2b343d5134f --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/UnnecessaryBooleanAssertionTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.errorprone; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class UnnecessaryBooleanAssertionTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/UnnecessaryCaseChangeTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/UnnecessaryCaseChangeTest.java new file mode 100644 index 00000000000..4cbb6ea7e3b --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/UnnecessaryCaseChangeTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.errorprone; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class UnnecessaryCaseChangeTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/UnnecessaryConversionTemporaryTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/UnnecessaryConversionTemporaryTest.java new file mode 100644 index 00000000000..aa375ea1045 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/UnnecessaryConversionTemporaryTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.errorprone; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class UnnecessaryConversionTemporaryTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/UnusedNullCheckInEqualsTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/UnusedNullCheckInEqualsTest.java new file mode 100644 index 00000000000..0d5020056d5 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/UnusedNullCheckInEqualsTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.errorprone; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class UnusedNullCheckInEqualsTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/UseCorrectExceptionLoggingTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/UseCorrectExceptionLoggingTest.java new file mode 100644 index 00000000000..f6efc457153 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/UseCorrectExceptionLoggingTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.errorprone; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class UseCorrectExceptionLoggingTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/UseEqualsToCompareStringsTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/UseEqualsToCompareStringsTest.java new file mode 100644 index 00000000000..903c0d420d7 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/UseEqualsToCompareStringsTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.errorprone; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class UseEqualsToCompareStringsTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/UseLocaleWithCaseConversionsTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/UseLocaleWithCaseConversionsTest.java new file mode 100644 index 00000000000..7c0403ba8dc --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/UseLocaleWithCaseConversionsTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.errorprone; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class UseLocaleWithCaseConversionsTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/UseProperClassLoaderTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/UseProperClassLoaderTest.java new file mode 100644 index 00000000000..ede72712894 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/UseProperClassLoaderTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.errorprone; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class UseProperClassLoaderTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/UselessOperationOnImmutableTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/UselessOperationOnImmutableTest.java new file mode 100644 index 00000000000..54012b3130c --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/errorprone/UselessOperationOnImmutableTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.errorprone; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class UselessOperationOnImmutableTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/finalizers/FinalizersRulesTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/finalizers/FinalizersRulesTest.java deleted file mode 100644 index b1d48b2530e..00000000000 --- a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/finalizers/FinalizersRulesTest.java +++ /dev/null @@ -1,22 +0,0 @@ -/** - * BSD-style license; for more info see http://pmd.sourceforge.net/license.html - */ - -package net.sourceforge.pmd.lang.java.rule.finalizers; - -import net.sourceforge.pmd.testframework.SimpleAggregatorTst; - -public class FinalizersRulesTest extends SimpleAggregatorTst { - - private static final String RULESET = "java-finalizers"; - - @Override - public void setUp() { - addRule(RULESET, "AvoidCallingFinalize"); - addRule(RULESET, "EmptyFinalizer"); - addRule(RULESET, "FinalizeDoesNotCallSuperFinalize"); - addRule(RULESET, "FinalizeOnlyCallsSuperFinalize"); - addRule(RULESET, "FinalizeOverloaded"); - addRule(RULESET, "FinalizeShouldBeProtected"); - } -} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/imports/ImportsRulesTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/imports/ImportsRulesTest.java deleted file mode 100644 index ce7e5233a2d..00000000000 --- a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/imports/ImportsRulesTest.java +++ /dev/null @@ -1,65 +0,0 @@ -/** - * BSD-style license; for more info see http://pmd.sourceforge.net/license.html - */ - -package net.sourceforge.pmd.lang.java.rule.imports; - -import net.sourceforge.pmd.testframework.SimpleAggregatorTst; - -public class ImportsRulesTest extends SimpleAggregatorTst { - - private static final String RULESET = "java-imports"; - - @Override - public void setUp() { - addRule(RULESET, "DontImportJavaLang"); - addRule(RULESET, "DuplicateImports"); - addRule(RULESET, "ImportFromSamePackage"); - addRule(RULESET, "TooManyStaticImports"); - addRule(RULESET, "UnnecessaryFullyQualifiedName"); - addRule(RULESET, "UnusedImports"); - } - - /** - * This is just for testing DuplicateImports for static imports and - * disambiguation. - */ - // Do not delete this method, its needed for a test case - // see: - // /pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/imports/xml/DuplicateImports.xml - // #1306 False positive on duplicate when using static imports - public static void assertTrue(String message, boolean condition) { - if (!condition) { - System.out.println(message); - } - } - - // Do not delete these two enums - it is needed for a test case - // see: - // /pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/imports/xml/UnnecessaryFullyQualifiedName.xml - // #1436 UnnecessaryFullyQualifiedName false positive on clashing static - // imports with enums - public enum ENUM1 { - A, B; - } - - public enum ENUM2 { - C, D; - } - - // Do not delete these two classes - it is needed for a test case - // see: /pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/imports/xml/UnnecessaryFullyQualifiedName.xml - // #1546 part 1 UnnecessaryFullyQualifiedName doesn't take into consideration conflict resolution - // #1546 part 2 UnnecessaryFullyQualifiedName doesn't take into consideration conflict resolution - public static class PhonyMockito { - public static T mock(Class clazz) { - return null; - } - } - - public static class PhonyPowerMockito { - public static T mock(Class clazz) { - return null; - } - } -} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/j2ee/J2EERulesTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/j2ee/J2EERulesTest.java deleted file mode 100644 index c33b136c7f6..00000000000 --- a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/j2ee/J2EERulesTest.java +++ /dev/null @@ -1,25 +0,0 @@ -/** - * BSD-style license; for more info see http://pmd.sourceforge.net/license.html - */ - -package net.sourceforge.pmd.lang.java.rule.j2ee; - -import net.sourceforge.pmd.testframework.SimpleAggregatorTst; - -public class J2EERulesTest extends SimpleAggregatorTst { - - private static final String RULESET = "java-j2ee"; - - @Override - public void setUp() { - addRule(RULESET, "DoNotCallSystemExit"); - addRule(RULESET, "DoNotUseThreads"); - addRule(RULESET, "LocalHomeNamingConvention"); - addRule(RULESET, "LocalInterfaceSessionNamingConvention"); - addRule(RULESET, "MDBAndSessionBeanNamingConvention"); - addRule(RULESET, "RemoteInterfaceNamingConvention"); - addRule(RULESET, "RemoteSessionInterfaceNamingConvention"); - addRule(RULESET, "StaticEJBFieldShouldBeFinal"); - addRule(RULESET, "UseProperClassLoader"); - } -} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/javabeans/JavabeansRulesTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/javabeans/JavabeansRulesTest.java deleted file mode 100644 index 9db68bbadd5..00000000000 --- a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/javabeans/JavabeansRulesTest.java +++ /dev/null @@ -1,18 +0,0 @@ -/** - * BSD-style license; for more info see http://pmd.sourceforge.net/license.html - */ - -package net.sourceforge.pmd.lang.java.rule.javabeans; - -import net.sourceforge.pmd.testframework.SimpleAggregatorTst; - -public class JavabeansRulesTest extends SimpleAggregatorTst { - - private static final String RULESET = "java-javabeans"; - - @Override - public void setUp() { - addRule(RULESET, "BeanMembersShouldSerialize"); - addRule(RULESET, "MissingSerialVersionUID"); - } -} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/junit/JunitRulesTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/junit/JunitRulesTest.java deleted file mode 100644 index 55eaed60fb8..00000000000 --- a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/junit/JunitRulesTest.java +++ /dev/null @@ -1,28 +0,0 @@ -/** - * BSD-style license; for more info see http://pmd.sourceforge.net/license.html - */ - -package net.sourceforge.pmd.lang.java.rule.junit; - -import net.sourceforge.pmd.testframework.SimpleAggregatorTst; - -public class JunitRulesTest extends SimpleAggregatorTst { - - private static final String RULESET = "java-junit"; - - @Override - public void setUp() { - addRule(RULESET, "JUnitAssertionsShouldIncludeMessage"); - addRule(RULESET, "JUnitSpelling"); - addRule(RULESET, "JUnitStaticSuite"); - addRule(RULESET, "JUnitTestContainsTooManyAsserts"); - addRule(RULESET, "JUnitTestsShouldIncludeAssert"); - addRule(RULESET, "SimplifyBooleanAssertion"); - addRule(RULESET, "TestClassWithoutTestCases"); - addRule(RULESET, "UnnecessaryBooleanAssertion"); - addRule(RULESET, "UseAssertEqualsInsteadOfAssertTrue"); - addRule(RULESET, "UseAssertNullInsteadOfAssertTrue"); - addRule(RULESET, "UseAssertSameInsteadOfAssertTrue"); - addRule(RULESET, "UseAssertTrueInsteadOfAssertEquals"); - } -} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/loggingjakartacommons/LoggingJakartaCommonsRulesTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/loggingjakartacommons/LoggingJakartaCommonsRulesTest.java deleted file mode 100644 index ff3b0d89f63..00000000000 --- a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/loggingjakartacommons/LoggingJakartaCommonsRulesTest.java +++ /dev/null @@ -1,20 +0,0 @@ -/** - * BSD-style license; for more info see http://pmd.sourceforge.net/license.html - */ - -package net.sourceforge.pmd.lang.java.rule.loggingjakartacommons; - -import net.sourceforge.pmd.testframework.SimpleAggregatorTst; - -public class LoggingJakartaCommonsRulesTest extends SimpleAggregatorTst { - - private static final String RULESET = "java-logging-jakarta-commons"; - - @Override - public void setUp() { - addRule(RULESET, "ProperLogger"); - addRule(RULESET, "UseCorrectExceptionLogging"); - addRule(RULESET, "GuardDebugLogging"); - addRule(RULESET, "GuardLogStatement"); - } -} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/loggingjava/LoggingJavaRulesTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/loggingjava/LoggingJavaRulesTest.java deleted file mode 100644 index 8728a35305c..00000000000 --- a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/loggingjava/LoggingJavaRulesTest.java +++ /dev/null @@ -1,22 +0,0 @@ -/** - * BSD-style license; for more info see http://pmd.sourceforge.net/license.html - */ - -package net.sourceforge.pmd.lang.java.rule.loggingjava; - -import net.sourceforge.pmd.testframework.SimpleAggregatorTst; - -public class LoggingJavaRulesTest extends SimpleAggregatorTst { - - private static final String RULESET = "java-logging-java"; - - @Override - public void setUp() { - addRule(RULESET, "AvoidPrintStackTrace"); - addRule(RULESET, "LoggerIsNotStaticFinal"); - addRule(RULESET, "MoreThanOneLogger"); - addRule(RULESET, "SystemPrintln"); - addRule(RULESET, "GuardLogStatementJavaUtil"); - addRule(RULESET, "InvalidSlf4jMessageFormat"); - } -} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/metrics/MetricsRulesTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/metrics/MetricsRulesTest.java deleted file mode 100644 index 30d7b90bb43..00000000000 --- a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/metrics/MetricsRulesTest.java +++ /dev/null @@ -1,32 +0,0 @@ -/** - * BSD-style license; for more info see http://pmd.sourceforge.net/license.html - */ - -package net.sourceforge.pmd.lang.java.rule.metrics; - -import net.sourceforge.pmd.Rule; -import net.sourceforge.pmd.lang.java.metrics.MetricsHook; -import net.sourceforge.pmd.testframework.SimpleAggregatorTst; - -/** - * @author Clément Fournier - */ -public class MetricsRulesTest extends SimpleAggregatorTst { - - private static final String RULESET = "java-metrics"; - - - @Override - protected Rule reinitializeRule(Rule rule) { - MetricsHook.reset(); - return super.reinitializeRule(rule); - } - - - @Override - public void setUp() { - addRule(RULESET, "CyclomaticComplexity"); - addRule(RULESET, "NPathComplexity"); - addRule(RULESET, "DataClass"); - } -} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/migrating/MigratingRulesTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/migrating/MigratingRulesTest.java deleted file mode 100644 index 4354dca831f..00000000000 --- a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/migrating/MigratingRulesTest.java +++ /dev/null @@ -1,31 +0,0 @@ -/** - * BSD-style license; for more info see http://pmd.sourceforge.net/license.html - */ - -package net.sourceforge.pmd.lang.java.rule.migrating; - -import net.sourceforge.pmd.testframework.SimpleAggregatorTst; - -public class MigratingRulesTest extends SimpleAggregatorTst { - - private static final String RULESET = "java-migrating"; - - @Override - public void setUp() { - addRule(RULESET, "AvoidAssertAsIdentifier"); - addRule(RULESET, "AvoidEnumAsIdentifier"); - addRule(RULESET, "ByteInstantiation"); - addRule(RULESET, "ForLoopCanBeForeach"); - addRule(RULESET, "IntegerInstantiation"); - addRule(RULESET, "JUnit4SuitesShouldUseSuiteAnnotation"); - addRule(RULESET, "JUnit4TestShouldUseAfterAnnotation"); - addRule(RULESET, "JUnit4TestShouldUseBeforeAnnotation"); - addRule(RULESET, "JUnit4TestShouldUseTestAnnotation"); - addRule(RULESET, "JUnitUseExpected"); - addRule(RULESET, "LongInstantiation"); - addRule(RULESET, "ReplaceEnumerationWithIterator"); - addRule(RULESET, "ReplaceHashtableWithMap"); - addRule(RULESET, "ReplaceVectorWithList"); - addRule(RULESET, "ShortInstantiation"); - } -} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/multithreading/AvoidSynchronizedAtMethodLevelTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/multithreading/AvoidSynchronizedAtMethodLevelTest.java new file mode 100644 index 00000000000..5c1793c6d8f --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/multithreading/AvoidSynchronizedAtMethodLevelTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.multithreading; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class AvoidSynchronizedAtMethodLevelTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/multithreading/AvoidThreadGroupTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/multithreading/AvoidThreadGroupTest.java new file mode 100644 index 00000000000..2afcbaa379c --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/multithreading/AvoidThreadGroupTest.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.multithreading; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class AvoidThreadGroupTest extends PmdRuleTst { + // Used by AvoidThreadGroup test cases + public static class ThreadGroup { + + } +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/multithreading/AvoidUsingVolatileTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/multithreading/AvoidUsingVolatileTest.java new file mode 100644 index 00000000000..ce61a3e5c63 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/multithreading/AvoidUsingVolatileTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.multithreading; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class AvoidUsingVolatileTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/multithreading/DoNotUseThreadsTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/multithreading/DoNotUseThreadsTest.java new file mode 100644 index 00000000000..9ba3e87d618 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/multithreading/DoNotUseThreadsTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.multithreading; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class DoNotUseThreadsTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/multithreading/DontCallThreadRunTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/multithreading/DontCallThreadRunTest.java new file mode 100644 index 00000000000..c628ed4b78c --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/multithreading/DontCallThreadRunTest.java @@ -0,0 +1,17 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.multithreading; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class DontCallThreadRunTest extends PmdRuleTst { + // Used by DontCallThreadRun test cases + public static class TestThread extends Thread { + @Override + public void run() { + System.out.println("test"); + } + } +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/multithreading/DoubleCheckedLockingTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/multithreading/DoubleCheckedLockingTest.java new file mode 100644 index 00000000000..201f12e621e --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/multithreading/DoubleCheckedLockingTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.multithreading; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class DoubleCheckedLockingTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/multithreading/NonThreadSafeSingletonTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/multithreading/NonThreadSafeSingletonTest.java new file mode 100644 index 00000000000..928f3501799 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/multithreading/NonThreadSafeSingletonTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.multithreading; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class NonThreadSafeSingletonTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/multithreading/UnsynchronizedStaticDateFormatterTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/multithreading/UnsynchronizedStaticDateFormatterTest.java new file mode 100644 index 00000000000..16a92e05fd7 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/multithreading/UnsynchronizedStaticDateFormatterTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.multithreading; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class UnsynchronizedStaticDateFormatterTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/multithreading/UseConcurrentHashMapTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/multithreading/UseConcurrentHashMapTest.java new file mode 100644 index 00000000000..2ddf93e4e90 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/multithreading/UseConcurrentHashMapTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.multithreading; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class UseConcurrentHashMapTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/multithreading/UseNotifyAllInsteadOfNotifyTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/multithreading/UseNotifyAllInsteadOfNotifyTest.java new file mode 100644 index 00000000000..c03cbd61d06 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/multithreading/UseNotifyAllInsteadOfNotifyTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.multithreading; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class UseNotifyAllInsteadOfNotifyTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/naming/NamingRulesTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/naming/NamingRulesTest.java deleted file mode 100644 index 36b48e7ef22..00000000000 --- a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/naming/NamingRulesTest.java +++ /dev/null @@ -1,36 +0,0 @@ -/** - * BSD-style license; for more info see http://pmd.sourceforge.net/license.html - */ - -package net.sourceforge.pmd.lang.java.rule.naming; - -import net.sourceforge.pmd.testframework.SimpleAggregatorTst; - -public class NamingRulesTest extends SimpleAggregatorTst { - - private static final String RULESET = "java-naming"; - - @Override - public void setUp() { - addRule(RULESET, "AbstractNaming"); - addRule(RULESET, "AvoidDollarSigns"); - addRule(RULESET, "AvoidFieldNameMatchingMethodName"); - addRule(RULESET, "AvoidFieldNameMatchingTypeName"); - addRule(RULESET, "BooleanGetMethodName"); - addRule(RULESET, "ClassNamingConventions"); - addRule(RULESET, "LongVariable"); - addRule(RULESET, "MethodNamingConventions"); - addRule(RULESET, "MethodWithSameNameAsEnclosingClass"); - addRule(RULESET, "MisleadingVariableName"); - addRule(RULESET, "NoPackage"); - addRule(RULESET, "PackageCase"); - addRule(RULESET, "ShortMethodName"); - addRule(RULESET, "ShortClassName"); - addRule(RULESET, "ShortVariable"); - addRule(RULESET, "SuspiciousConstantFieldName"); - addRule(RULESET, "SuspiciousEqualsMethodName"); - addRule(RULESET, "SuspiciousHashcodeMethodName"); - addRule(RULESET, "VariableNamingConventions"); - addRule(RULESET, "GenericsNaming"); - } -} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/optimizations/OptimizationsRulesTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/optimizations/OptimizationsRulesTest.java deleted file mode 100644 index ee1e3579b1b..00000000000 --- a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/optimizations/OptimizationsRulesTest.java +++ /dev/null @@ -1,28 +0,0 @@ -/** - * BSD-style license; for more info see http://pmd.sourceforge.net/license.html - */ - -package net.sourceforge.pmd.lang.java.rule.optimizations; - -import net.sourceforge.pmd.testframework.SimpleAggregatorTst; - -public class OptimizationsRulesTest extends SimpleAggregatorTst { - - private static final String RULESET = "java-optimizations"; - - @Override - public void setUp() { - addRule(RULESET, "AddEmptyString"); - addRule(RULESET, "AvoidArrayLoops"); - addRule(RULESET, "AvoidInstantiatingObjectsInLoops"); - addRule(RULESET, "LocalVariableCouldBeFinal"); - addRule(RULESET, "MethodArgumentCouldBeFinal"); - addRule(RULESET, "RedundantFieldInitializer"); - addRule(RULESET, "SimplifyStartsWith"); - addRule(RULESET, "UnnecessaryWrapperObjectCreation"); - addRule(RULESET, "UseArrayListInsteadOfVector"); - addRule(RULESET, "UseArraysAsList"); - addRule(RULESET, "UseStringBufferForStringAppends"); - addRule(RULESET, "PrematureDeclaration"); - } -} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/performance/AddEmptyStringTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/performance/AddEmptyStringTest.java new file mode 100644 index 00000000000..c2302294358 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/performance/AddEmptyStringTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.performance; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class AddEmptyStringTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/performance/AppendCharacterWithCharTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/performance/AppendCharacterWithCharTest.java new file mode 100644 index 00000000000..d289d834308 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/performance/AppendCharacterWithCharTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.performance; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class AppendCharacterWithCharTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/performance/AvoidArrayLoopsTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/performance/AvoidArrayLoopsTest.java new file mode 100644 index 00000000000..6377d8f8914 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/performance/AvoidArrayLoopsTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.performance; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class AvoidArrayLoopsTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/performance/AvoidFileStreamTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/performance/AvoidFileStreamTest.java new file mode 100644 index 00000000000..592afe9c97e --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/performance/AvoidFileStreamTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.performance; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class AvoidFileStreamTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/performance/AvoidInstantiatingObjectsInLoopsTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/performance/AvoidInstantiatingObjectsInLoopsTest.java new file mode 100644 index 00000000000..33cea34ab7e --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/performance/AvoidInstantiatingObjectsInLoopsTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.performance; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class AvoidInstantiatingObjectsInLoopsTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/performance/AvoidUsingShortTypeTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/performance/AvoidUsingShortTypeTest.java new file mode 100644 index 00000000000..5a639a8a745 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/performance/AvoidUsingShortTypeTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.performance; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class AvoidUsingShortTypeTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/performance/BigIntegerInstantiationTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/performance/BigIntegerInstantiationTest.java new file mode 100644 index 00000000000..2903a458221 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/performance/BigIntegerInstantiationTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.performance; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class BigIntegerInstantiationTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/performance/BooleanInstantiationTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/performance/BooleanInstantiationTest.java new file mode 100644 index 00000000000..f55c886596c --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/performance/BooleanInstantiationTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.performance; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class BooleanInstantiationTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/performance/ByteInstantiationTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/performance/ByteInstantiationTest.java new file mode 100644 index 00000000000..2db21997797 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/performance/ByteInstantiationTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.performance; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class ByteInstantiationTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/performance/ConsecutiveAppendsShouldReuseTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/performance/ConsecutiveAppendsShouldReuseTest.java new file mode 100644 index 00000000000..e7b68474ad8 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/performance/ConsecutiveAppendsShouldReuseTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.performance; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class ConsecutiveAppendsShouldReuseTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/performance/ConsecutiveLiteralAppendsTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/performance/ConsecutiveLiteralAppendsTest.java new file mode 100644 index 00000000000..53e2204c980 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/performance/ConsecutiveLiteralAppendsTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.performance; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class ConsecutiveLiteralAppendsTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/performance/InefficientEmptyStringCheckTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/performance/InefficientEmptyStringCheckTest.java new file mode 100644 index 00000000000..3adeb48d5b5 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/performance/InefficientEmptyStringCheckTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.performance; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class InefficientEmptyStringCheckTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/performance/InefficientStringBufferingTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/performance/InefficientStringBufferingTest.java new file mode 100644 index 00000000000..a7c07cecfac --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/performance/InefficientStringBufferingTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.performance; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class InefficientStringBufferingTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/performance/InsufficientStringBufferDeclarationTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/performance/InsufficientStringBufferDeclarationTest.java new file mode 100644 index 00000000000..d138b38196e --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/performance/InsufficientStringBufferDeclarationTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.performance; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class InsufficientStringBufferDeclarationTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/performance/IntegerInstantiationTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/performance/IntegerInstantiationTest.java new file mode 100644 index 00000000000..a951c8eda74 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/performance/IntegerInstantiationTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.performance; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class IntegerInstantiationTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/performance/LongInstantiationTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/performance/LongInstantiationTest.java new file mode 100644 index 00000000000..a2b951cb008 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/performance/LongInstantiationTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.performance; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class LongInstantiationTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/performance/OptimizableToArrayCallTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/performance/OptimizableToArrayCallTest.java new file mode 100644 index 00000000000..2fc02260a8f --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/performance/OptimizableToArrayCallTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.performance; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class OptimizableToArrayCallTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/performance/RedundantFieldInitializerTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/performance/RedundantFieldInitializerTest.java new file mode 100644 index 00000000000..0a3548a3f6a --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/performance/RedundantFieldInitializerTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.performance; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class RedundantFieldInitializerTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/performance/ShortInstantiationTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/performance/ShortInstantiationTest.java new file mode 100644 index 00000000000..564c94b6fd3 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/performance/ShortInstantiationTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.performance; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class ShortInstantiationTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/performance/SimplifyStartsWithTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/performance/SimplifyStartsWithTest.java new file mode 100644 index 00000000000..e2a3fbcd159 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/performance/SimplifyStartsWithTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.performance; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class SimplifyStartsWithTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/performance/StringInstantiationTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/performance/StringInstantiationTest.java new file mode 100644 index 00000000000..fc6fdbdd97a --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/performance/StringInstantiationTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.performance; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class StringInstantiationTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/performance/StringToStringTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/performance/StringToStringTest.java new file mode 100644 index 00000000000..90482891f0f --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/performance/StringToStringTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.performance; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class StringToStringTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/performance/TooFewBranchesForASwitchStatementTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/performance/TooFewBranchesForASwitchStatementTest.java new file mode 100644 index 00000000000..730ab7469ef --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/performance/TooFewBranchesForASwitchStatementTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.performance; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class TooFewBranchesForASwitchStatementTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/performance/UnnecessaryWrapperObjectCreationTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/performance/UnnecessaryWrapperObjectCreationTest.java new file mode 100644 index 00000000000..08da3812452 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/performance/UnnecessaryWrapperObjectCreationTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.performance; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class UnnecessaryWrapperObjectCreationTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/performance/UseArrayListInsteadOfVectorTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/performance/UseArrayListInsteadOfVectorTest.java new file mode 100644 index 00000000000..296890c7090 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/performance/UseArrayListInsteadOfVectorTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.performance; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class UseArrayListInsteadOfVectorTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/performance/UseArraysAsListTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/performance/UseArraysAsListTest.java new file mode 100644 index 00000000000..bc196304e47 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/performance/UseArraysAsListTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.performance; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class UseArraysAsListTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/performance/UseIndexOfCharTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/performance/UseIndexOfCharTest.java new file mode 100644 index 00000000000..401a76f34b2 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/performance/UseIndexOfCharTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.performance; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class UseIndexOfCharTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/performance/UseStringBufferForStringAppendsTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/performance/UseStringBufferForStringAppendsTest.java new file mode 100644 index 00000000000..a77785ec25c --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/performance/UseStringBufferForStringAppendsTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.performance; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class UseStringBufferForStringAppendsTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/performance/UseStringBufferLengthTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/performance/UseStringBufferLengthTest.java new file mode 100644 index 00000000000..1d7ebb58d60 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/performance/UseStringBufferLengthTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.performance; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class UseStringBufferLengthTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/performance/UselessStringValueOfTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/performance/UselessStringValueOfTest.java new file mode 100644 index 00000000000..ef7221eafa1 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/performance/UselessStringValueOfTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.performance; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class UselessStringValueOfTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/security/HardCodedCryptoKeyTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/security/HardCodedCryptoKeyTest.java new file mode 100644 index 00000000000..cabd4852ee4 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/security/HardCodedCryptoKeyTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.security; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class HardCodedCryptoKeyTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/security/InsecureCryptoIvTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/security/InsecureCryptoIvTest.java new file mode 100644 index 00000000000..2849df93b4d --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/security/InsecureCryptoIvTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.java.rule.security; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class InsecureCryptoIvTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/strictexception/StrictExceptionRulesTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/strictexception/StrictExceptionRulesTest.java deleted file mode 100644 index 608f6e89708..00000000000 --- a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/strictexception/StrictExceptionRulesTest.java +++ /dev/null @@ -1,28 +0,0 @@ -/** - * BSD-style license; for more info see http://pmd.sourceforge.net/license.html - */ - -package net.sourceforge.pmd.lang.java.rule.strictexception; - -import net.sourceforge.pmd.testframework.SimpleAggregatorTst; - -public class StrictExceptionRulesTest extends SimpleAggregatorTst { - - private static final String RULESET = "java-strictexception"; - - @Override - public void setUp() { - addRule(RULESET, "AvoidCatchingGenericException"); - addRule(RULESET, "AvoidCatchingNPE"); - addRule(RULESET, "AvoidCatchingThrowable"); - addRule(RULESET, "AvoidLosingExceptionInformation"); - addRule(RULESET, "AvoidRethrowingException"); - addRule(RULESET, "AvoidThrowingNewInstanceOfSameException"); - addRule(RULESET, "AvoidThrowingNullPointerException"); - addRule(RULESET, "AvoidThrowingRawExceptionTypes"); - addRule(RULESET, "DoNotExtendJavaLangError"); - addRule(RULESET, "ExceptionAsFlowControl"); - addRule(RULESET, "SignatureDeclareThrowsException"); - addRule(RULESET, "DoNotThrowExceptionInFinally"); - } -} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/strings/AvoidDuplicateLiteralsRuleTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/strings/AvoidDuplicateLiteralsRuleTest.java deleted file mode 100644 index 6fd8b8fbee2..00000000000 --- a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/strings/AvoidDuplicateLiteralsRuleTest.java +++ /dev/null @@ -1,51 +0,0 @@ -/** - * BSD-style license; for more info see http://pmd.sourceforge.net/license.html - */ - -package net.sourceforge.pmd.lang.java.rule.strings; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; - -import java.util.Set; - -import org.junit.Test; - -public class AvoidDuplicateLiteralsRuleTest { - @Test - public void testStringParserEmptyString() { - AvoidDuplicateLiteralsRule.ExceptionParser p = new AvoidDuplicateLiteralsRule.ExceptionParser(','); - Set res = p.parse(""); - assertTrue(res.isEmpty()); - } - - @Test - public void testStringParserSimple() { - AvoidDuplicateLiteralsRule.ExceptionParser p = new AvoidDuplicateLiteralsRule.ExceptionParser(','); - Set res = p.parse("a,b,c"); - assertEquals(3, res.size()); - assertTrue(res.contains("a")); - assertTrue(res.contains("b")); - assertTrue(res.contains("c")); - } - - @Test - public void testStringParserEscapedChar() { - AvoidDuplicateLiteralsRule.ExceptionParser p = new AvoidDuplicateLiteralsRule.ExceptionParser(','); - Set res = p.parse("a,b,\\,"); - assertEquals(3, res.size()); - assertTrue(res.contains("a")); - assertTrue(res.contains("b")); - assertTrue(res.contains(",")); - } - - @Test - public void testStringParserEscapedEscapedChar() { - AvoidDuplicateLiteralsRule.ExceptionParser p = new AvoidDuplicateLiteralsRule.ExceptionParser(','); - Set res = p.parse("a,b,\\\\"); - assertEquals(3, res.size()); - assertTrue(res.contains("a")); - assertTrue(res.contains("b")); - assertTrue(res.contains("\\")); - } -} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/strings/StringsRulesTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/strings/StringsRulesTest.java deleted file mode 100644 index 1f53ce3dffe..00000000000 --- a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/strings/StringsRulesTest.java +++ /dev/null @@ -1,32 +0,0 @@ -/** - * BSD-style license; for more info see http://pmd.sourceforge.net/license.html - */ - -package net.sourceforge.pmd.lang.java.rule.strings; - -import net.sourceforge.pmd.testframework.SimpleAggregatorTst; - -public class StringsRulesTest extends SimpleAggregatorTst { - - private static final String RULESET = "java-strings"; - - @Override - public void setUp() { - addRule(RULESET, "AppendCharacterWithChar"); - addRule(RULESET, "AvoidDuplicateLiterals"); - addRule(RULESET, "AvoidStringBufferField"); - addRule(RULESET, "ConsecutiveAppendsShouldReuse"); - addRule(RULESET, "ConsecutiveLiteralAppends"); - addRule(RULESET, "InefficientEmptyStringCheck"); - addRule(RULESET, "InefficientStringBuffering"); - addRule(RULESET, "InsufficientStringBufferDeclaration"); - addRule(RULESET, "StringBufferInstantiationWithChar"); - addRule(RULESET, "StringInstantiation"); - addRule(RULESET, "StringToString"); - addRule(RULESET, "UnnecessaryCaseChange"); - addRule(RULESET, "UseEqualsToCompareStrings"); - addRule(RULESET, "UseIndexOfChar"); - addRule(RULESET, "UselessStringValueOf"); - addRule(RULESET, "UseStringBufferLength"); - } -} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/sunsecure/SunSecureRulesTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/sunsecure/SunSecureRulesTest.java deleted file mode 100644 index ca913e04c5a..00000000000 --- a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/sunsecure/SunSecureRulesTest.java +++ /dev/null @@ -1,18 +0,0 @@ -/** - * BSD-style license; for more info see http://pmd.sourceforge.net/license.html - */ - -package net.sourceforge.pmd.lang.java.rule.sunsecure; - -import net.sourceforge.pmd.testframework.SimpleAggregatorTst; - -public class SunSecureRulesTest extends SimpleAggregatorTst { - - private static final String RULESET = "java-sunsecure"; - - @Override - public void setUp() { - addRule(RULESET, "MethodReturnsInternalArray"); - addRule(RULESET, "ArrayIsStoredDirectly"); - } -} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/typeresolution/CloneMethodMustImplementCloneableTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/typeresolution/CloneMethodMustImplementCloneableTest.java deleted file mode 100644 index 46f4e0ae5c5..00000000000 --- a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/typeresolution/CloneMethodMustImplementCloneableTest.java +++ /dev/null @@ -1,16 +0,0 @@ -/** - * BSD-style license; for more info see http://pmd.sourceforge.net/license.html - */ - -package net.sourceforge.pmd.lang.java.rule.typeresolution; - -import net.sourceforge.pmd.testframework.SimpleAggregatorTst; - -public class CloneMethodMustImplementCloneableTest extends SimpleAggregatorTst { - private static final String RULESET = "java-typeresolution"; - - @Override - public void setUp() { - addRule(RULESET, "CloneMethodMustImplementCloneable"); - } -} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/typeresolution/LooseCouplingTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/typeresolution/LooseCouplingTest.java deleted file mode 100644 index a3865d3ad07..00000000000 --- a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/typeresolution/LooseCouplingTest.java +++ /dev/null @@ -1,17 +0,0 @@ -/** - * BSD-style license; for more info see http://pmd.sourceforge.net/license.html - */ - -package net.sourceforge.pmd.lang.java.rule.typeresolution; - -import net.sourceforge.pmd.testframework.SimpleAggregatorTst; - -public class LooseCouplingTest extends SimpleAggregatorTst { - - private static final String RULESET = "java-typeresolution"; - - @Override - public void setUp() { - addRule(RULESET, "LooseCoupling"); - } -} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/typeresolution/SignatureDeclareThrowsExceptionTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/typeresolution/SignatureDeclareThrowsExceptionTest.java deleted file mode 100644 index f38e0598d77..00000000000 --- a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/typeresolution/SignatureDeclareThrowsExceptionTest.java +++ /dev/null @@ -1,17 +0,0 @@ -/** - * BSD-style license; for more info see http://pmd.sourceforge.net/license.html - */ - -package net.sourceforge.pmd.lang.java.rule.typeresolution; - -import net.sourceforge.pmd.testframework.SimpleAggregatorTst; - -public class SignatureDeclareThrowsExceptionTest extends SimpleAggregatorTst { - - private static final String RULESET = "java-typeresolution"; - - @Override - public void setUp() { - addRule(RULESET, "SignatureDeclareThrowsException"); - } -} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/typeresolution/UnusedImportsTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/typeresolution/UnusedImportsTest.java deleted file mode 100644 index 6b0e1ce82fd..00000000000 --- a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/typeresolution/UnusedImportsTest.java +++ /dev/null @@ -1,17 +0,0 @@ -/** - * BSD-style license; for more info see http://pmd.sourceforge.net/license.html - */ - -package net.sourceforge.pmd.lang.java.rule.typeresolution; - -import net.sourceforge.pmd.testframework.SimpleAggregatorTst; - -public class UnusedImportsTest extends SimpleAggregatorTst { - - private static final String RULESET = "java-typeresolution"; - - @Override - public void setUp() { - addRule(RULESET, "UnusedImports"); - } -} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/typeresolution/xml/MyInterface.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/typeresolution/xml/MyInterface.java deleted file mode 100644 index 179be18d623..00000000000 --- a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/typeresolution/xml/MyInterface.java +++ /dev/null @@ -1,13 +0,0 @@ -/** - * BSD-style license; for more info see http://pmd.sourceforge.net/license.html - */ - -package net.sourceforge.pmd.lang.java.rule.typeresolution.xml; - -/** - * Warning, this class ARE not useless. It is used by the some of regression - * tests. - */ -public interface MyInterface extends Cloneable { - -} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/typeresolution/xml/MyTestCase.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/typeresolution/xml/MyTestCase.java deleted file mode 100644 index 24847e51726..00000000000 --- a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/typeresolution/xml/MyTestCase.java +++ /dev/null @@ -1,21 +0,0 @@ -/** - * BSD-style license; for more info see http://pmd.sourceforge.net/license.html - */ - -package net.sourceforge.pmd.lang.java.rule.typeresolution.xml; - -import org.junit.Ignore; - -import junit.framework.TestCase; - -/** - * Warning, this class IS NOT useless. It is used by the some regression tests. - * - * See file: SignatureDeclareThrowsException.xml - * - * The file is already excluded from maven/surefire. - */ -@Ignore -public class MyTestCase extends TestCase { - -} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/unnecessary/UnnecessaryRulesTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/unnecessary/UnnecessaryRulesTest.java deleted file mode 100644 index b80a68c9ae1..00000000000 --- a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/unnecessary/UnnecessaryRulesTest.java +++ /dev/null @@ -1,28 +0,0 @@ -/** - * BSD-style license; for more info see http://pmd.sourceforge.net/license.html - */ - -package net.sourceforge.pmd.lang.java.rule.unnecessary; - -import net.sourceforge.pmd.testframework.SimpleAggregatorTst; - -/** - * Rule tests for the unnecessary ruleset - */ -public class UnnecessaryRulesTest extends SimpleAggregatorTst { - - private static final String RULESET = "java-unnecessary"; - - @Override - public void setUp() { - addRule(RULESET, "UnnecessaryConversionTemporary"); - addRule(RULESET, "UnnecessaryReturn"); - addRule(RULESET, "UnnecessaryFinalModifier"); - addRule(RULESET, "UnnecessaryModifier"); - addRule(RULESET, "UnusedNullCheckInEquals"); - addRule(RULESET, "UselessOverridingMethod"); - addRule(RULESET, "UselessOperationOnImmutable"); - addRule(RULESET, "UselessParentheses"); - addRule(RULESET, "UselessQualifiedThis"); - } -} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/unusedcode/UnusedCodeRulesTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/unusedcode/UnusedCodeRulesTest.java deleted file mode 100644 index fde86e6c8f3..00000000000 --- a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/unusedcode/UnusedCodeRulesTest.java +++ /dev/null @@ -1,26 +0,0 @@ -/** - * BSD-style license; for more info see http://pmd.sourceforge.net/license.html - */ - -package net.sourceforge.pmd.lang.java.rule.unusedcode; - -import net.sourceforge.pmd.testframework.SimpleAggregatorTst; - -/** - * Rule tests for the unused code ruleset. - */ -public class UnusedCodeRulesTest extends SimpleAggregatorTst { - - private static final String RULESET = "java-unusedcode"; - - /** - * Configure the rules. - */ - @Override - public void setUp() { - addRule(RULESET, "UnusedFormalParameter"); - addRule(RULESET, "UnusedLocalVariable"); - addRule(RULESET, "UnusedPrivateField"); - addRule(RULESET, "UnusedPrivateMethod"); - } -} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/symboltable/AcceptanceTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/symboltable/AcceptanceTest.java index d18a89d03e1..0e0db65c925 100644 --- a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/symboltable/AcceptanceTest.java +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/symboltable/AcceptanceTest.java @@ -9,7 +9,6 @@ import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; -import java.util.Iterator; import java.util.List; import java.util.Map; @@ -19,6 +18,7 @@ import net.sourceforge.pmd.lang.ast.Node; import net.sourceforge.pmd.lang.java.ast.ASTBlock; import net.sourceforge.pmd.lang.java.ast.ASTCatchStatement; +import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceDeclaration; import net.sourceforge.pmd.lang.java.ast.ASTEqualityExpression; import net.sourceforge.pmd.lang.java.ast.ASTInitializer; import net.sourceforge.pmd.lang.java.ast.ASTMethodDeclaration; @@ -49,7 +49,7 @@ public void testCatchBlocks() { ASTCatchStatement c = acu.findDescendantsOfType(ASTCatchStatement.class).get(0); ASTBlock a = c.findDescendantsOfType(ASTBlock.class).get(0); Scope s = a.getScope(); - Map> vars = s.getParent().getDeclarations(); + Map> vars = s.getDeclarations(); assertEquals(1, vars.size()); NameDeclaration v = vars.keySet().iterator().next(); assertEquals("e", v.getImage()); @@ -100,11 +100,10 @@ public void testDemo() { ASTMethodDeclaration node = acu.findDescendantsOfType(ASTMethodDeclaration.class).get(0); Scope s = node.getScope(); Map> m = s.getDeclarations(); - for (Iterator i = m.keySet().iterator(); i.hasNext();) { - NameDeclaration d = i.next(); - assertEquals("buz", d.getImage()); - assertEquals("ArrayList", ((TypedNameDeclaration) d).getTypeImage()); - List u = m.get(d); + for (Map.Entry> entry : m.entrySet()) { + assertEquals("buz", entry.getKey().getImage()); + assertEquals("ArrayList", ((TypedNameDeclaration) entry.getKey()).getTypeImage()); + List u = entry.getValue(); assertEquals(1, u.size()); NameOccurrence o = u.get(0); int beginLine = o.getLocation().getBeginLine(); @@ -131,7 +130,8 @@ public void testEnum() { @Test public void testInnerOuterClass() { parseCode(TEST_INNER_CLASS); - ASTVariableDeclaratorId vdi = acu.findDescendantsOfType(ASTVariableDeclaratorId.class).get(0); + ASTVariableDeclaratorId vdi = acu.findDescendantsOfType(ASTClassOrInterfaceDeclaration.class).get(1) // get inner class + .getFirstDescendantOfType(ASTVariableDeclaratorId.class); // get first declaration List usages = vdi.getUsages(); assertEquals(2, usages.size()); assertEquals(5, usages.get(0).getLocation().getBeginLine()); diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/symboltable/ClassScopeTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/symboltable/ClassScopeTest.java index c543c41d618..d2438c6bd05 100644 --- a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/symboltable/ClassScopeTest.java +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/symboltable/ClassScopeTest.java @@ -280,20 +280,25 @@ public void testMethodUsageSeen2() { public void testNestedClassFieldAndParameter() { parseCode(NESTED_CLASS_FIELD_AND_PARAM); ASTMethodDeclaration node = acu.getFirstDescendantOfType(ASTMethodDeclaration.class); - Map> vd = node.getScope().getDeclarations(); - assertEquals(1, vd.size()); - - for (Map.Entry> entry : vd.entrySet()) { - assertEquals("field", entry.getKey().getImage()); - - List occurrences = entry.getValue(); - assertEquals(2, occurrences.size()); - NameOccurrence no1 = occurrences.get(0); - assertEquals(8, no1.getLocation().getBeginLine()); - NameOccurrence no2 = occurrences.get(1); - assertEquals(9, no2.getLocation().getBeginLine()); + Map> vd = node.getScope().getDeclarations(VariableNameDeclaration.class); + assertEquals(2, vd.size()); + + int paramCount = 0; + for (Map.Entry> entry : vd.entrySet()) { + if (entry.getKey().getDeclaratorId().isFormalParameter()) { + assertEquals("field", entry.getKey().getImage()); + + List occurrences = entry.getValue(); + assertEquals(2, occurrences.size()); + NameOccurrence no1 = occurrences.get(0); + assertEquals(8, no1.getLocation().getBeginLine()); + NameOccurrence no2 = occurrences.get(1); + assertEquals(9, no2.getLocation().getBeginLine()); + paramCount++; + } } + assertEquals(1, paramCount); } @Test diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/symboltable/LocalScopeTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/symboltable/LocalScopeTest.java index 176e172343d..cb07b81a8f2 100644 --- a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/symboltable/LocalScopeTest.java +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/symboltable/LocalScopeTest.java @@ -119,12 +119,8 @@ public void testgetEnclosingMethodScope() { public static final String TEST3 = "public class Foo {" + PMD.EOL + " void foo() {" + PMD.EOL + " int x = 2;" + PMD.EOL + " x++;" + PMD.EOL + " }" + PMD.EOL + "}"; - public static final String TEST4 = "public class Foo {" + PMD.EOL + " void foo(String x, String z) { int y; }" + public static final String TEST4 = "public class Foo {" + PMD.EOL + " void foo(String x, String z) { { int x; } }" + PMD.EOL + "}"; public static final String TEST5 = "public class Foo {" + PMD.EOL + " void foo(String x);" + PMD.EOL + "}"; - - public static junit.framework.Test suite() { - return new junit.framework.JUnit4TestAdapter(LocalScopeTest.class); - } } diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/symboltable/STBBaseTst.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/symboltable/STBBaseTst.java index 2e4e3a8ee80..e020c72f28c 100644 --- a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/symboltable/STBBaseTst.java +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/symboltable/STBBaseTst.java @@ -7,6 +7,7 @@ import java.io.IOException; import java.io.InputStream; import java.io.StringReader; +import java.nio.charset.StandardCharsets; import org.apache.commons.io.IOUtils; @@ -49,7 +50,7 @@ protected void parseForClass(final Class clazz) { } final String source; try { - source = IOUtils.toString(is); + source = IOUtils.toString(is, StandardCharsets.UTF_8); } catch (final IOException e) { throw new RuntimeException(e); } diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/symboltable/ScopeAndDeclarationFinderTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/symboltable/ScopeAndDeclarationFinderTest.java index 09f57978fca..ed0f727f4cf 100644 --- a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/symboltable/ScopeAndDeclarationFinderTest.java +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/symboltable/ScopeAndDeclarationFinderTest.java @@ -13,6 +13,7 @@ import net.sourceforge.pmd.PMD; import net.sourceforge.pmd.lang.LanguageRegistry; import net.sourceforge.pmd.lang.java.JavaLanguageModule; +import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceBody; import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceDeclaration; import net.sourceforge.pmd.lang.java.ast.ASTLambdaExpression; import net.sourceforge.pmd.lang.java.ast.ASTMethodDeclarator; @@ -65,7 +66,9 @@ public void testAnnonInnerClassScoping() { ClassScope cs = (ClassScope) acu.getFirstDescendantOfType(ASTClassOrInterfaceDeclaration.class).getScope(); Assert.assertEquals(1, cs.getClassDeclarations().size()); // There should be 1 anonymous class - List methods = acu.findDescendantsOfType(ASTMethodDeclarator.class); + List methods = acu.getFirstDescendantOfType(ASTClassOrInterfaceBody.class) // outer class + .getFirstDescendantOfType(ASTClassOrInterfaceBody.class) // inner class + .findDescendantsOfType(ASTMethodDeclarator.class, true); // inner class methods Assert.assertEquals(2, methods.size()); ClassScope scope1 = methods.get(0).getScope().getEnclosingScope(ClassScope.class); ClassScope scope2 = methods.get(1).getScope().getEnclosingScope(ClassScope.class); diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/symboltable/ScopeCreationVisitorTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/symboltable/ScopeCreationVisitorTest.java index ea41e754b70..6b67c02e305 100644 --- a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/symboltable/ScopeCreationVisitorTest.java +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/symboltable/ScopeCreationVisitorTest.java @@ -9,6 +9,7 @@ import org.junit.Test; import net.sourceforge.pmd.PMD; +import net.sourceforge.pmd.lang.java.ast.ASTBlock; import net.sourceforge.pmd.lang.java.ast.ASTIfStatement; public class ScopeCreationVisitorTest extends STBBaseTst { @@ -16,7 +17,8 @@ public class ScopeCreationVisitorTest extends STBBaseTst { @Test public void testScopesAreCreated() { parseCode(TEST1); - ASTIfStatement n = acu.findDescendantsOfType(ASTIfStatement.class).get(0); + ASTBlock n = acu.getFirstDescendantOfType(ASTIfStatement.class) + .getFirstDescendantOfType(ASTBlock.class); assertTrue(n.getScope() instanceof LocalScope); } diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/symboltable/SimpleTypedNameDeclarationTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/symboltable/SimpleTypedNameDeclarationTest.java index 7411c34efb2..05a2dcd9b56 100644 --- a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/symboltable/SimpleTypedNameDeclarationTest.java +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/symboltable/SimpleTypedNameDeclarationTest.java @@ -7,7 +7,6 @@ import java.util.ArrayList; import java.util.List; import java.util.Map; - import javax.swing.JComponent; import javax.swing.JTextField; diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/symboltable/VariableNameDeclarationTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/symboltable/VariableNameDeclarationTest.java index f5edc8acbf9..777c8a57532 100644 --- a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/symboltable/VariableNameDeclarationTest.java +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/symboltable/VariableNameDeclarationTest.java @@ -4,6 +4,7 @@ package net.sourceforge.pmd.lang.java.symboltable; +import static net.sourceforge.pmd.lang.java.ParserTstUtil.getNodes; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; @@ -12,8 +13,7 @@ import org.junit.Test; import net.sourceforge.pmd.PMD; -import net.sourceforge.pmd.lang.java.ast.ASTFormalParameter; -import net.sourceforge.pmd.lang.java.ast.ASTTryStatement; +import net.sourceforge.pmd.lang.java.ast.ASTCompilationUnit; import net.sourceforge.pmd.lang.java.ast.ASTVariableDeclaratorId; import net.sourceforge.pmd.lang.symboltable.NameDeclaration; import net.sourceforge.pmd.lang.symboltable.Scope; @@ -32,14 +32,9 @@ public void testConstructor() { @Test public void testExceptionBlkParam() { - ASTVariableDeclaratorId id = new ASTVariableDeclaratorId(3); - id.testingOnlySetBeginLine(10); - id.setImage("foo"); - ASTFormalParameter param = new ASTFormalParameter(2); - id.jjtSetParent(param); - param.jjtSetParent(new ASTTryStatement(1)); - VariableNameDeclaration decl = new VariableNameDeclaration(id); - assertTrue(decl.isExceptionBlockParameter()); + ASTCompilationUnit acu = getNodes(ASTCompilationUnit.class, EXCEPTION_PARAMETER).iterator().next(); + ASTVariableDeclaratorId id = acu.getFirstDescendantOfType(ASTVariableDeclaratorId.class); + assertTrue(new VariableNameDeclaration(id).isExceptionBlockParameter()); } @Test @@ -90,6 +85,9 @@ public void testParamTypeImage() { assertEquals("String", ((TypedNameDeclaration) decl).getTypeImage()); } + + private static final String EXCEPTION_PARAMETER = "public class Test { { try {} catch(Exception ie) {} } }"; + public static final String TEST1 = "public class Foo {" + PMD.EOL + " void foo() {" + PMD.EOL + " int bar = 42;" + PMD.EOL + " }" + PMD.EOL + "}"; diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/typeresolution/ClassTypeResolverTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/typeresolution/ClassTypeResolverTest.java index 3883a743417..52c6cb82e38 100644 --- a/pmd-java/src/test/java/net/sourceforge/pmd/typeresolution/ClassTypeResolverTest.java +++ b/pmd-java/src/test/java/net/sourceforge/pmd/typeresolution/ClassTypeResolverTest.java @@ -11,7 +11,9 @@ import static net.sourceforge.pmd.lang.java.typeresolution.typeinference.InferenceRuleType.LOOSE_INVOCATION; import static net.sourceforge.pmd.lang.java.typeresolution.typeinference.InferenceRuleType.SUBTYPE; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertSame; @@ -20,6 +22,7 @@ import java.util.ArrayList; import java.util.Collection; import java.util.Comparator; +import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.StringTokenizer; @@ -36,18 +39,22 @@ import net.sourceforge.pmd.lang.java.ast.ASTAllocationExpression; import net.sourceforge.pmd.lang.java.ast.ASTArgumentList; import net.sourceforge.pmd.lang.java.ast.ASTBooleanLiteral; +import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceBodyDeclaration; import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceDeclaration; import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceType; import net.sourceforge.pmd.lang.java.ast.ASTCompilationUnit; +import net.sourceforge.pmd.lang.java.ast.ASTEnumConstant; import net.sourceforge.pmd.lang.java.ast.ASTExpression; import net.sourceforge.pmd.lang.java.ast.ASTFieldDeclaration; import net.sourceforge.pmd.lang.java.ast.ASTFormalParameter; import net.sourceforge.pmd.lang.java.ast.ASTImportDeclaration; import net.sourceforge.pmd.lang.java.ast.ASTLiteral; +import net.sourceforge.pmd.lang.java.ast.ASTLocalVariableDeclaration; import net.sourceforge.pmd.lang.java.ast.ASTName; import net.sourceforge.pmd.lang.java.ast.ASTNullLiteral; import net.sourceforge.pmd.lang.java.ast.ASTPrimaryExpression; import net.sourceforge.pmd.lang.java.ast.ASTPrimaryPrefix; +import net.sourceforge.pmd.lang.java.ast.ASTPrimitiveType; import net.sourceforge.pmd.lang.java.ast.ASTReferenceType; import net.sourceforge.pmd.lang.java.ast.ASTStatementExpression; import net.sourceforge.pmd.lang.java.ast.ASTType; @@ -65,10 +72,13 @@ import net.sourceforge.pmd.lang.java.typeresolution.typeinference.Bound; import net.sourceforge.pmd.lang.java.typeresolution.typeinference.Constraint; import net.sourceforge.pmd.lang.java.typeresolution.typeinference.Variable; +import net.sourceforge.pmd.typeresolution.testdata.AbstractReturnTypeUseCase; import net.sourceforge.pmd.typeresolution.testdata.AnonymousClassFromInterface; import net.sourceforge.pmd.typeresolution.testdata.AnonymousInnerClass; import net.sourceforge.pmd.typeresolution.testdata.AnoymousExtendingObject; import net.sourceforge.pmd.typeresolution.testdata.ArrayListFound; +import net.sourceforge.pmd.typeresolution.testdata.ArrayTypes; +import net.sourceforge.pmd.typeresolution.testdata.ArrayVariableDeclaration; import net.sourceforge.pmd.typeresolution.testdata.DefaultJavaLangImport; import net.sourceforge.pmd.typeresolution.testdata.EnumWithAnonymousInnerClass; import net.sourceforge.pmd.typeresolution.testdata.ExtraTopLevelClass; @@ -84,21 +94,32 @@ import net.sourceforge.pmd.typeresolution.testdata.FieldAccessStatic; import net.sourceforge.pmd.typeresolution.testdata.FieldAccessSuper; import net.sourceforge.pmd.typeresolution.testdata.GenericMethodsImplicit; +import net.sourceforge.pmd.typeresolution.testdata.GenericsArrays; import net.sourceforge.pmd.typeresolution.testdata.InnerClass; +import net.sourceforge.pmd.typeresolution.testdata.JavaTypeDefinitionToStringNPE; import net.sourceforge.pmd.typeresolution.testdata.Literals; +import net.sourceforge.pmd.typeresolution.testdata.LocalGenericClass; import net.sourceforge.pmd.typeresolution.testdata.MethodAccessibility; import net.sourceforge.pmd.typeresolution.testdata.MethodFirstPhase; import net.sourceforge.pmd.typeresolution.testdata.MethodGenericExplicit; +import net.sourceforge.pmd.typeresolution.testdata.MethodGenericParam; import net.sourceforge.pmd.typeresolution.testdata.MethodMostSpecific; import net.sourceforge.pmd.typeresolution.testdata.MethodPotentialApplicability; import net.sourceforge.pmd.typeresolution.testdata.MethodSecondPhase; import net.sourceforge.pmd.typeresolution.testdata.MethodStaticAccess; import net.sourceforge.pmd.typeresolution.testdata.MethodThirdPhase; +import net.sourceforge.pmd.typeresolution.testdata.NestedAllocationExpressions; import net.sourceforge.pmd.typeresolution.testdata.NestedAnonymousClass; import net.sourceforge.pmd.typeresolution.testdata.Operators; +import net.sourceforge.pmd.typeresolution.testdata.OverloadedMethodsUsage; +import net.sourceforge.pmd.typeresolution.testdata.PmdStackOverflow; import net.sourceforge.pmd.typeresolution.testdata.Promotion; +import net.sourceforge.pmd.typeresolution.testdata.SubTypeUsage; import net.sourceforge.pmd.typeresolution.testdata.SuperExpression; import net.sourceforge.pmd.typeresolution.testdata.ThisExpression; +import net.sourceforge.pmd.typeresolution.testdata.VarArgsMethodUseCase; +import net.sourceforge.pmd.typeresolution.testdata.VarargsAsFixedArity; +import net.sourceforge.pmd.typeresolution.testdata.VarargsZeroArity; import net.sourceforge.pmd.typeresolution.testdata.dummytypes.Converter; import net.sourceforge.pmd.typeresolution.testdata.dummytypes.GenericClass; import net.sourceforge.pmd.typeresolution.testdata.dummytypes.JavaTypeDefinitionEquals; @@ -110,9 +131,15 @@ import net.sourceforge.pmd.typeresolution.testdata.dummytypes.SuperClassB; import net.sourceforge.pmd.typeresolution.testdata.dummytypes.SuperClassB2; - +// TODO split that class public class ClassTypeResolverTest { + @Test + public void stackOverflowTest() { + // See #831 https://github.com/pmd/pmd/issues/831 - [java] StackOverflow in JavaTypeDefinitionSimple.toString + parseAndTypeResolveForClass15(PmdStackOverflow.class); + } + @Test public void testClassNameExists() { ClassTypeResolver classTypeResolver = new ClassTypeResolver(); @@ -148,9 +175,23 @@ public void acceptanceTest() { @Test public void testEnumAnonymousInnerClass() { ASTCompilationUnit acu = parseAndTypeResolveForClass15(EnumWithAnonymousInnerClass.class); + // try it in jshell, an enum constant with a body is compiled to an anonymous class, + // the counter is shared with other anonymous classes of the enum + Class enumAnon = acu.getFirstDescendantOfType(ASTEnumConstant.class).getQualifiedName().getType(); + assertEquals("net.sourceforge.pmd.typeresolution.testdata.EnumWithAnonymousInnerClass$1", enumAnon.getName()); + Class inner = acu.getFirstDescendantOfType(ASTAllocationExpression.class) .getFirstDescendantOfType(ASTClassOrInterfaceType.class).getType(); - assertEquals("net.sourceforge.pmd.typeresolution.testdata.EnumWithAnonymousInnerClass$1", inner.getName()); + assertEquals("net.sourceforge.pmd.typeresolution.testdata.EnumWithAnonymousInnerClass$2", inner.getName()); + } + + /** + * See bug #899 toString causes NPE + */ + @Test + public void testNPEInJavaTypeDefinitionToString() { + // Just parsing this file throws a NPE + parseAndTypeResolveForClass(JavaTypeDefinitionToStringNPE.class, "1.8"); } @Test @@ -185,7 +226,7 @@ public void testInnerClass() throws ClassNotFoundException { outerClassDeclaration.getFirstDescendantOfType(ASTClassOrInterfaceDeclaration.class).getType()); // Method parameter as inner class ASTFormalParameter formalParameter = typeDeclaration.getFirstDescendantOfType(ASTFormalParameter.class); - assertEquals(theInnerClass, formalParameter.getTypeNode().getType()); + assertEquals(theInnerClass, formalParameter.getType()); } /** @@ -219,14 +260,15 @@ public void testNestedAnonymousClass() throws Exception { Node acu = parseAndTypeResolveForClass(NestedAnonymousClass.class, "1.8"); ASTAllocationExpression allocationExpression = acu.getFirstDescendantOfType(ASTAllocationExpression.class); ASTAllocationExpression nestedAllocation - = allocationExpression.getFirstDescendantOfType(ASTAllocationExpression.class); + = allocationExpression.getFirstDescendantOfType(ASTClassOrInterfaceBodyDeclaration.class) // get the declaration (boundary) + .getFirstDescendantOfType(ASTAllocationExpression.class); // and dive for the nested allocation TypeNode child = (TypeNode) nestedAllocation.jjtGetChild(0); Assert.assertTrue(Converter.class.isAssignableFrom(child.getType())); Assert.assertSame(String.class, child.getTypeDefinition().getGenericType(0).getType()); } @Test - public void testAnoymousExtendingObject() throws Exception { + public void testAnonymousExtendingObject() throws Exception { Node acu = parseAndTypeResolveForClass(AnoymousExtendingObject.class, "1.8"); ASTAllocationExpression allocationExpression = acu.getFirstDescendantOfType(ASTAllocationExpression.class); TypeNode child = (TypeNode) allocationExpression.jjtGetChild(0); @@ -251,8 +293,7 @@ public void testAnonymousInnerClass() throws ClassNotFoundException { @Test public void testLiterals() throws JaxenException { - ASTCompilationUnit acu = parseAndTypeResolveForClass15(Literals.class); - List literals = convertList(acu.findChildNodesWithXPath("//Literal"), ASTLiteral.class); + List literals = selectNodes(Literals.class, ASTLiteral.class); int index = 0; // String s = "s"; @@ -590,14 +631,6 @@ public void testUnaryNumericOperators() throws JaxenException { assertEquals("All expressions not tested", index, expressions.size()); } - private static List convertList(List nodes, Class target) { - List converted = new ArrayList<>(); - for (Node n : nodes) { - converted.add(target.cast(n)); - } - return converted; - } - @Test public void testBinaryNumericOperators() throws JaxenException { ASTCompilationUnit acu = parseAndTypeResolveForClass15(Operators.class); @@ -678,15 +711,11 @@ public void testFullyQualifiedType() { } @Test - public void testThisExpression() throws JaxenException { - ASTCompilationUnit acu = parseAndTypeResolveForClass15(ThisExpression.class); - - List expressions = convertList( - acu.findChildNodesWithXPath("//PrimaryExpression"), - ASTPrimaryExpression.class); - List prefixes = convertList( - acu.findChildNodesWithXPath("//PrimaryPrefix"), - ASTPrimaryPrefix.class); + public void testThisExpression() { + ASTCompilationUnit compilationUnit = parseAndTypeResolveForClass15(ThisExpression.class); + // need to cross borders, to find expressions of the nested classes + List expressions = compilationUnit.findDescendantsOfType(ASTPrimaryExpression.class, true); + List prefixes = compilationUnit.findDescendantsOfType(ASTPrimaryPrefix.class, true); int index = 0; @@ -718,11 +747,8 @@ public void testThisExpression() throws JaxenException { @Test public void testSuperExpression() throws JaxenException { - ASTCompilationUnit acu = parseAndTypeResolveForClass15(SuperExpression.class); - - List expressions = convertList( - acu.findChildNodesWithXPath("//VariableInitializer/Expression/PrimaryExpression/PrimaryPrefix"), - AbstractJavaTypeNode.class); + List expressions = selectNodes(SuperExpression.class, AbstractJavaTypeNode.class, + "//VariableInitializer/Expression/PrimaryExpression/PrimaryPrefix"); int index = 0; @@ -742,14 +768,130 @@ public void testSuperExpression() throws JaxenException { assertEquals("All expressions not tested", index, expressions.size()); } + @Test + public void testArrayTypes() throws JaxenException { + // We must not select the expression in the dimensions + List expressions = selectNodes(ArrayTypes.class, ASTExpression.class, "//VariableInitializer/Expression"); + + int index = 0; + + // int[] a = new int[1]; + // ---------- + assertEquals(int[].class, expressions.get(index++).getType()); + + // Object[][] b = new Object[1][0]; + // ---------------- + assertEquals(Object[][].class, expressions.get(index++).getType()); + + // ArrayTypes[][][] c = new ArrayTypes[][][] { new ArrayTypes[1][2] }; + // --------------------------------------------- + assertEquals(ArrayTypes[][][].class, expressions.get(index++).getType()); + + // ArrayTypes[][][] c = new ArrayTypes[][][] { new ArrayTypes[1][2] }; + // -------------------- + assertEquals(ArrayTypes[][].class, expressions.get(index++).getType()); + + // Make sure we got them all + assertEquals("All expressions not tested", index, expressions.size()); + } + @Test - public void testFieldAccess() throws JaxenException { - ASTCompilationUnit acu = parseAndTypeResolveForClass15(FieldAccess.class); + public void testReferenceType() { + List referenceTypes = selectNodes(ArrayTypes.class, ASTReferenceType.class); + + int index = 0; + + // int[] a = new int[1]; + // ----- + assertEquals(int[].class, referenceTypes.get(index++).getType()); + + // Object[][] b = new Object[1][0]; + // ---------- + assertEquals(Object[][].class, referenceTypes.get(index++).getType()); + + // ArrayTypes[][][] c = new ArrayTypes[][][] { ... }; + // ---------------- + assertEquals(ArrayTypes[][][].class, referenceTypes.get(index++).getType()); - List expressions = convertList( - acu.findChildNodesWithXPath("//StatementExpression/PrimaryExpression"), - AbstractJavaTypeNode.class); + + // Make sure we got them all + assertEquals("All expressions not tested", index, referenceTypes.size()); + } + + + @Test + public void testHeterogeneousArrayFieldDeclaration() throws JaxenException { + List fields = selectNodes(ArrayVariableDeclaration.class, ASTFieldDeclaration.class); + List locals = selectNodes(ArrayVariableDeclaration.class, ASTLocalVariableDeclaration.class); + + // public int[] a, b[]; + testPrimitiveTypeFieldDecl(fields.get(0)); + testPrimitiveTypeFieldDecl(locals.get(0)); + + // public String[] c, d[]; + testRefTypeFieldDecl(fields.get(1)); + testRefTypeFieldDecl(locals.get(1)); + } + + + // subtest + private void testPrimitiveTypeFieldDecl(Node declaration) throws JaxenException { + // public int[] a, b[]; + + ASTReferenceType typeNode = declaration.getFirstChildOfType(ASTType.class).getFirstChildOfType(ASTReferenceType.class); + assertNotNull(typeNode); + assertTrue(typeNode.isArray()); + assertEquals(1, typeNode.getArrayDepth()); + assertEquals("int", typeNode.getFirstChildOfType(ASTPrimitiveType.class).getImage()); + + ASTVariableDeclaratorId aID = declaration.getFirstChildOfType(ASTVariableDeclarator.class).getFirstChildOfType(ASTVariableDeclaratorId.class); + assertNotNull(aID); + assertEquals("a", aID.getImage()); + assertFalse(aID.isArray()); + assertEquals(0, aID.getArrayDepth()); + assertEquals(int[].class, aID.getType()); + + ASTVariableDeclaratorId bID = declaration.findChildrenOfType(ASTVariableDeclarator.class).get(1).getFirstChildOfType(ASTVariableDeclaratorId.class); + assertNotNull(bID); + assertEquals("b", bID.getImage()); + assertTrue(bID.isArray()); + assertEquals(1, bID.getArrayDepth()); + assertEquals(int[][].class, bID.getType()); + } + + + // subtest + private void testRefTypeFieldDecl(Node declaration) throws JaxenException { + + // public String[] c, d[]; + + ASTReferenceType typeNode = declaration.getFirstChildOfType(ASTType.class).getFirstChildOfType(ASTReferenceType.class); + assertNotNull(typeNode); + assertTrue(typeNode.isArray()); + assertEquals(1, typeNode.getArrayDepth()); + assertEquals("String", typeNode.getFirstChildOfType(ASTClassOrInterfaceType.class).getImage()); + + ASTVariableDeclaratorId cID = declaration.getFirstChildOfType(ASTVariableDeclarator.class).getFirstChildOfType(ASTVariableDeclaratorId.class); + assertNotNull(cID); + assertEquals("c", cID.getImage()); + assertFalse(cID.isArray()); + assertEquals(0, cID.getArrayDepth()); + assertEquals(String[].class, cID.getType()); + + ASTVariableDeclaratorId dID = declaration.findChildrenOfType(ASTVariableDeclarator.class).get(1).getFirstChildOfType(ASTVariableDeclaratorId.class); + assertNotNull(dID); + assertEquals("d", dID.getImage()); + assertTrue(dID.isArray()); + assertEquals(1, dID.getArrayDepth()); + assertEquals(String[][].class, dID.getType()); + } + + + @Test + public void testFieldAccess() throws JaxenException { + List expressions = selectNodes(FieldAccess.class, AbstractJavaTypeNode.class, + "//StatementExpression/PrimaryExpression"); int index = 0; @@ -782,11 +924,8 @@ public void testFieldAccess() throws JaxenException { @Test public void testFieldAccessNested() throws JaxenException { - ASTCompilationUnit acu = parseAndTypeResolveForClass15(FieldAccessNested.class); - - List expressions = convertList( - acu.findChildNodesWithXPath("//StatementExpression/PrimaryExpression"), - AbstractJavaTypeNode.class); + List expressions = selectNodes(FieldAccessNested.class, AbstractJavaTypeNode.class, + "//StatementExpression/PrimaryExpression"); int index = 0; @@ -816,11 +955,8 @@ public void testFieldAccessNested() throws JaxenException { @Test public void testFieldAccessShadow() throws JaxenException { - ASTCompilationUnit acu = parseAndTypeResolveForClass15(FieldAccessShadow.class); - - List expressions = convertList( - acu.findChildNodesWithXPath("//StatementExpression/PrimaryExpression"), - AbstractJavaTypeNode.class); + List expressions = selectNodes(FieldAccessShadow.class, AbstractJavaTypeNode.class, + "//StatementExpression/PrimaryExpression"); int index = 0; @@ -852,11 +988,8 @@ public void testFieldAccessShadow() throws JaxenException { @Test public void testFieldAccessSuper() throws JaxenException { - ASTCompilationUnit acu = parseAndTypeResolveForClass15(FieldAccessSuper.class); - - List expressions = convertList( - acu.findChildNodesWithXPath("//StatementExpression/PrimaryExpression"), - AbstractJavaTypeNode.class); + List expressions = selectNodes(FieldAccessSuper.class, AbstractJavaTypeNode.class, + "//StatementExpression/PrimaryExpression"); int index = 0; @@ -911,11 +1044,8 @@ public void testFieldAccessSuper() throws JaxenException { @Test public void testBoundsGenericFieldAccess() throws JaxenException { - ASTCompilationUnit acu = parseAndTypeResolveForClass15(FieldAccessGenericBounds.class); - - List expressions = convertList( - acu.findChildNodesWithXPath("//StatementExpression/PrimaryExpression"), - AbstractJavaTypeNode.class); + List expressions = selectNodes(FieldAccessGenericBounds.class, AbstractJavaTypeNode.class, + "//StatementExpression/PrimaryExpression"); int index = 0; @@ -951,11 +1081,8 @@ public void testBoundsGenericFieldAccess() throws JaxenException { @Test public void testParameterGenericFieldAccess() throws JaxenException { - ASTCompilationUnit acu = parseAndTypeResolveForClass15(FieldAccessGenericParameter.class); - - List expressions = convertList( - acu.findChildNodesWithXPath("//StatementExpression/PrimaryExpression"), - AbstractJavaTypeNode.class); + List expressions = selectNodes(FieldAccessGenericParameter.class, AbstractJavaTypeNode.class, + "//StatementExpression/PrimaryExpression"); int index = 0; @@ -982,11 +1109,8 @@ public void testParameterGenericFieldAccess() throws JaxenException { @Test public void testSimpleGenericFieldAccess() throws JaxenException { - ASTCompilationUnit acu = parseAndTypeResolveForClass15(FieldAccessGenericSimple.class); - - List expressions = convertList( - acu.findChildNodesWithXPath("//StatementExpression/PrimaryExpression"), - AbstractJavaTypeNode.class); + List expressions = selectNodes(FieldAccessGenericSimple.class, AbstractJavaTypeNode.class, + "//StatementExpression/PrimaryExpression"); int index = 0; @@ -1045,11 +1169,8 @@ public void testSimpleGenericFieldAccess() throws JaxenException { @Test public void testRawGenericFieldAccess() throws JaxenException { - ASTCompilationUnit acu = parseAndTypeResolveForClass15(FieldAccessGenericRaw.class); - - List expressions = convertList( - acu.findChildNodesWithXPath("//StatementExpression/PrimaryExpression"), - AbstractJavaTypeNode.class); + List expressions = selectNodes(FieldAccessGenericRaw.class, AbstractJavaTypeNode.class, + "//StatementExpression/PrimaryExpression"); int index = 0; @@ -1119,11 +1240,8 @@ public void testRawGenericFieldAccess() throws JaxenException { @Test public void testPrimarySimpleGenericFieldAccess() throws JaxenException { - ASTCompilationUnit acu = parseAndTypeResolveForClass15(FieldAccessPrimaryGenericSimple.class); - - List expressions = convertList( - acu.findChildNodesWithXPath("//StatementExpression/PrimaryExpression"), - AbstractJavaTypeNode.class); + List expressions = selectNodes(FieldAccessPrimaryGenericSimple.class, AbstractJavaTypeNode.class, + "//StatementExpression/PrimaryExpression"); int index = 0; @@ -1174,11 +1292,8 @@ public void testPrimarySimpleGenericFieldAccess() throws JaxenException { @Test public void testFieldAccessGenericNested() throws JaxenException { - ASTCompilationUnit acu = parseAndTypeResolveForClass15(FieldAccessGenericNested.class); - - List expressions = convertList( - acu.findChildNodesWithXPath("//StatementExpression/PrimaryExpression"), - AbstractJavaTypeNode.class); + List expressions = selectNodes(FieldAccessGenericNested.class, AbstractJavaTypeNode.class, + "//StatementExpression/PrimaryExpression"); int index = 0; @@ -1196,11 +1311,8 @@ public void testFieldAccessGenericNested() throws JaxenException { @Test public void testFieldAccessStatic() throws JaxenException { - ASTCompilationUnit acu = parseAndTypeResolveForClass15(FieldAccessStatic.class); - - List expressions = convertList( - acu.findChildNodesWithXPath("//StatementExpression/PrimaryExpression"), - AbstractJavaTypeNode.class); + List expressions = selectNodes(FieldAccessStatic.class, AbstractJavaTypeNode.class, + "//StatementExpression/PrimaryExpression"); int index = 0; @@ -1245,11 +1357,8 @@ public void testFieldAccessStatic() throws JaxenException { @Test public void testMethodPotentialApplicability() throws JaxenException { - ASTCompilationUnit acu = parseAndTypeResolveForClass15(MethodPotentialApplicability.class); - - List expressions = convertList( - acu.findChildNodesWithXPath("//VariableInitializer/Expression/PrimaryExpression"), - AbstractJavaTypeNode.class); + List expressions = selectNodes(MethodPotentialApplicability.class, AbstractJavaTypeNode.class, + "//VariableInitializer/Expression/PrimaryExpression"); int index = 0; @@ -1289,11 +1398,8 @@ public void testMethodPotentialApplicability() throws JaxenException { @Test public void testMethodAccessibility() throws JaxenException { - ASTCompilationUnit acu = parseAndTypeResolveForClass15(MethodAccessibility.class); - - List expressions = convertList( - acu.findChildNodesWithXPath("//VariableInitializer/Expression/PrimaryExpression"), - AbstractJavaTypeNode.class); + List expressions = selectNodes(MethodAccessibility.class, AbstractJavaTypeNode.class, + "//VariableInitializer/Expression/PrimaryExpression"); int index = 0; @@ -1314,15 +1420,12 @@ public void testMethodAccessibility() throws JaxenException { @Test public void testMethodFirstPhase() throws JaxenException { - ASTCompilationUnit acu = parseAndTypeResolveForClass15(MethodFirstPhase.class); - - List expressions = convertList( - acu.findChildNodesWithXPath("//VariableInitializer/Expression/PrimaryExpression"), - AbstractJavaTypeNode.class); + List expressions = selectNodes(MethodFirstPhase.class, "1.8", AbstractJavaTypeNode.class, + "//VariableInitializer/Expression/PrimaryExpression"); int index = 0; - // int a = subtype(10, 'a', null, new Integer[0]); + // int a = subtype(10, 'a', ""); assertEquals(int.class, expressions.get(index).getType()); assertEquals(int.class, getChildType(expressions.get(index), 0)); assertEquals(int.class, getChildType(expressions.get(index++), 1)); @@ -1332,17 +1435,19 @@ public void testMethodFirstPhase() throws JaxenException { assertEquals(Exception.class, getChildType(expressions.get(index), 0)); assertEquals(Exception.class, getChildType(expressions.get(index++), 1)); + // Set set = new HashSet<>(); + assertEquals(HashSet.class, expressions.get(index++).getType()); + + // List myList = new ArrayList<>(); + assertEquals(ArrayList.class, expressions.get(index++).getType()); + // Make sure we got them all assertEquals("All expressions not tested", index, expressions.size()); } @Test public void testMethodMostSpecific() throws JaxenException { - ASTCompilationUnit acu = parseAndTypeResolveForClass15(MethodMostSpecific.class); - - List expressions = convertList( - acu.findChildNodesWithXPath("//VariableInitializer/Expression/PrimaryExpression"), - AbstractJavaTypeNode.class); + List expressions = selectNodes(MethodMostSpecific.class, AbstractJavaTypeNode.class, "//VariableInitializer/Expression/PrimaryExpression"); int index = 0; @@ -1367,11 +1472,7 @@ public void testMethodMostSpecific() throws JaxenException { @Test public void testMethodSecondPhase() throws JaxenException { - ASTCompilationUnit acu = parseAndTypeResolveForClass15(MethodSecondPhase.class); - - List expressions = convertList( - acu.findChildNodesWithXPath("//VariableInitializer/Expression/PrimaryExpression"), - AbstractJavaTypeNode.class); + List expressions = selectNodes(MethodSecondPhase.class, AbstractJavaTypeNode.class, "//VariableInitializer/Expression/PrimaryExpression"); int index = 0; @@ -1407,11 +1508,7 @@ public void testMethodSecondPhase() throws JaxenException { @Test public void testMethodThirdPhase() throws JaxenException { - ASTCompilationUnit acu = parseAndTypeResolveForClass15(MethodThirdPhase.class); - - List expressions = convertList( - acu.findChildNodesWithXPath("//VariableInitializer/Expression/PrimaryExpression"), - AbstractJavaTypeNode.class); + List expressions = selectNodes(MethodThirdPhase.class, AbstractJavaTypeNode.class, "//VariableInitializer/Expression/PrimaryExpression"); int index = 0; @@ -1438,11 +1535,7 @@ public void testMethodThirdPhase() throws JaxenException { @Test public void testMethodStaticAccess() throws JaxenException { - ASTCompilationUnit acu = parseAndTypeResolveForClass15(MethodStaticAccess.class); - - List expressions = convertList( - acu.findChildNodesWithXPath("//VariableInitializer/Expression/PrimaryExpression"), - AbstractJavaTypeNode.class); + List expressions = selectNodes(MethodStaticAccess.class, AbstractJavaTypeNode.class, "//VariableInitializer/Expression/PrimaryExpression"); int index = 0; @@ -1473,11 +1566,7 @@ public void testMethodStaticAccess() throws JaxenException { @Test public void testMethodGenericExplicit() throws JaxenException { - ASTCompilationUnit acu = parseAndTypeResolveForClass15(MethodGenericExplicit.class); - - List expressions = convertList( - acu.findChildNodesWithXPath("//VariableInitializer/Expression/PrimaryExpression"), - AbstractJavaTypeNode.class); + List expressions = selectNodes(MethodGenericExplicit.class, AbstractJavaTypeNode.class, "//VariableInitializer/Expression/PrimaryExpression"); int index = 0; @@ -1492,12 +1581,34 @@ public void testMethodGenericExplicit() throws JaxenException { @Test - public void testMethodTypeInference() throws JaxenException { - ASTCompilationUnit acu = parseAndTypeResolveForClass15(GenericMethodsImplicit.class); + public void testGenericArrays() throws JaxenException { + List expressions = selectNodes(GenericsArrays.class, AbstractJavaTypeNode.class, "//VariableInitializer/Expression/PrimaryExpression"); + + int index = 0; - List expressions = convertList( - acu.findChildNodesWithXPath("//VariableInitializer/Expression/PrimaryExpression"), - AbstractJavaTypeNode.class); + // List var = Arrays.asList(params); + AbstractJavaTypeNode expression = expressions.get(index++); + // TODO : Type inference is still incomplete, we fail to detect the return type of the method + //assertEquals(List.class, expression.getTypeDefinition().getType()); + //assertEquals(String.class, expression.getTypeDefinition().getGenericType(0).getType()); + + // List var2 = Arrays.asList(params); + AbstractJavaTypeNode expression2 = expressions.get(index++); + assertEquals(List.class, expression2.getTypeDefinition().getType()); + assertEquals(String.class, expression2.getTypeDefinition().getGenericType(0).getType()); + + // List var3 = Arrays.asList(params); + AbstractJavaTypeNode expression3 = expressions.get(index++); + assertEquals(List.class, expression3.getTypeDefinition().getType()); + assertEquals(String[].class, expression3.getTypeDefinition().getGenericType(0).getType()); + + // Make sure we got them all + assertEquals("All expressions not tested", index, expressions.size()); + } + + @Test + public void testMethodTypeInference() throws JaxenException { + List expressions = selectNodes(GenericMethodsImplicit.class, AbstractJavaTypeNode.class, "//VariableInitializer/Expression/PrimaryExpression"); int index = 0; @@ -1509,6 +1620,47 @@ public void testMethodTypeInference() throws JaxenException { // Make sure we got them all assertEquals("All expressions not tested", index, expressions.size()); } + + @Test + public void testMethodTypeInferenceVarargsZeroArity() throws JaxenException { + List expressions = selectNodes(VarargsZeroArity.class, AbstractJavaTypeNode.class, "//VariableInitializer/Expression/PrimaryExpression"); + + int index = 0; + + // int var = aMethod(); + assertEquals(int.class, expressions.get(index++).getType()); + + //String var2 = aMethod(""); + assertEquals(String.class, expressions.get(index++).getType()); + + // Make sure we got them all + assertEquals("All expressions not tested", index, expressions.size()); + } + + @Test + public void testMethodTypeInferenceVarargsAsFixedArity() throws JaxenException { + List expressions = selectNodes(VarargsAsFixedArity.class, AbstractJavaTypeNode.class, "//VariableInitializer/Expression/PrimaryExpression"); + + int index = 0; + + // int var = aMethod(""); + assertEquals(int.class, expressions.get(index++).getType()); + + // String var2 = aMethod(); + assertEquals(String.class, expressions.get(index++).getType()); + + // String var3 = aMethod("", ""); + assertEquals(String.class, expressions.get(index++).getType()); + + // String var4 = aMethod(new Object[] { null }); + assertEquals(String.class, expressions.get(index++).getType()); + + // null literal has null type + assertNull(expressions.get(index++).getType()); + + // Make sure we got them all + assertEquals("All expressions not tested", index, expressions.size()); + } @Test public void testJavaTypeDefinitionEquals() { @@ -1593,11 +1745,7 @@ public void testMethodInitialBounds() throws NoSuchMethodException { @Test public void testMethodInitialConstraints() throws NoSuchMethodException, JaxenException { - ASTCompilationUnit acu = parseAndTypeResolveForClass15(GenericMethodsImplicit.class); - - List expressions = convertList( - acu.findChildNodesWithXPath("//ArgumentList"), - AbstractJavaNode.class); + List expressions = selectNodes(GenericMethodsImplicit.class, AbstractJavaNode.class, "//ArgumentList"); List variables = new ArrayList<>(); for (int i = 0; i < 2; ++i) { @@ -1623,18 +1771,12 @@ public void testMethodInitialConstraints() throws NoSuchMethodException, JaxenEx } @Test - public void testMethodParameterization() throws JaxenException, NoSuchMethodException { - ASTCompilationUnit acu = parseAndTypeResolveForClass15(GenericMethodsImplicit.class); - - List expressions = convertList( - acu.findChildNodesWithXPath("//ArgumentList"), - AbstractJavaNode.class); + public void testMethodParameterization() throws NoSuchMethodException { + ASTArgumentList argList = selectNodes(GenericMethodsImplicit.class, ASTArgumentList.class).get(0); - JavaTypeDefinition context = forClass(GenericMethodsImplicit.class, - forClass(Thread.class)); + JavaTypeDefinition context = forClass(GenericMethodsImplicit.class, forClass(Thread.class)); Method method = GenericMethodsImplicit.class.getMethod("bar", Object.class, Object.class, Integer.class, Object.class); - ASTArgumentList argList = (ASTArgumentList) expressions.get(0); MethodType inferedMethod = MethodTypeResolution.parameterizeInvocation(context, method, argList); @@ -1649,6 +1791,54 @@ public void testMethodParameterization() throws JaxenException, NoSuchMethodExce } + @Test + public void testNestedAllocationExpressions() { + ASTCompilationUnit acu = parseAndTypeResolveForClass15(NestedAllocationExpressions.class); + List allocs = acu.findDescendantsOfType(ASTAllocationExpression.class); + + assertFalse(allocs.get(0).isAnonymousClass()); + assertEquals(Thread.class, allocs.get(0).getType()); + + assertTrue(allocs.get(1).isAnonymousClass()); + // FUTURE 1.8 use Class.getTypeName() instead of toString + assertTrue(allocs.get(1).getType().toString().endsWith("NestedAllocationExpressions$1")); + } + + @Test + public void testAnnotatedTypeParams() { + parseAndTypeResolveForString("public class Foo { public static > T getEnum() { return null; } }", "1.8"); + } + + @Test + public void testMethodOverrides() throws Exception { + parseAndTypeResolveForClass(SubTypeUsage.class, "1.8"); + } + + @Test + public void testMethodWildcardParam() throws Exception { + parseAndTypeResolveForClass(MethodGenericParam.class, "1.8"); + } + + @Test + public void testAbstractMethodReturnType() throws Exception { + parseAndTypeResolveForClass(AbstractReturnTypeUseCase.class, "1.8"); + } + + @Test + public void testMethodOverloaded() throws Exception { + parseAndTypeResolveForClass(OverloadedMethodsUsage.class, "1.8"); + } + + @Test + public void testVarArgsMethodUseCase() throws Exception { + parseAndTypeResolveForClass(VarArgsMethodUseCase.class, "1.8"); + } + + @Test + public void testLocalGenericClass() throws Exception { + parseAndTypeResolveForClass(LocalGenericClass.class, "9"); + } + private JavaTypeDefinition getChildTypeDef(Node node, int childIndex) { return ((TypeNode) node.jjtGetChild(childIndex)).getTypeDefinition(); } @@ -1657,6 +1847,32 @@ private Class getChildType(Node node, int childIndex) { return ((TypeNode) node.jjtGetChild(childIndex)).getType(); } + + private List selectNodes(String source, Class resultType, String xpath) throws JaxenException { + return selectNodes(source, "1.5", resultType, xpath); + } + + // This is the master overload, others just default the parameters + private List selectNodes(String source, String version, Class resultType, String xpath) throws JaxenException { + ASTCompilationUnit acu = parseAndTypeResolveForString(source, version); + return convertList(acu.findChildNodesWithXPath(xpath), resultType); + } + + + private List selectNodes(Class source, Class resultType) { + return parseAndTypeResolveForClass(source, "1.5").findDescendantsOfType(resultType); + } + + private List selectNodes(Class source, Class resultType, String xpath) throws JaxenException { + return selectNodes(source, "1.5", resultType, xpath); + } + + + private List selectNodes(Class source, String version, Class resultType, String xpath) throws JaxenException { + return selectNodes(ParserTstUtil.getSourceFromClass(source), version, resultType, xpath); + } + + private void assertChildTypeArgsEqualTo(Node node, int childIndex, Class... classes) { JavaTypeDefinition typeDef = ((TypeNode) node.jjtGetChild(childIndex)).getTypeDefinition(); @@ -1683,8 +1899,18 @@ private ASTCompilationUnit parseAndTypeResolveForString(String source, String ve .getVersion(version).getLanguageVersionHandler(); ASTCompilationUnit acu = (ASTCompilationUnit) languageVersionHandler .getParser(languageVersionHandler.getDefaultParserOptions()).parse(null, new StringReader(source)); + languageVersionHandler.getQualifiedNameResolutionFacade(ClassTypeResolverTest.class.getClassLoader()).start(acu); languageVersionHandler.getSymbolFacade().start(acu); languageVersionHandler.getTypeResolutionFacade(ClassTypeResolverTest.class.getClassLoader()).start(acu); return acu; } + + + private static List convertList(List nodes, Class target) { + List converted = new ArrayList<>(); + for (Node n : nodes) { + converted.add(target.cast(n)); + } + return converted; + } } diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/typeresolution/MethodTypeResolutionTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/typeresolution/MethodTypeResolutionTest.java new file mode 100644 index 00000000000..7a9a5bd0157 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/typeresolution/MethodTypeResolutionTest.java @@ -0,0 +1,28 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.typeresolution; + +import static org.junit.Assert.assertSame; + +import org.junit.Test; + +import net.sourceforge.pmd.lang.java.typeresolution.MethodTypeResolution; +import net.sourceforge.pmd.lang.java.typeresolution.typedefinition.JavaTypeDefinition; + +public class MethodTypeResolutionTest { + + @Test + public void testBoxingRules() { + assertSame(Boolean.class, MethodTypeResolution.boxPrimitive(JavaTypeDefinition.forClass(boolean.class)).getType()); + assertSame(Double.class, MethodTypeResolution.boxPrimitive(JavaTypeDefinition.forClass(double.class)).getType()); + assertSame(Float.class, MethodTypeResolution.boxPrimitive(JavaTypeDefinition.forClass(float.class)).getType()); + assertSame(Long.class, MethodTypeResolution.boxPrimitive(JavaTypeDefinition.forClass(long.class)).getType()); + assertSame(Integer.class, MethodTypeResolution.boxPrimitive(JavaTypeDefinition.forClass(int.class)).getType()); + assertSame(Character.class, MethodTypeResolution.boxPrimitive(JavaTypeDefinition.forClass(char.class)).getType()); + assertSame(Short.class, MethodTypeResolution.boxPrimitive(JavaTypeDefinition.forClass(short.class)).getType()); + assertSame(Byte.class, MethodTypeResolution.boxPrimitive(JavaTypeDefinition.forClass(byte.class)).getType()); + assertSame(Void.class, MethodTypeResolution.boxPrimitive(JavaTypeDefinition.forClass(void.class)).getType()); + } +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/typeresolution/TypeInferenceTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/typeresolution/TypeInferenceTest.java index bf6f0194984..4a535717b70 100644 --- a/pmd-java/src/test/java/net/sourceforge/pmd/typeresolution/TypeInferenceTest.java +++ b/pmd-java/src/test/java/net/sourceforge/pmd/typeresolution/TypeInferenceTest.java @@ -9,10 +9,14 @@ import static net.sourceforge.pmd.lang.java.typeresolution.typeinference.InferenceRuleType.LOOSE_INVOCATION; import static net.sourceforge.pmd.lang.java.typeresolution.typeinference.InferenceRuleType.SUBTYPE; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; import java.util.ArrayList; import java.util.Collection; +import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Set; @@ -26,7 +30,6 @@ import net.sourceforge.pmd.lang.java.typeresolution.typeinference.InferenceRuleType; import net.sourceforge.pmd.lang.java.typeresolution.typeinference.TypeInferenceResolver; import net.sourceforge.pmd.lang.java.typeresolution.typeinference.Variable; - import net.sourceforge.pmd.typeresolution.testdata.dummytypes.SuperClassA; import net.sourceforge.pmd.typeresolution.testdata.dummytypes.SuperClassA2; import net.sourceforge.pmd.typeresolution.testdata.dummytypes.SuperClassAOther; @@ -39,15 +42,15 @@ public class TypeInferenceTest { private JavaTypeDefinition generic = JavaTypeDefinition.forClass(Map.class, number, integer); private Variable alpha = new Variable(); private Variable beta = new Variable(); - JavaTypeDefinition s = JavaTypeDefinition.forClass(int.class); - JavaTypeDefinition t = JavaTypeDefinition.forClass(double.class); + private JavaTypeDefinition s = JavaTypeDefinition.forClass(int.class); + private JavaTypeDefinition t = JavaTypeDefinition.forClass(double.class); @Test public void testEqualityReduceProperVsProper() { // If S and T are proper types, the constraint reduces to true if S is the same as T (§4.3.4), and false // otherwise. assertTrue(new Constraint(number, number, EQUALITY).reduce().isEmpty()); - assertEquals(new Constraint(number, integer, EQUALITY).reduce(), null); + assertNull(new Constraint(number, integer, EQUALITY).reduce()); // Otherwise, if S or T is the null type, the constraint reduces to false. TODO } @@ -57,7 +60,7 @@ public void testEqualityReduceVariableVsNotPrimitive() { // Otherwise, if S is an inference variable, α, and T is not a primitive type, the constraint reduces to // the bound α = T. List result = new Constraint(alpha, number, EQUALITY).reduce(); - assertEquals(result.size(), 1); + assertEquals(1, result.size()); testBoundOrConstraint(result.get(0), alpha, number, EQUALITY, Bound.class); } @@ -66,11 +69,11 @@ public void testEqualityReduceNotPrimitiveVsVariable() { // Otherwise, if T is an inference variable, α, and S is not a primitive type, the constraint reduces // to the bound S = α. List result = new Constraint(number, alpha, EQUALITY).reduce(); - assertEquals(result.size(), 1); + assertEquals(1, result.size()); testBoundOrConstraint(result.get(0), number, alpha, EQUALITY, Bound.class); result = new Constraint(alpha, beta, EQUALITY).reduce(); - assertEquals(result.size(), 1); + assertEquals(1, result.size()); testBoundOrConstraint(result.get(0), alpha, beta, EQUALITY, Bound.class); } @@ -80,7 +83,7 @@ public void testEqualityReduceSameErasure() { // arguments B1, ..., Bn and T has type arguments A1, ..., An, the constraint reduces to the // following new constraints: for all i (1 ≤ i ≤ n), ‹Bi = Ai›. List result = new Constraint(generic, generic, EQUALITY).reduce(); - assertEquals(result.size(), 2); + assertEquals(2, result.size()); testBoundOrConstraint(result.get(0), number, number, EQUALITY, Constraint.class); testBoundOrConstraint(result.get(1), integer, integer, EQUALITY, Constraint.class); } @@ -91,7 +94,7 @@ public void testEqualityReduceArrayTypes() { List result = new Constraint(JavaTypeDefinition.forClass(Number[].class), JavaTypeDefinition.forClass(Integer[].class), EQUALITY) .reduce(); - assertEquals(result.size(), 1); + assertEquals(1, result.size()); testBoundOrConstraint(result.get(0), number, integer, EQUALITY, Constraint.class); } @@ -102,9 +105,9 @@ public void testSubtypeReduceProperVsProper() { // If S and T are proper types, the constraint reduces to true if S is a subtype of T (§4.10), // and false otherwise. List result = new Constraint(integer, number, SUBTYPE).reduce(); - assertEquals(result.size(), 0); + assertEquals(0, result.size()); result = new Constraint(number, integer, SUBTYPE).reduce(); - assertEquals(result, null); + assertNull(result); // Otherwise, if S is the null type, the constraint reduces to true. TODO @@ -116,7 +119,7 @@ public void testSubtypeReduceProperVsProper() { public void testSubtypeReduceVariableVsAny() { // Otherwise, if S is an inference variable, α, the constraint reduces to the bound α <: T. List result = new Constraint(alpha, integer, SUBTYPE).reduce(); - assertEquals(result.size(), 1); + assertEquals(1, result.size()); testBoundOrConstraint(result.get(0), alpha, integer, SUBTYPE, Bound.class); } @@ -124,11 +127,11 @@ public void testSubtypeReduceVariableVsAny() { public void testSubtypeReduceAnyVsVariable() { // Otherwise, if T is an inference variable, α, the constraint reduces to the bound S <: α. List result = new Constraint(integer, alpha, SUBTYPE).reduce(); - assertEquals(result.size(), 1); + assertEquals(1, result.size()); testBoundOrConstraint(result.get(0), integer, alpha, SUBTYPE, Bound.class); result = new Constraint(alpha, beta, SUBTYPE).reduce(); - assertEquals(result.size(), 1); + assertEquals(1, result.size()); testBoundOrConstraint(result.get(0), alpha, beta, SUBTYPE, Bound.class); } @@ -140,10 +143,10 @@ public void testLooseInvocationProperVsProper() { // If S and T are proper types, the constraint reduces to true if S is compatible in a loose invocation // context with T (§5.3), and false otherwise. List result = new Constraint(number, integer, LOOSE_INVOCATION).reduce(); - assertEquals(result, null); + assertNull(result); result = new Constraint(integer, number, LOOSE_INVOCATION).reduce(); - assertEquals(result.size(), 0); + assertEquals(0, result.size()); } @Test @@ -151,7 +154,7 @@ public void testLooseInvocationLeftBoxing() { // Otherwise, if S is a primitive type, let S' be the result of applying boxing conversion (§5.1.7) to S. // Then the constraint reduces to ‹S' → T›. List result = new Constraint(primitiveInt, number, LOOSE_INVOCATION).reduce(); - assertEquals(result.size(), 1); + assertEquals(1, result.size()); testBoundOrConstraint(result.get(0), integer, number, LOOSE_INVOCATION, Constraint.class); } @@ -161,7 +164,7 @@ public void testLooseInvocationRightBoxing() { // Otherwise, if T is a primitive type, let T' be the result of applying boxing conversion (§5.1.7) to T. // Then the constraint reduces to ‹S = T'›. List result = new Constraint(number, primitiveInt, LOOSE_INVOCATION).reduce(); - assertEquals(result.size(), 1); + assertEquals(1, result.size()); testBoundOrConstraint(result.get(0), number, integer, EQUALITY, Constraint.class); // Otherwise, if T is a parameterized type of the form G, and there exists no type of the @@ -178,11 +181,11 @@ public void testLooseInvocationRightBoxing() { public void testLooseInvocationAnythingElse() { // Otherwise, the constraint reduces to ‹S<:T›. List result = new Constraint(number, alpha, LOOSE_INVOCATION).reduce(); - assertEquals(result.size(), 1); + assertEquals(1, result.size()); testBoundOrConstraint(result.get(0), number, alpha, SUBTYPE, Constraint.class); result = new Constraint(alpha, number, LOOSE_INVOCATION).reduce(); - assertEquals(result.size(), 1); + assertEquals(1, result.size()); testBoundOrConstraint(result.get(0), alpha, number, SUBTYPE, Constraint.class); } @@ -193,7 +196,7 @@ public void testContainmentReduceTypeVsType() { // If T is a type: // If S is a type, the constraint reduces to ‹S = T›. List result = new Constraint(number, integer, CONTAINS).reduce(); - assertEquals(result.size(), 1); + assertEquals(1, result.size()); testBoundOrConstraint(result.get(0), number, integer, EQUALITY, Constraint.class); // If T is a type: // If S is a wildcard, the constraint reduces to false. TODO @@ -213,22 +216,22 @@ public void testIncorporationEqualityAndEquality() { // ### Original rule 1. : α = S and α = T imply ‹S = T› result = incorporationResult(new Bound(alpha, s, EQUALITY), new Bound(alpha, t, EQUALITY)); - assertEquals(result.size(), 1); + assertEquals(1, result.size()); testBoundOrConstraint(result.get(0), s, t, EQUALITY, Constraint.class); // α = S and T = α imply ‹S = T› result = incorporationResult(new Bound(alpha, s, EQUALITY), new Bound(t, alpha, EQUALITY)); - assertEquals(result.size(), 1); + assertEquals(1, result.size()); testBoundOrConstraint(result.get(0), s, t, EQUALITY, Constraint.class); // S = α and α = T imply ‹S = T› result = incorporationResult(new Bound(s, alpha, EQUALITY), new Bound(alpha, t, EQUALITY)); - assertEquals(result.size(), 1); + assertEquals(1, result.size()); testBoundOrConstraint(result.get(0), s, t, EQUALITY, Constraint.class); // S = α and T = α imply ‹S = T› result = incorporationResult(new Bound(s, alpha, EQUALITY), new Bound(t, alpha, EQUALITY)); - assertEquals(result.size(), 1); + assertEquals(1, result.size()); testBoundOrConstraint(result.get(0), s, t, EQUALITY, Constraint.class); } @@ -238,22 +241,22 @@ public void testIncorporationEqualityAndSubtypeLeftVariable() { // ### Original rule 2. : α = S and α <: T imply ‹S <: T› result = incorporationResult(new Bound(alpha, s, EQUALITY), new Bound(alpha, t, SUBTYPE)); - assertEquals(result.size(), 1); + assertEquals(1, result.size()); testBoundOrConstraint(result.get(0), s, t, SUBTYPE, Constraint.class); // S = α and α <: T imply ‹S <: T› result = incorporationResult(new Bound(s, alpha, EQUALITY), new Bound(alpha, t, SUBTYPE)); - assertEquals(result.size(), 1); + assertEquals(1, result.size()); testBoundOrConstraint(result.get(0), s, t, SUBTYPE, Constraint.class); // α <: T and α = S imply ‹S <: T› result = incorporationResult(new Bound(alpha, t, SUBTYPE), new Bound(alpha, s, EQUALITY)); - assertEquals(result.size(), 1); + assertEquals(1, result.size()); testBoundOrConstraint(result.get(0), s, t, SUBTYPE, Constraint.class); // α <: T and S = α imply ‹S <: T› result = incorporationResult(new Bound(alpha, t, SUBTYPE), new Bound(s, alpha, EQUALITY)); - assertEquals(result.size(), 1); + assertEquals(1, result.size()); testBoundOrConstraint(result.get(0), s, t, SUBTYPE, Constraint.class); } @@ -263,22 +266,22 @@ public void testIncorporationEqualityAndSubtypeRightVariable() { // ### Original rule 3. : α = S and T <: α imply ‹T <: S› result = incorporationResult(new Bound(alpha, s, EQUALITY), new Bound(t, alpha, SUBTYPE)); - assertEquals(result.size(), 1); + assertEquals(1, result.size()); testBoundOrConstraint(result.get(0), t, s, SUBTYPE, Constraint.class); // S = α and T <: α imply ‹T <: S› result = incorporationResult(new Bound(s, alpha, EQUALITY), new Bound(t, alpha, SUBTYPE)); - assertEquals(result.size(), 1); + assertEquals(1, result.size()); testBoundOrConstraint(result.get(0), t, s, SUBTYPE, Constraint.class); // T <: α and α = S imply ‹T <: S› result = incorporationResult(new Bound(t, alpha, SUBTYPE), new Bound(alpha, s, EQUALITY)); - assertEquals(result.size(), 1); + assertEquals(1, result.size()); testBoundOrConstraint(result.get(0), t, s, SUBTYPE, Constraint.class); // T <: α and S = α imply ‹T <: S› result = incorporationResult(new Bound(t, alpha, SUBTYPE), new Bound(s, alpha, EQUALITY)); - assertEquals(result.size(), 1); + assertEquals(1, result.size()); testBoundOrConstraint(result.get(0), t, s, SUBTYPE, Constraint.class); } @@ -288,12 +291,12 @@ public void testIncorporationSubtypeAndSubtype() { // ### Original rule 4. : S <: α and α <: T imply ‹S <: T› result = incorporationResult(new Bound(s, alpha, EQUALITY), new Bound(alpha, t, SUBTYPE)); - assertEquals(result.size(), 1); + assertEquals(1, result.size()); testBoundOrConstraint(result.get(0), s, t, SUBTYPE, Constraint.class); // α <: T and S <: α imply ‹S <: T› result = incorporationResult(new Bound(alpha, t, SUBTYPE), new Bound(s, alpha, EQUALITY)); - assertEquals(result.size(), 1); + assertEquals(1, result.size()); testBoundOrConstraint(result.get(0), s, t, SUBTYPE, Constraint.class); } @@ -306,10 +309,14 @@ public void testErasedCandidateSet() { Set> erasedCandidate = TypeInferenceResolver.getErasedCandidateSet(types); - assertEquals(erasedCandidate.size(), 3); + assertEquals(3, erasedCandidate.size()); assertTrue(erasedCandidate.contains(Object.class)); assertTrue(erasedCandidate.contains(Collection.class)); assertTrue(erasedCandidate.contains(Iterable.class)); + + Set> emptySet = TypeInferenceResolver.getErasedCandidateSet(Collections.emptyList()); + assertNotNull(emptySet); + assertEquals(0, emptySet.size()); } @Test @@ -317,7 +324,7 @@ public void testMinimalErasedCandidateSet() { Set> minimalSet = TypeInferenceResolver.getMinimalErasedCandidateSet( JavaTypeDefinition.forClass(List.class).getErasedSuperTypeSet()); - assertEquals(minimalSet.size(), 1); + assertEquals(1, minimalSet.size()); assertTrue(minimalSet.contains(List.class)); } @@ -328,7 +335,7 @@ public void testLeastUpperBound() { lowerBounds.add(JavaTypeDefinition.forClass(SuperClassAOther.class)); lowerBounds.add(JavaTypeDefinition.forClass(SuperClassAOther2.class)); - assertEquals(TypeInferenceResolver.lub(lowerBounds), JavaTypeDefinition.forClass(SuperClassA2.class)); + assertEquals(JavaTypeDefinition.forClass(SuperClassA2.class), TypeInferenceResolver.lub(lowerBounds)); } @Test @@ -337,8 +344,8 @@ public void testResolution() { bounds.add(new Bound(JavaTypeDefinition.forClass(SuperClassA.class), alpha, SUBTYPE)); bounds.add(new Bound(JavaTypeDefinition.forClass(SuperClassAOther.class), alpha, SUBTYPE)); Map result = TypeInferenceResolver.resolveVariables(bounds); - assertEquals(result.size(), 1); - assertEquals(result.get(alpha), JavaTypeDefinition.forClass(SuperClassA2.class)); + assertEquals(1, result.size()); + assertEquals(JavaTypeDefinition.forClass(SuperClassA2.class), result.get(alpha)); } private List incorporationResult(Bound firstBound, Bound secondBound) { @@ -352,34 +359,34 @@ private List incorporationResult(Bound firstBound, Bound secondBound private void testBoundOrConstraint(BoundOrConstraint val, JavaTypeDefinition left, JavaTypeDefinition right, InferenceRuleType rule, Class type) { - assertTrue(val.getClass() == type); - assertEquals(val.leftProper(), left); - assertEquals(val.rightProper(), right); - assertEquals(val.ruleType(), rule); + assertSame(type, val.getClass()); + assertEquals(left, val.leftProper()); + assertEquals(right, val.rightProper()); + assertEquals(rule, val.ruleType()); } private void testBoundOrConstraint(BoundOrConstraint val, JavaTypeDefinition left, Variable right, InferenceRuleType rule, Class type) { - assertTrue(val.getClass() == type); - assertEquals(val.leftProper(), left); - assertEquals(val.rightVariable(), right); - assertEquals(val.ruleType(), rule); + assertSame(type, val.getClass()); + assertEquals(left, val.leftProper()); + assertEquals(right, val.rightVariable()); + assertEquals(rule, val.ruleType()); } private void testBoundOrConstraint(BoundOrConstraint val, Variable left, JavaTypeDefinition right, InferenceRuleType rule, Class type) { - assertTrue(val.getClass() == type); - assertEquals(val.leftVariable(), left); - assertEquals(val.rightProper(), right); - assertEquals(val.ruleType(), rule); + assertSame(type, val.getClass()); + assertEquals(left, val.leftVariable()); + assertEquals(right, val.rightProper()); + assertEquals(rule, val.ruleType()); } private void testBoundOrConstraint(BoundOrConstraint val, Variable left, Variable right, InferenceRuleType rule, Class type) { - assertTrue(val.getClass() == type); - assertEquals(val.leftVariable(), left); - assertEquals(val.rightVariable(), right); - assertEquals(val.ruleType(), rule); + assertSame(type, val.getClass()); + assertEquals(left, val.leftVariable()); + assertEquals(right, val.rightVariable()); + assertEquals(rule, val.ruleType()); } } diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/typeresolution/testdata/AbstractReturnTypeUseCase.java b/pmd-java/src/test/java/net/sourceforge/pmd/typeresolution/testdata/AbstractReturnTypeUseCase.java new file mode 100644 index 00000000000..57142c80dee --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/typeresolution/testdata/AbstractReturnTypeUseCase.java @@ -0,0 +1,18 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.typeresolution.testdata; + +import java.util.List; + +import net.sourceforge.pmd.typeresolution.testdata.dummytypes.AbstractReturnType; + +public class AbstractReturnTypeUseCase { + + public void foo() { + AbstractReturnType sample = new AbstractReturnType(); + List list = sample.getList(); + System.out.println(list.size()); + } +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/typeresolution/testdata/ArrayTypes.java b/pmd-java/src/test/java/net/sourceforge/pmd/typeresolution/testdata/ArrayTypes.java new file mode 100644 index 00000000000..15dcda6bbf1 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/typeresolution/testdata/ArrayTypes.java @@ -0,0 +1,15 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.typeresolution.testdata; + +public class ArrayTypes { + + @SuppressWarnings("unused") + public void test() { + int[] a = new int[1]; + Object[][] b = new Object[1][0]; + ArrayTypes[][][] c = new ArrayTypes[][][] { new ArrayTypes[1][2] }; + } +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/typeresolution/testdata/ArrayVariableDeclaration.java b/pmd-java/src/test/java/net/sourceforge/pmd/typeresolution/testdata/ArrayVariableDeclaration.java new file mode 100644 index 00000000000..05a99d22381 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/typeresolution/testdata/ArrayVariableDeclaration.java @@ -0,0 +1,40 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.typeresolution.testdata; + +/** + * @since 6.3.0 + */ +@SuppressWarnings("unused") +public class ArrayVariableDeclaration { + + /* In the following declarations: + * * The VariableDeclarators of a should have type int[], b should be int[][], c should be String[], d should be String[][] + * * The types of the FieldDeclaration and LocalVariableDeclaration nodes are undefined + * + * See https://github.com/pmd/pmd/issues/910 + */ + + /* + FieldDeclaration + |+ Type + | + ReferenceType[@Array=true() and @ArrayDims=1] + | + PrimitiveType + | + |+ VariableDeclarator + | + VariableDeclaratorId[@Image="a" and @Array=false() and ArrayDims=0 and typeIs(.)='int[].class'] + | + |+ VariableDeclarator + | + VariableDeclaratorId[@Image="b" and @Array=true() and ArrayDims=1 and typeIs(.)='int[][].class'] + */ + public int[] a, b[]; // SUPPRESS CHECKSTYLE now + public String[] c, d[]; // SUPPRESS CHECKSTYLE now + + + public void testLocalVars() { + int[] a, b[]; // SUPPRESS CHECKSTYLE now + String[] c, d[]; // SUPPRESS CHECKSTYLE now + } +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/typeresolution/testdata/GenericsArrays.java b/pmd-java/src/test/java/net/sourceforge/pmd/typeresolution/testdata/GenericsArrays.java new file mode 100644 index 00000000000..096092dea17 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/typeresolution/testdata/GenericsArrays.java @@ -0,0 +1,18 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.typeresolution.testdata; + +import java.util.Arrays; +import java.util.List; + +public class GenericsArrays { + + @SuppressWarnings("unused") + public void test(String[] params) { + List var = Arrays.asList(params); + List var2 = Arrays.asList(params); + List var3 = Arrays.asList(params); + } +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/typeresolution/testdata/JavaTypeDefinitionToStringNPE.java b/pmd-java/src/test/java/net/sourceforge/pmd/typeresolution/testdata/JavaTypeDefinitionToStringNPE.java new file mode 100644 index 00000000000..e3f26ea9acb --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/typeresolution/testdata/JavaTypeDefinitionToStringNPE.java @@ -0,0 +1,23 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.typeresolution.testdata; + +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +public class JavaTypeDefinitionToStringNPE> { + + Map, Collection> contents = new HashMap<>(); + + public static class TypeLink { } + + public static class SelfReferringType> { } + + public final void putNull(TypeLink field) { + contents.put(field, Collections.singleton(null)); + } +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/typeresolution/testdata/LocalGenericClass.java b/pmd-java/src/test/java/net/sourceforge/pmd/typeresolution/testdata/LocalGenericClass.java new file mode 100644 index 00000000000..35cf462ad72 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/typeresolution/testdata/LocalGenericClass.java @@ -0,0 +1,47 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.typeresolution.testdata; + +public class LocalGenericClass { + + public static void localClassInGeneric() { + class MyLocalClass implements MyCombiner, MyLocalClass> { + private T state; + + @Override + public void accept(T t) { } + + @Override + public Optional get() { + return Optional.empty(); + } + + @Override + public void combine(MyLocalClass other) { + accept(other.state); + } + } + + new MyLocalClass(); + } + + private interface MyCombiner extends MyConsumer, MySupplier { + void combine(T t); + } + + private interface MyConsumer { + void accept(R r); + } + + private interface MySupplier { + S get(); + } + + private static class Optional { + public static Optional empty() { + return new Optional(); + } + } +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/typeresolution/testdata/MethodFirstPhase.java b/pmd-java/src/test/java/net/sourceforge/pmd/typeresolution/testdata/MethodFirstPhase.java index 9cca02ca089..8e2408af2f8 100644 --- a/pmd-java/src/test/java/net/sourceforge/pmd/typeresolution/testdata/MethodFirstPhase.java +++ b/pmd-java/src/test/java/net/sourceforge/pmd/typeresolution/testdata/MethodFirstPhase.java @@ -4,13 +4,26 @@ package net.sourceforge.pmd.typeresolution.testdata; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + public class MethodFirstPhase { void test() { // primitive, char, simple - int a = subtype(10, 'a', ""); + int a = subtype(Long.valueOf(10), 'a', ""); // TODO: add null, array types Exception b = vararg((Number) null); + + Set set = new HashSet<>(); + set.addAll(Arrays.asList("a", "b")); // TODO: return type of method call Arrays.asList is missing + + List myList = new ArrayList<>(); + Collections.sort(myList); // TODO: generic type variables on methods } String vararg(Number... a) { @@ -21,12 +34,23 @@ Exception vararg(Number a) { return null; } + void stringVarargs(String... s) { + + } + + void classVarargs(Class... c) { + + } Exception subtype(short a, int b, String c) { return null; } - int subtype(long a, int b, String c) { + int subtype(T a, int b, String c) { + return 0; + } + + int subtype(Long a, int b, String c) { return 0; } diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/typeresolution/testdata/MethodGenericParam.java b/pmd-java/src/test/java/net/sourceforge/pmd/typeresolution/testdata/MethodGenericParam.java new file mode 100644 index 00000000000..2db2f7d5343 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/typeresolution/testdata/MethodGenericParam.java @@ -0,0 +1,18 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.typeresolution.testdata; + +import net.sourceforge.pmd.typeresolution.testdata.dummytypes.ParametrizedSubType; +import net.sourceforge.pmd.typeresolution.testdata.dummytypes.WildcardMethod; + +public class MethodGenericParam { + + public void foo() { + ParametrizedSubType type = new ParametrizedSubType(); + + WildcardMethod m = new WildcardMethod(); + m.useWildcard(type); + } +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/typeresolution/testdata/NestedAllocationExpressions.java b/pmd-java/src/test/java/net/sourceforge/pmd/typeresolution/testdata/NestedAllocationExpressions.java new file mode 100644 index 00000000000..cd57ddc8ea6 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/typeresolution/testdata/NestedAllocationExpressions.java @@ -0,0 +1,20 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.typeresolution.testdata; + +/** + * @author Clément Fournier + * @since 6.2.0 + */ +public class NestedAllocationExpressions { + static { + new Thread(new Runnable() { + // missing + public void run() { + + } + }).start(); + } +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/typeresolution/testdata/OverloadedMethodsUsage.java b/pmd-java/src/test/java/net/sourceforge/pmd/typeresolution/testdata/OverloadedMethodsUsage.java new file mode 100644 index 00000000000..7827d619ea1 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/typeresolution/testdata/OverloadedMethodsUsage.java @@ -0,0 +1,17 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.typeresolution.testdata; + +import net.sourceforge.pmd.typeresolution.testdata.dummytypes.OverloadedMethods; + +public class OverloadedMethodsUsage { + + private String[] arg1 = null; + private String[] arg2 = new String[1]; + + public void foo() { + OverloadedMethods.equals(arg1, arg2); + } +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/typeresolution/testdata/PmdStackOverflow.java b/pmd-java/src/test/java/net/sourceforge/pmd/typeresolution/testdata/PmdStackOverflow.java new file mode 100644 index 00000000000..328032912b4 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/typeresolution/testdata/PmdStackOverflow.java @@ -0,0 +1,47 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.typeresolution.testdata; + +public class PmdStackOverflow { + + public void shouldThrowStackOverfloError() { + MessageBuilder messageBuilder = new MessageBuilderA(); + + // Works + PartBuilder partBuilder = messageBuilder.newComponent(); + messageBuilder.addComponent(partBuilder.withSomeValue("ABC")); + + // Does not work + messageBuilder.addComponent(messageBuilder.newComponent().withSomeValue("ABC")); + } + +} + +abstract class MessageBuilder> { + + public abstract U newComponent(); + + public T addComponent(U ignore) { + return (T) this; + } +} + +class MessageBuilderA extends MessageBuilder { + + @Override + public PartBuilderA newComponent() { + return new PartBuilderA(); + } +} + +class PartBuilder { + + public T withSomeValue(String ignore) { + return (T) this; + } +} + +class PartBuilderA extends PartBuilder { +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/typeresolution/testdata/SubTypeUsage.java b/pmd-java/src/test/java/net/sourceforge/pmd/typeresolution/testdata/SubTypeUsage.java new file mode 100644 index 00000000000..2a8ae44204d --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/typeresolution/testdata/SubTypeUsage.java @@ -0,0 +1,15 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.typeresolution.testdata; + +import net.sourceforge.pmd.typeresolution.testdata.dummytypes.SubType; + +public class SubTypeUsage { + + public void foo() { + SubType var = new SubType(); + var.myMethod(); + } +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/typeresolution/testdata/VarArgsMethodUseCase.java b/pmd-java/src/test/java/net/sourceforge/pmd/typeresolution/testdata/VarArgsMethodUseCase.java new file mode 100644 index 00000000000..f7b7a3d9fd0 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/typeresolution/testdata/VarArgsMethodUseCase.java @@ -0,0 +1,14 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.typeresolution.testdata; + +public class VarArgsMethodUseCase { + + public void foo() { + MethodFirstPhase methods = new MethodFirstPhase(); + methods.stringVarargs("a", "b"); + methods.classVarargs(String.class, VarArgsMethodUseCase.class); + } +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/typeresolution/testdata/VarargsAsFixedArity.java b/pmd-java/src/test/java/net/sourceforge/pmd/typeresolution/testdata/VarargsAsFixedArity.java new file mode 100644 index 00000000000..11df97d3dc4 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/typeresolution/testdata/VarargsAsFixedArity.java @@ -0,0 +1,23 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.typeresolution.testdata; + +public class VarargsAsFixedArity { + + public void tester() { + int var = aMethod(""); + String var2 = aMethod(); + String var3 = aMethod("", ""); + String var4 = aMethod(new Object[] { null }); + } + + public int aMethod(Object s) { + return 0; + } + + public String aMethod(Object... s) { + return null; + } +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/typeresolution/testdata/VarargsZeroArity.java b/pmd-java/src/test/java/net/sourceforge/pmd/typeresolution/testdata/VarargsZeroArity.java new file mode 100644 index 00000000000..835fde9bab8 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/typeresolution/testdata/VarargsZeroArity.java @@ -0,0 +1,21 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.typeresolution.testdata; + +public class VarargsZeroArity { + + public void tester() { + int var = aMethod(); + String var2 = aMethod(""); + } + + public int aMethod() { + return 0; + } + + public String aMethod(String... args) { + return null; + } +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/typeresolution/testdata/dummytypes/AbstractReturnType.java b/pmd-java/src/test/java/net/sourceforge/pmd/typeresolution/testdata/dummytypes/AbstractReturnType.java new file mode 100644 index 00000000000..c5da5b03672 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/typeresolution/testdata/dummytypes/AbstractReturnType.java @@ -0,0 +1,15 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.typeresolution.testdata.dummytypes; + +import java.util.ArrayList; +import java.util.List; + +public class AbstractReturnType { + + public List getList() { + return new ArrayList<>(); + } +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/typeresolution/testdata/dummytypes/GenericSuperType.java b/pmd-java/src/test/java/net/sourceforge/pmd/typeresolution/testdata/dummytypes/GenericSuperType.java new file mode 100644 index 00000000000..e884b6f8708 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/typeresolution/testdata/dummytypes/GenericSuperType.java @@ -0,0 +1,9 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.typeresolution.testdata.dummytypes; + +public class GenericSuperType { + +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/typeresolution/testdata/dummytypes/MyList.java b/pmd-java/src/test/java/net/sourceforge/pmd/typeresolution/testdata/dummytypes/MyList.java new file mode 100644 index 00000000000..f785d51c006 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/typeresolution/testdata/dummytypes/MyList.java @@ -0,0 +1,16 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.typeresolution.testdata.dummytypes; + +import java.util.List; + +// This is a stub for src/test/resources/net/sourceforge/pmd/lang/java/ast/jdkversiontests/java10/LocalVariableTypeInference_typeres.java +public class MyList { + + public void checkIterator(List other) { + //var oit = other.iterator(); + //oit.hasNext(); + } +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/typeresolution/testdata/dummytypes/OverloadedMethods.java b/pmd-java/src/test/java/net/sourceforge/pmd/typeresolution/testdata/dummytypes/OverloadedMethods.java new file mode 100644 index 00000000000..439952fb2c1 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/typeresolution/testdata/dummytypes/OverloadedMethods.java @@ -0,0 +1,16 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.typeresolution.testdata.dummytypes; + +public class OverloadedMethods { + + public static boolean equals(byte[] a, byte[] b) { + return false; + } + + public static boolean equals(Object[] a, Object[] b) { + return false; + } +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/typeresolution/testdata/dummytypes/ParametrizedSubType.java b/pmd-java/src/test/java/net/sourceforge/pmd/typeresolution/testdata/dummytypes/ParametrizedSubType.java new file mode 100644 index 00000000000..01d6ea971b3 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/typeresolution/testdata/dummytypes/ParametrizedSubType.java @@ -0,0 +1,9 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.typeresolution.testdata.dummytypes; + +public class ParametrizedSubType extends GenericSuperType { + +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/typeresolution/testdata/dummytypes/SubType.java b/pmd-java/src/test/java/net/sourceforge/pmd/typeresolution/testdata/dummytypes/SubType.java new file mode 100644 index 00000000000..5220059f04d --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/typeresolution/testdata/dummytypes/SubType.java @@ -0,0 +1,13 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.typeresolution.testdata.dummytypes; + +public class SubType extends SuperType { + + @Override + public void myMethod() { + super.myMethod(); + } +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/typeresolution/testdata/dummytypes/SuperType.java b/pmd-java/src/test/java/net/sourceforge/pmd/typeresolution/testdata/dummytypes/SuperType.java new file mode 100644 index 00000000000..e6200a63f87 --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/typeresolution/testdata/dummytypes/SuperType.java @@ -0,0 +1,12 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.typeresolution.testdata.dummytypes; + +public class SuperType { + + public void myMethod() { + // this will be overridden by a SubType + } +} diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/typeresolution/testdata/dummytypes/WildcardMethod.java b/pmd-java/src/test/java/net/sourceforge/pmd/typeresolution/testdata/dummytypes/WildcardMethod.java new file mode 100644 index 00000000000..275d06e632a --- /dev/null +++ b/pmd-java/src/test/java/net/sourceforge/pmd/typeresolution/testdata/dummytypes/WildcardMethod.java @@ -0,0 +1,12 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.typeresolution.testdata.dummytypes; + +public class WildcardMethod { + + public void useWildcard(GenericSuperType param) { + + } +} diff --git a/pmd-java/src/test/kotlin/net/sourceforge/pmd/lang/java/ast/ASTCatchStatementTest.kt b/pmd-java/src/test/kotlin/net/sourceforge/pmd/lang/java/ast/ASTCatchStatementTest.kt new file mode 100644 index 00000000000..5285c084bd3 --- /dev/null +++ b/pmd-java/src/test/kotlin/net/sourceforge/pmd/lang/java/ast/ASTCatchStatementTest.kt @@ -0,0 +1,75 @@ +package net.sourceforge.pmd.lang.java.ast + +import io.kotlintest.matchers.collections.shouldContainExactly +import io.kotlintest.should +import io.kotlintest.shouldBe +import io.kotlintest.specs.FunSpec +import net.sourceforge.pmd.lang.java.ast.JavaVersion.* +import net.sourceforge.pmd.lang.java.ast.JavaVersion.Companion.Earliest +import net.sourceforge.pmd.lang.java.ast.JavaVersion.Companion.Latest +import java.io.IOException + + +class ASTCatchStatementTest : FunSpec({ + + parserTest("Test crash on multicatch", javaVersions = Earliest..J1_6) { + + expectParseException("Cannot catch multiple exceptions when running in JDK inferior to 1.7 mode") { + parseAstStatement("try { } catch (IOException | AssertionError e) { }") + } + + } + + parserTest("Test single type", javaVersions = J1_5..Latest) { + + importedTypes += IOException::class.java + + "try { } catch (IOException ioe) { }" should matchStmt { + child { } + child { + it.isMulticatchStatement shouldBe false + + unspecifiedChildren(2) + } + } + + } + + parserTest("Test multicatch", javaVersions = J1_7..Latest) { + + importedTypes += IOException::class.java + + "try { } catch (IOException | AssertionError e) { }" should matchStmt { + child { } + child { + it.isMulticatchStatement shouldBe true + + val types = childRet> { + val ioe = child(ignoreChildren = true) { + it.type shouldBe IOException::class.java + } + + val aerr = child(ignoreChildren = true) { + it.type shouldBe java.lang.AssertionError::class.java + } + + child { + it.image shouldBe "e" + } + + return@childRet listOf(ioe, aerr) + } + + it.caughtExceptionTypeNodes.shouldContainExactly(types) + it.caughtExceptionTypes.shouldContainExactly(types.map { it.type }) + + it.exceptionName shouldBe "e" + + child { } + } + } + + } + + +}) diff --git a/pmd-java/src/test/kotlin/net/sourceforge/pmd/lang/java/ast/Java11Test.kt b/pmd-java/src/test/kotlin/net/sourceforge/pmd/lang/java/ast/Java11Test.kt new file mode 100644 index 00000000000..0c8b4f04015 --- /dev/null +++ b/pmd-java/src/test/kotlin/net/sourceforge/pmd/lang/java/ast/Java11Test.kt @@ -0,0 +1,93 @@ + +import io.kotlintest.should +import io.kotlintest.shouldBe +import io.kotlintest.specs.FunSpec +import net.sourceforge.pmd.lang.java.ast.* +import net.sourceforge.pmd.lang.java.ast.JavaVersion.* +import net.sourceforge.pmd.lang.java.ast.JavaVersion.Companion.Latest + +class Java11Test : FunSpec({ + + + parserTest("Test lambda parameter with var keyword", javaVersions = J1_8..J10) { + + "(var x) -> String.valueOf(x)" should matchExpr { + child { + child { + child { + it.typeImage shouldBe "var" + + child { + child { + it.image shouldBe "var" + } + } + } + + child { } + } + } + + unspecifiedChild() + } + + "(var x, var y) -> x + y" should matchExpr { + child { + child { + child { + it.typeImage shouldBe "var" + + child { + child { + it.image shouldBe "var" + } + } + } + child { } + } + + child { + child { + it.typeImage shouldBe "var" + + child { + child { + it.image shouldBe "var" + } + } + } + child { } + + } + } + + unspecifiedChild() + } + + "(@Nonnull var x) -> String.valueOf(x)" should matchExpr { + child { + child { + child(ignoreChildren = true) {} + child(ignoreChildren = true) {} + child {} + } + } + unspecifiedChild() + } + } + + parserTest("Test lambda parameter with var keyword", javaVersions = J11..Latest) { + + "(var x) -> String.valueOf(x)" should matchExpr { + child { + child { + it.isTypeInferred shouldBe true + child { } + } + } + + unspecifiedChild() + } + } + +}) \ No newline at end of file diff --git a/pmd-java/src/test/kotlin/net/sourceforge/pmd/lang/java/ast/KotlinTestingDsl.kt b/pmd-java/src/test/kotlin/net/sourceforge/pmd/lang/java/ast/KotlinTestingDsl.kt new file mode 100644 index 00000000000..a955c584e89 --- /dev/null +++ b/pmd-java/src/test/kotlin/net/sourceforge/pmd/lang/java/ast/KotlinTestingDsl.kt @@ -0,0 +1,370 @@ +package net.sourceforge.pmd.lang.java.ast + +import io.kotlintest.Matcher +import io.kotlintest.Result +import io.kotlintest.matchers.string.shouldContain +import io.kotlintest.shouldThrow +import io.kotlintest.specs.AbstractFunSpec +import net.sourceforge.pmd.lang.ast.Node +import net.sourceforge.pmd.lang.ast.test.* +import net.sourceforge.pmd.lang.java.ParserTstUtil +import io.kotlintest.should as kotlintestShould + + +/** + * Represents the different Java language versions. + */ +enum class JavaVersion : Comparable { + J1_3, J1_4, J1_5, J1_6, J1_7, J1_8, J9, J10, J11; + + /** Name suitable for use with e.g. [ParserTstUtil.parseAndTypeResolveJava] */ + val pmdName: String = name.removePrefix("J").replace('_', '.') + + /** + * Overloads the range operator, e.g. (`J9..J11`). + * If both operands are the same, a singleton list is returned. + */ + operator fun rangeTo(last: JavaVersion): List = + when { + last == this -> listOf(this) + last.ordinal > this.ordinal -> values().filter { ver -> ver >= this && ver <= last } + else -> values().filter { ver -> ver <= this && ver >= last } + } + + companion object { + val Latest = values().last() + val Earliest = values().first() + } +} + + +/** + * Specify several tests at once for different java versions. + * One test will be generated per version in [javaVersions]. + * Use [focusOn] to execute one test in isolation. + * + * @param name Name of the test. Will be postfixed by the specific + * java version used to run it + * @param javaVersions Language versions for which to generate tests + * @param focusOn Sets the java version of the test to isolate + * @param assertions Assertions and further configuration + * to perform with the parsing context + */ +fun AbstractFunSpec.parserTest(name: String, + javaVersions: List, + focusOn: JavaVersion? = null, + assertions: ParserTestCtx.() -> Unit) { + + javaVersions.forEach { + + val focus = if (focusOn != null && focusOn == it) "f:" else "" + + test("$focus$name (Java ${it.pmdName})") { + ParserTestCtx(it).assertions() + } + } +} + +/** + * Specify a new test for a single java version. To execute the test in isolation, + * prefix the name with `"f:"`. + * + * @param name Name of the test. Will be postfixed by the [javaVersion] + * @param javaVersion Language version to use when parsing + * @param assertions Assertions and further configuration + * to perform with the parsing context + */ +fun AbstractFunSpec.parserTest(name: String, + javaVersion: JavaVersion = JavaVersion.Latest, + assertions: ParserTestCtx.() -> Unit) { + parserTest(name, listOf(javaVersion), null, assertions) +} + + +/** + * Defines a group of tests that should be named similarly, + * executed on several java versions. Calls to "should" in + * the block are intercepted to create a new test, with the + * given [name] as a common prefix. + * + * This is useful to make a batch of grammar specs for grammar + * regression tests without bothering to find a name. + * + * @param name Common prefix for the test names + * @param javaVersions Language versions for which to generate tests + * @param spec Assertions. Each call to [io.kotlintest.should] on a string + * receiver is replaced by a [GroupTestCtx.should], which creates a + * new parser test. + */ +fun AbstractFunSpec.testGroup(name: String, + javaVersions: List, + spec: GroupTestCtx.() -> Unit) { + javaVersions.forEach { + testGroup(name, it, spec) + } +} + + +/** + * Defines a group of tests that should be named similarly. + * Calls to "should" in the block are intercepted to create + * a new test, with the given [name] as a common prefix. + * + * This is useful to make a batch of grammar specs for grammar + * regression tests without bothering to find a name. + * + * @param name Common prefix for the test names + * @param javaVersion Language versions to use when parsing + * @param spec Assertions. Each call to [io.kotlintest.should] on a string + * receiver is replaced by a [GroupTestCtx.should], which creates a + * new parser test. + * + */ +fun AbstractFunSpec.testGroup(name: String, + javaVersion: JavaVersion = JavaVersion.Latest, + spec: GroupTestCtx.() -> Unit) { + GroupTestCtx(this, name, javaVersion).spec() +} + +class GroupTestCtx(private val funspec: AbstractFunSpec, private val groupName: String, javaVersion: JavaVersion) : ParserTestCtx(javaVersion) { + + infix fun String.should(matcher: Matcher) { + funspec.parserTest("$groupName: '$this'") { + this@should kotlintestShould matcher + } + } + +} + + +/** + * Extensible environment to describe parse/match testing workflows in a concise way. + * Can be used inside of a [io.kotlintest.specs.FunSpec] with [parserTest]. + * + * Parsing contexts allow to parse a string containing only the node you're interested + * in instead of writing up a full class that the parser can handle. See [parseAstExpression], + * [parseAstStatement]. + * + * The methods [parseExpression] and [parseStatement] add some sugar to those by skipping + * some nodes we're not interested in to find the node of interest using their reified type + * parameter. + * + * These are implicitly used by [matchExpr] and [matchStmt], which specify a matcher directly + * on the strings, using their type parameter and the info in this test context to parse, find + * the node, and execute the matcher in a single call. These may be used by [io.kotlintest.should], + * e.g. + * + * parserTest("Test ShiftExpression operator") { + * "1 >> 2" should matchExpr(ignoreChildren = true) { + * it.operator shouldBe ">>" + * } + * } + * + * + * Import statements in the parsing contexts can be configured by adding types to [importedTypes], + * or strings to [otherImports]. + * + * Technically the utilities provided by this class may be used outside of [io.kotlintest.specs.FunSpec]s, + * e.g. in regular JUnit tests, but I think we should strive to uniformize our testing style, + * especially since KotlinTest defines so many. + * + * TODO allow to reference an existing type as the parsing context, for full type resolution + * + * @property javaVersion The java version that will be used for parsing. + * @property importedTypes Types to import at the beginning of parsing contexts + * @property otherImports Other imports, without the `import` and semicolon + */ +open class ParserTestCtx(val javaVersion: JavaVersion = JavaVersion.Latest, + val importedTypes: MutableList> = mutableListOf(), + val otherImports: MutableList = mutableListOf()) { + + /** Imports to add to the top of the parsing contexts. */ + internal val imports: List + get() { + val types = importedTypes.mapNotNull { it.canonicalName }.map { "import $it;" } + return types + otherImports.map { "import $it;" } + } + + inline fun makeMatcher(nodeParsingCtx: NodeParsingCtx<*>, ignoreChildren: Boolean, noinline nodeSpec: NWrapper.() -> Unit): Matcher = + object : Matcher { + override fun test(value: String): Result = + matchNode(ignoreChildren, nodeSpec).test(nodeParsingCtx.parseAndFind(value)) + } + + + /** + * Returns a String matcher that parses the node using [parseExpression] with + * type param [N], then matches it against the [nodeSpec] using [matchNode]. + * + */ + inline fun matchExpr(ignoreChildren: Boolean = false, + noinline nodeSpec: NWrapper.() -> Unit): Matcher = + makeMatcher(ExpressionParsingCtx(this), ignoreChildren, nodeSpec) + + /** + * Returns a String matcher that parses the node using [parseStatement] with + * type param [N], then matches it against the [nodeSpec] using [matchNode]. + */ + inline fun matchStmt(ignoreChildren: Boolean = false, + noinline nodeSpec: NWrapper.() -> Unit) = + makeMatcher(StatementParsingCtx(this), ignoreChildren, nodeSpec) + + + /** + * Returns a String matcher that parses the node using [parseType] with + * type param [N], then matches it against the [nodeSpec] using [matchNode]. + */ + inline fun matchType(ignoreChildren: Boolean = false, + noinline nodeSpec: NWrapper.() -> Unit) = + makeMatcher(TypeParsingCtx(this), ignoreChildren, nodeSpec) + + + /** + * Expect a parse exception to be thrown by [block]. + * The message is asserted to contain [messageContains]. + */ + fun expectParseException(messageContains: String, block: () -> Unit) { + + val thrown = shouldThrow(block) + + thrown.message.shouldContain(messageContains) + + } + + + fun parseAstExpression(expr: String): ASTExpression = ExpressionParsingCtx(this).parseNode(expr) + + + fun parseAstStatement(statement: String): ASTBlockStatement = StatementParsingCtx(this).parseNode(statement) + + fun parseAstType(type: String): ASTType = TypeParsingCtx(this).parseNode(type) + + + inline fun parseExpression(expr: String): N = ExpressionParsingCtx(this).parseAndFind(expr) + + // don't forget the semicolon + inline fun parseStatement(stmt: String): N = StatementParsingCtx(this).parseAndFind(stmt) + + inline fun parseType(type: String): N = TypeParsingCtx(this).parseAndFind(type) + + + companion object { + + + /** + * Finds the first descendant of type [N] of [this] node which is + * accessible in a straight line. The descendant must be accessible + * from the [this] on a path where each node has a single child. + * + * If one node has another child, the search is aborted and the method + * returns null. + */ + fun Node.findFirstNodeOnStraightLine(klass: Class): N? { + return when { + klass.isInstance(this) -> { + @Suppress("UNCHECKED_CAST") + val n = this as N + n + } + this.numChildren == 1 -> getChild(0).findFirstNodeOnStraightLine(klass) + else -> null + } + } + + /** + * Describes a kind of node that can be found commonly in the same contexts. + * This type defines some machinery to parse a string to this kind of node + * without much ado by placing it in a specific parsing context. + */ + abstract class NodeParsingCtx(val constructName: String, protected val ctx: ParserTestCtx) { + + abstract fun getTemplate(construct: String): String + + abstract fun retrieveNode(acu: ASTCompilationUnit): T + + /** + * Parse the string in the context described by this object. The parsed node is usually + * the child of the returned [T] node. Note that [parseAndFind] can save you some keystrokes + * because it finds a descendant of the wanted type. + * + * @param construct The construct to parse + * + * @return A [T] whose child is the given statement + * + * @throws ParseException If the argument is no valid construct of this kind (mind the language version) + */ + fun parseNode(construct: String): T { + val root = ParserTstUtil.parseAndTypeResolveJava(ctx.javaVersion.pmdName, getTemplate(construct)) + + return retrieveNode(root) + } + + /** + * Parse the string the context described by this object, and finds the first descendant of type [N]. + * The descendant is searched for by [findFirstNodeOnStraightLine], to prevent accidental + * mis-selection of a node. In such a case, a [NoSuchElementException] is thrown, and you + * should fix your test case. + * + * @param construct The construct to parse + * @param N The type of node to find + * + * @return The first descendant of type [N] found in the parsed expression + * + * @throws NoSuchElementException If no node of type [N] is found by [findFirstNodeOnStraightLine] + * @throws ParseException If the argument is no valid construct of this kind + * + */ + inline fun parseAndFind(construct: String): N = + parseNode(construct).findFirstNodeOnStraightLine(N::class.java) + ?: throw NoSuchElementException("No node of type ${N::class.java.simpleName} in the given $constructName:\n\t$construct") + + } + + + class ExpressionParsingCtx(ctx: ParserTestCtx) : NodeParsingCtx("expression", ctx) { + + override fun getTemplate(construct: String): String = + """ + ${ctx.imports.joinToString(separator = "\n")} + class Foo { + { + Object o = $construct; + } + } + """.trimIndent() + + + override fun retrieveNode(acu: ASTCompilationUnit): ASTExpression = acu.getFirstDescendantOfType(ASTVariableInitializer::class.java).getChild(0) as ASTExpression + } + + class StatementParsingCtx(ctx: ParserTestCtx) : NodeParsingCtx("statement", ctx) { + + override fun getTemplate(construct: String): String = + """ + ${ctx.imports.joinToString(separator = "\n")} + class Foo { + { + $construct + } + } + """.trimIndent() + + + override fun retrieveNode(acu: ASTCompilationUnit): ASTBlockStatement = acu.getFirstDescendantOfType(ASTBlockStatement::class.java) + } + + class TypeParsingCtx(ctx: ParserTestCtx) : NodeParsingCtx("type", ctx) { + override fun getTemplate(construct: String): String = + """ + ${ctx.imports.joinToString(separator = "\n")} + class Foo { + $construct foo; + } + """.trimIndent() + + override fun retrieveNode(acu: ASTCompilationUnit): ASTType = acu.getFirstDescendantOfType(ASTType::class.java) + } + + } +} + diff --git a/pmd-java/src/test/kotlin/net/sourceforge/pmd/lang/java/ast/WildcardBoundsTest.kt b/pmd-java/src/test/kotlin/net/sourceforge/pmd/lang/java/ast/WildcardBoundsTest.kt new file mode 100644 index 00000000000..f3a50d2fd70 --- /dev/null +++ b/pmd-java/src/test/kotlin/net/sourceforge/pmd/lang/java/ast/WildcardBoundsTest.kt @@ -0,0 +1,30 @@ +package net.sourceforge.pmd.lang.java.ast + +import io.kotlintest.should +import io.kotlintest.shouldBe +import io.kotlintest.specs.FunSpec + +// prototype using a junit syntax + +class WildcardBoundsTest : FunSpec({ + + parserTest("Simple grammar test") { + + "SomeClass" should matchType { + + val ref = child { + child { + it.image shouldBe "Another" + } + } + + it.typeBoundNode shouldBe ref + } + } + +}) + + + + + diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/ant/classpathtest/ruleset.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/ant/classpathtest/ruleset.xml index 2f7fdd02625..8b3efddecc1 100644 --- a/pmd-java/src/test/resources/net/sourceforge/pmd/ant/classpathtest/ruleset.xml +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/ant/classpathtest/ruleset.xml @@ -3,7 +3,7 @@ + xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd"> The Basic ruleset contains a collection of good practices which should be followed. @@ -51,7 +51,7 @@ public class JumbledIncrementerRule1 { language="java" since="0.4" message="Ensure you override both equals() and hashCode()" - class="net.sourceforge.pmd.lang.java.rule.basic.OverrideBothEqualsAndHashcodeRule" + class="net.sourceforge.pmd.lang.java.rule.errorprone.OverrideBothEqualsAndHashcodeRule" externalInfoUrl="${pmd.website.baseurl}/rules/java/basic.html#OverrideBothEqualsAndHashcode"> Override both public boolean Object.equals(Object other), and public int Object.hashCode(), or override neither. Even if you are inheriting a hashCode() from a parent class, consider implementing hashCode and explicitly delegating to your superclass. diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/ant/xml/custom_ruleset.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/ant/xml/custom_ruleset.xml index b4fdad77717..b9041833ff8 100644 --- a/pmd-java/src/test/resources/net/sourceforge/pmd/ant/xml/custom_ruleset.xml +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/ant/xml/custom_ruleset.xml @@ -3,12 +3,12 @@ + xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd"> Custom ruleset - + diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/ant/xml/pmdtasktest.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/ant/xml/pmdtasktest.xml index 857b9bd7c71..3eedc1b1b27 100644 --- a/pmd-java/src/test/resources/net/sourceforge/pmd/ant/xml/pmdtasktest.xml +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/ant/xml/pmdtasktest.xml @@ -6,8 +6,8 @@ - ${pmd.home}/src/main/resources/rulesets/java/codesize.xml - ${pmd.home}/src/main/resources/rulesets/java/design.xml + ${pmd.home}/src/main/resources/category/java/codestyle.xml + ${pmd.home}/src/main/resources/category/java/design.xml @@ -17,8 +17,8 @@ - ${pmd.home}/src/main/resources/rulesets/java/codesize.xml - ${pmd.home}/src/main/resources/rulesets/java/design.xml + ${pmd.home}/src/main/resources/category/java/codestyle.xml + ${pmd.home}/src/main/resources/category/java/design.xml @@ -30,7 +30,7 @@ - + @@ -40,8 +40,8 @@ - java-codesize - java-design + category/java/codestyle.xml + category/java/design.xml @@ -50,7 +50,7 @@ - + @@ -61,7 +61,7 @@ custom_ruleset.xml - rulesets/java/design.xml + category/java/codestyle.xml @@ -70,7 +70,7 @@ - + @@ -79,7 +79,7 @@ - + @@ -87,7 +87,7 @@ - + @@ -111,12 +111,11 @@ - java-strings - java-unusedcode + category/java/bestpractices.xml - + @@ -129,7 +128,7 @@ - java-unusedcode + category/java/bestpractices.xml 7 - '.StatementAndBraceFinder' has value 10 highest 6. - '.StatementAndBraceFinder#buildDataFlowFor(JavaNode)' has value 6. - '.StatementAndBraceFinder#tryToLog(String, NodeType, Node)' has value 4. - '.StatementAndBraceFinder#tryToLog(NodeType, Node)' has value 0. - '.StatementAndBraceFinder#visit(ASTStatementExpression, Object)' has value 4. - '.StatementAndBraceFinder#visit(ASTVariableDeclarator, Object)' has value 4. - '.StatementAndBraceFinder#visit(ASTExpression, Object)' has value 18. + 'StatementAndBraceFinder' has value 10 highest 6. + 'StatementAndBraceFinder#buildDataFlowFor(JavaNode)' has value 6. + 'StatementAndBraceFinder#tryToLog(String, NodeType, Node)' has value 4. + 'StatementAndBraceFinder#tryToLog(NodeType, Node)' has value 0. + 'StatementAndBraceFinder#visit(ASTStatementExpression, Object)' has value 4. + 'StatementAndBraceFinder#visit(ASTVariableDeclarator, Object)' has value 4. + 'StatementAndBraceFinder#visit(ASTExpression, Object)' has value 18. @@ -145,7 +145,7 @@ public class StatementAndBraceFinder extends JavaParserVisitorAdapter { false 1 - '.Foo#bar()' has value 1. + 'Foo#bar()' has value 1. 1 - '.Foo' has value 0 highest 0. + 'Foo' has value 0 highest 0. Full example - No options - 2 + 8 - '.Complicated#exception()' has value 4. - '.Complicated#example()' has value 23. + 'Complicated#exception()' has value 4. + 'Complicated#aSwitch(int)' has value 4. + 'Complicated#aDo(int, int)' has value 2. + 'Complicated#anAssert()' has value 1. + 'Complicated#aFor()' has value 2. + 'Complicated#aWhile()' has value 2. + 'Complicated#ternaries()' has value 4. + 'Complicated#booleans()' has value 10. @@ -82,10 +106,16 @@ Full example - Ignore boolean path option ignoreBooleanPaths - 2 + 8 - '.Complicated#exception()' has value 4. - '.Complicated#example()' has value 14. + 'Complicated#exception()' has value 4. + 'Complicated#aSwitch(int)' has value 3. + 'Complicated#aDo(int, int)' has value 2. + 'Complicated#anAssert()' has value 1. + 'Complicated#aFor()' has value 2. + 'Complicated#aWhile()' has value 2. + 'Complicated#ternaries()' has value 3. + 'Complicated#booleans()' has value 5. @@ -93,10 +123,16 @@ Full example - Consider assert option considerAssert - 2 + 8 - '.Complicated#exception()' has value 4. - '.Complicated#example()' has value 28. + 'Complicated#exception()' has value 4. + 'Complicated#aSwitch(int)' has value 4. + 'Complicated#aDo(int, int)' has value 2. + 'Complicated#anAssert()' has value 6. + 'Complicated#aFor()' has value 2. + 'Complicated#aWhile()' has value 2. + 'Complicated#ternaries()' has value 4. + 'Complicated#booleans()' has value 10. @@ -104,10 +140,16 @@ Full example - considerAssert + ignoreBooleanPaths ignoreBooleanPaths|considerAssert - 2 + 8 - '.Complicated#exception()' has value 4. - '.Complicated#example()' has value 18. + 'Complicated#exception()' has value 4. + 'Complicated#aSwitch(int)' has value 3. + 'Complicated#aDo(int, int)' has value 2. + 'Complicated#anAssert()' has value 5. + 'Complicated#aFor()' has value 2. + 'Complicated#aWhile()' has value 2. + 'Complicated#ternaries()' has value 3. + 'Complicated#booleans()' has value 5. @@ -117,7 +159,7 @@ false 1 - '.Foo#foo()' has value 1. + 'Foo#foo()' has value 1. false 1 - '.Test#Test()' has value 4. + 'Test#Test()' has value 4. @@ -177,7 +219,7 @@ 2 1 - '.Foo#foo()' has value 3. + 'Foo#foo()' has value 3. @@ -204,7 +246,7 @@ false 1 - '.Foo#foo()' has value 8. + 'Foo#foo()' has value 8. @@ -213,7 +255,7 @@ Ternary expression counts 1 + boolean complexity 1 - '.Foo#bar()' has value 3. + 'Foo#bar()' has value 3. + + Ternary expression adds a single code path, refs #1217 + 1 + + 'Foo#bar()' has value 2. + + + + + + Test consider assert option with ternary considerAssert 1 - '.Foo#bar()' has value 5. + 'Foo#bar()' has value 4. + + lambda1 = (message, nme) -> {// lambda$notSoComplex$0, total = 2 + if (binary) { // +1 + System.out.println(message); + } else { + System.out.println(nme); + } + }; + + BiConsumer lambda2 = (nme, message) -> {// lambda$notSoComplex$1, total = 8 + + if ((nme != 0) && (message != 0)) { // +2 + try { + System.out.println(nme); + } catch (IllegalArgumentException illegalArgumentException) { // +1 + throw new RuntimeException(); // +1 + } + + if (binary) { // +1 + System.out.println(nme); + } else { + System.out.println(nme); + + if (nme != 1) { // +1 + System.out.println(nme); + } + + for (int i = 0; i < message; i++) { // +1 + System.out.println(nme); + } + } + } + }; + } + } + ]]> + + + + Complexity of lambdas doesn't affect the complexity of the method, refs #837 + 3 + + 'LambdaTest#notSoComplex(int)' has value 4. + 'LambdaTest#lambda$notSoComplex$0' has value 2. + 'LambdaTest#lambda$notSoComplex$1' has value 8. + + + + + + Foreach loops count one, and add no boolean complexity + 1 + + 'Foo#foreach()' has value 2. + + + 0)) { + + } + } + } + ]]> + + + diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/metrics/impl/xml/LocTest.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/metrics/impl/xml/LocTest.xml index 2179da97d2d..2ccfcb54acc 100644 --- a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/metrics/impl/xml/LocTest.xml +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/metrics/impl/xml/LocTest.xml @@ -71,9 +71,9 @@ Full example 3 - '.Foo' has value 58 highest 40. - '.Foo#foo()' has value 1. - '.Foo#main(String)' has value 40. + 'Foo' has value 58 highest 40. + 'Foo#foo()' has value 1. + 'Foo#main(String)' has value 40. @@ -82,8 +82,8 @@ Empty method 2 - '.Foo' has value 5 highest 3. - '.Foo#foo()' has value 3. + 'Foo' has value 5 highest 3. + 'Foo#foo()' has value 3. Empty class 1 - '.Foo' has value 2 highest 0. + 'Foo' has value 2 highest 0. Switch 2 - '.Foo' has value 12 highest 10. - '.Foo#foo()' has value 10. + 'Foo' has value 12 highest 10. + 'Foo#foo()' has value 10. Full example 1 - '.Foo#bar()' has value 8064. + 'Foo#bar()' has value 8064. @@ -69,7 +69,7 @@ public class Foo { If with no else 1 - '.Foo#bar()' has value 2. + 'Foo#bar()' has value 2. Nested if with no else 1 - '.Foo#bar()' has value 3. + 'Foo#bar()' has value 3. Nested if with else 1 - '.Foo#bar()' has value 4. + 'Foo#bar()' has value 4. Two ifs 1 - '.Foo#bar()' has value 4. + 'Foo#bar()' has value 4. Two ifs one else 1 - '.Foo#bar()' has value 4. + 'Foo#bar()' has value 4. Two ifs and nested 1 - '.Foo#bar()' has value 6. + 'Foo#bar()' has value 6. Full switch 1 - '.Foo#bar()' has value 7. + 'Foo#bar()' has value 7. Ensure switch has same complexity as equivalent ifs 1 - '.Foo#bar()' has value 7. + 'Foo#bar()' has value 7. Boolean operators 1 - '.Foo#bar()' has value 4. + 'Foo#bar()' has value 4. Test case for bug 3484404 (Invalid NPath calculation in return statement) 3 - '.Bar#x(boolean, boolean)' has value 4. - '.Bar#y(boolean, boolean)' has value 4. - '.Bar#z(int, int)' has value 1. + 'Bar#x(boolean, boolean)' has value 4. + 'Bar#y(boolean, boolean)' has value 4. + 'Bar#z(int, int)' has value 1. @@ -288,7 +288,7 @@ class Bar { NPath supports constructors 1 - '.Foo#Foo()' has value 7. + 'Foo#Foo()' has value 7. NPath should count the boolean complexity of ternaries 1 - '.Test#method(Date)' has value 4. + 'Test#method(Date)' has value 4. #01 NPath should count ternaries like if - else constructs (https://stackoverflow.com/q/5079923/6245827) 1 - '.Test#method(Date)' has value 2. + 'Test#method(Date)' has value 2. #02 NPath should count ternaries like if - else constructs (https://stackoverflow.com/q/5079923/6245827) 1 - '.Test#method(Date)' has value 2. + 'Test#method(Date)' has value 2. 6 1 - '.SOFExample#usefulMethod(List)' has value 272. + 'SOFExample#usefulMethod(List)' has value 272. 6 1 - '.SOFExample#usefulMethod(List)' has value 272. + 'SOFExample#usefulMethod(List)' has value 272. + + + #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/metrics/impl/xml/NcssTest.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/metrics/impl/xml/NcssTest.xml index 2522c9f1bf1..4a9bae7faca 100644 --- a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/metrics/impl/xml/NcssTest.xml +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/metrics/impl/xml/NcssTest.xml @@ -157,7 +157,7 @@ Empty method 1 - '.Foo#foo()' has value 1. + 'Foo#foo()' has value 1. true 1 - '.Foo' has value 1 highest 0. + 'Foo' has value 1 highest 0. Switch 1 - '.Foo#foo()' has value 11. + 'Foo#foo()' has value 11. Multiple local variable declarations 1 - '.Foo#foo()' has value 2. + 'Foo#foo()' has value 2. First catch is not counted 1 - '.Foo#foo()' has value 3. + 'Foo#foo()' has value 3. Empty statements are not counted 1 - '.Boo#foo()' has value 2. + 'Boo#foo()' has value 2. Blocks are not counted 1 - '.Boo#foo()' has value 3. + 'Boo#foo()' has value 3. For initialisations are not counted 1 - '.Hoo#foo()' has value 3. + 'Hoo#foo()' has value 3. false 2 - '.Boo' has value 8 highest 2. - '.Boo$Barnabee' has value 5 highest 2. + 'Boo' has value 8 highest 2. + 'Boo$Barnabee' has value 5 highest 2. false 2 - '.Boo' has value 8 highest 2. - '.Boo$Barnabee' has value 5 highest 2. + 'Boo' has value 8 highest 2. + 'Boo$Barnabee' has value 5 highest 2. false 1 - '.Shoo' has value 1 highest 0. + 'Shoo' has value 1 highest 0. false 1 - '.Moo' has value 1 highest 0. + 'Moo' has value 1 highest 0. Abstract methods are counted 1 - '.Koo#bar()' has value 1. + 'Koo#bar()' has value 1. Full example 1 - '.Foo' has value 3. + 'Foo' has value 3. @@ -51,7 +51,7 @@ public class Foo { Test empty class 1 - '.Foo' has value 0. + 'Foo' has value 0. Full example 1 - '.Foo' has value 7. + 'Foo' has value 7. @@ -37,7 +37,7 @@ public class Foo { Test empty class 1 - '.Foo' has value 0. + 'Foo' has value 0. Full example 1 - '.Property' has value 0.0952. + 'Property' has value 0.0952. @@ -97,4 +97,99 @@ return _name.compareTo(((Property) o)._name); ]]> + + Do not crash on local class, refs #827 + 1 + + 'com.pack.Pack$1Inner' has value 0. + + get() { + + class Inner implements IInner { + + private Map results; + + public Inner(Map results) { + this.results = results; + } + + public void method() { + this.results = new HashMap(); + } + + private void otherMethod() { + this.results.clear(); + } + } + return null; + } + } + ]]> + + + + Attribute accesses in local class count as accesses of the method + 2 + + 'com.pack.Pack' has value 1. + 'com.pack.Pack$1Inner' has value 0. + + results; + + public void paired() { + results.clear(); + } + + @Override + public Map get() { + + class Inner implements IInner { + + + public Inner(Map results) { + this.results = results; + } + + public void method() { + this.results = new HashMap(); + } + + private void otherMethod() { + this.results.clear(); + } + } + return null; + } + } + ]]> + + + diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/metrics/impl/xml/WmcTest.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/metrics/impl/xml/WmcTest.xml index f6be6384937..03fcd25790e 100644 --- a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/metrics/impl/xml/WmcTest.xml +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/metrics/impl/xml/WmcTest.xml @@ -62,7 +62,7 @@ Complicated class 1 - '.Complicated' has value 20. + 'Complicated' has value 20. @@ -71,7 +71,7 @@ Empty classes count 0 1 - '.Foo' has value 0. + 'Foo' has value 0. Abstract classes and enums are supported 2 - '.Foo' has value 2. - '.Foo$Bar' has value 4. + 'Foo' has value 2. + 'Foo$Bar' has value 4. Full example 1 - '.Property' has value 0.1111. + 'Property' has value 0.1111. diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/design/xml/AbstractClassWithoutAbstractMethod.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/AbstractClassWithoutAbstractMethod.xml similarity index 100% rename from pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/design/xml/AbstractClassWithoutAbstractMethod.xml rename to pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/AbstractClassWithoutAbstractMethod.xml diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/design/xml/AccessorClassGeneration.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/AccessorClassGeneration.xml similarity index 75% rename from pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/design/xml/AccessorClassGeneration.xml rename to pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/AccessorClassGeneration.xml index 091b027d964..9bd3fd0345b 100644 --- a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/design/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/design/xml/AccessorMethodGeneration.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/AccessorMethodGeneration.xml similarity index 76% rename from pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/design/xml/AccessorMethodGeneration.xml rename to pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/AccessorMethodGeneration.xml index 17b12632f47..bf1b8fd638e 100644 --- a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/design/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/sunsecure/xml/ArrayIsStoredDirectly.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/ArrayIsStoredDirectly.xml similarity index 94% rename from pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/sunsecure/xml/ArrayIsStoredDirectly.xml rename to pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/ArrayIsStoredDirectly.xml index 59924ce0f64..417c19f9608 100644 --- a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/sunsecure/xml/ArrayIsStoredDirectly.xml +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/ArrayIsStoredDirectly.xml @@ -136,6 +136,18 @@ public class Foo { } ]]> + + + 1 + + #1063 False+: ArrayIsStoredDirectly diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/loggingjava/xml/AvoidPrintStackTrace.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/AvoidPrintStackTrace.xml similarity index 100% rename from pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/loggingjava/xml/AvoidPrintStackTrace.xml rename to pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/AvoidPrintStackTrace.xml diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/design/xml/AvoidReassigningParameters.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/AvoidReassigningParameters.xml similarity index 100% rename from pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/design/xml/AvoidReassigningParameters.xml rename to pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/AvoidReassigningParameters.xml diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/strings/xml/AvoidStringBufferField.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/AvoidStringBufferField.xml similarity index 100% rename from pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/strings/xml/AvoidStringBufferField.xml rename to pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/AvoidStringBufferField.xml diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/basic/xml/AvoidUsingHardCodedIP.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/AvoidUsingHardCodedIP.xml similarity index 96% rename from pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/basic/xml/AvoidUsingHardCodedIP.xml rename to pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/AvoidUsingHardCodedIP.xml index 703082a1197..bd8457e1d01 100644 --- a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/basic/xml/AvoidUsingHardCodedIP.xml +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/AvoidUsingHardCodedIP.xml @@ -23,6 +23,7 @@ public class Foo { "0", "0000000000000", "2001:0db8:0000:0000:0000:0000:1428:57ab:0000", + ":bee", // IPv4 "0.0.0.0", @@ -43,7 +44,8 @@ public class Foo { "::ffff:0c22:384e", "0:0:0:0:0:ffff:0c22:384e", "ff00::", - + "::bee", + // IPv4 mapped IPv6 "2001:0db8:0000:0000:0000:0000:12.34.56.78", "::ffff:12.34.56.78", @@ -120,7 +122,7 @@ public class Foo { - 20 + 21 @@ -144,7 +146,7 @@ Comprehensive, check for IPv4 Comprehensive, check for IPv6 ]]> IPv6 - 10 + 11 @@ -160,7 +162,7 @@ Comprehensive, check for IPv4 mapped IPv6 Comprehensive, check for IPv6 and IPv4 mapped IPv6 ]]> IPv6|IPv4 mapped IPv6 - 14 + 15 diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/basic/xml/CheckResultSet.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/CheckResultSet.xml similarity index 100% rename from pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/basic/xml/CheckResultSet.xml rename to pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/CheckResultSet.xml diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/design/xml/ConstantsInInterface.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/ConstantsInInterface.xml similarity index 100% rename from pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/design/xml/ConstantsInInterface.xml rename to pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/ConstantsInInterface.xml diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/design/xml/DefaultLabelNotLastInSwitchStmt.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/DefaultLabelNotLastInSwitchStmt.xml similarity index 100% rename from pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/design/xml/DefaultLabelNotLastInSwitchStmt.xml rename to pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/DefaultLabelNotLastInSwitchStmt.xml diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/ForLoopCanBeForeach.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/ForLoopCanBeForeach.xml new file mode 100644 index 00000000000..7327ec9ebc9 --- /dev/null +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/ForLoopCanBeForeach.xml @@ -0,0 +1,353 @@ + + + + + + Positive with list + 1 + l) { + for (int i = 0; i < l.size(); i++) { + System.out.println(l.get(i)); + } + } + } + ]]> + + + + Positive with lower or equal + 1 + lo) { + for (int i = 0; i <= lo.size() - 1; i++) { + System.out.println(lo.get(i)); + } + } + } + ]]> + + + + Usage of index var outside get + 0 + l) { + for (int i = 0; i < l.size(); i++) { + System.out.println(i + ": " + l.get(i)); + + } + } + } + ]]> + + + + Subclass of List + 1 + l) { + for (int i = 0; i < l.size(); i++) { + System.out.println(l.get(i)); + + } + } + } + ]]> + + + + Get called on another list + 0 + l) { + List l2 = new ArrayList<>(l); + for (int i = 0; i < l.size(); i++) { + System.out.println(l2.get(i)); + } + } + } + ]]> + + + + Backwards iteration + 0 + l) { + for (int i = l.size() - 1; i > 0; i-= 1) { + System.out.println(i + ": " + l.get(i)); + } + } + } + ]]> + + + + + Index var initialized outside for init + 1 + l) { + int i = 0; + for (; i < l.size(); i++) { + System.out.println(l.get(i)); + } + } + } + ]]> + + + + + Array positives + 2 + + + + + Consider iterators + 1 + path = null; + for (Iterator i = path.iterator(); i.hasNext();) { + DataFlowNode inode = i.next(); + if (inode.getVariableAccess() == null) { + continue; + } + } + } + } + ]]> + + + + + Index var starts after zero + 0 + l) { + for (int i = 1; i < filters.size(); i++) { + builder.append(' ').append(getOperator()).append(' '); + builder.append(filters.get(i)); + } + } + } + ]]> + + + + + Prefix increment should work + 1 + apexNode = (ApexNode) children[i]; + apexNode.jjtAccept(visitor, data); + } + } + return data; + } + } + ]]> + + + + Index inside arithmetic expression should whitelist the loop + 0 + + + + + + Array assignment whitelists the loop + 0 + + + + + Consider iterators only if safe + 0 + path = null; + + for (Iterator i = path.iterator(); i.hasNext();) { + DataFlowNode inode = i.next(); + path.remove(inode); // throws ConcurrentModificationException if it were a foreach + if (inode.getVariableAccess() == null) { + continue; + } + } + } + } + ]]> + + + + Do not report iterator loop if we can't find iterator decl + 0 + i = path.iterator(); i.hasNext();) { + DataFlowNode inode = i.next(); + if (inode.getVariableAccess() == null) { + continue; + } + } + } + } + ]]> + + + + Iterating on this object NPE, refs #800 + 0 + implements Iterable { + + @Override + public Iterator iterator() { + return null; + } + + private void fofo() { + for (Iterator it = this.iterator(); it.hasNext();) { + T item = it.next(); + } + } + } + ]]> + + + + Iterating on multiple iterators should whitelist the loop, refs #784 + 0 + it; + Iterable other; + for (Iterator iterator = it.iterator(), otherIterator = other.iterator(); iterator.hasNext();) { + E item = iterator.next(); + E otherItem = otherIterator.next(); + doStuff(); + } + } + } + ]]> + + + NPE when for init is there, but not a local var declaration, refs #884 + 0 + list = parent.findDescendantsOfType(ASTCatchStatement.class); + // ... + } + return data; + } + + } + ]]> + + + + NPE when using instance fields with this + 0 + stringList; + + this.hashes = new int[stringList.size()]; + for (int i = 0; i < stringList.size(); i++) { + this.hashes[i] = stringList.get(i).hashCode(); + } + } +} + ]]> + + diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/GuardLogStatement.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/GuardLogStatement.xml new file mode 100644 index 00000000000..706575dd35a --- /dev/null +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/GuardLogStatement.xml @@ -0,0 +1,362 @@ + + + + OK, guard is here - log4j + 0 + + + + ok, no error expected - apache commons logging + 0 + + + + Guarded call - OK - java util + 0 + + + + KO, missing guard 1 - log4j + 1 + + + + KO, missing guard 2 - log4j + debug,trace + isDebugEnabled,isTraceEnabled + 1 + + + + Complex logging without guard - apache commons logging + 2 + + + + Complex logging with misplaced guard - apache commons logging + 1 + + + + Unguarded call - KO - java util + 1 + + + + ok #1189 GuardLogStatementRule and GuardDebugLoggingRule broken for log4j2 + 0 + + + + violation - wrong guard #1189 GuardLogStatementRule and GuardDebugLoggingRule broken for log4j2 + 2 + + + + violation - no if #1189 GuardLogStatementRule and GuardDebugLoggingRule broken for log4j2 + 1 + + + + #1224 GuardDebugLogging broken in 5.1.1 - missing additive statement check in log statement - apache commons logging + 0 + + + + #1341 pmd:GuardDebugLogging violates LOGGER.debug with format "{}" - slf4j + 0 + + + + #1203 GuardLogStatementJavaUtil issues warning for severe level not being specified as property + finest,finer,fine,info + isLoggable + 0 + + + + #1227 GuardLogStatementJavaUtil doesn't catch log(Level.FINE, "msg" + " msg") calls + 1 + 8 + + + + #1347 False positive for GuardLogStatementJavaUtil with slf4j + 0 + + + + #1398 False positive for GuardLogStatementJavaUtil with Log4j + 0 + + + + #783 [java] GuardLogStatement regression + 0 + + + + Wrong guard method for java util logging + 1 + + + + #370 rule not considering lambdas + 0 + "Bla " + " bla"); // The lambda is free to do whatever it likes + } + + @Override + public void info(Supplier message) { + if (logger.isInfoEnabled()) { + logger.info(message.get()); + } + } +} + ]]> + + + + #869 [java] GuardLogStatement false positive with Math.log + 0 + + + + + #869 [java] GuardLogStatement false positive with something.error on return + 0 + + + diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/migrating/xml/JUnit4SuitesShouldUseSuiteAnnotation.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/JUnit4SuitesShouldUseSuiteAnnotation.xml similarity index 100% rename from pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/migrating/xml/JUnit4SuitesShouldUseSuiteAnnotation.xml rename to pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/JUnit4SuitesShouldUseSuiteAnnotation.xml 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 new file mode 100644 index 00000000000..e0854a0c665 --- /dev/null +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/JUnit4TestShouldUseAfterAnnotation.xml @@ -0,0 +1,95 @@ + + + + + 1 + + + + + 0 + + + + + 0 + + + + #1446 False positive with JUnit4TestShouldUseBeforeAnnotation when TestNG is used + 0 + + + + #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 new file mode 100644 index 00000000000..b9243c2bbec --- /dev/null +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/JUnit4TestShouldUseBeforeAnnotation.xml @@ -0,0 +1,107 @@ + + + + + 1 + + + + + 0 + + + + + 0 + + + + #1400 False positive with JUnit4TestShouldUseBeforeAnnotation + 0 + + + + #1446 False positive with JUnit4TestShouldUseBeforeAnnotation when TestNG is used + 0 + + + + #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 new file mode 100644 index 00000000000..7385b233bf4 --- /dev/null +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/JUnit4TestShouldUseTestAnnotation.xml @@ -0,0 +1,187 @@ + + + + + 1 + + + + + 0 + + + + + 0 + + + + + 1 + + + + + 2 + + + + #1197 JUnit4TestShouldUseTestAnnotation for private method + 0 + + + + #572 [java] False Alarm of JUnit4TestShouldUseTestAnnotation on Predicates + 0 + implements Predicate { + @Override + public boolean test(T t) { + return false; + } +} + ]]> + + + Test class with test method not annotated + 1 + + + + Test class named TestCase with test method not annotated + 1 + + + + Test class prefix with Test with test method not annotated + 1 + + + + Test class named MyTests with test method not annotated + 1 + + + + #940 False positives with JUnit4TestShouldUseTestAnnotation when JUnit5 is used + 0 + + + diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/junit/xml/JUnitAssertionsShouldIncludeMessage.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/JUnitAssertionsShouldIncludeMessage.xml similarity index 95% rename from pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/junit/xml/JUnitAssertionsShouldIncludeMessage.xml rename to pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/JUnitAssertionsShouldIncludeMessage.xml index dde7f72f487..75fe4f1fb4a 100644 --- a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/junit/xml/JUnitAssertionsShouldIncludeMessage.xml +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/JUnitAssertionsShouldIncludeMessage.xml @@ -426,4 +426,21 @@ public class TestNgTestCase { } ]]> + + +Issue #1009: JUnitAssertionsShouldIncludeMessage - False positive with assertEquals and JUnit5 + + 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 new file mode 100644 index 00000000000..1701f8bd70e --- /dev/null +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/JUnitTestContainsTooManyAsserts.xml @@ -0,0 +1,206 @@ + + + + + 0 + + + + + 0 + + + + + 0 + + + + + 0 + + + + + 1 + + + + + 1 + + + + + 2 + 0 + + + + + 2 + 0 + + + + + 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/junit/xml/JUnitTestsShouldIncludeAssert.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/JUnitTestsShouldIncludeAssert.xml similarity index 96% rename from pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/junit/xml/JUnitTestsShouldIncludeAssert.xml rename to pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/JUnitTestsShouldIncludeAssert.xml index bf0d7e05151..b69044eff31 100644 --- a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/junit/xml/JUnitTestsShouldIncludeAssert.xml +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/JUnitTestsShouldIncludeAssert.xml @@ -415,6 +415,20 @@ public class FooTest { assertEquals("doesn't matter", "doesn't matter"); } +}]]> + + + #1365 detect test classes marked with fully qualified annotation. + 0 + diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/migrating/xml/JUnitUseExpected.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/JUnitUseExpected.xml similarity index 100% rename from pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/migrating/xml/JUnitUseExpected.xml rename to pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/JUnitUseExpected.xml diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/LooseCoupling.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/LooseCoupling.xml new file mode 100644 index 00000000000..513f46a7d29 --- /dev/null +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/LooseCoupling.xml @@ -0,0 +1,151 @@ + + + + + 1 + + + + + 0 + + + + + 0 + + + + + 0 + + + + + 1 + + + + + 2 + + + + + 2 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + #938 False positive on LooseCoupling for overriding methods + 0 + + + diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/sunsecure/xml/MethodReturnsInternalArray.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/MethodReturnsInternalArray.xml similarity index 100% rename from pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/sunsecure/xml/MethodReturnsInternalArray.xml rename to pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/MethodReturnsInternalArray.xml diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/MissingOverride.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/MissingOverride.xml new file mode 100644 index 00000000000..d828bba4d1c --- /dev/null +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/MissingOverride.xml @@ -0,0 +1,532 @@ + + + + Missing override on method from interface + 1 + + + + Override present on method from interface + 0 + + + + + Override absent in method from superclass + 1 + + + + + Override present in method from superclass + 0 + + + + + Override present in method from interface transitively + 0 + + + + + Override absent in method from interface transitively + 1 + + + + + Consider anonymous classes + 1 + + The method 'run()' is missing an @Override annotation. + + + + + + Consider anonymous classes + 0 + + + + + Consider enum methods + 0 + { + Foo { + @Override + public Metric getCalculator() { + return null; + } + }; + + @Override + public Metric getCalculator() { + return null; + } + + + @Override + public boolean supports(ASTAnyTypeDeclaration node) { + return false; + } + } + ]]> + + + + Consider enum methods + 2 + 9,15 + { + Foo { + public Metric getCalculator() { + return null; + } + }; + + + public Metric getCalculator() { + return null; + } + } + ]]> + + + + Consider methods with array parameters + 1 + + The method 'arrayParams(String, int, StringBuilder)' is missing an @Override annotation. + + + + + + Consider enum anon class + 1 + 10 + + + + + Consider method inherited from generic supertype + 1 + + The method 'supports(ASTAnyTypeDeclaration)' is missing an @Override annotation. + + { + Foo; + + // missing + // this is determined from the bridge method, but only works if there are no overloads + public boolean supports(ASTAnyTypeDeclaration node) { + return false; + } + } + ]]> + + + + + Consider method inherited from generic supertype with overloads + 2 + + The method 'visit(ASTCompilationUnit, String)' is missing an @Override annotation. + The method 'visit(ASTPackageDeclaration, String)' is missing an @Override annotation. + + { + + // a bridge method is generated for each of these + + + // missing + public String visit(ASTCompilationUnit node, String data) { + return null; + } + + + // missing + public String visit(ASTPackageDeclaration node, String data) { + return null; + } + } + ]]> + + + + Consider generic method + 1 + + The method 'generic(P, Q)' is missing an @Override annotation. + + + Q generic(P t, Q r) { // generic param names are different from superclass + return super.generic(t, r); + } + } + ]]> + + + + + + Consider varargs parameter + 1 + + The method 'setProperty(MultiValuePropertyDescriptor, V...)' is missing an @Override annotation. + + + void setProperty(MultiValuePropertyDescriptor propertyDescriptor, V... values) { + rule.setProperty(propertyDescriptor, values); + } + + } + ]]> + + + + + Consider Object methods inherited into interfaces + 1 + + The method 'toString()' is missing an @Override annotation. + + + + + + + + + + Consider covariant return types + 1 + + The method 'fun(String)' is missing an @Override annotation. + + + + + + + + Avoid false positives when in ambiguous situation + 0 + + { + + // only one of those overloads is an override, and so there's only one bridge, + // so we can't choose the inherited overload + + // missing + public int compare(StringBuilder o1, StringBuilder o2) { + return 0; + } + + public int compare(String s, String s2) { + return 0; + } + } + ]]> + + + + + Avoid false positives when in ambiguous situation + 0 + + { + + abstract void foo(T node); + + public static abstract class SubclassOne extends HierarchyWithSeveralBridges { + + // this one could be resolved + // @Override + // abstract void foo(T node); + + } + + public static abstract class SubclassTwo extends SubclassOne { + + } + + + public static class Concrete extends SubclassTwo { + + // bridges: foo(AbstractJavaTypeNode), foo(JavaNode), foo(Node) + + // missing + void foo(ASTType node) { + + } + } + } + ]]> + + + + + Protected methods of Object redefined in an interface cannot have @Override + 0 + + + + + + + Overriding of method from interface needs @Override, even if it's a protected Object method + 1 + + The method 'clone()' is missing an @Override annotation. + + + + + + + + Private methods can't be overridden + 0 + + + + + + + Static methods can't be overridden + 0 + + + + + + + \ No newline at end of file diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/OneDeclarationPerLine.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/OneDeclarationPerLine.xml new file mode 100644 index 00000000000..39768c62330 --- /dev/null +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/OneDeclarationPerLine.xml @@ -0,0 +1,92 @@ + + + + + 1 + + + + + + + 0 + + + + + + #1221 OneDeclarationPerLine really checks for one declaration each statement + false + 0 + + + + #1221 OneDeclarationPerLine really checks for one declaration each statement + true + 1 + + + + + Check for field declarations without strictMode + false + 0 + + + + + Check for field declarations with strictMode + true + 1 + + + diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/design/xml/PositionLiteralsFirstInCaseInsensitiveComparisons.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/PositionLiteralsFirstInCaseInsensitiveComparisons.xml similarity index 100% rename from pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/design/xml/PositionLiteralsFirstInCaseInsensitiveComparisons.xml rename to pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/PositionLiteralsFirstInCaseInsensitiveComparisons.xml diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/design/xml/PositionLiteralsFirstInComparisons.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/PositionLiteralsFirstInComparisons.xml similarity index 100% rename from pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/design/xml/PositionLiteralsFirstInComparisons.xml rename to pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/PositionLiteralsFirstInComparisons.xml diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/design/xml/PreserveStackTrace.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/PreserveStackTrace.xml similarity index 95% rename from pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/design/xml/PreserveStackTrace.xml rename to pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/PreserveStackTrace.xml index 5d207ecc1ed..1900c107863 100644 --- a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/design/xml/PreserveStackTrace.xml +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/PreserveStackTrace.xml @@ -539,4 +539,24 @@ public class Bug { } ]]> + + + #543 False negative with String concatenation + 3 + + diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/migrating/xml/ReplaceEnumerationWithIterator.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/ReplaceEnumerationWithIterator.xml similarity index 100% rename from pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/migrating/xml/ReplaceEnumerationWithIterator.xml rename to pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/ReplaceEnumerationWithIterator.xml diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/migrating/xml/ReplaceHashtableWithMap.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/ReplaceHashtableWithMap.xml similarity index 100% rename from pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/migrating/xml/ReplaceHashtableWithMap.xml rename to pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/ReplaceHashtableWithMap.xml diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/migrating/xml/ReplaceVectorWithList.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/ReplaceVectorWithList.xml similarity index 100% rename from pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/migrating/xml/ReplaceVectorWithList.xml rename to pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/ReplaceVectorWithList.xml diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/SwitchStmtsShouldHaveDefault.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/SwitchStmtsShouldHaveDefault.xml new file mode 100644 index 00000000000..859de1da168 --- /dev/null +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/SwitchStmtsShouldHaveDefault.xml @@ -0,0 +1,83 @@ + + + + simple failure case + 1 + + + + + simple ok case + 0 + + + + + #651 Enum type, not ok + 1 + + + + + #651 Enum type, ok + 0 + + + diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/loggingjava/xml/SystemPrintln.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/SystemPrintln.xml similarity index 100% rename from pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/loggingjava/xml/SystemPrintln.xml rename to pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/SystemPrintln.xml diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/unusedcode/xml/UnusedFormalParameter.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/UnusedFormalParameter.xml similarity index 95% rename from pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/unusedcode/xml/UnusedFormalParameter.xml rename to pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/UnusedFormalParameter.xml index 8c439d6e1c5..ba281bbf8a2 100644 --- a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/unusedcode/xml/UnusedFormalParameter.xml +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/UnusedFormalParameter.xml @@ -16,6 +16,18 @@ class Foo { + 0 + + + + 0 @@ -224,7 +236,7 @@ class Foo { violation suppression xpath works, by type ]]> - .[typeof('java.lang.String')] + .[typeIs('java.lang.String')] 0 + + + + 1 + + + + + 0 + + + + + 2 + + + + + 0 + + + + + 0 + + + + + 0 + + java 1.5 + + + + 0 + x = new ArrayList(); +} + ]]> + java 1.5 + + + + 0 + x = new ArrayList(); +} + ]]> + java 1.5 + + + + 0 + + java 1.5 + + + + 0 + + java 1.5 + + + + 0 + + + + + 1 + + + + + 0 + + java 1.5 + + + + 1 + + java 1.5 + + + + 0 + + + + + 0 + + + + #1280 False Positive in UnusedImports when import used in javadoc + 0 + + + + + + 0 + + + + + bug #254 False+ : UnusedImport with Javadoc @link + 0 + + + + #1181 unused import false positive if used as parameter in javadoc only. + 0 + + + + #1280 False Positive in UnusedImports when import used in javadoc + 0 + + + + #914 False +ve from UnusedImports with wildcard static imports + 0 + + + + + #1465 False Positve UnusedImports with javadoc @link + 0 + + * An agent is active if it has not posted a {@link AgentStateChangeEvent} containing {@link AgentState#TERMINATED}. + * + * @return agent handles. + * @see OtherState#TERMINATED + */ + Iterable getAgentHandles(); +} + ]]> + + + + #1547 False Positve UnusedImports with javadoc for identifiers with underscores + 0 + + + + + #348 False Positive UnusedImports with javadoc for public static inner classes of imports + 0 + + + + + #925 [java] UnusedImports false positive for static import + 0 + + + + + #1404 [java] UnusedImports false positive for static import + 0 + + + + + #1209 [java] UnusedImports false positive for static import with package-private method usage + 0 + + + diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/unusedcode/xml/UnusedLocalVariable.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/UnusedLocalVariable.xml similarity index 100% rename from pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/unusedcode/xml/UnusedLocalVariable.xml rename to pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/UnusedLocalVariable.xml diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/unusedcode/xml/UnusedPrivateField.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/UnusedPrivateField.xml similarity index 92% rename from pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/unusedcode/xml/UnusedPrivateField.xml rename to pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/UnusedPrivateField.xml index 549b62bfc21..3124ab44b16 100644 --- a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/unusedcode/xml/UnusedPrivateField.xml +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/UnusedPrivateField.xml @@ -531,7 +531,7 @@ public class Foo { #1420 UnusedPrivateField: Ignore fields if using lombok - 7 - 1 + 0 + + #907 UnusedPrivateField false-positive with @FXML - 1 + 0 + + + + + #907 UnusedPrivateField false-positive with @FXML - 2 + 0 + + + + + #907 UnusedPrivateField false-positive with @FXML - 3 + javafx.fxml.FXML + 1 + + + #1428 False positive in UnusedPrivateField when local variable hides member variable 0 diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/unusedcode/xml/UnusedPrivateMethod.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/UnusedPrivateMethod.xml similarity index 96% rename from pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/unusedcode/xml/UnusedPrivateMethod.xml rename to pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/UnusedPrivateMethod.xml index 9b4ee70c46a..2bc869b8d79 100644 --- a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/unusedcode/xml/UnusedPrivateMethod.xml +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/UnusedPrivateMethod.xml @@ -580,7 +580,7 @@ public class Foo extends MultiActionController { #1228 UnusedPrivateMethod returns false positives (5b) 0 + + #907 Add IgnoredAnnotations property to UnusedPrivateMethod + 0 + + + + + 0 + + + + + Verify property ignoredAnnotations is used + java.lang.Override + 0 + + + + + Verify property ignoredAnnotations is used - 2 + + 1 + + diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/junit/xml/UseAssertEqualsInsteadOfAssertTrue.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/UseAssertEqualsInsteadOfAssertTrue.xml similarity index 82% rename from pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/junit/xml/UseAssertEqualsInsteadOfAssertTrue.xml rename to pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/bestpractices/xml/UseAssertEqualsInsteadOfAssertTrue.xml index da8e4054072..82d90cfc3cb 100644 --- a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/junit/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 + - - - - 0 - - - - - 1 - - - - - 0 - - - - - 0 - - - - - 0 - - - - - 0 - - - - - 0 - - - - - #1534 [java] CloneMethodMustImplementCloneable: ClassCastException with Annotation (java8) - 0 - implements @Readonly List<@Readonly T> {} - ]]> - - - - #1532 [java] CloneMethodMustImplementCloneable: Implemented Interface extends Cloneable - part 1: interface - 0 - - - diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/codesize/xml/CyclomaticComplexity.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/codesize/xml/CyclomaticComplexity.xml deleted file mode 100644 index 02a266529d8..00000000000 --- a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/codesize/xml/CyclomaticComplexity.xml +++ /dev/null @@ -1,189 +0,0 @@ - - - - - - - - Simple method - 1 - 2 - - The class 'Foo' has a Cyclomatic Complexity of 2 (Highest = 1). - The method 'foo' has a Cyclomatic Complexity of 1. - - - - - testLessComplicatedThanReportLevel - 10 - 0 - - - - Complicated method - 10 - 2 - - The class 'Foo' has a Cyclomatic Complexity of 12 (Highest = 11). - The method 'example' has a Cyclomatic Complexity of 11. - - - - - Constructor - 1 - 2 - - The class 'Foo' has a Cyclomatic Complexity of 2 (Highest = 1). - The constructor 'Foo' has a Cyclomatic Complexity of 1. - - - - - - - - false - 1 - - - - - - - false - 1 - - - - - - - 2 - - - - - #984 Cyclomatic complexity should treat constructors like methods: 1 - showMethodsComplexity=true - false - true - 1 - 1 - - - - #984 Cyclomatic complexity should treat constructors like methods: 2 - showMethodsComplexity=false - false - false - 1 - 0 - - - - #985 Suppressed methods shouldn't affect avg CyclomaticComplexity - 2 - 0 - - - diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/codesize/xml/ExcessiveClassLength.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/codesize/xml/ExcessiveClassLength.xml deleted file mode 100644 index 4ac9a7d81a0..00000000000 --- a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/codesize/xml/ExcessiveClassLength.xml +++ /dev/null @@ -1,52 +0,0 @@ - - - - - 10 - 0 - - - - - - 10 - 1 - - - - - 2000 - 0 - - - diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/codesize/xml/NPathComplexity.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/codesize/xml/NPathComplexity.xml deleted file mode 100644 index a3c5a14c24d..00000000000 --- a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/codesize/xml/NPathComplexity.xml +++ /dev/null @@ -1,98 +0,0 @@ - - - - ok - 0 - - - - fail, with minimum - 1.0 - 1 - - The method bar() has an NPath complexity of 2 - - - - - - 1 - - - - - test case for bug 3484404 (Invalid NPath calculation in return statement) - 0 - - - - test case for bug 3484404 (Invalid NPath calculation in return statement) with minimum 25 - 25.0 - 2 - - The method x() has an NPath complexity of 25 - The method y() has an NPath complexity of 25 - - - - diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/naming/xml/AbstractNaming.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/codestyle/xml/AbstractNaming.xml similarity index 100% rename from pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/naming/xml/AbstractNaming.xml rename to pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/codestyle/xml/AbstractNaming.xml diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/codestyle/xml/AtLeastOneConstructor.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/codestyle/xml/AtLeastOneConstructor.xml new file mode 100644 index 00000000000..fe3fab3c725 --- /dev/null +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/codestyle/xml/AtLeastOneConstructor.xml @@ -0,0 +1,150 @@ + + + + + 0 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 2 + + + + + 0 + + + + + 0 + + + + + 0 + + + + + 0 + + + + Don't skip classes with non-static methods; #1216 AtLeastOneConstructor ignores classes with *any* methods + 1 + + + + Ignore classes with lombok-generated constructors + 0 + + + + Report classes with at least one non-static method + 1 + + + + Report classes with at least one non-static field + 1 + + + diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/naming/xml/AvoidDollarSigns.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/codestyle/xml/AvoidDollarSigns.xml similarity index 100% rename from pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/naming/xml/AvoidDollarSigns.xml rename to pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/codestyle/xml/AvoidDollarSigns.xml diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/controversial/xml/AvoidFinalLocalVariable.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/codestyle/xml/AvoidFinalLocalVariable.xml similarity index 100% rename from pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/controversial/xml/AvoidFinalLocalVariable.xml rename to pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/codestyle/xml/AvoidFinalLocalVariable.xml diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/controversial/xml/AvoidPrefixingMethodParameters.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/codestyle/xml/AvoidPrefixingMethodParameters.xml similarity index 100% rename from pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/controversial/xml/AvoidPrefixingMethodParameters.xml rename to pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/codestyle/xml/AvoidPrefixingMethodParameters.xml diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/design/xml/AvoidProtectedFieldInFinalClass.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/codestyle/xml/AvoidProtectedFieldInFinalClass.xml similarity index 100% rename from pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/design/xml/AvoidProtectedFieldInFinalClass.xml rename to pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/codestyle/xml/AvoidProtectedFieldInFinalClass.xml diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/design/xml/AvoidProtectedMethodInFinalClassNotExtending.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/codestyle/xml/AvoidProtectedMethodInFinalClassNotExtending.xml similarity index 100% rename from pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/design/xml/AvoidProtectedMethodInFinalClassNotExtending.xml rename to pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/codestyle/xml/AvoidProtectedMethodInFinalClassNotExtending.xml diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/controversial/xml/AvoidUsingNativeCode.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/codestyle/xml/AvoidUsingNativeCode.xml similarity index 100% rename from pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/controversial/xml/AvoidUsingNativeCode.xml rename to pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/codestyle/xml/AvoidUsingNativeCode.xml diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/naming/xml/BooleanGetMethodName.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/codestyle/xml/BooleanGetMethodName.xml similarity index 100% rename from pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/naming/xml/BooleanGetMethodName.xml rename to pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/codestyle/xml/BooleanGetMethodName.xml diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/controversial/xml/CallSuperInConstructor.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/codestyle/xml/CallSuperInConstructor.xml similarity index 100% rename from pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/controversial/xml/CallSuperInConstructor.xml rename to pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/codestyle/xml/CallSuperInConstructor.xml diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/codestyle/xml/ClassNamingConventions.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/codestyle/xml/ClassNamingConventions.xml new file mode 100644 index 00000000000..0248ab34b9c --- /dev/null +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/codestyle/xml/ClassNamingConventions.xml @@ -0,0 +1,258 @@ + + + + + Default config reports on every type decl + 5 + + + + + This is ok + 0 + + + + + Control annotation convention + [a-z][A-Za-z]* + 1 + + The class name 'foo' doesn't match '[A-Z][a-zA-Z0-9]*' + + + + + + Utility class convention + 1 + + The utility class name 'Foo' doesn't match '[A-Z][a-zA-Z0-9]+(Utils?|Helper)' + + + + + + Class with only empty decls should not be a utility class + 0 + + + + + Class with some instance fields should not be a utility class + 0 + + + + + Class with static initializer alone should not be a utility class + 0 + + + + + Class with instance initializer should not be a utility class + 0 + + + + + Class with only static members except constructors should be a utility class + 1 + + The utility class name 'Foo' doesn't match '[A-Z][a-zA-Z0-9]+(Utils?|Helper)' + + + + + + Class with only constructors should not be a utility class + 0 + + + + + Numbers should be allowed by default + 0 + + + + + Class extending another class should not be utility class + 0 + { + static StringList emptyList() { + return new StringList(); + } + } + ]]> + + + + Class extending another class should not be utility class 2 + 0 + + + + + Class with only main method should not be utility class + 0 + + + + + Class with only main method should not be utility class - varargs case + 0 + + + + + Class with main method and private static fields should not be utility class + 0 + + + + diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/codestyle/xml/CommentDefaultAccessModifier.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/codestyle/xml/CommentDefaultAccessModifier.xml new file mode 100755 index 00000000000..2a8874ff8ec --- /dev/null +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/codestyle/xml/CommentDefaultAccessModifier.xml @@ -0,0 +1,257 @@ + + + + + + Some methods and Fields with default access modifier in a class + 2 + 3,10 + + + + + + + All methods and Field with default access modifier in a class + 6 + 2,3,4,6,9,12 + + + + + + + All methods and Field without default access modifier in a class + 0 + + + + + + Methods with default access modifier in an Interface + 0 + + + + + + Nested classes with default access modifier + 2 + 7,10 + + + + + + Test own regex to default access modifier rule + \/\*\s+package-private\s+\*\/ + 3 + 2,4,13 + + + + + Declarations inside an enum + 0 + + + + + #1430 CommentDefaultAccessModifier triggers on field annotated with @VisibleForTesting + 0 + + + + + #1430 CommentDefaultAccessModifier triggers on field annotated with @VisibleForTesting 2 + 0 + + + + + #536 Constructor with default access modifier should trigger + 1 + + + + + #536 Enum constructor with implicit private modifier should not trigger + 0 + + + + + Nested Enum constructor with implicit private modifier should not trigger + 0 + + + + + Nested annotation method with default access modifier should not trigger + 0 + + + + + #1211 [java] CommentDefaultAccessModifier false positive with nested interface + 0 + + + + + Use property ignoredAnnotations (see #1343) + net.sourceforge.pmd.lang.java.rule.codestyle.commentdefaultaccessmodifier.OnlyForTesting + 1 + 6 + + To avoid mistakes add a comment at the beginning of the method method if you want a default access modifier + + + + \ No newline at end of file diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/design/xml/ConfusingTernary.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/codestyle/xml/ConfusingTernary.xml similarity index 100% rename from pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/design/xml/ConfusingTernary.xml rename to pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/codestyle/xml/ConfusingTernary.xml diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/codestyle/xml/ControlStatementBraces.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/codestyle/xml/ControlStatementBraces.xml new file mode 100644 index 00000000000..7766946d719 --- /dev/null +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/codestyle/xml/ControlStatementBraces.xml @@ -0,0 +1,496 @@ + + + + While, no braces + 1 + + + + + While, no braces, allowed + false + 0 + + + + + While, with braces + 0 + + + + + Empty while + 1 + + + + + Empty while, allowed + true + 0 + + + + + For, no braces + 1 + + + + + For, no braces, allowed + false + 0 + + + + + For, with braces + 0 + + + + + Empty for + 1 + + + + + Empty for, allowed + true + 0 + + + + + Do while, no braces + 1 + + + + + Do while, no braces, allowed + false + 0 + + + + + Do while, with braces + 0 + + + + + Empty Do while + 1 + + + + + Empty Do while, allowed + true + 0 + + + + + If else, no braces + 2 + + + + + If else, no braces, allowed + false + 0 + + + + + If else, braces on if + 1 + + + + + If else, braces on else + 1 + + + + + If chain, partial braces + 2 + 5,10 + + + + + Single if, allowed + false + 0 + + + + + If chain, checkSingleIfStmt doesn't apply + false + 2 + 5,10 + + + + + If chain, checkSingleIfStmt doesn't apply (even on last branch) + + false + 2 + 5,7 + + + + + If else, braces on else, checkSingleIfStmt doesn't apply + false + 1 + + + + + Single if, checkIfElseStmt overrides checkSingleIfStmt + true + false + 0 + + + + + + Case, no braces, allowed + 0 + + + + + Case, no braces + true + 2 + 6,9 + + + + + Case, dangling unbraced statement + true + 1 + 6 + + + + + Case, dangling unbraced statement + true + 1 + 6 + + + + + diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/controversial/xml/DefaultPackage.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/codestyle/xml/DefaultPackage.xml similarity index 100% rename from pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/controversial/xml/DefaultPackage.xml rename to pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/codestyle/xml/DefaultPackage.xml diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/imports/xml/DontImportJavaLang.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/codestyle/xml/DontImportJavaLang.xml similarity index 100% rename from pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/imports/xml/DontImportJavaLang.xml rename to pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/codestyle/xml/DontImportJavaLang.xml diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/imports/xml/DuplicateImports.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/codestyle/xml/DuplicateImports.xml similarity index 95% rename from pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/imports/xml/DuplicateImports.xml rename to pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/codestyle/xml/DuplicateImports.xml index d7936cd8c0b..d963dfd45d3 100644 --- a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/imports/xml/DuplicateImports.xml +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/codestyle/xml/DuplicateImports.xml @@ -90,8 +90,8 @@ class Foo { //import static org.mockito.Matchers.*; import static org.junit.Assert.*; -import static net.sourceforge.pmd.lang.java.rule.imports.ImportsRulesTest.*; -import static org.junit.Assert.assertTrue; // this import is neeeded for disambiguation - as ImportsRulesTest +import static net.sourceforge.pmd.lang.java.rule.codestyle.DuplicateImportsTest.*; +import static org.junit.Assert.assertTrue; // this import is neeeded for disambiguation - as DuplicateImportsTest // defines assertTrue with the same signature, too. public class DuplicateImports { diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/design/xml/EmptyMethodInAbstractClassShouldBeAbstract.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/codestyle/xml/EmptyMethodInAbstractClassShouldBeAbstract.xml similarity index 100% rename from pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/design/xml/EmptyMethodInAbstractClassShouldBeAbstract.xml rename to pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/codestyle/xml/EmptyMethodInAbstractClassShouldBeAbstract.xml diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/basic/xml/ExtendsObject.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/codestyle/xml/ExtendsObject.xml similarity index 100% rename from pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/basic/xml/ExtendsObject.xml rename to pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/codestyle/xml/ExtendsObject.xml diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/design/xml/FieldDeclarationsShouldBeAtStartOfClass.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/codestyle/xml/FieldDeclarationsShouldBeAtStartOfClass.xml similarity index 100% rename from pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/design/xml/FieldDeclarationsShouldBeAtStartOfClass.xml rename to pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/codestyle/xml/FieldDeclarationsShouldBeAtStartOfClass.xml diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/codestyle/xml/FieldNamingConventions.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/codestyle/xml/FieldNamingConventions.xml new file mode 100644 index 00000000000..a6d343bcb66 --- /dev/null +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/codestyle/xml/FieldNamingConventions.xml @@ -0,0 +1,209 @@ + + + + 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]+ + 5 + + 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 'cons_BOLG_FIELD' doesn't match '[A-Z][A-Z_0-9]*' + The public constant name 'DDD' doesn't match 'cons_[A-Z][A-Z0-9]+' + + + + + + + + Interface fields should be treated like public constants + 3 + + The public constant name 'Foo' doesn't match '[A-Z][A-Z_0-9]*' + The public constant name 'Hoo' doesn't match '[A-Z][A-Z_0-9]*' + The public constant name 'Bar' doesn't match '[A-Z][A-Z_0-9]*' + + + + + + + + Exclude serialVersionUID by default + 0 + + + + + More exclusions can be configured + m$mangled + 0 + + + + diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/basic/xml/ForLoopShouldBeWhileLoop.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/codestyle/xml/ForLoopShouldBeWhileLoop.xml similarity index 100% rename from pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/basic/xml/ForLoopShouldBeWhileLoop.xml rename to pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/codestyle/xml/ForLoopShouldBeWhileLoop.xml diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/braces/xml/ForLoopsMustUseBraces.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/codestyle/xml/ForLoopsMustUseBraces.xml similarity index 100% rename from pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/braces/xml/ForLoopsMustUseBraces.xml rename to pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/codestyle/xml/ForLoopsMustUseBraces.xml 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 new file mode 100644 index 00000000000..e7af6d9c9fa --- /dev/null +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/codestyle/xml/FormalParameterNamingConventions.xml @@ -0,0 +1,277 @@ + + + + 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]*' + + i = (Koo) -> { + + }; + + Consumer k = (String Voo) -> { + + }; + + Consumer l = (final String Ooo) -> { + + }; + } + + } + ]]> + + + + 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]*' + + i = (Koo) -> { + + }; + + Consumer k = (String Voo) -> { + + }; + + Consumer l = (final String Ooo) -> { + + }; + } + + } + ]]> + + + + Test final method param pattern + [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]+' + 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]*' + + i = (Koo) -> { + + }; + + Consumer k = (String Voo) -> { + + }; + + Consumer l = (final String Ooo) -> { + + }; + } + + } + ]]> + + + + Test inferred lambda parameter pattern + [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]+' + 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]*' + + i = (Koo) -> { + + }; + + Consumer q = (KOO) -> { // that's ok + + }; + + Consumer k = (String Voo) -> { + + }; + + Consumer l = (final String Ooo) -> { + + }; + } + + } + ]]> + + + + Test explicitly-typed lambda parameter pattern + [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 explicitly-typed lambda parameter name 'Voo' doesn't match '[A-Z]+' + The explicitly-typed lambda parameter name 'Ooo' doesn't match '[A-Z]+' + + i = (Koo) -> { + + }; + + Consumer q = (String KOO) -> { // that's ok + + }; + + Consumer qq = (final String MOO) -> { // that's ok + + }; + + Consumer k = (String Voo) -> { + + }; + + Consumer l = (final String Ooo) -> { + + }; + } + + } + ]]> + + + + diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/naming/xml/GenericsNaming.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/codestyle/xml/GenericsNaming.xml similarity index 100% rename from pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/naming/xml/GenericsNaming.xml rename to pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/codestyle/xml/GenericsNaming.xml diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/codestyle/xml/IdenticalCatchBranches.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/codestyle/xml/IdenticalCatchBranches.xml new file mode 100644 index 00000000000..30b250f928a --- /dev/null +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/codestyle/xml/IdenticalCatchBranches.xml @@ -0,0 +1,144 @@ + + + + + + Collapsible statement + 1 + + 'catch' branch identical to 'IllegalArgumentException' branch + + + + + + + + Different statement + 0 + + + + + + + Identical statements, with different exception names + 1 + + 'catch' branch identical to 'IllegalArgumentException' branch + + + + + + + + #1158 false positive 1 + 0 + + + + + + + #1158 false positive 2 + 0 + + + + + + + False positive with method name mistaken for exception parameter + 0 + + + + + + + \ No newline at end of file diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/braces/xml/IfElseStmtsMustUseBraces.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/codestyle/xml/IfElseStmtsMustUseBraces.xml similarity index 100% rename from pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/braces/xml/IfElseStmtsMustUseBraces.xml rename to pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/codestyle/xml/IfElseStmtsMustUseBraces.xml diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/braces/xml/IfStmtsMustUseBraces.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/codestyle/xml/IfStmtsMustUseBraces.xml similarity index 100% rename from pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/braces/xml/IfStmtsMustUseBraces.xml rename to pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/codestyle/xml/IfStmtsMustUseBraces.xml 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 00000000000..006b005d355 --- /dev/null +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/codestyle/xml/LinguisticNaming.xml @@ -0,0 +1,555 @@ + + + + + + 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 Prefix as: Transform Method with property configuration + as + 1 + 6 + + Linguistics Antipattern - The transform method 'asDataType' should not return void linguistically + + + + + + Method Contains To: Transformation methods + true + 1 + 2 + + Linguistics Antipattern - The transform method 'grapeToWine' should not return void linguistically + + + + + + Method Contains As: Transformation methods with configured infix + true + as + 1 + 2 + + Linguistics Antipattern - The transform method 'grapeAsWine' 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 + + + + + #1334 [java] LinguisticNaming should support AtomicBooleans + 0 + + + + + #1334 [java] LinguisticNaming should support AtomicBooleans false negative + 18 + + + diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/j2ee/xml/LocalHomeNamingConvention.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/codestyle/xml/LocalHomeNamingConvention.xml similarity index 100% rename from pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/j2ee/xml/LocalHomeNamingConvention.xml rename to pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/codestyle/xml/LocalHomeNamingConvention.xml diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/j2ee/xml/LocalInterfaceSessionNamingConvention.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/codestyle/xml/LocalInterfaceSessionNamingConvention.xml similarity index 100% rename from pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/j2ee/xml/LocalInterfaceSessionNamingConvention.xml rename to pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/codestyle/xml/LocalInterfaceSessionNamingConvention.xml diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/optimizations/xml/LocalVariableCouldBeFinal.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/codestyle/xml/LocalVariableCouldBeFinal.xml similarity index 100% rename from pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/optimizations/xml/LocalVariableCouldBeFinal.xml rename to pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/codestyle/xml/LocalVariableCouldBeFinal.xml 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 new file mode 100644 index 00000000000..e54e1fed1d8 --- /dev/null +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/codestyle/xml/LocalVariableNamingConventions.xml @@ -0,0 +1,136 @@ + + + + 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]*' + + + + + + Test local variable property + [A-Z][A-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]*' + + + + + + Test final local variable property + [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-Z0-9]+' + The exception block parameter name 'E' doesn't match '[a-z][a-zA-Z0-9]*' + + + + + + Test exception parameter property + [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 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/naming/xml/LongVariable.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/codestyle/xml/LongVariable.xml similarity index 100% rename from pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/naming/xml/LongVariable.xml rename to pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/codestyle/xml/LongVariable.xml diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/j2ee/xml/MDBAndSessionBeanNamingConvention.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/codestyle/xml/MDBAndSessionBeanNamingConvention.xml similarity index 100% rename from pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/j2ee/xml/MDBAndSessionBeanNamingConvention.xml rename to pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/codestyle/xml/MDBAndSessionBeanNamingConvention.xml diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/naming/xml/MisleadingVariableName.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/codestyle/xml/MIsLeadingVariableName.xml similarity index 100% rename from pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/naming/xml/MisleadingVariableName.xml rename to pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/codestyle/xml/MIsLeadingVariableName.xml diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/optimizations/xml/MethodArgumentCouldBeFinal.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/codestyle/xml/MethodArgumentCouldBeFinal.xml similarity index 100% rename from pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/optimizations/xml/MethodArgumentCouldBeFinal.xml rename to pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/codestyle/xml/MethodArgumentCouldBeFinal.xml 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 new file mode 100644 index 00000000000..c494c196af0 --- /dev/null +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/codestyle/xml/MethodNamingConventions.xml @@ -0,0 +1,211 @@ + + + + method names should start with lowercase character + 1 + + + + method names should not contain underscores + 1 + + The instance method name 'bar_foo' doesn't match '[a-z][a-zA-Z0-9]*' + + + + + all is well + 0 + + + + #1288 MethodNamingConventions for native should be deactivated + false + 0 + + + + #1288 MethodNamingConventions for native should be deactivated - prevent false negative + true + 1 + 2 + + The native method name '__surfunc__' doesn't match '[a-z][a-zA-Z0-9]*' + + + + + #1343 MethodNamingConventions for overridden methods + 0 + + + + + Static method pattern + st_[a-z][A-Za-z]* + 1 + 2 + + The static method name 'foo' doesn't match 'st_[a-z][A-Za-z]*' + + + + + + + Native method pattern + nt_[a-z][A-Za-z]* + 1 + 2 + + The native method name 'foo' doesn't match 'nt_[a-z][A-Za-z]*' + + + + + + + JUnit 3 test detection + test_[a-z][A-Za-z]* + 1 + 9 + + The JUnit 3 test method name 'testGetBestTeam' doesn't match 'test_[a-z][A-Za-z]*' + + + + + + JUnit 4 test detection + [a-z][A-Za-z]*Test + 2 + 12,16 + + The JUnit 4 test method name 'testGetBestTeam' doesn't match '[a-z][A-Za-z]*Test' + The JUnit 4 test method name 'getBestTeam' doesn't match '[a-z][A-Za-z]*Test' + + + + + + + Instance method custom convention + m_[a-z][A-Za-z]* + 1 + 3 + + The instance method name 'fooBar' doesn't match 'm_[a-z][A-Za-z]*' + + + + + + diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/naming/xml/NoPackage.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/codestyle/xml/NoPackage.xml similarity index 100% rename from pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/naming/xml/NoPackage.xml rename to pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/codestyle/xml/NoPackage.xml diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/controversial/xml/OnlyOneReturn.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/codestyle/xml/OnlyOneReturn.xml similarity index 100% rename from pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/controversial/xml/OnlyOneReturn.xml rename to pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/codestyle/xml/OnlyOneReturn.xml diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/naming/xml/PackageCase.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/codestyle/xml/PackageCase.xml similarity index 100% rename from pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/naming/xml/PackageCase.xml rename to pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/codestyle/xml/PackageCase.xml diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/codestyle/xml/PrematureDeclaration.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/codestyle/xml/PrematureDeclaration.xml new file mode 100644 index 00000000000..fb34cfa0172 --- /dev/null +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/codestyle/xml/PrematureDeclaration.xml @@ -0,0 +1,188 @@ + + + + + 1 + + + + + 0 + + + + + Bug #1064 Exception running PrematureDeclaration + 0 + + + + + #1305 variable declaration inside switch causes ClassCastException + 0 + + + + + #1396 PrematureDeclaration lambda false positive + 0 + { return; }; + r.run(); + return sum; + } +} + ]]> + + + + PrematureDeclaration false negative with reference that is a suffix of the variable name + 1 + + + + + #1067 PrematureDeclaration lambda closure false positive + 0 + ().stream().anyMatch(bar -> foo.equals(bar)); + } +} + ]]> + + + + + #1108 PrematureDeclaration lambda false positive + 0 + sign(signingInput)) + .getOrElse(() -> null); + } + } + ]]> + + + + + PrematureDeclaration should also check inside lambdas + 1 + { + // Inside that lambda *is* a premature declaration of `sum` + int sum = 0; + + if (strings == null || strings.length == 0) + return 0; + + for (int i = 0; i < strings.length; i++) { + sum += strings[i].length(); + } + + return sum; + }); + } + } + ]]> + + diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/j2ee/xml/RemoteInterfaceNamingConvention.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/codestyle/xml/RemoteInterfaceNamingConvention.xml similarity index 100% rename from pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/j2ee/xml/RemoteInterfaceNamingConvention.xml rename to pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/codestyle/xml/RemoteInterfaceNamingConvention.xml diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/j2ee/xml/RemoteSessionInterfaceNamingConvention.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/codestyle/xml/RemoteSessionInterfaceNamingConvention.xml similarity index 100% rename from pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/j2ee/xml/RemoteSessionInterfaceNamingConvention.xml rename to pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/codestyle/xml/RemoteSessionInterfaceNamingConvention.xml diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/naming/xml/ShortClassName.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/codestyle/xml/ShortClassName.xml similarity index 100% rename from pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/naming/xml/ShortClassName.xml rename to pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/codestyle/xml/ShortClassName.xml diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/naming/xml/ShortMethodName.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/codestyle/xml/ShortMethodName.xml similarity index 100% rename from pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/naming/xml/ShortMethodName.xml rename to pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/codestyle/xml/ShortMethodName.xml diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/naming/xml/ShortVariable.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/codestyle/xml/ShortVariable.xml similarity index 81% rename from pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/naming/xml/ShortVariable.xml rename to pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/codestyle/xml/ShortVariable.xml index 5ebb0b29448..d25c0bf0a91 100644 --- a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/naming/xml/ShortVariable.xml +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/codestyle/xml/ShortVariable.xml @@ -3,10 +3,9 @@ xmlns="http://pmd.sourceforge.net/rule-tests" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://pmd.sourceforge.net/rule-tests http://pmd.sourceforge.net/rule-tests_1_0_0.xsd"> + - + param 1 + - + none 0 + - + local 1 + - + for 0 + - + field 1 + - + catch(Exception e) is OK 0 + ShortVariable false positive with for-each loops 0 @@ -93,6 +88,7 @@ public class Foo { } ]]> + ShortVariable within for-each loops 1 @@ -106,6 +102,7 @@ public class Foo { } ]]> + #1361 ShortVariable configuration - 7 characters 7 @@ -120,6 +117,7 @@ public class ShortVariable { } ]]> + #1361 ShortVariable configuration - 1 characters 1 @@ -130,6 +128,21 @@ public class ShortVariable { String thisIsOk = ""; String foo = ""; // that's ok, too, now } +} + ]]> + + + + #720 Whitelist lambda parameters + 1 + 0 + foo(); + String foo = (a, b) -> foo(); + String bar = (String a, Boolean b) -> foo(); + } } ]]> diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/naming/xml/SuspiciousConstantFieldName.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/codestyle/xml/SuspiciousConstantFieldName.xml similarity index 100% rename from pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/naming/xml/SuspiciousConstantFieldName.xml rename to pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/codestyle/xml/SuspiciousConstantFieldName.xml diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/imports/xml/TooManyStaticImports.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/codestyle/xml/TooManyStaticImports.xml similarity index 100% rename from pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/imports/xml/TooManyStaticImports.xml rename to pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/codestyle/xml/TooManyStaticImports.xml diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/codestyle/xml/UnnecessaryAnnotationValueElement.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/codestyle/xml/UnnecessaryAnnotationValueElement.xml new file mode 100644 index 00000000000..803533ed273 --- /dev/null +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/codestyle/xml/UnnecessaryAnnotationValueElement.xml @@ -0,0 +1,120 @@ + + + + + 1 + + + + + + 1 + + + + + + 1 + + + + + + 0 + + + + + + 0 + + + + + + 0 + + + diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/codestyle/xml/UnnecessaryConstructor.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/codestyle/xml/UnnecessaryConstructor.xml new file mode 100644 index 00000000000..0870e071d8d --- /dev/null +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/codestyle/xml/UnnecessaryConstructor.xml @@ -0,0 +1,232 @@ + + + + + 1 + + + + + 0 + + + + + 0 + + + + + 0 + + + + + 0 + + + + + 0 + + + + + 1 + + + + + 2 + + + + + 0 + + + + + 1 + + + + + 0 + + + + + 0 + + + + + 1 + + + + + 0 + + + + + 0 + + + + + 1 + 3 + + + + + 1 + 3 + + + + + Verify ignoredAnnotations property is used + java.lang.Deprecated + 0 + + + diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/codestyle/xml/UnnecessaryFullyQualifiedName.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/codestyle/xml/UnnecessaryFullyQualifiedName.xml new file mode 100644 index 00000000000..8f3d8021358 --- /dev/null +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/codestyle/xml/UnnecessaryFullyQualifiedName.xml @@ -0,0 +1,505 @@ + + + + + 1, nothing wrong + 0 + + + + + 2, valid implements + 0 + + + + + 3, invalid implements + 1 + + + + + 4, valid extends + 0 + + + + + 5, invalid extends + 1 + + + + + 6, valid field + 0 + + + + + 7, invalid field + 1 + + + + + 8, valid return type + 0 + + + + + 9, invalid return type + 1 + + + + + 10, valid formal parameter + 0 + + + + + 11, invalid formal parameter + 1 + + + + + 12, valid static method call + 0 + + + + + 13, invalid static method call + 1 + + + + + 14, valid static import method call + 0 + + + + + 15, invalid static import method call + 1 + + + + + 16, valid static import method call with class import + 0 + + + + + 17, invalid static import method call with class import + 1 + + + + + 18, on-demand, valid field + 0 + + + + + 19, on-demand, invalid field + 1 + + + + + 20, on-demand, valid static import method call + 0 + + + + + 21, on-demand, invalid static import method call + 1 + + + + + 22, valid on-demand static import method call with class import + 0 + + + + + 23, invalid on-demand static import method call with class import + 1 + + + + + #1078 Package statement introduces false positive UnnecessaryFullyQualifiedName violation + 0 + + + + + #1404 Java8 'Unnecessary use of fully qualified name' in Streams Collector + 1 + 17 + + Unnecessary use of fully qualified name 'Collectors.toList' due to existing static import 'java.util.stream.Collectors.toList' + + + + + + #1436 UnnecessaryFullyQualifiedName false positive on clashing static imports with enums + 0 + + + + + #1546 part 1 UnnecessaryFullyQualifiedName doesn't take into consideration conflict resolution + 0 + + + + + #1546 part 2 UnnecessaryFullyQualifiedName doesn't take into consideration conflict resolution + 0 + + + + + #1555 - UnnecessaryFullyQualifiedName for conflict resolution with inner class + 0 + + + + + #1114 - Star import overwritten by explicit import is not correctly handled + 0 + + + + + #1186 [java] UnnecessaryFullyQualifiedName doesn't detect java.lang FQ names as violations: without import + 1 + + Unnecessary use of fully qualified name 'java.lang.String' due to existing implicit import 'java.lang.*' + + + + + + #1186 [java] UnnecessaryFullyQualifiedName doesn't detect java.lang FQ names as violations: with import + 1 + + Unnecessary use of fully qualified name 'java.lang.String' due to existing import 'java.lang.String' + + + + + + #1186 [java] UnnecessaryFullyQualifiedName doesn't detect java.lang FQ names as violations: with avoiding conflict import + 0 + + + + + #1186 [java] UnnecessaryFullyQualifiedName doesn't detect java.lang FQ names as violations: static members of classes in java.lang + 1 + + + + + #1216 [java] UnnecessaryFullyQualifiedName false positive for the same name method + 0 + stream(Container parent) { + return Arrays.asList("", ""); + } + } + ]]> + + + + #1255 [java] UnnecessaryFullyQualifiedName false positive: static method on shadowed implicitly imported class + 0 + + + + + Nullpointer in isJavaLangImplicit for java.lang.String[] arrays + 1 + 6 + + + + + #1199 false negative for same package FQCN + 1 + 4 + + + diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/design/xml/UnnecessaryLocalBeforeReturn.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/codestyle/xml/UnnecessaryLocalBeforeReturn.xml similarity index 100% rename from pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/design/xml/UnnecessaryLocalBeforeReturn.xml rename to pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/codestyle/xml/UnnecessaryLocalBeforeReturn.xml diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/codestyle/xml/UnnecessaryModifier.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/codestyle/xml/UnnecessaryModifier.xml new file mode 100644 index 00000000000..ab2dd7cb33a --- /dev/null +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/codestyle/xml/UnnecessaryModifier.xml @@ -0,0 +1,708 @@ + + + + + 1 + + + + + 0 + + + + + 1 + + + + + 0 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 1 + + + + + 0 + + + + + 1 + + + + + 1 + + + + + 0 + + + + + 1 + + + + + 1 + + + + + 0 + + + + + 1 + + + + + 0 + + + + + 2 + + Unnecessary modifier 'public' on interface 'Bar': members of interface types are implicitly public + Unnecessary modifiers 'public static final' on field 'X': the field is declared in an interface type + + + + + + 2 + + Unnecessary modifier 'public' on annotation 'Bar': members of interface types are implicitly public + Unnecessary modifiers 'public static final' on field 'X': the field is declared in an annotation type + + + + + + 0 + + + + + 0 + + + + + 0 + + + + + 0 + + + + + False negative: #1185 UnusedModifier throws NPE when parsing enum with a nested static interface + 1 + + + + #1275 False positive: UnusedModifier rule for static inner class in enum + 0 + + + + + #1480 false positive on public modifier used with inner interface in enum + 0 + + + + + Unnecessary public on annotation element + 1 + + Unnecessary modifier 'public' on method 'message': the method is declared in an annotation type + + + + + Unnecessary abstract on annotation element + 1 + + Unnecessary modifier 'abstract' on method 'message': the method is declared in an annotation type + + + + + Unnecessary final on annotation field + 1 + + + + Unnecessary static on annotation field + 1 + + + + Unnecessary public on annotation field + 1 + + + + Unnecessary public on annotation nested class + 1 + + + + Unnecessary static on annotation nested class + 1 + + + + Unnecessary public on annotation nested interface + 1 + + + + Unnecessary public on annotation nested annotation + 1 + + + + Unnecessary static on annotation nested enum + 2 + + Unnecessary modifier 'public' on enum 'Inner': the enum is declared in an annotation type + Unnecessary modifier 'static' on enum 'Inner': nested enums are implicitly static + + + + + Unnecessary static on interface nested enum + 2 + + Unnecessary modifier 'public' on enum 'Inner': the enum is declared in an interface type + Unnecessary modifier 'static' on enum 'Inner': nested enums are implicitly static + + + + + Unnecessary static on class nested enum + 1 + + Unnecessary modifier 'static' on enum 'Inner': nested enums are implicitly static + + + + + Unnecessary abstract on interface + 1 + + Unnecessary modifier 'abstract' on interface 'TestInterface': interface types are implicitly abstract + + + + + Unnecessary abstract on annotation + 1 + + Unnecessary modifier 'abstract' on annotation 'TestAnnotation': annotations types are implicitly abstract + + + + + + 0 + + + + final and non-final methods mixed + 0 + + + + + 1 + + Unnecessary modifier 'final' on method 'foo': the method is already in a final class + + + + + + 1 + + Unnecessary modifier 'final' on method 'foo': the method is already in a final class + + + + + + 3 + + + + + 0 + + + + + 1 + + Unnecessary modifier 'final' on method 'buz': the method is already in a final class + + + + + + #1464 UnnecessaryFinalModifier false positive on a @SafeVarargs method + 0 + { + @SafeVarargs + public final InboxContents conflateWith(T... values) { // false positive + return conflateWith(ImmutableList.copyOf(values)); + } +} +public final class InboxContents2 { + @java.lang.SafeVarargs + public final InboxContents conflateWith(String... values) { + return conflateWith(ImmutableList.copyOf(values)); + } +} + ]]> + + + Unnecessary final of private method + 1 + + Unnecessary modifier 'final' on method 'getValue': private methods cannot be overridden + + + + + Unnecessary final of enum method + 1 + + Unnecessary modifier 'final' on method 'magic': an anonymous class cannot be extended + + + + + Unnecessary final of anonymous class method + 1 + + + + Unnecessary final of try-with-resources resource + 1 + + Unnecessary modifier 'final' on resource specification 'fw': resource specifications are implicitly final + + + + + #817 [java] UnnecessaryModifierRule crashes on valid code + 0 + + + + Private constructor of enum + 1 + + Unnecessary modifier 'private' on constructor 'Foo(String)': enum constructors are implicitly private + + + + + Static Modifier on interface + 1 + + Unnecessary modifier 'static' on interface 'Bar': member interfaces are implicitly static + + + + + Static Modifier on interface + 1 + + Unnecessary modifier 'static' on class 'Foo': types nested within an interface type are implicitly static + + + + diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/unnecessary/xml/UnnecessaryReturn.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/codestyle/xml/UnnecessaryReturn.xml similarity index 100% rename from pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/unnecessary/xml/UnnecessaryReturn.xml rename to pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/codestyle/xml/UnnecessaryReturn.xml diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/codestyle/xml/UseUnderscoresInNumericLiterals.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/codestyle/xml/UseUnderscoresInNumericLiterals.xml new file mode 100644 index 00000000000..403dfd7d1f9 --- /dev/null +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/codestyle/xml/UseUnderscoresInNumericLiterals.xml @@ -0,0 +1,329 @@ + + + + + ok, numeric literal with correct '_' usage + + 0 + + + + + bad, numeric literal without '_' + + 1 + + + + + bad, numeric literal used as a method parameter without '_' + + 1 + + + + + ok, float value with less than 3 numbers. + + 0 + + + + + bad, double value without '_' + + 1 + + + + + ok, numeric literal used as a method parameter with '_' + + 0 + + + + + ok, Long value with 'l' used as a method parameter with '_' + + 0 + + + + + bad, numeric literal with incorrect '_' usage + + 1 + + + + + ok, Double value with 'D' used as a method parameter with '_' + + 0 + + + + + ok, Float value with 'F' with less than 3 numbers + + 0 + + + + + ok, Double value with 'd' with correct '_' usage + + 0 + + + + + ok, Long value with 'L' used as a method parameter with '_' + + 0 + + + + + ok, String value with '_' + + 0 + + + + + ok, String value without '_' + + 0 + + + + + ok, Numeric Literal in binary + + 0 + + + + + ok, Numeric Literal in octal + + 0 + + + + + ok, Numeric Literal in hexadecimal + + 0 + + + + + ok, Numeric Literal in negative binary + + 0 + + + + + ok, Numeric Literal in negative octal + + 0 + + + + + ok, Numeric Literal in negative hexadecimal + + 0 + + + + + ok, Numeric Literal with exponent + + 0 + + + + + bad, Negative exponent without '_' + + 1 + + + + + ok, Numeric Literal with positive exponent + + 0 + + + + + ok, Lengthy numeric literal with variable name as serialVersionUID + + 0 + + + + + bad, testing acceptableDecimalLength + + 3 + 1 + + + + + ok, testing acceptableDecimalLength + + 5 + 0 + + + + + bad, testing acceptableDecimalLength + + 15 + 1 + + + + + ok, Decimal number test + + 0 + + + diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/unnecessary/xml/UselessParentheses.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/codestyle/xml/UselessParentheses.xml similarity index 92% rename from pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/unnecessary/xml/UselessParentheses.xml rename to pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/codestyle/xml/UselessParentheses.xml index 6618aec8ba9..777c87bcca4 100644 --- a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/unnecessary/xml/UselessParentheses.xml +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/codestyle/xml/UselessParentheses.xml @@ -460,4 +460,46 @@ public class Useless { ]]> + + FP on binary operator + 0 + + + + + [java] False positive for useless parenthesis #1023 + 0 + + + + + [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/unnecessary/xml/UselessQualifiedThis.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/codestyle/xml/UselessQualifiedThis.xml similarity index 87% rename from pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/unnecessary/xml/UselessQualifiedThis.xml rename to pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/codestyle/xml/UselessQualifiedThis.xml index 2c8ba82356d..f9044564af3 100644 --- a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/unnecessary/xml/UselessQualifiedThis.xml +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/codestyle/xml/UselessQualifiedThis.xml @@ -152,4 +152,27 @@ public class ApiExceptionCtrlAdvice { } ]]> + + + False positive for UselessQualifiedThis #1372 + 0 + + + + \ No newline at end of file diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/naming/xml/VariableNamingConventions.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/codestyle/xml/VariableNamingConventions.xml similarity index 100% rename from pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/naming/xml/VariableNamingConventions.xml rename to pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/codestyle/xml/VariableNamingConventions.xml diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/braces/xml/WhileLoopsMustUseBraces.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/codestyle/xml/WhileLoopsMustUseBraces.xml similarity index 100% rename from pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/braces/xml/WhileLoopsMustUseBraces.xml rename to pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/codestyle/xml/WhileLoopsMustUseBraces.xml diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/comments/xml/CommentDefaultAccessModifier.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/comments/xml/CommentDefaultAccessModifier.xml deleted file mode 100755 index 8d8ae310fd3..00000000000 --- a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/comments/xml/CommentDefaultAccessModifier.xml +++ /dev/null @@ -1,186 +0,0 @@ - - - - - - Some methods and Fields with default access modifier in a class - 2 - 3,10 - - - - - - - All methods and Field with default access modifier in a class - 6 - 2,3,4,6,9,12 - - - - - - - All methods and Field without default access modifier in a class - 0 - - - - - - Methods with default access modifier in an Interface - 0 - - - - - - Nested classes with default access modifier - 2 - 7,10 - - - - - - Test own regex to default access modifier rule - \/\*\s+package-private\s+\*\/ - 3 - 2,4,13 - - - - - Declarations inside an enum - 0 - - - - - #1430 CommentDefaultAccessModifier triggers on field annotated with @VisibleForTesting - 0 - - - - - #536 Constructor with default access modifier should trigger - 1 - - - - - #536 Enum constructor with implicit private modifier should not trigger - 0 - - - \ No newline at end of file diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/comments/xml/CommentRequired.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/comments/xml/CommentRequired.xml deleted file mode 100755 index 48f8e8fe434..00000000000 --- a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/comments/xml/CommentRequired.xml +++ /dev/null @@ -1,219 +0,0 @@ - - - - - - - Missing comments - all required (default). - 6 - - - - - All elements have javadoc comments. - 0 - - - - - Missing comments - only class level required. - Required - Ignored - Ignored - Ignored - Ignored - 1 - - - - - Too many comments - all comments are unwanted. - Unwanted - Unwanted - Unwanted - Unwanted - Unwanted - 6 - - - - - #1115 commentRequiredRule in pmd 5.1 is not working properly - 2 - - - - - #1115 commentRequiredRule in pmd 5.1 is not working properly - without new lines - 2 - - - - - #1289 CommentRequired not ignored if javadoc {@inheritDoc} anon inner classes - 0 - - - - - #1434 CommentRequired raises violation on serialVersionUID field - 0 - - - - comment required on serialVersionUID of wrong type - 1 - 3 - - - - serialVersionUID comment required - Required - 1 - 3 - - - - - #1522 [java] CommentRequired: false positive - 0 - - - diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/controversial/xml/AtLeastOneConstructor.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/controversial/xml/AtLeastOneConstructor.xml deleted file mode 100644 index 9260be007e7..00000000000 --- a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/controversial/xml/AtLeastOneConstructor.xml +++ /dev/null @@ -1,119 +0,0 @@ - - - - - 0 - - - - - 1 - - - - - 1 - - - - - 1 - - - - - 2 - - - - - 0 - - - - - 0 - - - - - 0 - - - - - 0 - - - - Don't skip classes with non-static methods; #1216 AtLeastOneConstructor ignores classes with *any* methods - 1 - - - diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/controversial/xml/AvoidUsingShortType.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/controversial/xml/AvoidUsingShortType.xml deleted file mode 100644 index 4686c9c2e48..00000000000 --- a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/controversial/xml/AvoidUsingShortType.xml +++ /dev/null @@ -1,85 +0,0 @@ - - - - - 1 - - - - - - - 1 - - - - - 2 - - - - - - - 0 - - - - - - - #1449 false positive when casting a variable to short - 0 - - - diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/controversial/xml/NullAssignment.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/controversial/xml/NullAssignment.xml deleted file mode 100644 index bc183811aca..00000000000 --- a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/controversial/xml/NullAssignment.xml +++ /dev/null @@ -1,132 +0,0 @@ - - - - - 0 - - - - - 1 - - - - - 0 - - - - - 0 - - - - - 1 - - - - - 1 - - - - - 0 - - - - - 0 - - - - - 0 - - - diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/controversial/xml/OneDeclarationPerLine.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/controversial/xml/OneDeclarationPerLine.xml deleted file mode 100644 index 72b07d6c97a..00000000000 --- a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/controversial/xml/OneDeclarationPerLine.xml +++ /dev/null @@ -1,68 +0,0 @@ - - - - - 1 - - - - - - - 0 - - - - - - #1221 OneDeclarationPerLine really checks for one declaration each statement - false - 0 - - - - #1221 OneDeclarationPerLine really checks for one declaration each statement - true - 1 - - - diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/controversial/xml/UnnecessaryConstructor.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/controversial/xml/UnnecessaryConstructor.xml deleted file mode 100644 index 40a8b7a2b86..00000000000 --- a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/controversial/xml/UnnecessaryConstructor.xml +++ /dev/null @@ -1,164 +0,0 @@ - - - - - 1 - - - - - 0 - - - - - 0 - - - - - 0 - - - - - 0 - - - - - 0 - - - - - 1 - - - - - 2 - - - - - 0 - - - - - 1 - - - - - 0 - - - - - 0 - - - - - 1 - - - diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/coupling/xml/LooseCoupling.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/coupling/xml/LooseCoupling.xml deleted file mode 100644 index 49ee78f2984..00000000000 --- a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/coupling/xml/LooseCoupling.xml +++ /dev/null @@ -1,141 +0,0 @@ - - - - - 1 - - - - - 0 - - - - - 0 - - - - - 0 - - - - - 1 - - - - - 2 - - - - - 2 - - - - - 1 - - - - - 1 - - - - - 1 - - - - - #938 False positive on LooseCoupling for overriding methods - 0 - - - diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/strictexception/xml/AvoidCatchingGenericException.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/design/xml/AvoidCatchingGenericException.xml similarity index 100% rename from pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/strictexception/xml/AvoidCatchingGenericException.xml rename to pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/design/xml/AvoidCatchingGenericException.xml diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/strictexception/xml/AvoidRethrowingException.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/design/xml/AvoidRethrowingException.xml similarity index 100% rename from pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/strictexception/xml/AvoidRethrowingException.xml rename to pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/design/xml/AvoidRethrowingException.xml diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/strictexception/xml/AvoidThrowingNewInstanceOfSameException.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/design/xml/AvoidThrowingNewInstanceOfSameException.xml similarity index 100% rename from pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/strictexception/xml/AvoidThrowingNewInstanceOfSameException.xml rename to pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/design/xml/AvoidThrowingNewInstanceOfSameException.xml diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/strictexception/xml/AvoidThrowingNullPointerException.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/design/xml/AvoidThrowingNullPointerException.xml similarity index 100% rename from pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/strictexception/xml/AvoidThrowingNullPointerException.xml rename to pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/design/xml/AvoidThrowingNullPointerException.xml diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/design/xml/AvoidThrowingRawExceptionTypes.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/design/xml/AvoidThrowingRawExceptionTypes.xml new file mode 100644 index 00000000000..b074b1d80eb --- /dev/null +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/design/xml/AvoidThrowingRawExceptionTypes.xml @@ -0,0 +1,87 @@ + + + + + 4 + + + + + 0 + + + + #1337: False positive "Avoid throwing raw exception types" when exception is not thrown + 0 + + + + False negative when importing another exception + 1 + + + + Ok when throwing another exception + 0 + + + diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/basic/xml/CollapsibleIfStatements.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/design/xml/CollapsibleIfStatements.xml similarity index 100% rename from pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/basic/xml/CollapsibleIfStatements.xml rename to pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/design/xml/CollapsibleIfStatements.xml diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/coupling/xml/CouplingBetweenObjects.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/design/xml/CouplingBetweenObjects.xml similarity index 100% rename from pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/coupling/xml/CouplingBetweenObjects.xml rename to pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/design/xml/CouplingBetweenObjects.xml diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/design/xml/CyclomaticComplexity.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/design/xml/CyclomaticComplexity.xml new file mode 100644 index 00000000000..c5deb2f18d6 --- /dev/null +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/design/xml/CyclomaticComplexity.xml @@ -0,0 +1,471 @@ + + + + + + + + + Simple method + 1 + 1 + 2 + + The class 'Foo' has a total cyclomatic complexity of 1 (highest 1). + The method 'foo()' has a cyclomatic complexity of 1. + + + + + + + + testLessComplicatedThanReportLevel + 0 + + + + + + + Complicated method + 1 + + The method 'example()' has a cyclomatic complexity of 17. + + + + + + Complicated method (ignoreBooleanPath) + ignoreBooleanPaths + 1 + + The method 'example()' has a cyclomatic complexity of 10. + + + + + + Constructor + 1 + 1 + + The constructor 'Foo()' has a cyclomatic complexity of 1. + + + + + + + + Test class report level + 17 + 999 + 1 + + + + + Test method report level + 999 + 17 + 1 + + + + + + + + + #984 Cyclomatic complexity should treat constructors like methods: 1 - reportMethods=true + 1 + 1 + + + + + #984 Cyclomatic complexity should treat constructors like methods: 2 -reportMethods=false + 999 + 0 + + + + + #985 Suppressed methods shouldn't affect avg CyclomaticComplexity + 2 + 0 + + + + + + + + + + + Standard Cyclo should count empty switch labels too + 2 + 1 + + The method 'switchCase()' has a cyclomatic complexity of 3. + + + + + + IgnoreBooleanPaths Cyclo should not count empty switch labels + ignoreBooleanPaths + 2 + 1 + + The method 'switchCase()' has a cyclomatic complexity of 2. + + + + + + 2 || y < 4) { + while (x++ < 10 && !(y-- < 0)); + } else if (a && b || x < 4) { + return; + } + } + } + ]]> + + + + Standard Cyclo should count boolean paths + 2 + 1 + + The method 'foo()' has a cyclomatic complexity of 8. + + + + + + IgnoreBooleanPaths Cyclo should not count boolean paths + ignoreBooleanPaths + 2 + 1 + + The method 'foo()' has a cyclomatic complexity of 4. + + + + + + + + + + + Test many unreported methods + 1 + + The class 'Complicated' has a total cyclomatic complexity of 80 (highest 8). + + + + + + Test property 'reportLevel' changes methodReportLevel + 1 + 1 + + + + + Test property 'reportLevel' changes classReportLevel + 11 + 0 + + + + diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/metrics/xml/DataClass.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/design/xml/DataClass.xml similarity index 94% rename from pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/metrics/xml/DataClass.xml rename to pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/design/xml/DataClass.xml index 3004926e461..f0d98e49240 100644 --- a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/metrics/xml/DataClass.xml +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/design/xml/DataClass.xml @@ -214,4 +214,16 @@ public class TestDescriptor { - \ No newline at end of file + + [java] [6.0.0] NPE in DataClass rule caused by empty declaration #785 + 0 + + + + + + diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/strictexception/xml/DoNotExtendJavaLangError.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/design/xml/DoNotExtendJavaLangError.xml similarity index 75% rename from pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/strictexception/xml/DoNotExtendJavaLangError.xml rename to pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/design/xml/DoNotExtendJavaLangError.xml index eb3adbc9085..d803d511422 100644 --- a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/strictexception/xml/DoNotExtendJavaLangError.xml +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/design/xml/DoNotExtendJavaLangError.xml @@ -33,4 +33,18 @@ public class Foo extends FooError { } ]]> + + + + 0 + + + diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/strictexception/xml/ExceptionAsFlowControl.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/design/xml/ExceptionAsFlowControl.xml similarity index 100% rename from pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/strictexception/xml/ExceptionAsFlowControl.xml rename to pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/design/xml/ExceptionAsFlowControl.xml diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/design/xml/ExcessiveClassLength.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/design/xml/ExcessiveClassLength.xml new file mode 100644 index 00000000000..734a314116e --- /dev/null +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/design/xml/ExcessiveClassLength.xml @@ -0,0 +1,78 @@ + + + + short + 10 + 0 + + + + + long + 10 + 1 + + + + long class - changed minimum + 2000 + 0 + + + + + + Consider enum types, refs #825 + 4 + 1 + + + + + Consider annotation types, refs #825 + 4 + 1 + + + + diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/coupling/xml/ExcessiveImports.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/design/xml/ExcessiveImports.xml similarity index 100% rename from pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/coupling/xml/ExcessiveImports.xml rename to pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/design/xml/ExcessiveImports.xml diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/codesize/xml/ExcessiveMethodLength.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/design/xml/ExcessiveMethodLength.xml similarity index 77% rename from pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/codesize/xml/ExcessiveMethodLength.xml rename to pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/design/xml/ExcessiveMethodLength.xml index 749aa6a5ccf..b1c3cfcff08 100644 --- a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/codesize/xml/ExcessiveMethodLength.xml +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/design/xml/ExcessiveMethodLength.xml @@ -1,8 +1,8 @@ + xmlns="http://pmd.sourceforge.net/rule-tests" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://pmd.sourceforge.net/rule-tests http://pmd.sourceforge.net/rule-tests_1_0_0.xsd"> - + long, minimum with longer range 20 0 - + not quite long 10 0 - + Consider constructors, refs #825 10 1 10 lines - Not a violation + bar(); + bar(); + } } ]]> diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/codesize/xml/ExcessiveParameterList.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/design/xml/ExcessiveParameterList.xml similarity index 100% rename from pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/codesize/xml/ExcessiveParameterList.xml rename to pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/design/xml/ExcessiveParameterList.xml diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/codesize/xml/ExcessivePublicCount.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/design/xml/ExcessivePublicCount.xml similarity index 100% rename from pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/codesize/xml/ExcessivePublicCount.xml rename to pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/design/xml/ExcessivePublicCount.xml diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/design/xml/GodClass.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/design/xml/GodClass.xml index c474660ae2f..77996fff470 100644 --- a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/design/xml/GodClass.xml +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/design/xml/GodClass.xml @@ -133,15 +133,52 @@ public class Foo { #1085 NullPointerException by at net.sourceforge.pmd.lang.java.rule.design.GodClassRule.visit(GodClassRule.java:313) 0 + + + + GodClass crashes with java.lang.NullPointerException, refs #827 + 0 + + get() { + + class Inner implements IInner { + + private Map results; + + public Inner(Map results) { + this.results = results; + } + } + return null; + } + + } + ]]> + + diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/design/xml/ImmutableField.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/design/xml/ImmutableField.xml index 9be2e842965..007376dc33f 100644 --- a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/design/xml/ImmutableField.xml +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/design/xml/ImmutableField.xml @@ -1,410 +1,483 @@ + xmlns="http://pmd.sourceforge.net/rule-tests" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://pmd.sourceforge.net/rule-tests http://pmd.sourceforge.net/rule-tests_1_0_0.xsd"> - + Could be immutable, only assigned in constructor 1 + public class Foo { + private int x; + + public Foo() { + x = 2; + } + } + ]]> + - + Could be immutable, only assigned in decl 1 + public class Foo { + private int x = 42; + } + ]]> + - + Ok, assigned twice 0 + public class Foo { + private int x; + + public Foo() { + x = 41; + } + + public void bar() { + x = 42; + } + } + ]]> + - + Ok, static field 0 + public class Foo { + private static int x = 0; + private final int y; + + public Foo() { + y = x; + x++; + } + } + ]]> + - + Ok, one constructor assigns, one doesn't 0 + public class Foo { + private int x; + + public Foo(int y) { + x = y; + } + + public Foo() { + } + } + ]]> + - + Ok, assignment via postfix expression 0 + public class Foo { + private int x; + + public Foo() { + } + + private void bar() { + x++; + } + } + ]]> + - + Postfix expressions imply mutability 0 + public class Foo { + private int x = 0; + + private void bar() { + x++; + } + } + ]]> + - + Compound assignment 0 + public class Foo { + private int w; + private int z; + + private void bar() { + w = 2; + z = 4; + } + + private void gaz() { + w += z++; + } + } + ]]> + - + Preincrement 0 + public class Foo { + private int x = 0; + + public void bar() { + ++x; + } + } + ]]> + - + Predecrement 0 + public class Foo { + private int x = 0; + + public void bar() { + --x; + } + } + ]]> + - + Compound assignment 2 0 + public class Foo { + private int x = 0; + + public void bar() { + x += 1; + } + } + ]]> + - + Rhs 2 0 + public class Foo { + private int x = 0; + + public void bar() { + Object y = new Bar(x++); + } + } + ]]> + - + Assignment in constructor is in try block 0 + public class Foo { + private int x; + + public Foo() { + try { + x = 2; + } catch (Exception e) { + } + } + } + ]]> + - + Assignment in method is in try block 0 + public class Foo { + private int x; + + public void bar() { + try { + x = 2; + } catch (Exception e) { + } + } + } + ]]> + - + Assignment in constructor in loop is ok 0 + public class Foo { + private int x; + + public Foo() { + for (int i = 0; i < 10; i++) { + x += 5; + } + } + } + ]]> + - + Assignment in anonymous inner class method is OK 0 + public class Foo { + private int x = 2; + + public Foo() { + mouseListener = new MouseAdapter() { + public void mouseClicked(MouseEvent e) { + x = e.getSource(); + super.mouseClicked(e); + } + }; + } + } + ]]> + - + Assignment through this 0 + private int foo() { + if (++this.counter < 3) { + return 0; + } + return 1; + } + } + ]]> + - + Volatile variables can't be final 0 + public final class Foo { + private volatile boolean bar = false; + + public Foo() { + } + } + ]]> + - + Bug 1740480, optional override of default value based on constructor argument check 0 0) { - positive = value; - } - } -} - ]]> + public class MyClass { + private int positive = 1; + + public MyClass(int value) { + // if negative keep default + if (value > 0) { + positive = value; + } + } + } + ]]> + - + Bug 1740480, assignment in single constructor based on constructor argument check 0 0) { - positive = value; - } else { - positive = 1; - } - } -} - ]]> + public class MyClass { + private int positive; + + public MyClass(int value) { + if (value > 0) { + positive = value; + } else { + positive = 1; + } + } + } + ]]> + - -3526212, pmd-5.0.0: ImmutableField false positive on self-inc/dec - + 3526212, pmd-5.0.0: ImmutableField false positive on self-inc/dec 0 + public void inc_test(int val) { + this.test += val; + } + } + ]]> + #946 ImmutableField false + 0 void run(final E entity, final Class> m) throws MyException { - this.counter += 1; - } -} -]]> + public class MyClass { + private long counter = 0; + + public void run(final E entity, final Class> m) throws MyException { + this.counter += 1; + } + } + ]]> + #1032 ImmutableField Rule: Private field in inner class gives false positive 0 + Bar(final int i) { + this.i = i; + } + } + + public int foo() { + final Bar b = new Bar(1); + b.i = 0; + return b.i; + } + } + ]]> #1448 [java] ImmutableField: Private field in inner class gives false positive with lambdas 0 ADDITION = (p1, p2) -> { - p1.amount += p2.amount; - return p1; -}; + private static final BinaryOperator ADDITION = (p1, p2) -> { + p1.amount += p2.amount; + return p1; + }; -private static class Purse { - private int amount; + private static class Purse { + private int amount; - public Purse(final int amount) { - this.amount = amount; - } - } + public Purse(final int amount) { + this.amount = amount; + } + } -} - ]]> + } + ]]> #410 [java] ImmutableField: False positive with lombok 0 + + + + #855 [java] ImmutableField: False positive within lambda + 0 + { + bar = new Bar(); + }); + } + + private void someMethod(Runnable action) { + action.run(); + } -@Getter -@Setter + static class Bar { + } + } + ]]> + + + + #855 [java] ImmutableField: False positive within lambda (expression style) + 0 + bar = new Bar()); + } + + private void someMethod(Runnable action) { + action.run(); + } + + static class Bar { + } + } + ]]> + + + + #1056 [java] Property ignoredAnnotations does not work for SingularField and ImmutableField + java.lang.Deprecated + 0 + diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/coupling/xml/LawOfDemeter.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/design/xml/LawOfDemeter.xml similarity index 100% rename from pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/coupling/xml/LawOfDemeter.xml rename to pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/design/xml/LawOfDemeter.xml diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/coupling/xml/LoosePackageCoupling.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/design/xml/LoosePackageCoupling.xml similarity index 100% rename from pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/coupling/xml/LoosePackageCoupling.xml rename to pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/design/xml/LoosePackageCoupling.xml diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/codesize/xml/ModifiedCyclomaticComplexity.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/design/xml/ModifiedCyclomaticComplexity.xml similarity index 100% rename from pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/codesize/xml/ModifiedCyclomaticComplexity.xml rename to pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/design/xml/ModifiedCyclomaticComplexity.xml diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/design/xml/NPathComplexity.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/design/xml/NPathComplexity.xml new file mode 100644 index 00000000000..82dcb7c33f5 --- /dev/null +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/design/xml/NPathComplexity.xml @@ -0,0 +1,221 @@ + + + + + + + + Full example + 1 + + The method 'bar()' has an NPath complexity of 2016 + + + + + + + Test default report level - report 200 + 0 + 1 + + The method 'bar()' has an NPath complexity of 200 + + + + + + Test default report level - ignore 199 + 0 + + + + + Boolean operators + 2 + 1 + + The method 'bar()' has an NPath complexity of 4 + + + + + + + + + test case for bug 3484404 (Invalid NPath calculation in return statement) + 4 + 2 + + The method 'x(boolean, boolean)' has an NPath complexity of 4 + The method 'y(boolean, boolean)' has an NPath complexity of 4 + + + + + + + NPath supports constructors + 6 + 1 + + The constructor 'Foo()' has an NPath complexity of 7 + + + + + + Backwards compatibility with minimum property + 300 + 0 + + + + diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/codesize/xml/NcssConstructorCount.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/design/xml/NcssConstructorCount.xml similarity index 100% rename from pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/codesize/xml/NcssConstructorCount.xml rename to pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/design/xml/NcssConstructorCount.xml diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/codesize/xml/NcssCount.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/design/xml/NcssCount.xml similarity index 90% rename from pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/codesize/xml/NcssCount.xml rename to pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/design/xml/NcssCount.xml index ac88a3b9d42..8cb9b90517d 100644 --- a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/codesize/xml/NcssCount.xml +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/design/xml/NcssCount.xml @@ -124,6 +124,7 @@ Full example - Standard version 60 + 10 4 The class 'Foo' has a NCSS line count of 65 (Highest = 20). @@ -137,6 +138,7 @@ Full example - JavaNcss version 60 + 10 countImports 4 @@ -215,4 +217,34 @@ public class Foo { 0 + + + Nullpointer for getQualifiedName + 0 + + diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/codesize/xml/NcssMethodCount.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/design/xml/NcssMethodCount.xml similarity index 100% rename from pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/codesize/xml/NcssMethodCount.xml rename to pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/design/xml/NcssMethodCount.xml diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/codesize/xml/NcssTypeCount.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/design/xml/NcssTypeCount.xml similarity index 100% rename from pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/codesize/xml/NcssTypeCount.xml rename to pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/design/xml/NcssTypeCount.xml diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/design/xml/OptimizableToArrayCall.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/design/xml/OptimizableToArrayCall.xml deleted file mode 100644 index b58567951c8..00000000000 --- a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/design/xml/OptimizableToArrayCall.xml +++ /dev/null @@ -1,51 +0,0 @@ - - - - - 1 - - - - - 0 - - - - - 0 - - - - - #937 OptimizableToArrayCall does not catch multilevel method chains - 1 - - - diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/design/xml/SignatureDeclareThrowsException.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/design/xml/SignatureDeclareThrowsException.xml new file mode 100644 index 00000000000..412d13a8a9f --- /dev/null +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/design/xml/SignatureDeclareThrowsException.xml @@ -0,0 +1,253 @@ + + + + + 1 + + + + + 0 + + + + + 1 + + + + + 0 + + + + + 0 + + + + + 0 + + + + + 0 + + + + + 1 + + + + + true + 0 + + + + + 0 + + + + + 0 + + + + + 0 + + + + + 0 + + + + + 0 + Bar foo() { /* blah */} +} + ]]> + java 1.5 + + + + 0 + + + + #913 SignatureDeclareThrowsException is raised twice + 1 + + + + + #1535 [java] SignatureDeclareThrowsException: ClassCastException with Annotation + 0 + implements @Readonly List<@Readonly T> {} + ]]> + + + + #350 allow throws exception when overriding a method defined elsewhere + 0 + + + + + SignatureDeclareThrowsException's IgnoreJUnitCompletely property not honored for constructors + true + 0 + + + + + #1369 ClassCastException in annotated extends + 0 + + + diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/basic/xml/SimplifiedTernary.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/design/xml/SimplifiedTernary.xml similarity index 100% rename from pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/basic/xml/SimplifiedTernary.xml rename to pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/design/xml/SimplifiedTernary.xml diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/junit/xml/SimplifyBooleanAssertion.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/design/xml/SimplifyBooleanAssertion.xml similarity index 82% rename from pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/junit/xml/SimplifyBooleanAssertion.xml rename to pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/design/xml/SimplifyBooleanAssertion.xml index cc43b31f29d..fbc49586388 100644 --- a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/junit/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 + - - - - 1 - - - - - - - 0 - - - - - \ No newline at end of file diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/design/xml/SingularField.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/design/xml/SingularField.xml index 46fcd24b8d8..5b9ff1086ab 100644 --- a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/design/xml/SingularField.xml +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/design/xml/SingularField.xml @@ -628,4 +628,20 @@ public enum Foo { ]]> + + #1056 [java] Property ignoredAnnotations does not work for SingularField and ImmutableField + java.lang.Deprecated + 0 + + diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/codesize/xml/StdCyclomaticComplexity.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/design/xml/StdCyclomaticComplexity.xml similarity index 100% rename from pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/codesize/xml/StdCyclomaticComplexity.xml rename to pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/design/xml/StdCyclomaticComplexity.xml diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/design/xml/SwitchStmtsShouldHaveDefault.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/design/xml/SwitchStmtsShouldHaveDefault.xml deleted file mode 100644 index 0904d56e314..00000000000 --- a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/design/xml/SwitchStmtsShouldHaveDefault.xml +++ /dev/null @@ -1,39 +0,0 @@ - - - - - 1 - - - - - 0 - - - diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/codesize/xml/TooManyFields.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/design/xml/TooManyFields.xml similarity index 100% rename from pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/codesize/xml/TooManyFields.xml rename to pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/design/xml/TooManyFields.xml diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/codesize/xml/TooManyMethods.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/design/xml/TooManyMethods.xml similarity index 100% rename from pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/codesize/xml/TooManyMethods.xml rename to pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/design/xml/TooManyMethods.xml diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/controversial/xml/UseObjectForClearerAPI.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/design/xml/UseObjectForClearerAPI.xml similarity index 100% rename from pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/controversial/xml/UseObjectForClearerAPI.xml rename to pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/design/xml/UseObjectForClearerAPI.xml diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/design/xml/UseUtilityClass.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/design/xml/UseUtilityClass.xml index 364d32784fe..2540573f5e0 100644 --- a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/design/xml/UseUtilityClass.xml +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/design/xml/UseUtilityClass.xml @@ -238,4 +238,126 @@ public class AccountFragment extends Fragment { } ]]> + + + + 0 + + + + + + 0 + + + + + + 0 + + + + + + 1 + + + + + + 1 + + + + + + 1 + + + + + + 0 + { + public static FooLocal get() { + return new FooLocal(); + } +} + ]]> + diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/unnecessary/xml/UselessOverridingMethod.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/design/xml/UselessOverridingMethod.xml similarity index 100% rename from pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/unnecessary/xml/UselessOverridingMethod.xml rename to pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/design/xml/UselessOverridingMethod.xml diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/comments/xml/CommentContent.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/documentation/xml/CommentContent.xml similarity index 100% rename from pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/comments/xml/CommentContent.xml rename to pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/documentation/xml/CommentContent.xml diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/documentation/xml/CommentRequired.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/documentation/xml/CommentRequired.xml new file mode 100755 index 00000000000..d06fea79d74 --- /dev/null +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/documentation/xml/CommentRequired.xml @@ -0,0 +1,430 @@ + + + + + + + Missing comments - all required (default). + 6 + + + + + All elements have javadoc comments. + 0 + + + + + Missing comments - only class level required. + Required + Ignored + Ignored + Ignored + Ignored + 1 + + + + + Too many comments - all comments are unwanted. + Unwanted + Unwanted + Unwanted + Unwanted + Unwanted + 6 + + + + + #1115 commentRequiredRule in pmd 5.1 is not working properly + 2 + + + + + #1115 commentRequiredRule in pmd 5.1 is not working properly - without new lines + 2 + + + + + #1289 CommentRequired not ignored if javadoc {@inheritDoc} anon inner classes + 0 + + + + + #1434 CommentRequired raises violation on serialVersionUID field + 0 + + + + comment required on serialVersionUID of wrong type + 1 + 3 + + + + serialVersionUID comment required + Required + 1 + 3 + + + + + #1522 [java] CommentRequired: false positive + 0 + + + + + Comment required ignores @override methods by default + 0 + + + + + Test @override methods reporting + Required + 2 + + + + + Test @override methods reporting #2 + Required + 0 + + + + + Test @override methods unwanted + Unwanted + 2 + + + + + Test getter or setter comments are ignored by default + Ignored + 0 + + + + + + Test accessors are reported when required + Ignored + Required + 3 + + + + + + Test accessors are reported when unwanted + Ignored + Unwanted + 2 + + + + + diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/comments/xml/CommentSize.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/documentation/xml/CommentSize.xml similarity index 100% rename from pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/comments/xml/CommentSize.xml rename to pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/documentation/xml/CommentSize.xml diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/design/xml/UncommentedEmptyConstructor.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/documentation/xml/UncommentedEmptyConstructor.xml similarity index 100% rename from pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/design/xml/UncommentedEmptyConstructor.xml rename to pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/documentation/xml/UncommentedEmptyConstructor.xml diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/design/xml/UncommentedEmptyMethodBody.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/documentation/xml/UncommentedEmptyMethodBody.xml similarity index 100% rename from pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/design/xml/UncommentedEmptyMethodBody.xml rename to pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/documentation/xml/UncommentedEmptyMethodBody.xml diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/empty/xml/EmptyCatchBlock.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/empty/xml/EmptyCatchBlock.xml deleted file mode 100644 index bc5f219cbc8..00000000000 --- a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/empty/xml/EmptyCatchBlock.xml +++ /dev/null @@ -1,190 +0,0 @@ - - - - - 1 - - - - - 0 - - - - - 1 - - - - - 2 - - - - - 0 - - - - - 0 - - - - - 0 - - - - - - - 1 - - - - - true - 0 - - - - - - 1 - - - - - true - 0 - - - - - - 1 - - - - - - - ^(ignored|expected)$ - 0 - - - diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/empty/xml/EmptyStaticInitializer.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/empty/xml/EmptyStaticInitializer.xml deleted file mode 100644 index 8f64a1e5e4f..00000000000 --- a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/empty/xml/EmptyStaticInitializer.xml +++ /dev/null @@ -1,30 +0,0 @@ - - - - - 1 - - - - - 0 - - - diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/controversial/xml/AssignmentInOperand.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/AssignmentInOperand.xml similarity index 100% rename from pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/controversial/xml/AssignmentInOperand.xml rename to pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/AssignmentInOperand.xml diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/design/xml/AssignmentToNonFinalStatic.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/AssignmentToNonFinalStatic.xml similarity index 100% rename from pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/design/xml/AssignmentToNonFinalStatic.xml rename to pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/AssignmentToNonFinalStatic.xml diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/AvoidAssertAsIdentifier.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/AvoidAssertAsIdentifier.xml new file mode 100644 index 00000000000..6a39b2adcb3 --- /dev/null +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/AvoidAssertAsIdentifier.xml @@ -0,0 +1,34 @@ + + + + + 1 + + java 1.3 + + + + 1 + + java 1.3 + + diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/basic/xml/AvoidBranchingStatementAsLastInLoop.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/AvoidBranchingStatementAsLastInLoop.xml similarity index 100% rename from pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/basic/xml/AvoidBranchingStatementAsLastInLoop.xml rename to pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/AvoidBranchingStatementAsLastInLoop.xml diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/finalizers/xml/AvoidCallingFinalize.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/AvoidCallingFinalize.xml similarity index 100% rename from pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/finalizers/xml/AvoidCallingFinalize.xml rename to pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/AvoidCallingFinalize.xml diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/strictexception/xml/AvoidCatchingNPE.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/AvoidCatchingNPE.xml similarity index 100% rename from pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/strictexception/xml/AvoidCatchingNPE.xml rename to pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/AvoidCatchingNPE.xml diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/strictexception/xml/AvoidCatchingThrowable.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/AvoidCatchingThrowable.xml similarity index 100% rename from pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/strictexception/xml/AvoidCatchingThrowable.xml rename to pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/AvoidCatchingThrowable.xml diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/basic/xml/AvoidDecimalLiteralsInBigDecimalConstructor.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/AvoidDecimalLiteralsInBigDecimalConstructor.xml similarity index 100% rename from pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/basic/xml/AvoidDecimalLiteralsInBigDecimalConstructor.xml rename to pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/AvoidDecimalLiteralsInBigDecimalConstructor.xml diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/strings/xml/AvoidDuplicateLiterals.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/AvoidDuplicateLiterals.xml similarity index 100% rename from pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/strings/xml/AvoidDuplicateLiterals.xml rename to pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/AvoidDuplicateLiterals.xml diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/AvoidEnumAsIdentifier.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/AvoidEnumAsIdentifier.xml new file mode 100644 index 00000000000..15bb7d26ec2 --- /dev/null +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/AvoidEnumAsIdentifier.xml @@ -0,0 +1,34 @@ + + + + + 1 + + java 1.4 + + + + 1 + + java 1.4 + + diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/naming/xml/AvoidFieldNameMatchingMethodName.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/AvoidFieldNameMatchingMethodName.xml similarity index 100% rename from pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/naming/xml/AvoidFieldNameMatchingMethodName.xml rename to pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/AvoidFieldNameMatchingMethodName.xml diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/naming/xml/AvoidFieldNameMatchingTypeName.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/AvoidFieldNameMatchingTypeName.xml similarity index 100% rename from pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/naming/xml/AvoidFieldNameMatchingTypeName.xml rename to pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/AvoidFieldNameMatchingTypeName.xml diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/design/xml/AvoidInstanceofChecksInCatchClause.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/AvoidInstanceofChecksInCatchClause.xml similarity index 100% rename from pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/design/xml/AvoidInstanceofChecksInCatchClause.xml rename to pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/AvoidInstanceofChecksInCatchClause.xml diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/controversial/xml/AvoidLiteralsInIfCondition.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/AvoidLiteralsInIfCondition.xml similarity index 100% rename from pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/controversial/xml/AvoidLiteralsInIfCondition.xml rename to pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/AvoidLiteralsInIfCondition.xml diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/strictexception/xml/AvoidLosingExceptionInformation.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/AvoidLosingExceptionInformation.xml similarity index 100% rename from pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/strictexception/xml/AvoidLosingExceptionInformation.xml rename to pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/AvoidLosingExceptionInformation.xml diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/basic/xml/AvoidMultipleUnaryOperators.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/AvoidMultipleUnaryOperators.xml similarity index 100% rename from pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/basic/xml/AvoidMultipleUnaryOperators.xml rename to pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/AvoidMultipleUnaryOperators.xml diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/basic/xml/AvoidUsingOctalValues.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/AvoidUsingOctalValues.xml similarity index 100% rename from pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/basic/xml/AvoidUsingOctalValues.xml rename to pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/AvoidUsingOctalValues.xml diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/design/xml/BadComparison.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/BadComparison.xml similarity index 100% rename from pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/design/xml/BadComparison.xml rename to pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/BadComparison.xml diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/javabeans/xml/BeanMembersShouldSerialize.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/BeanMembersShouldSerialize.xml similarity index 100% rename from pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/javabeans/xml/BeanMembersShouldSerialize.xml rename to pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/BeanMembersShouldSerialize.xml diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/basic/xml/BrokenNullCheck.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/BrokenNullCheck.xml similarity index 100% rename from pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/basic/xml/BrokenNullCheck.xml rename to pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/BrokenNullCheck.xml diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/android/xml/CallSuperFirst.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/CallSuperFirst.xml similarity index 100% rename from pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/android/xml/CallSuperFirst.xml rename to pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/CallSuperFirst.xml diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/android/xml/CallSuperLast.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/CallSuperLast.xml similarity index 100% rename from pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/android/xml/CallSuperLast.xml rename to pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/CallSuperLast.xml diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/basic/xml/CheckSkipResult.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/CheckSkipResult.xml similarity index 100% rename from pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/basic/xml/CheckSkipResult.xml rename to pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/CheckSkipResult.xml diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/basic/xml/ClassCastExceptionWithToArray.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/ClassCastExceptionWithToArray.xml similarity index 100% rename from pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/basic/xml/ClassCastExceptionWithToArray.xml rename to pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/ClassCastExceptionWithToArray.xml diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/clone/xml/CloneMethodMustBePublic.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/CloneMethodMustBePublic.xml similarity index 100% rename from pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/clone/xml/CloneMethodMustBePublic.xml rename to pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/CloneMethodMustBePublic.xml diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/CloneMethodMustImplementCloneable.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/CloneMethodMustImplementCloneable.xml new file mode 100644 index 00000000000..38d0397212f --- /dev/null +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/CloneMethodMustImplementCloneable.xml @@ -0,0 +1,202 @@ + + + + + 0 + + + + + 1 + + + + + 0 + + + + + 0 + + + + + 0 + + + + + 0 + + + + + 0 + + + + + 0 + + + + + 0 + + + + + 0 + + + + + 0 + + + + + 0 + + + + + 0 + + + + + #1534 [java] CloneMethodMustImplementCloneable: ClassCastException with Annotation (java8) + 0 + implements @Readonly List<@Readonly T> {} + ]]> + + + + #1532 [java] CloneMethodMustImplementCloneable: Implemented Interface extends Cloneable - part 1: interface + 0 + + + + + #1532 [java] CloneMethodMustImplementCloneable: Implemented Interface extends Cloneable + 0 + + + diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/clone/xml/CloneMethodReturnTypeMustMatchClassName.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/CloneMethodReturnTypeMustMatchClassName.xml similarity index 100% rename from pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/clone/xml/CloneMethodReturnTypeMustMatchClassName.xml rename to pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/CloneMethodReturnTypeMustMatchClassName.xml diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/clone/xml/CloneThrowsCloneNotSupportedException.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/CloneThrowsCloneNotSupportedException.xml similarity index 100% rename from pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/clone/xml/CloneThrowsCloneNotSupportedException.xml rename to pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/CloneThrowsCloneNotSupportedException.xml diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/design/xml/CloseResource.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/CloseResource.xml similarity index 100% rename from pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/design/xml/CloseResource.xml rename to pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/CloseResource.xml diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/design/xml/CompareObjectsWithEquals.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/CompareObjectsWithEquals.xml similarity index 94% rename from pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/design/xml/CompareObjectsWithEquals.xml rename to pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/CompareObjectsWithEquals.xml index d86625b9632..33d694078a6 100644 --- a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/design/xml/CompareObjectsWithEquals.xml +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/CompareObjectsWithEquals.xml @@ -201,6 +201,18 @@ public class CompareWithEqualsTest { { return a == b; } +} + ]]> + + + #885 CompareObjectsWithEquals fails with Enum class + 0 + a, Enum b) + { + return a == b; + } } ]]> diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/design/xml/ConstructorCallsOverridableMethod.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/ConstructorCallsOverridableMethod.xml similarity index 100% rename from pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/design/xml/ConstructorCallsOverridableMethod.xml rename to pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/ConstructorCallsOverridableMethod.xml diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/controversial/xml/DataflowAnomalyAnalysis.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/DataflowAnomalyAnalysis.xml similarity index 100% rename from pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/controversial/xml/DataflowAnomalyAnalysis.xml rename to pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/DataflowAnomalyAnalysis.xml diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/controversial/xml/DoNotCallGarbageCollectionExplicitly.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/DoNotCallGarbageCollectionExplicitly.xml similarity index 100% rename from pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/controversial/xml/DoNotCallGarbageCollectionExplicitly.xml rename to pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/DoNotCallGarbageCollectionExplicitly.xml diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/j2ee/xml/DoNotCallSystemExit.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/DoNotCallSystemExit.xml similarity index 100% rename from pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/j2ee/xml/DoNotCallSystemExit.xml rename to pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/DoNotCallSystemExit.xml diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/DoNotExtendJavaLangThrowable.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/DoNotExtendJavaLangThrowable.xml new file mode 100644 index 00000000000..b2ee04a52b0 --- /dev/null +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/DoNotExtendJavaLangThrowable.xml @@ -0,0 +1,36 @@ + + + + + 1 + + + + + 1 + + + + + 0 + + + diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/android/xml/DoNotHardCodeSDCard.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/DoNotHardCodeSDCard.xml similarity index 100% rename from pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/android/xml/DoNotHardCodeSDCard.xml rename to pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/DoNotHardCodeSDCard.xml diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/strictexception/xml/DoNotThrowExceptionInFinally.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/DoNotThrowExceptionInFinally.xml similarity index 100% rename from pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/strictexception/xml/DoNotThrowExceptionInFinally.xml rename to pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/DoNotThrowExceptionInFinally.xml diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/controversial/xml/DontImportSun.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/DontImportSun.xml similarity index 100% rename from pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/controversial/xml/DontImportSun.xml rename to pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/DontImportSun.xml diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/basic/xml/DontUseFloatTypeForLoopIndices.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/DontUseFloatTypeForLoopIndices.xml similarity index 100% rename from pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/basic/xml/DontUseFloatTypeForLoopIndices.xml rename to pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/DontUseFloatTypeForLoopIndices.xml diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/EmptyCatchBlock.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/EmptyCatchBlock.xml new file mode 100644 index 00000000000..198c7ec873a --- /dev/null +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/EmptyCatchBlock.xml @@ -0,0 +1,205 @@ + + + + + 1 + + + + + 0 + + + + + 1 + + + + + 2 + + + + + 0 + + + + + 0 + + + + + 0 + + + + + + + 1 + + + + + true + 0 + + + + + + 1 + + + + + true + 0 + + + + + + 1 + + + + + + + 0 + + + + + ^idontcare$ + 0 + + + diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/finalizers/xml/EmptyFinalizer.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/EmptyFinalizer.xml similarity index 100% rename from pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/finalizers/xml/EmptyFinalizer.xml rename to pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/EmptyFinalizer.xml diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/empty/xml/EmptyFinallyBlock.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/EmptyFinallyBlock.xml similarity index 100% rename from pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/empty/xml/EmptyFinallyBlock.xml rename to pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/EmptyFinallyBlock.xml diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/empty/xml/EmptyIfStmt.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/EmptyIfStmt.xml similarity index 100% rename from pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/empty/xml/EmptyIfStmt.xml rename to pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/EmptyIfStmt.xml diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/empty/xml/EmptyInitializer.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/EmptyInitializer.xml similarity index 83% rename from pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/empty/xml/EmptyInitializer.xml rename to pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/EmptyInitializer.xml index cafa40d2e1e..9a994bf1302 100644 --- a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/empty/xml/EmptyInitializer.xml +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/EmptyInitializer.xml @@ -46,6 +46,19 @@ initializer not empty + + + + 0 + diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/empty/xml/EmptyStatementBlock.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/EmptyStatementBlock.xml similarity index 100% rename from pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/empty/xml/EmptyStatementBlock.xml rename to pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/EmptyStatementBlock.xml diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/empty/xml/EmptyStatementNotInLoop.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/EmptyStatementNotInLoop.xml similarity index 100% rename from pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/empty/xml/EmptyStatementNotInLoop.xml rename to pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/EmptyStatementNotInLoop.xml diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/empty/xml/EmptySwitchStatements.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/EmptySwitchStatements.xml similarity index 100% rename from pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/empty/xml/EmptySwitchStatements.xml rename to pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/EmptySwitchStatements.xml diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/empty/xml/EmptySynchronizedBlock.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/EmptySynchronizedBlock.xml similarity index 100% rename from pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/empty/xml/EmptySynchronizedBlock.xml rename to pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/EmptySynchronizedBlock.xml diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/empty/xml/EmptyTryBlock.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/EmptyTryBlock.xml similarity index 100% rename from pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/empty/xml/EmptyTryBlock.xml rename to pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/EmptyTryBlock.xml diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/empty/xml/EmptyWhileStmt.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/EmptyWhileStmt.xml similarity index 100% rename from pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/empty/xml/EmptyWhileStmt.xml rename to pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/EmptyWhileStmt.xml diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/design/xml/EqualsNull.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/EqualsNull.xml similarity index 100% rename from pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/design/xml/EqualsNull.xml rename to pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/EqualsNull.xml diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/finalizers/xml/FinalizeDoesNotCallSuperFinalize.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/FinalizeDoesNotCallSuperFinalize.xml similarity index 100% rename from pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/finalizers/xml/FinalizeDoesNotCallSuperFinalize.xml rename to pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/FinalizeDoesNotCallSuperFinalize.xml diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/finalizers/xml/FinalizeOnlyCallsSuperFinalize.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/FinalizeOnlyCallsSuperFinalize.xml similarity index 100% rename from pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/finalizers/xml/FinalizeOnlyCallsSuperFinalize.xml rename to pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/FinalizeOnlyCallsSuperFinalize.xml diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/finalizers/xml/FinalizeOverloaded.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/FinalizeOverloaded.xml similarity index 100% rename from pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/finalizers/xml/FinalizeOverloaded.xml rename to pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/FinalizeOverloaded.xml diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/finalizers/xml/FinalizeShouldBeProtected.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/FinalizeShouldBeProtected.xml similarity index 100% rename from pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/finalizers/xml/FinalizeShouldBeProtected.xml rename to pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/FinalizeShouldBeProtected.xml diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/design/xml/IdempotentOperations.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/IdempotentOperations.xml similarity index 100% rename from pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/design/xml/IdempotentOperations.xml rename to pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/IdempotentOperations.xml diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/imports/xml/ImportFromSamePackage.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/ImportFromSamePackage.xml similarity index 100% rename from pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/imports/xml/ImportFromSamePackage.xml rename to pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/ImportFromSamePackage.xml diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/design/xml/InstantiationToGetClass.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/InstantiationToGetClass.xml similarity index 100% rename from pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/design/xml/InstantiationToGetClass.xml rename to pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/InstantiationToGetClass.xml diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/loggingjava/xml/InvalidSlf4jMessageFormat.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/InvalidSlf4jMessageFormat.xml similarity index 81% rename from pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/loggingjava/xml/InvalidSlf4jMessageFormat.xml rename to pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/InvalidSlf4jMessageFormat.xml index ef58a1a79b7..0cbc4296a59 100644 --- a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/loggingjava/xml/InvalidSlf4jMessageFormat.xml +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/InvalidSlf4jMessageFormat.xml @@ -221,6 +221,51 @@ public class Foo int attempt = 0; LOGGER.info("test (attempt #{})", ++attempt); } +} + ]]> + + + + #721 NPE in PMD 5.8.1 InvalidSlf4jMessageFormat + 0 + + + + + #1291 [java] InvalidSlf4jMessageFormat false positive: too many arguments with string concatenation operator + 0 + diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/junit/xml/JUnitSpelling.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/JUnitSpelling.xml similarity index 100% rename from pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/junit/xml/JUnitSpelling.xml rename to pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/JUnitSpelling.xml diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/junit/xml/JUnitStaticSuite.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/JUnitStaticSuite.xml similarity index 100% rename from pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/junit/xml/JUnitStaticSuite.xml rename to pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/JUnitStaticSuite.xml diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/basic/xml/JumbledIncrementer.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/JumbledIncrementer.xml similarity index 100% rename from pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/basic/xml/JumbledIncrementer.xml rename to pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/JumbledIncrementer.xml diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/loggingjava/xml/LoggerIsNotStaticFinal.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/LoggerIsNotStaticFinal.xml similarity index 100% rename from pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/loggingjava/xml/LoggerIsNotStaticFinal.xml rename to pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/LoggerIsNotStaticFinal.xml diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/naming/xml/MethodWithSameNameAsEnclosingClass.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/MethodWithSameNameAsEnclosingClass.xml similarity index 100% rename from pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/naming/xml/MethodWithSameNameAsEnclosingClass.xml rename to pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/MethodWithSameNameAsEnclosingClass.xml diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/basic/xml/MisplacedNullCheck.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/MisplacedNullCheck.xml similarity index 100% rename from pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/basic/xml/MisplacedNullCheck.xml rename to pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/MisplacedNullCheck.xml diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/design/xml/MissingBreakInSwitch.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/MissingBreakInSwitch.xml similarity index 100% rename from pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/design/xml/MissingBreakInSwitch.xml rename to pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/MissingBreakInSwitch.xml diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/MissingSerialVersionUID.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/MissingSerialVersionUID.xml new file mode 100644 index 00000000000..0a6ac38ecb7 --- /dev/null +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/MissingSerialVersionUID.xml @@ -0,0 +1,119 @@ + + + + + 0 + + + + + 1 + + + + + 1 + + + + + 0 + + + + + 0 + + + + + 0 + + + + abstract case, see #1352 + 1 + + + + inherited abstract case + 1 + + + + + 0 + + + + + #1078 [java] MissingSerialVersionUID rule does not seem to catch inherited classes + 1 + + + + + #1350 [java] MissingSerialVersionUID false-positive on interfaces + 0 + > extends MutablePrimaryIdentifier, Serializable { +} + ]]> + + diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/design/xml/MissingStaticMethodInNonInstantiatableClass.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/MissingStaticMethodInNonInstantiatableClass.xml similarity index 100% rename from pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/design/xml/MissingStaticMethodInNonInstantiatableClass.xml rename to pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/MissingStaticMethodInNonInstantiatableClass.xml diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/loggingjava/xml/MoreThanOneLogger.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/MoreThanOneLogger.xml similarity index 100% rename from pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/loggingjava/xml/MoreThanOneLogger.xml rename to pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/MoreThanOneLogger.xml diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/design/xml/NonCaseLabelInSwitchStatement.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/NonCaseLabelInSwitchStatement.xml similarity index 100% rename from pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/design/xml/NonCaseLabelInSwitchStatement.xml rename to pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/NonCaseLabelInSwitchStatement.xml diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/design/xml/NonStaticInitializer.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/NonStaticInitializer.xml similarity index 100% rename from pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/design/xml/NonStaticInitializer.xml rename to pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/NonStaticInitializer.xml diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/NullAssignment.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/NullAssignment.xml new file mode 100644 index 00000000000..ef656c0d503 --- /dev/null +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/NullAssignment.xml @@ -0,0 +1,189 @@ + + + + + 0 + + + + + 1 + + + + + 0 + + + + + 0 + + + + null assignment in ternary - initialization + 0 + + + + null assignment in ternary + 1 + + + + null assignment in ternary, part deux - initialization + 0 + + + + null assignment in ternary, part deux + 1 + + + + + 0 + + + + + 0 + + + + + 0 + + + + + [java] NullAssignment false positive - initialization #629 + 0 + test ? truthy : null); + } +} + ]]> + + + + [java] NullAssignment false positive - no direct assignment, but lambda #629 + 0 + test ? truthy : null); + } +} + ]]> + + + + [java] NullAssignment false positive - return with ternary #629 + 0 + + + diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/basic/xml/OverrideBothEqualsAndHashcode.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/OverrideBothEqualsAndHashcode.xml similarity index 100% rename from pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/basic/xml/OverrideBothEqualsAndHashcode.xml rename to pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/OverrideBothEqualsAndHashcode.xml diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/clone/xml/ProperCloneImplementation.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/ProperCloneImplementation.xml similarity index 100% rename from pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/clone/xml/ProperCloneImplementation.xml rename to pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/ProperCloneImplementation.xml diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/loggingjakartacommons/xml/ProperLogger.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/ProperLogger.xml similarity index 76% rename from pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/loggingjakartacommons/xml/ProperLogger.xml rename to pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/ProperLogger.xml index 85d79508117..73f92b6747e 100644 --- a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/loggingjakartacommons/xml/ProperLogger.xml +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/ProperLogger.xml @@ -36,6 +36,18 @@ public class Foo { Foo(Log log) { this.log = log; } +} + ]]> + + + + 1 + @@ -51,6 +63,18 @@ public class Foo { Foo(Log log) { this.log = log; } +} + ]]> + + + + 0 + diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/design/xml/ReturnEmptyArrayRatherThanNull.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/ReturnEmptyArrayRatherThanNull.xml similarity index 100% rename from pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/design/xml/ReturnEmptyArrayRatherThanNull.xml rename to pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/ReturnEmptyArrayRatherThanNull.xml diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/basic/xml/ReturnFromFinallyBlock.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/ReturnFromFinallyBlock.xml similarity index 100% rename from pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/basic/xml/ReturnFromFinallyBlock.xml rename to pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/ReturnFromFinallyBlock.xml diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/design/xml/SimpleDateFormatNeedsLocale.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/SimpleDateFormatNeedsLocale.xml similarity index 100% rename from pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/design/xml/SimpleDateFormatNeedsLocale.xml rename to pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/SimpleDateFormatNeedsLocale.xml diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/SingleMethodSingleton.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/SingleMethodSingleton.xml new file mode 100644 index 00000000000..ea2cc5b43df --- /dev/null +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/SingleMethodSingleton.xml @@ -0,0 +1,74 @@ + + + + + 1 + + + + + + + 0 + + + + + + + + 0 + + + + + + + + + diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/design/xml/SingletonClassReturningNewInstance.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/SingletonClassReturningNewInstance.xml similarity index 100% rename from pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/design/xml/SingletonClassReturningNewInstance.xml rename to pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/SingletonClassReturningNewInstance.xml diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/j2ee/xml/StaticEJBFieldShouldBeFinal.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/StaticEJBFieldShouldBeFinal.xml similarity index 100% rename from pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/j2ee/xml/StaticEJBFieldShouldBeFinal.xml rename to pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/StaticEJBFieldShouldBeFinal.xml diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/strings/xml/StringBufferInstantiationWithChar.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/StringBufferInstantiationWithChar.xml similarity index 100% rename from pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/strings/xml/StringBufferInstantiationWithChar.xml rename to pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/StringBufferInstantiationWithChar.xml diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/naming/xml/SuspiciousEqualsMethodName.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/SuspiciousEqualsMethodName.xml similarity index 100% rename from pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/naming/xml/SuspiciousEqualsMethodName.xml rename to pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/SuspiciousEqualsMethodName.xml diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/naming/xml/SuspiciousHashcodeMethodName.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/SuspiciousHashcodeMethodName.xml similarity index 100% rename from pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/naming/xml/SuspiciousHashcodeMethodName.xml rename to pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/SuspiciousHashcodeMethodName.xml diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/controversial/xml/SuspiciousOctalEscape.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/SuspiciousOctalEscape.xml similarity index 100% rename from pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/controversial/xml/SuspiciousOctalEscape.xml rename to pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/SuspiciousOctalEscape.xml diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/junit/xml/TestClassWithoutTestCases.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/TestClassWithoutTestCases.xml similarity index 100% rename from pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/junit/xml/TestClassWithoutTestCases.xml rename to pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/TestClassWithoutTestCases.xml diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/basic/xml/UnconditionalIfStatement.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/UnconditionalIfStatement.xml similarity index 100% rename from pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/basic/xml/UnconditionalIfStatement.xml rename to pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/UnconditionalIfStatement.xml diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/junit/xml/UnnecessaryBooleanAssertion.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/UnnecessaryBooleanAssertion.xml similarity index 88% rename from pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/junit/xml/UnnecessaryBooleanAssertion.xml rename to pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/errorprone/xml/UnnecessaryBooleanAssertion.xml index d06f84fb169..a0be95c777d 100644 --- a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/junit/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 + - - - - 0 - - - - - 0 - - - - - 1 - - - - - 0 - - - - - 1 - - - - - 0 - - - - - 1 - - - - - 0 - - - - - 1 - - - - - 0 - - - - - 1 - - - - - 0 - - - - - 1 - - - - - 0 - - - - - 1 - - - - - 0 - - - - - 1 - - - - - 0 - - - - - 1 - - - - - 0 - - - - - 1 - - - - - 0 - - - - - 1 - - - - - #1078 Package statement introduces false positive UnnecessaryFullyQualifiedName violation - 0 - - - - - #1404 Java8 'Unnecessary use of fully qualified name' in Streams Collector - 1 - 17 - - Unnecessary use of fully qualified name 'Collectors.toList' due to existing static import 'java.util.stream.Collectors.toList' - - - - - - #1436 UnnecessaryFullyQualifiedName false positive on clashing static imports with enums - 0 - - - - - #1546 part 1 UnnecessaryFullyQualifiedName doesn't take into consideration conflict resolution - 0 - - - - - #1546 part 2 UnnecessaryFullyQualifiedName doesn't take into consideration conflict resolution - 0 - - - - #1555 - UnnecessaryFullyQualifiedName for conflict resolution with inner class - 0 - - - diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/imports/xml/UnusedImports.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/imports/xml/UnusedImports.xml deleted file mode 100644 index 4ff63b31e21..00000000000 --- a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/imports/xml/UnusedImports.xml +++ /dev/null @@ -1,348 +0,0 @@ - - - - - 1 - - - - - 0 - - - - - 2 - - - - - 0 - - - - - 0 - - - - - 0 - - java 1.5 - - - - 0 - x = new ArrayList(); -} - ]]> - java 1.5 - - - - 0 - - java 1.5 - - - - 0 - - java 1.5 - - - - 0 - - - - - 1 - - - - - 0 - - java 1.5 - - - - 1 - - java 1.5 - - - - 0 - - - - - 0 - - - - - - 0 - - - - - bug #254 False+ : UnusedImport with Javadoc @link - 0 - - - - #1181 unused import false positive if used as parameter in javadoc only. - 0 - - - - #1280 False Positive in UnusedImports when import used in javadoc - 0 - - - - #914 False +ve from UnusedImports with wildcard static imports - 0 - - - - - #1465 False Positve UnusedImports with javadoc @link - 0 - - * An agent is active if it has not posted a {@link AgentStateChangeEvent} containing {@link AgentState#TERMINATED}. - * - * @return agent handles. - * @see OtherState#TERMINATED - */ - Iterable getAgentHandles(); -} - ]]> - - - - #1547 False Positve UnusedImports with javadoc for identifiers with underscores - 0 - - - diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/javabeans/xml/MissingSerialVersionUID.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/javabeans/xml/MissingSerialVersionUID.xml deleted file mode 100644 index 7837f07499f..00000000000 --- a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/javabeans/xml/MissingSerialVersionUID.xml +++ /dev/null @@ -1,91 +0,0 @@ - - - - - 0 - - - - - 1 - - - - - 1 - - - - - 0 - - - - - 0 - - - - - 0 - - - - - 0 - - - - - 0 - - - diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/junit/xml/JUnitTestContainsTooManyAsserts.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/junit/xml/JUnitTestContainsTooManyAsserts.xml deleted file mode 100644 index 839d9cb0e5f..00000000000 --- a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/junit/xml/JUnitTestContainsTooManyAsserts.xml +++ /dev/null @@ -1,136 +0,0 @@ - - - - - 0 - - - - - 0 - - - - - 0 - - - - - 0 - - - - - 1 - - - - - 1 - - - - - 2 - 0 - - - - - 2 - 0 - - - \ No newline at end of file diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/loggingjakartacommons/xml/GuardDebugLogging.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/loggingjakartacommons/xml/GuardDebugLogging.xml deleted file mode 100644 index 46c089fd8e2..00000000000 --- a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/loggingjakartacommons/xml/GuardDebugLogging.xml +++ /dev/null @@ -1,140 +0,0 @@ - - - - - 0 - - - - - 2 - - - - - 1 - - - - ok #1189 GuardLogStatementRule and GuardDebugLoggingRule broken for log4j2 - 0 - - - - violation - wrong guard #1189 GuardLogStatementRule and GuardDebugLoggingRule broken for log4j2 - 1 - - - - violation - no if #1189 GuardLogStatementRule and GuardDebugLoggingRule broken for log4j2 - 1 - - - - #1224 GuardDebugLogging broken in 5.1.1 - missing additive statement check in log statement - 0 - - - - #1341 pmd:GuardDebugLogging violates LOGGER.debug with format "{}" - 0 - - - diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/loggingjakartacommons/xml/GuardLogStatement.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/loggingjakartacommons/xml/GuardLogStatement.xml deleted file mode 100644 index 2c43ef76ad7..00000000000 --- a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/loggingjakartacommons/xml/GuardLogStatement.xml +++ /dev/null @@ -1,93 +0,0 @@ - - - - - 0 - - - - - 1 - - - - - debug,trace - isDebugEnabled,isTraceEnabled - 1 - - - - ok #1189 GuardLogStatementRule and GuardDebugLoggingRule broken for log4j2 - 0 - - - - violation - wrong guard #1189 GuardLogStatementRule and GuardDebugLoggingRule broken for log4j2 - 2 - - - diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/loggingjava/xml/GuardLogStatementJavaUtil.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/loggingjava/xml/GuardLogStatementJavaUtil.xml deleted file mode 100644 index 29235c132c3..00000000000 --- a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/loggingjava/xml/GuardLogStatementJavaUtil.xml +++ /dev/null @@ -1,122 +0,0 @@ - - - - - 0 - - - - - 1 - - - - #1203 GuardLogStatementJavaUtil issues warning for severe level not being specified as property - finest,finer,fine,info - isLoggable - 0 - - - - #1227 GuardLogStatementJavaUtil doesn't catch log(Level.FINE, "msg" + " msg") calls - 1 - 8 - - - - #1335 GuardLogStatementJavaUtil should not apply to SLF4J Logger - 0 - - - - #1347 False positive for GuardLogStatementJavaUtil - 0 - - - - #1398 False positive for GuardLogStatementJavaUtil with Log4j - 0 - - - diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/metrics/xml/CyclomaticComplexity.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/metrics/xml/CyclomaticComplexity.xml deleted file mode 100644 index 8f405a049e6..00000000000 --- a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/metrics/xml/CyclomaticComplexity.xml +++ /dev/null @@ -1,457 +0,0 @@ - - - - - - - - - Simple method - 1 - 1 - 2 - - The class 'Foo' has a total cyclomatic complexity of 1 (highest 1). - The method 'foo()' has a cyclomatic complexity of 1. - - - - - - - - testLessComplicatedThanReportLevel - 0 - - - - - - - Complicated method - 1 - - The method 'example()' has a cyclomatic complexity of 17. - - - - - - Complicated method (ignoreBooleanPath) - ignoreBooleanPaths - 1 - - The method 'example()' has a cyclomatic complexity of 10. - - - - - - Constructor - 1 - 1 - - The constructor 'Foo()' has a cyclomatic complexity of 1. - - - - - - - - Test class report level - 17 - 999 - 1 - - - - - Test method report level - 999 - 17 - 1 - - - - - - - - - #984 Cyclomatic complexity should treat constructors like methods: 1 - reportMethods=true - 1 - 1 - - - - - #984 Cyclomatic complexity should treat constructors like methods: 2 -reportMethods=false - 999 - 0 - - - - - #985 Suppressed methods shouldn't affect avg CyclomaticComplexity - 2 - 0 - - - - - - - - - - - Standard Cyclo should count empty switch labels too - 2 - 1 - - The method 'switchCase()' has a cyclomatic complexity of 3. - - - - - - IgnoreBooleanPaths Cyclo should not count empty switch labels - ignoreBooleanPaths - 2 - 1 - - The method 'switchCase()' has a cyclomatic complexity of 2. - - - - - - 2 || y < 4) { - while (x++ < 10 && !(y-- < 0)); - } else if (a && b || x < 4) { - return; - } - } - } - ]]> - - - - Standard Cyclo should count boolean paths - 2 - 1 - - The method 'foo()' has a cyclomatic complexity of 8. - - - - - - IgnoreBooleanPaths Cyclo should not count boolean paths - ignoreBooleanPaths - 2 - 1 - - The method 'foo()' has a cyclomatic complexity of 4. - - - - - - - - - - - Test many unreported methods - 1 - - The class 'Complicated' has a total cyclomatic complexity of 80 (highest 8). - - - - - diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/metrics/xml/NPathComplexity.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/metrics/xml/NPathComplexity.xml deleted file mode 100644 index 0626139634d..00000000000 --- a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/metrics/xml/NPathComplexity.xml +++ /dev/null @@ -1,199 +0,0 @@ - - - - - - - - Full example - 1 - - The method 'bar()' has an NPath complexity of 2016 - - - - - - - Test default report level - report 200 - 0 - 1 - - The method 'bar()' has an NPath complexity of 200 - - - - - - Test default report level - ignore 199 - 0 - - - - - Boolean operators - 2 - 1 - - The method 'bar()' has an NPath complexity of 4 - - - - - - - - - test case for bug 3484404 (Invalid NPath calculation in return statement) - 4 - 2 - - The method 'x(boolean, boolean)' has an NPath complexity of 4 - The method 'y(boolean, boolean)' has an NPath complexity of 4 - - - - - - - NPath supports constructors - 6 - 1 - - The constructor 'Foo()' has an NPath complexity of 7 - - - - diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/migrating/xml/AvoidAssertAsIdentifier.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/migrating/xml/AvoidAssertAsIdentifier.xml deleted file mode 100644 index 116576c271a..00000000000 --- a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/migrating/xml/AvoidAssertAsIdentifier.xml +++ /dev/null @@ -1,20 +0,0 @@ - - - - - 2 - - java 1.3 - - diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/migrating/xml/AvoidEnumAsIdentifier.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/migrating/xml/AvoidEnumAsIdentifier.xml deleted file mode 100644 index 93dad20c528..00000000000 --- a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/migrating/xml/AvoidEnumAsIdentifier.xml +++ /dev/null @@ -1,20 +0,0 @@ - - - - - 2 - - java 1.4 - - diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/migrating/xml/ForLoopCanBeForeach.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/migrating/xml/ForLoopCanBeForeach.xml deleted file mode 100644 index 66cb51ac90a..00000000000 --- a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/migrating/xml/ForLoopCanBeForeach.xml +++ /dev/null @@ -1,270 +0,0 @@ - - - - - - Positive with list - 1 - l) { - for (int i = 0; i < l.size(); i++) { - System.out.println(l.get(i)); - } - } - } - ]]> - - - - Positive with lower or equal - 1 - lo) { - for (int i = 0; i <= lo.size() - 1; i++) { - System.out.println(lo.get(i)); - } - } - } - ]]> - - - - Usage of index var outside get - 0 - l) { - for (int i = 0; i < l.size(); i++) { - System.out.println(i + ": " + l.get(i)); - - } - } - } - ]]> - - - - Subclass of List - 1 - l) { - for (int i = 0; i < l.size(); i++) { - System.out.println(l.get(i)); - - } - } - } - ]]> - - - - Get called on another list - 0 - l) { - List l2 = new ArrayList<>(l); - for (int i = 0; i < l.size(); i++) { - System.out.println(l2.get(i)); - } - } - } - ]]> - - - - Backwards iteration - 0 - l) { - for (int i = l.size() - 1; i > 0; i-= 1) { - System.out.println(i + ": " + l.get(i)); - } - } - } - ]]> - - - - - Index var initialized outside for init - 1 - l) { - int i = 0; - for (; i < l.size(); i++) { - System.out.println(l.get(i)); - } - } - } - ]]> - - - - - Array positives - 2 - - - - - Consider iterators - 1 - path = null; - for (Iterator i = path.iterator(); i.hasNext();) { - DataFlowNode inode = i.next(); - if (inode.getVariableAccess() == null) { - continue; - } - } - } - } - ]]> - - - - - Index var starts after zero - 0 - l) { - for (int i = 1; i < filters.size(); i++) { - builder.append(' ').append(getOperator()).append(' '); - builder.append(filters.get(i)); - } - } - } - ]]> - - - - - Prefix increment should work - 1 - apexNode = (ApexNode) children[i]; - apexNode.jjtAccept(visitor, data); - } - } - return data; - } - } - ]]> - - - - Index inside arithmetic expression should whitelist the loop - 0 - - - - - - Array assignment whitelists the loop - 0 - - - - - Consider iterators only if safe - 0 - path = null; - - for (Iterator i = path.iterator(); i.hasNext();) { - DataFlowNode inode = i.next(); - path.remove(inode); // throws ConcurrentModificationException if it were a foreach - if (inode.getVariableAccess() == null) { - continue; - } - } - } - } - ]]> - - - - Do not report iterator loop if we can't find iterator decl - 0 - i = path.iterator(); i.hasNext();) { - DataFlowNode inode = i.next(); - if (inode.getVariableAccess() == null) { - continue; - } - } - } - } - ]]> - - - - diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/migrating/xml/JUnit4TestShouldUseAfterAnnotation.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/migrating/xml/JUnit4TestShouldUseAfterAnnotation.xml deleted file mode 100644 index a71967faf6f..00000000000 --- a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/migrating/xml/JUnit4TestShouldUseAfterAnnotation.xml +++ /dev/null @@ -1,67 +0,0 @@ - - - - - 1 - - - - - 0 - - - - - 0 - - - - #1446 False positive with JUnit4TestShouldUseBeforeAnnotation when TestNG is used - 0 - - - diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/migrating/xml/JUnit4TestShouldUseBeforeAnnotation.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/migrating/xml/JUnit4TestShouldUseBeforeAnnotation.xml deleted file mode 100644 index 272663322d6..00000000000 --- a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/migrating/xml/JUnit4TestShouldUseBeforeAnnotation.xml +++ /dev/null @@ -1,79 +0,0 @@ - - - - - 1 - - - - - 0 - - - - - 0 - - - - #1400 False positive with JUnit4TestShouldUseBeforeAnnotation - 0 - - - - #1446 False positive with JUnit4TestShouldUseBeforeAnnotation when TestNG is used - 0 - - - diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/migrating/xml/JUnit4TestShouldUseTestAnnotation.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/migrating/xml/JUnit4TestShouldUseTestAnnotation.xml deleted file mode 100644 index c01eb76dd59..00000000000 --- a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/migrating/xml/JUnit4TestShouldUseTestAnnotation.xml +++ /dev/null @@ -1,93 +0,0 @@ - - - - - 1 - - - - - 0 - - - - - 0 - - - - - 1 - - - - - 2 - - - - #1197 JUnit4TestShouldUseTestAnnotation for private method - 0 - - - diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/design/xml/AvoidSynchronizedAtMethodLevel.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/multithreading/xml/AvoidSynchronizedAtMethodLevel.xml similarity index 100% rename from pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/design/xml/AvoidSynchronizedAtMethodLevel.xml rename to pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/multithreading/xml/AvoidSynchronizedAtMethodLevel.xml diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/basic/xml/AvoidThreadGroup.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/multithreading/xml/AvoidThreadGroup.xml similarity index 96% rename from pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/basic/xml/AvoidThreadGroup.xml rename to pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/multithreading/xml/AvoidThreadGroup.xml index 3333b05376e..c5b009fe5ef 100644 --- a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/basic/xml/AvoidThreadGroup.xml +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/multithreading/xml/AvoidThreadGroup.xml @@ -61,7 +61,8 @@ ThreadGroup() but not java.lang.ThreadGroup ]]> 0 1 - - - - 1 - - - - - 0 - - - diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/naming/xml/MethodNamingConventions.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/naming/xml/MethodNamingConventions.xml deleted file mode 100644 index d8516e3bd5c..00000000000 --- a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/naming/xml/MethodNamingConventions.xml +++ /dev/null @@ -1,71 +0,0 @@ - - - - - 1 - - - - - 1 - - - - - 0 - - - - #1288 MethodNamingConventions for native should be deactivated - false - 0 - - - - #1288 MethodNamingConventions for native should be deactivated - prevent false negative - true - 1 - 2 - - - - #1343 MethodNamingConventions for overrided methods - 0 - - - diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/optimizations/xml/PrematureDeclaration.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/optimizations/xml/PrematureDeclaration.xml deleted file mode 100644 index 2196cab6a8d..00000000000 --- a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/optimizations/xml/PrematureDeclaration.xml +++ /dev/null @@ -1,108 +0,0 @@ - - - - - 1 - - - - - 0 - - - - - Bug #1064 Exception running PrematureDeclaration - 0 - - - - - #1305 variable declaration inside switch causes ClassCastException - 0 - - - - - #1396 PrematureDeclaration lambda false positive - 0 - { return; }; - r.run(); - return sum; - } -} - ]]> - - diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/optimizations/xml/AddEmptyString.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/performance/xml/AddEmptyString.xml similarity index 100% rename from pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/optimizations/xml/AddEmptyString.xml rename to pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/performance/xml/AddEmptyString.xml diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/strings/xml/AppendCharacterWithChar.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/performance/xml/AppendCharacterWithChar.xml similarity index 100% rename from pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/strings/xml/AppendCharacterWithChar.xml rename to pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/performance/xml/AppendCharacterWithChar.xml diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/optimizations/xml/AvoidArrayLoops.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/performance/xml/AvoidArrayLoops.xml similarity index 100% rename from pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/optimizations/xml/AvoidArrayLoops.xml rename to pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/performance/xml/AvoidArrayLoops.xml diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/performance/xml/AvoidFileStream.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/performance/xml/AvoidFileStream.xml new file mode 100644 index 00000000000..53f76a8c492 --- /dev/null +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/performance/xml/AvoidFileStream.xml @@ -0,0 +1,123 @@ + + + + + + 1 + + + + + + 1 + + + + + + 1 + + + + + + 1 + + + + + + 1 + + + + + + 1 + + + + + + 1 + + + + + + 1 + + + + diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/optimizations/xml/AvoidInstantiatingObjectsInLoops.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/performance/xml/AvoidInstantiatingObjectsInLoops.xml similarity index 100% rename from pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/optimizations/xml/AvoidInstantiatingObjectsInLoops.xml rename to pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/performance/xml/AvoidInstantiatingObjectsInLoops.xml diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/performance/xml/AvoidUsingShortType.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/performance/xml/AvoidUsingShortType.xml new file mode 100644 index 00000000000..89e2c399a26 --- /dev/null +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/performance/xml/AvoidUsingShortType.xml @@ -0,0 +1,149 @@ + + + + + 1 + + + + + + + 1 + + + + + 2 + + + + + + + 0 + + + + + + + #1449 false positive when casting a variable to short + 0 + + + + + short as method parameter + 1 + + + + + short as method parameter with @Override + 0 + + + + + [java] AvoidUsingShortType erroneously triggered on overrides of 3rd party methods (anon. class) #586 + 0 + + + + + [java] AvoidUsingShortType erroneously triggered on overrides of 3rd party methods #586 + 0 + + + + + short as annotation property + 1 + + + diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/basic/xml/BigIntegerInstantiation.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/performance/xml/BigIntegerInstantiation.xml similarity index 100% rename from pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/basic/xml/BigIntegerInstantiation.xml rename to pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/performance/xml/BigIntegerInstantiation.xml diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/basic/xml/BooleanInstantiation.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/performance/xml/BooleanInstantiation.xml similarity index 100% rename from pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/basic/xml/BooleanInstantiation.xml rename to pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/performance/xml/BooleanInstantiation.xml diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/migrating/xml/ByteInstantiation.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/performance/xml/ByteInstantiation.xml similarity index 100% rename from pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/migrating/xml/ByteInstantiation.xml rename to pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/performance/xml/ByteInstantiation.xml diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/strings/xml/ConsecutiveAppendsShouldReuse.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/performance/xml/ConsecutiveAppendsShouldReuse.xml similarity index 95% rename from pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/strings/xml/ConsecutiveAppendsShouldReuse.xml rename to pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/performance/xml/ConsecutiveAppendsShouldReuse.xml index 67140ee0546..1fa9aaea686 100644 --- a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/strings/xml/ConsecutiveAppendsShouldReuse.xml +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/performance/xml/ConsecutiveAppendsShouldReuse.xml @@ -250,6 +250,19 @@ public class Foo { stringBuffer = new StringBuffer().append("agrego ").append("un "); stringBuffer.append("string "); } +} + ]]> + + + #1051 ConsecutiveAppendsShouldReuse not detect on parameter + 2 + diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/strings/xml/ConsecutiveLiteralAppends.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/performance/xml/ConsecutiveLiteralAppends.xml similarity index 89% rename from pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/strings/xml/ConsecutiveLiteralAppends.xml rename to pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/performance/xml/ConsecutiveLiteralAppends.xml index 92720089b79..9e3d41b59fa 100644 --- a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/strings/xml/ConsecutiveLiteralAppends.xml +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/performance/xml/ConsecutiveLiteralAppends.xml @@ -1245,8 +1245,8 @@ public class Escape { 2 6,12 - StringBuffer (or StringBuilder).append is called 3 consecutive times with literal Strings. Use a single append with a single combined String. - StringBuffer (or StringBuilder).append is called 3 consecutive times with literal Strings. Use a single append with a single combined String. + StringBuffer (or StringBuilder).append is called 3 consecutive times with literals. Use a single append with a single combined String. + StringBuffer (or StringBuilder).append is called 3 consecutive times with literals. Use a single append with a single combined String. + + + + Consecutive append of literals other than string: integer + 1 + + + + + Consecutive append of literals other than string: chars + 1 + + + + + Consecutive append of literals other than string: mix chars and strings + 1 + + + + + #1325 [java] False positive in ConsecutiveLiteralAppends + 0 + builder + .append(k) + .append(" = ") + .append(valueToStringFunction.apply(v)) + .append(", ")); + int length = builder.length(); + if (length > 1) { + builder.delete(length - 2, length); + } + builder.append(']'); + return builder.toString(); + } +} + ]]> + + + + FN append inside if statement + 2 + 12,13 + + + + + FP with constructor, append in while + 0 + diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/strings/xml/InefficientEmptyStringCheck.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/performance/xml/InefficientEmptyStringCheck.xml similarity index 100% rename from pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/strings/xml/InefficientEmptyStringCheck.xml rename to pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/performance/xml/InefficientEmptyStringCheck.xml diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/strings/xml/InefficientStringBuffering.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/performance/xml/InefficientStringBuffering.xml similarity index 100% rename from pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/strings/xml/InefficientStringBuffering.xml rename to pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/performance/xml/InefficientStringBuffering.xml diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/strings/xml/InsufficientStringBufferDeclaration.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/performance/xml/InsufficientStringBufferDeclaration.xml similarity index 98% rename from pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/strings/xml/InsufficientStringBufferDeclaration.xml rename to pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/performance/xml/InsufficientStringBufferDeclaration.xml index 1efccd82ee1..1b00b27e2ef 100644 --- a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/strings/xml/InsufficientStringBufferDeclaration.xml +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/performance/xml/InsufficientStringBufferDeclaration.xml @@ -310,7 +310,7 @@ public class Foo { 2 @@ -1059,6 +1059,19 @@ public final class test { "# Testing" + NEWLINE + "# More Contents" + NEWLINE); } +} + ]]> + + + + #841 InsufficientStringBufferDeclaration NumberFormatException + 0 + diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/migrating/xml/IntegerInstantiation.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/performance/xml/IntegerInstantiation.xml similarity index 100% rename from pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/migrating/xml/IntegerInstantiation.xml rename to pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/performance/xml/IntegerInstantiation.xml diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/migrating/xml/LongInstantiation.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/performance/xml/LongInstantiation.xml similarity index 100% rename from pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/migrating/xml/LongInstantiation.xml rename to pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/performance/xml/LongInstantiation.xml diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/performance/xml/OptimizableToArrayCall.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/performance/xml/OptimizableToArrayCall.xml new file mode 100644 index 00000000000..d4866c48b20 --- /dev/null +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/performance/xml/OptimizableToArrayCall.xml @@ -0,0 +1,69 @@ + + + + Preferred usage (sf #1454) + 0 + + + + Array dimensioner uses method call, performance issue + 1 + + + + Array dimensioner uses variable, performance issue + 1 + + + + + #937 OptimizableToArrayCall does not catch multilevel method chains + 0 + + + + + Array with a literal dimension, zero still better + 1 + + + + + toArray call without an array should not be flagged + 0 + + + diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/optimizations/xml/RedundantFieldInitializer.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/performance/xml/RedundantFieldInitializer.xml similarity index 94% rename from pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/optimizations/xml/RedundantFieldInitializer.xml rename to pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/performance/xml/RedundantFieldInitializer.xml index 5a72358454a..5870880e9bd 100644 --- a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/optimizations/xml/RedundantFieldInitializer.xml +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/performance/xml/RedundantFieldInitializer.xml @@ -1322,6 +1322,100 @@ public class SomeClass { + + + + java7 numeric literals + 4 + 8,21,31,47 + + + + + #1298 [java] RedundantFieldInitializer - NumberFormatException with Long + 0 + diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/migrating/xml/ShortInstantiation.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/performance/xml/ShortInstantiation.xml similarity index 100% rename from pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/migrating/xml/ShortInstantiation.xml rename to pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/performance/xml/ShortInstantiation.xml diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/optimizations/xml/SimplifyStartsWith.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/performance/xml/SimplifyStartsWith.xml similarity index 100% rename from pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/optimizations/xml/SimplifyStartsWith.xml rename to pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/performance/xml/SimplifyStartsWith.xml diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/strings/xml/StringInstantiation.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/performance/xml/StringInstantiation.xml similarity index 100% rename from pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/strings/xml/StringInstantiation.xml rename to pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/performance/xml/StringInstantiation.xml diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/strings/xml/StringToString.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/performance/xml/StringToString.xml similarity index 100% rename from pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/strings/xml/StringToString.xml rename to pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/performance/xml/StringToString.xml diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/design/xml/TooFewBranchesForASwitchStatement.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/performance/xml/TooFewBranchesForASwitchStatement.xml similarity index 100% rename from pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/design/xml/TooFewBranchesForASwitchStatement.xml rename to pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/performance/xml/TooFewBranchesForASwitchStatement.xml diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/optimizations/xml/UnnecessaryWrapperObjectCreation.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/performance/xml/UnnecessaryWrapperObjectCreation.xml similarity index 100% rename from pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/optimizations/xml/UnnecessaryWrapperObjectCreation.xml rename to pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/performance/xml/UnnecessaryWrapperObjectCreation.xml diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/optimizations/xml/UseArrayListInsteadOfVector.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/performance/xml/UseArrayListInsteadOfVector.xml similarity index 100% rename from pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/optimizations/xml/UseArrayListInsteadOfVector.xml rename to pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/performance/xml/UseArrayListInsteadOfVector.xml diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/optimizations/xml/UseArraysAsList.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/performance/xml/UseArraysAsList.xml similarity index 100% rename from pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/optimizations/xml/UseArraysAsList.xml rename to pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/performance/xml/UseArraysAsList.xml diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/strings/xml/UseIndexOfChar.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/performance/xml/UseIndexOfChar.xml similarity index 89% rename from pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/strings/xml/UseIndexOfChar.xml rename to pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/performance/xml/UseIndexOfChar.xml index 2fd4e5b8ddd..d422a76062c 100644 --- a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/strings/xml/UseIndexOfChar.xml +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/performance/xml/UseIndexOfChar.xml @@ -170,4 +170,22 @@ public class Foo { ]]> java 1.8 + + + NPE with var used in try-with-resources + 0 + + java 10 + diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/optimizations/xml/UseStringBufferForStringAppends.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/performance/xml/UseStringBufferForStringAppends.xml similarity index 100% rename from pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/optimizations/xml/UseStringBufferForStringAppends.xml rename to pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/performance/xml/UseStringBufferForStringAppends.xml diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/strings/xml/UseStringBufferLength.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/performance/xml/UseStringBufferLength.xml similarity index 100% rename from pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/strings/xml/UseStringBufferLength.xml rename to pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/performance/xml/UseStringBufferLength.xml diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/strings/xml/UselessStringValueOf.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/performance/xml/UselessStringValueOf.xml similarity index 100% rename from pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/strings/xml/UselessStringValueOf.xml rename to pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/performance/xml/UselessStringValueOf.xml diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/security/xml/HardCodedCryptoKey.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/security/xml/HardCodedCryptoKey.xml new file mode 100644 index 00000000000..23807406ad1 --- /dev/null +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/security/xml/HardCodedCryptoKey.xml @@ -0,0 +1,88 @@ + + + + Hard coded inline cryptographic key, bad + 1 + + + + + Key stored in a property, good + 0 + + + + + Hard coded in field cryptographic key, bad + 1 + + + + + Hard coded crypto key byte[], bad + 1 + + + + + Key fetched from a property, good + 0 + + + + diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/security/xml/InsecureCryptoIv.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/security/xml/InsecureCryptoIv.xml new file mode 100644 index 00000000000..6e25a5ea483 --- /dev/null +++ b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/security/xml/InsecureCryptoIv.xml @@ -0,0 +1,144 @@ + + + + Hard coded inline IvSpec, bad + 1 + + + + Randomly generated IV, good + 0 + + + + Hard coded Iv from string, bad + 1 + + + + Hard coded Iv field, bad + 1 + + + + Hard coded Iv local var, bad + 1 + + + + Random IV, good + 0 + + + + Inline IvSpec Random IV, good + 0 + + + + + NPE when byte array is not initialized directly + 0 + + + diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/strictexception/xml/AvoidThrowingRawExceptionTypes.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/strictexception/xml/AvoidThrowingRawExceptionTypes.xml deleted file mode 100644 index 3235e2068e5..00000000000 --- a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/strictexception/xml/AvoidThrowingRawExceptionTypes.xml +++ /dev/null @@ -1,59 +0,0 @@ - - - - - 4 - - - - - 0 - - - - #1337: False positive "Avoid throwing raw exception types" when exception is not thrown - 0 - - - diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/strictexception/xml/SignatureDeclareThrowsException.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/strictexception/xml/SignatureDeclareThrowsException.xml deleted file mode 100644 index cef45981b43..00000000000 --- a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/strictexception/xml/SignatureDeclareThrowsException.xml +++ /dev/null @@ -1,121 +0,0 @@ - - - - - 1 - - - - - 0 - - - - - 1 - - - - - 0 - - - - - 0 - - - - - 0 - Bar foo() { /* blah */} -} - ]]> - java 1.5 - - - - 0 - - - - #913 SignatureDeclareThrowsException is raised twice - 1 - - - - - #1535 [java] SignatureDeclareThrowsException: ClassCastException with Annotation - 0 - implements @Readonly List<@Readonly T> {} - ]]> - - - - #350 allow throws exception when overriding a method defined elsewhere - 0 - - - diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/typeresolution/xml/CloneMethodMustImplementCloneable.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/typeresolution/xml/CloneMethodMustImplementCloneable.xml deleted file mode 100644 index 391b3457c95..00000000000 --- a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/typeresolution/xml/CloneMethodMustImplementCloneable.xml +++ /dev/null @@ -1,202 +0,0 @@ - - - - - 0 - - - - - 1 - - - - - 0 - - - - - 0 - - - - - 0 - - - - - 0 - - - - - 0 - - - - - 0 - - - - - 0 - - - - - 0 - - - - - 0 - - - - - 0 - - - - - 0 - - - - - #1534 [java] CloneMethodMustImplementCloneable: ClassCastException with Annotation (java8) - 0 - implements @Readonly List<@Readonly T> {} - ]]> - - - - #1532 [java] CloneMethodMustImplementCloneable: Implemented Interface extends Cloneable - part 1: interface - 0 - - - - - #1532 [java] CloneMethodMustImplementCloneable: Implemented Interface extends Cloneable - 0 - - - diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/typeresolution/xml/LooseCoupling.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/typeresolution/xml/LooseCoupling.xml deleted file mode 100644 index 784e2a2fe45..00000000000 --- a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/typeresolution/xml/LooseCoupling.xml +++ /dev/null @@ -1,164 +0,0 @@ - - - - - 1 - - - - - 0 - - - - - 0 - - - - - 0 - - - - - 1 - - - - - 2 - - - - - 2 - - - - - 1 - - - - - 1 - - - - - 1 - - - - - 1 - - - - - #938 False positive on LooseCoupling for overriding methods - 0 - - - diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/typeresolution/xml/SignatureDeclareThrowsException.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/typeresolution/xml/SignatureDeclareThrowsException.xml deleted file mode 100644 index 0e273096b87..00000000000 --- a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/typeresolution/xml/SignatureDeclareThrowsException.xml +++ /dev/null @@ -1,169 +0,0 @@ - - - - - 1 - - - - - 0 - - - - - 1 - - - - - 0 - - - - - 0 - - - - - 1 - - - - - true - 0 - - - - - 0 - - - - - 0 - - - - - 0 - - - - - 0 - - - - - 0 - Bar foo() { /* blah */} -} - ]]> - java 1.5 - - - - #1535 [java] SignatureDeclareThrowsException: ClassCastException with Annotation - 0 - implements @Readonly List<@Readonly T> {} - ]]> - - - - #350 allow throws exception when overriding a method defined elsewhere - 0 - - - diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/typeresolution/xml/UnusedImports.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/typeresolution/xml/UnusedImports.xml deleted file mode 100644 index db7edb84b4d..00000000000 --- a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/typeresolution/xml/UnusedImports.xml +++ /dev/null @@ -1,285 +0,0 @@ - - - - - 1 - - - - - 0 - - - - - 2 - - - - - 0 - - - - - 0 - - - - - 0 - - java 1.5 - - - - 0 - x = new ArrayList(); -} - ]]> - java 1.5 - - - - 0 - - java 1.5 - - - - 0 - - java 1.5 - - - - 0 - - - - - 1 - - - - - 0 - - - - - 0 - - - - #1280 False Positive in UnusedImports when import used in javadoc - 0 - - - - #914 False +ve from UnusedImports with wildcard static imports - 0 - - - - - #1465 False Positve UnusedImports with javadoc @link - 0 - - * An agent is active if it has not posted a {@link AgentStateChangeEvent} containing {@link AgentState#TERMINATED}. - * - * @return agent handles. - * @see OtherState#TERMINATED - */ - Iterable getAgentHandles(); -} - ]]> - - - - #1547 False Positve UnusedImports with javadoc for identifiers with underscores - 0 - - - - #348 False Positive UnusedImports with javadoc for public static inner classes of imports - 0 - - - diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/unnecessary/xml/UnnecessaryFinalModifier.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/unnecessary/xml/UnnecessaryFinalModifier.xml deleted file mode 100644 index 2f418ce542b..00000000000 --- a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/unnecessary/xml/UnnecessaryFinalModifier.xml +++ /dev/null @@ -1,161 +0,0 @@ - - - - - 0 - - - - - 0 - - - - - 0 - - - - - 1 - - - - - 1 - - - - - 3 - - - - - 0 - - - - - 1 - - - - - #1464 UnnecessaryFinalModifier false positive on a @SafeVarargs method - 0 - { - @SafeVarargs - public final InboxContents conflateWith(T... values) { // false positive - return conflateWith(ImmutableList.copyOf(values)); - } -} -public final class InboxContents2 { - @java.lang.SafeVarargs - public final InboxContents conflateWith(String... values) { - return conflateWith(ImmutableList.copyOf(values)); - } -} - ]]> - - - Unnecessary final of private method - 1 - - - - Unnecessary final of enum method - 1 - - - - Unnecessary final of anonymous class method - 1 - - - diff --git a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/unnecessary/xml/UnnecessaryModifier.xml b/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/unnecessary/xml/UnnecessaryModifier.xml deleted file mode 100644 index e395440c594..00000000000 --- a/pmd-java/src/test/resources/net/sourceforge/pmd/lang/java/rule/unnecessary/xml/UnnecessaryModifier.xml +++ /dev/null @@ -1,434 +0,0 @@ - - - - - 1 - - - - - 0 - - - - - 1 - - - - - 0 - - - - - 1 - - - - - 1 - - - - - 1 - - - - - 1 - - - - - 0 - - - - - 1 - - - - - 1 - - - - - 0 - - - - - 1 - - - - - 1 - - - - - 0 - - - - - 1 - - - - - 0 - - - - - 2 - - - - - 0 - - - - - 0 - - - - - 0 - - - - - 0 - - - - - False negative: #1185 UnusedModifier throws NPE when parsing enum with a nested static interface - 1 - - - - #1275 False positive: UnusedModifier rule for static inner class in enum - 0 - - - - - #1480 false positive on public modifier used with inner interface in enum - 0 - - - - - Unnecessary public on annotation element - 1 - - - - Unnecessary abstract on annotation element - 1 - - - - Unnecessary final on annotation field - 1 - - - - Unnecessary static on annotation field - 1 - - - - Unnecessary public on annotation field - 1 - - - - Unnecessary public on annotation nested class - 1 - - - - Unnecessary static on annotation nested class - 1 - - - - Unnecessary public on annotation nested interface - 1 - - - - Unnecessary public on annotation nested annotation - 1 - - - - Unnecessary static on annotation nested enum - 1 - - - - Unnecessary static on interface nested enum - 1 - - - - Unnecessary static on class nested enum - 1 - - - - Unnecessary abstract on interface - 1 - - - - Unnecessary abstract on annotation - 1 - - - diff --git a/pmd-java/src/test/resources/rulesets/java/metrics_test.xml b/pmd-java/src/test/resources/rulesets/java/metrics_test.xml index be4470c5a55..be01a06ce77 100644 --- a/pmd-java/src/test/resources/rulesets/java/metrics_test.xml +++ b/pmd-java/src/test/resources/rulesets/java/metrics_test.xml @@ -1,9 +1,9 @@ + xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd"> Metrics testing ruleset. Each metric is tested through a dummy rule. @@ -11,62 +11,52 @@ + class="net.sourceforge.pmd.lang.java.metrics.impl.CycloTestRule"> + class="net.sourceforge.pmd.lang.java.metrics.impl.NcssTestRule"> + class="net.sourceforge.pmd.lang.java.metrics.impl.WmcTestRule"> + class="net.sourceforge.pmd.lang.java.metrics.impl.LocTestRule"> + class="net.sourceforge.pmd.lang.java.metrics.impl.NPathTestRule"> + class="net.sourceforge.pmd.lang.java.metrics.impl.NopaTestRule"> + class="net.sourceforge.pmd.lang.java.metrics.impl.NoamTestRule"> + class="net.sourceforge.pmd.lang.java.metrics.impl.WocTestRule"> + class="net.sourceforge.pmd.lang.java.metrics.impl.TccTestRule"> + class="net.sourceforge.pmd.lang.java.metrics.impl.AtfdTestRule"> diff --git a/pmd-java8/pom.xml b/pmd-java8/pom.xml index 92875f10ea0..521025f38b8 100644 --- a/pmd-java8/pom.xml +++ b/pmd-java8/pom.xml @@ -7,12 +7,14 @@ net.sourceforge.pmd pmd - 6.0.0-SNAPSHOT + 6.10.0-SNAPSHOT - ${basedir}/../pmd-core - 1.8 + 8 + + 1.${java.version} + 1.${java.version} @@ -52,42 +54,14 @@ ${project.version} - - jaxen - jaxen - - - net.java.dev.javacc - javacc - net.sourceforge.pmd pmd-core - - net.sourceforge.saxon - saxon - - - org.ow2.asm - asm - - net.sourceforge.saxon - saxon - dom - runtime - - - - net.sourceforge.pmd - pmd-test - test - - - org.slf4j - slf4j-api + junit + junit test diff --git a/pmd-java8/src/test/java/net/sourceforge/pmd/typeresolution/ClassTypeResolverJava8Test.java b/pmd-java8/src/test/java/net/sourceforge/pmd/typeresolution/ClassTypeResolverJava8Test.java index 70c40708c0f..349023a9987 100644 --- a/pmd-java8/src/test/java/net/sourceforge/pmd/typeresolution/ClassTypeResolverJava8Test.java +++ b/pmd-java8/src/test/java/net/sourceforge/pmd/typeresolution/ClassTypeResolverJava8Test.java @@ -109,6 +109,7 @@ private ASTCompilationUnit parseAndTypeResolveForClass(Class clazz, String ve ASTCompilationUnit acu = (ASTCompilationUnit) languageVersionHandler .getParser(languageVersionHandler.getDefaultParserOptions()).parse(null, new InputStreamReader(is)); languageVersionHandler.getSymbolFacade().start(acu); + languageVersionHandler.getQualifiedNameResolutionFacade(ClassTypeResolverJava8Test.class.getClassLoader()).start(acu); languageVersionHandler.getTypeResolutionFacade(ClassTypeResolverJava8Test.class.getClassLoader()).start(acu); return acu; } diff --git a/pmd-javascript/pom.xml b/pmd-javascript/pom.xml index 3c86ee98069..c1683664bce 100644 --- a/pmd-javascript/pom.xml +++ b/pmd-javascript/pom.xml @@ -7,13 +7,9 @@ net.sourceforge.pmd pmd - 6.0.0-SNAPSHOT + 6.10.0-SNAPSHOT - - ${basedir}/../pmd-core - - @@ -99,7 +95,19 @@ org.mozilla rhino + + commons-io + commons-io + + + net.sourceforge.saxon + saxon + + + junit + junit + net.sourceforge.pmd pmd-test diff --git a/pmd-javascript/src/main/ant/alljavacc.xml b/pmd-javascript/src/main/ant/alljavacc.xml index 15cf9780387..6f7a0a7bc48 100644 --- a/pmd-javascript/src/main/ant/alljavacc.xml +++ b/pmd-javascript/src/main/ant/alljavacc.xml @@ -40,5 +40,55 @@ + + + public class Token implements java.io.Serializable + + + + + + public Token specialToken; + + diff --git a/pmd-javascript/src/main/java/net/sourceforge/pmd/cpd/EcmascriptTokenizer.java b/pmd-javascript/src/main/java/net/sourceforge/pmd/cpd/EcmascriptTokenizer.java index 2d27aa58975..3c236c57e42 100644 --- a/pmd-javascript/src/main/java/net/sourceforge/pmd/cpd/EcmascriptTokenizer.java +++ b/pmd-javascript/src/main/java/net/sourceforge/pmd/cpd/EcmascriptTokenizer.java @@ -4,14 +4,14 @@ package net.sourceforge.pmd.cpd; +import java.io.IOException; import java.io.Reader; import java.io.StringReader; -import org.apache.commons.io.IOUtils; - +import net.sourceforge.pmd.cpd.token.JavaCCTokenFilter; +import net.sourceforge.pmd.cpd.token.TokenFilter; import net.sourceforge.pmd.lang.LanguageRegistry; import net.sourceforge.pmd.lang.LanguageVersionHandler; -import net.sourceforge.pmd.lang.TokenManager; import net.sourceforge.pmd.lang.ast.TokenMgrError; import net.sourceforge.pmd.lang.ecmascript.EcmascriptLanguageModule; import net.sourceforge.pmd.lang.ecmascript5.ast.Ecmascript5ParserConstants; @@ -25,19 +25,17 @@ public class EcmascriptTokenizer implements Tokenizer { @Override public void tokenize(SourceCode sourceCode, Tokens tokenEntries) { StringBuilder buffer = sourceCode.getCodeBuffer(); - Reader reader = null; - try { + try (Reader reader = new StringReader(buffer.toString())) { LanguageVersionHandler languageVersionHandler = LanguageRegistry.getLanguage(EcmascriptLanguageModule.NAME) .getDefaultVersion().getLanguageVersionHandler(); - reader = new StringReader(buffer.toString()); - TokenManager tokenManager = languageVersionHandler + TokenFilter tokenFilter = new JavaCCTokenFilter(languageVersionHandler .getParser(languageVersionHandler.getDefaultParserOptions()) - .getTokenManager(sourceCode.getFileName(), reader); - Token currentToken = (Token) tokenManager.getNextToken(); - while (currentToken.image.length() > 0) { + .getTokenManager(sourceCode.getFileName(), reader)); + Token currentToken = (Token) tokenFilter.getNextToken(); + while (currentToken != null) { tokenEntries.add( new TokenEntry(getTokenImage(currentToken), sourceCode.getFileName(), currentToken.beginLine)); - currentToken = (Token) tokenManager.getNextToken(); + currentToken = (Token) tokenFilter.getNextToken(); } tokenEntries.add(TokenEntry.getEOF()); System.err.println("Added " + sourceCode.getFileName()); @@ -45,8 +43,8 @@ public void tokenize(SourceCode sourceCode, Tokens tokenEntries) { err.printStackTrace(); System.err.println("Skipping " + sourceCode.getFileName() + " due to parse error"); tokenEntries.add(TokenEntry.getEOF()); - } finally { - IOUtils.closeQuietly(reader); + } catch (IOException e) { + e.printStackTrace(); } } diff --git a/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/Ecmascript3Handler.java b/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/Ecmascript3Handler.java index cf21d6fca81..bfbdd95fae7 100644 --- a/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/Ecmascript3Handler.java +++ b/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/Ecmascript3Handler.java @@ -12,14 +12,12 @@ import net.sourceforge.pmd.lang.VisitorStarter; import net.sourceforge.pmd.lang.XPathHandler; import net.sourceforge.pmd.lang.ast.Node; -import net.sourceforge.pmd.lang.ast.xpath.AbstractASTXPathHandler; +import net.sourceforge.pmd.lang.ast.xpath.DefaultASTXPathHandler; import net.sourceforge.pmd.lang.ecmascript.ast.DumpFacade; import net.sourceforge.pmd.lang.ecmascript.ast.EcmascriptNode; import net.sourceforge.pmd.lang.ecmascript.rule.EcmascriptRuleViolationFactory; import net.sourceforge.pmd.lang.rule.RuleViolationFactory; -import net.sf.saxon.sxpath.IndependentContext; - /** * Implementation of LanguageVersionHandler for the ECMAScript Version 3. */ @@ -27,15 +25,10 @@ public class Ecmascript3Handler extends AbstractLanguageVersionHandler { @Override public XPathHandler getXPathHandler() { - return new AbstractASTXPathHandler() { - public void initialize() { - } - - public void initialize(IndependentContext context) { - } - }; + return new DefaultASTXPathHandler(); } + @Override public RuleViolationFactory getRuleViolationFactory() { return EcmascriptRuleViolationFactory.INSTANCE; } @@ -45,6 +38,7 @@ public ParserOptions getDefaultParserOptions() { return new EcmascriptParserOptions(); } + @Override public Parser getParser(ParserOptions parserOptions) { return new Ecmascript3Parser(parserOptions); } @@ -52,6 +46,7 @@ public Parser getParser(ParserOptions parserOptions) { @Override public VisitorStarter getDumpFacade(final Writer writer, final String prefix, final boolean recurse) { return new VisitorStarter() { + @Override public void start(Node rootNode) { new DumpFacade().initializeWith(writer, prefix, recurse, (EcmascriptNode) rootNode); } diff --git a/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/Ecmascript3Parser.java b/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/Ecmascript3Parser.java index 5b3efb7eca5..bd5fbbdf96c 100644 --- a/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/Ecmascript3Parser.java +++ b/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/Ecmascript3Parser.java @@ -31,14 +31,17 @@ public TokenManager createTokenManager(Reader source) { return new Ecmascript5TokenManager(source); } + @Override public boolean canParse() { return true; } + @Override public Node parse(String fileName, Reader source) throws ParseException { return ecmascriptParser.parse(source); } + @Override public Map getSuppressMap() { return ecmascriptParser.getSuppressMap(); } diff --git a/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/EcmascriptParserOptions.java b/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/EcmascriptParserOptions.java index de549e7524e..e616857df19 100644 --- a/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/EcmascriptParserOptions.java +++ b/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/EcmascriptParserOptions.java @@ -4,13 +4,14 @@ package net.sourceforge.pmd.lang.ecmascript; +import java.util.Objects; + import org.mozilla.javascript.Context; import net.sourceforge.pmd.Rule; import net.sourceforge.pmd.lang.ParserOptions; import net.sourceforge.pmd.properties.BooleanProperty; import net.sourceforge.pmd.properties.EnumeratedProperty; -import net.sourceforge.pmd.util.StringUtil; public class EcmascriptParserOptions extends ParserOptions { @@ -120,7 +121,7 @@ public boolean equals(Object obj) { return false; } final EcmascriptParserOptions that = (EcmascriptParserOptions) obj; - return StringUtil.isSame(this.suppressMarker, that.suppressMarker, false, false, false) + return Objects.equals(this.suppressMarker, that.suppressMarker) && this.recordingComments == that.recordingComments && this.recordingLocalJsDocComments == that.recordingLocalJsDocComments && this.rhinoLanguageVersion == that.rhinoLanguageVersion; diff --git a/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/ast/ASTArrayComprehension.java b/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/ast/ASTArrayComprehension.java index 634825d4dc0..42b9740ec1a 100644 --- a/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/ast/ASTArrayComprehension.java +++ b/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/ast/ASTArrayComprehension.java @@ -14,6 +14,7 @@ public ASTArrayComprehension(ArrayComprehension arrayComprehension) { /** * Accept the visitor. */ + @Override public Object jjtAccept(EcmascriptParserVisitor visitor, Object data) { return visitor.visit(this, data); } diff --git a/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/ast/ASTArrayComprehensionLoop.java b/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/ast/ASTArrayComprehensionLoop.java index a19e5920b21..6ede0d83a09 100644 --- a/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/ast/ASTArrayComprehensionLoop.java +++ b/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/ast/ASTArrayComprehensionLoop.java @@ -15,6 +15,7 @@ public ASTArrayComprehensionLoop(ArrayComprehensionLoop arrayComprehensionLoop) /** * Accept the visitor. */ + @Override public Object jjtAccept(EcmascriptParserVisitor visitor, Object data) { return visitor.visit(this, data); } diff --git a/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/ast/ASTArrayLiteral.java b/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/ast/ASTArrayLiteral.java index 24edf8904cc..0365dce7b20 100644 --- a/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/ast/ASTArrayLiteral.java +++ b/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/ast/ASTArrayLiteral.java @@ -17,18 +17,22 @@ public ASTArrayLiteral(ArrayLiteral arrayLiteral) { /** * Accept the visitor. */ + @Override public Object jjtAccept(EcmascriptParserVisitor visitor, Object data) { return visitor.visit(this, data); } + @Override public boolean isDestructuring() { return node.isDestructuring(); } + @Override public boolean isTrailingComma() { return trailingComma; } + @Override public void setTrailingComma(boolean trailingComma) { this.trailingComma = trailingComma; } diff --git a/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/ast/ASTForInLoop.java b/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/ast/ASTForInLoop.java index 8f2d1c095e2..b66a875ced7 100644 --- a/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/ast/ASTForInLoop.java +++ b/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/ast/ASTForInLoop.java @@ -14,6 +14,7 @@ public ASTForInLoop(ForInLoop forInLoop) { /** * Accept the visitor. */ + @Override public Object jjtAccept(EcmascriptParserVisitor visitor, Object data) { return visitor.visit(this, data); } diff --git a/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/ast/ASTForLoop.java b/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/ast/ASTForLoop.java index c2ee8cd4a95..5a19df40061 100644 --- a/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/ast/ASTForLoop.java +++ b/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/ast/ASTForLoop.java @@ -14,6 +14,7 @@ public ASTForLoop(ForLoop forLoop) { /** * Accept the visitor. */ + @Override public Object jjtAccept(EcmascriptParserVisitor visitor, Object data) { return visitor.visit(this, data); } diff --git a/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/ast/ASTFunctionCall.java b/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/ast/ASTFunctionCall.java index 508987f83d0..255203274c8 100644 --- a/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/ast/ASTFunctionCall.java +++ b/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/ast/ASTFunctionCall.java @@ -14,6 +14,7 @@ public ASTFunctionCall(FunctionCall functionCall) { /** * Accept the visitor. */ + @Override public Object jjtAccept(EcmascriptParserVisitor visitor, Object data) { return visitor.visit(this, data); } diff --git a/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/ast/ASTFunctionNode.java b/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/ast/ASTFunctionNode.java index 6c7333efcde..5d1db39db5d 100644 --- a/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/ast/ASTFunctionNode.java +++ b/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/ast/ASTFunctionNode.java @@ -15,6 +15,7 @@ public ASTFunctionNode(FunctionNode functionNode) { /** * Accept the visitor. */ + @Override public Object jjtAccept(EcmascriptParserVisitor visitor, Object data) { return visitor.visit(this, data); } diff --git a/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/ast/ASTIfStatement.java b/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/ast/ASTIfStatement.java index d5f6f61c08a..60a0d69fc9d 100644 --- a/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/ast/ASTIfStatement.java +++ b/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/ast/ASTIfStatement.java @@ -14,6 +14,7 @@ public ASTIfStatement(IfStatement ifStatement) { /** * Accept the visitor. */ + @Override public Object jjtAccept(EcmascriptParserVisitor visitor, Object data) { return visitor.visit(this, data); } diff --git a/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/ast/ASTInfixExpression.java b/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/ast/ASTInfixExpression.java index 7c1ae88805f..bbf8842263c 100644 --- a/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/ast/ASTInfixExpression.java +++ b/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/ast/ASTInfixExpression.java @@ -14,6 +14,7 @@ public ASTInfixExpression(InfixExpression infixExpression) { /** * Accept the visitor. */ + @Override public Object jjtAccept(EcmascriptParserVisitor visitor, Object data) { return visitor.visit(this, data); } diff --git a/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/ast/ASTKeywordLiteral.java b/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/ast/ASTKeywordLiteral.java index d69ce8034da..9d797fe6d41 100644 --- a/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/ast/ASTKeywordLiteral.java +++ b/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/ast/ASTKeywordLiteral.java @@ -4,13 +4,15 @@ package net.sourceforge.pmd.lang.ecmascript.ast; +import java.util.Locale; + import org.mozilla.javascript.Token; import org.mozilla.javascript.ast.KeywordLiteral; public class ASTKeywordLiteral extends AbstractEcmascriptNode { public ASTKeywordLiteral(KeywordLiteral keywordLiteral) { super(keywordLiteral); - super.setImage(Token.typeToName(keywordLiteral.getType()).toLowerCase()); + super.setImage(Token.typeToName(keywordLiteral.getType()).toLowerCase(Locale.ROOT)); } /** diff --git a/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/ast/ASTLabel.java b/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/ast/ASTLabel.java index 876f4c25df8..6cd6466a679 100644 --- a/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/ast/ASTLabel.java +++ b/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/ast/ASTLabel.java @@ -15,6 +15,7 @@ public ASTLabel(Label label) { /** * Accept the visitor. */ + @Override public Object jjtAccept(EcmascriptParserVisitor visitor, Object data) { return visitor.visit(this, data); } diff --git a/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/ast/ASTLabeledStatement.java b/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/ast/ASTLabeledStatement.java index f8987ad6d78..953628dd56b 100644 --- a/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/ast/ASTLabeledStatement.java +++ b/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/ast/ASTLabeledStatement.java @@ -14,6 +14,7 @@ public ASTLabeledStatement(LabeledStatement labeledStatement) { /** * Accept the visitor. */ + @Override public Object jjtAccept(EcmascriptParserVisitor visitor, Object data) { return visitor.visit(this, data); } diff --git a/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/ast/ASTLetNode.java b/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/ast/ASTLetNode.java index bdc003a01ad..eb5a849b352 100644 --- a/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/ast/ASTLetNode.java +++ b/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/ast/ASTLetNode.java @@ -14,6 +14,7 @@ public ASTLetNode(LetNode letNode) { /** * Accept the visitor. */ + @Override public Object jjtAccept(EcmascriptParserVisitor visitor, Object data) { return visitor.visit(this, data); } diff --git a/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/ast/ASTNewExpression.java b/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/ast/ASTNewExpression.java index f77bf715e9b..ca8b03eb91f 100644 --- a/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/ast/ASTNewExpression.java +++ b/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/ast/ASTNewExpression.java @@ -14,6 +14,7 @@ public ASTNewExpression(NewExpression newExpression) { /** * Accept the visitor. */ + @Override public Object jjtAccept(EcmascriptParserVisitor visitor, Object data) { return visitor.visit(this, data); } diff --git a/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/ast/ASTNumberLiteral.java b/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/ast/ASTNumberLiteral.java index a0eda4c93ec..02e77d3fbed 100644 --- a/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/ast/ASTNumberLiteral.java +++ b/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/ast/ASTNumberLiteral.java @@ -6,7 +6,6 @@ import org.mozilla.javascript.ast.NumberLiteral; -// TODO The Rhino node does not tell us whether this was specified via decimal, octal or hexidecimal. public class ASTNumberLiteral extends AbstractEcmascriptNode { public ASTNumberLiteral(NumberLiteral numberLiteral) { super(numberLiteral); @@ -16,13 +15,26 @@ public ASTNumberLiteral(NumberLiteral numberLiteral) { /** * Accept the visitor. */ + @Override public Object jjtAccept(EcmascriptParserVisitor visitor, Object data) { return visitor.visit(this, data); } public String getNormalizedImage() { - // TODO Implement - return super.getImage(); + String image = getImage(); + image = normalizeHexIntegerLiteral(image); + image = image.replace('e', 'E'); + if (image.indexOf('.') == -1 && image.indexOf('E') == -1) { + image = image + ".0"; + } + return image; + } + + private String normalizeHexIntegerLiteral(String image) { + if (image.startsWith("0x") || image.startsWith("0X")) { + return String.valueOf(Integer.parseInt(image.substring(2), 16)); + } + return image; } public double getNumber() { diff --git a/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/ast/ASTObjectLiteral.java b/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/ast/ASTObjectLiteral.java index beb0687a081..af98dfb8ac4 100644 --- a/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/ast/ASTObjectLiteral.java +++ b/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/ast/ASTObjectLiteral.java @@ -17,6 +17,7 @@ public ASTObjectLiteral(ObjectLiteral objectLiteral) { /** * Accept the visitor. */ + @Override public Object jjtAccept(EcmascriptParserVisitor visitor, Object data) { return visitor.visit(this, data); } @@ -25,14 +26,17 @@ public ASTObjectProperty getObjectProperty(int index) { return (ASTObjectProperty) jjtGetChild(index); } + @Override public boolean isDestructuring() { return node.isDestructuring(); } + @Override public boolean isTrailingComma() { return trailingComma; } + @Override public void setTrailingComma(boolean trailingComma) { this.trailingComma = trailingComma; } diff --git a/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/ast/ASTObjectProperty.java b/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/ast/ASTObjectProperty.java index 96bae791c97..86a7bf8b472 100644 --- a/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/ast/ASTObjectProperty.java +++ b/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/ast/ASTObjectProperty.java @@ -14,6 +14,7 @@ public ASTObjectProperty(ObjectProperty objectProperty) { /** * Accept the visitor. */ + @Override public Object jjtAccept(EcmascriptParserVisitor visitor, Object data) { return visitor.visit(this, data); } diff --git a/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/ast/ASTParenthesizedExpression.java b/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/ast/ASTParenthesizedExpression.java index 6ec5949bdd9..170f0a9c7ba 100644 --- a/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/ast/ASTParenthesizedExpression.java +++ b/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/ast/ASTParenthesizedExpression.java @@ -14,6 +14,7 @@ public ASTParenthesizedExpression(ParenthesizedExpression parenthesizedExpressio /** * Accept the visitor. */ + @Override public Object jjtAccept(EcmascriptParserVisitor visitor, Object data) { return visitor.visit(this, data); } diff --git a/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/ast/ASTPropertyGet.java b/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/ast/ASTPropertyGet.java index 8334e61e28d..cf8b3a5570e 100644 --- a/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/ast/ASTPropertyGet.java +++ b/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/ast/ASTPropertyGet.java @@ -15,6 +15,7 @@ public ASTPropertyGet(PropertyGet propertyGet) { /** * Accept the visitor. */ + @Override public Object jjtAccept(EcmascriptParserVisitor visitor, Object data) { return visitor.visit(this, data); } diff --git a/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/ast/ASTRegExpLiteral.java b/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/ast/ASTRegExpLiteral.java index 30dd9fa1dda..485537794eb 100644 --- a/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/ast/ASTRegExpLiteral.java +++ b/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/ast/ASTRegExpLiteral.java @@ -15,6 +15,7 @@ public ASTRegExpLiteral(RegExpLiteral regExpLiteral) { /** * Accept the visitor. */ + @Override public Object jjtAccept(EcmascriptParserVisitor visitor, Object data) { return visitor.visit(this, data); } diff --git a/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/ast/ASTReturnStatement.java b/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/ast/ASTReturnStatement.java index 58a5354e670..78dcbf1f9eb 100644 --- a/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/ast/ASTReturnStatement.java +++ b/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/ast/ASTReturnStatement.java @@ -14,6 +14,7 @@ public ASTReturnStatement(ReturnStatement returnStatement) { /** * Accept the visitor. */ + @Override public Object jjtAccept(EcmascriptParserVisitor visitor, Object data) { return visitor.visit(this, data); } diff --git a/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/ast/ASTScope.java b/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/ast/ASTScope.java index e50fe23e8e0..54dd0a5bc30 100644 --- a/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/ast/ASTScope.java +++ b/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/ast/ASTScope.java @@ -14,6 +14,7 @@ public ASTScope(Scope scope) { /** * Accept the visitor. */ + @Override public Object jjtAccept(EcmascriptParserVisitor visitor, Object data) { return visitor.visit(this, data); } diff --git a/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/ast/ASTStringLiteral.java b/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/ast/ASTStringLiteral.java index bafcabe8422..2d10e25d862 100644 --- a/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/ast/ASTStringLiteral.java +++ b/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/ast/ASTStringLiteral.java @@ -15,6 +15,7 @@ public ASTStringLiteral(StringLiteral stringLiteral) { /** * Accept the visitor. */ + @Override public Object jjtAccept(EcmascriptParserVisitor visitor, Object data) { return visitor.visit(this, data); } diff --git a/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/ast/ASTSwitchCase.java b/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/ast/ASTSwitchCase.java index 395e357a0bc..b2bf1b22e5a 100644 --- a/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/ast/ASTSwitchCase.java +++ b/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/ast/ASTSwitchCase.java @@ -14,6 +14,7 @@ public ASTSwitchCase(SwitchCase switchCase) { /** * Accept the visitor. */ + @Override public Object jjtAccept(EcmascriptParserVisitor visitor, Object data) { return visitor.visit(this, data); } diff --git a/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/ast/ASTSwitchStatement.java b/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/ast/ASTSwitchStatement.java index f2e676a1999..330cd46717d 100644 --- a/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/ast/ASTSwitchStatement.java +++ b/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/ast/ASTSwitchStatement.java @@ -14,6 +14,7 @@ public ASTSwitchStatement(SwitchStatement switchStatement) { /** * Accept the visitor. */ + @Override public Object jjtAccept(EcmascriptParserVisitor visitor, Object data) { return visitor.visit(this, data); } diff --git a/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/ast/ASTThrowStatement.java b/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/ast/ASTThrowStatement.java index dc61d1d0407..eb380fbb17f 100644 --- a/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/ast/ASTThrowStatement.java +++ b/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/ast/ASTThrowStatement.java @@ -14,6 +14,7 @@ public ASTThrowStatement(ThrowStatement throwStatement) { /** * Accept the visitor. */ + @Override public Object jjtAccept(EcmascriptParserVisitor visitor, Object data) { return visitor.visit(this, data); } diff --git a/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/ast/ASTTryStatement.java b/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/ast/ASTTryStatement.java index d8d7c175d6e..fb923cb9085 100644 --- a/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/ast/ASTTryStatement.java +++ b/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/ast/ASTTryStatement.java @@ -14,6 +14,7 @@ public ASTTryStatement(TryStatement tryStatement) { /** * Accept the visitor. */ + @Override public Object jjtAccept(EcmascriptParserVisitor visitor, Object data) { return visitor.visit(this, data); } diff --git a/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/ast/ASTVariableDeclaration.java b/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/ast/ASTVariableDeclaration.java index c3f4fa4cc44..966894d76f8 100644 --- a/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/ast/ASTVariableDeclaration.java +++ b/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/ast/ASTVariableDeclaration.java @@ -4,18 +4,21 @@ package net.sourceforge.pmd.lang.ecmascript.ast; +import java.util.Locale; + import org.mozilla.javascript.Token; import org.mozilla.javascript.ast.VariableDeclaration; public class ASTVariableDeclaration extends AbstractEcmascriptNode { public ASTVariableDeclaration(VariableDeclaration variableDeclaration) { super(variableDeclaration); - super.setImage(Token.typeToName(variableDeclaration.getType()).toLowerCase()); + super.setImage(Token.typeToName(variableDeclaration.getType()).toLowerCase(Locale.ROOT)); } /** * Accept the visitor. */ + @Override public Object jjtAccept(EcmascriptParserVisitor visitor, Object data) { return visitor.visit(this, data); } diff --git a/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/ast/ASTVariableInitializer.java b/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/ast/ASTVariableInitializer.java index c838c4aed69..e6a52837a86 100644 --- a/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/ast/ASTVariableInitializer.java +++ b/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/ast/ASTVariableInitializer.java @@ -14,6 +14,7 @@ public ASTVariableInitializer(VariableInitializer variableInitializer) { /** * Accept the visitor. */ + @Override public Object jjtAccept(EcmascriptParserVisitor visitor, Object data) { return visitor.visit(this, data); } @@ -30,6 +31,7 @@ public EcmascriptNode getInitializer() { } } + @Override public boolean isDestructuring() { return node.isDestructuring(); } diff --git a/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/ast/ASTWhileLoop.java b/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/ast/ASTWhileLoop.java index 8dd70cd0dc3..feeb0e3ad07 100644 --- a/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/ast/ASTWhileLoop.java +++ b/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/ast/ASTWhileLoop.java @@ -14,6 +14,7 @@ public ASTWhileLoop(WhileLoop whileLoop) { /** * Accept the visitor. */ + @Override public Object jjtAccept(EcmascriptParserVisitor visitor, Object data) { return visitor.visit(this, data); } diff --git a/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/ast/ASTWithStatement.java b/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/ast/ASTWithStatement.java index 0b2e3f22e1f..539f63ddc7e 100644 --- a/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/ast/ASTWithStatement.java +++ b/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/ast/ASTWithStatement.java @@ -14,6 +14,7 @@ public ASTWithStatement(WithStatement withStatement) { /** * Accept the visitor. */ + @Override public Object jjtAccept(EcmascriptParserVisitor visitor, Object data) { return visitor.visit(this, data); } diff --git a/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/ast/ASTXmlDotQuery.java b/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/ast/ASTXmlDotQuery.java index bf1379c2615..0d20ad94a7e 100644 --- a/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/ast/ASTXmlDotQuery.java +++ b/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/ast/ASTXmlDotQuery.java @@ -14,6 +14,7 @@ public ASTXmlDotQuery(XmlDotQuery xmlDotQuery) { /** * Accept the visitor. */ + @Override public Object jjtAccept(EcmascriptParserVisitor visitor, Object data) { return visitor.visit(this, data); } diff --git a/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/ast/ASTXmlExpression.java b/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/ast/ASTXmlExpression.java index 69f3b143f3e..0e851fbe7eb 100644 --- a/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/ast/ASTXmlExpression.java +++ b/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/ast/ASTXmlExpression.java @@ -14,6 +14,7 @@ public ASTXmlExpression(XmlExpression xmlExpression) { /** * Accept the visitor. */ + @Override public Object jjtAccept(EcmascriptParserVisitor visitor, Object data) { return visitor.visit(this, data); } diff --git a/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/ast/ASTXmlMemberGet.java b/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/ast/ASTXmlMemberGet.java index 40e5efff432..70b2a81001b 100644 --- a/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/ast/ASTXmlMemberGet.java +++ b/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/ast/ASTXmlMemberGet.java @@ -14,6 +14,7 @@ public ASTXmlMemberGet(XmlMemberGet xmlMemberGet) { /** * Accept the visitor. */ + @Override public Object jjtAccept(EcmascriptParserVisitor visitor, Object data) { return visitor.visit(this, data); } diff --git a/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/ast/ASTXmlString.java b/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/ast/ASTXmlString.java index 2a844c1abdd..fd171270641 100644 --- a/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/ast/ASTXmlString.java +++ b/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/ast/ASTXmlString.java @@ -15,6 +15,7 @@ public ASTXmlString(XmlString xmlString) { /** * Accept the visitor. */ + @Override public Object jjtAccept(EcmascriptParserVisitor visitor, Object data) { return visitor.visit(this, data); } diff --git a/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/ast/AbstractEcmascriptNode.java b/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/ast/AbstractEcmascriptNode.java index 3d34c7d356e..671b83b2e77 100644 --- a/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/ast/AbstractEcmascriptNode.java +++ b/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/ast/AbstractEcmascriptNode.java @@ -36,6 +36,7 @@ void calculateLineNumbers(SourceCodePositioner positioner) { /** * Accept the visitor. * */ + @Override public Object jjtAccept(EcmascriptParserVisitor visitor, Object data) { return visitor.visit(this, data); } @@ -43,6 +44,7 @@ public Object jjtAccept(EcmascriptParserVisitor visitor, Object data) { /** * Accept the visitor. * */ + @Override public Object childrenAccept(EcmascriptParserVisitor visitor, Object data) { if (children != null) { for (int i = 0; i < children.length; ++i) { @@ -56,20 +58,26 @@ public Object childrenAccept(EcmascriptParserVisitor visitor, Object data) { return data; } + @Override public T getNode() { return node; } + @Override public String getJsDoc() { return node.getJsDoc(); } + @Override public boolean hasSideEffects() { return node.hasSideEffects(); } + + + @Override - public String toString() { + public String getXPathNodeName() { return node.shortName(); } } diff --git a/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/ast/DumpFacade.java b/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/ast/DumpFacade.java index d9058d8e0a9..4c2910248b8 100644 --- a/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/ast/DumpFacade.java +++ b/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/ast/DumpFacade.java @@ -49,7 +49,7 @@ private void dump(EcmascriptNode node, String prefix) { writer.print(prefix); // 2) JJT Name of the Node - writer.print(node.toString()); + writer.print(node.getXPathNodeName()); // // If there are any additional details, then: diff --git a/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/ast/EcmascriptNode.java b/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/ast/EcmascriptNode.java index d346ebeb274..cf8ea8c1bba 100644 --- a/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/ast/EcmascriptNode.java +++ b/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/ast/EcmascriptNode.java @@ -29,7 +29,7 @@ public interface EcmascriptNode extends Node { * Get the JsDoc associated with the given node. If there is no JsDoc on * this node, it may be associated with a parent node, on more * representative of the entire expression containing this node. - * + * * @return The JsDoc comment for the node, may be null. */ String getJsDoc(); diff --git a/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/ast/EcmascriptParserVisitorAdapter.java b/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/ast/EcmascriptParserVisitorAdapter.java index b3cf8c6fc91..50cf23cff52 100644 --- a/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/ast/EcmascriptParserVisitorAdapter.java +++ b/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/ast/EcmascriptParserVisitorAdapter.java @@ -6,203 +6,253 @@ public class EcmascriptParserVisitorAdapter implements EcmascriptParserVisitor { + @Override public Object visit(EcmascriptNode node, Object data) { node.childrenAccept(this, data); return null; } + @Override public Object visit(ASTArrayComprehension node, Object data) { return visit((EcmascriptNode) node, data); } + @Override public Object visit(ASTArrayComprehensionLoop node, Object data) { return visit((EcmascriptNode) node, data); } + @Override public Object visit(ASTArrayLiteral node, Object data) { return visit((EcmascriptNode) node, data); } + @Override public Object visit(ASTAssignment node, Object data) { return visit((EcmascriptNode) node, data); } + @Override public Object visit(ASTAstRoot node, Object data) { return visit((EcmascriptNode) node, data); } + @Override public Object visit(ASTBlock node, Object data) { return visit((EcmascriptNode) node, data); } + @Override public Object visit(ASTBreakStatement node, Object data) { return visit((EcmascriptNode) node, data); } + @Override public Object visit(ASTCatchClause node, Object data) { return visit((EcmascriptNode) node, data); } + @Override public Object visit(ASTComment node, Object data) { return visit((EcmascriptNode) node, data); } + @Override public Object visit(ASTConditionalExpression node, Object data) { return visit((EcmascriptNode) node, data); } + @Override public Object visit(ASTContinueStatement node, Object data) { return visit((EcmascriptNode) node, data); } + @Override public Object visit(ASTDoLoop node, Object data) { return visit((EcmascriptNode) node, data); } + @Override public Object visit(ASTElementGet node, Object data) { return visit((EcmascriptNode) node, data); } + @Override public Object visit(ASTEmptyExpression node, Object data) { return visit((EcmascriptNode) node, data); } + @Override public Object visit(ASTExpressionStatement node, Object data) { return visit((EcmascriptNode) node, data); } + @Override public Object visit(ASTForInLoop node, Object data) { return visit((EcmascriptNode) node, data); } + @Override public Object visit(ASTForLoop node, Object data) { return visit((EcmascriptNode) node, data); } + @Override public Object visit(ASTFunctionCall node, Object data) { return visit((EcmascriptNode) node, data); } + @Override public Object visit(ASTFunctionNode node, Object data) { return visit((EcmascriptNode) node, data); } + @Override public Object visit(ASTIfStatement node, Object data) { return visit((EcmascriptNode) node, data); } + @Override public Object visit(ASTInfixExpression node, Object data) { return visit((EcmascriptNode) node, data); } + @Override public Object visit(ASTKeywordLiteral node, Object data) { return visit((EcmascriptNode) node, data); } + @Override public Object visit(ASTLabel node, Object data) { return visit((EcmascriptNode) node, data); } + @Override public Object visit(ASTLabeledStatement node, Object data) { return visit((EcmascriptNode) node, data); } + @Override public Object visit(ASTLetNode node, Object data) { return visit((EcmascriptNode) node, data); } + @Override public Object visit(ASTName node, Object data) { return visit((EcmascriptNode) node, data); } + @Override public Object visit(ASTNewExpression node, Object data) { return visit((EcmascriptNode) node, data); } + @Override public Object visit(ASTNumberLiteral node, Object data) { return visit((EcmascriptNode) node, data); } + @Override public Object visit(ASTObjectLiteral node, Object data) { return visit((EcmascriptNode) node, data); } + @Override public Object visit(ASTObjectProperty node, Object data) { return visit((EcmascriptNode) node, data); } + @Override public Object visit(ASTParenthesizedExpression node, Object data) { return visit((EcmascriptNode) node, data); } + @Override public Object visit(ASTPropertyGet node, Object data) { return visit((EcmascriptNode) node, data); } + @Override public Object visit(ASTRegExpLiteral node, Object data) { return visit((EcmascriptNode) node, data); } + @Override public Object visit(ASTReturnStatement node, Object data) { return visit((EcmascriptNode) node, data); } + @Override public Object visit(ASTScope node, Object data) { return visit((EcmascriptNode) node, data); } + @Override public Object visit(ASTStringLiteral node, Object data) { return visit((EcmascriptNode) node, data); } + @Override public Object visit(ASTSwitchCase node, Object data) { return visit((EcmascriptNode) node, data); } + @Override public Object visit(ASTSwitchStatement node, Object data) { return visit((EcmascriptNode) node, data); } + @Override public Object visit(ASTThrowStatement node, Object data) { return visit((EcmascriptNode) node, data); } + @Override public Object visit(ASTTryStatement node, Object data) { return visit((EcmascriptNode) node, data); } + @Override public Object visit(ASTUnaryExpression node, Object data) { return visit((EcmascriptNode) node, data); } + @Override public Object visit(ASTVariableDeclaration node, Object data) { return visit((EcmascriptNode) node, data); } + @Override public Object visit(ASTVariableInitializer node, Object data) { return visit((EcmascriptNode) node, data); } + @Override public Object visit(ASTWhileLoop node, Object data) { return visit((EcmascriptNode) node, data); } + @Override public Object visit(ASTWithStatement node, Object data) { return visit((EcmascriptNode) node, data); } + @Override public Object visit(ASTXmlDotQuery node, Object data) { return visit((EcmascriptNode) node, data); } + @Override public Object visit(ASTXmlExpression node, Object data) { return visit((EcmascriptNode) node, data); } + @Override public Object visit(ASTXmlMemberGet node, Object data) { return visit((EcmascriptNode) node, data); } + @Override public Object visit(ASTXmlString node, Object data) { return visit((EcmascriptNode) node, data); } diff --git a/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/rule/AbstractEcmascriptRule.java b/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/rule/AbstractEcmascriptRule.java index 5b8686d4dac..795ea35ad8b 100644 --- a/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/rule/AbstractEcmascriptRule.java +++ b/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/rule/AbstractEcmascriptRule.java @@ -87,6 +87,7 @@ public ParserOptions getParserOptions() { return new EcmascriptParserOptions(this); } + @Override public void apply(List nodes, RuleContext ctx) { visitAll(nodes, ctx); } @@ -105,203 +106,253 @@ protected void visitAll(List nodes, RuleContext ctx) { // complex Rule base class instead of from relatively simple Visitor. // + @Override public Object visit(EcmascriptNode node, Object data) { node.childrenAccept(this, data); return null; } + @Override public Object visit(ASTArrayComprehension node, Object data) { return visit((EcmascriptNode) node, data); } + @Override public Object visit(ASTArrayComprehensionLoop node, Object data) { return visit((EcmascriptNode) node, data); } + @Override public Object visit(ASTArrayLiteral node, Object data) { return visit((EcmascriptNode) node, data); } + @Override public Object visit(ASTAssignment node, Object data) { return visit((EcmascriptNode) node, data); } + @Override public Object visit(ASTAstRoot node, Object data) { return visit((EcmascriptNode) node, data); } + @Override public Object visit(ASTBlock node, Object data) { return visit((EcmascriptNode) node, data); } + @Override public Object visit(ASTBreakStatement node, Object data) { return visit((EcmascriptNode) node, data); } + @Override public Object visit(ASTCatchClause node, Object data) { return visit((EcmascriptNode) node, data); } + @Override public Object visit(ASTComment node, Object data) { return visit((EcmascriptNode) node, data); } + @Override public Object visit(ASTConditionalExpression node, Object data) { return visit((EcmascriptNode) node, data); } + @Override public Object visit(ASTContinueStatement node, Object data) { return visit((EcmascriptNode) node, data); } + @Override public Object visit(ASTDoLoop node, Object data) { return visit((EcmascriptNode) node, data); } + @Override public Object visit(ASTElementGet node, Object data) { return visit((EcmascriptNode) node, data); } + @Override public Object visit(ASTEmptyExpression node, Object data) { return visit((EcmascriptNode) node, data); } + @Override public Object visit(ASTExpressionStatement node, Object data) { return visit((EcmascriptNode) node, data); } + @Override public Object visit(ASTForInLoop node, Object data) { return visit((EcmascriptNode) node, data); } + @Override public Object visit(ASTForLoop node, Object data) { return visit((EcmascriptNode) node, data); } + @Override public Object visit(ASTFunctionCall node, Object data) { return visit((EcmascriptNode) node, data); } + @Override public Object visit(ASTFunctionNode node, Object data) { return visit((EcmascriptNode) node, data); } + @Override public Object visit(ASTIfStatement node, Object data) { return visit((EcmascriptNode) node, data); } + @Override public Object visit(ASTInfixExpression node, Object data) { return visit((EcmascriptNode) node, data); } + @Override public Object visit(ASTKeywordLiteral node, Object data) { return visit((EcmascriptNode) node, data); } + @Override public Object visit(ASTLabel node, Object data) { return visit((EcmascriptNode) node, data); } + @Override public Object visit(ASTLabeledStatement node, Object data) { return visit((EcmascriptNode) node, data); } + @Override public Object visit(ASTLetNode node, Object data) { return visit((EcmascriptNode) node, data); } + @Override public Object visit(ASTName node, Object data) { return visit((EcmascriptNode) node, data); } + @Override public Object visit(ASTNewExpression node, Object data) { return visit((EcmascriptNode) node, data); } + @Override public Object visit(ASTNumberLiteral node, Object data) { return visit((EcmascriptNode) node, data); } + @Override public Object visit(ASTObjectLiteral node, Object data) { return visit((EcmascriptNode) node, data); } + @Override public Object visit(ASTObjectProperty node, Object data) { return visit((EcmascriptNode) node, data); } + @Override public Object visit(ASTParenthesizedExpression node, Object data) { return visit((EcmascriptNode) node, data); } + @Override public Object visit(ASTPropertyGet node, Object data) { return visit((EcmascriptNode) node, data); } + @Override public Object visit(ASTRegExpLiteral node, Object data) { return visit((EcmascriptNode) node, data); } + @Override public Object visit(ASTReturnStatement node, Object data) { return visit((EcmascriptNode) node, data); } + @Override public Object visit(ASTScope node, Object data) { return visit((EcmascriptNode) node, data); } + @Override public Object visit(ASTStringLiteral node, Object data) { return visit((EcmascriptNode) node, data); } + @Override public Object visit(ASTSwitchCase node, Object data) { return visit((EcmascriptNode) node, data); } + @Override public Object visit(ASTSwitchStatement node, Object data) { return visit((EcmascriptNode) node, data); } + @Override public Object visit(ASTThrowStatement node, Object data) { return visit((EcmascriptNode) node, data); } + @Override public Object visit(ASTTryStatement node, Object data) { return visit((EcmascriptNode) node, data); } + @Override public Object visit(ASTUnaryExpression node, Object data) { return visit((EcmascriptNode) node, data); } + @Override public Object visit(ASTVariableDeclaration node, Object data) { return visit((EcmascriptNode) node, data); } + @Override public Object visit(ASTVariableInitializer node, Object data) { return visit((EcmascriptNode) node, data); } + @Override public Object visit(ASTWhileLoop node, Object data) { return visit((EcmascriptNode) node, data); } + @Override public Object visit(ASTWithStatement node, Object data) { return visit((EcmascriptNode) node, data); } + @Override public Object visit(ASTXmlDotQuery node, Object data) { return visit((EcmascriptNode) node, data); } + @Override public Object visit(ASTXmlExpression node, Object data) { return visit((EcmascriptNode) node, data); } + @Override public Object visit(ASTXmlMemberGet node, Object data) { return visit((EcmascriptNode) node, data); } + @Override public Object visit(ASTXmlString node, Object data) { return visit((EcmascriptNode) node, data); } diff --git a/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/rule/EcmascriptRuleChainVisitor.java b/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/rule/EcmascriptRuleChainVisitor.java index 1c7b5db3430..12cd04b1ff2 100644 --- a/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/rule/EcmascriptRuleChainVisitor.java +++ b/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/rule/EcmascriptRuleChainVisitor.java @@ -18,6 +18,7 @@ public class EcmascriptRuleChainVisitor extends AbstractRuleChainVisitor { + @Override protected void indexNodes(List nodes, RuleContext ctx) { // Visit Nodes in DFS order Stack stack = new Stack<>(); @@ -34,6 +35,7 @@ protected void indexNodes(List nodes, RuleContext ctx) { } } + @Override protected void visit(Rule rule, Node node, RuleContext ctx) { // Rule better either be a EcmascriptParserVisitor, or a XPathRule if (rule instanceof XPathRule) { diff --git a/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/rule/EcmascriptRuleViolationFactory.java b/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/rule/EcmascriptRuleViolationFactory.java index 1d63e1f400a..3deca0d4e37 100644 --- a/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/rule/EcmascriptRuleViolationFactory.java +++ b/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/rule/EcmascriptRuleViolationFactory.java @@ -25,6 +25,7 @@ protected RuleViolation createRuleViolation(Rule rule, RuleContext ruleContext, return new ParametricRuleViolation<>(rule, ruleContext, (EcmascriptNode) node, message); } + @Override protected RuleViolation createRuleViolation(Rule rule, RuleContext ruleContext, Node node, String message, int beginLine, int endLine) { return null; // FIXME diff --git a/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/rule/basic/ConsistentReturnRule.java b/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/rule/bestpractices/ConsistentReturnRule.java similarity index 95% rename from pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/rule/basic/ConsistentReturnRule.java rename to pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/rule/bestpractices/ConsistentReturnRule.java index 350a28def85..89e0974fced 100644 --- a/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/rule/basic/ConsistentReturnRule.java +++ b/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript/rule/bestpractices/ConsistentReturnRule.java @@ -2,7 +2,7 @@ * BSD-style license; for more info see http://pmd.sourceforge.net/license.html */ -package net.sourceforge.pmd.lang.ecmascript.rule.basic; +package net.sourceforge.pmd.lang.ecmascript.rule.bestpractices; import java.util.List; diff --git a/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript5/Ecmascript5TokenManager.java b/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript5/Ecmascript5TokenManager.java index a629dbf924b..bdfea305bc4 100644 --- a/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript5/Ecmascript5TokenManager.java +++ b/pmd-javascript/src/main/java/net/sourceforge/pmd/lang/ecmascript5/Ecmascript5TokenManager.java @@ -18,7 +18,7 @@ public class Ecmascript5TokenManager implements TokenManager { /** * Creates a new Ecmascript 5 Token Manager from the given source code. - * + * * @param source * the source code */ @@ -26,6 +26,7 @@ public Ecmascript5TokenManager(Reader source) { tokenManager = new Ecmascript5ParserTokenManager(new SimpleCharStream(source)); } + @Override public Object getNextToken() { return tokenManager.getNextToken(); } diff --git a/pmd-javascript/src/main/resources/category/ecmascript/bestpractices.xml b/pmd-javascript/src/main/resources/category/ecmascript/bestpractices.xml new file mode 100644 index 00000000000..52cdac1822b --- /dev/null +++ b/pmd-javascript/src/main/resources/category/ecmascript/bestpractices.xml @@ -0,0 +1,195 @@ + + + + + +Rules which enforce generally accepted best practices. + + + + Avoid using with - it's bad news + 1 + + + + + + + + + + + + + + +ECMAScript does provide for return types on functions, and therefore there is no solid rule as to their usage. +However, when a function does use returns they should all have a value, or all with no value. Mixed return +usage is likely a bug, or at best poor style. + + 2 + + + + + + + +This rule helps to avoid using accidently global variables by simply missing the "var" declaration. +Global variables can lead to side-effects that are hard to debug. + + 1 + + + + + + + + + + + + + + +A for-in loop in which the variable name is not explicitly scoped to the enclosing scope with the 'var' keyword can +refer to a variable in an enclosing scope outside the nearest enclosing scope. This will overwrite the +existing value of the variable in the outer scope when the body of the for-in is evaluated. When the for-in loop +has finished, the variable will contain the last value used in the for-in, and the original value from before +the for-in loop will be gone. Since the for-in variable name is most likely intended to be a temporary name, it +is better to explicitly scope the variable name to the nearest enclosing scope with 'var'. + + 1 + + + + + + + + + + + + + + +This rule checks for usages of parseInt. While the second parameter is optional and usually defaults +to 10 (base/radix is 10 for a decimal number), different implementations may behave differently. +It also improves readability, if the base is given. + +See also: [parseInt()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/parseInt) + + 1 + + + + + + + + + + + + + diff --git a/pmd-javascript/src/main/resources/category/ecmascript/categories.properties b/pmd-javascript/src/main/resources/category/ecmascript/categories.properties new file mode 100644 index 00000000000..d8a50e2fe75 --- /dev/null +++ b/pmd-javascript/src/main/resources/category/ecmascript/categories.properties @@ -0,0 +1,17 @@ +# +# BSD-style license; for more info see http://pmd.sourceforge.net/license.html +# + +rulesets.filenames=\ + category/ecmascript/bestpractices.xml,\ + category/ecmascript/codestyle.xml,\ + category/ecmascript/errorprone.xml + +# +#empty categories: +# +#category/ecmascript/design.xml, +#category/ecmascript/documentation.xml, +#category/ecmascript/multithreading.xml, +#category/ecmascript/performance.xml, +#category/ecmascript/security.xml, diff --git a/pmd-javascript/src/main/resources/category/ecmascript/codestyle.xml b/pmd-javascript/src/main/resources/category/ecmascript/codestyle.xml new file mode 100644 index 00000000000..285262c59d2 --- /dev/null +++ b/pmd-javascript/src/main/resources/category/ecmascript/codestyle.xml @@ -0,0 +1,351 @@ + + + + + +Rules which enforce a specific coding style. + + + + + +Avoid assignments in operands; this can make code more complicated and harder to read. This is sometime +indicative of the bug where the assignment operator '=' was used instead of the equality operator '=='. + + 2 + + + + + + + + + + + + + + + + + + + + +Avoid using 'for' statements without using curly braces. + + 3 + + + + + + + + + + + + + + +Avoid using if..else statements without using curly braces. + + 3 + + + + + + + + + + + + + + +Avoid using if statements without using curly braces. + + 3 + + + + + + + + + + + + + + +The else block in a if-else-construct is unnecessary if the `if` block contains a return. +Then the content of the else block can be put outside. + +See also: <http://eslint.org/docs/rules/no-else-return> + + 3 + + + + + + + + + + + + + + +An unnecessary Block is present. Such Blocks are often used in other languages to +introduce a new variable scope. Blocks do not behave like this in ECMAScipt, and using them can +be misleading. Considering removing this unnecessary Block. + + 3 + + + + + + + + + + + + + + Unnecessary parentheses should be removed. + 4 + + + + + + + + + + + + + + +A 'return', 'break', 'continue', or 'throw' statement should be the last in a block. Statements after these +will never execute. This is a bug, or extremely poor style. + + 1 + + + + + + + + + + + + + + +Avoid using 'while' statements without using curly braces. + + 3 + + + + + + + + + + + + + diff --git a/pmd-javascript/src/main/resources/category/ecmascript/design.xml b/pmd-javascript/src/main/resources/category/ecmascript/design.xml new file mode 100644 index 00000000000..bfb2bad983c --- /dev/null +++ b/pmd-javascript/src/main/resources/category/ecmascript/design.xml @@ -0,0 +1,12 @@ + + + + + +Rules that help you discover design issues. + + + diff --git a/pmd-javascript/src/main/resources/category/ecmascript/documentation.xml b/pmd-javascript/src/main/resources/category/ecmascript/documentation.xml new file mode 100644 index 00000000000..8e7fe038963 --- /dev/null +++ b/pmd-javascript/src/main/resources/category/ecmascript/documentation.xml @@ -0,0 +1,12 @@ + + + + + +Rules that are related to code documentation. + + + diff --git a/pmd-javascript/src/main/resources/category/ecmascript/errorprone.xml b/pmd-javascript/src/main/resources/category/ecmascript/errorprone.xml new file mode 100644 index 00000000000..4b48e85dfe4 --- /dev/null +++ b/pmd-javascript/src/main/resources/category/ecmascript/errorprone.xml @@ -0,0 +1,128 @@ + + + + + +Rules to detect constructs that are either broken, extremely confusing or prone to runtime errors. + + + + +This rule helps improve code portability due to differences in browser treatment of trailing commas in object or array literals. + + 1 + + + + + + + + + + + + + + + + +Using == in condition may lead to unexpected results, as the variables are automatically casted to be of the +same type. The === operator avoids the casting. + + 3 + + + + + + + + + + + + + + +The numeric literal will have a different value at runtime, which can happen if you provide too much +precision in a floating point number. This may result in numeric calculations being in error. + + 2 + + + + + + + + + + + + + diff --git a/pmd-javascript/src/main/resources/category/ecmascript/multithreading.xml b/pmd-javascript/src/main/resources/category/ecmascript/multithreading.xml new file mode 100644 index 00000000000..ee972ffd580 --- /dev/null +++ b/pmd-javascript/src/main/resources/category/ecmascript/multithreading.xml @@ -0,0 +1,12 @@ + + + + + +Rules that flag issues when dealing with multiple threads of execution. + + + diff --git a/pmd-javascript/src/main/resources/category/ecmascript/performance.xml b/pmd-javascript/src/main/resources/category/ecmascript/performance.xml new file mode 100644 index 00000000000..a3a1b2fd09c --- /dev/null +++ b/pmd-javascript/src/main/resources/category/ecmascript/performance.xml @@ -0,0 +1,12 @@ + + + + + +Rules that flag suboptimal code. + + + diff --git a/pmd-javascript/src/main/resources/category/ecmascript/security.xml b/pmd-javascript/src/main/resources/category/ecmascript/security.xml new file mode 100644 index 00000000000..2c59db990c1 --- /dev/null +++ b/pmd-javascript/src/main/resources/category/ecmascript/security.xml @@ -0,0 +1,12 @@ + + + + + +Rules that flag potential security flaws. + + + diff --git a/pmd-javascript/src/main/resources/rulesets/ecmascript/basic.xml b/pmd-javascript/src/main/resources/rulesets/ecmascript/basic.xml index 32e7ba49abb..2d7f4e50909 100644 --- a/pmd-javascript/src/main/resources/rulesets/ecmascript/basic.xml +++ b/pmd-javascript/src/main/resources/rulesets/ecmascript/basic.xml @@ -1,379 +1,21 @@ + xmlns="http://pmd.sourceforge.net/ruleset/2.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd"> - Rules concerning basic ECMAScript guidelines. + Rules concerning basic ECMAScript guidelines. - - -Avoid assignments in operands; this can make code more complicated and harder to read. This is sometime -indicative of the bug where the assignment operator '=' was used instead of the equality operator '=='. - - 2 - - - - - - - - - - - - - - - + -function getX() { - return 3; -} -]]> - - - - - -A 'return', 'break', 'continue', or 'throw' statement should be the last in a block. Statements after these -will never execute. This is a bug, or extremely poor style. - - 1 - - - - - - - - - - - - - - -The numeric literal will have at different value at runtime, which can happen if you provide too much -precision in a floating point number. This may result in numeric calculations being in error. - - 2 - - - - - - - - - - - - - - -ECMAScript does provide for return types on functions, and therefore there is no solid rule as to their usage. -However, when a function does use returns they should all have a value, or all with no value. Mixed return -usage is likely a bug, or at best poor style. - - 2 - - - - - - - -A for-in loop in which the variable name is not explicitly scoped to the enclosing scope with the 'var' keyword can -refer to a variable in an enclosing scope outside the nearest enclosing scope. This will overwrite the -existing value of the variable in the outer scope when the body of the for-in is evaluated. When the for-in loop -has finished, the variable will contain the last value used in the for-in, and the original value from before -the for-in loop will be gone. Since the for-in variable name is most likely intended to be a temporary name, it -is better to explicitly scope the variable name to the nearest enclosing scope with 'var'. - - 1 - - - - - - - - - - - - - - -Using == in condition may lead to unexpected results, as the variables are automatically casted to be of the -same type. The === operator avoids the casting. - - 3 - - - - - - - - - - - - - - -This rule helps to avoid using accidently global variables by simply missing the "var" declaration. -Global variables can lead to side-effects that are hard to debug. - - 1 - - - - - - - - - - - - - - -This rule helps improve code portability due to differences in browser treatment of trailing commas in object or array literals. - - 1 - - - - - - - - - - - - - - - - -This rule checks for usages of parseInt. While the second parameter is optional and usually defaults -to 10 (base/radix is 10 for a decimal number), different implementations may behave differently. -It also improves readability, if the base is given. - -See also: [parseInt()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/parseInt) - - 1 - - - - - - - - - - - + " + + + + + + diff --git a/pmd-javascript/src/main/resources/rulesets/ecmascript/braces.xml b/pmd-javascript/src/main/resources/rulesets/ecmascript/braces.xml index 7a45898ea0d..623314a0a25 100644 --- a/pmd-javascript/src/main/resources/rulesets/ecmascript/braces.xml +++ b/pmd-javascript/src/main/resources/rulesets/ecmascript/braces.xml @@ -1,151 +1,16 @@ - -The Braces Ruleset contains a collection of braces rules. - - - - -Avoid using if statements without using curly braces. - - 3 - - - - - - - - - - - - - + xmlns="http://pmd.sourceforge.net/ruleset/2.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd"> -Avoid using if..else statements without using curly braces. - - 3 - - - - - - - - - - - - - - -Avoid using 'while' statements without using curly braces. - - 3 - - - - - - - - - - - - - - -Avoid using 'for' statements without using curly braces. +The Braces Ruleset contains a collection of braces rules. - 3 - - - - - - - - - - - + + + + diff --git a/pmd-javascript/src/main/resources/rulesets/ecmascript/controversial.xml b/pmd-javascript/src/main/resources/rulesets/ecmascript/controversial.xml index 3383da9f4f7..d7af94c2586 100644 --- a/pmd-javascript/src/main/resources/rulesets/ecmascript/controversial.xml +++ b/pmd-javascript/src/main/resources/rulesets/ecmascript/controversial.xml @@ -1,39 +1,15 @@ + xmlns="http://pmd.sourceforge.net/ruleset/2.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd"> - + The Controversial ruleset contains rules that, for whatever reason, are considered controversial. They are held here to allow people to include them as they see fit within their custom rulesets. - + - - Avoid using with - it's bad news - 1 - - - - - - - - - - - + diff --git a/pmd-javascript/src/main/resources/rulesets/ecmascript/rulesets.properties b/pmd-javascript/src/main/resources/rulesets/ecmascript/rulesets.properties index f16abf38415..d8a50e2fe75 100644 --- a/pmd-javascript/src/main/resources/rulesets/ecmascript/rulesets.properties +++ b/pmd-javascript/src/main/resources/rulesets/ecmascript/rulesets.properties @@ -3,7 +3,15 @@ # rulesets.filenames=\ - rulesets/ecmascript/basic.xml,\ - rulesets/ecmascript/braces.xml,\ - rulesets/ecmascript/controversial.xml,\ - rulesets/ecmascript/unnecessary.xml + category/ecmascript/bestpractices.xml,\ + category/ecmascript/codestyle.xml,\ + category/ecmascript/errorprone.xml + +# +#empty categories: +# +#category/ecmascript/design.xml, +#category/ecmascript/documentation.xml, +#category/ecmascript/multithreading.xml, +#category/ecmascript/performance.xml, +#category/ecmascript/security.xml, diff --git a/pmd-javascript/src/main/resources/rulesets/ecmascript/unnecessary.xml b/pmd-javascript/src/main/resources/rulesets/ecmascript/unnecessary.xml index f5a7ad8eef8..e6ca60db735 100644 --- a/pmd-javascript/src/main/resources/rulesets/ecmascript/unnecessary.xml +++ b/pmd-javascript/src/main/resources/rulesets/ecmascript/unnecessary.xml @@ -1,116 +1,15 @@ - -The Unnecessary Ruleset contains a collection of rules for unnecessary code. - - - - Unnecessary parentheses should be removed. - 4 - - - - - - - - - - - - - - -An unnecessary Block is present. Such Blocks are often used in other languages to -introduce a new variable scope. Blocks do not behave like this in ECMAScipt, and using them can -be misleading. Considering removing this unnecessary Block. - - 3 - - - - - - - - - - - - - + xmlns="http://pmd.sourceforge.net/ruleset/2.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd"> -The else block in a if-else-construct is unnecessary if the `if` block contains a return. -Then the content of the else block can be put outside. - -See also: <http://eslint.org/docs/rules/no-else-return> +The Unnecessary Ruleset contains a collection of rules for unnecessary code. - 3 - - - - - - - - - - - + + + diff --git a/pmd-javascript/src/test/java/net/sourceforge/pmd/cpd/EcmascriptTokenizerTest.java b/pmd-javascript/src/test/java/net/sourceforge/pmd/cpd/EcmascriptTokenizerTest.java index 5f573573875..edf4dafdf6e 100644 --- a/pmd-javascript/src/test/java/net/sourceforge/pmd/cpd/EcmascriptTokenizerTest.java +++ b/pmd-javascript/src/test/java/net/sourceforge/pmd/cpd/EcmascriptTokenizerTest.java @@ -33,6 +33,22 @@ public void test2() throws IOException { t.tokenize(sourceCode, tokens); assertEquals(45, tokens.size()); } + + @Test + public void testIgnoreBetweenSpecialComments() throws IOException { + final String code = "// CPD-OFF" + PMD.EOL + + "function switchToRealPassword() {" + PMD.EOL + + "var real = $('realPass');" + PMD.EOL + + " var prompt = $('promptPass');" + PMD.EOL + + "// CPD-ON" + PMD.EOL + + "}" + PMD.EOL; + + Tokenizer t = new EcmascriptTokenizer(); + SourceCode sourceCode = new SourceCode(new SourceCode.StringCodeLoader(code)); + Tokens tokens = new Tokens(); + t.tokenize(sourceCode, tokens); + assertEquals(2, tokens.size()); // Only "}" and EOL + } /** * See: https://sourceforge.net/p/pmd/bugs/1239/ diff --git a/pmd-javascript/src/test/java/net/sourceforge/pmd/lang/ecmascript/rule/basic/BasicRulesTest.java b/pmd-javascript/src/test/java/net/sourceforge/pmd/lang/ecmascript/rule/basic/BasicRulesTest.java deleted file mode 100644 index 1c6fb9190b3..00000000000 --- a/pmd-javascript/src/test/java/net/sourceforge/pmd/lang/ecmascript/rule/basic/BasicRulesTest.java +++ /dev/null @@ -1,25 +0,0 @@ -/** - * BSD-style license; for more info see http://pmd.sourceforge.net/license.html - */ - -package net.sourceforge.pmd.lang.ecmascript.rule.basic; - -import net.sourceforge.pmd.testframework.SimpleAggregatorTst; - -public class BasicRulesTest extends SimpleAggregatorTst { - - private static final String RULESET = "ecmascript-basic"; - - @Override - public void setUp() { - addRule(RULESET, "AssignmentInOperand"); - addRule(RULESET, "AvoidTrailingComma"); - addRule(RULESET, "ConsistentReturn"); - addRule(RULESET, "InnaccurateNumericLiteral"); - addRule(RULESET, "ScopeForInVariable"); - addRule(RULESET, "UnreachableCode"); - addRule(RULESET, "EqualComparison"); - addRule(RULESET, "GlobalVariable"); - addRule(RULESET, "UseBaseWithParseInt"); - } -} diff --git a/pmd-javascript/src/test/java/net/sourceforge/pmd/lang/ecmascript/rule/bestpractices/AvoidWithStatementTest.java b/pmd-javascript/src/test/java/net/sourceforge/pmd/lang/ecmascript/rule/bestpractices/AvoidWithStatementTest.java new file mode 100644 index 00000000000..bd5518696b7 --- /dev/null +++ b/pmd-javascript/src/test/java/net/sourceforge/pmd/lang/ecmascript/rule/bestpractices/AvoidWithStatementTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.ecmascript.rule.bestpractices; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class AvoidWithStatementTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-javascript/src/test/java/net/sourceforge/pmd/lang/ecmascript/rule/bestpractices/ConsistentReturnTest.java b/pmd-javascript/src/test/java/net/sourceforge/pmd/lang/ecmascript/rule/bestpractices/ConsistentReturnTest.java new file mode 100644 index 00000000000..aae6a135d4f --- /dev/null +++ b/pmd-javascript/src/test/java/net/sourceforge/pmd/lang/ecmascript/rule/bestpractices/ConsistentReturnTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.ecmascript.rule.bestpractices; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class ConsistentReturnTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-javascript/src/test/java/net/sourceforge/pmd/lang/ecmascript/rule/bestpractices/GlobalVariableTest.java b/pmd-javascript/src/test/java/net/sourceforge/pmd/lang/ecmascript/rule/bestpractices/GlobalVariableTest.java new file mode 100644 index 00000000000..683d24de02c --- /dev/null +++ b/pmd-javascript/src/test/java/net/sourceforge/pmd/lang/ecmascript/rule/bestpractices/GlobalVariableTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.ecmascript.rule.bestpractices; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class GlobalVariableTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-javascript/src/test/java/net/sourceforge/pmd/lang/ecmascript/rule/bestpractices/ScopeForInVariableTest.java b/pmd-javascript/src/test/java/net/sourceforge/pmd/lang/ecmascript/rule/bestpractices/ScopeForInVariableTest.java new file mode 100644 index 00000000000..6956e4511da --- /dev/null +++ b/pmd-javascript/src/test/java/net/sourceforge/pmd/lang/ecmascript/rule/bestpractices/ScopeForInVariableTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.ecmascript.rule.bestpractices; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class ScopeForInVariableTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-javascript/src/test/java/net/sourceforge/pmd/lang/ecmascript/rule/bestpractices/UseBaseWithParseIntTest.java b/pmd-javascript/src/test/java/net/sourceforge/pmd/lang/ecmascript/rule/bestpractices/UseBaseWithParseIntTest.java new file mode 100644 index 00000000000..4e9682176f1 --- /dev/null +++ b/pmd-javascript/src/test/java/net/sourceforge/pmd/lang/ecmascript/rule/bestpractices/UseBaseWithParseIntTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.ecmascript.rule.bestpractices; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class UseBaseWithParseIntTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-javascript/src/test/java/net/sourceforge/pmd/lang/ecmascript/rule/braces/BracesRulesTest.java b/pmd-javascript/src/test/java/net/sourceforge/pmd/lang/ecmascript/rule/braces/BracesRulesTest.java deleted file mode 100644 index 2e400e363f6..00000000000 --- a/pmd-javascript/src/test/java/net/sourceforge/pmd/lang/ecmascript/rule/braces/BracesRulesTest.java +++ /dev/null @@ -1,20 +0,0 @@ -/** - * BSD-style license; for more info see http://pmd.sourceforge.net/license.html - */ - -package net.sourceforge.pmd.lang.ecmascript.rule.braces; - -import net.sourceforge.pmd.testframework.SimpleAggregatorTst; - -public class BracesRulesTest extends SimpleAggregatorTst { - - private static final String RULESET = "ecmascript-braces"; - - @Override - public void setUp() { - addRule(RULESET, "ForLoopsMustUseBraces"); - addRule(RULESET, "IfElseStmtsMustUseBraces"); - addRule(RULESET, "IfStmtsMustUseBraces"); - addRule(RULESET, "WhileLoopsMustUseBraces"); - } -} diff --git a/pmd-javascript/src/test/java/net/sourceforge/pmd/lang/ecmascript/rule/codestyle/AssignmentInOperandTest.java b/pmd-javascript/src/test/java/net/sourceforge/pmd/lang/ecmascript/rule/codestyle/AssignmentInOperandTest.java new file mode 100644 index 00000000000..7016e982b56 --- /dev/null +++ b/pmd-javascript/src/test/java/net/sourceforge/pmd/lang/ecmascript/rule/codestyle/AssignmentInOperandTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.ecmascript.rule.codestyle; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class AssignmentInOperandTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-javascript/src/test/java/net/sourceforge/pmd/lang/ecmascript/rule/codestyle/ForLoopsMustUseBracesTest.java b/pmd-javascript/src/test/java/net/sourceforge/pmd/lang/ecmascript/rule/codestyle/ForLoopsMustUseBracesTest.java new file mode 100644 index 00000000000..13d9908bc69 --- /dev/null +++ b/pmd-javascript/src/test/java/net/sourceforge/pmd/lang/ecmascript/rule/codestyle/ForLoopsMustUseBracesTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.ecmascript.rule.codestyle; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class ForLoopsMustUseBracesTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-javascript/src/test/java/net/sourceforge/pmd/lang/ecmascript/rule/codestyle/IfElseStmtsMustUseBracesTest.java b/pmd-javascript/src/test/java/net/sourceforge/pmd/lang/ecmascript/rule/codestyle/IfElseStmtsMustUseBracesTest.java new file mode 100644 index 00000000000..ff854d637cc --- /dev/null +++ b/pmd-javascript/src/test/java/net/sourceforge/pmd/lang/ecmascript/rule/codestyle/IfElseStmtsMustUseBracesTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.ecmascript.rule.codestyle; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class IfElseStmtsMustUseBracesTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-javascript/src/test/java/net/sourceforge/pmd/lang/ecmascript/rule/codestyle/IfStmtsMustUseBracesTest.java b/pmd-javascript/src/test/java/net/sourceforge/pmd/lang/ecmascript/rule/codestyle/IfStmtsMustUseBracesTest.java new file mode 100644 index 00000000000..dfaef80b324 --- /dev/null +++ b/pmd-javascript/src/test/java/net/sourceforge/pmd/lang/ecmascript/rule/codestyle/IfStmtsMustUseBracesTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.ecmascript.rule.codestyle; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class IfStmtsMustUseBracesTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-javascript/src/test/java/net/sourceforge/pmd/lang/ecmascript/rule/codestyle/NoElseReturnTest.java b/pmd-javascript/src/test/java/net/sourceforge/pmd/lang/ecmascript/rule/codestyle/NoElseReturnTest.java new file mode 100644 index 00000000000..5d928fcf6de --- /dev/null +++ b/pmd-javascript/src/test/java/net/sourceforge/pmd/lang/ecmascript/rule/codestyle/NoElseReturnTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.ecmascript.rule.codestyle; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class NoElseReturnTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-javascript/src/test/java/net/sourceforge/pmd/lang/ecmascript/rule/codestyle/UnnecessaryBlockTest.java b/pmd-javascript/src/test/java/net/sourceforge/pmd/lang/ecmascript/rule/codestyle/UnnecessaryBlockTest.java new file mode 100644 index 00000000000..013c249c5e1 --- /dev/null +++ b/pmd-javascript/src/test/java/net/sourceforge/pmd/lang/ecmascript/rule/codestyle/UnnecessaryBlockTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.ecmascript.rule.codestyle; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class UnnecessaryBlockTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-javascript/src/test/java/net/sourceforge/pmd/lang/ecmascript/rule/codestyle/UnnecessaryParenthesesTest.java b/pmd-javascript/src/test/java/net/sourceforge/pmd/lang/ecmascript/rule/codestyle/UnnecessaryParenthesesTest.java new file mode 100644 index 00000000000..818c4375a64 --- /dev/null +++ b/pmd-javascript/src/test/java/net/sourceforge/pmd/lang/ecmascript/rule/codestyle/UnnecessaryParenthesesTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.ecmascript.rule.codestyle; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class UnnecessaryParenthesesTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-javascript/src/test/java/net/sourceforge/pmd/lang/ecmascript/rule/codestyle/UnreachableCodeTest.java b/pmd-javascript/src/test/java/net/sourceforge/pmd/lang/ecmascript/rule/codestyle/UnreachableCodeTest.java new file mode 100644 index 00000000000..42bb27eaf02 --- /dev/null +++ b/pmd-javascript/src/test/java/net/sourceforge/pmd/lang/ecmascript/rule/codestyle/UnreachableCodeTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.ecmascript.rule.codestyle; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class UnreachableCodeTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-javascript/src/test/java/net/sourceforge/pmd/lang/ecmascript/rule/codestyle/WhileLoopsMustUseBracesTest.java b/pmd-javascript/src/test/java/net/sourceforge/pmd/lang/ecmascript/rule/codestyle/WhileLoopsMustUseBracesTest.java new file mode 100644 index 00000000000..6b822a35ba3 --- /dev/null +++ b/pmd-javascript/src/test/java/net/sourceforge/pmd/lang/ecmascript/rule/codestyle/WhileLoopsMustUseBracesTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.ecmascript.rule.codestyle; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class WhileLoopsMustUseBracesTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-javascript/src/test/java/net/sourceforge/pmd/lang/ecmascript/rule/controversial/ControversialRulesTest.java b/pmd-javascript/src/test/java/net/sourceforge/pmd/lang/ecmascript/rule/controversial/ControversialRulesTest.java deleted file mode 100644 index 266fbb5cff7..00000000000 --- a/pmd-javascript/src/test/java/net/sourceforge/pmd/lang/ecmascript/rule/controversial/ControversialRulesTest.java +++ /dev/null @@ -1,17 +0,0 @@ -/** - * BSD-style license; for more info see http://pmd.sourceforge.net/license.html - */ - -package net.sourceforge.pmd.lang.ecmascript.rule.controversial; - -import net.sourceforge.pmd.testframework.SimpleAggregatorTst; - -public class ControversialRulesTest extends SimpleAggregatorTst { - - private static final String RULESET = "ecmascript-controversial"; - - @Override - public void setUp() { - addRule(RULESET, "AvoidWithStatement"); - } -} diff --git a/pmd-javascript/src/test/java/net/sourceforge/pmd/lang/ecmascript/rule/errorprone/AvoidTrailingCommaTest.java b/pmd-javascript/src/test/java/net/sourceforge/pmd/lang/ecmascript/rule/errorprone/AvoidTrailingCommaTest.java new file mode 100644 index 00000000000..e4ce7c82445 --- /dev/null +++ b/pmd-javascript/src/test/java/net/sourceforge/pmd/lang/ecmascript/rule/errorprone/AvoidTrailingCommaTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.ecmascript.rule.errorprone; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class AvoidTrailingCommaTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-javascript/src/test/java/net/sourceforge/pmd/lang/ecmascript/rule/errorprone/EqualComparisonTest.java b/pmd-javascript/src/test/java/net/sourceforge/pmd/lang/ecmascript/rule/errorprone/EqualComparisonTest.java new file mode 100644 index 00000000000..5ec5a9088d9 --- /dev/null +++ b/pmd-javascript/src/test/java/net/sourceforge/pmd/lang/ecmascript/rule/errorprone/EqualComparisonTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.ecmascript.rule.errorprone; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class EqualComparisonTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-javascript/src/test/java/net/sourceforge/pmd/lang/ecmascript/rule/errorprone/InnaccurateNumericLiteralTest.java b/pmd-javascript/src/test/java/net/sourceforge/pmd/lang/ecmascript/rule/errorprone/InnaccurateNumericLiteralTest.java new file mode 100644 index 00000000000..f2dc5217c01 --- /dev/null +++ b/pmd-javascript/src/test/java/net/sourceforge/pmd/lang/ecmascript/rule/errorprone/InnaccurateNumericLiteralTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.ecmascript.rule.errorprone; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class InnaccurateNumericLiteralTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-javascript/src/test/java/net/sourceforge/pmd/lang/ecmascript/rule/unnecessary/UnnecessaryRulesTest.java b/pmd-javascript/src/test/java/net/sourceforge/pmd/lang/ecmascript/rule/unnecessary/UnnecessaryRulesTest.java deleted file mode 100644 index 2ed4b10ef78..00000000000 --- a/pmd-javascript/src/test/java/net/sourceforge/pmd/lang/ecmascript/rule/unnecessary/UnnecessaryRulesTest.java +++ /dev/null @@ -1,19 +0,0 @@ -/** - * BSD-style license; for more info see http://pmd.sourceforge.net/license.html - */ - -package net.sourceforge.pmd.lang.ecmascript.rule.unnecessary; - -import net.sourceforge.pmd.testframework.SimpleAggregatorTst; - -public class UnnecessaryRulesTest extends SimpleAggregatorTst { - - private static final String RULESET = "ecmascript-unnecessary"; - - @Override - public void setUp() { - addRule(RULESET, "UnnecessaryBlock"); - addRule(RULESET, "UnnecessaryParentheses"); - addRule(RULESET, "NoElseReturn"); - } -} diff --git a/pmd-javascript/src/test/resources/net/sourceforge/pmd/lang/ecmascript/rule/basic/xml/InnaccurateNumericLiteral.xml b/pmd-javascript/src/test/resources/net/sourceforge/pmd/lang/ecmascript/rule/basic/xml/InnaccurateNumericLiteral.xml deleted file mode 100644 index 6659953521f..00000000000 --- a/pmd-javascript/src/test/resources/net/sourceforge/pmd/lang/ecmascript/rule/basic/xml/InnaccurateNumericLiteral.xml +++ /dev/null @@ -1,66 +0,0 @@ - - - - - 0 - - ecmascript 3 - - - - 1 - - ecmascript 3 - - - - 0 - - ecmascript 3 - - - - 1 - - ecmascript 3 - - - - 0 - - ecmascript 3 - - - - 1 - - ecmascript 3 - - diff --git a/pmd-javascript/src/test/resources/net/sourceforge/pmd/lang/ecmascript/rule/controversial/xml/AvoidWithStatement.xml b/pmd-javascript/src/test/resources/net/sourceforge/pmd/lang/ecmascript/rule/bestpractices/xml/AvoidWithStatement.xml similarity index 100% rename from pmd-javascript/src/test/resources/net/sourceforge/pmd/lang/ecmascript/rule/controversial/xml/AvoidWithStatement.xml rename to pmd-javascript/src/test/resources/net/sourceforge/pmd/lang/ecmascript/rule/bestpractices/xml/AvoidWithStatement.xml diff --git a/pmd-javascript/src/test/resources/net/sourceforge/pmd/lang/ecmascript/rule/basic/xml/ConsistentReturn.xml b/pmd-javascript/src/test/resources/net/sourceforge/pmd/lang/ecmascript/rule/bestpractices/xml/ConsistentReturn.xml similarity index 100% rename from pmd-javascript/src/test/resources/net/sourceforge/pmd/lang/ecmascript/rule/basic/xml/ConsistentReturn.xml rename to pmd-javascript/src/test/resources/net/sourceforge/pmd/lang/ecmascript/rule/bestpractices/xml/ConsistentReturn.xml diff --git a/pmd-javascript/src/test/resources/net/sourceforge/pmd/lang/ecmascript/rule/basic/xml/GlobalVariable.xml b/pmd-javascript/src/test/resources/net/sourceforge/pmd/lang/ecmascript/rule/bestpractices/xml/GlobalVariable.xml similarity index 100% rename from pmd-javascript/src/test/resources/net/sourceforge/pmd/lang/ecmascript/rule/basic/xml/GlobalVariable.xml rename to pmd-javascript/src/test/resources/net/sourceforge/pmd/lang/ecmascript/rule/bestpractices/xml/GlobalVariable.xml diff --git a/pmd-javascript/src/test/resources/net/sourceforge/pmd/lang/ecmascript/rule/basic/xml/ScopeForInVariable.xml b/pmd-javascript/src/test/resources/net/sourceforge/pmd/lang/ecmascript/rule/bestpractices/xml/ScopeForInVariable.xml similarity index 100% rename from pmd-javascript/src/test/resources/net/sourceforge/pmd/lang/ecmascript/rule/basic/xml/ScopeForInVariable.xml rename to pmd-javascript/src/test/resources/net/sourceforge/pmd/lang/ecmascript/rule/bestpractices/xml/ScopeForInVariable.xml diff --git a/pmd-javascript/src/test/resources/net/sourceforge/pmd/lang/ecmascript/rule/basic/xml/UseBaseWithParseInt.xml b/pmd-javascript/src/test/resources/net/sourceforge/pmd/lang/ecmascript/rule/bestpractices/xml/UseBaseWithParseInt.xml similarity index 100% rename from pmd-javascript/src/test/resources/net/sourceforge/pmd/lang/ecmascript/rule/basic/xml/UseBaseWithParseInt.xml rename to pmd-javascript/src/test/resources/net/sourceforge/pmd/lang/ecmascript/rule/bestpractices/xml/UseBaseWithParseInt.xml diff --git a/pmd-javascript/src/test/resources/net/sourceforge/pmd/lang/ecmascript/rule/basic/xml/AssignmentInOperand.xml b/pmd-javascript/src/test/resources/net/sourceforge/pmd/lang/ecmascript/rule/codestyle/xml/AssignmentInOperand.xml similarity index 100% rename from pmd-javascript/src/test/resources/net/sourceforge/pmd/lang/ecmascript/rule/basic/xml/AssignmentInOperand.xml rename to pmd-javascript/src/test/resources/net/sourceforge/pmd/lang/ecmascript/rule/codestyle/xml/AssignmentInOperand.xml diff --git a/pmd-javascript/src/test/resources/net/sourceforge/pmd/lang/ecmascript/rule/braces/xml/ForLoopsMustUseBraces.xml b/pmd-javascript/src/test/resources/net/sourceforge/pmd/lang/ecmascript/rule/codestyle/xml/ForLoopsMustUseBraces.xml similarity index 100% rename from pmd-javascript/src/test/resources/net/sourceforge/pmd/lang/ecmascript/rule/braces/xml/ForLoopsMustUseBraces.xml rename to pmd-javascript/src/test/resources/net/sourceforge/pmd/lang/ecmascript/rule/codestyle/xml/ForLoopsMustUseBraces.xml diff --git a/pmd-javascript/src/test/resources/net/sourceforge/pmd/lang/ecmascript/rule/braces/xml/IfElseStmtsMustUseBraces.xml b/pmd-javascript/src/test/resources/net/sourceforge/pmd/lang/ecmascript/rule/codestyle/xml/IfElseStmtsMustUseBraces.xml similarity index 100% rename from pmd-javascript/src/test/resources/net/sourceforge/pmd/lang/ecmascript/rule/braces/xml/IfElseStmtsMustUseBraces.xml rename to pmd-javascript/src/test/resources/net/sourceforge/pmd/lang/ecmascript/rule/codestyle/xml/IfElseStmtsMustUseBraces.xml diff --git a/pmd-javascript/src/test/resources/net/sourceforge/pmd/lang/ecmascript/rule/braces/xml/IfStmtsMustUseBraces.xml b/pmd-javascript/src/test/resources/net/sourceforge/pmd/lang/ecmascript/rule/codestyle/xml/IfStmtsMustUseBraces.xml similarity index 100% rename from pmd-javascript/src/test/resources/net/sourceforge/pmd/lang/ecmascript/rule/braces/xml/IfStmtsMustUseBraces.xml rename to pmd-javascript/src/test/resources/net/sourceforge/pmd/lang/ecmascript/rule/codestyle/xml/IfStmtsMustUseBraces.xml diff --git a/pmd-javascript/src/test/resources/net/sourceforge/pmd/lang/ecmascript/rule/unnecessary/xml/NoElseReturn.xml b/pmd-javascript/src/test/resources/net/sourceforge/pmd/lang/ecmascript/rule/codestyle/xml/NoElseReturn.xml similarity index 100% rename from pmd-javascript/src/test/resources/net/sourceforge/pmd/lang/ecmascript/rule/unnecessary/xml/NoElseReturn.xml rename to pmd-javascript/src/test/resources/net/sourceforge/pmd/lang/ecmascript/rule/codestyle/xml/NoElseReturn.xml diff --git a/pmd-javascript/src/test/resources/net/sourceforge/pmd/lang/ecmascript/rule/unnecessary/xml/UnnecessaryBlock.xml b/pmd-javascript/src/test/resources/net/sourceforge/pmd/lang/ecmascript/rule/codestyle/xml/UnnecessaryBlock.xml similarity index 100% rename from pmd-javascript/src/test/resources/net/sourceforge/pmd/lang/ecmascript/rule/unnecessary/xml/UnnecessaryBlock.xml rename to pmd-javascript/src/test/resources/net/sourceforge/pmd/lang/ecmascript/rule/codestyle/xml/UnnecessaryBlock.xml diff --git a/pmd-javascript/src/test/resources/net/sourceforge/pmd/lang/ecmascript/rule/unnecessary/xml/UnnecessaryParentheses.xml b/pmd-javascript/src/test/resources/net/sourceforge/pmd/lang/ecmascript/rule/codestyle/xml/UnnecessaryParentheses.xml similarity index 100% rename from pmd-javascript/src/test/resources/net/sourceforge/pmd/lang/ecmascript/rule/unnecessary/xml/UnnecessaryParentheses.xml rename to pmd-javascript/src/test/resources/net/sourceforge/pmd/lang/ecmascript/rule/codestyle/xml/UnnecessaryParentheses.xml diff --git a/pmd-javascript/src/test/resources/net/sourceforge/pmd/lang/ecmascript/rule/basic/xml/UnreachableCode.xml b/pmd-javascript/src/test/resources/net/sourceforge/pmd/lang/ecmascript/rule/codestyle/xml/UnreachableCode.xml similarity index 100% rename from pmd-javascript/src/test/resources/net/sourceforge/pmd/lang/ecmascript/rule/basic/xml/UnreachableCode.xml rename to pmd-javascript/src/test/resources/net/sourceforge/pmd/lang/ecmascript/rule/codestyle/xml/UnreachableCode.xml diff --git a/pmd-javascript/src/test/resources/net/sourceforge/pmd/lang/ecmascript/rule/braces/xml/WhileLoopsMustUseBraces.xml b/pmd-javascript/src/test/resources/net/sourceforge/pmd/lang/ecmascript/rule/codestyle/xml/WhileLoopsMustUseBraces.xml similarity index 100% rename from pmd-javascript/src/test/resources/net/sourceforge/pmd/lang/ecmascript/rule/braces/xml/WhileLoopsMustUseBraces.xml rename to pmd-javascript/src/test/resources/net/sourceforge/pmd/lang/ecmascript/rule/codestyle/xml/WhileLoopsMustUseBraces.xml diff --git a/pmd-javascript/src/test/resources/net/sourceforge/pmd/lang/ecmascript/rule/basic/xml/AvoidTrailingComma.xml b/pmd-javascript/src/test/resources/net/sourceforge/pmd/lang/ecmascript/rule/errorprone/xml/AvoidTrailingComma.xml similarity index 100% rename from pmd-javascript/src/test/resources/net/sourceforge/pmd/lang/ecmascript/rule/basic/xml/AvoidTrailingComma.xml rename to pmd-javascript/src/test/resources/net/sourceforge/pmd/lang/ecmascript/rule/errorprone/xml/AvoidTrailingComma.xml diff --git a/pmd-javascript/src/test/resources/net/sourceforge/pmd/lang/ecmascript/rule/basic/xml/EqualComparison.xml b/pmd-javascript/src/test/resources/net/sourceforge/pmd/lang/ecmascript/rule/errorprone/xml/EqualComparison.xml similarity index 100% rename from pmd-javascript/src/test/resources/net/sourceforge/pmd/lang/ecmascript/rule/basic/xml/EqualComparison.xml rename to pmd-javascript/src/test/resources/net/sourceforge/pmd/lang/ecmascript/rule/errorprone/xml/EqualComparison.xml diff --git a/pmd-javascript/src/test/resources/net/sourceforge/pmd/lang/ecmascript/rule/errorprone/xml/InnaccurateNumericLiteral.xml b/pmd-javascript/src/test/resources/net/sourceforge/pmd/lang/ecmascript/rule/errorprone/xml/InnaccurateNumericLiteral.xml new file mode 100644 index 00000000000..d5123e18871 --- /dev/null +++ b/pmd-javascript/src/test/resources/net/sourceforge/pmd/lang/ecmascript/rule/errorprone/xml/InnaccurateNumericLiteral.xml @@ -0,0 +1,68 @@ + + + + + Ok integer + 0 + + ecmascript 3 + + + + Bad integer + 1 + var x = 999999999999999999999999; + ecmascript 3 + + + + Ok float + 0 + + ecmascript 3 + + + + Bad float + 1 + + ecmascript 3 + + + + Ok float w/ exponent + 0 + + ecmascript 3 + + + + Bad float w/ exponent + 1 + + ecmascript 3 + + + + #861 [ecmascript] InnaccurateNumericLiteral false positive + 0 + + ecmascript 3 + + diff --git a/pmd-jsp/etc/grammar/JspParser.jjt b/pmd-jsp/etc/grammar/JspParser.jjt index 7ea94dcf5f2..08b9218a32c 100644 --- a/pmd-jsp/etc/grammar/JspParser.jjt +++ b/pmd-jsp/etc/grammar/JspParser.jjt @@ -137,6 +137,7 @@ PARSER_END(JspParser) // anything but --%> | <#NO_JSP_COMMENT_END: (~["-"] | "-" ~["-"] | "--" ~["%"] | "--%" ~[">"])+ > | <#NO_JSP_TAG_END: ( ~["%"] | ("%" ~[">"]) )+ > +| <#NO_JSP_TAG_END_EXCEPT_QUOTED: ( ~["%", "\"", "'"] | ("%" ~[">"]) | )+ > } @@ -194,7 +195,7 @@ PARSER_END(JspParser) TOKEN : { " > : AfterTagState -| > +| > } TOKEN : @@ -206,7 +207,7 @@ PARSER_END(JspParser) TOKEN : { " > : AfterTagState -| > +| > } TOKEN : @@ -248,7 +249,7 @@ PARSER_END(JspParser) TOKEN : { - > + | "${" ( | )* "}") > | " > : AfterTagState | " | "!>") > : AfterTagState | " > : AfterTagState diff --git a/pmd-jsp/pom.xml b/pmd-jsp/pom.xml index ba0abfbea78..64c86863567 100644 --- a/pmd-jsp/pom.xml +++ b/pmd-jsp/pom.xml @@ -7,13 +7,9 @@ net.sourceforge.pmd pmd - 6.0.0-SNAPSHOT + 6.10.0-SNAPSHOT - - ${basedir}/../pmd-core - - @@ -100,7 +96,20 @@ net.sourceforge.pmd pmd-core + + commons-io + commons-io + + + net.sourceforge.saxon + saxon + + + junit + junit + test + net.sourceforge.pmd pmd-test diff --git a/pmd-jsp/src/main/ant/alljavacc.xml b/pmd-jsp/src/main/ant/alljavacc.xml index da338412b93..34207aa4a6b 100644 --- a/pmd-jsp/src/main/ant/alljavacc.xml +++ b/pmd-jsp/src/main/ant/alljavacc.xml @@ -62,12 +62,55 @@ public class]]> - public class Token - public class Token implements java.io.Serializable + +public class Token implements GenericToken, java.io.Serializable]]> + + + public Token specialToken; + + + 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 0d29af459e3..543602a60ce 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 @@ 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/cpd/JSPTokenizer.java b/pmd-jsp/src/main/java/net/sourceforge/pmd/cpd/JSPTokenizer.java index 679ec783a45..48cfb265008 100644 --- a/pmd-jsp/src/main/java/net/sourceforge/pmd/cpd/JSPTokenizer.java +++ b/pmd-jsp/src/main/java/net/sourceforge/pmd/cpd/JSPTokenizer.java @@ -4,11 +4,10 @@ package net.sourceforge.pmd.cpd; +import java.io.IOException; import java.io.Reader; import java.io.StringReader; -import org.apache.commons.io.IOUtils; - import net.sourceforge.pmd.lang.LanguageRegistry; import net.sourceforge.pmd.lang.LanguageVersionHandler; import net.sourceforge.pmd.lang.TokenManager; @@ -18,15 +17,13 @@ public class JSPTokenizer implements Tokenizer { + @Override public void tokenize(SourceCode sourceCode, Tokens tokenEntries) { StringBuilder buffer = sourceCode.getCodeBuffer(); LanguageVersionHandler languageVersionHandler = LanguageRegistry.getLanguage(JspLanguageModule.NAME) .getDefaultVersion().getLanguageVersionHandler(); - Reader reader = null; - try { - reader = new StringReader(buffer.toString()); - reader = IOUtil.skipBOM(reader); + try (Reader reader = IOUtil.skipBOM(new StringReader(buffer.toString()))) { TokenManager tokenMgr = languageVersionHandler.getParser(languageVersionHandler.getDefaultParserOptions()) .getTokenManager(sourceCode.getFileName(), reader); Token currentToken = (Token) tokenMgr.getNextToken(); @@ -36,8 +33,8 @@ public void tokenize(SourceCode sourceCode, Tokens tokenEntries) { currentToken.beginLine)); currentToken = (Token) tokenMgr.getNextToken(); } - } finally { - IOUtils.closeQuietly(reader); + } catch (IOException e) { + e.printStackTrace(); } tokenEntries.add(TokenEntry.getEOF()); } diff --git a/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/JspHandler.java b/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/JspHandler.java index 33d07840d15..5965244316c 100644 --- a/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/JspHandler.java +++ b/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/JspHandler.java @@ -12,36 +12,30 @@ import net.sourceforge.pmd.lang.VisitorStarter; import net.sourceforge.pmd.lang.XPathHandler; import net.sourceforge.pmd.lang.ast.Node; -import net.sourceforge.pmd.lang.ast.xpath.AbstractASTXPathHandler; +import net.sourceforge.pmd.lang.ast.xpath.DefaultASTXPathHandler; import net.sourceforge.pmd.lang.jsp.ast.DumpFacade; import net.sourceforge.pmd.lang.jsp.ast.JspNode; import net.sourceforge.pmd.lang.jsp.rule.JspRuleViolationFactory; import net.sourceforge.pmd.lang.rule.RuleViolationFactory; -import net.sf.saxon.sxpath.IndependentContext; - /** * Implementation of LanguageVersionHandler for the JSP parser. - * + * * @author pieter_van_raemdonck - Application Engineers NV/SA - www.ae.be */ public class JspHandler extends AbstractLanguageVersionHandler { @Override public XPathHandler getXPathHandler() { - return new AbstractASTXPathHandler() { - public void initialize() { - } - - public void initialize(IndependentContext context) { - } - }; + return new DefaultASTXPathHandler(); } + @Override public RuleViolationFactory getRuleViolationFactory() { return JspRuleViolationFactory.INSTANCE; } + @Override public Parser getParser(ParserOptions parserOptions) { return new JspParser(parserOptions); } @@ -49,6 +43,7 @@ public Parser getParser(ParserOptions parserOptions) { @Override public VisitorStarter getDumpFacade(final Writer writer, final String prefix, final boolean recurse) { return new VisitorStarter() { + @Override public void start(Node rootNode) { new DumpFacade().initializeWith(writer, prefix, recurse, (JspNode) rootNode); } 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 8fa12452f27..2ad43f34beb 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/main/java/net/sourceforge/pmd/lang/jsp/JspParser.java b/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/JspParser.java index e5e74518dd9..0409ca60240 100644 --- a/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/JspParser.java +++ b/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/JspParser.java @@ -30,15 +30,18 @@ public TokenManager createTokenManager(Reader source) { return new JspTokenManager(source); } + @Override public boolean canParse() { return true; } + @Override public Node parse(String fileName, Reader source) throws ParseException { AbstractTokenManager.setFileName(fileName); return new net.sourceforge.pmd.lang.jsp.ast.JspParser(new SimpleCharStream(source)).CompilationUnit(); } + @Override public Map getSuppressMap() { return new HashMap<>(); // FIXME } diff --git a/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/JspTokenManager.java b/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/JspTokenManager.java index 3de636041ab..5d19e5d8a08 100644 --- a/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/JspTokenManager.java +++ b/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/JspTokenManager.java @@ -20,10 +20,12 @@ public JspTokenManager(Reader source) { tokenManager = new JspParserTokenManager(new JavaCharStream(source)); } + @Override public Object getNextToken() { return tokenManager.getNextToken(); } + @Override public void setFileName(String fileName) { JspParserTokenManager.setFileName(fileName); } diff --git a/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/ast/ASTAttributeValue.java b/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/ast/ASTAttributeValue.java index 27b842bd602..8c56e9bfd57 100644 --- a/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/ast/ASTAttributeValue.java +++ b/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/ast/ASTAttributeValue.java @@ -17,6 +17,7 @@ public ASTAttributeValue(JspParser p, int id) { /** * Accept the visitor. * */ + @Override public Object jjtAccept(JspParserVisitor visitor, Object data) { return visitor.visit(this, data); } diff --git a/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/ast/ASTCData.java b/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/ast/ASTCData.java index c10ff286d1c..00e04452d17 100644 --- a/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/ast/ASTCData.java +++ b/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/ast/ASTCData.java @@ -17,6 +17,7 @@ public ASTCData(JspParser p, int id) { /** * Accept the visitor. * */ + @Override public Object jjtAccept(JspParserVisitor visitor, Object data) { return visitor.visit(this, data); } diff --git a/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/ast/ASTCommentTag.java b/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/ast/ASTCommentTag.java index c26c74d943e..2dbc4f0f61e 100644 --- a/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/ast/ASTCommentTag.java +++ b/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/ast/ASTCommentTag.java @@ -17,6 +17,7 @@ public ASTCommentTag(JspParser p, int id) { /** * Accept the visitor. * */ + @Override public Object jjtAccept(JspParserVisitor visitor, Object data) { return visitor.visit(this, data); } diff --git a/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/ast/ASTCompilationUnit.java b/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/ast/ASTCompilationUnit.java index 35ac45872d0..dd4b411f4a8 100644 --- a/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/ast/ASTCompilationUnit.java +++ b/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/ast/ASTCompilationUnit.java @@ -19,6 +19,7 @@ public ASTCompilationUnit(JspParser p, int id) { /** * Accept the visitor. * */ + @Override public Object jjtAccept(JspParserVisitor visitor, Object data) { return visitor.visit(this, data); } diff --git a/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/ast/ASTContent.java b/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/ast/ASTContent.java index 7202cae4794..5a17814f1cb 100644 --- a/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/ast/ASTContent.java +++ b/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/ast/ASTContent.java @@ -17,6 +17,7 @@ public ASTContent(JspParser p, int id) { /** * Accept the visitor. * */ + @Override public Object jjtAccept(JspParserVisitor visitor, Object data) { return visitor.visit(this, data); } diff --git a/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/ast/ASTElExpression.java b/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/ast/ASTElExpression.java index 7f082d35c63..9b411302108 100644 --- a/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/ast/ASTElExpression.java +++ b/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/ast/ASTElExpression.java @@ -17,6 +17,7 @@ public ASTElExpression(JspParser p, int id) { /** * Accept the visitor. * */ + @Override public Object jjtAccept(JspParserVisitor visitor, Object data) { return visitor.visit(this, data); } diff --git a/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/ast/ASTJspComment.java b/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/ast/ASTJspComment.java index 9b36e14fba2..8d7c7b3d750 100644 --- a/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/ast/ASTJspComment.java +++ b/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/ast/ASTJspComment.java @@ -17,6 +17,7 @@ public ASTJspComment(JspParser p, int id) { /** * Accept the visitor. * */ + @Override public Object jjtAccept(JspParserVisitor visitor, Object data) { return visitor.visit(this, data); } diff --git a/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/ast/ASTJspDeclaration.java b/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/ast/ASTJspDeclaration.java index fe2eb479408..65814f382e6 100644 --- a/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/ast/ASTJspDeclaration.java +++ b/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/ast/ASTJspDeclaration.java @@ -17,6 +17,7 @@ public ASTJspDeclaration(JspParser p, int id) { /** * Accept the visitor. * */ + @Override public Object jjtAccept(JspParserVisitor visitor, Object data) { return visitor.visit(this, data); } diff --git a/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/ast/ASTJspDeclarations.java b/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/ast/ASTJspDeclarations.java index 885b2e88475..afc12ee48a4 100644 --- a/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/ast/ASTJspDeclarations.java +++ b/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/ast/ASTJspDeclarations.java @@ -17,6 +17,7 @@ public ASTJspDeclarations(JspParser p, int id) { /** * Accept the visitor. * */ + @Override public Object jjtAccept(JspParserVisitor visitor, Object data) { return visitor.visit(this, data); } diff --git a/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/ast/ASTJspDocument.java b/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/ast/ASTJspDocument.java index f6c8a4459ce..9826676aab6 100644 --- a/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/ast/ASTJspDocument.java +++ b/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/ast/ASTJspDocument.java @@ -17,6 +17,7 @@ public ASTJspDocument(JspParser p, int id) { /** * Accept the visitor. * */ + @Override public Object jjtAccept(JspParserVisitor visitor, Object data) { return visitor.visit(this, data); } diff --git a/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/ast/ASTJspExpression.java b/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/ast/ASTJspExpression.java index fb3436515bc..6a9f41ae8d5 100644 --- a/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/ast/ASTJspExpression.java +++ b/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/ast/ASTJspExpression.java @@ -17,6 +17,7 @@ public ASTJspExpression(JspParser p, int id) { /** * Accept the visitor. * */ + @Override public Object jjtAccept(JspParserVisitor visitor, Object data) { return visitor.visit(this, data); } diff --git a/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/ast/ASTJspExpressionInAttribute.java b/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/ast/ASTJspExpressionInAttribute.java index 1b789b7937e..8a1f88f9c4c 100644 --- a/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/ast/ASTJspExpressionInAttribute.java +++ b/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/ast/ASTJspExpressionInAttribute.java @@ -17,6 +17,7 @@ public ASTJspExpressionInAttribute(JspParser p, int id) { /** * Accept the visitor. * */ + @Override public Object jjtAccept(JspParserVisitor visitor, Object data) { return visitor.visit(this, data); } diff --git a/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/ast/ASTJspScriptlet.java b/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/ast/ASTJspScriptlet.java index 31517840a44..8fec3d9414d 100644 --- a/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/ast/ASTJspScriptlet.java +++ b/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/ast/ASTJspScriptlet.java @@ -17,6 +17,7 @@ public ASTJspScriptlet(JspParser p, int id) { /** * Accept the visitor. * */ + @Override public Object jjtAccept(JspParserVisitor visitor, Object data) { return visitor.visit(this, data); } diff --git a/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/ast/ASTText.java b/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/ast/ASTText.java index b37f5827419..56a4654e017 100644 --- a/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/ast/ASTText.java +++ b/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/ast/ASTText.java @@ -17,6 +17,7 @@ public ASTText(JspParser p, int id) { /** * Accept the visitor. * */ + @Override public Object jjtAccept(JspParserVisitor visitor, Object data) { return visitor.visit(this, data); } diff --git a/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/ast/ASTUnparsedText.java b/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/ast/ASTUnparsedText.java index b1a7f384d4c..07b5bff083d 100644 --- a/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/ast/ASTUnparsedText.java +++ b/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/ast/ASTUnparsedText.java @@ -17,6 +17,7 @@ public ASTUnparsedText(JspParser p, int id) { /** * Accept the visitor. * */ + @Override public Object jjtAccept(JspParserVisitor visitor, Object data) { return visitor.visit(this, data); } diff --git a/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/ast/ASTValueBinding.java b/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/ast/ASTValueBinding.java index 00553f1ff6d..d7ddf39a4d5 100644 --- a/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/ast/ASTValueBinding.java +++ b/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/ast/ASTValueBinding.java @@ -17,6 +17,7 @@ public ASTValueBinding(JspParser p, int id) { /** * Accept the visitor. * */ + @Override public Object jjtAccept(JspParserVisitor visitor, Object data) { return visitor.visit(this, data); } diff --git a/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/ast/AbstractJspNode.java b/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/ast/AbstractJspNode.java index 6ddb35f76fe..97c1d3de89e 100644 --- a/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/ast/AbstractJspNode.java +++ b/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/ast/AbstractJspNode.java @@ -19,6 +19,7 @@ public AbstractJspNode(JspParser parser, int id) { this.parser = parser; } + @Override public void jjtOpen() { if (beginLine == -1 && parser.token.next != null) { beginLine = parser.token.next.beginLine; @@ -26,6 +27,7 @@ public void jjtOpen() { } } + @Override public void jjtClose() { if (beginLine == -1 && (children == null || children.length == 0)) { beginColumn = parser.token.beginColumn; @@ -40,6 +42,7 @@ public void jjtClose() { /** * Accept the visitor. * */ + @Override public Object jjtAccept(JspParserVisitor visitor, Object data) { return visitor.visit(this, data); } @@ -47,6 +50,7 @@ public Object jjtAccept(JspParserVisitor visitor, Object data) { /** * Accept the visitor. * */ + @Override public Object childrenAccept(JspParserVisitor visitor, Object data) { if (children != null) { for (int i = 0; i < children.length; ++i) { @@ -56,7 +60,11 @@ public Object childrenAccept(JspParserVisitor visitor, Object data) { return data; } - public String toString() { + + + + @Override + public String getXPathNodeName() { return JspParserTreeConstants.jjtNodeName[id]; } } diff --git a/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/ast/DumpFacade.java b/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/ast/DumpFacade.java index 0179275e95d..e1381a96264 100644 --- a/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/ast/DumpFacade.java +++ b/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/ast/DumpFacade.java @@ -47,7 +47,7 @@ private void dump(Node node, String prefix) { writer.print(prefix); // 2) JJT Name of the Node - writer.print(node.toString()); + writer.print(node.getXPathNodeName()); // // If there are any additional details, then: diff --git a/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/ast/JspParserVisitorAdapter.java b/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/ast/JspParserVisitorAdapter.java index 3a83294ab3b..a167ecd04a3 100644 --- a/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/ast/JspParserVisitorAdapter.java +++ b/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/ast/JspParserVisitorAdapter.java @@ -6,95 +6,118 @@ public class JspParserVisitorAdapter implements JspParserVisitor { + @Override public Object visit(JspNode node, Object data) { node.childrenAccept(this, data); return null; } + @Override public Object visit(ASTCompilationUnit node, Object data) { return visit((JspNode) node, data); } + @Override public Object visit(ASTContent node, Object data) { return visit((JspNode) node, data); } + @Override public Object visit(ASTJspDirective node, Object data) { return visit((JspNode) node, data); } + @Override public Object visit(ASTJspDirectiveAttribute node, Object data) { return visit((JspNode) node, data); } + @Override public Object visit(ASTJspScriptlet node, Object data) { return visit((JspNode) node, data); } + @Override public Object visit(ASTJspExpression node, Object data) { return visit((JspNode) node, data); } + @Override public Object visit(ASTJspDeclaration node, Object data) { return visit((JspNode) node, data); } + @Override public Object visit(ASTJspComment node, Object data) { return visit((JspNode) node, data); } + @Override public Object visit(ASTText node, Object data) { return visit((JspNode) node, data); } + @Override public Object visit(ASTUnparsedText node, Object data) { return visit((JspNode) node, data); } + @Override public Object visit(ASTElExpression node, Object data) { return visit((JspNode) node, data); } + @Override public Object visit(ASTValueBinding node, Object data) { return visit((JspNode) node, data); } + @Override public Object visit(ASTCData node, Object data) { return visit((JspNode) node, data); } + @Override public Object visit(ASTElement node, Object data) { return visit((JspNode) node, data); } + @Override public Object visit(ASTAttribute node, Object data) { return visit((JspNode) node, data); } + @Override public Object visit(ASTAttributeValue node, Object data) { return visit((JspNode) node, data); } + @Override public Object visit(ASTJspExpressionInAttribute node, Object data) { return visit((JspNode) node, data); } + @Override public Object visit(ASTCommentTag node, Object data) { return visit((JspNode) node, data); } + @Override public Object visit(ASTDeclaration node, Object data) { return visit((JspNode) node, data); } + @Override public Object visit(ASTDoctypeDeclaration node, Object data) { return visit((JspNode) node, data); } + @Override public Object visit(ASTDoctypeExternalId node, Object data) { return visit((JspNode) node, data); } + @Override public Object visit(ASTHtmlScript node, Object data) { return visit((JspNode) node, data); } diff --git a/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/ast/OpenTagRegister.java b/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/ast/OpenTagRegister.java index 8a4e604374e..4b76e95e963 100644 --- a/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/ast/OpenTagRegister.java +++ b/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/ast/OpenTagRegister.java @@ -15,9 +15,9 @@ * tag list and it will mark the first tag named 'x' as closed. If other tags * have been opened after 'x' ( <x> <y> <z> </x>) it * will mark y and z as unclosed. - * + * * @author Victor Bucutea - * + * */ public class OpenTagRegister { @@ -32,7 +32,7 @@ public void openTag(ASTElement elm) { } /** - * + * * @param closingTagName * @return true if a matching tag was found. False if no tag with this name * was ever opened ( or registered ) diff --git a/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/ast/StartAndEndTagMismatchException.java b/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/ast/StartAndEndTagMismatchException.java index de1320211bd..86c9131d9c6 100644 --- a/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/ast/StartAndEndTagMismatchException.java +++ b/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/ast/StartAndEndTagMismatchException.java @@ -73,7 +73,7 @@ public int getStartLine() { /* * (non-Javadoc) - * + * * @see java.lang.Throwable#getMessage() */ @Override diff --git a/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/ast/SyntaxErrorException.java b/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/ast/SyntaxErrorException.java index 1e2a60eed40..f886b882dc0 100644 --- a/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/ast/SyntaxErrorException.java +++ b/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/ast/SyntaxErrorException.java @@ -6,7 +6,7 @@ /** * Exception indicating that a syntactic error has been found. - * + * * @author Pieter_Van_Raemdonck * @since Created on 11-jan-2006 */ diff --git a/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/rule/AbstractJspRule.java b/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/rule/AbstractJspRule.java index f4eae5a37aa..350cc3313af 100644 --- a/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/rule/AbstractJspRule.java +++ b/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/rule/AbstractJspRule.java @@ -48,6 +48,7 @@ public void setUsesTypeResolution() { // No Type resolution for JSP rules? } + @Override public void apply(List nodes, RuleContext ctx) { visitAll(nodes, ctx); } @@ -65,95 +66,118 @@ protected void visitAll(List nodes, RuleContext ctx) { // complex Rule base class instead of from relatively simple Visitor. // + @Override public Object visit(JspNode node, Object data) { node.childrenAccept(this, data); return null; } + @Override public Object visit(ASTCompilationUnit node, Object data) { return visit((JspNode) node, data); } + @Override public Object visit(ASTContent node, Object data) { return visit((JspNode) node, data); } + @Override public Object visit(ASTJspDirective node, Object data) { return visit((JspNode) node, data); } + @Override public Object visit(ASTJspDirectiveAttribute node, Object data) { return visit((JspNode) node, data); } + @Override public Object visit(ASTJspScriptlet node, Object data) { return visit((JspNode) node, data); } + @Override public Object visit(ASTJspExpression node, Object data) { return visit((JspNode) node, data); } + @Override public Object visit(ASTJspDeclaration node, Object data) { return visit((JspNode) node, data); } + @Override public Object visit(ASTJspComment node, Object data) { return visit((JspNode) node, data); } + @Override public Object visit(ASTText node, Object data) { return visit((JspNode) node, data); } + @Override public Object visit(ASTUnparsedText node, Object data) { return visit((JspNode) node, data); } + @Override public Object visit(ASTElExpression node, Object data) { return visit((JspNode) node, data); } + @Override public Object visit(ASTValueBinding node, Object data) { return visit((JspNode) node, data); } + @Override public Object visit(ASTCData node, Object data) { return visit((JspNode) node, data); } + @Override public Object visit(ASTElement node, Object data) { return visit((JspNode) node, data); } + @Override public Object visit(ASTAttribute node, Object data) { return visit((JspNode) node, data); } + @Override public Object visit(ASTAttributeValue node, Object data) { return visit((JspNode) node, data); } + @Override public Object visit(ASTJspExpressionInAttribute node, Object data) { return visit((JspNode) node, data); } + @Override public Object visit(ASTCommentTag node, Object data) { return visit((JspNode) node, data); } + @Override public Object visit(ASTDeclaration node, Object data) { return visit((JspNode) node, data); } + @Override public Object visit(ASTDoctypeDeclaration node, Object data) { return visit((JspNode) node, data); } + @Override public Object visit(ASTDoctypeExternalId node, Object data) { return visit((JspNode) node, data); } + @Override public Object visit(ASTHtmlScript node, Object data) { return visit((JspNode) node, data); } diff --git a/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/rule/JspRuleChainVisitor.java b/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/rule/JspRuleChainVisitor.java index 1b02e3be947..a9f76d1c32c 100644 --- a/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/rule/JspRuleChainVisitor.java +++ b/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/rule/JspRuleChainVisitor.java @@ -18,10 +18,12 @@ public class JspRuleChainVisitor extends AbstractRuleChainVisitor { + @Override protected void indexNodes(List nodes, RuleContext ctx) { JspParserVisitor jspParserVisitor = new JspParserVisitorAdapter() { // Perform a visitation of the AST to index nodes which need // visiting by type + @Override public Object visit(JspNode node, Object data) { indexNode(node); return super.visit(node, data); @@ -33,6 +35,7 @@ public Object visit(JspNode node, Object data) { } } + @Override protected void visit(Rule rule, Node node, RuleContext ctx) { // Rule better either be a JspParserVisitor, or a XPathRule if (rule instanceof JspParserVisitor) { diff --git a/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/rule/JspRuleViolationFactory.java b/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/rule/JspRuleViolationFactory.java index 8f91eb339fc..8135d54f72e 100644 --- a/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/rule/JspRuleViolationFactory.java +++ b/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/rule/JspRuleViolationFactory.java @@ -25,6 +25,7 @@ protected RuleViolation createRuleViolation(Rule rule, RuleContext ruleContext, return new ParametricRuleViolation<>(rule, ruleContext, (JspNode) node, message); } + @Override protected RuleViolation createRuleViolation(Rule rule, RuleContext ruleContext, Node node, String message, int beginLine, int endLine) { return null; // FIXME diff --git a/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/rule/basic/DuplicateJspImportsRule.java b/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/rule/codestyle/DuplicateJspImportsRule.java similarity index 96% rename from pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/rule/basic/DuplicateJspImportsRule.java rename to pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/rule/codestyle/DuplicateJspImportsRule.java index 4763d420e8f..b02003f632e 100644 --- a/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/rule/basic/DuplicateJspImportsRule.java +++ b/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/rule/codestyle/DuplicateJspImportsRule.java @@ -2,7 +2,7 @@ * BSD-style license; for more info see http://pmd.sourceforge.net/license.html */ -package net.sourceforge.pmd.lang.jsp.rule.basic; +package net.sourceforge.pmd.lang.jsp.rule.codestyle; import java.util.HashSet; import java.util.List; diff --git a/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/rule/basic/NoInlineStyleInformationRule.java b/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/rule/design/NoInlineStyleInformationRule.java similarity index 93% rename from pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/rule/basic/NoInlineStyleInformationRule.java rename to pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/rule/design/NoInlineStyleInformationRule.java index de71e01ef5b..21f42db1b57 100644 --- a/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/rule/basic/NoInlineStyleInformationRule.java +++ b/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/rule/design/NoInlineStyleInformationRule.java @@ -2,8 +2,9 @@ * BSD-style license; for more info see http://pmd.sourceforge.net/license.html */ -package net.sourceforge.pmd.lang.jsp.rule.basic; +package net.sourceforge.pmd.lang.jsp.rule.design; +import java.util.Locale; import java.util.Set; import net.sourceforge.pmd.lang.jsp.ast.ASTAttribute; @@ -40,6 +41,7 @@ public class NoInlineStyleInformationRule extends AbstractJspRule { private static final Set STYLE_ATTRIBUTES = CollectionUtil .asSet(new String[] { "STYLE", "FONT", "SIZE", "COLOR", "FACE", "ALIGN", "VALIGN", "BGCOLOR" }); + @Override public Object visit(ASTAttribute node, Object data) { if (isStyleAttribute(node)) { addViolation(data, node); @@ -48,6 +50,7 @@ public Object visit(ASTAttribute node, Object data) { return super.visit(node, data); } + @Override public Object visit(ASTElement node, Object data) { if (isStyleElement(node)) { addViolation(data, node); @@ -64,7 +67,7 @@ public Object visit(ASTElement node, Object data) { * @return boolean */ private boolean isStyleElement(ASTElement elementNode) { - return STYLE_ELEMENT_NAMES.contains(elementNode.getName().toUpperCase()); + return STYLE_ELEMENT_NAMES.contains(elementNode.getName().toUpperCase(Locale.ROOT)); } /** @@ -77,10 +80,10 @@ private boolean isStyleElement(ASTElement elementNode) { * otherwise. */ private boolean isStyleAttribute(ASTAttribute attributeNode) { - if (STYLE_ATTRIBUTES.contains(attributeNode.getName().toUpperCase())) { + if (STYLE_ATTRIBUTES.contains(attributeNode.getName().toUpperCase(Locale.ROOT))) { if (attributeNode.jjtGetParent() instanceof ASTElement) { ASTElement parent = (ASTElement) attributeNode.jjtGetParent(); - if (ELEMENT_NAMES_THAT_CAN_HAVE_STYLE_ATTRIBUTES.contains(parent.getName().toUpperCase())) { + if (ELEMENT_NAMES_THAT_CAN_HAVE_STYLE_ATTRIBUTES.contains(parent.getName().toUpperCase(Locale.ROOT))) { return true; } } diff --git a/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/rule/basic/NoUnsanitizedJSPExpressionRule.java b/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/rule/security/NoUnsanitizedJSPExpressionRule.java similarity index 95% rename from pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/rule/basic/NoUnsanitizedJSPExpressionRule.java rename to pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/rule/security/NoUnsanitizedJSPExpressionRule.java index 8ebd7f5e5b0..a1b995d48fe 100644 --- a/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/rule/basic/NoUnsanitizedJSPExpressionRule.java +++ b/pmd-jsp/src/main/java/net/sourceforge/pmd/lang/jsp/rule/security/NoUnsanitizedJSPExpressionRule.java @@ -2,7 +2,7 @@ * BSD-style license; for more info see http://pmd.sourceforge.net/license.html */ -package net.sourceforge.pmd.lang.jsp.rule.basic; +package net.sourceforge.pmd.lang.jsp.rule.security; import net.sourceforge.pmd.lang.jsp.ast.ASTElExpression; import net.sourceforge.pmd.lang.jsp.ast.ASTElement; diff --git a/pmd-jsp/src/main/resources/category/jsp/bestpractices.xml b/pmd-jsp/src/main/resources/category/jsp/bestpractices.xml new file mode 100644 index 00000000000..564c6459755 --- /dev/null +++ b/pmd-jsp/src/main/resources/category/jsp/bestpractices.xml @@ -0,0 +1,134 @@ + + + + + +Rules which enforce generally accepted best practices. + + + + +Do not nest JSF component custom actions inside a custom action that iterates over its body. + + 3 + + + + + + + + + + +

+ + +]]> + + + + + +Do not use an attribute called 'class'. Use "styleclass" for CSS styles. + + 2 + + + + + + + + + +

Some text

+ +]]> +
+
+ + + +In a production system, HTML comments increase the payload +between the application server to the client, and serve +little other purpose. Consider switching to JSP comments. + + 2 + + + + + + + + +bad example><BODY> +<!-- HTML comment --> +</BODY> </HTML> + +<HTML><title>good example><BODY> +<%-- JSP comment --%> +</BODY> </HTML> +]]> + </example> + </rule> + + <rule name="NoJspForward" + language="jsp" + since="3.6" + message="Do not do a forward from within a JSP file." + class="net.sourceforge.pmd.lang.rule.XPathRule" + externalInfoUrl="${pmd.website.baseurl}/pmd_rules_jsp_bestpractices.html#nojspforward"> + <description> +Do not do a forward from within a JSP file. + </description> + <priority>3</priority> + <properties> + <property name="xpath"> + <value> +<![CDATA[ +//Element[ @Name="jsp:forward" ] +]]> + </value> + </property> + </properties> + <example> +<![CDATA[ +<jsp:forward page='UnderConstruction.jsp'/> +]]> + </example> + </rule> + +</ruleset> diff --git a/pmd-jsp/src/main/resources/category/jsp/categories.properties b/pmd-jsp/src/main/resources/category/jsp/categories.properties new file mode 100644 index 00000000000..c8b84831027 --- /dev/null +++ b/pmd-jsp/src/main/resources/category/jsp/categories.properties @@ -0,0 +1,17 @@ +# +# BSD-style license; for more info see http://pmd.sourceforge.net/license.html +# + +rulesets.filenames=\ + category/jsp/bestpractices.xml,\ + category/jsp/codestyle.xml,\ + category/jsp/design.xml,\ + category/jsp/errorprone.xml,\ + category/jsp/security.xml + +# +# categories without rules +# +# category/jsp/documentation.xml +# category/jsp/multithreading.xml +# category/jsp/performance.xml diff --git a/pmd-jsp/src/main/resources/category/jsp/codestyle.xml b/pmd-jsp/src/main/resources/category/jsp/codestyle.xml new file mode 100644 index 00000000000..0303c211187 --- /dev/null +++ b/pmd-jsp/src/main/resources/category/jsp/codestyle.xml @@ -0,0 +1,28 @@ +<?xml version="1.0"?> + +<ruleset name="Code Style" + xmlns="http://pmd.sourceforge.net/ruleset/2.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd"> + + <description> +Rules which enforce a specific coding style. + </description> + + <rule name="DuplicateJspImports" + since="3.7" + message="Avoid duplicate imports such as ''{0}''" + class="net.sourceforge.pmd.lang.jsp.rule.codestyle.DuplicateJspImportsRule" + externalInfoUrl="${pmd.website.baseurl}/pmd_rules_jsp_codestyle.html#duplicatejspimports"> + <description> +Avoid duplicate import statements inside JSP's. + </description> + <priority>3</priority> + <example> +<![CDATA[ +<%@ page import=\"com.foo.MyClass,com.foo.MyClass\"%><html><body><b><img src=\"<%=Some.get()%>/foo\">xx</img>text</b></body></html> +]]> + </example> + </rule> + +</ruleset> diff --git a/pmd-jsp/src/main/resources/category/jsp/design.xml b/pmd-jsp/src/main/resources/category/jsp/design.xml new file mode 100644 index 00000000000..11404c7284d --- /dev/null +++ b/pmd-jsp/src/main/resources/category/jsp/design.xml @@ -0,0 +1,134 @@ +<?xml version="1.0"?> + +<ruleset name="Design" + xmlns="http://pmd.sourceforge.net/ruleset/2.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd"> + + <description> +Rules that help you discover design issues. + </description> + + <rule name="NoInlineScript" + language="jsp" + since="4.0" + class="net.sourceforge.pmd.lang.rule.XPathRule" + message="Avoiding inlining HTML script content" + externalInfoUrl="${pmd.website.baseurl}/pmd_rules_jsp_design.html#noinlinescript"> + <description> +Avoid inlining HTML script content. Consider externalizing the HTML script using the 'src' attribute on the "script" element. +Externalized script could be reused between pages. Browsers can also cache the script, reducing overall download bandwidth. + </description> + <priority>3</priority> + <properties> + <property name="xpath"> + <value> +<![CDATA[ +//HtmlScript[@Image != ''] +]]> + </value> + </property> + </properties> + </rule> + + <rule name="NoInlineStyleInformation" + since="3.6" + message="Avoid having style information in JSP files." + class="net.sourceforge.pmd.lang.jsp.rule.design.NoInlineStyleInformationRule" + externalInfoUrl="${pmd.website.baseurl}/pmd_rules_jsp_design.html#noinlinestyleinformation"> + <description> +Style information should be put in CSS files, not in JSPs. Therefore, don't use <B> or <FONT> +tags, or attributes like "align='center'". + </description> + <priority>3</priority> + <example> +<![CDATA[ +<html><body><p align='center'><b>text</b></p></body></html> +]]> + </example> + </rule> + + <rule name="NoLongScripts" + language="jsp" + since="3.6" + message="Avoid having long scripts (e.g. Javascript) inside a JSP file." + class="net.sourceforge.pmd.lang.rule.XPathRule" + externalInfoUrl="${pmd.website.baseurl}/pmd_rules_jsp_design.html#nolongscripts"> + <description> +Scripts should be part of Tag Libraries, rather than part of JSP pages. + </description> + <priority>2</priority> + <properties> + <property name="xpath"> + <value> +<![CDATA[ +//HtmlScript[(@EndLine - @BeginLine > 10)] +]]> + </value> + </property> + </properties> + <example> +<![CDATA[ +<HTML> +<BODY> +<!--Java Script--> +<SCRIPT language="JavaScript" type="text/javascript"> +<!-- +function calcDays(){ + var date1 = document.getElementById('d1').lastChild.data; + var date2 = document.getElementById('d2').lastChild.data; + date1 = date1.split("-"); + date2 = date2.split("-"); + var sDate = new Date(date1[0]+"/"+date1[1]+"/"+date1[2]); + var eDate = new Date(date2[0]+"/"+date2[1]+"/"+date2[2]); + var daysApart = Math.abs(Math.round((sDate-eDate)/86400000)); + document.getElementById('diffDays').lastChild.data = daysApart; +} + +onload=calcDays; +//--> +</SCRIPT> +</BODY> +</HTML> +]]> + </example> + </rule> + + <rule name="NoScriptlets" + language="jsp" + since="3.6" + message="Avoid having scriptlets inside a JSP file." + class="net.sourceforge.pmd.lang.rule.XPathRule" + externalInfoUrl="${pmd.website.baseurl}/pmd_rules_jsp_design.html#noscriptlets"> + <description> +Scriptlets should be factored into Tag Libraries or JSP declarations, rather than being part of JSP pages. + </description> + <priority>3</priority> + <properties> + <property name="xpath"> + <value> +<![CDATA[ +//JspScriptlet +| +//Element[ upper-case(@Name)="JSP:SCRIPTLET" ] +]]> + </value> + </property> + </properties> + <example> +<![CDATA[ +<HTML> +<HEAD> +<% +response.setHeader("Pragma", "No-cache"); +%> +</HEAD> + <BODY> + <jsp:scriptlet>String title = "Hello world!";</jsp:scriptlet> + </BODY> +</HTML> +]]> + </example> + </rule> + +</ruleset> diff --git a/pmd-jsp/src/main/resources/category/jsp/documentation.xml b/pmd-jsp/src/main/resources/category/jsp/documentation.xml new file mode 100644 index 00000000000..c049f5b6f01 --- /dev/null +++ b/pmd-jsp/src/main/resources/category/jsp/documentation.xml @@ -0,0 +1,11 @@ +<?xml version="1.0"?> + +<ruleset name="Documentation" + xmlns="http://pmd.sourceforge.net/ruleset/2.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd"> + + <description> +Rules that are related to code documentation. + </description> +</ruleset> diff --git a/pmd-jsp/src/main/resources/category/jsp/errorprone.xml b/pmd-jsp/src/main/resources/category/jsp/errorprone.xml new file mode 100644 index 00000000000..482eef476b4 --- /dev/null +++ b/pmd-jsp/src/main/resources/category/jsp/errorprone.xml @@ -0,0 +1,48 @@ +<?xml version="1.0"?> + +<ruleset name="Error Prone" + xmlns="http://pmd.sourceforge.net/ruleset/2.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd"> + + <description> +Rules to detect constructs that are either broken, extremely confusing or prone to runtime errors. + </description> + + <rule name="JspEncoding" + language="jsp" + since="4.0" + class="net.sourceforge.pmd.lang.rule.XPathRule" + message="JSP file should use UTF-8 encoding" + externalInfoUrl="${pmd.website.baseurl}/pmd_rules_jsp_errorprone.html#jspencoding"> + <description> +A missing 'meta' tag or page directive will trigger this rule, as well as a non-UTF-8 charset. + </description> + <priority>3</priority> + <properties> + <property name="xpath"> + <value> +<![CDATA[ +//CompilationUnit/Content[ +not(Element[@Name="meta"][ + Attribute[@Name="content"]/AttributeValue[contains(lower-case(@Image),"charset=utf-8")] +]) +and + not(JspDirective[@Name='page']/JspDirectiveAttribute[@Name='contentType'][contains(lower-case(@Value),"charset=utf-8")]) +] +]]> + </value> + </property> + </properties> + <example> +<![CDATA[ +Most browsers should be able to interpret the following headers: + +<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %> + +<meta http-equiv="Content-Type"  content="text/html; charset=UTF-8" /> +]]> + </example> + </rule> + +</ruleset> diff --git a/pmd-jsp/src/main/resources/category/jsp/multithreading.xml b/pmd-jsp/src/main/resources/category/jsp/multithreading.xml new file mode 100644 index 00000000000..88c22dfc8bc --- /dev/null +++ b/pmd-jsp/src/main/resources/category/jsp/multithreading.xml @@ -0,0 +1,11 @@ +<?xml version="1.0"?> + +<ruleset name="Multithreading" + xmlns="http://pmd.sourceforge.net/ruleset/2.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd"> + + <description> +Rules that flag issues when dealing with multiple threads of execution. + </description> +</ruleset> diff --git a/pmd-jsp/src/main/resources/category/jsp/performance.xml b/pmd-jsp/src/main/resources/category/jsp/performance.xml new file mode 100644 index 00000000000..251663aafc1 --- /dev/null +++ b/pmd-jsp/src/main/resources/category/jsp/performance.xml @@ -0,0 +1,11 @@ +<?xml version="1.0"?> + +<ruleset name="Performance" + xmlns="http://pmd.sourceforge.net/ruleset/2.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd"> + + <description> +Rules that flag suboptimal code. + </description> +</ruleset> diff --git a/pmd-jsp/src/main/resources/category/jsp/security.xml b/pmd-jsp/src/main/resources/category/jsp/security.xml new file mode 100644 index 00000000000..57ebba02ea7 --- /dev/null +++ b/pmd-jsp/src/main/resources/category/jsp/security.xml @@ -0,0 +1,66 @@ +<?xml version="1.0"?> + +<ruleset name="Security" + xmlns="http://pmd.sourceforge.net/ruleset/2.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd"> + + <description> +Rules that flag potential security flaws. + </description> + + <rule name="IframeMissingSrcAttribute" + language="jsp" + since="3.6" + message="IFrames must have a src attribute." + class="net.sourceforge.pmd.lang.rule.XPathRule" + externalInfoUrl="${pmd.website.baseurl}/pmd_rules_jsp_security.html#iframemissingsrcattribute"> + <description> +IFrames which are missing a src element can cause security information popups in IE if you are accessing the page +through SSL. See http://support.microsoft.com/default.aspx?scid=kb;EN-US;Q261188 + </description> + <priority>2</priority> + <properties> + <property name="xpath"> + <value> +<![CDATA[ +//Element[upper-case(@Name)="IFRAME"][count(Attribute[upper-case(@Name)="SRC" ]) = 0] +]]> + </value> + </property> + </properties> + <example> +<![CDATA[ +<HTML><title>bad example><BODY> +<iframe></iframe> +</BODY> </HTML> + +<HTML><title>good example><BODY> +<iframe src="foo"></iframe> +</BODY> </HTML> +]]> + </example> + </rule> + + <rule name="NoUnsanitizedJSPExpression" + since="5.1.4" + class="net.sourceforge.pmd.lang.jsp.rule.security.NoUnsanitizedJSPExpressionRule" + message="Using unsanitized JSP expression can lead to Cross Site Scripting (XSS) attacks" + externalInfoUrl="${pmd.website.baseurl}/pmd_rules_jsp_security.html#nounsanitizedjspexpression"> + <description> +Avoid using expressions without escaping / sanitizing. This could lead to cross site scripting - as the expression +would be interpreted by the browser directly (e.g. "<script>alert('hello');</script>"). + </description> + <priority>3</priority> + <example> +<![CDATA[ +<%@ page contentType="text/html; charset=UTF-8" %> +<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %> +${expression} <!-- don't use this --> +${fn:escapeXml(expression)} <!-- instead, escape it --> +<c:out value="${expression}" /> <!-- or use c:out --> +]]> + </example> + </rule> + +</ruleset> diff --git a/pmd-jsp/src/main/resources/rulesets/jsp/basic-jsf.xml b/pmd-jsp/src/main/resources/rulesets/jsp/basic-jsf.xml index de392febc0e..22c8c5a29d4 100644 --- a/pmd-jsp/src/main/resources/rulesets/jsp/basic-jsf.xml +++ b/pmd-jsp/src/main/resources/rulesets/jsp/basic-jsf.xml @@ -3,43 +3,12 @@ <ruleset name="Basic JSF" xmlns="http://pmd.sourceforge.net/ruleset/2.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 http://pmd.sourceforge.net/ruleset_2_0_0.xsd"> - <description> -Rules concerning basic JSF guidelines. - </description> + xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd"> - <rule name="DontNestJsfInJstlIteration" - language="jsp" - since="3.6" - message="Do not nest JSF component custom actions inside a custom action that iterates over its body." - class="net.sourceforge.pmd.lang.rule.XPathRule" - externalInfoUrl="${pmd.website.baseurl}/pmd_rules_jsp_basic-jsf.html#dontnestjsfinjstliteration"> <description> -Do not nest JSF component custom actions inside a custom action that iterates over its body. +Rules concerning basic JSF guidelines. </description> - <priority>3</priority> - <properties> - <property name="xpath"> - <value> -<![CDATA[ -//Element[ @Name="c:forEach" ] // Element[ @NamespacePrefix="h" or @NamespacePrefix="f" ] -]]> - </value> - </property> - </properties> - <example> -<![CDATA[ -<html> - <body> - <ul> - <c:forEach items='${books}' var='b'> - <li> <h:outputText value='#{b}' /> </li> - </c:forEach> - </ul> - </body> -</html> -]]> - </example> - </rule> + + <rule ref="category/jsp/bestpractices.xml/DontNestJsfInJstlIteration" deprecated="true" /> </ruleset> diff --git a/pmd-jsp/src/main/resources/rulesets/jsp/basic.xml b/pmd-jsp/src/main/resources/rulesets/jsp/basic.xml index 96d016c507b..bbe9a50711f 100644 --- a/pmd-jsp/src/main/resources/rulesets/jsp/basic.xml +++ b/pmd-jsp/src/main/resources/rulesets/jsp/basic.xml @@ -3,335 +3,25 @@ <ruleset name="Basic JSP" xmlns="http://pmd.sourceforge.net/ruleset/2.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 http://pmd.sourceforge.net/ruleset_2_0_0.xsd"> + xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd"> - <description> -Rules concerning basic JSP guidelines. - </description> - - <rule name="NoLongScripts" - language="jsp" - since="3.6" - message="Avoid having long scripts (e.g. Javascript) inside a JSP file." - class="net.sourceforge.pmd.lang.rule.XPathRule" - externalInfoUrl="${pmd.website.baseurl}/pmd_rules_jsp_basic.html#nolongscripts"> - <description> -Scripts should be part of Tag Libraries, rather than part of JSP pages. - </description> - <priority>2</priority> - <properties> - <property name="xpath"> - <value> -<![CDATA[ -//HtmlScript[(@EndLine - @BeginLine > 10)] -]]> - </value> - </property> - </properties> - <example> -<![CDATA[ -<HTML> -<BODY> -<!--Java Script--> -<SCRIPT language="JavaScript" type="text/javascript"> -<!-- -function calcDays(){ - var date1 = document.getElementById('d1').lastChild.data; - var date2 = document.getElementById('d2').lastChild.data; - date1 = date1.split("-"); - date2 = date2.split("-"); - var sDate = new Date(date1[0]+"/"+date1[1]+"/"+date1[2]); - var eDate = new Date(date2[0]+"/"+date2[1]+"/"+date2[2]); - var daysApart = Math.abs(Math.round((sDate-eDate)/86400000)); - document.getElementById('diffDays').lastChild.data = daysApart; -} - -onload=calcDays; -//--> -</SCRIPT> -</BODY> -</HTML> -]]> - </example> - </rule> - - <rule name="NoScriptlets" - language="jsp" - since="3.6" - message="Avoid having scriptlets inside a JSP file." - class="net.sourceforge.pmd.lang.rule.XPathRule" - externalInfoUrl="${pmd.website.baseurl}/pmd_rules_jsp_basic.html#noscriptlets"> - <description> -Scriptlets should be factored into Tag Libraries or JSP declarations, rather than being part of JSP pages. - </description> - <priority>3</priority> - <properties> - <property name="xpath"> - <value> -<![CDATA[ -//JspScriptlet -| -//Element[ upper-case(@Name)="JSP:SCRIPTLET" ] -]]> - </value> - </property> - </properties> - <example> -<![CDATA[ -<HTML> -<HEAD> -<% -response.setHeader("Pragma", "No-cache"); -%> -</HEAD> - <BODY> - <jsp:scriptlet>String title = "Hello world!";</jsp:scriptlet> - </BODY> -</HTML> -]]> - </example> - </rule> - - <rule name="NoInlineStyleInformation" - since="3.6" - message="Avoid having style information in JSP files." - class="net.sourceforge.pmd.lang.jsp.rule.basic.NoInlineStyleInformationRule" - externalInfoUrl="${pmd.website.baseurl}/pmd_rules_jsp_basic.html#noinlinestyleinformation"> - <description> -Style information should be put in CSS files, not in JSPs. Therefore, don't use <B> or <FONT> -tags, or attributes like "align='center'". - </description> - <priority>3</priority> - <example> -<![CDATA[ -<html><body><p align='center'><b>text</b></p></body></html> -]]> - </example> - </rule> - - <rule name="NoClassAttribute" - language="jsp" - since="3.6" - message="Do not use an attribute called 'class'." - class="net.sourceforge.pmd.lang.rule.XPathRule" - externalInfoUrl="${pmd.website.baseurl}/pmd_rules_jsp_basic.html#noclassattribute"> - <description> -Do not use an attribute called 'class'. Use "styleclass" for CSS styles. - </description> - <priority>2</priority> - <properties> - <property name="xpath"> - <value> -<![CDATA[ -//Attribute[ upper-case(@Name)="CLASS" ] -]]> - </value> - </property> - </properties> - <example> -<![CDATA[ -<HTML> <BODY> -<P class="MajorHeading">Some text</P> -</BODY> </HTML> -]]> - </example> - </rule> - - <rule name="NoJspForward" - language="jsp" - since="3.6" - message="Do not do a forward from within a JSP file." - class="net.sourceforge.pmd.lang.rule.XPathRule" - externalInfoUrl="${pmd.website.baseurl}/pmd_rules_jsp_basic.html#nojspforward"> - <description> -Do not do a forward from within a JSP file. - </description> - <priority>3</priority> - <properties> - <property name="xpath"> - <value> -<![CDATA[ -//Element[ @Name="jsp:forward" ] -]]> - </value> - </property> - </properties> - <example> -<![CDATA[ -<jsp:forward page='UnderConstruction.jsp'/> -]]> - </example> - </rule> - - <rule name="IframeMissingSrcAttribute" - language="jsp" - since="3.6" - message="IFrames must have a src attribute." - class="net.sourceforge.pmd.lang.rule.XPathRule" - externalInfoUrl="${pmd.website.baseurl}/pmd_rules_jsp_basic.html#iframemissingsrcattribute"> <description> -IFrames which are missing a src element can cause security information popups in IE if you are accessing the page -through SSL. See http://support.microsoft.com/default.aspx?scid=kb;EN-US;Q261188 - </description> - <priority>2</priority> - <properties> - <property name="xpath"> - <value> -<![CDATA[ -//Element[upper-case(@Name)="IFRAME"][count(Attribute[upper-case(@Name)="SRC" ]) = 0] -]]> - </value> - </property> - </properties> - <example> -<![CDATA[ -<HTML><title>bad example><BODY> -<iframe></iframe> -</BODY> </HTML> - -<HTML><title>good example><BODY> -<iframe src="foo"></iframe> -</BODY> </HTML> -]]> - </example> - </rule> - - <rule name="NoHtmlComments" - language="jsp" - since="3.6" - message="Use JSP comments instead of HTML comments" - class="net.sourceforge.pmd.lang.rule.XPathRule" - externalInfoUrl="${pmd.website.baseurl}/pmd_rules_jsp_basic.html#nohtmlcomments"> - <description> -In a production system, HTML comments increase the payload -between the application server to the client, and serve -little other purpose. Consider switching to JSP comments. - </description> - <priority>2</priority> - <properties> - <property name="xpath"> - <value> -<![CDATA[ -//CommentTag -]]> - </value> - </property> - </properties> - <example> -<![CDATA[ -<HTML><title>bad example><BODY> -<!-- HTML comment --> -</BODY> </HTML> - -<HTML><title>good example><BODY> -<%-- JSP comment --%> -</BODY> </HTML> -]]> - </example> - </rule> - - <rule name="DuplicateJspImports" - since="3.7" - message="Avoid duplicate imports such as ''{0}''" - class="net.sourceforge.pmd.lang.jsp.rule.basic.DuplicateJspImportsRule" - externalInfoUrl="${pmd.website.baseurl}/pmd_rules_jsp_basic.html#duplicatejspimports"> - <description> -Avoid duplicate import statements inside JSP's. - </description> - <priority>3</priority> - <example> -<![CDATA[ -<%@ page import=\"com.foo.MyClass,com.foo.MyClass\"%><html><body><b><img src=\"<%=Some.get()%>/foo\">xx</img>text</b></body></html> -]]> - </example> - </rule> - - <rule name="JspEncoding" - language="jsp" - since="4.0" - class="net.sourceforge.pmd.lang.rule.XPathRule" - message="JSP file should use UTF-8 encoding" - externalInfoUrl="${pmd.website.baseurl}/pmd_rules_jsp_basic.html#jspencoding"> - <description> -A missing 'meta' tag or page directive will trigger this rule, as well as a non-UTF-8 charset. +Rules concerning basic JSP guidelines. </description> - <priority>3</priority> - <properties> - <property name="xpath"> - <value> -<![CDATA[ -//CompilationUnit/Content[ -not(Element[@Name="meta"][ - Attribute[@Name="content"]/AttributeValue[contains(lower-case(@Image),"charset=utf-8")] -]) -and - not(JspDirective[@Name='page']/JspDirectiveAttribute[@Name='contentType'][contains(lower-case(@Value),"charset=utf-8")]) -] -]]> - </value> - </property> - </properties> - <example> -<![CDATA[ -Most browsers should be able to interpret the following headers: -<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %> + <rule ref="category/jsp/design.xml/NoInlineScript" deprecated="true" /> + <rule ref="category/jsp/design.xml/NoInlineStyleInformation" deprecated="true" /> + <rule ref="category/jsp/design.xml/NoLongScripts" deprecated="true" /> + <rule ref="category/jsp/design.xml/NoScriptlets" deprecated="true" /> -<meta http-equiv="Content-Type"  content="text/html; charset=UTF-8" /> -]]> - </example> - </rule> + <rule ref="category/jsp/bestpractices.xml/NoClassAttribute" deprecated="true" /> + <rule ref="category/jsp/bestpractices.xml/NoHtmlComments" deprecated="true" /> + <rule ref="category/jsp/bestpractices.xml/NoJspForward" deprecated="true" /> - <rule name="NoInlineScript" - language="jsp" - since="4.0" - class="net.sourceforge.pmd.lang.rule.XPathRule" - message="Avoiding inlining HTML script content" - externalInfoUrl="${pmd.website.baseurl}/pmd_rules_jsp_basic.html#noinlinescript"> - <description> -Avoid inlining HTML script content. Consider externalizing the HTML script using the 'src' attribute on the "script" element. -Externalized script could be reused between pages. Browsers can also cache the script, reducing overall download bandwidth. - </description> - <priority>3</priority> - <properties> - <property name="xpath"> - <value> -<![CDATA[ -//HtmlScript[@Image != ''] -]]> - </value> - </property> - </properties> - <example> -<![CDATA[ -Most browsers should be able to interpret the following headers: + <rule ref="category/jsp/security.xml/IframeMissingSrcAttribute" deprecated="true" /> + <rule ref="category/jsp/security.xml/NoUnsanitizedJSPExpression" deprecated="true" /> -<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %> - -<meta http-equiv="Content-Type"  content="text/html; charset=UTF-8" /> -]]> - </example> - </rule> - - <rule name="NoUnsanitizedJSPExpression" - since="5.1.4" - class="net.sourceforge.pmd.lang.jsp.rule.basic.NoUnsanitizedJSPExpressionRule" - message="Using unsanitized JSP expression can lead to Cross Site Scripting (XSS) attacks" - externalInfoUrl="${pmd.website.baseurl}/pmd_rules_jsp_basic.html#nounsanitizedjspexpression"> - <description> -Avoid using expressions without escaping / sanitizing. This could lead to cross site scripting - as the expression -would be interpreted by the browser directly (e.g. "<script>alert('hello');</script>"). - </description> - <priority>3</priority> - <example> -<![CDATA[ -<%@ page contentType="text/html; charset=UTF-8" %> -<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %> -${expression} <!-- don't use this --> -${fn:escapeXml(expression)} <!-- instead, escape it --> -<c:out value="${expression}" /> <!-- or use c:out --> -]]> - </example> - </rule> + <rule ref="category/jsp/codestyle.xml/DuplicateJspImports" deprecated="true" /> + <rule ref="category/jsp/errorprone.xml/JspEncoding" deprecated="true" /> </ruleset> diff --git a/pmd-jsp/src/main/resources/rulesets/jsp/rulesets.properties b/pmd-jsp/src/main/resources/rulesets/jsp/rulesets.properties index ad1ef2494ba..c8b84831027 100644 --- a/pmd-jsp/src/main/resources/rulesets/jsp/rulesets.properties +++ b/pmd-jsp/src/main/resources/rulesets/jsp/rulesets.properties @@ -3,5 +3,15 @@ # rulesets.filenames=\ - rulesets/jsp/basic.xml,\ - rulesets/jsp/basic-jsf.xml + category/jsp/bestpractices.xml,\ + category/jsp/codestyle.xml,\ + category/jsp/design.xml,\ + category/jsp/errorprone.xml,\ + category/jsp/security.xml + +# +# categories without rules +# +# category/jsp/documentation.xml +# category/jsp/multithreading.xml +# category/jsp/performance.xml diff --git a/pmd-jsp/src/test/java/net/sourceforge/pmd/cpd/JSPTokenizerTest.java b/pmd-jsp/src/test/java/net/sourceforge/pmd/cpd/JSPTokenizerTest.java new file mode 100644 index 00000000000..f16f1487c8f --- /dev/null +++ b/pmd-jsp/src/test/java/net/sourceforge/pmd/cpd/JSPTokenizerTest.java @@ -0,0 +1,53 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.cpd; + +import java.nio.charset.StandardCharsets; + +import org.apache.commons.io.IOUtils; +import org.junit.Assert; +import org.junit.Test; + +import net.sourceforge.pmd.lang.jsp.ast.JspParserConstants; + + +public class JSPTokenizerTest { + + @Test + public void scriptletWithString() throws Exception { + JSPTokenizer tokenizer = new JSPTokenizer(); + Tokens tokenEntries = new Tokens(); + String code = IOUtils.toString(JSPTokenizerTest.class.getResourceAsStream("scriptletWithString.jsp"), + StandardCharsets.UTF_8); + SourceCode sourceCode = new SourceCode(new SourceCode.StringCodeLoader(code)); + tokenizer.tokenize(sourceCode, tokenEntries); + + int[] expectedTokens = new int[] { + JspParserConstants.JSP_COMMENT_START, + JspParserConstants.JSP_COMMENT_CONTENT, + JspParserConstants.JSP_COMMENT_END, + JspParserConstants.JSP_SCRIPTLET_START, + JspParserConstants.JSP_SCRIPTLET, + JspParserConstants.JSP_SCRIPTLET_END, + JspParserConstants.JSP_SCRIPTLET_START, + JspParserConstants.JSP_SCRIPTLET, + JspParserConstants.JSP_SCRIPTLET_END, + JspParserConstants.EOF, + }; + Assert.assertEquals(expectedTokens.length, tokenEntries.getTokens().size()); + for (int i = 0; i < expectedTokens.length - 1; i++) { + Assert.assertEquals(String.valueOf(expectedTokens[i]), tokenEntries.getTokens().get(i).toString()); + } + Assert.assertEquals("<JSP_SCRIPTLET>", getTokenImage(tokenEntries.getTokens().get(4))); + } + + private static String getTokenImage(TokenEntry token) { + int kind = 0; + if (token != TokenEntry.EOF) { + kind = Integer.parseInt(token.toString()); + } + return JspParserConstants.tokenImage[kind]; + } +} 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 b05018c207d..6f4cae6f8b2 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 void testParseDollar() { "<span class=\"CostUnit\">$</span><span class=\"CostMain\">129</span><span class=\"CostFrac\">.00</span>"); Assert.assertNotNull(node); } - + + @Test + public void testParseELAttribute() { + Node node = parse( + "<div ${something ? 'class=\"red\"' : ''}> Div content here.</div>"); + Assert.assertNotNull(node); + } + + @Test + public void testParseELAttributeValue() { + Node node = parse( + "<div class=\"${something == 0 ? 'zero_something' : something == 1 ? 'one_something' : 'other_something'}\">Div content here.</div>"); + Assert.assertNotNull(node); + } + /** * Verifies bug #311 Jsp parser fails on boolean attribute */ @@ -40,6 +58,29 @@ public void testParseBooleanAttribute() { 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-jsp/src/test/java/net/sourceforge/pmd/lang/jsp/rule/basic/BasicRulesTest.java b/pmd-jsp/src/test/java/net/sourceforge/pmd/lang/jsp/rule/basic/BasicRulesTest.java deleted file mode 100644 index 2db83da7a63..00000000000 --- a/pmd-jsp/src/test/java/net/sourceforge/pmd/lang/jsp/rule/basic/BasicRulesTest.java +++ /dev/null @@ -1,27 +0,0 @@ -/** - * BSD-style license; for more info see http://pmd.sourceforge.net/license.html - */ - -package net.sourceforge.pmd.lang.jsp.rule.basic; - -import net.sourceforge.pmd.testframework.SimpleAggregatorTst; - -public class BasicRulesTest extends SimpleAggregatorTst { - - private static final String RULESET = "jsp-basic"; - - @Override - public void setUp() { - addRule(RULESET, "DuplicateJspImports"); - addRule(RULESET, "IframeMissingSrcAttribute"); - addRule(RULESET, "JspEncoding"); - addRule(RULESET, "NoClassAttribute"); - addRule(RULESET, "NoHtmlComments"); - addRule(RULESET, "NoInlineScript"); - addRule(RULESET, "NoInlineStyleInformation"); - addRule(RULESET, "NoJspForward"); - addRule(RULESET, "NoLongScripts"); - addRule(RULESET, "NoScriptlets"); - addRule(RULESET, "NoUnsanitizedJSPExpression"); - } -} diff --git a/pmd-jsp/src/test/java/net/sourceforge/pmd/lang/jsp/rule/basicjsf/BasicJsfRulesTest.java b/pmd-jsp/src/test/java/net/sourceforge/pmd/lang/jsp/rule/basicjsf/BasicJsfRulesTest.java deleted file mode 100644 index 2147a11d43b..00000000000 --- a/pmd-jsp/src/test/java/net/sourceforge/pmd/lang/jsp/rule/basicjsf/BasicJsfRulesTest.java +++ /dev/null @@ -1,17 +0,0 @@ -/** - * BSD-style license; for more info see http://pmd.sourceforge.net/license.html - */ - -package net.sourceforge.pmd.lang.jsp.rule.basicjsf; - -import net.sourceforge.pmd.testframework.SimpleAggregatorTst; - -public class BasicJsfRulesTest extends SimpleAggregatorTst { - - private static final String RULESET = "jsp-basic-jsf"; - - @Override - public void setUp() { - addRule(RULESET, "DontNestJsfInJstlIteration"); - } -} diff --git a/pmd-jsp/src/test/java/net/sourceforge/pmd/lang/jsp/rule/bestpractices/DontNestJsfInJstlIterationTest.java b/pmd-jsp/src/test/java/net/sourceforge/pmd/lang/jsp/rule/bestpractices/DontNestJsfInJstlIterationTest.java new file mode 100644 index 00000000000..7072c0d4348 --- /dev/null +++ b/pmd-jsp/src/test/java/net/sourceforge/pmd/lang/jsp/rule/bestpractices/DontNestJsfInJstlIterationTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.jsp.rule.bestpractices; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class DontNestJsfInJstlIterationTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-jsp/src/test/java/net/sourceforge/pmd/lang/jsp/rule/bestpractices/NoClassAttributeTest.java b/pmd-jsp/src/test/java/net/sourceforge/pmd/lang/jsp/rule/bestpractices/NoClassAttributeTest.java new file mode 100644 index 00000000000..061c8f8ca3d --- /dev/null +++ b/pmd-jsp/src/test/java/net/sourceforge/pmd/lang/jsp/rule/bestpractices/NoClassAttributeTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.jsp.rule.bestpractices; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class NoClassAttributeTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-jsp/src/test/java/net/sourceforge/pmd/lang/jsp/rule/bestpractices/NoHtmlCommentsTest.java b/pmd-jsp/src/test/java/net/sourceforge/pmd/lang/jsp/rule/bestpractices/NoHtmlCommentsTest.java new file mode 100644 index 00000000000..85bed7dc181 --- /dev/null +++ b/pmd-jsp/src/test/java/net/sourceforge/pmd/lang/jsp/rule/bestpractices/NoHtmlCommentsTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.jsp.rule.bestpractices; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class NoHtmlCommentsTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-jsp/src/test/java/net/sourceforge/pmd/lang/jsp/rule/bestpractices/NoJspForwardTest.java b/pmd-jsp/src/test/java/net/sourceforge/pmd/lang/jsp/rule/bestpractices/NoJspForwardTest.java new file mode 100644 index 00000000000..3da268b78a5 --- /dev/null +++ b/pmd-jsp/src/test/java/net/sourceforge/pmd/lang/jsp/rule/bestpractices/NoJspForwardTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.jsp.rule.bestpractices; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class NoJspForwardTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-jsp/src/test/java/net/sourceforge/pmd/lang/jsp/rule/codestyle/DuplicateJspImportsTest.java b/pmd-jsp/src/test/java/net/sourceforge/pmd/lang/jsp/rule/codestyle/DuplicateJspImportsTest.java new file mode 100644 index 00000000000..30f1f0726b0 --- /dev/null +++ b/pmd-jsp/src/test/java/net/sourceforge/pmd/lang/jsp/rule/codestyle/DuplicateJspImportsTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.jsp.rule.codestyle; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class DuplicateJspImportsTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-jsp/src/test/java/net/sourceforge/pmd/lang/jsp/rule/design/NoInlineScriptTest.java b/pmd-jsp/src/test/java/net/sourceforge/pmd/lang/jsp/rule/design/NoInlineScriptTest.java new file mode 100644 index 00000000000..1b63db20b80 --- /dev/null +++ b/pmd-jsp/src/test/java/net/sourceforge/pmd/lang/jsp/rule/design/NoInlineScriptTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.jsp.rule.design; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class NoInlineScriptTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-jsp/src/test/java/net/sourceforge/pmd/lang/jsp/rule/design/NoInlineStyleInformationTest.java b/pmd-jsp/src/test/java/net/sourceforge/pmd/lang/jsp/rule/design/NoInlineStyleInformationTest.java new file mode 100644 index 00000000000..b787098efcd --- /dev/null +++ b/pmd-jsp/src/test/java/net/sourceforge/pmd/lang/jsp/rule/design/NoInlineStyleInformationTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.jsp.rule.design; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class NoInlineStyleInformationTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-jsp/src/test/java/net/sourceforge/pmd/lang/jsp/rule/design/NoLongScriptsTest.java b/pmd-jsp/src/test/java/net/sourceforge/pmd/lang/jsp/rule/design/NoLongScriptsTest.java new file mode 100644 index 00000000000..355cbe28560 --- /dev/null +++ b/pmd-jsp/src/test/java/net/sourceforge/pmd/lang/jsp/rule/design/NoLongScriptsTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.jsp.rule.design; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class NoLongScriptsTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-jsp/src/test/java/net/sourceforge/pmd/lang/jsp/rule/design/NoScriptletsTest.java b/pmd-jsp/src/test/java/net/sourceforge/pmd/lang/jsp/rule/design/NoScriptletsTest.java new file mode 100644 index 00000000000..6913dfefac7 --- /dev/null +++ b/pmd-jsp/src/test/java/net/sourceforge/pmd/lang/jsp/rule/design/NoScriptletsTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.jsp.rule.design; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class NoScriptletsTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-jsp/src/test/java/net/sourceforge/pmd/lang/jsp/rule/errorprone/JspEncodingTest.java b/pmd-jsp/src/test/java/net/sourceforge/pmd/lang/jsp/rule/errorprone/JspEncodingTest.java new file mode 100644 index 00000000000..a6dddfe5538 --- /dev/null +++ b/pmd-jsp/src/test/java/net/sourceforge/pmd/lang/jsp/rule/errorprone/JspEncodingTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.jsp.rule.errorprone; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class JspEncodingTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-jsp/src/test/java/net/sourceforge/pmd/lang/jsp/rule/security/IframeMissingSrcAttributeTest.java b/pmd-jsp/src/test/java/net/sourceforge/pmd/lang/jsp/rule/security/IframeMissingSrcAttributeTest.java new file mode 100644 index 00000000000..959ca562aa0 --- /dev/null +++ b/pmd-jsp/src/test/java/net/sourceforge/pmd/lang/jsp/rule/security/IframeMissingSrcAttributeTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.jsp.rule.security; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class IframeMissingSrcAttributeTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-jsp/src/test/java/net/sourceforge/pmd/lang/jsp/rule/security/NoUnsanitizedJSPExpressionTest.java b/pmd-jsp/src/test/java/net/sourceforge/pmd/lang/jsp/rule/security/NoUnsanitizedJSPExpressionTest.java new file mode 100644 index 00000000000..7d4482c54e6 --- /dev/null +++ b/pmd-jsp/src/test/java/net/sourceforge/pmd/lang/jsp/rule/security/NoUnsanitizedJSPExpressionTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.jsp.rule.security; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class NoUnsanitizedJSPExpressionTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-jsp/src/test/resources/net/sourceforge/pmd/cpd/scriptletWithString.jsp b/pmd-jsp/src/test/resources/net/sourceforge/pmd/cpd/scriptletWithString.jsp new file mode 100644 index 00000000000..38c451ff4ca --- /dev/null +++ b/pmd-jsp/src/test/resources/net/sourceforge/pmd/cpd/scriptletWithString.jsp @@ -0,0 +1,12 @@ +<%-- +BSD-style license; for more info see http://pmd.sourceforge.net/license.html +--%> + +<% +String nodeContent = "<% %>"; +%> +<% +<![cdata[ +String nodeContent = "<% %>"; +]]> +%> diff --git a/pmd-jsp/src/test/resources/net/sourceforge/pmd/lang/jsp/rule/basicjsf/xml/DontNestJsfInJstlIteration.xml b/pmd-jsp/src/test/resources/net/sourceforge/pmd/lang/jsp/rule/bestpractices/xml/DontNestJsfInJstlIteration.xml similarity index 100% rename from pmd-jsp/src/test/resources/net/sourceforge/pmd/lang/jsp/rule/basicjsf/xml/DontNestJsfInJstlIteration.xml rename to pmd-jsp/src/test/resources/net/sourceforge/pmd/lang/jsp/rule/bestpractices/xml/DontNestJsfInJstlIteration.xml diff --git a/pmd-jsp/src/test/resources/net/sourceforge/pmd/lang/jsp/rule/basic/xml/NoClassAttribute.xml b/pmd-jsp/src/test/resources/net/sourceforge/pmd/lang/jsp/rule/bestpractices/xml/NoClassAttribute.xml similarity index 100% rename from pmd-jsp/src/test/resources/net/sourceforge/pmd/lang/jsp/rule/basic/xml/NoClassAttribute.xml rename to pmd-jsp/src/test/resources/net/sourceforge/pmd/lang/jsp/rule/bestpractices/xml/NoClassAttribute.xml diff --git a/pmd-jsp/src/test/resources/net/sourceforge/pmd/lang/jsp/rule/basic/xml/NoHtmlComments.xml b/pmd-jsp/src/test/resources/net/sourceforge/pmd/lang/jsp/rule/bestpractices/xml/NoHtmlComments.xml similarity index 100% rename from pmd-jsp/src/test/resources/net/sourceforge/pmd/lang/jsp/rule/basic/xml/NoHtmlComments.xml rename to pmd-jsp/src/test/resources/net/sourceforge/pmd/lang/jsp/rule/bestpractices/xml/NoHtmlComments.xml diff --git a/pmd-jsp/src/test/resources/net/sourceforge/pmd/lang/jsp/rule/basic/xml/NoJspForward.xml b/pmd-jsp/src/test/resources/net/sourceforge/pmd/lang/jsp/rule/bestpractices/xml/NoJspForward.xml similarity index 100% rename from pmd-jsp/src/test/resources/net/sourceforge/pmd/lang/jsp/rule/basic/xml/NoJspForward.xml rename to pmd-jsp/src/test/resources/net/sourceforge/pmd/lang/jsp/rule/bestpractices/xml/NoJspForward.xml diff --git a/pmd-jsp/src/test/resources/net/sourceforge/pmd/lang/jsp/rule/basic/xml/DuplicateJspImports.xml b/pmd-jsp/src/test/resources/net/sourceforge/pmd/lang/jsp/rule/codestyle/xml/DuplicateJspImports.xml similarity index 100% rename from pmd-jsp/src/test/resources/net/sourceforge/pmd/lang/jsp/rule/basic/xml/DuplicateJspImports.xml rename to pmd-jsp/src/test/resources/net/sourceforge/pmd/lang/jsp/rule/codestyle/xml/DuplicateJspImports.xml diff --git a/pmd-jsp/src/test/resources/net/sourceforge/pmd/lang/jsp/rule/basic/xml/NoInlineScript.xml b/pmd-jsp/src/test/resources/net/sourceforge/pmd/lang/jsp/rule/design/xml/NoInlineScript.xml similarity index 100% rename from pmd-jsp/src/test/resources/net/sourceforge/pmd/lang/jsp/rule/basic/xml/NoInlineScript.xml rename to pmd-jsp/src/test/resources/net/sourceforge/pmd/lang/jsp/rule/design/xml/NoInlineScript.xml diff --git a/pmd-jsp/src/test/resources/net/sourceforge/pmd/lang/jsp/rule/basic/xml/NoInlineStyleInformation.xml b/pmd-jsp/src/test/resources/net/sourceforge/pmd/lang/jsp/rule/design/xml/NoInlineStyleInformation.xml similarity index 100% rename from pmd-jsp/src/test/resources/net/sourceforge/pmd/lang/jsp/rule/basic/xml/NoInlineStyleInformation.xml rename to pmd-jsp/src/test/resources/net/sourceforge/pmd/lang/jsp/rule/design/xml/NoInlineStyleInformation.xml diff --git a/pmd-jsp/src/test/resources/net/sourceforge/pmd/lang/jsp/rule/basic/xml/NoLongScripts.xml b/pmd-jsp/src/test/resources/net/sourceforge/pmd/lang/jsp/rule/design/xml/NoLongScripts.xml similarity index 100% rename from pmd-jsp/src/test/resources/net/sourceforge/pmd/lang/jsp/rule/basic/xml/NoLongScripts.xml rename to pmd-jsp/src/test/resources/net/sourceforge/pmd/lang/jsp/rule/design/xml/NoLongScripts.xml diff --git a/pmd-jsp/src/test/resources/net/sourceforge/pmd/lang/jsp/rule/basic/xml/NoScriptlets.xml b/pmd-jsp/src/test/resources/net/sourceforge/pmd/lang/jsp/rule/design/xml/NoScriptlets.xml similarity index 100% rename from pmd-jsp/src/test/resources/net/sourceforge/pmd/lang/jsp/rule/basic/xml/NoScriptlets.xml rename to pmd-jsp/src/test/resources/net/sourceforge/pmd/lang/jsp/rule/design/xml/NoScriptlets.xml diff --git a/pmd-jsp/src/test/resources/net/sourceforge/pmd/lang/jsp/rule/basic/xml/JspEncoding.xml b/pmd-jsp/src/test/resources/net/sourceforge/pmd/lang/jsp/rule/errorprone/xml/JspEncoding.xml similarity index 100% rename from pmd-jsp/src/test/resources/net/sourceforge/pmd/lang/jsp/rule/basic/xml/JspEncoding.xml rename to pmd-jsp/src/test/resources/net/sourceforge/pmd/lang/jsp/rule/errorprone/xml/JspEncoding.xml diff --git a/pmd-jsp/src/test/resources/net/sourceforge/pmd/lang/jsp/rule/basic/xml/IframeMissingSrcAttribute.xml b/pmd-jsp/src/test/resources/net/sourceforge/pmd/lang/jsp/rule/security/xml/IframeMissingSrcAttribute.xml similarity index 100% rename from pmd-jsp/src/test/resources/net/sourceforge/pmd/lang/jsp/rule/basic/xml/IframeMissingSrcAttribute.xml rename to pmd-jsp/src/test/resources/net/sourceforge/pmd/lang/jsp/rule/security/xml/IframeMissingSrcAttribute.xml diff --git a/pmd-jsp/src/test/resources/net/sourceforge/pmd/lang/jsp/rule/basic/xml/NoUnsanitizedJSPExpression.xml b/pmd-jsp/src/test/resources/net/sourceforge/pmd/lang/jsp/rule/security/xml/NoUnsanitizedJSPExpression.xml similarity index 100% rename from pmd-jsp/src/test/resources/net/sourceforge/pmd/lang/jsp/rule/basic/xml/NoUnsanitizedJSPExpression.xml rename to pmd-jsp/src/test/resources/net/sourceforge/pmd/lang/jsp/rule/security/xml/NoUnsanitizedJSPExpression.xml diff --git a/pmd-lang-test/pom.xml b/pmd-lang-test/pom.xml new file mode 100644 index 00000000000..5c279334176 --- /dev/null +++ b/pmd-lang-test/pom.xml @@ -0,0 +1,94 @@ +<?xml version="1.0" encoding="UTF-8"?> + +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + <artifactId>pmd-lang-test</artifactId> + <name>PMD language module testing utilities</name> + <description> + Module containing utilities to test language implementations, + including parsers and ASTs. This module uses Kotlin. + </description> + + <parent> + <groupId>net.sourceforge.pmd</groupId> + <artifactId>pmd</artifactId> + <version>6.10.0-SNAPSHOT</version> + </parent> + + <build> + <plugins> + <!-- The kotlin plugin has to run before the java plugin--> + <plugin> + <artifactId>kotlin-maven-plugin</artifactId> + <groupId>org.jetbrains.kotlin</groupId> + <version>${kotlin.version}</version> + <executions> + <execution> + <id>kotlin-compile</id> + <goals> + <goal>compile</goal> + </goals> + <phase>process-sources</phase> + <configuration> + <sourceDirs> + <sourceDir>${project.basedir}/src/main/kotlin</sourceDir> + <sourceDir>${project.basedir}/src/main/java</sourceDir> + </sourceDirs> + </configuration> + </execution> + </executions> + </plugin> + </plugins> + </build> + + + <dependencies> + + <dependency> + <groupId>junit</groupId> + <artifactId>junit</artifactId> + </dependency> + + <dependency> + <groupId>net.sourceforge.pmd</groupId> + <artifactId>pmd-core</artifactId> + </dependency> + + <dependency> + <groupId>org.jetbrains.kotlin</groupId> + <artifactId>kotlin-stdlib</artifactId> + <version>${kotlin.version}</version> + <scope>compile</scope> + </dependency> + + <dependency> + <groupId>org.jetbrains.kotlin</groupId> + <artifactId>kotlin-stdlib-jdk8</artifactId> + <version>${kotlin.version}</version> + <scope>compile</scope> + </dependency> + + <dependency> + <groupId>org.jetbrains.kotlin</groupId> + <artifactId>kotlin-test-junit</artifactId> + <version>${kotlin.version}</version> + <scope>compile</scope> + </dependency> + + <dependency> + <groupId>io.kotlintest</groupId> + <artifactId>kotlintest-runner-junit5</artifactId> + <version>3.1.8</version> + <scope>compile</scope> + </dependency> + + <!-- Use pmd-java for tests --> + <dependency> + <groupId>net.sourceforge.pmd</groupId> + <artifactId>pmd-java</artifactId> + <version>6.9.0</version> + <scope>test</scope> + </dependency> + </dependencies> +</project> diff --git a/pmd-lang-test/src/main/kotlin/net/sourceforge/pmd/lang/ast/test/AstMatcherDsl.kt b/pmd-lang-test/src/main/kotlin/net/sourceforge/pmd/lang/ast/test/AstMatcherDsl.kt new file mode 100644 index 00000000000..09080d87d25 --- /dev/null +++ b/pmd-lang-test/src/main/kotlin/net/sourceforge/pmd/lang/ast/test/AstMatcherDsl.kt @@ -0,0 +1,322 @@ +package net.sourceforge.pmd.lang.ast.test + +import arrow.core.Either +import io.kotlintest.Matcher +import io.kotlintest.Result +import net.sourceforge.pmd.lang.ast.Node +import kotlin.test.assertFalse +import kotlin.test.assertTrue + + +/** + * Wraps a node, providing easy access to [it]. Additional matching + * methods are provided to match children. + * + * @param matcherPath List of types of the parents of this node, used to reconstruct a path for error messages + * @param childMatchersAreIgnored Ignore calls to [child] + * + * @property it Wrapped node + * @param <N> Type of the node + */ +class NWrapper<N : Node> private constructor(val it: N, + private val matcherPath: List<Class<out Node>>, + private val childMatchersAreIgnored: Boolean) { + + /** Index to which the next child matcher will apply. */ + private var nextChildMatcherIdx = 0 + + private fun shiftChild(num: Int = 1): Node { + + checkChildExists(nextChildMatcherIdx) + + val ret = it.jjtGetChild(nextChildMatcherIdx) + + nextChildMatcherIdx += num + return ret + } + + + private fun checkChildExists(childIdx: Int) = + assertTrue(formatErrorMessage(matcherPath, "Node has fewer children than expected, child #$childIdx doesn't exist")) { + childIdx < it.numChildren + } + + + /** + * Specify that the next [num] children will only be tested for existence, + * but not for type, or anything else. + */ + fun unspecifiedChildren(num: Int) { + shiftChild(num) + // Checks that the last child mentioned exists + checkChildExists(nextChildMatcherIdx - 1) + } + + + /** + * Specify that the next child will only be tested for existence, + * but not for type, or anything else. + */ + fun unspecifiedChild() = unspecifiedChildren(1) + + + /** + * Specify that the next child will be tested against the assertions + * defined by the lambda. + * + * This method asserts that the child exists, and that it is of the + * required type [M]. The lambda is then executed on it. Subsequent + * calls to this method at the same tree level will test the next + * children. + * + * @param ignoreChildren If true, the number of children of the child is not asserted. + * Calls to [child] in the [nodeSpec] throw an exception. + * @param nodeSpec Sequence of assertions to carry out on the child node + * + * @param M Expected type of the child + * + * @throws AssertionError If the child is not of type [M], or fails the assertions of the [nodeSpec] + * @return The child, if it passes all assertions, otherwise throws an exception + */ + inline fun <reified M : Node> child(ignoreChildren: Boolean = false, noinline nodeSpec: NWrapper<M>.() -> Unit): M = + childImpl(ignoreChildren, M::class.java) { nodeSpec(); it } + + /** + * Specify that the next child will be tested against the assertions + * defined by the lambda, and returns the return value of the lambda. + * + * This method asserts that the child exists, and that it is of the + * required type [M]. The lambda is then executed on it. Subsequent + * calls to this method at the same tree level will test the next + * children. + * + * @param ignoreChildren If true, the number of children of the child is not asserted. + * Calls to [child] in the [nodeSpec] throw an exception. + * @param nodeSpec Sequence of assertions to carry out on the child node + * + * @param M Expected type of the child + * @param R Return type of the call + * + * @throws AssertionError If the child is not of type [M], or fails the assertions of the [nodeSpec] + * @return The return value of the lambda + */ + inline fun <reified M : Node, R> childRet(ignoreChildren: Boolean = false, noinline nodeSpec: NWrapper<M>.() -> R): R = + childImpl(ignoreChildren, M::class.java, nodeSpec) + + + @PublishedApi + internal fun <M : Node, R> childImpl(ignoreChildren: Boolean, childType: Class<M>, nodeSpec: NWrapper<M>.() -> R): R { + if (!childMatchersAreIgnored) + return executeWrapper(childType, shiftChild(), matcherPath, ignoreChildren, nodeSpec) + else + throw IllegalStateException(formatErrorMessage(matcherPath, "Calling child when ignoreChildren=true is forbidden")) + } + + + override fun toString(): String { + return "NWrapper<${it.xPathNodeName}>" + } + + + companion object { + + private val <M : Node> Class<M>.nodeName + get() = + if (simpleName.startsWith("AST", ignoreCase = false)) + simpleName.substring("AST".length) + else simpleName + + private fun formatPath(matcherPath: List<Class<out Node>>) = + when { + matcherPath.isEmpty() -> "<root>" + else -> matcherPath.joinToString(separator = "/", prefix = "/") { it.nodeName } + } + + private fun formatErrorMessage(matcherPath: List<Class<out Node>>, message: String) = + "At ${formatPath(matcherPath)}: $message" + + /** + * Execute wrapper assertions on a node. + * + * @param childType Expected type of [toWrap] + * @param toWrap Node on which to execute the assertions + * @param matcherPath List of types of the parents of this node, used to reconstruct a path for error messages + * @param ignoreChildrenMatchers Ignore the children matchers in [spec] + * @param spec Assertions to carry out on [toWrap] + * + * @param M Expected type of [toWrap] + * @param R Return type + * + * @throws AssertionError If some assertions fail + * @return [toWrap], if it passes all assertions, otherwise throws an exception + */ + @PublishedApi + internal fun <M : Node, R> executeWrapper(childType: Class<M>, + toWrap: Node, + matcherPath: List<Class<out Node>>, + ignoreChildrenMatchers: Boolean, + spec: NWrapper<M>.() -> R): R { + + val nodeNameForMsg = when { + matcherPath.isEmpty() -> "node" + else -> "child #${toWrap.jjtGetChildIndex()}" + } + + assertTrue(formatErrorMessage(matcherPath, "Expected $nodeNameForMsg to have type ${childType.nodeName}, actual ${toWrap.javaClass.nodeName}")) { + childType.isInstance(toWrap) + } + + val childPath = matcherPath + childType + @Suppress("UNCHECKED_CAST") + val m = toWrap as M + + val wrapper = NWrapper(m, childPath, ignoreChildrenMatchers) + + val ret: R = try { + wrapper.spec() + } catch (e: AssertionError) { + if (e.message?.matches("At (/.*?|<root>):.*".toRegex()) == false) { + // the exception has no path, let's add one + throw AssertionError(formatErrorMessage(childPath, e.message ?: "No explanation provided"), e) + } + throw e + } + + assertFalse(formatErrorMessage(childPath, "Wrong number of children, expected ${wrapper.nextChildMatcherIdx}, actual ${wrapper.it.numChildren}")) { + !ignoreChildrenMatchers && wrapper.nextChildMatcherIdx != wrapper.it.numChildren + } + return ret + } + } +} + + +/** + * Matcher for a node, using [NWrapper] to specify a subtree against which + * the tested node will be tested. + * + * Use it with [io.kotlintest.should], e.g. `node should matchNode<ASTExpression> {}`. + * + * @param N Expected type of the node + * + * @param ignoreChildren If true, calls to [NWrapper.child] in the [nodeSpec] are forbidden. + * The number of children of the child is not asserted. + * + * @param nodeSpec Sequence of assertions to carry out on the node, which can be referred to by [NWrapper.it]. + * Assertions may consist of [NWrapper.child] calls, which perform the same type of node + * matching on a child of the tested node. + * + * @return A matcher for AST nodes, suitable for use by [io.kotlintest.should]. + * + * ### Samples + * + * node should matchNode<ASTStatement> { + * + * // nesting matchers allow to specify a whole subtree + * child<ASTForStatement> { + * + * // This would fail if the first child of the ForStatement wasn't a ForInit + * child<ASTForInit> { + * child<ASTLocalVariableDeclaration> { + * + * // If the parameter ignoreChildren is set to true, the number of children is not asserted + * // Calls to "child" in the block are forbidden + * // The only checks carried out here are the type test and the assertions of the block + * child<ASTType>(ignoreChildren = true) { + * + * // In a "child" block, the tested node can be referred to as "it" + * // Here, its static type is ASTType, so we can inspect properties + * // of the node and make assertions + * + * it.typeImage shouldBe "int" + * it.type shouldNotBe null + * } + * + * // We don't care about that node, we only care that there is "some" node + * unspecifiedChild() + * } + * } + * + * // The subtree is ignored, but we check a ForUpdate is present at this child position + * child<ASTForUpdate>(ignoreChildren = true) {} + * + * // Here, ignoreChildren is not specified and takes its default value of false. + * // The lambda has no "child" calls and the node will be asserted to have no children + * child<ASTBlock> {} + * } + * } + * + * // To get good error messages, it's important to define assertions + * // on the node that is supposed to verify them, so if it needs some + * // value from its children, you can go fetch that value in two ways: + * // * if you just need the child node, the child method already returns that + * // * if you need some more complex value, or to return some subchild, use childRet + * + * catchStmt should matchStmt<ASTCatchStatement> { + * it.isMulticatchStatement shouldBe true + * + * // The childRet method is a variant of child which can return anything. + * // Specify the return type as a type parameter + * val types = childRet<ASTFormalParameter, List<ASTType>> { + * + * // The child method returns the child (strongly typed) + * val ioe = child<ASTType>(ignoreChildren = true) { + * it.type shouldBe IOException::class.java + * } + * + * val aerr = child<ASTType>(ignoreChildren = true) { + * it.type shouldBe java.lang.AssertionError::class.java + * } + * + * unspecifiedChild() + * + * // You have to use the annotated return type syntax + * return@childRet listOf(ioe, aerr) + * } + * + * // Here you can use the returned value to perform more assertions* + * + * it.caughtExceptionTypeNodes.shouldContainExactly(types) + * it.caughtExceptionTypes.shouldContainExactly(types.map { it.type }) + * + * it.exceptionName shouldBe "e" + * + * child<ASTBlock> { } + * } + */ +inline fun <reified N : Node> matchNode(ignoreChildren: Boolean = false, noinline nodeSpec: NWrapper<N>.() -> Unit) = object : Matcher<Node?> { + override fun test(value: Node?): Result { + if (value == null) { + return Result(false, "Expecting the node not to be null", "") + } + + val matchRes = try { + Either.Right(NWrapper.executeWrapper(N::class.java, value, emptyList(), ignoreChildren, nodeSpec)) + } catch (e: AssertionError) { + Either.Left(e) + } + + val didMatch = matchRes.isRight() + + + // Output when the node should have matched and did not + // + val failureMessage: String = matchRes.fold({ + // Here the node failed + it.message ?: "The node did not match the pattern (no cause specified)" + }, { + // The node matched, which was expected + "SHOULD NOT BE OUTPUT" + }) + + val negatedMessage = matchRes.fold({ + // the node didn't match, which was expected + "SHOULD NOT BE OUTPUT" + }, { + "The node should not have matched this pattern" + }) + + + return Result(didMatch, failureMessage, negatedMessage) + } +} \ No newline at end of file diff --git a/pmd-lang-test/src/main/kotlin/net/sourceforge/pmd/lang/ast/test/NodeExtensions.kt b/pmd-lang-test/src/main/kotlin/net/sourceforge/pmd/lang/ast/test/NodeExtensions.kt new file mode 100644 index 00000000000..7dccd387900 --- /dev/null +++ b/pmd-lang-test/src/main/kotlin/net/sourceforge/pmd/lang/ast/test/NodeExtensions.kt @@ -0,0 +1,27 @@ +package net.sourceforge.pmd.lang.ast.test + +import net.sourceforge.pmd.lang.ast.Node + + +/** Extension methods to make the Node API more Kotlin-like */ + +// kotlin converts getters of java types into property accessors +// but it doesn't recognise jjtGet* methods as getters + +val Node.numChildren: Int + get() = this.jjtGetNumChildren() + +val Node.childIndex: Int + get() = this.jjtGetChildIndex() + +val Node.parent: Node? + get() = this.jjtGetParent() + + +fun Node.getChild(i: Int) = jjtGetChild(i) + +fun Node.safeGetChild(i: Int): Node? = when { + i < numChildren -> jjtGetChild(i) + else -> null +} + diff --git a/pmd-lang-test/src/test/kotlin/net/sourceforge/pmd/lang/ast/test/DslTest.kt b/pmd-lang-test/src/test/kotlin/net/sourceforge/pmd/lang/ast/test/DslTest.kt new file mode 100644 index 00000000000..e6d9fe245d9 --- /dev/null +++ b/pmd-lang-test/src/test/kotlin/net/sourceforge/pmd/lang/ast/test/DslTest.kt @@ -0,0 +1,138 @@ +package net.sourceforge.pmd.lang.ast.test + +import io.kotlintest.should +import io.kotlintest.shouldBe +import io.kotlintest.specs.FunSpec +import net.sourceforge.pmd.lang.java.ast.* + + +class DslTest : FunSpec({ + + failureTest("Empty matcher spec should check the number of children", + messageContains = setOf("Wrong", "number", "children", "expected 0", "actual 2")) { + + parseStatement("int i = 0;") should matchNode<ASTLocalVariableDeclaration> {} + } + + test("Matcher with ignoreChildren should not check the number of children") { + + parseStatement("int i = 0;") should matchNode<ASTLocalVariableDeclaration>(ignoreChildren = true) {} + } + + failureTest("Incorrect node type should cause failure", + messageContains = setOf("Expression", "actual LocalVariableDeclaration")) { + parseStatement("int i = 0;") should matchNode<ASTExpression>(ignoreChildren = true) {} + } + + failureTest("Specifying any child in a pattern should cause the number of children to be checked", + messageContains = setOf("number", "children", "expected 1", "actual 2")) { + + parseStatement("int i = 0;") should matchNode<ASTLocalVariableDeclaration> { + child<ASTType>(ignoreChildren = true) {} + // There's a VarDeclarator + } + } + + + test("Unspecified children should shift the next child matchers") { + parseStatement("int i = 0;") should matchNode<ASTLocalVariableDeclaration> { + unspecifiedChild() + child<ASTVariableDeclarator>(ignoreChildren = true) {} + } + } + + test("Unspecified children should count in total number of children") { + parseStatement("int i = 0;") should matchNode<ASTLocalVariableDeclaration> { + unspecifiedChildren(2) + } + } + + failureTest("Unspecified children should be counted in the number of expected children", + messageContains = setOf("#2 doesn't exist")) { + + parseStatement("int i = 0;") should matchNode<ASTLocalVariableDeclaration> { + unspecifiedChildren(3) + } + } + + failureTest("Assertions are always executed in order", + messageContains = setOf("PrimitiveType")) { + + parseStatement("int[] i = 0;") should matchNode<ASTLocalVariableDeclaration> { + + child<ASTType> { + + // Here we check that the child type check fails before the assertion + child<ASTPrimitiveType> {} + + it.typeImage shouldBe "bratwurst" + + } + + unspecifiedChild() + } + } + + failureTest("Assertions are always executed in order #2", + messageContains = setOf("bratwurst")) { + + parseStatement("int[] i = 0;") should matchNode<ASTLocalVariableDeclaration> { + + child<ASTType> { + + it.typeImage shouldBe "bratwurst" + + child<ASTPrimitiveType> {} + + } + + unspecifiedChild() + } + } + + failureTest("All assertions should have a node path", + messageContains = setOf("At /LocalVariableDeclaration/Type:", "expected: \"bratwurst\"")) { + + parseStatement("int[] i = 0;") should matchNode<ASTLocalVariableDeclaration> { + + child<ASTType> { + + // this fails + it.typeImage shouldBe "bratwurst" + + } + + unspecifiedChild() + } + } + + failureTest("Child assertions should have a node path", + messageContains = setOf("At /LocalVariableDeclaration/Type:", "expected", "type", "LambdaExpression")) { + + parseStatement("int[] i = 0;") should matchNode<ASTLocalVariableDeclaration> { + + child<ASTType> { + + // this fails + child<ASTLambdaExpression> { } + } + + unspecifiedChild() + } + } + + failureTest("Leaf nodes should assert that they have no children", + messageContains = setOf("number", "children", "expected 0")) { + + parseStatement("int[] i = 0;") should matchNode<ASTLocalVariableDeclaration> { + + child<ASTType> {} // This should fail + unspecifiedChild() + } + } + + +}) + + + diff --git a/pmd-lang-test/src/test/kotlin/net/sourceforge/pmd/lang/ast/test/ParseUtils.kt b/pmd-lang-test/src/test/kotlin/net/sourceforge/pmd/lang/ast/test/ParseUtils.kt new file mode 100644 index 00000000000..d11554543ac --- /dev/null +++ b/pmd-lang-test/src/test/kotlin/net/sourceforge/pmd/lang/ast/test/ParseUtils.kt @@ -0,0 +1,41 @@ +package net.sourceforge.pmd.lang.ast.test + +import net.sourceforge.pmd.lang.LanguageRegistry +import net.sourceforge.pmd.lang.ast.Node +import net.sourceforge.pmd.lang.java.JavaLanguageModule +import net.sourceforge.pmd.lang.java.ast.ASTBlockStatement +import net.sourceforge.pmd.lang.java.ast.ASTCompilationUnit +import java.io.StringReader + + +// These could be used directly by the pmd-java test module + +fun parseStatement(statement: String): Node { + + // place the param in a statement parsing context + val source = """ + class Foo { + { + $statement + } + } + """.trimIndent() + + val root = parseCompilationUnit(source) + + return root.getFirstDescendantOfType(ASTBlockStatement::class.java).jjtGetChild(0) +} + +fun parseCompilationUnit(sourceCode: String): ASTCompilationUnit { + + val languageVersionHandler = LanguageRegistry.getLanguage(JavaLanguageModule.NAME).defaultVersion.languageVersionHandler + val rootNode = languageVersionHandler.getParser(languageVersionHandler.defaultParserOptions).parse(":test:", StringReader(sourceCode)) + languageVersionHandler.getQualifiedNameResolutionFacade(ClassLoader.getSystemClassLoader()).start(rootNode) + languageVersionHandler.symbolFacade.start(rootNode) + languageVersionHandler.dataFlowFacade.start(rootNode) + languageVersionHandler.getTypeResolutionFacade(ClassLoader.getSystemClassLoader()).start(rootNode) + languageVersionHandler.multifileFacade.start(rootNode) + return rootNode as ASTCompilationUnit + +} + diff --git a/pmd-lang-test/src/test/kotlin/net/sourceforge/pmd/lang/ast/test/TestUtils.kt b/pmd-lang-test/src/test/kotlin/net/sourceforge/pmd/lang/ast/test/TestUtils.kt new file mode 100644 index 00000000000..f821080efa6 --- /dev/null +++ b/pmd-lang-test/src/test/kotlin/net/sourceforge/pmd/lang/ast/test/TestUtils.kt @@ -0,0 +1,30 @@ +package net.sourceforge.pmd.lang.ast.test + +import io.kotlintest.matchers.string.shouldContainIgnoringCase +import io.kotlintest.shouldThrow +import io.kotlintest.specs.AbstractFunSpec + + +// Improve on the KotlinTest DSL for our specific needs +// a testing DSL testing a testing DSL! + + +fun AbstractFunSpec.failureTest(testName: String, + messageContains: Set<String> = emptySet(), + param: io.kotlintest.TestContext.() -> kotlin.Unit) { + + this.expectFailure<AssertionError>(testName, messageContains, param) +} + +inline fun <reified T : Throwable> AbstractFunSpec.expectFailure(testName: String, + messageContains: Set<String> = emptySet(), + noinline param: io.kotlintest.TestContext.() -> kotlin.Unit) { + test(testName) { + val exception = shouldThrow<T> { + this.param() // this is the test context here + } + + for (substr in messageContains) exception.message.shouldContainIgnoringCase(substr) + + } +} diff --git a/pmd-matlab/etc/grammar/matlab.jj b/pmd-matlab/etc/grammar/matlab.jj index 35dbc8625d3..6f61dd0d42a 100644 --- a/pmd-matlab/etc/grammar/matlab.jj +++ b/pmd-matlab/etc/grammar/matlab.jj @@ -39,27 +39,19 @@ PARSER_END(MatlabParser) "\r\n" : DEFAULT | "\n" : DEFAULT -| - "%{" : IN_COMMENT -| - "%" : IN_LINE_COMMENT - } -<IN_COMMENT> SKIP: -{ - "%}" : DEFAULT -} +MORE: +{ "%{": IN_COMMENT } -<IN_LINE_COMMENT> SKIP: -{ - "\n" : DEFAULT -} +SPECIAL_TOKEN: +{ <SINGLE_LINE_COMMENT: "%"(~["\n","\r"])* ("\n"|"\r"|"\r\n")?> } -<IN_LINE_COMMENT, IN_COMMENT> MORE: -{ - < ~[] > -} +<IN_COMMENT> SPECIAL_TOKEN: +{ <MULTI_LINE_COMMENT: "%}">: DEFAULT } + +<IN_COMMENT> MORE: +{ < ~[] > } <DEFAULT, TRANSPOSE> TOKEN : /* SEPARATORS AND OTHER USEFULL LANGUAGE CONSTRUCTS*/ { diff --git a/pmd-matlab/pom.xml b/pmd-matlab/pom.xml index 48c697fa12f..a678e020350 100644 --- a/pmd-matlab/pom.xml +++ b/pmd-matlab/pom.xml @@ -7,13 +7,9 @@ <parent> <groupId>net.sourceforge.pmd</groupId> <artifactId>pmd</artifactId> - <version>6.0.0-SNAPSHOT</version> + <version>6.10.0-SNAPSHOT</version> </parent> - <properties> - <config.basedir>${basedir}/../pmd-core</config.basedir> - </properties> - <build> <plugins> <plugin> @@ -73,6 +69,10 @@ <groupId>net.sourceforge.pmd</groupId> <artifactId>pmd-core</artifactId> </dependency> + <dependency> + <groupId>commons-io</groupId> + <artifactId>commons-io</artifactId> + </dependency> <dependency> <groupId>junit</groupId> diff --git a/pmd-matlab/src/main/ant/alljavacc.xml b/pmd-matlab/src/main/ant/alljavacc.xml index e17d98fb20b..ecf431f7b6b 100644 --- a/pmd-matlab/src/main/ant/alljavacc.xml +++ b/pmd-matlab/src/main/ant/alljavacc.xml @@ -40,6 +40,56 @@ <delete file="${target}/net/sourceforge/pmd/lang/matlab/ast/CharStream.java" /> <delete file="${target}/net/sourceforge/pmd/lang/matlab/ast/ParseException.java" /> <delete file="${target}/net/sourceforge/pmd/lang/matlab/ast/TokenMgrError.java" /> + + <replace file="${target}/net/sourceforge/pmd/lang/matlab/ast/Token.java"> + <replacetoken>public class Token implements java.io.Serializable</replacetoken> + <replacevalue><![CDATA[import net.sourceforge.pmd.lang.ast.GenericToken; + +public class Token implements GenericToken, java.io.Serializable]]></replacevalue> + </replace> + + <!--Add implementation methods of GenericToken--> + <replace file="${target}/net/sourceforge/pmd/lang/matlab/ast/Token.java"> + <replacetoken>public Token specialToken;</replacetoken> + <replacevalue><![CDATA[public Token specialToken; + + @Override + public GenericToken getNext() { + return next; + } + + @Override + public GenericToken getPreviousComment() { + return specialToken; + } + + @Override + public String getImage() { + return image; + } + + @Override + public int getBeginLine() { + return beginLine; + } + + @Override + public int getEndLine() { + return endLine; + } + + @Override + public int getBeginColumn() { + return beginColumn; + } + + @Override + public int getEndColumn() { + return endColumn; + } + +]]></replacevalue> + </replace> </target> </project> diff --git a/pmd-matlab/src/main/java/net/sourceforge/pmd/cpd/MatlabTokenizer.java b/pmd-matlab/src/main/java/net/sourceforge/pmd/cpd/MatlabTokenizer.java index b2157bd0431..818c2ff282e 100644 --- a/pmd-matlab/src/main/java/net/sourceforge/pmd/cpd/MatlabTokenizer.java +++ b/pmd-matlab/src/main/java/net/sourceforge/pmd/cpd/MatlabTokenizer.java @@ -4,14 +4,14 @@ package net.sourceforge.pmd.cpd; +import java.io.IOException; import java.io.Reader; import java.io.StringReader; -import org.apache.commons.io.IOUtils; - +import net.sourceforge.pmd.cpd.token.JavaCCTokenFilter; +import net.sourceforge.pmd.cpd.token.TokenFilter; import net.sourceforge.pmd.lang.LanguageRegistry; import net.sourceforge.pmd.lang.LanguageVersionHandler; -import net.sourceforge.pmd.lang.TokenManager; import net.sourceforge.pmd.lang.ast.TokenMgrError; import net.sourceforge.pmd.lang.matlab.MatlabLanguageModule; import net.sourceforge.pmd.lang.matlab.ast.Token; @@ -25,28 +25,24 @@ public class MatlabTokenizer implements Tokenizer { @Override public void tokenize(SourceCode sourceCode, Tokens tokenEntries) { StringBuilder buffer = sourceCode.getCodeBuffer(); - Reader reader = null; - try { + try (Reader reader = IOUtil.skipBOM(new StringReader(buffer.toString()))) { LanguageVersionHandler languageVersionHandler = LanguageRegistry.getLanguage(MatlabLanguageModule.NAME) .getDefaultVersion().getLanguageVersionHandler(); - reader = new StringReader(buffer.toString()); - reader = IOUtil.skipBOM(reader); - TokenManager tokenManager = languageVersionHandler + + final TokenFilter tokenFilter = new JavaCCTokenFilter(languageVersionHandler .getParser(languageVersionHandler.getDefaultParserOptions()) - .getTokenManager(sourceCode.getFileName(), reader); - Token currentToken = (Token) tokenManager.getNextToken(); - while (currentToken.image.length() > 0) { + .getTokenManager(sourceCode.getFileName(), reader)); + Token currentToken = (Token) tokenFilter.getNextToken(); + while (currentToken != null) { tokenEntries.add(new TokenEntry(currentToken.image, sourceCode.getFileName(), currentToken.beginLine)); - currentToken = (Token) tokenManager.getNextToken(); + currentToken = (Token) tokenFilter.getNextToken(); } tokenEntries.add(TokenEntry.getEOF()); System.err.println("Added " + sourceCode.getFileName()); - } catch (TokenMgrError err) { + } catch (TokenMgrError | IOException err) { err.printStackTrace(); System.err.println("Skipping " + sourceCode.getFileName() + " due to parse error"); tokenEntries.add(TokenEntry.getEOF()); - } finally { - IOUtils.closeQuietly(reader); } } } diff --git a/pmd-matlab/src/main/java/net/sourceforge/pmd/lang/matlab/MatlabTokenManager.java b/pmd-matlab/src/main/java/net/sourceforge/pmd/lang/matlab/MatlabTokenManager.java index c090519e001..5b620f76645 100644 --- a/pmd-matlab/src/main/java/net/sourceforge/pmd/lang/matlab/MatlabTokenManager.java +++ b/pmd-matlab/src/main/java/net/sourceforge/pmd/lang/matlab/MatlabTokenManager.java @@ -18,7 +18,7 @@ public class MatlabTokenManager implements TokenManager { /** * Creates a new Matlab Token Manager from the given source code. - * + * * @param source * the source code */ @@ -26,6 +26,7 @@ public MatlabTokenManager(Reader source) { tokenManager = new MatlabParserTokenManager(new SimpleCharStream(source)); } + @Override public Object getNextToken() { return tokenManager.getNextToken(); } diff --git a/pmd-matlab/src/test/java/net/sourceforge/pmd/cpd/MatlabTokenizerTest.java b/pmd-matlab/src/test/java/net/sourceforge/pmd/cpd/MatlabTokenizerTest.java index e2ce74f0643..50fdb1832e9 100644 --- a/pmd-matlab/src/test/java/net/sourceforge/pmd/cpd/MatlabTokenizerTest.java +++ b/pmd-matlab/src/test/java/net/sourceforge/pmd/cpd/MatlabTokenizerTest.java @@ -4,12 +4,16 @@ package net.sourceforge.pmd.cpd; +import static org.junit.Assert.assertEquals; + import java.io.IOException; +import java.nio.charset.StandardCharsets; import org.apache.commons.io.IOUtils; import org.junit.Before; import org.junit.Test; +import net.sourceforge.pmd.PMD; import net.sourceforge.pmd.testframework.AbstractTokenizerTest; public class MatlabTokenizerTest extends AbstractTokenizerTest { @@ -25,7 +29,7 @@ public void buildTokenizer() throws IOException { @Override public String getSampleCode() throws IOException { - return IOUtils.toString(MatlabTokenizer.class.getResourceAsStream(FILENAME)); + return IOUtils.toString(MatlabTokenizer.class.getResourceAsStream(FILENAME), StandardCharsets.UTF_8); } @Test @@ -33,4 +37,21 @@ public void tokenizeTest() throws IOException { this.expectedTokenCount = 3925; super.tokenizeTest(); } + + @Test + public void testIgnoreBetweenSpecialComments() throws IOException { + SourceCode sourceCode = new SourceCode(new SourceCode.StringCodeLoader("% CPD-OFF" + PMD.EOL + + "function g = vec(op, y)" + PMD.EOL + + " opy = op(y);" + PMD.EOL + + " if ( any(size(opy) > 1) )" + PMD.EOL + + " g = @loopWrapperArray;" + PMD.EOL + + " end" + PMD.EOL + + " % CPD-ON" + PMD.EOL + + "end" + )); + Tokens tokens = new Tokens(); + tokenizer.tokenize(sourceCode, tokens); + TokenEntry.getEOF(); + assertEquals(2, tokens.size()); // 2 tokens: "end" + EOF + } } diff --git a/pmd-objectivec/pom.xml b/pmd-objectivec/pom.xml index af412ffb4ea..9b9fe4da2c9 100644 --- a/pmd-objectivec/pom.xml +++ b/pmd-objectivec/pom.xml @@ -7,13 +7,9 @@ <parent> <groupId>net.sourceforge.pmd</groupId> <artifactId>pmd</artifactId> - <version>6.0.0-SNAPSHOT</version> + <version>6.10.0-SNAPSHOT</version> </parent> - <properties> - <config.basedir>${basedir}/../pmd-core</config.basedir> - </properties> - <build> <plugins> <plugin> @@ -73,6 +69,10 @@ <groupId>net.sourceforge.pmd</groupId> <artifactId>pmd-core</artifactId> </dependency> + <dependency> + <groupId>commons-io</groupId> + <artifactId>commons-io</artifactId> + </dependency> <dependency> <groupId>junit</groupId> diff --git a/pmd-objectivec/src/main/ant/alljavacc.xml b/pmd-objectivec/src/main/ant/alljavacc.xml index 5396813b9af..26ab8cd3d93 100644 --- a/pmd-objectivec/src/main/ant/alljavacc.xml +++ b/pmd-objectivec/src/main/ant/alljavacc.xml @@ -40,6 +40,56 @@ <delete file="${target}/net/sourceforge/pmd/lang/objectivec/ast/CharStream.java" /> <delete file="${target}/net/sourceforge/pmd/lang/objectivec/ast/ParseException.java" /> <delete file="${target}/net/sourceforge/pmd/lang/objectivec/ast/TokenMgrError.java" /> + + <replace file="${target}/net/sourceforge/pmd/lang/objectivec/ast/Token.java"> + <replacetoken>public class Token implements java.io.Serializable</replacetoken> + <replacevalue><![CDATA[import net.sourceforge.pmd.lang.ast.GenericToken; + +public class Token implements GenericToken, java.io.Serializable]]></replacevalue> + </replace> + + <!--Add implementation methods of GenericToken--> + <replace file="${target}/net/sourceforge/pmd/lang/objectivec/ast/Token.java"> + <replacetoken>public Token specialToken;</replacetoken> + <replacevalue><![CDATA[public Token specialToken; + + @Override + public GenericToken getNext() { + return next; + } + + @Override + public GenericToken getPreviousComment() { + return specialToken; + } + + @Override + public String getImage() { + return image; + } + + @Override + public int getBeginLine() { + return beginLine; + } + + @Override + public int getEndLine() { + return endLine; + } + + @Override + public int getBeginColumn() { + return beginColumn; + } + + @Override + public int getEndColumn() { + return endColumn; + } + +]]></replacevalue> + </replace> </target> </project> diff --git a/pmd-objectivec/src/main/java/net/sourceforge/pmd/cpd/ObjectiveCTokenizer.java b/pmd-objectivec/src/main/java/net/sourceforge/pmd/cpd/ObjectiveCTokenizer.java index 27dab7e2e58..00e56d793c4 100644 --- a/pmd-objectivec/src/main/java/net/sourceforge/pmd/cpd/ObjectiveCTokenizer.java +++ b/pmd-objectivec/src/main/java/net/sourceforge/pmd/cpd/ObjectiveCTokenizer.java @@ -4,14 +4,14 @@ package net.sourceforge.pmd.cpd; +import java.io.IOException; import java.io.Reader; import java.io.StringReader; -import org.apache.commons.io.IOUtils; - +import net.sourceforge.pmd.cpd.token.JavaCCTokenFilter; +import net.sourceforge.pmd.cpd.token.TokenFilter; import net.sourceforge.pmd.lang.LanguageRegistry; import net.sourceforge.pmd.lang.LanguageVersionHandler; -import net.sourceforge.pmd.lang.TokenManager; import net.sourceforge.pmd.lang.ast.TokenMgrError; import net.sourceforge.pmd.lang.objectivec.ObjectiveCLanguageModule; import net.sourceforge.pmd.lang.objectivec.ast.Token; @@ -24,18 +24,16 @@ public class ObjectiveCTokenizer implements Tokenizer { @Override public void tokenize(SourceCode sourceCode, Tokens tokenEntries) { StringBuilder buffer = sourceCode.getCodeBuffer(); - Reader reader = null; - try { + try (Reader reader = new StringReader(buffer.toString())) { LanguageVersionHandler languageVersionHandler = LanguageRegistry.getLanguage(ObjectiveCLanguageModule.NAME) .getDefaultVersion().getLanguageVersionHandler(); - reader = new StringReader(buffer.toString()); - TokenManager tokenManager = languageVersionHandler + final TokenFilter tokenFilter = new JavaCCTokenFilter(languageVersionHandler .getParser(languageVersionHandler.getDefaultParserOptions()) - .getTokenManager(sourceCode.getFileName(), reader); - Token currentToken = (Token) tokenManager.getNextToken(); - while (currentToken.image.length() > 0) { + .getTokenManager(sourceCode.getFileName(), reader)); + Token currentToken = (Token) tokenFilter.getNextToken(); + while (currentToken != null) { tokenEntries.add(new TokenEntry(currentToken.image, sourceCode.getFileName(), currentToken.beginLine)); - currentToken = (Token) tokenManager.getNextToken(); + currentToken = (Token) tokenFilter.getNextToken(); } tokenEntries.add(TokenEntry.getEOF()); System.err.println("Added " + sourceCode.getFileName()); @@ -43,8 +41,8 @@ public void tokenize(SourceCode sourceCode, Tokens tokenEntries) { err.printStackTrace(); System.err.println("Skipping " + sourceCode.getFileName() + " due to parse error"); tokenEntries.add(TokenEntry.getEOF()); - } finally { - IOUtils.closeQuietly(reader); + } catch (IOException e) { + e.printStackTrace(); } } } diff --git a/pmd-objectivec/src/main/java/net/sourceforge/pmd/lang/objectivec/ObjectiveCTokenManager.java b/pmd-objectivec/src/main/java/net/sourceforge/pmd/lang/objectivec/ObjectiveCTokenManager.java index 9190e673be1..ec573e5d96b 100644 --- a/pmd-objectivec/src/main/java/net/sourceforge/pmd/lang/objectivec/ObjectiveCTokenManager.java +++ b/pmd-objectivec/src/main/java/net/sourceforge/pmd/lang/objectivec/ObjectiveCTokenManager.java @@ -18,7 +18,7 @@ public class ObjectiveCTokenManager implements TokenManager { /** * Creates a new Objective-C Token Manager from the given source code. - * + * * @param source * the source code */ @@ -26,6 +26,7 @@ public ObjectiveCTokenManager(Reader source) { tokenManager = new ObjectiveCParserTokenManager(new SimpleCharStream(source)); } + @Override public Object getNextToken() { return tokenManager.getNextToken(); } diff --git a/pmd-objectivec/src/test/java/net/sourceforge/pmd/cpd/ObjectiveCTokenizerTest.java b/pmd-objectivec/src/test/java/net/sourceforge/pmd/cpd/ObjectiveCTokenizerTest.java index 2013927abc5..7fdd53a4ca2 100644 --- a/pmd-objectivec/src/test/java/net/sourceforge/pmd/cpd/ObjectiveCTokenizerTest.java +++ b/pmd-objectivec/src/test/java/net/sourceforge/pmd/cpd/ObjectiveCTokenizerTest.java @@ -5,6 +5,7 @@ package net.sourceforge.pmd.cpd; import java.io.IOException; +import java.nio.charset.StandardCharsets; import org.apache.commons.io.IOUtils; import org.junit.Before; @@ -25,7 +26,7 @@ public void buildTokenizer() throws IOException { @Override public String getSampleCode() throws IOException { - return IOUtils.toString(ObjectiveCTokenizer.class.getResourceAsStream(FILENAME)); + return IOUtils.toString(ObjectiveCTokenizer.class.getResourceAsStream(FILENAME), StandardCharsets.UTF_8); } @Test diff --git a/pmd-objectivec/src/test/java/net/sourceforge/pmd/cpd/UTF8EscapesInStringLiteralObjCTokenizerTest.java b/pmd-objectivec/src/test/java/net/sourceforge/pmd/cpd/UTF8EscapesInStringLiteralObjCTokenizerTest.java index e5067e5f8bf..8a4491742ef 100644 --- a/pmd-objectivec/src/test/java/net/sourceforge/pmd/cpd/UTF8EscapesInStringLiteralObjCTokenizerTest.java +++ b/pmd-objectivec/src/test/java/net/sourceforge/pmd/cpd/UTF8EscapesInStringLiteralObjCTokenizerTest.java @@ -5,6 +5,7 @@ package net.sourceforge.pmd.cpd; import java.io.IOException; +import java.nio.charset.StandardCharsets; import org.apache.commons.io.IOUtils; import org.junit.Before; @@ -26,7 +27,7 @@ public void buildTokenizer() throws IOException { @Override public String getSampleCode() throws IOException { - return IOUtils.toString(ObjectiveCTokenizer.class.getResourceAsStream(FILENAME), "UTF-8"); + return IOUtils.toString(ObjectiveCTokenizer.class.getResourceAsStream(FILENAME), StandardCharsets.UTF_8); } @Test diff --git a/pmd-objectivec/src/test/java/net/sourceforge/pmd/cpd/UnicodeObjectiveCTokenizerTest.java b/pmd-objectivec/src/test/java/net/sourceforge/pmd/cpd/UnicodeObjectiveCTokenizerTest.java index 7418a99b49f..31dfca0aa97 100644 --- a/pmd-objectivec/src/test/java/net/sourceforge/pmd/cpd/UnicodeObjectiveCTokenizerTest.java +++ b/pmd-objectivec/src/test/java/net/sourceforge/pmd/cpd/UnicodeObjectiveCTokenizerTest.java @@ -4,12 +4,16 @@ package net.sourceforge.pmd.cpd; +import static org.junit.Assert.assertEquals; + import java.io.IOException; +import java.nio.charset.StandardCharsets; import org.apache.commons.io.IOUtils; import org.junit.Before; import org.junit.Test; +import net.sourceforge.pmd.PMD; import net.sourceforge.pmd.testframework.AbstractTokenizerTest; //Tests if the ObjectiveC tokenizer supports identifiers with unicode characters @@ -26,7 +30,7 @@ public void buildTokenizer() throws IOException { @Override public String getSampleCode() throws IOException { - return IOUtils.toString(ObjectiveCTokenizer.class.getResourceAsStream(FILENAME), "UTF-8"); + return IOUtils.toString(ObjectiveCTokenizer.class.getResourceAsStream(FILENAME), StandardCharsets.UTF_8); } @Test @@ -34,4 +38,18 @@ public void tokenizeTest() throws IOException { this.expectedTokenCount = 10; super.tokenizeTest(); } + + @Test + public void testIgnoreBetweenSpecialComments() throws IOException { + SourceCode sourceCode = new SourceCode(new SourceCode.StringCodeLoader( + "// CPD-OFF" + PMD.EOL + + "static SecCertificateRef gNСServerLogonCertificate;" + PMD.EOL + + "// CPD-ON" + PMD.EOL + + "@end" + PMD.EOL + )); + Tokens tokens = new Tokens(); + tokenizer.tokenize(sourceCode, tokens); + TokenEntry.getEOF(); + assertEquals(2, tokens.size()); // 2 tokens: "@end" + EOF + } } diff --git a/pmd-perl/pom.xml b/pmd-perl/pom.xml index 90c47547bf0..b3d51fa72cc 100644 --- a/pmd-perl/pom.xml +++ b/pmd-perl/pom.xml @@ -7,13 +7,9 @@ <parent> <groupId>net.sourceforge.pmd</groupId> <artifactId>pmd</artifactId> - <version>6.0.0-SNAPSHOT</version> + <version>6.10.0-SNAPSHOT</version> </parent> - <properties> - <config.basedir>${basedir}/../pmd-core</config.basedir> - </properties> - <build> <plugins> <plugin> diff --git a/pmd-php/pom.xml b/pmd-php/pom.xml index ec55df8dd1a..d2308286268 100644 --- a/pmd-php/pom.xml +++ b/pmd-php/pom.xml @@ -7,13 +7,9 @@ <parent> <groupId>net.sourceforge.pmd</groupId> <artifactId>pmd</artifactId> - <version>6.0.0-SNAPSHOT</version> + <version>6.10.0-SNAPSHOT</version> </parent> - <properties> - <config.basedir>${basedir}/../pmd-core</config.basedir> - </properties> - <build> <plugins> <plugin> @@ -33,6 +29,11 @@ <artifactId>pmd-core</artifactId> </dependency> + <dependency> + <groupId>junit</groupId> + <artifactId>junit</artifactId> + <scope>test</scope> + </dependency> <dependency> <groupId>net.sourceforge.pmd</groupId> <artifactId>pmd-test</artifactId> diff --git a/pmd-plsql/etc/grammar/PldocAST.jjt b/pmd-plsql/etc/grammar/PldocAST.jjt index 57cb0cc594a..cac0995006e 100644 --- a/pmd-plsql/etc/grammar/PldocAST.jjt +++ b/pmd-plsql/etc/grammar/PldocAST.jjt @@ -27,6 +27,26 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. /** + * Added support for DELETE Statements + * Added support for UPDATE Statements + * Fully parse the select statement in CursorForLoops. + * + * Andreas Dangel 09/2018 + *==================================================================== + * Added support for OrderBy and RowLimiting clauses for SELECT statements + * Removed FROM from the RelationalExpression + * Support QueryPartitionClause + * Support GroupByClause + * + * Andreas Dangel 08/2018 + *==================================================================== + * Added more complete support for CREATE TABLE + * Added support for SELECT INTO statement + * Avoiding deep AST for *Expression nodes + * Added ASTCursorForLoop and ASTSelectStatement + * + * Andreas Dangel 07/2018 + *==================================================================== * Added ASTIsOfTypeCondition node, added support for USING IN|OUT|IN_OUT * See PMD Bug #1520 * @@ -46,7 +66,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // options { - DEBUG_PARSER = false ; + DEBUG_PARSER = false; DEBUG_TOKEN_MANAGER = false; DEBUG_LOOKAHEAD = false; IGNORE_CASE = true; @@ -199,7 +219,9 @@ ASTInput Input() : {} | LOOKAHEAD(6) DatabaseLink() | LOOKAHEAD(6) Global() | LOOKAHEAD(6) DDLCommand() //Ignore any other DDL Event - | LOOKAHEAD(2) SqlPlusCommand() + | LOOKAHEAD(2) SqlPlusCommand() + | LOOKAHEAD(2) UpdateStatement() + | LOOKAHEAD(2) DeleteStatement() |(<SELECT>|<UPDATE>|<INSERT>|<DELETE>|<COMMIT>|<ROLLBACK>|<SAVEPOINT>|<LOCK><TABLE>|<MERGE>|<WITH>) SkipPastNextTokenOccurrence(SQLPLUS_TERMINATOR) //Ignore SQL statements in scripts ) ("/")* @@ -939,7 +961,7 @@ void Skip2NextTerminator(String initiator,String terminator) : t = getToken(1); if(t.image.equals(initiator)) count++; if(t.image.equals(terminator)) count--; - if((null != t.specialToken && beginToken.kind != SELECT && beginToken.kind != INSERT && beginToken.kind != UPDATE && beginToken.kind != DELETE && beginToken.kind != MERGE) || t.kind == EOF) + if((null != t.specialToken && beginToken.kind != SELECT && beginToken.kind != INSERT && beginToken.kind != UPDATE && beginToken.kind != DELETE && beginToken.kind != MERGE && beginToken.kind != EXECUTE) || t.kind == EOF) return; if (t.specialToken != null && "/".equals(t.image)) return; @@ -1092,6 +1114,574 @@ ASTSqlStatement SqlStatement(String initiator, String terminator) : } } +void AbstractSelectStatement(AbstractSelectStatement node) #void : +{} +{ + <SELECT> + [ <DISTINCT> { node.setDistinct(true); } + | <UNIQUE> { node.setUnique(true); } + | <ALL> { node.setAll(true); } + ] +} + +/** + * @see https://docs.oracle.com/en/database/oracle/oracle-database/18/lnpls/SELECT-INTO-statement.html + * @see https://docs.oracle.com/en/database/oracle/oracle-database/18/lnpls/img_text/select_into_statement.html + */ +ASTSelectIntoStatement SelectIntoStatement() : +{} +{ + AbstractSelectStatement(jjtThis) + SelectList() + ( IntoClause() | BulkCollectIntoClause() ) + RestOfStatement() + + { return jjtThis; } +} + +void RestOfStatement() #void : +{} +{ + FromClause() + [ WhereClause() ] + [ GroupByClause() ] + [ OrderByClause() ] + [ RowLimitingClause() ] +} + +void Subquery() #void : +{} +{ + ( + QueryBlock() (LOOKAHEAD(2) ( SubqueryOperation() ) Subquery() )* + | "(" Subquery() ")" + ) + [ LOOKAHEAD(2) OrderByClause() ] + [ LOOKAHEAD(2) RowLimitingClause() ] +} + +ASTSubqueryOperation SubqueryOperation() : +{} +{ + ( + <UNION> { jjtThis.setImage(token.image); jjtThis.setUnion(true); } [ <ALL> { jjtThis.setImage(jjtThis.getImage() + " " + token.image); jjtThis.setAll(true); } ] + | + <INTERSECT> { jjtThis.setImage(token.image); jjtThis.setIntersect(true); } + | + <MINUS> { jjtThis.setImage(token.image); jjtThis.setMinus(true); } + ) + { return jjtThis; } +} + +/** + * @see https://docs.oracle.com/en/database/oracle/oracle-database/18/sqlrf/img_text/order_by_clause.html + */ +ASTOrderByClause OrderByClause() : +{} +{ + <ORDER> [ <SIBLINGS> ] <BY> + OrderByEntry() ( "," OrderByEntry() )* + { return jjtThis; } +} + +void OrderByEntry() #void : +{} +{ + ( LOOKAHEAD(2) ColumnAlias() | LOOKAHEAD(2) SqlExpression() ) + [ <ASC> | <DESC> ] + [ LOOKAHEAD(2) <NULLS> <FIRST> | LOOKAHEAD(2) <NULLS> <LAST> ] +} + +/** + * @see https://docs.oracle.com/en/database/oracle/oracle-database/18/sqlrf/img_text/row_limiting_clause.html + */ +ASTRowLimitingClause RowLimitingClause() : +{} +{ + ( + <OFFSET> NumericLiteral() ( <ROW> | <ROWS> ) + | + <FETCH> ( <FIRST> | <NEXT> ) [ NumericLiteral() [ <PERCENT> ] ] ( <ROW> | <ROWS> ) ( <ONLY> | <WITH> <TIES> ) + ) + { return jjtThis; } +} + +ASTQueryBlock QueryBlock() : +{} +{ + // [ WithClause() ] + AbstractSelectStatement(jjtThis) + SelectList() + FromClause() + [ WhereClause() ] + // [ HierarchicalQueryClause() ] + [ GroupByClause() ] + // [ ModelClause() ] + { return jjtThis; } +} + +/** + * @see https://docs.oracle.com/en/database/oracle/oracle-database/18/sqlrf/img_text/group_by_clause.html + */ +ASTGroupByClause GroupByClause() : +{} +{ + <GROUP> <BY> + ( LOOKAHEAD(2) [","] + ( + LOOKAHEAD(3) RollupCubeClause() + | + LOOKAHEAD(3) GroupingSetsClause() + | + LOOKAHEAD(3) Expression() + ) + )+ + [ <HAVING> Condition() ] + { return jjtThis; } +} + +ASTRollupCubeClause RollupCubeClause() : +{} +{ + ( <ROLLUP> | <CUBE> ) { jjtThis.setImage(token.image); } + "(" GroupingExpressionList() ")" + { return jjtThis; } +} + +ASTGroupingSetsClause GroupingSetsClause() : +{} +{ + <GROUPING> <SETS> + "(" + ( [","] + ( + LOOKAHEAD(3) RollupCubeClause() + | + LOOKAHEAD(3) GroupingExpressionList() + ) + )+ + ")" + { return jjtThis; } +} + +ASTGroupingExpressionList GroupingExpressionList() : +{} +{ + ExpressionList() (LOOKAHEAD(2) "," ExpressionList() )* + { return jjtThis; } +} + +ASTWhereClause WhereClause() : +{} +{ + <WHERE> Condition() + { return jjtThis; } +} + +/** + * @see https://docs.oracle.com/en/database/oracle/oracle-database/18/sqlrf/About-SQL-Conditions.html + */ +ASTCondition Condition() : +{} +{ + ( + LOOKAHEAD(4) CompoundCondition() + | + LOOKAHEAD(4) Condition2() + ) + { return jjtThis; } +} + +// Separate production to resolve left recursion Condition -> CompoundCondition -> Condition +void Condition2() #void : +{} +{ + ( + // a IS OF Type condition that starts with a function... + LOOKAHEAD(FunctionCall() <IS> <OF>) IsOfTypeCondition() + | + LOOKAHEAD(9) ComparisonCondition() + | + LOOKAHEAD(9) FloatingPointCondition() + | + LOOKAHEAD(4) InCondition() + | + LOOKAHEAD(4) LikeCondition() + | + LOOKAHEAD(4) BetweenCondition() + | + LOOKAHEAD(4) IsNullCondition() + | + LOOKAHEAD(4) IsOfTypeCondition() + ) +} + +ASTFloatingPointCondition FloatingPointCondition() : +{} +{ + SqlExpression() <IS> [<NOT>] (<NAN>|<INFINITE>) + { return jjtThis; } +} + +ASTBetweenCondition BetweenCondition() : +{} +{ + SqlExpression() [<NOT>] <BETWEEN> SqlExpression() <AND> SqlExpression() + { return jjtThis; } +} + +ASTLikeCondition LikeCondition() : +{} +{ + SqlExpression() [ <NOT> ] (<LIKE>|<LIKEC>|<LIKE2>|<LIKE4>) SqlExpression() [ <ESCAPE> SqlExpression() ] + { return jjtThis; } +} + +ASTCompoundCondition CompoundCondition() : +{} +{ + ( + LOOKAHEAD(3) "(" Condition() ")" (LOOKAHEAD(2) ( <AND> | <OR> ) Condition() )* + | + LOOKAHEAD(3) <NOT> Condition() + | + LOOKAHEAD(3) Condition2() (LOOKAHEAD(2) ( <AND> | <OR> ) Condition() )* + ) + { return jjtThis; } +} + +ASTInCondition InCondition() : +{} +{ + SqlExpression() [<NOT>] <IN> "(" ( LOOKAHEAD(3) Subquery() | LOOKAHEAD(3) ExpressionListSingle() ) ")" + { return jjtThis; } +} + +ASTComparisonCondition ComparisonCondition() : +{} +{ + ( + // SimpleComparisonCondition + LOOKAHEAD(3) (SqlExpression() ( "=" | "!=" | "^=" | "<>" | ">" | "<" | ">=" | "<=" ) SqlExpression() ) + | LOOKAHEAD(3) ( "(" SqlExpression() ("," SqlExpression())* ")" ( "=" | "!=" | "^=" | "<>" ) "(" ( LOOKAHEAD(3) ExpressionList() | LOOKAHEAD(3) Subquery() ) ")" ) + + // GroupComparisonCondition + | LOOKAHEAD(3) (SqlExpression() ( "=" | "!=" | "^=" | "<>" | ">" | "<" | ">=" | "<=" ) ( <ANY> | <SOME> | <ALL> ) "(" ( LOOKAHEAD(3) ExpressionListSingle() | LOOKAHEAD(3) Subquery() ) ")" ) + | LOOKAHEAD(3) ( "(" SqlExpression() ("," SqlExpression())* ")" ( "=" | "!=" | "^=" | "<>" ) ( <ANY> | <SOME> | <ALL> ) "(" ( LOOKAHEAD(3) ExpressionListMultiple() ("," ExpressionListMultiple())* | LOOKAHEAD(3) Subquery() ) ")" ) + ) + { return jjtThis; } +} + +ASTExpressionListSingle ExpressionListSingle() : +{} +{ + SqlExpression() (LOOKAHEAD(2) "," SqlExpression())* + { return jjtThis; } +} + +ASTExpressionListMultiple ExpressionListMultiple() : +{} +{ + "(" [ SqlExpression() ("," SqlExpression())* ] ")" + { return jjtThis; } +} + +ASTExpressionList ExpressionList() : +{} +{ + ( + LOOKAHEAD(3) ExpressionListSingle() + | LOOKAHEAD(3) ExpressionListMultiple() + ) + { return jjtThis; } +} + +/** + * @see https://docs.oracle.com/en/database/oracle/oracle-database/18/sqlrf/About-SQL-Expressions.html + */ +ASTSqlExpression SqlExpression() : +{} +{ + // SimpleExpression + ( + LOOKAHEAD(FunctionCall()) FunctionCall() + | + LOOKAHEAD(5) SchemaName() "." TableName() "." Column() + | + LOOKAHEAD(3) TableName() "." Column() + | + LOOKAHEAD(2) Column() + | + LOOKAHEAD(2) <ROWNUM> + | + LOOKAHEAD(2) Literal() + ) + { return jjtThis; } +} + +ASTFunctionCall FunctionCall() : +{ ASTID id; } +{ + id = ID() { jjtThis.setImage(id.getImage()); } Arguments() + { return jjtThis; } +} + +ASTColumn Column() : +{ ASTID id; } +{ + id = ID() { jjtThis.setImage(id.getImage()); } + { return jjtThis; } +} + +ASTFromClause FromClause() : +{} +{ + <FROM> FromClauseEntry() ("," FromClauseEntry() )* + { return jjtThis; } +} + +void FromClauseEntry() #void : +{} +{ + ( + LOOKAHEAD(JoinClause()) JoinClause() + | + // using lookahead to avoid misinterpreting the token after the identifier + // as a table alias rather than as a keyword + LOOKAHEAD(<IDENTIFIER> (<PARTITION>|<NATURAL>|<OUTER>|<JOIN>|<RIGHT>|<CROSS>)) JoinClause() + | + LOOKAHEAD(3) TableReference() + | + "(" JoinClause() ")" + ) +} + +/** + * @see https://docs.oracle.com/en/database/oracle/oracle-database/18/sqlrf/img_text/select_list.html + */ +ASTSelectList SelectList() : +{} +{ + ( + "*" + | + SelectListEntry() ("," SelectListEntry() )* + ) + + { return jjtThis; } +} + +void SelectListEntry() #void : +{} +{ + ( + LOOKAHEAD(3) TableAlias() "." "*" + | + LOOKAHEAD(FunctionCall()) FunctionCall() [ [<AS>] ColumnAlias() ] + | + LOOKAHEAD(3) Expression() [ [<AS>] ColumnAlias() ] + ) +} + +ASTColumnAlias ColumnAlias() : +{} +{ + <IDENTIFIER> { jjtThis.setImage(token.image); } + { return jjtThis; } +} + +ASTTableAlias TableAlias() : +{ ASTID id; } +{ + id = ID() {jjtThis.setImage(id.getImage());} + { return jjtThis; } +} + +ASTIntoClause IntoClause() : +{} +{ + <INTO> + VariableName() ("," VariableName())* + { return jjtThis; } +} + +ASTVariableName VariableName() : +{ ASTID id; } +{ + id = ID() {jjtThis.setImage(id.getImage());} + { return jjtThis; } +} + +ASTBulkCollectIntoClause BulkCollectIntoClause() : +{} +{ + <BULK> <COLLECT> <INTO> BulkCollectIntoClauseEntry() ("," BulkCollectIntoClauseEntry())* + { return jjtThis; } +} + +void BulkCollectIntoClauseEntry() #void : +{} +{ + CollectionName() | ":" HostArrayName() +} + +ASTCollectionName CollectionName() : +{ ASTID id; } +{ + id = ID() {jjtThis.setImage(id.getImage());} + { return jjtThis; } +} + +ASTHostArrayName HostArrayName() : +{ ASTID id; } +{ + id = ID() {jjtThis.setImage(id.getImage());} + { return jjtThis; } +} + +ASTTableReference TableReference() : +{} +{ + // QueryTableExpression + ( + [ LOOKAHEAD(2) SchemaName() "." ] TableName() + | + "(" Subquery() ")" + ) + + [ TableAlias() ] + + { return jjtThis; } +} + +/** + * Special production, used in joins. The table reference might have + * a table alias, but this should not match any following NATURAL, CROSS, etc. + * keywords, although these are allowed as alias names since these are + * not reserved words. + */ +ASTTableReference TableReferenceInJoin() #TableReference : +{} +{ + // QueryTableExpression + ( + [ LOOKAHEAD(2) SchemaName() "." ] TableName() + | + "(" Subquery() ")" + ) + + [ LOOKAHEAD(1, ID(), {getToken(1).kind != NATURAL + && getToken(1).kind != CROSS + && getToken(1).kind != ON + && getToken(1).kind != PARTITION + && getToken(1).kind != RIGHT}) + + TableAlias() ] + + { return jjtThis; } +} + + +ASTSchemaName SchemaName() : +{ ASTID id; } +{ + id = ID() {jjtThis.setImage(id.getImage());} + { return jjtThis; } +} + +ASTTableName TableName() : +{ ASTID id; } +{ + id = ID() {jjtThis.setImage(id.getImage());} + { return jjtThis; } +} + +/** + * @see https://docs.oracle.com/en/database/oracle/oracle-database/18/sqlrf/SELECT.html#GUID-CFA006CA-6FF1-4972-821E-6996142A51C6__CHDIJFDJ + */ +ASTJoinClause JoinClause() : +{} +{ + TableReferenceInJoin() + ( + LOOKAHEAD(2) InnerCrossJoinClause() + | + LOOKAHEAD(2) OuterJoinClause() + | + LOOKAHEAD(2) CrossOuterApplyClause() + )+ + { return jjtThis; } +} + +ASTInnerCrossJoinClause InnerCrossJoinClause() : +{} +{ + ( + [ <INNER> ] <JOIN> TableReferenceInJoin() + ( + <ON> ConditionalOrExpression() + | + <USING> "(" Column() ("," Column() )* ")" + ) + | + ( + <CROSS> { jjtThis.setCross(true); } + | + <NATURAL> { jjtThis.setNatural(true); } [ <INNER> ] + ) + <JOIN> TableReferenceInJoin() + ) + { return jjtThis; } +} + +ASTOuterJoinClause OuterJoinClause() : +{} +{ + [ QueryPartitionClause() ] + [ <NATURAL> { jjtThis.setNatural(true); } ] + OuterJoinType() <JOIN> TableReferenceInJoin() + [ LOOKAHEAD(2) QueryPartitionClause() ] + [ <ON> ConditionalOrExpression() + | <USING> "(" Column() ("," Column() )* ")" + ] + { return jjtThis; } +} + +/** + * @see https://docs.oracle.com/en/database/oracle/oracle-database/18/sqlrf/img_text/query_partition_clause.html + */ +ASTQueryPartitionClause QueryPartitionClause() : +{} +{ + <PARTITION> <BY> + ( + LOOKAHEAD(3) Expression() (LOOKAHEAD(2) "," Expression() )* + | + LOOKAHEAD(3) "(" Expression() ("," Expression() )* ")" + ) + { return jjtThis; } +} + +ASTOuterJoinType OuterJoinType() : +{} +{ + ( + <FULL> {jjtThis.setType(ASTOuterJoinType.Type.FULL); } + | + <LEFT> {jjtThis.setType(ASTOuterJoinType.Type.LEFT); } + | + <RIGHT> {jjtThis.setType(ASTOuterJoinType.Type.RIGHT); } + ) + [ <OUTER> ] + { return jjtThis; } +} + +ASTCrossOuterApplyClause CrossOuterApplyClause() : +{} +{ + ( <CROSS> | <OUTER> ) <APPLY> ( LOOKAHEAD(2) TableReferenceInJoin() | LOOKAHEAD(2) ID() /*collection_expression*/ ) + { return jjtThis; } +} /** * 2011-05-15 - SRT - Added to cope with wrapped objects @@ -1168,10 +1758,14 @@ ASTUnlabelledStatement UnlabelledStatement() : {} { ( + SelectIntoStatement() ";" | + UpdateStatement() ";" | + DeleteStatement() ";" | LOOKAHEAD(["("] <SELECT>|<UPDATE>|<INSERT>|<DELETE>|<COMMIT>|<ROLLBACK>|<SAVEPOINT>|<EXECUTE>|<SET><TRANSACTION>|<LOCK><TABLE>|<MERGE>|<WITH>) SqlStatement(null,";") [";"] | LOOKAHEAD(3) ContinueStatement() ";" // CONTINUE keyword was added in 11G, so Oracle compilation supports CONTINUE as a variable name | CaseStatement() ";" | IfStatement() ";" + | LOOKAHEAD(<FOR> ID() <IN> "(" <SELECT>) CursorForLoopStatement() ";" | ForStatement() ";" | ForAllStatement() ";" | LoopStatement() ";" @@ -1269,6 +1863,61 @@ ASTLoopStatement LoopStatement() : { return jjtThis ; } } +ASTCursorForLoopStatement CursorForLoopStatement() : +{} +{ + <FOR> ForIndex() <IN> "(" SelectStatement() ")" <LOOP> (Statement())+ <END> <LOOP> [<IDENTIFIER>] + { return jjtThis ; } +} + +ASTSelectStatement SelectStatement() : +{} +{ + AbstractSelectStatement(jjtThis) + + SelectList() + + // note: the into clause is here to allow parsing of wrong cursor for loop statements. + // while it will be parsed, the into clause is ignored by the database. + // see https://github.com/pmd/pmd/issues/1047#issuecomment-424757382 + // see http://www.dba-oracle.com/t_adv_plsql_implicit_cursor_FOR_loop.htm + [ IntoClause() | BulkCollectIntoClause() ] + + RestOfStatement() + + { return jjtThis; } +} + +ASTUpdateStatement UpdateStatement() : +{} +{ + <UPDATE> SqlExpression() + UpdateSetClause() + [ WhereClause() ] + { return jjtThis; } +} + +ASTUpdateSetClause UpdateSetClause() : +{} +{ + <SET> + ( + <ROW> "=" ID() + | + Column() "=" ( Expression() | <_DEFAULT> ) + ) + { return jjtThis; } +} + +ASTDeleteStatement DeleteStatement() : +{} +{ + <DELETE> [ <FROM> ] + ( TableReference() | <ONLY> "(" TableReference() ")" ) + [ WhereClause() ] + { return jjtThis; } +} + /** Scope rule: the loop index only exists within the Loop */ ASTForStatement ForStatement() : {} @@ -1793,7 +2442,7 @@ ASTObjectExpression ObjectExpression() : } } -ASTConditionalOrExpression ConditionalOrExpression() : +ASTConditionalOrExpression ConditionalOrExpression() #ConditionalOrExpression(>1) : { PLSQLNode simpleNode = null; StringBuilder sb = new StringBuilder() ; } { ( @@ -1807,7 +2456,7 @@ ASTConditionalOrExpression ConditionalOrExpression() : } } -ASTConditionalAndExpression ConditionalAndExpression() : +ASTConditionalAndExpression ConditionalAndExpression() #ConditionalAndExpression(>1) : { PLSQLNode simpleNode = null; StringBuilder sb = new StringBuilder() ; } { ( @@ -1821,7 +2470,7 @@ ASTConditionalAndExpression ConditionalAndExpression() : } } -ASTEqualityExpression EqualityExpression() : +ASTEqualityExpression EqualityExpression() #EqualityExpression(>1) : { PLSQLNode simpleNode = null; StringBuilder sb = new StringBuilder() ; } { //RelationalExpression() ( ( "=" | "!=" | "<>" | <IS>) RelationalExpression() )* @@ -1845,14 +2494,11 @@ ASTEqualityExpression EqualityExpression() : } } -/** - * 2006-05-23 - Matthias Hendler - added FROM - */ -ASTRelationalExpression RelationalExpression() : +ASTRelationalExpression RelationalExpression() #RelationalExpression(>1) : { PLSQLNode simpleNode = null; StringBuilder sb = new StringBuilder() ; } { ( - //AdditiveExpression() ( ( "<" | ">" | "<=" | ">=" | [<NOT>] (<IN> | <BETWEEN> | <LIKE> | <FROM>) ) AdditiveExpression() )* + //AdditiveExpression() ( ( "<" | ">" | "<=" | ">=" | [<NOT>] (<IN> | <BETWEEN> | <LIKE>) ) AdditiveExpression() )* (simpleNode = AdditiveExpression() ) { sb.append(simpleNode.getImage()); } ( ( @@ -1871,7 +2517,6 @@ ASTRelationalExpression RelationalExpression() : ((<IN>) { sb.append(" IN "); } | (<BETWEEN> ) { sb.append(" BETWEEN "); } | (<LIKE> ) { sb.append(" LIKE "); } - | (<FROM>) { sb.append(" FROM "); } | ( ( (<MEMBER>) { sb.append(" MEMBER "); } @@ -1907,7 +2552,7 @@ ASTRelationalExpression RelationalExpression() : } } -ASTAdditiveExpression AdditiveExpression() : +ASTAdditiveExpression AdditiveExpression() #AdditiveExpression(>1) : { PLSQLNode simpleNode = null; StringBuilder sb = new StringBuilder() ; } { ( @@ -1940,7 +2585,7 @@ ASTStringExpression StringExpression() : } } -ASTMultiplicativeExpression MultiplicativeExpression() : +ASTMultiplicativeExpression MultiplicativeExpression() #MultiplicativeExpression(>1) : { PLSQLNode simpleNode = null; StringBuilder sb = new StringBuilder() ; } { @@ -1961,7 +2606,7 @@ ASTMultiplicativeExpression MultiplicativeExpression() : } } -ASTUnaryExpression UnaryExpression(boolean isUnarySign) : +ASTUnaryExpression UnaryExpression(boolean isUnarySign) #UnaryExpression(>1) : { PLSQLNode simpleNode = null; StringBuilder sb = new StringBuilder() ; } { ( @@ -1977,7 +2622,7 @@ ASTUnaryExpression UnaryExpression(boolean isUnarySign) : } } -ASTUnaryExpressionNotPlusMinus UnaryExpressionNotPlusMinus() : +ASTUnaryExpressionNotPlusMinus UnaryExpressionNotPlusMinus() #UnaryExpressionNotPlusMinus(>1) : { PLSQLNode simpleNode = null; StringBuilder sb = new StringBuilder() ; } { ( @@ -1990,15 +2635,15 @@ ASTUnaryExpressionNotPlusMinus UnaryExpressionNotPlusMinus() : jjtThis.setImage(sb.toString()); return jjtThis; } } -ASTIsNullCondition IsNullCondition() : //yanzin +ASTIsNullCondition IsNullCondition() #IsNullCondition(>1) : //yanzin { PLSQLNode simpleNode = null; PLSQLNode name = null; StringBuilder sb = new StringBuilder(); } { ( - LOOKAHEAD(<IDENTIFIER> <IS> (<NOT> <NULL>|<NULL>)) + LOOKAHEAD(<IDENTIFIER> <IS> [<NOT>] <NULL>) ( (name = Name()) {sb.append(name.getImage());} <IS> {sb.append(" IS");} [<NOT> {sb.append(" NOT");}] <NULL> {sb.append(" NULL");} ) - | + | ( simpleNode = IsOfTypeCondition() ) @@ -2012,13 +2657,18 @@ ASTIsNullCondition IsNullCondition() : //yanzin } } -ASTIsOfTypeCondition IsOfTypeCondition() : +ASTIsOfTypeCondition IsOfTypeCondition() #IsOfTypeCondition(>1) : { PLSQLNode simpleNode = null; PLSQLNode name = null; StringBuilder sb = new StringBuilder(); } { ( LOOKAHEAD(<IDENTIFIER> <IS>) ((name = Name()) {sb.append(name.getImage());} <IS> {sb.append(" IS");} [<NOT> {sb.append(" NOT");}] <OF> {sb.append(" OF");} [<TYPE>] "(" [<ONLY>] Name() ("," [<ONLY>] Name() )* ")") + | + LOOKAHEAD(PrimaryExpression() <IS>) + (simpleNode = PrimaryExpression() ) { sb.append(simpleNode.getImage()); } + <IS> {sb.append(" IS");} [<NOT> {sb.append(" NOT");}] <OF> {sb.append(" OF");} [<TYPE>] + "(" [<ONLY>] Name() ("," [<ONLY>] Name() )* ")" | (simpleNode = PrimaryExpression() ) { sb.append(simpleNode.getImage()); } ) @@ -2032,7 +2682,7 @@ ASTIsOfTypeCondition IsOfTypeCondition() : * Warning arised while adding methode triggerUnit(). * 2011-04-27 - SRT - Add optional NEW Keyword to cope with Object Type constructors */ -ASTPrimaryExpression PrimaryExpression() : +ASTPrimaryExpression PrimaryExpression() #PrimaryExpression(>1) : { Token thisToken ; PLSQLNode simpleNode = null; StringBuilder sb = new StringBuilder() ; } { @@ -2471,10 +3121,12 @@ ASTTable Table() : { } { - <CREATE> [<GLOBAL> <TEMPORARY>] + <CREATE> [ ( <GLOBAL> | <PRIVATE> ) <TEMPORARY> | <SHARDED> | <DUPLICATED> ] <TABLE> ObjectNameDeclaration() - "(" TableColumn() ("," TableColumn())* ")" - [LOOKAHEAD(2) <ON> <COMMIT> (<DELETE> | <PRESERVE>) <ROWS>] + [ <SHARING> "=" ( <METADATA> | <DATA> | <EXTENDED> <DATA> | <NONE> ) ] + [ "(" ( LOOKAHEAD(2) OutOfLineConstraint() | TableColumn() ) ("," ( LOOKAHEAD(2) OutOfLineConstraint() | TableColumn() ))* ")" ] + [LOOKAHEAD(4) <ON> <COMMIT> (<DROP> | <PRESERVE>) <DEFINITION>] + [LOOKAHEAD(4) <ON> <COMMIT> (<DELETE> | <PRESERVE>) <ROWS>] //### [physicalProperties()] //### [tableProperties()] [";"] @@ -2490,7 +3142,26 @@ ASTTableColumn TableColumn() : { return jjtThis ; } } +ASTOutOfLineConstraint OutOfLineConstraint() : +{} +{ + [ <CONSTRAINT> ID() ] + ( <UNIQUE> { jjtThis.setType(ConstraintType.UNIQUE); } "(" ID() ("," ID())* ")" + | <PRIMARY> { jjtThis.setType(ConstraintType.PRIMARY); } <KEY> "(" ID() ("," ID())* ")" + | <FOREIGN> { jjtThis.setType(ConstraintType.FOREIGN); } <KEY> "(" ID() ("," ID())* ")" ReferencesClause() + | <CHECK> { jjtThis.setType(ConstraintType.CHECK); } "(" Statement() ")" + ) + { return jjtThis; } +} +ASTReferencesClause ReferencesClause() : +{} +{ + <REFERENCES> ObjectNameDeclaration() + [ "(" ID() ( LOOKAHEAD(2) "," ID())* ] + [ <ON> <DELETE> ( <CASCADE> | <SET> <NULL> ) ] + { return jjtThis; } +} ASTView View() : { @@ -2503,8 +3174,7 @@ ASTView View() : ["(" ViewColumn() ("," ViewColumn())* ")"] //### OF ... WITH OBJECT IDENTIFIER... <AS> - Statement() //SRT select() - //### WITH ... + SqlStatement(null,";") (";" | "/") { jjtThis.setImage(simpleNode.getImage()) ; return jjtThis ; } @@ -3312,6 +3982,7 @@ TOKEN [IGNORE_CASE]: <ALTER: "ALTER"> | <AND: "AND"> | <ANY: "ANY"> | +<APPLY: "APPLY"> | <ARRAY: "ARRAY"> | <AS: "AS"> | <ASC: "ASC"> | @@ -3342,10 +4013,13 @@ TOKEN [IGNORE_CASE]: <COMPRESS: "COMPRESS"> | <CONNECT: "CONNECT"> | <CONSTANT: "CONSTANT"> | +<CONSTRAINT: "CONSTRAINT"> | <CONSTRUCTOR: "CONSTRUCTOR"> | <CONTINUE: "CONTINUE"> | <CONVERT: "CONVERT"> | <CREATE: "CREATE"> | +<CROSS: "CROSS"> | +<CUBE: "CUBE"> | <CURRENT: "CURRENT"> | <CURRVAL: "CURRVAL"> | <CURSOR: "CURSOR"> | @@ -3355,6 +4029,7 @@ TOKEN [IGNORE_CASE]: <DECLARE: "DECLARE"> | <DECIMAL: "DECIMAL"> | <_DEFAULT: "DEFAULT"> | +<DEFINITION: "DEFINITION"> | <DELETE: "DELETE"> | <DESC: "DESC"> | <DETERMINISTIC: "DETERMINISTIC"> | @@ -3362,6 +4037,7 @@ TOKEN [IGNORE_CASE]: <DISTINCT: "DISTINCT"> | <DO: "DO"> | <DROP: "DROP"> | +<DUPLICATED: "DUPLICATED"> | <EDITIONABLE: "EDITIONABLE"> | <ELEMENT: "ELEMENT"> | <ELSE: "ELSE"> | @@ -3378,20 +4054,25 @@ TOKEN [IGNORE_CASE]: <EXIT: "EXIT"> | <EXTERNAL: "EXTERNAL"> | //<EXTERNAL_NAME: "EXTERNAL NAME"> | +<EXTENDED: "EXTENDED"> | <EXTENDS: "EXTENDS"> | <EXTRACT: "EXTRACT"> | <FALSE: "FALSE"> | <FETCH: "FETCH"> | <FINAL: "FINAL"> | +<FIRST: "FIRST"> | <FLOAT: "FLOAT"> | <FOR: "FOR"> | <FORALL: "FORALL"> | +<FOREIGN: "FOREIGN"> | <FORCE: "FORCE"> | <FROM: "FROM"> | +<FULL: "FULL"> | <FUNCTION: "FUNCTION"> | <GLOBAL: "GLOBAL"> | <GOTO: "GOTO"> | <GROUP: "GROUP"> | +<GROUPING: "GROUPING"> | <GRANT: "GRANT"> | //SRT 2011-04-17 <HASH: "HASH"> | <HAVING: "HAVING"> | @@ -3401,12 +4082,14 @@ TOKEN [IGNORE_CASE]: <IF: "IF"> | <IMMEDIATE: "IMMEDIATE"> | <IN: "IN"> | +<INNER: "INNER"> | <IN_OUT: "IN_OUT"> | <INDEX: "INDEX"> | <INDICES: "INDICES"> | <INCLUDING: "INCLUDING"> | <INDEXTYPE: "INDEXTYPE"> | <INDICATOR: "INDICATOR"> | +<INFINITE: "INFINITE"> | <INSERT: "INSERT"> | <INSTANTIABLE: "INSTANTIABLE"> | <INTEGER: "INTEGER"> | @@ -3418,9 +4101,15 @@ TOKEN [IGNORE_CASE]: <IS: "IS"> | <ISOLATION: "ISOLATION"> | <JAVA: "JAVA"> | +<JOIN: "JOIN"> | +<KEY: "KEY"> | //<LANGUAGE: "LANGUAGE"> | +<LAST: "LAST"> | <LEVEL: "LEVEL"> | <LIKE: "LIKE"> | +<LIKEC: "LIKEC"> | +<LIKE2: "LIKE2"> | +<LIKE4: "LIKE4"> | <LIMIT: "LIMIT"> | <LIMITED: "LIMITED"> | <LOCK: "LOCK"> | @@ -3430,6 +4119,7 @@ TOKEN [IGNORE_CASE]: <MAX: "MAX"> | <MEMBER: "MEMBER"> | <MERGE: "MERGE"> | +<METADATA: "METADATA"> | <MIN: "MIN"> | <MINUS: "MINUS"> | <MINUTE: "MINUTE"> | @@ -3438,19 +4128,23 @@ TOKEN [IGNORE_CASE]: <MOD: "MOD"> | <MODE: "MODE"> | <MONTH: "MONTH"> | +<NAN: "NAN"> | <NATURAL: "NATURAL"> | <NATURALN: "NATURALN"> | <NEW: "NEW"> | <NEW_DOT: "NEW."> | +<NEXT: "NEXT"> | <NEXTVAL: "NEXTVAL"> | <NO: "NO"> | <NOCOMPRESS: "NOCOMPRESS"> | //SRT 2011-04-17 <NOCOPY: "NOCOPY"> | +<NONE: "NONE"> | <NONEDITIONABLE: "NONEDITIONABLE"> | <NOT: "NOT"> | <NOWAIT: "NOWAIT"> | <NULL: "NULL"> | <NULLIF: "NULLIF"> | +<NULLS: "NULLS"> | <NUMBER: "NUMBER"> | <BFILE_BASE: "BFILE_BASE"> | <BLOB_BASE: "BLOB_BASE"> | @@ -3461,6 +4155,7 @@ TOKEN [IGNORE_CASE]: <OBJECT: "OBJECT"> | <OCIROWID: "OCIROWID"> | <OF: "OF"> | +<OFFSET: "OFFSET"> | <OID: "OID"> | <ON: "ON"> | <ONLY: "ONLY"> | @@ -3473,11 +4168,13 @@ TOKEN [IGNORE_CASE]: <ORGANIZATION: "ORGANIZATION"> | <OTHERS: "OTHERS"> | <OUT: "OUT"> | +<OUTER: "OUTER"> | <OVERRIDING: "OVERRIDING"> | <PACKAGE: "PACKAGE"> | <PARALLEL_ENABLE: "PARALLEL_ENABLE"> | <PARTITION: "PARTITION"> | <PCTFREE: "PCTFREE"> | +<PERCENT: "PERCENT"> | <PIPE: "PIPE"> | <PIPELINED: "PIPELINED"> | <PLS_INTEGER: "PLS_INTEGER"> | @@ -3487,6 +4184,7 @@ TOKEN [IGNORE_CASE]: <PRESERVE: "PRESERVE"> | <PRIOR: "PRIOR"> | <PROMPT: "PROMPT"> | +<PRIMARY: "PRIMARY"> | <PRIVATE: "PRIVATE"> | <PROCEDURE: "PROCEDURE"> | <PUBLIC: "PUBLIC"> | @@ -3497,6 +4195,7 @@ TOKEN [IGNORE_CASE]: <REAL: "REAL"> | <RECORD: "RECORD"> | <REF: "REF"> | +<REFERENCES: "REFERENCES"> | <RELEASE: "RELEASE"> | <RELIES_ON: "RELIES_ON"> | <RENAME: "RENAME"> | //SRT 2011-04-17 @@ -3508,6 +4207,7 @@ TOKEN [IGNORE_CASE]: <REVERSE: "REVERSE"> | <REVOKE: "REVOKE"> | //SRT 2011-04-17 <ROLLBACK: "ROLLBACK"> | +<ROLLUP: "ROLLUP"> | <ROW: "ROW"> | <ROWS: "ROWS"> | <ROWID: "ROWID"> | @@ -3520,9 +4220,14 @@ TOKEN [IGNORE_CASE]: <SELF: "SELF"> | <SEPARATE: "SEPARATE"> | <SET: "SET"> | +<SETS: "SETS"> | +<SIBLINGS: "SIBLINGS"> | <SIZE: "SIZE"> | //SRT 2011-04-17 +<SHARDED: "SHARDED"> | <SHARE: "SHARE"> | +<SHARING: "SHARING"> | <SMALLINT: "SMALLINT"> | +<SOME: "SOME"> | <SPACE: "SPACE"> | <SQL: "SQL"> | <SQLCODE: "SQLCODE"> | @@ -3540,6 +4245,7 @@ TOKEN [IGNORE_CASE]: <TABLE: "TABLE"> | <TEMPORARY: "TEMPORARY"> | <THEN: "THEN"> | +<TIES: "TIES"> | <TIME: "TIME"> | <TIMESTAMP: "TIMESTAMP"> | <TIMEZONE_REGION: "TIMEZONE_REGION"> | @@ -3566,8 +4272,7 @@ TOKEN [IGNORE_CASE]: <SPOOL: "SPOOL"> | // are they reserved or not ? -// most are not reserved, but cannot use just "WHERE" etc instead - resolves as identifier ! -//<WHERE: "WHERE"> | +// most are not reserved, but cannot use just define them - might be resolved as identifiers //<WHILE: "WHILE"> | //<NAME: "NAME"> | <A: "A"> | @@ -3696,7 +4401,7 @@ TOKEN [IGNORE_CASE]: */ TOKEN : { -< #GERMAN_SPECIAL_CHARACTERS: "Ä" | "Ö" | "Ü" | "ä" | "ü" | "ö" | "ß" > +< #GERMAN_SPECIAL_CHARACTERS: "Ä" | "Ö" | "Ãœ" | "ä" | "ü" | "ö" | "ß" > | < #LETTER: ["A"-"Z"] | ["a"-"z"] | <GERMAN_SPECIAL_CHARACTERS> > | @@ -3709,7 +4414,7 @@ TOKEN : | "$" | "&" | "_" | "|" | "{" | "}" | "?" | "[" | "]" | " " | "\t" > | -< #SPECIAL_CHARACTERS: "á" | "Ž" | "™" | "š" | "„" | "”" | "ý" | "²" | "€" | "³" | "µ"> +< #SPECIAL_CHARACTERS: "á" | "Ž" | "â„¢" | "Å¡" | "„" | "â€" | "ý" | "²" | "€" | "³" | "µ"> | < #DELIMITER: "+" | "%" | "'" | "\"" | "." | "/" | "(" | ")" | ":" | "," | "*" | "=" | "<" | ">" | "@" | ";" | "-"> | @@ -3889,7 +4594,7 @@ ASTKEYWORD_UNRESERVED KEYWORD_UNRESERVED (): {} //| <AND_EQUAL> //| <ANTIJOIN> //| <APPEND> -//| <APPLY> +| <APPLY> //| <ARCHIVE> //| <ARCHIVELOG> //| <ARRAY>test_unreserved_keyword.pks @@ -4003,7 +4708,7 @@ ASTKEYWORD_UNRESERVED KEYWORD_UNRESERVED (): {} //| <CONNECT_TIME> //| <CONSIDER> //| <CONSISTENT> -//| <CONSTRAINT> +| <CONSTRAINT> //| <CONSTRAINTS> | <CONSTRUCTOR> //| <CONTAINER> @@ -4020,8 +4725,8 @@ ASTKEYWORD_UNRESERVED KEYWORD_UNRESERVED (): {} //| <CPU_PER_CALL> //| <CPU_PER_SESSION> //| <CREATE_STORED_OUTLINES> -//| <CROSS> -//| <CUBE> +| <CROSS> +| <CUBE> //| <CUBE_GB> | <CURRENT> //| <CURRENT_DATE> @@ -4058,6 +4763,7 @@ ASTKEYWORD_UNRESERVED KEYWORD_UNRESERVED (): {} | <DEFINE>// SQL*Plus command //| <DEFINED> | <DEFINER> +| <DEFINITION> //| <DEGREE> //| <DELAY> //| <DEMAND> @@ -4089,6 +4795,7 @@ ASTKEYWORD_UNRESERVED KEYWORD_UNRESERVED (): {} //| <DOWNGRADE> //| <DRIVING_SITE> //| <DUMP> +| <DUPLICATED> //| <DYNAMIC> //| <DYNAMIC_SAMPLING> //| <DYNAMIC_SAMPLING_EST_CDN> @@ -4130,6 +4837,7 @@ ASTKEYWORD_UNRESERVED KEYWORD_UNRESERVED (): {} //| <EXPLOSION> //| <EXPORT> //| <EXPR_CORR_CHECK> +| <EXTENDED> | <EXTENDS> //| <EXTENT> //| <EXTENTS> @@ -4149,7 +4857,7 @@ ASTKEYWORD_UNRESERVED KEYWORD_UNRESERVED (): {} | <FINAL> //| <FINE> //| <FINISH> -//| <FIRST> +| <FIRST> //| <FIRST_ROWS> //| <FLAGGER> //| <FLASHBACK> @@ -4159,12 +4867,12 @@ ASTKEYWORD_UNRESERVED KEYWORD_UNRESERVED (): {} | <FOLLOWS> | <FORCE> //| <FORCE_XML_QUERY_REWRITE> -//| <FOREIGN> +| <FOREIGN> //| <FREELIST> //| <FREELISTS> //| <FREEPOOLS> //| <FRESH> -//| <FULL> +| <FULL> | <FUNCTION> //| <FUNCTIONS> //| <G> @@ -4176,7 +4884,7 @@ ASTKEYWORD_UNRESERVED KEYWORD_UNRESERVED (): {} //| <GLOBAL_TOPIC_ENABLED> //| <GLOBALLY> //| <GROUP_BY> -//| <GROUPING> +| <GROUPING> //| <GROUPS> //| <GUARANTEE> //| <GUARANTEED> @@ -4233,7 +4941,7 @@ ASTKEYWORD_UNRESERVED KEYWORD_UNRESERVED (): {} //| <INDEXTYPES> | <INDICATOR> | <INDICES> // FORALL i I INDICES OF collection - SPARSE COLLECTIONS -//| <INFINITE> +| <INFINITE> //| <INFORMATIONAL> //| <INITIAL> //| <INITIALIZED> @@ -4241,7 +4949,7 @@ ASTKEYWORD_UNRESERVED KEYWORD_UNRESERVED (): {} //| <INITRANS> //| <INLINE> //| <INLINE_XMLTYPE_NT> -//| <INNER> +| <INNER> //| <INSTANCE> //| <INSTANCES> | <INSTANTIABLE> @@ -4260,17 +4968,17 @@ ASTKEYWORD_UNRESERVED KEYWORD_UNRESERVED (): {} //| <ITERATION_NUMBER> | <JAVA> //| <JOB> -//| <JOIN> +| <JOIN> //| <K> //| <KEEP> //| <KERBEROS> -//| <KEY> +| <KEY> //| <KEY_LENGTH> //| <KEYS> //| <KEYSIZE> //| <KILL> | <LANGUAGE> -//| <LAST> +| <LAST> //| <LATERAL> //| <LAYER> //| <LDAP_REG_SYNC_INTERVAL> @@ -4284,9 +4992,9 @@ ASTKEYWORD_UNRESERVED KEYWORD_UNRESERVED (): {} //| <LEVELS> | <LIBRARY> //| <LIKE_EXPAND> -//| <LIKE2> -//| <LIKE4> -//| <LIKEC> +| <LIKE2> +| <LIKE4> +| <LIKEC> | <LIMIT> | <LINK> //| <LIST> @@ -4338,6 +5046,7 @@ ASTKEYWORD_UNRESERVED KEYWORD_UNRESERVED (): {} //| <MERGE_AJ> //| <MERGE_CONST_ON> //| <MERGE_SJ> +| <METADATA> //| <METHOD> //| <MIGRATE> | <MIN> @@ -4367,7 +5076,7 @@ ASTKEYWORD_UNRESERVED KEYWORD_UNRESERVED (): {} //| <MV_MERGE> | <NAME> //| <NAMED> -//| <NAN> +| <NAN> //| <NATIONAL> //| <NATIVE> //| <NATIVE_FULL_OUTER_JOIN> @@ -4386,7 +5095,7 @@ ASTKEYWORD_UNRESERVED KEYWORD_UNRESERVED (): {} //| <NETWORK> //| <NEVER> | <NEW> -//| <NEXT> +| <NEXT> //| <NL_AJ> //| <NL_SJ> //| <NLS_CALENDAR> @@ -4470,7 +5179,7 @@ ASTKEYWORD_UNRESERVED KEYWORD_UNRESERVED (): {} //| <NOMINIMIZE> //| <NOMINVALUE> //| <NOMONITORING> -//| <NONE> +| <NONE> //| <NOORDER> //| <NOOVERRIDE> //| <NOPARALLEL> @@ -4489,7 +5198,7 @@ ASTKEYWORD_UNRESERVED KEYWORD_UNRESERVED (): {} //| <NOTHING> //| <NOTIFICATION> //| <NOVALIDATE> -//| <NULLS> +| <NULLS> //| <NUM_INDEX_KEYS> | <NUMERIC> | <NVARCHAR2> @@ -4498,6 +5207,7 @@ ASTKEYWORD_UNRESERVED KEYWORD_UNRESERVED (): {} //| <OBJNO_REUSE> //| <OFF> //| <OFFLINE> +| <OFFSET> | <OID> //| <OIDINDEX> | <OLD> @@ -4523,7 +5233,7 @@ ASTKEYWORD_UNRESERVED KEYWORD_UNRESERVED (): {} | <ORGANIZATION> | <OTHERS> //| <OUT_OF_LINE> -//| <OUTER> +| <OUTER> //| <OUTLINE> //| <OUTLINE_LEAF> //| <OVER> @@ -4560,7 +5270,7 @@ ASTKEYWORD_UNRESERVED KEYWORD_UNRESERVED (): {} //| <PCTTHRESHOLD> //| <PCTUSED> //| <PCTVERSION> -//| <PERCENT> +| <PERCENT> //| <PERFORMANCE> //| <PERMANENT> //| <PFILE> @@ -4588,7 +5298,7 @@ ASTKEYWORD_UNRESERVED KEYWORD_UNRESERVED (): {} //| <PRESENT> | <PRESERVE> //| <PRESERVE_OID> -//| <PRIMARY> +| <PRIMARY> | <PRINT> //| <PRIVATE> //| <PRIVATE_SGA> @@ -4638,7 +5348,7 @@ ASTKEYWORD_UNRESERVED KEYWORD_UNRESERVED (): {} //| <REF_CASCADE_CURSOR> //| <REFERENCE> //| <REFERENCED> -//| <REFERENCES> +| <REFERENCES> //| <REFERENCING> //| <REFRESH> //| <REGEXP_LIKE> @@ -4678,7 +5388,7 @@ ASTKEYWORD_UNRESERVED KEYWORD_UNRESERVED (): {} //| <ROLES> | <ROLLBACK> //| <ROLLING> -//| <ROLLUP> +| <ROLLUP> | <ROW> //| <ROW_LENGTH> //| <ROWDEPENDENCIES> @@ -4727,14 +5437,16 @@ ASTKEYWORD_UNRESERVED KEYWORD_UNRESERVED (): {} //| <SESSIONTZNAME> | <SET> //SET is defined as a reserved word but is used in "SYS"."DBMS_RESULT_CACHE_API" as a function name and as a Pragma parameter //| <SET_TO_JOIN> -//| <SETS> +| <SETS> //| <SETTINGS> //| <SEVERE> +| <SHARDED> | <SHARED> +| <SHARING> //| <SHARED_POOL> //| <SHRINK> | <SHUTDOWN> -//| <SIBLINGS> +| <SIBLINGS> //| <SID> //| <SIMPLE> //| <SINGLE> @@ -4745,7 +5457,7 @@ ASTKEYWORD_UNRESERVED KEYWORD_UNRESERVED (): {} //| <SKIP_UNUSABLE_INDEXES> //| <SMALLFILE> //| <SNAPSHOT> -//| <SOME> +| <SOME> //| <SORT> //| <SOURCE> | <SPACE> @@ -4820,6 +5532,7 @@ ASTKEYWORD_UNRESERVED KEYWORD_UNRESERVED (): {} //| <THE> //| <THREAD> //| <THROUGH> +| <TIES> | <TIME> //| <TIME_ZONE> //| <TIMEOUT> @@ -4969,7 +5682,7 @@ ASTID ID(): {} //20120501 | <DEFINER> | <SERIALLY_REUSABLE> | <RESTRICT_REFERENCES> | <EXCEPTION_INIT> | <AUTONOMOUS_TRANSACTION> // | <LANGUAGE> - | <ALL> //RESERVED WORD + //| <ALL> //RESERVED WORD | <ALTER> //SYNTAX //RESERVED WORD | <AND> //SYNTAX //RESERVED WORD | <ANY> //RESERVED WORD @@ -4984,7 +5697,7 @@ ASTID ID(): {} | <BODY> //SYNTAX | <BOOLEAN> //201020430 | <BULK> - | <BY> //RESERVED WORD + //| <BY> //RESERVED WORD //201020430 | <BYTE> | <CASE> //SYNTAX | <CHAR> //RESERVED WPRDS @@ -5005,7 +5718,7 @@ ASTID ID(): {} | <_DEFAULT> //RESERVED WORD | <DELETE> //RESERVED WORD | <DESC> //RESERVED WORD - | <DISTINCT> //RESERVED WORD + //| <DISTINCT> //RESERVED WORD | <DO> | <DROP> //RESERVED WORD | <ELSE> //SYNTAX //RESERVED WORD @@ -5026,7 +5739,7 @@ ASTID ID(): {} // <COMMIT> | <FUNCTION> | // this causes bug 643043 Procedure w/o params appears as variable | <GOTO> //SYNTAX - | <GROUP> //RESERVED WORD + //| <GROUP> //RESERVED WORD | <HAVING> //RESERVED WORD //20120501 | <HEAP> | <IF> //SYNTAX @@ -5062,7 +5775,7 @@ ASTID ID(): {} | <NUMBER_BASE> | <OCIROWID> | <OF> //RESERVED WORD - | <ON> //RESERVED WORD + //| <ON> //RESERVED WORD //20120501 | <OPAQUE> | <BFILE_BASE> | <BLOB_BASE> | @@ -5072,7 +5785,7 @@ ASTID ID(): {} //20120501 | <OPEN> | <OPERATOR> | <OPTION> | <OR> //SYNTAX //RESERVED WORD - | <ORDER> //RESERVED WORD + //| <ORDER> //RESERVED WORD //20120501 | <ORGANIZATION> //| <OTHERS> | <OUT> //20120501 | <PACKAGE> //SYNTAX diff --git a/pmd-plsql/pom.xml b/pmd-plsql/pom.xml index f9d44d975a5..c8dfef23f2f 100644 --- a/pmd-plsql/pom.xml +++ b/pmd-plsql/pom.xml @@ -7,13 +7,9 @@ <parent> <groupId>net.sourceforge.pmd</groupId> <artifactId>pmd</artifactId> - <version>6.0.0-SNAPSHOT</version> + <version>6.10.0-SNAPSHOT</version> </parent> - <properties> - <config.basedir>${basedir}/../pmd-core</config.basedir> - </properties> - <build> <resources> <resource> @@ -108,7 +104,20 @@ <groupId>net.sourceforge.pmd</groupId> <artifactId>pmd-core</artifactId> </dependency> + <dependency> + <groupId>commons-io</groupId> + <artifactId>commons-io</artifactId> + </dependency> + <dependency> + <groupId>net.sourceforge.saxon</groupId> + <artifactId>saxon</artifactId> + </dependency> + <dependency> + <groupId>junit</groupId> + <artifactId>junit</artifactId> + <scope>test</scope> + </dependency> <dependency> <groupId>net.sourceforge.pmd</groupId> <artifactId>pmd-test</artifactId> diff --git a/pmd-plsql/src/main/ant/alljavacc.xml b/pmd-plsql/src/main/ant/alljavacc.xml index 2cb7679a0f0..49ab2913a6d 100644 --- a/pmd-plsql/src/main/ant/alljavacc.xml +++ b/pmd-plsql/src/main/ant/alljavacc.xml @@ -47,15 +47,23 @@ <delete file="${target}/net/sourceforge/pmd/lang/plsql/ast/ASTDatatype.java" /> <delete file="${target}/net/sourceforge/pmd/lang/plsql/ast/ASTFormalParameter.java" /> <delete file="${target}/net/sourceforge/pmd/lang/plsql/ast/ASTIfStatement.java" /> + <delete file="${target}/net/sourceforge/pmd/lang/plsql/ast/ASTInnerCrossJoinClause.java" /> <delete file="${target}/net/sourceforge/pmd/lang/plsql/ast/ASTInput.java" /> <delete file="${target}/net/sourceforge/pmd/lang/plsql/ast/ASTMethodDeclaration.java" /> <delete file="${target}/net/sourceforge/pmd/lang/plsql/ast/ASTMethodDeclarator.java" /> <delete file="${target}/net/sourceforge/pmd/lang/plsql/ast/ASTName.java" /> + <delete file="${target}/net/sourceforge/pmd/lang/plsql/ast/ASTOuterJoinClause.java" /> + <delete file="${target}/net/sourceforge/pmd/lang/plsql/ast/ASTOuterJoinType.java" /> + <delete file="${target}/net/sourceforge/pmd/lang/plsql/ast/ASTOutOfLineConstraint.java" /> <delete file="${target}/net/sourceforge/pmd/lang/plsql/ast/ASTPackageBody.java" /> <delete file="${target}/net/sourceforge/pmd/lang/plsql/ast/ASTPackageSpecification.java" /> + <delete file="${target}/net/sourceforge/pmd/lang/plsql/ast/ASTQueryBlock.java" /> <delete file="${target}/net/sourceforge/pmd/lang/plsql/ast/ASTPrimaryPrefix.java" /> <delete file="${target}/net/sourceforge/pmd/lang/plsql/ast/ASTPrimarySuffix.java" /> <delete file="${target}/net/sourceforge/pmd/lang/plsql/ast/ASTProgramUnit.java" /> + <delete file="${target}/net/sourceforge/pmd/lang/plsql/ast/ASTSelectIntoStatement.java" /> + <delete file="${target}/net/sourceforge/pmd/lang/plsql/ast/ASTSelectStatement.java" /> + <delete file="${target}/net/sourceforge/pmd/lang/plsql/ast/ASTSubqueryOperation.java" /> <delete file="${target}/net/sourceforge/pmd/lang/plsql/ast/ASTTriggerTimingPointSection.java" /> <delete file="${target}/net/sourceforge/pmd/lang/plsql/ast/ASTTriggerUnit.java" /> <delete file="${target}/net/sourceforge/pmd/lang/plsql/ast/ASTTypeMethod.java" /> @@ -78,5 +86,55 @@ public class]]></replacevalue> token="extends Exception" value="extends net.sourceforge.pmd.lang.ast.ParseException" /> + <replace file="${target}/net/sourceforge/pmd/lang/plsql/ast/Token.java"> + <replacetoken>public class Token implements java.io.Serializable</replacetoken> + <replacevalue><![CDATA[import net.sourceforge.pmd.lang.ast.GenericToken; + +public class Token implements GenericToken, java.io.Serializable]]></replacevalue> + </replace> + + <!--Add implementation methods of GenericToken--> + <replace file="${target}/net/sourceforge/pmd/lang/plsql/ast/Token.java"> + <replacetoken>public Token specialToken;</replacetoken> + <replacevalue><![CDATA[public Token specialToken; + + @Override + public GenericToken getNext() { + return next; + } + + @Override + public GenericToken getPreviousComment() { + return specialToken; + } + + @Override + public String getImage() { + return image; + } + + @Override + public int getBeginLine() { + return beginLine; + } + + @Override + public int getEndLine() { + return endLine; + } + + @Override + public int getBeginColumn() { + return beginColumn; + } + + @Override + public int getEndColumn() { + return endColumn; + } + +]]></replacevalue> + </replace> + </target> </project> diff --git a/pmd-plsql/src/main/java/net/sourceforge/pmd/cpd/PLSQLTokenizer.java b/pmd-plsql/src/main/java/net/sourceforge/pmd/cpd/PLSQLTokenizer.java index 711b4305dd4..74cfb32a410 100644 --- a/pmd-plsql/src/main/java/net/sourceforge/pmd/cpd/PLSQLTokenizer.java +++ b/pmd-plsql/src/main/java/net/sourceforge/pmd/cpd/PLSQLTokenizer.java @@ -9,19 +9,21 @@ import java.util.logging.Level; import java.util.logging.Logger; -import net.sourceforge.pmd.lang.ast.SimpleCharStream; +import net.sourceforge.pmd.cpd.token.JavaCCTokenFilter; +import net.sourceforge.pmd.cpd.token.TokenFilter; +import net.sourceforge.pmd.lang.plsql.PLSQLTokenManager; import net.sourceforge.pmd.lang.plsql.ast.PLSQLParserConstants; -import net.sourceforge.pmd.lang.plsql.ast.PLSQLParserTokenManager; import net.sourceforge.pmd.lang.plsql.ast.Token; public class PLSQLTokenizer implements Tokenizer { private static final Logger LOGGER = Logger.getLogger(PLSQLTokenizer.class.getName()); + // This is actually useless, the comments are special tokens, never taken into account by CPD + @Deprecated public static final String IGNORE_COMMENTS = "ignore_comments"; public static final String IGNORE_IDENTIFIERS = "ignore_identifiers"; public static final String IGNORE_LITERALS = "ignore_literals"; - private boolean ignoreComments; private boolean ignoreIdentifiers; private boolean ignoreLiterals; @@ -32,13 +34,13 @@ public void setProperties(Properties properties) { * interested in comment variation, so we shall default ignoreComments * to true */ - ignoreComments = Boolean.parseBoolean(properties.getProperty(IGNORE_COMMENTS, "true")); ignoreIdentifiers = Boolean.parseBoolean(properties.getProperty(IGNORE_IDENTIFIERS, "false")); ignoreLiterals = Boolean.parseBoolean(properties.getProperty(IGNORE_LITERALS, "false")); } + @Deprecated public void setIgnoreComments(boolean ignore) { - this.ignoreComments = ignore; + // This is actually useless, the comments are special tokens, never taken into account by CPD } public void setIgnoreLiterals(boolean ignore) { @@ -65,7 +67,6 @@ public void tokenize(SourceCode sourceCode, Tokens tokenEntries) { long addedTokens = 0; if (LOGGER.isLoggable(Level.FINE)) { - LOGGER.fine("PLSQLTokenizer: ignoreComments==" + ignoreComments); LOGGER.fine("PLSQLTokenizer: ignoreIdentifiers==" + ignoreIdentifiers); LOGGER.fine("PLSQLTokenizer: ignoreLiterals==" + ignoreLiterals); } @@ -73,21 +74,12 @@ public void tokenize(SourceCode sourceCode, Tokens tokenEntries) { String fileName = sourceCode.getFileName(); StringBuilder sb = sourceCode.getCodeBuffer(); - PLSQLParserTokenManager tokenMgr = new PLSQLParserTokenManager( - new SimpleCharStream(new StringReader(sb.toString()))); - Token currentToken = tokenMgr.getNextToken(); - while (currentToken.image.length() > 0) { + TokenFilter tokenFilter = new JavaCCTokenFilter(new PLSQLTokenManager(new StringReader(sb.toString()))); + Token currentToken = (Token) tokenFilter.getNextToken(); + while (currentToken != null) { String image = currentToken.image; encounteredTokens++; - if (ignoreComments && (currentToken.kind == PLSQLParserConstants.SINGLE_LINE_COMMENT - || currentToken.kind == PLSQLParserConstants.MULTI_LINE_COMMENT - || currentToken.kind == PLSQLParserConstants.FORMAL_COMMENT - || currentToken.kind == PLSQLParserConstants.COMMENT - || currentToken.kind == PLSQLParserConstants.IN_MULTI_LINE_COMMENT - || currentToken.kind == PLSQLParserConstants.IN_FORMAL_COMMENT)) { - image = String.valueOf(currentToken.kind); - } if (ignoreIdentifiers && currentToken.kind == PLSQLParserConstants.IDENTIFIER) { image = String.valueOf(currentToken.kind); @@ -104,7 +96,7 @@ public void tokenize(SourceCode sourceCode, Tokens tokenEntries) { tokenEntries.add(new TokenEntry(image, fileName, currentToken.beginLine)); addedTokens++; - currentToken = tokenMgr.getNextToken(); + currentToken = (Token) tokenFilter.getNextToken(); } tokenEntries.add(TokenEntry.getEOF()); if (LOGGER.isLoggable(Level.FINE)) { diff --git a/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/PLSQLDataFlowHandler.java b/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/PLSQLDataFlowHandler.java index 5c65ec32ecf..8f61ed7a516 100644 --- a/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/PLSQLDataFlowHandler.java +++ b/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/PLSQLDataFlowHandler.java @@ -13,10 +13,12 @@ import net.sourceforge.pmd.lang.plsql.dfa.PLSQLDataFlowNode; public class PLSQLDataFlowHandler implements DataFlowHandler { + @Override public DataFlowNode createDataFlowNode(List<DataFlowNode> dataFlow, Node node) { return new PLSQLDataFlowNode(dataFlow, node); } + @Override public Class<ASTLabelledStatement> getLabelStatementNodeClass() { return ASTLabelledStatement.class; } diff --git a/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/PLSQLHandler.java b/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/PLSQLHandler.java index a4672729210..2b2de68d98f 100644 --- a/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/PLSQLHandler.java +++ b/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/PLSQLHandler.java @@ -6,8 +6,6 @@ import java.io.Writer; -import org.jaxen.Navigator; - import net.sourceforge.pmd.lang.AbstractLanguageVersionHandler; import net.sourceforge.pmd.lang.DataFlowHandler; import net.sourceforge.pmd.lang.Parser; @@ -15,7 +13,7 @@ import net.sourceforge.pmd.lang.VisitorStarter; import net.sourceforge.pmd.lang.XPathHandler; import net.sourceforge.pmd.lang.ast.Node; -import net.sourceforge.pmd.lang.ast.xpath.DocumentNavigator; +import net.sourceforge.pmd.lang.ast.xpath.DefaultASTXPathHandler; import net.sourceforge.pmd.lang.dfa.DFAGraphRule; import net.sourceforge.pmd.lang.plsql.ast.ASTInput; import net.sourceforge.pmd.lang.plsql.ast.DumpFacade; @@ -26,8 +24,6 @@ import net.sourceforge.pmd.lang.plsql.symboltable.SymbolFacade; import net.sourceforge.pmd.lang.rule.RuleViolationFactory; -import net.sf.saxon.sxpath.IndependentContext; - /** * Implementation of LanguageVersionHandler for the PLSQL AST. It uses anonymous * classes as adapters of the visitors to the VisitorStarter interface. @@ -36,10 +32,12 @@ */ public class PLSQLHandler extends AbstractLanguageVersionHandler { + @Override public Parser getParser(ParserOptions parserOptions) { return new PLSQLParser(parserOptions); } + @Override public RuleViolationFactory getRuleViolationFactory() { return PLSQLRuleViolationFactory.INSTANCE; } @@ -57,6 +55,7 @@ public DataFlowHandler getDataFlowHandler() { @Override public VisitorStarter getDataFlowFacade() { return new VisitorStarter() { + @Override public void start(Node rootNode) { new DataFlowFacade().initializeWith(getDataFlowHandler(), (ASTInput) rootNode); } @@ -66,6 +65,7 @@ public void start(Node rootNode) { @Override public VisitorStarter getSymbolFacade() { return new VisitorStarter() { + @Override public void start(Node rootNode) { new SymbolFacade().initializeWith((ASTInput) rootNode); } @@ -75,27 +75,18 @@ public void start(Node rootNode) { @Override public VisitorStarter getDumpFacade(final Writer writer, final String prefix, final boolean recurse) { return new VisitorStarter() { + @Override public void start(Node rootNode) { new DumpFacade().initializeWith(writer, prefix, recurse, (PLSQLNode) rootNode); } }; } - @Override /** * Return minimal XPathHandler to cope with Jaxen XPath Rules. */ + @Override public XPathHandler getXPathHandler() { - return new XPathHandler() { - public void initialize() { - } - - public void initialize(IndependentContext context) { - } - - public Navigator getNavigator() { - return new DocumentNavigator(); - } - }; + return new DefaultASTXPathHandler(); } } diff --git a/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/PLSQLParser.java b/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/PLSQLParser.java index d828abd9477..2ce2ffea8b7 100644 --- a/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/PLSQLParser.java +++ b/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/PLSQLParser.java @@ -35,19 +35,21 @@ public TokenManager createTokenManager(Reader source) { protected net.sourceforge.pmd.lang.plsql.ast.PLSQLParser createPLSQLParser(Reader source) throws ParseException { Reader in = IOUtil.skipBOM(source); // Wrapped PLSQL AST Parser - net.sourceforge.pmd.lang.plsql.ast.PLSQLParser parser = new net.sourceforge.pmd.lang.plsql.ast.PLSQLParser(in); - return parser; + return new net.sourceforge.pmd.lang.plsql.ast.PLSQLParser(in); } + @Override public boolean canParse() { return true; } + @Override public Node parse(String fileName, Reader source) throws ParseException { AbstractTokenManager.setFileName(fileName); return createPLSQLParser(source).Input(); } + @Override public Map<Integer, String> getSuppressMap() { return new HashMap<>(); // FIXME } diff --git a/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/PLSQLTokenManager.java b/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/PLSQLTokenManager.java index 047d8285b2a..e07ee37d392 100644 --- a/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/PLSQLTokenManager.java +++ b/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/PLSQLTokenManager.java @@ -20,10 +20,12 @@ public PLSQLTokenManager(Reader source) { tokenManager = new PLSQLParserTokenManager(new SimpleCharStream(source)); } + @Override public Object getNextToken() { return tokenManager.getNextToken(); } + @Override public void setFileName(String fileName) { PLSQLParserTokenManager.setFileName(fileName); } diff --git a/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/ast/ASTArguments.java b/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/ast/ASTArguments.java index a725b25c29a..2ae20d49f62 100644 --- a/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/ast/ASTArguments.java +++ b/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/ast/ASTArguments.java @@ -17,6 +17,7 @@ public ASTArguments(PLSQLParser p, int id) { } /** Accept the visitor. **/ + @Override public Object jjtAccept(PLSQLParserVisitor visitor, Object data) { return visitor.visit(this, data); } diff --git a/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/ast/ASTDatatype.java b/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/ast/ASTDatatype.java index f8854162d31..4b377a8b319 100644 --- a/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/ast/ASTDatatype.java +++ b/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/ast/ASTDatatype.java @@ -17,6 +17,7 @@ public ASTDatatype(PLSQLParser p, int id) { } /** Accept the visitor. **/ + @Override public Object jjtAccept(PLSQLParserVisitor visitor, Object data) { return visitor.visit(this, data); } diff --git a/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/ast/ASTFormalParameter.java b/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/ast/ASTFormalParameter.java index 1803fba8071..71665221904 100644 --- a/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/ast/ASTFormalParameter.java +++ b/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/ast/ASTFormalParameter.java @@ -17,6 +17,7 @@ public ASTFormalParameter(PLSQLParser p, int id) { } /** Accept the visitor. **/ + @Override public Object jjtAccept(PLSQLParserVisitor visitor, Object data) { return visitor.visit(this, data); } diff --git a/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/ast/ASTIfStatement.java b/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/ast/ASTIfStatement.java index 5f4f30e054f..0b31e653641 100644 --- a/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/ast/ASTIfStatement.java +++ b/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/ast/ASTIfStatement.java @@ -27,6 +27,7 @@ public boolean hasElse() { } /** Accept the visitor. **/ + @Override public Object jjtAccept(PLSQLParserVisitor visitor, Object data) { return visitor.visit(this, data); } diff --git a/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/ast/ASTInnerCrossJoinClause.java b/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/ast/ASTInnerCrossJoinClause.java new file mode 100644 index 00000000000..f6ae6672079 --- /dev/null +++ b/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/ast/ASTInnerCrossJoinClause.java @@ -0,0 +1,46 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +/* Generated By:JJTree: Do not edit this line. ASTInnerCrossJoinClause.java Version 4.3 */ +/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=true,VISITOR=true,TRACK_TOKENS=false,NODE_PREFIX=AST,NODE_EXTENDS=,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ + +package net.sourceforge.pmd.lang.plsql.ast; + +public class ASTInnerCrossJoinClause extends net.sourceforge.pmd.lang.plsql.ast.AbstractPLSQLNode { + private boolean cross; + private boolean natural; + + public ASTInnerCrossJoinClause(int id) { + super(id); + } + + public ASTInnerCrossJoinClause(PLSQLParser p, int id) { + super(p, id); + } + + public boolean isCross() { + return cross; + } + + public boolean isNatural() { + return natural; + } + + void setCross(boolean cross) { + this.cross = cross; + } + + void setNatural(boolean natural) { + this.natural = natural; + } + + @Override + public Object jjtAccept(PLSQLParserVisitor visitor, Object data) { + return visitor.visit(this, data); + } +} +/* + * JavaCC - OriginalChecksum=b723eab15e6116e51540164aa27c0f7f (do not edit this + * line) + */ diff --git a/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/ast/ASTInput.java b/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/ast/ASTInput.java index 5cad91d7602..a2e7f11f294 100644 --- a/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/ast/ASTInput.java +++ b/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/ast/ASTInput.java @@ -18,6 +18,7 @@ public ASTInput(PLSQLParser p, int id) { } /** Accept the visitor. **/ + @Override public Object jjtAccept(PLSQLParserVisitor visitor, Object data) { return visitor.visit(this, data); } diff --git a/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/ast/ASTMethodDeclaration.java b/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/ast/ASTMethodDeclaration.java index 3c1e86db5f0..e094dab750e 100644 --- a/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/ast/ASTMethodDeclaration.java +++ b/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/ast/ASTMethodDeclaration.java @@ -17,6 +17,7 @@ public ASTMethodDeclaration(PLSQLParser p, int id) { } /** Accept the visitor. **/ + @Override public Object jjtAccept(PLSQLParserVisitor visitor, Object data) { return visitor.visit(this, data); } diff --git a/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/ast/ASTMethodDeclarator.java b/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/ast/ASTMethodDeclarator.java index be546c36d35..fccaf89874e 100644 --- a/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/ast/ASTMethodDeclarator.java +++ b/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/ast/ASTMethodDeclarator.java @@ -17,6 +17,7 @@ public ASTMethodDeclarator(PLSQLParser p, int id) { } /** Accept the visitor. **/ + @Override public Object jjtAccept(PLSQLParserVisitor visitor, Object data) { return visitor.visit(this, data); } diff --git a/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/ast/ASTName.java b/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/ast/ASTName.java index 18d67945bf8..32633fbb1dd 100644 --- a/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/ast/ASTName.java +++ b/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/ast/ASTName.java @@ -19,6 +19,7 @@ public ASTName(PLSQLParser p, int id) { } /** Accept the visitor. **/ + @Override public Object jjtAccept(PLSQLParserVisitor visitor, Object data) { return visitor.visit(this, data); } diff --git a/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/ast/ASTOutOfLineConstraint.java b/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/ast/ASTOutOfLineConstraint.java new file mode 100644 index 00000000000..6b8a826b2d3 --- /dev/null +++ b/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/ast/ASTOutOfLineConstraint.java @@ -0,0 +1,51 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +/* Generated By:JJTree: Do not edit this line. ASTOutOfLineConstraint.java Version 4.3 */ +/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=true,VISITOR=true,TRACK_TOKENS=false,NODE_PREFIX=AST,NODE_EXTENDS=,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ + +package net.sourceforge.pmd.lang.plsql.ast; + +public class ASTOutOfLineConstraint extends net.sourceforge.pmd.lang.plsql.ast.AbstractPLSQLNode { + private ConstraintType type; + + public ASTOutOfLineConstraint(int id) { + super(id); + } + + public ASTOutOfLineConstraint(PLSQLParser p, int id) { + super(p, id); + } + + void setType(ConstraintType type) { + this.type = type; + } + + public ConstraintType getType() { + return type; + } + + public boolean isUnique() { + return type == ConstraintType.UNIQUE; + } + + public boolean isPrimaryKey() { + return type == ConstraintType.PRIMARY; + } + + public boolean isForeignKey() { + return type == ConstraintType.FOREIGN; + } + + public boolean isCheck() { + return type == ConstraintType.CHECK; + } + + /** Accept the visitor. **/ + @Override + public Object jjtAccept(PLSQLParserVisitor visitor, Object data) { + return visitor.visit(this, data); + } +} +/* JavaCC - OriginalChecksum=acc724ff05131b35dbc2c287ce89725d (do not edit this line) */ diff --git a/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/ast/ASTOuterJoinClause.java b/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/ast/ASTOuterJoinClause.java new file mode 100644 index 00000000000..82c7e9b05ae --- /dev/null +++ b/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/ast/ASTOuterJoinClause.java @@ -0,0 +1,37 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +/* Generated By:JJTree: Do not edit this line. ASTOuterJoinClause.java Version 4.3 */ +/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=true,VISITOR=true,TRACK_TOKENS=false,NODE_PREFIX=AST,NODE_EXTENDS=,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ + +package net.sourceforge.pmd.lang.plsql.ast; + +public class ASTOuterJoinClause extends net.sourceforge.pmd.lang.plsql.ast.AbstractPLSQLNode { + private boolean natural; + + public ASTOuterJoinClause(int id) { + super(id); + } + + public ASTOuterJoinClause(PLSQLParser p, int id) { + super(p, id); + } + + public boolean isNatural() { + return natural; + } + + void setNatural(boolean natural) { + this.natural = natural; + } + + @Override + public Object jjtAccept(PLSQLParserVisitor visitor, Object data) { + return visitor.visit(this, data); + } +} +/* + * JavaCC - OriginalChecksum=198b19071abda6c9f27712e8d645cf9d (do not edit this + * line) + */ diff --git a/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/ast/ASTOuterJoinType.java b/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/ast/ASTOuterJoinType.java new file mode 100644 index 00000000000..9576bfd58b4 --- /dev/null +++ b/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/ast/ASTOuterJoinType.java @@ -0,0 +1,56 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +/* Generated By:JJTree: Do not edit this line. ASTOuterJoinType.java Version 4.3 */ +/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=true,VISITOR=true,TRACK_TOKENS=false,NODE_PREFIX=AST,NODE_EXTENDS=,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ + +package net.sourceforge.pmd.lang.plsql.ast; + +public class ASTOuterJoinType extends net.sourceforge.pmd.lang.plsql.ast.AbstractPLSQLNode { + private Type type; + + public ASTOuterJoinType(int id) { + super(id); + } + + public ASTOuterJoinType(PLSQLParser p, int id) { + super(p, id); + } + + void setType(Type type) { + this.type = type; + } + + public Type getType() { + return type; + } + + @Override + public String getImage() { + return String.valueOf(type); + } + + public boolean isLeft() { + return type == Type.LEFT; + } + + public boolean isRight() { + return type == Type.RIGHT; + } + + public boolean isFull() { + return type == Type.FULL; + } + + @Override + public Object jjtAccept(PLSQLParserVisitor visitor, Object data) { + return visitor.visit(this, data); + } + + public enum Type { FULL, LEFT, RIGHT } +} +/* + * JavaCC - OriginalChecksum=a9d3d0c7e83eeffeba49ea365c782af6 (do not edit this + * line) + */ diff --git a/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/ast/ASTPackageBody.java b/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/ast/ASTPackageBody.java index c22a2cc946d..9b3a015f514 100644 --- a/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/ast/ASTPackageBody.java +++ b/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/ast/ASTPackageBody.java @@ -17,13 +17,14 @@ public ASTPackageBody(PLSQLParser p, int id) { } /** Accept the visitor. **/ + @Override public Object jjtAccept(PLSQLParserVisitor visitor, Object data) { return visitor.visit(this, data); } /** * Gets the name of the Oracle Object. - * + * * @return a String representing the name of the Oracle Object */ @Override diff --git a/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/ast/ASTPackageSpecification.java b/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/ast/ASTPackageSpecification.java index 4d8d6fd004b..5fca76315aa 100644 --- a/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/ast/ASTPackageSpecification.java +++ b/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/ast/ASTPackageSpecification.java @@ -18,13 +18,14 @@ public ASTPackageSpecification(PLSQLParser p, int id) { } /** Accept the visitor. **/ + @Override public Object jjtAccept(PLSQLParserVisitor visitor, Object data) { return visitor.visit(this, data); } /** * Gets the name of the Oracle Object. - * + * * @return a String representing the name of the Oracle Object */ @Override diff --git a/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/ast/ASTPrimaryPrefix.java b/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/ast/ASTPrimaryPrefix.java index e09ef329abc..39f5964a7e7 100644 --- a/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/ast/ASTPrimaryPrefix.java +++ b/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/ast/ASTPrimaryPrefix.java @@ -17,6 +17,7 @@ public ASTPrimaryPrefix(PLSQLParser p, int id) { } /** Accept the visitor. **/ + @Override public Object jjtAccept(PLSQLParserVisitor visitor, Object data) { return visitor.visit(this, data); } diff --git a/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/ast/ASTPrimarySuffix.java b/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/ast/ASTPrimarySuffix.java index ea3d6d8bf99..fa31ed75b74 100644 --- a/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/ast/ASTPrimarySuffix.java +++ b/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/ast/ASTPrimarySuffix.java @@ -17,6 +17,7 @@ public ASTPrimarySuffix(PLSQLParser p, int id) { } /** Accept the visitor. **/ + @Override public Object jjtAccept(PLSQLParserVisitor visitor, Object data) { return visitor.visit(this, data); } @@ -44,7 +45,7 @@ public boolean isArguments() { * Get the number of arguments for this primary suffix. One should call * {@link #isArguments()} to see if there are arguments. If this method is * called when there are no arguments it returns <code>-1</code>. - * + * * @return A non-negative argument number when there are arguments, * <code>-1</code> otherwise. */ diff --git a/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/ast/ASTProgramUnit.java b/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/ast/ASTProgramUnit.java index c279fc3c40c..55103494047 100644 --- a/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/ast/ASTProgramUnit.java +++ b/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/ast/ASTProgramUnit.java @@ -19,6 +19,7 @@ public ASTProgramUnit(PLSQLParser p, int id) { } /** Accept the visitor. **/ + @Override public Object jjtAccept(PLSQLParserVisitor visitor, Object data) { return visitor.visit(this, data); } diff --git a/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/ast/ASTQueryBlock.java b/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/ast/ASTQueryBlock.java new file mode 100644 index 00000000000..e1f78a0ca1f --- /dev/null +++ b/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/ast/ASTQueryBlock.java @@ -0,0 +1,25 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +/* Generated By:JJTree: Do not edit this line. ASTQueryBlock.java Version 4.3 */ +/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=true,VISITOR=true,TRACK_TOKENS=false,NODE_PREFIX=AST,NODE_EXTENDS=,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ + +package net.sourceforge.pmd.lang.plsql.ast; + +public class ASTQueryBlock extends AbstractSelectStatement { + public ASTQueryBlock(int id) { + super(id); + } + + public ASTQueryBlock(PLSQLParser p, int id) { + super(p, id); + } + + /** Accept the visitor. **/ + @Override + public Object jjtAccept(PLSQLParserVisitor visitor, Object data) { + return visitor.visit(this, data); + } +} +/* JavaCC - OriginalChecksum=135c003dc97f05b5cd8eee33c60c450b (do not edit this line) */ diff --git a/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/ast/ASTSelectIntoStatement.java b/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/ast/ASTSelectIntoStatement.java new file mode 100644 index 00000000000..435380c2892 --- /dev/null +++ b/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/ast/ASTSelectIntoStatement.java @@ -0,0 +1,27 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +/* Generated By:JJTree: Do not edit this line. ASTSelectIntoStatement.java Version 4.3 */ +/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=true,VISITOR=true,TRACK_TOKENS=false,NODE_PREFIX=AST,NODE_EXTENDS=,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ + +package net.sourceforge.pmd.lang.plsql.ast; + +public class ASTSelectIntoStatement extends AbstractSelectStatement { + public ASTSelectIntoStatement(int id) { + super(id); + } + + public ASTSelectIntoStatement(PLSQLParser p, int id) { + super(p, id); + } + + @Override + public Object jjtAccept(PLSQLParserVisitor visitor, Object data) { + return visitor.visit(this, data); + } +} +/* + * JavaCC - OriginalChecksum=4c9f978ec55212d4281827784416a324 (do not edit this + * line) + */ diff --git a/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/ast/ASTSelectStatement.java b/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/ast/ASTSelectStatement.java new file mode 100644 index 00000000000..e6bee8060c1 --- /dev/null +++ b/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/ast/ASTSelectStatement.java @@ -0,0 +1,25 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +/* Generated By:JJTree: Do not edit this line. ASTSelectStatement.java Version 4.3 */ +/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=true,VISITOR=true,TRACK_TOKENS=false,NODE_PREFIX=AST,NODE_EXTENDS=,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ + +package net.sourceforge.pmd.lang.plsql.ast; + +public class ASTSelectStatement extends AbstractSelectStatement { + public ASTSelectStatement(int id) { + super(id); + } + + public ASTSelectStatement(PLSQLParser p, int id) { + super(p, id); + } + + /** Accept the visitor. **/ + @Override + public Object jjtAccept(PLSQLParserVisitor visitor, Object data) { + return visitor.visit(this, data); + } +} +/* JavaCC - OriginalChecksum=6a27b24d958d9811f9cd9229a7d3a3ac (do not edit this line) */ diff --git a/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/ast/ASTSubqueryOperation.java b/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/ast/ASTSubqueryOperation.java new file mode 100644 index 00000000000..33249547bb1 --- /dev/null +++ b/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/ast/ASTSubqueryOperation.java @@ -0,0 +1,64 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +/* Generated By:JJTree: Do not edit this line. ASTSubqueryOperation.java Version 4.3 */ +/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=true,VISITOR=true,TRACK_TOKENS=false,NODE_PREFIX=AST,NODE_EXTENDS=,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */ + +package net.sourceforge.pmd.lang.plsql.ast; + +public class ASTSubqueryOperation extends net.sourceforge.pmd.lang.plsql.ast.AbstractPLSQLNode { + private boolean union; + private boolean all; + private boolean intersect; + private boolean minus; + + public ASTSubqueryOperation(int id) { + super(id); + } + + public ASTSubqueryOperation(PLSQLParser p, int id) { + super(p, id); + } + + public boolean isAll() { + return all; + } + + void setAll(boolean all) { + this.all = all; + } + + public boolean isIntersect() { + return intersect; + } + + void setIntersect(boolean intersect) { + this.intersect = intersect; + } + + public boolean isMinus() { + return minus; + } + + void setMinus(boolean minus) { + this.minus = minus; + } + + public boolean isUnion() { + return union; + } + + void setUnion(boolean union) { + this.union = union; + } + + @Override + public Object jjtAccept(PLSQLParserVisitor visitor, Object data) { + return visitor.visit(this, data); + } +} +/* + * JavaCC - OriginalChecksum=7b5a6fcb0c82881a41ca30b699fb8a85 (do not edit this + * line) + */ diff --git a/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/ast/ASTTriggerTimingPointSection.java b/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/ast/ASTTriggerTimingPointSection.java index c115925b9f9..d8b4600c0a4 100644 --- a/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/ast/ASTTriggerTimingPointSection.java +++ b/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/ast/ASTTriggerTimingPointSection.java @@ -19,15 +19,17 @@ public ASTTriggerTimingPointSection(PLSQLParser p, int id) { } /** Accept the visitor. **/ + @Override public Object jjtAccept(PLSQLParserVisitor visitor, Object data) { return visitor.visit(this, data); } /** * return executable's name. - * + * * @return */ + @Override public String getMethodName() { return getImage(); } diff --git a/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/ast/ASTTriggerUnit.java b/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/ast/ASTTriggerUnit.java index ff05e8f83f5..336773bb233 100644 --- a/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/ast/ASTTriggerUnit.java +++ b/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/ast/ASTTriggerUnit.java @@ -19,6 +19,7 @@ public ASTTriggerUnit(PLSQLParser p, int id) { } /** Accept the visitor. **/ + @Override public Object jjtAccept(PLSQLParserVisitor visitor, Object data) { return visitor.visit(this, data); } @@ -28,6 +29,7 @@ public Object jjtAccept(PLSQLParserVisitor visitor, Object data) { * * @return a String representing the name of the trigger */ + @Override public String getMethodName() { return getImage(); } diff --git a/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/ast/ASTTypeMethod.java b/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/ast/ASTTypeMethod.java index f05aec2c1e8..e69627d770e 100644 --- a/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/ast/ASTTypeMethod.java +++ b/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/ast/ASTTypeMethod.java @@ -19,6 +19,7 @@ public ASTTypeMethod(PLSQLParser p, int id) { } /** Accept the visitor. **/ + @Override public Object jjtAccept(PLSQLParserVisitor visitor, Object data) { return visitor.visit(this, data); } @@ -28,6 +29,7 @@ public Object jjtAccept(PLSQLParserVisitor visitor, Object data) { * * @return a String representing the name of the method */ + @Override public String getMethodName() { ASTMethodDeclarator md = getFirstChildOfType(ASTMethodDeclarator.class); if (md != null) { diff --git a/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/ast/ASTTypeSpecification.java b/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/ast/ASTTypeSpecification.java index a1d8769774f..cd3d598de1b 100644 --- a/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/ast/ASTTypeSpecification.java +++ b/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/ast/ASTTypeSpecification.java @@ -17,6 +17,7 @@ public ASTTypeSpecification(PLSQLParser p, int id) { } /** Accept the visitor. **/ + @Override public Object jjtAccept(PLSQLParserVisitor visitor, Object data) { return visitor.visit(this, data); } diff --git a/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/ast/ASTVariableOrConstantDeclaratorId.java b/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/ast/ASTVariableOrConstantDeclaratorId.java index df694a6e20e..c7fd192a1ad 100644 --- a/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/ast/ASTVariableOrConstantDeclaratorId.java +++ b/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/ast/ASTVariableOrConstantDeclaratorId.java @@ -23,6 +23,7 @@ public ASTVariableOrConstantDeclaratorId(PLSQLParser p, int id) { } /** Accept the visitor. **/ + @Override public Object jjtAccept(PLSQLParserVisitor visitor, Object data) { return visitor.visit(this, data); } diff --git a/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/ast/AbstractPLSQLNode.java b/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/ast/AbstractPLSQLNode.java index 16415788e98..aebc9680986 100644 --- a/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/ast/AbstractPLSQLNode.java +++ b/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/ast/AbstractPLSQLNode.java @@ -23,6 +23,7 @@ public AbstractPLSQLNode(PLSQLParser p, int i) { parser = p; } + @Override public void jjtOpen() { if (beginLine == -1 && parser.token.next != null) { beginLine = parser.token.next.beginLine; @@ -30,6 +31,7 @@ public void jjtOpen() { } } + @Override public void jjtClose() { if (beginLine == -1 && (children == null || children.length == 0)) { beginColumn = parser.token.beginColumn; @@ -64,6 +66,12 @@ public Object childrenAccept(PLSQLParserVisitor visitor, Object data) { return data; } + + @Override + public String getXPathNodeName() { + return PLSQLParserTreeConstants.jjtNodeName[id]; + } + /* * You can override these two methods in subclasses of SimpleNode to * customize the way the node appears when the tree is dumped. If your @@ -71,9 +79,7 @@ public Object childrenAccept(PLSQLParserVisitor visitor, Object data) { * otherwise overriding toString() is probably all you need to do. */ - public String toString() { - return PLSQLParserTreeConstants.jjtNodeName[id]; - } + public String toString(String prefix) { return prefix + toString(); @@ -98,7 +104,7 @@ public void dump(String prefix) { /** * Return node image converted to the normal Oracle form. - * + * * <p> * Normally this is uppercase, unless the names is quoted ("name"). * </p> @@ -110,12 +116,12 @@ public String getCanonicalImage() { /** * Convert arbitrary String to normal Oracle format, under assumption that * the passed image is an Oracle name. - * + * * <p> * This a helper method for PLSQL classes dependent on SimpleNode, that * would otherwise have to import PLSQParser. * </p> - * + * * @param image * @return */ diff --git a/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/ast/AbstractSelectStatement.java b/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/ast/AbstractSelectStatement.java new file mode 100644 index 00000000000..a1ea88655bb --- /dev/null +++ b/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/ast/AbstractSelectStatement.java @@ -0,0 +1,45 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.plsql.ast; + +public abstract class AbstractSelectStatement extends AbstractPLSQLNode { + + private boolean distinct; + private boolean unique; + private boolean all; + + public AbstractSelectStatement(int i) { + super(i); + } + + public AbstractSelectStatement(PLSQLParser p, int i) { + super(p, i); + } + + protected void setDistinct(boolean distinct) { + this.distinct = true; + } + + public boolean isDistinct() { + return distinct; + } + + protected void setUnique(boolean unique) { + this.unique = unique; + } + + public boolean isUnique() { + return unique; + } + + protected void setAll(boolean all) { + this.all = all; + } + + public boolean isAll() { + return all; + } + +} diff --git a/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/ast/ConstraintType.java b/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/ast/ConstraintType.java new file mode 100644 index 00000000000..61b92059597 --- /dev/null +++ b/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/ast/ConstraintType.java @@ -0,0 +1,12 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.plsql.ast; + +public enum ConstraintType { + UNIQUE, + PRIMARY, + FOREIGN, + CHECK; +} diff --git a/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/ast/DumpFacade.java b/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/ast/DumpFacade.java index 4841521acc1..d6be0486fe9 100644 --- a/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/ast/DumpFacade.java +++ b/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/ast/DumpFacade.java @@ -45,7 +45,7 @@ private void dump(PLSQLNode node, String prefix) { writer.print(prefix); // 2) JJT Name of the Node - writer.print(node.toString()); + writer.print(node.getXPathNodeName()); // // If there are any additional details, then: diff --git a/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/ast/PLSQLNode.java b/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/ast/PLSQLNode.java index 1ac627c6ee8..c465306830a 100644 --- a/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/ast/PLSQLNode.java +++ b/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/ast/PLSQLNode.java @@ -16,6 +16,7 @@ public interface PLSQLNode extends Node, ScopedNode { /** Accept the visitor. **/ Object childrenAccept(PLSQLParserVisitor visitor, Object data); + @Override Scope getScope(); void setScope(Scope scope); diff --git a/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/ast/PLSQLParserVisitorAdapter.java b/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/ast/PLSQLParserVisitorAdapter.java index ea7a42a39fd..5f4447fd887 100644 --- a/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/ast/PLSQLParserVisitorAdapter.java +++ b/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/ast/PLSQLParserVisitorAdapter.java @@ -700,4 +700,245 @@ public Object visit(ASTIsNullCondition node, Object data) { public Object visit(ASTIsOfTypeCondition node, Object data) { return visit((PLSQLNode) node, data); } + + @Override + + public Object visit(ASTOutOfLineConstraint node, Object data) { + return visit((PLSQLNode) node, data); + } + + @Override + public Object visit(ASTSelectIntoStatement node, Object data) { + return visit((PLSQLNode) node, data); + } + + @Override + public Object visit(ASTReferencesClause node, Object data) { + return visit((PLSQLNode) node, data); + } + + @Override + public Object visit(ASTCrossOuterApplyClause node, Object data) { + return visit((PLSQLNode) node, data); + } + + @Override + public Object visit(ASTCursorForLoopStatement node, Object data) { + return visit((PLSQLNode) node, data); + } + + @Override + public Object visit(ASTInnerCrossJoinClause node, Object data) { + return visit((PLSQLNode) node, data); + } + + @Override + public Object visit(ASTJoinClause node, Object data) { + return visit((PLSQLNode) node, data); + } + + @Override + public Object visit(ASTOuterJoinClause node, Object data) { + return visit((PLSQLNode) node, data); + } + + @Override + public Object visit(ASTOuterJoinType node, Object data) { + return visit((PLSQLNode) node, data); + } + + @Override + public Object visit(ASTTableReference node, Object data) { + return visit((PLSQLNode) node, data); + } + + @Override + public Object visit(ASTSelectList node, Object data) { + return visit((PLSQLNode) node, data); + } + + @Override + public Object visit(ASTBulkCollectIntoClause node, Object data) { + return visit((PLSQLNode) node, data); + } + + @Override + public Object visit(ASTIntoClause node, Object data) { + return visit((PLSQLNode) node, data); + } + + @Override + public Object visit(ASTColumnAlias node, Object data) { + return visit((PLSQLNode) node, data); + } + + @Override + public Object visit(ASTTableAlias node, Object data) { + return visit((PLSQLNode) node, data); + } + + @Override + public Object visit(ASTCollectionName node, Object data) { + return visit((PLSQLNode) node, data); + } + + @Override + public Object visit(ASTHostArrayName node, Object data) { + return visit((PLSQLNode) node, data); + } + + @Override + public Object visit(ASTQueryBlock node, Object data) { + return visit((PLSQLNode) node, data); + } + + @Override + public Object visit(ASTSchemaName node, Object data) { + return visit((PLSQLNode) node, data); + } + + @Override + public Object visit(ASTTableName node, Object data) { + return visit((PLSQLNode) node, data); + } + + @Override + public Object visit(ASTComparisonCondition node, Object data) { + return visit((PLSQLNode) node, data); + } + + @Override + public Object visit(ASTCondition node, Object data) { + return visit((PLSQLNode) node, data); + } + + @Override + public Object visit(ASTExpressionList node, Object data) { + return visit((PLSQLNode) node, data); + } + + @Override + public Object visit(ASTWhereClause node, Object data) { + return visit((PLSQLNode) node, data); + } + + @Override + public Object visit(ASTSqlExpression node, Object data) { + return visit((PLSQLNode) node, data); + } + + @Override + public Object visit(ASTInCondition node, Object data) { + return visit((PLSQLNode) node, data); + } + + @Override + public Object visit(ASTExpressionListMultiple node, Object data) { + return visit((PLSQLNode) node, data); + } + + @Override + public Object visit(ASTExpressionListSingle node, Object data) { + return visit((PLSQLNode) node, data); + } + + @Override + public Object visit(ASTCompoundCondition node, Object data) { + return visit((PLSQLNode) node, data); + } + + @Override + public Object visit(ASTColumn node, Object data) { + return visit((PLSQLNode) node, data); + } + + @Override + public Object visit(ASTOrderByClause node, Object data) { + return visit((PLSQLNode) node, data); + } + + @Override + public Object visit(ASTRowLimitingClause node, Object data) { + return visit((PLSQLNode) node, data); + } + + @Override + public Object visit(ASTVariableName node, Object data) { + return visit((PLSQLNode) node, data); + } + + @Override + public Object visit(ASTFromClause node, Object data) { + return visit((PLSQLNode) node, data); + } + + @Override + public Object visit(ASTSubqueryOperation node, Object data) { + return visit((PLSQLNode) node, data); + } + + @Override + public Object visit(ASTQueryPartitionClause node, Object data) { + return visit((PLSQLNode) node, data); + } + + @Override + public Object visit(ASTGroupByClause node, Object data) { + return visit((PLSQLNode) node, data); + } + + @Override + public Object visit(ASTGroupingExpressionList node, Object data) { + return visit((PLSQLNode) node, data); + } + + @Override + public Object visit(ASTGroupingSetsClause node, Object data) { + return visit((PLSQLNode) node, data); + } + + @Override + public Object visit(ASTRollupCubeClause node, Object data) { + return visit((PLSQLNode) node, data); + } + + @Override + public Object visit(ASTSelectStatement node, Object data) { + return visit((PLSQLNode) node, data); + } + + @Override + public Object visit(ASTLikeCondition node, Object data) { + return visit((PLSQLNode) node, data); + } + + @Override + public Object visit(ASTBetweenCondition node, Object data) { + return visit((PLSQLNode) node, data); + } + + @Override + public Object visit(ASTFloatingPointCondition node, Object data) { + return visit((PLSQLNode) node, data); + } + + @Override + public Object visit(ASTFunctionCall node, Object data) { + return visit((PLSQLNode) node, data); + } + + @Override + public Object visit(ASTUpdateStatement node, Object data) { + return visit((PLSQLNode) node, data); + } + + @Override + public Object visit(ASTUpdateSetClause node, Object data) { + return visit((PLSQLNode) node, data); + } + + @Override + public Object visit(ASTDeleteStatement node, Object data) { + return visit((PLSQLNode) node, data); + } } diff --git a/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/dfa/DFAPLSQLGraphRule.java b/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/dfa/DFAPLSQLGraphRule.java index 0568c359330..01c5309a001 100644 --- a/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/dfa/DFAPLSQLGraphRule.java +++ b/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/dfa/DFAPLSQLGraphRule.java @@ -21,10 +21,10 @@ public class DFAPLSQLGraphRule extends AbstractPLSQLRule implements DFAGraphRule private List<DFAGraphMethod> executables; public DFAPLSQLGraphRule() { - super(); - super.setUsesDFA(); + super.setDfa(true); } + @Override public List<DFAGraphMethod> getMethods() { return this.executables; } diff --git a/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/dfa/PLSQLDataFlowNode.java b/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/dfa/PLSQLDataFlowNode.java index 8cb13e0ff59..b1c4db193d1 100644 --- a/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/dfa/PLSQLDataFlowNode.java +++ b/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/dfa/PLSQLDataFlowNode.java @@ -19,6 +19,7 @@ public PLSQLDataFlowNode(List<DataFlowNode> dataFlow, Node node) { super(dataFlow, node); } + @Override public String toString() { String res = "PLSQLDataFlowNode: line " + this.getLine() + ", (Type BitField==" + type + ")"; if (node instanceof ASTProgramUnit || node instanceof ASTTypeMethod diff --git a/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/dfa/StatementAndBraceFinder.java b/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/dfa/StatementAndBraceFinder.java index 3b63bb223fd..a584be582e2 100644 --- a/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/dfa/StatementAndBraceFinder.java +++ b/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/dfa/StatementAndBraceFinder.java @@ -767,6 +767,7 @@ public Object visit(ASTRaiseStatement node, Object data) { * The method handles the special "for" loop. It creates always an * expression node even if the loop looks like for(;;). */ + @SuppressWarnings("PMD.UnusedFormalParameter") // TODO: dfa implementation in plsql is incomplete private void addForExpressionNode(Node node, Structure dataFlow) { ASTForStatement parent = (ASTForStatement) node.jjtGetParent(); boolean hasExpressionChild = false; diff --git a/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/rule/AbstractPLSQLRule.java b/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/rule/AbstractPLSQLRule.java index 51f6450845d..3271e19c7bc 100644 --- a/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/rule/AbstractPLSQLRule.java +++ b/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/rule/AbstractPLSQLRule.java @@ -22,7 +22,7 @@ public abstract class AbstractPLSQLRule extends AbstractRule implements PLSQLPar public AbstractPLSQLRule() { super.setLanguage(LanguageRegistry.getLanguage(PLSQLLanguageModule.NAME)); // Enable Type Resolution on PLSQL Rules by default - super.setUsesTypeResolution(); + super.setTypeResolution(true); } @Override @@ -795,6 +795,246 @@ public Object visit(ASTIsNullCondition node, Object data) { return visit((PLSQLNode) node, data); } + @Override + public Object visit(ASTOutOfLineConstraint node, Object data) { + return visit((PLSQLNode) node, data); + } + + @Override + public Object visit(ASTSelectIntoStatement node, Object data) { + return visit((PLSQLNode) node, data); + } + + @Override + public Object visit(ASTReferencesClause node, Object data) { + return visit((PLSQLNode) node, data); + } + + @Override + public Object visit(ASTCrossOuterApplyClause node, Object data) { + return visit((PLSQLNode) node, data); + } + + @Override + public Object visit(ASTCursorForLoopStatement node, Object data) { + return visit((PLSQLNode) node, data); + } + + @Override + public Object visit(ASTInnerCrossJoinClause node, Object data) { + return visit((PLSQLNode) node, data); + } + + @Override + public Object visit(ASTJoinClause node, Object data) { + return visit((PLSQLNode) node, data); + } + + @Override + public Object visit(ASTOuterJoinClause node, Object data) { + return visit((PLSQLNode) node, data); + } + + @Override + public Object visit(ASTOuterJoinType node, Object data) { + return visit((PLSQLNode) node, data); + } + + @Override + public Object visit(ASTTableReference node, Object data) { + return visit((PLSQLNode) node, data); + } + + @Override + public Object visit(ASTSelectList node, Object data) { + return visit((PLSQLNode) node, data); + } + + @Override + public Object visit(ASTBulkCollectIntoClause node, Object data) { + return visit((PLSQLNode) node, data); + } + + @Override + public Object visit(ASTIntoClause node, Object data) { + return visit((PLSQLNode) node, data); + } + + @Override + public Object visit(ASTColumnAlias node, Object data) { + return visit((PLSQLNode) node, data); + } + + @Override + public Object visit(ASTTableAlias node, Object data) { + return visit((PLSQLNode) node, data); + } + + @Override + public Object visit(ASTCollectionName node, Object data) { + return visit((PLSQLNode) node, data); + } + + @Override + public Object visit(ASTHostArrayName node, Object data) { + return visit((PLSQLNode) node, data); + } + + @Override + public Object visit(ASTQueryBlock node, Object data) { + return visit((PLSQLNode) node, data); + } + + @Override + public Object visit(ASTSchemaName node, Object data) { + return visit((PLSQLNode) node, data); + } + + @Override + public Object visit(ASTTableName node, Object data) { + return visit((PLSQLNode) node, data); + } + + @Override + public Object visit(ASTComparisonCondition node, Object data) { + return visit((PLSQLNode) node, data); + } + + @Override + public Object visit(ASTCondition node, Object data) { + return visit((PLSQLNode) node, data); + } + + @Override + public Object visit(ASTExpressionList node, Object data) { + return visit((PLSQLNode) node, data); + } + + @Override + public Object visit(ASTWhereClause node, Object data) { + return visit((PLSQLNode) node, data); + } + + @Override + public Object visit(ASTSqlExpression node, Object data) { + return visit((PLSQLNode) node, data); + } + + @Override + public Object visit(ASTInCondition node, Object data) { + return visit((PLSQLNode) node, data); + } + + @Override + public Object visit(ASTExpressionListMultiple node, Object data) { + return visit((PLSQLNode) node, data); + } + + @Override + public Object visit(ASTExpressionListSingle node, Object data) { + return visit((PLSQLNode) node, data); + } + + @Override + public Object visit(ASTCompoundCondition node, Object data) { + return visit((PLSQLNode) node, data); + } + + @Override + public Object visit(ASTColumn node, Object data) { + return visit((PLSQLNode) node, data); + } + + @Override + public Object visit(ASTOrderByClause node, Object data) { + return visit((PLSQLNode) node, data); + } + + @Override + public Object visit(ASTRowLimitingClause node, Object data) { + return visit((PLSQLNode) node, data); + } + + @Override + public Object visit(ASTVariableName node, Object data) { + return visit((PLSQLNode) node, data); + } + + @Override + public Object visit(ASTFromClause node, Object data) { + return visit((PLSQLNode) node, data); + } + + @Override + public Object visit(ASTSubqueryOperation node, Object data) { + return visit((PLSQLNode) node, data); + } + + @Override + public Object visit(ASTQueryPartitionClause node, Object data) { + return visit((PLSQLNode) node, data); + } + + @Override + public Object visit(ASTGroupByClause node, Object data) { + return visit((PLSQLNode) node, data); + } + + @Override + public Object visit(ASTGroupingExpressionList node, Object data) { + return visit((PLSQLNode) node, data); + } + + @Override + public Object visit(ASTGroupingSetsClause node, Object data) { + return visit((PLSQLNode) node, data); + } + + @Override + public Object visit(ASTRollupCubeClause node, Object data) { + return visit((PLSQLNode) node, data); + } + + @Override + public Object visit(ASTSelectStatement node, Object data) { + return visit((PLSQLNode) node, data); + } + + @Override + public Object visit(ASTLikeCondition node, Object data) { + return visit((PLSQLNode) node, data); + } + + @Override + public Object visit(ASTBetweenCondition node, Object data) { + return visit((PLSQLNode) node, data); + } + + @Override + public Object visit(ASTFloatingPointCondition node, Object data) { + return visit((PLSQLNode) node, data); + } + + @Override + public Object visit(ASTFunctionCall node, Object data) { + return visit((PLSQLNode) node, data); + } + + @Override + public Object visit(ASTUpdateStatement node, Object data) { + return visit((PLSQLNode) node, data); + } + + @Override + public Object visit(ASTUpdateSetClause node, Object data) { + return visit((PLSQLNode) node, data); + } + + @Override + public Object visit(ASTDeleteStatement node, Object data) { + return visit((PLSQLNode) node, data); + } + /* * Treat all Executable Code */ diff --git a/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/rule/AbstractStatisticalPLSQLRule.java b/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/rule/AbstractStatisticalPLSQLRule.java index 4971f0e6a7d..440aaf8463d 100644 --- a/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/rule/AbstractStatisticalPLSQLRule.java +++ b/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/rule/AbstractStatisticalPLSQLRule.java @@ -16,12 +16,14 @@ public abstract class AbstractStatisticalPLSQLRule extends AbstractPLSQLRule imp private final StatisticalRuleHelper helper = new StatisticalRuleHelper(this); + @Override public void addDataPoint(DataPoint point) { helper.addDataPoint(point); } + @Override public Object[] getViolationParameters(DataPoint point) { - return null; + return new Object[0]; } @Override diff --git a/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/rule/PLSQLRuleViolationFactory.java b/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/rule/PLSQLRuleViolationFactory.java index b5cd2413234..f4adec6b2d6 100644 --- a/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/rule/PLSQLRuleViolationFactory.java +++ b/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/rule/PLSQLRuleViolationFactory.java @@ -24,6 +24,7 @@ protected RuleViolation createRuleViolation(Rule rule, RuleContext ruleContext, return new ParametricRuleViolation<>(rule, ruleContext, node, message); } + @Override protected RuleViolation createRuleViolation(Rule rule, RuleContext ruleContext, Node node, String message, int beginLine, int endLine) { ParametricRuleViolation<Node> violation = new ParametricRuleViolation<>(rule, ruleContext, node, message); diff --git a/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/rule/codesize/AbstractNcssCountRule.java b/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/rule/codesize/AbstractNcssCountRule.java deleted file mode 100644 index 01abb29d654..00000000000 --- a/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/rule/codesize/AbstractNcssCountRule.java +++ /dev/null @@ -1,212 +0,0 @@ -/** - * BSD-style license; for more info see http://pmd.sourceforge.net/license.html - */ - -package net.sourceforge.pmd.lang.plsql.rule.codesize; - -import java.util.logging.Level; -import java.util.logging.Logger; - -import net.sourceforge.pmd.lang.ast.Node; -import net.sourceforge.pmd.lang.plsql.ast.ASTCaseStatement; -import net.sourceforge.pmd.lang.plsql.ast.ASTCaseWhenClause; -import net.sourceforge.pmd.lang.plsql.ast.ASTContinueStatement; -import net.sourceforge.pmd.lang.plsql.ast.ASTElseClause; -import net.sourceforge.pmd.lang.plsql.ast.ASTElsifClause; -import net.sourceforge.pmd.lang.plsql.ast.ASTExceptionHandler; -import net.sourceforge.pmd.lang.plsql.ast.ASTExitStatement; -import net.sourceforge.pmd.lang.plsql.ast.ASTExpression; -import net.sourceforge.pmd.lang.plsql.ast.ASTForStatement; -import net.sourceforge.pmd.lang.plsql.ast.ASTGotoStatement; -import net.sourceforge.pmd.lang.plsql.ast.ASTIfStatement; -import net.sourceforge.pmd.lang.plsql.ast.ASTLabelledStatement; -import net.sourceforge.pmd.lang.plsql.ast.ASTLoopStatement; -import net.sourceforge.pmd.lang.plsql.ast.ASTRaiseStatement; -import net.sourceforge.pmd.lang.plsql.ast.ASTReturnStatement; -import net.sourceforge.pmd.lang.plsql.ast.ASTStatement; -import net.sourceforge.pmd.lang.plsql.ast.ASTWhileStatement; -import net.sourceforge.pmd.lang.plsql.ast.PLSQLNode; -import net.sourceforge.pmd.lang.plsql.rule.AbstractStatisticalPLSQLRule; -import net.sourceforge.pmd.stat.DataPoint; -import net.sourceforge.pmd.util.NumericConstants; - -/** - * Abstract superclass for NCSS counting methods. Analogous to and cribbed from - * the Java version of the rule. - */ -public abstract class AbstractNcssCountRule extends AbstractStatisticalPLSQLRule { - private static final Logger LOGGER = Logger.getLogger(AbstractNcssCountRule.class.getName()); - - private Class<?> nodeClass; - - /** - * Count the nodes of the given type using NCSS rules. - * - * @param nodeClass - * class of node to count - */ - protected AbstractNcssCountRule(Class<?> nodeClass) { - this.nodeClass = nodeClass; - if (LOGGER.isLoggable(Level.FINE)) { - LOGGER.fine("Counting for " + nodeClass.getCanonicalName()); - } - } - - @Override - public Object visit(PLSQLNode node, Object data) { - int numNodes = 0; - - for (int i = 0; i < node.jjtGetNumChildren(); i++) { - PLSQLNode n = (PLSQLNode) node.jjtGetChild(i); - Integer treeSize = (Integer) n.jjtAccept(this, data); - numNodes += treeSize.intValue(); - } - - if (LOGGER.isLoggable(Level.FINER)) { - LOGGER.finer("Checking candidate " + node.getClass().getCanonicalName() + " against target class " - + nodeClass.getCanonicalName() + " with " + numNodes + " nodes"); - } - - if (this.nodeClass.isInstance(node)) { - if (LOGGER.isLoggable(Level.FINE)) { - LOGGER.fine("Matched candidate " + node.getClass().getCanonicalName() + " against target class " - + nodeClass.getCanonicalName()); - } - // Add 1 to account for base node - numNodes++; - DataPoint point = new DataPoint(); - point.setNode(node); - point.setScore(1.0 * numNodes); - point.setMessage(getMessage()); - addDataPoint(point); - if (LOGGER.isLoggable(Level.FINE)) { - LOGGER.fine("Running score is " + point.getScore()); - } - } - - return Integer.valueOf(numNodes); - } - - /** - * Count the number of children of the given PLSQL node. Adds one to count - * the node itself. - * - * @param node - * PLSQL node having children counted - * @param data - * node data - * @return count of the number of children of the node, plus one - */ - protected Integer countNodeChildren(Node node, Object data) { - Integer nodeCount = null; - int lineCount = 0; - for (int i = 0; i < node.jjtGetNumChildren(); i++) { - nodeCount = (Integer) ((PLSQLNode) node.jjtGetChild(i)).jjtAccept(this, data); - lineCount += nodeCount.intValue(); - } - return ++lineCount; - } - - @Override - public Object visit(ASTForStatement node, Object data) { - return countNodeChildren(node, data); - } - - @Override - public Object visit(ASTLoopStatement node, Object data) { - return countNodeChildren(node, data); - } - - @Override - public Object visit(ASTIfStatement node, Object data) { - - Integer lineCount = countNodeChildren(node, data); - - return lineCount; - } - - @Override - public Object visit(ASTElsifClause node, Object data) { - - Integer lineCount = countNodeChildren(node, data); - - return lineCount; - } - - @Override - public Object visit(ASTElseClause node, Object data) { - - Integer lineCount = countNodeChildren(node, data); - - return lineCount; - } - - @Override - public Object visit(ASTWhileStatement node, Object data) { - return countNodeChildren(node, data); - } - - @Override - public Object visit(ASTExitStatement node, Object data) { - return NumericConstants.ONE; - } - - @Override - public Object visit(ASTExceptionHandler node, Object data) { - return countNodeChildren(node, data); - } - - @Override - public Object visit(ASTContinueStatement node, Object data) { - return NumericConstants.ONE; - } - - @Override - public Object visit(ASTGotoStatement node, Object data) { - return NumericConstants.ONE; - } - - @Override - public Object visit(ASTReturnStatement node, Object data) { - return countNodeChildren(node, data); - } - - @Override - public Object visit(ASTCaseStatement node, Object data) { - return countNodeChildren(node, data); - } - - @Override - public Object visit(ASTRaiseStatement node, Object data) { - return NumericConstants.ONE; - } - - @Override - public Object visit(ASTExpression node, Object data) { - - // "For" update expressions do not count as separate lines of code - if (node.jjtGetParent() instanceof ASTStatement) { - return NumericConstants.ZERO; - } - - return NumericConstants.ONE; - } - - @Override - public Object visit(ASTLabelledStatement node, Object data) { - return countNodeChildren(node, data); - } - - @Override - public Object visit(ASTCaseWhenClause node, Object data) { - return countNodeChildren(node, data); - } - - @Override - public Object[] getViolationParameters(DataPoint point) { - if (LOGGER.isLoggable(Level.FINE)) { - LOGGER.fine("Point score is " + point.getScore()); - } - return new String[] { String.valueOf((int) point.getScore()) }; - } -} diff --git a/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/rule/codesize/CyclomaticComplexityRule.java b/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/rule/codesize/CyclomaticComplexityRule.java deleted file mode 100644 index 1712855aa0f..00000000000 --- a/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/rule/codesize/CyclomaticComplexityRule.java +++ /dev/null @@ -1,405 +0,0 @@ -/** - * BSD-style license; for more info see http://pmd.sourceforge.net/license.html - */ - -package net.sourceforge.pmd.lang.plsql.rule.codesize; - -import java.util.Stack; -import java.util.logging.Level; -import java.util.logging.Logger; - -import net.sourceforge.pmd.lang.ast.Node; -import net.sourceforge.pmd.lang.plsql.ast.ASTCaseStatement; -import net.sourceforge.pmd.lang.plsql.ast.ASTCaseWhenClause; -import net.sourceforge.pmd.lang.plsql.ast.ASTConditionalOrExpression; -import net.sourceforge.pmd.lang.plsql.ast.ASTElsifClause; -import net.sourceforge.pmd.lang.plsql.ast.ASTExceptionHandler; -import net.sourceforge.pmd.lang.plsql.ast.ASTExpression; -import net.sourceforge.pmd.lang.plsql.ast.ASTForStatement; -import net.sourceforge.pmd.lang.plsql.ast.ASTIfStatement; -import net.sourceforge.pmd.lang.plsql.ast.ASTInput; -import net.sourceforge.pmd.lang.plsql.ast.ASTLoopStatement; -import net.sourceforge.pmd.lang.plsql.ast.ASTMethodDeclarator; -import net.sourceforge.pmd.lang.plsql.ast.ASTPackageBody; -import net.sourceforge.pmd.lang.plsql.ast.ASTPackageSpecification; -import net.sourceforge.pmd.lang.plsql.ast.ASTProgramUnit; -import net.sourceforge.pmd.lang.plsql.ast.ASTTriggerTimingPointSection; -import net.sourceforge.pmd.lang.plsql.ast.ASTTriggerUnit; -import net.sourceforge.pmd.lang.plsql.ast.ASTTypeMethod; -import net.sourceforge.pmd.lang.plsql.ast.ASTTypeSpecification; -import net.sourceforge.pmd.lang.plsql.ast.ASTWhileStatement; -import net.sourceforge.pmd.lang.plsql.rule.AbstractPLSQLRule; -import net.sourceforge.pmd.properties.BooleanProperty; -import net.sourceforge.pmd.properties.IntegerProperty; - -/** - * @author Donald A. Leckie, - * - * @version $Revision: 5956 $, $Date: 2008-04-04 04:59:25 -0500 (Fri, 04 Apr - * 2008) $ - * @since January 14, 2003 - */ -public class CyclomaticComplexityRule extends AbstractPLSQLRule { - private static final Logger LOGGER = Logger.getLogger(CyclomaticComplexityRule.class.getName()); - private static final String CLASS_NAME = CyclomaticComplexityRule.class.getName(); - - public static final IntegerProperty REPORT_LEVEL_DESCRIPTOR = new IntegerProperty("reportLevel", - "Cyclomatic Complexity reporting threshold", 1, 30, 10, 1.0f); - - public static final BooleanProperty SHOW_CLASSES_COMPLEXITY_DESCRIPTOR = new BooleanProperty( - "showClassesComplexity", "Add class average violations to the report", true, 2.0f); - - public static final BooleanProperty SHOW_METHODS_COMPLEXITY_DESCRIPTOR = new BooleanProperty( - "showMethodsComplexity", "Add method average violations to the report", true, 3.0f); - - private int reportLevel; - private boolean showClassesComplexity = true; - private boolean showMethodsComplexity = true; - - private static class Entry { - private Node node; - private int decisionPoints = 1; - public int highestDecisionPoints; - public int methodCount; - - private Entry(Node node) { - this.node = node; - } - - public void bumpDecisionPoints() { - decisionPoints++; - } - - public void bumpDecisionPoints(int size) { - decisionPoints += size; - } - - public int getComplexityAverage() { - return (double) methodCount == 0 ? 1 : (int) Math.rint((double) decisionPoints / (double) methodCount); - } - } - - private Stack<Entry> entryStack = new Stack<>(); - - public CyclomaticComplexityRule() { - definePropertyDescriptor(REPORT_LEVEL_DESCRIPTOR); - definePropertyDescriptor(SHOW_CLASSES_COMPLEXITY_DESCRIPTOR); - definePropertyDescriptor(SHOW_METHODS_COMPLEXITY_DESCRIPTOR); - } - - @Override - public Object visit(ASTInput node, Object data) { - LOGGER.entering(CLASS_NAME, "visit(ASTInput)"); - reportLevel = getProperty(REPORT_LEVEL_DESCRIPTOR); - showClassesComplexity = getProperty(SHOW_CLASSES_COMPLEXITY_DESCRIPTOR); - showMethodsComplexity = getProperty(SHOW_METHODS_COMPLEXITY_DESCRIPTOR); - super.visit(node, data); - LOGGER.exiting(CLASS_NAME, "visit(ASTInput)"); - return data; - } - - @Override - public Object visit(ASTElsifClause node, Object data) { - LOGGER.entering(CLASS_NAME, "visit(ASTElsifClause)"); - int boolCompIf = NPathComplexityRule.sumExpressionComplexity(node.getFirstChildOfType(ASTExpression.class)); - // If statement always has a complexity of at least 1 - boolCompIf++; - - entryStack.peek().bumpDecisionPoints(boolCompIf); - super.visit(node, data); - LOGGER.exiting(CLASS_NAME, "visit(ASTElsifClause)"); - return data; - } - - @Override - public Object visit(ASTIfStatement node, Object data) { - LOGGER.entering(CLASS_NAME, "visit(ASTIfClause)"); - int boolCompIf = NPathComplexityRule.sumExpressionComplexity(node.getFirstChildOfType(ASTExpression.class)); - // If statement always has a complexity of at least 1 - boolCompIf++; - - entryStack.peek().bumpDecisionPoints(boolCompIf); - LOGGER.exiting(CLASS_NAME, "visit(ASTIfClause)"); - super.visit(node, data); - return data; - } - - @Override - public Object visit(ASTExceptionHandler node, Object data) { - LOGGER.entering(CLASS_NAME, "visit(ASTExceptionHandler)"); - entryStack.peek().bumpDecisionPoints(); - LOGGER.exiting(CLASS_NAME, "visit(ASTExceptionHandler)"); - super.visit(node, data); - return data; - } - - @Override - public Object visit(ASTForStatement node, Object data) { - LOGGER.entering(CLASS_NAME, "visit(ASTForStatement)"); - int boolCompFor = NPathComplexityRule - .sumExpressionComplexity(node.getFirstDescendantOfType(ASTExpression.class)); - // For statement always has a complexity of at least 1 - boolCompFor++; - - entryStack.peek().bumpDecisionPoints(boolCompFor); - super.visit(node, data); - LOGGER.exiting(CLASS_NAME, "visit(ASTForStatement)"); - return data; - } - - @Override - public Object visit(ASTLoopStatement node, Object data) { - LOGGER.entering(CLASS_NAME, "visit(ASTLoopStatement)"); - int boolCompDo = NPathComplexityRule.sumExpressionComplexity(node.getFirstChildOfType(ASTExpression.class)); - // Do statement always has a complexity of at least 1 - boolCompDo++; - - entryStack.peek().bumpDecisionPoints(boolCompDo); - super.visit(node, data); - LOGGER.exiting(CLASS_NAME, "visit(ASTLoopStatement)"); - return data; - } - - @Override - public Object visit(ASTCaseStatement node, Object data) { - LOGGER.entering(CLASS_NAME, "visit(ASTCaseStatement)"); - Entry entry = entryStack.peek(); - - int boolCompSwitch = NPathComplexityRule.sumExpressionComplexity(node.getFirstChildOfType(ASTExpression.class)); - entry.bumpDecisionPoints(boolCompSwitch); - - super.visit(node, data); - LOGGER.exiting(CLASS_NAME, "visit(ASTCaseStatement)"); - return data; - } - - @Override - public Object visit(ASTCaseWhenClause node, Object data) { - LOGGER.entering(CLASS_NAME, "visit(ASTCaseWhenClause)"); - Entry entry = entryStack.peek(); - - entry.bumpDecisionPoints(); - super.visit(node, data); - LOGGER.exiting(CLASS_NAME, "visit(ASTCaseWhenClause)"); - return data; - } - - @Override - public Object visit(ASTWhileStatement node, Object data) { - LOGGER.entering(CLASS_NAME, "visit(ASTWhileStatement)"); - int boolCompWhile = NPathComplexityRule.sumExpressionComplexity(node.getFirstChildOfType(ASTExpression.class)); - // While statement always has a complexity of at least 1 - boolCompWhile++; - - entryStack.peek().bumpDecisionPoints(boolCompWhile); - super.visit(node, data); - LOGGER.exiting(CLASS_NAME, "visit(ASTWhileStatement)"); - return data; - } - - @Override - public Object visit(ASTConditionalOrExpression node, Object data) { - return data; - } - - @Override - public Object visit(ASTPackageSpecification node, Object data) { - LOGGER.entering(CLASS_NAME, "visit(ASTPackageSpecification)"); - // Treat Package Specification like an Interface - LOGGER.exiting(CLASS_NAME, "visit(ASTPackageSpecification)"); - return data; - } - - @Override - public Object visit(ASTTypeSpecification node, Object data) { - LOGGER.entering(CLASS_NAME, "visit(ASTTypeSpecification)"); - // Treat Type Specification like an Interface - LOGGER.exiting(CLASS_NAME, "visit(ASTTypeSpecification)"); - return data; - } - - @Override - public Object visit(ASTPackageBody node, Object data) { - LOGGER.entering(CLASS_NAME, "visit(ASTPackageBody)"); - - entryStack.push(new Entry(node)); - super.visit(node, data); - Entry classEntry = entryStack.pop(); - if (LOGGER.isLoggable(Level.FINEST)) { - LOGGER.finest("ASTPackageBody: ComplexityAverage==" + classEntry.getComplexityAverage() - + ", highestDecisionPoint=" + classEntry.highestDecisionPoints); - } - if (showClassesComplexity) { - if (classEntry.getComplexityAverage() >= reportLevel || classEntry.highestDecisionPoints >= reportLevel) { - addViolation(data, node, new String[] { "class", node.getImage(), - classEntry.getComplexityAverage() + " (Highest = " + classEntry.highestDecisionPoints + ')', }); - } - } - LOGGER.exiting(CLASS_NAME, "visit(ASTPackageBody)"); - return data; - } - - @Override - public Object visit(ASTTriggerUnit node, Object data) { - LOGGER.entering(CLASS_NAME, "visit(ASTTriggerUnit)"); - - entryStack.push(new Entry(node)); - super.visit(node, data); - Entry classEntry = entryStack.pop(); - if (LOGGER.isLoggable(Level.FINEST)) { - LOGGER.finest("ASTTriggerUnit: ComplexityAverage==" + classEntry.getComplexityAverage() - + ", highestDecisionPoint=" + classEntry.highestDecisionPoints); - } - if (showClassesComplexity) { - if (classEntry.getComplexityAverage() >= reportLevel || classEntry.highestDecisionPoints >= reportLevel) { - addViolation(data, node, new String[] { "class", node.getImage(), - classEntry.getComplexityAverage() + " (Highest = " + classEntry.highestDecisionPoints + ')', }); - } - } - LOGGER.exiting(CLASS_NAME, "visit(ASTTriggerUnit)"); - return data; - } - - @Override - public Object visit(ASTProgramUnit node, Object data) { - LOGGER.entering(CLASS_NAME, "visit(ASTProgramUnit)"); - entryStack.push(new Entry(node)); - super.visit(node, data); - Entry methodEntry = entryStack.pop(); - if (LOGGER.isLoggable(Level.FINEST)) { - LOGGER.finest("ASTProgramUnit: ComplexityAverage==" + methodEntry.getComplexityAverage() - + ", highestDecisionPoint=" + methodEntry.highestDecisionPoints); - } - if (showMethodsComplexity) { - // Entry methodEntry = entryStack.pop(); - int methodDecisionPoints = methodEntry.decisionPoints; - // PackageBody (including Object Type Body) - if (null != node.getFirstParentOfType(ASTPackageBody.class) - // Trigger of any form - || null != node.getFirstParentOfType(ASTTriggerUnit.class) - // TODO || null != node.getFirstParentOfType(ASTProgramUnit.class) - // //Another Procedure - // TODO || null != node.getFirstParentOfType(ASTTypeMethod.class) - // //Another Type method - ) { - /* - * TODO This does not cope with nested methods We need the - * outer most ASTPackageBody ASTTriggerUni ASTProgramUnit - * ASTTypeMethod - * - */ - Entry classEntry = entryStack.peek(); - classEntry.methodCount++; - classEntry.bumpDecisionPoints(methodDecisionPoints); - - if (methodDecisionPoints > classEntry.highestDecisionPoints) { - classEntry.highestDecisionPoints = methodDecisionPoints; - } - } - - ASTMethodDeclarator methodDeclarator = null; - for (int n = 0; n < node.jjtGetNumChildren(); n++) { - Node childNode = node.jjtGetChild(n); - if (childNode instanceof ASTMethodDeclarator) { - methodDeclarator = (ASTMethodDeclarator) childNode; - break; - } - } - - if (methodEntry.decisionPoints >= reportLevel) { - addViolation(data, node, - new String[] { "method", methodDeclarator == null ? "" : methodDeclarator.getImage(), - String.valueOf(methodEntry.decisionPoints), }); - } - } - LOGGER.exiting(CLASS_NAME, "visit(ASTProgramUnit)"); - return data; - } - - @Override - public Object visit(ASTTypeMethod node, Object data) { - LOGGER.entering(CLASS_NAME, "visit(ASTTypeMethod)"); - entryStack.push(new Entry(node)); - super.visit(node, data); - Entry methodEntry = entryStack.pop(); - if (LOGGER.isLoggable(Level.FINEST)) { - LOGGER.finest("ASTProgramUnit: ComplexityAverage==" + methodEntry.getComplexityAverage() - + ", highestDecisionPoint=" + methodEntry.highestDecisionPoints); - } - if (showMethodsComplexity) { - // Entry methodEntry = entryStack.pop(); - int methodDecisionPoints = methodEntry.decisionPoints; - // PAckageBody (including Object Type Body) - if (null != node.getFirstParentOfType(ASTPackageBody.class)) { - /* - * TODO This does not cope with nested methods We need the - * outer most ASTPackageBody - */ - Entry classEntry = entryStack.peek(); - classEntry.methodCount++; - classEntry.bumpDecisionPoints(methodDecisionPoints); - - if (methodDecisionPoints > classEntry.highestDecisionPoints) { - classEntry.highestDecisionPoints = methodDecisionPoints; - } - } - - ASTMethodDeclarator methodDeclarator = null; - for (int n = 0; n < node.jjtGetNumChildren(); n++) { - Node childNode = node.jjtGetChild(n); - if (childNode instanceof ASTMethodDeclarator) { - methodDeclarator = (ASTMethodDeclarator) childNode; - break; - } - } - - if (methodEntry.decisionPoints >= reportLevel) { - addViolation(data, node, - new String[] { "method", methodDeclarator == null ? "" : methodDeclarator.getImage(), - String.valueOf(methodEntry.decisionPoints), }); - } - } - LOGGER.exiting(CLASS_NAME, "visit(ASTTypeMethod)"); - return data; - } - - @Override - public Object visit(ASTTriggerTimingPointSection node, Object data) { - LOGGER.entering(CLASS_NAME, "visit(ASTTriggerTimingPointSection)"); - entryStack.push(new Entry(node)); - super.visit(node, data); - Entry methodEntry = entryStack.pop(); - if (LOGGER.isLoggable(Level.FINE)) { - LOGGER.fine("ASTTriggerTimingPointSection: ComplexityAverage==" + methodEntry.getComplexityAverage() - + ", highestDecisionPoint=" + methodEntry.highestDecisionPoints); - } - if (showMethodsComplexity) { - int methodDecisionPoints = methodEntry.decisionPoints; - Entry classEntry = entryStack.peek(); - classEntry.methodCount++; - classEntry.bumpDecisionPoints(methodDecisionPoints); - - if (methodDecisionPoints > classEntry.highestDecisionPoints) { - classEntry.highestDecisionPoints = methodDecisionPoints; - } - - ASTMethodDeclarator methodDeclarator = null; - for (int n = 0; n < node.jjtGetNumChildren(); n++) { - Node childNode = node.jjtGetChild(n); - if (childNode instanceof ASTMethodDeclarator) { - methodDeclarator = (ASTMethodDeclarator) childNode; - break; - } - } - - if (methodEntry.decisionPoints >= reportLevel) { - addViolation(data, node, - new String[] { "method", methodDeclarator == null ? "" : methodDeclarator.getImage(), - String.valueOf(methodEntry.decisionPoints), }); - } - } - LOGGER.exiting(CLASS_NAME, "visit(ASTTriggerTimingPointSection)"); - return data; - } - -} diff --git a/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/rule/codesize/ExcessiveMethodLengthRule.java b/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/rule/codesize/ExcessiveMethodLengthRule.java deleted file mode 100644 index b8a1b9b4ac7..00000000000 --- a/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/rule/codesize/ExcessiveMethodLengthRule.java +++ /dev/null @@ -1,19 +0,0 @@ -/** - * BSD-style license; for more info see http://pmd.sourceforge.net/license.html - */ - -package net.sourceforge.pmd.lang.plsql.rule.codesize; - -import net.sourceforge.pmd.lang.plsql.ast.ExecutableCode; -import net.sourceforge.pmd.lang.plsql.rule.design.ExcessiveLengthRule; - -/** - * This rule detects when a method exceeds a certain threshold. i.e. if a method - * has more than x lines of code. - */ -public class ExcessiveMethodLengthRule extends ExcessiveLengthRule { - public ExcessiveMethodLengthRule() { - super(ExecutableCode.class); - setProperty(MINIMUM_DESCRIPTOR, 100d); - } -} diff --git a/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/rule/codesize/ExcessiveParameterListRule.java b/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/rule/codesize/ExcessiveParameterListRule.java deleted file mode 100644 index 5f339bb56d3..00000000000 --- a/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/rule/codesize/ExcessiveParameterListRule.java +++ /dev/null @@ -1,27 +0,0 @@ -/** - * BSD-style license; for more info see http://pmd.sourceforge.net/license.html - */ - -package net.sourceforge.pmd.lang.plsql.rule.codesize; - -import net.sourceforge.pmd.lang.plsql.ast.ASTFormalParameter; -import net.sourceforge.pmd.lang.plsql.ast.ASTFormalParameters; -import net.sourceforge.pmd.lang.plsql.rule.design.ExcessiveNodeCountRule; -import net.sourceforge.pmd.util.NumericConstants; - -/** - * This rule detects an abnormally long parameter list. Note: This counts Nodes, - * and not necessarily parameters, so the numbers may not match up. (But - * topcount and sigma should work.) - */ -public class ExcessiveParameterListRule extends ExcessiveNodeCountRule { - public ExcessiveParameterListRule() { - super(ASTFormalParameters.class); - setProperty(MINIMUM_DESCRIPTOR, 10d); - } - - // Count these nodes, but no others. - public Object visit(ASTFormalParameter node, Object data) { - return NumericConstants.ONE; - } -} diff --git a/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/rule/codesize/NPathComplexityRule.java b/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/rule/codesize/NPathComplexityRule.java deleted file mode 100644 index 67d9f72f471..00000000000 --- a/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/rule/codesize/NPathComplexityRule.java +++ /dev/null @@ -1,441 +0,0 @@ -/** - * BSD-style license; for more info see http://pmd.sourceforge.net/license.html - */ - -package net.sourceforge.pmd.lang.plsql.rule.codesize; - -import java.util.ArrayList; -import java.util.List; -import java.util.logging.Level; -import java.util.logging.Logger; - -import net.sourceforge.pmd.lang.plsql.ast.ASTCaseStatement; -import net.sourceforge.pmd.lang.plsql.ast.ASTCaseWhenClause; -import net.sourceforge.pmd.lang.plsql.ast.ASTConditionalAndExpression; -import net.sourceforge.pmd.lang.plsql.ast.ASTConditionalOrExpression; -import net.sourceforge.pmd.lang.plsql.ast.ASTElseClause; -import net.sourceforge.pmd.lang.plsql.ast.ASTElsifClause; -import net.sourceforge.pmd.lang.plsql.ast.ASTExpression; -import net.sourceforge.pmd.lang.plsql.ast.ASTForStatement; -import net.sourceforge.pmd.lang.plsql.ast.ASTIfStatement; -import net.sourceforge.pmd.lang.plsql.ast.ASTLoopStatement; -import net.sourceforge.pmd.lang.plsql.ast.ASTMethodDeclaration; -import net.sourceforge.pmd.lang.plsql.ast.ASTProgramUnit; -import net.sourceforge.pmd.lang.plsql.ast.ASTReturnStatement; -import net.sourceforge.pmd.lang.plsql.ast.ASTStatement; -import net.sourceforge.pmd.lang.plsql.ast.ASTTriggerTimingPointSection; -import net.sourceforge.pmd.lang.plsql.ast.ASTTriggerUnit; -import net.sourceforge.pmd.lang.plsql.ast.ASTTypeMethod; -import net.sourceforge.pmd.lang.plsql.ast.ASTWhileStatement; -import net.sourceforge.pmd.lang.plsql.ast.ExecutableCode; -import net.sourceforge.pmd.lang.plsql.ast.PLSQLNode; -import net.sourceforge.pmd.lang.plsql.rule.AbstractStatisticalPLSQLRule; -import net.sourceforge.pmd.stat.DataPoint; -import net.sourceforge.pmd.util.NumericConstants; - -/** - * NPath complexity is a measurement of the acyclic execution paths through a - * function. See Nejmeh, Communications of the ACM Feb 1988 pp 188-200. - * - * @author Jason Bennett - */ -public class NPathComplexityRule extends AbstractStatisticalPLSQLRule { - private static final String CLASS_NAME = NPathComplexityRule.class.getCanonicalName(); - private static final Logger LOGGER = Logger.getLogger(NPathComplexityRule.class.getName()); - - public NPathComplexityRule() { - super(); - setProperty(MINIMUM_DESCRIPTOR, 200d); - } - - private int complexityMultipleOf(PLSQLNode node, int npathStart, Object data) { - LOGGER.entering(CLASS_NAME, "complexityMultipleOf(SimpleNode)"); - - int npath = npathStart; - PLSQLNode n; - - for (int i = 0; i < node.jjtGetNumChildren(); i++) { - n = (PLSQLNode) node.jjtGetChild(i); - npath *= (Integer) n.jjtAccept(this, data); - } - - LOGGER.exiting(CLASS_NAME, "complexityMultipleOf(SimpleNode)", npath); - return npath; - } - - @Override - public Object visit(ASTMethodDeclaration node, Object data) { - LOGGER.entering(CLASS_NAME, "visit(ASTMethodDeclaration)"); - int npath = complexityMultipleOf(node, 1, data); - - DataPoint point = new DataPoint(); - point.setNode(node); - point.setScore(1.0 * npath); - point.setMessage(getMessage()); - addDataPoint(point); - - if (LOGGER.isLoggable(Level.FINEST)) { - LOGGER.finest("NPath complexity: " + npath + " for line " + node.getBeginLine() + ", column " - + node.getBeginColumn()); - } - LOGGER.exiting(CLASS_NAME, "visit(ASTMethodDeclaration)", npath); - return Integer.valueOf(npath); - } - - @Override - public Object visit(ASTProgramUnit node, Object data) { - LOGGER.entering(CLASS_NAME, "visit(ASTProgramUnit)"); - int npath = complexityMultipleOf(node, 1, data); - - DataPoint point = new DataPoint(); - point.setNode(node); - point.setScore(1.0 * npath); - point.setMessage(getMessage()); - addDataPoint(point); - - if (LOGGER.isLoggable(Level.FINEST)) { - LOGGER.finest("NPath complexity: " + npath + " for line " + node.getBeginLine() + ", column " - + node.getBeginColumn()); - } - LOGGER.exiting(CLASS_NAME, "visit(ASTProgramUnit)", npath); - return Integer.valueOf(npath); - } - - @Override - public Object visit(ASTTypeMethod node, Object data) { - LOGGER.entering(CLASS_NAME, "visit(ASTTypeMethod)"); - int npath = complexityMultipleOf(node, 1, data); - - DataPoint point = new DataPoint(); - point.setNode(node); - point.setScore(1.0 * npath); - point.setMessage(getMessage()); - addDataPoint(point); - - if (LOGGER.isLoggable(Level.FINEST)) { - LOGGER.finest("NPath complexity: " + npath + " for line " + node.getBeginLine() + ", column " - + node.getBeginColumn()); - } - LOGGER.exiting(CLASS_NAME, "visit(ASTTypeMethod)", npath); - return Integer.valueOf(npath); - } - - @Override - public Object visit(ASTTriggerUnit node, Object data) { - LOGGER.entering(CLASS_NAME, "visit(ASTTriggerUnit)"); - int npath = complexityMultipleOf(node, 1, data); - - DataPoint point = new DataPoint(); - point.setNode(node); - point.setScore(1.0 * npath); - point.setMessage(getMessage()); - addDataPoint(point); - - if (LOGGER.isLoggable(Level.FINEST)) { - LOGGER.finest("NPath complexity: " + npath + " for line " + node.getBeginLine() + ", column " - + node.getBeginColumn()); - } - LOGGER.exiting(CLASS_NAME, "visit(ASTTriggerUnit)", npath); - return Integer.valueOf(npath); - } - - @Override - public Object visit(ASTTriggerTimingPointSection node, Object data) { - LOGGER.entering(CLASS_NAME, "visit(ASTTriggerTimingPointSection)"); - int npath = complexityMultipleOf(node, 1, data); - - DataPoint point = new DataPoint(); - point.setNode(node); - point.setScore(1.0 * npath); - point.setMessage(getMessage()); - addDataPoint(point); - - if (LOGGER.isLoggable(Level.FINEST)) { - LOGGER.finest("NPath complexity: " + npath + " for line " + node.getBeginLine() + ", column " - + node.getBeginColumn()); - } - LOGGER.exiting(CLASS_NAME, "visit(ASTTriggerTimingPointSection)", npath); - return Integer.valueOf(npath); - } - - @Override - public Object visit(PLSQLNode node, Object data) { - LOGGER.entering(CLASS_NAME, "visit(SimpleNode)"); - int npath = complexityMultipleOf(node, 1, data); - LOGGER.exiting(CLASS_NAME, "visit(SimpleNode)", npath); - return Integer.valueOf(npath); - } - - @Override - public Object visit(ASTIfStatement node, Object data) { - LOGGER.entering(CLASS_NAME, "visit(ASTIfStatement)"); - // (npath of if + npath of else (or 1) + bool_comp of if) * npath of - // next - - int boolCompIf = sumExpressionComplexity(node.getFirstChildOfType(ASTExpression.class)); - - int complexity = 0; - - List<PLSQLNode> statementChildren = new ArrayList<>(); - for (int i = 0; i < node.jjtGetNumChildren(); i++) { - if (node.jjtGetChild(i).getClass() == ASTStatement.class - || node.jjtGetChild(i).getClass() == ASTElsifClause.class - || node.jjtGetChild(i).getClass() == ASTElseClause.class) { - statementChildren.add((PLSQLNode) node.jjtGetChild(i)); - } - } - if (LOGGER.isLoggable(Level.FINEST)) { - LOGGER.finest(statementChildren.size() + " statementChildren found for IF statement " + node.getBeginLine() - + ", column " + node.getBeginColumn()); - } - - /* - * SRT if (statementChildren.isEmpty() || statementChildren.size() == 1 - * && ( null != node.getFirstChildOfType(ASTElseClause.class) ) - * //.hasElse() || statementChildren.size() != 1 && ( null == - * node.getFirstChildOfType(ASTElseClause.class) ) // !node.hasElse() ) - * { throw new - * IllegalStateException("If node has wrong number of children"); } - */ - - /* - * @TODO Any explicit Elsif clause(s) and Else clause are included in - * the list of statements // add path for not taking if if (null == - * node.getFirstChildOfType(ASTElsifClause.class) ) // - * !node.hasElse()!node.hasElse()) { complexity++; } - * - * if (null == node.getFirstChildOfType(ASTElseClause.class) ) // - * !node.hasElse()!node.hasElse()) { complexity++; } - */ - - for (PLSQLNode element : statementChildren) { - complexity += (Integer) element.jjtAccept(this, data); - } - - LOGGER.exiting(CLASS_NAME, "visit(ASTIfStatement)", boolCompIf + complexity); - return Integer.valueOf(boolCompIf + complexity); - } - - @Override - public Object visit(ASTElsifClause node, Object data) { - LOGGER.entering(CLASS_NAME, "visit(ASTElsifClause)"); - // (npath of if + npath of else (or 1) + bool_comp of if) * npath of - // next - - int boolCompIf = sumExpressionComplexity(node.getFirstChildOfType(ASTExpression.class)); - - int complexity = 0; - - List<PLSQLNode> statementChildren = new ArrayList<>(); - for (int i = 0; i < node.jjtGetNumChildren(); i++) { - if (node.jjtGetChild(i).getClass() == ASTStatement.class) { - statementChildren.add((PLSQLNode) node.jjtGetChild(i)); - } - } - if (LOGGER.isLoggable(Level.FINEST)) { - LOGGER.finest(statementChildren.size() + " statementChildren found for ELSIF statement " - + node.getBeginLine() + ", column " + node.getBeginColumn()); - } - - /* - * SRT if (statementChildren.isEmpty() || statementChildren.size() == 1 - * && ( null != node.getFirstChildOfType(ASTElseClause.class) ) - * //.hasElse() || statementChildren.size() != 1 && ( null == - * node.getFirstChildOfType(ASTElseClause.class) ) // !node.hasElse() ) - * { throw new - * IllegalStateException("If node has wrong number of children"); } - */ - - for (PLSQLNode element : statementChildren) { - complexity += (Integer) element.jjtAccept(this, data); - } - - LOGGER.exiting(CLASS_NAME, "visit(ASTElsifClause)", boolCompIf + complexity); - return Integer.valueOf(boolCompIf + complexity); - } - - @Override - public Object visit(ASTElseClause node, Object data) { - LOGGER.entering(CLASS_NAME, "visit(ASTElseClause)"); - // (npath of if + npath of else (or 1) + bool_comp of if) * npath of - // next - - int complexity = 0; - - List<PLSQLNode> statementChildren = new ArrayList<>(); - for (int i = 0; i < node.jjtGetNumChildren(); i++) { - if (node.jjtGetChild(i).getClass() == ASTStatement.class) { - statementChildren.add((PLSQLNode) node.jjtGetChild(i)); - } - } - if (LOGGER.isLoggable(Level.FINEST)) { - LOGGER.finest(statementChildren.size() + " statementChildren found for ELSE clause statement " - + node.getBeginLine() + ", column " + node.getBeginColumn()); - } - - for (PLSQLNode element : statementChildren) { - complexity += (Integer) element.jjtAccept(this, data); - } - - LOGGER.exiting(CLASS_NAME, "visit(ASTElseClause)", complexity); - return Integer.valueOf(complexity); - } - - @Override - public Object visit(ASTWhileStatement node, Object data) { - LOGGER.entering(CLASS_NAME, "visit(ASTWhileStatement)"); - // (npath of while + bool_comp of while + 1) * npath of next - - int boolCompWhile = sumExpressionComplexity(node.getFirstChildOfType(ASTExpression.class)); - - Integer nPathWhile = (Integer) ((PLSQLNode) node.getFirstChildOfType(ASTStatement.class)).jjtAccept(this, data); - - LOGGER.exiting(CLASS_NAME, "visit(ASTWhileStatement)", boolCompWhile + nPathWhile + 1); - return Integer.valueOf(boolCompWhile + nPathWhile + 1); - } - - @Override - public Object visit(ASTLoopStatement node, Object data) { - LOGGER.entering(CLASS_NAME, "visit(ASTLoopStatement)"); - // (npath of do + bool_comp of do + 1) * npath of next - - int boolCompDo = sumExpressionComplexity(node.getFirstChildOfType(ASTExpression.class)); - - Integer nPathDo = (Integer) ((PLSQLNode) node.getFirstChildOfType(ASTStatement.class)).jjtAccept(this, data); - - LOGGER.exiting(CLASS_NAME, "visit(ASTLoopStatement)", boolCompDo + nPathDo + 1); - return Integer.valueOf(boolCompDo + nPathDo + 1); - } - - @Override - public Object visit(ASTForStatement node, Object data) { - LOGGER.entering(CLASS_NAME, "visit(ASTForStatement)"); - // (npath of for + bool_comp of for + 1) * npath of next - - int boolCompFor = sumExpressionComplexity(node.getFirstDescendantOfType(ASTExpression.class)); - - Integer nPathFor = (Integer) ((PLSQLNode) node.getFirstChildOfType(ASTStatement.class)).jjtAccept(this, data); - - LOGGER.exiting(CLASS_NAME, "visit(ASTForStatement)", boolCompFor + nPathFor + 1); - return Integer.valueOf(boolCompFor + nPathFor + 1); - } - - @Override - public Object visit(ASTReturnStatement node, Object data) { - LOGGER.entering(CLASS_NAME, "visit(ASTReturnStatement)"); - // return statements are valued at 1, or the value of the boolean - // expression - - ASTExpression expr = node.getFirstChildOfType(ASTExpression.class); - - if (expr == null) { - return NumericConstants.ONE; - } - - int boolCompReturn = sumExpressionComplexity(expr); - int conditionalExpressionComplexity = complexityMultipleOf(expr, 1, data); - - if (conditionalExpressionComplexity > 1) { - boolCompReturn += conditionalExpressionComplexity; - } - - if (boolCompReturn > 0) { - return Integer.valueOf(boolCompReturn); - } - LOGGER.entering(CLASS_NAME, "visit(ASTReturnStatement)", NumericConstants.ONE); - return NumericConstants.ONE; - } - - @Override - public Object visit(ASTCaseWhenClause node, Object data) { - LOGGER.entering(CLASS_NAME, "visit(ASTCaseWhenClause)"); - // bool_comp of switch + sum(npath(case_range)) - - int boolCompSwitch = sumExpressionComplexity(node.getFirstChildOfType(ASTExpression.class)); - - int npath = 1; - int caseRange = 0; - for (int i = 0; i < node.jjtGetNumChildren(); i++) { - PLSQLNode n = (PLSQLNode) node.jjtGetChild(i); - - // Fall-through labels count as 1 for complexity - Integer complexity = (Integer) n.jjtAccept(this, data); - caseRange *= complexity; - } - // add in npath of last label - npath += caseRange; - LOGGER.exiting(CLASS_NAME, "visit(ASTCaseWhenClause)", (boolCompSwitch + npath)); - return Integer.valueOf(boolCompSwitch + npath); - } - - @Override - public Object visit(ASTCaseStatement node, Object data) { - LOGGER.entering(CLASS_NAME, "visit(ASTCaseStatement)"); - // bool_comp of switch + sum(npath(case_range)) - - int boolCompSwitch = sumExpressionComplexity(node.getFirstChildOfType(ASTExpression.class)); - - int npath = 0; - int caseRange = 0; - for (int i = 0; i < node.jjtGetNumChildren(); i++) { - PLSQLNode n = (PLSQLNode) node.jjtGetChild(i); - - // Fall-through labels count as 1 for complexity - Integer complexity = (Integer) n.jjtAccept(this, data); - caseRange *= complexity; - } - // add in npath of last label - npath += caseRange; - LOGGER.exiting(CLASS_NAME, "visit(ASTCaseStatement)", (boolCompSwitch + npath)); - return Integer.valueOf(boolCompSwitch + npath); - } - - @Override - public Object visit(ASTConditionalOrExpression node, Object data) { - return NumericConstants.ONE; - } - - /** - * Calculate the boolean complexity of the given expression. NPath boolean - * complexity is the sum of && and || tokens. This is calculated by summing - * the number of children of the &&'s (minus one) and the children of the - * ||'s (minus one). - * - * <p>Note that this calculation applies to Cyclomatic Complexity as well.</p> - * - * @param expr - * control structure expression - * @return complexity of the boolean expression - */ - public static int sumExpressionComplexity(ASTExpression expr) { - LOGGER.entering(CLASS_NAME, "visit(ASTExpression)"); - if (expr == null) { - LOGGER.exiting(CLASS_NAME, "visit(ASTExpression)", 0); - return 0; - } - - List<ASTConditionalAndExpression> andNodes = expr.findDescendantsOfType(ASTConditionalAndExpression.class); - List<ASTConditionalOrExpression> orNodes = expr.findDescendantsOfType(ASTConditionalOrExpression.class); - - int children = 0; - - for (ASTConditionalOrExpression element : orNodes) { - children += element.jjtGetNumChildren(); - children--; - } - - for (ASTConditionalAndExpression element : andNodes) { - children += element.jjtGetNumChildren(); - children--; - } - - LOGGER.exiting(CLASS_NAME, "visit(ASTExpression)", children); - return children; - } - - @Override - public Object[] getViolationParameters(DataPoint point) { - return new String[] { ((ExecutableCode) point.getNode()).getMethodName(), - String.valueOf((int) point.getScore()), }; - } -} diff --git a/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/rule/codesize/NcssMethodCountRule.java b/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/rule/codesize/NcssMethodCountRule.java deleted file mode 100644 index f2bf4525b2f..00000000000 --- a/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/rule/codesize/NcssMethodCountRule.java +++ /dev/null @@ -1,35 +0,0 @@ -/** - * BSD-style license; for more info see http://pmd.sourceforge.net/license.html - */ - -package net.sourceforge.pmd.lang.plsql.rule.codesize; - -import net.sourceforge.pmd.lang.plsql.ast.ExecutableCode; -import net.sourceforge.pmd.stat.DataPoint; - -/** - * Non-commented source statement counter for methods. - * - * <p>Analogous to and cribbed from Java version of the rule.</p> - */ -public class NcssMethodCountRule extends AbstractNcssCountRule { - - /** - * Count the size of all non-constructor methods. - */ - public NcssMethodCountRule() { - super(ExecutableCode.class); - setProperty(MINIMUM_DESCRIPTOR, 100d); - } - - @Override - public Object visit(ExecutableCode node, Object data) { - return super.visit(node, data); - } - - @Override - public Object[] getViolationParameters(DataPoint point) { - return new String[] { ((ExecutableCode) point.getNode()).getMethodName(), - String.valueOf((int) point.getScore()), }; - } -} diff --git a/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/rule/codesize/TooManyFieldsRule.java b/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/rule/codesize/TooManyFieldsRule.java deleted file mode 100644 index d44881ff246..00000000000 --- a/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/rule/codesize/TooManyFieldsRule.java +++ /dev/null @@ -1,93 +0,0 @@ -/** - * BSD-style license; for more info see http://pmd.sourceforge.net/license.html - */ - -package net.sourceforge.pmd.lang.plsql.rule.codesize; - -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import net.sourceforge.pmd.lang.ast.Node; -import net.sourceforge.pmd.lang.plsql.ast.ASTInput; -import net.sourceforge.pmd.lang.plsql.ast.ASTPackageSpecification; -import net.sourceforge.pmd.lang.plsql.ast.ASTTypeSpecification; -import net.sourceforge.pmd.lang.plsql.ast.ASTVariableOrConstantDeclaration; -import net.sourceforge.pmd.lang.plsql.ast.PLSQLNode; -import net.sourceforge.pmd.lang.plsql.rule.AbstractPLSQLRule; -import net.sourceforge.pmd.properties.IntegerProperty; -import net.sourceforge.pmd.util.NumericConstants; - -public class TooManyFieldsRule extends AbstractPLSQLRule { - - private static final int DEFAULT_MAXFIELDS = 15; - - private Map<String, Integer> stats; - private Map<String, PLSQLNode> nodes; - - private static final IntegerProperty MAX_FIELDS_DESCRIPTOR = new IntegerProperty("maxfields", - "Max allowable fields", 1, 300, DEFAULT_MAXFIELDS, 1.0f); - - public TooManyFieldsRule() { - definePropertyDescriptor(MAX_FIELDS_DESCRIPTOR); - } - - @Override - public Object visit(ASTInput node, Object data) { - - stats = new HashMap<>(5); - nodes = new HashMap<>(5); - - return super.visit(node, data); - } - - @Override - public Object visit(ASTPackageSpecification node, Object data) { - - int maxFields = getProperty(MAX_FIELDS_DESCRIPTOR); - - List<ASTVariableOrConstantDeclaration> l = node.findDescendantsOfType(ASTVariableOrConstantDeclaration.class); - - for (ASTVariableOrConstantDeclaration fd : l) { - bumpCounterFor(fd); - } - for (String k : stats.keySet()) { - int val = stats.get(k); - Node n = nodes.get(k); - if (val > maxFields) { - addViolation(data, n); - } - } - return data; - } - - @Override - public Object visit(ASTTypeSpecification node, Object data) { - - int maxFields = getProperty(MAX_FIELDS_DESCRIPTOR); - - List<ASTVariableOrConstantDeclaration> l = node.findDescendantsOfType(ASTVariableOrConstantDeclaration.class); - - for (ASTVariableOrConstantDeclaration fd : l) { - bumpCounterFor(fd); - } - for (String k : stats.keySet()) { - int val = stats.get(k); - Node n = nodes.get(k); - if (val > maxFields) { - addViolation(data, n); - } - } - return data; - } - - private void bumpCounterFor(PLSQLNode clazz) { - String key = clazz.getImage(); - if (!stats.containsKey(key)) { - stats.put(key, NumericConstants.ZERO); - nodes.put(key, clazz); - } - Integer i = Integer.valueOf(stats.get(key) + 1); - stats.put(key, i); - } -} diff --git a/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/rule/codestyle/CodeFormatRule.java b/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/rule/codestyle/CodeFormatRule.java new file mode 100644 index 00000000000..8c4516e16a4 --- /dev/null +++ b/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/rule/codestyle/CodeFormatRule.java @@ -0,0 +1,261 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.plsql.rule.codestyle; + +import java.util.List; + +import net.sourceforge.pmd.lang.ast.Node; +import net.sourceforge.pmd.lang.plsql.ast.ASTArgument; +import net.sourceforge.pmd.lang.plsql.ast.ASTArgumentList; +import net.sourceforge.pmd.lang.plsql.ast.ASTBulkCollectIntoClause; +import net.sourceforge.pmd.lang.plsql.ast.ASTDatatype; +import net.sourceforge.pmd.lang.plsql.ast.ASTDeclarativeSection; +import net.sourceforge.pmd.lang.plsql.ast.ASTEqualityExpression; +import net.sourceforge.pmd.lang.plsql.ast.ASTFormalParameter; +import net.sourceforge.pmd.lang.plsql.ast.ASTFormalParameters; +import net.sourceforge.pmd.lang.plsql.ast.ASTFromClause; +import net.sourceforge.pmd.lang.plsql.ast.ASTInput; +import net.sourceforge.pmd.lang.plsql.ast.ASTJoinClause; +import net.sourceforge.pmd.lang.plsql.ast.ASTSelectList; +import net.sourceforge.pmd.lang.plsql.ast.ASTSubqueryOperation; +import net.sourceforge.pmd.lang.plsql.ast.ASTUnqualifiedID; +import net.sourceforge.pmd.lang.plsql.ast.ASTVariableOrConstantDeclarator; +import net.sourceforge.pmd.lang.plsql.rule.AbstractPLSQLRule; +import net.sourceforge.pmd.properties.IntegerProperty; + +public class CodeFormatRule extends AbstractPLSQLRule { + + private static final IntegerProperty INDENTATION_PROPERTY = IntegerProperty.named("indentation") + .desc("Indentation to be used for blocks").defaultValue(2).range(0, 20).build(); + + private int indentation = INDENTATION_PROPERTY.defaultValue(); + + public CodeFormatRule() { + definePropertyDescriptor(INDENTATION_PROPERTY); + } + + @Override + public Object visit(ASTInput node, Object data) { + indentation = getProperty(INDENTATION_PROPERTY); + return super.visit(node, data); + } + + @Override + public Object visit(ASTSelectList node, Object data) { + Node parent = node.jjtGetParent(); + checkEachChildOnNextLine(data, node, parent.getBeginLine(), parent.getBeginColumn() + 7); + return super.visit(node, data); + } + + @Override + public Object visit(ASTBulkCollectIntoClause node, Object data) { + Node parent = node.jjtGetParent(); + checkIndentation(data, node, parent.getBeginColumn() + indentation, "BULK COLLECT INTO"); + checkEachChildOnNextLine(data, node, node.getBeginLine(), parent.getBeginColumn() + 7); + return super.visit(node, data); + } + + @Override + public Object visit(ASTFromClause node, Object data) { + checkIndentation(data, node, node.jjtGetParent().getBeginColumn() + indentation, "FROM"); + return super.visit(node, data); + } + + @Override + public Object visit(ASTJoinClause node, Object data) { + // first child is the table reference + Node tableReference = node.jjtGetChild(0); + + // remaining children are joins + int lineNumber = tableReference.getBeginLine(); + for (int i = 1; i < node.jjtGetNumChildren(); i++) { + lineNumber++; + Node child = node.jjtGetChild(i); + if (child.getBeginLine() != lineNumber) { + addViolationWithMessage(data, child, child.getXPathNodeName() + " should be on line " + lineNumber); + } + List<ASTEqualityExpression> conditions = child.findDescendantsOfType(ASTEqualityExpression.class); + + if (conditions.size() == 1) { + // one condition should be on the same line + ASTEqualityExpression singleCondition = conditions.get(0); + if (singleCondition.getBeginLine() != lineNumber) { + addViolationWithMessage(data, child, + "Join condition \"" + singleCondition.getImage() + "\" should be on line " + lineNumber); + } + } else { + // each condition on a separate line + for (ASTEqualityExpression singleCondition : conditions) { + lineNumber++; + if (singleCondition.getBeginLine() != lineNumber) { + addViolationWithMessage(data, child, + "Join condition \"" + singleCondition.getImage() + "\" should be on line " + + lineNumber); + } + } + } + } + return super.visit(node, data); + } + + @Override + public Object visit(ASTSubqueryOperation node, Object data) { + // get previous sibling + int thisIndex = node.jjtGetChildIndex(); + Node prevSibling = node.jjtGetParent().jjtGetChild(thisIndex - 1); + + checkIndentation(data, node, prevSibling.getBeginColumn(), node.getImage()); + + // it should also be on the next line + if (node.getBeginLine() != prevSibling.getEndLine() + 1) { + addViolationWithMessage(data, node, + node.getImage() + " should be on line " + (prevSibling.getEndLine() + 1)); + } + + return super.visit(node, data); + } + + private int checkEachChildOnNextLine(Object data, Node parent, int firstLine, int indentation) { + int currentLine = firstLine; + for (int i = 0; i < parent.jjtGetNumChildren(); i++) { + Node child = parent.jjtGetChild(i); + if (child.getBeginLine() != currentLine) { + addViolationWithMessage(data, child, child.getImage() + " should be on line " + currentLine); + } else if (i > 0 && child.getBeginColumn() != indentation) { + addViolationWithMessage(data, child, child.getImage() + " should begin at column " + indentation); + } + // next entry needs to be on the next line + currentLine++; + } + return currentLine; + } + + private void checkLineAndIndentation(Object data, Node node, int line, int indentation, String name) { + if (node.getBeginLine() != line) { + addViolationWithMessage(data, node, name + " should be on line " + line); + } else if (node.getBeginColumn() != indentation) { + addViolationWithMessage(data, node, name + " should begin at column " + indentation); + } + } + + private void checkIndentation(Object data, Node node, int indentation, String name) { + if (node.getBeginColumn() != indentation) { + addViolationWithMessage(data, node, name + " should begin at column " + indentation); + } + } + + @Override + public Object visit(ASTFormalParameters node, Object data) { + int parameterIndentation = node.jjtGetParent().getBeginColumn() + indentation; + checkEachChildOnNextLine(data, node, node.getBeginLine() + 1, parameterIndentation); + + // check the data type alignment + List<ASTFormalParameter> parameters = node.findChildrenOfType(ASTFormalParameter.class); + if (parameters.size() > 1) { + ASTDatatype first = parameters.get(0).getFirstChildOfType(ASTDatatype.class); + for (int i = 1; first != null && i < parameters.size(); i++) { + ASTDatatype nextType = parameters.get(i).getFirstChildOfType(ASTDatatype.class); + if (nextType != null) { + checkIndentation(data, nextType, first.getBeginColumn(), "Type " + nextType.getImage()); + } + } + } + return super.visit(node, data); + } + + @Override + public Object visit(ASTDeclarativeSection node, Object data) { + int variableIndentation = node.getNthParent(2).getBeginColumn() + 2 * indentation; + int line = node.getBeginLine(); + + List<ASTVariableOrConstantDeclarator> variables = node + .findDescendantsOfType(ASTVariableOrConstantDeclarator.class); + + int datatypeIndentation = variableIndentation; + if (!variables.isEmpty()) { + ASTDatatype datatype = variables.get(0).getFirstChildOfType(ASTDatatype.class); + if (datatype != null) { + datatypeIndentation = datatype.getBeginColumn(); + } + } + + for (ASTVariableOrConstantDeclarator variable : variables) { + checkLineAndIndentation(data, variable, line, variableIndentation, variable.getImage()); + + ASTDatatype datatype = variable.getFirstChildOfType(ASTDatatype.class); + if (datatype != null) { + checkIndentation(data, datatype, datatypeIndentation, "Type " + datatype.getImage()); + } + + line++; + } + + return super.visit(node, data); + } + + @Override + public Object visit(ASTArgumentList node, Object data) { + List<ASTArgument> arguments = node.findChildrenOfType(ASTArgument.class); + + if (node.getEndColumn() > 120) { + addViolationWithMessage(data, node, "Line is too long, please split parameters on separate lines"); + return super.visit(node, data); + } + + if (arguments.size() > 3) { + // procedure calls with more than 3 parameters should use named parameters + if (usesSimpleParameters(arguments)) { + addViolationWithMessage(data, node, + "Procedure call with more than three parameters should use named parameters."); + } + + // more than three parameters -> each parameter on a separate line + int line = node.getBeginLine(); + int indentation = node.getBeginColumn(); + int longestParameterEndColumn = 0; + for (ASTArgument argument : arguments) { + checkLineAndIndentation(data, argument, line, indentation, "Parameter " + argument.getImage()); + line++; + + if (argument.jjtGetChild(0) instanceof ASTUnqualifiedID) { + if (argument.jjtGetChild(0).getEndColumn() > longestParameterEndColumn) { + longestParameterEndColumn = argument.jjtGetChild(0).getEndColumn(); + } + } + } + + // now check for the indentation of the expressions + int expectedBeginColumn = longestParameterEndColumn + 3 + "=> ".length(); + // take the indentation from the first one, if it is greater + if (!arguments.isEmpty() && arguments.get(0).jjtGetNumChildren() == 2 + && arguments.get(0).jjtGetChild(1).getBeginColumn() > expectedBeginColumn) { + expectedBeginColumn = arguments.get(0).jjtGetChild(1).getBeginColumn(); + } + for (ASTArgument argument : arguments) { + if (argument.jjtGetNumChildren() == 2 && argument.jjtGetChild(0) instanceof ASTUnqualifiedID) { + Node expr = argument.jjtGetChild(1); + checkIndentation(data, expr, expectedBeginColumn, expr.getImage()); + } + } + + // closing paranthesis should be on a new line + Node primaryExpression = node.getNthParent(3); + if (primaryExpression.getEndLine() != node.getEndLine() + 1) { + addViolationWithMessage(data, primaryExpression, "Closing paranthesis should be on a new line."); + } + } + + return super.visit(node, data); + } + + private boolean usesSimpleParameters(List<ASTArgument> arguments) { + for (ASTArgument argument : arguments) { + if (argument.jjtGetNumChildren() == 1) { + return true; + } + } + return false; + } +} diff --git a/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/rule/design/AbstractNcssCountRule.java b/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/rule/design/AbstractNcssCountRule.java new file mode 100644 index 00000000000..180fb22d662 --- /dev/null +++ b/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/rule/design/AbstractNcssCountRule.java @@ -0,0 +1,203 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.plsql.rule.design; + +import java.util.logging.Level; +import java.util.logging.Logger; + +import net.sourceforge.pmd.lang.ast.Node; +import net.sourceforge.pmd.lang.plsql.ast.ASTCaseStatement; +import net.sourceforge.pmd.lang.plsql.ast.ASTCaseWhenClause; +import net.sourceforge.pmd.lang.plsql.ast.ASTContinueStatement; +import net.sourceforge.pmd.lang.plsql.ast.ASTElseClause; +import net.sourceforge.pmd.lang.plsql.ast.ASTElsifClause; +import net.sourceforge.pmd.lang.plsql.ast.ASTExceptionHandler; +import net.sourceforge.pmd.lang.plsql.ast.ASTExitStatement; +import net.sourceforge.pmd.lang.plsql.ast.ASTExpression; +import net.sourceforge.pmd.lang.plsql.ast.ASTForStatement; +import net.sourceforge.pmd.lang.plsql.ast.ASTGotoStatement; +import net.sourceforge.pmd.lang.plsql.ast.ASTIfStatement; +import net.sourceforge.pmd.lang.plsql.ast.ASTLabelledStatement; +import net.sourceforge.pmd.lang.plsql.ast.ASTLoopStatement; +import net.sourceforge.pmd.lang.plsql.ast.ASTRaiseStatement; +import net.sourceforge.pmd.lang.plsql.ast.ASTReturnStatement; +import net.sourceforge.pmd.lang.plsql.ast.ASTStatement; +import net.sourceforge.pmd.lang.plsql.ast.ASTWhileStatement; +import net.sourceforge.pmd.lang.plsql.ast.PLSQLNode; +import net.sourceforge.pmd.lang.plsql.rule.AbstractStatisticalPLSQLRule; +import net.sourceforge.pmd.stat.DataPoint; +import net.sourceforge.pmd.util.NumericConstants; + +/** + * Abstract superclass for NCSS counting methods. Analogous to and cribbed from + * the Java version of the rule. + */ +public abstract class AbstractNcssCountRule extends AbstractStatisticalPLSQLRule { + private static final Logger LOGGER = Logger.getLogger(AbstractNcssCountRule.class.getName()); + + private Class<?> nodeClass; + + /** + * Count the nodes of the given type using NCSS rules. + * + * @param nodeClass + * class of node to count + */ + protected AbstractNcssCountRule(Class<?> nodeClass) { + this.nodeClass = nodeClass; + if (LOGGER.isLoggable(Level.FINE)) { + LOGGER.fine("Counting for " + nodeClass.getCanonicalName()); + } + } + + @Override + public Object visit(PLSQLNode node, Object data) { + int numNodes = 0; + + for (int i = 0; i < node.jjtGetNumChildren(); i++) { + PLSQLNode n = (PLSQLNode) node.jjtGetChild(i); + Integer treeSize = (Integer) n.jjtAccept(this, data); + numNodes += treeSize.intValue(); + } + + if (LOGGER.isLoggable(Level.FINER)) { + LOGGER.finer("Checking candidate " + node.getClass().getCanonicalName() + " against target class " + + nodeClass.getCanonicalName() + " with " + numNodes + " nodes"); + } + + if (this.nodeClass.isInstance(node)) { + if (LOGGER.isLoggable(Level.FINE)) { + LOGGER.fine("Matched candidate " + node.getClass().getCanonicalName() + " against target class " + + nodeClass.getCanonicalName()); + } + // Add 1 to account for base node + numNodes++; + DataPoint point = new DataPoint(); + point.setNode(node); + point.setScore(1.0 * numNodes); + point.setMessage(getMessage()); + addDataPoint(point); + if (LOGGER.isLoggable(Level.FINE)) { + LOGGER.fine("Running score is " + point.getScore()); + } + } + + return Integer.valueOf(numNodes); + } + + /** + * Count the number of children of the given PLSQL node. Adds one to count + * the node itself. + * + * @param node + * PLSQL node having children counted + * @param data + * node data + * @return count of the number of children of the node, plus one + */ + protected Integer countNodeChildren(Node node, Object data) { + Integer nodeCount = null; + int lineCount = 0; + for (int i = 0; i < node.jjtGetNumChildren(); i++) { + nodeCount = (Integer) ((PLSQLNode) node.jjtGetChild(i)).jjtAccept(this, data); + lineCount += nodeCount.intValue(); + } + return ++lineCount; + } + + @Override + public Object visit(ASTForStatement node, Object data) { + return countNodeChildren(node, data); + } + + @Override + public Object visit(ASTLoopStatement node, Object data) { + return countNodeChildren(node, data); + } + + @Override + public Object visit(ASTIfStatement node, Object data) { + return countNodeChildren(node, data); + } + + @Override + public Object visit(ASTElsifClause node, Object data) { + return countNodeChildren(node, data); + } + + @Override + public Object visit(ASTElseClause node, Object data) { + return countNodeChildren(node, data); + } + + @Override + public Object visit(ASTWhileStatement node, Object data) { + return countNodeChildren(node, data); + } + + @Override + public Object visit(ASTExitStatement node, Object data) { + return NumericConstants.ONE; + } + + @Override + public Object visit(ASTExceptionHandler node, Object data) { + return countNodeChildren(node, data); + } + + @Override + public Object visit(ASTContinueStatement node, Object data) { + return NumericConstants.ONE; + } + + @Override + public Object visit(ASTGotoStatement node, Object data) { + return NumericConstants.ONE; + } + + @Override + public Object visit(ASTReturnStatement node, Object data) { + return countNodeChildren(node, data); + } + + @Override + public Object visit(ASTCaseStatement node, Object data) { + return countNodeChildren(node, data); + } + + @Override + public Object visit(ASTRaiseStatement node, Object data) { + return NumericConstants.ONE; + } + + @Override + public Object visit(ASTExpression node, Object data) { + + // "For" update expressions do not count as separate lines of code + if (node.jjtGetParent() instanceof ASTStatement) { + return NumericConstants.ZERO; + } + + return NumericConstants.ONE; + } + + @Override + public Object visit(ASTLabelledStatement node, Object data) { + return countNodeChildren(node, data); + } + + @Override + public Object visit(ASTCaseWhenClause node, Object data) { + return countNodeChildren(node, data); + } + + @Override + public Object[] getViolationParameters(DataPoint point) { + if (LOGGER.isLoggable(Level.FINE)) { + LOGGER.fine("Point score is " + point.getScore()); + } + return new String[] { String.valueOf((int) point.getScore()) }; + } +} diff --git a/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/rule/design/CyclomaticComplexityRule.java b/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/rule/design/CyclomaticComplexityRule.java new file mode 100644 index 00000000000..fe23505c62d --- /dev/null +++ b/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/rule/design/CyclomaticComplexityRule.java @@ -0,0 +1,407 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.plsql.rule.design; + +import java.util.Stack; +import java.util.logging.Level; +import java.util.logging.Logger; + +import net.sourceforge.pmd.lang.ast.Node; +import net.sourceforge.pmd.lang.plsql.ast.ASTCaseStatement; +import net.sourceforge.pmd.lang.plsql.ast.ASTCaseWhenClause; +import net.sourceforge.pmd.lang.plsql.ast.ASTConditionalOrExpression; +import net.sourceforge.pmd.lang.plsql.ast.ASTElsifClause; +import net.sourceforge.pmd.lang.plsql.ast.ASTExceptionHandler; +import net.sourceforge.pmd.lang.plsql.ast.ASTExpression; +import net.sourceforge.pmd.lang.plsql.ast.ASTForStatement; +import net.sourceforge.pmd.lang.plsql.ast.ASTIfStatement; +import net.sourceforge.pmd.lang.plsql.ast.ASTInput; +import net.sourceforge.pmd.lang.plsql.ast.ASTLoopStatement; +import net.sourceforge.pmd.lang.plsql.ast.ASTMethodDeclarator; +import net.sourceforge.pmd.lang.plsql.ast.ASTPackageBody; +import net.sourceforge.pmd.lang.plsql.ast.ASTPackageSpecification; +import net.sourceforge.pmd.lang.plsql.ast.ASTProgramUnit; +import net.sourceforge.pmd.lang.plsql.ast.ASTTriggerTimingPointSection; +import net.sourceforge.pmd.lang.plsql.ast.ASTTriggerUnit; +import net.sourceforge.pmd.lang.plsql.ast.ASTTypeMethod; +import net.sourceforge.pmd.lang.plsql.ast.ASTTypeSpecification; +import net.sourceforge.pmd.lang.plsql.ast.ASTWhileStatement; +import net.sourceforge.pmd.lang.plsql.rule.AbstractPLSQLRule; +import net.sourceforge.pmd.properties.BooleanProperty; +import net.sourceforge.pmd.properties.IntegerProperty; + +/** + * @author Donald A. Leckie, + * + * @version $Revision: 5956 $, $Date: 2008-04-04 04:59:25 -0500 (Fri, 04 Apr + * 2008) $ + * @since January 14, 2003 + */ +public class CyclomaticComplexityRule extends AbstractPLSQLRule { + private static final Logger LOGGER = Logger.getLogger(CyclomaticComplexityRule.class.getName()); + private static final String CLASS_NAME = CyclomaticComplexityRule.class.getName(); + + public static final IntegerProperty REPORT_LEVEL_DESCRIPTOR + = IntegerProperty.named("reportLevel") + .desc("Cyclomatic Complexity reporting threshold") + .range(1, 30).defaultValue(10).uiOrder(1.0f).build(); + + public static final BooleanProperty SHOW_CLASSES_COMPLEXITY_DESCRIPTOR = new BooleanProperty( + "showClassesComplexity", "Add class average violations to the report", true, 2.0f); + + public static final BooleanProperty SHOW_METHODS_COMPLEXITY_DESCRIPTOR = new BooleanProperty( + "showMethodsComplexity", "Add method average violations to the report", true, 3.0f); + + private int reportLevel; + private boolean showClassesComplexity = true; + private boolean showMethodsComplexity = true; + + private static class Entry { + private Node node; + private int decisionPoints = 1; + public int highestDecisionPoints; + public int methodCount; + + private Entry(Node node) { + this.node = node; + } + + public void bumpDecisionPoints() { + decisionPoints++; + } + + public void bumpDecisionPoints(int size) { + decisionPoints += size; + } + + public int getComplexityAverage() { + return (double) methodCount == 0 ? 1 : (int) Math.rint((double) decisionPoints / (double) methodCount); + } + } + + private Stack<Entry> entryStack = new Stack<>(); + + public CyclomaticComplexityRule() { + definePropertyDescriptor(REPORT_LEVEL_DESCRIPTOR); + definePropertyDescriptor(SHOW_CLASSES_COMPLEXITY_DESCRIPTOR); + definePropertyDescriptor(SHOW_METHODS_COMPLEXITY_DESCRIPTOR); + } + + @Override + public Object visit(ASTInput node, Object data) { + LOGGER.entering(CLASS_NAME, "visit(ASTInput)"); + reportLevel = getProperty(REPORT_LEVEL_DESCRIPTOR); + showClassesComplexity = getProperty(SHOW_CLASSES_COMPLEXITY_DESCRIPTOR); + showMethodsComplexity = getProperty(SHOW_METHODS_COMPLEXITY_DESCRIPTOR); + super.visit(node, data); + LOGGER.exiting(CLASS_NAME, "visit(ASTInput)"); + return data; + } + + @Override + public Object visit(ASTElsifClause node, Object data) { + LOGGER.entering(CLASS_NAME, "visit(ASTElsifClause)"); + int boolCompIf = NPathComplexityRule.sumExpressionComplexity(node.getFirstChildOfType(ASTExpression.class)); + // If statement always has a complexity of at least 1 + boolCompIf++; + + entryStack.peek().bumpDecisionPoints(boolCompIf); + super.visit(node, data); + LOGGER.exiting(CLASS_NAME, "visit(ASTElsifClause)"); + return data; + } + + @Override + public Object visit(ASTIfStatement node, Object data) { + LOGGER.entering(CLASS_NAME, "visit(ASTIfClause)"); + int boolCompIf = NPathComplexityRule.sumExpressionComplexity(node.getFirstChildOfType(ASTExpression.class)); + // If statement always has a complexity of at least 1 + boolCompIf++; + + entryStack.peek().bumpDecisionPoints(boolCompIf); + LOGGER.exiting(CLASS_NAME, "visit(ASTIfClause)"); + super.visit(node, data); + return data; + } + + @Override + public Object visit(ASTExceptionHandler node, Object data) { + LOGGER.entering(CLASS_NAME, "visit(ASTExceptionHandler)"); + entryStack.peek().bumpDecisionPoints(); + LOGGER.exiting(CLASS_NAME, "visit(ASTExceptionHandler)"); + super.visit(node, data); + return data; + } + + @Override + public Object visit(ASTForStatement node, Object data) { + LOGGER.entering(CLASS_NAME, "visit(ASTForStatement)"); + int boolCompFor = NPathComplexityRule + .sumExpressionComplexity(node.getFirstDescendantOfType(ASTExpression.class)); + // For statement always has a complexity of at least 1 + boolCompFor++; + + entryStack.peek().bumpDecisionPoints(boolCompFor); + super.visit(node, data); + LOGGER.exiting(CLASS_NAME, "visit(ASTForStatement)"); + return data; + } + + @Override + public Object visit(ASTLoopStatement node, Object data) { + LOGGER.entering(CLASS_NAME, "visit(ASTLoopStatement)"); + int boolCompDo = NPathComplexityRule.sumExpressionComplexity(node.getFirstChildOfType(ASTExpression.class)); + // Do statement always has a complexity of at least 1 + boolCompDo++; + + entryStack.peek().bumpDecisionPoints(boolCompDo); + super.visit(node, data); + LOGGER.exiting(CLASS_NAME, "visit(ASTLoopStatement)"); + return data; + } + + @Override + public Object visit(ASTCaseStatement node, Object data) { + LOGGER.entering(CLASS_NAME, "visit(ASTCaseStatement)"); + Entry entry = entryStack.peek(); + + int boolCompSwitch = NPathComplexityRule.sumExpressionComplexity(node.getFirstChildOfType(ASTExpression.class)); + entry.bumpDecisionPoints(boolCompSwitch); + + super.visit(node, data); + LOGGER.exiting(CLASS_NAME, "visit(ASTCaseStatement)"); + return data; + } + + @Override + public Object visit(ASTCaseWhenClause node, Object data) { + LOGGER.entering(CLASS_NAME, "visit(ASTCaseWhenClause)"); + Entry entry = entryStack.peek(); + + entry.bumpDecisionPoints(); + super.visit(node, data); + LOGGER.exiting(CLASS_NAME, "visit(ASTCaseWhenClause)"); + return data; + } + + @Override + public Object visit(ASTWhileStatement node, Object data) { + LOGGER.entering(CLASS_NAME, "visit(ASTWhileStatement)"); + int boolCompWhile = NPathComplexityRule.sumExpressionComplexity(node.getFirstChildOfType(ASTExpression.class)); + // While statement always has a complexity of at least 1 + boolCompWhile++; + + entryStack.peek().bumpDecisionPoints(boolCompWhile); + super.visit(node, data); + LOGGER.exiting(CLASS_NAME, "visit(ASTWhileStatement)"); + return data; + } + + @Override + public Object visit(ASTConditionalOrExpression node, Object data) { + return data; + } + + @Override + public Object visit(ASTPackageSpecification node, Object data) { + LOGGER.entering(CLASS_NAME, "visit(ASTPackageSpecification)"); + // Treat Package Specification like an Interface + LOGGER.exiting(CLASS_NAME, "visit(ASTPackageSpecification)"); + return data; + } + + @Override + public Object visit(ASTTypeSpecification node, Object data) { + LOGGER.entering(CLASS_NAME, "visit(ASTTypeSpecification)"); + // Treat Type Specification like an Interface + LOGGER.exiting(CLASS_NAME, "visit(ASTTypeSpecification)"); + return data; + } + + @Override + public Object visit(ASTPackageBody node, Object data) { + LOGGER.entering(CLASS_NAME, "visit(ASTPackageBody)"); + + entryStack.push(new Entry(node)); + super.visit(node, data); + Entry classEntry = entryStack.pop(); + if (LOGGER.isLoggable(Level.FINEST)) { + LOGGER.finest("ASTPackageBody: ComplexityAverage==" + classEntry.getComplexityAverage() + + ", highestDecisionPoint=" + classEntry.highestDecisionPoints); + } + if (showClassesComplexity) { + if (classEntry.getComplexityAverage() >= reportLevel || classEntry.highestDecisionPoints >= reportLevel) { + addViolation(data, node, new String[] { "class", node.getImage(), + classEntry.getComplexityAverage() + " (Highest = " + classEntry.highestDecisionPoints + ')', }); + } + } + LOGGER.exiting(CLASS_NAME, "visit(ASTPackageBody)"); + return data; + } + + @Override + public Object visit(ASTTriggerUnit node, Object data) { + LOGGER.entering(CLASS_NAME, "visit(ASTTriggerUnit)"); + + entryStack.push(new Entry(node)); + super.visit(node, data); + Entry classEntry = entryStack.pop(); + if (LOGGER.isLoggable(Level.FINEST)) { + LOGGER.finest("ASTTriggerUnit: ComplexityAverage==" + classEntry.getComplexityAverage() + + ", highestDecisionPoint=" + classEntry.highestDecisionPoints); + } + if (showClassesComplexity) { + if (classEntry.getComplexityAverage() >= reportLevel || classEntry.highestDecisionPoints >= reportLevel) { + addViolation(data, node, new String[] { "class", node.getImage(), + classEntry.getComplexityAverage() + " (Highest = " + classEntry.highestDecisionPoints + ')', }); + } + } + LOGGER.exiting(CLASS_NAME, "visit(ASTTriggerUnit)"); + return data; + } + + @Override + public Object visit(ASTProgramUnit node, Object data) { + LOGGER.entering(CLASS_NAME, "visit(ASTProgramUnit)"); + entryStack.push(new Entry(node)); + super.visit(node, data); + Entry methodEntry = entryStack.pop(); + if (LOGGER.isLoggable(Level.FINEST)) { + LOGGER.finest("ASTProgramUnit: ComplexityAverage==" + methodEntry.getComplexityAverage() + + ", highestDecisionPoint=" + methodEntry.highestDecisionPoints); + } + if (showMethodsComplexity) { + // Entry methodEntry = entryStack.pop(); + int methodDecisionPoints = methodEntry.decisionPoints; + // PackageBody (including Object Type Body) + if (null != node.getFirstParentOfType(ASTPackageBody.class) + // Trigger of any form + || null != node.getFirstParentOfType(ASTTriggerUnit.class) + // TODO || null != node.getFirstParentOfType(ASTProgramUnit.class) + // //Another Procedure + // TODO || null != node.getFirstParentOfType(ASTTypeMethod.class) + // //Another Type method + ) { + /* + * TODO This does not cope with nested methods We need the + * outer most ASTPackageBody ASTTriggerUni ASTProgramUnit + * ASTTypeMethod + * + */ + Entry classEntry = entryStack.peek(); + classEntry.methodCount++; + classEntry.bumpDecisionPoints(methodDecisionPoints); + + if (methodDecisionPoints > classEntry.highestDecisionPoints) { + classEntry.highestDecisionPoints = methodDecisionPoints; + } + } + + ASTMethodDeclarator methodDeclarator = null; + for (int n = 0; n < node.jjtGetNumChildren(); n++) { + Node childNode = node.jjtGetChild(n); + if (childNode instanceof ASTMethodDeclarator) { + methodDeclarator = (ASTMethodDeclarator) childNode; + break; + } + } + + if (methodEntry.decisionPoints >= reportLevel) { + addViolation(data, node, + new String[] { "method", methodDeclarator == null ? "" : methodDeclarator.getImage(), + String.valueOf(methodEntry.decisionPoints), }); + } + } + LOGGER.exiting(CLASS_NAME, "visit(ASTProgramUnit)"); + return data; + } + + @Override + public Object visit(ASTTypeMethod node, Object data) { + LOGGER.entering(CLASS_NAME, "visit(ASTTypeMethod)"); + entryStack.push(new Entry(node)); + super.visit(node, data); + Entry methodEntry = entryStack.pop(); + if (LOGGER.isLoggable(Level.FINEST)) { + LOGGER.finest("ASTProgramUnit: ComplexityAverage==" + methodEntry.getComplexityAverage() + + ", highestDecisionPoint=" + methodEntry.highestDecisionPoints); + } + if (showMethodsComplexity) { + // Entry methodEntry = entryStack.pop(); + int methodDecisionPoints = methodEntry.decisionPoints; + // PAckageBody (including Object Type Body) + if (null != node.getFirstParentOfType(ASTPackageBody.class)) { + /* + * TODO This does not cope with nested methods We need the + * outer most ASTPackageBody + */ + Entry classEntry = entryStack.peek(); + classEntry.methodCount++; + classEntry.bumpDecisionPoints(methodDecisionPoints); + + if (methodDecisionPoints > classEntry.highestDecisionPoints) { + classEntry.highestDecisionPoints = methodDecisionPoints; + } + } + + ASTMethodDeclarator methodDeclarator = null; + for (int n = 0; n < node.jjtGetNumChildren(); n++) { + Node childNode = node.jjtGetChild(n); + if (childNode instanceof ASTMethodDeclarator) { + methodDeclarator = (ASTMethodDeclarator) childNode; + break; + } + } + + if (methodEntry.decisionPoints >= reportLevel) { + addViolation(data, node, + new String[] { "method", methodDeclarator == null ? "" : methodDeclarator.getImage(), + String.valueOf(methodEntry.decisionPoints), }); + } + } + LOGGER.exiting(CLASS_NAME, "visit(ASTTypeMethod)"); + return data; + } + + @Override + public Object visit(ASTTriggerTimingPointSection node, Object data) { + LOGGER.entering(CLASS_NAME, "visit(ASTTriggerTimingPointSection)"); + entryStack.push(new Entry(node)); + super.visit(node, data); + Entry methodEntry = entryStack.pop(); + if (LOGGER.isLoggable(Level.FINE)) { + LOGGER.fine("ASTTriggerTimingPointSection: ComplexityAverage==" + methodEntry.getComplexityAverage() + + ", highestDecisionPoint=" + methodEntry.highestDecisionPoints); + } + if (showMethodsComplexity) { + int methodDecisionPoints = methodEntry.decisionPoints; + Entry classEntry = entryStack.peek(); + classEntry.methodCount++; + classEntry.bumpDecisionPoints(methodDecisionPoints); + + if (methodDecisionPoints > classEntry.highestDecisionPoints) { + classEntry.highestDecisionPoints = methodDecisionPoints; + } + + ASTMethodDeclarator methodDeclarator = null; + for (int n = 0; n < node.jjtGetNumChildren(); n++) { + Node childNode = node.jjtGetChild(n); + if (childNode instanceof ASTMethodDeclarator) { + methodDeclarator = (ASTMethodDeclarator) childNode; + break; + } + } + + if (methodEntry.decisionPoints >= reportLevel) { + addViolation(data, node, + new String[] { "method", methodDeclarator == null ? "" : methodDeclarator.getImage(), + String.valueOf(methodEntry.decisionPoints), }); + } + } + LOGGER.exiting(CLASS_NAME, "visit(ASTTriggerTimingPointSection)"); + return data; + } + +} diff --git a/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/rule/design/ExcessiveMethodLengthRule.java b/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/rule/design/ExcessiveMethodLengthRule.java new file mode 100644 index 00000000000..2573a21be9f --- /dev/null +++ b/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/rule/design/ExcessiveMethodLengthRule.java @@ -0,0 +1,18 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.plsql.rule.design; + +import net.sourceforge.pmd.lang.plsql.ast.ExecutableCode; + +/** + * This rule detects when a method exceeds a certain threshold. i.e. if a method + * has more than x lines of code. + */ +public class ExcessiveMethodLengthRule extends ExcessiveLengthRule { + public ExcessiveMethodLengthRule() { + super(ExecutableCode.class); + setProperty(MINIMUM_DESCRIPTOR, 100d); + } +} diff --git a/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/rule/codesize/ExcessiveObjectLengthRule.java b/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/rule/design/ExcessiveObjectLengthRule.java similarity index 79% rename from pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/rule/codesize/ExcessiveObjectLengthRule.java rename to pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/rule/design/ExcessiveObjectLengthRule.java index 961cefa0dbb..13a36b4457e 100644 --- a/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/rule/codesize/ExcessiveObjectLengthRule.java +++ b/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/rule/design/ExcessiveObjectLengthRule.java @@ -2,10 +2,9 @@ * BSD-style license; for more info see http://pmd.sourceforge.net/license.html */ -package net.sourceforge.pmd.lang.plsql.rule.codesize; +package net.sourceforge.pmd.lang.plsql.rule.design; import net.sourceforge.pmd.lang.plsql.ast.OracleObject; -import net.sourceforge.pmd.lang.plsql.rule.design.ExcessiveLengthRule; /** * This rule detects when an Oracle object exceeds a certain threshold. i.e. if diff --git a/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/rule/codesize/ExcessivePackageBodyLengthRule.java b/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/rule/design/ExcessivePackageBodyLengthRule.java similarity index 79% rename from pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/rule/codesize/ExcessivePackageBodyLengthRule.java rename to pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/rule/design/ExcessivePackageBodyLengthRule.java index 1b4c202d223..6b21d2d7acd 100644 --- a/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/rule/codesize/ExcessivePackageBodyLengthRule.java +++ b/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/rule/design/ExcessivePackageBodyLengthRule.java @@ -2,10 +2,9 @@ * BSD-style license; for more info see http://pmd.sourceforge.net/license.html */ -package net.sourceforge.pmd.lang.plsql.rule.codesize; +package net.sourceforge.pmd.lang.plsql.rule.design; import net.sourceforge.pmd.lang.plsql.ast.ASTPackageBody; -import net.sourceforge.pmd.lang.plsql.rule.design.ExcessiveLengthRule; /** * This rule detects when a class exceeds a certain threshold. i.e. if a class diff --git a/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/rule/codesize/ExcessivePackageSpecificationLengthRule.java b/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/rule/design/ExcessivePackageSpecificationLengthRule.java similarity index 80% rename from pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/rule/codesize/ExcessivePackageSpecificationLengthRule.java rename to pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/rule/design/ExcessivePackageSpecificationLengthRule.java index f586b42f453..bbe922a2e28 100644 --- a/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/rule/codesize/ExcessivePackageSpecificationLengthRule.java +++ b/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/rule/design/ExcessivePackageSpecificationLengthRule.java @@ -2,10 +2,9 @@ * BSD-style license; for more info see http://pmd.sourceforge.net/license.html */ -package net.sourceforge.pmd.lang.plsql.rule.codesize; +package net.sourceforge.pmd.lang.plsql.rule.design; import net.sourceforge.pmd.lang.plsql.ast.ASTPackageSpecification; -import net.sourceforge.pmd.lang.plsql.rule.design.ExcessiveLengthRule; /** * This rule detects when a class exceeds a certain threshold. i.e. if a class diff --git a/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/rule/design/ExcessiveParameterListRule.java b/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/rule/design/ExcessiveParameterListRule.java new file mode 100644 index 00000000000..87817338993 --- /dev/null +++ b/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/rule/design/ExcessiveParameterListRule.java @@ -0,0 +1,27 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.plsql.rule.design; + +import net.sourceforge.pmd.lang.plsql.ast.ASTFormalParameter; +import net.sourceforge.pmd.lang.plsql.ast.ASTFormalParameters; +import net.sourceforge.pmd.util.NumericConstants; + +/** + * This rule detects an abnormally long parameter list. Note: This counts Nodes, + * and not necessarily parameters, so the numbers may not match up. (But + * topcount and sigma should work.) + */ +public class ExcessiveParameterListRule extends ExcessiveNodeCountRule { + public ExcessiveParameterListRule() { + super(ASTFormalParameters.class); + setProperty(MINIMUM_DESCRIPTOR, 10d); + } + + // Count these nodes, but no others. + @Override + public Object visit(ASTFormalParameter node, Object data) { + return NumericConstants.ONE; + } +} diff --git a/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/rule/codesize/ExcessiveTypeLengthRule.java b/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/rule/design/ExcessiveTypeLengthRule.java similarity index 79% rename from pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/rule/codesize/ExcessiveTypeLengthRule.java rename to pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/rule/design/ExcessiveTypeLengthRule.java index b35a2c5da48..2ba44cdb6a7 100644 --- a/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/rule/codesize/ExcessiveTypeLengthRule.java +++ b/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/rule/design/ExcessiveTypeLengthRule.java @@ -2,10 +2,9 @@ * BSD-style license; for more info see http://pmd.sourceforge.net/license.html */ -package net.sourceforge.pmd.lang.plsql.rule.codesize; +package net.sourceforge.pmd.lang.plsql.rule.design; import net.sourceforge.pmd.lang.plsql.ast.ASTTypeSpecification; -import net.sourceforge.pmd.lang.plsql.rule.design.ExcessiveLengthRule; /** * This rule detects when a class exceeds a certain threshold. i.e. if a class diff --git a/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/rule/design/NPathComplexityRule.java b/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/rule/design/NPathComplexityRule.java new file mode 100644 index 00000000000..0ca015f7237 --- /dev/null +++ b/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/rule/design/NPathComplexityRule.java @@ -0,0 +1,441 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.plsql.rule.design; + +import java.util.ArrayList; +import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; + +import net.sourceforge.pmd.lang.plsql.ast.ASTCaseStatement; +import net.sourceforge.pmd.lang.plsql.ast.ASTCaseWhenClause; +import net.sourceforge.pmd.lang.plsql.ast.ASTConditionalAndExpression; +import net.sourceforge.pmd.lang.plsql.ast.ASTConditionalOrExpression; +import net.sourceforge.pmd.lang.plsql.ast.ASTElseClause; +import net.sourceforge.pmd.lang.plsql.ast.ASTElsifClause; +import net.sourceforge.pmd.lang.plsql.ast.ASTExpression; +import net.sourceforge.pmd.lang.plsql.ast.ASTForStatement; +import net.sourceforge.pmd.lang.plsql.ast.ASTIfStatement; +import net.sourceforge.pmd.lang.plsql.ast.ASTLoopStatement; +import net.sourceforge.pmd.lang.plsql.ast.ASTMethodDeclaration; +import net.sourceforge.pmd.lang.plsql.ast.ASTProgramUnit; +import net.sourceforge.pmd.lang.plsql.ast.ASTReturnStatement; +import net.sourceforge.pmd.lang.plsql.ast.ASTStatement; +import net.sourceforge.pmd.lang.plsql.ast.ASTTriggerTimingPointSection; +import net.sourceforge.pmd.lang.plsql.ast.ASTTriggerUnit; +import net.sourceforge.pmd.lang.plsql.ast.ASTTypeMethod; +import net.sourceforge.pmd.lang.plsql.ast.ASTWhileStatement; +import net.sourceforge.pmd.lang.plsql.ast.ExecutableCode; +import net.sourceforge.pmd.lang.plsql.ast.PLSQLNode; +import net.sourceforge.pmd.lang.plsql.rule.AbstractStatisticalPLSQLRule; +import net.sourceforge.pmd.stat.DataPoint; +import net.sourceforge.pmd.util.NumericConstants; + +/** + * NPath complexity is a measurement of the acyclic execution paths through a + * function. See Nejmeh, Communications of the ACM Feb 1988 pp 188-200. + * + * @author Jason Bennett + */ +public class NPathComplexityRule extends AbstractStatisticalPLSQLRule { + private static final String CLASS_NAME = NPathComplexityRule.class.getCanonicalName(); + private static final Logger LOGGER = Logger.getLogger(NPathComplexityRule.class.getName()); + + public NPathComplexityRule() { + super(); + setProperty(MINIMUM_DESCRIPTOR, 200d); + } + + private int complexityMultipleOf(PLSQLNode node, int npathStart, Object data) { + LOGGER.entering(CLASS_NAME, "complexityMultipleOf(SimpleNode)"); + + int npath = npathStart; + PLSQLNode n; + + for (int i = 0; i < node.jjtGetNumChildren(); i++) { + n = (PLSQLNode) node.jjtGetChild(i); + npath *= (Integer) n.jjtAccept(this, data); + } + + LOGGER.exiting(CLASS_NAME, "complexityMultipleOf(SimpleNode)", npath); + return npath; + } + + @Override + public Object visit(ASTMethodDeclaration node, Object data) { + LOGGER.entering(CLASS_NAME, "visit(ASTMethodDeclaration)"); + int npath = complexityMultipleOf(node, 1, data); + + DataPoint point = new DataPoint(); + point.setNode(node); + point.setScore(1.0 * npath); + point.setMessage(getMessage()); + addDataPoint(point); + + if (LOGGER.isLoggable(Level.FINEST)) { + LOGGER.finest("NPath complexity: " + npath + " for line " + node.getBeginLine() + ", column " + + node.getBeginColumn()); + } + LOGGER.exiting(CLASS_NAME, "visit(ASTMethodDeclaration)", npath); + return Integer.valueOf(npath); + } + + @Override + public Object visit(ASTProgramUnit node, Object data) { + LOGGER.entering(CLASS_NAME, "visit(ASTProgramUnit)"); + int npath = complexityMultipleOf(node, 1, data); + + DataPoint point = new DataPoint(); + point.setNode(node); + point.setScore(1.0 * npath); + point.setMessage(getMessage()); + addDataPoint(point); + + if (LOGGER.isLoggable(Level.FINEST)) { + LOGGER.finest("NPath complexity: " + npath + " for line " + node.getBeginLine() + ", column " + + node.getBeginColumn()); + } + LOGGER.exiting(CLASS_NAME, "visit(ASTProgramUnit)", npath); + return Integer.valueOf(npath); + } + + @Override + public Object visit(ASTTypeMethod node, Object data) { + LOGGER.entering(CLASS_NAME, "visit(ASTTypeMethod)"); + int npath = complexityMultipleOf(node, 1, data); + + DataPoint point = new DataPoint(); + point.setNode(node); + point.setScore(1.0 * npath); + point.setMessage(getMessage()); + addDataPoint(point); + + if (LOGGER.isLoggable(Level.FINEST)) { + LOGGER.finest("NPath complexity: " + npath + " for line " + node.getBeginLine() + ", column " + + node.getBeginColumn()); + } + LOGGER.exiting(CLASS_NAME, "visit(ASTTypeMethod)", npath); + return Integer.valueOf(npath); + } + + @Override + public Object visit(ASTTriggerUnit node, Object data) { + LOGGER.entering(CLASS_NAME, "visit(ASTTriggerUnit)"); + int npath = complexityMultipleOf(node, 1, data); + + DataPoint point = new DataPoint(); + point.setNode(node); + point.setScore(1.0 * npath); + point.setMessage(getMessage()); + addDataPoint(point); + + if (LOGGER.isLoggable(Level.FINEST)) { + LOGGER.finest("NPath complexity: " + npath + " for line " + node.getBeginLine() + ", column " + + node.getBeginColumn()); + } + LOGGER.exiting(CLASS_NAME, "visit(ASTTriggerUnit)", npath); + return Integer.valueOf(npath); + } + + @Override + public Object visit(ASTTriggerTimingPointSection node, Object data) { + LOGGER.entering(CLASS_NAME, "visit(ASTTriggerTimingPointSection)"); + int npath = complexityMultipleOf(node, 1, data); + + DataPoint point = new DataPoint(); + point.setNode(node); + point.setScore(1.0 * npath); + point.setMessage(getMessage()); + addDataPoint(point); + + if (LOGGER.isLoggable(Level.FINEST)) { + LOGGER.finest("NPath complexity: " + npath + " for line " + node.getBeginLine() + ", column " + + node.getBeginColumn()); + } + LOGGER.exiting(CLASS_NAME, "visit(ASTTriggerTimingPointSection)", npath); + return Integer.valueOf(npath); + } + + @Override + public Object visit(PLSQLNode node, Object data) { + LOGGER.entering(CLASS_NAME, "visit(SimpleNode)"); + int npath = complexityMultipleOf(node, 1, data); + LOGGER.exiting(CLASS_NAME, "visit(SimpleNode)", npath); + return Integer.valueOf(npath); + } + + @Override + public Object visit(ASTIfStatement node, Object data) { + LOGGER.entering(CLASS_NAME, "visit(ASTIfStatement)"); + // (npath of if + npath of else (or 1) + bool_comp of if) * npath of + // next + + int boolCompIf = sumExpressionComplexity(node.getFirstChildOfType(ASTExpression.class)); + + int complexity = 0; + + List<PLSQLNode> statementChildren = new ArrayList<>(); + for (int i = 0; i < node.jjtGetNumChildren(); i++) { + if (node.jjtGetChild(i).getClass() == ASTStatement.class + || node.jjtGetChild(i).getClass() == ASTElsifClause.class + || node.jjtGetChild(i).getClass() == ASTElseClause.class) { + statementChildren.add((PLSQLNode) node.jjtGetChild(i)); + } + } + if (LOGGER.isLoggable(Level.FINEST)) { + LOGGER.finest(statementChildren.size() + " statementChildren found for IF statement " + node.getBeginLine() + + ", column " + node.getBeginColumn()); + } + + /* + * SRT if (statementChildren.isEmpty() || statementChildren.size() == 1 + * && ( null != node.getFirstChildOfType(ASTElseClause.class) ) + * //.hasElse() || statementChildren.size() != 1 && ( null == + * node.getFirstChildOfType(ASTElseClause.class) ) // !node.hasElse() ) + * { throw new + * IllegalStateException("If node has wrong number of children"); } + */ + + /* + * @TODO Any explicit Elsif clause(s) and Else clause are included in + * the list of statements // add path for not taking if if (null == + * node.getFirstChildOfType(ASTElsifClause.class) ) // + * !node.hasElse()!node.hasElse()) { complexity++; } + * + * if (null == node.getFirstChildOfType(ASTElseClause.class) ) // + * !node.hasElse()!node.hasElse()) { complexity++; } + */ + + for (PLSQLNode element : statementChildren) { + complexity += (Integer) element.jjtAccept(this, data); + } + + LOGGER.exiting(CLASS_NAME, "visit(ASTIfStatement)", boolCompIf + complexity); + return Integer.valueOf(boolCompIf + complexity); + } + + @Override + public Object visit(ASTElsifClause node, Object data) { + LOGGER.entering(CLASS_NAME, "visit(ASTElsifClause)"); + // (npath of if + npath of else (or 1) + bool_comp of if) * npath of + // next + + int boolCompIf = sumExpressionComplexity(node.getFirstChildOfType(ASTExpression.class)); + + int complexity = 0; + + List<PLSQLNode> statementChildren = new ArrayList<>(); + for (int i = 0; i < node.jjtGetNumChildren(); i++) { + if (node.jjtGetChild(i).getClass() == ASTStatement.class) { + statementChildren.add((PLSQLNode) node.jjtGetChild(i)); + } + } + if (LOGGER.isLoggable(Level.FINEST)) { + LOGGER.finest(statementChildren.size() + " statementChildren found for ELSIF statement " + + node.getBeginLine() + ", column " + node.getBeginColumn()); + } + + /* + * SRT if (statementChildren.isEmpty() || statementChildren.size() == 1 + * && ( null != node.getFirstChildOfType(ASTElseClause.class) ) + * //.hasElse() || statementChildren.size() != 1 && ( null == + * node.getFirstChildOfType(ASTElseClause.class) ) // !node.hasElse() ) + * { throw new + * IllegalStateException("If node has wrong number of children"); } + */ + + for (PLSQLNode element : statementChildren) { + complexity += (Integer) element.jjtAccept(this, data); + } + + LOGGER.exiting(CLASS_NAME, "visit(ASTElsifClause)", boolCompIf + complexity); + return Integer.valueOf(boolCompIf + complexity); + } + + @Override + public Object visit(ASTElseClause node, Object data) { + LOGGER.entering(CLASS_NAME, "visit(ASTElseClause)"); + // (npath of if + npath of else (or 1) + bool_comp of if) * npath of + // next + + int complexity = 0; + + List<PLSQLNode> statementChildren = new ArrayList<>(); + for (int i = 0; i < node.jjtGetNumChildren(); i++) { + if (node.jjtGetChild(i).getClass() == ASTStatement.class) { + statementChildren.add((PLSQLNode) node.jjtGetChild(i)); + } + } + if (LOGGER.isLoggable(Level.FINEST)) { + LOGGER.finest(statementChildren.size() + " statementChildren found for ELSE clause statement " + + node.getBeginLine() + ", column " + node.getBeginColumn()); + } + + for (PLSQLNode element : statementChildren) { + complexity += (Integer) element.jjtAccept(this, data); + } + + LOGGER.exiting(CLASS_NAME, "visit(ASTElseClause)", complexity); + return Integer.valueOf(complexity); + } + + @Override + public Object visit(ASTWhileStatement node, Object data) { + LOGGER.entering(CLASS_NAME, "visit(ASTWhileStatement)"); + // (npath of while + bool_comp of while + 1) * npath of next + + int boolCompWhile = sumExpressionComplexity(node.getFirstChildOfType(ASTExpression.class)); + + Integer nPathWhile = (Integer) ((PLSQLNode) node.getFirstChildOfType(ASTStatement.class)).jjtAccept(this, data); + + LOGGER.exiting(CLASS_NAME, "visit(ASTWhileStatement)", boolCompWhile + nPathWhile + 1); + return Integer.valueOf(boolCompWhile + nPathWhile + 1); + } + + @Override + public Object visit(ASTLoopStatement node, Object data) { + LOGGER.entering(CLASS_NAME, "visit(ASTLoopStatement)"); + // (npath of do + bool_comp of do + 1) * npath of next + + int boolCompDo = sumExpressionComplexity(node.getFirstChildOfType(ASTExpression.class)); + + Integer nPathDo = (Integer) ((PLSQLNode) node.getFirstChildOfType(ASTStatement.class)).jjtAccept(this, data); + + LOGGER.exiting(CLASS_NAME, "visit(ASTLoopStatement)", boolCompDo + nPathDo + 1); + return Integer.valueOf(boolCompDo + nPathDo + 1); + } + + @Override + public Object visit(ASTForStatement node, Object data) { + LOGGER.entering(CLASS_NAME, "visit(ASTForStatement)"); + // (npath of for + bool_comp of for + 1) * npath of next + + int boolCompFor = sumExpressionComplexity(node.getFirstDescendantOfType(ASTExpression.class)); + + Integer nPathFor = (Integer) ((PLSQLNode) node.getFirstChildOfType(ASTStatement.class)).jjtAccept(this, data); + + LOGGER.exiting(CLASS_NAME, "visit(ASTForStatement)", boolCompFor + nPathFor + 1); + return Integer.valueOf(boolCompFor + nPathFor + 1); + } + + @Override + public Object visit(ASTReturnStatement node, Object data) { + LOGGER.entering(CLASS_NAME, "visit(ASTReturnStatement)"); + // return statements are valued at 1, or the value of the boolean + // expression + + ASTExpression expr = node.getFirstChildOfType(ASTExpression.class); + + if (expr == null) { + return NumericConstants.ONE; + } + + int boolCompReturn = sumExpressionComplexity(expr); + int conditionalExpressionComplexity = complexityMultipleOf(expr, 1, data); + + if (conditionalExpressionComplexity > 1) { + boolCompReturn += conditionalExpressionComplexity; + } + + if (boolCompReturn > 0) { + return Integer.valueOf(boolCompReturn); + } + LOGGER.entering(CLASS_NAME, "visit(ASTReturnStatement)", NumericConstants.ONE); + return NumericConstants.ONE; + } + + @Override + public Object visit(ASTCaseWhenClause node, Object data) { + LOGGER.entering(CLASS_NAME, "visit(ASTCaseWhenClause)"); + // bool_comp of switch + sum(npath(case_range)) + + int boolCompSwitch = sumExpressionComplexity(node.getFirstChildOfType(ASTExpression.class)); + + int npath = 1; + int caseRange = 0; + for (int i = 0; i < node.jjtGetNumChildren(); i++) { + PLSQLNode n = (PLSQLNode) node.jjtGetChild(i); + + // Fall-through labels count as 1 for complexity + Integer complexity = (Integer) n.jjtAccept(this, data); + caseRange *= complexity; + } + // add in npath of last label + npath += caseRange; + LOGGER.exiting(CLASS_NAME, "visit(ASTCaseWhenClause)", boolCompSwitch + npath); + return Integer.valueOf(boolCompSwitch + npath); + } + + @Override + public Object visit(ASTCaseStatement node, Object data) { + LOGGER.entering(CLASS_NAME, "visit(ASTCaseStatement)"); + // bool_comp of switch + sum(npath(case_range)) + + int boolCompSwitch = sumExpressionComplexity(node.getFirstChildOfType(ASTExpression.class)); + + int npath = 0; + int caseRange = 0; + for (int i = 0; i < node.jjtGetNumChildren(); i++) { + PLSQLNode n = (PLSQLNode) node.jjtGetChild(i); + + // Fall-through labels count as 1 for complexity + Integer complexity = (Integer) n.jjtAccept(this, data); + caseRange *= complexity; + } + // add in npath of last label + npath += caseRange; + LOGGER.exiting(CLASS_NAME, "visit(ASTCaseStatement)", boolCompSwitch + npath); + return Integer.valueOf(boolCompSwitch + npath); + } + + @Override + public Object visit(ASTConditionalOrExpression node, Object data) { + return NumericConstants.ONE; + } + + /** + * Calculate the boolean complexity of the given expression. NPath boolean + * complexity is the sum of && and || tokens. This is calculated by summing + * the number of children of the &&'s (minus one) and the children of the + * ||'s (minus one). + * + * <p>Note that this calculation applies to Cyclomatic Complexity as well.</p> + * + * @param expr + * control structure expression + * @return complexity of the boolean expression + */ + public static int sumExpressionComplexity(ASTExpression expr) { + LOGGER.entering(CLASS_NAME, "visit(ASTExpression)"); + if (expr == null) { + LOGGER.exiting(CLASS_NAME, "visit(ASTExpression)", 0); + return 0; + } + + List<ASTConditionalAndExpression> andNodes = expr.findDescendantsOfType(ASTConditionalAndExpression.class); + List<ASTConditionalOrExpression> orNodes = expr.findDescendantsOfType(ASTConditionalOrExpression.class); + + int children = 0; + + for (ASTConditionalOrExpression element : orNodes) { + children += element.jjtGetNumChildren(); + children--; + } + + for (ASTConditionalAndExpression element : andNodes) { + children += element.jjtGetNumChildren(); + children--; + } + + LOGGER.exiting(CLASS_NAME, "visit(ASTExpression)", children); + return children; + } + + @Override + public Object[] getViolationParameters(DataPoint point) { + return new String[] { ((ExecutableCode) point.getNode()).getMethodName(), + String.valueOf((int) point.getScore()), }; + } +} diff --git a/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/rule/design/NcssMethodCountRule.java b/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/rule/design/NcssMethodCountRule.java new file mode 100644 index 00000000000..6b9a1b97b15 --- /dev/null +++ b/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/rule/design/NcssMethodCountRule.java @@ -0,0 +1,35 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.plsql.rule.design; + +import net.sourceforge.pmd.lang.plsql.ast.ExecutableCode; +import net.sourceforge.pmd.stat.DataPoint; + +/** + * Non-commented source statement counter for methods. + * + * <p>Analogous to and cribbed from Java version of the rule.</p> + */ +public class NcssMethodCountRule extends AbstractNcssCountRule { + + /** + * Count the size of all non-constructor methods. + */ + public NcssMethodCountRule() { + super(ExecutableCode.class); + setProperty(MINIMUM_DESCRIPTOR, 100d); + } + + @Override + public Object visit(ExecutableCode node, Object data) { + return super.visit(node, data); + } + + @Override + public Object[] getViolationParameters(DataPoint point) { + return new String[] { ((ExecutableCode) point.getNode()).getMethodName(), + String.valueOf((int) point.getScore()), }; + } +} diff --git a/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/rule/codesize/NcssObjectCountRule.java b/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/rule/design/NcssObjectCountRule.java similarity index 98% rename from pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/rule/codesize/NcssObjectCountRule.java rename to pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/rule/design/NcssObjectCountRule.java index 7dfc518fdeb..9bd0ada8a3d 100644 --- a/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/rule/codesize/NcssObjectCountRule.java +++ b/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/rule/design/NcssObjectCountRule.java @@ -2,7 +2,7 @@ * BSD-style license; for more info see http://pmd.sourceforge.net/license.html */ -package net.sourceforge.pmd.lang.plsql.rule.codesize; +package net.sourceforge.pmd.lang.plsql.rule.design; import java.util.logging.Level; import java.util.logging.Logger; diff --git a/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/rule/design/TooManyFieldsRule.java b/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/rule/design/TooManyFieldsRule.java new file mode 100644 index 00000000000..10a2bf7d7d2 --- /dev/null +++ b/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/rule/design/TooManyFieldsRule.java @@ -0,0 +1,93 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.plsql.rule.design; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import net.sourceforge.pmd.lang.ast.Node; +import net.sourceforge.pmd.lang.plsql.ast.ASTInput; +import net.sourceforge.pmd.lang.plsql.ast.ASTPackageSpecification; +import net.sourceforge.pmd.lang.plsql.ast.ASTTypeSpecification; +import net.sourceforge.pmd.lang.plsql.ast.ASTVariableOrConstantDeclaration; +import net.sourceforge.pmd.lang.plsql.ast.PLSQLNode; +import net.sourceforge.pmd.lang.plsql.rule.AbstractPLSQLRule; +import net.sourceforge.pmd.properties.IntegerProperty; +import net.sourceforge.pmd.util.NumericConstants; + +public class TooManyFieldsRule extends AbstractPLSQLRule { + + private static final int DEFAULT_MAXFIELDS = 15; + + private Map<String, Integer> stats; + private Map<String, PLSQLNode> nodes; + + private static final IntegerProperty MAX_FIELDS_DESCRIPTOR = new IntegerProperty("maxfields", + "Max allowable fields", 1, 300, DEFAULT_MAXFIELDS, 1.0f); + + public TooManyFieldsRule() { + definePropertyDescriptor(MAX_FIELDS_DESCRIPTOR); + } + + @Override + public Object visit(ASTInput node, Object data) { + + stats = new HashMap<>(5); + nodes = new HashMap<>(5); + + return super.visit(node, data); + } + + @Override + public Object visit(ASTPackageSpecification node, Object data) { + + int maxFields = getProperty(MAX_FIELDS_DESCRIPTOR); + + List<ASTVariableOrConstantDeclaration> l = node.findDescendantsOfType(ASTVariableOrConstantDeclaration.class); + + for (ASTVariableOrConstantDeclaration fd : l) { + bumpCounterFor(fd); + } + for (String k : stats.keySet()) { + int val = stats.get(k); + Node n = nodes.get(k); + if (val > maxFields) { + addViolation(data, n); + } + } + return data; + } + + @Override + public Object visit(ASTTypeSpecification node, Object data) { + + int maxFields = getProperty(MAX_FIELDS_DESCRIPTOR); + + List<ASTVariableOrConstantDeclaration> l = node.findDescendantsOfType(ASTVariableOrConstantDeclaration.class); + + for (ASTVariableOrConstantDeclaration fd : l) { + bumpCounterFor(fd); + } + for (String k : stats.keySet()) { + int val = stats.get(k); + Node n = nodes.get(k); + if (val > maxFields) { + addViolation(data, n); + } + } + return data; + } + + private void bumpCounterFor(PLSQLNode clazz) { + String key = clazz.getImage(); + if (!stats.containsKey(key)) { + stats.put(key, NumericConstants.ZERO); + nodes.put(key, clazz); + } + Integer i = Integer.valueOf(stats.get(key) + 1); + stats.put(key, i); + } +} diff --git a/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/symboltable/LocalScope.java b/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/symboltable/LocalScope.java index bbd52e10903..c9bf64a4361 100644 --- a/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/symboltable/LocalScope.java +++ b/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/symboltable/LocalScope.java @@ -61,6 +61,7 @@ public Set<NameDeclaration> findVariableHere(PLSQLNameOccurrence occurrence) { return result; } + @Override public String toString() { return "LocalScope:" + getVariableDeclarations().keySet(); } diff --git a/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/symboltable/MethodScope.java b/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/symboltable/MethodScope.java index 17fea72a6ed..a1edc43f7ff 100644 --- a/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/symboltable/MethodScope.java +++ b/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/symboltable/MethodScope.java @@ -75,6 +75,7 @@ public String getName() { return ((AbstractPLSQLNode) node.jjtGetChild(1)).getCanonicalImage(); } + @Override public String toString() { return "MethodScope:" + getVariableDeclarations().keySet(); } diff --git a/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/symboltable/NameFinder.java b/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/symboltable/NameFinder.java index 3ada3cef2de..5a98b2ba93e 100644 --- a/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/symboltable/NameFinder.java +++ b/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/symboltable/NameFinder.java @@ -14,7 +14,6 @@ import net.sourceforge.pmd.lang.plsql.ast.ASTPrimaryExpression; import net.sourceforge.pmd.lang.plsql.ast.ASTPrimaryPrefix; import net.sourceforge.pmd.lang.plsql.ast.ASTPrimarySuffix; -//import net.sourceforge.pmd.lang.plsql.ast.ASTMemberSelector; import net.sourceforge.pmd.lang.plsql.ast.PLSQLNode; public class NameFinder { diff --git a/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/symboltable/PLSQLNameOccurrence.java b/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/symboltable/PLSQLNameOccurrence.java index f52529e0672..fca9e592384 100644 --- a/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/symboltable/PLSQLNameOccurrence.java +++ b/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/symboltable/PLSQLNameOccurrence.java @@ -94,15 +94,11 @@ public boolean isOnLeftHandSide() { * ASTAssignmentOperator)) { return false; } */ - if (isPartOfQualifiedName() /* or is an array type */) { - return false; - } - /* * if (isCompoundAssignment(primaryExpression)) { return false; } */ - return true; + return !isPartOfQualifiedName() /* and not is an array type */; } /* diff --git a/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/symboltable/SourceFileScope.java b/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/symboltable/SourceFileScope.java index 8d953f7c584..d57c7af6579 100644 --- a/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/symboltable/SourceFileScope.java +++ b/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/symboltable/SourceFileScope.java @@ -27,8 +27,8 @@ public String getPackageName() { } /** - * {@inheritDoc} - * + * + * * @throws IllegalArgumentException * if declaration is not a {@link ClassNameDeclaration} */ @@ -40,6 +40,7 @@ public void addDeclaration(NameDeclaration declaration) { super.addDeclaration(declaration); } + @Override public String toString() { return "SourceFileScope: " + getDeclarations().keySet(); } diff --git a/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/symboltable/TypeSet.java b/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/symboltable/TypeSet.java index f43d9b67761..9793550123e 100644 --- a/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/symboltable/TypeSet.java +++ b/pmd-plsql/src/main/java/net/sourceforge/pmd/lang/plsql/symboltable/TypeSet.java @@ -32,6 +32,7 @@ public ExplicitImportResolver(Set<String> importStmts) { this.importStmts = importStmts; } + @Override public Class<?> resolve(String name) throws ClassNotFoundException { for (String importStmt : importStmts) { if (importStmt.endsWith(name)) { @@ -49,6 +50,7 @@ public CurrentPackageResolver(String pkg) { this.pkg = pkg; } + @Override public Class<?> resolve(String name) throws ClassNotFoundException { return Class.forName(pkg + name); } @@ -56,6 +58,7 @@ public Class<?> resolve(String name) throws ClassNotFoundException { // TODO cite the JLS section on implicit imports public static class ImplicitImportResolver implements Resolver { + @Override public Class<?> resolve(String name) throws ClassNotFoundException { return Class.forName("java.lang." + name); } @@ -68,13 +71,16 @@ public ImportOnDemandResolver(Set<String> importStmts) { this.importStmts = importStmts; } + @Override public Class<?> resolve(String name) throws ClassNotFoundException { for (String importStmt : importStmts) { if (importStmt.endsWith("*")) { try { String importPkg = importStmt.substring(0, importStmt.indexOf('*') - 1); return Class.forName(importPkg + '.' + name); - } catch (ClassNotFoundException cnfe) { + } catch (ClassNotFoundException ignored) { + // Ignored, we'll throw a custom exception later, after all import possibilities have + // been checked } } } @@ -97,6 +103,7 @@ public PrimitiveTypeResolver() { primitiveTypes.put("char", char.class); } + @Override public Class<?> resolve(String name) throws ClassNotFoundException { if (!primitiveTypes.containsKey(name)) { throw new ClassNotFoundException(); @@ -106,6 +113,7 @@ public Class<?> resolve(String name) throws ClassNotFoundException { } public static class VoidResolver implements Resolver { + @Override public Class<?> resolve(String name) throws ClassNotFoundException { if ("void".equals(name)) { return void.class; @@ -115,6 +123,7 @@ public Class<?> resolve(String name) throws ClassNotFoundException { } public static class FullyQualifiedNameResolver implements Resolver { + @Override public Class<?> resolve(String name) throws ClassNotFoundException { return Class.forName(name); } @@ -150,7 +159,8 @@ public Class<?> findClass(String name) throws ClassNotFoundException { for (Resolver resolver : resolvers) { try { return resolver.resolve(name); - } catch (ClassNotFoundException cnfe) { + } catch (ClassNotFoundException ignored) { + // Ignored, maybe another resolver might find the class } } diff --git a/pmd-plsql/src/main/resources/category/plsql/bestpractices.xml b/pmd-plsql/src/main/resources/category/plsql/bestpractices.xml new file mode 100644 index 00000000000..909c70cdd61 --- /dev/null +++ b/pmd-plsql/src/main/resources/category/plsql/bestpractices.xml @@ -0,0 +1,78 @@ +<?xml version="1.0"?> + +<ruleset name="Best Practices" + xmlns="http://pmd.sourceforge.net/ruleset/2.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd"> + + <description> +Rules which enforce generally accepted best practices. + </description> + + + <rule name="TomKytesDespair" + language="plsql" + since="5.1" + message="WHEN OTHERS THEN NULL - when you do this, Tom Kyte cries" + class="net.sourceforge.pmd.lang.rule.XPathRule" + externalInfoUrl="${pmd.website.baseurl}/pmd_rules_plsql_bestpractices.html#tomkytesdespair"> + <description> +"WHEN OTHERS THEN NULL" hides all errors - (Re)RAISE an exception or call RAISE_APPLICATION_ERROR + </description> + <priority>3</priority> + <properties> + <property name="xpath"> + <value> +<![CDATA[ +//ExceptionHandler[QualifiedName/@Image='OTHERS' and upper-case(Statement/UnlabelledStatement/Expression/@Image)='NULL'] +]]> + </value> + </property> + </properties> + <example> +<![CDATA[ +CREATE OR REPLACE PACKAGE BODY update_planned_hrs +IS + +PROCEDURE set_new_planned (p_emp_id IN NUMBER, p_project_id IN NUMBER, p_hours IN NUMBER) +IS +BEGIN + UPDATE employee_on_activity ea + SET ea.ea_planned_hours = p_hours + WHERE + ea.ea_emp_id = p_emp_id + AND ea.ea_proj_id = p_project_id; + +EXCEPTION + WHEN NO_DATA_FOUND THEN + RAISE_APPLICATION_ERROR (-20100, 'No such employee or project'); + +END set_new_planned; + +FUNCTION existing_planned (p_emp_id IN NUMBER, p_project_id IN NUMBER) RETURN NUMBER + +IS + +existing_hours NUMBER(4); + +BEGIN + SELECT ea.ea_planned_hours INTO existing_hours + FROM employee_on_activity ea + WHERE + ea.ea_emp_id = p_emp_id + AND ea.ea_proj_id = p_project_id; + + RETURN (existing_hours); + + EXCEPTION + WHEN OTHERS THEN NULL; + + END existing_planned; + +END update_planned_hrs; +/ +]]> + </example> + </rule> + +</ruleset> diff --git a/pmd-plsql/src/main/resources/category/plsql/categories.properties b/pmd-plsql/src/main/resources/category/plsql/categories.properties new file mode 100644 index 00000000000..0fbe9bd42ea --- /dev/null +++ b/pmd-plsql/src/main/resources/category/plsql/categories.properties @@ -0,0 +1,17 @@ +# +# BSD-style license; for more info see http://pmd.sourceforge.net/license.html +# + +rulesets.filenames=\ + category/plsql/bestpractices.xml,\ + category/plsql/codestyle.xml,\ + category/plsql/design.xml,\ + category/plsql/errorprone.xml + +# +# categories without rules +# +# category/plsql/documentation.xml +# category/plsql/multithreading.xml +# category/plsql/performance.xml +# category/plsql/security.xml diff --git a/pmd-plsql/src/main/resources/category/plsql/codestyle.xml b/pmd-plsql/src/main/resources/category/plsql/codestyle.xml new file mode 100644 index 00000000000..719f2c59fe4 --- /dev/null +++ b/pmd-plsql/src/main/resources/category/plsql/codestyle.xml @@ -0,0 +1,180 @@ +<?xml version="1.0"?> + +<ruleset name="Code Style" + xmlns="http://pmd.sourceforge.net/ruleset/2.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd"> + + <description> +Rules which enforce a specific coding style. + </description> + + <rule name="CodeFormat" + language="plsql" + since="6.9.0" + message="Please check the formatting/indentation" + class="net.sourceforge.pmd.lang.plsql.rule.codestyle.CodeFormatRule" + externalInfoUrl="${pmd.website.baseurl}/pmd_rules_plsql_codestyle.html#codeformat"> + <description> +This rule verifies that the PLSQL code is properly formatted. The following checks are executed: + +SQL Queries: + +* The selected columns must be each on a new line +* The keywords (BULK COLLECT INTO, FROM) start on a new line and are indented by one level +* UNION should be on the same indentation level as SELECT +* Each JOIN is on a new line. If there are more than one JOIN conditions, then each condition should be + on a separate line. + +Parameter definitions for procedures: + +* Each parameter should be on a new line +* Variable names as well as their types should be aligned + +Variable declarations: + +* Each variable should be on a new line +* Variable names as well as their types should be aligned + +Calling a procedure: + +* If there are more than 3 parameters + * then named parameters should be used + * and each parameter should be on a new line + </description> + <priority>3</priority> + <example><![CDATA[ +BEGIN + -- select columns each on a separate line + SELECT cmer_id + ,version + ,cmp_id + BULK COLLECT INTO v_cmer_ids + ,v_versions + ,v_cmp_ids + FROM cmer; + + -- each parameter on a new line + PROCEDURE create_prospect( + company_info_in IN prospects.company_info%TYPE -- Organization + ,firstname_in IN persons.firstname%TYPE -- FirstName + ,lastname_in IN persons.lastname%TYPE -- LastName + ); + + -- more than three parameters, each parameter on a separate line + webcrm_marketing.prospect_ins( + cmp_id_in => NULL + ,company_info_in => company_info_in + ,firstname_in => firstname_in + ,lastname_in => lastname_in + ,slt_code_in => NULL + ); + +END; + ]]></example> + </rule> + + <rule name="MisplacedPragma" + language="plsql" + since="5.5.2" + message="Pragma should be used only inside the declaration block before 'BEGIN'." + class="net.sourceforge.pmd.lang.rule.XPathRule" + externalInfoUrl="${pmd.website.baseurl}/pmd_rules_plsql_codestyle.html#misplacedpragma"> + <description> +Oracle states that the PRAQMA AUTONOMOUS_TRANSACTION must be in the declaration block, +but the code does not complain, when being compiled on the 11g DB. +https://docs.oracle.com/cd/B28359_01/appdev.111/b28370/static.htm#BABIIHBJ + </description> + <priority>3</priority> + <properties> + <property name="xpath"> + <value> +<![CDATA[ +//ProgramUnit/Pragma +]]> + </value> + </property> + </properties> + <example> +<![CDATA[ +create or replace package inline_pragma_error is + +end; +/ + +create or replace package body inline_pragma_error is + procedure do_transaction(p_input_token in varchar(200)) is + PRAGMA AUTONOMOUS_TRANSACTION; /* this is correct place for PRAGMA */ + begin + PRAGMA AUTONOMOUS_TRANSACTION; /* this is the wrong place for PRAGMA -> violation */ + /* do something */ + COMMIT; + end do_transaction; + +end inline_pragma_error; +/ +]]> + </example> + </rule> + + <rule name="ForLoopNaming" + language="plsql" + since="6.7.0" + message="Use meaningful names for loop variables" + class="net.sourceforge.pmd.lang.rule.XPathRule" + externalInfoUrl="${pmd.website.baseurl}/pmd_rules_plsql_codestyle.html#forloopnaming"> + <description> +In case you have loops please name the loop variables more meaningful. + </description> + <priority>3</priority> + <properties> + <property name="xpath"> + <value> +<![CDATA[ +//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))] +]]> + </value> + </property> + <property name="allowSimpleLoops" type="Boolean" description="Ignore simple loops, that are not nested" value="false" /> + <property name="cursorPattern" type="Regex" description="The pattern used for the curosr loop variable" value="[a-zA-Z_0-9]{5,}" /> + <property name="indexPattern" type="Regex" description="The pattern used for the index loop variable" value="[a-zA-Z_0-9]{5,}" /> + </properties> + <example> +<![CDATA[ +-- 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; +/ +]]> + </example> + </rule> +</ruleset> diff --git a/pmd-plsql/src/main/resources/category/plsql/design.xml b/pmd-plsql/src/main/resources/category/plsql/design.xml new file mode 100644 index 00000000000..24e549f2aba --- /dev/null +++ b/pmd-plsql/src/main/resources/category/plsql/design.xml @@ -0,0 +1,542 @@ +<?xml version="1.0"?> + +<ruleset name="Design" + xmlns="http://pmd.sourceforge.net/ruleset/2.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd"> + + <description> +Rules that help you discover design issues. + </description> + + <rule name="CyclomaticComplexity" + language="plsql" + since="5.1" + message = "The {0} ''{1}'' has a Cyclomatic Complexity of {2}." + class="net.sourceforge.pmd.lang.plsql.rule.design.CyclomaticComplexityRule" + externalInfoUrl="${pmd.website.baseurl}/pmd_rules_plsql_design.html#cyclomaticcomplexity"> + <description> +Complexity directly affects maintenance costs is determined by the number of decision points in a method +plus one for the method entry. The decision points include 'if', 'while', 'for', and 'case labels' calls. +Generally, numbers ranging from 1-4 denote low complexity, 5-7 denote moderate complexity, 8-10 denote +high complexity, and 11+ is very high complexity. + </description> + <priority>3</priority> + <example> +<![CDATA[ +-- Cyclomatic Complexity of 25 +CREATE OR REPLACE PACKAGE BODY pkg_pmd_working_sequence AS +1 PROCEDURE ty_logger IS BEGIN +2 IF true + THEN + DBMS_OUTPUT.PUT_LINE('IF/THEN l_Integer='||l_integer); +3 IF true + THEN + DBMS_OUTPUT.PUT_LINE('IF/THEN l_Integer='||l_integer); +4 IF true + THEN + DBMS_OUTPUT.PUT_LINE('IF/THEN l_Integer='||l_integer); +5 ELSIF false + THEN + DBMS_OUTPUT.PUT_LINE('ELSIF l_Integer='||l_integer); + ELSE + DBMS_OUTPUT.PUT_LINE('ELSIF l_Integer='||l_integer); + END IF; +6 ELSIF false + THEN + DBMS_OUTPUT.PUT_LINE('ELSIF l_Integer='||l_integer); +7 IF true + THEN + DBMS_OUTPUT.PUT_LINE('IF/THEN l_Integer='||l_integer); +8 ELSIF false + THEN + DBMS_OUTPUT.PUT_LINE('ELSIF l_Integer='||l_integer); + ELSE + DBMS_OUTPUT.PUT_LINE('ELSIF l_Integer='||l_integer); + END IF; + ELSE + DBMS_OUTPUT.PUT_LINE('ELSIF l_Integer='||l_integer); +9 IF true + THEN + DBMS_OUTPUT.PUT_LINE('IF/THEN l_Integer='||l_integer); +10 ELSIF false + THEN + DBMS_OUTPUT.PUT_LINE('ELSIF l_Integer='||l_integer); + ELSE + DBMS_OUTPUT.PUT_LINE('ELSIF l_Integer='||l_integer); + END IF; + END IF; +11 ELSIF false + THEN + DBMS_OUTPUT.PUT_LINE('ELSIF l_Integer='||l_integer); +12 IF true + THEN + DBMS_OUTPUT.PUT_LINE('IF/THEN l_Integer='||l_integer); +13 IF true + THEN + DBMS_OUTPUT.PUT_LINE('IF/THEN l_Integer='||l_integer); +14 ELSIF false + THEN + DBMS_OUTPUT.PUT_LINE('ELSIF l_Integer='||l_integer); + ELSE + DBMS_OUTPUT.PUT_LINE('ELSIF l_Integer='||l_integer); + END IF; +15 ELSIF false + THEN +16 IF true + THEN + DBMS_OUTPUT.PUT_LINE('IF/THEN l_Integer='||l_integer); +17 ELSIF false + THEN + DBMS_OUTPUT.PUT_LINE('ELSIF l_Integer='||l_integer); + ELSE + DBMS_OUTPUT.PUT_LINE('ELSIF l_Integer='||l_integer); + END IF; + DBMS_OUTPUT.PUT_LINE('ELSIF l_Integer='||l_integer); + ELSE + DBMS_OUTPUT.PUT_LINE('ELSIF l_Integer='||l_integer); + END IF; + ELSE + DBMS_OUTPUT.PUT_LINE('ELSIF l_Integer='||l_integer); +18 IF true + THEN + DBMS_OUTPUT.PUT_LINE('IF/THEN l_Integer='||l_integer); +19 IF true + THEN + DBMS_OUTPUT.PUT_LINE('IF/THEN l_Integer='||l_integer); +20 ELSIF false + THEN + DBMS_OUTPUT.PUT_LINE('ELSIF l_Integer='||l_integer); + ELSE + DBMS_OUTPUT.PUT_LINE('ELSIF l_Integer='||l_integer); + END IF; +21 ELSIF false + THEN + DBMS_OUTPUT.PUT_LINE('ELSIF l_Integer='||l_integer); +22 IF true + THEN + DBMS_OUTPUT.PUT_LINE('IF/THEN l_Integer='||l_integer); +23 ELSIF false + THEN + DBMS_OUTPUT.PUT_LINE('ELSIF l_Integer='||l_integer); + ELSE + DBMS_OUTPUT.PUT_LINE('ELSIF l_Integer='||l_integer); + END IF; + ELSE + DBMS_OUTPUT.PUT_LINE('ELSIF l_Integer='||l_integer); +24 IF true + THEN + DBMS_OUTPUT.PUT_LINE('IF/THEN l_Integer='||l_integer); +25 ELSIF false + THEN + DBMS_OUTPUT.PUT_LINE('ELSIF l_Integer='||l_integer); + ELSE + DBMS_OUTPUT.PUT_LINE('ELSIF l_Integer='||l_integer); + END IF; + END IF; + END IF; +END; + +END; +]]> + </example> + </rule> + + <rule name="ExcessiveMethodLength" + language="plsql" + since="5.1" + message="Avoid really long methods ({0} lines found)." + class="net.sourceforge.pmd.lang.plsql.rule.design.ExcessiveMethodLengthRule" + externalInfoUrl="${pmd.website.baseurl}/pmd_rules_plsql_design.html#excessivemethodlength"> + <description> +When methods are excessively long this usually indicates that the method is doing more than its +name/signature might suggest. They also become challenging for others to digest since excessive +scrolling causes readers to lose focus. +Try to reduce the method length by creating helper methods and removing any copy/pasted code. + </description> + <priority>3</priority> + <example> +<![CDATA[ +CREATE OR REPLACE +PROCEDURE doSomething BEGIN + DBMS_OUTPUT.PUT_LINE("Hello world!"); + DBMS_OUTPUT.PUT_LINE("Hello world!"); + -- 98 copies omitted for brevity. +END; +]]> + </example> + </rule> + + <rule name="ExcessiveObjectLength" + language="plsql" + since="5.1" + message="Avoid really long Oracle object specifications and bodies ({0} lines found)." + class="net.sourceforge.pmd.lang.plsql.rule.design.ExcessiveObjectLengthRule" + externalInfoUrl="${pmd.website.baseurl}/pmd_rules_plsql_design.html#excessiveobjectlength"> + <description> +Excessive object line lengths are usually indications that the object may be burdened with excessive +responsibilities that could be provided by other objects. In breaking these methods +apart the code becomes more managable and ripe for reuse. + </description> + <priority>3</priority> + <example> +<![CDATA[ +CREATE OR REPLACE +PACKAGE BODY Foo AS + PROCEDURE bar1 IS BEGIN + -- 1000 lines of code + END bar1; + PROCEDURE bar2 IS BEGIN + -- 1000 lines of code + END bar2; + PROCEDURE bar3 IS BEGIN + -- 1000 lines of code + END bar3; + + + PROCEDURE barN IS BEGIN + -- 1000 lines of code + END barn; +END; +]]> + </example> + </rule> + + <rule name="ExcessivePackageBodyLength" + language="plsql" + since="5.1" + message="Avoid really long Object Type and Package bodies ({0} lines found)." + class="net.sourceforge.pmd.lang.plsql.rule.design.ExcessivePackageBodyLengthRule" + externalInfoUrl="${pmd.website.baseurl}/pmd_rules_plsql_design.html#excessivepackagebodylength"> + <description> +Excessive class file lengths are usually indications that the class may be burdened with excessive +responsibilities that could be provided by external classes or functions. In breaking these methods +apart the code becomes more managable and ripe for reuse. + </description> + <priority>3</priority> + <example> +<![CDATA[ +CREATE OR REPLACE +PACKAGE BODY Foo AS + PROCEDURE bar1 IS BEGIN + -- 1000 lines of code + END bar1; + PROCEDURE bar2 IS BEGIN + -- 1000 lines of code + END bar2; + PROCEDURE bar3 IS BEGIN + -- 1000 lines of code + END bar3; + + + PROCEDURE barN IS BEGIN + -- 1000 lines of code + END barn; +END; +]]> + </example> + </rule> + + <rule name="ExcessivePackageSpecificationLength" + language="plsql" + since="5.1" + message="Avoid really long Package Specifications ({0} lines found)." + class="net.sourceforge.pmd.lang.plsql.rule.design.ExcessivePackageSpecificationLengthRule" + externalInfoUrl="${pmd.website.baseurl}/pmd_rules_plsql_design.html#excessivepackagespecificationlength"> + <description> +Excessive class file lengths are usually indications that the class may be burdened with excessive +responsibilities that could be provided by external classes or functions. In breaking these methods +apart the code becomes more managable and ripe for reuse. + </description> + <priority>3</priority> + <example> +<![CDATA[ +CREATE OR REPLACE +PACKAGE Foo AS + PROCEDURE bar1; + PROCEDURE bar2; + PROCEDURE bar3; + + ... + + PROCEDURE barN; +END; +]]> + </example> + </rule> + + <rule name="ExcessiveParameterList" + language="plsql" + since="5.1" + message="Avoid long parameter lists." + class="net.sourceforge.pmd.lang.plsql.rule.design.ExcessiveParameterListRule" + externalInfoUrl="${pmd.website.baseurl}/pmd_rules_plsql_design.html#excessiveparameterlist"> + <description> +Methods with numerous parameters are a challenge to maintain, especially if most of them share the +same datatype. These situations usually denote the need for new objects to wrap the numerous parameters. + </description> + <priority>3</priority> + <example> +<![CDATA[ +CREATE OR REPLACE +PROCEDURE addPerson( -- too many arguments liable to be mixed up + birthYear pls_integer, birthMonth pls_integer, birthDate pls_integer, height pls_integer, weight pls_integer, ssn pls_integer) { + + . . . . +END ADDPERSON; + +CREATE OR REPLACE +PROCEDURE addPerson( -- preferred approach + birthdate DATE, measurements BodyMeasurements , ssn INTEGER) BEGIN + + . . . . +END; +]]> + </example> + </rule> + +<!-- + <rule name="ExcessivePublicCount" + language="plsql" + since="5.1" + message="This class has a bunch of public methods and attributes" + class="net.sourceforge.pmd.lang.plsql.rule.design.ExcessivePublicCountRule" + externalInfoUrl="${pmd.website.baseurl}/pmd_rules_plsql_codesize.html#excessivepubliccount"> + <description> +Classes with large numbers of public methods and attributes require disproportionate testing efforts +since combinational side effects grow rapidly and increase risk. Refactoring these classes into +smaller ones not only increases testability and reliability but also allows new variations to be +developed easily. + </description> + <priority>3</priority> + <example> +<![CDATA[ +public class Foo { + public String value; + public Bar something; + public Variable var; +// [... more more public attributes ...] + + public void doWork() {} + public void doMoreWork() {} + public void doWorkAgain() {} +// [... more more public methods ...] +} +]]> + </example> + </rule> +--> + + <rule name="ExcessiveTypeLength" + language="plsql" + since="5.1" + message="Avoid really long Object Type specifications ({0} lines found)." + class="net.sourceforge.pmd.lang.plsql.rule.design.ExcessiveTypeLengthRule" + externalInfoUrl="${pmd.website.baseurl}/pmd_rules_plsql_design.html#excessivetypelength"> + <description> +Excessive class file lengths are usually indications that the class may be burdened with excessive +responsibilities that could be provided by external classes or functions. In breaking these methods +apart the code becomes more managable and ripe for reuse. + </description> + <priority>3</priority> + <example> +<![CDATA[ +CREATE OR REPLACE +TYPE BODY Foo AS + MEMBER PROCEDURE bar1 IS BEGIN + -- 1000 lines of code + END bar1; + MEMBER PROCEDURE bar2 IS BEGIN + -- 1000 lines of code + END bar2; + MEMBER PROCEDURE bar3 IS BEGIN + -- 1000 lines of code + END bar3; + + + MEMBER PROCEDURE barN IS BEGIN + -- 1000 lines of code + END barn; +END; +]]> + </example> + </rule> + + <rule name="NcssMethodCount" + message="The method {0}() has an NCSS line count of {1}" + language="plsql" + since="5.1" + class="net.sourceforge.pmd.lang.plsql.rule.design.NcssMethodCountRule" + externalInfoUrl="${pmd.website.baseurl}/pmd_rules_plsql_design.html#ncssmethodcount"> + <description> +This rule uses the NCSS (Non-Commenting Source Statements) algorithm to determine the number of lines +of code for a given method. NCSS ignores comments, and counts actual statements. Using this algorithm, +lines of code that are split are counted as one. + </description> + <priority>3</priority> + <example> +<![CDATA[ +CREATE OR REPLACE PACKAGE BODY AS + FUNCTION methd RETURN INTEGER IS + BEGIN + RETURN 1;; + END; +END; +]]> + </example> + </rule> + + <rule name="NcssObjectCount" + message="The Oracle object has an NCSS line count of {0}" + language="plsql" + since="5.1" + class="net.sourceforge.pmd.lang.plsql.rule.design.NcssObjectCountRule" + externalInfoUrl="${pmd.website.baseurl}/pmd_rules_plsql_design.html#ncssobjectcount"> + <description> +This rule uses the NCSS (Non-Commenting Source Statements) algorithm to determine the number of lines +of code for a given Oracle object. NCSS ignores comments, and counts actual statements. Using this algorithm, +lines of code that are split are counted as one. + </description> + <priority>3</priority> + <example> +<![CDATA[ +CREATE OR REPLACE PACKAGE pkg_ + PROCEDURE Foo IS + BEGIN + --this class only has 6 NCSS lines + super(); + super(); + END; +} +]]> + </example> + </rule> + + <rule name="NPathComplexity" + language="plsql" + since="5.1" + message="The method {0}() has an NPath complexity of {1}" + class="net.sourceforge.pmd.lang.plsql.rule.design.NPathComplexityRule" + externalInfoUrl="${pmd.website.baseurl}/pmd_rules_plsql_design.html#npathcomplexity"> + <description> +The NPath complexity of a method is the number of acyclic execution paths through that method. +A threshold of 200 is generally considered the point where measures should be taken to reduce +complexity and increase readability. + </description> + <priority>3</priority> + <example> +<![CDATA[ +CREATE OR REPLACE +PROCEDURE bar AS BEGIN -- this is something more complex than it needs to be, + if (y) THEN -- it should be broken down into smaller methods or functions + for j IN 0 .. j-1 LOOP + if (j > r) THEN + doSomething; + while (f < 5 ) LOOP + anotherThing; + f := f - 27; + END LOOP; + else + tryThis(); + END IF; + END LOOP; + END IF; + if ( r - n > 45) THEN + while (doMagic) LOOP + findRabbits; + END LOOP; + END IF; + BEGIN + doSomethingDangerous(); + EXCEPTION WHEN FooException THEN + makeAmends; + BEGIN + dontDoItAgain; + EXCEPTION + WHEN OTHERS THEN + log_problem; + END; + END; +END; +]]> + </example> + </rule> + + <rule name="TooManyFields" + language="plsql" + since="5.1" + message="Too many fields" + class="net.sourceforge.pmd.lang.plsql.rule.design.TooManyFieldsRule" + externalInfoUrl="${pmd.website.baseurl}/pmd_rules_plsql_design.html#toomanyfields"> + <description> +Classes that have too many fields can become unwieldy and could be redesigned to have fewer fields, +possibly through grouping related fields in new objects. For example, a class with individual +city/state/zip fields could park them within a single Address field. + </description> + <priority>3</priority> + <example> +<![CDATA[ +CREATE OR REPLACE PACKAGE pkg_too_many_fields AS + C_CHAR_A CONSTANT CHAR(1 CHAR) := 'A'; + C_CHAR_B CONSTANT CHAR(1 CHAR) := 'B'; + ... + C_CHAR_Z CONSTANT CHAR(1 CHAR) := 'Z'; +END pkg_too_many_fields; +]]> + </example> + </rule> + + <rule name="TooManyMethods" + language="plsql" + since="5.1" + class="net.sourceforge.pmd.lang.rule.XPathRule" + message="This object has too many methods, consider refactoring it." + externalInfoUrl="${pmd.website.baseurl}/pmd_rules_plsql_design.html#toomanymethods"> + <description> +A package or type with too many methods is probably a good suspect for refactoring, in order to reduce its complexity and find a way to +have more fine grained objects. + </description> + <priority>3</priority> + <properties> + <property name="maxmethods" type="Integer" description="The method count reporting threshold" min="1" max="1000" value="1"/> + <property name="xpath"> + <value> +<![CDATA[ +//node() + [ ( + local-name(.) = 'PackageSpecification' + or + local-name(.) = 'TypeSpecification' + ) + and + ( + count(/descendant::ProgramUnit[ + not ( + starts-with(@Image,'get') + or + starts-with(@Image,'set') + or + starts-with(@Image,'is') + ) + ] + ) + + + count(/descendant::TypeMethod[ + not ( + starts-with(@Image,'get') + or + starts-with(@Image,'set') + or + starts-with(@Image,'is') + ) + ] + ) + ) > $maxmethods + ] +]]> + </value> + </property> + </properties> + </rule> + +</ruleset> diff --git a/pmd-plsql/src/main/resources/category/plsql/documentation.xml b/pmd-plsql/src/main/resources/category/plsql/documentation.xml new file mode 100644 index 00000000000..c049f5b6f01 --- /dev/null +++ b/pmd-plsql/src/main/resources/category/plsql/documentation.xml @@ -0,0 +1,11 @@ +<?xml version="1.0"?> + +<ruleset name="Documentation" + xmlns="http://pmd.sourceforge.net/ruleset/2.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd"> + + <description> +Rules that are related to code documentation. + </description> +</ruleset> diff --git a/pmd-plsql/src/main/resources/category/plsql/errorprone.xml b/pmd-plsql/src/main/resources/category/plsql/errorprone.xml new file mode 100644 index 00000000000..ccc1780ef64 --- /dev/null +++ b/pmd-plsql/src/main/resources/category/plsql/errorprone.xml @@ -0,0 +1,158 @@ +<?xml version="1.0"?> + +<ruleset name="Error Prone" + xmlns="http://pmd.sourceforge.net/ruleset/2.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd"> + + <description> +Rules to detect constructs that are either broken, extremely confusing or prone to runtime errors. + </description> + + <rule name="TO_DATE_TO_CHAR" + language="plsql" + since="5.1" + message="TO_DATE(TO_CHAR(variable)) instead of TRUNC(variable)" + class="net.sourceforge.pmd.lang.rule.XPathRule" + externalInfoUrl="${pmd.website.baseurl}/pmd_rules_plsql_errorprone.html#to_date_to_char"> + <description> +TO_DATE(TO_CHAR(date-variable)) used to remove time component - use TRUNC(date-variable) + </description> + <priority>3</priority> + <properties> + <property name="xpath"> + <value> +<![CDATA[ +//PrimaryExpression + [PrimaryPrefix/Name/@Image='TO_DATE'] + [count(PrimarySuffix/Arguments/ArgumentList/Argument) = 1] + [.//PrimaryExpression + [PrimaryPrefix/Name/@Image='TO_CHAR'] + [count(PrimarySuffix/Arguments/ArgumentList/Argument) = 1] + ] +]]> + </value> + </property> + </properties> + <example> +<![CDATA[ +CREATE OR REPLACE PACKAGE BODY date_utilities +IS + +-- Take single parameter, relying on current default NLS date format +FUNCTION strip_time (p_date IN DATE) RETURN DATE +IS +BEGIN + RETURN TO_DATE(TO_CHAR(p_date)); +END strip_time; + + +END date_utilities; +/ +]]> + </example> + </rule> + + <rule name="TO_DATEWithoutDateFormat" + language="plsql" + since="5.1" + message="TO_DATE without date format" + class="net.sourceforge.pmd.lang.rule.XPathRule" + externalInfoUrl="${pmd.website.baseurl}/pmd_rules_plsql_errorprone.html#to_datewithoutdateformat"> + <description> +TO_DATE without date format- use TO_DATE(expression, date-format) + </description> + <priority>3</priority> + <properties> + <property name="xpath"> + <value> +<![CDATA[ +//PrimaryExpression[PrimaryPrefix/Name/@Image='TO_DATE' and count(PrimarySuffix/Arguments/ArgumentList/Argument) = 1 ] +]]> + </value> + </property> + </properties> + <example> +<![CDATA[ +CREATE OR REPLACE PACKAGE BODY date_utilities +IS + +-- Take single parameter, relying on current default NLS date format +FUNCTION to_date_single_parameter (p_date_string IN VARCHAR2) RETURN DATE +IS +BEGIN + RETURN TO_DATE(p_date_string); +END to_date_single_parameter ; + +-- Take 2 parameters, using an explicit date format string +FUNCTION to_date_two_parameters (p_date_string IN VARCHAR2, p_format_mask IN VARCHAR2) RETURN DATE +IS +BEGIN + TO_DATE(p_date_string, p_date_format); +END to_date_two_parameters; + +-- Take 3 parameters, using an explicit date format string and an explicit language +FUNCTION to_date_three_parameters (p_date_string IN VARCHAR2, p_format_mask IN VARCHAR2, p_nls_language VARCHAR2 ) RETURN DATE +IS +BEGIN + TO_DATE(p_date_string, p_format_mask, p_nls_language); +END to_date_three_parameters; + +END date_utilities; +/ +]]> + </example> + </rule> + + <rule name="TO_TIMESTAMPWithoutDateFormat" + language="plsql" + message="TO_TIMESTAMP without date format" + class="net.sourceforge.pmd.lang.rule.XPathRule" + since="5.1" + externalInfoUrl="${pmd.website.baseurl}/pmd_rules_plsql_errorprone.html#to_timestampwithoutdateformat"> + <description> +TO_TIMESTAMP without date format- use TO_TIMESTAMP(expression, date-format) + </description> + <priority>3</priority> + <properties> + <property name="xpath"> + <value> +<![CDATA[ +//PrimaryExpression[PrimaryPrefix/Name/@Image='TO_TIMESTAMP' and count(PrimarySuffix/Arguments/ArgumentList/Argument) = 1 ] +]]> + </value> + </property> + </properties> + <example> +<![CDATA[ +CREATE OR REPLACE PACKAGE BODY date_utilities +IS + +-- Take single parameter, relying on current default NLS date format +FUNCTION to_timestamp_single_parameter (p_date_string IN VARCHAR2) RETURN DATE +IS +BEGIN + RETURN TO_TIMESTAMP(p_date_string); +END to_timestamp_single_parameter; + +-- Take 2 parameters, using an explicit date format string +FUNCTION to_timestamp_two_parameters (p_date_string IN VARCHAR2, p_format_mask IN VARCHAR2) RETURN DATE +IS +BEGIN + TO_TIMESTAMP(p_date_string, p_date_format); +END to_timestamp_two_parameters; + +-- Take 3 parameters, using an explicit date format string and an explicit language +FUNCTION to_timestamp_three_parameters (p_date_string IN VARCHAR2, p_format_mask IN VARCHAR2, p_nls_language VARCHAR2 ) RETURN DATE +IS +BEGIN + TO_TIMESTAMP(p_date_string, p_format_mask, p_nls_language); +END to_timestamp_three_parameters; + +END date_utilities; +/ +]]> + </example> + </rule> + +</ruleset> diff --git a/pmd-plsql/src/main/resources/category/plsql/multithreading.xml b/pmd-plsql/src/main/resources/category/plsql/multithreading.xml new file mode 100644 index 00000000000..88c22dfc8bc --- /dev/null +++ b/pmd-plsql/src/main/resources/category/plsql/multithreading.xml @@ -0,0 +1,11 @@ +<?xml version="1.0"?> + +<ruleset name="Multithreading" + xmlns="http://pmd.sourceforge.net/ruleset/2.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd"> + + <description> +Rules that flag issues when dealing with multiple threads of execution. + </description> +</ruleset> diff --git a/pmd-plsql/src/main/resources/category/plsql/performance.xml b/pmd-plsql/src/main/resources/category/plsql/performance.xml new file mode 100644 index 00000000000..251663aafc1 --- /dev/null +++ b/pmd-plsql/src/main/resources/category/plsql/performance.xml @@ -0,0 +1,11 @@ +<?xml version="1.0"?> + +<ruleset name="Performance" + xmlns="http://pmd.sourceforge.net/ruleset/2.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd"> + + <description> +Rules that flag suboptimal code. + </description> +</ruleset> diff --git a/pmd-plsql/src/main/resources/category/plsql/security.xml b/pmd-plsql/src/main/resources/category/plsql/security.xml new file mode 100644 index 00000000000..d6fa5415a54 --- /dev/null +++ b/pmd-plsql/src/main/resources/category/plsql/security.xml @@ -0,0 +1,11 @@ +<?xml version="1.0"?> + +<ruleset name="Security" + xmlns="http://pmd.sourceforge.net/ruleset/2.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd"> + + <description> +Rules that flag potential security flaws. + </description> +</ruleset> diff --git a/pmd-plsql/src/main/resources/rulesets/plsql/TomKytesDespair.xml b/pmd-plsql/src/main/resources/rulesets/plsql/TomKytesDespair.xml index b7147f6af4c..8116934a187 100644 --- a/pmd-plsql/src/main/resources/rulesets/plsql/TomKytesDespair.xml +++ b/pmd-plsql/src/main/resources/rulesets/plsql/TomKytesDespair.xml @@ -2,75 +2,12 @@ <ruleset name="Tom Kyte's Despair" xmlns="http://pmd.sourceforge.net/ruleset/2.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 http://pmd.sourceforge.net/ruleset_2_0_0.xsd"> + xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd"> <description> Rules based on Thomas Kyte's recommendations on http://asktom.oracle.com/ and http://tkyte.blogspot.com/. </description> - <rule name="TomKytesDespair" - language="plsql" - since="5.1" - message="WHEN OTHERS THEN NULL - when you do this, Tom Kyte cries" - class="net.sourceforge.pmd.lang.rule.XPathRule" - externalInfoUrl="${pmd.website.baseurl}/pmd_rules_plsql_TomKytesDespair.html#tomkytesdespair"> - <description> -"WHEN OTHERS THEN NULL" hides all errors - (Re)RAISE an exception or call RAISE_APPLICATION_ERROR - </description> - <priority>3</priority> - <properties> - <property name="xpath"> - <value> -<![CDATA[ -//ExceptionHandler[QualifiedName/@Image='OTHERS' and upper-case(Statement/UnlabelledStatement/Expression/@Image)='NULL'] -]]> - </value> - </property> - </properties> - <example> -<![CDATA[ -CREATE OR REPLACE PACKAGE BODY update_planned_hrs -IS - -PROCEDURE set_new_planned (p_emp_id IN NUMBER, p_project_id IN NUMBER, p_hours IN NUMBER) -IS -BEGIN - UPDATE employee_on_activity ea - SET ea.ea_planned_hours = p_hours - WHERE - ea.ea_emp_id = p_emp_id - AND ea.ea_proj_id = p_project_id; - -EXCEPTION - WHEN NO_DATA_FOUND THEN - RAISE_APPLICATION_ERROR (-20100, 'No such employee or project'); - -END set_new_planned; - -FUNCTION existing_planned (p_emp_id IN NUMBER, p_project_id IN NUMBER) RETURN NUMBER - -IS - -existing_hours NUMBER(4); - -BEGIN - SELECT ea.ea_planned_hours INTO existing_hours - FROM employee_on_activity ea - WHERE - ea.ea_emp_id = p_emp_id - AND ea.ea_proj_id = p_project_id; - - RETURN (existing_hours); - - EXCEPTION - WHEN OTHERS THEN NULL; - - END existing_planned; - -END update_planned_hrs; -/ -]]> - </example> - </rule> + <rule ref="category/plsql/bestpractices.xml/TomKytesDespair" deprecated="true" /> </ruleset> diff --git a/pmd-plsql/src/main/resources/rulesets/plsql/codesize.xml b/pmd-plsql/src/main/resources/rulesets/plsql/codesize.xml index a0b80cbc432..fb9872933af 100644 --- a/pmd-plsql/src/main/resources/rulesets/plsql/codesize.xml +++ b/pmd-plsql/src/main/resources/rulesets/plsql/codesize.xml @@ -2,543 +2,23 @@ <ruleset name="Code Size" xmlns="http://pmd.sourceforge.net/ruleset/2.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 http://pmd.sourceforge.net/ruleset_2_0_0.xsd"> + xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd"> <description> The Code Size ruleset contains rules that find problems related to code size or complexity. </description> - <rule name="NPathComplexity" - language="plsql" - since="5.1" - message="The method {0}() has an NPath complexity of {1}" - class="net.sourceforge.pmd.lang.plsql.rule.codesize.NPathComplexityRule" - externalInfoUrl="${pmd.website.baseurl}/pmd_rules_plsql_codesize.html#npathcomplexity"> - <description> -The NPath complexity of a method is the number of acyclic execution paths through that method. -A threshold of 200 is generally considered the point where measures should be taken to reduce -complexity and increase readability. - </description> - <priority>3</priority> - <example> -<![CDATA[ -CREATE OR REPLACE -PROCEDURE bar AS BEGIN -- this is something more complex than it needs to be, - if (y) THEN -- it should be broken down into smaller methods or functions - for j IN 0 .. j-1 LOOP - if (j > r) THEN - doSomething; - while (f < 5 ) LOOP - anotherThing; - f := f - 27; - END LOOP; - else - tryThis(); - END IF; - END LOOP; - END IF; - if ( r - n > 45) THEN - while (doMagic) LOOP - findRabbits; - END LOOP; - END IF; - BEGIN - doSomethingDangerous(); - EXCEPTION WHEN FooException THEN - makeAmends; - BEGIN - dontDoItAgain; - EXCEPTION - WHEN OTHERS THEN - log_problem; - END; - END; -END; -]]> - </example> - </rule> - - <rule name="ExcessiveMethodLength" - language="plsql" - since="5.1" - message="Avoid really long methods ({0} lines found)." - class="net.sourceforge.pmd.lang.plsql.rule.codesize.ExcessiveMethodLengthRule" - externalInfoUrl="${pmd.website.baseurl}/pmd_rules_plsql_codesize.html#excessivemethodlength"> - <description> -When methods are excessively long this usually indicates that the method is doing more than its -name/signature might suggest. They also become challenging for others to digest since excessive -scrolling causes readers to lose focus. -Try to reduce the method length by creating helper methods and removing any copy/pasted code. - </description> - <priority>3</priority> - <example> -<![CDATA[ -CREATE OR REPLACE -PROCEDURE doSomething BEGIN - DBMS_OUTPUT.PUT_LINE("Hello world!"); - DBMS_OUTPUT.PUT_LINE("Hello world!"); - -- 98 copies omitted for brevity. -END; -]]> - </example> - </rule> - - <rule name="ExcessiveParameterList" - language="plsql" - since="5.1" - message="Avoid long parameter lists." - class="net.sourceforge.pmd.lang.plsql.rule.codesize.ExcessiveParameterListRule" - externalInfoUrl="${pmd.website.baseurl}/pmd_rules_plsql_codesize.html#excessiveparameterlist"> - <description> -Methods with numerous parameters are a challenge to maintain, especially if most of them share the -same datatype. These situations usually denote the need for new objects to wrap the numerous parameters. - </description> - <priority>3</priority> - <example> -<![CDATA[ -CREATE OR REPLACE -PROCEDURE addPerson( -- too many arguments liable to be mixed up - birthYear pls_integer, birthMonth pls_integer, birthDate pls_integer, height pls_integer, weight pls_integer, ssn pls_integer) { - - . . . . -END ADDPERSON; - -CREATE OR REPLACE -PROCEDURE addPerson( -- preferred approach - birthdate DATE, measurements BodyMeasurements , ssn INTEGER) BEGIN - - . . . . -END; -]]> - </example> - </rule> - - <rule name="ExcessiveObjectLength" - language="plsql" - since="5.1" - message="Avoid really long Oracle object specifications and bodies ({0} lines found)." - class="net.sourceforge.pmd.lang.plsql.rule.codesize.ExcessiveObjectLengthRule" - externalInfoUrl="${pmd.website.baseurl}/pmd_rules_plsql_codesize.html#excessiveobjectlength"> - <description> -Excessive object line lengths are usually indications that the object may be burdened with excessive -responsibilities that could be provided by other objects. In breaking these methods -apart the code becomes more managable and ripe for reuse. - </description> - <priority>3</priority> - <example> -<![CDATA[ -CREATE OR REPLACE -PACKAGE BODY Foo AS - PROCEDURE bar1 IS BEGIN - -- 1000 lines of code - END bar1; - PROCEDURE bar2 IS BEGIN - -- 1000 lines of code - END bar2; - PROCEDURE bar3 IS BEGIN - -- 1000 lines of code - END bar3; - - - PROCEDURE barN IS BEGIN - -- 1000 lines of code - END barn; -END; -]]> - </example> - </rule> - - - <rule name="ExcessiveTypeLength" - language="plsql" - since="5.1" - message="Avoid really long Object Type specifications ({0} lines found)." - class="net.sourceforge.pmd.lang.plsql.rule.codesize.ExcessiveTypeLengthRule" - externalInfoUrl="${pmd.website.baseurl}/pmd_rules_plsql_codesize.html#excessivetypelength"> - <description> -Excessive class file lengths are usually indications that the class may be burdened with excessive -responsibilities that could be provided by external classes or functions. In breaking these methods -apart the code becomes more managable and ripe for reuse. - </description> - <priority>3</priority> - <example> -<![CDATA[ -CREATE OR REPLACE -TYPE BODY Foo AS - MEMBER PROCEDURE bar1 IS BEGIN - -- 1000 lines of code - END bar1; - MEMBER PROCEDURE bar2 IS BEGIN - -- 1000 lines of code - END bar2; - MEMBER PROCEDURE bar3 IS BEGIN - -- 1000 lines of code - END bar3; - - - MEMBER PROCEDURE barN IS BEGIN - -- 1000 lines of code - END barn; -END; -]]> - </example> - </rule> - - <rule name="ExcessivePackageBodyLength" - language="plsql" - since="5.1" - message="Avoid really long Object Type and Package bodies ({0} lines found)." - class="net.sourceforge.pmd.lang.plsql.rule.codesize.ExcessivePackageBodyLengthRule" - externalInfoUrl="${pmd.website.baseurl}/pmd_rules_plsql_codesize.html#excessivepackagebodylength"> - <description> -Excessive class file lengths are usually indications that the class may be burdened with excessive -responsibilities that could be provided by external classes or functions. In breaking these methods -apart the code becomes more managable and ripe for reuse. - </description> - <priority>3</priority> - <example> -<![CDATA[ -CREATE OR REPLACE -PACKAGE BODY Foo AS - PROCEDURE bar1 IS BEGIN - -- 1000 lines of code - END bar1; - PROCEDURE bar2 IS BEGIN - -- 1000 lines of code - END bar2; - PROCEDURE bar3 IS BEGIN - -- 1000 lines of code - END bar3; - - - PROCEDURE barN IS BEGIN - -- 1000 lines of code - END barn; -END; -]]> - </example> - </rule> - - - <rule name="ExcessivePackageSpecificationLength" - language="plsql" - since="5.1" - message="Avoid really long Package Specifications ({0} lines found)." - class="net.sourceforge.pmd.lang.plsql.rule.codesize.ExcessivePackageSpecificationLengthRule" - externalInfoUrl="${pmd.website.baseurl}/pmd_rules_plsql_codesize.html#excessivepackagespecificationlength"> - <description> -Excessive class file lengths are usually indications that the class may be burdened with excessive -responsibilities that could be provided by external classes or functions. In breaking these methods -apart the code becomes more managable and ripe for reuse. - </description> - <priority>3</priority> - <example> -<![CDATA[ -CREATE OR REPLACE -PACKAGE Foo AS - PROCEDURE bar1; - PROCEDURE bar2; - PROCEDURE bar3; - - ... - - PROCEDURE barN; -END; -]]> - </example> - </rule> - - <rule name="CyclomaticComplexity" - language="plsql" - since="5.1" - message = "The {0} ''{1}'' has a Cyclomatic Complexity of {2}." - class="net.sourceforge.pmd.lang.plsql.rule.codesize.CyclomaticComplexityRule" - externalInfoUrl="${pmd.website.baseurl}/pmd_rules_plsql_codesize.html#cyclomaticcomplexity"> - <description> -Complexity directly affects maintenance costs is determined by the number of decision points in a method -plus one for the method entry. The decision points include 'if', 'while', 'for', and 'case labels' calls. -Generally, numbers ranging from 1-4 denote low complexity, 5-7 denote moderate complexity, 8-10 denote -high complexity, and 11+ is very high complexity. - </description> - <priority>3</priority> - <example> -<![CDATA[ --- Cyclomatic Complexity of 25 -CREATE OR REPLACE PACKAGE BODY pkg_pmd_working_sequence AS -1 PROCEDURE ty_logger IS BEGIN -2 IF true - THEN - DBMS_OUTPUT.PUT_LINE('IF/THEN l_Integer='||l_integer); -3 IF true - THEN - DBMS_OUTPUT.PUT_LINE('IF/THEN l_Integer='||l_integer); -4 IF true - THEN - DBMS_OUTPUT.PUT_LINE('IF/THEN l_Integer='||l_integer); -5 ELSIF false - THEN - DBMS_OUTPUT.PUT_LINE('ELSIF l_Integer='||l_integer); - ELSE - DBMS_OUTPUT.PUT_LINE('ELSIF l_Integer='||l_integer); - END IF; -6 ELSIF false - THEN - DBMS_OUTPUT.PUT_LINE('ELSIF l_Integer='||l_integer); -7 IF true - THEN - DBMS_OUTPUT.PUT_LINE('IF/THEN l_Integer='||l_integer); -8 ELSIF false - THEN - DBMS_OUTPUT.PUT_LINE('ELSIF l_Integer='||l_integer); - ELSE - DBMS_OUTPUT.PUT_LINE('ELSIF l_Integer='||l_integer); - END IF; - ELSE - DBMS_OUTPUT.PUT_LINE('ELSIF l_Integer='||l_integer); -9 IF true - THEN - DBMS_OUTPUT.PUT_LINE('IF/THEN l_Integer='||l_integer); -10 ELSIF false - THEN - DBMS_OUTPUT.PUT_LINE('ELSIF l_Integer='||l_integer); - ELSE - DBMS_OUTPUT.PUT_LINE('ELSIF l_Integer='||l_integer); - END IF; - END IF; -11 ELSIF false - THEN - DBMS_OUTPUT.PUT_LINE('ELSIF l_Integer='||l_integer); -12 IF true - THEN - DBMS_OUTPUT.PUT_LINE('IF/THEN l_Integer='||l_integer); -13 IF true - THEN - DBMS_OUTPUT.PUT_LINE('IF/THEN l_Integer='||l_integer); -14 ELSIF false - THEN - DBMS_OUTPUT.PUT_LINE('ELSIF l_Integer='||l_integer); - ELSE - DBMS_OUTPUT.PUT_LINE('ELSIF l_Integer='||l_integer); - END IF; -15 ELSIF false - THEN -16 IF true - THEN - DBMS_OUTPUT.PUT_LINE('IF/THEN l_Integer='||l_integer); -17 ELSIF false - THEN - DBMS_OUTPUT.PUT_LINE('ELSIF l_Integer='||l_integer); - ELSE - DBMS_OUTPUT.PUT_LINE('ELSIF l_Integer='||l_integer); - END IF; - DBMS_OUTPUT.PUT_LINE('ELSIF l_Integer='||l_integer); - ELSE - DBMS_OUTPUT.PUT_LINE('ELSIF l_Integer='||l_integer); - END IF; - ELSE - DBMS_OUTPUT.PUT_LINE('ELSIF l_Integer='||l_integer); -18 IF true - THEN - DBMS_OUTPUT.PUT_LINE('IF/THEN l_Integer='||l_integer); -19 IF true - THEN - DBMS_OUTPUT.PUT_LINE('IF/THEN l_Integer='||l_integer); -20 ELSIF false - THEN - DBMS_OUTPUT.PUT_LINE('ELSIF l_Integer='||l_integer); - ELSE - DBMS_OUTPUT.PUT_LINE('ELSIF l_Integer='||l_integer); - END IF; -21 ELSIF false - THEN - DBMS_OUTPUT.PUT_LINE('ELSIF l_Integer='||l_integer); -22 IF true - THEN - DBMS_OUTPUT.PUT_LINE('IF/THEN l_Integer='||l_integer); -23 ELSIF false - THEN - DBMS_OUTPUT.PUT_LINE('ELSIF l_Integer='||l_integer); - ELSE - DBMS_OUTPUT.PUT_LINE('ELSIF l_Integer='||l_integer); - END IF; - ELSE - DBMS_OUTPUT.PUT_LINE('ELSIF l_Integer='||l_integer); -24 IF true - THEN - DBMS_OUTPUT.PUT_LINE('IF/THEN l_Integer='||l_integer); -25 ELSIF false - THEN - DBMS_OUTPUT.PUT_LINE('ELSIF l_Integer='||l_integer); - ELSE - DBMS_OUTPUT.PUT_LINE('ELSIF l_Integer='||l_integer); - END IF; - END IF; - END IF; -END; - -END; -]]> - </example> - </rule> - - -<!-- - <rule name="ExcessivePublicCount" - language="plsql" - since="5.1" - message="This class has a bunch of public methods and attributes" - class="net.sourceforge.pmd.lang.plsql.rule.codesize.ExcessivePublicCountRule" - externalInfoUrl="${pmd.website.baseurl}/pmd_rules_plsql_codesize.html#excessivepubliccount"> - <description> -Classes with large numbers of public methods and attributes require disproportionate testing efforts -since combinational side effects grow rapidly and increase risk. Refactoring these classes into -smaller ones not only increases testability and reliability but also allows new variations to be -developed easily. - </description> - <priority>3</priority> - <example> -<![CDATA[ -public class Foo { - public String value; - public Bar something; - public Variable var; -// [... more more public attributes ...] - - public void doWork() {} - public void doMoreWork() {} - public void doWorkAgain() {} -// [... more more public methods ...] -} -]]> - </example> - </rule> ---> - - <rule name="TooManyFields" - language="plsql" - since="5.1" - message="Too many fields" - class="net.sourceforge.pmd.lang.plsql.rule.codesize.TooManyFieldsRule" - externalInfoUrl="${pmd.website.baseurl}/pmd_rules_plsql_codesize.html#toomanyfields"> - <description> -Classes that have too many fields can become unwieldy and could be redesigned to have fewer fields, -possibly through grouping related fields in new objects. For example, a class with individual -city/state/zip fields could park them within a single Address field. - </description> - <priority>3</priority> - <example> -<![CDATA[ -CREATE OR REPLACE PACKAGE pkg_too_many_fields AS - C_CHAR_A CONSTANT CHAR(1 CHAR) := 'A'; - C_CHAR_B CONSTANT CHAR(1 CHAR) := 'B'; - ... - C_CHAR_Z CONSTANT CHAR(1 CHAR) := 'Z'; -END pkg_too_many_fields; -]]> - </example> - </rule> - - <rule name="NcssMethodCount" - message="The method {0}() has an NCSS line count of {1}" - language="plsql" - since="5.1" - class="net.sourceforge.pmd.lang.plsql.rule.codesize.NcssMethodCountRule" - externalInfoUrl="${pmd.website.baseurl}/pmd_rules_plsql_codesize.html#ncssmethodcount"> - <description> -This rule uses the NCSS (Non-Commenting Source Statements) algorithm to determine the number of lines -of code for a given method. NCSS ignores comments, and counts actual statements. Using this algorithm, -lines of code that are split are counted as one. - </description> - <priority>3</priority> - <example> -<![CDATA[ -CREATE OR REPLACE PACKAGE BODY AS - FUNCTION methd RETURN INTEGER IS - BEGIN - RETURN 1;; - END; -END; -]]> - </example> - </rule> - - <rule name="NcssObjectCount" - message="The Oracle object has an NCSS line count of {0}" - language="plsql" - since="5.1" - class="net.sourceforge.pmd.lang.plsql.rule.codesize.NcssObjectCountRule" - externalInfoUrl="${pmd.website.baseurl}/pmd_rules_plsql_codesize.html#ncssobjectcount"> - <description> -This rule uses the NCSS (Non-Commenting Source Statements) algorithm to determine the number of lines -of code for a given Oracle object. NCSS ignores comments, and counts actual statements. Using this algorithm, -lines of code that are split are counted as one. - </description> - <priority>3</priority> - <example> -<![CDATA[ -CREATE OR REPLACE PACKAGE pkg_ - PROCEDURE Foo IS - BEGIN - --this class only has 6 NCSS lines - super(); - super(); - END; -} -]]> - </example> - </rule> - - <rule language="plsql" - name="TooManyMethods" - since="5.1" - class="net.sourceforge.pmd.lang.rule.XPathRule" - message="This object has too many methods, consider refactoring it." - externalInfoUrl="${pmd.website.baseurl}/pmd_rules_plsql_codesize.html#toomanymethods"> - <description> -A package or type with too many methods is probably a good suspect for refactoring, in order to reduce its complexity and find a way to -have more fine grained objects. - </description> - <priority>3</priority> - <properties> - <property name="maxmethods" type="Integer" description="The method count reporting threshold" min="1" max="1000" value="1"/> - <property name="xpath"> - <value> -<![CDATA[ -//node() - [ ( - local-name(.) = 'PackageSpecification' - or - local-name(.) = 'TypeSpecification' - ) - and - ( - count(/descendant::ProgramUnit[ - not ( - starts-with(@Image,'get') - or - starts-with(@Image,'set') - or - starts-with(@Image,'is') - ) - ] - ) - + - count(/descendant::TypeMethod[ - not ( - starts-with(@Image,'get') - or - starts-with(@Image,'set') - or - starts-with(@Image,'is') - ) - ] - ) - ) > $maxmethods - ] -]]> - </value> - </property> - </properties> - </rule> + <rule ref="category/plsql/design.xml/CyclomaticComplexity" deprecated="true" /> + <rule ref="category/plsql/design.xml/ExcessiveMethodLength" deprecated="true" /> + <rule ref="category/plsql/design.xml/ExcessiveObjectLength" deprecated="true" /> + <rule ref="category/plsql/design.xml/ExcessivePackageBodyLength" deprecated="true" /> + <rule ref="category/plsql/design.xml/ExcessivePackageSpecificationLength" deprecated="true" /> + <rule ref="category/plsql/design.xml/ExcessiveParameterList" deprecated="true" /> + <rule ref="category/plsql/design.xml/ExcessiveTypeLength" deprecated="true" /> + <rule ref="category/plsql/design.xml/NcssMethodCount" deprecated="true" /> + <rule ref="category/plsql/design.xml/NcssObjectCount" deprecated="true" /> + <rule ref="category/plsql/design.xml/NPathComplexity" deprecated="true" /> + <rule ref="category/plsql/design.xml/TooManyFields" deprecated="true" /> + <rule ref="category/plsql/design.xml/TooManyMethods" deprecated="true" /> </ruleset> diff --git a/pmd-plsql/src/main/resources/rulesets/plsql/dates.xml b/pmd-plsql/src/main/resources/rulesets/plsql/dates.xml index 70339133d23..fc09892d9e5 100644 --- a/pmd-plsql/src/main/resources/rulesets/plsql/dates.xml +++ b/pmd-plsql/src/main/resources/rulesets/plsql/dates.xml @@ -2,156 +2,14 @@ <ruleset name="PLSQL DATETIME" xmlns="http://pmd.sourceforge.net/ruleset/2.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 http://pmd.sourceforge.net/ruleset_2_0_0.xsd"> + xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd"> <description> The Dates ruleset deals with PLSQL DATETIME usages. </description> - <rule name="TO_DATEWithoutDateFormat" - language="plsql" - since="5.1" - message="TO_DATE without date format" - class="net.sourceforge.pmd.lang.rule.XPathRule" - externalInfoUrl="${pmd.website.baseurl}/pmd_rules_plsql_dates.html#to_datewithoutdateformat"> - <description> -TO_DATE without date format- use TO_DATE(expression, date-format) - </description> - <priority>3</priority> - <properties> - <property name="xpath"> - <value> -<![CDATA[ -//PrimaryExpression[PrimaryPrefix/Name/@Image='TO_DATE' and count(PrimarySuffix/Arguments/ArgumentList/Argument) = 1 ] -]]> - </value> - </property> - </properties> - <example> -<![CDATA[ -CREATE OR REPLACE PACKAGE BODY date_utilities -IS - --- Take single parameter, relyimg on current default NLS date format -FUNCTION to_date_single_parameter (p_date_string IN VARCHAR2) RETURN DATE -IS -BEGIN - RETURN TO_DATE(p_date_string); -END to_date_single_parameter ; - --- Take 2 parameters, using an explicit date format string -FUNCTION to_date_two_parameters (p_date_string IN VARCHAR2, p_format_mask IN VARCHAR2) RETURN DATE -IS -BEGIN - TO_DATE(p_date_string, p_date_format); -END to_date_two_parameters; - --- Take 3 parameters, using an explicit date format string and an explicit language -FUNCTION to_date_three_parameters (p_date_string IN VARCHAR2, p_format_mask IN VARCHAR2, p_nls_language VARCHAR2 ) RETURN DATE -IS -BEGIN - TO_DATE(p_date_string, p_format_mask, p_nls_language); -END to_date_three_parameters; - -END date_utilities; -/ -]]> - </example> - </rule> - - <rule name="TO_DATE_TO_CHAR" - language="plsql" - since="5.1" - message="TO_DATE(TO_CHAR(variable)) instead of TRUNC(variable)" - class="net.sourceforge.pmd.lang.rule.XPathRule" - externalInfoUrl="${pmd.website.baseurl}/pmd_rules_plsql_dates.html#to_date_to_char"> - <description> -TO_DATE(TO_CHAR(date-variable)) used to remove time component - use TRUNC(date-veriable) - </description> - <priority>3</priority> - <properties> - <property name="xpath"> - <value> -<![CDATA[ -//PrimaryExpression - [PrimaryPrefix/Name/@Image='TO_DATE'] - [count(PrimarySuffix/Arguments/ArgumentList/Argument) = 1] - [.//PrimaryExpression - [PrimaryPrefix/Name/@Image='TO_CHAR'] - [count(PrimarySuffix/Arguments/ArgumentList/Argument) = 1] - ] -]]> - </value> - </property> - </properties> - <example> -<![CDATA[ -CREATE OR REPLACE PACKAGE BODY date_utilities -IS - --- Take single parameter, relyimg on current default NLS date format -FUNCTION strip_time (p_date IN DATE) RETURN DATE -IS -BEGIN - RETURN TO_DATE(TO_CHAR(p_date)); -END strip_time; - - -END date_utilities; -/ -]]> - </example> - </rule> - - <rule name="TO_TIMESTAMPWithoutDateFormat" - language="plsql" - message="TO_TIMESTAMP without date format" - class="net.sourceforge.pmd.lang.rule.XPathRule" - since="5.1" - externalInfoUrl="${pmd.website.baseurl}/pmd_rules_plsql_dates.html#to_timestampwithoutdateformat"> - <description> -TO_TIMESTAMP without date format- use TO_TIMESTAMP(expression, date-format) - </description> - <priority>3</priority> - <properties> - <property name="xpath"> - <value> -<![CDATA[ -//PrimaryExpression[PrimaryPrefix/Name/@Image='TO_TIMESTAMP' and count(PrimarySuffix/Arguments/ArgumentList/Argument) = 1 ] -]]> - </value> - </property> - </properties> - <example> -<![CDATA[ -CREATE OR REPLACE PACKAGE BODY date_utilities -IS - --- Take single parameter, relyimg on current default NLS date format -FUNCTION to_timestamp_single_parameter (p_date_string IN VARCHAR2) RETURN DATE -IS -BEGIN - RETURN TO_TIMESTAMP(p_date_string); -END to_timestamp_single_parameter; - --- Take 2 parameters, using an explicit date format string -FUNCTION to_timestamp_two_parameters (p_date_string IN VARCHAR2, p_format_mask IN VARCHAR2) RETURN DATE -IS -BEGIN - TO_TIMESTAMP(p_date_string, p_date_format); -END to_timestamp_two_parameters; - --- Take 3 parameters, using an explicit date format string and an explicit language -FUNCTION to_timestamp_three_parameters (p_date_string IN VARCHAR2, p_format_mask IN VARCHAR2, p_nls_language VARCHAR2 ) RETURN DATE -IS -BEGIN - TO_TIMESTAMP(p_date_string, p_format_mask, p_nls_language); -END to_timestamp_three_parameters; - -END date_utilities; -/ -]]> - </example> - </rule> + <rule ref="category/plsql/errorprone.xml/TO_DATE_TO_CHAR" deprecated="true" /> + <rule ref="category/plsql/errorprone.xml/TO_DATEWithoutDateFormat" deprecated="true" /> + <rule ref="category/plsql/errorprone.xml/TO_TIMESTAMPWithoutDateFormat" deprecated="true" /> </ruleset> diff --git a/pmd-plsql/src/main/resources/rulesets/plsql/rulesets.properties b/pmd-plsql/src/main/resources/rulesets/plsql/rulesets.properties index 018e4b3912a..0fbe9bd42ea 100644 --- a/pmd-plsql/src/main/resources/rulesets/plsql/rulesets.properties +++ b/pmd-plsql/src/main/resources/rulesets/plsql/rulesets.properties @@ -3,7 +3,15 @@ # rulesets.filenames=\ - rulesets/plsql/codesize.xml,\ - rulesets/plsql/dates.xml,\ - rulesets/plsql/TomKytesDespair.xml,\ - rulesets/plsql/strictsyntax.xml + category/plsql/bestpractices.xml,\ + category/plsql/codestyle.xml,\ + category/plsql/design.xml,\ + category/plsql/errorprone.xml + +# +# categories without rules +# +# category/plsql/documentation.xml +# category/plsql/multithreading.xml +# category/plsql/performance.xml +# category/plsql/security.xml diff --git a/pmd-plsql/src/main/resources/rulesets/plsql/strictsyntax.xml b/pmd-plsql/src/main/resources/rulesets/plsql/strictsyntax.xml index c8f8d322656..ea3c9ab7e01 100644 --- a/pmd-plsql/src/main/resources/rulesets/plsql/strictsyntax.xml +++ b/pmd-plsql/src/main/resources/rulesets/plsql/strictsyntax.xml @@ -2,53 +2,12 @@ <ruleset name="Strict Syntax" xmlns="http://pmd.sourceforge.net/ruleset/2.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 http://pmd.sourceforge.net/ruleset_2_0_0.xsd"> + xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd"> <description> The Strict Syntax ruleset contains rules that highlight invalid plsql syntax, which works, but should be avoided. </description> - <rule name="MisplacedPragma" - language="plsql" - since="5.5.2" - message="Pragma should be used only inside the declaration block before 'BEGIN'." - class="net.sourceforge.pmd.lang.rule.XPathRule" - externalInfoUrl="${pmd.website.baseurl}/pmd_rules_plsql_strictsyntax.html#misplacedpragma"> - <description> -Oracle states that the PRAQMA AUTONOMOUS_TRANSACTION must be in the declaration block, -but the code does not complain, when being compiled on the 11g DB. -https://docs.oracle.com/cd/B28359_01/appdev.111/b28370/static.htm#BABIIHBJ - </description> - <priority>3</priority> - <properties> - <property name="xpath"> - <value> -<![CDATA[ -//ProgramUnit/Pragma -]]> - </value> - </property> - </properties> - <example> -<![CDATA[ -create or replace package inline_pragma_error is - -end; -/ - -create or replace package body inline_pragma_error is - procedure do_transaction(p_input_token in varchar(200)) is - PRAGMA AUTONOMOUS_TRANSACTION; /* this is correct place for PRAGMA */ - begin - PRAGMA AUTONOMOUS_TRANSACTION; /* this is the wrong place for PRAGMA -> violation */ - /* do something */ - COMMIT; - end do_transaction; - -end inline_pragma_error; -/ -]]> - </example> - </rule> + <rule ref="category/plsql/codestyle.xml/MisplacedPragma" deprecated="true" /> </ruleset> diff --git a/pmd-plsql/src/test/java/net/sourceforge/pmd/cpd/PLSQLTokenizerTest.java b/pmd-plsql/src/test/java/net/sourceforge/pmd/cpd/PLSQLTokenizerTest.java index ace4b0517c4..62f4f2e36c5 100644 --- a/pmd-plsql/src/test/java/net/sourceforge/pmd/cpd/PLSQLTokenizerTest.java +++ b/pmd-plsql/src/test/java/net/sourceforge/pmd/cpd/PLSQLTokenizerTest.java @@ -4,12 +4,16 @@ package net.sourceforge.pmd.cpd; +import static org.junit.Assert.assertEquals; + import java.io.IOException; +import java.nio.charset.StandardCharsets; import org.apache.commons.io.IOUtils; import org.junit.Before; import org.junit.Test; +import net.sourceforge.pmd.PMD; import net.sourceforge.pmd.testframework.AbstractTokenizerTest; public class PLSQLTokenizerTest extends AbstractTokenizerTest { @@ -25,7 +29,7 @@ public void buildTokenizer() throws IOException { @Override public String getSampleCode() throws IOException { - return IOUtils.toString(PLSQLTokenizer.class.getResourceAsStream(FILENAME)); + return IOUtils.toString(PLSQLTokenizer.class.getResourceAsStream(FILENAME), StandardCharsets.UTF_8); } @Test @@ -33,8 +37,20 @@ public void tokenizeTest() throws IOException { this.expectedTokenCount = 1422; super.tokenizeTest(); } - - public static junit.framework.Test suite() { - return new junit.framework.JUnit4TestAdapter(PLSQLTokenizerTest.class); + + @Test + public void testIgnoreBetweenSpecialComments() throws IOException { + SourceCode sourceCode = new SourceCode(new SourceCode.StringCodeLoader("-- CPD-OFF" + PMD.EOL + + "CREATE OR REPLACE" + PMD.EOL + + "PACKAGE \"test_schema\".\"BANK_DATA\"" + PMD.EOL + + "IS" + PMD.EOL + + "pi CONSTANT NUMBER := 3.1415;" + PMD.EOL + + "--CPD-ON" + PMD.EOL + + "END;" + )); + Tokens tokens = new Tokens(); + tokenizer.tokenize(sourceCode, tokens); + TokenEntry.getEOF(); + assertEquals(3, tokens.size()); // 3 tokens: "END" + ";" + EOF } } diff --git a/pmd-plsql/src/test/java/net/sourceforge/pmd/lang/plsql/AbstractPLSQLParserTst.java b/pmd-plsql/src/test/java/net/sourceforge/pmd/lang/plsql/AbstractPLSQLParserTst.java index 8b065bbbba0..8923f43683f 100644 --- a/pmd-plsql/src/test/java/net/sourceforge/pmd/lang/plsql/AbstractPLSQLParserTst.java +++ b/pmd-plsql/src/test/java/net/sourceforge/pmd/lang/plsql/AbstractPLSQLParserTst.java @@ -4,24 +4,26 @@ package net.sourceforge.pmd.lang.plsql; +import java.io.IOException; import java.io.StringReader; import java.lang.reflect.InvocationHandler; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Proxy; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.List; import java.util.Set; +import org.apache.commons.io.IOUtils; + import net.sourceforge.pmd.lang.LanguageRegistry; import net.sourceforge.pmd.lang.LanguageVersion; import net.sourceforge.pmd.lang.LanguageVersionHandler; import net.sourceforge.pmd.lang.ast.Node; -// Root Production comprising PLSQL definitions, and SQL*PLus, DDL, GRANTS etc. import net.sourceforge.pmd.lang.plsql.ast.ASTInput; -//Covers all executbale code units, such as package and object type bodies, standalone procedures and functions, and triggers import net.sourceforge.pmd.lang.plsql.ast.PLSQLParserVisitor; import net.sourceforge.pmd.lang.plsql.dfa.DataFlowFacade; import net.sourceforge.pmd.lang.plsql.symboltable.SymbolFacade; @@ -129,4 +131,12 @@ public Node parseLanguage(LanguageVersion languageVersion, String code) { return languageVersionHandler.getParser(languageVersionHandler.getDefaultParserOptions()).parse(null, new StringReader(code)); } + + public String loadTestResource(String name) { + try { + return IOUtils.toString(this.getClass().getResourceAsStream(name), StandardCharsets.UTF_8); + } catch (IOException e) { + throw new RuntimeException(e); + } + } } diff --git a/pmd-plsql/src/test/java/net/sourceforge/pmd/lang/plsql/PLSQLParserTest.java b/pmd-plsql/src/test/java/net/sourceforge/pmd/lang/plsql/PLSQLParserTest.java index fe5b79fb90c..f1568e199b1 100644 --- a/pmd-plsql/src/test/java/net/sourceforge/pmd/lang/plsql/PLSQLParserTest.java +++ b/pmd-plsql/src/test/java/net/sourceforge/pmd/lang/plsql/PLSQLParserTest.java @@ -4,6 +4,8 @@ package net.sourceforge.pmd.lang.plsql; +import java.nio.charset.StandardCharsets; + import org.apache.commons.io.IOUtils; import org.junit.Test; @@ -35,31 +37,36 @@ public void testBug1531() { @Test public void testBug1527() throws Exception { - parsePLSQL(IOUtils.toString(PLSQLParserTest.class.getResourceAsStream("ast/InlinePragmaProcError.pls"))); + parsePLSQL(IOUtils.toString(PLSQLParserTest.class.getResourceAsStream("ast/InlinePragmaProcError.pls"), StandardCharsets.UTF_8)); } @Test public void testBug1520IsOfType() throws Exception { - parsePLSQL(IOUtils.toString(PLSQLParserTest.class.getResourceAsStream("ast/IsOfType.pls"))); + parsePLSQL(IOUtils.toString(PLSQLParserTest.class.getResourceAsStream("ast/IsOfType.pls"), StandardCharsets.UTF_8)); } @Test public void testBug1520Using() throws Exception { - parsePLSQL(IOUtils.toString(PLSQLParserTest.class.getResourceAsStream("ast/Using.pls"))); + parsePLSQL(IOUtils.toString(PLSQLParserTest.class.getResourceAsStream("ast/Using.pls"), StandardCharsets.UTF_8)); } @Test public void testSingleLineSelect() throws Exception { - parsePLSQL(IOUtils.toString(PLSQLParserTest.class.getResourceAsStream("ast/SingleLineSelect.pls"))); + parsePLSQL(IOUtils.toString(PLSQLParserTest.class.getResourceAsStream("ast/SingleLineSelect.pls"), StandardCharsets.UTF_8)); } @Test public void testMultiLineSelect() throws Exception { - parsePLSQL(IOUtils.toString(PLSQLParserTest.class.getResourceAsStream("ast/MultiLineSelect.pls"))); + parsePLSQL(IOUtils.toString(PLSQLParserTest.class.getResourceAsStream("ast/MultiLineSelect.pls"), StandardCharsets.UTF_8)); } @Test public void testIsNull() throws Exception { - parsePLSQL(IOUtils.toString(PLSQLParserTest.class.getResourceAsStream("ast/IsNull.pls"))); + parsePLSQL(IOUtils.toString(PLSQLParserTest.class.getResourceAsStream("ast/IsNull.pls"), StandardCharsets.UTF_8)); + } + + @Test + public void testCodingStyleExample() throws Exception { + parsePLSQL(IOUtils.toString(PLSQLParserTest.class.getResourceAsStream("ast/CodingStyleExample.pls"), StandardCharsets.UTF_8)); } } diff --git a/pmd-plsql/src/test/java/net/sourceforge/pmd/lang/plsql/PLSQLXPathRuleTest.java b/pmd-plsql/src/test/java/net/sourceforge/pmd/lang/plsql/PLSQLXPathRuleTest.java index cf1eda23e54..fe3340396e9 100644 --- a/pmd-plsql/src/test/java/net/sourceforge/pmd/lang/plsql/PLSQLXPathRuleTest.java +++ b/pmd-plsql/src/test/java/net/sourceforge/pmd/lang/plsql/PLSQLXPathRuleTest.java @@ -66,7 +66,7 @@ public void testXPathRule2() { } private XPathRule createRule(String version) { - XPathRule rule = new XPathRule("//PrimaryExpression"); + XPathRule rule = new XPathRule("//PrimaryPrefix"); rule.setLanguage(LanguageRegistry.getLanguage(PLSQLLanguageModule.NAME)); rule.setVersion(version); rule.setMessage("Test Violation"); diff --git a/pmd-plsql/src/test/java/net/sourceforge/pmd/lang/plsql/ast/CreateTableTest.java b/pmd-plsql/src/test/java/net/sourceforge/pmd/lang/plsql/ast/CreateTableTest.java new file mode 100644 index 00000000000..381efe7a788 --- /dev/null +++ b/pmd-plsql/src/test/java/net/sourceforge/pmd/lang/plsql/ast/CreateTableTest.java @@ -0,0 +1,24 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.plsql.ast; + +import java.nio.charset.StandardCharsets; + +import org.apache.commons.io.IOUtils; +import org.junit.Assert; +import org.junit.Test; + +import net.sourceforge.pmd.lang.plsql.AbstractPLSQLParserTst; + +public class CreateTableTest extends AbstractPLSQLParserTst { + + @Test + public void parseCreateTable() throws Exception { + String code = IOUtils.toString(this.getClass().getResourceAsStream("CreateTable.pls"), + StandardCharsets.UTF_8); + ASTInput input = parsePLSQL(code); + Assert.assertNotNull(input); + } +} diff --git a/pmd-plsql/src/test/java/net/sourceforge/pmd/lang/plsql/ast/CursorForLoopTest.java b/pmd-plsql/src/test/java/net/sourceforge/pmd/lang/plsql/ast/CursorForLoopTest.java new file mode 100644 index 00000000000..e3d0a77fc53 --- /dev/null +++ b/pmd-plsql/src/test/java/net/sourceforge/pmd/lang/plsql/ast/CursorForLoopTest.java @@ -0,0 +1,64 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.plsql.ast; + +import org.junit.Assert; +import org.junit.Test; + +import net.sourceforge.pmd.lang.plsql.AbstractPLSQLParserTst; + +public class CursorForLoopTest extends AbstractPLSQLParserTst { + + @Test + public void parseCursorForLoopSimple() { + String code = loadTestResource("CursorForLoopSimple.pls"); + ASTInput input = parsePLSQL(code); + ASTCursorForLoopStatement forloop = input.getFirstDescendantOfType(ASTCursorForLoopStatement.class); + Assert.assertNotNull(forloop); + ASTForIndex forindex = forloop.getFirstChildOfType(ASTForIndex.class); + Assert.assertNotNull(forindex); + Assert.assertEquals("someone", forindex.getImage()); + } + + @Test + public void parseCursorForLoopNested() { + String code = loadTestResource("CursorForLoopNested.pls"); + ASTInput input = parsePLSQL(code); + ASTCursorForLoopStatement forloop = input.getFirstDescendantOfType(ASTCursorForLoopStatement.class); + Assert.assertNotNull(forloop); + ASTForIndex forindex = forloop.getFirstChildOfType(ASTForIndex.class); + Assert.assertNotNull(forindex); + Assert.assertEquals("c_cmp", forindex.getImage()); + + ASTCursorForLoopStatement forloop2 = forloop.getFirstDescendantOfType(ASTCursorForLoopStatement.class); + ASTForIndex forindex2 = forloop2.getFirstChildOfType(ASTForIndex.class); + Assert.assertEquals("c_con", forindex2.getImage()); + + ASTCursorForLoopStatement forloop3 = forloop2.getFirstDescendantOfType(ASTCursorForLoopStatement.class); + ASTForIndex forindex3 = forloop3.getFirstChildOfType(ASTForIndex.class); + Assert.assertEquals("c_pa", forindex3.getImage()); + } + + @Test + public void parseCursorForLoop1047a() { + String code = loadTestResource("CursorForLoop1047a.pls"); + ASTInput input = parsePLSQL(code); + Assert.assertNotNull(input); + } + + @Test + public void parseCursorForLoop1047b() { + String code = loadTestResource("CursorForLoop1047b.pls"); + ASTInput input = parsePLSQL(code); + Assert.assertNotNull(input); + } + + @Test + public void parseCursorForLoop681() { + String code = loadTestResource("CursorForLoop681.pls"); + ASTInput input = parsePLSQL(code); + Assert.assertNotNull(input); + } +} diff --git a/pmd-plsql/src/test/java/net/sourceforge/pmd/lang/plsql/ast/DeleteStatementTest.java b/pmd-plsql/src/test/java/net/sourceforge/pmd/lang/plsql/ast/DeleteStatementTest.java new file mode 100644 index 00000000000..fa2196466b7 --- /dev/null +++ b/pmd-plsql/src/test/java/net/sourceforge/pmd/lang/plsql/ast/DeleteStatementTest.java @@ -0,0 +1,29 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.plsql.ast; + +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.plsql.AbstractPLSQLParserTst; + +public class DeleteStatementTest extends AbstractPLSQLParserTst { + + @Test + public void parseDeleteStatementExample() throws Exception { + String code = IOUtils.toString(this.getClass().getResourceAsStream("DeleteStatementExample.pls"), + StandardCharsets.UTF_8); + ASTInput input = parsePLSQL(code); + List<ASTDeleteStatement> deleteStatements = input.findDescendantsOfType(ASTDeleteStatement.class); + Assert.assertEquals(3, deleteStatements.size()); + + Assert.assertEquals("product_descriptions", deleteStatements.get(0).jjtGetChild(0) + .getFirstChildOfType(ASTTableName.class).getImage()); + } +} diff --git a/pmd-plsql/src/test/java/net/sourceforge/pmd/lang/plsql/ast/ExecuteImmediateTest.java b/pmd-plsql/src/test/java/net/sourceforge/pmd/lang/plsql/ast/ExecuteImmediateTest.java new file mode 100644 index 00000000000..ff12b5d1487 --- /dev/null +++ b/pmd-plsql/src/test/java/net/sourceforge/pmd/lang/plsql/ast/ExecuteImmediateTest.java @@ -0,0 +1,32 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.plsql.ast; + +import java.nio.charset.StandardCharsets; + +import org.apache.commons.io.IOUtils; +import org.junit.Assert; +import org.junit.Test; + +import net.sourceforge.pmd.lang.plsql.AbstractPLSQLParserTst; + +public class ExecuteImmediateTest extends AbstractPLSQLParserTst { + + @Test + public void parseExecuteImmediate1047a() throws Exception { + String code = IOUtils.toString(this.getClass().getResourceAsStream("ExecuteImmediate1047a.pls"), + StandardCharsets.UTF_8); + ASTInput input = parsePLSQL(code); + Assert.assertNotNull(input); + } + + @Test + public void parseExecuteImmediate1047b() throws Exception { + String code = IOUtils.toString(this.getClass().getResourceAsStream("ExecuteImmediate1047b.pls"), + StandardCharsets.UTF_8); + ASTInput input = parsePLSQL(code); + Assert.assertNotNull(input); + } +} diff --git a/pmd-plsql/src/test/java/net/sourceforge/pmd/lang/plsql/ast/JoinClauseTest.java b/pmd-plsql/src/test/java/net/sourceforge/pmd/lang/plsql/ast/JoinClauseTest.java new file mode 100644 index 00000000000..56bf56418a8 --- /dev/null +++ b/pmd-plsql/src/test/java/net/sourceforge/pmd/lang/plsql/ast/JoinClauseTest.java @@ -0,0 +1,102 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.plsql.ast; + +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.plsql.AbstractPLSQLParserTst; + +public class JoinClauseTest extends AbstractPLSQLParserTst { + + @Test + public void testInnerCrossJoin() throws Exception { + String code = IOUtils.toString(this.getClass().getResourceAsStream("InnerCrossJoin.pls"), + StandardCharsets.UTF_8); + ASTInput input = parsePLSQL(code); + List<ASTInnerCrossJoinClause> joins = input.findDescendantsOfType(ASTInnerCrossJoinClause.class); + Assert.assertEquals(1, joins.size()); + Assert.assertTrue(joins.get(0).isCross()); + Assert.assertFalse(joins.get(0).isNatural()); + } + + @Test + public void testInnerNaturalJoin() throws Exception { + String code = IOUtils.toString(this.getClass().getResourceAsStream("InnerNaturalJoin.pls"), + StandardCharsets.UTF_8); + ASTInput input = parsePLSQL(code); + List<ASTInnerCrossJoinClause> joins = input.findDescendantsOfType(ASTInnerCrossJoinClause.class); + Assert.assertEquals(1, joins.size()); + Assert.assertFalse(joins.get(0).isCross()); + Assert.assertTrue(joins.get(0).isNatural()); + } + + @Test + public void testInnerJoinUsing() throws Exception { + String code = IOUtils.toString(this.getClass().getResourceAsStream("InnerJoinUsing.pls"), + StandardCharsets.UTF_8); + ASTInput input = parsePLSQL(code); + List<ASTInnerCrossJoinClause> joins = input.findDescendantsOfType(ASTInnerCrossJoinClause.class); + Assert.assertEquals(1, joins.size()); + Assert.assertFalse(joins.get(0).isCross()); + Assert.assertFalse(joins.get(0).isNatural()); + List<ASTColumn> columns = joins.get(0).findChildrenOfType(ASTColumn.class); + Assert.assertEquals(1, columns.size()); + Assert.assertEquals("department_id", columns.get(0).getImage()); + } + + @Test + public void testOuterJoinUsing() throws Exception { + String code = IOUtils.toString(this.getClass().getResourceAsStream("OuterJoinUsing.pls"), + StandardCharsets.UTF_8); + ASTInput input = parsePLSQL(code); + List<ASTOuterJoinClause> joins = input.findDescendantsOfType(ASTOuterJoinClause.class); + Assert.assertEquals(1, joins.size()); + ASTOuterJoinType type = joins.get(0).getFirstChildOfType(ASTOuterJoinType.class); + Assert.assertEquals(ASTOuterJoinType.Type.FULL, type.getType()); + List<ASTColumn> columns = joins.get(0).findChildrenOfType(ASTColumn.class); + Assert.assertEquals(1, columns.size()); + Assert.assertEquals("department_id", columns.get(0).getImage()); + } + + @Test + public void testRightOuterJoin() throws Exception { + String code = IOUtils.toString(this.getClass().getResourceAsStream("RightOuterJoin.pls"), + StandardCharsets.UTF_8); + ASTInput input = parsePLSQL(code); + List<ASTOuterJoinClause> joins = input.findDescendantsOfType(ASTOuterJoinClause.class); + Assert.assertEquals(1, joins.size()); + ASTOuterJoinType type = joins.get(0).getFirstChildOfType(ASTOuterJoinType.class); + Assert.assertEquals(ASTOuterJoinType.Type.RIGHT, type.getType()); + } + + @Test + public void testNaturalRightOuterJoin() throws Exception { + String code = IOUtils.toString(this.getClass().getResourceAsStream("NaturalRightOuterJoin.pls"), + StandardCharsets.UTF_8); + ASTInput input = parsePLSQL(code); + List<ASTOuterJoinClause> joins = input.findDescendantsOfType(ASTOuterJoinClause.class); + Assert.assertEquals(1, joins.size()); + ASTOuterJoinType type = joins.get(0).getFirstChildOfType(ASTOuterJoinType.class); + Assert.assertEquals(ASTOuterJoinType.Type.RIGHT, type.getType()); + Assert.assertTrue(joins.get(0).isNatural()); + } + + @Test + public void testOuterJoinPartitioned() throws Exception { + String code = IOUtils.toString(this.getClass().getResourceAsStream("OuterJoinPartitioned.pls"), + StandardCharsets.UTF_8); + ASTInput input = parsePLSQL(code); + List<ASTOuterJoinClause> joins = input.findDescendantsOfType(ASTOuterJoinClause.class); + Assert.assertEquals(1, joins.size()); + ASTOuterJoinType type = joins.get(0).getFirstChildOfType(ASTOuterJoinType.class); + Assert.assertEquals(ASTOuterJoinType.Type.RIGHT, type.getType()); + Assert.assertNotNull(joins.get(0).getFirstChildOfType(ASTQueryPartitionClause.class)); + } +} diff --git a/pmd-plsql/src/test/java/net/sourceforge/pmd/lang/plsql/ast/SelectExpressionsTest.java b/pmd-plsql/src/test/java/net/sourceforge/pmd/lang/plsql/ast/SelectExpressionsTest.java new file mode 100644 index 00000000000..7bd3b43c386 --- /dev/null +++ b/pmd-plsql/src/test/java/net/sourceforge/pmd/lang/plsql/ast/SelectExpressionsTest.java @@ -0,0 +1,26 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.plsql.ast; + +import java.nio.charset.StandardCharsets; + +import org.apache.commons.io.IOUtils; +import org.junit.Assert; +import org.junit.Ignore; +import org.junit.Test; + +import net.sourceforge.pmd.lang.plsql.AbstractPLSQLParserTst; + +public class SelectExpressionsTest extends AbstractPLSQLParserTst { + + @Test + @Ignore + public void parseSelectCount() throws Exception { + String code = IOUtils.toString(this.getClass().getResourceAsStream("SelectExpressions.pls"), + StandardCharsets.UTF_8); + ASTInput input = parsePLSQL(code); + Assert.assertNotNull(input); + } +} diff --git a/pmd-plsql/src/test/java/net/sourceforge/pmd/lang/plsql/ast/SelectIntoStatementTest.java b/pmd-plsql/src/test/java/net/sourceforge/pmd/lang/plsql/ast/SelectIntoStatementTest.java new file mode 100644 index 00000000000..788fcda7abb --- /dev/null +++ b/pmd-plsql/src/test/java/net/sourceforge/pmd/lang/plsql/ast/SelectIntoStatementTest.java @@ -0,0 +1,64 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.plsql.ast; + +import java.nio.charset.StandardCharsets; + +import org.apache.commons.io.IOUtils; +import org.junit.Test; + +import net.sourceforge.pmd.lang.plsql.AbstractPLSQLParserTst; + +public class SelectIntoStatementTest extends AbstractPLSQLParserTst { + + @Test + public void testParsingComplex() throws Exception { + String code = IOUtils.toString(this.getClass().getResourceAsStream("SelectIntoStatement.pls"), + StandardCharsets.UTF_8); + ASTInput input = parsePLSQL(code); + } + + @Test + public void testParsingExample1() throws Exception { + String code = IOUtils.toString(this.getClass().getResourceAsStream("SelectIntoStatementExample1.pls"), + StandardCharsets.UTF_8); + ASTInput input = parsePLSQL(code); + } + + @Test + public void testParsingExample2() throws Exception { + String code = IOUtils.toString(this.getClass().getResourceAsStream("SelectIntoStatementExample2.pls"), + StandardCharsets.UTF_8); + ASTInput input = parsePLSQL(code); + } + + @Test + public void testParsingExample3() throws Exception { + String code = IOUtils.toString(this.getClass().getResourceAsStream("SelectIntoStatementExample3.pls"), + StandardCharsets.UTF_8); + ASTInput input = parsePLSQL(code); + } + + @Test + public void testParsingExample4() throws Exception { + String code = IOUtils.toString(this.getClass().getResourceAsStream("SelectIntoStatementExample4.pls"), + StandardCharsets.UTF_8); + ASTInput input = parsePLSQL(code); + } + + @Test + public void testParsingExample5() throws Exception { + String code = IOUtils.toString(this.getClass().getResourceAsStream("SelectIntoStatementExample5.pls"), + StandardCharsets.UTF_8); + ASTInput input = parsePLSQL(code); + } + + @Test + public void testParsingWithFunctionCall() throws Exception { + String code = IOUtils.toString(this.getClass().getResourceAsStream("SelectIntoStatementFunctionCall.pls"), + StandardCharsets.UTF_8); + ASTInput input = parsePLSQL(code); + } +} diff --git a/pmd-plsql/src/test/java/net/sourceforge/pmd/lang/plsql/ast/SelectIntoWithGroupByTest.java b/pmd-plsql/src/test/java/net/sourceforge/pmd/lang/plsql/ast/SelectIntoWithGroupByTest.java new file mode 100644 index 00000000000..3eeeec46cd0 --- /dev/null +++ b/pmd-plsql/src/test/java/net/sourceforge/pmd/lang/plsql/ast/SelectIntoWithGroupByTest.java @@ -0,0 +1,58 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.plsql.ast; + +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.plsql.AbstractPLSQLParserTst; + +public class SelectIntoWithGroupByTest extends AbstractPLSQLParserTst { + + @Test + public void testExample1() throws Exception { + String code = IOUtils.toString(this.getClass().getResourceAsStream("SelectIntoWithGroupBy1.pls"), + StandardCharsets.UTF_8); + ASTInput input = parsePLSQL(code); + ASTGroupByClause groupByClause = input.getFirstDescendantOfType(ASTGroupByClause.class); + Assert.assertNotNull(groupByClause); + } + + @Test + public void testExample2() throws Exception { + String code = IOUtils.toString(this.getClass().getResourceAsStream("SelectIntoWithGroupBy2.pls"), + StandardCharsets.UTF_8); + ASTInput input = parsePLSQL(code); + ASTGroupByClause groupByClause = input.getFirstDescendantOfType(ASTGroupByClause.class); + Assert.assertNotNull(groupByClause); + } + + @Test + public void testExample3WithCube() throws Exception { + String code = IOUtils.toString(this.getClass().getResourceAsStream("SelectIntoWithGroupBy3.pls"), + StandardCharsets.UTF_8); + ASTInput input = parsePLSQL(code); + ASTRollupCubeClause cubeClause = input.getFirstDescendantOfType(ASTRollupCubeClause.class); + Assert.assertNotNull(cubeClause); + Assert.assertEquals("CUBE", cubeClause.getImage()); + } + + @Test + public void testExample4WithGroupingSets() throws Exception { + String code = IOUtils.toString(this.getClass().getResourceAsStream("SelectIntoWithGroupBy4.pls"), + StandardCharsets.UTF_8); + ASTInput input = parsePLSQL(code); + ASTGroupingSetsClause groupingSetsClause = input.getFirstDescendantOfType(ASTGroupingSetsClause.class); + Assert.assertNotNull(groupingSetsClause); + + List<ASTFromClause> fromClauses = input.findDescendantsOfType(ASTFromClause.class); + Assert.assertEquals(1, fromClauses.size()); + Assert.assertEquals(5, fromClauses.get(0).jjtGetNumChildren()); + } +} diff --git a/pmd-plsql/src/test/java/net/sourceforge/pmd/lang/plsql/ast/UpdateStatementTest.java b/pmd-plsql/src/test/java/net/sourceforge/pmd/lang/plsql/ast/UpdateStatementTest.java new file mode 100644 index 00000000000..ee86400a278 --- /dev/null +++ b/pmd-plsql/src/test/java/net/sourceforge/pmd/lang/plsql/ast/UpdateStatementTest.java @@ -0,0 +1,17 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.plsql.ast; + +import org.junit.Test; + +import net.sourceforge.pmd.lang.plsql.AbstractPLSQLParserTst; + +public class UpdateStatementTest extends AbstractPLSQLParserTst { + @Test + public void parseUpdateStatementExample() { + String code = loadTestResource("UpdateStatementExample.pls"); + ASTInput input = parsePLSQL(code); + } +} diff --git a/pmd-plsql/src/test/java/net/sourceforge/pmd/lang/plsql/ast/ViewTest.java b/pmd-plsql/src/test/java/net/sourceforge/pmd/lang/plsql/ast/ViewTest.java new file mode 100644 index 00000000000..79595e9d869 --- /dev/null +++ b/pmd-plsql/src/test/java/net/sourceforge/pmd/lang/plsql/ast/ViewTest.java @@ -0,0 +1,24 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.plsql.ast; + +import java.nio.charset.StandardCharsets; + +import org.apache.commons.io.IOUtils; +import org.junit.Assert; +import org.junit.Test; + +import net.sourceforge.pmd.lang.plsql.AbstractPLSQLParserTst; + +public class ViewTest extends AbstractPLSQLParserTst { + + @Test + public void parseCreateViewIssue981() throws Exception { + String code = IOUtils.toString(this.getClass().getResourceAsStream("ViewIssue981.pls"), + StandardCharsets.UTF_8); + ASTInput input = parsePLSQL(code); + Assert.assertNotNull(input); + } +} diff --git a/pmd-plsql/src/test/java/net/sourceforge/pmd/lang/plsql/ast/WhereClauseTest.java b/pmd-plsql/src/test/java/net/sourceforge/pmd/lang/plsql/ast/WhereClauseTest.java new file mode 100644 index 00000000000..b01f7faa5e1 --- /dev/null +++ b/pmd-plsql/src/test/java/net/sourceforge/pmd/lang/plsql/ast/WhereClauseTest.java @@ -0,0 +1,57 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.plsql.ast; + +import java.nio.charset.StandardCharsets; + +import org.apache.commons.io.IOUtils; +import org.junit.Test; + +import net.sourceforge.pmd.lang.plsql.AbstractPLSQLParserTst; + +public class WhereClauseTest extends AbstractPLSQLParserTst { + + @Test + public void testFunctionCall() throws Exception { + String code = IOUtils.toString(this.getClass().getResourceAsStream("WhereClauseFunctionCall.pls"), + StandardCharsets.UTF_8); + ASTInput input = parsePLSQL(code); + } + + @Test + public void testLikeCondition() throws Exception { + String code = IOUtils.toString(this.getClass().getResourceAsStream("WhereClauseLike.pls"), + StandardCharsets.UTF_8); + ASTInput input = parsePLSQL(code); + } + + @Test + public void testNullCondition() throws Exception { + String code = IOUtils.toString(this.getClass().getResourceAsStream("WhereClauseIsNull.pls"), + StandardCharsets.UTF_8); + ASTInput input = parsePLSQL(code); + } + + @Test + public void testBetweenCondition() throws Exception { + String code = IOUtils.toString(this.getClass().getResourceAsStream("WhereClauseBetween.pls"), + StandardCharsets.UTF_8); + ASTInput input = parsePLSQL(code); + } + + @Test + public void testInCondition() throws Exception { + String code = IOUtils.toString(this.getClass().getResourceAsStream("WhereClauseIn.pls"), + StandardCharsets.UTF_8); + ASTInput input = parsePLSQL(code); + } + + @Test + public void testIsOfTypeCondition() throws Exception { + String code = IOUtils.toString(this.getClass().getResourceAsStream("WhereClauseIsOfType.pls"), + StandardCharsets.UTF_8); + ASTInput input = parsePLSQL(code); + } +} diff --git a/pmd-plsql/src/test/java/net/sourceforge/pmd/lang/plsql/rule/codesize/CodesizeRulesTest.java b/pmd-plsql/src/test/java/net/sourceforge/pmd/lang/plsql/rule/codesize/CodesizeRulesTest.java deleted file mode 100644 index edd0dc8d370..00000000000 --- a/pmd-plsql/src/test/java/net/sourceforge/pmd/lang/plsql/rule/codesize/CodesizeRulesTest.java +++ /dev/null @@ -1,27 +0,0 @@ -/** - * BSD-style license; for more info see http://pmd.sourceforge.net/license.html - */ - -package net.sourceforge.pmd.lang.plsql.rule.codesize; - -import net.sourceforge.pmd.testframework.SimpleAggregatorTst; - -public class CodesizeRulesTest extends SimpleAggregatorTst { - - private static final String RULESET = "plsql-codesize"; - - @Override - public void setUp() { - addRule(RULESET, "NPathComplexity"); - addRule(RULESET, "ExcessiveTypeLength"); - addRule(RULESET, "CyclomaticComplexity"); - addRule(RULESET, "ExcessiveObjectLength"); - addRule(RULESET, "ExcessivePackageBodyLength"); - addRule(RULESET, "ExcessivePackageSpecificationLength"); - addRule(RULESET, "ExcessiveParameterList"); - addRule(RULESET, "ExcessiveMethodLength"); - addRule(RULESET, "NcssMethodCount"); - addRule(RULESET, "NcssObjectCount"); - addRule(RULESET, "TooManyFields"); - } -} diff --git a/pmd-plsql/src/test/java/net/sourceforge/pmd/lang/plsql/rule/codestyle/CodeFormatTest.java b/pmd-plsql/src/test/java/net/sourceforge/pmd/lang/plsql/rule/codestyle/CodeFormatTest.java new file mode 100644 index 00000000000..7c2b877152d --- /dev/null +++ b/pmd-plsql/src/test/java/net/sourceforge/pmd/lang/plsql/rule/codestyle/CodeFormatTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.plsql.rule.codestyle; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class CodeFormatTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-plsql/src/test/java/net/sourceforge/pmd/lang/plsql/rule/codestyle/ForLoopNamingTest.java b/pmd-plsql/src/test/java/net/sourceforge/pmd/lang/plsql/rule/codestyle/ForLoopNamingTest.java new file mode 100644 index 00000000000..68139209336 --- /dev/null +++ b/pmd-plsql/src/test/java/net/sourceforge/pmd/lang/plsql/rule/codestyle/ForLoopNamingTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.plsql.rule.codestyle; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class ForLoopNamingTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-plsql/src/test/java/net/sourceforge/pmd/lang/plsql/rule/codestyle/MisplacedPragmaTest.java b/pmd-plsql/src/test/java/net/sourceforge/pmd/lang/plsql/rule/codestyle/MisplacedPragmaTest.java new file mode 100644 index 00000000000..ef16e2a0a57 --- /dev/null +++ b/pmd-plsql/src/test/java/net/sourceforge/pmd/lang/plsql/rule/codestyle/MisplacedPragmaTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.plsql.rule.codestyle; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class MisplacedPragmaTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-plsql/src/test/java/net/sourceforge/pmd/lang/plsql/rule/design/CyclomaticComplexityTest.java b/pmd-plsql/src/test/java/net/sourceforge/pmd/lang/plsql/rule/design/CyclomaticComplexityTest.java new file mode 100644 index 00000000000..bb124beb554 --- /dev/null +++ b/pmd-plsql/src/test/java/net/sourceforge/pmd/lang/plsql/rule/design/CyclomaticComplexityTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.plsql.rule.design; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class CyclomaticComplexityTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-plsql/src/test/java/net/sourceforge/pmd/lang/plsql/rule/design/ExcessiveMethodLengthTest.java b/pmd-plsql/src/test/java/net/sourceforge/pmd/lang/plsql/rule/design/ExcessiveMethodLengthTest.java new file mode 100644 index 00000000000..e8d3b476937 --- /dev/null +++ b/pmd-plsql/src/test/java/net/sourceforge/pmd/lang/plsql/rule/design/ExcessiveMethodLengthTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.plsql.rule.design; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class ExcessiveMethodLengthTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-plsql/src/test/java/net/sourceforge/pmd/lang/plsql/rule/design/ExcessiveObjectLengthTest.java b/pmd-plsql/src/test/java/net/sourceforge/pmd/lang/plsql/rule/design/ExcessiveObjectLengthTest.java new file mode 100644 index 00000000000..b29f3fd4466 --- /dev/null +++ b/pmd-plsql/src/test/java/net/sourceforge/pmd/lang/plsql/rule/design/ExcessiveObjectLengthTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.plsql.rule.design; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class ExcessiveObjectLengthTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-plsql/src/test/java/net/sourceforge/pmd/lang/plsql/rule/design/ExcessivePackageBodyLengthTest.java b/pmd-plsql/src/test/java/net/sourceforge/pmd/lang/plsql/rule/design/ExcessivePackageBodyLengthTest.java new file mode 100644 index 00000000000..622befeac76 --- /dev/null +++ b/pmd-plsql/src/test/java/net/sourceforge/pmd/lang/plsql/rule/design/ExcessivePackageBodyLengthTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.plsql.rule.design; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class ExcessivePackageBodyLengthTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-plsql/src/test/java/net/sourceforge/pmd/lang/plsql/rule/design/ExcessivePackageSpecificationLengthTest.java b/pmd-plsql/src/test/java/net/sourceforge/pmd/lang/plsql/rule/design/ExcessivePackageSpecificationLengthTest.java new file mode 100644 index 00000000000..0640ce86ebc --- /dev/null +++ b/pmd-plsql/src/test/java/net/sourceforge/pmd/lang/plsql/rule/design/ExcessivePackageSpecificationLengthTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.plsql.rule.design; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class ExcessivePackageSpecificationLengthTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-plsql/src/test/java/net/sourceforge/pmd/lang/plsql/rule/design/ExcessiveParameterListTest.java b/pmd-plsql/src/test/java/net/sourceforge/pmd/lang/plsql/rule/design/ExcessiveParameterListTest.java new file mode 100644 index 00000000000..fb04705feb6 --- /dev/null +++ b/pmd-plsql/src/test/java/net/sourceforge/pmd/lang/plsql/rule/design/ExcessiveParameterListTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.plsql.rule.design; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class ExcessiveParameterListTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-plsql/src/test/java/net/sourceforge/pmd/lang/plsql/rule/design/ExcessiveTypeLengthTest.java b/pmd-plsql/src/test/java/net/sourceforge/pmd/lang/plsql/rule/design/ExcessiveTypeLengthTest.java new file mode 100644 index 00000000000..6be7fb12680 --- /dev/null +++ b/pmd-plsql/src/test/java/net/sourceforge/pmd/lang/plsql/rule/design/ExcessiveTypeLengthTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.plsql.rule.design; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class ExcessiveTypeLengthTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-plsql/src/test/java/net/sourceforge/pmd/lang/plsql/rule/design/NPathComplexityTest.java b/pmd-plsql/src/test/java/net/sourceforge/pmd/lang/plsql/rule/design/NPathComplexityTest.java new file mode 100644 index 00000000000..5a3ba94273d --- /dev/null +++ b/pmd-plsql/src/test/java/net/sourceforge/pmd/lang/plsql/rule/design/NPathComplexityTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.plsql.rule.design; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class NPathComplexityTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-plsql/src/test/java/net/sourceforge/pmd/lang/plsql/rule/design/NcssMethodCountTest.java b/pmd-plsql/src/test/java/net/sourceforge/pmd/lang/plsql/rule/design/NcssMethodCountTest.java new file mode 100644 index 00000000000..34d7cccbf24 --- /dev/null +++ b/pmd-plsql/src/test/java/net/sourceforge/pmd/lang/plsql/rule/design/NcssMethodCountTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.plsql.rule.design; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class NcssMethodCountTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-plsql/src/test/java/net/sourceforge/pmd/lang/plsql/rule/design/NcssObjectCountTest.java b/pmd-plsql/src/test/java/net/sourceforge/pmd/lang/plsql/rule/design/NcssObjectCountTest.java new file mode 100644 index 00000000000..4f2a2189230 --- /dev/null +++ b/pmd-plsql/src/test/java/net/sourceforge/pmd/lang/plsql/rule/design/NcssObjectCountTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.plsql.rule.design; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class NcssObjectCountTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-plsql/src/test/java/net/sourceforge/pmd/lang/plsql/rule/design/TooManyFieldsTest.java b/pmd-plsql/src/test/java/net/sourceforge/pmd/lang/plsql/rule/design/TooManyFieldsTest.java new file mode 100644 index 00000000000..ba987410dea --- /dev/null +++ b/pmd-plsql/src/test/java/net/sourceforge/pmd/lang/plsql/rule/design/TooManyFieldsTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.plsql.rule.design; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class TooManyFieldsTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-plsql/src/test/java/net/sourceforge/pmd/lang/plsql/rule/strictsyntax/StrictsyntaxRulesTest.java b/pmd-plsql/src/test/java/net/sourceforge/pmd/lang/plsql/rule/strictsyntax/StrictsyntaxRulesTest.java deleted file mode 100644 index 97c01812046..00000000000 --- a/pmd-plsql/src/test/java/net/sourceforge/pmd/lang/plsql/rule/strictsyntax/StrictsyntaxRulesTest.java +++ /dev/null @@ -1,17 +0,0 @@ -/** - * BSD-style license; for more info see http://pmd.sourceforge.net/license.html - */ - -package net.sourceforge.pmd.lang.plsql.rule.strictsyntax; - -import net.sourceforge.pmd.testframework.SimpleAggregatorTst; - -public class StrictsyntaxRulesTest extends SimpleAggregatorTst { - - private static final String RULESET = "plsql-strictsyntax"; - - @Override - public void setUp() { - addRule(RULESET, "MisplacedPragma"); - } -} diff --git a/pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/ast/CodingStyleExample.pls b/pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/ast/CodingStyleExample.pls new file mode 100644 index 00000000000..98d549fc651 --- /dev/null +++ b/pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/ast/CodingStyleExample.pls @@ -0,0 +1,96 @@ +PROCEDURE create_prospect( + company_info_in IN prospects.company_info%TYPE -- Organization + ,firstname_in IN persons.firstname%TYPE -- FirstName + ,lastname_in IN persons.lastname%TYPE -- LastName + +-- ... + ,message_in IN OUT NOCOPY CLOB -- the whole message + ) + IS + v_adt_id academic_titles.adt_id%TYPE := 1; + v_region_id addresses.rgn_id%TYPE; + v_prospect_id prospects.prosp_id%TYPE; + v_address_id addresses.adr_id%TYPE := 1; + v_message_rec snapaddy_messages%ROWTYPE; + BEGIN + /* I AM A FORBIDDEN COMMENT SINCE I AM A BLOCK COMMENT */ + + -- try to find a matching academic title. + -- this comment is on a separate line, so it is left aligned + BEGIN -- this comment is on the same line as an PL/SQL statement, it is right aligned + SELECT adt.adt_id + INTO v_adt_id + FROM academic_titles adt + WHERE UPPER(adt.short_description) = UPPER(title_in); + EXCEPTION + WHEN NO_DATA_FOUND THEN + v_adt_id := NULL; + WHEN TOO_MANY_ROWS THEN + v_adt_id := NULL; + END; + + + -- try to find a matching region + -- 1. a case insensitive lookup on the name (restricted to the given country) + BEGIN + SELECT rgn.rgn_id + INTO v_region_id + FROM regions rgn + WHERE (street_cny_code_in IS NULL + OR rgn.cny_code = street_cny_code_in) + AND UPPER(rgn.name) = UPPER(street_rgn_in); + EXCEPTION + WHEN NO_DATA_FOUND THEN + v_region_id := NULL; + WHEN TOO_MANY_ROWS THEN + v_region_id := NULL; + END; + + -- 2. a case insensitive lookup on the SAP_CODE (restricted to the given country) + IF v_region_id IS NULL THEN + BEGIN + SELECT rgn.rgn_id + INTO v_region_id + FROM regions rgn + WHERE (street_cny_code_in IS NULL + OR rgn.cny_code = street_cny_code_in) + AND UPPER(rgn.sap_code) = UPPER(street_rgn_in); + EXCEPTION + WHEN NO_DATA_FOUND THEN + v_region_id := NULL; + WHEN TOO_MANY_ROWS THEN + v_region_id := NULL; + END; + END IF; + + -- all preparations are done - create the prospect with the available data + webcrm_marketing.prospect_ins( + cmp_id_in => NULL + ,company_info_in => company_info_in + ,firstname_in => firstname_in + ,lastname_in => lastname_in + -- ... + ,prosp_id_out => v_prospect_id + ); + + -- create the address with the available data + webcrm_marketing.address_ins( + pa_id_in => v_prospect_id + ,street_city_in => street_city_in + ,street_tmz_id_in => NULL + ,street_cny_code_in => street_cny_code_in + ,street_rgn_id_in => v_region_id + ,street_postalcode_in => street_postalcode_in + ,street_addrline_1_in => street_addrline_1_in + ,street_addrline_2_in => NULL + ,street_addrline_3_in => NULL + ,street_addr_id_out => v_address_id + ); + + -- store the obtained message + v_message_rec.snap_msg_id := seq_snap_msg.NEXTVAL; + v_message_rec.MESSAGE := message_in; + v_message_rec.endpoint := 'prospect/creation'; + v_message_rec.processing_time := SYSDATE; + dml_snap_msg.ins(v_message_rec); +END create_prospect; diff --git a/pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/ast/CreateTable.pls b/pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/ast/CreateTable.pls new file mode 100644 index 00000000000..5d996edfc8f --- /dev/null +++ b/pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/ast/CreateTable.pls @@ -0,0 +1,11 @@ +-- +-- BSD-style license; for more info see http://pmd.sourceforge.net/license.html +-- + +-- this is the customers.sql file: +CREATE TABLE customers +( customer_id number(10) NOT NULL, +customer_name varchar2(50) NOT NULL, +city varchar2(50), +CONSTRAINT customers_pk PRIMARY KEY (customer_id) +); diff --git a/pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/ast/CursorForLoop1047a.pls b/pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/ast/CursorForLoop1047a.pls new file mode 100644 index 00000000000..04a27665e4d --- /dev/null +++ b/pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/ast/CursorForLoop1047a.pls @@ -0,0 +1,16 @@ +-- +-- BSD-style license; for more info see http://pmd.sourceforge.net/license.html +-- + +CREATE OR REPLACE PROCEDURE test2 ( a OUT number, b OUT number ) + AS + BEGIN + FOR registro IN ( SELECT SUM(field1), + MAX(field2) + INTO a,b + FROM test_tbl + GROUP BY fieldx) + LOOP + null; + END LOOP; +END test2; diff --git a/pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/ast/CursorForLoop1047b.pls b/pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/ast/CursorForLoop1047b.pls new file mode 100644 index 00000000000..009e39cff9c --- /dev/null +++ b/pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/ast/CursorForLoop1047b.pls @@ -0,0 +1,13 @@ +-- +-- BSD-style license; for more info see http://pmd.sourceforge.net/license.html +-- + +-- this one was processed without errors +CREATE OR REPLACE PROCEDURE test2 ( a OUT number, b OUT number ) + AS +BEGIN + FOR registro IN ( SELECT SUM(field1),MAX(field2) INTO a,b FROM test_tbl GROUP BY fieldx ) + LOOP + null; + END LOOP; +END test2; diff --git a/pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/ast/CursorForLoop681.pls b/pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/ast/CursorForLoop681.pls new file mode 100644 index 00000000000..c98deddc3af --- /dev/null +++ b/pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/ast/CursorForLoop681.pls @@ -0,0 +1,20 @@ +-- +-- BSD-style license; for more info see http://pmd.sourceforge.net/license.html +-- + +BEGIN + FOR sum_res IN (SELECT SUM(loss_reserve) loss_reserve, + SUM(expense_reserve) exp_reserve + FROM gicl_clm_reserve a, gicl_item_peril b + WHERE a.claim_id = b.claim_id + AND a.item_no = b.item_no + AND a.peril_cd = b.peril_cd + AND a.claim_id = p_claim_id + AND (NVL(b.close_flag, 'AP') IN ('AP','CC','CP') OR + NVL(b.close_flag2, 'AP') IN ('AP','CC','CP'))) + LOOP + v_loss_res_amt := sum_res.loss_reserve; + v_exp_res_amt := sum_res.exp_reserve; + END LOOP; +END; +/ diff --git a/pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/ast/CursorForLoopNested.pls b/pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/ast/CursorForLoopNested.pls new file mode 100644 index 00000000000..8eccf7a6020 --- /dev/null +++ b/pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/ast/CursorForLoopNested.pls @@ -0,0 +1,14 @@ +-- +-- BSD-style license; for more info see http://pmd.sourceforge.net/license.html +-- + +BEGIN +FOR c_cmp IN (SELECT * FROM companies) LOOP + FOR c_con IN (SELECT * FROM contacts) LOOP + FOR c_pa IN (SELECT * FROM parties) LOOP + NULL; + END LOOP; + END LOOP; +END LOOP; +END; +/ diff --git a/pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/ast/CursorForLoopSimple.pls b/pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/ast/CursorForLoopSimple.pls new file mode 100644 index 00000000000..9c43ab8f7dd --- /dev/null +++ b/pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/ast/CursorForLoopSimple.pls @@ -0,0 +1,16 @@ +-- +-- BSD-style license; for more info see http://pmd.sourceforge.net/license.html +-- + +BEGIN +FOR someone IN ( +SELECT * FROM employees +WHERE employee_id < 120 +ORDER BY employee_id +) +LOOP +DBMS_OUTPUT.PUT_LINE('First name = ' || someone.first_name || +', Last name = ' || someone.last_name); +END LOOP; +END; +/ diff --git a/pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/ast/DeleteStatementExample.pls b/pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/ast/DeleteStatementExample.pls new file mode 100644 index 00000000000..ee865a8ea3a --- /dev/null +++ b/pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/ast/DeleteStatementExample.pls @@ -0,0 +1,21 @@ +-- +-- Deleting Rows: Examples +-- https://docs.oracle.com/en/database/oracle/oracle-database/18/sqlrf/DELETE.html#GUID-156845A5-B626-412B-9F95-8869B988ABD7 +-- + +DECLARE +BEGIN + + DELETE FROM product_descriptions + WHERE language_id = 'AR'; + + DELETE FROM employees + WHERE job_id = 'SA_REP' + AND commission_pct < .2; + + DELETE FROM (SELECT * FROM employees) + WHERE job_id = 'SA_REP' + AND commission_pct < .2; + +END; +/ diff --git a/pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/ast/ExecuteImmediate1047a.pls b/pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/ast/ExecuteImmediate1047a.pls new file mode 100644 index 00000000000..e296bbfe8aa --- /dev/null +++ b/pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/ast/ExecuteImmediate1047a.pls @@ -0,0 +1,14 @@ +-- +-- BSD-style license; for more info see http://pmd.sourceforge.net/license.html +-- + +CREATE OR REPLACE PROCEDURE test ( p_num_reg OUT number ) +AS + v_query clob; +BEGIN + v_query:='select count(1) from test_tbl where id =:param'; + + -- Note: execute immediate statement on multiple lines! + execute immediate v_query into p_num_reg + USING 'P'; +END test; diff --git a/pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/ast/ExecuteImmediate1047b.pls b/pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/ast/ExecuteImmediate1047b.pls new file mode 100644 index 00000000000..ab8e6f2c0ed --- /dev/null +++ b/pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/ast/ExecuteImmediate1047b.pls @@ -0,0 +1,12 @@ +-- +-- BSD-style license; for more info see http://pmd.sourceforge.net/license.html +-- + +CREATE OR REPLACE PROCEDURE test ( p_num_reg OUT number ) +AS + v_query clob; +BEGIN + v_query:='select count(1) from test_tbl where id =:param'; + + execute immediate v_query into p_num_reg USING 'P'; +END test; diff --git a/pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/ast/InnerCrossJoin.pls b/pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/ast/InnerCrossJoin.pls new file mode 100644 index 00000000000..98cddf4aa95 --- /dev/null +++ b/pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/ast/InnerCrossJoin.pls @@ -0,0 +1,6 @@ +BEGIN +SELECT department_id AS d_e_dept_id, e.last_name + INTO r_record + FROM departments CROSS JOIN employees; +END; +/ diff --git a/pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/ast/InnerJoinUsing.pls b/pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/ast/InnerJoinUsing.pls new file mode 100644 index 00000000000..fb426ab8dc5 --- /dev/null +++ b/pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/ast/InnerJoinUsing.pls @@ -0,0 +1,7 @@ +BEGIN +SELECT department_id AS d_e_dept_id, e.last_name +INTO r_record + FROM departments d JOIN employees e + USING (department_id); +END; +/ diff --git a/pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/ast/InnerNaturalJoin.pls b/pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/ast/InnerNaturalJoin.pls new file mode 100644 index 00000000000..8f47eab32d3 --- /dev/null +++ b/pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/ast/InnerNaturalJoin.pls @@ -0,0 +1,6 @@ +BEGIN +SELECT department_id AS d_e_dept_id, e.last_name + INTO r_record + FROM departments NATURAL JOIN employees; +END; +/ diff --git a/pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/ast/NaturalRightOuterJoin.pls b/pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/ast/NaturalRightOuterJoin.pls new file mode 100644 index 00000000000..b1d713d2453 --- /dev/null +++ b/pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/ast/NaturalRightOuterJoin.pls @@ -0,0 +1,8 @@ +BEGIN +SELECT times.time_id, product, quantity +INTO r_record +FROM inventory + NATURAL RIGHT OUTER JOIN times ON (times.time_id = inventory.time_id) + ORDER BY 2,1; +END; +/ \ No newline at end of file diff --git a/pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/ast/OuterJoinPartitioned.pls b/pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/ast/OuterJoinPartitioned.pls new file mode 100644 index 00000000000..3e5b856696c --- /dev/null +++ b/pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/ast/OuterJoinPartitioned.pls @@ -0,0 +1,9 @@ +BEGIN +SELECT times.time_id, product, quantity +INTO r_record +FROM inventory + PARTITION BY (product) + RIGHT OUTER JOIN times ON (times.time_id = inventory.time_id) + ORDER BY 2,1; +END; +/ \ No newline at end of file diff --git a/pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/ast/OuterJoinUsing.pls b/pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/ast/OuterJoinUsing.pls new file mode 100644 index 00000000000..744edf91267 --- /dev/null +++ b/pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/ast/OuterJoinUsing.pls @@ -0,0 +1,7 @@ +BEGIN +SELECT department_id AS d_e_dept_id, e.last_name + INTO r_record + FROM departments d FULL OUTER JOIN employees e + USING (department_id); +END; +/ \ No newline at end of file diff --git a/pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/ast/RightOuterJoin.pls b/pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/ast/RightOuterJoin.pls new file mode 100644 index 00000000000..6b40ad95d1d --- /dev/null +++ b/pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/ast/RightOuterJoin.pls @@ -0,0 +1,8 @@ +BEGIN +SELECT times.time_id, product, quantity +INTO r_record +FROM inventory + RIGHT OUTER JOIN times ON (times.time_id = inventory.time_id) + ORDER BY 2,1; +END; +/ \ No newline at end of file diff --git a/pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/ast/SelectExpressions.pls b/pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/ast/SelectExpressions.pls new file mode 100644 index 00000000000..9270c1c1f11 --- /dev/null +++ b/pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/ast/SelectExpressions.pls @@ -0,0 +1,29 @@ +-- +-- Expressions in the SelectList +-- + +BEGIN + +SELECT + some_col "alias in quotes" + INTO some_record + FROM some_table; + + +SELECT + COUNT(*) "Total Empl" + INTO some_record + FROM some_table; + +SELECT + AVG(salary) * 12 "Average Sal" + INTO some_record + FROM some_table; + +SELECT + TO_CHAR(sum(amount_sold) , '9,999,999,999') SALES$ + INTO some_record + FROM some_table; + +END; +/ \ No newline at end of file diff --git a/pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/ast/SelectIntoStatement.pls b/pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/ast/SelectIntoStatement.pls new file mode 100644 index 00000000000..acd5d5c3fc0 --- /dev/null +++ b/pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/ast/SelectIntoStatement.pls @@ -0,0 +1,99 @@ +DECLARE + TYPE t_cmer_ids IS TABLE OF comp_mprc_emp_role.id%TYPE; + TYPE t_versions IS TABLE OF comp_mprc_emp_role.version%TYPE; + TYPE t_cmp_ids IS TABLE OF comp_mprc_emp_role.cmp_id%TYPE; + TYPE t_mprc_codes IS TABLE OF comp_mprc_emp_role.mprc_code%TYPE; + TYPE t_emp_ids IS TABLE OF comp_mprc_emp_role.emp_id%TYPE; + TYPE t_emp_role_codes IS TABLE OF comp_mprc_emp_role.emp_role_code%TYPE; + TYPE t_gsn_ids IS TABLE OF comp_mprc_emp_role.gsn_id%TYPE; + TYPE t_dep_codes IS TABLE OF comp_mprc_emp_role.dep_code%TYPE; + TYPE t_validfroms IS TABLE OF DATE; + TYPE t_validtos IS TABLE OF DATE; + TYPE t_fl_actives IS TABLE OF VARCHAR2(1); + TYPE t_reporting_lists IS TABLE OF v_sfm_emp_role_cny_repg.reporting_groups%TYPE; + + v_cmer_ids t_cmer_ids; + v_versions t_versions; + v_cmp_ids t_cmp_ids; + v_mprc_codes t_mprc_codes; + v_emp_ids t_emp_ids; + v_emp_role_codes t_emp_role_codes; + v_gsn_ids t_gsn_ids; + v_dep_codes t_dep_codes; + v_validfroms t_validfroms; + v_validtos t_validtos; + v_fl_actives t_fl_actives; + v_reporting_lists t_reporting_lists; +BEGIN + SELECT cmer_id + ,version + ,cmp_id + ,mprc_code + ,emp_id + ,emp_role_code + ,gsn_id + ,dep_code + ,validfrom + ,validto + ,fl_active + ,reporting_list + BULK COLLECT INTO v_cmer_ids + ,v_versions + ,v_cmp_ids + ,v_mprc_codes + ,v_emp_ids + ,v_emp_role_codes + ,v_gsn_ids + ,v_dep_codes + ,v_validfroms + ,v_validtos + ,v_fl_actives + ,v_reporting_lists + FROM (SELECT cmer.cmer_id + ,cmer.version + ,cmer.cmp_id + ,cmer.mprc_code + ,cmer.emp_id + ,cmer.emp_role_code + ,cmer.gsn_id + ,cmer.dep_code + ,TRUNC(insert_entry.dmltime) AS validfrom + ,NULL AS validto + ,'Y' AS fl_active + ,sfm.reporting_groups AS reporting_list + FROM webcrm.comp_mprc_emp_role cmer + JOIN webcrm.jcomp_mprc_emp_role insert_entry + ON (insert_entry.cmer_id = cmer.cmer_id + AND insert_entry.operation = 'I') + LEFT JOIN webcrm.v_company_address caddr ON (caddr.cmp_id = cmer.cmp_id) + LEFT JOIN webcrm.v_sfm_emp_role_cny_repg sfm + ON (sfm.emp_id = cmer.emp_id + AND sfm.emp_role_code = cmer.emp_role_code + AND sfm.cny_code = NVL(caddr.s_cny_code, caddr.m_cny_code)) + UNION + SELECT jcmer.cmer_id + ,jcmer.version + ,jcmer.cmp_id + ,jcmer.mprc_code + ,jcmer.emp_id + ,jcmer.emp_role_code + ,jcmer.gsn_id + ,jcmer.dep_code + ,TRUNC(insert_entry.dmltime) AS validfrom + ,TRUNC(jcmer.dmltime) AS validfrom + ,'N' AS fl_active + ,sfm.reporting_groups AS reporting_list + FROM webcrm.jcomp_mprc_emp_role jcmer + JOIN webcrm.jcomp_mprc_emp_role insert_entry + ON (insert_entry.cmer_id = jcmer.cmer_id + AND insert_entry.operation = 'I') + LEFT JOIN webcrm.v_company_address caddr ON (caddr.cmp_id = jcmer.cmp_id) + LEFT JOIN v_sfm_emp_role_cny_repg sfm + ON (sfm.emp_id = jcmer.emp_id + AND sfm.emp_role_code = jcmer.emp_role_code + AND sfm.cny_code = NVL(caddr.s_cny_code, caddr.m_cny_code)) + WHERE jcmer.cmer_id NOT IN (SELECT cmer.cmer_id + FROM webcrm.comp_mprc_emp_role cmer) + AND jcmer.operation = 'D'); +END; +/ diff --git a/pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/ast/SelectIntoStatementExample1.pls b/pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/ast/SelectIntoStatementExample1.pls new file mode 100644 index 00000000000..137703c5698 --- /dev/null +++ b/pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/ast/SelectIntoStatementExample1.pls @@ -0,0 +1,14 @@ +-- +-- Example 2-25 Assigning Value to Variable with SELECT INTO Statement +-- From: https://docs.oracle.com/en/database/oracle/oracle-database/18/lnpls/plsql-language-fundamentals.html#GUID-664BFFEA-063A-48B6-A65B-95225EDDED59 +-- +DECLARE + bonus NUMBER(8,2); +BEGIN + SELECT salary * 0.10 INTO bonus + FROM employees + WHERE employee_id = 100; +END; + +DBMS_OUTPUT.PUT_LINE('bonus = ' || TO_CHAR(bonus)); +/ \ No newline at end of file diff --git a/pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/ast/SelectIntoStatementExample2.pls b/pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/ast/SelectIntoStatementExample2.pls new file mode 100644 index 00000000000..99211e7f49f --- /dev/null +++ b/pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/ast/SelectIntoStatementExample2.pls @@ -0,0 +1,18 @@ +-- +-- Example 5-50 SELECT INTO Assigns Values to Record Variable +-- From: https://docs.oracle.com/en/database/oracle/oracle-database/18/lnpls/plsql-collections-and-records.html#GUID-CC1DA893-4087-4DA4-8B13-052AB76DAC4F +-- +DECLARE + TYPE RecordTyp IS RECORD ( + last employees.last_name%TYPE, + id employees.employee_id%TYPE + ); + rec1 RecordTyp; +BEGIN + SELECT last_name, employee_id INTO rec1 + FROM employees + WHERE job_id = 'AD_PRES'; + + DBMS_OUTPUT.PUT_LINE ('Employee #' || rec1.id || ' = ' || rec1.last); +END; +/ diff --git a/pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/ast/SelectIntoStatementExample3.pls b/pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/ast/SelectIntoStatementExample3.pls new file mode 100644 index 00000000000..2d059b9f592 --- /dev/null +++ b/pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/ast/SelectIntoStatementExample3.pls @@ -0,0 +1,57 @@ +-- +-- Example 6-37 ROLLBACK Statement +-- From: https://docs.oracle.com/en/database/oracle/oracle-database/18/lnpls/static-sql.html#GUID-B1B20D2F-C421-446C-9171-1D03E2D77BF8 +-- +DROP TABLE emp_name; +CREATE TABLE emp_name AS + SELECT employee_id, last_name + FROM employees; + +CREATE UNIQUE INDEX empname_ix +ON emp_name (employee_id); + + +DROP TABLE emp_sal; +CREATE TABLE emp_sal AS + SELECT employee_id, salary + FROM employees; + +CREATE UNIQUE INDEX empsal_ix +ON emp_sal (employee_id); + + +DROP TABLE emp_job; +CREATE TABLE emp_job AS + SELECT employee_id, job_id + FROM employees; + +CREATE UNIQUE INDEX empjobid_ix +ON emp_job (employee_id); + + +DECLARE + emp_id NUMBER(6); + emp_lastname VARCHAR2(25); + emp_salary NUMBER(8,2); + emp_jobid VARCHAR2(10); +BEGIN + SELECT employee_id, last_name, salary, job_id + INTO emp_id, emp_lastname, emp_salary, emp_jobid + FROM employees + WHERE employee_id = 120; + + INSERT INTO emp_name (employee_id, last_name) + VALUES (emp_id, emp_lastname); + + INSERT INTO emp_sal (employee_id, salary) + VALUES (emp_id, emp_salary); + + INSERT INTO emp_job (employee_id, job_id) + VALUES (emp_id, emp_jobid); + +EXCEPTION + WHEN DUP_VAL_ON_INDEX THEN + ROLLBACK; + DBMS_OUTPUT.PUT_LINE('Inserts were rolled back'); +END; +/ diff --git a/pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/ast/SelectIntoStatementExample4.pls b/pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/ast/SelectIntoStatementExample4.pls new file mode 100644 index 00000000000..e91ae710bb8 --- /dev/null +++ b/pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/ast/SelectIntoStatementExample4.pls @@ -0,0 +1,25 @@ +-- +-- Example 6-43 Declaring Autonomous Function in Package +-- From: https://docs.oracle.com/en/database/oracle/oracle-database/18/lnpls/static-sql.html#GUID-FBD2040A-9047-44DB-8C82-5F6D08990227 +-- +CREATE OR REPLACE PACKAGE emp_actions AUTHID DEFINER AS -- package specification + FUNCTION raise_salary (emp_id NUMBER, sal_raise NUMBER) + RETURN NUMBER; +END emp_actions; +/ +CREATE OR REPLACE PACKAGE BODY emp_actions AS -- package body + -- code for function raise_salary + FUNCTION raise_salary (emp_id NUMBER, sal_raise NUMBER) + RETURN NUMBER IS + PRAGMA AUTONOMOUS_TRANSACTION; + new_sal NUMBER(8,2); + BEGIN + UPDATE employees SET salary = + salary + sal_raise WHERE employee_id = emp_id; + COMMIT; + SELECT salary INTO new_sal FROM employees + WHERE employee_id = emp_id; + RETURN new_sal; + END raise_salary; +END emp_actions; +/ diff --git a/pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/ast/SelectIntoStatementExample5.pls b/pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/ast/SelectIntoStatementExample5.pls new file mode 100644 index 00000000000..7a765c53444 --- /dev/null +++ b/pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/ast/SelectIntoStatementExample5.pls @@ -0,0 +1,35 @@ +-- +-- Example 12-16 Bulk-Selecting Two Database Columns into Two Nested Tables +-- From: https://docs.oracle.com/en/database/oracle/oracle-database/18/lnpls/plsql-optimization-and-tuning.html#GUID-6E09E4FC-28C0-43C8-9E7C-A54D6398D1DE +-- +DECLARE + TYPE NumTab IS TABLE OF employees.employee_id%TYPE; + TYPE NameTab IS TABLE OF employees.last_name%TYPE; + + enums NumTab; + names NameTab; + + PROCEDURE print_first_n (n POSITIVE) IS + BEGIN + IF enums.COUNT = 0 THEN + DBMS_OUTPUT.PUT_LINE ('Collections are empty.'); + ELSE + DBMS_OUTPUT.PUT_LINE ('First ' || n || ' employees:'); + + FOR i IN 1 .. n LOOP + DBMS_OUTPUT.PUT_LINE ( + ' Employee #' || enums(i) || ': ' || names(i)); + END LOOP; + END IF; + END; + +BEGIN + SELECT employee_id, last_name + BULK COLLECT INTO enums, names + FROM employees + ORDER BY employee_id; + + print_first_n(3); + print_first_n(6); +END; +/ diff --git a/pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/ast/SelectIntoStatementFunctionCall.pls b/pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/ast/SelectIntoStatementFunctionCall.pls new file mode 100644 index 00000000000..ce746f3ffb2 --- /dev/null +++ b/pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/ast/SelectIntoStatementFunctionCall.pls @@ -0,0 +1,17 @@ +-- +-- BSD-style license; for more info see http://pmd.sourceforge.net/license.html +-- + +DECLARE + a NUMBER(8,2); + b NUMBER(8,2); +BEGIN + +SELECT SUM(field1), + MAX(field2) + INTO a,b + FROM test_tbl + GROUP BY fieldx; + +END; +/ \ No newline at end of file diff --git a/pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/ast/SelectIntoWithGroupBy1.pls b/pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/ast/SelectIntoWithGroupBy1.pls new file mode 100644 index 00000000000..58479a20e55 --- /dev/null +++ b/pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/ast/SelectIntoWithGroupBy1.pls @@ -0,0 +1,11 @@ +-- +-- Using the GROUP BY Clause: Examples +-- https://docs.oracle.com/en/database/oracle/oracle-database/18/sqlrf/SELECT.html#GUID-CFA006CA-6FF1-4972-821E-6996142A51C6__I2066419 +-- +BEGIN +SELECT department_id, MIN(salary), MAX (salary) INTO some_record + FROM employees + GROUP BY department_id + ORDER BY department_id; +END; +/ diff --git a/pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/ast/SelectIntoWithGroupBy2.pls b/pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/ast/SelectIntoWithGroupBy2.pls new file mode 100644 index 00000000000..74056058cdc --- /dev/null +++ b/pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/ast/SelectIntoWithGroupBy2.pls @@ -0,0 +1,12 @@ +-- +-- Using the GROUP BY Clause: Examples +-- https://docs.oracle.com/en/database/oracle/oracle-database/18/sqlrf/SELECT.html#GUID-CFA006CA-6FF1-4972-821E-6996142A51C6__I2066419 +-- +BEGIN +SELECT department_id, MIN(salary), MAX (salary) INTO some_record + FROM employees + WHERE job_id = 'PU_CLERK' + GROUP BY department_id + ORDER BY department_id; +END; +/ diff --git a/pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/ast/SelectIntoWithGroupBy3.pls b/pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/ast/SelectIntoWithGroupBy3.pls new file mode 100644 index 00000000000..5639d389e29 --- /dev/null +++ b/pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/ast/SelectIntoWithGroupBy3.pls @@ -0,0 +1,16 @@ +-- +-- Using the GROUP BY CUBE Clause: Example +-- https://docs.oracle.com/en/database/oracle/oracle-database/18/sqlrf/SELECT.html#GUID-CFA006CA-6FF1-4972-821E-6996142A51C6__I2066419 +-- +BEGIN +SELECT DECODE(GROUPING(department_name), 1, 'All Departments', + department_name) AS department_name, + DECODE(GROUPING(job_id), 1, 'All Jobs', job_id) AS job_id, + count Total_Empl +INTO some_record + FROM employees e, departments d + WHERE d.department_id = e.department_id + GROUP BY CUBE (department_name, job_id) + ORDER BY department_name, job_id; +END; +/ diff --git a/pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/ast/SelectIntoWithGroupBy4.pls b/pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/ast/SelectIntoWithGroupBy4.pls new file mode 100644 index 00000000000..add9c343d45 --- /dev/null +++ b/pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/ast/SelectIntoWithGroupBy4.pls @@ -0,0 +1,24 @@ +-- +-- Using the GROUPING SETS Clause: Example +-- https://docs.oracle.com/en/database/oracle/oracle-database/18/sqlrf/SELECT.html#GUID-CFA006CA-6FF1-4972-821E-6996142A51C6__I2066419 +-- +BEGIN + +SELECT channel_desc, calendar_month_desc, co.country_id, + amount_sold SALES$ + INTO some_record + FROM sales, customers, times, channels, countries co + WHERE sales.time_id=times.time_id + AND sales.cust_id=customers.cust_id + AND sales.channel_id= channels.channel_id + AND customers.country_id = co.country_id + AND channels.channel_desc IN ('Direct Sales', 'Internet') + AND times.calendar_month_desc IN ('2000-09', '2000-10') + AND co.country_iso_code IN ('UK', 'US') + GROUP BY GROUPING SETS( + (channel_desc, calendar_month_desc, co.country_id), + (channel_desc, co.country_id), + (calendar_month_desc, co.country_id) ); + +END; +/ diff --git a/pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/ast/UpdateStatementExample.pls b/pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/ast/UpdateStatementExample.pls new file mode 100644 index 00000000000..7d950e92f5c --- /dev/null +++ b/pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/ast/UpdateStatementExample.pls @@ -0,0 +1,27 @@ +-- +-- Example 5-55, "Updating Rows with Records" +-- https://docs.oracle.com/en/database/oracle/oracle-database/18/lnpls/plsql-collections-and-records.html#GUID-11D63245-591D-4CBF-BFBA-8F3C0AE0E968__BABDGCDI +-- + +DECLARE + default_week schedule%ROWTYPE; +BEGIN + default_week.Mon := 'Day Off'; + default_week.Tue := '0900-1800'; + default_week.Wed := '0900-1800'; + default_week.Thu := '0900-1800'; + default_week.Fri := '0900-1800'; + default_week.Sat := '0900-1800'; + default_week.Sun := 'Day Off'; + + FOR i IN 1..3 LOOP + default_week.week := i; + + UPDATE schedule + SET ROW = default_week + WHERE week = i; + END LOOP; +END; +/ + +SELECT * FROM schedule; diff --git a/pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/ast/ViewIssue981.pls b/pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/ast/ViewIssue981.pls new file mode 100644 index 00000000000..64d171cf948 --- /dev/null +++ b/pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/ast/ViewIssue981.pls @@ -0,0 +1,11 @@ +-- +-- BSD-style license; for more info see http://pmd.sourceforge.net/license.html +-- + +CREATE OR REPLACE FORCE VIEW "HR_DEV1"."EMP_DETAILS_VIEW" ("EMPLOYEE_ID") AS +SELECT +e.employee_id +FROM +employees e +WITH READ ONLY +; diff --git a/pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/ast/WhereClauseBetween.pls b/pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/ast/WhereClauseBetween.pls new file mode 100644 index 00000000000..ed39abf2f8b --- /dev/null +++ b/pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/ast/WhereClauseBetween.pls @@ -0,0 +1,16 @@ +-- +-- Where Clause With Between Condition +-- https://docs.oracle.com/en/database/oracle/oracle-database/18/sqlrf/BETWEEN-Condition.html#GUID-868A7C9D-EDF9-44E7-91B5-C3F69E503CCB +-- + +BEGIN + + SELECT * + INTO rec + FROM employees + WHERE salary + BETWEEN 2000 AND 3000 + ORDER BY employee_id; + +END; +/ diff --git a/pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/ast/WhereClauseFunctionCall.pls b/pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/ast/WhereClauseFunctionCall.pls new file mode 100644 index 00000000000..e2513cfe274 --- /dev/null +++ b/pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/ast/WhereClauseFunctionCall.pls @@ -0,0 +1,20 @@ +-- +-- Where Clause Conditions +-- + +BEGIN + + SELECT adt.adt_id + INTO v_adt_id + FROM academic_titles adt + WHERE UPPER(adt.short_description) = UPPER(title_in); + + SELECT rgn.rgn_id + INTO v_region_id + FROM regions rgn + WHERE (street_cny_code_in IS NULL + OR rgn.cny_code = street_cny_code_in) + AND UPPER(rgn.name) = UPPER(street_rgn_in); + +END; +/ diff --git a/pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/ast/WhereClauseIn.pls b/pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/ast/WhereClauseIn.pls new file mode 100644 index 00000000000..4ab0f436f20 --- /dev/null +++ b/pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/ast/WhereClauseIn.pls @@ -0,0 +1,44 @@ +-- +-- Where Clause With In Condition +-- https://docs.oracle.com/en/database/oracle/oracle-database/18/sqlrf/IN-Condition.html#GUID-C7961CB3-8F60-47E0-96EB-BDCF5DB1317C +-- + +BEGIN + + -- IN + + SELECT * + INTO rec + FROM employees + WHERE job_id IN + ('PU_CLERK','SH_CLERK') + ORDER BY employee_id; + + SELECT * + INTO rec + FROM employees + WHERE salary IN + (SELECT salary + FROM employees + WHERE department_id =30) + ORDER BY employee_id; + + -- NOT IN + + SELECT * + INTO rec + FROM employees + WHERE salary NOT IN + (SELECT salary + FROM employees + WHERE department_id = 30) + ORDER BY employee_id; + + SELECT * + INTO rec + FROM employees + WHERE job_id NOT IN + ('PU_CLERK', 'SH_CLERK') + ORDER BY employee_id; +END; +/ diff --git a/pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/ast/WhereClauseIsNull.pls b/pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/ast/WhereClauseIsNull.pls new file mode 100644 index 00000000000..bc06f13d0b7 --- /dev/null +++ b/pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/ast/WhereClauseIsNull.pls @@ -0,0 +1,16 @@ +-- +-- Where Clause With Null Condition +-- https://docs.oracle.com/en/database/oracle/oracle-database/18/sqlrf/Null-Conditions.html#GUID-657F2BA6-5687-4A00-8C2F-57515FD2DAEB +-- + +BEGIN + + SELECT last_name + INTO rec + FROM employees + WHERE commission_pct + IS NULL + ORDER BY last_name; + +END; +/ diff --git a/pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/ast/WhereClauseIsOfType.pls b/pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/ast/WhereClauseIsOfType.pls new file mode 100644 index 00000000000..19338efb213 --- /dev/null +++ b/pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/ast/WhereClauseIsOfType.pls @@ -0,0 +1,19 @@ +-- +-- Where Clause With Is Of Type Condition +-- https://docs.oracle.com/en/database/oracle/oracle-database/18/sqlrf/IS-OF-type-Condition.html#GUID-7254E4C7-0194-4C1F-A3B2-2CFB0AD907CD +-- + +BEGIN + +SELECT * + INTO rec + FROM persons p + WHERE VALUE(p) IS OF TYPE (employee_t); + +SELECT * + INTO rec + FROM persons p + WHERE VALUE(p) IS OF (ONLY part_time_emp_t); + +END; +/ diff --git a/pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/ast/WhereClauseLike.pls b/pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/ast/WhereClauseLike.pls new file mode 100644 index 00000000000..572b380ce5f --- /dev/null +++ b/pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/ast/WhereClauseLike.pls @@ -0,0 +1,15 @@ +-- +-- Where Clause With Like +-- https://docs.oracle.com/en/database/oracle/oracle-database/18/sqlrf/Pattern-matching-Conditions.html#GUID-0779657B-06A8-441F-90C5-044B47862A0A +-- + +BEGIN + + SELECT salary + INTO rec + FROM employees + WHERE last_name LIKE 'R%' + ORDER BY salary; + +END; +/ diff --git a/pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/rule/codestyle/xml/CodeFormat.xml b/pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/rule/codestyle/xml/CodeFormat.xml new file mode 100644 index 00000000000..5d89af12808 --- /dev/null +++ b/pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/rule/codestyle/xml/CodeFormat.xml @@ -0,0 +1,540 @@ +<?xml version="1.0" encoding="UTF-8"?> +<test-data + xmlns="http://pmd.sourceforge.net/rule-tests" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://pmd.sourceforge.net/rule-tests http://pmd.sourceforge.net/rule-tests_1_0_0.xsd"> + + <test-code> + <description>SQL Queries - ok</description> + <expected-problems>0</expected-problems> + <code><![CDATA[ +BEGIN + SELECT cmer_id + ,version + ,cmp_id + BULK COLLECT INTO v_cmer_ids + ,v_versions + ,v_cmp_ids + FROM cmer; +END; +/ + ]]></code> + <source-type>plsql</source-type> + </test-code> + + <test-code> + <description>SQL Queries - bad</description> + <expected-problems>6</expected-problems> + <expected-linenumbers>2,2,3,3,3,4</expected-linenumbers> + <expected-messages> + <message>cmp_id should be on line 4</message> + <message>version should be on line 3</message> + <message>BULK COLLECT INTO should begin at column 5</message> + <message>v_cmp_ids should be on line 5</message> + <message>v_versions should be on line 4</message> + <message>FROM should begin at column 5</message> + </expected-messages> + <code><![CDATA[ +BEGIN + SELECT cmer_id, version, cmp_id + BULK COLLECT INTO v_cmer_ids, v_versions, v_cmp_ids + FROM cmer; +END; +/ + ]]></code> + <source-type>plsql</source-type> + </test-code> + + <test-code> + <description>SQL Queries: each join on separate lines, union aligned - ok</description> + <expected-problems>0</expected-problems> + <code><![CDATA[ +BEGIN + SELECT cmer_id + ,version + ,cmp_id + BULK COLLECT INTO v_cmer_ids + ,v_versions + ,v_cmp_ids + FROM (SELECT cmer.cmer_id + ,cmer.version + ,cmer.cmp_id + FROM webcrm.comp_mprc_emp_role cmer + JOIN webcrm.jcomp_mprc_emp_role insert_entry + ON (insert_entry.cmer_id = cmer.cmer_id + AND insert_entry.operation = 'I') + LEFT JOIN webcrm.v_company_address caddr ON (caddr.cmp_id = cmer.cmp_id) + UNION + SELECT jcmer.cmer_id + ,jcmer.version + ,jcmer.cmp_id + FROM webcrm.jcomp_mprc_emp_role jcmer); +END; +/ + ]]></code> + <source-type>plsql</source-type> + </test-code> + + <test-code> + <description>SQL Queries: each join on separate lines - violation</description> + <expected-problems>4</expected-problems> + <expected-linenumbers>12,12,12,12</expected-linenumbers> + <expected-messages> + <message>Join condition "caddr.cmp_id = cmer.cmp_id" should be on line 15</message> + <message>Join condition "insert_entry.cmer_id = cmer.cmer_id" should be on line 13</message> + <message>Join condition "insert_entry.operation = I" should be on line 14</message> + <message>OuterJoinClause should be on line 15</message> + </expected-messages> + <code><![CDATA[ +BEGIN + SELECT cmer_id + ,version + ,cmp_id + BULK COLLECT INTO v_cmer_ids + ,v_versions + ,v_cmp_ids + FROM (SELECT cmer.cmer_id + ,cmer.version + ,cmer.cmp_id + FROM webcrm.comp_mprc_emp_role cmer + JOIN webcrm.jcomp_mprc_emp_role insert_entry ON (insert_entry.cmer_id = cmer.cmer_id AND insert_entry.operation = 'I') LEFT JOIN webcrm.v_company_address caddr ON (caddr.cmp_id = cmer.cmp_id) + UNION + SELECT jcmer.cmer_id + ,jcmer.version + ,jcmer.cmp_id + FROM webcrm.jcomp_mprc_emp_role jcmer); +END; +/ + ]]></code> + <source-type>plsql</source-type> + </test-code> + + <test-code> + <description>SQL Queries: union not aligned - violation</description> + <expected-problems>1</expected-problems> + <expected-linenumbers>16</expected-linenumbers> + <expected-messages> + <message>UNION should begin at column 11</message> + </expected-messages> + <code><![CDATA[ +BEGIN + SELECT cmer_id + ,version + ,cmp_id + BULK COLLECT INTO v_cmer_ids + ,v_versions + ,v_cmp_ids + FROM (SELECT cmer.cmer_id + ,cmer.version + ,cmer.cmp_id + FROM webcrm.comp_mprc_emp_role cmer + JOIN webcrm.jcomp_mprc_emp_role insert_entry + ON (insert_entry.cmer_id = cmer.cmer_id + AND insert_entry.operation = 'I') + LEFT JOIN webcrm.v_company_address caddr ON (caddr.cmp_id = cmer.cmp_id) + UNION + SELECT jcmer.cmer_id + ,jcmer.version + ,jcmer.cmp_id + FROM webcrm.jcomp_mprc_emp_role jcmer); +END; +/ ]]></code> + <source-type>plsql</source-type> + </test-code> + + <test-code> + <description>SQL Queries: union not aligned on wrong line - violation</description> + <expected-problems>2</expected-problems> + <expected-linenumbers>15,15</expected-linenumbers> + <expected-messages> + <message>UNION should be on line 16</message> + <message>UNION should begin at column 11</message> + </expected-messages> + <code><![CDATA[ +BEGIN + SELECT cmer_id + ,version + ,cmp_id + BULK COLLECT INTO v_cmer_ids + ,v_versions + ,v_cmp_ids + FROM (SELECT cmer.cmer_id + ,cmer.version + ,cmer.cmp_id + FROM webcrm.comp_mprc_emp_role cmer + JOIN webcrm.jcomp_mprc_emp_role insert_entry + ON (insert_entry.cmer_id = cmer.cmer_id + AND insert_entry.operation = 'I') + LEFT JOIN webcrm.v_company_address caddr ON (caddr.cmp_id = cmer.cmp_id) UNION + SELECT jcmer.cmer_id + ,jcmer.version + ,jcmer.cmp_id + FROM webcrm.jcomp_mprc_emp_role jcmer); +END; +/ ]]></code> + <source-type>plsql</source-type> + </test-code> + + <test-code> + <description>Parameters - ok</description> + <expected-problems>0</expected-problems> + <code><![CDATA[ +-- comment is necessary in order to not trim the whitespace at the beginning + PROCEDURE create_prospect( + company_info_in IN prospects.company_info%TYPE -- Organization + ,firstname_in IN persons.firstname%TYPE -- FirstName + ,lastname_in IN persons.lastname%TYPE -- LastName + ); + ]]></code> + <source-type>plsql</source-type> + </test-code> + + <test-code> + <description>Parameters - not aligned</description> + <expected-problems>4</expected-problems> + <expected-linenumbers>3,3,4,4</expected-linenumbers> + <expected-messages> + <message>Type persons.firstname%TYPE should begin at column 32</message> + <message>firstname_in should be on line 4</message> + <message>Type persons.lastname%TYPE should begin at column 32</message> + <message>lastname_in should be on line 5</message> + </expected-messages> + <code><![CDATA[ +-- comment is necessary in order to not trim the whitespace at the beginning + PROCEDURE create_prospect( + company_info_in IN prospects.company_info%TYPE ,firstname_in IN persons.firstname%TYPE -- FirstName + ,lastname_in IN persons.lastname%TYPE -- LastName + ); + ]]></code> + <source-type>plsql</source-type> + </test-code> + + <test-code> + <description>Variables - ok</description> + <expected-problems>0</expected-problems> + <code><![CDATA[ +PROCEDURE create_prospect() + IS + v_adt_id academic_titles.adt_id%TYPE := 1; + v_region_id addresses.rgn_id%TYPE; + v_prospect_id prospects.prosp_id%TYPE; + v_address_id addresses.adr_id%TYPE := 1; + v_message_rec snapaddy_messages%ROWTYPE; + BEGIN + END; + ]]></code> + <source-type>plsql</source-type> + </test-code> + + <test-code> + <description>Variables - same line, not ok</description> + <expected-problems>8</expected-problems> + <expected-linenumbers>3,3,3,3,3,3,3,3</expected-linenumbers> + <expected-messages> + <message>Type addresses.adr_id%TYPE should begin at column 21</message> + <message>Type addresses.rgn_id%TYPE should begin at column 21</message> + <message>Type prospects.prosp_id%TYPE should begin at column 21</message> + <message>Type snapaddy_messages%ROWTYPE should begin at column 21</message> + <message>v_address_id addresses.adr_id%TYPE := 1 should be on line 6</message> + <message>v_message_rec snapaddy_messages%ROWTYPE should be on line 7</message> + <message>v_prospect_id prospects.prosp_id%TYPE should be on line 5</message> + <message>v_region_id addresses.rgn_id%TYPE should be on line 4</message> + </expected-messages> + <code><![CDATA[ +PROCEDURE create_prospect() + IS + v_adt_id academic_titles.adt_id%TYPE := 1; v_region_id addresses.rgn_id%TYPE; v_prospect_id prospects.prosp_id%TYPE; v_address_id addresses.adr_id%TYPE := 1; v_message_rec snapaddy_messages%ROWTYPE; + BEGIN + END; + ]]></code> + <source-type>plsql</source-type> + </test-code> + + <test-code> + <description>Variables - types not aligned</description> + <expected-problems>4</expected-problems> + <expected-linenumbers>4,5,6,7</expected-linenumbers> + <expected-messages> + <message>Type addresses.rgn_id%TYPE should begin at column 21</message> + <message>Type prospects.prosp_id%TYPE should begin at column 21</message> + <message>Type addresses.adr_id%TYPE should begin at column 21</message> + <message>Type snapaddy_messages%ROWTYPE should begin at column 21</message> + </expected-messages> + <code><![CDATA[ +PROCEDURE create_prospect() + IS + v_adt_id academic_titles.adt_id%TYPE := 1; + v_region_id addresses.rgn_id%TYPE; + v_prospect_id prospects.prosp_id%TYPE; + v_address_id addresses.adr_id%TYPE := 1; + v_message_rec snapaddy_messages%ROWTYPE; + BEGIN + END; + ]]></code> + <source-type>plsql</source-type> + </test-code> + + <test-code> + <description>Calling a procedure / function - ok</description> + <expected-problems>0</expected-problems> + <code><![CDATA[ +BEGIN +-- more than three parameters, each parameter on a separate line +webcrm_marketing.prospect_ins( + cmp_id_in => NULL + ,company_info_in => company_info_in + ,firstname_in => firstname_in + ,lastname_in => lastname_in + ,slt_code_in => NULL +); + +-- three or less parameters, but line too long +webcrm_utils.breakup_string( + string_in => 'test 123, test test, abadfhao , dhfj, wiezrohadf , ' + ,row_length_in => 3 + ,separator_in => ',' +); + +-- three or less parameters, line does not exceed 120 characters +webcrm_utils.breakup_string(string_in => 'test 123, test test, ab', row_length_in => 3, separator_in => ','); + +END; +/ + ]]></code> + <source-type>plsql</source-type> + </test-code> + + <test-code> + <description>Calling a procedure / function - same line</description> + <expected-problems>8</expected-problems> + <expected-linenumbers>4,4,5,5,5,5,6,6</expected-linenumbers> + <expected-messages> + <message>NULL should begin at column 57</message> + <message>Parameter company_info_in should be on line 5</message> + <message>Parameter firstname_in should be on line 6</message> + <message>Parameter lastname_in should be on line 7</message> + <message>firstname_in should begin at column 57</message> + <message>lastname_in should begin at column 57</message> + <message>NULL should begin at column 57</message> + <message>Parameter slt_code_in should be on line 8</message> + </expected-messages> + <code><![CDATA[ +BEGIN +-- more than three parameters, each parameter should be on a separate line +webcrm_marketing.prospect_ins( + cmp_id_in => NULL ,company_info_in => company_info_in + ,firstname_in => firstname_in, lastname_in => lastname_in + ,slt_code_in => NULL +); +END; +/ + ]]></code> + <source-type>plsql</source-type> + </test-code> + + <test-code> + <description>Calling a procedure / function - line too long</description> + <expected-problems>1</expected-problems> + <expected-linenumbers>3</expected-linenumbers> + <expected-messages> + <message>Line is too long, please split parameters on separate lines</message> + </expected-messages> + <code><![CDATA[ +BEGIN +-- three or less parameters, but line too long +webcrm_utils.breakup_string(string_in => 'test 123, test test, abadfhao , dhfj, wiezrohadf , ',row_length_in => 3,separator_in => ','); +END; +/ + ]]></code> + <source-type>plsql</source-type> + </test-code> + + <test-code> + <description>Calling a procedure / function - bad alignment</description> + <expected-problems>5</expected-problems> + <expected-linenumbers>4,5,6,7,8</expected-linenumbers> + <expected-messages> + <message>NULL should begin at column 23</message> + <message>company_info_in should begin at column 23</message> + <message>firstname_in should begin at column 23</message> + <message>lastname_in should begin at column 23</message> + <message>NULL should begin at column 23</message> + </expected-messages> + <code><![CDATA[ +BEGIN +-- more than three parameters, each parameter on a separate line +webcrm_marketing.prospect_ins( + cmp_id_in => NULL + ,company_info_in => company_info_in + ,firstname_in => firstname_in + ,lastname_in => lastname_in + ,slt_code_in => NULL +); +END; +/ + ]]></code> + <source-type>plsql</source-type> + </test-code> + + <test-code> + <description>Calling a procedure / function - closing paranthesis should be on a new line</description> + <expected-problems>1</expected-problems> + <expected-linenumbers>3</expected-linenumbers> + <expected-messages> + <message>Closing paranthesis should be on a new line.</message> + </expected-messages> + <code><![CDATA[ +BEGIN +-- more than three parameters, each parameter on a separate line +webcrm_marketing.prospect_ins( + cmp_id_in => NULL + ,company_info_in => company_info_in + ,firstname_in => firstname_in + ,lastname_in => lastname_in + ,slt_code_in => NULL); +END; +/ + ]]></code> + <source-type>plsql</source-type> + </test-code> + + <test-code> + <description>Calling a procedure / function without named parameters - ok</description> + <expected-problems>0</expected-problems> + <code><![CDATA[ +BEGIN +-- one parameter only, everything is on one line +webcrm_utils.boolean_to_string(TRUE); + +-- three parameters that fit on one line +webcrm_utils.breakup_string('test 123, test test, ', 3, ','); +END; +/ + ]]></code> + <source-type>plsql</source-type> + </test-code> + + <test-code> + <description>Calling a procedure / function without named parameters - not ok</description> + <expected-problems>1</expected-problems> + <expected-linenumbers>4</expected-linenumbers> + <expected-messages> + <message>Procedure call with more than three parameters should use named parameters.</message> + </expected-messages> + <code><![CDATA[ +BEGIN +-- procedure call with more than three parameters, should not appear (procedure should be called using the named notation) +webcrm_marketing.prospect_ins( + NULL + ,company_info_in + ,firstname_in + ,lastname_in + ,NULL +); +END; +/ + ]]></code> + <source-type>plsql</source-type> + </test-code> + + <test-code> + <description>Complete Coding Style example</description> + <expected-problems>0</expected-problems> + <code><![CDATA[ +PROCEDURE create_prospect( + company_info_in IN prospects.company_info%TYPE -- Organization + ,firstname_in IN persons.firstname%TYPE -- FirstName + ,lastname_in IN persons.lastname%TYPE -- LastName + ,message_in IN OUT NOCOPY CLOB -- the whole message + ) + IS + v_adt_id academic_titles.adt_id%TYPE := 1; + v_region_id addresses.rgn_id%TYPE; + v_prospect_id prospects.prosp_id%TYPE; + v_address_id addresses.adr_id%TYPE := 1; + v_message_rec snapaddy_messages%ROWTYPE; + BEGIN + /* I AM A FORBIDDEN COMMENT SINCE I AM A BLOCK COMMENT */ + + -- try to find a matching academic title. + -- this comment is on a separate line, so it is left aligned + BEGIN -- this comment is on the same line as an PL/SQL statement, it is right aligned + SELECT adt.adt_id + INTO v_adt_id + FROM academic_titles adt + WHERE UPPER(adt.short_description) = UPPER(title_in); + EXCEPTION + WHEN NO_DATA_FOUND THEN + v_adt_id := NULL; + WHEN TOO_MANY_ROWS THEN + v_adt_id := NULL; + END; + + + -- try to find a matching region + -- 1. a case insensitive lookup on the name (restricted to the given country) + BEGIN + SELECT rgn.rgn_id + INTO v_region_id + FROM regions rgn + WHERE (street_cny_code_in IS NULL + OR rgn.cny_code = street_cny_code_in) + AND UPPER(rgn.name) = UPPER(street_rgn_in); + EXCEPTION + WHEN NO_DATA_FOUND THEN + v_region_id := NULL; + WHEN TOO_MANY_ROWS THEN + v_region_id := NULL; + END; + + -- 2. a case insensitive lookup on the SAP_CODE (restricted to the given country) + IF v_region_id IS NULL THEN + BEGIN + SELECT rgn.rgn_id + INTO v_region_id + FROM regions rgn + WHERE (street_cny_code_in IS NULL + OR rgn.cny_code = street_cny_code_in) + AND UPPER(rgn.sap_code) = UPPER(street_rgn_in); + EXCEPTION + WHEN NO_DATA_FOUND THEN + v_region_id := NULL; + WHEN TOO_MANY_ROWS THEN + v_region_id := NULL; + END; + END IF; + + -- all preparations are done - create the prospect with the available data + webcrm_marketing.prospect_ins( + cmp_id_in => NULL + ,company_info_in => company_info_in + ,firstname_in => firstname_in + ,lastname_in => lastname_in + ,prosp_id_out => v_prospect_id + ); + + -- create the address with the available data + webcrm_marketing.address_ins( + pa_id_in => v_prospect_id + ,street_city_in => street_city_in + ,street_tmz_id_in => NULL + ,street_cny_code_in => street_cny_code_in + ,street_rgn_id_in => v_region_id + ,street_postalcode_in => street_postalcode_in + ,street_addrline_1_in => street_addrline_1_in + ,street_addrline_2_in => NULL + ,street_addrline_3_in => NULL + ,street_addr_id_out => v_address_id + ); + + -- store the obtained message + v_message_rec.snap_msg_id := seq_snap_msg.NEXTVAL; + v_message_rec.MESSAGE := message_in; + v_message_rec.endpoint := 'prospect/creation'; + v_message_rec.processing_time := SYSDATE; + dml_snap_msg.ins(v_message_rec); +END create_prospect; + ]]></code> + <source-type>plsql</source-type> + </test-code> +</test-data> \ No newline at end of file diff --git a/pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/rule/codestyle/xml/ForLoopNaming.xml b/pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/rule/codestyle/xml/ForLoopNaming.xml new file mode 100644 index 00000000000..5d6bdf5df10 --- /dev/null +++ b/pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/rule/codestyle/xml/ForLoopNaming.xml @@ -0,0 +1,211 @@ +<?xml version="1.0" encoding="UTF-8"?> +<test-data + xmlns="http://pmd.sourceforge.net/rule-tests" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://pmd.sourceforge.net/rule-tests http://pmd.sourceforge.net/rule-tests_1_0_0.xsd"> + + <test-code> + <description>CursorForLoop Simple case - no nesting</description> + <expected-problems>1</expected-problems> + <code><![CDATA[ +BEGIN +FOR c IN (SELECT * FROM companies) LOOP + NULL; +END LOOP; +END; +/ + ]]></code> + <source-type>plsql</source-type> + </test-code> + + <test-code> + <description>CursorForLoop Simple case - no nesting - allowSimpleLoops</description> + <rule-property name="allowSimpleLoops">true</rule-property> + <expected-problems>0</expected-problems> + <code><![CDATA[ +BEGIN +FOR c IN (SELECT * FROM companies) LOOP + NULL; +END LOOP; +END; +/ + ]]></code> + <source-type>plsql</source-type> + </test-code> + + <test-code> + <description>CursorForLoop Nested loops - good example, default pattern</description> + <expected-problems>0</expected-problems> + <code><![CDATA[ +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; +/ + ]]></code> + <source-type>plsql</source-type> + </test-code> + + <test-code> + <description>CursorForLoop Nested loops - good example, custom pattern</description> + <rule-property name="cursorPattern">c_[a-z]+</rule-property> + <expected-problems>0</expected-problems> + <code><![CDATA[ +BEGIN +FOR c_cmp IN (SELECT * FROM companies) LOOP + FOR c_con IN (SELECT * FROM contacts) LOOP + FOR c_pa IN (SELECT * FROM parties) LOOP + NULL; + END LOOP; + END LOOP; +END LOOP; +END; +/ + ]]></code> + <source-type>plsql</source-type> + </test-code> + + <test-code> + <description>CursorForLoop Nested loops - bad example, default pattern</description> + <expected-problems>3</expected-problems> + <expected-linenumbers>2,3,4</expected-linenumbers> + <code><![CDATA[ +BEGIN +FOR cmp IN (SELECT * FROM companies) LOOP + FOR con IN (SELECT * FROM contacts) LOOP + FOR pa IN (SELECT * FROM parties) LOOP + NULL; + END LOOP; + END LOOP; +END LOOP; +END; +/ + ]]></code> + <source-type>plsql</source-type> + </test-code> + + <test-code> + <description>CursorForLoop Nested loops - bad example, custom pattern</description> + <rule-property name="cursorPattern">c_[a-z]+</rule-property> + <expected-problems>3</expected-problems> + <expected-linenumbers>2,3,4</expected-linenumbers> + <code><![CDATA[ +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; +/ + ]]></code> + <source-type>plsql</source-type> + </test-code> + + <test-code> + <description>ForLoop Simple case - no nesting</description> + <expected-problems>1</expected-problems> + <expected-linenumbers>2</expected-linenumbers> + <code><![CDATA[ +BEGIN +FOR i IN 1..v_companies.COUNT LOOP + NULL; +END LOOP; +END; +/ + ]]></code> + </test-code> + + <test-code> + <description>ForLoop Simple case - no nesting - allowSimpleLoops</description> + <rule-property name="allowSimpleLoops">true</rule-property> + <expected-problems>0</expected-problems> + <code><![CDATA[ +BEGIN +FOR i IN 1..v_companies.COUNT LOOP + NULL; +END LOOP; +END; +/ + ]]></code> + </test-code> + + <test-code> + <description>ForLoop Nested loops - good example, default pattern</description> + <expected-problems>0</expected-problems> + <code><![CDATA[ +BEGIN +FOR company IN 1..v_companies.COUNT LOOP + FOR contact IN 1..v_contacts.COUNT LOOP + FOR party IN 1..v_parties.COUNT LOOP + NULL; + END LOOP; + END LOOP; +END LOOP; +END; +/ + ]]></code> + </test-code> + + <test-code> + <description>ForLoop Nested loops - good example, custom pattern</description> + <rule-property name="indexPattern">i_[a-z]+</rule-property> + <expected-problems>0</expected-problems> + <code><![CDATA[ +BEGIN +FOR i_cmp IN 1..v_companies.COUNT LOOP + FOR i_con IN 1..v_contacts.COUNT LOOP + FOR i_pa IN 1..v_parties.COUNT LOOP + NULL; + END LOOP; + END LOOP; +END LOOP; +END; +/ + ]]></code> + </test-code> + + <test-code> + <description>ForLoop Nested Loops - bad example, default pattern</description> + <expected-problems>3</expected-problems> + <expected-linenumbers>2,3,4</expected-linenumbers> + <code><![CDATA[ +BEGIN +FOR i IN 1..v_companies.COUNT LOOP + FOR j IN 1..v_contacts.COUNT LOOP + FOR k IN 1..v_parties.COUNT LOOP + NULL; + END LOOP; + END LOOP; +END LOOP; +END; +/ + ]]></code> + </test-code> + + <test-code> + <description>ForLoop Nested Loops - bad example, custom pattern</description> + <rule-property name="indexPattern">i_[a-z]+</rule-property> + <expected-problems>3</expected-problems> + <expected-linenumbers>2,3,4</expected-linenumbers> + <code><![CDATA[ +BEGIN +FOR i IN 1..v_companies.COUNT LOOP + FOR j IN 1..v_contacts.COUNT LOOP + FOR k IN 1..v_parties.COUNT LOOP + NULL; + END LOOP; + END LOOP; +END LOOP; +END; +/ + ]]></code> + </test-code> +</test-data> \ No newline at end of file diff --git a/pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/rule/strictsyntax/xml/MisplacedPragma.xml b/pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/rule/codestyle/xml/MisplacedPragma.xml similarity index 100% rename from pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/rule/strictsyntax/xml/MisplacedPragma.xml rename to pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/rule/codestyle/xml/MisplacedPragma.xml diff --git a/pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/rule/codesize/xml/CyclomaticComplexity.xml b/pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/rule/design/xml/CyclomaticComplexity.xml similarity index 100% rename from pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/rule/codesize/xml/CyclomaticComplexity.xml rename to pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/rule/design/xml/CyclomaticComplexity.xml diff --git a/pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/rule/codesize/xml/ExcessiveMethodLength.xml b/pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/rule/design/xml/ExcessiveMethodLength.xml similarity index 100% rename from pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/rule/codesize/xml/ExcessiveMethodLength.xml rename to pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/rule/design/xml/ExcessiveMethodLength.xml diff --git a/pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/rule/codesize/xml/ExcessiveObjectLength.xml b/pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/rule/design/xml/ExcessiveObjectLength.xml similarity index 100% rename from pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/rule/codesize/xml/ExcessiveObjectLength.xml rename to pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/rule/design/xml/ExcessiveObjectLength.xml diff --git a/pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/rule/codesize/xml/ExcessivePackageBodyLength.xml b/pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/rule/design/xml/ExcessivePackageBodyLength.xml similarity index 100% rename from pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/rule/codesize/xml/ExcessivePackageBodyLength.xml rename to pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/rule/design/xml/ExcessivePackageBodyLength.xml diff --git a/pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/rule/codesize/xml/ExcessivePackageSpecificationLength.xml b/pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/rule/design/xml/ExcessivePackageSpecificationLength.xml similarity index 100% rename from pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/rule/codesize/xml/ExcessivePackageSpecificationLength.xml rename to pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/rule/design/xml/ExcessivePackageSpecificationLength.xml diff --git a/pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/rule/codesize/xml/ExcessiveParameterList.xml b/pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/rule/design/xml/ExcessiveParameterList.xml similarity index 100% rename from pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/rule/codesize/xml/ExcessiveParameterList.xml rename to pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/rule/design/xml/ExcessiveParameterList.xml diff --git a/pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/rule/codesize/xml/ExcessiveTypeLength.xml b/pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/rule/design/xml/ExcessiveTypeLength.xml similarity index 100% rename from pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/rule/codesize/xml/ExcessiveTypeLength.xml rename to pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/rule/design/xml/ExcessiveTypeLength.xml diff --git a/pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/rule/codesize/xml/NPathComplexity.xml b/pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/rule/design/xml/NPathComplexity.xml similarity index 100% rename from pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/rule/codesize/xml/NPathComplexity.xml rename to pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/rule/design/xml/NPathComplexity.xml diff --git a/pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/rule/codesize/xml/NcssMethodCount.xml b/pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/rule/design/xml/NcssMethodCount.xml similarity index 100% rename from pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/rule/codesize/xml/NcssMethodCount.xml rename to pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/rule/design/xml/NcssMethodCount.xml diff --git a/pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/rule/codesize/xml/NcssObjectCount.xml b/pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/rule/design/xml/NcssObjectCount.xml similarity index 100% rename from pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/rule/codesize/xml/NcssObjectCount.xml rename to pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/rule/design/xml/NcssObjectCount.xml diff --git a/pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/rule/codesize/xml/TooManyFields.xml b/pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/rule/design/xml/TooManyFields.xml similarity index 100% rename from pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/rule/codesize/xml/TooManyFields.xml rename to pmd-plsql/src/test/resources/net/sourceforge/pmd/lang/plsql/rule/design/xml/TooManyFields.xml diff --git a/pmd-python/etc/grammar/python.jj b/pmd-python/etc/grammar/python.jj index e69d5886bd1..60f723870c3 100644 --- a/pmd-python/etc/grammar/python.jj +++ b/pmd-python/etc/grammar/python.jj @@ -33,7 +33,11 @@ SKIP : | "\014" | <CONTINUATION: ("\\") ("\r\n"|"\n"|"\r")> | <NEWLINE: ("\r\n"|"\n"|"\r")> -| <TRAILING_COMMENT: "#" (~["\n","\r"])* > +} + +SPECIAL_TOKEN : +{ + <TRAILING_COMMENT: "#" (~["\n","\r"])* > } TOKEN : /* SEPARATORS */ diff --git a/pmd-python/pom.xml b/pmd-python/pom.xml index d46b2b0d697..2dde0dcd33d 100644 --- a/pmd-python/pom.xml +++ b/pmd-python/pom.xml @@ -7,13 +7,9 @@ <parent> <groupId>net.sourceforge.pmd</groupId> <artifactId>pmd</artifactId> - <version>6.0.0-SNAPSHOT</version> + <version>6.10.0-SNAPSHOT</version> </parent> - <properties> - <config.basedir>${basedir}/../pmd-core</config.basedir> - </properties> - <build> <plugins> <plugin> @@ -73,6 +69,10 @@ <groupId>net.sourceforge.pmd</groupId> <artifactId>pmd-core</artifactId> </dependency> + <dependency> + <groupId>commons-io</groupId> + <artifactId>commons-io</artifactId> + </dependency> <dependency> <groupId>junit</groupId> diff --git a/pmd-python/src/main/ant/alljavacc.xml b/pmd-python/src/main/ant/alljavacc.xml index 06b4bfddcc8..b8766172634 100644 --- a/pmd-python/src/main/ant/alljavacc.xml +++ b/pmd-python/src/main/ant/alljavacc.xml @@ -40,6 +40,56 @@ <delete file="${target}/net/sourceforge/pmd/lang/python/ast/CharStream.java" /> <delete file="${target}/net/sourceforge/pmd/lang/python/ast/ParseException.java" /> <delete file="${target}/net/sourceforge/pmd/lang/python/ast/TokenMgrError.java" /> + + <replace file="${target}/net/sourceforge/pmd/lang/python/ast/Token.java"> + <replacetoken>public class Token implements java.io.Serializable</replacetoken> + <replacevalue><![CDATA[import net.sourceforge.pmd.lang.ast.GenericToken; + +public class Token implements GenericToken, java.io.Serializable]]></replacevalue> + </replace> + + <!--Add implementation methods of GenericToken--> + <replace file="${target}/net/sourceforge/pmd/lang/python/ast/Token.java"> + <replacetoken>public Token specialToken;</replacetoken> + <replacevalue><![CDATA[public Token specialToken; + + @Override + public GenericToken getNext() { + return next; + } + + @Override + public GenericToken getPreviousComment() { + return specialToken; + } + + @Override + public String getImage() { + return image; + } + + @Override + public int getBeginLine() { + return beginLine; + } + + @Override + public int getEndLine() { + return endLine; + } + + @Override + public int getBeginColumn() { + return beginColumn; + } + + @Override + public int getEndColumn() { + return endColumn; + } + +]]></replacevalue> + </replace> </target> </project> diff --git a/pmd-python/src/main/java/net/sourceforge/pmd/cpd/PythonTokenizer.java b/pmd-python/src/main/java/net/sourceforge/pmd/cpd/PythonTokenizer.java index 9ab7965b0b4..9996d619856 100644 --- a/pmd-python/src/main/java/net/sourceforge/pmd/cpd/PythonTokenizer.java +++ b/pmd-python/src/main/java/net/sourceforge/pmd/cpd/PythonTokenizer.java @@ -4,14 +4,14 @@ package net.sourceforge.pmd.cpd; +import java.io.IOException; import java.io.Reader; import java.io.StringReader; -import org.apache.commons.io.IOUtils; - +import net.sourceforge.pmd.cpd.token.JavaCCTokenFilter; +import net.sourceforge.pmd.cpd.token.TokenFilter; import net.sourceforge.pmd.lang.LanguageRegistry; import net.sourceforge.pmd.lang.LanguageVersionHandler; -import net.sourceforge.pmd.lang.TokenManager; import net.sourceforge.pmd.lang.ast.TokenMgrError; import net.sourceforge.pmd.lang.python.PythonLanguageModule; import net.sourceforge.pmd.lang.python.ast.Token; @@ -25,28 +25,23 @@ public class PythonTokenizer implements Tokenizer { @Override public void tokenize(SourceCode sourceCode, Tokens tokenEntries) { StringBuilder buffer = sourceCode.getCodeBuffer(); - Reader reader = null; - try { + try (Reader reader = IOUtil.skipBOM(new StringReader(buffer.toString()))) { LanguageVersionHandler languageVersionHandler = LanguageRegistry.getLanguage(PythonLanguageModule.NAME) .getDefaultVersion().getLanguageVersionHandler(); - reader = new StringReader(buffer.toString()); - reader = IOUtil.skipBOM(reader); - TokenManager tokenManager = languageVersionHandler + TokenFilter tokenFilter = new JavaCCTokenFilter(languageVersionHandler .getParser(languageVersionHandler.getDefaultParserOptions()) - .getTokenManager(sourceCode.getFileName(), reader); - Token currentToken = (Token) tokenManager.getNextToken(); - while (currentToken.image.length() > 0) { + .getTokenManager(sourceCode.getFileName(), reader)); + Token currentToken = (Token) tokenFilter.getNextToken(); + while (currentToken != null) { tokenEntries.add(new TokenEntry(currentToken.image, sourceCode.getFileName(), currentToken.beginLine)); - currentToken = (Token) tokenManager.getNextToken(); + currentToken = (Token) tokenFilter.getNextToken(); } tokenEntries.add(TokenEntry.getEOF()); System.err.println("Added " + sourceCode); - } catch (TokenMgrError err) { + } catch (TokenMgrError | IOException err) { err.printStackTrace(); System.err.println("Skipping " + sourceCode + " due to parse error"); tokenEntries.add(TokenEntry.getEOF()); - } finally { - IOUtils.closeQuietly(reader); } } } diff --git a/pmd-python/src/main/java/net/sourceforge/pmd/lang/python/PythonTokenManager.java b/pmd-python/src/main/java/net/sourceforge/pmd/lang/python/PythonTokenManager.java index 168609a5da2..30a3aa72fca 100644 --- a/pmd-python/src/main/java/net/sourceforge/pmd/lang/python/PythonTokenManager.java +++ b/pmd-python/src/main/java/net/sourceforge/pmd/lang/python/PythonTokenManager.java @@ -18,7 +18,7 @@ public class PythonTokenManager implements TokenManager { /** * Creates a new Python Token Manager from the given source code. - * + * * @param source * the source code */ @@ -26,6 +26,7 @@ public PythonTokenManager(Reader source) { tokenManager = new PythonParserTokenManager(new SimpleCharStream(source)); } + @Override public Object getNextToken() { return tokenManager.getNextToken(); } diff --git a/pmd-python/src/test/java/net/sourceforge/pmd/cpd/PythonTokenizerTest.java b/pmd-python/src/test/java/net/sourceforge/pmd/cpd/PythonTokenizerTest.java index 16798c6e640..58d771f7eb8 100644 --- a/pmd-python/src/test/java/net/sourceforge/pmd/cpd/PythonTokenizerTest.java +++ b/pmd-python/src/test/java/net/sourceforge/pmd/cpd/PythonTokenizerTest.java @@ -4,12 +4,16 @@ package net.sourceforge.pmd.cpd; +import static org.junit.Assert.assertEquals; + import java.io.IOException; +import java.nio.charset.StandardCharsets; import org.apache.commons.io.IOUtils; import org.junit.Before; import org.junit.Test; +import net.sourceforge.pmd.PMD; import net.sourceforge.pmd.testframework.AbstractTokenizerTest; public class PythonTokenizerTest extends AbstractTokenizerTest { @@ -25,7 +29,7 @@ public void buildTokenizer() throws IOException { @Override public String getSampleCode() throws IOException { - return IOUtils.toString(PythonTokenizer.class.getResourceAsStream(FILENAME)); + return IOUtils.toString(PythonTokenizer.class.getResourceAsStream(FILENAME), StandardCharsets.UTF_8); } @Test @@ -33,4 +37,20 @@ public void tokenizeTest() throws IOException { this.expectedTokenCount = 1218; super.tokenizeTest(); } + + @Test + public void testIgnoreBetweenSpecialComments() throws IOException { + SourceCode sourceCode = new SourceCode(new SourceCode.StringCodeLoader("import logging" + PMD.EOL + + "# CPD-OFF" + PMD.EOL + + "logger = logging.getLogger('django.request')" + PMD.EOL + + "class BaseHandler(object):" + PMD.EOL + + " def __init__(self):" + PMD.EOL + + " self._request_middleware = None" + PMD.EOL + + " # CPD-ON" + PMD.EOL + )); + Tokens tokens = new Tokens(); + tokenizer.tokenize(sourceCode, tokens); + TokenEntry.getEOF(); + assertEquals(3, tokens.size()); // 3 tokens: "import" + "logging" + EOF + } } diff --git a/pmd-ruby/pom.xml b/pmd-ruby/pom.xml index fc1476fd79d..9e69c0f3606 100644 --- a/pmd-ruby/pom.xml +++ b/pmd-ruby/pom.xml @@ -7,19 +7,24 @@ <parent> <groupId>net.sourceforge.pmd</groupId> <artifactId>pmd</artifactId> - <version>6.0.0-SNAPSHOT</version> + <version>6.10.0-SNAPSHOT</version> </parent> - <properties> - <config.basedir>${basedir}/../pmd-core</config.basedir> - </properties> - <dependencies> <dependency> <groupId>net.sourceforge.pmd</groupId> <artifactId>pmd-core</artifactId> </dependency> + <dependency> + <groupId>commons-io</groupId> + <artifactId>commons-io</artifactId> + </dependency> + <dependency> + <groupId>junit</groupId> + <artifactId>junit</artifactId> + <scope>test</scope> + </dependency> <dependency> <groupId>net.sourceforge.pmd</groupId> <artifactId>pmd-test</artifactId> diff --git a/pmd-scala/pom.xml b/pmd-scala/pom.xml index 4b572f979e7..12e72dacf93 100644 --- a/pmd-scala/pom.xml +++ b/pmd-scala/pom.xml @@ -7,12 +7,11 @@ <parent> <groupId>net.sourceforge.pmd</groupId> <artifactId>pmd</artifactId> - <version>6.0.0-SNAPSHOT</version> + <version>6.10.0-SNAPSHOT</version> </parent> <properties> - <config.basedir>${basedir}/../pmd-core</config.basedir> - <scala.version>2.10.4</scala.version> + <scala.version>2.12.4</scala.version> </properties> <build> @@ -21,7 +20,7 @@ <plugin> <groupId>net.alchim31.maven</groupId> <artifactId>scala-maven-plugin</artifactId> - <version>3.2.0</version> + <version>3.3.1</version> </plugin> </plugins> </pluginManagement> @@ -41,9 +40,8 @@ <groupId>net.alchim31.maven</groupId> <artifactId>scala-maven-plugin</artifactId> <configuration> - <jvmArgs> - <jvmArg>-Dscalac.patmat.analysisBudget=off</jvmArg> - </jvmArgs> + <addScalacArgs>-deprecation</addScalacArgs> + <scalaVersion>${scala.version}</scalaVersion> </configuration> <executions> <execution> @@ -54,6 +52,12 @@ <goal>compile</goal> </goals> </execution> + <execution> + <id>attach-javadocs</id> + <goals> + <goal>doc-jar</goal> + </goals> + </execution> </executions> </plugin> @@ -64,6 +68,21 @@ <suppressionsLocation>pmd-scala-checkstyle-suppressions.xml</suppressionsLocation> </configuration> </plugin> + + <!-- Disabling the default javadoc plugin - we use scala-maven-plugin instead --> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-javadoc-plugin</artifactId> + <executions> + <execution> + <id>attach-javadocs</id> + <phase>none</phase> + <goals> + <goal>jar</goal> + </goals> + </execution> + </executions> + </plugin> </plugins> </build> diff --git a/pmd-scala/src/main/java/org/sonar/plugins/scala/cpd/ScalaTokenizer.java b/pmd-scala/src/main/java/org/sonar/plugins/scala/cpd/ScalaTokenizer.java index 3ff303f1953..ac9b1591770 100644 --- a/pmd-scala/src/main/java/org/sonar/plugins/scala/cpd/ScalaTokenizer.java +++ b/pmd-scala/src/main/java/org/sonar/plugins/scala/cpd/ScalaTokenizer.java @@ -38,6 +38,7 @@ */ public final class ScalaTokenizer implements Tokenizer { + @Override public void tokenize(SourceCode source, Tokens cpdTokens) { String filename = source.getFileName(); diff --git a/pmd-scala/src/main/java/org/sonar/plugins/scala/util/StringUtils.java b/pmd-scala/src/main/java/org/sonar/plugins/scala/util/StringUtils.java index 8027437555e..0000321a6e4 100644 --- a/pmd-scala/src/main/java/org/sonar/plugins/scala/util/StringUtils.java +++ b/pmd-scala/src/main/java/org/sonar/plugins/scala/util/StringUtils.java @@ -26,8 +26,6 @@ import java.util.ArrayList; import java.util.List; -import org.apache.commons.io.IOUtils; - public final class StringUtils { private StringUtils() { // to prevent instantiation @@ -35,15 +33,11 @@ private StringUtils() { public static List<String> convertStringToListOfLines(String string) throws IOException { final List<String> lines = new ArrayList<>(); - BufferedReader reader = null; - try { - reader = new BufferedReader(new StringReader(string)); + try (BufferedReader reader = new BufferedReader(new StringReader(string))) { String line = null; while ((line = reader.readLine()) != null) { lines.add(line); } - } finally { - IOUtils.closeQuietly(reader); } return lines; } diff --git a/pmd-scala/src/main/scala/org/sonar/plugins/scala/compiler/Lexer.scala b/pmd-scala/src/main/scala/org/sonar/plugins/scala/compiler/Lexer.scala index e6a58f1b183..2ff98040ae0 100644 --- a/pmd-scala/src/main/scala/org/sonar/plugins/scala/compiler/Lexer.scala +++ b/pmd-scala/src/main/scala/org/sonar/plugins/scala/compiler/Lexer.scala @@ -19,7 +19,7 @@ */ package org.sonar.plugins.scala.compiler -import collection.mutable.ListBuffer +import scala.collection.mutable.Buffer import org.sonar.plugins.scala.language.{Comment, CommentType} import scala.reflect.io.AbstractFile @@ -34,7 +34,7 @@ import scala.reflect.internal.util.BatchSourceFile */ class Lexer { - import scala.collection.JavaConversions._ + import scala.collection.JavaConverters._ import Compiler._ def getTokens(code: String): java.util.List[Token] = { @@ -49,7 +49,7 @@ class Lexer { private def tokenize(unit: CompilationUnit): java.util.List[Token] = { val scanner = new syntaxAnalyzer.UnitScanner(unit) - val tokens = ListBuffer[Token]() + val tokens = Buffer[Token]() scanner.init() while (scanner.token != scala.tools.nsc.ast.parser.Tokens.EOF) { @@ -60,69 +60,7 @@ class Lexer { tokens += Token(scanner.token, linenr, tokenVal) scanner.nextToken() } - tokens + tokens.asJava } - def getComments(code: String): java.util.List[Comment] = { - val unit = new CompilationUnit(new BatchSourceFile("", code.toCharArray)) - tokenizeComments(unit) - } - - def getCommentsOfFile(path: String): java.util.List[Comment] = { - val unit = new CompilationUnit(new BatchSourceFile(AbstractFile.getFile(path))) - tokenizeComments(unit) - } - - private def tokenizeComments(unit: CompilationUnit): java.util.List[Comment] = { - val comments = ListBuffer[Comment]() - val scanner = new syntaxAnalyzer.UnitScanner(unit) { - - private var lastDocCommentRange: Option[Range] = None - - private var foundToken = false - - override def nextToken() { - super.nextToken() - foundToken = token != 0 - } - - override def foundComment(value: String, start: Int, end: Int) = { - super.foundComment(value, start, end) - - def isHeaderComment(value: String) = { - !foundToken && comments.isEmpty && value.trim().startsWith("/*") - } - - lastDocCommentRange match { - - case Some(r: Range) => { - if (r.start != start || r.end != end) { - comments += new Comment(value, CommentType.NORMAL) - } - } - - case None => { - if (isHeaderComment(value)) { - comments += new Comment(value, CommentType.HEADER) - } else { - comments += new Comment(value, CommentType.NORMAL) - } - } - } - } - - override def foundDocComment(value: String, start: Int, end: Int) = { - super.foundDocComment(value, start, end) - comments += new Comment(value, CommentType.DOC) - lastDocCommentRange = Some(Range(start, end)) - } - } - - scanner.init() - while (scanner.token != scala.tools.nsc.ast.parser.Tokens.EOF) { - scanner.nextToken() - } - - comments - } } \ No newline at end of file diff --git a/pmd-scala/src/test/java/net/sourceforge/pmd/cpd/ScalaTokenizerTest.java b/pmd-scala/src/test/java/net/sourceforge/pmd/cpd/ScalaTokenizerTest.java index ecc48301433..c2df7ca994a 100644 --- a/pmd-scala/src/test/java/net/sourceforge/pmd/cpd/ScalaTokenizerTest.java +++ b/pmd-scala/src/test/java/net/sourceforge/pmd/cpd/ScalaTokenizerTest.java @@ -6,6 +6,8 @@ import java.io.File; import java.io.IOException; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; import org.apache.commons.io.FileUtils; import org.apache.commons.io.IOUtils; @@ -18,7 +20,7 @@ public class ScalaTokenizerTest extends AbstractTokenizerTest { - private static final String ENCODING = "UTF-8"; + private static final Charset ENCODING = StandardCharsets.UTF_8; private static final String FILENAME = "sample-LiftActor.scala"; diff --git a/pmd-swift/pom.xml b/pmd-swift/pom.xml index adb286f2e03..a338179d32c 100644 --- a/pmd-swift/pom.xml +++ b/pmd-swift/pom.xml @@ -7,13 +7,9 @@ <parent> <groupId>net.sourceforge.pmd</groupId> <artifactId>pmd</artifactId> - <version>6.0.0-SNAPSHOT</version> + <version>6.10.0-SNAPSHOT</version> </parent> - <properties> - <config.basedir>${basedir}/../pmd-core</config.basedir> - </properties> - <build> <plugins> <plugin> @@ -38,11 +34,14 @@ <groupId>org.antlr</groupId> <artifactId>antlr4-runtime</artifactId> </dependency> - <dependency> <groupId>net.sourceforge.pmd</groupId> <artifactId>pmd-core</artifactId> </dependency> + <dependency> + <groupId>commons-io</groupId> + <artifactId>commons-io</artifactId> + </dependency> <dependency> <groupId>junit</groupId> diff --git a/pmd-swift/src/main/antlr4/net/sourceforge/pmd/lang/swift/antlr4/Swift.g4 b/pmd-swift/src/main/antlr4/net/sourceforge/pmd/lang/swift/antlr4/Swift.g4 index 3a8fef8af55..0eb9f7517ec 100644 --- a/pmd-swift/src/main/antlr4/net/sourceforge/pmd/lang/swift/antlr4/Swift.g4 +++ b/pmd-swift/src/main/antlr4/net/sourceforge/pmd/lang/swift/antlr4/Swift.g4 @@ -33,7 +33,13 @@ */ grammar Swift; -topLevel : (statement | expression)* EOF ; +/* +@header { +package com.sleekbyte.tailor.antlr; +} +*/ + +topLevel : statements? EOF ; // Statements @@ -55,33 +61,22 @@ statements : statement+ ; // GRAMMAR OF A LOOP STATEMENT -loopStatement : forStatement - | forInStatement +loopStatement : forInStatement | whileStatement | repeatWhileStatement ; -// GRAMMAR OF A FOR STATEMENT - -// Swift Language Reference has expression? instead of expressionList? -forStatement - : 'for' forInit? ';' expression? ';' expressionList? codeBlock - | 'for' '(' forInit?';' expression? ';' expressionList? ')' codeBlock - ; - -forInit : variableDeclaration | expressionList ; - // GRAMMAR OF A FOR_IN STATEMENT forInStatement : 'for' 'case'? pattern 'in' expression whereClause? codeBlock ; // GRAMMAR OF A WHILE STATEMENT -whileStatement : 'while' conditionClause codeBlock ; +whileStatement : 'while' conditionList codeBlock ; // GRAMMAR OF A REPEAT WHILE STATEMENT -repeatWhileStatement: 'repeat' codeBlock 'while' conditionClause ; +repeatWhileStatement: 'repeat' codeBlock 'while' expression ; // GRAMMAR OF A BRANCH STATEMENT @@ -89,17 +84,17 @@ branchStatement : ifStatement | guardStatement | switchStatement ; // GRAMMAR OF AN IF STATEMENT -ifStatement : 'if' conditionClause codeBlock elseClause? ; +ifStatement : 'if' conditionList codeBlock elseClause? ; elseClause : 'else' codeBlock | 'else' ifStatement ; // GRAMMAR OF A GUARD STATEMENT -guardStatement : 'guard' conditionClause 'else' codeBlock ; +guardStatement : 'guard' conditionList 'else' codeBlock ; // GRAMMAR OF A SWITCH STATEMENT switchStatement : 'switch' expression '{' switchCases? '}' ; -switchCases : switchCase switchCases? ; +switchCases : switchCase+ ; switchCase : caseLabel statements | defaultLabel statements | caseLabel ';' | defaultLabel ';' ; caseLabel : 'case' caseItemList ':' ; caseItemList : caseItem (',' caseItem)* ; @@ -108,7 +103,7 @@ defaultLabel : 'default' ':' ; // GRAMMAR OF A LABELED STATEMENT -labeledStatement : statementLabel loopStatement | statementLabel switchStatement ; +labeledStatement : statementLabel (loopStatement | ifStatement | switchStatement | doStatement) ; statementLabel : labelName ':' ; labelName : identifier ; @@ -151,22 +146,10 @@ doStatement: 'do' codeBlock catchClauses? ; catchClauses: catchClause catchClauses? ; catchClause: 'catch' pattern? whereClause? codeBlock ; -// GRAMMAR FOR CONDITION CLAUSES - -conditionClause : expression - | expression ',' conditionList - | conditionList - | availabilityCondition ',' expression - ; - conditionList : condition (',' condition)* ; -condition: availabilityCondition | caseCondition | optionalBindingCondition ; -caseCondition: 'case' pattern initializer whereClause? ; -// optionalBindingCondition is incorrect in the Swift Language Reference (missing a ',') -optionalBindingCondition: optionalBindingHead (',' optionalBindingContinuationList)? whereClause? ; -optionalBindingHead: 'let' pattern initializer | 'var' pattern initializer ; -optionalBindingContinuationList: optionalBindingContinuation (',' optionalBindingContinuation)* ; -optionalBindingContinuation: optionalBindingHead | pattern initializer ; +condition: availabilityCondition | caseCondition | optionalBindingCondition | expression ; +caseCondition: 'case' pattern initializer ; +optionalBindingCondition: ('let'|'var') pattern initializer ; whereClause: 'where' whereExpression ; whereExpression: expression ; @@ -184,10 +167,10 @@ platformVersion: VersionLiteral | DecimalLiteral | FloatingPointLiteral ; // TOD // GRAMMAR OF A GENERIC PARAMETER CLAUSE -genericParameterClause : '<' genericParameterList requirementClause? '>' ; +genericParameterClause : '<' genericParameterList '>' ; genericParameterList : genericParameter (',' genericParameter)* ; genericParameter : typeName | typeName ':' typeIdentifier | typeName ':' protocolCompositionType ; -requirementClause : 'where' requirementList ; +genericWhereClause : 'where' requirementList ; requirementList : requirement (',' requirement)* ; requirement : conformanceRequirement | sameTypeRequirement ; conformanceRequirement : typeIdentifier ':' typeIdentifier | typeIdentifier ':' protocolCompositionType ; @@ -220,20 +203,26 @@ declaration | operatorDeclaration ';'? // compiler-control-statement not in Swift Language Reference | compilerControlStatement ';'? + | precedenceGroupDeclaration ';'? ; -declarations : declaration declarations? ; -declarationModifiers : declarationModifier declarationModifiers? ; +declarations : declaration+ ; +declarationModifiers : declarationModifier+ ; declarationModifier : 'class' | 'convenience' | 'dynamic' | 'final' | 'infix' - | 'lazy' | 'mutating' | 'nonmutating' | 'optional' | 'override' | 'postfix' + | 'lazy' | 'optional' | 'override' | 'postfix' | 'prefix' | 'required' | 'static' | 'unowned' | 'unowned' '(' 'safe' ')' | 'unowned' '(' 'unsafe' ')' | 'weak' - | accessLevelModifier ; + | accessLevelModifier + | mutationModifier ; -accessLevelModifier : 'internal' | 'internal' '(' 'set' ')' - | 'private' | 'private' '(' 'set' ')' - | 'public' | 'public' '(' 'set' ')' ; -accessLevelModifiers : accessLevelModifier accessLevelModifiers? ; +accessLevelModifier : 'private' | 'private' '(' 'set' ')' + | 'fileprivate' | 'fileprivate' '(' 'set' ')' + | 'internal' | 'internal' '(' 'set' ')' + | 'public' | 'public' '(' 'set' ')' + | 'open' | 'open' '(' 'set' ')' ; +accessLevelModifiers : accessLevelModifier+ ; + +mutationModifier: 'mutating' | 'nonmutating' ; // GRAMMAR OF A CODE BLOCK @@ -242,7 +231,8 @@ codeBlock : '{' statements? '}' ; // GRAMMAR OF AN IMPORT DECLARATION importDeclaration : attributes? 'import' importKind? importPath ; -importKind : 'typealias' | 'struct' | 'class' | 'enum' | 'protocol' | 'var' | 'func' ; +// Swift Language Reference does not have let +importKind : 'typealias' | 'struct' | 'class' | 'enum' | 'protocol' | 'var' | 'func' | 'let' ; importPath : importPathIdentifier | importPathIdentifier '.' importPath ; importPathIdentifier : identifier | operator ; @@ -268,8 +258,10 @@ variableDeclaration variableDeclarationHead : attributes? declarationModifiers? 'var' ; variableName : identifier ; getterSetterBlock : '{' getterClause setterClause?'}' | '{' setterClause getterClause '}' ; -getterClause : attributes? 'get' codeBlock ; -setterClause : attributes? 'set' setterName? codeBlock ; +// declarationModifiers missing in the Swift Language Reference +getterClause : attributes? declarationModifiers? 'get' codeBlock ; +// declarationModifiers missing in the Swift Language Reference +setterClause : attributes? declarationModifiers? 'set' setterName? codeBlock ; setterName : '(' identifier ')' ; getterSetterKeywordBlock : '{' getterKeywordClause setterKeywordClause?'}' | '{' setterKeywordClause getterKeywordClause '}' ; getterKeywordClause : attributes? 'get' ; @@ -281,7 +273,7 @@ didSetClause : attributes? 'didSet' setterName? codeBlock ; // GRAMMAR OF A TYPE ALIAS DECLARATION typealiasDeclaration : typealiasHead typealiasAssignment ; -typealiasHead : attributes? accessLevelModifier? 'typealias' typealiasName ; +typealiasHead : attributes? accessLevelModifier? 'typealias' typealiasName genericParameterClause? ; typealiasName : identifier ; typealiasAssignment : '=' sType ; @@ -290,45 +282,44 @@ typealiasAssignment : '=' sType ; /* HACK: functionBody? is intentionally not used to force the parser to try and match a functionBody first * This can be removed once we figure out how to enforce that statements are either separated by semi colons or new line characters */ -functionDeclaration : functionHead functionName genericParameterClause? functionSignature functionBody - | functionHead functionName genericParameterClause? functionSignature +functionDeclaration : functionHead functionName genericParameterClause? functionSignature genericWhereClause? functionBody + | functionHead functionName genericParameterClause? functionSignature genericWhereClause? ; functionHead : attributes? declarationModifiers? 'func' ; functionName : identifier | operator ; // rethrows is not marked as optional in the Swift Language Reference -functionSignature : parameterClauses ('throws' | 'rethrows')? functionResult? ; +functionSignature : parameterClause ('throws' | 'rethrows')? functionResult? ; functionResult : '->' attributes? sType ; functionBody : codeBlock ; -parameterClauses : parameterClause parameterClauses? ; parameterClause : '(' ')' | '(' parameterList '...'? ')' ; parameterList : parameter (',' parameter)* ; // Parameters don't have attributes in the Swift Language Reference -parameter : attributes? 'inout'? 'let'? '#'? externalParameterName? localParameterName typeAnnotation? defaultArgumentClause? - | 'inout'? 'var' '#'? externalParameterName? localParameterName typeAnnotation? defaultArgumentClause? - | attributes? sType - | externalParameterName? localParameterName typeAnnotation '...' +parameter + : attributes? externalParameterName? localParameterName typeAnnotation defaultArgumentClause? + | attributes? externalParameterName? localParameterName typeAnnotation '...' ; -externalParameterName : identifier | '_' ; -localParameterName : identifier | '_' ; +// Swift Language Reference does not have "keyword" or "_" +externalParameterName : identifier | keyword | '_'; +localParameterName : identifier | '_' ; defaultArgumentClause : '=' expression ; // GRAMMAR OF AN ENUMERATION DECLARATION enumDeclaration : attributes? accessLevelModifier? enumDef ; enumDef: unionStyleEnum | rawValueStyleEnum ; -unionStyleEnum : 'indirect'? 'enum' enumName genericParameterClause? typeInheritanceClause? '{' unionStyleEnumMembers?'}' ; -unionStyleEnumMembers : unionStyleEnumMember unionStyleEnumMembers? ; -unionStyleEnumMember : declaration | unionStyleEnumCaseClause ';'? ; +unionStyleEnum : 'indirect'? 'enum' enumName genericParameterClause? typeInheritanceClause? genericWhereClause? '{' unionStyleEnumMembers?'}' ; +unionStyleEnumMembers : unionStyleEnumMember+ ; +unionStyleEnumMember : declaration | unionStyleEnumCaseClause ';'? | compilerControlStatement ; unionStyleEnumCaseClause : attributes? 'indirect'? 'case' unionStyleEnumCaseList ; unionStyleEnumCaseList : unionStyleEnumCase (',' unionStyleEnumCase)* ; unionStyleEnumCase : enumCaseName tupleType? ; enumName : identifier ; enumCaseName : identifier ; // typeInheritanceClause is not optional in the Swift Language Reference -rawValueStyleEnum : 'enum' enumName genericParameterClause? typeInheritanceClause? '{' rawValueStyleEnumMembers?'}' ; -rawValueStyleEnumMembers : rawValueStyleEnumMember rawValueStyleEnumMembers? ; -rawValueStyleEnumMember : declaration | rawValueStyleEnumCaseClause ; +rawValueStyleEnum : 'enum' enumName genericParameterClause? typeInheritanceClause? genericWhereClause? '{' rawValueStyleEnumMembers?'}' ; +rawValueStyleEnumMembers : rawValueStyleEnumMember+ ; +rawValueStyleEnumMember : declaration | rawValueStyleEnumCaseClause | compilerControlStatement ; rawValueStyleEnumCaseClause : attributes? 'case' rawValueStyleEnumCaseList ; rawValueStyleEnumCaseList : rawValueStyleEnumCase (',' rawValueStyleEnumCase)* ; rawValueStyleEnumCase : enumCaseName rawValueAssignment? ; @@ -336,29 +327,34 @@ rawValueAssignment : '=' literal ; // GRAMMAR OF A STRUCTURE DECLARATION -structDeclaration : attributes? accessLevelModifier? 'struct' structName genericParameterClause? typeInheritanceClause? structBody ; +structDeclaration : attributes? accessLevelModifier? 'struct' structName genericParameterClause? typeInheritanceClause? genericWhereClause? structBody ; structName : identifier ; -structBody : '{' declarations?'}' ; +structBody : '{' structMembers? '}' ; +structMembers: structMember+ ; +structMember: declaration | compilerControlStatement ; // GRAMMAR OF A CLASS DECLARATION -// declarationModifier missing in Swift Language Reference -classDeclaration : attributes? declarationModifier* 'class' className genericParameterClause? typeInheritanceClause? classBody ; +classDeclaration : attributes? classDeclarationModifiers? 'class' className genericParameterClause? typeInheritanceClause? genericWhereClause? classBody ; +classDeclarationModifiers: accessLevelModifier 'final'? | 'final' accessLevelModifier? ; className : identifier ; -classBody : '{' declarations? '}' ; +classBody : '{' classMembers? '}' ; +classMembers: classMember+ ; +classMember: declaration | compilerControlStatement ; // GRAMMAR OF A PROTOCOL DECLARATION protocolDeclaration : attributes? accessLevelModifier? 'protocol' protocolName typeInheritanceClause? protocolBody ; protocolName : identifier ; -protocolBody : '{' protocolMemberDeclarations?'}' ; +protocolBody : '{' protocolMembers? '}' ; +protocolMembers: protocolMember+ ; +protocolMember: protocolMemberDeclaration | compilerControlStatement ; protocolMemberDeclaration : protocolPropertyDeclaration ';'? | protocolMethodDeclaration ';'? | protocolInitializerDeclaration ';'? | protocolSubscriptDeclaration ';'? | protocolAssociatedTypeDeclaration ';'? ; -protocolMemberDeclarations : protocolMemberDeclaration protocolMemberDeclarations? ; // GRAMMAR OF A PROTOCOL PROPERTY DECLARATION @@ -366,11 +362,11 @@ protocolPropertyDeclaration : variableDeclarationHead variableName typeAnnotatio // GRAMMAR OF A PROTOCOL METHOD DECLARATION -protocolMethodDeclaration : functionHead functionName genericParameterClause? functionSignature ; +protocolMethodDeclaration : functionHead functionName genericParameterClause? functionSignature genericWhereClause? ; // GRAMMAR OF A PROTOCOL INITIALIZER DECLARATION -protocolInitializerDeclaration : initializerHead genericParameterClause? parameterClause ('throws' | 'rethrows')? ; +protocolInitializerDeclaration : initializerHead genericParameterClause? parameterClause ('throws' | 'rethrows')? genericWhereClause? ; // GRAMMAR OF A PROTOCOL SUBSCRIPT DECLARATION @@ -378,11 +374,11 @@ protocolSubscriptDeclaration : subscriptHead subscriptResult getterSetterKeyword // GRAMMAR OF A PROTOCOL ASSOCIATED TYPE DECLARATION -protocolAssociatedTypeDeclaration : typealiasHead typeInheritanceClause? typealiasAssignment? ; +protocolAssociatedTypeDeclaration : attributes? accessLevelModifier? 'associatedtype' typealiasName typeInheritanceClause? typealiasAssignment?; // GRAMMAR OF AN INITIALIZER DECLARATION -initializerDeclaration : initializerHead genericParameterClause? parameterClause ('throws' | 'rethrows')? initializerBody ; +initializerDeclaration : initializerHead genericParameterClause? parameterClause ('throws' | 'rethrows')? genericWhereClause? initializerBody ; initializerHead : attributes? declarationModifiers? 'init' ('?' | '!')? ; initializerBody : codeBlock ; @@ -392,9 +388,10 @@ deinitializerDeclaration : attributes? 'deinit' codeBlock ; // GRAMMAR OF AN EXTENSION DECLARATION -// attributes, requirementClause missing in the Swift Language Reference -extensionDeclaration : attributes? accessLevelModifier? 'extension' typeIdentifier requirementClause? typeInheritanceClause? extensionBody ; -extensionBody : '{' declarations?'}' ; +extensionDeclaration : attributes? accessLevelModifier? 'extension' typeIdentifier (genericWhereClause | typeInheritanceClause)? extensionBody ; +extensionBody : '{' extensionMembers?'}' ; +extensionMembers: extensionMember+ ; +extensionMember: declaration | compilerControlStatement ; // GRAMMAR OF A SUBSCRIPT DECLARATION @@ -409,15 +406,25 @@ subscriptResult : '->' attributes? sType ; // GRAMMAR OF AN OPERATOR DECLARATION operatorDeclaration : prefixOperatorDeclaration | postfixOperatorDeclaration | infixOperatorDeclaration ; -prefixOperatorDeclaration : 'prefix' 'operator' operator '{' '}' ; -postfixOperatorDeclaration : 'postfix' 'operator' operator '{' '}' ; -infixOperatorDeclaration : 'infix' 'operator' operator '{' infixOperatorAttributes '}' ; -// Order of clauses can be reversed, not indicated in Swift Language Reference -infixOperatorAttributes : precedenceClause? associativityClause? | associativityClause? precedenceClause? ; -precedenceClause : 'precedence' precedenceLevel ; -precedenceLevel : integerLiteral ; -associativityClause : 'associativity' associativity ; -associativity : 'left' | 'right' | 'none' ; +prefixOperatorDeclaration : 'prefix' 'operator' operator ; +postfixOperatorDeclaration : 'postfix' 'operator' operator ; +infixOperatorDeclaration : 'infix' 'operator' operator infixOperatorGroup? ; +infixOperatorGroup: ':' precedenceGroupName ; + + +// GRAMMAR OF A PRECEDENCE GROUP DECLARATION + +precedenceGroupDeclaration: 'precedencegroup' precedenceGroupName '{' precedenceGroupAttributes? '}' ; + +precedenceGroupAttributes: precedenceGroupAttribute+; +precedenceGroupAttribute: precedenceGroupRelation | precedenceGroupAssignment | precedenceGroupAssociativity ; +precedenceGroupRelation: ('higherThan' | 'lowerThan') ':' precedenceGroupNames; +precedenceGroupAssignment: 'assignment' ':' booleanLiteral ; +precedenceGroupAssociativity: 'associativity' ':' ('left' | 'right' | 'none') ; + +precedenceGroupNames: precedenceGroupName (',' precedenceGroupName)* ; +precedenceGroupName: identifier ; + // Patterns @@ -450,10 +457,8 @@ valueBindingPattern : 'var' pattern | 'let' pattern ; // GRAMMAR OF A TUPLE PATTERN tuplePattern : '(' tuplePatternElementList? ')' ; -tuplePatternElementList - : tuplePatternElement (',' tuplePatternElement)* - ; -tuplePatternElement : pattern ; +tuplePatternElementList : tuplePatternElement (',' tuplePatternElement)* ; +tuplePatternElement : pattern | identifier ':' pattern ; // GRAMMAR OF AN ENUMERATION CASE PATTERN @@ -479,12 +484,13 @@ attributeName : identifier ; attributeArgumentClause : '(' balancedTokens? ')' ; attributes : attribute+ ; // Swift Language Reference does not have ',' -balancedTokens : balancedToken balancedTokens? ; +balancedTokens : balancedToken+ ; balancedToken : '(' balancedTokens? ')' | '[' balancedTokens? ']' | '{' balancedTokens? '}' - | identifier | expression | contextSensitiveKeyword | literal | operator + // VersionLiteral and availabilityArgument are not in the Swift Language Reference + | identifier | expression | contextSensitiveKeyword | literal | operator | VersionLiteral | availabilityArgument // | Any punctuation except ( , ')' , '[' , ']' , { , or } // Punctuation is very ambiguous, interpreting punctuation as defined in www.thepunctuationguide.com | ':' | ';' | ',' | '!' | '<' | '>' | '-' | '\'' | '/' | '...' | '"' @@ -494,8 +500,6 @@ balancedToken // GRAMMAR OF AN EXPRESSION -expressionList : expression (',' expression)* ; - expression : tryOperator? prefixExpression binaryExpression* ; prefixExpression @@ -505,24 +509,24 @@ prefixExpression /* expression - : prefixOperator expression - | inOutExpression - | primaryExpression - | expression binaryOperator expression - | expression assignmentOperator expression - | expression conditionalOperator expression - | expression typeCastingOperator - | expression postfixOperator - | expression parenthesizedExpression trailingClosure? - | expression '.' 'init' - | expression '.' DecimalLiteral - | expression '.' identifier genericArgumentClause? - | expression '.' 'self' - | expression '.' 'dynamicType' - | expression '[' expressionList ']' - | expression '!' - | expression '?' - ; + : prefixOperator expression + | inOutExpression + | primaryExpression + | expression binaryOperator expression + | expression assignmentOperator expression + | expression conditionalOperator expression + | expression typeCastingOperator + | expression postfixOperator + | expression parenthesizedExpression trailingClosure? + | expression '.' 'init' + | expression '.' DecimalLiteral + | expression '.' identifier genericArgumentClause? + | expression '.' 'self' + | expression '.' 'dynamicType' + | expression '[' expressionList ']' + | expression '!' + | expression '?' + ; */ // GRAMMAR OF A PREFIX EXPRESSION @@ -562,15 +566,18 @@ typeCastingOperator // GRAMMAR OF A PRIMARY EXPRESSION primaryExpression - : (identifier | operator) genericArgumentClause? // operator not mentioned in the Swift Language Reference + : (identifier | operator | keyword | contextSensitiveKeyword) genericArgumentClause? // operator, keyword, and contextSensitiveKeyword are not mentioned in the Swift Language Reference | literalExpression | selfExpression | superclassExpression | closureExpression | parenthesizedExpression + | tupleExpression | implicitMemberExpression // | implicit_member_expression disallow as ambig with explicit member expr in postfix_expression | wildcardExpression + | selectorExpression + | keyPathExpression ; // GRAMMAR OF A LITERAL EXPRESSION @@ -579,23 +586,30 @@ literalExpression : literal | arrayLiteral | dictionaryLiteral - | '__FILE__' | '__LINE__' | '__COLUMN__' | '__FUNCTION__' + | playgroundLiteral + | '#file' | '#line' | '#column' | '#function' ; arrayLiteral : '[' arrayLiteralItems? ']' ; arrayLiteralItems : arrayLiteralItem (',' arrayLiteralItem)* ','? ; arrayLiteralItem : expression ; + dictionaryLiteral : '[' dictionaryLiteralItems ']' | '[' ':' ']' ; dictionaryLiteralItems : dictionaryLiteralItem (',' dictionaryLiteralItem)* ','? ; dictionaryLiteralItem : expression ':' expression ; +playgroundLiteral: '#colorLiteral' '(' 'red' ':' expression ',' 'green' ':' expression ',' 'blue' ':' expression ',' 'alpha' ':' expression ')' + | '#fileLiteral' '(' 'resourceName' ':' expression ')' + | '#imageLiteral' '(' 'resourceName' ':' expression ')' ; + // GRAMMAR OF A SELF EXPRESSION selfExpression : 'self' - | 'self' '.' identifier - | 'self' '[' expressionList ']' - | 'self' '.' 'init' + | 'self' '.' identifier // self-method-expression + // Swift Language Reference uses expressionList + | 'self' '[' tupleElementList ']' // self-subscript-expression + | 'self' '.' 'init' // self-initializer-expression ; // GRAMMAR OF A SUPERCLASS EXPRESSION @@ -607,23 +621,29 @@ superclassExpression ; superclassMethodExpression : 'super' '.' identifier ; -superclassSubscriptExpression : 'super' '[' expressionList ']' ; +// Swift Language Reference uses expressionList +superclassSubscriptExpression : 'super' '[' tupleElementList ']' ; superclassInitializerExpression : 'super' '.' 'init' ; // GRAMMAR OF A CLOSURE EXPRESSION -// Statements are not optional in the Swift Language Reference closureExpression : '{' closureSignature? statements? '}' ; + closureSignature - : parameterClause functionResult? 'in' - | identifierList functionResult? 'in' - | captureList parameterClause functionResult? 'in' - | captureList identifierList functionResult? 'in' + : captureList? closureParameterClause 'throws'? functionResult? 'in' | captureList 'in' ; +closureParameterClause: '(' ')' | '(' closureParameterList ')' | identifierList ; +closureParameterList: closureParameter (',' closureParameterList)* ; +closureParameter: closureParameterName typeAnnotation? + | closureParameterName typeAnnotation '...' + ; +// Swift Language Reference does not have "_" +closureParameterName: identifier | '_'; + captureList : '[' captureListItems ']' ; -captureListItems: captureListItem (',' captureListItem)? ; +captureListItems: captureListItem (',' captureListItem)* ; captureListItem: captureSpecifier? expression ; captureSpecifier : 'weak' | 'unowned' | 'unowned(safe)' | 'unowned(unsafe)' ; @@ -633,51 +653,78 @@ implicitMemberExpression : '.' identifier ; // GRAMMAR OF A PARENTHESIZED EXPRESSION -parenthesizedExpression : '(' expressionElementList? ')' ; -expressionElementList : expressionElement (',' expressionElement)* ; -expressionElement : expression | identifier ':' expression ; +parenthesizedExpression : '(' expression ')' ; + +// GRAMMAR OF A TUPLE EXPRESSION + +tupleExpression: '(' tupleElementList? ')' ; +tupleElementList: tupleElement (',' tupleElement)* ; +tupleElement: (identifier ':')? expression ; // GRAMMAR OF A WILDCARD EXPRESSION wildcardExpression : '_' ; +// GRAMMAR OF A SELECTOR EXPRESSION + +selectorExpression + : '#selector' '(' expression ')' + | '#selector' '(' ('getter:' | 'setter:') expression ')' + ; + +// GRAMMAR OF A KEY PATH EXPRESSION + +keyPathExpression + : '#keyPath' '(' expression ')' + | '\\' expression +; + // GRAMMAR OF A POSTFIX EXPRESSION postfixExpression : primaryExpression # primary | postfixExpression postfixOperator # postfixOperation // Function call with closure expression should always be above a lone parenthesized expression to reduce ambiguity - | postfixExpression parenthesizedExpression? closureExpression # functionCallWithClosureExpression - | postfixExpression parenthesizedExpression # functionCallExpression + | postfixExpression functionCallArgumentClause? closureExpression # functionCallWithClosureExpression + | postfixExpression functionCallArgumentClause # functionCallExpression | postfixExpression '.' 'init' # initializerExpression + | postfixExpression '.' 'init' '(' argumentNames ')' # initializerExpressionWithArguments // TODO: don't allow '_' here in DecimalLiteral: | postfixExpression '.' DecimalLiteral # explicitMemberExpression1 - | postfixExpression '.' identifier genericArgumentClause? # explicitMemberExpression2 + | postfixExpression '.' identifier genericArgumentClause? # explicitMemberExpression2 + | postfixExpression '.' identifier '(' argumentNames ')' # explicitMemberExpression3 | postfixExpression '.' 'self' # postfixSelfExpression - | postfixExpression '.' 'dynamicType' # dynamicTypeExpression - | postfixExpression '[' expressionList ']' # subscriptExpression + | 'type' '(' 'of' ':' expression ')' # dynamicTypeExpression + // Swift Language Reference uses expressionList + | postfixExpression '[' tupleElementList ']' # subscriptExpression | postfixExpression '!' # forcedValueExpression | postfixExpression '?' # optionalChainingExpression ; // GRAMMAR OF A FUNCTION CALL EXPRESSION - -/* -functionCallExpression - : postfixExpression parenthesizedExpression - : postfixExpression parenthesizedExpression? trailingClosure - ; - */ +functionCallArgumentClause: '(' functionCallArgumentList? ')' ; +functionCallArgumentList: functionCallArgument (',' functionCallArgument)* ; +// (expression | operator) is optional to handle selector expressions (see #425 for example) +functionCallArgument: (functionCallIdentifier ':') (expression | operator)? + | (functionCallIdentifier ':')? (expression | operator) ; +// SwiftLanguageReference does not have keyword +functionCallIdentifier: identifier | keyword ; + +// GRAMMAR OF AN ARGUMENT NAME +argumentNames : argumentName+ ; +argumentName: (identifier | '_') ':' ; // Swift Language Reference has argumentName → identifier : //trailing_closure : closure_expression ; //initializer_expression : postfix_expression '.' 'init' ; -/*explicitMemberExpression +/* +explicitMemberExpression : postfixExpression '.' DecimalLiteral // TODO: don't allow '_' here in DecimalLiteral | postfixExpression '.' identifier genericArgumentClause? + | postfixExpression '.' identifier '(' argumentNames ')' ; - */ +*/ //postfix_self_expression : postfix_expression '.' 'self' ; @@ -703,7 +750,7 @@ functionCallExpression // are also referenced individually. For example, type signatures use // <...>. -operatorHead: '=' | '<' | '>' | '!' | '*' | '&' | '==' | '?' | '-' | '&&' | '||' | '/' | OperatorHead; +operatorHead: '=' | '<' | '>' | '!' | '*' | '&' | '==' | '?' | '-' | '&&' | '||' | '/' | '>=' | '->' | OperatorHead; operatorCharacter: operatorHead | OperatorCharacter; operator: operatorHead operatorCharacter* @@ -758,16 +805,23 @@ postfixOperator : operator ; sType : arrayType | dictionaryType - | sType 'throws'? '->' sType // function-type - | sType 'rethrows' '->' sType // function-type + | functionType | typeIdentifier | tupleType | sType '?' // optional-type | sType '!' // implicitly-unwrapped-optional-type | protocolCompositionType | sType '.' 'Type' | sType '.' 'Protocol' // metatype + | 'Any' | 'Self' ; +functionType: attributes? functionTypeArgumentClause ('throws' | 'rethrows')? '->' sType ; +functionTypeArgumentClause: '(' ')' + | '(' functionTypeArgumentList '...'? ')' ; +functionTypeArgumentList: functionTypeArgument (',' functionTypeArgument)* ; +functionTypeArgument: attributes? 'inout'? sType | argumentLabel typeAnnotation ; +argumentLabel: identifier ; + arrayType: '[' sType ']' ; dictionaryType: '[' sType ':' sType ']' ; @@ -778,31 +832,29 @@ implicitlyUnwrappedOptionalType: sType '!' ; // GRAMMAR OF A TYPE ANNOTATION -typeAnnotation : ':' attributes? sType ; +typeAnnotation : ':' attributes? 'inout'? sType ; // GRAMMAR OF A TYPE IDENTIFIER typeIdentifier : typeName genericArgumentClause? | typeName genericArgumentClause? '.' typeIdentifier - | 'Self' // Swift Language Reference does not have this ; typeName : identifier ; // GRAMMAR OF A TUPLE TYPE -tupleType : '(' tupleTypeBody? ')' ; -tupleTypeBody : tupleTypeElementList '...'? ; -tupleTypeElementList : tupleTypeElement | tupleTypeElement ',' tupleTypeElementList ; -tupleTypeElement : attributes? 'inout'? sType | 'inout'? elementName typeAnnotation ; +tupleType : '(' tupleTypeElementList? ')' ; +tupleTypeElementList : tupleTypeElement (',' tupleTypeElement)* ; +tupleTypeElement : elementName typeAnnotation | sType ; elementName : identifier ; // GRAMMAR OF A PROTOCOL COMPOSITION TYPE -protocolCompositionType : 'protocol' '<' protocolIdentifierList? '>' ; -protocolIdentifierList : protocolIdentifier | protocolIdentifier ',' protocolIdentifierList ; -protocolIdentifier : typeIdentifier ; +protocolCompositionType: protocolIdentifier '&' protocolCompositionContinuation ; +protocolCompositionContinuation: protocolIdentifier | protocolCompositionType ; +protocolIdentifier: typeIdentifier ; // GRAMMAR OF A METATYPE TYPE @@ -817,27 +869,44 @@ typeInheritanceClause : ':' classRequirement ',' typeInheritanceList typeInheritanceList : typeIdentifier (',' typeIdentifier)* ; classRequirement: 'class' ; -// ------ Build Configurations (Macros) ------- +// GRAMMAR OF A COMPILER CONTROL STATEMENT + +compilerControlStatement: conditionalCompilationBlock | lineControlStatement ; + +// GRAMMAR OF A CONDITIONAL COMPILATION BLOCK -compilerControlStatement: buildConfigurationStatement | lineControlStatement ; -buildConfigurationStatement: '#if' buildConfiguration statements? buildConfigurationElseIfClauses? buildConfigurationElseClause? '#endif' ; -buildConfigurationElseIfClauses: buildConfigurationElseIfClause+ ; -buildConfigurationElseIfClause: '#elseif' buildConfiguration statements? ; -buildConfigurationElseClause: '#else' statements? ; +conditionalCompilationBlock: ifDirectiveClause elseifDirectiveClauses? elseDirectiveClause? '#endif' ; +ifDirectiveClause: '#if' compilationCondition statements? ; +elseifDirectiveClauses: elseifDirectiveClause+ ; +elseifDirectiveClause: '#elseif' compilationCondition statements? ; +elseDirectiveClause: '#else' statements? ; -buildConfiguration: platformTestingFunction | identifier | booleanLiteral - | '(' buildConfiguration ')' - | '!' buildConfiguration - | buildConfiguration ('&&' | '||') buildConfiguration +compilationCondition + : platformCondition + | identifier + | booleanLiteral + | '(' compilationCondition ')' + | '!' compilationCondition + | compilationCondition ('&&' | '||') compilationCondition + ; + +platformCondition + : 'os' '(' operatingSystem ')' + | 'arch' '(' architecture ')' + | 'swift' '(' '>=' swiftVersion ')' + | 'canImport' '(' moduleName ')' + | 'targetEnvironment' '(' 'simulator' ')' ; -platformTestingFunction: 'os' '(' operatingSystem ')' | 'arch' '(' architecture ')' ; operatingSystem: 'OSX' | 'iOS' | 'watchOS' | 'tvOS' ; architecture: 'i386' | 'x86_64' | 'arm' | 'arm64' ; +swiftVersion: FloatingPointLiteral ; +moduleName: IdentifierCharacters ; -lineControlStatement: '#line' (lineNumber fileName)? ; +lineControlStatement: '#sourceLocation' '(' 'file' ':' fileName ',' 'line' ':' lineNumber ')' + | '#sourceLocation' '(' ')' ; lineNumber: integerLiteral ; -fileName: StringLiteral ; +fileName: SingleStringLiteral ; // ---------- Lexical Structure ----------- @@ -846,18 +915,38 @@ NilLiteral: 'nil' ; // GRAMMAR OF AN IDENTIFIER -identifier : Identifier | contextSensitiveKeyword ; - -keyword : 'convenience' | 'class' | 'deinit' | 'enum' | 'extension' | 'func' | 'import' | 'init' | 'let' | 'protocol' | 'static' | 'struct' | 'subscript' | 'typealias' | 'var' | 'break' | 'case' | 'continue' | 'default' | 'do' | 'else' | 'fallthrough' | 'if' | 'in' | 'for' | 'return' | 'switch' | 'where' | 'while' | 'as' | 'dynamicType' | 'is' | 'super' | 'self' | 'Self' | 'Type' | 'repeat' ; +identifier : Identifier | contextSensitiveKeyword | grammarString ; + +keyword : + // Keywords used in declarations + 'associatedtype' | 'class' | 'deinit' | 'enum' | 'extension' | 'fileprivate' | 'func' | 'import' | 'init' | 'inout' + | 'internal' | 'let' | 'open' | 'operator' | 'private' | 'protocol' | 'public' | 'static' | 'struct' | 'subscript' + | 'typealias' | 'var' + // Keywords used in statements + | 'break' | 'case' | 'continue' | 'default' | 'defer' | 'do' | 'else' | 'fallthrough' | 'for' | 'guard' | 'if' | 'in' + | 'repeat' | 'return' | 'switch' | 'where' | 'while' + // Keywords used in expressions and types + | 'as' | 'Any' | 'catch' | 'dynamicType' | 'is' | 'nil' | 'rethrows' | 'super' | 'self' | 'Self' | 'throw' + | 'throws' | 'try' + | BooleanLiteral + // Keywords used in patterns + | '_' + // Keywords that begin with a number sign (#) + | '#available' | '#colorLiteral' | '#column' | '#else' | '#elseif' | '#endif' | '#file' | 'fileLiteral' | '#function' + | '#if' | 'imageLiteral' | '#line' | '#selector' + ; contextSensitiveKeyword : 'associativity' | 'convenience' | 'dynamic' | 'didSet' | 'final' | 'get' | 'infix' | 'indirect' | 'lazy' | 'left' | 'mutating' | 'none' | 'nonmutating' | 'optional' | 'operator' | 'override' | 'postfix' | 'precedence' | 'prefix' | 'Protocol' | 'required' | 'right' | 'set' | 'Type' | 'unowned' | 'weak' | 'willSet' | - 'iOS' | 'iOSApplicationExtension' | 'OSX' | 'OSXApplicationExtension-' | 'watchOS' | 'x86_64' | - 'arm' | 'arm64' | 'i386' | 'os' | 'arch' + 'iOS' | 'iOSApplicationExtension' | 'OSX' | 'OSXApplicationExtension­' | 'watchOS' | 'x86_64' | + 'arm' | 'arm64' | 'i386' | 'os' | 'arch' | 'safe' | 'tvOS' | 'file' | 'line' | 'default' | 'Self' | 'var' ; +grammarString: + 'red' | 'blue' | 'green' | 'alpha' | 'resourceName' | 'of' | 'type' ; + OperatorHead : '/' | '=' | '-' | '+' | '!' | '*' | '%' | '<' | '>' | '&' | '|' | '^' | '~' | '?' | [\u00A1-\u00A7] @@ -927,7 +1016,7 @@ ImplicitParameterName : '$' DecimalLiteral ; // TODO: don't allow '_' here // GRAMMAR OF A LITERAL booleanLiteral: BooleanLiteral ; -literal : numericLiteral | StringLiteral | BooleanLiteral | NilLiteral ; +literal : numericLiteral | MultiStringLiteral | SingleStringLiteral | BooleanLiteral | NilLiteral ; // GRAMMAR OF AN INTEGER LITERAL @@ -943,7 +1032,7 @@ integerLiteral BinaryLiteral : '0b' BinaryDigit BinaryLiteralCharacters? ; fragment BinaryDigit : [01] ; fragment BinaryLiteralCharacter : BinaryDigit | '_' ; -fragment BinaryLiteralCharacters : BinaryLiteralCharacter BinaryLiteralCharacters? ; +fragment BinaryLiteralCharacters : BinaryLiteralCharacter+ ; OctalLiteral : '0o' OctalDigit OctalLiteralCharacters? ; fragment OctalDigit : [0-7] ; @@ -978,13 +1067,26 @@ VersionLiteral: DecimalLiteral DecimalFraction DecimalFraction ; // GRAMMAR OF A STRING LITERAL -StringLiteral : '"' QuotedText? '"' ; -fragment QuotedText : QuotedTextItem QuotedText? ; -fragment QuotedTextItem : EscapedCharacter +TRIPLEDQUOTES : '"""' ; + +MultiStringLiteral : TRIPLEDQUOTES '\n' .*? '\n' TRIPLEDQUOTES ; +fragment MultiQuotedText : MultiQuotedTextItem+ ; +fragment MultiQuotedTextItem : MultiInterpolatedString + | ~[\\\u000A\u000D] + ; +fragment MultiInterpolatedString: '\\(' (MultiQuotedTextItem | SingleStringLiteral)* ')'; + +// StringLiteral : '"' QuotedText? '"' ; +SingleStringLiteral : '"' QuotedText? '"' ; +fragment SingleDoubleQuote : '"' | ~["] ; +fragment QuotedText : QuotedTextItem+ ; +fragment QuotedTextItem : EscapedCharacter | InterpolatedString // | '\\(' expression ')' | ~["\\\u000A\u000D] ; -EscapedCharacter : '\\' [0\\(tnr"'] +fragment InterpolatedString: '\\(' (QuotedTextItem | SingleStringLiteral)* ')'; + +EscapedCharacter : '\\' [0\\tnr"'] | '\\x' HexadecimalDigit HexadecimalDigit | '\\u' '{' HexadecimalDigit HexadecimalDigit? HexadecimalDigit? HexadecimalDigit? HexadecimalDigit? HexadecimalDigit? HexadecimalDigit? HexadecimalDigit? '}' ; diff --git a/pmd-swift/src/main/java/net/sourceforge/pmd/cpd/SwiftTokenizer.java b/pmd-swift/src/main/java/net/sourceforge/pmd/cpd/SwiftTokenizer.java index d401eefd12f..d97b1e6426a 100644 --- a/pmd-swift/src/main/java/net/sourceforge/pmd/cpd/SwiftTokenizer.java +++ b/pmd-swift/src/main/java/net/sourceforge/pmd/cpd/SwiftTokenizer.java @@ -4,79 +4,19 @@ package net.sourceforge.pmd.cpd; -import org.antlr.v4.runtime.ANTLRInputStream; -import org.antlr.v4.runtime.BaseErrorListener; -import org.antlr.v4.runtime.Lexer; -import org.antlr.v4.runtime.RecognitionException; -import org.antlr.v4.runtime.Recognizer; -import org.antlr.v4.runtime.Token; +import org.antlr.v4.runtime.CharStream; -import net.sourceforge.pmd.lang.ast.TokenMgrError; +import net.sourceforge.pmd.lang.antlr.AntlrTokenManager; import net.sourceforge.pmd.lang.swift.antlr4.SwiftLexer; /** - * The Swift Tokenizer + * SwiftTokenizer */ -public class SwiftTokenizer implements Tokenizer { +public class SwiftTokenizer extends AntlrTokenizer { @Override - public void tokenize(SourceCode sourceCode, Tokens tokenEntries) { - StringBuilder buffer = sourceCode.getCodeBuffer(); - - try { - ANTLRInputStream ais = new ANTLRInputStream(buffer.toString()); - SwiftLexer lexer = new SwiftLexer(ais); - - lexer.removeErrorListeners(); - lexer.addErrorListener(new ErrorHandler()); - Token token = lexer.nextToken(); - - while (token.getType() != Token.EOF) { - if (token.getChannel() != Lexer.HIDDEN) { - TokenEntry tokenEntry = new TokenEntry(token.getText(), sourceCode.getFileName(), token.getLine()); - - tokenEntries.add(tokenEntry); - } - token = lexer.nextToken(); - } - } catch (ANTLRSyntaxError err) { - // Wrap exceptions of the Swift tokenizer in a TokenMgrError, so - // they are correctly handled - // when CPD is executed with the '--skipLexicalErrors' command line - // option - throw new TokenMgrError("Lexical error in file " + sourceCode.getFileName() + " at line " + err.getLine() - + ", column " + err.getColumn() + ". Encountered: " + err.getMessage(), - TokenMgrError.LEXICAL_ERROR); - } finally { - tokenEntries.add(TokenEntry.getEOF()); - } - } - - private static class ErrorHandler extends BaseErrorListener { - @Override - public void syntaxError(Recognizer<?, ?> recognizer, Object offendingSymbol, int line, int charPositionInLine, - String msg, RecognitionException ex) { - throw new ANTLRSyntaxError(msg, line, charPositionInLine, ex); - } - } - - private static class ANTLRSyntaxError extends RuntimeException { - private static final long serialVersionUID = 1L; - private final int line; - private final int column; - - ANTLRSyntaxError(String msg, int line, int column, RecognitionException cause) { - super(msg, cause); - this.line = line; - this.column = column; - } - - public int getLine() { - return line; - } - - public int getColumn() { - return column; - } + protected AntlrTokenManager getLexerForSource(final SourceCode sourceCode) { + CharStream charStream = AntlrTokenizer.getCharStreamFromSourceCode(sourceCode); + return new AntlrTokenManager(new SwiftLexer(charStream), sourceCode.getFileName()); } } diff --git a/pmd-swift/src/test/java/net/sourceforge/pmd/cpd/SwiftTokenizerTest.java b/pmd-swift/src/test/java/net/sourceforge/pmd/cpd/SwiftTokenizerTest.java index ebad5ae5d7d..e2945ad0f33 100644 --- a/pmd-swift/src/test/java/net/sourceforge/pmd/cpd/SwiftTokenizerTest.java +++ b/pmd-swift/src/test/java/net/sourceforge/pmd/cpd/SwiftTokenizerTest.java @@ -5,6 +5,7 @@ package net.sourceforge.pmd.cpd; import java.io.IOException; +import java.nio.charset.StandardCharsets; import org.apache.commons.io.IOUtils; import org.junit.Before; @@ -25,12 +26,12 @@ public void buildTokenizer() throws IOException { @Override public String getSampleCode() throws IOException { - return IOUtils.toString(SwiftTokenizer.class.getResourceAsStream(FILENAME)); + return IOUtils.toString(SwiftTokenizer.class.getResourceAsStream(FILENAME), StandardCharsets.UTF_8); } @Test public void tokenizeTest() throws IOException { - this.expectedTokenCount = 3811; + this.expectedTokenCount = 4239; super.tokenizeTest(); } } diff --git a/pmd-swift/src/test/resources/net/sourceforge/pmd/cpd/BTree.swift b/pmd-swift/src/test/resources/net/sourceforge/pmd/cpd/BTree.swift index 96731997ad9..9b283a3ec8b 100644 --- a/pmd-swift/src/test/resources/net/sourceforge/pmd/cpd/BTree.swift +++ b/pmd-swift/src/test/resources/net/sourceforge/pmd/cpd/BTree.swift @@ -888,3 +888,128 @@ extension BTree { self.init(builder.finish()) } } + +/// Swift 4 news + + +extension BTree { + + // Multi-line String Literals + + func multiline() { + let star = "â­ï¸" + let introString = """ + A long time ago in a galaxy far, + far away.... + + You could write multi-lined strings + without "escaping" single quotes. + + The indentation of the closing quotes + below deside where the text line + begins. + + You can even dynamically add values + from properties: \(star) + """ + print(introString) // prints the string exactly as written above with the value of star + } + + // One-Sided Ranges + + func planets() { + var planets = ["Mercury", "Venus", "Earth", "Mars", "Jupiter", "Saturn", "Uranus", "Neptune"] + let outsideAsteroidBelt = planets[4...] // Before: planets[4..<planets.endIndex] + let firstThree = planets[..<4] // Before: planets[planets.startIndex..<4] + + temperature(planetNumber: 3) // Earth + } + + func temperature(planetNumber: Int) { + switch planetNumber { + case ...2: // anything less than or equal to 2 + print("Too hot") + case 4...: // anything greater than or equal to 4 + print("Too cold") + default: + print("Justtttt right") + } + } + + +} + +// Generic Subscripts + +struct GenericDictionary<Key: Hashable, Value> { + private var data: [Key: Value] + + init(data: [Key: Value]) { + self.data = data + } + + subscript<T>(key: Key) -> T? { + return data[key] as? T + } +} + +func useSomeGenericSubscript() { + // Dictionary of type: [String: Any] + var earthData = GenericDictionary(data: ["name": "Earth", "population": 7500000000, "moons": 1]) + + // Automatically infers return type without "as? String" + let name: String? = earthData["name"] + + // Automatically infers return type without "as? Int" + let population: Int? = earthData["population"] +} + +extension GenericDictionary { + subscript<Keys: Sequence>(keys: Keys) -> [Value] where Keys.Iterator.Element == Key { + var values: [Value] = [] + for key in keys { + if let value = data[key] { + values.append(value) + } + } + return values + } +} + +func useSomeGenericFromExtension() { + // Array subscript value + let nameAndMoons = earthData[["moons", "name"]] // [1, "Earth"] + // Set subscript value + let nameAndMoons2 = earthData[Set(["moons", "name"])] // [1, "Earth"] +} + +// Associated Type Constraints + +protocol MyProtocol { + associatedtype Element + associatedtype SubSequence : Sequence where SubSequence.Iterator.Element == Iterator.Element +} + +// Class and Protocol Existential + +protocol MyProtocol { } +class View { } +class ViewSubclass: View, MyProtocol { } + +class MyClass { + var delegate: (View & MyProtocol)? +} + +let myClass = MyClass() +//myClass.delegate = View() // error: cannot assign value of type 'View' to type '(View & MyProtocol)?' +myClass.delegate = ViewSubclass() + +// 4.1 conditional + +#if canImport(SomeModule) +let someModuleImportedVersion = SomeModule.version +#endif + +#if targetEnvironment(simulator) +print("code only compiled for simulator") +#endif diff --git a/pmd-test/pom.xml b/pmd-test/pom.xml index 001772b1735..c4dee986597 100644 --- a/pmd-test/pom.xml +++ b/pmd-test/pom.xml @@ -3,17 +3,14 @@ <modelVersion>4.0.0</modelVersion> <artifactId>pmd-test</artifactId> <name>PMD Test Framework</name> + <description>Contains a test framework to test rules.</description> <parent> <groupId>net.sourceforge.pmd</groupId> <artifactId>pmd</artifactId> - <version>6.0.0-SNAPSHOT</version> + <version>6.10.0-SNAPSHOT</version> </parent> - <properties> - <config.basedir>${basedir}/../pmd-core</config.basedir> - </properties> - <dependencies> <dependency> <groupId>junit</groupId> @@ -23,10 +20,18 @@ <groupId>net.sourceforge.pmd</groupId> <artifactId>pmd-core</artifactId> </dependency> + <dependency> + <groupId>org.apache.ant</groupId> + <artifactId>ant</artifactId> + </dependency> <dependency> <groupId>org.apache.ant</groupId> <artifactId>ant-testutil</artifactId> </dependency> + <dependency> + <groupId>commons-io</groupId> + <artifactId>commons-io</artifactId> + </dependency> <dependency> <groupId>org.mockito</groupId> diff --git a/pmd-test/src/main/java/net/sourceforge/pmd/AbstractLanguageVersionTest.java b/pmd-test/src/main/java/net/sourceforge/pmd/AbstractLanguageVersionTest.java index b5636a18afd..faa31940fd5 100644 --- a/pmd-test/src/main/java/net/sourceforge/pmd/AbstractLanguageVersionTest.java +++ b/pmd-test/src/main/java/net/sourceforge/pmd/AbstractLanguageVersionTest.java @@ -115,9 +115,10 @@ public void testRegisteredRulesets() throws Exception { return; } + ResourceLoader rl = new ResourceLoader(); Properties props = new Properties(); String rulesetsProperties = "rulesets/" + simpleTerseName + "/rulesets.properties"; - try (InputStream inputStream = ResourceLoader.loadResourceAsStream(rulesetsProperties);) { + try (InputStream inputStream = rl.loadClassPathResourceAsStreamOrThrow(rulesetsProperties)) { props.load(inputStream); } String rulesetFilenames = props.getProperty("rulesets.filenames"); @@ -131,7 +132,7 @@ public void testRegisteredRulesets() throws Exception { String[] rulesets = rulesetFilenames.split(","); for (String r : rulesets) { - InputStream stream = ResourceLoader.loadResourceAsStream(r); + InputStream stream = rl.loadClassPathResourceAsStream(r); assertNotNull(stream); stream.close(); RuleSet ruleset = factory.createRuleSet(r); diff --git a/pmd-test/src/main/java/net/sourceforge/pmd/AbstractRuleSetFactoryTest.java b/pmd-test/src/main/java/net/sourceforge/pmd/AbstractRuleSetFactoryTest.java index 92ad5765654..677e4290a76 100644 --- a/pmd-test/src/main/java/net/sourceforge/pmd/AbstractRuleSetFactoryTest.java +++ b/pmd-test/src/main/java/net/sourceforge/pmd/AbstractRuleSetFactoryTest.java @@ -25,6 +25,7 @@ import java.util.Properties; import java.util.Set; import java.util.StringTokenizer; +import java.util.regex.Pattern; import javax.xml.parsers.ParserConfigurationException; import javax.xml.parsers.SAXParser; import javax.xml.parsers.SAXParserFactory; @@ -243,7 +244,7 @@ private List<String> getRuleSetFileNames(String language) throws IOException, Ru List<String> ruleSetFileNames = new ArrayList<>(); try { Properties properties = new Properties(); - try (InputStream is = ResourceLoader.loadResourceAsStream("rulesets/" + language + "/rulesets.properties")) { + try (InputStream is = new ResourceLoader().loadClassPathResourceAsStreamOrThrow("rulesets/" + language + "/rulesets.properties")) { properties.load(is); } String fileNames = properties.getProperty("rulesets.filenames"); @@ -300,23 +301,17 @@ private boolean validateAgainstDtd(InputStream inputStream) inputStream.close(); String rulesetNamespace = RuleSetWriter.RULESET_2_0_0_NS_URI; - if (file.contains(RuleSetWriter.RULESET_3_0_0_NS_URI)) { - rulesetNamespace = RuleSetWriter.RULESET_3_0_0_NS_URI; - } // Remove XML Schema stuff, replace with DTD file = file.replaceAll("<\\?xml [ a-zA-Z0-9=\".-]*\\?>", ""); file = file.replaceAll("xmlns=\"" + rulesetNamespace + "\"", ""); file = file.replaceAll("xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"", ""); file = file.replaceAll("xsi:schemaLocation=\"" + rulesetNamespace - + " http://pmd.sourceforge.net/ruleset_\\d_0_0.xsd\"", ""); + + " https://pmd.sourceforge.io/ruleset_\\d_0_0.xsd\"", ""); if (rulesetNamespace.equals(RuleSetWriter.RULESET_2_0_0_NS_URI)) { file = "<?xml version=\"1.0\"?>" + PMD.EOL + "<!DOCTYPE ruleset SYSTEM " - + "\"http://pmd.sourceforge.net/ruleset_2_0_0.dtd\">" + PMD.EOL + file; - } else if (rulesetNamespace.equals(RuleSetWriter.RULESET_3_0_0_NS_URI)) { - file = "<?xml version=\"1.0\"?>" + PMD.EOL + "<!DOCTYPE ruleset SYSTEM " - + "\"http://pmd.sourceforge.net/ruleset_3_0_0.dtd\">" + PMD.EOL + file; + + "\"https://pmd.sourceforge.io/ruleset_2_0_0.dtd\">" + PMD.EOL + file; } else { file = "<?xml version=\"1.0\"?>" + PMD.EOL + "<!DOCTYPE ruleset>" + PMD.EOL + file; } @@ -341,14 +336,7 @@ private String readFullyToString(InputStream inputStream) throws IOException { } private static InputStream loadResourceAsStream(String resource) throws RuleSetNotFoundException { - InputStream inputStream = ResourceLoader.loadResourceAsStream(resource, - AbstractRuleSetFactoryTest.class.getClassLoader()); - if (inputStream == null) { - throw new RuleSetNotFoundException("Can't find resource " + resource - + " Make sure the resource is a valid file or URL or is on the CLASSPATH. Here's the current classpath: " - + System.getProperty("java.class.path")); - } - return inputStream; + return new ResourceLoader().loadClassPathResourceAsStreamOrThrow(resource); } private void testRuleSet(String fileName) @@ -465,8 +453,14 @@ private void assertEqualsRuleSet(String message, RuleSet ruleSet1, RuleSet ruleS List<PropertyDescriptor<?>> propertyDescriptors2 = rule2.getPropertyDescriptors(); assertEquals(message + ", Rule property descriptor ", propertyDescriptors1, propertyDescriptors2); for (int j = 0; j < propertyDescriptors1.size(); j++) { - assertEquals(message + ", Rule property value " + j, rule1.getProperty(propertyDescriptors1.get(j)), - rule2.getProperty(propertyDescriptors2.get(j))); + Object value1 = rule1.getProperty(propertyDescriptors1.get(j)); + Object value2 = rule2.getProperty(propertyDescriptors2.get(j)); + // special case for Pattern, there is no equals method + if (propertyDescriptors1.get(j).type() == Pattern.class) { + value1 = ((Pattern) value1).pattern(); + value2 = ((Pattern) value2).pattern(); + } + assertEquals(message + ", Rule property value " + j, value1, value2); } assertEquals(message + ", Rule property descriptor count", propertyDescriptors1.size(), propertyDescriptors2.size()); @@ -483,7 +477,7 @@ private void assertEqualsRuleSet(String message, RuleSet ruleSet1, RuleSet ruleS protected static RuleSetReferenceId createRuleSetReferenceId(final String ruleSetXml) { return new RuleSetReferenceId(null) { @Override - public InputStream getInputStream(ClassLoader classLoader) throws RuleSetNotFoundException { + public InputStream getInputStream(ResourceLoader resourceLoader) throws RuleSetNotFoundException { try { return new ByteArrayInputStream(ruleSetXml.getBytes("UTF-8")); } catch (UnsupportedEncodingException e) { @@ -502,11 +496,8 @@ private static class ValidateDefaultHandler extends DefaultHandler { ValidateDefaultHandler() { schemaMapping = new HashMap<>(); - schemaMapping.put("http://pmd.sourceforge.net/ruleset_2_0_0.xsd", "ruleset_2_0_0.xsd"); - schemaMapping.put("http://pmd.sourceforge.net/ruleset_3_0_0.xsd", "ruleset_3_0_0.xsd"); - - schemaMapping.put("http://pmd.sourceforge.net/ruleset_2_0_0.dtd", "ruleset_2_0_0.dtd"); - schemaMapping.put("http://pmd.sourceforge.net/ruleset_3_0_0.dtd", "ruleset_3_0_0.dtd"); + schemaMapping.put("https://pmd.sourceforge.io/ruleset_2_0_0.xsd", "ruleset_2_0_0.xsd"); + schemaMapping.put("https://pmd.sourceforge.io/ruleset_2_0_0.dtd", "ruleset_2_0_0.dtd"); } public ValidateDefaultHandler resetValid() { diff --git a/pmd-test/src/main/java/net/sourceforge/pmd/ant/AbstractAntTestHelper.java b/pmd-test/src/main/java/net/sourceforge/pmd/ant/AbstractAntTestHelper.java index 0a4ca4caf2d..9859b8c8374 100644 --- a/pmd-test/src/main/java/net/sourceforge/pmd/ant/AbstractAntTestHelper.java +++ b/pmd-test/src/main/java/net/sourceforge/pmd/ant/AbstractAntTestHelper.java @@ -7,12 +7,15 @@ import static java.io.File.separator; import java.io.File; +import java.io.IOException; import org.apache.tools.ant.BuildFileRule; import org.apache.tools.ant.Project; import org.junit.Assert; import org.junit.Before; import org.junit.Rule; +import org.junit.rules.TemporaryFolder; + /** * Quite an ugly classe, arguably useful for just 2 units test - nevertheless as @@ -24,6 +27,9 @@ */ public abstract class AbstractAntTestHelper { + @Rule + public final TemporaryFolder tempFolder = new TemporaryFolder(); + @Rule public final BuildFileRule buildRule = new BuildFileRule(); @@ -41,11 +47,16 @@ public AbstractAntTestHelper() { } @Before - public void setUp() { + public void setUp() throws IOException { validatePostConstruct(); // initialize Ant buildRule.configureProject(pathToTestScript + separator + antTestScriptFilename); + // Each test case gets one temp file name, accessible with ${tmpfile} + final File newFile = tempFolder.newFile(); + newFile.delete(); // It shouldn't exist yet, but we want a unique name + buildRule.getProject().setProperty("tmpfile", newFile.getAbsolutePath()); + Project project = buildRule.getProject(); if (!project.getBaseDir().toString().endsWith(mvnWorkaround)) { // when running from maven, the path needs to be adapted... @@ -54,6 +65,17 @@ public void setUp() { } } + + /** + * Returns the current temporary file. Replaced by a fresh (inexistent) + * file before each test. + */ + public File currentTempFile() { + String tmpname = buildRule.getProject().getProperty("tmpfile"); + return tmpname == null ? null : new File(tmpname); + } + + private void validatePostConstruct() { if (pathToTestScript == null || "".equals(pathToTestScript) || antTestScriptFilename == null || "".equals(antTestScriptFilename) || mvnWorkaround == null || "".equals(mvnWorkaround)) { @@ -66,7 +88,18 @@ public void executeTarget(String target) { } public void assertOutputContaining(String text) { - Assert.assertTrue("Expected to find \"" + text + "\" in the output, but it's missing", - buildRule.getOutput().contains(text)); + assertContains(buildRule.getOutput(), text); + } + + + public void assertContains(String text, String toFind) { + Assert.assertTrue("Expected to find \"" + toFind + "\", but it's missing", + text.contains(toFind)); + } + + + public void assertDoesntContain(String text, String toFind) { + Assert.assertTrue("Expected no occurrence of \"" + toFind + "\", but found at least one", + !text.contains(toFind)); } } diff --git a/pmd-test/src/main/java/net/sourceforge/pmd/test/lang/DummyLanguageModule.java b/pmd-test/src/main/java/net/sourceforge/pmd/test/lang/DummyLanguageModule.java index 4527f14b754..59f71e22257 100644 --- a/pmd-test/src/main/java/net/sourceforge/pmd/test/lang/DummyLanguageModule.java +++ b/pmd-test/src/main/java/net/sourceforge/pmd/test/lang/DummyLanguageModule.java @@ -113,9 +113,10 @@ protected RuleViolation createRuleViolation(Rule rule, RuleContext ruleContext, protected RuleViolation createRuleViolation(Rule rule, RuleContext ruleContext, Node node, String message, int beginLine, int endLine) { ParametricRuleViolation<Node> rv = new ParametricRuleViolation<Node>(rule, ruleContext, node, message) { - { - // just for testing variable expansion - this.packageName = "foo"; + @Override + public String getPackageName() { + this.packageName = "foo"; // just for testing variable expansion + return super.getPackageName(); } }; rv.setLines(beginLine, endLine); diff --git a/pmd-test/src/main/java/net/sourceforge/pmd/test/lang/ast/DummyNode.java b/pmd-test/src/main/java/net/sourceforge/pmd/test/lang/ast/DummyNode.java index c6f67a7d9ba..1f7a0d4e822 100644 --- a/pmd-test/src/main/java/net/sourceforge/pmd/test/lang/ast/DummyNode.java +++ b/pmd-test/src/main/java/net/sourceforge/pmd/test/lang/ast/DummyNode.java @@ -11,8 +11,15 @@ public DummyNode(int id) { super(id); } + @Deprecated @Override public String toString() { return "dummyNode"; } + + + @Override + public String getXPathNodeName() { + return "dummyNode"; + } } diff --git a/pmd-test/src/main/java/net/sourceforge/pmd/testframework/PMDTestRunner.java b/pmd-test/src/main/java/net/sourceforge/pmd/testframework/PMDTestRunner.java index 9b4f12f82a0..0f9b6d310f5 100644 --- a/pmd-test/src/main/java/net/sourceforge/pmd/testframework/PMDTestRunner.java +++ b/pmd-test/src/main/java/net/sourceforge/pmd/testframework/PMDTestRunner.java @@ -23,13 +23,13 @@ * A JUnit Runner, that combines the default {@link JUnit4} * and our custom {@link RuleTestRunner}. * It allows to selectively execute single test cases (it is {@link Filterable}). - * + * * <p>Note: Since we actually run two runners one after another, the static {@code BeforeClass} * and {@Code AfterClass} methods will be executed twice and the test class will be instantiated twice, too.</p> * * <p>In order to use it, you'll need to subclass {@link SimpleAggregatorTst} and * annotate your test class with RunWith:</p> - * + * * <pre> * @RunWith(PMDTestRunner.class) * public class MyRuleSetTest extends SimpleAggregatorTst { @@ -78,7 +78,9 @@ public void filter(Filter filter) throws NoTestsRemainException { public Description getDescription() { Description description = Description.createSuiteDescription(klass); description.addChild(createChildrenDescriptions(ruleTests, "Rule Tests")); - description.addChild(createChildrenDescriptions(unitTests, "Unit Tests")); + if (ruleTests.hasUnitTests()) { + description.addChild(createChildrenDescriptions(unitTests, "Unit Tests")); + } return description; } diff --git a/pmd-test/src/main/java/net/sourceforge/pmd/testframework/PmdRuleTst.java b/pmd-test/src/main/java/net/sourceforge/pmd/testframework/PmdRuleTst.java new file mode 100644 index 00000000000..ff6b3d349ba --- /dev/null +++ b/pmd-test/src/main/java/net/sourceforge/pmd/testframework/PmdRuleTst.java @@ -0,0 +1,32 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.testframework; + +import java.util.Collections; +import java.util.List; + +import org.junit.runner.RunWith; + +import net.sourceforge.pmd.Rule; + +@RunWith(PMDTestRunner.class) +public class PmdRuleTst extends RuleTst { + + @Override + protected void setUp() { + // empty, nothing to do + } + + @Override + protected List<Rule> getRules() { + String[] packages = getClass().getPackage().getName().split("\\."); + String categoryName = packages[packages.length - 1]; + String language = packages[packages.length - 3]; + String rulesetXml = "category/" + language + "/" + categoryName + ".xml"; + + Rule rule = findRule(rulesetXml, getClass().getSimpleName().replaceFirst("Test$", "")); + return Collections.singletonList(rule); + } +} diff --git a/pmd-test/src/main/java/net/sourceforge/pmd/testframework/RuleTestRunner.java b/pmd-test/src/main/java/net/sourceforge/pmd/testframework/RuleTestRunner.java index f3bb45aa375..7420567b0b9 100644 --- a/pmd-test/src/main/java/net/sourceforge/pmd/testframework/RuleTestRunner.java +++ b/pmd-test/src/main/java/net/sourceforge/pmd/testframework/RuleTestRunner.java @@ -35,14 +35,15 @@ */ public class RuleTestRunner extends ParentRunner<TestDescriptor> { private ConcurrentHashMap<TestDescriptor, Description> testDescriptions = new ConcurrentHashMap<>(); - private final SimpleAggregatorTst instance; + private final RuleTst instance; - public RuleTestRunner(Class<? extends SimpleAggregatorTst> testClass) throws InitializationError { + public RuleTestRunner(Class<? extends RuleTst> testClass) throws InitializationError { super(testClass); instance = createTestClass(); instance.setUp(); } + @Override protected Description describeChild(TestDescriptor testCase) { Description description = testDescriptions.get(testCase); if (description == null) { @@ -84,9 +85,9 @@ public int compare(Rule o1, Rule o2) { return tests; } - private SimpleAggregatorTst createTestClass() throws InitializationError { + private RuleTst createTestClass() throws InitializationError { try { - return (SimpleAggregatorTst) getTestClass().getOnlyConstructor().newInstance(); + return (RuleTst) getTestClass().getOnlyConstructor().newInstance(); } catch (Exception e) { throw new InitializationError(e); } @@ -105,7 +106,7 @@ protected void runChild(TestDescriptor testCase, RunNotifier notifier) { /** * Executes the actual test case. If there are Before, After, or TestRules present, * they are executed accordingly. - * + * * @param testCase the PMD rule test case to be executed * @return a single statement which includes any rules, if present. */ @@ -116,18 +117,18 @@ public void evaluate() throws Throwable { instance.runTest(testCase); } }; - statement = withBefores(testCase, statement); - statement = withAfters(testCase, statement); + statement = withBefores(statement); + statement = withAfters(statement); statement = withRules(testCase, statement); return statement; } - private Statement withBefores(final TestDescriptor testCase, Statement statement) { + private Statement withBefores(Statement statement) { List<FrameworkMethod> befores = getTestClass().getAnnotatedMethods(Before.class); return befores.isEmpty() ? statement : new RunBefores(statement, befores, instance); } - private Statement withAfters(final TestDescriptor testCase, Statement statement) { + private Statement withAfters(Statement statement) { List<FrameworkMethod> afters = getTestClass().getAnnotatedMethods(After.class); return afters.isEmpty() ? statement : new RunAfters(statement, afters, instance); } diff --git a/pmd-test/src/main/java/net/sourceforge/pmd/testframework/RuleTst.java b/pmd-test/src/main/java/net/sourceforge/pmd/testframework/RuleTst.java index 4b18938d6c6..83030f7fe55 100644 --- a/pmd-test/src/main/java/net/sourceforge/pmd/testframework/RuleTst.java +++ b/pmd-test/src/main/java/net/sourceforge/pmd/testframework/RuleTst.java @@ -12,6 +12,7 @@ import java.io.StringReader; import java.io.StringWriter; import java.util.ArrayList; +import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Map; @@ -24,7 +25,6 @@ import javax.xml.validation.Schema; import javax.xml.validation.SchemaFactory; -import org.apache.commons.io.IOUtils; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; @@ -85,6 +85,14 @@ public void error(SAXParseException exception) throws SAXException { } } + protected void setUp() { + // This method is intended to be overridden by subclasses. + } + + protected List<Rule> getRules() { + return Collections.emptyList(); + } + /** * Find a rule in a certain ruleset by name */ @@ -138,9 +146,9 @@ public void runTest(TestDescriptor test) { report = processUsingStringReader(test, rule); res = report.size(); - } catch (Throwable t) { - t.printStackTrace(); - throw new RuntimeException('"' + test.getDescription() + "\" failed", t); + } catch (Exception e) { + e.printStackTrace(); + throw new RuntimeException('"' + test.getDescription() + "\" failed", e); } if (test.getNumberOfProblemsExpected() != res) { printReport(test, report); @@ -163,7 +171,7 @@ public void runTest(TestDescriptor test) { * * @param rule The rule to reinitialise * - * @return The rule once it has be reinitialised + * @return The rule once it has been reinitialised */ protected Rule reinitializeRule(Rule rule) { return findRule(rule.getRuleSetName(), rule.getName()); @@ -202,12 +210,12 @@ private void assertLineNumbers(Report report, TestDescriptor test) { } List<Integer> expected = test.getExpectedLineNumbers(); - if (report.getViolationTree().size() != expected.size()) { + if (report.size() != expected.size()) { throw new RuntimeException("Test setup error: number of execpted line numbers doesn't match " + "number of violations for test case '" + test.getDescription() + "'"); } - Iterator<RuleViolation> it = report.getViolationTree().iterator(); + Iterator<RuleViolation> it = report.iterator(); int index = 0; while (it.hasNext()) { RuleViolation violation = it.next(); @@ -260,6 +268,7 @@ public void runTestFromString(String code, Rule rule, Report report, LanguageVer try { PMD p = new PMD(); p.getConfiguration().setDefaultLanguageVersion(languageVersion); + p.getConfiguration().setIgnoreIncrementalAnalysis(true); if (isUseAuxClasspath) { // configure the "auxclasspath" option for unit testing p.getConfiguration().prependClasspath("."); @@ -318,13 +327,12 @@ public TestDescriptor[] extractTestsFromXml(Rule rule, String testsFileName) { */ public TestDescriptor[] extractTestsFromXml(Rule rule, String testsFileName, String baseDirectory) { String testXmlFileName = baseDirectory + testsFileName + ".xml"; - InputStream inputStream = getClass().getResourceAsStream(testXmlFileName); - if (inputStream == null) { - throw new RuntimeException("Couldn't find " + testXmlFileName); - } Document doc; - try { + try (InputStream inputStream = getClass().getResourceAsStream(testXmlFileName)) { + if (inputStream == null) { + throw new RuntimeException("Couldn't find " + testXmlFileName); + } doc = documentBuilder.parse(inputStream); } catch (FactoryConfigurationError fce) { throw new RuntimeException("Couldn't parse " + testXmlFileName + ", due to: " + fce, fce); @@ -332,13 +340,38 @@ public TestDescriptor[] extractTestsFromXml(Rule rule, String testsFileName, Str throw new RuntimeException("Couldn't parse " + testXmlFileName + ", due to: " + ioe, ioe); } catch (SAXException se) { throw new RuntimeException("Couldn't parse " + testXmlFileName + ", due to: " + se, se); - } finally { - IOUtils.closeQuietly(inputStream); } return parseTests(rule, doc); } + /** + * Run a set of tests defined in an XML test-data file for a rule. The file + * should be ./xml/RuleName.xml relative to the test-class. The format is + * defined in test-data.xsd. + */ + public void runTests(Rule rule) { + runTests(extractTestsFromXml(rule)); + } + + /** + * Run a set of tests defined in a XML test-data file. The file should be + * ./xml/[testsFileName].xml relative to the test-class. The format is + * defined in test-data.xsd. + */ + public void runTests(Rule rule, String testsFileName) { + runTests(extractTestsFromXml(rule, testsFileName)); + } + + /** + * Run a set of tests of a certain sourceType. + */ + public void runTests(TestDescriptor[] tests) { + for (int i = 0; i < tests.length; i++) { + runTest(tests[i]); + } + } + private TestDescriptor[] parseTests(Rule rule, Document doc) { Element root = doc.getDocumentElement(); NodeList testCodes = root.getElementsByTagName("test-code"); diff --git a/pmd-test/src/main/java/net/sourceforge/pmd/testframework/SimpleAggregatorTst.java b/pmd-test/src/main/java/net/sourceforge/pmd/testframework/SimpleAggregatorTst.java index d4187f7f199..a463c5a6de8 100644 --- a/pmd-test/src/main/java/net/sourceforge/pmd/testframework/SimpleAggregatorTst.java +++ b/pmd-test/src/main/java/net/sourceforge/pmd/testframework/SimpleAggregatorTst.java @@ -12,7 +12,12 @@ import net.sourceforge.pmd.Rule; /** - * Standard methods for (simple) testcases. + * Simple setup for a rule unit test, + * capable of testing multiple rules. + * + * <p>Override {@link #setUp()} to register the + * rules, that should be tested via calls to + * {@link #addRule(String, String)}. */ @RunWith(PMDTestRunner.class) public abstract class SimpleAggregatorTst extends RuleTst { @@ -20,40 +25,16 @@ public abstract class SimpleAggregatorTst extends RuleTst { private List<Rule> rules = new ArrayList<>(); /** - * Configure the rule tests to be executed. Implement this method in - * subclasses by calling adRule. + * Configure the rule tests to be executed. Override this method in + * subclasses by calling addRule, e.g. + * + * <pre>addRule("path/myruleset.xml", "CustomRule");</pre> * * @see #addRule(String, String) */ + @Override protected void setUp() { - // empty, to be overridden - } - - /** - * Run a set of tests defined in an XML test-data file for a rule. The file - * should be ./xml/RuleName.xml relative to the test-class. The format is - * defined in test-data.xsd. - */ - public void runTests(Rule rule) { - runTests(extractTestsFromXml(rule)); - } - - /** - * Run a set of tests defined in a XML test-data file. The file should be - * ./xml/[testsFileName].xml relative to the test-class. The format is - * defined in test-data.xsd. - */ - public void runTests(Rule rule, String testsFileName) { - runTests(extractTestsFromXml(rule, testsFileName)); - } - - /** - * Run a set of tests of a certain sourceType. - */ - public void runTests(TestDescriptor[] tests) { - for (int i = 0; i < tests.length; i++) { - runTest(tests[i]); - } + // empty, to be overridden. } /** @@ -69,6 +50,7 @@ protected void addRule(String ruleSet, String ruleName) { * * @return all configured rules. */ + @Override protected List<Rule> getRules() { return rules; } diff --git a/pmd-test/src/main/java/net/sourceforge/pmd/testframework/TestDescriptor.java b/pmd-test/src/main/java/net/sourceforge/pmd/testframework/TestDescriptor.java index 9cb36fe334d..dcb4ba6fd49 100644 --- a/pmd-test/src/main/java/net/sourceforge/pmd/testframework/TestDescriptor.java +++ b/pmd-test/src/main/java/net/sourceforge/pmd/testframework/TestDescriptor.java @@ -32,9 +32,8 @@ public class TestDescriptor { private boolean useAuxClasspath = true; private int numberInDocument = -1; - // Empty descriptor added to please mvn surefire plugin public TestDescriptor() { - + // Empty default descriptor added to please mvn surefire plugin } public TestDescriptor(String code, String description, int numberOfProblemsExpected, Rule rule) { @@ -128,6 +127,7 @@ public static boolean inRegressionTestMode() { inRegressionMode = Boolean.parseBoolean(property); } } catch (IllegalArgumentException e) { + throw new RuntimeException("Invalid system property 'pmd.regress'", e); } return inRegressionMode; diff --git a/pmd-test/src/main/resources/rulesets/dummy/basic.xml b/pmd-test/src/main/resources/rulesets/dummy/basic.xml index 0a141832a22..db1bd0c98a5 100644 --- a/pmd-test/src/main/resources/rulesets/dummy/basic.xml +++ b/pmd-test/src/main/resources/rulesets/dummy/basic.xml @@ -1,6 +1,6 @@ <?xml version="1.0"?> <ruleset name="Test Ruleset" xmlns="http://pmd.sourceforge.net/ruleset/2.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 http://pmd.sourceforge.net/ruleset_2_0_0.xsd"> + xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd"> <description> Ruleset used by test RuleSetReferenceIdTest diff --git a/pmd-test/src/test/java/net/sourceforge/pmd/testframework/RuleTstTest.java b/pmd-test/src/test/java/net/sourceforge/pmd/testframework/RuleTstTest.java index dd9992ec856..364823dbec6 100644 --- a/pmd-test/src/test/java/net/sourceforge/pmd/testframework/RuleTstTest.java +++ b/pmd-test/src/test/java/net/sourceforge/pmd/testframework/RuleTstTest.java @@ -12,35 +12,47 @@ import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; +import java.util.Arrays; + +import org.junit.Assert; import org.junit.Test; +import org.mockito.Mockito; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; import net.sourceforge.pmd.Report; import net.sourceforge.pmd.Rule; import net.sourceforge.pmd.RuleContext; +import net.sourceforge.pmd.RuleViolation; import net.sourceforge.pmd.lang.LanguageRegistry; import net.sourceforge.pmd.lang.LanguageVersion; +import net.sourceforge.pmd.lang.ast.Node; +import net.sourceforge.pmd.lang.rule.ParametricRuleViolation; +import net.sourceforge.pmd.test.lang.ast.DummyNode; public class RuleTstTest { + private LanguageVersion dummyLanguage = LanguageRegistry.findLanguageByTerseName("dummy").getDefaultVersion(); + + private Rule rule = mock(Rule.class); + + private RuleTst ruleTester = new RuleTst() { + }; @Test public void shouldCallStartAndEnd() { - RuleTst ruleTester = new RuleTst() { - }; - LanguageVersion languageVersion = LanguageRegistry.findLanguageByTerseName("dummy").getDefaultVersion(); Report report = new Report(); - Rule rule = mock(Rule.class); - when(rule.getLanguage()).thenReturn(languageVersion.getLanguage()); + when(rule.getLanguage()).thenReturn(dummyLanguage.getLanguage()); when(rule.getName()).thenReturn("test rule"); - ruleTester.runTestFromString("the code", rule, report, languageVersion, false); + ruleTester.runTestFromString("the code", rule, report, dummyLanguage, false); verify(rule).start(any(RuleContext.class)); verify(rule).end(any(RuleContext.class)); verify(rule, times(5)).getLanguage(); - verify(rule).usesDFA(); - verify(rule).usesTypeResolution(); - verify(rule).usesMetrics(); - verify(rule, times(2)).usesRuleChain(); + verify(rule).isDfa(); + verify(rule).isTypeResolution(); + verify(rule).isMultifile(); + verify(rule, times(2)).isRuleChain(); verify(rule).getMinimumLanguageVersion(); verify(rule).getMaximumLanguageVersion(); verify(rule).apply(anyList(), any(RuleContext.class)); @@ -48,4 +60,40 @@ public void shouldCallStartAndEnd() { verify(rule).getPropertiesByPropertyDescriptor(); verifyNoMoreInteractions(rule); } + + @Test + public void shouldAssertLinenumbersSorted() { + when(rule.getLanguage()).thenReturn(dummyLanguage.getLanguage()); + when(rule.getName()).thenReturn("test rule"); + Mockito.doAnswer(new Answer<Void>() { + private RuleViolation createViolation(RuleContext context, int beginLine, String message) { + DummyNode node = new DummyNode(1); + node.testingOnlySetBeginLine(beginLine); + node.testingOnlySetBeginColumn(1); + ParametricRuleViolation<Node> violation = new ParametricRuleViolation<Node>(rule, context, node, message); + return violation; + } + + @Override + public Void answer(InvocationOnMock invocation) throws Throwable { + RuleContext context = invocation.getArgumentAt(1, RuleContext.class); + // the violations are reported out of order + context.getReport().addRuleViolation(createViolation(context, 15, "first reported violation")); + context.getReport().addRuleViolation(createViolation(context, 5, "second reported violation")); + return null; + } + }).when(rule).apply(Mockito.anyList(), Mockito.any(RuleContext.class)); + + TestDescriptor testDescriptor = new TestDescriptor("the code", "sample test", 2, rule, dummyLanguage); + testDescriptor.setReinitializeRule(false); + testDescriptor.setExpectedLineNumbers(Arrays.asList(5, 15)); + + try { + ruleTester.runTest(testDescriptor); + // there should be no assertion failures + // expected line numbers and actual line numbers match + } catch (AssertionError assertionError) { + Assert.fail(assertionError.toString()); + } + } } diff --git a/pmd-ui/pom.xml b/pmd-ui/pom.xml index 5ba13b6c0fb..89b7862a28a 100644 --- a/pmd-ui/pom.xml +++ b/pmd-ui/pom.xml @@ -7,24 +7,189 @@ <parent> <groupId>net.sourceforge.pmd</groupId> <artifactId>pmd</artifactId> - <version>6.0.0-SNAPSHOT</version> + <version>6.10.0-SNAPSHOT</version> </parent> <properties> - <config.basedir>${basedir}/../pmd-core</config.basedir> - <java.version>1.8</java.version> + <java.version>8</java.version> + <!-- Workaround for https://youtrack.jetbrains.com/issue/IDEA-188690 --> + <maven.compiler.source>1.${java.version}</maven.compiler.source> + <maven.compiler.target>1.${java.version}</maven.compiler.target> + + <openjfx.version>11</openjfx.version> </properties> + <build> + + <resources> + <resource> + <directory>${project.basedir}/src/main/resources/</directory> + <excludes> + <exclude>**/*.less</exclude> + </excludes> + </resource> + </resources> + + <plugins> + <!-- Compiles Less files into CSS files. Currently compatible with Less 1.7.0 --> + <!-- If need be, we can probably update the Less version by using the lessJs + configuration property and having a custom less.js file around. --> + <!-- See https://github.com/marceloverdijk/lesscss-maven-plugin --> + <plugin> + <groupId>org.lesscss</groupId> + <artifactId>lesscss-maven-plugin</artifactId> + <version>1.7.0.1.1</version> + <configuration> + <sourceDirectory>${project.basedir}/src/main/resources/net/sourceforge/pmd/util/fxdesigner/less</sourceDirectory> + <outputDirectory>${project.build.outputDirectory}/net/sourceforge/pmd/util/fxdesigner/css</outputDirectory> + </configuration> + <executions> + <execution> + <goals> + <goal>compile</goal> + </goals> + </execution> + </executions> + </plugin> + + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-pmd-plugin</artifactId> + <configuration> + <rulesets> + <ruleset>/net/sourceforge/pmd/pmd-ui-dogfood-config.xml</ruleset> + </rulesets> + </configuration> + </plugin> + </plugins> + </build> + + <dependencies> <dependency> <groupId>net.sourceforge.pmd</groupId> <artifactId>pmd-core</artifactId> + </dependency> + + <!-- OpenJFX dependencies are provided. OpenJFX needs to be installed separately. --> + <dependency> + <groupId>org.openjfx</groupId> + <artifactId>javafx-base</artifactId> + <version>${openjfx.version}</version> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>org.openjfx</groupId> + <artifactId>javafx-controls</artifactId> + <version>${openjfx.version}</version> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>org.openjfx</groupId> + <artifactId>javafx-fxml</artifactId> + <version>${openjfx.version}</version> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>org.openjfx</groupId> + <artifactId>javafx-graphics</artifactId> + <version>${openjfx.version}</version> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>org.openjfx</groupId> + <artifactId>javafx-media</artifactId> + <version>${openjfx.version}</version> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>org.openjfx</groupId> + <artifactId>javafx-web</artifactId> + <version>${openjfx.version}</version> + <scope>provided</scope> + </dependency> + + <dependency> + <groupId>org.fxmisc.richtext</groupId> + <artifactId>richtextfx</artifactId> + <version>0.9.0</version> + </dependency> + <dependency> + <groupId>org.controlsfx</groupId> + <artifactId>controlsfx</artifactId> + <version>8.40.13</version> + </dependency> + <dependency> + <groupId>commons-beanutils</groupId> + <artifactId>commons-beanutils-core</artifactId> + <version>1.8.3</version> + </dependency> + + <!-- Icon packs --> + <dependency> + <groupId>org.kordamp.ikonli</groupId> + <artifactId>ikonli-javafx</artifactId> + <version>2.3.0</version> + </dependency> + <dependency> + <groupId>org.kordamp.ikonli</groupId> + <artifactId>ikonli-fontawesome-pack</artifactId> + <version>2.3.0</version> + </dependency> + + <!-- PMD dependencies --> + <dependency> + <groupId>net.sourceforge.pmd</groupId> + <artifactId>pmd-apex</artifactId> <version>${project.version}</version> + <optional>true</optional> </dependency> <dependency> <groupId>net.sourceforge.pmd</groupId> <artifactId>pmd-java</artifactId> <version>${project.version}</version> + <optional>true</optional> + </dependency> + <dependency> + <groupId>net.sourceforge.pmd</groupId> + <artifactId>pmd-javascript</artifactId> + <version>${project.version}</version> + <optional>true</optional> + </dependency> + <dependency> + <groupId>net.sourceforge.pmd</groupId> + <artifactId>pmd-jsp</artifactId> + <version>${project.version}</version> + <optional>true</optional> + </dependency> + <dependency> + <groupId>net.sourceforge.pmd</groupId> + <artifactId>pmd-plsql</artifactId> + <version>${project.version}</version> + <optional>true</optional> + </dependency> + <dependency> + <groupId>net.sourceforge.pmd</groupId> + <artifactId>pmd-visualforce</artifactId> + <version>${project.version}</version> + <optional>true</optional> + </dependency> + <dependency> + <groupId>net.sourceforge.pmd</groupId> + <artifactId>pmd-vm</artifactId> + <version>${project.version}</version> + <optional>true</optional> + </dependency> + <dependency> + <groupId>net.sourceforge.pmd</groupId> + <artifactId>pmd-xml</artifactId> + <version>${project.version}</version> + <optional>true</optional> + </dependency> + <dependency> + <groupId>junit</groupId> + <artifactId>junit</artifactId> + <scope>test</scope> </dependency> </dependencies> </project> diff --git a/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/Designer.java b/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/Designer.java new file mode 100644 index 00000000000..4237ad740a5 --- /dev/null +++ b/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/Designer.java @@ -0,0 +1,117 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.util.fxdesigner; + +import java.io.IOException; +import java.util.Arrays; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; + +import net.sourceforge.pmd.PMDVersion; +import net.sourceforge.pmd.util.fxdesigner.util.DesignerUtil; + +import javafx.application.Application; +import javafx.collections.ObservableList; +import javafx.fxml.FXMLLoader; +import javafx.scene.Parent; +import javafx.scene.Scene; +import javafx.scene.image.Image; +import javafx.stage.Stage; + + +/** + * Main class for the designer. + * + * @author Clément Fournier + * @since 6.0.0 + */ +public class Designer extends Application { + + private void parseParameters(Parameters params) { + List<String> raw = params.getRaw(); + if (!raw.contains("-v") + && !raw.contains("--verbose")) { + // error output is disabled by default + + System.err.close(); + } + + } + + + @Override + public void start(Stage stage) throws IOException { + parseParameters(getParameters()); + + + FXMLLoader loader + = new FXMLLoader(DesignerUtil.getFxml("designer.fxml")); + + DesignerRoot owner = new DesignerRoot(stage); + MainDesignerController mainController = new MainDesignerController(owner); + + NodeInfoPanelController nodeInfoPanelController = new NodeInfoPanelController(mainController); + XPathPanelController xpathPanelController = new XPathPanelController(owner, mainController); + SourceEditorController sourceEditorController = new SourceEditorController(owner, mainController); + EventLogController eventLogController = new EventLogController(owner, mainController); + + loader.setControllerFactory(type -> { + if (type == MainDesignerController.class) { + return mainController; + } else if (type == NodeInfoPanelController.class) { + return nodeInfoPanelController; + } else if (type == XPathPanelController.class) { + return xpathPanelController; + } else if (type == SourceEditorController.class) { + return sourceEditorController; + } else if (type == EventLogController.class) { + return eventLogController; + } else { + // default behavior for controllerFactory: + try { + return type.newInstance(); + } catch (Exception exc) { + exc.printStackTrace(); + throw new RuntimeException(exc); // fatal, just bail... + } + } + }); + + stage.setOnCloseRequest(e -> mainController.shutdown()); + + Parent root = loader.load(); + Scene scene = new Scene(root); + + stage.setTitle("PMD Rule Designer (v " + PMDVersion.VERSION + ')'); + setIcons(stage); + + stage.setScene(scene); + stage.show(); + } + + + private void setIcons(Stage primaryStage) { + ObservableList<Image> icons = primaryStage.getIcons(); + final String dirPrefix = "icons/app/"; + List<String> imageNames = Arrays.asList("designer_logo.jpeg"); + + // TODO make more icon sizes + + List<Image> images = imageNames.stream() + .map(s -> dirPrefix + s) + .map(s -> getClass().getResourceAsStream(s)) + .filter(Objects::nonNull) + .map(Image::new) + .collect(Collectors.toList()); + + icons.addAll(images); + } + + + public static void main(String[] args) { + launch(args); + } +} diff --git a/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/DesignerRoot.java b/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/DesignerRoot.java new file mode 100644 index 00000000000..7f1bbad2d39 --- /dev/null +++ b/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/DesignerRoot.java @@ -0,0 +1,49 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.util.fxdesigner; + +import net.sourceforge.pmd.util.fxdesigner.model.EventLogger; + +import javafx.stage.Stage; + + +/** + * Interface for the singleton of the app. + * + * @author Clément Fournier + * @since 6.0.0 + */ +public class DesignerRoot { + + + private final Stage mainStage; + private final EventLogger logger = new EventLogger(); + + + public DesignerRoot(Stage mainStage) { + this.mainStage = mainStage; + } + + + /** + * Gets the logger of the application. + * + * @return The logger + */ + public EventLogger getLogger() { + return logger; + } + + + /** + * Gets the main stage of the application. + * + * @return The main stage + */ + public Stage getMainStage() { + return mainStage; + } + +} diff --git a/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/DesignerStarter.java b/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/DesignerStarter.java new file mode 100644 index 00000000000..f73279029f6 --- /dev/null +++ b/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/DesignerStarter.java @@ -0,0 +1,33 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.util.fxdesigner; + +import javax.swing.JOptionPane; + +public final class DesignerStarter { + private static final String MISSING_JAVAFX = "You seem to be missing the JavaFX runtime. Please install JavaFX on your system and try again."; + + private DesignerStarter() { + } + + private static boolean isJavaFxAvailable() { + try { + DesignerStarter.class.getClassLoader().loadClass("javafx.application.Application"); + return true; + } catch (ClassNotFoundException | LinkageError e) { + return false; + } + } + + public static void main(String[] args) { + if (!isJavaFxAvailable()) { + System.err.println(MISSING_JAVAFX); + JOptionPane.showMessageDialog(null, MISSING_JAVAFX); + System.exit(1); + } + + Designer.main(args); + } +} diff --git a/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/EventLogController.java b/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/EventLogController.java new file mode 100644 index 00000000000..60a0172639a --- /dev/null +++ b/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/EventLogController.java @@ -0,0 +1,165 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.util.fxdesigner; + +import java.net.URL; +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.time.Duration; +import java.util.Collections; +import java.util.Date; +import java.util.List; +import java.util.Objects; +import java.util.ResourceBundle; + +import org.reactfx.EventStream; +import org.reactfx.EventStreams; +import org.reactfx.value.Var; + +import net.sourceforge.pmd.lang.ast.Node; +import net.sourceforge.pmd.util.fxdesigner.model.LogEntry; +import net.sourceforge.pmd.util.fxdesigner.model.LogEntry.Category; +import net.sourceforge.pmd.util.fxdesigner.util.DesignerUtil; + +import javafx.beans.property.SimpleObjectProperty; +import javafx.fxml.FXML; +import javafx.fxml.Initializable; +import javafx.scene.control.TableCell; +import javafx.scene.control.TableColumn; +import javafx.scene.control.TableColumn.SortType; +import javafx.scene.control.TableView; +import javafx.scene.control.TextArea; +import javafx.scene.control.cell.PropertyValueFactory; + + +/** + * @author Clément Fournier + * @since 6.0.0 + */ +public class EventLogController implements Initializable { + + /** + * Exceptions from XPath evaluation or parsing are never emitted + * within less than that time interval to keep them from flooding the tableview. + */ + private static final Duration PARSE_EXCEPTION_DELAY = Duration.ofMillis(3000); + + private final DesignerRoot designerRoot; + private final MainDesignerController mediator; + + @FXML + private TableView<LogEntry> eventLogTableView; + @FXML + private TableColumn<LogEntry, Date> logDateColumn; + @FXML + private TableColumn<LogEntry, Category> logCategoryColumn; + @FXML + private TableColumn<LogEntry, String> logMessageColumn; + @FXML + private TextArea logDetailsTextArea; + + private Var<List<Node>> selectedErrorNodes = Var.newSimpleVar(Collections.emptyList()); + + + public EventLogController(DesignerRoot owner, MainDesignerController mediator) { + this.designerRoot = owner; + this.mediator = mediator; + } + + + @Override + public void initialize(URL location, ResourceBundle resources) { + logCategoryColumn.setCellValueFactory(new PropertyValueFactory<>("category")); + logMessageColumn.setCellValueFactory(new PropertyValueFactory<>("message")); + final DateFormat dateFormat = new SimpleDateFormat("HH:mm:ss"); + logDateColumn.setCellValueFactory(entry -> new SimpleObjectProperty<>(entry.getValue().getTimestamp())); + logDateColumn.setCellFactory(column -> new TableCell<LogEntry, Date>() { + @Override + protected void updateItem(Date item, boolean empty) { + super.updateItem(item, empty); + if (item == null || empty) { + setText(null); + setGraphic(null); + } else { + setText(dateFormat.format(item)); + } + } + }); + + EventStream<LogEntry> onlyParseException = designerRoot.getLogger().getLog() + .filter(x -> x.getCategory() == Category.PARSE_EXCEPTION) + .successionEnds(PARSE_EXCEPTION_DELAY); + + EventStream<LogEntry> onlyXPathException = designerRoot.getLogger().getLog() + .filter(x -> x.getCategory() == Category.XPATH_EVALUATION_EXCEPTION) + .successionEnds(PARSE_EXCEPTION_DELAY); + + EventStream<LogEntry> otherExceptions = designerRoot.getLogger().getLog() + .filter(x -> x.getCategory() != Category.PARSE_EXCEPTION) + .filter(y -> y.getCategory() != Category.XPATH_EVALUATION_EXCEPTION); + + EventStreams.merge(onlyParseException, otherExceptions, onlyXPathException) + .subscribe(t -> eventLogTableView.getItems().add(t)); + + eventLogTableView.getSelectionModel() + .selectedItemProperty() + .addListener((obs, oldVal, newVal) -> onExceptionSelectionChanges(oldVal, newVal)); + + EventStreams.combine(EventStreams.changesOf(eventLogTableView.focusedProperty()), + EventStreams.changesOf(selectedErrorNodes)); + + EventStreams.valuesOf(eventLogTableView.focusedProperty()) + .successionEnds(Duration.ofMillis(100)) + .subscribe(b -> { + if (b) { + mediator.handleSelectedNodeInError(selectedErrorNodes.getValue()); + } else { + mediator.resetSelectedErrorNodes(); + } + }); + + selectedErrorNodes.values().subscribe(mediator::handleSelectedNodeInError); + + eventLogTableView.resizeColumn(logMessageColumn, -1); + + logMessageColumn.prefWidthProperty() + .bind(eventLogTableView.widthProperty() + .subtract(logCategoryColumn.getPrefWidth()) + .subtract(logDateColumn.getPrefWidth()) + .subtract(2)); // makes it work + logDateColumn.setSortType(SortType.DESCENDING); + + } + + + private void handleSelectedEntry(LogEntry entry) { + if (entry == null) { + selectedErrorNodes.setValue(Collections.emptyList()); + return; + } + switch (entry.getCategory()) { + case OTHER: + break; + case PARSE_EXCEPTION: + // TODO + break; + case TYPERESOLUTION_EXCEPTION: + case SYMBOL_FACADE_EXCEPTION: + DesignerUtil.stackTraceToXPath(entry.getThrown()).map(mediator::runXPathQuery).ifPresent(selectedErrorNodes::setValue); + break; + default: + break; + } + } + + + private void onExceptionSelectionChanges(LogEntry oldVal, LogEntry newVal) { + logDetailsTextArea.setText(newVal == null ? "" : newVal.getStackTrace()); + + if (!Objects.equals(newVal, oldVal)) { + handleSelectedEntry(newVal); + } + } +} diff --git a/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/MainDesignerController.java b/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/MainDesignerController.java new file mode 100644 index 00000000000..dca8575c5aa --- /dev/null +++ b/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/MainDesignerController.java @@ -0,0 +1,502 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.util.fxdesigner; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Optional; +import java.util.ResourceBundle; +import java.util.Stack; +import java.util.stream.Collectors; + +import org.apache.commons.io.IOUtils; +import org.reactfx.value.Val; + +import net.sourceforge.pmd.lang.LanguageVersion; +import net.sourceforge.pmd.lang.ast.Node; +import net.sourceforge.pmd.lang.symboltable.NameDeclaration; +import net.sourceforge.pmd.lang.symboltable.NameOccurrence; +import net.sourceforge.pmd.util.fxdesigner.model.XPathEvaluationException; +import net.sourceforge.pmd.util.fxdesigner.util.DesignerUtil; +import net.sourceforge.pmd.util.fxdesigner.util.LimitedSizeStack; +import net.sourceforge.pmd.util.fxdesigner.util.TextAwareNodeWrapper; +import net.sourceforge.pmd.util.fxdesigner.util.beans.SettingsOwner; +import net.sourceforge.pmd.util.fxdesigner.util.beans.SettingsPersistenceUtil; +import net.sourceforge.pmd.util.fxdesigner.util.beans.SettingsPersistenceUtil.PersistentProperty; + +import javafx.animation.KeyFrame; +import javafx.animation.KeyValue; +import javafx.animation.Timeline; +import javafx.application.Platform; +import javafx.beans.property.DoubleProperty; +import javafx.fxml.FXML; +import javafx.fxml.Initializable; +import javafx.scene.control.Alert; +import javafx.scene.control.Alert.AlertType; +import javafx.scene.control.ChoiceBox; +import javafx.scene.control.CustomMenuItem; +import javafx.scene.control.Label; +import javafx.scene.control.Menu; +import javafx.scene.control.MenuItem; +import javafx.scene.control.ScrollPane; +import javafx.scene.control.SeparatorMenuItem; +import javafx.scene.control.SplitPane; +import javafx.scene.control.Tab; +import javafx.scene.control.TabPane; +import javafx.scene.control.TextArea; +import javafx.scene.control.ToggleButton; +import javafx.scene.control.Tooltip; +import javafx.stage.FileChooser; +import javafx.util.Duration; + + +/** + * Main controller of the app. Mediator for subdivisions of the UI. + * + * @author Clément Fournier + * @see NodeInfoPanelController + * @see SourceEditorController + * @see EventLogController + * @see XPathPanelController + * @since 6.0.0 + */ +@SuppressWarnings("PMD.UnusedPrivateField") +public class MainDesignerController implements Initializable, SettingsOwner { + + /** + * Callback to the owner. + */ + private final DesignerRoot designerRoot; + + + /* Menu bar */ + @FXML + private MenuItem setupAuxclasspathMenuItem; + @FXML + private MenuItem openFileMenuItem; + @FXML + private MenuItem licenseMenuItem; + @FXML + private Menu openRecentMenu; + @FXML + private MenuItem exportToTestCodeMenuItem; + @FXML + private MenuItem exportXPathMenuItem; + @FXML + private Menu fileMenu; + /* Center toolbar */ + @FXML + private ChoiceBox<LanguageVersion> languageChoiceBox; + @FXML + private ChoiceBox<String> xpathVersionChoiceBox; + @FXML + private ToggleButton bottomTabsToggle; + /* Bottom panel */ + @FXML + private TabPane bottomTabPane; + @FXML + private Tab eventLogTab; + @FXML + private Tab xpathEditorTab; + @FXML + private SplitPane mainHorizontalSplitPane; + /* Children */ + @FXML + private NodeInfoPanelController nodeInfoPanelController; + @FXML + private XPathPanelController xpathPanelController; + @FXML + private SourceEditorController sourceEditorController; + @FXML + private EventLogController eventLogPanelController; + + // Other fields + private Stack<File> recentFiles = new LimitedSizeStack<>(5); + // Properties + private Val<LanguageVersion> languageVersion = Val.constant(DesignerUtil.defaultLanguageVersion()); + private Val<String> xpathVersion = Val.constant(DesignerUtil.defaultXPathVersion()); + + + public MainDesignerController(DesignerRoot owner) { + this.designerRoot = owner; + } + + + @Override + public void initialize(URL location, ResourceBundle resources) { + try { + SettingsPersistenceUtil.restoreProperties(this, DesignerUtil.getSettingsFile()); + } catch (Exception e) { + // shouldn't prevent the app from opening + // in case the file is corrupted, it will be overwritten on shutdown + e.printStackTrace(); + } + + initializeLanguageVersionMenu(); + initializeViewAnimation(); + + xpathPanelController.initialiseVersionChoiceBox(xpathVersionChoiceBox); + + languageVersion = Val.wrap(languageChoiceBox.getSelectionModel().selectedItemProperty()); + DesignerUtil.rewire(sourceEditorController.languageVersionProperty(), + languageVersion, this::setLanguageVersion); + + xpathVersion = Val.wrap(xpathVersionChoiceBox.getSelectionModel().selectedItemProperty()); + DesignerUtil.rewire(xpathPanelController.xpathVersionProperty(), + xpathVersion, this::setXpathVersion); + + + licenseMenuItem.setOnAction(e -> showLicensePopup()); + openFileMenuItem.setOnAction(e -> onOpenFileClicked()); + openRecentMenu.setOnAction(e -> updateRecentFilesMenu()); + openRecentMenu.setOnShowing(e -> updateRecentFilesMenu()); + fileMenu.setOnShowing(e -> onFileMenuShowing()); + exportXPathMenuItem.setOnAction(e -> { + try { + xpathPanelController.showExportXPathToRuleWizard(); + } catch (IOException e1) { + e1.printStackTrace(); + } + }); + + setupAuxclasspathMenuItem.setOnAction(e -> sourceEditorController.showAuxclasspathSetupPopup(designerRoot)); + + Platform.runLater(this::updateRecentFilesMenu); + Platform.runLater(this::refreshAST); // initial refreshing + + Platform.runLater(() -> sourceEditorController.moveCaret(0, 0)); + Platform.runLater(() -> { // fixes choicebox bad rendering on first opening + languageChoiceBox.show(); + languageChoiceBox.hide(); + }); + } + + + private void initializeLanguageVersionMenu() { + List<LanguageVersion> supported = DesignerUtil.getSupportedLanguageVersions(); + supported.sort(LanguageVersion::compareTo); + languageChoiceBox.getItems().addAll(supported); + + languageChoiceBox.setConverter(DesignerUtil.languageVersionStringConverter()); + + languageChoiceBox.getSelectionModel().select(DesignerUtil.defaultLanguageVersion()); + languageChoiceBox.show(); + } + + + private void initializeViewAnimation() { + + // gets captured in the closure + final double defaultMainHorizontalSplitPaneDividerPosition + = mainHorizontalSplitPane.getDividerPositions()[0]; + + + // show/ hide bottom pane + bottomTabsToggle.selectedProperty().addListener((observable, wasExpanded, isNowExpanded) -> { + KeyValue keyValue = null; + DoubleProperty divPosition = mainHorizontalSplitPane.getDividers().get(0).positionProperty(); + if (wasExpanded && !isNowExpanded) { + keyValue = new KeyValue(divPosition, 1); + } else if (!wasExpanded && isNowExpanded) { + keyValue = new KeyValue(divPosition, defaultMainHorizontalSplitPaneDividerPosition); + } + + if (keyValue != null) { + Timeline timeline = new Timeline(new KeyFrame(Duration.millis(200), keyValue)); + timeline.play(); + } + }); + } + + + public void shutdown() { + try { + SettingsPersistenceUtil.persistProperties(this, DesignerUtil.getSettingsFile()); + } catch (IOException ioe) { + // nevermind + ioe.printStackTrace(); + } + } + + + /** + * Attempts to refresh the AST with the up-to-date source, + * also updating XPath results. + */ + public void refreshAST() { + Optional<Node> root = sourceEditorController.refreshAST(); + + if (root.isPresent()) { + xpathPanelController.evaluateXPath(root.get(), getLanguageVersion()); + } else { + xpathPanelController.invalidateResults(true); + } + } + + + /** + * Refreshes the XPath results if the compilation unit is valid. + * Otherwise does nothing. + */ + public void refreshXPathResults() { + sourceEditorController.getCompilationUnit().ifPresent(root -> xpathPanelController.evaluateXPath(root, getLanguageVersion())); + } + + + /** + * Returns a wrapper around the given node that gives access + * to its textual representation in the editor area. + */ + public TextAwareNodeWrapper wrapNode(Node node) { + return sourceEditorController.wrapNode(node); + } + + + /** + * Executed when the user selects a node in a treeView or listView. + */ + public void onNodeItemSelected(Node selectedValue) { + nodeInfoPanelController.displayInfo(selectedValue); + sourceEditorController.setFocusNode(selectedValue); + sourceEditorController.focusNodeInTreeView(selectedValue); + } + + + public void onNameDeclarationSelected(NameDeclaration declaration) { + sourceEditorController.clearNameOccurences(); + + List<NameOccurrence> occurrences = declaration.getNode().getScope().getDeclarations().get(declaration); + + if (occurrences != null) { + sourceEditorController.highlightNameOccurrences(occurrences); + } + + Platform.runLater(() -> onNodeItemSelected(declaration.getNode())); + } + + /** + * Runs an XPath (2.0) query on the current AST. + * Performs no side effects. + * + * @param query the query + * @return the matched nodes + * @throws XPathEvaluationException if the query fails + */ + public List<Node> runXPathQuery(String query) throws XPathEvaluationException { + return sourceEditorController.getCompilationUnit() + .map(n -> xpathPanelController.runXPathQuery(n, getLanguageVersion(), query)) + .orElseGet(Collections::emptyList); + } + + + /** + * Handles nodes that potentially caused an error. + * This can for example highlight nodes on the + * editor. Effects can be reset with {@link #resetSelectedErrorNodes()}. + * + * @param n Node + */ + public void handleSelectedNodeInError(List<Node> n) { + resetSelectedErrorNodes(); + sourceEditorController.highlightErrorNodes(n); + } + + public void resetSelectedErrorNodes() { + sourceEditorController.clearErrorNodes(); + } + + public void resetXPathResults() { + sourceEditorController.clearXPathHighlight(); + } + + /** Replaces previously highlighted XPath results with the given nodes. */ + public void highlightXPathResults(List<Node> nodes) { + sourceEditorController.highlightXPathResults(nodes); + } + + private void showLicensePopup() { + Alert licenseAlert = new Alert(AlertType.INFORMATION); + licenseAlert.setWidth(500); + licenseAlert.setHeaderText("License"); + + ScrollPane scroll = new ScrollPane(); + try { + scroll.setContent(new TextArea(IOUtils.toString(getClass().getResourceAsStream("LICENSE"), + StandardCharsets.UTF_8))); + } catch (IOException e) { + e.printStackTrace(); + } + + licenseAlert.getDialogPane().setContent(scroll); + licenseAlert.showAndWait(); + } + + + private void onFileMenuShowing() { + openRecentMenu.setDisable(recentFiles.isEmpty()); + } + + + private void onOpenFileClicked() { + FileChooser chooser = new FileChooser(); + chooser.setTitle("Load source from file"); + File file = chooser.showOpenDialog(designerRoot.getMainStage()); + loadSourceFromFile(file); + } + + private void loadSourceFromFile(File file) { + if (file != null) { + try { + String source = IOUtils.toString(new FileInputStream(file), StandardCharsets.UTF_8); + sourceEditorController.setText(source); + LanguageVersion guess = DesignerUtil.getLanguageVersionFromExtension(file.getName()); + if (guess != null) { // guess the language from the extension + languageChoiceBox.getSelectionModel().select(guess); + refreshAST(); + } + + recentFiles.push(file); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + + + private void updateRecentFilesMenu() { + List<MenuItem> items = new ArrayList<>(); + List<File> filesToClear = new ArrayList<>(); + + for (final File f : recentFiles) { + if (f.exists() && f.isFile()) { + CustomMenuItem item = new CustomMenuItem(new Label(f.getName())); + item.setOnAction(e -> loadSourceFromFile(f)); + item.setMnemonicParsing(false); + Tooltip.install(item.getContent(), new Tooltip(f.getAbsolutePath())); + items.add(item); + } else { + filesToClear.add(f); + } + } + recentFiles.removeAll(filesToClear); + + if (items.isEmpty()) { + openRecentMenu.setDisable(true); + return; + } + + Collections.reverse(items); + + items.add(new SeparatorMenuItem()); + MenuItem clearItem = new MenuItem(); + clearItem.setText("Clear menu"); + clearItem.setOnAction(e -> { + recentFiles.clear(); + openRecentMenu.setDisable(true); + }); + items.add(clearItem); + + openRecentMenu.getItems().setAll(items); + } + + + public void invalidateAst() { + nodeInfoPanelController.invalidateInfo(); + xpathPanelController.invalidateResults(false); + sourceEditorController.setFocusNode(null); + } + + + public LanguageVersion getLanguageVersion() { + return languageVersion.getValue(); + } + + + public void setLanguageVersion(LanguageVersion version) { + if (languageChoiceBox.getItems().contains(version)) { + languageChoiceBox.getSelectionModel().select(version); + } + } + + + public Val<LanguageVersion> languageVersionProperty() { + return languageVersion; + } + + + public String getXpathVersion() { + return xpathVersion.getValue(); + } + + + public void setXpathVersion(String version) { + if (xpathVersionChoiceBox.getItems().contains(version)) { + xpathVersionChoiceBox.getSelectionModel().select(version); + } + } + + + public Val<String> xpathVersionProperty() { + return xpathVersion; + } + + + @PersistentProperty + public String getRecentFiles() { + return recentFiles.stream().map(File::getAbsolutePath).collect(Collectors.joining(File.pathSeparator)); + } + + + public void setRecentFiles(String files) { + Arrays.stream(files.split(File.pathSeparator)).map(File::new).forEach(recentFiles::push); + } + + + @PersistentProperty + public boolean isMaximized() { + return designerRoot.getMainStage().isMaximized(); + } + + + public void setMaximized(boolean b) { + designerRoot.getMainStage().setMaximized(!b); // trigger change listener anyway + designerRoot.getMainStage().setMaximized(b); + } + + + @PersistentProperty + public boolean isBottomTabExpanded() { + return bottomTabsToggle.isSelected(); + } + + + public void setBottomTabExpanded(boolean b) { + bottomTabsToggle.setSelected(b); + } + + + @PersistentProperty + public int getBottomTabIndex() { + return bottomTabPane.getSelectionModel().getSelectedIndex(); + } + + + public void setBottomTabIndex(int i) { + bottomTabPane.getSelectionModel().select(i); + } + + + @Override + public List<SettingsOwner> getChildrenSettingsNodes() { + return Arrays.asList(xpathPanelController, sourceEditorController); + } +} diff --git a/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/NodeInfoPanelController.java b/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/NodeInfoPanelController.java new file mode 100644 index 00000000000..2acbff2c9a2 --- /dev/null +++ b/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/NodeInfoPanelController.java @@ -0,0 +1,155 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.util.fxdesigner; + +import java.net.URL; +import java.util.Collections; +import java.util.Iterator; +import java.util.Objects; +import java.util.ResourceBundle; + +import org.reactfx.EventStreams; + +import net.sourceforge.pmd.lang.ast.Node; +import net.sourceforge.pmd.lang.ast.xpath.Attribute; +import net.sourceforge.pmd.lang.java.ast.TypeNode; +import net.sourceforge.pmd.lang.symboltable.NameDeclaration; +import net.sourceforge.pmd.util.fxdesigner.model.MetricEvaluator; +import net.sourceforge.pmd.util.fxdesigner.model.MetricResult; +import net.sourceforge.pmd.util.fxdesigner.util.controls.ScopeHierarchyTreeCell; +import net.sourceforge.pmd.util.fxdesigner.util.controls.ScopeHierarchyTreeItem; + +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import javafx.fxml.FXML; +import javafx.fxml.Initializable; +import javafx.scene.control.Label; +import javafx.scene.control.ListView; +import javafx.scene.control.Tab; +import javafx.scene.control.TabPane; +import javafx.scene.control.TreeItem; +import javafx.scene.control.TreeView; + + +/** + * Controller of the node info panel (left). + * + * @author Clément Fournier + * @since 6.0.0 + */ +@SuppressWarnings("PMD.UnusedPrivateField") +public class NodeInfoPanelController implements Initializable { + + private final MainDesignerController parent; + + @FXML + private TabPane nodeInfoTabPane; + @FXML + private Tab xpathAttributesTab; + @FXML + private ListView<String> xpathAttributesListView; + @FXML + private Tab metricResultsTab; + @FXML + private ListView<MetricResult> metricResultsListView; + @FXML + private Label metricsTitleLabel; + @FXML + private TreeView<Object> scopeHierarchyTreeView; + private MetricEvaluator metricEvaluator = new MetricEvaluator(); + private Node selectedNode; + + public NodeInfoPanelController(MainDesignerController mainController) { + parent = mainController; + } + + + @Override + public void initialize(URL location, ResourceBundle resources) { + EventStreams.valuesOf(scopeHierarchyTreeView.getSelectionModel().selectedItemProperty()) + .filter(Objects::nonNull) + .map(TreeItem::getValue) + .filterMap(o -> o instanceof NameDeclaration, o -> (NameDeclaration) o) + .subscribe(parent::onNameDeclarationSelected); + + scopeHierarchyTreeView.setCellFactory(view -> new ScopeHierarchyTreeCell()); + } + + + /** + * Displays info about a node. + * + * @param node Node to inspect + */ + public void displayInfo(Node node) { + Objects.requireNonNull(node, "Node cannot be null"); + + if (node.equals(selectedNode)) { + return; + } + + ObservableList<String> atts = getAttributes(node); + xpathAttributesListView.setItems(atts); + + ObservableList<MetricResult> metrics = evaluateAllMetrics(node); + metricResultsListView.setItems(metrics); + notifyMetricsAvailable(metrics.stream() + .map(MetricResult::getValue) + .filter(result -> !result.isNaN()) + .count()); + + // TODO maybe a better way would be to build all the scope TreeItem hierarchy once + // and only expand the ascendants of the node. + TreeItem<Object> rootScope = ScopeHierarchyTreeItem.buildAscendantHierarchy(node); + scopeHierarchyTreeView.setRoot(rootScope); + } + + + /** + * Invalidates the info being displayed. + */ + public void invalidateInfo() { + metricResultsListView.setItems(FXCollections.emptyObservableList()); + xpathAttributesListView.setItems(FXCollections.emptyObservableList()); + scopeHierarchyTreeView.setRoot(null); + } + + + private void notifyMetricsAvailable(long numMetrics) { + metricResultsTab.setText("Metrics\t(" + (numMetrics == 0 ? "none" : numMetrics) + ")"); + metricsTitleLabel.setText("Metrics\t(" + (numMetrics == 0 ? "none" : numMetrics) + " available)"); + metricResultsTab.setDisable(numMetrics == 0); + } + + + private ObservableList<MetricResult> evaluateAllMetrics(Node n) { + try { + return FXCollections.observableArrayList(metricEvaluator.evaluateAllMetrics(n)); + } catch (UnsupportedOperationException e) { + return FXCollections.emptyObservableList(); + } + } + + + /** + * Gets the XPath attributes of the node for display within a listview. + */ + private static ObservableList<String> getAttributes(Node node) { + ObservableList<String> result = FXCollections.observableArrayList(); + Iterator<Attribute> attributeAxisIterator = node.getXPathAttributesIterator(); + while (attributeAxisIterator.hasNext()) { + Attribute attribute = attributeAxisIterator.next(); + // TODO the display should be handled in a ListCell + result.add(attribute.getName() + " = " + + ((attribute.getValue() != null) ? attribute.getStringValue() : "null")); + } + + if (node instanceof TypeNode) { + result.add("typeIs() = " + ((TypeNode) node).getType()); + } + Collections.sort(result); + return result; + } +} diff --git a/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/SourceEditorController.java b/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/SourceEditorController.java new file mode 100644 index 00000000000..29ce74155e6 --- /dev/null +++ b/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/SourceEditorController.java @@ -0,0 +1,392 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.util.fxdesigner; + +import static java.util.Collections.emptyList; +import static java.util.Collections.singleton; + +import java.io.File; +import java.io.IOException; +import java.net.URL; +import java.time.Duration; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.Locale; +import java.util.Objects; +import java.util.Optional; +import java.util.ResourceBundle; +import java.util.function.IntFunction; +import java.util.stream.Collectors; + +import org.apache.commons.lang3.StringUtils; +import org.fxmisc.richtext.LineNumberFactory; +import org.reactfx.EventStreams; +import org.reactfx.value.Val; +import org.reactfx.value.Var; + +import net.sourceforge.pmd.lang.Language; +import net.sourceforge.pmd.lang.LanguageVersion; +import net.sourceforge.pmd.lang.ast.Node; +import net.sourceforge.pmd.lang.symboltable.NameOccurrence; +import net.sourceforge.pmd.util.ClasspathClassLoader; +import net.sourceforge.pmd.util.fxdesigner.model.ASTManager; +import net.sourceforge.pmd.util.fxdesigner.model.ParseAbortedException; +import net.sourceforge.pmd.util.fxdesigner.popups.AuxclasspathSetupController; +import net.sourceforge.pmd.util.fxdesigner.util.TextAwareNodeWrapper; +import net.sourceforge.pmd.util.fxdesigner.util.beans.SettingsOwner; +import net.sourceforge.pmd.util.fxdesigner.util.beans.SettingsPersistenceUtil.PersistentProperty; +import net.sourceforge.pmd.util.fxdesigner.util.codearea.AvailableSyntaxHighlighters; +import net.sourceforge.pmd.util.fxdesigner.util.codearea.HighlightLayerCodeArea; +import net.sourceforge.pmd.util.fxdesigner.util.codearea.HighlightLayerCodeArea.LayerId; +import net.sourceforge.pmd.util.fxdesigner.util.controls.ASTTreeCell; +import net.sourceforge.pmd.util.fxdesigner.util.controls.ASTTreeItem; +import net.sourceforge.pmd.util.fxdesigner.util.controls.TreeViewWrapper; + +import javafx.application.Platform; +import javafx.css.PseudoClass; +import javafx.fxml.FXML; +import javafx.fxml.Initializable; +import javafx.scene.control.Label; +import javafx.scene.control.SelectionModel; +import javafx.scene.control.TreeItem; +import javafx.scene.control.TreeView; + + +/** + * One editor, i.e. source editor and ast tree view. + * + * @author Clément Fournier + * @since 6.0.0 + */ +public class SourceEditorController implements Initializable, SettingsOwner { + + private static final Duration AST_REFRESH_DELAY = Duration.ofMillis(100); + + @FXML + private Label astTitleLabel; + @FXML + private TreeView<Node> astTreeView; + @FXML + private HighlightLayerCodeArea<StyleLayerIds> codeEditorArea; + + private ASTManager astManager; + private TreeViewWrapper<Node> treeViewWrapper; + + private final MainDesignerController parent; + + private Var<Node> currentFocusNode = Var.newSimpleVar(null); + private ASTTreeItem selectedTreeItem; + + private Var<List<File>> auxclasspathFiles = Var.newSimpleVar(emptyList()); + private final Val<ClassLoader> auxclasspathClassLoader = auxclasspathFiles.map(fileList -> { + try { + return new ClasspathClassLoader(fileList, SourceEditorController.class.getClassLoader()); + } catch (IOException e) { + e.printStackTrace(); + return SourceEditorController.class.getClassLoader(); + } + }); + + public SourceEditorController(DesignerRoot owner, MainDesignerController mainController) { + parent = mainController; + astManager = new ASTManager(owner); + + } + + @Override + public void initialize(URL location, ResourceBundle resources) { + + treeViewWrapper = new TreeViewWrapper<>(astTreeView); + astTreeView.setCellFactory(treeView -> new ASTTreeCell(parent)); + + languageVersionProperty().values() + .filterMap(Objects::nonNull, LanguageVersion::getLanguage) + .distinct() + .subscribe(this::updateSyntaxHighlighter); + + EventStreams.valuesOf(astTreeView.getSelectionModel().selectedItemProperty()) + .filterMap(Objects::nonNull, TreeItem::getValue) + .subscribe(parent::onNodeItemSelected); + + codeEditorArea.plainTextChanges() + .filter(t -> !t.isIdentity()) + .successionEnds(AST_REFRESH_DELAY) + // Refresh the AST anytime the text, classloader, or language version changes + .or(auxclasspathClassLoader.changes()) + .or(languageVersionProperty().changes()) + .subscribe(tick -> { + // Discard the AST if the language version has changed + tick.ifRight(c -> astTreeView.setRoot(null)); + Platform.runLater(parent::refreshAST); + }); + + codeEditorArea.setParagraphGraphicFactory(lineNumberFactory()); + } + + + private IntFunction<javafx.scene.Node> lineNumberFactory() { + IntFunction<javafx.scene.Node> base = LineNumberFactory.get(codeEditorArea); + Val<Integer> activePar = Val.wrap(codeEditorArea.currentParagraphProperty()); + + return idx -> { + + javafx.scene.Node label = base.apply(idx); + + activePar.conditionOnShowing(label) + .values() + .subscribe(p -> label.pseudoClassStateChanged(PseudoClass.getPseudoClass("has-caret"), idx == p)); + + // adds a pseudo class if part of the focus node appears on this line + currentFocusNode.conditionOnShowing(label) + .values() + .subscribe(n -> label.pseudoClassStateChanged(PseudoClass.getPseudoClass("is-focus-node"), + n != null && idx + 1 <= n.getEndLine() && idx + 1 >= n.getBeginLine())); + + return label; + }; + } + + + /** + * Refreshes the AST and returns the new compilation unit if the parse didn't fail. + */ + public Optional<Node> refreshAST() { + String source = getText(); + + if (StringUtils.isBlank(source)) { + astTreeView.setRoot(null); + return Optional.empty(); + } + + Optional<Node> current; + + try { + current = astManager.updateIfChanged(source, auxclasspathClassLoader.getValue()); + } catch (ParseAbortedException e) { + astTitleLabel.setText("Abstract syntax tree (error)"); + return Optional.empty(); + } + + current.ifPresent(this::setUpToDateCompilationUnit); + return current; + } + + + public void showAuxclasspathSetupPopup(DesignerRoot root) { + new AuxclasspathSetupController(root).show(root.getMainStage(), + auxclasspathFiles.getValue(), + auxclasspathFiles::setValue); + } + + private void setUpToDateCompilationUnit(Node node) { + parent.invalidateAst(); + astTitleLabel.setText("Abstract syntax tree"); + ASTTreeItem root = ASTTreeItem.getRoot(node); + astTreeView.setRoot(root); + } + + + private void updateSyntaxHighlighter(Language language) { + codeEditorArea.setSyntaxHighlighter(AvailableSyntaxHighlighters.getHighlighterForLanguage(language) + .orElse(null)); + } + + + /** Clears the name occurences. */ + public void clearErrorNodes() { + codeEditorArea.clearStyleLayer(StyleLayerIds.ERROR); + } + + + /** Clears the name occurences. */ + public void clearNameOccurences() { + codeEditorArea.clearStyleLayer(StyleLayerIds.ERROR); + } + + + /** Clears the highlighting of XPath results. */ + public void clearXPathHighlight() { + codeEditorArea.clearStyleLayer(StyleLayerIds.XPATH_RESULT); + } + + + /** + * Highlights the given node (or nothing if null). + * Removes highlighting on the previously highlighted node. + */ + public void setFocusNode(Node node) { + if (Objects.equals(node, currentFocusNode.getValue())) { + return; + } + + codeEditorArea.styleNodes(node == null ? emptyList() : singleton(node), StyleLayerIds.FOCUS, true); + + if (node != null) { + scrollEditorToNode(node); + } + + currentFocusNode.setValue(node); + } + + + /** Highlights xpath results (xpath highlight). */ + public void highlightXPathResults(Collection<? extends Node> nodes) { + codeEditorArea.styleNodes(nodes, StyleLayerIds.XPATH_RESULT, true); + } + + + /** Highlights name occurrences (secondary highlight). */ + public void highlightNameOccurrences(Collection<? extends NameOccurrence> occs) { + codeEditorArea.styleNodes(occs.stream().map(NameOccurrence::getLocation).collect(Collectors.toList()), StyleLayerIds.NAME_OCCURENCE, true); + } + + + /** Highlights nodes that are in error (secondary highlight). */ + public void highlightErrorNodes(Collection<? extends Node> nodes) { + codeEditorArea.styleNodes(nodes, StyleLayerIds.ERROR, true); + if (!nodes.isEmpty()) { + scrollEditorToNode(nodes.iterator().next()); + } + } + + + /** Scroll the editor to a node and makes it visible. */ + private void scrollEditorToNode(Node node) { + + codeEditorArea.moveTo(node.getBeginLine() - 1, 0); + + if (codeEditorArea.getVisibleParagraphs().size() < 1) { + return; + } + + int visibleLength = codeEditorArea.lastVisibleParToAllParIndex() - codeEditorArea.firstVisibleParToAllParIndex(); + + if (node.getEndLine() - node.getBeginLine() > visibleLength + || node.getBeginLine() < codeEditorArea.firstVisibleParToAllParIndex()) { + codeEditorArea.showParagraphAtTop(Math.max(node.getBeginLine() - 2, 0)); + } else if (node.getEndLine() > codeEditorArea.lastVisibleParToAllParIndex()) { + codeEditorArea.showParagraphAtBottom(Math.min(node.getEndLine(), codeEditorArea.getParagraphs().size())); + } + } + + + public void clearStyleLayers() { + codeEditorArea.clearStyleLayers(); + } + + + public void focusNodeInTreeView(Node node) { + SelectionModel<TreeItem<Node>> selectionModel = astTreeView.getSelectionModel(); + + // node is different from the old one + if (selectedTreeItem == null && node != null + || selectedTreeItem != null && !Objects.equals(node, selectedTreeItem.getValue())) { + ASTTreeItem found = ((ASTTreeItem) astTreeView.getRoot()).findItem(node); + if (found != null) { + selectionModel.select(found); + } + selectedTreeItem = found; + + astTreeView.getFocusModel().focus(selectionModel.getSelectedIndex()); + if (!treeViewWrapper.isIndexVisible(selectionModel.getSelectedIndex())) { + astTreeView.scrollTo(selectionModel.getSelectedIndex()); + } + } + } + + + /** Moves the caret to a position and makes the view follow it. */ + public void moveCaret(int line, int column) { + codeEditorArea.moveTo(line, column); + codeEditorArea.requestFollowCaret(); + } + + + public TextAwareNodeWrapper wrapNode(Node node) { + return codeEditorArea.wrapNode(node); + } + + @PersistentProperty + public LanguageVersion getLanguageVersion() { + return astManager.getLanguageVersion(); + } + + + public void setLanguageVersion(LanguageVersion version) { + astManager.setLanguageVersion(version); + } + + + public Var<LanguageVersion> languageVersionProperty() { + return astManager.languageVersionProperty(); + } + + + /** + * Returns the most up-to-date compilation unit, or empty if it can't be parsed. + */ + public Optional<Node> getCompilationUnit() { + return astManager.getCompilationUnit(); + } + + + @PersistentProperty + public String getText() { + return codeEditorArea.getText(); + } + + + public void setText(String expression) { + codeEditorArea.replaceText(expression); + } + + + public Val<String> textProperty() { + return Val.wrap(codeEditorArea.textProperty()); + } + + + @PersistentProperty + public String getAuxclasspathFiles() { + return auxclasspathFiles.getValue().stream().map(File::getAbsolutePath).collect(Collectors.joining(File.pathSeparator)); + } + + + public void setAuxclasspathFiles(String files) { + List<File> newVal = Arrays.stream(files.split(File.pathSeparator)).map(File::new).collect(Collectors.toList()); + auxclasspathFiles.setValue(newVal); + } + + + /** Style layers for the code area. */ + private enum StyleLayerIds implements LayerId { + // caution, the name of the constants are used as style classes + + /** For the currently selected node. */ + FOCUS, + /** For declaration usages. */ + NAME_OCCURENCE, + /** For nodes in error. */ + ERROR, + /** For xpath results. */ + XPATH_RESULT; + + private final String styleClass; // the id will be used as a style class + + + StyleLayerIds() { + this.styleClass = name().toLowerCase(Locale.ROOT).replace('_', '-') + "-highlight"; + } + + + /** focus-highlight, xpath-highlight, error-highlight, name-occurrence-highlight */ + @Override + public String getStyleClass() { + return styleClass; + } + } + +} diff --git a/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/XPathPanelController.java b/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/XPathPanelController.java new file mode 100644 index 00000000000..cadf8dea071 --- /dev/null +++ b/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/XPathPanelController.java @@ -0,0 +1,400 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.util.fxdesigner; + + +import java.io.IOException; +import java.net.URL; +import java.time.Duration; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Locale; +import java.util.Objects; +import java.util.ResourceBundle; +import java.util.stream.Collectors; + +import org.apache.commons.lang3.StringUtils; +import org.controlsfx.validation.ValidationSupport; +import org.controlsfx.validation.Validator; +import org.reactfx.EventStream; +import org.reactfx.EventStreams; +import org.reactfx.collection.LiveArrayList; +import org.reactfx.util.Tuples; +import org.reactfx.value.Val; +import org.reactfx.value.Var; + +import net.sourceforge.pmd.lang.LanguageVersion; +import net.sourceforge.pmd.lang.ast.Node; +import net.sourceforge.pmd.lang.rule.XPathRule; +import net.sourceforge.pmd.lang.rule.xpath.XPathRuleQuery; +import net.sourceforge.pmd.util.fxdesigner.model.LogEntry; +import net.sourceforge.pmd.util.fxdesigner.model.LogEntry.Category; +import net.sourceforge.pmd.util.fxdesigner.model.ObservableXPathRuleBuilder; +import net.sourceforge.pmd.util.fxdesigner.model.XPathEvaluationException; +import net.sourceforge.pmd.util.fxdesigner.model.XPathEvaluator; +import net.sourceforge.pmd.util.fxdesigner.model.XPathSuggestions; +import net.sourceforge.pmd.util.fxdesigner.popups.ExportXPathWizardController; +import net.sourceforge.pmd.util.fxdesigner.util.DesignerUtil; +import net.sourceforge.pmd.util.fxdesigner.util.TextAwareNodeWrapper; +import net.sourceforge.pmd.util.fxdesigner.util.beans.SettingsOwner; +import net.sourceforge.pmd.util.fxdesigner.util.beans.SettingsPersistenceUtil.PersistentProperty; +import net.sourceforge.pmd.util.fxdesigner.util.codearea.SyntaxHighlightingCodeArea; +import net.sourceforge.pmd.util.fxdesigner.util.codearea.syntaxhighlighting.XPathSyntaxHighlighter; +import net.sourceforge.pmd.util.fxdesigner.util.controls.ContextMenuWithNoArrows; +import net.sourceforge.pmd.util.fxdesigner.util.controls.PropertyTableView; +import net.sourceforge.pmd.util.fxdesigner.util.controls.XpathViolationListCell; + +import javafx.application.Platform; +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import javafx.fxml.FXML; +import javafx.fxml.FXMLLoader; +import javafx.fxml.Initializable; +import javafx.scene.Parent; +import javafx.scene.Scene; +import javafx.scene.control.Button; +import javafx.scene.control.ChoiceBox; +import javafx.scene.control.ContextMenu; +import javafx.scene.control.CustomMenuItem; +import javafx.scene.control.Label; +import javafx.scene.control.ListView; +import javafx.scene.control.MenuItem; +import javafx.scene.control.TextArea; +import javafx.scene.control.TitledPane; +import javafx.scene.input.KeyCode; +import javafx.scene.input.KeyEvent; +import javafx.scene.input.MouseButton; +import javafx.scene.input.MouseEvent; +import javafx.scene.paint.Color; +import javafx.scene.text.Text; +import javafx.scene.text.TextFlow; +import javafx.stage.Modality; +import javafx.stage.Stage; +import javafx.stage.StageStyle; + + +/** + * XPath panel controller. + * + * @author Clément Fournier + * @see ExportXPathWizardController + * @since 6.0.0 + */ +public class XPathPanelController implements Initializable, SettingsOwner { + + private static final Duration XPATH_REFRESH_DELAY = Duration.ofMillis(100); + private final DesignerRoot designerRoot; + private final MainDesignerController parent; + private final XPathEvaluator xpathEvaluator = new XPathEvaluator(); + private final ObservableXPathRuleBuilder ruleBuilder = new ObservableXPathRuleBuilder(); + + @FXML + private PropertyTableView propertyTableView; + @FXML + private SyntaxHighlightingCodeArea xpathExpressionArea; + @FXML + private TitledPane violationsTitledPane; + @FXML + private ListView<TextAwareNodeWrapper> xpathResultListView; + + // Actually a child of the main view toolbar, but this controller is responsible for it + @SuppressWarnings("PMD.SingularField") + private ChoiceBox<String> xpathVersionChoiceBox; + + + public XPathPanelController(DesignerRoot owner, MainDesignerController mainController) { + this.designerRoot = owner; + parent = mainController; + + getRuleBuilder().setClazz(XPathRule.class); + } + + + @Override + public void initialize(URL location, ResourceBundle resources) { + xpathExpressionArea.setSyntaxHighlighter(new XPathSyntaxHighlighter()); + + initGenerateXPathFromStackTrace(); + + xpathResultListView.setCellFactory(v -> new XpathViolationListCell()); + + EventStreams.valuesOf(xpathResultListView.getSelectionModel().selectedItemProperty()) + .conditionOn(xpathResultListView.focusedProperty()) + .filter(Objects::nonNull) + .map(TextAwareNodeWrapper::getNode) + .subscribe(parent::onNodeItemSelected); + + Platform.runLater(this::bindToParent); + + xpathExpressionArea.richChanges() + .filter(t -> !t.isIdentity()) + .successionEnds(XPATH_REFRESH_DELAY) + // Reevaluate XPath anytime the expression or the XPath version changes + .or(xpathVersionProperty().changes()) + .subscribe(tick -> parent.refreshXPathResults()); + + initialiseAutoCompletion(); + } + + private void initialiseAutoCompletion() { + + EventStream<Integer> changesEventStream = xpathExpressionArea.plainTextChanges() + .map(characterChanges -> { + if (characterChanges.getRemoved().length() > 0) { + return characterChanges.getRemovalEnd() - 1; + } + return characterChanges.getInsertionEnd(); + }); + + EventStream<Integer> keyCombo = EventStreams.eventsOf(xpathExpressionArea, KeyEvent.KEY_PRESSED) + .filter(key -> key.isControlDown() && key.getCode().equals(KeyCode.SPACE)) + .map(searchPoint -> xpathExpressionArea.getCaretPosition()); + + // captured in the closure + final ContextMenu autoCompletePopup = new ContextMenuWithNoArrows(); + autoCompletePopup.setId("xpathAutocomplete"); + autoCompletePopup.setHideOnEscape(true); + + EventStreams.merge(keyCombo, changesEventStream) + .map(searchPoint -> { + int indexOfSlash = xpathExpressionArea.getText().lastIndexOf("/", searchPoint - 1) + 1; + String input = xpathExpressionArea.getText(); + if (searchPoint > input.length()) { + searchPoint = input.length(); + } + input = input.substring(indexOfSlash, searchPoint); + + return Tuples.t(indexOfSlash, input); + }) + .filter(t -> StringUtils.isAlpha(t._2)) + .subscribe(s -> autoComplete(s._1, s._2, autoCompletePopup)); + + + } + + + private void autoComplete(int slashPosition, String input, ContextMenu autoCompletePopup) { + + XPathSuggestions xPathSuggestions = new XPathSuggestions(parent.getLanguageVersion().getLanguage()); + List<String> suggestions = xPathSuggestions.getXPathSuggestions(input.trim()); + + List<CustomMenuItem> resultToDisplay = new ArrayList<>(); + if (!suggestions.isEmpty()) { + + for (int i = 0; i < suggestions.size() && i < 5; i++) { + final String searchResult = suggestions.get(i); + + Label entryLabel = new Label(); + entryLabel.setGraphic(highlightXPathSuggestion(suggestions.get(i), input)); + entryLabel.setPrefHeight(5); + CustomMenuItem item = new CustomMenuItem(entryLabel, true); + resultToDisplay.add(item); + + item.setOnAction(e -> { + xpathExpressionArea.replaceText(slashPosition, slashPosition + input.length(), searchResult); + autoCompletePopup.hide(); + }); + } + } + autoCompletePopup.getItems().setAll(resultToDisplay); + + xpathExpressionArea.getCharacterBoundsOnScreen(slashPosition, slashPosition + input.length()) + .ifPresent(bounds -> autoCompletePopup.show(xpathExpressionArea, bounds.getMinX(), bounds.getMaxY())); + } + + + private static TextFlow highlightXPathSuggestion(String text, String match) { + int filterIndex = text.toLowerCase(Locale.ROOT).indexOf(match.toLowerCase(Locale.ROOT)); + + Text textBefore = new Text(text.substring(0, filterIndex)); + Text textAfter = new Text(text.substring(filterIndex + match.length())); + Text textFilter = new Text(text.substring(filterIndex, filterIndex + match.length())); //instead of "filter" to keep all "case sensitive" + textFilter.setFill(Color.ORANGE); + return new TextFlow(textBefore, textFilter, textAfter); + } + + + private void initGenerateXPathFromStackTrace() { + + ContextMenu menu = new ContextMenu(); + + MenuItem item = new MenuItem("Generate from stack trace..."); + item.setOnAction(e -> { + try { + Stage popup = new Stage(); + FXMLLoader loader = new FXMLLoader(DesignerUtil.getFxml("generate-xpath-from-stack-trace.fxml")); + Parent root = loader.load(); + Button button = (Button) loader.getNamespace().get("generateButton"); + TextArea area = (TextArea) loader.getNamespace().get("stackTraceArea"); + + ValidationSupport validation = new ValidationSupport(); + + validation.registerValidator(area, Validator.createEmptyValidator("The stack trace may not be empty")); + button.disableProperty().bind(validation.invalidProperty()); + + button.setOnAction(f -> { + DesignerUtil.stackTraceToXPath(area.getText()).ifPresent(xpathExpressionArea::replaceText); + popup.close(); + }); + + popup.setScene(new Scene(root)); + popup.initStyle(StageStyle.UTILITY); + popup.initModality(Modality.WINDOW_MODAL); + popup.initOwner(designerRoot.getMainStage()); + popup.show(); + } catch (IOException e1) { + throw new RuntimeException(e1); + } + }); + + menu.getItems().add(item); + + xpathExpressionArea.addEventHandler(MouseEvent.MOUSE_CLICKED, t -> { + if (t.getButton() == MouseButton.SECONDARY) { + menu.show(xpathExpressionArea, t.getScreenX(), t.getScreenY()); + } + }); + } + + + // Binds the underlying rule parameters to the parent UI, disconnecting it from the wizard if need be + private void bindToParent() { + DesignerUtil.rewire(getRuleBuilder().languageProperty(), + Val.map(parent.languageVersionProperty(), LanguageVersion::getLanguage)); + + DesignerUtil.rewire(getRuleBuilder().xpathVersionProperty(), parent.xpathVersionProperty()); + DesignerUtil.rewire(getRuleBuilder().xpathExpressionProperty(), xpathExpressionProperty()); + + DesignerUtil.rewire(getRuleBuilder().rulePropertiesProperty(), + propertyTableView.rulePropertiesProperty(), propertyTableView::setRuleProperties); + } + + + public void initialiseVersionChoiceBox(ChoiceBox<String> choiceBox) { + this.xpathVersionChoiceBox = choiceBox; + + ObservableList<String> versionItems = choiceBox.getItems(); + versionItems.add(XPathRuleQuery.XPATH_1_0); + versionItems.add(XPathRuleQuery.XPATH_1_0_COMPATIBILITY); + versionItems.add(XPathRuleQuery.XPATH_2_0); + + xpathVersionChoiceBox.getSelectionModel().select(XPathRuleQuery.XPATH_2_0); + choiceBox.setConverter(DesignerUtil.stringConverter(s -> "XPath " + s, s -> s.substring(6))); + } + + + /** + * Evaluate the contents of the XPath expression area + * on the given compilation unit. This updates the xpath + * result panel, and can log XPath exceptions to the + * event log panel. + * + * @param compilationUnit The AST root + * @param version The language version + */ + public void evaluateXPath(Node compilationUnit, LanguageVersion version) { + + try { + String xpath = getXpathExpression(); + if (StringUtils.isBlank(xpath)) { + invalidateResults(false); + return; + } + + ObservableList<Node> results + = FXCollections.observableArrayList(xpathEvaluator.evaluateQuery(compilationUnit, + version, + getXpathVersion(), + xpath, + ruleBuilder.getRuleProperties())); + xpathResultListView.setItems(results.stream().map(parent::wrapNode).collect(Collectors.toCollection(LiveArrayList::new))); + parent.highlightXPathResults(results); + violationsTitledPane.setText("Matched nodes\t(" + results.size() + ")"); + } catch (XPathEvaluationException e) { + invalidateResults(true); + designerRoot.getLogger().logEvent(new LogEntry(e, Category.XPATH_EVALUATION_EXCEPTION)); + } + + xpathResultListView.refresh(); + + + } + + + public List<Node> runXPathQuery(Node compilationUnit, LanguageVersion version, String query) throws XPathEvaluationException { + return xpathEvaluator.evaluateQuery(compilationUnit, version, XPathRuleQuery.XPATH_2_0, query, ruleBuilder.getRuleProperties()); + } + + + public void invalidateResults(boolean error) { + xpathResultListView.getItems().clear(); + parent.resetXPathResults(); + violationsTitledPane.setText("Matched nodes" + (error ? "\t(error)" : "")); + } + + + public void showExportXPathToRuleWizard() throws IOException { + ExportXPathWizardController wizard + = new ExportXPathWizardController(xpathExpressionProperty()); + + FXMLLoader loader = new FXMLLoader(getClass().getResource("fxml/xpath-export-wizard.fxml")); + loader.setController(wizard); + + final Stage dialog = new Stage(); + dialog.initOwner(designerRoot.getMainStage()); + dialog.setOnCloseRequest(e -> wizard.shutdown()); + dialog.initModality(Modality.WINDOW_MODAL); + + Parent root = loader.load(); + Scene scene = new Scene(root); + //stage.setTitle("PMD Rule Designer (v " + PMD.VERSION + ')'); + dialog.setScene(scene); + dialog.show(); + } + + + @PersistentProperty + public String getXpathExpression() { + return xpathExpressionArea.getText(); + } + + + public void setXpathExpression(String expression) { + xpathExpressionArea.replaceText(expression); + } + + + public Val<String> xpathExpressionProperty() { + return Val.wrap(xpathExpressionArea.textProperty()); + } + + + @PersistentProperty + public String getXpathVersion() { + return getRuleBuilder().getXpathVersion(); + } + + + public void setXpathVersion(String xpathVersion) { + getRuleBuilder().setXpathVersion(xpathVersion); + } + + + public Var<String> xpathVersionProperty() { + return getRuleBuilder().xpathVersionProperty(); + } + + + private ObservableXPathRuleBuilder getRuleBuilder() { + return ruleBuilder; + } + + + @Override + public List<SettingsOwner> getChildrenSettingsNodes() { + return Collections.singletonList(getRuleBuilder()); + } +} diff --git a/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/model/ASTManager.java b/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/model/ASTManager.java new file mode 100644 index 00000000000..29718e156da --- /dev/null +++ b/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/model/ASTManager.java @@ -0,0 +1,123 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.util.fxdesigner.model; + +import java.io.StringReader; +import java.util.Optional; + +import org.apache.commons.lang3.StringUtils; +import org.reactfx.value.Var; + +import net.sourceforge.pmd.lang.LanguageRegistry; +import net.sourceforge.pmd.lang.LanguageVersion; +import net.sourceforge.pmd.lang.LanguageVersionHandler; +import net.sourceforge.pmd.lang.Parser; +import net.sourceforge.pmd.lang.ast.Node; +import net.sourceforge.pmd.util.fxdesigner.DesignerRoot; +import net.sourceforge.pmd.util.fxdesigner.model.LogEntry.Category; + + +/** + * Main class of the model. Manages a compilation unit. + * + * @author Clément Fournier + * @since 6.0.0 + */ +public class ASTManager { + + private final DesignerRoot designerRoot; + + /** + * Last valid source that was compiled, corresponds to {@link #compilationUnit}. + */ + private String lastValidSource; + /** + * Last language version used. + */ + private LanguageVersion lastLanguageVersion; + /** + * Most up-to-date compilation unit. Is null if the current source cannot be parsed. + */ + private Var<Node> compilationUnit = Var.newSimpleVar(null); + /** + * Selected language version. + */ + private Var<LanguageVersion> languageVersion = Var.newSimpleVar(LanguageRegistry.getDefaultLanguage().getDefaultVersion()); + + + public ASTManager(DesignerRoot owner) { + this.designerRoot = owner; + } + + + public LanguageVersion getLanguageVersion() { + return languageVersion.getValue(); + } + + + public void setLanguageVersion(LanguageVersion version) { + languageVersion.setValue(version); + } + + + public Var<LanguageVersion> languageVersionProperty() { + return languageVersion; + } + + + public Optional<Node> getCompilationUnit() { + return compilationUnit.getOpt(); + } + + + /** + * Refreshes the compilation unit given the current parameters of the model. + * + * @param source Source code + * + * @throws ParseAbortedException if parsing fails and cannot recover + */ + public Optional<Node> updateIfChanged(String source, ClassLoader classLoader) throws ParseAbortedException { + if (compilationUnit.isPresent() + && getLanguageVersion().equals(lastLanguageVersion) + && StringUtils.equals(source, lastValidSource)) { + return getCompilationUnit(); + } + LanguageVersionHandler languageVersionHandler = getLanguageVersion().getLanguageVersionHandler(); + Parser parser = languageVersionHandler.getParser(languageVersionHandler.getDefaultParserOptions()); + + Node node; + try { + node = parser.parse(null, new StringReader(source)); + } catch (Exception e) { + designerRoot.getLogger().logEvent(new LogEntry(e, Category.PARSE_EXCEPTION)); + compilationUnit.setValue(null); + throw new ParseAbortedException(e); + } + try { + languageVersionHandler.getSymbolFacade().start(node); + } catch (Exception e) { + designerRoot.getLogger().logEvent(new LogEntry(e, Category.SYMBOL_FACADE_EXCEPTION)); + } + try { + languageVersionHandler.getQualifiedNameResolutionFacade(classLoader).start(node); + } catch (Exception e) { + designerRoot.getLogger().logEvent(new LogEntry(e, Category.QUALIFIED_NAME_RESOLUTION_EXCEPTION)); + } + + try { + languageVersionHandler.getTypeResolutionFacade(classLoader).start(node); + } catch (Exception e) { + designerRoot.getLogger().logEvent(new LogEntry(e, Category.TYPERESOLUTION_EXCEPTION)); + } + + compilationUnit.setValue(node); + lastValidSource = source; + lastLanguageVersion = getLanguageVersion(); + return getCompilationUnit(); + } + + +} diff --git a/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/model/EventLogger.java b/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/model/EventLogger.java new file mode 100644 index 00000000000..e0e34f4b5d8 --- /dev/null +++ b/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/model/EventLogger.java @@ -0,0 +1,33 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.util.fxdesigner.model; + +import java.util.Objects; + +import org.reactfx.EventSource; +import org.reactfx.EventStream; + +/** + * Logs events. + * + * @author Clément Fournier + * @since 6.0.0 + */ +public class EventLogger { + + private final EventSource<LogEntry> latestEvent = new EventSource<>(); + + public void logEvent(LogEntry event) { + latestEvent.push(event); + } + + /** + * Returns a stream that emits an event each time an exception is logged by some + * part of the application. + */ + public EventStream<LogEntry> getLog() { + return latestEvent.filter(Objects::nonNull); + } +} diff --git a/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/model/LogEntry.java b/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/model/LogEntry.java new file mode 100644 index 00000000000..7d129630bf9 --- /dev/null +++ b/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/model/LogEntry.java @@ -0,0 +1,79 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.util.fxdesigner.model; + +import java.util.Date; + +import org.apache.commons.lang3.exception.ExceptionUtils; + +/** + * Log entry of an {@link EventLogger}. + * + * @author Clément Fournier + * @since 6.0.0 + */ +public class LogEntry { + + + private final Throwable throwable; + private final Category category; + private final Date timestamp; + + + public LogEntry(Throwable thrown, Category cat) { + this.throwable = thrown; + this.category = cat; + timestamp = new Date(); + } + + + public Throwable getThrown() { + return throwable; + } + + public String getMessage() { + return throwable.getMessage(); + } + + + public Category getCategory() { + return category; + } + + + public String getStackTrace() { + return ExceptionUtils.getStackTrace(throwable); + } + + + public Date getTimestamp() { + return timestamp; + } + + + public enum Category { + PARSE_EXCEPTION("Parse exception"), + TYPERESOLUTION_EXCEPTION("Type resolution exception"), + QUALIFIED_NAME_RESOLUTION_EXCEPTION("Qualified name resolution exception"), + SYMBOL_FACADE_EXCEPTION("Symbol façade exception"), + XPATH_EVALUATION_EXCEPTION("XPath evaluation exception"), + OTHER("Other"); + + public final String name; + + + Category(String name) { + this.name = name; + } + + + @Override + public String toString() { + return name; + } + } + + +} diff --git a/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/model/MetricEvaluator.java b/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/model/MetricEvaluator.java new file mode 100644 index 00000000000..d590f97ddda --- /dev/null +++ b/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/model/MetricEvaluator.java @@ -0,0 +1,92 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.util.fxdesigner.model; + +import java.util.ArrayList; +import java.util.List; + +import net.sourceforge.pmd.lang.apex.ast.ASTMethod; +import net.sourceforge.pmd.lang.apex.ast.ASTUserClass; +import net.sourceforge.pmd.lang.apex.metrics.ApexMetrics; +import net.sourceforge.pmd.lang.apex.metrics.api.ApexClassMetricKey; +import net.sourceforge.pmd.lang.apex.metrics.api.ApexOperationMetricKey; +import net.sourceforge.pmd.lang.ast.Node; +import net.sourceforge.pmd.lang.java.ast.ASTAnyTypeDeclaration; +import net.sourceforge.pmd.lang.java.ast.ASTMethodOrConstructorDeclaration; +import net.sourceforge.pmd.lang.java.metrics.JavaMetrics; +import net.sourceforge.pmd.lang.java.metrics.api.JavaClassMetricKey; +import net.sourceforge.pmd.lang.java.metrics.api.JavaOperationMetricKey; + +/** + * Evaluates metrics. + * + * @author Clément Fournier + * @since 6.0.0 + */ +public class MetricEvaluator { + + /** + * Evaluates all available metrics and returns a list of results. + * + * @param node Node + * + * @return List of all metric results (metric key + result), including NaN results + * + * @throws UnsupportedOperationException If no metrics are available for this node + */ + public List<MetricResult> evaluateAllMetrics(Node node) throws UnsupportedOperationException { + if (ASTAnyTypeDeclaration.class.isInstance(node)) { + return evaluateAllMetrics((ASTAnyTypeDeclaration) node); + } else if (ASTMethodOrConstructorDeclaration.class.isInstance(node)) { + return evaluateAllMetrics((ASTMethodOrConstructorDeclaration) node); + } else if (ASTMethod.class.isInstance(node)) { + return evaluateAllMetrics((ASTMethod) node); + } else if (ASTUserClass.class.isInstance(node)) { + return evaluateAllMetrics((ASTUserClass) node); + } + throw new UnsupportedOperationException("That language does not support metrics"); + } + + + private List<MetricResult> evaluateAllMetrics(ASTMethodOrConstructorDeclaration node) { + List<MetricResult> metricResults = new ArrayList<>(); + for (JavaOperationMetricKey key : JavaOperationMetricKey.values()) { + metricResults.add(new MetricResult(key, JavaMetrics.get(key, node))); + } + + return metricResults; + } + + + private List<MetricResult> evaluateAllMetrics(ASTAnyTypeDeclaration node) { + List<MetricResult> metricResults = new ArrayList<>(); + for (JavaClassMetricKey key : JavaClassMetricKey.values()) { + metricResults.add(new MetricResult(key, JavaMetrics.get(key, node))); + } + + return metricResults; + } + + + private List<MetricResult> evaluateAllMetrics(ASTMethod node) { + List<MetricResult> metricResults = new ArrayList<>(); + for (ApexOperationMetricKey key : ApexOperationMetricKey.values()) { + metricResults.add(new MetricResult(key, ApexMetrics.get(key, node))); + } + + return metricResults; + } + + + private List<MetricResult> evaluateAllMetrics(ASTUserClass node) { + List<MetricResult> metricResults = new ArrayList<>(); + for (ApexClassMetricKey key : ApexClassMetricKey.values()) { + metricResults.add(new MetricResult(key, ApexMetrics.get(key, node))); + } + + return metricResults; + } + +} diff --git a/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/model/MetricResult.java b/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/model/MetricResult.java new file mode 100644 index 00000000000..34b86828e11 --- /dev/null +++ b/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/model/MetricResult.java @@ -0,0 +1,39 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.util.fxdesigner.model; + +import java.util.AbstractMap.SimpleEntry; +import java.util.Map.Entry; + +import net.sourceforge.pmd.lang.metrics.MetricKey; + +/** + * @author Clément Fournier + * @since 6.0.0 + */ +public class MetricResult { + + private final SimpleEntry<MetricKey<?>, Double> simpleEntry; + + + public MetricResult(MetricKey<?> key, Double value) { + simpleEntry = new SimpleEntry<>(key, value); + } + + + MetricResult(Entry<? extends MetricKey<?>, ? extends Double> entry) { + simpleEntry = new SimpleEntry<>(entry); + } + + + public MetricKey<?> getKey() { + return simpleEntry.getKey(); + } + + + public Double getValue() { + return simpleEntry.getValue(); + } +} diff --git a/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/model/ObservableRuleBuilder.java b/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/model/ObservableRuleBuilder.java new file mode 100644 index 00000000000..71eb8ed60b9 --- /dev/null +++ b/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/model/ObservableRuleBuilder.java @@ -0,0 +1,358 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.util.fxdesigner.model; + +import java.util.Optional; + +import org.reactfx.collection.LiveArrayList; +import org.reactfx.value.Var; + +import net.sourceforge.pmd.Rule; +import net.sourceforge.pmd.RulePriority; +import net.sourceforge.pmd.lang.Language; +import net.sourceforge.pmd.lang.LanguageRegistry; +import net.sourceforge.pmd.lang.LanguageVersion; +import net.sourceforge.pmd.rules.RuleBuilder; +import net.sourceforge.pmd.util.fxdesigner.util.PropertyDescriptorSpec; +import net.sourceforge.pmd.util.fxdesigner.util.beans.SettingsOwner; +import net.sourceforge.pmd.util.fxdesigner.util.beans.SettingsPersistenceUtil.PersistentProperty; +import net.sourceforge.pmd.util.fxdesigner.util.beans.SettingsPersistenceUtil.PersistentSequence; + +import javafx.beans.property.ListProperty; +import javafx.beans.property.SimpleListProperty; +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; + + +/** + * Holds info about a rule, and can build it to validate it. + * + * @author Clément Fournier + * @since 6.0.0 + */ +public class ObservableRuleBuilder implements SettingsOwner { + + private Var<Language> language = Var.newSimpleVar(LanguageRegistry.getDefaultLanguage()); + private Var<String> name = Var.newSimpleVar(""); + private Var<Class<?>> clazz = Var.newSimpleVar(null); + + // doesn't contain the "xpath" and "version" properties for XPath rules + private ListProperty<PropertyDescriptorSpec> ruleProperties = new SimpleListProperty<>(FXCollections.observableArrayList(PropertyDescriptorSpec.extractor())); + private Var<ObservableList<String>> examples = Var.newSimpleVar(new LiveArrayList<>()); + + private Var<LanguageVersion> minimumVersion = Var.newSimpleVar(null); + private Var<LanguageVersion> maximumVersion = Var.newSimpleVar(null); + + private Var<String> since = Var.newSimpleVar(""); + + private Var<String> message = Var.newSimpleVar(""); + private Var<String> externalInfoUrl = Var.newSimpleVar(""); + private Var<String> description = Var.newSimpleVar(""); + + private Var<RulePriority> priority = Var.newSimpleVar(RulePriority.MEDIUM); + private Var<Boolean> deprecated = Var.newSimpleVar(false); + private Var<Boolean> usesDfa = Var.newSimpleVar(false); + private Var<Boolean> usesMultifile = Var.newSimpleVar(false); + private Var<Boolean> usesTypeResolution = Var.newSimpleVar(false); + + + public Language getLanguage() { + return language.getValue(); + } + + + public void setLanguage(Language language) { + this.language.setValue(language); + } + + + public Var<Language> languageProperty() { + return language; + } + + + @PersistentProperty + public String getName() { + return name.getValue(); + } + + + public void setName(String name) { + this.name.setValue(name); + } + + + public Var<String> nameProperty() { + return name; + } + + + @PersistentProperty + public Class<?> getClazz() { + return clazz.getValue(); + } + + + public void setClazz(Class<?> clazz) { + this.clazz.setValue(clazz); + } + + + public Var<Class<?>> clazzProperty() { + return clazz; + } + + + @PersistentSequence + public ObservableList<PropertyDescriptorSpec> getRuleProperties() { + return ruleProperties.getValue(); + } + + + public void setRuleProperties(ObservableList<PropertyDescriptorSpec> ruleProperties) { + this.ruleProperties.setValue(ruleProperties); + } + + + public ListProperty<PropertyDescriptorSpec> rulePropertiesProperty() { + return ruleProperties; + } + + + public LanguageVersion getMinimumVersion() { + return minimumVersion.getValue(); + } + + + public void setMinimumVersion(LanguageVersion minimumVersion) { + this.minimumVersion.setValue(minimumVersion); + } + + + public Var<LanguageVersion> minimumVersionProperty() { + return minimumVersion; + } + + + public LanguageVersion getMaximumVersion() { + return maximumVersion.getValue(); + } + + + public void setMaximumVersion(LanguageVersion maximumVersion) { + this.maximumVersion.setValue(maximumVersion); + } + + + public Var<LanguageVersion> maximumVersionProperty() { + return maximumVersion; + } + + + @PersistentProperty + public String getSince() { + return since.getValue(); + } + + + public void setSince(String since) { + this.since.setValue(since); + } + + + public Var<String> sinceProperty() { + return since; + } + + + @PersistentProperty + public String getMessage() { + return message.getValue(); + } + + + public void setMessage(String message) { + this.message.setValue(message); + } + + + public Var<String> messageProperty() { + return message; + } + + + @PersistentProperty + public String getExternalInfoUrl() { + return externalInfoUrl.getValue(); + } + + + public void setExternalInfoUrl(String externalInfoUrl) { + this.externalInfoUrl.setValue(externalInfoUrl); + } + + + public Var<String> externalInfoUrlProperty() { + return externalInfoUrl; + } + + + @PersistentProperty + public String getDescription() { + return description.getValue(); + } + + + public void setDescription(String description) { + this.description.setValue(description); + } + + + public Var<String> descriptionProperty() { + return description; + } + + + public Var<ObservableList<String>> getExamples() { + return examples; + } + + + public void setExamples(ObservableList<String> examples) { + this.examples.setValue(examples); + } + + + @PersistentProperty + public RulePriority getPriority() { + return priority.getValue(); + } + + + public void setPriority(RulePriority priority) { + this.priority.setValue(priority); + } + + + public Var<RulePriority> priorityProperty() { + return priority; + } + + + public boolean isDeprecated() { + return deprecated.getValue(); + } + + + public void setDeprecated(boolean deprecated) { + this.deprecated.setValue(deprecated); + } + + + public Var<Boolean> deprecatedProperty() { + return deprecated; + } + + + public boolean isUsesDfa() { + return usesDfa.getValue(); + } + + + public void setUsesDfa(boolean usesDfa) { + this.usesDfa.setValue(usesDfa); + } + + + public Var<Boolean> usesDfaProperty() { + return usesDfa; + } + + + public boolean isUsesMultifile() { + return usesMultifile.getValue(); + } + + + public void setUsesMultifile(boolean usesMultifile) { + this.usesMultifile.setValue(usesMultifile); + } + + + public Var<Boolean> usesMultifileProperty() { + return usesMultifile; + } + + + public boolean getUsesTypeResolution() { + return usesTypeResolution.getValue(); + } + + + public void setUsesTypeResolution(boolean usesTypeResolution) { + this.usesTypeResolution.setValue(usesTypeResolution); + } + + + public Var<Boolean> usesTypeResolutionProperty() { + return usesTypeResolution; + } + + + /** + * Returns true if the parameters of the rule are consistent and the rule can be built. + * + * @return whether the rule can be built + */ + public boolean canBuild() { + try { + build(); + return true; + } catch (IllegalArgumentException e) { + return false; + } + } + + + /** + * Builds the rule. + * + * @return the built rule. + * + * @throws IllegalArgumentException if parameters are incorrect + */ + public Optional<Rule> build() throws IllegalArgumentException { + + try { + RuleBuilder builder = new RuleBuilder(name.getValue(), + clazz.getValue().getCanonicalName(), + language.getValue().getTerseName()); + + builder.minimumLanguageVersion(minimumVersion.getValue().getTerseName()); + builder.maximumLanguageVersion(maximumVersion.getValue().getTerseName()); + + builder.message(message.getValue()); + builder.since(since.getValue()); + builder.externalInfoUrl(externalInfoUrl.getValue()); + builder.description(description.getValue()); + builder.priority(priority.getValue().getPriority()); + + builder.setDeprecated(deprecated.getValue()); + builder.usesDFA(usesDfa.getValue()); + builder.usesTyperesolution(usesTypeResolution.getValue()); + builder.usesMultifile(usesMultifile.getValue()); + + ruleProperties.getValue().stream().map(PropertyDescriptorSpec::build).forEach(builder::defineProperty); + examples.getValue().forEach(builder::addExample); + + return Optional.of(builder.build()); + } catch (ClassNotFoundException | IllegalAccessException | InstantiationException e) { + e.printStackTrace(); + return Optional.empty(); + } + + } + +} diff --git a/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/model/ObservableXPathRuleBuilder.java b/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/model/ObservableXPathRuleBuilder.java new file mode 100644 index 00000000000..368bf999269 --- /dev/null +++ b/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/model/ObservableXPathRuleBuilder.java @@ -0,0 +1,54 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.util.fxdesigner.model; + +import org.reactfx.value.Var; + +import net.sourceforge.pmd.util.fxdesigner.util.DesignerUtil; + + +/** + * Specialises rule builders for XPath rules. + * + * @author Clément Fournier + * @since 6.0.0 + */ +public class ObservableXPathRuleBuilder extends ObservableRuleBuilder { + + + private final Var<String> xpathVersion = Var.newSimpleVar(DesignerUtil.defaultXPathVersion()); + private final Var<String> xpathExpression = Var.newSimpleVar(""); + + + public String getXpathVersion() { + return xpathVersion.getValue(); + } + + + public void setXpathVersion(String xpathVersion) { + this.xpathVersion.setValue(xpathVersion); + } + + + public Var<String> xpathVersionProperty() { + return xpathVersion; + } + + + public String getXpathExpression() { + return xpathExpression.getValue(); + } + + + public Var<String> xpathExpressionProperty() { + return xpathExpression; + } + + // TODO: Once the xpath expression changes, we'll need to rebuild the rule + // @Override + // public Optional<Rule> build() throws IllegalArgumentException { + // return super.build(); + // } +} diff --git a/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/model/ParseAbortedException.java b/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/model/ParseAbortedException.java new file mode 100644 index 00000000000..bd151d8f636 --- /dev/null +++ b/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/model/ParseAbortedException.java @@ -0,0 +1,16 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.util.fxdesigner.model; + +/** + * Exception during the parsing and visitors of the compilation units. Could be specialized into one exception per + * visitor (eg type res). + */ +public class ParseAbortedException extends RuntimeException { + + public ParseAbortedException(Throwable t) { + super(t); + } +} diff --git a/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/model/XPathEvaluationException.java b/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/model/XPathEvaluationException.java new file mode 100644 index 00000000000..2003b5b1072 --- /dev/null +++ b/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/model/XPathEvaluationException.java @@ -0,0 +1,14 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.util.fxdesigner.model; + +/** Exception during XPath evaluation. */ +public class XPathEvaluationException extends RuntimeException { + + public XPathEvaluationException(Throwable cause) { + super(cause); + } + +} diff --git a/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/model/XPathEvaluator.java b/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/model/XPathEvaluator.java new file mode 100644 index 00000000000..338b1b939d3 --- /dev/null +++ b/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/model/XPathEvaluator.java @@ -0,0 +1,92 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.util.fxdesigner.model; + +import static java.util.Collections.emptyList; +import static java.util.Collections.singletonList; + +import java.util.ArrayList; +import java.util.List; + +import org.apache.commons.lang3.StringUtils; + +import net.sourceforge.pmd.RuleContext; +import net.sourceforge.pmd.RuleSet; +import net.sourceforge.pmd.RuleSetFactory; +import net.sourceforge.pmd.RuleSets; +import net.sourceforge.pmd.lang.LanguageVersion; +import net.sourceforge.pmd.lang.ast.Node; +import net.sourceforge.pmd.lang.rule.XPathRule; +import net.sourceforge.pmd.util.fxdesigner.util.PropertyDescriptorSpec; + + +/** + * Evaluates XPath expressions. + * + * @author Clément Fournier + * @since 6.0.0 + */ +public class XPathEvaluator { + + + /** + * Evaluates an XPath query on the compilation unit. Performs + * no side effects. + * + * @param compilationUnit AST root + * @param languageVersion language version + * @param xpathVersion XPath version + * @param xpathQuery XPath query + * @param properties Properties of the rule + * + * @throws XPathEvaluationException if there was an error during the evaluation. The cause is preserved + */ + public List<Node> evaluateQuery(Node compilationUnit, + LanguageVersion languageVersion, + String xpathVersion, + String xpathQuery, + List<PropertyDescriptorSpec> properties) throws XPathEvaluationException { + + if (StringUtils.isBlank(xpathQuery)) { + return emptyList(); + } + + try { + List<Node> results = new ArrayList<>(); + + XPathRule xpathRule = new XPathRule() { + @Override + public void addViolation(Object data, Node node, String arg) { + results.add(node); + } + }; + + + xpathRule.setMessage(""); + xpathRule.setLanguage(languageVersion.getLanguage()); + xpathRule.setXPath(xpathQuery); + xpathRule.setVersion(xpathVersion); + + properties.stream() + .map(PropertyDescriptorSpec::build) + .forEach(xpathRule::definePropertyDescriptor); + + final RuleSet ruleSet = new RuleSetFactory().createSingleRuleRuleSet(xpathRule); + + RuleSets ruleSets = new RuleSets(ruleSet); + + RuleContext ruleContext = new RuleContext(); + ruleContext.setLanguageVersion(languageVersion); + ruleContext.setIgnoreExceptions(false); + + ruleSets.apply(singletonList(compilationUnit), ruleContext, xpathRule.getLanguage()); + + return results; + + } catch (RuntimeException e) { + throw new XPathEvaluationException(e); + } + } +} diff --git a/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/model/XPathSuggestions.java b/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/model/XPathSuggestions.java new file mode 100644 index 00000000000..db6411b6fd0 --- /dev/null +++ b/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/model/XPathSuggestions.java @@ -0,0 +1,103 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.util.fxdesigner.model; + +import java.io.File; +import java.io.IOException; +import java.net.URL; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Enumeration; +import java.util.List; +import java.util.stream.Collectors; + +import net.sourceforge.pmd.lang.Language; + + +public class XPathSuggestions { + private List<String> availableNodeNames = new ArrayList<>(); + + + public XPathSuggestions(Language language) { + + try { + availableNodeNames = createList(getClasses("net.sourceforge.pmd.lang." + + language.getTerseName() + ".ast")); + } catch (ClassNotFoundException | IOException e) { + e.printStackTrace(); + } + } + + + /** + * Get Suggestions based on the input by the user. + */ + public List<String> getXPathSuggestions(String input) { + return availableNodeNames.stream() + .filter(s -> s.contains(input)) + .collect(Collectors.toList()); + + } + + + /** + * Creates a list of of AST Names. + * @param classArray + * @return List<String> + */ + private List<String> createList(Class[] classArray) { + + return Arrays.asList(classArray).stream() + .filter(files -> files.getSimpleName().startsWith("AST")) + .map(m -> m.getSimpleName().substring("AST".length())) + .collect(Collectors.toList()); + + } + + private static Class[] getClasses(String packageName) throws ClassNotFoundException, IOException { + + ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); + assert classLoader != null; + + String path = packageName.replace('.', '/'); + Enumeration<URL> resources = classLoader.getResources(path); + + List<File> dirs = new ArrayList<>(); + + while (resources.hasMoreElements()) { + URL resource = resources.nextElement(); + dirs.add(new File(resource.getFile())); + } + + ArrayList<Class> classes = new ArrayList<>(); + + for (File directory : dirs) { + classes.addAll(findClasses(directory, packageName)); + } + + return classes.toArray(new Class[0]); + } + + + private static List<Class> findClasses(File directory, String packageName) throws ClassNotFoundException { + + List<Class> classes = new ArrayList<>(); + if (!directory.exists()) { + return classes; + } + + File[] files = directory.listFiles(); + for (File file : files) { + if (file.getName().endsWith(".class")) { + classes.add(Class.forName(packageName + "." + file.getName().substring(0, file.getName().length() - ".class".length()))); + } + } + + return classes; + } + + + +} diff --git a/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/popups/AuxclasspathSetupController.java b/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/popups/AuxclasspathSetupController.java new file mode 100644 index 00000000000..91ddba00e90 --- /dev/null +++ b/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/popups/AuxclasspathSetupController.java @@ -0,0 +1,176 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + + +package net.sourceforge.pmd.util.fxdesigner.popups; + +import java.io.File; +import java.io.IOException; +import java.net.URL; +import java.util.List; +import java.util.ResourceBundle; +import java.util.function.Consumer; + +import org.reactfx.collection.LiveList; +import org.reactfx.value.Val; + +import net.sourceforge.pmd.util.fxdesigner.DesignerRoot; +import net.sourceforge.pmd.util.fxdesigner.util.DesignerUtil; + +import javafx.beans.binding.Bindings; +import javafx.beans.binding.BooleanBinding; +import javafx.beans.binding.IntegerBinding; +import javafx.collections.FXCollections; +import javafx.fxml.FXML; +import javafx.fxml.FXMLLoader; +import javafx.fxml.Initializable; +import javafx.scene.Scene; +import javafx.scene.control.Button; +import javafx.scene.control.ListView; +import javafx.stage.FileChooser; +import javafx.stage.Modality; +import javafx.stage.Stage; + + +public class AuxclasspathSetupController implements Initializable { + + private final DesignerRoot designerRoot; + + @FXML + private Button removeFileButton; + @FXML + private Button selectFilesButton; + @FXML + private ListView<File> fileListView = new ListView<>(); + @FXML + private Button moveItemUpButton; + @FXML + private Button moveItemDownButton; + @FXML + private Button setClassPathButton; + @FXML + private Button cancelButton; + + + public AuxclasspathSetupController(DesignerRoot designerRoot) { + this.designerRoot = designerRoot; + } + + @Override + public void initialize(URL location, ResourceBundle resources) { + + BooleanBinding noSelection = fileListView.getSelectionModel().selectedItemProperty().isNull(); + + removeFileButton.disableProperty().bind(noSelection); + + moveItemUpButton.disableProperty().bind(noSelection.or(fileListView.getSelectionModel().selectedIndexProperty().isEqualTo(0))); + + + // we can't just map the val because we need an ObservableNumberValue + IntegerBinding lastIndexBinding = Bindings.createIntegerBinding(() -> fileListView.getItems().size() - 1, + Val.wrap(fileListView.itemsProperty()).flatMap(LiveList::sizeOf)); + + moveItemDownButton.disableProperty().bind(noSelection.or(fileListView.getSelectionModel().selectedIndexProperty().isEqualTo(lastIndexBinding))); + + fileListView.setCellFactory(DesignerUtil.simpleListCellFactory(File::getName, File::getAbsolutePath)); + + selectFilesButton.setOnAction(e -> onSelectFileClicked()); + removeFileButton.setOnAction(e -> onRemoveFileClicked()); + moveItemUpButton.setOnAction(e -> moveUp()); + moveItemDownButton.setOnAction(e -> moveDown()); + + } + + + private void onSelectFileClicked() { + FileChooser chooser = new FileChooser(); + chooser.setTitle("Add files to the auxilliary classpath"); + chooser.getExtensionFilters().addAll( + new FileChooser.ExtensionFilter("Java archives", "*.jar", "*.war", "*.ear"), + new FileChooser.ExtensionFilter("Java class files", "*.class") + ); + List<File> files = chooser.showOpenMultipleDialog(designerRoot.getMainStage()); + fileListView.getItems().addAll(files); + } + + + private void onRemoveFileClicked() { + File f = fileListView.getSelectionModel().getSelectedItem(); + fileListView.getItems().remove(f); + } + + + private void moveUp() { + moveItem(-1); + } + + + private void moveDown() { + moveItem(1); + } + + + private void moveItem(int direction) { + // Checking selected item + if (fileListView.getSelectionModel().getSelectedItem() == null) { + return; + } + + // Calculate new index using move direction + int newIndex = fileListView.getSelectionModel().getSelectedIndex() + direction; + + if (newIndex < 0 || newIndex >= fileListView.getItems().size()) { + return; + } + + File selected = fileListView.getSelectionModel().getSelectedItem(); + + // Removing removable element + fileListView.getItems().remove(selected); + // Insert it in new position + fileListView.getItems().add(newIndex, selected); + //Restore Selection + fileListView.scrollTo(newIndex); + fileListView.getSelectionModel().select(newIndex); + + } + + + /** Displays the popup. */ + public void show(Stage parentStage, List<File> currentItems, Consumer<List<File>> onApply) { + + FXMLLoader fxmlLoader = new FXMLLoader(DesignerUtil.getFxml("auxclasspath-setup-popup.fxml")); + + fxmlLoader.setControllerFactory(type -> { + if (type == AuxclasspathSetupController.class) { + return this; + } else { + throw new IllegalStateException("Wrong controller!"); + } + }); + + Stage stage = new Stage(); + try { + stage.setScene(new Scene(fxmlLoader.load())); + } catch (IOException e) { + e.printStackTrace(); + return; + } + + fileListView.setItems(FXCollections.observableArrayList(currentItems)); + + stage.setTitle("Auxilliary classpath setup"); + stage.initOwner(parentStage); + stage.initModality(Modality.WINDOW_MODAL); + + setClassPathButton.setOnAction(e -> { + stage.close(); + onApply.accept(fileListView.getItems()); + }); + + cancelButton.setOnAction(e -> stage.close()); + + stage.show(); + } +} diff --git a/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/popups/EditPropertyDialogController.java b/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/popups/EditPropertyDialogController.java new file mode 100644 index 00000000000..ae025478f48 --- /dev/null +++ b/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/popups/EditPropertyDialogController.java @@ -0,0 +1,241 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.util.fxdesigner.popups; + +import static net.sourceforge.pmd.properties.MultiValuePropertyDescriptor.DEFAULT_DELIMITER; +import static net.sourceforge.pmd.properties.MultiValuePropertyDescriptor.DEFAULT_NUMERIC_DELIMITER; +import static net.sourceforge.pmd.util.fxdesigner.util.DesignerUtil.rewire; + +import java.net.URL; +import java.util.Objects; +import java.util.ResourceBundle; + +import org.controlsfx.validation.Severity; +import org.controlsfx.validation.ValidationResult; +import org.controlsfx.validation.ValidationSupport; +import org.controlsfx.validation.Validator; +import org.reactfx.util.Try; +import org.reactfx.value.Var; + +import net.sourceforge.pmd.properties.PropertyTypeId; +import net.sourceforge.pmd.properties.ValueParser; +import net.sourceforge.pmd.properties.ValueParserConstants; +import net.sourceforge.pmd.util.fxdesigner.util.DesignerUtil; +import net.sourceforge.pmd.util.fxdesigner.util.PropertyDescriptorSpec; +import net.sourceforge.pmd.util.fxdesigner.util.controls.PropertyTableView; + +import javafx.application.Platform; +import javafx.beans.property.Property; +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import javafx.fxml.FXML; +import javafx.fxml.Initializable; +import javafx.scene.control.Button; +import javafx.scene.control.ChoiceBox; +import javafx.scene.control.TextField; +import javafx.stage.Stage; + + +/** + * Property edition dialog. Use {@link #bindToDescriptor(PropertyDescriptorSpec, ObservableList)} )} + * to use this dialog to edit a descriptor spec. Typically owned by a {@link PropertyTableView}. + * The controller must be instantiated by hand. + * + * @author Clément Fournier + * @see PropertyDescriptorSpec + * @since 6.0.0 + */ +public class EditPropertyDialogController implements Initializable { + + private final Var<PropertyTypeId> typeId = Var.newSimpleVar(PropertyTypeId.STRING); + private final Var<Runnable> commitHandler = Var.newSimpleVar(null); + private Var<PropertyDescriptorSpec> backingDescriptor = Var.newSimpleVar(null); + private Var<ObservableList<PropertyDescriptorSpec>> backingDescriptorList = Var.newSimpleVar(null); + + private ValidationSupport validationSupport = new ValidationSupport(); + @FXML + private TextField nameField; + @FXML + private TextField descriptionField; + @FXML + private ChoiceBox<PropertyTypeId> typeChoiceBox; + @FXML + private TextField valueField; + @FXML + private Button commitButton; + + + public EditPropertyDialogController() { + // default constructor + } + + + public EditPropertyDialogController(Runnable commitHandler) { + this.commitHandler.setValue(commitHandler); + } + + + @Override + public void initialize(URL location, ResourceBundle resources) { + + commitButton.setOnAction(e -> { + commitHandler.ifPresent(Runnable::run); + getStage().close(); + this.free(); + }); + + commitButton.disableProperty().bind(validationSupport.invalidProperty()); + + Platform.runLater(() -> { + typeId.bind(typeChoiceBox.getSelectionModel().selectedItemProperty()); + typeChoiceBox.setConverter(DesignerUtil.stringConverter(PropertyTypeId::getStringId, + PropertyTypeId::lookupMnemonic)); + typeChoiceBox.getItems().addAll(PropertyTypeId.typeIdsToConstants().values()); + FXCollections.sort(typeChoiceBox.getItems()); + }); + + Platform.runLater(this::registerBasicValidators); + + typeIdProperty().values() + .filter(Objects::nonNull) + .subscribe(this::registerTypeDependentValidators); + + } + + + private Stage getStage() { + return (Stage) commitButton.getScene().getWindow(); + } + + + /** Unbinds this dialog from its backing properties. */ + public void free() { + backingDescriptor.ifPresent(PropertyDescriptorSpec::unbind); + backingDescriptor.setValue(null); + backingDescriptorList.setValue(null); + this.nameProperty().setValue(""); // necessary to get the validator to reevaluate each time + } + + + /** + * Wires this dialog to the descriptor, so that the controls edit the descriptor. + * + * @param spec The descriptor + */ + public void bindToDescriptor(PropertyDescriptorSpec spec, ObservableList<PropertyDescriptorSpec> allDescriptors) { + backingDescriptor.setValue(spec); + backingDescriptorList.setValue(allDescriptors); + rewire(spec.nameProperty(), this.nameProperty(), this::setName); + rewire(spec.typeIdProperty(), this.typeIdProperty(), this::setTypeId); + rewire(spec.valueProperty(), this.valueProperty(), this::setValue); + rewire(spec.descriptionProperty(), this.descriptionProperty(), this::setDescription); + } + + + // Validators for attributes common to all properties + private void registerBasicValidators() { + Validator<String> noWhitespaceName + = Validator.createRegexValidator("Name cannot contain whitespace", "\\S*+", Severity.ERROR); + Validator<String> emptyName = Validator.createEmptyValidator("Name required"); + Validator<String> uniqueName = (c, val) -> { + long sameNameDescriptors = backingDescriptorList.getOrElse(FXCollections.emptyObservableList()) + .stream() + .map(PropertyDescriptorSpec::getName) + .filter(getName()::equals) + .count(); + + return new ValidationResult().addErrorIf(c, "The name must be unique", sameNameDescriptors > 1); + }; + + validationSupport.registerValidator(nameField, Validator.combine(noWhitespaceName, emptyName, uniqueName)); + + Validator<String> noWhitespaceDescription + = Validator.createRegexValidator("Message cannot be whitespace", "(\\s*+\\S.*)?", Severity.ERROR); + Validator<String> emptyDescription = Validator.createEmptyValidator("Message required"); + validationSupport.registerValidator(descriptionField, Validator.combine(noWhitespaceDescription, emptyDescription)); + } + + + private void registerTypeDependentValidators(PropertyTypeId typeId) { + Validator<String> valueValidator = (c, val) -> + ValidationResult.fromErrorIf(valueField, "The value couldn't be parsed", + Try.tryGet(() -> getValueParser(typeId).valueOf(getValue())).isFailure()); + + + validationSupport.registerValidator(valueField, valueValidator); + } + + + private ValueParser<?> getValueParser(PropertyTypeId typeId) { + ValueParser<?> parser = typeId.getValueParser(); + if (typeId.isPropertyMultivalue()) { + char delimiter = typeId.isPropertyNumeric() ? DEFAULT_NUMERIC_DELIMITER : DEFAULT_DELIMITER; + parser = ValueParserConstants.multi(parser, delimiter); + } + return parser; + } + + + public String getName() { + return nameField.getText(); + } + + + public void setName(String name) { + nameField.setText(name); + } + + + public Property<String> nameProperty() { + return nameField.textProperty(); + } + + + public String getDescription() { + return descriptionField.getText(); + } + + + public void setDescription(String description) { + descriptionField.setText(description); + } + + + public Property<String> descriptionProperty() { + return descriptionField.textProperty(); + } + + + public PropertyTypeId getTypeId() { + return typeId.getValue(); + } + + + public void setTypeId(PropertyTypeId typeId) { + typeChoiceBox.getSelectionModel().select(typeId); + } + + + public Var<PropertyTypeId> typeIdProperty() { + return typeId; + } + + + public String getValue() { + return valueField.getText(); + } + + + public void setValue(String value) { + valueField.setText(value); + } + + + public Property<String> valueProperty() { + return valueField.textProperty(); + } + + +} diff --git a/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/popups/ExportXPathWizardController.java b/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/popups/ExportXPathWizardController.java new file mode 100644 index 00000000000..65c30bc38cd --- /dev/null +++ b/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/popups/ExportXPathWizardController.java @@ -0,0 +1,150 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.util.fxdesigner.popups; + +import java.net.URL; +import java.util.Map; +import java.util.ResourceBundle; +import java.util.WeakHashMap; +import java.util.stream.Collectors; + +import net.sourceforge.pmd.lang.Language; +import net.sourceforge.pmd.lang.LanguageRegistry; +import net.sourceforge.pmd.lang.LanguageVersion; +import net.sourceforge.pmd.util.fxdesigner.util.DesignerUtil; +import net.sourceforge.pmd.util.fxdesigner.util.codearea.SyntaxHighlightingCodeArea; +import net.sourceforge.pmd.util.fxdesigner.util.codearea.syntaxhighlighting.XmlSyntaxHighlighter; + +import javafx.beans.value.ChangeListener; +import javafx.beans.value.ObservableValue; +import javafx.fxml.FXML; +import javafx.fxml.Initializable; +import javafx.scene.control.ChoiceBox; +import javafx.scene.control.Slider; +import javafx.scene.control.TextField; +import javafx.util.StringConverter; + + +/** + * @author Clément Fournier + * @since 6.0.0 + */ +public class ExportXPathWizardController implements Initializable { + + private final ObservableValue<String> xpathExpression; + Map<ObservableValue<?>, ChangeListener<Object>> registeredListeners = new WeakHashMap<>(); + @FXML + private SyntaxHighlightingCodeArea exportResultArea; + @FXML + private TextField descriptionField; + @FXML + private TextField messageField; + @FXML + private Slider prioritySlider; + @FXML + private ChoiceBox<Language> languageChoiceBox; + @FXML + private TextField nameField; + + + public ExportXPathWizardController(ObservableValue<String> xpathExpression) { + this.xpathExpression = xpathExpression; + } + + + @Override + public void initialize(URL location, ResourceBundle resources) { + + languageChoiceBox.getItems().addAll(DesignerUtil.getSupportedLanguageVersions() + .stream() + .map(LanguageVersion::getLanguage) + .distinct() + .collect(Collectors.toList())); + + languageChoiceBox.setConverter(new StringConverter<Language>() { + @Override + public String toString(Language object) { + return object.getTerseName(); + } + + + @Override + public Language fromString(String string) { + return LanguageRegistry.findLanguageByTerseName(string); + } + }); + + languageChoiceBox.getSelectionModel().select(LanguageRegistry.getDefaultLanguage()); + + exportResultArea.setSyntaxHighlighter(new XmlSyntaxHighlighter()); + + registerListener(nameField.textProperty(), updateResultListener()); + registerListener(messageField.textProperty(), updateResultListener()); + registerListener(descriptionField.textProperty(), updateResultListener()); + registerListener(prioritySlider.valueProperty(), updateResultListener()); + registerListener(languageChoiceBox.getSelectionModel().selectedItemProperty(), updateResultListener()); + registerListener(xpathExpression, updateResultListener()); + updateResultListener().changed(null, null, null); + } + + + public void shutdown() { + registeredListeners.entrySet().stream() + .filter(e -> e.getKey() != null) + .forEach(e -> e.getKey().removeListener(e.getValue())); + } + + + private <T> void registerListener(ObservableValue<T> value, ChangeListener<Object> listener) { + ChangeListener<Object> previous = registeredListeners.put(value, listener); + if (previous != null) { + value.removeListener(previous); + } + + value.addListener(listener); + } + + + private ChangeListener<Object> updateResultListener() { + return (observable, oldValue, newValue) -> exportResultArea.replaceText(getUpToDateRuleElement()); + } + + + private String getUpToDateRuleElement() { + // TODO very inefficient, can we do better? + + final String template = "<rule name=\"%s\"\n" + + " language=\"%s\"\n" + + " message=\"%s\"\n" + + " class=\"net.sourceforge.pmd.lang.rule.XPathRule\"\n" + + " <!-- externalInfoUrl=\"%s\"--> >\n" + + " <description>\n" + + "%s\n" + + " </description>\n" + + " <priority>%d</priority>\n" + + " <properties>\n" + + " <property name=\"xpath\">\n" + + " <value>\n" + + "<![CDATA[\n" + + "%s\n" + + "]]>\n" + + " </value>\n" + + " </property>\n" + + " </properties>\n" + + " <!--<example><![CDATA[]]></example>-->\n" + + "</rule>"; + + return String.format(template, + nameField.getText(), + languageChoiceBox.getSelectionModel().getSelectedItem().getTerseName(), + messageField.getText(), + "TODO", + descriptionField.getText(), // TODO format + (int) prioritySlider.getValue(), + xpathExpression.getValue() + ); + } + +} diff --git a/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/util/DesignerUtil.java b/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/util/DesignerUtil.java new file mode 100644 index 00000000000..9179e718e64 --- /dev/null +++ b/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/util/DesignerUtil.java @@ -0,0 +1,233 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.util.fxdesigner.util; + +import java.io.File; +import java.net.URL; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Optional; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +import org.apache.commons.lang3.exception.ExceptionUtils; + +import net.sourceforge.pmd.lang.Language; +import net.sourceforge.pmd.lang.LanguageRegistry; +import net.sourceforge.pmd.lang.LanguageVersion; +import net.sourceforge.pmd.lang.Parser; +import net.sourceforge.pmd.lang.rule.xpath.XPathRuleQuery; + +import javafx.beans.property.Property; +import javafx.beans.value.ObservableValue; +import javafx.scene.control.ListCell; +import javafx.scene.control.ListView; +import javafx.scene.control.Tooltip; +import javafx.util.Callback; +import javafx.util.StringConverter; + + +/** + * @author Clément Fournier + * @since 6.0.0 + */ +public final class DesignerUtil { + + + private static final Path PMD_SETTINGS_DIR = Paths.get(System.getProperty("user.home"), ".pmd"); + private static final File DESIGNER_SETTINGS_FILE = PMD_SETTINGS_DIR.resolve("designer.xml").toFile(); + private static final Pattern JJT_ACCEPT_PATTERN = Pattern.compile("net.sourceforge.pmd.lang.\\w++.ast.AST(\\w+).jjtAccept"); + + private static List<LanguageVersion> supportedLanguageVersions; + private static Map<String, LanguageVersion> extensionsToLanguage; + + + private DesignerUtil() { + + } + + + public static String defaultXPathVersion() { + return XPathRuleQuery.XPATH_2_0; + } + + + public static LanguageVersion defaultLanguageVersion() { + return LanguageRegistry.getDefaultLanguage().getDefaultVersion(); + } + + + /** + * Gets the URL to an fxml file from its simple name. + * + * @param simpleName Simple name of the file, i.e. with no directory prefixes + * + * @return A URL to an fxml file + */ + public static URL getFxml(String simpleName) { + return DesignerUtil.class.getResource("/net/sourceforge/pmd/util/fxdesigner/fxml/" + simpleName); + } + + + /** + * Name of the designer's settings file. + * + * @return The name + */ + public static File getSettingsFile() { + return DESIGNER_SETTINGS_FILE; + } + + + public static <T> Callback<ListView<T>, ListCell<T>> simpleListCellFactory(Function<T, String> converter, Function<T, String> toolTipMaker) { + return collection -> new ListCell<T>() { + @Override + protected void updateItem(T item, boolean empty) { + super.updateItem(item, empty); + + if (empty || item == null) { + setText(null); + setGraphic(null); + Tooltip.uninstall(this, getTooltip()); + } else { + setText(converter.apply(item)); + Tooltip.install(this, new Tooltip(toolTipMaker.apply(item))); + } + } + }; + } + + + public static <T> StringConverter<T> stringConverter(Function<T, String> toString, Function<String, T> fromString) { + return new StringConverter<T>() { + @Override + public String toString(T object) { + return toString.apply(object); + } + + + @Override + public T fromString(String string) { + return fromString.apply(string); + } + }; + } + + + public static StringConverter<LanguageVersion> languageVersionStringConverter() { + return DesignerUtil.stringConverter(LanguageVersion::getShortName, + s -> LanguageRegistry.findLanguageVersionByTerseName(s.toLowerCase(Locale.ROOT))); + } + + + private static Map<String, LanguageVersion> getExtensionsToLanguageMap() { + Map<String, LanguageVersion> result = new HashMap<>(); + getSupportedLanguageVersions().stream() + .map(LanguageVersion::getLanguage) + .distinct() + .collect(Collectors.toMap(Language::getExtensions, + Language::getDefaultVersion)) + .forEach((key, value) -> key.forEach(ext -> result.put(ext, value))); + return result; + } + + + public static synchronized LanguageVersion getLanguageVersionFromExtension(String filename) { + if (extensionsToLanguage == null) { + extensionsToLanguage = getExtensionsToLanguageMap(); + } + + if (filename.indexOf('.') > 0) { + String[] tokens = filename.split("\\."); + return extensionsToLanguage.get(tokens[tokens.length - 1]); + } + return null; + } + + + public static synchronized List<LanguageVersion> getSupportedLanguageVersions() { + if (supportedLanguageVersions == null) { + List<LanguageVersion> languageVersions = new ArrayList<>(); + for (LanguageVersion languageVersion : LanguageRegistry.findAllVersions()) { + Optional.ofNullable(languageVersion.getLanguageVersionHandler()) + .map(handler -> handler.getParser(handler.getDefaultParserOptions())) + .filter(Parser::canParse) + .ifPresent(p -> languageVersions.add(languageVersion)); + } + supportedLanguageVersions = languageVersions; + } + return supportedLanguageVersions; + } + + + /** + * Binds the underlying property to a source of values. The source property is also initialised using the setter. + * + * @param underlying The underlying property + * @param ui The property exposed to the user (the one in this wizard) + * @param setter Setter to initialise the UI value + * @param <T> Type of values + */ + public static <T> void rewire(Property<T> underlying, ObservableValue<? extends T> ui, Consumer<? super T> setter) { + setter.accept(underlying.getValue()); + rewire(underlying, ui); + } + + /** Like rewire, with no initialisation. */ + public static <T> void rewire(Property<T> underlying, ObservableValue<? extends T> source) { + underlying.unbind(); + underlying.bind(source); // Bindings are garbage collected after the popup dies + } + + + /** + * Works out an xpath query that matches the node + * which was being visited during the failure. + * + * <p>The query selects nodes that have exactly the + * same ancestors than the node in which the last call + * from the stack trace. + * + * @param stackTrace full stack trace + * + * @return An xpath expression if possible + */ + public static Optional<String> stackTraceToXPath(String stackTrace) { + List<String> lines = Arrays.stream(stackTrace.split("\\n")) + .map(JJT_ACCEPT_PATTERN::matcher) + .filter(Matcher::find) + .map(m -> m.group(1)) + .collect(Collectors.toList()); + + Collections.reverse(lines); + + return lines.isEmpty() ? Optional.empty() : Optional.of("//" + String.join("/", lines)); + } + + + /** + * Works out an xpath query that matches the node + * which was being visited during the failure. + * + * @param e Exception + * + * @return A query, if possible. + * + * @see #stackTraceToXPath(String) + */ + public static Optional<String> stackTraceToXPath(Throwable e) { + return stackTraceToXPath(ExceptionUtils.getStackTrace(e)); + } +} diff --git a/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/util/LimitedSizeStack.java b/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/util/LimitedSizeStack.java new file mode 100644 index 00000000000..7b48615833e --- /dev/null +++ b/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/util/LimitedSizeStack.java @@ -0,0 +1,45 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.util.fxdesigner.util; + +import java.util.Stack; + +/** + * Stack with a limited size, without duplicates, without null value. Used to store recent files. + * + * @param <E> Element type + * + * @author Clément Fournier + * @since 6.0.0 + */ +public class LimitedSizeStack<E> extends Stack<E> { + + private final int maxSize; + + + public LimitedSizeStack(int maxSize) { + this.maxSize = maxSize; + } + + + @Override + public E push(E item) { + if (item == null) { + return null; + } + + if (this.contains(item)) { + this.remove(item); + } + + super.push(item); + + if (size() > maxSize) { + this.removeElementAt(size() - 1); + } + + return item; + } +} diff --git a/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/util/PropertyDescriptorSpec.java b/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/util/PropertyDescriptorSpec.java new file mode 100644 index 00000000000..41706679071 --- /dev/null +++ b/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/util/PropertyDescriptorSpec.java @@ -0,0 +1,203 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.util.fxdesigner.util; + + +import java.util.HashMap; +import java.util.Map; + +import org.reactfx.value.Val; +import org.reactfx.value.Var; + +import net.sourceforge.pmd.properties.PropertyDescriptor; +import net.sourceforge.pmd.properties.PropertyDescriptorField; +import net.sourceforge.pmd.properties.PropertyTypeId; +import net.sourceforge.pmd.properties.builders.PropertyDescriptorExternalBuilder; +import net.sourceforge.pmd.util.fxdesigner.util.beans.SettingsOwner; +import net.sourceforge.pmd.util.fxdesigner.util.beans.SettingsPersistenceUtil.PersistentProperty; + +import javafx.beans.Observable; +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import javafx.util.Callback; + + +/** + * Stores enough data to build a property descriptor, can be displayed within table views. + * + * @author Clément Fournier + * @since 6.0.0 + */ +public class PropertyDescriptorSpec implements SettingsOwner { + + private static final String DEFAULT_STRING = "TODO"; + + private final Val<Boolean> isNumerical; + private final Val<Boolean> isPackaged; + private final Val<Boolean> isMultivalue; + + private final Var<PropertyTypeId> typeId = Var.newSimpleVar(PropertyTypeId.STRING); + private final Var<String> name = Var.newSimpleVar(DEFAULT_STRING); + private final Var<String> value = Var.newSimpleVar(DEFAULT_STRING); + private final Var<String> description = Var.newSimpleVar(DEFAULT_STRING); + + + public PropertyDescriptorSpec() { + isNumerical = typeId.map(PropertyTypeId::isPropertyNumeric); + isPackaged = typeId.map(PropertyTypeId::isPropertyPackaged); + isMultivalue = typeId.map(PropertyTypeId::isPropertyMultivalue); + } + + + public Boolean getIsNumerical() { + return isNumerical.getValue(); + } + + + public Val<Boolean> isNumericalProperty() { + return isNumerical; + } + + + public Boolean getIsPackaged() { + return isPackaged.getValue(); + } + + + public Val<Boolean> isPackagedProperty() { + return isPackaged; + } + + + public Boolean getIsMultivalue() { + return isMultivalue.getValue(); + } + + + public Val<Boolean> isMultivalueProperty() { + return isMultivalue; + } + + + @PersistentProperty + public String getDescription() { + return description.getValue(); + } + + + public void setDescription(String description) { + this.description.setValue(description); + } + + + public Var<String> descriptionProperty() { + return description; + } + + + @PersistentProperty + public PropertyTypeId getTypeId() { + return typeId.getValue(); + } + + + public void setTypeId(PropertyTypeId typeId) { + this.typeId.setValue(typeId); + } + + + public Var<PropertyTypeId> typeIdProperty() { + return typeId; + } + + + @PersistentProperty + public String getName() { + return name.getValue(); + } + + + public void setName(String name) { + this.name.setValue(name); + } + + + public Var<String> nameProperty() { + return name; + } + + + @PersistentProperty + public String getValue() { + return value.getValue(); + } + + + public void setValue(String value) { + this.value.setValue(value); + } + + + public Var<String> valueProperty() { + return value; + } + + + /** + * Returns an xml string of this property definition. + * + * @return An xml string + */ + public String toXml() { + return String.format("<property name=\"%s\" type=\"%s\" value=\"%s\" />", + getName(), getTypeId().getStringId(), getValue()); + } + + + @Override + public String toString() { + return toXml(); + } + + + /** + * Builds the descriptor. May throw IllegalArgumentException. + * + * @return the descriptor if it can be built + */ + public PropertyDescriptor<?> build() { + PropertyDescriptorExternalBuilder<?> externalBuilder = getTypeId().getFactory(); + Map<PropertyDescriptorField, String> values = new HashMap<>(); + values.put(PropertyDescriptorField.NAME, getName()); + values.put(PropertyDescriptorField.DEFAULT_VALUE, getValue()); + values.put(PropertyDescriptorField.DESCRIPTION, getDescription()); + values.put(PropertyDescriptorField.MIN, "-2000000"); + values.put(PropertyDescriptorField.MAX, "+2000000"); + + return externalBuilder.build(values); + } + + + /** + * Removes bindings from this property spec. + */ + public void unbind() { + typeIdProperty().unbind(); + nameProperty().unbind(); + descriptionProperty().unbind(); + valueProperty().unbind(); + } + + + /** Extractor for observable lists. */ + public static Callback<PropertyDescriptorSpec, Observable[]> extractor() { + return spec -> new Observable[]{spec.nameProperty(), spec.typeIdProperty(), spec.valueProperty()}; + } + + + public static ObservableList<PropertyDescriptorSpec> observableList() { + return FXCollections.observableArrayList(extractor()); + } +} diff --git a/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/util/TextAwareNodeWrapper.java b/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/util/TextAwareNodeWrapper.java new file mode 100644 index 00000000000..dfc37ee52ab --- /dev/null +++ b/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/util/TextAwareNodeWrapper.java @@ -0,0 +1,41 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.util.fxdesigner.util; + +import java.util.Collection; + +import org.fxmisc.richtext.model.StyledDocument; + +import net.sourceforge.pmd.lang.ast.Node; + + +/** + * Node wrapper providing convenience methods to get the text representing the node + * from the code area. + * + * @author Clément Fournier + * @since 6.5.0 + */ +public interface TextAwareNodeWrapper { + + /** + * Gets the rich text corresponding to the node in the code area. + */ + StyledDocument<Collection<String>, String, Collection<String>> getNodeRichText(); + + + /** + * Gets the text corresponding to the node in the code area. + */ + String getNodeText(); + + + /** + * Gets the underlying node. + */ + Node getNode(); + + +} diff --git a/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/util/beans/BeanModelNode.java b/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/util/beans/BeanModelNode.java new file mode 100644 index 00000000000..679dc8bb34d --- /dev/null +++ b/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/util/beans/BeanModelNode.java @@ -0,0 +1,37 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.util.fxdesigner.util.beans; + +import java.util.Collections; +import java.util.List; + + +/** + * Represents a node in the settings model. The settings model is a + * tree of such nodes, mirroring the state hierarchy of the application. + * + * <p>Each node can be serialised to XML. + * + * @author Clément Fournier + * @since 6.1.0 + */ +public abstract class BeanModelNode { + + /** Makes the children accept the visitor. */ + public <T> void childrenAccept(BeanNodeVisitor<T> visitor, T data) { + for (BeanModelNode child : getChildrenNodes()) { + child.accept(visitor, data); + } + } + + + /** Accepts a visitor. */ + protected abstract <T> void accept(BeanNodeVisitor<T> visitor, T data); + + + public List<? extends BeanModelNode> getChildrenNodes() { + return Collections.emptyList(); + } +} diff --git a/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/util/beans/BeanModelNodeSeq.java b/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/util/beans/BeanModelNodeSeq.java new file mode 100644 index 00000000000..b9b32450c6a --- /dev/null +++ b/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/util/beans/BeanModelNodeSeq.java @@ -0,0 +1,77 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.util.fxdesigner.util.beans; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +import net.sourceforge.pmd.util.fxdesigner.util.beans.SettingsPersistenceUtil.PersistentSequence; + + +/** + * Represents an indexed list of nodes sharing the same type. + * This type of node is flagged with a {@link PersistentSequence}, + * which is applied to a getter of a collection. + * + * @author Clément Fournier + * @since 6.1.0 + */ +public class BeanModelNodeSeq<T extends SimpleBeanModelNode> extends BeanModelNode { + + private final String propertyName; + private final List<T> children = new ArrayList<>(); + + + public BeanModelNodeSeq(String name) { + this.propertyName = name; + } + + + public void addChild(T node) { + children.add(node); + } + + + /** Returns the elements of the sequence. */ + @Override + public List<? extends SimpleBeanModelNode> getChildrenNodes() { + return children; + } + + + /** Returns the name of the property that contains the collection. */ + public String getPropertyName() { + return propertyName; + } + + + @Override + protected <U> void accept(BeanNodeVisitor<U> visitor, U data) { + visitor.visit(this, data); + } + + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + BeanModelNodeSeq<?> that = (BeanModelNodeSeq<?>) o; + return Objects.equals(propertyName, that.propertyName) + && Objects.equals(children, that.children); + } + + + @Override + public int hashCode() { + + return Objects.hash(propertyName, children); + } + +} diff --git a/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/util/beans/BeanNodeVisitor.java b/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/util/beans/BeanNodeVisitor.java new file mode 100644 index 00000000000..82417391e55 --- /dev/null +++ b/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/util/beans/BeanNodeVisitor.java @@ -0,0 +1,31 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.util.fxdesigner.util.beans; + +/** + * Implements a visitor pattern over bean nodes. Used to restore properties + * from a model and build an XML document to represent the model. + * + * @author Clément Fournier + * @since 6.1.0 + */ +public abstract class BeanNodeVisitor<T> { + + public void visit(BeanModelNode node, T data) { + node.childrenAccept(this, data); + } + + + public void visit(BeanModelNodeSeq<?> node, T data) { + visit((BeanModelNode) node, data); + } + + + public void visit(SimpleBeanModelNode node, T data) { + visit((BeanModelNode) node, data); + } + + +} diff --git a/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/util/beans/RestorePropertyVisitor.java b/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/util/beans/RestorePropertyVisitor.java new file mode 100644 index 00000000000..19a00dff746 --- /dev/null +++ b/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/util/beans/RestorePropertyVisitor.java @@ -0,0 +1,128 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.util.fxdesigner.util.beans; + +import java.beans.PropertyDescriptor; +import java.lang.reflect.InvocationTargetException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.stream.Collectors; + +import org.apache.commons.beanutils.PropertyUtils; + +import net.sourceforge.pmd.util.fxdesigner.util.beans.SettingsPersistenceUtil.PersistentProperty; + + +/** + * Visits a bean model and restores the properties described by the nodes + * into their respective settings owner. + * + * @author Clément Fournier + * @since 6.1.0 + */ +public class RestorePropertyVisitor extends BeanNodeVisitor<SettingsOwner> { + + + @Override + public void visit(SimpleBeanModelNode model, SettingsOwner target) { + if (model == null) { + return; // possibly it wasn't saved during the previous save cycle + } + + if (target == null) { + throw new IllegalArgumentException(); + } + + if (target.getClass() != model.getNodeType()) { + throw new IllegalArgumentException("Incorrect settings restoration target, expected " + + model.getNodeType() + ", actual " + target.getClass()); + } + + Map<String, PropertyDescriptor> descriptors = Arrays.stream(PropertyUtils.getPropertyDescriptors(target)) + .filter(d -> d.getReadMethod() != null && d.getReadMethod().isAnnotationPresent(PersistentProperty.class)) + .collect(Collectors.toMap(PropertyDescriptor::getName, d -> d)); + + for (Entry<String, Object> saved : model.getSettingsValues().entrySet()) { + if (descriptors.containsKey(saved.getKey())) { + try { + PropertyUtils.setProperty(target, saved.getKey(), saved.getValue()); + } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { + System.err.println("Error setting property " + saved.getKey() + " on a " + target.getClass().getSimpleName()); + e.printStackTrace(); + } + } + } + + for (BeanModelNodeSeq<?> seq : model.getSequenceProperties()) { + this.visit(seq, target); + } + + for (SettingsOwner child : target.getChildrenSettingsNodes()) { + model.getChildrenByType().get(child.getClass()).accept(this, child); + } + } + + + @Override + public void visit(BeanModelNodeSeq<?> model, SettingsOwner target) { + if (model == null) { + return; // possibly it wasn't saved during the previous save cycle + } + + if (target == null) { + throw new IllegalArgumentException(); + } + + Collection<SettingsOwner> container; + try { + @SuppressWarnings("unchecked") + Collection<SettingsOwner> tmp = (Collection<SettingsOwner>) PropertyUtils.getProperty(target, model.getPropertyName()); + container = tmp; + } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e) { + e.printStackTrace(); + return; + } + + + Iterator<SettingsOwner> existingItems = container.iterator(); + Class<?> itemType = null; + // use a buffer to avoid concurrent modification + List<SettingsOwner> itemsToAdd = new ArrayList<>(); + + for (SimpleBeanModelNode child : model.getChildrenNodes()) { + SettingsOwner item; + if (existingItems.hasNext()) { + item = existingItems.next(); + } else { + if (itemType == null) { + itemType = child.getNodeType(); + } + + try { + item = (SettingsOwner) itemType.newInstance(); + } catch (InstantiationException | IllegalAccessException e) { + e.printStackTrace(); + continue; // try hard + } + } + + child.accept(this, item); + itemsToAdd.add(item); + } + + container.addAll(itemsToAdd); + + try { + PropertyUtils.setProperty(target, model.getPropertyName(), container); + } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e) { + e.printStackTrace(); + } + } +} diff --git a/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/util/beans/SettingsOwner.java b/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/util/beans/SettingsOwner.java new file mode 100644 index 00000000000..2eb05b141ef --- /dev/null +++ b/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/util/beans/SettingsOwner.java @@ -0,0 +1,28 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.util.fxdesigner.util.beans; + +import java.util.Collections; +import java.util.List; + + +/** + * Marker interface for settings owners. Settings owners form a + * tree-like hierarchy, which is explored recursively to build + * a model of the settings to persist, under the form of a + * {@link SimpleBeanModelNode}. + * + * @author Clément Fournier + * @since 6.1.0 + */ +public interface SettingsOwner { + + + /** Gets the children of this node in order. */ + default List<SettingsOwner> getChildrenSettingsNodes() { + return Collections.emptyList(); + } + +} diff --git a/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/util/beans/SettingsPersistenceUtil.java b/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/util/beans/SettingsPersistenceUtil.java new file mode 100644 index 00000000000..22255494dc3 --- /dev/null +++ b/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/util/beans/SettingsPersistenceUtil.java @@ -0,0 +1,245 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.util.fxdesigner.util.beans; + +import java.beans.PropertyDescriptor; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.lang.reflect.InvocationTargetException; +import java.util.Arrays; +import java.util.Collection; +import java.util.Comparator; +import java.util.Optional; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; + +import org.apache.commons.beanutils.ConvertUtils; +import org.apache.commons.beanutils.PropertyUtils; +import org.w3c.dom.Document; +import org.xml.sax.SAXException; + +import net.sourceforge.pmd.RulePriority; +import net.sourceforge.pmd.lang.LanguageVersion; +import net.sourceforge.pmd.properties.PropertyTypeId; +import net.sourceforge.pmd.util.fxdesigner.util.beans.converters.LanguageVersionConverter; +import net.sourceforge.pmd.util.fxdesigner.util.beans.converters.PropertyTypeIdConverter; +import net.sourceforge.pmd.util.fxdesigner.util.beans.converters.RulePriorityConverter; + + +/** + * Utility methods to persist settings of the application. + * + * @author Clément Fournier + * @see SimpleBeanModelNode + * @see SettingsOwner + * @since 6.1.0 + */ +public final class SettingsPersistenceUtil { + + static { + // register converters for custom types + ConvertUtils.register(new RulePriorityConverter(), RulePriority.class); + ConvertUtils.register(new PropertyTypeIdConverter(), PropertyTypeId.class); + ConvertUtils.register(new LanguageVersionConverter(), LanguageVersion.class); + } + + + private SettingsPersistenceUtil() { + } + + + /** + * Restores properties contained in the file into the given object. + * + * @param root Root of the hierarchy + * @param file Properties file + */ + public static void restoreProperties(SettingsOwner root, File file) { + Optional<Document> odoc = getDocument(file); + + odoc.flatMap(XmlFormatRevision::getSuitableReader) + .map(rev -> rev.xmlInterface) + .flatMap(xmlInterface -> odoc.flatMap(xmlInterface::parseXml)) + .ifPresent(n -> restoreSettings(root, n)); + } + + + /** + * Save properties of this object and descendants into the given file. + * + * @param root Root of the hierarchy + * @param file Properties file + */ + public static void persistProperties(SettingsOwner root, File file) throws IOException { + SimpleBeanModelNode node = SettingsPersistenceUtil.buildSettingsModel(root); + XmlFormatRevision.getLatest().xmlInterface.writeModelToXml(file, node); + } + + + /** + * Returns an XML document for the given file if it exists and can be parsed. + * + * @param file File to parse + */ + private static Optional<Document> getDocument(File file) { + if (file.exists()) { + try (InputStream stream = new FileInputStream(file)) { + DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder(); + Document document = builder.parse(stream); + return Optional.of(document); + } catch (SAXException | ParserConfigurationException | IOException e) { + e.printStackTrace(); + } + } + + return Optional.empty(); + } + + + /** + * Builds a settings model recursively for the given settings owner. + * The properties which have a getter tagged with {@link PersistentProperty} + * are retrieved for later serialisation. + * + * @param root The root of the settings owner hierarchy. + * + * @return The built model + */ + // test only + static SimpleBeanModelNode buildSettingsModel(SettingsOwner root) { + SimpleBeanModelNode node = new SimpleBeanModelNode(root.getClass()); + + for (PropertyDescriptor d : PropertyUtils.getPropertyDescriptors(root)) { + if (d.getReadMethod() == null) { + continue; + } + + try { + if (d.getReadMethod().isAnnotationPresent(PersistentSequence.class)) { + + Object val = d.getReadMethod().invoke(root); + if (!Collection.class.isAssignableFrom(val.getClass())) { + continue; + } + + @SuppressWarnings("unchecked") + Collection<SettingsOwner> values = (Collection<SettingsOwner>) val; + + BeanModelNodeSeq<SimpleBeanModelNode> seq = new BeanModelNodeSeq<>(d.getName()); + + for (SettingsOwner item : values) { + seq.addChild(buildSettingsModel(item)); + } + + node.addChild(seq); + } else if (d.getReadMethod().isAnnotationPresent(PersistentProperty.class)) { + node.addProperty(d.getName(), d.getReadMethod().invoke(root), d.getPropertyType()); + } + } catch (IllegalAccessException | InvocationTargetException e) { + e.printStackTrace(); + } + + } + + for (SettingsOwner child : root.getChildrenSettingsNodes()) { + node.addChild(buildSettingsModel(child)); + } + + return node; + } + + + /** + * Restores the settings from the model into the target. Dual of + * {@link #buildSettingsModel(SettingsOwner)}. Traverses all the + * tree. + * + * @param target Object in which to restore the properties + * @param model The model + */ + // test only + static void restoreSettings(SettingsOwner target, BeanModelNode model) { + if (model == null) { + return; // possibly it wasn't saved during the previous save cycle + } + + if (target == null) { + throw new IllegalArgumentException(); + } + + model.accept(new RestorePropertyVisitor(), target); + } + + + /** Enumerates different formats for compatibility. */ + private enum XmlFormatRevision implements Comparable<XmlFormatRevision> { + V1(new XmlInterfaceVersion1(1)); + + private final XmlInterface xmlInterface; + + + XmlFormatRevision(XmlInterface xmlI) { + this.xmlInterface = xmlI; + } + + + public static XmlFormatRevision getLatest() { + return Arrays.stream(values()).max(Comparator.comparingInt(x -> x.xmlInterface.getRevisionNumber())).get(); + } + + + /** + * Gets a handler capable of reading the given document. + * + * @param doc The revision number + * + * @return A handler, if it can be found + */ + public static Optional<XmlFormatRevision> getSuitableReader(Document doc) { + return Arrays.stream(values()) + .filter(rev -> rev.xmlInterface.canParse(doc)) + .findAny(); + } + } + + + /** + * Tags the *getter* of a property as suitable for persistence. + * The property will be serialized and restored on startup, so + * it must have a setter. + * + * <p>Properties setters and getters must respect JavaBeans + * conventions. + */ + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.METHOD) + public @interface PersistentProperty { + } + + + /** + * Tags the getter of a collection for persistence. The collection + * elements must implement {@link SettingsOwner} and have a noargs + * constructor. This is a solution to the problem of serializing + * collections of items of arbitrary complexity. + * + * <p>When restoring such a property, we assume that the property + * already has a value, and we either update existing items with + * the properties or we instantiate new items if not enough are + * already available. + */ + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.METHOD) + public @interface PersistentSequence { + } + +} diff --git a/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/util/beans/SimpleBeanModelNode.java b/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/util/beans/SimpleBeanModelNode.java new file mode 100644 index 00000000000..0b8e9b8b9c7 --- /dev/null +++ b/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/util/beans/SimpleBeanModelNode.java @@ -0,0 +1,145 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.util.fxdesigner.util.beans; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; + +import net.sourceforge.pmd.util.fxdesigner.util.beans.SettingsPersistenceUtil.PersistentSequence; + + +/** + * Represents a node in the settings owner tree, and stores the values of the properties that + * should be saved and restored. A node can have other nodes as children, in which case they are + * identified using their type at restore time. To persist the properties of multiple children with + * the same type, see {@link PersistentSequence} and {@link BeanModelNodeSeq}. + * + * <p>This intermediary representation decouples the XML representation from the business logic, + * allowing several parsers / serializers to coexist for different versions of the schema. + * + * @author Clément Fournier + * @since 6.1.0 + */ +public class SimpleBeanModelNode extends BeanModelNode { + + + private final Class<?> nodeType; + + private final Map<String, Object> propertyValues = new HashMap<>(); + private final Map<String, Class<?>> propertyTypes = new HashMap<>(); + private final Map<Class<?>, BeanModelNode> children = new HashMap<>(); + private final Set<BeanModelNodeSeq<?>> sequenceProperties = new HashSet<>(); + + + public SimpleBeanModelNode(Class<?> nodeType) { + this.nodeType = nodeType; + } + + + /** + * Add one more property with its value. + * + * @param propertyKey Unique name identifying the property. + * @param value Value + * @param type Type of the property + */ + public void addProperty(String propertyKey, Object value, Class<?> type) { + propertyValues.put(propertyKey, value); + propertyTypes.put(propertyKey, type); + } + + + /** + * Add a sequence of nodes as a child of this node. + * + * @param seq Sequence of nodes + */ + public void addChild(BeanModelNodeSeq<?> seq) { + sequenceProperties.add(seq); + } + + + /** + * Add a node to the children of this node. + * + * @param child Node + */ + public void addChild(SimpleBeanModelNode child) { + children.put(child.nodeType, child); + } + + + /** Returns a map of property names to their value. */ + public Map<String, Object> getSettingsValues() { + return Collections.unmodifiableMap(propertyValues); + } + + + /** Returns a map of property names to their type. */ + public Map<String, Class<?>> getSettingsTypes() { + return Collections.unmodifiableMap(propertyTypes); + } + + + /** Returns a map of children by type. */ + public Map<Class<?>, BeanModelNode> getChildrenByType() { + return Collections.unmodifiableMap(children); + } + + + @Override + public List<? extends BeanModelNode> getChildrenNodes() { + Set<BeanModelNode> allChildren = new HashSet<>(children.values()); + allChildren.addAll(sequenceProperties); + return new ArrayList<>(allChildren); + } + + + /** Gets the sequences of nodes registered as children. */ + public Set<BeanModelNodeSeq<?>> getSequenceProperties() { + return sequenceProperties; + } + + + /** Get the type of the settings owner represented by this node. */ + public Class<?> getNodeType() { + return nodeType; + } + + + @Override + protected <U> void accept(BeanNodeVisitor<U> visitor, U data) { + visitor.visit(this, data); + } + + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + SimpleBeanModelNode that = (SimpleBeanModelNode) o; + return Objects.equals(nodeType, that.nodeType) + && Objects.equals(propertyValues, that.propertyValues) + && Objects.equals(propertyTypes, that.propertyTypes) + && Objects.equals(children, that.children) + && Objects.equals(sequenceProperties, that.sequenceProperties); + } + + + @Override + public int hashCode() { + return Objects.hash(nodeType, propertyValues, propertyTypes, children, sequenceProperties); + } +} diff --git a/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/util/beans/XmlInterface.java b/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/util/beans/XmlInterface.java new file mode 100644 index 00000000000..4667b4386b5 --- /dev/null +++ b/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/util/beans/XmlInterface.java @@ -0,0 +1,152 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.util.fxdesigner.util.beans; + +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.util.Optional; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.transform.OutputKeys; +import javax.xml.transform.Result; +import javax.xml.transform.Source; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerException; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.dom.DOMSource; +import javax.xml.transform.stream.StreamResult; + +import org.w3c.dom.Document; +import org.w3c.dom.Element; + + +/** + * Represents a version of the Xml format used to store settings. The + * parser and serializer must understand each other, so they're kept + * together. + * + * @author Clément Fournier + * @since 6.1.0 + */ +public abstract class XmlInterface { + + // modifying these will break compatibility + private static final String SCHEMA_MODEL_VERSION = "revision"; + private static final String SCHEMA_DOCUMENT_ELEMENT = "designer-settings"; + + private final int revisionNumber; + + + public XmlInterface(int rev) { + this.revisionNumber = rev; + } + + + public int getRevisionNumber() { + return revisionNumber; + } + + + /** + * Parses a XML document produced by {@link #writeModelToXml(File, SimpleBeanModelNode)} + * into a settings node. + * + * @param document The document to parse + * + * @return The root of the model hierarchy, or empty if the revision is not supported + */ + public final Optional<SimpleBeanModelNode> parseXml(Document document) { + if (canParse(document)) { + Element rootNodeElement = (Element) document.getDocumentElement().getChildNodes().item(1); + return Optional.ofNullable(parseSettingsOwnerNode(rootNodeElement)); + } + return Optional.empty(); + } + + + /** + * Returns true if the document can be read by this object. + * + * @param document Document to test + */ + public boolean canParse(Document document) { + int docVersion = Integer.parseInt(document.getDocumentElement().getAttribute(SCHEMA_MODEL_VERSION)); + return docVersion == getRevisionNumber(); + } + + + private Document initDocument() throws IOException { + DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance(); + DocumentBuilder documentBuilder; + try { + documentBuilder = documentBuilderFactory.newDocumentBuilder(); + } catch (ParserConfigurationException e) { + throw new IOException("Failed to create settings document builder", e); + } + Document document = documentBuilder.newDocument(); + + Element settingsElement = document.createElement(SCHEMA_DOCUMENT_ELEMENT); + settingsElement.setAttribute(SCHEMA_MODEL_VERSION, "" + getRevisionNumber()); + document.appendChild(settingsElement); + return document; + } + + + /** Saves parameters to disk. */ + private void save(Document document, File outputFile) throws IOException { + try { + TransformerFactory transformerFactory = TransformerFactory.newInstance(); + Transformer transformer = transformerFactory.newTransformer(); + + transformer.setOutputProperty(OutputKeys.METHOD, "xml"); + transformer.setOutputProperty(OutputKeys.INDENT, "yes"); + transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "4"); + transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8"); + + Source source = new DOMSource(document); + outputFile.getParentFile().mkdirs(); + Result result = new StreamResult(new FileWriter(outputFile)); + transformer.transform(source, result); + } catch (TransformerException e) { + throw new IOException("Failed to save settings", e); + } + } + + + /** + * Writes the model to a file. + * + * @param output The output file + * @param model The model to serialize + * + * @throws IOException If saving the settings failed + */ + public final void writeModelToXml(File output, SimpleBeanModelNode model) throws IOException { + Document document = initDocument(); + model.accept(getDocumentMakerVisitor(), document.getDocumentElement()); + save(document, output); + } + + + /** + * Parses a settings node and its descendants recursively. + * + * @param nodeElement Element to parse + * + * @return The model described by the element + */ + protected abstract SimpleBeanModelNode parseSettingsOwnerNode(Element nodeElement); + + + /** + * Gets a visitor which populates xml elements with corresponding nodes. + * + * @return A visitor + */ + protected abstract BeanNodeVisitor<Element> getDocumentMakerVisitor(); + +} diff --git a/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/util/beans/XmlInterfaceVersion1.java b/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/util/beans/XmlInterfaceVersion1.java new file mode 100644 index 00000000000..cad6ebe6dc7 --- /dev/null +++ b/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/util/beans/XmlInterfaceVersion1.java @@ -0,0 +1,160 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.util.fxdesigner.util.beans; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map.Entry; + +import org.apache.commons.beanutils.ConvertUtils; +import org.apache.commons.lang3.ClassUtils; +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; + + +/** + * V0, really. + * + * @author Clément Fournier + * @since 6.1.0 + */ +public class XmlInterfaceVersion1 extends XmlInterface { + + + // names used in the Xml schema + private static final String SCHEMA_NODE_ELEMENT = "node"; + private static final String SCHEMA_NODESEQ_ELEMENT = "nodeseq"; + private static final String SCHEMA_NODE_CLASS_ATTRIBUTE = "class"; + private static final String SCHEMA_PROPERTY_ELEMENT = "property"; + private static final String SCHEMA_PROPERTY_NAME = "name"; + private static final String SCHEMA_PROPERTY_TYPE = "type"; + + + public XmlInterfaceVersion1(int revisionNumber) { + super(revisionNumber); + } + + + private List<Element> getChildrenByTagName(Element element, String tagName) { + NodeList children = element.getChildNodes(); + List<Element> elts = new ArrayList<>(); + for (int i = 0; i < children.getLength(); i++) { + if (children.item(i).getNodeType() == Node.ELEMENT_NODE && tagName.equals(children.item(i).getNodeName())) { + elts.add((Element) children.item(i)); + } + } + + return elts; + } + + + @Override + protected SimpleBeanModelNode parseSettingsOwnerNode(Element nodeElement) { + Class<?> clazz; + try { + clazz = Class.forName(nodeElement.getAttribute(SCHEMA_NODE_CLASS_ATTRIBUTE)); + } catch (ClassNotFoundException e) { + return null; + } + + SimpleBeanModelNode node = new SimpleBeanModelNode(clazz); + + for (Element setting : getChildrenByTagName(nodeElement, SCHEMA_PROPERTY_ELEMENT)) { + parseSingleProperty(setting, node); + } + + for (Element child : getChildrenByTagName(nodeElement, SCHEMA_NODE_ELEMENT)) { + try { + if (node.getChildrenByType().get(Class.forName(child.getAttribute(SCHEMA_NODE_CLASS_ATTRIBUTE))) == null) { // FIXME + node.addChild(parseSettingsOwnerNode(child)); + } + } catch (ClassNotFoundException e) { + e.printStackTrace(); + } + } + + for (Element seq : getChildrenByTagName(nodeElement, SCHEMA_NODESEQ_ELEMENT)) { + parseNodeSeq(seq, node); + } + + return node; + } + + + private void parseSingleProperty(Element propertyElement, SimpleBeanModelNode owner) { + String typeName = propertyElement.getAttribute(SCHEMA_PROPERTY_TYPE); + String name = propertyElement.getAttribute(SCHEMA_PROPERTY_NAME); + Class<?> type; + try { + type = ClassUtils.getClass(typeName); + } catch (ClassNotFoundException e) { + e.printStackTrace(); + return; + } + + ConvertUtils.convert(new Object()); + Object value = ConvertUtils.convert(propertyElement.getTextContent(), type); + + owner.addProperty(name, value, type); + } + + + private void parseNodeSeq(Element nodeSeq, SimpleBeanModelNode parent) { + BeanModelNodeSeq<SimpleBeanModelNode> built = new BeanModelNodeSeq<>(nodeSeq.getAttribute(SCHEMA_PROPERTY_NAME)); + for (Element child : getChildrenByTagName(nodeSeq, SCHEMA_NODE_ELEMENT)) { + built.addChild(parseSettingsOwnerNode(child)); + } + parent.addChild(built); + } + + + @Override + protected BeanNodeVisitor<Element> getDocumentMakerVisitor() { + return new DocumentMakerVisitor(); + } + + + public static class DocumentMakerVisitor extends BeanNodeVisitor<Element> { + + + @Override + public void visit(SimpleBeanModelNode node, Element parent) { + Element nodeElement = parent.getOwnerDocument().createElement(SCHEMA_NODE_ELEMENT); + nodeElement.setAttribute(SCHEMA_NODE_CLASS_ATTRIBUTE, node.getNodeType().getCanonicalName()); + + for (Entry<String, Object> keyValue : node.getSettingsValues().entrySet()) { + // I don't think the API is intended to be used like that + // but ConvertUtils wouldn't use the convertToString methods + // defined in the converters otherwise. + // Even when a built-in converter is available, objects are + // still converted with Object::toString which fucks up the + // conversion... + String value = (String) ConvertUtils.lookup(keyValue.getValue().getClass()).convert(String.class, keyValue.getValue()); + if (value == null) { + continue; + } + + Element setting = parent.getOwnerDocument().createElement(SCHEMA_PROPERTY_ELEMENT); + setting.setAttribute(SCHEMA_PROPERTY_NAME, keyValue.getKey()); + setting.setAttribute(SCHEMA_PROPERTY_TYPE, node.getSettingsTypes().get(keyValue.getKey()).getCanonicalName()); + setting.appendChild(parent.getOwnerDocument().createCDATASection(value)); + nodeElement.appendChild(setting); + } + + parent.appendChild(nodeElement); + super.visit(node, nodeElement); + } + + + @Override + public void visit(BeanModelNodeSeq<?> node, Element parent) { + Element nodeElement = parent.getOwnerDocument().createElement(SCHEMA_NODESEQ_ELEMENT); + nodeElement.setAttribute(SCHEMA_PROPERTY_NAME, node.getPropertyName()); + parent.appendChild(nodeElement); + super.visit(node, nodeElement); + } + } +} diff --git a/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/util/beans/converters/LanguageVersionConverter.java b/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/util/beans/converters/LanguageVersionConverter.java new file mode 100644 index 00000000000..58b584b54c0 --- /dev/null +++ b/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/util/beans/converters/LanguageVersionConverter.java @@ -0,0 +1,35 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.util.fxdesigner.util.beans.converters; + +import org.apache.commons.beanutils.converters.AbstractConverter; + +import net.sourceforge.pmd.lang.LanguageRegistry; +import net.sourceforge.pmd.lang.LanguageVersion; + + +/** + * @author Clément Fournier + * @since 6.1.0 + */ +public class LanguageVersionConverter extends AbstractConverter { + + @Override + protected String convertToString(Object value) { + return ((LanguageVersion) value).getTerseName(); + } + + + @Override + protected Object convertToType(Class aClass, Object o) { + return LanguageRegistry.findLanguageVersionByTerseName(o.toString()); + } + + + @Override + protected Class getDefaultType() { + return LanguageVersion.class; + } +} diff --git a/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/util/beans/converters/PropertyTypeIdConverter.java b/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/util/beans/converters/PropertyTypeIdConverter.java new file mode 100644 index 00000000000..0967cd816cd --- /dev/null +++ b/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/util/beans/converters/PropertyTypeIdConverter.java @@ -0,0 +1,34 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.util.fxdesigner.util.beans.converters; + +import org.apache.commons.beanutils.converters.AbstractConverter; + +import net.sourceforge.pmd.properties.PropertyTypeId; + + +/** + * @author Clément Fournier + * @since 6.1.0 + */ +public class PropertyTypeIdConverter extends AbstractConverter { + + @Override + protected String convertToString(Object value) { + return ((PropertyTypeId) value).getStringId(); + } + + + @Override + protected Object convertToType(Class aClass, Object o) { + return PropertyTypeId.lookupMnemonic(o.toString()); + } + + + @Override + protected Class getDefaultType() { + return PropertyTypeId.class; + } +} diff --git a/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/util/beans/converters/RulePriorityConverter.java b/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/util/beans/converters/RulePriorityConverter.java new file mode 100644 index 00000000000..22bd3dc4320 --- /dev/null +++ b/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/util/beans/converters/RulePriorityConverter.java @@ -0,0 +1,34 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.util.fxdesigner.util.beans.converters; + +import org.apache.commons.beanutils.converters.AbstractConverter; + +import net.sourceforge.pmd.RulePriority; + + +/** + * @author Clément Fournier + * @since 6.1.0 + */ +public class RulePriorityConverter extends AbstractConverter { + + @Override + protected String convertToString(Object value) { + return Integer.toString(((RulePriority) value).getPriority()); + } + + + @Override + protected Object convertToType(Class aClass, Object o) { + return RulePriority.valueOf(Integer.parseInt(o.toString())); + } + + + @Override + protected Class getDefaultType() { + return RulePriority.class; + } +} diff --git a/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/util/codearea/AvailableSyntaxHighlighters.java b/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/util/codearea/AvailableSyntaxHighlighters.java new file mode 100644 index 00000000000..f5402a1d199 --- /dev/null +++ b/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/util/codearea/AvailableSyntaxHighlighters.java @@ -0,0 +1,57 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.util.fxdesigner.util.codearea; + +import java.util.Arrays; +import java.util.Optional; + +import net.sourceforge.pmd.lang.Language; +import net.sourceforge.pmd.util.fxdesigner.util.codearea.syntaxhighlighting.ApexSyntaxHighlighter; +import net.sourceforge.pmd.util.fxdesigner.util.codearea.syntaxhighlighting.JavaSyntaxHighlighter; +import net.sourceforge.pmd.util.fxdesigner.util.codearea.syntaxhighlighting.XPathSyntaxHighlighter; +import net.sourceforge.pmd.util.fxdesigner.util.codearea.syntaxhighlighting.XmlSyntaxHighlighter; + + +/** + * Lists the available syntax highlighter engines by language. + * + * @author Clément Fournier + * @since 6.0.0 + */ +public enum AvailableSyntaxHighlighters { + JAVA("java", new JavaSyntaxHighlighter()), + APEX("apex", new ApexSyntaxHighlighter()), + XML("xml", new XmlSyntaxHighlighter()), + XSL("xsl", new XmlSyntaxHighlighter()), + WSDL("wsdl", new XmlSyntaxHighlighter()), + POM("pom", new XmlSyntaxHighlighter()), + XPATH("xpath", new XPathSyntaxHighlighter()); + + + private final String language; + private final SyntaxHighlighter engine; + + + AvailableSyntaxHighlighters(String languageTerseName, SyntaxHighlighter engine) { + this.language = languageTerseName; + this.engine = engine; + } + + + /** + * Gets the highlighter for a language if available. + * + * @param language Language to look for + * + * @return A highlighter, if available + */ + public static Optional<SyntaxHighlighter> getHighlighterForLanguage(Language language) { + return Arrays.stream(AvailableSyntaxHighlighters.values()) + .filter(e -> e.language.equals(language.getTerseName())) + .findFirst() + .map(h -> h.engine); + + } +} diff --git a/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/util/codearea/HighlightLayerCodeArea.java b/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/util/codearea/HighlightLayerCodeArea.java new file mode 100644 index 00000000000..2d0d635daac --- /dev/null +++ b/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/util/codearea/HighlightLayerCodeArea.java @@ -0,0 +1,214 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.util.fxdesigner.util.codearea; + +import java.util.Collection; +import java.util.Collections; +import java.util.EnumSet; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; +import java.util.stream.Collectors; + +import org.fxmisc.richtext.model.StyleSpans; + +import net.sourceforge.pmd.lang.ast.Node; +import net.sourceforge.pmd.util.fxdesigner.util.codearea.HighlightLayerCodeArea.LayerId; + +import javafx.beans.NamedArg; +import javafx.scene.control.IndexRange; + + +/** + * Code area that can manipulate different layers of styling independently, + * in addition to syntax highlighting. Layers are identified by a {@link LayerId}, + * which are listed in an enum. + * + * @param <K> Enum type listing the layer ids to use + * @author Clément Fournier + * @since 6.5.0 + */ +public class HighlightLayerCodeArea<K extends Enum<K> & LayerId> extends SyntaxHighlightingCodeArea { + + + /** Contains the highlighting layers. */ + private final Map<K, StyleLayer> layersById; + + + /** + * Builds a new code area with the given enum type as layer id provider. + * Constants of the enum will identify layers of the code area. + * + * @param idEnum Enum type + */ + // the annotation lets the value be passed from FXML + public HighlightLayerCodeArea(@NamedArg("idEnum") Class<K> idEnum) { + super(); + + this.layersById = EnumSet.allOf(idEnum) + .stream() + .collect(Collectors.toConcurrentMap(id -> id, id -> new StyleLayer())); + } + + + /** + * Styles some nodes in a given layer and updates the visual appearance of the area. + * + * <p>Each layer has its own style class, that is assigned to the nodes + * that belong to it. + * + * @param nodes Nodes to style + * @param layerId Id of the layer in which to save the node highlight + * @param resetLayer Whether to replace the contents of the layer with the + * styling for these nodes, or just add them. + */ + // TODO there's no reason to only be able to style nodes, in fact, this causes problem + // to highlight errors that are not bound to a node, eg parsing errors + // We'll need to abstract away NodeStyleSpan + public void styleNodes(Collection<? extends Node> nodes, K layerId, boolean resetLayer) { + Objects.requireNonNull(nodes, "Pass an empty collection to represent absence, not null!"); + + if (nodes.isEmpty() && resetLayer) { + clearStyleLayer(layerId); + return; + } + + List<NodeStyleSpan> wrappedNodes = nodes.stream().map(n -> NodeStyleSpan.fromNode(n, this)).collect(Collectors.toList()); + + UniformStyleCollection collection = new UniformStyleCollection(Collections.singleton(layerId.getStyleClass()), wrappedNodes); + + updateStyling(() -> layersById.get(layerId).styleNodes(resetLayer, collection)); + } + + + /** + * Applies the given update and applies the styling to the code area. + * We use a closure parameter to encapsulate the application of the + * update inside the restyling procedure, and mostly to make obvious + * that each update needs restyling, and each restyling needs an update. + * + * @param update Update to carry out + */ + private void updateStyling(Runnable update) { + update.run(); + + try { + this.setStyleSpans(0, recomputePainting()); + } catch (Exception e) { + // we ignore these particular exceptions because they're + // commonly thrown when the text is being edited while + // the layering algorithm runs, and it doesn't matter + if ("StyleSpan's length cannot be negative".equals(e.getMessage()) + || e.getMessage().contains("is not a valid range within")) { + return; + } + throw new RuntimeException("Unhandled error while recomputing the styling", e); + } + } + + + /** + * Clears all style layers from their contents, including syntax highlighting. + */ + public void clearStyleLayers() { + updateStyling(() -> { + layersById.values().forEach(StyleLayer::clearStyles); + clearSyntaxHighlighting(); + }); + } + + + /** + * Clears a style layer. + * + * @param id layer id. + */ + public void clearStyleLayer(K id) { + updateStyling(layersById.get(id)::clearStyles); + } + + + /** + * Recomputes a single style spans from the syntax highlighting layer and nodes to highlight. + */ + private StyleSpans<Collection<String>> recomputePainting() { + + List<StyleSpans<Collection<String>>> allSpans = layersById.values().stream() + .flatMap(layer -> layer.getCollections().stream()) + .filter(c -> !c.isEmpty()) + .map(UniformStyleCollection::toSpans) + .collect(Collectors.toList()); + + if (allSpans.isEmpty()) { + return syntaxHighlight.getOrElse(emptySpan()); + } + + if (syntaxHighlight.getOpt().map(StyleSpans::length).filter(l -> l != getLength()).isPresent()) { + // This is only executed if the text has changed (we use the length as an approximation) + // This makes the highlighting much more resilient to staccato code changes, + // which previously would have overlaid an outdated syntax highlighting layer on the + // up-to-date node highlights, making the highlighting twitch briefly before the + // asynchronous syntax highlight catches up + updateSyntaxHighlightingSynchronously(); + } + + syntaxHighlight.ifPresent(allSpans::add); + + final StyleSpans<Collection<String>> base = allSpans.get(0); + + return allSpans.stream() + .filter(spans -> spans != base) + .filter(spans -> spans.length() <= getLength()) + .reduce(base, (accumulator, elt) -> accumulator.overlay(elt, SyntaxHighlightingCodeArea::additiveOverlay)); + + + } + + + /** + * Attempts to preserve the other layers when syntax highlighting changes. The result + * misplaces some style classes, which is undesirable, but covered up by the subsequent + * parsing update. + */ + @Override + protected final StyleSpans<Collection<String>> styleSyntaxHighlightChange(final Optional<StyleSpans<Collection<String>>> oldSyntax, + final StyleSpans<Collection<String>> newSyntax) { + + StyleSpans<Collection<String>> currentSpans = getStyleSpans(new IndexRange(0, getLength())); + StyleSpans<Collection<String>> base = oldSyntax.map(s -> subtract(currentSpans, s)).orElse(currentSpans); + + return Optional.ofNullable(newSyntax) + .map(s -> base.overlay(s, SyntaxHighlightingCodeArea::additiveOverlay)) + .orElse(base) + .subView(0, getLength()); + } + + + /** Subtracts the second argument from the first. */ + private static StyleSpans<Collection<String>> subtract(StyleSpans<Collection<String>> base, StyleSpans<Collection<String>> diff) { + return base.overlay(diff, (style1, style2) -> { + if (style2.isEmpty()) { + return style1; + } + Set<String> styles = new HashSet<>(style1); + styles.removeAll(style2); + return styles; + }); + } + + /** Identifier for a highlighting layer. */ + public interface LayerId { + /** + * Returns the style class associated with that layer. + * Nodes styled in that layer will have this style class. + * + * @return The style class + */ + String getStyleClass(); + } +} diff --git a/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/util/codearea/NodeStyleSpan.java b/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/util/codearea/NodeStyleSpan.java new file mode 100644 index 00000000000..52eb8ba3e09 --- /dev/null +++ b/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/util/codearea/NodeStyleSpan.java @@ -0,0 +1,153 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.util.fxdesigner.util.codearea; + +import java.util.Collection; +import java.util.Comparator; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.fxmisc.richtext.model.Paragraph; +import org.fxmisc.richtext.model.StyledDocument; + +import net.sourceforge.pmd.lang.ast.Node; +import net.sourceforge.pmd.util.fxdesigner.util.TextAwareNodeWrapper; + + +/** + * Wrapper around a node used to declutter the layering algorithm with + * convenience methods. The point is that it's aware of the code area. + * See also {@link #snapshot()}. + * + * @author Clément Fournier + * @since 6.5.0 + */ +class NodeStyleSpan { + + private static final Pattern TAB_INDENT = Pattern.compile("^(\t*).*$"); + private static final Comparator<NodeStyleSpan> COMPARATOR = Comparator.comparing(NodeStyleSpan::getNode, Comparator.comparingInt(Node::getBeginLine).thenComparing(Node::getBeginColumn)); + private final Node node; + private final SyntaxHighlightingCodeArea codeArea; + + + private NodeStyleSpan(Node node, SyntaxHighlightingCodeArea codeArea) { + this.node = node; + this.codeArea = codeArea; + } + + + /** Gets the underlying node. */ + public Node getNode() { + return node; + } + + + /** + * Snapshots the absolute coordinates of the node in the code area + * for the duration of the layering algorithm. + */ + // TODO I don't think there's any good reason for this laziness, + // if anything, it may cause trouble if the layering algorithm uses + // a snapshot taken too late, with outdated line and column coordinates + // I originally wrote it like that because I didn't think enough about it, + // and I don't have time to simplify it before 6.5.0 + public PositionSnapshot snapshot() { + int lastKnownStart = getAbsolutePosition(node.getBeginLine(), node.getBeginColumn() - 1); + int lastKnownEnd = getAbsolutePosition(node.getEndLine(), node.getEndColumn()); + return new PositionSnapshot(lastKnownStart, lastKnownEnd); + } + + private int getAbsolutePosition(int line, int column) { + return codeArea.getAbsolutePosition(line - 1, column) - indentationOffset(line - 1); + } + + + // CodeArea counts a tab as 1 column width but displays it as 8 columns width. + // PMD counts it correctly as 8 columns, so we must offset the position + private int indentationOffset(int paragraph) { + Paragraph<Collection<String>, String, Collection<String>> p = codeArea.getParagraph(paragraph); + Matcher m = TAB_INDENT.matcher(p.getText()); + if (m.matches()) { + return m.group(1).length() * 7; + } + return 0; + } + + + @Override + public String toString() { + return node.getXPathNodeName() + "@" + snapshot(); + } + + + /** + * Returns a comparator that orders spans according to the start + * index of the node they wrap. + */ + public static Comparator<NodeStyleSpan> documentOrderComparator() { + return COMPARATOR; + } + + + /** Builds a new node style span. */ + public static NodeStyleSpan fromNode(Node node, SyntaxHighlightingCodeArea codeArea) { + return new NodeStyleSpan(node, codeArea); + } + + + /** + * Snapshot of the node's absolute position in the code area. + */ + class PositionSnapshot implements TextAwareNodeWrapper { + private int beginIndex; + private int endIndex; + + + PositionSnapshot(int beginIndex, int endIndex) { + this.beginIndex = beginIndex; + this.endIndex = endIndex; + } + + + @Override + public String toString() { + // debug only + return getNodeText() + "@[" + beginIndex + "," + endIndex + ']'; + } + + + @Override + public String getNodeText() { + return codeArea.getText(beginIndex, endIndex); + } + + + @Override + public StyledDocument<Collection<String>, String, Collection<String>> getNodeRichText() { + return codeArea.subDocument(beginIndex, endIndex); + } + + + int getBeginIndex() { + return beginIndex; + } + + + int getEndIndex() { + return endIndex; + } + + + int getLength() { + return endIndex - beginIndex; + } + + + @Override + public Node getNode() { + return NodeStyleSpan.this.getNode(); + } + } +} diff --git a/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/util/codearea/SimpleRegexSyntaxHighlighter.java b/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/util/codearea/SimpleRegexSyntaxHighlighter.java new file mode 100644 index 00000000000..31ba2630e70 --- /dev/null +++ b/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/util/codearea/SimpleRegexSyntaxHighlighter.java @@ -0,0 +1,250 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.util.fxdesigner.util.codearea; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +import org.apache.commons.lang3.RandomStringUtils; +import org.fxmisc.richtext.model.StyleSpans; +import org.fxmisc.richtext.model.StyleSpansBuilder; + + +/** + * Language-specific engine for syntax highlighting. This implementation tokenises the text using regex, and assigns a + * specific CSS class to every found token. The whole text also receives a style class named after the language of the + * tokenizer (e.g. "xml" or "java"). Styling of each class is then done in stylesheets. + * + * @author Clément Fournier + * @since 6.0.0 + */ +public abstract class SimpleRegexSyntaxHighlighter implements SyntaxHighlighter { + + private final RegexHighlightGrammar grammar; + private final String languageName; + + + /** + * Creates a highlighter given a name for the language and a "regex grammar". + * + * @param languageName The language name + * @param grammar The grammar + */ + protected SimpleRegexSyntaxHighlighter(String languageName, final RegexHighlightGrammar grammar) { + this.grammar = grammar; + this.languageName = languageName; + + grammar.addCommonClass("code"); + grammar.addCommonClass(languageName); + } + + + @Override + public StyleSpans<Collection<String>> computeHighlighting(String text) { + StyleSpansBuilder<Collection<String>> builder = new StyleSpansBuilder<>(); + Matcher matcher = grammar.getMatcher(text); + int lastKwEnd = 0; + + final Set<String> onlyLang = Collections.singleton(languageName); + try { + while (matcher.find()) { + Set<String> styleClasses = grammar.getCssClassesOfLastGroup(matcher); + + builder.add(onlyLang, matcher.start() - lastKwEnd); + builder.add(styleClasses, matcher.end() - matcher.start()); + + lastKwEnd = matcher.end(); + } + } catch (StackOverflowError ignored) { + // matcher.find overflowed, might happen when coloring ginormous files with incorrect language + } + + // add the remainder + builder.add(onlyLang, text.length() - lastKwEnd); + + return builder.create(); + } + + + @Override + public final String getLanguageTerseName() { + return languageName; + } + + + /** + * Returns a regex alternation for the given words. + * The words must not begin with an escaped character. + * + * @param alternatives Words to join + */ + protected static String alternation(String[] alternatives) { + // first characters of each alternative, to optimise the regex + String firstChars = Arrays.stream(alternatives) + .map(s -> s.substring(0, 1)) + .distinct() + .reduce((s1, s2) -> s1 + s2) + .get(); + + String alt = "(?=[" + firstChars + "])(?:" + String.join("|", alternatives) + ")"; + + return asWord(alt); + } + + + protected static String asWord(String regex) { + return "(?:\\b" + regex + "\\b)"; + } + + /** + * Gets a builder to make a grammar to build a highlighter. + * + * @param cssClass The css class of the first pattern + * @param pattern The first pattern + * + * @return A builder + */ + protected static RegexHighlightGrammarBuilder grammarBuilder(Collection<String> cssClass, String pattern) { + return new RegexHighlightGrammarBuilder(cssClass, pattern); + } + + + /** + * Builds a highlight grammar in a concise way. + */ + protected static final class RegexHighlightGrammarBuilder { + + private Map<String, String> groupNameToRegex = new LinkedHashMap<>(); + private Map<String, Set<String>> groupNameToCssClasses = new LinkedHashMap<>(); + + + RegexHighlightGrammarBuilder(Collection<String> cssClass, String regex) { + or(cssClass, regex); + } + + + /** + * Adds a branch to the alternation (...|pattern). Order is important. + * + * @param cssClass css class that the matching regions should bear + * @param regex Regex pattern + * + * @return The same builder + */ + public RegexHighlightGrammarBuilder or(Collection<String> cssClass, String regex) { + String groupName = RandomStringUtils.randomAlphabetic(8); + groupNameToRegex.put(groupName, regex); + groupNameToCssClasses.put(groupName, new HashSet<>(cssClass)); + return this; + } + + + private String namedGroup(String name, String regex) { + return "(?<" + name + ">(?:" + regex + "))"; + } + + + private String getRegexString() { + return String.join("|", groupNameToRegex.entrySet() + .stream() + .map(e -> namedGroup(e.getKey(), e.getValue())) + .collect(Collectors.toList())); + } + + + /** + * Builds the grammar. + * + * @return A new grammar + */ + public RegexHighlightGrammar create() { + return new RegexHighlightGrammar(Pattern.compile(getRegexString()), + getGroupNamesToCssClasses()); + } + + + /** + * Builds the grammar. + * + * @param flags Regex compilation flags + * + * @return A new grammar + */ + public RegexHighlightGrammar create(int flags) { + return new RegexHighlightGrammar(Pattern.compile(getRegexString(), flags), + getGroupNamesToCssClasses()); + } + + + private Map<String, Set<String>> getGroupNamesToCssClasses() { + return Collections.unmodifiableMap(groupNameToCssClasses); + } + } + + + /** + * Describes the tokens of the language that should be colored with a regular expression. + */ + protected static class RegexHighlightGrammar { + + private final Pattern pattern; + private final Map<String, Set<String>> namesToCssClass; + + + RegexHighlightGrammar(Pattern pattern, Map<String, Set<String>> namesToCssClass) { + this.pattern = pattern; + this.namesToCssClass = namesToCssClass; + } + + + /** + * Gets a matcher for the given piece of text. + * + * @return A matcher + */ + Matcher getMatcher(String text) { + return pattern.matcher(text); + } + + + /** + * Adds a css class to all the tokens described by this grammar. + * + * @param css The css class to add + */ + void addCommonClass(String css) { + namesToCssClass.values().forEach(e -> e.add(css)); + } + + + /** + * Gets the css class that should be applied to the last matched group (token), according to this grammar. + * + * @param matcher The matcher in which to look + * + * @return The name of the css class corresponding to the token + */ + Set<String> getCssClassesOfLastGroup(Matcher matcher) { + for (Entry<String, Set<String>> groupToClass : namesToCssClass.entrySet()) { + if (matcher.group(groupToClass.getKey()) != null) { + return groupToClass.getValue(); + } + } + + return Collections.emptySet(); + } + + } + +} diff --git a/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/util/codearea/StyleLayer.java b/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/util/codearea/StyleLayer.java new file mode 100644 index 00000000000..b06177c16ea --- /dev/null +++ b/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/util/codearea/StyleLayer.java @@ -0,0 +1,73 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.util.fxdesigner.util.codearea; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; + + +/** + * Represents a layer of styling in the text. Several layers are + * aggregated into a {@link StyleContext}, and can evolve + * independently. + */ +class StyleLayer { + + + private final Map<Set<String>, UniformStyleCollection> styleToCollection = new HashMap<>(); + + + /** Reset this layer to its empty state, clearing all the styles. */ + public void clearStyles() { + styleToCollection.clear(); + } + + + public Collection<UniformStyleCollection> getCollections() { + return styleToCollection.values(); + } + + + public void styleNodes(UniformStyleCollection updates) { + styleNodes(false, updates); + } + + + public void styleNodes(boolean reset, UniformStyleCollection updates) { + if (reset) { + clearStyles(); + } + + UniformStyleCollection newValue = Optional.ofNullable(styleToCollection.get(updates.getStyle())) + .map(updates::merge) + .orElse(updates); + + styleToCollection.put(updates.getStyle(), newValue); + } + + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + StyleLayer that = (StyleLayer) o; + return Objects.equals(styleToCollection, that.styleToCollection); + } + + + @Override + public int hashCode() { + + return Objects.hash(styleToCollection); + } +} diff --git a/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/util/codearea/SyntaxHighlighter.java b/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/util/codearea/SyntaxHighlighter.java new file mode 100644 index 00000000000..c28af2d5b92 --- /dev/null +++ b/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/util/codearea/SyntaxHighlighter.java @@ -0,0 +1,39 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.util.fxdesigner.util.codearea; + +import java.util.Collection; + +import org.fxmisc.richtext.model.StyleSpans; + +/** + * Language-specific engine for syntax highlighting. + * + * @author Clément Fournier + * @since 6.0.0 + */ +public interface SyntaxHighlighter { + + /** + * Gets the terse name of the language this highlighter cares for. That's used as a css class for text regions. + * + * @return The terse name of the language + */ + String getLanguageTerseName(); + + + /** + * Computes the syntax highlighting on the given text. + * The returned spans are exactly the length of the given + * text. + * + * @param text The text + * + * @return The computed style spans + */ + StyleSpans<Collection<String>> computeHighlighting(String text); + + +} diff --git a/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/util/codearea/SyntaxHighlightingCodeArea.java b/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/util/codearea/SyntaxHighlightingCodeArea.java new file mode 100644 index 00000000000..3bd65123ebb --- /dev/null +++ b/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/util/codearea/SyntaxHighlightingCodeArea.java @@ -0,0 +1,214 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.util.fxdesigner.util.codearea; + +import java.time.Duration; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +import org.fxmisc.richtext.CodeArea; +import org.fxmisc.richtext.model.StyleSpans; +import org.reactfx.EventStream; +import org.reactfx.Subscription; +import org.reactfx.value.Val; +import org.reactfx.value.Var; + +import net.sourceforge.pmd.lang.ast.Node; +import net.sourceforge.pmd.util.fxdesigner.util.TextAwareNodeWrapper; + +import javafx.concurrent.Task; +import javafx.event.EventHandler; +import javafx.scene.Scene; +import javafx.stage.WindowEvent; + + +/** + * Code area that can handle syntax highlighting. Syntax highlighting is performed asynchronously + * by another thread. It can be enabled by providing a {@link SyntaxHighlighter} to + * {@link #setSyntaxHighlighter(SyntaxHighlighter)}, and disabled by passing a {@code null} reference + * to that method. + * + * @see AvailableSyntaxHighlighters + * @author Clément Fournier + * @since 6.0.0 + */ +public class SyntaxHighlightingCodeArea extends CodeArea { + + /** Minimum delay between each code highlighting recomputation. Changes are ignored until then. */ + private static final Duration TEXT_CHANGE_DELAY = Duration.ofMillis(30); + + /** Current subscription to syntax highlighting auto-refresh. */ + private final Var<Subscription> syntaxAutoRefresh = Var.newSimpleVar(null); + + /** Current syntax highlighter. Can be absent. */ + private final Var<SyntaxHighlighter> syntaxHighlighter = Var.newSimpleVar(null); + + /** Current highlighting spans. */ + private final Var<StyleSpans<Collection<String>>> currentSyntaxHighlight = Var.newSimpleVar(null); + + /** Read-only view on the current highlighting spans. Can be absent. */ + protected final Val<StyleSpans<Collection<String>>> syntaxHighlight = currentSyntaxHighlight; + + + public SyntaxHighlightingCodeArea() { + // captured in the closure + final EventHandler<WindowEvent> autoCloseHandler = e -> syntaxAutoRefresh.ifPresent(Subscription::unsubscribe); + + // handles auto shutdown of executor services + // by attaching a handler to the stage responsible for the control + Val.wrap(sceneProperty()) + .filter(Objects::nonNull) + .flatMap(Scene::windowProperty) + .values() + .filter(Objects::nonNull) + .subscribe(c -> c.addEventHandler(WindowEvent.WINDOW_CLOSE_REQUEST, autoCloseHandler)); + } + + + /** + * Enables syntax highlighting if disabled and sets it to use the given highlighter. + * If the argument is null, then this method disables syntax highlighting. + */ + public void setSyntaxHighlighter(SyntaxHighlighter highlighter) { + + if (Objects.equals(highlighter, syntaxHighlighter.getValue())) { + return; + } + + syntaxHighlighter.ifPresent(previous -> getStyleClass().remove("." + previous.getLanguageTerseName())); + syntaxAutoRefresh.ifPresent(Subscription::unsubscribe); + + if (highlighter == null) { + syntaxAutoRefresh.setValue(null); + this.setCurrentSyntaxHighlight(null); + return; + } + + syntaxHighlighter.setValue(highlighter); + + getStyleClass().add("." + highlighter.getLanguageTerseName()); + syntaxAutoRefresh.setValue(subscribeSyntaxHighlighting(defaultHighlightingTicks(), highlighter)); + + try { // refresh the highlighting once. + Task<StyleSpans<Collection<String>>> t = computeHighlightingAsync(Executors.newSingleThreadExecutor(), highlighter, getText()); + t.setOnSucceeded(e -> this.setCurrentSyntaxHighlight(t.getValue())); + } catch (Exception ignored) { + // nevermind + } + } + + + public Val<Boolean> syntaxHighlightingEnabledProperty() { + return syntaxHighlighter.map(Objects::nonNull); + } + + + private EventStream<?> defaultHighlightingTicks() { + return this.plainTextChanges() + .filter(ch -> !ch.isIdentity()) + .distinct(); + } + + + private Subscription subscribeSyntaxHighlighting(EventStream<?> ticks, SyntaxHighlighter highlighter) { + // captured in the closure, shutdown when unsubscribing + final ExecutorService executorService = Executors.newSingleThreadExecutor(); + return ticks.successionEnds(TEXT_CHANGE_DELAY) + .supplyTask(() -> computeHighlightingAsync(executorService, highlighter, this.getText())) + .awaitLatest(ticks) + .filterMap(t -> { + t.ifFailure(Throwable::printStackTrace); + return t.toOptional(); + }) + .subscribe(this::setCurrentSyntaxHighlight) + .and(executorService::shutdownNow); + } + + + private static Task<StyleSpans<Collection<String>>> computeHighlightingAsync(ExecutorService service, SyntaxHighlighter highlighter, String text) { + Task<StyleSpans<Collection<String>>> task = new Task<StyleSpans<Collection<String>>>() { + @Override + protected StyleSpans<Collection<String>> call() { + return highlighter.computeHighlighting(text); + } + }; + if (!service.isShutdown()) { + service.execute(task); + } + return task; + } + + /** Removes the current syntax highlighting span. */ + protected void clearSyntaxHighlighting() { + setCurrentSyntaxHighlight(null); + } + + /** + * Update the syntax highlighting to the specified value. + * If null, syntax highlighting is stripped off. + */ + private void setCurrentSyntaxHighlight(final StyleSpans<Collection<String>> newSyntax) { + Optional<StyleSpans<Collection<String>>> oldSyntaxHighlight = currentSyntaxHighlight.getOpt(); + this.currentSyntaxHighlight.setValue(newSyntax); + + setStyleSpans(0, styleSyntaxHighlightChange(oldSyntaxHighlight, newSyntax)); + } + + + /** + * Given the old value of the highlighting spans, and a newly computed value, + * computes the spans as they should be applied to the codearea. The default behaviour + * simply returns the newest spans, which works flawlessly when there is no other + * style layer in the game. Subclasses are free to override, to get a chance to + * preserve additional style layers. + */ + protected StyleSpans<Collection<String>> styleSyntaxHighlightChange(final Optional<StyleSpans<Collection<String>>> oldSyntax, + final StyleSpans<Collection<String>> newSyntax) { + return newSyntax; + } + + + /** + * Forces synchronous updating of the syntax highlighting. + * This can be done when we suspect the highlighting is outdated + * but we really need the most up to date one, for example because + * we want to overlay other spans on it. + */ + protected void updateSyntaxHighlightingSynchronously() { + syntaxHighlighter.getOpt().map(h -> h.computeHighlighting(getText())).ifPresent(currentSyntaxHighlight::setValue); + } + + + protected StyleSpans<Collection<String>> emptySpan() { + return StyleSpans.singleton(Collections.emptyList(), getLength()); + } + + + /** Overlay operation that stacks up the style classes of the two overlaid spans. */ + protected static Collection<String> additiveOverlay(Collection<String> style1, Collection<String> style2) { + // todo using persistent collections here may be beneficial + if (style1.isEmpty()) { + return style2; + } else if (style2.isEmpty()) { + return style1; + } + Set<String> styles = new HashSet<>(style1); + styles.addAll(style2); + return styles; + } + + + /** Wraps a node into a convenience layer that can for example provide the rich text associated with it. */ + public TextAwareNodeWrapper wrapNode(Node node) { + return NodeStyleSpan.fromNode(node, this).snapshot(); + } + +} diff --git a/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/util/codearea/UniformStyleCollection.java b/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/util/codearea/UniformStyleCollection.java new file mode 100644 index 00000000000..7a713bf57d2 --- /dev/null +++ b/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/util/codearea/UniformStyleCollection.java @@ -0,0 +1,236 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.util.fxdesigner.util.codearea; + +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Deque; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.fxmisc.richtext.model.StyleSpans; +import org.fxmisc.richtext.model.StyleSpansBuilder; + +import net.sourceforge.pmd.lang.ast.Node; +import net.sourceforge.pmd.util.fxdesigner.util.codearea.NodeStyleSpan.PositionSnapshot; + + +/** + * Collection of nodes that share the same style. In case of overlap, + * the nested ones gain css classes like depth-0, depth-1, etc. A + * collection can be overlaid into a single span in one pass using + * {@link #toSpans()}. + * + * @author Clément Fournier + * @since 6.5.0 + */ +public class UniformStyleCollection { + + private static final Map<Set<String>, Map<Integer, Map<Boolean, Set<String>>>> DEPTH_STYLE_CACHE = new HashMap<>(); + + private final Set<String> style; + // sorted in document order + private final List<NodeStyleSpan> nodes; + private StyleSpans<Collection<String>> spanCache; + + public UniformStyleCollection(Set<String> style, Collection<NodeStyleSpan> ns) { + this.style = style; + this.nodes = new ArrayList<>(ns); + nodes.sort(NodeStyleSpan.documentOrderComparator()); + } + + + public boolean isEmpty() { + return nodes.isEmpty(); + } + + + public Set<String> getStyle() { + return style; + } + + + public UniformStyleCollection merge(UniformStyleCollection collection) { + assert collection.getStyle().equals(getStyle()); + + if (collection.isEmpty()) { + return this; + } else if (this.isEmpty()) { + return collection; + } else { + Set<NodeStyleSpan> merged = new HashSet<>(nodes); + merged.addAll(collection.nodes); + + return new UniformStyleCollection(style, merged); + } + } + + + private boolean useInlineHighlight(Node node) { + return node.getBeginLine() == node.getEndLine(); + } + + + private Set<String> styleForDepth(int depth, PositionSnapshot n) { + return styleForDepth(depth, n != null && useInlineHighlight(n.getNode())); + } + + + private Set<String> styleForDepth(int depth, boolean inlineHighlight) { + if (depth < 0) { + // that's the style when we're outside any node + return Collections.emptySet(); + } else { + // Caching reduces the number of sets used by this step of the overlaying routine to + // only a few. The number is probably blowing up during the actual spans overlaying + // in StyleContext#recomputePainting + DEPTH_STYLE_CACHE.putIfAbsent(style, new HashMap<>()); + Map<Integer, Map<Boolean, Set<String>>> depthToStyle = DEPTH_STYLE_CACHE.get(style); + + depthToStyle.putIfAbsent(depth, new HashMap<>()); + Map<Boolean, Set<String>> isInlineToStyle = depthToStyle.get(depth); + + if (isInlineToStyle.containsKey(inlineHighlight)) { + return isInlineToStyle.get(inlineHighlight); + } + + Set<String> s = new HashSet<>(style); + s.add("depth-" + depth); + if (inlineHighlight) { + // inline highlight can be used to add boxing around a node if it wouldn't be ugly + s.add("inline-highlight"); + } + isInlineToStyle.put(inlineHighlight, s); + return s; + } + } + + + /** + * Overlays all the nodes in this collection into a single StyleSpans. + * This algorithm makes the strong assumption that the nodes can be + * ordered as a tree, that is, given two nodes n and m, then one of the + * following holds true: + * - m and n are disjoint + * - m is entirely contained within n, or the reverse is true + * + * E.g. [ m ] but not [ m ] + *        [ n ] [ n' ] [ n ] + */ + public StyleSpans<Collection<String>> toSpans() { + // We cache the result so that eg if only the focus node changes, + // we don't have to overlay all XPath results again + if (spanCache == null) { + spanCache = buildSpans(); + } + return spanCache; + } + + public StyleSpans<Collection<String>> buildSpans() { + + if (nodes.isEmpty()) { + return StyleSpans.singleton(Collections.emptyList(), 0); + } else if (nodes.size() == 1) { + PositionSnapshot snapshot = nodes.get(0).snapshot(); + return new StyleSpansBuilder<Collection<String>>().add(Collections.emptyList(), snapshot.getBeginIndex()) + .add(styleForDepth(0, snapshot), snapshot.getLength()) + // we don't bother adding the remainder + .create(); + } + + final StyleSpansBuilder<Collection<String>> builder = new StyleSpansBuilder<>(); + + // stores the parents of the node we're in, to account for nesting depth + final Deque<PositionSnapshot> overlappingNodes = new ArrayDeque<>(); + PositionSnapshot previous = null; + int lastSpanEnd = 0; + + for (NodeStyleSpan span : nodes) { // sorted in document order + + PositionSnapshot current = span.snapshot(); + if (current == null) { + continue; + } + + // first iteration + if (previous == null) { + previous = current; + builder.add(Collections.emptyList(), previous.getBeginIndex()); + lastSpanEnd = previous.getBeginIndex(); + continue; + } + + if (previous.getEndIndex() > current.getBeginIndex()) { + // The current overlaps with the previous + + // This part sometimes throws exceptions when the text changes while the computation is in progress + // In practice, they can totally be ignored since the highlighting will be recomputed next time + // the AST is refreshed, which *will* happen... because the text is being edited + + // gap + builder.add(styleForDepth(overlappingNodes.size() - 1, overlappingNodes.peek()), previous.getBeginIndex() - lastSpanEnd); + // Part between the start of the previous node and the start of the current one. + // this is the underscored part [_[ ] ] + // The current node will be styled on the next iteration. + builder.add(styleForDepth(overlappingNodes.size(), current), current.getBeginIndex() - previous.getBeginIndex()); + lastSpanEnd = current.getBeginIndex(); + + overlappingNodes.addFirst(previous); + + previous = current; + continue; + } else { + // no overlap, the previous span can be added + + // the depth - 1 is for the gap + builder.add(styleForDepth(overlappingNodes.size() - 1, overlappingNodes.peek()), previous.getBeginIndex() - lastSpanEnd); + // previous node + builder.add(styleForDepth(overlappingNodes.size(), previous), previous.getLength()); + lastSpanEnd = previous.getEndIndex(); + previous = current; + } + + // Check whether some of the enclosing spans end between the end of the previous and the beginning of the current + Iterator<PositionSnapshot> overlaps = overlappingNodes.iterator(); + while (overlaps.hasNext()) { + PositionSnapshot enclosing = overlaps.next(); + if (enclosing.getEndIndex() < current.getBeginIndex()) { + overlaps.remove(); + // this is the underscored part [ [ ]_] + builder.add(styleForDepth(overlappingNodes.size(), enclosing), enclosing.getEndIndex() - lastSpanEnd); + lastSpanEnd = enclosing.getEndIndex(); + } + } + } + + // gap + builder.add(styleForDepth(overlappingNodes.size() - 1, overlappingNodes.peek()), previous.getBeginIndex() - lastSpanEnd); + // last node + builder.add(styleForDepth(overlappingNodes.size(), previous), previous.getLength()); + lastSpanEnd = previous.getEndIndex(); + + // close the remaining enclosing contexts + int depth = overlappingNodes.size(); + for (PositionSnapshot enclosing : overlappingNodes) { + depth--; + builder.add(styleForDepth(depth, enclosing), enclosing.getEndIndex() - lastSpanEnd); + lastSpanEnd = enclosing.getEndIndex(); + } + + return builder.create(); + } + + + /** Returns an empty style collection. */ + public static UniformStyleCollection empty() { + return new UniformStyleCollection(Collections.emptySet(), Collections.emptySet()); + } +} diff --git a/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/util/codearea/syntaxhighlighting/ApexSyntaxHighlighter.java b/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/util/codearea/syntaxhighlighting/ApexSyntaxHighlighter.java new file mode 100644 index 00000000000..819a4df65ed --- /dev/null +++ b/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/util/codearea/syntaxhighlighting/ApexSyntaxHighlighter.java @@ -0,0 +1,75 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.util.fxdesigner.util.codearea.syntaxhighlighting; + +import static net.sourceforge.pmd.util.fxdesigner.util.codearea.syntaxhighlighting.HighlightClasses.ANNOTATION; +import static net.sourceforge.pmd.util.fxdesigner.util.codearea.syntaxhighlighting.HighlightClasses.BOOLEAN; +import static net.sourceforge.pmd.util.fxdesigner.util.codearea.syntaxhighlighting.HighlightClasses.BRACE; +import static net.sourceforge.pmd.util.fxdesigner.util.codearea.syntaxhighlighting.HighlightClasses.BRACKET; +import static net.sourceforge.pmd.util.fxdesigner.util.codearea.syntaxhighlighting.HighlightClasses.KEYWORD; +import static net.sourceforge.pmd.util.fxdesigner.util.codearea.syntaxhighlighting.HighlightClasses.MULTIL_COMMENT; +import static net.sourceforge.pmd.util.fxdesigner.util.codearea.syntaxhighlighting.HighlightClasses.PAREN; +import static net.sourceforge.pmd.util.fxdesigner.util.codearea.syntaxhighlighting.HighlightClasses.SEMICOLON; +import static net.sourceforge.pmd.util.fxdesigner.util.codearea.syntaxhighlighting.HighlightClasses.SINGLEL_COMMENT; +import static net.sourceforge.pmd.util.fxdesigner.util.codearea.syntaxhighlighting.HighlightClasses.STRING; + +import java.util.regex.Pattern; + +import net.sourceforge.pmd.util.fxdesigner.util.codearea.SimpleRegexSyntaxHighlighter; + +/** + * Syntax highlighter for Apex. + * + * @author Clément Fournier + * @since 6.0.0 + */ +public class ApexSyntaxHighlighter extends SimpleRegexSyntaxHighlighter { + + + private static final String[] KEYWORDS = { + "abstract", "activate", "and", "any", "array", "as", + "asc", "autonomous", "begin", "bigdecimal", "blob", + "break", "bulk", "by", "byte", "case", "cast", "catch", + "char", "class", "collect", "commit", "const", "continue", + "convertcurrency", "decimal", "default", "delete", "desc", + "do", "else", "end", "enum", "exception", "exit", "export", + "extends", "false", "final", "finally", "float", "for", "from", + "future", "global", "goto", "group", "having", "hint", "if", + "implements", "import", "inner", "insert", "instanceof", + "interface", "into", "int", "join", "last_90_days", "last_month", + "last_n_days", "last_week", "like", "limit", "list", "long", + "loop", "map", "merge", "new", "next_90_days", "next_month", + "next_n_days", "next_week", "not", "null", "nulls", "number", + "object", "of", "on", "or", "outer", "override", "package", + "parallel", "pragma", "private", "protected", "public", "retrieve", + "return", "returning", "rollback", "savepoint", "search", "select", + "set", "short", "sort", "stat", "static", "super", "switch", "synchronized", + "system", "testmethod", "then", "this", "this_month", "this_week", + "throw", "today", "tolabel", "tomorrow", "transaction", "trigger", + "true", "try", "type", "undelete", "update", "upsert", "using", + "virtual", "webservice", "when", "where", "while", "yesterday", + "after", "before", "count", "excludes", "first", "includes", + "last", "order", "sharing", "with", + }; + + private static final RegexHighlightGrammar GRAMMAR + = grammarBuilder(SINGLEL_COMMENT.css, "//[^\r\n]*") + .or(MULTIL_COMMENT.css, "/\\*.*?\\*/") + .or(KEYWORD.css, "(?i)" + alternation(KEYWORDS)) + .or(PAREN.css, "[()]") + .or(BRACE.css, "[{}]") + .or(BRACKET.css, "[\\[]]") + .or(SEMICOLON.css, ";") + .or(STRING.css, "'[^'\\\\]*(\\\\.[^'\\\\]*)*'") + .or(BOOLEAN.css, asWord("(?i)true|false")) + .or(ANNOTATION.css, "@[\\w]+") + .create(Pattern.DOTALL | Pattern.CASE_INSENSITIVE); + + public ApexSyntaxHighlighter() { + super("apex", GRAMMAR); + } + + +} diff --git a/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/util/codearea/syntaxhighlighting/HighlightClasses.java b/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/util/codearea/syntaxhighlighting/HighlightClasses.java new file mode 100644 index 00000000000..d1148ab687f --- /dev/null +++ b/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/util/codearea/syntaxhighlighting/HighlightClasses.java @@ -0,0 +1,71 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.util.fxdesigner.util.codearea.syntaxhighlighting; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + + +/** + * @author Clément Fournier + * @since 6.0.0 + */ +public enum HighlightClasses { + + COMMENT(Constants.COMMENT), + MULTIL_COMMENT("multi-line-comment", Constants.COMMENT), + SINGLEL_COMMENT("single-line-comment", Constants.COMMENT), + + PUNCTUATION(Constants.PUNCTUATION), + PAREN("paren", Constants.PUNCTUATION), + BRACE("brace", Constants.PUNCTUATION), + BRACKET("bracket", Constants.PUNCTUATION), + SEMICOLON("semicolon", Constants.PUNCTUATION), + + LITERAL(Constants.LITERAL), + BOOLEAN("boolean", Constants.LITERAL), + STRING("string", Constants.LITERAL), + URI("uri", "string", Constants.LITERAL), + CHAR("char", Constants.LITERAL), + NULL("null", Constants.LITERAL), + NUMBER("number", Constants.LITERAL), + + KEYWORD("keyword"), + ANNOTATION("annotation"), + CLASS_IDENTIFIER("class-identifier"), + + // XPath specific + XPATH_ATTRIBUTE("attribute", Constants.XPATH), + XPATH_AXIS("axis", Constants.XPATH), + XPATH_FUNCTION("function", Constants.XPATH), + XPATH_PATH("path", Constants.XPATH, Constants.PUNCTUATION), + XPATH_KIND_TEST("kind-test", "function", Constants.XPATH), + + XML_CDATA_TAG("cdata-tag", Constants.XML), + XML_CDATA_CONTENT("cdata-content", Constants.XML), + XML_PROLOG("xml-prolog", Constants.XML), + XML_LT_GT("lt-gt", Constants.XML), + XML_TAG_NAME("tag-name", Constants.XML), + XML_ATTRIBUTE_NAME("attribute-name", Constants.XML); + + + /** Name of the css class. */ + public final List<String> css; + + + HighlightClasses(String... classes) { + this.css = Collections.unmodifiableList(Arrays.asList(classes)); + } + + + private static final class Constants { + static final String LITERAL = "literal"; + static final String COMMENT = "comment"; + static final String PUNCTUATION = "punctuation"; + static final String XML = "xml"; + static final String XPATH = "xpath"; + } +} diff --git a/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/util/codearea/syntaxhighlighting/JavaSyntaxHighlighter.java b/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/util/codearea/syntaxhighlighting/JavaSyntaxHighlighter.java new file mode 100644 index 00000000000..99ce7dda024 --- /dev/null +++ b/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/util/codearea/syntaxhighlighting/JavaSyntaxHighlighter.java @@ -0,0 +1,72 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.util.fxdesigner.util.codearea.syntaxhighlighting; + +import static net.sourceforge.pmd.util.fxdesigner.util.codearea.syntaxhighlighting.HighlightClasses.ANNOTATION; +import static net.sourceforge.pmd.util.fxdesigner.util.codearea.syntaxhighlighting.HighlightClasses.BOOLEAN; +import static net.sourceforge.pmd.util.fxdesigner.util.codearea.syntaxhighlighting.HighlightClasses.BRACE; +import static net.sourceforge.pmd.util.fxdesigner.util.codearea.syntaxhighlighting.HighlightClasses.BRACKET; +import static net.sourceforge.pmd.util.fxdesigner.util.codearea.syntaxhighlighting.HighlightClasses.CHAR; +import static net.sourceforge.pmd.util.fxdesigner.util.codearea.syntaxhighlighting.HighlightClasses.CLASS_IDENTIFIER; +import static net.sourceforge.pmd.util.fxdesigner.util.codearea.syntaxhighlighting.HighlightClasses.KEYWORD; +import static net.sourceforge.pmd.util.fxdesigner.util.codearea.syntaxhighlighting.HighlightClasses.MULTIL_COMMENT; +import static net.sourceforge.pmd.util.fxdesigner.util.codearea.syntaxhighlighting.HighlightClasses.NULL; +import static net.sourceforge.pmd.util.fxdesigner.util.codearea.syntaxhighlighting.HighlightClasses.NUMBER; +import static net.sourceforge.pmd.util.fxdesigner.util.codearea.syntaxhighlighting.HighlightClasses.PAREN; +import static net.sourceforge.pmd.util.fxdesigner.util.codearea.syntaxhighlighting.HighlightClasses.SEMICOLON; +import static net.sourceforge.pmd.util.fxdesigner.util.codearea.syntaxhighlighting.HighlightClasses.SINGLEL_COMMENT; +import static net.sourceforge.pmd.util.fxdesigner.util.codearea.syntaxhighlighting.HighlightClasses.STRING; + +import java.util.regex.Pattern; + +import net.sourceforge.pmd.util.fxdesigner.util.codearea.SimpleRegexSyntaxHighlighter; + +/** + * Syntax highlighter for Java. + * + * @author Clément Fournier + * @since 6.0.0 + */ +public final class JavaSyntaxHighlighter extends SimpleRegexSyntaxHighlighter { + + + private static final String[] KEYWORDS = { + "public", "return", "final", "import", "static", "new", + "extends", "int", "throws?", "void", "if", "this", + "private", "class", "else", "case", "package", "abstract", + "boolean", "break", "byte", "catch", "char", "for", + "continue", "default", "double", "enum", "finally", + "float", "implements", "instanceof", "interface", "long", + "native", "protected", "while", "assert", "short", "super", + "switch", "synchronized", "transient", "try", "volatile", + "do", "strictfp", "goto", "const", "open", + "module", "requires", "transitive", "exports", + "opens", "to", "uses", "provides", "var", "with", + }; + + + private static final RegexHighlightGrammar GRAMMAR + = grammarBuilder(SINGLEL_COMMENT.css, "//[^\n]*") + .or(MULTIL_COMMENT.css, "/\\*.*?\\*/") + .or(PAREN.css, "[()]") + .or(NUMBER.css, asWord("\\d[_\\d]*+(\\.\\d(_?\\d)*+)?[fdlFDL]?")) + .or(BRACE.css, "[{}]") + .or(BRACKET.css, "[\\[]]") + .or(SEMICOLON.css, ";") + .or(KEYWORD.css, alternation(KEYWORDS)) + .or(STRING.css, "\"[^\"\\\\]*(\\\\.[^\"\\\\]*)*\"") + .or(CHAR.css, "'(?:[^']|\\\\(?:'|u\\w{4}))'") // char + .or(NULL.css, asWord("null")) + .or(BOOLEAN.css, asWord("true|false")) + .or(ANNOTATION.css, "@[\\w]+") + .or(CLASS_IDENTIFIER.css, asWord("[A-Z][\\w_$]*")) + .create(Pattern.DOTALL); + + + public JavaSyntaxHighlighter() { + super("java", GRAMMAR); + } + +} diff --git a/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/util/codearea/syntaxhighlighting/XPathSyntaxHighlighter.java b/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/util/codearea/syntaxhighlighting/XPathSyntaxHighlighter.java new file mode 100644 index 00000000000..538905b9a42 --- /dev/null +++ b/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/util/codearea/syntaxhighlighting/XPathSyntaxHighlighter.java @@ -0,0 +1,72 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.util.fxdesigner.util.codearea.syntaxhighlighting; + +import static net.sourceforge.pmd.util.fxdesigner.util.codearea.syntaxhighlighting.HighlightClasses.BRACKET; +import static net.sourceforge.pmd.util.fxdesigner.util.codearea.syntaxhighlighting.HighlightClasses.KEYWORD; +import static net.sourceforge.pmd.util.fxdesigner.util.codearea.syntaxhighlighting.HighlightClasses.MULTIL_COMMENT; +import static net.sourceforge.pmd.util.fxdesigner.util.codearea.syntaxhighlighting.HighlightClasses.NUMBER; +import static net.sourceforge.pmd.util.fxdesigner.util.codearea.syntaxhighlighting.HighlightClasses.PAREN; +import static net.sourceforge.pmd.util.fxdesigner.util.codearea.syntaxhighlighting.HighlightClasses.STRING; +import static net.sourceforge.pmd.util.fxdesigner.util.codearea.syntaxhighlighting.HighlightClasses.URI; +import static net.sourceforge.pmd.util.fxdesigner.util.codearea.syntaxhighlighting.HighlightClasses.XPATH_ATTRIBUTE; +import static net.sourceforge.pmd.util.fxdesigner.util.codearea.syntaxhighlighting.HighlightClasses.XPATH_AXIS; +import static net.sourceforge.pmd.util.fxdesigner.util.codearea.syntaxhighlighting.HighlightClasses.XPATH_FUNCTION; +import static net.sourceforge.pmd.util.fxdesigner.util.codearea.syntaxhighlighting.HighlightClasses.XPATH_KIND_TEST; +import static net.sourceforge.pmd.util.fxdesigner.util.codearea.syntaxhighlighting.HighlightClasses.XPATH_PATH; + +import net.sourceforge.pmd.util.fxdesigner.util.codearea.SimpleRegexSyntaxHighlighter; + +/** + * @author Clément Fournier + * @since 6.0.0 + */ +public class XPathSyntaxHighlighter extends SimpleRegexSyntaxHighlighter { + + private static final String[] AXIS_NAMES = { + "self", "child", "attribute", "descendant", "descendant-or-self", "ancestor", + "ancestor-or-self", "following", "following-sibling", "namespace", "parent", + "preceding-sibling", + }; + + private static final String[] KEYWORDS = { + "or", "and", "not", "some", "in", "satisfies", + "as", "is", "for", "every", "cast", "castable", + "treat", "instance", "of", "to", "if", "then", "else", + "return", "let", + "intersect", "except", "union", "div", "idiv", "mod", + "ne", "eq", "lt", "le", "gt", "ge", + }; + + private static final String[] KIND_TESTS = { + "node", "document-node", "text", "comment", + "namespace-node", "processing-instruction", + "attribute", "schema-attribute", "element", + "schema-element", "function", + }; + + + private static final RegexHighlightGrammar GRAMMAR + = grammarBuilder(XPATH_ATTRIBUTE.css, "@[\\w]+") + .or(XPATH_PATH.css, "//?") + .or(XPATH_AXIS.css, alternation(AXIS_NAMES) + "::") + .or(KEYWORD.css, alternation(KEYWORDS)) + .or(XPATH_KIND_TEST.css, alternation(KIND_TESTS) + "\\(\\)") + .or(XPATH_FUNCTION.css, "[\\w-]+?(?=\\()") + .or(MULTIL_COMMENT.css, "\\(:.*?:\\)") // comments can be nested but whatever + .or(PAREN.css, "[()]") + .or(BRACKET.css, "[\\[\\]]") + .or(NUMBER.css, "(\\.\\d++\\b|\\b\\d++\\.|(\\b\\d++(\\.\\d*+)?([eE][+-]?\\d+)?))") + .or(STRING.css, "('([^']|'')*')|(\"([^\"]|\"\")*\")") + .or(URI.css, "Q\\{[^{}]*}") + .create(); + + + public XPathSyntaxHighlighter() { + super("xpath", GRAMMAR); + } + + +} diff --git a/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/util/codearea/syntaxhighlighting/XmlSyntaxHighlighter.java b/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/util/codearea/syntaxhighlighting/XmlSyntaxHighlighter.java new file mode 100644 index 00000000000..d336e01adc3 --- /dev/null +++ b/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/util/codearea/syntaxhighlighting/XmlSyntaxHighlighter.java @@ -0,0 +1,41 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.util.fxdesigner.util.codearea.syntaxhighlighting; + +import static net.sourceforge.pmd.util.fxdesigner.util.codearea.syntaxhighlighting.HighlightClasses.MULTIL_COMMENT; +import static net.sourceforge.pmd.util.fxdesigner.util.codearea.syntaxhighlighting.HighlightClasses.XML_ATTRIBUTE_NAME; +import static net.sourceforge.pmd.util.fxdesigner.util.codearea.syntaxhighlighting.HighlightClasses.XML_CDATA_CONTENT; +import static net.sourceforge.pmd.util.fxdesigner.util.codearea.syntaxhighlighting.HighlightClasses.XML_CDATA_TAG; +import static net.sourceforge.pmd.util.fxdesigner.util.codearea.syntaxhighlighting.HighlightClasses.XML_LT_GT; +import static net.sourceforge.pmd.util.fxdesigner.util.codearea.syntaxhighlighting.HighlightClasses.XML_PROLOG; +import static net.sourceforge.pmd.util.fxdesigner.util.codearea.syntaxhighlighting.HighlightClasses.XML_TAG_NAME; + +import java.util.regex.Pattern; + +import net.sourceforge.pmd.util.fxdesigner.util.codearea.SimpleRegexSyntaxHighlighter; + +/** + * @author Clément Fournier + * @since 6.0.0 + */ +public class XmlSyntaxHighlighter extends SimpleRegexSyntaxHighlighter { + + + private static final RegexHighlightGrammar GRAMMAR + = grammarBuilder(MULTIL_COMMENT.css, "<!--.*?-->") + .or(XML_CDATA_TAG.css, "<!\\[CDATA\\[|]]>") + .or(XML_CDATA_CONTENT.css, "(?<=<!\\[CDATA\\[).*?(?=]]>)") + .or(XML_PROLOG.css, "<\\?xml.*?\\?>") + .or(XML_LT_GT.css, "</?|/?>") + .or(XML_TAG_NAME.css, "\\b(?<=(</?))\\w[-\\w:]*") + .or(XML_ATTRIBUTE_NAME.css, "\\w[-\\w]*(?=\\s*=\\s*[\"'])") + .or(HighlightClasses.STRING.css, "('([^'<>\\\\]|\\\\.)*')|(\"([^\"<>\\\\]|\\\\.)*\")") + .create(Pattern.DOTALL); + + + public XmlSyntaxHighlighter() { + super("xml", GRAMMAR); + } +} diff --git a/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/util/controls/ASTTreeCell.java b/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/util/controls/ASTTreeCell.java new file mode 100644 index 00000000000..e8afbcd7d53 --- /dev/null +++ b/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/util/controls/ASTTreeCell.java @@ -0,0 +1,208 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.util.fxdesigner.util.controls; + +import java.beans.PropertyDescriptor; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Modifier; + +import org.apache.commons.beanutils.PropertyUtils; +import org.apache.commons.lang3.StringUtils; + +import net.sourceforge.pmd.lang.ast.Node; +import net.sourceforge.pmd.util.fxdesigner.MainDesignerController; + +import javafx.scene.control.ContextMenu; +import javafx.scene.control.CustomMenuItem; +import javafx.scene.control.Label; +import javafx.scene.control.Tooltip; +import javafx.scene.control.TreeCell; +import javafx.scene.input.Clipboard; +import javafx.scene.input.ClipboardContent; +import javafx.scene.input.MouseButton; +import javafx.scene.input.MouseEvent; + + +/** + * Formats the cell for AST nodes in the main AST TreeView. + * + * @author Clément Fournier + * @since 6.0.0 + */ +public class ASTTreeCell extends TreeCell<Node> { + + private final MainDesignerController controller; + + + public ASTTreeCell(MainDesignerController controller) { + this.controller = controller; + } + + + // returns null if the value is unsupported + private String valueToString(Object value) { + if (value instanceof String) { + String stringVal; + stringVal = value.toString(); + stringVal = stringVal.replaceAll("\"", "\\\""); + // escape kt string interpolators + stringVal = stringVal.replaceAll("\\$(?=[a-zA-Z{])", "\\${'\\$'}"); + return '"' + stringVal + '"'; + } else if (value instanceof Character) { + return '\'' + value.toString().replaceAll("'", "\\'") + '\''; + } else if (value instanceof Enum) { + return ((Enum) value).getDeclaringClass().getCanonicalName() + "." + ((Enum) value).name(); + } else if (value instanceof Class) { + return ((Class) value).getCanonicalName() + "::class.java"; + } else if (value instanceof Number || value instanceof Boolean || value == null) { + return String.valueOf(value); + } + return null; + } + + + private void subtreeToNodeTest(Node node, StringBuilder builder, boolean isChild, int indentDepth) { + // indentDepth is the indent level of the outer block, assertions have +4 depth + int bodyIndent = indentDepth + 4; + + if (isChild) { + builder.append("child<").append(node.getClass().getSimpleName()).append("> {"); + } else { + builder.append("<").append(node.getClass().getSimpleName()).append("> {"); + } + + String[] childrenProps = new String[node.jjtGetNumChildren()]; + + for (PropertyDescriptor prop : PropertyUtils.getPropertyDescriptors(node.getClass())) { + if (prop.getReadMethod() != null && Modifier.isPublic(prop.getReadMethod().getModifiers()) + && prop.getReadMethod().getDeclaringClass() == node.getClass()) { + + Object value; + try { + value = prop.getReadMethod().invoke(node); + } catch (IllegalAccessException | InvocationTargetException e) { + e.printStackTrace(); + continue; + } + + String stringVal = valueToString(value); + if (stringVal != null) { + + // take care of Kotlin property access conventions + String propName = value instanceof Boolean && prop.getReadMethod().getName().startsWith("is") + ? prop.getReadMethod().getName() + : prop.getName(); + + // Filter this one out + if ("XPathNodeName".equals(propName)) { + continue; + } + + newLine(builder, bodyIndent) + .append("it.") + .append(StringUtils.uncapitalize(propName)) + .append(" shouldBe ") + .append(stringVal); + + } else if (value instanceof Node) { + // The property may give access to a child, in which case we'll use the child call later + + for (int i = 0; i < node.jjtGetNumChildren(); i++) { + Node child = node.jjtGetChild(i); + if (value.equals(child)) { + // The array contains name of corresponding properties + childrenProps[i] = prop.getName(); + break; + } + } + } else { + newLine(builder, bodyIndent) + .append("// it.").append(prop.getName()); + } + } + } + if (node.jjtGetNumChildren() > 0) { + for (int i = 0; i < node.jjtGetNumChildren(); i++) { + Node c = node.jjtGetChild(i); + newLine(builder, 0); + newLine(builder, bodyIndent); + // We have to write them in child order + if (childrenProps[i] != null) { + builder.append("it.").append(childrenProps[i]).append(" shouldBe "); + } + subtreeToNodeTest(c, builder, true, bodyIndent); + } + } + newLine(builder, indentDepth).append("}"); + } + + + /** + * Dumps the entire subtree of a node to a Kotlin AST matcher. + * + * @param node Node to dump + * + * @return A string that can be copy pasted into a kotlin test file + */ + private String dumpToSubtreeTest(Node node) { + StringBuilder sb = new StringBuilder(); + subtreeToNodeTest(node, sb, false, 0); + String result = sb.toString(); + // some preliminary formatting + result = result.replaceAll("\\{\\s*+} ", "{}"); + + return result; + } + + + private ContextMenu buildContextMenu(Node item) { + ContextMenu contextMenu = new ContextMenuWithNoArrows(); + CustomMenuItem menuItem = new CustomMenuItem(new Label("Copy subtree test to clipboard...")); + + Tooltip tooltip = new Tooltip("Creates a node spec using the Kotlin AST matcher DSL, and dumps it to the clipboard"); + Tooltip.install(menuItem.getContent(), tooltip); + + menuItem.setOnAction(e -> { + Clipboard clipboard = Clipboard.getSystemClipboard(); + ClipboardContent content = new ClipboardContent(); + content.putString(dumpToSubtreeTest(item)); // item is captured in the closure + clipboard.setContent(content); + }); + + contextMenu.getItems().add(menuItem); + + return contextMenu; + } + + + @Override + protected void updateItem(Node item, boolean empty) { + super.updateItem(item, empty); + + if (empty || item == null) { + setText(null); + setGraphic(null); + return; + } else { + setText(item.toString() + (item.getImage() == null ? "" : " \"" + item.getImage() + "\"")); + setContextMenu(buildContextMenu(item)); + } + + // Reclicking the selected node in the ast will scroll back to the node in the editor + this.addEventHandler(MouseEvent.MOUSE_CLICKED, t -> { + if (t.getButton() == MouseButton.PRIMARY + && getTreeView().getSelectionModel().getSelectedItem().getValue() == item) { + controller.onNodeItemSelected(item); + t.consume(); + } + }); + + } + + + private static StringBuilder newLine(StringBuilder builder, int indentDepth) { + return builder.append("\n").append(StringUtils.repeat(' ', indentDepth)); + } +} diff --git a/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/util/controls/ASTTreeItem.java b/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/util/controls/ASTTreeItem.java new file mode 100644 index 00000000000..f6e7cd09a2d --- /dev/null +++ b/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/util/controls/ASTTreeItem.java @@ -0,0 +1,57 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.util.fxdesigner.util.controls; + +import net.sourceforge.pmd.lang.ast.Node; + +import javafx.collections.ObservableList; +import javafx.scene.control.TreeItem; + +/** + * Represents a tree item (data, not UI) in the ast TreeView. + * + * @author Clément Fournier + * @since 6.0.0 + */ +public final class ASTTreeItem extends TreeItem<Node> { + + + private ASTTreeItem(Node n) { + super(n); + setExpanded(true); + } + + + public ASTTreeItem findItem(Node node) { + if (this.getValue().equals(node)) { + return this; + } + + ObservableList<TreeItem<Node>> children = this.getChildren(); + ASTTreeItem found; + for (TreeItem<Node> child : children) { + found = ((ASTTreeItem) child).findItem(node); + if (found != null) { + return found; + } + } + + return null; + } + + + /** Builds an ASTTreeItem recursively from a node. */ + public static ASTTreeItem getRoot(Node n) { + ASTTreeItem item = new ASTTreeItem(n); + if (n.jjtGetNumChildren() > 0) { + for (int i = 0; i < n.jjtGetNumChildren(); i++) { + item.getChildren().add(getRoot(n.jjtGetChild(i))); + } + } + return item; + } + + +} diff --git a/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/util/controls/ContextMenuWithNoArrows.java b/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/util/controls/ContextMenuWithNoArrows.java new file mode 100644 index 00000000000..746fd9330c9 --- /dev/null +++ b/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/util/controls/ContextMenuWithNoArrows.java @@ -0,0 +1,22 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.util.fxdesigner.util.controls; + +import javafx.scene.control.ContextMenu; + + +/** + * Context menu which has no scroll arrows (which by default appear on the top and bottom element). + * Implemented with simple CSS. + * + * @author Clément Fournier + * @since 6.6.0 + */ +public class ContextMenuWithNoArrows extends ContextMenu { + + public ContextMenuWithNoArrows() { + getStyleClass().add("no-scroll-arrows"); // sync with designer.less + } +} diff --git a/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/util/controls/MetricResultListCell.java b/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/util/controls/MetricResultListCell.java new file mode 100644 index 00000000000..77271ce9486 --- /dev/null +++ b/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/util/controls/MetricResultListCell.java @@ -0,0 +1,53 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.util.fxdesigner.util.controls; + +import java.util.Locale; + +import net.sourceforge.pmd.util.fxdesigner.model.MetricResult; + +import javafx.scene.control.ListCell; +import javafx.scene.control.ListView; +import javafx.util.Callback; + + +/** + * List cell for a metric result. + * + * @author Clément Fournier + * @since 6.0.0 + */ +public class MetricResultListCell extends ListCell<MetricResult> { + + + @Override + protected void updateItem(MetricResult item, boolean empty) { + super.updateItem(item, empty); + + if (empty || item == null) { + setText(null); + setGraphic(null); + } else { + setText(item.getKey().name() + " = " + niceDoubleString(item.getValue())); + } + } + + + /** Gets a nice string representation of a double. */ + private String niceDoubleString(double val) { + if (val == (int) val) { + return String.valueOf((int) val); + } else { + return String.format(Locale.ROOT, "%.4f", val); + } + } + + + public static Callback<ListView<MetricResult>, MetricResultListCell> callback() { + return param -> new MetricResultListCell(); + } + + +} diff --git a/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/util/controls/PropertyTableView.java b/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/util/controls/PropertyTableView.java new file mode 100644 index 00000000000..3a03537398b --- /dev/null +++ b/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/util/controls/PropertyTableView.java @@ -0,0 +1,217 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.util.fxdesigner.util.controls; + +import java.io.IOException; +import java.lang.ref.SoftReference; +import java.util.Collections; +import java.util.function.Consumer; + +import org.reactfx.value.Var; + +import net.sourceforge.pmd.properties.PropertyTypeId; +import net.sourceforge.pmd.util.fxdesigner.popups.EditPropertyDialogController; +import net.sourceforge.pmd.util.fxdesigner.util.DesignerUtil; +import net.sourceforge.pmd.util.fxdesigner.util.PropertyDescriptorSpec; + +import javafx.application.Platform; +import javafx.beans.property.ObjectProperty; +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import javafx.fxml.FXMLLoader; +import javafx.scene.Parent; +import javafx.scene.Scene; +import javafx.scene.control.ContextMenu; +import javafx.scene.control.Label; +import javafx.scene.control.MenuItem; +import javafx.scene.control.SeparatorMenuItem; +import javafx.scene.control.TableColumn; +import javafx.scene.control.TableView; +import javafx.scene.control.cell.ChoiceBoxTableCell; +import javafx.scene.control.cell.PropertyValueFactory; +import javafx.scene.control.cell.TextFieldTableCell; +import javafx.scene.input.MouseButton; +import javafx.scene.input.MouseEvent; +import javafx.stage.Modality; +import javafx.stage.Stage; +import javafx.stage.StageStyle; +import javafx.util.StringConverter; + + +/** + * Controls a table view used to inspect and edit the properties of + * the rule being built. This component is made to be reused in several + * views. + * <p> + * TODO: would be great to make it directly editable without compromising content validation + * + * @author Clément Fournier + * @since 6.0.0 + */ +public class PropertyTableView extends TableView<PropertyDescriptorSpec> { + + private TableColumn<PropertyDescriptorSpec, String> propertyNameColumn = new TableColumn<>("Name"); + private TableColumn<PropertyDescriptorSpec, PropertyTypeId> propertyTypeColumn = new TableColumn<>("Type"); + private TableColumn<PropertyDescriptorSpec, String> propertyValueColumn = new TableColumn<>("Value"); + + private SoftReference<Stage> editPropertyDialogCache; + + private Var<Consumer<? super PropertyDescriptorSpec>> onEditCommit = Var.newSimpleVar(null); + + + public PropertyTableView() { + initialize(); + } + + + private void initialize() { + + this.getColumns().add(propertyNameColumn); + this.getColumns().add(propertyTypeColumn); + this.getColumns().add(propertyValueColumn); + this.setColumnResizePolicy(CONSTRAINED_RESIZE_POLICY); + this.setTableMenuButtonVisible(true); + + ObservableList<PropertyTypeId> availableBuilders = FXCollections.observableArrayList(PropertyTypeId.typeIdsToConstants().values()); + Collections.sort(availableBuilders); + StringConverter<PropertyTypeId> converter = DesignerUtil.stringConverter(PropertyTypeId::getStringId, PropertyTypeId::lookupMnemonic); + propertyTypeColumn.setCellFactory(ChoiceBoxTableCell.forTableColumn(converter, availableBuilders)); + propertyNameColumn.setCellValueFactory(new PropertyValueFactory<>("name")); + propertyValueColumn.setCellValueFactory(new PropertyValueFactory<>("value")); + propertyTypeColumn.setCellValueFactory(new PropertyValueFactory<>("typeId")); + + this.setPlaceholder(new Label("Right-click to add properties")); + + MenuItem editItem = new MenuItem("Edit..."); + editItem.setOnAction(e -> { + PropertyDescriptorSpec spec = this.getSelectionModel().getSelectedItem(); + if (spec != null) { + popEditPropertyDialog(spec); + } + }); + + MenuItem removeItem = new MenuItem("Remove"); + removeItem.setOnAction(e -> { + PropertyDescriptorSpec selected = this.getSelectionModel().getSelectedItem(); + if (selected != null) { + this.getItems().remove(selected); + } + }); + + MenuItem addItem = new MenuItem("Add property..."); + addItem.setOnAction(e -> onAddPropertyClicked()); + + ContextMenu fullMenu = new ContextMenu(); + fullMenu.getItems().addAll(editItem, removeItem, new SeparatorMenuItem(), addItem); + + // Reduced context menu, for when there are no properties or none is selected + MenuItem addItem2 = new MenuItem("Add property..."); + addItem2.setOnAction(e -> onAddPropertyClicked()); + + ContextMenu smallMenu = new ContextMenu(); + smallMenu.getItems().add(addItem2); + + this.addEventHandler(MouseEvent.MOUSE_CLICKED, t -> { + if (t.getButton() == MouseButton.SECONDARY + || t.getButton() == MouseButton.PRIMARY && t.getClickCount() > 1) { + if (this.getSelectionModel().getSelectedItem() != null) { + fullMenu.show(this, t.getScreenX(), t.getScreenY()); + } else { + smallMenu.show(this, t.getScreenX(), t.getScreenY()); + } + } + }); + + propertyNameColumn.setCellFactory(TextFieldTableCell.forTableColumn()); + propertyValueColumn.setCellFactory(TextFieldTableCell.forTableColumn()); + this.setEditable(false); + } + + + private void onAddPropertyClicked() { + PropertyDescriptorSpec spec = new PropertyDescriptorSpec(); + this.getItems().add(spec); + popEditPropertyDialog(spec); + } + + + /** + * Pops an edition dialog for the given descriptor spec. The dialog is cached and + * reused, so that it's parsed a minimal amount of time. + * + * @param edited The edited property descriptor + */ + private void popEditPropertyDialog(PropertyDescriptorSpec edited) { + if (editPropertyDialogCache == null || editPropertyDialogCache.get() == null) { + try { + editPropertyDialogCache = new SoftReference<>(createEditPropertyDialog()); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + Stage dialog = editPropertyDialogCache.get(); + EditPropertyDialogController wizard = (EditPropertyDialogController) dialog.getUserData(); + Platform.runLater(() -> wizard.bindToDescriptor(edited, getRuleProperties())); + dialog.setOnHiding(e -> { + edited.unbind(); + onEditCommit.ifPresent(handler -> handler.accept(edited)); + }); + dialog.show(); + } + + + private Stage createEditPropertyDialog() throws IOException { + EditPropertyDialogController wizard = new EditPropertyDialogController(); + + FXMLLoader loader = new FXMLLoader(DesignerUtil.getFxml("edit-property-dialog.fxml")); + loader.setController(wizard); + + final Stage dialog = new Stage(); + dialog.initOwner(this.getScene().getWindow()); + dialog.initModality(Modality.WINDOW_MODAL); + dialog.initStyle(StageStyle.UNDECORATED); + + Parent root = loader.load(); + + Scene scene = new Scene(root); + dialog.setTitle("Edit property"); + dialog.setScene(scene); + dialog.setUserData(wizard); + return dialog; + } + + + public ObservableList<PropertyDescriptorSpec> getRuleProperties() { + return this.getItems(); + } + + + public void setRuleProperties(ObservableList<PropertyDescriptorSpec> ruleProperties) { + this.setItems(ruleProperties); + } + + + public ObjectProperty<ObservableList<PropertyDescriptorSpec>> rulePropertiesProperty() { + return this.itemsProperty(); + } + + + public Consumer<? super PropertyDescriptorSpec> getOnEditCommit() { + return onEditCommit.getValue(); + } + + + public Var<Consumer<? super PropertyDescriptorSpec>> onEditCommitProperty() { + return onEditCommit; + } + + + public void setOnEditCommit(Consumer<? super PropertyDescriptorSpec> onEditCommit) { + this.onEditCommit.setValue(onEditCommit); + } + + +} diff --git a/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/util/controls/ScopeHierarchyTreeCell.java b/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/util/controls/ScopeHierarchyTreeCell.java new file mode 100644 index 00000000000..49958bd6e96 --- /dev/null +++ b/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/util/controls/ScopeHierarchyTreeCell.java @@ -0,0 +1,72 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.util.fxdesigner.util.controls; + +import net.sourceforge.pmd.lang.java.ast.TypeNode; +import net.sourceforge.pmd.lang.java.symboltable.ClassNameDeclaration; +import net.sourceforge.pmd.lang.java.symboltable.MethodNameDeclaration; +import net.sourceforge.pmd.lang.java.symboltable.VariableNameDeclaration; +import net.sourceforge.pmd.lang.symboltable.NameDeclaration; +import net.sourceforge.pmd.lang.symboltable.Scope; + +import javafx.scene.control.TreeCell; + + +/** + * Renders scope nodes and declaration in the scope treeview. + * + * @author Clément Fournier + * @since 6.0.0 + */ +public class ScopeHierarchyTreeCell extends TreeCell<Object> { + + @Override + protected void updateItem(Object item, boolean empty) { + super.updateItem(item, empty); + + if (empty || item == null) { + setText(null); + setGraphic(null); + } else { + setText(item instanceof Scope ? getTextForScope((Scope) item) + : getTextForDeclaration((NameDeclaration) item)); + } + } + + + private String getTextForScope(Scope scope) { + return scope.getClass().getSimpleName(); + } + + + private String getTextForDeclaration(NameDeclaration declaration) { + + StringBuilder sb = new StringBuilder(); + if (declaration instanceof MethodNameDeclaration + || declaration instanceof net.sourceforge.pmd.lang.plsql.symboltable.MethodNameDeclaration) { + sb.append("Method "); + } else if (declaration instanceof VariableNameDeclaration + || declaration instanceof net.sourceforge.pmd.lang.plsql.symboltable.VariableNameDeclaration) { + sb.append("Variable "); + } else if (declaration instanceof ClassNameDeclaration + || declaration instanceof net.sourceforge.pmd.lang.plsql.symboltable.ClassNameDeclaration) { + sb.append("Class "); + } + + Class<?> type = declaration.getNode() instanceof TypeNode ? ((TypeNode) declaration.getNode()).getType() + : null; + + sb.append(declaration.getName()); + + if (type != null) { + sb.append(" : ").append(type.getSimpleName()); + } + + sb.append(" (l. ").append(declaration.getNode().getBeginLine()).append(")"); + + return sb.toString(); + } + +} diff --git a/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/util/controls/ScopeHierarchyTreeItem.java b/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/util/controls/ScopeHierarchyTreeItem.java new file mode 100644 index 00000000000..36a82659819 --- /dev/null +++ b/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/util/controls/ScopeHierarchyTreeItem.java @@ -0,0 +1,82 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.util.fxdesigner.util.controls; + +import java.util.List; +import java.util.Map.Entry; + +import net.sourceforge.pmd.lang.ast.Node; +import net.sourceforge.pmd.lang.symboltable.NameDeclaration; +import net.sourceforge.pmd.lang.symboltable.NameOccurrence; +import net.sourceforge.pmd.lang.symboltable.Scope; +import net.sourceforge.pmd.lang.symboltable.ScopedNode; + +import javafx.scene.control.TreeItem; + + +/** + * @author Clément Fournier + * @since 6.0.0 + */ +public final class ScopeHierarchyTreeItem extends TreeItem<Object> { + + private ScopeHierarchyTreeItem(Object scopeOrDecl) { + super(scopeOrDecl); + setExpanded(true); + } + + + /** + * Gets the scope hierarchy of a node. + * + * @param node Node + * + * @return Root of the tree + */ + public static ScopeHierarchyTreeItem buildAscendantHierarchy(Node node) { + ScopeHierarchyTreeItem item = buildAscendantHierarchyHelper(getScope(node)); + + if (item == null) { + return null; + } + + while (item.getParent() != null) { + item = (ScopeHierarchyTreeItem) item.getParent(); + } + + return item; + + } + + + private static ScopeHierarchyTreeItem buildAscendantHierarchyHelper(Scope scope) { + if (scope == null) { + return null; + } + + ScopeHierarchyTreeItem scopeTreeNode = new ScopeHierarchyTreeItem(scope); + + for (Entry<NameDeclaration, List<NameOccurrence>> entry : scope.getDeclarations().entrySet()) { + ScopeHierarchyTreeItem nameDeclaration = new ScopeHierarchyTreeItem(entry.getKey()); + scopeTreeNode.getChildren().add(nameDeclaration); + } + + ScopeHierarchyTreeItem parent = buildAscendantHierarchyHelper(scope.getParent()); + + if (parent != null) { + parent.getChildren().add(scopeTreeNode); + } + return scopeTreeNode; + } + + + private static Scope getScope(Node node) { + if (node instanceof ScopedNode) { + return ((ScopedNode) node).getScope(); + } + return null; + } + +} diff --git a/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/util/controls/TreeViewWrapper.java b/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/util/controls/TreeViewWrapper.java new file mode 100644 index 00000000000..57809eb07fc --- /dev/null +++ b/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/util/controls/TreeViewWrapper.java @@ -0,0 +1,136 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.util.fxdesigner.util.controls; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.Objects; +import java.util.Optional; + +import org.apache.commons.lang3.reflect.FieldUtils; +import org.apache.commons.lang3.reflect.MethodUtils; + +import javafx.scene.control.Skin; +import javafx.scene.control.TreeCell; +import javafx.scene.control.TreeView; + + +/** + * Reflective solution to know if a cell in a TreeView is + * visible or not, to prevent confusing scrolling. Works + * under Java 8, 9, 10. Under Java 9+, requires the + * "--add-opens javafx.controls/javafx.scene.control.skin=ALL-UNNAMED" + * VM option. + * + * @param <T> Element type of the treeview + * @author Clément Fournier + * @since 6.4.0 + */ +public class TreeViewWrapper<T> { + + + private final TreeView<T> wrapped; + private Method treeViewFirstVisibleMethod; + private Method treeViewLastVisibleMethod; + // We can't use strong typing + // because the class has moved packages over different java versions + private Object virtualFlow = null; + + + public TreeViewWrapper(TreeView<T> wrapped) { + Objects.requireNonNull(wrapped); + this.wrapped = wrapped; + initialiseTreeViewReflection(); + } + + + private void initialiseTreeViewReflection() { + + // we can't use wrapped.getSkin() because it may be null. + // we don't care about the specific instance, we just want the class + @SuppressWarnings("PMD.UselessOverridingMethod") + Skin<?> dftSkin = new TreeView<Object>() { + @Override + protected Skin<?> createDefaultSkin() { + return super.createDefaultSkin(); + } + }.createDefaultSkin(); + + Object flow = getVirtualFlow(dftSkin); + + if (flow == null) { + return; + } + + treeViewFirstVisibleMethod = MethodUtils.getMatchingMethod(flow.getClass(), "getFirstVisibleCell"); + treeViewLastVisibleMethod = MethodUtils.getMatchingMethod(flow.getClass(), "getLastVisibleCell"); + } + + + /** + * Returns true if the item at the given index + * is visible in the TreeView. + */ + public boolean isIndexVisible(int index) { + if (virtualFlow == null && wrapped.getSkin() == null) { + return false; + } else if (virtualFlow == null && wrapped.getSkin() != null) { + // the flow is cached, so the skin must not be changed + virtualFlow = getVirtualFlow(wrapped.getSkin()); + } + + if (virtualFlow == null) { + return false; + } + + Optional<TreeCell<T>> first = getFirstVisibleCell(); + Optional<TreeCell<T>> last = getLastVisibleCell(); + + return first.isPresent() + && last.isPresent() + && first.get().getIndex() <= index + && last.get().getIndex() >= index; + } + + + private Optional<TreeCell<T>> getCellFromAccessor(Method accessor) { + return Optional.ofNullable(accessor).map(m -> { + try { + @SuppressWarnings("unchecked") + TreeCell<T> cell = (TreeCell<T>) m.invoke(virtualFlow); + return cell; + } catch (IllegalAccessException | InvocationTargetException e) { + e.printStackTrace(); + } + return null; + }); + } + + + private Optional<TreeCell<T>> getFirstVisibleCell() { + return getCellFromAccessor(treeViewFirstVisibleMethod); + } + + + private Optional<TreeCell<T>> getLastVisibleCell() { + return getCellFromAccessor(treeViewLastVisibleMethod); + } + + + private static Object getVirtualFlow(Skin<?> skin) { + try { + // On JRE 9 and 10, the field is declared in TreeViewSkin + // http://hg.openjdk.java.net/openjfx/9/rt/file/c734b008e3e8/modules/javafx.controls/src/main/java/javafx/scene/control/skin/TreeViewSkin.java#l85 + // http://hg.openjdk.java.net/openjfx/10/rt/file/d14b61c6be12/modules/javafx.controls/src/main/java/javafx/scene/control/skin/TreeViewSkin.java#l85 + // On JRE 8, the field is declared in the VirtualContainerBase superclass + // http://hg.openjdk.java.net/openjfx/8/master/rt/file/f89b7dc932af/modules/controls/src/main/java/com/sun/javafx/scene/control/skin/VirtualContainerBase.java#l68 + + return FieldUtils.readField(skin, "flow", true); + } catch (IllegalAccessException ignored) { + + } + return null; + } +} diff --git a/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/util/controls/XpathViolationListCell.java b/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/util/controls/XpathViolationListCell.java new file mode 100644 index 00000000000..8fb3237b90e --- /dev/null +++ b/pmd-ui/src/main/java/net/sourceforge/pmd/util/fxdesigner/util/controls/XpathViolationListCell.java @@ -0,0 +1,70 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.util.fxdesigner.util.controls; + + +import java.util.Collection; + +import org.fxmisc.richtext.model.StyleSpan; +import org.fxmisc.richtext.model.StyledDocument; + +import net.sourceforge.pmd.util.fxdesigner.util.TextAwareNodeWrapper; + +import javafx.scene.control.ListCell; +import javafx.scene.text.Text; +import javafx.scene.text.TextFlow; + + +/** + * Represents an XPath result in the list view of the XPath panel. + * + * @author Clément Fournier + * @since 6.0.0 + */ +public class XpathViolationListCell extends ListCell<TextAwareNodeWrapper> { + + @Override + protected void updateItem(TextAwareNodeWrapper item, boolean empty) { + super.updateItem(item, empty); + + if (empty || item == null) { + setText(null); + setGraphic(null); + } else { + // Use the first line of the rich text of the node + setGraphic(richTextForNode(item)); + } + } + + + private static TextFlow richTextForNode(TextAwareNodeWrapper node) { + StyledDocument<Collection<String>, String, Collection<String>> richText = node.getNodeRichText(); + + TextFlow result = new TextFlow(); + int lastSpanEnd = 0; + for (StyleSpan<Collection<String>> span : richText.getStyleSpans(0, richText.length())) { + String spanText = richText.getText(lastSpanEnd, lastSpanEnd + span.getLength()); + int truncateTo = spanText.indexOf("\n"); + + Text text = new Text(truncateTo < 0 ? spanText : spanText.substring(0, truncateTo)); + text.getStyleClass().addAll(span.getStyle()); + + result.getChildren().add(text); + + lastSpanEnd += text.getText().length(); + if (truncateTo > 0) { + break; + } + } + + // we truncated + if (lastSpanEnd < richText.length()) { + result.getChildren().add(new Text("...")); + } + + return result; + } + +} diff --git a/pmd-ui/src/main/resources/net/sourceforge/pmd/util/fxdesigner/LICENSE b/pmd-ui/src/main/resources/net/sourceforge/pmd/util/fxdesigner/LICENSE new file mode 100644 index 00000000000..d90bc731ecb --- /dev/null +++ b/pmd-ui/src/main/resources/net/sourceforge/pmd/util/fxdesigner/LICENSE @@ -0,0 +1,405 @@ +PMD's BSD-style license: + +Copyright (c) 2003-2009, InfoEther, LLC +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. + * The end-user documentation included with the redistribution, if +any, must include the following acknowledgement: + "This product includes software developed in part by support from +the Defense Advanced Research Project Agency (DARPA)" + * Neither the name of InfoEther, LLC nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS +IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED +TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A +PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER +OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +--------------------------------------------------------------------- + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +--------------------------------------------------------------------- + + GNU LESSER GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/> + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + + This version of the GNU Lesser General Public License incorporates +the terms and conditions of version 3 of the GNU General Public +License, supplemented by the additional permissions listed below. + + 0. Additional Definitions. + + As used herein, "this License" refers to version 3 of the GNU Lesser +General Public License, and the "GNU GPL" refers to version 3 of the GNU +General Public License. + + "The Library" refers to a covered work governed by this License, +other than an Application or a Combined Work as defined below. + + An "Application" is any work that makes use of an interface provided +by the Library, but which is not otherwise based on the Library. +Defining a subclass of a class defined by the Library is deemed a mode +of using an interface provided by the Library. + + A "Combined Work" is a work produced by combining or linking an +Application with the Library. The particular version of the Library +with which the Combined Work was made is also called the "Linked +Version". + + The "Minimal Corresponding Source" for a Combined Work means the +Corresponding Source for the Combined Work, excluding any source code +for portions of the Combined Work that, considered in isolation, are +based on the Application, and not on the Linked Version. + + The "Corresponding Application Code" for a Combined Work means the +object code and/or source code for the Application, including any data +and utility programs needed for reproducing the Combined Work from the +Application, but excluding the System Libraries of the Combined Work. + + 1. Exception to Section 3 of the GNU GPL. + + You may convey a covered work under sections 3 and 4 of this License +without being bound by section 3 of the GNU GPL. + + 2. Conveying Modified Versions. + + If you modify a copy of the Library, and, in your modifications, a +facility refers to a function or data to be supplied by an Application +that uses the facility (other than as an argument passed when the +facility is invoked), then you may convey a copy of the modified +version: + + a) under this License, provided that you make a good faith effort to + ensure that, in the event an Application does not supply the + function or data, the facility still operates, and performs + whatever part of its purpose remains meaningful, or + + b) under the GNU GPL, with none of the additional permissions of + this License applicable to that copy. + + 3. Object Code Incorporating Material from Library Header Files. + + The object code form of an Application may incorporate material from +a header file that is part of the Library. You may convey such object +code under terms of your choice, provided that, if the incorporated +material is not limited to numerical parameters, data structure +layouts and accessors, or small macros, inline functions and templates +(ten or fewer lines in length), you do both of the following: + + a) Give prominent notice with each copy of the object code that the + Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the object code with a copy of the GNU GPL and this license + document. + + 4. Combined Works. + + You may convey a Combined Work under terms of your choice that, +taken together, effectively do not restrict modification of the +portions of the Library contained in the Combined Work and reverse +engineering for debugging such modifications, if you also do each of +the following: + + a) Give prominent notice with each copy of the Combined Work that + the Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the Combined Work with a copy of the GNU GPL and this license + document. + + c) For a Combined Work that displays copyright notices during + execution, include the copyright notice for the Library among + these notices, as well as a reference directing the user to the + copies of the GNU GPL and this license document. + + d) Do one of the following: + + 0) Convey the Minimal Corresponding Source under the terms of this + License, and the Corresponding Application Code in a form + suitable for, and under terms that permit, the user to + recombine or relink the Application with a modified version of + the Linked Version to produce a modified Combined Work, in the + manner specified by section 6 of the GNU GPL for conveying + Corresponding Source. + + 1) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (a) uses at run time + a copy of the Library already present on the user's computer + system, and (b) will operate properly with a modified version + of the Library that is interface-compatible with the Linked + Version. + + e) Provide Installation Information, but only if you would otherwise + be required to provide such information under section 6 of the + GNU GPL, and only to the extent that such information is + necessary to install and execute a modified version of the + Combined Work produced by recombining or relinking the + Application with a modified version of the Linked Version. (If + you use option 4d0, the Installation Information must accompany + the Minimal Corresponding Source and Corresponding Application + Code. If you use option 4d1, you must provide the Installation + Information in the manner specified by section 6 of the GNU GPL + for conveying Corresponding Source.) + + 5. Combined Libraries. + + You may place library facilities that are a work based on the +Library side by side in a single library together with other library +facilities that are not Applications and are not covered by this +License, and convey such a combined library under terms of your +choice, if you do both of the following: + + a) Accompany the combined library with a copy of the same work based + on the Library, uncombined with any other library facilities, + conveyed under the terms of this License. + + b) Give prominent notice with the combined library that part of it + is a work based on the Library, and explaining where to find the + accompanying uncombined form of the same work. + + 6. Revised Versions of the GNU Lesser General Public License. + + The Free Software Foundation may publish revised and/or new versions +of the GNU Lesser General Public License from time to time. Such new +versions will be similar in spirit to the present version, but may +differ in detail to address new problems or concerns. + + Each version is given a distinguishing version number. If the +Library as you received it specifies that a certain numbered version +of the GNU Lesser General Public License "or any later version" +applies to it, you have the option of following the terms and +conditions either of that published version or of any later version +published by the Free Software Foundation. If the Library as you +received it does not specify a version number of the GNU Lesser +General Public License, you may choose any version of the GNU Lesser +General Public License ever published by the Free Software Foundation. + + If the Library as you received it specifies that a proxy can decide +whether future versions of the GNU Lesser General Public License shall +apply, that proxy's public statement of acceptance of any version is +permanent authorization for you to choose that version for the +Library. diff --git a/pmd-ui/src/main/resources/net/sourceforge/pmd/util/fxdesigner/fxml/auxclasspath-setup-popup.fxml b/pmd-ui/src/main/resources/net/sourceforge/pmd/util/fxdesigner/fxml/auxclasspath-setup-popup.fxml new file mode 100644 index 00000000000..c3a773f04e7 --- /dev/null +++ b/pmd-ui/src/main/resources/net/sourceforge/pmd/util/fxdesigner/fxml/auxclasspath-setup-popup.fxml @@ -0,0 +1,98 @@ +<?xml version="1.0" encoding="UTF-8"?> + +<?import javafx.geometry.Insets?> +<?import javafx.scene.control.Button?> +<?import javafx.scene.control.ListView?> +<?import javafx.scene.control.ToolBar?> +<?import javafx.scene.control.Tooltip?> +<?import javafx.scene.layout.AnchorPane?> +<?import javafx.scene.layout.HBox?> +<?import org.kordamp.ikonli.javafx.FontIcon?> + +<AnchorPane prefHeight="300.0" + prefWidth="400.0" + xmlns="http://javafx.com/javafx/10.0.1" + xmlns:fx="http://javafx.com/fxml/1" + fx:controller="net.sourceforge.pmd.util.fxdesigner.popups.AuxclasspathSetupController"> + <children> + <ToolBar layoutX="320.3999938964844" + layoutY="10.399999618530273" + maxWidth="40.0" + minWidth="40.0" + orientation="VERTICAL" + prefWidth="40.0" + AnchorPane.bottomAnchor="30.0" + AnchorPane.rightAnchor="0.0" + AnchorPane.topAnchor="0.0"> + <items> + <Button fx:id="selectFilesButton" + defaultButton="true" + minWidth="-Infinity" + mnemonicParsing="false" + styleClass="icon-button" + textAlignment="CENTER"> + <graphic> + <FontIcon iconLiteral="fa-plus"/> + </graphic> + <tooltip> + <Tooltip text="Add new jars"/> + </tooltip> + </Button> + <Button fx:id="removeFileButton" + minWidth="-Infinity" + mnemonicParsing="false" + styleClass="icon-button" + textAlignment="CENTER"> + <graphic> + <FontIcon iconLiteral="fa-minus"/> + </graphic> + <tooltip> + <Tooltip text="Remove item"/> + </tooltip> + </Button> + <Button fx:id="moveItemUpButton" minWidth="-Infinity" mnemonicParsing="false" styleClass="icon-button"> + <graphic> + <FontIcon iconLiteral="fa-arrow-up"/> + </graphic> + </Button> + <Button fx:id="moveItemDownButton" minWidth="-Infinity" mnemonicParsing="false" styleClass="icon-button"> + <graphic> + <FontIcon iconLiteral="fa-arrow-down"/> + </graphic> + <tooltip> + <Tooltip text="Move item down"/> + </tooltip> + </Button> + </items> + <tooltip> + <Tooltip text="Move item up"/> + </tooltip> + </ToolBar> + <ListView fx:id="fileListView" + layoutX="10.399999618530273" + layoutY="10.399999618530273" + AnchorPane.bottomAnchor="30.0" + AnchorPane.leftAnchor="0.0" + AnchorPane.rightAnchor="40.0" + AnchorPane.topAnchor="0.0"/> + <HBox layoutX="14.399999618530273" + layoutY="248.60000610351562" + spacing="10.0" + AnchorPane.bottomAnchor="0.0" + AnchorPane.rightAnchor="0.0"> + <children> + + <Button fx:id="cancelButton" + cancelButton="true" + minWidth="-Infinity" + mnemonicParsing="false" + text="Cancel" + textAlignment="CENTER"/> + <Button fx:id="setClassPathButton" minWidth="-Infinity" mnemonicParsing="false" text="OK" textAlignment="CENTER"/> + </children> + </HBox> + </children> + <padding> + <Insets bottom="5.0" left="5.0" right="5.0" top="5.0"/> + </padding> +</AnchorPane> diff --git a/pmd-ui/src/main/resources/net/sourceforge/pmd/util/fxdesigner/fxml/designer.fxml b/pmd-ui/src/main/resources/net/sourceforge/pmd/util/fxdesigner/fxml/designer.fxml new file mode 100644 index 00000000000..ac6c5aabb51 --- /dev/null +++ b/pmd-ui/src/main/resources/net/sourceforge/pmd/util/fxdesigner/fxml/designer.fxml @@ -0,0 +1,140 @@ +<?xml version="1.0" encoding="UTF-8"?> + +<!--suppress JavaFxDefaultTag --> + +<?import javafx.geometry.Insets?> +<?import javafx.scene.control.ChoiceBox?> +<?import javafx.scene.control.Label?> +<?import javafx.scene.control.Menu?> +<?import javafx.scene.control.MenuBar?> +<?import javafx.scene.control.MenuItem?> +<?import javafx.scene.control.SeparatorMenuItem?> +<?import javafx.scene.control.SplitPane?> +<?import javafx.scene.control.Tab?> +<?import javafx.scene.control.TabPane?> +<?import javafx.scene.control.ToggleButton?> +<?import javafx.scene.control.ToolBar?> +<?import javafx.scene.layout.AnchorPane?> +<?import javafx.scene.layout.BorderPane?> +<?import javafx.scene.layout.HBox?> +<?import javafx.scene.layout.Pane?> +<AnchorPane prefHeight="750.0" prefWidth="1200.0" stylesheets="@../css/designer.css" + xmlns="http://javafx.com/javafx/8.0.121" xmlns:fx="http://javafx.com/fxml/1" + fx:controller="net.sourceforge.pmd.util.fxdesigner.MainDesignerController"> + <children> + <BorderPane prefHeight="600.0" prefWidth="900.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0"> + <top> + <AnchorPane> + <children> + <MenuBar layoutX="-11.0" maxHeight="20.0" prefHeight="20.0" AnchorPane.bottomAnchor="0.0" + AnchorPane.leftAnchor="-11.0" AnchorPane.rightAnchor="11.0" AnchorPane.topAnchor="0.0"> + <menus> + <Menu fx:id="fileMenu" mnemonicParsing="false" text="File"> + <items> + <MenuItem fx:id="openFileMenuItem" mnemonicParsing="false" text="Open..." /> + <Menu fx:id="openRecentMenu" mnemonicParsing="false" text="Open recent..." /> + <SeparatorMenuItem mnemonicParsing="false" /> + <MenuItem fx:id="exportToTestCodeMenuItem" mnemonicParsing="false" text="Export code to test code" /> + <MenuItem fx:id="exportXPathMenuItem" mnemonicParsing="false" text="Export XPath to rule" /> + </items> + </Menu> + + <Menu mnemonicParsing="false" text="Configuration"> + <items> + <MenuItem fx:id="setupAuxclasspathMenuItem" mnemonicParsing="false" + text="Set up the auxclasspath" > + </MenuItem> + </items> + </Menu> + + <Menu mnemonicParsing="false" text="About"> + <items> + <MenuItem mnemonicParsing="false" text="About"/> + <MenuItem fx:id="licenseMenuItem" mnemonicParsing="false" text="License"/> + </items> + </Menu> + + </menus> + </MenuBar> + </children> + </AnchorPane> + </top> + <center> + <SplitPane id="main-horizontal-split-pane" fx:id="mainHorizontalSplitPane" dividerPositions="0.8" orientation="VERTICAL" BorderPane.alignment="BOTTOM_CENTER"> + <items> + <BorderPane prefHeight="200.0" prefWidth="200.0"> + <left> + <fx:include fx:id="nodeInfoPanel" source="node-info.fxml" /> + </left> + <center> + <BorderPane prefHeight="200.0" prefWidth="200.0" BorderPane.alignment="CENTER"> + <center> + <fx:include fx:id="sourceEditor" source="editor.fxml" /> + </center> + <bottom> + <ToolBar id="main-toolbar" prefHeight="40.0" prefWidth="200.0" BorderPane.alignment="CENTER"> + <items> + <HBox alignment="CENTER" spacing="10.0"> + <children> + <Label text="Language Version:" /> + <ChoiceBox fx:id="languageChoiceBox" maxWidth="115.0" prefWidth="115.0" HBox.hgrow="NEVER"> + <padding> + <Insets bottom="-3.0" left="-3.0" right="-3.0" top="-3.0" /> + </padding> + </ChoiceBox> + <Pane prefWidth="10.0" HBox.hgrow="NEVER" /> + <Label text="XPath Version:" /> + <ChoiceBox maxWidth="115.0" prefWidth="115.0" fx:id="xpathVersionChoiceBox"> + <padding> + <Insets bottom="-3.0" left="-3.0" right="-3.0" top="-3.0" /> + </padding> + </ChoiceBox> + </children> + </HBox> + <Pane HBox.hgrow="ALWAYS" /> + <ToggleButton fx:id="bottomTabsToggle" mnemonicParsing="false" selected="true" styleClass="expand-toggle" /> + </items> + <padding> + <Insets left="20.0" right="20.0" /> + </padding> + </ToolBar> + </bottom> + </BorderPane> + </center> + </BorderPane> + <BorderPane prefHeight="200.0" prefWidth="200.0"> + <center> + <AnchorPane BorderPane.alignment="CENTER"> + <children> + <TabPane fx:id="bottomTabPane" minHeight="20.0" styleClass="bottom-pane-tab-pane" tabClosingPolicy="UNAVAILABLE" tabMaxHeight="10000.0" tabMaxWidth="10000.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0"> + <tabs> + <Tab text="XPath Editor" fx:id="xpathEditorTab"> + <content> + <fx:include source="xpath.fxml" fx:id="xpathPanel" /> + </content> + </Tab> + <Tab fx:id="eventLogTab" text="Exception Log"> + <content> + <fx:include fx:id="eventLogPanel" source="event-log.fxml" /> + </content> + </Tab> + <Tab disable="true"> + <content> + <AnchorPane minHeight="0.0" minWidth="0.0" prefHeight="180.0" prefWidth="200.0" /> + </content> + </Tab> + </tabs> + </TabPane> + </children> + </AnchorPane> + </center> + <top> + <AnchorPane prefWidth="200.0" BorderPane.alignment="CENTER" /> + </top> + </BorderPane> + </items> + </SplitPane> + </center> + </BorderPane> + </children> +</AnchorPane> diff --git a/pmd-ui/src/main/resources/net/sourceforge/pmd/util/fxdesigner/fxml/edit-property-dialog.fxml b/pmd-ui/src/main/resources/net/sourceforge/pmd/util/fxdesigner/fxml/edit-property-dialog.fxml new file mode 100644 index 00000000000..4fdecfa582d --- /dev/null +++ b/pmd-ui/src/main/resources/net/sourceforge/pmd/util/fxdesigner/fxml/edit-property-dialog.fxml @@ -0,0 +1,48 @@ +<?xml version="1.0" encoding="UTF-8"?> + +<?import javafx.geometry.Insets?> +<?import javafx.scene.control.Button?> +<?import javafx.scene.control.ChoiceBox?> +<?import javafx.scene.control.Label?> +<?import javafx.scene.control.Separator?> +<?import javafx.scene.control.TextField?> +<?import javafx.scene.layout.AnchorPane?> +<?import javafx.scene.layout.ColumnConstraints?> +<?import javafx.scene.layout.GridPane?> +<?import javafx.scene.layout.RowConstraints?> + +<AnchorPane prefHeight="250.0" prefWidth="300.0" xmlns="http://javafx.com/javafx/9" xmlns:fx="http://javafx.com/fxml/1"> + <children> + <GridPane layoutX="15.0" layoutY="15.0" prefHeight="104.0" prefWidth="270.0" vgap="10.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0"> + <columnConstraints> + <ColumnConstraints hgrow="SOMETIMES" maxWidth="90.0" minWidth="90.0" prefWidth="90.0" /> + <ColumnConstraints hgrow="SOMETIMES" maxWidth="209.0" minWidth="10.0" prefWidth="205.0" /> + </columnConstraints> + <rowConstraints> + <RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" /> + <RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" /> + <RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" /> + <RowConstraints minHeight="10.0" prefHeight="10.0" vgrow="SOMETIMES" /> + <RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" /> + <RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" /> + <RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" /> + <RowConstraints minHeight="10.0" prefHeight="30.0" valignment="CENTER" vgrow="SOMETIMES" /> + </rowConstraints> + <children> + <TextField fx:id="nameField" GridPane.columnIndex="1" /> + <TextField fx:id="descriptionField" GridPane.columnIndex="1" GridPane.rowIndex="1" /> + <Label text="Name" /> + <Label text="Description" GridPane.rowIndex="1" /> + <ChoiceBox fx:id="typeChoiceBox" prefWidth="150.0" GridPane.columnIndex="1" GridPane.hgrow="ALWAYS" GridPane.rowIndex="2" /> + <Label text="Type" GridPane.rowIndex="2" /> + <Separator prefWidth="200.0" GridPane.columnSpan="2" GridPane.rowIndex="3" /> + <Label text="Value" GridPane.rowIndex="4" /> + <TextField fx:id="valueField" GridPane.columnIndex="1" GridPane.rowIndex="4" /> + <Button fx:id="commitButton" mnemonicParsing="false" text="Commit changes" textAlignment="CENTER" GridPane.columnSpan="2" GridPane.halignment="CENTER" GridPane.hgrow="ALWAYS" GridPane.rowIndex="7" /> + </children> + </GridPane> + </children> + <padding> + <Insets bottom="15.0" left="15.0" right="15.0" top="15.0" /> + </padding> +</AnchorPane> diff --git a/pmd-ui/src/main/resources/net/sourceforge/pmd/util/fxdesigner/fxml/editor.fxml b/pmd-ui/src/main/resources/net/sourceforge/pmd/util/fxdesigner/fxml/editor.fxml new file mode 100644 index 00000000000..3981a382ea1 --- /dev/null +++ b/pmd-ui/src/main/resources/net/sourceforge/pmd/util/fxdesigner/fxml/editor.fxml @@ -0,0 +1,53 @@ +<?xml version="1.0" encoding="UTF-8"?> + +<!-- One editor, ie source + ast view --> + +<?import net.sourceforge.pmd.util.fxdesigner.util.codearea.HighlightLayerCodeArea?> +<?import javafx.geometry.Insets?> +<?import javafx.scene.control.Label?> +<?import javafx.scene.control.SplitPane?> +<?import javafx.scene.control.ToolBar?> +<?import javafx.scene.control.TreeView?> +<?import javafx.scene.layout.AnchorPane?> +<?import javafx.scene.layout.BorderPane?> +<SplitPane dividerPositions="0.5" prefHeight="400.0" prefWidth="500.0" styleClass="accent-header" stylesheets="@../css/designer.css" BorderPane.alignment="CENTER" xmlns="http://javafx.com/javafx/9" xmlns:fx="http://javafx.com/fxml/1" fx:controller="net.sourceforge.pmd.util.fxdesigner.SourceEditorController"> + <items> + <AnchorPane minHeight="0.0" minWidth="0.0" prefHeight="160.0" prefWidth="100.0"> + <children> + <BorderPane prefHeight="200.0" prefWidth="200.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0"> + <center> + <HighlightLayerCodeArea fx:id="codeEditorArea" idEnum="net.sourceforge.pmd.util.fxdesigner.SourceEditorController$StyleLayerIds" + prefHeight="346.0" prefWidth="445.0" stylesheets="@../css/editor-theme.css" BorderPane.alignment="CENTER"> + <BorderPane.margin> + <Insets /> + </BorderPane.margin> + </HighlightLayerCodeArea> + </center> + <top> + <ToolBar prefHeight="40.0" prefWidth="200.0" styleClass="accent-header" BorderPane.alignment="CENTER"> + <items> + <Label text="Source code" /> + </items> + </ToolBar> + </top> + </BorderPane> + </children> + </AnchorPane> + <AnchorPane minHeight="0.0" minWidth="0.0" prefHeight="160.0" prefWidth="100.0"> + <children> + <BorderPane prefHeight="200.0" prefWidth="200.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0"> + <center> + <TreeView fx:id="astTreeView" prefHeight="200.0" prefWidth="200.0" BorderPane.alignment="CENTER"/> + </center> + <top> + <ToolBar prefHeight="40.0" prefWidth="200.0" styleClass="accent-header" BorderPane.alignment="CENTER"> + <items> + <Label fx:id="astTitleLabel" text="Abstract Syntax Tree" /> + </items> + </ToolBar> + </top> + </BorderPane> + </children> + </AnchorPane> + </items> +</SplitPane> diff --git a/pmd-ui/src/main/resources/net/sourceforge/pmd/util/fxdesigner/fxml/event-log.fxml b/pmd-ui/src/main/resources/net/sourceforge/pmd/util/fxdesigner/fxml/event-log.fxml new file mode 100644 index 00000000000..9ab8f2abd1f --- /dev/null +++ b/pmd-ui/src/main/resources/net/sourceforge/pmd/util/fxdesigner/fxml/event-log.fxml @@ -0,0 +1,45 @@ +<?xml version="1.0" encoding="UTF-8"?> + +<?import java.lang.String?> +<?import javafx.scene.control.SplitPane?> +<?import javafx.scene.control.TableColumn?> +<?import javafx.scene.control.TableView?> +<?import javafx.scene.control.TextArea?> +<?import javafx.scene.control.TitledPane?> +<?import javafx.scene.layout.AnchorPane?> + +<SplitPane dividerPositions="0.6" prefWidth="1200.0" stylesheets="@../css/designer.css" xmlns="http://javafx.com/javafx/9" xmlns:fx="http://javafx.com/fxml/1" fx:controller="net.sourceforge.pmd.util.fxdesigner.EventLogController"> + <items> + <AnchorPane> + <children> + <TitledPane animated="false" collapsible="false" styleClass="accent-header" text="Exception log" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0"> + <content> + <TableView fx:id="eventLogTableView" prefHeight="200.0" prefWidth="200.0"> + <columns> + <TableColumn fx:id="logDateColumn" editable="false" prefWidth="97.0" resizable="false" sortType="DESCENDING" text="Date" /> + <TableColumn fx:id="logCategoryColumn" editable="false" prefWidth="174.0" resizable="false" text="Category" /> + <TableColumn fx:id="logMessageColumn" editable="false" prefWidth="123.0" text="Message" /> + </columns> + <columnResizePolicy> + <TableView fx:constant="UNCONSTRAINED_RESIZE_POLICY" /> + </columnResizePolicy> + </TableView> + </content> + </TitledPane> + </children> + </AnchorPane> + <AnchorPane prefWidth="300.0"> + <children> + <TitledPane collapsible="false" prefWidth="300.0" styleClass="accent-header" text="Details" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0"> + <content> + <TextArea fx:id="logDetailsTextArea" editable="false" prefHeight="200.0" prefWidth="300.0" /> + </content> + </TitledPane> + </children> + </AnchorPane> + </items> + <styleClass> + <String fx:value="bottom-pane-split-pane" /> + <String fx:value="accent-header" /> + </styleClass> +</SplitPane> diff --git a/pmd-ui/src/main/resources/net/sourceforge/pmd/util/fxdesigner/fxml/generate-xpath-from-stack-trace.fxml b/pmd-ui/src/main/resources/net/sourceforge/pmd/util/fxdesigner/fxml/generate-xpath-from-stack-trace.fxml new file mode 100644 index 00000000000..549d8e8a2d0 --- /dev/null +++ b/pmd-ui/src/main/resources/net/sourceforge/pmd/util/fxdesigner/fxml/generate-xpath-from-stack-trace.fxml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="UTF-8"?> + +<?import javafx.geometry.Insets?> +<?import javafx.scene.control.Button?> +<?import javafx.scene.control.TextArea?> +<?import javafx.scene.control.TitledPane?> +<?import javafx.scene.layout.AnchorPane?> + +<AnchorPane prefHeight="263.0" prefWidth="323.0" xmlns="http://javafx.com/javafx/9" xmlns:fx="http://javafx.com/fxml/1"> + <TitledPane animated="false" collapsible="false" prefWidth="211.0" stylesheets="@../css/designer.css" text="Generate XPath from a stack trace" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0"> + <AnchorPane minHeight="0.0" minWidth="0.0" prefHeight="180.0" prefWidth="200.0"> + <padding> + <Insets bottom="4.0" left="4.0" right="4.0" top="4.0" /> + </padding> + <Button fx:id="generateButton" layoutX="129.0" layoutY="196.0" mnemonicParsing="false" text="Generate" AnchorPane.bottomAnchor="0.0" /> + <TextArea fx:id="stackTraceArea" layoutY="-44.0" prefHeight="200.0" prefWidth="200.0" promptText="Paste a stack trace here. Clicking the button will generate an XPath expression that matches the node in which the exception was thrown —but may match other nodes." AnchorPane.bottomAnchor="40.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0" /> + </AnchorPane> + </TitledPane> +</AnchorPane> diff --git a/pmd-ui/src/main/resources/net/sourceforge/pmd/util/fxdesigner/fxml/node-info.fxml b/pmd-ui/src/main/resources/net/sourceforge/pmd/util/fxdesigner/fxml/node-info.fxml new file mode 100644 index 00000000000..2561fb159b8 --- /dev/null +++ b/pmd-ui/src/main/resources/net/sourceforge/pmd/util/fxdesigner/fxml/node-info.fxml @@ -0,0 +1,90 @@ +<?xml version="1.0" encoding="UTF-8"?> + +<!-- Left pane of the designer. Displays node information. --> + +<?import javafx.scene.control.Label?> +<?import javafx.scene.control.ListView?> +<?import javafx.scene.control.Tab?> +<?import javafx.scene.control.TabPane?> +<?import javafx.scene.control.ToolBar?> +<?import javafx.scene.control.Tooltip?> +<?import javafx.scene.control.TreeView?> +<?import javafx.scene.layout.AnchorPane?> +<?import javafx.scene.layout.BorderPane?> +<?import net.sourceforge.pmd.util.fxdesigner.util.controls.MetricResultListCell?> +<?import net.sourceforge.pmd.util.fxdesigner.util.controls.ScopeHierarchyTreeCell?> + +<TabPane fx:id="nodeInfoTabPane" maxWidth="-Infinity" minWidth="-Infinity" prefWidth="300.0" side="LEFT" stylesheets="@../css/designer.css" tabClosingPolicy="UNAVAILABLE" BorderPane.alignment="CENTER" xmlns="http://javafx.com/javafx/9" xmlns:fx="http://javafx.com/fxml/1" fx:controller="net.sourceforge.pmd.util.fxdesigner.NodeInfoPanelController"> + + <tabs> + <Tab text="Attributes"> + <content> + <AnchorPane minHeight="0.0" minWidth="0.0" prefHeight="180.0" prefWidth="200.0"> + <children> + <BorderPane AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0"> + <top> + <ToolBar styleClass="info-title-bar" BorderPane.alignment="CENTER"> + <items> + <Label text="XPath Attributes"> + <tooltip> + <Tooltip text="Those attributes can be accessed via XPath" /> + </tooltip> + </Label> + </items> + </ToolBar> + </top> + <center> + <ListView BorderPane.alignment="CENTER" fx:id="xpathAttributesListView" /> + </center> + </BorderPane> + </children> + </AnchorPane> + </content> + </Tab> + <Tab fx:id="metricResultsTab" text="Metrics"> + <content> + <AnchorPane minHeight="0.0" minWidth="0.0" prefHeight="180.0" prefWidth="200.0"> + <children> + <BorderPane AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0"> + <center> + <ListView fx:id="metricResultsListView" BorderPane.alignment="CENTER"> + <cellFactory> + <MetricResultListCell fx:factory="callback" /> + </cellFactory> + </ListView> + </center> + <top> + <ToolBar styleClass="info-title-bar" BorderPane.alignment="CENTER"> + <items> + <Label fx:id="metricsTitleLabel" text="Metrics" /> + </items> + </ToolBar> + </top> + </BorderPane> + </children> + </AnchorPane> + </content> + </Tab> + <Tab text="Scopes"> + <content> + <AnchorPane minHeight="0.0" minWidth="0.0" prefHeight="180.0" prefWidth="200.0"> + <children> + <BorderPane AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0"> + <center> + <TreeView fx:id="scopeHierarchyTreeView" + BorderPane.alignment="CENTER"/> + </center> + <top> + <ToolBar styleClass="info-title-bar" BorderPane.alignment="CENTER"> + <items> + <Label text="Scope hierarchy" /> + </items> + </ToolBar> + </top> + </BorderPane> + </children> + </AnchorPane> + </content> + </Tab> + </tabs> +</TabPane> diff --git a/pmd-ui/src/main/resources/net/sourceforge/pmd/util/fxdesigner/fxml/xpath-export-wizard.fxml b/pmd-ui/src/main/resources/net/sourceforge/pmd/util/fxdesigner/fxml/xpath-export-wizard.fxml new file mode 100644 index 00000000000..bc34397b4a8 --- /dev/null +++ b/pmd-ui/src/main/resources/net/sourceforge/pmd/util/fxdesigner/fxml/xpath-export-wizard.fxml @@ -0,0 +1,72 @@ +<?xml version="1.0" encoding="UTF-8"?> + +<?import java.lang.*?> +<?import javafx.scene.control.*?> +<?import javafx.scene.layout.*?> +<?import net.sourceforge.pmd.util.fxdesigner.util.codearea.*?> +<?import net.sourceforge.pmd.util.fxdesigner.util.codearea.SyntaxHighlightingCodeArea?> +<?import javafx.scene.control.ChoiceBox?> +<?import javafx.scene.control.Label?> +<?import javafx.scene.control.Slider?> +<?import javafx.scene.control.TextField?> +<?import javafx.scene.control.TitledPane?> +<?import javafx.scene.layout.AnchorPane?> +<?import javafx.scene.layout.ColumnConstraints?> +<?import javafx.scene.layout.GridPane?> +<?import javafx.scene.layout.Pane?> +<?import javafx.scene.layout.RowConstraints?> +<?import javafx.scene.layout.VBox?> + +<AnchorPane prefHeight="400.0" prefWidth="600.0" stylesheets="@../css/designer.css" xmlns="http://javafx.com/javafx/8" + xmlns:fx="http://javafx.com/fxml/1"> + <children> + <TitledPane animated="false" collapsible="false" layoutX="155.0" layoutY="107.0" text="Export XPath to rule" + AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" + AnchorPane.topAnchor="0.0"> + <content> + <AnchorPane minHeight="0.0" minWidth="0.0" prefHeight="180.0" prefWidth="200.0"> + <children> + <VBox prefHeight="200.0" prefWidth="100.0" spacing="10.0" AnchorPane.bottomAnchor="0.0" + AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0"> + <children> + <Pane prefHeight="67.0" prefWidth="578.0"/> + <GridPane hgap="5.0" vgap="10.0"> + <columnConstraints> + <ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0"/> + <ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0"/> + </columnConstraints> + <rowConstraints> + <RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES"/> + <RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES"/> + <RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES"/> + <RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES"/> + <RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES"/> + </rowConstraints> + <children> + <TextField fx:id="nameField" GridPane.columnIndex="1"/> + <Label text="Name"/> + <Label text="Language" GridPane.rowIndex="1"/> + <ChoiceBox fx:id="languageChoiceBox" GridPane.columnIndex="1" + GridPane.rowIndex="1"/> + <Label text="Message" GridPane.rowIndex="2"/> + <Label text="Priority" GridPane.rowIndex="3"/> + <TextField fx:id="messageField" GridPane.columnIndex="1" GridPane.rowIndex="2"/> + <Slider fx:id="prioritySlider" blockIncrement="1.0" majorTickUnit="1.0" + max="5.0" min="1.0" minorTickCount="0" showTickLabels="true" + showTickMarks="true" snapToTicks="true" value="3.0" + GridPane.columnIndex="1" GridPane.rowIndex="3"/> + <TextField fx:id="descriptionField" GridPane.columnIndex="1" + GridPane.rowIndex="4"/> + <Label text="Description" GridPane.rowIndex="4"/> + </children> + </GridPane> + <SyntaxHighlightingCodeArea fx:id="exportResultArea" prefHeight="221.0" prefWidth="563.0" + stylesheets="@../css/editor-theme.css" VBox.vgrow="ALWAYS"/> + </children> + </VBox> + </children> + </AnchorPane> + </content> + </TitledPane> + </children> +</AnchorPane> diff --git a/pmd-ui/src/main/resources/net/sourceforge/pmd/util/fxdesigner/fxml/xpath.fxml b/pmd-ui/src/main/resources/net/sourceforge/pmd/util/fxdesigner/fxml/xpath.fxml new file mode 100644 index 00000000000..b6281032a1a --- /dev/null +++ b/pmd-ui/src/main/resources/net/sourceforge/pmd/util/fxdesigner/fxml/xpath.fxml @@ -0,0 +1,60 @@ +<?xml version="1.0" encoding="UTF-8"?> + +<?import java.lang.String?> +<?import net.sourceforge.pmd.util.fxdesigner.util.codearea.SyntaxHighlightingCodeArea?> +<?import net.sourceforge.pmd.util.fxdesigner.util.controls.PropertyTableView?> +<?import javafx.geometry.Insets?> +<?import javafx.scene.control.ListView?> +<?import javafx.scene.control.SplitPane?> +<?import javafx.scene.control.TitledPane?> +<?import javafx.scene.layout.AnchorPane?> +<AnchorPane minHeight="0.0" minWidth="0.0" prefHeight="180.0" prefWidth="1200.0" stylesheets="@../css/designer.css" xmlns="http://javafx.com/javafx/9" xmlns:fx="http://javafx.com/fxml/1" fx:controller="net.sourceforge.pmd.util.fxdesigner.XPathPanelController"> + <children> + <SplitPane dividerPositions="0.2, 0.8" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0"> + <items> + <AnchorPane> + <children> + <TitledPane animated="false" collapsible="false" styleClass="accent-header" text="Properties" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0"> + <content> + <AnchorPane> + <PropertyTableView fx:id="propertyTableView" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0" /> + <padding> + <Insets bottom="-0.5" left="-0.5" right="-0.5" top="-0.5" /> + </padding> + </AnchorPane> + </content> + </TitledPane> + </children> + </AnchorPane> + <AnchorPane> + <children> + <TitledPane collapsible="false" styleClass="accent-header" text="XPath Expression" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0"> + <content> + <SyntaxHighlightingCodeArea stylesheets="@../css/editor-theme.css" + fx:id="xpathExpressionArea"> + </SyntaxHighlightingCodeArea> + </content> + </TitledPane> + </children> + </AnchorPane> + <AnchorPane> + <children> + <TitledPane fx:id="violationsTitledPane" animated="false" + collapsible="false" styleClass="accent-header" + text="Matched Nodes" AnchorPane.bottomAnchor="0.0" + AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" + AnchorPane.topAnchor="0.0"> + <content> + <ListView fx:id="xpathResultListView" stylesheets="@../css/syntax-highlighting.css" /> + </content> + </TitledPane> + </children> + </AnchorPane> + </items> + <styleClass> + <String fx:value="bottom-pane-split-pane" /> + <String fx:value="accent-header" /> + </styleClass> + </SplitPane> + </children> +</AnchorPane> diff --git a/pmd-ui/src/main/resources/net/sourceforge/pmd/util/fxdesigner/icons/app/designer_logo.jpeg b/pmd-ui/src/main/resources/net/sourceforge/pmd/util/fxdesigner/icons/app/designer_logo.jpeg new file mode 100644 index 00000000000..c886d3ea20c Binary files /dev/null and b/pmd-ui/src/main/resources/net/sourceforge/pmd/util/fxdesigner/icons/app/designer_logo.jpeg differ diff --git a/pmd-ui/src/main/resources/net/sourceforge/pmd/util/fxdesigner/less/constants.less b/pmd-ui/src/main/resources/net/sourceforge/pmd/util/fxdesigner/less/constants.less new file mode 100644 index 00000000000..e7472d9c7e9 --- /dev/null +++ b/pmd-ui/src/main/resources/net/sourceforge/pmd/util/fxdesigner/less/constants.less @@ -0,0 +1,52 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +/* + App-wide color constants and useful mixins. + + Useful resources (TODO add that to a doc page on the site): + + * Less reference (we use Less 1.7.0): + http://lesscss.org/features/ + + * JavaFX CSS reference: + https://docs.oracle.com/javase/8/javafx/api/javafx/scene/doc-files/cssref.html + + * modena.css (base JavaFX stylesheet): + http://hg.openjdk.java.net/openjfx/jfx-dev/rt/file/762a57e4b74a/modules/javafx.controls/src/main/resources/com/sun/javafx/scene/control/skin/modena/modena.css + + * Icon gallery: + https://fontawesome.com/icons?d=gallery + +*/ + +// This is repeated to use it from Less +// Prefer using it over -fx-base +@fx-base: #ececec; + +// Base colors for background slates +@app-base-color: darken(@fx-base, 4%); +@app-darker-slate-color: darken(@fx-base, 14%); + +// Darker accent colors +@darker-accent: darken(@fx-base, 10%); +@darker-accent-focus: darken(@fx-base, 23%); +// Not reserved to borders, I just ran out of names +@darker-accent-border: darken(@fx-base, 20%); + +@selection-focus-color: fadeout(royalblue, 20%); + +// mixin to fix the width of a component +.fix-width(@width) { + -fx-pref-width: @width; + -fx-min-width: @width; + -fx-max-width: @width; +} + +// mixin to fix the height of a component +.fix-height(@height) { + -fx-pref-height: @height; + -fx-min-height: @height; + -fx-max-height: @height; +} diff --git a/pmd-ui/src/main/resources/net/sourceforge/pmd/util/fxdesigner/less/designer.less b/pmd-ui/src/main/resources/net/sourceforge/pmd/util/fxdesigner/less/designer.less new file mode 100644 index 00000000000..91a3fd565d8 --- /dev/null +++ b/pmd-ui/src/main/resources/net/sourceforge/pmd/util/fxdesigner/less/designer.less @@ -0,0 +1,231 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +/* + * Base sheet for the whole app + */ + +@import "constants"; + + +.list-view, .tree-view, .table-view { + -fx-selection-bar: @selection-focus-color; + -fx-selection-bar-non-focused: @darker-accent-border; +} + +.table-view { + -fx-border-color: @darker-accent; + -fx-padding: -1 0 0 0; + + .column-header { + -fx-background-color: @darker-accent; + -fx-border-color: @darker-accent-border; + -fx-border-style: none solid solid solid; + -fx-padding: -1 0 0 0; + + .label { + -fx-font-weight: normal; + -fx-border-color: @darker-accent; + -fx-font-size: 9pt; + -fx-min-height: 19pt; + -fx-max-height: 19pt; + } + } + + .show-hide-columns-button { + -fx-background-color: @darker-accent; + -fx-border-color: @darker-accent-border; + -fx-border-style: none solid solid solid; + -fx-padding: -1 0 0 0; + } +} + +.text-area { + -fx-background-insets: 0; + -fx-background-color: transparent, white, transparent, white; + -fx-background-radius: 0; + + -fx-box-border: none; + -fx-focus-color: -fx-control-inner-background; + -fx-faint-focus-color: -fx-control-inner-background; + -fx-text-box-border: -fx-control-inner-background; + -fx-border-width: -1; + + .content { + -fx-background-color: transparent, white, transparent, white; + } +} + + +.context-menu.no-scroll-arrows { + + .menu-item { + // OK this is weird but it does solve a padding bug on the context menu. + // Without it, items are offset to the bottom, and the last one is partially hidden + -fx-border-style: solid; + -fx-border-color: transparent; + } + + .scroll-arrow { + -fx-padding: 0; + } + +} + +.split-pane { + -fx-padding: 0; + + .split-pane-divider { + -fx-padding: 0; + -fx-border-color: transparent; + -fx-background-color: @darker-accent; + } +} + + +#nodeInfoPanel { + .fix-width(300); +} + +.titled-pane .title, +.info-title-bar { + -fx-font-size: 9pt; + -fx-pref-height: 24.0; + -fx-border-radius: 0.0; + -fx-background-radius: 0.0; + -fx-background-color: @app-darker-slate-color; + + &.info-title-bar .label { + -fx-padding: 0 0 0 6; + } + +} + +#main-horizontal-split-pane > .split-pane-divider { + -fx-background-color: @darker-accent-focus; +} + +#main-toolbar, +.tool-bar.accent-header, +.split-pane.accent-header > .split-pane-divider { + -fx-background-color: @app-darker-slate-color; +} + + +.tool-bar { + .fix-height(30); + -fx-border-color: transparent; + -fx-border-width: .6; + + .button, .choice-box { + -fx-background-color: @app-base-color; + -fx-border-color: @darker-accent-border; + -fx-border-radius: 3; + } + + .button { + -fx-padding: -3 5 -3 5; + } +} + +// This is used for buttons that have just an icon and no text +.button.icon-button { + -fx-pref-width: 20; + -fx-pref-height: 20; +} + +/* This is the special button to reduce the lower split pane. */ +.toggle-button.expand-toggle { + -fx-background-color: -fx-mark-highlight-color, -fx-mark-color; + -fx-background-insets: 1 0 -1 0, 0; + -fx-padding: 0.25em 0.3125em 0.25em 0.3125em; /* 3 3.75 3 3.75 */ + -fx-shape: "M 0 0 h 7 l -3.5 -4 z"; + + .fix-height(5); + .fix-width(10); + + &:selected { + // This changes the shape to point upward + -fx-shape: "M 0 0 h 7 l -3.5 4 z"; + } +} + + +.tab { + -fx-background-insets: 0.0; + -fx-background-radius: 0.0; + -fx-padding: 0 30 0 30; + -fx-border-color: transparent; + -fx-background-color: transparent; + + &:selected { + -fx-background-color: @darker-accent-focus; + + .focus-indicator { + -fx-focus-color: transparent; + -fx-border-color: transparent; + } + } + + &:focused, &:disabled { + -fx-focus-color: transparent; + } + + .tab-label { + -fx-background-color: transparent; + -fx-alignment: CENTER; + -fx-padding: 0 10 0 10; + } + +} + +.tab-pane { + -fx-tab-min-height: 20px; + -fx-tab-max-height: 20px; + + .tab-header-area { + -fx-padding: 0; + + .tab-header-background { + -fx-border-style: none none solid none; + -fx-border-insets: 0 0 1pt 0; + -fx-border-width: 0 0 1pt 0; + -fx-border-color: transparent; + -fx-background-color: @app-base-color; + } + + } + + &:top .tab-header-area { + -fx-background-insets: 0 0 1 0; + } + + &:left .tab-header-area { + -fx-background-insets: 0 1 0 0; + } +} + +// Useful link: http://www.guigarage.com/2015/11/styling-a-javafx-scrollbar/ +.scroll-bar { + + -fx-padding: 0; + + * { + -fx-background-color: white; + } + + .thumb { + -fx-background-color: derive(black, 90%); + -fx-background-radius: 2em; + } + + .increment-arrow, .decrement-arrow { + -fx-shape: " "; + } +} + +// this is the corner when there is a horizontal and vertical scrollbar +.corner { + -fx-background-color: white; +} diff --git a/pmd-ui/src/main/resources/net/sourceforge/pmd/util/fxdesigner/less/editor-theme.less b/pmd-ui/src/main/resources/net/sourceforge/pmd/util/fxdesigner/less/editor-theme.less new file mode 100644 index 00000000000..0e434691c13 --- /dev/null +++ b/pmd-ui/src/main/resources/net/sourceforge/pmd/util/fxdesigner/less/editor-theme.less @@ -0,0 +1,127 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +/* Presets for the editor theme and syntax highlighting. + + * CSS reference of the code area: + https://github.com/FXMisc/RichTextFX/wiki/RichTextFX-CSS-Reference-Guide +*/ + +@import "constants"; + +// Each color in this stack corresponds to a depth level in XPath colors +@xpath-color-stack: + rgba(249, 248, 113, 0.25), + rgba(255, 199, 95, 0.4), + rgba(255, 150, 113, 0.5), + rgba(255, 111, 145, 0.6), + rgba(214, 93, 177, 0.7), + rgba(132, 94, 194, 0.8); + +@active-line-background-color: #fbf4e1; +@area-background-color: whitesmoke; +@code-color: #585858; + +/* Highlight classes */ + +/* Add a border around highlighted nodes if they fit on one line */ +/* Otherwise, the background variation should be enough to spot them */ +.inline-highlight { + -rtfx-border-stroke-width: .75pt; + -rtfx-border-stroke-type: centered; +} + +.xpath-result-highlight { + + // mixin to iterate over xpath colors and create ruleset for each one + .make-depth-classes(@depth: length(@xpath-color-stack) - 1) when (@depth >= 0) { + .make-depth-classes(@depth - 1); + + // the first style class is depth-0, but CSS arrays indices start at 1 + @background: extract(@xpath-color-stack, @depth + 1); + @border: darken(@background, 30%); + + &.depth-@{depth} { + -rtfx-background-color: @background; + -rtfx-border-stroke-color: @border; + } + } + + .make-depth-classes; +} + +.error-highlight.depth-0 { + -rtfx-border-stroke-color: darkred; + -rtfx-background-color: lightcoral; +} + +.name-occurence-highlight.depth-0 { + -rtfx-border-stroke-color: olivedrab; + -rtfx-background-color: palegreen; +} + +/* With syntax highlighting, it's more readable to not change the fill color */ +.focus-highlight { + -fx-font-weight: bold; +} + + +/* Base */ +.styled-text-area { + -fx-background-color: @area-background-color; + -fx-border-color: -fx-outer-border, -fx-text-box-border; + -fx-border-insets: 0, 0; + -fx-padding: 0; + + .scroll-bar { + * { + -fx-background-color: @area-background-color; + -fx-border-color: @area-background-color; + } + } + + .paragraph-box:has-caret { + -fx-background-color: @active-line-background-color; + } + + .text { + -fx-fill: @code-color; + -fx-font-size: 10.5pt; + } + +} + +#xpathExpressionArea .text { + -fx-font-size: 11.5pt; +} + + +/* Line numbers */ +.lineno { + + -fx-fill: #586E75; + -fx-font-size: 80%; + -fx-font-weight: normal; + + @lineno-focus-base: average(@active-line-background-color, @darker-accent); + + &:is-focus-node { + -fx-background-color: darken(@lineno-focus-base, 5%); + + .text { + -fx-font-weight: bolder; + } + } + + &:has-caret { + -fx-background-color: darken(@lineno-focus-base, 15%); + + .text { + -fx-fill: white; + -fx-font-weight: bolder; + } + } +} + +@import "syntax-highlighting"; \ No newline at end of file diff --git a/pmd-ui/src/main/resources/net/sourceforge/pmd/util/fxdesigner/less/syntax-highlighting.less b/pmd-ui/src/main/resources/net/sourceforge/pmd/util/fxdesigner/less/syntax-highlighting.less new file mode 100644 index 00000000000..5c8897f2daf --- /dev/null +++ b/pmd-ui/src/main/resources/net/sourceforge/pmd/util/fxdesigner/less/syntax-highlighting.less @@ -0,0 +1,101 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +/* + Rules for the syntax highlighters. + This file doesn't include highlight rules, so that highlight is not displayed + on controls that display node rich text if only this file is set as stylesheet. + */ + + +.code { + + /* Common classes */ + + &.keyword { + -fx-fill: #B58900; + /* -fx-font-weight: bold; */ + } + + &.punctuation { + + } + + &.literal { + -fx-fill: #317ECC; + } + + &.comment { + -fx-fill: #93A1A1; + -fx-font-style: italic; /* doesn't work... */ + } + + &.annotation { + -fx-fill: #d30102; + } + + /* Java specific */ + + &.java { + &.class-identifier { + -fx-fill: #B05A65; + } + } + + /* XPath specific */ + + &.xpath { + + &.path { + -fx-font-weight: bold; + } + + &.axis { + -fx-fill: #B05A65; + } + + &.bracket { + -fx-font-weight: bolder; + } + + &.attribute { + -fx-fill: #B05A65; + } + + &.function { + -fx-fill: #cc971b; + } + + } + + /* XML specific */ + + &.xml { + + &.xml-prolog { + -fx-font-weight: bolder; + } + + &.tag-name { + -fx-fill: #B05A65; + } + + &.lt-gt { + -fx-fill: #B05A65; + } + + &.attribute-name { + -fx-fill: #B58900; + } + + &.cdata-tag { + -fx-fill: #B58900; + } + + &.cdata-content { + -fx-fill: #93A1A1; + } + + } +} diff --git a/pmd-ui/src/test/java/net/sourceforge/pmd/util/fxdesigner/util/DesignerUtilTest.java b/pmd-ui/src/test/java/net/sourceforge/pmd/util/fxdesigner/util/DesignerUtilTest.java new file mode 100644 index 00000000000..03913ad5748 --- /dev/null +++ b/pmd-ui/src/test/java/net/sourceforge/pmd/util/fxdesigner/util/DesignerUtilTest.java @@ -0,0 +1,25 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.util.fxdesigner.util; + +import static org.junit.Assert.assertNotNull; + +import org.junit.Test; + + +/** + * @author Clément Fournier + * @since 6.0.0 + */ +public class DesignerUtilTest { + + @Test + public void testGetFxml() { + assertNotNull(DesignerUtil.getFxml("designer.fxml")); + assertNotNull(DesignerUtil.getFxml("xpath.fxml")); + } + + +} diff --git a/pmd-visualforce/pom.xml b/pmd-visualforce/pom.xml index 4a8d26ce932..d0bdcc7ea56 100644 --- a/pmd-visualforce/pom.xml +++ b/pmd-visualforce/pom.xml @@ -7,13 +7,9 @@ <parent> <groupId>net.sourceforge.pmd</groupId> <artifactId>pmd</artifactId> - <version>6.0.0-SNAPSHOT</version> + <version>6.10.0-SNAPSHOT</version> </parent> - <properties> - <config.basedir>${basedir}/../pmd-core</config.basedir> - </properties> - <build> <resources> <resource> @@ -100,7 +96,20 @@ <groupId>net.sourceforge.pmd</groupId> <artifactId>pmd-core</artifactId> </dependency> + <dependency> + <groupId>commons-io</groupId> + <artifactId>commons-io</artifactId> + </dependency> + <dependency> + <groupId>net.sourceforge.saxon</groupId> + <artifactId>saxon</artifactId> + </dependency> + <dependency> + <groupId>junit</groupId> + <artifactId>junit</artifactId> + <scope>test</scope> + </dependency> <dependency> <groupId>net.sourceforge.pmd</groupId> <artifactId>pmd-test</artifactId> diff --git a/pmd-visualforce/src/main/ant/alljavacc.xml b/pmd-visualforce/src/main/ant/alljavacc.xml index 9637b351514..b2463ff9569 100644 --- a/pmd-visualforce/src/main/ant/alljavacc.xml +++ b/pmd-visualforce/src/main/ant/alljavacc.xml @@ -63,12 +63,54 @@ public class]]></replacevalue> </replace> <replace file="${target}/net/sourceforge/pmd/lang/vf/ast/Token.java"> - <replacetoken>public class Token</replacetoken> + <replacetoken>public class Token implements java.io.Serializable</replacetoken> <replacevalue><![CDATA[import net.sourceforge.pmd.lang.ast.GenericToken; -public class Token extends GenericToken]]></replacevalue> +public class Token implements GenericToken, java.io.Serializable]]></replacevalue> </replace> + <replace file="${target}/net/sourceforge/pmd/lang/vf/ast/Token.java"> + <replacetoken>public Token specialToken;</replacetoken> + <replacevalue><![CDATA[public Token specialToken; + + @Override + public GenericToken getNext() { + return next; + } + + @Override + public GenericToken getPreviousComment() { + return specialToken; + } + + @Override + public String getImage() { + return image; + } + + @Override + public int getBeginLine() { + return beginLine; + } + + @Override + public int getEndLine() { + return endLine; + } + + @Override + public int getBeginColumn() { + return beginColumn; + } + + @Override + public int getEndColumn() { + return endColumn; + } + +]]></replacevalue> + </replace> + <delete> <fileset dir="${target}/net/sourceforge/pmd/lang/vf/ast"> <include name="AST*.java" /> diff --git a/pmd-visualforce/src/main/java/net/sourceforge/pmd/cpd/VfTokenizer.java b/pmd-visualforce/src/main/java/net/sourceforge/pmd/cpd/VfTokenizer.java index 7c7f8386ec4..9ddce331de7 100644 --- a/pmd-visualforce/src/main/java/net/sourceforge/pmd/cpd/VfTokenizer.java +++ b/pmd-visualforce/src/main/java/net/sourceforge/pmd/cpd/VfTokenizer.java @@ -4,11 +4,10 @@ package net.sourceforge.pmd.cpd; +import java.io.IOException; import java.io.Reader; import java.io.StringReader; -import org.apache.commons.io.IOUtils; - import net.sourceforge.pmd.lang.LanguageRegistry; import net.sourceforge.pmd.lang.LanguageVersionHandler; import net.sourceforge.pmd.lang.TokenManager; @@ -22,15 +21,13 @@ */ public class VfTokenizer implements Tokenizer { + @Override public void tokenize(SourceCode sourceCode, Tokens tokenEntries) { StringBuilder buffer = sourceCode.getCodeBuffer(); LanguageVersionHandler languageVersionHandler = LanguageRegistry.getLanguage(VfLanguageModule.NAME) .getDefaultVersion().getLanguageVersionHandler(); - Reader reader = null; - try { - reader = new StringReader(buffer.toString()); - reader = IOUtil.skipBOM(reader); + try (Reader reader = IOUtil.skipBOM(new StringReader(buffer.toString()))) { TokenManager tokenMgr = languageVersionHandler.getParser(languageVersionHandler.getDefaultParserOptions()) .getTokenManager(sourceCode.getFileName(), reader); Token currentToken = (Token) tokenMgr.getNextToken(); @@ -40,8 +37,8 @@ public void tokenize(SourceCode sourceCode, Tokens tokenEntries) { currentToken.beginLine)); currentToken = (Token) tokenMgr.getNextToken(); } - } finally { - IOUtils.closeQuietly(reader); + } catch (IOException e) { + e.printStackTrace(); } tokenEntries.add(TokenEntry.getEOF()); } diff --git a/pmd-visualforce/src/main/java/net/sourceforge/pmd/lang/vf/VfHandler.java b/pmd-visualforce/src/main/java/net/sourceforge/pmd/lang/vf/VfHandler.java index 986b44bd904..236a304f353 100644 --- a/pmd-visualforce/src/main/java/net/sourceforge/pmd/lang/vf/VfHandler.java +++ b/pmd-visualforce/src/main/java/net/sourceforge/pmd/lang/vf/VfHandler.java @@ -12,31 +12,25 @@ import net.sourceforge.pmd.lang.VisitorStarter; import net.sourceforge.pmd.lang.XPathHandler; import net.sourceforge.pmd.lang.ast.Node; -import net.sourceforge.pmd.lang.ast.xpath.AbstractASTXPathHandler; +import net.sourceforge.pmd.lang.ast.xpath.DefaultASTXPathHandler; import net.sourceforge.pmd.lang.rule.RuleViolationFactory; import net.sourceforge.pmd.lang.vf.ast.DumpFacade; import net.sourceforge.pmd.lang.vf.ast.VfNode; import net.sourceforge.pmd.lang.vf.rule.VfRuleViolationFactory; -import net.sf.saxon.sxpath.IndependentContext; - public class VfHandler extends AbstractLanguageVersionHandler { @Override public XPathHandler getXPathHandler() { - return new AbstractASTXPathHandler() { - public void initialize() { - } - - public void initialize(IndependentContext context) { - } - }; + return new DefaultASTXPathHandler(); } + @Override public RuleViolationFactory getRuleViolationFactory() { return VfRuleViolationFactory.INSTANCE; } + @Override public Parser getParser(ParserOptions parserOptions) { return new VfParser(parserOptions); } @@ -44,6 +38,7 @@ public Parser getParser(ParserOptions parserOptions) { @Override public VisitorStarter getDumpFacade(final Writer writer, final String prefix, final boolean recurse) { return new VisitorStarter() { + @Override public void start(Node rootNode) { new DumpFacade().initializeWith(writer, prefix, recurse, (VfNode) rootNode); } diff --git a/pmd-visualforce/src/main/java/net/sourceforge/pmd/lang/vf/VfParser.java b/pmd-visualforce/src/main/java/net/sourceforge/pmd/lang/vf/VfParser.java index 07a973e209f..36a92c060ca 100644 --- a/pmd-visualforce/src/main/java/net/sourceforge/pmd/lang/vf/VfParser.java +++ b/pmd-visualforce/src/main/java/net/sourceforge/pmd/lang/vf/VfParser.java @@ -29,15 +29,18 @@ public TokenManager createTokenManager(Reader source) { return new VfTokenManager(source); } + @Override public boolean canParse() { return true; } + @Override public Node parse(String fileName, Reader source) throws ParseException { AbstractTokenManager.setFileName(fileName); return new net.sourceforge.pmd.lang.vf.ast.VfParser(new VfSimpleCharStream(source)).CompilationUnit(); } + @Override public Map<Integer, String> getSuppressMap() { return new HashMap<>(); // FIXME } diff --git a/pmd-visualforce/src/main/java/net/sourceforge/pmd/lang/vf/VfTokenManager.java b/pmd-visualforce/src/main/java/net/sourceforge/pmd/lang/vf/VfTokenManager.java index afbba717f0a..1fe672d3aab 100644 --- a/pmd-visualforce/src/main/java/net/sourceforge/pmd/lang/vf/VfTokenManager.java +++ b/pmd-visualforce/src/main/java/net/sourceforge/pmd/lang/vf/VfTokenManager.java @@ -20,10 +20,12 @@ public VfTokenManager(Reader source) { tokenManager = new VfParserTokenManager(new JavaCharStream(source)); } + @Override public Object getNextToken() { return tokenManager.getNextToken(); } + @Override public void setFileName(String fileName) { VfParserTokenManager.setFileName(fileName); } diff --git a/pmd-visualforce/src/main/java/net/sourceforge/pmd/lang/vf/ast/ASTArguments.java b/pmd-visualforce/src/main/java/net/sourceforge/pmd/lang/vf/ast/ASTArguments.java index 25968e9e51a..64a92dd2e65 100644 --- a/pmd-visualforce/src/main/java/net/sourceforge/pmd/lang/vf/ast/ASTArguments.java +++ b/pmd-visualforce/src/main/java/net/sourceforge/pmd/lang/vf/ast/ASTArguments.java @@ -14,6 +14,7 @@ public ASTArguments(VfParser p, int id) { } /** Accept the visitor. **/ + @Override public Object jjtAccept(VfParserVisitor visitor, Object data) { return visitor.visit(this, data); diff --git a/pmd-visualforce/src/main/java/net/sourceforge/pmd/lang/vf/ast/ASTAttributeValue.java b/pmd-visualforce/src/main/java/net/sourceforge/pmd/lang/vf/ast/ASTAttributeValue.java index 4a0afe94864..0245ad60300 100644 --- a/pmd-visualforce/src/main/java/net/sourceforge/pmd/lang/vf/ast/ASTAttributeValue.java +++ b/pmd-visualforce/src/main/java/net/sourceforge/pmd/lang/vf/ast/ASTAttributeValue.java @@ -14,6 +14,7 @@ public ASTAttributeValue(VfParser p, int id) { } /** Accept the visitor. **/ + @Override public Object jjtAccept(VfParserVisitor visitor, Object data) { return visitor.visit(this, data); diff --git a/pmd-visualforce/src/main/java/net/sourceforge/pmd/lang/vf/ast/ASTCData.java b/pmd-visualforce/src/main/java/net/sourceforge/pmd/lang/vf/ast/ASTCData.java index 341f9bd74fa..53767f363ce 100644 --- a/pmd-visualforce/src/main/java/net/sourceforge/pmd/lang/vf/ast/ASTCData.java +++ b/pmd-visualforce/src/main/java/net/sourceforge/pmd/lang/vf/ast/ASTCData.java @@ -17,6 +17,7 @@ public ASTCData(VfParser p, int id) { /** * Accept the visitor. * */ + @Override public Object jjtAccept(VfParserVisitor visitor, Object data) { return visitor.visit(this, data); } diff --git a/pmd-visualforce/src/main/java/net/sourceforge/pmd/lang/vf/ast/ASTCompilationUnit.java b/pmd-visualforce/src/main/java/net/sourceforge/pmd/lang/vf/ast/ASTCompilationUnit.java index 2c58d140e72..10b6110e9ea 100644 --- a/pmd-visualforce/src/main/java/net/sourceforge/pmd/lang/vf/ast/ASTCompilationUnit.java +++ b/pmd-visualforce/src/main/java/net/sourceforge/pmd/lang/vf/ast/ASTCompilationUnit.java @@ -19,6 +19,7 @@ public ASTCompilationUnit(VfParser p, int id) { /** * Accept the visitor. * */ + @Override public Object jjtAccept(VfParserVisitor visitor, Object data) { return visitor.visit(this, data); } diff --git a/pmd-visualforce/src/main/java/net/sourceforge/pmd/lang/vf/ast/ASTContent.java b/pmd-visualforce/src/main/java/net/sourceforge/pmd/lang/vf/ast/ASTContent.java index 2aa397b0ca4..af7bbafc5a6 100644 --- a/pmd-visualforce/src/main/java/net/sourceforge/pmd/lang/vf/ast/ASTContent.java +++ b/pmd-visualforce/src/main/java/net/sourceforge/pmd/lang/vf/ast/ASTContent.java @@ -14,6 +14,7 @@ public ASTContent(VfParser p, int id) { } /** Accept the visitor. **/ + @Override public Object jjtAccept(VfParserVisitor visitor, Object data) { return visitor.visit(this, data); diff --git a/pmd-visualforce/src/main/java/net/sourceforge/pmd/lang/vf/ast/ASTDotExpression.java b/pmd-visualforce/src/main/java/net/sourceforge/pmd/lang/vf/ast/ASTDotExpression.java index df1c373f575..7b46142c7a0 100644 --- a/pmd-visualforce/src/main/java/net/sourceforge/pmd/lang/vf/ast/ASTDotExpression.java +++ b/pmd-visualforce/src/main/java/net/sourceforge/pmd/lang/vf/ast/ASTDotExpression.java @@ -16,6 +16,7 @@ public ASTDotExpression(VfParser p, int id) { /** * Accept the visitor. * */ + @Override public Object jjtAccept(VfParserVisitor visitor, Object data) { return visitor.visit(this, data); } diff --git a/pmd-visualforce/src/main/java/net/sourceforge/pmd/lang/vf/ast/ASTElExpression.java b/pmd-visualforce/src/main/java/net/sourceforge/pmd/lang/vf/ast/ASTElExpression.java index 395f5282198..4b7e7af26c6 100644 --- a/pmd-visualforce/src/main/java/net/sourceforge/pmd/lang/vf/ast/ASTElExpression.java +++ b/pmd-visualforce/src/main/java/net/sourceforge/pmd/lang/vf/ast/ASTElExpression.java @@ -17,6 +17,7 @@ public ASTElExpression(VfParser p, int id) { /** * Accept the visitor. * */ + @Override public Object jjtAccept(VfParserVisitor visitor, Object data) { return visitor.visit(this, data); } diff --git a/pmd-visualforce/src/main/java/net/sourceforge/pmd/lang/vf/ast/ASTExpression.java b/pmd-visualforce/src/main/java/net/sourceforge/pmd/lang/vf/ast/ASTExpression.java index ec6055597fd..5caec195982 100644 --- a/pmd-visualforce/src/main/java/net/sourceforge/pmd/lang/vf/ast/ASTExpression.java +++ b/pmd-visualforce/src/main/java/net/sourceforge/pmd/lang/vf/ast/ASTExpression.java @@ -14,6 +14,7 @@ public ASTExpression(VfParser p, int id) { } /** Accept the visitor. **/ + @Override public Object jjtAccept(VfParserVisitor visitor, Object data) { return visitor.visit(this, data); diff --git a/pmd-visualforce/src/main/java/net/sourceforge/pmd/lang/vf/ast/ASTIdentifier.java b/pmd-visualforce/src/main/java/net/sourceforge/pmd/lang/vf/ast/ASTIdentifier.java index 1286ee029f4..9d84c200894 100644 --- a/pmd-visualforce/src/main/java/net/sourceforge/pmd/lang/vf/ast/ASTIdentifier.java +++ b/pmd-visualforce/src/main/java/net/sourceforge/pmd/lang/vf/ast/ASTIdentifier.java @@ -14,6 +14,7 @@ public ASTIdentifier(VfParser p, int id) { } /** Accept the visitor. **/ + @Override public Object jjtAccept(VfParserVisitor visitor, Object data) { return visitor.visit(this, data); diff --git a/pmd-visualforce/src/main/java/net/sourceforge/pmd/lang/vf/ast/ASTLiteral.java b/pmd-visualforce/src/main/java/net/sourceforge/pmd/lang/vf/ast/ASTLiteral.java index 9694247107e..cec178d1df8 100644 --- a/pmd-visualforce/src/main/java/net/sourceforge/pmd/lang/vf/ast/ASTLiteral.java +++ b/pmd-visualforce/src/main/java/net/sourceforge/pmd/lang/vf/ast/ASTLiteral.java @@ -14,6 +14,7 @@ public ASTLiteral(VfParser p, int id) { } /** Accept the visitor. **/ + @Override public Object jjtAccept(VfParserVisitor visitor, Object data) { return visitor.visit(this, data); diff --git a/pmd-visualforce/src/main/java/net/sourceforge/pmd/lang/vf/ast/ASTNegationExpression.java b/pmd-visualforce/src/main/java/net/sourceforge/pmd/lang/vf/ast/ASTNegationExpression.java index 6bd638c3e9f..130ade8dd98 100644 --- a/pmd-visualforce/src/main/java/net/sourceforge/pmd/lang/vf/ast/ASTNegationExpression.java +++ b/pmd-visualforce/src/main/java/net/sourceforge/pmd/lang/vf/ast/ASTNegationExpression.java @@ -14,6 +14,7 @@ public ASTNegationExpression(VfParser p, int id) { } /** Accept the visitor. **/ + @Override public Object jjtAccept(VfParserVisitor visitor, Object data) { return visitor.visit(this, data); diff --git a/pmd-visualforce/src/main/java/net/sourceforge/pmd/lang/vf/ast/ASTText.java b/pmd-visualforce/src/main/java/net/sourceforge/pmd/lang/vf/ast/ASTText.java index e6848143875..16a359cdf86 100644 --- a/pmd-visualforce/src/main/java/net/sourceforge/pmd/lang/vf/ast/ASTText.java +++ b/pmd-visualforce/src/main/java/net/sourceforge/pmd/lang/vf/ast/ASTText.java @@ -17,6 +17,7 @@ public ASTText(VfParser p, int id) { /** * Accept the visitor. * */ + @Override public Object jjtAccept(VfParserVisitor visitor, Object data) { return visitor.visit(this, data); } diff --git a/pmd-visualforce/src/main/java/net/sourceforge/pmd/lang/vf/ast/AbstractVFNode.java b/pmd-visualforce/src/main/java/net/sourceforge/pmd/lang/vf/ast/AbstractVFNode.java index 00b8c3d880e..57e12782d3b 100644 --- a/pmd-visualforce/src/main/java/net/sourceforge/pmd/lang/vf/ast/AbstractVFNode.java +++ b/pmd-visualforce/src/main/java/net/sourceforge/pmd/lang/vf/ast/AbstractVFNode.java @@ -19,6 +19,7 @@ public AbstractVFNode(VfParser parser, int id) { this.parser = parser; } + @Override public void jjtOpen() { if (beginLine == -1 && parser.token.next != null) { beginLine = parser.token.next.beginLine; @@ -26,6 +27,7 @@ public void jjtOpen() { } } + @Override public void jjtClose() { if (beginLine == -1 && (children == null || children.length == 0)) { beginColumn = parser.token.beginColumn; @@ -40,6 +42,7 @@ public void jjtClose() { /** * Accept the visitor. * */ + @Override public Object jjtAccept(VfParserVisitor visitor, Object data) { return visitor.visit(this, data); } @@ -47,6 +50,7 @@ public Object jjtAccept(VfParserVisitor visitor, Object data) { /** * Accept the visitor. * */ + @Override public Object childrenAccept(VfParserVisitor visitor, Object data) { if (children != null) { for (int i = 0; i < children.length; ++i) { @@ -56,7 +60,11 @@ public Object childrenAccept(VfParserVisitor visitor, Object data) { return data; } - public String toString() { + + + + @Override + public String getXPathNodeName() { return VfParserTreeConstants.jjtNodeName[id]; } } diff --git a/pmd-visualforce/src/main/java/net/sourceforge/pmd/lang/vf/ast/DumpFacade.java b/pmd-visualforce/src/main/java/net/sourceforge/pmd/lang/vf/ast/DumpFacade.java index 8f37a4d67f8..704ae8e1a34 100644 --- a/pmd-visualforce/src/main/java/net/sourceforge/pmd/lang/vf/ast/DumpFacade.java +++ b/pmd-visualforce/src/main/java/net/sourceforge/pmd/lang/vf/ast/DumpFacade.java @@ -47,7 +47,7 @@ private void dump(Node node, String prefix) { writer.print(prefix); // 2) JJT Name of the Node - writer.print(node.toString()); + writer.print(node.getXPathNodeName()); // // If there are any additional details, then: diff --git a/pmd-visualforce/src/main/java/net/sourceforge/pmd/lang/vf/ast/OpenTagRegister.java b/pmd-visualforce/src/main/java/net/sourceforge/pmd/lang/vf/ast/OpenTagRegister.java index 6ef3adbcbe0..2937e2a080a 100644 --- a/pmd-visualforce/src/main/java/net/sourceforge/pmd/lang/vf/ast/OpenTagRegister.java +++ b/pmd-visualforce/src/main/java/net/sourceforge/pmd/lang/vf/ast/OpenTagRegister.java @@ -15,9 +15,9 @@ * tag list and it will mark the first tag named 'x' as closed. If other tags * have been opened after 'x' ( <x> <y> <z> </x>) it * will mark y and z as unclosed. - * + * * @author Victor Bucutea - * + * */ public class OpenTagRegister { @@ -32,7 +32,7 @@ public void openTag(ASTElement elm) { } /** - * + * * @param closingTagName * @return true if a matching tag was found. False if no tag with this name * was ever opened ( or registered ) diff --git a/pmd-visualforce/src/main/java/net/sourceforge/pmd/lang/vf/ast/StartAndEndTagMismatchException.java b/pmd-visualforce/src/main/java/net/sourceforge/pmd/lang/vf/ast/StartAndEndTagMismatchException.java index 6a261d7b246..bb398ce1114 100644 --- a/pmd-visualforce/src/main/java/net/sourceforge/pmd/lang/vf/ast/StartAndEndTagMismatchException.java +++ b/pmd-visualforce/src/main/java/net/sourceforge/pmd/lang/vf/ast/StartAndEndTagMismatchException.java @@ -73,7 +73,7 @@ public int getStartLine() { /* * (non-Javadoc) - * + * * @see java.lang.Throwable#getMessage() */ @Override diff --git a/pmd-visualforce/src/main/java/net/sourceforge/pmd/lang/vf/ast/SyntaxErrorException.java b/pmd-visualforce/src/main/java/net/sourceforge/pmd/lang/vf/ast/SyntaxErrorException.java index d24a9712347..c2c8b0d6ec6 100644 --- a/pmd-visualforce/src/main/java/net/sourceforge/pmd/lang/vf/ast/SyntaxErrorException.java +++ b/pmd-visualforce/src/main/java/net/sourceforge/pmd/lang/vf/ast/SyntaxErrorException.java @@ -6,7 +6,7 @@ /** * Exception indicating that a syntactic error has been found. - * + * * @author Pieter_Van_Raemdonck * @since Created on 11-jan-2006 */ diff --git a/pmd-visualforce/src/main/java/net/sourceforge/pmd/lang/vf/ast/VfParserVisitorAdapter.java b/pmd-visualforce/src/main/java/net/sourceforge/pmd/lang/vf/ast/VfParserVisitorAdapter.java index 8600cc48ac6..693bda0b514 100644 --- a/pmd-visualforce/src/main/java/net/sourceforge/pmd/lang/vf/ast/VfParserVisitorAdapter.java +++ b/pmd-visualforce/src/main/java/net/sourceforge/pmd/lang/vf/ast/VfParserVisitorAdapter.java @@ -6,51 +6,63 @@ public class VfParserVisitorAdapter implements VfParserVisitor { + @Override public Object visit(VfNode node, Object data) { node.childrenAccept(this, data); return data; } + @Override public Object visit(ASTCompilationUnit node, Object data) { return visit((VfNode) node, data); } + @Override public Object visit(ASTText node, Object data) { return visit((VfNode) node, data); } + @Override public Object visit(ASTElExpression node, Object data) { return visit((VfNode) node, data); } + @Override public Object visit(ASTCData node, Object data) { return visit((VfNode) node, data); } + @Override public Object visit(ASTElement node, Object data) { return visit((VfNode) node, data); } + @Override public Object visit(ASTAttribute node, Object data) { return visit((VfNode) node, data); } + @Override public Object visit(ASTAttributeValue node, Object data) { return visit((VfNode) node, data); } + @Override public Object visit(ASTDeclaration node, Object data) { return visit((VfNode) node, data); } + @Override public Object visit(ASTDoctypeDeclaration node, Object data) { return visit((VfNode) node, data); } + @Override public Object visit(ASTDoctypeExternalId node, Object data) { return visit((VfNode) node, data); } + @Override public Object visit(ASTHtmlScript node, Object data) { return visit((VfNode) node, data); } @@ -69,12 +81,12 @@ public Object visit(ASTIdentifier node, Object data) { public Object visit(ASTExpression node, Object data) { return visit((VfNode) node, data); } - + @Override public Object visit(ASTArguments node, Object data) { return visit((VfNode) node, data); } - + @Override public Object visit(ASTDotExpression node, Object data) { return visit((VfNode) node, data); @@ -84,10 +96,10 @@ public Object visit(ASTDotExpression node, Object data) { public Object visit(ASTContent node, Object data) { return visit((VfNode) node, data); } - + @Override public Object visit(ASTNegationExpression node, Object data) { return visit((VfNode) node, data); } - + } diff --git a/pmd-visualforce/src/main/java/net/sourceforge/pmd/lang/vf/rule/AbstractVfRule.java b/pmd-visualforce/src/main/java/net/sourceforge/pmd/lang/vf/rule/AbstractVfRule.java index ae0d0362e23..2329824e180 100644 --- a/pmd-visualforce/src/main/java/net/sourceforge/pmd/lang/vf/rule/AbstractVfRule.java +++ b/pmd-visualforce/src/main/java/net/sourceforge/pmd/lang/vf/rule/AbstractVfRule.java @@ -39,6 +39,7 @@ public AbstractVfRule() { super.setLanguage(LanguageRegistry.getLanguage(VfLanguageModule.NAME)); } + @Override public void apply(List<? extends Node> nodes, RuleContext ctx) { visitAll(nodes, ctx); } @@ -55,79 +56,98 @@ protected void visitAll(List<? extends Node> nodes, RuleContext ctx) { } } + @Override public Object visit(VfNode node, Object data) { node.childrenAccept(this, data); return null; } + @Override public Object visit(ASTCompilationUnit node, Object data) { return visit((VfNode) node, data); } + @Override public Object visit(ASTText node, Object data) { return visit((VfNode) node, data); } + @Override public Object visit(ASTAttributeValue node, Object data) { return visit((VfNode) node, data); } + @Override public Object visit(ASTElExpression node, Object data) { return visit((VfNode) node, data); } + @Override public Object visit(ASTCData node, Object data) { return visit((VfNode) node, data); } + @Override public Object visit(ASTElement node, Object data) { return visit((VfNode) node, data); } + @Override public Object visit(ASTAttribute node, Object data) { return visit((VfNode) node, data); } + @Override public Object visit(ASTDeclaration node, Object data) { return visit((VfNode) node, data); } + @Override public Object visit(ASTDoctypeDeclaration node, Object data) { return visit((VfNode) node, data); } + @Override public Object visit(ASTDoctypeExternalId node, Object data) { return visit((VfNode) node, data); } + @Override public Object visit(ASTHtmlScript node, Object data) { return visit((VfNode) node, data); } + @Override public Object visit(ASTLiteral node, Object data) { return visit((VfNode) node, data); } + @Override public Object visit(ASTIdentifier node, Object data) { return visit((VfNode) node, data); } + @Override public Object visit(ASTExpression node, Object data) { return visit((VfNode) node, data); } + @Override public Object visit(ASTArguments node, Object data) { return visit((VfNode) node, data); } + @Override public Object visit(ASTDotExpression node, Object data) { return visit((VfNode) node, data); } + @Override public Object visit(ASTContent node, Object data) { return visit((VfNode) node, data); } + @Override public Object visit(ASTNegationExpression node, Object data) { return visit((VfNode) node, data); } diff --git a/pmd-visualforce/src/main/java/net/sourceforge/pmd/lang/vf/rule/VfRuleChainVisitor.java b/pmd-visualforce/src/main/java/net/sourceforge/pmd/lang/vf/rule/VfRuleChainVisitor.java index 86a05e63b44..3f476729017 100644 --- a/pmd-visualforce/src/main/java/net/sourceforge/pmd/lang/vf/rule/VfRuleChainVisitor.java +++ b/pmd-visualforce/src/main/java/net/sourceforge/pmd/lang/vf/rule/VfRuleChainVisitor.java @@ -18,6 +18,7 @@ public class VfRuleChainVisitor extends AbstractRuleChainVisitor { + @Override protected void indexNodes(List<Node> nodes, RuleContext ctx) { VfParserVisitor vfParserVisitor = new VfParserVisitorAdapter(); @@ -26,6 +27,7 @@ protected void indexNodes(List<Node> nodes, RuleContext ctx) { } } + @Override protected void visit(Rule rule, Node node, RuleContext ctx) { // Rule better either be a vfParserVisitor, or a XPathRule if (rule instanceof VfParserVisitor) { diff --git a/pmd-visualforce/src/main/java/net/sourceforge/pmd/lang/vf/rule/VfRuleViolationFactory.java b/pmd-visualforce/src/main/java/net/sourceforge/pmd/lang/vf/rule/VfRuleViolationFactory.java index 829856af367..202394278e5 100644 --- a/pmd-visualforce/src/main/java/net/sourceforge/pmd/lang/vf/rule/VfRuleViolationFactory.java +++ b/pmd-visualforce/src/main/java/net/sourceforge/pmd/lang/vf/rule/VfRuleViolationFactory.java @@ -25,9 +25,10 @@ protected RuleViolation createRuleViolation(Rule rule, RuleContext ruleContext, return new ParametricRuleViolation<>(rule, ruleContext, (VfNode) node, message); } + @Override protected RuleViolation createRuleViolation(Rule rule, RuleContext ruleContext, Node node, String message, int beginLine, int endLine) { - + ParametricRuleViolation<VfNode> rViolation = new ParametricRuleViolation<>(rule, ruleContext, (VfNode) node, message); rViolation.setLines(beginLine, endLine); return rViolation; diff --git a/pmd-visualforce/src/main/java/net/sourceforge/pmd/lang/vf/rule/security/VfCsrfRule.java b/pmd-visualforce/src/main/java/net/sourceforge/pmd/lang/vf/rule/security/VfCsrfRule.java index 80877ac92bf..447d0c0e9c1 100644 --- a/pmd-visualforce/src/main/java/net/sourceforge/pmd/lang/vf/rule/security/VfCsrfRule.java +++ b/pmd-visualforce/src/main/java/net/sourceforge/pmd/lang/vf/rule/security/VfCsrfRule.java @@ -5,6 +5,7 @@ package net.sourceforge.pmd.lang.vf.rule.security; import java.util.List; +import java.util.Locale; import net.sourceforge.pmd.lang.vf.ast.ASTAttribute; import net.sourceforge.pmd.lang.vf.ast.ASTElExpression; @@ -29,7 +30,7 @@ public Object visit(ASTElement node, Object data) { ASTElExpression valToReport = null; for (ASTAttribute attr : attribs) { - switch (attr.getName().toLowerCase()) { + switch (attr.getName().toLowerCase(Locale.ROOT)) { case "action": ASTElExpression value = attr.getFirstDescendantOfType(ASTElExpression.class); if (value != null) { diff --git a/pmd-visualforce/src/main/java/net/sourceforge/pmd/lang/vf/rule/security/VfUnescapeElRule.java b/pmd-visualforce/src/main/java/net/sourceforge/pmd/lang/vf/rule/security/VfUnescapeElRule.java index 89145c4ff26..6f7919649cf 100644 --- a/pmd-visualforce/src/main/java/net/sourceforge/pmd/lang/vf/rule/security/VfUnescapeElRule.java +++ b/pmd-visualforce/src/main/java/net/sourceforge/pmd/lang/vf/rule/security/VfUnescapeElRule.java @@ -7,6 +7,7 @@ import java.util.EnumSet; import java.util.HashSet; import java.util.List; +import java.util.Locale; import java.util.Set; import java.util.regex.Pattern; @@ -104,14 +105,10 @@ private void processElInScriptContext(ASTElExpression elExpression, ASTText prev } private boolean isJsonParse(ASTText prevText) { - final String text = (prevText.getImage().endsWith("'") || prevText.getImage().endsWith("'")) + final String text = prevText.getImage().endsWith("'") ? prevText.getImage().substring(0, prevText.getImage().length() - 1) : prevText.getImage(); - if (text.endsWith("JSON.parse(") || text.endsWith("jQuery.parseJSON(") || text.endsWith("$.parseJSON(")) { - return true; - } - - return false; + return text.endsWith("JSON.parse(") || text.endsWith("jQuery.parseJSON(") || text.endsWith("$.parseJSON("); } private boolean isUnbalanced(String image, char pattern) { @@ -125,11 +122,7 @@ private boolean isUnbalanced(String image, char pattern) { } if (array[i] == ';') { - if (foundPattern) { - return true; - } else { - return false; - } + return foundPattern; } } @@ -149,7 +142,7 @@ public Object visit(ASTElement node, Object data) { } private void checkLimitedFlags(ASTElement node, Object data) { - switch (node.getName().toLowerCase()) { + switch (node.getName().toLowerCase(Locale.ROOT)) { case IFRAME_CONST: case APEXIFRAME_CONST: case A_CONST: @@ -163,7 +156,7 @@ private void checkLimitedFlags(ASTElement node, Object data) { final Set<ASTElExpression> toReport = new HashSet<>(); for (ASTAttribute attr : attributes) { - String name = attr.getName().toLowerCase(); + String name = attr.getName().toLowerCase(Locale.ROOT); // look for onevents if (HREF.equalsIgnoreCase(name) || SRC.equalsIgnoreCase(name)) { @@ -172,8 +165,9 @@ private void checkLimitedFlags(ASTElement node, Object data) { final ASTText attrText = attr.getFirstDescendantOfType(ASTText.class); if (attrText != null) { if (0 == attrText.jjtGetChildIndex()) { - if (attrText.getImage().startsWith("/") || attrText.getImage().toLowerCase().startsWith("http") - || attrText.getImage().toLowerCase().startsWith("mailto")) { + String lowerCaseImage = attrText.getImage().toLowerCase(Locale.ROOT); + if (lowerCaseImage.startsWith("/") || lowerCaseImage.startsWith("http") + || lowerCaseImage.startsWith("mailto")) { startingWithSlashText = true; } } @@ -215,7 +209,7 @@ private void checkAllOnEventTags(ASTElement node, Object data) { final Set<ASTElExpression> toReport = new HashSet<>(); for (ASTAttribute attr : attributes) { - String name = attr.getName().toLowerCase(); + String name = attr.getName().toLowerCase(Locale.ROOT); // look for onevents if (ON_EVENT.matcher(name).matches()) { @@ -226,7 +220,7 @@ private void checkAllOnEventTags(ASTElement node, Object data) { } if (doesElContainAnyUnescapedIdentifiers(el, - EnumSet.of(Escaping.JSINHTMLENCODE, Escaping.JSENCODE))) { + EnumSet.of(Escaping.ANY))) { isEL = true; toReport.add(el); } @@ -253,14 +247,15 @@ private boolean startsWithSafeResource(final ASTElExpression el) { final ASTIdentifier id = expression.getFirstChildOfType(ASTIdentifier.class); if (id != null) { + String lowerCaseId = id.getImage().toLowerCase(Locale.ROOT); List<ASTArguments> args = expression.findChildrenOfType(ASTArguments.class); if (!args.isEmpty()) { - switch (id.getImage().toLowerCase()) { + switch (lowerCaseId) { case "urlfor": case "casesafeid": case "begins": case "contains": - case "len": + case "len": case "getrecordids": case "linkto": case "sqrt": @@ -292,7 +287,7 @@ private boolean startsWithSafeResource(final ASTElExpression el) { } } else { // has no arguments - switch (id.getImage().toLowerCase()) { + switch (lowerCaseId) { case "$action": case "$page": case "$site": @@ -318,9 +313,10 @@ private boolean startsWithSlashLiteral(final ASTElExpression elExpression) { if (expression != null) { final ASTLiteral literal = expression.getFirstChildOfType(ASTLiteral.class); if (literal != null && literal.jjtGetChildIndex() == 0) { - if (literal.getImage().startsWith("'/") || literal.getImage().startsWith("\"/") - || literal.getImage().toLowerCase().startsWith("'http") - || literal.getImage().toLowerCase().startsWith("\"http")) { + String lowerCaseLiteral = literal.getImage().toLowerCase(Locale.ROOT); + if (lowerCaseLiteral.startsWith("'/") || lowerCaseLiteral.startsWith("\"/") + || lowerCaseLiteral.startsWith("'http") + || lowerCaseLiteral.startsWith("\"http")) { return true; } } @@ -338,7 +334,7 @@ private void checkApexTagsThatSupportEscaping(ASTElement node, Object data) { boolean hasPlaceholders = false; for (ASTAttribute attr : attributes) { - String name = attr.getName().toLowerCase(); + String name = attr.getName().toLowerCase(Locale.ROOT); switch (name) { case ESCAPE: case ITEM_ESCAPED: @@ -448,7 +444,7 @@ private boolean doesElContainAnyUnescapedIdentifiers(final ASTElExpression elExp private boolean containsSafeFields(final AbstractVFNode expression) { final ASTExpression ex = expression.getFirstChildOfType(ASTExpression.class); - return ex == null ? false : innerContainsSafeFields(ex); + return ex != null && innerContainsSafeFields(ex); } @@ -457,7 +453,7 @@ private boolean innerContainsSafeFields(final AbstractVFNode expression) { Node child = expression.jjtGetChild(i); if (child instanceof ASTIdentifier) { - switch (child.getImage().toLowerCase()) { + switch (child.getImage().toLowerCase(Locale.ROOT)) { case "id": case "size": case "caseNumber": @@ -488,7 +484,7 @@ private boolean doesTagSupportEscaping(final ASTElement node) { return false; } - switch (node.getName().toLowerCase()) { // vf is case insensitive + switch (node.getName().toLowerCase(Locale.ROOT)) { // vf is case insensitive case APEX_OUTPUT_TEXT: case APEX_PAGE_MESSAGE: case APEX_PAGE_MESSAGES: diff --git a/pmd-visualforce/src/main/resources/category/vf/bestpractices.xml b/pmd-visualforce/src/main/resources/category/vf/bestpractices.xml new file mode 100644 index 00000000000..58e7fd43a3d --- /dev/null +++ b/pmd-visualforce/src/main/resources/category/vf/bestpractices.xml @@ -0,0 +1,11 @@ +<?xml version="1.0"?> + +<ruleset name="Best Practices" + xmlns="http://pmd.sourceforge.net/ruleset/2.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd"> + + <description> +Rules which enforce generally accepted best practices. + </description> +</ruleset> diff --git a/pmd-visualforce/src/main/resources/category/vf/categories.properties b/pmd-visualforce/src/main/resources/category/vf/categories.properties new file mode 100644 index 00000000000..e7764fe4ff2 --- /dev/null +++ b/pmd-visualforce/src/main/resources/category/vf/categories.properties @@ -0,0 +1,17 @@ +# +# BSD-style license; for more info see http://pmd.sourceforge.net/license.html +# + +rulesets.filenames=\ + category/vf/security.xml + +# +# categories without rules +# +# category/vf/bestpractices.xml +# category/vf/codestyle.xml +# category/vf/design.xml +# category/vf/documentation.xml +# category/vf/errorprone.xml +# category/vf/multithreading.xml +# category/vf/performance.xml diff --git a/pmd-visualforce/src/main/resources/category/vf/codestyle.xml b/pmd-visualforce/src/main/resources/category/vf/codestyle.xml new file mode 100644 index 00000000000..c9a6b51d10e --- /dev/null +++ b/pmd-visualforce/src/main/resources/category/vf/codestyle.xml @@ -0,0 +1,11 @@ +<?xml version="1.0"?> + +<ruleset name="Code Style" + xmlns="http://pmd.sourceforge.net/ruleset/2.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd"> + + <description> +Rules which enforce a specific coding style. + </description> +</ruleset> diff --git a/pmd-visualforce/src/main/resources/category/vf/design.xml b/pmd-visualforce/src/main/resources/category/vf/design.xml new file mode 100644 index 00000000000..6833b8b773b --- /dev/null +++ b/pmd-visualforce/src/main/resources/category/vf/design.xml @@ -0,0 +1,11 @@ +<?xml version="1.0"?> + +<ruleset name="Design" + xmlns="http://pmd.sourceforge.net/ruleset/2.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd"> + + <description> +Rules that help you discover design issues. + </description> +</ruleset> diff --git a/pmd-visualforce/src/main/resources/category/vf/documentation.xml b/pmd-visualforce/src/main/resources/category/vf/documentation.xml new file mode 100644 index 00000000000..c049f5b6f01 --- /dev/null +++ b/pmd-visualforce/src/main/resources/category/vf/documentation.xml @@ -0,0 +1,11 @@ +<?xml version="1.0"?> + +<ruleset name="Documentation" + xmlns="http://pmd.sourceforge.net/ruleset/2.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd"> + + <description> +Rules that are related to code documentation. + </description> +</ruleset> diff --git a/pmd-visualforce/src/main/resources/category/vf/errorprone.xml b/pmd-visualforce/src/main/resources/category/vf/errorprone.xml new file mode 100644 index 00000000000..d3d13d8c34c --- /dev/null +++ b/pmd-visualforce/src/main/resources/category/vf/errorprone.xml @@ -0,0 +1,11 @@ +<?xml version="1.0"?> + +<ruleset name="Error Prone" + xmlns="http://pmd.sourceforge.net/ruleset/2.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd"> + + <description> +Rules to detect constructs that are either broken, extremely confusing or prone to runtime errors. + </description> +</ruleset> diff --git a/pmd-visualforce/src/main/resources/category/vf/multithreading.xml b/pmd-visualforce/src/main/resources/category/vf/multithreading.xml new file mode 100644 index 00000000000..88c22dfc8bc --- /dev/null +++ b/pmd-visualforce/src/main/resources/category/vf/multithreading.xml @@ -0,0 +1,11 @@ +<?xml version="1.0"?> + +<ruleset name="Multithreading" + xmlns="http://pmd.sourceforge.net/ruleset/2.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd"> + + <description> +Rules that flag issues when dealing with multiple threads of execution. + </description> +</ruleset> diff --git a/pmd-visualforce/src/main/resources/category/vf/performance.xml b/pmd-visualforce/src/main/resources/category/vf/performance.xml new file mode 100644 index 00000000000..251663aafc1 --- /dev/null +++ b/pmd-visualforce/src/main/resources/category/vf/performance.xml @@ -0,0 +1,11 @@ +<?xml version="1.0"?> + +<ruleset name="Performance" + xmlns="http://pmd.sourceforge.net/ruleset/2.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd"> + + <description> +Rules that flag suboptimal code. + </description> +</ruleset> diff --git a/pmd-visualforce/src/main/resources/category/vf/security.xml b/pmd-visualforce/src/main/resources/category/vf/security.xml new file mode 100644 index 00000000000..1d07a8d3b95 --- /dev/null +++ b/pmd-visualforce/src/main/resources/category/vf/security.xml @@ -0,0 +1,44 @@ +<?xml version="1.0"?> + +<ruleset name="Security" + xmlns="http://pmd.sourceforge.net/ruleset/2.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd"> + + <description> +Rules that flag potential security flaws. + </description> + + <rule name="VfCsrf" + since="5.6.0" + message="Avoid calling VF action upon page load" + class="net.sourceforge.pmd.lang.vf.rule.security.VfCsrfRule" + externalInfoUrl="${pmd.website.baseurl}/pmd_rules_vf_security.html#vfcsrf"> + <description> +Avoid calling VF action upon page load as the action becomes vulnerable to CSRF. + </description> + <priority>3</priority> + <example> +<![CDATA[ +<apex:page controller="AcRestActionsController" action="{!csrfInitMethod}" > +]]> + </example> + </rule> + + <rule name="VfUnescapeEl" + since="5.6.0" + message="Avoid unescaped user controlled content in EL" + class="net.sourceforge.pmd.lang.vf.rule.security.VfUnescapeElRule" + externalInfoUrl="${pmd.website.baseurl}/pmd_rules_vf_security.html#vfunescapeel"> + <description> +Avoid unescaped user controlled content in EL as it results in XSS. + </description> + <priority>3</priority> + <example> +<![CDATA[ +<apex:outputText value="Potential XSS is {! here }" escape="false" /> +]]> + </example> + </rule> + +</ruleset> diff --git a/pmd-visualforce/src/main/resources/rulesets/vf/rulesets.properties b/pmd-visualforce/src/main/resources/rulesets/vf/rulesets.properties index 996ea6ddff2..e7764fe4ff2 100644 --- a/pmd-visualforce/src/main/resources/rulesets/vf/rulesets.properties +++ b/pmd-visualforce/src/main/resources/rulesets/vf/rulesets.properties @@ -2,4 +2,16 @@ # BSD-style license; for more info see http://pmd.sourceforge.net/license.html # -rulesets.filenames=rulesets/vf/security.xml +rulesets.filenames=\ + category/vf/security.xml + +# +# categories without rules +# +# category/vf/bestpractices.xml +# category/vf/codestyle.xml +# category/vf/design.xml +# category/vf/documentation.xml +# category/vf/errorprone.xml +# category/vf/multithreading.xml +# category/vf/performance.xml diff --git a/pmd-visualforce/src/main/resources/rulesets/vf/security.xml b/pmd-visualforce/src/main/resources/rulesets/vf/security.xml index 41a673e84b8..3e266af7215 100644 --- a/pmd-visualforce/src/main/resources/rulesets/vf/security.xml +++ b/pmd-visualforce/src/main/resources/rulesets/vf/security.xml @@ -2,42 +2,13 @@ <ruleset name="Basic VF" xmlns="http://pmd.sourceforge.net/ruleset/2.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 http://pmd.sourceforge.net/ruleset_2_0_0.xsd"> + xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd"> <description> Rules concerning basic VF guidelines. </description> - <rule name="VfUnescapeEl" - since="5.6.0" - message="Avoid unescaped user controlled content in EL" - class="net.sourceforge.pmd.lang.vf.rule.security.VfUnescapeElRule" - externalInfoUrl="${pmd.website.baseurl}/pmd_rules_vf_security.html#vfunescapeel"> - <description> -Avoid unescaped user controlled content in EL as it results in XSS. - </description> - <priority>3</priority> - <example> -<![CDATA[ -<apex:outputText value="Potential XSS is {! here }" escape="false" /> -]]> - </example> - </rule> - - <rule name="VfCsrf" - since="5.6.0" - message="Avoid calling VF action upon page load" - class="net.sourceforge.pmd.lang.vf.rule.security.VfCsrfRule" - externalInfoUrl="${pmd.website.baseurl}/pmd_rules_vf_security.html#vfcsrf"> - <description> -Avoid calling VF action upon page load as the action becomes vulnerable to CSRF. - </description> - <priority>3</priority> - <example> -<![CDATA[ -<apex:page controller="AcRestActionsController" action="{!csrfInitMethod}" > -]]> - </example> - </rule> + <rule ref="category/vf/security.xml/VfCsrf" deprecated="true" /> + <rule ref="category/vf/security.xml/VfUnescapeEl" deprecated="true" /> </ruleset> diff --git a/pmd-visualforce/src/test/java/net/sourceforge/pmd/lang/vf/rule/security/SecurityRulesTest.java b/pmd-visualforce/src/test/java/net/sourceforge/pmd/lang/vf/rule/security/SecurityRulesTest.java deleted file mode 100644 index 682992e33ac..00000000000 --- a/pmd-visualforce/src/test/java/net/sourceforge/pmd/lang/vf/rule/security/SecurityRulesTest.java +++ /dev/null @@ -1,18 +0,0 @@ -/** - * BSD-style license; for more info see http://pmd.sourceforge.net/license.html - */ - -package net.sourceforge.pmd.lang.vf.rule.security; - -import net.sourceforge.pmd.testframework.SimpleAggregatorTst; - -public class SecurityRulesTest extends SimpleAggregatorTst { - - private static final String RULESET = "vf-security"; - - @Override - public void setUp() { - addRule(RULESET, "VfUnescapeEl"); - addRule(RULESET, "VfCsrf"); - } -} diff --git a/pmd-visualforce/src/test/java/net/sourceforge/pmd/lang/vf/rule/security/VfCsrfTest.java b/pmd-visualforce/src/test/java/net/sourceforge/pmd/lang/vf/rule/security/VfCsrfTest.java new file mode 100644 index 00000000000..f392fd90494 --- /dev/null +++ b/pmd-visualforce/src/test/java/net/sourceforge/pmd/lang/vf/rule/security/VfCsrfTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.vf.rule.security; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class VfCsrfTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-visualforce/src/test/java/net/sourceforge/pmd/lang/vf/rule/security/VfUnescapeElTest.java b/pmd-visualforce/src/test/java/net/sourceforge/pmd/lang/vf/rule/security/VfUnescapeElTest.java new file mode 100644 index 00000000000..40212b810ee --- /dev/null +++ b/pmd-visualforce/src/test/java/net/sourceforge/pmd/lang/vf/rule/security/VfUnescapeElTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.vf.rule.security; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class VfUnescapeElTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-visualforce/src/test/resources/net/sourceforge/pmd/lang/vf/rule/security/xml/VfUnescapeEl.xml b/pmd-visualforce/src/test/resources/net/sourceforge/pmd/lang/vf/rule/security/xml/VfUnescapeEl.xml index 9a0a38d89d8..fca8199fc35 100644 --- a/pmd-visualforce/src/test/resources/net/sourceforge/pmd/lang/vf/rule/security/xml/VfUnescapeEl.xml +++ b/pmd-visualforce/src/test/resources/net/sourceforge/pmd/lang/vf/rule/security/xml/VfUnescapeEl.xml @@ -654,5 +654,40 @@ NOW() is a safe call <source-type>vf</source-type> </test-code> + <test-code> + <description><![CDATA[ +URLENCODE is ignored as valid escape method #1100 + ]]></description> + <expected-problems>0</expected-problems> + <code><![CDATA[ +<a onclick="openTab('/apex/Download?redirectUrl={!URLENCODE(downloadURL)}', 'test');"> +]]></code> + <source-type>vf</source-type> + </test-code> + + <test-code> + <description><![CDATA[ +a onclick snippet should be escaped #1100 + ]]></description> + <expected-problems>1</expected-problems> + <code><![CDATA[ +<a onclick="openTab('/apex/Download?redirectUrl={!downloadURL}', 'test');"> +]]></code> + <source-type>vf</source-type> + </test-code> + + <test-code> + <description><![CDATA[ +ensure all encoding methods are considered valid #1100 + ]]></description> + <expected-problems>0</expected-problems> + <code><![CDATA[ +<a onclick="openTab('/apex/Download?redirectUrl={!URLENCODE(downloadURL)}', 'test');"> +<a onclick="openTab({!JSENCODE('/apex/Download?redirectUrl=' + downloadURL)}', 'test');"> +<a onclick="openTab({!JSINHTMLENCODE('/apex/Download?redirectUrl=' + downloadURL)}', 'test');"> +<a onclick="openTab({!HTMLENCODE('/apex/Download?redirectUrl=' + downloadURL)}', 'test');"> +]]></code> + <source-type>vf</source-type> + </test-code> </test-data> diff --git a/pmd-vm/pom.xml b/pmd-vm/pom.xml index d8c1778685b..278df82e3fa 100644 --- a/pmd-vm/pom.xml +++ b/pmd-vm/pom.xml @@ -7,13 +7,9 @@ <parent> <groupId>net.sourceforge.pmd</groupId> <artifactId>pmd</artifactId> - <version>6.0.0-SNAPSHOT</version> + <version>6.10.0-SNAPSHOT</version> </parent> - <properties> - <config.basedir>${basedir}/../pmd-core</config.basedir> - </properties> - <build> <resources> <resource> @@ -108,7 +104,20 @@ <groupId>net.sourceforge.pmd</groupId> <artifactId>pmd-core</artifactId> </dependency> + <dependency> + <groupId>org.apache.commons</groupId> + <artifactId>commons-lang3</artifactId> + </dependency> + <dependency> + <groupId>net.sourceforge.saxon</groupId> + <artifactId>saxon</artifactId> + </dependency> + <dependency> + <groupId>junit</groupId> + <artifactId>junit</artifactId> + <scope>test</scope> + </dependency> <dependency> <groupId>net.sourceforge.pmd</groupId> <artifactId>pmd-test</artifactId> diff --git a/pmd-vm/src/main/ant/alljavacc.xml b/pmd-vm/src/main/ant/alljavacc.xml index cab38ed72f8..8f07d54ade8 100644 --- a/pmd-vm/src/main/ant/alljavacc.xml +++ b/pmd-vm/src/main/ant/alljavacc.xml @@ -73,5 +73,55 @@ public class]]></replacevalue> <delete file="${target}/net/sourceforge/pmd/lang/vm/ast/ASTReference.java"/> <delete file="${target}/net/sourceforge/pmd/lang/vm/ast/ASTStringLiteral.java"/> <delete file="${target}/net/sourceforge/pmd/lang/vm/ast/ASTSubtractNode.java"/> + + <replace file="${target}/net/sourceforge/pmd/lang/vm/ast/Token.java"> + <replacetoken>public class Token implements java.io.Serializable</replacetoken> + <replacevalue><![CDATA[import net.sourceforge.pmd.lang.ast.GenericToken; + +public class Token implements GenericToken, java.io.Serializable]]></replacevalue> + </replace> + + <!--Add implementation methods of GenericToken--> + <replace file="${target}/net/sourceforge/pmd/lang/vm/ast/Token.java"> + <replacetoken>public Token specialToken;</replacetoken> + <replacevalue><![CDATA[public Token specialToken; + + @Override + public GenericToken getNext() { + return next; + } + + @Override + public GenericToken getPreviousComment() { + return specialToken; + } + + @Override + public String getImage() { + return image; + } + + @Override + public int getBeginLine() { + return beginLine; + } + + @Override + public int getEndLine() { + return endLine; + } + + @Override + public int getBeginColumn() { + return beginColumn; + } + + @Override + public int getEndColumn() { + return endColumn; + } + +]]></replacevalue> + </replace> </target> </project> diff --git a/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/VmHandler.java b/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/VmHandler.java index 084fd58ae93..6012bddc48c 100644 --- a/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/VmHandler.java +++ b/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/VmHandler.java @@ -12,34 +12,28 @@ import net.sourceforge.pmd.lang.VisitorStarter; import net.sourceforge.pmd.lang.XPathHandler; import net.sourceforge.pmd.lang.ast.Node; -import net.sourceforge.pmd.lang.ast.xpath.AbstractASTXPathHandler; +import net.sourceforge.pmd.lang.ast.xpath.DefaultASTXPathHandler; import net.sourceforge.pmd.lang.rule.RuleViolationFactory; import net.sourceforge.pmd.lang.vm.ast.AbstractVmNode; import net.sourceforge.pmd.lang.vm.rule.VmRuleViolationFactory; -import net.sf.saxon.sxpath.IndependentContext; - /** * Implementation of LanguageVersionHandler for the VM parser. - * + * */ public class VmHandler extends AbstractLanguageVersionHandler { @Override public XPathHandler getXPathHandler() { - return new AbstractASTXPathHandler() { - public void initialize() { - } - - public void initialize(final IndependentContext context) { - } - }; + return new DefaultASTXPathHandler(); } + @Override public RuleViolationFactory getRuleViolationFactory() { return VmRuleViolationFactory.INSTANCE; } + @Override public Parser getParser(final ParserOptions parserOptions) { return new VmParser(parserOptions); } @@ -47,6 +41,7 @@ public Parser getParser(final ParserOptions parserOptions) { @Override public VisitorStarter getDumpFacade(final Writer writer, final String prefix, final boolean recurse) { return new VisitorStarter() { + @Override public void start(final Node rootNode) { ((AbstractVmNode) rootNode).dump(prefix, recurse, writer); } diff --git a/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/VmParser.java b/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/VmParser.java index f1a2de9773a..8721e782ed3 100644 --- a/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/VmParser.java +++ b/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/VmParser.java @@ -30,15 +30,18 @@ public TokenManager createTokenManager(final Reader source) { return new VmTokenManager(source); } + @Override public boolean canParse() { return true; } + @Override public Node parse(final String fileName, final Reader source) throws ParseException { AbstractTokenManager.setFileName(fileName); return new net.sourceforge.pmd.lang.vm.ast.VmParser(new VelocityCharStream(source, 1, 1)).process(); } + @Override public Map<Integer, String> getSuppressMap() { return new HashMap<>(); // FIXME } diff --git a/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/VmTokenManager.java b/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/VmTokenManager.java index ff6721e2980..4d8f01e46c4 100644 --- a/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/VmTokenManager.java +++ b/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/VmTokenManager.java @@ -19,10 +19,12 @@ public VmTokenManager(final Reader source) { vmParserTokenManager = new VmParserTokenManager(new VelocityCharStream(source, 1, 1)); } + @Override public Object getNextToken() { return vmParserTokenManager.getNextToken(); } + @Override public void setFileName(final String fileName) { AbstractTokenManager.setFileName(fileName); } diff --git a/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/ast/ASTAddNode.java b/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/ast/ASTAddNode.java index 8fd510c5409..ac8136b9f23 100644 --- a/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/ast/ASTAddNode.java +++ b/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/ast/ASTAddNode.java @@ -17,16 +17,16 @@ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations - * under the License. + * under the License. */ /** * Handles number addition of nodes.<br> * <br> - * + * * Please look at the Parser.jjt file which is what controls the generation of * this class. - * + * * @author <a href="mailto:wglass@forio.com">Will Glass-Husain</a> * @author <a href="mailto:pero@antaramusic.de">Peter Romianowski</a> * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a> @@ -50,6 +50,7 @@ public ASTAddNode(final VmParser p, final int id) { } /** Accept the visitor. **/ + @Override public Object jjtAccept(VmParserVisitor visitor, Object data) { return visitor.visit(this, data); } diff --git a/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/ast/ASTDirective.java b/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/ast/ASTDirective.java index 548291c8211..9068449cce6 100644 --- a/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/ast/ASTDirective.java +++ b/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/ast/ASTDirective.java @@ -19,17 +19,17 @@ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations - * under the License. + * under the License. */ /** * This class is responsible for handling the pluggable directives in VTL. - * + * * For example : #foreach() - * + * * Please look at the Parser.jjt file which is what controls the generation of * this class. - * + * * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a> * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a> * @author <a href="mailto:kav@kav.dk">Kasper Nielsen</a> @@ -62,7 +62,7 @@ public Object jjtAccept(final VmParserVisitor visitor, final Object data) { * Sets the directive name. Used by the parser. This keeps us from having to * dig it out of the token stream and gives the parse the change to * override. - * + * * @param str */ public void setDirectiveName(final String str) { @@ -71,7 +71,7 @@ public void setDirectiveName(final String str) { /** * Gets the name of this directive. - * + * * @return The name of this directive. */ public String getDirectiveName() { @@ -81,6 +81,7 @@ public String getDirectiveName() { /** * @since 1.5 */ + @Deprecated @Override public String toString() { return new ToStringBuilder(this).appendSuper(super.toString()).append("directiveName", getDirectiveName()) diff --git a/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/ast/ASTDivNode.java b/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/ast/ASTDivNode.java index b2b91c8e50a..ba8cc16168f 100644 --- a/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/ast/ASTDivNode.java +++ b/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/ast/ASTDivNode.java @@ -17,16 +17,16 @@ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations - * under the License. + * under the License. */ /** * Handles number division of nodes<br> * <br> - * + * * Please look at the Parser.jjt file which is what controls the generation of * this class. - * + * * @author <a href="mailto:wglass@forio.com">Will Glass-Husain</a> * @author <a href="mailto:pero@antaramusic.de">Peter Romianowski</a> * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a> @@ -50,6 +50,7 @@ public ASTDivNode(final VmParser p, final int id) { } /** Accept the visitor. **/ + @Override public Object jjtAccept(VmParserVisitor visitor, Object data) { return visitor.visit(this, data); } diff --git a/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/ast/ASTEscape.java b/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/ast/ASTEscape.java index 0e4e54a1f3f..8c8197ac556 100644 --- a/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/ast/ASTEscape.java +++ b/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/ast/ASTEscape.java @@ -17,15 +17,15 @@ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations - * under the License. + * under the License. */ /** * This class is responsible for handling Escapes in VTL. - * + * * Please look at the Parser.jjt file which is what controls the generation of * this class. - * + * * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a> * @version $Id: ASTEscape.java 517553 2007-03-13 06:09:58Z wglass $ */ diff --git a/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/ast/ASTMathNode.java b/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/ast/ASTMathNode.java index 9de92901063..34733b9be12 100644 --- a/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/ast/ASTMathNode.java +++ b/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/ast/ASTMathNode.java @@ -17,16 +17,16 @@ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations - * under the License. + * under the License. */ /** * Helps handle math<br> * <br> - * + * * Please look at the Parser.jjt file which is what controls the generation of * this class. - * + * * @author <a href="mailto:wglass@forio.com">Will Glass-Husain</a> * @author <a href="mailto:pero@antaramusic.de">Peter Romianowski</a> * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a> @@ -45,9 +45,6 @@ public ASTMathNode(final VmParser p, final int id) { super(p, id); } - /** - * {@inheritDoc} - */ @Override public Object jjtAccept(final VmParserVisitor visitor, final Object data) { return visitor.visit(this, data); diff --git a/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/ast/ASTMethod.java b/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/ast/ASTMethod.java index e5c25b6c3f0..5dc1e3d5637 100644 --- a/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/ast/ASTMethod.java +++ b/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/ast/ASTMethod.java @@ -20,21 +20,21 @@ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations - * under the License. + * under the License. */ /** * ASTMethod.java - * + * * Method support for references : $foo.method() - * + * * NOTE : - * + * * introspection is now done at render time. - * + * * Please look at the Parser.jjt file which is what controls the generation of * this class. - * + * * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a> * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a> * @version $Id: ASTMethod.java 720228 2008-11-24 16:58:33Z nbubna $ @@ -64,7 +64,7 @@ public Object jjtAccept(final VmParserVisitor visitor, final Object data) { * Internal class used as key for method cache. Combines ASTMethod fields * with array of parameter classes. Has public access (and complete * constructor) for unit test purposes. - * + * * @since 1.5 */ public static class MethodCacheKey { diff --git a/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/ast/ASTModNode.java b/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/ast/ASTModNode.java index 6df412e48e1..9bd588007b0 100644 --- a/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/ast/ASTModNode.java +++ b/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/ast/ASTModNode.java @@ -17,16 +17,16 @@ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations - * under the License. + * under the License. */ /** * Handles modulus division<br> * <br> - * + * * Please look at the Parser.jjt file which is what controls the generation of * this class. - * + * * @author <a href="mailto:wglass@forio.com">Will Glass-Husain</a> * @author <a href="mailto:pero@antaramusic.de">Peter Romianowski</a> * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a> @@ -49,6 +49,7 @@ public ASTModNode(final VmParser p, final int id) { } /** Accept the visitor. **/ + @Override public Object jjtAccept(VmParserVisitor visitor, Object data) { return visitor.visit(this, data); } diff --git a/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/ast/ASTMulNode.java b/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/ast/ASTMulNode.java index 98dad114710..da565930a51 100644 --- a/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/ast/ASTMulNode.java +++ b/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/ast/ASTMulNode.java @@ -17,16 +17,16 @@ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations - * under the License. + * under the License. */ /** * Handles multiplication<br> * <br> - * + * * Please look at the Parser.jjt file which is what controls the generation of * this class. - * + * * @author <a href="mailto:wglass@forio.com">Will Glass-Husain</a> * @author <a href="mailto:pero@antaramusic.de">Peter Romianowski</a> * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a> @@ -50,6 +50,7 @@ public ASTMulNode(final VmParser p, final int id) { } /** Accept the visitor. **/ + @Override public Object jjtAccept(VmParserVisitor visitor, Object data) { return visitor.visit(this, data); } diff --git a/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/ast/ASTReference.java b/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/ast/ASTReference.java index 484b123e77f..d17e6b5a403 100644 --- a/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/ast/ASTReference.java +++ b/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/ast/ASTReference.java @@ -17,15 +17,15 @@ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations - * under the License. + * under the License. */ /** * This class is responsible for handling the references in VTL ($foo). - * + * * Please look at the Parser.jjt file which is what controls the generation of * this class. - * + * * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a> * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a> * @author <a href="mailto:Christoph.Reck@dlr.de">Christoph Reck</a> @@ -73,7 +73,7 @@ public Object jjtAccept(final VmParserVisitor visitor, final Object data) { /** * Returns the 'root string', the reference key - * + * * @return the root string. */ public String getRootString() { @@ -85,9 +85,9 @@ public String getRootString() { * Used now in the VM system to override a reference in a VM tree with the * literal of the calling arg to make it work nicely when calling arg is * null. It seems a bit much, but does keep things consistant. - * + * * Note, you can only set the literal once... - * + * * @param literal * String to render to when null */ @@ -104,7 +104,7 @@ public void setLiteral(final String literal) { /** * Override of the SimpleNode method literal() Returns the literal * representation of the node. Should be something like $<token>. - * + * * @return A literal string. */ @Override diff --git a/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/ast/ASTStringLiteral.java b/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/ast/ASTStringLiteral.java index e82d68f4dd8..16aea343dce 100644 --- a/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/ast/ASTStringLiteral.java +++ b/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/ast/ASTStringLiteral.java @@ -10,9 +10,9 @@ * licenses this file to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the @@ -22,7 +22,7 @@ /** * ASTStringLiteral support. Will interpolate! - * + * * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a> * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a> * @version $Id: ASTStringLiteral.java 705297 2008-10-16 17:59:24Z nbubna $ @@ -112,7 +112,7 @@ public Object jjtAccept(final VmParserVisitor visitor, final Object data) { /** * Check to see if this is an interpolated string. - * + * * @return true if this is constant (not an interpolated string) * @since 1.6 */ diff --git a/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/ast/ASTSubtractNode.java b/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/ast/ASTSubtractNode.java index a93f7c56767..88e694669d7 100644 --- a/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/ast/ASTSubtractNode.java +++ b/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/ast/ASTSubtractNode.java @@ -17,16 +17,16 @@ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations - * under the License. + * under the License. */ /** * Handles subtraction of nodes (in #set() )<br> * <br> - * + * * Please look at the Parser.jjt file which is what controls the generation of * this class. - * + * * @author <a href="mailto:wglass@forio.com">Will Glass-Husain</a> * @author <a href="mailto:pero@antaramusic.de">Peter Romianowski</a> * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a> @@ -50,6 +50,7 @@ public ASTSubtractNode(final VmParser p, final int id) { } /** Accept the visitor. **/ + @Override public Object jjtAccept(VmParserVisitor visitor, Object data) { return visitor.visit(this, data); } diff --git a/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/ast/AbstractVmNode.java b/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/ast/AbstractVmNode.java index f877d2a973f..a640db2813c 100644 --- a/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/ast/AbstractVmNode.java +++ b/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/ast/AbstractVmNode.java @@ -17,7 +17,7 @@ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations - * under the License. + * under the License. */ import java.io.PrintWriter; @@ -108,10 +108,12 @@ public Token getLastToken() { return last; } + @Override public Object jjtAccept(final VmParserVisitor visitor, final Object data) { return visitor.visit(this, data); } + @Override public Object childrenAccept(final VmParserVisitor visitor, final Object data) { if (children != null) { for (int i = 0; i < children.length; ++i) { @@ -121,6 +123,12 @@ public Object childrenAccept(final VmParserVisitor visitor, final Object data) { return data; } + + @Override + public String getXPathNodeName() { + return VmParserTreeConstants.jjtNodeName[id]; + } + /* * You can override these two methods in subclasses of SimpleNode to * customize the way the node appears when the tree is dumped. If your @@ -128,9 +136,7 @@ public Object childrenAccept(final VmParserVisitor visitor, final Object data) { * otherwise overriding toString() is probably all you need to do. */ - public String toString() { - return VmParserTreeConstants.jjtNodeName[id]; - } + /** * @param prefix @@ -143,7 +149,7 @@ public String toString(final String prefix) { /** * Override this method if you want to customize how the node dumps out its * children. - * + * * @param prefix */ public void dump(final String prefix, final boolean recurse, final Writer writer) { diff --git a/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/ast/MacroParseException.java b/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/ast/MacroParseException.java index 6d726b578ba..16237c1aba9 100644 --- a/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/ast/MacroParseException.java +++ b/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/ast/MacroParseException.java @@ -17,16 +17,16 @@ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations - * under the License. + * under the License. */ import net.sourceforge.pmd.lang.vm.util.LogUtil; /** * Exception to indicate problem happened while constructing #macro() - * + * * For internal use in parser - not to be passed to app level - * + * * @author <a href="mailto:geirm@apache.org">Geir Magnusson Jr.</a> * @author <a href="hps@intermeta.de">Henning P. Schmiedehausen</a> * @version $Id: MacroParseException.java 735709 2009-01-19 14:30:03Z byron $ @@ -52,7 +52,7 @@ public MacroParseException(final String msg, final String templateName, final To /** * returns the Template name where this exception occured. - * + * * @return The Template name where this exception occured. * @since 1.5 */ @@ -62,7 +62,7 @@ public String getTemplateName() { /** * returns the line number where this exception occured. - * + * * @return The line number where this exception occured. * @since 1.5 */ @@ -78,7 +78,7 @@ public int getLineNumber() { /** * returns the column number where this exception occured. - * + * * @return The column number where this exception occured. * @since 1.5 */ @@ -100,7 +100,7 @@ public int getColumnNumber() { * catch it (it gets thrown from the parser), then this method is called * during the printing of the final stack trace, and hence the correct error * message gets displayed. - * + * * @return the current message. * @since 1.5 */ diff --git a/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/ast/NodeUtils.java b/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/ast/NodeUtils.java index 95e5f1e8ecd..270d1395c06 100644 --- a/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/ast/NodeUtils.java +++ b/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/ast/NodeUtils.java @@ -19,17 +19,17 @@ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations - * under the License. + * under the License. */ /** * Utilities for dealing with the AST node structure. - * + * * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a> * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a> * @version $Id: NodeUtils.java 687386 2008-08-20 16:57:07Z nbubna $ */ -public class NodeUtils { +public final class NodeUtils { private NodeUtils() { } /** @@ -37,7 +37,7 @@ private NodeUtils() { } * Special tokens do not participate in parsing but can still trigger * certain lexical actions. In some cases you may want to retrieve these * special tokens, this is simply a way to extract them. - * + * * @param t * the Token * @return StrBuilder with the special tokens. @@ -63,7 +63,7 @@ private static StrBuilder getSpecialText(final Token t) { /* * more dreaded MORE hack :) - * + * * looking for ("\\")*"$" sequences */ @@ -109,7 +109,7 @@ private static StrBuilder getSpecialText(final Token t) { /** * complete node literal - * + * * @param t * @return A node literal. */ diff --git a/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/ast/TemplateParseException.java b/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/ast/TemplateParseException.java index 11c874ea68d..3f4122123f5 100644 --- a/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/ast/TemplateParseException.java +++ b/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/ast/TemplateParseException.java @@ -17,16 +17,16 @@ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations - * under the License. + * under the License. */ import net.sourceforge.pmd.lang.vm.util.LogUtil; /** * This is an extension of the ParseException, which also takes a template name. - * + * * <p>see also the original <code>org.apache.velocity.runtime.parser.ParseException</code></p> - * + * * @author <a href="hps@intermeta.de">Henning P. Schmiedehausen</a> * @version $Id: TemplateParseException.java 703544 2008-10-10 18:15:53Z nbubna * $ @@ -46,7 +46,7 @@ public class TemplateParseException extends ParseException { /** * This constructor is used to add a template name to info cribbed from a * ParseException generated in the parser. - * + * * @param currentTokenVal * @param expectedTokenSequencesVal * @param tokenImageVal @@ -68,7 +68,7 @@ public TemplateParseException(final Token currentTokenVal, final int[][] expecte * This constructor calls its super class with the empty string to force the * "toString" method of parent class "Throwable" to print the error message * in the form: ParseException: <result of getMessage> - * + * * @param currentTokenVal * @param expectedTokenSequencesVal * @param tokenImageVal @@ -95,7 +95,7 @@ public TemplateParseException() { /** * Creates a new TemplateParseException object. - * + * * @param message * TODO: DOCUMENT ME! */ @@ -106,7 +106,7 @@ public TemplateParseException(final String message) { /** * returns the Template name where this exception occured. - * + * * @return The Template name where this exception occured. */ public String getTemplateName() { @@ -115,7 +115,7 @@ public String getTemplateName() { /** * returns the line number where this exception occured. - * + * * @return The line number where this exception occured. */ public int getLineNumber() { @@ -128,7 +128,7 @@ public int getLineNumber() { /** * returns the column number where this exception occured. - * + * * @return The column number where this exception occured. */ public int getColumnNumber() { @@ -147,7 +147,7 @@ public int getColumnNumber() { * catch it (it gets thrown from the parser), then this method is called * during the printing of the final stack trace, and hence the correct error * message gets displayed. - * + * * @return The error message. */ @Override diff --git a/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/ast/TokenMgrError.java b/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/ast/TokenMgrError.java index fc7b2a14a3e..546b054b603 100644 --- a/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/ast/TokenMgrError.java +++ b/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/ast/TokenMgrError.java @@ -9,7 +9,7 @@ public class TokenMgrError extends RuntimeException { */ /** - * + * */ private static final long serialVersionUID = 1L; @@ -45,6 +45,7 @@ public class TokenMgrError extends RuntimeException { */ public TokenMgrError() { + // default constructor } public TokenMgrError(final String message, final int reason) { @@ -117,10 +118,10 @@ protected static final String addEscapes(final String str) { */ protected static String lexicalError(final boolean eofSeen, final int lexState, final int errorLine, final int errorColumn, final String errorAfter, final char curChar) { - return ("Lexical error at line " + errorLine + ", column " + errorColumn + ". Encountered: " + return "Lexical error at line " + errorLine + ", column " + errorColumn + ". Encountered: " + (eofSeen ? "<EOF> " : ("\"" + addEscapes(String.valueOf(curChar)) + "\"") + " (" + (int) curChar + "), ") - + "after : \"" + addEscapes(errorAfter) + "\""); + + "after : \"" + addEscapes(errorAfter) + "\""; } } diff --git a/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/directive/Block.java b/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/directive/Block.java index de6bf9e449a..b10b9dbe755 100644 --- a/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/directive/Block.java +++ b/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/directive/Block.java @@ -17,7 +17,7 @@ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations - * under the License. + * under the License. */ /** @@ -36,6 +36,7 @@ public abstract class Block extends Directive { /** * Return type of this directive. */ + @Override public int getType() { return BLOCK; } diff --git a/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/directive/BlockMacro.java b/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/directive/BlockMacro.java index 4eb0ad778ea..e1b80173afc 100644 --- a/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/directive/BlockMacro.java +++ b/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/directive/BlockMacro.java @@ -17,7 +17,7 @@ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations - * under the License. + * under the License. */ /** @@ -28,10 +28,10 @@ * as a "decorator". Body AST can contain any valid Velocity syntax. * * An example: - * + * * <pre> * #set($foobar = "yeah!") - * + * * #macro(strong $txt) * <strong>$bodyContent</strong> $txt * #end @@ -40,13 +40,13 @@ * <u>This text is underlined and bold</u> * #end * </pre> - * + * * Will print: - * + * * <pre> * <strong><u>This text is underlined and bold<u></strong> yeah! * </pre> - * + * * bodyContent reference name is configurable (see velocity.properties). * * @author <a href="mailto:wyla@removethis.sci.fi">Jarkko Viinamaki</a> @@ -60,6 +60,7 @@ public BlockMacro(String name) { this.name = name; } + @Override public String getName() { return key; } @@ -68,6 +69,7 @@ public String getName() { * Override to use the macro name, since it is within an #@myMacro() ... * #end block that the scope in question would be used. */ + @Override public String getScopeName() { return name; } diff --git a/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/directive/Break.java b/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/directive/Break.java index 8adcda445b4..ee3bf5b6297 100644 --- a/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/directive/Break.java +++ b/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/directive/Break.java @@ -17,7 +17,7 @@ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations - * under the License. + * under the License. */ /** @@ -31,18 +31,20 @@ public class Break extends Directive { /** * Return name of this directive. - * + * * @return The name of this directive. */ + @Override public String getName() { return "break"; } /** * Return type of this directive. - * + * * @return The type of this directive. */ + @Override public int getType() { return LINE; } @@ -51,6 +53,7 @@ public int getType() { * Since there is no processing of content, there is never a need for an * internal scope. */ + @Override public boolean isScopeProvided() { return false; } diff --git a/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/directive/Define.java b/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/directive/Define.java index d4f54d95b5a..08683debf58 100644 --- a/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/directive/Define.java +++ b/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/directive/Define.java @@ -17,7 +17,7 @@ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations - * under the License. + * under the License. */ /** @@ -32,6 +32,7 @@ public class Define extends Block { /** * Return name of this directive. */ + @Override public String getName() { return "define"; } diff --git a/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/directive/Directive.java b/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/directive/Directive.java index 49f9892e933..0baaee2eaee 100644 --- a/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/directive/Directive.java +++ b/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/directive/Directive.java @@ -17,7 +17,7 @@ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations - * under the License. + * under the License. */ /** @@ -41,21 +41,21 @@ public abstract class Directive implements Cloneable { /** * Return the name of this directive. - * + * * @return The name of this directive. */ public abstract String getName(); /** * Get the directive type BLOCK/LINE. - * + * * @return The directive type BLOCK/LINE. */ public abstract int getType(); /** * Allows the template location to be set. - * + * * @param line * @param column */ @@ -66,7 +66,7 @@ public void setLocation(int line, int column) { /** * Allows the template location to be set. - * + * * @param line * @param column */ @@ -77,7 +77,7 @@ public void setLocation(int line, int column, String templateName) { /** * for log msg purposes - * + * * @return The current line for log msg purposes. */ public int getLine() { @@ -86,7 +86,7 @@ public int getLine() { /** * for log msg purposes - * + * * @return The current column for log msg purposes. */ public int getColumn() { diff --git a/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/directive/Evaluate.java b/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/directive/Evaluate.java index 2b6bba2a77a..ab48ac23922 100644 --- a/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/directive/Evaluate.java +++ b/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/directive/Evaluate.java @@ -17,7 +17,7 @@ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations - * under the License. + * under the License. */ /** @@ -31,18 +31,20 @@ public class Evaluate extends Directive { /** * Return name of this directive. - * + * * @return The name of this directive. */ + @Override public String getName() { return "evaluate"; } /** * Return type of this directive. - * + * * @return The type of this directive. */ + @Override public int getType() { return LINE; } diff --git a/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/directive/Foreach.java b/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/directive/Foreach.java index 7e60b3257b6..715f17f5f03 100644 --- a/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/directive/Foreach.java +++ b/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/directive/Foreach.java @@ -17,7 +17,7 @@ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations - * under the License. + * under the License. */ /** @@ -32,18 +32,20 @@ public class Foreach extends Directive { /** * Return name of this directive. - * + * * @return The name of this directive. */ + @Override public String getName() { return "foreach"; } /** * Return type of this directive. - * + * * @return The type of this directive. */ + @Override public int getType() { return BLOCK; } diff --git a/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/directive/Include.java b/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/directive/Include.java index ca65a98c9fc..fa6e04fe361 100644 --- a/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/directive/Include.java +++ b/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/directive/Include.java @@ -17,7 +17,7 @@ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations - * under the License. + * under the License. */ /** @@ -58,18 +58,20 @@ public class Include extends InputBase { /** * Return name of this directive. - * + * * @return The name of this directive. */ + @Override public String getName() { return "include"; } /** * Return type of this directive. - * + * * @return The type of this directive. */ + @Override public int getType() { return LINE; } @@ -78,6 +80,7 @@ public int getType() { * Since there is no processing of content, there is never a need for an * internal scope. */ + @Override public boolean isScopeProvided() { return false; } diff --git a/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/directive/InputBase.java b/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/directive/InputBase.java index a6ee202fba3..5c47a5fe369 100644 --- a/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/directive/InputBase.java +++ b/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/directive/InputBase.java @@ -17,7 +17,7 @@ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations - * under the License. + * under the License. */ /** @@ -30,8 +30,9 @@ public abstract class InputBase extends Directive { /** * Return name of this directive. - * + * * @return The name of this directive. */ + @Override public abstract String getName(); } diff --git a/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/directive/Literal.java b/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/directive/Literal.java index 1683a5db5e0..0d81c6c919c 100644 --- a/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/directive/Literal.java +++ b/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/directive/Literal.java @@ -17,7 +17,7 @@ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations - * under the License. + * under the License. */ /** @@ -30,23 +30,26 @@ * @version $Id: Literal.java 746438 2009-02-21 05:41:24Z nbubna $ * @deprecated Use the #[[unparsed content]]# syntax instead. */ +@Deprecated public class Literal extends Directive { String literalText; /** * Return name of this directive. - * + * * @return The name of this directive. */ + @Override public String getName() { return "literal"; } /** * Return type of this directive. - * + * * @return The type of this directive. */ + @Override public int getType() { return BLOCK; } @@ -55,6 +58,7 @@ public int getType() { * Since there is no processing of content, there is never a need for an * internal scope. */ + @Override public boolean isScopeProvided() { return false; } diff --git a/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/directive/Macro.java b/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/directive/Macro.java index ff736adea53..1c15e2c6a85 100644 --- a/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/directive/Macro.java +++ b/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/directive/Macro.java @@ -17,7 +17,7 @@ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations - * under the License. + * under the License. */ /** @@ -37,18 +37,20 @@ public class Macro extends Directive { /** * Return name of this directive. - * + * * @return The name of this directive. */ + @Override public String getName() { return "macro"; } /** * Return type of this directive. - * + * * @return The type of this directive. */ + @Override public int getType() { return BLOCK; } @@ -57,6 +59,7 @@ public int getType() { * Since this class does no processing of content, there is never a need for * an internal scope. */ + @Override public boolean isScopeProvided() { return false; } diff --git a/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/directive/Parse.java b/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/directive/Parse.java index 780a0ee2d97..9aa23dfa052 100644 --- a/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/directive/Parse.java +++ b/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/directive/Parse.java @@ -17,7 +17,7 @@ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations - * under the License. + * under the License. */ /** @@ -46,9 +46,10 @@ public class Parse extends InputBase { /** * Return name of this directive. - * + * * @return The name of this directive. */ + @Override public String getName() { return "parse"; } @@ -57,15 +58,17 @@ public String getName() { * Overrides the default to use "template", so that all templates can use * the same scope reference, whether rendered via #parse or direct merge. */ + @Override public String getScopeName() { return "template"; } /** * Return type of this directive. - * + * * @return The type of this directive. */ + @Override public int getType() { return LINE; } diff --git a/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/directive/RuntimeMacro.java b/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/directive/RuntimeMacro.java index f22a5f9ee07..2b2b513cb66 100644 --- a/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/directive/RuntimeMacro.java +++ b/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/directive/RuntimeMacro.java @@ -26,7 +26,7 @@ * actually defined). At render time we check whether there is a implementation * for the macro call. If an implementation cannot be found the literal text is * rendered. - * + * * @since 1.6 */ public class RuntimeMacro extends Directive { @@ -55,6 +55,7 @@ public RuntimeMacro(String macroName) { * * @return The name of this Velocimacro. */ + @Override public String getName() { return macroName; } @@ -65,6 +66,7 @@ public String getName() { * call. The macro name will instead be used as the scope name when defining * the body of a BlockMacro. */ + @Override public String getScopeName() { return "macro"; } @@ -74,6 +76,7 @@ public String getScopeName() { * * @return The type of this directive. */ + @Override public int getType() { return LINE; } diff --git a/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/directive/Stop.java b/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/directive/Stop.java index b12b1662464..83a92cb7313 100644 --- a/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/directive/Stop.java +++ b/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/directive/Stop.java @@ -17,7 +17,7 @@ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations - * under the License. + * under the License. */ /** @@ -29,18 +29,20 @@ public class Stop extends Directive { /** * Return name of this directive. - * + * * @return The name of this directive. */ + @Override public String getName() { return "stop"; } /** * Return type of this directive. - * + * * @return The type of this directive. */ + @Override public int getType() { return LINE; } @@ -49,6 +51,7 @@ public int getType() { * Since there is no processing of content, there is never a need for an * internal scope. */ + @Override public boolean isScopeProvided() { return false; } diff --git a/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/directive/VelocimacroProxy.java b/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/directive/VelocimacroProxy.java index 3380b4195c1..5f1f79f2a92 100644 --- a/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/directive/VelocimacroProxy.java +++ b/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/directive/VelocimacroProxy.java @@ -17,7 +17,7 @@ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations - * under the License. + * under the License. */ /** @@ -34,25 +34,27 @@ public class VelocimacroProxy extends Directive { /** * Return name of this Velocimacro. - * + * * @return The name of this Velocimacro. */ + @Override public String getName() { return macroName; } /** * Velocimacros are always LINE type directives. - * + * * @return The type of this directive. */ + @Override public int getType() { return LINE; } /** * sets the directive name of this VM - * + * * @param name */ public void setName(String name) { @@ -61,7 +63,7 @@ public void setName(String name) { /** * sets the array of arguments specified in the macro definition - * + * * @param arr */ public void setArgArray(String[] arr) { @@ -74,7 +76,7 @@ public void setArgArray(String[] arr) { /** * returns the number of ars needed for this VM - * + * * @return The number of ars needed for this VM */ public int getNumArgs() { diff --git a/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/rule/AbstractStatisticalVmRule.java b/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/rule/AbstractStatisticalVmRule.java index 934f39dcb93..dc78c1a85e6 100644 --- a/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/rule/AbstractStatisticalVmRule.java +++ b/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/rule/AbstractStatisticalVmRule.java @@ -23,7 +23,7 @@ public void addDataPoint(final DataPoint point) { @Override public Object[] getViolationParameters(final DataPoint point) { - return null; + return new Object[0]; } @Override diff --git a/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/rule/basic/AvoidDeeplyNestedIfStmtsRule.java b/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/rule/basic/AvoidDeeplyNestedIfStmtsRule.java deleted file mode 100644 index 228d9ab9e85..00000000000 --- a/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/rule/basic/AvoidDeeplyNestedIfStmtsRule.java +++ /dev/null @@ -1,50 +0,0 @@ -/** - * BSD-style license; for more info see http://pmd.sourceforge.net/license.html - */ - -package net.sourceforge.pmd.lang.vm.rule.basic; - -import net.sourceforge.pmd.lang.vm.ast.ASTElseIfStatement; -import net.sourceforge.pmd.lang.vm.ast.ASTIfStatement; -import net.sourceforge.pmd.lang.vm.ast.ASTprocess; -import net.sourceforge.pmd.lang.vm.ast.AbstractVmNode; -import net.sourceforge.pmd.lang.vm.rule.AbstractVmRule; -import net.sourceforge.pmd.properties.IntegerProperty; - -public class AvoidDeeplyNestedIfStmtsRule extends AbstractVmRule { - - private int depth; - private int depthLimit; - - private static final IntegerProperty PROBLEM_DEPTH_DESCRIPTOR = new IntegerProperty("problemDepth", - "The if statement depth reporting threshold", 1, 25, 3, 1.0f); - - public AvoidDeeplyNestedIfStmtsRule() { - definePropertyDescriptor(PROBLEM_DEPTH_DESCRIPTOR); - } - - public Object visit(ASTprocess node, Object data) { - depth = 0; - depthLimit = getProperty(PROBLEM_DEPTH_DESCRIPTOR); - return super.visit(node, data); - } - - public Object visit(ASTIfStatement node, Object data) { - return handleIf(node, data); - } - - public Object visit(ASTElseIfStatement node, Object data) { - return handleIf(node, data); - } - - private Object handleIf(AbstractVmNode node, Object data) { - depth++; - super.visit(node, data); - if (depth == depthLimit) { - addViolation(data, node); - } - depth--; - return data; - } - -} diff --git a/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/rule/basic/AvoidReassigningParametersRule.java b/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/rule/basic/AvoidReassigningParametersRule.java deleted file mode 100644 index 6cb48e04067..00000000000 --- a/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/rule/basic/AvoidReassigningParametersRule.java +++ /dev/null @@ -1,36 +0,0 @@ -/** - * BSD-style license; for more info see http://pmd.sourceforge.net/license.html - */ - -package net.sourceforge.pmd.lang.vm.rule.basic; - -import java.util.HashSet; -import java.util.List; -import java.util.Set; - -import net.sourceforge.pmd.lang.vm.ast.ASTDirective; -import net.sourceforge.pmd.lang.vm.ast.ASTReference; -import net.sourceforge.pmd.lang.vm.ast.ASTSetDirective; -import net.sourceforge.pmd.lang.vm.rule.AbstractVmRule; - -public class AvoidReassigningParametersRule extends AbstractVmRule { - - @Override - public Object visit(final ASTDirective node, final Object data) { - if ("macro".equals(node.getDirectiveName())) { - final Set<String> paramNames = new HashSet<>(); - final List<ASTReference> params = node.findChildrenOfType(ASTReference.class); - for (final ASTReference param : params) { - paramNames.add(param.getFirstToken().toString()); - } - final List<ASTSetDirective> assignments = node.findDescendantsOfType(ASTSetDirective.class); - for (final ASTSetDirective assignment : assignments) { - final ASTReference ref = assignment.getFirstChildOfType(ASTReference.class); - if (ref != null && paramNames.contains(ref.getFirstToken().toString())) { - addViolation(data, node, ref.getFirstToken().toString()); - } - } - } - return super.visit(node, data); - } -} diff --git a/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/rule/bestpractices/AvoidReassigningParametersRule.java b/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/rule/bestpractices/AvoidReassigningParametersRule.java new file mode 100644 index 00000000000..ee71e7b2878 --- /dev/null +++ b/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/rule/bestpractices/AvoidReassigningParametersRule.java @@ -0,0 +1,36 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.vm.rule.bestpractices; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import net.sourceforge.pmd.lang.vm.ast.ASTDirective; +import net.sourceforge.pmd.lang.vm.ast.ASTReference; +import net.sourceforge.pmd.lang.vm.ast.ASTSetDirective; +import net.sourceforge.pmd.lang.vm.rule.AbstractVmRule; + +public class AvoidReassigningParametersRule extends AbstractVmRule { + + @Override + public Object visit(final ASTDirective node, final Object data) { + if ("macro".equals(node.getDirectiveName())) { + final Set<String> paramNames = new HashSet<>(); + final List<ASTReference> params = node.findChildrenOfType(ASTReference.class); + for (final ASTReference param : params) { + paramNames.add(param.getFirstToken().toString()); + } + final List<ASTSetDirective> assignments = node.findDescendantsOfType(ASTSetDirective.class); + for (final ASTSetDirective assignment : assignments) { + final ASTReference ref = assignment.getFirstChildOfType(ASTReference.class); + if (ref != null && paramNames.contains(ref.getFirstToken().toString())) { + addViolation(data, node, ref.getFirstToken().toString()); + } + } + } + return super.visit(node, data); + } +} diff --git a/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/rule/basic/UnusedMacroParameterRule.java b/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/rule/bestpractices/UnusedMacroParameterRule.java similarity index 97% rename from pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/rule/basic/UnusedMacroParameterRule.java rename to pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/rule/bestpractices/UnusedMacroParameterRule.java index 690e9b63f16..cbfff22317f 100644 --- a/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/rule/basic/UnusedMacroParameterRule.java +++ b/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/rule/bestpractices/UnusedMacroParameterRule.java @@ -2,7 +2,7 @@ * BSD-style license; for more info see http://pmd.sourceforge.net/license.html */ -package net.sourceforge.pmd.lang.vm.rule.basic; +package net.sourceforge.pmd.lang.vm.rule.bestpractices; import java.util.HashSet; import java.util.List; diff --git a/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/rule/design/AvoidDeeplyNestedIfStmtsRule.java b/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/rule/design/AvoidDeeplyNestedIfStmtsRule.java new file mode 100644 index 00000000000..ceff76e81e5 --- /dev/null +++ b/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/rule/design/AvoidDeeplyNestedIfStmtsRule.java @@ -0,0 +1,55 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.vm.rule.design; + +import net.sourceforge.pmd.lang.vm.ast.ASTElseIfStatement; +import net.sourceforge.pmd.lang.vm.ast.ASTIfStatement; +import net.sourceforge.pmd.lang.vm.ast.ASTprocess; +import net.sourceforge.pmd.lang.vm.ast.AbstractVmNode; +import net.sourceforge.pmd.lang.vm.rule.AbstractVmRule; +import net.sourceforge.pmd.properties.IntegerProperty; + +public class AvoidDeeplyNestedIfStmtsRule extends AbstractVmRule { + + private int depth; + private int depthLimit; + + private static final IntegerProperty PROBLEM_DEPTH_DESCRIPTOR + = IntegerProperty.named("problemDepth") + .desc("The if statement depth reporting threshold") + .range(1, 25).defaultValue(3).uiOrder(1.0f).build(); + + public AvoidDeeplyNestedIfStmtsRule() { + definePropertyDescriptor(PROBLEM_DEPTH_DESCRIPTOR); + } + + @Override + public Object visit(ASTprocess node, Object data) { + depth = 0; + depthLimit = getProperty(PROBLEM_DEPTH_DESCRIPTOR); + return super.visit(node, data); + } + + @Override + public Object visit(ASTIfStatement node, Object data) { + return handleIf(node, data); + } + + @Override + public Object visit(ASTElseIfStatement node, Object data) { + return handleIf(node, data); + } + + private Object handleIf(AbstractVmNode node, Object data) { + depth++; + super.visit(node, data); + if (depth == depthLimit) { + addViolation(data, node); + } + depth--; + return data; + } + +} diff --git a/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/rule/basic/CollapsibleIfStatementsRule.java b/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/rule/design/CollapsibleIfStatementsRule.java similarity index 98% rename from pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/rule/basic/CollapsibleIfStatementsRule.java rename to pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/rule/design/CollapsibleIfStatementsRule.java index 0eb3556f292..5ba53206b1a 100644 --- a/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/rule/basic/CollapsibleIfStatementsRule.java +++ b/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/rule/design/CollapsibleIfStatementsRule.java @@ -2,7 +2,7 @@ * BSD-style license; for more info see http://pmd.sourceforge.net/license.html */ -package net.sourceforge.pmd.lang.vm.rule.basic; +package net.sourceforge.pmd.lang.vm.rule.design; import org.apache.commons.lang3.StringUtils; diff --git a/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/rule/basic/ExcessiveTemplateLengthRule.java b/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/rule/design/ExcessiveTemplateLengthRule.java similarity index 93% rename from pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/rule/basic/ExcessiveTemplateLengthRule.java rename to pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/rule/design/ExcessiveTemplateLengthRule.java index a8b79be490c..263326fdb03 100644 --- a/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/rule/basic/ExcessiveTemplateLengthRule.java +++ b/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/rule/design/ExcessiveTemplateLengthRule.java @@ -2,7 +2,7 @@ * BSD-style license; for more info see http://pmd.sourceforge.net/license.html */ -package net.sourceforge.pmd.lang.vm.rule.basic; +package net.sourceforge.pmd.lang.vm.rule.design; import net.sourceforge.pmd.lang.vm.ast.ASTprocess; import net.sourceforge.pmd.lang.vm.rule.AbstractStatisticalVmRule; diff --git a/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/rule/basic/NoInlineJavaScriptRule.java b/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/rule/design/NoInlineJavaScriptRule.java similarity index 94% rename from pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/rule/basic/NoInlineJavaScriptRule.java rename to pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/rule/design/NoInlineJavaScriptRule.java index 6ef5b2a2f10..b52831e8c49 100644 --- a/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/rule/basic/NoInlineJavaScriptRule.java +++ b/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/rule/design/NoInlineJavaScriptRule.java @@ -2,7 +2,7 @@ * BSD-style license; for more info see http://pmd.sourceforge.net/license.html */ -package net.sourceforge.pmd.lang.vm.rule.basic; +package net.sourceforge.pmd.lang.vm.rule.design; import java.util.regex.Matcher; import java.util.regex.Pattern; diff --git a/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/rule/basic/EmptyForeachStmtRule.java b/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/rule/errorprone/EmptyForeachStmtRule.java similarity index 95% rename from pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/rule/basic/EmptyForeachStmtRule.java rename to pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/rule/errorprone/EmptyForeachStmtRule.java index f791e9700ee..176cff4f584 100644 --- a/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/rule/basic/EmptyForeachStmtRule.java +++ b/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/rule/errorprone/EmptyForeachStmtRule.java @@ -2,7 +2,7 @@ * BSD-style license; for more info see http://pmd.sourceforge.net/license.html */ -package net.sourceforge.pmd.lang.vm.rule.basic; +package net.sourceforge.pmd.lang.vm.rule.errorprone; import org.apache.commons.lang3.StringUtils; diff --git a/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/rule/basic/EmptyIfStmtRule.java b/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/rule/errorprone/EmptyIfStmtRule.java similarity index 96% rename from pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/rule/basic/EmptyIfStmtRule.java rename to pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/rule/errorprone/EmptyIfStmtRule.java index 959bea42aec..e4beb947195 100644 --- a/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/rule/basic/EmptyIfStmtRule.java +++ b/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/rule/errorprone/EmptyIfStmtRule.java @@ -2,7 +2,7 @@ * BSD-style license; for more info see http://pmd.sourceforge.net/license.html */ -package net.sourceforge.pmd.lang.vm.rule.basic; +package net.sourceforge.pmd.lang.vm.rule.errorprone; import org.apache.commons.lang3.StringUtils; diff --git a/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/util/DirectiveMapper.java b/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/util/DirectiveMapper.java index ed7eb9caaa9..6b72a0d9a8f 100644 --- a/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/util/DirectiveMapper.java +++ b/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/util/DirectiveMapper.java @@ -20,7 +20,7 @@ import net.sourceforge.pmd.lang.vm.directive.Parse; import net.sourceforge.pmd.lang.vm.directive.Stop; -public class DirectiveMapper { +public final class DirectiveMapper { private DirectiveMapper() { } private static final Map<String, Directive> DIRECTIVE_MAP = new HashMap<>(); diff --git a/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/util/LogUtil.java b/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/util/LogUtil.java index b925c5b1fa0..93a5a38da53 100644 --- a/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/util/LogUtil.java +++ b/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/util/LogUtil.java @@ -32,7 +32,7 @@ * @version $Id: Log.java 724825 2008-12-09 18:56:06Z nbubna $ * @since 1.5 */ -public class LogUtil { +public final class LogUtil { private LogUtil() { } /** @@ -40,7 +40,7 @@ private LogUtil() { } * column of the given Directive. We use this routine to provide a cosistent * format for displaying file errors. */ - public static final String formatFileString(final Directive directive) { + public static String formatFileString(final Directive directive) { return formatFileString(directive.getTemplateName(), directive.getLine(), directive.getColumn()); } @@ -49,7 +49,7 @@ public static final String formatFileString(final Directive directive) { * column of the given Node. We use this routine to provide a cosistent * format for displaying file errors. */ - public static final String formatFileString(final AbstractVmNode node) { + public static String formatFileString(final AbstractVmNode node) { return formatFileString(node.getTemplateName(), node.getLine(), node.getColumn()); } @@ -65,7 +65,7 @@ public static final String formatFileString(final AbstractVmNode node) { * @param colnum * Column number withing the file at linenum */ - public static final String formatFileString(String template, final int linenum, final int colnum) { + public static String formatFileString(String template, final int linenum, final int colnum) { if (template == null || "".equals(template)) { template = "<unknown template>"; } diff --git a/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/util/VelocityCharStream.java b/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/util/VelocityCharStream.java index 7e21bda02bd..78a3e91fa38 100644 --- a/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/util/VelocityCharStream.java +++ b/pmd-vm/src/main/java/net/sourceforge/pmd/lang/vm/util/VelocityCharStream.java @@ -113,37 +113,33 @@ private void expandBuff(boolean wrapAround) { int[] newbufline = new int[bufsize + nextBufExpand]; int[] newbufcolumn = new int[bufsize + nextBufExpand]; - try { - if (wrapAround) { - System.arraycopy(buffer, tokenBegin, newbuffer, 0, bufsize - tokenBegin); - System.arraycopy(buffer, 0, newbuffer, bufsize - tokenBegin, bufpos); - buffer = newbuffer; + if (wrapAround) { + System.arraycopy(buffer, tokenBegin, newbuffer, 0, bufsize - tokenBegin); + System.arraycopy(buffer, 0, newbuffer, bufsize - tokenBegin, bufpos); + buffer = newbuffer; - System.arraycopy(bufline, tokenBegin, newbufline, 0, bufsize - tokenBegin); - System.arraycopy(bufline, 0, newbufline, bufsize - tokenBegin, bufpos); - bufline = newbufline; + System.arraycopy(bufline, tokenBegin, newbufline, 0, bufsize - tokenBegin); + System.arraycopy(bufline, 0, newbufline, bufsize - tokenBegin, bufpos); + bufline = newbufline; - System.arraycopy(bufcolumn, tokenBegin, newbufcolumn, 0, bufsize - tokenBegin); - System.arraycopy(bufcolumn, 0, newbufcolumn, bufsize - tokenBegin, bufpos); - bufcolumn = newbufcolumn; + System.arraycopy(bufcolumn, tokenBegin, newbufcolumn, 0, bufsize - tokenBegin); + System.arraycopy(bufcolumn, 0, newbufcolumn, bufsize - tokenBegin, bufpos); + bufcolumn = newbufcolumn; - bufpos += bufsize - tokenBegin; - maxNextCharInd = bufpos; - } else { - System.arraycopy(buffer, tokenBegin, newbuffer, 0, bufsize - tokenBegin); - buffer = newbuffer; + bufpos += bufsize - tokenBegin; + maxNextCharInd = bufpos; + } else { + System.arraycopy(buffer, tokenBegin, newbuffer, 0, bufsize - tokenBegin); + buffer = newbuffer; - System.arraycopy(bufline, tokenBegin, newbufline, 0, bufsize - tokenBegin); - bufline = newbufline; + System.arraycopy(bufline, tokenBegin, newbufline, 0, bufsize - tokenBegin); + bufline = newbufline; - System.arraycopy(bufcolumn, tokenBegin, newbufcolumn, 0, bufsize - tokenBegin); - bufcolumn = newbufcolumn; + System.arraycopy(bufcolumn, tokenBegin, newbufcolumn, 0, bufsize - tokenBegin); + bufcolumn = newbufcolumn; - bufpos -= tokenBegin; - maxNextCharInd = bufpos; - } - } catch (Throwable t) { - throw new Error(t.getMessage()); + bufpos -= tokenBegin; + maxNextCharInd = bufpos; } bufsize += nextBufExpand; diff --git a/pmd-vm/src/main/resources/category/vm/bestpractices.xml b/pmd-vm/src/main/resources/category/vm/bestpractices.xml new file mode 100644 index 00000000000..c17fa32ed8d --- /dev/null +++ b/pmd-vm/src/main/resources/category/vm/bestpractices.xml @@ -0,0 +1,34 @@ +<?xml version="1.0"?> + +<ruleset name="Best Practices" + xmlns="http://pmd.sourceforge.net/ruleset/2.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd"> + + <description> +Rules which enforce generally accepted best practices. + </description> + + <rule name="AvoidReassigningParameters" + since="5.1" + message="Avoid reassigning macro parameters such as ''{0}''" + class="net.sourceforge.pmd.lang.vm.rule.bestpractices.AvoidReassigningParametersRule" + externalInfoUrl="${pmd.website.baseurl}/pmd_rules_vm_bestpractices.html#avoidreassigningparameters"> + <description> +Reassigning values to incoming parameters is not recommended. Use temporary local variables instead. + </description> + <priority>2</priority> + </rule> + + <rule name="UnusedMacroParameter" + since="5.1" + message="Avoid unused macro parameters such as ''{0}''" + class="net.sourceforge.pmd.lang.vm.rule.bestpractices.UnusedMacroParameterRule" + externalInfoUrl="${pmd.website.baseurl}/pmd_rules_vm_bestpractices.html#unusedmacroparameter"> + <description> +Avoid unused macro parameters. They should be deleted. + </description> + <priority>2</priority> + </rule> + +</ruleset> diff --git a/pmd-vm/src/main/resources/category/vm/categories.properties b/pmd-vm/src/main/resources/category/vm/categories.properties new file mode 100644 index 00000000000..ea336212f63 --- /dev/null +++ b/pmd-vm/src/main/resources/category/vm/categories.properties @@ -0,0 +1,17 @@ +# +# BSD-style license; for more info see http://pmd.sourceforge.net/license.html +# + +rulesets.filenames=\ + category/vm/bestpractices.xml,\ + category/vm/design.xml,\ + category/vm/errorprone.xml + +# +# categories without rules +# +# category/vm/codestyle.xml +# category/vm/documentation.xml +# category/vm/multithreading.xml +# category/vm/performance.xml +# category/vm/security.xml diff --git a/pmd-vm/src/main/resources/category/vm/codestyle.xml b/pmd-vm/src/main/resources/category/vm/codestyle.xml new file mode 100644 index 00000000000..c9a6b51d10e --- /dev/null +++ b/pmd-vm/src/main/resources/category/vm/codestyle.xml @@ -0,0 +1,11 @@ +<?xml version="1.0"?> + +<ruleset name="Code Style" + xmlns="http://pmd.sourceforge.net/ruleset/2.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd"> + + <description> +Rules which enforce a specific coding style. + </description> +</ruleset> diff --git a/pmd-vm/src/main/resources/category/vm/design.xml b/pmd-vm/src/main/resources/category/vm/design.xml new file mode 100644 index 00000000000..e28b00d4451 --- /dev/null +++ b/pmd-vm/src/main/resources/category/vm/design.xml @@ -0,0 +1,80 @@ +<?xml version="1.0"?> + +<ruleset name="Design" + xmlns="http://pmd.sourceforge.net/ruleset/2.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd"> + + <description> +Rules that help you discover design issues. + </description> + + <rule name="AvoidDeeplyNestedIfStmts" + since="5.1" + message="Deeply nested if..then statements are hard to read" + class="net.sourceforge.pmd.lang.vm.rule.design.AvoidDeeplyNestedIfStmtsRule" + externalInfoUrl="${pmd.website.baseurl}/pmd_rules_vm_design.html#avoiddeeplynestedifstmts"> + <description> +Avoid creating deeply nested if-then statements since they are harder to read and error-prone to maintain. + </description> + <priority>3</priority> + </rule> + + <rule name="CollapsibleIfStatements" + since="5.1" + message="These nested if statements could be combined" + class="net.sourceforge.pmd.lang.vm.rule.design.CollapsibleIfStatementsRule" + externalInfoUrl="${pmd.website.baseurl}/pmd_rules_vm_design.html#collapsibleifstatements"> + <description> +Sometimes two consecutive 'if' statements can be consolidated by separating their conditions with a boolean short-circuit operator. + </description> + <priority>3</priority> + </rule> + + <rule name="ExcessiveTemplateLength" + since="5.1" + message="Template is too long" + class="net.sourceforge.pmd.lang.vm.rule.design.ExcessiveTemplateLengthRule" + externalInfoUrl="${pmd.website.baseurl}/pmd_rules_vm_design.html#excessivetemplatelength"> + <description> +The template is too long. It should be broken up into smaller pieces. + </description> + <priority>3</priority> + <properties> + <property name="minimum" value="1000"/> + </properties> + </rule> + + <rule name="NoInlineJavaScript" + since="5.1" + message="Avoid inline JavaScript" + class="net.sourceforge.pmd.lang.vm.rule.design.NoInlineJavaScriptRule" + externalInfoUrl="${pmd.website.baseurl}/pmd_rules_vm_design.html#noinlinejavascript"> + <description> +Avoid inline JavaScript. Import .js files instead. + </description> + <priority>2</priority> + </rule> + + <rule name="NoInlineStyles" + since="5.1" + message="Avoid inline styles" + language="vm" + class="net.sourceforge.pmd.lang.rule.XPathRule" + externalInfoUrl="${pmd.website.baseurl}/pmd_rules_vm_design.html#noinlinestyles"> + <description> +Avoid inline styles. Use css classes instead. + </description> + <priority>2</priority> + <properties> + <property name="xpath"> + <value> +<![CDATA[ +//Text[matches(@literal, "<[^>]+\s[sS][tT][yY][lL][eE]\s*=")] +]]> + </value> + </property> + </properties> + </rule> + +</ruleset> diff --git a/pmd-vm/src/main/resources/category/vm/documentation.xml b/pmd-vm/src/main/resources/category/vm/documentation.xml new file mode 100644 index 00000000000..c049f5b6f01 --- /dev/null +++ b/pmd-vm/src/main/resources/category/vm/documentation.xml @@ -0,0 +1,11 @@ +<?xml version="1.0"?> + +<ruleset name="Documentation" + xmlns="http://pmd.sourceforge.net/ruleset/2.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd"> + + <description> +Rules that are related to code documentation. + </description> +</ruleset> diff --git a/pmd-vm/src/main/resources/category/vm/errorprone.xml b/pmd-vm/src/main/resources/category/vm/errorprone.xml new file mode 100644 index 00000000000..ad2c1b05bd0 --- /dev/null +++ b/pmd-vm/src/main/resources/category/vm/errorprone.xml @@ -0,0 +1,34 @@ +<?xml version="1.0"?> + +<ruleset name="Error Prone" + xmlns="http://pmd.sourceforge.net/ruleset/2.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd"> + + <description> +Rules to detect constructs that are either broken, extremely confusing or prone to runtime errors. + </description> + + <rule name="EmptyForeachStmt" + since="5.1" + message="Avoid empty foreach loops" + class="net.sourceforge.pmd.lang.vm.rule.errorprone.EmptyForeachStmtRule" + externalInfoUrl="${pmd.website.baseurl}/pmd_rules_vm_errorprone.html#emptyforeachstmt"> + <description> +Empty foreach statements should be deleted. + </description> + <priority>2</priority> + </rule> + + <rule name="EmptyIfStmt" + since="5.1" + message="Avoid empty if statements" + class="net.sourceforge.pmd.lang.vm.rule.errorprone.EmptyIfStmtRule" + externalInfoUrl="${pmd.website.baseurl}/pmd_rules_vm_errorprone.html#emptyifstmt"> + <description> +Empty if statements should be deleted. + </description> + <priority>2</priority> + </rule> + +</ruleset> diff --git a/pmd-vm/src/main/resources/category/vm/multithreading.xml b/pmd-vm/src/main/resources/category/vm/multithreading.xml new file mode 100644 index 00000000000..88c22dfc8bc --- /dev/null +++ b/pmd-vm/src/main/resources/category/vm/multithreading.xml @@ -0,0 +1,11 @@ +<?xml version="1.0"?> + +<ruleset name="Multithreading" + xmlns="http://pmd.sourceforge.net/ruleset/2.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd"> + + <description> +Rules that flag issues when dealing with multiple threads of execution. + </description> +</ruleset> diff --git a/pmd-vm/src/main/resources/category/vm/performance.xml b/pmd-vm/src/main/resources/category/vm/performance.xml new file mode 100644 index 00000000000..251663aafc1 --- /dev/null +++ b/pmd-vm/src/main/resources/category/vm/performance.xml @@ -0,0 +1,11 @@ +<?xml version="1.0"?> + +<ruleset name="Performance" + xmlns="http://pmd.sourceforge.net/ruleset/2.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd"> + + <description> +Rules that flag suboptimal code. + </description> +</ruleset> diff --git a/pmd-vm/src/main/resources/category/vm/security.xml b/pmd-vm/src/main/resources/category/vm/security.xml new file mode 100644 index 00000000000..d6fa5415a54 --- /dev/null +++ b/pmd-vm/src/main/resources/category/vm/security.xml @@ -0,0 +1,11 @@ +<?xml version="1.0"?> + +<ruleset name="Security" + xmlns="http://pmd.sourceforge.net/ruleset/2.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd"> + + <description> +Rules that flag potential security flaws. + </description> +</ruleset> diff --git a/pmd-vm/src/main/resources/rulesets/vm/basic.xml b/pmd-vm/src/main/resources/rulesets/vm/basic.xml index 702eb6e2b17..2177c30ea35 100644 --- a/pmd-vm/src/main/resources/rulesets/vm/basic.xml +++ b/pmd-vm/src/main/resources/rulesets/vm/basic.xml @@ -3,122 +3,22 @@ <ruleset name="Basic Velocity" xmlns="http://pmd.sourceforge.net/ruleset/2.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 http://pmd.sourceforge.net/ruleset_2_0_0.xsd"> + xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd"> <description> The Basic Velocity ruleset contains basic rules for Apache Velocity pages. </description> - <rule name="AvoidDeeplyNestedIfStmts" - since="5.1" - message="Deeply nested if..then statements are hard to read" - class="net.sourceforge.pmd.lang.vm.rule.basic.AvoidDeeplyNestedIfStmtsRule" - externalInfoUrl="${pmd.website.baseurl}/pmd_rules_vm_basic.html#avoiddeeplynestedifstmts"> - <description> -Avoid creating deeply nested if-then statements since they are harder to read and error-prone to maintain. - </description> - <priority>3</priority> - </rule> + <rule ref="category/vm/design.xml/AvoidDeeplyNestedIfStmts" deprecated="true" /> + <rule ref="category/vm/design.xml/CollapsibleIfStatements" deprecated="true" /> + <rule ref="category/vm/design.xml/ExcessiveTemplateLength" deprecated="true" /> + <rule ref="category/vm/design.xml/NoInlineJavaScript" deprecated="true" /> + <rule ref="category/vm/design.xml/NoInlineStyles" deprecated="true" /> - <rule name="CollapsibleIfStatements" - since="5.1" - message="These nested if statements could be combined" - class="net.sourceforge.pmd.lang.vm.rule.basic.CollapsibleIfStatementsRule" - externalInfoUrl="${pmd.website.baseurl}/pmd_rules_vm_basic.html#collapsibleifstatements"> - <description> -Sometimes two consecutive 'if' statements can be consolidated by separating their conditions with a boolean short-circuit operator. - </description> - <priority>3</priority> - </rule> + <rule ref="category/vm/bestpractices.xml/AvoidReassigningParameters" deprecated="true" /> + <rule ref="category/vm/bestpractices.xml/UnusedMacroParameter" deprecated="true" /> - <rule name="ExcessiveTemplateLength" - since="5.1" - message="Template is too long" - class="net.sourceforge.pmd.lang.vm.rule.basic.ExcessiveTemplateLengthRule" - externalInfoUrl="${pmd.website.baseurl}/pmd_rules_vm_basic.html#excessivetemplatelength"> - <description> -The template is too long. It should be broken up into smaller pieces. - </description> - <priority>3</priority> - <properties> - <property name="minimum" value="1000"/> - </properties> - </rule> - - <rule name="AvoidReassigningParameters" - since="5.1" - message="Avoid reassigning macro parameters such as ''{0}''" - class="net.sourceforge.pmd.lang.vm.rule.basic.AvoidReassigningParametersRule" - externalInfoUrl="${pmd.website.baseurl}/pmd_rules_vm_basic.html#avoidreassigningparameters"> - <description> -Reassigning values to incoming parameters is not recommended. Use temporary local variables instead. - </description> - <priority>2</priority> - </rule> - - <rule name="EmptyIfStmt" - since="5.1" - message="Avoid empty if statements" - class="net.sourceforge.pmd.lang.vm.rule.basic.EmptyIfStmtRule" - externalInfoUrl="${pmd.website.baseurl}/pmd_rules_vm_basic.html#emptyifstmt"> - <description> -Empty if statements should be deleted. - </description> - <priority>2</priority> - </rule> - - <rule name="EmptyForeachStmt" - since="5.1" - message="Avoid empty foreach loops" - class="net.sourceforge.pmd.lang.vm.rule.basic.EmptyForeachStmtRule" - externalInfoUrl="${pmd.website.baseurl}/pmd_rules_vm_basic.html#emptyforeachstmt"> - <description> -Empty foreach statements should be deleted. - </description> - <priority>2</priority> - </rule> - - <rule name="UnusedMacroParameter" - since="5.1" - message="Avoid unused macro parameters such as ''{0}''" - class="net.sourceforge.pmd.lang.vm.rule.basic.UnusedMacroParameterRule" - externalInfoUrl="${pmd.website.baseurl}/pmd_rules_vm_basic.html#unusedmacroparameter"> - <description> -Avoid unused macro parameters. They should be deleted. - </description> - <priority>2</priority> - </rule> - - <rule name="NoInlineJavaScript" - since="5.1" - message="Avoid inline JavaScript" - class="net.sourceforge.pmd.lang.vm.rule.basic.NoInlineJavaScriptRule" - externalInfoUrl="${pmd.website.baseurl}/pmd_rules_vm_basic.html#noinlinejavascript"> - <description> -Avoid inline JavaScript. Import .js files instead. - </description> - <priority>2</priority> - </rule> - - <rule name="NoInlineStyles" - since="5.1" - message="Avoid inline styles" - language="vm" - class="net.sourceforge.pmd.lang.rule.XPathRule" - externalInfoUrl="${pmd.website.baseurl}/pmd_rules_vm_basic.html#noinlinestyles"> - <description> -Avoid inline styles. Use css classes instead. - </description> - <priority>2</priority> - <properties> - <property name="xpath"> - <value> -<![CDATA[ -//Text[matches(@literal, "<[^>]+\s[sS][tT][yY][lL][eE]\s*=")] -]]> - </value> - </property> - </properties> - </rule> + <rule ref="category/vm/errorprone.xml/EmptyForeachStmt" deprecated="true" /> + <rule ref="category/vm/errorprone.xml/EmptyIfStmt" deprecated="true" /> </ruleset> diff --git a/pmd-vm/src/main/resources/rulesets/vm/rulesets.properties b/pmd-vm/src/main/resources/rulesets/vm/rulesets.properties index 11fc27409df..ea336212f63 100644 --- a/pmd-vm/src/main/resources/rulesets/vm/rulesets.properties +++ b/pmd-vm/src/main/resources/rulesets/vm/rulesets.properties @@ -2,4 +2,16 @@ # BSD-style license; for more info see http://pmd.sourceforge.net/license.html # -rulesets.filenames=rulesets/vm/basic.xml +rulesets.filenames=\ + category/vm/bestpractices.xml,\ + category/vm/design.xml,\ + category/vm/errorprone.xml + +# +# categories without rules +# +# category/vm/codestyle.xml +# category/vm/documentation.xml +# category/vm/multithreading.xml +# category/vm/performance.xml +# category/vm/security.xml diff --git a/pmd-vm/src/test/java/net/sourceforge/pmd/lang/vm/rule/basic/BasicRulesTest.java b/pmd-vm/src/test/java/net/sourceforge/pmd/lang/vm/rule/basic/BasicRulesTest.java deleted file mode 100644 index 8b66c3e1964..00000000000 --- a/pmd-vm/src/test/java/net/sourceforge/pmd/lang/vm/rule/basic/BasicRulesTest.java +++ /dev/null @@ -1,25 +0,0 @@ -/** - * BSD-style license; for more info see http://pmd.sourceforge.net/license.html - */ - -package net.sourceforge.pmd.lang.vm.rule.basic; - -import net.sourceforge.pmd.testframework.SimpleAggregatorTst; - -public class BasicRulesTest extends SimpleAggregatorTst { - - private static final String RULESET = "vm-basic"; - - @Override - public void setUp() { - addRule(RULESET, "AvoidDeeplyNestedIfStmts"); - addRule(RULESET, "CollapsibleIfStatements"); - addRule(RULESET, "ExcessiveTemplateLength"); - addRule(RULESET, "AvoidReassigningParameters"); - addRule(RULESET, "EmptyIfStmt"); - addRule(RULESET, "EmptyForeachStmt"); - addRule(RULESET, "UnusedMacroParameter"); - addRule(RULESET, "NoInlineJavaScript"); - addRule(RULESET, "NoInlineStyles"); - } -} diff --git a/pmd-vm/src/test/java/net/sourceforge/pmd/lang/vm/rule/bestpractices/AvoidReassigningParametersTest.java b/pmd-vm/src/test/java/net/sourceforge/pmd/lang/vm/rule/bestpractices/AvoidReassigningParametersTest.java new file mode 100644 index 00000000000..b02ee8345a0 --- /dev/null +++ b/pmd-vm/src/test/java/net/sourceforge/pmd/lang/vm/rule/bestpractices/AvoidReassigningParametersTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.vm.rule.bestpractices; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class AvoidReassigningParametersTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-vm/src/test/java/net/sourceforge/pmd/lang/vm/rule/bestpractices/UnusedMacroParameterTest.java b/pmd-vm/src/test/java/net/sourceforge/pmd/lang/vm/rule/bestpractices/UnusedMacroParameterTest.java new file mode 100644 index 00000000000..feab866273e --- /dev/null +++ b/pmd-vm/src/test/java/net/sourceforge/pmd/lang/vm/rule/bestpractices/UnusedMacroParameterTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.vm.rule.bestpractices; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class UnusedMacroParameterTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-vm/src/test/java/net/sourceforge/pmd/lang/vm/rule/design/AvoidDeeplyNestedIfStmtsTest.java b/pmd-vm/src/test/java/net/sourceforge/pmd/lang/vm/rule/design/AvoidDeeplyNestedIfStmtsTest.java new file mode 100644 index 00000000000..84038b33370 --- /dev/null +++ b/pmd-vm/src/test/java/net/sourceforge/pmd/lang/vm/rule/design/AvoidDeeplyNestedIfStmtsTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.vm.rule.design; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class AvoidDeeplyNestedIfStmtsTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-vm/src/test/java/net/sourceforge/pmd/lang/vm/rule/design/CollapsibleIfStatementsTest.java b/pmd-vm/src/test/java/net/sourceforge/pmd/lang/vm/rule/design/CollapsibleIfStatementsTest.java new file mode 100644 index 00000000000..f3a83a7e5a0 --- /dev/null +++ b/pmd-vm/src/test/java/net/sourceforge/pmd/lang/vm/rule/design/CollapsibleIfStatementsTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.vm.rule.design; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class CollapsibleIfStatementsTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-vm/src/test/java/net/sourceforge/pmd/lang/vm/rule/design/ExcessiveTemplateLengthTest.java b/pmd-vm/src/test/java/net/sourceforge/pmd/lang/vm/rule/design/ExcessiveTemplateLengthTest.java new file mode 100644 index 00000000000..2394b02fc9e --- /dev/null +++ b/pmd-vm/src/test/java/net/sourceforge/pmd/lang/vm/rule/design/ExcessiveTemplateLengthTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.vm.rule.design; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class ExcessiveTemplateLengthTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-vm/src/test/java/net/sourceforge/pmd/lang/vm/rule/design/NoInlineJavaScriptTest.java b/pmd-vm/src/test/java/net/sourceforge/pmd/lang/vm/rule/design/NoInlineJavaScriptTest.java new file mode 100644 index 00000000000..90e87c54382 --- /dev/null +++ b/pmd-vm/src/test/java/net/sourceforge/pmd/lang/vm/rule/design/NoInlineJavaScriptTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.vm.rule.design; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class NoInlineJavaScriptTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-vm/src/test/java/net/sourceforge/pmd/lang/vm/rule/design/NoInlineStylesTest.java b/pmd-vm/src/test/java/net/sourceforge/pmd/lang/vm/rule/design/NoInlineStylesTest.java new file mode 100644 index 00000000000..ed09b4024f2 --- /dev/null +++ b/pmd-vm/src/test/java/net/sourceforge/pmd/lang/vm/rule/design/NoInlineStylesTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.vm.rule.design; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class NoInlineStylesTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-vm/src/test/java/net/sourceforge/pmd/lang/vm/rule/errorprone/EmptyForeachStmtTest.java b/pmd-vm/src/test/java/net/sourceforge/pmd/lang/vm/rule/errorprone/EmptyForeachStmtTest.java new file mode 100644 index 00000000000..c21a43f2444 --- /dev/null +++ b/pmd-vm/src/test/java/net/sourceforge/pmd/lang/vm/rule/errorprone/EmptyForeachStmtTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.vm.rule.errorprone; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class EmptyForeachStmtTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-vm/src/test/java/net/sourceforge/pmd/lang/vm/rule/errorprone/EmptyIfStmtTest.java b/pmd-vm/src/test/java/net/sourceforge/pmd/lang/vm/rule/errorprone/EmptyIfStmtTest.java new file mode 100644 index 00000000000..be82d1bb855 --- /dev/null +++ b/pmd-vm/src/test/java/net/sourceforge/pmd/lang/vm/rule/errorprone/EmptyIfStmtTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.vm.rule.errorprone; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class EmptyIfStmtTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-vm/src/test/resources/net/sourceforge/pmd/lang/vm/rule/basic/xml/AvoidReassigningParameters.xml b/pmd-vm/src/test/resources/net/sourceforge/pmd/lang/vm/rule/bestpractices/xml/AvoidReassigningParameters.xml similarity index 100% rename from pmd-vm/src/test/resources/net/sourceforge/pmd/lang/vm/rule/basic/xml/AvoidReassigningParameters.xml rename to pmd-vm/src/test/resources/net/sourceforge/pmd/lang/vm/rule/bestpractices/xml/AvoidReassigningParameters.xml diff --git a/pmd-vm/src/test/resources/net/sourceforge/pmd/lang/vm/rule/basic/xml/UnusedMacroParameter.xml b/pmd-vm/src/test/resources/net/sourceforge/pmd/lang/vm/rule/bestpractices/xml/UnusedMacroParameter.xml similarity index 100% rename from pmd-vm/src/test/resources/net/sourceforge/pmd/lang/vm/rule/basic/xml/UnusedMacroParameter.xml rename to pmd-vm/src/test/resources/net/sourceforge/pmd/lang/vm/rule/bestpractices/xml/UnusedMacroParameter.xml diff --git a/pmd-vm/src/test/resources/net/sourceforge/pmd/lang/vm/rule/basic/xml/AvoidDeeplyNestedIfStmts.xml b/pmd-vm/src/test/resources/net/sourceforge/pmd/lang/vm/rule/design/xml/AvoidDeeplyNestedIfStmts.xml similarity index 100% rename from pmd-vm/src/test/resources/net/sourceforge/pmd/lang/vm/rule/basic/xml/AvoidDeeplyNestedIfStmts.xml rename to pmd-vm/src/test/resources/net/sourceforge/pmd/lang/vm/rule/design/xml/AvoidDeeplyNestedIfStmts.xml diff --git a/pmd-vm/src/test/resources/net/sourceforge/pmd/lang/vm/rule/basic/xml/CollapsibleIfStatements.xml b/pmd-vm/src/test/resources/net/sourceforge/pmd/lang/vm/rule/design/xml/CollapsibleIfStatements.xml similarity index 100% rename from pmd-vm/src/test/resources/net/sourceforge/pmd/lang/vm/rule/basic/xml/CollapsibleIfStatements.xml rename to pmd-vm/src/test/resources/net/sourceforge/pmd/lang/vm/rule/design/xml/CollapsibleIfStatements.xml diff --git a/pmd-vm/src/test/resources/net/sourceforge/pmd/lang/vm/rule/basic/xml/ExcessiveTemplateLength.xml b/pmd-vm/src/test/resources/net/sourceforge/pmd/lang/vm/rule/design/xml/ExcessiveTemplateLength.xml similarity index 100% rename from pmd-vm/src/test/resources/net/sourceforge/pmd/lang/vm/rule/basic/xml/ExcessiveTemplateLength.xml rename to pmd-vm/src/test/resources/net/sourceforge/pmd/lang/vm/rule/design/xml/ExcessiveTemplateLength.xml diff --git a/pmd-vm/src/test/resources/net/sourceforge/pmd/lang/vm/rule/basic/xml/NoInlineJavaScript.xml b/pmd-vm/src/test/resources/net/sourceforge/pmd/lang/vm/rule/design/xml/NoInlineJavaScript.xml similarity index 100% rename from pmd-vm/src/test/resources/net/sourceforge/pmd/lang/vm/rule/basic/xml/NoInlineJavaScript.xml rename to pmd-vm/src/test/resources/net/sourceforge/pmd/lang/vm/rule/design/xml/NoInlineJavaScript.xml diff --git a/pmd-vm/src/test/resources/net/sourceforge/pmd/lang/vm/rule/basic/xml/NoInlineStyles.xml b/pmd-vm/src/test/resources/net/sourceforge/pmd/lang/vm/rule/design/xml/NoInlineStyles.xml similarity index 100% rename from pmd-vm/src/test/resources/net/sourceforge/pmd/lang/vm/rule/basic/xml/NoInlineStyles.xml rename to pmd-vm/src/test/resources/net/sourceforge/pmd/lang/vm/rule/design/xml/NoInlineStyles.xml diff --git a/pmd-vm/src/test/resources/net/sourceforge/pmd/lang/vm/rule/basic/xml/EmptyForeachStmt.xml b/pmd-vm/src/test/resources/net/sourceforge/pmd/lang/vm/rule/errorprone/xml/EmptyForeachStmt.xml similarity index 100% rename from pmd-vm/src/test/resources/net/sourceforge/pmd/lang/vm/rule/basic/xml/EmptyForeachStmt.xml rename to pmd-vm/src/test/resources/net/sourceforge/pmd/lang/vm/rule/errorprone/xml/EmptyForeachStmt.xml diff --git a/pmd-vm/src/test/resources/net/sourceforge/pmd/lang/vm/rule/basic/xml/EmptyIfStmt.xml b/pmd-vm/src/test/resources/net/sourceforge/pmd/lang/vm/rule/errorprone/xml/EmptyIfStmt.xml similarity index 100% rename from pmd-vm/src/test/resources/net/sourceforge/pmd/lang/vm/rule/basic/xml/EmptyIfStmt.xml rename to pmd-vm/src/test/resources/net/sourceforge/pmd/lang/vm/rule/errorprone/xml/EmptyIfStmt.xml diff --git a/pmd-xml/pmd-xml-ruleset.xml b/pmd-xml/pmd-xml-ruleset.xml new file mode 100644 index 00000000000..88fc15f32b3 --- /dev/null +++ b/pmd-xml/pmd-xml-ruleset.xml @@ -0,0 +1,13 @@ +<?xml version="1.0"?> +<ruleset name="dogfood-without-missing-override" + xmlns="http://pmd.sourceforge.net/ruleset/2.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd"> + <description>Rules to check PMD itself.</description> + + <rule ref="net/sourceforge/pmd/pmd-dogfood-config.xml"> + <!-- Disable rule MissingOverride, see https://github.com/pmd/pmd/issues/1074 --> + <exclude name="MissingOverride"/> + </rule> + +</ruleset> \ No newline at end of file diff --git a/pmd-xml/pom.xml b/pmd-xml/pom.xml index 3191d99e726..eb67f4e58c0 100644 --- a/pmd-xml/pom.xml +++ b/pmd-xml/pom.xml @@ -7,13 +7,9 @@ <parent> <groupId>net.sourceforge.pmd</groupId> <artifactId>pmd</artifactId> - <version>6.0.0-SNAPSHOT</version> + <version>6.10.0-SNAPSHOT</version> </parent> - <properties> - <config.basedir>${basedir}/../pmd-core</config.basedir> - </properties> - <build> <resources> <resource> @@ -55,13 +51,20 @@ </execution> </executions> </plugin> + + <!-- Disable rule MissingOverride, see https://github.com/pmd/pmd/issues/1074 --> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-pmd-plugin</artifactId> + <configuration> + <rulesets> + <ruleset>pmd-xml-ruleset.xml</ruleset> + </rulesets> + </configuration> + </plugin> </plugins> </build> <dependencies> - <dependency> - <groupId>jaxen</groupId> - <artifactId>jaxen</artifactId> - </dependency> <dependency> <groupId>net.sourceforge.pmd</groupId> <artifactId>pmd-core</artifactId> @@ -70,6 +73,10 @@ <groupId>net.sourceforge.saxon</groupId> <artifactId>saxon</artifactId> </dependency> + <dependency> + <groupId>commons-io</groupId> + <artifactId>commons-io</artifactId> + </dependency> <dependency> <groupId>net.sourceforge.saxon</groupId> @@ -78,6 +85,11 @@ <scope>runtime</scope> </dependency> + <dependency> + <groupId>junit</groupId> + <artifactId>junit</artifactId> + <scope>test</scope> + </dependency> <dependency> <groupId>net.sourceforge.pmd</groupId> <artifactId>pmd-test</artifactId> diff --git a/pmd-xml/src/main/java/net/sourceforge/pmd/lang/xml/XmlHandler.java b/pmd-xml/src/main/java/net/sourceforge/pmd/lang/xml/XmlHandler.java index 0536fccc80f..f8591b418b1 100644 --- a/pmd-xml/src/main/java/net/sourceforge/pmd/lang/xml/XmlHandler.java +++ b/pmd-xml/src/main/java/net/sourceforge/pmd/lang/xml/XmlHandler.java @@ -6,22 +6,18 @@ import java.io.Writer; -import org.jaxen.Navigator; - import net.sourceforge.pmd.lang.AbstractLanguageVersionHandler; import net.sourceforge.pmd.lang.Parser; import net.sourceforge.pmd.lang.ParserOptions; import net.sourceforge.pmd.lang.VisitorStarter; import net.sourceforge.pmd.lang.XPathHandler; import net.sourceforge.pmd.lang.ast.Node; -import net.sourceforge.pmd.lang.ast.xpath.DocumentNavigator; +import net.sourceforge.pmd.lang.ast.xpath.DefaultASTXPathHandler; import net.sourceforge.pmd.lang.rule.RuleViolationFactory; import net.sourceforge.pmd.lang.xml.ast.DumpFacade; import net.sourceforge.pmd.lang.xml.ast.XmlNode; import net.sourceforge.pmd.lang.xml.rule.XmlRuleViolationFactory; -import net.sf.saxon.sxpath.IndependentContext; - /** * Implementation of LanguageVersionHandler for the XML. */ @@ -29,19 +25,10 @@ public class XmlHandler extends AbstractLanguageVersionHandler { @Override public XPathHandler getXPathHandler() { - return new XPathHandler() { - public void initialize() { - } - - public void initialize(IndependentContext context) { - } - - public Navigator getNavigator() { - return new DocumentNavigator(); - } - }; + return new DefaultASTXPathHandler(); } + @Override public RuleViolationFactory getRuleViolationFactory() { return XmlRuleViolationFactory.INSTANCE; } @@ -51,6 +38,7 @@ public ParserOptions getDefaultParserOptions() { return new XmlParserOptions(); } + @Override public Parser getParser(ParserOptions parserOptions) { return new XmlParser(parserOptions); } @@ -58,6 +46,7 @@ public Parser getParser(ParserOptions parserOptions) { @Override public VisitorStarter getDumpFacade(final Writer writer, final String prefix, final boolean recurse) { return new VisitorStarter() { + @Override public void start(Node rootNode) { new DumpFacade().initializeWith(writer, prefix, recurse, (XmlNode) rootNode); } diff --git a/pmd-xml/src/main/java/net/sourceforge/pmd/lang/xml/XmlParser.java b/pmd-xml/src/main/java/net/sourceforge/pmd/lang/xml/XmlParser.java index 989b877bf78..8d72efcad78 100644 --- a/pmd-xml/src/main/java/net/sourceforge/pmd/lang/xml/XmlParser.java +++ b/pmd-xml/src/main/java/net/sourceforge/pmd/lang/xml/XmlParser.java @@ -28,14 +28,17 @@ public TokenManager createTokenManager(Reader source) { return null; } + @Override public boolean canParse() { return true; } + @Override public Node parse(String fileName, Reader source) throws ParseException { return new net.sourceforge.pmd.lang.xml.ast.XmlParser((XmlParserOptions) parserOptions).parse(source); } + @Override public Map<Integer, String> getSuppressMap() { return new HashMap<>(); // FIXME } diff --git a/pmd-xml/src/main/java/net/sourceforge/pmd/lang/xml/XmlParserOptions.java b/pmd-xml/src/main/java/net/sourceforge/pmd/lang/xml/XmlParserOptions.java index a8c466e3e64..078105bdace 100644 --- a/pmd-xml/src/main/java/net/sourceforge/pmd/lang/xml/XmlParserOptions.java +++ b/pmd-xml/src/main/java/net/sourceforge/pmd/lang/xml/XmlParserOptions.java @@ -6,6 +6,7 @@ import java.io.ByteArrayInputStream; import java.io.IOException; +import java.util.Objects; import org.xml.sax.EntityResolver; import org.xml.sax.InputSource; @@ -14,7 +15,6 @@ import net.sourceforge.pmd.Rule; import net.sourceforge.pmd.lang.ParserOptions; import net.sourceforge.pmd.properties.BooleanProperty; -import net.sourceforge.pmd.util.StringUtil; public class XmlParserOptions extends ParserOptions { @@ -43,6 +43,7 @@ public class XmlParserOptions extends ParserOptions { "Specifies whether XML parser will attempt to lookup the DTD.", Boolean.FALSE, 10.0f); public static final EntityResolver SILENT_ENTITY_RESOLVER = new EntityResolver() { + @Override public InputSource resolveEntity(String publicId, String systemId) throws SAXException, IOException { return new InputSource(new ByteArrayInputStream("".getBytes())); } @@ -81,7 +82,7 @@ public XmlParserOptions(Rule rule) { } /** - * + * * @return the configured entity resolver. If {@link #lookupDescriptorDoc} * is false it would normally force the XML parser to use its own * resolver @@ -181,7 +182,7 @@ public boolean equals(Object obj) { return false; } final XmlParserOptions that = (XmlParserOptions) obj; - return StringUtil.isSame(this.suppressMarker, that.suppressMarker, false, false, false) + return Objects.equals(this.suppressMarker, that.suppressMarker) && this.coalescing == that.coalescing && this.expandEntityReferences == that.expandEntityReferences && this.ignoringComments == that.ignoringComments && this.ignoringElementContentWhitespace == that.ignoringElementContentWhitespace diff --git a/pmd-xml/src/main/java/net/sourceforge/pmd/lang/xml/ast/AbstractDomNodeProxy.java b/pmd-xml/src/main/java/net/sourceforge/pmd/lang/xml/ast/AbstractDomNodeProxy.java new file mode 100644 index 00000000000..1d29bac0775 --- /dev/null +++ b/pmd-xml/src/main/java/net/sourceforge/pmd/lang/xml/ast/AbstractDomNodeProxy.java @@ -0,0 +1,257 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.xml.ast; + +import org.w3c.dom.DOMException; +import org.w3c.dom.Document; +import org.w3c.dom.NamedNodeMap; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; +import org.w3c.dom.UserDataHandler; + +import net.sourceforge.pmd.lang.ast.AbstractNode; + + +/** + * Moves boilerplate out of {@link XmlNodeWrapper}. + * + * @author Clément Fournier + * @since 6.1.0 + */ +public abstract class AbstractDomNodeProxy extends AbstractNode implements org.w3c.dom.Node { + + protected final org.w3c.dom.Node node; + + + protected AbstractDomNodeProxy(Node node) { + super(0); + this.node = node; + } + + + @Override + public String getNodeName() { + return node.getNodeName(); + } + + + @Override + public String getNodeValue() throws DOMException { + return node.getNodeValue(); + } + + + @Override + public void setNodeValue(String nodeValue) throws DOMException { + node.setNodeValue(nodeValue); + } + + + @SuppressWarnings("PMD.AvoidUsingShortType") + @Override + public short getNodeType() { + return node.getNodeType(); + } + + + @Override + public org.w3c.dom.Node getParentNode() { + return node.getParentNode(); + } + + + @Override + public NodeList getChildNodes() { + return node.getChildNodes(); + } + + + @Override + public org.w3c.dom.Node getFirstChild() { + return node.getFirstChild(); + } + + + @Override + public org.w3c.dom.Node getLastChild() { + return node.getLastChild(); + } + + + @Override + public org.w3c.dom.Node getPreviousSibling() { + return node.getPreviousSibling(); + } + + + @Override + public org.w3c.dom.Node getNextSibling() { + return node.getNextSibling(); + } + + + @Override + public NamedNodeMap getAttributes() { + return node.getAttributes(); + } + + + @Override + public Document getOwnerDocument() { + return node.getOwnerDocument(); + } + + + @Override + public org.w3c.dom.Node insertBefore(org.w3c.dom.Node newChild, org.w3c.dom.Node refChild) throws DOMException { + return node.insertBefore(newChild, refChild); + } + + + @Override + public org.w3c.dom.Node replaceChild(org.w3c.dom.Node newChild, org.w3c.dom.Node oldChild) throws DOMException { + return node.replaceChild(newChild, oldChild); + } + + + @Override + public org.w3c.dom.Node removeChild(org.w3c.dom.Node oldChild) throws DOMException { + return node.removeChild(oldChild); + } + + + @Override + public org.w3c.dom.Node appendChild(org.w3c.dom.Node newChild) throws DOMException { + return node.appendChild(newChild); + } + + + @Override + public boolean hasChildNodes() { + return node.hasChildNodes(); + } + + + @Override + public org.w3c.dom.Node cloneNode(boolean deep) { + return node.cloneNode(deep); + } + + + @Override + public void normalize() { + node.normalize(); + } + + + @Override + public boolean isSupported(String feature, String version) { + return node.isSupported(feature, version); + } + + + @Override + public String getNamespaceURI() { + return node.getNamespaceURI(); + } + + + @Override + public String getPrefix() { + return node.getPrefix(); + } + + + @Override + public void setPrefix(String prefix) throws DOMException { + node.setPrefix(prefix); + } + + + @Override + public String getLocalName() { + return node.getLocalName(); + } + + + @Override + public boolean hasAttributes() { + return node.hasAttributes(); + } + + + @Override + public String getBaseURI() { + return node.getBaseURI(); + } + + + @SuppressWarnings("PMD.AvoidUsingShortType") + @Override + public short compareDocumentPosition(org.w3c.dom.Node other) throws DOMException { + return node.compareDocumentPosition(other); + } + + + @Override + public String getTextContent() throws DOMException { + return node.getTextContent(); + } + + + @Override + public void setTextContent(String textContent) throws DOMException { + node.setTextContent(textContent); + } + + + @Override + public boolean isSameNode(org.w3c.dom.Node other) { + return node.isSameNode(other); + } + + + @Override + public String lookupPrefix(String namespaceURI) { + return node.lookupPrefix(namespaceURI); + } + + + @Override + public boolean isDefaultNamespace(String namespaceURI) { + return node.isDefaultNamespace(namespaceURI); + } + + + @Override + public String lookupNamespaceURI(String prefix) { + return node.lookupNamespaceURI(prefix); + } + + + @Override + public boolean isEqualNode(org.w3c.dom.Node arg) { + return node.isEqualNode(arg); + } + + + @Override + public Object getFeature(String feature, String version) { + return node.getFeature(feature, version); + } + + + @Override + public Object setUserData(String key, Object data, UserDataHandler handler) { + return node.setUserData(key, data, handler); + } + + + @Override + public Object getUserData(String key) { + return node.getUserData(key); + } + +} diff --git a/pmd-xml/src/main/java/net/sourceforge/pmd/lang/xml/ast/DOMLineNumbers.java b/pmd-xml/src/main/java/net/sourceforge/pmd/lang/xml/ast/DOMLineNumbers.java index 827b179c934..702efa69600 100644 --- a/pmd-xml/src/main/java/net/sourceforge/pmd/lang/xml/ast/DOMLineNumbers.java +++ b/pmd-xml/src/main/java/net/sourceforge/pmd/lang/xml/ast/DOMLineNumbers.java @@ -40,7 +40,6 @@ private int determineLocation(Node n, int index) { int textLength = 0; if (n.getNodeType() == Node.DOCUMENT_TYPE_NODE) { nextIndex = xmlString.indexOf("<!DOCTYPE", nextIndex); - nodeLength = "<!DOCTYPE".length(); } else if (n.getNodeType() == Node.COMMENT_NODE) { nextIndex = xmlString.indexOf("<!--", nextIndex); } else if (n.getNodeType() == Node.ELEMENT_NODE) { @@ -67,15 +66,23 @@ private int determineLocation(Node n, int index) { nextIndex = xmlString.indexOf("&" + n.getNodeName() + ";", nextIndex); } setBeginLocation(n, nextIndex); + + nextIndex += nodeLength; + if (n.hasChildNodes()) { - // next nodes begin after the current start tag - nextIndex += nodeLength; NodeList childs = n.getChildNodes(); for (int i = 0; i < childs.getLength(); i++) { nextIndex = determineLocation(childs.item(i), nextIndex); } } - if (n.getNodeType() == Node.ELEMENT_NODE) { + + // autoclosing element, eg <a /> + boolean isAutoClose = !n.hasChildNodes() + && n.getNodeType() == Node.ELEMENT_NODE + // nextIndex is up to the closing > at this point + && xmlString.startsWith("/>", nextIndex - 2); + + if (n.getNodeType() == Node.ELEMENT_NODE && !isAutoClose) { nextIndex += 2 + n.getNodeName().length() + 1; // </nodename> } else if (n.getNodeType() == Node.DOCUMENT_TYPE_NODE) { Node nextSibling = n.getNextSibling(); diff --git a/pmd-xml/src/main/java/net/sourceforge/pmd/lang/xml/ast/DumpFacade.java b/pmd-xml/src/main/java/net/sourceforge/pmd/lang/xml/ast/DumpFacade.java index 0d224c9b83f..888995fe9bc 100644 --- a/pmd-xml/src/main/java/net/sourceforge/pmd/lang/xml/ast/DumpFacade.java +++ b/pmd-xml/src/main/java/net/sourceforge/pmd/lang/xml/ast/DumpFacade.java @@ -51,7 +51,7 @@ private void dump(XmlNode node, String prefix) { writer.print(prefix); // 2) JJT Name of the Node - writer.print(node.toString()); + writer.print(node.getXPathNodeName()); // // If there are any additional details, then: diff --git a/pmd-xml/src/main/java/net/sourceforge/pmd/lang/xml/ast/XmlNode.java b/pmd-xml/src/main/java/net/sourceforge/pmd/lang/xml/ast/XmlNode.java index 0cc4ee451a8..7c43a09e36b 100644 --- a/pmd-xml/src/main/java/net/sourceforge/pmd/lang/xml/ast/XmlNode.java +++ b/pmd-xml/src/main/java/net/sourceforge/pmd/lang/xml/ast/XmlNode.java @@ -19,7 +19,7 @@ public interface XmlNode extends Node, AttributeNode { /** * Provide access to the underlying DOM node. - * + * * @return The DOM node. */ org.w3c.dom.Node getNode(); diff --git a/pmd-xml/src/main/java/net/sourceforge/pmd/lang/xml/ast/XmlNodeInvocationHandler.java b/pmd-xml/src/main/java/net/sourceforge/pmd/lang/xml/ast/XmlNodeInvocationHandler.java deleted file mode 100644 index 8ab5dc81156..00000000000 --- a/pmd-xml/src/main/java/net/sourceforge/pmd/lang/xml/ast/XmlNodeInvocationHandler.java +++ /dev/null @@ -1,144 +0,0 @@ -/** - * BSD-style license; for more info see http://pmd.sourceforge.net/license.html - */ - -package net.sourceforge.pmd.lang.xml.ast; - -import static net.sourceforge.pmd.lang.xml.ast.XmlNode.BEGIN_COLUMN; -import static net.sourceforge.pmd.lang.xml.ast.XmlNode.BEGIN_LINE; -import static net.sourceforge.pmd.lang.xml.ast.XmlNode.END_COLUMN; -import static net.sourceforge.pmd.lang.xml.ast.XmlNode.END_LINE; - -import java.lang.reflect.InvocationHandler; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Iterator; -import java.util.List; - -import org.w3c.dom.Document; -import org.w3c.dom.NamedNodeMap; -import org.w3c.dom.Node; -import org.w3c.dom.NodeList; -import org.w3c.dom.Text; - -import net.sourceforge.pmd.lang.ast.xpath.Attribute; -import net.sourceforge.pmd.util.CompoundIterator; - -public class XmlNodeInvocationHandler implements InvocationHandler { - private final Node node; - private Object userData; - private XmlParser parser; - - public XmlNodeInvocationHandler(XmlParser parser, Node node) { - this.parser = parser; - this.node = node; - } - - public Object invoke(Object proxy, Method method, Object[] args) - throws IllegalAccessException, IllegalArgumentException, InvocationTargetException { - // XmlNode method? - if (method.getDeclaringClass().isAssignableFrom(XmlNode.class) - && !"java.lang.Object".equals(method.getDeclaringClass().getName())) { - if ("jjtGetNumChildren".equals(method.getName())) { - return node.hasChildNodes() ? node.getChildNodes().getLength() : 0; - } else if ("jjtGetChild".equals(method.getName())) { - return parser.createProxy(node.getChildNodes().item(((Integer) args[0]).intValue())); - } else if ("jjtGetChildIndex".equals(method.getName())) { - Node parent = node.getParentNode(); - NodeList childNodes = parent.getChildNodes(); - for (int i = 0; i < childNodes.getLength(); i++) { - if (node == childNodes.item(i)) { - return i; - } - } - throw new IllegalStateException("This node is not a child of its parent: " + node); - } else if ("getImage".equals(method.getName())) { - if (node instanceof Text) { - return ((Text) node).getData(); - } else { - return null; - } - } else if ("jjtGetParent".equals(method.getName())) { - Node parent = node.getParentNode(); - if (parent != null && !(parent instanceof Document)) { - return parser.createProxy(parent); - } else { - return null; - } - } else if ("getAttributeIterator".equals(method.getName())) { - List<Iterator<Attribute>> iterators = new ArrayList<>(); - - // Expose DOM Attributes - final NamedNodeMap attributes = node.getAttributes(); - iterators.add(new Iterator<Attribute>() { - private int index; - - public boolean hasNext() { - return attributes != null && index < attributes.getLength(); - } - - public Attribute next() { - Node attributeNode = attributes.item(index++); - return new Attribute(parser.createProxy(node), attributeNode.getNodeName(), - attributeNode.getNodeValue()); - } - - public void remove() { - throw new UnsupportedOperationException(); - } - }); - - // Expose Text/CDATA nodes to have an 'Image' attribute like - // AST Nodes - if (proxy instanceof Text) { - iterators.add(Collections.singletonList( - new Attribute((net.sourceforge.pmd.lang.ast.Node) proxy, "Image", ((Text) proxy).getData())) - .iterator()); - } - - // Expose Java Attributes - // iterators.add(new - // AttributeAxisIterator((net.sourceforge.pmd.lang.ast.Node) - // p)); - - return new CompoundIterator<Attribute>(iterators.toArray(new Iterator[iterators.size()])); - } else if ("getBeginLine".equals(method.getName())) { - return getUserData(BEGIN_LINE); - } else if ("getBeginColumn".equals(method.getName())) { - return getUserData(BEGIN_COLUMN); - } else if ("getEndLine".equals(method.getName())) { - return getUserData(END_LINE); - } else if ("getEndColumn".equals(method.getName())) { - return getUserData(END_COLUMN); - } else if ("getNode".equals(method.getName())) { - return node; - } else if ("getUserData".equals(method.getName())) { - return userData; - } else if ("setUserData".equals(method.getName())) { - userData = args[0]; - return null; - } else if ("isFindBoundary".equals(method.getName())) { - return false; - } - throw new UnsupportedOperationException("Method not supported for XmlNode: " + method); - } else { - if ("toString".equals(method.getName())) { - String s = node.getNodeName(); - s = s.replace("#", ""); - return s; - } - // Delegate method - Object result = method.invoke(node, args); - return result; - } - } - - private Integer getUserData(String key) { - if (node.getUserData(key) != null) { - return (Integer) node.getUserData(key); - } - return Integer.valueOf(-1); - } -} diff --git a/pmd-xml/src/main/java/net/sourceforge/pmd/lang/xml/ast/XmlNodeWrapper.java b/pmd-xml/src/main/java/net/sourceforge/pmd/lang/xml/ast/XmlNodeWrapper.java new file mode 100644 index 00000000000..a3fd69ad251 --- /dev/null +++ b/pmd-xml/src/main/java/net/sourceforge/pmd/lang/xml/ast/XmlNodeWrapper.java @@ -0,0 +1,266 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.xml.ast; + + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.Objects; + +import org.w3c.dom.Document; +import org.w3c.dom.NamedNodeMap; +import org.w3c.dom.NodeList; +import org.w3c.dom.Text; + +import net.sourceforge.pmd.lang.ast.Node; +import net.sourceforge.pmd.lang.ast.xpath.Attribute; +import net.sourceforge.pmd.lang.dfa.DataFlowNode; +import net.sourceforge.pmd.util.CompoundIterator; + + +/** + * Proxy wrapping an XML DOM node ({@link org.w3c.dom.Node}) to implement PMD interfaces. + * + * @author Clément Fournier + * @since 6.1.0 + */ +public class XmlNodeWrapper extends AbstractDomNodeProxy implements XmlNode { + + private final XmlParser parser; + private Object userData; + + + public XmlNodeWrapper(XmlParser parser, org.w3c.dom.Node domNode) { + super(domNode); + this.parser = parser; + } + + + @Override + public void jjtClose() { + throw new UnsupportedOperationException(); + } + + + @Override + public void jjtSetParent(Node parent) { + throw new UnsupportedOperationException(); + } + + + @Override + public XmlNode jjtGetParent() { + org.w3c.dom.Node parent = node.getParentNode(); + return parent != null ? parser.wrapDomNode(parent) : null; + } + + + @Override + public void jjtAddChild(Node child, int index) { + throw new UnsupportedOperationException(); + } + + + @Override + public void jjtSetChildIndex(int index) { + throw new UnsupportedOperationException(); + } + + + @Override + public int jjtGetChildIndex() { + org.w3c.dom.Node parent = node.getParentNode(); + NodeList childNodes = parent.getChildNodes(); + for (int i = 0; i < childNodes.getLength(); i++) { + if (node == childNodes.item(i)) { + return i; + } + } + throw new IllegalStateException("This node is not a child of its parent: " + node); + } + + + @Override + public XmlNode jjtGetChild(int index) { + return parser.wrapDomNode(node.getChildNodes().item(index)); + } + + + @Override + public int jjtGetNumChildren() { + return node.hasChildNodes() ? node.getChildNodes().getLength() : 0; + } + + + @Override + public int jjtGetId() { + return 0; + } + + + @Override + public String getImage() { + return node instanceof Text ? ((Text) node).getData() : null; + } + + + @Override + public void setImage(String image) { + throw new UnsupportedOperationException(); + } + + + @Override + public boolean hasImageEqualTo(String image) { + return Objects.equals(image, getImage()); + } + + + @Override + public int getBeginLine() { + return (int) getUserData(BEGIN_LINE); + } + + + @Override + public int getBeginColumn() { + return (int) getUserData(BEGIN_COLUMN); + } + + + @Override + public int getEndLine() { + return (int) getUserData(END_LINE); + } + + + @Override + public int getEndColumn() { + return (int) getUserData(END_COLUMN); + } + + + @Override + public DataFlowNode getDataFlowNode() { + throw new UnsupportedOperationException(); + } + + + @Override + public void setDataFlowNode(DataFlowNode dataFlowNode) { + throw new UnsupportedOperationException(); + } + + + @Override + public boolean isFindBoundary() { + return false; + } + + @Override + public Document getAsDocument() { + throw new UnsupportedOperationException(); + } + + + @Override + public Object getUserData() { + return userData; + } + + + @Override + public void setUserData(Object userData) { + this.userData = userData; + } + + + @Override + public void remove() { + throw new UnsupportedOperationException(); + } + + + @Override + public void removeChildAtIndex(int childIndex) { + throw new UnsupportedOperationException(); + } + + + @Override + public String getXPathNodeName() { + return node.getNodeName().replace("#", ""); + } + + + @Override + public String toString() { + return node.getNodeName().replace("#", ""); + } + + + @Override + public Iterator<Attribute> getXPathAttributesIterator() { + List<Iterator<Attribute>> iterators = new ArrayList<>(); + + // Expose DOM Attributes + final NamedNodeMap attributes = node.getAttributes(); + iterators.add(new Iterator<Attribute>() { + private int index; + + + @Override + public boolean hasNext() { + return attributes != null && index < attributes.getLength(); + } + + + @Override + public Attribute next() { + org.w3c.dom.Node attributeNode = attributes.item(index++); + return new Attribute(XmlNodeWrapper.this, + attributeNode.getNodeName(), + attributeNode.getNodeValue()); + } + + + @Override + public void remove() { + throw new UnsupportedOperationException(); + } + }); + + // Expose Text/CDATA nodes to have an 'Image' attribute like AST Nodes + if (node instanceof Text) { + iterators.add(Collections.singletonList(new Attribute(this, "Image", ((Text) node).getData())).iterator()); + } + + // Expose Java Attributes + // iterators.add(new AttributeAxisIterator((net.sourceforge.pmd.lang.ast.Node) p)); + + @SuppressWarnings("unchecked") + Iterator<Attribute>[] it = new Iterator[iterators.size()]; + + return new CompoundIterator<>(iterators.toArray(it)); + } + + + /** + * @deprecated use {@link #getXPathAttributesIterator()} + */ + @Override + @Deprecated + public Iterator<Attribute> getAttributeIterator() { + return getXPathAttributesIterator(); + } + + + @Override + public org.w3c.dom.Node getNode() { + return node; + } +} diff --git a/pmd-xml/src/main/java/net/sourceforge/pmd/lang/xml/ast/XmlParser.java b/pmd-xml/src/main/java/net/sourceforge/pmd/lang/xml/ast/XmlParser.java index 69cb06ad966..4f0f9286aa8 100644 --- a/pmd-xml/src/main/java/net/sourceforge/pmd/lang/xml/ast/XmlParser.java +++ b/pmd-xml/src/main/java/net/sourceforge/pmd/lang/xml/ast/XmlParser.java @@ -7,12 +7,8 @@ import java.io.IOException; import java.io.Reader; import java.io.StringReader; -import java.lang.reflect.Proxy; -import java.util.Arrays; import java.util.HashMap; -import java.util.LinkedHashSet; import java.util.Map; -import java.util.Set; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; @@ -27,14 +23,17 @@ import net.sourceforge.pmd.lang.ast.RootNode; import net.sourceforge.pmd.lang.xml.XmlParserOptions; + public class XmlParser { protected final XmlParserOptions parserOptions; - protected Map<Node, XmlNode> nodeCache = new HashMap<>(); + protected Map<org.w3c.dom.Node, XmlNode> nodeCache = new HashMap<>(); + public XmlParser(XmlParserOptions parserOptions) { this.parserOptions = parserOptions; } + protected Document parseDocument(Reader reader) throws ParseException { nodeCache.clear(); try { @@ -56,44 +55,43 @@ protected Document parseDocument(Reader reader) throws ParseException { DOMLineNumbers lineNumbers = new DOMLineNumbers(document, xmlData); lineNumbers.determine(); return document; - } catch (ParserConfigurationException e) { - throw new ParseException(e); - } catch (SAXException e) { - throw new ParseException(e); - } catch (IOException e) { + } catch (ParserConfigurationException | SAXException | IOException e) { throw new ParseException(e); } } + public XmlNode parse(Reader reader) { Document document = parseDocument(reader); - return createProxy(document); + XmlNode root = new RootXmlNode(this, document); + nodeCache.put(document, root); + return root; } - public XmlNode createProxy(Node node) { - XmlNode proxy = nodeCache.get(node); - if (proxy != null) { - return proxy; - } - // TODO Change Parser interface to take ClassLoader? - LinkedHashSet<Class<?>> interfaces = new LinkedHashSet<>(); - interfaces.add(XmlNode.class); - if (node instanceof Document) { - interfaces.add(RootNode.class); + /** + * Gets the wrapper for a DOM node, implementing PMD interfaces. + * + * @param domNode The node to wrap + * + * @return The wrapper + */ + XmlNode wrapDomNode(Node domNode) { + XmlNode wrapper = nodeCache.get(domNode); + if (wrapper == null) { + wrapper = new XmlNodeWrapper(this, domNode); + nodeCache.put(domNode, wrapper); } - addAllInterfaces(interfaces, node.getClass()); - - proxy = (XmlNode) Proxy.newProxyInstance(XmlParser.class.getClassLoader(), - interfaces.toArray(new Class[interfaces.size()]), new XmlNodeInvocationHandler(this, node)); - nodeCache.put(node, proxy); - return proxy; + return wrapper; } - public void addAllInterfaces(Set<Class<?>> interfaces, Class<?> clazz) { - interfaces.addAll(Arrays.asList(clazz.getInterfaces())); - if (clazz.getSuperclass() != null) { - addAllInterfaces(interfaces, clazz.getSuperclass()); + + /** + * The root should implement {@link RootNode}. + */ + public static class RootXmlNode extends XmlNodeWrapper implements RootNode { + RootXmlNode(XmlParser parser, Node domNode) { + super(parser, domNode); } } diff --git a/pmd-xml/src/main/java/net/sourceforge/pmd/lang/xml/rule/AbstractDomXmlRule.java b/pmd-xml/src/main/java/net/sourceforge/pmd/lang/xml/rule/AbstractDomXmlRule.java index a4fe974e573..13f3ebb65a3 100644 --- a/pmd-xml/src/main/java/net/sourceforge/pmd/lang/xml/rule/AbstractDomXmlRule.java +++ b/pmd-xml/src/main/java/net/sourceforge/pmd/lang/xml/rule/AbstractDomXmlRule.java @@ -86,6 +86,7 @@ protected void visitAttributeNodes(XmlNode node, Node domNode, RuleContext ctx) } protected void visit(XmlNode node, Attr attr, RuleContext ctx) { + // does nothing by default since attributes are leaf nodes } protected void visit(XmlNode node, CharacterData characterData, RuleContext ctx) { diff --git a/pmd-xml/src/main/java/net/sourceforge/pmd/lang/xml/rule/AbstractXmlRule.java b/pmd-xml/src/main/java/net/sourceforge/pmd/lang/xml/rule/AbstractXmlRule.java index 7bd2b3fda34..0eaea880bf3 100644 --- a/pmd-xml/src/main/java/net/sourceforge/pmd/lang/xml/rule/AbstractXmlRule.java +++ b/pmd-xml/src/main/java/net/sourceforge/pmd/lang/xml/rule/AbstractXmlRule.java @@ -58,6 +58,7 @@ public ParserOptions getParserOptions() { return new XmlParserOptions(this); } + @Override public void apply(List<? extends Node> nodes, RuleContext ctx) { visitAll(nodes, ctx); } diff --git a/pmd-xml/src/main/java/net/sourceforge/pmd/lang/xml/rule/XmlRuleChainVisitor.java b/pmd-xml/src/main/java/net/sourceforge/pmd/lang/xml/rule/XmlRuleChainVisitor.java index bfed6a2e434..0962c9ebea2 100644 --- a/pmd-xml/src/main/java/net/sourceforge/pmd/lang/xml/rule/XmlRuleChainVisitor.java +++ b/pmd-xml/src/main/java/net/sourceforge/pmd/lang/xml/rule/XmlRuleChainVisitor.java @@ -16,6 +16,7 @@ public class XmlRuleChainVisitor extends AbstractRuleChainVisitor { + @Override protected void indexNodes(List<Node> nodes, RuleContext ctx) { // Visit Nodes in DFS order Stack<Node> stack = new Stack<>(); @@ -32,6 +33,7 @@ protected void indexNodes(List<Node> nodes, RuleContext ctx) { } } + @Override protected void visit(Rule rule, Node node, RuleContext ctx) { // Rule better be a XPathRule ((XPathRule) rule).evaluate(node, ctx); diff --git a/pmd-xml/src/main/java/net/sourceforge/pmd/lang/xml/rule/XmlRuleViolationFactory.java b/pmd-xml/src/main/java/net/sourceforge/pmd/lang/xml/rule/XmlRuleViolationFactory.java index 5eb52620bf0..190ec2152a3 100644 --- a/pmd-xml/src/main/java/net/sourceforge/pmd/lang/xml/rule/XmlRuleViolationFactory.java +++ b/pmd-xml/src/main/java/net/sourceforge/pmd/lang/xml/rule/XmlRuleViolationFactory.java @@ -25,6 +25,7 @@ protected RuleViolation createRuleViolation(Rule rule, RuleContext ruleContext, return new ParametricRuleViolation<>(rule, ruleContext, (XmlNode) node, message); } + @Override protected RuleViolation createRuleViolation(Rule rule, RuleContext ruleContext, Node node, String message, int beginLine, int endLine) { return null; // FIXME diff --git a/pmd-xml/src/main/resources/category/pom/bestpractices.xml b/pmd-xml/src/main/resources/category/pom/bestpractices.xml new file mode 100644 index 00000000000..58e7fd43a3d --- /dev/null +++ b/pmd-xml/src/main/resources/category/pom/bestpractices.xml @@ -0,0 +1,11 @@ +<?xml version="1.0"?> + +<ruleset name="Best Practices" + xmlns="http://pmd.sourceforge.net/ruleset/2.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd"> + + <description> +Rules which enforce generally accepted best practices. + </description> +</ruleset> diff --git a/pmd-xml/src/main/resources/category/pom/categories.properties b/pmd-xml/src/main/resources/category/pom/categories.properties new file mode 100644 index 00000000000..f0e9445d7a5 --- /dev/null +++ b/pmd-xml/src/main/resources/category/pom/categories.properties @@ -0,0 +1,18 @@ +# +# BSD-style license; for more info see http://pmd.sourceforge.net/license.html +# + +rulesets.filenames=\ + category/pom/errorprone.xml + +# +# categories without rules +# +# category/pom/bestpractices.xml +# category/pom/codestyle.xml +# category/pom/design.xml +# category/pom/documentation.xml +# category/pom/multithreading.xml +# category/pom/performance.xml +# category/pom/security.xml + diff --git a/pmd-xml/src/main/resources/category/pom/codestyle.xml b/pmd-xml/src/main/resources/category/pom/codestyle.xml new file mode 100644 index 00000000000..a161db8c963 --- /dev/null +++ b/pmd-xml/src/main/resources/category/pom/codestyle.xml @@ -0,0 +1,12 @@ +<?xml version="1.0"?> + +<ruleset name="Code Style" + xmlns="http://pmd.sourceforge.net/ruleset/2.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd"> + + <description> +Rules which enforce a specific coding style. + </description> + +</ruleset> diff --git a/pmd-xml/src/main/resources/category/pom/design.xml b/pmd-xml/src/main/resources/category/pom/design.xml new file mode 100644 index 00000000000..6833b8b773b --- /dev/null +++ b/pmd-xml/src/main/resources/category/pom/design.xml @@ -0,0 +1,11 @@ +<?xml version="1.0"?> + +<ruleset name="Design" + xmlns="http://pmd.sourceforge.net/ruleset/2.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd"> + + <description> +Rules that help you discover design issues. + </description> +</ruleset> diff --git a/pmd-xml/src/main/resources/category/pom/documentation.xml b/pmd-xml/src/main/resources/category/pom/documentation.xml new file mode 100644 index 00000000000..c049f5b6f01 --- /dev/null +++ b/pmd-xml/src/main/resources/category/pom/documentation.xml @@ -0,0 +1,11 @@ +<?xml version="1.0"?> + +<ruleset name="Documentation" + xmlns="http://pmd.sourceforge.net/ruleset/2.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd"> + + <description> +Rules that are related to code documentation. + </description> +</ruleset> diff --git a/pmd-xml/src/main/resources/category/pom/errorprone.xml b/pmd-xml/src/main/resources/category/pom/errorprone.xml new file mode 100644 index 00000000000..f3048a88840 --- /dev/null +++ b/pmd-xml/src/main/resources/category/pom/errorprone.xml @@ -0,0 +1,100 @@ +<?xml version="1.0"?> + +<ruleset name="Error Prone" + xmlns="http://pmd.sourceforge.net/ruleset/2.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd"> + + <description> +Rules to detect constructs that are either broken, extremely confusing or prone to runtime errors. + </description> + + <rule name="InvalidDependencyTypes" + language="pom" + since="5.4" + message="By default, Maven only recognizes the following types: pom, jar, maven-plugin, ejb, war, ear, rar, par." + class="net.sourceforge.pmd.lang.rule.XPathRule" + externalInfoUrl="${pmd.website.baseurl}/pmd_rules_pom_errorprone.html#invaliddependencytypes"> + <description> +If you use an invalid dependency type in the dependency management section, Maven doesn't fail. Instead, +the entry is just ignored, which might have the effect, that the wrong version of the dependency is used. + +The following types are considered valid: pom, jar, maven-plugin, ejb, war, ear, rar, par. + </description> + <priority>3</priority> + <properties> + <property name="xpath"> + <value> +<![CDATA[ +//dependencyManagement/dependency/type/text[not(@Image = $validTypes)] +]]> + </value> + </property> + <!-- XPath 2.0 is needed to use a multi-valued property. --> + <property name="version" value="2.0"/> + <property name="validTypes" + value="pom,jar,maven-plugin,ejb,war,ear,rar,par" + type="List[String]" + delimiter="," + description="Set of valid types."/> + </properties> + <example> +<![CDATA[ +<project...> + ... + <dependencyManagement> + ... + <dependency> + <groupId>org.jboss.arquillian</groupId> + <artifactId>arquillian-bom</artifactId> + <version>${arquillian.version}</version> + <type>bom</type> <!-- not a valid type ! 'pom' is ! --> + <scope>import</scope> + </dependency> + ... + </dependencyManagement> +</project> + ]]> + </example> + </rule> + + <rule name="ProjectVersionAsDependencyVersion" + language="pom" + since="5.4" + message="Do not use project.version to express a dependency version." + class="net.sourceforge.pmd.lang.rule.XPathRule" + externalInfoUrl="${pmd.website.baseurl}/pmd_rules_pom_errorprone.html#projectversionasdependencyversion"> + <description> +Using that expression in dependency declarations seems like a shortcut, but it can go wrong. +By far the most common problem is the use of ${project.version} in a BOM or parent POM. + </description> + <priority>3</priority> + <properties> + <property name="xpath"> + <value> +<![CDATA[ +//dependencies/dependency + [contains(version/text/@Image,'{project.version}')] + [ + (/project/parent/groupId and groupId/text/@Image != /project/parent/groupId/text/@Image) + or + (/project/groupId and groupId/text/@Image != /project/groupId/text/@Image) + ]/version +]]> + </value> + </property> + </properties> + <example> +<![CDATA[ +<project...> + ... + <dependency> + ... + <version>${project.dependency}</version> + </dependency> +</project> +]]> + </example> + </rule> + +</ruleset> diff --git a/pmd-xml/src/main/resources/category/pom/multithreading.xml b/pmd-xml/src/main/resources/category/pom/multithreading.xml new file mode 100644 index 00000000000..88c22dfc8bc --- /dev/null +++ b/pmd-xml/src/main/resources/category/pom/multithreading.xml @@ -0,0 +1,11 @@ +<?xml version="1.0"?> + +<ruleset name="Multithreading" + xmlns="http://pmd.sourceforge.net/ruleset/2.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd"> + + <description> +Rules that flag issues when dealing with multiple threads of execution. + </description> +</ruleset> diff --git a/pmd-xml/src/main/resources/category/pom/performance.xml b/pmd-xml/src/main/resources/category/pom/performance.xml new file mode 100644 index 00000000000..251663aafc1 --- /dev/null +++ b/pmd-xml/src/main/resources/category/pom/performance.xml @@ -0,0 +1,11 @@ +<?xml version="1.0"?> + +<ruleset name="Performance" + xmlns="http://pmd.sourceforge.net/ruleset/2.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd"> + + <description> +Rules that flag suboptimal code. + </description> +</ruleset> diff --git a/pmd-xml/src/main/resources/category/pom/security.xml b/pmd-xml/src/main/resources/category/pom/security.xml new file mode 100644 index 00000000000..d6fa5415a54 --- /dev/null +++ b/pmd-xml/src/main/resources/category/pom/security.xml @@ -0,0 +1,11 @@ +<?xml version="1.0"?> + +<ruleset name="Security" + xmlns="http://pmd.sourceforge.net/ruleset/2.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd"> + + <description> +Rules that flag potential security flaws. + </description> +</ruleset> diff --git a/pmd-xml/src/main/resources/category/wsdl/bestpractices.xml b/pmd-xml/src/main/resources/category/wsdl/bestpractices.xml new file mode 100644 index 00000000000..58e7fd43a3d --- /dev/null +++ b/pmd-xml/src/main/resources/category/wsdl/bestpractices.xml @@ -0,0 +1,11 @@ +<?xml version="1.0"?> + +<ruleset name="Best Practices" + xmlns="http://pmd.sourceforge.net/ruleset/2.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd"> + + <description> +Rules which enforce generally accepted best practices. + </description> +</ruleset> diff --git a/pmd-xml/src/main/resources/category/wsdl/categories.properties b/pmd-xml/src/main/resources/category/wsdl/categories.properties new file mode 100644 index 00000000000..28b33d4e5e5 --- /dev/null +++ b/pmd-xml/src/main/resources/category/wsdl/categories.properties @@ -0,0 +1,18 @@ +# +# BSD-style license; for more info see http://pmd.sourceforge.net/license.html +# + +rulesets.filenames= + +# +# categories without rules +# +# category/wsdl/bestpractices.xml +# category/wsdl/codestyle.xml +# category/wsdl/design.xml +# category/wsdl/documentation.xml +# category/wsdl/errorprone.xml +# category/wsdl/multithreading.xml +# category/wsdl/performance.xml +# category/wsdl/security.xml + diff --git a/pmd-xml/src/main/resources/category/wsdl/codestyle.xml b/pmd-xml/src/main/resources/category/wsdl/codestyle.xml new file mode 100644 index 00000000000..c9a6b51d10e --- /dev/null +++ b/pmd-xml/src/main/resources/category/wsdl/codestyle.xml @@ -0,0 +1,11 @@ +<?xml version="1.0"?> + +<ruleset name="Code Style" + xmlns="http://pmd.sourceforge.net/ruleset/2.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd"> + + <description> +Rules which enforce a specific coding style. + </description> +</ruleset> diff --git a/pmd-xml/src/main/resources/category/wsdl/design.xml b/pmd-xml/src/main/resources/category/wsdl/design.xml new file mode 100644 index 00000000000..6833b8b773b --- /dev/null +++ b/pmd-xml/src/main/resources/category/wsdl/design.xml @@ -0,0 +1,11 @@ +<?xml version="1.0"?> + +<ruleset name="Design" + xmlns="http://pmd.sourceforge.net/ruleset/2.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd"> + + <description> +Rules that help you discover design issues. + </description> +</ruleset> diff --git a/pmd-xml/src/main/resources/category/wsdl/documentation.xml b/pmd-xml/src/main/resources/category/wsdl/documentation.xml new file mode 100644 index 00000000000..c049f5b6f01 --- /dev/null +++ b/pmd-xml/src/main/resources/category/wsdl/documentation.xml @@ -0,0 +1,11 @@ +<?xml version="1.0"?> + +<ruleset name="Documentation" + xmlns="http://pmd.sourceforge.net/ruleset/2.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd"> + + <description> +Rules that are related to code documentation. + </description> +</ruleset> diff --git a/pmd-xml/src/main/resources/category/wsdl/errorprone.xml b/pmd-xml/src/main/resources/category/wsdl/errorprone.xml new file mode 100644 index 00000000000..d3d13d8c34c --- /dev/null +++ b/pmd-xml/src/main/resources/category/wsdl/errorprone.xml @@ -0,0 +1,11 @@ +<?xml version="1.0"?> + +<ruleset name="Error Prone" + xmlns="http://pmd.sourceforge.net/ruleset/2.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd"> + + <description> +Rules to detect constructs that are either broken, extremely confusing or prone to runtime errors. + </description> +</ruleset> diff --git a/pmd-xml/src/main/resources/category/wsdl/multithreading.xml b/pmd-xml/src/main/resources/category/wsdl/multithreading.xml new file mode 100644 index 00000000000..88c22dfc8bc --- /dev/null +++ b/pmd-xml/src/main/resources/category/wsdl/multithreading.xml @@ -0,0 +1,11 @@ +<?xml version="1.0"?> + +<ruleset name="Multithreading" + xmlns="http://pmd.sourceforge.net/ruleset/2.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd"> + + <description> +Rules that flag issues when dealing with multiple threads of execution. + </description> +</ruleset> diff --git a/pmd-xml/src/main/resources/category/wsdl/performance.xml b/pmd-xml/src/main/resources/category/wsdl/performance.xml new file mode 100644 index 00000000000..251663aafc1 --- /dev/null +++ b/pmd-xml/src/main/resources/category/wsdl/performance.xml @@ -0,0 +1,11 @@ +<?xml version="1.0"?> + +<ruleset name="Performance" + xmlns="http://pmd.sourceforge.net/ruleset/2.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd"> + + <description> +Rules that flag suboptimal code. + </description> +</ruleset> diff --git a/pmd-xml/src/main/resources/category/wsdl/security.xml b/pmd-xml/src/main/resources/category/wsdl/security.xml new file mode 100644 index 00000000000..d6fa5415a54 --- /dev/null +++ b/pmd-xml/src/main/resources/category/wsdl/security.xml @@ -0,0 +1,11 @@ +<?xml version="1.0"?> + +<ruleset name="Security" + xmlns="http://pmd.sourceforge.net/ruleset/2.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd"> + + <description> +Rules that flag potential security flaws. + </description> +</ruleset> diff --git a/pmd-xml/src/main/resources/category/xml/bestpractices.xml b/pmd-xml/src/main/resources/category/xml/bestpractices.xml new file mode 100644 index 00000000000..58e7fd43a3d --- /dev/null +++ b/pmd-xml/src/main/resources/category/xml/bestpractices.xml @@ -0,0 +1,11 @@ +<?xml version="1.0"?> + +<ruleset name="Best Practices" + xmlns="http://pmd.sourceforge.net/ruleset/2.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd"> + + <description> +Rules which enforce generally accepted best practices. + </description> +</ruleset> diff --git a/pmd-xml/src/main/resources/category/xml/categories.properties b/pmd-xml/src/main/resources/category/xml/categories.properties new file mode 100644 index 00000000000..5fbda38080c --- /dev/null +++ b/pmd-xml/src/main/resources/category/xml/categories.properties @@ -0,0 +1,18 @@ +# +# BSD-style license; for more info see http://pmd.sourceforge.net/license.html +# + +rulesets.filenames=\ + category/xml/errorprone.xml + +# +# categories without rules +# +# category/xml/bestpractices.xml +# category/xml/codestyle.xml +# category/xml/design.xml +# category/xml/documentation.xml +# category/xml/multithreading.xml +# category/xml/performance.xml +# category/xml/security.xml + diff --git a/pmd-xml/src/main/resources/category/xml/codestyle.xml b/pmd-xml/src/main/resources/category/xml/codestyle.xml new file mode 100644 index 00000000000..c9a6b51d10e --- /dev/null +++ b/pmd-xml/src/main/resources/category/xml/codestyle.xml @@ -0,0 +1,11 @@ +<?xml version="1.0"?> + +<ruleset name="Code Style" + xmlns="http://pmd.sourceforge.net/ruleset/2.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd"> + + <description> +Rules which enforce a specific coding style. + </description> +</ruleset> diff --git a/pmd-xml/src/main/resources/category/xml/design.xml b/pmd-xml/src/main/resources/category/xml/design.xml new file mode 100644 index 00000000000..6833b8b773b --- /dev/null +++ b/pmd-xml/src/main/resources/category/xml/design.xml @@ -0,0 +1,11 @@ +<?xml version="1.0"?> + +<ruleset name="Design" + xmlns="http://pmd.sourceforge.net/ruleset/2.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd"> + + <description> +Rules that help you discover design issues. + </description> +</ruleset> diff --git a/pmd-xml/src/main/resources/category/xml/documentation.xml b/pmd-xml/src/main/resources/category/xml/documentation.xml new file mode 100644 index 00000000000..c049f5b6f01 --- /dev/null +++ b/pmd-xml/src/main/resources/category/xml/documentation.xml @@ -0,0 +1,11 @@ +<?xml version="1.0"?> + +<ruleset name="Documentation" + xmlns="http://pmd.sourceforge.net/ruleset/2.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd"> + + <description> +Rules that are related to code documentation. + </description> +</ruleset> diff --git a/pmd-xml/src/main/resources/category/xml/errorprone.xml b/pmd-xml/src/main/resources/category/xml/errorprone.xml new file mode 100644 index 00000000000..18c01924d6d --- /dev/null +++ b/pmd-xml/src/main/resources/category/xml/errorprone.xml @@ -0,0 +1,38 @@ +<?xml version="1.0"?> + +<ruleset name="Error Prone" + xmlns="http://pmd.sourceforge.net/ruleset/2.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd"> + + <description> +Rules to detect constructs that are either broken, extremely confusing or prone to runtime errors. + </description> + + <rule name="MistypedCDATASection" + language="xml" + since="5.0" + message="Potentialy mistyped CDATA section with extra [ at beginning or ] at the end." + class="net.sourceforge.pmd.lang.rule.XPathRule" + externalInfoUrl="${pmd.website.baseurl}/pmd_rules_xml_errorprone.html#mistypedcdatasection"> + <description> +An XML CDATA section begins with a <!CDATA[ marker, which has only one [, and ends with a ]]> marker, which has only two ]. + </description> + <priority>3</priority> + <properties> + <property name="xpath"> + <value> +<![CDATA[ +//cdata-section[starts-with(@Image,'[') or ends-with(@Image,']')] +]]> + </value> + </property> + </properties> + <example> +<![CDATA[ +An extra [ looks like <!CDATA[[]]>, and an extra ] looks like <!CDATA[]]]>. +]]> + </example> + </rule> + +</ruleset> diff --git a/pmd-xml/src/main/resources/category/xml/multithreading.xml b/pmd-xml/src/main/resources/category/xml/multithreading.xml new file mode 100644 index 00000000000..88c22dfc8bc --- /dev/null +++ b/pmd-xml/src/main/resources/category/xml/multithreading.xml @@ -0,0 +1,11 @@ +<?xml version="1.0"?> + +<ruleset name="Multithreading" + xmlns="http://pmd.sourceforge.net/ruleset/2.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd"> + + <description> +Rules that flag issues when dealing with multiple threads of execution. + </description> +</ruleset> diff --git a/pmd-xml/src/main/resources/category/xml/performance.xml b/pmd-xml/src/main/resources/category/xml/performance.xml new file mode 100644 index 00000000000..251663aafc1 --- /dev/null +++ b/pmd-xml/src/main/resources/category/xml/performance.xml @@ -0,0 +1,11 @@ +<?xml version="1.0"?> + +<ruleset name="Performance" + xmlns="http://pmd.sourceforge.net/ruleset/2.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd"> + + <description> +Rules that flag suboptimal code. + </description> +</ruleset> diff --git a/pmd-xml/src/main/resources/category/xml/security.xml b/pmd-xml/src/main/resources/category/xml/security.xml new file mode 100644 index 00000000000..d6fa5415a54 --- /dev/null +++ b/pmd-xml/src/main/resources/category/xml/security.xml @@ -0,0 +1,11 @@ +<?xml version="1.0"?> + +<ruleset name="Security" + xmlns="http://pmd.sourceforge.net/ruleset/2.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd"> + + <description> +Rules that flag potential security flaws. + </description> +</ruleset> diff --git a/pmd-xml/src/main/resources/category/xsl/bestpractices.xml b/pmd-xml/src/main/resources/category/xsl/bestpractices.xml new file mode 100644 index 00000000000..58e7fd43a3d --- /dev/null +++ b/pmd-xml/src/main/resources/category/xsl/bestpractices.xml @@ -0,0 +1,11 @@ +<?xml version="1.0"?> + +<ruleset name="Best Practices" + xmlns="http://pmd.sourceforge.net/ruleset/2.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd"> + + <description> +Rules which enforce generally accepted best practices. + </description> +</ruleset> diff --git a/pmd-xml/src/main/resources/category/xsl/categories.properties b/pmd-xml/src/main/resources/category/xsl/categories.properties new file mode 100644 index 00000000000..a6c028c2511 --- /dev/null +++ b/pmd-xml/src/main/resources/category/xsl/categories.properties @@ -0,0 +1,18 @@ +# +# BSD-style license; for more info see http://pmd.sourceforge.net/license.html +# + +rulesets.filenames=\ + category/xsl/codestyle.xml,\ + category/xsl/performance.xml + +# +# categories without rules +# +# category/xsl/bestpractices.xml +# category/xsl/design.xml +# category/xsl/documentation.xml +# category/xsl/errorprone.xml +# category/xsl/multithreading.xml +# category/xsl/security.xml + diff --git a/pmd-xml/src/main/resources/category/xsl/codestyle.xml b/pmd-xml/src/main/resources/category/xsl/codestyle.xml new file mode 100644 index 00000000000..fd87bfc1ff8 --- /dev/null +++ b/pmd-xml/src/main/resources/category/xsl/codestyle.xml @@ -0,0 +1,43 @@ +<?xml version="1.0"?> + +<ruleset name="Code Style" + xmlns="http://pmd.sourceforge.net/ruleset/2.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd"> + + <description> +Rules which enforce a specific coding style. + </description> + + <rule name="UseConcatOnce" + language="xsl" + since="5.0" + message="The xpath concat() function accepts as many arguments as required, you may be able to factorize this expression" + class="net.sourceforge.pmd.lang.rule.XPathRule" + externalInfoUrl="${pmd.website.baseurl}/pmd_rules_xsl_codestyle.html#useconcatonce"> + <description> +The XPath concat() functions accepts as many arguments as required so you can have +"concat($a,'b',$c)" rather than "concat($a,concat('b',$c)". + </description> + <priority>3</priority> + <properties> + <property name="xpath"> +<!-- the use of node() allow to target any node with an select attribute + but also workaround the ambiguity of having the xsl namespace in the + node name.--> + <value> +<![CDATA[ +//node()[contains(substring-after(@select,'concat'),'concat')] +]]> + </value> + </property> + </properties> + <example> +<![CDATA[ +<xsl:variable name="var" select="concat("Welcome",concat("to you ",$name))"/> +<xsl:variable name="var" select="concat("Welcome","to you ",$name))"> +]]> + </example> + </rule> + +</ruleset> diff --git a/pmd-xml/src/main/resources/category/xsl/design.xml b/pmd-xml/src/main/resources/category/xsl/design.xml new file mode 100644 index 00000000000..6833b8b773b --- /dev/null +++ b/pmd-xml/src/main/resources/category/xsl/design.xml @@ -0,0 +1,11 @@ +<?xml version="1.0"?> + +<ruleset name="Design" + xmlns="http://pmd.sourceforge.net/ruleset/2.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd"> + + <description> +Rules that help you discover design issues. + </description> +</ruleset> diff --git a/pmd-xml/src/main/resources/category/xsl/documentation.xml b/pmd-xml/src/main/resources/category/xsl/documentation.xml new file mode 100644 index 00000000000..c049f5b6f01 --- /dev/null +++ b/pmd-xml/src/main/resources/category/xsl/documentation.xml @@ -0,0 +1,11 @@ +<?xml version="1.0"?> + +<ruleset name="Documentation" + xmlns="http://pmd.sourceforge.net/ruleset/2.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd"> + + <description> +Rules that are related to code documentation. + </description> +</ruleset> diff --git a/pmd-xml/src/main/resources/category/xsl/errorprone.xml b/pmd-xml/src/main/resources/category/xsl/errorprone.xml new file mode 100644 index 00000000000..d3d13d8c34c --- /dev/null +++ b/pmd-xml/src/main/resources/category/xsl/errorprone.xml @@ -0,0 +1,11 @@ +<?xml version="1.0"?> + +<ruleset name="Error Prone" + xmlns="http://pmd.sourceforge.net/ruleset/2.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd"> + + <description> +Rules to detect constructs that are either broken, extremely confusing or prone to runtime errors. + </description> +</ruleset> diff --git a/pmd-xml/src/main/resources/category/xsl/multithreading.xml b/pmd-xml/src/main/resources/category/xsl/multithreading.xml new file mode 100644 index 00000000000..88c22dfc8bc --- /dev/null +++ b/pmd-xml/src/main/resources/category/xsl/multithreading.xml @@ -0,0 +1,11 @@ +<?xml version="1.0"?> + +<ruleset name="Multithreading" + xmlns="http://pmd.sourceforge.net/ruleset/2.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd"> + + <description> +Rules that flag issues when dealing with multiple threads of execution. + </description> +</ruleset> diff --git a/pmd-xml/src/main/resources/category/xsl/performance.xml b/pmd-xml/src/main/resources/category/xsl/performance.xml new file mode 100644 index 00000000000..93180bb0718 --- /dev/null +++ b/pmd-xml/src/main/resources/category/xsl/performance.xml @@ -0,0 +1,58 @@ +<?xml version="1.0"?> + +<ruleset name="Performance" + xmlns="http://pmd.sourceforge.net/ruleset/2.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd"> + + <description> +Rules that flag suboptimal code. + </description> + + <rule name="AvoidAxisNavigation" + language="xsl" + since="5.0" + message="Axis navigation has the largest impact when writing an XPath query." + class="net.sourceforge.pmd.lang.rule.XPathRule" + externalInfoUrl="${pmd.website.baseurl}/pmd_rules_xsl_performance.html#avoidaxisnavigation"> + <description> +Avoid using the 'following' or 'preceeding' axes whenever possible, as these can cut +through 100% of the document in the worst case. Also, try to avoid using 'descendant' +or 'descendant-self' axes, as if you're at the top of the Document, it necessarily means +cutting through 100% of the document. + </description> + <priority>3</priority> + <properties> + <property name="xpath"> +<!-- the use of node() allow to target any node with an select attribute + but also workaround the ambiguity of having the xsl namespace in the + node name.--> + <value> +<![CDATA[ +//node()[ + contains(@select,'preceeding::') + or + contains(@select,'following::') + or + contains(@select,'descendant::') + or + contains(@select,'descendant-self::') + or ( + ($checkSelfDescendantAbreviation = 'true' ) + and + contains(@select,'//') + ) +] +]]> + </value> + </property> + <property name="checkSelfDescendantAbreviation" type="Boolean" description="descendant::self abreviation, '//', will also trigger this rule." value="false"/> + </properties> + <example> +<![CDATA[ +<xsl:variable name="var" select="//item/descendant::child"/> +]]> + </example> + </rule> + +</ruleset> diff --git a/pmd-xml/src/main/resources/category/xsl/security.xml b/pmd-xml/src/main/resources/category/xsl/security.xml new file mode 100644 index 00000000000..d6fa5415a54 --- /dev/null +++ b/pmd-xml/src/main/resources/category/xsl/security.xml @@ -0,0 +1,11 @@ +<?xml version="1.0"?> + +<ruleset name="Security" + xmlns="http://pmd.sourceforge.net/ruleset/2.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd"> + + <description> +Rules that flag potential security flaws. + </description> +</ruleset> diff --git a/pmd-xml/src/main/resources/rulesets/pom/basic.xml b/pmd-xml/src/main/resources/rulesets/pom/basic.xml index 2a56620ebd9..1083938e834 100644 --- a/pmd-xml/src/main/resources/rulesets/pom/basic.xml +++ b/pmd-xml/src/main/resources/rulesets/pom/basic.xml @@ -3,84 +3,12 @@ <ruleset name="Basic POM" xmlns="http://pmd.sourceforge.net/ruleset/2.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 http://pmd.sourceforge.net/ruleset_2_0_0.xsd"> + xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd"> <description> The Basic POM Ruleset contains a collection of good practices regarding Maven's POM files. </description> - <rule name="ProjectVersionAsDependencyVersion" - language="pom" - since="5.4" - message="Do not use project's version to express a dependency's version." - class="net.sourceforge.pmd.lang.rule.XPathRule" - externalInfoUrl="${pmd.website.baseurl}/pmd_rules_pom_basic.html#projectversionasdependencyversion"> - <description> -Using that expression in dependency declarations seems like a shortcut, but it can go wrong. -By far the most common problem is the use of ${project.version} in a BOM or parent POM. - </description> - <priority>3</priority> - <properties> - <property name="xpath"> - <value> -<![CDATA[ -//dependency/version/text[contains(@Image,'{project.version}')] -]]> - </value> - </property> - </properties> - <example> -<![CDATA[ -<project...> - ... - <dependency> - ... - <version>${project.dependency}</version> - </dependency> -</project> -]]> - </example> - </rule> - - <rule name="InvalidDependencyTypes" - language="pom" - since="5.4" - message="By default, Maven only recognized the following types: $validTypes." - class="net.sourceforge.pmd.lang.rule.XPathRule" - externalInfoUrl="${pmd.website.baseurl}/pmd_rules_pom_basic.html#invaliddependencytypes"> - <description> -While Maven will not failed if you use an invalid type for a dependency in the -dependency management section, it will not also uses the dependency. - </description> - <priority>3</priority> - <properties> - <property name="xpath"> - <value> -<![CDATA[ -//dependencyManagement/dependency/type/text[not(contains('pom, jar, maven-plugin, ejb, war, ear, rar, par',@Image))] -]]> - </value> - </property> - <!-- FIXME: property injection seems to be failing with XML.... --> - <!-- property name="validTypes" value="pom, jar, maven-plugin, ejb, war, ear, rar, par" description="Set of valid types."--> - </properties> - <example> -<![CDATA[ -<project...> - ... - <dependencyManagement> - ... - <dependency> - <groupId>org.jboss.arquillian</groupId> - <artifactId>arquillian-bom</artifactId> - <version>${arquillian.version}</version> - <type>bom</type> <!-- not a valid type ! 'pom' is ! --> - <scope>import</scope> - </dependency> - ... - </dependencyManagement> -</project> - ]]> - </example> - </rule> + <rule ref="category/pom/errorprone.xml/InvalidDependencyTypes" deprecated="true" /> + <rule ref="category/pom/errorprone.xml/ProjectVersionAsDependencyVersion" deprecated="true" /> </ruleset> diff --git a/pmd-xml/src/main/resources/rulesets/pom/rulesets.properties b/pmd-xml/src/main/resources/rulesets/pom/rulesets.properties index c5f6f4b8a2e..f0e9445d7a5 100644 --- a/pmd-xml/src/main/resources/rulesets/pom/rulesets.properties +++ b/pmd-xml/src/main/resources/rulesets/pom/rulesets.properties @@ -2,4 +2,17 @@ # BSD-style license; for more info see http://pmd.sourceforge.net/license.html # -rulesets.filenames=rulesets/pom/basic.xml +rulesets.filenames=\ + category/pom/errorprone.xml + +# +# categories without rules +# +# category/pom/bestpractices.xml +# category/pom/codestyle.xml +# category/pom/design.xml +# category/pom/documentation.xml +# category/pom/multithreading.xml +# category/pom/performance.xml +# category/pom/security.xml + diff --git a/pmd-xml/src/main/resources/rulesets/wsdl/rulesets.properties b/pmd-xml/src/main/resources/rulesets/wsdl/rulesets.properties index c7514b5edc6..28b33d4e5e5 100644 --- a/pmd-xml/src/main/resources/rulesets/wsdl/rulesets.properties +++ b/pmd-xml/src/main/resources/rulesets/wsdl/rulesets.properties @@ -3,3 +3,16 @@ # rulesets.filenames= + +# +# categories without rules +# +# category/wsdl/bestpractices.xml +# category/wsdl/codestyle.xml +# category/wsdl/design.xml +# category/wsdl/documentation.xml +# category/wsdl/errorprone.xml +# category/wsdl/multithreading.xml +# category/wsdl/performance.xml +# category/wsdl/security.xml + diff --git a/pmd-xml/src/main/resources/rulesets/xml/basic.xml b/pmd-xml/src/main/resources/rulesets/xml/basic.xml index f847520c7cd..15b7e2249ea 100644 --- a/pmd-xml/src/main/resources/rulesets/xml/basic.xml +++ b/pmd-xml/src/main/resources/rulesets/xml/basic.xml @@ -3,35 +3,11 @@ <ruleset name="Basic XML" xmlns="http://pmd.sourceforge.net/ruleset/2.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 http://pmd.sourceforge.net/ruleset_2_0_0.xsd"> + xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd"> <description> The Basic XML Ruleset contains a collection of good practices which everyone should follow. </description> - <rule name="MistypedCDATASection" - language="xml" - since="5.0" - message="Potentialy mistyped CDATA section with extra [ at beginning or ] at the end." - class="net.sourceforge.pmd.lang.rule.XPathRule" - externalInfoUrl="${pmd.website.baseurl}/pmd_rules_xml_basic.html#mistypedcdatasection"> - <description> -An XML CDATA section begins with a <!CDATA[ marker, which has only one [, and ends with a ]]> marker, which has only two ]. - </description> - <priority>3</priority> - <properties> - <property name="xpath"> - <value> -<![CDATA[ -//cdata-section[starts-with(@Image,'[') or ends-with(@Image,']')] -]]> - </value> - </property> - </properties> - <example> -<![CDATA[ -An extra [ looks like <!CDATA[[]]>, and an extra ] looks like <!CDATA[]]]>. -]]> - </example> - </rule> + <rule ref="category/xml/errorprone.xml/MistypedCDATASection" deprecated="true" /> </ruleset> diff --git a/pmd-xml/src/main/resources/rulesets/xml/rulesets.properties b/pmd-xml/src/main/resources/rulesets/xml/rulesets.properties index a19ec1e61e9..5fbda38080c 100644 --- a/pmd-xml/src/main/resources/rulesets/xml/rulesets.properties +++ b/pmd-xml/src/main/resources/rulesets/xml/rulesets.properties @@ -2,4 +2,17 @@ # BSD-style license; for more info see http://pmd.sourceforge.net/license.html # -rulesets.filenames=rulesets/xml/basic.xml +rulesets.filenames=\ + category/xml/errorprone.xml + +# +# categories without rules +# +# category/xml/bestpractices.xml +# category/xml/codestyle.xml +# category/xml/design.xml +# category/xml/documentation.xml +# category/xml/multithreading.xml +# category/xml/performance.xml +# category/xml/security.xml + diff --git a/pmd-xml/src/main/resources/rulesets/xsl/rulesets.properties b/pmd-xml/src/main/resources/rulesets/xsl/rulesets.properties index 89e311fb031..a6c028c2511 100644 --- a/pmd-xml/src/main/resources/rulesets/xsl/rulesets.properties +++ b/pmd-xml/src/main/resources/rulesets/xsl/rulesets.properties @@ -2,4 +2,17 @@ # BSD-style license; for more info see http://pmd.sourceforge.net/license.html # -rulesets.filenames=rulesets/xsl/xpath.xml +rulesets.filenames=\ + category/xsl/codestyle.xml,\ + category/xsl/performance.xml + +# +# categories without rules +# +# category/xsl/bestpractices.xml +# category/xsl/design.xml +# category/xsl/documentation.xml +# category/xsl/errorprone.xml +# category/xsl/multithreading.xml +# category/xsl/security.xml + diff --git a/pmd-xml/src/main/resources/rulesets/xsl/xpath.xml b/pmd-xml/src/main/resources/rulesets/xsl/xpath.xml index c075ceb4e24..4f88172dbea 100644 --- a/pmd-xml/src/main/resources/rulesets/xsl/xpath.xml +++ b/pmd-xml/src/main/resources/rulesets/xsl/xpath.xml @@ -3,86 +3,12 @@ <ruleset name="XPath in XSL" xmlns="http://pmd.sourceforge.net/ruleset/2.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 http://pmd.sourceforge.net/ruleset_2_0_0.xsd"> + xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd"> <description> This ruleset regroups a collection of good practices regarding XPath querying and functions inside an XSL. </description> - <rule name="UseConcatOnce" - language="xsl" - since="5.0" - message="The xpath concat() function accepts as many arguments as required, you may be able to factorize this expression" - class="net.sourceforge.pmd.lang.rule.XPathRule" - externalInfoUrl="${pmd.website.baseurl}/pmd_rules_xsl_xpath.html#useconcatonce"> - <description> -The XPath concat() functions accepts as many arguments as required so you can have -"concat($a,'b',$c)" rather than "concat($a,concat('b',$c)". - </description> - <priority>3</priority> - <properties> - <property name="xpath"> -<!-- the use of node() allow to target any node with an select attribute - but also workaround the ambiguity of having the xsl namespace in the - node name.--> - <value> -<![CDATA[ -//node()[contains(substring-after(@select,'concat'),'concat')] -]]> - </value> - </property> - </properties> - <example> -<![CDATA[ -<xsl:variable name="var" select="concat("Welcome",concat("to you ",$name))"/> -<xsl:variable name="var" select="concat("Welcome","to you ",$name))"> -]]> - </example> - </rule> - - <rule name="AvoidAxisNavigation" - language="xsl" - since="5.0" - message="Axis navigation has the largest impact when writing an XPath query." - class="net.sourceforge.pmd.lang.rule.XPathRule" - externalInfoUrl="${pmd.website.baseurl}/pmd_rules_xsl_xpath.html#avoidaxisnavigation"> - <description> -Avoid using the 'following' or 'preceeding' axes whenever possible, as these can cut -through 100% of the document in the worst case. Also, try to avoid using 'descendant' -or 'descendant-self' axes, as if you're at the top of the Document, it necessarily means -cutting through 100% of the document. - </description> - <priority>3</priority> - <properties> - <property name="xpath"> -<!-- the use of node() allow to target any node with an select attribute - but also workaround the ambiguity of having the xsl namespace in the - node name.--> - <value> -<![CDATA[ -//node()[ - contains(@select,'preceeding::') - or - contains(@select,'following::') - or - contains(@select,'descendant::') - or - contains(@select,'descendant-self::') - or ( - ($checkSelfDescendantAbreviation = 'true' ) - and - contains(@select,'//') - ) -] -]]> - </value> - </property> - <property name="checkSelfDescendantAbreviation" type="Boolean" description="descendant::self abreviation, '//', will also trigger this rule." value="false"/> - </properties> - <example> -<![CDATA[ -<xsl:variable name="var" select="//item/descendant::child"/> -]]> - </example> - </rule> + <rule ref="category/xsl/codestyle.xml/UseConcatOnce" deprecated="true" /> + <rule ref="category/xsl/performance.xml/AvoidAxisNavigation" deprecated="true" /> </ruleset> diff --git a/pmd-xml/src/test/java/net/sourceforge/pmd/lang/pom/rule/basic/BasicRulesTest.java b/pmd-xml/src/test/java/net/sourceforge/pmd/lang/pom/rule/basic/BasicRulesTest.java deleted file mode 100644 index c917dbbff89..00000000000 --- a/pmd-xml/src/test/java/net/sourceforge/pmd/lang/pom/rule/basic/BasicRulesTest.java +++ /dev/null @@ -1,18 +0,0 @@ -/** - * BSD-style license; for more info see http://pmd.sourceforge.net/license.html - */ - -package net.sourceforge.pmd.lang.pom.rule.basic; - -import net.sourceforge.pmd.testframework.SimpleAggregatorTst; - -public class BasicRulesTest extends SimpleAggregatorTst { - - private static final String RULESET = "pom-basic"; - - @Override - public void setUp() { - addRule(RULESET, "ProjectVersionAsDependencyVersion"); - addRule(RULESET, "InvalidDependencyTypes"); - } -} diff --git a/pmd-xml/src/test/java/net/sourceforge/pmd/lang/pom/rule/errorprone/InvalidDependencyTypesTest.java b/pmd-xml/src/test/java/net/sourceforge/pmd/lang/pom/rule/errorprone/InvalidDependencyTypesTest.java new file mode 100644 index 00000000000..2e749236edc --- /dev/null +++ b/pmd-xml/src/test/java/net/sourceforge/pmd/lang/pom/rule/errorprone/InvalidDependencyTypesTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.pom.rule.errorprone; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class InvalidDependencyTypesTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-xml/src/test/java/net/sourceforge/pmd/lang/pom/rule/errorprone/ProjectVersionAsDependencyVersionTest.java b/pmd-xml/src/test/java/net/sourceforge/pmd/lang/pom/rule/errorprone/ProjectVersionAsDependencyVersionTest.java new file mode 100644 index 00000000000..c7aa9995252 --- /dev/null +++ b/pmd-xml/src/test/java/net/sourceforge/pmd/lang/pom/rule/errorprone/ProjectVersionAsDependencyVersionTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.pom.rule.errorprone; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class ProjectVersionAsDependencyVersionTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-xml/src/test/java/net/sourceforge/pmd/lang/xml/XmlParserTest.java b/pmd-xml/src/test/java/net/sourceforge/pmd/lang/xml/XmlParserTest.java index 9513d773c0c..cadd18a1459 100644 --- a/pmd-xml/src/test/java/net/sourceforge/pmd/lang/xml/XmlParserTest.java +++ b/pmd-xml/src/test/java/net/sourceforge/pmd/lang/xml/XmlParserTest.java @@ -10,6 +10,7 @@ import java.io.PrintStream; import java.io.StringReader; import java.io.UnsupportedEncodingException; +import java.nio.charset.StandardCharsets; import java.util.Iterator; import java.util.Locale; @@ -366,17 +367,23 @@ private Node parseXml(String xml) { LanguageVersionHandler xmlVersionHandler = LanguageRegistry.getLanguage(XmlLanguageModule.NAME).getDefaultVersion().getLanguageVersionHandler(); XmlParserOptions options = (XmlParserOptions) xmlVersionHandler.getDefaultParserOptions(); Parser parser = xmlVersionHandler.getParser(options); - Node document = parser.parse(null, new StringReader(xml)); - return document; + return parser.parse(null, new StringReader(xml)); } @Test public void testBug1518() throws Exception { - String xml = IOUtils.toString(XmlParserTest.class.getResourceAsStream("parsertests/bug1518.xml")); + String xml = IOUtils.toString(XmlParserTest.class.getResourceAsStream("parsertests/bug1518.xml"), + StandardCharsets.UTF_8); Node document = parseXml(xml); assertNotNull(document); } + @Test + public void testAutoclosingElementLength() { + final String xml = "<elementName att1='foo' att2='bar' att3='other' />"; + assertLineNumbers(parseXml(xml), 1, 1, 1, xml.length()); + } + /** * Asserts a single node inclusive attributes. * diff --git a/pmd-xml/src/test/java/net/sourceforge/pmd/lang/xml/rule/basic/BasicRulesTest.java b/pmd-xml/src/test/java/net/sourceforge/pmd/lang/xml/rule/basic/BasicRulesTest.java deleted file mode 100644 index 315b31cfef6..00000000000 --- a/pmd-xml/src/test/java/net/sourceforge/pmd/lang/xml/rule/basic/BasicRulesTest.java +++ /dev/null @@ -1,17 +0,0 @@ -/** - * BSD-style license; for more info see http://pmd.sourceforge.net/license.html - */ - -package net.sourceforge.pmd.lang.xml.rule.basic; - -import net.sourceforge.pmd.testframework.SimpleAggregatorTst; - -public class BasicRulesTest extends SimpleAggregatorTst { - - private static final String RULESET = "xml-basic"; - - @Override - public void setUp() { - addRule(RULESET, "MistypedCDATASection"); - } -} diff --git a/pmd-xml/src/test/java/net/sourceforge/pmd/lang/xml/rule/errorprone/MistypedCDATASectionTest.java b/pmd-xml/src/test/java/net/sourceforge/pmd/lang/xml/rule/errorprone/MistypedCDATASectionTest.java new file mode 100644 index 00000000000..f31e4aa014d --- /dev/null +++ b/pmd-xml/src/test/java/net/sourceforge/pmd/lang/xml/rule/errorprone/MistypedCDATASectionTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.xml.rule.errorprone; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class MistypedCDATASectionTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-xml/src/test/java/net/sourceforge/pmd/lang/xsl/rule/codestyle/UseConcatOnceTest.java b/pmd-xml/src/test/java/net/sourceforge/pmd/lang/xsl/rule/codestyle/UseConcatOnceTest.java new file mode 100644 index 00000000000..a13e5e1f1f2 --- /dev/null +++ b/pmd-xml/src/test/java/net/sourceforge/pmd/lang/xsl/rule/codestyle/UseConcatOnceTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.xsl.rule.codestyle; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class UseConcatOnceTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-xml/src/test/java/net/sourceforge/pmd/lang/xsl/rule/performance/AvoidAxisNavigationTest.java b/pmd-xml/src/test/java/net/sourceforge/pmd/lang/xsl/rule/performance/AvoidAxisNavigationTest.java new file mode 100644 index 00000000000..597fe35c5d6 --- /dev/null +++ b/pmd-xml/src/test/java/net/sourceforge/pmd/lang/xsl/rule/performance/AvoidAxisNavigationTest.java @@ -0,0 +1,11 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ + +package net.sourceforge.pmd.lang.xsl.rule.performance; + +import net.sourceforge.pmd.testframework.PmdRuleTst; + +public class AvoidAxisNavigationTest extends PmdRuleTst { + // no additional unit tests +} diff --git a/pmd-xml/src/test/java/net/sourceforge/pmd/lang/xsl/rule/xpath/XPathRulesTest.java b/pmd-xml/src/test/java/net/sourceforge/pmd/lang/xsl/rule/xpath/XPathRulesTest.java deleted file mode 100644 index c4fc0caffc1..00000000000 --- a/pmd-xml/src/test/java/net/sourceforge/pmd/lang/xsl/rule/xpath/XPathRulesTest.java +++ /dev/null @@ -1,18 +0,0 @@ -/** - * BSD-style license; for more info see http://pmd.sourceforge.net/license.html - */ - -package net.sourceforge.pmd.lang.xsl.rule.xpath; - -import net.sourceforge.pmd.testframework.SimpleAggregatorTst; - -public class XPathRulesTest extends SimpleAggregatorTst { - - private static final String RULESET = "xsl-xpath"; - - @Override - public void setUp() { - addRule(RULESET, "UseConcatOnce"); - addRule(RULESET, "AvoidAxisNavigation"); - } -} diff --git a/pmd-xml/src/test/resources/net/sourceforge/pmd/lang/pom/rule/basic/xml/InvalidDependencyTypes.xml b/pmd-xml/src/test/resources/net/sourceforge/pmd/lang/pom/rule/basic/xml/InvalidDependencyTypes.xml deleted file mode 100644 index b1b1fd9142b..00000000000 --- a/pmd-xml/src/test/resources/net/sourceforge/pmd/lang/pom/rule/basic/xml/InvalidDependencyTypes.xml +++ /dev/null @@ -1,54 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<test-data - xmlns="http://pmd.sourceforge.net/rule-tests" - xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:schemaLocation="http://pmd.sourceforge.net/rule-tests http://pmd.sourceforge.net/rule-tests_1_0_0.xsd"> - <test-code> - <description>Simple POM file - no violation</description> - <expected-problems>0</expected-problems> - <code><![CDATA[ -<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> - <modelVersion>4.0.0</modelVersion> - <groupId>net.sourceforge.pmd</groupId> - <artifactId>xml-pom</artifactId> - <version>1.0.0-SNAPSHOT</version> - - <dependencyManagement> - <dependency> - <groupId>org.jboss.arquillian</groupId> - <artifactId>arquillian-bom</artifactId> - <version>${arquillian.version}</version> - <type>pom</type> - <scope>import</scope> - </dependency> - </dependencyManagement> - -</project> - ]]></code> - <source-type>pom</source-type> - </test-code> - <test-code> - <description>Simple POM file with violation</description> - <expected-problems>1</expected-problems> - <code><![CDATA[ -<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> - <modelVersion>4.0.0</modelVersion> - <groupId>net.sourceforge.pmd</groupId> - <artifactId>xml-pom</artifactId> - <version>1.0.0-SNAPSHOT</version> - - <dependencyManagement> - <dependency> - <groupId>org.jboss.arquillian</groupId> - <artifactId>arquillian-bom</artifactId> - <version>${arquillian.version}</version> - <type>bom</type> - <scope>import</scope> - </dependency> - </dependencyManagement> - -</project> - ]]></code> - <source-type>pom</source-type> - </test-code> -</test-data> diff --git a/pmd-xml/src/test/resources/net/sourceforge/pmd/lang/pom/rule/basic/xml/ProjectVersionAsDependencyVersion.xml b/pmd-xml/src/test/resources/net/sourceforge/pmd/lang/pom/rule/basic/xml/ProjectVersionAsDependencyVersion.xml deleted file mode 100644 index 0c1f0b7e8a1..00000000000 --- a/pmd-xml/src/test/resources/net/sourceforge/pmd/lang/pom/rule/basic/xml/ProjectVersionAsDependencyVersion.xml +++ /dev/null @@ -1,48 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<test-data - xmlns="http://pmd.sourceforge.net/rule-tests" - xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:schemaLocation="http://pmd.sourceforge.net/rule-tests http://pmd.sourceforge.net/rule-tests_1_0_0.xsd"> - <test-code> - <description>Simple POM file</description> - <expected-problems>0</expected-problems> - <code><![CDATA[ -<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> - <modelVersion>4.0.0</modelVersion> - <groupId>net.sourceforge.pmd</groupId> - <artifactId>xml-pom</artifactId> - <version>1.0.0-SNAPSHOT</version> - - <dependencies> - <dependency> - <groupId>x.y.z</groupId> - <artifactId>z</artifactId> - <version>1.0.0</version> - </dependency> - </dependencies> -</project> - ]]></code> - <source-type>pom</source-type> - </test-code> - <test-code> - <description>Simple POM file with violation</description> - <expected-problems>1</expected-problems> - <code><![CDATA[ -<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> - <modelVersion>4.0.0</modelVersion> - <groupId>net.sourceforge.pmd</groupId> - <artifactId>xml-pom</artifactId> - <version>1.0.0-SNAPSHOT</version> - - <dependencies> - <dependency> - <groupId>x.y.z</groupId> - <artifactId>z</artifactId> - <version>${project.version}</version> - </dependency> - </dependencies> -</project> - ]]></code> - <source-type>pom</source-type> - </test-code> -</test-data> diff --git a/pmd-xml/src/test/resources/net/sourceforge/pmd/lang/pom/rule/errorprone/xml/InvalidDependencyTypes.xml b/pmd-xml/src/test/resources/net/sourceforge/pmd/lang/pom/rule/errorprone/xml/InvalidDependencyTypes.xml new file mode 100644 index 00000000000..54a88ae2747 --- /dev/null +++ b/pmd-xml/src/test/resources/net/sourceforge/pmd/lang/pom/rule/errorprone/xml/InvalidDependencyTypes.xml @@ -0,0 +1,80 @@ +<?xml version="1.0" encoding="UTF-8"?> +<test-data + xmlns="http://pmd.sourceforge.net/rule-tests" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://pmd.sourceforge.net/rule-tests http://pmd.sourceforge.net/rule-tests_1_0_0.xsd"> + <test-code> + <description>Simple POM file - no violation</description> + <expected-problems>0</expected-problems> + <code><![CDATA[ +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + <groupId>net.sourceforge.pmd</groupId> + <artifactId>xml-pom</artifactId> + <version>1.0.0-SNAPSHOT</version> + + <dependencyManagement> + <dependency> + <groupId>org.jboss.arquillian</groupId> + <artifactId>arquillian-bom</artifactId> + <version>${arquillian.version}</version> + <type>pom</type> + <scope>import</scope> + </dependency> + </dependencyManagement> + +</project> + ]]></code> + <source-type>pom</source-type> + </test-code> + <test-code> + <description>Simple POM file with violation</description> + <expected-problems>1</expected-problems> + <code><![CDATA[ +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + <groupId>net.sourceforge.pmd</groupId> + <artifactId>xml-pom</artifactId> + <version>1.0.0-SNAPSHOT</version> + + <dependencyManagement> + <dependency> + <groupId>org.jboss.arquillian</groupId> + <artifactId>arquillian-bom</artifactId> + <version>${arquillian.version}</version> + <type>bom</type> + <scope>import</scope> + </dependency> + </dependencyManagement> + +</project> + ]]></code> + <source-type>pom</source-type> + </test-code> + + <test-code> + <description>Simple POM file with violation</description> + <rule-property name="validTypes">bom</rule-property> + <expected-problems>0</expected-problems> + <code><![CDATA[ +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + <groupId>net.sourceforge.pmd</groupId> + <artifactId>xml-pom</artifactId> + <version>1.0.0-SNAPSHOT</version> + + <dependencyManagement> + <dependency> + <groupId>org.jboss.arquillian</groupId> + <artifactId>arquillian-bom</artifactId> + <version>${arquillian.version}</version> + <type>bom</type> + <scope>import</scope> + </dependency> + </dependencyManagement> + +</project> + ]]></code> + <source-type>pom</source-type> + </test-code> +</test-data> diff --git a/pmd-xml/src/test/resources/net/sourceforge/pmd/lang/pom/rule/errorprone/xml/ProjectVersionAsDependencyVersion.xml b/pmd-xml/src/test/resources/net/sourceforge/pmd/lang/pom/rule/errorprone/xml/ProjectVersionAsDependencyVersion.xml new file mode 100644 index 00000000000..2f13256ca66 --- /dev/null +++ b/pmd-xml/src/test/resources/net/sourceforge/pmd/lang/pom/rule/errorprone/xml/ProjectVersionAsDependencyVersion.xml @@ -0,0 +1,78 @@ +<?xml version="1.0" encoding="UTF-8"?> +<test-data + xmlns="http://pmd.sourceforge.net/rule-tests" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://pmd.sourceforge.net/rule-tests http://pmd.sourceforge.net/rule-tests_1_0_0.xsd"> + <test-code> + <description>Simple POM file</description> + <expected-problems>0</expected-problems> + <code><![CDATA[ +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + <groupId>net.sourceforge.pmd</groupId> + <artifactId>xml-pom</artifactId> + <version>1.0.0-SNAPSHOT</version> + + <dependencies> + <dependency> + <groupId>x.y.z</groupId> + <artifactId>z</artifactId> + <version>1.0.0</version> + </dependency> + </dependencies> +</project> + ]]></code> + <source-type>pom</source-type> + </test-code> + <test-code> + <description>Simple POM file with violation</description> + <expected-problems>1</expected-problems> + <code><![CDATA[ +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + <groupId>net.sourceforge.pmd</groupId> + <artifactId>xml-pom</artifactId> + <version>1.0.0-SNAPSHOT</version> + + <dependencies> + <dependency> + <groupId>x.y.z</groupId> + <artifactId>z</artifactId> + <version>${project.version}</version> + </dependency> + </dependencies> +</project> + ]]></code> + <source-type>pom</source-type> + </test-code> + <test-code> + <description>#715 [xml] ProjectVersionAsDependencyVersion false positive: Valid usage child modules</description> + <expected-problems>1</expected-problems> + <expected-linenumbers>20</expected-linenumbers> + <code><![CDATA[ +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + <artifactId>a-child</artifactId> + + <parent> + <groupId>net.sourceforge.pmd</groupId> + <artifactId>parent</artifactId> + <version>1.0.0-SNAPSHOT</version> + </parent> + + <dependencies> + <dependency> + <groupId>net.sourceforge.pmd</groupId> + <artifactId>a-different-child</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>unrelated.group</groupId> + <artifactId>unrelated-artifact</artifactId> + <version>${project.version}</version> <!-- here is the violation --> + </dependency> + </dependencies> +</project> + ]]></code> + </test-code> +</test-data> diff --git a/pmd-xml/src/test/resources/net/sourceforge/pmd/lang/xml/rule/basic/xml/MistypedCDATASection.xml b/pmd-xml/src/test/resources/net/sourceforge/pmd/lang/xml/rule/errorprone/xml/MistypedCDATASection.xml similarity index 100% rename from pmd-xml/src/test/resources/net/sourceforge/pmd/lang/xml/rule/basic/xml/MistypedCDATASection.xml rename to pmd-xml/src/test/resources/net/sourceforge/pmd/lang/xml/rule/errorprone/xml/MistypedCDATASection.xml diff --git a/pmd-xml/src/test/resources/net/sourceforge/pmd/lang/xsl/rule/xpath/xml/UseConcatOnce.xml b/pmd-xml/src/test/resources/net/sourceforge/pmd/lang/xsl/rule/codestyle/xml/UseConcatOnce.xml similarity index 100% rename from pmd-xml/src/test/resources/net/sourceforge/pmd/lang/xsl/rule/xpath/xml/UseConcatOnce.xml rename to pmd-xml/src/test/resources/net/sourceforge/pmd/lang/xsl/rule/codestyle/xml/UseConcatOnce.xml diff --git a/pmd-xml/src/test/resources/net/sourceforge/pmd/lang/xsl/rule/xpath/xml/AvoidAxisNavigation.xml b/pmd-xml/src/test/resources/net/sourceforge/pmd/lang/xsl/rule/performance/xml/AvoidAxisNavigation.xml similarity index 100% rename from pmd-xml/src/test/resources/net/sourceforge/pmd/lang/xsl/rule/xpath/xml/AvoidAxisNavigation.xml rename to pmd-xml/src/test/resources/net/sourceforge/pmd/lang/xsl/rule/performance/xml/AvoidAxisNavigation.xml diff --git a/pom.xml b/pom.xml index d073446c69a..52722c32d29 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ <modelVersion>4.0.0</modelVersion> <groupId>net.sourceforge.pmd</groupId> <artifactId>pmd</artifactId> - <version>6.0.0-SNAPSHOT</version> + <version>6.10.0-SNAPSHOT</version> <packaging>pom</packaging> <name>PMD</name> @@ -255,34 +255,38 @@ Additionally it includes CPD, the copy-paste-detector. CPD finds duplicated code </issueManagement> <properties> + <java.version>7</java.version> + <!-- Workaround for https://youtrack.jetbrains.com/issue/IDEA-188690 --> + <maven.compiler.source>1.${java.version}</maven.compiler.source> + <maven.compiler.target>1.${java.version}</maven.compiler.target> + + <maven.compiler.test.source>1.8</maven.compiler.test.source> + <maven.compiler.test.target>1.8</maven.compiler.test.target> + + + <kotlin.compiler.jvmTarget>${maven.compiler.test.target}</kotlin.compiler.jvmTarget> + <kotlin.version>1.2.61</kotlin.version> + + <javacc.version>5.0</javacc.version> - <surefire.version>2.18.1</surefire.version> - <checkstyle.version>2.17</checkstyle.version> - <pmd.plugin.version>3.8</pmd.plugin.version> - <java.version>1.7</java.version> - <ant.version>1.9.6</ant.version> - <javadoc.plugin.version>3.0.0-M1</javadoc.plugin.version> - <antlr.version>4.5.2-1</antlr.version> + <surefire.version>2.22.0</surefire.version> + <checkstyle.version>3.0.0</checkstyle.version> + <pmd.plugin.version>3.11.0</pmd.plugin.version> + <ant.version>1.10.1</ant.version> + <javadoc.plugin.version>3.0.1</javadoc.plugin.version> + <antlr.version>4.7</antlr.version> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> - <pmd.website.baseurl>http://pmd.sourceforge.net/snapshot</pmd.website.baseurl> + <pmd.website.baseurl>https://pmd.github.io/pmd</pmd.website.baseurl> <argLine>-Xmx512m -Dfile.encoding=${project.build.sourceEncoding}</argLine> - <config.basedir>${basedir}/pmd-core</config.basedir> - <pmd.dogfood.ruleset>${config.basedir}/src/main/resources/rulesets/internal/dogfood.xml</pmd.dogfood.ruleset> - <pmd.build-tools.version>1.0.1</pmd.build-tools.version> + <pmd.build-tools.version>1.2</pmd.build-tools.version> + </properties> <build> - <extensions> - <extension> - <groupId>org.apache.maven.wagon</groupId> - <artifactId>wagon-ssh</artifactId> - <version>2.10</version> - </extension> - </extensions> <pluginManagement> <plugins> <plugin> @@ -290,7 +294,7 @@ Additionally it includes CPD, the copy-paste-detector. CPD finds duplicated code <artifactId>antlr4-maven-plugin</artifactId> <version>${antlr.version}</version> <configuration> - <encoding>${project.build.sourceEncoding}</encoding> + <inputEncoding>${project.build.sourceEncoding}</inputEncoding> </configuration> <executions> <execution> @@ -309,26 +313,17 @@ Additionally it includes CPD, the copy-paste-detector. CPD finds duplicated code <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-assembly-plugin</artifactId> - <version>2.6</version> - <dependencies> - <!-- Remove this once m-a-p 3.0.0 is available --> - <!-- Needed to fix java9 issue -> https://github.com/codehaus-plexus/plexus-archiver/commit/c0357c5234fedb958bc2dd93a8397424bdcea7cf --> - <dependency> - <groupId>org.codehaus.plexus</groupId> - <artifactId>plexus-archiver</artifactId> - <version>3.0.3</version> - </dependency> - </dependencies> + <version>3.1.0</version> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-dependency-plugin</artifactId> - <version>2.10</version> + <version>3.1.1</version> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-release-plugin</artifactId> - <version>2.5.2</version> + <version>2.5.3</version> <configuration> <releaseProfiles>pmd-release</releaseProfiles> <pushChanges>true</pushChanges> @@ -341,16 +336,69 @@ Additionally it includes CPD, the copy-paste-detector. CPD finds duplicated code <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-clean-plugin</artifactId> - <version>2.6.1</version> + <version>3.1.0</version> </plugin> + + <!-- Kotlin compiler for test-compile --> + <!-- The kotlin plugin has to run before the java plugin--> + <plugin> + <artifactId>kotlin-maven-plugin</artifactId> + <groupId>org.jetbrains.kotlin</groupId> + <version>${kotlin.version}</version> + <executions> + <execution> + <id>kotlin-test-compile</id> + <goals> + <goal>test-compile</goal> + </goals> + <phase>process-test-sources</phase> + <configuration> + <sourceDirs> + <sourceDir>${project.basedir}/src/test/kotlin</sourceDir> + <sourceDir>${project.basedir}/src/test/java</sourceDir> + </sourceDirs> + </configuration> + </execution> + </executions> + </plugin> + <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> - <version>3.3</version> + <version>3.7.0</version> <configuration> - <source>${java.version}</source> - <target>${java.version}</target> + <release>${java.version}</release> </configuration> + <executions> + <!-- Replacing default-compile as it is treated specially by maven --> + <execution> + <id>default-compile</id> + <phase>none</phase> + </execution> + <!-- Replacing default-testCompile as it is treated specially by maven --> + <execution> + <id>default-testCompile</id> + <phase>none</phase> + </execution> + <execution> + <id>java-compile</id> + <phase>compile</phase> + <goals> + <goal>compile</goal> + </goals> + </execution> + <execution> + <id>java-test-compile</id> + <phase>test-compile</phase> + <goals> + <goal>testCompile</goal> + </goals> + <configuration> + <source>${maven.compiler.test.source}</source> + <target>${maven.compiler.test.target}</target> + </configuration> + </execution> + </executions> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> @@ -365,17 +413,17 @@ Additionally it includes CPD, the copy-paste-detector. CPD finds duplicated code <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-jar-plugin</artifactId> - <version>3.0.0</version> + <version>3.1.0</version> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-resources-plugin</artifactId> - <version>2.7</version> + <version>3.1.0</version> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-shade-plugin</artifactId> - <version>2.4.3</version> + <version>3.1.1</version> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> @@ -384,31 +432,32 @@ Additionally it includes CPD, the copy-paste-detector. CPD finds duplicated code <configuration> <forkMode>once</forkMode> <runOrder>alphabetical</runOrder> + <testSourceDirectory>${project.basedir}/src/test/java</testSourceDirectory> + <testSourceDirectory>${project.basedir}/src/test/kotlin</testSourceDirectory> </configuration> - </plugin> - <plugin> - <groupId>org.apache.maven.plugins</groupId> - <artifactId>maven-toolchains-plugin</artifactId> - <version>1.1</version> - <executions> - <execution> - <goals> - <goal>toolchain</goal> - </goals> - </execution> - </executions> - <configuration> - <toolchains> - <jdk> - <version>${java.version}</version> - </jdk> - </toolchains> - </configuration> + <dependencies> + <!-- Junit 5 = Platform + Jupiter (5) + Vintage (3 & 4)--> + + <!-- Junit platform --> + <!-- Needed to use kotlintest --> + <dependency> + <groupId>org.junit.platform</groupId> + <artifactId>junit-platform-surefire-provider</artifactId> + <version>1.2.0</version> + </dependency> + + <!-- Junit 3 & 4 engine --> + <dependency> + <groupId>org.junit.vintage</groupId> + <artifactId>junit-vintage-engine</artifactId> + <version>5.3.0-M1</version> + </dependency> + </dependencies> </plugin> <plugin> <groupId>org.codehaus.mojo</groupId> <artifactId>build-helper-maven-plugin</artifactId> - <version>1.9.1</version> + <version>3.0.0</version> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> @@ -421,6 +470,18 @@ Additionally it includes CPD, the copy-paste-detector. CPD finds duplicated code <version>${javadoc.plugin.version}</version> <configuration> <quiet>true</quiet> + <doclint>none</doclint> + <additionalJOptions> + <additionalJOption>-html5</additionalJOption> + </additionalJOptions> + <additionalDependencies> + <!-- TODO: this is only needed to make javadoc happy --> + <additionalDependency> + <groupId>org.apache.ant</groupId> + <artifactId>ant</artifactId> + <version>${ant.version}</version> + </additionalDependency> + </additionalDependencies> </configuration> </plugin> <plugin> @@ -431,18 +492,72 @@ Additionally it includes CPD, the copy-paste-detector. CPD finds duplicated code <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-enforcer-plugin</artifactId> - <version>1.4.1</version> + <version>3.0.0-M2</version> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-pmd-plugin</artifactId> <version>${pmd.plugin.version}</version> + <executions> + <execution> + <phase>verify</phase> + <goals> + <goal>check</goal> + <goal>cpd</goal> + </goals> + </execution> + </executions> + <configuration> + <linkXRef>true</linkXRef> + <minimumTokens>100</minimumTokens> + <targetJdk>1.${java.version}</targetJdk> + <rulesets> + <ruleset>/net/sourceforge/pmd/pmd-dogfood-config.xml</ruleset> + </rulesets> + <excludeRoots> + <excludeRoot>target/generated-sources/javacc</excludeRoot> + <excludeRoot>target/generated-sources/antlr4</excludeRoot> + </excludeRoots> + <skipPmdError>false</skipPmdError> + <failurePriority>2</failurePriority> + <failOnViolation>true</failOnViolation> + <printFailingErrors>true</printFailingErrors> + </configuration> + <dependencies> + <!-- Note: we can't use a property for the version here due to https://issues.apache.org/jira/browse/MRELEASE-932 --> + <dependency> + <groupId>net.sourceforge.pmd</groupId> + <artifactId>pmd-core</artifactId> + <version>6.9.0</version> + </dependency> + <dependency> + <groupId>net.sourceforge.pmd</groupId> + <artifactId>pmd-java</artifactId> + <version>6.9.0</version> + </dependency> + <!-- This contains the dogfood ruleset --> + <dependency> + <groupId>net.sourceforge.pmd</groupId> + <artifactId>pmd-build-tools-config</artifactId> + <version>${pmd.build-tools.version}</version> + </dependency> + </dependencies> + </plugin> + <plugin> + <groupId>org.codehaus.mojo</groupId> + <artifactId>versions-maven-plugin</artifactId> + <version>2.5</version> </plugin> <plugin> <groupId>org.sonatype.plugins</groupId> <artifactId>nexus-staging-maven-plugin</artifactId> <version>1.6.8</version> </plugin> + <plugin> + <groupId>org.jacoco</groupId> + <artifactId>jacoco-maven-plugin</artifactId> + <version>0.8.2</version> + </plugin> <!--This plugin's configuration is used to store Eclipse m2e settings only. It has no influence on the Maven build itself. --> <plugin> @@ -475,6 +590,25 @@ Additionally it includes CPD, the copy-paste-detector. CPD finds duplicated code </plugins> </pluginManagement> <plugins> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-enforcer-plugin</artifactId> + <executions> + <execution> + <id>enforce-versions</id> + <goals> + <goal>enforce</goal> + </goals> + <configuration> + <rules> + <requireJavaVersion> + <version>[10,)</version> + </requireJavaVersion> + </rules> + </configuration> + </execution> + </executions> + </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-antrun-plugin</artifactId> @@ -501,26 +635,7 @@ Additionally it includes CPD, the copy-paste-detector. CPD finds duplicated code <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-pmd-plugin</artifactId> - <executions> - <execution> - <phase>verify</phase> - <goals> - <goal>pmd</goal> - <goal>cpd</goal> - </goals> - </execution> - </executions> - <configuration> - <minimumTokens>100</minimumTokens> - <targetJdk>${java.version}</targetJdk> - <rulesets> - <ruleset>${pmd.dogfood.ruleset}</ruleset> - </rulesets> - <excludeRoots> - <excludeRoot>target/generated-sources/javacc</excludeRoot> - </excludeRoots> - <skipPmdError>false</skipPmdError> - </configuration> + <!-- configuration is in plugin management section --> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> @@ -538,7 +653,7 @@ Additionally it includes CPD, the copy-paste-detector. CPD finds duplicated code <dependency> <groupId>com.puppycrawl.tools</groupId> <artifactId>checkstyle</artifactId> - <version>7.2</version> + <version>8.8</version> </dependency> <dependency> <groupId>net.sourceforge.pmd</groupId> @@ -555,10 +670,6 @@ Additionally it includes CPD, the copy-paste-detector. CPD finds duplicated code </sourceDirectories> </configuration> </plugin> - <plugin> - <groupId>org.apache.maven.plugins</groupId> - <artifactId>maven-toolchains-plugin</artifactId> - </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-javadoc-plugin</artifactId> @@ -626,17 +737,7 @@ Additionally it includes CPD, the copy-paste-detector. CPD finds duplicated code <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-pmd-plugin</artifactId> <version>${pmd.plugin.version}</version> - <configuration> - <linkXref>true</linkXref> - <minimumTokens>100</minimumTokens> - <targetJdk>${java.version}</targetJdk> - <rulesets> - <ruleset>${pmd.dogfood.ruleset}</ruleset> - </rulesets> - <excludeRoots> - <excludeRoot>target/generated-sources/javacc</excludeRoot> - </excludeRoots> - </configuration> + <!-- configuration is in plugin management section --> </plugin> <plugin> @@ -663,7 +764,7 @@ Additionally it includes CPD, the copy-paste-detector. CPD finds duplicated code <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-project-info-reports-plugin</artifactId> - <version>2.8.1</version> + <version>2.9</version> <reportSets> <reportSet> <reports> @@ -689,7 +790,6 @@ Additionally it includes CPD, the copy-paste-detector. CPD finds duplicated code <plugin> <groupId>org.codehaus.mojo</groupId> <artifactId>versions-maven-plugin</artifactId> - <version>2.2</version> <reportSets> <reportSet> <reports> @@ -742,12 +842,12 @@ Additionally it includes CPD, the copy-paste-detector. CPD finds duplicated code <dependency> <groupId>com.beust</groupId> <artifactId>jcommander</artifactId> - <version>1.48</version> + <version>1.72</version> </dependency> <dependency> <groupId>org.ow2.asm</groupId> <artifactId>asm</artifactId> - <version>5.0.4</version> + <version>6.2.1</version> </dependency> <dependency> <groupId>net.sourceforge.pmd</groupId> @@ -773,13 +873,23 @@ Additionally it includes CPD, the copy-paste-detector. CPD finds duplicated code <dependency> <groupId>org.mozilla</groupId> <artifactId>rhino</artifactId> - <version>1.7.7</version> + <version>1.7.7.2</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> </dependency> + <dependency> + <groupId>org.hamcrest</groupId> + <artifactId>hamcrest-library</artifactId> + <version>1.3</version> + </dependency> + <dependency> + <groupId>pl.pragmatists</groupId> + <artifactId>JUnitParams</artifactId> + <version>1.1.1</version> + </dependency> <dependency> <groupId>net.java.dev.javacc</groupId> <artifactId>javacc</artifactId> @@ -788,7 +898,7 @@ Additionally it includes CPD, the copy-paste-detector. CPD finds duplicated code <dependency> <groupId>commons-io</groupId> <artifactId>commons-io</artifactId> - <version>2.4</version> + <version>2.6</version> </dependency> <dependency> <groupId>org.mockito</groupId> @@ -798,12 +908,12 @@ Additionally it includes CPD, the copy-paste-detector. CPD finds duplicated code <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-lang3</artifactId> - <version>3.4</version> + <version>3.8.1</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> - <version>1.7.12</version> + <version>1.7.25</version> </dependency> <dependency> <groupId>com.github.tomakehurst</groupId> @@ -826,6 +936,47 @@ Additionally it includes CPD, the copy-paste-detector. CPD finds duplicated code <artifactId>system-rules</artifactId> <version>1.8.0</version> </dependency> + + <!-- TEST DEPENDENCIES --> + + + <dependency> + <groupId>net.sourceforge.pmd</groupId> + <artifactId>pmd-lang-test</artifactId> + <version>${project.version}</version> + <scope>test</scope> + </dependency> + + <!-- Kotlin --> + + <dependency> + <groupId>org.jetbrains.kotlin</groupId> + <artifactId>kotlin-stdlib</artifactId> + <version>${kotlin.version}</version> + <scope>test</scope> + </dependency> + + <dependency> + <groupId>org.jetbrains.kotlin</groupId> + <artifactId>kotlin-stdlib-jdk8</artifactId> + <version>${kotlin.version}</version> + <scope>test</scope> + </dependency> + + <dependency> + <groupId>org.jetbrains.kotlin</groupId> + <artifactId>kotlin-test-junit</artifactId> + <version>${kotlin.version}</version> + <scope>test</scope> + </dependency> + + <dependency> + <groupId>io.kotlintest</groupId> + <artifactId>kotlintest-runner-junit5</artifactId> + <version>3.1.8</version> + <scope>test</scope> + </dependency> + </dependencies> </dependencyManagement> @@ -901,39 +1052,8 @@ Additionally it includes CPD, the copy-paste-detector. CPD finds duplicated code </build> </profile> - <profile> - <id>jdk8-modules</id> - <activation> - <jdk>[1.8,</jdk> - </activation> - <modules> - <module>pmd-apex</module> - <module>pmd-java8</module> - <module>pmd-ui</module> - <module>pmd-doc</module> - </modules> - </profile> - - <profile> - <id>jdk9-disabled</id> - <activation> - <jdk>!1.9</jdk> - </activation> - <modules> - <!-- - https://github.com/scala/scala-dev/issues/139 - https://issues.scala-lang.org/browse/SI-9103 - https://sourceforge.net/p/pmd/bugs/1314/ - --> - <module>pmd-scala</module> - </modules> - </profile> - <profile> <id>doclint</id> - <properties> - <java.version>1.8</java.version> - </properties> <build> <pluginManagement> <plugins> @@ -941,7 +1061,7 @@ Additionally it includes CPD, the copy-paste-detector. CPD finds duplicated code <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-javadoc-plugin</artifactId> <configuration> - <additionalparam>-Xdoclint:all</additionalparam> + <doclint>all</doclint> </configuration> </plugin> </plugins> @@ -956,7 +1076,6 @@ Additionally it includes CPD, the copy-paste-detector. CPD finds duplicated code <plugin> <groupId>org.jacoco</groupId> <artifactId>jacoco-maven-plugin</artifactId> - <version>0.7.9</version> <executions> <execution> <id>default-prepare-agent</id> @@ -970,6 +1089,49 @@ Additionally it includes CPD, the copy-paste-detector. CPD finds duplicated code <groupId>org.eluder.coveralls</groupId> <artifactId>coveralls-maven-plugin</artifactId> <version>4.3.0</version> + <dependencies> + <!-- + coveralls maven plugin needs javax.xml.bind api + See also https://github.com/trautonen/coveralls-maven-plugin/issues/112 + --> + <dependency> + <groupId>javax.xml.bind</groupId> + <artifactId>jaxb-api</artifactId> + <version>2.3.0</version> + </dependency> + </dependencies> + </plugin> + </plugins> + </build> + </profile> + + <profile> + <id>sonar</id> + <properties> + <sonar.host.url>https://sonarcloud.io</sonar.host.url> + </properties> + <build> + <pluginManagement> + <plugins> + <plugin> + <groupId>org.sonarsource.scanner.maven</groupId> + <artifactId>sonar-maven-plugin</artifactId> + <version>3.4.1.1168</version> + </plugin> + </plugins> + </pluginManagement> + <plugins> + <plugin> + <groupId>org.jacoco</groupId> + <artifactId>jacoco-maven-plugin</artifactId> + <executions> + <execution> + <id>default-prepare-agent</id> + <goals> + <goal>prepare-agent</goal> + </goals> + </execution> + </executions> </plugin> </plugins> </build> @@ -994,10 +1156,19 @@ Additionally it includes CPD, the copy-paste-detector. CPD finds duplicated code <module>pmd-plsql</module> <module>pmd-python</module> <module>pmd-ruby</module> + <module>pmd-scala</module> <module>pmd-swift</module> <module>pmd-test</module> <module>pmd-visualforce</module> <module>pmd-vm</module> <module>pmd-xml</module> + + <!-- java8 modules --> + <module>pmd-apex-jorje</module> + <module>pmd-apex</module> + <module>pmd-java8</module> + <module>pmd-ui</module> + <module>pmd-doc</module> + <module>pmd-lang-test</module> </modules> </project>