testCompile 'junit:junit' | testCompile 'junit:junit' | ||||
testCompile 'org.assertj:assertj-core' | testCompile 'org.assertj:assertj-core' | ||||
testCompile 'org.mockito:mockito-core' | testCompile 'org.mockito:mockito-core' | ||||
testCompile project(':server:sonar-server') | |||||
} | } | ||||
jar { | jar { |
import org.sonar.api.server.debt.DebtRemediationFunction; | import org.sonar.api.server.debt.DebtRemediationFunction; | ||||
import org.sonar.api.server.rule.RulesDefinition; | import org.sonar.api.server.rule.RulesDefinition; | ||||
import org.sonar.api.utils.Version; | import org.sonar.api.utils.Version; | ||||
import org.sonar.server.rule.RuleDefinitionContext; | |||||
import static org.assertj.core.api.Assertions.assertThat; | import static org.assertj.core.api.Assertions.assertThat; | ||||
@Before | @Before | ||||
public void setUp() { | public void setUp() { | ||||
XooRulesDefinition def = new XooRulesDefinition(SonarRuntimeImpl.forSonarQube(Version.create(7, 3), SonarQubeSide.SCANNER, SonarEdition.COMMUNITY)); | XooRulesDefinition def = new XooRulesDefinition(SonarRuntimeImpl.forSonarQube(Version.create(7, 3), SonarQubeSide.SCANNER, SonarEdition.COMMUNITY)); | ||||
context = new RulesDefinition.Context(); | |||||
context = new RuleDefinitionContext(); | |||||
def.define(context); | def.define(context); | ||||
} | } | ||||
* along with this program; if not, write to the Free Software Foundation, | * along with this program; if not, write to the Free Software Foundation, | ||||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | ||||
*/ | */ | ||||
package org.sonar.api.server.rule; | |||||
package org.sonar.server.rule; | |||||
import javax.annotation.Nullable; | import javax.annotation.Nullable; | ||||
import org.sonar.api.server.debt.DebtRemediationFunction; | import org.sonar.api.server.debt.DebtRemediationFunction; | ||||
import org.sonar.api.server.debt.internal.DefaultDebtRemediationFunction; | import org.sonar.api.server.debt.internal.DefaultDebtRemediationFunction; | ||||
import org.sonar.api.server.rule.RulesDefinition; | |||||
import org.sonar.api.utils.MessageException; | import org.sonar.api.utils.MessageException; | ||||
/** | /** |
/* | |||||
* SonarQube | |||||
* Copyright (C) 2009-2019 SonarSource SA | |||||
* mailto:info AT sonarsource DOT com | |||||
* | |||||
* This program 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. | |||||
* | |||||
* This program 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.server.rule; | |||||
import javax.annotation.Nullable; | |||||
import org.apache.commons.lang.StringUtils; | |||||
import org.sonar.api.server.rule.RuleParamType; | |||||
import org.sonar.api.server.rule.RulesDefinition; | |||||
import static org.apache.commons.lang.StringUtils.defaultIfEmpty; | |||||
public class DefaultNewParam implements RulesDefinition.NewParam { | |||||
private final String key; | |||||
private String name; | |||||
private String description; | |||||
private String defaultValue; | |||||
private RuleParamType type = RuleParamType.STRING; | |||||
DefaultNewParam(String key) { | |||||
this.key = this.name = key; | |||||
} | |||||
@Override | |||||
public String key() { | |||||
return key; | |||||
} | |||||
@Override | |||||
public DefaultNewParam setName(@Nullable String s) { | |||||
// name must never be null. | |||||
this.name = StringUtils.defaultIfBlank(s, key); | |||||
return this; | |||||
} | |||||
@Override | |||||
public DefaultNewParam setType(RuleParamType t) { | |||||
this.type = t; | |||||
return this; | |||||
} | |||||
@Override | |||||
public DefaultNewParam setDescription(@Nullable String s) { | |||||
this.description = StringUtils.defaultIfBlank(s, null); | |||||
return this; | |||||
} | |||||
@Override | |||||
public DefaultNewParam setDefaultValue(@Nullable String s) { | |||||
this.defaultValue = defaultIfEmpty(s, null); | |||||
return this; | |||||
} | |||||
public String name() { | |||||
return name; | |||||
} | |||||
public String description() { | |||||
return description; | |||||
} | |||||
public String defaultValue() { | |||||
return defaultValue; | |||||
} | |||||
public RuleParamType type() { | |||||
return type; | |||||
} | |||||
} |
/* | |||||
* SonarQube | |||||
* Copyright (C) 2009-2019 SonarSource SA | |||||
* mailto:info AT sonarsource DOT com | |||||
* | |||||
* This program 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. | |||||
* | |||||
* This program 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.server.rule; | |||||
import java.util.Collection; | |||||
import java.util.HashMap; | |||||
import java.util.Map; | |||||
import javax.annotation.CheckForNull; | |||||
import javax.annotation.Nullable; | |||||
import org.apache.commons.lang.StringUtils; | |||||
import org.sonar.api.server.rule.RulesDefinition; | |||||
import static org.sonar.api.utils.Preconditions.checkArgument; | |||||
public class DefaultNewRepository implements RulesDefinition.NewRepository { | |||||
private final RuleDefinitionContext context; | |||||
private final String key; | |||||
private final boolean isExternal; | |||||
private final String language; | |||||
private String name; | |||||
private final Map<String, RulesDefinition.NewRule> newRules = new HashMap<>(); | |||||
DefaultNewRepository(RuleDefinitionContext context, String key, String language, boolean isExternal) { | |||||
this.context = context; | |||||
this.key = key; | |||||
this.name = key; | |||||
this.language = language; | |||||
this.isExternal = isExternal; | |||||
} | |||||
@Override | |||||
public boolean isExternal() { | |||||
return isExternal; | |||||
} | |||||
@Override | |||||
public String key() { | |||||
return key; | |||||
} | |||||
String language() { | |||||
return language; | |||||
} | |||||
Map<String, RulesDefinition.NewRule> newRules() { | |||||
return newRules; | |||||
} | |||||
String name() { | |||||
return name; | |||||
} | |||||
@Override | |||||
public DefaultNewRepository setName(@Nullable String s) { | |||||
if (StringUtils.isNotEmpty(s)) { | |||||
this.name = s; | |||||
} | |||||
return this; | |||||
} | |||||
@Override | |||||
public RulesDefinition.NewRule createRule(String ruleKey) { | |||||
checkArgument(!newRules.containsKey(ruleKey), "The rule '%s' of repository '%s' is declared several times", ruleKey, key); | |||||
RulesDefinition.NewRule newRule = new DefaultNewRule(context.currentPluginKey(), key, ruleKey); | |||||
newRules.put(ruleKey, newRule); | |||||
return newRule; | |||||
} | |||||
@CheckForNull | |||||
@Override | |||||
public RulesDefinition.NewRule rule(String ruleKey) { | |||||
return newRules.get(ruleKey); | |||||
} | |||||
@Override | |||||
public Collection<RulesDefinition.NewRule> 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 | |||||
context.registerRepository(this); | |||||
} | |||||
@Override | |||||
public String toString() { | |||||
StringBuilder sb = new StringBuilder("NewRepository{"); | |||||
sb.append("key='").append(key).append('\''); | |||||
sb.append(", language='").append(language).append('\''); | |||||
sb.append('}'); | |||||
return sb.toString(); | |||||
} | |||||
} |
/* | |||||
* SonarQube | |||||
* Copyright (C) 2009-2019 SonarSource SA | |||||
* mailto:info AT sonarsource DOT com | |||||
* | |||||
* This program 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. | |||||
* | |||||
* This program 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.server.rule; | |||||
import java.io.IOException; | |||||
import java.net.URL; | |||||
import java.util.Collection; | |||||
import java.util.HashMap; | |||||
import java.util.Locale; | |||||
import java.util.Map; | |||||
import java.util.Set; | |||||
import java.util.TreeSet; | |||||
import javax.annotation.CheckForNull; | |||||
import javax.annotation.Nullable; | |||||
import org.apache.commons.io.IOUtils; | |||||
import org.sonar.api.rule.RuleKey; | |||||
import org.sonar.api.rule.RuleScope; | |||||
import org.sonar.api.rule.RuleStatus; | |||||
import org.sonar.api.rule.Severity; | |||||
import org.sonar.api.rules.RuleType; | |||||
import org.sonar.api.server.debt.DebtRemediationFunction; | |||||
import org.sonar.api.server.rule.RuleTagFormat; | |||||
import org.sonar.api.server.rule.RulesDefinition; | |||||
import static java.lang.String.format; | |||||
import static java.nio.charset.StandardCharsets.UTF_8; | |||||
import static org.apache.commons.lang.StringUtils.isEmpty; | |||||
import static org.apache.commons.lang.StringUtils.trimToNull; | |||||
import static org.sonar.api.utils.Preconditions.checkArgument; | |||||
import static org.sonar.api.utils.Preconditions.checkState; | |||||
class DefaultNewRule implements RulesDefinition.NewRule { | |||||
private final String pluginKey; | |||||
private final String repoKey; | |||||
private final String key; | |||||
private RuleType type; | |||||
private String name; | |||||
private String htmlDescription; | |||||
private String markdownDescription; | |||||
private String internalKey; | |||||
private String severity = Severity.MAJOR; | |||||
private boolean template; | |||||
private RuleStatus status = RuleStatus.defaultStatus(); | |||||
private DebtRemediationFunction debtRemediationFunction; | |||||
private String gapDescription; | |||||
private final Set<String> tags = new TreeSet<>(); | |||||
private final Set<String> securityStandards = new TreeSet<>(); | |||||
private final Map<String, RulesDefinition.NewParam> paramsByKey = new HashMap<>(); | |||||
private final RulesDefinition.DebtRemediationFunctions functions; | |||||
private boolean activatedByDefault; | |||||
private RuleScope scope; | |||||
private final Set<RuleKey> deprecatedRuleKeys = new TreeSet<>(); | |||||
DefaultNewRule(@Nullable String pluginKey, String repoKey, String key) { | |||||
this.pluginKey = pluginKey; | |||||
this.repoKey = repoKey; | |||||
this.key = key; | |||||
this.functions = new DefaultDebtRemediationFunctions(repoKey, key); | |||||
} | |||||
@Override | |||||
public String key() { | |||||
return this.key; | |||||
} | |||||
@CheckForNull | |||||
@Override | |||||
public RuleScope scope() { | |||||
return this.scope; | |||||
} | |||||
@Override | |||||
public DefaultNewRule setScope(RuleScope scope) { | |||||
this.scope = scope; | |||||
return this; | |||||
} | |||||
@Override | |||||
public DefaultNewRule setName(String s) { | |||||
this.name = trimToNull(s); | |||||
return this; | |||||
} | |||||
@Override | |||||
public DefaultNewRule setTemplate(boolean template) { | |||||
this.template = template; | |||||
return this; | |||||
} | |||||
@Override | |||||
public DefaultNewRule setActivatedByDefault(boolean activatedByDefault) { | |||||
this.activatedByDefault = activatedByDefault; | |||||
return this; | |||||
} | |||||
@Override | |||||
public DefaultNewRule setSeverity(String s) { | |||||
checkArgument(Severity.ALL.contains(s), "Severity of rule %s is not correct: %s", this, s); | |||||
this.severity = s; | |||||
return this; | |||||
} | |||||
@Override | |||||
public DefaultNewRule setType(RuleType t) { | |||||
this.type = t; | |||||
return this; | |||||
} | |||||
@Override | |||||
public DefaultNewRule setHtmlDescription(@Nullable String s) { | |||||
checkState(markdownDescription == null, "Rule '%s' already has a Markdown description", this); | |||||
this.htmlDescription = trimToNull(s); | |||||
return this; | |||||
} | |||||
@Override | |||||
public DefaultNewRule setHtmlDescription(@Nullable URL classpathUrl) { | |||||
if (classpathUrl != null) { | |||||
try { | |||||
setHtmlDescription(IOUtils.toString(classpathUrl, UTF_8)); | |||||
} catch (IOException e) { | |||||
throw new IllegalStateException("Fail to read: " + classpathUrl, e); | |||||
} | |||||
} else { | |||||
this.htmlDescription = null; | |||||
} | |||||
return this; | |||||
} | |||||
@Override | |||||
public DefaultNewRule setMarkdownDescription(@Nullable String s) { | |||||
checkState(htmlDescription == null, "Rule '%s' already has an HTML description", this); | |||||
this.markdownDescription = trimToNull(s); | |||||
return this; | |||||
} | |||||
@Override | |||||
public DefaultNewRule setMarkdownDescription(@Nullable URL classpathUrl) { | |||||
if (classpathUrl != null) { | |||||
try { | |||||
setMarkdownDescription(IOUtils.toString(classpathUrl, UTF_8)); | |||||
} catch (IOException e) { | |||||
throw new IllegalStateException("Fail to read: " + classpathUrl, e); | |||||
} | |||||
} else { | |||||
this.markdownDescription = null; | |||||
} | |||||
return this; | |||||
} | |||||
@Override | |||||
public DefaultNewRule setStatus(RuleStatus status) { | |||||
checkArgument(RuleStatus.REMOVED != status, "Status 'REMOVED' is not accepted on rule '%s'", this); | |||||
this.status = status; | |||||
return this; | |||||
} | |||||
@Override | |||||
public DefaultNewRule setDebtSubCharacteristic(@Nullable String s) { | |||||
return this; | |||||
} | |||||
@Override | |||||
public RulesDefinition.DebtRemediationFunctions debtRemediationFunctions() { | |||||
return functions; | |||||
} | |||||
@Override | |||||
public DefaultNewRule setDebtRemediationFunction(@Nullable DebtRemediationFunction fn) { | |||||
this.debtRemediationFunction = fn; | |||||
return this; | |||||
} | |||||
@Deprecated | |||||
@Override | |||||
public DefaultNewRule setEffortToFixDescription(@Nullable String s) { | |||||
return setGapDescription(s); | |||||
} | |||||
@Override | |||||
public DefaultNewRule setGapDescription(@Nullable String s) { | |||||
this.gapDescription = s; | |||||
return this; | |||||
} | |||||
@Override | |||||
public RulesDefinition.NewParam createParam(String paramKey) { | |||||
checkArgument(!paramsByKey.containsKey(paramKey), "The parameter '%s' is declared several times on the rule %s", paramKey, this); | |||||
DefaultNewParam param = new DefaultNewParam(paramKey); | |||||
paramsByKey.put(paramKey, param); | |||||
return param; | |||||
} | |||||
@CheckForNull | |||||
@Override | |||||
public RulesDefinition.NewParam param(String paramKey) { | |||||
return paramsByKey.get(paramKey); | |||||
} | |||||
@Override | |||||
public Collection<RulesDefinition.NewParam> params() { | |||||
return paramsByKey.values(); | |||||
} | |||||
@Override | |||||
public DefaultNewRule addTags(String... list) { | |||||
for (String tag : list) { | |||||
RuleTagFormat.validate(tag); | |||||
tags.add(tag); | |||||
} | |||||
return this; | |||||
} | |||||
@Override | |||||
public DefaultNewRule setTags(String... list) { | |||||
tags.clear(); | |||||
addTags(list); | |||||
return this; | |||||
} | |||||
@Override | |||||
public DefaultNewRule addOwaspTop10(RulesDefinition.OwaspTop10... standards) { | |||||
for (RulesDefinition.OwaspTop10 owaspTop10 : standards) { | |||||
String standard = "owaspTop10:" + owaspTop10.name().toLowerCase(Locale.ENGLISH); | |||||
securityStandards.add(standard); | |||||
} | |||||
return this; | |||||
} | |||||
@Override | |||||
public DefaultNewRule addCwe(int... nums) { | |||||
for (int num : nums) { | |||||
String standard = "cwe:" + num; | |||||
securityStandards.add(standard); | |||||
} | |||||
return this; | |||||
} | |||||
@Override | |||||
public DefaultNewRule setInternalKey(@Nullable String s) { | |||||
this.internalKey = s; | |||||
return this; | |||||
} | |||||
void validate() { | |||||
if (isEmpty(name)) { | |||||
throw new IllegalStateException(format("Name of rule %s is empty", this)); | |||||
} | |||||
if (isEmpty(htmlDescription) && isEmpty(markdownDescription)) { | |||||
throw new IllegalStateException(format("One of HTML description or Markdown description must be defined for rule %s", this)); | |||||
} | |||||
} | |||||
@Override | |||||
public DefaultNewRule addDeprecatedRuleKey(String repository, String key) { | |||||
deprecatedRuleKeys.add(RuleKey.of(repository, key)); | |||||
return this; | |||||
} | |||||
String pluginKey() { | |||||
return pluginKey; | |||||
} | |||||
String repoKey() { | |||||
return repoKey; | |||||
} | |||||
RuleType type() { | |||||
return type; | |||||
} | |||||
String name() { | |||||
return name; | |||||
} | |||||
String htmlDescription() { | |||||
return htmlDescription; | |||||
} | |||||
String markdownDescription() { | |||||
return markdownDescription; | |||||
} | |||||
@CheckForNull | |||||
String internalKey() { | |||||
return internalKey; | |||||
} | |||||
String severity() { | |||||
return severity; | |||||
} | |||||
boolean template() { | |||||
return template; | |||||
} | |||||
RuleStatus status() { | |||||
return status; | |||||
} | |||||
DebtRemediationFunction debtRemediationFunction() { | |||||
return debtRemediationFunction; | |||||
} | |||||
String gapDescription() { | |||||
return gapDescription; | |||||
} | |||||
Set<String> tags() { | |||||
return tags; | |||||
} | |||||
Set<String> securityStandards() { | |||||
return securityStandards; | |||||
} | |||||
Map<String, RulesDefinition.NewParam> paramsByKey() { | |||||
return paramsByKey; | |||||
} | |||||
boolean activatedByDefault() { | |||||
return activatedByDefault; | |||||
} | |||||
Set<RuleKey> deprecatedRuleKeys() { | |||||
return deprecatedRuleKeys; | |||||
} | |||||
@Override | |||||
public String toString() { | |||||
return format("[repository=%s, key=%s]", repoKey, key); | |||||
} | |||||
} |
/* | |||||
* SonarQube | |||||
* Copyright (C) 2009-2019 SonarSource SA | |||||
* mailto:info AT sonarsource DOT com | |||||
* | |||||
* This program 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. | |||||
* | |||||
* This program 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.server.rule; | |||||
import javax.annotation.Nullable; | |||||
import javax.annotation.concurrent.Immutable; | |||||
import org.sonar.api.server.rule.RuleParamType; | |||||
import org.sonar.api.server.rule.RulesDefinition; | |||||
@Immutable | |||||
public class DefaultParam implements RulesDefinition.Param { | |||||
private final String key; | |||||
private final String name; | |||||
private final String description; | |||||
private final String defaultValue; | |||||
private final RuleParamType type; | |||||
DefaultParam(DefaultNewParam newParam) { | |||||
this.key = newParam.key(); | |||||
this.name = newParam.name(); | |||||
this.description = newParam.description(); | |||||
this.defaultValue = newParam.defaultValue(); | |||||
this.type = newParam.type(); | |||||
} | |||||
@Override | |||||
public String key() { | |||||
return key; | |||||
} | |||||
@Override | |||||
public String name() { | |||||
return name; | |||||
} | |||||
@Override | |||||
@Nullable | |||||
public String description() { | |||||
return description; | |||||
} | |||||
@Override | |||||
@Nullable | |||||
public String defaultValue() { | |||||
return defaultValue; | |||||
} | |||||
@Override | |||||
public RuleParamType type() { | |||||
return type; | |||||
} | |||||
@Override | |||||
public boolean equals(Object o) { | |||||
if (this == o) { | |||||
return true; | |||||
} | |||||
if (o == null || getClass() != o.getClass()) { | |||||
return false; | |||||
} | |||||
RulesDefinition.Param that = (RulesDefinition.Param) o; | |||||
return key.equals(that.key()); | |||||
} | |||||
@Override | |||||
public int hashCode() { | |||||
return key.hashCode(); | |||||
} | |||||
} |
/* | |||||
* SonarQube | |||||
* Copyright (C) 2009-2019 SonarSource SA | |||||
* mailto:info AT sonarsource DOT com | |||||
* | |||||
* This program 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. | |||||
* | |||||
* This program 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.server.rule; | |||||
import java.util.ArrayList; | |||||
import java.util.HashMap; | |||||
import java.util.List; | |||||
import java.util.Map; | |||||
import javax.annotation.CheckForNull; | |||||
import javax.annotation.Nullable; | |||||
import javax.annotation.concurrent.Immutable; | |||||
import org.apache.commons.lang.StringUtils; | |||||
import org.sonar.api.server.rule.RulesDefinition; | |||||
import org.sonar.api.utils.log.Loggers; | |||||
import static java.lang.String.format; | |||||
import static java.util.Collections.unmodifiableList; | |||||
import static java.util.Collections.unmodifiableMap; | |||||
@Immutable | |||||
class DefaultRepository implements RulesDefinition.Repository { | |||||
private final String key; | |||||
private final String language; | |||||
private final String name; | |||||
private final boolean isExternal; | |||||
private final Map<String, RulesDefinition.Rule> rulesByKey; | |||||
DefaultRepository(DefaultNewRepository newRepository, @Nullable RulesDefinition.Repository mergeInto) { | |||||
this.key = newRepository.key(); | |||||
this.language = newRepository.language(); | |||||
this.isExternal = newRepository.isExternal(); | |||||
Map<String, RulesDefinition.Rule> ruleBuilder = new HashMap<>(); | |||||
if (mergeInto != null) { | |||||
if (!StringUtils.equals(newRepository.language(), mergeInto.language()) || !StringUtils.equals(newRepository.key(), mergeInto.key())) { | |||||
throw new IllegalArgumentException(format("Bug - language and key of the repositories to be merged should be the sames: %s and %s", newRepository, mergeInto)); | |||||
} | |||||
this.name = StringUtils.defaultIfBlank(mergeInto.name(), newRepository.name()); | |||||
for (RulesDefinition.Rule rule : mergeInto.rules()) { | |||||
if (!newRepository.key().startsWith("common-") && ruleBuilder.containsKey(rule.key())) { | |||||
Loggers.get(getClass()).warn("The rule '{}' of repository '{}' is declared several times", rule.key(), mergeInto.key()); | |||||
} | |||||
ruleBuilder.put(rule.key(), rule); | |||||
} | |||||
} else { | |||||
this.name = newRepository.name(); | |||||
} | |||||
for (RulesDefinition.NewRule newRule : newRepository.newRules().values()) { | |||||
DefaultNewRule defaultNewRule = (DefaultNewRule) newRule; | |||||
defaultNewRule.validate(); | |||||
ruleBuilder.put(newRule.key(), new DefaultRule(this, defaultNewRule)); | |||||
} | |||||
this.rulesByKey = unmodifiableMap(ruleBuilder); | |||||
} | |||||
@Override | |||||
public String key() { | |||||
return key; | |||||
} | |||||
@Override | |||||
public String language() { | |||||
return language; | |||||
} | |||||
@Override | |||||
public String name() { | |||||
return name; | |||||
} | |||||
@Override | |||||
public boolean isExternal() { | |||||
return isExternal; | |||||
} | |||||
@Override | |||||
@CheckForNull | |||||
public RulesDefinition.Rule rule(String ruleKey) { | |||||
return rulesByKey.get(ruleKey); | |||||
} | |||||
@Override | |||||
public List<RulesDefinition.Rule> rules() { | |||||
return unmodifiableList(new ArrayList<>(rulesByKey.values())); | |||||
} | |||||
@Override | |||||
public boolean equals(Object o) { | |||||
if (this == o) { | |||||
return true; | |||||
} | |||||
if (o == null || getClass() != o.getClass()) { | |||||
return false; | |||||
} | |||||
DefaultRepository that = (DefaultRepository) o; | |||||
return key.equals(that.key); | |||||
} | |||||
@Override | |||||
public int hashCode() { | |||||
return key.hashCode(); | |||||
} | |||||
@Override | |||||
public String toString() { | |||||
StringBuilder sb = new StringBuilder("Repository{"); | |||||
sb.append("key='").append(key).append('\''); | |||||
sb.append(", language='").append(language).append('\''); | |||||
sb.append('}'); | |||||
return sb.toString(); | |||||
} | |||||
} |
/* | |||||
* SonarQube | |||||
* Copyright (C) 2009-2019 SonarSource SA | |||||
* mailto:info AT sonarsource DOT com | |||||
* | |||||
* This program 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. | |||||
* | |||||
* This program 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.server.rule; | |||||
import java.util.ArrayList; | |||||
import java.util.Collections; | |||||
import java.util.HashMap; | |||||
import java.util.List; | |||||
import java.util.Map; | |||||
import java.util.Set; | |||||
import java.util.TreeSet; | |||||
import javax.annotation.CheckForNull; | |||||
import javax.annotation.concurrent.Immutable; | |||||
import org.sonar.api.rule.RuleKey; | |||||
import org.sonar.api.rule.RuleScope; | |||||
import org.sonar.api.rule.RuleStatus; | |||||
import org.sonar.api.rules.RuleType; | |||||
import org.sonar.api.server.debt.DebtRemediationFunction; | |||||
import org.sonar.api.server.rule.RuleTagsToTypeConverter; | |||||
import org.sonar.api.server.rule.RulesDefinition; | |||||
import static java.lang.String.format; | |||||
import static java.util.Collections.unmodifiableList; | |||||
@Immutable | |||||
public class DefaultRule implements RulesDefinition.Rule { | |||||
private final String pluginKey; | |||||
private final RulesDefinition.Repository repository; | |||||
private final String repoKey; | |||||
private final String key; | |||||
private final String name; | |||||
private final RuleType type; | |||||
private final String htmlDescription; | |||||
private final String markdownDescription; | |||||
private final String internalKey; | |||||
private final String severity; | |||||
private final boolean template; | |||||
private final DebtRemediationFunction debtRemediationFunction; | |||||
private final String gapDescription; | |||||
private final Set<String> tags; | |||||
private final Set<String> securityStandards; | |||||
private final Map<String, RulesDefinition.Param> params; | |||||
private final RuleStatus status; | |||||
private final boolean activatedByDefault; | |||||
private final RuleScope scope; | |||||
private final Set<RuleKey> deprecatedRuleKeys; | |||||
DefaultRule(DefaultRepository repository, DefaultNewRule newRule) { | |||||
this.pluginKey = newRule.pluginKey(); | |||||
this.repository = repository; | |||||
this.repoKey = newRule.repoKey(); | |||||
this.key = newRule.key(); | |||||
this.name = newRule.name(); | |||||
this.htmlDescription = newRule.htmlDescription(); | |||||
this.markdownDescription = newRule.markdownDescription(); | |||||
this.internalKey = newRule.internalKey(); | |||||
this.severity = newRule.severity(); | |||||
this.template = newRule.template(); | |||||
this.status = newRule.status(); | |||||
this.debtRemediationFunction = newRule.debtRemediationFunction(); | |||||
this.gapDescription = newRule.gapDescription(); | |||||
this.scope = newRule.scope() == null ? RuleScope.MAIN : newRule.scope(); | |||||
this.type = newRule.type() == null ? RuleTagsToTypeConverter.convert(newRule.tags()) : newRule.type(); | |||||
Set<String> tagsBuilder = new TreeSet<>(newRule.tags()); | |||||
tagsBuilder.removeAll(RuleTagsToTypeConverter.RESERVED_TAGS); | |||||
this.tags = Collections.unmodifiableSet(tagsBuilder); | |||||
this.securityStandards = Collections.unmodifiableSet(new TreeSet<>(newRule.securityStandards())); | |||||
Map<String, RulesDefinition.Param> paramsBuilder = new HashMap<>(); | |||||
for (RulesDefinition.NewParam newParam : newRule.paramsByKey().values()) { | |||||
paramsBuilder.put(newParam.key(), new DefaultParam((DefaultNewParam) newParam)); | |||||
} | |||||
this.params = Collections.unmodifiableMap(paramsBuilder); | |||||
this.activatedByDefault = newRule.activatedByDefault(); | |||||
this.deprecatedRuleKeys = Collections.unmodifiableSet(new TreeSet<>(newRule.deprecatedRuleKeys())); | |||||
} | |||||
public RulesDefinition.Repository repository() { | |||||
return repository; | |||||
} | |||||
@Override | |||||
@CheckForNull | |||||
public String pluginKey() { | |||||
return pluginKey; | |||||
} | |||||
@Override | |||||
public String key() { | |||||
return key; | |||||
} | |||||
@Override | |||||
public String name() { | |||||
return name; | |||||
} | |||||
@Override | |||||
public RuleScope scope() { | |||||
return scope; | |||||
} | |||||
@Override | |||||
public RuleType type() { | |||||
return type; | |||||
} | |||||
@Override | |||||
public String severity() { | |||||
return severity; | |||||
} | |||||
@Override | |||||
@CheckForNull | |||||
public String htmlDescription() { | |||||
return htmlDescription; | |||||
} | |||||
@Override | |||||
@CheckForNull | |||||
public String markdownDescription() { | |||||
return markdownDescription; | |||||
} | |||||
@Override | |||||
public boolean template() { | |||||
return template; | |||||
} | |||||
@Override | |||||
public boolean activatedByDefault() { | |||||
return activatedByDefault; | |||||
} | |||||
@Override | |||||
public RuleStatus status() { | |||||
return status; | |||||
} | |||||
@CheckForNull | |||||
@Deprecated | |||||
@Override | |||||
public String debtSubCharacteristic() { | |||||
return null; | |||||
} | |||||
@CheckForNull | |||||
@Override | |||||
public DebtRemediationFunction debtRemediationFunction() { | |||||
return debtRemediationFunction; | |||||
} | |||||
@Deprecated | |||||
@CheckForNull | |||||
@Override | |||||
public String effortToFixDescription() { | |||||
return gapDescription(); | |||||
} | |||||
@CheckForNull | |||||
@Override | |||||
public String gapDescription() { | |||||
return gapDescription; | |||||
} | |||||
@CheckForNull | |||||
@Override | |||||
public RulesDefinition.Param param(String key) { | |||||
return params.get(key); | |||||
} | |||||
@Override | |||||
public List<RulesDefinition.Param> params() { | |||||
return unmodifiableList(new ArrayList<>(params.values())); | |||||
} | |||||
@Override | |||||
public Set<String> tags() { | |||||
return tags; | |||||
} | |||||
@Override | |||||
public Set<String> securityStandards() { | |||||
return securityStandards; | |||||
} | |||||
@Override | |||||
public Set<RuleKey> deprecatedRuleKeys() { | |||||
return deprecatedRuleKeys; | |||||
} | |||||
@CheckForNull | |||||
@Override | |||||
public String internalKey() { | |||||
return internalKey; | |||||
} | |||||
@Override | |||||
public boolean equals(Object o) { | |||||
if (this == o) { | |||||
return true; | |||||
} | |||||
if (o == null || getClass() != o.getClass()) { | |||||
return false; | |||||
} | |||||
DefaultRule other = (DefaultRule) 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 format("[repository=%s, key=%s]", repoKey, key); | |||||
} | |||||
} | |||||
/* | |||||
* SonarQube | |||||
* Copyright (C) 2009-2019 SonarSource SA | |||||
* mailto:info AT sonarsource DOT com | |||||
* | |||||
* This program 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. | |||||
* | |||||
* This program 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.server.rule; | |||||
import java.util.ArrayList; | |||||
import java.util.HashMap; | |||||
import java.util.List; | |||||
import java.util.Map; | |||||
import javax.annotation.CheckForNull; | |||||
import javax.annotation.Nullable; | |||||
import org.sonar.api.rule.RuleKey; | |||||
import org.sonar.api.server.rule.RulesDefinition; | |||||
import static java.util.Collections.emptyList; | |||||
import static java.util.Collections.unmodifiableList; | |||||
import static org.sonar.api.utils.Preconditions.checkState; | |||||
public class RuleDefinitionContext implements RulesDefinition.Context { | |||||
private final Map<String, RulesDefinition.Repository> repositoriesByKey = new HashMap<>(); | |||||
private String currentPluginKey; | |||||
@Override | |||||
public RulesDefinition.NewRepository createRepository(String key, String language) { | |||||
return new DefaultNewRepository(this, key, language, false); | |||||
} | |||||
@Override | |||||
public RulesDefinition.NewRepository createExternalRepository(String engineId, String language) { | |||||
return new DefaultNewRepository(this, RuleKey.EXTERNAL_RULE_REPO_PREFIX + engineId, language, true); | |||||
} | |||||
@Override | |||||
@Deprecated | |||||
public RulesDefinition.NewRepository extendRepository(String key, String language) { | |||||
return createRepository(key, language); | |||||
} | |||||
@Override | |||||
@CheckForNull | |||||
public RulesDefinition.Repository repository(String key) { | |||||
return repositoriesByKey.get(key); | |||||
} | |||||
@Override | |||||
public List<RulesDefinition.Repository> repositories() { | |||||
return unmodifiableList(new ArrayList<>(repositoriesByKey.values())); | |||||
} | |||||
@Override | |||||
@Deprecated | |||||
public List<RulesDefinition.ExtendedRepository> extendedRepositories(String repositoryKey) { | |||||
return emptyList(); | |||||
} | |||||
@Override | |||||
@Deprecated | |||||
public List<RulesDefinition.ExtendedRepository> extendedRepositories() { | |||||
return emptyList(); | |||||
} | |||||
void registerRepository(DefaultNewRepository newRepository) { | |||||
RulesDefinition.Repository existing = repositoriesByKey.get(newRepository.key()); | |||||
if (existing != null) { | |||||
String existingLanguage = existing.language(); | |||||
checkState(existingLanguage.equals(newRepository.language()), | |||||
"The rule repository '%s' must not be defined for two different languages: %s and %s", | |||||
newRepository.key(), existingLanguage, newRepository.language()); | |||||
} | |||||
repositoriesByKey.put(newRepository.key(), new DefaultRepository(newRepository, existing)); | |||||
} | |||||
public String currentPluginKey() { | |||||
return currentPluginKey; | |||||
} | |||||
@Override | |||||
public void setCurrentPluginKey(@Nullable String pluginKey) { | |||||
this.currentPluginKey = pluginKey; | |||||
} | |||||
} |
} | } | ||||
public RulesDefinition.Context load() { | public RulesDefinition.Context load() { | ||||
RulesDefinition.Context context = new RulesDefinition.Context(); | |||||
RulesDefinition.Context context = new RuleDefinitionContext(); | |||||
for (RulesDefinition pluginDefinition : pluginDefs) { | for (RulesDefinition pluginDefinition : pluginDefs) { | ||||
context.setCurrentPluginKey(serverPluginRepository.getPluginKey(pluginDefinition)); | context.setCurrentPluginKey(serverPluginRepository.getPluginKey(pluginDefinition)); | ||||
pluginDefinition.define(context); | pluginDefinition.define(context); |
@Test | @Test | ||||
public void wrap_deprecated_rule_repositories() { | public void wrap_deprecated_rule_repositories() { | ||||
RulesDefinition.Context context = new RulesDefinition.Context(); | |||||
RulesDefinition.Context context = new RuleDefinitionContext(); | |||||
CheckstyleRules checkstyleRules = new CheckstyleRules(); | CheckstyleRules checkstyleRules = new CheckstyleRules(); | ||||
when(pluginRepository.getPluginKey(checkstyleRules)).thenReturn("unittest"); | when(pluginRepository.getPluginKey(checkstyleRules)).thenReturn("unittest"); | ||||
new DeprecatedRulesDefinitionLoader(i18n, debtModelRepository, importer, pluginRepository, new RuleRepository[] {checkstyleRules}).complete(context); | new DeprecatedRulesDefinitionLoader(i18n, debtModelRepository, importer, pluginRepository, new RuleRepository[] {checkstyleRules}).complete(context); | ||||
@Test | @Test | ||||
public void emulate_the_day_deprecated_api_can_be_dropped() { | public void emulate_the_day_deprecated_api_can_be_dropped() { | ||||
RulesDefinition.Context context = new RulesDefinition.Context(); | |||||
RulesDefinition.Context context = new RuleDefinitionContext(); | |||||
// no more RuleRepository ! | // no more RuleRepository ! | ||||
new DeprecatedRulesDefinitionLoader(i18n, debtModelRepository, importer, pluginRepository); | new DeprecatedRulesDefinitionLoader(i18n, debtModelRepository, importer, pluginRepository); | ||||
@Test | @Test | ||||
public void use_l10n_bundles() { | public void use_l10n_bundles() { | ||||
RulesDefinition.Context context = new RulesDefinition.Context(); | |||||
RulesDefinition.Context context = new RuleDefinitionContext(); | |||||
when(i18n.getName("checkstyle", "ConstantName")).thenReturn("Constant Name"); | when(i18n.getName("checkstyle", "ConstantName")).thenReturn("Constant Name"); | ||||
when(i18n.getDescription("checkstyle", "ConstantName")).thenReturn("Checks that constant names conform to the specified format"); | when(i18n.getDescription("checkstyle", "ConstantName")).thenReturn("Checks that constant names conform to the specified format"); | ||||
when(i18n.getParamDescription("checkstyle", "ConstantName", "format")).thenReturn("Regular expression"); | when(i18n.getParamDescription("checkstyle", "ConstantName", "format")).thenReturn("Regular expression"); | ||||
@Test | @Test | ||||
public void define_rule_debt() { | public void define_rule_debt() { | ||||
RulesDefinition.Context context = new RulesDefinition.Context(); | |||||
RulesDefinition.Context context = new RuleDefinitionContext(); | |||||
List<DebtModelXMLExporter.RuleDebt> ruleDebts = newArrayList( | List<DebtModelXMLExporter.RuleDebt> ruleDebts = newArrayList( | ||||
new DebtModelXMLExporter.RuleDebt() | new DebtModelXMLExporter.RuleDebt() | ||||
@Test | @Test | ||||
public void fail_on_invalid_rule_debt() { | public void fail_on_invalid_rule_debt() { | ||||
RulesDefinition.Context context = new RulesDefinition.Context(); | |||||
RulesDefinition.Context context = new RuleDefinitionContext(); | |||||
List<DebtModelXMLExporter.RuleDebt> ruleDebts = newArrayList( | List<DebtModelXMLExporter.RuleDebt> ruleDebts = newArrayList( | ||||
new DebtModelXMLExporter.RuleDebt() | new DebtModelXMLExporter.RuleDebt() |
testCompile 'org.assertj:assertj-core' | testCompile 'org.assertj:assertj-core' | ||||
testCompile 'org.mockito:mockito-core' | testCompile 'org.mockito:mockito-core' | ||||
testCompile project(':sonar-scanner-engine') | testCompile project(':sonar-scanner-engine') | ||||
testCompile project(':server:sonar-server') | |||||
} | } | ||||
sourceSets { | sourceSets { |
* @see org.sonar.api.server.rule.RulesDefinition.NewRule#setType(RuleType) | * @see org.sonar.api.server.rule.RulesDefinition.NewRule#setType(RuleType) | ||||
* @since 5.5 | * @since 5.5 | ||||
*/ | */ | ||||
class RuleTagsToTypeConverter { | |||||
public class RuleTagsToTypeConverter { | |||||
public static final String TAG_BUG = "bug"; | public static final String TAG_BUG = "bug"; | ||||
public static final String TAG_SECURITY = "security"; | public static final String TAG_SECURITY = "security"; | ||||
static final Set<String> RESERVED_TAGS = unmodifiableSet(new HashSet<>(asList(TAG_BUG, TAG_SECURITY))); | |||||
public static final Set<String> RESERVED_TAGS = unmodifiableSet(new HashSet<>(asList(TAG_BUG, TAG_SECURITY))); | |||||
private RuleTagsToTypeConverter() { | private RuleTagsToTypeConverter() { | ||||
// only statics | // only statics | ||||
} | } | ||||
static RuleType convert(Collection<String> tags) { | |||||
public static RuleType convert(Collection<String> tags) { | |||||
if (tags.contains(TAG_BUG)) { | if (tags.contains(TAG_BUG)) { | ||||
return RuleType.BUG; | return RuleType.BUG; | ||||
} | } |
*/ | */ | ||||
package org.sonar.api.server.rule; | package org.sonar.api.server.rule; | ||||
import java.io.IOException; | |||||
import java.net.URL; | import java.net.URL; | ||||
import java.util.ArrayList; | |||||
import java.util.Collection; | import java.util.Collection; | ||||
import java.util.Collections; | |||||
import java.util.HashMap; | |||||
import java.util.List; | import java.util.List; | ||||
import java.util.Locale; | |||||
import java.util.Map; | |||||
import java.util.Set; | import java.util.Set; | ||||
import java.util.TreeSet; | |||||
import javax.annotation.CheckForNull; | import javax.annotation.CheckForNull; | ||||
import javax.annotation.Nullable; | import javax.annotation.Nullable; | ||||
import javax.annotation.concurrent.Immutable; | import javax.annotation.concurrent.Immutable; | ||||
import org.apache.commons.io.IOUtils; | |||||
import org.apache.commons.lang.StringUtils; | |||||
import org.sonar.api.ExtensionPoint; | import org.sonar.api.ExtensionPoint; | ||||
import org.sonar.api.ce.ComputeEngineSide; | import org.sonar.api.ce.ComputeEngineSide; | ||||
import org.sonar.api.rule.RuleKey; | import org.sonar.api.rule.RuleKey; | ||||
import org.sonar.api.rule.RuleScope; | import org.sonar.api.rule.RuleScope; | ||||
import org.sonar.api.rule.RuleStatus; | import org.sonar.api.rule.RuleStatus; | ||||
import org.sonar.api.rule.Severity; | |||||
import org.sonar.api.rules.RuleType; | import org.sonar.api.rules.RuleType; | ||||
import org.sonar.api.server.ServerSide; | import org.sonar.api.server.ServerSide; | ||||
import org.sonar.api.server.debt.DebtRemediationFunction; | import org.sonar.api.server.debt.DebtRemediationFunction; | ||||
import org.sonar.api.utils.log.Loggers; | |||||
import org.sonarsource.api.sonarlint.SonarLintSide; | import org.sonarsource.api.sonarlint.SonarLintSide; | ||||
import static java.lang.String.format; | |||||
import static java.nio.charset.StandardCharsets.UTF_8; | |||||
import static java.util.Collections.emptyList; | |||||
import static java.util.Collections.unmodifiableList; | |||||
import static java.util.Collections.unmodifiableMap; | |||||
import static org.apache.commons.lang.StringUtils.defaultIfEmpty; | |||||
import static org.apache.commons.lang.StringUtils.isEmpty; | |||||
import static org.apache.commons.lang.StringUtils.trimToNull; | |||||
import static org.sonar.api.utils.Preconditions.checkArgument; | |||||
import static org.sonar.api.utils.Preconditions.checkState; | |||||
/** | /** | ||||
* Defines some coding rules of the same repository. For example the Java Findbugs plugin provides an implementation of | * Defines some coding rules of the same repository. For example the Java Findbugs plugin provides an implementation of | ||||
* this extension point in order to define the rules that it supports. | * this extension point in order to define the rules that it supports. | ||||
/** | /** | ||||
* Instantiated by core but not by plugins, except for their tests. | * Instantiated by core but not by plugins, except for their tests. | ||||
*/ | */ | ||||
class Context { | |||||
private final Map<String, Repository> repositoriesByKey = new HashMap<>(); | |||||
private String currentPluginKey; | |||||
/** | |||||
interface Context { | |||||
/* | |||||
* New builder for {@link org.sonar.api.server.rule.RulesDefinition.Repository}. | * New builder for {@link org.sonar.api.server.rule.RulesDefinition.Repository}. | ||||
* <br> | * <br> | ||||
* A plugin can add rules to a repository that is defined then executed by another plugin. For instance | * A plugin can add rules to a repository that is defined then executed by another plugin. For instance | ||||
* the FbContrib plugin contributes to the Findbugs plugin rules. In this case no need | * the FbContrib plugin contributes to the Findbugs plugin rules. In this case no need | ||||
* to execute {@link org.sonar.api.server.rule.RulesDefinition.NewRepository#setName(String)} | * to execute {@link org.sonar.api.server.rule.RulesDefinition.NewRepository#setName(String)} | ||||
*/ | */ | ||||
public NewRepository createRepository(String key, String language) { | |||||
return new NewRepositoryImpl(this, key, language, false); | |||||
} | |||||
NewRepository createRepository(String key, String language); | |||||
/** | /** | ||||
* Creates a repository of rules from external rule engines. | * Creates a repository of rules from external rule engines. | ||||
* | * | ||||
* @since 7.2 | * @since 7.2 | ||||
*/ | */ | ||||
public NewRepository createExternalRepository(String engineId, String language) { | |||||
return new NewRepositoryImpl(this, RuleKey.EXTERNAL_RULE_REPO_PREFIX + engineId, language, true); | |||||
} | |||||
NewRepository createExternalRepository(String engineId, String language); | |||||
/** | /** | ||||
* @deprecated since 5.2. Simply use {@link #createRepository(String, String)} | * @deprecated since 5.2. Simply use {@link #createRepository(String, String)} | ||||
*/ | */ | ||||
@Deprecated | @Deprecated | ||||
public NewRepository extendRepository(String key, String language) { | |||||
return createRepository(key, language); | |||||
} | |||||
NewRepository extendRepository(String key, String language); | |||||
@CheckForNull | @CheckForNull | ||||
public Repository repository(String key) { | |||||
return repositoriesByKey.get(key); | |||||
} | |||||
Repository repository(String key); | |||||
public List<Repository> repositories() { | |||||
return unmodifiableList(new ArrayList<>(repositoriesByKey.values())); | |||||
} | |||||
List<Repository> repositories(); | |||||
/** | /** | ||||
* @deprecated returns empty list since 5.2. Concept of "extended repository" was misleading and not valuable. Simply declare | * @deprecated returns empty list since 5.2. Concept of "extended repository" was misleading and not valuable. Simply declare | ||||
* repositories and use {@link #repositories()}. See http://jira.sonarsource.com/browse/SONAR-6709 | * repositories and use {@link #repositories()}. See http://jira.sonarsource.com/browse/SONAR-6709 | ||||
*/ | */ | ||||
@Deprecated | @Deprecated | ||||
public List<ExtendedRepository> extendedRepositories(String repositoryKey) { | |||||
return emptyList(); | |||||
} | |||||
List<ExtendedRepository> extendedRepositories(String repositoryKey); | |||||
/** | /** | ||||
* @deprecated returns empty list since 5.2. Concept of "extended repository" was misleading and not valuable. Simply declare | * @deprecated returns empty list since 5.2. Concept of "extended repository" was misleading and not valuable. Simply declare | ||||
* repositories and use {@link #repositories()}. See http://jira.sonarsource.com/browse/SONAR-6709 | * repositories and use {@link #repositories()}. See http://jira.sonarsource.com/browse/SONAR-6709 | ||||
*/ | */ | ||||
@Deprecated | @Deprecated | ||||
public List<ExtendedRepository> extendedRepositories() { | |||||
return emptyList(); | |||||
} | |||||
List<ExtendedRepository> extendedRepositories(); | |||||
private void registerRepository(NewRepositoryImpl newRepository) { | |||||
Repository existing = repositoriesByKey.get(newRepository.key()); | |||||
if (existing != null) { | |||||
String existingLanguage = existing.language(); | |||||
checkState(existingLanguage.equals(newRepository.language), | |||||
"The rule repository '%s' must not be defined for two different languages: %s and %s", | |||||
newRepository.key, existingLanguage, newRepository.language); | |||||
} | |||||
repositoriesByKey.put(newRepository.key, new RepositoryImpl(newRepository, existing)); | |||||
} | |||||
public void setCurrentPluginKey(@Nullable String pluginKey) { | |||||
this.currentPluginKey = pluginKey; | |||||
} | |||||
void setCurrentPluginKey(@Nullable String pluginKey); | |||||
} | } | ||||
interface NewExtendedRepository { | interface NewExtendedRepository { | ||||
A1, A2, A3, A4, A5, A6, A7, A8, A9, A10; | A1, A2, A3, A4, A5, A6, A7, A8, A9, A10; | ||||
} | } | ||||
class NewRepositoryImpl implements NewRepository { | |||||
private final Context context; | |||||
private final String key; | |||||
private final boolean isExternal; | |||||
private String language; | |||||
private String name; | |||||
private final Map<String, NewRule> newRules = new HashMap<>(); | |||||
private NewRepositoryImpl(Context context, String key, String language, boolean isExternal) { | |||||
this.context = context; | |||||
this.key = key; | |||||
this.name = key; | |||||
this.language = language; | |||||
this.isExternal = isExternal; | |||||
} | |||||
@Override | |||||
public boolean isExternal() { | |||||
return isExternal; | |||||
} | |||||
@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) { | |||||
checkArgument(!newRules.containsKey(ruleKey), "The rule '%s' of repository '%s' is declared several times", ruleKey, key); | |||||
NewRule newRule = new NewRule(context.currentPluginKey, key, ruleKey); | |||||
newRules.put(ruleKey, newRule); | |||||
return newRule; | |||||
} | |||||
@CheckForNull | |||||
@Override | |||||
public NewRule rule(String ruleKey) { | |||||
return newRules.get(ruleKey); | |||||
} | |||||
@Override | |||||
public Collection<NewRule> 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 | |||||
context.registerRepository(this); | |||||
} | |||||
@Override | |||||
public String toString() { | |||||
StringBuilder sb = new StringBuilder("NewRepository{"); | |||||
sb.append("key='").append(key).append('\''); | |||||
sb.append(", language='").append(language).append('\''); | |||||
sb.append('}'); | |||||
return sb.toString(); | |||||
} | |||||
} | |||||
interface ExtendedRepository { | interface ExtendedRepository { | ||||
String key(); | String key(); | ||||
boolean isExternal(); | boolean isExternal(); | ||||
} | } | ||||
@Immutable | |||||
class RepositoryImpl implements Repository { | |||||
private final String key; | |||||
private final String language; | |||||
private final String name; | |||||
private final boolean isExternal; | |||||
private final Map<String, Rule> rulesByKey; | |||||
private RepositoryImpl(NewRepositoryImpl newRepository, @Nullable Repository mergeInto) { | |||||
this.key = newRepository.key; | |||||
this.language = newRepository.language; | |||||
this.isExternal = newRepository.isExternal; | |||||
Map<String, Rule> ruleBuilder = new HashMap<>(); | |||||
if (mergeInto != null) { | |||||
if (!StringUtils.equals(newRepository.language, mergeInto.language()) || !StringUtils.equals(newRepository.key, mergeInto.key())) { | |||||
throw new IllegalArgumentException(format("Bug - language and key of the repositories to be merged should be the sames: %s and %s", newRepository, mergeInto)); | |||||
} | |||||
this.name = StringUtils.defaultIfBlank(mergeInto.name(), newRepository.name); | |||||
for (Rule rule : mergeInto.rules()) { | |||||
if (!newRepository.key().startsWith("common-") && ruleBuilder.containsKey(rule.key())) { | |||||
Loggers.get(getClass()).warn("The rule '{}' of repository '{}' is declared several times", rule.key(), mergeInto.key()); | |||||
} | |||||
ruleBuilder.put(rule.key(), rule); | |||||
} | |||||
} else { | |||||
this.name = newRepository.name; | |||||
} | |||||
for (NewRule newRule : newRepository.newRules.values()) { | |||||
newRule.validate(); | |||||
ruleBuilder.put(newRule.key, new Rule(this, newRule)); | |||||
} | |||||
this.rulesByKey = unmodifiableMap(ruleBuilder); | |||||
} | |||||
@Override | |||||
public String key() { | |||||
return key; | |||||
} | |||||
@Override | |||||
public String language() { | |||||
return language; | |||||
} | |||||
@Override | |||||
public String name() { | |||||
return name; | |||||
} | |||||
@Override | |||||
public boolean isExternal() { | |||||
return isExternal; | |||||
} | |||||
@Override | |||||
@CheckForNull | |||||
public Rule rule(String ruleKey) { | |||||
return rulesByKey.get(ruleKey); | |||||
} | |||||
@Override | |||||
public List<Rule> rules() { | |||||
return unmodifiableList(new ArrayList<>(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(); | |||||
} | |||||
@Override | |||||
public String toString() { | |||||
StringBuilder sb = new StringBuilder("Repository{"); | |||||
sb.append("key='").append(key).append('\''); | |||||
sb.append(", language='").append(language).append('\''); | |||||
sb.append('}'); | |||||
return sb.toString(); | |||||
} | |||||
} | |||||
/** | /** | ||||
* Factory of {@link org.sonar.api.server.debt.DebtRemediationFunction}. | * Factory of {@link org.sonar.api.server.debt.DebtRemediationFunction}. | ||||
*/ | */ | ||||
DebtRemediationFunction create(DebtRemediationFunction.Type type, @Nullable String gapMultiplier, @Nullable String baseEffort); | DebtRemediationFunction create(DebtRemediationFunction.Type type, @Nullable String gapMultiplier, @Nullable String baseEffort); | ||||
} | } | ||||
class NewRule { | |||||
private final String pluginKey; | |||||
private final String repoKey; | |||||
private final String key; | |||||
private RuleType type; | |||||
private String name; | |||||
private String htmlDescription; | |||||
private String markdownDescription; | |||||
private String internalKey; | |||||
private String severity = Severity.MAJOR; | |||||
private boolean template; | |||||
private RuleStatus status = RuleStatus.defaultStatus(); | |||||
private DebtRemediationFunction debtRemediationFunction; | |||||
private String gapDescription; | |||||
private final Set<String> tags = new TreeSet<>(); | |||||
private final Set<String> securityStandards = new TreeSet<>(); | |||||
private final Map<String, NewParam> paramsByKey = new HashMap<>(); | |||||
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; | |||||
this.repoKey = repoKey; | |||||
this.key = key; | |||||
this.functions = new DefaultDebtRemediationFunctions(repoKey, key); | |||||
} | |||||
interface NewRule { | |||||
public String key() { | |||||
return this.key; | |||||
} | |||||
String key(); | |||||
/** | /** | ||||
* @since 7.1 | * @since 7.1 | ||||
*/ | */ | ||||
@CheckForNull | @CheckForNull | ||||
public RuleScope scope() { | |||||
return this.scope; | |||||
} | |||||
RuleScope scope(); | |||||
/** | /** | ||||
* @since 7.1 | * @since 7.1 | ||||
*/ | */ | ||||
public NewRule setScope(RuleScope scope) { | |||||
this.scope = scope; | |||||
return this; | |||||
} | |||||
NewRule setScope(RuleScope scope); | |||||
/** | /** | ||||
* Required rule name | * Required rule name | ||||
*/ | */ | ||||
public NewRule setName(String s) { | |||||
this.name = trimToNull(s); | |||||
return this; | |||||
} | |||||
NewRule setName(String s); | |||||
public NewRule setTemplate(boolean template) { | |||||
this.template = template; | |||||
return this; | |||||
} | |||||
NewRule setTemplate(boolean template); | |||||
/** | /** | ||||
* Should this rule be enabled by default. For example in SonarLint standalone. | * Should this rule be enabled by default. For example in SonarLint standalone. | ||||
* | * | ||||
* @since 6.0 | * @since 6.0 | ||||
*/ | */ | ||||
public NewRule setActivatedByDefault(boolean activatedByDefault) { | |||||
this.activatedByDefault = activatedByDefault; | |||||
return this; | |||||
} | |||||
NewRule setActivatedByDefault(boolean activatedByDefault); | |||||
public NewRule setSeverity(String s) { | |||||
checkArgument(Severity.ALL.contains(s), "Severity of rule %s is not correct: %s", this, s); | |||||
this.severity = s; | |||||
return this; | |||||
} | |||||
NewRule setSeverity(String s); | |||||
/** | /** | ||||
* The type as defined by the SonarQube Quality Model. | * The type as defined by the SonarQube Quality Model. | ||||
* | * | ||||
* @since 5.5 | * @since 5.5 | ||||
*/ | */ | ||||
public NewRule setType(RuleType t) { | |||||
this.type = t; | |||||
return this; | |||||
} | |||||
NewRule setType(RuleType t); | |||||
/** | /** | ||||
* The optional description, in HTML format, has no max length. It's exclusive with markdown description | * The optional description, in HTML format, has no max length. It's exclusive with markdown description | ||||
* (see {@link #setMarkdownDescription(String)}) | * (see {@link #setMarkdownDescription(String)}) | ||||
*/ | */ | ||||
public NewRule setHtmlDescription(@Nullable String s) { | |||||
checkState(markdownDescription == null, "Rule '%s' already has a Markdown description", this); | |||||
this.htmlDescription = trimToNull(s); | |||||
return this; | |||||
} | |||||
NewRule setHtmlDescription(@Nullable String s); | |||||
/** | /** | ||||
* Load description from a file available in classpath. Example : <code>setHtmlDescription(getClass().getResource("/myrepo/Rule1234.html")</code> | * Load description from a file available in classpath. Example : <code>setHtmlDescription(getClass().getResource("/myrepo/Rule1234.html")</code> | ||||
*/ | */ | ||||
public NewRule setHtmlDescription(@Nullable URL classpathUrl) { | |||||
if (classpathUrl != null) { | |||||
try { | |||||
setHtmlDescription(IOUtils.toString(classpathUrl, UTF_8)); | |||||
} catch (IOException e) { | |||||
throw new IllegalStateException("Fail to read: " + classpathUrl, e); | |||||
} | |||||
} else { | |||||
this.htmlDescription = null; | |||||
} | |||||
return this; | |||||
} | |||||
NewRule setHtmlDescription(@Nullable URL classpathUrl); | |||||
/** | /** | ||||
* The optional description, in a restricted Markdown format, has no max length. It's exclusive with HTML description | * The optional description, in a restricted Markdown format, has no max length. It's exclusive with HTML description | ||||
* (see {@link #setHtmlDescription(String)}) | * (see {@link #setHtmlDescription(String)}) | ||||
*/ | */ | ||||
public NewRule setMarkdownDescription(@Nullable String s) { | |||||
checkState(htmlDescription == null, "Rule '%s' already has an HTML description", this); | |||||
this.markdownDescription = trimToNull(s); | |||||
return this; | |||||
} | |||||
NewRule setMarkdownDescription(@Nullable String s); | |||||
/** | /** | ||||
* Load description from a file available in classpath. Example : {@code setMarkdownDescription(getClass().getResource("/myrepo/Rule1234.md")} | * Load description from a file available in classpath. Example : {@code setMarkdownDescription(getClass().getResource("/myrepo/Rule1234.md")} | ||||
*/ | */ | ||||
public NewRule setMarkdownDescription(@Nullable URL classpathUrl) { | |||||
if (classpathUrl != null) { | |||||
try { | |||||
setMarkdownDescription(IOUtils.toString(classpathUrl, UTF_8)); | |||||
} catch (IOException e) { | |||||
throw new IllegalStateException("Fail to read: " + classpathUrl, e); | |||||
} | |||||
} else { | |||||
this.markdownDescription = null; | |||||
} | |||||
return this; | |||||
} | |||||
NewRule setMarkdownDescription(@Nullable URL classpathUrl); | |||||
/** | /** | ||||
* Default value is {@link org.sonar.api.rule.RuleStatus#READY}. The value | * 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 org.sonar.api.rule.RuleStatus#REMOVED} is not accepted and raises an | ||||
* {@link java.lang.IllegalArgumentException}. | * {@link java.lang.IllegalArgumentException}. | ||||
*/ | */ | ||||
public NewRule setStatus(RuleStatus status) { | |||||
checkArgument(RuleStatus.REMOVED != status, "Status 'REMOVED' is not accepted on rule '%s'", this); | |||||
this.status = status; | |||||
return this; | |||||
} | |||||
NewRule setStatus(RuleStatus status); | |||||
/** | /** | ||||
* SQALE sub-characteristic. See http://www.sqale.org | * SQALE sub-characteristic. See http://www.sqale.org | ||||
* @deprecated in 5.5. SQALE Quality Model is replaced by SonarQube Quality Model. This method does nothing. | * @deprecated in 5.5. SQALE Quality Model is replaced by SonarQube Quality Model. This method does nothing. | ||||
* See https://jira.sonarsource.com/browse/MMF-184 | * See https://jira.sonarsource.com/browse/MMF-184 | ||||
*/ | */ | ||||
public NewRule setDebtSubCharacteristic(@Nullable String s) { | |||||
return this; | |||||
} | |||||
NewRule setDebtSubCharacteristic(@Nullable String s); | |||||
/** | /** | ||||
* Factory of {@link org.sonar.api.server.debt.DebtRemediationFunction} | * Factory of {@link org.sonar.api.server.debt.DebtRemediationFunction} | ||||
*/ | */ | ||||
public DebtRemediationFunctions debtRemediationFunctions() { | |||||
return functions; | |||||
} | |||||
DebtRemediationFunctions debtRemediationFunctions(); | |||||
/** | /** | ||||
* @see #debtRemediationFunctions() | * @see #debtRemediationFunctions() | ||||
*/ | */ | ||||
public NewRule setDebtRemediationFunction(@Nullable DebtRemediationFunction fn) { | |||||
this.debtRemediationFunction = fn; | |||||
return this; | |||||
} | |||||
NewRule setDebtRemediationFunction(@Nullable DebtRemediationFunction fn); | |||||
/** | /** | ||||
* @deprecated since 5.5, replaced by {@link #setGapDescription(String)} | * @deprecated since 5.5, replaced by {@link #setGapDescription(String)} | ||||
*/ | */ | ||||
@Deprecated | @Deprecated | ||||
public NewRule setEffortToFixDescription(@Nullable String s) { | |||||
return setGapDescription(s); | |||||
} | |||||
NewRule setEffortToFixDescription(@Nullable String s); | |||||
/** | /** | ||||
* For rules that use LINEAR or LINEAR_OFFSET remediation functions, the meaning | * For rules that use LINEAR or LINEAR_OFFSET remediation functions, the meaning | ||||
* remediation function gap multiplier/base effort would be something like | * remediation function gap multiplier/base effort would be something like | ||||
* "Effort to test one uncovered condition". | * "Effort to test one uncovered condition". | ||||
*/ | */ | ||||
public NewRule setGapDescription(@Nullable String s) { | |||||
this.gapDescription = s; | |||||
return this; | |||||
} | |||||
NewRule setGapDescription(@Nullable String s); | |||||
/** | /** | ||||
* Create a parameter with given unique key. Max length of key is 128 characters. | * Create a parameter with given unique key. Max length of key is 128 characters. | ||||
*/ | */ | ||||
public NewParam createParam(String paramKey) { | |||||
checkArgument(!paramsByKey.containsKey(paramKey), "The parameter '%s' is declared several times on the rule %s", paramKey, this); | |||||
NewParam param = new NewParam(paramKey); | |||||
paramsByKey.put(paramKey, param); | |||||
return param; | |||||
} | |||||
NewParam createParam(String paramKey); | |||||
@CheckForNull | @CheckForNull | ||||
public NewParam param(String paramKey) { | |||||
return paramsByKey.get(paramKey); | |||||
} | |||||
NewParam param(String paramKey); | |||||
public Collection<NewParam> params() { | |||||
return paramsByKey.values(); | |||||
} | |||||
Collection<NewParam> params(); | |||||
/** | /** | ||||
* @see RuleTagFormat | * @see RuleTagFormat | ||||
*/ | */ | ||||
public NewRule addTags(String... list) { | |||||
for (String tag : list) { | |||||
RuleTagFormat.validate(tag); | |||||
tags.add(tag); | |||||
} | |||||
return this; | |||||
} | |||||
NewRule addTags(String... list); | |||||
/** | /** | ||||
* @see RuleTagFormat | * @see RuleTagFormat | ||||
*/ | */ | ||||
public NewRule setTags(String... list) { | |||||
tags.clear(); | |||||
addTags(list); | |||||
return this; | |||||
} | |||||
NewRule setTags(String... list); | |||||
/** | /** | ||||
* @since 7.3 | * @since 7.3 | ||||
*/ | */ | ||||
public NewRule addOwaspTop10(OwaspTop10... standards) { | |||||
for (OwaspTop10 owaspTop10 : standards) { | |||||
String standard = "owaspTop10:" + owaspTop10.name().toLowerCase(Locale.ENGLISH); | |||||
securityStandards.add(standard); | |||||
} | |||||
return this; | |||||
} | |||||
NewRule addOwaspTop10(OwaspTop10... standards); | |||||
/** | /** | ||||
* @since 7.3 | * @since 7.3 | ||||
*/ | */ | ||||
public NewRule addCwe(int... nums) { | |||||
for (int num : nums) { | |||||
String standard = "cwe:" + num; | |||||
securityStandards.add(standard); | |||||
} | |||||
return this; | |||||
} | |||||
NewRule addCwe(int... nums); | |||||
/** | /** | ||||
* Optional key that can be used by the rule engine. Not displayed | * Optional key that can be used by the rule engine. Not displayed | ||||
* in webapp. For example the Java Checkstyle plugin feeds this field | * in webapp. For example the Java Checkstyle plugin feeds this field | ||||
* with the internal path ("Checker/TreeWalker/AnnotationUseStyle"). | * with the internal path ("Checker/TreeWalker/AnnotationUseStyle"). | ||||
*/ | */ | ||||
public NewRule setInternalKey(@Nullable String s) { | |||||
this.internalKey = s; | |||||
return this; | |||||
} | |||||
private void validate() { | |||||
if (isEmpty(name)) { | |||||
throw new IllegalStateException(format("Name of rule %s is empty", this)); | |||||
} | |||||
if (isEmpty(htmlDescription) && isEmpty(markdownDescription)) { | |||||
throw new IllegalStateException(format("One of HTML description or Markdown description must be defined for rule %s", this)); | |||||
} | |||||
} | |||||
NewRule setInternalKey(@Nullable String s); | |||||
/** | /** | ||||
* Register a repository and key under which this rule used to be known | * Register a repository and key under which this rule used to be known | ||||
* @see Rule#deprecatedRuleKeys | * @see Rule#deprecatedRuleKeys | ||||
* @since 7.1 | * @since 7.1 | ||||
*/ | */ | ||||
public NewRule addDeprecatedRuleKey(String repository, String key) { | |||||
deprecatedRuleKeys.add(RuleKey.of(repository, key)); | |||||
return this; | |||||
} | |||||
NewRule addDeprecatedRuleKey(String repository, String key); | |||||
@Override | @Override | ||||
public String toString() { | |||||
return format("[repository=%s, key=%s]", repoKey, key); | |||||
} | |||||
String toString(); | |||||
} | } | ||||
@Immutable | @Immutable | ||||
class Rule { | |||||
private final String pluginKey; | |||||
private final Repository repository; | |||||
private final String repoKey; | |||||
private final String key; | |||||
private final String name; | |||||
private final RuleType type; | |||||
private final String htmlDescription; | |||||
private final String markdownDescription; | |||||
private final String internalKey; | |||||
private final String severity; | |||||
private final boolean template; | |||||
private final DebtRemediationFunction debtRemediationFunction; | |||||
private final String gapDescription; | |||||
private final Set<String> tags; | |||||
private final Set<String> securityStandards; | |||||
private final Map<String, Param> params; | |||||
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; | |||||
this.repository = repository; | |||||
this.repoKey = newRule.repoKey; | |||||
this.key = newRule.key; | |||||
this.name = newRule.name; | |||||
this.htmlDescription = newRule.htmlDescription; | |||||
this.markdownDescription = newRule.markdownDescription; | |||||
this.internalKey = newRule.internalKey; | |||||
this.severity = newRule.severity; | |||||
this.template = newRule.template; | |||||
this.status = newRule.status; | |||||
this.debtRemediationFunction = newRule.debtRemediationFunction; | |||||
this.gapDescription = newRule.gapDescription; | |||||
this.scope = newRule.scope == null ? RuleScope.MAIN : newRule.scope; | |||||
this.type = newRule.type == null ? RuleTagsToTypeConverter.convert(newRule.tags) : newRule.type; | |||||
Set<String> tagsBuilder = new TreeSet<>(newRule.tags); | |||||
tagsBuilder.removeAll(RuleTagsToTypeConverter.RESERVED_TAGS); | |||||
this.tags = Collections.unmodifiableSet(tagsBuilder); | |||||
this.securityStandards = Collections.unmodifiableSet(new TreeSet<>(newRule.securityStandards)); | |||||
Map<String, Param> paramsBuilder = new HashMap<>(); | |||||
for (NewParam newParam : newRule.paramsByKey.values()) { | |||||
paramsBuilder.put(newParam.key, new Param(newParam)); | |||||
} | |||||
this.params = Collections.unmodifiableMap(paramsBuilder); | |||||
this.activatedByDefault = newRule.activatedByDefault; | |||||
this.deprecatedRuleKeys = Collections.unmodifiableSet(new TreeSet<>(newRule.deprecatedRuleKeys)); | |||||
} | |||||
interface Rule { | |||||
public Repository repository() { | |||||
return repository; | |||||
} | |||||
Repository repository(); | |||||
/** | /** | ||||
* @since 6.6 the plugin the rule was declared in | * @since 6.6 the plugin the rule was declared in | ||||
*/ | */ | ||||
@CheckForNull | @CheckForNull | ||||
public String pluginKey() { | |||||
return pluginKey; | |||||
} | |||||
String pluginKey(); | |||||
public String key() { | |||||
return key; | |||||
} | |||||
String key(); | |||||
public String name() { | |||||
return name; | |||||
} | |||||
String name(); | |||||
/** | /** | ||||
* @since 7.1 | * @since 7.1 | ||||
*/ | */ | ||||
public RuleScope scope() { | |||||
return scope; | |||||
} | |||||
RuleScope scope(); | |||||
/** | /** | ||||
* @see NewRule#setType(RuleType) | * @see NewRule#setType(RuleType) | ||||
* @since 5.5 | * @since 5.5 | ||||
*/ | */ | ||||
public RuleType type() { | |||||
return type; | |||||
} | |||||
RuleType type(); | |||||
public String severity() { | |||||
return severity; | |||||
} | |||||
String severity(); | |||||
@CheckForNull | @CheckForNull | ||||
public String htmlDescription() { | |||||
return htmlDescription; | |||||
} | |||||
String htmlDescription(); | |||||
@CheckForNull | @CheckForNull | ||||
public String markdownDescription() { | |||||
return markdownDescription; | |||||
} | |||||
String markdownDescription(); | |||||
public boolean template() { | |||||
return template; | |||||
} | |||||
boolean template(); | |||||
/** | /** | ||||
* Should this rule be enabled by default. For example in SonarLint standalone. | * Should this rule be enabled by default. For example in SonarLint standalone. | ||||
* | * | ||||
* @since 6.0 | * @since 6.0 | ||||
*/ | */ | ||||
public boolean activatedByDefault() { | |||||
return activatedByDefault; | |||||
} | |||||
boolean activatedByDefault(); | |||||
public RuleStatus status() { | |||||
return status; | |||||
} | |||||
RuleStatus status(); | |||||
/** | /** | ||||
* @see #type() | * @see #type() | ||||
*/ | */ | ||||
@CheckForNull | @CheckForNull | ||||
@Deprecated | @Deprecated | ||||
public String debtSubCharacteristic() { | |||||
return null; | |||||
} | |||||
String debtSubCharacteristic(); | |||||
@CheckForNull | @CheckForNull | ||||
public DebtRemediationFunction debtRemediationFunction() { | |||||
return debtRemediationFunction; | |||||
} | |||||
DebtRemediationFunction debtRemediationFunction(); | |||||
/** | /** | ||||
* @deprecated since 5.5, replaced by {@link #gapDescription()} | * @deprecated since 5.5, replaced by {@link #gapDescription()} | ||||
*/ | */ | ||||
@Deprecated | @Deprecated | ||||
@CheckForNull | @CheckForNull | ||||
public String effortToFixDescription() { | |||||
return gapDescription(); | |||||
} | |||||
String effortToFixDescription(); | |||||
@CheckForNull | @CheckForNull | ||||
public String gapDescription() { | |||||
return gapDescription; | |||||
} | |||||
String gapDescription(); | |||||
@CheckForNull | @CheckForNull | ||||
public Param param(String key) { | |||||
return params.get(key); | |||||
} | |||||
Param param(String key); | |||||
public List<Param> params() { | |||||
return unmodifiableList(new ArrayList<>(params.values())); | |||||
} | |||||
List<Param> params(); | |||||
public Set<String> tags() { | |||||
return tags; | |||||
} | |||||
Set<String> tags(); | |||||
public Set<String> securityStandards() { | |||||
return securityStandards; | |||||
} | |||||
Set<String> securityStandards(); | |||||
/** | /** | ||||
* Deprecated rules keys for this rule. | * Deprecated rules keys for this rule. | ||||
* @see NewRule#addDeprecatedRuleKey(String, String) | * @see NewRule#addDeprecatedRuleKey(String, String) | ||||
* @since 7.1 | * @since 7.1 | ||||
*/ | */ | ||||
public Set<RuleKey> deprecatedRuleKeys() { | |||||
return deprecatedRuleKeys; | |||||
} | |||||
Set<RuleKey> deprecatedRuleKeys(); | |||||
/** | /** | ||||
* @see RulesDefinition.NewRule#setInternalKey(String) | * @see RulesDefinition.NewRule#setInternalKey(String) | ||||
*/ | */ | ||||
@CheckForNull | @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); | |||||
} | |||||
String internalKey(); | |||||
@Override | |||||
public int hashCode() { | |||||
int result = repoKey.hashCode(); | |||||
result = 31 * result + key.hashCode(); | |||||
return result; | |||||
} | |||||
@Override | |||||
public String toString() { | |||||
return format("[repository=%s, key=%s]", repoKey, key); | |||||
} | |||||
} | } | ||||
class NewParam { | |||||
private final String key; | |||||
private String name; | |||||
private String description; | |||||
private String defaultValue; | |||||
private RuleParamType type = RuleParamType.STRING; | |||||
private NewParam(String key) { | |||||
this.key = this.name = key; | |||||
} | |||||
public String key() { | |||||
return key; | |||||
} | |||||
interface NewParam { | |||||
String key(); | |||||
public NewParam setName(@Nullable String s) { | |||||
// name must never be null. | |||||
this.name = StringUtils.defaultIfBlank(s, key); | |||||
return this; | |||||
} | |||||
NewParam setName(@Nullable String s); | |||||
public NewParam setType(RuleParamType t) { | |||||
this.type = t; | |||||
return this; | |||||
} | |||||
NewParam setType(RuleParamType t); | |||||
/** | /** | ||||
* Plain-text description. Can be null. Max length is 4000 characters. | * Plain-text description. Can be null. Max length is 4000 characters. | ||||
*/ | */ | ||||
public NewParam setDescription(@Nullable String s) { | |||||
this.description = StringUtils.defaultIfBlank(s, null); | |||||
return this; | |||||
} | |||||
NewParam setDescription(@Nullable String s); | |||||
/** | /** | ||||
* Empty default value will be converted to null. Max length is 4000 characters. | * Empty default value will be converted to null. Max length is 4000 characters. | ||||
*/ | */ | ||||
public NewParam setDefaultValue(@Nullable String s) { | |||||
this.defaultValue = defaultIfEmpty(s, null); | |||||
return this; | |||||
} | |||||
NewParam setDefaultValue(@Nullable String s); | |||||
} | } | ||||
@Immutable | @Immutable | ||||
class Param { | |||||
private final String key; | |||||
private final String name; | |||||
private final String description; | |||||
private final String 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; | |||||
} | |||||
interface Param { | |||||
String key(); | |||||
public String name() { | |||||
return name; | |||||
} | |||||
String name(); | |||||
@Nullable | @Nullable | ||||
public String description() { | |||||
return description; | |||||
} | |||||
String description(); | |||||
@Nullable | @Nullable | ||||
public String defaultValue() { | |||||
return defaultValue; | |||||
} | |||||
public RuleParamType type() { | |||||
return type; | |||||
} | |||||
String defaultValue(); | |||||
@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(); | |||||
} | |||||
RuleParamType type(); | |||||
} | } | ||||
/** | /** |
import org.sonar.api.rule.Severity; | import org.sonar.api.rule.Severity; | ||||
import org.sonar.api.server.rule.RulesDefinition.NewRule; | import org.sonar.api.server.rule.RulesDefinition.NewRule; | ||||
import org.sonar.check.Priority; | import org.sonar.check.Priority; | ||||
import org.sonar.server.rule.RuleDefinitionContext; | |||||
import static org.assertj.core.api.Assertions.assertThat; | import static org.assertj.core.api.Assertions.assertThat; | ||||
@Test | @Test | ||||
public void override_annotation_programmatically() { | public void override_annotation_programmatically() { | ||||
RulesDefinition.Context context = new RulesDefinition.Context(); | |||||
RulesDefinition.Context context = new RuleDefinitionContext(); | |||||
RulesDefinition.NewRepository newRepository = context.createRepository("squid", "java"); | RulesDefinition.NewRepository newRepository = context.createRepository("squid", "java"); | ||||
NewRule newRule = annotationLoader.loadRule(newRepository, RuleWithProperty.class); | NewRule newRule = annotationLoader.loadRule(newRepository, RuleWithProperty.class); | ||||
newRule.setName("Overridden name"); | newRule.setName("Overridden name"); | ||||
} | } | ||||
private RulesDefinition.Repository load(Class annotatedClass) { | private RulesDefinition.Repository load(Class annotatedClass) { | ||||
RulesDefinition.Context context = new RulesDefinition.Context(); | |||||
RulesDefinition.Context context = new RuleDefinitionContext(); | |||||
RulesDefinition.NewExtendedRepository newRepository = context.createRepository("squid", "java"); | RulesDefinition.NewExtendedRepository newRepository = context.createRepository("squid", "java"); | ||||
annotationLoader.load(newRepository, annotatedClass); | annotationLoader.load(newRepository, annotatedClass); | ||||
newRepository.done(); | newRepository.done(); |
import org.junit.Test; | import org.junit.Test; | ||||
import org.sonar.api.i18n.RuleI18n; | import org.sonar.api.i18n.RuleI18n; | ||||
import org.sonar.server.rule.RuleDefinitionContext; | |||||
import static org.assertj.core.api.Assertions.assertThat; | import static org.assertj.core.api.Assertions.assertThat; | ||||
import static org.mockito.Mockito.mock; | import static org.mockito.Mockito.mock; | ||||
when(i18n.getName("squid", "S0001")).thenReturn("SOne"); | when(i18n.getName("squid", "S0001")).thenReturn("SOne"); | ||||
when(i18n.getDescription("squid", "S0001")).thenReturn("S One"); | when(i18n.getDescription("squid", "S0001")).thenReturn("S One"); | ||||
RulesDefinition.Context context = new RulesDefinition.Context(); | |||||
RulesDefinition.Context context = new RuleDefinitionContext(); | |||||
RulesDefinition.NewRepository repo = context.createRepository("squid", "java"); | RulesDefinition.NewRepository repo = context.createRepository("squid", "java"); | ||||
// rule without description | // rule without description | ||||
repo.createRule("S0001"); | repo.createRule("S0001"); | ||||
public void do_not_override_if_no_bundle() { | public void do_not_override_if_no_bundle() { | ||||
// i18n returns null values | // i18n returns null values | ||||
RulesDefinition.Context context = new RulesDefinition.Context(); | |||||
RulesDefinition.Context context = new RuleDefinitionContext(); | |||||
RulesDefinition.NewRepository repo = context.createRepository("squid", "java"); | RulesDefinition.NewRepository repo = context.createRepository("squid", "java"); | ||||
repo.createRule("S0001").setName("SOne").setHtmlDescription("S One"); | repo.createRule("S0001").setName("SOne").setHtmlDescription("S One"); | ||||
when(i18n.getName("squid", "S0001")).thenReturn("SOne"); | when(i18n.getName("squid", "S0001")).thenReturn("SOne"); | ||||
when(i18n.getDescription("squid", "S0001")).thenReturn("S One"); | when(i18n.getDescription("squid", "S0001")).thenReturn("S One"); | ||||
RulesDefinition.Context context = new RulesDefinition.Context(); | |||||
RulesDefinition.Context context = new RuleDefinitionContext(); | |||||
RulesDefinition.NewRepository repo = context.createRepository("squid", "java"); | RulesDefinition.NewRepository repo = context.createRepository("squid", "java"); | ||||
repo.createRule("S0001").setName("Bad").setHtmlDescription("Bad"); | repo.createRule("S0001").setName("Bad").setHtmlDescription("Bad"); | ||||
public void complete_param_description() { | public void complete_param_description() { | ||||
when(i18n.getParamDescription("squid", "S0001", "max")).thenReturn("Maximum"); | when(i18n.getParamDescription("squid", "S0001", "max")).thenReturn("Maximum"); | ||||
RulesDefinition.Context context = new RulesDefinition.Context(); | |||||
RulesDefinition.Context context = new RuleDefinitionContext(); | |||||
RulesDefinition.NewRepository repo = context.createRepository("squid", "java"); | RulesDefinition.NewRepository repo = context.createRepository("squid", "java"); | ||||
repo.createRule("S0001").setName("SOne").setHtmlDescription("S One").createParam("max"); | repo.createRule("S0001").setName("SOne").setHtmlDescription("S One").createParam("max"); | ||||
import org.sonar.api.rules.RuleType; | import org.sonar.api.rules.RuleType; | ||||
import org.sonar.api.server.debt.DebtRemediationFunction; | import org.sonar.api.server.debt.DebtRemediationFunction; | ||||
import org.sonar.api.utils.log.LogTester; | import org.sonar.api.utils.log.LogTester; | ||||
import org.sonar.server.rule.RuleDefinitionContext; | |||||
import static org.assertj.core.api.Assertions.assertThat; | import static org.assertj.core.api.Assertions.assertThat; | ||||
import static org.junit.Assert.fail; | import static org.junit.Assert.fail; | ||||
@RunWith(DataProviderRunner.class) | @RunWith(DataProviderRunner.class) | ||||
public class RulesDefinitionTest { | public class RulesDefinitionTest { | ||||
RulesDefinition.Context context = new RulesDefinition.Context(); | |||||
RulesDefinition.Context context = new RuleDefinitionContext(); | |||||
@Rule | @Rule | ||||
public LogTester logTester = new LogTester(); | public LogTester logTester = new LogTester(); | ||||
/** | /** | ||||
* This is temporarily accepted only for the support of the common-rules that are still declared | * This is temporarily accepted only for the support of the common-rules that are still declared | ||||
* by plugins. It could be removed in 7.0 | * by plugins. It could be removed in 7.0 | ||||
* | |||||
* @since 5.2 | * @since 5.2 | ||||
*/ | */ | ||||
@Test | @Test |
import org.sonar.api.rule.Severity; | import org.sonar.api.rule.Severity; | ||||
import org.sonar.api.rules.RuleType; | import org.sonar.api.rules.RuleType; | ||||
import org.sonar.api.server.debt.DebtRemediationFunction; | import org.sonar.api.server.debt.DebtRemediationFunction; | ||||
import org.sonar.server.rule.RuleDefinitionContext; | |||||
import static org.assertj.core.api.Assertions.assertThat; | import static org.assertj.core.api.Assertions.assertThat; | ||||
import static org.sonar.api.utils.ExceptionCauseMatcher.hasType; | import static org.sonar.api.utils.ExceptionCauseMatcher.hasType; | ||||
} | } | ||||
private RulesDefinition.Repository load(InputStream input, String encoding) { | private RulesDefinition.Repository load(InputStream input, String encoding) { | ||||
RulesDefinition.Context context = new RulesDefinition.Context(); | |||||
RulesDefinition.Context context = new RuleDefinitionContext(); | |||||
RulesDefinition.NewRepository newRepository = context.createRepository("squid", "java"); | RulesDefinition.NewRepository newRepository = context.createRepository("squid", "java"); | ||||
underTest.load(newRepository, input, encoding); | underTest.load(newRepository, input, encoding); | ||||
newRepository.done(); | newRepository.done(); | ||||
} | } | ||||
private RulesDefinition.Repository load(String xml) { | private RulesDefinition.Repository load(String xml) { | ||||
RulesDefinition.Context context = new RulesDefinition.Context(); | |||||
RulesDefinition.Context context = new RuleDefinitionContext(); | |||||
RulesDefinition.NewRepository newRepository = context.createRepository("squid", "java"); | RulesDefinition.NewRepository newRepository = context.createRepository("squid", "java"); | ||||
underTest.load(newRepository, new StringReader(xml)); | underTest.load(newRepository, new StringReader(xml)); | ||||
newRepository.done(); | newRepository.done(); |
testCompile 'org.mockito:mockito-core' | testCompile 'org.mockito:mockito-core' | ||||
testCompile project(':plugins:sonar-xoo-plugin') | testCompile project(':plugins:sonar-xoo-plugin') | ||||
testCompile project(':sonar-plugin-api').sourceSets.test.output | testCompile project(':sonar-plugin-api').sourceSets.test.output | ||||
testCompile project(':server:sonar-server') | |||||
} | } | ||||
license { | license { |
import org.sonar.scanner.scan.branch.BranchType; | import org.sonar.scanner.scan.branch.BranchType; | ||||
import org.sonar.scanner.scan.branch.ProjectBranches; | import org.sonar.scanner.scan.branch.ProjectBranches; | ||||
import org.sonar.scanner.scan.branch.ProjectPullRequests; | import org.sonar.scanner.scan.branch.ProjectPullRequests; | ||||
import org.sonar.server.rule.RuleDefinitionContext; | |||||
import org.sonarqube.ws.Qualityprofiles.SearchWsResponse.QualityProfile; | import org.sonarqube.ws.Qualityprofiles.SearchWsResponse.QualityProfile; | ||||
import org.sonarqube.ws.Rules.ListResponse.Rule; | import org.sonarqube.ws.Rules.ListResponse.Rule; | ||||
} | } | ||||
public ScannerMediumTester addRules(RulesDefinition rulesDefinition) { | public ScannerMediumTester addRules(RulesDefinition rulesDefinition) { | ||||
RulesDefinition.Context context = new RulesDefinition.Context(); | |||||
RulesDefinition.Context context = new RuleDefinitionContext(); | |||||
rulesDefinition.define(context); | rulesDefinition.define(context); | ||||
List<Repository> repositories = context.repositories(); | List<Repository> repositories = context.repositories(); | ||||
for (Repository repo : repositories) { | for (Repository repo : repositories) { |