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