diff options
author | Sébastien Lesaint <sebastien.lesaint@sonarsource.com> | 2018-01-24 15:45:12 +0100 |
---|---|---|
committer | Sébastien Lesaint <sebastien.lesaint@sonarsource.com> | 2018-02-08 13:41:00 +0100 |
commit | eb4a773da108157e48998c84cb0477434fd4add5 (patch) | |
tree | de9ea6559ccc8af5750fd3d6a037f624185e3aac /sonar-plugin-api | |
parent | e497662d7c337fcc7c5293e2a38fd63cf8783bfd (diff) | |
download | sonarqube-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.java | 82 | ||||
-rw-r--r-- | sonar-plugin-api/src/test/java/org/sonar/api/server/rule/RulesDefinitionTest.java | 106 |
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"); |