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 }