View Javadoc
1   /*
2    * SPDX-FileCopyrightText: Copyright (c) 2011-2026 Yegor Bugayenko
3    * SPDX-License-Identifier: MIT
4    */
5   package com.qulice.pmd;
6   
7   import com.qulice.spi.Environment;
8   import com.qulice.spi.Violation;
9   import java.io.File;
10  import java.util.Collections;
11  import java.util.List;
12  import java.util.stream.Collectors;
13  import org.cactoos.text.TextOf;
14  import org.hamcrest.MatcherAssert;
15  import org.hamcrest.Matchers;
16  import org.junit.jupiter.api.Test;
17  
18  /**
19   * Test case for UnnecessaryLocalRule.
20   *
21   * @since 0.24
22   */
23  final class UnnecessaryLocalRuleTest {
24  
25      /**
26       * The name of the rule under test.
27       */
28      private static final String RULE = "UnnecessaryLocalRule";
29  
30      @Test
31      void detectsUnnecessaryLocalVariableOnReturn() throws Exception {
32          MatcherAssert.assertThat(
33              "UnnecessaryLocalRule should fire on a local variable used only in return",
34              this.violations("UnnecessaryLocal.java"),
35              Matchers.hasItem(UnnecessaryLocalRuleTest.RULE)
36          );
37      }
38  
39      @Test
40      void doesNotFireWhenVariableIsUsedInLoop() throws Exception {
41          MatcherAssert.assertThat(
42              "UnnecessaryLocalRule should not fire when variable is used in a loop",
43              this.violations("UnnecessaryLocalInLoop.java"),
44              Matchers.not(Matchers.hasItem(UnnecessaryLocalRuleTest.RULE))
45          );
46      }
47  
48      @Test
49      void doesNotFireWhenVariableIsUsedMoreThanOnce() throws Exception {
50          MatcherAssert.assertThat(
51              "UnnecessaryLocalRule should not fire when variable is used more than once",
52              this.violations("UnnecessaryLocalUsedTwice.java"),
53              Matchers.not(Matchers.hasItem(UnnecessaryLocalRuleTest.RULE))
54          );
55      }
56  
57      @Test
58      void doesNotFireWhenUseIsAcrossAnonymousClassOrLambda() throws Exception {
59          MatcherAssert.assertThat(
60              "UnnecessaryLocalRule should not fire when the only use is inside an anonymous class or lambda body",
61              this.violations("UnnecessaryLocalAcrossAnonymousClass.java"),
62              Matchers.not(Matchers.hasItem(UnnecessaryLocalRuleTest.RULE))
63          );
64      }
65  
66      @Test
67      void doesNotFireWhenInitialiserCapturesFreshState() throws Exception {
68          MatcherAssert.assertThat(
69              "UnnecessaryLocalRule should not fire when the initialiser captures a clock or randomness source",
70              this.violations("UnnecessaryLocalCapturedTimestamp.java"),
71              Matchers.not(Matchers.hasItem(UnnecessaryLocalRuleTest.RULE))
72          );
73      }
74  
75      @Test
76      void doesNotFireWhenInterveningCallMutatesSameTarget() throws Exception {
77          MatcherAssert.assertThat(
78              "UnnecessaryLocalRule should not fire when a later statement calls the same target",
79              this.violations("UnnecessaryLocalDestructiveCleanup.java"),
80              Matchers.not(Matchers.hasItem(UnnecessaryLocalRuleTest.RULE))
81          );
82      }
83  
84      @Test
85      void doesNotFireWhenFieldAccessQualifierIsMutated() throws Exception {
86          MatcherAssert.assertThat(
87              "UnnecessaryLocalRule should not fire when a later call mutates the field-access initialiser's qualifier",
88              this.violations("UnnecessaryLocalCapturedFieldAccess.java"),
89              Matchers.not(Matchers.hasItem(UnnecessaryLocalRuleTest.RULE))
90          );
91      }
92  
93      @Test
94      void doesNotFireWhenLocalCapturedBeforeUnrelatedCall() throws Exception {
95          MatcherAssert.assertThat(
96              "UnnecessaryLocalRule should not fire when the local pins evaluation order before an unrelated side-effecting call",
97              this.violations("UnnecessaryLocalCapturedBeforeCall.java"),
98              Matchers.not(Matchers.hasItem(UnnecessaryLocalRuleTest.RULE))
99          );
100     }
101 
102     @Test
103     void doesNotFireWhenInterveningCallSitsBeforeNestedUse() throws Exception {
104         MatcherAssert.assertThat(
105             "UnnecessaryLocalRule should not fire when a side-effecting statement intervenes before a use nested in an if block",
106             this.violations("UnnecessaryLocalInterveningNestedBlock.java"),
107             Matchers.not(Matchers.hasItem(UnnecessaryLocalRuleTest.RULE))
108         );
109     }
110 
111     @Test
112     void doesNotFireWhenConstructorCapturedBeforeCall() throws Exception {
113         MatcherAssert.assertThat(
114             "UnnecessaryLocalRule should not fire when a constructor-call local pins evaluation order before an intervening call",
115             this.violations("UnnecessaryLocalConstructorBeforeCall.java"),
116             Matchers.not(Matchers.hasItem(UnnecessaryLocalRuleTest.RULE))
117         );
118     }
119 
120     @Test
121     void doesNotFireOnTryWithResourcesResource() throws Exception {
122         MatcherAssert.assertThat(
123             "UnnecessaryLocalRule should not fire on a try-with-resources resource variable",
124             this.violations("UnnecessaryLocalTryWithResources.java"),
125             Matchers.not(Matchers.hasItem(UnnecessaryLocalRuleTest.RULE))
126         );
127     }
128 
129     private List<String> violations(final String file) throws Exception {
130         final String name = String.format("src/main/java/foo/%s", file);
131         final Environment env = new Environment.Mock().withFile(
132             name,
133             new TextOf(this.getClass().getResourceAsStream(file)).asString()
134         );
135         return new PmdValidator(env).validate(
136             Collections.singletonList(new File(env.basedir(), name))
137         ).stream().map(Violation::name).collect(Collectors.toList());
138     }
139 }