1 /////////////////////////////////////////////////////////////////////////////////////////////// 2 // checkstyle: Checks Java source code and other text files for adherence to a set of rules. 3 // Copyright (C) 2001-2025 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 * <div class="wrapper"><pre class="prettyprint"><code class="language-java"> 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 * </code></pre></div> 72 * 73 * @since 3.1 74 * @noinspection ThisEscapedInObjectConstruction 75 * @noinspectionreason ThisEscapedInObjectConstruction - class is instantiated in handlers 76 */ 77 @FileStatefulCheck 78 public class IndentationCheck extends AbstractCheck { 79 80 /* -- Implementation -- 81 * 82 * Basically, this check requests visitation for all handled token 83 * types (those tokens registered in the HandlerFactory). When visitToken 84 * is called, a new ExpressionHandler is created for the AST and pushed 85 * onto the handlers stack. The new handler then checks the indentation 86 * for the currently visiting AST. When leaveToken is called, the 87 * ExpressionHandler is popped from the stack. 88 * 89 * While on the stack the ExpressionHandler can be queried for the 90 * indentation level it suggests for children as well as for other 91 * values. 92 * 93 * While an ExpressionHandler checks the indentation level of its own 94 * AST, it typically also checks surrounding ASTs. For instance, a 95 * while loop handler checks the while loop as well as the braces 96 * and immediate children. 97 * 98 * - handler class -to-> ID mapping kept in Map 99 * - parent passed in during construction 100 * - suggest child indent level 101 * - allows for some tokens to be on same line (ie inner classes OBJBLOCK) 102 * and not increase indentation level 103 * - looked at using double dispatch for getSuggestedChildIndent(), but it 104 * doesn't seem worthwhile, at least now 105 * - both tabs and spaces are considered whitespace in front of the line... 106 * tabs are converted to spaces 107 * - block parents with parens -- for, while, if, etc... -- are checked that 108 * they match the level of the parent 109 */ 110 111 /** 112 * A key is pointing to the warning message text in "messages.properties" 113 * file. 114 */ 115 public static final String MSG_ERROR = "indentation.error"; 116 117 /** 118 * A key is pointing to the warning message text in "messages.properties" 119 * file. 120 */ 121 public static final String MSG_ERROR_MULTI = "indentation.error.multi"; 122 123 /** 124 * A key is pointing to the warning message text in "messages.properties" 125 * file. 126 */ 127 public static final String MSG_CHILD_ERROR = "indentation.child.error"; 128 129 /** 130 * A key is pointing to the warning message text in "messages.properties" 131 * file. 132 */ 133 public static final String MSG_CHILD_ERROR_MULTI = "indentation.child.error.multi"; 134 135 /** Default indentation amount - based on Sun. */ 136 private static final int DEFAULT_INDENTATION = 4; 137 138 /** Handlers currently in use. */ 139 private final Deque<AbstractExpressionHandler> handlers = new ArrayDeque<>(); 140 141 /** Instance of line wrapping handler to use. */ 142 private final LineWrappingHandler lineWrappingHandler = new LineWrappingHandler(this); 143 144 /** Factory from which handlers are distributed. */ 145 private final HandlerFactory handlerFactory = new HandlerFactory(); 146 147 /** Lines logged as having incorrect indentation. */ 148 private Set<Integer> incorrectIndentationLines; 149 150 /** Specify how far new indentation level should be indented when on the next line. */ 151 private int basicOffset = DEFAULT_INDENTATION; 152 153 /** Specify how far a case label should be indented when on next line. */ 154 private int caseIndent = DEFAULT_INDENTATION; 155 156 /** Specify how far a braces should be indented when on the next line. */ 157 private int braceAdjustment; 158 159 /** Specify how far a throws clause should be indented when on next line. */ 160 private int throwsIndent = DEFAULT_INDENTATION; 161 162 /** Specify how far an array initialization should be indented when on next line. */ 163 private int arrayInitIndent = DEFAULT_INDENTATION; 164 165 /** Specify how far continuation line should be indented when line-wrapping is present. */ 166 private int lineWrappingIndentation = DEFAULT_INDENTATION; 167 168 /** 169 * Force strict indent level in line wrapping case. If value is true, line wrap indent 170 * have to be same as lineWrappingIndentation parameter. If value is false, line wrap indent 171 * could be bigger on any value user would like. 172 */ 173 private boolean forceStrictCondition; 174 175 /** 176 * Getter to query strict indent level in line wrapping case. If value is true, line wrap indent 177 * have to be same as lineWrappingIndentation parameter. If value is false, line wrap indent 178 * could be bigger on any value user would like. 179 * 180 * @return forceStrictCondition value. 181 */ 182 public boolean isForceStrictCondition() { 183 return forceStrictCondition; 184 } 185 186 /** 187 * Setter to force strict indent level in line wrapping case. If value is true, line wrap indent 188 * have to be same as lineWrappingIndentation parameter. If value is false, line wrap indent 189 * could be bigger on any value user would like. 190 * 191 * @param value user's value of forceStrictCondition. 192 * @since 6.3 193 */ 194 public void setForceStrictCondition(boolean value) { 195 forceStrictCondition = value; 196 } 197 198 /** 199 * Setter to specify how far new indentation level should be indented when on the next line. 200 * 201 * @param basicOffset the number of tabs or spaces to indent 202 * @since 3.1 203 */ 204 public void setBasicOffset(int basicOffset) { 205 this.basicOffset = basicOffset; 206 } 207 208 /** 209 * Getter to query how far new indentation level should be indented when on the next line. 210 * 211 * @return the number of tabs or spaces to indent 212 */ 213 public int getBasicOffset() { 214 return basicOffset; 215 } 216 217 /** 218 * Setter to specify how far a braces should be indented when on the next line. 219 * 220 * @param adjustmentAmount the brace offset 221 * @since 3.1 222 */ 223 public void setBraceAdjustment(int adjustmentAmount) { 224 braceAdjustment = adjustmentAmount; 225 } 226 227 /** 228 * Getter to query how far a braces should be indented when on the next line. 229 * 230 * @return the positive offset to adjust braces 231 */ 232 public int getBraceAdjustment() { 233 return braceAdjustment; 234 } 235 236 /** 237 * Setter to specify how far a case label should be indented when on next line. 238 * 239 * @param amount the case indentation level 240 * @since 3.1 241 */ 242 public void setCaseIndent(int amount) { 243 caseIndent = amount; 244 } 245 246 /** 247 * Getter to query how far a case label should be indented when on next line. 248 * 249 * @return the case indentation level 250 */ 251 public int getCaseIndent() { 252 return caseIndent; 253 } 254 255 /** 256 * Setter to specify how far a throws clause should be indented when on next line. 257 * 258 * @param throwsIndent the throws indentation level 259 * @since 5.7 260 */ 261 public void setThrowsIndent(int throwsIndent) { 262 this.throwsIndent = throwsIndent; 263 } 264 265 /** 266 * Getter to query how far a throws clause should be indented when on next line. 267 * 268 * @return the throws indentation level 269 */ 270 public int getThrowsIndent() { 271 return throwsIndent; 272 } 273 274 /** 275 * Setter to specify how far an array initialization should be indented when on next line. 276 * 277 * @param arrayInitIndent the array initialization indentation level 278 * @since 5.8 279 */ 280 public void setArrayInitIndent(int arrayInitIndent) { 281 this.arrayInitIndent = arrayInitIndent; 282 } 283 284 /** 285 * Getter to query how far an array initialization should be indented when on next line. 286 * 287 * @return the initialization indentation level 288 */ 289 public int getArrayInitIndent() { 290 return arrayInitIndent; 291 } 292 293 /** 294 * Getter to query how far continuation line should be indented when line-wrapping is present. 295 * 296 * @return the line-wrapping indentation level 297 */ 298 public int getLineWrappingIndentation() { 299 return lineWrappingIndentation; 300 } 301 302 /** 303 * Setter to specify how far continuation line should be indented when line-wrapping is present. 304 * 305 * @param lineWrappingIndentation the line-wrapping indentation level 306 * @since 5.9 307 */ 308 public void setLineWrappingIndentation(int lineWrappingIndentation) { 309 this.lineWrappingIndentation = lineWrappingIndentation; 310 } 311 312 /** 313 * Log a violation message. 314 * 315 * @param ast the ast for which error to be logged 316 * @param key the message that describes the violation 317 * @param args the details of the message 318 * 319 * @see java.text.MessageFormat 320 */ 321 public void indentationLog(DetailAST ast, String key, Object... args) { 322 if (!incorrectIndentationLines.contains(ast.getLineNo())) { 323 incorrectIndentationLines.add(ast.getLineNo()); 324 log(ast, key, args); 325 } 326 } 327 328 /** 329 * Get the width of a tab. 330 * 331 * @return the width of a tab 332 */ 333 public int getIndentationTabWidth() { 334 return getTabWidth(); 335 } 336 337 @Override 338 public int[] getDefaultTokens() { 339 return getRequiredTokens(); 340 } 341 342 @Override 343 public int[] getAcceptableTokens() { 344 return getRequiredTokens(); 345 } 346 347 @Override 348 public int[] getRequiredTokens() { 349 return handlerFactory.getHandledTypes(); 350 } 351 352 @Override 353 public void beginTree(DetailAST ast) { 354 handlerFactory.clearCreatedHandlers(); 355 handlers.clear(); 356 final PrimordialHandler primordialHandler = new PrimordialHandler(this); 357 handlers.push(primordialHandler); 358 primordialHandler.checkIndentation(); 359 incorrectIndentationLines = new HashSet<>(); 360 } 361 362 @Override 363 public void visitToken(DetailAST ast) { 364 final AbstractExpressionHandler handler = handlerFactory.getHandler(this, ast, 365 handlers.peek()); 366 handlers.push(handler); 367 handler.checkIndentation(); 368 } 369 370 @Override 371 public void leaveToken(DetailAST ast) { 372 handlers.pop(); 373 } 374 375 /** 376 * Accessor for the line wrapping handler. 377 * 378 * @return the line wrapping handler 379 */ 380 public LineWrappingHandler getLineWrappingHandler() { 381 return lineWrappingHandler; 382 } 383 384 /** 385 * Accessor for the handler factory. 386 * 387 * @return the handler factory 388 */ 389 public final HandlerFactory getHandlerFactory() { 390 return handlerFactory; 391 } 392 393 }