diff options
author | Duarte Meneses <duarte.meneses@sonarsource.com> | 2018-04-06 11:33:23 +0200 |
---|---|---|
committer | SonarTech <sonartech@sonarsource.com> | 2018-04-26 20:20:49 +0200 |
commit | 7bd31bc52d296c558803ee633c49851afe13e9ff (patch) | |
tree | 00bf40713bda81ae39861a9bf24fe843f43945ec /sonar-plugin-api/src | |
parent | 95b338ed1b91e2fd4684ac0fe0b4f7f9ac736bb0 (diff) | |
download | sonarqube-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')
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(); + } + +} |