Compare commits

...

50 Commits

Author SHA1 Message Date
Benedek Halasi
92071c854c Merge fixes of inner type decls, enable sorting nested types in one-go 2025-03-26 06:47:22 +01:00
Rick Ossendrijver
06028a0729 Pair programming 2025-03-26 06:47:21 +01:00
Rick Ossendrijver
1112cef15c Cleanup 2025-03-26 06:47:21 +01:00
Benedek Halasi
7eb5479f98 Fix & enable TypeMemberOrderEnumTest 2025-03-26 06:47:21 +01:00
Benedek Halasi
8d3fd072d3 Move TypeMemberOrder BugChecker into experimental module 2025-03-26 06:47:21 +01:00
Rick Ossendrijver
0ccea3c3c2 Final suggestions, but three failing tests 2025-03-26 06:47:21 +01:00
Rick Ossendrijver
d9607bde04 Try to implement new thing 2025-03-26 06:47:21 +01:00
Rick Ossendrijver
6b766891d2 Suggestions part 1 2025-03-26 06:47:21 +01:00
Rick Ossendrijver
a2c92b54dd Suggestions 2025-03-26 06:47:20 +01:00
Benedek Halasi
8c4b8c27d2 Fix syntactically wrong replecement with annotations containing LBRACE 2025-03-26 06:47:20 +01:00
Benedek Halasi
bdb37a553d Reproduce syntactically wrong replacement with annotations containing LBRACE 2025-03-26 06:47:20 +01:00
Rick Ossendrijver
56f58a45b4 Improve more tests 2025-03-26 06:47:20 +01:00
Rick Ossendrijver
bf98ce2f28 Suggestions 2025-03-26 06:47:20 +01:00
Benedek Halasi
6bd1f577ce Tweaks 2025-03-26 06:47:20 +01:00
Benedek Halasi
cd0a54c486 Test abstract and default methods 2025-03-26 06:47:20 +01:00
Rick Ossendrijver
4f139830ef Pair programming session 2025-03-26 06:47:20 +01:00
Rick Ossendrijver
173b43b0ad Post rebase fix 2025-03-26 06:47:19 +01:00
Benedek Halasi
7ec43faf6a Support classes, interfaces and enums 2025-03-26 06:47:19 +01:00
Benedek Halasi
46490b83fc Rename TypeMemberWithComments to TypeMember 2025-03-26 06:47:19 +01:00
Benedek Halasi
01c04007d4 Stop sorting unsorted members annotated w/ SuppressWarnings - all or TypeMemberOrdering 2025-03-26 06:47:19 +01:00
Benedek Halasi
4b9de605e7 Test not-ordering unordered members annotated w/ SuppressWarnings 2025-03-26 06:47:19 +01:00
Benedek Halasi
4ba51ac275 Implement inner type ordering 2025-03-26 06:47:19 +01:00
Benedek Halasi
58d21a5ea5 Test inner type ordering 2025-03-26 06:47:19 +01:00
Benedek Halasi
92181f3320 Implement initializer block ordering 2025-03-26 06:47:18 +01:00
Benedek Halasi
9b7e5a2da7 Test initializer block ordering 2025-03-26 06:47:18 +01:00
Benedek Halasi
9476e78fec Suggestions 2025-03-26 06:47:18 +01:00
Benedek Halasi
0eb4c8d93b Verify that SupressWarnings("all" or "TypeMemberOrdering") are not ordered 2025-03-26 06:47:18 +01:00
Benedek Halasi
1652a24bd2 Add identification test for an empty class, sync identifaction test w/ new error message 2025-03-26 06:47:18 +01:00
Benedek Halasi
69fe9bea95 Rename *class* to *type* 2025-03-26 06:47:18 +01:00
Benedek Halasi
778e2439ae List all remarks as TODOs, or address them 2025-03-26 06:47:18 +01:00
Stephan Schroevers
38adfa0507 Suggestions 2025-03-26 06:47:17 +01:00
Benedek Halasi
5a7995a598 Reproduce error-prone token start - end position overlap 2025-03-26 06:47:17 +01:00
Benedek Halasi
9a9a86cb60 Suggestions on naming, minor organizational changes, rewordings 2025-03-26 06:47:17 +01:00
Benedek Halasi
d7fc940a99 Assume that only generated members' end position is unavailable. 2025-03-26 06:47:17 +01:00
Benedek Halasi
a4dd402172 "Fix" previous token detection 2025-03-26 06:47:17 +01:00
Benedek Halasi
32bd39a4c3 Simplifing logic, killing mutants 2025-03-26 06:47:17 +01:00
Gijs de Jong
4453d9f122 Remove stray unused import 2025-03-26 06:47:17 +01:00
Gijs de Jong
253a35fc93 suggestions 2025-03-26 06:47:16 +01:00
Benedek Halasi
b531cd3fc9 Checkstyle 2025-03-26 06:47:16 +01:00
Benedek Halasi
dec75a841e Suggestions 2025-03-26 06:47:16 +01:00
Rick Ossendrijver
91f11dea71 Revert "Drop replaceIncludingComments"
This reverts commit 133c85da4f86a3d3965384acf4cdc24b3f50677b.
2025-03-26 06:47:16 +01:00
Rick Ossendrijver
01a31c6585 Drop replaceIncludingComments 2025-03-26 06:47:16 +01:00
Rick Ossendrijver
bd08fd30e1 Suggestions 2025-03-26 06:47:16 +01:00
Benedek Halasi
c8b7c7cdbf Apply my suggestions
- phrasing: last , -> and
- Reasonably concise `COMPARATOR` and its docs
2025-03-26 06:47:16 +01:00
Benedek Halasi
305cedd02f Cannot verify whitespace related requirements as TestMode.TEXT_MATCH formats the sources 2025-03-26 06:47:16 +01:00
Benedek Halasi
1bb8b85421 Code clean-up, formatting, docs, TEMPORARY supress warnings 2025-03-26 06:47:15 +01:00
Benedek Halasi
78cf52beff Test if error message contains crucial information 2025-03-26 06:47:15 +01:00
Benedek Halasi
f61539d266 Prepare the code for others to see
- kinda cleanup `replaceIncludingComments` copy
 - mark unsure parts of the code
 - list remaining tasks
