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