001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code for adherence to a set of rules. 003// Copyright (C) 2001-2020 the original author or authors. 004// 005// This library is free software; you can redistribute it and/or 006// modify it under the terms of the GNU Lesser General Public 007// License as published by the Free Software Foundation; either 008// version 2.1 of the License, or (at your option) any later version. 009// 010// This library is distributed in the hope that it will be useful, 011// but WITHOUT ANY WARRANTY; without even the implied warranty of 012// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 013// Lesser General Public License for more details. 014// 015// You should have received a copy of the GNU Lesser General Public 016// License along with this library; if not, write to the Free Software 017// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 018//////////////////////////////////////////////////////////////////////////////// 019 020package com.puppycrawl.tools.checkstyle.checks.indentation; 021 022import java.util.Arrays; 023 024import com.puppycrawl.tools.checkstyle.api.DetailAST; 025import com.puppycrawl.tools.checkstyle.api.TokenTypes; 026import com.puppycrawl.tools.checkstyle.utils.CommonUtil; 027 028/** 029 * Abstract base class for all handlers. 030 * 031 */ 032public abstract class AbstractExpressionHandler { 033 034 /** 035 * The instance of {@code IndentationCheck} using this handler. 036 */ 037 private final IndentationCheck indentCheck; 038 039 /** The AST which is handled by this handler. */ 040 private final DetailAST mainAst; 041 042 /** Name used during output to user. */ 043 private final String typeName; 044 045 /** Containing AST handler. */ 046 private final AbstractExpressionHandler parent; 047 048 /** Indentation amount for this handler. */ 049 private IndentLevel indent; 050 051 /** 052 * Construct an instance of this handler with the given indentation check, 053 * name, abstract syntax tree, and parent handler. 054 * 055 * @param indentCheck the indentation check 056 * @param typeName the name of the handler 057 * @param expr the abstract syntax tree 058 * @param parent the parent handler 059 */ 060 protected AbstractExpressionHandler(IndentationCheck indentCheck, String typeName, 061 DetailAST expr, AbstractExpressionHandler parent) { 062 this.indentCheck = indentCheck; 063 this.typeName = typeName; 064 mainAst = expr; 065 this.parent = parent; 066 } 067 068 /** 069 * Check the indentation of the expression we are handling. 070 */ 071 public abstract void checkIndentation(); 072 073 /** 074 * Get the indentation amount for this handler. For performance reasons, 075 * this value is cached. The first time this method is called, the 076 * indentation amount is computed and stored. On further calls, the stored 077 * value is returned. 078 * 079 * @return the expected indentation amount 080 * @noinspection WeakerAccess 081 */ 082 public final IndentLevel getIndent() { 083 if (indent == null) { 084 indent = getIndentImpl(); 085 } 086 return indent; 087 } 088 089 /** 090 * Compute the indentation amount for this handler. 091 * 092 * @return the expected indentation amount 093 */ 094 protected IndentLevel getIndentImpl() { 095 return parent.getSuggestedChildIndent(this); 096 } 097 098 /** 099 * 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 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 logError(ast, subtypeName, actualIndent, getIndent()); 122 } 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 if (subtypeName.isEmpty()) { 137 typeStr = ""; 138 } 139 else { 140 typeStr = " " + subtypeName; 141 } 142 String messageKey = IndentationCheck.MSG_ERROR; 143 if (expectedIndent.isMultiLevel()) { 144 messageKey = IndentationCheck.MSG_ERROR_MULTI; 145 } 146 indentCheck.indentationLog(ast, messageKey, 147 typeName + typeStr, actualIndent, expectedIndent); 148 } 149 150 /** 151 * Log child indentation error. 152 * 153 * @param ast the abstract syntax tree that causes 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(DetailAST ast, 158 int actualIndent, 159 IndentLevel expectedIndent) { 160 String messageKey = IndentationCheck.MSG_CHILD_ERROR; 161 if (expectedIndent.isMultiLevel()) { 162 messageKey = IndentationCheck.MSG_CHILD_ERROR_MULTI; 163 } 164 indentCheck.indentationLog(ast, messageKey, 165 typeName, actualIndent, expectedIndent); 166 } 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 return getLineStart(ast) == expandedTabsColumnNo(ast); 177 } 178 179 /** 180 * Searches in given sub-tree (including given node) for the token 181 * which represents first symbol for this sub-tree in file. 182 * 183 * @param ast a root of sub-tree in which the search should be performed. 184 * @return a token which occurs first in the file. 185 * @noinspection WeakerAccess 186 */ 187 public static DetailAST getFirstToken(DetailAST ast) { 188 DetailAST first = ast; 189 DetailAST child = ast.getFirstChild(); 190 191 while (child != null) { 192 final DetailAST toTest = getFirstToken(child); 193 if (toTest.getColumnNo() < first.getColumnNo()) { 194 first = toTest; 195 } 196 child = child.getNextSibling(); 197 } 198 199 return first; 200 } 201 202 /** 203 * Get the start of the line for the given expression. 204 * 205 * @param ast the expression to find the start of the line for 206 * 207 * @return the start of the line for the given expression 208 */ 209 protected final int getLineStart(DetailAST ast) { 210 return getLineStart(ast.getLineNo()); 211 } 212 213 /** 214 * Get the start of the line for the given line number. 215 * 216 * @param lineNo the line number to find the start for 217 * 218 * @return the start of the line for the given expression 219 */ 220 protected final int getLineStart(int lineNo) { 221 return getLineStart(indentCheck.getLine(lineNo - 1)); 222 } 223 224 /** 225 * Get the start of the specified line. 226 * 227 * @param line the specified line number 228 * 229 * @return the start of the specified line 230 */ 231 private int getLineStart(String line) { 232 int index = 0; 233 while (Character.isWhitespace(line.charAt(index))) { 234 index++; 235 } 236 return CommonUtil.lengthExpandedTabs( 237 line, index, indentCheck.getIndentationTabWidth()); 238 } 239 240 /** 241 * Checks that indentation should be increased after first line in checkLinesIndent(). 242 * 243 * @return true if indentation should be increased after 244 * first line in checkLinesIndent() 245 * false otherwise 246 */ 247 protected boolean shouldIncreaseIndent() { 248 return true; 249 } 250 251 /** 252 * Check the indentation for a set of lines. 253 * 254 * @param astSet the set of abstract syntax tree to check 255 * @param indentLevel the indentation level 256 * @param firstLineMatches whether or not the first line has to match 257 * @param firstLine first line of whole expression 258 * @param allowNesting whether or not subtree nesting is allowed 259 */ 260 private void checkLinesIndent(DetailAstSet astSet, 261 IndentLevel indentLevel, 262 boolean firstLineMatches, 263 int firstLine, 264 boolean allowNesting) { 265 if (!astSet.isEmpty()) { 266 // check first line 267 final DetailAST startLineAst = astSet.firstLine(); 268 final int endLine = astSet.lastLine(); 269 int startCol = expandedTabsColumnNo(astSet.firstLine()); 270 271 final int realStartCol = 272 getLineStart(indentCheck.getLine(startLineAst.getLineNo() - 1)); 273 274 if (firstLineMatches && !allowNesting) { 275 startCol = realStartCol; 276 } 277 278 if (realStartCol == startCol) { 279 checkLineIndent(startLineAst, indentLevel, 280 firstLineMatches); 281 } 282 283 // if first line starts the line, following lines are indented 284 // one level; but if the first line of this expression is 285 // nested with the previous expression (which is assumed if it 286 // doesn't start the line) then don't indent more, the first 287 // indentation is absorbed by the nesting 288 289 IndentLevel theLevel = indentLevel; 290 if (firstLineMatches 291 || firstLine > mainAst.getLineNo() && shouldIncreaseIndent()) { 292 theLevel = new IndentLevel(indentLevel, getBasicOffset()); 293 } 294 295 // check following lines 296 for (int i = startLineAst.getLineNo() + 1; i <= endLine; i++) { 297 final Integer col = astSet.getStartColumn(i); 298 // startCol could be null if this line didn't have an 299 // expression that was required to be checked (it could be 300 // checked by a child expression) 301 302 if (col != null) { 303 checkLineIndent(astSet.getAst(i), theLevel, false); 304 } 305 } 306 } 307 } 308 309 /** 310 * Check the indentation for a single line. 311 * 312 * @param ast the abstract syntax tree to check 313 * @param indentLevel the indentation level 314 * @param mustMatch whether or not the indentation level must match 315 */ 316 private void checkLineIndent(DetailAST ast, 317 IndentLevel indentLevel, boolean mustMatch) { 318 final String line = indentCheck.getLine(ast.getLineNo() - 1); 319 final int start = getLineStart(line); 320 final int columnNumber = expandedTabsColumnNo(ast); 321 // if must match is set, it is a violation if the line start is not 322 // at the correct indention level; otherwise, it is an only an 323 // violation if this statement starts the line and it is less than 324 // the correct indentation level 325 if (mustMatch && !indentLevel.isAcceptable(start) 326 || !mustMatch && columnNumber == start && indentLevel.isGreaterThan(start)) { 327 logChildError(ast, start, indentLevel); 328 } 329 } 330 331 /** 332 * Checks indentation on wrapped lines between and including 333 * {@code firstNode} and {@code lastNode}. 334 * 335 * @param firstNode First node to start examining. 336 * @param lastNode Last node to examine inclusively. 337 */ 338 protected void checkWrappingIndentation(DetailAST firstNode, DetailAST lastNode) { 339 indentCheck.getLineWrappingHandler().checkIndentation(firstNode, lastNode); 340 } 341 342 /** 343 * Checks indentation on wrapped lines between and including 344 * {@code firstNode} and {@code lastNode}. 345 * 346 * @param firstNode First node to start examining. 347 * @param lastNode Last node to examine inclusively. 348 * @param wrappedIndentLevel Indentation all wrapped lines should use. 349 * @param startIndent Indentation first line before wrapped lines used. 350 * @param ignoreFirstLine Test if first line's indentation should be checked or not. 351 */ 352 protected void checkWrappingIndentation(DetailAST firstNode, DetailAST lastNode, 353 int wrappedIndentLevel, int startIndent, boolean ignoreFirstLine) { 354 indentCheck.getLineWrappingHandler().checkIndentation(firstNode, lastNode, 355 wrappedIndentLevel, startIndent, 356 LineWrappingHandler.LineWrappingOptions.ofBoolean(ignoreFirstLine)); 357 } 358 359 /** 360 * Check the indent level of the children of the specified parent 361 * expression. 362 * 363 * @param parentNode the parent whose children we are checking 364 * @param tokenTypes the token types to check 365 * @param startIndent the starting indent level 366 * @param firstLineMatches whether or not the first line needs to match 367 * @param allowNesting whether or not nested children are allowed 368 */ 369 protected final void checkChildren(DetailAST parentNode, 370 int[] tokenTypes, 371 IndentLevel startIndent, 372 boolean firstLineMatches, 373 boolean allowNesting) { 374 Arrays.sort(tokenTypes); 375 for (DetailAST child = parentNode.getFirstChild(); 376 child != null; 377 child = child.getNextSibling()) { 378 if (Arrays.binarySearch(tokenTypes, child.getType()) >= 0) { 379 checkExpressionSubtree(child, startIndent, 380 firstLineMatches, allowNesting); 381 } 382 } 383 } 384 385 /** 386 * Check the indentation level for an expression subtree. 387 * 388 * @param tree the expression subtree to check 389 * @param indentLevel the indentation level 390 * @param firstLineMatches whether or not the first line has to match 391 * @param allowNesting whether or not subtree nesting is allowed 392 */ 393 protected final void checkExpressionSubtree( 394 DetailAST tree, 395 IndentLevel indentLevel, 396 boolean firstLineMatches, 397 boolean allowNesting 398 ) { 399 final DetailAstSet subtreeAst = new DetailAstSet(indentCheck); 400 final int firstLine = getFirstLine(tree); 401 if (firstLineMatches && !allowNesting) { 402 final DetailAST firstAst = getFirstAst(tree, tree); 403 subtreeAst.addAst(firstAst); 404 } 405 findSubtreeAst(subtreeAst, tree, allowNesting); 406 407 checkLinesIndent(subtreeAst, indentLevel, firstLineMatches, firstLine, allowNesting); 408 } 409 410 /** 411 * Get the first line number for given expression. 412 * 413 * @param tree the expression to find the first line for 414 * @return the first line of expression 415 */ 416 protected static int getFirstLine(DetailAST tree) { 417 return getFirstAst(tree, tree).getLineNo(); 418 } 419 420 /** 421 * Get the first ast for given expression. 422 * 423 * @param ast the current ast that may have minimum line number 424 * @param tree the expression to find the first line for 425 * 426 * @return the first ast of the expression 427 */ 428 protected static DetailAST getFirstAst(DetailAST ast, DetailAST tree) { 429 DetailAST realStart = ast; 430 431 if (tree.getLineNo() < realStart.getLineNo() 432 || tree.getLineNo() == realStart.getLineNo() 433 && tree.getColumnNo() < realStart.getColumnNo() 434 ) { 435 realStart = tree; 436 } 437 438 // check children 439 for (DetailAST node = tree.getFirstChild(); 440 node != null; 441 node = node.getNextSibling()) { 442 realStart = getFirstAst(realStart, node); 443 } 444 445 return realStart; 446 } 447 448 /** 449 * Get the column number for the start of a given expression, expanding 450 * tabs out into spaces in the process. 451 * 452 * @param ast the expression to find the start of 453 * 454 * @return the column number for the start of the expression 455 */ 456 protected final int expandedTabsColumnNo(DetailAST ast) { 457 final String line = 458 indentCheck.getLine(ast.getLineNo() - 1); 459 460 return CommonUtil.lengthExpandedTabs(line, ast.getColumnNo(), 461 indentCheck.getIndentationTabWidth()); 462 } 463 464 /** 465 * Find the set of abstract syntax tree for a given subtree. 466 * 467 * @param astSet the set of ast to add 468 * @param tree the subtree to examine 469 * @param allowNesting whether or not to allow nested subtrees 470 */ 471 protected final void findSubtreeAst(DetailAstSet astSet, DetailAST tree, 472 boolean allowNesting) { 473 if (!indentCheck.getHandlerFactory().isHandledType(tree.getType())) { 474 final int lineNum = tree.getLineNo(); 475 final Integer colNum = astSet.getStartColumn(lineNum); 476 477 final int thisLineColumn = expandedTabsColumnNo(tree); 478 if (colNum == null || thisLineColumn < colNum) { 479 astSet.addAst(tree); 480 } 481 482 // check children 483 for (DetailAST node = tree.getFirstChild(); 484 node != null; 485 node = node.getNextSibling()) { 486 findSubtreeAst(astSet, node, allowNesting); 487 } 488 } 489 } 490 491 /** 492 * Check the indentation level of modifiers. 493 */ 494 protected void checkModifiers() { 495 final DetailAST modifiers = 496 mainAst.findFirstToken(TokenTypes.MODIFIERS); 497 for (DetailAST modifier = modifiers.getFirstChild(); 498 modifier != null; 499 modifier = modifier.getNextSibling()) { 500 if (isOnStartOfLine(modifier) 501 && !getIndent().isAcceptable(expandedTabsColumnNo(modifier))) { 502 logError(modifier, "modifier", 503 expandedTabsColumnNo(modifier)); 504 } 505 } 506 } 507 508 /** 509 * Accessor for the IndentCheck attribute. 510 * 511 * @return the IndentCheck attribute 512 */ 513 protected final IndentationCheck getIndentCheck() { 514 return indentCheck; 515 } 516 517 /** 518 * Accessor for the MainAst attribute. 519 * 520 * @return the MainAst attribute 521 */ 522 protected final DetailAST getMainAst() { 523 return mainAst; 524 } 525 526 /** 527 * Accessor for the Parent attribute. 528 * 529 * @return the Parent attribute 530 */ 531 protected final AbstractExpressionHandler getParent() { 532 return parent; 533 } 534 535 /** 536 * A shortcut for {@code IndentationCheck} property. 537 * 538 * @return value of basicOffset property of {@code IndentationCheck} 539 */ 540 protected final int getBasicOffset() { 541 return indentCheck.getBasicOffset(); 542 } 543 544 /** 545 * A shortcut for {@code IndentationCheck} property. 546 * 547 * @return value of braceAdjustment property 548 * of {@code IndentationCheck} 549 */ 550 protected final int getBraceAdjustment() { 551 return indentCheck.getBraceAdjustment(); 552 } 553 554 /** 555 * Check the indentation of the right parenthesis. 556 * 557 * @param rparen parenthesis to check 558 * @param lparen left parenthesis associated with aRparen 559 */ 560 protected final void checkRightParen(DetailAST lparen, DetailAST rparen) { 561 if (rparen != null) { 562 // the rcurly can either be at the correct indentation, 563 // or not first on the line 564 final int rparenLevel = expandedTabsColumnNo(rparen); 565 // or has <lparen level> + 1 indentation 566 final int lparenLevel = expandedTabsColumnNo(lparen); 567 568 if (rparenLevel != lparenLevel + 1 569 && !getIndent().isAcceptable(rparenLevel) 570 && isOnStartOfLine(rparen)) { 571 logError(rparen, "rparen", rparenLevel); 572 } 573 } 574 } 575 576 /** 577 * Check the indentation of the left parenthesis. 578 * 579 * @param lparen parenthesis to check 580 */ 581 protected final void checkLeftParen(final DetailAST lparen) { 582 // the rcurly can either be at the correct indentation, or on the 583 // same line as the lcurly 584 if (lparen != null 585 && !getIndent().isAcceptable(expandedTabsColumnNo(lparen)) 586 && isOnStartOfLine(lparen)) { 587 logError(lparen, "lparen", expandedTabsColumnNo(lparen)); 588 } 589 } 590 591}