aboutsummaryrefslogtreecommitdiffstats
path: root/sonar-plugin-api
diff options
context:
space:
mode:
authorSébastien Lesaint <sebastien.lesaint@sonarsource.com>2018-01-24 15:45:12 +0100
committerSébastien Lesaint <sebastien.lesaint@sonarsource.com>2018-02-08 13:41:00 +0100
commiteb4a773da108157e48998c84cb0477434fd4add5 (patch)
treede9ea6559ccc8af5750fd3d6a037f624185e3aac /sonar-plugin-api
parente497662d7c337fcc7c5293e2a38fd63cf8783bfd (diff)
downloadsonarqube-eb4a773da108157e48998c84cb0477434fd4add5.tar.gz
sonarqube-eb4a773da108157e48998c84cb0477434fd4add5.zip
SONAR-10304 add deprecatedRuleKeys to RuleDefinition#Rule
Diffstat (limited to 'sonar-plugin-api')
-rw-r--r--sonar-plugin-api/src/main/java/org/sonar/api/server/rule/RulesDefinition.java82
-rw-r--r--sonar-plugin-api/src/test/java/org/sonar/api/server/rule/RulesDefinitionTest.java106
2 files changed, 188 insertions, 0 deletions
diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/server/rule/RulesDefinition.java b/sonar-plugin-api/src/main/java/org/sonar/api/server/rule/RulesDefinition.java
index c7633fc3c54..cb1ce8ceac3 100644
--- a/sonar-plugin-api/src/main/java/org/sonar/api/server/rule/RulesDefinition.java
+++ b/sonar-plugin-api/src/main/java/org/sonar/api/server/rule/RulesDefinition.java
@@ -38,6 +38,7 @@ import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.sonar.api.ExtensionPoint;
import org.sonar.api.ce.ComputeEngineSide;
+import org.sonar.api.rule.RuleKey;
import org.sonar.api.rule.RuleScope;
import org.sonar.api.rule.RuleStatus;
import org.sonar.api.rule.Severity;
@@ -695,6 +696,7 @@ public interface RulesDefinition {
private final DebtRemediationFunctions functions;
private boolean activatedByDefault;
private RuleScope scope;
+ private final Set<RuleKey> deprecatedRuleKeys = new TreeSet<>();
private NewRule(@Nullable String pluginKey, String repoKey, String key) {
this.pluginKey = pluginKey;
@@ -943,6 +945,21 @@ public interface RulesDefinition {
}
}
+ /**
+ * Register a repository and key under which this rule used to be known
+ * (see {@link Rule#deprecatedRuleKeys} for details).
+ * <p>
+ * Deprecated keys should be added with this method in order, oldest first, for documentation purpose.
+ *
+ * @since 7.1
+ * @throws IllegalArgumentException if {@code repository} or {@code key} is {@code null} or empty.
+ * @see Rule#deprecatedRuleKeys
+ */
+ public NewRule addDeprecatedRuleKey(String repository, String key) {
+ deprecatedRuleKeys.add(RuleKey.of(repository, key));
+ return this;
+ }
+
@Override
public String toString() {
return format("[repository=%s, key=%s]", repoKey, key);
@@ -969,6 +986,7 @@ public interface RulesDefinition {
private final RuleStatus status;
private final boolean activatedByDefault;
private final RuleScope scope;
+ private final Set<RuleKey> deprecatedRuleKeys;
private Rule(Repository repository, NewRule newRule) {
this.pluginKey = newRule.pluginKey;
@@ -993,6 +1011,7 @@ public interface RulesDefinition {
}
this.params = Collections.unmodifiableMap(paramsBuilder);
this.activatedByDefault = newRule.activatedByDefault;
+ this.deprecatedRuleKeys = ImmutableSortedSet.copyOf(newRule.deprecatedRuleKeys);
}
public Repository repository() {
@@ -1106,6 +1125,69 @@ public interface RulesDefinition {
}
/**
+ * Deprecated rules keys for this rule.
+ * <p>
+ * If you want to rename the key of a rule or change its repository or both, register the rule's previous repository
+ * and key (see {@link NewRule#addDeprecatedRuleKey(String, String) addDeprecatedRuleKey}). This will allow
+ * SonarQube to support "issue re-keying" for this rule.
+ * <p>
+ * If the repository and/or key of an existing rule is changed without declaring deprecated keys, existing issues
+ * for this rule, created under the rule's previous repository and/or key, will be closed and new ones will be
+ * created under the issue's new repository and/or key.
+ * <p>
+ * Several deprecated keys can be provided to allow SonarQube to support several key (and/or repository) changes
+ * across multiple versions of a plugin.
+ * <br>
+ * Consider the following use case scenario:
+ * <ul>
+ * <li>Rule {@code Foo:A} is defined in version 1 of the plugin
+ * <pre>
+ * NewRepository newRepository = context.createRepository("Foo", "my_language");
+ * NewRule r = newRepository.createRule("A");
+ * </pre>
+ * </li>
+ * <li>Rule's key is renamed to B in version 2 of the plugin
+ * <pre>
+ * NewRepository newRepository = context.createRepository("Foo", "my_language");
+ * NewRule r = newRepository.createRule("B")
+ * .addDeprecatedRuleKey("Foo", "A");
+ * </pre>
+ * </li>
+ * <li>All rules, including {@code Foo:B}, are moved to a new repository Bar in version 3 of the plugin
+ * <pre>
+ * NewRepository newRepository = context.createRepository("Bar", "my_language");
+ * NewRule r = newRepository.createRule("B")
+ * .addDeprecatedRuleKey("Foo", "A")
+ * .addDeprecatedRuleKey("Bar", "B");
+ * </pre>
+ * </li>
+ * </ul>
+ *
+ * With all deprecated keys defined in version 3 of the plugin, SonarQube will be able to support "issue re-keying"
+ * for this rule in all cases:
+ * <ul>
+ * <li>plugin upgrade from v1 to v2,</li>
+ * <li>plugin upgrade from v2 to v3</li>
+ * <li>AND plugin upgrade from v1 to v3</li>
+ * </ul>
+ * <p>
+ * Finally, repository/key pairs must be unique across all rules and their deprecated keys.
+ * <br>
+ * This implies that no rule can use the same repository and key as the deprecated key of another rule. This
+ * uniqueness applies across plugins.
+ * <p>
+ * Note that, even though this method returns a {@code Set}, its elements are ordered according to calls to
+ * {@link NewRule#addDeprecatedRuleKey(String, String) addDeprecatedRuleKey}. This allows to describe the history
+ * of a rule's repositories and keys over time. Oldest repository and key must be specified first.
+ *
+ * @since 7.1
+ * @see NewRule#addDeprecatedRuleKey(String, String)
+ */
+ public Set<RuleKey> deprecatedRuleKeys() {
+ return deprecatedRuleKeys;
+ }
+
+ /**
* @see RulesDefinition.NewRule#setInternalKey(String)
*/
@CheckForNull
diff --git a/sonar-plugin-api/src/test/java/org/sonar/api/server/rule/RulesDefinitionTest.java b/sonar-plugin-api/src/test/java/org/sonar/api/server/rule/RulesDefinitionTest.java
index 64f3b0a96e3..8398f50a2ae 100644
--- a/sonar-plugin-api/src/test/java/org/sonar/api/server/rule/RulesDefinitionTest.java
+++ b/sonar-plugin-api/src/test/java/org/sonar/api/server/rule/RulesDefinitionTest.java
@@ -19,10 +19,22 @@
*/
package org.sonar.api.server.rule;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Ordering;
+import com.tngtech.java.junit.dataprovider.DataProvider;
+import com.tngtech.java.junit.dataprovider.DataProviderRunner;
+import com.tngtech.java.junit.dataprovider.UseDataProvider;
import java.net.URL;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+import org.junit.Assume;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
+import org.junit.runner.RunWith;
+import org.sonar.api.rule.RuleKey;
import org.sonar.api.rule.RuleScope;
import org.sonar.api.rule.RuleStatus;
import org.sonar.api.rule.Severity;
@@ -33,6 +45,7 @@ import org.sonar.api.utils.log.LogTester;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.Assert.fail;
+@RunWith(DataProviderRunner.class)
public class RulesDefinitionTest {
RulesDefinition.Context context = new RulesDefinition.Context();
@@ -201,6 +214,99 @@ public class RulesDefinitionTest {
}
@Test
+ @UseDataProvider("nullOrEmpty")
+ public void addDeprecatedRuleKey_fails_with_IAE_if_repository_is_null_or_empty(String nullOrEmpty) {
+ RulesDefinition.NewRepository newRepository = context.createRepository("foo", "bar");
+ RulesDefinition.NewRule newRule = newRepository.createRule("doh");
+
+ expectedException.expect(IllegalArgumentException.class);
+ expectedException.expectMessage("Repository must be set");
+
+ newRule.addDeprecatedRuleKey(nullOrEmpty, "oldKey");
+ }
+
+ @Test
+ @UseDataProvider("nullOrEmpty")
+ public void addDeprecatedRuleKey_fails_with_IAE_if_key_is_null_or_empty(String nullOrEmpty) {
+ RulesDefinition.NewRepository newRepository = context.createRepository("foo", "bar");
+ RulesDefinition.NewRule newRule = newRepository.createRule("doh");
+
+ expectedException.expect(IllegalArgumentException.class);
+ expectedException.expectMessage("Rule must be set");
+
+ newRule.addDeprecatedRuleKey("oldRepo", nullOrEmpty);
+ }
+
+ @DataProvider
+ public static Object[][] nullOrEmpty() {
+ return new Object[][] {
+ {null},
+ {""}
+ };
+ }
+
+ @Test
+ public void getDeprecatedKeys_returns_empty_if_addDeprecatedKeys_never_called() {
+ String repositoryKey = "foo";
+ String ruleKey = "doh";
+ RulesDefinition.NewRepository newRepository = context.createRepository(repositoryKey, "bar");
+ newRepository.createRule(ruleKey)
+ .setName("doh rule")
+ .setHtmlDescription("doh description");
+ newRepository.done();
+ RulesDefinition.Repository repository = context.repository(repositoryKey);
+ RulesDefinition.Rule rule = repository.rule(ruleKey);
+
+ assertThat(rule.deprecatedRuleKeys()).isEmpty();
+ }
+
+ @Test
+ public void getDeprecatedKeys_returns_keys_in_order_of_addDeprecatedKeys_calls() {
+ Set<RuleKey> ruleKeys = ImmutableSet.of(RuleKey.of("foo", "AAA"),
+ RuleKey.of("bar", "CCCC"), RuleKey.of("doh", "CCCC"), RuleKey.of("foo", "BBBBBBBBBB"));
+ List<RuleKey> sortedRuleKeys = ruleKeys.stream().sorted(Ordering.natural().onResultOf(RuleKey::toString)).collect(Collectors.toList());
+
+ // ensure we don't have the same order
+ Assume.assumeTrue(!ImmutableList.copyOf(ruleKeys).equals(sortedRuleKeys));
+
+ String repositoryKey = "foo";
+ String ruleKey = "doh";
+ RulesDefinition.NewRepository newRepository = context.createRepository(repositoryKey, "bar");
+ RulesDefinition.NewRule newRule = newRepository.createRule(ruleKey)
+ .setName("doh rule")
+ .setHtmlDescription("doh description");
+ sortedRuleKeys.forEach(r -> newRule.addDeprecatedRuleKey(r.repository(), r.rule()));
+ newRepository.done();
+ RulesDefinition.Repository repository = context.repository(repositoryKey);
+ RulesDefinition.Rule rule = repository.rule(ruleKey);
+
+ assertThat(ImmutableList.copyOf(rule.deprecatedRuleKeys()))
+ .isEqualTo(sortedRuleKeys);
+ }
+
+ @Test
+ public void getDeprecatedKeys_does_not_return_the_same_key_more_than_once() {
+ RuleKey duplicatedRuleKey = RuleKey.of("foo", "AAA");
+ RuleKey ruleKey2 = RuleKey.of("bar", "CCCC");
+ RuleKey ruleKey3 = RuleKey.of("foo", "BBBBBBBBBB");
+ List<RuleKey> ruleKeys = ImmutableList.of(duplicatedRuleKey, ruleKey2, duplicatedRuleKey, duplicatedRuleKey, ruleKey3);
+
+ String repositoryKey = "foo";
+ String ruleKey = "doh";
+ RulesDefinition.NewRepository newRepository = context.createRepository(repositoryKey, "bar");
+ RulesDefinition.NewRule newRule = newRepository.createRule(ruleKey)
+ .setName("doh rule")
+ .setHtmlDescription("doh description");
+ ruleKeys.forEach(r -> newRule.addDeprecatedRuleKey(r.repository(), r.rule()));
+ newRepository.done();
+ RulesDefinition.Repository repository = context.repository(repositoryKey);
+ RulesDefinition.Rule rule = repository.rule(ruleKey);
+
+ assertThat(rule.deprecatedRuleKeys())
+ .containsExactly(ruleKey2, duplicatedRuleKey, ruleKey3);
+ }
+
+ @Test
public void sanitize_rule_name() {
RulesDefinition.NewRepository newFindbugs = context.createRepository("findbugs", "java");
newFindbugs.createRule("NPE").setName(" \n NullPointer \n ").setHtmlDescription("NPE");