aboutsummaryrefslogtreecommitdiffstats
path: root/sonar-plugin-api/src
diff options
context:
space:
mode:
authorDuarte Meneses <duarte.meneses@sonarsource.com>2018-04-06 11:33:23 +0200
committerSonarTech <sonartech@sonarsource.com>2018-04-26 20:20:49 +0200
commit7bd31bc52d296c558803ee633c49851afe13e9ff (patch)
tree00bf40713bda81ae39861a9bf24fe843f43945ec /sonar-plugin-api/src
parent95b338ed1b91e2fd4684ac0fe0b4f7f9ac736bb0 (diff)
downloadsonarqube-7bd31bc52d296c558803ee633c49851afe13e9ff.tar.gz
sonarqube-7bd31bc52d296c558803ee633c49851afe13e9ff.zip
SONAR-10543 Sensor Java API should allow to add external rule engine issues
Diffstat (limited to 'sonar-plugin-api/src')
-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.java7
-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.java3
-rw-r--r--sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/issue/ExternalIssue.java51
-rw-r--r--sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/issue/IIssue.java44
-rw-r--r--sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/issue/Issue.java18
-rw-r--r--sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/issue/NewExternalIssue.java88
-rw-r--r--sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/issue/internal/AbstractDefaultIssue.java95
-rw-r--r--sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/issue/internal/DefaultExternalIssue.java103
-rw-r--r--sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/issue/internal/DefaultIssue.java62
-rw-r--r--sonar-plugin-api/src/main/java/org/sonar/api/rule/RuleKey.java1
-rw-r--r--sonar-plugin-api/src/test/java/org/sonar/api/batch/sensor/internal/SensorContextTesterTest.java23
-rw-r--r--sonar-plugin-api/src/test/java/org/sonar/api/batch/sensor/issue/internal/DefaultExternalIssueTest.java126
14 files changed, 569 insertions, 73 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 e1a6f6cdf57..287fc2f89b3 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,6 +20,7 @@
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;
@@ -30,7 +31,9 @@ import org.sonar.api.batch.sensor.cpd.NewCpdTokens;
import org.sonar.api.batch.sensor.error.NewAnalysisError;
import org.sonar.api.batch.sensor.highlighting.NewHighlighting;
import org.sonar.api.batch.sensor.internal.SensorContextTester;
+import org.sonar.api.batch.sensor.issue.ExternalIssue;
import org.sonar.api.batch.sensor.issue.Issue;
+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;
@@ -112,6 +115,12 @@ public interface SensorContext {
*/
NewIssue newIssue();
+ /**
+ * Fluent builder to create a new {@link ExternalIssue}. Don't forget to call {@link NewExternalIssue#save()} once all parameters are provided.
+ * @since 7.2
+ */
+ NewExternalIssue newExternalIssue();
+
// ------------ 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 b8e8c969160..ef225532139 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
@@ -31,6 +31,7 @@ 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.measure.Measure;
import org.sonar.api.batch.sensor.symbol.internal.DefaultSymbolTable;
@@ -43,6 +44,7 @@ class InMemorySensorStorage implements SensorStorage {
Table<String, String, Measure> measuresByComponentAndMetric = HashBasedTable.create();
Collection<Issue> allIssues = new ArrayList<>();
+ Collection<ExternalIssue> allExternalIssues = new ArrayList<>();
Collection<AnalysisError> allAnalysisErrors = new ArrayList<>();
Map<String, DefaultHighlighting> highlightingByComponent = new HashMap<>();
@@ -114,4 +116,9 @@ class InMemorySensorStorage implements SensorStorage {
checkArgument(value != null, "Value of context property must not be null");
contextProperties.put(key, value);
}
+
+ @Override
+ public void store(ExternalIssue 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 b6968768d17..b13340fb1c1 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
@@ -58,8 +58,11 @@ import org.sonar.api.batch.sensor.highlighting.NewHighlighting;
import org.sonar.api.batch.sensor.highlighting.TypeOfText;
import org.sonar.api.batch.sensor.highlighting.internal.DefaultHighlighting;
import org.sonar.api.batch.sensor.highlighting.internal.SyntaxHighlightingRule;
+import org.sonar.api.batch.sensor.issue.ExternalIssue;
import org.sonar.api.batch.sensor.issue.Issue;
+import org.sonar.api.batch.sensor.issue.NewExternalIssue;
import org.sonar.api.batch.sensor.issue.NewIssue;
+import org.sonar.api.batch.sensor.issue.internal.DefaultExternalIssue;
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;
@@ -219,6 +222,15 @@ public class SensorContextTester implements SensorContext {
return sensorStorage.allIssues;
}
+ @Override
+ public NewExternalIssue newExternalIssue() {
+ return new DefaultExternalIssue(sensorStorage);
+ }
+
+ public Collection<ExternalIssue> allExternalIssues() {
+ return sensorStorage.allExternalIssues;
+ }
+
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 d2dbcb416da..d8c7cb5648f 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
@@ -24,6 +24,7 @@ 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.measure.Measure;
import org.sonar.api.batch.sensor.symbol.internal.DefaultSymbolTable;
@@ -39,6 +40,8 @@ public interface SensorStorage {
void store(Issue issue);
+ void store(ExternalIssue issue);
+
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
new file mode 100644
index 00000000000..cdc7d6df2f2
--- /dev/null
+++ b/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/issue/ExternalIssue.java
@@ -0,0 +1,51 @@
+/*
+ * 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.issue;
+
+import javax.annotation.CheckForNull;
+import org.sonar.api.batch.rule.Severity;
+import org.sonar.api.batch.sensor.Sensor;
+import org.sonar.api.rules.RuleType;
+
+/**
+ * Represents an issue imported from an external rule engine by a {@link Sensor}.
+ * @since 7.2
+ */
+public interface ExternalIssue extends IIssue {
+
+ Severity severity();
+
+ /**
+ * Link to a page describing more details about the rule that triggered this issue.
+ */
+ @CheckForNull
+ String descriptionUrl();
+
+ /**
+ * Effort to fix the issue, in minutes.
+ */
+ @CheckForNull
+ Long remediationEffort();
+
+ /**
+ * Type of the issue.
+ */
+ RuleType type();
+}
diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/issue/IIssue.java b/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/issue/IIssue.java
new file mode 100644
index 00000000000..32ac7acf9e2
--- /dev/null
+++ b/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/issue/IIssue.java
@@ -0,0 +1,44 @@
+/*
+ * 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.issue;
+
+import java.util.List;
+import org.sonar.api.batch.sensor.issue.Issue.Flow;
+import org.sonar.api.rule.RuleKey;
+
+/**
+ * @since 7.2
+ */
+public interface IIssue {
+ /**
+ * The {@link RuleKey} of this issue.
+ */
+ RuleKey ruleKey();
+
+ /**
+ * Primary locations for this issue.
+ */
+ IssueLocation primaryLocation();
+
+ /**
+ * List of flows for this issue. Can be empty.
+ */
+ List<Flow> flows();
+}
diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/issue/Issue.java b/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/issue/Issue.java
index 5bb21addabb..0f752778775 100644
--- a/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/issue/Issue.java
+++ b/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/issue/Issue.java
@@ -23,27 +23,20 @@ import java.util.List;
import javax.annotation.CheckForNull;
import org.sonar.api.batch.rule.Severity;
import org.sonar.api.batch.sensor.Sensor;
-import org.sonar.api.rule.RuleKey;
/**
* Represents an issue detected by a {@link Sensor}.
*
* @since 5.1
*/
-public interface Issue {
-
+public interface Issue extends IIssue {
interface Flow {
/**
* @return Ordered list of locations for the execution flow
*/
List<IssueLocation> locations();
}
-
- /**
- * The {@link RuleKey} of this issue.
- */
- RuleKey ruleKey();
-
+
/**
* Effort to fix the issue. Used by technical debt model.
* @deprecated since 5.5 use {@link #gap()}
@@ -58,23 +51,24 @@ public interface Issue {
*/
@CheckForNull
Double gap();
-
+
/**
* Overridden severity.
*/
@CheckForNull
Severity overriddenSeverity();
-
+
/**
* Primary locations for this issue.
* @since 5.2
*/
+ @Override
IssueLocation primaryLocation();
/**
* List of flows for this issue. Can be empty.
* @since 5.2
*/
+ @Override
List<Flow> flows();
-
}
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
new file mode 100644
index 00000000000..2a3f41b4f60
--- /dev/null
+++ b/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/issue/NewExternalIssue.java
@@ -0,0 +1,88 @@
+/*
+ * 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.issue;
+
+import javax.annotation.Nullable;
+import org.sonar.api.batch.rule.Severity;
+import org.sonar.api.batch.sensor.Sensor;
+import org.sonar.api.rule.RuleKey;
+import org.sonar.api.rules.RuleType;
+
+/**
+ * Builder for an issue imported from an external rule engine by a {@link Sensor}.
+ * Don't forget to {@link #save()} after setting the fields.
+ *
+ * @since 7.2
+ */
+public interface NewExternalIssue {
+ /**
+ * The {@link RuleKey} of the issue.
+ */
+ NewExternalIssue forRule(RuleKey ruleKey);
+
+ /**
+ * Type of issue.
+ */
+ NewExternalIssue type(RuleType type);
+
+ /**
+ * Effort to fix the issue, in minutes.
+ */
+ NewExternalIssue remediationEffort(@Nullable Long effort);
+
+ /**
+ * Set the severity of the issue.
+ */
+ NewExternalIssue severity(Severity severity);
+
+ /**
+ * Primary location for this issue.
+ */
+ NewExternalIssue at(NewIssueLocation primaryLocation);
+
+ /**
+ * Add a secondary location for this issue. Several secondary locations can be registered.
+ */
+ NewExternalIssue addLocation(NewIssueLocation secondaryLocation);
+
+ /**
+ * Add a URL pointing to more information about the rule triggering this issue.
+ */
+ NewExternalIssue descriptionUrl(String url);
+
+ /**
+ * Register a flow for this issue. A flow is an ordered list of issue locations that help to understand the issue.
+ * It should be a <b>path that backtracks the issue from its primary location to the start of the flow</b>.
+ * Several flows can be registered.
+ */
+ NewExternalIssue addFlow(Iterable<NewIssueLocation> flowLocations);
+
+ /**
+ * Create a new location for this issue. First registered location is considered as primary location.
+ */
+ NewIssueLocation newLocation();
+
+ /**
+ * Save the issue. If rule key is unknown or rule not enabled in the current quality profile then a warning is logged but no exception
+ * is thrown.
+ */
+ void save();
+
+}
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
new file mode 100644
index 00000000000..694942984c2
--- /dev/null
+++ b/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/issue/internal/AbstractDefaultIssue.java
@@ -0,0 +1,95 @@
+/*
+ * 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.issue.internal;
+
+import com.google.common.base.Preconditions;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import org.sonar.api.batch.sensor.internal.DefaultStorable;
+import org.sonar.api.batch.sensor.internal.SensorStorage;
+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<>();
+
+ protected AbstractDefaultIssue() {
+ super(null);
+ }
+
+ public AbstractDefaultIssue(SensorStorage storage) {
+ super(storage);
+ }
+
+ public T forRule(RuleKey ruleKey) {
+ this.ruleKey = ruleKey;
+ return (T) this;
+ }
+
+ public RuleKey ruleKey() {
+ return this.ruleKey;
+ }
+
+ public IssueLocation primaryLocation() {
+ return primaryLocation;
+ }
+
+ public List<Flow> flows() {
+ return this.flows.stream()
+ .<Flow>map(l -> () -> unmodifiableList(new ArrayList<>(l)))
+ .collect(toList());
+ }
+
+ public NewIssueLocation newLocation() {
+ return new DefaultIssueLocation();
+ }
+
+ public T at(NewIssueLocation primaryLocation) {
+ Preconditions.checkArgument(primaryLocation != null, "Cannot use a location that is null");
+ checkState(this.primaryLocation == null, "at() already called");
+ this.primaryLocation = (DefaultIssueLocation) primaryLocation;
+ Preconditions.checkArgument(this.primaryLocation.inputComponent() != null, "Cannot use a location with no input component");
+ return (T) this;
+ }
+
+ public T addLocation(NewIssueLocation secondaryLocation) {
+ flows.add(Arrays.asList((IssueLocation) secondaryLocation));
+ return (T) this;
+ }
+
+ public T addFlow(Iterable<NewIssueLocation> locations) {
+ List<IssueLocation> flowAsList = new ArrayList<>();
+ for (NewIssueLocation issueLocation : locations) {
+ flowAsList.add((DefaultIssueLocation) issueLocation);
+ }
+ flows.add(flowAsList);
+ return (T) this;
+ }
+
+}
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
new file mode 100644
index 00000000000..de3eed8b4e4
--- /dev/null
+++ b/sonar-plugin-api/src/main/java/org/sonar/api/batch/sensor/issue/internal/DefaultExternalIssue.java
@@ -0,0 +1,103 @@
+/*
+ * 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.issue.internal;
+
+import com.google.common.base.Preconditions;
+import javax.annotation.Nullable;
+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.rules.RuleType;
+
+import static com.google.common.base.Preconditions.checkState;
+import static java.lang.String.format;
+import static java.util.Objects.requireNonNull;
+
+public class DefaultExternalIssue extends AbstractDefaultIssue<DefaultExternalIssue> implements ExternalIssue, NewExternalIssue {
+ private Long effort;
+ private Severity severity;
+ private String url;
+ private RuleType type;
+
+ public DefaultExternalIssue() {
+ super(null);
+ }
+
+ public DefaultExternalIssue(SensorStorage storage) {
+ super(storage);
+ }
+
+ @Override
+ public DefaultExternalIssue remediationEffort(@Nullable Long effort) {
+ Preconditions.checkArgument(effort == null || effort >= 0, format("effort must be greater than or equal 0 (got %s)", effort));
+ this.effort = effort;
+ return this;
+ }
+
+ @Override
+ public DefaultExternalIssue severity(@Nullable Severity severity) {
+ this.severity = severity;
+ return this;
+ }
+
+ @Override
+ public Severity severity() {
+ return this.severity;
+ }
+
+ @Override
+ public Long remediationEffort() {
+ return this.effort;
+ }
+
+ @Override
+ public void doSave() {
+ requireNonNull(this.ruleKey, "ruleKey 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(type != null, "Type is mandatory on every external issue");
+ checkState(severity != null, "Severity is mandatory on every external issue");
+ checkState(severity != null, "Severity is mandatory on every external issue");
+ storage.store(this);
+ }
+
+ @Override
+ public DefaultExternalIssue descriptionUrl(String url) {
+ this.url = url;
+ return this;
+ }
+
+ @Override
+ public String descriptionUrl() {
+ return url;
+ }
+
+ @Override
+ public RuleType type() {
+ return type;
+ }
+
+ @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 8b1f8bcb2f0..30095731169 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
@@ -20,32 +20,20 @@
package org.sonar.api.batch.sensor.issue.internal;
import com.google.common.base.Preconditions;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
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.issue.Issue;
import org.sonar.api.batch.sensor.issue.IssueLocation;
import org.sonar.api.batch.sensor.issue.NewIssue;
-import org.sonar.api.batch.sensor.issue.NewIssueLocation;
-import org.sonar.api.rule.RuleKey;
import static com.google.common.base.Preconditions.checkState;
import static java.lang.String.format;
-import static java.util.Collections.unmodifiableList;
import static java.util.Objects.requireNonNull;
-import static java.util.stream.Collectors.toList;
-public class DefaultIssue extends DefaultStorable implements Issue, NewIssue {
-
- private RuleKey ruleKey;
+public class DefaultIssue extends AbstractDefaultIssue<DefaultIssue> implements Issue, NewIssue {
private Double gap;
private Severity overriddenSeverity;
- private IssueLocation primaryLocation;
- private List<List<IssueLocation>> flows = new ArrayList<>();
public DefaultIssue() {
super(null);
@@ -56,12 +44,6 @@ public class DefaultIssue extends DefaultStorable implements Issue, NewIssue {
}
@Override
- public DefaultIssue forRule(RuleKey ruleKey) {
- this.ruleKey = ruleKey;
- return this;
- }
-
- @Override
public DefaultIssue effortToFix(@Nullable Double effortToFix) {
return gap(effortToFix);
}
@@ -80,41 +62,6 @@ public class DefaultIssue extends DefaultStorable implements Issue, NewIssue {
}
@Override
- public NewIssueLocation newLocation() {
- return new DefaultIssueLocation();
- }
-
- @Override
- public DefaultIssue at(NewIssueLocation primaryLocation) {
- Preconditions.checkArgument(primaryLocation != null, "Cannot use a location that is null");
- checkState(this.primaryLocation == null, "at() already called");
- this.primaryLocation = (DefaultIssueLocation) primaryLocation;
- Preconditions.checkArgument(this.primaryLocation.inputComponent() != null, "Cannot use a location with no input component");
- return this;
- }
-
- @Override
- public NewIssue addLocation(NewIssueLocation secondaryLocation) {
- flows.add(Arrays.asList((IssueLocation) secondaryLocation));
- return this;
- }
-
- @Override
- public DefaultIssue addFlow(Iterable<NewIssueLocation> locations) {
- List<IssueLocation> flowAsList = new ArrayList<>();
- for (NewIssueLocation issueLocation : locations) {
- flowAsList.add((DefaultIssueLocation) issueLocation);
- }
- flows.add(flowAsList);
- return this;
- }
-
- @Override
- public RuleKey ruleKey() {
- return this.ruleKey;
- }
-
- @Override
public Severity overriddenSeverity() {
return this.overriddenSeverity;
}
@@ -135,13 +82,6 @@ public class DefaultIssue extends DefaultStorable implements Issue, NewIssue {
}
@Override
- public List<Flow> flows() {
- return this.flows.stream()
- .<Flow>map(l -> () -> unmodifiableList(new ArrayList<>(l)))
- .collect(toList());
- }
-
- @Override
public void doSave() {
requireNonNull(this.ruleKey, "ruleKey is mandatory on issue");
checkState(primaryLocation != null, "Primary location is mandatory on every issue");
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 25378ef02e6..d206d512fbd 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
@@ -39,6 +39,7 @@ public class RuleKey implements Serializable, Comparable<RuleKey> {
*/
@Deprecated
public static final String MANUAL_REPOSITORY_KEY = "manual";
+ public static final String EXTERNAL_RULE_REPO_PREFIX = "external_";
private final String repository;
private final String rule;
diff --git a/sonar-plugin-api/src/test/java/org/sonar/api/batch/sensor/internal/SensorContextTesterTest.java b/sonar-plugin-api/src/test/java/org/sonar/api/batch/sensor/internal/SensorContextTesterTest.java
index a8f57a13ef8..0cee9bc2a96 100644
--- a/sonar-plugin-api/src/test/java/org/sonar/api/batch/sensor/internal/SensorContextTesterTest.java
+++ b/sonar-plugin-api/src/test/java/org/sonar/api/batch/sensor/internal/SensorContextTesterTest.java
@@ -34,16 +34,19 @@ import org.sonar.api.batch.fs.internal.DefaultInputModule;
import org.sonar.api.batch.fs.internal.DefaultTextPointer;
import org.sonar.api.batch.fs.internal.TestInputFileBuilder;
import org.sonar.api.batch.rule.ActiveRules;
+import org.sonar.api.batch.rule.Severity;
import org.sonar.api.batch.rule.internal.ActiveRulesBuilder;
import org.sonar.api.batch.sensor.error.AnalysisError;
import org.sonar.api.batch.sensor.error.NewAnalysisError;
import org.sonar.api.batch.sensor.highlighting.TypeOfText;
+import org.sonar.api.batch.sensor.issue.NewExternalIssue;
import org.sonar.api.batch.sensor.issue.NewIssue;
import org.sonar.api.batch.sensor.symbol.NewSymbolTable;
import org.sonar.api.config.Settings;
import org.sonar.api.config.internal.MapSettings;
import org.sonar.api.measures.CoreMetrics;
import org.sonar.api.rule.RuleKey;
+import org.sonar.api.rules.RuleType;
import org.sonar.api.utils.SonarException;
import static org.assertj.core.api.Assertions.assertThat;
@@ -106,6 +109,26 @@ public class SensorContextTesterTest {
}
@Test
+ public void testExternalIssues() {
+ assertThat(tester.allExternalIssues()).isEmpty();
+ NewExternalIssue newExternalIssue = tester.newExternalIssue();
+ newExternalIssue
+ .at(newExternalIssue.newLocation().on(new TestInputFileBuilder("foo", "src/Foo.java").build()))
+ .forRule(RuleKey.of("repo", "rule"))
+ .type(RuleType.BUG)
+ .severity(Severity.BLOCKER)
+ .save();
+ newExternalIssue = tester.newExternalIssue();
+ newExternalIssue
+ .at(newExternalIssue.newLocation().on(new TestInputFileBuilder("foo", "src/Foo.java").build()))
+ .type(RuleType.BUG)
+ .severity(Severity.BLOCKER)
+ .forRule(RuleKey.of("repo", "rule"))
+ .save();
+ assertThat(tester.allExternalIssues()).hasSize(2);
+ }
+
+ @Test
public void testAnalysisErrors() {
assertThat(tester.allAnalysisErrors()).isEmpty();
NewAnalysisError newAnalysisError = tester.newAnalysisError();
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
new file mode 100644
index 00000000000..d219cefc8ed
--- /dev/null
+++ b/sonar-plugin-api/src/test/java/org/sonar/api/batch/sensor/issue/internal/DefaultExternalIssueTest.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.issue.internal;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.sonar.api.batch.fs.InputComponent;
+import org.sonar.api.batch.fs.internal.DefaultInputFile;
+import org.sonar.api.batch.fs.internal.TestInputFileBuilder;
+import org.sonar.api.batch.rule.Severity;
+import org.sonar.api.batch.sensor.internal.SensorStorage;
+import org.sonar.api.rule.RuleKey;
+import org.sonar.api.rules.RuleType;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+
+public class DefaultExternalIssueTest {
+ @Rule
+ public ExpectedException exception = ExpectedException.none();
+
+ private DefaultInputFile inputFile = new TestInputFileBuilder("foo", "src/Foo.php")
+ .initMetadata("Foo\nBar\n")
+ .build();
+
+ @Test
+ public void build_file_issue() {
+ SensorStorage storage = mock(SensorStorage.class);
+ DefaultExternalIssue issue = new DefaultExternalIssue(storage)
+ .at(new DefaultIssueLocation()
+ .on(inputFile)
+ .at(inputFile.selectLine(1))
+ .message("Wrong way!"))
+ .forRule(RuleKey.of("repo", "rule"))
+ .remediationEffort(10l)
+ .descriptionUrl("url")
+ .type(RuleType.BUG)
+ .severity(Severity.BLOCKER);
+
+ assertThat(issue.primaryLocation().inputComponent()).isEqualTo(inputFile);
+ assertThat(issue.ruleKey()).isEqualTo(RuleKey.of("repo", "rule"));
+ assertThat(issue.primaryLocation().textRange().start().line()).isEqualTo(1);
+ assertThat(issue.remediationEffort()).isEqualTo(10l);
+ assertThat(issue.descriptionUrl()).isEqualTo("url");
+ assertThat(issue.type()).isEqualTo(RuleType.BUG);
+ assertThat(issue.severity()).isEqualTo(Severity.BLOCKER);
+ assertThat(issue.primaryLocation().message()).isEqualTo("Wrong way!");
+
+ issue.save();
+
+ verify(storage).store(issue);
+ }
+
+ @Test
+ public void fail_to_store_if_no_type() {
+ SensorStorage storage = mock(SensorStorage.class);
+ DefaultExternalIssue issue = new DefaultExternalIssue(storage)
+ .at(new DefaultIssueLocation()
+ .on(inputFile)
+ .at(inputFile.selectLine(1))
+ .message("Wrong way!"))
+ .forRule(RuleKey.of("repo", "rule"))
+ .remediationEffort(10l)
+ .descriptionUrl("url")
+ .severity(Severity.BLOCKER);
+
+ exception.expect(IllegalStateException.class);
+ exception.expectMessage("Type is mandatory");
+ issue.save();
+ }
+
+ @Test
+ public void fail_to_store_if_primary_location_is_not_a_file() {
+ SensorStorage storage = mock(SensorStorage.class);
+ DefaultExternalIssue issue = new DefaultExternalIssue(storage)
+ .at(new DefaultIssueLocation()
+ .on(mock(InputComponent.class))
+ .message("Wrong way!"))
+ .forRule(RuleKey.of("repo", "rule"))
+ .remediationEffort(10l)
+ .descriptionUrl("url")
+ .severity(Severity.BLOCKER);
+
+ exception.expect(IllegalStateException.class);
+ exception.expectMessage("External issues must be located in files");
+ issue.save();
+ }
+
+ @Test
+ public void fail_to_store_if_no_severity() {
+ SensorStorage storage = mock(SensorStorage.class);
+ DefaultExternalIssue issue = new DefaultExternalIssue(storage)
+ .at(new DefaultIssueLocation()
+ .on(inputFile)
+ .at(inputFile.selectLine(1))
+ .message("Wrong way!"))
+ .forRule(RuleKey.of("repo", "rule"))
+ .remediationEffort(10l)
+ .descriptionUrl("url")
+ .type(RuleType.BUG);
+
+ exception.expect(IllegalStateException.class);
+ exception.expectMessage("Severity is mandatory");
+ issue.save();
+ }
+
+}