From 04fc6db186342232be36c9f1b682ed218e210f9f Mon Sep 17 00:00:00 2001 From: Benjamin Campomenosi <109955405+benjamin-campomenosi-sonarsource@users.noreply.github.com> Date: Tue, 22 Aug 2023 10:47:51 +0200 Subject: [PATCH] SONAR-20198 - add clean code fields in rules endpoints --- .../sonar/server/rule/index/RuleIndex.java | 3 + .../sonar/server/rule/ws/CreateActionIT.java | 63 +++++++---- .../sonar/server/rule/ws/SearchActionIT.java | 37 ++++-- .../sonar/server/rule/ws/ShowActionIT.java | 22 +++- .../sonar/server/rule/ws/UpdateActionIT.java | 24 ++++ .../sonar/server/rule/ws/CreateAction.java | 4 +- .../org/sonar/server/rule/ws/RuleMapper.java | 24 ++++ .../server/rule/ws/RulesWsParameters.java | 3 +- .../sonar/server/rule/ws/SearchAction.java | 15 ++- .../org/sonar/server/rule/ws/ShowAction.java | 4 +- .../sonar/server/rule/ws/UpdateAction.java | 3 + .../sonar/server/rule/ws/create-example.json | 56 ++++++---- .../sonar/server/rule/ws/search-example.json | 53 +++++++-- .../sonar/server/rule/ws/show-example.json | 105 ++++++++++-------- .../sonar/server/rule/ws/update-example.json | 59 +++++----- sonar-ws/src/main/protobuf/ws-rules.proto | 25 +++-- 16 files changed, 347 insertions(+), 153 deletions(-) diff --git a/server/sonar-server-common/src/main/java/org/sonar/server/rule/index/RuleIndex.java b/server/sonar-server-common/src/main/java/org/sonar/server/rule/index/RuleIndex.java index 739c46ecdcb..a9438fcc768 100644 --- a/server/sonar-server-common/src/main/java/org/sonar/server/rule/index/RuleIndex.java +++ b/server/sonar-server-common/src/main/java/org/sonar/server/rule/index/RuleIndex.java @@ -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; diff --git a/server/sonar-webserver-webapi/src/it/java/org/sonar/server/rule/ws/CreateActionIT.java b/server/sonar-webserver-webapi/src/it/java/org/sonar/server/rule/ws/CreateActionIT.java index 23cef90350c..273a96fc718 100644 --- a/server/sonar-webserver-webapi/src/it/java/org/sonar/server/rule/ws/CreateActionIT.java +++ b/server/sonar-webserver-webapi/src/it/java/org/sonar/server/rule/ws/CreateActionIT.java @@ -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 diff --git a/server/sonar-webserver-webapi/src/it/java/org/sonar/server/rule/ws/SearchActionIT.java b/server/sonar-webserver-webapi/src/it/java/org/sonar/server/rule/ws/SearchActionIT.java index a99e9d26800..bf590159d22 100644 --- a/server/sonar-webserver-webapi/src/it/java/org/sonar/server/rule/ws/SearchActionIT.java +++ b/server/sonar-webserver-webapi/src/it/java/org/sonar/server/rule/ws/SearchActionIT.java @@ -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 requestPopulator) { verify(requestPopulator); } diff --git a/server/sonar-webserver-webapi/src/it/java/org/sonar/server/rule/ws/ShowActionIT.java b/server/sonar-webserver-webapi/src/it/java/org/sonar/server/rule/ws/ShowActionIT.java index eb34ca1e806..35d4f29319a 100644 --- a/server/sonar-webserver-webapi/src/it/java/org/sonar/server/rule/ws/ShowActionIT.java +++ b/server/sonar-webserver-webapi/src/it/java/org/sonar/server/rule/ws/ShowActionIT.java @@ -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])); } + //_when_should + @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, "
This is not a problem
", "", ""), tuple(HOW_TO_FIX_SECTION_KEY, "
I don't want to fix
", "", ""), tuple(RESOURCES_SECTION_KEY, "
I want to fix with Spring
", section4context1.getContext().getKey(), section4context1.getContext().getDisplayName()), - tuple(RESOURCES_SECTION_KEY, "
I want to fix with Servlet
", section4context2.getContext().getKey(), section4context2.getContext().getDisplayName()) - ); + tuple(RESOURCES_SECTION_KEY, "
I want to fix with Servlet
", section4context2.getContext().getKey(), section4context2.getContext().getDisplayName())); } @Test diff --git a/server/sonar-webserver-webapi/src/it/java/org/sonar/server/rule/ws/UpdateActionIT.java b/server/sonar-webserver-webapi/src/it/java/org/sonar/server/rule/ws/UpdateActionIT.java index a9a68ba2574..21385ba831c 100644 --- a/server/sonar-webserver-webapi/src/it/java/org/sonar/server/rule/ws/UpdateActionIT.java +++ b/server/sonar-webserver-webapi/src/it/java/org/sonar/server/rule/ws/UpdateActionIT.java @@ -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() diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/rule/ws/CreateAction.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/rule/ws/CreateAction.java index b1f8090a610..2c51ecfcd97 100644 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/rule/ws/CreateAction.java +++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/rule/ws/CreateAction.java @@ -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 diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/rule/ws/RuleMapper.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/rule/ws/RuleMapper.java index a2a73a3ab2a..f9b8dbf7903 100644 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/rule/ws/RuleMapper.java +++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/rule/ws/RuleMapper.java @@ -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 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 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 fieldsToReturn, Map> deprecatedRuleKeysByRuleUuid) { if (shouldReturnField(fieldsToReturn, FIELD_DEPRECATED_KEYS)) { diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/rule/ws/RulesWsParameters.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/rule/ws/RulesWsParameters.java index 8505722956a..0c1ce5eaf99 100644 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/rule/ws/RulesWsParameters.java +++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/rule/ws/RulesWsParameters.java @@ -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 diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/rule/ws/SearchAction.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/rule/ws/SearchAction.java index f4cf74b5b2a..7ddee24e87b 100644 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/rule/ws/SearchAction.java +++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/rule/ws/SearchAction.java @@ -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.") diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/rule/ws/ShowAction.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/rule/ws/ShowAction.java index 32d3a5c62da..83b7fac4744 100644 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/rule/ws/ShowAction.java +++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/rule/ws/ShowAction.java @@ -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) diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/rule/ws/UpdateAction.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/rule/ws/UpdateAction.java index 7fef619c450..a00da292edf 100644 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/rule/ws/UpdateAction.java +++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/rule/ws/UpdateAction.java @@ -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.
" + "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) diff --git a/server/sonar-webserver-webapi/src/main/resources/org/sonar/server/rule/ws/create-example.json b/server/sonar-webserver-webapi/src/main/resources/org/sonar/server/rule/ws/create-example.json index 7fab5a09713..34d6481c4ab 100644 --- a/server/sonar-webserver-webapi/src/main/resources/org/sonar/server/rule/ws/create-example.json +++ b/server/sonar-webserver-webapi/src/main/resources/org/sonar/server/rule/ws/create-example.json @@ -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 diff --git a/server/sonar-webserver-webapi/src/main/resources/org/sonar/server/rule/ws/search-example.json b/server/sonar-webserver-webapi/src/main/resources/org/sonar/server/rule/ws/search-example.json index e2a94d52852..9f4a94727e1 100644 --- a/server/sonar-webserver-webapi/src/main/resources/org/sonar/server/rule/ws/search-example.json +++ b/server/sonar-webserver-webapi/src/main/resources/org/sonar/server/rule/ws/search-example.json @@ -17,12 +17,22 @@ "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", @@ -65,12 +75,22 @@ "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", @@ -91,12 +111,22 @@ "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", @@ -116,8 +146,8 @@ "status": "READY", "internalKey": "XPath", "isTemplate": true, - "tags": [ ], - "sysTags": [ ], + "tags": [], + "sysTags": [], "mdNote": "

