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   */
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-&gt; 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 }