2025-03-26 06:47:15 +01:00
Benedek Halasi
1a6f6ec04c Consider comments 2025-03-26 06:47:15 +01:00
Benedek Halasi
f8d6ae147b Naive impl 2025-03-26 06:47:15 +01:00
8 changed files with 1387 additions and 0 deletions

View File

@@ -45,6 +45,11 @@
<artifactId>error_prone_test_helpers</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.google.auto.value</groupId>
<artifactId>auto-value-annotations</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
@@ -68,4 +73,16 @@
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-checkstyle-plugin</artifactId>
<configuration>
<skip>true</skip>
</configuration>
</plugin>
</plugins>
</build>
</project>

View File

@@ -0,0 +1,296 @@
package tech.picnic.errorprone.experimental.bugpatterns;
import static com.google.errorprone.BugPattern.LinkType.CUSTOM;
import static com.google.errorprone.BugPattern.SeverityLevel.WARNING;
import static com.google.errorprone.BugPattern.StandardTags.STYLE;
import static com.sun.tools.javac.code.Flags.ENUM;
import static java.util.Objects.requireNonNull;
import static tech.picnic.errorprone.utils.Documentation.BUG_PATTERNS_BASE_URL;
import com.google.auto.service.AutoService;
import com.google.auto.value.AutoValue;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Streams;
import com.google.errorprone.BugPattern;
import com.google.errorprone.VisitorState;
import com.google.errorprone.annotations.Var;
import com.google.errorprone.bugpatterns.BugChecker;
import com.google.errorprone.bugpatterns.BugChecker.CompilationUnitTreeMatcher;
import com.google.errorprone.fixes.Replacement;
import com.google.errorprone.fixes.SuggestedFix;
import com.google.errorprone.matchers.Description;
import com.google.errorprone.util.ASTHelpers;
import com.google.errorprone.util.ErrorProneToken;
import com.google.errorprone.util.ErrorProneTokens;
import com.sun.source.tree.BlockTree;
import com.sun.source.tree.ClassTree;
import com.sun.source.tree.CompilationUnitTree;
import com.sun.source.tree.MethodTree;
import com.sun.source.tree.Tree;
import com.sun.source.tree.Tree.Kind;
import com.sun.source.tree.VariableTree;
import com.sun.tools.javac.parser.Tokens.TokenKind;
import com.sun.tools.javac.tree.JCTree;
import com.sun.tools.javac.tree.JCTree.JCVariableDecl;
import com.sun.tools.javac.util.Position;
import java.util.Optional;
import java.util.Set;
import javax.lang.model.element.Modifier;
/**
* A {@link BugChecker} that flags classes with a non-canonical member order.
*
* <p>Class members should be ordered as follows:
*
* <ol>
* <li>Static fields
* <li>Instance fields
* <li>Static initializer blocks
* <li>Instance initializer blocks
* <li>Constructors
* <li>Methods
* <li>Nested classes, interfaces and enums
* </ol>
*
* @see <a
* href="https://checkstyle.sourceforge.io/apidocs/com/puppycrawl/tools/checkstyle/checks/coding/DeclarationOrderCheck.html">Checkstyle's
* {@code DeclarationOrderCheck}</a>
*/
// XXX: Consider introducing support for ordering members in records or annotation definitions.
// XXX: Exclude checkstyle from running against (this checker's) test files.
@AutoService(BugChecker.class)
@BugPattern(
summary = "Type members should be defined in a canonical order",
link = BUG_PATTERNS_BASE_URL + "TypeMemberOrder",
linkType = CUSTOM,
severity = WARNING,
tags = STYLE)
public final class TypeMemberOrder extends BugChecker implements CompilationUnitTreeMatcher {
private static final long serialVersionUID = 1L;
/** Instantiates a new {@link TypeMemberOrder} instance. */
public TypeMemberOrder() {}
@Override
public Description matchCompilationUnit(
CompilationUnitTree compilationUnitTree, VisitorState state) {
SuggestedFix.Builder suggestedFixes = SuggestedFix.builder();
for (Tree tree : compilationUnitTree.getTypeDecls()) {
if (!isSuppressed(tree, state) && tree instanceof ClassTree classTree) {
suggestedFixes.merge(
matchClass(classTree, state, (JCTree.JCCompilationUnit) compilationUnitTree));
}
}
SuggestedFix suggestedFix = suggestedFixes.build();
if (suggestedFix.isEmpty()) {
return Description.NO_MATCH;
}
return describeMatch(compilationUnitTree, suggestedFix);
}
private SuggestedFix matchClass(
ClassTree tree, VisitorState state, JCTree.JCCompilationUnit compilationUnit) {
Kind treeKind = tree.getKind();
if (treeKind != Kind.CLASS && treeKind != Kind.INTERFACE && treeKind != Kind.ENUM) {
return SuggestedFix.emptyFix();
}
int bodyStartPos = getBodyStartPos(tree, state);
if (bodyStartPos == Position.NOPOS) {
/*
* We can't determine the type body's start position in the source code. This generally means
* that (part of) its code was generated. Even if the source code for a subset of its members
* is available, dealing with this edge case is not worth the trouble.
*/
return SuggestedFix.emptyFix();
}
ImmutableList<TypeMember> members = getAllTypeMembers(tree, bodyStartPos, state);
boolean topLevelSorted = members.equals(ImmutableList.sortedCopyOf(members));
if (topLevelSorted) {
SuggestedFix.Builder nestedSuggestedFixes = SuggestedFix.builder();
for (TypeMember member : members) {
if (member.tree() instanceof ClassTree memberClassTree) {
SuggestedFix other = matchClass(memberClassTree, state, compilationUnit);
nestedSuggestedFixes.merge(other);
}
}
return nestedSuggestedFixes.build();
}
CharSequence source = requireNonNull(state.getSourceCode(), "Source code");
ImmutableMap.Builder<TypeMember, String> typeMemberSource = ImmutableMap.builder();
for (TypeMember member : members) {
if (member.tree() instanceof ClassTree memberClassTree) {
SuggestedFix suggestedFix = matchClass(memberClassTree, state, compilationUnit);
@Var
String memberSource =
source.subSequence(member.startPosition(), member.endPosition()).toString();
// Diff between memberSource and replacement positions.
@Var int diff = -member.startPosition();
for (Replacement replacement : suggestedFix.getReplacements(compilationUnit.endPositions)) {
memberSource =
memberSource.subSequence(0, replacement.startPosition() + diff)
+ replacement.replaceWith()
+ memberSource.subSequence(
replacement.endPosition() + diff, memberSource.length());
diff +=
replacement.replaceWith().length()
- (replacement.endPosition() - replacement.startPosition());
}
typeMemberSource.put(member, memberSource);
} else {
typeMemberSource.put(
member, source.subSequence(member.startPosition(), member.endPosition()).toString());
}
}
return sortTypeMembers(members, typeMemberSource.build());
}
/** Returns all members that can be moved or may lay between movable ones. */
private ImmutableList<TypeMember> getAllTypeMembers(
ClassTree tree, int bodyStartPos, VisitorState state) {
ImmutableList.Builder<TypeMember> builder = ImmutableList.builder();
@Var int currentStartPos = bodyStartPos;
for (Tree member : tree.getMembers()) {
if (state.getEndPosition(member) == Position.NOPOS) {
continue;
}
int treeStartPos = currentStartPos;
getMemberTypeOrdinal(member, state)
.ifPresent(
e ->
builder.add(
new AutoValue_TypeMemberOrder_TypeMember(
member, treeStartPos, state.getEndPosition(member), e)));
/* XXX: Write explanation about this enum. */
currentStartPos = Math.max(currentStartPos, state.getEndPosition(member));
}
return builder.build();
}
/**
* Returns the preferred ordinal of the given member, or empty if it's unmovable for any reason,
* including it lacking a preferred ordinal.
*/
private Optional<Integer> getMemberTypeOrdinal(Tree tree, VisitorState state) {
if (isSuppressed(tree, state) || isEnumeratorDefinition(tree)) {
return Optional.empty();
}
return switch (tree.getKind()) {
case VARIABLE -> Optional.of(isStatic((VariableTree) tree) ? 1 : 2);
case BLOCK -> Optional.of(isStatic((BlockTree) tree) ? 3 : 4);
case METHOD -> Optional.of(isConstructor((MethodTree) tree) ? 5 : 6);
case CLASS, INTERFACE, ENUM -> Optional.of(7);
default -> Optional.empty();
};
}
/**
* Returns the start position of the body of the given type, in the case of enums, it returns the
* position that follows the enumerated type's enumerations.
*/
private static int getBodyStartPos(ClassTree tree, VisitorState state) {
CharSequence sourceCode = state.getSourceCode();
/*
* To avoid including the type's preceding annotations, use `getPreferredPosition()` rather than
* `ASTHelpers`.
*/
int typeStart = ((JCTree.JCClassDecl) tree).getPreferredPosition();
int typeEnd = state.getEndPosition(tree);
if (sourceCode == null || typeStart == Position.NOPOS || typeEnd == Position.NOPOS) {
return Position.NOPOS;
}
/*
* Returns the source code position of the first token that comes after the first curly left
* bracket.
*/
return ErrorProneTokens.getTokens(
sourceCode.subSequence(typeStart, typeEnd).toString(), typeStart, state.context)
.stream()
.dropWhile(token -> token.kind() != TokenKind.LBRACE)
/*
* To accommodate enums, skip processing their enumerators. This is needed as Error Prone
* has access to the enumerations individually, but not to the whole expression that
* declares them, leaving the semicolon trailing the declarations unaccounted for. The
* current logic would move this trailing semicolon with the first member after the
* enumerations instead of leaving it to close the enumerations' declaration, introducing a
* syntax error.
*/
.dropWhile(token -> tree.getKind() == Kind.ENUM && token.kind() != TokenKind.SEMI)
.findFirst()
.map(ErrorProneToken::endPos)
.orElse(Position.NOPOS);
}
/**
* Returns true if {@link Tree} is an enum or an enumerator definition, false otherwise.
*
* @see com.sun.tools.javac.code.Flags#ENUM
*/
private static boolean isEnumeratorDefinition(Tree tree) {
return tree instanceof JCVariableDecl variableDecl && (variableDecl.mods.flags & ENUM) != 0;
}
/**
* Suggests a different way of ordering the given type members.
*
* @implNote For each member, this method tracks the source code between the end of the definition
* of the member that precedes it (or the start of the type body if there is no such member)
* and the end of the definition of the member itself. This subsequently enables moving
* members around, including any preceding comments and Javadoc. This approach isn't perfect,
* and may at times move too much code or documentation around; users will have to manually
* resolve this.
*/
private static SuggestedFix sortTypeMembers(
ImmutableList<TypeMember> members, ImmutableMap<TypeMember, String> sourceCode) {
return Streams.zip(
members.stream(),
members.stream().sorted(),
(original, replacement) -> {
String replacementSource = requireNonNull(sourceCode.get(replacement), "replacement");
return original.equals(replacement)
? SuggestedFix.emptyFix()
: SuggestedFix.replace(
original.startPosition(), original.endPosition(), replacementSource);
})
.reduce(SuggestedFix.builder(), SuggestedFix.Builder::merge, SuggestedFix.Builder::merge)
.build();
}
private static boolean isStatic(VariableTree variableTree) {
Set<Modifier> modifiers = variableTree.getModifiers().getFlags();
return modifiers.contains(Modifier.STATIC);
}
private static boolean isStatic(BlockTree blockTree) {
return blockTree.isStatic();
}
private static boolean isConstructor(MethodTree methodTree) {
return ASTHelpers.getSymbol(methodTree).isConstructor();
}
@AutoValue
abstract static class TypeMember implements Comparable<TypeMember> {
abstract Tree tree();
abstract int startPosition();
abstract int endPosition();
abstract int preferredOrdinal();
@Override
public int compareTo(TypeMemberOrder.TypeMember o) {
return Integer.compare(preferredOrdinal(), o.preferredOrdinal());
}
}
}

