Merge remote-tracking branch 'origin/pmd7-language-lifecycle' into pmd7-language-lifecycle
This commit is contained in:
8 files changed
+204
-129
No files matched your search
@@ -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
|
||||
@@ -399,6 +401,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)
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -11,35 +11,77 @@ 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.
|
||||
|
||||
TODO describe CLI syntax
|
||||
TODO describe env var syntax
|
||||
The name of the environment variables follow the following pattern,
|
||||
completely in uppercase:
|
||||
|
||||
As a convention, properties whose name start with an *x* are internal and may be
|
||||
removed or changed without notice.
|
||||
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 uppercase name of the property, that is set to a specific value, e.g. "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.
|
||||
|
||||
- `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
|
||||
- `xTypeInferenceLogging`: Verbosity of type inference logging, possible values `DISABLED`, `SIMPLE`, `VERBOSE`
|
||||
|
||||
This property can also be set via the CLI option `--aux-classpath`.
|
||||
|
||||
Environment variable: `PMD_JAVA_AUXCLASSPATH`
|
||||
|
||||
- `xTypeInferenceLogging`: Verbosity of type inference logging, possible values `DISABLED`, `SIMPLE`, `VERBOSE`.
|
||||
|
||||
Environment variable: `PMD_JAVA_XTYPEINFERENCELOGGING`
|
||||
|
||||
## Apex language properties
|
||||
|
||||
- `rootDirectory`:
|
||||
- `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_ROOTDIRECTORY`
|
||||
|
||||
## 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.
|
||||
- `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.
|
||||
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_APEXDIRECTORIES`
|
||||
|
||||
- `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_OBJECTSDIRECTORIES`
|
||||
@@ -46,7 +46,7 @@ import net.sourceforge.pmd.util.log.MessageReporter;
|
||||
|
||||
import com.github.stefanbirkner.systemlambda.SystemLambda;
|
||||
|
||||
public class PmdRunnableTest {
|
||||
class PmdRunnableTest {
|
||||
|
||||
public static final String TEST_MESSAGE_SEMANTIC_ERROR = "An error occurred!";
|
||||
private static final String PARSER_REPORTS_SEMANTIC_ERROR = "1.9-semantic_error";
|
||||
@@ -59,7 +59,7 @@ public class PmdRunnableTest {
|
||||
|
||||
|
||||
@BeforeEach
|
||||
public void prepare() {
|
||||
void prepare() {
|
||||
// reset data
|
||||
rule = spy(new RuleThatThrows());
|
||||
configuration = new PMDConfiguration(LanguageRegistry.singleton(ThrowingLanguageModule.INSTANCE));
|
||||
@@ -82,7 +82,7 @@ public class PmdRunnableTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void inErrorRecoveryModeErrorsShouldBeLoggedByParser() throws Exception {
|
||||
void inErrorRecoveryModeErrorsShouldBeLoggedByParser() throws Exception {
|
||||
SystemLambda.restoreSystemProperties(() -> {
|
||||
System.setProperty(SystemProps.PMD_ERROR_RECOVERY, "");
|
||||
|
||||
@@ -93,7 +93,7 @@ public class PmdRunnableTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void inErrorRecoveryModeErrorsShouldBeLoggedByRule() throws Exception {
|
||||
void inErrorRecoveryModeErrorsShouldBeLoggedByRule() throws Exception {
|
||||
SystemLambda.restoreSystemProperties(() -> {
|
||||
System.setProperty(SystemProps.PMD_ERROR_RECOVERY, "");
|
||||
|
||||
@@ -107,7 +107,7 @@ public class PmdRunnableTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void withoutErrorRecoveryModeProcessingShouldBeAbortedByParser() throws Exception {
|
||||
void withoutErrorRecoveryModeProcessingShouldBeAbortedByParser() throws Exception {
|
||||
SystemLambda.restoreSystemProperties(() -> {
|
||||
System.clearProperty(SystemProps.PMD_ERROR_RECOVERY);
|
||||
assertThrows(AssertionError.class, () -> process(versionWithParserThatThrowsAssertionError()));
|
||||
@@ -115,7 +115,7 @@ public class PmdRunnableTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void withoutErrorRecoveryModeProcessingShouldBeAbortedByRule() throws Exception {
|
||||
void withoutErrorRecoveryModeProcessingShouldBeAbortedByRule() throws Exception {
|
||||
SystemLambda.restoreSystemProperties(() -> {
|
||||
System.clearProperty(SystemProps.PMD_ERROR_RECOVERY);
|
||||
assertThrows(AssertionError.class, () -> process(ThrowingLanguageModule.INSTANCE.getDefaultVersion()));
|
||||
@@ -124,7 +124,7 @@ public class PmdRunnableTest {
|
||||
|
||||
|
||||
@Test
|
||||
public void semanticErrorShouldAbortTheRun() {
|
||||
void semanticErrorShouldAbortTheRun() {
|
||||
Report report = process(versionWithParserThatReportsSemanticError());
|
||||
|
||||
verify(reporter, times(1))
|
||||
@@ -135,7 +135,7 @@ public class PmdRunnableTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void semanticErrorThrownShouldAbortTheRun() {
|
||||
void semanticErrorThrownShouldAbortTheRun() {
|
||||
Report report = process(getVersionWithParserThatThrowsSemanticError());
|
||||
|
||||
verify(reporter, times(1)).log(eq(Level.ERROR), contains(TEST_MESSAGE_SEMANTIC_ERROR));
|
||||
@@ -144,23 +144,23 @@ public class PmdRunnableTest {
|
||||
assertEquals(1, report.getProcessingErrors().size());
|
||||
}
|
||||
|
||||
public static LanguageVersion versionWithParserThatThrowsAssertionError() {
|
||||
private static LanguageVersion versionWithParserThatThrowsAssertionError() {
|
||||
return ThrowingLanguageModule.INSTANCE.getVersion(THROWS_ASSERTION_ERROR);
|
||||
}
|
||||
|
||||
public static LanguageVersion getVersionWithParserThatThrowsSemanticError() {
|
||||
private static LanguageVersion getVersionWithParserThatThrowsSemanticError() {
|
||||
return ThrowingLanguageModule.INSTANCE.getVersion(THROWS_SEMANTIC_ERROR);
|
||||
}
|
||||
|
||||
public static LanguageVersion versionWithParserThatReportsSemanticError() {
|
||||
private static LanguageVersion versionWithParserThatReportsSemanticError() {
|
||||
return ThrowingLanguageModule.INSTANCE.getVersion(PARSER_REPORTS_SEMANTIC_ERROR);
|
||||
}
|
||||
|
||||
public static class ThrowingLanguageModule extends SimpleLanguageModuleBase {
|
||||
private static class ThrowingLanguageModule extends SimpleLanguageModuleBase {
|
||||
|
||||
public static final ThrowingLanguageModule INSTANCE = new ThrowingLanguageModule();
|
||||
static final ThrowingLanguageModule INSTANCE = new ThrowingLanguageModule();
|
||||
|
||||
public ThrowingLanguageModule() {
|
||||
ThrowingLanguageModule() {
|
||||
super(LanguageMetadata.withId("foo").name("Foo").extensions("foo")
|
||||
.addVersion(THROWS_ASSERTION_ERROR)
|
||||
.addVersion(THROWS_SEMANTIC_ERROR)
|
||||
|
||||
@@ -35,10 +35,10 @@ class BinaryDistributionIT extends AbstractBinaryDistributionTest {
|
||||
+ " java-11, java-12, java-13, java-14, java-15," + System.lineSeparator()
|
||||
+ " java-16, java-17, java-18, java-18-preview," + System.lineSeparator()
|
||||
+ " java-19, java-19-preview, java-5, java-6, java-7," + System.lineSeparator()
|
||||
+ " java-8, java-9, jsp-, kotlin-, kotlin-1.6," + System.lineSeparator()
|
||||
+ " kotlin-1.6-rfc+0.1, modelica-, plsql-, pom-," + System.lineSeparator()
|
||||
+ " scala-2.10, scala-2.11, scala-2.12, scala-2.13," + System.lineSeparator()
|
||||
+ " swift-, vf-, vm-, wsdl-, xml-, xsl-";
|
||||
+ " java-8, java-9, jsp-, kotlin-1.6, kotlin-1." + System.lineSeparator()
|
||||
+ " 6-rfc+0.1, modelica-, plsql-, pom-, scala-2.10," + System.lineSeparator()
|
||||
+ " scala-2.11, scala-2.12, scala-2.13, swift-, vf-," + System.lineSeparator()
|
||||
+ " vm-, wsdl-, xml-, xsl-";
|
||||
}
|
||||
|
||||
private final String srcDir = new File(".", "src/test/resources/sample-source/java/").getAbsolutePath();
|
||||
|
||||
@@ -13,7 +13,7 @@ class LanguageVersionTest extends AbstractLanguageVersionTest {
|
||||
|
||||
static Collection<TestDescriptor> data() {
|
||||
return Arrays.asList(
|
||||
new TestDescriptor(KotlinLanguageModule.NAME, KotlinLanguageModule.TERSE_NAME, "",
|
||||
new TestDescriptor(KotlinLanguageModule.NAME, KotlinLanguageModule.TERSE_NAME, "1.6-rfc+0.1",
|
||||
getLanguage(KotlinLanguageModule.NAME).getDefaultVersion()),
|
||||
new TestDescriptor(KotlinLanguageModule.NAME, KotlinLanguageModule.TERSE_NAME, "1.6",
|
||||
getLanguage(KotlinLanguageModule.NAME).getDefaultVersion()));
|
||||
|
||||
Reference in new issue
Block a user