forked from phoedos/pmd
Allow to write tests in Kotlin
This commit is contained in:
@ -39,6 +39,11 @@
|
|||||||
</configuration>
|
</configuration>
|
||||||
</plugin>
|
</plugin>
|
||||||
|
|
||||||
|
<plugin>
|
||||||
|
<artifactId>kotlin-maven-plugin</artifactId>
|
||||||
|
<groupId>org.jetbrains.kotlin</groupId>
|
||||||
|
</plugin>
|
||||||
|
|
||||||
<plugin>
|
<plugin>
|
||||||
<groupId>org.apache.maven.plugins</groupId>
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
<artifactId>maven-antrun-plugin</artifactId>
|
<artifactId>maven-antrun-plugin</artifactId>
|
||||||
|
@ -1,89 +0,0 @@
|
|||||||
/**
|
|
||||||
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
|
|
||||||
*/
|
|
||||||
|
|
||||||
package net.sourceforge.pmd.lang.java.ast;
|
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
|
||||||
import static org.junit.Assert.assertNotNull;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.nio.charset.StandardCharsets;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import org.apache.commons.io.IOUtils;
|
|
||||||
import org.junit.Assert;
|
|
||||||
import org.junit.Test;
|
|
||||||
|
|
||||||
import net.sourceforge.pmd.lang.ast.Node;
|
|
||||||
import net.sourceforge.pmd.lang.java.ParserTstUtil;
|
|
||||||
|
|
||||||
public class Java11Test {
|
|
||||||
private static String loadSource(String name) {
|
|
||||||
try {
|
|
||||||
return IOUtils.toString(Java10Test.class.getResourceAsStream("jdkversiontests/java11/" + name),
|
|
||||||
StandardCharsets.UTF_8);
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testLocalVariableSyntaxForLambdaParametersWithJava10() {
|
|
||||||
ASTCompilationUnit compilationUnit = ParserTstUtil.parseAndTypeResolveJava("10",
|
|
||||||
loadSource("LocalVariableSyntaxForLambdaParameters.java"));
|
|
||||||
|
|
||||||
List<ASTLambdaExpression> lambdas = compilationUnit.findDescendantsOfType(ASTLambdaExpression.class);
|
|
||||||
Assert.assertEquals(4, lambdas.size());
|
|
||||||
|
|
||||||
// (var x) -> String.valueOf(x);
|
|
||||||
List<ASTFormalParameter> formalParameters = lambdas.get(0).findDescendantsOfType(ASTFormalParameter.class);
|
|
||||||
Assert.assertEquals(1, formalParameters.size());
|
|
||||||
ASTType type = formalParameters.get(0).getFirstChildOfType(ASTType.class);
|
|
||||||
assertEquals("var", type.getTypeImage());
|
|
||||||
assertEquals(1, type.jjtGetNumChildren());
|
|
||||||
ASTReferenceType referenceType = type.getFirstChildOfType(ASTReferenceType.class);
|
|
||||||
assertNotNull(referenceType);
|
|
||||||
assertEquals(1, referenceType.jjtGetNumChildren());
|
|
||||||
ASTClassOrInterfaceType classType = referenceType.getFirstChildOfType(ASTClassOrInterfaceType.class);
|
|
||||||
assertNotNull(classType);
|
|
||||||
assertEquals("var", classType.getImage());
|
|
||||||
|
|
||||||
// (var x, var y) -> x + y;
|
|
||||||
formalParameters = lambdas.get(1).findDescendantsOfType(ASTFormalParameter.class);
|
|
||||||
Assert.assertEquals(2, formalParameters.size());
|
|
||||||
type = formalParameters.get(0).getFirstChildOfType(ASTType.class);
|
|
||||||
assertEquals("var", type.getTypeImage());
|
|
||||||
assertEquals(1, type.jjtGetNumChildren());
|
|
||||||
referenceType = type.getFirstChildOfType(ASTReferenceType.class);
|
|
||||||
assertNotNull(referenceType);
|
|
||||||
assertEquals(1, referenceType.jjtGetNumChildren());
|
|
||||||
classType = referenceType.getFirstChildOfType(ASTClassOrInterfaceType.class);
|
|
||||||
assertNotNull(classType);
|
|
||||||
assertEquals("var", classType.getImage());
|
|
||||||
type = formalParameters.get(1).getFirstChildOfType(ASTType.class);
|
|
||||||
assertEquals("var", type.getTypeImage());
|
|
||||||
assertEquals(1, type.jjtGetNumChildren());
|
|
||||||
|
|
||||||
// (@Nonnull var x) -> String.valueOf(x);
|
|
||||||
formalParameters = lambdas.get(2).findDescendantsOfType(ASTFormalParameter.class);
|
|
||||||
Assert.assertEquals(1, formalParameters.size());
|
|
||||||
Node firstChild = formalParameters.get(0).jjtGetChild(0);
|
|
||||||
Assert.assertTrue(firstChild instanceof ASTAnnotation);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testLocalVariableSyntaxForLambdaParametersWithJava11() {
|
|
||||||
ASTCompilationUnit compilationUnit = ParserTstUtil.parseAndTypeResolveJava("11",
|
|
||||||
loadSource("LocalVariableSyntaxForLambdaParameters.java"));
|
|
||||||
|
|
||||||
List<ASTLambdaExpression> lambdas = compilationUnit.findDescendantsOfType(ASTLambdaExpression.class);
|
|
||||||
Assert.assertEquals(4, lambdas.size());
|
|
||||||
|
|
||||||
// (var x) -> String.valueOf(x);
|
|
||||||
List<ASTFormalParameter> formalParameters = lambdas.get(0).findDescendantsOfType(ASTFormalParameter.class);
|
|
||||||
Assert.assertEquals(1, formalParameters.size());
|
|
||||||
Assert.assertNull(formalParameters.get(0).getTypeNode());
|
|
||||||
Assert.assertTrue(formalParameters.get(0).isTypeInferred());
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,103 @@
|
|||||||
|
import io.kotlintest.should
|
||||||
|
import io.kotlintest.shouldBe
|
||||||
|
import net.sourceforge.pmd.lang.ast.test.matchNode
|
||||||
|
import net.sourceforge.pmd.lang.java.ast.*
|
||||||
|
|
||||||
|
import org.junit.Test
|
||||||
|
|
||||||
|
class Java11Test {
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testLocalVariableSyntaxForLambdaParametersWithJava10() {
|
||||||
|
|
||||||
|
val lambdas = listOf(
|
||||||
|
"(var x) -> String.valueOf(x)",
|
||||||
|
"(var x, var y) -> x + y",
|
||||||
|
"(@Nonnull var x) -> String.valueOf(x)"
|
||||||
|
).map { parseExpression<ASTLambdaExpression>(it, javaVersion = "10") }
|
||||||
|
|
||||||
|
// (var x) -> String.valueOf(x)
|
||||||
|
lambdas[0] should matchNode<ASTLambdaExpression> {
|
||||||
|
child<ASTFormalParameters> {
|
||||||
|
child<ASTFormalParameter> {
|
||||||
|
child<ASTType> {
|
||||||
|
it.typeImage shouldBe "var"
|
||||||
|
|
||||||
|
child<ASTReferenceType> {
|
||||||
|
child<ASTClassOrInterfaceType> {
|
||||||
|
it.image shouldBe "var"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
child<ASTVariableDeclaratorId> { }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unspecifiedChild()
|
||||||
|
}
|
||||||
|
|
||||||
|
// (var x, var y) -> x + y
|
||||||
|
lambdas[1] should matchNode<ASTLambdaExpression> {
|
||||||
|
child<ASTFormalParameters> {
|
||||||
|
child<ASTFormalParameter> {
|
||||||
|
child<ASTType> {
|
||||||
|
it.typeImage shouldBe "var"
|
||||||
|
|
||||||
|
child<ASTReferenceType> {
|
||||||
|
child<ASTClassOrInterfaceType> {
|
||||||
|
it.image shouldBe "var"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
child<ASTVariableDeclaratorId> { }
|
||||||
|
}
|
||||||
|
|
||||||
|
child<ASTFormalParameter> {
|
||||||
|
child<ASTType> {
|
||||||
|
it.typeImage shouldBe "var"
|
||||||
|
|
||||||
|
child<ASTReferenceType> {
|
||||||
|
child<ASTClassOrInterfaceType> {
|
||||||
|
it.image shouldBe "var"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
child<ASTVariableDeclaratorId> { }
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unspecifiedChild()
|
||||||
|
}
|
||||||
|
|
||||||
|
// (@Nonnull var x) -> String.valueOf(x)
|
||||||
|
lambdas[2] should matchNode<ASTLambdaExpression> {
|
||||||
|
child<ASTFormalParameters> {
|
||||||
|
child<ASTFormalParameter> {
|
||||||
|
child<ASTAnnotation>(ignoreChildren = true) {}
|
||||||
|
unspecifiedChildren(2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
unspecifiedChild()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testLocalVariableSyntaxForLambdaParametersWithJava11() {
|
||||||
|
|
||||||
|
val lambda: ASTLambdaExpression = parseExpression("(var x) -> String.valueOf(x)", javaVersion = "11")
|
||||||
|
|
||||||
|
lambda should matchNode<ASTLambdaExpression> {
|
||||||
|
child<ASTFormalParameters> {
|
||||||
|
child<ASTFormalParameter> {
|
||||||
|
it.isTypeInferred shouldBe true
|
||||||
|
child<ASTVariableDeclaratorId> { }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unspecifiedChild()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,48 @@
|
|||||||
|
package net.sourceforge.pmd.lang.java.ast
|
||||||
|
|
||||||
|
import net.sourceforge.pmd.lang.ast.Node
|
||||||
|
import net.sourceforge.pmd.lang.java.ParserTstUtil
|
||||||
|
|
||||||
|
|
||||||
|
const val defaultJavaVersion = "11"
|
||||||
|
|
||||||
|
inline fun <reified N : Node> parseExpression(expr: String, javaVersion: String = defaultJavaVersion): N =
|
||||||
|
parseAstExpression(expr, javaVersion).getFirstDescendantOfType(N::class.java)
|
||||||
|
|
||||||
|
fun parseAstExpression(expr: String, javaVersion: String = defaultJavaVersion): ASTExpression {
|
||||||
|
|
||||||
|
val source = """
|
||||||
|
class Foo {
|
||||||
|
{
|
||||||
|
Object o = $expr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
""".trimIndent()
|
||||||
|
|
||||||
|
val acu = ParserTstUtil.parseAndTypeResolveJava(javaVersion, source)
|
||||||
|
|
||||||
|
return acu.getFirstDescendantOfType(ASTVariableInitializer::class.java).jjtGetChild(0) as ASTExpression
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
inline fun <reified N : Node> parseStatement(stmt: String, javaVersion: String = defaultJavaVersion): N =
|
||||||
|
parseStatement(stmt, javaVersion).getFirstChildOfType(N::class.java)
|
||||||
|
|
||||||
|
|
||||||
|
fun parseStatement(statement: String, javaVersion: String = defaultJavaVersion): ASTBlockStatement {
|
||||||
|
|
||||||
|
// place the param in a statement parsing context
|
||||||
|
val source = """
|
||||||
|
class Foo {
|
||||||
|
{
|
||||||
|
$statement
|
||||||
|
}
|
||||||
|
}
|
||||||
|
""".trimIndent()
|
||||||
|
|
||||||
|
val root = ParserTstUtil.parseAndTypeResolveJava(javaVersion, source)
|
||||||
|
|
||||||
|
return root.getFirstDescendantOfType(ASTBlockStatement::class.java)
|
||||||
|
}
|
||||||
|
|
||||||
|
// also need e.g. parseDeclaration
|
@ -38,5 +38,68 @@
|
|||||||
<version>1.10.19</version>
|
<version>1.10.19</version>
|
||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.jetbrains.kotlin</groupId>
|
||||||
|
<artifactId>kotlin-stdlib</artifactId>
|
||||||
|
<version>${kotlin.version}</version>
|
||||||
|
<scope>compile</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.jetbrains.kotlin</groupId>
|
||||||
|
<artifactId>kotlin-stdlib-jdk8</artifactId>
|
||||||
|
<version>${kotlin.version}</version>
|
||||||
|
<scope>compile</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.jetbrains.kotlin</groupId>
|
||||||
|
<artifactId>kotlin-test-junit</artifactId>
|
||||||
|
<version>${kotlin.version}</version>
|
||||||
|
<scope>compile</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.kotlintest</groupId>
|
||||||
|
<artifactId>kotlintest-runner-junit5</artifactId>
|
||||||
|
<version>3.1.8</version>
|
||||||
|
<scope>compile</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Use pmd-java for tests -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>net.sourceforge.pmd</groupId>
|
||||||
|
<artifactId>pmd-java</artifactId>
|
||||||
|
<version>6.6.0</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<!-- The kotlin plugin has to run before the java plugin-->
|
||||||
|
<plugin>
|
||||||
|
<artifactId>kotlin-maven-plugin</artifactId>
|
||||||
|
<groupId>org.jetbrains.kotlin</groupId>
|
||||||
|
<version>${kotlin.version}</version>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<id>kotlin-compile</id>
|
||||||
|
<goals>
|
||||||
|
<goal>compile</goal>
|
||||||
|
</goals>
|
||||||
|
<phase>process-sources</phase>
|
||||||
|
<configuration>
|
||||||
|
<sourceDirs>
|
||||||
|
<sourceDir>${project.basedir}/src/main/kotlin</sourceDir>
|
||||||
|
<sourceDir>${project.basedir}/src/main/java</sourceDir>
|
||||||
|
</sourceDirs>
|
||||||
|
</configuration>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
</project>
|
</project>
|
||||||
|
@ -0,0 +1,222 @@
|
|||||||
|
package net.sourceforge.pmd.lang.ast.test
|
||||||
|
|
||||||
|
import arrow.legacy.disjunctionTry
|
||||||
|
import io.kotlintest.Matcher
|
||||||
|
import io.kotlintest.Result
|
||||||
|
import net.sourceforge.pmd.lang.ast.Node
|
||||||
|
import kotlin.test.assertFalse
|
||||||
|
import kotlin.test.assertTrue
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wraps a node, providing easy access to [it]. Additional matching
|
||||||
|
* methods are provided to match children.
|
||||||
|
*
|
||||||
|
* @property it Wrapped node
|
||||||
|
* @param <N> Type of the node
|
||||||
|
*/
|
||||||
|
class NWrapper<N : Node> private constructor(val it: N, private val childMatchersAreIgnored: Boolean) {
|
||||||
|
|
||||||
|
/** Index to which the next child matcher will apply. */
|
||||||
|
private var nextChildMatcherIdx = 0
|
||||||
|
|
||||||
|
private fun shiftChild(num: Int = 1): Node {
|
||||||
|
|
||||||
|
checkChildExists(nextChildMatcherIdx)
|
||||||
|
|
||||||
|
val ret = it.jjtGetChild(nextChildMatcherIdx)
|
||||||
|
|
||||||
|
nextChildMatcherIdx += num
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private fun checkChildExists(childIdx: Int) =
|
||||||
|
assertTrue("Node has fewer children than expected, child #$childIdx doesn't exist") {
|
||||||
|
childIdx < it.numChildren
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Specify that the next [num] children will only be tested for existence,
|
||||||
|
* but not for type, or anything else.
|
||||||
|
*/
|
||||||
|
fun unspecifiedChildren(num: Int) {
|
||||||
|
shiftChild(num)
|
||||||
|
// Checks that the last child mentioned exists
|
||||||
|
checkChildExists(nextChildMatcherIdx - 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Specify that the next child will only be tested for existence,
|
||||||
|
* but not for type, or anything else.
|
||||||
|
*/
|
||||||
|
fun unspecifiedChild() = unspecifiedChildren(1)
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Specify that the next child will be tested against the assertions
|
||||||
|
* defined by the lambda.
|
||||||
|
*
|
||||||
|
* This method asserts that the child exists, and that it is of the
|
||||||
|
* required type [M]. The lambda is then executed on it. Subsequent
|
||||||
|
* calls to this method at the same tree level will test the next
|
||||||
|
* children.
|
||||||
|
*
|
||||||
|
* @param ignoreChildren If true, calls to [child] in the [nodeSpec] are ignored.
|
||||||
|
* The number of children of the child is not asserted either.
|
||||||
|
* @param nodeSpec Sequence of assertions to carry out on the child node
|
||||||
|
*
|
||||||
|
* @param M Expected type of the child
|
||||||
|
*/
|
||||||
|
inline fun <reified M : Node> child(ignoreChildren: Boolean = false, noinline nodeSpec: NWrapper<M>.() -> Unit) =
|
||||||
|
childImpl(ignoreChildren, M::class.java, nodeSpec)
|
||||||
|
|
||||||
|
|
||||||
|
@PublishedApi
|
||||||
|
internal fun <M : Node> childImpl(ignoreChildren: Boolean, childType: Class<M>, nodeSpec: NWrapper<M>.() -> Unit) {
|
||||||
|
if (!childMatchersAreIgnored) executeWrapper(childType, shiftChild(), ignoreChildren, nodeSpec)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
override fun toString(): String {
|
||||||
|
return "NWrapper<${it.xPathNodeName}>"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
internal val Node.numChildren: Int
|
||||||
|
get() = this.jjtGetNumChildren()
|
||||||
|
|
||||||
|
|
||||||
|
private val <M : Node> Class<M>.nodeName
|
||||||
|
get() =
|
||||||
|
if (simpleName.startsWith("AST", ignoreCase = false))
|
||||||
|
simpleName.substring("AST".length)
|
||||||
|
else simpleName
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute wrapper assertions on a node.
|
||||||
|
*
|
||||||
|
* @param childType Expected type of [toWrap]
|
||||||
|
* @param toWrap Node on which to execute the assertions
|
||||||
|
* @param ignoreChildrenMatchers Ignore the children matchers in [spec]
|
||||||
|
* @param spec Assertions to carry out on [toWrap]
|
||||||
|
*
|
||||||
|
* @throws AssertionError If some assertions fail
|
||||||
|
*/
|
||||||
|
@PublishedApi
|
||||||
|
internal fun <M : Node> executeWrapper(childType: Class<M>, toWrap: Node, ignoreChildrenMatchers: Boolean, spec: NWrapper<M>.() -> Unit) {
|
||||||
|
|
||||||
|
assertTrue("Expected node to have type ${childType.nodeName}, actual ${toWrap.javaClass.nodeName}") {
|
||||||
|
childType.isInstance(toWrap)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
val wrapper = NWrapper(toWrap as M, ignoreChildrenMatchers)
|
||||||
|
|
||||||
|
wrapper.spec()
|
||||||
|
|
||||||
|
assertFalse("<${childType.nodeName}>: Wrong number of children, expected ${wrapper.nextChildMatcherIdx}, actual ${wrapper.it.numChildren}") {
|
||||||
|
!ignoreChildrenMatchers && wrapper.nextChildMatcherIdx != wrapper.it.numChildren
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Matcher for a node, using [NWrapper] to specify a subtree against which
|
||||||
|
* the tested node will be tested.
|
||||||
|
*
|
||||||
|
* Use it with [io.kotlintest.should], e.g. `nodeshould matchNode<ASTExpression> {}`.
|
||||||
|
*
|
||||||
|
* @param N Expected type of the node
|
||||||
|
*
|
||||||
|
* @param ignoreChildren If true, calls to [NWrapper.child] in the [nodeSpec] are ignored.
|
||||||
|
* The number of children of the child is not asserted either.
|
||||||
|
*
|
||||||
|
* @param nodeSpec Sequence of assertions to carry out on the node, which can be referred to by [NWrapper.it].
|
||||||
|
* Assertions may onsist of [NWrapper.child] calls, which perform the same type of node
|
||||||
|
* matching on a child of the tested node.
|
||||||
|
*
|
||||||
|
* @return A matcher for AST nodes, suitable for use by [io.kotlintest.should].
|
||||||
|
*
|
||||||
|
* ### Samples
|
||||||
|
*
|
||||||
|
* node should matchNode<ASTStatement> {
|
||||||
|
*
|
||||||
|
* // nesting matchers allow to specify a whole subtree
|
||||||
|
* child<ASTForStatement> {
|
||||||
|
*
|
||||||
|
* // This would fail if the first child of the ForStatement wasn't a ForInit
|
||||||
|
* child<ASTForInit> {
|
||||||
|
* child<ASTLocalVariableDeclaration> {
|
||||||
|
*
|
||||||
|
* // If the parameter ignoreChildren is set to true, the number of children is not asserted
|
||||||
|
* // Calls to "child" in the block are completely ignored
|
||||||
|
* // The only checks carried out here are the type test and the assertions of the block
|
||||||
|
* child<ASTType>(ignoreChildren = true) {
|
||||||
|
*
|
||||||
|
* // In a "child" block, the tested node can be referred to as "it"
|
||||||
|
* // Here, its static type is ASTType, so we can inspect properties
|
||||||
|
* // of the node and make assertions
|
||||||
|
*
|
||||||
|
* it.typeImage shouldBe "int"
|
||||||
|
* it.type shouldNotBe null
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* // We don't care about that node, we only care that there is "some" node
|
||||||
|
* unspecifiedChild()
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* // The subtree is ignored, but we check a ForUpdate is present at this child position
|
||||||
|
* child<ASTForUpdate>(ignoreChildren = true) {}
|
||||||
|
*
|
||||||
|
* // Here, ignoreChildren is not specified and takes its default value of false.
|
||||||
|
* // The lambda has no "child" calls and the node will be asserted to have no children
|
||||||
|
* child<ASTBlock> {}
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
inline fun <reified N : Node> matchNode(ignoreChildren: Boolean = false, noinline nodeSpec: NWrapper<N>.() -> Unit) = object : Matcher<Node> {
|
||||||
|
override fun test(value: Node): Result {
|
||||||
|
val matchRes = disjunctionTry {
|
||||||
|
NWrapper.executeWrapper(N::class.java, value, ignoreChildren, nodeSpec)
|
||||||
|
}
|
||||||
|
|
||||||
|
val didMatch = matchRes.isRight()
|
||||||
|
|
||||||
|
|
||||||
|
// Output when the node should have matched and did not
|
||||||
|
//
|
||||||
|
val failureMessage: String = matchRes.fold({
|
||||||
|
// Here the node failed
|
||||||
|
it.message ?: "The node did not match the pattern (no cause specified)"
|
||||||
|
}, {
|
||||||
|
// The node matched, which was expected
|
||||||
|
"SHOULD NOT BE OUTPUT"
|
||||||
|
})
|
||||||
|
|
||||||
|
val negatedMessage = matchRes.fold({
|
||||||
|
// the node didn't match, which was expected
|
||||||
|
"SHOULD NOT BE OUTPUT"
|
||||||
|
}, {
|
||||||
|
"The node should not have matched this pattern"
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
return Result(didMatch, failureMessage, negatedMessage)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// This one preserves the stack trace
|
||||||
|
// It's still hard to read because of the inlines, and possibly only IntelliJ knows how to do that
|
||||||
|
// I'll try to get kotlintest to preserve the original stack trace
|
||||||
|
|
||||||
|
//inline fun <reified M : Node> Node.shouldMatchNode(ignoreChildren: Boolean = false, noinline nodeSpec: NWrapper<M>.() -> Unit) {
|
||||||
|
// NWrapper.executeWrapper(M::class.java, this, ignoreChildren, nodeSpec)
|
||||||
|
//}
|
@ -0,0 +1,107 @@
|
|||||||
|
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("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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -0,0 +1,41 @@
|
|||||||
|
package net.sourceforge.pmd.lang.ast.test
|
||||||
|
|
||||||
|
import net.sourceforge.pmd.lang.LanguageRegistry
|
||||||
|
import net.sourceforge.pmd.lang.ast.Node
|
||||||
|
import net.sourceforge.pmd.lang.java.JavaLanguageModule
|
||||||
|
import net.sourceforge.pmd.lang.java.ast.ASTBlockStatement
|
||||||
|
import net.sourceforge.pmd.lang.java.ast.ASTCompilationUnit
|
||||||
|
import java.io.StringReader
|
||||||
|
|
||||||
|
|
||||||
|
// These could be used directly by the pmd-java test module
|
||||||
|
|
||||||
|
fun parseStatement(statement: String): Node {
|
||||||
|
|
||||||
|
// place the param in a statement parsing context
|
||||||
|
val source = """
|
||||||
|
class Foo {
|
||||||
|
{
|
||||||
|
$statement
|
||||||
|
}
|
||||||
|
}
|
||||||
|
""".trimIndent()
|
||||||
|
|
||||||
|
val root = parseCompilationUnit(source)
|
||||||
|
|
||||||
|
return root.getFirstDescendantOfType(ASTBlockStatement::class.java).jjtGetChild(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun parseCompilationUnit(sourceCode: String): ASTCompilationUnit {
|
||||||
|
|
||||||
|
val languageVersionHandler = LanguageRegistry.getLanguage(JavaLanguageModule.NAME).defaultVersion.languageVersionHandler
|
||||||
|
val rootNode = languageVersionHandler.getParser(languageVersionHandler.defaultParserOptions).parse(":test:", StringReader(sourceCode))
|
||||||
|
languageVersionHandler.getQualifiedNameResolutionFacade(ClassLoader.getSystemClassLoader()).start(rootNode)
|
||||||
|
languageVersionHandler.symbolFacade.start(rootNode)
|
||||||
|
languageVersionHandler.dataFlowFacade.start(rootNode)
|
||||||
|
languageVersionHandler.getTypeResolutionFacade(ClassLoader.getSystemClassLoader()).start(rootNode)
|
||||||
|
languageVersionHandler.multifileFacade.start(rootNode)
|
||||||
|
return rootNode as ASTCompilationUnit
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,30 @@
|
|||||||
|
package net.sourceforge.pmd.lang.ast.test
|
||||||
|
|
||||||
|
import io.kotlintest.matchers.string.shouldContainIgnoringCase
|
||||||
|
import io.kotlintest.shouldThrow
|
||||||
|
import io.kotlintest.specs.AbstractFunSpec
|
||||||
|
|
||||||
|
|
||||||
|
// Improve on the KotlinTest DSL for our specific needs
|
||||||
|
// a testing DSL testing a testing DSL!
|
||||||
|
|
||||||
|
|
||||||
|
fun AbstractFunSpec.failureTest(testName: String,
|
||||||
|
messageContains: Set<String> = emptySet(),
|
||||||
|
param: io.kotlintest.TestContext.() -> kotlin.Unit) {
|
||||||
|
|
||||||
|
this.expectFailure<AssertionError>(testName, messageContains, param)
|
||||||
|
}
|
||||||
|
|
||||||
|
inline fun <reified T : Throwable> AbstractFunSpec.expectFailure(testName: String,
|
||||||
|
messageContains: Set<String> = emptySet(),
|
||||||
|
noinline param: io.kotlintest.TestContext.() -> kotlin.Unit) {
|
||||||
|
test(testName) {
|
||||||
|
val exception = shouldThrow<T> {
|
||||||
|
this.param() // this is the test context here
|
||||||
|
}
|
||||||
|
|
||||||
|
for (substr in messageContains) exception.message.shouldContainIgnoringCase(substr)
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
116
pom.xml
116
pom.xml
@ -260,6 +260,14 @@ Additionally it includes CPD, the copy-paste-detector. CPD finds duplicated code
|
|||||||
<maven.compiler.source>1.${java.version}</maven.compiler.source>
|
<maven.compiler.source>1.${java.version}</maven.compiler.source>
|
||||||
<maven.compiler.target>1.${java.version}</maven.compiler.target>
|
<maven.compiler.target>1.${java.version}</maven.compiler.target>
|
||||||
|
|
||||||
|
<maven.compiler.test.source>1.8</maven.compiler.test.source>
|
||||||
|
<maven.compiler.test.target>1.8</maven.compiler.test.target>
|
||||||
|
|
||||||
|
|
||||||
|
<kotlin.compiler.jvmTarget>${maven.compiler.test.target}</kotlin.compiler.jvmTarget>
|
||||||
|
<kotlin.version>1.2.61</kotlin.version>
|
||||||
|
|
||||||
|
|
||||||
<javacc.version>5.0</javacc.version>
|
<javacc.version>5.0</javacc.version>
|
||||||
<surefire.version>2.22.0</surefire.version>
|
<surefire.version>2.22.0</surefire.version>
|
||||||
<checkstyle.version>3.0.0</checkstyle.version>
|
<checkstyle.version>3.0.0</checkstyle.version>
|
||||||
@ -275,6 +283,7 @@ Additionally it includes CPD, the copy-paste-detector. CPD finds duplicated code
|
|||||||
<argLine>-Xmx512m -Dfile.encoding=${project.build.sourceEncoding}</argLine>
|
<argLine>-Xmx512m -Dfile.encoding=${project.build.sourceEncoding}</argLine>
|
||||||
|
|
||||||
<pmd.build-tools.version>1.2</pmd.build-tools.version>
|
<pmd.build-tools.version>1.2</pmd.build-tools.version>
|
||||||
|
|
||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
<build>
|
<build>
|
||||||
@ -329,6 +338,30 @@ Additionally it includes CPD, the copy-paste-detector. CPD finds duplicated code
|
|||||||
<artifactId>maven-clean-plugin</artifactId>
|
<artifactId>maven-clean-plugin</artifactId>
|
||||||
<version>3.1.0</version>
|
<version>3.1.0</version>
|
||||||
</plugin>
|
</plugin>
|
||||||
|
|
||||||
|
<!-- Kotlin compiler for test-compile -->
|
||||||
|
<!-- The kotlin plugin has to run before the java plugin-->
|
||||||
|
<plugin>
|
||||||
|
<artifactId>kotlin-maven-plugin</artifactId>
|
||||||
|
<groupId>org.jetbrains.kotlin</groupId>
|
||||||
|
<version>${kotlin.version}</version>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<id>kotlin-test-compile</id>
|
||||||
|
<goals>
|
||||||
|
<goal>test-compile</goal>
|
||||||
|
</goals>
|
||||||
|
<phase>process-test-sources</phase>
|
||||||
|
<configuration>
|
||||||
|
<sourceDirs>
|
||||||
|
<sourceDir>${project.basedir}/src/test/kotlin</sourceDir>
|
||||||
|
<sourceDir>${project.basedir}/src/test/java</sourceDir>
|
||||||
|
</sourceDirs>
|
||||||
|
</configuration>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
|
|
||||||
<plugin>
|
<plugin>
|
||||||
<groupId>org.apache.maven.plugins</groupId>
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
<artifactId>maven-compiler-plugin</artifactId>
|
<artifactId>maven-compiler-plugin</artifactId>
|
||||||
@ -336,6 +369,36 @@ Additionally it includes CPD, the copy-paste-detector. CPD finds duplicated code
|
|||||||
<configuration>
|
<configuration>
|
||||||
<release>${java.version}</release>
|
<release>${java.version}</release>
|
||||||
</configuration>
|
</configuration>
|
||||||
|
<executions>
|
||||||
|
<!-- Replacing default-compile as it is treated specially by maven -->
|
||||||
|
<execution>
|
||||||
|
<id>default-compile</id>
|
||||||
|
<phase>none</phase>
|
||||||
|
</execution>
|
||||||
|
<!-- Replacing default-testCompile as it is treated specially by maven -->
|
||||||
|
<execution>
|
||||||
|
<id>default-testCompile</id>
|
||||||
|
<phase>none</phase>
|
||||||
|
</execution>
|
||||||
|
<execution>
|
||||||
|
<id>java-compile</id>
|
||||||
|
<phase>compile</phase>
|
||||||
|
<goals>
|
||||||
|
<goal>compile</goal>
|
||||||
|
</goals>
|
||||||
|
</execution>
|
||||||
|
<execution>
|
||||||
|
<id>java-test-compile</id>
|
||||||
|
<phase>test-compile</phase>
|
||||||
|
<goals>
|
||||||
|
<goal>testCompile</goal>
|
||||||
|
</goals>
|
||||||
|
<configuration>
|
||||||
|
<source>${maven.compiler.test.source}</source>
|
||||||
|
<target>${maven.compiler.test.target}</target>
|
||||||
|
</configuration>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
</plugin>
|
</plugin>
|
||||||
<plugin>
|
<plugin>
|
||||||
<groupId>org.apache.maven.plugins</groupId>
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
@ -369,7 +432,27 @@ Additionally it includes CPD, the copy-paste-detector. CPD finds duplicated code
|
|||||||
<configuration>
|
<configuration>
|
||||||
<forkMode>once</forkMode>
|
<forkMode>once</forkMode>
|
||||||
<runOrder>alphabetical</runOrder>
|
<runOrder>alphabetical</runOrder>
|
||||||
|
<testSourceDirectory>${project.basedir}/src/test/java</testSourceDirectory>
|
||||||
|
<testSourceDirectory>${project.basedir}/src/test/kotlin</testSourceDirectory>
|
||||||
</configuration>
|
</configuration>
|
||||||
|
<dependencies>
|
||||||
|
<!-- Junit 5 = Platform + Jupiter (5) + Vintage (3 & 4)-->
|
||||||
|
|
||||||
|
<!-- Junit platform -->
|
||||||
|
<!-- Needed to use kotlintest -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.junit.platform</groupId>
|
||||||
|
<artifactId>junit-platform-surefire-provider</artifactId>
|
||||||
|
<version>1.2.0</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Junit 3 & 4 engine -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.junit.vintage</groupId>
|
||||||
|
<artifactId>junit-vintage-engine</artifactId>
|
||||||
|
<version>5.3.0-M1</version>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
</plugin>
|
</plugin>
|
||||||
<plugin>
|
<plugin>
|
||||||
<groupId>org.codehaus.mojo</groupId>
|
<groupId>org.codehaus.mojo</groupId>
|
||||||
@ -853,6 +936,39 @@ Additionally it includes CPD, the copy-paste-detector. CPD finds duplicated code
|
|||||||
<artifactId>system-rules</artifactId>
|
<artifactId>system-rules</artifactId>
|
||||||
<version>1.8.0</version>
|
<version>1.8.0</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<!-- TEST DEPENDENCIES -->
|
||||||
|
|
||||||
|
<!-- Kotlin -->
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.jetbrains.kotlin</groupId>
|
||||||
|
<artifactId>kotlin-stdlib</artifactId>
|
||||||
|
<version>${kotlin.version}</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.jetbrains.kotlin</groupId>
|
||||||
|
<artifactId>kotlin-stdlib-jdk8</artifactId>
|
||||||
|
<version>${kotlin.version}</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.jetbrains.kotlin</groupId>
|
||||||
|
<artifactId>kotlin-test-junit</artifactId>
|
||||||
|
<version>${kotlin.version}</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.kotlintest</groupId>
|
||||||
|
<artifactId>kotlintest-runner-junit5</artifactId>
|
||||||
|
<version>3.1.8</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
</dependencies>
|
</dependencies>
|
||||||
</dependencyManagement>
|
</dependencyManagement>
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user