View File

@@ -0,0 +1,428 @@
package tech.picnic.errorprone.experimental.bugpatterns;
import static java.nio.charset.StandardCharsets.UTF_8;
import com.google.errorprone.BugCheckerRefactoringTestHelper;
import com.google.errorprone.BugCheckerRefactoringTestHelper.TestMode;
import com.google.errorprone.CompilationTestHelper;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import org.junit.jupiter.api.Test;
final class TypeMemberOrderClassTest {
@Test
void identification() {
CompilationTestHelper.newInstance(TypeMemberOrder.class, getClass())
.addSourceLines(
"A.java",
"// BUG: Diagnostic contains:",
"class A {",
" class InnerClass {}",
"",
" interface InnerInterface {}",
"",
" enum InnerEnum {}",
"",
" int foo() {",
" return foo;",
" }",
"",
" A() {}",
"",
" {",
" System.out.println(\"bar\");",
" }",
"",
" static {",
" System.out.println(\"foo\");",
" }",
"",
" private final int bar = 2;",
" private static final int foo = 1;",
"}")
.addSourceLines(
"B.java",
"class B {",
" private static final int foo = 1;",
" private final int bar = 2;",
"",
" static {",
" System.out.println(\"foo\");",
" }",
"",
" {",
" System.out.println(\"bar\");",
" }",
"",
" B() {}",
"",
" int foo() {",
" return foo;",
" }",
"",
" class InnerClass {}",
"",
" interface InnerInterface {}",
"",
" enum InnerEnum {}",
"}")
.addSourceLines(
"C.java",
"class C {",
" @SuppressWarnings({\"foo\", \"all\", \"bar\"})",
" void unorderedMethod() {}",
"",
" C() {}",
"}")
.addSourceLines(
"D.java",
"class D {",
" @SuppressWarnings(\"TypeMemberOrder\")",
" void unorderedMethod() {}",
"",
" D() {}",
"}")
.addSourceLines(
"E.java",
"@SuppressWarnings(\"TypeMemberOrder\")",
"class E {",
" void unorderedMethod() {}",
"",
" E() {}",
"}")
.addSourceLines("F.java", "class F {}")
.doTest();
}
@Test
void replacement() {
BugCheckerRefactoringTestHelper.newInstance(TypeMemberOrder.class, getClass())
.addInputLines(
"A.java",
"class A {",
" class Inner {",
" int innerFoo() {",
" return 1;",
" }",
"",
" private final int innerBar = 2;",
" }",
"",
" int foo() {",
" return foo;",
" }",
"",
" A() {}",
"",
" {",
" System.out.println(\"bar\");",
" }",
"",
" static {",
" System.out.println(\"foo\");",
" }",
"",
" private final int bar = 2;",
" private static final int foo = 1;",
"}")
.addOutputLines(
"A.java",
"class A {",
" private static final int foo = 1;",
"",
" private final int bar = 2;",
"",
" static {",
" System.out.println(\"foo\");",
" }",
"",
" {",
" System.out.println(\"bar\");",
" }",
"",
" A() {}",
"",
" int foo() {",
" return foo;",
" }",
"",
" class Inner {",
"",
" private final int innerBar = 2;",
"",
" int innerFoo() {",
" return 1;",
" }",
" }",
"}")
.doTest(TestMode.TEXT_MATCH);
}
@Test
void replacementNestedClasses() {
BugCheckerRefactoringTestHelper.newInstance(TypeMemberOrder.class, getClass())
.addInputLines(
"A.java",
// XXX: Use dedicated helper method to read test files here and below.
new BufferedReader(
new InputStreamReader(
this.getClass()
.getClassLoader()
.getResourceAsStream(
"TypeMemberOrderNestedCompilationUnitTestInput.java"),
UTF_8))
.lines()
.toArray(String[]::new))
.addOutputLines(
"A.java",
new BufferedReader(
new InputStreamReader(
this.getClass()
.getClassLoader()
.getResourceAsStream(
"TypeMemberOrderNestedCompilationUnitTestOutput.java"),
UTF_8))
.lines()
.toArray(String[]::new))
.doTest(TestMode.TEXT_MATCH);
}
@Test
void replacementAbstractMethods() {
BugCheckerRefactoringTestHelper.newInstance(TypeMemberOrder.class, getClass())
.addInputLines(
"A.java",
"abstract class A {",
" static class InnerClass {}",
"",
" void foo() {}",
"",
" abstract void bar();",
"",
" void baz() {}",
"",
" A() {}",
"}")
.addOutputLines(
"A.java",
"abstract class A {",
"",
" A() {}",
"",
" void foo() {}",
"",
" abstract void bar();",
"",
" void baz() {}",
"",
" static class InnerClass {}",
"}")
.doTest(TestMode.TEXT_MATCH);
}
@Test
void replacementUnmovableMembers() {
BugCheckerRefactoringTestHelper.newInstance(TypeMemberOrder.class, getClass())
.addInputLines(
"A.java",
"class A {",
" @SuppressWarnings(\"TypeMemberOrder\")",
" int foo() {",
" return foo;",
" }",
"",
" {",
" System.out.println(\"bar\");",
" }",
"",
" static {",
" System.out.println(\"foo\");",
" }",
"",
" private final int bar = 2;",
"",
" @SuppressWarnings(\"TypeMemberOrder\")",
" private static final int foo = 1;",
"}")
.addOutputLines(
"A.java",
"class A {",
" @SuppressWarnings(\"TypeMemberOrder\")",
" int foo() {",
" return foo;",
" }",
"",
" private final int bar = 2;",
"",
" static {",
" System.out.println(\"foo\");",
" }",
"",
" {",
" System.out.println(\"bar\");",
" }",
"",
" @SuppressWarnings(\"TypeMemberOrder\")",
" private static final int foo = 1;",
"}")
.doTest(TestMode.TEXT_MATCH);
}
@Test
void replacementHandlesGeneratedDefaultConstructor() {
BugCheckerRefactoringTestHelper.newInstance(TypeMemberOrder.class, getClass())
.addInputLines(
"A.java",
"class A {",
" int foo() {",
" return foo;",
" }",
"",
" {",
" System.out.println(\"bar\");",
" }",
"",
" static {",
" System.out.println(\"foo\");",
" }",
"",
" private final int bar = 2;",
" private static final int foo = 1;",
"}")
.addOutputLines(
"A.java",
"class A {",
" private static final int foo = 1;",
"",
" private final int bar = 2;",
"",
" static {",
" System.out.println(\"foo\");",
" }",
"",
" {",
" System.out.println(\"bar\");",
" }",
"",
" int foo() {",
" return foo;",
" }",
"}")
.doTest(TestMode.TEXT_MATCH);
}
@Test
void replacementDanglingComments() {
BugCheckerRefactoringTestHelper.newInstance(TypeMemberOrder.class, getClass())
.addInputLines(
"A.java",
"class A {",
" /* empty statement's dangling comment */",
" ;",
" /**",
" * Multiline JavaDoc",
" *",
" * <p>`foo` method's comment",
" */",
" int foo() {",
" return foo;",
" }",
"",
" // initializer block's comment",
" {",
" System.out.println(\"bar\");",
" }",
"",
" // static initializer block's comment",
" static {",
" System.out.println(\"foo\");",
" }",
"",
" /* `bar` field's dangling comment */",
"",
" private final int bar = 2;",
" // `foo` field's comment",
" private static final int foo = 1;",
" // trailing comment",
"}")
.addOutputLines(
"A.java",
"class A {",
" // `foo` field's comment",
" private static final int foo = 1;",
"",
" /* `bar` field's dangling comment */",
"",
" private final int bar = 2;",
"",
" // static initializer block's comment",
" static {",
" System.out.println(\"foo\");",
" }",
"",
" // initializer block's comment",
" {",
" System.out.println(\"bar\");",
" }",
" /* empty statement's dangling comment */",
" ;",
"",
" /**",
" * Multiline JavaDoc",
" *",
" * <p>`foo` method's comment",
" */",
" int foo() {",
" return foo;",
" }",
" // trailing comment",
"}")
.doTest(TestMode.TEXT_MATCH);
}
@Test
void replacementComplexAnnotation() {
BugCheckerRefactoringTestHelper.newInstance(TypeMemberOrder.class, getClass())
.addInputLines(
"A.java",
"class A {",
"",
" @interface AnnotationWithClassReferences {",
" Class<?>[] value() default {};",
" }",
"",
" @AnnotationWithClassReferences({Object.class})",
" class InnerClassOneValue {",
" String bar;",
" private static final int foo = 1;",
" }",
"",
" @AnnotationWithClassReferences(value = {Integer.class, String.class})",
" class InnerClassTwoValues {",
" String bar;",
" private static final int foo = 1;",
" }",
"}")
.addOutputLines(
"A.java",
"class A {",
"",
" @interface AnnotationWithClassReferences {",
" Class<?>[] value() default {};",
" }",
"",
" @AnnotationWithClassReferences({Object.class})",
" class InnerClassOneValue {",
" private static final int foo = 1;",
" String bar;",
" }",
"",
" @AnnotationWithClassReferences(value = {Integer.class, String.class})",
" class InnerClassTwoValues {",
" private static final int foo = 1;",
" String bar;",
" }",
"}")
.doTest(TestMode.TEXT_MATCH);
}
}

