Coverage Report - com.puppycrawl.tools.checkstyle.checks.indentation.AbstractExpressionHandler
 
Classes in this File Line Coverage Branch Coverage Complexity
AbstractExpressionHandler
100%
142/142
100%
82/82
2.219
 
 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.indentation;
 21  
 
 22  
 import java.util.Arrays;
 23  
 
 24  
 import com.puppycrawl.tools.checkstyle.api.DetailAST;
 25  
 import com.puppycrawl.tools.checkstyle.api.TokenTypes;
 26  
 import com.puppycrawl.tools.checkstyle.utils.CommonUtils;
 27  
 
 28  
 /**
 29  
  * Abstract base class for all handlers.
 30  
  *
 31  
  * @author jrichard
 32  
  */
 33  
 public abstract class AbstractExpressionHandler {
 34  
     /**
 35  
      * The instance of {@code IndentationCheck} using this handler.
 36  
      */
 37  
     private final IndentationCheck indentCheck;
 38  
 
 39  
     /** The AST which is handled by this handler. */
 40  
     private final DetailAST mainAst;
 41  
 
 42  
     /** Name used during output to user. */
 43  
     private final String typeName;
 44  
 
 45  
     /** Containing AST handler. */
 46  
     private final AbstractExpressionHandler parent;
 47  
 
 48  
     /** Indentation amount for this handler. */
 49  
     private IndentLevel indent;
 50  
 
 51  
     /**
 52  
      * Construct an instance of this handler with the given indentation check,
 53  
      * name, abstract syntax tree, and parent handler.
 54  
      *
 55  
      * @param indentCheck   the indentation check
 56  
      * @param typeName      the name of the handler
 57  
      * @param expr          the abstract syntax tree
 58  
      * @param parent        the parent handler
 59  
      */
 60  
     protected AbstractExpressionHandler(IndentationCheck indentCheck, String typeName,
 61  3736
             DetailAST expr, AbstractExpressionHandler parent) {
 62  3736
         this.indentCheck = indentCheck;
 63  3736
         this.typeName = typeName;
 64  3736
         mainAst = expr;
 65  3736
         this.parent = parent;
 66  3736
     }
 67  
 
 68  
     /**
 69  
      * Check the indentation of the expression we are handling.
 70  
      */
 71  
     public abstract void checkIndentation();
 72  
 
 73  
     /**
 74  
      * Get the indentation amount for this handler. For performance reasons,
 75  
      * this value is cached. The first time this method is called, the
 76  
      * indentation amount is computed and stored. On further calls, the stored
 77  
      * value is returned.
 78  
      *
 79  
      * @return the expected indentation amount
 80  
      * @noinspection WeakerAccess
 81  
      */
 82  
     public final IndentLevel getIndent() {
 83  12218
         if (indent == null) {
 84  2543
             indent = getIndentImpl();
 85  
         }
 86  12218
         return indent;
 87  
     }
 88  
 
 89  
     /**
 90  
      * Compute the indentation amount for this handler.
 91  
      *
 92  
      * @return the expected indentation amount
 93  
      */
 94  
     protected IndentLevel getIndentImpl() {
 95  2021
         return parent.getSuggestedChildIndent(this);
 96  
     }
 97  
 
 98  
     /**
 99  
      * Indentation level suggested for a child element. Children don't have
 100  
      * to respect this, but most do.
 101  
      *
 102  
      * @param child  child AST (so suggestion level can differ based on child
 103  
      *                  type)
 104  
      *
 105  
      * @return suggested indentation for child
 106  
      * @noinspection WeakerAccess
 107  
      */
 108  
     public IndentLevel getSuggestedChildIndent(AbstractExpressionHandler child) {
 109  62
         return new IndentLevel(getIndent(), getBasicOffset());
 110  
     }
 111  
 
 112  
     /**
 113  
      * Log an indentation error.
 114  
      *
 115  
      * @param ast           the expression that caused the error
 116  
      * @param subtypeName   the type of the expression
 117  
      * @param actualIndent  the actual indent level of the expression
 118  
      */
 119  
     protected final void logError(DetailAST ast, String subtypeName,
 120  
                                   int actualIndent) {
 121  127
         logError(ast, subtypeName, actualIndent, getIndent());
 122  127
     }
 123  
 
 124  
     /**
 125  
      * Log an indentation error.
 126  
      *
 127  
      * @param ast            the expression that caused the error
 128  
      * @param subtypeName    the type of the expression
 129  
      * @param actualIndent   the actual indent level of the expression
 130  
      * @param expectedIndent the expected indent level of the expression
 131  
      */
 132  
     protected final void logError(DetailAST ast, String subtypeName,
 133  
                                   int actualIndent, IndentLevel expectedIndent) {
 134  
         final String typeStr;
 135  
 
 136  281
         if (subtypeName.isEmpty()) {
 137  57
             typeStr = "";
 138  
         }
 139  
         else {
 140  224
             typeStr = " " + subtypeName;
 141  
         }
 142  281
         String messageKey = IndentationCheck.MSG_ERROR;
 143  281
         if (expectedIndent.isMultiLevel()) {
 144  24
             messageKey = IndentationCheck.MSG_ERROR_MULTI;
 145  
         }
 146  562
         indentCheck.indentationLog(ast.getLineNo(), messageKey,
 147  281
             typeName + typeStr, actualIndent, expectedIndent);
 148  281
     }
 149  
 
 150  
     /**
 151  
      * Log child indentation error.
 152  
      *
 153  
      * @param line           the expression that caused the error
 154  
      * @param actualIndent   the actual indent level of the expression
 155  
      * @param expectedIndent the expected indent level of the expression
 156  
      */
 157  
     private void logChildError(int line,
 158  
                                int actualIndent,
 159  
                                IndentLevel expectedIndent) {
 160  156
         String messageKey = IndentationCheck.MSG_CHILD_ERROR;
 161  156
         if (expectedIndent.isMultiLevel()) {
 162  10
             messageKey = IndentationCheck.MSG_CHILD_ERROR_MULTI;
 163  
         }
 164  312
         indentCheck.indentationLog(line, messageKey,
 165  156
             typeName, actualIndent, expectedIndent);
 166  156
     }
 167  
 
 168  
     /**
 169  
      * Determines if the given expression is at the start of a line.
 170  
      *
 171  
      * @param ast   the expression to check
 172  
      *
 173  
      * @return true if it is, false otherwise
 174  
      */
 175  
     protected final boolean isOnStartOfLine(DetailAST ast) {
 176  4193
         return getLineStart(ast) == expandedTabsColumnNo(ast);
 177  
     }
 178  
 
 179  
     /**
 180  
      * Determines if two expressions are on the same line.
 181  
      *
 182  
      * @param ast1   the first expression
 183  
      * @param ast2   the second expression
 184  
      *
 185  
      * @return true if they are, false otherwise
 186  
      * @noinspection WeakerAccess
 187  
      */
 188  
     public static boolean areOnSameLine(DetailAST ast1, DetailAST ast2) {
 189  1202
         return ast1.getLineNo() == ast2.getLineNo();
 190  
     }
 191  
 
 192  
     /**
 193  
      * Searches in given sub-tree (including given node) for the token
 194  
      * which represents first symbol for this sub-tree in file.
 195  
      * @param ast a root of sub-tree in which the search should be performed.
 196  
      * @return a token which occurs first in the file.
 197  
      * @noinspection WeakerAccess
 198  
      */
 199  
     public static DetailAST getFirstToken(DetailAST ast) {
 200  729
         DetailAST first = ast;
 201  729
         DetailAST child = ast.getFirstChild();
 202  
 
 203  1199
         while (child != null) {
 204  470
             final DetailAST toTest = getFirstToken(child);
 205  470
             if (toTest.getColumnNo() < first.getColumnNo()) {
 206  116
                 first = toTest;
 207  
             }
 208  470
             child = child.getNextSibling();
 209  470
         }
 210  
 
 211  729
         return first;
 212  
     }
 213  
 
 214  
     /**
 215  
      * Get the start of the line for the given expression.
 216  
      *
 217  
      * @param ast   the expression to find the start of the line for
 218  
      *
 219  
      * @return the start of the line for the given expression
 220  
      */
 221  
     protected final int getLineStart(DetailAST ast) {
 222  5294
         return getLineStart(ast.getLineNo());
 223  
     }
 224  
 
 225  
     /**
 226  
      * Get the start of the line for the given line number.
 227  
      *
 228  
      * @param lineNo   the line number to find the start for
 229  
      *
 230  
      * @return the start of the line for the given expression
 231  
      */
 232  
     protected final int getLineStart(int lineNo) {
 233  5312
         return getLineStart(indentCheck.getLine(lineNo - 1));
 234  
     }
 235  
 
 236  
     /**
 237  
      * Get the start of the specified line.
 238  
      *
 239  
      * @param line   the specified line number
 240  
      *
 241  
      * @return the start of the specified line
 242  
      */
 243  
     private int getLineStart(String line) {
 244  9457
         int index = 0;
 245  87122
         while (Character.isWhitespace(line.charAt(index))) {
 246  77665
             index++;
 247  
         }
 248  18914
         return CommonUtils.lengthExpandedTabs(
 249  9457
             line, index, indentCheck.getIndentationTabWidth());
 250  
     }
 251  
 
 252  
     /**
 253  
      * Checks that indentation should be increased after first line in checkLinesIndent().
 254  
      * @return true if indentation should be increased after
 255  
      *              first line in checkLinesIndent()
 256  
      *         false otherwise
 257  
      */
 258  
     protected boolean shouldIncreaseIndent() {
 259  69
         return true;
 260  
     }
 261  
 
 262  
     /**
 263  
      * Check the indentation for a set of lines.
 264  
      *
 265  
      * @param lines              the set of lines to check
 266  
      * @param indentLevel        the indentation level
 267  
      * @param firstLineMatches   whether or not the first line has to match
 268  
      * @param firstLine          first line of whole expression
 269  
      */
 270  
     private void checkLinesIndent(LineSet lines,
 271  
                                   IndentLevel indentLevel,
 272  
                                   boolean firstLineMatches,
 273  
                                   int firstLine) {
 274  2012
         if (!lines.isEmpty()) {
 275  
             // check first line
 276  1992
             final int startLine = lines.firstLine();
 277  1992
             final int endLine = lines.lastLine();
 278  1992
             final int startCol = lines.firstLineCol();
 279  
 
 280  1992
             final int realStartCol =
 281  1992
                 getLineStart(indentCheck.getLine(startLine - 1));
 282  
 
 283  1992
             if (realStartCol == startCol) {
 284  1210
                 checkLineIndent(startLine, startCol, indentLevel,
 285  
                     firstLineMatches);
 286  
             }
 287  
 
 288  
             // if first line starts the line, following lines are indented
 289  
             // one level; but if the first line of this expression is
 290  
             // nested with the previous expression (which is assumed if it
 291  
             // doesn't start the line) then don't indent more, the first
 292  
             // indentation is absorbed by the nesting
 293  
 
 294  1992
             IndentLevel theLevel = indentLevel;
 295  1992
             if (firstLineMatches
 296  891
                 || firstLine > mainAst.getLineNo() && shouldIncreaseIndent()) {
 297  1170
                 theLevel = new IndentLevel(indentLevel, getBasicOffset());
 298  
             }
 299  
 
 300  
             // check following lines
 301  2213
             for (int i = startLine + 1; i <= endLine; i++) {
 302  221
                 final Integer col = lines.getStartColumn(i);
 303  
                 // startCol could be null if this line didn't have an
 304  
                 // expression that was required to be checked (it could be
 305  
                 // checked by a child expression)
 306  
 
 307  221
                 if (col != null) {
 308  145
                     checkLineIndent(i, col, theLevel, false);
 309  
                 }
 310  
             }
 311  
         }
 312  2012
     }
 313  
 
 314  
     /**
 315  
      * Check the indentation for a single line.
 316  
      *
 317  
      * @param lineNum       the number of the line to check
 318  
      * @param colNum        the column number we are starting at
 319  
      * @param indentLevel   the indentation level
 320  
      * @param mustMatch     whether or not the indentation level must match
 321  
      */
 322  
     private void checkLineIndent(int lineNum, int colNum,
 323  
         IndentLevel indentLevel, boolean mustMatch) {
 324  1355
         final String line = indentCheck.getLine(lineNum - 1);
 325  1355
         final int start = getLineStart(line);
 326  
         // if must match is set, it is an error if the line start is not
 327  
         // at the correct indention level; otherwise, it is an only an
 328  
         // error if this statement starts the line and it is less than
 329  
         // the correct indentation level
 330  1355
         if (mustMatch && !indentLevel.isAcceptable(start)
 331  410
                 || !mustMatch && colNum == start && indentLevel.isGreaterThan(start)) {
 332  156
             logChildError(lineNum, start, indentLevel);
 333  
         }
 334  1355
     }
 335  
 
 336  
     /**
 337  
      * Checks indentation on wrapped lines between and including
 338  
      * {@code firstNode} and {@code lastNode}.
 339  
      *
 340  
      * @param firstNode First node to start examining.
 341  
      * @param lastNode Last node to examine inclusively.
 342  
      */
 343  
     protected void checkWrappingIndentation(DetailAST firstNode, DetailAST lastNode) {
 344  1200
         indentCheck.getLineWrappingHandler().checkIndentation(firstNode, lastNode);
 345  1200
     }
 346  
 
 347  
     /**
 348  
      * Checks indentation on wrapped lines between and including
 349  
      * {@code firstNode} and {@code lastNode}.
 350  
      *
 351  
      * @param firstNode First node to start examining.
 352  
      * @param lastNode Last node to examine inclusively.
 353  
      * @param wrappedIndentLevel Indentation all wrapped lines should use.
 354  
      * @param startIndent Indentation first line before wrapped lines used.
 355  
      * @param ignoreFirstLine Test if first line's indentation should be checked or not.
 356  
      */
 357  
     protected void checkWrappingIndentation(DetailAST firstNode, DetailAST lastNode,
 358  
             int wrappedIndentLevel, int startIndent, boolean ignoreFirstLine) {
 359  232
         indentCheck.getLineWrappingHandler().checkIndentation(firstNode, lastNode,
 360  
                 wrappedIndentLevel, startIndent,
 361  116
                 LineWrappingHandler.LineWrappingOptions.ofBoolean(ignoreFirstLine));
 362  116
     }
 363  
 
 364  
     /**
 365  
      * Check the indent level of the children of the specified parent
 366  
      * expression.
 367  
      *
 368  
      * @param parentNode         the parent whose children we are checking
 369  
      * @param tokenTypes         the token types to check
 370  
      * @param startIndent        the starting indent level
 371  
      * @param firstLineMatches   whether or not the first line needs to match
 372  
      * @param allowNesting       whether or not nested children are allowed
 373  
      */
 374  
     protected final void checkChildren(DetailAST parentNode,
 375  
                                        int[] tokenTypes,
 376  
                                        IndentLevel startIndent,
 377  
                                        boolean firstLineMatches,
 378  
                                        boolean allowNesting) {
 379  1106
         Arrays.sort(tokenTypes);
 380  1106
         for (DetailAST child = parentNode.getFirstChild();
 381  5164
                 child != null;
 382  4058
                 child = child.getNextSibling()) {
 383  4058
             if (Arrays.binarySearch(tokenTypes, child.getType()) >= 0) {
 384  1019
                 checkExpressionSubtree(child, startIndent,
 385  
                     firstLineMatches, allowNesting);
 386  
             }
 387  
         }
 388  1106
     }
 389  
 
 390  
     /**
 391  
      * Check the indentation level for an expression subtree.
 392  
      *
 393  
      * @param tree               the expression subtree to check
 394  
      * @param indentLevel        the indentation level
 395  
      * @param firstLineMatches   whether or not the first line has to match
 396  
      * @param allowNesting       whether or not subtree nesting is allowed
 397  
      */
 398  
     protected final void checkExpressionSubtree(
 399  
         DetailAST tree,
 400  
         IndentLevel indentLevel,
 401  
         boolean firstLineMatches,
 402  
         boolean allowNesting
 403  
     ) {
 404  2012
         final LineSet subtreeLines = new LineSet();
 405  2012
         final int firstLine = getFirstLine(Integer.MAX_VALUE, tree);
 406  2012
         if (firstLineMatches && !allowNesting) {
 407  1596
             subtreeLines.addLineAndCol(firstLine,
 408  798
                 getLineStart(indentCheck.getLine(firstLine - 1)));
 409  
         }
 410  2012
         findSubtreeLines(subtreeLines, tree, allowNesting);
 411  
 
 412  2012
         checkLinesIndent(subtreeLines, indentLevel, firstLineMatches, firstLine);
 413  2012
     }
 414  
 
 415  
     /**
 416  
      * Get the first line for a given expression.
 417  
      *
 418  
      * @param startLine   the line we are starting from
 419  
      * @param tree        the expression to find the first line for
 420  
      *
 421  
      * @return the first line of the expression
 422  
      */
 423  
     protected static int getFirstLine(int startLine, DetailAST tree) {
 424  17469
         int realStart = startLine;
 425  17469
         final int currLine = tree.getLineNo();
 426  17469
         if (currLine < realStart) {
 427  2224
             realStart = currLine;
 428  
         }
 429  
 
 430  
         // check children
 431  17469
         for (DetailAST node = tree.getFirstChild();
 432  32839
             node != null;
 433  15370
             node = node.getNextSibling()) {
 434  15370
             realStart = getFirstLine(realStart, node);
 435  
         }
 436  
 
 437  17469
         return realStart;
 438  
     }
 439  
 
 440  
     /**
 441  
      * Get the column number for the start of a given expression, expanding
 442  
      * tabs out into spaces in the process.
 443  
      *
 444  
      * @param ast   the expression to find the start of
 445  
      *
 446  
      * @return the column number for the start of the expression
 447  
      */
 448  
     protected final int expandedTabsColumnNo(DetailAST ast) {
 449  15830
         final String line =
 450  15830
             indentCheck.getLine(ast.getLineNo() - 1);
 451  
 
 452  31660
         return CommonUtils.lengthExpandedTabs(line, ast.getColumnNo(),
 453  15830
             indentCheck.getIndentationTabWidth());
 454  
     }
 455  
 
 456  
     /**
 457  
      * Find the set of lines for a given subtree.
 458  
      *
 459  
      * @param lines          the set of lines to add to
 460  
      * @param tree           the subtree to examine
 461  
      * @param allowNesting   whether or not to allow nested subtrees
 462  
      */
 463  
     protected final void findSubtreeLines(LineSet lines, DetailAST tree,
 464  
         boolean allowNesting) {
 465  6003
         if (!indentCheck.getHandlerFactory().isHandledType(tree.getType())) {
 466  5294
             final int lineNum = tree.getLineNo();
 467  5294
             final Integer colNum = lines.getStartColumn(lineNum);
 468  
 
 469  5294
             final int thisLineColumn = expandedTabsColumnNo(tree);
 470  5294
             if (colNum == null || thisLineColumn < colNum) {
 471  2229
                 lines.addLineAndCol(lineNum, thisLineColumn);
 472  
             }
 473  
 
 474  
             // check children
 475  5294
             for (DetailAST node = tree.getFirstChild();
 476  9016
                 node != null;
 477  3722
                 node = node.getNextSibling()) {
 478  3722
                 findSubtreeLines(lines, node, allowNesting);
 479  
             }
 480  
         }
 481  6003
     }
 482  
 
 483  
     /**
 484  
      * Check the indentation level of modifiers.
 485  
      */
 486  
     protected void checkModifiers() {
 487  124
         final DetailAST modifiers =
 488  124
             mainAst.findFirstToken(TokenTypes.MODIFIERS);
 489  124
         for (DetailAST modifier = modifiers.getFirstChild();
 490  296
              modifier != null;
 491  172
              modifier = modifier.getNextSibling()) {
 492  172
             if (isOnStartOfLine(modifier)
 493  153
                 && !getIndent().isAcceptable(expandedTabsColumnNo(modifier))) {
 494  20
                 logError(modifier, "modifier",
 495  10
                     expandedTabsColumnNo(modifier));
 496  
             }
 497  
         }
 498  124
     }
 499  
 
 500  
     /**
 501  
      * Accessor for the IndentCheck attribute.
 502  
      *
 503  
      * @return the IndentCheck attribute
 504  
      */
 505  
     protected final IndentationCheck getIndentCheck() {
 506  1039
         return indentCheck;
 507  
     }
 508  
 
 509  
     /**
 510  
      * Accessor for the MainAst attribute.
 511  
      *
 512  
      * @return the MainAst attribute
 513  
      */
 514  
     protected final DetailAST getMainAst() {
 515  26918
         return mainAst;
 516  
     }
 517  
 
 518  
     /**
 519  
      * Accessor for the Parent attribute.
 520  
      *
 521  
      * @return the Parent attribute
 522  
      */
 523  
     protected final AbstractExpressionHandler getParent() {
 524  3069
         return parent;
 525  
     }
 526  
 
 527  
     /**
 528  
      * A shortcut for {@code IndentationCheck} property.
 529  
      * @return value of basicOffset property of {@code IndentationCheck}
 530  
      */
 531  
     protected final int getBasicOffset() {
 532  4212
         return indentCheck.getBasicOffset();
 533  
     }
 534  
 
 535  
     /**
 536  
      * A shortcut for {@code IndentationCheck} property.
 537  
      * @return value of braceAdjustment property
 538  
      *         of {@code IndentationCheck}
 539  
      */
 540  
     protected final int getBraceAdjustment() {
 541  2570
         return indentCheck.getBraceAdjustment();
 542  
     }
 543  
 
 544  
     /**
 545  
      * Check the indentation of the right parenthesis.
 546  
      * @param rparen parenthesis to check
 547  
      * @param lparen left parenthesis associated with aRparen
 548  
      */
 549  
     protected final void checkRightParen(DetailAST lparen, DetailAST rparen) {
 550  1283
         if (rparen != null) {
 551  
             // the rcurly can either be at the correct indentation,
 552  
             // or not first on the line
 553  666
             final int rparenLevel = expandedTabsColumnNo(rparen);
 554  
             // or has <lparen level> + 1 indentation
 555  666
             final int lparenLevel = expandedTabsColumnNo(lparen);
 556  
 
 557  666
             if (rparenLevel != lparenLevel + 1
 558  480
                     && !getIndent().isAcceptable(rparenLevel)
 559  470
                     && isOnStartOfLine(rparen)) {
 560  10
                 logError(rparen, "rparen", rparenLevel);
 561  
             }
 562  
         }
 563  1283
     }
 564  
 
 565  
     /**
 566  
      * Check the indentation of the left parenthesis.
 567  
      * @param lparen parenthesis to check
 568  
      */
 569  
     protected final void checkLeftParen(final DetailAST lparen) {
 570  
         // the rcurly can either be at the correct indentation, or on the
 571  
         // same line as the lcurly
 572  1749
         if (lparen != null
 573  1054
                 && !getIndent().isAcceptable(expandedTabsColumnNo(lparen))
 574  1047
                 && isOnStartOfLine(lparen)) {
 575  4
             logError(lparen, "lparen", expandedTabsColumnNo(lparen));
 576  
         }
 577  1749
     }
 578  
 }