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-scanner-engine/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-scanner-engine/src')
8 files changed, 137 insertions, 5 deletions
diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/issue/ModuleIssues.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/issue/ModuleIssues.java index ef7bda53f16..40167c3c769 100644 --- a/sonar-scanner-engine/src/main/java/org/sonar/scanner/issue/ModuleIssues.java +++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/issue/ModuleIssues.java @@ -20,6 +20,8 @@ package org.sonar.scanner.issue; import com.google.common.base.Strings; +import java.util.Collection; +import java.util.function.Consumer; import javax.annotation.concurrent.ThreadSafe; import org.sonar.api.batch.fs.TextRange; import org.sonar.api.batch.fs.internal.DefaultInputComponent; @@ -27,6 +29,7 @@ import org.sonar.api.batch.rule.ActiveRule; import org.sonar.api.batch.rule.ActiveRules; import org.sonar.api.batch.rule.Rule; import org.sonar.api.batch.rule.Rules; +import org.sonar.api.batch.sensor.issue.ExternalIssue; import org.sonar.api.batch.sensor.issue.Issue; import org.sonar.api.batch.sensor.issue.Issue.Flow; import org.sonar.api.rule.RuleKey; @@ -73,6 +76,12 @@ public class ModuleIssues { return false; } + public void initAndAddExternalIssue(ExternalIssue issue) { + DefaultInputComponent inputComponent = (DefaultInputComponent) issue.primaryLocation().inputComponent(); + ScannerReport.ExternalIssue rawExternalIssue = createReportExternalIssue(issue, inputComponent.batchId()); + write(inputComponent.batchId(), rawExternalIssue); + } + private static ScannerReport.Issue createReportIssue(Issue issue, int componentRef, String ruleName, String activeRuleSeverity) { String primaryMessage = Strings.isNullOrEmpty(issue.primaryLocation().message()) ? ruleName : issue.primaryLocation().message(); org.sonar.api.batch.rule.Severity overriddenSeverity = issue.overriddenSeverity(); @@ -97,14 +106,41 @@ public class ModuleIssues { if (gap != null) { builder.setGap(gap); } - applyFlows(componentRef, builder, locationBuilder, textRangeBuilder, issue); + applyFlows(builder::addFlow, locationBuilder, textRangeBuilder, issue.flows()); return builder.build(); } - private static void applyFlows(int componentRef, ScannerReport.Issue.Builder builder, ScannerReport.IssueLocation.Builder locationBuilder, - ScannerReport.TextRange.Builder textRangeBuilder, Issue issue) { + private static ScannerReport.ExternalIssue createReportExternalIssue(ExternalIssue issue, int componentRef) { + String primaryMessage = issue.primaryLocation().message(); + Severity severity = Severity.valueOf(issue.severity().name()); + + ScannerReport.ExternalIssue.Builder builder = ScannerReport.ExternalIssue.newBuilder(); + ScannerReport.IssueLocation.Builder locationBuilder = IssueLocation.newBuilder(); + ScannerReport.TextRange.Builder textRangeBuilder = ScannerReport.TextRange.newBuilder(); + // non-null fields + builder.setSeverity(severity); + builder.setRuleRepository(issue.ruleKey().repository()); + builder.setRuleKey(issue.ruleKey().rule()); + builder.setMsg(primaryMessage); + locationBuilder.setMsg(primaryMessage); + + locationBuilder.setComponentRef(componentRef); + TextRange primaryTextRange = issue.primaryLocation().textRange(); + if (primaryTextRange != null) { + builder.setTextRange(toProtobufTextRange(textRangeBuilder, primaryTextRange)); + } + Long effort = issue.remediationEffort(); + if (effort != null) { + builder.setEffort(effort); + } + applyFlows(builder::addFlow, locationBuilder, textRangeBuilder, issue.flows()); + return builder.build(); + } + + private static void applyFlows(Consumer<ScannerReport.Flow> consumer, ScannerReport.IssueLocation.Builder locationBuilder, + ScannerReport.TextRange.Builder textRangeBuilder, Collection<Flow> flows) { ScannerReport.Flow.Builder flowBuilder = ScannerReport.Flow.newBuilder(); - for (Flow flow : issue.flows()) { + for (Flow flow : flows) { if (flow.locations().isEmpty()) { return; } @@ -123,7 +159,7 @@ public class ModuleIssues { } flowBuilder.addLocation(locationBuilder.build()); } - builder.addFlow(flowBuilder.build()); + consumer.accept(flowBuilder.build()); } } @@ -152,4 +188,7 @@ public class ModuleIssues { reportPublisher.getWriter().appendComponentIssue(batchId, rawIssue); } + public void write(int batchId, ScannerReport.ExternalIssue rawIssue) { + reportPublisher.getWriter().appendComponentExternalIssue(batchId, rawIssue); + } } diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/mediumtest/TaskResult.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/mediumtest/TaskResult.java index 232fbc88c06..80a33078927 100644 --- a/sonar-scanner-engine/src/main/java/org/sonar/scanner/mediumtest/TaskResult.java +++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/mediumtest/TaskResult.java @@ -123,6 +123,11 @@ public class TaskResult implements org.sonar.scanner.mediumtest.ScanTaskObserver int ref = reportComponents.get(inputComponent.key()).getRef(); return issuesFor(ref); } + + public List<ScannerReport.ExternalIssue> externalIssuesFor(InputComponent inputComponent) { + int ref = reportComponents.get(inputComponent.key()).getRef(); + return externalIssuesFor(ref); + } public List<ScannerReport.Issue> issuesFor(Component reportComponent) { int ref = reportComponent.getRef(); @@ -138,6 +143,16 @@ public class TaskResult implements org.sonar.scanner.mediumtest.ScanTaskObserver } return result; } + + private List<ScannerReport.ExternalIssue> externalIssuesFor(int ref) { + List<ScannerReport.ExternalIssue> result = Lists.newArrayList(); + try (CloseableIterator<ScannerReport.ExternalIssue> it = reader.readComponentExternalIssues(ref)) { + while (it.hasNext()) { + result.add(it.next()); + } + } + return result; + } public Collection<InputFile> inputFiles() { return inputFiles.values(); diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/sensor/DefaultSensorContext.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/sensor/DefaultSensorContext.java index 6972e241500..ff9a7ba1685 100644 --- a/sonar-scanner-engine/src/main/java/org/sonar/scanner/sensor/DefaultSensorContext.java +++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/sensor/DefaultSensorContext.java @@ -37,7 +37,9 @@ import org.sonar.api.batch.sensor.error.NewAnalysisError; import org.sonar.api.batch.sensor.highlighting.NewHighlighting; import org.sonar.api.batch.sensor.highlighting.internal.DefaultHighlighting; import org.sonar.api.batch.sensor.internal.SensorStorage; +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.NewMeasure; import org.sonar.api.batch.sensor.measure.internal.DefaultMeasure; @@ -131,6 +133,11 @@ public class DefaultSensorContext implements SensorContext { } @Override + public NewExternalIssue newExternalIssue() { + return new DefaultExternalIssue(sensorStorage); + } + + @Override public NewHighlighting newHighlighting() { if (analysisMode.isIssues()) { return NO_OP_NEW_HIGHLIGHTING; @@ -182,4 +189,5 @@ public class DefaultSensorContext implements SensorContext { DefaultInputFile file = (DefaultInputFile) inputFile; file.setPublished(true); } + } diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/sensor/DefaultSensorStorage.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/sensor/DefaultSensorStorage.java index 6b5a7137d94..1ccd219c600 100644 --- a/sonar-scanner-engine/src/main/java/org/sonar/scanner/sensor/DefaultSensorStorage.java +++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/sensor/DefaultSensorStorage.java @@ -41,6 +41,7 @@ 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.internal.SensorStorage; +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.measure.internal.DefaultMeasure; @@ -373,6 +374,18 @@ public class DefaultSensorStorage implements SensorStorage { moduleIssues.initAndAddIssue(issue); } + /** + * Thread safe assuming that each issues for each file are only written once. + */ + @Override + public void store(ExternalIssue externalIssue) { + if (externalIssue.primaryLocation().inputComponent() instanceof DefaultInputFile) { + DefaultInputFile defaultInputFile = (DefaultInputFile) externalIssue.primaryLocation().inputComponent(); + defaultInputFile.setPublished(true); + } + moduleIssues.initAndAddExternalIssue(externalIssue); + } + @Override public void store(DefaultHighlighting highlighting) { ScannerReportWriter writer = reportPublisher.getWriter(); @@ -493,4 +506,5 @@ public class DefaultSensorStorage implements SensorStorage { public void storeProperty(String key, String value) { contextPropertiesCache.put(key, value); } + } diff --git a/sonar-scanner-engine/src/test/java/org/sonar/scanner/issue/ModuleIssuesTest.java b/sonar-scanner-engine/src/test/java/org/sonar/scanner/issue/ModuleIssuesTest.java index 07f627c8423..6309720339d 100644 --- a/sonar-scanner-engine/src/test/java/org/sonar/scanner/issue/ModuleIssuesTest.java +++ b/sonar-scanner-engine/src/test/java/org/sonar/scanner/issue/ModuleIssuesTest.java @@ -28,6 +28,7 @@ import org.sonar.api.batch.fs.internal.DefaultInputFile; import org.sonar.api.batch.fs.internal.TestInputFileBuilder; import org.sonar.api.batch.rule.internal.ActiveRulesBuilder; import org.sonar.api.batch.rule.internal.RulesBuilder; +import org.sonar.api.batch.sensor.issue.internal.DefaultExternalIssue; import org.sonar.api.batch.sensor.issue.internal.DefaultIssue; import org.sonar.api.batch.sensor.issue.internal.DefaultIssueLocation; import org.sonar.api.rule.RuleKey; @@ -147,6 +148,23 @@ public class ModuleIssuesTest { } @Test + public void add_external_issue_to_cache() { + ruleBuilder.add(SQUID_RULE_KEY).setName(SQUID_RULE_NAME); + initModuleIssues(); + + DefaultExternalIssue issue = new DefaultExternalIssue() + .at(new DefaultIssueLocation().on(file).at(file.selectLine(3)).message("Foo")) + .forRule(SQUID_RULE_KEY) + .severity(org.sonar.api.batch.rule.Severity.CRITICAL); + + moduleIssues.initAndAddExternalIssue(issue); + + ArgumentCaptor<ScannerReport.ExternalIssue> argument = ArgumentCaptor.forClass(ScannerReport.ExternalIssue.class); + verify(reportPublisher.getWriter()).appendComponentExternalIssue(eq(file.batchId()), argument.capture()); + assertThat(argument.getValue().getSeverity()).isEqualTo(org.sonar.scanner.protocol.Constants.Severity.CRITICAL); + } + + @Test public void use_severity_from_active_rule_if_no_severity_on_issue() { ruleBuilder.add(SQUID_RULE_KEY).setName(SQUID_RULE_NAME); activeRulesBuilder.create(SQUID_RULE_KEY).setSeverity(Severity.INFO).activate(); diff --git a/sonar-scanner-engine/src/test/java/org/sonar/scanner/mediumtest/issues/IssuesMediumTest.java b/sonar-scanner-engine/src/test/java/org/sonar/scanner/mediumtest/issues/IssuesMediumTest.java index 64c8b66cb84..babe6f078ba 100644 --- a/sonar-scanner-engine/src/test/java/org/sonar/scanner/mediumtest/issues/IssuesMediumTest.java +++ b/sonar-scanner-engine/src/test/java/org/sonar/scanner/mediumtest/issues/IssuesMediumTest.java @@ -29,8 +29,10 @@ import org.junit.Test; import org.junit.rules.TemporaryFolder; import org.sonar.scanner.mediumtest.ScannerMediumTester; import org.sonar.scanner.mediumtest.TaskResult; +import org.sonar.scanner.protocol.output.ScannerReport.ExternalIssue; import org.sonar.scanner.protocol.output.ScannerReport.Issue; import org.sonar.xoo.XooPlugin; +import org.sonar.xoo.rule.OneExternalIssuePerLineSensor; import org.sonar.xoo.rule.XooRulesDefinition; import static org.assertj.core.api.Assertions.assertThat; @@ -60,10 +62,31 @@ public class IssuesMediumTest { List<Issue> issues = result.issuesFor(result.inputFile("xources/hello/HelloJava.xoo")); assertThat(issues).hasSize(8 /* lines */); + + List<ExternalIssue> externalIssues = result.externalIssuesFor(result.inputFile("xources/hello/HelloJava.xoo")); + assertThat(externalIssues).isEmpty(); Issue issue = issues.get(0); assertThat(issue.getTextRange().getStartLine()).isEqualTo(issue.getTextRange().getStartLine()); } + + @Test + public void testOneExternalIssuePerLine() throws Exception { + File projectDir = new File(IssuesMediumTest.class.getResource("/mediumtest/xoo/sample").toURI()); + File tmpDir = temp.newFolder(); + FileUtils.copyDirectory(projectDir, tmpDir); + + TaskResult result = tester + .newScanTask(new File(tmpDir, "sonar-project.properties")) + .property(OneExternalIssuePerLineSensor.ACTIVATE_EXTERNAL_ISSUES, "true") + .execute(); + + List<ExternalIssue> externalIssues = result.externalIssuesFor(result.inputFile("xources/hello/HelloJava.xoo")); + assertThat(externalIssues).hasSize(8 /* lines */); + + ExternalIssue externalIssue = externalIssues.get(0); + assertThat(externalIssue.getTextRange().getStartLine()).isEqualTo(externalIssue.getTextRange().getStartLine()); + } @Test public void findActiveRuleByInternalKey() throws Exception { diff --git a/sonar-scanner-engine/src/test/java/org/sonar/scanner/sensor/DefaultSensorContextTest.java b/sonar-scanner-engine/src/test/java/org/sonar/scanner/sensor/DefaultSensorContextTest.java index 68771fe5059..24cc70506be 100644 --- a/sonar-scanner-engine/src/test/java/org/sonar/scanner/sensor/DefaultSensorContextTest.java +++ b/sonar-scanner-engine/src/test/java/org/sonar/scanner/sensor/DefaultSensorContextTest.java @@ -84,6 +84,7 @@ public class DefaultSensorContextTest { assertThat(adaptor.runtime()).isEqualTo(runtime); assertThat(adaptor.newIssue()).isNotNull(); + assertThat(adaptor.newExternalIssue()).isNotNull(); assertThat(adaptor.newMeasure()).isNotNull(); assertThat(adaptor.isCancelled()).isFalse(); diff --git a/sonar-scanner-engine/src/test/java/org/sonar/scanner/sensor/DefaultSensorStorageTest.java b/sonar-scanner-engine/src/test/java/org/sonar/scanner/sensor/DefaultSensorStorageTest.java index 843e4730b45..bb039e2da18 100644 --- a/sonar-scanner-engine/src/test/java/org/sonar/scanner/sensor/DefaultSensorStorageTest.java +++ b/sonar-scanner-engine/src/test/java/org/sonar/scanner/sensor/DefaultSensorStorageTest.java @@ -36,7 +36,9 @@ import org.sonar.api.batch.fs.internal.TestInputFileBuilder; import org.sonar.api.batch.measure.MetricFinder; import org.sonar.api.batch.sensor.highlighting.TypeOfText; 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.issue.internal.DefaultIssue; import org.sonar.api.batch.sensor.issue.internal.DefaultIssueLocation; import org.sonar.api.batch.sensor.measure.internal.DefaultMeasure; @@ -125,6 +127,18 @@ public class DefaultSensorStorageTest { } @Test + public void should_save_external_issue() { + InputFile file = new TestInputFileBuilder("foo", "src/Foo.php").build(); + + DefaultExternalIssue externalIssue = new DefaultExternalIssue().at(new DefaultIssueLocation().on(file)); + underTest.store(externalIssue); + + ArgumentCaptor<ExternalIssue> argumentCaptor = ArgumentCaptor.forClass(ExternalIssue.class); + verify(moduleIssues).initAndAddExternalIssue(argumentCaptor.capture()); + assertThat(argumentCaptor.getValue()).isEqualTo(externalIssue); + } + + @Test public void should_skip_issue_on_short_branch_when_file_status_is_SAME() { InputFile file = new TestInputFileBuilder("foo", "src/Foo.php").setStatus(InputFile.Status.SAME).build(); when(branchConfiguration.isShortOrPullRequest()).thenReturn(true); |