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   * <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;
234 
235         switch (ast.getType()) {
236             case TokenTypes.CTOR_DEF:
237             case TokenTypes.METHOD_DEF:
238             case TokenTypes.COMPACT_CTOR_DEF:
239                 startToken = skipModifierAnnotations(ast);
240                 brace = ast.findFirstToken(TokenTypes.SLIST);
241                 break;
242             case TokenTypes.INTERFACE_DEF:
243             case TokenTypes.CLASS_DEF:
244             case TokenTypes.ANNOTATION_DEF:
245             case TokenTypes.ENUM_DEF:
246             case TokenTypes.ENUM_CONSTANT_DEF:
247             case TokenTypes.RECORD_DEF:
248                 startToken = skipModifierAnnotations(ast);
249                 brace = ast.findFirstToken(TokenTypes.OBJBLOCK);
250                 break;
251             case TokenTypes.LITERAL_WHILE:
252             case TokenTypes.LITERAL_CATCH:
253             case TokenTypes.LITERAL_SYNCHRONIZED:
254             case TokenTypes.LITERAL_FOR:
255             case TokenTypes.LITERAL_TRY:
256             case TokenTypes.LITERAL_FINALLY:
257             case TokenTypes.LITERAL_DO:
258             case TokenTypes.LITERAL_IF:
259             case TokenTypes.STATIC_INIT:
260             case TokenTypes.LAMBDA:
261                 startToken = ast;
262                 brace = ast.findFirstToken(TokenTypes.SLIST);
263                 break;
264             case TokenTypes.LITERAL_ELSE:
265                 startToken = ast;
266                 brace = getBraceAsFirstChild(ast);
267                 break;
268             case TokenTypes.LITERAL_CASE:
269             case TokenTypes.LITERAL_DEFAULT:
270                 startToken = ast;
271                 brace = getBraceFromSwitchMember(ast);
272                 break;
273             default:
274                 // ATTENTION! We have default here, but we expect case TokenTypes.METHOD_DEF,
275                 // TokenTypes.LITERAL_FOR, TokenTypes.LITERAL_WHILE, TokenTypes.LITERAL_DO only.
276                 // It has been done to improve coverage to 100%. I couldn't replace it with
277                 // if-else-if block because code was ugly and didn't pass pmd check.
278 
279                 startToken = ast;
280                 brace = ast.findFirstToken(TokenTypes.LCURLY);
281                 break;
282         }
283 
284         if (brace != null) {
285             verifyBrace(brace, startToken);
286         }
287     }
288 
289     /**
290      * Gets the brace of a switch statement/ expression member.
291      *
292      * @param ast {@code DetailAST}.
293      * @return {@code DetailAST} if the first child is {@code TokenTypes.SLIST},
294      *     {@code null} otherwise.
295      */
296     @Nullable
297     private static DetailAST getBraceFromSwitchMember(DetailAST ast) {
298         final DetailAST brace;
299         final DetailAST parent = ast.getParent();
300         if (parent.getType() == TokenTypes.SWITCH_RULE) {
301             brace = parent.findFirstToken(TokenTypes.SLIST);
302         }
303         else {
304             brace = getBraceAsFirstChild(ast.getNextSibling());
305         }
306         return brace;
307     }
308 
309     /**
310      * Gets a SLIST if it is the first child of the AST.
311      *
312      * @param ast {@code DetailAST}.
313      * @return {@code DetailAST} if the first child is {@code TokenTypes.SLIST},
314      *     {@code null} otherwise.
315      */
316     @Nullable
317     private static DetailAST getBraceAsFirstChild(DetailAST ast) {
318         DetailAST brace = null;
319         if (ast != null) {
320             final DetailAST candidate = ast.getFirstChild();
321             if (candidate != null && candidate.getType() == TokenTypes.SLIST) {
322                 brace = candidate;
323             }
324         }
325         return brace;
326     }
327 
328     /**
329      * Skip all {@code TokenTypes.ANNOTATION}s to the first non-annotation.
330      *
331      * @param ast {@code DetailAST}.
332      * @return {@code DetailAST}.
333      */
334     private static DetailAST skipModifierAnnotations(DetailAST ast) {
335         DetailAST resultNode = ast;
336         final DetailAST modifiers = ast.findFirstToken(TokenTypes.MODIFIERS);
337 
338         if (modifiers != null) {
339             final DetailAST lastAnnotation = findLastAnnotation(modifiers);
340 
341             if (lastAnnotation != null) {
342                 if (lastAnnotation.getNextSibling() == null) {
343                     resultNode = modifiers.getNextSibling();
344                 }
345                 else {
346                     resultNode = lastAnnotation.getNextSibling();
347                 }
348             }
349         }
350         return resultNode;
351     }
352 
353     /**
354      * Find the last token of type {@code TokenTypes.ANNOTATION}
355      * under the given set of modifiers.
356      *
357      * @param modifiers {@code DetailAST}.
358      * @return {@code DetailAST} or null if there are no annotations.
359      */
360     private static DetailAST findLastAnnotation(DetailAST modifiers) {
361         DetailAST annotation = modifiers.findFirstToken(TokenTypes.ANNOTATION);
362         while (annotation != null && annotation.getNextSibling() != null
363                && annotation.getNextSibling().getType() == TokenTypes.ANNOTATION) {
364             annotation = annotation.getNextSibling();
365         }
366         return annotation;
367     }
368 
369     /**
370      * Verifies that a specified left curly brace is placed correctly
371      * according to policy.
372      *
373      * @param brace token for left curly brace
374      * @param startToken token for start of expression
375      */
376     private void verifyBrace(final DetailAST brace,
377                              final DetailAST startToken) {
378         final String braceLine = getLine(brace.getLineNo() - 1);
379 
380         // Check for being told to ignore, or have '{}' which is a special case
381         if (braceLine.length() <= brace.getColumnNo() + 1
382                 || braceLine.charAt(brace.getColumnNo() + 1) != '}') {
383             if (option == LeftCurlyOption.NL) {
384                 if (!CommonUtil.hasWhitespaceBefore(brace.getColumnNo(), braceLine)) {
385                     log(brace, MSG_KEY_LINE_NEW, OPEN_CURLY_BRACE, brace.getColumnNo() + 1);
386                 }
387             }
388             else if (option == LeftCurlyOption.EOL) {
389                 validateEol(brace, braceLine);
390             }
391             else if (!TokenUtil.areOnSameLine(startToken, brace)) {
392                 validateNewLinePosition(brace, startToken, braceLine);
393             }
394         }
395     }
396 
397     /**
398      * Validate EOL case.
399      *
400      * @param brace brace AST
401      * @param braceLine line content
402      */
403     private void validateEol(DetailAST brace, String braceLine) {
404         if (CommonUtil.hasWhitespaceBefore(brace.getColumnNo(), braceLine)) {
405             log(brace, MSG_KEY_LINE_PREVIOUS, OPEN_CURLY_BRACE, brace.getColumnNo() + 1);
406         }
407         if (!hasLineBreakAfter(brace)) {
408             log(brace, MSG_KEY_LINE_BREAK_AFTER, OPEN_CURLY_BRACE, brace.getColumnNo() + 1);
409         }
410     }
411 
412     /**
413      * Validate token on new Line position.
414      *
415      * @param brace brace AST
416      * @param startToken start Token
417      * @param braceLine content of line with Brace
418      */
419     private void validateNewLinePosition(DetailAST brace, DetailAST startToken, String braceLine) {
420         // not on the same line
421         if (startToken.getLineNo() + 1 == brace.getLineNo()) {
422             if (CommonUtil.hasWhitespaceBefore(brace.getColumnNo(), braceLine)) {
423                 log(brace, MSG_KEY_LINE_PREVIOUS, OPEN_CURLY_BRACE, brace.getColumnNo() + 1);
424             }
425             else {
426                 log(brace, MSG_KEY_LINE_NEW, OPEN_CURLY_BRACE, brace.getColumnNo() + 1);
427             }
428         }
429         else if (!CommonUtil.hasWhitespaceBefore(brace.getColumnNo(), braceLine)) {
430             log(brace, MSG_KEY_LINE_NEW, OPEN_CURLY_BRACE, brace.getColumnNo() + 1);
431         }
432     }
433 
434     /**
435      * Checks if left curly has line break after.
436      *
437      * @param leftCurly
438      *        Left curly token.
439      * @return
440      *        True, left curly has line break after.
441      */
442     private boolean hasLineBreakAfter(DetailAST leftCurly) {
443         DetailAST nextToken = null;
444         if (leftCurly.getType() == TokenTypes.SLIST) {
445             nextToken = leftCurly.getFirstChild();
446         }
447         else {
448             if (!ignoreEnums
449                     && leftCurly.getParent().getParent().getType() == TokenTypes.ENUM_DEF) {
450                 nextToken = leftCurly.getNextSibling();
451             }
452         }
453         return nextToken == null
454                 || nextToken.getType() == TokenTypes.RCURLY
455                 || !TokenUtil.areOnSameLine(leftCurly, nextToken);
456     }
457 
458 }