@Override
public Map<SoftwareQuality, org.sonar.api.issue.impact.Severity> defaultImpacts() {
- return impacts.isEmpty() ? Map.of(SoftwareQuality.MAINTAINABILITY, org.sonar.api.issue.impact.Severity.MEDIUM) : impacts;
+ return impacts;
}
@CheckForNull
@Override
public CleanCodeAttribute cleanCodeAttribute() {
- return cleanCodeAttribute == null ? CleanCodeAttribute.defaultCleanCodeAttribute() : cleanCodeAttribute;
+ return cleanCodeAttribute;
}
@Override
import org.sonar.api.issue.impact.SoftwareQuality;
import org.sonar.api.rules.CleanCodeAttribute;
import org.sonar.api.rules.RuleType;
-import org.sonar.api.server.rule.internal.ImpactMapper;
import org.sonar.scanner.externalissue.ExternalIssueReport.Issue;
import org.sonar.scanner.externalissue.ExternalIssueReport.Location;
import org.sonar.scanner.externalissue.ExternalIssueReport.Rule;
import static java.lang.String.format;
+import static java.util.Optional.ofNullable;
public class ExternalIssueImporter {
private static final Logger LOG = LoggerFactory.getLogger(ExternalIssueImporter.class);
adHocRule.name(rule.name);
adHocRule.description(rule.description);
adHocRule.engineId(rule.engineId);
- adHocRule.cleanCodeAttribute(CleanCodeAttribute.valueOf(rule.cleanCodeAttribute));
- adHocRule.severity(backmapSeverityFromImpact(rule));
- adHocRule.type(backmapTypeFromImpact(rule));
- for (ExternalIssueReport.Impact impact : rule.impacts) {
- adHocRule.addDefaultImpact(SoftwareQuality.valueOf(impact.softwareQuality),
- org.sonar.api.issue.impact.Severity.valueOf(impact.severity));
+ ofNullable(rule.cleanCodeAttribute).ifPresent(cca -> adHocRule.cleanCodeAttribute(CleanCodeAttribute.valueOf(cca)));
+ ofNullable(rule.severity).ifPresent(s -> adHocRule.severity(Severity.valueOf(s)));
+ ofNullable(rule.type).ifPresent(t -> adHocRule.type(RuleType.valueOf(t)));
+
+ if (rule.impacts != null) {
+ for (ExternalIssueReport.Impact impact : rule.impacts) {
+ adHocRule.addDefaultImpact(SoftwareQuality.valueOf(impact.softwareQuality),
+ org.sonar.api.issue.impact.Severity.valueOf(impact.severity));
+ }
}
return adHocRule;
}
- private static RuleType backmapTypeFromImpact(Rule rule) {
- return ImpactMapper.convertToRuleType(SoftwareQuality.valueOf(rule.impacts[0].softwareQuality));
- }
-
- private static Severity backmapSeverityFromImpact(Rule rule) {
- org.sonar.api.issue.impact.Severity impactSeverity = org.sonar.api.issue.impact.Severity.valueOf(rule.impacts[0].severity);
- return Severity.valueOf(ImpactMapper.convertToDeprecatedSeverity(impactSeverity));
- }
-
private boolean populateCommonValues(Issue issue, NewExternalIssue externalIssue) {
if (issue.effortMinutes != null) {
externalIssue.remediationEffortMinutes(Long.valueOf(issue.effortMinutes));
private boolean importIssue(Issue issue, ExternalIssueReport.Rule rule) {
NewExternalIssue externalIssue = context.newExternalIssue()
.engineId(rule.engineId)
- .ruleId(rule.id)
- .severity(backmapSeverityFromImpact(rule))
- .type(backmapTypeFromImpact(rule));
+ .ruleId(rule.id);
+
+ ofNullable(rule.severity).ifPresent(s -> externalIssue.severity(Severity.valueOf(s)));
+ ofNullable(rule.type).ifPresent(t -> externalIssue.type(RuleType.valueOf(t)));
return populateCommonValues(issue, externalIssue);
}
String name;
@Nullable
String description;
+ String type;
+ String severity;
String cleanCodeAttribute;
Impact[] impacts;
}
import javax.annotation.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import org.sonar.api.rules.RuleType;
import org.sonar.api.scanner.ScannerSide;
import org.sonar.core.documentation.DocumentationLinkGenerator;
public void validate(ExternalIssueReport report, Path reportPath) {
if (report.rules != null && report.issues != null) {
Set<String> ruleIds = validateRules(report.rules, reportPath);
- validateIssuesCctFormat(report.issues, ruleIds, reportPath);
+ validateIssuesNewFormat(report.issues, ruleIds, reportPath);
} else if (report.rules == null && report.issues != null) {
String documentationLink = documentationLinkGenerator.getDocumentationLink(DOCUMENTATION_SUFFIX);
LOGGER.warn("External issues were imported with a deprecated format which will be removed soon. " +
}
}
- private static void validateIssuesCctFormat(ExternalIssueReport.Issue[] issues, Set<String> ruleIds, Path reportPath) {
+ private static void validateIssuesNewFormat(ExternalIssueReport.Issue[] issues, Set<String> ruleIds, Path reportPath) {
for (ExternalIssueReport.Issue issue : issues) {
mandatoryField(issue.ruleId, ISSUE_RULE_ID, reportPath);
checkRuleExistsInReport(ruleIds, issue, reportPath);
mandatoryField(rule.id, "id", reportPath);
mandatoryField(rule.name, "name", reportPath);
mandatoryField(rule.engineId, "engineId", reportPath);
- mandatoryField(rule.cleanCodeAttribute, "cleanCodeAttribute", reportPath);
- checkImpactsArray(rule.impacts, reportPath);
+ validateTypeOrImpacts(reportPath, rule);
+
+ if (rule.impacts != null) {
+ checkImpactsArray(rule.impacts, reportPath);
+ validateImpactsOnSecurityHostpots(rule.impacts, rule.type, reportPath);
+ }
+ if (rule.type != null) {
+ mandatoryField(rule.severity, SEVERITY, reportPath);
+ }
+
+ if (rule.severity != null) {
+ mandatoryField(rule.type, TYPE, reportPath);
+ }
if (!ruleIds.add(rule.id)) {
throw new IllegalStateException(String.format("Failed to parse report '%s': found duplicate rule ID '%s'.", reportPath, rule.id));
return ruleIds;
}
+ private static void validateImpactsOnSecurityHostpots(ExternalIssueReport.Impact[] impacts, String type, Path reportPath) {
+ if (impacts.length > 0 && RuleType.SECURITY_HOTSPOT.name().equals(type)) {
+ throw new IllegalStateException(String.format("Failed to parse report '%s': impacts should not be provided for rule type 'SECURITY_HOTSPOT'.", reportPath));
+ }
+ }
+
+ private static void validateTypeOrImpacts(Path reportPath, ExternalIssueReport.Rule rule) {
+ if (rule.type == null && rule.impacts == null) {
+ throw new IllegalStateException(String.format("Failed to parse report '%s': either type, impacts or both should be provided.", reportPath));
+ }
+ }
+
private static void checkNoField(@Nullable Object value, String fieldName, Path reportPath) {
if (value != null) {
throw new IllegalStateException(String.format("Deprecated '%s' field found in the following report: '%s'.", fieldName, reportPath));
ExternalIssueReport report = new ExternalIssueReport();
ExternalIssueReport.Rule rule = createRule();
report.issues = new ExternalIssueReport.Issue[0];
- report.rules = new ExternalIssueReport.Rule[]{rule};
+ report.rules = new ExternalIssueReport.Rule[] {rule};
ExternalIssueImporter underTest = new ExternalIssueImporter(this.context, report);
underTest.execute();
ExternalIssue output = context.allExternalIssues().iterator().next();
assertThat(output.engineId()).isEqualTo(RULE_ENGINE_ID);
assertThat(output.ruleId()).isEqualTo(RULE_ID);
- assertThat(output.severity()).isEqualTo(Severity.BLOCKER); // backmapped
- assertThat(output.type()).isEqualTo(RuleType.VULNERABILITY); //backmapped
+ assertThat(output.severity()).isNull();
+ assertThat(output.type()).isNull();
assertThat(output.remediationEffort()).isNull();
assertThat(logs.logs(Level.INFO)).contains("Imported 1 issue in 1 file");
assertThat(context.allAdHocRules()).hasSize(1);
assertThat(output1.ruleId()).isEqualTo(RULE_ID);
assertThat(output1.name()).isEqualTo(RULE_NAME);
assertThat(output1.engineId()).isEqualTo(RULE_ENGINE_ID);
- assertThat(output1.severity()).isEqualTo(Severity.BLOCKER); // backmapped
- assertThat(output1.type()).isEqualTo(RuleType.VULNERABILITY); //backmapped
+ assertThat(output1.severity()).isNull();
+ assertThat(output1.type()).isNull();
assertThat(output1.cleanCodeAttribute()).isEqualTo(RULE_ATTRIBUTE);
assertThat(output1.defaultImpacts()).containsExactlyInAnyOrderEntriesOf(Map.of(SECURITY, BLOCKER, MAINTAINABILITY, LOW));
}
public void execute_whenNewFormatContainsNonExistentCleanCodeAttribute_shouldThrowException() {
ExternalIssueReport report = new ExternalIssueReport();
ExternalIssueReport.Rule rule = createRule("not_existent_attribute", MAINTAINABILITY.name(), HIGH.name());
- report.issues = new ExternalIssueReport.Issue[]{};
- report.rules = new ExternalIssueReport.Rule[]{rule};
+ report.issues = new ExternalIssueReport.Issue[] {};
+ report.rules = new ExternalIssueReport.Rule[] {rule};
ExternalIssueImporter underTest = new ExternalIssueImporter(this.context, report);
.hasMessage("No enum constant org.sonar.api.rules.CleanCodeAttribute.not_existent_attribute");
}
+ @Test
+ public void execute_whenCleanCodeAttributeIsNotProvided_shouldBeNull() {
+ ExternalIssueReport report = new ExternalIssueReport();
+ ExternalIssueReport.Rule rule = createRule(RuleType.CODE_SMELL, Severity.MAJOR);
+ ExternalIssueReport.TextRange input = new ExternalIssueReport.TextRange();
+ input.startLine = 1;
+ input.startColumn = 4;
+ input.endLine = 2;
+ input.endColumn = 3;
+
+ ExternalIssueReport.Issue issue = newIssue(input);
+ issue.engineId = rule.engineId;
+ issue.ruleId = rule.id;
+
+ report.issues = new ExternalIssueReport.Issue[] {issue};
+ report.rules = new ExternalIssueReport.Rule[] {rule};
+
+ ExternalIssueImporter underTest = new ExternalIssueImporter(this.context, report);
+
+ underTest.execute();
+ assertThat(context.allExternalIssues()).hasSize(1);
+ ExternalIssue externalIssue = context.allExternalIssues().iterator().next();
+ assertThat(externalIssue.type()).isEqualTo(RuleType.CODE_SMELL);
+ assertThat(externalIssue.severity()).isEqualTo(Severity.MAJOR);
+ }
+
@Test
public void execute_whenNewFormatContainsNonExistentSoftwareQuality_shouldThrowException() {
ExternalIssueReport report = new ExternalIssueReport();
ExternalIssueReport.Rule rule = createRule(CleanCodeAttribute.CONVENTIONAL.name(), "not_existent_software_quality", HIGH.name());
- report.issues = new ExternalIssueReport.Issue[]{};
- report.rules = new ExternalIssueReport.Rule[]{rule};
+ report.issues = new ExternalIssueReport.Issue[] {};
+ report.rules = new ExternalIssueReport.Rule[] {rule};
ExternalIssueImporter underTest = new ExternalIssueImporter(this.context, report);
ExternalIssueReport report = new ExternalIssueReport();
ExternalIssueReport.Rule rule = createRule(CleanCodeAttribute.CONVENTIONAL.name(), SoftwareQuality.RELIABILITY.name(),
"not_existent_impact_severity");
- report.issues = new ExternalIssueReport.Issue[]{};
- report.rules = new ExternalIssueReport.Rule[]{rule};
+ report.issues = new ExternalIssueReport.Issue[] {};
+ report.rules = new ExternalIssueReport.Rule[] {rule};
ExternalIssueImporter underTest = new ExternalIssueImporter(this.context, report);
input.primaryLocation = new ExternalIssueReport.Location();
input.primaryLocation.filePath = sourceFile.getProjectRelativePath();
input.primaryLocation.message = secure().nextAlphabetic(5);
- report.issues = new ExternalIssueReport.Issue[]{input};
+ report.issues = new ExternalIssueReport.Issue[] {input};
ExternalIssueImporter underTest = new ExternalIssueImporter(this.context, report);
underTest.execute();
.hasMessage("A 'startColumn' [line=3, lineOffset=0] cannot be provided when the line is empty");
}
-
private static ExternalIssueReport.Rule createRule() {
return createRule(RULE_ATTRIBUTE.name(), SECURITY.name(), BLOCKER.name());
}
- private static ExternalIssueReport.Rule createRule(String cleanCodeAttribute, String softwareQuality, String impactSeverity) {
+ private static ExternalIssueReport.Rule createRule(@Nullable String cleanCodeAttribute, String softwareQuality, String impactSeverity) {
ExternalIssueReport.Rule rule = new ExternalIssueReport.Rule();
rule.id = RULE_ID;
rule.name = RULE_NAME;
ExternalIssueReport.Impact impact2 = new ExternalIssueReport.Impact();
impact2.severity = LOW.name();
impact2.softwareQuality = MAINTAINABILITY.name();
- rule.impacts = new ExternalIssueReport.Impact[]{impact1, impact2};
+ rule.impacts = new ExternalIssueReport.Impact[] {impact1, impact2};
+ return rule;
+ }
+
+ private static ExternalIssueReport.Rule createRule(RuleType ruleType, Severity severity) {
+ ExternalIssueReport.Rule rule = new ExternalIssueReport.Rule();
+ rule.id = RULE_ID;
+ rule.name = RULE_NAME;
+ rule.engineId = RULE_ENGINE_ID;
+ rule.type = ruleType.name();
+ rule.severity = severity.name();
return rule;
}
private void runOnDeprecatedFormat(ExternalIssueReport.Issue input) {
ExternalIssueReport report = new ExternalIssueReport();
- report.issues = new ExternalIssueReport.Issue[]{input};
+ report.issues = new ExternalIssueReport.Issue[] {input};
ExternalIssueImporter underTest = new ExternalIssueImporter(this.context, report);
underTest.execute();
ExternalIssueReport report = new ExternalIssueReport();
ExternalIssueReport.Rule rule = createRule();
input.ruleId = rule.id;
- report.issues = new ExternalIssueReport.Issue[]{input};
- report.rules = new ExternalIssueReport.Rule[]{rule};
+ report.issues = new ExternalIssueReport.Issue[] {input};
+ report.rules = new ExternalIssueReport.Rule[] {rule};
ExternalIssueImporter underTest = new ExternalIssueImporter(this.context, report);
underTest.execute();
import org.junit.Rule;
import org.junit.Test;
import org.slf4j.event.Level;
+import org.sonar.api.rules.RuleType;
import org.sonar.api.testfixtures.log.LogAndArguments;
import org.sonar.api.testfixtures.log.LogTester;
import org.sonar.core.documentation.DocumentationLinkGenerator;
.hasMessage("Failed to parse report 'report-path': found duplicate rule ID 'rule2'.");
}
- @Test
- public void validate_whenMissingMandatoryCleanCodeAttributeField_shouldThrowException() throws IOException {
- ExternalIssueReport report = read(REPORTS_LOCATION);
- report.rules[0].cleanCodeAttribute = null;
-
- assertThatThrownBy(() -> validator.validate(report, reportPath))
- .isInstanceOf(IllegalStateException.class)
- .hasMessage("Failed to parse report 'report-path': missing mandatory field 'cleanCodeAttribute'.");
- }
-
@Test
public void validate_whenMissingEngineIdField_shouldThrowException() throws IOException {
ExternalIssueReport report = read(REPORTS_LOCATION);
}
@Test
- public void validate_whenMissingImpactsField_shouldThrowException() throws IOException {
+ public void validate_whenMissingImpactsAndTypeField_shouldThrowException() throws IOException {
ExternalIssueReport report = read(REPORTS_LOCATION);
report.rules[0].impacts = null;
+ report.rules[0].type = null;
+
+ assertThatThrownBy(() -> validator.validate(report, reportPath))
+ .isInstanceOf(IllegalStateException.class)
+ .hasMessage("Failed to parse report 'report-path': either type, impacts or both should be provided.");
+ }
+
+ @Test
+ public void validate_whenImpactIsProvided_shouldNotBeEmpty() throws IOException {
+ ExternalIssueReport report = read(REPORTS_LOCATION);
+ report.rules[0].impacts = new ExternalIssueReport.Impact[0];
+ report.rules[0].type = null;
assertThatThrownBy(() -> validator.validate(report, reportPath))
.isInstanceOf(IllegalStateException.class)
- .hasMessage("Failed to parse report 'report-path': missing mandatory field 'impacts'.");
+ .hasMessage("Failed to parse report 'report-path': mandatory array 'impacts' not populated.");
+ }
+
+ @Test
+ public void validate_whenSecurityHotspotAndImpactIsProvided_shouldThrowException() throws IOException {
+ ExternalIssueReport report = read(REPORTS_LOCATION);
+ report.rules[0].impacts = new ExternalIssueReport.Impact[] {
+ new ExternalIssueReport.Impact()
+ };
+ report.rules[0].type = RuleType.SECURITY_HOTSPOT.name();
+
+ assertThatThrownBy(() -> validator.validate(report, reportPath))
+ .isInstanceOf(IllegalStateException.class)
+ .hasMessage("Failed to parse report 'report-path': impacts should not be provided for rule type 'SECURITY_HOTSPOT'.");
+ }
+
+ @Test
+ public void validate_whenTypeIsProvidedAndNotSeverity_shouldThrowException() throws IOException {
+ ExternalIssueReport report = read(REPORTS_LOCATION);
+ report.rules[0].impacts = new ExternalIssueReport.Impact[] {
+ new ExternalIssueReport.Impact()
+ };
+ report.rules[0].type = null;
+ report.rules[0].severity = "MAJOR";
+
+ assertThatThrownBy(() -> validator.validate(report, reportPath))
+ .isInstanceOf(IllegalStateException.class)
+ .hasMessage("Failed to parse report 'report-path': missing mandatory field 'type'.");
+ }
+
+ @Test
+ public void validate_whenSeverityIsProvidedAndNotType_shouldThrowException() throws IOException {
+ ExternalIssueReport report = read(REPORTS_LOCATION);
+ report.rules[0].impacts = new ExternalIssueReport.Impact[] {
+ new ExternalIssueReport.Impact()
+ };
+ report.rules[0].type = "CODE_SMELL";
+ report.rules[0].severity = null;
+
+ assertThatThrownBy(() -> validator.validate(report, reportPath))
+ .isInstanceOf(IllegalStateException.class)
+ .hasMessage("Failed to parse report 'report-path': missing mandatory field 'severity'.");
}
@Test
.on(file)
.forMetric(CoreMetrics.LINES)
.withValue(10)))
- .isInstanceOf(UnsupportedOperationException.class)
- .hasMessage("Unknown metric: lines");
+ .isInstanceOf(UnsupportedOperationException.class)
+ .hasMessage("Unknown metric: lines");
}
@Test
ScannerReport.AdHocRule adhocRule = adhocRuleIt.next();
assertThat(adhocRule).extracting(ScannerReport.AdHocRule::getSeverity, ScannerReport.AdHocRule::getType)
.containsExactlyInAnyOrder(Constants.Severity.UNSET_SEVERITY, ScannerReport.IssueType.UNSET);
- assertThat(adhocRule.getDefaultImpactsList()).extracting(ScannerReport.Impact::getSoftwareQuality, ScannerReport.Impact::getSeverity)
- .containsExactlyInAnyOrder(Tuple.tuple(SoftwareQuality.MAINTAINABILITY.name(), Severity.MEDIUM.name()));
- assertThat(adhocRule.getCleanCodeAttribute()).isEqualTo(CleanCodeAttribute.CONVENTIONAL.name());
+ assertThat(adhocRule.getDefaultImpactsList()).isEmpty();
+ assertThat(adhocRule.getCleanCodeAttribute()).isNullOrEmpty();
}
}
}