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 java.util.List;
8   import net.sourceforge.pmd.lang.java.ast.ASTArgumentList;
9   import net.sourceforge.pmd.lang.java.ast.ASTBlock;
10  import net.sourceforge.pmd.lang.java.ast.ASTLoopStatement;
11  import net.sourceforge.pmd.lang.java.ast.ASTResource;
12  import net.sourceforge.pmd.lang.java.ast.ASTReturnStatement;
13  import net.sourceforge.pmd.lang.java.ast.ASTVariableAccess;
14  import net.sourceforge.pmd.lang.java.ast.ASTVariableDeclarator;
15  import net.sourceforge.pmd.lang.java.rule.AbstractJavaRulechainRule;
16  
17  /**
18   * Rule to check unnecessary local variables.
19   * @since 0.4
20   */
21  public final class UnnecessaryLocalRule extends AbstractJavaRulechainRule {
22  
23      /**
24       * Default constructor.
25       */
26      public UnnecessaryLocalRule() {
27          super(ASTVariableDeclarator.class);
28      }
29  
30      @Override
31      public Object visit(
32          final ASTVariableDeclarator variable,
33          final Object data
34      ) {
35          if (variable.getInitializer() != null
36              && variable.ancestors(ASTResource.class).first() == null) {
37              final String name = UnnecessaryLocalRule.variableName(variable);
38              if (!name.isEmpty()) {
39                  this.asCtx(data).addViolation(variable, name);
40              }
41          }
42          return data;
43      }
44  
45      private static boolean hasReturnOrArguments(
46          final List<ASTVariableAccess> uses
47      ) {
48          boolean result = false;
49          if (uses.size() == 1) {
50              final ASTVariableAccess use = uses.get(0);
51              final boolean loop = use.ancestors(ASTLoopStatement.class)
52                  .toStream().findAny().isPresent();
53              if (!loop
54                  && (use.ancestors(ASTReturnStatement.class).toStream()
55                  .findAny().isPresent()
56                  || use.ancestors(ASTArgumentList.class).toStream()
57                  .findAny().isPresent())
58              ) {
59                  result = true;
60              }
61          }
62          return result;
63      }
64  
65      private static String variableName(final ASTVariableDeclarator variable) {
66          String result = "";
67          final ASTBlock block = variable.ancestors(ASTBlock.class).first();
68          if (block != null
69              && !UnnecessaryLocalSkips.freshState(variable.getInitializer())) {
70              final String name = variable.getName();
71              final List<ASTVariableAccess> uses = block
72                  .descendants(ASTVariableAccess.class)
73                  .crossFindBoundaries()
74                  .filter(ref -> name.equals(ref.getName()))
75                  .toList();
76              if (UnnecessaryLocalRule.hasReturnOrArguments(uses)
77                  && !UnnecessaryLocalSkips.acrossBoundary(block, name, uses.size())
78                  && !UnnecessaryLocalSkips.interveningCall(variable, uses.get(0))) {
79                  result = name;
80              }
81          }
82          return result;
83      }
84  }