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 final int endLine = astSet.lastLine(); 279 int startCol = expandedTabsColumnNo(astSet.firstLine()); 280 281 final int realStartCol = 282 getLineStart(indentCheck.getLine(startLineAst.getLineNo() - 1)); 283 284 if (firstLineMatches && !allowNesting) { 285 startCol = realStartCol; 286 } 287 288 if (realStartCol == startCol) { 289 checkLineIndent(startLineAst, indentLevel, 290 firstLineMatches); 291 } 292 293 // if first line starts the line, following lines are indented 294 // one level; but if the first line of this expression is 295 // nested with the previous expression (which is assumed if it 296 // doesn't start the line) then don't indent more, the first 297 // indentation is absorbed by the nesting 298 299 IndentLevel theLevel = indentLevel; 300 if ((firstLineMatches || firstLine > mainAst.getLineNo()) 301 && shouldIncreaseIndent()) { 302 theLevel = new IndentLevel(indentLevel, indentCheck.getLineWrappingIndentation()); 303 } 304 305 // check following lines 306 for (int i = startLineAst.getLineNo() + 1; i <= endLine; i++) { 307 final Integer col = astSet.getStartColumn(i); 308 // startCol could be null if this line didn't have an 309 // expression that was required to be checked (it could be 310 // checked by a child expression) 311 312 if (col != null) { 313 checkLineIndent(astSet.getAst(i), theLevel, false); 314 } 315 } 316 } 317 } 318 319 /** 320 * Check the indentation for a single-line. 321 * 322 * @param ast the abstract syntax tree to check 323 * @param indentLevel the indentation level 324 * @param mustMatch whether or not the indentation level must match 325 */ 326 private void checkLineIndent(DetailAST ast, 327 IndentLevel indentLevel, boolean mustMatch) { 328 final String line = indentCheck.getLine(ast.getLineNo() - 1); 329 final int start = getLineStart(line); 330 final int columnNumber = expandedTabsColumnNo(ast); 331 // if must match is set, it is a violation if the line start is not 332 // at the correct indention level; otherwise, it is an only a 333 // violation if this statement starts the line and it is less than 334 // the correct indentation level 335 if (mustMatch && !indentLevel.isAcceptable(start) 336 || !mustMatch && columnNumber == start && indentLevel.isGreaterThan(start)) { 337 logChildError(ast, start, indentLevel); 338 } 339 } 340 341 /** 342 * Checks indentation on wrapped lines between and including 343 * {@code firstNode} and {@code lastNode}. 344 * 345 * @param firstNode First node to start examining. 346 * @param lastNode Last node to examine inclusively. 347 */ 348 protected void checkWrappingIndentation(DetailAST firstNode, DetailAST lastNode) { 349 indentCheck.getLineWrappingHandler().checkIndentation(firstNode, lastNode); 350 } 351 352 /** 353 * Checks indentation on wrapped lines between and including 354 * {@code firstNode} and {@code lastNode}. 355 * 356 * @param firstNode First node to start examining. 357 * @param lastNode Last node to examine inclusively. 358 * @param wrappedIndentLevel Indentation all wrapped lines should use. 359 * @param startIndent Indentation first line before wrapped lines used. 360 * @param ignoreFirstLine Test if first line's indentation should be checked or not. 361 */ 362 protected void checkWrappingIndentation(DetailAST firstNode, DetailAST lastNode, 363 int wrappedIndentLevel, int startIndent, boolean ignoreFirstLine) { 364 indentCheck.getLineWrappingHandler().checkIndentation(firstNode, lastNode, 365 wrappedIndentLevel, startIndent, 366 LineWrappingHandler.LineWrappingOptions.ofBoolean(ignoreFirstLine)); 367 } 368 369 /** 370 * Check the indent level of the children of the specified parent 371 * expression. 372 * 373 * @param parentNode the parent whose children we are checking 374 * @param tokenTypes the token types to check 375 * @param startIndent the starting indent level 376 * @param firstLineMatches whether or not the first line needs to match 377 * @param allowNesting whether or not nested children are allowed 378 */ 379 protected final void checkChildren(DetailAST parentNode, 380 int[] tokenTypes, 381 IndentLevel startIndent, 382 boolean firstLineMatches, 383 boolean allowNesting) { 384 Arrays.sort(tokenTypes); 385 for (DetailAST child = parentNode.getFirstChild(); 386 child != null; 387 child = child.getNextSibling()) { 388 if (Arrays.binarySearch(tokenTypes, child.getType()) >= 0) { 389 checkExpressionSubtree(child, startIndent, 390 firstLineMatches, allowNesting); 391 } 392 } 393 } 394 395 /** 396 * Check the indentation level for an expression subtree. 397 * 398 * @param tree the expression subtree to check 399 * @param indentLevel the indentation level 400 * @param firstLineMatches whether or not the first line has to match 401 * @param allowNesting whether or not subtree nesting is allowed 402 */ 403 protected final void checkExpressionSubtree( 404 DetailAST tree, 405 IndentLevel indentLevel, 406 boolean firstLineMatches, 407 boolean allowNesting 408 ) { 409 final DetailAstSet subtreeAst = new DetailAstSet(indentCheck); 410 final int firstLine = getFirstLine(tree); 411 if (firstLineMatches && !allowNesting) { 412 final DetailAST firstAst = getFirstAstNode(tree); 413 subtreeAst.addAst(firstAst); 414 } 415 findSubtreeAst(subtreeAst, tree, allowNesting); 416 417 checkLinesIndent(subtreeAst, indentLevel, firstLineMatches, firstLine, allowNesting); 418 } 419 420 /** 421 * Get the first line number for given expression. 422 * 423 * @param tree the expression to find the first line for 424 * @return the first line of expression 425 */ 426 protected static int getFirstLine(DetailAST tree) { 427 return getFirstAstNode(tree).getLineNo(); 428 } 429 430 /** 431 * Get the first ast for given expression. 432 * 433 * @param ast the abstract syntax tree for which the starting ast is to be found 434 * 435 * @return the first ast of the expression 436 */ 437 protected static DetailAST getFirstAstNode(DetailAST ast) { 438 439 DetailAST curNode = ast; 440 DetailAST realStart = ast; 441 while (curNode != null) { 442 if (curNode.getLineNo() < realStart.getLineNo() 443 || curNode.getLineNo() == realStart.getLineNo() 444 && curNode.getColumnNo() < realStart.getColumnNo()) { 445 realStart = curNode; 446 } 447 DetailAST toVisit = curNode.getFirstChild(); 448 while (curNode != ast && toVisit == null) { 449 toVisit = curNode.getNextSibling(); 450 curNode = curNode.getParent(); 451 } 452 curNode = toVisit; 453 } 454 return realStart; 455 } 456 457 /** 458 * Get the column number for the start of a given expression, expanding 459 * tabs out into spaces in the process. 460 * 461 * @param ast the expression to find the start of 462 * 463 * @return the column number for the start of the expression 464 */ 465 protected final int expandedTabsColumnNo(DetailAST ast) { 466 final String line = 467 indentCheck.getLine(ast.getLineNo() - 1); 468 469 return CommonUtil.lengthExpandedTabs(line, ast.getColumnNo(), 470 indentCheck.getIndentationTabWidth()); 471 } 472 473 /** 474 * Find the set of abstract syntax tree for a given subtree. 475 * 476 * @param astSet the set of ast to add 477 * @param tree the subtree to examine 478 * @param allowNesting whether or not to allow nested subtrees 479 */ 480 protected final void findSubtreeAst(DetailAstSet astSet, DetailAST tree, 481 boolean allowNesting) { 482 if (!indentCheck.getHandlerFactory().isHandledType(tree.getType())) { 483 final int lineNum = tree.getLineNo(); 484 final Integer colNum = astSet.getStartColumn(lineNum); 485 486 final int thisLineColumn = expandedTabsColumnNo(tree); 487 if (colNum == null || thisLineColumn < colNum) { 488 astSet.addAst(tree); 489 } 490 491 // check children 492 for (DetailAST node = tree.getFirstChild(); 493 node != null; 494 node = node.getNextSibling()) { 495 findSubtreeAst(astSet, node, allowNesting); 496 } 497 } 498 } 499 500 /** 501 * Check the indentation level of modifiers. 502 */ 503 protected void checkModifiers() { 504 final DetailAST modifiers = 505 mainAst.findFirstToken(TokenTypes.MODIFIERS); 506 for (DetailAST modifier = modifiers.getFirstChild(); 507 modifier != null; 508 modifier = modifier.getNextSibling()) { 509 if (isOnStartOfLine(modifier) 510 && !getIndent().isAcceptable(expandedTabsColumnNo(modifier))) { 511 logError(modifier, "modifier", 512 expandedTabsColumnNo(modifier)); 513 } 514 } 515 } 516 517 /** 518 * Accessor for the IndentCheck attribute. 519 * 520 * @return the IndentCheck attribute 521 */ 522 protected final IndentationCheck getIndentCheck() { 523 return indentCheck; 524 } 525 526 /** 527 * Accessor for the MainAst attribute. 528 * 529 * @return the MainAst attribute 530 */ 531 protected final DetailAST getMainAst() { 532 return mainAst; 533 } 534 535 /** 536 * Accessor for the Parent attribute. 537 * 538 * @return the Parent attribute 539 */ 540 protected final AbstractExpressionHandler getParent() { 541 return parent; 542 } 543 544 /** 545 * A shortcut for {@code IndentationCheck} property. 546 * 547 * @return value of basicOffset property of {@code IndentationCheck} 548 */ 549 protected final int getBasicOffset() { 550 return indentCheck.getBasicOffset(); 551 } 552 553 /** 554 * A shortcut for {@code IndentationCheck} property. 555 * 556 * @return value of braceAdjustment property 557 * of {@code IndentationCheck} 558 */ 559 protected final int getBraceAdjustment() { 560 return indentCheck.getBraceAdjustment(); 561 } 562 563 /** 564 * Check the indentation of the right parenthesis. 565 * 566 * @param lparen left parenthesis associated with aRparen 567 * @param rparen parenthesis to check 568 */ 569 protected final void checkRightParen(DetailAST lparen, DetailAST rparen) { 570 if (rparen != null) { 571 // the rcurly can either be at the correct indentation, 572 // or not first on the line 573 final int rparenLevel = expandedTabsColumnNo(rparen); 574 // or has <lparen level> + 1 indentation 575 final int lparenLevel = expandedTabsColumnNo(lparen); 576 577 if (rparenLevel != lparenLevel + 1 578 && !getIndent().isAcceptable(rparenLevel) 579 && isOnStartOfLine(rparen)) { 580 logError(rparen, "rparen", rparenLevel); 581 } 582 } 583 } 584 585 /** 586 * Check the indentation of the left parenthesis. 587 * 588 * @param lparen parenthesis to check 589 */ 590 protected final void checkLeftParen(final DetailAST lparen) { 591 // the rcurly can either be at the correct indentation, or on the 592 // same line as the lcurly 593 if (lparen != null 594 && !getIndent().isAcceptable(expandedTabsColumnNo(lparen)) 595 && isOnStartOfLine(lparen)) { 596 logError(lparen, "lparen", expandedTabsColumnNo(lparen)); 597 } 598 } 599 600}