]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-16302 adapt RuleDto, RuleDefinitionDto and introduce RuleSectionDescriptionDto...
authorLéo Geoffroy <99647462+leo-geoffroy-sonarsource@users.noreply.github.com>
Fri, 22 Apr 2022 12:13:59 +0000 (14:13 +0200)
committersonartech <sonartech@sonarsource.com>
Fri, 6 May 2022 20:02:43 +0000 (20:02 +0000)
31 files changed:
server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/AdHocRuleCreatorTest.java
server/sonar-db-dao/src/main/java/org/sonar/db/rule/RuleDefinitionDto.java
server/sonar-db-dao/src/main/java/org/sonar/db/rule/RuleDescriptionSectionDto.java [new file with mode: 0644]
server/sonar-db-dao/src/main/java/org/sonar/db/rule/RuleDto.java
server/sonar-db-dao/src/test/java/org/sonar/db/qualityprofile/QualityProfileExportDaoTest.java
server/sonar-db-dao/src/test/java/org/sonar/db/rule/RuleDaoTest.java
server/sonar-db-dao/src/testFixtures/java/org/sonar/db/rule/RuleTesting.java
server/sonar-server-common/src/main/java/org/sonar/server/rule/DefaultRuleFinder.java
server/sonar-server-common/src/main/java/org/sonar/server/rule/HotspotRuleDescription.java
server/sonar-server-common/src/main/java/org/sonar/server/rule/RuleDescriptionFormatter.java
server/sonar-server-common/src/test/java/org/sonar/server/rule/HotspotRuleDescriptionTest.java
server/sonar-server-common/src/test/java/org/sonar/server/rule/RuleDescriptionFormatterTest.java
server/sonar-server-common/src/test/java/org/sonar/server/rule/index/RuleIndexTest.java
server/sonar-server-common/src/test/java/org/sonar/server/rule/index/RuleIndexerTest.java
server/sonar-webserver-api/src/main/java/org/sonar/server/rule/CachingRuleFinder.java
server/sonar-webserver-api/src/test/java/org/sonar/server/rule/CachingRuleFinderTest.java
server/sonar-webserver-core/src/main/java/org/sonar/server/rule/RegisterRules.java
server/sonar-webserver-core/src/test/java/org/sonar/server/rule/RegisterRulesTest.java
server/sonar-webserver-pushapi/src/test/java/org/sonar/server/qualityprofile/builtin/QualityProfileChangeEventServiceImplTest.java
server/sonar-webserver-webapi/src/main/java/org/sonar/server/rule/RuleCreator.java
server/sonar-webserver-webapi/src/main/java/org/sonar/server/rule/RuleUpdater.java
server/sonar-webserver-webapi/src/main/java/org/sonar/server/rule/ws/RuleMapper.java
server/sonar-webserver-webapi/src/test/java/org/sonar/server/hotspot/ws/ShowActionTest.java
server/sonar-webserver-webapi/src/test/java/org/sonar/server/issue/ws/SearchActionTest.java
server/sonar-webserver-webapi/src/test/java/org/sonar/server/qualityprofile/QProfileBackuperImplTest.java
server/sonar-webserver-webapi/src/test/java/org/sonar/server/rule/RuleCreatorTest.java
server/sonar-webserver-webapi/src/test/java/org/sonar/server/rule/RuleUpdaterTest.java
server/sonar-webserver-webapi/src/test/java/org/sonar/server/rule/ws/CreateActionTest.java
server/sonar-webserver-webapi/src/test/java/org/sonar/server/rule/ws/SearchActionTest.java
server/sonar-webserver-webapi/src/test/java/org/sonar/server/rule/ws/ShowActionTest.java
server/sonar-webserver-webapi/src/test/java/org/sonar/server/rule/ws/UpdateActionTest.java

