View Javadoc
1   ///////////////////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code and other text files for adherence to a set of rules.
3   // Copyright (C) 2001-2024 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   * <p>
33   * Checks correct indentation of Java code.
34   * </p>
35   * <p>
36   * The idea behind this is that while
37   * pretty printers are sometimes convenient for bulk reformats of
38   * legacy code, they often either aren't configurable enough or
39   * just can't anticipate how format should be done. Sometimes this is
40   * personal preference, other times it is practical experience. In any
41   * case, this check should just ensure that a minimal set of indentation
42   * rules is followed.
43   * </p>
44   * <p>
45   * Basic offset indentation is used for indentation inside code blocks.
46   * For any lines that span more than 1, line wrapping indentation is used for those lines
47   * after the first. Brace adjustment, case, and throws indentations are all used only if
48   * those specific identifiers start the line. If, for example, a brace is used in the
49   * middle of the line, its indentation will not take effect. All indentations have an
50   * accumulative/recursive effect when they are triggered. If during a line wrapping, another
51   * code block is found and it doesn't end on that same line, then the subsequent lines
52   * afterwards, in that new code block, are increased on top of the line wrap and any
53   * indentations above it.
54   * </p>
55   * <p>
56   * Example:
57   * </p>
58   * <pre>
59   * if ((condition1 &amp;&amp; condition2)
60   *         || (condition3 &amp;&amp; condition4)    // line wrap with bigger indentation
61   *         ||!(condition5 &amp;&amp; condition6)) { // line wrap with bigger indentation
62   *   field.doSomething()                    // basic offset
63   *       .doSomething()                     // line wrap
64   *       .doSomething( c -&gt; {               // line wrap
65   *         return c.doSome();               // basic offset
66   *       });
67   * }
68   * </pre>
69   * <ul>
70   * <li>
71   * Property {@code arrayInitIndent} - Specify how far an array initialization
72   * should be indented when on next line.
73   * Type is {@code int}.
74   * Default value is {@code 4}.
75   * </li>
76   * <li>
77   * Property {@code basicOffset} - Specify how far new indentation level should be
78   * indented when on the next line.
79   * Type is {@code int}.
80   * Default value is {@code 4}.
81   * </li>
82   * <li>
83   * Property {@code braceAdjustment} - Specify how far a braces should be indented
84   * when on the next line.
85   * Type is {@code int}.
86   * Default value is {@code 0}.
87   * </li>
88   * <li>
89   * Property {@code caseIndent} - Specify how far a case label should be indented
90   * when on next line.
91   * Type is {@code int}.
92   * Default value is {@code 4}.
93   * </li>
94   * <li>
95   * Property {@code forceStrictCondition} - Force strict indent level in line
96   * wrapping case. If value is true, line wrap indent have to be same as
97   * lineWrappingIndentation parameter. If value is false, line wrap indent
98   * could be bigger on any value user would like.
99   * Type is {@code boolean}.
100  * Default value is {@code false}.
101  * </li>
102  * <li>
103  * Property {@code lineWrappingIndentation} - Specify how far continuation line
104  * should be indented when line-wrapping is present.
105  * Type is {@code int}.
106  * Default value is {@code 4}.
107  * </li>
108  * <li>
109  * Property {@code throwsIndent} - Specify how far a throws clause should be
110  * indented when on next line.
111  * Type is {@code int}.
112  * Default value is {@code 4}.
113  * </li>
114  * </ul>
115  * <p>
116  * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker}
117  * </p>
118  * <p>
119  * Violation Message Keys:
120  * </p>
121  * <ul>
122  * <li>
123  * {@code indentation.child.error}
124  * </li>
125  * <li>
126  * {@code indentation.child.error.multi}
127  * </li>
128  * <li>
129  * {@code indentation.error}
130  * </li>
131  * <li>
132  * {@code indentation.error.multi}
133  * </li>
134  * </ul>
135  *
136  * @noinspection ThisEscapedInObjectConstruction
137  * @noinspectionreason ThisEscapedInObjectConstruction - class is instantiated in handlers
138  * @since 3.1
139  */
140 @FileStatefulCheck
141 public class IndentationCheck extends AbstractCheck {
142 
143     /*  -- Implementation --
144      *
145      *  Basically, this check requests visitation for all handled token
146      *  types (those tokens registered in the HandlerFactory).  When visitToken
147      *  is called, a new ExpressionHandler is created for the AST and pushed
148      *  onto the handlers stack.  The new handler then checks the indentation
149      *  for the currently visiting AST.  When leaveToken is called, the
150      *  ExpressionHandler is popped from the stack.
151      *
152      *  While on the stack the ExpressionHandler can be queried for the
153      *  indentation level it suggests for children as well as for other
154      *  values.
155      *
156      *  While an ExpressionHandler checks the indentation level of its own
157      *  AST, it typically also checks surrounding ASTs.  For instance, a
158      *  while loop handler checks the while loop as well as the braces
159      *  and immediate children.
160      *
161      *   - handler class -to-&gt; ID mapping kept in Map
162      *   - parent passed in during construction
163      *   - suggest child indent level
164      *   - allows for some tokens to be on same line (ie inner classes OBJBLOCK)
165      *     and not increase indentation level
166      *   - looked at using double dispatch for getSuggestedChildIndent(), but it
167      *     doesn't seem worthwhile, at least now
168      *   - both tabs and spaces are considered whitespace in front of the line...
169      *     tabs are converted to spaces
170      *   - block parents with parens -- for, while, if, etc... -- are checked that
171      *     they match the level of the parent
172      */
173 
174     /**
175      * A key is pointing to the warning message text in "messages.properties"
176      * file.
177      */
178     public static final String MSG_ERROR = "indentation.error";
179 
180     /**
181      * A key is pointing to the warning message text in "messages.properties"
182      * file.
183      */
184     public static final String MSG_ERROR_MULTI = "indentation.error.multi";
185 
186     /**
187      * A key is pointing to the warning message text in "messages.properties"
188      * file.
189      */
190     public static final String MSG_CHILD_ERROR = "indentation.child.error";
191 
192     /**
193      * A key is pointing to the warning message text in "messages.properties"
194      * file.
195      */
196     public static final String MSG_CHILD_ERROR_MULTI = "indentation.child.error.multi";
197 
198     /** Default indentation amount - based on Sun. */
199     private static final int DEFAULT_INDENTATION = 4;
200 
201     /** Handlers currently in use. */
202     private final Deque<AbstractExpressionHandler> handlers = new ArrayDeque<>();
203 
204     /** Instance of line wrapping handler to use. */
205     private final LineWrappingHandler lineWrappingHandler = new LineWrappingHandler(this);
206 
207     /** Factory from which handlers are distributed. */
208     private final HandlerFactory handlerFactory = new HandlerFactory();
209 
210     /** Lines logged as having incorrect indentation. */
211     private Set<Integer> incorrectIndentationLines;
212 
213     /** Specify how far new indentation level should be indented when on the next line. */
214     private int basicOffset = DEFAULT_INDENTATION;
215 
216     /** Specify how far a case label should be indented when on next line. */
217     private int caseIndent = DEFAULT_INDENTATION;
218 
219     /** Specify how far a braces should be indented when on the next line. */
220     private int braceAdjustment;
221 
222     /** Specify how far a throws clause should be indented when on next line. */
223     private int throwsIndent = DEFAULT_INDENTATION;
224 
225     /** Specify how far an array initialization should be indented when on next line. */
226     private int arrayInitIndent = DEFAULT_INDENTATION;
227 
228     /** Specify how far continuation line should be indented when line-wrapping is present. */
229     private int lineWrappingIndentation = DEFAULT_INDENTATION;
230 
231     /**
232      * Force strict indent level in line wrapping case. If value is true, line wrap indent
233      * have to be same as lineWrappingIndentation parameter. If value is false, line wrap indent
234      * could be bigger on any value user would like.
235      */
236     private boolean forceStrictCondition;
237 
238     /**
239      * Getter to query strict indent level in line wrapping case. If value is true, line wrap indent
240      * have to be same as lineWrappingIndentation parameter. If value is false, line wrap indent
241      * could be bigger on any value user would like.
242      *
243      * @return forceStrictCondition value.
244      */
245     public boolean isForceStrictCondition() {
246         return forceStrictCondition;
247     }
248 
249     /**
250      * Setter to force strict indent level in line wrapping case. If value is true, line wrap indent
251      * have to be same as lineWrappingIndentation parameter. If value is false, line wrap indent
252      * could be bigger on any value user would like.
253      *
254      * @param value user's value of forceStrictCondition.
255      * @since 6.3
256      */
257     public void setForceStrictCondition(boolean value) {
258         forceStrictCondition = value;
259     }
260 
261     /**
262      * Setter to specify how far new indentation level should be indented when on the next line.
263      *
264      * @param basicOffset   the number of tabs or spaces to indent
265      * @since 3.1
266      */
267     public void setBasicOffset(int basicOffset) {
268         this.basicOffset = basicOffset;
269     }
270 
271     /**
272      * Getter to query how far new indentation level should be indented when on the next line.
273      *
274      * @return the number of tabs or spaces to indent
275      */
276     public int getBasicOffset() {
277         return basicOffset;
278     }
279 
280     /**
281      * Setter to specify how far a braces should be indented when on the next line.
282      *
283      * @param adjustmentAmount   the brace offset
284      * @since 3.1
285      */
286     public void setBraceAdjustment(int adjustmentAmount) {
287         braceAdjustment = adjustmentAmount;
288     }
289 
290     /**
291      * Getter to query how far a braces should be indented when on the next line.
292      *
293      * @return the positive offset to adjust braces
294      */
295     public int getBraceAdjustment() {
296         return braceAdjustment;
297     }
298 
299     /**
300      * Setter to specify how far a case label should be indented when on next line.
301      *
302      * @param amount   the case indentation level
303      * @since 3.1
304      */
305     public void setCaseIndent(int amount) {
306         caseIndent = amount;
307     }
308 
309     /**
310      * Getter to query how far a case label should be indented when on next line.
311      *
312      * @return the case indentation level
313      */
314     public int getCaseIndent() {
315         return caseIndent;
316     }
317 
318     /**
319      * Setter to specify how far a throws clause should be indented when on next line.
320      *
321      * @param throwsIndent the throws indentation level
322      * @since 5.7
323      */
324     public void setThrowsIndent(int throwsIndent) {
325         this.throwsIndent = throwsIndent;
326     }
327 
328     /**
329      * Getter to query how far a throws clause should be indented when on next line.
330      *
331      * @return the throws indentation level
332      */
333     public int getThrowsIndent() {
334         return throwsIndent;
335     }
336 
337     /**
338      * Setter to specify how far an array initialization should be indented when on next line.
339      *
340      * @param arrayInitIndent the array initialization indentation level
341      * @since 5.8
342      */
343     public void setArrayInitIndent(int arrayInitIndent) {
344         this.arrayInitIndent = arrayInitIndent;
345     }
346 
347     /**
348      * Getter to query how far an array initialization should be indented when on next line.
349      *
350      * @return the initialization indentation level
351      */
352     public int getArrayInitIndent() {
353         return arrayInitIndent;
354     }
355 
356     /**
357      * Getter to query how far continuation line should be indented when line-wrapping is present.
358      *
359      * @return the line-wrapping indentation level
360      */
361     public int getLineWrappingIndentation() {
362         return lineWrappingIndentation;
363     }
364 
365     /**
366      * Setter to specify how far continuation line should be indented when line-wrapping is present.
367      *
368      * @param lineWrappingIndentation the line-wrapping indentation level
369      * @since 5.9
370      */
371     public void setLineWrappingIndentation(int lineWrappingIndentation) {
372         this.lineWrappingIndentation = lineWrappingIndentation;
373     }
374 
375     /**
376      * Log a violation message.
377      *
378      * @param  ast the ast for which error to be logged
379      * @param key the message that describes the violation
380      * @param args the details of the message
381      *
382      * @see java.text.MessageFormat
383      */
384     public void indentationLog(DetailAST ast, String key, Object... args) {
385         if (!incorrectIndentationLines.contains(ast.getLineNo())) {
386             incorrectIndentationLines.add(ast.getLineNo());
387             log(ast, key, args);
388         }
389     }
390 
391     /**
392      * Get the width of a tab.
393      *
394      * @return the width of a tab
395      */
396     public int getIndentationTabWidth() {
397         return getTabWidth();
398     }
399 
400     @Override
401     public int[] getDefaultTokens() {
402         return getRequiredTokens();
403     }
404 
405     @Override
406     public int[] getAcceptableTokens() {
407         return getRequiredTokens();
408     }
409 
410     @Override
411     public int[] getRequiredTokens() {
412         return handlerFactory.getHandledTypes();
413     }
414 
415     @Override
416     public void beginTree(DetailAST ast) {
417         handlerFactory.clearCreatedHandlers();
418         handlers.clear();
419         final PrimordialHandler primordialHandler = new PrimordialHandler(this);
420         handlers.push(primordialHandler);
421         primordialHandler.checkIndentation();
422         incorrectIndentationLines = new HashSet<>();
423     }
424 
425     @Override
426     public void visitToken(DetailAST ast) {
427         final AbstractExpressionHandler handler = handlerFactory.getHandler(this, ast,
428             handlers.peek());
429         handlers.push(handler);
430         handler.checkIndentation();
431     }
432 
433     @Override
434     public void leaveToken(DetailAST ast) {
435         handlers.pop();
436     }
437 
438     /**
439      * Accessor for the line wrapping handler.
440      *
441      * @return the line wrapping handler
442      */
443     public LineWrappingHandler getLineWrappingHandler() {
444         return lineWrappingHandler;
445     }
446 
447     /**
448      * Accessor for the handler factory.
449      *
450      * @return the handler factory
451      */
452     public final HandlerFactory getHandlerFactory() {
453         return handlerFactory;
454     }
455 
456 }