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