Merge branch 'master' into 7.0.x

This commit is contained in:
Clément Fournier
2019-03-11 22:16:13 +01:00
125 changed files with 443 additions and 12533 deletions

View File

@ -1,6 +1,8 @@
<!-- Please, prefix the report title with the language it applies to within brackets, such as [java] or [apex].
If not specific to a language, you can use [core]. -->
<!-- NB: issues about the rule designer should be opened at https://github.com/pmd/pmd-designer/issues -->
**Affects PMD Version:**
**Rule:**

View File

@ -3,6 +3,9 @@
First off, thanks for taking the time to contribute!
| NB: the rule designer is developed over at [pmd/pmd-designer](https://github.com/pmd/pmd-designer). Please refer to the specific [contributor documentation](https://github.com/pmd/pmd-designer#contributing) if your issue, feature request or PR touches the designer. |
| --- |
## Pull requests
* Please create your pull request against the `master` branch. We will rebase/merge it to the maintenance

View File

@ -22,6 +22,10 @@ Our latest source of PMD can be found on [GitHub](https://github.com/pmd/pmd). F
* [How to build PMD](BUILDING.md)
* [How to contribute to PMD](CONTRIBUTING.md)
The rule designer is developed over at [pmd/pmd-designer](https://github.com/pmd/pmd-designer).
Please see [its README](https://github.com/pmd/pmd-designer#contributing) for
developer documentation.
## News and Website
More information can be found on our [Website](https://pmd.github.io) and on [SourceForge](https://sourceforge.net/projects/pmd/).

View File

@ -80,6 +80,9 @@ echo " the new release based on the release notes"
echo
echo "* Update **../pmd.github.io/_config.yml** to mention the new release"
echo
echo "* Update property `pmd-designer.version` in **pom.xml** to reference the latest pmd-designer release"
echo " See <https://search.maven.org/search?q=g:net.sourceforge.pmd%20AND%20a:pmd-ui&core=gav> for the available releases."
echo
echo "Press enter to continue..."
read

View File

@ -33,8 +33,10 @@ The date and the version must be updated in `docs/_config.yml`, e.g.
pmd:
version: 6.0.0
date: 2017-12-15
release_type: minor
```
The release type could be one of "bugfix", "minor", or "major".
The release notes usual mention any new rules that have been added since the last release.
Please double check the file `pmd-core/src/main/resources/rulesets/releases/<version>.xml`, so
@ -43,6 +45,10 @@ that all new rules are listed.
We maintain a documentation for the [next major release](pmd_next_major_development.html). Copy the API
changes from the current release notes to this document: `docs/pages/next_major_development.md`.
The designer lives at [pmd/pmd-designer](https://github.com/pmd/pmd-designer).
Update property `pmd-designer.version` in **pom.xml** to reference the latest pmd-designer release.
See <https://search.maven.org/search?q=g:net.sourceforge.pmd%20AND%20a:pmd-ui&core=gav> for the available releases.
Check in all (version) changes to branch master or any other branch, from which the release takes place:
$ git commit -a -m "Prepare pmd release <version>"

View File

@ -58,6 +58,7 @@ folder: pmd/rules
* [UseCollectionIsEmpty](pmd_rules_java_bestpractices.html#usecollectionisempty): The isEmpty() method on java.util.Collection is provided to determine if a collection has any ele...
* [UseTryWithResources](pmd_rules_java_bestpractices.html#usetrywithresources): Java 7 introduced the try-with-resources statement. This statement ensures that each resource is ...
* [UseVarargs](pmd_rules_java_bestpractices.html#usevarargs): Java 5 introduced the varargs parameter declaration for methods and constructors. This syntactic...
* [WhileLoopWithLiteralBoolean](pmd_rules_java_bestpractices.html#whileloopwithliteralboolean): 'do {} while (true);' requires reading the end of the statement before it isapparent that it loop...
## Code Style

View File

@ -5,7 +5,7 @@ permalink: pmd_rules_java_bestpractices.html
folder: pmd/rules/java
sidebaractiveurl: /pmd_rules_java.html
editmepath: ../pmd-java/src/main/resources/category/java/bestpractices.xml
keywords: Best Practices, AbstractClassWithoutAbstractMethod, AccessorClassGeneration, AccessorMethodGeneration, ArrayIsStoredDirectly, AvoidPrintStackTrace, AvoidReassigningLoopVariables, AvoidReassigningParameters, AvoidStringBufferField, AvoidUsingHardCodedIP, CheckResultSet, ConstantsInInterface, DefaultLabelNotLastInSwitchStmt, ForLoopCanBeForeach, ForLoopVariableCount, GuardLogStatement, JUnit4SuitesShouldUseSuiteAnnotation, JUnit4TestShouldUseAfterAnnotation, JUnit4TestShouldUseBeforeAnnotation, JUnit4TestShouldUseTestAnnotation, JUnitAssertionsShouldIncludeMessage, JUnitTestContainsTooManyAsserts, JUnitTestsShouldIncludeAssert, JUnitUseExpected, LooseCoupling, MethodReturnsInternalArray, MissingOverride, OneDeclarationPerLine, PositionLiteralsFirstInCaseInsensitiveComparisons, PositionLiteralsFirstInComparisons, PreserveStackTrace, ReplaceEnumerationWithIterator, ReplaceHashtableWithMap, ReplaceVectorWithList, SwitchStmtsShouldHaveDefault, SystemPrintln, UnusedFormalParameter, UnusedImports, UnusedLocalVariable, UnusedPrivateField, UnusedPrivateMethod, UseAssertEqualsInsteadOfAssertTrue, UseAssertNullInsteadOfAssertTrue, UseAssertSameInsteadOfAssertTrue, UseAssertTrueInsteadOfAssertEquals, UseCollectionIsEmpty, UseTryWithResources, UseVarargs
keywords: Best Practices, AbstractClassWithoutAbstractMethod, AccessorClassGeneration, AccessorMethodGeneration, ArrayIsStoredDirectly, AvoidPrintStackTrace, AvoidReassigningLoopVariables, AvoidReassigningParameters, AvoidStringBufferField, AvoidUsingHardCodedIP, CheckResultSet, ConstantsInInterface, DefaultLabelNotLastInSwitchStmt, ForLoopCanBeForeach, ForLoopVariableCount, GuardLogStatement, JUnit4SuitesShouldUseSuiteAnnotation, JUnit4TestShouldUseAfterAnnotation, JUnit4TestShouldUseBeforeAnnotation, JUnit4TestShouldUseTestAnnotation, JUnitAssertionsShouldIncludeMessage, JUnitTestContainsTooManyAsserts, JUnitTestsShouldIncludeAssert, JUnitUseExpected, LooseCoupling, MethodReturnsInternalArray, MissingOverride, OneDeclarationPerLine, PositionLiteralsFirstInCaseInsensitiveComparisons, PositionLiteralsFirstInComparisons, PreserveStackTrace, ReplaceEnumerationWithIterator, ReplaceHashtableWithMap, ReplaceVectorWithList, SwitchStmtsShouldHaveDefault, SystemPrintln, UnusedFormalParameter, UnusedImports, UnusedLocalVariable, UnusedPrivateField, UnusedPrivateMethod, UseAssertEqualsInsteadOfAssertTrue, UseAssertNullInsteadOfAssertTrue, UseAssertSameInsteadOfAssertTrue, UseAssertTrueInsteadOfAssertEquals, UseCollectionIsEmpty, UseTryWithResources, UseVarargs, WhileLoopWithLiteralBoolean
language: Java
---
<!-- DO NOT EDIT THIS FILE. This file is generated from file ../pmd-java/src/main/resources/category/java/bestpractices.xml. -->
@ -1750,3 +1750,41 @@ public class Foo {
<rule ref="category/java/bestpractices.xml/UseVarargs" />
```
## WhileLoopWithLiteralBoolean
**Since:** PMD 6.13.0
**Priority:** Medium (3)
`do {} while (true);` requires reading the end of the statement before it is
apparent that it loops forever, whereas `while (true) {}` is easier to understand.
`do {} while (false);` is redundant, and if an inner variable scope is required,
a block `{}` is sufficient.
`while (false) {}` will never execute the block and can be removed in its entirety.
**This rule is defined by the following XPath expression:**
``` xpath
//DoStatement[Expression/PrimaryExpression/PrimaryPrefix/Literal/BooleanLiteral] |
//WhileStatement[Expression/PrimaryExpression/PrimaryPrefix/Literal/BooleanLiteral[@True = false()]]
```
**Example(s):**
``` java
public class Example {
{
while (true) { } // allowed
while (false) { } // disallowed
do { } while (true); // disallowed
do { } while (false); // disallowed
}
}
```
**Use this rule by referencing it:**
``` xml
<rule ref="category/java/bestpractices.xml/WhileLoopWithLiteralBoolean" />
```

View File

@ -1265,8 +1265,8 @@ should be annotated with @Test and @Ignore.
or pmd-java:typeIs('org.junit.jupiter.params.ParameterizedTest')]
]
[not(Annotation)]
[MethodDeclaration[(@Public = 'true' or @PackagePrivate = 'true') and @Static = 'false' and
ResultType[@Void = 'true'] and
[MethodDeclaration[(@Public = true() or @PackagePrivate = true()) and @Static = false() and
ResultType[@Void = true()] and
MethodDeclarator/FormalParameters[@ParameterCount = 0]
]
]

View File

@ -31,6 +31,12 @@ references only rules, that are most likely to apply everywhere.
Any feedback would be greatly appreciated.
#### PMD Designer
The rule designer's codebase has been moved out of the main repository and
will be developed at [pmd/pmd-designer](https://github.com/pmd/pmd-designer)
from now on. The maven coordinates will stay the same for the time being.
The designer will still be shipped with PMD's binaries.
#### New Rules
@ -42,6 +48,10 @@ Any feedback would be greatly appreciated.
methods in test classes, which are not annotated with `@Test`. These methods might be test cases where
the annotation has been forgotten. Because of that those test cases are never executed.
* The new Java rule {% rule "java/bestpractices/WhileLoopWithLiteralBoolean" %} (`java-bestpractices`) finds
Do-While-Loops and While-Loops that can be simplified since they use simply `true` or `false` as their
loop condition.
### Fixed Issues
### API Changes
@ -53,10 +63,14 @@ Any feedback would be greatly appreciated.
Properties "cc_categories", "cc_remediation_points_multiplier", "cc_block_highlighting" will also be removed.
See [#1702](https://github.com/pmd/pmd/pull/1702) for more.
* The Apex ruleset `rulesets/apex/ruleset.xml` has been deprecated and will be removed in 7.0.0. Please use the new
quickstart ruleset `rulesets/apex/quickstart.xml` instead.
### External Contributions
* [#1704](https://github.com/pmd/pmd/pull/1704): \[java] Added AvoidUncheckedExceptionsInSignatures Rule - [Bhanu Prakash Pamidi](https://github.com/pamidi99)
* [#1706](https://github.com/pmd/pmd/pull/1706): \[java] Add DetachedTestCase rule - [David Burström](https://github.com/davidburstromspotify)
* [#1709](https://github.com/pmd/pmd/pull/1709): \[java] Detect while loops with literal booleans conditions - [David Burström](https://github.com/davidburstromspotify)
{% endtocmaker %}

View File

@ -8,6 +8,7 @@
This ruleset contains links to rules that are new in PMD v6.13.0
</description>
<rule ref="category/java/bestpractices.xml/WhileLoopWithLiteralBoolean"/>
<rule ref="category/java/design.xml/AvoidUncheckedExceptionsInSignatures"/>
<rule ref="category/java/errorprone.xml/DetachedTestCase"/>

View File

@ -242,7 +242,7 @@
<dependency>
<groupId>net.sourceforge.pmd</groupId>
<artifactId>pmd-ui</artifactId>
<version>${project.version}</version>
<version>${pmd-designer.version}</version>
</dependency>
</dependencies>
</profile>

View File

@ -1633,4 +1633,41 @@ public class Foo {
</example>
</rule>
<rule name="WhileLoopWithLiteralBoolean"
class="net.sourceforge.pmd.lang.rule.XPathRule"
language="java"
since="6.13.0"
message="The loop can be simplified."
externalInfoUrl="${pmd.website.baseurl}/pmd_rules_java_bestpractices.html#whileloopwithliteralboolean">
<description>
`do {} while (true);` requires reading the end of the statement before it is
apparent that it loops forever, whereas `while (true) {}` is easier to understand.
`do {} while (false);` is redundant, and if an inner variable scope is required,
a block `{}` is sufficient.
`while (false) {}` will never execute the block and can be removed in its entirety.
</description>
<priority>3</priority>
<properties>
<property name="xpath">
<value>
//DoStatement[Expression/PrimaryExpression/PrimaryPrefix/Literal/BooleanLiteral] |
//WhileStatement[Expression/PrimaryExpression/PrimaryPrefix/Literal/BooleanLiteral[@True = false()]]
</value>
</property>
<property name="version" value="2.0" />
</properties>
<example>
public class Example {
{
while (true) { } // allowed
while (false) { } // disallowed
do { } while (true); // disallowed
do { } while (false); // disallowed
}
}
</example>
</rule>
</ruleset>

View File

@ -1144,13 +1144,14 @@ should be annotated with @Test and @Ignore.
or pmd-java:typeIs('org.junit.jupiter.params.ParameterizedTest')]
]
[not(Annotation)]
[MethodDeclaration[(@Public = 'true' or @PackagePrivate = 'true') and @Static = 'false' and
ResultType[@Void = 'true'] and
[MethodDeclaration[(@Public = true() or @PackagePrivate = true()) and @Static = false() and
ResultType[@Void = true()] and
MethodDeclarator/FormalParameters[@ParameterCount = 0]
]
]
]]></value>
</property>
<property name="version" value="2.0" />
</properties>
<example>
<![CDATA[

View File

@ -0,0 +1,11 @@
/**
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
*/
package net.sourceforge.pmd.lang.java.rule.bestpractices;
import net.sourceforge.pmd.testframework.PmdRuleTst;
public class WhileLoopWithLiteralBooleanTest extends PmdRuleTst {
// no additional unit tests
}

View File

@ -10,7 +10,7 @@ import net.sourceforge.pmd.lang.java.ast.JavaVersion.Companion.Latest
import java.io.IOException
class ASTCatchStatementTest : FunSpec({
class ASTCatchStatementTest : ParserTestSpec({
parserTest("Test crash on multicatch", javaVersions = Earliest..J1_6) {
@ -44,7 +44,7 @@ class ASTCatchStatementTest : FunSpec({
child<ASTCatchStatement> {
it.isMulticatchStatement shouldBe true
val types = childRet<ASTFormalParameter, List<ASTType>> {
val types = fromChild<ASTFormalParameter, List<ASTType>> {
val ioe = child<ASTType>(ignoreChildren = true) {
it.type shouldBe IOException::class.java
}
@ -57,7 +57,7 @@ class ASTCatchStatementTest : FunSpec({
it.image shouldBe "e"
}
return@childRet listOf(ioe, aerr)
listOf(ioe, aerr)
}
it.caughtExceptionTypeNodes.shouldContainExactly(types)

View File

@ -1,14 +1,12 @@
package net.sourceforge.pmd.lang.java.ast
import io.kotlintest.should
import io.kotlintest.specs.FunSpec
import net.sourceforge.pmd.lang.ast.test.shouldBe
import net.sourceforge.pmd.lang.java.ast.JavaVersion.Companion.Earliest
import net.sourceforge.pmd.lang.java.ast.JavaVersion.Companion.Latest
import net.sourceforge.pmd.lang.java.ast.JavaVersion.J1_8
import net.sourceforge.pmd.lang.java.ast.JavaVersion.J9
class ASTMethodDeclarationTest : FunSpec({
class ASTMethodDeclarationTest : ParserTestSpec({
// notes about dsl:
// * testGroup generates one test per "should" assertion that
@ -16,7 +14,7 @@ class ASTMethodDeclarationTest : FunSpec({
// (without explicitly giving them each a specific name)
// * the it::isPublic syntax allows including the property name in the error message in case of failure
testGroup("Non-private interfaces members should be public", javaVersions = Earliest..Latest) {
parserTest("Non-private interfaces members should be public", javaVersions = Earliest..Latest) {
genClassHeader = "interface Bar"
@ -55,7 +53,7 @@ class ASTMethodDeclarationTest : FunSpec({
}
testGroup("Non-default methods in interfaces should be abstract", javaVersions = J1_8..Latest) {
parserTest("Non-default methods in interfaces should be abstract", javaVersions = J1_8..Latest) {
genClassHeader = "interface Bar"

View File

@ -1,15 +1,13 @@
import io.kotlintest.should
import io.kotlintest.shouldBe
import io.kotlintest.specs.FunSpec
import net.sourceforge.pmd.lang.java.ast.*
import net.sourceforge.pmd.lang.java.ast.JavaVersion.*
import net.sourceforge.pmd.lang.java.ast.JavaVersion.Companion.Latest
class Java11Test : FunSpec({
class Java11Test : ParserTestSpec({
parserTest("Test lambda parameter with var keyword", javaVersions = J1_8..J10) {
parserTest("var keyword should be a normal type pre-java 11", javaVersions = J1_8..J10) {
"(var x) -> String.valueOf(x)" should matchExpr<ASTLambdaExpression> {
child<ASTFormalParameters> {
@ -76,7 +74,7 @@ class Java11Test : FunSpec({
}
}
parserTest("Test lambda parameter with var keyword", javaVersions = J11..Latest) {
parserTest("var keyword should generate no type after java 11", javaVersions = J11..Latest) {
"(var x) -> String.valueOf(x)" should matchExpr<ASTLambdaExpression> {
child<ASTFormalParameters> {

View File

@ -1,10 +1,7 @@
package net.sourceforge.pmd.lang.java.ast
import io.kotlintest.Matcher
import io.kotlintest.Result
import io.kotlintest.matchers.string.shouldContain
import io.kotlintest.shouldThrow
import io.kotlintest.specs.AbstractFunSpec
import net.sourceforge.pmd.lang.ast.Node
import net.sourceforge.pmd.lang.ast.test.*
import net.sourceforge.pmd.lang.java.ParserTstUtil
@ -36,109 +33,9 @@ enum class JavaVersion : Comparable<JavaVersion> {
}
}
/**
* Specify several tests at once for different java versions.
* One test will be generated per version in [javaVersions].
* Use [focusOn] to execute one test in isolation.
*
* @param name Name of the test. Will be postfixed by the specific
* java version used to run it
* @param javaVersions Language versions for which to generate tests
* @param focusOn Sets the java version of the test to isolate
* @param assertions Assertions and further configuration
* to perform with the parsing context
*/
fun AbstractFunSpec.parserTest(name: String,
javaVersions: List<JavaVersion>,
focusOn: JavaVersion? = null,
assertions: ParserTestCtx.() -> Unit) {
javaVersions.forEach {
val focus = if (focusOn != null && focusOn == it) "f:" else ""
test("$focus$name (Java ${it.pmdName})") {
ParserTestCtx(it).assertions()
}
}
}
/**
* Specify a new test for a single java version. To execute the test in isolation,
* prefix the name with `"f:"`.
*
* @param name Name of the test. Will be postfixed by the [javaVersion]
* @param javaVersion Language version to use when parsing
* @param assertions Assertions and further configuration
* to perform with the parsing context
*/
fun AbstractFunSpec.parserTest(name: String,
javaVersion: JavaVersion = JavaVersion.Latest,
assertions: ParserTestCtx.() -> Unit) {
parserTest(name, listOf(javaVersion), null, assertions)
}
/**
* Defines a group of tests that should be named similarly,
* executed on several java versions. Calls to "should" in
* the block are intercepted to create a new test, with the
* given [name] as a common prefix.
*
* This is useful to make a batch of grammar specs for grammar
* regression tests without bothering to find a name.
*
* @param name Common prefix for the test names
* @param javaVersions Language versions for which to generate tests
* @param spec Assertions. Each call to [io.kotlintest.should] on a string
* receiver is replaced by a [GroupTestCtx.should], which creates a
* new parser test.
*/
fun AbstractFunSpec.testGroup(name: String,
javaVersions: List<JavaVersion>,
spec: GroupTestCtx.() -> Unit) {
javaVersions.forEach {
testGroup(name, it, spec)
}
}
/**
* Defines a group of tests that should be named similarly.
* Calls to "should" in the block are intercepted to create
* a new test, with the given [name] as a common prefix.
*
* This is useful to make a batch of grammar specs for grammar
* regression tests without bothering to find a name.
*
* @param name Common prefix for the test names
* @param javaVersion Language versions to use when parsing
* @param spec Assertions. Each call to [io.kotlintest.should] on a string
* receiver is replaced by a [GroupTestCtx.should], which creates a
* new parser test.
*
*/
fun AbstractFunSpec.testGroup(name: String,
javaVersion: JavaVersion = JavaVersion.Latest,
spec: GroupTestCtx.() -> Unit) {
GroupTestCtx(this, name, javaVersion).spec()
}
class GroupTestCtx(private val funspec: AbstractFunSpec, private val groupName: String, javaVersion: JavaVersion) : ParserTestCtx(javaVersion) {
infix fun String.should(matcher: Matcher<String>) {
funspec.parserTest("$groupName: '$this'", javaVersion = javaVersion) {
this@should kotlintestShould matcher
}
}
}
/**
* Extensible environment to describe parse/match testing workflows in a concise way.
* Can be used inside of a [io.kotlintest.specs.FunSpec] with [parserTest].
* Can be used inside of a [ParserTestSpec] with [ParserTestSpec.parserTest].
*
* Parsing contexts allow to parse a string containing only the node you're interested
* in instead of writing up a full class that the parser can handle. See [parseAstExpression],
@ -186,11 +83,8 @@ open class ParserTestCtx(val javaVersion: JavaVersion = JavaVersion.Latest,
return types + otherImports.map { "import $it;" }
}
inline fun <reified N : Node> makeMatcher(nodeParsingCtx: NodeParsingCtx<*>, ignoreChildren: Boolean, noinline nodeSpec: NWrapper<N>.() -> Unit): Matcher<String> =
object : Matcher<String> {
override fun test(value: String): Result =
matchNode(ignoreChildren, nodeSpec).test(nodeParsingCtx.parseAndFind<N>(value))
}
inline fun <reified N : Node> makeMatcher(nodeParsingCtx: NodeParsingCtx<*>, ignoreChildren: Boolean, noinline nodeSpec: NodeSpec<N>)
: Assertions<String> = { nodeParsingCtx.parseAndFind<N>(it).shouldMatchNode(ignoreChildren, nodeSpec) }
/**
@ -199,7 +93,7 @@ open class ParserTestCtx(val javaVersion: JavaVersion = JavaVersion.Latest,
*
*/
inline fun <reified N : Node> matchExpr(ignoreChildren: Boolean = false,
noinline nodeSpec: NWrapper<N>.() -> Unit): Matcher<String> =
noinline nodeSpec: NodeSpec<N>) =
makeMatcher(ExpressionParsingCtx(this), ignoreChildren, nodeSpec)
/**
@ -207,7 +101,7 @@ open class ParserTestCtx(val javaVersion: JavaVersion = JavaVersion.Latest,
* type param [N], then matches it against the [nodeSpec] using [matchNode].
*/
inline fun <reified N : Node> matchStmt(ignoreChildren: Boolean = false,
noinline nodeSpec: NWrapper<N>.() -> Unit) =
noinline nodeSpec: NodeSpec<N>) =
makeMatcher(StatementParsingCtx(this), ignoreChildren, nodeSpec)
@ -216,16 +110,15 @@ open class ParserTestCtx(val javaVersion: JavaVersion = JavaVersion.Latest,
* type param [N], then matches it against the [nodeSpec] using [matchNode].
*/
inline fun <reified N : Node> matchType(ignoreChildren: Boolean = false,
noinline nodeSpec: NWrapper<N>.() -> Unit) =
noinline nodeSpec: NodeSpec<N>) =
makeMatcher(TypeParsingCtx(this), ignoreChildren, nodeSpec)
/**
* Returns a String matcher that parses the node using [parseToplevelDeclaration] with
* type param [N], then matches it against the [nodeSpec] using [matchNode].
*/
inline fun <reified N : ASTAnyTypeDeclaration> matchToplevelType(
ignoreChildren: Boolean = false,
noinline nodeSpec: NWrapper<N>.() -> Unit) =
inline fun <reified N : ASTAnyTypeDeclaration> matchToplevelType(ignoreChildren: Boolean = false,
noinline nodeSpec: NodeSpec<N>) =
makeMatcher(TopLevelTypeDeclarationParsingCtx(this), ignoreChildren, nodeSpec)
/**
@ -236,7 +129,7 @@ open class ParserTestCtx(val javaVersion: JavaVersion = JavaVersion.Latest,
*/
inline fun <reified N : Node> matchDeclaration(
ignoreChildren: Boolean = false,
noinline nodeSpec: NWrapper<N>.() -> Unit) = makeMatcher(EnclosedDeclarationParsingCtx(this), ignoreChildren, nodeSpec)
noinline nodeSpec: NodeSpec<N>) = makeMatcher(EnclosedDeclarationParsingCtx(this), ignoreChildren, nodeSpec)
/**

View File

@ -0,0 +1,130 @@
package net.sourceforge.pmd.lang.java.ast
import io.kotlintest.AbstractSpec
import io.kotlintest.TestContext
import io.kotlintest.TestType
import io.kotlintest.specs.IntelliMarker
import net.sourceforge.pmd.lang.ast.test.Assertions
import io.kotlintest.should as kotlintestShould
/**
* Base class for grammar tests that use the DSL. Tests are layered into
* containers that make it easier to browse in the IDE. Layout is group name,
* then java version, then test case. Test cases are "should" assertions matching
* a string against a matcher defined in [ParserTestCtx], e.g. [ParserTestCtx.matchExpr].
*
* @author Clément Fournier
*/
abstract class ParserTestSpec(body: ParserTestSpec.() -> Unit) : AbstractSpec(), IntelliMarker {
init {
body()
}
fun test(name: String, test: TestContext.() -> Unit) =
addTestCase(name, test, defaultTestCaseConfig, TestType.Test)
/**
* Defines a group of tests that should be named similarly,
* with separate tests for separate versions.
*
* Calls to "should" in the block are intercepted to create
* a new test.
*
* This is useful to make a batch of grammar specs for grammar
* regression tests without bothering to find a name.
*
* @param name Name of the container test
* @param spec Assertions. Each call to [io.kotlintest.should] on a string
* receiver is replaced by a [GroupTestCtx.should], which creates a
* new parser test.
*
*/
fun parserTestGroup(name: String,
spec: GroupTestCtx.() -> Unit) =
addTestCase(name, { GroupTestCtx(this).spec() }, defaultTestCaseConfig, TestType.Container)
/**
* Defines a group of tests that should be named similarly.
* Calls to "should" in the block are intercepted to create
* a new test, with the given [name] as a common prefix.
*
* This is useful to make a batch of grammar specs for grammar
* regression tests without bothering to find a name.
*
* @param name Name of the container test
* @param javaVersion Language versions to use when parsing
* @param spec Assertions. Each call to [io.kotlintest.should] on a string
* receiver is replaced by a [GroupTestCtx.should], which creates a
* new parser test.
*
*/
fun parserTest(name: String,
javaVersion: JavaVersion = JavaVersion.Latest,
spec: GroupTestCtx.VersionedTestCtx.() -> Unit) =
parserTest(name, listOf(javaVersion), spec)
/**
* Defines a group of tests that should be named similarly,
* executed on several java versions. Calls to "should" in
* the block are intercepted to create a new test, with the
* given [name] as a common prefix.
*
* This is useful to make a batch of grammar specs for grammar
* regression tests without bothering to find a name.
*
* @param name Name of the container test
* @param javaVersions Language versions for which to generate tests
* @param spec Assertions. Each call to [io.kotlintest.should] on a string
* receiver is replaced by a [GroupTestCtx.should], which creates a
* new parser test.
*/
fun parserTest(name: String,
javaVersions: List<JavaVersion>,
spec: GroupTestCtx.VersionedTestCtx.() -> Unit) =
parserTestGroup(name) {
onVersions(javaVersions) {
spec()
}
}
private fun containedParserTestImpl(
context: TestContext,
name: String,
javaVersion: JavaVersion,
assertions: ParserTestCtx.() -> Unit) {
context.registerTestCase(
name = name,
spec = this,
test = { ParserTestCtx(javaVersion).assertions() },
config = defaultTestCaseConfig,
type = TestType.Test
)
}
inner class GroupTestCtx(private val context: TestContext) {
fun onVersions(javaVersions: List<JavaVersion>, spec: VersionedTestCtx.() -> Unit) {
javaVersions.forEach { javaVersion ->
context.registerTestCase(
name = "Java ${javaVersion.pmdName}",
spec = this@ParserTestSpec,
test = { VersionedTestCtx(this, javaVersion).spec() },
config = defaultTestCaseConfig,
type = TestType.Container
)
}
}
inner class VersionedTestCtx(private val context: TestContext, javaVersion: JavaVersion) : ParserTestCtx(javaVersion) {
infix fun String.should(matcher: Assertions<String>) {
containedParserTestImpl(context, "'$this'", javaVersion = javaVersion) {
this@should kotlintestShould matcher
}
}
}
}
}

View File

@ -0,0 +1,25 @@
package net.sourceforge.pmd.lang.java.ast
import net.sourceforge.pmd.lang.ast.test.shouldMatch
import net.sourceforge.pmd.lang.ast.test.shouldBe
import java.util.*
import kotlin.reflect.KCallable
infix fun <T, U : T> Optional<T>.shouldBePresent(any: U) {
::isPresent shouldBe true
::get shouldBe any
}
fun Optional<*>.shouldBeEmpty() {
::isPresent shouldBe false
}
fun KCallable<Optional<*>>.shouldBeEmpty() = this shouldMatch {
::isPresent shouldBe false
}
infix fun <T, U : T> KCallable<Optional<T>>.shouldBePresent(any: U) = this shouldMatch {
::isPresent shouldBe true
::get shouldBe any
}

View File

@ -1,12 +1,8 @@
package net.sourceforge.pmd.lang.java.ast
import io.kotlintest.should
import io.kotlintest.shouldBe
import io.kotlintest.specs.FunSpec
// prototype using a junit syntax
class WildcardBoundsTest : FunSpec({
class WildcardBoundsTest : ParserTestSpec({
parserTest("Simple grammar test") {

View File

@ -0,0 +1,81 @@
<?xml version="1.0" encoding="UTF-8"?>
<test-data
xmlns="http://pmd.sourceforge.net/rule-tests"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://pmd.sourceforge.net/rule-tests http://pmd.sourceforge.net/rule-tests_1_0_0.xsd">
<test-code>
<description>do while true</description>
<expected-problems>1</expected-problems>
<expected-linenumbers>3</expected-linenumbers>
<code><![CDATA[
class Foo {
{
do {
} while (true);
}
}
]]></code>
</test-code>
<test-code>
<description>do while false</description>
<expected-problems>1</expected-problems>
<expected-linenumbers>3</expected-linenumbers>
<code><![CDATA[
class Foo {
{
do {
} while (false);
}
}
]]></code>
</test-code>
<test-code>
<description>do while call</description>
<expected-problems>0</expected-problems>
<code><![CDATA[
class Foo {
{
do {
} while (call(true));
}
}
]]></code>
</test-code>
<test-code>
<description>while true</description>
<expected-problems>0</expected-problems>
<code><![CDATA[
class Foo {
{
while (true) {
}
}
}
]]></code>
</test-code>
<test-code>
<description>while false</description>
<expected-problems>1</expected-problems>
<expected-linenumbers>3</expected-linenumbers>
<code><![CDATA[
class Foo {
{
while (false) {
}
}
}
]]></code>
</test-code>
<test-code>
<description>while call false</description>
<expected-problems>0</expected-problems>
<code><![CDATA[
class Foo {
{
while (call(false)) {
}
}
}
]]></code>
</test-code>
</test-data>

View File

@ -82,6 +82,13 @@
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.github.oowekyala.treeutils</groupId>
<artifactId>tree-matchers</artifactId>
<version>2.0.1</version>
<scope>compile</scope>
</dependency>
<!-- Use pmd-java for tests -->
<dependency>
<groupId>net.sourceforge.pmd</groupId>

View File

@ -0,0 +1,47 @@
package net.sourceforge.pmd.lang.ast.test
import com.github.oowekyala.treeutils.TreeLikeAdapter
import com.github.oowekyala.treeutils.matchers.MatchingConfig
import com.github.oowekyala.treeutils.matchers.TreeNodeWrapper
import com.github.oowekyala.treeutils.matchers.baseShouldMatchSubtree
import com.github.oowekyala.treeutils.printers.KotlintestBeanTreePrinter
import net.sourceforge.pmd.lang.ast.Node
/** An adapter for [baseShouldMatchSubtree]. */
object NodeTreeLikeAdapter : TreeLikeAdapter<Node> {
override fun getChildren(node: Node): List<Node> = node.findChildrenOfType(Node::class.java)
override fun nodeName(type: Class<out Node>): String = type.simpleName.removePrefix("AST")
}
/** A subtree matcher written in the DSL documented on [TreeNodeWrapper]. */
typealias NodeSpec<N> = TreeNodeWrapper<Node, N>.() -> Unit
/** A function feedable to [io.kotlintest.should], which fails the test if an [AssertionError] is thrown. */
typealias Assertions<M> = (M) -> Unit
/** A shorthand for [baseShouldMatchSubtree] providing the [NodeTreeLikeAdapter]. */
inline fun <reified N : Node> Node?.shouldMatchNode(ignoreChildren: Boolean = false, noinline nodeSpec: NodeSpec<N>) {
this.baseShouldMatchSubtree(MatchingConfig(adapter = NodeTreeLikeAdapter, errorPrinter = KotlintestBeanTreePrinter(NodeTreeLikeAdapter)), ignoreChildren, nodeSpec)
}
/**
* Returns [an assertion function][Assertions] asserting that its parameter conforms to the given [NodeSpec].
*
* Use it with [io.kotlintest.should], e.g. `node should matchNode<ASTExpression> {}`.
*
* See also the samples on [TreeNodeWrapper].
*
* @param N Expected type of the node
*
* @param ignoreChildren If true, calls to [TreeNodeWrapper.child] in the [nodeSpec] are forbidden.
* The number of children of the child is not asserted.
*
* @param nodeSpec Sequence of assertions to carry out on the node, which can be referred to by [TreeNodeWrapper.it].
* Assertions may consist of [NWrapper.child] calls, which perform the same type of node
* matching on a child of the tested node.
*
* @return A matcher for AST nodes, suitable for use by [io.kotlintest.should].
*/
inline fun <reified N : Node> matchNode(ignoreChildren: Boolean = false, noinline nodeSpec: NodeSpec<N>)
: Assertions<Node?> = { it.shouldMatchNode(ignoreChildren, nodeSpec) }

View File

@ -1,6 +1,6 @@
package net.sourceforge.pmd.lang.ast.test
import io.kotlintest.Matcher
import io.kotlintest.should
import kotlin.reflect.KCallable
import io.kotlintest.shouldBe as ktShouldBe
@ -47,4 +47,5 @@ private fun <N, V> assertWrapper(callable: KCallable<N>, right: V, asserter: (N,
*/
infix fun <N, V : N> KCallable<N>.shouldBe(expected: V?) = this.shouldEqual(expected)
infix fun <T> KCallable<T>.shouldBe(expected: Matcher<T>) = assertWrapper(this, expected) { n, v -> n ktShouldBe v }
infix fun <T> KCallable<T>.shouldMatch(expected: T.() -> Unit) = assertWrapper(this, expected) { n, v -> n should v }

View File

@ -1,138 +0,0 @@
package net.sourceforge.pmd.lang.ast.test
import io.kotlintest.should
import io.kotlintest.shouldBe
import io.kotlintest.specs.FunSpec
import net.sourceforge.pmd.lang.java.ast.*
class DslTest : FunSpec({
failureTest("Empty matcher spec should check the number of children",
messageContains = setOf("Wrong", "number", "children", "expected 0", "actual 2")) {
parseStatement("int i = 0;") should matchNode<ASTLocalVariableDeclaration> {}
}
test("Matcher with ignoreChildren should not check the number of children") {
parseStatement("int i = 0;") should matchNode<ASTLocalVariableDeclaration>(ignoreChildren = true) {}
}
failureTest("Incorrect node type should cause failure",
messageContains = setOf("Expression", "actual LocalVariableDeclaration")) {
parseStatement("int i = 0;") should matchNode<ASTExpression>(ignoreChildren = true) {}
}
failureTest("Specifying any child in a pattern should cause the number of children to be checked",
messageContains = setOf("number", "children", "expected 1", "actual 2")) {
parseStatement("int i = 0;") should matchNode<ASTLocalVariableDeclaration> {
child<ASTType>(ignoreChildren = true) {}
// There's a VarDeclarator
}
}
test("Unspecified children should shift the next child matchers") {
parseStatement("int i = 0;") should matchNode<ASTLocalVariableDeclaration> {
unspecifiedChild()
child<ASTVariableDeclarator>(ignoreChildren = true) {}
}
}
test("Unspecified children should count in total number of children") {
parseStatement("int i = 0;") should matchNode<ASTLocalVariableDeclaration> {
unspecifiedChildren(2)
}
}
failureTest("Unspecified children should be counted in the number of expected children",
messageContains = setOf("#2 doesn't exist")) {
parseStatement("int i = 0;") should matchNode<ASTLocalVariableDeclaration> {
unspecifiedChildren(3)
}
}
failureTest("Assertions are always executed in order",
messageContains = setOf("PrimitiveType")) {
parseStatement("int[] i = 0;") should matchNode<ASTLocalVariableDeclaration> {
child<ASTType> {
// Here we check that the child type check fails before the assertion
child<ASTPrimitiveType> {}
it.typeImage shouldBe "bratwurst"
}
unspecifiedChild()
}
}
failureTest("Assertions are always executed in order #2",
messageContains = setOf("bratwurst")) {
parseStatement("int[] i = 0;") should matchNode<ASTLocalVariableDeclaration> {
child<ASTType> {
it.typeImage shouldBe "bratwurst"
child<ASTPrimitiveType> {}
}
unspecifiedChild()
}
}
failureTest("All assertions should have a node path",
messageContains = setOf("At /LocalVariableDeclaration/Type:", "expected: \"bratwurst\"")) {
parseStatement("int[] i = 0;") should matchNode<ASTLocalVariableDeclaration> {
child<ASTType> {
// this fails
it.typeImage shouldBe "bratwurst"
}
unspecifiedChild()
}
}
failureTest("Child assertions should have a node path",
messageContains = setOf("At /LocalVariableDeclaration/Type:", "expected", "type", "LambdaExpression")) {
parseStatement("int[] i = 0;") should matchNode<ASTLocalVariableDeclaration> {
child<ASTType> {
// this fails
child<ASTLambdaExpression> { }
}
unspecifiedChild()
}
}
failureTest("Leaf nodes should assert that they have no children",
messageContains = setOf("number", "children", "expected 0")) {
parseStatement("int[] i = 0;") should matchNode<ASTLocalVariableDeclaration> {
child<ASTType> {} // This should fail
unspecifiedChild()
}
}
})

View File

@ -1,41 +0,0 @@
package net.sourceforge.pmd.lang.ast.test
import net.sourceforge.pmd.lang.LanguageRegistry
import net.sourceforge.pmd.lang.ast.Node
import net.sourceforge.pmd.lang.java.JavaLanguageModule
import net.sourceforge.pmd.lang.java.ast.ASTBlockStatement
import net.sourceforge.pmd.lang.java.ast.ASTCompilationUnit
import java.io.StringReader
// These could be used directly by the pmd-java test module
fun parseStatement(statement: String): Node {
// place the param in a statement parsing context
val source = """
class Foo {
{
$statement
}
}
""".trimIndent()
val root = parseCompilationUnit(source)
return root.getFirstDescendantOfType(ASTBlockStatement::class.java).jjtGetChild(0)
}
fun parseCompilationUnit(sourceCode: String): ASTCompilationUnit {
val languageVersionHandler = LanguageRegistry.getLanguage(JavaLanguageModule.NAME).defaultVersion.languageVersionHandler
val rootNode = languageVersionHandler.getParser(languageVersionHandler.defaultParserOptions).parse(":test:", StringReader(sourceCode))
languageVersionHandler.getQualifiedNameResolutionFacade(ClassLoader.getSystemClassLoader()).start(rootNode)
languageVersionHandler.symbolFacade.start(rootNode)
languageVersionHandler.dataFlowFacade.start(rootNode)
languageVersionHandler.getTypeResolutionFacade(ClassLoader.getSystemClassLoader()).start(rootNode)
languageVersionHandler.multifileFacade.start(rootNode)
return rootNode as ASTCompilationUnit
}

View File

@ -1,30 +0,0 @@
package net.sourceforge.pmd.lang.ast.test
import io.kotlintest.matchers.string.shouldContainIgnoringCase
import io.kotlintest.shouldThrow
import io.kotlintest.specs.AbstractFunSpec
// Improve on the KotlinTest DSL for our specific needs
// a testing DSL testing a testing DSL!
fun AbstractFunSpec.failureTest(testName: String,
messageContains: Set<String> = emptySet(),
param: io.kotlintest.TestContext.() -> kotlin.Unit) {
this.expectFailure<AssertionError>(testName, messageContains, param)
}
inline fun <reified T : Throwable> AbstractFunSpec.expectFailure(testName: String,
messageContains: Set<String> = emptySet(),
noinline param: io.kotlintest.TestContext.() -> kotlin.Unit) {
test(testName) {
val exception = shouldThrow<T> {
this.param() // this is the test context here
}
for (substr in messageContains) exception.message.shouldContainIgnoringCase(substr)
}
}

3
pmd-ui/README.md Normal file
View File

@ -0,0 +1,3 @@
The rule designer's codebase lives at [pmd/pmd-designer](https://github.com/pmd/pmd-designer)
from now on (March 2019).

Some files were not shown because too many files have changed in this diff Show More