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