]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-14192 Deprecating rule keys may break users' exclusion settings
authorDuarte Meneses <duarte.meneses@sonarsource.com>
Thu, 11 Mar 2021 23:04:42 +0000 (17:04 -0600)
committersonartech <sonartech@sonarsource.com>
Wed, 17 Mar 2021 20:08:34 +0000 (20:08 +0000)
17 files changed:
sonar-plugin-api-impl/src/main/java/org/sonar/api/batch/rule/internal/ActiveRulesBuilder.java
sonar-plugin-api-impl/src/main/java/org/sonar/api/batch/rule/internal/DefaultActiveRule.java
sonar-plugin-api-impl/src/main/java/org/sonar/api/batch/rule/internal/DefaultActiveRules.java
sonar-plugin-api-impl/src/main/java/org/sonar/api/batch/rule/internal/NewActiveRule.java
sonar-plugin-api-impl/src/test/java/org/sonar/api/batch/rule/internal/DefaultActiveRulesTest.java [new file with mode: 0644]
sonar-scanner-engine/src/main/java/org/sonar/scanner/issue/ignore/EnforceIssuesFilter.java
sonar-scanner-engine/src/main/java/org/sonar/scanner/issue/ignore/IgnoreIssuesFilter.java
sonar-scanner-engine/src/main/java/org/sonar/scanner/issue/ignore/pattern/AbstractPatternInitializer.java
sonar-scanner-engine/src/main/java/org/sonar/scanner/issue/ignore/pattern/IssuePattern.java
sonar-scanner-engine/src/main/java/org/sonar/scanner/rule/ActiveRulesProvider.java
sonar-scanner-engine/src/test/java/org/sonar/scanner/issue/ignore/EnforceIssuesFilterTest.java
sonar-scanner-engine/src/test/java/org/sonar/scanner/issue/ignore/IgnoreIssuesFilterTest.java
sonar-scanner-engine/src/test/java/org/sonar/scanner/issue/ignore/pattern/IssuePatternTest.java
sonar-scanner-engine/src/test/java/org/sonar/scanner/mediumtest/ScannerMediumTester.java
sonar-scanner-engine/src/test/java/org/sonar/scanner/mediumtest/issues/ChecksMediumTest.java
sonar-scanner-engine/src/test/java/org/sonar/scanner/mediumtest/issues/IssuesMediumTest.java
sonar-scanner-engine/src/test/java/org/sonar/scanner/rule/ActiveRulesProviderTest.java

index 50de775d2cff17d203d36ac9cfaa450d3a68056c..bdb93d0f7f01fc24342cefb7a659547877b73b1b 100644 (file)
@@ -21,7 +21,6 @@ package org.sonar.api.batch.rule.internal;
 
 import java.util.LinkedHashMap;
 import java.util.Map;
