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 com.puppycrawl.tools.checkstyle.api.DetailAST;
23  import com.puppycrawl.tools.checkstyle.api.TokenTypes;
24  import com.puppycrawl.tools.checkstyle.utils.TokenUtil;
25  
26  /**
27   * Handler for parents of blocks ('if', 'else', 'while', etc).
28   *
29   * <P>
30   * The "block" handler classes use a common superclass BlockParentHandler,
31   * employing the Template Method pattern.
32   * </P>
33   *
34   * <UL>
35   *   <LI>template method to get the lcurly</LI>
36   *   <LI>template method to get the rcurly</LI>
37   *   <LI>if curlies aren't present, then template method to get expressions
38   *       is called</LI>
39   *   <LI>now all the repetitious code which checks for BOL, if curlies are on
40   *       same line, etc. can be collapsed into the superclass</LI>
41   * </UL>
42   *
43   *
44   */
45  public class BlockParentHandler extends AbstractExpressionHandler {
46  
47      /**
48       * Children checked by parent handlers.
49       */
50      private static final int[] CHECKED_CHILDREN = {
51          TokenTypes.VARIABLE_DEF,
52          TokenTypes.EXPR,
53          TokenTypes.ANNOTATION,
54          TokenTypes.OBJBLOCK,
55          TokenTypes.LITERAL_BREAK,
56          TokenTypes.LITERAL_RETURN,
57          TokenTypes.LITERAL_THROW,
58          TokenTypes.LITERAL_CONTINUE,
59          TokenTypes.CTOR_CALL,
60          TokenTypes.SUPER_CTOR_CALL,
61      };
62  
63      /**
64       * Construct an instance of this handler with the given indentation check,
65       * name, abstract syntax tree, and parent handler.
66       *
67       * @param indentCheck   the indentation check
68       * @param name          the name of the handler
69       * @param ast           the abstract syntax tree
70       * @param parent        the parent handler
71       * @noinspection WeakerAccess
72       * @noinspectionreason WeakerAccess - we avoid 'protected' when possible
73       */
74      public BlockParentHandler(IndentationCheck indentCheck,
75          String name, DetailAST ast, AbstractExpressionHandler parent) {
76          super(indentCheck, name, ast, parent);
77      }
78  
79      /**
80       * Returns array of token types which should be checked among children.
81       *
82       * @return array of token types to check.
83       */
84      protected int[] getCheckedChildren() {
85          return CHECKED_CHILDREN.clone();
86      }
87  
88      /**
89       * Get the top level expression being managed by this handler.
90       *
91       * @return the top level expression
92       */
93      protected DetailAST getTopLevelAst() {
94          return getMainAst();
95      }
96  
97      /**
98       * Check the indent of the top level token.
99       */
100     protected void checkTopLevelToken() {
101         final DetailAST topLevel = getTopLevelAst();
102 
103         if (topLevel != null
104                 && !getIndent().isAcceptable(expandedTabsColumnNo(topLevel))
105                 && isOnStartOfLine(topLevel)) {
106             logError(topLevel, "", expandedTabsColumnNo(topLevel));
107         }
108     }
109 
110     /**
111      * Determines if this block expression has curly braces.
112      *
113      * @return true if curly braces are present, false otherwise
114      */
115     private boolean hasCurlies() {
116         return getLeftCurly() != null && getRightCurly() != null;
117     }
118 
119     /**
120      * Get the left curly brace portion of the expression we are handling.
121      *
122      * @return the left curly brace expression
123      */
124     protected DetailAST getLeftCurly() {
125         return getMainAst().findFirstToken(TokenTypes.SLIST);
126     }
127 
128     /**
129      * Get the right curly brace portion of the expression we are handling.
130      *
131      * @return the right curly brace expression
132      */
133     protected DetailAST getRightCurly() {
134         final DetailAST slist = getMainAst().findFirstToken(TokenTypes.SLIST);
135         return slist.findFirstToken(TokenTypes.RCURLY);
136     }
137 
138     /**
139      * Check the indentation of the left curly brace.
140      */
141     private void checkLeftCurly() {
142         // the lcurly can either be at the correct indentation, or nested
143         // with a previous expression
144         final DetailAST lcurly = getLeftCurly();
145         final int lcurlyPos = expandedTabsColumnNo(lcurly);
146 
147         if (!curlyIndent().isAcceptable(lcurlyPos) && isOnStartOfLine(lcurly)) {
148             logError(lcurly, "lcurly", lcurlyPos, curlyIndent());
149         }
150     }
151 
152     /**
153      * Get the expected indentation level for the curly braces.
154      *
155      * @return the curly brace indentation level
156      */
157     protected IndentLevel curlyIndent() {
158         final DetailAST lcurly = getLeftCurly();
159         IndentLevel expIndentLevel = new IndentLevel(getIndent(), getBraceAdjustment());
160         if (!isOnStartOfLine(lcurly)
161             || lcurly.getParent().getType() == TokenTypes.INSTANCE_INIT) {
162             expIndentLevel = new IndentLevel(getIndent(), 0);
163         }
164 
165         return expIndentLevel;
166     }
167 
168     /**
169      * Determines if child elements within the expression may be nested.
170      *
171      * @return false
172      */
173     protected boolean canChildrenBeNested() {
174         return false;
175     }
176 
177     /**
178      * Check the indentation of the right curly brace.
179      */
180     private void checkRightCurly() {
181         final DetailAST rcurly = getRightCurly();
182         final int rcurlyPos = expandedTabsColumnNo(rcurly);
183 
184         if (!curlyIndent().isAcceptable(rcurlyPos)
185                 && isOnStartOfLine(rcurly)) {
186             logError(rcurly, "rcurly", rcurlyPos, curlyIndent());
187         }
188     }
189 
190     /**
191      * Get the child element that is not a list of statements.
192      *
193      * @return the non-list child element
194      */
195     protected DetailAST getNonListChild() {
196         return getMainAst().findFirstToken(TokenTypes.RPAREN).getNextSibling();
197     }
198 
199     /**
200      * Check the indentation level of a child that is not a list of statements.
201      */
202     private void checkNonListChild() {
203         final DetailAST nonList = getNonListChild();
204         if (nonList != null) {
205             final IndentLevel expected = new IndentLevel(getIndent(), getBasicOffset());
206             checkExpressionSubtree(nonList, expected, false, false);
207 
208             final DetailAST nonListStartAst = getFirstAstNode(nonList);
209             if (nonList != nonListStartAst) {
210                 checkExpressionSubtree(nonListStartAst, expected, false, false);
211             }
212         }
213     }
214 
215     /**
216      * Get the child element representing the list of statements.
217      *
218      * @return the statement list child
219      */
220     protected DetailAST getListChild() {
221         return getMainAst().findFirstToken(TokenTypes.SLIST);
222     }
223 
224     /**
225      * Get the right parenthesis portion of the expression we are handling.
226      *
227      * @return the right parenthesis expression
228      */
229     private DetailAST getRightParen() {
230         return getMainAst().findFirstToken(TokenTypes.RPAREN);
231     }
232 
233     /**
234      * Get the left parenthesis portion of the expression we are handling.
235      *
236      * @return the left parenthesis expression
237      */
238     private DetailAST getLeftParen() {
239         return getMainAst().findFirstToken(TokenTypes.LPAREN);
240     }
241 
242     @Override
243     public void checkIndentation() {
244         checkTopLevelToken();
245         // separate to allow for eventual configuration
246         checkLeftParen(getLeftParen());
247         checkRightParen(getLeftParen(), getRightParen());
248         if (hasCurlies()) {
249             checkLeftCurly();
250             checkRightCurly();
251         }
252         final DetailAST listChild = getListChild();
253         if (listChild == null) {
254             checkNonListChild();
255         }
256         else {
257             // NOTE: switch statements usually don't have curlies
258             if (!hasCurlies() || !TokenUtil.areOnSameLine(getLeftCurly(), getRightCurly())) {
259                 checkChildren(listChild,
260                         getCheckedChildren(),
261                         getChildrenExpectedIndent(),
262                         true,
263                         canChildrenBeNested());
264             }
265         }
266     }
267 
268     /**
269      * Gets indentation level expected for children.
270      *
271      * @return indentation level expected for children
272      */
273     protected IndentLevel getChildrenExpectedIndent() {
274         IndentLevel indentLevel = new IndentLevel(getIndent(), getBasicOffset());
275         // if we have multileveled expected level then we should
276         // try to suggest single level to children using curlies'
277         // levels.
278         if (getIndent().isMultiLevel() && hasCurlies()) {
279             if (isOnStartOfLine(getLeftCurly())) {
280                 indentLevel = new IndentLevel(expandedTabsColumnNo(getLeftCurly())
281                         + getBasicOffset());
282             }
283             else if (isOnStartOfLine(getRightCurly())) {
284                 final IndentLevel level = new IndentLevel(curlyIndent(), getBasicOffset());
285                 indentLevel = IndentLevel.addAcceptable(level, level.getFirstIndentLevel()
286                         + getLineWrappingIndent());
287             }
288         }
289         if (hasCurlies() && isOnStartOfLine(getLeftCurly())) {
290             indentLevel = IndentLevel.addAcceptable(indentLevel,
291                     curlyIndent().getFirstIndentLevel() + getBasicOffset());
292         }
293         return indentLevel;
294     }
295 
296     @Override
297     public IndentLevel getSuggestedChildIndent(AbstractExpressionHandler child) {
298         return getChildrenExpectedIndent();
299     }
300 
301     /**
302      * A shortcut for {@code IndentationCheck} property.
303      *
304      * @return value of lineWrappingIndentation property
305      *         of {@code IndentationCheck}
306      */
307     private int getLineWrappingIndent() {
308         return getIndentCheck().getLineWrappingIndentation();
309     }
310 
311 }