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 */ 075@FileStatefulCheck 076public class IndentationCheck extends AbstractCheck { 077 078 /* -- Implementation -- 079 * 080 * Basically, this check requests visitation for all handled token 081 * types (those tokens registered in the HandlerFactory). When visitToken 082 * is called, a new ExpressionHandler is created for the AST and pushed 083 * onto the handlers stack. The new handler then checks the indentation 084 * for the currently visiting AST. When leaveToken is called, the 085 * ExpressionHandler is popped from the stack. 086 * 087 * While on the stack the ExpressionHandler can be queried for the 088 * indentation level it suggests for children as well as for other 089 * values. 090 * 091 * While an ExpressionHandler checks the indentation level of its own 092 * AST, it typically also checks surrounding ASTs. For instance, a 093 * while loop handler checks the while loop as well as the braces 094 * and immediate children. 095 * 096 * - handler class -to-> ID mapping kept in Map 097 * - parent passed in during construction 098 * - suggest child indent level 099 * - allows for some tokens to be on same line (ie inner classes OBJBLOCK) 100 * and not increase indentation level 101 * - looked at using double dispatch for getSuggestedChildIndent(), but it 102 * doesn't seem worthwhile, at least now 103 * - both tabs and spaces are considered whitespace in front of the line... 104 * tabs are converted to spaces 105 * - block parents with parens -- for, while, if, etc... -- are checked that 106 * they match the level of the parent 107 */ 108 109 /** 110 * A key is pointing to the warning message text in "messages.properties" 111 * file. 112 */ 113 public static final String MSG_ERROR = IndentationContext.MSG_ERROR; 114 115 /** 116 * A key is pointing to the warning message text in "messages.properties" 117 * file. 118 */ 119 public static final String MSG_ERROR_MULTI = IndentationContext.MSG_ERROR_MULTI; 120 121 /** 122 * A key is pointing to the warning message text in "messages.properties" 123 * file. 124 */ 125 public static final String MSG_CHILD_ERROR = IndentationContext.MSG_CHILD_ERROR; 126 127 /** 128 * A key is pointing to the warning message text in "messages.properties" 129 * file. 130 */ 131 public static final String MSG_CHILD_ERROR_MULTI = 132 IndentationContext.MSG_CHILD_ERROR_MULTI; 133 134 /** Default indentation amount - based on Sun. */ 135 private static final int DEFAULT_INDENTATION = 4; 136 137 /** Handlers currently in use. */ 138 private final Deque<AbstractExpressionHandler> handlers = new ArrayDeque<>(); 139 140 /** Factory from which handlers are distributed. */ 141 private final HandlerFactory handlerFactory = new HandlerFactory(); 142 143 /** Lines logged as having incorrect indentation. */ 144 private final Set<Integer> incorrectIndentationLines = new HashSet<>(); 145 146 /** Context handed to handlers for this file; rebuilt in {@link #beginTree}. */ 147 private IndentationContext context; 148 149 /** Specify how far new indentation level should be indented when on the next line. */ 150 private int basicOffset = DEFAULT_INDENTATION; 151 152 /** Specify how far a case label should be indented when on next line. */ 153 private int caseIndent = DEFAULT_INDENTATION; 154 155 /** Specify how far a braces should be indented when on the next line. */ 156 private int braceAdjustment; 157 158 /** Specify how far a throws clause should be indented when on next line. */ 159 private int throwsIndent = DEFAULT_INDENTATION; 160 161 /** Specify how far an array initialization should be indented when on next line. */ 162 private int arrayInitIndent = DEFAULT_INDENTATION; 163 164 /** Specify how far continuation line should be indented when line-wrapping is present. */ 165 private int lineWrappingIndentation = DEFAULT_INDENTATION; 166 167 /** 168 * Force strict indent level in line wrapping case. If value is true, line wrap indent 169 * have to be same as lineWrappingIndentation parameter. If value is false, line wrap indent 170 * could be bigger on any value user would like. 171 */ 172 private boolean forceStrictCondition; 173 174 /** 175 * Creates a new {@code IndentationCheck} instance. 176 */ 177 public IndentationCheck() { 178 // no code by default 179 } 180 181 /** 182 * Setter to force strict indent level in line wrapping case. If value is true, line wrap indent 183 * have to be same as lineWrappingIndentation parameter. If value is false, line wrap indent 184 * could be bigger on any value user would like. 185 * 186 * @param value user's value of forceStrictCondition. 187 * @since 6.3 188 */ 189 public void setForceStrictCondition(boolean value) { 190 forceStrictCondition = value; 191 } 192 193 /** 194 * Setter to specify how far new indentation level should be indented when on the next line. 195 * 196 * @param basicOffset the number of tabs or spaces to indent 197 * @since 3.1 198 */ 199 public void setBasicOffset(int basicOffset) { 200 this.basicOffset = basicOffset; 201 } 202 203 /** 204 * Setter to specify how far a braces should be indented when on the next line. 205 * 206 * @param adjustmentAmount the brace offset 207 * @since 3.1 208 */ 209 public void setBraceAdjustment(int adjustmentAmount) { 210 braceAdjustment = adjustmentAmount; 211 } 212 213 /** 214 * Setter to specify how far a case label should be indented when on next line. 215 * 216 * @param amount the case indentation level 217 * @since 3.1 218 */ 219 public void setCaseIndent(int amount) { 220 caseIndent = amount; 221 } 222 223 /** 224 * Setter to specify how far a throws clause should be indented when on next line. 225 * 226 * @param throwsIndent the throws indentation level 227 * @since 5.7 228 */ 229 public void setThrowsIndent(int throwsIndent) { 230 this.throwsIndent = throwsIndent; 231 } 232 233 /** 234 * Setter to specify how far an array initialization should be indented when on next line. 235 * 236 * @param arrayInitIndent the array initialization indentation level 237 * @since 5.8 238 */ 239 public void setArrayInitIndent(int arrayInitIndent) { 240 this.arrayInitIndent = arrayInitIndent; 241 } 242 243 /** 244 * Setter to specify how far continuation line should be indented when line-wrapping is present. 245 * 246 * @param lineWrappingIndentation the line-wrapping indentation level 247 * @since 5.9 248 */ 249 public void setLineWrappingIndentation(int lineWrappingIndentation) { 250 this.lineWrappingIndentation = lineWrappingIndentation; 251 } 252 253 @Override 254 public int[] getDefaultTokens() { 255 return getRequiredTokens(); 256 } 257 258 @Override 259 public int[] getAcceptableTokens() { 260 return getRequiredTokens(); 261 } 262 263 @Override 264 public int[] getRequiredTokens() { 265 return handlerFactory.getHandledTypes(); 266 } 267 268 @Override 269 public void beginTree(DetailAST ast) { 270 clearState(); 271 final LineWrappingHandler lineWrappingHandler = new LineWrappingHandler(); 272 context = new IndentationContext( 273 basicOffset, braceAdjustment, caseIndent, throwsIndent, 274 arrayInitIndent, lineWrappingIndentation, forceStrictCondition, 275 getTabWidth(), 276 this::getLine, 277 handlerFactory, 278 lineWrappingHandler, 279 this::logIndentation); 280 lineWrappingHandler.setContext(context); 281 handlers.push(new PrimordialHandler(context)); 282 } 283 284 @Override 285 public void visitToken(DetailAST ast) { 286 final AbstractExpressionHandler handler = handlerFactory.getHandler(context, ast, 287 handlers.peek()); 288 handlers.push(handler); 289 handler.checkIndentation(); 290 } 291 292 @Override 293 public void leaveToken(DetailAST ast) { 294 handlers.pop(); 295 } 296 297 /** 298 * Log a violation, deduplicating by line number so each line only produces 299 * one indentation error. Invoked by handlers through {@link IndentationLogger}. 300 * 301 * @param ast the AST for which the error is logged 302 * @param key the message key 303 * @param args message arguments 304 */ 305 private void logIndentation(DetailAST ast, String key, Object... args) { 306 if (!incorrectIndentationLines.contains(ast.getLineNo())) { 307 incorrectIndentationLines.add(ast.getLineNo()); 308 log(ast, key, args); 309 } 310 } 311 312 /** 313 * Clears internal state for memory management between files. 314 */ 315 private void clearState() { 316 handlerFactory.clearCreatedHandlers(); 317 handlers.clear(); 318 incorrectIndentationLines.clear(); 319 } 320 321}