3 * Copyright (C) 2009-2024 SonarSource SA
4 * mailto:info AT sonarsource DOT com
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.
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.
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.
20 package org.sonar.server.rule;
22 import java.util.Objects;
23 import java.util.Optional;
25 import java.util.stream.Collectors;
26 import java.util.stream.Stream;
27 import javax.annotation.CheckForNull;
28 import org.sonar.api.server.rule.RulesDefinition;
29 import org.sonar.core.util.UuidFactory;
30 import org.sonar.db.rule.RuleDescriptionSectionDto;
31 import org.sonar.markdown.Markdown;
33 import static java.util.Collections.emptySet;
34 import static org.sonar.api.rules.RuleType.SECURITY_HOTSPOT;
35 import static org.sonar.api.server.rule.RuleDescriptionSection.RuleDescriptionSectionKeys.ASSESS_THE_PROBLEM_SECTION_KEY;
36 import static org.sonar.api.server.rule.RuleDescriptionSection.RuleDescriptionSectionKeys.HOW_TO_FIX_SECTION_KEY;
37 import static org.sonar.api.server.rule.RuleDescriptionSection.RuleDescriptionSectionKeys.ROOT_CAUSE_SECTION_KEY;
38 import static org.sonar.db.rule.RuleDescriptionSectionDto.createDefaultRuleDescriptionSection;
40 public class LegacyHotspotRuleDescriptionSectionsGenerator implements RuleDescriptionSectionsGenerator {
41 private final UuidFactory uuidFactory;
43 public LegacyHotspotRuleDescriptionSectionsGenerator(UuidFactory uuidFactory) {
44 this.uuidFactory = uuidFactory;
48 public boolean isGeneratorForRule(RulesDefinition.Rule rule) {
49 // 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.
50 // In the future, the generator should not be used for advanced rules (add condition && rule.ruleDescriptionSections().isEmpty())
51 return SECURITY_HOTSPOT.equals(rule.type());
55 public Set<RuleDescriptionSectionDto> generateSections(RulesDefinition.Rule rule) {
56 return getDescriptionInHtml(rule)
57 .map(this::generateSections)
61 private static Optional<String> getDescriptionInHtml(RulesDefinition.Rule rule) {
62 if (rule.htmlDescription() != null) {
63 return Optional.of(rule.htmlDescription());
64 } else if (rule.markdownDescription() != null) {
65 return Optional.of(Markdown.convertToHtml(rule.markdownDescription()));
67 return Optional.empty();
70 private Set<RuleDescriptionSectionDto> generateSections(String descriptionInHtml) {
71 if (descriptionInHtml.isEmpty()) {
74 String[] split = extractSection("", descriptionInHtml);
75 String remainingText = split[0];
76 String ruleDescriptionSection = split[1];
78 split = extractSection("<h2>Exceptions</h2>", remainingText);
79 remainingText = split[0];
80 String exceptions = split[1];
82 split = extractSection("<h2>Ask Yourself Whether</h2>", remainingText);
83 remainingText = split[0];
84 String askSection = split[1];
86 split = extractSection("<h2>Sensitive Code Example</h2>", remainingText);
87 remainingText = split[0];
88 String sensitiveSection = split[1];
90 split = extractSection("<h2>Noncompliant Code Example</h2>", remainingText);
91 remainingText = split[0];
92 String noncompliantSection = split[1];
94 split = extractSection("<h2>Recommended Secure Coding Practices</h2>", remainingText);
95 remainingText = split[0];
96 String recommendedSection = split[1];
98 split = extractSection("<h2>Compliant Solution</h2>", remainingText);
99 remainingText = split[0];
100 String compliantSection = split[1];
102 split = extractSection("<h2>See</h2>", remainingText);
103 remainingText = split[0];
104 String seeSection = split[1];
106 RuleDescriptionSectionDto rootSection = createSection(ROOT_CAUSE_SECTION_KEY, ruleDescriptionSection, exceptions, remainingText);
107 RuleDescriptionSectionDto assessSection = createSection(ASSESS_THE_PROBLEM_SECTION_KEY, askSection, sensitiveSection, noncompliantSection);
108 RuleDescriptionSectionDto fixSection = createSection(HOW_TO_FIX_SECTION_KEY, recommendedSection, compliantSection, seeSection);
110 // For backward compatibility with SonarLint, see SONAR-16635. Should be removed in 10.x
111 RuleDescriptionSectionDto defaultSection = createDefaultRuleDescriptionSection(uuidFactory.create(), descriptionInHtml);
113 return Stream.of(rootSection, assessSection, fixSection, defaultSection)
114 .filter(Objects::nonNull)
115 .collect(Collectors.toSet());
119 private static String[] extractSection(String beginning, String description) {
120 String endSection = "<h2>";
121 int beginningIndex = description.indexOf(beginning);
122 if (beginningIndex != -1) {
123 int endIndex = description.indexOf(endSection, beginningIndex + beginning.length());
124 if (endIndex == -1) {
125 endIndex = description.length();
127 return new String[] {
128 description.substring(0, beginningIndex) + description.substring(endIndex),
129 description.substring(beginningIndex, endIndex)
132 return new String[] {description, ""};
138 private RuleDescriptionSectionDto createSection(String key, String... contentPieces) {
139 String content = trimToNull(String.join("", contentPieces));
140 if (content == null) {
143 return RuleDescriptionSectionDto.builder()
144 .uuid(uuidFactory.create())
151 private static String trimToNull(String input) {
152 return input.isEmpty() ? null : input;