diff --git a/settings.gradle b/settings.gradle index baae4825..d0d509f9 100644 --- a/settings.gradle +++ b/settings.gradle @@ -6,7 +6,7 @@ pluginManagement { } dependencyResolutionManagement { - def slangDependenciesVersion = '1.17.0.6351' + def slangDependenciesVersion = '1.18.0.6375' def analyzerCommonsVersion = '2.16.0.3141' def pluginApiVersion = '10.10.0.2391' def sonarqubeVersion = '10.0.0.68432' diff --git a/sonar-go-plugin/src/main/java/org/sonar/go/externalreport/GolangCILintReportSensor.java b/sonar-go-plugin/src/main/java/org/sonar/go/externalreport/GolangCILintReportSensor.java index 8fff2b08..49926474 100644 --- a/sonar-go-plugin/src/main/java/org/sonar/go/externalreport/GolangCILintReportSensor.java +++ b/sonar-go-plugin/src/main/java/org/sonar/go/externalreport/GolangCILintReportSensor.java @@ -17,11 +17,15 @@ package org.sonar.go.externalreport; import java.io.File; +import java.util.List; import java.util.Locale; import java.util.function.Consumer; import javax.annotation.Nullable; +import org.sonar.api.SonarEdition; +import org.sonar.api.SonarProduct; import org.sonar.api.batch.rule.Severity; import org.sonar.api.batch.sensor.SensorContext; +import org.sonar.api.issue.impact.SoftwareQuality; import org.sonar.api.notifications.AnalysisWarnings; import org.sonar.api.rule.RuleKey; import org.sonar.api.rules.RuleType; @@ -47,8 +51,12 @@ public Consumer reportConsumer(SensorContext context) { private static class GolangCILintCheckstyleFormatImporter extends CheckstyleFormatImporter { + private static final String GOSEC = "gosec"; + private final SensorContext context; + public GolangCILintCheckstyleFormatImporter(SensorContext context, String linterKey) { super(context, linterKey); + this.context = context; } /** @@ -63,7 +71,7 @@ public GolangCILintCheckstyleFormatImporter(SensorContext context, String linter */ @Override protected RuleType ruleType(@Nullable String severity, String source) { - if ("gosec".equals(source)) { + if (GOSEC.equals(source)) { return RuleType.VULNERABILITY; } return super.ruleType(severity, source); @@ -71,7 +79,7 @@ protected RuleType ruleType(@Nullable String severity, String source) { @Override protected RuleKey createRuleKey(String source, RuleType ruleType, Severity ruleSeverity) { - if ("gosec".equals(source)) { + if (GOSEC.equals(source)) { // gosec issues are exclusively "major vulnerability", keeping "gosec" as rule key. return RuleKey.of(linterKey, source); } @@ -79,5 +87,18 @@ protected RuleKey createRuleKey(String source, RuleType ruleType, Severity ruleS ruleSeverity.toString().toLowerCase(Locale.ROOT)); return RuleKey.of(linterKey, ruleKey); } + + @Override + protected List impacts(String severity, String source) { + var isSonarCloud = context.runtime().getProduct() == SonarProduct.SONARQUBE && context.runtime().getEdition() == SonarEdition.SONARCLOUD; + if (isSonarCloud) { + // SonarQube Cloud does not yet support the `impact` field for external issues + return List.of(); + } + if (GOSEC.equals(source)) { + return List.of(new Impact(SoftwareQuality.SECURITY, org.sonar.api.issue.impact.Severity.MEDIUM)); + } + return List.of(new Impact(SoftwareQuality.MAINTAINABILITY, org.sonar.api.issue.impact.Severity.MEDIUM)); + } } } diff --git a/sonar-go-plugin/src/test/java/org/sonar/go/externalreport/GolangCILintReportSensorTest.java b/sonar-go-plugin/src/test/java/org/sonar/go/externalreport/GolangCILintReportSensorTest.java index 164f80e3..6dcc4240 100644 --- a/sonar-go-plugin/src/test/java/org/sonar/go/externalreport/GolangCILintReportSensorTest.java +++ b/sonar-go-plugin/src/test/java/org/sonar/go/externalreport/GolangCILintReportSensorTest.java @@ -22,15 +22,23 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; +import org.mockito.Mockito; import org.slf4j.event.Level; +import org.sonar.api.SonarEdition; +import org.sonar.api.SonarProduct; +import org.sonar.api.SonarRuntime; import org.sonar.api.batch.rule.Severity; import org.sonar.api.batch.sensor.internal.DefaultSensorDescriptor; import org.sonar.api.batch.sensor.internal.SensorContextTester; import org.sonar.api.batch.sensor.issue.ExternalIssue; +import org.sonar.api.issue.impact.SoftwareQuality; import org.sonar.api.rules.RuleType; +import org.sonar.api.utils.Version; import org.sonarsource.slang.testing.ThreadLocalLogTester; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.entry; +import static org.mockito.Mockito.when; import static org.sonar.go.externalreport.ExternalLinterSensorHelper.REPORT_BASE_PATH; class GolangCILintReportSensorTest { @@ -46,7 +54,7 @@ void setup() { public ThreadLocalLogTester logTester = new ThreadLocalLogTester(); @Test - void test_descriptor() { + void shouldValidateDescriptor() { DefaultSensorDescriptor sensorDescriptor = new DefaultSensorDescriptor(); golangCILintReportSensor().describe(sensorDescriptor); assertThat(sensorDescriptor.name()).isEqualTo("Import of GolangCI-Lint issues"); @@ -58,7 +66,7 @@ private GolangCILintReportSensor golangCILintReportSensor() { } @Test - void issues_with_sonarqube() throws IOException { + void shouldValidateWithSonarqube() throws IOException { SensorContextTester context = ExternalLinterSensorHelper.createContext(); context.settings().setProperty("sonar.go.golangci-lint.reportPaths", REPORT_BASE_PATH.resolve("golandci-lint-report.xml").toString()); List externalIssues = ExternalLinterSensorHelper.executeSensor(golangCILintReportSensor(), context); @@ -69,6 +77,7 @@ void issues_with_sonarqube() throws IOException { assertThat(first.severity()).isEqualTo(Severity.MAJOR); assertThat(first.ruleKey().repository()).isEqualTo("external_golangci-lint"); assertThat(first.ruleKey().rule()).isEqualTo("deadcode.bug.major"); + assertThat(first.impacts()).contains(entry(SoftwareQuality.MAINTAINABILITY, org.sonar.api.issue.impact.Severity.MEDIUM)); assertThat(first.primaryLocation().message()).isEqualTo("`three` is unused"); assertThat(first.primaryLocation().textRange().start().line()).isEqualTo(3); @@ -77,6 +86,42 @@ void issues_with_sonarqube() throws IOException { assertThat(second.severity()).isEqualTo(Severity.MAJOR); assertThat(second.ruleKey().repository()).isEqualTo("external_golangci-lint"); assertThat(second.ruleKey().rule()).isEqualTo("gosec"); + assertThat(second.impacts()).contains(entry(SoftwareQuality.SECURITY, org.sonar.api.issue.impact.Severity.MEDIUM)); + assertThat(second.primaryLocation().message()).isEqualTo("G402: TLS InsecureSkipVerify set true."); + assertThat(second.primaryLocation().inputComponent().key()).isEqualTo("module:main.go"); + assertThat(second.primaryLocation().textRange().start().line()).isEqualTo(4); + + assertThat(logTester.logs(Level.ERROR)).isEmpty(); + } + + @Test + void shouldValidateWithSonarcloud() throws IOException { + SensorContextTester context = ExternalLinterSensorHelper.createContext(); + var sonarRuntime = Mockito.mock(SonarRuntime.class); + when(sonarRuntime.getProduct()).thenReturn(SonarProduct.SONARQUBE); + when(sonarRuntime.getEdition()).thenReturn(SonarEdition.SONARCLOUD); + when(sonarRuntime.getApiVersion()).thenReturn(Version.create(7,2)); + context.setRuntime(sonarRuntime); + context.settings().setProperty("sonar.go.golangci-lint.reportPaths", REPORT_BASE_PATH.resolve("golandci-lint-report.xml").toString()); + List externalIssues = ExternalLinterSensorHelper.executeSensor(golangCILintReportSensor(), context); + assertThat(externalIssues).hasSize(2); + + org.sonar.api.batch.sensor.issue.ExternalIssue first = externalIssues.get(0); + assertThat(first.type()).isEqualTo(RuleType.BUG); + assertThat(first.severity()).isEqualTo(Severity.MAJOR); + assertThat(first.ruleKey().repository()).isEqualTo("external_golangci-lint"); + assertThat(first.ruleKey().rule()).isEqualTo("deadcode.bug.major"); + // For SonarQube Cloud the impact should be empty as it is not supported + assertThat(first.impacts()).isEmpty(); + assertThat(first.primaryLocation().message()).isEqualTo("`three` is unused"); + assertThat(first.primaryLocation().textRange().start().line()).isEqualTo(3); + + ExternalIssue second = externalIssues.get(1); + assertThat(second.type()).isEqualTo(RuleType.VULNERABILITY); + assertThat(second.severity()).isEqualTo(Severity.MAJOR); + assertThat(second.ruleKey().repository()).isEqualTo("external_golangci-lint"); + assertThat(second.ruleKey().rule()).isEqualTo("gosec"); + assertThat(first.impacts()).isEmpty(); assertThat(second.primaryLocation().message()).isEqualTo("G402: TLS InsecureSkipVerify set true."); assertThat(second.primaryLocation().inputComponent().key()).isEqualTo("module:main.go"); assertThat(second.primaryLocation().textRange().start().line()).isEqualTo(4); @@ -86,7 +131,7 @@ void issues_with_sonarqube() throws IOException { @Test - void import_check_style_report_same_source_different_key() throws IOException { + void shouldImportSameSourceDifferentKey() throws IOException { // Check that rules have different key based on the severity SensorContextTester context = ExternalLinterSensorHelper.createContext(); context.settings().setProperty("sonar.go.golangci-lint.reportPaths", REPORT_BASE_PATH.resolve("checkstyle-different-severity.xml").toString());