Browse Source

Extract implementation from plugin API - Server rule definition

tags/8.0
Duarte Meneses 5 years ago
parent
commit
7c7d9b6b90
21 changed files with 1206 additions and 636 deletions
  1. 1
    0
      plugins/sonar-xoo-plugin/build.gradle
  2. 2
    1
      plugins/sonar-xoo-plugin/src/test/java/org/sonar/xoo/rule/XooRulesDefinitionTest.java
  3. 2
    1
      server/sonar-server/src/main/java/org/sonar/server/rule/DefaultDebtRemediationFunctions.java
  4. 85
    0
      server/sonar-server/src/main/java/org/sonar/server/rule/DefaultNewParam.java
  5. 113
    0
      server/sonar-server/src/main/java/org/sonar/server/rule/DefaultNewRepository.java
  6. 350
    0
      server/sonar-server/src/main/java/org/sonar/server/rule/DefaultNewRule.java
  7. 87
    0
      server/sonar-server/src/main/java/org/sonar/server/rule/DefaultParam.java
  8. 128
    0
      server/sonar-server/src/main/java/org/sonar/server/rule/DefaultRepository.java
  9. 238
    0
      server/sonar-server/src/main/java/org/sonar/server/rule/DefaultRule.java
  10. 97
    0
      server/sonar-server/src/main/java/org/sonar/server/rule/RuleDefinitionContext.java
  11. 1
    1
      server/sonar-server/src/main/java/org/sonar/server/rule/RuleDefinitionsLoader.java
  12. 5
    5
      server/sonar-server/src/test/java/org/sonar/server/rule/DeprecatedRulesDefinitionLoaderTest.java
  13. 2
    0
      sonar-plugin-api/build.gradle
  14. 3
    3
      sonar-plugin-api/src/main/java/org/sonar/api/server/rule/RuleTagsToTypeConverter.java
  15. 74
    615
      sonar-plugin-api/src/main/java/org/sonar/api/server/rule/RulesDefinition.java
  16. 3
    2
      sonar-plugin-api/src/test/java/org/sonar/api/server/rule/RulesDefinitionAnnotationLoaderTest.java
  17. 5
    4
      sonar-plugin-api/src/test/java/org/sonar/api/server/rule/RulesDefinitionI18nLoaderTest.java
  18. 3
    1
      sonar-plugin-api/src/test/java/org/sonar/api/server/rule/RulesDefinitionTest.java
  19. 3
    2
      sonar-plugin-api/src/test/java/org/sonar/api/server/rule/RulesDefinitionXmlLoaderTest.java
  20. 2
    0
      sonar-scanner-engine/build.gradle
  21. 2
    1
      sonar-scanner-engine/src/test/java/org/sonar/scanner/mediumtest/ScannerMediumTester.java

+ 1
- 0
plugins/sonar-xoo-plugin/build.gradle View File

@@ -15,6 +15,7 @@ dependencies {
testCompile 'junit:junit'
testCompile 'org.assertj:assertj-core'
testCompile 'org.mockito:mockito-core'
testCompile project(':server:sonar-server')
}

