Skip to content

Commit 9a60006

Browse files
committed
Add JUnit 5 checkstyle rule
Fixes gh-106
1 parent 29caadc commit 9a60006

File tree

17 files changed

+376
-0
lines changed

17 files changed

+376
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
/*
2+
* Copyright 2017-2019 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package io.spring.javaformat.checkstyle.check;
18+
19+
import java.util.ArrayList;
20+
import java.util.Arrays;
21+
import java.util.Collections;
22+
import java.util.LinkedHashMap;
23+
import java.util.List;
24+
import java.util.Map;
25+
import java.util.stream.Collectors;
26+
27+
import com.puppycrawl.tools.checkstyle.api.DetailAST;
28+
import com.puppycrawl.tools.checkstyle.api.FullIdent;
29+
import com.puppycrawl.tools.checkstyle.api.TokenTypes;
30+
import com.puppycrawl.tools.checkstyle.utils.AnnotationUtil;
31+
32+
/**
33+
* Checks that JUnit 5 conventions are followed and that JUnit 4 is not accidentally used.
34+
*
35+
* @author Phillip Webb
36+
*/
37+
public class SpringJUnit5Check extends AbstractSpringCheck {
38+
39+
private static final String JUNIT4_TEST_ANNOTATION = "org.junit.Test";
40+
41+
private static final String JUNIT5_TEST_ANNOTATION = "org.junit.jupiter.api.Test";
42+
43+
private static final List<String> TEST_ANNOTATIONS = Collections
44+
.unmodifiableList(Arrays.asList("Test", JUNIT4_TEST_ANNOTATION, JUNIT5_TEST_ANNOTATION));
45+
46+
private static final List<String> BANNED_IMPORTS;
47+
static {
48+
List<String> bannedImports = new ArrayList<>();
49+
bannedImports.add(JUNIT4_TEST_ANNOTATION);
50+
bannedImports.add("org.junit.After");
51+
bannedImports.add("org.junit.AfterClass");
52+
bannedImports.add("org.junit.Before");
53+
bannedImports.add("org.junit.BeforeClass");
54+
bannedImports.add("org.junit.Rule");
55+
bannedImports.add("org.junit.ClassRule");
56+
BANNED_IMPORTS = Collections.unmodifiableList(bannedImports);
57+
}
58+
59+
private List<String> unlessImports = new ArrayList<>();
60+
61+
private final List<DetailAST> testMethods = new ArrayList<>();
62+
63+
private final Map<String, FullIdent> imports = new LinkedHashMap<>();
64+
65+
@Override
66+
public int[] getAcceptableTokens() {
67+
return new int[] { TokenTypes.METHOD_DEF, TokenTypes.IMPORT };
68+
}
69+
70+
@Override
71+
public void beginTree(DetailAST rootAST) {
72+
this.testMethods.clear();
73+
}
74+
75+
@Override
76+
public void visitToken(DetailAST ast) {
77+
switch (ast.getType()) {
78+
case TokenTypes.METHOD_DEF:
79+
visitMethodDef(ast);
80+
case TokenTypes.IMPORT:
81+
visitImport(ast);
82+
break;
83+
}
84+
}
85+
86+
private void visitMethodDef(DetailAST ast) {
87+
if (AnnotationUtil.containsAnnotation(ast, TEST_ANNOTATIONS)) {
88+
this.testMethods.add(ast);
89+
}
90+
}
91+
92+
private void visitImport(DetailAST ast) {
93+
FullIdent ident = FullIdent.createFullIdentBelow(ast);
94+
this.imports.put(ident.getText(), ident);
95+
}
96+
97+
@Override
98+
public void finishTree(DetailAST rootAST) {
99+
if (shouldCheck()) {
100+
check();
101+
}
102+
}
103+
104+
private boolean shouldCheck() {
105+
if (this.testMethods.isEmpty()) {
106+
return false;
107+
}
108+
for (String unlessImport : this.unlessImports) {
109+
if (this.imports.containsKey(unlessImport)) {
110+
return false;
111+
}
112+
}
113+
return true;
114+
}
115+
116+
private void check() {
117+
for (String bannedImport : BANNED_IMPORTS) {
118+
FullIdent ident = this.imports.get(bannedImport);
119+
if (ident != null) {
120+
log(ident.getLineNo(), ident.getColumnNo(), "junit5.bannedImport", bannedImport);
121+
}
122+
}
123+
for (DetailAST testMethod : this.testMethods) {
124+
if (AnnotationUtil.containsAnnotation(testMethod, JUNIT4_TEST_ANNOTATION)) {
125+
log(testMethod, "junit5.bannedTestAnnotation");
126+
}
127+
}
128+
for (DetailAST testMethod : this.testMethods) {
129+
DetailAST modifiers = testMethod.findFirstToken(TokenTypes.MODIFIERS);
130+
if (modifiers.findFirstToken(TokenTypes.LITERAL_PUBLIC) != null) {
131+
log(testMethod, "junit5.publicMethod");
132+
}
133+
if (modifiers.findFirstToken(TokenTypes.LITERAL_PRIVATE) != null) {
134+
log(testMethod, "junit5.privateMethod");
135+
}
136+
}
137+
}
138+
139+
private void log(DetailAST method, String key) {
140+
String name = method.findFirstToken(TokenTypes.IDENT).getText();
141+
log(method.getLineNo(), method.getColumnNo(), key, name);
142+
}
143+
144+
public void setUnlessImports(String unlessImports) {
145+
this.unlessImports = Collections.unmodifiableList(
146+
Arrays.stream(unlessImports.split(",")).map(String::trim).collect(Collectors.toList()));
147+
}
148+
149+
}

