View Javadoc
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 }