]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-20198 - add clean code fields in rules endpoints
authorBenjamin Campomenosi <109955405+benjamin-campomenosi-sonarsource@users.noreply.github.com>
Tue, 22 Aug 2023 08:47:51 +0000 (10:47 +0200)
committersonartech <sonartech@sonarsource.com>
Tue, 22 Aug 2023 20:03:05 +0000 (20:03 +0000)
16 files changed:
server/sonar-server-common/src/main/java/org/sonar/server/rule/index/RuleIndex.java
server/sonar-webserver-webapi/src/it/java/org/sonar/server/rule/ws/CreateActionIT.java
server/sonar-webserver-webapi/src/it/java/org/sonar/server/rule/ws/SearchActionIT.java
server/sonar-webserver-webapi/src/it/java/org/sonar/server/rule/ws/ShowActionIT.java
server/sonar-webserver-webapi/src/it/java/org/sonar/server/rule/ws/UpdateActionIT.java
server/sonar-webserver-webapi/src/main/java/org/sonar/server/rule/ws/CreateAction.java
server/sonar-webserver-webapi/src/main/java/org/sonar/server/rule/ws/RuleMapper.java
server/sonar-webserver-webapi/src/main/java/org/sonar/server/rule/ws/RulesWsParameters.java
server/sonar-webserver-webapi/src/main/java/org/sonar/server/rule/ws/SearchAction.java
server/sonar-webserver-webapi/src/main/java/org/sonar/server/rule/ws/ShowAction.java
server/sonar-webserver-webapi/src/main/java/org/sonar/server/rule/ws/UpdateAction.java
server/sonar-webserver-webapi/src/main/resources/org/sonar/server/rule/ws/create-example.json
server/sonar-webserver-webapi/src/main/resources/org/sonar/server/rule/ws/search-example.json
server/sonar-webserver-webapi/src/main/resources/org/sonar/server/rule/ws/show-example.json
server/sonar-webserver-webapi/src/main/resources/org/sonar/server/rule/ws/update-example.json
sonar-ws/src/main/protobuf/ws-rules.proto

index 739c46ecdcb2214d0dc6481ab8805a3df199e2ae..a9438fcc768e0cffb3ae4b812a3a1666495ec72f 100644 (file)
@@ -135,6 +135,9 @@ public class RuleIndex {
   public static final String FACET_OWASP_TOP_10 = "owaspTop10";
   public static final String FACET_OWASP_TOP_10_2021 = "owaspTop10-2021";
   public static final String FACET_SONARSOURCE_SECURITY = "sonarsourceSecurity";
+  public static final String FACET_CLEAN_CODE_ATTRIBUTE_CATEGORY = "cleanCodeAttributeCategories";
+  public static final String FACET_IMPACT_SOFTWARE_QUALITY = "impactSoftwareQualities";
+  public static final String FACET_IMPACT_SEVERITY = "impactSeverities";
 
   private static final int MAX_FACET_SIZE = 100;
 
index 23cef90350c25869ac6f3b5c0c2657b814f4990b..273a96fc718552c66afbe25dbfd409aa67fd2a17 100644 (file)
@@ -107,30 +107,45 @@ public class CreateActionIT {
       .setParam("params", "regex=a.*")
       .execute().getInput();
 
-    assertJson(result).isSimilarTo("{\n" +
-      "  \"rule\": {\n" +
-      "    \"key\": \"java:MY_CUSTOM\",\n" +
-      "    \"repo\": \"java\",\n" +
-      "    \"name\": \"My custom rule\",\n" +
-      "    \"htmlDesc\": \"Description\",\n" +
-      "    \"severity\": \"MAJOR\",\n" +
-      "    \"status\": \"BETA\",\n" +
-      "    \"type\": \"BUG\",\n" +
-      "    \"internalKey\": \"configKey_S001\",\n" +
-      "    \"isTemplate\": false,\n" +
-      "    \"templateKey\": \"java:S001\",\n" +
-      "    \"sysTags\": [\"systag1\", \"systag2\"],\n" +
-      "    \"lang\": \"js\",\n" +
-      "    \"params\": [\n" +
-      "      {\n" +
-      "        \"key\": \"regex\",\n" +
-      "        \"htmlDesc\": \"Reg ex\",\n" +
-      "        \"defaultValue\": \"a.*\",\n" +
-      "        \"type\": \"STRING\"\n" +
-      "      }\n" +
-      "    ]\n" +
-      "  }\n" +
-      "}\n");
+    String expetedResult = """
+      {
+        "rule": {
+          "key": "java:MY_CUSTOM",
+          "repo": "java",
+          "name": "My custom rule",
+          "htmlDesc": "Description",
+          "severity": "MAJOR",
+          "status": "BETA",
+          "type": "BUG",
+          "internalKey": "configKey_S001",
+          "isTemplate": false,
+          "templateKey": "java:S001",
+          "sysTags": [
+            "systag1",
+            "systag2"
+          ],
+          "lang": "js",
+          "params": [
+            {
+              "key": "regex",
+              "htmlDesc": "Reg ex",
+              "defaultValue": "a.*",
+              "type": "STRING"
+            }
+          ],
+          "cleanCodeAttribute": "CONVENTIONAL",
+          "cleanCodeAttributeCategory": "CONSISTENT",
+          "impacts": [
+            {
+              "softwareQuality": "RELIABILITY",
+              "severity": "MEDIUM"
+            }
+          ]
+        }
+      }
+      """;
+
+    assertJson(result).isSimilarTo(expetedResult);
   }
 
   @Test
index a99e9d26800240a950a93cfe6a486e2bc3d14fd9..bf590159d22836118c474cec8212ddec54d5dcc7 100644 (file)
@@ -98,6 +98,7 @@ import static org.sonar.db.rule.RuleTesting.newRule;
 import static org.sonar.db.rule.RuleTesting.newRuleWithoutDescriptionSection;
 import static org.sonar.db.rule.RuleTesting.setSystemTags;
 import static org.sonar.db.rule.RuleTesting.setTags;
+import static org.sonar.server.rule.ws.RulesWsParameters.FIELD_CLEAN_CODE_ATTRIBUTE;
 import static org.sonar.server.rule.ws.RulesWsParameters.PARAM_ACTIVATION;
 import static org.sonar.server.rule.ws.RulesWsParameters.PARAM_COMPARE_TO_PROFILE;
 import static org.sonar.server.rule.ws.RulesWsParameters.PARAM_QPROFILE;
@@ -320,8 +321,7 @@ public class SearchActionIT {
       r -> r.setName("Name"),
       r -> r.setRepositoryKey("repo_key"),
       r -> r.setSeverity("MINOR"),
-      r -> r.setLanguage("java")
-      );
+      r -> r.setLanguage("java"));
     indexRules();
 
     Rules.SearchResponse response = ws.newRequest().executeProtobuf(Rules.SearchResponse.class);
