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