-import org.sonar.api.batch.rule.ActiveRules;
 import org.sonar.api.rule.RuleKey;
 
 /**
@@ -42,7 +41,7 @@ public class ActiveRulesBuilder {
     return this;
   }
 
-  public ActiveRules build() {
+  public DefaultActiveRules build() {
     return new DefaultActiveRules(map.values());
   }
 }
index eaf74a2bd53f90c09ccdd954655b623b2fccd11f..ecfdaad492640a968e1cdf157e3288917cd6334a 100644 (file)
@@ -21,7 +21,9 @@ package org.sonar.api.batch.rule.internal;
 
 import java.util.Collections;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.Map;
+import java.util.Set;
 import javax.annotation.concurrent.Immutable;
 import org.sonar.api.batch.rule.ActiveRule;
 import org.sonar.api.rule.RuleKey;
@@ -37,6 +39,7 @@ public class DefaultActiveRule implements ActiveRule {
   private final long createdAt;
   private final long updatedAt;
   private final String qProfileKey;
+  private final Set<RuleKey> deprecatedKeys;
 
   public DefaultActiveRule(NewActiveRule newActiveRule) {
     this.severity = newActiveRule.severity;
@@ -48,6 +51,7 @@ public class DefaultActiveRule implements ActiveRule {
     this.createdAt = newActiveRule.createdAt;
     this.updatedAt = newActiveRule.updatedAt;
     this.qProfileKey = newActiveRule.qProfileKey;
+    this.deprecatedKeys = Collections.unmodifiableSet(new HashSet<>(newActiveRule.deprecatedKeys));
   }
 
   @Override
@@ -98,4 +102,9 @@ public class DefaultActiveRule implements ActiveRule {
   public String qpKey() {
     return qProfileKey;
   }
+
+  public Set<RuleKey> getDeprecatedKeys() {
+    // already immutable
+    return deprecatedKeys;
+  }
 }
index bcfc1601d6251f14ea8eae0731ac8bdee7415f78..f6b120655806ac47f9b6864b0c08728d7805eb05 100644 (file)
@@ -23,16 +23,22 @@ import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
+import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 import java.util.stream.Collectors;
 import javax.annotation.concurrent.Immutable;
 import org.sonar.api.batch.rule.ActiveRule;
 import org.sonar.api.batch.rule.ActiveRules;
 import org.sonar.api.rule.RuleKey;
+import org.sonar.api.utils.WildcardPattern;
+
+import static java.util.Collections.emptySet;
 
 @Immutable
 public class DefaultActiveRules implements ActiveRules {
+  private final Map<RuleKey, Set<String>> deprecatedRuleKeysByRuleKey = new LinkedHashMap<>();
   private final Map<String, List<ActiveRule>> activeRulesByRepository = new HashMap<>();
   private final Map<String, Map<String, ActiveRule>> activeRulesByRepositoryAndKey = new HashMap<>();
   private final Map<String, Map<String, ActiveRule>> activeRulesByRepositoryAndInternalKey = new HashMap<>();
@@ -52,9 +58,19 @@ public class DefaultActiveRules implements ActiveRules {
       if (internalKey != null) {
         activeRulesByRepositoryAndInternalKey.computeIfAbsent(repo, r -> new HashMap<>()).put(internalKey, ar);
       }
+
+      deprecatedRuleKeysByRuleKey.put(ar.ruleKey(), ar.getDeprecatedKeys().stream().map(RuleKey::toString).collect(Collectors.toSet()));
     }
   }
 
+  public Set<String> getDeprecatedRuleKeys(RuleKey ruleKey) {
+    return deprecatedRuleKeysByRuleKey.getOrDefault(ruleKey, emptySet());
+  }
+
+  public boolean matchesDeprecatedKeys(RuleKey ruleKey, WildcardPattern rulePattern) {
+    return getDeprecatedRuleKeys(ruleKey).contains(rulePattern.toString());
+  }
+
   @Override
   public ActiveRule find(RuleKey ruleKey) {
     return activeRulesByRepositoryAndKey.getOrDefault(ruleKey.repository(), Collections.emptyMap())
index d22819c643bd012650f2bb8bdad376c81ec2bb51..64b762a02e85d47ed78e936929f6999fa0806851 100644 (file)
@@ -20,7 +20,9 @@
 package org.sonar.api.batch.rule.internal;
 
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.Map;
+import java.util.Set;
 import javax.annotation.Nullable;
 import javax.annotation.concurrent.Immutable;
 import org.apache.commons.lang.StringUtils;
@@ -42,6 +44,7 @@ public class NewActiveRule {
   final String language;
   final String templateRuleKey;
   final String qProfileKey;
+  final Set<RuleKey> deprecatedKeys;
 
   NewActiveRule(Builder builder) {
     this.ruleKey = builder.ruleKey;
@@ -54,6 +57,7 @@ public class NewActiveRule {
     this.language = builder.language;
     this.templateRuleKey = builder.templateRuleKey;
     this.qProfileKey = builder.qProfileKey;
+    this.deprecatedKeys = builder.deprecatedKeys;
   }
 
   public RuleKey ruleKey() {
@@ -71,6 +75,7 @@ public class NewActiveRule {
     private String language;
     private String templateRuleKey;
     private String qProfileKey;
+    private Set<RuleKey> deprecatedKeys = new HashSet<>();
 
     public Builder setRuleKey(RuleKey ruleKey) {
       this.ruleKey = ruleKey;
@@ -127,6 +132,11 @@ public class NewActiveRule {
       return this;
     }
 
+    public Builder setDeprecatedKeys(Set<RuleKey> deprecatedKeys) {
+      this.deprecatedKeys = deprecatedKeys;
+      return this;
+    }
+
     public NewActiveRule build() {
       return new NewActiveRule(this);
     }
diff --git a/sonar-plugin-api-impl/src/test/java/org/sonar/api/batch/rule/internal/DefaultActiveRulesTest.java b/sonar-plugin-api-impl/src/test/java/org/sonar/api/batch/rule/internal/DefaultActiveRulesTest.java
new file mode 100644 (file)
index 0000000..32a60c6
--- /dev/null
@@ -0,0 +1,63 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2021 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.api.batch.rule.internal;
+
+import com.google.common.collect.ImmutableSet;
+import java.util.Collections;
+import org.junit.Test;
+import org.sonar.api.rule.RuleKey;
+import org.sonar.api.utils.WildcardPattern;
+
+import static java.util.Collections.singleton;
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class DefaultActiveRulesTest {
+  private final RuleKey ruleKey = RuleKey.of("repo", "rule");
+
+  @Test
+  public void empty_returns_nothing() {
+    DefaultActiveRules underTest = new DefaultActiveRules(Collections.emptyList());
+
+    assertThat(underTest.getDeprecatedRuleKeys(ruleKey)).isEmpty();
+    assertThat(underTest.matchesDeprecatedKeys(ruleKey, WildcardPattern.create("**"))).isFalse();
+  }
+
+  @Test
+  public void finds_match() {
+    DefaultActiveRules underTest = new DefaultActiveRules(ImmutableSet.of(new NewActiveRule.Builder()
+      .setRuleKey(ruleKey)
+      .setDeprecatedKeys(singleton(RuleKey.of("oldrepo", "oldrule")))
+      .build()));
+
+    assertThat(underTest.getDeprecatedRuleKeys(ruleKey)).containsOnly("oldrepo:oldrule");
+    assertThat(underTest.matchesDeprecatedKeys(ruleKey, WildcardPattern.create("oldrepo:oldrule"))).isTrue();
+  }
+
+  @Test
+  public void finds_match_with_multiple_deprecated_keys() {
+    DefaultActiveRules underTest = new DefaultActiveRules(ImmutableSet.of(new NewActiveRule.Builder()
+      .setRuleKey(ruleKey)
+      .setDeprecatedKeys(ImmutableSet.of(RuleKey.of("oldrepo", "oldrule"), (RuleKey.of("oldrepo2", "oldrule2"))))
+      .build()));
+
+    assertThat(underTest.getDeprecatedRuleKeys(ruleKey)).containsOnly("oldrepo:oldrule", "oldrepo2:oldrule2");
+    assertThat(underTest.matchesDeprecatedKeys(ruleKey, WildcardPattern.create("oldrepo:oldrule"))).isTrue();
+  }
+}
index ea5ab87d89190e87696a754a3a2cee5a3a8d0b69..d29a993985b0e88e6b06e72ee1b5588b1ffda778 100644 (file)
@@ -21,16 +21,20 @@ package org.sonar.scanner.issue.ignore;
 
 import java.util.ArrayList;
 import java.util.Collections;
+import java.util.LinkedHashSet;
 import java.util.List;
+import java.util.Set;
 import javax.annotation.concurrent.ThreadSafe;
 import org.sonar.api.batch.fs.InputComponent;
+import org.sonar.api.batch.fs.internal.DefaultInputFile;
+import org.sonar.api.batch.rule.internal.DefaultActiveRules;
 import org.sonar.api.notifications.AnalysisWarnings;
+import org.sonar.api.rule.RuleKey;
 import org.sonar.api.scan.issue.filter.FilterableIssue;
 import org.sonar.api.scan.issue.filter.IssueFilter;
 import org.sonar.api.scan.issue.filter.IssueFilterChain;
 import org.sonar.api.utils.log.Logger;
 import org.sonar.api.utils.log.Loggers;
-import org.sonar.api.batch.fs.internal.DefaultInputFile;
 import org.sonar.scanner.issue.DefaultFilterableIssue;
 import org.sonar.scanner.issue.ignore.pattern.IssueInclusionPatternInitializer;
 import org.sonar.scanner.issue.ignore.pattern.IssuePattern;
@@ -41,12 +45,15 @@ public class EnforceIssuesFilter implements IssueFilter {
 
   private final List<IssuePattern> multicriteriaPatterns;
   private final AnalysisWarnings analysisWarnings;
+  private final DefaultActiveRules activeRules;
+  private final Set<RuleKey> warnedDeprecatedRuleKeys = new LinkedHashSet<>();
 
   private boolean warnDeprecatedIssuePatternAlreadyLogged;
 
-  public EnforceIssuesFilter(IssueInclusionPatternInitializer patternInitializer, AnalysisWarnings analysisWarnings) {
+  public EnforceIssuesFilter(IssueInclusionPatternInitializer patternInitializer, AnalysisWarnings analysisWarnings, DefaultActiveRules activeRules) {
     this.multicriteriaPatterns = Collections.unmodifiableList(new ArrayList<>(patternInitializer.getMulticriteriaPatterns()));
     this.analysisWarnings = analysisWarnings;
+    this.activeRules = activeRules;
   }
 
   @Override
@@ -56,7 +63,7 @@ public class EnforceIssuesFilter implements IssueFilter {
     IssuePattern matchingPattern = null;
 
     for (IssuePattern pattern : multicriteriaPatterns) {
-      if (pattern.matchRule(issue.ruleKey())) {
+      if (ruleMatches(pattern, issue.ruleKey())) {
         atLeastOneRuleMatched = true;
         InputComponent component = ((DefaultFilterableIssue) issue).getComponent();
         if (component.isFile()) {
@@ -78,7 +85,7 @@ public class EnforceIssuesFilter implements IssueFilter {
 
     if (atLeastOneRuleMatched) {
       if (atLeastOnePatternFullyMatched) {
-        LOG.debug("Issue {} enforced by pattern {}", issue, matchingPattern);
+        LOG.debug("Issue '{}' enforced by pattern '{}'", issue, matchingPattern);
       }
       return atLeastOnePatternFullyMatched;
     } else {
@@ -86,6 +93,19 @@ public class EnforceIssuesFilter implements IssueFilter {
     }
   }
 
+  private boolean ruleMatches(IssuePattern pattern, RuleKey ruleKey) {
+    if (activeRules.matchesDeprecatedKeys(ruleKey, pattern.getRulePattern())) {
+      String msg = String.format("The issue multicriteria pattern '%s' matches a rule key that has been changed. The pattern should be updated to '%s'",
+        pattern.getRulePattern(), ruleKey);
+      analysisWarnings.addUnique(msg);
+      if (warnedDeprecatedRuleKeys.add(ruleKey)) {
+        LOG.warn(msg);
+      }
+      return true;
+    }
+    return pattern.matchRule(ruleKey);
+  }
+
   private void warnOnceDeprecatedIssuePattern(String msg) {
     if (!warnDeprecatedIssuePatternAlreadyLogged) {
       LOG.warn(msg);
index 7c6e8db2596376450fb97e95952c69b97826a71c..23aeea36e18788f5aa7db230297e1ceadff0e0f8 100644 (file)
@@ -21,11 +21,16 @@ package org.sonar.scanner.issue.ignore;
 
 import java.util.Collections;
 import java.util.HashMap;
+import java.util.LinkedHashSet;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 import org.sonar.api.batch.fs.InputComponent;
 import org.sonar.api.batch.fs.internal.DefaultInputFile;
+import org.sonar.api.batch.rule.internal.DefaultActiveRules;
+import org.sonar.api.notifications.AnalysisWarnings;
+import org.sonar.api.rule.RuleKey;
 import org.sonar.api.scan.issue.filter.FilterableIssue;
 import org.sonar.api.scan.issue.filter.IssueFilter;
 import org.sonar.api.scan.issue.filter.IssueFilterChain;
@@ -36,10 +41,17 @@ import org.sonar.scanner.issue.DefaultFilterableIssue;
 
 public class IgnoreIssuesFilter implements IssueFilter {
 
-  private Map<InputComponent, List<WildcardPattern>> rulePatternByComponent = new HashMap<>();
-
+  private final DefaultActiveRules activeRules;
+  private final AnalysisWarnings analysisWarnings;
+  private final Map<InputComponent, List<WildcardPattern>> rulePatternByComponent = new HashMap<>();
+  private final Set<RuleKey> warnedDeprecatedRuleKeys = new LinkedHashSet<>();
   private static final Logger LOG = Loggers.get(IgnoreIssuesFilter.class);
 
+  public IgnoreIssuesFilter(DefaultActiveRules activeRules, AnalysisWarnings analysisWarnings) {
+    this.activeRules = activeRules;
+    this.analysisWarnings = analysisWarnings;
+  }
+
   @Override
   public boolean accept(FilterableIssue issue, IssueFilterChain chain) {
     InputComponent component = ((DefaultFilterableIssue) issue).getComponent();
@@ -73,7 +85,18 @@ public class IgnoreIssuesFilter implements IssueFilter {
   private boolean hasRuleMatchFor(InputComponent component, FilterableIssue issue) {
     for (WildcardPattern pattern : rulePatternByComponent.getOrDefault(component, Collections.emptyList())) {
       if (pattern.match(issue.ruleKey().toString())) {
-        LOG.debug("Issue {} ignored by exclusion pattern {}", issue, pattern);
+        LOG.debug("Issue '{}' ignored by exclusion pattern '{}'", issue, pattern);
+        return true;
+      }
+
+      RuleKey ruleKey = issue.ruleKey();
+      if (activeRules.matchesDeprecatedKeys(ruleKey, pattern)) {
+        String msg = String.format("The issue multicriteria pattern '%s' matches a rule key that has been changed. The pattern should be updated to '%s'", pattern, ruleKey);
+        analysisWarnings.addUnique(msg);
+        if (warnedDeprecatedRuleKeys.add(ruleKey)) {
+          LOG.warn(msg);
+        }
+        LOG.debug("Issue '{}' ignored by exclusion pattern '{}' matching a deprecated rule key", issue, pattern);
         return true;
       }
     }
index cd106fd45b770428db066960d49d9fec7744c257..3ad758dcc457d2a7d60e60267564e6c55891fc89 100644 (file)
@@ -26,7 +26,7 @@ import org.sonar.api.config.Configuration;
 import org.sonar.api.utils.MessageException;
 
 public abstract class AbstractPatternInitializer {
-  private Configuration settings;
+  private final Configuration settings;
   private List<IssuePattern> multicriteriaPatterns;
 
   protected AbstractPatternInitializer(Configuration config) {
@@ -63,7 +63,7 @@ public abstract class AbstractPatternInitializer {
       if (StringUtils.isBlank(ruleKeyPattern)) {
         throw MessageException.of("Issue exclusions are misconfigured. Rule key pattern is mandatory for each entry of '" + getMulticriteriaConfigurationKey() + "'");
       }
-      IssuePattern pattern = new IssuePattern(filePathPattern != null ? filePathPattern : "*", ruleKeyPattern != null ? ruleKeyPattern : "*");
+      IssuePattern pattern = new IssuePattern(filePathPattern, ruleKeyPattern);
 
       multicriteriaPatterns.add(pattern);
     }
index 095b429969ac0133a262672c31c56e473a7ca1e3..03bf6a5d78b6108df4126ae05bd69d63f6076309 100644 (file)
@@ -19,6 +19,7 @@
  */
 package org.sonar.scanner.issue.ignore.pattern;
 
