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