001/////////////////////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code and other text files for adherence to a set of rules. 003// Copyright (C) 2001-2023 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.ArrayDeque; 023import java.util.Deque; 024import java.util.HashSet; 025import java.util.Set; 026 027import com.puppycrawl.tools.checkstyle.FileStatefulCheck; 028import com.puppycrawl.tools.checkstyle.api.AbstractCheck; 029import com.puppycrawl.tools.checkstyle.api.DetailAST; 030 031/** 032 * <p> 033 * Checks correct indentation of Java code. 034 * </p> 035 * <p> 036 * The idea behind this is that while 037 * pretty printers are sometimes convenient for bulk reformats of 038 * legacy code, they often either aren't configurable enough or 039 * just can't anticipate how format should be done. Sometimes this is 040 * personal preference, other times it is practical experience. In any 041 * case, this check should just ensure that a minimal set of indentation 042 * rules is followed. 043 * </p> 044 * <p> 045 * Basic offset indentation is used for indentation inside code blocks. 046 * For any lines that span more than 1, line wrapping indentation is used for those lines 047 * after the first. Brace adjustment, case, and throws indentations are all used only if 048 * those specific identifiers start the line. If, for example, a brace is used in the 049 * middle of the line, its indentation will not take effect. All indentations have an 050 * accumulative/recursive effect when they are triggered. If during a line wrapping, another 051 * code block is found and it doesn't end on that same line, then the subsequent lines 052 * afterwards, in that new code block, are increased on top of the line wrap and any 053 * indentations above it. 054 * </p> 055 * <p> 056 * Example: 057 * </p> 058 * <pre> 059 * if ((condition1 && condition2) 060 * || (condition3 && condition4) // line wrap with bigger indentation 061 * ||!(condition5 && condition6)) { // line wrap with bigger indentation 062 * field.doSomething() // basic offset 063 * .doSomething() // line wrap 064 * .doSomething( c -> { // line wrap 065 * return c.doSome(); // basic offset 066 * }); 067 * } 068 * </pre> 069 * <ul> 070 * <li> 071 * Property {@code basicOffset} - Specify how far new indentation level should be 072 * indented when on the next line. 073 * Type is {@code int}. 074 * Default value is {@code 4}. 075 * </li> 076 * <li> 077 * Property {@code braceAdjustment} - Specify how far a braces should be indented 078 * when on the next line. 079 * Type is {@code int}. 080 * Default value is {@code 0}. 081 * </li> 082 * <li> 083 * Property {@code caseIndent} - Specify how far a case label should be indented 084 * when on next line. 085 * Type is {@code int}. 086 * Default value is {@code 4}. 087 * </li> 088 * <li> 089 * Property {@code throwsIndent} - Specify how far a throws clause should be 090 * indented when on next line. 091 * Type is {@code int}. 092 * Default value is {@code 4}. 093 * </li> 094 * <li> 095 * Property {@code arrayInitIndent} - Specify how far an array initialisation 096 * should be indented when on next line. 097 * Type is {@code int}. 098 * Default value is {@code 4}. 099 * </li> 100 * <li> 101 * Property {@code lineWrappingIndentation} - Specify how far continuation line 102 * should be indented when line-wrapping is present. 103 * Type is {@code int}. 104 * Default value is {@code 4}. 105 * </li> 106 * <li> 107 * Property {@code forceStrictCondition} - Force strict indent level in line 108 * wrapping case. If value is true, line wrap indent have to be same as 109 * lineWrappingIndentation parameter. If value is false, line wrap indent 110 * could be bigger on any value user would like. 111 * Type is {@code boolean}. 112 * Default value is {@code false}. 113 * </li> 114 * </ul> 115 * <p> 116 * To configure the default check: 117 * </p> 118 * <pre> 119 * <module name="Indentation"/> 120 * </pre> 121 * <p> 122 * Example of Compliant code for default configuration (in comment name of property 123 * that controls indentations): 124 * </p> 125 * <pre> 126 * class Test { 127 * String field; // basicOffset 128 * int[] arr = { // basicOffset 129 * 5, // arrayInitIndent 130 * 6 }; // arrayInitIndent 131 * void bar() throws Exception // basicOffset 132 * { // braceAdjustment 133 * foo(); // basicOffset 134 * } // braceAdjustment 135 * void foo() { // basicOffset 136 * if ((cond1 && cond2) // basicOffset 137 * || (cond3 && cond4) // lineWrappingIndentation, forceStrictCondition 138 * ||!(cond5 && cond6)) { // lineWrappingIndentation, forceStrictCondition 139 * field.doSomething() // basicOffset 140 * .doSomething() // lineWrappingIndentation and forceStrictCondition 141 * .doSomething( c -> { // lineWrappingIndentation and forceStrictCondition 142 * return c.doSome(); // basicOffset 143 * }); 144 * } 145 * } 146 * void fooCase() // basicOffset 147 * throws Exception { // throwsIndent 148 * switch (field) { // basicOffset 149 * case "value" : bar(); // caseIndent 150 * } 151 * } 152 * } 153 * </pre> 154 * <p> 155 * To configure the check to enforce the indentation style recommended by Oracle: 156 * </p> 157 * <pre> 158 * <module name="Indentation"> 159 * <property name="caseIndent" value="0"/> 160 * </module> 161 * </pre> 162 * <p> 163 * Example of Compliant code for default configuration (in comment name of property that controls 164 * indentation): 165 * </p> 166 * <pre> 167 * void fooCase() { // basicOffset 168 * switch (field) { // basicOffset 169 * case "value" : bar(); // caseIndent 170 * } 171 * } 172 * </pre> 173 * <p> 174 * To configure the Check to enforce strict condition in line-wrapping validation. 175 * </p> 176 * <pre> 177 * <module name="Indentation"> 178 * <property name="forceStrictCondition" value="true"/> 179 * </module> 180 * </pre> 181 * <p> 182 * Such config doesn't allow next cases even code is aligned further to the right for better 183 * reading: 184 * </p> 185 * <pre> 186 * void foo(String aFooString, 187 * int aFooInt) { // indent:8 ; expected: 4; violation, because 8 != 4 188 * if (cond1 189 * || cond2) { 190 * field.doSomething() 191 * .doSomething(); 192 * } 193 * if ((cond1 && cond2) 194 * || (cond3 && cond4) // violation 195 * ||!(cond5 && cond6)) { // violation 196 * field.doSomething() 197 * .doSomething() // violation 198 * .doSomething( c -> { // violation 199 * return c.doSome(); 200 * }); 201 * } 202 * } 203 * </pre> 204 * <p> 205 * But if forceStrictCondition = false, this code is valid: 206 * </p> 207 * <pre> 208 * void foo(String aFooString, 209 * int aFooInt) { // indent:8 ; expected: > 4; ok, because 8 > 4 210 * if (cond1 211 * || cond2) { 212 * field.doSomething() 213 * .doSomething(); 214 * } 215 * if ((cond1 && cond2) 216 * || (cond3 && cond4) 217 * ||!(cond5 && cond6)) { 218 * field.doSomething() 219 * .doSomething() 220 * .doSomething( c -> { 221 * return c.doSome(); 222 * }); 223 * } 224 * } 225 * </pre> 226 * 227 * <p> 228 * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker} 229 * </p> 230 * <p> 231 * Violation Message Keys: 232 * </p> 233 * <ul> 234 * <li> 235 * {@code indentation.child.error} 236 * </li> 237 * <li> 238 * {@code indentation.child.error.multi} 239 * </li> 240 * <li> 241 * {@code indentation.error} 242 * </li> 243 * <li> 244 * {@code indentation.error.multi} 245 * </li> 246 * </ul> 247 * 248 * @noinspection ThisEscapedInObjectConstruction 249 * @noinspectionreason ThisEscapedInObjectConstruction - class is instantiated in handlers 250 * @since 3.1 251 */ 252@FileStatefulCheck 253public class IndentationCheck extends AbstractCheck { 254 255 /* -- Implementation -- 256 * 257 * Basically, this check requests visitation for all handled token 258 * types (those tokens registered in the HandlerFactory). When visitToken 259 * is called, a new ExpressionHandler is created for the AST and pushed 260 * onto the handlers stack. The new handler then checks the indentation 261 * for the currently visiting AST. When leaveToken is called, the 262 * ExpressionHandler is popped from the stack. 263 * 264 * While on the stack the ExpressionHandler can be queried for the 265 * indentation level it suggests for children as well as for other 266 * values. 267 * 268 * While an ExpressionHandler checks the indentation level of its own 269 * AST, it typically also checks surrounding ASTs. For instance, a 270 * while loop handler checks the while loop as well as the braces 271 * and immediate children. 272 * 273 * - handler class -to-> ID mapping kept in Map 274 * - parent passed in during construction 275 * - suggest child indent level 276 * - allows for some tokens to be on same line (ie inner classes OBJBLOCK) 277 * and not increase indentation level 278 * - looked at using double dispatch for getSuggestedChildIndent(), but it 279 * doesn't seem worthwhile, at least now 280 * - both tabs and spaces are considered whitespace in front of the line... 281 * tabs are converted to spaces 282 * - block parents with parens -- for, while, if, etc... -- are checked that 283 * they match the level of the parent 284 */ 285 286 /** 287 * A key is pointing to the warning message text in "messages.properties" 288 * file. 289 */ 290 public static final String MSG_ERROR = "indentation.error"; 291 292 /** 293 * A key is pointing to the warning message text in "messages.properties" 294 * file. 295 */ 296 public static final String MSG_ERROR_MULTI = "indentation.error.multi"; 297 298 /** 299 * A key is pointing to the warning message text in "messages.properties" 300 * file. 301 */ 302 public static final String MSG_CHILD_ERROR = "indentation.child.error"; 303 304 /** 305 * A key is pointing to the warning message text in "messages.properties" 306 * file. 307 */ 308 public static final String MSG_CHILD_ERROR_MULTI = "indentation.child.error.multi"; 309 310 /** Default indentation amount - based on Sun. */ 311 private static final int DEFAULT_INDENTATION = 4; 312 313 /** Handlers currently in use. */ 314 private final Deque<AbstractExpressionHandler> handlers = new ArrayDeque<>(); 315 316 /** Instance of line wrapping handler to use. */ 317 private final LineWrappingHandler lineWrappingHandler = new LineWrappingHandler(this); 318 319 /** Factory from which handlers are distributed. */ 320 private final HandlerFactory handlerFactory = new HandlerFactory(); 321 322 /** Lines logged as having incorrect indentation. */ 323 private Set<Integer> incorrectIndentationLines; 324 325 /** Specify how far new indentation level should be indented when on the next line. */ 326 private int basicOffset = DEFAULT_INDENTATION; 327 328 /** Specify how far a case label should be indented when on next line. */ 329 private int caseIndent = DEFAULT_INDENTATION; 330 331 /** Specify how far a braces should be indented when on the next line. */ 332 private int braceAdjustment; 333 334 /** Specify how far a throws clause should be indented when on next line. */ 335 private int throwsIndent = DEFAULT_INDENTATION; 336 337 /** Specify how far an array initialisation should be indented when on next line. */ 338 private int arrayInitIndent = DEFAULT_INDENTATION; 339 340 /** Specify how far continuation line should be indented when line-wrapping is present. */ 341 private int lineWrappingIndentation = DEFAULT_INDENTATION; 342 343 /** 344 * Force strict indent level in line wrapping case. If value is true, line wrap indent 345 * have to be same as lineWrappingIndentation parameter. If value is false, line wrap indent 346 * could be bigger on any value user would like. 347 */ 348 private boolean forceStrictCondition; 349 350 /** 351 * Getter to query strict indent level in line wrapping case. If value is true, line wrap indent 352 * have to be same as lineWrappingIndentation parameter. If value is false, line wrap indent 353 * could be bigger on any value user would like. 354 * 355 * @return forceStrictCondition value. 356 */ 357 public boolean isForceStrictCondition() { 358 return forceStrictCondition; 359 } 360 361 /** 362 * Setter to force strict indent level in line wrapping case. If value is true, line wrap indent 363 * have to be same as lineWrappingIndentation parameter. If value is false, line wrap indent 364 * could be bigger on any value user would like. 365 * 366 * @param value user's value of forceStrictCondition. 367 */ 368 public void setForceStrictCondition(boolean value) { 369 forceStrictCondition = value; 370 } 371 372 /** 373 * Setter to specify how far new indentation level should be indented when on the next line. 374 * 375 * @param basicOffset the number of tabs or spaces to indent 376 */ 377 public void setBasicOffset(int basicOffset) { 378 this.basicOffset = basicOffset; 379 } 380 381 /** 382 * Getter to query how far new indentation level should be indented when on the next line. 383 * 384 * @return the number of tabs or spaces to indent 385 */ 386 public int getBasicOffset() { 387 return basicOffset; 388 } 389 390 /** 391 * Setter to specify how far a braces should be indented when on the next line. 392 * 393 * @param adjustmentAmount the brace offset 394 */ 395 public void setBraceAdjustment(int adjustmentAmount) { 396 braceAdjustment = adjustmentAmount; 397 } 398 399 /** 400 * Getter to query how far a braces should be indented when on the next line. 401 * 402 * @return the positive offset to adjust braces 403 */ 404 public int getBraceAdjustment() { 405 return braceAdjustment; 406 } 407 408 /** 409 * Setter to specify how far a case label should be indented when on next line. 410 * 411 * @param amount the case indentation level 412 */ 413 public void setCaseIndent(int amount) { 414 caseIndent = amount; 415 } 416 417 /** 418 * Getter to query how far a case label should be indented when on next line. 419 * 420 * @return the case indentation level 421 */ 422 public int getCaseIndent() { 423 return caseIndent; 424 } 425 426 /** 427 * Setter to specify how far a throws clause should be indented when on next line. 428 * 429 * @param throwsIndent the throws indentation level 430 */ 431 public void setThrowsIndent(int throwsIndent) { 432 this.throwsIndent = throwsIndent; 433 } 434 435 /** 436 * Getter to query how far a throws clause should be indented when on next line. 437 * 438 * @return the throws indentation level 439 */ 440 public int getThrowsIndent() { 441 return throwsIndent; 442 } 443 444 /** 445 * Setter to specify how far an array initialisation should be indented when on next line. 446 * 447 * @param arrayInitIndent the array initialisation indentation level 448 */ 449 public void setArrayInitIndent(int arrayInitIndent) { 450 this.arrayInitIndent = arrayInitIndent; 451 } 452 453 /** 454 * Getter to query how far an array initialisation should be indented when on next line. 455 * 456 * @return the initialisation indentation level 457 */ 458 public int getArrayInitIndent() { 459 return arrayInitIndent; 460 } 461 462 /** 463 * Getter to query how far continuation line should be indented when line-wrapping is present. 464 * 465 * @return the line-wrapping indentation level 466 */ 467 public int getLineWrappingIndentation() { 468 return lineWrappingIndentation; 469 } 470 471 /** 472 * Setter to specify how far continuation line should be indented when line-wrapping is present. 473 * 474 * @param lineWrappingIndentation the line-wrapping indentation level 475 */ 476 public void setLineWrappingIndentation(int lineWrappingIndentation) { 477 this.lineWrappingIndentation = lineWrappingIndentation; 478 } 479 480 /** 481 * Log a violation message. 482 * 483 * @param ast the ast for which error to be logged 484 * @param key the message that describes the violation 485 * @param args the details of the message 486 * 487 * @see java.text.MessageFormat 488 */ 489 public void indentationLog(DetailAST ast, String key, Object... args) { 490 if (!incorrectIndentationLines.contains(ast.getLineNo())) { 491 incorrectIndentationLines.add(ast.getLineNo()); 492 log(ast, key, args); 493 } 494 } 495 496 /** 497 * Get the width of a tab. 498 * 499 * @return the width of a tab 500 */ 501 public int getIndentationTabWidth() { 502 return getTabWidth(); 503 } 504 505 @Override 506 public int[] getDefaultTokens() { 507 return getRequiredTokens(); 508 } 509 510 @Override 511 public int[] getAcceptableTokens() { 512 return getRequiredTokens(); 513 } 514 515 @Override 516 public int[] getRequiredTokens() { 517 return handlerFactory.getHandledTypes(); 518 } 519 520 @Override 521 public void beginTree(DetailAST ast) { 522 handlerFactory.clearCreatedHandlers(); 523 handlers.clear(); 524 final PrimordialHandler primordialHandler = new PrimordialHandler(this); 525 handlers.push(primordialHandler); 526 primordialHandler.checkIndentation(); 527 incorrectIndentationLines = new HashSet<>(); 528 } 529 530 @Override 531 public void visitToken(DetailAST ast) { 532 final AbstractExpressionHandler handler = handlerFactory.getHandler(this, ast, 533 handlers.peek()); 534 handlers.push(handler); 535 handler.checkIndentation(); 536 } 537 538 @Override 539 public void leaveToken(DetailAST ast) { 540 handlers.pop(); 541 } 542 543 /** 544 * Accessor for the line wrapping handler. 545 * 546 * @return the line wrapping handler 547 */ 548 public LineWrappingHandler getLineWrappingHandler() { 549 return lineWrappingHandler; 550 } 551 552 /** 553 * Accessor for the handler factory. 554 * 555 * @return the handler factory 556 */ 557 public final HandlerFactory getHandlerFactory() { 558 return handlerFactory; 559 } 560 561}