Skip to content

Commit 580fac0

Browse files
committed
Introduce Whitespace check
1 parent 5e74db4 commit 580fac0

File tree

2 files changed

+242
-0
lines changed

2 files changed

+242
-0
lines changed
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
package tech.picnic.errorprone.experimental.bugpatterns;
2+
3+
import static com.google.errorprone.BugPattern.LinkType.CUSTOM;
4+
import static com.google.errorprone.BugPattern.SeverityLevel.WARNING;
5+
import static com.google.errorprone.BugPattern.StandardTags.STYLE;
6+
import static java.util.Objects.requireNonNull;
7+
import static tech.picnic.errorprone.utils.Documentation.BUG_PATTERNS_BASE_URL;
8+
9+
import com.google.auto.service.AutoService;
10+
import com.google.errorprone.BugPattern;
11+
import com.google.errorprone.VisitorState;
12+
import com.google.errorprone.annotations.Var;
13+
import com.google.errorprone.bugpatterns.BugChecker;
14+
import com.google.errorprone.bugpatterns.BugChecker.BlockTreeMatcher;
15+
import com.google.errorprone.bugpatterns.BugChecker.ClassTreeMatcher;
16+
import com.google.errorprone.fixes.SuggestedFix;
17+
import com.google.errorprone.matchers.Description;
18+
import com.sun.source.tree.BlockTree;
19+
import com.sun.source.tree.ClassTree;
20+
import com.sun.source.tree.Tree;
21+
22+
/**
23+
* XXX: Docs: BugChecker that flags blocks that start with a new line. Link corresponding checkstyle
24+
* rule. XXX: Rename to something more generic, maybe "WhitespaceChecker"?
25+
*/
26+
@AutoService(BugChecker.class)
27+
@BugPattern(
28+
summary = "XXX",
29+
link = BUG_PATTERNS_BASE_URL + "BlockStartWhitespace",
30+
linkType = CUSTOM,
31+
severity = WARNING,
32+
tags = STYLE)
33+
public final class BlockStartWhitespace extends BugChecker
34+
implements BlockTreeMatcher, ClassTreeMatcher {
35+
private static final long serialVersionUID = 1L;
36+
37+
/** Instantiates a new {@link BlockStartWhitespace} instance. */
38+
public BlockStartWhitespace() {}
39+
40+
@Override
41+
public Description matchBlock(BlockTree blockTree, VisitorState state) {
42+
return match(blockTree, state);
43+
}
44+
45+
@Override
46+
public Description matchClass(ClassTree classTree, VisitorState state) {
47+
return match(classTree, state);
48+
}
49+
50+
@SuppressWarnings({"LoopOverCharArray"})
51+
private Description match(Tree tree, VisitorState state) {
52+
String source = requireNonNull(state.getSourceForNode(tree), "Source code");
53+
54+
@Var boolean isInBlock = false;
55+
@Var int newLineCount = 0;
56+
@Var int lastNewLinePos = -1;
57+
source_loop:
58+
for (int pos = 0; pos < source.length(); pos++) {
59+
if (!isInBlock) {
60+
if (source.charAt(pos) == '{') {
61+
isInBlock = true;
62+
}
63+
continue;
64+
}
65+
switch (source.charAt(pos)) {
66+
// TODO: Windows? 🤢
67+
case '\n':
68+
newLineCount++;
69+
lastNewLinePos = pos;
70+
continue;
71+
case ' ', '\t':
72+
continue;
73+
default:
74+
break source_loop;
75+
}
76+
}
77+
78+
if (newLineCount < 2) {
79+
return Description.NO_MATCH;
80+
}
81+
82+
if (lastNewLinePos == -1) {
83+
throw new RuntimeException("Big oof (lastNewLinePos == -1)");
84+
}
85+
86+
// TODO: Do we the source with the correctly formatted one, or do we replace the redundant
87+
// whitespace with nothing? Colliding replacements and such.
88+
// SuggestedFix suggestedFix = SuggestedFix.replace(source.indexOf('{') + 1, lastNewLinePos,
89+
// "");
90+
String replacement =
91+
source.substring(0, source.indexOf('{') + 1) + source.substring(lastNewLinePos);
92+
SuggestedFix suggestedFix = SuggestedFix.replace(tree, replacement);
93+
return describeMatch(tree, suggestedFix);
94+
}
95+
}
Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
package tech.picnic.errorprone.experimental.bugpatterns;
2+
3+
import com.google.errorprone.BugCheckerRefactoringTestHelper;
4+
import com.google.errorprone.CompilationTestHelper;
5+
import org.junit.jupiter.api.Test;
6+
7+
final class BlockStartWhitespaceTest {
8+
@Test
9+
void identification() {
10+
CompilationTestHelper.newInstance(BlockStartWhitespace.class, getClass())
11+
.addSourceLines(
12+
"A.java",
13+
"// BUG: Diagnostic contains:",
14+
"class A {",
15+
" ",
16+
" private static final int foo = 1;",
17+
" ",
18+
" ",
19+
" // BUG: Diagnostic contains:",
20+
" void method (String foo) {",
21+
" ",
22+
" System.out.println(foo);",
23+
" }",
24+
"}")
25+
.addSourceLines(
26+
"B.java",
27+
"class B {",
28+
" private static final int foo = 1;",
29+
// XXX: Check whitespace in-between members. Enforce different clustering based on the
30+
// members modifiers, e.g. empty line between static and non static fields.
31+
" ",
32+
" ",
33+
" void method (String foo) {",
34+
" System.out.println(foo);",
35+
" }",
36+
"}")
37+
.doTest();
38+
}
39+
40+
@Test
41+
void classBodyBlock() {
42+
BugCheckerRefactoringTestHelper.newInstance(BlockStartWhitespace.class, getClass())
43+
.addInputLines(
44+
"Nested.java",
45+
"class Nested {",
46+
" static final class A {",
47+
"",
48+
" private static final int foo = 1;",
49+
" }",
50+
"",
51+
" static final class B {",
52+
" ",
53+
" private static final int foo = 1;",
54+
" }",
55+
"",
56+
" static final class C {",
57+
"",
58+
"\t",
59+
" ",
60+
" private static final int foo = 1;",
61+
" }",
62+
"",
63+
" static final class D {",
64+
"",
65+
" // comment",
66+
"",
67+
" private static final int foo = 1;",
68+
" }",
69+
"}")
70+
.addOutputLines(
71+
"Nested.java",
72+
"class Nested {",
73+
" static final class A {",
74+
" private static final int foo = 1;",
75+
" }",
76+
"",
77+
" static final class B {",
78+
" private static final int foo = 1;",
79+
" }",
80+
"",
81+
" static final class C {",
82+
" private static final int foo = 1;",
83+
" }",
84+
"",
85+
" static final class D {",
86+
" // comment",
87+
"",
88+
" private static final int foo = 1;",
89+
" }",
90+
"}")
91+
.doTest(BugCheckerRefactoringTestHelper.TestMode.TEXT_MATCH);
92+
}
93+
94+
@Test
95+
void methodBodyBlock() {
96+
BugCheckerRefactoringTestHelper.newInstance(BlockStartWhitespace.class, getClass())
97+
.addInputLines(
98+
"A.java",
99+
"class A {",
100+
" static int methodA() {",
101+
"",
102+
" return 1;",
103+
" }",
104+
"",
105+
" static int methodB() {",
106+
" ",
107+
" return 1;",
108+
" }",
109+
"",
110+
" static int methodC() {",
111+
"",
112+
"\t",
113+
" ",
114+
" return 1;",
115+
" }",
116+
"",
117+
" static int methodD() {",
118+
"",
119+
" // comment",
120+
"",
121+
" return 1;",
122+
" }",
123+
"}")
124+
.addOutputLines(
125+
"A.java",
126+
"class A {",
127+
" static int methodA() {",
128+
" return 1;",
129+
" }",
130+
"",
131+
" static int methodB() {",
132+
" return 1;",
133+
" }",
134+
"",
135+
" static int methodC() {",
136+
" return 1;",
137+
" }",
138+
"",
139+
" static int methodD() {",
140+
" // comment",
141+
"",
142+
" return 1;",
143+
" }",
144+
"}")
145+
.doTest(BugCheckerRefactoringTestHelper.TestMode.TEXT_MATCH);
146+
}
147+
}

0 commit comments

Comments
 (0)