1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 package com.puppycrawl.tools.checkstyle.checks.whitespace;
21
22 import javax.annotation.Nullable;
23
24 import com.puppycrawl.tools.checkstyle.StatelessCheck;
25 import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
26 import com.puppycrawl.tools.checkstyle.api.DetailAST;
27 import com.puppycrawl.tools.checkstyle.api.TokenTypes;
28 import com.puppycrawl.tools.checkstyle.utils.CommonUtil;
29 import com.puppycrawl.tools.checkstyle.utils.NullUtil;
30 import com.puppycrawl.tools.checkstyle.utils.TokenUtil;
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51 @StatelessCheck
52 public class WhitespaceBeforeEmptyBodyCheck extends AbstractCheck {
53
54
55
56
57
58 public static final String MSG_KEY = "ws.notPreceded";
59
60
61
62
63 public WhitespaceBeforeEmptyBodyCheck() {
64
65 }
66
67 @Override
68 public int[] getDefaultTokens() {
69 return getAcceptableTokens();
70 }
71
72 @Override
73 public int[] getAcceptableTokens() {
74 return new int[] {
75 TokenTypes.METHOD_DEF,
76 TokenTypes.CTOR_DEF,
77 TokenTypes.COMPACT_CTOR_DEF,
78 TokenTypes.CLASS_DEF,
79 TokenTypes.INTERFACE_DEF,
80 TokenTypes.RECORD_DEF,
81 TokenTypes.ENUM_DEF,
82 TokenTypes.ANNOTATION_DEF,
83 TokenTypes.LITERAL_NEW,
84 TokenTypes.LITERAL_DO,
85 TokenTypes.LITERAL_WHILE,
86 TokenTypes.LITERAL_FOR,
87 TokenTypes.LITERAL_IF,
88 TokenTypes.LITERAL_ELSE,
89 TokenTypes.STATIC_INIT,
90 TokenTypes.LITERAL_TRY,
91 TokenTypes.LITERAL_CATCH,
92 TokenTypes.LITERAL_FINALLY,
93 TokenTypes.LITERAL_SYNCHRONIZED,
94 TokenTypes.LITERAL_SWITCH,
95 TokenTypes.LAMBDA,
96 };
97 }
98
99 @Override
100 public int[] getRequiredTokens() {
101 return CommonUtil.EMPTY_INT_ARRAY;
102 }
103
104 @Override
105 public void visitToken(DetailAST ast) {
106 final DetailAST lcurly = findOpeningBrace(ast);
107 if (lcurly != null && isBodyEmpty(lcurly) && !precededByWhiteSpace(lcurly)) {
108 log(lcurly, MSG_KEY, lcurly.getText());
109 }
110 }
111
112
113
114
115
116
117
118 @Nullable
119 private static DetailAST findOpeningBrace(DetailAST ast) {
120 final int astType = ast.getType();
121 return switch (astType) {
122 case TokenTypes.CLASS_DEF,
123 TokenTypes.INTERFACE_DEF,
124 TokenTypes.ENUM_DEF,
125 TokenTypes.RECORD_DEF,
126 TokenTypes.ANNOTATION_DEF,
127 TokenTypes.LITERAL_NEW -> {
128 final DetailAST objBlock = ast.findFirstToken(TokenTypes.OBJBLOCK);
129 DetailAST lcurly = null;
130 if (objBlock != null) {
131 lcurly = objBlock.findFirstToken(TokenTypes.LCURLY);
132 }
133 yield lcurly;
134 }
135 case TokenTypes.LITERAL_SWITCH -> ast.findFirstToken(TokenTypes.LCURLY);
136 default -> ast.findFirstToken(TokenTypes.SLIST);
137 };
138 }
139
140
141
142
143
144
145
146 private static boolean precededByWhiteSpace(DetailAST lcurly) {
147 DetailAST previousToken = lcurly.getPreviousSibling();
148 if (previousToken != null) {
149 previousToken = descendToLeafNode(previousToken);
150 }
151 if (previousToken == null
152 || previousToken.getParent().getType() == TokenTypes.LAMBDA) {
153 previousToken = NullUtil.notNull(lcurly.getParent());
154 }
155 if (previousToken.getType() == TokenTypes.OBJBLOCK) {
156 previousToken = NullUtil.notNull(previousToken.getPreviousSibling());
157 previousToken = descendToLeafNode(previousToken);
158 }
159
160 return !TokenUtil.areOnSameLine(lcurly, previousToken)
161 || isSpaceBetweenTokens(previousToken, lcurly);
162 }
163
164
165
166
167
168
169
170 private static DetailAST descendToLeafNode(DetailAST token) {
171 DetailAST current = token;
172 while (current.hasChildren()) {
173 current = current.getLastChild();
174 }
175 return current;
176 }
177
178
179
180
181
182
183
184 private static boolean isBodyEmpty(DetailAST lcurly) {
185 final DetailAST nextSibling = lcurly.getNextSibling();
186 final DetailAST firstChild = lcurly.getFirstChild();
187 return nextSibling != null && nextSibling.getType() == TokenTypes.RCURLY
188 || firstChild != null && firstChild.getType() == TokenTypes.RCURLY;
189 }
190
191
192
193
194
195
196
197
198 private static boolean isSpaceBetweenTokens(DetailAST firstToken, DetailAST secondToken) {
199 String tokenText = firstToken.getText();
200 if (firstToken.getType() == TokenTypes.STATIC_INIT) {
201 tokenText = "static";
202 }
203 final int firstLastColumn = firstToken.getColumnNo()
204 + tokenText.length() - 1;
205 return secondToken.getColumnNo() - firstLastColumn > 1;
206 }
207
208 }