View Javadoc
1   /*
2    * SPDX-FileCopyrightText: Copyright (c) 2011-2026 Yegor Bugayenko
3    * SPDX-License-Identifier: MIT
4    */
5   package com.qulice.pmd.rules;
6   
7   import net.sourceforge.pmd.lang.java.ast.ASTExpression;
8   import net.sourceforge.pmd.lang.java.ast.ASTInfixExpression;
9   import net.sourceforge.pmd.lang.java.ast.ASTMethodCall;
10  import net.sourceforge.pmd.lang.java.ast.ASTNumericLiteral;
11  import net.sourceforge.pmd.lang.java.rule.AbstractJavaRulechainRule;
12  import net.sourceforge.pmd.lang.java.types.JTypeMirror;
13  
14  /**
15   * Rule to prohibit use of String.length() when checking for empty string.
16   * String.isEmpty() should be used instead.
17   *
18   * @since 0.18
19   */
20  public final class UseStringIsEmptyRule extends AbstractJavaRulechainRule {
21      public UseStringIsEmptyRule() {
22          super(ASTInfixExpression.class);
23      }
24  
25      @Override
26      public Object visit(final ASTInfixExpression expr, final Object data) {
27          if (isComparison(expr) && (
28              matchesLengthCheck(
29                  expr.getLeftOperand(),
30                  expr.getRightOperand()
31              )
32                  || matchesLengthCheck(
33                  expr.getRightOperand(),
34                  expr.getLeftOperand()
35              )
36              )
37          ) {
38              asCtx(data).addViolation(expr);
39          }
40          return data;
41      }
42  
43      private static boolean isComparison(final ASTInfixExpression expr) {
44          final boolean result;
45          switch (expr.getOperator()) {
46              case EQ:
47              case NE:
48              case GT:
49              case LT:
50              case GE:
51              case LE:
52                  result = true;
53                  break;
54              default:
55                  result = false;
56                  break;
57          }
58          return result;
59      }
60  
61      /**
62       * Checks if length is length() or literal is 0 or 1.
63       *
64       * @param length The method
65       * @param literal The number
66       * @return True if matches, false otherwise
67       * @checkstyle BooleanExpressionComplexityCheck (20 lines)
68       */
69      private static boolean matchesLengthCheck(
70          final ASTExpression length,
71          final ASTExpression literal
72      ) {
73          boolean result = false;
74          if (length != null && literal != null && isZeroOrOneLiteral(literal)
75              && length instanceof ASTMethodCall) {
76              final ASTMethodCall call = (ASTMethodCall) length;
77              result = "length".equals(call.getMethodName())
78                  && call.getArguments().isEmpty()
79                  && call.getQualifier() != null
80                  && isStringExpression(call.getQualifier());
81          }
82          return result;
83      }
84  
85      private static boolean isZeroOrOneLiteral(final ASTExpression expr) {
86          boolean matches = false;
87          if (expr instanceof ASTNumericLiteral) {
88              final ASTNumericLiteral lit = (ASTNumericLiteral) expr;
89              final String image = lit.getImage();
90              matches = "0".equals(image) || "1".equals(image);
91          }
92          return matches;
93      }
94  
95      private static boolean isStringExpression(final ASTExpression expr) {
96          final JTypeMirror type = expr.getTypeMirror();
97          return type.isClassOrInterface()
98              && "java.lang.String".equals(type.toString());
99      }
100 }