View Javadoc
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 &amp;&amp; condition2)
63   *         || (condition3 &amp;&amp; condition4)    // line wrap with bigger indentation
64   *         ||!(condition5 &amp;&amp; condition6)) { // line wrap with bigger indentation
65   *   field.doSomething()                    // basic offset
66   *       .doSomething()                     // line wrap
67   *       .doSomething( c -&gt; {               // 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-&gt; 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 }