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