View Javadoc
1   /*
2    * SPDX-FileCopyrightText: Copyright (c) 2011-2025 Yegor Bugayenko
3    * SPDX-License-Identifier: MIT
4    */
5   package com.qulice.pmd.rules;
6   
7   import java.util.Arrays;
8   import java.util.HashMap;
9   import java.util.List;
10  import java.util.Map;
11  import net.sourceforge.pmd.lang.ast.Node;
12  import net.sourceforge.pmd.lang.java.ast.ASTExpression;
13  import net.sourceforge.pmd.lang.java.ast.ASTMethodDeclaration;
14  import net.sourceforge.pmd.lang.java.ast.ASTReferenceType;
15  import net.sourceforge.pmd.lang.java.ast.ASTResultType;
16  import net.sourceforge.pmd.lang.java.ast.ASTType;
17  import net.sourceforge.pmd.lang.java.ast.ASTVariableDeclaratorId;
18  import net.sourceforge.pmd.lang.java.ast.JavaNode;
19  import net.sourceforge.pmd.lang.java.symboltable.JavaNameOccurrence;
20  import net.sourceforge.pmd.lang.java.symboltable.MethodNameDeclaration;
21  import net.sourceforge.pmd.lang.symboltable.NameOccurrence;
22  import net.sourceforge.pmd.lang.symboltable.Scope;
23  
24  /**
25   * Rule to prohibit use of String.length() when checking for empty string.
26   * String.isEmpty() should be used instead.
27   * @since 0.18
28   */
29  @SuppressWarnings("deprecation")
30  public final class UseStringIsEmptyRule
31      extends net.sourceforge.pmd.lang.java.rule.AbstractInefficientZeroCheck {
32  
33      @Override
34      public boolean appliesToClassName(final String name) {
35          return net.sourceforge.pmd.util.StringUtil.isSame(
36              name, "String", true, true, true
37          );
38      }
39  
40      @Override
41      public Map<String, List<String>> getComparisonTargets() {
42          final Map<String, List<String>> rules = new HashMap<>();
43          rules.put("<", Arrays.asList("1"));
44          rules.put(">", Arrays.asList("0"));
45          rules.put("==", Arrays.asList("0"));
46          rules.put("!=", Arrays.asList("0"));
47          rules.put(">=", Arrays.asList("0", "1"));
48          rules.put("<=", Arrays.asList("0"));
49          return rules;
50      }
51  
52      @Override
53      public boolean isTargetMethod(final JavaNameOccurrence occ) {
54          final NameOccurrence name = occ.getNameForWhichThisIsAQualifier();
55          return name != null && "length".equals(name.getImage());
56      }
57  
58      @Override
59      public Object visit(
60          final ASTVariableDeclaratorId variable, final Object data
61      ) {
62          final Node node = variable.getTypeNameNode();
63          if (node instanceof ASTReferenceType) {
64              final Class<?> clazz = variable.getType();
65              final String type = variable.getNameDeclaration().getTypeImage();
66              if (clazz != null && !clazz.isArray()
67                  && this.appliesToClassName(type)
68              ) {
69                  final List<NameOccurrence> declarations = variable.getUsages();
70                  this.checkDeclarations(declarations, data);
71              }
72          }
73          variable.childrenAccept(this, data);
74          return data;
75      }
76  
77      @Override
78      public Object visit(
79          final ASTMethodDeclaration declaration, final Object data
80      ) {
81          final ASTResultType result = declaration.getResultType();
82          if (!result.isVoid()) {
83              final ASTType node = (ASTType) result.jjtGetChild(0);
84              final Class<?> clazz = node.getType();
85              final String type = node.getTypeImage();
86              if (clazz != null && !clazz.isArray()
87                  && this.appliesToClassName(type)
88              ) {
89                  final Scope scope = declaration.getScope().getParent();
90                  final MethodNameDeclaration method = new MethodNameDeclaration(
91                      declaration.getMethodDeclarator()
92                  );
93                  final List<NameOccurrence> declarations = scope
94                      .getDeclarations(MethodNameDeclaration.class)
95                      .get(method);
96                  this.checkDeclarations(declarations, data);
97              }
98          }
99          declaration.childrenAccept(this, data);
100         return data;
101     }
102 
103     /**
104      * Checks all uses of a variable or method with a String type.
105      * @param occurrences Variable or method occurrences.
106      * @param data Rule context.
107      */
108     private void checkDeclarations(
109         final Iterable<NameOccurrence> occurrences, final Object data
110     ) {
111         for (final NameOccurrence occurrence : occurrences) {
112             final JavaNameOccurrence jocc = (JavaNameOccurrence) occurrence;
113             if (this.isTargetMethod(jocc)) {
114                 final JavaNode location = jocc.getLocation();
115                 final Node expr = location.getFirstParentOfType(
116                     ASTExpression.class
117                 );
118                 this.checkNodeAndReport(
119                     data, occurrence.getLocation(), expr.jjtGetChild(0)
120                 );
121             }
122         }
123     }
124 }