dependency 'org.sonarsource.kotlin:sonar-kotlin-plugin:2.9.0.1147'
dependency 'org.sonarsource.slang:sonar-ruby-plugin:1.9.0.3429'
dependency 'org.sonarsource.slang:sonar-scala-plugin:1.9.0.3429'
- dependency 'org.sonarsource.api.plugin:sonar-plugin-api:9.5.0.71'
+ dependency 'org.sonarsource.api.plugin:sonar-plugin-api:9.6.1.114'
dependency 'org.sonarsource.xml:sonar-xml-plugin:2.5.0.3376'
dependency 'org.sonarsource.iac:sonar-iac-plugin:1.7.0.2012'
dependency 'org.sonarsource.text:sonar-text-plugin:1.0.0.120'
private String internalKey;
private String language;
private boolean isExternal;
+
private int type;
+
private long createdAt;
private long updatedAt;
-
private Set<RuleDescriptionSectionDto> ruleDescriptionSectionsDtos = new HashSet<>();
@VisibleForTesting
public void setTemplateRepository(String templateRepository) {
this.templateRepository = templateRepository;
}
+
+ public void setType(int type) {
+ this.type = type;
+ }
}
package org.sonar.server.rule;
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.RuleDescriptionSectionDto;
import org.sonar.db.rule.RuleDto;
import org.sonar.markdown.Markdown;
import static com.google.common.collect.MoreCollectors.toOptional;
-import static java.lang.String.format;
+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's 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"
+ );
+
private RuleDescriptionFormatter() { /* static helpers */ }
+ @CheckForNull
public static String getDescriptionAsHtml(RuleDto ruleDto) {
if (ruleDto.getDescriptionFormat() == null) {
return null;
}
Collection<RuleDescriptionSectionDto> ruleDescriptionSectionDtos = ruleDto.getRuleDescriptionSectionDtos();
- return retrieveDescription(ruleDescriptionSectionDtos, ruleDto.getRuleKey(), Objects.requireNonNull(ruleDto.getDescriptionFormat()));
+ return retrieveDescription(ruleDescriptionSectionDtos, RuleType.valueOf(ruleDto.getType()), Objects.requireNonNull(ruleDto.getDescriptionFormat()));
}
+ @CheckForNull
private static String retrieveDescription(Collection<RuleDescriptionSectionDto> ruleDescriptionSectionDtos,
- String ruleKey, RuleDto.Format descriptionFormat) {
- Optional<RuleDescriptionSectionDto> ruleDescriptionSectionDto = findDefaultDescription(ruleDescriptionSectionDtos);
- return ruleDescriptionSectionDto
- .map(ruleDescriptionSection -> toHtml(ruleKey, descriptionFormat, ruleDescriptionSection))
- .orElse(null);
+ RuleType ruleType, RuleDto.Format descriptionFormat) {
+ if (ruleDescriptionSectionDtos.isEmpty()) {
+ return null;
+ }
+ Map<String, String> sectionKeyToHtml = ruleDescriptionSectionDtos.stream()
+ .collect(toMap(RuleDescriptionSectionDto::getKey, section -> toHtml(descriptionFormat, section)));
+ if (sectionKeyToHtml.containsKey(DEFAULT_KEY)) {
+ return sectionKeyToHtml.get(DEFAULT_KEY);
+ } else {
+ return concatHtmlSections(sectionKeyToHtml, ruleType);
+ }
}
- private static Optional<RuleDescriptionSectionDto> findDefaultDescription(Collection<RuleDescriptionSectionDto> ruleDescriptionSectionDtos) {
- return ruleDescriptionSectionDtos.stream()
- .filter(RuleDescriptionSectionDto::isDefault)
- .collect(toOptional());
+ 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)) {
+ builder.append("<h2>")
+ .append(titleMapping.get(sectionKey))
+ .append("</h2>")
+ .append(sectionKeyToHtml.get(sectionKey))
+ .append("<br/>");
+ }
+ }
+ return builder.toString();
}
- private static String toHtml(String ruleKey, RuleDto.Format descriptionFormat, RuleDescriptionSectionDto ruleDescriptionSectionDto) {
- switch (descriptionFormat) {
- case MARKDOWN:
- return Markdown.convertToHtml(ruleDescriptionSectionDto.getContent());
- case HTML:
- return ruleDescriptionSectionDto.getContent();
- default:
- throw new IllegalStateException(format("Rule description section format '%s' is unknown for rule key '%s'", descriptionFormat, ruleKey));
+ private static String toHtml(RuleDto.Format descriptionFormat, RuleDescriptionSectionDto ruleDescriptionSectionDto) {
+ if (MARKDOWN.equals(descriptionFormat)) {
+ return Markdown.convertToHtml(ruleDescriptionSectionDto.getContent());
+ } else {
+ return ruleDescriptionSectionDto.getContent();
}
}
package org.sonar.server.rule;
import org.junit.Test;
+import org.sonar.api.rules.RuleType;
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.ROOT_CAUSE_SECTION_KEY;
import static org.sonar.db.rule.RuleDescriptionSectionDto.createDefaultRuleDescriptionSection;
public class RuleDescriptionFormatterTest {
@Test
public void getMarkdownDescriptionAsHtml() {
- RuleDto rule = new RuleDto().setDescriptionFormat(RuleDto.Format.MARKDOWN).addRuleDescriptionSectionDto(MARKDOWN_SECTION);
+ RuleDto rule = new RuleDto().setDescriptionFormat(RuleDto.Format.MARKDOWN).addRuleDescriptionSectionDto(MARKDOWN_SECTION).setType(RuleType.BUG);
String html = RuleDescriptionFormatter.getDescriptionAsHtml(rule);
assertThat(html).isEqualTo("<strong>md</strong> <code>description</code>");
}
@Test
public void getHtmlDescriptionAsIs() {
- RuleDto rule = new RuleDto().setDescriptionFormat(RuleDto.Format.HTML).addRuleDescriptionSectionDto(HTML_SECTION);
+ RuleDto rule = new RuleDto().setDescriptionFormat(RuleDto.Format.HTML).addRuleDescriptionSectionDto(HTML_SECTION).setType(RuleType.BUG);
String html = RuleDescriptionFormatter.getDescriptionAsHtml(rule);
assertThat(html).isEqualTo(HTML_SECTION.getContent());
}
+ @Test
+ public void concatHtmlDescriptionSections() {
+ 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>");
+ RuleDto rule = new RuleDto().setDescriptionFormat(RuleDto.Format.HTML)
+ .setType(RuleType.SECURITY_HOTSPOT)
+ .addRuleDescriptionSectionDto(section1)
+ .addRuleDescriptionSectionDto(section2)
+ .addRuleDescriptionSectionDto(section3);
+ String html = RuleDescriptionFormatter.getDescriptionAsHtml(rule);
+ assertThat(html)
+ .contains(
+ "<h2>What's 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/>"
+ );
+ }
+
@Test
public void handleEmptyDescription() {
- RuleDto rule = new RuleDto().setDescriptionFormat(RuleDto.Format.HTML);
+ RuleDto rule = new RuleDto().setDescriptionFormat(RuleDto.Format.HTML).setType(RuleType.BUG);
String result = RuleDescriptionFormatter.getDescriptionAsHtml(rule);
assertThat(result).isNull();
}
@Test
public void handleNullDescriptionFormat() {
RuleDescriptionSectionDto sectionWithNullFormat = createDefaultRuleDescriptionSection("uuid", "whatever");
- RuleDto rule = new RuleDto().addRuleDescriptionSectionDto(sectionWithNullFormat);
+ RuleDto rule = new RuleDto().addRuleDescriptionSectionDto(sectionWithNullFormat).setType(RuleType.BUG);
String result = RuleDescriptionFormatter.getDescriptionAsHtml(rule);
assertThat(result).isNull();
}
+ private static RuleDescriptionSectionDto createRuleDescriptionSection(String key, String content) {
+ return RuleDescriptionSectionDto.builder().key(key).content(content).build();
+ }
}
responseBuilder.setCanChangeStatus(hotspotWsSupport.canChangeStatus(components.getProject()));
}
- private static void formatRule(ShowWsResponse.Builder responseBuilder, RuleDto ruleDefinitionDto) {
- SecurityStandards securityStandards = SecurityStandards.fromSecurityStandards(ruleDefinitionDto.getSecurityStandards());
+ private static void formatRule(ShowWsResponse.Builder responseBuilder, RuleDto ruleDto) {
+ SecurityStandards securityStandards = SecurityStandards.fromSecurityStandards(ruleDto.getSecurityStandards());
SecurityStandards.SQCategory sqCategory = securityStandards.getSqCategory();
Hotspots.Rule.Builder ruleBuilder = Hotspots.Rule.newBuilder()
- .setKey(ruleDefinitionDto.getKey().toString())
- .setName(nullToEmpty(ruleDefinitionDto.getName()))
+ .setKey(ruleDto.getKey().toString())
+ .setName(nullToEmpty(ruleDto.getName()))
.setSecurityCategory(sqCategory.getKey())
.setVulnerabilityProbability(sqCategory.getVulnerability().name());
- HotspotRuleDescription hotspotRuleDescription = HotspotRuleDescription.from(ruleDefinitionDto);
+ HotspotRuleDescription hotspotRuleDescription = HotspotRuleDescription.from(ruleDto);
hotspotRuleDescription.getVulnerable().ifPresent(ruleBuilder::setVulnerabilityDescription);
hotspotRuleDescription.getRisk().ifPresent(ruleBuilder::setRiskDescription);
hotspotRuleDescription.getFixIt().ifPresent(ruleBuilder::setFixRecommendations);
import java.util.List;
import java.util.Locale;
import java.util.Map;
+import java.util.Optional;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;
import javax.annotation.CheckForNull;
+import javax.annotation.Nullable;
import org.sonar.api.resources.Language;
import org.sonar.api.resources.Languages;
import org.sonar.api.rule.RuleKey;
import static java.util.stream.Collectors.joining;
import static org.sonar.api.utils.DateUtils.formatDateTime;
import static org.sonar.core.util.stream.MoreCollectors.toList;
+import static org.sonar.db.rule.RuleDto.Format.MARKDOWN;
import static org.sonar.server.rule.ws.RulesWsParameters.FIELD_CREATED_AT;
import static org.sonar.server.rule.ws.RulesWsParameters.FIELD_DEBT_OVERLOADED;
import static org.sonar.server.rule.ws.RulesWsParameters.FIELD_DEBT_REM_FUNCTION;
import static org.sonar.server.rule.ws.RulesWsParameters.FIELD_DEFAULT_DEBT_REM_FUNCTION;
import static org.sonar.server.rule.ws.RulesWsParameters.FIELD_DEFAULT_REM_FUNCTION;
import static org.sonar.server.rule.ws.RulesWsParameters.FIELD_DEPRECATED_KEYS;
+import static org.sonar.server.rule.ws.RulesWsParameters.FIELD_DESCRIPTION_SECTIONS;
import static org.sonar.server.rule.ws.RulesWsParameters.FIELD_EFFORT_TO_FIX_DESCRIPTION;
import static org.sonar.server.rule.ws.RulesWsParameters.FIELD_GAP_DESCRIPTION;
import static org.sonar.server.rule.ws.RulesWsParameters.FIELD_HTML_DESCRIPTION;
return ruleResponse.build();
}
- private Rules.Rule.Builder applyRuleDefinition(Rules.Rule.Builder ruleResponse, RuleDto ruleDefinitionDto, SearchResult result,
+ private Rules.Rule.Builder applyRuleDefinition(Rules.Rule.Builder ruleResponse, RuleDto ruleDto, SearchResult result,
Set<String> fieldsToReturn, Map<String, List<DeprecatedRuleKeyDto>> deprecatedRuleKeysByRuleUuid) {
// Mandatory fields
- ruleResponse.setKey(ruleDefinitionDto.getKey().toString());
- ruleResponse.setType(Common.RuleType.forNumber(ruleDefinitionDto.getType()));
+ ruleResponse.setKey(ruleDto.getKey().toString());
+ ruleResponse.setType(Common.RuleType.forNumber(ruleDto.getType()));
// Optional fields
- setName(ruleResponse, ruleDefinitionDto, fieldsToReturn);
- setRepository(ruleResponse, ruleDefinitionDto, fieldsToReturn);
- setStatus(ruleResponse, ruleDefinitionDto, fieldsToReturn);
- setSysTags(ruleResponse, ruleDefinitionDto, fieldsToReturn);
- setParams(ruleResponse, ruleDefinitionDto, result, fieldsToReturn);
- setCreatedAt(ruleResponse, ruleDefinitionDto, fieldsToReturn);
- setDescriptionFields(ruleResponse, ruleDefinitionDto, fieldsToReturn);
- setSeverity(ruleResponse, ruleDefinitionDto, fieldsToReturn);
- setInternalKey(ruleResponse, ruleDefinitionDto, fieldsToReturn);
- setLanguage(ruleResponse, ruleDefinitionDto, fieldsToReturn);
- setLanguageName(ruleResponse, ruleDefinitionDto, fieldsToReturn);
- setIsTemplate(ruleResponse, ruleDefinitionDto, fieldsToReturn);
- setIsExternal(ruleResponse, ruleDefinitionDto, fieldsToReturn);
- setTemplateKey(ruleResponse, ruleDefinitionDto, result, fieldsToReturn);
- setDefaultDebtRemediationFunctionFields(ruleResponse, ruleDefinitionDto, fieldsToReturn);
- setEffortToFixDescription(ruleResponse, ruleDefinitionDto, fieldsToReturn);
- setScope(ruleResponse, ruleDefinitionDto, fieldsToReturn);
- setDeprecatedKeys(ruleResponse, ruleDefinitionDto, fieldsToReturn, deprecatedRuleKeysByRuleUuid);
+ setName(ruleResponse, ruleDto, fieldsToReturn);
+ setRepository(ruleResponse, ruleDto, fieldsToReturn);
+ setStatus(ruleResponse, ruleDto, fieldsToReturn);
+ setSysTags(ruleResponse, ruleDto, fieldsToReturn);
+ setParams(ruleResponse, ruleDto, result, fieldsToReturn);
+ setCreatedAt(ruleResponse, ruleDto, fieldsToReturn);
+ setDescriptionFields(ruleResponse, ruleDto, fieldsToReturn);
+ setSeverity(ruleResponse, ruleDto, fieldsToReturn);
+ setInternalKey(ruleResponse, ruleDto, fieldsToReturn);
+ setLanguage(ruleResponse, ruleDto, fieldsToReturn);
+ setLanguageName(ruleResponse, ruleDto, fieldsToReturn);
+ setIsTemplate(ruleResponse, ruleDto, fieldsToReturn);
+ setIsExternal(ruleResponse, ruleDto, fieldsToReturn);
+ setTemplateKey(ruleResponse, ruleDto, result, fieldsToReturn);
+ setDefaultDebtRemediationFunctionFields(ruleResponse, ruleDto, fieldsToReturn);
+ setEffortToFixDescription(ruleResponse, ruleDto, fieldsToReturn);
+ setScope(ruleResponse, ruleDto, fieldsToReturn);
+ setDeprecatedKeys(ruleResponse, ruleDto, fieldsToReturn, deprecatedRuleKeysByRuleUuid);
return ruleResponse;
}
}
}
- if (shouldReturnField(fieldsToReturn, FIELD_MARKDOWN_DESCRIPTION)
- && !ruleDto.getRuleDescriptionSectionDtos().isEmpty()) {
- String description = concatenateSectionTemporaryForSonar16302(ruleDto);
- ruleResponse.setMdDesc(description);
+ if (shouldReturnField(fieldsToReturn, FIELD_DESCRIPTION_SECTIONS)) {
+ for (var section : ruleDto.getRuleDescriptionSectionDtos()) {
+ ruleResponse.addDescriptionSectionsBuilder()
+ .setKey(section.getKey())
+ .setContent(retrieveDescriptionContent(ruleDto.getDescriptionFormat(), section))
+ .build();
+ }
+ }
+
+ if (shouldReturnField(fieldsToReturn, FIELD_MARKDOWN_DESCRIPTION)) {
+ if (MARKDOWN.equals(ruleDto.getDescriptionFormat())) {
+ Optional.ofNullable(ruleDto.getDefaultRuleDescriptionSection())
+ .map(RuleDescriptionSectionDto::getContent)
+ .ifPresent(ruleResponse::setMdDesc);
+ } else {
+ ruleResponse.setMdDesc(ruleResponse.getHtmlDesc());
+ }
}
}
- private static String concatenateSectionTemporaryForSonar16302(RuleDto ruleDto) {
- return ruleDto.getRuleDescriptionSectionDtos().stream()
- .map(RuleDescriptionSectionDto::getContent)
- .collect(joining());
+ private static String retrieveDescriptionContent(@Nullable RuleDto.Format format, RuleDescriptionSectionDto sectionDto) {
+ return MARKDOWN.equals(format) ?
+ Markdown.convertToHtml(sectionDto.getContent()) :
+ sectionDto.getContent();
}
private void setNotesFields(Rules.Rule.Builder ruleResponse, RuleMetadataDto ruleDto, Map<String, UserDto> usersByUuid, Set<String> fieldsToReturn) {
*/
package org.sonar.server.rule.ws;
-import com.google.common.collect.ImmutableSet;
import java.util.Set;
public class RulesWsParameters {
public static final String FIELD_LANGUAGE_NAME = "langName";
public static final String FIELD_HTML_DESCRIPTION = "htmlDesc";
public static final String FIELD_MARKDOWN_DESCRIPTION = "mdDesc";
+
+ public static final String FIELD_DESCRIPTION_SECTIONS = "descriptionSections";
public static final String FIELD_NOTE_LOGIN = "noteLogin";
public static final String FIELD_MARKDOWN_NOTE = "mdNote";
public static final String FIELD_HTML_NOTE = "htmlNote";
public static final String FIELD_DEPRECATED_KEYS = "deprecatedKeys";
- public static final Set<String> OPTIONAL_FIELDS = ImmutableSet.of(FIELD_REPO, FIELD_NAME, FIELD_CREATED_AT, FIELD_UPDATED_AT, FIELD_SEVERITY, FIELD_STATUS, FIELD_INTERNAL_KEY,
+ public static final Set<String> OPTIONAL_FIELDS = Set.of(FIELD_REPO, FIELD_NAME, FIELD_CREATED_AT, FIELD_UPDATED_AT, FIELD_SEVERITY, FIELD_STATUS, FIELD_INTERNAL_KEY,
FIELD_IS_EXTERNAL, FIELD_IS_TEMPLATE, FIELD_TEMPLATE_KEY, FIELD_TAGS, FIELD_SYSTEM_TAGS, FIELD_LANGUAGE, FIELD_LANGUAGE_NAME, FIELD_HTML_DESCRIPTION,
- FIELD_MARKDOWN_DESCRIPTION, FIELD_NOTE_LOGIN, FIELD_MARKDOWN_NOTE, FIELD_HTML_NOTE,
+ FIELD_MARKDOWN_DESCRIPTION, FIELD_DESCRIPTION_SECTIONS, FIELD_NOTE_LOGIN, FIELD_MARKDOWN_NOTE, FIELD_HTML_NOTE,
FIELD_DEFAULT_DEBT_REM_FUNCTION, FIELD_EFFORT_TO_FIX_DESCRIPTION, FIELD_DEBT_OVERLOADED, FIELD_DEBT_REM_FUNCTION,
FIELD_DEFAULT_REM_FUNCTION, FIELD_GAP_DESCRIPTION, FIELD_REM_FUNCTION_OVERLOADED, FIELD_REM_FUNCTION,
FIELD_PARAMS, FIELD_ACTIVES, FIELD_SCOPE, FIELD_DEPRECATED_KEYS);
"<li>\"debtRemFnOffset\" becomes \"remFnBaseEffort\"</li>" +
"<li>\"defaultDebtRemFnOffset\" becomes \"defaultRemFnBaseEffort\"</li>" +
"<li>\"debtOverloaded\" becomes \"remFnOverloaded\"</li>" +
+ "</ul><br/>" +
+ "Since 9.5 :" +
+ "<ul>" +
+ "<li>the field \"htmlDesc\" has been deprecated.</li>" +
+ "<li>the field \"descriptionSections\" has been added.</li>" +
"</ul>")
.setResponseExample(getClass().getResource("search-example.json"))
.setSince("4.4")
"<li>\"defaultDebtRemFnOffset\" becomes \"defaultRemFnBaseEffort\"</li>" +
"<li>\"debtOverloaded\" becomes \"remFnOverloaded\"</li>" +
"</ul>" +
- "In 7.1, the field 'scope' has been added.")
+ "In 7.1, the field 'scope' has been added.<br/>" +
+ "Since 9.5 :" +
+ "<ul>" +
+ "<li>the field \"htmlDesc\" has been deprecated.</li>" +
+ "<li>the field \"descriptionSections\" has been added.</li>" +
+ "</ul>")
.setSince("4.2")
.setResponseExample(Resources.getResource(getClass(), "show-example.json"))
.setHandler(this);
"scope": "MAIN",
"isExternal": false,
"type": "CODE_SMELL",
+ "descriptionSections": [
+ {
+ "key": "Why is this an issue ?",
+ "content": "<h3 class=\"page-title coding-rules-detail-header\"><big>Unnecessary imports should be removed</big></h3>"
+ },
+ {
+ "key": "How to fix it ?",
+ "content": "<h2>Recommended Secure Coding Practices</h2><ul><li> activate Spring Security's CSRF protection. </li></ul>"
+ }
+ ],
"params": [
{
"key": "max",
"scope": "MAIN",
"isExternal": false,
"type": "CODE_SMELL",
+ "descriptionSections": [
+ {
+ "key": "Why is this an issue ?",
+ "content": "<h3 class=\"page-title coding-rules-detail-header\"><big>Unnecessary imports should be removed</big></h3>"
+ },
+ {
+ "key": "How to fix it ?",
+ "content": "<h2>Recommended Secure Coding Practices</h2><ul><li> activate Spring Security's CSRF protection. </li></ul>"
+ }
+ ],
"params": [
{
"key": "max",
import org.sonar.db.qualityprofile.ActiveRuleDto;
import org.sonar.db.qualityprofile.ActiveRuleParamDto;
import org.sonar.db.qualityprofile.QProfileDto;
+import org.sonar.db.rule.RuleDescriptionSectionDto;
import org.sonar.db.rule.RuleDto;
import org.sonar.db.rule.RuleMetadataDto;
import org.sonar.db.rule.RuleParamDto;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+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.DEFAULT_KEY;
import static org.sonar.db.rule.RuleDescriptionSectionDto.createDefaultRuleDescriptionSection;
import static org.sonar.db.rule.RuleDto.Format.MARKDOWN;
import static org.sonar.db.rule.RuleTesting.newCustomRule;
+import static org.sonar.db.rule.RuleTesting.newRuleWithoutDescriptionSection;
import static org.sonar.db.rule.RuleTesting.newTemplateRule;
import static org.sonar.db.rule.RuleTesting.setTags;
import static org.sonar.server.language.LanguageTesting.newLanguage;
.containsExactlyInAnyOrder("adhoc name", "<div>desc2</div>", Severity.BLOCKER, VULNERABILITY);
}
+ @Test
+ public void show_rule_desc_sections() {
+ when(macroInterpreter.interpret(anyString())).thenAnswer(invocation -> invocation.getArgument(0));
+
+ 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>");
+
+ RuleDto rule = createRuleWithDescriptionSections(section1, section2, section3);
+ rule.setType(RuleType.SECURITY_HOTSPOT);
+ 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(
+ "<h2>What's 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/>"
+ );
+
+ assertThat(resultRule.getMdDesc())
+ .contains(
+ "<h2>What's 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/>");
+
+ assertThat(resultRule.getDescriptionSectionsList())
+ .extracting(Rule.DescriptionSection::getKey, Rule.DescriptionSection::getContent)
+ .containsExactlyInAnyOrder(
+ tuple(ROOT_CAUSE_SECTION_KEY, "<div>Root is Root</div>"),
+ tuple(ASSESS_THE_PROBLEM_SECTION_KEY, "<div>This is not a problem</div>"),
+ tuple(HOW_TO_FIX_SECTION_KEY, "<div>I don't want to fix</div>"));
+ }
+
+ @Test
+ public void show_rule_markdown_description() {
+ when(macroInterpreter.interpret(anyString())).thenAnswer(invocation -> invocation.getArgument(0));
+
+ var section = createRuleDescriptionSection("default", "*toto is toto*");
+
+ RuleDto rule = createRuleWithDescriptionSections(section);
+ rule.setDescriptionFormat(MARKDOWN);
+ 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("<strong>toto is toto</strong>");
+ assertThat(resultRule.getMdDesc()).contains("*toto is toto*");
+
+ assertThat(resultRule.getDescriptionSectionsList())
+ .extracting(Rule.DescriptionSection::getKey, Rule.DescriptionSection::getContent)
+ .contains(tuple(DEFAULT_KEY, "<strong>toto is toto</strong>"));
+ }
+
@Test
public void ignore_predefined_info_on_adhoc_rule() {
RuleDto externalRule = db.rules().insert(r -> r
tuple("actives", false));
}
+ private RuleDescriptionSectionDto createRuleDescriptionSection(String key, String content) {
+ return RuleDescriptionSectionDto.builder().uuid(uuidFactory.create()).key(key).content(content).build();
+ }
+
+ private RuleDto createRuleWithDescriptionSections(RuleDescriptionSectionDto... sections) {
+ var rule = newRuleWithoutDescriptionSection();
+ for (var section : sections) {
+ rule.addRuleDescriptionSectionDto(section);
+ }
+ return rule;
+ }
}
optional string repo = 2;
optional string name = 3;
optional string createdAt = 4;
- optional string htmlDesc = 5;
+ optional string htmlDesc = 5 [deprecated=true];
optional string htmlNote = 6;
optional string mdDesc = 7;
optional string mdNote = 8;
optional sonarqube.ws.commons.RuleScope scope = 46;
optional bool isExternal = 47;
optional DeprecatedKeys deprecatedKeys = 48;
+ repeated DescriptionSection descriptionSections = 49;
+
+ message DescriptionSection {
+ required string key = 1;
+ required string content = 2;
+ }
message Params {
repeated Param params = 1;