Skip to content

Commit 341a16c

Browse files
PLUGINAPI-63 added mappings for impacts to plugin api when creating a rule
1 parent 3900fea commit 341a16c

File tree

7 files changed

+426
-9
lines changed

7 files changed

+426
-9
lines changed

plugin-api/src/main/java/org/sonar/api/server/rule/RuleTagsToTypeConverter.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,6 @@ public static RuleType convert(Collection<String> tags) {
5151
if (tags.contains(TAG_SECURITY)) {
5252
return RuleType.VULNERABILITY;
5353
}
54-
return RuleType.CODE_SMELL;
54+
return null;
5555
}
5656
}

plugin-api/src/main/java/org/sonar/api/server/rule/internal/DefaultNewRule.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ class DefaultNewRule extends RulesDefinition.NewRule {
8585
private String htmlDescription;
8686
private String markdownDescription;
8787
private String internalKey;
88-
private String severity = Severity.MAJOR;
88+
private String severity;
8989
private final Map<SoftwareQuality, org.sonar.api.issue.impact.Severity> defaultImpacts = new EnumMap<>(SoftwareQuality.class);
9090
private boolean template;
9191
private RuleStatus status = RuleStatus.defaultStatus();

plugin-api/src/main/java/org/sonar/api/server/rule/internal/DefaultRule.java

Lines changed: 46 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242

4343
import static java.lang.String.format;
4444
import static java.util.Collections.unmodifiableList;
45+
import static org.sonar.api.rule.Severity.MAJOR;
4546

4647
@Immutable
4748
public class DefaultRule extends RulesDefinition.Rule {
@@ -79,14 +80,11 @@ public class DefaultRule extends RulesDefinition.Rule {
7980
this.htmlDescription = newRule.htmlDescription();
8081
this.markdownDescription = newRule.markdownDescription();
8182
this.internalKey = newRule.internalKey();
82-
this.severity = newRule.severity();
83-
this.defaultImpacts = Collections.unmodifiableMap(newRule.defaultImpacts());
8483
this.template = newRule.template();
8584
this.status = newRule.status();
8685
this.debtRemediationFunction = newRule.debtRemediationFunction();
8786
this.gapDescription = newRule.gapDescription();
8887
this.scope = newRule.scope() == null ? RuleScope.MAIN : newRule.scope();
89-
this.type = newRule.type() == null ? RuleTagsToTypeConverter.convert(newRule.tags()) : newRule.type();
9088
this.cleanCodeAttribute = newRule.cleanCodeAttribute();
9189
Set<String> tagsBuilder = new TreeSet<>(newRule.tags());
9290
tagsBuilder.removeAll(RuleTagsToTypeConverter.RESERVED_TAGS);
@@ -101,6 +99,50 @@ public class DefaultRule extends RulesDefinition.Rule {
10199
this.deprecatedRuleKeys = Collections.unmodifiableSet(new TreeSet<>(newRule.deprecatedRuleKeys()));
102100
this.ruleDescriptionSections = newRule.getRuleDescriptionSections();
103101
this.educationPrincipleKeys = Collections.unmodifiableSet(newRule.educationPrincipleKeys());
102+
103+
this.type = determineType(newRule);
104+
this.severity = determineSeverity(newRule);
105+
this.defaultImpacts = determineImpacts(newRule);
106+
}
107+
108+
private static RuleType determineType(DefaultNewRule newRule) {
109+
RuleType type = newRule.type() == null ? RuleTagsToTypeConverter.convert(newRule.tags()) : newRule.type();
110+
if (type != null) {
111+
return type;
112+
}
113+
114+
if (shouldUseBackmapping(newRule)) {
115+
SoftwareQuality softwareQuality = ImpactMapper.getBestImpactForBackmapping(newRule.defaultImpacts()).getKey();
116+
return ImpactMapper.convertToRuleType(softwareQuality);
117+
}
118+
return RuleType.CODE_SMELL;
119+
}
120+
121+
private static String determineSeverity(DefaultNewRule newRule) {
122+
String severity = newRule.severity();
123+
if (severity != null) {
124+
return severity;
125+
}
126+
127+
if (shouldUseBackmapping(newRule)) {
128+
Severity impactSeverity = ImpactMapper.getBestImpactForBackmapping(newRule.defaultImpacts()).getValue();
129+
return ImpactMapper.convertToDeprecatedSeverity(impactSeverity);
130+
}
131+
return MAJOR;
132+
}
133+
134+
private Map<SoftwareQuality, Severity> determineImpacts(DefaultNewRule newRule) {
135+
if (!newRule.defaultImpacts().isEmpty() || type == RuleType.SECURITY_HOTSPOT) {
136+
return Collections.unmodifiableMap(newRule.defaultImpacts());
137+
}
138+
SoftwareQuality softwareQuality = ImpactMapper.convertToSoftwareQuality(type);
139+
Severity impactSeverity = ImpactMapper.convertToImpactSeverity(severity);
140+
141+
return Map.of(softwareQuality, impactSeverity);
142+
}
143+
144+
private static boolean shouldUseBackmapping(DefaultNewRule newRule) {
145+
return newRule.type() == null && newRule.severity() == null && !newRule.defaultImpacts().isEmpty();
104146
}
105147

106148
@Override
@@ -259,4 +301,5 @@ public int hashCode() {
259301
public String toString() {
260302
return format("[repository=%s, key=%s]", repoKey, key);
261303
}
304+
262305
}
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
/*
2+
* Sonar Plugin API
3+
* Copyright (C) 2009-2023 SonarSource SA
4+
* mailto:info AT sonarsource DOT com
5+
*
6+
* This program is free software; you can redistribute it and/or
7+
* modify it under the terms of the GNU Lesser General Public
8+
* License as published by the Free Software Foundation; either
9+
* version 3 of the License, or (at your option) any later version.
10+
*
11+
* This program is distributed in the hope that it will be useful,
12+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14+
* Lesser General Public License for more details.
15+
*
16+
* You should have received a copy of the GNU Lesser General Public License
17+
* along with this program; if not, write to the Free Software Foundation,
18+
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19+
*/
20+
package org.sonar.api.server.rule.internal;
21+
22+
import java.util.Comparator;
23+
import java.util.List;
24+
import java.util.Map;
25+
import org.sonar.api.issue.impact.Severity;
26+
import org.sonar.api.issue.impact.SoftwareQuality;
27+
import org.sonar.api.rules.RuleType;
28+
29+
import static org.sonar.api.rule.Severity.BLOCKER;
30+
import static org.sonar.api.rule.Severity.CRITICAL;
31+
import static org.sonar.api.rule.Severity.INFO;
32+
import static org.sonar.api.rule.Severity.MAJOR;
33+
import static org.sonar.api.rule.Severity.MINOR;
34+
35+
/**
36+
* @deprecated since 10.1 This is only used for mapping deprecated types and severities until they are removed
37+
*/
38+
@Deprecated(since = "10.1")
39+
public class ImpactMapper {
40+
41+
static final List<SoftwareQuality> ORDERED_SOFTWARE_QUALITIES = List.of(SoftwareQuality.MAINTAINABILITY,
42+
SoftwareQuality.RELIABILITY, SoftwareQuality.SECURITY);
43+
44+
private ImpactMapper() {
45+
// This class is designed to be static
46+
}
47+
48+
public static SoftwareQuality convertToSoftwareQuality(RuleType ruleType) {
49+
switch (ruleType) {
50+
case CODE_SMELL:
51+
return SoftwareQuality.MAINTAINABILITY;
52+
case BUG:
53+
return SoftwareQuality.RELIABILITY;
54+
case VULNERABILITY:
55+
return SoftwareQuality.SECURITY;
56+
case SECURITY_HOTSPOT:
57+
throw new IllegalStateException("Can not map Security Hotspot to Software Quality");
58+
default:
59+
throw new IllegalStateException("Unknown rule type");
60+
}
61+
}
62+
63+
public static RuleType convertToRuleType(SoftwareQuality softwareQuality) {
64+
switch (softwareQuality) {
65+
case MAINTAINABILITY:
66+
return RuleType.CODE_SMELL;
67+
case RELIABILITY:
68+
return RuleType.BUG;
69+
case SECURITY:
70+
return RuleType.VULNERABILITY;
71+
default:
72+
throw new IllegalStateException("Unknown software quality");
73+
}
74+
}
75+
76+
public static String convertToDeprecatedSeverity(Severity severity) {
77+
switch (severity) {
78+
case HIGH:
79+
return CRITICAL;
80+
case MEDIUM:
81+
return MAJOR;
82+
case LOW:
83+
return MINOR;
84+
default:
85+
throw new IllegalStateException("This severity value " + severity + " is illegal.");
86+
}
87+
}
88+
89+
public static Severity convertToImpactSeverity(String deprecatedSeverity) {
90+
switch (deprecatedSeverity) {
91+
case CRITICAL:
92+
case BLOCKER:
93+
return Severity.HIGH;
94+
case MAJOR:
95+
return Severity.MEDIUM;
96+
case MINOR:
97+
case INFO:
98+
return Severity.LOW;
99+
default:
100+
throw new IllegalStateException("This old severity value " + deprecatedSeverity + " is illegal.");
101+
}
102+
}
103+
104+
/**
105+
* This method is needed for picking the best impact on which we are going to base backmapping (getting type and severity from impact).
106+
* As Impacts like any ordering (there is no "best" impact, all of them are equal) we just need to ensure that our choice is consistent
107+
* to make sure we always pick the same impact when the list (map) of impacts is the same.
108+
*/
109+
public static Map.Entry<SoftwareQuality, Severity> getBestImpactForBackmapping(Map<SoftwareQuality, Severity> impacts) {
110+
return impacts.entrySet()
111+
.stream().min(Comparator.comparing(i -> ORDERED_SOFTWARE_QUALITIES.indexOf(i.getKey())))
112+
.orElseThrow(() -> new IllegalArgumentException("There is no impact to choose from."));
113+
}
114+
}

plugin-api/src/test/java/org/sonar/api/server/rule/RuleTagsToTypeConverterTest.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,9 +43,9 @@ public void type_is_vulnerability_if_has_tag_security() {
4343
}
4444

4545
@Test
46-
public void default_is_code_smell() {
47-
assertThat(convert(asList("clumsy", "spring"))).isEqualTo(RuleType.CODE_SMELL);
48-
assertThat(convert(Collections.emptyList())).isEqualTo(RuleType.CODE_SMELL);
46+
public void default_is_null() {
47+
assertThat(convert(asList("clumsy", "spring"))).isNull();
48+
assertThat(convert(Collections.emptyList())).isNull();
4949
}
5050

5151
@Test

plugin-api/src/test/java/org/sonar/api/server/rule/internal/DefaultRuleTest.java

Lines changed: 79 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,15 +34,19 @@
3434

3535
import static org.assertj.core.api.Assertions.assertThat;
3636
import static org.mockito.Mockito.mock;
37+
import static org.sonar.api.rules.RuleType.BUG;
38+
import static org.sonar.api.rules.RuleType.CODE_SMELL;
39+
import static org.sonar.api.rules.RuleType.VULNERABILITY;
3740

3841
public class DefaultRuleTest {
3942

4043
private static final RuleDescriptionSection RULE_DESCRIPTION_SECTION = new RuleDescriptionSectionBuilder().sectionKey("section_key").htmlContent("html desc").build();
4144
private static final RuleDescriptionSection RULE_DESCRIPTION_SECTION_2 = new RuleDescriptionSectionBuilder().sectionKey("section_key_2").htmlContent("html desc 2").build();
4245

46+
private DefaultRepository repo = mock(DefaultRepository.class);
47+
4348
@Test
4449
public void getters() {
45-
DefaultRepository repo = mock(DefaultRepository.class);
4650
DefaultNewRule rule = new DefaultNewRule("plugin", "repo", "key");
4751

4852
rule.setScope(RuleScope.MAIN);
@@ -92,6 +96,80 @@ public void getters() {
9296
assertThat(defaultRule.educationPrincipleKeys()).containsOnly("principle_key1", "principle_key2", "principle_key3");
9397
}
9498

99+
@Test
100+
public void constructor_impact_is_set_when_type_and_severity_is_null() {
101+
DefaultNewRule rule = new DefaultNewRule("plugin", "repo", "key");
102+
103+
DefaultRule defaultRule = new DefaultRule(repo, rule);
104+
105+
assertThat(defaultRule.type()).isEqualTo(CODE_SMELL);
106+
assertThat(defaultRule.severity()).isEqualTo(org.sonar.api.rule.Severity.MAJOR);
107+
assertThat(defaultRule.defaultImpacts()).containsEntry(SoftwareQuality.MAINTAINABILITY, Severity.MEDIUM);
108+
}
109+
110+
@Test
111+
public void constructor_type_and_severity_are_set_when_impact_is_defined() {
112+
DefaultNewRule rule = new DefaultNewRule("plugin", "repo", "key");
113+
rule.addDefaultImpact(SoftwareQuality.SECURITY, Severity.LOW);
114+
115+
DefaultRule defaultRule = new DefaultRule(repo, rule);
116+
117+
assertThat(defaultRule.type()).isEqualTo(VULNERABILITY);
118+
assertThat(defaultRule.severity()).isEqualTo(org.sonar.api.rule.Severity.MINOR);
119+
assertThat(defaultRule.defaultImpacts()).containsEntry(SoftwareQuality.SECURITY, Severity.LOW);
120+
}
121+
122+
@Test
123+
public void constructor_impact_is_mapped_when_type_and_severity_are_set() {
124+
DefaultNewRule rule = new DefaultNewRule("plugin", "repo", "key");
125+
rule.setSeverity(org.sonar.api.rule.Severity.CRITICAL);
126+
rule.setType(BUG);
127+
128+
DefaultRule defaultRule = new DefaultRule(repo, rule);
129+
130+
assertThat(defaultRule.type()).isEqualTo(BUG);
131+
assertThat(defaultRule.severity()).isEqualTo(org.sonar.api.rule.Severity.CRITICAL);
132+
assertThat(defaultRule.defaultImpacts()).containsEntry(SoftwareQuality.RELIABILITY, Severity.HIGH);
133+
}
134+
135+
@Test
136+
public void constructor_impact_is_mapped_and_severity_is_major_when_only_type_is_set() {
137+
DefaultNewRule rule = new DefaultNewRule("plugin", "repo", "key");
138+
rule.setType(VULNERABILITY);
139+
140+
DefaultRule defaultRule = new DefaultRule(repo, rule);
141+
142+
assertThat(defaultRule.type()).isEqualTo(VULNERABILITY);
143+
assertThat(defaultRule.severity()).isEqualTo(org.sonar.api.rule.Severity.MAJOR);
144+
assertThat(defaultRule.defaultImpacts()).containsEntry(SoftwareQuality.SECURITY, Severity.MEDIUM);
145+
}
146+
147+
@Test
148+
public void constructor_severity_is_set_to_major_when_type_and_impact_are_defined() {
149+
DefaultNewRule rule = new DefaultNewRule("plugin", "repo", "key");
150+
rule.setType(VULNERABILITY);
151+
rule.addDefaultImpact(SoftwareQuality.MAINTAINABILITY, Severity.LOW);
152+
153+
DefaultRule defaultRule = new DefaultRule(repo, rule);
154+
155+
assertThat(defaultRule.type()).isEqualTo(VULNERABILITY);
156+
assertThat(defaultRule.severity()).isEqualTo(org.sonar.api.rule.Severity.MAJOR);
157+
assertThat(defaultRule.defaultImpacts()).containsEntry(SoftwareQuality.MAINTAINABILITY, Severity.LOW);
158+
}
159+
160+
@Test
161+
public void constructor_type_is_set_to_code_smell_when_severity_and_impact_are_defined() {
162+
DefaultNewRule rule = new DefaultNewRule("plugin", "repo", "key");
163+
rule.addDefaultImpact(SoftwareQuality.MAINTAINABILITY, Severity.MEDIUM);
164+
rule.setSeverity(org.sonar.api.rule.Severity.BLOCKER);
165+
166+
DefaultRule defaultRule = new DefaultRule(repo, rule);
167+
168+
assertThat(defaultRule.type()).isEqualTo(CODE_SMELL);
169+
assertThat(defaultRule.severity()).isEqualTo(org.sonar.api.rule.Severity.BLOCKER);
170+
assertThat(defaultRule.defaultImpacts()).containsEntry(SoftwareQuality.MAINTAINABILITY, Severity.MEDIUM);
171+
}
172+
95173
@Test
96174
public void to_string() {
97175
DefaultRepository repo = mock(DefaultRepository.class);

0 commit comments

Comments
 (0)