index bc0391f9f8eac3f0bb455126445cc0da4e48cd22..d3e2524d08dc5560e67ff468f836f64bb62b771c 100644 (file)
@@ -61,7 +61,7 @@ public class AdHocRuleCreatorTest {
     assertThat(rule.getUuid()).isNotBlank();
     assertThat(rule.getKey()).isEqualTo(RuleKey.of("external_eslint", "no-cond-assign"));
     assertThat(rule.getName()).isEqualTo("eslint:no-cond-assign");
-    assertThat(rule.getDescription()).isNull();
+    assertThat(rule.getRuleDescriptionSectionDtos()).isEmpty();
     assertThat(rule.getSeverity()).isNull();
     assertThat(rule.getType()).isZero();
     assertThat(rule.getMetadata().getAdHocName()).isNull();
@@ -89,7 +89,7 @@ public class AdHocRuleCreatorTest {
     assertThat(rule.getUuid()).isNotBlank();
     assertThat(rule.getKey()).isEqualTo(RuleKey.of("external_eslint", "no-cond-assign"));
     assertThat(rule.getName()).isEqualTo("eslint:no-cond-assign");
-    assertThat(rule.getDescription()).isNull();
+    assertThat(rule.getRuleDescriptionSectionDtos()).isEmpty();
     assertThat(rule.getSeverity()).isNull();
     assertThat(rule.getType()).isZero();
     assertThat(rule.getMetadata().getAdHocName()).isEqualTo("No condition assigned");
@@ -144,7 +144,7 @@ public class AdHocRuleCreatorTest {
     assertThat(ruleUpdated.getUuid()).isNotBlank();
     assertThat(ruleUpdated.getKey()).isEqualTo(RuleKey.of("external_eslint", "no-cond-assign"));
     assertThat(ruleUpdated.getName()).isEqualTo("eslint:no-cond-assign");
-    assertThat(ruleUpdated.getDescription()).isNull();
+    assertThat(ruleUpdated.getRuleDescriptionSectionDtos()).isEmpty();
     assertThat(ruleUpdated.getSeverity()).isNull();
     assertThat(ruleUpdated.getType()).isZero();
     assertThat(ruleUpdated.getMetadata().getAdHocName()).isEqualTo("No condition assigned updated");
@@ -175,7 +175,7 @@ public class AdHocRuleCreatorTest {
     assertThat(ruleUpdated.isAdHoc()).isTrue();
     assertThat(ruleUpdated.getKey()).isEqualTo(rule.getKey());
     assertThat(ruleUpdated.getName()).isEqualTo(rule.getName());
-    assertThat(ruleUpdated.getDescription()).isEqualTo(rule.getDescription());
+    assertThat(ruleUpdated.getRuleDescriptionSectionDtos()).usingRecursiveFieldByFieldElementComparator().isEqualTo(rule.getRuleDescriptionSectionDtos());
     assertThat(ruleUpdated.getSeverity()).isEqualTo(rule.getSeverity());
     assertThat(ruleUpdated.getType()).isEqualTo(rule.getType());
     assertThat(ruleUpdated.getDefinition().getCreatedAt()).isEqualTo(rule.getCreatedAt());
index fcdfe997c2e28b68b2a705dc5d83359e8167901c..1cd974855ff125a934663c28de0d3d2caf814507 100644 (file)
@@ -21,6 +21,9 @@ package org.sonar.db.rule;
 
 import com.google.common.base.Splitter;
 import com.google.common.collect.ImmutableSet;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
 import javax.annotation.CheckForNull;
@@ -33,6 +36,7 @@ import org.sonar.api.rules.RuleType;
 import org.sonar.db.rule.RuleDto.Scope;
 
 import static com.google.common.base.Preconditions.checkArgument;
+import static java.util.Collections.unmodifiableCollection;
 
 public class RuleDefinitionDto {
 
@@ -42,10 +46,7 @@ public class RuleDefinitionDto {
   private String repositoryKey;
   private String ruleKey;
 
-  /**
-   * Description can be null on external rule, otherwise it should never be null
-   */
-  private String description;
+  private Map<String, RuleDescriptionSectionDto> ruleDescriptionSectionDtos = new HashMap<>();
 
   /**
    * Description format can be null on external rule, otherwise it should never be null
@@ -166,13 +167,32 @@ public class RuleDefinitionDto {
     return this;
   }
 
+  public Collection<RuleDescriptionSectionDto> getRuleDescriptionSectionDtos() {
+    return unmodifiableCollection(ruleDescriptionSectionDtos.values());
+  }
+
+  @CheckForNull
+  public RuleDescriptionSectionDto getRuleDescriptionSectionDto(String ruleDescriptionSectionKey) {
+    return ruleDescriptionSectionDtos.get(ruleDescriptionSectionKey);
+  }
+
   @CheckForNull
-  public String getDescription() {
-    return description;
+  public RuleDescriptionSectionDto getDefaultRuleDescriptionSectionDto() {
+    return ruleDescriptionSectionDtos.get(RuleDescriptionSectionDto.DEFAULT_KEY);
+  }
+
+  public RuleDefinitionDto addRuleDescriptionSectionDto(RuleDescriptionSectionDto ruleDescriptionSectionDto) {
+    checkArgument(!isSectionKeyUsed(ruleDescriptionSectionDto.getKey()), "A section with key %s already exists", ruleDescriptionSectionDto.getKey());
+    this.ruleDescriptionSectionDtos.put(ruleDescriptionSectionDto.getKey(), ruleDescriptionSectionDto);
+    return this;
+  }
+
+  private boolean isSectionKeyUsed(String sectionKey) {
+    return ruleDescriptionSectionDtos.containsKey(sectionKey);
   }
 
-  public RuleDefinitionDto setDescription(@Nullable String description) {
-    this.description = description;
+  public RuleDefinitionDto addOrReplaceRuleDescriptionSectionDto(RuleDescriptionSectionDto ruleDescriptionSectionDto) {
+    this.ruleDescriptionSectionDtos.put(ruleDescriptionSectionDto.getKey(), ruleDescriptionSectionDto);
     return this;
   }
 
@@ -434,7 +454,7 @@ public class RuleDefinitionDto {
       "uuid=" + uuid +
       ", repositoryKey='" + repositoryKey + '\'' +
       ", ruleKey='" + ruleKey + '\'' +
-      ", description='" + description + '\'' +
+      ", ruleDescriptionSections='" + ruleDescriptionSectionDtos + '\'' +
       ", descriptionFormat=" + descriptionFormat +
       ", status=" + status +
       ", name='" + name + '\'' +
diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/rule/RuleDescriptionSectionDto.java b/server/sonar-db-dao/src/main/java/org/sonar/db/rule/RuleDescriptionSectionDto.java
new file mode 100644 (file)
index 0000000..bff3a3d
--- /dev/null
@@ -0,0 +1,96 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2022 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.db.rule;
+
+import java.util.StringJoiner;
+
+import static org.sonar.api.utils.Preconditions.checkArgument;
+
+public class RuleDescriptionSectionDto {
+  static final String DEFAULT_KEY = "default";
+  private final String key;
+  private final String description;
+
+  private RuleDescriptionSectionDto(String key, String description) {
+    this.key = key;
+    this.description = description;
+  }
+
+  public String getKey() {
+    return key;
+  }
+
+  public String getDescription() {
+    return description;
+  }
+
+  public static RuleDescriptionSectionDto createDefaultRuleDescriptionSection(String description) {
+    return RuleDescriptionSectionDto.builder()
+      .setDefault()
+      .description(description)
+      .build();
+  }
+
+  public static RuleDescriptionSectionDtoBuilder builder() {
+    return new RuleDescriptionSectionDtoBuilder();
+  }
+
+  public boolean isDefault() {
+    return DEFAULT_KEY.equals(key);
+  }
+
+  @Override
+  public String toString() {
+    return new StringJoiner(", ", RuleDescriptionSectionDto.class.getSimpleName() + "[", "]")
+      .add("key='" + key + "'")
+      .add("description='" + description + "'")
+      .toString();
+  }
+
+  public static final class RuleDescriptionSectionDtoBuilder {
+    private String key = null;
+    private String description;
+
+    private RuleDescriptionSectionDtoBuilder() {
+    }
+
+    public RuleDescriptionSectionDtoBuilder setDefault() {
+      checkArgument(this.key == null, "Only one of setDefault and key methods can be called");
+      this.key = DEFAULT_KEY;
+      return this;
+    }
+
+    public RuleDescriptionSectionDtoBuilder key(String key) {
+      checkArgument(this.key == null, "Only one of setDefault and key methods can be called");
+      this.key = key;
+      return this;
+    }
+
+
+    public RuleDescriptionSectionDtoBuilder description(String description) {
+      this.description = description;
+      return this;
+    }
+
+    public RuleDescriptionSectionDto build() {
+      return new RuleDescriptionSectionDto(key, description);
+    }
+  }
+}
index 28fe1ca75ee368e265c9ea0ff29c6e1ecfe46f0a..3e518801fa71c1318af6d56af161b7ea55f20e98 100644 (file)
@@ -19,6 +19,7 @@
  */
 package org.sonar.db.rule;
 
+import java.util.Collection;
 import java.util.Set;
 import javax.annotation.CheckForNull;
 import javax.annotation.Nullable;
@@ -100,12 +101,27 @@ public class RuleDto {
     return this;
   }
 
-  public String getDescription() {
-    return definition.getDescription();
+  @CheckForNull
+  public RuleDescriptionSectionDto getRuleDescriptionSection(String ruleDescriptionSectionKey) {
+    return definition.getRuleDescriptionSectionDto(ruleDescriptionSectionKey);
+  }
+
+  @CheckForNull
+  public RuleDescriptionSectionDto getDefaultRuleDescriptionSection() {
+    return definition.getDefaultRuleDescriptionSectionDto();
+  }
+
+  public Collection<RuleDescriptionSectionDto> getRuleDescriptionSectionDtos() {
+    return definition.getRuleDescriptionSectionDtos();
+  }
+
+  public RuleDto addRuleDescriptionSectionDto(RuleDescriptionSectionDto ruleDescriptionSectionDto) {
+    definition.addRuleDescriptionSectionDto(ruleDescriptionSectionDto);
+    return this;
   }
 
-  public RuleDto setDescription(String description) {
-    definition.setDescription(description);
+  public RuleDto addOrReplaceRuleDescriptionSectionDto(RuleDescriptionSectionDto ruleDescriptionSectionDto) {
+    definition.addOrReplaceRuleDescriptionSectionDto(ruleDescriptionSectionDto);
     return this;
   }
 
index 629418aaa759a68a7dc2a6bdc3568c1b9cfe27da..fb9efff61ed3698b2abee1dddc1354afbf36e471 100644 (file)
@@ -36,6 +36,7 @@ import org.sonar.api.rules.RuleType;
 import org.sonar.db.DbSession;
 import org.sonar.db.DbTester;
 import org.sonar.db.rule.RuleDefinitionDto;
+import org.sonar.db.rule.RuleDescriptionSectionDto;
 import org.sonar.db.rule.RuleMetadataDto;
 import org.sonar.db.rule.RuleParamDto;
 
@@ -94,7 +95,8 @@ public class QualityProfileExportDaoTest {
     assertThat(exportCustomRuleDto).isNotNull();
     assertThat(exportCustomRuleDto.isCustomRule()).isTrue();
     assertThat(exportCustomRuleDto.getParams()).isEmpty();
-    assertThat(exportCustomRuleDto.getDescription()).isEqualTo(customRule.getDescription());
+    //FIXME SONAR-16314
+    assertThat(exportCustomRuleDto.getDescription()).isEqualTo(customRule.getRuleDescriptionSectionDtos().stream().map(RuleDescriptionSectionDto::getDescription).collect(Collectors.joining()));
     assertThat(exportCustomRuleDto.getExtendedDescription()).isEqualTo(customRuleMetadata.getNoteData());
     assertThat(exportCustomRuleDto.getName()).isEqualTo(customRule.getName());
     assertThat(exportCustomRuleDto.getRuleKey()).isEqualTo(customRule.getKey());
@@ -110,7 +112,8 @@ public class QualityProfileExportDaoTest {
     assertThat(exportRuleDto).isNotNull();
     assertThat(exportRuleDto.isCustomRule()).isFalse();
     assertThat(exportRuleDto.getParams()).isEmpty();
-    assertThat(exportRuleDto.getDescription()).isEqualTo(rule.getDescription());
+    //FIXME SONAR-16314
+    assertThat(exportRuleDto.getDescription()).isEqualTo(rule.getRuleDescriptionSectionDtos().stream().map(RuleDescriptionSectionDto::getDescription).collect(Collectors.joining()));
     assertThat(exportRuleDto.getExtendedDescription()).isEqualTo(ruleMetadata.getNoteData());
     assertThat(exportRuleDto.getName()).isEqualTo(rule.getName());
     assertThat(exportRuleDto.getRuleKey()).isEqualTo(rule.getKey());
index 41893e0c3e86415d1b282319702fe4fb7c028c95..438b517a244eb18956b8413dea7c56d1459c5988 100644 (file)
@@ -28,6 +28,7 @@ import java.util.List;
 import java.util.Optional;
 import java.util.Set;
 import java.util.function.Consumer;
+import java.util.stream.Collectors;
 import org.apache.ibatis.exceptions.PersistenceException;
 import org.apache.ibatis.session.ResultHandler;
 import org.junit.Rule;
@@ -48,15 +49,18 @@ import static com.google.common.collect.Sets.newHashSet;
 import static java.util.Arrays.asList;
 import static java.util.Collections.emptyList;
 import static java.util.Collections.singletonList;
+import static org.apache.commons.lang.RandomStringUtils.randomAlphabetic;
 import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric;
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.assertj.core.api.Assertions.assertThatThrownBy;
 import static org.assertj.core.api.Assertions.tuple;
 import static org.sonar.api.rule.RuleStatus.REMOVED;
+import static org.sonar.db.rule.RuleDescriptionSectionDto.createDefaultRuleDescriptionSection;
 import static org.sonar.db.rule.RuleTesting.newRuleMetadata;
 
 public class RuleDaoTest {
   private static final String UNKNOWN_RULE_UUID = "unknown-uuid";
+  private static final RuleDescriptionSectionDto RULE_DESCRIPTION_SECTION_1 = createDefaultRuleDescriptionSection("new description");
 
   @Rule
   public DbTester db = DbTester.create(System2.INSTANCE);
@@ -244,8 +248,6 @@ public class RuleDaoTest {
     assertThat(actual.getRepositoryKey()).isEqualTo(expected.getRepositoryKey());
     assertThat(actual.getRuleKey()).isEqualTo(expected.getRuleKey());
     assertThat(actual.getKey()).isEqualTo(expected.getKey());
-    assertThat(actual.getDescription()).isEqualTo(expected.getDescription());
-    assertThat(actual.getDescriptionFormat()).isEqualTo(expected.getDescriptionFormat());
     assertThat(actual.getStatus()).isEqualTo(expected.getStatus());
     assertThat(actual.getName()).isEqualTo(expected.getName());
     assertThat(actual.getConfigKey()).isEqualTo(expected.getConfigKey());
@@ -262,6 +264,9 @@ public class RuleDaoTest {
     assertThat(actual.getSystemTags()).isEqualTo(expected.getSystemTags());
     assertThat(actual.getSecurityStandards()).isEqualTo(expected.getSecurityStandards());
     assertThat(actual.getType()).isEqualTo(expected.getType());
+    assertThat(actual.getDescriptionFormat()).isEqualTo(expected.getDescriptionFormat());
+    assertThat(actual.getRuleDescriptionSectionDtos()).usingRecursiveFieldByFieldElementComparator()
+      .isEqualTo(expected.getRuleDescriptionSectionDtos());
   }
 
   private static void verifyMetadata(RuleMetadataDto metadata, RuleMetadataDto expected) {
@@ -427,8 +432,8 @@ public class RuleDaoTest {
       .setRuleKey("NewRuleKey")
       .setRepositoryKey("plugin")
       .setName("new name")
-      .setDescription("new description")
       .setDescriptionFormat(RuleDto.Format.MARKDOWN)
+      .addRuleDescriptionSectionDto(RULE_DESCRIPTION_SECTION_1)
       .setStatus(RuleStatus.DEPRECATED)
       .setConfigKey("NewConfigKey")
       .setSeverity(Severity.INFO)
@@ -453,8 +458,6 @@ public class RuleDaoTest {
     RuleDefinitionDto ruleDto = underTest.selectOrFailDefinitionByKey(db.getSession(), RuleKey.of("plugin", "NewRuleKey"));
     assertThat(ruleDto.getUuid()).isNotNull();
     assertThat(ruleDto.getName()).isEqualTo("new name");
-    assertThat(ruleDto.getDescription()).isEqualTo("new description");
-    assertThat(ruleDto.getDescriptionFormat()).isEqualTo(RuleDto.Format.MARKDOWN);
     assertThat(ruleDto.getStatus()).isEqualTo(RuleStatus.DEPRECATED);
     assertThat(ruleDto.getRuleKey()).isEqualTo("NewRuleKey");
     assertThat(ruleDto.getRepositoryKey()).isEqualTo("plugin");
@@ -475,6 +478,9 @@ public class RuleDaoTest {
     assertThat(ruleDto.getType()).isEqualTo(RuleType.BUG.getDbConstant());
     assertThat(ruleDto.getCreatedAt()).isEqualTo(1_500_000_000_000L);
     assertThat(ruleDto.getUpdatedAt()).isEqualTo(2_000_000_000_000L);
+    assertThat(ruleDto.getDescriptionFormat()).isEqualTo(RuleDto.Format.MARKDOWN);
+    assertThat(ruleDto.getRuleDescriptionSectionDtos()).usingRecursiveFieldByFieldElementComparator()
+      .containsOnly(RULE_DESCRIPTION_SECTION_1);
   }
 
   @Test
@@ -485,8 +491,8 @@ public class RuleDaoTest {
       .setRuleKey("NewRuleKey")
       .setRepositoryKey("plugin")
       .setName("new name")
-      .setDescription("new description")
       .setDescriptionFormat(RuleDto.Format.MARKDOWN)
+      .addRuleDescriptionSectionDto(createDefaultRuleDescriptionSection(randomAlphabetic(5)))
       .setStatus(RuleStatus.DEPRECATED)
       .setConfigKey("NewConfigKey")
       .setSeverity(Severity.INFO)
@@ -510,8 +516,6 @@ public class RuleDaoTest {
 
     RuleDefinitionDto ruleDto = underTest.selectOrFailDefinitionByKey(db.getSession(), RuleKey.of("plugin", "NewRuleKey"));
     assertThat(ruleDto.getName()).isEqualTo("new name");
-    assertThat(ruleDto.getDescription()).isEqualTo("new description");
-    assertThat(ruleDto.getDescriptionFormat()).isEqualTo(RuleDto.Format.MARKDOWN);
     assertThat(ruleDto.getStatus()).isEqualTo(RuleStatus.DEPRECATED);
     assertThat(ruleDto.getRuleKey()).isEqualTo("NewRuleKey");
     assertThat(ruleDto.getRepositoryKey()).isEqualTo("plugin");
@@ -532,6 +536,9 @@ public class RuleDaoTest {
     assertThat(ruleDto.getType()).isEqualTo(RuleType.BUG.getDbConstant());
     assertThat(ruleDto.getCreatedAt()).isEqualTo(rule.getCreatedAt());
     assertThat(ruleDto.getUpdatedAt()).isEqualTo(2_000_000_000_000L);
+    assertThat(ruleDto.getDescriptionFormat()).isEqualTo(RuleDto.Format.MARKDOWN);
+    assertThat(ruleDto.getRuleDescriptionSectionDtos()).usingRecursiveFieldByFieldElementComparator()
+      .containsOnly(RULE_DESCRIPTION_SECTION_1);
   }
 
   @Test
@@ -798,7 +805,8 @@ public class RuleDaoTest {
     assertThat(firstRule.getRepository()).isEqualTo(r1.getRepositoryKey());
     assertThat(firstRule.getPluginRuleKey()).isEqualTo(r1.getRuleKey());
     assertThat(firstRule.getName()).isEqualTo(r1.getName());
-    assertThat(firstRule.getDescription()).isEqualTo(r1.getDescription());
+    //FIXME SONAR-16309
+    assertThat(firstRule.getDescription()).isEqualTo(r1.getRuleDescriptionSectionDtos().stream().map(RuleDescriptionSectionDto::getDescription).collect(Collectors.joining()));
     assertThat(firstRule.getDescriptionFormat()).isEqualTo(r1.getDescriptionFormat());
     assertThat(firstRule.getSeverity()).isEqualTo(r1.getSeverity());
     assertThat(firstRule.getStatus()).isEqualTo(r1.getStatus());
@@ -877,7 +885,8 @@ public class RuleDaoTest {
     assertThat(firstRule.getRepository()).isEqualTo(r1.getRepositoryKey());
     assertThat(firstRule.getPluginRuleKey()).isEqualTo(r1.getRuleKey());
     assertThat(firstRule.getName()).isEqualTo(r1.getName());
-    assertThat(firstRule.getDescription()).isEqualTo(r1.getDescription());
+    //FIXME SONAR-16309
+    assertThat(firstRule.getDescription()).isEqualTo(r1.getRuleDescriptionSectionDtos().stream().map(RuleDescriptionSectionDto::getDescription).collect(Collectors.joining()));
     assertThat(firstRule.getDescriptionFormat()).isEqualTo(r1.getDescriptionFormat());
     assertThat(firstRule.getSeverity()).isEqualTo(r1.getSeverity());
     assertThat(firstRule.getSeverityAsString()).isEqualTo(SeverityUtil.getSeverityFromOrdinal(r1.getSeverity()));
index b86f5ddb33dbbcf622dd2565a0f4d2fca50f066e..b544afa6379e140953debbf2aca5cf311399b70b 100644 (file)
@@ -32,7 +32,6 @@ import org.sonar.api.server.rule.RuleParamType;
 import org.sonar.core.util.UuidFactory;
 import org.sonar.core.util.UuidFactoryFast;
 import org.sonar.core.util.Uuids;
-import org.sonar.db.rule.RuleDto.Format;
 import org.sonar.db.rule.RuleDto.Scope;
 import org.sonar.db.user.UserDto;
 
@@ -43,6 +42,7 @@ import static org.apache.commons.lang.RandomStringUtils.randomAlphabetic;
 import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric;
 import static org.apache.commons.lang.math.RandomUtils.nextInt;
 import static org.sonar.api.rule.RuleKey.EXTERNAL_RULE_REPO_PREFIX;
+import static org.sonar.db.rule.RuleDescriptionSectionDto.createDefaultRuleDescriptionSection;
 
 /**
  * Utility class for tests involving rules
@@ -65,13 +65,22 @@ public class RuleTesting {
   }
 
   public static RuleDefinitionDto newRule(RuleKey key) {
+    RuleDefinitionDto ruleDefinitionDto = newRuleWithoutSection(key);
+    ruleDefinitionDto.addRuleDescriptionSectionDto(createDefaultRuleDescriptionSection("description_" + randomAlphabetic(5)));
+    return ruleDefinitionDto;
+  }
+
+  public static RuleDefinitionDto newRuleWithoutSection() {
+    return newRuleWithoutSection(randomRuleKey());
+  }
+
+  public static RuleDefinitionDto newRuleWithoutSection(RuleKey ruleKey) {
     return new RuleDefinitionDto()
-      .setRepositoryKey(key.repository())
-      .setRuleKey(key.rule())
+      .setRepositoryKey(ruleKey.repository())
+      .setRuleKey(ruleKey.rule())
       .setUuid("rule_uuid_" + randomAlphanumeric(5))
       .setName("name_" + randomAlphanumeric(5))
-      .setDescription("description_" + randomAlphanumeric(5))
-      .setDescriptionFormat(Format.HTML)
+      .setDescriptionFormat(RuleDto.Format.HTML)
       .setType(RuleType.values()[nextInt(RuleType.values().length)])
       .setStatus(RuleStatus.READY)
       .setConfigKey("configKey_" + randomAlphanumeric(5))
@@ -171,8 +180,8 @@ public class RuleTesting {
       .setRuleKey(ruleKey.rule())
       .setRepositoryKey(ruleKey.repository())
       .setName("Rule " + ruleKey.rule())
-      .setDescription("Description " + ruleKey.rule())
-      .setDescriptionFormat(Format.HTML)
+      .setDescriptionFormat(RuleDto.Format.HTML)
+      .addRuleDescriptionSectionDto(createDefaultRuleDescriptionSection("Description" + ruleKey.rule()))
       .setStatus(RuleStatus.READY)
       .setConfigKey("InternalKey" + ruleKey.rule())
       .setSeverity(Severity.INFO)
index c31892506c8ef9746b126581e97f66af8b8ca861..e821838fd17e2af6fbcf93d7478d2cb5146cd149 100644 (file)
@@ -40,7 +40,8 @@ import org.sonar.db.rule.RuleDao;
 import org.sonar.db.rule.RuleDefinitionDto;
 import org.sonar.db.rule.RuleDto;
 import org.sonar.db.rule.RuleParamDto;
-import org.sonar.markdown.Markdown;
+
+import static org.sonar.server.rule.RuleDescriptionFormatter.getDescriptionAsHtml;
 
 /**
  * Will be removed in the future.
@@ -135,8 +136,6 @@ public class DefaultRuleFinder implements ServerRuleFinder {
 
   private static org.sonar.api.rules.Rule toRule(RuleDto rule, List<RuleParamDto> params) {
     String severity = rule.getSeverityString();
-    String description = rule.getDescription();
-    RuleDto.Format descriptionFormat = rule.getDescriptionFormat();
 
     org.sonar.api.rules.Rule apiRule = new org.sonar.api.rules.Rule();
     apiRule
@@ -151,14 +150,9 @@ public class DefaultRuleFinder implements ServerRuleFinder {
       .setSeverity(severity != null ? RulePriority.valueOf(severity) : null)
       .setStatus(rule.getStatus().name())
       .setSystemTags(rule.getSystemTags().toArray(new String[rule.getSystemTags().size()]))
-      .setTags(rule.getTags().toArray(new String[rule.getTags().size()]));
-    if (description != null && descriptionFormat != null) {
-      if (RuleDto.Format.HTML.equals(descriptionFormat)) {
-        apiRule.setDescription(description);
-      } else {
-        apiRule.setDescription(Markdown.convertToHtml(description));
-      }
-    }
+      .setTags(rule.getTags().toArray(new String[rule.getTags().size()]))
+      .setDescription(getDescriptionAsHtml(rule.getDefinition()));
+
 
     List<org.sonar.api.rules.RuleParam> apiParams = new ArrayList<>();
     for (RuleParamDto param : params) {
index a9e685e0cd4fe717bbf834d741da7d3276498368..b3b27628cb009bd31cf61c1ae753245444fbb49f 100644 (file)
@@ -48,7 +48,7 @@ public class HotspotRuleDescription {
   }
 
   public static HotspotRuleDescription from(RuleDefinitionDto dto) {
-    String description = dto.isCustomRule() ? RuleDescriptionFormatter.getDescriptionAsHtml(dto) : dto.getDescription();
+    String description = RuleDescriptionFormatter.getDescriptionAsHtml(dto);
     return from(description);
   }
 
index 0eeaaa83d9a4e3dd50c200f4840340e8eff32cb9..2cae26e26958a82a1a18486c1bdfbfc82095e0d7 100644 (file)
  */
 package org.sonar.server.rule;
 
+import java.util.Collection;
+import java.util.Objects;
+import java.util.Optional;
 import org.sonar.db.rule.RuleDefinitionDto;
+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;
 
 public class RuleDescriptionFormatter {
 
   private RuleDescriptionFormatter() { /* static helpers */ }
 
-  public static String getDescriptionAsHtml(RuleDefinitionDto ruleDto) {
-    String description = ruleDto.getDescription();
-    RuleDto.Format descriptionFormat = ruleDto.getDescriptionFormat();
-    if (description != null && descriptionFormat != null) {
-      switch (descriptionFormat) {
-        case MARKDOWN:
-          return Markdown.convertToHtml(description);
-        case HTML:
-          return description;
-        default:
-          throw new IllegalStateException(format("Rule description format '%s' is unknown for key '%s'", descriptionFormat, ruleDto.getKey().toString()));
-      }
+  public static String getDescriptionAsHtml(RuleDefinitionDto ruleDefinitionDto) {
+    if (ruleDefinitionDto.getDescriptionFormat() == null) {
+      return null;
+    }
+    Collection<RuleDescriptionSectionDto> ruleDescriptionSectionDtos = ruleDefinitionDto.getRuleDescriptionSectionDtos();
+    Optional<RuleDescriptionSectionDto> ruleDescriptionSectionDto = findDefaultDescription(ruleDescriptionSectionDtos);
+    return ruleDescriptionSectionDto
+      .map(ruleDescriptionSection -> toHtml(ruleDefinitionDto, ruleDescriptionSection))
+      .orElse(null);
+  }
+
+  private static Optional<RuleDescriptionSectionDto> findDefaultDescription(Collection<RuleDescriptionSectionDto> ruleDescriptionSectionDtos) {
+    return ruleDescriptionSectionDtos.stream()
+      .filter(RuleDescriptionSectionDto::isDefault)
+      .collect(toOptional());
+  }
+
+  private static String toHtml(RuleDefinitionDto ruleDefinitionDto, RuleDescriptionSectionDto ruleDescriptionSectionDto) {
+    RuleDto.Format descriptionFormat = Objects.requireNonNull(ruleDefinitionDto.getDescriptionFormat(),
+      "Rule " + ruleDefinitionDto.getDescriptionFormat() + " contains section(s) but has no format set");
+    switch (descriptionFormat) {
+      case MARKDOWN:
+        return Markdown.convertToHtml(ruleDescriptionSectionDto.getDescription());
+      case HTML:
+        return ruleDescriptionSectionDto.getDescription();
+      default:
+        throw new IllegalStateException(format("Rule description section format '%s' is unknown for rule key '%s'", descriptionFormat, ruleDefinitionDto.getKey()));
     }
-    return null;
   }
 
 }
index 18982042bd3f6840f4e71ec1f4c169a33f99e614..12c34c19c62404f277d0d9adec45ff165c533ecf 100644 (file)
@@ -26,17 +26,18 @@ import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.sonar.db.rule.RuleDefinitionDto;
 import org.sonar.db.rule.RuleDto;
-import org.sonar.db.rule.RuleTesting;
 
 import static org.apache.commons.lang.RandomStringUtils.randomAlphabetic;
 import static org.assertj.core.api.Assertions.assertThat;
+import static org.sonar.db.rule.RuleDescriptionSectionDto.createDefaultRuleDescriptionSection;
+import static org.sonar.db.rule.RuleTesting.newRuleWithoutSection;
 
 @RunWith(DataProviderRunner.class)
 public class HotspotRuleDescriptionTest {
 
   @Test
   public void parse_returns_all_empty_fields_when_no_description() {
-    RuleDefinitionDto dto = RuleTesting.newRule().setDescription(null);
+    RuleDefinitionDto dto = newRuleWithoutSection();
 
     HotspotRuleDescription result = HotspotRuleDescription.from(dto);
 
@@ -47,7 +48,7 @@ public class HotspotRuleDescriptionTest {
 
   @Test
   public void parse_returns_all_empty_fields_when_empty_description() {
-    RuleDefinitionDto dto = RuleTesting.newRule().setDescription("");
+    RuleDefinitionDto dto = newRuleWithoutSection().addRuleDescriptionSectionDto(createDefaultRuleDescriptionSection(""));
 
     HotspotRuleDescription result = HotspotRuleDescription.from(dto);
 
@@ -59,7 +60,7 @@ public class HotspotRuleDescriptionTest {
   @Test
   @UseDataProvider("descriptionsWithoutTitles")
   public void parse_to_risk_description_fields_when_desc_contains_no_section(String description) {
-    RuleDefinitionDto dto = RuleTesting.newRule().setDescription(description);
+    RuleDefinitionDto dto = newRuleWithoutSection().addRuleDescriptionSectionDto(createDefaultRuleDescriptionSection(description));
 
     HotspotRuleDescription result = HotspotRuleDescription.from(dto);
 
@@ -80,7 +81,8 @@ public class HotspotRuleDescriptionTest {
 
   @Test
   public void parse_return_null_risk_when_desc_starts_with_ask_yourself_title() {
-    RuleDefinitionDto dto = RuleTesting.newRule().setDescription(ASKATRISK + RECOMMENTEDCODINGPRACTICE);
+    RuleDefinitionDto dto = newRuleWithoutSection().addRuleDescriptionSectionDto(
+      createDefaultRuleDescriptionSection((ASKATRISK + RECOMMENTEDCODINGPRACTICE)));
 
     HotspotRuleDescription result = HotspotRuleDescription.from(dto);
 
@@ -91,7 +93,8 @@ public class HotspotRuleDescriptionTest {
 
   @Test
   public void parse_return_null_vulnerable_when_no_ask_yourself_whether_title() {
-    RuleDefinitionDto dto = RuleTesting.newRule().setDescription(DESCRIPTION + RECOMMENTEDCODINGPRACTICE);
+    RuleDefinitionDto dto = newRuleWithoutSection()
+      .addRuleDescriptionSectionDto(createDefaultRuleDescriptionSection((DESCRIPTION + RECOMMENTEDCODINGPRACTICE)));
 
     HotspotRuleDescription result = HotspotRuleDescription.from(dto);
 
@@ -102,7 +105,8 @@ public class HotspotRuleDescriptionTest {
 
   @Test
   public void parse_return_null_fixIt_when_desc_has_no_Recommended_Secure_Coding_Practices_title() {
-    RuleDefinitionDto dto = RuleTesting.newRule().setDescription(DESCRIPTION + ASKATRISK);
+    RuleDefinitionDto dto = newRuleWithoutSection()
+      .addRuleDescriptionSectionDto(createDefaultRuleDescriptionSection((DESCRIPTION + ASKATRISK)));
 
     HotspotRuleDescription result = HotspotRuleDescription.from(dto);
 
@@ -113,7 +117,8 @@ public class HotspotRuleDescriptionTest {
 
   @Test
   public void parse_with_noncompliant_section_not_removed() {
-    RuleDefinitionDto dto = RuleTesting.newRule().setDescription(DESCRIPTION + NONCOMPLIANTCODE + COMPLIANTCODE);
+    RuleDefinitionDto dto = newRuleWithoutSection().addRuleDescriptionSectionDto(
+      createDefaultRuleDescriptionSection((DESCRIPTION + NONCOMPLIANTCODE + COMPLIANTCODE)));
 
     HotspotRuleDescription result = HotspotRuleDescription.from(dto);
 
@@ -124,7 +129,8 @@ public class HotspotRuleDescriptionTest {
 
   @Test
   public void parse_moved_noncompliant_code() {
-    RuleDefinitionDto dto = RuleTesting.newRule().setDescription(DESCRIPTION + RECOMMENTEDCODINGPRACTICE + NONCOMPLIANTCODE + SEE);
+    RuleDefinitionDto dto = newRuleWithoutSection().addRuleDescriptionSectionDto(
+      createDefaultRuleDescriptionSection((DESCRIPTION + RECOMMENTEDCODINGPRACTICE + NONCOMPLIANTCODE + SEE)));
 
     HotspotRuleDescription result = HotspotRuleDescription.from(dto);
 
@@ -137,7 +143,8 @@ public class HotspotRuleDescriptionTest {
 
   @Test
   public void parse_moved_sensitivecode_code() {
-    RuleDefinitionDto dto = RuleTesting.newRule().setDescription(DESCRIPTION + ASKATRISK + RECOMMENTEDCODINGPRACTICE + SENSITIVECODE + SEE);
+    RuleDefinitionDto dto = newRuleWithoutSection().addRuleDescriptionSectionDto(
+      createDefaultRuleDescriptionSection((DESCRIPTION + ASKATRISK + RECOMMENTEDCODINGPRACTICE + SENSITIVECODE + SEE)));
 
     HotspotRuleDescription result = HotspotRuleDescription.from(dto);
 
@@ -153,18 +160,18 @@ public class HotspotRuleDescriptionTest {
     String askContent = "This is the ask section content";
     String recommendedContent = "This is the recommended section content";
 
-    RuleDefinitionDto dto = RuleTesting.newRule()
+    RuleDefinitionDto dto = newRuleWithoutSection()
       .setTemplateUuid("123")
       .setDescriptionFormat(RuleDto.Format.MARKDOWN)
-      .setDescription(
+      .addRuleDescriptionSectionDto(createDefaultRuleDescriptionSection(
         ruleDescription + "\n"
-        + "== Exceptions" + "\n"
-        + exceptionsContent + "\n"
-        + "== Ask Yourself Whether" + "\n"
-        + askContent + "\n"
-        + "== Recommended Secure Coding Practices" + "\n"
-        + recommendedContent + "\n"
-      );
+          + "== Exceptions" + "\n"
+          + exceptionsContent + "\n"
+          + "== Ask Yourself Whether" + "\n"
+          + askContent + "\n"
+          + "== Recommended Secure Coding Practices" + "\n"
+          + recommendedContent + "\n"
+      ));
 
     HotspotRuleDescription result = HotspotRuleDescription.from(dto);
 
index d0f5d6df339b4cdab988dc89f9f5399606581822..b50f77deb3e82311754146c93df4e1cfd6273950 100644 (file)
@@ -21,37 +21,42 @@ package org.sonar.server.rule;
 
 import org.junit.Test;
 import org.sonar.db.rule.RuleDefinitionDto;
+import org.sonar.db.rule.RuleDescriptionSectionDto;
 import org.sonar.db.rule.RuleDto;
 
 import static org.assertj.core.api.Assertions.assertThat;
+import static org.sonar.db.rule.RuleDescriptionSectionDto.createDefaultRuleDescriptionSection;
 
 public class RuleDescriptionFormatterTest {
 
+  private static final RuleDescriptionSectionDto HTML_SECTION = createDefaultRuleDescriptionSection("<span class=\"example\">*md* ``description``</span>");
+  private static final RuleDescriptionSectionDto MARKDOWN_SECTION = createDefaultRuleDescriptionSection("*md* ``description``");
+
   @Test
   public void getMarkdownDescriptionAsHtml() {
-    RuleDefinitionDto rule = new RuleDefinitionDto().setDescription("*md* ``description``").setDescriptionFormat(RuleDto.Format.MARKDOWN);
+    RuleDefinitionDto rule = new RuleDefinitionDto().setDescriptionFormat(RuleDto.Format.MARKDOWN).addRuleDescriptionSectionDto(MARKDOWN_SECTION);
     String html = RuleDescriptionFormatter.getDescriptionAsHtml(rule);
     assertThat(html).isEqualTo("<strong>md</strong> <code>description</code>");
   }
 
   @Test
   public void getHtmlDescriptionAsIs() {
-    String description = "<span class=\"example\">*md* ``description``</span>";
-    RuleDefinitionDto rule = new RuleDefinitionDto().setDescription(description).setDescriptionFormat(RuleDto.Format.HTML);
+    RuleDefinitionDto rule = new RuleDefinitionDto().setDescriptionFormat(RuleDto.Format.HTML).addRuleDescriptionSectionDto(HTML_SECTION);
     String html = RuleDescriptionFormatter.getDescriptionAsHtml(rule);
-    assertThat(html).isEqualTo(description);
+    assertThat(html).isEqualTo(HTML_SECTION.getDescription());
   }
 
   @Test
-  public void handleNullDescription() {
-    RuleDefinitionDto rule = new RuleDefinitionDto().setDescription(null).setDescriptionFormat(RuleDto.Format.HTML);
+  public void handleEmptyDescription() {
+    RuleDefinitionDto rule = new RuleDefinitionDto().setDescriptionFormat(RuleDto.Format.HTML);
     String result = RuleDescriptionFormatter.getDescriptionAsHtml(rule);
     assertThat(result).isNull();
   }
 
   @Test
   public void handleNullDescriptionFormat() {
-    RuleDefinitionDto rule = new RuleDefinitionDto().setDescription("whatever").setDescriptionFormat(null);
+    RuleDescriptionSectionDto sectionWithNullFormat = createDefaultRuleDescriptionSection("whatever");
+    RuleDefinitionDto rule = new RuleDefinitionDto().addRuleDescriptionSectionDto(sectionWithNullFormat);
     String result = RuleDescriptionFormatter.getDescriptionAsHtml(rule);
     assertThat(result).isNull();
   }
index 9ca8078f54f8e99bf375af2ba8a06c9491bc30d7..d4ae6d45d5baec8741d23faf97b6bfc11dcc4654 100644 (file)
@@ -22,14 +22,11 @@ package org.sonar.server.rule.index;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Sets;
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
 import java.util.function.Consumer;
-import java.util.stream.Collectors;
-import org.assertj.core.api.Assertions;
 import org.junit.Rule;
 import org.junit.Test;
 import org.sonar.api.impl.utils.AlwaysIncreasingSystem2;
@@ -66,6 +63,7 @@ import static org.sonar.api.rules.RuleType.BUG;
 import static org.sonar.api.rules.RuleType.CODE_SMELL;
 import static org.sonar.api.rules.RuleType.SECURITY_HOTSPOT;
 import static org.sonar.api.rules.RuleType.VULNERABILITY;
+import static org.sonar.db.rule.RuleDescriptionSectionDto.createDefaultRuleDescriptionSection;
 import static org.sonar.db.rule.RuleTesting.setCreatedAt;
 import static org.sonar.db.rule.RuleTesting.setIsExternal;
 import static org.sonar.db.rule.RuleTesting.setIsTemplate;
@@ -216,19 +214,19 @@ public class RuleIndexTest {
     // otherwise the generated random values may raise false-positives
     RuleDefinitionDto rule1 = createJavaRule(rule -> rule.setRuleKey("123")
       .setName("rule 123")
-      .setDescription("My great rule CWE-123 which makes your code 1000 times better!"));
+      .addRuleDescriptionSectionDto(createDefaultRuleDescriptionSection("My great rule CWE-123 which makes your code 1000 times better!")));
     RuleDefinitionDto rule2 = createJavaRule(rule -> rule.setRuleKey("124")
       .setName("rule 124")
-      .setDescription("Another great and shiny rule CWE-124"));
+      .addRuleDescriptionSectionDto(createDefaultRuleDescriptionSection("Another great and shiny rule CWE-124")));
     RuleDefinitionDto rule3 = createJavaRule(rule -> rule.setRuleKey("1000")
       .setName("rule 1000")
-      .setDescription("Another great rule CWE-1000"));
+      .addRuleDescriptionSectionDto(createDefaultRuleDescriptionSection("Another great rule CWE-1000")));
     RuleDefinitionDto rule4 = createJavaRule(rule -> rule.setRuleKey("404")
       .setName("rule 404")
-      .setDescription("<h1>HTML-Geeks</h1><p style=\"color:blue\">special formatting!</p><table><tr><td>inside</td><td>tables</td></tr></table>"));
+      .addRuleDescriptionSectionDto(createDefaultRuleDescriptionSection("<h1>HTML-Geeks</h1><p style=\"color:blue\">special formatting!</p><table><tr><td>inside</td><td>tables</td></tr></table>")));
     RuleDefinitionDto rule5 = createJavaRule(rule -> rule.setRuleKey("405")
       .setName("rule 405")
-      .setDescription("internationalization missunderstandings alsdkjfnadklsjfnadkdfnsksdjfn"));
+      .addRuleDescriptionSectionDto(createDefaultRuleDescriptionSection("internationalization missunderstandings alsdkjfnadklsjfnadkdfnsksdjfn")));
     index();
 
     // partial match at word boundary
index 6fc139078840826a77d6e03010d5ec2deadf56c3..e3362dd71ee9a56eb0c23f9079048c8e1ee5575a 100644 (file)
@@ -28,7 +28,6 @@ import java.util.Random;
 import java.util.Set;
 import java.util.stream.IntStream;
 import java.util.stream.Stream;
-import javax.annotation.Nullable;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -41,6 +40,7 @@ import org.sonar.db.DbClient;
 import org.sonar.db.DbSession;
 import org.sonar.db.DbTester;
 import org.sonar.db.rule.RuleDefinitionDto;
+import org.sonar.db.rule.RuleDescriptionSectionDto;
 import org.sonar.db.rule.RuleDto;
 import org.sonar.db.rule.RuleDto.Scope;
 import org.sonar.db.rule.RuleTesting;
@@ -55,6 +55,7 @@ import static java.util.stream.Collectors.joining;
 import static java.util.stream.Collectors.toSet;
 import static org.apache.commons.lang.RandomStringUtils.randomAlphabetic;
 import static org.assertj.core.api.Assertions.assertThat;
+import static org.sonar.db.rule.RuleDescriptionSectionDto.createDefaultRuleDescriptionSection;
 import static org.sonar.server.rule.index.RuleIndexDefinition.TYPE_RULE;
 import static org.sonar.server.security.SecurityStandards.CWES_BY_SQ_CATEGORY;
 import static org.sonar.server.security.SecurityStandards.SQ_CATEGORY_KEYS_ORDERING;
@@ -62,11 +63,14 @@ import static org.sonar.server.security.SecurityStandards.SQ_CATEGORY_KEYS_ORDER
 @RunWith(DataProviderRunner.class)
 public class RuleIndexerTest {
 
-  public static final String VALID_HOTSPOT_RULE_DESCRIPTION = "acme\n" +
+  private static final String VALID_HOTSPOT_RULE_DESCRIPTION = "acme\n" +
     "<h2>Ask Yourself Whether</h2>\n" +
     "bar\n" +
     "<h2>Recommended Secure Coding Practices</h2>\n" +
     "foo";
+
+  private static final RuleDescriptionSectionDto RULE_DESCRIPTION_SECTION_DTO = createDefaultRuleDescriptionSection(VALID_HOTSPOT_RULE_DESCRIPTION);
+
   @Rule
   public EsTester es = EsTester.create();
   @Rule
@@ -83,8 +87,8 @@ public class RuleIndexerTest {
     .setRepositoryKey("xoo")
     .setConfigKey("S1")
     .setName("Null Pointer")
-    .setDescription("S001 desc")
     .setDescriptionFormat(RuleDto.Format.HTML)
+    .addRuleDescriptionSectionDto(RULE_DESCRIPTION_SECTION_DTO)
     .setLanguage("xoo")
     .setSeverity(Severity.BLOCKER)
     .setStatus(RuleStatus.READY)
@@ -127,7 +131,8 @@ public class RuleIndexerTest {
   @Test
   public void index_long_rule_description() {
     String description = IntStream.range(0, 100000).map(i -> i % 100).mapToObj(Integer::toString).collect(joining(" "));
-    RuleDefinitionDto rule = dbTester.rules().insert(r -> r.setDescription(description));
+    RuleDescriptionSectionDto ruleDescriptionSectionDto = createDefaultRuleDescriptionSection(description);
+    RuleDefinitionDto rule = dbTester.rules().insert(r -> r.addRuleDescriptionSectionDto(ruleDescriptionSectionDto));
     underTest.commitAndIndex(dbTester.getSession(), rule.getUuid());
 
     assertThat(es.countDocuments(TYPE_RULE)).isOne();
@@ -143,7 +148,7 @@ public class RuleIndexerTest {
     RuleDefinitionDto rule = dbTester.rules().insert(RuleTesting.newRule()
       .setType(RuleType.SECURITY_HOTSPOT)
       .setSecurityStandards(standards)
-      .setDescription(VALID_HOTSPOT_RULE_DESCRIPTION));
+      .addRuleDescriptionSectionDto(RULE_DESCRIPTION_SECTION_DTO));
     underTest.commitAndIndex(dbTester.getSession(), rule.getUuid());
 
     assertThat(logTester.getLogs()).hasSize(1);
@@ -174,11 +179,9 @@ public class RuleIndexerTest {
   }
 
   @Test
-  @UseDataProvider("nullEmptyOrNoTitleDescription")
-  public void log_debug_when_hotspot_rule_description_is_null_or_empty(@Nullable String description) {
-    RuleDefinitionDto rule = dbTester.rules().insert(RuleTesting.newRule()
-      .setType(RuleType.SECURITY_HOTSPOT)
-      .setDescription(description));
+  public void log_debug_when_hotspot_rule_no_description () {
+    RuleDefinitionDto rule = dbTester.rules().insert(RuleTesting.newRuleWithoutSection()
+      .setType(RuleType.SECURITY_HOTSPOT));
     underTest.commitAndIndex(dbTester.getSession(), rule.getUuid());
 
     assertThat(logTester.getLogs()).hasSize(1);
@@ -188,19 +191,11 @@ public class RuleIndexerTest {
         rule.getKey()));
   }
 
-  @DataProvider
-  public static Object[][] nullEmptyOrNoTitleDescription() {
-    return new Object[][] {
-      {null},
-      {""},
-    };
-  }
-
   @Test
   public void log_debug_when_hotspot_rule_description_has_none_of_the_key_titles() {
     RuleDefinitionDto rule = dbTester.rules().insert(RuleTesting.newRule()
       .setType(RuleType.SECURITY_HOTSPOT)
-      .setDescription(randomAlphabetic(30)));
+      .addRuleDescriptionSectionDto(createDefaultRuleDescriptionSection(randomAlphabetic(30))));
     underTest.commitAndIndex(dbTester.getSession(), rule.getUuid());
 
     assertThat(logTester.getLogs()).hasSize(1);
@@ -214,9 +209,9 @@ public class RuleIndexerTest {
   public void log_debug_when_hotspot_rule_description_is_missing_fixIt_tab_content() {
     RuleDefinitionDto rule = dbTester.rules().insert(RuleTesting.newRule()
       .setType(RuleType.SECURITY_HOTSPOT)
-      .setDescription("bar\n" +
+      .addRuleDescriptionSectionDto(createDefaultRuleDescriptionSection("bar\n" +
         "<h2>Ask Yourself Whether</h2>\n" +
-        "foo"));
+        "foo")));
     underTest.commitAndIndex(dbTester.getSession(), rule.getUuid());
 
     assertThat(logTester.getLogs()).hasSize(1);
@@ -230,10 +225,10 @@ public class RuleIndexerTest {
   public void log_debug_when_hotspot_rule_description_is_missing_risk_tab_content() {
     RuleDefinitionDto rule = dbTester.rules().insert(RuleTesting.newRule()
       .setType(RuleType.SECURITY_HOTSPOT)
-      .setDescription("<h2>Ask Yourself Whether</h2>\n" +
+      .addRuleDescriptionSectionDto(createDefaultRuleDescriptionSection("<h2>Ask Yourself Whether</h2>\n" +
         "bar\n" +
         "<h2>Recommended Secure Coding Practices</h2>\n" +
-        "foo"));
+        "foo")));
     underTest.commitAndIndex(dbTester.getSession(), rule.getUuid());
 
     assertThat(logTester.getLogs()).hasSize(1);
@@ -247,9 +242,9 @@ public class RuleIndexerTest {
   public void log_debug_when_hotspot_rule_description_is_missing_vulnerable_tab_content() {
     RuleDefinitionDto rule = dbTester.rules().insert(RuleTesting.newRule()
       .setType(RuleType.SECURITY_HOTSPOT)
-      .setDescription("bar\n" +
+      .addRuleDescriptionSectionDto(createDefaultRuleDescriptionSection("bar\n" +
         "<h2>Recommended Secure Coding Practices</h2>\n" +
-        "foo"));
+        "foo")));
     underTest.commitAndIndex(dbTester.getSession(), rule.getUuid());
 
     assertThat(logTester.getLogs()).hasSize(1);
index 17199eb9ce1c37507fb50b1187d62def80108222..43f6fdb03c6ec52fddb0013249a2f808c23828fe 100644 (file)
@@ -41,9 +41,7 @@ import org.sonar.core.util.stream.MoreCollectors;
 import org.sonar.db.DbClient;
 import org.sonar.db.DbSession;
 import org.sonar.db.rule.RuleDefinitionDto;
-import org.sonar.db.rule.RuleDto;
 import org.sonar.db.rule.RuleParamDto;
-import org.sonar.markdown.Markdown;
 
 import static java.util.Collections.emptyList;
 import static java.util.Collections.unmodifiableMap;
@@ -147,8 +145,6 @@ public class CachingRuleFinder implements ServerRuleFinder {
 
   private static Rule toRule(RuleDefinitionDto ruleDefinition, List<RuleParamDto> params) {
     String severity = ruleDefinition.getSeverityString();
-    String description = ruleDefinition.getDescription();
-    RuleDto.Format descriptionFormat = ruleDefinition.getDescriptionFormat();
 
     Rule apiRule = new Rule();
     apiRule
@@ -163,14 +159,9 @@ public class CachingRuleFinder implements ServerRuleFinder {
       .setSeverity(severity != null ? RulePriority.valueOf(severity) : null)
       .setStatus(ruleDefinition.getStatus().name())
       .setSystemTags(ruleDefinition.getSystemTags().toArray(new String[ruleDefinition.getSystemTags().size()]))
-      .setTags(new String[0]);
-    if (description != null && descriptionFormat != null) {
-      if (RuleDto.Format.HTML.equals(descriptionFormat)) {
-        apiRule.setDescription(description);
-      } else {
-        apiRule.setDescription(Markdown.convertToHtml(description));
-      }
-    }
+      .setTags(new String[0])
+      .setDescription(RuleDescriptionFormatter.getDescriptionAsHtml(ruleDefinition));
+
 
     List<org.sonar.api.rules.RuleParam> apiParams = new ArrayList<>();
     for (RuleParamDto param : params) {
index 0d37e1221feca26b447ed933361225c5bb61b25b..cd2c223598156cb52f01d14d4a2731b81c688599 100644 (file)
@@ -46,6 +46,7 @@ import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyNoMoreInteractions;
 import static org.mockito.Mockito.when;
+import static org.sonar.db.rule.RuleDescriptionSectionDto.createDefaultRuleDescriptionSection;
 
 public class CachingRuleFinderTest {
   @org.junit.Rule
@@ -422,7 +423,7 @@ public class CachingRuleFinderTest {
     assertThat(rule.getSeverity().name()).isEqualTo(ruleDefinition.getSeverityString());
     assertThat(rule.getSystemTags()).isEqualTo(ruleDefinition.getSystemTags().toArray(new String[0]));
     assertThat(rule.getTags()).isEmpty();
-    assertThat(rule.getDescription()).isEqualTo(ruleDefinition.getDescription());
+    assertThat(rule.getDescription()).isEqualTo(createDefaultRuleDescriptionSection(randomAlphabetic(5)).getDescription());
 
     assertThat(rule.getParams()).hasSize(1);
     org.sonar.api.rules.RuleParam param = rule.getParams().iterator().next();
index 5309cbb697fce61af596b99798f324e98191f783..fb4e08ec15a1a0a65a2b132576dcaa0cda3cffc1 100644 (file)
@@ -75,9 +75,11 @@ import static java.lang.String.format;
 import static java.util.Collections.emptyList;
 import static java.util.Collections.emptySet;
 import static java.util.Collections.unmodifiableMap;
+import static org.apache.commons.lang.StringUtils.isNotEmpty;
 import static org.sonar.core.util.stream.MoreCollectors.toList;
 import static org.sonar.core.util.stream.MoreCollectors.toSet;
 import static org.sonar.core.util.stream.MoreCollectors.uniqueIndex;
+import static org.sonar.db.rule.RuleDescriptionSectionDto.createDefaultRuleDescriptionSection;
 
 /**
  * Register rules at server startup
@@ -261,10 +263,10 @@ public class RegisterRules implements Startable {
 
     private Stream<RuleDefinitionDto> getAllModified() {
       return Stream.of(
-        created.stream(),
-        updated.stream(),
-        removed.stream(),
-        renamed.keySet().stream())
+          created.stream(),
+          updated.stream(),
+          removed.stream(),
+          renamed.keySet().stream())
         .flatMap(s -> s);
     }
 
@@ -398,13 +400,14 @@ public class RegisterRules implements Startable {
       .setIsAdHoc(false)
       .setCreatedAt(system2.now())
       .setUpdatedAt(system2.now());
-    if (ruleDef.htmlDescription() != null) {
-      ruleDto.setDescription(ruleDef.htmlDescription());
+    if (isNotEmpty(ruleDef.htmlDescription())) {
+      ruleDto.addRuleDescriptionSectionDto(createDefaultRuleDescriptionSection(ruleDef.htmlDescription()));
       ruleDto.setDescriptionFormat(Format.HTML);
-    } else {
-      ruleDto.setDescription(ruleDef.markdownDescription());
+    } else if (isNotEmpty(ruleDef.markdownDescription())) {
+      ruleDto.addRuleDescriptionSectionDto(createDefaultRuleDescriptionSection(ruleDef.markdownDescription()));
       ruleDto.setDescriptionFormat(Format.MARKDOWN);
     }
+
     DebtRemediationFunction debtRemediationFunction = ruleDef.debtRemediationFunction();
     if (debtRemediationFunction != null) {
       ruleDto.setDefRemediationFunction(debtRemediationFunction.type().name());
@@ -481,20 +484,30 @@ public class RegisterRules implements Startable {
     return changed;
   }
 
-  private static boolean mergeDescription(RulesDefinition.Rule def, RuleDefinitionDto dto) {
+  private static boolean mergeDescription(RulesDefinition.Rule rule, RuleDefinitionDto ruleDefinitionDto) {
     boolean changed = false;
-    if (def.htmlDescription() != null && !Objects.equals(dto.getDescription(), def.htmlDescription())) {
-      dto.setDescription(def.htmlDescription());
-      dto.setDescriptionFormat(Format.HTML);
+
+    String currentDescription = ruleDefinitionDto.getDefaultRuleDescriptionSectionDto() != null ? ruleDefinitionDto.getDefaultRuleDescriptionSectionDto().getDescription() : null;
+    if (isHtmlDescriptionUpdated(rule, currentDescription)) {
+      ruleDefinitionDto.addOrReplaceRuleDescriptionSectionDto(createDefaultRuleDescriptionSection(rule.htmlDescription()));
+      ruleDefinitionDto.setDescriptionFormat(Format.HTML);
       changed = true;
-    } else if (def.markdownDescription() != null && !Objects.equals(dto.getDescription(), def.markdownDescription())) {
-      dto.setDescription(def.markdownDescription());
-      dto.setDescriptionFormat(Format.MARKDOWN);
+    } else if (isMarkdownDescriptionUpdated(rule, currentDescription)) {
+      ruleDefinitionDto.addOrReplaceRuleDescriptionSectionDto(createDefaultRuleDescriptionSection(rule.markdownDescription()));
+      ruleDefinitionDto.setDescriptionFormat(Format.MARKDOWN);
       changed = true;
     }
     return changed;
   }
 
+  private static boolean isMarkdownDescriptionUpdated(RulesDefinition.Rule rule, @Nullable String currentDescription) {
+    return isNotEmpty(rule.markdownDescription()) && !Objects.equals(rule.markdownDescription(), currentDescription);
+  }
+
+  private static boolean isHtmlDescriptionUpdated(RulesDefinition.Rule def, @Nullable String currentDescription) {
+    return isNotEmpty(def.htmlDescription()) && !Objects.equals(def.htmlDescription(), currentDescription);
+  }
+
   private static boolean mergeDebtDefinitions(RulesDefinition.Rule def, RuleDefinitionDto dto) {
     // Debt definitions are set to null if the sub-characteristic and the remediation function are null
     DebtRemediationFunction debtRemediationFunction = def.debtRemediationFunction();
index 6c7f40dc89f212a374e670d6a1a5b5b52414de0a..e9cbb4ebbc79a8fdd10aff5003c8cb5e3d45b01c 100644 (file)
@@ -88,6 +88,7 @@ import static org.sonar.api.server.rule.RulesDefinition.NewRepository;
 import static org.sonar.api.server.rule.RulesDefinition.NewRule;
 import static org.sonar.api.server.rule.RulesDefinition.OwaspTop10;
 import static org.sonar.api.server.rule.RulesDefinition.OwaspTop10Version.Y2021;
+import static org.sonar.db.rule.RuleDescriptionSectionDto.createDefaultRuleDescriptionSection;
 
 @RunWith(DataProviderRunner.class)
 public class RegisterRulesTest {
@@ -124,7 +125,6 @@ public class RegisterRulesTest {
   private ActiveRuleIndexer activeRuleIndexer;
   private RuleIndex ruleIndex;
 
-
   @Before
   public void before() {
     ruleIndexer = new RuleIndexer(es.client(), dbClient);
@@ -140,7 +140,7 @@ public class RegisterRulesTest {
     assertThat(dbClient.ruleDao().selectAllDefinitions(db.getSession())).hasSize(3);
     RuleDto rule1 = dbClient.ruleDao().selectOrFailByKey(db.getSession(), RULE_KEY1);
     assertThat(rule1.getName()).isEqualTo("One");
-    assertThat(rule1.getDescription()).isEqualTo("Description of One");
+    assertThat(rule1.getDefaultRuleDescriptionSection().getDescription()).isEqualTo("Description of One");
     assertThat(rule1.getSeverityString()).isEqualTo(BLOCKER);
     assertThat(rule1.getTags()).isEmpty();
     assertThat(rule1.getSystemTags()).containsOnly("tag1", "tag2", "tag3");
@@ -159,7 +159,7 @@ public class RegisterRulesTest {
 
     RuleDto hotspotRule = dbClient.ruleDao().selectOrFailByKey(db.getSession(), HOTSPOT_RULE_KEY);
     assertThat(hotspotRule.getName()).isEqualTo("Hotspot");
-    assertThat(hotspotRule.getDescription()).isEqualTo("Minimal hotspot");
+    assertThat(hotspotRule.getDefaultRuleDescriptionSection().getDescription()).isEqualTo("Minimal hotspot");
     assertThat(hotspotRule.getCreatedAt()).isEqualTo(DATE1.getTime());
     assertThat(hotspotRule.getUpdatedAt()).isEqualTo(DATE1.getTime());
     assertThat(hotspotRule.getType()).isEqualTo(RuleType.SECURITY_HOTSPOT.getDbConstant());
@@ -188,7 +188,7 @@ public class RegisterRulesTest {
     assertThat(dbClient.ruleDao().selectAllDefinitions(db.getSession())).hasSize(2);
     RuleDto rule1 = dbClient.ruleDao().selectOrFailByKey(db.getSession(), EXTERNAL_RULE_KEY1);
     assertThat(rule1.getName()).isEqualTo("One");
-    assertThat(rule1.getDescription()).isEqualTo("Description of One");
+    assertThat(rule1.getDefaultRuleDescriptionSection().getDescription()).isEqualTo("Description of One");
     assertThat(rule1.getSeverityString()).isEqualTo(BLOCKER);
     assertThat(rule1.getTags()).isEmpty();
     assertThat(rule1.getSystemTags()).containsOnly("tag1", "tag2", "tag3");
@@ -207,7 +207,7 @@ public class RegisterRulesTest {
 
     RuleDto hotspotRule = dbClient.ruleDao().selectOrFailByKey(db.getSession(), EXTERNAL_HOTSPOT_RULE_KEY);
     assertThat(hotspotRule.getName()).isEqualTo("Hotspot");
-    assertThat(hotspotRule.getDescription()).isEqualTo("Minimal hotspot");
+    assertThat(hotspotRule.getDefaultRuleDescriptionSection().getDescription()).isEqualTo("Minimal hotspot");
     assertThat(hotspotRule.getCreatedAt()).isEqualTo(DATE1.getTime());
     assertThat(hotspotRule.getUpdatedAt()).isEqualTo(DATE1.getTime());
     assertThat(hotspotRule.getType()).isEqualTo(RuleType.SECURITY_HOTSPOT.getDbConstant());
@@ -335,7 +335,7 @@ public class RegisterRulesTest {
     // rule1 has been updated
     rule1 = dbClient.ruleDao().selectOrFailByKey(db.getSession(), RULE_KEY1);
     assertThat(rule1.getName()).isEqualTo("One v2");
-    assertThat(rule1.getDescription()).isEqualTo("Description of One v2");
+    assertThat(rule1.getDefaultRuleDescriptionSection().getDescription()).isEqualTo("Description of One v2");
     assertThat(rule1.getSeverityString()).isEqualTo(INFO);
     assertThat(rule1.getTags()).containsOnly("usertag1", "usertag2");
     assertThat(rule1.getSystemTags()).containsOnly("tag1", "tag4");
@@ -449,7 +449,7 @@ public class RegisterRulesTest {
     // rule1 has been updated
     RuleDto rule1 = dbClient.ruleDao().selectOrFailByKey(db.getSession(), RuleKey.of("fake", "rule"));
     assertThat(rule1.getName()).isEqualTo("Name2");
-    assertThat(rule1.getDescription()).isEqualTo("Description");
+    assertThat(rule1.getDefaultRuleDescriptionSection().getDescription()).isEqualTo("Description");
 
     assertThat(ruleIndex.search(new RuleQuery().setQueryText("Name2"), new SearchOptions()).getTotal()).isOne();
     assertThat(ruleIndex.search(new RuleQuery().setQueryText("Name1"), new SearchOptions()).getTotal()).isZero();
@@ -528,7 +528,7 @@ public class RegisterRulesTest {
     RuleDto rule2 = dbClient.ruleDao().selectOrFailByKey(db.getSession(), RuleKey.of(repository, ruleKey2));
     assertThat(rule2.getUuid()).isEqualTo(rule1.getUuid());
     assertThat(rule2.getName()).isEqualTo("Name2");
-    assertThat(rule2.getDescription()).isEqualTo(rule1.getDescription());
+    assertThat(rule2.getDefaultRuleDescriptionSection().getDescription()).isEqualTo(rule1.getDefaultRuleDescriptionSection().getDescription());
 
     SearchIdResult<String> searchRule2 = ruleIndex.search(new RuleQuery().setQueryText("Name2"), new SearchOptions());
     assertThat(searchRule2.getUuids()).containsOnly(rule2.getUuid());
@@ -570,7 +570,7 @@ public class RegisterRulesTest {
     RuleDto rule2 = dbClient.ruleDao().selectOrFailByKey(db.getSession(), RuleKey.of(repository2, ruleKey));
     assertThat(rule2.getUuid()).isEqualTo(rule1.getUuid());
     assertThat(rule2.getName()).isEqualTo("Name2");
-    assertThat(rule2.getDescription()).isEqualTo(rule1.getDescription());
+    assertThat(rule2.getDefaultRuleDescriptionSection().getDescription()).isEqualTo(rule1.getDefaultRuleDescriptionSection().getDescription());
 
     SearchIdResult<String> searchRule2 = ruleIndex.search(new RuleQuery().setQueryText("Name2"), new SearchOptions());
     assertThat(searchRule2.getUuids()).containsOnly(rule2.getUuid());
@@ -610,7 +610,7 @@ public class RegisterRulesTest {
     RuleDto rule2 = dbClient.ruleDao().selectOrFailByKey(db.getSession(), RuleKey.of(repo2, ruleKey2));
     assertThat(rule2.getUuid()).isEqualTo(rule1.getUuid());
     assertThat(rule2.getName()).isEqualTo(rule1.getName());
-    assertThat(rule2.getDescription()).isEqualTo(rule1.getDescription());
+    assertThat(rule2.getDefaultRuleDescriptionSection().getDescription()).isEqualTo(rule1.getDefaultRuleDescriptionSection().getDescription());
 
     assertThat(ruleIndex.search(new RuleQuery().setQueryText(name), new SearchOptions()).getUuids())
       .containsOnly(rule2.getUuid());
@@ -618,7 +618,7 @@ public class RegisterRulesTest {
 
   @DataProvider
   public static Object[][] allRenamingCases() {
-    return new Object[][]{
+    return new Object[][] {
       {"repo1", "rule1", "repo1", "rule2"},
       {"repo1", "rule1", "repo2", "rule1"},
       {"repo1", "rule1", "repo2", "rule2"},
@@ -688,7 +688,7 @@ public class RegisterRulesTest {
     // rule1 has been updated
     RuleDto rule1 = dbClient.ruleDao().selectOrFailByKey(db.getSession(), RuleKey.of("fake", "rule"));
     assertThat(rule1.getName()).isEqualTo("Name");
-    assertThat(rule1.getDescription()).isEqualTo("Desc2");
+    assertThat(rule1.getDefaultRuleDescriptionSection().getDescription()).isEqualTo("Desc2");
 
     assertThat(ruleIndex.search(new RuleQuery().setQueryText("Desc2"), new SearchOptions()).getTotal()).isOne();
     assertThat(ruleIndex.search(new RuleQuery().setQueryText("Desc1"), new SearchOptions()).getTotal()).isZero();
@@ -702,7 +702,7 @@ public class RegisterRulesTest {
       NewRepository repo = context.createExternalRepository("fake", rule.getLanguage());
       repo.createRule(rule.getRuleKey())
         .setName(rule.getName())
-        .setHtmlDescription(rule.getDescription());
+        .setHtmlDescription(rule.getDefaultRuleDescriptionSectionDto().getDescription());
       repo.done();
     });
 
@@ -836,7 +836,7 @@ public class RegisterRulesTest {
       .setRepositoryKey("findbugs")
       .setName("Rule One")
       .setScope(Scope.ALL)
-      .setDescription("Rule one description")
+      .addRuleDescriptionSectionDto(createDefaultRuleDescriptionSection("Rule one description"))
       .setDescriptionFormat(RuleDto.Format.HTML)
       .setSystemTags(newHashSet("tag1", "tag2")));
     db.getSession().commit();
index 1e12fda35ca5e890d93bfbc99f6cb91cd2936d7c..77b5db39783d2ecc0d57b6ff64a659daf868987d 100644 (file)
@@ -47,7 +47,7 @@ import static org.assertj.core.api.Assertions.assertThat;
 import static org.assertj.core.api.Assertions.tuple;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
-import static org.sonar.db.rule.RuleDto.Format.MARKDOWN;
+import static org.sonar.db.rule.RuleDescriptionSectionDto.createDefaultRuleDescriptionSection;
 import static org.sonar.db.rule.RuleTesting.newCustomRule;
 import static org.sonar.db.rule.RuleTesting.newTemplateRule;
 import static org.sonar.server.qualityprofile.ActiveRuleChange.Type.ACTIVATED;
@@ -73,8 +73,8 @@ public class QualityProfileChangeEventServiceImplTest {
       .setLanguage("xoo")
       .setRepositoryKey("repo")
       .setRuleKey("ruleKey")
-      .setDescription("<div>line1\nline2</div>")
-      .setDescriptionFormat(MARKDOWN);
+      .setDescriptionFormat(RuleDto.Format.MARKDOWN)
+      .addRuleDescriptionSectionDto(createDefaultRuleDescriptionSection("<div>line1\nline2</div>"));
     db.rules().insert(rule1);
 
     ActiveRuleDto activeRuleDto = ActiveRuleDto.createFor(qualityProfileDto, rule1);
index ed07ddfa98c375e48189ebd0ec1793baae710561..e67c9d22a4db126be6c4d8ec27cf0ce2711c565e 100644 (file)
@@ -39,6 +39,7 @@ import org.sonar.core.util.UuidFactory;
 import org.sonar.db.DbClient;
 import org.sonar.db.DbSession;
 import org.sonar.db.rule.RuleDefinitionDto;
+import org.sonar.db.rule.RuleDescriptionSectionDto;
 import org.sonar.db.rule.RuleDto;
 import org.sonar.db.rule.RuleDto.Format;
 import org.sonar.db.rule.RuleMetadataDto;
@@ -50,6 +51,7 @@ import org.sonar.server.util.TypeValidations;
 import static com.google.common.base.Preconditions.checkArgument;
 import static com.google.common.collect.Lists.newArrayList;
 import static java.lang.String.format;
+import static org.sonar.db.rule.RuleDescriptionSectionDto.createDefaultRuleDescriptionSection;
 import static org.sonar.server.exceptions.BadRequestException.checkRequest;
 
 @ServerSide
@@ -189,8 +191,6 @@ public class RuleCreator {
       .setTemplateUuid(templateRuleDto.getUuid())
       .setConfigKey(templateRuleDto.getConfigKey())
       .setName(newRule.name())
-      .setDescription(newRule.markdownDescription())
-      .setDescriptionFormat(Format.MARKDOWN)
       .setSeverity(newRule.severity())
       .setStatus(newRule.status())
       .setType(newRule.type() == null ? templateRuleDto.getType() : newRule.type().getDbConstant())
@@ -206,6 +206,13 @@ public class RuleCreator {
       .setIsAdHoc(false)
       .setCreatedAt(system2.now())
       .setUpdatedAt(system2.now());
+
+    if (newRule.markdownDescription() != null) {
+      RuleDescriptionSectionDto ruleDescriptionSectionDto = createDefaultRuleDescriptionSection(newRule.markdownDescription());
+      ruleDefinition.setDescriptionFormat(Format.MARKDOWN);
+      ruleDefinition.addRuleDescriptionSectionDto(ruleDescriptionSectionDto);
+    }
+
     dbClient.ruleDao().insert(dbSession, ruleDefinition);
 
     Set<String> tags = templateRuleDto.getTags();
index 09dc0e58de0a49f5e981edef9568a9aab0adeada..78e4aebf313542c56df1ca357d2339cf9cf737f2 100644 (file)
@@ -43,6 +43,7 @@ import org.sonar.db.qualityprofile.ActiveRuleDto;
 import org.sonar.db.qualityprofile.ActiveRuleParamDto;
 import org.sonar.db.qualityprofile.OrgActiveRuleDto;
 import org.sonar.db.rule.RuleDefinitionDto;
+import org.sonar.db.rule.RuleDescriptionSectionDto;
 import org.sonar.db.rule.RuleDto;
 import org.sonar.db.rule.RuleParamDto;
 import org.sonar.server.rule.index.RuleIndexer;
@@ -53,6 +54,7 @@ import static com.google.common.base.Strings.isNullOrEmpty;
 import static com.google.common.collect.FluentIterable.from;
 import static com.google.common.collect.Lists.newArrayList;
 import static org.apache.commons.lang.StringUtils.isBlank;
+import static org.sonar.db.rule.RuleDescriptionSectionDto.createDefaultRuleDescriptionSection;
 
 @ServerSide
 public class RuleUpdater {
@@ -136,8 +138,9 @@ public class RuleUpdater {
     if (isNullOrEmpty(description)) {
       throw new IllegalArgumentException("The description is missing");
     }
-    rule.setDescription(description);
+    RuleDescriptionSectionDto descriptionSectionDto = createDefaultRuleDescriptionSection(description);
     rule.setDescriptionFormat(RuleDto.Format.MARKDOWN);
+    rule.addOrReplaceRuleDescriptionSectionDto(descriptionSectionDto);
   }
 
   private static void updateSeverity(RuleUpdate update, RuleDto rule) {
index 867e323cabde2cadba18e4ecc20d3d826e14ea97..4b4afd81a9a3246d9d19cd6d66c64fecd8d6c444 100644 (file)
@@ -34,6 +34,7 @@ import org.sonar.api.server.debt.DebtRemediationFunction;
 import org.sonar.api.server.debt.internal.DefaultDebtRemediationFunction;
 import org.sonar.db.rule.DeprecatedRuleKeyDto;
 import org.sonar.db.rule.RuleDefinitionDto;
+import org.sonar.db.rule.RuleDescriptionSectionDto;
 import org.sonar.db.rule.RuleDto;
 import org.sonar.db.rule.RuleDto.Scope;
 import org.sonar.db.rule.RuleMetadataDto;
@@ -47,6 +48,7 @@ import org.sonarqube.ws.Common;
 import org.sonarqube.ws.Common.RuleScope;
 import org.sonarqube.ws.Rules;
 
+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.server.rule.ws.RulesWsParameters.FIELD_CREATED_AT;
@@ -329,12 +331,19 @@ public class RuleMapper {
       }
     }
 
-    String description = ruleDto.getDescription();
-    if (shouldReturnField(fieldsToReturn, FIELD_MARKDOWN_DESCRIPTION) && description != null) {
+    if (shouldReturnField(fieldsToReturn, FIELD_MARKDOWN_DESCRIPTION)
+      && !ruleDto.getRuleDescriptionSectionDtos().isEmpty()) {
+      String description = concatenateSectionTemporaryForSonar16302(ruleDto);
       ruleResponse.setMdDesc(description);
     }
   }
 
+  private static String concatenateSectionTemporaryForSonar16302(RuleDefinitionDto ruleDto) {
+    return ruleDto.getRuleDescriptionSectionDtos().stream()
+      .map(RuleDescriptionSectionDto::getDescription)
+      .collect(joining());
+  }
+
   private void setNotesFields(Rules.Rule.Builder ruleResponse, RuleMetadataDto ruleDto, Map<String, UserDto> usersByUuid, Set<String> fieldsToReturn) {
     String noteData = ruleDto.getNoteData();
     if (shouldReturnField(fieldsToReturn, "htmlNote") && noteData != null) {
index f571e58bbd9cc5178a879f6ffc19224e90069faf..fd9c031e807fbe4dec5d873b570da8ea3d968330 100644 (file)
@@ -30,6 +30,7 @@ import java.util.List;
 import java.util.Random;
 import java.util.Set;
 import java.util.function.Consumer;
+import java.util.function.Supplier;
 import java.util.stream.Collectors;
 import java.util.stream.IntStream;
 import java.util.stream.Stream;
@@ -89,6 +90,7 @@ import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 import static org.sonar.api.rules.RuleType.SECURITY_HOTSPOT;
 import static org.sonar.db.component.ComponentTesting.newFileDto;
+import static org.sonar.db.rule.RuleDescriptionSectionDto.createDefaultRuleDescriptionSection;
 import static org.sonar.db.rule.RuleDto.Format.MARKDOWN;
 
 @RunWith(DataProviderRunner.class)
@@ -431,9 +433,9 @@ public class ShowActionTest {
 
     String description = "== Title\n<div>line1\nline2</div>";
 
-    RuleDefinitionDto rule = newRule(SECURITY_HOTSPOT,
+    RuleDefinitionDto rule = newRuleWithoutSection(SECURITY_HOTSPOT,
       r -> r.setTemplateUuid("123")
-        .setDescription(description)
+        .addRuleDescriptionSectionDto(createDefaultRuleDescriptionSection(description))
         .setDescriptionFormat(MARKDOWN));
 
     IssueDto hotspot = dbTester.issues().insertHotspot(rule, project, file);
@@ -452,7 +454,7 @@ public class ShowActionTest {
     userSessionRule.logIn().addProjectPermission(UserRole.USER, project);
     ComponentDto file = dbTester.components().insertComponent(newFileDto(project));
 
-    RuleDefinitionDto rule = newRule(SECURITY_HOTSPOT, r -> r.setTemplateUuid("123").setDescription(null));
+    RuleDefinitionDto rule = newRule(SECURITY_HOTSPOT, r -> r.setTemplateUuid("123"));
 
     IssueDto hotspot = dbTester.issues().insertHotspot(rule, project, file);
     mockChangelogAndCommentsFormattingContext();
@@ -1024,11 +1026,18 @@ public class ShowActionTest {
   }
 
   private RuleDefinitionDto newRule(RuleType ruleType, Consumer<RuleDefinitionDto> populate) {
-    RuleDefinitionDto ruleDefinition = RuleTesting.newRule()
-      .setType(ruleType);
-    populate.accept(ruleDefinition);
-    dbTester.rules().insert(ruleDefinition);
-    return ruleDefinition;
+    return newRule(ruleType, RuleTesting::newRule, populate);
+  }
+
+  private RuleDefinitionDto newRuleWithoutSection(RuleType ruleType, Consumer<RuleDefinitionDto> populate) {
+    return newRule(ruleType, RuleTesting::newRuleWithoutSection, populate);
+  }
+
+  private RuleDefinitionDto newRule(RuleType ruleType, Supplier<RuleDefinitionDto> ruleDefinitionDtoSupplier, Consumer<RuleDefinitionDto> populate) {
+    RuleDefinitionDto ruleDefinitionDto = ruleDefinitionDtoSupplier.get().setType(ruleType);
+    populate.accept(ruleDefinitionDto);
+    dbTester.rules().insert(ruleDefinitionDto);
+    return ruleDefinitionDto;
   }
 
   private static class IssueDtoSetArgumentMatcher implements ArgumentMatcher<Set<IssueDto>> {
index c1c4ba82695f80a37686b487aaff8861a9da2468..cfc87dc6e5f1855f55258b9a5cc73000b24a7572 100644 (file)
@@ -100,6 +100,7 @@ import static org.sonar.api.web.UserRole.ISSUE_ADMIN;
 import static org.sonar.db.component.ComponentDto.PULL_REQUEST_SEPARATOR;
 import static org.sonar.db.component.ComponentTesting.newFileDto;
 import static org.sonar.db.issue.IssueTesting.newDto;
+import static org.sonar.db.rule.RuleDescriptionSectionDto.createDefaultRuleDescriptionSection;
 import static org.sonar.server.tester.UserSessionRule.standalone;
 import static org.sonarqube.ws.Common.RuleType.BUG;
 import static org.sonarqube.ws.Common.RuleType.SECURITY_HOTSPOT_VALUE;
@@ -1441,7 +1442,7 @@ public class SearchActionTest {
   private RuleDto newIssueRule() {
     RuleDto rule = RuleTesting.newXooX1()
       .setName("Rule name")
-      .setDescription("Rule desc")
+      .addOrReplaceRuleDescriptionSectionDto(createDefaultRuleDescriptionSection("Rule desc"))
       .setStatus(RuleStatus.READY);
     db.rules().insert(rule.getDefinition());
     return rule;
@@ -1450,7 +1451,7 @@ public class SearchActionTest {
   private RuleDto newHotspotRule() {
     RuleDto rule = RuleTesting.newXooX2()
       .setName("Rule name")
-      .setDescription("Rule desc")
+      .addOrReplaceRuleDescriptionSectionDto(createDefaultRuleDescriptionSection("Rule desc"))
       .setStatus(RuleStatus.READY)
       .setType(SECURITY_HOTSPOT_VALUE);
     db.rules().insert(rule.getDefinition());
index 800c5623d594139039a859f9aa7bf109eb873d99..a4faa701fa1e680d2b38dcec36cab1faae2a1ce6 100644 (file)
@@ -54,6 +54,7 @@ import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyList;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
+import static org.sonar.db.rule.RuleDescriptionSectionDto.createDefaultRuleDescriptionSection;
 
 public class QProfileBackuperImplTest {
 
@@ -141,7 +142,7 @@ public class QProfileBackuperImplTest {
     RuleDefinitionDto templateRule = db.rules().insert(ruleDefinitionDto -> ruleDefinitionDto
       .setIsTemplate(true));
     RuleDefinitionDto rule = db.rules().insert(ruleDefinitionDto -> ruleDefinitionDto
-      .setDescription("custom rule description")
+      .addOrReplaceRuleDescriptionSectionDto(createDefaultRuleDescriptionSection("custom rule description"))
       .setName("custom rule name")
       .setStatus(RuleStatus.READY)
       .setTemplateUuid(templateRule.getUuid()));
@@ -163,7 +164,7 @@ public class QProfileBackuperImplTest {
       "<priority>" + activeRule.getSeverityString() + "</priority>" +
       "<name>" + rule.getName() + "</name>" +
       "<templateKey>" + templateRule.getKey().rule() + "</templateKey>" +
-      "<description>" + rule.getDescription() + "</description>" +
+      "<description>" + rule.getDefaultRuleDescriptionSectionDto().getDescription() + "</description>" +
       "<parameters><parameter>" +
       "<key>" + param.getName() + "</key>" +
       "<value>20</value>" +
index dd98bd5922374b74a803ac4729ec0e80811e899d..63627fd5556f529d876c2f19e59ace07d657a07b 100644 (file)
@@ -55,6 +55,7 @@ import org.sonar.server.rule.index.RuleQuery;
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.assertj.core.api.Assertions.assertThatThrownBy;
 import static org.junit.Assert.fail;
+import static org.sonar.db.rule.RuleDescriptionSectionDto.createDefaultRuleDescriptionSection;
 import static org.sonar.db.rule.RuleTesting.newRule;
 import static org.sonar.server.util.TypeValidationsTesting.newFullTypeValidations;
 
@@ -95,7 +96,7 @@ public class RuleCreatorTest {
     assertThat(rule.getPluginKey()).isEqualTo("sonarjava");
     assertThat(rule.getTemplateUuid()).isEqualTo(templateRule.getUuid());
     assertThat(rule.getName()).isEqualTo("My custom");
-    assertThat(rule.getDescription()).isEqualTo("Some description");
+    assertThat(rule.getDefaultRuleDescriptionSection().getDescription()).isEqualTo("Some description");
     assertThat(rule.getSeverityString()).isEqualTo("MAJOR");
     assertThat(rule.getStatus()).isEqualTo(RuleStatus.READY);
     assertThat(rule.getLanguage()).isEqualTo("java");
@@ -288,7 +289,7 @@ public class RuleCreatorTest {
       .setRuleKey(key)
       .setStatus(RuleStatus.REMOVED)
       .setName("Old name")
-      .setDescription("Old description")
+      .addOrReplaceRuleDescriptionSectionDto(createDefaultRuleDescriptionSection("Old description"))
       .setDescriptionFormat(Format.MARKDOWN)
       .setSeverity(Severity.INFO);
     dbTester.rules().insert(rule.getDefinition());
@@ -310,7 +311,7 @@ public class RuleCreatorTest {
 
     // These values should be the same than before
     assertThat(result.getName()).isEqualTo("Old name");
-    assertThat(result.getDescription()).isEqualTo("Old description");
+    assertThat(result.getDefaultRuleDescriptionSectionDto().getDescription()).isEqualTo("Old description");
     assertThat(result.getSeverityString()).isEqualTo(Severity.INFO);
 
     List<RuleParamDto> params = dbTester.getDbClient().ruleDao().selectRuleParamsByRuleKey(dbSession, customRuleKey);
@@ -328,7 +329,7 @@ public class RuleCreatorTest {
       .setRuleKey(key)
       .setStatus(RuleStatus.REMOVED)
       .setName("Old name")
-      .setDescription("Old description")
+      .addOrReplaceRuleDescriptionSectionDto(createDefaultRuleDescriptionSection("Old description"))
       .setSeverity(Severity.INFO);
     dbTester.rules().insert(rule.getDefinition());
     dbTester.rules().insertRuleParam(rule.getDefinition(), param -> param.setDefaultValue("a.*"));
index 5670c5d148936b032217a63f465a81a9cd57de05..0c2fbae7dc7dcf51a767cb92fe2e0d2a34edebc5 100644 (file)
@@ -57,6 +57,7 @@ import org.sonar.server.tester.UserSessionRule;
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.assertj.core.api.Assertions.assertThatThrownBy;
 import static org.sonar.api.rule.Severity.CRITICAL;
+import static org.sonar.db.rule.RuleDescriptionSectionDto.createDefaultRuleDescriptionSection;
 import static org.sonar.db.rule.RuleTesting.newRule;
 import static org.sonar.server.rule.RuleUpdate.createForCustomRule;
 import static org.sonar.server.rule.RuleUpdate.createForPluginRule;
@@ -341,7 +342,7 @@ public class RuleUpdaterTest {
     // Create custom rule
     RuleDefinitionDto customRule = RuleTesting.newCustomRule(templateRule)
       .setName("Old name")
-      .setDescription("Old description")
+      .addOrReplaceRuleDescriptionSectionDto(createDefaultRuleDescriptionSection("Old description"))
       .setSeverity(Severity.MINOR)
       .setStatus(RuleStatus.BETA)
       .getDefinition();
@@ -364,7 +365,7 @@ public class RuleUpdaterTest {
     RuleDto customRuleReloaded = db.getDbClient().ruleDao().selectOrFailByKey(dbSession, customRule.getKey());
     assertThat(customRuleReloaded).isNotNull();
     assertThat(customRuleReloaded.getName()).isEqualTo("New name");
-    assertThat(customRuleReloaded.getDescription()).isEqualTo("New description");
+    assertThat(customRuleReloaded.getDefaultRuleDescriptionSection().getDescription()).isEqualTo("New description");
     assertThat(customRuleReloaded.getSeverityString()).isEqualTo("MAJOR");
     assertThat(customRuleReloaded.getStatus()).isEqualTo(RuleStatus.READY);
 
@@ -389,7 +390,7 @@ public class RuleUpdaterTest {
     // Create custom rule
     RuleDefinitionDto customRule = RuleTesting.newCustomRule(templateRule)
       .setName("Old name")
-      .setDescription("Old description")
+      .addRuleDescriptionSectionDto(createDefaultRuleDescriptionSection("Old description"))
       .setSeverity(Severity.MINOR)
       .setStatus(RuleStatus.BETA)
       .getDefinition();
index f81c42ce1d128682c4056e80e0c5d7a938760764..dca7895a18941e7d8257a2f450545a1485cbe290 100644 (file)
@@ -51,6 +51,7 @@ import static org.mockito.Mockito.mock;
 import static org.sonar.api.rules.RuleType.BUG;
 import static org.sonar.api.rules.RuleType.CODE_SMELL;
 import static org.sonar.db.permission.GlobalPermission.ADMINISTER_QUALITY_PROFILES;
+import static org.sonar.db.rule.RuleDescriptionSectionDto.createDefaultRuleDescriptionSection;
 import static org.sonar.db.rule.RuleTesting.newCustomRule;
 import static org.sonar.db.rule.RuleTesting.newTemplateRule;
 import static org.sonar.server.util.TypeValidationsTesting.newFullTypeValidations;
@@ -141,7 +142,7 @@ public class CreateActionTest {
       .setRuleKey("MY_CUSTOM")
       .setStatus(RuleStatus.REMOVED)
       .setName("My custom rule")
-      .setDescription("Description")
+      .addOrReplaceRuleDescriptionSectionDto(createDefaultRuleDescriptionSection("Description"))
       .setDescriptionFormat(RuleDto.Format.MARKDOWN)
       .setSeverity(Severity.MAJOR);
     db.rules().insert(customRule);
index 81e08380c3d6619103103a2b0e89e4b3013e0349..3b6db659ba20f96fa000ab61e2995462460e154c 100644 (file)
@@ -79,6 +79,7 @@ import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
 import static org.sonar.api.rule.Severity.BLOCKER;
+import static org.sonar.db.rule.RuleDescriptionSectionDto.createDefaultRuleDescriptionSection;
 import static org.sonar.db.rule.RuleTesting.setSystemTags;
 import static org.sonar.db.rule.RuleTesting.setTags;
 import static org.sonar.server.rule.ws.RulesWsParameters.PARAM_ACTIVATION;
@@ -232,7 +233,8 @@ public class SearchActionTest {
 
   @Test
   public void filter_by_rule_description() {
-    RuleDefinitionDto rule1 = db.rules().insert(r1 -> r1.setDescription("This is the <bold>best</bold> rule now&amp;for<b>ever</b>"));
+    RuleDefinitionDto rule1 = db.rules()
+      .insert(r1 -> r1.addOrReplaceRuleDescriptionSectionDto(createDefaultRuleDescriptionSection("This is the <bold>best</bold> rule now&amp;for<b>ever</b>")));
     RuleDefinitionDto rule2 = db.rules().insert(r1 -> r1.setName("Some other stuff"));
     indexRules();
 
@@ -243,8 +245,10 @@ public class SearchActionTest {
 
   @Test
   public void filter_by_rule_name_or_descriptions_requires_all_words_to_match_anywhere() {
-    RuleDefinitionDto rule1 = db.rules().insert(r1 -> r1.setName("Best rule ever").setDescription("This is a good rule"));
-    RuleDefinitionDto rule2 = db.rules().insert(r1 -> r1.setName("Some other stuff").setDescription("Another thing"));
+    RuleDefinitionDto rule1 = db.rules().insert(r1 -> r1.setName("Best rule ever")
+      .addOrReplaceRuleDescriptionSectionDto(createDefaultRuleDescriptionSection("This is a good rule")));
+    RuleDefinitionDto rule2 = db.rules().insert(r1 -> r1.setName("Another thing")
+      .addOrReplaceRuleDescriptionSectionDto(createDefaultRuleDescriptionSection("Another thing")));
     indexRules();
 
     verify(r -> r.setParam("q", "Best good"), rule1);
index 6a0400dd5d3cb2afc7749ab5e5ee2cd79a81ea7e..41ba8fab653aaefc513a0913a693573053385c3e 100644 (file)
@@ -50,6 +50,7 @@ import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
+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.newTemplateRule;
@@ -284,7 +285,7 @@ public class ShowActionTest {
     db.rules().insert(templateRule.getDefinition());
     // Custom rule
     RuleDefinitionDto customRule = newCustomRule(templateRule.getDefinition())
-      .setDescription("<div>line1\nline2</div>")
+      .addOrReplaceRuleDescriptionSectionDto(createDefaultRuleDescriptionSection("<div>line1\nline2</div>"))
       .setDescriptionFormat(MARKDOWN);
     db.rules().insert(customRule);
     doReturn("&lt;div&gt;line1<br/>line2&lt;/div&gt;").when(macroInterpreter).interpret("<div>line1\nline2</div>");
@@ -342,7 +343,7 @@ public class ShowActionTest {
       .setIsExternal(true)
       .setIsAdHoc(true)
       .setName("predefined name")
-      .setDescription("<div>predefined desc</div>")
+      .addOrReplaceRuleDescriptionSectionDto(createDefaultRuleDescriptionSection("<div>predefined desc</div>"))
       .setSeverity(Severity.BLOCKER)
       .setType(RuleType.VULNERABILITY));
     RuleMetadataDto metadata = db.rules().insertOrUpdateMetadata(externalRule, m -> m
@@ -370,7 +371,6 @@ public class ShowActionTest {
       .setIsExternal(true)
       .setIsAdHoc(true)
       .setName(null)
-      .setDescription(null)
       .setDescriptionFormat(null)
       .setSeverity((String) null)
       .setType(0));
index 60f00c186e02e681624f5025545f90b59280bb94..8be2f583b11e838c8c016a581b98c4492c9914c9 100644 (file)
@@ -53,6 +53,7 @@ import static org.mockito.Mockito.mock;
 import static org.sonar.api.server.debt.DebtRemediationFunction.Type.LINEAR;
 import static org.sonar.api.server.debt.DebtRemediationFunction.Type.LINEAR_OFFSET;
 import static org.sonar.db.permission.GlobalPermission.ADMINISTER_QUALITY_PROFILES;
+import static org.sonar.db.rule.RuleDescriptionSectionDto.createDefaultRuleDescriptionSection;
 import static org.sonar.db.rule.RuleTesting.setSystemTags;
 import static org.sonar.db.rule.RuleTesting.setTags;
 import static org.sonar.server.rule.ws.UpdateAction.PARAM_KEY;
@@ -107,7 +108,7 @@ public class UpdateActionTest {
     RuleDefinitionDto customRule = db.rules().insert(
       r -> r.setRuleKey(RuleKey.of("java", "MY_CUSTOM")),
       r -> r.setName("Old custom"),
-      r -> r.setDescription("Old description"),
+      r -> r.addOrReplaceRuleDescriptionSectionDto(createDefaultRuleDescriptionSection("Old description")),
       r -> r.setSeverity(Severity.MINOR),
       r -> r.setStatus(RuleStatus.BETA),
       r -> r.setTemplateUuid(templateRule.getUuid()),
@@ -243,7 +244,7 @@ public class UpdateActionTest {
     RuleDefinitionDto customRule = db.rules().insert(
       r -> r.setRuleKey(RuleKey.of("java", "MY_CUSTOM")),
       r -> r.setName("Old custom"),
-      r -> r.setDescription("Old description"),
+      r -> r.addOrReplaceRuleDescriptionSectionDto(createDefaultRuleDescriptionSection("Old description")),
       r -> r.setTemplateUuid(templateRule.getUuid()),
       r -> r.setCreatedAt(PAST),
       r -> r.setUpdatedAt(PAST));