jar {

+ 2
- 1
plugins/sonar-xoo-plugin/src/test/java/org/sonar/xoo/rule/XooRulesDefinitionTest.java View File

@@ -27,6 +27,7 @@ import org.sonar.api.internal.SonarRuntimeImpl;
import org.sonar.api.server.debt.DebtRemediationFunction;
import org.sonar.api.server.rule.RulesDefinition;
import org.sonar.api.utils.Version;
import org.sonar.server.rule.RuleDefinitionContext;

import static org.assertj.core.api.Assertions.assertThat;

@@ -36,7 +37,7 @@ public class XooRulesDefinitionTest {
@Before
public void setUp() {
XooRulesDefinition def = new XooRulesDefinition(SonarRuntimeImpl.forSonarQube(Version.create(7, 3), SonarQubeSide.SCANNER, SonarEdition.COMMUNITY));
context = new RulesDefinition.Context();
context = new RuleDefinitionContext();
def.define(context);
}


sonar-plugin-api/src/main/java/org/sonar/api/server/rule/DefaultDebtRemediationFunctions.java → server/sonar-server/src/main/java/org/sonar/server/rule/DefaultDebtRemediationFunctions.java View File

@@ -17,11 +17,12 @@
* 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;
package org.sonar.server.rule;

import javax.annotation.Nullable;
import org.sonar.api.server.debt.DebtRemediationFunction;
import org.sonar.api.server.debt.internal.DefaultDebtRemediationFunction;
import org.sonar.api.server.rule.RulesDefinition;
import org.sonar.api.utils.MessageException;

/**

+ 85
- 0
server/sonar-server/src/main/java/org/sonar/server/rule/DefaultNewParam.java View File

@@ -0,0 +1,85 @@
/*
* 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;
}
}

+ 113
- 0
server/sonar-server/src/main/java/org/sonar/server/rule/DefaultNewRepository.java View File

@@ -0,0 +1,113 @@
/*
* 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();
}
}

+ 350
- 0
server/sonar-server/src/main/java/org/sonar/server/rule/DefaultNewRule.java View File

@@ -0,0 +1,350 @@
/*
* 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);
}
}

+ 87
- 0
server/sonar-server/src/main/java/org/sonar/server/rule/DefaultParam.java View File

@@ -0,0 +1,87 @@
/*
* 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();
}

}

+ 128
- 0
server/sonar-server/src/main/java/org/sonar/server/rule/DefaultRepository.java View File

@@ -0,0 +1,128 @@
/*
* 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();
}
}

+ 238
- 0
server/sonar-server/src/main/java/org/sonar/server/rule/DefaultRule.java View File

@@ -0,0 +1,238 @@
/*
* 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);
}
}


+ 97
- 0
server/sonar-server/src/main/java/org/sonar/server/rule/RuleDefinitionContext.java View File

@@ -0,0 +1,97 @@
/*
* 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;
}
}

+ 1
- 1
server/sonar-server/src/main/java/org/sonar/server/rule/RuleDefinitionsLoader.java View File

@@ -50,7 +50,7 @@ public class RuleDefinitionsLoader {
}

public RulesDefinition.Context load() {
RulesDefinition.Context context = new RulesDefinition.Context();
RulesDefinition.Context context = new RuleDefinitionContext();
for (RulesDefinition pluginDefinition : pluginDefs) {
context.setCurrentPluginKey(serverPluginRepository.getPluginKey(pluginDefinition));
pluginDefinition.define(context);

+ 5
- 5
server/sonar-server/src/test/java/org/sonar/server/rule/DeprecatedRulesDefinitionLoaderTest.java View File

@@ -99,7 +99,7 @@ public class DeprecatedRulesDefinitionLoaderTest {

@Test
public void wrap_deprecated_rule_repositories() {
RulesDefinition.Context context = new RulesDefinition.Context();
RulesDefinition.Context context = new RuleDefinitionContext();
CheckstyleRules checkstyleRules = new CheckstyleRules();
when(pluginRepository.getPluginKey(checkstyleRules)).thenReturn("unittest");
new DeprecatedRulesDefinitionLoader(i18n, debtModelRepository, importer, pluginRepository, new RuleRepository[] {checkstyleRules}).complete(context);
@@ -132,7 +132,7 @@ public class DeprecatedRulesDefinitionLoaderTest {

@Test
public void emulate_the_day_deprecated_api_can_be_dropped() {
RulesDefinition.Context context = new RulesDefinition.Context();
RulesDefinition.Context context = new RuleDefinitionContext();

// no more RuleRepository !
new DeprecatedRulesDefinitionLoader(i18n, debtModelRepository, importer, pluginRepository);
@@ -142,7 +142,7 @@ public class DeprecatedRulesDefinitionLoaderTest {

@Test
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.getDescription("checkstyle", "ConstantName")).thenReturn("Checks that constant names conform to the specified format");
when(i18n.getParamDescription("checkstyle", "ConstantName", "format")).thenReturn("Regular expression");
@@ -162,7 +162,7 @@ public class DeprecatedRulesDefinitionLoaderTest {

@Test
public void define_rule_debt() {
RulesDefinition.Context context = new RulesDefinition.Context();
RulesDefinition.Context context = new RuleDefinitionContext();

List<DebtModelXMLExporter.RuleDebt> ruleDebts = newArrayList(
new DebtModelXMLExporter.RuleDebt()
@@ -192,7 +192,7 @@ public class DeprecatedRulesDefinitionLoaderTest {

@Test
public void fail_on_invalid_rule_debt() {
RulesDefinition.Context context = new RulesDefinition.Context();
RulesDefinition.Context context = new RuleDefinitionContext();

List<DebtModelXMLExporter.RuleDebt> ruleDebts = newArrayList(
new DebtModelXMLExporter.RuleDebt()

+ 2
- 0
sonar-plugin-api/build.gradle View File

@@ -34,6 +34,8 @@ dependencies {
testCompile 'org.assertj:assertj-core'
testCompile 'org.mockito:mockito-core'
testCompile project(':sonar-scanner-engine')
testCompile project(':server:sonar-server')

}

sourceSets {

+ 3
- 3
sonar-plugin-api/src/main/java/org/sonar/api/server/rule/RuleTagsToTypeConverter.java View File

@@ -31,18 +31,18 @@ import static java.util.Collections.unmodifiableSet;
* @see org.sonar.api.server.rule.RulesDefinition.NewRule#setType(RuleType)
* @since 5.5
*/
class RuleTagsToTypeConverter {
public class RuleTagsToTypeConverter {

public static final String TAG_BUG = "bug";
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() {
// only statics
}

static RuleType convert(Collection<String> tags) {
public static RuleType convert(Collection<String> tags) {
if (tags.contains(TAG_BUG)) {
return RuleType.BUG;
}

+ 74
- 615
sonar-plugin-api/src/main/java/org/sonar/api/server/rule/RulesDefinition.java View File

@@ -19,45 +19,23 @@
*/
package org.sonar.api.server.rule;

import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
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 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.ce.ComputeEngineSide;
import org.sonar.api.rule.RuleKey;
import org.sonar.api.rule.RuleScope;
import org.sonar.api.rule.RuleStatus;
import org.sonar.api.rule.Severity;
import org.sonar.api.rules.RuleType;
import org.sonar.api.server.ServerSide;
import org.sonar.api.server.debt.DebtRemediationFunction;
import org.sonar.api.utils.log.Loggers;
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
* this extension point in order to define the rules that it supports.
@@ -369,20 +347,15 @@ public interface RulesDefinition {
/**
* 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}.
* <br>
* 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
* 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.
@@ -390,59 +363,34 @@ public interface RulesDefinition {
*
* @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
public NewRepository extendRepository(String key, String language) {
return createRepository(key, language);
}
NewRepository extendRepository(String key, String language);

@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
* repositories and use {@link #repositories()}. See http://jira.sonarsource.com/browse/SONAR-6709
*/
@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
* repositories and use {@link #repositories()}. See http://jira.sonarsource.com/browse/SONAR-6709
*/
@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 {
@@ -477,77 +425,6 @@ public interface RulesDefinition {
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 {
String key();

@@ -568,98 +445,6 @@ public interface RulesDefinition {
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}.
*/
@@ -699,83 +484,36 @@ public interface RulesDefinition {
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
*/
@CheckForNull
public RuleScope scope() {
return this.scope;
}
RuleScope scope();

/**
* @since 7.1
*/
public NewRule setScope(RuleScope scope) {
this.scope = scope;
return this;
}
NewRule setScope(RuleScope scope);

/**
* 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.
*
* @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.
@@ -793,73 +531,36 @@ public interface RulesDefinition {
*
* @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
* (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>
*/
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
* (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")}
*/
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
* {@link org.sonar.api.rule.RuleStatus#REMOVED} is not accepted and raises an
* {@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
@@ -869,32 +570,23 @@ public interface RulesDefinition {
* @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
*/
public NewRule setDebtSubCharacteristic(@Nullable String s) {
return this;
}
NewRule setDebtSubCharacteristic(@Nullable String s);

/**
* Factory of {@link org.sonar.api.server.debt.DebtRemediationFunction}
*/
public DebtRemediationFunctions debtRemediationFunctions() {
return functions;
}
DebtRemediationFunctions 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
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
@@ -905,90 +597,44 @@ public interface RulesDefinition {
* remediation function gap multiplier/base effort would be something like
* "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.
*/
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
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
*/
public NewRule addTags(String... list) {
for (String tag : list) {
RuleTagFormat.validate(tag);
tags.add(tag);
}
return this;
}
NewRule addTags(String... list);

/**
* @see RuleTagFormat
*/
public NewRule setTags(String... list) {
tags.clear();
addTags(list);
return this;
}
NewRule setTags(String... list);

/**
* @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
*/
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
* 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 (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
@@ -1000,134 +646,56 @@ public interface RulesDefinition {
* @see Rule#deprecatedRuleKeys
* @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
public String toString() {
return format("[repository=%s, key=%s]", repoKey, key);
}
String toString();
}

@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
*/
@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
*/
public RuleScope scope() {
return scope;
}
RuleScope scope();

/**
* @see NewRule#setType(RuleType)
* @since 5.5
*/
public RuleType type() {
return type;
}
RuleType type();

public String severity() {
return severity;
}
String severity();

@CheckForNull
public String htmlDescription() {
return htmlDescription;
}
String htmlDescription();

@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.
*
* @since 6.0
*/
public boolean activatedByDefault() {
return activatedByDefault;
}
boolean activatedByDefault();

public RuleStatus status() {
return status;
}
RuleStatus status();

/**
* @see #type()
@@ -1136,45 +704,29 @@ public interface RulesDefinition {
*/
@CheckForNull
@Deprecated
public String debtSubCharacteristic() {
return null;
}
String debtSubCharacteristic();

@CheckForNull
public DebtRemediationFunction debtRemediationFunction() {
return debtRemediationFunction;
}
DebtRemediationFunction debtRemediationFunction();

/**
* @deprecated since 5.5, replaced by {@link #gapDescription()}
*/
@Deprecated
@CheckForNull
public String effortToFixDescription() {
return gapDescription();
}
String effortToFixDescription();

@CheckForNull
public String gapDescription() {
return gapDescription;
}
String gapDescription();

@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.
@@ -1234,140 +786,47 @@ public interface RulesDefinition {
* @see NewRule#addDeprecatedRuleKey(String, String)
* @since 7.1
*/
public Set<RuleKey> deprecatedRuleKeys() {
return deprecatedRuleKeys;
}
Set<RuleKey> deprecatedRuleKeys();

/**
* @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);
}
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.
*/
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.
*/
public NewParam setDefaultValue(@Nullable String s) {
this.defaultValue = defaultIfEmpty(s, null);
return this;
}
NewParam setDefaultValue(@Nullable String s);
}

@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
public String description() {
return description;
}
String description();

@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();
}

/**

+ 3
- 2
sonar-plugin-api/src/test/java/org/sonar/api/server/rule/RulesDefinitionAnnotationLoaderTest.java View File

@@ -25,6 +25,7 @@ import org.sonar.api.rule.RuleStatus;
import org.sonar.api.rule.Severity;
import org.sonar.api.server.rule.RulesDefinition.NewRule;
import org.sonar.check.Priority;
import org.sonar.server.rule.RuleDefinitionContext;

import static org.assertj.core.api.Assertions.assertThat;

@@ -57,7 +58,7 @@ public class RulesDefinitionAnnotationLoaderTest {

@Test
public void override_annotation_programmatically() {
RulesDefinition.Context context = new RulesDefinition.Context();
RulesDefinition.Context context = new RuleDefinitionContext();
RulesDefinition.NewRepository newRepository = context.createRepository("squid", "java");
NewRule newRule = annotationLoader.loadRule(newRepository, RuleWithProperty.class);
newRule.setName("Overridden name");
@@ -144,7 +145,7 @@ public class RulesDefinitionAnnotationLoaderTest {
}

private RulesDefinition.Repository load(Class annotatedClass) {
RulesDefinition.Context context = new RulesDefinition.Context();
RulesDefinition.Context context = new RuleDefinitionContext();
RulesDefinition.NewExtendedRepository newRepository = context.createRepository("squid", "java");
annotationLoader.load(newRepository, annotatedClass);
newRepository.done();

+ 5
- 4
sonar-plugin-api/src/test/java/org/sonar/api/server/rule/RulesDefinitionI18nLoaderTest.java View File

@@ -21,6 +21,7 @@ package org.sonar.api.server.rule;

import org.junit.Test;
import org.sonar.api.i18n.RuleI18n;
import org.sonar.server.rule.RuleDefinitionContext;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
@@ -36,7 +37,7 @@ public class RulesDefinitionI18nLoaderTest {
when(i18n.getName("squid", "S0001")).thenReturn("SOne");
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");
// rule without description
repo.createRule("S0001");
@@ -53,7 +54,7 @@ public class RulesDefinitionI18nLoaderTest {
public void do_not_override_if_no_bundle() {
// i18n returns null values

RulesDefinition.Context context = new RulesDefinition.Context();
RulesDefinition.Context context = new RuleDefinitionContext();
RulesDefinition.NewRepository repo = context.createRepository("squid", "java");
repo.createRule("S0001").setName("SOne").setHtmlDescription("S One");

@@ -70,7 +71,7 @@ public class RulesDefinitionI18nLoaderTest {
when(i18n.getName("squid", "S0001")).thenReturn("SOne");
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");
repo.createRule("S0001").setName("Bad").setHtmlDescription("Bad");

@@ -86,7 +87,7 @@ public class RulesDefinitionI18nLoaderTest {
public void complete_param_description() {
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");
repo.createRule("S0001").setName("SOne").setHtmlDescription("S One").createParam("max");


+ 3
- 1
sonar-plugin-api/src/test/java/org/sonar/api/server/rule/RulesDefinitionTest.java View File

@@ -41,6 +41,7 @@ import org.sonar.api.rule.Severity;
import org.sonar.api.rules.RuleType;
import org.sonar.api.server.debt.DebtRemediationFunction;
import org.sonar.api.utils.log.LogTester;
import org.sonar.server.rule.RuleDefinitionContext;

import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.Assert.fail;
@@ -48,7 +49,7 @@ import static org.junit.Assert.fail;
@RunWith(DataProviderRunner.class)
public class RulesDefinitionTest {

RulesDefinition.Context context = new RulesDefinition.Context();
RulesDefinition.Context context = new RuleDefinitionContext();

@Rule
public LogTester logTester = new LogTester();
@@ -400,6 +401,7 @@ public class RulesDefinitionTest {
/**
* 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
*
* @since 5.2
*/
@Test

+ 3
- 2
sonar-plugin-api/src/test/java/org/sonar/api/server/rule/RulesDefinitionXmlLoaderTest.java View File

@@ -30,6 +30,7 @@ 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.server.rule.RuleDefinitionContext;

import static org.assertj.core.api.Assertions.assertThat;
import static org.sonar.api.utils.ExceptionCauseMatcher.hasType;
@@ -294,7 +295,7 @@ public class RulesDefinitionXmlLoaderTest {
}

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");
underTest.load(newRepository, input, encoding);
newRepository.done();
@@ -302,7 +303,7 @@ public class RulesDefinitionXmlLoaderTest {
}

private RulesDefinition.Repository load(String xml) {
RulesDefinition.Context context = new RulesDefinition.Context();
RulesDefinition.Context context = new RuleDefinitionContext();
RulesDefinition.NewRepository newRepository = context.createRepository("squid", "java");
underTest.load(newRepository, new StringReader(xml));
newRepository.done();

+ 2
- 0
sonar-scanner-engine/build.gradle View File

@@ -44,6 +44,8 @@ dependencies {
testCompile 'org.mockito:mockito-core'
testCompile project(':plugins:sonar-xoo-plugin')
testCompile project(':sonar-plugin-api').sourceSets.test.output
testCompile project(':server:sonar-server')

}

license {

+ 2
- 1
sonar-scanner-engine/src/test/java/org/sonar/scanner/mediumtest/ScannerMediumTester.java View File

@@ -73,6 +73,7 @@ import org.sonar.scanner.scan.branch.BranchConfigurationLoader;
import org.sonar.scanner.scan.branch.BranchType;
import org.sonar.scanner.scan.branch.ProjectBranches;
import org.sonar.scanner.scan.branch.ProjectPullRequests;
import org.sonar.server.rule.RuleDefinitionContext;
import org.sonarqube.ws.Qualityprofiles.SearchWsResponse.QualityProfile;
import org.sonarqube.ws.Rules.ListResponse.Rule;

@@ -168,7 +169,7 @@ public class ScannerMediumTester extends ExternalResource {
}

public ScannerMediumTester addRules(RulesDefinition rulesDefinition) {
RulesDefinition.Context context = new RulesDefinition.Context();
RulesDefinition.Context context = new RuleDefinitionContext();
rulesDefinition.define(context);
List<Repository> repositories = context.repositories();
for (Repository repo : repositories) {

Loading…
Cancel
Save