View Javadoc
1   ///////////////////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code and other text files for adherence to a set of rules.
3   // Copyright (C) 2001-2024 the original author or authors.
4   //
5   // This library is free software; you can redistribute it and/or
6   // modify it under the terms of the GNU Lesser General Public
7   // License as published by the Free Software Foundation; either
8   // version 2.1 of the License, or (at your option) any later version.
9   //
10  // This library is distributed in the hope that it will be useful,
11  // but WITHOUT ANY WARRANTY; without even the implied warranty of
12  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  // Lesser General Public License for more details.
14  //
15  // You should have received a copy of the GNU Lesser General Public
16  // License along with this library; if not, write to the Free Software
17  // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
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   * <p>
35   * Checks for the placement of left curly braces (<code>'{'</code>) for code blocks.
36   * </p>
37   * <ul>
38   * <li>
39   * Property {@code ignoreEnums} - Allow to ignore enums when left curly brace policy is EOL.
40   * Type is {@code boolean}.
41   * Default value is {@code true}.
42   * </li>
43   * <li>
44   * Property {@code option} - Specify the policy on placement of a left curly brace
45   * (<code>'{'</code>).
46   * Type is {@code com.puppycrawl.tools.checkstyle.checks.blocks.LeftCurlyOption}.
47   * Default value is {@code eol}.
48   * </li>
49   * <li>
50   * Property {@code tokens} - tokens to check
51   * Type is {@code java.lang.String[]}.
52   * Validation type is {@code tokenSet}.
53   * Default value is:
54   * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#ANNOTATION_DEF">
55   * ANNOTATION_DEF</a>,
56   * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#CLASS_DEF">
57   * CLASS_DEF</a>,
58   * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#CTOR_DEF">
59   * CTOR_DEF</a>,
60   * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#ENUM_CONSTANT_DEF">
61   * ENUM_CONSTANT_DEF</a>,
62   * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#ENUM_DEF">
63   * ENUM_DEF</a>,
64   * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#INTERFACE_DEF">
65   * INTERFACE_DEF</a>,
66   * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LAMBDA">
67   * LAMBDA</a>,
68   * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_CASE">
69   * LITERAL_CASE</a>,
70   * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_CATCH">
71   * LITERAL_CATCH</a>,
72   * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_DEFAULT">
73   * LITERAL_DEFAULT</a>,
74   * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_DO">
75   * LITERAL_DO</a>,
76   * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_ELSE">
77   * LITERAL_ELSE</a>,
78   * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_FINALLY">
79   * LITERAL_FINALLY</a>,
80   * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_FOR">
81   * LITERAL_FOR</a>,
82   * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_IF">
83   * LITERAL_IF</a>,
84   * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_SWITCH">
85   * LITERAL_SWITCH</a>,
86   * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_SYNCHRONIZED">
87   * LITERAL_SYNCHRONIZED</a>,
88   * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_TRY">
89   * LITERAL_TRY</a>,
90   * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#LITERAL_WHILE">
91   * LITERAL_WHILE</a>,
92   * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#METHOD_DEF">
93   * METHOD_DEF</a>,
94   * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#OBJBLOCK">
95   * OBJBLOCK</a>,
96   * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#STATIC_INIT">
97   * STATIC_INIT</a>,
98   * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#RECORD_DEF">
99   * RECORD_DEF</a>,
100  * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#COMPACT_CTOR_DEF">
101  * COMPACT_CTOR_DEF</a>.
102  * </li>
103  * </ul>
104  * <p>
105  * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker}
106  * </p>
107  * <p>
108  * Violation Message Keys:
109  * </p>
110  * <ul>
111  * <li>
112  * {@code line.break.after}
113  * </li>
114  * <li>
115  * {@code line.new}
116  * </li>
117  * <li>
118  * {@code line.previous}
119  * </li>
120  * </ul>
121  *
122  * @since 3.0
123  */
124 @StatelessCheck
125 public class LeftCurlyCheck
126     extends AbstractCheck {
127 
128     /**
129      * A key is pointing to the warning message text in "messages.properties"
130      * file.
131      */
132     public static final String MSG_KEY_LINE_NEW = "line.new";
133 
134     /**
135      * A key is pointing to the warning message text in "messages.properties"
136      * file.
137      */
138     public static final String MSG_KEY_LINE_PREVIOUS = "line.previous";
139 
140     /**
141      * A key is pointing to the warning message text in "messages.properties"
142      * file.
143      */
144     public static final String MSG_KEY_LINE_BREAK_AFTER = "line.break.after";
145 
146     /** Open curly brace literal. */
147     private static final String OPEN_CURLY_BRACE = "{";
148 
149     /** Allow to ignore enums when left curly brace policy is EOL. */
150     private boolean ignoreEnums = true;
151 
152     /**
153      * Specify the policy on placement of a left curly brace (<code>'{'</code>).
154      * */
155     private LeftCurlyOption option = LeftCurlyOption.EOL;
156 
157     /**
158      * Setter to specify the policy on placement of a left curly brace (<code>'{'</code>).
159      *
160      * @param optionStr string to decode option from
161      * @throws IllegalArgumentException if unable to decode
162      * @since 3.0
163      */
164     public void setOption(String optionStr) {
165         option = LeftCurlyOption.valueOf(optionStr.trim().toUpperCase(Locale.ENGLISH));
166     }
167 
168     /**
169      * Setter to allow to ignore enums when left curly brace policy is EOL.
170      *
171      * @param ignoreEnums check's option for ignoring enums.
172      * @since 6.9
173      */
174     public void setIgnoreEnums(boolean ignoreEnums) {
175         this.ignoreEnums = ignoreEnums;
176     }
177 
178     @Override
179     public int[] getDefaultTokens() {
180         return getAcceptableTokens();
181     }
182 
183     @Override
184     public int[] getAcceptableTokens() {
185         return new int[] {
186             TokenTypes.ANNOTATION_DEF,
187             TokenTypes.CLASS_DEF,
188             TokenTypes.CTOR_DEF,
189             TokenTypes.ENUM_CONSTANT_DEF,
190             TokenTypes.ENUM_DEF,
191             TokenTypes.INTERFACE_DEF,
192             TokenTypes.LAMBDA,
193             TokenTypes.LITERAL_CASE,
194             TokenTypes.LITERAL_CATCH,
195             TokenTypes.LITERAL_DEFAULT,
196             TokenTypes.LITERAL_DO,
197             TokenTypes.LITERAL_ELSE,
198             TokenTypes.LITERAL_FINALLY,
199             TokenTypes.LITERAL_FOR,
200             TokenTypes.LITERAL_IF,
201             TokenTypes.LITERAL_SWITCH,
202             TokenTypes.LITERAL_SYNCHRONIZED,
203             TokenTypes.LITERAL_TRY,
204             TokenTypes.LITERAL_WHILE,
205             TokenTypes.METHOD_DEF,
206             TokenTypes.OBJBLOCK,
207             TokenTypes.STATIC_INIT,
208             TokenTypes.RECORD_DEF,
209             TokenTypes.COMPACT_CTOR_DEF,
210         };
211     }
212 
213     @Override
214     public int[] getRequiredTokens() {
215         return CommonUtil.EMPTY_INT_ARRAY;
216     }
217 
218     /**
219      * Visits token.
220      *
221      * @param ast the token to process
222      * @noinspection SwitchStatementWithTooManyBranches
223      * @noinspectionreason SwitchStatementWithTooManyBranches - we cannot reduce
224      *      the number of branches in this switch statement, since many tokens
225      *      require specific methods to find the first left curly
226      */
227     @Override
228     public void visitToken(DetailAST ast) {
229         final DetailAST startToken;
230         final DetailAST brace;
231 
232         switch (ast.getType()) {
233             case TokenTypes.CTOR_DEF:
234             case TokenTypes.METHOD_DEF:
235             case TokenTypes.COMPACT_CTOR_DEF:
236                 startToken = skipModifierAnnotations(ast);
237                 brace = ast.findFirstToken(TokenTypes.SLIST);
238                 break;
239             case TokenTypes.INTERFACE_DEF:
240             case TokenTypes.CLASS_DEF:
241             case TokenTypes.ANNOTATION_DEF:
242             case TokenTypes.ENUM_DEF:
243             case TokenTypes.ENUM_CONSTANT_DEF:
244             case TokenTypes.RECORD_DEF:
245                 startToken = skipModifierAnnotations(ast);
246                 brace = ast.findFirstToken(TokenTypes.OBJBLOCK);
247                 break;
248             case TokenTypes.LITERAL_WHILE:
249             case TokenTypes.LITERAL_CATCH:
250             case TokenTypes.LITERAL_SYNCHRONIZED:
251             case TokenTypes.LITERAL_FOR:
252             case TokenTypes.LITERAL_TRY:
253             case TokenTypes.LITERAL_FINALLY:
254             case TokenTypes.LITERAL_DO:
255             case TokenTypes.LITERAL_IF:
256             case TokenTypes.STATIC_INIT:
257             case TokenTypes.LAMBDA:
258                 startToken = ast;
259                 brace = ast.findFirstToken(TokenTypes.SLIST);
260                 break;
261             case TokenTypes.LITERAL_ELSE:
262                 startToken = ast;
263                 brace = getBraceAsFirstChild(ast);
264                 break;
265             case TokenTypes.LITERAL_CASE:
266             case TokenTypes.LITERAL_DEFAULT:
267                 startToken = ast;
268                 brace = getBraceFromSwitchMember(ast);
269                 break;
270             default:
271                 // ATTENTION! We have default here, but we expect case TokenTypes.METHOD_DEF,
272                 // TokenTypes.LITERAL_FOR, TokenTypes.LITERAL_WHILE, TokenTypes.LITERAL_DO only.
273                 // It has been done to improve coverage to 100%. I couldn't replace it with
274                 // if-else-if block because code was ugly and didn't pass pmd check.
275 
276                 startToken = ast;
277                 brace = ast.findFirstToken(TokenTypes.LCURLY);
278                 break;
279         }
280 
281         if (brace != null) {
282             verifyBrace(brace, startToken);
283         }
284     }
285 
286     /**
287      * Gets the brace of a switch statement/ expression member.
288      *
289      * @param ast {@code DetailAST}.
290      * @return {@code DetailAST} if the first child is {@code TokenTypes.SLIST},
291      *     {@code null} otherwise.
292      */
293     @Nullable
294     private static DetailAST getBraceFromSwitchMember(DetailAST ast) {
295         final DetailAST brace;
296         final DetailAST parent = ast.getParent();
297         if (parent.getType() == TokenTypes.SWITCH_RULE) {
298             brace = parent.findFirstToken(TokenTypes.SLIST);
299         }
300         else {
301             brace = getBraceAsFirstChild(ast.getNextSibling());
302         }
303         return brace;
304     }
305 
306     /**
307      * Gets a SLIST if it is the first child of the AST.
308      *
309      * @param ast {@code DetailAST}.
310      * @return {@code DetailAST} if the first child is {@code TokenTypes.SLIST},
311      *     {@code null} otherwise.
312      */
313     @Nullable
314     private static DetailAST getBraceAsFirstChild(DetailAST ast) {
315         DetailAST brace = null;
316         if (ast != null) {
317             final DetailAST candidate = ast.getFirstChild();
318             if (candidate != null && candidate.getType() == TokenTypes.SLIST) {
319                 brace = candidate;
320             }
321         }
322         return brace;
323     }
324 
325     /**
326      * Skip all {@code TokenTypes.ANNOTATION}s to the first non-annotation.
327      *
328      * @param ast {@code DetailAST}.
329      * @return {@code DetailAST}.
330      */
331     private static DetailAST skipModifierAnnotations(DetailAST ast) {
332         DetailAST resultNode = ast;
333         final DetailAST modifiers = ast.findFirstToken(TokenTypes.MODIFIERS);
334 
335         if (modifiers != null) {
336             final DetailAST lastAnnotation = findLastAnnotation(modifiers);
337 
338             if (lastAnnotation != null) {
339                 if (lastAnnotation.getNextSibling() == null) {
340                     resultNode = modifiers.getNextSibling();
341                 }
342                 else {
343                     resultNode = lastAnnotation.getNextSibling();
344                 }
345             }
346         }
347         return resultNode;
348     }
349 
350     /**
351      * Find the last token of type {@code TokenTypes.ANNOTATION}
352      * under the given set of modifiers.
353      *
354      * @param modifiers {@code DetailAST}.
355      * @return {@code DetailAST} or null if there are no annotations.
356      */
357     private static DetailAST findLastAnnotation(DetailAST modifiers) {
358         DetailAST annotation = modifiers.findFirstToken(TokenTypes.ANNOTATION);
359         while (annotation != null && annotation.getNextSibling() != null
360                && annotation.getNextSibling().getType() == TokenTypes.ANNOTATION) {
361             annotation = annotation.getNextSibling();
362         }
363         return annotation;
364     }
365 
366     /**
367      * Verifies that a specified left curly brace is placed correctly
368      * according to policy.
369      *
370      * @param brace token for left curly brace
371      * @param startToken token for start of expression
372      */
373     private void verifyBrace(final DetailAST brace,
374                              final DetailAST startToken) {
375         final String braceLine = getLine(brace.getLineNo() - 1);
376 
377         // Check for being told to ignore, or have '{}' which is a special case
378         if (braceLine.length() <= brace.getColumnNo() + 1
379                 || braceLine.charAt(brace.getColumnNo() + 1) != '}') {
380             if (option == LeftCurlyOption.NL) {
381                 if (!CommonUtil.hasWhitespaceBefore(brace.getColumnNo(), braceLine)) {
382                     log(brace, MSG_KEY_LINE_NEW, OPEN_CURLY_BRACE, brace.getColumnNo() + 1);
383                 }
384             }
385             else if (option == LeftCurlyOption.EOL) {
386                 validateEol(brace, braceLine);
387             }
388             else if (!TokenUtil.areOnSameLine(startToken, brace)) {
389                 validateNewLinePosition(brace, startToken, braceLine);
390             }
391         }
392     }
393 
394     /**
395      * Validate EOL case.
396      *
397      * @param brace brace AST
398      * @param braceLine line content
399      */
400     private void validateEol(DetailAST brace, String braceLine) {
401         if (CommonUtil.hasWhitespaceBefore(brace.getColumnNo(), braceLine)) {
402             log(brace, MSG_KEY_LINE_PREVIOUS, OPEN_CURLY_BRACE, brace.getColumnNo() + 1);
403         }
404         if (!hasLineBreakAfter(brace)) {
405             log(brace, MSG_KEY_LINE_BREAK_AFTER, OPEN_CURLY_BRACE, brace.getColumnNo() + 1);
406         }
407     }
408 
409     /**
410      * Validate token on new Line position.
411      *
412      * @param brace brace AST
413      * @param startToken start Token
414      * @param braceLine content of line with Brace
415      */
416     private void validateNewLinePosition(DetailAST brace, DetailAST startToken, String braceLine) {
417         // not on the same line
418         if (startToken.getLineNo() + 1 == brace.getLineNo()) {
419             if (CommonUtil.hasWhitespaceBefore(brace.getColumnNo(), braceLine)) {
420                 log(brace, MSG_KEY_LINE_PREVIOUS, OPEN_CURLY_BRACE, brace.getColumnNo() + 1);
421             }
422             else {
423                 log(brace, MSG_KEY_LINE_NEW, OPEN_CURLY_BRACE, brace.getColumnNo() + 1);
424             }
425         }
426         else if (!CommonUtil.hasWhitespaceBefore(brace.getColumnNo(), braceLine)) {
427             log(brace, MSG_KEY_LINE_NEW, OPEN_CURLY_BRACE, brace.getColumnNo() + 1);
428         }
429     }
430 
431     /**
432      * Checks if left curly has line break after.
433      *
434      * @param leftCurly
435      *        Left curly token.
436      * @return
437      *        True, left curly has line break after.
438      */
439     private boolean hasLineBreakAfter(DetailAST leftCurly) {
440         DetailAST nextToken = null;
441         if (leftCurly.getType() == TokenTypes.SLIST) {
442             nextToken = leftCurly.getFirstChild();
443         }
444         else {
445             if (!ignoreEnums
446                     && leftCurly.getParent().getParent().getType() == TokenTypes.ENUM_DEF) {
447                 nextToken = leftCurly.getNextSibling();
448             }
449         }
450         return nextToken == null
451                 || nextToken.getType() == TokenTypes.RCURLY
452                 || !TokenUtil.areOnSameLine(leftCurly, nextToken);
453     }
454 
455 }