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