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 * @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 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 }