001/////////////////////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code and other text files for adherence to a set of rules. 003// Copyright (C) 2001-2025 the original author or authors. 004// 005// This library is free software; you can redistribute it and/or 006// modify it under the terms of the GNU Lesser General Public 007// License as published by the Free Software Foundation; either 008// version 2.1 of the License, or (at your option) any later version. 009// 010// This library is distributed in the hope that it will be useful, 011// but WITHOUT ANY WARRANTY; without even the implied warranty of 012// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 013// Lesser General Public License for more details. 014// 015// You should have received a copy of the GNU Lesser General Public 016// License along with this library; if not, write to the Free Software 017// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 018/////////////////////////////////////////////////////////////////////////////////////////////// 019 020package com.puppycrawl.tools.checkstyle.checks.indentation; 021 022import com.puppycrawl.tools.checkstyle.api.DetailAST; 023import com.puppycrawl.tools.checkstyle.api.TokenTypes; 024import com.puppycrawl.tools.checkstyle.utils.TokenUtil; 025 026/** 027 * Handler for method calls. 028 * 029 */ 030public class MethodCallHandler extends AbstractExpressionHandler { 031 032 /** 033 * The instance of {@code IndentationCheck} used by this class. 034 */ 035 private final IndentationCheck indentCheck; 036 037 /** 038 * Construct an instance of this handler with the given indentation check, 039 * abstract syntax tree, and parent handler. 040 * 041 * @param indentCheck the indentation check 042 * @param ast the abstract syntax tree 043 * @param parent the parent handler 044 */ 045 public MethodCallHandler(IndentationCheck indentCheck, 046 DetailAST ast, AbstractExpressionHandler parent) { 047 super(indentCheck, "method call", ast, parent); 048 this.indentCheck = indentCheck; 049 } 050 051 @Override 052 protected IndentLevel getIndentImpl() { 053 final IndentLevel indentLevel; 054 // if inside a method call's params, this could be part of 055 // an expression, so get the previous line's start 056 if (getParent() instanceof MethodCallHandler container) { 057 if (TokenUtil.areOnSameLine(container.getMainAst(), getMainAst()) 058 || isChainedMethodCallWrapped() 059 || areMethodsChained(container.getMainAst(), getMainAst())) { 060 indentLevel = container.getIndent(); 061 } 062 // we should increase indentation only if this is the first 063 // chained method call which was moved to the next line 064 else { 065 indentLevel = new IndentLevel(container.getIndent(), 066 getIndentCheck().getLineWrappingIndentation()); 067 } 068 } 069 else if (getMainAst().getFirstChild().getType() == TokenTypes.LITERAL_NEW) { 070 indentLevel = super.getIndentImpl(); 071 } 072 else { 073 // if our expression isn't first on the line, just use the start 074 // of the line 075 final DetailAstSet astSet = new DetailAstSet(indentCheck); 076 findSubtreeAst(astSet, getMainAst().getFirstChild(), true); 077 final int firstCol = expandedTabsColumnNo(astSet.firstLine()); 078 final int lineStart = getLineStart(getFirstAst(getMainAst())); 079 if (lineStart == firstCol) { 080 indentLevel = super.getIndentImpl(); 081 } 082 else { 083 indentLevel = new IndentLevel(lineStart); 084 } 085 } 086 return indentLevel; 087 } 088 089 /** 090 * Checks if ast2 is a chained method call that starts on the same level as ast1 ends. 091 * In other words, if the right paren of ast1 is on the same level as the lparen of ast2: 092 * {@code 093 * value.methodOne( 094 * argument1 095 * ).methodTwo( 096 * argument2 097 * ); 098 * } 099 * 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}