\nThe tree produced by the firstOf() matcher is hard to work with from checks when alternatives are not named.\n

\n\n

\nConsider the following rule:\n

\n\n
\nb.rule(COMPILATION_UNIT).is(\n b.firstOf( /* Non-Compliant */\n \"FOO\",\n \"BAR\"));\n
\n\n

\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. \"BAR\".equals(compilationUnitNode.getTokenValue()).\n

\n\n

\nThis is not maintainable, for at least two reasons:\n

\n\n
    \n
  • The grammar might evolve to also accept \"bar\" in lowercase, which will break \"BAR\".equals(...)
  • \n
  • The grammar might evolve to optionally accept \"hello\" before the firstOf(), which will break compilationUnitNode.getTokenValue()
  • \n
\n\n

\nInstead, it is much better to rewrite the grammar as:\n

\n\n
\nb.rule(COMPILATION_UNIT).is(\n firstOf( /* Compliant */\n FOO,\n BAR));\nb.rule(FOO).is(\"FOO\");\nb.rule(BAR).is(\"BAR\");\n
\n\n

\nThe same check which forbids \"BAR\" would be written as: compilationUnitNode.hasDirectChildren(BAR).\nThis allows both of the previous grammar evolutions to be made without impacting the check at all.\n

", "htmlNote": "<p>
The tree produced by the <code>firstOf()</code> matcher is hard to work with from checks when alternatives are not named.
</p>

<p>
Consider the following rule:
</p>

<pre>
b.rule(COMPILATION_UNIT).is(
b.firstOf( /* Non-Compliant */
"FOO",
"BAR"));
</pre>

<p>
If, from a check, one wants to forbid the usage of the "BAR" alternative,
the easiest option will be to verify that the value of the first token is "BAR",
i.e. <code>"BAR".equals(compilationUnitNode.getTokenValue())</code>.
</p>

<p>
This is not maintainable, for at least two reasons:
</p>

<ul>
<li>The grammar might evolve to also accept "bar" in lowercase, which will break <code>"BAR".equals(...)</code></li>
<li>The grammar might evolve to optionally accept "hello" before the <code>firstOf()</code>, which will break <code>compilationUnitNode.getTokenValue()</code></li>
</ul>

<p>
Instead, it is much better to rewrite the grammar as:
</p>

<pre>
b.rule(COMPILATION_UNIT).is(
firstOf( /* Compliant */
FOO,
BAR));
b.rule(FOO).is("FOO");
b.rule(BAR).is("BAR");
</pre>

<p>
The same check which forbids "BAR" would be written as: <code>compilationUnitNode.hasDirectChildren(BAR)</code>.
This allows both of the previous grammar evolutions to be made without impacting the check at all.
</p>", "noteLogin": "eric.hartmann", @@ -151,13 +181,21 @@ "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", @@ -290,6 +328,5 @@ } ] } - ] } diff --git a/server/sonar-webserver-webapi/src/main/resources/org/sonar/server/rule/ws/show-example.json b/server/sonar-webserver-webapi/src/main/resources/org/sonar/server/rule/ws/show-example.json index ea42b51a842..98248c45029 100644 --- a/server/sonar-webserver-webapi/src/main/resources/org/sonar/server/rule/ws/show-example.json +++ b/server/sonar-webserver-webapi/src/main/resources/org/sonar/server/rule/ws/show-example.json @@ -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", @@ -22,56 +25,66 @@ "scope": "MAIN", "isExternal": false, "type": "CODE_SMELL", + "cleanCodeAttributeCategory": "INTENTIONAL", + "cleanCodeAttribute": "CLEAR", + "impacts": [ + { + "softwareQuality": "MAINTAINABILITY", + "severity": "HIGH" + } + ], "descriptionSections": [ - { - "key": "root_cause", - "content": "

Unnecessary imports should be removed

" - }, - { - "key": "how_to_fix", - "content": "

Recommended Secure Coding Practices

  • activate Spring Security's CSRF protection.
", - "context": { - "displayName": "Spring", - "key": "spring" - } - }, - { - "key": "how_to_fix", - "content": "

Recommended Secure Coding Practices

  • activate hibernate protection.
", - "context": { - "displayName": "Hibernate", - "key": "hibernate" - } + { + "key": "root_cause", + "content": "

Unnecessary imports should be removed

" + }, + { + "key": "how_to_fix", + "content": "

Recommended Secure Coding Practices

  • activate Spring Security's CSRF protection.
", + "context": { + "displayName": "Spring", + "key": "spring" + } + }, + { + "key": "how_to_fix", + "content": "

Recommended Secure Coding Practices

  • activate hibernate protection.
", + "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" + } + ] } -]} + ] +} diff --git a/server/sonar-webserver-webapi/src/main/resources/org/sonar/server/rule/ws/update-example.json b/server/sonar-webserver-webapi/src/main/resources/org/sonar/server/rule/ws/update-example.json index 667cac2768e..fba18dd3e7d 100644 --- a/server/sonar-webserver-webapi/src/main/resources/org/sonar/server/rule/ws/update-example.json +++ b/server/sonar-webserver-webapi/src/main/resources/org/sonar/server/rule/ws/update-example.json @@ -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 diff --git a/sonar-ws/src/main/protobuf/ws-rules.proto b/sonar-ws/src/main/protobuf/ws-rules.proto index e1e242b8b81..06d090623d3 100644 --- a/sonar-ws/src/main/protobuf/ws-rules.proto +++ b/sonar-ws/src/main/protobuf/ws-rules.proto @@ -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; } -- 2.39.5