1 /*
2 * SPDX-FileCopyrightText: Copyright (c) 2011-2025 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 import java.util.LinkedList;
11 import java.util.List;
12
13 /**
14 * Checks for not using concatenation of string literals in any form.
15 *
16 * <p>The following constructs are prohibited:
17 *
18 * <pre>
19 * String a = "done in " + time + " seconds";
20 * System.out.println("File not found: " + file);
21 * x += "done";
22 * </pre>
23 *
24 * <p>You should avoid string concatenation at all cost. Why? There are two
25 * reasons: readability of the code and translateability. First of all it's
26 * difficult to understand how the text will look after concatenation,
27 * especially if the text is long and there are more than a few {@code +}
28 * operators. Second, you won't be able to translate your text to other
29 * languages later, if you don't have solid string literals.
30 *
31 * <p>There are two alternatives to concatenation: {@link StringBuilder}
32 * and {@link String#format(String,Object[])}.
33 *
34 * @since 0.3
35 */
36 public final class StringLiteralsConcatenationCheck extends AbstractCheck {
37
38 @Override
39 public int[] getDefaultTokens() {
40 return new int[] {TokenTypes.OBJBLOCK};
41 }
42
43 @Override
44 public int[] getAcceptableTokens() {
45 return this.getDefaultTokens();
46 }
47
48 @Override
49 public int[] getRequiredTokens() {
50 return this.getDefaultTokens();
51 }
52
53 @Override
54 public void visitToken(final DetailAST ast) {
55 final List<DetailAST> pluses = this.findChildAstsOfType(
56 ast,
57 TokenTypes.PLUS,
58 TokenTypes.PLUS_ASSIGN
59 );
60 for (final DetailAST plus : pluses) {
61 if (!this.findChildAstsOfType(
62 plus,
63 TokenTypes.STRING_LITERAL
64 ).isEmpty()) {
65 this.log(plus, "Concatenation of string literals prohibited");
66 }
67 }
68 }
69
70 /**
71 * Recursively traverse the <code>tree</code> and return all ASTs subtrees
72 * matching any type from <code>types</code>.
73 * @param tree AST to traverse.
74 * @param types Token types to match against.
75 * @return All ASTs subtrees with token types matching any from
76 * <tt>types</tt>.
77 * @see TokenTypes
78 */
79 private List<DetailAST> findChildAstsOfType(final DetailAST tree,
80 final int... types) {
81 final List<DetailAST> children = new LinkedList<>();
82 DetailAST child = tree.getFirstChild();
83 while (child != null) {
84 if (StringLiteralsConcatenationCheck.isOfType(child, types)) {
85 children.add(child);
86 } else {
87 children.addAll(this.findChildAstsOfType(child, types));
88 }
89 child = child.getNextSibling();
90 }
91 return children;
92 }
93
94 /**
95 * Checks if this <code>ast</code> is of any type from <code>types</code>.
96 * @param ast AST to check.
97 * @param types Token types to match against.
98 * @return True if of type, false otherwise.
99 * @see TokenTypes
100 */
101 private static boolean isOfType(final DetailAST ast, final int... types) {
102 boolean yes = false;
103 for (final int type : types) {
104 if (ast.getType() == type) {
105 yes = true;
106 break;
107 }
108 }
109 return yes;
110 }
111 }