From: Léo Geoffroy Date: Thu, 10 Oct 2024 16:14:38 +0000 (+0200) Subject: SONAR-23250 handle impacts on active rules in scanner engine X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=e2cb22069d25733459a1d058be1a7c1f3ca370ef;p=sonarqube.git SONAR-23250 handle impacts on active rules in scanner engine --- diff --git a/gradle.properties b/gradle.properties index ed397c125b2..4167263696f 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,12 +1,11 @@ group=org.sonarsource.sonarqube version=10.8 - # End Of Life date for the version. MMF-3763. format is yyyy-MM-dd # 6 months from the release date for non LTA versions # 30 months from the release date for LTA versions # No change required for patch versions versionEOL=2025-05-27 -pluginApiVersion=10.11.0.2468 +pluginApiVersion=10.12.0.2507 description=Open source platform for continuous inspection of code quality projectTitle=SonarQube org.gradle.jvmargs=-Xmx2048m diff --git a/server/sonar-webserver-webapi/src/it/java/org/sonar/server/issue/ws/PullTaintActionIT.java b/server/sonar-webserver-webapi/src/it/java/org/sonar/server/issue/ws/PullTaintActionIT.java index 17cebf289af..8d1155f7108 100644 --- a/server/sonar-webserver-webapi/src/it/java/org/sonar/server/issue/ws/PullTaintActionIT.java +++ b/server/sonar-webserver-webapi/src/it/java/org/sonar/server/issue/ws/PullTaintActionIT.java @@ -32,6 +32,7 @@ import org.sonar.api.issue.impact.Severity; import org.sonar.api.issue.impact.SoftwareQuality; import org.sonar.api.resources.Qualifiers; import org.sonar.api.utils.System2; +import org.sonar.core.rule.ImpactFormatter; import org.sonar.db.DbTester; import org.sonar.db.component.ComponentDto; import org.sonar.db.component.ProjectData; @@ -48,7 +49,6 @@ import org.sonar.db.user.UserDto; import org.sonar.server.component.ComponentFinder; import org.sonar.server.exceptions.ForbiddenException; import org.sonar.server.exceptions.NotFoundException; -import org.sonar.server.issue.ImpactFormatter; import org.sonar.server.issue.TaintChecker; import org.sonar.server.issue.ws.pull.PullTaintActionProtobufObjectGenerator; import org.sonar.server.tester.UserSessionRule; diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/ImpactFormatter.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/ImpactFormatter.java deleted file mode 100644 index 3d018120b07..00000000000 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/ImpactFormatter.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2024 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.server.issue; - -import org.sonar.api.issue.impact.Severity; -import org.sonarqube.ws.Common; - -public class ImpactFormatter { - private ImpactFormatter() { - } - - public static Common.ImpactSeverity mapImpactSeverity(Severity severity) { - return switch (severity) { - case BLOCKER -> Common.ImpactSeverity.ImpactSeverity_BLOCKER; - case HIGH -> Common.ImpactSeverity.HIGH; - case MEDIUM -> Common.ImpactSeverity.MEDIUM; - case LOW -> Common.ImpactSeverity.LOW; - case INFO -> Common.ImpactSeverity.ImpactSeverity_INFO; - }; - } -} diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/ws/SearchResponseFormat.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/ws/SearchResponseFormat.java index b04eeee8b66..19309c3f288 100644 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/ws/SearchResponseFormat.java +++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/ws/SearchResponseFormat.java @@ -38,6 +38,7 @@ import org.sonar.api.utils.DateUtils; import org.sonar.api.utils.Duration; import org.sonar.api.utils.Durations; import org.sonar.api.utils.Paging; +import org.sonar.core.rule.ImpactFormatter; import org.sonar.db.component.BranchDto; import org.sonar.db.component.BranchType; import org.sonar.db.component.ComponentDto; @@ -49,7 +50,6 @@ import org.sonar.db.rule.RuleDto; import org.sonar.db.user.UserDto; import org.sonar.markdown.Markdown; import org.sonar.server.es.Facets; -import org.sonar.server.issue.ImpactFormatter; import org.sonar.server.issue.TextRangeResponseFormatter; import org.sonar.server.issue.index.IssueScope; import org.sonar.server.issue.workflow.Transition; diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/ws/pull/PullTaintActionProtobufObjectGenerator.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/ws/pull/PullTaintActionProtobufObjectGenerator.java index a31082b6693..ac19caea286 100644 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/ws/pull/PullTaintActionProtobufObjectGenerator.java +++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/issue/ws/pull/PullTaintActionProtobufObjectGenerator.java @@ -28,13 +28,13 @@ import java.util.Set; import java.util.stream.Collectors; import org.sonar.api.rules.CleanCodeAttribute; import org.sonar.api.server.ServerSide; +import org.sonar.core.rule.ImpactFormatter; import org.sonar.db.DbClient; import org.sonar.db.DbSession; import org.sonar.db.component.ComponentDto; import org.sonar.db.issue.IssueDto; import org.sonar.db.protobuf.DbIssues; import org.sonar.db.rule.RuleDto; -import org.sonar.server.issue.ImpactFormatter; import org.sonar.server.user.UserSession; import org.sonar.server.ws.MessageFormattingUtils; import org.sonarqube.ws.Common; diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/rule/ws/RuleMapper.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/rule/ws/RuleMapper.java index 5f695661ae8..a90216e1b55 100644 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/rule/ws/RuleMapper.java +++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/rule/ws/RuleMapper.java @@ -35,6 +35,7 @@ import org.sonar.api.rule.RuleKey; import org.sonar.api.rules.CleanCodeAttribute; import org.sonar.api.server.debt.DebtRemediationFunction; import org.sonar.api.server.debt.internal.DefaultDebtRemediationFunction; +import org.sonar.core.rule.ImpactFormatter; import org.sonar.db.issue.ImpactDto; import org.sonar.db.rule.DeprecatedRuleKeyDto; import org.sonar.db.rule.RuleDescriptionSectionContextDto; @@ -45,7 +46,6 @@ import org.sonar.db.rule.RuleParamDto; import org.sonar.db.user.UserDto; import org.sonar.markdown.Markdown; import org.sonar.server.common.text.MacroInterpreter; -import org.sonar.server.issue.ImpactFormatter; import org.sonar.server.rule.RuleDescriptionFormatter; import org.sonar.server.rule.ws.RulesResponseFormatter.SearchResult; import org.sonarqube.ws.Common; diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/rule/ws/RulesResponseFormatter.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/rule/ws/RulesResponseFormatter.java index 4da000f5943..7c20a8cf616 100644 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/rule/ws/RulesResponseFormatter.java +++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/rule/ws/RulesResponseFormatter.java @@ -42,6 +42,7 @@ import org.sonar.api.resources.Languages; import org.sonar.api.rule.RuleKey; import org.sonar.api.server.ServerSide; import org.sonar.api.utils.DateUtils; +import org.sonar.core.rule.ImpactFormatter; import org.sonar.core.util.stream.MoreCollectors; import org.sonar.db.DbClient; import org.sonar.db.DbSession; @@ -55,7 +56,6 @@ import org.sonar.db.rule.RuleDto; import org.sonar.db.rule.RuleParamDto; import org.sonar.db.user.UserDto; import org.sonar.server.es.Facets; -import org.sonar.server.issue.ImpactFormatter; import org.sonar.server.qualityprofile.ActiveRuleInheritance; import org.sonarqube.ws.Common; import org.sonarqube.ws.Rules; @@ -256,10 +256,9 @@ public class RulesResponseFormatter { public static Rules.Impacts mapImpacts(Map impacts) { Rules.Impacts.Builder impactsBuilder = Rules.Impacts.newBuilder(); - impacts.forEach((quality, severity) -> - impactsBuilder.addImpacts(Common.Impact.newBuilder() - .setSoftwareQuality(Common.SoftwareQuality.valueOf(quality.name())) - .setSeverity(ImpactFormatter.mapImpactSeverity(severity)))); + impacts.forEach((quality, severity) -> impactsBuilder.addImpacts(Common.Impact.newBuilder() + .setSoftwareQuality(Common.SoftwareQuality.valueOf(quality.name())) + .setSeverity(ImpactFormatter.mapImpactSeverity(severity)))); return impactsBuilder.build(); } diff --git a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/issue/ImpactFormatterTest.java b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/issue/ImpactFormatterTest.java deleted file mode 100644 index 3a801c4e6cb..00000000000 --- a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/issue/ImpactFormatterTest.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2024 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.server.issue; - -import org.junit.jupiter.api.Test; -import org.sonar.api.issue.impact.Severity; -import org.sonarqube.ws.Common; - -import static org.junit.jupiter.api.Assertions.assertEquals; - -class ImpactFormatterTest { - - @Test - void mapImpactSeverity_shouldReturnExpectedValue() { - assertEquals(Common.ImpactSeverity.ImpactSeverity_BLOCKER, ImpactFormatter.mapImpactSeverity(Severity.BLOCKER)); - assertEquals(Common.ImpactSeverity.HIGH, ImpactFormatter.mapImpactSeverity(Severity.HIGH)); - assertEquals(Common.ImpactSeverity.MEDIUM, ImpactFormatter.mapImpactSeverity(Severity.MEDIUM)); - assertEquals(Common.ImpactSeverity.LOW, ImpactFormatter.mapImpactSeverity(Severity.LOW)); - assertEquals(Common.ImpactSeverity.ImpactSeverity_INFO, ImpactFormatter.mapImpactSeverity(Severity.INFO)); - } -} diff --git a/sonar-core/src/main/java/org/sonar/core/rule/ImpactFormatter.java b/sonar-core/src/main/java/org/sonar/core/rule/ImpactFormatter.java new file mode 100644 index 00000000000..73120079f0f --- /dev/null +++ b/sonar-core/src/main/java/org/sonar/core/rule/ImpactFormatter.java @@ -0,0 +1,55 @@ +/* + * SonarQube + * Copyright (C) 2009-2024 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.core.rule; + +import org.sonar.api.issue.impact.Severity; +import org.sonarqube.ws.Common; + +import static org.sonar.api.issue.impact.Severity.BLOCKER; +import static org.sonar.api.issue.impact.Severity.HIGH; +import static org.sonar.api.issue.impact.Severity.INFO; +import static org.sonar.api.issue.impact.Severity.LOW; +import static org.sonar.api.issue.impact.Severity.MEDIUM; + +public class ImpactFormatter { + private ImpactFormatter() { + } + + public static Common.ImpactSeverity mapImpactSeverity(Severity severity) { + return switch (severity) { + case BLOCKER -> Common.ImpactSeverity.ImpactSeverity_BLOCKER; + case HIGH -> Common.ImpactSeverity.HIGH; + case MEDIUM -> Common.ImpactSeverity.MEDIUM; + case LOW -> Common.ImpactSeverity.LOW; + case INFO -> Common.ImpactSeverity.ImpactSeverity_INFO; + }; + } + + public static Severity mapImpactSeverity(Common.ImpactSeverity severity) { + return switch (severity) { + case ImpactSeverity_BLOCKER -> BLOCKER; + case HIGH -> HIGH; + case MEDIUM -> MEDIUM; + case LOW -> LOW; + case ImpactSeverity_INFO -> INFO; + case UNKNOWN_IMPACT_SEVERITY -> throw new UnsupportedOperationException("Impact severity not supported"); + }; + } +} diff --git a/sonar-core/src/test/java/org/sonar/core/rule/ImpactFormatterTest.java b/sonar-core/src/test/java/org/sonar/core/rule/ImpactFormatterTest.java new file mode 100644 index 00000000000..6883ef15ae2 --- /dev/null +++ b/sonar-core/src/test/java/org/sonar/core/rule/ImpactFormatterTest.java @@ -0,0 +1,50 @@ +/* + * SonarQube + * Copyright (C) 2009-2024 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.core.rule; + +import org.junit.jupiter.api.Test; +import org.sonar.api.issue.impact.Severity; +import org.sonarqube.ws.Common; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +class ImpactFormatterTest { + + @Test + void mapImpactSeverity_whenMappingStandardSeverity_shouldReturnExpectedValue() { + assertThat(ImpactFormatter.mapImpactSeverity(Severity.BLOCKER)).isEqualTo(Common.ImpactSeverity.ImpactSeverity_BLOCKER); + assertThat(ImpactFormatter.mapImpactSeverity(Severity.HIGH)).isEqualTo(Common.ImpactSeverity.HIGH); + assertThat(ImpactFormatter.mapImpactSeverity(Severity.MEDIUM)).isEqualTo(Common.ImpactSeverity.MEDIUM); + assertThat(ImpactFormatter.mapImpactSeverity(Severity.LOW)).isEqualTo(Common.ImpactSeverity.LOW); + assertThat(ImpactFormatter.mapImpactSeverity(Severity.INFO)).isEqualTo(Common.ImpactSeverity.ImpactSeverity_INFO); + } + + @Test + void mapImpactSeverity_whenMappingProtobufSeverity_shouldReturnExpectedValue() { + assertThat(ImpactFormatter.mapImpactSeverity(Common.ImpactSeverity.ImpactSeverity_BLOCKER)).isEqualTo(Severity.BLOCKER); + assertThat(ImpactFormatter.mapImpactSeverity(Common.ImpactSeverity.HIGH)).isEqualTo(Severity.HIGH); + assertThat(ImpactFormatter.mapImpactSeverity(Common.ImpactSeverity.MEDIUM)).isEqualTo(Severity.MEDIUM); + assertThat(ImpactFormatter.mapImpactSeverity(Common.ImpactSeverity.LOW)).isEqualTo(Severity.LOW); + assertThat(ImpactFormatter.mapImpactSeverity(Common.ImpactSeverity.ImpactSeverity_INFO)).isEqualTo(Severity.INFO); + assertThatThrownBy(() -> ImpactFormatter.mapImpactSeverity(Common.ImpactSeverity.UNKNOWN_IMPACT_SEVERITY)) + .isInstanceOf(UnsupportedOperationException.class); + } +} diff --git a/sonar-plugin-api-impl/src/main/java/org/sonar/api/batch/rule/internal/DefaultActiveRule.java b/sonar-plugin-api-impl/src/main/java/org/sonar/api/batch/rule/internal/DefaultActiveRule.java index e0af1b32898..e893c7842c7 100644 --- a/sonar-plugin-api-impl/src/main/java/org/sonar/api/batch/rule/internal/DefaultActiveRule.java +++ b/sonar-plugin-api-impl/src/main/java/org/sonar/api/batch/rule/internal/DefaultActiveRule.java @@ -19,13 +19,12 @@ */ package org.sonar.api.batch.rule.internal; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; import java.util.Map; import java.util.Set; import javax.annotation.concurrent.Immutable; import org.sonar.api.batch.rule.ActiveRule; +import org.sonar.api.issue.impact.Severity; +import org.sonar.api.issue.impact.SoftwareQuality; import org.sonar.api.rule.RuleKey; @Immutable @@ -36,6 +35,7 @@ public class DefaultActiveRule implements ActiveRule { private final String language; private final String templateRuleKey; private final Map params; + private final Map impacts; private final long createdAt; private final long updatedAt; private final String qProfileKey; @@ -46,12 +46,13 @@ public class DefaultActiveRule implements ActiveRule { this.internalKey = newActiveRule.internalKey; this.templateRuleKey = newActiveRule.templateRuleKey; this.ruleKey = newActiveRule.ruleKey; - this.params = Collections.unmodifiableMap(new HashMap<>(newActiveRule.params)); + this.params = Map.copyOf(newActiveRule.params); + this.impacts = Map.copyOf(newActiveRule.impacts); this.language = newActiveRule.language; this.createdAt = newActiveRule.createdAt; this.updatedAt = newActiveRule.updatedAt; this.qProfileKey = newActiveRule.qProfileKey; - this.deprecatedKeys = Collections.unmodifiableSet(new HashSet<>(newActiveRule.deprecatedKeys)); + this.deprecatedKeys = Set.copyOf(newActiveRule.deprecatedKeys); } @Override @@ -64,6 +65,11 @@ public class DefaultActiveRule implements ActiveRule { return severity; } + @Override + public Map impacts() { + return impacts; + } + @Override public String language() { return language; diff --git a/sonar-plugin-api-impl/src/main/java/org/sonar/api/batch/rule/internal/NewActiveRule.java b/sonar-plugin-api-impl/src/main/java/org/sonar/api/batch/rule/internal/NewActiveRule.java index 9afdf89f9c9..cf21a1b39cd 100644 --- a/sonar-plugin-api-impl/src/main/java/org/sonar/api/batch/rule/internal/NewActiveRule.java +++ b/sonar-plugin-api-impl/src/main/java/org/sonar/api/batch/rule/internal/NewActiveRule.java @@ -19,6 +19,7 @@ */ package org.sonar.api.batch.rule.internal; +import java.util.EnumMap; import java.util.HashMap; import java.util.HashSet; import java.util.Map; @@ -26,6 +27,7 @@ import java.util.Set; import javax.annotation.Nullable; import javax.annotation.concurrent.Immutable; import org.apache.commons.lang3.StringUtils; +import org.sonar.api.issue.impact.SoftwareQuality; import org.sonar.api.rule.RuleKey; import org.sonar.api.rule.Severity; @@ -37,6 +39,7 @@ public class NewActiveRule { final RuleKey ruleKey; final String name; final String severity; + final Map impacts; final Map params; final long createdAt; final long updatedAt; @@ -50,6 +53,7 @@ public class NewActiveRule { this.ruleKey = builder.ruleKey; this.name = builder.name; this.severity = builder.severity; + this.impacts = builder.impacts; this.params = builder.params; this.createdAt = builder.createdAt; this.updatedAt = builder.updatedAt; @@ -68,6 +72,7 @@ public class NewActiveRule { private RuleKey ruleKey; private String name; private String severity = Severity.defaultSeverity(); + private final Map impacts = new EnumMap<>(SoftwareQuality.class); private Map params = new HashMap<>(); private long createdAt; private long updatedAt; @@ -92,6 +97,11 @@ public class NewActiveRule { return this; } + public Builder setImpact(SoftwareQuality softwareQuality, org.sonar.api.issue.impact.Severity severity) { + impacts.put(softwareQuality, severity); + return this; + } + public Builder setParam(String key, @Nullable String value) { // possible improvement : check that the param key exists in rule definition if (value == null) { diff --git a/sonar-plugin-api-impl/src/test/java/org/sonar/api/batch/rule/internal/NewActiveRuleTest.java b/sonar-plugin-api-impl/src/test/java/org/sonar/api/batch/rule/internal/NewActiveRuleTest.java index 51d6facef11..449b7a35b40 100644 --- a/sonar-plugin-api-impl/src/test/java/org/sonar/api/batch/rule/internal/NewActiveRuleTest.java +++ b/sonar-plugin-api-impl/src/test/java/org/sonar/api/batch/rule/internal/NewActiveRuleTest.java @@ -20,9 +20,10 @@ package org.sonar.api.batch.rule.internal; import com.google.common.collect.ImmutableMap; +import java.util.Map; import org.junit.Before; import org.junit.Test; -import org.sonar.api.batch.rule.internal.NewActiveRule; +import org.sonar.api.issue.impact.SoftwareQuality; import org.sonar.api.rule.RuleKey; import org.sonar.api.rule.Severity; @@ -43,6 +44,7 @@ public class NewActiveRuleTest { .setRuleKey(RuleKey.of("foo", "bar")) .setName("name") .setSeverity(org.sonar.api.rule.Severity.CRITICAL) + .setImpact(SoftwareQuality.MAINTAINABILITY, org.sonar.api.issue.impact.Severity.MEDIUM) .setParam("key", "value") .setCreatedAt(1_000L) .setUpdatedAt(1_000L) @@ -55,6 +57,7 @@ public class NewActiveRuleTest { assertThat(rule.ruleKey).isEqualTo(RuleKey.of("foo", "bar")); assertThat(rule.name).isEqualTo("name"); assertThat(rule.severity).isEqualTo(org.sonar.api.rule.Severity.CRITICAL); + assertThat(rule.impacts).containsExactlyInAnyOrderEntriesOf(Map.of(SoftwareQuality.MAINTAINABILITY, org.sonar.api.issue.impact.Severity.MEDIUM)); assertThat(rule.params).isEqualTo(ImmutableMap.of("key", "value")); assertThat(rule.createdAt).isEqualTo(1_000L); assertThat(rule.updatedAt).isEqualTo(1_000L); diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/issue/IssuePublisher.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/issue/IssuePublisher.java index d09209bb2c7..53d04e5faa5 100644 --- a/sonar-scanner-engine/src/main/java/org/sonar/scanner/issue/IssuePublisher.java +++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/issue/IssuePublisher.java @@ -21,6 +21,7 @@ package org.sonar.scanner.issue; import java.util.ArrayList; import java.util.Collection; +import java.util.EnumMap; import java.util.List; import java.util.Map; import java.util.function.Consumer; @@ -77,7 +78,7 @@ public class IssuePublisher { return false; } - ScannerReport.Issue rawIssue = createReportIssue(issue, inputComponent.scannerId(), activeRule.severity()); + ScannerReport.Issue rawIssue = createReportIssue(issue, inputComponent.scannerId(), activeRule.severity(), activeRule.impacts()); if (filters.accept(inputComponent, rawIssue)) { write(inputComponent.scannerId(), rawIssue); @@ -107,7 +108,8 @@ public class IssuePublisher { return str; } - private static ScannerReport.Issue createReportIssue(Issue issue, int componentRef, String activeRuleSeverity) { + private static ScannerReport.Issue createReportIssue(Issue issue, int componentRef, String activeRuleSeverity, + Map activeRuleImpacts) { String primaryMessage = nullToEmpty(issue.primaryLocation().message()); org.sonar.api.batch.rule.Severity overriddenSeverity = issue.overriddenSeverity(); Severity severity = overriddenSeverity != null ? Severity.valueOf(overriddenSeverity.name()) : Severity.valueOf(activeRuleSeverity); @@ -121,7 +123,10 @@ public class IssuePublisher { builder.setRuleKey(issue.ruleKey().rule()); builder.setMsg(primaryMessage); builder.addAllMsgFormatting(toProtobufMessageFormattings(issue.primaryLocation().messageFormattings())); - builder.addAllOverridenImpacts(toProtobufImpacts(issue.overridenImpacts())); + Map overriddenImpacts = new EnumMap<>(issue.overridenImpacts()); + activeRuleImpacts.entrySet().forEach(e -> overriddenImpacts.putIfAbsent(e.getKey(), e.getValue())); + builder.addAllOverridenImpacts(toProtobufImpacts(overriddenImpacts)); + locationBuilder.setMsg(primaryMessage); locationBuilder.addAllMsgFormatting(toProtobufMessageFormattings(issue.primaryLocation().messageFormattings())); diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/rule/ActiveRulesProvider.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/rule/ActiveRulesProvider.java index 4028fbbcd9a..8e0691a069e 100644 --- a/sonar-scanner-engine/src/main/java/org/sonar/scanner/rule/ActiveRulesProvider.java +++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/rule/ActiveRulesProvider.java @@ -29,6 +29,8 @@ import org.sonar.api.batch.rule.LoadedActiveRule; import org.sonar.api.batch.rule.internal.ActiveRulesBuilder; import org.sonar.api.batch.rule.internal.DefaultActiveRules; import org.sonar.api.batch.rule.internal.NewActiveRule; +import org.sonar.api.issue.impact.Severity; +import org.sonar.api.issue.impact.SoftwareQuality; import org.sonar.api.rule.RuleKey; import org.sonar.api.utils.log.Logger; import org.sonar.api.utils.log.Loggers; @@ -91,6 +93,12 @@ public class ActiveRulesProvider { } } + if (activeRule.getImpacts() != null) { + for (Map.Entry impact : activeRule.getImpacts().entrySet()) { + builder.setImpact(impact.getKey(), impact.getValue()); + } + } + return builder.build(); } diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/rule/DefaultActiveRulesLoader.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/rule/DefaultActiveRulesLoader.java index 1bc34c0198c..9c2bb5d86d1 100644 --- a/sonar-scanner-engine/src/main/java/org/sonar/scanner/rule/DefaultActiveRulesLoader.java +++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/rule/DefaultActiveRulesLoader.java @@ -21,6 +21,7 @@ package org.sonar.scanner.rule; import java.io.IOException; import java.io.InputStream; +import java.util.EnumMap; import java.util.HashMap; import java.util.LinkedList; import java.util.List; @@ -29,9 +30,13 @@ import java.util.stream.Collectors; import org.apache.commons.io.IOUtils; import org.sonar.api.batch.rule.LoadedActiveRule; import org.sonar.api.impl.utils.ScannerUtils; +import org.sonar.api.issue.impact.Severity; +import org.sonar.api.issue.impact.SoftwareQuality; import org.sonar.api.rule.RuleKey; import org.sonar.api.utils.DateUtils; +import org.sonar.core.rule.ImpactFormatter; import org.sonar.scanner.http.ScannerWsClient; +import org.sonarqube.ws.Common; import org.sonarqube.ws.Common.Paging; import org.sonarqube.ws.Rules; import org.sonarqube.ws.Rules.Active; @@ -109,6 +114,7 @@ public class DefaultActiveRulesLoader implements ActiveRulesLoader { loadedRule.setRuleKey(RuleKey.parse(r.getKey())); loadedRule.setName(r.getName()); loadedRule.setSeverity(active.getSeverity()); + loadedRule.setCreatedAt(DateUtils.dateToLong(DateUtils.parseDateTime(active.getCreatedAt()))); loadedRule.setUpdatedAt(DateUtils.dateToLong(DateUtils.parseDateTime(active.getUpdatedAt()))); loadedRule.setLanguage(r.getLang()); @@ -128,7 +134,15 @@ public class DefaultActiveRulesLoader implements ActiveRulesLoader { for (Param param : active.getParamsList()) { params.put(param.getKey(), param.getValue()); } + loadedRule.setParams(params); + + Map impacts = new EnumMap<>(SoftwareQuality.class); + for (Common.Impact impact : active.getImpacts().getImpactsList()) { + impacts.put(SoftwareQuality.valueOf(impact.getSoftwareQuality().name()), ImpactFormatter.mapImpactSeverity(impact.getSeverity())); + } + loadedRule.setImpacts(impacts); + loadedRule.setDeprecatedKeys(r.getDeprecatedKeys().getDeprecatedKeyList() .stream() .map(RuleKey::parse) @@ -138,4 +152,5 @@ public class DefaultActiveRulesLoader implements ActiveRulesLoader { return loadedRules; } + } diff --git a/sonar-scanner-engine/src/test/java/org/sonar/scanner/issue/IssuePublisherTest.java b/sonar-scanner-engine/src/test/java/org/sonar/scanner/issue/IssuePublisherTest.java index 5a58ff47540..0c4b12443ec 100644 --- a/sonar-scanner-engine/src/test/java/org/sonar/scanner/issue/IssuePublisherTest.java +++ b/sonar-scanner-engine/src/test/java/org/sonar/scanner/issue/IssuePublisherTest.java @@ -62,6 +62,7 @@ import static org.mockito.Mockito.when; import static org.sonar.api.batch.sensor.issue.MessageFormatting.Type.CODE; import static org.sonar.api.issue.impact.SoftwareQuality.MAINTAINABILITY; import static org.sonar.api.issue.impact.SoftwareQuality.RELIABILITY; +import static org.sonar.api.issue.impact.SoftwareQuality.SECURITY; @RunWith(MockitoJUnitRunner.class) public class IssuePublisherTest { @@ -149,7 +150,7 @@ public class IssuePublisherTest { ScannerReport.Impact impact1 = ScannerReport.Impact.newBuilder().setSoftwareQuality(MAINTAINABILITY.name()).setSeverity("HIGH").build(); ScannerReport.Impact impact2 = ScannerReport.Impact.newBuilder().setSoftwareQuality(RELIABILITY.name()).setSeverity("LOW").build(); - assertThat(argument.getValue().getOverridenImpactsList()).containsExactly(impact1, impact2); + assertThat(argument.getValue().getOverridenImpactsList()).containsExactlyInAnyOrder(impact1, impact2); } @Test @@ -243,6 +244,58 @@ public class IssuePublisherTest { ArgumentCaptor argument = ArgumentCaptor.forClass(ScannerReport.Issue.class); verify(reportPublisher.getWriter()).appendComponentIssue(eq(file.scannerId()), argument.capture()); assertThat(argument.getValue().getSeverity()).isEqualTo(org.sonar.scanner.protocol.Constants.Severity.INFO); + assertThat(argument.getValue().getOverridenImpactsList()).isEmpty(); + } + + @Test + public void initAndAddIssue_whenImpactsOverriddenOnActiveRule_shouldOverrideIssue() { + activeRulesBuilder.addRule(new NewActiveRule.Builder() + .setRuleKey(NOSONAR_RULE_KEY) + .setSeverity(Severity.INFO) + .setImpact(MAINTAINABILITY, org.sonar.api.issue.impact.Severity.HIGH) + .setQProfileKey("qp-1") + .build()); + initModuleIssues(); + + DefaultIssue issue = new DefaultIssue(project) + .at(new DefaultIssueLocation().on(file).at(file.selectLine(3)).message("Foo")) + .forRule(NOSONAR_RULE_KEY); + when(filters.accept(any(InputComponent.class), any(ScannerReport.Issue.class))).thenReturn(true); + moduleIssues.initAndAddIssue(issue); + + ArgumentCaptor argument = ArgumentCaptor.forClass(ScannerReport.Issue.class); + verify(reportPublisher.getWriter()).appendComponentIssue(eq(file.scannerId()), argument.capture()); + assertThat(argument.getValue().getSeverity()).isEqualTo(org.sonar.scanner.protocol.Constants.Severity.INFO); + assertThat(argument.getValue().getOverridenImpactsList()).extracting(ScannerReport.Impact::getSoftwareQuality, ScannerReport.Impact::getSeverity) + .containsExactly(tuple(MAINTAINABILITY.name(), org.sonar.api.issue.impact.Severity.HIGH.name())); + } + + @Test + public void initAndAddIssue_whenImpactsOverriddenOnActiveRuleAndInIssue_shouldCombineOverriddenImpacts() { + activeRulesBuilder.addRule(new NewActiveRule.Builder() + .setRuleKey(NOSONAR_RULE_KEY) + .setSeverity(Severity.INFO) + .setImpact(MAINTAINABILITY, org.sonar.api.issue.impact.Severity.HIGH) + .setImpact(SECURITY, org.sonar.api.issue.impact.Severity.INFO) + .setQProfileKey("qp-1") + .build()); + initModuleIssues(); + + DefaultIssue issue = new DefaultIssue(project) + .at(new DefaultIssueLocation().on(file).at(file.selectLine(3)).message("Foo")) + .overrideImpact(RELIABILITY, org.sonar.api.issue.impact.Severity.MEDIUM) + .overrideImpact(MAINTAINABILITY, org.sonar.api.issue.impact.Severity.LOW) + .forRule(NOSONAR_RULE_KEY); + when(filters.accept(any(InputComponent.class), any(ScannerReport.Issue.class))).thenReturn(true); + moduleIssues.initAndAddIssue(issue); + + ArgumentCaptor argument = ArgumentCaptor.forClass(ScannerReport.Issue.class); + verify(reportPublisher.getWriter()).appendComponentIssue(eq(file.scannerId()), argument.capture()); + assertThat(argument.getValue().getSeverity()).isEqualTo(org.sonar.scanner.protocol.Constants.Severity.INFO); + assertThat(argument.getValue().getOverridenImpactsList()).extracting(ScannerReport.Impact::getSoftwareQuality, ScannerReport.Impact::getSeverity) + .containsExactlyInAnyOrder(tuple(MAINTAINABILITY.name(), org.sonar.api.issue.impact.Severity.LOW.name()), + tuple(RELIABILITY.name(), org.sonar.api.issue.impact.Severity.MEDIUM.name()), + tuple(SECURITY.name(), org.sonar.api.issue.impact.Severity.INFO.name())); } @Test diff --git a/sonar-scanner-engine/src/test/java/org/sonar/scanner/rule/ActiveRulesProviderTest.java b/sonar-scanner-engine/src/test/java/org/sonar/scanner/rule/ActiveRulesProviderTest.java index 32611fb0cf2..14a5036e254 100644 --- a/sonar-scanner-engine/src/test/java/org/sonar/scanner/rule/ActiveRulesProviderTest.java +++ b/sonar-scanner-engine/src/test/java/org/sonar/scanner/rule/ActiveRulesProviderTest.java @@ -25,11 +25,16 @@ import com.google.common.collect.ImmutableSet; import java.util.Date; import java.util.LinkedList; import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; import org.assertj.core.groups.Tuple; import org.junit.Test; +import org.sonar.api.batch.rule.ActiveRule; import org.sonar.api.batch.rule.ActiveRules; import org.sonar.api.batch.rule.LoadedActiveRule; import org.sonar.api.batch.rule.internal.DefaultActiveRules; +import org.sonar.api.issue.impact.Severity; +import org.sonar.api.issue.impact.SoftwareQuality; import org.sonar.api.rule.RuleKey; import org.sonar.api.utils.DateUtils; import org.sonarqube.ws.Qualityprofiles.SearchWsResponse.QualityProfile; @@ -50,6 +55,8 @@ public class ActiveRulesProviderTest { LoadedActiveRule r2 = mockRule("rule2"); LoadedActiveRule r3 = mockRule("rule3"); + r1.setImpacts(Map.of(SoftwareQuality.MAINTAINABILITY, Severity.HIGH)); + List qp1Rules = ImmutableList.of(r1, r2); List qp2Rules = ImmutableList.of(r2, r3); List qp3Rules = ImmutableList.of(r1, r3); @@ -65,6 +72,13 @@ public class ActiveRulesProviderTest { assertThat(activeRules.findAll()).extracting("ruleKey").containsOnly( RuleKey.of("rule1", "rule1"), RuleKey.of("rule2", "rule2"), RuleKey.of("rule3", "rule3")); + Map activeRuleByKey = activeRules.findAll().stream().collect(Collectors.toMap(e -> e.ruleKey().rule(), e -> e)); + assertThat(activeRuleByKey.get("rule1").impacts()) + .containsExactlyInAnyOrderEntriesOf(Map.of(SoftwareQuality.MAINTAINABILITY, Severity.HIGH)); + + assertThat(activeRuleByKey.get("rule2").impacts()).isEmpty(); + assertThat(activeRuleByKey.get("rule3").impacts()).isEmpty(); + verify(loader).load("qp1"); verify(loader).load("qp2"); verify(loader).load("qp3"); diff --git a/sonar-scanner-engine/src/test/java/org/sonar/scanner/rule/DefaultActiveRulesLoaderTest.java b/sonar-scanner-engine/src/test/java/org/sonar/scanner/rule/DefaultActiveRulesLoaderTest.java index bae17ee866d..ad70dd74d47 100644 --- a/sonar-scanner-engine/src/test/java/org/sonar/scanner/rule/DefaultActiveRulesLoaderTest.java +++ b/sonar-scanner-engine/src/test/java/org/sonar/scanner/rule/DefaultActiveRulesLoaderTest.java @@ -27,6 +27,7 @@ import java.util.stream.IntStream; import org.junit.Before; import org.junit.Test; import org.sonar.api.batch.rule.LoadedActiveRule; +import org.sonar.api.issue.impact.SoftwareQuality; import org.sonar.api.rule.RuleKey; import org.sonar.api.rule.Severity; import org.sonar.scanner.WsTestUtil; @@ -82,6 +83,10 @@ public class DefaultActiveRulesLoaderTest { .filteredOn(r -> r.getRuleKey().equals(EXAMPLE_KEY)) .extracting(LoadedActiveRule::getSeverity) .containsExactly(SEVERITY_VALUE); + assertThat(activeRules) + .filteredOn(r -> r.getRuleKey().equals(EXAMPLE_KEY)) + .extracting(LoadedActiveRule::getImpacts) + .containsExactlyInAnyOrder(Map.of(SoftwareQuality.MAINTAINABILITY, org.sonar.api.issue.impact.Severity.HIGH)); WsTestUtil.verifyCall(wsClient, urlOfPage(1)); WsTestUtil.verifyCall(wsClient, urlOfPage(2)); @@ -119,6 +124,9 @@ public class DefaultActiveRulesLoaderTest { if (EXAMPLE_KEY.equals(key)) { activeBuilder.addParams(Rules.Active.Param.newBuilder().setKey(FORMAT_KEY).setValue(FORMAT_VALUE)); activeBuilder.setSeverity(SEVERITY_VALUE); + activeBuilder.setImpacts(Rules.Impacts.newBuilder().addImpacts(Common.Impact.newBuilder() + .setSoftwareQuality(Common.SoftwareQuality.MAINTAINABILITY) + .setSeverity(Common.ImpactSeverity.HIGH).build()).build()); } ActiveList activeList = Rules.ActiveList.newBuilder().addActiveList(activeBuilder).build(); actives.putAllActives(Map.of(key.toString(), activeList));