summaryrefslogtreecommitdiffstats
path: root/sonar-plugin-api
diff options
context:
space:
mode:
authorSimon Brandhof <simon.brandhof@gmail.com>2014-01-15 16:30:02 +0100
committerSimon Brandhof <simon.brandhof@gmail.com>2014-01-15 18:25:32 +0100
commit138e1ff721bddb519362a1f80b5a84950a8bd4d3 (patch)
treef1f1386fd5ed50fbeab36fe6df3a6aef004b645b /sonar-plugin-api
parent1cde18a7cee3b5d26bbcfd859d342ca3b3a1c50a (diff)
downloadsonarqube-138e1ff721bddb519362a1f80b5a84950a8bd4d3.tar.gz
sonarqube-138e1ff721bddb519362a1f80b5a84950a8bd4d3.zip
SONAR-4908 support deprecated types of rule parameters and add RuleDefinitions#loadAnnotatedClasses()
Diffstat (limited to 'sonar-plugin-api')
-rw-r--r--sonar-plugin-api/src/main/java/org/sonar/api/rule/AnnotationRuleDefinitions.java115
-rw-r--r--sonar-plugin-api/src/main/java/org/sonar/api/rule/RuleDefinitions.java16
-rw-r--r--sonar-plugin-api/src/main/java/org/sonar/api/rule/RuleParamType.java1
-rw-r--r--sonar-plugin-api/src/test/java/org/sonar/api/rule/AnnotationRuleDefinitionsTest.java161
-rw-r--r--sonar-plugin-api/src/test/java/org/sonar/api/rule/RuleDefinitionsTest.java10
5 files changed, 295 insertions, 8 deletions
diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/rule/AnnotationRuleDefinitions.java b/sonar-plugin-api/src/main/java/org/sonar/api/rule/AnnotationRuleDefinitions.java
new file mode 100644
index 00000000000..4fda6549ae2
--- /dev/null
+++ b/sonar-plugin-api/src/main/java/org/sonar/api/rule/AnnotationRuleDefinitions.java
@@ -0,0 +1,115 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2013 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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.rule;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Function;
+import com.google.common.base.Functions;
+import com.google.common.collect.ImmutableMap;
+import org.apache.commons.lang.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.sonar.api.utils.AnnotationUtils;
+import org.sonar.api.utils.FieldUtils2;
+import org.sonar.api.utils.SonarException;
+import org.sonar.check.Cardinality;
+
+import java.lang.reflect.Field;
+import java.util.List;
+
+/**
+ * Read definitions of rules based on the annotations provided by sonar-check-api.
+ * </p>
+ * It is internally used by {@link org.sonar.api.rule.RuleDefinitions} and can't be directly
+ * used by plugins.
+ * @since 4.2
+ */
+class AnnotationRuleDefinitions {
+
+ private static final Logger LOG = LoggerFactory.getLogger(AnnotationRuleDefinitions.class);
+
+ void loadRules(RuleDefinitions.NewRepository repo, Class... annotatedClasses) {
+ for (Class annotatedClass : annotatedClasses) {
+ loadRule(repo, annotatedClass);
+ }
+ }
+
+ private void loadRule(RuleDefinitions.NewRepository repo, Class clazz) {
+ org.sonar.check.Rule ruleAnnotation = AnnotationUtils.getAnnotation(clazz, org.sonar.check.Rule.class);
+ if (ruleAnnotation != null) {
+ loadRule(repo, clazz, ruleAnnotation);
+ } else {
+ LOG.warn("The class " + clazz.getCanonicalName() + " should be annotated with " + org.sonar.check.Rule.class);
+ }
+ }
+
+ private void loadRule(RuleDefinitions.NewRepository repo, Class clazz, org.sonar.check.Rule ruleAnnotation) {
+ String ruleKey = StringUtils.defaultIfEmpty(ruleAnnotation.key(), clazz.getCanonicalName());
+ String ruleName = StringUtils.defaultIfEmpty(ruleAnnotation.name(), null);
+ String description = StringUtils.defaultIfEmpty(ruleAnnotation.description(), null);
+
+ RuleDefinitions.NewRule rule = repo.newRule(ruleKey);
+ rule.setName(ruleName).setHtmlDescription(description);
+ rule.setDefaultSeverity(ruleAnnotation.priority().name());
+ rule.setTemplate(ruleAnnotation.cardinality() == Cardinality.MULTIPLE);
+ rule.setStatus(RuleDefinitions.Status.valueOf(ruleAnnotation.status()));
+
+ List<Field> fields = FieldUtils2.getFields(clazz, true);
+ for (Field field : fields) {
+ loadParameters(rule, field);
+ }
+ }
+
+ private void loadParameters(RuleDefinitions.NewRule rule, Field field) {
+ org.sonar.check.RuleProperty propertyAnnotation = field.getAnnotation(org.sonar.check.RuleProperty.class);
+ if (propertyAnnotation != null) {
+ String fieldKey = StringUtils.defaultIfEmpty(propertyAnnotation.key(), field.getName());
+ RuleDefinitions.NewParam param = rule.newParam(fieldKey)
+ .setDescription(propertyAnnotation.description())
+ .setDefaultValue(propertyAnnotation.defaultValue());
+
+ if (!StringUtils.isBlank(propertyAnnotation.type())) {
+ try {
+ param.setType(RuleParamType.parse(propertyAnnotation.type().trim()));
+ } catch (IllegalArgumentException e) {
+ throw new IllegalArgumentException("Invalid property type [" + propertyAnnotation.type() + "]", e);
+ }
+ } else {
+ param.setType(guessType(field.getType()));
+ }
+ }
+ }
+
+ private static final Function<Class<?>, RuleParamType> TYPE_FOR_CLASS = Functions.forMap(
+ ImmutableMap.<Class<?>, RuleParamType>builder()
+ .put(Integer.class, RuleParamType.INTEGER)
+ .put(int.class, RuleParamType.INTEGER)
+ .put(Float.class, RuleParamType.FLOAT)
+ .put(float.class, RuleParamType.FLOAT)
+ .put(Boolean.class, RuleParamType.BOOLEAN)
+ .put(boolean.class, RuleParamType.BOOLEAN)
+ .build(),
+ RuleParamType.STRING);
+
+ @VisibleForTesting
+ static RuleParamType guessType(Class<?> type) {
+ return TYPE_FOR_CLASS.apply(type);
+ }
+}
diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/rule/RuleDefinitions.java b/sonar-plugin-api/src/main/java/org/sonar/api/rule/RuleDefinitions.java
index 47ee055782d..6ad3136afb3 100644
--- a/sonar-plugin-api/src/main/java/org/sonar/api/rule/RuleDefinitions.java
+++ b/sonar-plugin-api/src/main/java/org/sonar/api/rule/RuleDefinitions.java
@@ -21,6 +21,7 @@ package org.sonar.api.rule;
import com.google.common.collect.*;
import org.apache.commons.lang.StringUtils;
+import org.slf4j.LoggerFactory;
import org.sonar.api.ServerExtension;
import javax.annotation.CheckForNull;
@@ -86,6 +87,8 @@ public interface RuleDefinitions extends ServerExtension {
static interface NewExtendedRepository {
NewRule newRule(String ruleKey);
+ void loadAnnotatedClasses(Class... classes);
+
void done();
}
@@ -117,13 +120,24 @@ public interface RuleDefinitions extends ServerExtension {
@Override
public NewRule newRule(String ruleKey) {
if (newRules.containsKey(ruleKey)) {
- throw new IllegalArgumentException("The rule '" + ruleKey + "' of repository '" + key + "' is declared several times");
+ // Should fail in a perfect world, but at the time being the Findbugs plugin
+ // defines several times the rule EC_INCOMPATIBLE_ARRAY_COMPARE
+ // See http://jira.codehaus.org/browse/SONARJAVA-428
+ LoggerFactory.getLogger(getClass()).warn(String.format("The rule '%s' of repository '%s' is declared several times", ruleKey, key));
}
NewRule newRule = new NewRule(key, ruleKey);
newRules.put(ruleKey, newRule);
return newRule;
}
+ /**
+ * Load definitions from classes annotated with #{@link org.sonar.check.Rule} of library sonar-check-api
+ */
+ @Override
+ public void loadAnnotatedClasses(Class... classes) {
+ new AnnotationRuleDefinitions().loadRules(this, classes);
+ }
+
@Override
public void done() {
// note that some validations can be done here, for example for
diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/rule/RuleParamType.java b/sonar-plugin-api/src/main/java/org/sonar/api/rule/RuleParamType.java
index 7b7d5285f3f..0330cab1aa5 100644
--- a/sonar-plugin-api/src/main/java/org/sonar/api/rule/RuleParamType.java
+++ b/sonar-plugin-api/src/main/java/org/sonar/api/rule/RuleParamType.java
@@ -35,6 +35,7 @@ public final class RuleParamType {
public static final RuleParamType TEXT = new RuleParamType("TEXT");
public static final RuleParamType BOOLEAN = new RuleParamType("BOOLEAN");
public static final RuleParamType INTEGER = new RuleParamType("INTEGER");
+ public static final RuleParamType FLOAT = new RuleParamType("FLOAT");
private final String type;
private final String[] options;
diff --git a/sonar-plugin-api/src/test/java/org/sonar/api/rule/AnnotationRuleDefinitionsTest.java b/sonar-plugin-api/src/test/java/org/sonar/api/rule/AnnotationRuleDefinitionsTest.java
new file mode 100644
index 00000000000..754878c9b25
--- /dev/null
+++ b/sonar-plugin-api/src/test/java/org/sonar/api/rule/AnnotationRuleDefinitionsTest.java
@@ -0,0 +1,161 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2013 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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.rule;
+
+import org.junit.Ignore;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.sonar.check.Priority;
+
+import static org.fest.assertions.Assertions.assertThat;
+
+public class AnnotationRuleDefinitionsTest {
+
+ @org.junit.Rule
+ public final ExpectedException exception = ExpectedException.none();
+
+ @Test
+ public void rule_with_property() {
+ RuleDefinitions.Repository repository = load(RuleWithProperty.class);
+ assertThat(repository.rules()).hasSize(1);
+ RuleDefinitions.Rule rule = repository.rules().get(0);
+ assertThat(rule.key()).isEqualTo("foo");
+ assertThat(rule.status()).isEqualTo(RuleDefinitions.Status.BETA);
+ assertThat(rule.name()).isEqualTo("bar");
+ assertThat(rule.htmlDescription()).isEqualTo("Foo Bar");
+ assertThat(rule.defaultSeverity()).isEqualTo(Severity.BLOCKER);
+ assertThat(rule.status()).isEqualTo(RuleDefinitions.Status.BETA);
+ assertThat(rule.params()).hasSize(1);
+
+ RuleDefinitions.Param prop = rule.param("property");
+ assertThat(prop.key()).isEqualTo("property");
+ assertThat(prop.description()).isEqualTo("Ignore ?");
+ assertThat(prop.defaultValue()).isEqualTo("false");
+ assertThat(prop.type()).isEqualTo(RuleParamType.STRING);
+ }
+
+ @Test
+ public void rule_with_integer_property() {
+ RuleDefinitions.Repository repository = load(RuleWithIntegerProperty.class);
+
+ RuleDefinitions.Param prop = repository.rules().get(0).param("property");
+ assertThat(prop.description()).isEqualTo("Max");
+ assertThat(prop.defaultValue()).isEqualTo("12");
+ assertThat(prop.type()).isEqualTo(RuleParamType.INTEGER);
+ }
+
+ @Test
+ public void rule_with_text_property() {
+ RuleDefinitions.Repository repository = load(RuleWithTextProperty.class);
+
+ RuleDefinitions.Param prop = repository.rules().get(0).param("property");
+ assertThat(prop.description()).isEqualTo("text");
+ assertThat(prop.defaultValue()).isEqualTo("Long text");
+ assertThat(prop.type()).isEqualTo(RuleParamType.TEXT);
+ }
+
+ @Test
+ @Ignore("TODO list supported types in RuleParamType")
+ public void should_reject_invalid_property_types() {
+ exception.expect(IllegalArgumentException.class);
+ exception.expectMessage("Invalid property type [INVALID]");
+
+ load(RuleWithInvalidPropertyType.class);
+ }
+
+ @Test
+ public void should_recognize_type() {
+ assertThat(AnnotationRuleDefinitions.guessType(Integer.class)).isEqualTo(RuleParamType.INTEGER);
+ assertThat(AnnotationRuleDefinitions.guessType(int.class)).isEqualTo(RuleParamType.INTEGER);
+ assertThat(AnnotationRuleDefinitions.guessType(Float.class)).isEqualTo(RuleParamType.FLOAT);
+ assertThat(AnnotationRuleDefinitions.guessType(float.class)).isEqualTo(RuleParamType.FLOAT);
+ assertThat(AnnotationRuleDefinitions.guessType(Boolean.class)).isEqualTo(RuleParamType.BOOLEAN);
+ assertThat(AnnotationRuleDefinitions.guessType(boolean.class)).isEqualTo(RuleParamType.BOOLEAN);
+ assertThat(AnnotationRuleDefinitions.guessType(String.class)).isEqualTo(RuleParamType.STRING);
+ assertThat(AnnotationRuleDefinitions.guessType(Object.class)).isEqualTo(RuleParamType.STRING);
+ }
+
+ @Test
+ public void use_classname_when_missing_key() {
+ RuleDefinitions.Repository repository = load(RuleWithoutKey.class);
+ assertThat(repository.rules()).hasSize(1);
+ RuleDefinitions.Rule rule = repository.rules().get(0);
+ assertThat(rule.key()).isEqualTo(RuleWithoutKey.class.getCanonicalName());
+ assertThat(rule.name()).isEqualTo("foo");
+ }
+
+ @Test
+ public void overridden_class() {
+ RuleDefinitions.Repository repository = load(OverridingRule.class);
+ assertThat(repository.rules()).hasSize(1);
+ RuleDefinitions.Rule rule = repository.rules().get(0);
+ assertThat(rule.key()).isEqualTo("overriding_foo");
+ assertThat(rule.name()).isEqualTo("Overriding Foo");
+ assertThat(rule.defaultSeverity()).isEqualTo(Severity.MAJOR);
+ assertThat(rule.htmlDescription()).isEqualTo("Desc of Overriding Foo");
+ assertThat(rule.params()).hasSize(2);
+ }
+
+ private RuleDefinitions.Repository load(Class annotatedClass) {
+ RuleDefinitions.Context context = new RuleDefinitions.Context();
+ RuleDefinitions.NewRepository newRepository = context.newRepository("squid", "java");
+ new AnnotationRuleDefinitions().loadRules(newRepository, annotatedClass);
+ newRepository.done();
+ return context.repository("squid");
+ }
+
+ @org.sonar.check.Rule(name = "foo", description = "Foo")
+ static class RuleWithoutKey {
+ }
+
+ @org.sonar.check.Rule(key = "foo")
+ static class RuleWithoutNameNorDescription {
+ }
+
+ @org.sonar.check.Rule(key = "foo", name = "bar", description = "Foo Bar", priority = Priority.BLOCKER, status="BETA")
+ static class RuleWithProperty {
+ @org.sonar.check.RuleProperty(description = "Ignore ?", defaultValue = "false")
+ private String property;
+ }
+
+ @org.sonar.check.Rule(key = "overriding_foo", name = "Overriding Foo", description = "Desc of Overriding Foo")
+ static class OverridingRule extends RuleWithProperty {
+ @org.sonar.check.RuleProperty
+ private String additionalProperty;
+ }
+
+ @org.sonar.check.Rule(key = "foo", name = "bar", description = "Foo Bar", priority = Priority.BLOCKER)
+ static class RuleWithIntegerProperty {
+ @org.sonar.check.RuleProperty(description = "Max", defaultValue = "12")
+ private Integer property;
+ }
+
+ @org.sonar.check.Rule(key = "foo", name = "bar", description = "Foo Bar", priority = Priority.BLOCKER)
+ static class RuleWithTextProperty {
+ @org.sonar.check.RuleProperty(description = "text", defaultValue = "Long text", type = "TEXT")
+ protected String property;
+ }
+
+ @org.sonar.check.Rule(key = "foo", name = "bar", description = "Foo Bar", priority = Priority.BLOCKER)
+ static class RuleWithInvalidPropertyType {
+ @org.sonar.check.RuleProperty(description = "text", defaultValue = "Long text", type = "INVALID")
+ public String property;
+ }
+}
diff --git a/sonar-plugin-api/src/test/java/org/sonar/api/rule/RuleDefinitionsTest.java b/sonar-plugin-api/src/test/java/org/sonar/api/rule/RuleDefinitionsTest.java
index e04e1c112c5..33b06647c07 100644
--- a/sonar-plugin-api/src/test/java/org/sonar/api/rule/RuleDefinitionsTest.java
+++ b/sonar-plugin-api/src/test/java/org/sonar/api/rule/RuleDefinitionsTest.java
@@ -169,15 +169,11 @@ public class RuleDefinitionsTest {
}
@Test
- public void fail_if_duplicated_rule_keys() {
+ public void warning_if_duplicated_rule_keys() {
RuleDefinitions.NewRepository findbugs = context.newRepository("findbugs", "java");
findbugs.newRule("NPE");
- try {
- findbugs.newRule("NPE");
- fail();
- } catch (IllegalArgumentException e) {
- assertThat(e).hasMessage("The rule 'NPE' of repository 'findbugs' is declared several times");
- }
+ findbugs.newRule("NPE");
+ // do not fail as long as http://jira.codehaus.org/browse/SONARJAVA-428 is not fixed
}
@Test