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 };
127 }
128
129 @Override
130 public int[] getRequiredTokens() {
131 return CommonUtil.EMPTY_INT_ARRAY;
132 }
133
134
135
136
137
138
139
140
141
142
143 @Override
144 public void visitToken(DetailAST ast) {
145 final DetailAST startToken;
146 final DetailAST brace = switch (ast.getType()) {
147 case TokenTypes.CTOR_DEF, TokenTypes.METHOD_DEF, TokenTypes.COMPACT_CTOR_DEF -> {
148 startToken = skipModifierAnnotations(ast);
149 yield ast.findFirstToken(TokenTypes.SLIST);
150 }
151 case TokenTypes.INTERFACE_DEF, TokenTypes.CLASS_DEF, TokenTypes.ANNOTATION_DEF,
152 TokenTypes.ENUM_DEF, TokenTypes.ENUM_CONSTANT_DEF, TokenTypes.RECORD_DEF -> {
153 startToken = skipModifierAnnotations(ast);
154 yield ast.findFirstToken(TokenTypes.OBJBLOCK);
155 }
156 case TokenTypes.LITERAL_WHILE, TokenTypes.LITERAL_CATCH,
157 TokenTypes.LITERAL_SYNCHRONIZED, TokenTypes.LITERAL_FOR, TokenTypes.LITERAL_TRY,
158 TokenTypes.LITERAL_FINALLY, TokenTypes.LITERAL_DO,
159 TokenTypes.LITERAL_IF, TokenTypes.STATIC_INIT, TokenTypes.LAMBDA -> {
160 startToken = ast;
161 yield ast.findFirstToken(TokenTypes.SLIST);
162 }
163 case TokenTypes.LITERAL_ELSE -> {
164 startToken = ast;
165 yield getBraceAsFirstChild(ast);
166 }
167 case TokenTypes.LITERAL_CASE, TokenTypes.LITERAL_DEFAULT -> {
168 startToken = ast;
169 yield getBraceFromSwitchMember(ast);
170 }
171 default -> {
172
173
174
175
176
177 startToken = ast;
178 yield ast.findFirstToken(TokenTypes.LCURLY);
179 }
180 };
181
182 if (brace != null) {
183 verifyBrace(brace, startToken);
184 }
185 }
186
187
188
189
190
191
192
193
194 @Nullable
195 private static DetailAST getBraceFromSwitchMember(DetailAST ast) {
196 final DetailAST brace;
197 final DetailAST parent = ast.getParent();
198 if (parent.getType() == TokenTypes.SWITCH_RULE) {
199 brace = parent.findFirstToken(TokenTypes.SLIST);
200 }
201 else {
202 brace = getBraceAsFirstChild(ast.getNextSibling());
203 }
204 return brace;
205 }
206
207
208
209
210
211
212
213
214 @Nullable
215 private static DetailAST getBraceAsFirstChild(DetailAST ast) {
216 DetailAST brace = null;
217 if (ast != null) {
218 final DetailAST candidate = ast.getFirstChild();
219 if (candidate != null && candidate.getType() == TokenTypes.SLIST) {
220 brace = candidate;
221 }
222 }
223 return brace;
224 }
225
226
227
228
229
230
231
232 private static DetailAST skipModifierAnnotations(DetailAST ast) {
233 DetailAST resultNode = ast;
234 final DetailAST modifiers = ast.findFirstToken(TokenTypes.MODIFIERS);
235
236 if (modifiers != null) {
237 final DetailAST lastAnnotation = findLastAnnotation(modifiers);
238
239 if (lastAnnotation != null) {
240 if (lastAnnotation.getNextSibling() == null) {
241 resultNode = modifiers.getNextSibling();
242 }
243 else {
244 resultNode = lastAnnotation.getNextSibling();
245 }
246 }
247 }
248 return resultNode;
249 }
250
251
252
253
254
255
256
257
258 private static DetailAST findLastAnnotation(DetailAST modifiers) {
259 DetailAST annotation = modifiers.findFirstToken(TokenTypes.ANNOTATION);
260 while (annotation != null && annotation.getNextSibling() != null
261 && annotation.getNextSibling().getType() == TokenTypes.ANNOTATION) {
262 annotation = annotation.getNextSibling();
263 }
264 return annotation;
265 }
266
267
268
269
270
271
272
273
274 private void verifyBrace(final DetailAST brace,
275 final DetailAST startToken) {
276 final String braceLine = getLine(brace.getLineNo() - 1);
277
278
279 if (braceLine.length() <= brace.getColumnNo() + 1
280 || braceLine.charAt(brace.getColumnNo() + 1) != '}') {
281 if (option == LeftCurlyOption.NL) {
282 if (!CommonUtil.hasWhitespaceBefore(brace.getColumnNo(), braceLine)) {
283 log(brace, MSG_KEY_LINE_NEW, OPEN_CURLY_BRACE, brace.getColumnNo() + 1);
284 }
285 }
286 else if (option == LeftCurlyOption.EOL) {
287 validateEol(brace, braceLine);
288 }
289 else if (!TokenUtil.areOnSameLine(startToken, brace)) {
290 validateNewLinePosition(brace, startToken, braceLine);
291 }
292 }
293 }
294
295
296
297
298
299
300
301 private void validateEol(DetailAST brace, String braceLine) {
302 if (CommonUtil.hasWhitespaceBefore(brace.getColumnNo(), braceLine)) {
303 log(brace, MSG_KEY_LINE_PREVIOUS, OPEN_CURLY_BRACE, brace.getColumnNo() + 1);
304 }
305 if (!hasLineBreakAfter(brace)) {
306 log(brace, MSG_KEY_LINE_BREAK_AFTER, OPEN_CURLY_BRACE, brace.getColumnNo() + 1);
307 }
308 }
309
310
311
312
313
314
315
316
317 private void validateNewLinePosition(DetailAST brace, DetailAST startToken, String braceLine) {
318
319 if (startToken.getLineNo() + 1 == brace.getLineNo()) {
320 if (CommonUtil.hasWhitespaceBefore(brace.getColumnNo(), braceLine)) {
321 log(brace, MSG_KEY_LINE_PREVIOUS, OPEN_CURLY_BRACE, brace.getColumnNo() + 1);
322 }
323 else {
324 log(brace, MSG_KEY_LINE_NEW, OPEN_CURLY_BRACE, brace.getColumnNo() + 1);
325 }
326 }
327 else if (!CommonUtil.hasWhitespaceBefore(brace.getColumnNo(), braceLine)) {
328 log(brace, MSG_KEY_LINE_NEW, OPEN_CURLY_BRACE, brace.getColumnNo() + 1);
329 }
330 }
331
332
333
334
335
336
337
338
339
340 private boolean hasLineBreakAfter(DetailAST leftCurly) {
341 DetailAST nextToken = null;
342 if (leftCurly.getType() == TokenTypes.SLIST) {
343 nextToken = leftCurly.getFirstChild();
344 }
345 else {
346 if (!ignoreEnums
347 && leftCurly.getParent().getParent().getType() == TokenTypes.ENUM_DEF) {
348 nextToken = leftCurly.getNextSibling();
349 }
350 }
351 return nextToken == null
352 || nextToken.getType() == TokenTypes.RCURLY
353 || !TokenUtil.areOnSameLine(leftCurly, nextToken);
354 }
355
356 }