@@ -496,6 +496,27 @@ public class SearchActionIT {
       .containsExactly(rule.getTags().toArray(new String[0]));
   }
 
+  @Test
+  public void returnRuleCleanCodeFields_whenEndpointIsCalled() {
+    RuleDto rule = db.rules()
+      .insert();
+    indexRules();
+
+    SearchResponse result = ws.newRequest()
+      .setParam(WebService.Param.FIELDS, FIELD_CLEAN_CODE_ATTRIBUTE)
+      .executeProtobuf(SearchResponse.class);
+
+    // mandatory fields
+    assertThat(result.getRulesList())
+      .extracting(r -> r.getImpacts().getImpactsList().stream().findFirst().orElseThrow(() -> new IllegalStateException("Impact is a mandatory field in the response.")))
+      .extracting(Common.Impact::getSoftwareQuality, Common.Impact::getSeverity)
+        .containsExactly(tuple(Common.SoftwareQuality.MAINTAINABILITY, Common.ImpactSeverity.HIGH));
+
+    // selected fields
+    assertThat(result.getRulesList()).extracting(Rule::getCleanCodeAttribute).containsExactly(Common.CleanCodeAttribute.CLEAR);
+    assertThat(result.getRulesList()).extracting(Rule::getCleanCodeAttributeCategory).containsExactly(Common.CleanCodeAttributeCategory.INTENTIONAL);
+  }
+
   @Test
   public void should_return_specified_fields() {
     when(macroInterpreter.interpret(anyString())).thenAnswer(invocation -> invocation.getArgument(0));
@@ -523,8 +544,7 @@ public class SearchActionIT {
     checkField(rule, "gapDescription", Rule::getGapDescription, rule.getGapDescription());
     checkDescriptionSections(rule, rule.getRuleDescriptionSectionDtos().stream()
       .map(SearchActionIT::toProtobufDto)
-      .collect(Collectors.toSet())
-    );
+      .collect(Collectors.toSet()));
   }
 
   private RuleDescriptionSectionDto createRuleDescriptionSectionWithContext(String key, String content, @Nullable String contextKey) {
@@ -995,10 +1015,10 @@ public class SearchActionIT {
     indexRules();
 
     ws.newRequest()
-            .setParam(WebService.Param.PAGE, "2")
-            .setParam(WebService.Param.PAGE_SIZE, "9")
-            .execute()
-            .assertJson(this.getClass(), "paging.json");
+      .setParam(WebService.Param.PAGE, "2")
+      .setParam(WebService.Param.PAGE_SIZE, "9")
+      .execute()
+      .assertJson(this.getClass(), "paging.json");
   }
 
   @Test
@@ -1050,7 +1070,6 @@ public class SearchActionIT {
     assertThat(actualSections).hasSameElementsAs(expected);
   }
 
-
   private void verifyNoResults(Consumer<TestRequest> requestPopulator) {
     verify(requestPopulator);
   }
index eb34ca1e80645e93171bcc493b4923feb0243652..35d4f29319a1ff26c2658f27716376feb373707e 100644 (file)
@@ -131,6 +131,10 @@ public class ShowActionIT {
     assertThat(resultRule.getParams().getParamsList())
       .extracting(Rule.Param::getKey, Rule.Param::getHtmlDesc, Rule.Param::getDefaultValue)
       .containsExactlyInAnyOrder(tuple(ruleParam.getName(), ruleParam.getDescription(), ruleParam.getDefaultValue()));
+    assertThat(resultRule.getImpacts().getImpactsList())
+      .extracting(Common.Impact::getSoftwareQuality, Common.Impact::getSeverity)
+      .containsExactly(tuple(Common.SoftwareQuality.MAINTAINABILITY, Common.ImpactSeverity.HIGH));
+
     assertThat(resultRule.getEducationPrinciples().getEducationPrinciplesList()).containsExactlyElementsOf(rule.getEducationPrinciples());
   }
 
@@ -146,6 +150,19 @@ public class ShowActionIT {
       .containsExactly(rule.getTags().toArray(new String[0]));
   }
 
+  //<test case name>_when<conditionInCamelCase>_should<assertionInCamelCase>
+  @Test
+  public void returnRuleCleanCodeFields_whenEndpointIsCalled() {
+    RuleDto rule = db.rules().insert(setTags("tag1", "tag2"), r -> r.setNoteData(null).setNoteUserUuid(null));
+
+    ShowResponse result = ws.newRequest()
+      .setParam(PARAM_KEY, rule.getKey().toString())
+      .executeProtobuf(ShowResponse.class);
+
+    assertThat(result.getRule().getCleanCodeAttribute()).isEqualTo(Common.CleanCodeAttribute.CLEAR);
+    assertThat(result.getRule().getCleanCodeAttributeCategory()).isEqualTo(Common.CleanCodeAttributeCategory.INTENTIONAL);
+  }
+
   @Test
   public void show_rule_with_note_login() {
     UserDto user = db.users().insertUser();
@@ -334,7 +351,7 @@ public class ShowActionIT {
 
   @Test
   public void show_adhoc_rule() {
-    //Ad-hoc description has no description sections defined
+    // Ad-hoc description has no description sections defined
     RuleDto externalRule = db.rules().insert(newRuleWithoutDescriptionSection()
       .setIsExternal(true)
       .setIsAdHoc(true)
@@ -391,8 +408,7 @@ public class ShowActionIT {
         tuple(ASSESS_THE_PROBLEM_SECTION_KEY, "<div>This is not a problem</div>", "", ""),
         tuple(HOW_TO_FIX_SECTION_KEY, "<div>I don't want to fix</div>", "", ""),
         tuple(RESOURCES_SECTION_KEY, "<div>I want to fix with Spring</div>", section4context1.getContext().getKey(), section4context1.getContext().getDisplayName()),
-        tuple(RESOURCES_SECTION_KEY, "<div>I want to fix with Servlet</div>", section4context2.getContext().getKey(), section4context2.getContext().getDisplayName())
-      );
+        tuple(RESOURCES_SECTION_KEY, "<div>I want to fix with Servlet</div>", section4context2.getContext().getKey(), section4context2.getContext().getDisplayName()));
   }
 
   @Test
index a9a68ba257484f8ad9e4f19a05773157f2b499ba..21385ba831cd0f3a06a52474478604a47bb4bf8c 100644 (file)
@@ -44,6 +44,7 @@ import org.sonar.server.text.MacroInterpreter;
 import org.sonar.server.ws.TestResponse;
 import org.sonar.server.ws.WsAction;
 import org.sonar.server.ws.WsActionTester;
+import org.sonarqube.ws.Common;
 import org.sonarqube.ws.Rules;
 
 import static org.assertj.core.api.Assertions.assertThat;
@@ -301,6 +302,29 @@ public class UpdateActionIT {
       .isInstanceOf(UnauthorizedException.class);
   }
 
+  @Test
+  public void returnRuleCleanCodeFields_whenEndpointIsCalled() {
+    UserDto userAuthenticated = db.users().insertUser();
+    userSession.logIn(userAuthenticated).addPermission(ADMINISTER_QUALITY_PROFILES);
+
+    RuleDto rule = db.rules()
+      .insert();
+
+    Rules.UpdateResponse updateResponse = ws.newRequest().setMethod("POST")
+      .setParam("key", rule.getKey().toString())
+      .executeProtobuf(Rules.UpdateResponse.class);
+
+    // mandatory fields
+    assertThat(updateResponse.getRule())
+      .extracting(r -> r.getImpacts().getImpactsList().stream().findFirst().orElseThrow(() -> new IllegalStateException("Impact is a mandatory field in the response.")))
+      .extracting(Common.Impact::getSoftwareQuality, Common.Impact::getSeverity)
+      .containsExactly(Common.SoftwareQuality.MAINTAINABILITY, Common.ImpactSeverity.HIGH);
+
+    // selected fields
+    assertThat(updateResponse.getRule()).extracting(Rules.Rule::getCleanCodeAttribute).isEqualTo(Common.CleanCodeAttribute.CLEAR);
+    assertThat(updateResponse.getRule()).extracting(Rules.Rule::getCleanCodeAttributeCategory).isEqualTo(Common.CleanCodeAttributeCategory.INTENTIONAL);
+  }
+
   private void logInAsQProfileAdministrator() {
     userSession
       .logIn()
index b1f8090a6103b520c5fc267e536728ec625a6ab0..2c51ecfcd9714751f000d2c0f35e041fcf705980 100644 (file)
@@ -86,8 +86,10 @@ public class CreateAction implements RulesWsAction {
       .setResponseExample(Resources.getResource(getClass(), "create-example.json"))
       .setSince("4.4")
       .setChangelog(
+        new Change("5.5", "Creating manual rule is not more possible"),
         new Change("10.0","Drop deprecated keys: 'custom_key', 'template_key', 'markdown_description', 'prevent_reactivation'"),
-        new Change("5.5", "Creating manual rule is not more possible"))
+        new Change("10.2", "Add 'impacts', 'cleanCodeAttribute', 'cleanCodeAttributeCategory' fields to the response")
+        )
       .setHandler(this);
 
     action
index a2a73a3ab2a1fd2a4e05ee8c46c9c57eabb18d41..f9b8dbf7903dd83da4e59c84377c855f30ad378b 100644 (file)
@@ -34,6 +34,7 @@ import org.sonar.api.resources.Languages;
 import org.sonar.api.rule.RuleKey;
 import org.sonar.api.server.debt.DebtRemediationFunction;
 import org.sonar.api.server.debt.internal.DefaultDebtRemediationFunction;
+import org.sonar.db.issue.ImpactDto;
 import org.sonar.db.rule.DeprecatedRuleKeyDto;
 import org.sonar.db.rule.RuleDescriptionSectionContextDto;
 import org.sonar.db.rule.RuleDescriptionSectionDto;
@@ -51,6 +52,7 @@ import org.sonarqube.ws.Rules;
 
 import static org.sonar.api.utils.DateUtils.formatDateTime;
 import static org.sonar.db.rule.RuleDto.Format.MARKDOWN;
+import static org.sonar.server.rule.ws.RulesWsParameters.FIELD_CLEAN_CODE_ATTRIBUTE;
 import static org.sonar.server.rule.ws.RulesWsParameters.FIELD_CREATED_AT;
 import static org.sonar.server.rule.ws.RulesWsParameters.FIELD_DEBT_REM_FUNCTION;
 import static org.sonar.server.rule.ws.RulesWsParameters.FIELD_DEFAULT_DEBT_REM_FUNCTION;
@@ -117,6 +119,7 @@ public class RuleMapper {
     // Mandatory fields
     ruleResponse.setKey(ruleDto.getKey().toString());
     ruleResponse.setType(Common.RuleType.forNumber(ruleDto.getType()));
+    setImpacts(ruleResponse, ruleDto);
 
     // Optional fields
     setName(ruleResponse, ruleDto, fieldsToReturn);
@@ -148,9 +151,23 @@ public class RuleMapper {
       setAdHocType(ruleResponse, ruleDto);
     }
     setEducationPrinciples(ruleResponse, ruleDto, fieldsToReturn);
+    setCleanCodeAttributes(ruleResponse, ruleDto, fieldsToReturn);
+
     return ruleResponse;
   }
 
+  private static void setImpacts(Rules.Rule.Builder ruleResponse, RuleDto ruleDto) {
+    Rules.Impacts.Builder impactsBuilder = Rules.Impacts.newBuilder();
+    ruleDto.getDefaultImpacts().forEach(impactDto -> impactsBuilder.addImpacts(toImpact(impactDto)));
+    ruleResponse.setImpacts(impactsBuilder.build());
+  }
+
+  private static Common.Impact toImpact(ImpactDto impactDto) {
+    Common.ImpactSeverity severity = Common.ImpactSeverity.valueOf(impactDto.getSeverity().name());
+    Common.SoftwareQuality softwareQuality = Common.SoftwareQuality.valueOf(impactDto.getSoftwareQuality().name());
+    return Common.Impact.newBuilder().setSeverity(severity).setSoftwareQuality(softwareQuality).build();
+  }
+
   private static void setAdHocName(Rules.Rule.Builder ruleResponse, RuleDto ruleDto, Set<String> fieldsToReturn) {
     String adHocName = ruleDto.getAdHocName();
     if (adHocName != null && shouldReturnField(fieldsToReturn, FIELD_NAME)) {
@@ -204,6 +221,13 @@ public class RuleMapper {
     }
   }
 
+  private static void setCleanCodeAttributes(Rules.Rule.Builder ruleResponse, RuleDto ruleDto, Set<String> fieldsToReturn) {
+    if(shouldReturnField(fieldsToReturn, FIELD_CLEAN_CODE_ATTRIBUTE)){
+      ruleResponse.setCleanCodeAttribute(Common.CleanCodeAttribute.valueOf(ruleDto.getCleanCodeAttribute().name()));
+      ruleResponse.setCleanCodeAttributeCategory(Common.CleanCodeAttributeCategory.valueOf(ruleDto.getCleanCodeAttribute().getAttributeCategory().name()));
+    }
+  }
+
   private static void setDeprecatedKeys(Rules.Rule.Builder ruleResponse, RuleDto ruleDto, Set<String> fieldsToReturn,
     Map<String, List<DeprecatedRuleKeyDto>> deprecatedRuleKeysByRuleUuid) {
     if (shouldReturnField(fieldsToReturn, FIELD_DEPRECATED_KEYS)) {
index 8505722956a73ce59611f02d0cb82334e232adee..0c1ce5eaf99e66fbb68297b97b97f7a997c2721c 100644 (file)
@@ -70,6 +70,7 @@ public class RulesWsParameters {
   public static final String FIELD_NOTE_LOGIN = "noteLogin";
   public static final String FIELD_MARKDOWN_NOTE = "mdNote";
   public static final String FIELD_HTML_NOTE = "htmlNote";
+  public static final String FIELD_CLEAN_CODE_ATTRIBUTE = "cleanCodeAttribute";
 
   /**
    * Value for 'f' parameter which is used to return all the "defaultDebtRemFn" fields.
@@ -112,7 +113,7 @@ public class RulesWsParameters {
     FIELD_MARKDOWN_DESCRIPTION, FIELD_DESCRIPTION_SECTIONS, FIELD_NOTE_LOGIN, FIELD_MARKDOWN_NOTE, FIELD_HTML_NOTE,
     FIELD_DEFAULT_DEBT_REM_FUNCTION, FIELD_DEBT_REM_FUNCTION,
     FIELD_DEFAULT_REM_FUNCTION, FIELD_GAP_DESCRIPTION, FIELD_REM_FUNCTION_OVERLOADED, FIELD_REM_FUNCTION,
-    FIELD_PARAMS, FIELD_ACTIVES, FIELD_SCOPE, FIELD_DEPRECATED_KEYS, FIELD_EDUCATION_PRINCIPLES);
+    FIELD_PARAMS, FIELD_ACTIVES, FIELD_SCOPE, FIELD_DEPRECATED_KEYS, FIELD_EDUCATION_PRINCIPLES, FIELD_CLEAN_CODE_ATTRIBUTE);
 
   private RulesWsParameters() {
     // prevent instantiation
index f4cf74b5b2a624bc622879b12d13031ffd85c826..7ddee24e87bb6d46586326a569355d69020dc77f 100644 (file)
@@ -66,7 +66,10 @@ import static org.sonar.api.server.ws.WebService.Param.PAGE_SIZE;
 import static org.sonar.server.es.SearchOptions.MAX_PAGE_SIZE;
 import static org.sonar.server.rule.index.RuleIndex.ALL_STATUSES_EXCEPT_REMOVED;
 import static org.sonar.server.rule.index.RuleIndex.FACET_ACTIVE_SEVERITIES;
+import static org.sonar.server.rule.index.RuleIndex.FACET_CLEAN_CODE_ATTRIBUTE_CATEGORY;
 import static org.sonar.server.rule.index.RuleIndex.FACET_CWE;
+import static org.sonar.server.rule.index.RuleIndex.FACET_IMPACT_SEVERITY;
+import static org.sonar.server.rule.index.RuleIndex.FACET_IMPACT_SOFTWARE_QUALITY;
 import static org.sonar.server.rule.index.RuleIndex.FACET_LANGUAGES;
 import static org.sonar.server.rule.index.RuleIndex.FACET_OLD_DEFAULT;
 import static org.sonar.server.rule.index.RuleIndex.FACET_OWASP_TOP_10;
@@ -111,7 +114,11 @@ public class SearchAction implements RulesWsAction {
     FACET_OWASP_TOP_10,
     FACET_OWASP_TOP_10_2021,
     FACET_SANS_TOP_25,
-    FACET_SONARSOURCE_SECURITY};
+    FACET_SONARSOURCE_SECURITY,
+    FACET_CLEAN_CODE_ATTRIBUTE_CATEGORY,
+    FACET_IMPACT_SEVERITY,
+    FACET_IMPACT_SOFTWARE_QUALITY
+  };
 
   private final RuleQueryFactory ruleQueryFactory;
   private final DbClient dbClient;
@@ -166,7 +173,11 @@ public class SearchAction implements RulesWsAction {
         new Change("10.0", "The value 'defaultDebtRemFn' for the 'f' parameter has been deprecated, use 'defaultRemFn' instead"),
         new Change("10.0", "The value 'sansTop25' for the parameter 'facets' has been deprecated"),
         new Change("10.0", "Parameter 'sansTop25' is deprecated"),
-        new Change("10.2", format("Parameters '%s', '%s', and '%s' are now deprecated.", PARAM_SEVERITIES, PARAM_TYPES, PARAM_ACTIVE_SEVERITIES)));
+        new Change("10.2", format("Parameters '%s', '%s', and '%s' are now deprecated.", PARAM_SEVERITIES, PARAM_TYPES, PARAM_ACTIVE_SEVERITIES)),
+        new Change("10.2", "Add 'impacts', 'cleanCodeAttribute', 'cleanCodeAttributeCategory' fields to the response"),
+        new Change("10.2", "The field 'cleanCodeAttribute' has been added to the 'f' parameter"),
+        new Change("10.2", format("add '%s', '%s' and '%s' to the 'facets' parameter.",FACET_CLEAN_CODE_ATTRIBUTE_CATEGORY, FACET_IMPACT_SOFTWARE_QUALITY, FACET_IMPACT_SEVERITY))
+        );
 
     action.createParam(FACETS)
       .setDescription("Comma-separated list of the facets to be computed. No facet is computed by default.")
index 32d3a5c62da8d226f0d224cd5fd0632d4cec285c..83b7fac47440ab95ebfdd5521a60bcf05c70b922 100644 (file)
@@ -87,8 +87,8 @@ public class ShowAction implements RulesWsAction {
         new Change("10.0", "The deprecated field 'defaultDebtRemFnOffset' has been removed, use 'defaultRemFnBaseEffort' instead."),
         new Change("10.0", "The deprecated field 'debtOverloaded' has been removed, use 'remFnOverloaded' instead."),
         new Change("10.0", "The field 'defaultDebtRemFnType' has been deprecated, use 'defaultRemFnType' instead"),
-        new Change("10.0", "The field 'debtRemFnType' has been deprecated, use 'remFnType' instead")
-      );
+        new Change("10.0", "The field 'debtRemFnType' has been deprecated, use 'remFnType' instead"),
+        new Change("10.2", "Add 'impacts', 'cleanCodeAttribute', 'cleanCodeAttributeCategory' fields to the response"));
 
     action
       .createParam(PARAM_KEY)
index 7fef619c45012de944e16ab4ce81e7d2925b7e4b..a00da292edf6774acc81c8d7030af096479bdac4 100644 (file)
@@ -30,6 +30,7 @@ import org.sonar.api.rule.RuleStatus;
 import org.sonar.api.rule.Severity;
 import org.sonar.api.server.debt.DebtRemediationFunction;
 import org.sonar.api.server.debt.internal.DefaultDebtRemediationFunction;
+import org.sonar.api.server.ws.Change;
 import org.sonar.api.server.ws.Request;
 import org.sonar.api.server.ws.Response;
 import org.sonar.api.server.ws.WebService;
@@ -90,6 +91,8 @@ public class UpdateAction implements RulesWsAction {
       .setDescription("Update an existing rule.<br>" +
         "Requires the 'Administer Quality Profiles' permission")
       .setSince("4.4")
+      .setChangelog(
+        new Change("10.2", "Add 'impacts', 'cleanCodeAttribute', 'cleanCodeAttributeCategory' fields to the response"))
       .setHandler(this);
 
     action.createParam(PARAM_KEY)
index 7fab5a09713c255904c68a35139e33f6d2e99fea..34d6481c4ab29f7b650a79b7fff6011be6748727 100644 (file)
@@ -1,28 +1,36 @@
 {
-  "rule":{
-    "key":"squid:forbidSonar",
-    "repo":"squid",
-    "name":"forbidSonar",
-    "createdAt":"2018-06-06T16:04:28+0200",
-    "htmlDesc":"Forbid classes with name starting with Sonar",
-    "mdDesc":"Forbid classes with name starting with Sonar",
-    "severity":"MAJOR",
-    "status":"READY",
-    "isTemplate":false,
-    "templateKey":"squid:S3688",
-    "sysTags":[],
-    "lang":"java",
-    "langName":"Java",
-    "params":[
-    {
-      "key":"className",
-      "htmlDesc":"Fully qualified name of the forbidden class. Use a regex to forbid a package.",
-      "defaultValue":"**/Sonar*",
-      "type":"STRING"
-    }
+  "rule": {
+    "key": "squid:forbidSonar",
+    "repo": "squid",
+    "name": "forbidSonar",
+    "createdAt": "2018-06-06T16:04:28+0200",
+    "htmlDesc": "Forbid classes with name starting with Sonar",
+    "mdDesc": "Forbid classes with name starting with Sonar",
+    "severity": "MAJOR",
+    "status": "READY",
+    "isTemplate": false,
+    "templateKey": "squid:S3688",
+    "sysTags": [],
+    "lang": "java",
+    "langName": "Java",
+    "params": [
+      {
+        "key": "className",
+        "htmlDesc": "Fully qualified name of the forbidden class. Use a regex to forbid a package.",
+        "defaultValue": "**/Sonar*",
+        "type": "STRING"
+      }
     ],
-    "scope":"MAIN",
-    "isExternal":false,
-    "type":"CODE_SMELL"
+    "scope": "MAIN",
+    "isExternal": false,
+    "type": "CODE_SMELL",
+    "cleanCodeAttributeCategory": "INTENTIONAL",
+    "cleanCodeAttribute": "CLEAR",
+    "impacts": [
+      {
+        "softwareQuality": "MAINTAINABILITY",
+        "severity": "HIGH"
+      }
+    ]
   }
 }
\ No newline at end of file
index e2a94d528529a01aec6144442570805686843a29..9f4a94727e18f1d5857e66fbcaf6b87dcff2dd5c 100644 (file)
       "internalKey": "S1067",
       "isTemplate": false,
       "tags": [],
-      "sysTags": ["brain-overload"],
+      "sysTags": [
+        "brain-overload"
+      ],
       "lang": "java",
       "langName": "Java",
       "scope": "MAIN",
       "isExternal": false,
       "type": "CODE_SMELL",
+      "cleanCodeAttributeCategory": "INTENTIONAL",
+      "cleanCodeAttribute": "CLEAR",
+      "impacts": [
+        {
+          "softwareQuality": "MAINTAINABILITY",
+          "severity": "HIGH"
+        }
+      ],
       "descriptionSections": [
         {
           "key": "root_cause",
       "internalKey": "ClassCyclomaticComplexity",
       "isTemplate": false,
       "tags": [],
-      "sysTags": ["brain-overload"],
+      "sysTags": [
+        "brain-overload"
+      ],
       "lang": "java",
       "langName": "Java",
       "scope": "MAIN",
       "isExternal": false,
       "type": "BUG",
+      "cleanCodeAttributeCategory": "INTENTIONAL",
+      "cleanCodeAttribute": "CLEAR",
+      "impacts": [
+        {
+          "softwareQuality": "RELIABILITY",
+          "severity": "HIGH"
+        }
+      ],
       "params": [
         {
           "key": "max",
       "internalKey": "MethodCyclomaticComplexity",
       "isTemplate": false,
       "tags": [],
-      "sysTags": ["brain-overload"],
+      "sysTags": [
+        "brain-overload"
+      ],
       "lang": "java",
       "langName": "Java",
       "scope": "MAIN",
       "isExternal": false,
       "type": "VULNERABILITY",
+      "cleanCodeAttributeCategory": "INTENTIONAL",
+      "cleanCodeAttribute": "CLEAR",
+      "impacts": [
+        {
+          "softwareQuality": "SECURITY",
+          "severity": "HIGH"
+        }
+      ],
       "params": [
         {
           "key": "max",
       "status": "READY",
       "internalKey": "XPath",
       "isTemplate": true,
-      "tags": [ ],
-      "sysTags": [ ],
+      "tags": [],
+      "sysTags": [],
       "mdNote": "<p>\nThe tree produced by the <code>firstOf()</code> matcher is hard to work with from checks when alternatives are not named.\n</p>\n\n<p>\nConsider the following rule:\n</p>\n\n<pre>\nb.rule(COMPILATION_UNIT).is(\n b.firstOf( /* Non-Compliant */\n \"FOO\",\n \"BAR\"));\n</pre>\n\n<p>\nIf, from a check, one wants to forbid the usage of the \"BAR\" alternative,\nthe easiest option will be to verify that the value of the first token is \"BAR\",\ni.e. <code>\"BAR\".equals(compilationUnitNode.getTokenValue())</code>.\n</p>\n\n<p>\nThis is not maintainable, for at least two reasons:\n</p>\n\n<ul>\n <li>The grammar might evolve to also accept \"bar\" in lowercase, which will break <code>\"BAR\".equals(...)</code></li>\n <li>The grammar might evolve to optionally accept \"hello\" before the <code>firstOf()</code>, which will break <code>compilationUnitNode.getTokenValue()</code></li>\n</ul>\n\n<p>\nInstead, it is much better to rewrite the grammar as:\n</p>\n\n<pre>\nb.rule(COMPILATION_UNIT).is(\n firstOf( /* Compliant */\n FOO,\n BAR));\nb.rule(FOO).is(\"FOO\");\nb.rule(BAR).is(\"BAR\");\n</pre>\n\n<p>\nThe same check which forbids \"BAR\" would be written as: <code>compilationUnitNode.hasDirectChildren(BAR)</code>.\nThis allows both of the previous grammar evolutions to be made without impacting the check at all.\n</p>",
       "htmlNote": "&lt;p&gt;<br/>The tree produced by the &lt;code&gt;firstOf()&lt;/code&gt; matcher is hard to work with from checks when alternatives are not named.<br/>&lt;/p&gt;<br/><br/>&lt;p&gt;<br/>Consider the following rule:<br/>&lt;/p&gt;<br/><br/>&lt;pre&gt;<br/>b.rule(COMPILATION_UNIT).is(<br/> b.firstOf( /* Non-Compliant */<br/> &quot;FOO&quot;,<br/> &quot;BAR&quot;));<br/>&lt;/pre&gt;<br/><br/>&lt;p&gt;<br/>If, from a check, one wants to forbid the usage of the &quot;BAR&quot; alternative,<br/>the easiest option will be to verify that the value of the first token is &quot;BAR&quot;,<br/>i.e. &lt;code&gt;&quot;BAR&quot;.equals(compilationUnitNode.getTokenValue())&lt;/code&gt;.<br/>&lt;/p&gt;<br/><br/>&lt;p&gt;<br/>This is not maintainable, for at least two reasons:<br/>&lt;/p&gt;<br/><br/>&lt;ul&gt;<br/> &lt;li&gt;The grammar might evolve to also accept &quot;bar&quot; in lowercase, which will break &lt;code&gt;&quot;BAR&quot;.equals(...)&lt;/code&gt;&lt;/li&gt;<br/> &lt;li&gt;The grammar might evolve to optionally accept &quot;hello&quot; before the &lt;code&gt;firstOf()&lt;/code&gt;, which will break &lt;code&gt;compilationUnitNode.getTokenValue()&lt;/code&gt;&lt;/li&gt;<br/>&lt;/ul&gt;<br/><br/>&lt;p&gt;<br/>Instead, it is much better to rewrite the grammar as:<br/>&lt;/p&gt;<br/><br/>&lt;pre&gt;<br/>b.rule(COMPILATION_UNIT).is(<br/> firstOf( /* Compliant */<br/> FOO,<br/> BAR));<br/>b.rule(FOO).is(&quot;FOO&quot;);<br/>b.rule(BAR).is(&quot;BAR&quot;);<br/>&lt;/pre&gt;<br/><br/>&lt;p&gt;<br/>The same check which forbids &quot;BAR&quot; would be written as: &lt;code&gt;compilationUnitNode.hasDirectChildren(BAR)&lt;/code&gt;.<br/>This allows both of the previous grammar evolutions to be made without impacting the check at all.<br/>&lt;/p&gt;",
       "noteLogin": "eric.hartmann",
       "internalKey": "XPath",
       "isTemplate": false,
       "templateKey": "squid:XPath",
-      "tags": [ ],
-      "sysTags": [ ],
+      "tags": [],
+      "sysTags": [],
       "lang": "java",
       "langName": "Java",
       "scope": "MAIN",
       "isExternal": false,
       "type": "CODE_SMELL",
+      "cleanCodeAttributeCategory": "INTENTIONAL",
+      "cleanCodeAttribute": "CLEAR",
+      "impacts": [
+        {
+          "softwareQuality": "MAINTAINABILITY",
+          "severity": "HIGH"
+        }
+      ],
       "params": [
         {
           "key": "xpathQuery",
         }
       ]
     }
-
   ]
 }
index ea42b51a8427b5d7ab52bfd8d4a069f9d5cb34a5..98248c45029e99695a109ef8389d37eef3068c21 100644 (file)
@@ -1,4 +1,5 @@
-{"rule": {
+{
+  "rule": {
     "key": "squid:ClassCyclomaticComplexity",
     "repo": "squid",
     "name": "Avoid too complex class",
@@ -8,7 +9,9 @@
     "internalKey": "ClassCyclomaticComplexity",
     "template": false,
     "tags": [],
-    "sysTags": ["brain-overload"],
+    "sysTags": [
+      "brain-overload"
+    ],
     "remFnType": "LINEAR_OFFSET",
     "remFnGapMultiplier": "5d",
     "remFnBaseEffort": "10h",
     "scope": "MAIN",
     "isExternal": false,
     "type": "CODE_SMELL",
+    "cleanCodeAttributeCategory": "INTENTIONAL",
+    "cleanCodeAttribute": "CLEAR",
+    "impacts": [
+      {
+        "softwareQuality": "MAINTAINABILITY",
+        "severity": "HIGH"
+      }
+    ],
     "descriptionSections": [
-        {
-            "key": "root_cause",
-            "content": "<h3 class=\"page-title coding-rules-detail-header\"><big>Unnecessary imports should be removed</big></h3>"
-        },
-        {
-            "key": "how_to_fix",
-            "content": "<h2>Recommended Secure Coding Practices</h2><ul><li> activate Spring Security's CSRF protection. </li></ul>",
-            "context": {
-                "displayName": "Spring",
-                "key": "spring"
-            }
-        },
-        {
-            "key": "how_to_fix",
-            "content": "<h2>Recommended Secure Coding Practices</h2><ul><li> activate hibernate protection. </li></ul>",
-            "context": {
-                "displayName": "Hibernate",
-                "key": "hibernate"
-            }
+      {
+        "key": "root_cause",
+        "content": "<h3 class=\"page-title coding-rules-detail-header\"><big>Unnecessary imports should be removed</big></h3>"
+      },
+      {
+        "key": "how_to_fix",
+        "content": "<h2>Recommended Secure Coding Practices</h2><ul><li> activate Spring Security's CSRF protection. </li></ul>",
+        "context": {
+          "displayName": "Spring",
+          "key": "spring"
+        }
+      },
+      {
+        "key": "how_to_fix",
+        "content": "<h2>Recommended Secure Coding Practices</h2><ul><li> activate hibernate protection. </li></ul>",
+        "context": {
+          "displayName": "Hibernate",
+          "key": "hibernate"
         }
+      }
     ],
     "params": [
-        {
-            "key": "max",
-            "desc": "Maximum complexity allowed.",
-            "defaultValue": "200"
-        }
+      {
+        "key": "max",
+        "desc": "Maximum complexity allowed.",
+        "defaultValue": "200"
+      }
     ]
-}, "actives": [
+  },
+  "actives": [
     {
-        "qProfile": "Sonar way with Findbugs:java",
-        "inherit": "NONE",
-        "severity": "MAJOR",
-        "params": [
-            {
-                "key": "max",
-                "value": "200"
-            }
-        ]
+      "qProfile": "Sonar way with Findbugs:java",
+      "inherit": "NONE",
+      "severity": "MAJOR",
+      "params": [
+        {
+          "key": "max",
+          "value": "200"
+        }
+      ]
     },
     {
-        "qProfile": "Sonar way:java",
-        "inherit": "NONE",
-        "severity": "MAJOR",
-        "params": [
-            {
-                "key": "max",
-                "value": "200"
-            }
-        ]
+      "qProfile": "Sonar way:java",
+      "inherit": "NONE",
+      "severity": "MAJOR",
+      "params": [
+        {
+          "key": "max",
+          "value": "200"
+        }
+      ]
     }
-]}
+  ]
+}
index 667cac2768ec5b715687cb742c90e046fe9e9a1c..fba18dd3e7d1137b19c28802dd0fb8d5eed5b029 100644 (file)
@@ -1,29 +1,38 @@
 {
-  "rule":{
-    "key":"squid:forbidSonar",
-    "repo":"squid",
-    "name":"forbidSonar",
-    "createdAt":"2018-06-06T16:09:09+0200",
-    "htmlDesc":"Forbid classes with name starting with Sonar",
-    "mdDesc":"Forbid classes with name starting with Sonar",
-    "severity":"MAJOR",
-    "status":"READY",
-    "isTemplate":false,
-    "templateKey":"squid:S3688",
-    "tags":[],
-    "sysTags":[],
-    "lang":"java",
-    "langName":"Java",
-    "params":[
-    {
-      "key":"className",
-      "htmlDesc":"Fully qualified name of the forbidden class. Use a regex to forbid a package.",
-      "defaultValue":"**/Sonar*","type":"STRING"
-    }
+  "rule": {
+    "key": "squid:forbidSonar",
+    "repo": "squid",
+    "name": "forbidSonar",
+    "createdAt": "2018-06-06T16:09:09+0200",
+    "htmlDesc": "Forbid classes with name starting with Sonar",
+    "mdDesc": "Forbid classes with name starting with Sonar",
+    "severity": "MAJOR",
+    "status": "READY",
+    "isTemplate": false,
+    "templateKey": "squid:S3688",
+    "tags": [],
+    "sysTags": [],
+    "lang": "java",
+    "langName": "Java",
+    "params": [
+      {
+        "key": "className",
+        "htmlDesc": "Fully qualified name of the forbidden class. Use a regex to forbid a package.",
+        "defaultValue": "**/Sonar*",
+        "type": "STRING"
+      }
     ],
-    "remFnOverloaded":false,
-    "scope":"MAIN",
-    "isExternal":false,
-    "type":"CODE_SMELL"
+    "remFnOverloaded": false,
+    "scope": "MAIN",
+    "isExternal": false,
+    "type": "CODE_SMELL",
+    "cleanCodeAttributeCategory": "INTENTIONAL",
+    "cleanCodeAttribute": "CLEAR",
+    "impacts": [
+      {
+        "softwareQuality": "MAINTAINABILITY",
+        "severity": "HIGH"
+      }
+    ]
   }
 }
\ No newline at end of file
index e1e242b8b81b818c1fa48d61c63447010486ba34..06d090623d3620517b49daf1a043239a5ea3ef0b 100644 (file)
@@ -41,9 +41,9 @@ message ListResponse {
 
 // WS api/rules/search
 message SearchResponse {
-  optional int64 total = 1 [deprecated=true];
-  optional int32 p = 2 [deprecated=true];
-  optional int64 ps = 3 [deprecated=true];
+  optional int64 total = 1 [deprecated = true];
+  optional int32 p = 2 [deprecated = true];
+  optional int64 ps = 3 [deprecated = true];
 
   repeated Rule rules = 4;
   optional Actives actives = 5;
@@ -73,12 +73,13 @@ message Rule {
   optional string repo = 2;
   optional string name = 3;
   optional string createdAt = 4;
-  optional string htmlDesc = 5 [deprecated=true];
+  optional string htmlDesc = 5 [deprecated = true];
   optional string htmlNote = 6;
   optional string mdDesc = 7;
   optional string mdNote = 8;
   optional string noteLogin = 9;
-  optional string severity = 10;
+  // Deprecated since 10.2, replace by impacts
+  optional string severity = 10 [deprecated = true];
   optional sonarqube.ws.commons.RuleStatus status = 11;
   optional string internalKey = 12;
   optional bool isTemplate = 13;
@@ -97,16 +98,17 @@ message Rule {
   optional string unusedDebtSubCharName = 28;
 
   // Deprecated since 10.0, replaced by defaultRemFnType
-  optional string defaultDebtRemFnType = 29 [deprecated=true];
+  optional string defaultDebtRemFnType = 29 [deprecated = true];
   reserved 30;
   reserved 31;
   reserved 32;
   reserved 33;
   // Deprecated since 10.0, replaced by remFnType
-  optional string debtRemFnType = 34 [deprecated=true];
+  optional string debtRemFnType = 34 [deprecated = true];
   reserved 35;
   reserved 36;
-  optional sonarqube.ws.commons.RuleType type = 37;
+  // Deprecated since 10.2, replace by impacts
+  optional sonarqube.ws.commons.RuleType type = 37 [deprecated = true];
   optional string defaultRemFnType = 38;
   optional string defaultRemFnGapMultiplier = 39;
   optional string defaultRemFnBaseEffort = 40;
@@ -121,6 +123,9 @@ message Rule {
   optional DescriptionSections descriptionSections = 49;
   optional EducationPrinciples educationPrinciples = 50;
   optional string updatedAt = 51;
+  optional sonarqube.ws.commons.CleanCodeAttribute cleanCodeAttribute = 52;
+  optional sonarqube.ws.commons.CleanCodeAttributeCategory cleanCodeAttributeCategory = 53;
+  optional Impacts impacts = 54;
 
   message DescriptionSections {
     repeated DescriptionSection descriptionSections = 1;
@@ -149,6 +154,10 @@ message Rule {
   }
 }
 
+message Impacts{
+  repeated sonarqube.ws.commons.Impact impacts = 1;
+}
+
 message DeprecatedKeys {
   repeated string deprecatedKey = 1;
 }