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.blocks;
21
22 import java.util.Arrays;
23 import java.util.Locale;
24 import java.util.Optional;
25
26 import com.puppycrawl.tools.checkstyle.StatelessCheck;
27 import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
28 import com.puppycrawl.tools.checkstyle.api.DetailAST;
29 import com.puppycrawl.tools.checkstyle.api.TokenTypes;
30 import com.puppycrawl.tools.checkstyle.utils.CodePointUtil;
31 import com.puppycrawl.tools.checkstyle.utils.CommonUtil;
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104 @StatelessCheck
105 public class EmptyBlockCheck
106 extends AbstractCheck {
107
108
109
110
111
112 public static final String MSG_KEY_BLOCK_NO_STATEMENT = "block.noStatement";
113
114
115
116
117
118 public static final String MSG_KEY_BLOCK_EMPTY = "block.empty";
119
120
121 private BlockOption option = BlockOption.STATEMENT;
122
123
124
125
126
127
128
129
130 public void setOption(String optionStr) {
131 option = BlockOption.valueOf(optionStr.trim().toUpperCase(Locale.ENGLISH));
132 }
133
134 @Override
135 public int[] getDefaultTokens() {
136 return new int[] {
137 TokenTypes.LITERAL_WHILE,
138 TokenTypes.LITERAL_TRY,
139 TokenTypes.LITERAL_FINALLY,
140 TokenTypes.LITERAL_DO,
141 TokenTypes.LITERAL_IF,
142 TokenTypes.LITERAL_ELSE,
143 TokenTypes.LITERAL_FOR,
144 TokenTypes.INSTANCE_INIT,
145 TokenTypes.STATIC_INIT,
146 TokenTypes.LITERAL_SWITCH,
147 TokenTypes.LITERAL_SYNCHRONIZED,
148 };
149 }
150
151 @Override
152 public int[] getAcceptableTokens() {
153 return new int[] {
154 TokenTypes.LITERAL_WHILE,
155 TokenTypes.LITERAL_TRY,
156 TokenTypes.LITERAL_CATCH,
157 TokenTypes.LITERAL_FINALLY,
158 TokenTypes.LITERAL_DO,
159 TokenTypes.LITERAL_IF,
160 TokenTypes.LITERAL_ELSE,
161 TokenTypes.LITERAL_FOR,
162 TokenTypes.INSTANCE_INIT,
163 TokenTypes.STATIC_INIT,
164 TokenTypes.LITERAL_SWITCH,
165 TokenTypes.LITERAL_SYNCHRONIZED,
166 TokenTypes.LITERAL_CASE,
167 TokenTypes.LITERAL_DEFAULT,
168 TokenTypes.ARRAY_INIT,
169 };
170 }
171
172 @Override
173 public int[] getRequiredTokens() {
174 return CommonUtil.EMPTY_INT_ARRAY;
175 }
176
177 @Override
178 public void visitToken(DetailAST ast) {
179 final Optional<DetailAST> leftCurly = getLeftCurly(ast);
180 if (leftCurly.isPresent()) {
181 final DetailAST leftCurlyAST = leftCurly.orElseThrow();
182 if (option == BlockOption.STATEMENT) {
183 final boolean emptyBlock;
184 if (leftCurlyAST.getType() == TokenTypes.LCURLY) {
185 final DetailAST nextSibling = leftCurlyAST.getNextSibling();
186 emptyBlock = nextSibling.getType() != TokenTypes.CASE_GROUP
187 && nextSibling.getType() != TokenTypes.SWITCH_RULE;
188 }
189 else {
190 emptyBlock = leftCurlyAST.getChildCount() <= 1;
191 }
192 if (emptyBlock) {
193 log(leftCurlyAST,
194 MSG_KEY_BLOCK_NO_STATEMENT);
195 }
196 }
197 else if (!hasText(leftCurlyAST)) {
198 log(leftCurlyAST,
199 MSG_KEY_BLOCK_EMPTY,
200 ast.getText());
201 }
202 }
203 }
204
205
206
207
208
209
210
211 private boolean hasText(final DetailAST slistAST) {
212 final DetailAST rightCurly = slistAST.findFirstToken(TokenTypes.RCURLY);
213 final DetailAST rcurlyAST;
214
215 if (rightCurly == null) {
216 rcurlyAST = slistAST.getParent().findFirstToken(TokenTypes.RCURLY);
217 }
218 else {
219 rcurlyAST = rightCurly;
220 }
221 final int slistLineNo = slistAST.getLineNo();
222 final int slistColNo = slistAST.getColumnNo();
223 final int rcurlyLineNo = rcurlyAST.getLineNo();
224 final int rcurlyColNo = rcurlyAST.getColumnNo();
225 boolean returnValue = false;
226 if (slistLineNo == rcurlyLineNo) {
227
228 final int[] txt = Arrays.copyOfRange(getLineCodePoints(slistLineNo - 1),
229 slistColNo + 1, rcurlyColNo);
230
231 if (!CodePointUtil.isBlank(txt)) {
232 returnValue = true;
233 }
234 }
235 else {
236 final int[] codePointsFirstLine = getLineCodePoints(slistLineNo - 1);
237 final int[] firstLine = Arrays.copyOfRange(codePointsFirstLine,
238 slistColNo + 1, codePointsFirstLine.length);
239 final int[] codePointsLastLine = getLineCodePoints(rcurlyLineNo - 1);
240 final int[] lastLine = Arrays.copyOfRange(codePointsLastLine, 0, rcurlyColNo);
241
242 returnValue = !(CodePointUtil.isBlank(firstLine) && CodePointUtil.isBlank(lastLine))
243 || !checkIsAllLinesAreWhitespace(slistLineNo, rcurlyLineNo);
244 }
245 return returnValue;
246 }
247
248
249
250
251
252
253
254
255
256
257
258 private boolean checkIsAllLinesAreWhitespace(int lineFrom, int lineTo) {
259 boolean result = true;
260 for (int i = lineFrom; i < lineTo - 1; i++) {
261 if (!CodePointUtil.isBlank(getLineCodePoints(i))) {
262 result = false;
263 break;
264 }
265 }
266 return result;
267 }
268
269
270
271
272
273
274
275 private static Optional<DetailAST> getLeftCurly(DetailAST ast) {
276 final DetailAST parent = ast.getParent();
277 final int parentType = parent.getType();
278 final Optional<DetailAST> leftCurly;
279
280 if (parentType == TokenTypes.SWITCH_RULE) {
281
282 leftCurly = Optional.ofNullable(parent.findFirstToken(TokenTypes.SLIST));
283 }
284 else if (parentType == TokenTypes.CASE_GROUP) {
285
286 leftCurly = Optional.ofNullable(ast.getNextSibling())
287 .map(DetailAST::getFirstChild)
288 .filter(node -> node.getType() == TokenTypes.SLIST);
289 }
290 else if (ast.findFirstToken(TokenTypes.SLIST) != null) {
291
292 leftCurly = Optional.of(ast.findFirstToken(TokenTypes.SLIST));
293 }
294 else {
295
296 leftCurly = Optional.ofNullable(ast.findFirstToken(TokenTypes.LCURLY));
297 }
298 return leftCurly;
299 }
300
301 }