View Javadoc
1   ///////////////////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code and other text files for adherence to a set of rules.
3   // Copyright (C) 2001-2025 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 method calls.
28   *
29   */
30  public class MethodCallHandler extends AbstractExpressionHandler {
31  
32      /**
33       * The instance of {@code IndentationCheck} used by this class.
34       */
35      private final IndentationCheck indentCheck;
36  
37      /**
38       * Construct an instance of this handler with the given indentation check,
39       * abstract syntax tree, and parent handler.
40       *
41       * @param indentCheck   the indentation check
42       * @param ast           the abstract syntax tree
43       * @param parent        the parent handler
44       */
45      public MethodCallHandler(IndentationCheck indentCheck,
46          DetailAST ast, AbstractExpressionHandler parent) {
47          super(indentCheck, "method call", ast, parent);
48          this.indentCheck = indentCheck;
49      }
50  
51      @Override
52      protected IndentLevel getIndentImpl() {
53          final IndentLevel indentLevel;
54          // if inside a method call's params, this could be part of
55          // an expression, so get the previous line's start
56          if (getParent() instanceof MethodCallHandler container) {
57              if (TokenUtil.areOnSameLine(container.getMainAst(), getMainAst())
58                      || isChainedMethodCallWrapped()
59                      || areMethodsChained(container.getMainAst(), getMainAst())) {
60                  indentLevel = container.getIndent();
61              }
62              // we should increase indentation only if this is the first
63              // chained method call which was moved to the next line
64              else {
65                  indentLevel = new IndentLevel(container.getIndent(),
66                      getIndentCheck().getLineWrappingIndentation());
67              }
68          }
69          else if (getMainAst().getFirstChild().getType() == TokenTypes.LITERAL_NEW) {
70              indentLevel = super.getIndentImpl();
71          }
72          else {
73              // if our expression isn't first on the line, just use the start
74              // of the line
75              final DetailAstSet astSet = new DetailAstSet(indentCheck);
76              findSubtreeAst(astSet, getMainAst().getFirstChild(), true);
77              final int firstCol = expandedTabsColumnNo(astSet.firstLine());
78              final int lineStart = getLineStart(getFirstAst(getMainAst()));
79              if (lineStart == firstCol) {
80                  indentLevel = super.getIndentImpl();
81              }
82              else {
83                  indentLevel = new IndentLevel(lineStart);
84              }
85          }
86          return indentLevel;
87      }
88  
89      /**
90       * Checks if ast2 is a chained method call that starts on the same level as ast1 ends.
91       * In other words, if the right paren of ast1 is on the same level as the lparen of ast2:
92       * {@code
93       *     value.methodOne(
94       *         argument1
95       *     ).methodTwo(
96       *         argument2
97       *     );
98       * }
99       *
100      * @param ast1 Ast1
101      * @param ast2 Ast2
102      * @return True if ast2 begins on the same level that ast1 ends
103      */
104     private static boolean areMethodsChained(DetailAST ast1, DetailAST ast2) {
105         final DetailAST rparen = ast1.findFirstToken(TokenTypes.RPAREN);
106         return TokenUtil.areOnSameLine(rparen, ast2);
107     }
108 
109     /**
110      * If this is the first chained method call which was moved to the next line.
111      *
112      * @return true if chained class are wrapped
113      */
114     private boolean isChainedMethodCallWrapped() {
115         boolean result = false;
116         final DetailAST main = getMainAst();
117         final DetailAST dot = main.getFirstChild();
118         final DetailAST target = dot.getFirstChild();
119 
120         final DetailAST dot1 = target.getFirstChild();
121         final DetailAST target1 = dot1.getFirstChild();
122 
123         if (dot1.getType() == TokenTypes.DOT
124             && target1.getType() == TokenTypes.METHOD_CALL) {
125             result = true;
126         }
127         return result;
128     }
129 
130     /**
131      * Get the first AST of the specified method call.
132      *
133      * @param ast
134      *            the method call
135      *
136      * @return the first AST of the specified method call
137      */
138     private static DetailAST getFirstAst(DetailAST ast) {
139         // walk down the first child part of the dots that make up a method
140         // call name
141 
142         DetailAST astNode = ast.getFirstChild();
143         while (astNode.getType() == TokenTypes.DOT) {
144             astNode = astNode.getFirstChild();
145         }
146         return astNode;
147     }
148 
149     /**
150      * Returns method or constructor name. For {@code foo(arg)} it is `foo`, for
151      *     {@code foo.bar(arg)} it is `bar` for {@code super(arg)} it is 'super'.
152      *
153      * @return TokenTypes.IDENT node for a method call, TokenTypes.SUPER_CTOR_CALL otherwise.
154      */
155     private DetailAST getMethodIdentAst() {
156         DetailAST ast = getMainAst();
157         if (ast.getType() != TokenTypes.SUPER_CTOR_CALL) {
158             ast = ast.getFirstChild();
159             if (ast.getType() == TokenTypes.DOT) {
160                 ast = ast.getLastChild();
161             }
162         }
163         return ast;
164     }
165 
166     @Override
167     public IndentLevel getSuggestedChildIndent(AbstractExpressionHandler child) {
168         // for whatever reason a method that crosses lines, like asList
169         // here:
170         //            System.out.println("methods are: " + Arrays.asList(
171         //                new String[] {"method"}).toString());
172         // will not have the right line num, so just get the child name
173 
174         final DetailAST ident = getMethodIdentAst();
175         final DetailAST rparen = getMainAst().findFirstToken(TokenTypes.RPAREN);
176         IndentLevel suggestedLevel = new IndentLevel(getLineStart(ident));
177         if (!TokenUtil.areOnSameLine(child.getMainAst().getFirstChild(), ident)) {
178             suggestedLevel = new IndentLevel(suggestedLevel,
179                     getBasicOffset(),
180                     getIndentCheck().getLineWrappingIndentation());
181         }
182 
183         // If the right parenthesis is at the start of a line;
184         // include line wrapping in suggested indent level.
185         if (getLineStart(rparen) == rparen.getColumnNo()) {
186             suggestedLevel = IndentLevel.addAcceptable(suggestedLevel, new IndentLevel(
187                     getParent().getSuggestedChildIndent(this),
188                     getIndentCheck().getLineWrappingIndentation()
189             ));
190         }
191 
192         return suggestedLevel;
193     }
194 
195     @Override
196     public void checkIndentation() {
197         DetailAST lparen = null;
198         if (getMainAst().getType() == TokenTypes.METHOD_CALL) {
199             final DetailAST exprNode = getMainAst().getParent();
200             if (exprNode.getParent().getType() == TokenTypes.SLIST) {
201                 checkExpressionSubtree(getMainAst().getFirstChild(), getIndent(), false, false);
202                 lparen = getMainAst();
203             }
204         }
205         else {
206             // TokenTypes.CTOR_CALL|TokenTypes.SUPER_CTOR_CALL
207             lparen = getMainAst().getFirstChild();
208         }
209 
210         if (lparen != null) {
211             final DetailAST rparen = getMainAst().findFirstToken(TokenTypes.RPAREN);
212             checkLeftParen(lparen);
213 
214             if (!TokenUtil.areOnSameLine(rparen, lparen)) {
215                 checkExpressionSubtree(
216                     getMainAst().findFirstToken(TokenTypes.ELIST),
217                     new IndentLevel(getIndent(), getBasicOffset()),
218                     false, true);
219 
220                 checkRightParen(lparen, rparen);
221                 checkWrappingIndentation(getMainAst(), getCallLastNode(getMainAst()));
222             }
223         }
224     }
225 
226     @Override
227     protected boolean shouldIncreaseIndent() {
228         return false;
229     }
230 
231     /**
232      * Returns method or constructor call right paren.
233      *
234      * @param firstNode
235      *          call ast(TokenTypes.METHOD_CALL|TokenTypes.CTOR_CALL|TokenTypes.SUPER_CTOR_CALL)
236      * @return ast node containing right paren for specified method or constructor call. If
237      *     method calls are chained returns right paren for last call.
238      */
239     private static DetailAST getCallLastNode(DetailAST firstNode) {
240         return firstNode.getLastChild();
241     }
242 
243 }