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