1 /////////////////////////////////////////////////////////////////////////////////////////////// 2 // checkstyle: Checks Java source code and other text files for adherence to a set of rules. 3 // Copyright (C) 2001-2024 the original author or authors. 4 // 5 // This library is free software; you can redistribute it and/or 6 // modify it under the terms of the GNU Lesser General Public 7 // License as published by the Free Software Foundation; either 8 // version 2.1 of the License, or (at your option) any later version. 9 // 10 // This library is distributed in the hope that it will be useful, 11 // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 // Lesser General Public License for more details. 14 // 15 // You should have received a copy of the GNU Lesser General Public 16 // License along with this library; if not, write to the Free Software 17 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 18 /////////////////////////////////////////////////////////////////////////////////////////////// 19 20 package com.puppycrawl.tools.checkstyle.checks.indentation; 21 22 import java.util.ArrayDeque; 23 import java.util.Deque; 24 import java.util.HashSet; 25 import java.util.Set; 26 27 import com.puppycrawl.tools.checkstyle.FileStatefulCheck; 28 import com.puppycrawl.tools.checkstyle.api.AbstractCheck; 29 import com.puppycrawl.tools.checkstyle.api.DetailAST; 30 31 /** 32 * <div> 33 * Checks correct indentation of Java code. 34 * </div> 35 * 36 * <p> 37 * The idea behind this is that while 38 * pretty printers are sometimes convenient for bulk reformats of 39 * legacy code, they often either aren't configurable enough or 40 * just can't anticipate how format should be done. Sometimes this is 41 * personal preference, other times it is practical experience. In any 42 * case, this check should just ensure that a minimal set of indentation 43 * rules is followed. 44 * </p> 45 * 46 * <p> 47 * Basic offset indentation is used for indentation inside code blocks. 48 * For any lines that span more than 1, line wrapping indentation is used for those lines 49 * after the first. Brace adjustment, case, and throws indentations are all used only if 50 * those specific identifiers start the line. If, for example, a brace is used in the 51 * middle of the line, its indentation will not take effect. All indentations have an 52 * accumulative/recursive effect when they are triggered. If during a line wrapping, another 53 * code block is found and it doesn't end on that same line, then the subsequent lines 54 * afterwards, in that new code block, are increased on top of the line wrap and any 55 * indentations above it. 56 * </p> 57 * 58 * <p> 59 * Example: 60 * </p> 61 * <pre> 62 * if ((condition1 && condition2) 63 * || (condition3 && condition4) // line wrap with bigger indentation 64 * ||!(condition5 && condition6)) { // line wrap with bigger indentation 65 * field.doSomething() // basic offset 66 * .doSomething() // line wrap 67 * .doSomething( c -> { // line wrap 68 * return c.doSome(); // basic offset 69 * }); 70 * } 71 * </pre> 72 * <ul> 73 * <li> 74 * Property {@code arrayInitIndent} - Specify how far an array initialization 75 * should be indented when on next line. 76 * Type is {@code int}. 77 * Default value is {@code 4}. 78 * </li> 79 * <li> 80 * Property {@code basicOffset} - Specify how far new indentation level should be 81 * indented when on the next line. 82 * Type is {@code int}. 83 * Default value is {@code 4}. 84 * </li> 85 * <li> 86 * Property {@code braceAdjustment} - Specify how far a braces should be indented 87 * when on the next line. 88 * Type is {@code int}. 89 * Default value is {@code 0}. 90 * </li> 91 * <li> 92 * Property {@code caseIndent} - Specify how far a case label should be indented 93 * when on next line. 94 * Type is {@code int}. 95 * Default value is {@code 4}. 96 * </li> 97 * <li> 98 * Property {@code forceStrictCondition} - Force strict indent level in line 99 * wrapping case. If value is true, line wrap indent have to be same as 100 * lineWrappingIndentation parameter. If value is false, line wrap indent 101 * could be bigger on any value user would like. 102 * Type is {@code boolean}. 103 * Default value is {@code false}. 104 * </li> 105 * <li> 106 * Property {@code lineWrappingIndentation} - Specify how far continuation line 107 * should be indented when line-wrapping is present. 108 * Type is {@code int}. 109 * Default value is {@code 4}. 110 * </li> 111 * <li> 112 * Property {@code throwsIndent} - Specify how far a throws clause should be 113 * indented when on next line. 114 * Type is {@code int}. 115 * Default value is {@code 4}. 116 * </li> 117 * </ul> 118 * 119 * <p> 120 * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker} 121 * </p> 122 * 123 * <p> 124 * Violation Message Keys: 125 * </p> 126 * <ul> 127 * <li> 128 * {@code indentation.child.error} 129 * </li> 130 * <li> 131 * {@code indentation.child.error.multi} 132 * </li> 133 * <li> 134 * {@code indentation.error} 135 * </li> 136 * <li> 137 * {@code indentation.error.multi} 138 * </li> 139 * </ul> 140 * 141 * @noinspection ThisEscapedInObjectConstruction 142 * @noinspectionreason ThisEscapedInObjectConstruction - class is instantiated in handlers 143 * @since 3.1 144 */ 145 @FileStatefulCheck 146 public class IndentationCheck extends AbstractCheck { 147 148 /* -- Implementation -- 149 * 150 * Basically, this check requests visitation for all handled token 151 * types (those tokens registered in the HandlerFactory). When visitToken 152 * is called, a new ExpressionHandler is created for the AST and pushed 153 * onto the handlers stack. The new handler then checks the indentation 154 * for the currently visiting AST. When leaveToken is called, the 155 * ExpressionHandler is popped from the stack. 156 * 157 * While on the stack the ExpressionHandler can be queried for the 158 * indentation level it suggests for children as well as for other 159 * values. 160 * 161 * While an ExpressionHandler checks the indentation level of its own 162 * AST, it typically also checks surrounding ASTs. For instance, a 163 * while loop handler checks the while loop as well as the braces 164 * and immediate children. 165 * 166 * - handler class -to-> ID mapping kept in Map 167 * - parent passed in during construction 168 * - suggest child indent level 169 * - allows for some tokens to be on same line (ie inner classes OBJBLOCK) 170 * and not increase indentation level 171 * - looked at using double dispatch for getSuggestedChildIndent(), but it 172 * doesn't seem worthwhile, at least now 173 * - both tabs and spaces are considered whitespace in front of the line... 174 * tabs are converted to spaces 175 * - block parents with parens -- for, while, if, etc... -- are checked that 176 * they match the level of the parent 177 */ 178 179 /** 180 * A key is pointing to the warning message text in "messages.properties" 181 * file. 182 */ 183 public static final String MSG_ERROR = "indentation.error"; 184 185 /** 186 * A key is pointing to the warning message text in "messages.properties" 187 * file. 188 */ 189 public static final String MSG_ERROR_MULTI = "indentation.error.multi"; 190 191 /** 192 * A key is pointing to the warning message text in "messages.properties" 193 * file. 194 */ 195 public static final String MSG_CHILD_ERROR = "indentation.child.error"; 196 197 /** 198 * A key is pointing to the warning message text in "messages.properties" 199 * file. 200 */ 201 public static final String MSG_CHILD_ERROR_MULTI = "indentation.child.error.multi"; 202 203 /** Default indentation amount - based on Sun. */ 204 private static final int DEFAULT_INDENTATION = 4; 205 206 /** Handlers currently in use. */ 207 private final Deque<AbstractExpressionHandler> handlers = new ArrayDeque<>(); 208 209 /** Instance of line wrapping handler to use. */ 210 private final LineWrappingHandler lineWrappingHandler = new LineWrappingHandler(this); 211 212 /** Factory from which handlers are distributed. */ 213 private final HandlerFactory handlerFactory = new HandlerFactory(); 214 215 /** Lines logged as having incorrect indentation. */ 216 private Set<Integer> incorrectIndentationLines; 217 218 /** Specify how far new indentation level should be indented when on the next line. */ 219 private int basicOffset = DEFAULT_INDENTATION; 220 221 /** Specify how far a case label should be indented when on next line. */ 222 private int caseIndent = DEFAULT_INDENTATION; 223 224 /** Specify how far a braces should be indented when on the next line. */ 225 private int braceAdjustment; 226 227 /** Specify how far a throws clause should be indented when on next line. */ 228 private int throwsIndent = DEFAULT_INDENTATION; 229 230 /** Specify how far an array initialization should be indented when on next line. */ 231 private int arrayInitIndent = DEFAULT_INDENTATION; 232 233 /** Specify how far continuation line should be indented when line-wrapping is present. */ 234 private int lineWrappingIndentation = DEFAULT_INDENTATION; 235 236 /** 237 * Force strict indent level in line wrapping case. If value is true, line wrap indent 238 * have to be same as lineWrappingIndentation parameter. If value is false, line wrap indent 239 * could be bigger on any value user would like. 240 */ 241 private boolean forceStrictCondition; 242 243 /** 244 * Getter to query strict indent level in line wrapping case. If value is true, line wrap indent 245 * have to be same as lineWrappingIndentation parameter. If value is false, line wrap indent 246 * could be bigger on any value user would like. 247 * 248 * @return forceStrictCondition value. 249 */ 250 public boolean isForceStrictCondition() { 251 return forceStrictCondition; 252 } 253 254 /** 255 * Setter to force strict indent level in line wrapping case. If value is true, line wrap indent 256 * have to be same as lineWrappingIndentation parameter. If value is false, line wrap indent 257 * could be bigger on any value user would like. 258 * 259 * @param value user's value of forceStrictCondition. 260 * @since 6.3 261 */ 262 public void setForceStrictCondition(boolean value) { 263 forceStrictCondition = value; 264 } 265 266 /** 267 * Setter to specify how far new indentation level should be indented when on the next line. 268 * 269 * @param basicOffset the number of tabs or spaces to indent 270 * @since 3.1 271 */ 272 public void setBasicOffset(int basicOffset) { 273 this.basicOffset = basicOffset; 274 } 275 276 /** 277 * Getter to query how far new indentation level should be indented when on the next line. 278 * 279 * @return the number of tabs or spaces to indent 280 */ 281 public int getBasicOffset() { 282 return basicOffset; 283 } 284 285 /** 286 * Setter to specify how far a braces should be indented when on the next line. 287 * 288 * @param adjustmentAmount the brace offset 289 * @since 3.1 290 */ 291 public void setBraceAdjustment(int adjustmentAmount) { 292 braceAdjustment = adjustmentAmount; 293 } 294 295 /** 296 * Getter to query how far a braces should be indented when on the next line. 297 * 298 * @return the positive offset to adjust braces 299 */ 300 public int getBraceAdjustment() { 301 return braceAdjustment; 302 } 303 304 /** 305 * Setter to specify how far a case label should be indented when on next line. 306 * 307 * @param amount the case indentation level 308 * @since 3.1 309 */ 310 public void setCaseIndent(int amount) { 311 caseIndent = amount; 312 } 313 314 /** 315 * Getter to query how far a case label should be indented when on next line. 316 * 317 * @return the case indentation level 318 */ 319 public int getCaseIndent() { 320 return caseIndent; 321 } 322 323 /** 324 * Setter to specify how far a throws clause should be indented when on next line. 325 * 326 * @param throwsIndent the throws indentation level 327 * @since 5.7 328 */ 329 public void setThrowsIndent(int throwsIndent) { 330 this.throwsIndent = throwsIndent; 331 } 332 333 /** 334 * Getter to query how far a throws clause should be indented when on next line. 335 * 336 * @return the throws indentation level 337 */ 338 public int getThrowsIndent() { 339 return throwsIndent; 340 } 341 342 /** 343 * Setter to specify how far an array initialization should be indented when on next line. 344 * 345 * @param arrayInitIndent the array initialization indentation level 346 * @since 5.8 347 */ 348 public void setArrayInitIndent(int arrayInitIndent) { 349 this.arrayInitIndent = arrayInitIndent; 350 } 351 352 /** 353 * Getter to query how far an array initialization should be indented when on next line. 354 * 355 * @return the initialization indentation level 356 */ 357 public int getArrayInitIndent() { 358 return arrayInitIndent; 359 } 360 361 /** 362 * Getter to query how far continuation line should be indented when line-wrapping is present. 363 * 364 * @return the line-wrapping indentation level 365 */ 366 public int getLineWrappingIndentation() { 367 return lineWrappingIndentation; 368 } 369 370 /** 371 * Setter to specify how far continuation line should be indented when line-wrapping is present. 372 * 373 * @param lineWrappingIndentation the line-wrapping indentation level 374 * @since 5.9 375 */ 376 public void setLineWrappingIndentation(int lineWrappingIndentation) { 377 this.lineWrappingIndentation = lineWrappingIndentation; 378 } 379 380 /** 381 * Log a violation message. 382 * 383 * @param ast the ast for which error to be logged 384 * @param key the message that describes the violation 385 * @param args the details of the message 386 * 387 * @see java.text.MessageFormat 388 */ 389 public void indentationLog(DetailAST ast, String key, Object... args) { 390 if (!incorrectIndentationLines.contains(ast.getLineNo())) { 391 incorrectIndentationLines.add(ast.getLineNo()); 392 log(ast, key, args); 393 } 394 } 395 396 /** 397 * Get the width of a tab. 398 * 399 * @return the width of a tab 400 */ 401 public int getIndentationTabWidth() { 402 return getTabWidth(); 403 } 404 405 @Override 406 public int[] getDefaultTokens() { 407 return getRequiredTokens(); 408 } 409 410 @Override 411 public int[] getAcceptableTokens() { 412 return getRequiredTokens(); 413 } 414 415 @Override 416 public int[] getRequiredTokens() { 417 return handlerFactory.getHandledTypes(); 418 } 419 420 @Override 421 public void beginTree(DetailAST ast) { 422 handlerFactory.clearCreatedHandlers(); 423 handlers.clear(); 424 final PrimordialHandler primordialHandler = new PrimordialHandler(this); 425 handlers.push(primordialHandler); 426 primordialHandler.checkIndentation(); 427 incorrectIndentationLines = new HashSet<>(); 428 } 429 430 @Override 431 public void visitToken(DetailAST ast) { 432 final AbstractExpressionHandler handler = handlerFactory.getHandler(this, ast, 433 handlers.peek()); 434 handlers.push(handler); 435 handler.checkIndentation(); 436 } 437 438 @Override 439 public void leaveToken(DetailAST ast) { 440 handlers.pop(); 441 } 442 443 /** 444 * Accessor for the line wrapping handler. 445 * 446 * @return the line wrapping handler 447 */ 448 public LineWrappingHandler getLineWrappingHandler() { 449 return lineWrappingHandler; 450 } 451 452 /** 453 * Accessor for the handler factory. 454 * 455 * @return the handler factory 456 */ 457 public final HandlerFactory getHandlerFactory() { 458 return handlerFactory; 459 } 460 461 }