aboutsummaryrefslogtreecommitdiffstats
path: root/server
diff options
context:
space:
mode:
authorLéo Geoffroy <leo.geoffroy@sonarsource.com>2023-08-09 16:49:35 +0200
committersonartech <sonartech@sonarsource.com>2023-08-18 20:02:49 +0000
commitdddfee0f55914f56d168ae830038bd58fe9e1e8a (patch)
treef765f6a2ad6dcdf26106d39805c6404f7bb1c6e4 /server
parent8b704e7f10a5525071975340cf97d0e5849beaaa (diff)
downloadsonarqube-dddfee0f55914f56d168ae830038bd58fe9e1e8a.tar.gz
sonarqube-dddfee0f55914f56d168ae830038bd58fe9e1e8a.zip
SONAR-20021 Manage external issues and adhoc rules
Diffstat (limited to 'server')
-rw-r--r--server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/issue/AdHocRuleCreatorIT.java16
-rw-r--r--server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/AdHocRuleCreator.java34
-rw-r--r--server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/NewAdHocRule.java91
-rw-r--r--server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/Rule.java4
-rw-r--r--server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/RuleImpl.java14
-rw-r--r--server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/RuleRepositoryImpl.java10
-rw-r--r--server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/TrackerRawInputFactory.java46
-rw-r--r--server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/DumbRule.java21
-rw-r--r--server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/NewAdHocRuleTest.java70
-rw-r--r--server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/TrackerRawInputFactoryTest.java64
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()