Browse Source

SONAR-20198 - add clean code fields in rules endpoints

tags/10.2.0.77647
Benjamin Campomenosi 8 months ago
parent
commit
04fc6db186
16 changed files with 347 additions and 153 deletions
  1. 3
    0
      server/sonar-server-common/src/main/java/org/sonar/server/rule/index/RuleIndex.java
  2. 39
    24
      server/sonar-webserver-webapi/src/it/java/org/sonar/server/rule/ws/CreateActionIT.java
  3. 28
    9
      server/sonar-webserver-webapi/src/it/java/org/sonar/server/rule/ws/SearchActionIT.java
  4. 19
    3
      server/sonar-webserver-webapi/src/it/java/org/sonar/server/rule/ws/ShowActionIT.java
  5. 24
    0
      server/sonar-webserver-webapi/src/it/java/org/sonar/server/rule/ws/UpdateActionIT.java
  6. 3
    1
      server/sonar-webserver-webapi/src/main/java/org/sonar/server/rule/ws/CreateAction.java
  7. 24
    0
      server/sonar-webserver-webapi/src/main/java/org/sonar/server/rule/ws/RuleMapper.java
  8. 2
    1
      server/sonar-webserver-webapi/src/main/java/org/sonar/server/rule/ws/RulesWsParameters.java
  9. 13
    2
      server/sonar-webserver-webapi/src/main/java/org/sonar/server/rule/ws/SearchAction.java
  10. 2
    2
      server/sonar-webserver-webapi/src/main/java/org/sonar/server/rule/ws/ShowAction.java
  11. 3
    0
      server/sonar-webserver-webapi/src/main/java/org/sonar/server/rule/ws/UpdateAction.java
  12. 32
    24
      server/sonar-webserver-webapi/src/main/resources/org/sonar/server/rule/ws/create-example.json
  13. 45
    8
      server/sonar-webserver-webapi/src/main/resources/org/sonar/server/rule/ws/search-example.json
  14. 59
    46
      server/sonar-webserver-webapi/src/main/resources/org/sonar/server/rule/ws/show-example.json
  15. 34
    25
      server/sonar-webserver-webapi/src/main/resources/org/sonar/server/rule/ws/update-example.json
  16. 17
    8
      sonar-ws/src/main/protobuf/ws-rules.proto

+ 3
- 0
server/sonar-server-common/src/main/java/org/sonar/server/rule/index/RuleIndex.java View 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;


+ 39
- 24
server/sonar-webserver-webapi/src/it/java/org/sonar/server/rule/ws/CreateActionIT.java View 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

+ 28
- 9
server/sonar-webserver-webapi/src/it/java/org/sonar/server/rule/ws/SearchActionIT.java View 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);
}

+ 19
- 3
server/sonar-webserver-webapi/src/it/java/org/sonar/server/rule/ws/ShowActionIT.java View 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

+ 24
- 0
server/sonar-webserver-webapi/src/it/java/org/sonar/server/rule/ws/UpdateActionIT.java View 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()

+ 3
- 1
server/sonar-webserver-webapi/src/main/java/org/sonar/server/rule/ws/CreateAction.java View 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

+ 24
- 0
server/sonar-webserver-webapi/src/main/java/org/sonar/server/rule/ws/RuleMapper.java View 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)) {

+ 2
- 1
server/sonar-webserver-webapi/src/main/java/org/sonar/server/rule/ws/RulesWsParameters.java View 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

+ 13
- 2
server/sonar-webserver-webapi/src/main/java/org/sonar/server/rule/ws/SearchAction.java View 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.")

+ 2
- 2
server/sonar-webserver-webapi/src/main/java/org/sonar/server/rule/ws/ShowAction.java View 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)

+ 3
- 0
server/sonar-webserver-webapi/src/main/java/org/sonar/server/rule/ws/UpdateAction.java View 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)

+ 32
- 24
server/sonar-webserver-webapi/src/main/resources/org/sonar/server/rule/ws/create-example.json View 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"
}
]
}
}

+ 45
- 8
server/sonar-webserver-webapi/src/main/resources/org/sonar/server/rule/ws/search-example.json View File

@@ -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": "<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",
@@ -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 @@
}
]
}

]
}

+ 59
- 46
server/sonar-webserver-webapi/src/main/resources/org/sonar/server/rule/ws/show-example.json View 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",
@@ -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": "<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"
}
]
}
]}
]
}

+ 34
- 25
server/sonar-webserver-webapi/src/main/resources/org/sonar/server/rule/ws/update-example.json View 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"
}
]
}
}

+ 17
- 8
sonar-ws/src/main/protobuf/ws-rules.proto View 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;
}

Loading…
Cancel
Save