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