View File

@@ -0,0 +1,309 @@
package tech.picnic.errorprone.experimental.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 TypeMemberOrderEnumTest {
@Test
void identification() {
CompilationTestHelper.newInstance(TypeMemberOrder.class, getClass())
.addSourceLines(
"A.java",
"// BUG: Diagnostic contains:",
"enum A {",
" FOO;",
"",
" class InnerClass {}",
"",
" interface InnerInterface {}",
"",
" enum InnerEnum {}",
"",
" void qux() {}",
"",
" A() {}",
"",
" {",
" System.out.println(\"bar\");",
" }",
"",
" static {",
" System.out.println(\"foo\");",
" }",
"",
" final int baz = 2;",
" static final int BAR = 1;",
"}")
.addSourceLines(
"B.java",
"enum B {",
" FOO;",
" static final int BAR = 1;",
" final int baz = 2;",
"",
" static {",
" System.out.println(\"foo\");",
" }",
"",
" {",
" System.out.println(\"bar\");",
" }",
"",
" B() {}",
"",
" void qux() {}",
"",
" class InnerClass {}",
"",
" interface InnerInterface {}",
"",
" enum InnerEnum {}",
"}")
.addSourceLines(
"C.java",
"enum C {",
" FOO;",
"",
" @SuppressWarnings({\"foo\", \"all\", \"bar\"})",
" void bar() {}",
"",
" C() {}",
"}")
.addSourceLines(
"D.java",
"enum D {",
" FOO;",
"",
" @SuppressWarnings({\"TypeMemberOrder\"})",
" void bar() {}",
"",
" D() {}",
"}")
.addSourceLines("E.java", "enum E {}")
.doTest();
}
@Test
void replacement() {
BugCheckerRefactoringTestHelper.newInstance(TypeMemberOrder.class, getClass())
.addInputLines(
"A.java",
"enum A {",
" FOO,",
" BAR;",
"",
" class InnerClass {}",
"",
" interface InnerInterface {}",
"",
" enum InnerEnum {}",
"",
" void qux() {}",
"",
" A() {}",
"",
" {",
" System.out.println(\"bar\");",
" }",
"",
" static {",
" System.out.println(\"foo\");",
" }",
"",
" final int baz = 2;",
" static final int BAZ = 1;",
"}")
.addOutputLines(
"A.java",
"enum A {",
" FOO,",
" BAR;",
" static final int BAZ = 1;",
"",
" final int baz = 2;",
"",
" static {",
" System.out.println(\"foo\");",
" }",
"",
" {",
" System.out.println(\"bar\");",
" }",
"",
" A() {}",
"",
" void qux() {}",
"",
" class InnerClass {}",
"",
" interface InnerInterface {}",
"",
" enum InnerEnum {}",
"}")
.doTest(TestMode.TEXT_MATCH);
}
@Test
void replacementUnmovableMembers() {
BugCheckerRefactoringTestHelper.newInstance(TypeMemberOrder.class, getClass())
.addInputLines(
"A.java",
"enum A {",
" FOO;",
"",
" @SuppressWarnings(\"TypeMemberOrder\")",
" class InnerClass {}",
"",
" interface InnerInterface {}",
"",
" enum InnerEnum {}",
"",
" void qux() {}",
"",
" A() {}",
"",
" {",
" System.out.println(\"bar\");",
" }",
"",
" static {",
" System.out.println(\"foo\");",
" }",
"",
" @SuppressWarnings(\"TypeMemberOrder\")",
" final int baz = 2;",
"",
" static final int BAR = 1;",
"}")
.addOutputLines(
"A.java",
"enum A {",
" FOO;",
"",
" @SuppressWarnings(\"TypeMemberOrder\")",
" class InnerClass {}",
"",
" static final int BAR = 1;",
"",
" static {",
" System.out.println(\"foo\");",
" }",
"",
" {",
" System.out.println(\"bar\");",
" }",
"",
" A() {}",
"",
" void qux() {}",
"",
" interface InnerInterface {}",
"",
" @SuppressWarnings(\"TypeMemberOrder\")",
" final int baz = 2;",
"",
" enum InnerEnum {}",
"}")
.doTest(TestMode.TEXT_MATCH);
}
@Test
void replacementHandlesGeneratedDefaultConstructor() {
BugCheckerRefactoringTestHelper.newInstance(TypeMemberOrder.class, getClass())
.addInputLines(
"A.java",
"enum A {",
" FOO;",
"",
" class InnerClass {}",
"",
" interface InnerInterface {}",
"",
" enum InnerEnum {}",
"",
" void qux() {}",
"",
" {",
" System.out.println(\"bar\");",
" }",
"",
" static {",
" System.out.println(\"foo\");",
" }",
"",
" final int baz = 2;",
" static final int BAR = 1;",
"}")
.addOutputLines(
"A.java",
"enum A {",
" FOO;",
" static final int BAR = 1;",
"",
" final int baz = 2;",
"",
" static {",
" System.out.println(\"foo\");",
" }",
"",
" {",
" System.out.println(\"bar\");",
" }",
"",
" void qux() {}",
"",
" class InnerClass {}",
"",
" interface InnerInterface {}",
"",
" enum InnerEnum {}",
"}")
.doTest(TestMode.TEXT_MATCH);
}
@Test
void replacementDanglingComments() {
BugCheckerRefactoringTestHelper.newInstance(TypeMemberOrder.class, getClass())
.addInputLines(
"A.java",
"enum A {",
" /** FOO's JavaDoc */",
" FOO",
"/* Dangling comment trailing enumerations. */ ;",
"",
" /* `quz` method's dangling comment */",
" ;",
"",
" /* `quz` method's comment */",
" void qux() {}",
"",
" // `baz` method's comment",
" final int baz = 2;",
"",
" static final int BAR = 1;",
" // trailing comment",
"}")
.addOutputLines(
"A.java",
"enum A {",
" /** FOO's JavaDoc */",
" FOO",
"/* Dangling comment trailing enumerations. */ ;",
"",
" static final int BAR = 1;",
"",
" // `baz` method's comment",
" final int baz = 2;",
"",
" /* `quz` method's dangling comment */",
" ;",
"",
" /* `quz` method's comment */",
" void qux() {}",
" // trailing comment",
"}")
.doTest(TestMode.TEXT_MATCH);
}
}

