001/////////////////////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code and other text files for adherence to a set of rules. 003// Copyright (C) 2001-2026 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 * <div> 033 * Checks correct indentation of Java code. 034 * </div> 035 * 036 * <p> 037 * The idea behind this is that while 038 * pretty printers are sometimes convenient for bulk reformats of 039 * legacy code, they often either aren't configurable enough or 040 * just can't anticipate how format should be done. Sometimes this is 041 * personal preference, other times it is practical experience. In any 042 * case, this check should just ensure that a minimal set of indentation 043 * rules is followed. 044 * </p> 045 * 046 * <p> 047 * Basic offset indentation is used for indentation inside code blocks. 048 * For any lines that span more than 1, line wrapping indentation is used for those lines 049 * after the first. Brace adjustment, case, and throws indentations are all used only if 050 * those specific identifiers start the line. If, for example, a brace is used in the 051 * middle of the line, its indentation will not take effect. All indentations have an 052 * accumulative/recursive effect when they are triggered. If during a line wrapping, another 053 * code block is found and it doesn't end on that same line, then the subsequent lines 054 * afterwards, in that new code block, are increased on top of the line wrap and any 055 * indentations above it. 056 * </p> 057 * 058 * <p> 059 * Example: 060 * </p> 061 * <div class="wrapper"><pre class="prettyprint"><code class="language-java"> 062 * if ((condition1 && condition2) 063 * || (condition3 && condition4) // line wrap with bigger indentation 064 * ||!(condition5 && condition6)) { // line wrap with bigger indentation 065 * field.doSomething() // basic offset 066 * .doSomething() // line wrap 067 * .doSomething( c -> { // line wrap 068 * return c.doSome(); // basic offset 069 * }); 070 * } 071 * </code></pre></div> 072 * 073 * @since 3.1 074 * @noinspection ThisEscapedInObjectConstruction 075 * @noinspectionreason ThisEscapedInObjectConstruction - class is instantiated in handlers 076 */ 077@FileStatefulCheck 078public class IndentationCheck extends AbstractCheck { 079 080 /* -- Implementation -- 081 * 082 * Basically, this check requests visitation for all handled token 083 * types (those tokens registered in the HandlerFactory). When visitToken 084 * is called, a new ExpressionHandler is created for the AST and pushed 085 * onto the handlers stack. The new handler then checks the indentation 086 * for the currently visiting AST. When leaveToken is called, the 087 * ExpressionHandler is popped from the stack. 088 * 089 * While on the stack the ExpressionHandler can be queried for the 090 * indentation level it suggests for children as well as for other 091 * values. 092 * 093 * While an ExpressionHandler checks the indentation level of its own 094 * AST, it typically also checks surrounding ASTs. For instance, a 095 * while loop handler checks the while loop as well as the braces 096 * and immediate children. 097 * 098 * - handler class -to-> ID mapping kept in Map 099 * - 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 final Set<Integer> incorrectIndentationLines = new HashSet<>(); 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 * Creates a new {@code IndentationCheck} instance. 177 */ 178 public IndentationCheck() { 179 // no code by default 180 } 181 182 /** 183 * Getter to query strict indent level in line wrapping case. If value is true, line wrap indent 184 * have to be same as lineWrappingIndentation parameter. If value is false, line wrap indent 185 * could be bigger on any value user would like. 186 * 187 * @return forceStrictCondition value. 188 */ 189 public boolean isForceStrictCondition() { 190 return forceStrictCondition; 191 } 192 193 /** 194 * Setter to force strict indent level in line wrapping case. If value is true, line wrap indent 195 * have to be same as lineWrappingIndentation parameter. If value is false, line wrap indent 196 * could be bigger on any value user would like. 197 * 198 * @param value user's value of forceStrictCondition. 199 * @since 6.3 200 */ 201 public void setForceStrictCondition(boolean value) { 202 forceStrictCondition = value; 203 } 204 205 /** 206 * Setter to specify how far new indentation level should be indented when on the next line. 207 * 208 * @param basicOffset the number of tabs or spaces to indent 209 * @since 3.1 210 */ 211 public void setBasicOffset(int basicOffset) { 212 this.basicOffset = basicOffset; 213 } 214 215 /** 216 * Getter to query how far new indentation level should be indented when on the next line. 217 * 218 * @return the number of tabs or spaces to indent 219 */ 220 public int getBasicOffset() { 221 return basicOffset; 222 } 223 224 /** 225 * Setter to specify how far a braces should be indented when on the next line. 226 * 227 * @param adjustmentAmount the brace offset 228 * @since 3.1 229 */ 230 public void setBraceAdjustment(int adjustmentAmount) { 231 braceAdjustment = adjustmentAmount; 232 } 233 234 /** 235 * Getter to query how far a braces should be indented when on the next line. 236 * 237 * @return the positive offset to adjust braces 238 */ 239 public int getBraceAdjustment() { 240 return braceAdjustment; 241 } 242 243 /** 244 * Setter to specify how far a case label should be indented when on next line. 245 * 246 * @param amount the case indentation level 247 * @since 3.1 248 */ 249 public void setCaseIndent(int amount) { 250 caseIndent = amount; 251 } 252 253 /** 254 * Getter to query how far a case label should be indented when on next line. 255 * 256 * @return the case indentation level 257 */ 258 public int getCaseIndent() { 259 return caseIndent; 260 } 261 262 /** 263 * Setter to specify how far a throws clause should be indented when on next line. 264 * 265 * @param throwsIndent the throws indentation level 266 * @since 5.7 267 */ 268 public void setThrowsIndent(int throwsIndent) { 269 this.throwsIndent = throwsIndent; 270 } 271 272 /** 273 * Getter to query how far a throws clause should be indented when on next line. 274 * 275 * @return the throws indentation level 276 */ 277 public int getThrowsIndent() { 278 return throwsIndent; 279 } 280 281 /** 282 * Setter to specify how far an array initialization should be indented when on next line. 283 * 284 * @param arrayInitIndent the array initialization indentation level 285 * @since 5.8 286 */ 287 public void setArrayInitIndent(int arrayInitIndent) { 288 this.arrayInitIndent = arrayInitIndent; 289 } 290 291 /** 292 * Getter to query how far an array initialization should be indented when on next line. 293 * 294 * @return the initialization indentation level 295 */ 296 public int getArrayInitIndent() { 297 return arrayInitIndent; 298 } 299 300 /** 301 * Getter to query how far continuation line should be indented when line-wrapping is present. 302 * 303 * @return the line-wrapping indentation level 304 */ 305 public int getLineWrappingIndentation() { 306 return lineWrappingIndentation; 307 } 308 309 /** 310 * Setter to specify how far continuation line should be indented when line-wrapping is present. 311 * 312 * @param lineWrappingIndentation the line-wrapping indentation level 313 * @since 5.9 314 */ 315 public void setLineWrappingIndentation(int lineWrappingIndentation) { 316 this.lineWrappingIndentation = lineWrappingIndentation; 317 } 318 319 /** 320 * Log a violation message. 321 * 322 * @param ast the ast for which error to be logged 323 * @param key the message that describes the violation 324 * @param args the details of the message 325 * 326 * @see java.text.MessageFormat 327 */ 328 public void indentationLog(DetailAST ast, String key, Object... args) { 329 if (!incorrectIndentationLines.contains(ast.getLineNo())) { 330 incorrectIndentationLines.add(ast.getLineNo()); 331 log(ast, key, args); 332 } 333 } 334 335 /** 336 * Get the width of a tab. 337 * 338 * @return the width of a tab 339 */ 340 public int getIndentationTabWidth() { 341 return getTabWidth(); 342 } 343 344 @Override 345 public int[] getDefaultTokens() { 346 return getRequiredTokens(); 347 } 348 349 @Override 350 public int[] getAcceptableTokens() { 351 return getRequiredTokens(); 352 } 353 354 @Override 355 public int[] getRequiredTokens() { 356 return handlerFactory.getHandledTypes(); 357 } 358 359 @Override 360 public void beginTree(DetailAST ast) { 361 clearState(); 362 final PrimordialHandler primordialHandler = new PrimordialHandler(this); 363 handlers.push(primordialHandler); 364 } 365 366 @Override 367 public void visitToken(DetailAST ast) { 368 final AbstractExpressionHandler handler = handlerFactory.getHandler(this, ast, 369 handlers.peek()); 370 handlers.push(handler); 371 handler.checkIndentation(); 372 } 373 374 @Override 375 public void leaveToken(DetailAST ast) { 376 handlers.pop(); 377 } 378 379 /** 380 * Clears internal state for memory management between files. 381 */ 382 private void clearState() { 383 handlerFactory.clearCreatedHandlers(); 384 handlers.clear(); 385 incorrectIndentationLines.clear(); 386 } 387 388 /** 389 * Accessor for the line wrapping handler. 390 * 391 * @return the line wrapping handler 392 */ 393 public LineWrappingHandler getLineWrappingHandler() { 394 return lineWrappingHandler; 395 } 396 397 /** 398 * Accessor for the handler factory. 399 * 400 * @return the handler factory 401 */ 402 public final HandlerFactory getHandlerFactory() { 403 return handlerFactory; 404 } 405 406}