-
Notifications
You must be signed in to change notification settings - Fork 46
Introduce JUnitClassModifiers
check
#214
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 6 commits
48a6180
4c53bf0
aa167a2
c9cf244
8bce2e7
10f0900
f5763ef
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,82 @@ | ||
package tech.picnic.errorprone.bugpatterns; | ||
|
||
import static com.google.errorprone.BugPattern.LinkType.CUSTOM; | ||
import static com.google.errorprone.BugPattern.SeverityLevel.SUGGESTION; | ||
import static com.google.errorprone.BugPattern.StandardTags.STYLE; | ||
import static com.google.errorprone.matchers.ChildMultiMatcher.MatchType.AT_LEAST_ONE; | ||
import static com.google.errorprone.matchers.Matchers.allOf; | ||
import static com.google.errorprone.matchers.Matchers.annotations; | ||
import static com.google.errorprone.matchers.Matchers.anyOf; | ||
import static com.google.errorprone.matchers.Matchers.hasMethod; | ||
import static com.google.errorprone.matchers.Matchers.hasModifier; | ||
import static com.google.errorprone.matchers.Matchers.isType; | ||
import static com.google.errorprone.matchers.Matchers.not; | ||
import static tech.picnic.errorprone.bugpatterns.util.Documentation.BUG_PATTERNS_BASE_URL; | ||
import static tech.picnic.errorprone.bugpatterns.util.MoreJUnitMatchers.TEST_METHOD; | ||
import static tech.picnic.errorprone.bugpatterns.util.MoreMatchers.hasMetaAnnotation; | ||
|
||
import com.google.auto.service.AutoService; | ||
import com.google.common.collect.ImmutableSet; | ||
import com.google.errorprone.BugPattern; | ||
import com.google.errorprone.VisitorState; | ||
import com.google.errorprone.bugpatterns.BugChecker; | ||
import com.google.errorprone.bugpatterns.BugChecker.ClassTreeMatcher; | ||
import com.google.errorprone.fixes.SuggestedFix; | ||
import com.google.errorprone.fixes.SuggestedFixes; | ||
import com.google.errorprone.matchers.Description; | ||
import com.google.errorprone.matchers.Matcher; | ||
import com.sun.source.tree.ClassTree; | ||
import javax.lang.model.element.Modifier; | ||
|
||
/** | ||
* A {@link BugChecker} that flags non-final and non package-private JUnit test class declarations, | ||
* unless abstract. | ||
*/ | ||
@AutoService(BugChecker.class) | ||
@BugPattern( | ||
summary = "Non-abstract JUnit test classes should be declared package-private and final", | ||
linkType = CUSTOM, | ||
link = BUG_PATTERNS_BASE_URL + "JUnitClassModifiers", | ||
severity = SUGGESTION, | ||
tags = STYLE) | ||
public final class JUnitClassModifiers extends BugChecker implements ClassTreeMatcher { | ||
private static final long serialVersionUID = 1L; | ||
private static final Matcher<ClassTree> HAS_SPRING_CONFIGURATION_ANNOTATION = | ||
annotations( | ||
AT_LEAST_ONE, | ||
anyOf( | ||
isType("org.springframework.context.annotation.Configuration"), | ||
hasMetaAnnotation("org.springframework.context.annotation.Configuration"))); | ||
private static final Matcher<ClassTree> TEST_CLASS_WITH_INCORRECT_MODIFIERS = | ||
allOf( | ||
hasMethod(TEST_METHOD), | ||
not(hasModifier(Modifier.ABSTRACT)), | ||
anyOf( | ||
hasModifier(Modifier.PRIVATE), | ||
hasModifier(Modifier.PROTECTED), | ||
hasModifier(Modifier.PUBLIC), | ||
allOf(not(hasModifier(Modifier.FINAL)), not(HAS_SPRING_CONFIGURATION_ANNOTATION)))); | ||
|
||
/** Instantiates a new {@link JUnitClassModifiers} instance. */ | ||
public JUnitClassModifiers() {} | ||
|
||
@Override | ||
public Description matchClass(ClassTree tree, VisitorState state) { | ||
if (!TEST_CLASS_WITH_INCORRECT_MODIFIERS.matches(tree, state)) { | ||
return Description.NO_MATCH; | ||
} | ||
|
||
SuggestedFix.Builder fixBuilder = SuggestedFix.builder(); | ||
SuggestedFixes.removeModifiers( | ||
tree.getModifiers(), | ||
state, | ||
ImmutableSet.of(Modifier.PRIVATE, Modifier.PROTECTED, Modifier.PUBLIC)) | ||
.ifPresent(fixBuilder::merge); | ||
|
||
if (!HAS_SPRING_CONFIGURATION_ANNOTATION.matches(tree, state)) { | ||
SuggestedFixes.addModifiers(tree, state, Modifier.FINAL).ifPresent(fixBuilder::merge); | ||
} | ||
|
||
return describeMatch(tree, fixBuilder.build()); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,129 @@ | ||
package tech.picnic.errorprone.bugpatterns; | ||
|
||
import com.google.errorprone.BugCheckerRefactoringTestHelper; | ||
import com.google.errorprone.BugCheckerRefactoringTestHelper.TestMode; | ||
import com.google.errorprone.CompilationTestHelper; | ||
import org.junit.jupiter.api.Test; | ||
|
||
final class JUnitClassModifiersTest { | ||
private final CompilationTestHelper compilationTestHelper = | ||
CompilationTestHelper.newInstance(JUnitClassModifiers.class, getClass()); | ||
private final BugCheckerRefactoringTestHelper refactoringTestHelper = | ||
BugCheckerRefactoringTestHelper.newInstance(JUnitClassModifiers.class, getClass()); | ||
|
||
@Test | ||
void identification() { | ||
compilationTestHelper | ||
.addSourceLines( | ||
"Container.java", | ||
"import org.junit.jupiter.api.Test;", | ||
"import org.junit.jupiter.params.ParameterizedTest;", | ||
"import org.springframework.boot.test.context.TestConfiguration;", | ||
"import org.springframework.context.annotation.Configuration;", | ||
"", | ||
"class Container {", | ||
" final class FinalAndPackagePrivate {", | ||
" @Test", | ||
" void foo() {}", | ||
" }", | ||
"", | ||
" public abstract class Abstract {", | ||
" @Test", | ||
" void foo() {}", | ||
" }", | ||
"", | ||
" @Configuration", | ||
" class WithConfigurationAnnotation {", | ||
" @Test", | ||
" void foo() {}", | ||
" }", | ||
"", | ||
" @TestConfiguration", | ||
" class WithConfigurationMetaAnnotation {", | ||
" @Test", | ||
" void foo() {}", | ||
" }", | ||
"", | ||
" // BUG: Diagnostic contains:", | ||
" private final class Private {", | ||
" @Test", | ||
" void foo() {}", | ||
" }", | ||
"", | ||
" // BUG: Diagnostic contains:", | ||
" protected final class Protected {", | ||
" @Test", | ||
" void foo() {}", | ||
" }", | ||
"", | ||
" // BUG: Diagnostic contains:", | ||
" public final class Public {", | ||
" @Test", | ||
" void foo() {}", | ||
" }", | ||
"", | ||
" // BUG: Diagnostic contains:", | ||
" class NonFinal {", | ||
" @Test", | ||
" void foo() {}", | ||
" }", | ||
"", | ||
" // BUG: Diagnostic contains:", | ||
" class NonFinalWithCustomTestMethod {", | ||
" @ParameterizedTest", | ||
" void foo() {}", | ||
" }", | ||
Comment on lines
+70
to
+80
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I feel like these two cases are misleading: IIUC, the latter doesn't fail due to the "custom test method" but solely because it's non-final. Did I get this right? Shouldn't we add a positive test case for test methods annotated with There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Yes :)
Can do :) |
||
"", | ||
" @Configuration", | ||
" // BUG: Diagnostic contains:", | ||
" public class PublicWithConfigurationAnnotation {", | ||
" @Test", | ||
" void foo() {}", | ||
" }", | ||
"", | ||
" @TestConfiguration", | ||
" // BUG: Diagnostic contains:", | ||
" protected class ProtectedWithConfigurationMetaAnnotation {", | ||
" @Test", | ||
" void foo() {}", | ||
" }", | ||
"}") | ||
.doTest(); | ||
} | ||
|
||
@Test | ||
void replacement() { | ||
refactoringTestHelper | ||
.addInputLines( | ||
"A.java", | ||
"import org.junit.jupiter.api.Test;", | ||
"import org.springframework.context.annotation.Configuration;", | ||
"", | ||
"public class A {", | ||
" @Test", | ||
" void foo() {}", | ||
"", | ||
" @Configuration", | ||
" private static class B {", | ||
" @Test", | ||
" void bar() {}", | ||
" }", | ||
"}") | ||
.addOutputLines( | ||
"A.java", | ||
"import org.junit.jupiter.api.Test;", | ||
"import org.springframework.context.annotation.Configuration;", | ||
"", | ||
"final class A {", | ||
" @Test", | ||
" void foo() {}", | ||
"", | ||
" @Configuration", | ||
" static class B {", | ||
" @Test", | ||
" void bar() {}", | ||
" }", | ||
"}") | ||
.doTest(TestMode.TEXT_MATCH); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's move this case down so that the modifier ordering here matches the resolution order below.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
^ I'll shuffle the test code accordingly.