1 ///////////////////////////////////////////////////////////////////////////////////////////////
2 // checkstyle: Checks Java source code and other text files for adherence to a set of rules.
3 // Copyright (C) 2001-2026 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 */
75 @FileStatefulCheck
76 public class IndentationCheck extends AbstractCheck {
77
78 /* -- Implementation --
79 *
80 * Basically, this check requests visitation for all handled token
81 * types (those tokens registered in the HandlerFactory). When visitToken
82 * is called, a new ExpressionHandler is created for the AST and pushed
83 * onto the handlers stack. The new handler then checks the indentation
84 * for the currently visiting AST. When leaveToken is called, the
85 * ExpressionHandler is popped from the stack.
86 *
87 * While on the stack the ExpressionHandler can be queried for the
88 * indentation level it suggests for children as well as for other
89 * values.
90 *
91 * While an ExpressionHandler checks the indentation level of its own
92 * AST, it typically also checks surrounding ASTs. For instance, a
93 * while loop handler checks the while loop as well as the braces
94 * and immediate children.
95 *
96 * - handler class -to-> ID mapping kept in Map
97 * - parent passed in during construction
98 * - suggest child indent level
99 * - 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 }