Merge branch '7.0.x' into port-properties

This commit is contained in:
Clément Fournier 2023-02-17 16:25:27 +01:00
commit 0ba2460f92
No known key found for this signature in database
GPG Key ID: 4D8D42402E4F47E2
177 changed files with 8946 additions and 2219 deletions

View File

@ -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,

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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&amp;utm_medium=referral&amp;utm_content=pmd/pmd&amp;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&amp;utm_medium=referral&amp;utm_content=pmd/pmd&amp;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

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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 modules constructor.
Use `addDefaultVersion` for defining the default version.
* Youll 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 its 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.

View File

@ -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
* Its 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`.
* Its 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 its 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)*
* Youll 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 modules 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.
* Youll 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 dont 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 dont 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 its 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 its 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

View 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

View File

@ -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 %}

View File

@ -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

View File

@ -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() {

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

@ -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

View File

@ -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()));
}

View File

@ -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());
}

View File

@ -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);

View File

@ -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
*/

View File

@ -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 {

View File

@ -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));
}

View File

@ -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

View File

@ -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());
}
}
}

View File

@ -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;
}

View File

@ -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

View File

@ -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