View File

@@ -0,0 +1,195 @@
package tech.picnic.errorprone.experimental.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 TypeMemberOrderInterfaceTest {
@Test
void identification() {
CompilationTestHelper.newInstance(TypeMemberOrder.class, getClass())
.addSourceLines(
"A.java",
"// BUG: Diagnostic contains:",
"interface A {",
" static class InnerClass {}",
"",
" static interface InnerInterface {}",
"",
" static enum InnerEnum {}",
"",
" void bar();",
"",
" static final int foo = 1;",
"}")
.addSourceLines(
"B.java",
"interface B {",
" static final int foo = 1;",
"",
" void bar();",
"",
" static class InnerClass {}",
"",
" static interface InnerInterface {}",
"",
" static enum InnerEnum {}",
"}")
.addSourceLines(
"C.java",
"interface C {",
" @SuppressWarnings({\"foo\", \"all\", \"bar\"})",
" void bar();",
"",
" static final int foo = 1;",
"}")
.addSourceLines(
"D.java",
"interface D {",
" @SuppressWarnings(\"TypeMemberOrder\")",
" void bar();",
"",
" static final int foo = 1;",
"}")
.addSourceLines("E.java", "interface E {}")
.doTest();
}
@Test
void replacement() {
BugCheckerRefactoringTestHelper.newInstance(TypeMemberOrder.class, getClass())
.addInputLines(
"A.java",
"interface A {",
" static class InnerClass {}",
"",
" static interface InnerInterface {}",
"",
" static enum InnerEnum {}",
"",
" void bar();",
"",
" static final int foo = 1;",
"}")
.addOutputLines(
"A.java",
"interface A {",
"",
" static final int foo = 1;",
"",
" void bar();",
"",
" static class InnerClass {}",
"",
" static interface InnerInterface {}",
"",
" static enum InnerEnum {}",
"}")
.doTest(TestMode.TEXT_MATCH);
}
@Test
void replacementDefaultMethods() {
BugCheckerRefactoringTestHelper.newInstance(TypeMemberOrder.class, getClass())
.addInputLines(
"A.java",
"interface A {",
" class InnerClass {}",
"",
" void foo();",
"",
" default void bar() {}",
"",
" void baz();",
"",
" static final int QUX = 1;",
"}")
.addOutputLines(
"A.java",
"interface A {",
"",
" static final int QUX = 1;",
"",
" void foo();",
"",
" default void bar() {}",
"",
" void baz();",
"",
" class InnerClass {}",
"}")
.doTest(TestMode.TEXT_MATCH);
}
@Test
void replacementUnmovableMembers() {
BugCheckerRefactoringTestHelper.newInstance(TypeMemberOrder.class, getClass())
.addInputLines(
"A.java",
"interface A {",
" @SuppressWarnings(\"TypeMemberOrder\")",
" static class InnerClass {}",
"",
" static interface InnerInterface {}",
"",
" static enum InnerEnum {}",
"",
" void bar();",
"",
" @SuppressWarnings(\"TypeMemberOrder\")",
" static final int baz = 1;",
"",
" static final int foo = 1;",
"}")
.addOutputLines(
"A.java",
"interface A {",
" @SuppressWarnings(\"TypeMemberOrder\")",
" static class InnerClass {}",
"",
" static final int foo = 1;",
"",
" void bar();",
"",
" static interface InnerInterface {}",
"",
" @SuppressWarnings(\"TypeMemberOrder\")",
" static final int baz = 1;",
"",
" static enum InnerEnum {}",
"}")
.doTest(TestMode.TEXT_MATCH);
}
@Test
void replacementDanglingComments() {
BugCheckerRefactoringTestHelper.newInstance(TypeMemberOrder.class, getClass())
.addInputLines(
"A.java",
"interface A {",
" // empty statement's dangling comment",
" ;",
" // `bar` method's comment",
" void bar();",
"",
" // `foo` field's comment",
" int foo = 1;",
" // trailing comment",
"}")
.addOutputLines(
"A.java",
"interface A {",
"",
" // `foo` field's comment",
" int foo = 1;",
" // empty statement's dangling comment",
" ;",
"",
" // `bar` method's comment",
" void bar();",
" // trailing comment",
"}")
.doTest(TestMode.TEXT_MATCH);
}
}

