org.sonar.api.server.rule.RulesDefinition Maven / Gradle / Ivy
/*
* 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 com.google.common.base.Strings;
import com.google.common.collect.*;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
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;
import javax.annotation.concurrent.Immutable;
import java.io.IOException;
import java.net.URL;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* Defines the coding rules. For example the Java Findbugs plugin provides an implementation of
* this extension point in order to define the rules that it supports.
*
* This interface replaces the deprecated class org.sonar.api.rules.RuleRepository.
*
* How to use
*
* public class MyJsRulesDefinition implements RulesDefinition {
*
* {@literal @}Override
* public void define(Context context) {
* NewRepository repository = context.createRepository("my_js", "js").setName("My Javascript Analyzer");
*
* // 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
* .setDebtSubCharacteristic("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();
* }
* }
*
*
* If rules are declared in a XML file with the standard SonarQube format (see
* {@link org.sonar.api.server.rule.RulesDefinitionXmlLoader}), then it can be loaded by using :
*
*
* public class MyJsRulesDefinition implements RulesDefinition {
*
* private final RulesDefinitionXmlLoader xmlLoader;
*
* public MyJsRulesDefinition(RulesDefinitionXmlLoader xmlLoader) {
* this.xmlLoader = xmlLoader;
* }
*
* {@literal @}Override
* public void define(Context context) {
* NewRepository repository = context.createRepository("my_js", "js").setName("My Javascript Analyzer");
* // see javadoc of RulesDefinitionXmlLoader for the format
* xmlLoader.load(repository, getClass().getResourceAsStream("/path/to/rules.xml"));
* repository.done();
* }
* }
*
*
* 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 :
*
*
* public class MyJsRulesDefinition implements RulesDefinition {
*
* private final RulesDefinitionXmlLoader xmlLoader;
* private final RulesDefinitionI18nLoader i18nLoader;
*
* public MyJsRulesDefinition(RulesDefinitionXmlLoader xmlLoader, RulesDefinitionI18nLoader i18nLoader) {
* this.xmlLoader = xmlLoader;
* this.i18nLoader = i18nLoader;
* }
*
* {@literal @}Override
* public void define(Context context) {
* NewRepository repository = context.createRepository("my_js", "js").setName("My Javascript Analyzer");
* xmlLoader.load(repository, getClass().getResourceAsStream("/path/to/rules.xml"));
* i18nLoader.load(repository);
* repository.done();
* }
* }
*
*
* @since 4.3
*/
public interface RulesDefinition extends ServerExtension {
/**
* Default sub-characteristics of technical debt model. See http://www.sqale.org
*/
final class SubCharacteristics {
/**
* Related to characteristic REUSABILITY
*/
public static final String MODULARITY = "MODULARITY";
/**
* Related to characteristic REUSABILITY
*/
public static final String TRANSPORTABILITY = "TRANSPORTABILITY";
/**
* Related to characteristic PORTABILITY
*/
public static final String COMPILER_RELATED_PORTABILITY = "COMPILER_RELATED_PORTABILITY";
/**
* Related to characteristic PORTABILITY
*/
public static final String HARDWARE_RELATED_PORTABILITY = "HARDWARE_RELATED_PORTABILITY";
/**
* Related to characteristic PORTABILITY
*/
public static final String LANGUAGE_RELATED_PORTABILITY = "LANGUAGE_RELATED_PORTABILITY";
/**
* Related to characteristic PORTABILITY
*/
public static final String OS_RELATED_PORTABILITY = "OS_RELATED_PORTABILITY";
/**
* Related to characteristic PORTABILITY
*/
public static final String SOFTWARE_RELATED_PORTABILITY = "SOFTWARE_RELATED_PORTABILITY";
/**
* Related to characteristic PORTABILITY
*/
public static final String TIME_ZONE_RELATED_PORTABILITY = "TIME_ZONE_RELATED_PORTABILITY";
/**
* Related to characteristic MAINTAINABILITY
*/
public static final String READABILITY = "READABILITY";
/**
* Related to characteristic MAINTAINABILITY
*/
public static final String UNDERSTANDABILITY = "UNDERSTANDABILITY";
/**
* Related to characteristic SECURITY
*/
public static final String API_ABUSE = "API_ABUSE";
/**
* Related to characteristic SECURITY
*/
public static final String ERRORS = "ERRORS";
/**
* Related to characteristic SECURITY
*/
public static final String INPUT_VALIDATION_AND_REPRESENTATION = "INPUT_VALIDATION_AND_REPRESENTATION";
/**
* Related to characteristic SECURITY
*/
public static final String SECURITY_FEATURES = "SECURITY_FEATURES";
/**
* Related to characteristic EFFICIENCY
*/
public static final String CPU_EFFICIENCY = "CPU_EFFICIENCY";
/**
* Related to characteristic EFFICIENCY
*/
public static final String MEMORY_EFFICIENCY = "MEMORY_EFFICIENCY";
/**
* Related to characteristic EFFICIENCY
*/
public static final String NETWORK_USE = "NETWORK_USE";
/**
* Related to characteristic CHANGEABILITY
*/
public static final String ARCHITECTURE_CHANGEABILITY = "ARCHITECTURE_CHANGEABILITY";
/**
* Related to characteristic CHANGEABILITY
*/
public static final String DATA_CHANGEABILITY = "DATA_CHANGEABILITY";
/**
* Related to characteristic CHANGEABILITY
*/
public static final String LOGIC_CHANGEABILITY = "LOGIC_CHANGEABILITY";
/**
* Related to characteristic RELIABILITY
*/
public static final String ARCHITECTURE_RELIABILITY = "ARCHITECTURE_RELIABILITY";
/**
* Related to characteristic RELIABILITY
*/
public static final String DATA_RELIABILITY = "DATA_RELIABILITY";
/**
* Related to characteristic RELIABILITY
*/
public static final String EXCEPTION_HANDLING = "EXCEPTION_HANDLING";
/**
* Related to characteristic RELIABILITY
*/
public static final String FAULT_TOLERANCE = "FAULT_TOLERANCE";
/**
* Related to characteristic RELIABILITY
*/
public static final String INSTRUCTION_RELIABILITY = "INSTRUCTION_RELIABILITY";
/**
* Related to characteristic RELIABILITY
*/
public static final String LOGIC_RELIABILITY = "LOGIC_RELIABILITY";
/**
* Related to characteristic RELIABILITY
*/
public static final String RESOURCE_RELIABILITY = "RESOURCE_RELIABILITY";
/**
* Related to characteristic RELIABILITY
*/
public static final String SYNCHRONIZATION_RELIABILITY = "SYNCHRONIZATION_RELIABILITY";
/**
* Related to characteristic RELIABILITY
*/
public static final String UNIT_TESTS = "UNIT_TESTS";
/**
* Related to characteristic TESTABILITY
*/
public static final String INTEGRATION_TESTABILITY = "INTEGRATION_TESTABILITY";
/**
* Related to characteristic TESTABILITY
*/
public static final String UNIT_TESTABILITY = "UNIT_TESTABILITY";
private SubCharacteristics() {
// only constants
}
}
/**
* Instantiated by core but not by plugins
*/
class Context {
private final Map repositoriesByKey = Maps.newHashMap();
private final ListMultimap extendedRepositoriesByKey = ArrayListMultimap.create();
public NewRepository createRepository(String key, String language) {
return new NewRepositoryImpl(this, key, language, false);
}
public NewExtendedRepository extendRepository(String key, String language) {
return new NewRepositoryImpl(this, key, language, true);
}
@CheckForNull
public Repository repository(String key) {
return repositoriesByKey.get(key);
}
public List repositories() {
return ImmutableList.copyOf(repositoriesByKey.values());
}
public List extendedRepositories(String repositoryKey) {
return ImmutableList.copyOf(extendedRepositoriesByKey.get(repositoryKey));
}
public List extendedRepositories() {
return ImmutableList.copyOf(extendedRepositoriesByKey.values());
}
private void registerRepository(NewRepositoryImpl newRepository) {
if (repositoriesByKey.containsKey(newRepository.key)) {
throw new IllegalStateException(String.format("The rule repository '%s' is defined several times", newRepository.key));
}
repositoriesByKey.put(newRepository.key, new RepositoryImpl(newRepository));
}
private void registerExtendedRepository(NewRepositoryImpl newRepository) {
extendedRepositoriesByKey.put(newRepository.key, new RepositoryImpl(newRepository));
}
}
interface NewExtendedRepository {
NewRule createRule(String ruleKey);
@CheckForNull
NewRule rule(String ruleKey);
Collection rules();
String key();
void done();
}
interface NewRepository extends NewExtendedRepository {
NewRepository setName(String s);
}
class NewRepositoryImpl implements NewRepository {
private final Context context;
private final boolean extended;
private final String key;
private String language;
private String name;
private final Map newRules = Maps.newHashMap();
private NewRepositoryImpl(Context context, String key, String language, boolean extended) {
this.extended = extended;
this.context = context;
this.key = this.name = key;
this.language = language;
}
@Override
public String key() {
return key;
}
@Override
public NewRepositoryImpl setName(@Nullable String s) {
if (StringUtils.isNotEmpty(s)) {
this.name = s;
}
return this;
}
@Override
public NewRule createRule(String ruleKey) {
if (newRules.containsKey(ruleKey)) {
// 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;
}
@CheckForNull
@Override
public NewRule rule(String ruleKey) {
return newRules.get(ruleKey);
}
@Override
public Collection rules() {
return newRules.values();
}
@Override
public void done() {
// note that some validations can be done here, for example for
// verifying that at least one rule is declared
if (extended) {
context.registerExtendedRepository(this);
} else {
context.registerRepository(this);
}
}
}
interface ExtendedRepository {
String key();
String language();
@CheckForNull
Rule rule(String ruleKey);
List rules();
}
interface Repository extends ExtendedRepository {
String name();
}
@Immutable
class RepositoryImpl implements Repository {
private final String key, language, name;
private final Map rulesByKey;
private RepositoryImpl(NewRepositoryImpl newRepository) {
this.key = newRepository.key;
this.language = newRepository.language;
this.name = newRepository.name;
ImmutableMap.Builder ruleBuilder = ImmutableMap.builder();
for (NewRule newRule : newRepository.newRules.values()) {
newRule.validate();
ruleBuilder.put(newRule.key, new Rule(this, newRule));
}
this.rulesByKey = ruleBuilder.build();
}
@Override
public String key() {
return key;
}
@Override
public String language() {
return language;
}
@Override
public String name() {
return name;
}
@Override
@CheckForNull
public Rule rule(String ruleKey) {
return rulesByKey.get(ruleKey);
}
@Override
public List rules() {
return ImmutableList.copyOf(rulesByKey.values());
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
RepositoryImpl that = (RepositoryImpl) o;
return key.equals(that.key);
}
@Override
public int hashCode() {
return key.hashCode();
}
}
/**
* Factory of {@link org.sonar.api.server.debt.DebtRemediationFunction}.
*/
interface DebtRemediationFunctions {
DebtRemediationFunction linear(String coefficient);
DebtRemediationFunction linearWithOffset(String coefficient, String offset);
DebtRemediationFunction constantPerIssue(String offset);
}
class NewRule {
private final String repoKey, key;
private String name, htmlDescription, internalKey, severity = Severity.MAJOR;
private boolean template;
private RuleStatus status = RuleStatus.defaultStatus();
private String debtSubCharacteristic;
private DebtRemediationFunction debtRemediationFunction;
private String effortToFixDescription;
private final Set tags = Sets.newTreeSet();
private final Map paramsByKey = Maps.newHashMap();
private final DebtRemediationFunctions functions;
private NewRule(String repoKey, String key) {
this.repoKey = repoKey;
this.key = key;
this.functions = new DefaultDebtRemediationFunctions(repoKey, key);
}
public String key() {
return this.key;
}
/**
* Required rule name
*/
public NewRule setName(@Nullable String s) {
this.name = StringUtils.trimToNull(s);
return this;
}
public NewRule setTemplate(boolean template) {
this.template = template;
return this;
}
public NewRule setSeverity(String s) {
if (!Severity.ALL.contains(s)) {
throw new IllegalArgumentException(String.format("Severity of rule %s is not correct: %s", this, s));
}
this.severity = s;
return this;
}
public NewRule setHtmlDescription(@Nullable String s) {
this.htmlDescription = StringUtils.trimToNull(s);
return this;
}
/**
* Load description from a file available in classpath. Example : setHtmlDescription(getClass().getResource("/myrepo/Rule1234.html")
*/
public NewRule setHtmlDescription(@Nullable URL classpathUrl) {
if (classpathUrl != null) {
try {
setHtmlDescription(IOUtils.toString(classpathUrl));
} catch (IOException e) {
throw new IllegalStateException("Fail to read: " + classpathUrl, e);
}
} else {
this.htmlDescription = null;
}
return this;
}
/**
* Default value is {@link org.sonar.api.rule.RuleStatus#READY}. The value
* {@link org.sonar.api.rule.RuleStatus#REMOVED} is not accepted and raises an
* {@link java.lang.IllegalArgumentException}.
*/
public NewRule setStatus(RuleStatus status) {
if (status.equals(RuleStatus.REMOVED)) {
throw new IllegalArgumentException(String.format("Status 'REMOVED' is not accepted on rule '%s'", this));
}
this.status = status;
return this;
}
/**
* SQALE sub-characteristic. See http://www.sqale.org
*
* @see org.sonar.api.server.rule.RulesDefinition.SubCharacteristics for constant values
*/
public NewRule setDebtSubCharacteristic(@Nullable String s) {
this.debtSubCharacteristic = s;
return this;
}
/**
* Factory of {@link org.sonar.api.server.debt.DebtRemediationFunction}
*/
public DebtRemediationFunctions debtRemediationFunctions() {
return functions;
}
/**
* @see #debtRemediationFunctions()
*/
public NewRule setDebtRemediationFunction(@Nullable DebtRemediationFunction fn) {
this.debtRemediationFunction = fn;
return this;
}
/**
* For rules that use "Linear"/"Linear with offset" remediation functions, the meaning
* of the function parameter (= "effort to fix") must be set. This description
* explains what 1 point of "effort to fix" represents for the rule.
*
* Example : : for the "Insufficient branch coverage", this description for the
* remediation function coefficient/offset would be something like
* "Effort to test one uncovered branch".
*/
public NewRule setEffortToFixDescription(@Nullable String s) {
this.effortToFixDescription = s;
return this;
}
public NewParam createParam(String paramKey) {
if (paramsByKey.containsKey(paramKey)) {
throw new IllegalArgumentException(String.format("The parameter '%s' is declared several times on the rule %s", paramKey, this));
}
NewParam param = new NewParam(paramKey);
paramsByKey.put(paramKey, param);
return param;
}
@CheckForNull
public NewParam param(String paramKey) {
return paramsByKey.get(paramKey);
}
public Collection params() {
return paramsByKey.values();
}
/**
* @see RuleTagFormat
*/
public NewRule addTags(String... list) {
for (String tag : list) {
RuleTagFormat.validate(tag);
tags.add(tag);
}
return this;
}
/**
* @see RuleTagFormat
*/
public NewRule setTags(String... list) {
tags.clear();
addTags(list);
return this;
}
/**
* Optional key that can be used by the rule engine. Not displayed
* in webapp. For example the Java Checkstyle plugin feeds this field
* with the internal path ("Checker/TreeWalker/AnnotationUseStyle").
*/
public NewRule setInternalKey(@Nullable String s) {
this.internalKey = s;
return this;
}
private void validate() {
if (Strings.isNullOrEmpty(name)) {
throw new IllegalStateException(String.format("Name of rule %s is empty", this));
}
if (Strings.isNullOrEmpty(htmlDescription)) {
throw new IllegalStateException(String.format("HTML description of rule %s is empty", this));
}
if ((Strings.isNullOrEmpty(debtSubCharacteristic) && debtRemediationFunction != null) || (!Strings.isNullOrEmpty(debtSubCharacteristic) && debtRemediationFunction == null)) {
throw new IllegalStateException(String.format("Both debt sub-characteristic and debt remediation function should be defined on rule '%s'", this));
}
}
@Override
public String toString() {
return String.format("[repository=%s, key=%s]", repoKey, key);
}
}
@Immutable
class Rule {
private final Repository repository;
private final String repoKey, key, name, htmlDescription, internalKey, severity;
private final boolean template;
private final String debtSubCharacteristic;
private final DebtRemediationFunction debtRemediationFunction;
private final String effortToFixDescription;
private final Set tags;
private final Map params;
private final RuleStatus status;
private Rule(Repository repository, NewRule newRule) {
this.repository = repository;
this.repoKey = newRule.repoKey;
this.key = newRule.key;
this.name = newRule.name;
this.htmlDescription = newRule.htmlDescription;
this.internalKey = newRule.internalKey;
this.severity = newRule.severity;
this.template = newRule.template;
this.status = newRule.status;
this.debtSubCharacteristic = newRule.debtSubCharacteristic;
this.debtRemediationFunction = newRule.debtRemediationFunction;
this.effortToFixDescription = newRule.effortToFixDescription;
this.tags = ImmutableSortedSet.copyOf(newRule.tags);
ImmutableMap.Builder paramsBuilder = ImmutableMap.builder();
for (NewParam newParam : newRule.paramsByKey.values()) {
paramsBuilder.put(newParam.key, new Param(newParam));
}
this.params = paramsBuilder.build();
}
public Repository repository() {
return repository;
}
public String key() {
return key;
}
public String name() {
return name;
}
public String severity() {
return severity;
}
@CheckForNull
public String htmlDescription() {
return htmlDescription;
}
public boolean template() {
return template;
}
public RuleStatus status() {
return status;
}
@CheckForNull
public String debtSubCharacteristic() {
return debtSubCharacteristic;
}
@CheckForNull
public DebtRemediationFunction debtRemediationFunction() {
return debtRemediationFunction;
}
@CheckForNull
public String effortToFixDescription() {
return effortToFixDescription;
}
@CheckForNull
public Param param(String key) {
return params.get(key);
}
public List params() {
return ImmutableList.copyOf(params.values());
}
public Set tags() {
return tags;
}
/**
* @see RulesDefinition.NewRule#setInternalKey(String)
*/
@CheckForNull
public String internalKey() {
return internalKey;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
Rule other = (Rule) o;
return key.equals(other.key) && repoKey.equals(other.repoKey);
}
@Override
public int hashCode() {
int result = repoKey.hashCode();
result = 31 * result + key.hashCode();
return result;
}
@Override
public String toString() {
return String.format("[repository=%s, key=%s]", repoKey, key);
}
}
class NewParam {
private final String key;
private String name, description, defaultValue;
private RuleParamType type = RuleParamType.STRING;
private NewParam(String key) {
this.key = this.name = key;
}
public String key() {
return key;
}
public NewParam setName(@Nullable String s) {
// name must never be null.
this.name = StringUtils.defaultIfBlank(s, key);
return this;
}
public NewParam setType(RuleParamType t) {
this.type = t;
return this;
}
/**
* Plain-text description. Can be null.
*/
public NewParam setDescription(@Nullable String s) {
this.description = StringUtils.defaultIfBlank(s, null);
return this;
}
public NewParam setDefaultValue(@Nullable String s) {
this.defaultValue = s;
return this;
}
}
@Immutable
class Param {
private final String key, name, description, defaultValue;
private final RuleParamType type;
private Param(NewParam newParam) {
this.key = newParam.key;
this.name = newParam.name;
this.description = newParam.description;
this.defaultValue = newParam.defaultValue;
this.type = newParam.type;
}
public String key() {
return key;
}
public String name() {
return name;
}
@Nullable
public String description() {
return description;
}
@Nullable
public String defaultValue() {
return defaultValue;
}
public RuleParamType type() {
return type;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
Param that = (Param) o;
return key.equals(that.key);
}
@Override
public int hashCode() {
return key.hashCode();
}
}
/**
* This method is executed when server is started.
*/
void define(Context context);
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy