diff options
author | Simon Brandhof <simon.brandhof@gmail.com> | 2014-03-25 15:50:29 +0100 |
---|---|---|
committer | Simon Brandhof <simon.brandhof@gmail.com> | 2014-03-25 16:45:32 +0100 |
commit | e2b033fccc4e306a11b77b3632b388f556385815 (patch) | |
tree | b54ffc40f007fa512b8c14fb77ff106ca813a9c5 /sonar-plugin-api | |
parent | 767869e7fdcd15e3cf290a247b60b34b1bde6958 (diff) | |
download | sonarqube-e2b033fccc4e306a11b77b3632b388f556385815.tar.gz sonarqube-e2b033fccc4e306a11b77b3632b388f556385815.zip |
SONAR-4908 refactor RulesDefinition API (debt functions, XML)
Diffstat (limited to 'sonar-plugin-api')
14 files changed, 577 insertions, 140 deletions
diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/i18n/RuleI18n.java b/sonar-plugin-api/src/main/java/org/sonar/api/i18n/RuleI18n.java new file mode 100644 index 00000000000..0dbcee23cc7 --- /dev/null +++ b/sonar-plugin-api/src/main/java/org/sonar/api/i18n/RuleI18n.java @@ -0,0 +1,152 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 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.i18n; + +import org.sonar.api.BatchComponent; +import org.sonar.api.ServerComponent; +import org.sonar.api.rules.Rule; + +import javax.annotation.CheckForNull; + +import java.util.Locale; + +/** + * {@link I18n}-companion component that provides translation facilities for rule names, descriptions and parameter names. + * + * @since 3.2 + * @deprecated in 4.1. Rules are not localized anymore. See http://jira.codehaus.org/browse/SONAR-4885 + */ +@Deprecated +public interface RuleI18n extends ServerComponent, BatchComponent { + + /** + * Returns the localized name of the rule identified by its repository key and rule key. + * <br> + * If the name is not found in the given locale, then the default name is returned (the English one). + * This method could return null if no default name found. This is the cause for instance the copies rules. + * + * @param repositoryKey the repository key + * @param ruleKey the rule key + * @param locale not used + * @return the translated name of the rule, or the default English one if the given locale is not supported, or null + * @deprecated since 4.1. Rules are not localized anymore. See http://jira.codehaus.org/browse/SONAR-4885 + */ + @Deprecated + @CheckForNull + String getName(String repositoryKey, String ruleKey, Locale locale); + + /** + * Returns the name of the rule identified by its repository key and rule key. + * <br> + * This method could return null if no default name found. This is the cause for instance the copies rules. + * + * @param repositoryKey the repository key + * @param ruleKey the rule key + * @return the nullable name of the rule + * @since 4.1 + */ + @CheckForNull + String getName(String repositoryKey, String ruleKey); + + /** + * Returns the localized name or the name of the rule. + * <br> + * If the name is not found in the given locale, then the default name is returned (the English one). + * It the default name is not found, then the rule name is returned. + * + * @param rule the rule + * @param locale the locale to translate into + * @return the translated name of the rule, or the default English one if the given locale is not supported, or the rule name. + * @deprecated since 4.1. Rules are not localized anymore. See http://jira.codehaus.org/browse/SONAR-4885 + */ + @Deprecated + @CheckForNull + String getName(Rule rule, Locale locale); + + /** + * Returns the name of the rule. + * <br> + * It the default name is not found, then the rule name is returned. + * + * @param rule the rule + * @return the nullable name of the rule + * @since 4.1 + */ + @CheckForNull + String getName(Rule rule); + + /** + * Returns the localized description of the rule identified by its repository key and rule key. + * <br> + * If the description is not found in the given locale, then the default description is returned (the English one). + * As a rule must have a description (this is a constraint in Sonar), this method never returns null. + * + * @param repositoryKey the repository key + * @param ruleKey the rule key + * @param locale the locale to translate into + * @return the translated description of the rule, or the default English one if the given locale is not supported + * @deprecated since 4.1. Rules are not localized anymore. See http://jira.codehaus.org/browse/SONAR-4885 + */ + @Deprecated + String getDescription(String repositoryKey, String ruleKey, Locale locale); + + /** + * Returns the description of the rule identified by its repository key and rule key. + * <br> + * As a rule must have a description (this is a constraint in SonarQube), this method never returns null. + * + * @param repositoryKey the repository key + * @param ruleKey the rule key + * @return the description of the rule + * @since 4.1 + */ + String getDescription(String repositoryKey, String ruleKey); + + /** + * Returns the localized name of the rule parameter identified by the rules's key and repository key, and by the parameter key. + * <br> + * If the name is not found in the given locale, then the English translation is searched and return if found. Otherwise, + * this method returns null (= if no translation can be found). + * + * @param repositoryKey the repository key + * @param ruleKey the rule key + * @param paramKey the parameter key + * @param locale the locale to translate into + * @return the translated name of the rule parameter, or the default English one if the given locale is not supported, or null if + * no translation can be found. + * @deprecated since 4.1. Rules are not localized anymore. See http://jira.codehaus.org/browse/SONAR-4885 + */ + @Deprecated + @CheckForNull + String getParamDescription(String repositoryKey, String ruleKey, String paramKey, Locale locale); + + /** + * Returns the name of the rule parameter identified by the rules's key and repository key, and by the parameter key. + * + * @param repositoryKey the repository key + * @param ruleKey the rule key + * @param paramKey the parameter key + * @return the nullable name of the rule parameter + * @since 4.1 + */ + @CheckForNull + String getParamDescription(String repositoryKey, String ruleKey, String paramKey); + +} diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/server/rule/DebtRemediationFunction.java b/sonar-plugin-api/src/main/java/org/sonar/api/server/debt/DebtRemediationFunction.java index d1ae702432a..3c8e9971dd2 100644 --- a/sonar-plugin-api/src/main/java/org/sonar/api/server/rule/DebtRemediationFunction.java +++ b/sonar-plugin-api/src/main/java/org/sonar/api/server/debt/DebtRemediationFunction.java @@ -18,30 +18,45 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -package org.sonar.api.server.rule; +package org.sonar.api.server.debt; import javax.annotation.CheckForNull; /** + * Function used to calculate the remediation cost of an issue. There are three types : + * <ul> + * <li> + * <b>Linear</b> - Each issue of the rule costs the same amount of time (factor) to fix. + * </li> + * <li> + * <b>Linear with offset</b> - It takes a certain amount of time to analyze the issues of such kind on the file (offset). + * Then, each issue of the rule costs the same amount of time (factor) to fix. Total remediation cost + * by file = offset + (number of issues x factor) + * </li> + * <li><b>Constant/issue</b> - The cost to fix all the issues of the rule is the same whatever the number of issues + * of this rule in the file. Total remediation cost by file = constant + * </li> + * </ul> + * * @since 4.3 */ public interface DebtRemediationFunction { - static class ValidationException extends RuntimeException { - public ValidationException(String message) { - super(message); - } - } - static enum Type { LINEAR, LINEAR_OFFSET, CONSTANT_ISSUE } Type type(); + /** + * Factor is set on types {@link Type#LINEAR} and {@link Type#LINEAR_OFFSET}, else it's null. + */ @CheckForNull String factor(); + /** + * Offset is set on types {@link Type#LINEAR_OFFSET} and {@link Type#CONSTANT_ISSUE}, else it's null. + */ @CheckForNull String offset(); diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/server/rule/DefaultDebtRemediationFunction.java b/sonar-plugin-api/src/main/java/org/sonar/api/server/debt/internal/DefaultDebtRemediationFunction.java index dedf5a3e60c..759aef8d543 100644 --- a/sonar-plugin-api/src/main/java/org/sonar/api/server/rule/DefaultDebtRemediationFunction.java +++ b/sonar-plugin-api/src/main/java/org/sonar/api/server/debt/internal/DefaultDebtRemediationFunction.java @@ -18,31 +18,45 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -package org.sonar.api.server.rule; +package org.sonar.api.server.debt.internal; +import com.google.common.base.Objects; import org.apache.commons.lang.StringUtils; -import org.apache.commons.lang.builder.EqualsBuilder; -import org.apache.commons.lang.builder.HashCodeBuilder; -import org.apache.commons.lang.builder.ReflectionToStringBuilder; -import org.apache.commons.lang.builder.ToStringStyle; +import org.sonar.api.server.debt.DebtRemediationFunction; +import org.sonar.api.utils.Duration; import javax.annotation.CheckForNull; import javax.annotation.Nullable; -class DefaultDebtRemediationFunction implements DebtRemediationFunction { +public class DefaultDebtRemediationFunction implements DebtRemediationFunction { - private Type type; - private String factor; - private String offset; + private static final int HOURS_IN_DAY = 24; + + private final Type type; + private final String factor; + private final String offset; public DefaultDebtRemediationFunction(Type type, @Nullable String factor, @Nullable String offset) { this.type = type; // TODO validate factor and offset format - this.factor = StringUtils.deleteWhitespace(factor); - this.offset = StringUtils.deleteWhitespace(offset); + this.factor = sanitizeValue("factor", factor); + this.offset = sanitizeValue("offset", offset); validate(); } + @CheckForNull + private String sanitizeValue(String label, @Nullable String s) { + if (StringUtils.isNotBlank(s)) { + try { + Duration duration = Duration.decode(s, HOURS_IN_DAY); + return duration.encode(HOURS_IN_DAY); + } catch (Exception e) { + throw new IllegalArgumentException(String.format("Invalid %s: %s", label, s), e); + } + } + return null; + } + @Override public Type type() { return type; @@ -64,21 +78,21 @@ class DefaultDebtRemediationFunction implements DebtRemediationFunction { switch (type) { case LINEAR: if (this.factor == null || this.offset != null) { - throw new ValidationException(String.format("%s is invalid, Linear remediation function should only define a factor", this)); + throw new IllegalArgumentException(String.format("Only factor must be set on %s", this)); } break; case LINEAR_OFFSET: if (this.factor == null || this.offset == null) { - throw new ValidationException(String.format("%s is invalid, Linear with offset remediation function should define both factor and offset", this)); + throw new IllegalArgumentException(String.format("Both factor and offset are required on %s", this)); } break; case CONSTANT_ISSUE: if (this.factor != null || this.offset == null) { - throw new ValidationException(String.format("%s is invalid, Constant/issue remediation function should only define an offset", this)); + throw new IllegalArgumentException(String.format("Only offset must be set on %s", this)); } break; default: - throw new IllegalStateException(String.format("Remediation function of %s is unknown", this)); + throw new IllegalArgumentException(String.format("Unknown type on %s", this)); } } @@ -90,26 +104,30 @@ class DefaultDebtRemediationFunction implements DebtRemediationFunction { if (o == null || getClass() != o.getClass()) { return false; } - - DebtRemediationFunction that = (DebtRemediationFunction) o; - return new EqualsBuilder() - .append(type, that.type()) - .append(factor, that.factor()) - .append(offset, that.offset()) - .isEquals(); + DefaultDebtRemediationFunction that = (DefaultDebtRemediationFunction) o; + if (factor != null ? !factor.equals(that.factor) : that.factor != null) { + return false; + } + if (offset != null ? !offset.equals(that.offset) : that.offset != null) { + return false; + } + return type == that.type; } @Override public int hashCode() { - return new HashCodeBuilder(15, 33) - .append(type) - .append(factor) - .append(offset) - .toHashCode(); + int result = type.hashCode(); + result = 31 * result + (factor != null ? factor.hashCode() : 0); + result = 31 * result + (offset != null ? offset.hashCode() : 0); + return result; } @Override public String toString() { - return new ReflectionToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE).toString(); + return Objects.toStringHelper(DebtRemediationFunction.class) + .add("type", type) + .add("factor", factor) + .add("offset", offset) + .toString(); } } diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/server/rule/DebtRemediationFunctions.java b/sonar-plugin-api/src/main/java/org/sonar/api/server/rule/DebtRemediationFunctions.java deleted file mode 100644 index ace4b88bf76..00000000000 --- a/sonar-plugin-api/src/main/java/org/sonar/api/server/rule/DebtRemediationFunctions.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * SonarQube, open source software quality management tool. - * Copyright (C) 2008-2014 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.server.rule; - -/** - * Factory of {@link org.sonar.api.server.rule.DebtRemediationFunction} - * - * @since 4.3 - */ -public interface DebtRemediationFunctions { - - DebtRemediationFunction linear(String factor); - - DebtRemediationFunction linearWithOffset(String factor, String offset); - - DebtRemediationFunction constantPerIssue(String offset); -} diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/server/rule/DefaultDebtRemediationFunctions.java b/sonar-plugin-api/src/main/java/org/sonar/api/server/rule/DefaultDebtRemediationFunctions.java index befa7907095..a2fa542e6dd 100644 --- a/sonar-plugin-api/src/main/java/org/sonar/api/server/rule/DefaultDebtRemediationFunctions.java +++ b/sonar-plugin-api/src/main/java/org/sonar/api/server/rule/DefaultDebtRemediationFunctions.java @@ -17,14 +17,21 @@ * 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.server.rule; +import org.sonar.api.server.debt.DebtRemediationFunction; +import org.sonar.api.server.debt.internal.DefaultDebtRemediationFunction; import org.sonar.api.utils.MessageException; import javax.annotation.Nullable; -class DefaultDebtRemediationFunctions implements DebtRemediationFunctions { +/** + * Factory of {@link org.sonar.api.server.debt.DebtRemediationFunction} that keeps + * a context of rule for better error messages. Used only when declaring rules. + * + * @see org.sonar.api.server.rule.RulesDefinition + */ +class DefaultDebtRemediationFunctions implements RulesDefinition.DebtRemediationFunctions { private final String repoKey, key; @@ -51,7 +58,7 @@ class DefaultDebtRemediationFunctions implements DebtRemediationFunctions { private DebtRemediationFunction create(DefaultDebtRemediationFunction.Type type, @Nullable String factor, @Nullable String offset) { try { return new DefaultDebtRemediationFunction(type, factor, offset); - } catch (DefaultDebtRemediationFunction.ValidationException e) { + } catch (Exception e) { throw MessageException.of(String.format("The rule '%s:%s' is invalid : %s ", this.repoKey, this.key, e.getMessage())); } } diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/server/rule/RuleTagFormat.java b/sonar-plugin-api/src/main/java/org/sonar/api/server/rule/RuleTagFormat.java index bd2326407c4..3885bede912 100644 --- a/sonar-plugin-api/src/main/java/org/sonar/api/server/rule/RuleTagFormat.java +++ b/sonar-plugin-api/src/main/java/org/sonar/api/server/rule/RuleTagFormat.java @@ -40,7 +40,7 @@ public class RuleTagFormat { public static void validate(String tag) { if (!isValid(tag)) { - throw new IllegalArgumentException(String.format("Tag '%s' is invalid. Rule tags accept only the following characters: a-z, 0-9, '+', '-', '#', '.'", tag)); + throw new IllegalArgumentException(String.format("Tag '%s' is invalid. Rule tags accept only the characters: a-z, 0-9, '+', '-', '#', '.'", tag)); } } } 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 2bab880020a..79d08f46588 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 @@ -33,6 +33,7 @@ import org.slf4j.LoggerFactory; import org.sonar.api.ServerExtension; import org.sonar.api.rule.RuleStatus; import org.sonar.api.rule.Severity; +import org.sonar.api.server.debt.DebtRemediationFunction; import javax.annotation.CheckForNull; import javax.annotation.Nullable; @@ -49,6 +50,89 @@ import java.util.Set; * this extension point in order to define the rules that it supports. * <p/> * This interface replaces the deprecated class org.sonar.api.rules.RuleRepository. + * <p/> + * <h3>How to use</h3> + * <pre> + * public class JsSquidRulesDefinition implements RulesDefinition { + * + * {@literal @}Override + * public void define(Context context) { + * NewRepository repository = context.createRepository("js_squid", "js").setName("Javascript Squid"); + * + * // define a rule programmatically. Note that rules + * // could be loaded from files (JSON, XML, ...) + * NewRule x1Rule = repository.createRule("x1") + * .setName("No empty line") + * .setHtmlDescription("Generate an issue on empty lines") + * + * // optional tags + * .setTags("style", "stupid") + * + * // optional status. Default value is READY. + * .setStatus(RuleStatus.BETA) + * + * // default severity when the rule is activated on a Quality profile. Default value is MAJOR. + * .setSeverity(Severity.MINOR); + * + * x1Rule + * .setDebtCharacteristic("INTEGRATION_TESTABILITY") + * .setDebtRemediationFunction(x1Rule.debtRemediationFunctions().linearWithOffset("1h", "30min")); + * + * x1Rule.createParam("acceptWhitespace") + * .setDefaultValue("false") + * .setType(RuleParamType.BOOLEAN) + * .setDescription("Accept whitespaces on the line"); + * + * // don't forget to call done() to finalize the definition + * repository.done(); + * } + * } + * </pre> + * <p/> + * If rules are declared in a XML file with the standard SonarQube format, then it can be loaded by using : + * + * <pre> + * public class JsSquidRulesDefinition implements RulesDefinition { + * + * private final RulesDefinitionXmlLoader xmlLoader; + * + * public JsSquidRulesDefinition(RulesDefinitionXmlLoader xmlLoader) { + * this.xmlLoader = xmlLoader; + * } + * + * {@literal @}Override + * public void define(Context context) { + * NewRepository repository = context.createRepository("js_squid", "js").setName("Javascript Squid"); + * // see javadoc of RulesDefinitionXmlLoader for the format + * xmlLoader.load(repository, getClass().getResourceAsStream("/path/to/rules.xml")); + * repository.done(); + * } + * } + * </pre> + * + * In the above example, XML file must contain name and description of each rule. If it's not the case, then the + * (deprecated) English bundles can be used : + * + * <pre> + * public class JsSquidRulesDefinition implements RulesDefinition { + * + * private final RulesDefinitionXmlLoader xmlLoader; + * private final RulesDefinitionI18nLoader i18nLoader; + * + * public JsSquidRulesDefinition(RulesDefinitionXmlLoader xmlLoader, RulesDefinitionI18nLoader i18nLoader) { + * this.xmlLoader = xmlLoader; + * this.i18nLoader = i18nLoader; + * } + * + * {@literal @}Override + * public void define(Context context) { + * NewRepository repository = context.createRepository("js_squid", "js").setName("Javascript Squid"); + * xmlLoader.load(repository, getClass().getResourceAsStream("/path/to/rules.xml")); + * i18nLoader.load(repository); + * repository.done(); + * } + * } + * </pre> * * @since 4.3 */ @@ -278,6 +362,15 @@ public interface RulesDefinition extends ServerExtension { } } + interface DebtRemediationFunctions { + DebtRemediationFunction linear(String factor); + + DebtRemediationFunction linearWithOffset(String factor, String offset); + + DebtRemediationFunction constantPerIssue(String offset); + } + + class NewRule { private final String repoKey, key; private String name, htmlDescription, internalKey, severity = Severity.MAJOR; @@ -288,7 +381,7 @@ public interface RulesDefinition extends ServerExtension { private String effortToFixDescription; private final Set<String> tags = Sets.newTreeSet(); private final Map<String, NewParam> paramsByKey = Maps.newHashMap(); - private final DefaultDebtRemediationFunctions functions; + private final DebtRemediationFunctions functions; private NewRule(String repoKey, String key) { this.repoKey = repoKey; @@ -356,13 +449,16 @@ public interface RulesDefinition extends ServerExtension { return functions; } - public NewRule setDebtRemediationFunction(@Nullable DebtRemediationFunction debtRemediationFunction) { - this.debtRemediationFunction = debtRemediationFunction; + /** + * @see #debtRemediationFunctions() + */ + public NewRule setDebtRemediationFunction(@Nullable DebtRemediationFunction fn) { + this.debtRemediationFunction = fn; return this; } - public NewRule setEffortToFixDescription(@Nullable String effortToFixDescription) { - this.effortToFixDescription = effortToFixDescription; + public NewRule setEffortToFixDescription(@Nullable String s) { + this.effortToFixDescription = s; return this; } diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/server/rule/RulesDefinitionI18nLoader.java b/sonar-plugin-api/src/main/java/org/sonar/api/server/rule/RulesDefinitionI18nLoader.java new file mode 100644 index 00000000000..70973c5a124 --- /dev/null +++ b/sonar-plugin-api/src/main/java/org/sonar/api/server/rule/RulesDefinitionI18nLoader.java @@ -0,0 +1,70 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 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.server.rule; + +import org.apache.commons.lang.StringUtils; +import org.sonar.api.ServerComponent; +import org.sonar.api.i18n.RuleI18n; + +/** + * Loads the English bundles of rules (name, description and parameters) that are + * deprecated since 4.2. It can be useful when loading existing XML files that + * do not contain rule names and descriptions. + * <p/> + * This class must be executed after declaring rules on {@link RulesDefinition.NewRepository}. + * <p/> + * Note that localization of rules was dropped in version 4.2. All texts are English. + * + * @see org.sonar.api.server.rule.RulesDefinition for an example + * @since 4.3 + */ +public class RulesDefinitionI18nLoader implements ServerComponent { + + private final RuleI18n i18n; + + public RulesDefinitionI18nLoader(RuleI18n i18n) { + this.i18n = i18n; + } + + /** + * Loads descriptions of rules and related rule parameters. Existing descriptions + * are overridden if English labels exist in bundles. + */ + public void load(RulesDefinition.NewRepository repo) { + for (RulesDefinition.NewRule rule : repo.rules()) { + String name = i18n.getName(repo.key(), rule.key()); + if (StringUtils.isNotBlank(name)) { + rule.setName(name); + } + + String desc = i18n.getDescription(repo.key(), rule.key()); + if (StringUtils.isNotBlank(desc)) { + rule.setHtmlDescription(desc); + } + + for (RulesDefinition.NewParam param : rule.params()) { + String paramDesc = i18n.getParamDescription(repo.key(), rule.key(), param.key()); + if (StringUtils.isNotBlank(paramDesc)) { + param.setDescription(paramDesc); + } + } + } + } +} diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/server/ws/WebService.java b/sonar-plugin-api/src/main/java/org/sonar/api/server/ws/WebService.java index 83e53ee72d2..a4725146bdd 100644 --- a/sonar-plugin-api/src/main/java/org/sonar/api/server/ws/WebService.java +++ b/sonar-plugin-api/src/main/java/org/sonar/api/server/ws/WebService.java @@ -39,7 +39,7 @@ import java.util.Map; * <p/> * The classes implementing this extension point must be declared in {@link org.sonar.api.SonarPlugin#getExtensions()}. * - * <h2>How to use</h2> + * <h3>How to use</h3> * <pre> * public class HelloWs implements WebService { * {@literal @}Override @@ -66,7 +66,7 @@ import java.util.Map; * } * } * </pre> - * <h2>How to unit test</h2> + * <h3>How to test</h3> * <pre> * public class HelloWsTest { * WebService ws = new HelloWs(); diff --git a/sonar-plugin-api/src/test/java/org/sonar/api/server/rule/DefaultDebtRemediationFunctionTest.java b/sonar-plugin-api/src/test/java/org/sonar/api/server/debt/DefaultDebtRemediationFunctionTest.java index 5f1b5ae57ec..6223fc8d17e 100644 --- a/sonar-plugin-api/src/test/java/org/sonar/api/server/rule/DefaultDebtRemediationFunctionTest.java +++ b/sonar-plugin-api/src/test/java/org/sonar/api/server/debt/DefaultDebtRemediationFunctionTest.java @@ -18,9 +18,11 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -package org.sonar.api.server.rule; +package org.sonar.api.server.debt; +import org.junit.Ignore; import org.junit.Test; +import org.sonar.api.server.debt.internal.DefaultDebtRemediationFunction; import static org.fest.assertions.Assertions.assertThat; import static org.fest.assertions.Fail.fail; @@ -28,7 +30,7 @@ import static org.fest.assertions.Fail.fail; public class DefaultDebtRemediationFunctionTest { @Test - public void create_linear() throws Exception { + public void create_linear() { DebtRemediationFunction function = new DefaultDebtRemediationFunction(DebtRemediationFunction.Type.LINEAR, "10h", null); assertThat(function.type()).isEqualTo(DefaultDebtRemediationFunction.Type.LINEAR); assertThat(function.factor()).isEqualTo("10h"); @@ -36,7 +38,7 @@ public class DefaultDebtRemediationFunctionTest { } @Test - public void create_linear_with_offset() throws Exception { + public void create_linear_with_offset() { DebtRemediationFunction function = new DefaultDebtRemediationFunction(DebtRemediationFunction.Type.LINEAR_OFFSET, "10h", "5min"); assertThat(function.type()).isEqualTo(DefaultDebtRemediationFunction.Type.LINEAR_OFFSET); assertThat(function.factor()).isEqualTo("10h"); @@ -44,7 +46,7 @@ public class DefaultDebtRemediationFunctionTest { } @Test - public void create_constant_per_issue() throws Exception { + public void create_constant_per_issue() { DebtRemediationFunction function = new DefaultDebtRemediationFunction(DebtRemediationFunction.Type.CONSTANT_ISSUE, null, "10h"); assertThat(function.type()).isEqualTo(DefaultDebtRemediationFunction.Type.CONSTANT_ISSUE); assertThat(function.factor()).isNull(); @@ -53,74 +55,74 @@ public class DefaultDebtRemediationFunctionTest { @Test public void sanitize_remediation_factor_and_offset() { - DebtRemediationFunction function = new DefaultDebtRemediationFunction(DebtRemediationFunction.Type.LINEAR_OFFSET, " 1 h ", " 10 mi n"); + DebtRemediationFunction function = new DefaultDebtRemediationFunction(DebtRemediationFunction.Type.LINEAR_OFFSET, " 1 h ", " 10 min"); assertThat(function.factor()).isEqualTo("1h"); assertThat(function.offset()).isEqualTo("10min"); } @Test - public void fail_to_create_linear_when_no_factor() throws Exception { + public void fail_to_create_linear_when_no_factor() { try { new DefaultDebtRemediationFunction(DebtRemediationFunction.Type.LINEAR, null, "10h"); fail(); - } catch (Exception e) { - assertThat(e).isInstanceOf(DefaultDebtRemediationFunction.ValidationException.class); + } catch (IllegalArgumentException e) { + assertThat(e).hasMessage("Only factor must be set on DebtRemediationFunction{type=LINEAR, factor=null, offset=10h}"); } } @Test - public void fail_to_create_linear_when_offset() throws Exception { + public void fail_to_create_linear_when_offset() { try { new DefaultDebtRemediationFunction(DebtRemediationFunction.Type.LINEAR, "5min", "10h"); fail(); - } catch (Exception e) { - assertThat(e).isInstanceOf(DefaultDebtRemediationFunction.ValidationException.class); + } catch (IllegalArgumentException e) { + assertThat(e).hasMessage("Only factor must be set on DebtRemediationFunction{type=LINEAR, factor=5min, offset=10h}"); } } @Test - public void fail_to_create_constant_per_issue_when_no_offset() throws Exception { + public void fail_to_create_constant_per_issue_when_no_offset() { try { new DefaultDebtRemediationFunction(DebtRemediationFunction.Type.CONSTANT_ISSUE, "10h", null); fail(); - } catch (Exception e) { - assertThat(e).isInstanceOf(DefaultDebtRemediationFunction.ValidationException.class); + } catch (IllegalArgumentException e) { + assertThat(e).hasMessage("Only offset must be set on DebtRemediationFunction{type=CONSTANT_ISSUE, factor=10h, offset=null}"); } } @Test - public void fail_to_create_constant_per_issue_when_factor() throws Exception { + public void fail_to_create_constant_per_issue_when_factor() { try { new DefaultDebtRemediationFunction(DebtRemediationFunction.Type.CONSTANT_ISSUE, "5min", "10h"); fail(); - } catch (Exception e) { - assertThat(e).isInstanceOf(DefaultDebtRemediationFunction.ValidationException.class); + } catch (IllegalArgumentException e) { + assertThat(e).hasMessage("Only offset must be set on DebtRemediationFunction{type=CONSTANT_ISSUE, factor=5min, offset=10h}"); } } @Test - public void fail_to_create_linear_with_offset_when_no_factor() throws Exception { + public void fail_to_create_linear_with_offset_when_no_factor() { try { - new DefaultDebtRemediationFunction(DebtRemediationFunction.Type.LINEAR_OFFSET, null, "10h"); + new DefaultDebtRemediationFunction(DebtRemediationFunction.Type.LINEAR_OFFSET, "", "10h"); fail(); - } catch (Exception e) { - assertThat(e).isInstanceOf(DefaultDebtRemediationFunction.ValidationException.class); + } catch (IllegalArgumentException e) { + assertThat(e).hasMessage("Both factor and offset are required on DebtRemediationFunction{type=LINEAR_OFFSET, factor=null, offset=10h}"); } } @Test - public void fail_to_create_linear_with_offset_when_no_offset() throws Exception { + public void fail_to_create_linear_with_offset_when_no_offset() { try { - new DefaultDebtRemediationFunction(DebtRemediationFunction.Type.LINEAR_OFFSET, "5min", null); + new DefaultDebtRemediationFunction(DebtRemediationFunction.Type.LINEAR_OFFSET, "5min", ""); fail(); - } catch (Exception e) { - assertThat(e).isInstanceOf(DefaultDebtRemediationFunction.ValidationException.class); + } catch (IllegalArgumentException e) { + assertThat(e).hasMessage("Both factor and offset are required on DebtRemediationFunction{type=LINEAR_OFFSET, factor=5min, offset=null}"); } } @Test - public void test_equals_and_hashcode() throws Exception { + public void test_equals_and_hashcode() { DebtRemediationFunction function = new DefaultDebtRemediationFunction(DebtRemediationFunction.Type.LINEAR_OFFSET, "10h", "5min"); DebtRemediationFunction functionWithSameValue = new DefaultDebtRemediationFunction(DebtRemediationFunction.Type.LINEAR_OFFSET, "10h", "5min"); DebtRemediationFunction functionWithDifferentType = new DefaultDebtRemediationFunction(DebtRemediationFunction.Type.CONSTANT_ISSUE, null, "5min"); @@ -140,8 +142,20 @@ public class DefaultDebtRemediationFunctionTest { } @Test - public void test_to_string() throws Exception { - assertThat(new DefaultDebtRemediationFunction(DebtRemediationFunction.Type.LINEAR_OFFSET, "10h", "5min").toString()).isNotNull(); + public void test_to_string() { + assertThat(new DefaultDebtRemediationFunction(DebtRemediationFunction.Type.LINEAR_OFFSET, "10h", "5min").toString()) + .isEqualTo("DebtRemediationFunction{type=LINEAR_OFFSET, factor=10h, offset=5min}"); } + @Ignore + @Test + public void fail_if_bad_factor_format() { + try { + new DefaultDebtRemediationFunction(DebtRemediationFunction.Type.LINEAR, "foo", null); + fail(); + } catch (IllegalArgumentException e) { + assertThat(e).hasMessage("TODO"); + } + + } } diff --git a/sonar-plugin-api/src/test/java/org/sonar/api/server/rule/RuleTagFormatTest.java b/sonar-plugin-api/src/test/java/org/sonar/api/server/rule/RuleTagFormatTest.java index e5a832178b4..fcc82e7ec7c 100644 --- a/sonar-plugin-api/src/test/java/org/sonar/api/server/rule/RuleTagFormatTest.java +++ b/sonar-plugin-api/src/test/java/org/sonar/api/server/rule/RuleTagFormatTest.java @@ -53,7 +53,7 @@ public class RuleTagFormatTest { RuleTagFormat.validate(" "); fail(); } catch (IllegalArgumentException e) { - assertThat(e).hasMessage("Tag ' ' is invalid. Rule tags accept only the following characters: a-z, 0-9, '+', '-', '#', '.'"); + assertThat(e).hasMessage("Tag ' ' is invalid. Rule tags accept only the characters: a-z, 0-9, '+', '-', '#', '.'"); } } } diff --git a/sonar-plugin-api/src/test/java/org/sonar/api/server/rule/RulesDefinitionI18nLoaderTest.java b/sonar-plugin-api/src/test/java/org/sonar/api/server/rule/RulesDefinitionI18nLoaderTest.java new file mode 100644 index 00000000000..5bea527e46d --- /dev/null +++ b/sonar-plugin-api/src/test/java/org/sonar/api/server/rule/RulesDefinitionI18nLoaderTest.java @@ -0,0 +1,99 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 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.server.rule; + +import org.junit.Test; +import org.sonar.api.i18n.RuleI18n; + +import static org.fest.assertions.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class RulesDefinitionI18nLoaderTest { + + RuleI18n i18n = mock(RuleI18n.class); + RulesDefinitionI18nLoader loader = new RulesDefinitionI18nLoader(i18n); + + @Test + public void complete_rule_name_and_description() throws Exception { + when(i18n.getName("squid", "S0001")).thenReturn("SOne"); + when(i18n.getDescription("squid", "S0001")).thenReturn("S One"); + + RulesDefinition.Context context = new RulesDefinition.Context(); + RulesDefinition.NewRepository repo = context.createRepository("squid", "java"); + // rule without description + repo.createRule("S0001"); + + loader.load(repo); + repo.done(); + + RulesDefinition.Rule rule = context.repository("squid").rule("S0001"); + assertThat(rule.name()).isEqualTo("SOne"); + assertThat(rule.htmlDescription()).isEqualTo("S One"); + } + + @Test + public void do_not_override_if_no_bundle() throws Exception { + // i18n returns null values + + RulesDefinition.Context context = new RulesDefinition.Context(); + RulesDefinition.NewRepository repo = context.createRepository("squid", "java"); + repo.createRule("S0001").setName("SOne").setHtmlDescription("S One"); + + loader.load(repo); + repo.done(); + + RulesDefinition.Rule rule = context.repository("squid").rule("S0001"); + assertThat(rule.name()).isEqualTo("SOne"); + assertThat(rule.htmlDescription()).isEqualTo("S One"); + } + + @Test + public void override_existing() throws Exception { + when(i18n.getName("squid", "S0001")).thenReturn("SOne"); + when(i18n.getDescription("squid", "S0001")).thenReturn("S One"); + + RulesDefinition.Context context = new RulesDefinition.Context(); + RulesDefinition.NewRepository repo = context.createRepository("squid", "java"); + repo.createRule("S0001").setName("Bad").setHtmlDescription("Bad"); + + loader.load(repo); + repo.done(); + + RulesDefinition.Rule rule = context.repository("squid").rule("S0001"); + assertThat(rule.name()).isEqualTo("SOne"); + assertThat(rule.htmlDescription()).isEqualTo("S One"); + } + + @Test + public void complete_param_description() throws Exception { + when(i18n.getParamDescription("squid", "S0001", "max")).thenReturn("Maximum"); + + RulesDefinition.Context context = new RulesDefinition.Context(); + RulesDefinition.NewRepository repo = context.createRepository("squid", "java"); + repo.createRule("S0001").setName("SOne").setHtmlDescription("S One").createParam("max"); + + loader.load(repo); + repo.done(); + + RulesDefinition.Rule rule = context.repository("squid").rule("S0001"); + assertThat(rule.param("max").description()).isEqualTo("Maximum"); + } +} 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 5a9f2511c12..96351855f53 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 @@ -22,6 +22,7 @@ package org.sonar.api.server.rule; import org.junit.Test; import org.sonar.api.rule.RuleStatus; import org.sonar.api.rule.Severity; +import org.sonar.api.server.debt.DebtRemediationFunction; import java.net.URL; @@ -63,8 +64,8 @@ public class RulesDefinitionTest { @Test public void define_rules() { - RulesDefinition.NewRepository newFindbugs = context.createRepository("findbugs", "java"); - RulesDefinition.NewRule newFindbug = newFindbugs.createRule("NPE") + RulesDefinition.NewRepository newRepo = context.createRepository("findbugs", "java"); + RulesDefinition.NewRule newRule = newRepo.createRule("NPE") .setName("Detect NPE") .setHtmlDescription("Detect <code>java.lang.NullPointerException</code>") .setSeverity(Severity.BLOCKER) @@ -74,36 +75,36 @@ public class RulesDefinitionTest { .setEffortToFixDescription("squid.S115.effortToFix") .setTags("one", "two") .addTags("two", "three", "four"); - newFindbug.setDebtRemediationFunction(newFindbug.debtRemediationFunctions().linearWithOffset("1h", "10min")); + newRule.setDebtRemediationFunction(newRule.debtRemediationFunctions().linearWithOffset("1h", "10min")); - newFindbugs.createRule("ABC").setName("ABC").setHtmlDescription("ABC"); - newFindbugs.done(); + newRepo.createRule("ABC").setName("ABC").setHtmlDescription("ABC"); + newRepo.done(); - RulesDefinition.Repository findbugs = context.repository("findbugs"); - assertThat(findbugs.rules()).hasSize(2); - - RulesDefinition.Rule npeRule = findbugs.rule("NPE"); - assertThat(npeRule.key()).isEqualTo("NPE"); - assertThat(npeRule.name()).isEqualTo("Detect NPE"); - assertThat(npeRule.severity()).isEqualTo(Severity.BLOCKER); - assertThat(npeRule.htmlDescription()).isEqualTo("Detect <code>java.lang.NullPointerException</code>"); - assertThat(npeRule.tags()).containsOnly("one", "two", "three", "four"); - assertThat(npeRule.params()).isEmpty(); - assertThat(npeRule.internalKey()).isEqualTo("/something"); - assertThat(npeRule.template()).isFalse(); - assertThat(npeRule.status()).isEqualTo(RuleStatus.BETA); - assertThat(npeRule.debtCharacteristic()).isEqualTo("COMPILER"); - assertThat(npeRule.debtRemediationFunction().type()).isEqualTo(DebtRemediationFunction.Type.LINEAR_OFFSET); - assertThat(npeRule.debtRemediationFunction().factor()).isEqualTo("1h"); - assertThat(npeRule.debtRemediationFunction().offset()).isEqualTo("10min"); - assertThat(npeRule.effortToFixDescription()).isEqualTo("squid.S115.effortToFix"); - assertThat(npeRule.toString()).isEqualTo("[repository=findbugs, key=NPE]"); - assertThat(npeRule.repository()).isSameAs(findbugs); + RulesDefinition.Repository repo = context.repository("findbugs"); + assertThat(repo.rules()).hasSize(2); + + RulesDefinition.Rule rule = repo.rule("NPE"); + assertThat(rule.key()).isEqualTo("NPE"); + assertThat(rule.name()).isEqualTo("Detect NPE"); + assertThat(rule.severity()).isEqualTo(Severity.BLOCKER); + assertThat(rule.htmlDescription()).isEqualTo("Detect <code>java.lang.NullPointerException</code>"); + assertThat(rule.tags()).containsOnly("one", "two", "three", "four"); + assertThat(rule.params()).isEmpty(); + assertThat(rule.internalKey()).isEqualTo("/something"); + assertThat(rule.template()).isFalse(); + assertThat(rule.status()).isEqualTo(RuleStatus.BETA); + assertThat(rule.debtCharacteristic()).isEqualTo("COMPILER"); + assertThat(rule.debtRemediationFunction().type()).isEqualTo(DebtRemediationFunction.Type.LINEAR_OFFSET); + assertThat(rule.debtRemediationFunction().factor()).isEqualTo("1h"); + assertThat(rule.debtRemediationFunction().offset()).isEqualTo("10min"); + assertThat(rule.effortToFixDescription()).isEqualTo("squid.S115.effortToFix"); + assertThat(rule.toString()).isEqualTo("[repository=findbugs, key=NPE]"); + assertThat(rule.repository()).isSameAs(repo); // test equals() and hashCode() - RulesDefinition.Rule otherRule = findbugs.rule("ABC"); - assertThat(npeRule).isEqualTo(npeRule).isNotEqualTo(otherRule).isNotEqualTo("NPE").isNotEqualTo(null); - assertThat(npeRule.hashCode()).isEqualTo(npeRule.hashCode()); + RulesDefinition.Rule otherRule = repo.rule("ABC"); + assertThat(rule).isEqualTo(rule).isNotEqualTo(otherRule).isNotEqualTo("NPE").isNotEqualTo(null); + assertThat(rule.hashCode()).isEqualTo(rule.hashCode()); } @Test @@ -239,14 +240,14 @@ public class RulesDefinitionTest { fail(); } catch (Exception e) { assertThat(e).isInstanceOf(IllegalArgumentException.class) - .hasMessage("Tag 'coding style' is invalid. Rule tags accept only the following characters: a-z, 0-9, '+', '-', '#', '.'"); + .hasMessage("Tag 'coding style' is invalid. Rule tags accept only the characters: a-z, 0-9, '+', '-', '#', '.'"); } } @Test public void load_rule_description_from_file() { RulesDefinition.NewRepository newRepository = context.createRepository("findbugs", "java"); - newRepository.createRule("NPE").setName("NPE").setHtmlDescription(getClass().getResource("/org/sonar/api/server/rule/RuleDefinitionsTest/sample.html")); + newRepository.createRule("NPE").setName("NPE").setHtmlDescription(getClass().getResource("/org/sonar/api/server/rule/RulesDefinitionTest/sample.html")); newRepository.done(); RulesDefinition.Rule rule = context.repository("findbugs").rule("NPE"); diff --git a/sonar-plugin-api/src/test/resources/org/sonar/api/server/rule/RuleDefinitionsTest/sample.html b/sonar-plugin-api/src/test/resources/org/sonar/api/server/rule/RulesDefinitionTest/sample.html index 86c36936b5c..86c36936b5c 100644 --- a/sonar-plugin-api/src/test/resources/org/sonar/api/server/rule/RuleDefinitionsTest/sample.html +++ b/sonar-plugin-api/src/test/resources/org/sonar/api/server/rule/RulesDefinitionTest/sample.html |