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