1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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
28
29
30 public class MethodCallHandler extends AbstractExpressionHandler {
31
32
33
34
35 private final IndentationContext context;
36
37
38
39
40
41
42
43
44
45 public MethodCallHandler(IndentationContext context,
46 DetailAST ast, AbstractExpressionHandler parent) {
47 super(context, "method call", ast, parent);
48 this.context = context;
49 }
50
51 @Override
52 protected IndentLevel getIndentImpl() {
53 final IndentLevel indentLevel;
54
55
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
63
64 else {
65 indentLevel = new IndentLevel(container.getIndent(),
66 getContext().getLineWrappingIndentation());
67 }
68 }
69 else if (getMainAst().getFirstChild().getType() == TokenTypes.LITERAL_NEW) {
70 indentLevel = super.getIndentImpl();
71 }
72 else {
73
74
75 final DetailAstSet astSet = new DetailAstSet(context);
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
91
92
93
94
95
96
97
98
99
100
101
102
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
111
112
113
114 private boolean isChainedMethodCallWrapped() {
115 final DetailAST main = getMainAst();
116 final DetailAST dot = main.getFirstChild();
117 final DetailAST target = dot.getFirstChild();
118
119 final DetailAST dot1 = target.getFirstChild();
120 DetailAST target1 = dot1;
121 while (target1.getFirstChild() != null
122 && target1.getType() != TokenTypes.METHOD_CALL) {
123 target1 = target1.getFirstChild();
124 }
125
126 return dot1.getType() == TokenTypes.DOT
127 && target1.getType() == TokenTypes.METHOD_CALL;
128 }
129
130
131
132
133
134
135
136
137
138 private static DetailAST getFirstAst(DetailAST ast) {
139
140
141
142 DetailAST astNode = ast.getFirstChild();
143 while (astNode.getType() == TokenTypes.DOT) {
144 astNode = astNode.getFirstChild();
145 }
146 return astNode;
147 }
148
149
150
151
152
153
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
169
170
171
172
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 && !isInvocationTarget(child)) {
179 suggestedLevel = new IndentLevel(suggestedLevel,
180 getBasicOffset(),
181 getContext().getLineWrappingIndentation());
182 }
183
184
185
186 if (getLineStart(rparen) == rparen.getColumnNo()) {
187 suggestedLevel = IndentLevel.addAcceptable(suggestedLevel, new IndentLevel(
188 getParent().getSuggestedChildIndent(this),
189 getContext().getLineWrappingIndentation()
190 ));
191 }
192
193 return suggestedLevel;
194 }
195
196
197
198
199
200
201
202
203
204 private boolean isInvocationTarget(AbstractExpressionHandler child) {
205 boolean crossedElist = false;
206 DetailAST node = child.getMainAst().getParent();
207 while (node != null && node != getMainAst()) {
208 if (node.getType() == TokenTypes.ELIST) {
209 crossedElist = true;
210 break;
211 }
212 node = node.getParent();
213 }
214 return !crossedElist;
215 }
216
217 @Override
218 public void checkIndentation() {
219 DetailAST lparen = null;
220 if (getMainAst().getType() == TokenTypes.METHOD_CALL) {
221 final DetailAST exprNode = getMainAst().getParent();
222 if (exprNode.getParent().getType() == TokenTypes.SLIST) {
223 checkExpressionSubtree(getMainAst().getFirstChild(), getIndent(), false, false);
224 lparen = getMainAst();
225 }
226 }
227 else {
228
229 lparen = getMainAst().getFirstChild();
230 }
231
232 if (lparen != null) {
233 final DetailAST rparen = getMainAst().findFirstToken(TokenTypes.RPAREN);
234 checkLeftParen(lparen);
235
236 if (!TokenUtil.areOnSameLine(rparen, lparen)) {
237 checkExpressionSubtree(
238 getMainAst().findFirstToken(TokenTypes.ELIST),
239 new IndentLevel(getIndent(), getBasicOffset()),
240 false, true);
241
242 checkRparenIndent(lparen, rparen);
243 checkWrappingIndentation(getMainAst(), getCallLastNode(getMainAst()));
244 }
245 }
246 else if (isSimpleReturnMethodCall()) {
247 checkReturnStatementCallArguments();
248 }
249 }
250
251
252
253
254
255
256
257
258
259
260
261 private void checkReturnStatementCallArguments() {
262 final DetailAST rparen = getMainAst().findFirstToken(TokenTypes.RPAREN);
263 if (!TokenUtil.areOnSameLine(rparen, getMainAst())) {
264 final DetailAST elist = getMainAst().findFirstToken(TokenTypes.ELIST);
265 final IndentLevel argIndent = new IndentLevel(getIndent(), getBasicOffset());
266 for (DetailAST arg = elist.getFirstChild(); arg != null;
267 arg = arg.getNextSibling()) {
268 if (arg.getType() == TokenTypes.EXPR) {
269 final DetailAST firstToken = getFirstAstNode(arg);
270
271
272 if (firstToken.getType() != TokenTypes.LITERAL_NEW
273 && isOnStartOfLine(firstToken)) {
274 final int actualColumn = expandedTabsColumnNo(firstToken);
275 if (argIndent.isGreaterThan(actualColumn)) {
276 logChildError(firstToken, actualColumn, argIndent);
277 }
278 }
279 }
280 }
281 }
282 }
283
284
285
286
287
288
289
290 private boolean isSimpleReturnMethodCall() {
291 final DetailAST exprNode = getMainAst().getParent();
292 return getMainAst().getFirstChild().getType() != TokenTypes.DOT
293 && exprNode.getParent().getType() == TokenTypes.LITERAL_RETURN;
294 }
295
296
297
298
299
300
301
302 private void checkRparenIndent(DetailAST lparen, DetailAST rparen) {
303 final int rparenLevel = expandedTabsColumnNo(rparen);
304 final int lparenLevel = expandedTabsColumnNo(lparen);
305
306 final IndentLevel standardIndent = getIndent();
307
308
309 IndentLevel enhancedIndent = standardIndent;
310 if (getParent() instanceof MethodCallHandler) {
311 final int lineStart = getLineStart(getFirstAst(getMainAst()));
312 if (lineStart != standardIndent.getFirstIndentLevel()) {
313 enhancedIndent = IndentLevel.addAcceptable(standardIndent,
314 new IndentLevel(lineStart));
315 }
316 }
317
318 if (rparenLevel != lparenLevel + 1
319 && !enhancedIndent.isAcceptable(rparenLevel)
320 && isOnStartOfLine(rparen)) {
321 logError(rparen, "rparen", rparenLevel);
322 }
323 }
324
325 @Override
326 protected boolean shouldIncreaseIndent() {
327 return false;
328 }
329
330
331
332
333
334
335
336
337
338 private static DetailAST getCallLastNode(DetailAST firstNode) {
339 return firstNode.getLastChild();
340 }
341
342 }