Merge branch '7.0.x' into port-properties
This commit is contained in:
commit
0ba2460f92
@ -7088,6 +7088,25 @@
|
||||
"contributions": [
|
||||
"bug"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "pguyot",
|
||||
"name": "Paul Guyot",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/168407?v=4",
|
||||
"profile": "http://paul-guyot.com/",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "dawiddc",
|
||||
"name": "Dawid Ciok",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/26235980?v=4",
|
||||
"profile": "https://github.com/dawiddc",
|
||||
"contributions": [
|
||||
"bug",
|
||||
"code"
|
||||
]
|
||||
}
|
||||
],
|
||||
"contributorsPerLine": 7,
|
||||
|
2
.mvn/wrapper/maven-wrapper.properties
vendored
2
.mvn/wrapper/maven-wrapper.properties
vendored
@ -14,5 +14,5 @@
|
||||
# KIND, either express or implied. See the License for the
|
||||
# specific language governing permissions and limitations
|
||||
# under the License.
|
||||
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.6/apache-maven-3.8.6-bin.zip
|
||||
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.0/apache-maven-3.9.0-bin.zip
|
||||
wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.1/maven-wrapper-3.1.1.jar
|
||||
|
@ -47,7 +47,7 @@ There are various channels, on which you can ask questions:
|
||||
|
||||
* Create a new discussion for your question at <https://github.com/pmd/pmd/discussions>.
|
||||
|
||||
* Ask your question on Gitter <https://gitter.im/pmd/pmd>.
|
||||
* Ask your question in our [Gitter room](https://app.gitter.im/#/room/#pmd_pmd:gitter.im).
|
||||
|
||||
## Code Style
|
||||
|
||||
|
34
Gemfile.lock
34
Gemfile.lock
@ -12,12 +12,12 @@ GEM
|
||||
concurrent-ruby (1.2.0)
|
||||
cork (0.3.0)
|
||||
colored2 (~> 3.1)
|
||||
danger (9.1.0)
|
||||
danger (9.2.0)
|
||||
claide (~> 1.0)
|
||||
claide-plugins (>= 0.9.2)
|
||||
colored2 (~> 3.1)
|
||||
cork (~> 0.1)
|
||||
faraday (>= 0.9.0, < 2.0)
|
||||
faraday (>= 0.9.0, < 3.0)
|
||||
faraday-http-cache (~> 2.0)
|
||||
git (~> 1.7)
|
||||
kramdown (~> 2.3)
|
||||
@ -28,31 +28,12 @@ GEM
|
||||
differ (0.1.2)
|
||||
et-orbi (1.2.7)
|
||||
tzinfo
|
||||
faraday (1.10.3)
|
||||
faraday-em_http (~> 1.0)
|
||||
faraday-em_synchrony (~> 1.0)
|
||||
faraday-excon (~> 1.1)
|
||||
faraday-httpclient (~> 1.0)
|
||||
faraday-multipart (~> 1.0)
|
||||
faraday-net_http (~> 1.0)
|
||||
faraday-net_http_persistent (~> 1.0)
|
||||
faraday-patron (~> 1.0)
|
||||
faraday-rack (~> 1.0)
|
||||
faraday-retry (~> 1.0)
|
||||
faraday (2.7.4)
|
||||
faraday-net_http (>= 2.0, < 3.1)
|
||||
ruby2_keywords (>= 0.0.4)
|
||||
faraday-em_http (1.0.0)
|
||||
faraday-em_synchrony (1.0.0)
|
||||
faraday-excon (1.1.0)
|
||||
faraday-http-cache (2.4.1)
|
||||
faraday (>= 0.8)
|
||||
faraday-httpclient (1.0.1)
|
||||
faraday-multipart (1.0.4)
|
||||
multipart-post (~> 2)
|
||||
faraday-net_http (1.0.1)
|
||||
faraday-net_http_persistent (1.2.0)
|
||||
faraday-patron (1.0.0)
|
||||
faraday-rack (1.0.0)
|
||||
faraday-retry (1.0.3)
|
||||
faraday-net_http (3.0.2)
|
||||
fugit (1.8.1)
|
||||
et-orbi (~> 1, >= 1.2.7)
|
||||
raabro (~> 1.4)
|
||||
@ -66,10 +47,9 @@ GEM
|
||||
liquid (5.4.0)
|
||||
logger-colors (1.0.0)
|
||||
mini_portile2 (2.8.1)
|
||||
multipart-post (2.2.3)
|
||||
nap (1.1.0)
|
||||
no_proxy_fix (0.1.2)
|
||||
nokogiri (1.14.0)
|
||||
nokogiri (1.14.1)
|
||||
mini_portile2 (~> 2.8.0)
|
||||
racc (~> 1.4)
|
||||
octokit (5.6.1)
|
||||
@ -99,7 +79,7 @@ GEM
|
||||
slop (4.9.3)
|
||||
terminal-table (3.0.2)
|
||||
unicode-display_width (>= 1.1.1, < 3)
|
||||
tzinfo (2.0.5)
|
||||
tzinfo (2.0.6)
|
||||
concurrent-ruby (~> 1.0)
|
||||
unicode-display_width (2.4.2)
|
||||
|
||||
|
14
README.md
14
README.md
@ -2,12 +2,12 @@
|
||||
|
||||
![PMD Logo](https://raw.githubusercontent.com/pmd/pmd/pmd/7.0.x/docs/images/logo/pmd-logo-300px.png)
|
||||
|
||||
[![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)
|
||||
[![Join the chat](https://img.shields.io/gitter/room/pmd/pmd)](https://app.gitter.im/#/room/#pmd_pmd:gitter.im?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||
[![Build Status](https://github.com/pmd/pmd/workflows/build/badge.svg?branch=master)](https://github.com/pmd/pmd/actions)
|
||||
[![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)
|
||||
[![Reproducible Builds](https://img.shields.io/badge/Reproducible_Builds-ok-green?labelColor=blue)](https://github.com/jvm-repo-rebuild/reproducible-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)
|
||||
[![Codacy Badge](https://app.codacy.com/project/badge/Grade/ea550046a02344ec850553476c4aa2ca)](https://www.codacy.com/gh/pmd/pmd/dashboard?utm_source=github.com&utm_medium=referral&utm_content=pmd/pmd&utm_campaign=Badge_Grade)
|
||||
[![Contributor Covenant](https://img.shields.io/badge/Contributor%20Covenant-v2.0%20adopted-ff69b4.svg)](code_of_conduct.md)
|
||||
[![Documentation (latest)](https://img.shields.io/badge/docs-latest-green)](https://pmd.github.io/latest/)
|
||||
|
||||
@ -17,12 +17,12 @@ It uses JavaCC and Antlr to parse source files into abstract syntax trees (AST)
|
||||
Rules can be written in Java or using a XPath query.
|
||||
|
||||
It supports Java, JavaScript, Salesforce.com Apex and Visualforce,
|
||||
Modelica, PLSQL, Apache Velocity, XML, XSL.
|
||||
Modelica, PLSQL, Apache Velocity, HTML, XML and XSL.
|
||||
Scala is supported, but there are currently no Scala rules available.
|
||||
|
||||
Additionally it includes **CPD**, the copy-paste-detector. CPD finds duplicated code in
|
||||
C/C++, C#, Dart, Fortran, Go, Groovy, Java, JavaScript, JSP, Kotlin, Lua, Matlab, Modelica,
|
||||
Objective-C, Perl, PHP, PLSQL, Python, Ruby, Salesforce.com Apex, Scala, Swift, Visualforce and XML.
|
||||
Additionally, it includes **CPD**, the copy-paste-detector. CPD finds duplicated code in
|
||||
C/C++, C#, Dart, Fortran, Gherkin, Go, Groovy, HTML, Java, JavaScript, JSP, Kotlin, Lua, Matlab, Modelica,
|
||||
Objective-C, Perl, PHP, PLSQL, Python, Ruby, Salesforce.com Apex and Visualforce, Scala, Swift, T-SQL and XML.
|
||||
|
||||
In the future we hope to add support for data/control flow analysis and automatic (quick) fixes where
|
||||
it makes sense.
|
||||
@ -53,7 +53,7 @@ See [Tools / Integrations](https://pmd.github.io/latest/pmd_userdocs_tools.html)
|
||||
or on [discussions](https://github.com/pmd/pmd/discussions).
|
||||
* I got this error and I'm sure it's a bug -- file an [issue](https://github.com/pmd/pmd/issues).
|
||||
* I have an idea/request/question -- create a new [discussion](https://github.com/pmd/pmd/discussions).
|
||||
* I have a quick question -- ask on our [Gitter chat](https://gitter.im/pmd/pmd).
|
||||
* I have a quick question -- ask in our [Gitter room](https://app.gitter.im/#/room/#pmd_pmd:gitter.im).
|
||||
* Where's your documentation? -- <https://pmd.github.io/latest/>
|
||||
|
||||
## 🤝 Contributing
|
||||
|
@ -1,12 +1,11 @@
|
||||
GEM
|
||||
remote: https://rubygems.org/
|
||||
specs:
|
||||
activesupport (6.0.6.1)
|
||||
activesupport (7.0.4.2)
|
||||
concurrent-ruby (~> 1.0, >= 1.0.2)
|
||||
i18n (>= 0.7, < 2)
|
||||
minitest (~> 5.1)
|
||||
tzinfo (~> 1.1)
|
||||
zeitwerk (~> 2.2, >= 2.2.2)
|
||||
i18n (>= 1.6, < 2)
|
||||
minitest (>= 5.1)
|
||||
tzinfo (~> 2.0)
|
||||
addressable (2.8.1)
|
||||
public_suffix (>= 2.0.2, < 6.0)
|
||||
coffee-script (2.4.1)
|
||||
@ -14,7 +13,7 @@ GEM
|
||||
execjs
|
||||
coffee-script-source (1.11.1)
|
||||
colorator (1.1.0)
|
||||
commonmarker (0.23.7)
|
||||
commonmarker (0.23.8)
|
||||
concurrent-ruby (1.2.0)
|
||||
dnsruby (1.61.9)
|
||||
simpleidn (~> 0.1)
|
||||
@ -32,12 +31,12 @@ GEM
|
||||
ffi (1.15.5)
|
||||
forwardable-extended (2.6.0)
|
||||
gemoji (3.0.1)
|
||||
github-pages (227)
|
||||
github-pages (228)
|
||||
github-pages-health-check (= 1.17.9)
|
||||
jekyll (= 3.9.2)
|
||||
jekyll (= 3.9.3)
|
||||
jekyll-avatar (= 0.7.0)
|
||||
jekyll-coffeescript (= 1.1.1)
|
||||
jekyll-commonmark-ghpages (= 0.2.0)
|
||||
jekyll-commonmark-ghpages (= 0.4.0)
|
||||
jekyll-default-layout (= 0.1.4)
|
||||
jekyll-feed (= 0.15.1)
|
||||
jekyll-gist (= 1.5.0)
|
||||
@ -71,7 +70,7 @@ GEM
|
||||
jemoji (= 0.12.0)
|
||||
kramdown (= 2.3.2)
|
||||
kramdown-parser-gfm (= 1.1.0)
|
||||
liquid (= 4.0.3)
|
||||
liquid (= 4.0.4)
|
||||
mercenary (~> 0.3)
|
||||
minima (= 2.5.1)
|
||||
nokogiri (>= 1.13.6, < 2.0)
|
||||
@ -87,13 +86,13 @@ GEM
|
||||
activesupport (>= 2)
|
||||
nokogiri (>= 1.4)
|
||||
http_parser.rb (0.8.0)
|
||||
i18n (0.9.5)
|
||||
i18n (1.12.0)
|
||||
concurrent-ruby (~> 1.0)
|
||||
jekyll (3.9.2)
|
||||
jekyll (3.9.3)
|
||||
addressable (~> 2.4)
|
||||
colorator (~> 1.0)
|
||||
em-websocket (~> 0.5)
|
||||
i18n (~> 0.7)
|
||||
i18n (>= 0.7, < 2)
|
||||
jekyll-sass-converter (~> 1.0)
|
||||
jekyll-watch (~> 2.0)
|
||||
kramdown (>= 1.17, < 3)
|
||||
@ -109,11 +108,11 @@ GEM
|
||||
coffee-script-source (~> 1.11.1)
|
||||
jekyll-commonmark (1.4.0)
|
||||
commonmarker (~> 0.22)
|
||||
jekyll-commonmark-ghpages (0.2.0)
|
||||
commonmarker (~> 0.23.4)
|
||||
jekyll-commonmark-ghpages (0.4.0)
|
||||
commonmarker (~> 0.23.7)
|
||||
jekyll (~> 3.9.0)
|
||||
jekyll-commonmark (~> 1.4.0)
|
||||
rouge (>= 2.0, < 4.0)
|
||||
rouge (>= 2.0, < 5.0)
|
||||
jekyll-default-layout (0.1.4)
|
||||
jekyll (~> 3.0)
|
||||
jekyll-feed (0.15.1)
|
||||
@ -201,7 +200,7 @@ GEM
|
||||
rexml
|
||||
kramdown-parser-gfm (1.1.0)
|
||||
kramdown (~> 2.0)
|
||||
liquid (4.0.3)
|
||||
liquid (4.0.4)
|
||||
listen (3.8.0)
|
||||
rb-fsevent (~> 0.10, >= 0.10.3)
|
||||
rb-inotify (~> 0.9, >= 0.9.10)
|
||||
@ -212,7 +211,7 @@ GEM
|
||||
jekyll-feed (~> 0.9)
|
||||
jekyll-seo-tag (~> 2.1)
|
||||
minitest (5.17.0)
|
||||
nokogiri (1.14.0)
|
||||
nokogiri (1.14.1)
|
||||
mini_portile2 (~> 2.8.0)
|
||||
racc (~> 1.4)
|
||||
octokit (4.25.1)
|
||||
@ -242,17 +241,15 @@ GEM
|
||||
unf (~> 0.1.4)
|
||||
terminal-table (1.8.0)
|
||||
unicode-display_width (~> 1.1, >= 1.1.1)
|
||||
thread_safe (0.3.6)
|
||||
typhoeus (1.4.0)
|
||||
ethon (>= 0.9.0)
|
||||
tzinfo (1.2.10)
|
||||
thread_safe (~> 0.1)
|
||||
tzinfo (2.0.6)
|
||||
concurrent-ruby (~> 1.0)
|
||||
unf (0.1.4)
|
||||
unf_ext
|
||||
unf_ext (0.0.8.2)
|
||||
unicode-display_width (1.8.0)
|
||||
webrick (1.7.0)
|
||||
zeitwerk (2.6.6)
|
||||
webrick (1.8.1)
|
||||
|
||||
PLATFORMS
|
||||
ruby
|
||||
|
@ -400,6 +400,9 @@ entries:
|
||||
- title: Language-Specific Documentation
|
||||
output: web, pdf
|
||||
folderitems:
|
||||
- title: Language configuration
|
||||
url: /pmd_languages_configuration.html
|
||||
output: web, pdf
|
||||
- title: Apex
|
||||
url: /pmd_languages_apex.html
|
||||
output: web, pdf
|
||||
|
@ -248,8 +248,10 @@ The following previously deprecated rules have been finally removed:
|
||||
* [#4080](https://github.com/pmd/pmd/issues/4080): \[ant] Split off Ant integration into a new submodule
|
||||
* core
|
||||
* [#2234](https://github.com/pmd/pmd/issues/2234): \[core] Consolidate PMD CLI into a single command
|
||||
* [#2518](https://github.com/pmd/pmd/issues/2518): \[core] Language properties
|
||||
* [#2873](https://github.com/pmd/pmd/issues/2873): \[core] Utility classes in pmd 7
|
||||
* [#3203](https://github.com/pmd/pmd/issues/3203): \[core] Replace RuleViolationFactory implementations with ViolationDecorator
|
||||
* [#3782](https://github.com/pmd/pmd/issues/3782): \[core] Language lifecycle
|
||||
* [#3902](https://github.com/pmd/pmd/issues/3902): \[core] Violation decorators
|
||||
* [#4035](https://github.com/pmd/pmd/issues/4035): \[core] ConcurrentModificationException in DefaultRuleViolationFactory
|
||||
* cli
|
||||
@ -260,6 +262,7 @@ The following previously deprecated rules have been finally removed:
|
||||
* java
|
||||
* [#4317](https://github.com/pmd/pmd/issues/4317): \[java] Some AST nodes should not be TypeNodes
|
||||
* [#4367](https://github.com/pmd/pmd/issues/4367): \[java] Move testrule TypeResTest into internal
|
||||
* [#4359](https://github.com/pmd/pmd/issues/4359): \[java] Type resolution fails with NPE when the scope is not a type declaration
|
||||
* java-bestpractices
|
||||
* [#342](https://github.com/pmd/pmd/issues/342): \[java] AccessorMethodGeneration: Name clash with another public field not properly handled
|
||||
* [#755](https://github.com/pmd/pmd/issues/755): \[java] AccessorClassGeneration false positive for private constructors
|
||||
@ -399,6 +402,23 @@ The metrics framework has been made simpler and more general.
|
||||
* Rule tests, that use {% jdoc test::testframework.SimpleAggregatorTst %} or {% jdoc test::testframework.PmdRuleTst %} work as before without change, but use
|
||||
now JUnit5 under the hood. If you added additional JUnit4 tests to your rule test classes, then you'll need to upgrade them to use JUnit5.
|
||||
|
||||
#### Language Modules
|
||||
|
||||
In order to support language properties and provide a proper lifecycle for languages, there were some changes needed
|
||||
in this area:
|
||||
|
||||
* The class `BaseLanguageModule` has been removed.
|
||||
* Individual language modules should now extend {% jdoc core::lang.impl.SimpleLanguageModuleBase %}. Like before
|
||||
this class is registered via the service loader mechanism via `META-INF/services/net.sourceforge.pmd.lang.Language`.
|
||||
* The implementation of a language version handler has been simplified by providing default implementations for
|
||||
most aspects. The minimum requirement is now to provide an own parser for the language version handler.
|
||||
* Language modules can define [custom language properties](pmd_languages_configuration.html)
|
||||
which can be set via environment variables. This allows
|
||||
to add and use language specific configuration options without the need to change pmd-core.
|
||||
* For each PMD analysis run a new `LanguageProcessor` instance is created and destroyed afterwards. This allows
|
||||
to store global information without using static fields. This enables the implementation of multifile analysis.
|
||||
* Rules have access to this language processor instance during initialization.
|
||||
|
||||
### External Contributions
|
||||
|
||||
* [#1658](https://github.com/pmd/pmd/pull/1658): \[core] Node support for Antlr-based languages - [Matías Fraga](https://github.com/matifraga)
|
||||
|
@ -7,7 +7,7 @@ keywords: changelog, release notes, deprecation, api changes
|
||||
We're excited to bring you the next major version of PMD!
|
||||
Here is a summary of what is planned for PMD 7.
|
||||
|
||||
To give us feedback or to suggest a new feature, drop us a line on [Gitter](https://gitter.im/pmd/pmd)!
|
||||
To give us feedback or to suggest a new feature, drop us a line in our [Gitter room](https://app.gitter.im/#/room/#pmd_pmd:gitter.im)!
|
||||
|
||||
## Summary
|
||||
|
||||
|
@ -3,7 +3,7 @@ title: Adding PMD support for a new ANTLR grammar based language
|
||||
short_title: Adding a new language with ANTLR
|
||||
tags: [devdocs, extending]
|
||||
summary: "How to add a new language to PMD using ANTLR grammar."
|
||||
last_updated: October 2021
|
||||
last_updated: February 2023 (7.0.0)
|
||||
sidebar: pmd_sidebar
|
||||
permalink: pmd_devdocs_major_adding_new_language_antlr.html
|
||||
folder: pmd/devdocs
|
||||
@ -21,7 +21,7 @@ folder: pmd/devdocs
|
||||
This is really a big contribution and can't be done with a drive by contribution. It requires dedicated passion
|
||||
and long commitment to implement support for a new language.<br><br>
|
||||
|
||||
This step by step guide is just a small intro to get the basics started and it's also not necessarily up-to-date
|
||||
This step-by-step guide is just a small intro to get the basics started and it's also not necessarily up-to-date
|
||||
or complete and you have to be able to fill in the blanks.<br><br>
|
||||
|
||||
Currently the Antlr integration has some basic limitations compared to JavaCC: The output of the
|
||||
@ -86,7 +86,7 @@ definitely don't come for free. It is much effort and requires perseverance to i
|
||||
* You can add additional methods in your "InnerNode" (e.g. `SwiftInnerNode`) that are available on all nodes.
|
||||
But on most cases you won't need to do anything.
|
||||
|
||||
## 4. Generate your parser
|
||||
## 4. Generate your parser (using ANTLR)
|
||||
* Make sure, you have the property `<antlr4.visitor>true</antlr4.visitor>` in your `pom.xml` file.
|
||||
* This is just a matter of building the language module. ANTLR is called via ant, and this step is added
|
||||
to the phase `generate-sources`. So you can just call e.g. `./mvnw generate-sources -pl pmd-swift` to
|
||||
@ -116,23 +116,17 @@ definitely don't come for free. It is much effort and requires perseverance to i
|
||||
implementation that you need to extend to create your own adapter as we do with
|
||||
[`PmdSwiftParser`](https://github.com/pmd/pmd/blob/pmd/7.0.x/pmd-swift/src/main/java/net/sourceforge/pmd/lang/swift/ast/PmdSwiftParser.java).
|
||||
|
||||
## 7. Create a rule violation factory
|
||||
* This is an optional step. Most like, the default implementation will do what you need.
|
||||
The default implementation is [`DefaultRuleViolationFactory`](https://github.com/pmd/pmd/blob/pmd/7.0.x/pmd-core/src/main/java/net/sourceforge/pmd/lang/rule/impl/DefaultRuleViolationFactory.java).
|
||||
* The purpose of a rule violation factory is to create a rule violation instance for your handler (spoiler).
|
||||
In case you want to provide additional data in your rule violation, you can create a custom one. However,
|
||||
adding additional date here is discouraged, as you would need a custom renderer to actually use this
|
||||
additional data. Such extensions are not language agnostic.
|
||||
|
||||
## 8. Create a version handler
|
||||
## 7. Create a language version handler
|
||||
* Now you need to create your version handler, as we did with [`SwiftHandler`](https://github.com/pmd/pmd/blob/pmd/7.0.x/pmd-swift/src/main/java/net/sourceforge/pmd/lang/swift/SwiftHandler.java).
|
||||
* This class is sort of a gateway between PMD and all parsing logic specific to your language. It has 2 purposes:
|
||||
* `getRuleViolationFactory` method returns an instance of your rule violation factory *(see step #7)*.
|
||||
By default, this returns the default rule violation factory.
|
||||
* `getParser` returns an instance of your parser adapter *(see step #6)*.
|
||||
That's the only method, that needs to be implemented here.
|
||||
* This class is sort of a gateway between PMD and all parsing logic specific to your language.
|
||||
* For a minimal implementation, it just needs to return a parser *(see step #6)*.
|
||||
* It can be used to provide other features for your language like
|
||||
* violation suppression logic
|
||||
* violation decorators, to add additional language specific information to the created violations
|
||||
* metrics
|
||||
* custom XPath functions
|
||||
|
||||
## 9. Create a parser visitor adapter
|
||||
## 8. Create a base visitor
|
||||
* A parser visitor adapter is not needed anymore with PMD 7. The visitor interface now provides a default
|
||||
implementation.
|
||||
* The visitor for ANTLR based AST is generated along the parser from the ANTLR grammar file. The
|
||||
@ -142,21 +136,16 @@ definitely don't come for free. It is much effort and requires perseverance to i
|
||||
See [`SwiftVisitorBase`](https://github.com/pmd/pmd/blob/pmd/7.0.x/pmd-swift/src/main/java/net/sourceforge/pmd/lang/swift/ast/SwiftVisitorBase.java)
|
||||
as an example.
|
||||
|
||||
## 10. Create a rule chain visitor
|
||||
* This step is not needed anymore. For using rule chain, there is no additional adjustment necessary anymore
|
||||
in the languages.
|
||||
* This feature has been merged into AbstractRule via the overridable method
|
||||
{% jdoc !!core::lang.rule.AbstractRule#buildTargetSelector() %}. Individual rules can make use of this optimization
|
||||
by overriding this method and return an appropriate RuleTargetSelector.
|
||||
|
||||
## 11. Make PMD recognize your language
|
||||
* Create your own subclass of `net.sourceforge.pmd.lang.BaseLanguageModule`, see Swift as an example:
|
||||
## 9. Make PMD recognize your language
|
||||
* Create your own subclass of `net.sourceforge.pmd.lang.impl.SimpleLanguageModuleBase`, see Swift as an example:
|
||||
[`SwiftLanguageModule`](https://github.com/pmd/pmd/blob/pmd/7.0.x/pmd-swift/src/main/java/net/sourceforge/pmd/lang/swift/SwiftLanguageModule.java).
|
||||
* Add your default version with `addDefaultVersion` in your language module's constructor.
|
||||
* Add for each additional version of your language a call to `addVersion` as well.
|
||||
* Create the service registration via the text file `src/main/resources/META-INF/services/net.sourceforge.pmd.lang.Language`. Add your fully qualified class name as a single line into it.
|
||||
* Add for each version of your language a call to `addVersion` in your language module’s constructor.
|
||||
Use `addDefaultVersion` for defining the default version.
|
||||
* You’ll need to refer the language version handler created in step #7.
|
||||
* Create the service registration via the text file `src/main/resources/META-INF/services/net.sourceforge.pmd.lang.Language`.
|
||||
Add your fully qualified class name as a single line into it.
|
||||
|
||||
## 12. Create an abstract rule class for the language
|
||||
## 10. Create an abstract rule class for the language
|
||||
* You need to create your own `AbstractRule` in order to interface your language with PMD's generic rule
|
||||
execution.
|
||||
* See [`AbstractSwiftRule`](https://github.com/pmd/pmd/blob/pmd/7.0.x/pmd-swift/src/main/java/net/sourceforge/pmd/lang/swift/AbstractSwiftRule.java) as an example.
|
||||
@ -167,7 +156,7 @@ definitely don't come for free. It is much effort and requires perseverance to i
|
||||
via the method `buildVisitor()` for analyzing the AST. The provided visitor only implements the visit methods
|
||||
for specific AST nodes. The other node types use the default behavior and you don't need to care about them.
|
||||
|
||||
## 13. Create rules
|
||||
## 11. Create rules
|
||||
* 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.
|
||||
* PMD supports 2 types of rules, through visitors or XPath.
|
||||
@ -179,15 +168,19 @@ definitely don't come for free. It is much effort and requires perseverance to i
|
||||
* To add an XPath rule you can follow our guide [Writing XPath Rules](pmd_userdocs_extending_writing_xpath_rules.html).
|
||||
|
||||
## 14. Test the rules
|
||||
* See UnavailableFunctionRuleTest for example. Each rule has it's own test class.
|
||||
* You have to create the category rule set for your language *(see pmd-swift/src/main/resources/bestpractices.xml for example)*
|
||||
* When executing the test class
|
||||
* this triggers the unit test to read the corresponding XML file with the rule test data
|
||||
*(see `UnavailableFunctionRule.xml` for example)*
|
||||
* This test XML file contains sample pieces of code which should trigger a specified number of
|
||||
violations of this rule. The unit test will execute the rule on this piece of code, and verify
|
||||
that the number of violations matches.
|
||||
* To verify the validity of all the created rulesets, create a subclass of `AbstractRuleSetFactoryTest` (*see `RuleSetFactoryTest` in pmd-swift for example)*.
|
||||
* Testing rules is described in depth in [Testing your rules](pmd_userdocs_extending_testing.html).
|
||||
* Each rule has its own test class: Create a test class for your rule extending `PmdRuleTst`
|
||||
*(see UnavailableFunctionTest for example)*
|
||||
* Create a category rule set for your language *(see pmd-swift/src/main/resources/bestpractices.xml for example)*
|
||||
* Place the test XML file with the test cases in the correct location
|
||||
* When executing the test class
|
||||
* this triggers the unit test to read the corresponding XML file with the rule test data
|
||||
*(see `UnavailableFunction.xml` for example)*
|
||||
* This test XML file contains sample pieces of code which should trigger a specified number of
|
||||
violations of this rule. The unit test will execute the rule on this piece of code, and verify
|
||||
that the number of violations matches.
|
||||
* To verify the validity of all the created rulesets, create a subclass of `AbstractRuleSetFactoryTest`
|
||||
(*see `RuleSetFactoryTest` in pmd-swift for example)*.
|
||||
This will load all rulesets and verify, that all required attributes are provided.
|
||||
|
||||
*Note:* You'll need to add your ruleset to `categories.properties`, so that it can be found.
|
||||
|
@ -3,7 +3,7 @@ title: Adding PMD support for a new JavaCC grammar based language
|
||||
short_title: Adding a new language with JavaCC
|
||||
tags: [devdocs, extending]
|
||||
summary: "How to add a new language to PMD using JavaCC grammar."
|
||||
last_updated: October 2021
|
||||
last_updated: February 2023 (7.0.0)
|
||||
sidebar: pmd_sidebar
|
||||
permalink: pmd_devdocs_major_adding_new_language_javacc.html
|
||||
folder: pmd/devdocs
|
||||
@ -16,7 +16,7 @@ folder: pmd/devdocs
|
||||
This is really a big contribution and can't be done with a drive by contribution. It requires dedicated passion
|
||||
and long commitment to implement support for a new language.<br><br>
|
||||
|
||||
This step by step guide is just a small intro to get the basics started and it's also not necessarily up-to-date
|
||||
This step-by-step guide is just a small intro to get the basics started and it's also not necessarily up-to-date
|
||||
or complete and you have to be able to fill in the blanks.<br><br>
|
||||
|
||||
After the basic support for a language is there, there are lots of missing features left. Typical features
|
||||
@ -39,68 +39,75 @@ definitely don't come for free. It is much effort and requires perseverance to i
|
||||
|
||||
## 2. Implement an AST parser for your language
|
||||
* Ideally an AST parser should be implemented as a JJT file *(see VmParser.jjt or Java.jjt for example)*
|
||||
* There is nothing preventing any other parser implementation, as long as you have some way to convert an input stream into an AST tree. Doing it as a JJT simplifies maintenance down the road.
|
||||
* There is nothing preventing any other parser implementation, as long as you have some way to convert an input
|
||||
stream into an AST tree. Doing it as a JJT simplifies maintenance down the road.
|
||||
* See this link for reference: [https://javacc.java.net/doc/JJTree.html](https://javacc.java.net/doc/JJTree.html)
|
||||
|
||||
## 3. Create AST node classes
|
||||
* For each AST node that your parser can generate, there should be a class
|
||||
* The name of the AST class should be “AST” + “whatever is the name of the node in JJT file”.
|
||||
* For example, if JJT contains a node called “IfStatement”, there should be a class called “ASTIfStatement”
|
||||
* Each AST class should have two constructors: one that takes an int id; and one that takes an instance of the parser, and an int id
|
||||
* It’s a good idea to create a parent AST class for all AST classes of the language. This simplifies rule creation later. *(see SimpleNode for Velocity and AbstractJavaNode for Java for example)*
|
||||
* Each AST class should have one package-private constructor, that takes an `int id`.
|
||||
* It’s a good idea to create a parent AST class for all AST classes of the language. This simplifies rule
|
||||
creation later. *(see SimpleNode for Velocity and AbstractJavaNode for Java for example)*
|
||||
* Note: These AST node classes are generated usually once by javacc/jjtree and can then be modified as needed.
|
||||
|
||||
## 4. Compile your parser (if using JJT)
|
||||
* An ant script is being used to compile jjt files into classes. This is in `pmd-<lang>/src/main/ant/alljavacc.xml` file.
|
||||
* Create `alljavacc.xml` file for your language, you can use one from `pmd-java` as an example.
|
||||
* You would probably want to adjust contents of the `<delete>` tag: start with an empty `<fileset>` and add there `<include>`s for those AST nodes you had to manually rewrite (moving those node classes from autogenerated directory to the regular source tree).
|
||||
## 4. Generate your parser (using JJT)
|
||||
* An ant script is being used to compile jjt files into classes. This is in `javacc-wrapper.xml` file in the
|
||||
top-level pmd sources.
|
||||
* The ant script is executed via the `maven-antrun-plugin`. Add this plugin to your `pom.xml` file and configure
|
||||
it the language name. You can use `pmd-java/pom.xml` as an example.
|
||||
* The ant script is called in the phase `generate-sources` whenever the whole project is built. But you can
|
||||
call `./mvnw generate-sources` directly for your module if you want your parser to be generated.
|
||||
|
||||
## 5. Create a TokenManager
|
||||
* Create a new class that implements the `TokenManager` interface *(see VmTokenManager or JavaTokenManager for example)*
|
||||
|
||||
## 6. Create a PMD parser “adapter”
|
||||
* Create a new class that extends AbstractParser
|
||||
## 5. Create a PMD parser “adapter”
|
||||
* Create a new class that extends `JjtreeParserAdapter`.
|
||||
* This is a generic class, and you need to declare the root AST node.
|
||||
* There are two important methods to implement
|
||||
* `createTokenManager` method should return a new instance of a token manager for your language *(see step #5)*
|
||||
* `parse` method should return the root node of the AST tree obtained by parsing the Reader source
|
||||
* `tokenBehavior` method should return a new instance of `TokenDocumentBehavior` constructed with the list
|
||||
of tokes in your language. The compile step #4 will generate a class `$langTokenKinds` which has
|
||||
all the available tokens in the field `TOKEN_NAMES`.
|
||||
* `parseImpl` method should return the root node of the AST tree obtained by parsing the CharStream source
|
||||
* See `VmParser` class as an example
|
||||
|
||||
## 7. Create a rule violation factory
|
||||
* Extend `AbstractRuleViolationFactory` *(see VmRuleViolationFactory for example)*
|
||||
* The purpose of this class is to create a rule violation instance specific to your language
|
||||
## 6. Create a language version handler
|
||||
* Extend `AbstractPmdLanguageVersionHandler` *(see VmHandler for example)*
|
||||
* This class is sort of a gateway between PMD and all parsing logic specific to your language.
|
||||
* For a minimal implementation, it just needs to return a parser *(see step #5)*.
|
||||
* It can be used to provide other features for your language like
|
||||
* violation suppression logic
|
||||
* violation decorators, to add additional language specific information to the created violations
|
||||
* metrics (see below "Optional features")
|
||||
* custom XPath functions
|
||||
* See `VmHandler` class as an example
|
||||
|
||||
## 8. Create a version handler
|
||||
* Extend `AbstractLanguageVersionHandler` *(see VmHandler for example)*
|
||||
* This class is sort of a gateway between PMD and all parsing logic specific to your language. It has 2 purposes:
|
||||
* `getRuleViolationFactory` method returns an instance of your rule violation factory *(see step #7)*
|
||||
* `getParser` returns an instance of your parser adapter *(see step #6)*
|
||||
## 7. Create a base visitor
|
||||
* A parser visitor adapter is not needed anymore with PMD 7. The visitor interface now provides a default
|
||||
implementation.
|
||||
* The visitor for JavaCC based AST is generated along the parser from the grammar file. The
|
||||
base interface for a visitor is [`AstVisitor`](https://github.com/pmd/pmd/blob/pmd/7.0.x/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/AstVisitor.java).
|
||||
* The generated visitor class for VM is called `VmVisitor`.
|
||||
* In order to help use this visitor later on, a base visitor class should be created.
|
||||
See `VmVisitorBase` as an example.
|
||||
|
||||
## 9. Create a parser visitor adapter
|
||||
* If you use JJT to generate your parser, it should also generate an interface for a parser visitor *(see VmParserVisitor for example)*
|
||||
* Create a class that implements this auto-generated interface *(see VmParserVisitorAdapter for example)*
|
||||
* The purpose of this class is to serve as a pass-through `visitor` implementation, which, for all AST types in your language, just executes visit on the base AST type
|
||||
|
||||
## 10. Create a rule chain visitor
|
||||
* Extend `AbstractRuleChainVisitor` *(see VmRuleChainVisitor for example)*
|
||||
* This class should `implement` two `important` methods:
|
||||
* `indexNodes` generates a map of "node type" to "list of nodes of that type". This is used to visit all applicable nodes when a rule is applied.
|
||||
* `visit` method should evaluate what kind of rule is being applied, and execute appropriate logic. Usually it will just check if the rule is a "parser visitor" kind of rule specific to your language, then execute the visitor. If it’s an XPath rule, then we just need to execute evaluate on that.
|
||||
|
||||
## 11. Make PMD recognize your language
|
||||
* Create your own subclass of `net.sourceforge.pmd.lang.BaseLanguageModule`. *(see VmLanguageModule or JavaLanguageModule as an example)*
|
||||
* You’ll need to refer the rule chain visitor created in step #10.
|
||||
## 8. Make PMD recognize your language
|
||||
* Create your own subclass of `net.sourceforge.pmd.lang.impl.SimpleLanguageModuleBase`. *(see VmLanguageModule or
|
||||
JavaLanguageModule as an example)*
|
||||
* Add for each version of your language a call to `addVersion` in your language module’s constructor.
|
||||
* Create the service registration via the text file `src/main/resources/META-INF/services/net.sourceforge.pmd.lang.Language`. Add your fully qualified class name as a single line into it.
|
||||
Use `addDefaultVersion` for defining the default version.
|
||||
* You’ll need to refer the language version handler created in step #6.
|
||||
* Create the service registration via the text file `src/main/resources/META-INF/services/net.sourceforge.pmd.lang.Language`.
|
||||
Add your fully qualified class name as a single line into it.
|
||||
|
||||
## 12. Add AST regression tests
|
||||
## 9. Add AST regression tests
|
||||
|
||||
For languages, that use an external library for parsing, the AST can easily change when upgrading the library.
|
||||
Also for languages, where we have the grammar under our control, it useful to have such tests.
|
||||
Also for languages, where we have the grammar under our control, it is useful to have such tests.
|
||||
|
||||
The tests parse one or more source files and generate a textual representation of the AST. This text is compared
|
||||
against a previously recorded version. If there are differences, the test fails.
|
||||
|
||||
This helps to detect anything in the AST structure, that changed, maybe unexpectedly.
|
||||
This helps to detect anything in the AST structure that changed, maybe unexpectedly.
|
||||
|
||||
* Create a test class in the package `net.sourceforge.pmd.lang.$lang.ast` with the name `$langTreeDumpTest`.
|
||||
* This test class must extend `net.sourceforge.pmd.lang.ast.test.BaseTreeDumpTest`. Note: This class
|
||||
@ -116,7 +123,7 @@ This helps to detect anything in the AST structure, that changed, maybe unexpect
|
||||
Replace "$lang" and "$extension" accordingly.
|
||||
* Implement the method `getParser()`. It must return a
|
||||
subclass of `net.sourceforge.pmd.lang.ast.test.BaseParsingHelper`. See
|
||||
`net.sourceforge.pmd.lang.ecmascript.ast.JsParsingHelper` for a example.
|
||||
`net.sourceforge.pmd.lang.ecmascript.ast.JsParsingHelper` for an example.
|
||||
With this parser helper you can also specify, where the test files are searched, by using
|
||||
the method `withResourceContext(Class<?>, String)`.
|
||||
* Add one or more test methods. Each test method parses one file and compares the result. The base
|
||||
@ -139,24 +146,35 @@ The Scala module also has a test, written in Kotlin instead of Java:
|
||||
`net.sourceforge.pmd.lang.scala.ast.ScalaParserTests`.
|
||||
|
||||
|
||||
## 13. Create an abstract rule class for the language
|
||||
## 10. Create an abstract rule class for the language
|
||||
* Extend `AbstractRule` and implement the parser visitor interface for your language *(see AbstractVmRule for example)*
|
||||
* 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.
|
||||
* 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.
|
||||
|
||||
## 14. Create rules
|
||||
* Rules are created by extending the abstract rule class created in step 13 *(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.
|
||||
## 11. Create rules
|
||||
* Rules are created by extending the abstract rule class created in step 9 *(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.
|
||||
|
||||
## 15. Test the rules
|
||||
* See BasicRulesTest for example
|
||||
* You have to create a rule set for your language *(see vm/basic.xml for example)*
|
||||
* For each rule in this set you want to test, call `addRule` method in setUp of the unit test
|
||||
* This triggers the unit test to read the corresponding XML file with rule test data *(see `EmptyForeachStmtRule.xml` for example)*
|
||||
* This test XML file contains sample pieces of code which should trigger a specified number of violations of this rule. The unit test will execute the rule on this piece of code, and verify that the number of violations matches
|
||||
* To verify the validity of the created ruleset, create a subclass of `AbstractRuleSetFactoryTest` (*see `RuleSetFactoryTest` in pmd-vm for example)*.
|
||||
## 12. Test the rules
|
||||
* Testing rules is described in depth in [Testing your rules](pmd_userdocs_extending_testing.html).
|
||||
* Each rule has its own test class: Create a test class for your rule extending `PmdRuleTst`
|
||||
*(see AvoidReassigningParametersTest in pmd-vm for example)*
|
||||
* Create a category rule set for your language *(see category/vm/bestpractices.xml for example)*
|
||||
* Place the test XML file with the test cases in the correct location
|
||||
* When executing the test class
|
||||
* this triggers the unit test to read the corresponding XML file with the rule test data
|
||||
*(see `AvoidReassigningParameters.xml` for example)*
|
||||
* This test XML file contains sample pieces of code which should trigger a specified number of
|
||||
violations of this rule. The unit test will execute the rule on this piece of code, and verify
|
||||
that the number of violations matches.
|
||||
* To verify the validity of the created ruleset, create a subclass of `AbstractRuleSetFactoryTest`
|
||||
(*see `RuleSetFactoryTest` in pmd-vm for example)*.
|
||||
This will load all rulesets and verify, that all required attributes are provided.
|
||||
|
||||
*Note:* You'll need to add your ruleset to `rulesets.properties`, so that it can be found.
|
||||
*Note:* You'll need to add your category ruleset to `categories.properties`, so that it can be found.
|
||||
|
||||
## Debugging with Rule Designer
|
||||
|
||||
|
87
docs/pages/pmd/languages/language_properties.md
Normal file
87
docs/pages/pmd/languages/language_properties.md
Normal file
@ -0,0 +1,87 @@
|
||||
---
|
||||
title: Language configuration
|
||||
permalink: pmd_languages_configuration.html
|
||||
author: Clément Fournier
|
||||
last_updated: July 2022 (7.0.0)
|
||||
tags: [languages]
|
||||
keywords: [pmd, cpd, options, command, auxclasspath, language, properties]
|
||||
summary: "Summary of language configuration options and properties"
|
||||
---
|
||||
|
||||
# Language properties
|
||||
|
||||
Since PMD 7.0.0, languages may be directly configured via properties.
|
||||
The properties can be specified via environment variables or programmatically.
|
||||
|
||||
The name of the environment variables follow the following pattern,
|
||||
completely in uppercase:
|
||||
|
||||
PMD_<LanguageId>_<PropertyName>
|
||||
|
||||
LanguageId is the short name of the language, which is being configured. This is e.g. "JAVA" or "APEX".
|
||||
|
||||
PropertyName is the name of the property converted to SCREAMING_SNAKE_CASE, that is set to a specific value, e.g. "SUPPRESS_MARKER" for "suppressMarker".
|
||||
|
||||
As a convention, properties whose name start with an *x* are internal and may be removed or changed without notice.
|
||||
|
||||
Programmatically, the language properties can be set on `PMDConfiguration` before using the PmdAnalyzer instance
|
||||
to start the analysis:
|
||||
|
||||
```java
|
||||
PMDConfiguration configuration = new PMDConfiguration();
|
||||
LanguagePropertyBundle properties = configuration.getLanguageProperties(LanguageRegistry.PMD.getLanguageById("java"));
|
||||
properties.setProperty(LanguagePropertyBundle.SUPPRESS_MARKER, "PMD");
|
||||
```
|
||||
|
||||
## Common language properties
|
||||
|
||||
All languages support the following properties:
|
||||
|
||||
- `suppressMarker`: A string to detect suppression comments. The default is `NOPMD`, so e.g. in Java, a
|
||||
comment `// NOPMD` will suppress warnings on the same line.
|
||||
|
||||
This property can also be set via the CLI option `--suppress-marker`. The CLI option applies for all languages
|
||||
and overrides any language property.
|
||||
|
||||
- `version`: The language version PMD should use when parsing source code. If not specified, the default
|
||||
version of the language will be used.
|
||||
|
||||
This property can also be set via the CLI option `--use-version`.
|
||||
|
||||
## Java language properties
|
||||
|
||||
The Java language can be configured with the following properties:
|
||||
|
||||
- `auxClasspath`: Classpath on which to find compiled classes for the language
|
||||
|
||||
This property can also be set via the CLI option `--aux-classpath`.
|
||||
|
||||
Environment variable: `PMD_JAVA_AUX_CLASSPATH`
|
||||
|
||||
- `xTypeInferenceLogging`: Verbosity of type inference logging, possible values `DISABLED`, `SIMPLE`, `VERBOSE`.
|
||||
|
||||
Environment variable: `PMD_JAVA_X_TYPE_INFERENCE_LOGGING`
|
||||
|
||||
## Apex language properties
|
||||
|
||||
- `rootDirectory`: With this property the root directory of the Salesforce metadata, where `sfdx-project.json`
|
||||
resides, is specified. [ApexLink](https://github.com/nawforce/ApexLink) can then load all the classes
|
||||
in the project and figure out, whether a method is used or not.
|
||||
|
||||
This property is needed for {% rule apex/design/UnusedMethod %}.
|
||||
|
||||
Environment variable: `PMD_APEX_ROOT_DIRECTORY`
|
||||
|
||||
## VisualForce language properties
|
||||
|
||||
- `apexDirectories`: Comma separated list of directories for Apex classes. Absolute
|
||||
or relative to the Visualforce directory. Default is `../classes`. Specifying an
|
||||
empty string will disable data type resolution for Apex Controller properties.
|
||||
|
||||
Environment variable: `PMD_VF_APEX_DIRECTORIES`
|
||||
|
||||
- `objectsDirectories`: Comma separated list of directories for Custom Objects.
|
||||
Absolute or relative to the Visualforce directory. Default is `../objects`.
|
||||
Specifying an empty string will disable data type resolution for Custom Object fields.
|
||||
|
||||
Environment variable: `PMD_VF_OBJECTS_DIRECTORIES`
|
File diff suppressed because it is too large
Load Diff
@ -19,11 +19,30 @@ This is a {{ site.pmd.release_type }} release.
|
||||
|
||||
### New and noteworthy
|
||||
|
||||
#### T-SQL support
|
||||
Thanks to the contribution from [Paul Guyot](https://github.com/pguyot) PMD now has CPD support
|
||||
for T-SQL (Transact-SQL).
|
||||
|
||||
Being based on a proper Antlr grammar, CPD can:
|
||||
|
||||
* ignore comments
|
||||
* honor [comment-based suppressions](pmd_userdocs_cpd.html#suppression)
|
||||
|
||||
### Fixed Issues
|
||||
* java-errorprone
|
||||
* [#4393](https://github.com/pmd/pmd/issues/4393): \[java] MissingStaticMethodInNonInstantiatableClass false-positive for Lombok's @UtilityClass for classes with non-private fields
|
||||
|
||||
### API Changes
|
||||
|
||||
* The LanguageModule of Go, that only supports CPD execution, has been deprecated. This language
|
||||
is not fully supported by PMD, so having a language module does not make sense. The functionality of CPD is
|
||||
not affected by this change. The following class has been deprecated and will be removed with PMD 7.0.0:
|
||||
* {% jdoc go::lang.go.GoLanguageModule %}
|
||||
|
||||
### External Contributions
|
||||
* [#4384](https://github.com/pmd/pmd/pull/4384): \[swift] Add more swift 5.x support (#unavailable mainly) - [Richard B.](https://github.com/kenji21) (@kenji21)
|
||||
* [#4390](https://github.com/pmd/pmd/pull/4390): Add support for T-SQL using Antlr4 lexer - [Paul Guyot](https://github.com/pguyot) (@pguyot)
|
||||
* [#4392](https://github.com/pmd/pmd/pull/4392): \[java] Fix #4393 MissingStaticMethodInNonInstantiatableClass: Fix false-positive for field-only class - [Dawid Ciok](https://github.com/dawiddc) (@dawiddc)
|
||||
|
||||
{% endtocmaker %}
|
||||
|
||||
|
@ -1,8 +1,8 @@
|
||||
/*
|
||||
/**
|
||||
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
|
||||
*/
|
||||
|
||||
package net.sourceforge.pmd.lang.apex.internal;
|
||||
package net.sourceforge.pmd.lang.apex;
|
||||
|
||||
import static net.sourceforge.pmd.util.CollectionUtil.setOf;
|
||||
|
||||
@ -10,34 +10,27 @@ import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import net.sourceforge.pmd.ViolationSuppressor;
|
||||
import net.sourceforge.pmd.lang.AbstractPmdLanguageVersionHandler;
|
||||
import net.sourceforge.pmd.lang.apex.ApexLanguageModule;
|
||||
import net.sourceforge.pmd.lang.LanguageVersionHandler;
|
||||
import net.sourceforge.pmd.lang.apex.ast.ApexParser;
|
||||
import net.sourceforge.pmd.lang.apex.internal.ApexDesignerBindings;
|
||||
import net.sourceforge.pmd.lang.apex.metrics.ApexMetrics;
|
||||
import net.sourceforge.pmd.lang.ast.Parser;
|
||||
import net.sourceforge.pmd.lang.metrics.LanguageMetricsProvider;
|
||||
import net.sourceforge.pmd.lang.metrics.Metric;
|
||||
import net.sourceforge.pmd.properties.PropertySource;
|
||||
import net.sourceforge.pmd.util.designerbindings.DesignerBindings;
|
||||
|
||||
public class ApexHandler extends AbstractPmdLanguageVersionHandler {
|
||||
class ApexLanguageHandler implements LanguageVersionHandler {
|
||||
|
||||
private final ApexMetricsProvider myMetricsProvider = new ApexMetricsProvider();
|
||||
|
||||
@Override
|
||||
public List<ViolationSuppressor> getExtraViolationSuppressors() {
|
||||
return ApexViolationSuppressors.ALL_APEX_SUPPRESSORS;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Parser getParser() {
|
||||
return new ApexParser();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void declareParserTaskProperties(PropertySource source) {
|
||||
source.definePropertyDescriptor(ApexParser.MULTIFILE_DIRECTORY);
|
||||
overridePropertiesFromEnv(ApexLanguageModule.TERSE_NAME, source);
|
||||
public List<ViolationSuppressor> getExtraViolationSuppressors() {
|
||||
return ApexViolationSuppressors.ALL_APEX_SUPPRESSORS;
|
||||
}
|
||||
|
||||
@Override
|
@ -4,23 +4,32 @@
|
||||
|
||||
package net.sourceforge.pmd.lang.apex;
|
||||
|
||||
import static net.sourceforge.pmd.util.CollectionUtil.listOf;
|
||||
|
||||
import net.sourceforge.pmd.lang.BaseLanguageModule;
|
||||
import net.sourceforge.pmd.lang.Language;
|
||||
import net.sourceforge.pmd.lang.LanguageModuleBase;
|
||||
import net.sourceforge.pmd.lang.LanguageProcessor;
|
||||
import net.sourceforge.pmd.lang.LanguagePropertyBundle;
|
||||
import net.sourceforge.pmd.lang.LanguageRegistry;
|
||||
import net.sourceforge.pmd.lang.apex.internal.ApexHandler;
|
||||
|
||||
import apex.jorje.services.Version;
|
||||
|
||||
public class ApexLanguageModule extends BaseLanguageModule {
|
||||
public class ApexLanguageModule extends LanguageModuleBase {
|
||||
|
||||
public static final String NAME = "Apex";
|
||||
public static final String TERSE_NAME = "apex";
|
||||
|
||||
public ApexLanguageModule() {
|
||||
super(NAME, null, TERSE_NAME, listOf("cls", "trigger"));
|
||||
addVersion(String.valueOf((int) Version.CURRENT.getExternal()), new ApexHandler(), true);
|
||||
super(LanguageMetadata.withId(TERSE_NAME).name(NAME).extensions("cls", "trigger")
|
||||
.addDefaultVersion(String.valueOf((int) Version.CURRENT.getExternal())));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ApexLanguageProperties newPropertyBundle() {
|
||||
return new ApexLanguageProperties();
|
||||
}
|
||||
|
||||
@Override
|
||||
public LanguageProcessor createProcessor(LanguagePropertyBundle bundle) {
|
||||
return new ApexLanguageProcessor((ApexLanguageProperties) bundle);
|
||||
}
|
||||
|
||||
public static Language getInstance() {
|
||||
|
@ -0,0 +1,34 @@
|
||||
/**
|
||||
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
|
||||
*/
|
||||
|
||||
package net.sourceforge.pmd.lang.apex;
|
||||
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
|
||||
import net.sourceforge.pmd.lang.LanguageVersionHandler;
|
||||
import net.sourceforge.pmd.lang.apex.multifile.ApexMultifileAnalysis;
|
||||
import net.sourceforge.pmd.lang.impl.BatchLanguageProcessor;
|
||||
|
||||
public class ApexLanguageProcessor
|
||||
extends BatchLanguageProcessor<ApexLanguageProperties> {
|
||||
|
||||
private final ApexMultifileAnalysis multifileAnalysis;
|
||||
private final ApexLanguageHandler services;
|
||||
|
||||
ApexLanguageProcessor(ApexLanguageProperties bundle) {
|
||||
super(bundle);
|
||||
this.multifileAnalysis = new ApexMultifileAnalysis(bundle);
|
||||
this.services = new ApexLanguageHandler();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull LanguageVersionHandler services() {
|
||||
return services;
|
||||
}
|
||||
|
||||
public ApexMultifileAnalysis getMultiFileState() {
|
||||
return multifileAnalysis;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
/*
|
||||
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
|
||||
*/
|
||||
|
||||
package net.sourceforge.pmd.lang.apex;
|
||||
|
||||
import net.sourceforge.pmd.lang.LanguagePropertyBundle;
|
||||
import net.sourceforge.pmd.properties.PropertyDescriptor;
|
||||
import net.sourceforge.pmd.properties.PropertyFactory;
|
||||
|
||||
/**
|
||||
* @author Clément Fournier
|
||||
*/
|
||||
public class ApexLanguageProperties extends LanguagePropertyBundle {
|
||||
|
||||
// todo change that to optional<file> when properties are updated
|
||||
public static final PropertyDescriptor<String> MULTIFILE_DIRECTORY =
|
||||
PropertyFactory.stringProperty("rootDirectory")
|
||||
.desc("The root directory of the Salesforce metadata, where `sfdx-project.json` resides.")
|
||||
.defaultValue("") // is this ok?
|
||||
.build();
|
||||
|
||||
public ApexLanguageProperties() {
|
||||
super(ApexLanguageModule.getInstance());
|
||||
definePropertyDescriptor(MULTIFILE_DIRECTORY);
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -2,7 +2,7 @@
|
||||
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
|
||||
*/
|
||||
|
||||
package net.sourceforge.pmd.lang.apex.internal;
|
||||
package net.sourceforge.pmd.lang.apex;
|
||||
|
||||
import static net.sourceforge.pmd.util.CollectionUtil.listOf;
|
||||
|
||||
@ -31,7 +31,7 @@ import net.sourceforge.pmd.lang.apex.ast.ASTVariableDeclarationStatements;
|
||||
import net.sourceforge.pmd.lang.apex.ast.ApexNode;
|
||||
import net.sourceforge.pmd.lang.ast.Node;
|
||||
|
||||
public final class ApexViolationSuppressors {
|
||||
final class ApexViolationSuppressors {
|
||||
|
||||
private static final ViolationSuppressor APEX_ANNOT_SUPPRESSOR = new ViolationSuppressor() {
|
||||
@Override
|
@ -11,6 +11,7 @@ import java.util.Map;
|
||||
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
|
||||
import net.sourceforge.pmd.lang.apex.ApexLanguageProcessor;
|
||||
import net.sourceforge.pmd.lang.apex.multifile.ApexMultifileAnalysis;
|
||||
import net.sourceforge.pmd.lang.ast.AstInfo;
|
||||
import net.sourceforge.pmd.lang.ast.Parser.ParserTask;
|
||||
@ -29,10 +30,10 @@ public final class ASTApexFile extends AbstractApexNode<AstNode> implements Root
|
||||
ASTApexFile(ParserTask task,
|
||||
Compilation jorjeNode,
|
||||
Map<Integer, String> suppressMap,
|
||||
@NonNull ApexMultifileAnalysis multifileAnalysis) {
|
||||
@NonNull ApexLanguageProcessor apexLang) {
|
||||
super(jorjeNode);
|
||||
this.astInfo = new AstInfo<>(task, this, suppressMap);
|
||||
this.multifileAnalysis = multifileAnalysis;
|
||||
this.astInfo = new AstInfo<>(task, this).withSuppressMap(suppressMap);
|
||||
this.multifileAnalysis = apexLang.getMultiFileState();
|
||||
this.setRegion(TextRegion.fromOffsetLength(0, task.getTextDocument().getLength()));
|
||||
}
|
||||
|
||||
|
@ -6,11 +6,9 @@ package net.sourceforge.pmd.lang.apex.ast;
|
||||
|
||||
import net.sourceforge.pmd.annotation.InternalApi;
|
||||
import net.sourceforge.pmd.lang.apex.ApexJorjeLogging;
|
||||
import net.sourceforge.pmd.lang.apex.multifile.ApexMultifileAnalysis;
|
||||
import net.sourceforge.pmd.lang.apex.ApexLanguageProcessor;
|
||||
import net.sourceforge.pmd.lang.ast.ParseException;
|
||||
import net.sourceforge.pmd.lang.ast.Parser;
|
||||
import net.sourceforge.pmd.properties.PropertyDescriptor;
|
||||
import net.sourceforge.pmd.properties.PropertyFactory;
|
||||
|
||||
import apex.jorje.data.Locations;
|
||||
import apex.jorje.semantic.ast.compilation.Compilation;
|
||||
@ -18,14 +16,6 @@ import apex.jorje.semantic.ast.compilation.Compilation;
|
||||
@InternalApi
|
||||
public final class ApexParser implements Parser {
|
||||
|
||||
@InternalApi // todo change that to optional<file> when properties are updated
|
||||
public static final PropertyDescriptor<String> MULTIFILE_DIRECTORY =
|
||||
PropertyFactory.stringProperty("rootDirectory")
|
||||
.desc("The root directory of the Salesforce metadata, where `sfdx-project.json` resides. "
|
||||
+ "Set environment variable PMD_APEX_ROOTDIRECTORY to use this.")
|
||||
.defaultValue("") // is this ok?
|
||||
.build();
|
||||
|
||||
public ApexParser() {
|
||||
ApexJorjeLogging.disableLogging();
|
||||
Locations.useIndexFactory();
|
||||
@ -39,12 +29,8 @@ public final class ApexParser implements Parser {
|
||||
|
||||
assert astRoot != null : "Normally replaced by Compilation.INVALID";
|
||||
|
||||
String property = task.getProperties().getProperty(MULTIFILE_DIRECTORY);
|
||||
ApexMultifileAnalysis analysisHandler = ApexMultifileAnalysis.getAnalysisInstance(property);
|
||||
|
||||
|
||||
final ApexTreeBuilder treeBuilder = new ApexTreeBuilder(task);
|
||||
return treeBuilder.buildTree(astRoot, analysisHandler);
|
||||
final ApexTreeBuilder treeBuilder = new ApexTreeBuilder(task, (ApexLanguageProcessor) task.getLanguageProcessor());
|
||||
return treeBuilder.buildTree(astRoot);
|
||||
} catch (apex.jorje.services.exception.ParseException e) {
|
||||
throw new ParseException(e).setFileName(task.getFileDisplayName());
|
||||
}
|
||||
|
@ -17,7 +17,7 @@ import java.util.function.Function;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import net.sourceforge.pmd.lang.apex.multifile.ApexMultifileAnalysis;
|
||||
import net.sourceforge.pmd.lang.apex.ApexLanguageProcessor;
|
||||
import net.sourceforge.pmd.lang.ast.Parser.ParserTask;
|
||||
import net.sourceforge.pmd.lang.document.Chars;
|
||||
import net.sourceforge.pmd.lang.document.TextDocument;
|
||||
@ -252,12 +252,14 @@ final class ApexTreeBuilder extends AstVisitor<AdditionalPassScope> {
|
||||
|
||||
private final TextDocument sourceCode;
|
||||
private final ParserTask task;
|
||||
private final ApexLanguageProcessor proc;
|
||||
private final CommentInformation commentInfo;
|
||||
|
||||
ApexTreeBuilder(ParserTask task) {
|
||||
ApexTreeBuilder(ParserTask task, ApexLanguageProcessor proc) {
|
||||
this.sourceCode = task.getTextDocument();
|
||||
this.task = task;
|
||||
commentInfo = extractInformationFromComments(sourceCode, task.getCommentMarker());
|
||||
this.proc = proc;
|
||||
commentInfo = extractInformationFromComments(sourceCode, proc.getProperties().getSuppressMarker());
|
||||
}
|
||||
|
||||
static <T extends AstNode> AbstractApexNode<T> createNodeAdapter(T node) {
|
||||
@ -273,9 +275,9 @@ final class ApexTreeBuilder extends AstVisitor<AdditionalPassScope> {
|
||||
return constructor.apply(node);
|
||||
}
|
||||
|
||||
ASTApexFile buildTree(Compilation astNode, ApexMultifileAnalysis analysisHandler) {
|
||||
ASTApexFile buildTree(Compilation astNode) {
|
||||
assert nodes.isEmpty() : "stack should be empty";
|
||||
ASTApexFile root = new ASTApexFile(task, astNode, commentInfo.suppressMap, analysisHandler);
|
||||
ASTApexFile root = new ASTApexFile(task, astNode, commentInfo.suppressMap, proc);
|
||||
nodes.push(root);
|
||||
parents.push(astNode);
|
||||
|
||||
|
@ -7,16 +7,15 @@ package net.sourceforge.pmd.lang.apex.multifile;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import net.sourceforge.pmd.annotation.Experimental;
|
||||
import net.sourceforge.pmd.lang.apex.ast.ApexParser;
|
||||
import net.sourceforge.pmd.annotation.InternalApi;
|
||||
import net.sourceforge.pmd.lang.apex.ApexLanguageProcessor;
|
||||
import net.sourceforge.pmd.lang.apex.ApexLanguageProperties;
|
||||
|
||||
import com.nawforce.common.api.FileIssueOptions;
|
||||
import com.nawforce.common.api.Org;
|
||||
@ -39,38 +38,45 @@ public final class ApexMultifileAnalysis {
|
||||
// test only
|
||||
static final Logger LOG = LoggerFactory.getLogger(ApexMultifileAnalysis.class);
|
||||
|
||||
/**
|
||||
* Instances of the apexlink index and data structures ({@link Org})
|
||||
* are stored statically for now. TODO make that language-wide (#2518).
|
||||
*/
|
||||
private static final Map<String, ApexMultifileAnalysis> INSTANCE_MAP = new ConcurrentHashMap<>();
|
||||
|
||||
// An arbitrary large number of errors to report
|
||||
private static final Integer MAX_ERRORS_PER_FILE = 100;
|
||||
private static final int MAX_ERRORS_PER_FILE = 100;
|
||||
|
||||
// Create a new org for each analysis
|
||||
// Null if failed.
|
||||
private final @Nullable Org org;
|
||||
private final FileIssueOptions options = makeOptions();
|
||||
|
||||
private static final ApexMultifileAnalysis FAILED_INSTANCE = new ApexMultifileAnalysis();
|
||||
|
||||
/** Ctor for the failed instance. */
|
||||
private ApexMultifileAnalysis() {
|
||||
org = null;
|
||||
static {
|
||||
// Default some library wide settings
|
||||
ServerOps.setAutoFlush(false);
|
||||
ServerOps.setLogger(new AnalysisLogger());
|
||||
ServerOps.setDebugLogging(new String[] { "ALL" });
|
||||
}
|
||||
|
||||
private ApexMultifileAnalysis(String multiFileAnalysisDirectory) {
|
||||
LOG.debug("MultiFile Analysis created for {}", multiFileAnalysisDirectory);
|
||||
org = Org.newOrg();
|
||||
if (multiFileAnalysisDirectory != null && !multiFileAnalysisDirectory.isEmpty()) {
|
||||
// Load the package into the org, this can take some time!
|
||||
org.newSFDXPackage(multiFileAnalysisDirectory); // this may fail if the config is wrong
|
||||
org.flush();
|
||||
|
||||
// FIXME: Syntax & Semantic errors found during Org loading are not currently being reported. These
|
||||
// should be routed to the new SemanticErrorReporter but that is not available for use just yet.
|
||||
@InternalApi
|
||||
public ApexMultifileAnalysis(ApexLanguageProperties properties) {
|
||||
String rootDir = properties.getProperty(ApexLanguageProperties.MULTIFILE_DIRECTORY);
|
||||
LOG.debug("MultiFile Analysis created for {}", rootDir);
|
||||
|
||||
Org org;
|
||||
try {
|
||||
org = Org.newOrg();
|
||||
if (rootDir != null && !rootDir.isEmpty()) {
|
||||
// Load the package into the org, this can take some time!
|
||||
org.newSFDXPackage(rootDir); // this may fail if the config is wrong
|
||||
org.flush();
|
||||
|
||||
// FIXME: Syntax & Semantic errors found during Org loading are not currently being reported. These
|
||||
// should be routed to the new SemanticErrorReporter but that is not available for use just yet.
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LOG.error("Exception while initializing Apexlink ({})", e.getMessage(), e);
|
||||
LOG.error("PMD will not attempt to initialize Apexlink further, this can cause rules like UnusedMethod to be dysfunctional");
|
||||
org = null;
|
||||
}
|
||||
this.org = org;
|
||||
}
|
||||
|
||||
private static FileIssueOptions makeOptions() {
|
||||
@ -84,8 +90,8 @@ public final class ApexMultifileAnalysis {
|
||||
/**
|
||||
* Returns true if this is analysis index is in a failed state.
|
||||
* This object is then useless. The failed instance is returned
|
||||
* from {@link #getAnalysisInstance(String)} if loading the org
|
||||
* failed, maybe because of malformed configuration.
|
||||
* from {@link ApexLanguageProcessor#getMultiFileState()} if
|
||||
* loading the org failed, maybe because of malformed configuration.
|
||||
*/
|
||||
public boolean isFailed() {
|
||||
return org == null;
|
||||
@ -97,33 +103,6 @@ public final class ApexMultifileAnalysis {
|
||||
: Collections.unmodifiableList(Arrays.asList(org.getFileIssues(filename, options)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the analysis instance. Returns a {@linkplain #isFailed() failed instance}
|
||||
* if this fails.
|
||||
*
|
||||
* @param multiFileAnalysisDirectory Root directory of the configuration (see {@link ApexParser#MULTIFILE_DIRECTORY}).
|
||||
*/
|
||||
public static @NonNull ApexMultifileAnalysis getAnalysisInstance(String multiFileAnalysisDirectory) {
|
||||
if (INSTANCE_MAP.isEmpty()) {
|
||||
// Default some library wide settings
|
||||
ServerOps.setAutoFlush(false);
|
||||
ServerOps.setLogger(new AnalysisLogger());
|
||||
ServerOps.setDebugLogging(new String[] { "ALL" });
|
||||
}
|
||||
|
||||
return INSTANCE_MAP.computeIfAbsent(
|
||||
multiFileAnalysisDirectory,
|
||||
dir -> {
|
||||
try {
|
||||
return new ApexMultifileAnalysis(dir);
|
||||
} catch (Exception e) {
|
||||
LOG.error("Exception while initializing Apexlink ({})", e.getMessage(), e);
|
||||
LOG.error("PMD will not attempt to initialize Apexlink further, this can cause rules like UnusedMethod to be dysfunctional");
|
||||
return FAILED_INSTANCE;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/*
|
||||
* Very simple logger to aid debugging, relays ApexLink logging into PMD
|
||||
*/
|
||||
|
@ -20,6 +20,7 @@ import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
|
||||
import net.sourceforge.pmd.internal.util.IOUtil;
|
||||
import net.sourceforge.pmd.lang.apex.ApexLanguageProperties;
|
||||
|
||||
import com.github.stefanbirkner.systemlambda.SystemLambda;
|
||||
|
||||
@ -66,7 +67,9 @@ class ApexMultifileAnalysisTest {
|
||||
}
|
||||
|
||||
private @NonNull ApexMultifileAnalysis getAnalysisForTempFolder() {
|
||||
return ApexMultifileAnalysis.getAnalysisInstance(tempFolder.toAbsolutePath().toString());
|
||||
ApexLanguageProperties props = new ApexLanguageProperties();
|
||||
props.setProperty(ApexLanguageProperties.MULTIFILE_DIRECTORY, tempFolder.toAbsolutePath().toString());
|
||||
return new ApexMultifileAnalysis(props);
|
||||
}
|
||||
|
||||
private void copyResource(String resourcePath, String relativePathInTempDir) throws IOException {
|
||||
|
@ -5,6 +5,7 @@
|
||||
package net.sourceforge.pmd.cli.commands.typesupport.internal;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.Objects;
|
||||
import java.util.TreeSet;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@ -36,7 +37,7 @@ public class PmdLanguageVersionTypeSupport implements ITypeConverter<LanguageVer
|
||||
return LanguageRegistry.PMD.getLanguages().stream()
|
||||
.filter(l -> value.startsWith(l.getTerseName() + "-"))
|
||||
.map(l -> l.getVersion(value.substring(l.getTerseName().length() + 1)))
|
||||
.filter(lv -> lv != null)
|
||||
.filter(Objects::nonNull)
|
||||
.findFirst()
|
||||
.orElseThrow(() -> new TypeConversionException("Unknown language version: " + value));
|
||||
}
|
||||
|
@ -52,8 +52,9 @@ import net.sourceforge.pmd.util.log.internal.SimpleMessageReporter;
|
||||
@Deprecated
|
||||
public final class PMD {
|
||||
|
||||
private static final String PMD_PACKAGE = "net.sourceforge.pmd";
|
||||
// not final, in order to re-initialize logging
|
||||
private static Logger log = LoggerFactory.getLogger(PMD.class);
|
||||
private static Logger log = LoggerFactory.getLogger(PMD_PACKAGE);
|
||||
|
||||
/**
|
||||
* The line delimiter used by PMD in outputs. Usually the platform specific
|
||||
@ -167,10 +168,30 @@ public final class PMD {
|
||||
return StatusCode.ERROR;
|
||||
}
|
||||
|
||||
MessageReporter pmdReporter = setupMessageReporter(configuration);
|
||||
configuration.setReporter(pmdReporter);
|
||||
|
||||
return runPmd(configuration);
|
||||
Level curLogLevel = Slf4jSimpleConfiguration.getDefaultLogLevel();
|
||||
boolean resetLogLevel = false;
|
||||
try {
|
||||
// only reconfigure logging, if debug flag was used on command line
|
||||
// otherwise just use whatever is in conf/simplelogger.properties which happens automatically
|
||||
if (configuration.isDebug()) {
|
||||
Slf4jSimpleConfiguration.reconfigureDefaultLogLevel(Level.TRACE);
|
||||
// need to reload the logger with the new configuration
|
||||
log = LoggerFactory.getLogger(PMD_PACKAGE);
|
||||
resetLogLevel = true;
|
||||
}
|
||||
|
||||
MessageReporter pmdReporter = setupMessageReporter();
|
||||
configuration.setReporter(pmdReporter);
|
||||
|
||||
return runPmd(configuration);
|
||||
} finally {
|
||||
if (resetLogLevel) {
|
||||
// reset to the previous value
|
||||
Slf4jSimpleConfiguration.reconfigureDefaultLogLevel(curLogLevel);
|
||||
log = LoggerFactory.getLogger(PMD_PACKAGE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -222,14 +243,8 @@ public final class PMD {
|
||||
}
|
||||
}
|
||||
|
||||
private static @NonNull MessageReporter setupMessageReporter(PMDConfiguration configuration) {
|
||||
// only reconfigure logging, if debug flag was used on command line
|
||||
// otherwise just use whatever is in conf/simplelogger.properties which happens automatically
|
||||
if (configuration.isDebug()) {
|
||||
Slf4jSimpleConfiguration.reconfigureDefaultLogLevel(Level.TRACE);
|
||||
// need to reload the logger with the new configuration
|
||||
log = LoggerFactory.getLogger(PMD.class);
|
||||
}
|
||||
private static @NonNull MessageReporter setupMessageReporter() {
|
||||
|
||||
// create a top-level reporter
|
||||
// TODO CLI errors should also be reported through this
|
||||
// TODO this should not use the logger as backend, otherwise without
|
||||
|
@ -13,7 +13,9 @@ 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.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Properties;
|
||||
|
||||
@ -27,6 +29,8 @@ import net.sourceforge.pmd.cache.FileAnalysisCache;
|
||||
import net.sourceforge.pmd.cache.NoopAnalysisCache;
|
||||
import net.sourceforge.pmd.cli.PmdParametersParseResult;
|
||||
import net.sourceforge.pmd.internal.util.ClasspathClassLoader;
|
||||
import net.sourceforge.pmd.lang.Language;
|
||||
import net.sourceforge.pmd.lang.LanguagePropertyBundle;
|
||||
import net.sourceforge.pmd.lang.LanguageRegistry;
|
||||
import net.sourceforge.pmd.lang.LanguageVersion;
|
||||
import net.sourceforge.pmd.lang.LanguageVersionDiscoverer;
|
||||
@ -135,6 +139,7 @@ public class PMDConfiguration extends AbstractConfiguration {
|
||||
private boolean ignoreIncrementalAnalysis;
|
||||
private final LanguageRegistry langRegistry;
|
||||
private final List<Path> relativizeRoots = new ArrayList<>();
|
||||
private final Map<Language, LanguagePropertyBundle> langProperties = new HashMap<>();
|
||||
|
||||
public PMDConfiguration() {
|
||||
this(DEFAULT_REGISTRY);
|
||||
@ -322,7 +327,10 @@ public class PMDConfiguration extends AbstractConfiguration {
|
||||
*
|
||||
* @param forceLanguageVersion the language version
|
||||
*/
|
||||
public void setForceLanguageVersion(LanguageVersion forceLanguageVersion) {
|
||||
public void setForceLanguageVersion(@Nullable LanguageVersion forceLanguageVersion) {
|
||||
if (forceLanguageVersion != null) {
|
||||
checkLanguageIsRegistered(forceLanguageVersion.getLanguage());
|
||||
}
|
||||
this.forceLanguageVersion = forceLanguageVersion;
|
||||
languageVersionDiscoverer.setForcedVersion(forceLanguageVersion);
|
||||
}
|
||||
@ -335,7 +343,8 @@ public class PMDConfiguration extends AbstractConfiguration {
|
||||
*/
|
||||
public void setDefaultLanguageVersion(LanguageVersion languageVersion) {
|
||||
Objects.requireNonNull(languageVersion);
|
||||
setDefaultLanguageVersions(Arrays.asList(languageVersion));
|
||||
languageVersionDiscoverer.setDefaultLanguageVersion(languageVersion);
|
||||
getLanguageProperties(languageVersion.getLanguage()).setLanguageVersion(languageVersion.getVersion());
|
||||
}
|
||||
|
||||
/**
|
||||
@ -347,8 +356,7 @@ public class PMDConfiguration extends AbstractConfiguration {
|
||||
*/
|
||||
public void setDefaultLanguageVersions(List<LanguageVersion> languageVersions) {
|
||||
for (LanguageVersion languageVersion : languageVersions) {
|
||||
Objects.requireNonNull(languageVersion);
|
||||
languageVersionDiscoverer.setDefaultLanguageVersion(languageVersion);
|
||||
setDefaultLanguageVersion(languageVersion);
|
||||
}
|
||||
}
|
||||
|
||||
@ -378,7 +386,7 @@ public class PMDConfiguration extends AbstractConfiguration {
|
||||
return languageVersionDiscoverer.getDefaultLanguageVersionForFile(fileName);
|
||||
}
|
||||
|
||||
LanguageRegistry languages() {
|
||||
LanguageRegistry getLanguageRegistry() {
|
||||
return langRegistry;
|
||||
}
|
||||
|
||||
@ -943,4 +951,22 @@ public class PMDConfiguration extends AbstractConfiguration {
|
||||
public List<Path> getRelativizeRoots() {
|
||||
return Collections.unmodifiableList(relativizeRoots);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a mutable bundle of language properties that are associated
|
||||
* to the given language (always the same for a given language).
|
||||
*
|
||||
* @param language A language, which must be registered
|
||||
*/
|
||||
public @NonNull LanguagePropertyBundle getLanguageProperties(Language language) {
|
||||
checkLanguageIsRegistered(language);
|
||||
return langProperties.computeIfAbsent(language, Language::newPropertyBundle);
|
||||
}
|
||||
|
||||
void checkLanguageIsRegistered(Language language) {
|
||||
if (!langRegistry.getLanguages().contains(language)) {
|
||||
throw new IllegalArgumentException(
|
||||
"Language '" + language.getId() + "' is not registered in " + getLanguageRegistry());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -10,8 +10,10 @@ import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
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;
|
||||
|
||||
@ -29,12 +31,17 @@ import net.sourceforge.pmd.internal.LogMessages;
|
||||
import net.sourceforge.pmd.internal.util.ClasspathClassLoader;
|
||||
import net.sourceforge.pmd.internal.util.FileCollectionUtil;
|
||||
import net.sourceforge.pmd.internal.util.IOUtil;
|
||||
import net.sourceforge.pmd.lang.JvmLanguagePropertyBundle;
|
||||
import net.sourceforge.pmd.lang.Language;
|
||||
import net.sourceforge.pmd.lang.LanguageProcessor.AnalysisTask;
|
||||
import net.sourceforge.pmd.lang.LanguageProcessorRegistry;
|
||||
import net.sourceforge.pmd.lang.LanguageProcessorRegistry.LanguageTerminationException;
|
||||
import net.sourceforge.pmd.lang.LanguagePropertyBundle;
|
||||
import net.sourceforge.pmd.lang.LanguageRegistry;
|
||||
import net.sourceforge.pmd.lang.LanguageVersion;
|
||||
import net.sourceforge.pmd.lang.LanguageVersionDiscoverer;
|
||||
import net.sourceforge.pmd.lang.document.FileCollector;
|
||||
import net.sourceforge.pmd.lang.document.TextFile;
|
||||
import net.sourceforge.pmd.processor.AbstractPMDProcessor;
|
||||
import net.sourceforge.pmd.renderers.Renderer;
|
||||
import net.sourceforge.pmd.reporting.GlobalAnalysisListener;
|
||||
import net.sourceforge.pmd.reporting.ListenerInitializer;
|
||||
@ -85,6 +92,7 @@ public final class PmdAnalysis implements AutoCloseable {
|
||||
private final PMDConfiguration configuration;
|
||||
private final MessageReporter reporter;
|
||||
|
||||
private final Map<Language, LanguagePropertyBundle> langProperties = new HashMap<>();
|
||||
private boolean closed;
|
||||
|
||||
/**
|
||||
@ -138,6 +146,25 @@ public final class PmdAnalysis implements AutoCloseable {
|
||||
final List<RuleSet> ruleSets = ruleSetLoader.loadRuleSetsWithoutException(config.getRuleSetPaths());
|
||||
pmd.addRuleSets(ruleSets);
|
||||
}
|
||||
|
||||
for (Language language : config.getLanguageRegistry()) {
|
||||
LanguagePropertyBundle props = config.getLanguageProperties(language);
|
||||
assert props.getLanguage().equals(language);
|
||||
pmd.langProperties.put(language, props);
|
||||
|
||||
LanguageVersion forcedVersion = config.getForceLanguageVersion();
|
||||
if (forcedVersion != null && forcedVersion.getLanguage().equals(language)) {
|
||||
props.setLanguageVersion(forcedVersion.getVersion());
|
||||
}
|
||||
|
||||
// TODO replace those with actual language properties when the
|
||||
// CLI syntax is implemented.
|
||||
props.setProperty(LanguagePropertyBundle.SUPPRESS_MARKER, config.getSuppressMarker());
|
||||
if (props instanceof JvmLanguagePropertyBundle) {
|
||||
((JvmLanguagePropertyBundle) props).setClassLoader(config.getClassLoader());
|
||||
}
|
||||
}
|
||||
|
||||
return pmd;
|
||||
}
|
||||
|
||||
@ -245,6 +272,17 @@ public final class PmdAnalysis implements AutoCloseable {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns a mutable bundle of language properties that are associated
|
||||
* to the given language (always the same for a given language).
|
||||
*
|
||||
* @param language A language, which must be registered
|
||||
*/
|
||||
public LanguagePropertyBundle getLanguageProperties(Language language) {
|
||||
configuration.checkLanguageIsRegistered(language);
|
||||
return langProperties.computeIfAbsent(language, Language::newPropertyBundle);
|
||||
}
|
||||
|
||||
/**
|
||||
* Run PMD with the current state of this instance. This will start
|
||||
* and finish the registered renderers, and close all
|
||||
@ -275,7 +313,7 @@ public final class PmdAnalysis implements AutoCloseable {
|
||||
|
||||
void performAnalysisImpl(List<? extends GlobalReportBuilderListener> extraListeners) {
|
||||
try (FileCollector files = collector) {
|
||||
files.filterLanguages(getApplicableLanguages());
|
||||
files.filterLanguages(getApplicableLanguages(false));
|
||||
performAnalysisImpl(extraListeners, files.getCollectedFiles());
|
||||
}
|
||||
}
|
||||
@ -310,16 +348,48 @@ public final class PmdAnalysis implements AutoCloseable {
|
||||
}
|
||||
|
||||
encourageToUseIncrementalAnalysis(configuration);
|
||||
try (AbstractPMDProcessor processor = AbstractPMDProcessor.newFileProcessor(configuration)) {
|
||||
processor.processFiles(rulesets, textFiles, listener);
|
||||
|
||||
try (LanguageProcessorRegistry lpRegistry = LanguageProcessorRegistry.create(
|
||||
// only start the applicable languages (and dependencies)
|
||||
new LanguageRegistry(getApplicableLanguages(true)),
|
||||
langProperties,
|
||||
reporter
|
||||
)) {
|
||||
// Note the analysis task is shared: all processors see
|
||||
// the same file list, which may contain files for other
|
||||
// languages.
|
||||
AnalysisTask analysisTask = new AnalysisTask(
|
||||
rulesets,
|
||||
textFiles,
|
||||
listener,
|
||||
configuration.getThreads(),
|
||||
configuration.getAnalysisCache(),
|
||||
reporter,
|
||||
lpRegistry
|
||||
);
|
||||
|
||||
List<AutoCloseable> analyses = new ArrayList<>();
|
||||
try {
|
||||
for (Language lang : lpRegistry.getLanguages()) {
|
||||
analyses.add(lpRegistry.getProcessor(lang).launchAnalysis(analysisTask));
|
||||
}
|
||||
} finally {
|
||||
Exception e = IOUtil.closeAll(analyses);
|
||||
if (e != null) {
|
||||
reporter.errorEx("Error while joining analysis", e);
|
||||
}
|
||||
}
|
||||
|
||||
} catch (LanguageTerminationException e) {
|
||||
reporter.errorEx("Error while closing language processors", e);
|
||||
}
|
||||
} finally {
|
||||
try {
|
||||
listener.close();
|
||||
} catch (Exception e) {
|
||||
reporter.errorEx("Exception while initializing analysis listeners", e);
|
||||
reporter.errorEx("Exception while closing analysis listeners", e);
|
||||
// todo better exception
|
||||
throw new RuntimeException("Exception while initializing analysis listeners", e);
|
||||
throw new RuntimeException("Exception while closing analysis listeners", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -346,23 +416,46 @@ public final class PmdAnalysis implements AutoCloseable {
|
||||
return GlobalAnalysisListener.tee(rendererListeners);
|
||||
}
|
||||
|
||||
private Set<Language> getApplicableLanguages() {
|
||||
final Set<Language> languages = new HashSet<>();
|
||||
final LanguageVersionDiscoverer discoverer = configuration.getLanguageVersionDiscoverer();
|
||||
private Set<Language> getApplicableLanguages(boolean quiet) {
|
||||
Set<Language> languages = new HashSet<>();
|
||||
LanguageVersionDiscoverer discoverer = configuration.getLanguageVersionDiscoverer();
|
||||
|
||||
for (RuleSet ruleSet : ruleSets) {
|
||||
for (final Rule rule : ruleSet.getRules()) {
|
||||
final Language ruleLanguage = rule.getLanguage();
|
||||
for (Rule rule : ruleSet.getRules()) {
|
||||
Language ruleLanguage = rule.getLanguage();
|
||||
Objects.requireNonNull(ruleLanguage, "Rule has no language " + rule);
|
||||
if (!languages.contains(ruleLanguage)) {
|
||||
final LanguageVersion version = discoverer.getDefaultLanguageVersion(ruleLanguage);
|
||||
LanguageVersion version = discoverer.getDefaultLanguageVersion(ruleLanguage);
|
||||
if (RuleSet.applies(rule, version)) {
|
||||
configuration.checkLanguageIsRegistered(ruleLanguage);
|
||||
languages.add(ruleLanguage);
|
||||
LOG.trace("Using {} version ''{}''", version.getLanguage().getName(), version.getTerseName());
|
||||
if (!quiet) {
|
||||
LOG.trace("Using {} version ''{}''", version.getLanguage().getName(), version.getTerseName());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// collect all dependencies, they shouldn't be filtered out
|
||||
LanguageRegistry reg = configuration.getLanguageRegistry();
|
||||
boolean changed;
|
||||
do {
|
||||
changed = false;
|
||||
for (Language lang : new HashSet<>(languages)) {
|
||||
for (String depId : lang.getDependencies()) {
|
||||
Language depLang = reg.getLanguageById(depId);
|
||||
if (depLang == null) {
|
||||
// todo maybe report all then throw
|
||||
throw new IllegalStateException(
|
||||
"Language " + lang.getId() + " has unsatisfied dependencies: "
|
||||
+ depId + " is not found in " + reg
|
||||
);
|
||||
}
|
||||
changed |= languages.add(depLang);
|
||||
}
|
||||
}
|
||||
} while (changed);
|
||||
return languages;
|
||||
}
|
||||
|
||||
|
@ -9,6 +9,7 @@ import java.util.Optional;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import net.sourceforge.pmd.lang.Language;
|
||||
import net.sourceforge.pmd.lang.LanguageProcessor;
|
||||
import net.sourceforge.pmd.lang.LanguageVersion;
|
||||
import net.sourceforge.pmd.lang.ast.Node;
|
||||
import net.sourceforge.pmd.lang.rule.RuleTargetSelector;
|
||||
@ -266,9 +267,17 @@ public interface Rule extends PropertySource {
|
||||
*/
|
||||
RuleTargetSelector getTargetSelector();
|
||||
|
||||
/**
|
||||
* Initialize the rule using the language processor if needed.
|
||||
*
|
||||
* @param languageProcessor The processor for the rule's language
|
||||
*/
|
||||
default void initialize(LanguageProcessor languageProcessor) {
|
||||
// by default do nothing
|
||||
}
|
||||
|
||||
/**
|
||||
* Start processing. Called once, before apply() is first called.
|
||||
* Start processing. Called once per file, before apply() is first called.
|
||||
*
|
||||
* @param ctx the rule context
|
||||
*/
|
||||
@ -285,7 +294,7 @@ public interface Rule extends PropertySource {
|
||||
void apply(Node target, RuleContext ctx);
|
||||
|
||||
/**
|
||||
* End processing. Called once, after apply() is last called.
|
||||
* End processing. Called once per file, after apply() is last called.
|
||||
*
|
||||
* @param ctx
|
||||
* the rule context
|
||||
|
@ -23,7 +23,6 @@ import net.sourceforge.pmd.lang.document.FileLocation;
|
||||
import net.sourceforge.pmd.lang.document.TextRange2d;
|
||||
import net.sourceforge.pmd.lang.rule.AbstractRule;
|
||||
import net.sourceforge.pmd.lang.rule.ParametricRuleViolation;
|
||||
import net.sourceforge.pmd.processor.AbstractPMDProcessor;
|
||||
import net.sourceforge.pmd.properties.PropertyDescriptor;
|
||||
import net.sourceforge.pmd.reporting.FileAnalysisListener;
|
||||
import net.sourceforge.pmd.reporting.ViolationDecorator;
|
||||
@ -138,7 +137,7 @@ public final class RuleContext {
|
||||
Objects.requireNonNull(message, "Message was null");
|
||||
Objects.requireNonNull(formatArgs, "Format arguments were null, use an empty array");
|
||||
|
||||
LanguageVersionHandler handler = node.getTextDocument().getLanguageVersion().getLanguageVersionHandler();
|
||||
LanguageVersionHandler handler = node.getAstInfo().getLanguageProcessor().services();
|
||||
|
||||
FileLocation location = node.getReportLocation();
|
||||
if (beginLine != -1 && endLine != -1) {
|
||||
@ -218,8 +217,7 @@ public final class RuleContext {
|
||||
|
||||
|
||||
/**
|
||||
* Create a new RuleContext. This is internal API owned by {@link AbstractPMDProcessor}
|
||||
* (can likely be hidden when everything relevant is moved into rule package).
|
||||
* Create a new RuleContext.
|
||||
*
|
||||
* The listener must be closed by its creator.
|
||||
*/
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user