spring-javaformat/spring-javaformat-checkstyle/src/main/resources/io/spring/javaformat/checkstyle/check/messages.properties

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,7 @@ javadoc.badCase=Javadoc element descriptions should not start with an uppercase
88
header.unexpected=Unexpected header.
99
nothis.unexpected=Reference to instance variable ''{0}'' should not use \"this.\".
1010
methodorder.outOfOrder=Method ''{0}'' is out of order, expected {1}.
11+
junit5.publicMethod="Test method ''{0}'' should not be public."
12+
junit5.privateMethod="Test method ''{0}'' should not be private."
13+
junit5.bannedImport="Import ''{0}'' should not be used in a JUnit 5 test."
14+
junit5.bannedTestAnnotation="JUnit 4 @Test annotation should not be used in a JUnit 5 test."
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
+JUnit 4 @Test annotation should not be used in a JUnit 5 test.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
+Import 'org.junit.Before' should not be used in a JUnit 5 test.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
+0 errors
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
+Test method 'doSomethingWorks' should not be public
2+
+Test method 'doSomethingElseWorks' should not be private
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
+0 errors
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<?xml version="1.0"?>
2+
<!DOCTYPE module PUBLIC
3+
"-//Checkstyle//DTD Checkstyle Configuration 1.3//EN"
4+
"https://checkstyle.org/dtds/configuration_1_3.dtd">
5+
<module name="com.puppycrawl.tools.checkstyle.Checker">
6+
<module name="com.puppycrawl.tools.checkstyle.TreeWalker">
7+
<module name="io.spring.javaformat.checkstyle.check.SpringJUnit5Check">
8+
</module>
9+
</module>
10+
</module>
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<?xml version="1.0"?>
2+
<!DOCTYPE module PUBLIC
3+
"-//Checkstyle//DTD Checkstyle Configuration 1.3//EN"
4+
"https://checkstyle.org/dtds/configuration_1_3.dtd">
5+
<module name="com.puppycrawl.tools.checkstyle.Checker">
6+
<module name="com.puppycrawl.tools.checkstyle.TreeWalker">
7+
<module name="io.spring.javaformat.checkstyle.check.SpringJUnit5Check">
8+
</module>
9+
</module>
10+
</module>
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<?xml version="1.0"?>
2+
<!DOCTYPE module PUBLIC
3+
"-//Checkstyle//DTD Checkstyle Configuration 1.3//EN"
4+
"https://checkstyle.org/dtds/configuration_1_3.dtd">
5+
<module name="com.puppycrawl.tools.checkstyle.Checker">
6+
<module name="com.puppycrawl.tools.checkstyle.TreeWalker">
7+
<module name="io.spring.javaformat.checkstyle.check.SpringJUnit5Check">
8+
<property name="unlessImports" value="com.example.OptOutRunner"/>
9+
</module>
10+
</module>
11+
</module>
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<?xml version="1.0"?>
2+
<!DOCTYPE module PUBLIC
3+
"-//Checkstyle//DTD Checkstyle Configuration 1.3//EN"
4+
"https://checkstyle.org/dtds/configuration_1_3.dtd">
5+
<module name="com.puppycrawl.tools.checkstyle.Checker">
6+
<module name="com.puppycrawl.tools.checkstyle.TreeWalker">
7+
<module name="io.spring.javaformat.checkstyle.check.SpringJUnit5Check">
8+
</module>
9+
</module>
10+
</module>
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<?xml version="1.0"?>
2+
<!DOCTYPE module PUBLIC
3+
"-//Checkstyle//DTD Checkstyle Configuration 1.3//EN"
4+
"https://checkstyle.org/dtds/configuration_1_3.dtd">
5+
<module name="com.puppycrawl.tools.checkstyle.Checker">
6+
<module name="com.puppycrawl.tools.checkstyle.TreeWalker">
7+
<module name="io.spring.javaformat.checkstyle.check.SpringJUnit5Check">
8+
</module>
9+
</module>
10+
</module>
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
/*
2+
* Copyright 2017-2019 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
/**
18+
* Test with bad full qualified annotation.
19+
*
20+
* @author Phillip Webb
21+
*/
22+
public class JUnit5BadAnnotation {
23+
24+
@org.junit.Test
25+
void doSomethingWorks() {
26+
// test here
27+
}
28+
29+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/*
2+
* Copyright 2017-2019 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
import org.junit.jupiter.api.Test;
18+
import org.junit.Before;
19+
20+
/**
21+
* Test with banned import.
22+
*
23+
* @author Phillip Webb
24+
*/
25+
public class JUnit5BadImport {
26+
27+
@Before
28+
public void bad() {
29+
}
30+
31+
@Test
32+
void doSomethingWorks() {
33+
// test here
34+
}
35+
36+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/*
2+
* Copyright 2017-2019 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
import com.example.OptOutRunner;
18+
import org.junit.RunWith;
19+
import org.junit.Test;
20+
21+
/**
22+
* Test with banned import but also opt-out trigger.
23+
*
24+
* @author Phillip Webb
25+
*/
26+
@RunWith(OptOutRunner.class)
27+
public class JUnit5BadImportWithOptOut {
28+
29+
@Test
30+
void doSomethingWorks() {
31+
// test here
32+
}
33+
34+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/*
2+
* Copyright 2017-2019 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
import org.junit.jupiter.api.Test;
18+
19+
/**
20+
* Test with bad modifiers.
21+
*
22+
* @author Phillip Webb
23+
*/
24+
public class JUnit5BadModifier {
25+
26+
@Test
27+
public void doSomethingWorks() {
28+
// test here
29+
}
30+
31+
@Test
32+
private void doSomethingElseWorks() {
33+
// test here
34+
}
35+
36+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
/*
2+
* Copyright 2017-2019 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
import org.junit.jupiter.api.Test;
18+
19+
/**
20+
* This is a valid example.
21+
*
22+
* @author Phillip Webb
23+
*/
24+
public class JUnit5Valid {
25+
26+
@Test
27+
void doSomethingWorks() {
28+
// test here
29+
}
30+
31+
}

0 commit comments

Comments
 (0)