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.Locale;
23
24 import javax.annotation.Nullable;
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.CommonUtil;
31 import com.puppycrawl.tools.checkstyle.utils.TokenUtil;
32
33
34
35
36
37
38
39
40 @StatelessCheck
41 public class LeftCurlyCheck
42 extends AbstractCheck {
43
44
45
46
47
48 public static final String MSG_KEY_LINE_NEW = "line.new";
49
50
51
52
53
54 public static final String MSG_KEY_LINE_PREVIOUS = "line.previous";
55
56
57
58
59
60 public static final String MSG_KEY_LINE_BREAK_AFTER = "line.break.after";
61
62
63 private static final String OPEN_CURLY_BRACE = "{";
64
65
66 private boolean ignoreEnums = true;
67
68
69
70
71 private LeftCurlyOption option = LeftCurlyOption.EOL;
72
73
74
75
76
77
78
79
80 public void setOption(String optionStr) {
81 option = LeftCurlyOption.valueOf(optionStr.trim().toUpperCase(Locale.ENGLISH));
82 }
83
84
85
86
87
88
89
90 public void setIgnoreEnums(boolean ignoreEnums) {
91 this.ignoreEnums = ignoreEnums;
92 }
93
94 @Override
95 public int[] getDefaultTokens() {
96 return getAcceptableTokens();
97 }
98
99 @Override
100 public int[] getAcceptableTokens() {
101 return new int[] {
102 TokenTypes.ANNOTATION_DEF,
103 TokenTypes.CLASS_DEF,
104 TokenTypes.CTOR_DEF,
105 TokenTypes.ENUM_CONSTANT_DEF,
106 TokenTypes.ENUM_DEF,
107 TokenTypes.INTERFACE_DEF,
108 TokenTypes.LAMBDA,
109 TokenTypes.LITERAL_CASE,
110 TokenTypes.LITERAL_CATCH,
111 TokenTypes.LITERAL_DEFAULT,
112 TokenTypes.LITERAL_DO,
113 TokenTypes.LITERAL_ELSE,
114 TokenTypes.LITERAL_FINALLY,
115 TokenTypes.LITERAL_FOR,
116 TokenTypes.LITERAL_IF,
117 TokenTypes.LITERAL_SWITCH,
118 TokenTypes.LITERAL_SYNCHRONIZED,
119 TokenTypes.LITERAL_TRY,
120 TokenTypes.LITERAL_WHILE,
121 TokenTypes.METHOD_DEF,
122 TokenTypes.OBJBLOCK,
123 TokenTypes.STATIC_INIT,
124 TokenTypes.RECORD_DEF,
125 TokenTypes.COMPACT_CTOR_DEF,
126 TokenTypes.SWITCH_RULE,
127 };
128 }
129
130 @Override
131 public int[] getRequiredTokens() {
132 return CommonUtil.EMPTY_INT_ARRAY;
133 }
134
135
136
137
138
139
140
141
142
143
144 @Override
145 public void visitToken(DetailAST ast) {
146 final DetailAST startToken;
147 final DetailAST brace = switch (ast.getType()) {
148 case TokenTypes.CTOR_DEF, TokenTypes.METHOD_DEF, TokenTypes.COMPACT_CTOR_DEF -> {
149 startToken = skipModifierAnnotations(ast);
150 yield ast.findFirstToken(TokenTypes.SLIST);
151 }
152 case TokenTypes.INTERFACE_DEF, TokenTypes.CLASS_DEF, TokenTypes.ANNOTATION_DEF,
153 TokenTypes.ENUM_DEF, TokenTypes.ENUM_CONSTANT_DEF, TokenTypes.RECORD_DEF -> {
154 startToken = skipModifierAnnotations(ast);
155 yield ast.findFirstToken(TokenTypes.OBJBLOCK);
156 }
157 case TokenTypes.LITERAL_WHILE, TokenTypes.LITERAL_CATCH,
158 TokenTypes.LITERAL_SYNCHRONIZED, TokenTypes.LITERAL_FOR, TokenTypes.LITERAL_TRY,
159 TokenTypes.LITERAL_FINALLY, TokenTypes.LITERAL_DO,
160 TokenTypes.LITERAL_IF, TokenTypes.STATIC_INIT, TokenTypes.LAMBDA,
161 TokenTypes.SWITCH_RULE -> {
162 startToken = ast;
163 yield ast.findFirstToken(TokenTypes.SLIST);
164 }
165 case TokenTypes.LITERAL_ELSE -> {
166 startToken = ast;
167 yield getBraceAsFirstChild(ast);
168 }
169 case TokenTypes.LITERAL_CASE, TokenTypes.LITERAL_DEFAULT -> {
170 startToken = ast;
171 yield getBraceFromSwitchMember(ast);
172 }
173 default -> {
174
175
176
177
178
179 startToken = ast;
180 yield ast.findFirstToken(TokenTypes.LCURLY);
181 }
182 };
183
184 if (brace != null) {
185 verifyBrace(brace, startToken);
186 }
187 }
188
189
190
191
192
193
194
195
196 @Nullable
197 private static DetailAST getBraceFromSwitchMember(DetailAST ast) {
198 final DetailAST brace;
199 final DetailAST parent = ast.getParent();
200 if (parent.getType() == TokenTypes.SWITCH_RULE) {
201 brace = parent.findFirstToken(TokenTypes.SLIST);
202 }
203 else {
204 brace = getBraceAsFirstChild(ast.getNextSibling());
205 }
206 return brace;
207 }
208
209
210
211
212
213
214
215
216 @Nullable
217 private static DetailAST getBraceAsFirstChild(DetailAST ast) {
218 DetailAST brace = null;
219 if (ast != null) {
220 final DetailAST candidate = ast.getFirstChild();
221 if (candidate != null && candidate.getType() == TokenTypes.SLIST) {
222 brace = candidate;
223 }
224 }
225 return brace;
226 }
227
228
229
230
231
232
233
234 private static DetailAST skipModifierAnnotations(DetailAST ast) {
235 DetailAST resultNode = ast;
236 final DetailAST modifiers = ast.findFirstToken(TokenTypes.MODIFIERS);
237
238 if (modifiers != null) {
239 final DetailAST lastAnnotation = findLastAnnotation(modifiers);
240
241 if (lastAnnotation != null) {
242 if (lastAnnotation.getNextSibling() == null) {
243 resultNode = modifiers.getNextSibling();
244 }
245 else {
246 resultNode = lastAnnotation.getNextSibling();
247 }
248 }
249 }
250 return resultNode;
251 }
252
253
254
255
256
257
258
259
260 private static DetailAST findLastAnnotation(DetailAST modifiers) {
261 DetailAST annotation = modifiers.findFirstToken(TokenTypes.ANNOTATION);
262 while (annotation != null && annotation.getNextSibling() != null
263 && annotation.getNextSibling().getType() == TokenTypes.ANNOTATION) {
264 annotation = annotation.getNextSibling();
265 }
266 return annotation;
267 }
268
269
270
271
272
273
274
275
276 private void verifyBrace(final DetailAST brace,
277 final DetailAST startToken) {
278 final String braceLine = getLine(brace.getLineNo() - 1);
279
280
281 if (braceLine.length() <= brace.getColumnNo() + 1
282 || braceLine.charAt(brace.getColumnNo() + 1) != '}') {
283 if (option == LeftCurlyOption.NL) {
284 if (!CommonUtil.hasWhitespaceBefore(brace.getColumnNo(), braceLine)) {
285 log(brace, MSG_KEY_LINE_NEW, OPEN_CURLY_BRACE, brace.getColumnNo() + 1);
286 }
287 }
288 else if (option == LeftCurlyOption.EOL) {
289 validateEol(brace, braceLine);
290 }
291 else if (!TokenUtil.areOnSameLine(startToken, brace)) {
292 validateNewLinePosition(brace, startToken, braceLine);
293 }
294 }
295 }
296
297
298
299
300
301
302
303 private void validateEol(DetailAST brace, String braceLine) {
304 if (CommonUtil.hasWhitespaceBefore(brace.getColumnNo(), braceLine)) {
305 log(brace, MSG_KEY_LINE_PREVIOUS, OPEN_CURLY_BRACE, brace.getColumnNo() + 1);
306 }
307 if (!hasLineBreakAfter(brace)) {
308 log(brace, MSG_KEY_LINE_BREAK_AFTER, OPEN_CURLY_BRACE, brace.getColumnNo() + 1);
309 }
310 }
311
312
313
314
315
316
317
318
319 private void validateNewLinePosition(DetailAST brace, DetailAST startToken, String braceLine) {
320
321 if (startToken.getLineNo() + 1 == brace.getLineNo()) {
322 if (CommonUtil.hasWhitespaceBefore(brace.getColumnNo(), braceLine)) {
323 log(brace, MSG_KEY_LINE_PREVIOUS, OPEN_CURLY_BRACE, brace.getColumnNo() + 1);
324 }
325 else {
326 log(brace, MSG_KEY_LINE_NEW, OPEN_CURLY_BRACE, brace.getColumnNo() + 1);
327 }
328 }
329 else if (!CommonUtil.hasWhitespaceBefore(brace.getColumnNo(), braceLine)) {
330 log(brace, MSG_KEY_LINE_NEW, OPEN_CURLY_BRACE, brace.getColumnNo() + 1);
331 }
332 }
333
334
335
336
337
338
339
340
341
342 private boolean hasLineBreakAfter(DetailAST leftCurly) {
343 DetailAST nextToken = null;
344 if (leftCurly.getType() == TokenTypes.SLIST) {
345 nextToken = leftCurly.getFirstChild();
346 }
347 else {
348 if (!ignoreEnums
349 && leftCurly.getParent().getParent().getType() == TokenTypes.ENUM_DEF) {
350 nextToken = leftCurly.getNextSibling();
351 }
352 }
353 return nextToken == null
354 || nextToken.getType() == TokenTypes.RCURLY
355 || !TokenUtil.areOnSameLine(leftCurly, nextToken);
356 }
357 }