aboutsummaryrefslogtreecommitdiffstats
path: root/sonar-plugin-api
diff options
context:
space:
mode:
authorJulien HENRY <julien.henry@sonarsource.com>2018-09-04 16:08:53 +0200
committerSonarTech <sonartech@sonarsource.com>2018-09-24 20:20:58 +0200
commitcfba7fcb6500d8217bd81ecfcb8f47ec48ad55f2 (patch)
tree81398a80d0269b523e630495a580daf8ac144228 /sonar-plugin-api
parent326b30334f0f5c0bb6a9565a3f6b367695bb1087 (diff)
downloadsonarqube-cfba7fcb6500d8217bd81ecfcb8f47ec48ad55f2.tar.gz
sonarqube-cfba7fcb6500d8217bd81ecfcb8f47ec48ad55f2.zip
SONAR-11209 Allow sensors to provide ad hoc rule metadata for external issues
Diffstat (limited to 'sonar-plugin-api')
-rw-r--r--sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/SensorContext.java9
-rw-r--r--sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/internal/InMemorySensorStorage.java11
-rw-r--r--sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/internal/SensorContextTester.java12
-rw-r--r--sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/internal/SensorStorage.java7
-rw-r--r--sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/issue/ExternalIssue.java10
-rw-r--r--sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/issue/NewExternalIssue.java14
-rw-r--r--sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/issue/internal/AbstractDefaultIssue.java13
-rw-r--r--sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/issue/internal/DefaultExternalIssue.java43
-rw-r--r--sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/issue/internal/DefaultIssue.java12
-rw-r--r--sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/issue/internal/DefaultIssueLocation.java6
-rw-r--r--sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/rule/AdHocRule.java62
-rw-r--r--sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/rule/NewAdHocRule.java70
-rw-r--r--sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/rule/internal/DefaultAdHocRule.java126
-rw-r--r--sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/rule/internal/package-info.java21
-rw-r--r--sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/rule/package-info.java21
-rw-r--r--sonar-plugin-api/src/main/java/org/sonar/api/rule/RuleKey.java12
-rw-r--r--sonar-plugin-api/src/main/java/org/sonar/api/server/rule/RulesDefinition.java8
-rw-r--r--sonar-plugin-api/src/test/java/org/sonar/api/batch/sensor/issue/internal/DefaultExternalIssueTest.java4
-rw-r--r--sonar-plugin-api/src/test/java/org/sonar/api/batch/sensor/rule/internal/DefaultAdHocRuleTest.java148
19 files changed, 569 insertions, 40 deletions
diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/SensorContext.java b/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/SensorContext.java
index ed4ffe22b3c..00dae1fdd65 100644
--- a/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/SensorContext.java
+++ b/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/SensorContext.java
@@ -20,7 +20,6 @@
package org.sonar.api.batch.sensor;
import java.io.Serializable;
-
import org.sonar.api.SonarRuntime;
import org.sonar.api.batch.fs.FileSystem;
import org.sonar.api.batch.fs.InputFile;
@@ -38,6 +37,8 @@ import org.sonar.api.batch.sensor.issue.NewExternalIssue;
import org.sonar.api.batch.sensor.issue.NewIssue;
import org.sonar.api.batch.sensor.measure.Measure;
import org.sonar.api.batch.sensor.measure.NewMeasure;
+import org.sonar.api.batch.sensor.rule.AdHocRule;
+import org.sonar.api.batch.sensor.rule.NewAdHocRule;
import org.sonar.api.batch.sensor.symbol.NewSymbolTable;
import org.sonar.api.config.Configuration;
import org.sonar.api.config.Settings;
@@ -122,6 +123,12 @@ public interface SensorContext {
*/
NewExternalIssue newExternalIssue();
+ /**
+ * Fluent builder to create a new {@link AdHocRule}. Don't forget to call {@link NewAdHocRule#save()} once all parameters are provided.
+ * @since 7.4
+ */
+ NewAdHocRule newAdHocRule();
+
// ------------ HIGHLIGHTING ------------
/**
diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/internal/InMemorySensorStorage.java b/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/internal/InMemorySensorStorage.java
index e46b8121830..681005b5ff7 100644
--- a/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/internal/InMemorySensorStorage.java
+++ b/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/internal/InMemorySensorStorage.java
@@ -34,7 +34,10 @@ import org.sonar.api.batch.sensor.error.AnalysisError;
import org.sonar.api.batch.sensor.highlighting.internal.DefaultHighlighting;
import org.sonar.api.batch.sensor.issue.ExternalIssue;
import org.sonar.api.batch.sensor.issue.Issue;
+import org.sonar.api.batch.sensor.issue.internal.DefaultExternalIssue;
import org.sonar.api.batch.sensor.measure.Measure;
+import org.sonar.api.batch.sensor.rule.AdHocRule;
+import org.sonar.api.batch.sensor.rule.internal.DefaultAdHocRule;
import org.sonar.api.batch.sensor.symbol.internal.DefaultSymbolTable;
import static com.google.common.base.Preconditions.checkArgument;
@@ -45,6 +48,7 @@ class InMemorySensorStorage implements SensorStorage {
Collection<Issue> allIssues = new ArrayList<>();
Collection<ExternalIssue> allExternalIssues = new ArrayList<>();
+ Collection<AdHocRule> allAdHocRules = new ArrayList<>();
Collection<AnalysisError> allAnalysisErrors = new ArrayList<>();
Map<String, DefaultHighlighting> highlightingByComponent = new HashMap<>();
@@ -71,6 +75,11 @@ class InMemorySensorStorage implements SensorStorage {
}
@Override
+ public void store(DefaultAdHocRule adHocRule) {
+ allAdHocRules.add(adHocRule);
+ }
+
+ @Override
public void store(DefaultHighlighting highlighting) {
String fileKey = highlighting.inputFile().key();
// Emulate duplicate storage check
@@ -119,7 +128,7 @@ class InMemorySensorStorage implements SensorStorage {
}
@Override
- public void store(ExternalIssue issue) {
+ public void store(DefaultExternalIssue issue) {
allExternalIssues.add(issue);
}
diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/internal/SensorContextTester.java b/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/internal/SensorContextTester.java
index 3559ebdb258..8549c5943c5 100644
--- a/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/internal/SensorContextTester.java
+++ b/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/internal/SensorContextTester.java
@@ -70,6 +70,9 @@ import org.sonar.api.batch.sensor.issue.internal.DefaultIssue;
import org.sonar.api.batch.sensor.measure.Measure;
import org.sonar.api.batch.sensor.measure.NewMeasure;
import org.sonar.api.batch.sensor.measure.internal.DefaultMeasure;
+import org.sonar.api.batch.sensor.rule.AdHocRule;
+import org.sonar.api.batch.sensor.rule.NewAdHocRule;
+import org.sonar.api.batch.sensor.rule.internal.DefaultAdHocRule;
import org.sonar.api.batch.sensor.symbol.NewSymbolTable;
import org.sonar.api.batch.sensor.symbol.internal.DefaultSymbolTable;
import org.sonar.api.config.Configuration;
@@ -229,10 +232,19 @@ public class SensorContextTester implements SensorContext {
return new DefaultExternalIssue(sensorStorage);
}
+ @Override
+ public NewAdHocRule newAdHocRule() {
+ return new DefaultAdHocRule(sensorStorage);
+ }
+
public Collection<ExternalIssue> allExternalIssues() {
return sensorStorage.allExternalIssues;
}
+ public Collection<AdHocRule> allAdHocRules() {
+ return sensorStorage.allAdHocRules;
+ }
+
public Collection<AnalysisError> allAnalysisErrors() {
return sensorStorage.allAnalysisErrors;
}
diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/internal/SensorStorage.java b/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/internal/SensorStorage.java
index ede3c9e9910..69177b4d924 100644
--- a/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/internal/SensorStorage.java
+++ b/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/internal/SensorStorage.java
@@ -25,9 +25,10 @@ import org.sonar.api.batch.sensor.coverage.internal.DefaultCoverage;
import org.sonar.api.batch.sensor.cpd.internal.DefaultCpdTokens;
import org.sonar.api.batch.sensor.error.AnalysisError;
import org.sonar.api.batch.sensor.highlighting.internal.DefaultHighlighting;
-import org.sonar.api.batch.sensor.issue.ExternalIssue;
import org.sonar.api.batch.sensor.issue.Issue;
+import org.sonar.api.batch.sensor.issue.internal.DefaultExternalIssue;
import org.sonar.api.batch.sensor.measure.Measure;
+import org.sonar.api.batch.sensor.rule.internal.DefaultAdHocRule;
import org.sonar.api.batch.sensor.symbol.internal.DefaultSymbolTable;
/**
@@ -41,7 +42,9 @@ public interface SensorStorage {
void store(Issue issue);
- void store(ExternalIssue issue);
+ void store(DefaultExternalIssue issue);
+
+ void store(DefaultAdHocRule adHocRule);
void store(DefaultHighlighting highlighting);
diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/issue/ExternalIssue.java b/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/issue/ExternalIssue.java
index b151a6582aa..9f0cb1fd76d 100644
--- a/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/issue/ExternalIssue.java
+++ b/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/issue/ExternalIssue.java
@@ -30,6 +30,16 @@ import org.sonar.api.rules.RuleType;
*/
public interface ExternalIssue extends IIssue {
+ /**
+ * @since 7.4
+ */
+ String engineId();
+
+ /**
+ * @since 7.4
+ */
+ String ruleId();
+
Severity severity();
/**
diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/issue/NewExternalIssue.java b/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/issue/NewExternalIssue.java
index ba9a4d7f3ec..99e63eb4d09 100644
--- a/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/issue/NewExternalIssue.java
+++ b/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/issue/NewExternalIssue.java
@@ -34,10 +34,24 @@ import org.sonar.api.rules.RuleType;
public interface NewExternalIssue {
/**
* The {@link RuleKey} of the issue.
+ * @deprecated since 7.4. It is misleading, because of the "external_" prefix that is added on server side. Use {@link #engineId(String)} and {@link #ruleId(String)}
*/
+ @Deprecated
NewExternalIssue forRule(RuleKey ruleKey);
/**
+ * Unique identifier of the external analyzer (e.g. eslint, pmd, ...)
+ * @since 7.4
+ */
+ NewExternalIssue engineId(String engineId);
+
+ /**
+ * Unique rule identifier for a given {@link #engineId(String)}
+ * @since 7.4
+ */
+ NewExternalIssue ruleId(String ruleId);
+
+ /**
* Type of issue.
*/
NewExternalIssue type(RuleType type);
diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/issue/internal/AbstractDefaultIssue.java b/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/issue/internal/AbstractDefaultIssue.java
index c845eb1ab7f..b44534f78d2 100644
--- a/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/issue/internal/AbstractDefaultIssue.java
+++ b/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/issue/internal/AbstractDefaultIssue.java
@@ -26,17 +26,15 @@ import java.util.List;
import javax.annotation.Nullable;
import org.sonar.api.batch.sensor.internal.DefaultStorable;
import org.sonar.api.batch.sensor.internal.SensorStorage;
+import org.sonar.api.batch.sensor.issue.Issue.Flow;
import org.sonar.api.batch.sensor.issue.IssueLocation;
import org.sonar.api.batch.sensor.issue.NewIssueLocation;
-import org.sonar.api.batch.sensor.issue.Issue.Flow;
-import org.sonar.api.rule.RuleKey;
import static com.google.common.base.Preconditions.checkState;
import static java.util.Collections.unmodifiableList;
import static java.util.stream.Collectors.toList;
public abstract class AbstractDefaultIssue<T extends AbstractDefaultIssue> extends DefaultStorable {
- protected RuleKey ruleKey;
protected IssueLocation primaryLocation;
protected List<List<IssueLocation>> flows = new ArrayList<>();
@@ -48,15 +46,6 @@ public abstract class AbstractDefaultIssue<T extends AbstractDefaultIssue> exte
super(storage);
}
- public T forRule(RuleKey ruleKey) {
- this.ruleKey = ruleKey;
- return (T) this;
- }
-
- public RuleKey ruleKey() {
- return this.ruleKey;
- }
-
public IssueLocation primaryLocation() {
return primaryLocation;
}
diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/issue/internal/DefaultExternalIssue.java b/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/issue/internal/DefaultExternalIssue.java
index 9118a4a373c..c4554bcd8ad 100644
--- a/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/issue/internal/DefaultExternalIssue.java
+++ b/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/issue/internal/DefaultExternalIssue.java
@@ -25,6 +25,7 @@ import org.sonar.api.batch.rule.Severity;
import org.sonar.api.batch.sensor.internal.SensorStorage;
import org.sonar.api.batch.sensor.issue.ExternalIssue;
import org.sonar.api.batch.sensor.issue.NewExternalIssue;
+import org.sonar.api.rule.RuleKey;
import org.sonar.api.rules.RuleType;
import static com.google.common.base.Preconditions.checkState;
@@ -35,6 +36,8 @@ public class DefaultExternalIssue extends AbstractDefaultIssue<DefaultExternalIs
private Long effort;
private Severity severity;
private RuleType type;
+ private String engineId;
+ private String ruleId;
public DefaultExternalIssue() {
super(null);
@@ -58,6 +61,16 @@ public class DefaultExternalIssue extends AbstractDefaultIssue<DefaultExternalIs
}
@Override
+ public String engineId() {
+ return engineId;
+ }
+
+ @Override
+ public String ruleId() {
+ return ruleId;
+ }
+
+ @Override
public Severity severity() {
return this.severity;
}
@@ -69,7 +82,8 @@ public class DefaultExternalIssue extends AbstractDefaultIssue<DefaultExternalIs
@Override
public void doSave() {
- requireNonNull(this.ruleKey, "Rule key is mandatory on external issue");
+ requireNonNull(this.engineId, "Engine id is mandatory on external issue");
+ requireNonNull(this.ruleId, "Rule id is mandatory on external issue");
checkState(primaryLocation != null, "Primary location is mandatory on every external issue");
checkState(primaryLocation.inputComponent().isFile(), "External issues must be located in files");
checkState(primaryLocation.message() != null, "External issues must have a message");
@@ -84,6 +98,33 @@ public class DefaultExternalIssue extends AbstractDefaultIssue<DefaultExternalIs
}
@Override
+ public NewExternalIssue engineId(String engineId) {
+ this.engineId = engineId;
+ return this;
+ }
+
+ @Override
+ public NewExternalIssue ruleId(String ruleId) {
+ this.ruleId = ruleId;
+ return this;
+ }
+
+ @Override
+ public DefaultExternalIssue forRule(RuleKey ruleKey) {
+ this.engineId = ruleKey.repository();
+ this.ruleId = ruleKey.rule();
+ return this;
+ }
+
+ @Override
+ public RuleKey ruleKey() {
+ if (engineId != null && ruleId != null) {
+ return RuleKey.of(RuleKey.EXTERNAL_RULE_REPO_PREFIX + engineId, ruleId);
+ }
+ return null;
+ }
+
+ @Override
public DefaultExternalIssue type(RuleType type) {
this.type = type;
return this;
diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/issue/internal/DefaultIssue.java b/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/issue/internal/DefaultIssue.java
index 65bebee580d..eb9ae29b61a 100644
--- a/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/issue/internal/DefaultIssue.java
+++ b/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/issue/internal/DefaultIssue.java
@@ -26,12 +26,14 @@ import org.sonar.api.batch.sensor.internal.SensorStorage;
import org.sonar.api.batch.sensor.issue.Issue;
import org.sonar.api.batch.sensor.issue.IssueLocation;
import org.sonar.api.batch.sensor.issue.NewIssue;
+import org.sonar.api.rule.RuleKey;
import static com.google.common.base.Preconditions.checkState;
import static java.lang.String.format;
import static java.util.Objects.requireNonNull;
public class DefaultIssue extends AbstractDefaultIssue<DefaultIssue> implements Issue, NewIssue {
+ private RuleKey ruleKey;
private Double gap;
private Severity overriddenSeverity;
@@ -43,6 +45,16 @@ public class DefaultIssue extends AbstractDefaultIssue<DefaultIssue> implements
super(storage);
}
+ public DefaultIssue forRule(RuleKey ruleKey) {
+ this.ruleKey = ruleKey;
+ return this;
+ }
+
+ public RuleKey ruleKey() {
+ return this.ruleKey;
+ }
+
+
@Override
public DefaultIssue gap(@Nullable Double gap) {
Preconditions.checkArgument(gap == null || gap >= 0, format("Gap must be greater than or equal 0 (got %s)", gap));
diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/issue/internal/DefaultIssueLocation.java b/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/issue/internal/DefaultIssueLocation.java
index fbb20c0353e..140b90593fe 100644
--- a/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/issue/internal/DefaultIssueLocation.java
+++ b/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/issue/internal/DefaultIssueLocation.java
@@ -90,10 +90,4 @@ public class DefaultIssueLocation implements NewIssueLocation, IssueLocation {
return this.message;
}
- public static void main (String[] args) {
-
- new DefaultIssueLocation().message("pipo");
-
- }
-
}
diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/rule/AdHocRule.java b/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/rule/AdHocRule.java
new file mode 100644
index 00000000000..6708bcd8533
--- /dev/null
+++ b/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/rule/AdHocRule.java
@@ -0,0 +1,62 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 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.api.batch.sensor.rule;
+
+import org.sonar.api.batch.rule.Severity;
+import org.sonar.api.batch.sensor.Sensor;
+import org.sonar.api.rules.RuleType;
+
+/**
+ * Represents a rule imported from an external rule engine by a {@link Sensor}.
+ * @since 7.4
+ */
+public interface AdHocRule {
+
+ /**
+ * Unique identifier of the external analyzer (e.g. eslint, pmd, ...)
+ */
+ String engineId();
+
+ /**
+ * Unique rule identifier for a given {@link #engineId()}
+ */
+ String ruleId();
+
+ /**
+ * Name of the rule.
+ */
+ String name();
+
+ /**
+ * Description of the rule.
+ */
+ String description();
+
+ /**
+ * Default severity of the rule.
+ */
+ Severity severity();
+
+ /**
+ * Type of the rule.
+ */
+ RuleType type();
+
+}
diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/rule/NewAdHocRule.java b/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/rule/NewAdHocRule.java
new file mode 100644
index 00000000000..993b19eeb02
--- /dev/null
+++ b/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/rule/NewAdHocRule.java
@@ -0,0 +1,70 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 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.api.batch.sensor.rule;
+
+import org.sonar.api.batch.rule.Severity;
+import org.sonar.api.batch.sensor.Sensor;
+import org.sonar.api.rules.RuleType;
+
+/**
+ * Builder for a rule imported from an external rule engine by a {@link Sensor}. This allows to provide more metadata
+ * for rules associated to {@link org.sonar.api.batch.sensor.issue.ExternalIssue}.
+ * Don't forget to {@link #save()} after setting the fields.
+ *
+ * @since 7.4
+ */
+public interface NewAdHocRule {
+
+ /**
+ * Unique identifier of the external analyzer (e.g. eslint, pmd, ...)
+ */
+ NewAdHocRule engineId(String engineId);
+
+ /**
+ * Unique rule identifier for a given {@link #engineId(String)}
+ */
+ NewAdHocRule ruleId(String ruleId);
+
+ /**
+ * The name of the rule.
+ */
+ NewAdHocRule name(String name);
+
+ /**
+ * The description of the rule.
+ */
+ NewAdHocRule description(String description);
+
+ /**
+ * Type of the rule.
+ */
+ NewAdHocRule type(RuleType type);
+
+ /**
+ * Set the severity of the rule.
+ */
+ NewAdHocRule severity(Severity severity);
+
+ /**
+ * Save the rule. There is almost no validation, except that no duplicated ad hoc rule keys are permitted.
+ */
+ void save();
+
+}
diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/rule/internal/DefaultAdHocRule.java b/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/rule/internal/DefaultAdHocRule.java
new file mode 100644
index 00000000000..0251f5ce839
--- /dev/null
+++ b/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/rule/internal/DefaultAdHocRule.java
@@ -0,0 +1,126 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 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.api.batch.sensor.rule.internal;
+
+import javax.annotation.Nullable;
+import org.sonar.api.batch.rule.Severity;
+import org.sonar.api.batch.sensor.internal.DefaultStorable;
+import org.sonar.api.batch.sensor.internal.SensorStorage;
+import org.sonar.api.batch.sensor.rule.AdHocRule;
+import org.sonar.api.batch.sensor.rule.NewAdHocRule;
+import org.sonar.api.rules.RuleType;
+
+import static com.google.common.base.Preconditions.checkState;
+import static org.apache.commons.lang.StringUtils.isNotBlank;
+
+public class DefaultAdHocRule extends DefaultStorable implements AdHocRule, NewAdHocRule {
+ private Severity severity;
+ private RuleType type;
+ private String name;
+ private String description;
+ private String engineId;
+ private String ruleId;
+
+ public DefaultAdHocRule() {
+ super(null);
+ }
+
+ public DefaultAdHocRule(@Nullable SensorStorage storage) {
+ super(storage);
+ }
+
+ @Override
+ public DefaultAdHocRule severity(Severity severity) {
+ this.severity = severity;
+ return this;
+ }
+
+ @Override
+ public String engineId() {
+ return engineId;
+ }
+
+ @Override
+ public String ruleId() {
+ return ruleId;
+ }
+
+ @Override
+ public String name() {
+ return name;
+ }
+
+ @Override
+ public String description() {
+ return description;
+ }
+
+ @Override
+ public Severity severity() {
+ return this.severity;
+ }
+
+ @Override
+ public void doSave() {
+ checkState(isNotBlank(engineId), "Engine id is mandatory on ad hoc rule");
+ checkState(isNotBlank(ruleId), "Rule id is mandatory on ad hoc rule");
+ checkState(isNotBlank(name), "Name is mandatory on every ad hoc rule");
+ checkState(isNotBlank(description), "Description is mandatory on every ad hoc rule");
+ checkState(severity != null, "Severity is mandatory on every ad hoc rule");
+ checkState(type != null, "Type is mandatory on every ad hoc rule");
+ storage.store(this);
+ }
+
+ @Override
+ public RuleType type() {
+ return type;
+ }
+
+ @Override
+ public DefaultAdHocRule engineId(String engineId) {
+ this.engineId = engineId;
+ return this;
+ }
+
+ @Override
+ public DefaultAdHocRule ruleId(String ruleId) {
+ this.ruleId = ruleId;
+ return this;
+ }
+
+ @Override
+ public DefaultAdHocRule name(String name) {
+ this.name = name;
+ return this;
+ }
+
+ @Override
+ public DefaultAdHocRule description(String description) {
+ this.description = description;
+ return this;
+ }
+
+ @Override
+ public DefaultAdHocRule type(RuleType type) {
+ this.type = type;
+ return this;
+ }
+
+}
diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/rule/internal/package-info.java b/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/rule/internal/package-info.java
new file mode 100644
index 00000000000..72502a544bb
--- /dev/null
+++ b/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/rule/internal/package-info.java
@@ -0,0 +1,21 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 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.
+ */
+@javax.annotation.ParametersAreNonnullByDefault
+package org.sonar.api.batch.sensor.rule.internal;
diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/rule/package-info.java b/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/rule/package-info.java
new file mode 100644
index 00000000000..aebc1b31470
--- /dev/null
+++ b/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/rule/package-info.java
@@ -0,0 +1,21 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 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.
+ */
+@javax.annotation.ParametersAreNonnullByDefault
+package org.sonar.api.batch.sensor.rule;
diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/rule/RuleKey.java b/sonar-plugin-api/src/main/java/org/sonar/api/rule/RuleKey.java
index 1adb4eaf3d2..0c429837af8 100644
--- a/sonar-plugin-api/src/main/java/org/sonar/api/rule/RuleKey.java
+++ b/sonar-plugin-api/src/main/java/org/sonar/api/rule/RuleKey.java
@@ -34,11 +34,6 @@ import static org.apache.commons.lang.StringUtils.isEmpty;
@Immutable
public class RuleKey implements Serializable, Comparable<RuleKey> {
- /**
- * @deprecated since 5.5, manual rule feature has been dropped
- */
- @Deprecated
- public static final String MANUAL_REPOSITORY_KEY = "manual";
public static final String EXTERNAL_RULE_REPO_PREFIX = "external_";
private final String repository;
@@ -86,13 +81,6 @@ public class RuleKey implements Serializable, Comparable<RuleKey> {
return rule;
}
- /**
- * @deprecated since 5.5, manual rule feature has been dropped
- */
- @Deprecated
- public boolean isManual() {
- return false;
- }
@Override
public boolean equals(@Nullable Object o) {
diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/server/rule/RulesDefinition.java b/sonar-plugin-api/src/main/java/org/sonar/api/server/rule/RulesDefinition.java
index 4d1a5304c55..eb7089dc97e 100644
--- a/sonar-plugin-api/src/main/java/org/sonar/api/server/rule/RulesDefinition.java
+++ b/sonar-plugin-api/src/main/java/org/sonar/api/server/rule/RulesDefinition.java
@@ -403,12 +403,12 @@ public interface RulesDefinition {
/**
* Creates a repository of rules from external rule engines.
- * The key will always be prefixed with "external_".
+ * The repository key will be "external_[engineId]".
*
* @since 7.2
*/
- public NewRepository createExternalRepository(String key, String language) {
- return new NewRepositoryImpl(this, key, language, true);
+ public NewRepository createExternalRepository(String engineId, String language) {
+ return new NewRepositoryImpl(this, RuleKey.EXTERNAL_RULE_REPO_PREFIX + engineId, language, true);
}
/**
@@ -504,7 +504,7 @@ public interface RulesDefinition {
private NewRepositoryImpl(Context context, String key, String language, boolean isExternal) {
this.context = context;
- this.key = isExternal ? (RuleKey.EXTERNAL_RULE_REPO_PREFIX + key) : key;
+ this.key = key;
this.name = key;
this.language = language;
this.isExternal = isExternal;
diff --git a/sonar-plugin-api/src/test/java/org/sonar/api/batch/sensor/issue/internal/DefaultExternalIssueTest.java b/sonar-plugin-api/src/test/java/org/sonar/api/batch/sensor/issue/internal/DefaultExternalIssueTest.java
index dc77fbc63fb..d0d38a0f547 100644
--- a/sonar-plugin-api/src/test/java/org/sonar/api/batch/sensor/issue/internal/DefaultExternalIssueTest.java
+++ b/sonar-plugin-api/src/test/java/org/sonar/api/batch/sensor/issue/internal/DefaultExternalIssueTest.java
@@ -56,7 +56,9 @@ public class DefaultExternalIssueTest {
.severity(Severity.BLOCKER);
assertThat(issue.primaryLocation().inputComponent()).isEqualTo(inputFile);
- assertThat(issue.ruleKey()).isEqualTo(RuleKey.of("repo", "rule"));
+ assertThat(issue.ruleKey()).isEqualTo(RuleKey.of("external_repo", "rule"));
+ assertThat(issue.engineId()).isEqualTo("repo");
+ assertThat(issue.ruleId()).isEqualTo("rule");
assertThat(issue.primaryLocation().textRange().start().line()).isEqualTo(1);
assertThat(issue.remediationEffort()).isEqualTo(10l);
assertThat(issue.type()).isEqualTo(RuleType.BUG);
diff --git a/sonar-plugin-api/src/test/java/org/sonar/api/batch/sensor/rule/internal/DefaultAdHocRuleTest.java b/sonar-plugin-api/src/test/java/org/sonar/api/batch/sensor/rule/internal/DefaultAdHocRuleTest.java
new file mode 100644
index 00000000000..7a2b38084bf
--- /dev/null
+++ b/sonar-plugin-api/src/test/java/org/sonar/api/batch/sensor/rule/internal/DefaultAdHocRuleTest.java
@@ -0,0 +1,148 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 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.api.batch.sensor.rule.internal;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.sonar.api.batch.rule.Severity;
+import org.sonar.api.batch.sensor.internal.SensorStorage;
+import org.sonar.api.batch.sensor.rule.NewAdHocRule;
+import org.sonar.api.rules.RuleType;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+
+public class DefaultAdHocRuleTest {
+
+ @Rule
+ public ExpectedException exception = ExpectedException.none();
+
+ @Test
+ public void store() {
+ SensorStorage storage = mock(SensorStorage.class);
+ new DefaultAdHocRule(storage)
+ .engineId("engine")
+ .ruleId("ruleId")
+ .name("name")
+ .description("desc")
+ .severity(Severity.BLOCKER)
+ .type(RuleType.CODE_SMELL)
+ .save();
+
+ verify(storage).store(any(DefaultAdHocRule.class));
+ }
+
+ @Test
+ public void fail_to_store_if_no_engine_id() {
+ SensorStorage storage = mock(SensorStorage.class);
+ NewAdHocRule rule = new DefaultAdHocRule(storage)
+ .engineId(" ")
+ .ruleId("ruleId")
+ .name("name")
+ .description("desc")
+ .severity(Severity.BLOCKER)
+ .type(RuleType.CODE_SMELL);
+
+ exception.expect(IllegalStateException.class);
+ exception.expectMessage("Engine id is mandatory");
+ rule.save();
+ }
+
+ @Test
+ public void fail_to_store_if_no_rule_id() {
+ SensorStorage storage = mock(SensorStorage.class);
+ NewAdHocRule rule = new DefaultAdHocRule(storage)
+ .engineId("engine")
+ .ruleId(" ")
+ .name("name")
+ .description("desc")
+ .severity(Severity.BLOCKER)
+ .type(RuleType.CODE_SMELL);
+
+ exception.expect(IllegalStateException.class);
+ exception.expectMessage("Rule id is mandatory");
+ rule.save();
+ }
+
+ @Test
+ public void fail_to_store_if_no_name() {
+ SensorStorage storage = mock(SensorStorage.class);
+ NewAdHocRule rule = new DefaultAdHocRule(storage)
+ .engineId("engine")
+ .ruleId("ruleId")
+ .name(" ")
+ .description("desc")
+ .severity(Severity.BLOCKER)
+ .type(RuleType.CODE_SMELL);
+
+ exception.expect(IllegalStateException.class);
+ exception.expectMessage("Name is mandatory");
+ rule.save();
+ }
+
+ @Test
+ public void fail_to_store_if_no_description() {
+ SensorStorage storage = mock(SensorStorage.class);
+ NewAdHocRule rule = new DefaultAdHocRule(storage)
+ .engineId("engine")
+ .ruleId("ruleId")
+ .name("name")
+ .description(" ")
+ .severity(Severity.BLOCKER)
+ .type(RuleType.CODE_SMELL);
+
+ exception.expect(IllegalStateException.class);
+ exception.expectMessage("Description is mandatory");
+ rule.save();
+ }
+
+ @Test
+ public void fail_to_store_if_no_severity() {
+ SensorStorage storage = mock(SensorStorage.class);
+ NewAdHocRule rule = new DefaultAdHocRule(storage)
+ .engineId("engine")
+ .ruleId("ruleId")
+ .name("name")
+ .description("desc")
+ .type(RuleType.CODE_SMELL);
+
+ exception.expect(IllegalStateException.class);
+ exception.expectMessage("Severity is mandatory");
+ rule.save();
+ }
+
+ @Test
+ public void fail_to_store_if_no_type() {
+ SensorStorage storage = mock(SensorStorage.class);
+ NewAdHocRule rule = new DefaultAdHocRule(storage)
+ .engineId("engine")
+ .ruleId("ruleId")
+ .name("name")
+ .description("desc")
+ .severity(Severity.BLOCKER);
+
+ exception.expect(IllegalStateException.class);
+ exception.expectMessage("Type is mandatory");
+ rule.save();
+ }
+
+} \ No newline at end of file