diff options
author | Léo Geoffroy <leo.geoffroy@sonarsource.com> | 2023-08-09 16:49:35 +0200 |
---|---|---|
committer | sonartech <sonartech@sonarsource.com> | 2023-08-18 20:02:49 +0000 |
commit | dddfee0f55914f56d168ae830038bd58fe9e1e8a (patch) | |
tree | f765f6a2ad6dcdf26106d39805c6404f7bb1c6e4 /server | |
parent | 8b704e7f10a5525071975340cf97d0e5849beaaa (diff) | |
download | sonarqube-dddfee0f55914f56d168ae830038bd58fe9e1e8a.tar.gz sonarqube-dddfee0f55914f56d168ae830038bd58fe9e1e8a.zip |
SONAR-20021 Manage external issues and adhoc rules
Diffstat (limited to 'server')
10 files changed, 348 insertions, 22 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 df05ba8d268..cc9f807ab70 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 @@ -19,9 +19,12 @@ */ package org.sonar.ce.task.projectanalysis.issue; +import org.assertj.core.groups.Tuple; import org.junit.Test; +import org.sonar.api.issue.impact.SoftwareQuality; import org.sonar.api.rule.RuleKey; import org.sonar.api.rule.Severity; +import org.sonar.api.rules.CleanCodeAttribute; import org.sonar.api.rules.RuleType; import org.sonar.api.utils.System2; import org.sonar.core.util.SequenceUuidFactory; @@ -35,6 +38,7 @@ import org.sonar.server.rule.index.RuleIndexer; import static org.apache.commons.lang.StringUtils.repeat; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.tuple; public class AdHocRuleCreatorIT { @@ -66,6 +70,9 @@ public class AdHocRuleCreatorIT { assertThat(rule.getAdHocDescription()).isNull(); assertThat(rule.getAdHocSeverity()).isNull(); assertThat(rule.getAdHocType()).isNull(); + assertThat(rule.getCleanCodeAttribute()).isEqualTo(CleanCodeAttribute.defaultCleanCodeAttribute()); + assertThat(rule.getDefaultImpacts()).extracting(i -> i.getSoftwareQuality(), i -> i.getSeverity()) + .containsExactly(Tuple.tuple(SoftwareQuality.MAINTAINABILITY, org.sonar.api.issue.impact.Severity.MEDIUM)); } @Test @@ -77,6 +84,9 @@ public class AdHocRuleCreatorIT { .setDescription("A description") .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()) .build()); RuleDto rule = underTest.persistAndIndex(dbSession, addHocRule); @@ -94,6 +104,9 @@ public class AdHocRuleCreatorIT { assertThat(rule.getAdHocDescription()).isEqualTo("A description"); assertThat(rule.getAdHocSeverity()).isEqualTo(Severity.BLOCKER); assertThat(rule.getAdHocType()).isEqualTo(RuleType.BUG.getDbConstant()); + assertThat(rule.getCleanCodeAttribute()).isEqualTo(CleanCodeAttribute.DISTINCT); + assertThat(rule.getDefaultImpacts()).extracting(i -> i.getSoftwareQuality(), i -> i.getSeverity()) + .containsExactly(tuple(SoftwareQuality.RELIABILITY, org.sonar.api.issue.impact.Severity.LOW)); } @Test @@ -164,6 +177,9 @@ 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()) + .setCleanCodeAttribute(CleanCodeAttribute.CLEAR.name()) .build())); assertThat(ruleUpdated).isNotNull(); diff --git a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/AdHocRuleCreator.java b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/AdHocRuleCreator.java index 81526cf28c5..773afc8b070 100644 --- a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/AdHocRuleCreator.java +++ b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/AdHocRuleCreator.java @@ -20,13 +20,19 @@ package org.sonar.ce.task.projectanalysis.issue; import com.google.common.base.Preconditions; +import java.util.Map; import java.util.Objects; 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.rules.RuleType; import org.sonar.api.utils.System2; import org.sonar.core.util.UuidFactory; +import org.sonar.core.util.Uuids; import org.sonar.db.DbClient; import org.sonar.db.DbSession; +import org.sonar.db.issue.ImpactDto; import org.sonar.db.rule.RuleDao; import org.sonar.db.rule.RuleDto; import org.sonar.server.rule.index.RuleIndexer; @@ -62,8 +68,8 @@ public class AdHocRuleCreator { RuleDto ruleDtoToUpdate = findOrCreateRuleDto(dbSession, adHoc, dao, now); + boolean changed = false; if (adHoc.hasDetails()) { - boolean changed = false; if (!Objects.equals(ruleDtoToUpdate.getAdHocName(), adHoc.getName())) { ruleDtoToUpdate.setAdHocName(substring(adHoc.getName(), 0, MAX_LENGTH_AD_HOC_NAME)); changed = true; @@ -81,11 +87,25 @@ public class AdHocRuleCreator { ruleDtoToUpdate.setAdHocType(ruleType); changed = true; } - if (changed) { - ruleDtoToUpdate.setUpdatedAt(now); - dao.update(dbSession, ruleDtoToUpdate); - } + } + + if (!Objects.equals(ruleDtoToUpdate.getCleanCodeAttribute(), adHoc.getCleanCodeAttribute())) { + ruleDtoToUpdate.setCleanCodeAttribute(adHoc.getCleanCodeAttribute()); + changed = true; + } + Map<SoftwareQuality, Severity> currentImpacts = ruleDtoToUpdate.getDefaultImpacts().stream() + .collect(Collectors.toMap(ImpactDto::getSoftwareQuality, ImpactDto::getSeverity)); + if (!Objects.equals(currentImpacts, adHoc.getDefaultImpacts())) { + ruleDtoToUpdate.replaceAllDefaultImpacts(adHoc.getDefaultImpacts().entrySet().stream() + .map(i -> createImpactDto(i.getKey(), i.getValue())) + .toList()); + changed = true; + } + + if (changed) { + ruleDtoToUpdate.setUpdatedAt(now); + dao.update(dbSession, ruleDtoToUpdate); } RuleDto ruleDto = dao.selectOrFailByKey(dbSession, adHoc.getKey()); @@ -93,6 +113,10 @@ public class AdHocRuleCreator { return ruleDto; } + private static ImpactDto createImpactDto(SoftwareQuality softwareQuality, Severity severity) { + return new ImpactDto().setUuid(Uuids.create()).setSoftwareQuality(softwareQuality).setSeverity(severity); + } + private RuleDto findOrCreateRuleDto(DbSession dbSession, NewAdHocRule adHoc, RuleDao dao, long now) { Optional<RuleDto> existingRuleDtoOpt = dbClient.ruleDao().selectByKey(dbSession, adHoc.getKey()); if (existingRuleDtoOpt.isEmpty()) { 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 abada431256..e83059b5acc 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 @@ -20,11 +20,21 @@ package org.sonar.ce.task.projectanalysis.issue; import com.google.common.base.Preconditions; +import java.util.Collections; +import java.util.EnumMap; +import java.util.List; +import java.util.Map; import java.util.Objects; +import java.util.stream.Collectors; 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; +import org.sonar.api.rules.CleanCodeAttribute; import org.sonar.api.rules.RuleType; +import org.sonar.api.server.rule.internal.ImpactMapper; import org.sonar.scanner.protocol.Constants; import org.sonar.scanner.protocol.output.ScannerReport; @@ -42,20 +52,83 @@ public class NewAdHocRule { private final RuleType ruleType; private final boolean hasDetails; + private final CleanCodeAttribute cleanCodeAttribute; + + private final Map<SoftwareQuality, Severity> defaultImpacts = new EnumMap<>(SoftwareQuality.class); + public NewAdHocRule(ScannerReport.AdHocRule ruleFromScannerReport) { Preconditions.checkArgument(isNotBlank(ruleFromScannerReport.getEngineId()), "'engine id' not expected to be null for an ad hoc rule"); Preconditions.checkArgument(isNotBlank(ruleFromScannerReport.getRuleId()), "'rule id' not expected to be null for an ad hoc rule"); Preconditions.checkArgument(isNotBlank(ruleFromScannerReport.getName()), "'name' not expected to be null for an ad hoc rule"); - Preconditions.checkArgument(ruleFromScannerReport.getSeverity() != Constants.Severity.UNSET_SEVERITY , "'severity' not expected to be null for an ad hoc rule"); - Preconditions.checkArgument(ruleFromScannerReport.getType() != ScannerReport.IssueType.UNSET, "'issue type' not expected to be null for an ad hoc rule"); + Preconditions.checkArgument(!ruleFromScannerReport.getDefaultImpactsList().isEmpty() || ruleFromScannerReport.getSeverity() != Constants.Severity.UNSET_SEVERITY, + "'severity' not expected to be null for an ad hoc rule, or impacts should be provided instead"); + Preconditions.checkArgument(!ruleFromScannerReport.getDefaultImpactsList().isEmpty() || ruleFromScannerReport.getType() != ScannerReport.IssueType.UNSET, + "'issue type' not expected to be null for an ad hoc rule, or impacts should be provided instead"); this.key = RuleKey.of(RuleKey.EXTERNAL_RULE_REPO_PREFIX + ruleFromScannerReport.getEngineId(), ruleFromScannerReport.getRuleId()); this.engineId = ruleFromScannerReport.getEngineId(); this.ruleId = ruleFromScannerReport.getRuleId(); this.name = ruleFromScannerReport.getName(); this.description = trimToNull(ruleFromScannerReport.getDescription()); - this.severity = ruleFromScannerReport.getSeverity().name(); - this.ruleType = RuleType.valueOf(ruleFromScannerReport.getType().name()); this.hasDetails = true; + this.cleanCodeAttribute = mapCleanCodeAttribute(trimToNull(ruleFromScannerReport.getCleanCodeAttribute())); + this.ruleType = determineType(ruleFromScannerReport); + this.severity = determineSeverity(ruleFromScannerReport); + this.defaultImpacts.putAll(determineImpacts(ruleFromScannerReport)); + + } + + private Map<SoftwareQuality, Severity> determineImpacts(ScannerReport.AdHocRule ruleFromScannerReport) { + if (ruleFromScannerReport.getType().equals(ScannerReport.IssueType.SECURITY_HOTSPOT)) { + return Collections.emptyMap(); + } + Map<SoftwareQuality, Severity> impacts = mapImpacts(ruleFromScannerReport.getDefaultImpactsList()); + if (impacts.isEmpty()) { + return Map.of(ImpactMapper.convertToSoftwareQuality(this.ruleType), + ImpactMapper.convertToImpactSeverity(this.severity)); + } else { + return impacts; + } + } + + private static RuleType determineType(ScannerReport.AdHocRule ruleFromScannerReport) { + if (ruleFromScannerReport.getType() != ScannerReport.IssueType.UNSET) { + return RuleType.valueOf(ruleFromScannerReport.getType().name()); + } + Map<SoftwareQuality, Severity> impacts = mapImpacts(ruleFromScannerReport.getDefaultImpactsList()); + Map.Entry<SoftwareQuality, Severity> bestImpactForBackMapping = ImpactMapper.getBestImpactForBackmapping(impacts); + return ImpactMapper.convertToRuleType(bestImpactForBackMapping.getKey()); + } + + private static String determineSeverity(ScannerReport.AdHocRule ruleFromScannerReport) { + if (ruleFromScannerReport.getSeverity() != Constants.Severity.UNSET_SEVERITY) { + return ruleFromScannerReport.getSeverity().name(); + } + Map<SoftwareQuality, Severity> impacts = mapImpacts(ruleFromScannerReport.getDefaultImpactsList()); + Map.Entry<SoftwareQuality, Severity> bestImpactForBackMapping = ImpactMapper.getBestImpactForBackmapping(impacts); + return ImpactMapper.convertToDeprecatedSeverity(bestImpactForBackMapping.getValue()); + } + + private static CleanCodeAttribute mapCleanCodeAttribute(@Nullable String cleanCodeAttribute) { + if (cleanCodeAttribute == null) { + return CleanCodeAttribute.defaultCleanCodeAttribute(); + } + return CleanCodeAttribute.valueOf(cleanCodeAttribute); + } + + 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 Collections.emptyMap(); + } + + private static Severity mapImpactSeverity(String severity) { + return Severity.valueOf(severity); + } + + private static SoftwareQuality mapSoftwareQuality(String softwareQuality) { + return SoftwareQuality.valueOf(softwareQuality); } public NewAdHocRule(ScannerReport.ExternalIssue fromIssue) { @@ -69,6 +142,8 @@ public class NewAdHocRule { this.severity = null; this.ruleType = null; this.hasDetails = false; + this.cleanCodeAttribute = CleanCodeAttribute.defaultCleanCodeAttribute(); + this.defaultImpacts.put(SoftwareQuality.MAINTAINABILITY, Severity.MEDIUM); } public RuleKey getKey() { @@ -107,6 +182,14 @@ public class NewAdHocRule { return hasDetails; } + public CleanCodeAttribute getCleanCodeAttribute() { + return cleanCodeAttribute; + } + + public Map<SoftwareQuality, Severity> getDefaultImpacts() { + return defaultImpacts; + } + @Override public boolean equals(Object o) { if (this == o) { diff --git a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/Rule.java b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/Rule.java index f59c3a04d56..53e41475825 100644 --- a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/Rule.java +++ b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/Rule.java @@ -26,6 +26,7 @@ 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.api.rules.CleanCodeAttribute; import org.sonar.api.rules.RuleType; import org.sonar.api.server.debt.DebtRemediationFunction; @@ -69,4 +70,7 @@ public interface Rule { Set<String> getSecurityStandards(); Map<SoftwareQuality, Severity> getDefaultImpacts(); + + @CheckForNull + CleanCodeAttribute cleanCodeAttribute(); } diff --git a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/RuleImpl.java b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/RuleImpl.java index 265e5c51033..42c5d5c26c3 100644 --- a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/RuleImpl.java +++ b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/RuleImpl.java @@ -31,9 +31,11 @@ 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.api.rules.CleanCodeAttribute; import org.sonar.api.rules.RuleType; import org.sonar.api.server.debt.DebtRemediationFunction; import org.sonar.api.server.debt.internal.DefaultDebtRemediationFunction; +import org.sonar.db.issue.ImpactDto; import org.sonar.db.rule.RuleDescriptionSectionDto; import org.sonar.db.rule.RuleDto; @@ -57,6 +59,7 @@ public class RuleImpl implements Rule { private final String severity; private final Set<String> securityStandards; private final Map<SoftwareQuality, Severity> defaultImpacts; + private final CleanCodeAttribute cleanCodeAttribute; public RuleImpl(RuleDto dto) { this.uuid = dto.getUuid(); @@ -73,8 +76,9 @@ public class RuleImpl implements Rule { this.defaultRuleDescription = getNonNullDefaultRuleDescription(dto); this.severity = Optional.ofNullable(dto.getSeverityString()).orElse(dto.getAdHocSeverity()); this.securityStandards = dto.getSecurityStandards(); - this.defaultImpacts = dto.getDefaultImpacts().stream() - .collect(Collectors.toMap(i -> i.getSoftwareQuality(), i -> i.getSeverity())); + this.defaultImpacts = dto.getDefaultImpacts() + .stream().collect(Collectors.toMap(ImpactDto::getSoftwareQuality, ImpactDto::getSeverity)); + this.cleanCodeAttribute = dto.getCleanCodeAttribute(); } private static String getNonNullDefaultRuleDescription(RuleDto dto) { @@ -149,6 +153,12 @@ public class RuleImpl implements Rule { return defaultImpacts; } + @CheckForNull + @Override + public CleanCodeAttribute cleanCodeAttribute() { + return cleanCodeAttribute; + } + @Override public boolean equals(@Nullable Object o) { if (this == o) { diff --git a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/RuleRepositoryImpl.java b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/RuleRepositoryImpl.java index aa7ad4e89fc..5d4dd2f96f1 100644 --- a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/RuleRepositoryImpl.java +++ b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/RuleRepositoryImpl.java @@ -31,6 +31,7 @@ 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.api.rules.CleanCodeAttribute; import org.sonar.api.rules.RuleType; import org.sonar.api.server.debt.DebtRemediationFunction; import org.sonar.core.util.stream.MoreCollectors; @@ -231,8 +232,13 @@ public class RuleRepositoryImpl implements RuleRepository { @Override public Map<SoftwareQuality, Severity> getDefaultImpacts() { - //TODO external issues - return Collections.emptyMap(); + return addHocRule.getDefaultImpacts(); + } + + @CheckForNull + @Override + public CleanCodeAttribute cleanCodeAttribute() { + return addHocRule.getCleanCodeAttribute(); } } } 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 17fcfd71b47..5da64cf469c 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 @@ -32,6 +32,7 @@ import org.slf4j.LoggerFactory; import org.sonar.api.issue.impact.SoftwareQuality; import org.sonar.api.rule.RuleKey; import org.sonar.api.rules.RuleType; +import org.sonar.api.server.rule.internal.ImpactMapper; import org.sonar.api.utils.Duration; import org.sonar.ce.task.projectanalysis.batch.BatchReportReader; import org.sonar.ce.task.projectanalysis.component.Component; @@ -46,6 +47,7 @@ import org.sonar.core.issue.tracking.LineHashSequence; 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.IssueType; @@ -213,11 +215,17 @@ public class TrackerRawInputFactory { private DefaultIssue toExternalIssue(LineHashSequence lineHashSeq, ScannerReport.ExternalIssue reportExternalIssue, Map<RuleKey, ScannerReport.AdHocRule> adHocRuleMap) { DefaultIssue issue = new DefaultIssue(); - RuleType type = toRuleType(reportExternalIssue.getType()); - init(issue, type == RuleType.SECURITY_HOTSPOT ? STATUS_TO_REVIEW : STATUS_OPEN); - RuleKey ruleKey = RuleKey.of(RuleKey.EXTERNAL_RULE_REPO_PREFIX + reportExternalIssue.getEngineId(), reportExternalIssue.getRuleId()); issue.setRuleKey(ruleKey); + ruleRepository.addOrUpdateAddHocRuleIfNeeded(ruleKey, () -> toAdHocRule(reportExternalIssue, adHocRuleMap.get(issue.ruleKey()))); + + Rule existingRule = ruleRepository.getByKey(ruleKey); + issue.setSeverity(determineDeprecatedSeverity(reportExternalIssue, existingRule)); + issue.setType(determineDeprecatedType(reportExternalIssue, existingRule)); + issue.replaceImpacts(convertImpacts(issue.ruleKey(), reportExternalIssue.getImpactsList())); + + init(issue, issue.type() == RuleType.SECURITY_HOTSPOT ? STATUS_TO_REVIEW : STATUS_OPEN); + if (reportExternalIssue.hasTextRange()) { int startLine = reportExternalIssue.getTextRange().getStartLine(); issue.setLine(startLine); @@ -231,9 +239,6 @@ public class TrackerRawInputFactory { issue.setMessageFormattings(convertMessageFormattings(reportExternalIssue.getMsgFormattingList())); } } - if (reportExternalIssue.getSeverity() != Severity.UNSET_SEVERITY) { - issue.setSeverity(reportExternalIssue.getSeverity().name()); - } issue.setEffort(Duration.create(reportExternalIssue.getEffort() != 0 ? reportExternalIssue.getEffort() : DEFAULT_EXTERNAL_ISSUE_EFFORT)); DbIssues.Locations.Builder dbLocationsBuilder = DbIssues.Locations.newBuilder(); if (reportExternalIssue.hasTextRange()) { @@ -247,9 +252,7 @@ public class TrackerRawInputFactory { } issue.setIsFromExternalRuleEngine(true); issue.setLocations(dbLocationsBuilder.build()); - issue.setType(type); - ruleRepository.addOrUpdateAddHocRuleIfNeeded(ruleKey, () -> toAdHocRule(reportExternalIssue, adHocRuleMap.get(issue.ruleKey()))); return issue; } @@ -330,6 +333,33 @@ public class TrackerRawInputFactory { targetRange.setEndOffset(sourceRange.getEndOffset()); return targetRange; } + + private RuleType determineDeprecatedType(ScannerReport.ExternalIssue reportExternalIssue, Rule rule) { + if (reportExternalIssue.getType() != ScannerReport.IssueType.UNSET) { + return toRuleType(reportExternalIssue.getType()); + } else if (rule.getType() != null) { + return rule.getType(); + } else if (!rule.getDefaultImpacts().isEmpty()) { + SoftwareQuality impactSoftwareQuality = ImpactMapper.getBestImpactForBackmapping(rule.getDefaultImpacts()).getKey(); + return ImpactMapper.convertToRuleType(impactSoftwareQuality); + } else { + throw new IllegalArgumentException("Cannot determine the type for issue of rule %s".formatted(reportExternalIssue.getRuleId())); + } + } + + private static String determineDeprecatedSeverity(ScannerReport.ExternalIssue reportExternalIssue, Rule rule) { + if (reportExternalIssue.getSeverity() != Constants.Severity.UNSET_SEVERITY) { + return reportExternalIssue.getSeverity().name(); + } else if (rule.getSeverity() != null) { + return rule.getSeverity(); + } else if (!rule.getDefaultImpacts().isEmpty()) { + org.sonar.api.issue.impact.Severity impactSeverity = ImpactMapper.getBestImpactForBackmapping(rule.getDefaultImpacts()).getValue(); + return ImpactMapper.convertToDeprecatedSeverity(impactSeverity); + } else { + throw new IllegalArgumentException("Cannot determine the severity for issue of rule %s".formatted(reportExternalIssue.getRuleId())); + } + } + } private Map<SoftwareQuality, org.sonar.api.issue.impact.Severity> convertImpacts(RuleKey ruleKey, List<ScannerReport.Impact> overridenImpactsList) { diff --git a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/DumbRule.java b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/DumbRule.java index 1e551f5dbeb..40259efce2d 100644 --- a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/DumbRule.java +++ b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/DumbRule.java @@ -29,6 +29,7 @@ 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.api.rules.CleanCodeAttribute; import org.sonar.api.rules.RuleType; import org.sonar.api.server.debt.DebtRemediationFunction; @@ -48,6 +49,8 @@ public class DumbRule implements Rule { private boolean isAdHoc; private final Set<String> securityStandards = new HashSet<>(); private final Map<SoftwareQuality, Severity> defaultImpacts = new EnumMap<>(SoftwareQuality.class); + private CleanCodeAttribute cleanCodeAttribute; + private String severity; public DumbRule(RuleKey key) { this.key = key; @@ -108,7 +111,7 @@ public class DumbRule implements Rule { @Override public String getSeverity() { - return null; + return severity; } @Override @@ -121,6 +124,12 @@ public class DumbRule implements Rule { return defaultImpacts; } + @CheckForNull + @Override + public CleanCodeAttribute cleanCodeAttribute() { + return cleanCodeAttribute; + } + @Override public boolean isExternal() { return isExternal; @@ -166,6 +175,11 @@ public class DumbRule implements Rule { return this; } + public DumbRule setSeverity(String severity) { + this.severity = severity; + return this; + } + public DumbRule setPluginKey(String pluginKey) { this.pluginKey = pluginKey; return this; @@ -186,4 +200,9 @@ public class DumbRule implements Rule { return this; } + public DumbRule setCleanCodeAttribute(CleanCodeAttribute cleanCodeAttribute) { + this.cleanCodeAttribute = cleanCodeAttribute; + return this; + } + } 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 3c19c9aeb0f..63e7e4a2c2d 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 @@ -19,7 +19,12 @@ */ package org.sonar.ce.task.projectanalysis.issue; +import java.util.Map; import org.junit.Test; +import org.sonar.api.issue.impact.Severity; +import org.sonar.api.issue.impact.SoftwareQuality; +import org.sonar.api.rules.RuleType; +import org.sonar.scanner.protocol.Constants; import org.sonar.scanner.protocol.output.ScannerReport; import static org.assertj.core.api.Assertions.assertThat; @@ -49,4 +54,69 @@ public class NewAdHocRuleTest { .hasSameHashCodeAs(adHocRule2); assertThat(adHocRule1.hashCode()).isNotEqualTo(anotherAdHocRule.hashCode()); } + + @Test + 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()) + .build()); + + assertThat(adHocRule.getRuleType()).isEqualTo(RuleType.CODE_SMELL); + assertThat(adHocRule.getSeverity()).isEqualTo(org.sonar.api.batch.rule.Severity.MINOR.name()); + } + + @Test + public void constructor_whenAdhocRuleHasNoProvidedImpact_shouldMapDefaultImpactAccordingly() { + NewAdHocRule adHocRule = new NewAdHocRule(ScannerReport.AdHocRule.newBuilder() + .setEngineId("eslint").setRuleId("no-cond-assign").setName("name") + .setType(ScannerReport.IssueType.CODE_SMELL) + .setSeverity(Constants.Severity.MINOR) + .build()); + + assertThat(adHocRule.getDefaultImpacts()) + .containsExactlyEntriesOf(Map.of(SoftwareQuality.MAINTAINABILITY, Severity.LOW)); + } + + @Test + public void constructor_whenAdhocRuleHasBothTypeAndSeverityAndProvidedImpact_shouldKeepSeverityAndTypeAndImpacts() { + NewAdHocRule adHocRule = new NewAdHocRule(ScannerReport.AdHocRule.newBuilder() + .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()) + .build()); + + assertThat(adHocRule.getDefaultImpacts()) + .containsExactlyEntriesOf(Map.of(SoftwareQuality.RELIABILITY, Severity.HIGH)); + + assertThat(adHocRule.getRuleType()).isEqualTo(RuleType.CODE_SMELL); + assertThat(adHocRule.getSeverity()).isEqualTo(org.sonar.api.batch.rule.Severity.MINOR.name()); + + } + + @Test + public void constructor_whenNoSeverityNorImpactsAreProvided_shouldThrowIllegalArgumentException() { + ScannerReport.AdHocRule scannerReport = ScannerReport.AdHocRule.newBuilder() + .setEngineId("eslint").setRuleId("no-cond-assign").setName("name") + .build(); + + assertThatThrownBy(() -> new NewAdHocRule(scannerReport)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("'severity' not expected to be null for an ad hoc rule, or impacts should be provided instead"); + } + + @Test + public void constructor_whenNoTypeNorImpactsAreProvided_shouldThrowIllegalArgumentException() { + ScannerReport.AdHocRule scannerReport = ScannerReport.AdHocRule.newBuilder() + .setSeverity(Constants.Severity.MINOR) + .setEngineId("eslint").setRuleId("no-cond-assign").setName("name") + .build(); + + assertThatThrownBy(() -> new NewAdHocRule(scannerReport)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("'issue type' not expected to be null for an ad hoc rule, or impacts should be provided instead"); + } + } 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 a40e033b5f6..431a717a203 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 @@ -28,6 +28,8 @@ import java.util.Collections; import java.util.Map; import java.util.Optional; import java.util.function.Consumer; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -335,6 +337,9 @@ public class TrackerRawInputFactoryTest { @Test @UseDataProvider("ruleTypeAndStatusByIssueType") public void load_external_issues_from_report(IssueType issueType, RuleType expectedRuleType, String expectedStatus) { + registerRule(RuleKey.of("external_eslint", "S001"), "rule", r -> { + r.addDefaultImpact(SoftwareQuality.MAINTAINABILITY, org.sonar.api.issue.impact.Severity.LOW); + }); ScannerReport.ExternalIssue reportIssue = ScannerReport.ExternalIssue.newBuilder() .setTextRange(newTextRange(2)) .setMsg("the message") @@ -345,6 +350,8 @@ public class TrackerRawInputFactoryTest { .setEffort(20L) .setType(issueType) .addFlow(ScannerReport.Flow.newBuilder().setType(FlowType.DATA).addLocation(ScannerReport.IssueLocation.newBuilder().build()).build()) + .addImpacts(ScannerReport.Impact.newBuilder().setSoftwareQuality(SoftwareQuality.MAINTAINABILITY.name()) + .setSeverity(org.sonar.api.issue.impact.Severity.MEDIUM.name()).build()) .build(); reportReader.putExternalIssues(FILE.getReportAttributes().getRef(), asList(reportIssue)); Input<DefaultIssue> input = underTest.create(FILE); @@ -378,6 +385,8 @@ public class TrackerRawInputFactoryTest { assertThat(issue.checksum()).isEqualTo(input.getLineHashSequence().getHashForLine(2)); assertThat(issue.tags()).isEmpty(); assertInitializedExternalIssue(issue, expectedStatus); + + assertThat(issue.impacts()).containsExactlyEntriesOf(Map.of(SoftwareQuality.MAINTAINABILITY, org.sonar.api.issue.impact.Severity.MEDIUM)); } @DataProvider @@ -393,6 +402,8 @@ public class TrackerRawInputFactoryTest { @Test @UseDataProvider("ruleTypeAndStatusByIssueType") public void load_external_issues_from_report_with_default_effort(IssueType issueType, RuleType expectedRuleType, String expectedStatus) { + registerRule(RuleKey.of("external_eslint", "S001"), "rule", r -> { + }); ScannerReport.ExternalIssue reportIssue = ScannerReport.ExternalIssue.newBuilder() .setTextRange(newTextRange(2)) .setMsg("the message") @@ -423,6 +434,59 @@ public class TrackerRawInputFactoryTest { } @Test + public void create_whenSeverityAndTypeNotProvided_shouldTakeFromTheRule() { + registerRule(RuleKey.of("external_eslint", "S001"), "rule", r -> { + r.setType(RuleType.BUG); + r.setSeverity(Severity.MAJOR); + }); + ScannerReport.ExternalIssue reportIssue = createIssue(null, null); + reportReader.putExternalIssues(FILE.getReportAttributes().getRef(), asList(reportIssue)); + Input<DefaultIssue> input = underTest.create(FILE); + + Collection<DefaultIssue> issues = input.getIssues(); + assertThat(issues).hasSize(1); + DefaultIssue issue = Iterators.getOnlyElement(issues.iterator()); + + assertThat(issue.type()).isEqualTo(RuleType.BUG); + assertThat(issue.severity()).isEqualTo(Severity.MAJOR); + } + + @Test + public void create_whenSeverityAndTypeNotProvidedByIssueAndRule_shouldTakeFromTheRuleImpact() { + registerRule(RuleKey.of("external_eslint", "S001"), "rule", r -> { + r.addDefaultImpact(SoftwareQuality.MAINTAINABILITY, org.sonar.api.issue.impact.Severity.MEDIUM); + }); + ScannerReport.ExternalIssue reportIssue = createIssue(null, null); + reportReader.putExternalIssues(FILE.getReportAttributes().getRef(), asList(reportIssue)); + Input<DefaultIssue> input = underTest.create(FILE); + + Collection<DefaultIssue> issues = input.getIssues(); + assertThat(issues).hasSize(1); + DefaultIssue issue = Iterators.getOnlyElement(issues.iterator()); + + assertThat(issue.type()).isEqualTo(RuleType.CODE_SMELL); + assertThat(issue.severity()).isEqualTo(Severity.MAJOR); + } + + @NotNull + private ScannerReport.ExternalIssue createIssue(@Nullable RuleType ruleType, @Nullable String severity) { + ScannerReport.ExternalIssue.Builder builder = ScannerReport.ExternalIssue.newBuilder() + .setTextRange(newTextRange(2)) + .setMsg("the message") + .setEngineId("eslint") + .setRuleId("S001"); + if (ruleType != null) { + builder.setType(IssueType.valueOf(ruleType.name())); + } + + if (severity != null) { + builder.setSeverity(Constants.Severity.valueOf(severity)); + } + + return builder.build(); + } + + @Test public void excludes_issues_on_inactive_rules() { RuleKey ruleKey = RuleKey.of("java", "S001"); ScannerReport.Issue reportIssue = ScannerReport.Issue.newBuilder() |