+import com.google.common.base.MoreObjects;
 import javax.annotation.Nullable;
 import javax.annotation.concurrent.Immutable;
 import org.sonar.api.rule.RuleKey;
@@ -47,4 +48,12 @@ public class IssuePattern {
     return filePath != null && filePattern.match(filePath);
   }
 
+  @Override
+  public String toString() {
+    return MoreObjects.toStringHelper(this)
+      .add("filePattern", filePattern)
+      .add("rulePattern", rulePattern)
+      .toString();
+  }
+
 }
index 848dc68a252bca266ea52b9128e2490273ee7694..935498f209a076d791d3494f89f23b7ba31454a2 100644 (file)
@@ -26,9 +26,9 @@ import java.util.List;
 import java.util.Map;
 import java.util.Set;
 import org.picocontainer.injectors.ProviderAdapter;
-import org.sonar.api.batch.rule.ActiveRules;
-import org.sonar.api.batch.rule.internal.ActiveRulesBuilder;
 import org.sonar.api.batch.rule.LoadedActiveRule;
+import org.sonar.api.batch.rule.internal.ActiveRulesBuilder;
+import org.sonar.api.batch.rule.internal.DefaultActiveRules;
 import org.sonar.api.batch.rule.internal.NewActiveRule;
 import org.sonar.api.rule.RuleKey;
 import org.sonar.api.utils.log.Logger;
