aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAurelien Poscia <aurelien.poscia@sonarsource.com>2022-07-13 14:48:11 +0200
committersonartech <sonartech@sonarsource.com>2022-07-14 20:03:48 +0000
commite840bd147581e8d73bd8f74c5488f1cde5844308 (patch)
treeaab348ff31aba260e6c836f7b26d1f3352c4fa77
parent70d175ec3b14e379a6a52de4124a0dfd0ca26a3e (diff)
downloadsonarqube-e840bd147581e8d73bd8f74c5488f1cde5844308.tar.gz
sonarqube-e840bd147581e8d73bd8f74c5488f1cde5844308.zip
SONAR-16635 Use plugin-api htmlDesc to populate web api/rule/*** rule.htmlDesc and /api/hotspots/show fields
-rw-r--r--server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v95/MigrateHotspotRuleDescriptions.java3
-rw-r--r--server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v95/MigrateHotspotRuleDescriptionsTest.java10
-rw-r--r--server/sonar-server-common/src/main/java/org/sonar/server/rule/RuleDescriptionFormatter.java67
-rw-r--r--server/sonar-server-common/src/test/java/org/sonar/server/rule/RuleDescriptionFormatterTest.java40
-rw-r--r--server/sonar-webserver-core/src/main/java/org/sonar/server/rule/AdvancedRuleDescriptionSectionsGenerator.java29
-rw-r--r--server/sonar-webserver-core/src/main/java/org/sonar/server/rule/LegacyHotspotRuleDescriptionSectionsGenerator.java13
-rw-r--r--server/sonar-webserver-core/src/test/java/org/sonar/server/rule/AdvancedRuleDescriptionSectionsGeneratorTest.java27
-rw-r--r--server/sonar-webserver-core/src/test/java/org/sonar/server/rule/LegacyHotspotRuleDescriptionSectionsGeneratorTest.java48
-rw-r--r--server/sonar-webserver-core/src/test/java/org/sonar/server/rule/RuleDescriptionSectionsGeneratorsTest.java31
-rw-r--r--server/sonar-webserver-webapi/src/main/java/org/sonar/server/rule/ws/RuleMapper.java30
-rw-r--r--server/sonar-webserver-webapi/src/test/java/org/sonar/server/rule/ws/ShowActionTest.java39
11 files changed, 167 insertions, 170 deletions
diff --git a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v95/MigrateHotspotRuleDescriptions.java b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v95/MigrateHotspotRuleDescriptions.java
index 1ac7e55bc33..06c05337e76 100644
--- a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v95/MigrateHotspotRuleDescriptions.java
+++ b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v95/MigrateHotspotRuleDescriptions.java
@@ -40,7 +40,8 @@ public class MigrateHotspotRuleDescriptions extends DataChange {
private static final String SELECT_DEFAULT_HOTSPOTS_DESCRIPTIONS = "select r.uuid, rds.uuid, rds.content from rules r \n"
+ "left join " + RULE_DESCRIPTION_SECTIONS_TABLE + " rds on r.uuid = rds.rule_uuid \n"
- + "where r.rule_type = 4 and r.template_uuid is null and rds.kee = '" + DEFAULT_DESCRIPTION_KEY + "'";
+ + "where r.rule_type = 4 and r.template_uuid is null and rds.kee = '" + DEFAULT_DESCRIPTION_KEY + "' and rds.rule_uuid not in"
+ + " (select rds2.rule_uuid from " + RULE_DESCRIPTION_SECTIONS_TABLE + " rds2 where rds2.kee != '" + DEFAULT_DESCRIPTION_KEY + "')";
private static final String INSERT_INTO_RULE_DESC_SECTIONS = "insert into " + RULE_DESCRIPTION_SECTIONS_TABLE + " (uuid, rule_uuid, kee, content) values "
+ "(?,?,?,?)";
diff --git a/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v95/MigrateHotspotRuleDescriptionsTest.java b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v95/MigrateHotspotRuleDescriptionsTest.java
index 1e5d742c217..66c086c4bee 100644
--- a/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v95/MigrateHotspotRuleDescriptionsTest.java
+++ b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v95/MigrateHotspotRuleDescriptionsTest.java
@@ -228,6 +228,16 @@ public class MigrateHotspotRuleDescriptionsTest {
assertThat(findRuleDescriptionSections(LEGACY_HOTSPOT_RULE)).hasSize(3);
}
+ @Test
+ public void insertRuleDescriptions_skipRules_thatAlreadyHaveAdvancedDesc() throws SQLException {
+ insertSectionForLegacyHotspotRule(LEGACY_HOTSPOT_RULE_HTML_DESC);
+ insertRuleDescriptionSection(LEGACY_HOTSPOT_RULE, "root_cause", "content to check reentrant");
+
+ fixHotspotRuleDescriptions.execute();
+
+ assertThat(findRuleDescriptionSections(LEGACY_HOTSPOT_RULE)).hasSize(2);
+ }
+
private List<Map<String, Object>> findAllRuleDescriptionSections() {
return db.select("select uuid, kee, rule_uuid, content from "
+ RULE_DESCRIPTION_SECTIONS_TABLE + "'");
diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/rule/RuleDescriptionFormatter.java b/server/sonar-server-common/src/main/java/org/sonar/server/rule/RuleDescriptionFormatter.java
index ad34b1f81d4..9492aaa3f1c 100644
--- a/server/sonar-server-common/src/main/java/org/sonar/server/rule/RuleDescriptionFormatter.java
+++ b/server/sonar-server-common/src/main/java/org/sonar/server/rule/RuleDescriptionFormatter.java
@@ -19,85 +19,34 @@
*/
package org.sonar.server.rule;
+import com.google.common.collect.MoreCollectors;
import java.util.Collection;
-import java.util.List;
-import java.util.Map;
import java.util.Objects;
-import java.util.Optional;
import javax.annotation.CheckForNull;
-import org.sonar.api.rules.RuleType;
-import org.sonar.db.rule.RuleDescriptionSectionContextDto;
import org.sonar.db.rule.RuleDescriptionSectionDto;
import org.sonar.db.rule.RuleDto;
import org.sonar.markdown.Markdown;
-import static java.util.Comparator.comparing;
-import static java.util.stream.Collectors.toMap;
-import static org.sonar.api.server.rule.RuleDescriptionSection.RuleDescriptionSectionKeys.ASSESS_THE_PROBLEM_SECTION_KEY;
-import static org.sonar.api.server.rule.RuleDescriptionSection.RuleDescriptionSectionKeys.HOW_TO_FIX_SECTION_KEY;
-import static org.sonar.api.server.rule.RuleDescriptionSection.RuleDescriptionSectionKeys.INTRODUCTION_SECTION_KEY;
-import static org.sonar.api.server.rule.RuleDescriptionSection.RuleDescriptionSectionKeys.RESOURCES_SECTION_KEY;
-import static org.sonar.api.server.rule.RuleDescriptionSection.RuleDescriptionSectionKeys.ROOT_CAUSE_SECTION_KEY;
-import static org.sonar.db.rule.RuleDescriptionSectionDto.DEFAULT_KEY;
import static org.sonar.db.rule.RuleDto.Format.MARKDOWN;
public class RuleDescriptionFormatter {
- public static final List<String> SECTION_KEYS = List.of(
- INTRODUCTION_SECTION_KEY,
- ROOT_CAUSE_SECTION_KEY,
- ASSESS_THE_PROBLEM_SECTION_KEY,
- HOW_TO_FIX_SECTION_KEY,
- RESOURCES_SECTION_KEY);
-
- public static final Map<String, String> HOTSPOT_SECTION_TITLES = Map.of(
- ROOT_CAUSE_SECTION_KEY, "What is the risk?",
- ASSESS_THE_PROBLEM_SECTION_KEY, "Assess the risk",
- HOW_TO_FIX_SECTION_KEY, "How can you fix it?"
- );
-
- public static final Map<String, String> RULE_SECTION_TITLES = Map.of(
- ROOT_CAUSE_SECTION_KEY, "Why is this an issue?",
- HOW_TO_FIX_SECTION_KEY, "How to fix it?",
- RESOURCES_SECTION_KEY, "Resources"
- );
-
@CheckForNull
public String getDescriptionAsHtml(RuleDto ruleDto) {
if (ruleDto.getDescriptionFormat() == null) {
return null;
}
Collection<RuleDescriptionSectionDto> ruleDescriptionSectionDtos = ruleDto.getRuleDescriptionSectionDtos();
- return retrieveDescription(ruleDescriptionSectionDtos, RuleType.valueOf(ruleDto.getType()), Objects.requireNonNull(ruleDto.getDescriptionFormat()));
+ return retrieveDescription(ruleDescriptionSectionDtos, Objects.requireNonNull(ruleDto.getDescriptionFormat()));
}
@CheckForNull
- private static String retrieveDescription(Collection<RuleDescriptionSectionDto> ruleDescriptionSectionDtos,
- RuleType ruleType, RuleDto.Format descriptionFormat) {
- if (ruleDescriptionSectionDtos.isEmpty()) {
- return null;
- }
- Map<String, String> sectionKeyToHtml = ruleDescriptionSectionDtos.stream()
- //TODO MMF-2765, merge operation on toMap should not be needed anymore
- .sorted(comparing(RuleDescriptionSectionDto::getKey).thenComparing(s -> Optional.ofNullable(s.getContext()).map(RuleDescriptionSectionContextDto::getKey).orElse(null)))
- .collect(toMap(RuleDescriptionSectionDto::getKey, section -> toHtml(descriptionFormat, section), (k1, k2) -> k1));
- if (sectionKeyToHtml.containsKey(DEFAULT_KEY)) {
- return sectionKeyToHtml.get(DEFAULT_KEY);
- } else {
- return concatHtmlSections(sectionKeyToHtml, ruleType);
- }
- }
-
- private static String concatHtmlSections(Map<String, String> sectionKeyToHtml, RuleType ruleType) {
- Map<String, String> titleMapping = ruleType.equals(RuleType.SECURITY_HOTSPOT) ? HOTSPOT_SECTION_TITLES : RULE_SECTION_TITLES;
- var builder = new StringBuilder();
- for (String sectionKey : SECTION_KEYS) {
- if (sectionKeyToHtml.containsKey(sectionKey)) {
- Optional.ofNullable(titleMapping.get(sectionKey)).ifPresent(title -> builder.append("<h2>").append(title).append("</h2>"));
- builder.append(sectionKeyToHtml.get(sectionKey)).append("<br/>");
- }
- }
- return builder.toString();
+ private static String retrieveDescription(Collection<RuleDescriptionSectionDto> ruleDescriptionSectionDtos, RuleDto.Format descriptionFormat) {
+ return ruleDescriptionSectionDtos.stream()
+ .filter(RuleDescriptionSectionDto::isDefault)
+ .collect(MoreCollectors.toOptional())
+ .map(section -> toHtml(descriptionFormat, section))
+ .orElse(null);
}
private static String toHtml(RuleDto.Format descriptionFormat, RuleDescriptionSectionDto ruleDescriptionSectionDto) {
diff --git a/server/sonar-server-common/src/test/java/org/sonar/server/rule/RuleDescriptionFormatterTest.java b/server/sonar-server-common/src/test/java/org/sonar/server/rule/RuleDescriptionFormatterTest.java
index 8466dfaaa4f..2268e2e408b 100644
--- a/server/sonar-server-common/src/test/java/org/sonar/server/rule/RuleDescriptionFormatterTest.java
+++ b/server/sonar-server-common/src/test/java/org/sonar/server/rule/RuleDescriptionFormatterTest.java
@@ -19,20 +19,13 @@
*/
package org.sonar.server.rule;
-import java.util.Optional;
-import org.apache.commons.lang.RandomStringUtils;
-import org.jetbrains.annotations.Nullable;
import org.junit.Test;
import org.sonar.api.rules.RuleType;
-import org.sonar.db.rule.RuleDescriptionSectionContextDto;
import org.sonar.db.rule.RuleDescriptionSectionDto;
import org.sonar.db.rule.RuleDto;
import static org.assertj.core.api.Assertions.assertThat;
import static org.sonar.api.server.rule.RuleDescriptionSection.RuleDescriptionSectionKeys.ASSESS_THE_PROBLEM_SECTION_KEY;
-import static org.sonar.api.server.rule.RuleDescriptionSection.RuleDescriptionSectionKeys.HOW_TO_FIX_SECTION_KEY;
-import static org.sonar.api.server.rule.RuleDescriptionSection.RuleDescriptionSectionKeys.INTRODUCTION_SECTION_KEY;
-import static org.sonar.api.server.rule.RuleDescriptionSection.RuleDescriptionSectionKeys.RESOURCES_SECTION_KEY;
import static org.sonar.api.server.rule.RuleDescriptionSection.RuleDescriptionSectionKeys.ROOT_CAUSE_SECTION_KEY;
import static org.sonar.db.rule.RuleDescriptionSectionDto.createDefaultRuleDescriptionSection;
@@ -57,33 +50,17 @@ public class RuleDescriptionFormatterTest {
}
@Test
- public void concatHtmlDescriptionSections() {
+ public void getDescriptionAsHtml_ignoresAdvancedSections() {
var section1 = createRuleDescriptionSection(ROOT_CAUSE_SECTION_KEY, "<div>Root is Root</div>");
var section2 = createRuleDescriptionSection(ASSESS_THE_PROBLEM_SECTION_KEY, "<div>This is not a problem</div>");
- var section3 = createRuleDescriptionSection(HOW_TO_FIX_SECTION_KEY, "<div>I don't want to fix</div>");
- var section4 = createRuleDescriptionSection(INTRODUCTION_SECTION_KEY, "<div>Introduction with no title</div>");
- var section5ctx1 = createRuleDescriptionSection(RESOURCES_SECTION_KEY, "<div>CTX_1</div>", "CTX_1");
- var section5ctx2 = createRuleDescriptionSection(RESOURCES_SECTION_KEY, "<div>CTX_2</div>", "CTX_2");
+ var defaultRuleDescriptionSection = createDefaultRuleDescriptionSection("uuid_432", "default description");
RuleDto rule = new RuleDto().setDescriptionFormat(RuleDto.Format.HTML)
.setType(RuleType.SECURITY_HOTSPOT)
.addRuleDescriptionSectionDto(section1)
.addRuleDescriptionSectionDto(section2)
- .addRuleDescriptionSectionDto(section3)
- .addRuleDescriptionSectionDto(section4)
- .addRuleDescriptionSectionDto(section5ctx2)
- .addRuleDescriptionSectionDto(section5ctx1);
+ .addRuleDescriptionSectionDto(defaultRuleDescriptionSection);
String html = ruleDescriptionFormatter.getDescriptionAsHtml(rule);
- assertThat(html)
- .isEqualTo(
- "<div>Introduction with no title</div><br/>"
- + "<h2>What is the risk?</h2>"
- + "<div>Root is Root</div><br/>"
- + "<h2>Assess the risk</h2>"
- + "<div>This is not a problem</div><br/>"
- + "<h2>How can you fix it?</h2>"
- + "<div>I don't want to fix</div><br/>"
- + "<div>CTX_1</div><br/>"
- );
+ assertThat(html).isEqualTo(defaultRuleDescriptionSection.getContent());
}
@Test
@@ -102,13 +79,6 @@ public class RuleDescriptionFormatterTest {
}
private static RuleDescriptionSectionDto createRuleDescriptionSection(String key, String content) {
- return createRuleDescriptionSection(key, content, null);
- }
-
- private static RuleDescriptionSectionDto createRuleDescriptionSection(String key, String content, @Nullable String contextKey) {
- RuleDescriptionSectionContextDto context = Optional.ofNullable(contextKey)
- .map(c -> RuleDescriptionSectionContextDto.of(contextKey, contextKey + RandomStringUtils.randomAlphanumeric(20)))
- .orElse(null);
- return RuleDescriptionSectionDto.builder().key(key).content(content).context(context).build();
+ return RuleDescriptionSectionDto.builder().key(key).content(content).build();
}
}
diff --git a/server/sonar-webserver-core/src/main/java/org/sonar/server/rule/AdvancedRuleDescriptionSectionsGenerator.java b/server/sonar-webserver-core/src/main/java/org/sonar/server/rule/AdvancedRuleDescriptionSectionsGenerator.java
index 08652f93ee4..9c49029690f 100644
--- a/server/sonar-webserver-core/src/main/java/org/sonar/server/rule/AdvancedRuleDescriptionSectionsGenerator.java
+++ b/server/sonar-webserver-core/src/main/java/org/sonar/server/rule/AdvancedRuleDescriptionSectionsGenerator.java
@@ -21,6 +21,8 @@ package org.sonar.server.rule;
import java.util.Optional;
import java.util.Set;
+import java.util.stream.Collectors;
+import org.elasticsearch.common.util.set.Sets;
import org.jetbrains.annotations.Nullable;
import org.sonar.api.server.rule.Context;
import org.sonar.api.server.rule.RuleDescriptionSection;
@@ -29,25 +31,42 @@ import org.sonar.core.util.UuidFactory;
import org.sonar.db.rule.RuleDescriptionSectionContextDto;
import org.sonar.db.rule.RuleDescriptionSectionDto;
-import static java.util.stream.Collectors.toSet;
+import static org.sonar.api.rules.RuleType.*;
public class AdvancedRuleDescriptionSectionsGenerator implements RuleDescriptionSectionsGenerator {
private final UuidFactory uuidFactory;
+ private final LegacyIssueRuleDescriptionSectionsGenerator legacyIssueRuleDescriptionSectionsGenerator;
- public AdvancedRuleDescriptionSectionsGenerator(UuidFactory uuidFactory) {
+ public AdvancedRuleDescriptionSectionsGenerator(UuidFactory uuidFactory, LegacyIssueRuleDescriptionSectionsGenerator legacyIssueRuleDescriptionSectionsGenerator) {
this.uuidFactory = uuidFactory;
+ this.legacyIssueRuleDescriptionSectionsGenerator = legacyIssueRuleDescriptionSectionsGenerator;
}
@Override
public boolean isGeneratorForRule(RulesDefinition.Rule rule) {
- return !rule.ruleDescriptionSections().isEmpty();
+ return !rule.ruleDescriptionSections().isEmpty() && skipHotspotRulesForSonar16635(rule);
+ }
+
+ private static boolean skipHotspotRulesForSonar16635(RulesDefinition.Rule rule) {
+ return !SECURITY_HOTSPOT.equals(rule.type());
}
@Override
public Set<RuleDescriptionSectionDto> generateSections(RulesDefinition.Rule rule) {
- return rule.ruleDescriptionSections().stream()
+ Set<RuleDescriptionSectionDto> advancedSections = rule.ruleDescriptionSections().stream()
.map(this::toRuleDescriptionSectionDto)
- .collect(toSet());
+ .collect(Collectors.toSet());
+ return addLegacySectionToAdvancedSections(advancedSections, rule);
+ }
+
+ /**
+ * This was done to preserve backward compatibility with SonarLint until they stop using htmlDesc field in api/rules/[show|search] endpoints, see SONAR-16635
+ * @deprecated the method should be removed once SonarLint supports rules.descriptionSections fields, I.E in 10.x
+ */
+ @Deprecated(since = "9.6", forRemoval = true)
+ private Set<RuleDescriptionSectionDto> addLegacySectionToAdvancedSections(Set<RuleDescriptionSectionDto> advancedSections, RulesDefinition.Rule rule) {
+ Set<RuleDescriptionSectionDto> legacySection = legacyIssueRuleDescriptionSectionsGenerator.generateSections(rule);
+ return Sets.union(advancedSections, legacySection);
}
private RuleDescriptionSectionDto toRuleDescriptionSectionDto(RuleDescriptionSection section) {
diff --git a/server/sonar-webserver-core/src/main/java/org/sonar/server/rule/LegacyHotspotRuleDescriptionSectionsGenerator.java b/server/sonar-webserver-core/src/main/java/org/sonar/server/rule/LegacyHotspotRuleDescriptionSectionsGenerator.java
index d0c93c7fdbc..e2d5675d236 100644
--- a/server/sonar-webserver-core/src/main/java/org/sonar/server/rule/LegacyHotspotRuleDescriptionSectionsGenerator.java
+++ b/server/sonar-webserver-core/src/main/java/org/sonar/server/rule/LegacyHotspotRuleDescriptionSectionsGenerator.java
@@ -35,6 +35,7 @@ import static org.sonar.api.rules.RuleType.SECURITY_HOTSPOT;
import static org.sonar.api.server.rule.RuleDescriptionSection.RuleDescriptionSectionKeys.ASSESS_THE_PROBLEM_SECTION_KEY;
import static org.sonar.api.server.rule.RuleDescriptionSection.RuleDescriptionSectionKeys.HOW_TO_FIX_SECTION_KEY;
import static org.sonar.api.server.rule.RuleDescriptionSection.RuleDescriptionSectionKeys.ROOT_CAUSE_SECTION_KEY;
+import static org.sonar.db.rule.RuleDescriptionSectionDto.createDefaultRuleDescriptionSection;
public class LegacyHotspotRuleDescriptionSectionsGenerator implements RuleDescriptionSectionsGenerator {
private final UuidFactory uuidFactory;
@@ -45,7 +46,9 @@ public class LegacyHotspotRuleDescriptionSectionsGenerator implements RuleDescri
@Override
public boolean isGeneratorForRule(RulesDefinition.Rule rule) {
- return SECURITY_HOTSPOT.equals(rule.type()) && rule.ruleDescriptionSections().isEmpty();
+ // To prevent compatibility issues with SonarLint, this Generator is used for all hotspots rules, regardless of if they expose advanced sections or not. See SONAR-16635.
+ // In the future, the generator should not be used for advanced rules (add condition && rule.ruleDescriptionSections().isEmpty())
+ return SECURITY_HOTSPOT.equals(rule.type());
}
@Override
@@ -65,6 +68,9 @@ public class LegacyHotspotRuleDescriptionSectionsGenerator implements RuleDescri
}
private Set<RuleDescriptionSectionDto> generateSections(String descriptionInHtml) {
+ if (descriptionInHtml.isEmpty()) {
+ return emptySet();
+ }
String[] split = extractSection("", descriptionInHtml);
String remainingText = split[0];
String ruleDescriptionSection = split[1];
@@ -101,7 +107,10 @@ public class LegacyHotspotRuleDescriptionSectionsGenerator implements RuleDescri
RuleDescriptionSectionDto assessSection = createSection(ASSESS_THE_PROBLEM_SECTION_KEY, askSection, sensitiveSection, noncompliantSection);
RuleDescriptionSectionDto fixSection = createSection(HOW_TO_FIX_SECTION_KEY, recommendedSection, compliantSection, seeSection);
- return Stream.of(rootSection, assessSection, fixSection)
+ // For backward compatibility with SonarLint, see SONAR-16635. Should be removed in 10.x
+ RuleDescriptionSectionDto defaultSection = createDefaultRuleDescriptionSection(uuidFactory.create(), descriptionInHtml);
+
+ return Stream.of(rootSection, assessSection, fixSection, defaultSection)
.filter(Objects::nonNull)
.collect(Collectors.toSet());
}
diff --git a/server/sonar-webserver-core/src/test/java/org/sonar/server/rule/AdvancedRuleDescriptionSectionsGeneratorTest.java b/server/sonar-webserver-core/src/test/java/org/sonar/server/rule/AdvancedRuleDescriptionSectionsGeneratorTest.java
index 08c4a4f4df8..b2c4425606a 100644
--- a/server/sonar-webserver-core/src/test/java/org/sonar/server/rule/AdvancedRuleDescriptionSectionsGeneratorTest.java
+++ b/server/sonar-webserver-core/src/test/java/org/sonar/server/rule/AdvancedRuleDescriptionSectionsGeneratorTest.java
@@ -91,65 +91,72 @@ public class AdvancedRuleDescriptionSectionsGeneratorTest {
@Mock
private RulesDefinition.Rule rule;
+ @Mock
+ private RuleDescriptionSectionDto LEGACY_SECTION;
+
+ @Mock
+ private LegacyIssueRuleDescriptionSectionsGenerator legacyIssueRuleDescriptionSectionsGenerator;
+
@InjectMocks
private AdvancedRuleDescriptionSectionsGenerator generator;
@Before
public void before() {
when(uuidFactory.create()).thenReturn(UUID_1).thenReturn(UUID_2);
+ when(legacyIssueRuleDescriptionSectionsGenerator.generateSections(rule)).thenReturn(Set.of(LEGACY_SECTION));
}
@Test
- public void generateSections_whenOneSection_createsOneSections() {
+ public void generateSections_whenOneSection_createsOneSectionAndDefault() {
when(rule.ruleDescriptionSections()).thenReturn(List.of(SECTION_1));
Set<RuleDescriptionSectionDto> ruleDescriptionSectionDtos = generator.generateSections(rule);
assertThat(ruleDescriptionSectionDtos)
.usingRecursiveFieldByFieldElementComparator()
- .containsOnly(EXPECTED_SECTION_1);
+ .containsExactlyInAnyOrder(EXPECTED_SECTION_1, LEGACY_SECTION);
}
@Test
- public void generateSections_whenTwoSections_createsTwoSections() {
+ public void generateSections_whenTwoSections_createsTwoSectionsAndDefault() {
when(rule.ruleDescriptionSections()).thenReturn(List.of(SECTION_1, SECTION_2));
Set<RuleDescriptionSectionDto> ruleDescriptionSectionDtos = generator.generateSections(rule);
assertThat(ruleDescriptionSectionDtos)
.usingRecursiveFieldByFieldElementComparator()
- .containsExactlyInAnyOrder(EXPECTED_SECTION_1, EXPECTED_SECTION_2);
+ .containsExactlyInAnyOrder(EXPECTED_SECTION_1, EXPECTED_SECTION_2, LEGACY_SECTION);
}
@Test
- public void generateSections_whenTwoContextSpecificSections_createsTwoSections() {
+ public void generateSections_whenTwoContextSpecificSections_createsTwoSectionsAndDefault() {
when(rule.ruleDescriptionSections()).thenReturn(List.of(SECTION_3_WITH_CTX_1, SECTION_3_WITH_CTX_2));
Set<RuleDescriptionSectionDto> ruleDescriptionSectionDtos = generator.generateSections(rule);
assertThat(ruleDescriptionSectionDtos)
.usingRecursiveFieldByFieldElementComparator()
- .containsExactlyInAnyOrder(EXPECTED_SECTION_3_WITH_CTX_1, EXPECTED_SECTION_3_WITH_CTX_2);
+ .containsExactlyInAnyOrder(EXPECTED_SECTION_3_WITH_CTX_1, EXPECTED_SECTION_3_WITH_CTX_2, LEGACY_SECTION);
}
@Test
- public void generateSections_whenContextSpecificSectionsAndNonContextSpecificSection_createsTwoSections() {
+ public void generateSections_whenContextSpecificSectionsAndNonContextSpecificSection_createsTwoSectionsAndDefault() {
when(rule.ruleDescriptionSections()).thenReturn(List.of(SECTION_1, SECTION_3_WITH_CTX_2));
Set<RuleDescriptionSectionDto> ruleDescriptionSectionDtos = generator.generateSections(rule);
assertThat(ruleDescriptionSectionDtos)
.usingRecursiveFieldByFieldElementComparator()
- .containsExactlyInAnyOrder(EXPECTED_SECTION_1, EXPECTED_SECTION_3_WITH_CTX_2);
+ .containsExactlyInAnyOrder(EXPECTED_SECTION_1, EXPECTED_SECTION_3_WITH_CTX_2, LEGACY_SECTION);
}
@Test
- public void generateSections_whenNoSections_returnsEmptySet() {
+ public void generateSections_whenNoSections_returnsDefault() {
when(rule.ruleDescriptionSections()).thenReturn(emptyList());
Set<RuleDescriptionSectionDto> ruleDescriptionSectionDtos = generator.generateSections(rule);
- assertThat(ruleDescriptionSectionDtos).isEmpty();
+ assertThat(ruleDescriptionSectionDtos).containsOnly(LEGACY_SECTION);
}
}
diff --git a/server/sonar-webserver-core/src/test/java/org/sonar/server/rule/LegacyHotspotRuleDescriptionSectionsGeneratorTest.java b/server/sonar-webserver-core/src/test/java/org/sonar/server/rule/LegacyHotspotRuleDescriptionSectionsGeneratorTest.java
index 6308f481229..dd4914f6d50 100644
--- a/server/sonar-webserver-core/src/test/java/org/sonar/server/rule/LegacyHotspotRuleDescriptionSectionsGeneratorTest.java
+++ b/server/sonar-webserver-core/src/test/java/org/sonar/server/rule/LegacyHotspotRuleDescriptionSectionsGeneratorTest.java
@@ -19,20 +19,16 @@
*/
package org.sonar.server.rule;
-import com.tngtech.java.junit.dataprovider.DataProvider;
-import com.tngtech.java.junit.dataprovider.DataProviderRunner;
-import com.tngtech.java.junit.dataprovider.UseDataProvider;
import java.util.Map;
import java.util.Set;
import org.junit.Before;
import org.junit.Test;
-import org.junit.runner.RunWith;
import org.sonar.api.server.rule.RulesDefinition;
import org.sonar.core.util.UuidFactory;
import org.sonar.db.rule.RuleDescriptionSectionDto;
+import org.sonar.markdown.Markdown;
import static java.util.stream.Collectors.toMap;
-import static org.apache.commons.lang.RandomStringUtils.randomAlphabetic;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@@ -40,7 +36,6 @@ import static org.sonar.api.server.rule.RuleDescriptionSection.RuleDescriptionSe
import static org.sonar.api.server.rule.RuleDescriptionSection.RuleDescriptionSectionKeys.HOW_TO_FIX_SECTION_KEY;
import static org.sonar.api.server.rule.RuleDescriptionSection.RuleDescriptionSectionKeys.ROOT_CAUSE_SECTION_KEY;
-@RunWith(DataProviderRunner.class)
public class LegacyHotspotRuleDescriptionSectionsGeneratorTest {
/*
@@ -84,6 +79,8 @@ public class LegacyHotspotRuleDescriptionSectionsGeneratorTest {
+ " @CrossOrigin(origins = \"http://domain2.com\") // Questionable\n" + " @RequestMapping(value = \"/test1\")\n" + " public ResponseEntity&lt;String&gt; test1() {\n"
+ " return ResponseEntity.ok().body(\"ok\");\n" + " }\n" + "}\n" + "</pre>\n";
+ private static final String DEFAULT_SECTION_KEY = "default";
+
private final UuidFactory uuidFactory = mock(UuidFactory.class);
private final RulesDefinition.Rule rule = mock(RulesDefinition.Rule.class);
@@ -114,21 +111,17 @@ public class LegacyHotspotRuleDescriptionSectionsGeneratorTest {
}
@Test
- @UseDataProvider("descriptionsWithoutTitles")
- public void parse_to_risk_description_fields_when_desc_contains_no_section(String description) {
- when(rule.htmlDescription()).thenReturn(description);
+ public void parse_to_risk_description_fields_when_desc_contains_no_section() {
+ String descriptionWithoutTitles = "description without titles";
+ when(rule.htmlDescription()).thenReturn(descriptionWithoutTitles);
Set<RuleDescriptionSectionDto> results = generator.generateSections(rule);
- assertThat(results).hasSize(1);
- RuleDescriptionSectionDto uniqueSection = results.iterator().next();
- assertThat(uniqueSection.getKey()).isEqualTo(ROOT_CAUSE_SECTION_KEY);
- assertThat(uniqueSection.getContent()).isEqualTo(description);
- }
+ Map<String, String> sectionKeyToContent = results.stream().collect(toMap(RuleDescriptionSectionDto::getKey, RuleDescriptionSectionDto::getContent));
+ assertThat(sectionKeyToContent).hasSize(2)
+ .containsEntry(ROOT_CAUSE_SECTION_KEY, descriptionWithoutTitles)
+ .containsEntry(DEFAULT_SECTION_KEY, rule.htmlDescription());
- @DataProvider
- public static Object[][] descriptionsWithoutTitles() {
- return new Object[][] {{randomAlphabetic(123)}, {"bar\n" + "acme\n" + "foo"}};
}
@Test
@@ -138,7 +131,8 @@ public class LegacyHotspotRuleDescriptionSectionsGeneratorTest {
Set<RuleDescriptionSectionDto> results = generator.generateSections(rule);
Map<String, String> sectionKeyToContent = results.stream().collect(toMap(RuleDescriptionSectionDto::getKey, RuleDescriptionSectionDto::getContent));
- assertThat(sectionKeyToContent).hasSize(2)
+ assertThat(sectionKeyToContent).hasSize(3)
+ .containsEntry(DEFAULT_SECTION_KEY, rule.htmlDescription())
.containsEntry(ASSESS_THE_PROBLEM_SECTION_KEY, ASKATRISK)
.containsEntry(HOW_TO_FIX_SECTION_KEY, RECOMMENTEDCODINGPRACTICE);
}
@@ -151,7 +145,8 @@ public class LegacyHotspotRuleDescriptionSectionsGeneratorTest {
Set<RuleDescriptionSectionDto> results = generator.generateSections(rule);
Map<String, String> sectionKeyToContent = results.stream().collect(toMap(RuleDescriptionSectionDto::getKey, RuleDescriptionSectionDto::getContent));
- assertThat(sectionKeyToContent).hasSize(2)
+ assertThat(sectionKeyToContent).hasSize(3)
+ .containsEntry(DEFAULT_SECTION_KEY, rule.htmlDescription())
.containsEntry(ROOT_CAUSE_SECTION_KEY, DESCRIPTION)
.containsEntry(HOW_TO_FIX_SECTION_KEY, RECOMMENTEDCODINGPRACTICE);
}
@@ -163,7 +158,8 @@ public class LegacyHotspotRuleDescriptionSectionsGeneratorTest {
Set<RuleDescriptionSectionDto> results = generator.generateSections(rule);
Map<String, String> sectionKeyToContent = results.stream().collect(toMap(RuleDescriptionSectionDto::getKey, RuleDescriptionSectionDto::getContent));
- assertThat(sectionKeyToContent).hasSize(2)
+ assertThat(sectionKeyToContent).hasSize(3)
+ .containsEntry(DEFAULT_SECTION_KEY, rule.htmlDescription())
.containsEntry(ROOT_CAUSE_SECTION_KEY, DESCRIPTION)
.containsEntry(ASSESS_THE_PROBLEM_SECTION_KEY, ASKATRISK);
}
@@ -175,7 +171,8 @@ public class LegacyHotspotRuleDescriptionSectionsGeneratorTest {
Set<RuleDescriptionSectionDto> results = generator.generateSections(rule);
Map<String, String> sectionKeyToContent = results.stream().collect(toMap(RuleDescriptionSectionDto::getKey, RuleDescriptionSectionDto::getContent));
- assertThat(sectionKeyToContent).hasSize(3)
+ assertThat(sectionKeyToContent).hasSize(4)
+ .containsEntry(DEFAULT_SECTION_KEY, rule.htmlDescription())
.containsEntry(ROOT_CAUSE_SECTION_KEY, DESCRIPTION)
.containsEntry(ASSESS_THE_PROBLEM_SECTION_KEY, NONCOMPLIANTCODE)
.containsEntry(HOW_TO_FIX_SECTION_KEY, COMPLIANTCODE);
@@ -188,7 +185,8 @@ public class LegacyHotspotRuleDescriptionSectionsGeneratorTest {
Set<RuleDescriptionSectionDto> results = generator.generateSections(rule);
Map<String, String> sectionKeyToContent = results.stream().collect(toMap(RuleDescriptionSectionDto::getKey, RuleDescriptionSectionDto::getContent));
- assertThat(sectionKeyToContent).hasSize(3)
+ assertThat(sectionKeyToContent).hasSize(4)
+ .containsEntry(DEFAULT_SECTION_KEY, rule.htmlDescription())
.containsEntry(ROOT_CAUSE_SECTION_KEY, DESCRIPTION)
.containsEntry(ASSESS_THE_PROBLEM_SECTION_KEY, NONCOMPLIANTCODE)
.containsEntry(HOW_TO_FIX_SECTION_KEY, RECOMMENTEDCODINGPRACTICE + SEE);
@@ -201,7 +199,8 @@ public class LegacyHotspotRuleDescriptionSectionsGeneratorTest {
Set<RuleDescriptionSectionDto> results = generator.generateSections(rule);
Map<String, String> sectionKeyToContent = results.stream().collect(toMap(RuleDescriptionSectionDto::getKey, RuleDescriptionSectionDto::getContent));
- assertThat(sectionKeyToContent).hasSize(3)
+ assertThat(sectionKeyToContent).hasSize(4)
+ .containsEntry(DEFAULT_SECTION_KEY, rule.htmlDescription())
.containsEntry(ROOT_CAUSE_SECTION_KEY, DESCRIPTION)
.containsEntry(ASSESS_THE_PROBLEM_SECTION_KEY, ASKATRISK + SENSITIVECODE)
.containsEntry(HOW_TO_FIX_SECTION_KEY, RECOMMENTEDCODINGPRACTICE + SEE);
@@ -225,7 +224,8 @@ public class LegacyHotspotRuleDescriptionSectionsGeneratorTest {
Set<RuleDescriptionSectionDto> results = generator.generateSections(rule);
Map<String, String> sectionKeyToContent = results.stream().collect(toMap(RuleDescriptionSectionDto::getKey, RuleDescriptionSectionDto::getContent));
- assertThat(sectionKeyToContent).hasSize(3)
+ assertThat(sectionKeyToContent).hasSize(4)
+ .containsEntry(DEFAULT_SECTION_KEY, Markdown.convertToHtml(rule.markdownDescription()))
.containsEntry(ROOT_CAUSE_SECTION_KEY, ruleDescription + "<br/>"
+ "<h2>Exceptions</h2>"
+ exceptionsContent + "<br/>")
diff --git a/server/sonar-webserver-core/src/test/java/org/sonar/server/rule/RuleDescriptionSectionsGeneratorsTest.java b/server/sonar-webserver-core/src/test/java/org/sonar/server/rule/RuleDescriptionSectionsGeneratorsTest.java
index 9687ab4f7a1..03ad1283b71 100644
--- a/server/sonar-webserver-core/src/test/java/org/sonar/server/rule/RuleDescriptionSectionsGeneratorsTest.java
+++ b/server/sonar-webserver-core/src/test/java/org/sonar/server/rule/RuleDescriptionSectionsGeneratorsTest.java
@@ -67,6 +67,8 @@ public class RuleDescriptionSectionsGeneratorsTest {
private static final RuleDescriptionSectionDto DEFAULT_MD_SECTION_1 = RuleDescriptionSectionDto.builder().uuid(UUID_1).key("default").content(MD_CONTENT).build();
private static final RuleDescriptionSectionDto HTML_SECTION_1 = RuleDescriptionSectionDto.builder().uuid(UUID_1).key(KEY_1).content(HTML_CONTENT).build();
private static final RuleDescriptionSectionDto HTML_SECTION_2 = RuleDescriptionSectionDto.builder().uuid(UUID_2).key(KEY_2).content(HTML_CONTENT).build();
+ private static final RuleDescriptionSectionDto LEGACY_HTML_SECTION = RuleDescriptionSectionDto.builder().uuid(UUID_2).key("default").content(HTML_CONTENT).build();
+ private static final RuleDescriptionSectionDto LEGACY_MD_SECTION = RuleDescriptionSectionDto.builder().uuid(UUID_2).key("default").content(MD_CONTENT).build();
@Parameterized.Parameters(name = "{index} = {0}")
public static List<RuleDescriptionGeneratorTestData> testData() {
@@ -80,21 +82,20 @@ public class RuleDescriptionSectionsGeneratorsTest {
aRuleOfType(VULNERABILITY).html(HTML_CONTENT).md(MD_CONTENT).expectedGenerator(LEGACY_ISSUE).addExpectedSection(DEFAULT_HTML_SECTION_1).build(),
// HOTSPOT
aRuleOfType(SECURITY_HOTSPOT).html(null).md(null).expectedGenerator(LEGACY_HOTSPOT).build(),
- aRuleOfType(SECURITY_HOTSPOT).html(HTML_CONTENT).md(null).expectedGenerator(LEGACY_HOTSPOT).addExpectedSection(DEFAULT_HTML_HOTSPOT_SECTION_1).build(),
- aRuleOfType(SECURITY_HOTSPOT).html(null).md(MD_CONTENT).expectedGenerator(LEGACY_HOTSPOT).addExpectedSection(DEFAULT_MD_HOTSPOT_SECTION_1).build(),
- aRuleOfType(SECURITY_HOTSPOT).html(HTML_CONTENT).md(MD_CONTENT).expectedGenerator(LEGACY_HOTSPOT).addExpectedSection(DEFAULT_HTML_HOTSPOT_SECTION_1).build(),
+ aRuleOfType(SECURITY_HOTSPOT).html(HTML_CONTENT).md(null).expectedGenerator(LEGACY_HOTSPOT).addExpectedSection(DEFAULT_HTML_HOTSPOT_SECTION_1).addExpectedSection(LEGACY_HTML_SECTION).build(),
+ aRuleOfType(SECURITY_HOTSPOT).html(null).md(MD_CONTENT).expectedGenerator(LEGACY_HOTSPOT).addExpectedSection(DEFAULT_MD_HOTSPOT_SECTION_1).addExpectedSection(LEGACY_MD_SECTION).build(),
+ aRuleOfType(SECURITY_HOTSPOT).html(HTML_CONTENT).md(MD_CONTENT).expectedGenerator(LEGACY_HOTSPOT).addExpectedSection(DEFAULT_HTML_HOTSPOT_SECTION_1).addExpectedSection(LEGACY_HTML_SECTION).build(),
// ADVANCED RULES
aRuleOfType(BUG).html(null).md(null).addSection(SECTION_1).expectedGenerator(ADVANCED_RULE).addExpectedSection(HTML_SECTION_1).build(),
- aRuleOfType(BUG).html(HTML_CONTENT).md(null).addSection(SECTION_1).expectedGenerator(ADVANCED_RULE).addExpectedSection(HTML_SECTION_1).build(),
- aRuleOfType(BUG).html(null).md(MD_CONTENT).addSection(SECTION_1).expectedGenerator(ADVANCED_RULE).addExpectedSection(HTML_SECTION_1).build(),
- aRuleOfType(BUG).html(HTML_CONTENT).md(MD_CONTENT).addSection(SECTION_1).expectedGenerator(ADVANCED_RULE).addExpectedSection(HTML_SECTION_1).build(),
- aRuleOfType(BUG).html(HTML_CONTENT).md(MD_CONTENT).addSection(SECTION_1).addSection(SECTION_2).expectedGenerator(ADVANCED_RULE).addExpectedSection(HTML_SECTION_1)
- .addExpectedSection(
- HTML_SECTION_2).build(),
- aRuleOfType(SECURITY_HOTSPOT).html(null).md(null).addSection(SECTION_1).expectedGenerator(ADVANCED_RULE).addExpectedSection(HTML_SECTION_1).build(),
- aRuleOfType(SECURITY_HOTSPOT).html(HTML_CONTENT).md(null).addSection(SECTION_1).expectedGenerator(ADVANCED_RULE).addExpectedSection(HTML_SECTION_1).build(),
- aRuleOfType(SECURITY_HOTSPOT).html(null).md(MD_CONTENT).addSection(SECTION_1).expectedGenerator(ADVANCED_RULE).addExpectedSection(HTML_SECTION_1).build(),
- aRuleOfType(SECURITY_HOTSPOT).html(HTML_CONTENT).md(MD_CONTENT).addSection(SECTION_1).expectedGenerator(ADVANCED_RULE).addExpectedSection(HTML_SECTION_1).build()
+ aRuleOfType(BUG).html(HTML_CONTENT).md(null).addSection(SECTION_1).expectedGenerator(ADVANCED_RULE).addExpectedSection(HTML_SECTION_1).addExpectedSection(LEGACY_HTML_SECTION).build(),
+ aRuleOfType(BUG).html(null).md(MD_CONTENT).addSection(SECTION_1).expectedGenerator(ADVANCED_RULE).addExpectedSection(HTML_SECTION_1).addExpectedSection(LEGACY_MD_SECTION).build(),
+ aRuleOfType(BUG).html(HTML_CONTENT).md(MD_CONTENT).addSection(SECTION_1).expectedGenerator(ADVANCED_RULE).addExpectedSection(HTML_SECTION_1).addExpectedSection(LEGACY_HTML_SECTION).build(),
+ aRuleOfType(BUG).html(HTML_CONTENT).md(MD_CONTENT).addSection(SECTION_1).addSection(SECTION_2).expectedGenerator(ADVANCED_RULE)
+ .addExpectedSection(HTML_SECTION_1).addExpectedSection(HTML_SECTION_2).addExpectedSection(LEGACY_HTML_SECTION).build(),
+ aRuleOfType(SECURITY_HOTSPOT).html(null).md(null).addSection(SECTION_1).expectedGenerator(LEGACY_HOTSPOT).build(),
+ aRuleOfType(SECURITY_HOTSPOT).html(HTML_CONTENT).md(null).addSection(SECTION_1).expectedGenerator(LEGACY_HOTSPOT).addExpectedSection(DEFAULT_HTML_HOTSPOT_SECTION_1).addExpectedSection(LEGACY_HTML_SECTION).build(),
+ aRuleOfType(SECURITY_HOTSPOT).html(null).md(MD_CONTENT).addSection(SECTION_1).expectedGenerator(LEGACY_HOTSPOT).addExpectedSection(DEFAULT_MD_HOTSPOT_SECTION_1).addExpectedSection(LEGACY_MD_SECTION).build(),
+ aRuleOfType(SECURITY_HOTSPOT).html(HTML_CONTENT).md(MD_CONTENT).addSection(SECTION_1).expectedGenerator(LEGACY_HOTSPOT).addExpectedSection(DEFAULT_HTML_HOTSPOT_SECTION_1).addExpectedSection(LEGACY_HTML_SECTION).build()
);
}
@@ -103,9 +104,9 @@ public class RuleDescriptionSectionsGeneratorsTest {
private final RuleDescriptionGeneratorTestData testData;
- private final RuleDescriptionSectionsGenerator advancedRuleDescriptionSectionsGenerator = new AdvancedRuleDescriptionSectionsGenerator(uuidFactory);
private final RuleDescriptionSectionsGenerator legacyHotspotRuleDescriptionSectionsGenerator = new LegacyHotspotRuleDescriptionSectionsGenerator(uuidFactory);
- private final RuleDescriptionSectionsGenerator legacyIssueRuleDescriptionSectionsGenerator = new LegacyIssueRuleDescriptionSectionsGenerator(uuidFactory);
+ private final LegacyIssueRuleDescriptionSectionsGenerator legacyIssueRuleDescriptionSectionsGenerator = new LegacyIssueRuleDescriptionSectionsGenerator(uuidFactory);
+ private final RuleDescriptionSectionsGenerator advancedRuleDescriptionSectionsGenerator = new AdvancedRuleDescriptionSectionsGenerator(uuidFactory, legacyIssueRuleDescriptionSectionsGenerator);
Map<RuleDescriptionSectionGeneratorIdentifier, RuleDescriptionSectionsGenerator> idToGenerator = ImmutableMap.<RuleDescriptionSectionGeneratorIdentifier, RuleDescriptionSectionsGenerator>builder()
.put(ADVANCED_RULE, advancedRuleDescriptionSectionsGenerator)
diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/rule/ws/RuleMapper.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/rule/ws/RuleMapper.java
index fa8f88e5382..18c7ff3cfba 100644
--- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/rule/ws/RuleMapper.java
+++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/rule/ws/RuleMapper.java
@@ -348,13 +348,12 @@ public class RuleMapper {
}
if (shouldReturnField(fieldsToReturn, FIELD_DESCRIPTION_SECTIONS)) {
- for (var section : ruleDto.getRuleDescriptionSectionDtos()) {
- Rules.Rule.DescriptionSection.Builder sectionBuilder = ruleResponse.getDescriptionSectionsBuilder().addDescriptionSectionsBuilder()
- .setKey(section.getKey())
- .setContent(retrieveDescriptionContent(ruleDto.getDescriptionFormat(), section));
- toProtobufContext(section.getContext()).ifPresent(sectionBuilder::setContext);
- sectionBuilder.build();
- }
+ Set<RuleDescriptionSectionDto> ruleDescriptionSectionDtos = ruleDto.getRuleDescriptionSectionDtos();
+ Set<Rules.Rule.DescriptionSection> sections = ruleDescriptionSectionDtos.stream()
+ .filter(sectionDto -> !isDefaultAndMoreThanOneSectionPresent(ruleDescriptionSectionDtos, sectionDto))
+ .map(sectionDto -> toDescriptionSection(ruleDto, sectionDto))
+ .collect(Collectors.toSet());
+ ruleResponse.setDescriptionSections(Rules.Rule.DescriptionSections.newBuilder().addAllDescriptionSections(sections).build());
}
if (shouldReturnField(fieldsToReturn, FIELD_MARKDOWN_DESCRIPTION)) {
@@ -368,6 +367,23 @@ public class RuleMapper {
}
}
+ /**
+ * This was done to preserve backward compatibility with SonarLint until they stop using htmlDesc field in api/rules/[show|search] endpoints, see SONAR-16635
+ * @deprecated the method should be removed once SonarLint supports rules.descriptionSections fields, I.E in 10.x and DB is cleaned up of non-necessary default sections.
+ */
+ @Deprecated(since = "9.6", forRemoval = true)
+ private static boolean isDefaultAndMoreThanOneSectionPresent(Set<RuleDescriptionSectionDto> ruleDescriptionSectionDtos, RuleDescriptionSectionDto s) {
+ return ruleDescriptionSectionDtos.size() > 1 && s.isDefault();
+ }
+
+ private static Rules.Rule.DescriptionSection toDescriptionSection(RuleDto ruleDto, RuleDescriptionSectionDto section) {
+ Rules.Rule.DescriptionSection.Builder sectionBuilder = Rules.Rule.DescriptionSection.newBuilder()
+ .setKey(section.getKey())
+ .setContent(retrieveDescriptionContent(ruleDto.getDescriptionFormat(), section));
+ toProtobufContext(section.getContext()).ifPresent(sectionBuilder::setContext);
+ return sectionBuilder.build();
+ }
+
private static String retrieveDescriptionContent(@Nullable RuleDto.Format format, RuleDescriptionSectionDto sectionDto) {
return MARKDOWN.equals(format) ?
Markdown.convertToHtml(sectionDto.getContent()) :
diff --git a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/rule/ws/ShowActionTest.java b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/rule/ws/ShowActionTest.java
index 2cfd6cbe501..0b0dd9fc73f 100644
--- a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/rule/ws/ShowActionTest.java
+++ b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/rule/ws/ShowActionTest.java
@@ -370,7 +370,6 @@ public class ShowActionTest {
RuleDescriptionSectionDto section4context1 = createRuleDescriptionSectionWithContext(RESOURCES_SECTION_KEY, "<div>I want to fix with Spring</div>", "ctx1");
RuleDescriptionSectionDto section4context2 = createRuleDescriptionSectionWithContext(RESOURCES_SECTION_KEY, "<div>I want to fix with Servlet</div>", "ctx2");
-
RuleDto rule = createRuleWithDescriptionSections(section1, section2, section3, section4context1, section4context2);
rule.setType(RuleType.SECURITY_HOTSPOT);
rule.setNoteUserUuid(userDto.getUuid());
@@ -381,17 +380,7 @@ public class ShowActionTest {
.executeProtobuf(ShowResponse.class);
Rule resultRule = result.getRule();
- assertThat(resultRule.getHtmlDesc())
- .contains(
- "<h2>What is the risk?</h2>"
- + "<div>Root is Root</div><br/>"
- + "<h2>Assess the risk</h2>"
- + "<div>This is not a problem</div><br/>"
- + "<h2>How can you fix it?</h2>"
- + "<div>I don't want to fix</div><br/>"
- + "<div>I want to fix with Spring</div>"
- );
-
+ assertThat(resultRule.getHtmlDesc()).isEmpty();
assertThat(resultRule.getMdDesc()).isEqualTo(resultRule.getHtmlDesc());
assertThat(resultRule.getDescriptionSections().getDescriptionSectionsList())
@@ -406,6 +395,32 @@ public class ShowActionTest {
}
@Test
+ public void show_if_advanced_sections_and_default_filters_out_default() {
+ when(macroInterpreter.interpret(anyString())).thenAnswer(invocation -> invocation.getArgument(0));
+
+ RuleDescriptionSectionDto section1 = createRuleDescriptionSection(ROOT_CAUSE_SECTION_KEY, "<div>Root is Root</div>");
+ RuleDescriptionSectionDto defaultSection = createDefaultRuleDescriptionSection(uuidFactory.create(), "This is the default section");
+
+ RuleDto rule = createRuleWithDescriptionSections(section1, defaultSection);
+ rule.setType(RuleType.SECURITY_HOTSPOT);
+ rule.setNoteUserUuid(userDto.getUuid());
+ db.rules().insert(rule);
+
+ ShowResponse result = ws.newRequest()
+ .setParam(PARAM_KEY, rule.getKey().toString())
+ .executeProtobuf(ShowResponse.class);
+
+ Rule resultRule = result.getRule();
+ assertThat(resultRule.getHtmlDesc()).contains(defaultSection.getContent());
+
+ assertThat(resultRule.getMdDesc()).isEqualTo(resultRule.getHtmlDesc());
+
+ assertThat(resultRule.getDescriptionSections().getDescriptionSectionsList())
+ .extracting(Rule.DescriptionSection::getKey, Rule.DescriptionSection::getContent)
+ .containsExactlyInAnyOrder(tuple(ROOT_CAUSE_SECTION_KEY, "<div>Root is Root</div>"));
+ }
+
+ @Test
public void show_rule_markdown_description() {
when(macroInterpreter.interpret(anyString())).thenAnswer(invocation -> invocation.getArgument(0));