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