@@ -42,9 +42,9 @@ import org.sonar.api.utils.log.Profiler;
 public class ActiveRulesProvider extends ProviderAdapter {
   private static final Logger LOG = Loggers.get(ActiveRulesProvider.class);
   private static final String LOG_MSG = "Load active rules";
-  private ActiveRules singleton = null;
+  private DefaultActiveRules singleton = null;
 
-  public ActiveRules provide(ActiveRulesLoader loader, QualityProfiles qProfiles) {
+  public DefaultActiveRules provide(ActiveRulesLoader loader, QualityProfiles qProfiles) {
     if (singleton == null) {
       Profiler profiler = Profiler.create(LOG).startInfo(LOG_MSG);
       singleton = load(loader, qProfiles);
@@ -53,7 +53,7 @@ public class ActiveRulesProvider extends ProviderAdapter {
     return singleton;
   }
 
-  private static ActiveRules load(ActiveRulesLoader loader, QualityProfiles qProfiles) {
+  private static DefaultActiveRules load(ActiveRulesLoader loader, QualityProfiles qProfiles) {
 
     Collection<String> qProfileKeys = getKeys(qProfiles);
     Set<RuleKey> loadedRulesKey = new HashSet<>();
@@ -65,7 +65,7 @@ public class ActiveRulesProvider extends ProviderAdapter {
       for (LoadedActiveRule r : qProfileRules) {
         if (!loadedRulesKey.contains(r.getRuleKey())) {
           loadedRulesKey.add(r.getRuleKey());
-          builder.addRule(transform(r, qProfileKey));
+          builder.addRule(transform(r, qProfileKey, r.getDeprecatedKeys()));
         }
       }
     }
@@ -73,7 +73,7 @@ public class ActiveRulesProvider extends ProviderAdapter {
     return builder.build();
   }
 
-  private static NewActiveRule transform(LoadedActiveRule activeRule, String qProfileKey) {
+  private static NewActiveRule transform(LoadedActiveRule activeRule, String qProfileKey, Set<RuleKey> deprecatedKeys) {
     NewActiveRule.Builder builder = new NewActiveRule.Builder();
     builder
       .setRuleKey(activeRule.getRuleKey())
@@ -84,7 +84,8 @@ public class ActiveRulesProvider extends ProviderAdapter {
       .setLanguage(activeRule.getLanguage())
       .setInternalKey(activeRule.getInternalKey())
       .setTemplateRuleKey(activeRule.getTemplateRuleKey())
-      .setQProfileKey(qProfileKey);
+      .setQProfileKey(qProfileKey)
+      .setDeprecatedKeys(deprecatedKeys);
     // load parameters
     if (activeRule.getParams() != null) {
       for (Map.Entry<String, String> params : activeRule.getParams().entrySet()) {
index 5391760024dacf864bf7aacac4993edc750d9b98..c28628d5b51cec7d9e253dd16252080707fa6c9d 100644 (file)
@@ -20,6 +20,7 @@
 package org.sonar.scanner.issue.ignore;
 
 import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
 import java.io.IOException;
 import org.junit.Before;
 import org.junit.Rule;
@@ -27,6 +28,8 @@ import org.junit.Test;
 import org.junit.rules.TemporaryFolder;
 import org.sonar.api.batch.fs.InputComponent;
 import org.sonar.api.batch.fs.internal.TestInputFileBuilder;
+import org.sonar.api.batch.rule.internal.DefaultActiveRules;
+import org.sonar.api.batch.rule.internal.NewActiveRule;
 import org.sonar.api.notifications.AnalysisWarnings;
 import org.sonar.api.rule.RuleKey;
 import org.sonar.api.scan.issue.filter.IssueFilterChain;
@@ -34,10 +37,11 @@ import org.sonar.scanner.issue.DefaultFilterableIssue;
 import org.sonar.scanner.issue.ignore.pattern.IssueInclusionPatternInitializer;
 import org.sonar.scanner.issue.ignore.pattern.IssuePattern;
 
+import static java.util.Collections.singleton;
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.verifyNoInteractions;
 import static org.mockito.Mockito.when;
 
 public class EnforceIssuesFilterTest {
@@ -45,59 +49,74 @@ public class EnforceIssuesFilterTest {
   @Rule
   public TemporaryFolder tempFolder = new TemporaryFolder();
 
-  private IssueInclusionPatternInitializer exclusionPatternInitializer;
+  private final IssueInclusionPatternInitializer exclusionPatternInitializer = mock(IssueInclusionPatternInitializer.class);
+  private final DefaultFilterableIssue issue = mock(DefaultFilterableIssue.class);
+  private final IssueFilterChain chain = mock(IssueFilterChain.class);
+  private final AnalysisWarnings analysisWarnings = mock(AnalysisWarnings.class);
   private EnforceIssuesFilter ignoreFilter;
-  private DefaultFilterableIssue issue;
-  private IssueFilterChain chain;
 
   @Before
   public void init() {
-    exclusionPatternInitializer = mock(IssueInclusionPatternInitializer.class);
-    issue = mock(DefaultFilterableIssue.class);
-    chain = mock(IssueFilterChain.class);
     when(chain.accept(issue)).thenReturn(true);
   }
 
   @Test
   public void shouldPassToChainIfNoConfiguredPatterns() {
-    ignoreFilter = new EnforceIssuesFilter(exclusionPatternInitializer, mock(AnalysisWarnings.class));
+    DefaultActiveRules activeRules = new DefaultActiveRules(ImmutableSet.of());
+    ignoreFilter = new EnforceIssuesFilter(exclusionPatternInitializer, analysisWarnings, activeRules);
     assertThat(ignoreFilter.accept(issue, chain)).isTrue();
     verify(chain).accept(issue);
   }
 
   @Test
   public void shouldPassToChainIfRuleDoesNotMatch() {
-    String rule = "rule";
-    RuleKey ruleKey = mock(RuleKey.class);
-    when(ruleKey.toString()).thenReturn(rule);
+    DefaultActiveRules activeRules = new DefaultActiveRules(ImmutableSet.of());
+    RuleKey ruleKey = RuleKey.of("repo", "rule");
     when(issue.ruleKey()).thenReturn(ruleKey);
 
-    IssuePattern matching = mock(IssuePattern.class);
-    when(matching.matchRule(ruleKey)).thenReturn(false);
+    IssuePattern matching = new IssuePattern("**", "unknown");
     when(exclusionPatternInitializer.getMulticriteriaPatterns()).thenReturn(ImmutableList.of(matching));
 
-    ignoreFilter = new EnforceIssuesFilter(exclusionPatternInitializer, mock(AnalysisWarnings.class));
+    ignoreFilter = new EnforceIssuesFilter(exclusionPatternInitializer, analysisWarnings, activeRules);
     assertThat(ignoreFilter.accept(issue, chain)).isTrue();
     verify(chain).accept(issue);
   }
 
   @Test
   public void shouldAcceptIssueIfFullyMatched() {
-    String rule = "rule";
+    DefaultActiveRules activeRules = new DefaultActiveRules(ImmutableSet.of());
     String path = "org/sonar/api/Issue.java";
-    RuleKey ruleKey = mock(RuleKey.class);
-    when(ruleKey.toString()).thenReturn(rule);
+    RuleKey ruleKey = RuleKey.of("repo", "rule");
     when(issue.ruleKey()).thenReturn(ruleKey);
 
-    IssuePattern matching = mock(IssuePattern.class);
-    when(matching.matchRule(ruleKey)).thenReturn(true);
-    when(matching.matchFile(path)).thenReturn(true);
+    IssuePattern matching = new IssuePattern(path, ruleKey.toString());
     when(exclusionPatternInitializer.getMulticriteriaPatterns()).thenReturn(ImmutableList.of(matching));
     when(issue.getComponent()).thenReturn(createComponentWithPath(path));
 
-    ignoreFilter = new EnforceIssuesFilter(exclusionPatternInitializer, mock(AnalysisWarnings.class));
+    ignoreFilter = new EnforceIssuesFilter(exclusionPatternInitializer, analysisWarnings, activeRules);
     assertThat(ignoreFilter.accept(issue, chain)).isTrue();
-    verifyZeroInteractions(chain);
+    verifyNoInteractions(chain);
+  }
+
+  @Test
+  public void shouldAcceptIssueIfMatchesDeprecatedRuleKey() {
+    RuleKey ruleKey = RuleKey.of("repo", "rule");
+    DefaultActiveRules activeRules = new DefaultActiveRules(ImmutableSet.of(new NewActiveRule.Builder()
+      .setRuleKey(ruleKey)
+      .setDeprecatedKeys(singleton(RuleKey.of("repo2", "deprecated")))
+      .build()));
+    String path = "org/sonar/api/Issue.java";
+    when(issue.ruleKey()).thenReturn(ruleKey);
+
+    IssuePattern matching = new IssuePattern("org/**", "repo2:deprecated");
+    when(exclusionPatternInitializer.getMulticriteriaPatterns()).thenReturn(ImmutableList.of(matching));
+    when(issue.getComponent()).thenReturn(createComponentWithPath(path));
+
+    ignoreFilter = new EnforceIssuesFilter(exclusionPatternInitializer, analysisWarnings, activeRules);
+    assertThat(ignoreFilter.accept(issue, chain)).isTrue();
+    verify(analysisWarnings)
+      .addUnique("The issue multicriteria pattern 'repo2:deprecated' matches a rule key that has been changed. The pattern should be updated to 'repo:rule'");
+    verifyNoInteractions(chain);
   }
 
   private InputComponent createComponentWithPath(String path) {
@@ -106,42 +125,35 @@ public class EnforceIssuesFilterTest {
 
   @Test
   public void shouldRefuseIssueIfRuleMatchesButNotPath() {
-    String rule = "rule";
+    DefaultActiveRules activeRules = new DefaultActiveRules(ImmutableSet.of());
     String path = "org/sonar/api/Issue.java";
     String componentKey = "org.sonar.api.Issue";
-    RuleKey ruleKey = mock(RuleKey.class);
-    when(ruleKey.toString()).thenReturn(rule);
+    RuleKey ruleKey = RuleKey.of("repo", "rule");
     when(issue.ruleKey()).thenReturn(ruleKey);
     when(issue.componentKey()).thenReturn(componentKey);
 
-    IssuePattern matching = mock(IssuePattern.class);
-    when(matching.matchRule(ruleKey)).thenReturn(true);
-    when(matching.matchFile(path)).thenReturn(false);
+    IssuePattern matching = new IssuePattern("no match", "repo:rule");
     when(exclusionPatternInitializer.getMulticriteriaPatterns()).thenReturn(ImmutableList.of(matching));
     when(issue.getComponent()).thenReturn(createComponentWithPath(path));
 
-    ignoreFilter = new EnforceIssuesFilter(exclusionPatternInitializer, mock(AnalysisWarnings.class));
+    ignoreFilter = new EnforceIssuesFilter(exclusionPatternInitializer, analysisWarnings, activeRules);
     assertThat(ignoreFilter.accept(issue, chain)).isFalse();
-    verifyZeroInteractions(chain);
+    verifyNoInteractions(chain, analysisWarnings);
   }
 
   @Test
   public void shouldRefuseIssueIfRuleMatchesAndNotFile() throws IOException {
-    String rule = "rule";
+    DefaultActiveRules activeRules = new DefaultActiveRules(ImmutableSet.of());
     String path = "org/sonar/api/Issue.java";
-    String componentKey = "org.sonar.api.Issue";
-    RuleKey ruleKey = mock(RuleKey.class);
-    when(ruleKey.toString()).thenReturn(rule);
+    RuleKey ruleKey = RuleKey.of("repo", "key");
     when(issue.ruleKey()).thenReturn(ruleKey);
 
-    IssuePattern matching = mock(IssuePattern.class);
-    when(matching.matchRule(ruleKey)).thenReturn(true);
-    when(matching.matchFile(path)).thenReturn(true);
+    IssuePattern matching = new IssuePattern(path, ruleKey.toString());
     when(exclusionPatternInitializer.getMulticriteriaPatterns()).thenReturn(ImmutableList.of(matching));
     when(issue.getComponent()).thenReturn(TestInputFileBuilder.newDefaultInputProject("foo", tempFolder.newFolder()));
 
-    ignoreFilter = new EnforceIssuesFilter(exclusionPatternInitializer, mock(AnalysisWarnings.class));
+    ignoreFilter = new EnforceIssuesFilter(exclusionPatternInitializer, analysisWarnings, activeRules);
     assertThat(ignoreFilter.accept(issue, chain)).isFalse();
-    verifyZeroInteractions(chain);
+    verifyNoInteractions(chain, analysisWarnings);
   }
 }
index 3e10340146faca8bd140c0733fe893016bfeabd5..1db66e55f5d00fc391ce21099742b26498fec1c1 100644 (file)
  */
 package org.sonar.scanner.issue.ignore;
 
+import com.google.common.collect.ImmutableSet;
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
+import org.sonar.api.batch.fs.internal.DefaultInputFile;
+import org.sonar.api.batch.rule.internal.DefaultActiveRules;
+import org.sonar.api.batch.rule.internal.NewActiveRule;
+import org.sonar.api.notifications.AnalysisWarnings;
 import org.sonar.api.rule.RuleKey;
 import org.sonar.api.scan.issue.filter.IssueFilterChain;
 import org.sonar.api.utils.WildcardPattern;
-import org.sonar.api.batch.fs.internal.DefaultInputFile;
+import org.sonar.api.utils.log.LogTester;
 import org.sonar.scanner.issue.DefaultFilterableIssue;
 
+import static java.util.Collections.singleton;
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoInteractions;
 import static org.mockito.Mockito.when;
 
 public class IgnoreIssuesFilterTest {
+  @Rule
+  public LogTester logTester = new LogTester();
+
+  private final DefaultFilterableIssue issue = mock(DefaultFilterableIssue.class);
+  private final IssueFilterChain chain = mock(IssueFilterChain.class);
+  private final AnalysisWarnings analysisWarnings = mock(AnalysisWarnings.class);
+  private final RuleKey ruleKey = RuleKey.of("foo", "bar");
 
-  private DefaultFilterableIssue issue = mock(DefaultFilterableIssue.class);
-  private IssueFilterChain chain = mock(IssueFilterChain.class);
-  private IgnoreIssuesFilter underTest = new IgnoreIssuesFilter();
   private DefaultInputFile component;
-  private RuleKey ruleKey = RuleKey.of("foo", "bar");
 
   @Before
   public void prepare() {
@@ -50,6 +61,9 @@ public class IgnoreIssuesFilterTest {
 
   @Test
   public void shouldPassToChainIfMatcherHasNoPatternForIssue() {
+    DefaultActiveRules activeRules = new DefaultActiveRules(ImmutableSet.of());
+    IgnoreIssuesFilter underTest = new IgnoreIssuesFilter(activeRules, analysisWarnings);
+
     when(chain.accept(issue)).thenReturn(true);
     assertThat(underTest.accept(issue, chain)).isTrue();
     verify(chain).accept(any());
@@ -57,15 +71,39 @@ public class IgnoreIssuesFilterTest {
 
   @Test
   public void shouldRejectIfRulePatternMatches() {
+    DefaultActiveRules activeRules = new DefaultActiveRules(ImmutableSet.of());
+    IgnoreIssuesFilter underTest = new IgnoreIssuesFilter(activeRules, analysisWarnings);
+
     WildcardPattern pattern = mock(WildcardPattern.class);
     when(pattern.match(ruleKey.toString())).thenReturn(true);
     underTest.addRuleExclusionPatternForComponent(component, pattern);
 
     assertThat(underTest.accept(issue, chain)).isFalse();
+    verifyNoInteractions(analysisWarnings);
+  }
+
+  @Test
+  public void shouldRejectIfRulePatternMatchesDeprecatedRule() {
+    DefaultActiveRules activeRules = new DefaultActiveRules(ImmutableSet.of(new NewActiveRule.Builder()
+      .setRuleKey(ruleKey)
+      .setDeprecatedKeys(singleton(RuleKey.of("repo", "rule")))
+      .build()));
+    IgnoreIssuesFilter underTest = new IgnoreIssuesFilter(activeRules, analysisWarnings);
+
+    WildcardPattern pattern = WildcardPattern.create("repo:rule");
+    underTest.addRuleExclusionPatternForComponent(component, pattern);
+    assertThat(underTest.accept(issue, chain)).isFalse();
+
+    verify(analysisWarnings).addUnique("The issue multicriteria pattern 'repo:rule' matches a rule key that has been changed. The pattern should be updated to 'foo:bar'");
+    assertThat(logTester.logs())
+      .contains("The issue multicriteria pattern 'repo:rule' matches a rule key that has been changed. The pattern should be updated to 'foo:bar'");
   }
 
   @Test
   public void shouldAcceptIfRulePatternDoesNotMatch() {
+    DefaultActiveRules activeRules = new DefaultActiveRules(ImmutableSet.of());
+    IgnoreIssuesFilter underTest = new IgnoreIssuesFilter(activeRules, analysisWarnings);
+
     WildcardPattern pattern = mock(WildcardPattern.class);
     when(pattern.match(ruleKey.toString())).thenReturn(false);
     underTest.addRuleExclusionPatternForComponent(component, pattern);
index c9cd8be9833657935d16a86791758fff046f5ace..7e9e3bcab41aa09657aec24c55233d703b9361ca 100644 (file)
@@ -57,4 +57,9 @@ public class IssuePatternTest {
     assertThat(new IssuePattern("*", "*:Foo*IllegalRegexp").matchRule(rule)).isFalse();
   }
 
+  @Test
+  public void toString_should_include_all_fields() {
+    assertThat(new IssuePattern("*", "*:Foo*IllegalRegexp").toString()).isEqualTo("IssuePattern{filePattern=*, rulePattern=*:Foo*IllegalRegexp}");
+  }
+
 }
index d70abb58dcc7f00b38aa57c2b78af6349fe4d617..2cdab7cea2ceebe7496351f5e9f2c4127cc1a942 100644 (file)
@@ -79,6 +79,8 @@ import org.sonarqube.ws.NewCodePeriods;
 import org.sonarqube.ws.Qualityprofiles.SearchWsResponse.QualityProfile;
 import org.sonarqube.ws.Rules.ListResponse.Rule;
 
+import static java.util.Collections.emptySet;
+
 /**
  * Main utility class for writing scanner medium tests.
  */
@@ -209,6 +211,7 @@ public class ScannerMediumTester extends ExternalResource {
     r.setTemplateRuleKey(templateRuleKey);
     r.setLanguage(language);
     r.setSeverity(severity);
+    r.setDeprecatedKeys(emptySet());
 
     activeRules.addActiveRule(r);
     return this;
index bc65e15d45f3737b913d2b3151227217e1fbf16d..591286dd5e6efe9283d411d9283e86d4a3f8a6b0 100644 (file)
@@ -30,14 +30,15 @@ import org.apache.commons.io.FileUtils;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.TemporaryFolder;
+import org.sonar.api.batch.rule.LoadedActiveRule;
 import org.sonar.api.rule.RuleKey;
-import org.sonar.scanner.mediumtest.ScannerMediumTester;
 import org.sonar.scanner.mediumtest.AnalysisResult;
+import org.sonar.scanner.mediumtest.ScannerMediumTester;
 import org.sonar.scanner.protocol.output.ScannerReport.Issue;
-import org.sonar.api.batch.rule.LoadedActiveRule;
 import org.sonar.xoo.XooPlugin;
 import org.sonar.xoo.rule.XooRulesDefinition;
 
+import static java.util.Collections.emptySet;
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.assertj.core.api.Assertions.tuple;
 
@@ -98,6 +99,7 @@ public class ChecksMediumTest {
     r.setTemplateRuleKey(templateRuleKey);
     r.setLanguage(languag);
     r.setSeverity(severity);
+    r.setDeprecatedKeys(emptySet());
 
     Map<String, String> params = new HashMap<>();
     params.put(paramKey, paramValue);
index f27fd55be553698fde440e00bb86e0f0ca426e8a..5a584bd275d00600aac0915c1ee221e503dd4caa 100644 (file)
@@ -28,19 +28,20 @@ import org.apache.commons.io.FileUtils;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.TemporaryFolder;
+import org.sonar.api.batch.rule.LoadedActiveRule;
 import org.sonar.api.rule.RuleKey;
 import org.sonar.api.utils.log.LogTester;
 import org.sonar.api.utils.log.LoggerLevel;
-import org.sonar.scanner.mediumtest.ScannerMediumTester;
 import org.sonar.scanner.mediumtest.AnalysisResult;
+import org.sonar.scanner.mediumtest.ScannerMediumTester;
 import org.sonar.scanner.protocol.output.ScannerReport.ExternalIssue;
 import org.sonar.scanner.protocol.output.ScannerReport.Issue;
-import org.sonar.api.batch.rule.LoadedActiveRule;
 import org.sonar.xoo.XooPlugin;
 import org.sonar.xoo.rule.HasTagSensor;
 import org.sonar.xoo.rule.OneExternalIssuePerLineSensor;
 import org.sonar.xoo.rule.XooRulesDefinition;
 
+import static java.util.Collections.emptySet;
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.assertj.core.api.Assertions.tuple;
 
@@ -385,6 +386,8 @@ public class IssuesMediumTest {
     r.setName("TODO");
     r.setLanguage("xoo");
     r.setSeverity("MAJOR");
+    r.setDeprecatedKeys(emptySet()
+    );
     r.setParams(ImmutableMap.of("tag", "TODO"));
     tester.activateRule(r);
   }
index 0f1aef9124d490641203a8d1ca70e03fe41c175c..6d5c502e447d5f70294018a9fcf7dc120248b288 100644 (file)
@@ -26,33 +26,24 @@ import java.util.Date;
 import java.util.LinkedList;
 import java.util.List;
 import org.assertj.core.groups.Tuple;
-import org.junit.Before;
 import org.junit.Test;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
 import org.sonar.api.batch.rule.ActiveRules;
 import org.sonar.api.batch.rule.LoadedActiveRule;
+import org.sonar.api.batch.rule.internal.DefaultActiveRules;
 import org.sonar.api.rule.RuleKey;
 import org.sonar.api.utils.DateUtils;
 import org.sonarqube.ws.Qualityprofiles.SearchWsResponse.QualityProfile;
 
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyNoMoreInteractions;
 import static org.mockito.Mockito.when;
 
 public class ActiveRulesProviderTest {
-  private ActiveRulesProvider provider;
-
-  @Mock
-  private DefaultActiveRulesLoader loader;
-
-  @Before
-  public void setUp() {
-    MockitoAnnotations.initMocks(this);
-    provider = new ActiveRulesProvider();
-  }
+  private final ActiveRulesProvider provider = new ActiveRulesProvider();
+  private final DefaultActiveRulesLoader loader = mock(DefaultActiveRulesLoader.class);
 
   @Test
   public void testCombinationOfRules() {
@@ -69,7 +60,7 @@ public class ActiveRulesProviderTest {
     when(loader.load(eq("qp3"))).thenReturn(qp3Rules);
 
     QualityProfiles profiles = mockProfiles("qp1", "qp2", "qp3");
-    ActiveRules activeRules = provider.provide(loader, profiles);
+    DefaultActiveRules activeRules = provider.provide(loader, profiles);
 
     assertThat(activeRules.findAll()).hasSize(3);
     assertThat(activeRules.findAll()).extracting("ruleKey").containsOnly(
@@ -78,6 +69,8 @@ public class ActiveRulesProviderTest {
     verify(loader).load(eq("qp1"));
     verify(loader).load(eq("qp2"));
     verify(loader).load(eq("qp3"));
+
+    assertThat(activeRules.getDeprecatedRuleKeys(RuleKey.of("rule1", "rule1"))).containsOnly("rule1old:rule1old");
     verifyNoMoreInteractions(loader);
   }