View File

@@ -0,0 +1,65 @@
class A {
class AInner2 {
private static final int foo = 1;
int bar = 2;
class AInner2Inner1 {
private static final int foo = 1;
int bar = 2;
}
class AInner2Inner2 {
int bar = 2;
private static final int foo = 1;
}
}
private static final int foo = 1;
class OnlyTopLevelSortedClass {
private static final int foo = 1;
int bar = 2;
class UnsortedNestedClass {
int bar = 2;
private static final int foo = 1;
}
}
class AInner1 {
class AInner1Inner1 {
int bar = 2;
private static final int foo = 1;
}
enum DeeplyNestedEnum {
/** FOO's JavaDoc */
FOO,
/* Dangling comment trailing enumerations. */ ;
/** `quz` method's dangling comment */
;
/** `quz` method's comment */
void qux() {}
// `baz` method's comment
final int baz = 2;
static final int BAR = 1;
// trailing comment
}
private static final int foo = 1;
class AInner1Inner2 {
int bar = 2;
private static final int foo = 1;
}
int bar = 2;
}
static int baz = 3;
private final int bar = 2;
}

View File

@@ -0,0 +1,67 @@
class A {
private static final int foo = 1;
static int baz = 3;
private final int bar = 2;
class AInner2 {
private static final int foo = 1;
int bar = 2;
class AInner2Inner1 {
private static final int foo = 1;
int bar = 2;
}
class AInner2Inner2 {
private static final int foo = 1;
int bar = 2;
}
}
class OnlyTopLevelSortedClass {
private static final int foo = 1;
int bar = 2;
class UnsortedNestedClass {
private static final int foo = 1;
int bar = 2;
}
}
class AInner1 {
private static final int foo = 1;
int bar = 2;
class AInner1Inner1 {
private static final int foo = 1;
int bar = 2;
}
enum DeeplyNestedEnum {
/** FOO's JavaDoc */
FOO,
/* Dangling comment trailing enumerations. */ ;
static final int BAR = 1;
// `baz` method's comment
final int baz = 2;
/** `quz` method's dangling comment */
;
/** `quz` method's comment */
void qux() {}
// trailing comment
}
class AInner1Inner2 {
private static final int foo = 1;
int bar = 2;
}
}
}

View File

@@ -79,6 +79,16 @@ public final class MoreASTHelpers {
return ASTHelpers.isSameType(ASTHelpers.getType(treeA), ASTHelpers.getType(treeB), state);
}
/**
* Tells whether a given tree is a generated constructor.
*
* @param tree The tree of interest.
* @return Whether the specified tree is a generated constructor.
*/
public static boolean isGeneratedConstructor(Tree tree) {
return tree instanceof MethodTree methodTree && ASTHelpers.isGeneratedConstructor(methodTree);
}
/**
* Tells whether the given tree is of type {@link String}.
*