diff options
author | Julien HENRY <julien.henry@sonarsource.com> | 2024-12-18 11:00:05 +0100 |
---|---|---|
committer | Steve Marion <steve.marion@sonarsource.com> | 2024-12-18 11:13:25 +0100 |
commit | fb42f37f9e0f25420d67211690aa9bd4273faa08 (patch) | |
tree | 7a3a614cb7fb22f9f2f2dd77987b461ce09bd462 | |
parent | d3bba946fe854e126d713fce6dcfbcaa61b6eedd (diff) | |
download | sonarqube-fb42f37f9e0f25420d67211690aa9bd4273faa08.tar.gz sonarqube-fb42f37f9e0f25420d67211690aa9bd4273faa08.zip |
SONAR-23974 Optimize the scanner report
26 files changed, 401 insertions, 206 deletions
diff --git a/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/issue/AdHocRuleCreatorIT.java b/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/issue/AdHocRuleCreatorIT.java index dcd42febcb9..41ea7e82137 100644 --- a/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/issue/AdHocRuleCreatorIT.java +++ b/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/issue/AdHocRuleCreatorIT.java @@ -86,8 +86,8 @@ public class AdHocRuleCreatorIT { .setSeverity(Constants.Severity.BLOCKER) .setType(ScannerReport.IssueType.BUG) .setCleanCodeAttribute(CleanCodeAttribute.DISTINCT.name()) - .addDefaultImpacts(ScannerReport.Impact.newBuilder().setSoftwareQuality(SoftwareQuality.RELIABILITY.name()) - .setSeverity(org.sonar.api.issue.impact.Severity.LOW.name()).build()) + .addDefaultImpacts(ScannerReport.Impact.newBuilder().setSoftwareQuality(ScannerReport.SoftwareQuality.RELIABILITY) + .setSeverity(ScannerReport.ImpactSeverity.ImpactSeverity_LOW).build()) .build()); RuleDto rule = underTest.persistAndIndex(dbSession, addHocRule); @@ -178,8 +178,8 @@ public class AdHocRuleCreatorIT { .setDescription(rule.getAdHocDescription()) .setSeverity(Constants.Severity.valueOf(rule.getAdHocSeverity())) .setType(ScannerReport.IssueType.forNumber(rule.getAdHocType())) - .addDefaultImpacts(ScannerReport.Impact.newBuilder().setSoftwareQuality(SoftwareQuality.MAINTAINABILITY.name()) - .setSeverity(org.sonar.api.issue.impact.Severity.HIGH.name()).build()) + .addDefaultImpacts(ScannerReport.Impact.newBuilder().setSoftwareQuality(ScannerReport.SoftwareQuality.MAINTAINABILITY) + .setSeverity(ScannerReport.ImpactSeverity.ImpactSeverity_HIGH).build()) .setCleanCodeAttribute(CleanCodeAttribute.CLEAR.name()) .build())); diff --git a/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/issue/IntegrateIssuesVisitorIT.java b/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/issue/IntegrateIssuesVisitorIT.java index 7f87fe8baa7..5a78470a1ee 100644 --- a/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/issue/IntegrateIssuesVisitorIT.java +++ b/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/issue/IntegrateIssuesVisitorIT.java @@ -170,6 +170,7 @@ public class IntegrateIssuesVisitorIT { @Test public void process_new_issue() { + // Active rule has severity major ruleRepositoryRule.add(RuleTesting.XOO_X1); when(analysisMetadataHolder.isBranch()).thenReturn(true); ScannerReport.Issue reportIssue = getReportIssue(RuleTesting.XOO_X1); @@ -180,32 +181,32 @@ public class IntegrateIssuesVisitorIT { List<DefaultIssue> issues = newArrayList(protoIssueCache.traverse()); assertThat(issues).hasSize(1); assertThat(issues.get(0).codeVariants()).containsExactlyInAnyOrder("foo", "bar"); + assertThat(issues.get(0).severity()).isEqualTo(Severity.MAJOR); } @Test - public void process_existing_issue() { - RuleKey ruleKey = RuleTesting.XOO_X1; - // Issue from db has severity major - addBaseIssue(ruleKey); - - // Issue from report has severity blocker - ScannerReport.Issue reportIssue = getReportIssue(ruleKey); + public void process_new_issue_with_overridden_severity() { + // Active rule has severity major + ruleRepositoryRule.add(RuleTesting.XOO_X1); + when(analysisMetadataHolder.isBranch()).thenReturn(true); + ScannerReport.Issue reportIssue = ScannerReport.Issue.newBuilder(getReportIssue(RuleTesting.XOO_X1)).setOverriddenSeverity(Constants.Severity.BLOCKER).build(); reportReader.putIssues(FILE_REF, singletonList(reportIssue)); underTest.visitAny(FILE); List<DefaultIssue> issues = newArrayList(protoIssueCache.traverse()); assertThat(issues).hasSize(1); + assertThat(issues.get(0).codeVariants()).containsExactlyInAnyOrder("foo", "bar"); assertThat(issues.get(0).severity()).isEqualTo(Severity.BLOCKER); } @Test - public void dont_cache_existing_issue_if_unmodified() { + public void process_existing_issue() { + // Active rule has severity major RuleKey ruleKey = RuleTesting.XOO_X1; - // Issue from db has severity major + // Issue from db has severity minor addBaseIssue(ruleKey); - // Issue from report has severity blocker ScannerReport.Issue reportIssue = getReportIssue(ruleKey); reportReader.putIssues(FILE_REF, singletonList(reportIssue)); @@ -213,7 +214,7 @@ public class IntegrateIssuesVisitorIT { List<DefaultIssue> issues = newArrayList(protoIssueCache.traverse()); assertThat(issues).hasSize(1); - assertThat(issues.get(0).severity()).isEqualTo(Severity.BLOCKER); + assertThat(issues.get(0).severity()).isEqualTo(Severity.MAJOR); } @Test @@ -267,7 +268,6 @@ public class IntegrateIssuesVisitorIT { @Test public void reuse_issues_when_data_unchanged() { RuleKey ruleKey = RuleTesting.XOO_X1; - // Issue from db has severity major addBaseIssue(ruleKey); // Issue from report has severity blocker @@ -298,11 +298,11 @@ public class IntegrateIssuesVisitorIT { when(branch.getType()).thenReturn(BranchType.BRANCH); when(analysisMetadataHolder.getBranch()).thenReturn(branch); + // Active rule has severity major RuleKey ruleKey = RuleTesting.XOO_X1; - // Issue from main branch has severity major + // Issue from main branch has severity minor addBaseIssueOnBranch(ruleKey); - // Issue from report has severity blocker ScannerReport.Issue reportIssue = getReportIssue(ruleKey); reportReader.putIssues(FILE_REF, singletonList(reportIssue)); @@ -310,7 +310,7 @@ public class IntegrateIssuesVisitorIT { List<DefaultIssue> issues = newArrayList(protoIssueCache.traverse()); assertThat(issues).hasSize(1); - assertThat(issues.get(0).severity()).isEqualTo(Severity.BLOCKER); + assertThat(issues.get(0).severity()).isEqualTo(Severity.MAJOR); assertThat(issues.get(0).isNew()).isFalse(); assertThat(issues.get(0).isCopied()).isTrue(); assertThat(issues.get(0).changes()).hasSize(1); @@ -352,7 +352,7 @@ public class IntegrateIssuesVisitorIT { IssueDto issue = IssueTesting.newIssue(ruleDto, project, file) .setKee("ISSUE") - .setSeverity(Severity.MAJOR); + .setSeverity(Severity.MINOR); dbTester.getDbClient().issueDao().insert(dbTester.getSession(), issue); dbTester.getSession().commit(); } @@ -382,7 +382,6 @@ public class IntegrateIssuesVisitorIT { .setRuleRepository(ruleKey.repository()) .setRuleKey(ruleKey.rule()) .addAllCodeVariants(Set.of("foo", "bar")) - .setSeverity(Constants.Severity.BLOCKER) .build(); } } diff --git a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/ImpactMapper.java b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/ImpactMapper.java new file mode 100644 index 00000000000..5f5ad83adbb --- /dev/null +++ b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/ImpactMapper.java @@ -0,0 +1,45 @@ +/* + * 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.ce.task.projectanalysis.issue; + +import org.sonar.api.issue.impact.Severity; +import org.sonar.scanner.protocol.output.ScannerReport; + +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 ImpactMapper { + private ImpactMapper() { + } + + public static Severity mapImpactSeverity(ScannerReport.ImpactSeverity severity) { + return switch (severity) { + case ImpactSeverity_BLOCKER -> BLOCKER; + case ImpactSeverity_HIGH -> HIGH; + case ImpactSeverity_MEDIUM -> MEDIUM; + case ImpactSeverity_LOW -> LOW; + case ImpactSeverity_INFO -> INFO; + case UNRECOGNIZED, ImpactSeverity_UNKNOWN_IMPACT_SEVERITY -> throw new IllegalArgumentException("Unknown impact severity: " + severity); + }; + } +} diff --git a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/NewAdHocRule.java b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/NewAdHocRule.java index 8ca6d401cdf..eabdef556dc 100644 --- a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/NewAdHocRule.java +++ b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/NewAdHocRule.java @@ -134,20 +134,12 @@ public class NewAdHocRule { private static Map<SoftwareQuality, Severity> mapImpacts(List<ScannerReport.Impact> impacts) { if (!impacts.isEmpty()) { - return impacts.stream() - .collect(Collectors.toMap(i -> mapSoftwareQuality(i.getSoftwareQuality()), i -> mapImpactSeverity(i.getSeverity()))); + return impacts.stream().collect(Collectors.toMap(impact -> SoftwareQuality.valueOf( + impact.getSoftwareQuality().name()), impact -> org.sonar.ce.task.projectanalysis.issue.ImpactMapper.mapImpactSeverity(impact.getSeverity()))); } return Collections.emptyMap(); } - private static Severity mapImpactSeverity(String severity) { - return Severity.valueOf(severity); - } - - private static SoftwareQuality mapSoftwareQuality(String softwareQuality) { - return SoftwareQuality.valueOf(softwareQuality); - } - public RuleKey getKey() { return key; } @@ -210,5 +202,4 @@ public class NewAdHocRule { return key.hashCode(); } - } diff --git a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/TrackerRawInputFactory.java b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/TrackerRawInputFactory.java index 3100a1368b6..6a86e5648be 100644 --- a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/TrackerRawInputFactory.java +++ b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/TrackerRawInputFactory.java @@ -21,13 +21,16 @@ package org.sonar.ce.task.projectanalysis.issue; import java.util.ArrayList; import java.util.Collections; +import java.util.EnumMap; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.stream.Collectors; import javax.annotation.Nullable; import org.jetbrains.annotations.NotNull; import org.slf4j.LoggerFactory; +import org.sonar.api.issue.impact.Severity; import org.sonar.api.issue.impact.SoftwareQuality; import org.sonar.api.rule.RuleKey; import org.sonar.api.rules.RuleType; @@ -37,6 +40,7 @@ import org.sonar.ce.task.projectanalysis.batch.BatchReportReader; import org.sonar.ce.task.projectanalysis.component.Component; import org.sonar.ce.task.projectanalysis.component.TreeRootHolder; import org.sonar.ce.task.projectanalysis.issue.filter.IssueFilter; +import org.sonar.ce.task.projectanalysis.qualityprofile.ActiveRule; import org.sonar.ce.task.projectanalysis.qualityprofile.ActiveRulesHolder; import org.sonar.ce.task.projectanalysis.source.SourceLinesHashRepository; import org.sonar.core.issue.DefaultIssue; @@ -47,13 +51,11 @@ import org.sonar.core.util.CloseableIterator; import org.sonar.db.protobuf.DbCommons; import org.sonar.db.protobuf.DbIssues; import org.sonar.scanner.protocol.Constants; -import org.sonar.scanner.protocol.Constants.Severity; import org.sonar.scanner.protocol.output.ScannerReport; import org.sonar.scanner.protocol.output.ScannerReport.Impact; import org.sonar.scanner.protocol.output.ScannerReport.IssueType; import org.sonar.server.rule.CommonRuleKeys; -import static java.util.stream.Collectors.toMap; import static org.apache.commons.lang3.StringUtils.isNotEmpty; import static org.sonar.api.issue.Issue.STATUS_OPEN; import static org.sonar.api.issue.Issue.STATUS_TO_REVIEW; @@ -146,7 +148,7 @@ public class TrackerRawInputFactory { private boolean isOnInactiveRule(ScannerReport.Issue reportIssue) { RuleKey ruleKey = RuleKey.of(reportIssue.getRuleRepository(), reportIssue.getRuleKey()); - return !activeRulesHolder.get(ruleKey).isPresent(); + return activeRulesHolder.get(ruleKey).isEmpty(); } private boolean isIssueOnUnsupportedCommonRule(ScannerReport.Issue issue) { @@ -176,9 +178,7 @@ public class TrackerRawInputFactory { Rule rule = ruleRepository.getByKey(ruleKey); issue.setMessage(rule.getName()); } - if (reportIssue.getSeverity() != Severity.UNSET_SEVERITY) { - issue.setSeverity(reportIssue.getSeverity().name()); - } + issue.setSeverity(replaceDefaultWithOverriddenSeverity(issue.ruleKey(), reportIssue)); if (Double.compare(reportIssue.getGap(), 0D) != 0) { issue.setGap(reportIssue.getGap()); } @@ -198,24 +198,56 @@ public class TrackerRawInputFactory { issue.setRuleDescriptionContextKey(reportIssue.hasRuleDescriptionContextKey() ? reportIssue.getRuleDescriptionContextKey() : null); issue.setCodeVariants(reportIssue.getCodeVariantsList()); - issue.replaceImpacts(replaceDefaultWithOverridenImpacts(issue.ruleKey(), reportIssue.getOverridenImpactsList())); + issue.replaceImpacts(replaceDefaultWithOverriddenImpactsForIssue(issue.ruleKey(), reportIssue.getOverriddenImpactsList())); return issue; } - private Map<SoftwareQuality, org.sonar.api.issue.impact.Severity> replaceDefaultWithOverridenImpacts(RuleKey ruleKey, List<Impact> overridenImpactsList) { - Map<SoftwareQuality, org.sonar.api.issue.impact.Severity> overridenImpactMap = getOverridenImpactMap(overridenImpactsList); - return ruleRepository.getByKey(ruleKey).getDefaultImpacts().entrySet() - .stream() - .collect(toMap( - Map.Entry::getKey, - entry -> overridenImpactMap.containsKey(entry.getKey()) ? overridenImpactMap.get(entry.getKey()) : entry.getValue())); + private String replaceDefaultWithOverriddenSeverity(RuleKey ruleKey, ScannerReport.Issue reportIssue) { + if (reportIssue.hasOverriddenSeverity()) { + return reportIssue.getOverriddenSeverity().name(); + } else { + // Rule can't be inactive (see contract of IssueVisitor) + ActiveRule activeRule = activeRulesHolder.get(ruleKey).orElseThrow(() -> new IllegalStateException("Rule " + ruleKey + " is not active")); + return activeRule.getSeverity(); + } + } + + private Map<SoftwareQuality, Severity> replaceDefaultWithOverriddenImpactsForIssue(RuleKey ruleKey, List<Impact> overriddenImpactsList) { + // Rule can't be inactive (see contract of IssueVisitor) + ActiveRule activeRule = activeRulesHolder.get(ruleKey).orElseThrow(() -> new IllegalStateException("Rule " + ruleKey + " is not active")); + return replaceDefaultWithOverriddenImpacts(ruleKey, activeRule, overriddenImpactsList); + } + + private Map<SoftwareQuality, Severity> replaceDefaultWithOverriddenImpactsForExternalIssue(RuleKey ruleKey, List<Impact> overriddenImpactsList) { + return replaceDefaultWithOverriddenImpacts(ruleKey, null, overriddenImpactsList); + } + + private Map<SoftwareQuality, Severity> replaceDefaultWithOverriddenImpacts(RuleKey ruleKey, @Nullable ActiveRule activeRule, + List<Impact> overriddenImpactsList) { + EnumMap<SoftwareQuality, Severity> impacts = new EnumMap<>(SoftwareQuality.class); + impacts.putAll(ruleRepository.getByKey(ruleKey).getDefaultImpacts()); + if (activeRule != null) { + // Override default impacts with the ones possibly defined in the quality profile + overrideImpacts(impacts, activeRule.getImpacts()); + } + // Override default impacts with the ones possibly defined at the issue level + overrideImpacts(impacts, toMap(overriddenImpactsList)); + return impacts; } - private static Map<SoftwareQuality, org.sonar.api.issue.impact.Severity> getOverridenImpactMap(List<Impact> overridenImpactsList) { - return overridenImpactsList.stream() - .collect(toMap( - impact -> SoftwareQuality.valueOf(impact.getSoftwareQuality()), - impact -> org.sonar.api.issue.impact.Severity.valueOf(impact.getSeverity()))); + private void overrideImpacts(EnumMap<SoftwareQuality, Severity> impacts, Map<SoftwareQuality, Severity> overridenImpactMap) { + overridenImpactMap.forEach((softwareQuality, severity) -> { + if (impacts.containsKey(softwareQuality)) { + impacts.put(softwareQuality, severity); + } + }); + } + + private static Map<SoftwareQuality, Severity> toMap(List<Impact> overriddenImpactsList) { + return overriddenImpactsList.stream() + .collect(Collectors.toMap( + impact -> SoftwareQuality.valueOf(impact.getSoftwareQuality().name()), + impact -> org.sonar.ce.task.projectanalysis.issue.ImpactMapper.mapImpactSeverity(impact.getSeverity()))); } private DbIssues.Flow.Builder convertLocations(ScannerReport.Flow flow) { @@ -230,7 +262,6 @@ public class TrackerRawInputFactory { return dbFlowBuilder; } - private DefaultIssue toExternalIssue(LineHashSequence lineHashSeq, ScannerReport.ExternalIssue reportExternalIssue, Map<RuleKey, ScannerReport.AdHocRule> adHocRuleMap) { DefaultIssue issue = new DefaultIssue(); RuleKey ruleKey = RuleKey.of(RuleKey.EXTERNAL_RULE_REPO_PREFIX + reportExternalIssue.getEngineId(), reportExternalIssue.getRuleId()); @@ -240,7 +271,7 @@ public class TrackerRawInputFactory { Rule existingRule = ruleRepository.getByKey(ruleKey); issue.setSeverity(determineDeprecatedSeverity(reportExternalIssue, existingRule)); issue.setType(determineDeprecatedType(reportExternalIssue, existingRule)); - issue.replaceImpacts(replaceDefaultWithOverridenImpacts(issue.ruleKey(), reportExternalIssue.getImpactsList())); + issue.replaceImpacts(replaceDefaultWithOverriddenImpactsForExternalIssue(issue.ruleKey(), reportExternalIssue.getImpactsList())); init(issue, issue.type() == RuleType.SECURITY_HOTSPOT ? STATUS_TO_REVIEW : STATUS_OPEN); @@ -282,32 +313,22 @@ public class TrackerRawInputFactory { } private Optional<DbIssues.FlowType> toFlowType(ScannerReport.FlowType flowType) { - switch (flowType) { - case DATA: - return Optional.of(DbIssues.FlowType.DATA); - case EXECUTION: - return Optional.of(DbIssues.FlowType.EXECUTION); - case UNDEFINED: - return Optional.empty(); - default: - throw new IllegalArgumentException("Unrecognized type: " + flowType); - } + return switch (flowType) { + case DATA -> Optional.of(DbIssues.FlowType.DATA); + case EXECUTION -> Optional.of(DbIssues.FlowType.EXECUTION); + case UNDEFINED -> Optional.empty(); + default -> throw new IllegalArgumentException("Unrecognized type: " + flowType); + }; } private RuleType toRuleType(IssueType type) { - switch (type) { - case BUG: - return RuleType.BUG; - case CODE_SMELL: - return RuleType.CODE_SMELL; - case VULNERABILITY: - return RuleType.VULNERABILITY; - case SECURITY_HOTSPOT: - return RuleType.SECURITY_HOTSPOT; - case UNRECOGNIZED: - default: - throw new IllegalStateException("Invalid issue type: " + type); - } + return switch (type) { + case BUG -> RuleType.BUG; + case CODE_SMELL -> RuleType.CODE_SMELL; + case VULNERABILITY -> RuleType.VULNERABILITY; + case SECURITY_HOTSPOT -> RuleType.SECURITY_HOTSPOT; + default -> throw new IllegalStateException("Invalid issue type: " + type); + }; } private DefaultIssue init(DefaultIssue issue, String initialStatus) { @@ -325,7 +346,7 @@ public class TrackerRawInputFactory { if (source.getComponentRef() != 0 && source.getComponentRef() != component.getReportAttributes().getRef()) { // SONAR-10781 Component might not exist because on PR, only changed components are included in the report Optional<Component> optionalComponent = treeRootHolder.getOptionalComponentByRef(source.getComponentRef()); - if (!optionalComponent.isPresent()) { + if (optionalComponent.isEmpty()) { return Optional.empty(); } target.setComponentId(optionalComponent.get().getUuid()); @@ -380,7 +401,6 @@ public class TrackerRawInputFactory { } - private static DbIssues.MessageFormattings convertMessageFormattings(List<ScannerReport.MessageFormatting> msgFormattings) { DbIssues.MessageFormattings.Builder builder = DbIssues.MessageFormattings.newBuilder(); msgFormattings.stream() diff --git a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/qualityprofile/ActiveRule.java b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/qualityprofile/ActiveRule.java index 1af38d22367..94e7293ad9b 100644 --- a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/qualityprofile/ActiveRule.java +++ b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/qualityprofile/ActiveRule.java @@ -24,6 +24,8 @@ import java.util.Map; import javax.annotation.CheckForNull; import javax.annotation.Nullable; import javax.annotation.concurrent.Immutable; +import org.sonar.api.issue.impact.Severity; +import org.sonar.api.issue.impact.SoftwareQuality; import org.sonar.api.rule.RuleKey; @Immutable @@ -34,14 +36,17 @@ public class ActiveRule { private final String pluginKey; private final long updatedAt; private final String qProfileKey; + private final Map<SoftwareQuality, Severity> impacts; - public ActiveRule(RuleKey ruleKey, String severity, Map<String, String> params, long updatedAt, @Nullable String pluginKey, String qProfileKey) { + public ActiveRule(RuleKey ruleKey, String severity, Map<String, String> params, long updatedAt, @Nullable String pluginKey, String qProfileKey, + Map<SoftwareQuality, Severity> impacts) { this.ruleKey = ruleKey; this.severity = severity; this.pluginKey = pluginKey; this.params = ImmutableMap.copyOf(params); this.updatedAt = updatedAt; this.qProfileKey = qProfileKey; + this.impacts = impacts; } public RuleKey getRuleKey() { @@ -68,4 +73,8 @@ public class ActiveRule { public String getQProfileKey() { return qProfileKey; } + + public Map<SoftwareQuality, Severity> getImpacts() { + return impacts; + } } diff --git a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/step/LoadQualityProfilesStep.java b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/step/LoadQualityProfilesStep.java index 4fb403138e9..faa5a55cc44 100644 --- a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/step/LoadQualityProfilesStep.java +++ b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/step/LoadQualityProfilesStep.java @@ -24,9 +24,13 @@ import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.stream.Collectors; +import org.sonar.api.issue.impact.Severity; +import org.sonar.api.issue.impact.SoftwareQuality; import org.sonar.api.rule.RuleKey; import org.sonar.api.rule.RuleStatus; import org.sonar.ce.task.projectanalysis.batch.BatchReportReader; +import org.sonar.ce.task.projectanalysis.issue.ImpactMapper; import org.sonar.ce.task.projectanalysis.issue.Rule; import org.sonar.ce.task.projectanalysis.issue.RuleRepository; import org.sonar.ce.task.projectanalysis.qualityprofile.ActiveRule; @@ -71,6 +75,10 @@ public class LoadQualityProfilesStep implements ComputationStep { private static ActiveRule convert(ScannerReport.ActiveRule input, Rule rule) { RuleKey key = RuleKey.of(input.getRuleRepository(), input.getRuleKey()); Map<String, String> params = new HashMap<>(input.getParamsByKeyMap()); - return new ActiveRule(key, input.getSeverity().name(), params, input.getUpdatedAt(), rule.getPluginKey(), input.getQProfileKey()); + Map<SoftwareQuality, Severity> impacts = input.getImpactsList().stream() + .collect(Collectors.toMap( + i -> SoftwareQuality.valueOf(i.getSoftwareQuality().name()), + i -> ImpactMapper.mapImpactSeverity(i.getSeverity()))); + return new ActiveRule(key, input.getSeverity().name(), params, input.getUpdatedAt(), rule.getPluginKey(), input.getQProfileKey(), impacts); } } diff --git a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/ImpactMapperTest.java b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/ImpactMapperTest.java new file mode 100644 index 00000000000..b6bfdac50c9 --- /dev/null +++ b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/ImpactMapperTest.java @@ -0,0 +1,46 @@ +/* + * 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.ce.task.projectanalysis.issue; + +import org.junit.jupiter.api.Test; +import org.sonar.api.issue.impact.Severity; +import org.sonar.scanner.protocol.output.ScannerReport; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +class ImpactMapperTest { + + @Test + void mapImpactSeverity_shouldReturnExpectedValue() { + assertThat(ImpactMapper.mapImpactSeverity(ScannerReport.ImpactSeverity.ImpactSeverity_BLOCKER)).isEqualTo(Severity.BLOCKER); + assertThat(ImpactMapper.mapImpactSeverity(ScannerReport.ImpactSeverity.ImpactSeverity_HIGH)).isEqualTo(Severity.HIGH); + assertThat(ImpactMapper.mapImpactSeverity(ScannerReport.ImpactSeverity.ImpactSeverity_MEDIUM)).isEqualTo(Severity.MEDIUM); + assertThat(ImpactMapper.mapImpactSeverity(ScannerReport.ImpactSeverity.ImpactSeverity_LOW)).isEqualTo(Severity.LOW); + assertThat(ImpactMapper.mapImpactSeverity(ScannerReport.ImpactSeverity.ImpactSeverity_INFO)).isEqualTo(Severity.INFO); + } + + @Test + void mapImpactSeverity_whenUnknownValue_shouldThrowException() { + assertThatThrownBy(() -> ImpactMapper.mapImpactSeverity(ScannerReport.ImpactSeverity.ImpactSeverity_UNKNOWN_IMPACT_SEVERITY)).isInstanceOf(IllegalArgumentException.class); + assertThatThrownBy(() -> ImpactMapper.mapImpactSeverity(ScannerReport.ImpactSeverity.UNRECOGNIZED)).isInstanceOf(IllegalArgumentException.class); + } + +} diff --git a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/NewAdHocRuleTest.java b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/NewAdHocRuleTest.java index aef0e1b632e..738c4d986f7 100644 --- a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/NewAdHocRuleTest.java +++ b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/NewAdHocRuleTest.java @@ -59,7 +59,8 @@ public class NewAdHocRuleTest { public void constructor_whenAdhocRuleHasProvidedImpact_shouldMapTypeAndSeverityAccordingly() { NewAdHocRule adHocRule = new NewAdHocRule(ScannerReport.AdHocRule.newBuilder() .setEngineId("eslint").setRuleId("no-cond-assign").setName("name") - .addDefaultImpacts(ScannerReport.Impact.newBuilder().setSoftwareQuality(SoftwareQuality.MAINTAINABILITY.name()).setSeverity(Severity.LOW.name()).build()) + .addDefaultImpacts( + ScannerReport.Impact.newBuilder().setSoftwareQuality(ScannerReport.SoftwareQuality.MAINTAINABILITY).setSeverity(ScannerReport.ImpactSeverity.ImpactSeverity_LOW).build()) .build()); assertThat(adHocRule.getRuleType()).isEqualTo(RuleType.CODE_SMELL); @@ -84,8 +85,8 @@ public class NewAdHocRuleTest { .setEngineId("eslint").setRuleId("no-cond-assign").setName("name") .setType(ScannerReport.IssueType.CODE_SMELL) .setSeverity(Constants.Severity.MINOR) - .addDefaultImpacts(ScannerReport.Impact.newBuilder().setSoftwareQuality(SoftwareQuality.RELIABILITY.name()) - .setSeverity(Severity.HIGH.name()).build()) + .addDefaultImpacts(ScannerReport.Impact.newBuilder().setSoftwareQuality(ScannerReport.SoftwareQuality.RELIABILITY) + .setSeverity(ScannerReport.ImpactSeverity.ImpactSeverity_HIGH).build()) .build()); assertThat(adHocRule.getDefaultImpacts()) diff --git a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/TrackerRawInputFactoryTest.java b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/TrackerRawInputFactoryTest.java index 646061b3525..3051e9198ab 100644 --- a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/TrackerRawInputFactoryTest.java +++ b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/TrackerRawInputFactoryTest.java @@ -66,8 +66,8 @@ import static org.mockito.Mockito.when; import static org.sonar.api.issue.Issue.STATUS_OPEN; import static org.sonar.api.issue.Issue.STATUS_TO_REVIEW; import static org.sonar.api.issue.impact.Severity.LOW; +import static org.sonar.api.issue.impact.Severity.MEDIUM; import static org.sonar.api.issue.impact.SoftwareQuality.MAINTAINABILITY; -import static org.sonar.api.issue.impact.SoftwareQuality.SECURITY; import static org.sonar.scanner.protocol.output.ScannerReport.MessageFormattingType.CODE; class TrackerRawInputFactoryTest { @@ -126,21 +126,23 @@ class TrackerRawInputFactoryTest { } @Test - void load_issues_from_report() { + void load_issues_from_report_with_overridden_severity() { RuleKey ruleKey = RuleKey.of("java", "S001"); + // Default severity is CRITICAL markRuleAsActive(ruleKey); registerRule(ruleKey, "name", r -> r.addDefaultImpact(MAINTAINABILITY, LOW)); ScannerReport.Issue reportIssue = ScannerReport.Issue.newBuilder() .setTextRange(newTextRange(2)) .setMsg("the message") .addMsgFormatting(ScannerReport.MessageFormatting.newBuilder().setStart(0).setEnd(3).setType(CODE).build()) - .addOverridenImpacts(ScannerReport.Impact.newBuilder() - .setSoftwareQuality(MAINTAINABILITY.name()) - .setSeverity(org.sonar.api.issue.impact.Severity.HIGH.name()) + .addOverriddenImpacts(ScannerReport.Impact.newBuilder() + .setSoftwareQuality(ScannerReport.SoftwareQuality.MAINTAINABILITY) + .setSeverity(ScannerReport.ImpactSeverity.ImpactSeverity_HIGH) .build()) .setRuleRepository(ruleKey.repository()) .setRuleKey(ruleKey.rule()) - .setSeverity(Constants.Severity.BLOCKER) + // Override to BLOCKER + .setOverriddenSeverity(Constants.Severity.BLOCKER) .setGap(3.14) .setQuickFixAvailable(true) .build(); @@ -248,6 +250,7 @@ class TrackerRawInputFactoryTest { @Test void create_whenImpactIsNotDefinedAtRuleLevel_shouldDiscardImpacts() { RuleKey ruleKey = RuleKey.of("java", "S001"); + // Maintainability impact is overridden to MEDIUM in the quality profile markRuleAsActive(ruleKey); registerRule(ruleKey, "name", r -> r.addDefaultImpact(MAINTAINABILITY, LOW)); ScannerReport.Issue reportIssue = ScannerReport.Issue.newBuilder() @@ -255,9 +258,10 @@ class TrackerRawInputFactoryTest { .setMsg("the message") .setRuleRepository(ruleKey.repository()) .setRuleKey(ruleKey.rule()) - .addOverridenImpacts(ScannerReport.Impact.newBuilder() - .setSoftwareQuality(SECURITY.name()) - .setSeverity(LOW.name()).build()) + .addOverriddenImpacts(ScannerReport.Impact.newBuilder() + // Security impact is not defined at rule level, so it should be ignored + .setSoftwareQuality(ScannerReport.SoftwareQuality.SECURITY) + .setSeverity(ScannerReport.ImpactSeverity.ImpactSeverity_LOW).build()) .build(); reportReader.putIssues(FILE.getReportAttributes().getRef(), singletonList(reportIssue)); @@ -265,7 +269,7 @@ class TrackerRawInputFactoryTest { Collection<DefaultIssue> issues = input.getIssues(); assertThat(issues).hasSize(1); - assertThat(issues.iterator().next().impacts()).hasSize(1).containsEntry(MAINTAINABILITY, LOW); + assertThat(issues.iterator().next().impacts()).hasSize(1).containsEntry(MAINTAINABILITY, MEDIUM); } @Test @@ -305,7 +309,6 @@ class TrackerRawInputFactoryTest { .setMsg("the message") .setRuleRepository(ruleKey.repository()) .setRuleKey(ruleKey.rule()) - .setSeverity(Constants.Severity.BLOCKER) .setGap(3.14) .addFlow(ScannerReport.Flow.newBuilder() .addLocation(ScannerReport.IssueLocation.newBuilder() @@ -352,8 +355,8 @@ class TrackerRawInputFactoryTest { .setEffort(20L) .setType(issueType) .addFlow(ScannerReport.Flow.newBuilder().setType(FlowType.DATA).addLocation(ScannerReport.IssueLocation.newBuilder().build()).build()) - .addImpacts(ScannerReport.Impact.newBuilder().setSoftwareQuality(MAINTAINABILITY.name()) - .setSeverity(org.sonar.api.issue.impact.Severity.MEDIUM.name()).build()) + .addImpacts(ScannerReport.Impact.newBuilder().setSoftwareQuality(ScannerReport.SoftwareQuality.MAINTAINABILITY) + .setSeverity(ScannerReport.ImpactSeverity.ImpactSeverity_MEDIUM).build()) .build(); reportReader.putExternalIssues(FILE.getReportAttributes().getRef(), asList(reportIssue)); Input<DefaultIssue> input = underTest.create(FILE); @@ -492,7 +495,6 @@ class TrackerRawInputFactoryTest { .setMsg("the message") .setRuleRepository(ruleKey.repository()) .setRuleKey(ruleKey.rule()) - .setSeverity(Constants.Severity.BLOCKER) .setGap(3.14) .build(); reportReader.putIssues(FILE.getReportAttributes().getRef(), singletonList(reportIssue)); @@ -513,7 +515,6 @@ class TrackerRawInputFactoryTest { .setMsg("the message") .setRuleRepository(ruleKey.repository()) .setRuleKey(ruleKey.rule()) - .setSeverity(Constants.Severity.BLOCKER) .setGap(3.14) .build(); reportReader.putIssues(FILE.getReportAttributes().getRef(), singletonList(reportIssue)); @@ -531,7 +532,6 @@ class TrackerRawInputFactoryTest { .setMsg("the message") .setRuleRepository(ruleKey.repository()) .setRuleKey(ruleKey.rule()) - .setSeverity(Constants.Severity.BLOCKER) .build(); reportReader.putIssues(FILE.getReportAttributes().getRef(), singletonList(reportIssue)); @@ -566,7 +566,7 @@ class TrackerRawInputFactoryTest { } private void markRuleAsActive(RuleKey ruleKey) { - activeRulesHolder.put(new ActiveRule(ruleKey, Severity.CRITICAL, emptyMap(), 1_000L, null, "qp1")); + activeRulesHolder.put(new ActiveRule(ruleKey, Severity.CRITICAL, emptyMap(), 1_000L, null, "qp1", Map.of(MAINTAINABILITY, org.sonar.api.issue.impact.Severity.MEDIUM))); } private void registerRule(RuleKey ruleKey, String name) { diff --git a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/qualityprofile/ActiveRulesHolderImplTest.java b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/qualityprofile/ActiveRulesHolderImplTest.java index 5ea0af56f0f..bcdc79efbbf 100644 --- a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/qualityprofile/ActiveRulesHolderImplTest.java +++ b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/qualityprofile/ActiveRulesHolderImplTest.java @@ -20,6 +20,7 @@ package org.sonar.ce.task.projectanalysis.qualityprofile; import java.util.Collections; +import java.util.Map; import java.util.Optional; import org.junit.Test; import org.sonar.api.rule.RuleKey; @@ -50,7 +51,7 @@ public class ActiveRulesHolderImplTest { @Test public void get_active_rule() { - underTest.set(asList(new ActiveRule(RULE_KEY, Severity.BLOCKER, Collections.emptyMap(), SOME_DATE, PLUGIN_KEY, QP_KEY))); + underTest.set(asList(new ActiveRule(RULE_KEY, Severity.BLOCKER, Collections.emptyMap(), SOME_DATE, PLUGIN_KEY, QP_KEY, Map.of()))); Optional<ActiveRule> activeRule = underTest.get(RULE_KEY); assertThat(activeRule).isPresent(); @@ -61,7 +62,7 @@ public class ActiveRulesHolderImplTest { @Test public void can_not_set_twice() { assertThatThrownBy(() -> { - underTest.set(asList(new ActiveRule(RULE_KEY, Severity.BLOCKER, Collections.emptyMap(), SOME_DATE, PLUGIN_KEY, QP_KEY))); + underTest.set(asList(new ActiveRule(RULE_KEY, Severity.BLOCKER, Collections.emptyMap(), SOME_DATE, PLUGIN_KEY, QP_KEY, Map.of()))); underTest.set(Collections.emptyList()); }) .isInstanceOf(IllegalStateException.class) @@ -79,8 +80,8 @@ public class ActiveRulesHolderImplTest { public void can_not_set_duplicated_rules() { assertThatThrownBy(() -> { underTest.set(asList( - new ActiveRule(RULE_KEY, Severity.BLOCKER, Collections.emptyMap(), SOME_DATE, PLUGIN_KEY, QP_KEY), - new ActiveRule(RULE_KEY, Severity.MAJOR, Collections.emptyMap(), SOME_DATE, PLUGIN_KEY, QP_KEY))); + new ActiveRule(RULE_KEY, Severity.BLOCKER, Collections.emptyMap(), SOME_DATE, PLUGIN_KEY, QP_KEY, Map.of()), + new ActiveRule(RULE_KEY, Severity.MAJOR, Collections.emptyMap(), SOME_DATE, PLUGIN_KEY, QP_KEY, Map.of()))); }) .isInstanceOf(IllegalArgumentException.class) .hasMessage("Active rule must not be declared multiple times: java:S001"); diff --git a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/qualityprofile/AlwaysActiveRulesHolderImpl.java b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/qualityprofile/AlwaysActiveRulesHolderImpl.java index d27cb06caab..10395dd16c2 100644 --- a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/qualityprofile/AlwaysActiveRulesHolderImpl.java +++ b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/qualityprofile/AlwaysActiveRulesHolderImpl.java @@ -19,6 +19,7 @@ */ package org.sonar.ce.task.projectanalysis.qualityprofile; +import java.util.Map; import java.util.Optional; import org.sonar.api.rule.RuleKey; import org.sonar.api.rule.Severity; @@ -28,7 +29,7 @@ import static java.util.Collections.emptyMap; public class AlwaysActiveRulesHolderImpl implements ActiveRulesHolder { @Override public Optional<ActiveRule> get(RuleKey ruleKey) { - return Optional.of(new ActiveRule(ruleKey, Severity.MAJOR, emptyMap(), 1_000L, null, "qp1")); + return Optional.of(new ActiveRule(ruleKey, Severity.MAJOR, emptyMap(), 1_000L, null, "qp1", Map.of())); } } diff --git a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/LoadQualityProfilesStepTest.java b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/LoadQualityProfilesStepTest.java index 70b74b91608..d86c748335a 100644 --- a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/LoadQualityProfilesStepTest.java +++ b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/LoadQualityProfilesStepTest.java @@ -60,6 +60,12 @@ class LoadQualityProfilesStepTest { .setRuleRepository(XOO_X1.repository()) .setRuleKey(XOO_X1.rule()) .setSeverity(Constants.Severity.BLOCKER) + .addImpacts(ScannerReport.Impact.newBuilder() + .setSoftwareQuality(ScannerReport.SoftwareQuality.MAINTAINABILITY) + .setSeverity(ScannerReport.ImpactSeverity.ImpactSeverity_LOW)) + .addImpacts(ScannerReport.Impact.newBuilder() + .setSoftwareQuality(ScannerReport.SoftwareQuality.RELIABILITY) + .setSeverity(ScannerReport.ImpactSeverity.ImpactSeverity_BLOCKER)) .setCreatedAt(1000L) .setUpdatedAt(1200L); batch1.getMutableParamsByKey().put("p1", "v1"); @@ -74,6 +80,9 @@ class LoadQualityProfilesStepTest { ActiveRule ar1 = activeRulesHolder.get(XOO_X1).get(); assertThat(ar1.getSeverity()).isEqualTo(Severity.BLOCKER); + assertThat(ar1.getImpacts()).contains( + MapEntry.entry(org.sonar.api.issue.impact.SoftwareQuality.MAINTAINABILITY, org.sonar.api.issue.impact.Severity.LOW), + MapEntry.entry(org.sonar.api.issue.impact.SoftwareQuality.RELIABILITY, org.sonar.api.issue.impact.Severity.BLOCKER)); assertThat(ar1.getParams()).containsExactly(MapEntry.entry("p1", "v1")); assertThat(ar1.getPluginKey()).isEqualTo("xoo"); assertThat(ar1.getUpdatedAt()).isEqualTo(1200L); diff --git a/sonar-scanner-engine/src/it/java/org/sonar/scanner/mediumtest/issues/IssuesMediumIT.java b/sonar-scanner-engine/src/it/java/org/sonar/scanner/mediumtest/issues/IssuesMediumIT.java index 541a690a63c..2ae6379db66 100644 --- a/sonar-scanner-engine/src/it/java/org/sonar/scanner/mediumtest/issues/IssuesMediumIT.java +++ b/sonar-scanner-engine/src/it/java/org/sonar/scanner/mediumtest/issues/IssuesMediumIT.java @@ -142,7 +142,7 @@ public class IssuesMediumIT { .execute(); List<Issue> issues = result.issuesFor(result.inputFile("xources/hello/HelloJava.xoo")); - assertThat(issues.get(0).getSeverity()).isEqualTo(org.sonar.scanner.protocol.Constants.Severity.CRITICAL); + assertThat(issues.get(0).getOverriddenSeverity()).isEqualTo(org.sonar.scanner.protocol.Constants.Severity.CRITICAL); } @Test diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/issue/DefaultFilterableIssue.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/issue/DefaultFilterableIssue.java index ddb665c7965..d070a464cb4 100644 --- a/sonar-scanner-engine/src/main/java/org/sonar/scanner/issue/DefaultFilterableIssue.java +++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/issue/DefaultFilterableIssue.java @@ -34,12 +34,14 @@ import org.sonar.scanner.protocol.output.ScannerReport.Issue; @ThreadSafe public class DefaultFilterableIssue implements FilterableIssue { private final Issue rawIssue; + private final String severity; private final InputComponent component; private DefaultInputProject project; - public DefaultFilterableIssue(DefaultInputProject project, Issue rawIssue, InputComponent component) { + public DefaultFilterableIssue(DefaultInputProject project, Issue rawIssue, String severity, InputComponent component) { this.project = project; this.rawIssue = rawIssue; + this.severity = severity; this.component = component; } @@ -55,7 +57,7 @@ public class DefaultFilterableIssue implements FilterableIssue { @Override public String severity() { - return rawIssue.getSeverity().name(); + return severity; } @Override diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/issue/ImpactMapper.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/issue/ImpactMapper.java new file mode 100644 index 00000000000..76111b56055 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/issue/ImpactMapper.java @@ -0,0 +1,38 @@ +/* + * 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.scanner.issue; + +import org.sonar.api.issue.impact.Severity; +import org.sonar.scanner.protocol.output.ScannerReport; + +public class ImpactMapper { + private ImpactMapper() { + } + + public static ScannerReport.ImpactSeverity mapImpactSeverity(Severity severity) { + return switch (severity) { + case BLOCKER -> ScannerReport.ImpactSeverity.ImpactSeverity_BLOCKER; + case HIGH -> ScannerReport.ImpactSeverity.ImpactSeverity_HIGH; + case MEDIUM -> ScannerReport.ImpactSeverity.ImpactSeverity_MEDIUM; + case LOW -> ScannerReport.ImpactSeverity.ImpactSeverity_LOW; + case INFO -> ScannerReport.ImpactSeverity.ImpactSeverity_INFO; + }; + } +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/issue/IssueFilters.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/issue/IssueFilters.java index cce20df21d8..948e9552370 100644 --- a/sonar-scanner-engine/src/main/java/org/sonar/scanner/issue/IssueFilters.java +++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/issue/IssueFilters.java @@ -41,11 +41,11 @@ public class IssueFilters { this.project = project; } - public boolean accept(InputComponent component, ScannerReport.Issue rawIssue) { + public boolean accept(InputComponent component, ScannerReport.Issue rawIssue, String severity) { if (filterChain == null) { throw new IllegalStateException("Issue filters must be registered before this class can be used"); } - FilterableIssue fIssue = new DefaultFilterableIssue(project, rawIssue, component); + FilterableIssue fIssue = new DefaultFilterableIssue(project, rawIssue, severity, component); return filterChain.accept(fIssue); } 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 6de30db7829..da79ae46bd0 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 @@ -33,7 +33,6 @@ import org.sonar.api.batch.fs.internal.DefaultInputComponent; import org.sonar.api.batch.fs.internal.DefaultInputFile; import org.sonar.api.batch.rule.ActiveRule; import org.sonar.api.batch.rule.ActiveRules; -import org.sonar.api.batch.rule.internal.DefaultActiveRule; 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; @@ -78,9 +77,9 @@ public class IssuePublisher { return false; } - ScannerReport.Issue rawIssue = createReportIssue(issue, inputComponent.scannerId(), activeRule.severity(), ((DefaultActiveRule) activeRule).impacts()); + ScannerReport.Issue rawIssue = createReportIssue(issue, inputComponent.scannerId()); - if (filters.accept(inputComponent, rawIssue)) { + if (filters.accept(inputComponent, rawIssue, activeRule.severity())) { write(inputComponent.scannerId(), rawIssue); return true; } @@ -108,29 +107,28 @@ public class IssuePublisher { return str; } - private static ScannerReport.Issue createReportIssue(Issue issue, int componentRef, String activeRuleSeverity, - Map<SoftwareQuality, org.sonar.api.issue.impact.Severity> activeRuleImpacts) { + private static ScannerReport.Issue createReportIssue(Issue issue, int componentRef) { 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); ScannerReport.Issue.Builder builder = ScannerReport.Issue.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); builder.addAllMsgFormatting(toProtobufMessageFormattings(issue.primaryLocation().messageFormattings())); Map<SoftwareQuality, org.sonar.api.issue.impact.Severity> overriddenImpacts = new EnumMap<>(issue.overridenImpacts()); - activeRuleImpacts.entrySet().forEach(e -> overriddenImpacts.putIfAbsent(e.getKey(), e.getValue())); - builder.addAllOverridenImpacts(toProtobufImpacts(overriddenImpacts)); + builder.addAllOverriddenImpacts(toProtobufImpacts(overriddenImpacts)); locationBuilder.setMsg(primaryMessage); locationBuilder.addAllMsgFormatting(toProtobufMessageFormattings(issue.primaryLocation().messageFormattings())); locationBuilder.setComponentRef(componentRef); + org.sonar.api.batch.rule.Severity overriddenSeverity = issue.overriddenSeverity(); + if (overriddenSeverity != null) { + builder.setOverriddenSeverity(Severity.valueOf(overriddenSeverity.name())); + } TextRange primaryTextRange = issue.primaryLocation().textRange(); if (primaryTextRange != null) { builder.setTextRange(toProtobufTextRange(textRangeBuilder, primaryTextRange)); @@ -151,7 +149,9 @@ public class IssuePublisher { private static List<ScannerReport.Impact> toProtobufImpacts(Map<SoftwareQuality, org.sonar.api.issue.impact.Severity> softwareQualitySeverityMap) { List<ScannerReport.Impact> impacts = new ArrayList<>(); - softwareQualitySeverityMap.forEach((q, s) -> impacts.add(ScannerReport.Impact.newBuilder().setSoftwareQuality(q.name()).setSeverity(s.name()).build())); + softwareQualitySeverityMap.forEach((q, s) -> impacts.add(ScannerReport.Impact.newBuilder() + .setSoftwareQuality(ScannerReport.SoftwareQuality.valueOf(q.name())) + .setSeverity(ImpactMapper.mapImpactSeverity(s)).build())); return impacts; } diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/report/ActiveRulesPublisher.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/report/ActiveRulesPublisher.java index 63672225c9e..dcad7da095b 100644 --- a/sonar-scanner-engine/src/main/java/org/sonar/scanner/report/ActiveRulesPublisher.java +++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/report/ActiveRulesPublisher.java @@ -21,6 +21,7 @@ package org.sonar.scanner.report; import org.sonar.api.batch.rule.ActiveRules; import org.sonar.api.batch.rule.internal.DefaultActiveRule; +import org.sonar.scanner.issue.ImpactMapper; import org.sonar.scanner.protocol.Constants; import org.sonar.scanner.protocol.output.ScannerReport; import org.sonar.scanner.protocol.output.ScannerReportWriter; @@ -46,7 +47,12 @@ public class ActiveRulesPublisher implements ReportPublisherStep { builder.setCreatedAt(input.createdAt()); builder.setUpdatedAt(input.updatedAt()); builder.setQProfileKey(input.qpKey()); - builder.getMutableParamsByKey().putAll(input.params()); + builder.putAllParamsByKey(input.params()); + builder.addAllImpacts(input.impacts().entrySet().stream() + .map(entry -> ScannerReport.Impact.newBuilder() + .setSoftwareQuality(ScannerReport.SoftwareQuality.valueOf(entry.getKey().name())) + .setSeverity(ImpactMapper.mapImpactSeverity(entry.getValue())).build()) + .toList()); return builder.build(); }).toList()); } 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 e0930fc5372..9ee364cf916 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 @@ -68,6 +68,7 @@ import org.sonar.core.util.CloseableIterator; import org.sonar.duplications.block.Block; import org.sonar.duplications.internal.pmd.PmdBlockChunker; import org.sonar.scanner.cpd.index.SonarCpdBlockIndex; +import org.sonar.scanner.issue.ImpactMapper; import org.sonar.scanner.issue.IssuePublisher; import org.sonar.scanner.protocol.Constants; import org.sonar.scanner.protocol.output.FileStructure; @@ -263,7 +264,6 @@ public class DefaultSensorStorage implements SensorStorage { builder.setDescription(description); } - org.sonar.api.batch.rule.Severity severity = adHocRule.severity(); if (severity != null) { builder.setSeverity(Constants.Severity.valueOf(severity.name())); @@ -285,8 +285,8 @@ public class DefaultSensorStorage implements SensorStorage { private static List<ScannerReport.Impact> mapImpacts(Map<SoftwareQuality, Severity> impactsMap) { return impactsMap.entrySet().stream() .map(e -> ScannerReport.Impact.newBuilder() - .setSoftwareQuality(e.getKey().name()) - .setSeverity(e.getValue().name()).build()) + .setSoftwareQuality(ScannerReport.SoftwareQuality.valueOf(e.getKey().name())) + .setSeverity(ImpactMapper.mapImpactSeverity(e.getValue())).build()) .toList(); } @@ -379,8 +379,7 @@ public class DefaultSensorStorage implements SensorStorage { private SortedMap<Integer, ScannerReport.LineCoverage.Builder> reloadExistingCoverage(DefaultInputFile inputFile) { SortedMap<Integer, ScannerReport.LineCoverage.Builder> coveragePerLine = new TreeMap<>(); - try (CloseableIterator<ScannerReport.LineCoverage> lineCoverageCloseableIterator = - reportPublisher.getReader().readComponentCoverage(inputFile.scannerId())) { + try (CloseableIterator<ScannerReport.LineCoverage> lineCoverageCloseableIterator = reportPublisher.getReader().readComponentCoverage(inputFile.scannerId())) { while (lineCoverageCloseableIterator.hasNext()) { final ScannerReport.LineCoverage lineCoverage = lineCoverageCloseableIterator.next(); coveragePerLine.put(lineCoverage.getLine(), ScannerReport.LineCoverage.newBuilder(lineCoverage)); @@ -393,8 +392,8 @@ public class DefaultSensorStorage implements SensorStorage { void apply(Integer value, ScannerReport.LineCoverage.Builder builder); } - private static void mergeLineCoverageValues(int lineCount, SortedMap<Integer, Integer> valueByLine, SortedMap<Integer, - ScannerReport.LineCoverage.Builder> coveragePerLine, LineCoverageOperation op) { + private static void mergeLineCoverageValues(int lineCount, SortedMap<Integer, Integer> valueByLine, SortedMap<Integer, ScannerReport.LineCoverage.Builder> coveragePerLine, + LineCoverageOperation op) { for (Map.Entry<Integer, Integer> lineMeasure : valueByLine.entrySet()) { int lineIdx = lineMeasure.getKey(); if (lineIdx <= lineCount) { diff --git a/sonar-scanner-engine/src/test/java/org/sonar/scanner/issue/DefaultFilterableIssueTest.java b/sonar-scanner-engine/src/test/java/org/sonar/scanner/issue/DefaultFilterableIssueTest.java index 1f8c28a2768..6145aec1c11 100644 --- a/sonar-scanner-engine/src/test/java/org/sonar/scanner/issue/DefaultFilterableIssueTest.java +++ b/sonar-scanner-engine/src/test/java/org/sonar/scanner/issue/DefaultFilterableIssueTest.java @@ -53,20 +53,18 @@ public class DefaultFilterableIssueTest { .setStartOffset(10) .setEndLine(31) .setEndOffset(3)); - builder.setSeverity(Severity.MAJOR); return builder.build(); } private Issue createIssueWithoutFields() { Issue.Builder builder = Issue.newBuilder(); - builder.setSeverity(Severity.MAJOR); return builder.build(); } @Test public void testRoundTrip() { rawIssue = createIssue(); - issue = new DefaultFilterableIssue(mockedProject, rawIssue, component); + issue = new DefaultFilterableIssue(mockedProject, rawIssue, "MAJOR", component); when(mockedProject.key()).thenReturn("projectKey"); @@ -84,7 +82,7 @@ public class DefaultFilterableIssueTest { @Test public void nullValues() { rawIssue = createIssueWithoutFields(); - issue = new DefaultFilterableIssue(mockedProject, rawIssue, component); + issue = new DefaultFilterableIssue(mockedProject, rawIssue, "MAJOR", component); assertThat(issue.line()).isNull(); assertThat(issue.gap()).isNull(); diff --git a/sonar-scanner-engine/src/test/java/org/sonar/scanner/issue/ImpactMapperTest.java b/sonar-scanner-engine/src/test/java/org/sonar/scanner/issue/ImpactMapperTest.java new file mode 100644 index 00000000000..f543b023c75 --- /dev/null +++ b/sonar-scanner-engine/src/test/java/org/sonar/scanner/issue/ImpactMapperTest.java @@ -0,0 +1,39 @@ +/* + * 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.scanner.issue; + +import org.junit.jupiter.api.Test; +import org.sonar.api.issue.impact.Severity; +import org.sonar.scanner.protocol.output.ScannerReport; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class ImpactMapperTest { + + @Test + void mapImpactSeverity_shouldReturnExpectedValue() { + assertEquals(ScannerReport.ImpactSeverity.ImpactSeverity_BLOCKER, ImpactMapper.mapImpactSeverity(Severity.BLOCKER)); + assertEquals(ScannerReport.ImpactSeverity.ImpactSeverity_HIGH, ImpactMapper.mapImpactSeverity(Severity.HIGH)); + assertEquals(ScannerReport.ImpactSeverity.ImpactSeverity_MEDIUM, ImpactMapper.mapImpactSeverity(Severity.MEDIUM)); + assertEquals(ScannerReport.ImpactSeverity.ImpactSeverity_LOW, ImpactMapper.mapImpactSeverity(Severity.LOW)); + assertEquals(ScannerReport.ImpactSeverity.ImpactSeverity_INFO, ImpactMapper.mapImpactSeverity(Severity.INFO)); + } + +} 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 aa59812f63c..0132e1d7ecc 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 @@ -53,6 +53,7 @@ import org.sonar.scanner.report.ReportPublisher; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.tuple; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.RETURNS_DEEP_STUBS; import static org.mockito.Mockito.mock; @@ -62,7 +63,6 @@ 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 { @@ -136,21 +136,23 @@ public class IssuePublisherTest { .overrideImpact(MAINTAINABILITY, org.sonar.api.issue.impact.Severity.HIGH) .overrideImpact(RELIABILITY, org.sonar.api.issue.impact.Severity.LOW); - when(filters.accept(any(InputComponent.class), any(ScannerReport.Issue.class))).thenReturn(true); + when(filters.accept(any(InputComponent.class), any(ScannerReport.Issue.class), anyString())).thenReturn(true); boolean added = moduleIssues.initAndAddIssue(issue); assertThat(added).isTrue(); ArgumentCaptor<ScannerReport.Issue> 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.CRITICAL); + assertThat(argument.getValue().getOverriddenSeverity()).isEqualTo(org.sonar.scanner.protocol.Constants.Severity.CRITICAL); assertThat(argument.getValue().getQuickFixAvailable()).isTrue(); assertThat(argument.getValue().getRuleDescriptionContextKey()).isEqualTo(ruleDescriptionContextKey); assertThat(argument.getValue().getCodeVariantsList()).containsExactly("variant1", "variant2"); - 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()).containsExactlyInAnyOrder(impact1, impact2); + ScannerReport.Impact impact1 = ScannerReport.Impact.newBuilder().setSoftwareQuality(ScannerReport.SoftwareQuality.MAINTAINABILITY) + .setSeverity(ScannerReport.ImpactSeverity.ImpactSeverity_HIGH).build(); + ScannerReport.Impact impact2 = ScannerReport.Impact.newBuilder().setSoftwareQuality(ScannerReport.SoftwareQuality.RELIABILITY) + .setSeverity(ScannerReport.ImpactSeverity.ImpactSeverity_LOW).build(); + assertThat(argument.getValue().getOverriddenImpactsList()).containsExactlyInAnyOrder(impact1, impact2); } @Test @@ -169,7 +171,7 @@ public class IssuePublisherTest { .addFlow(List.of(new DefaultIssueLocation().on(file)), NewIssue.FlowType.EXECUTION, null) .forRule(JAVA_RULE_KEY); - when(filters.accept(any(InputComponent.class), any(ScannerReport.Issue.class))).thenReturn(true); + when(filters.accept(any(InputComponent.class), any(ScannerReport.Issue.class), anyString())).thenReturn(true); moduleIssues.initAndAddIssue(issue); ArgumentCaptor<ScannerReport.Issue> argument = ArgumentCaptor.forClass(ScannerReport.Issue.class); @@ -227,75 +229,24 @@ public class IssuePublisherTest { ArgumentCaptor<ScannerReport.ExternalIssue> argument = ArgumentCaptor.forClass(ScannerReport.ExternalIssue.class); verify(reportPublisher.getWriter()).appendComponentExternalIssue(eq(file.scannerId()), argument.capture()); assertThat(argument.getValue().getImpactsList()).extracting(ScannerReport.Impact::getSoftwareQuality, ScannerReport.Impact::getSeverity) - .containsExactly(tuple(MAINTAINABILITY.name(), org.sonar.api.issue.impact.Severity.LOW.name())); + .containsExactly(tuple(ScannerReport.SoftwareQuality.MAINTAINABILITY, ScannerReport.ImpactSeverity.ImpactSeverity_LOW)); assertThat(argument.getValue().getCleanCodeAttribute()).isEqualTo(CleanCodeAttribute.CLEAR.name()); } @Test - public void use_severity_from_active_rule_if_no_severity_on_issue() { + public void dont_store_severity_if_no_severity_override_on_issue() { initModuleIssues(); DefaultIssue issue = new DefaultIssue(project) .at(new DefaultIssueLocation().on(file).at(file.selectLine(3)).message("Foo")) .forRule(JAVA_RULE_KEY); - when(filters.accept(any(InputComponent.class), any(ScannerReport.Issue.class))).thenReturn(true); - moduleIssues.initAndAddIssue(issue); - - ArgumentCaptor<ScannerReport.Issue> 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<ScannerReport.Issue> 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); + when(filters.accept(any(InputComponent.class), any(ScannerReport.Issue.class), eq("INFO"))).thenReturn(true); moduleIssues.initAndAddIssue(issue); ArgumentCaptor<ScannerReport.Issue> 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())); + assertThat(argument.getValue().hasOverriddenSeverity()).isFalse(); + assertThat(argument.getValue().getOverriddenImpactsList()).isEmpty(); } @Test @@ -304,7 +255,7 @@ public class IssuePublisherTest { .at(new DefaultIssueLocation().on(file).at(file.selectLine(3)).message("")) .forRule(JAVA_RULE_KEY); - when(filters.accept(any(InputComponent.class), any(ScannerReport.Issue.class))).thenReturn(false); + when(filters.accept(any(InputComponent.class), any(ScannerReport.Issue.class), anyString())).thenReturn(false); boolean added = moduleIssues.initAndAddIssue(issue); @@ -344,7 +295,7 @@ public class IssuePublisherTest { .at(new DefaultIssueLocation().on(file).at(file.selectLine(3)).message("")) .forRule(NOSONAR_RULE_KEY); - when(filters.accept(any(InputComponent.class), any(ScannerReport.Issue.class))).thenReturn(true); + when(filters.accept(any(InputComponent.class), any(ScannerReport.Issue.class), anyString())).thenReturn(true); boolean added = moduleIssues.initAndAddIssue(issue); diff --git a/sonar-scanner-engine/src/test/java/org/sonar/scanner/report/ActiveRulesPublisherTest.java b/sonar-scanner-engine/src/test/java/org/sonar/scanner/report/ActiveRulesPublisherTest.java index 772040e1759..8c4db588240 100644 --- a/sonar-scanner-engine/src/test/java/org/sonar/scanner/report/ActiveRulesPublisherTest.java +++ b/sonar-scanner-engine/src/test/java/org/sonar/scanner/report/ActiveRulesPublisherTest.java @@ -26,6 +26,8 @@ import org.junit.rules.TemporaryFolder; import org.sonar.api.batch.rule.ActiveRules; 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.core.util.CloseableIterator; import org.sonar.scanner.protocol.Constants; @@ -36,6 +38,7 @@ import org.sonar.scanner.protocol.output.ScannerReportWriter; import static java.util.Collections.singletonList; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.groups.Tuple.tuple; public class ActiveRulesPublisherTest { @@ -55,6 +58,8 @@ public class ActiveRulesPublisherTest { .setCreatedAt(1_000L) .setUpdatedAt(2_000L) .setQProfileKey("qp1") + .setImpact(SoftwareQuality.MAINTAINABILITY, Severity.BLOCKER) + .setImpact(SoftwareQuality.RELIABILITY, Severity.LOW) .build(); ActiveRules activeRules = new DefaultActiveRules(singletonList(ar)); @@ -73,6 +78,10 @@ public class ActiveRulesPublisherTest { assertThat(reportAr.getParamsByKeyMap()).hasSize(1); assertThat(reportAr.getParamsByKeyMap().entrySet().iterator().next().getKey()).isEqualTo("p1"); assertThat(reportAr.getParamsByKeyMap().entrySet().iterator().next().getValue()).isEqualTo("v1"); + assertThat(reportAr.getImpactsList()).extracting(ScannerReport.Impact::getSoftwareQuality, ScannerReport.Impact::getSeverity) + .containsExactlyInAnyOrder( + tuple(ScannerReport.SoftwareQuality.MAINTAINABILITY, ScannerReport.ImpactSeverity.ImpactSeverity_BLOCKER), + tuple(ScannerReport.SoftwareQuality.RELIABILITY, ScannerReport.ImpactSeverity.ImpactSeverity_LOW)); assertThat(readIt.hasNext()).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 a0d785bd163..5a6f8f66a1f 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 @@ -151,8 +151,8 @@ class DefaultSensorStorageTest { .forMetric(CoreMetrics.LINES) .withValue(10); assertThatThrownBy(() -> underTest.store(defaultMeasure)) - .isInstanceOf(UnsupportedOperationException.class) - .hasMessage("Unknown metric: lines"); + .isInstanceOf(UnsupportedOperationException.class) + .hasMessage("Unknown metric: lines"); } @Test @@ -392,8 +392,8 @@ class DefaultSensorStorageTest { .containsExactlyInAnyOrder("ruleId", "name", Constants.Severity.MAJOR, ScannerReport.IssueType.CODE_SMELL, "description"); assertThat(adhocRule.getDefaultImpactsList()).hasSize(2).extracting(ScannerReport.Impact::getSoftwareQuality, ScannerReport.Impact::getSeverity) .containsExactlyInAnyOrder( - Tuple.tuple(SoftwareQuality.MAINTAINABILITY.name(), Severity.HIGH.name()), - Tuple.tuple(SoftwareQuality.RELIABILITY.name(), Severity.MEDIUM.name())); + Tuple.tuple(ScannerReport.SoftwareQuality.MAINTAINABILITY, ScannerReport.ImpactSeverity.ImpactSeverity_HIGH), + Tuple.tuple(ScannerReport.SoftwareQuality.RELIABILITY, ScannerReport.ImpactSeverity.ImpactSeverity_MEDIUM)); assertThat(adhocRule.getCleanCodeAttribute()) .isEqualTo(CleanCodeAttribute.CLEAR.name()); } diff --git a/sonar-scanner-protocol/src/main/protobuf/scanner_report.proto b/sonar-scanner-protocol/src/main/protobuf/scanner_report.proto index 74ff2bc0404..c86f48c91d9 100644 --- a/sonar-scanner-protocol/src/main/protobuf/scanner_report.proto +++ b/sonar-scanner-protocol/src/main/protobuf/scanner_report.proto @@ -100,6 +100,7 @@ message ActiveRule { int64 createdAt = 5; int64 updatedAt = 6; string q_profile_key = 7; + repeated Impact impacts = 8; } message ComponentLink { @@ -196,7 +197,7 @@ message Issue { string rule_key = 2; // Only when issue component is a file. Can also be empty for a file if this is an issue global to the file. string msg = 3; - Severity severity = 4; + optional Severity overriddenSeverity = 4; double gap = 5; // Only when issue component is a file. Can also be empty for a file if this is an issue global to the file. // Will be identical to the first location of the first flow @@ -206,7 +207,7 @@ message Issue { optional string ruleDescriptionContextKey = 9; repeated MessageFormatting msgFormatting = 10; repeated string codeVariants = 11; - repeated Impact overridenImpacts = 12; + repeated Impact overriddenImpacts = 12; } message ExternalIssue { @@ -373,8 +374,30 @@ message AnalysisWarning { } message Impact { - string software_quality = 1; - string severity = 2; + SoftwareQuality software_quality = 1; + ImpactSeverity severity = 2; +} + +enum ImpactSeverity { + // Zero is required in order to not get first entry as a default value + // See http://androiddevblog.com/protocol-buffers-pitfall-adding-enum-values/ + ImpactSeverity_UNKNOWN_IMPACT_SEVERITY = 0; + ImpactSeverity_LOW = 1; + ImpactSeverity_MEDIUM = 2; + ImpactSeverity_HIGH = 3; + // INFO and BLOCKER conflicts with Severity enum, so we use different values prefixed with enum name + // See https://github.com/protocolbuffers/protobuf/issues/5425 + ImpactSeverity_INFO = 4; + ImpactSeverity_BLOCKER = 5; +} + +enum SoftwareQuality { + // Zero is required in order to not get first entry as a default value + // See http://androiddevblog.com/protocol-buffers-pitfall-adding-enum-values/ + UNKNOWN_IMPACT_QUALITY = 0; + MAINTAINABILITY = 1; + RELIABILITY = 2; + SECURITY = 3; } message Dependency { |