1 /*
2 * SPDX-FileCopyrightText: Copyright (c) 2011-2026 Yegor Bugayenko
3 * SPDX-License-Identifier: MIT
4 */
5 package com.qulice.checkstyle;
6
7 import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
8 import com.puppycrawl.tools.checkstyle.api.DetailAST;
9 import com.puppycrawl.tools.checkstyle.api.TokenTypes;
10
11 /**
12 * Checks that constructors do not contain any method calls.
13 *
14 * <p>A constructor must only assign fields from constructor parameters
15 * or from newly created objects, and may delegate to another constructor
16 * via {@code this(...)} or {@code super(...)}. Calling any method
17 * (static or instance) from inside a constructor is forbidden,
18 * including as the right-hand side of a field assignment
19 * (e.g. {@code this.bar = Foo.createBar()}), as an argument to a
20 * delegating constructor call, or as a nested argument to a {@code new}
21 * expression.
22 *
23 * <p>Method calls nested inside lambda bodies or anonymous class bodies
24 * are not considered constructor code, because they are not executed
25 * at construction time: only the lambda object or the anonymous class
26 * instance is created. Such subtrees are skipped.
27 *
28 * @since 0.24
29 */
30 public final class ConstructorsCodeFreeCheck extends AbstractCheck {
31
32 @Override
33 public int[] getDefaultTokens() {
34 return new int[] {TokenTypes.CTOR_DEF};
35 }
36
37 @Override
38 public int[] getAcceptableTokens() {
39 return this.getDefaultTokens();
40 }
41
42 @Override
43 public int[] getRequiredTokens() {
44 return this.getDefaultTokens();
45 }
46
47 @Override
48 public void visitToken(final DetailAST ast) {
49 final DetailAST body = ast.findFirstToken(TokenTypes.SLIST);
50 if (body != null) {
51 this.reportCalls(body);
52 }
53 }
54
55 /**
56 * Reports every method call found anywhere in the given subtree,
57 * except those nested inside lambda bodies or anonymous class bodies.
58 * @param node Root of the subtree to scan
59 */
60 private void reportCalls(final DetailAST node) {
61 for (DetailAST child = node.getFirstChild();
62 child != null; child = child.getNextSibling()) {
63 final int type = child.getType();
64 if (type == TokenTypes.LAMBDA || type == TokenTypes.OBJBLOCK) {
65 continue;
66 }
67 if (type == TokenTypes.METHOD_CALL) {
68 this.log(
69 child.getLineNo(),
70 "Constructor must not contain method calls"
71 );
72 }
73 this.reportCalls(child);
74 }
75 }
76 }