From: Julien Lancelot Date: Fri, 30 Jun 2017 07:27:37 +0000 (+0200) Subject: SONAR-9483 Add "compareToProfile" in rules search ws X-Git-Tag: 6.5-M2~14 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=323bede55a00b3b6e180b3b8951aa63b639bc308;p=sonarqube.git SONAR-9483 Add "compareToProfile" in rules search ws --- diff --git a/server/sonar-server/src/main/java/org/sonar/server/rule/ws/RuleQueryFactory.java b/server/sonar-server/src/main/java/org/sonar/server/rule/ws/RuleQueryFactory.java index 376d2e4f153..0046aa91904 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/rule/ws/RuleQueryFactory.java +++ b/server/sonar-server/src/main/java/org/sonar/server/rule/ws/RuleQueryFactory.java @@ -21,7 +21,6 @@ package org.sonar.server.rule.ws; import com.google.common.collect.ImmutableList; import java.util.Date; -import java.util.List; import org.sonar.api.rule.RuleStatus; import org.sonar.api.rules.RuleType; import org.sonar.api.server.ServerSide; @@ -32,13 +31,16 @@ import org.sonar.db.DbSession; import org.sonar.db.organization.OrganizationDto; import org.sonar.db.qualityprofile.QProfileDto; import org.sonar.server.rule.index.RuleQuery; -import org.sonar.server.ws.WsUtils; +import static com.google.common.base.Preconditions.checkArgument; import static java.lang.String.format; import static org.sonar.server.util.EnumUtils.toEnums; +import static org.sonar.server.ws.WsUtils.checkFound; +import static org.sonar.server.ws.WsUtils.checkFoundWithOptional; import static org.sonarqube.ws.client.rule.RulesWsParameters.PARAM_ACTIVATION; import static org.sonarqube.ws.client.rule.RulesWsParameters.PARAM_ACTIVE_SEVERITIES; import static org.sonarqube.ws.client.rule.RulesWsParameters.PARAM_AVAILABLE_SINCE; +import static org.sonarqube.ws.client.rule.RulesWsParameters.PARAM_COMPARE_TO_PROFILE; import static org.sonarqube.ws.client.rule.RulesWsParameters.PARAM_INHERITANCE; import static org.sonarqube.ws.client.rule.RulesWsParameters.PARAM_IS_TEMPLATE; import static org.sonarqube.ws.client.rule.RulesWsParameters.PARAM_LANGUAGES; @@ -78,30 +80,12 @@ public class RuleQueryFactory { Boolean activation = request.paramAsBoolean(PARAM_ACTIVATION); query.setActivation(activation); - String profileUuid = request.param(PARAM_QPROFILE); - String organizationKey = request.param(PARAM_ORGANIZATION); - OrganizationDto organization; - List languages; - if (profileUuid == null) { - organization = wsSupport.getOrganizationByKey(dbSession, organizationKey); - languages = request.paramAsStrings(PARAM_LANGUAGES); - } else { - QProfileDto profileOptional = dbClient.qualityProfileDao().selectByUuid(dbSession, profileUuid); - QProfileDto profile = WsUtils.checkFound(profileOptional, "The specified qualityProfile '%s' does not exist", profileUuid); - query.setQProfile(profile); - languages = ImmutableList.of(profile.getLanguage()); - organization = WsUtils.checkFoundWithOptional(dbClient.organizationDao().selectByUuid(dbSession, profile.getOrganizationUuid()), "No organization with UUID ", - profile.getOrganizationUuid()); - if (organizationKey != null) { - OrganizationDto inputOrganization = WsUtils.checkFoundWithOptional(dbClient.organizationDao().selectByKey(dbSession, organizationKey), "No organization with key '%s'", - organizationKey); - if (!organization.getUuid().equals(inputOrganization.getUuid())) { - throw new IllegalArgumentException(format("The specified quality profile '%s' is not part of the specified organization '%s'", profileUuid, organizationKey)); - } - } - } - query.setOrganization(organization); - query.setLanguages(languages); + // Order is important : 1. Load profile, 2. Load organization either from parameter or from profile, 3. Load compare to profile + setProfile(dbSession, query, request); + setOrganization(dbSession, query, request); + setCompareToProfile(dbSession, query, request); + QProfileDto profile = query.getQProfile(); + query.setLanguages(profile == null ? request.paramAsStrings(PARAM_LANGUAGES) : ImmutableList.of(profile.getLanguage())); query.setTags(request.paramAsStrings(PARAM_TAGS)); query.setInheritance(request.paramAsStrings(PARAM_INHERITANCE)); @@ -118,4 +102,46 @@ public class RuleQueryFactory { } return query; } + + private void setProfile(DbSession dbSession, RuleQuery query, Request request) { + String profileUuid = request.param(PARAM_QPROFILE); + if (profileUuid == null) { + return; + } + QProfileDto profileOptional = dbClient.qualityProfileDao().selectByUuid(dbSession, profileUuid); + QProfileDto profile = checkFound(profileOptional, "The specified qualityProfile '%s' does not exist", profileUuid); + query.setQProfile(profile); + } + + private void setOrganization(DbSession dbSession, RuleQuery query, Request request) { + String organizationKey = request.param(PARAM_ORGANIZATION); + QProfileDto profile = query.getQProfile(); + if (profile == null) { + query.setOrganization(wsSupport.getOrganizationByKey(dbSession, organizationKey)); + return; + } + OrganizationDto organization = checkFoundWithOptional(dbClient.organizationDao().selectByUuid(dbSession, profile.getOrganizationUuid()), "No organization with UUID ", + profile.getOrganizationUuid()); + if (organizationKey != null) { + OrganizationDto inputOrganization = checkFoundWithOptional(dbClient.organizationDao().selectByKey(dbSession, organizationKey), "No organization with key '%s'", + organizationKey); + checkArgument(organization.getUuid().equals(inputOrganization.getUuid()), + format("The specified quality profile '%s' is not part of the specified organization '%s'", profile.getKee(), organizationKey)); + } + query.setOrganization(organization); + } + + private void setCompareToProfile(DbSession dbSession, RuleQuery query, Request request) { + String compareToProfileUuid = request.param(PARAM_COMPARE_TO_PROFILE); + if (compareToProfileUuid == null) { + return; + } + QProfileDto profileOptional = dbClient.qualityProfileDao().selectByUuid(dbSession, compareToProfileUuid); + QProfileDto profile = checkFound(profileOptional, "The specified qualityProfile '%s' does not exist", compareToProfileUuid); + + checkArgument(query.getOrganization().getUuid().equals(profile.getOrganizationUuid()), + format("The specified quality profile '%s' is not part of the specified organization '%s'", profile.getKee(), query.getOrganization().getKey())); + + query.setCompareToQProfile(profile); + } } diff --git a/server/sonar-server/src/main/java/org/sonar/server/rule/ws/SearchAction.java b/server/sonar-server/src/main/java/org/sonar/server/rule/ws/SearchAction.java index 51fe97c7090..fb5a05efabc 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/rule/ws/SearchAction.java +++ b/server/sonar-server/src/main/java/org/sonar/server/rule/ws/SearchAction.java @@ -26,7 +26,6 @@ import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.common.collect.Ordering; import com.google.common.collect.Sets; -import com.google.common.io.Resources; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; @@ -69,6 +68,8 @@ import static org.sonar.api.server.ws.WebService.Param.PAGE; import static org.sonar.api.server.ws.WebService.Param.PAGE_SIZE; import static org.sonar.api.server.ws.WebService.Param.SORT; import static org.sonar.api.server.ws.WebService.Param.TEXT_QUERY; +import static org.sonar.core.util.Uuids.UUID_EXAMPLE_01; +import static org.sonar.core.util.Uuids.UUID_EXAMPLE_02; import static org.sonar.server.es.SearchOptions.MAX_LIMIT; import static org.sonar.server.rule.index.RuleIndex.ALL_STATUSES_EXCEPT_REMOVED; import static org.sonar.server.rule.index.RuleIndex.FACET_ACTIVE_SEVERITIES; @@ -84,6 +85,7 @@ import static org.sonarqube.ws.client.rule.RulesWsParameters.OPTIONAL_FIELDS; import static org.sonarqube.ws.client.rule.RulesWsParameters.PARAM_ACTIVATION; import static org.sonarqube.ws.client.rule.RulesWsParameters.PARAM_ACTIVE_SEVERITIES; import static org.sonarqube.ws.client.rule.RulesWsParameters.PARAM_AVAILABLE_SINCE; +import static org.sonarqube.ws.client.rule.RulesWsParameters.PARAM_COMPARE_TO_PROFILE; import static org.sonarqube.ws.client.rule.RulesWsParameters.PARAM_INHERITANCE; import static org.sonarqube.ws.client.rule.RulesWsParameters.PARAM_IS_TEMPLATE; import static org.sonarqube.ws.client.rule.RulesWsParameters.PARAM_LANGUAGES; @@ -148,7 +150,6 @@ public class SearchAction implements RulesWsAction { .setPossibleValues(Ordering.natural().sortedCopy(OPTIONAL_FIELDS)); Iterator it = OPTIONAL_FIELDS.iterator(); paramFields.setExampleValue(format("%s,%s", it.next(), it.next())); - doDefinition(action); } @@ -191,7 +192,7 @@ public class SearchAction implements RulesWsAction { "
  • \"defaultDebtRemFnOffset\" becomes \"defaultRemFnBaseEffort\"
  • " + "
  • \"debtOverloaded\" becomes \"remFnOverloaded\"
  • " + "") - .setResponseExample(Resources.getResource(getClass(), "example-search.json")) + .setResponseExample(getClass().getResource("search-example.json")) .setSince("4.4") .setHandler(this); @@ -257,9 +258,15 @@ public class SearchAction implements RulesWsAction { action .createParam(PARAM_QPROFILE) - .setDescription("Key of Quality profile to filter on. Used only if the parameter '" + + .setDescription("Quality profile key to filter on. Used only if the parameter '" + PARAM_ACTIVATION + "' is set.") - .setExampleValue("sonar-way-cs-12345"); + .setExampleValue(UUID_EXAMPLE_01); + + action.createParam(PARAM_COMPARE_TO_PROFILE) + .setDescription("Quality profile key to filter rules that are activated. Meant to compare easily to profile set in '%s'", PARAM_QPROFILE) + .setInternal(true) + .setSince("6.5") + .setExampleValue(UUID_EXAMPLE_02); action .createParam(PARAM_INHERITANCE) diff --git a/server/sonar-server/src/main/resources/org/sonar/server/rule/ws/example-search.json b/server/sonar-server/src/main/resources/org/sonar/server/rule/ws/example-search.json deleted file mode 100644 index 174745cdbbf..00000000000 --- a/server/sonar-server/src/main/resources/org/sonar/server/rule/ws/example-search.json +++ /dev/null @@ -1,256 +0,0 @@ -{ - "total": 4, - "p": 1, - "ps": 3, - "rules": [ - { - "key": "squid:S1067", - "repo": "squid", - "name": "Expressions should not be too complex", - "createdAt": "2013-03-27T08:52:40+0100", - "htmlDesc": "

    \nThe complexity of an expression is defined by the number of &&, || and condition ? ifTrue : ifFalse operators it contains.\nA single expression's complexity should not become too high to keep the code readable.\n

    \n\n

    The following code, with a maximum complexity of 3:

    \n\n
    \nif (condition1 && condition2 && condition3 && condition4) { /* ... */ }  // Non-Compliant\n
    \n\n

    could be refactored into something like:

    \n\n
    \nif (relevantMethodName1() && relevantMethodName2()) { /* ... */ }        // Compliant\n\n/* ... */\n\nprivate boolean relevantMethodName1() {\n  return condition1 && condition2;\n}\n\nprivate boolean relevantMethodName2() {\n  return condition3 && condition4;\n}\n
    ", - "severity": "MAJOR", - "status": "READY", - "internalKey": "S1067", - "isTemplate": false, - "tags": [], - "sysTags": ["brain-overload"], - "lang": "java", - "langName": "Java", - "type": "CODE_SMELL", - "params": [ - { - "key": "max", - "desc": "Maximum number of allowed conditional operators in an expression", - "defaultValue": "3" - } - ] - }, - { - "key": "squid:ClassCyclomaticComplexity", - "repo": "squid", - "name": "Avoid too complex class", - "createdAt": "2013-03-27T08:52:40+0100", - "htmlDesc": "

    The Cyclomatic Complexity is measured by the number of (&&, ||)\n\toperators and (if, while, do, for, ?:, catch, switch, case, return,\n\tthrow) statements in the body of a class plus one for each constructor,\n\tmethod (but not getter/setter), static initializer, or instance\n\tinitializer in the class. The last return stament in method, if exists,\n\tis not taken into account.

    \n

    \n\tEven when the Cyclomatic Complexity of a class is very high, this\n\tcomplexity might be well distributed among all methods. Nevertheless,\n\tmost of the time, a very complex class is a class which breaks the Single\n\t\tResponsibility Principle and which should be re-factored to be split\n\tin several classes.\n

    ", - "severity": "MAJOR", - "status": "READY", - "internalKey": "ClassCyclomaticComplexity", - "isTemplate": false, - "tags": [], - "sysTags": ["brain-overload"], - "lang": "java", - "langName": "Java", - "type": "BUG", - "params": [ - { - "key": "max", - "desc": "Maximum complexity allowed.", - "defaultValue": "200" - } - ] - }, - { - "key": "squid:MethodCyclomaticComplexity", - "repo": "squid", - "name": "Methods should not be too complex", - "createdAt": "2013-03-27T08:52:40+0100", - "htmlDesc": "

    The Cyclomatic Complexity is measured by the number of\n\t(&&, ||) operators and (if, while, do, for, ?:, catch, switch,\n\tcase, return, throw) statements in the body of a class plus one for\n\teach constructor, method (but not getter/setter), static initializer,\n\tor instance initializer in the class. The last return stament in\n\tmethod, if exists, is not taken into account.

    \n

    \n\tEven when the Cyclomatic Complexity of a class is very high, this\n\tcomplexity might be well distributed among all methods. Nevertheless,\n\tmost of the time, a very complex class is a class which breaks the Single\n\t\tResponsibility Principle and which should be re-factored to be split\n\tin several classes.\n

    ", - "severity": "MAJOR", - "status": "READY", - "internalKey": "MethodCyclomaticComplexity", - "isTemplate": false, - "tags": [], - "sysTags": ["brain-overload"], - "lang": "java", - "langName": "Java", - "type": "VULNERABILITY", - "params": [ - { - "key": "max", - "desc": "Maximum complexity allowed.", - "defaultValue": "10" - } - ] - }, - { - "key": "squid:XPath", - "repo": "squid", - "name": "XPath rule", - "createdAt": "2013-03-27T08:52:40+0100", - "htmlDesc": "

    \nThis rule allows to define some homemade Java rules with help of an XPath expression.\n

    \n\n

    \nIssues are created depending on the return value of the XPath expression. If the XPath expression returns:\n

    \n
      \n
    • a single or list of AST nodes, then a line issue with the given message is created for each node
    • \n
    • a boolean, then a file issue with the given message is created only if the boolean is true
    • \n
    • anything else, no issue is created
    • \n
    \n\n

    \nHere is an example of an XPath expression to log an issue on each if statement : //ifStatement\n

    ", - "severity": "MAJOR", - "status": "READY", - "internalKey": "XPath", - "isTemplate": true, - "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", - "lang": "java", - "langName": "Java", - "type": "CODE_SMELL", - "params": [ - { - "key": "xpathQuery", - "desc": "The XPath query", - "defaultValue": "" - }, - { - "key": "message", - "desc": "The violation message", - "defaultValue": "The XPath expression matches this piece of code" - } - ] - }, - { - "key": "squid:XPath_1369910135", - "repo": "squid", - "name": "firstOf() alternatives should be rules or token types", - "createdAt": "2013-05-30T10:35:35+0200", - "htmlDesc": "

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

    \r\n\r\n

    \r\nConsider the following rule:\r\n

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

    \r\nIf, from a check, one wants to forbid the usage of the \"BAR\" alternative,\r\nthe easiest option will be to verify that the value of the first token is \"BAR\",\r\ni.e. \"BAR\".equals(compilationUnitNode.getTokenValue()).\r\n

    \r\n\r\n

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

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

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

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

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

    ", - "severity": "MAJOR", - "status": "READY", - "internalKey": "XPath", - "isTemplate": false, - "templateKey": "squid:XPath", - "tags": [ ], - "sysTags": [ ], - "lang": "java", - "langName": "Java", - "type": "CODE_SMELL", - "params": [ - { - "key": "xpathQuery", - "desc": "The XPath query", - "defaultValue": "//expression[primary/qualifiedIdentifier[count(IDENTIFIER) = 2]/IDENTIFIER[2]/@tokenValue = 'firstOf' and primary/identifierSuffix/arguments/expression[not(primary) or primary[not(qualifiedIdentifier) or identifierSuffix]]]" - }, - { - "key": "message", - "desc": "The violation message", - "defaultValue": "Refactor this firstOf() to only use a rule or token type for each alternative." - } - ] - } - ], - "actives": { - "squid:MethodCyclomaticComplexity": [ - { - "qProfile": "Sonar way with Findbugs:java", - "inherit": "NONE", - "severity": "MAJOR", - "params": [ - { - "key": "max", - "value": "10" - } - ] - }, - { - "qProfile": "Sonar way:java", - "inherit": "NONE", - "severity": "MAJOR", - "params": [ - { - "key": "max", - "value": "10" - } - ] - } - ], - "squid:S1067": [ - { - "qProfile": "Sonar way with Findbugs:java", - "inherit": "NONE", - "severity": "MAJOR", - "params": [ - { - "key": "max", - "value": "3" - } - ] - }, - { - "qProfile": "Sonar way:java", - "inherit": "NONE", - "severity": "MAJOR", - "params": [ - { - "key": "max", - "value": "3" - } - ] - } - ], - "squid:ClassCyclomaticComplexity": [ - { - "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" - } - ] - } - ] - }, - "facets": [ - { - "name": "tags", - "values": [ - { - "val": "complexity", - "count": 141 - }, - { - "val": "java8", - "count": 42 - }, - { - "val": "javadoc", - "count": 13 - } - ] - }, - { - "name": "languages", - "values": [ - { - "val": "java", - "count": 563 - } - ] - }, - { - "name": "repositories", - "values": [ - { - "val": "findbugs", - "count": 419 - }, - { - "val": "squid", - "count": 138 - }, - { - "val": "common-java", - "count": 6 - } - ] - } - - ] -} diff --git a/server/sonar-server/src/main/resources/org/sonar/server/rule/ws/search-example.json b/server/sonar-server/src/main/resources/org/sonar/server/rule/ws/search-example.json new file mode 100644 index 00000000000..174745cdbbf --- /dev/null +++ b/server/sonar-server/src/main/resources/org/sonar/server/rule/ws/search-example.json @@ -0,0 +1,256 @@ +{ + "total": 4, + "p": 1, + "ps": 3, + "rules": [ + { + "key": "squid:S1067", + "repo": "squid", + "name": "Expressions should not be too complex", + "createdAt": "2013-03-27T08:52:40+0100", + "htmlDesc": "

    \nThe complexity of an expression is defined by the number of &&, || and condition ? ifTrue : ifFalse operators it contains.\nA single expression's complexity should not become too high to keep the code readable.\n

    \n\n

    The following code, with a maximum complexity of 3:

    \n\n
    \nif (condition1 && condition2 && condition3 && condition4) { /* ... */ }  // Non-Compliant\n
    \n\n

    could be refactored into something like:

    \n\n
    \nif (relevantMethodName1() && relevantMethodName2()) { /* ... */ }        // Compliant\n\n/* ... */\n\nprivate boolean relevantMethodName1() {\n  return condition1 && condition2;\n}\n\nprivate boolean relevantMethodName2() {\n  return condition3 && condition4;\n}\n
    ", + "severity": "MAJOR", + "status": "READY", + "internalKey": "S1067", + "isTemplate": false, + "tags": [], + "sysTags": ["brain-overload"], + "lang": "java", + "langName": "Java", + "type": "CODE_SMELL", + "params": [ + { + "key": "max", + "desc": "Maximum number of allowed conditional operators in an expression", + "defaultValue": "3" + } + ] + }, + { + "key": "squid:ClassCyclomaticComplexity", + "repo": "squid", + "name": "Avoid too complex class", + "createdAt": "2013-03-27T08:52:40+0100", + "htmlDesc": "

    The Cyclomatic Complexity is measured by the number of (&&, ||)\n\toperators and (if, while, do, for, ?:, catch, switch, case, return,\n\tthrow) statements in the body of a class plus one for each constructor,\n\tmethod (but not getter/setter), static initializer, or instance\n\tinitializer in the class. The last return stament in method, if exists,\n\tis not taken into account.

    \n

    \n\tEven when the Cyclomatic Complexity of a class is very high, this\n\tcomplexity might be well distributed among all methods. Nevertheless,\n\tmost of the time, a very complex class is a class which breaks the Single\n\t\tResponsibility Principle and which should be re-factored to be split\n\tin several classes.\n

    ", + "severity": "MAJOR", + "status": "READY", + "internalKey": "ClassCyclomaticComplexity", + "isTemplate": false, + "tags": [], + "sysTags": ["brain-overload"], + "lang": "java", + "langName": "Java", + "type": "BUG", + "params": [ + { + "key": "max", + "desc": "Maximum complexity allowed.", + "defaultValue": "200" + } + ] + }, + { + "key": "squid:MethodCyclomaticComplexity", + "repo": "squid", + "name": "Methods should not be too complex", + "createdAt": "2013-03-27T08:52:40+0100", + "htmlDesc": "

    The Cyclomatic Complexity is measured by the number of\n\t(&&, ||) operators and (if, while, do, for, ?:, catch, switch,\n\tcase, return, throw) statements in the body of a class plus one for\n\teach constructor, method (but not getter/setter), static initializer,\n\tor instance initializer in the class. The last return stament in\n\tmethod, if exists, is not taken into account.

    \n

    \n\tEven when the Cyclomatic Complexity of a class is very high, this\n\tcomplexity might be well distributed among all methods. Nevertheless,\n\tmost of the time, a very complex class is a class which breaks the Single\n\t\tResponsibility Principle and which should be re-factored to be split\n\tin several classes.\n

    ", + "severity": "MAJOR", + "status": "READY", + "internalKey": "MethodCyclomaticComplexity", + "isTemplate": false, + "tags": [], + "sysTags": ["brain-overload"], + "lang": "java", + "langName": "Java", + "type": "VULNERABILITY", + "params": [ + { + "key": "max", + "desc": "Maximum complexity allowed.", + "defaultValue": "10" + } + ] + }, + { + "key": "squid:XPath", + "repo": "squid", + "name": "XPath rule", + "createdAt": "2013-03-27T08:52:40+0100", + "htmlDesc": "

    \nThis rule allows to define some homemade Java rules with help of an XPath expression.\n

    \n\n

    \nIssues are created depending on the return value of the XPath expression. If the XPath expression returns:\n

    \n
      \n
    • a single or list of AST nodes, then a line issue with the given message is created for each node
    • \n
    • a boolean, then a file issue with the given message is created only if the boolean is true
    • \n
    • anything else, no issue is created
    • \n
    \n\n

    \nHere is an example of an XPath expression to log an issue on each if statement : //ifStatement\n

    ", + "severity": "MAJOR", + "status": "READY", + "internalKey": "XPath", + "isTemplate": true, + "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", + "lang": "java", + "langName": "Java", + "type": "CODE_SMELL", + "params": [ + { + "key": "xpathQuery", + "desc": "The XPath query", + "defaultValue": "" + }, + { + "key": "message", + "desc": "The violation message", + "defaultValue": "The XPath expression matches this piece of code" + } + ] + }, + { + "key": "squid:XPath_1369910135", + "repo": "squid", + "name": "firstOf() alternatives should be rules or token types", + "createdAt": "2013-05-30T10:35:35+0200", + "htmlDesc": "

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

    \r\n\r\n

    \r\nConsider the following rule:\r\n

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

    \r\nIf, from a check, one wants to forbid the usage of the \"BAR\" alternative,\r\nthe easiest option will be to verify that the value of the first token is \"BAR\",\r\ni.e. \"BAR\".equals(compilationUnitNode.getTokenValue()).\r\n

    \r\n\r\n

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

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

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

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

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

    ", + "severity": "MAJOR", + "status": "READY", + "internalKey": "XPath", + "isTemplate": false, + "templateKey": "squid:XPath", + "tags": [ ], + "sysTags": [ ], + "lang": "java", + "langName": "Java", + "type": "CODE_SMELL", + "params": [ + { + "key": "xpathQuery", + "desc": "The XPath query", + "defaultValue": "//expression[primary/qualifiedIdentifier[count(IDENTIFIER) = 2]/IDENTIFIER[2]/@tokenValue = 'firstOf' and primary/identifierSuffix/arguments/expression[not(primary) or primary[not(qualifiedIdentifier) or identifierSuffix]]]" + }, + { + "key": "message", + "desc": "The violation message", + "defaultValue": "Refactor this firstOf() to only use a rule or token type for each alternative." + } + ] + } + ], + "actives": { + "squid:MethodCyclomaticComplexity": [ + { + "qProfile": "Sonar way with Findbugs:java", + "inherit": "NONE", + "severity": "MAJOR", + "params": [ + { + "key": "max", + "value": "10" + } + ] + }, + { + "qProfile": "Sonar way:java", + "inherit": "NONE", + "severity": "MAJOR", + "params": [ + { + "key": "max", + "value": "10" + } + ] + } + ], + "squid:S1067": [ + { + "qProfile": "Sonar way with Findbugs:java", + "inherit": "NONE", + "severity": "MAJOR", + "params": [ + { + "key": "max", + "value": "3" + } + ] + }, + { + "qProfile": "Sonar way:java", + "inherit": "NONE", + "severity": "MAJOR", + "params": [ + { + "key": "max", + "value": "3" + } + ] + } + ], + "squid:ClassCyclomaticComplexity": [ + { + "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" + } + ] + } + ] + }, + "facets": [ + { + "name": "tags", + "values": [ + { + "val": "complexity", + "count": 141 + }, + { + "val": "java8", + "count": 42 + }, + { + "val": "javadoc", + "count": 13 + } + ] + }, + { + "name": "languages", + "values": [ + { + "val": "java", + "count": 563 + } + ] + }, + { + "name": "repositories", + "values": [ + { + "val": "findbugs", + "count": 419 + }, + { + "val": "squid", + "count": 138 + }, + { + "val": "common-java", + "count": 6 + } + ] + } + + ] +} diff --git a/server/sonar-server/src/test/java/org/sonar/server/qualityprofile/ws/ActivateRulesActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/qualityprofile/ws/ActivateRulesActionTest.java index 1074eda558a..0e475029e23 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/qualityprofile/ws/ActivateRulesActionTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/qualityprofile/ws/ActivateRulesActionTest.java @@ -82,6 +82,7 @@ public class ActivateRulesActionTest { "is_template", "inheritance", "qprofile", + "compareToProfile", "targetSeverity", "tags", "asc", diff --git a/server/sonar-server/src/test/java/org/sonar/server/qualityprofile/ws/DeactivateRulesActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/qualityprofile/ws/DeactivateRulesActionTest.java index 7936fb0387a..0312273fc84 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/qualityprofile/ws/DeactivateRulesActionTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/qualityprofile/ws/DeactivateRulesActionTest.java @@ -28,8 +28,8 @@ import org.sonar.db.DbClient; import org.sonar.db.DbTester; import org.sonar.db.organization.OrganizationDto; import org.sonar.db.permission.OrganizationPermission; -import org.sonar.server.exceptions.BadRequestException; import org.sonar.db.qualityprofile.QProfileDto; +import org.sonar.server.exceptions.BadRequestException; import org.sonar.server.exceptions.ForbiddenException; import org.sonar.server.exceptions.UnauthorizedException; import org.sonar.server.organization.TestDefaultOrganizationProvider; @@ -81,6 +81,7 @@ public class DeactivateRulesActionTest { "is_template", "inheritance", "qprofile", + "compareToProfile", "tags", "asc", "q", diff --git a/server/sonar-server/src/test/java/org/sonar/server/rule/ws/RuleQueryFactoryTest.java b/server/sonar-server/src/test/java/org/sonar/server/rule/ws/RuleQueryFactoryTest.java index 1010fc65c49..c81b8a7611d 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/rule/ws/RuleQueryFactoryTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/rule/ws/RuleQueryFactoryTest.java @@ -31,6 +31,7 @@ import org.sonar.db.DbClient; import org.sonar.db.DbTester; import org.sonar.db.organization.OrganizationDto; import org.sonar.db.qualityprofile.QProfileDto; +import org.sonar.server.exceptions.NotFoundException; import org.sonar.server.organization.TestDefaultOrganizationProvider; import org.sonar.server.rule.index.RuleQuery; import org.sonar.server.ws.TestRequest; @@ -54,6 +55,7 @@ import static org.sonar.server.rule.ws.SearchAction.defineRuleSearchParameters; import static org.sonarqube.ws.client.rule.RulesWsParameters.PARAM_ACTIVATION; import static org.sonarqube.ws.client.rule.RulesWsParameters.PARAM_ACTIVE_SEVERITIES; import static org.sonarqube.ws.client.rule.RulesWsParameters.PARAM_AVAILABLE_SINCE; +import static org.sonarqube.ws.client.rule.RulesWsParameters.PARAM_COMPARE_TO_PROFILE; import static org.sonarqube.ws.client.rule.RulesWsParameters.PARAM_INHERITANCE; import static org.sonarqube.ws.client.rule.RulesWsParameters.PARAM_IS_TEMPLATE; import static org.sonarqube.ws.client.rule.RulesWsParameters.PARAM_LANGUAGES; @@ -72,7 +74,7 @@ public class RuleQueryFactoryTest { @Rule public DbTester dbTester = DbTester.create(System2.INSTANCE); @Rule - public ExpectedException thrown = ExpectedException.none(); + public ExpectedException expectedException = ExpectedException.none(); private DbClient dbClient = dbTester.getDbClient(); @@ -108,13 +110,14 @@ public class RuleQueryFactoryTest { assertThat(result.getTags()).isNull(); assertThat(result.templateKey()).isNull(); assertThat(result.getTypes()).isEmpty(); - assertThat(result.getSortField()).isNull(); + assertThat(result.getCompareToQProfile()).isNull(); } @Test public void create_query() throws Exception { QProfileDto qualityProfile = dbTester.qualityProfiles().insert(organization); + QProfileDto compareToQualityProfile = dbTester.qualityProfiles().insert(organization); RuleQuery result = execute( PARAM_RULE_KEY, "ruleKey", @@ -126,8 +129,9 @@ public class RuleQueryFactoryTest { PARAM_IS_TEMPLATE, "true", PARAM_LANGUAGES, "java,js", TEXT_QUERY, "S001", - PARAM_QPROFILE, qualityProfile.getKee(), PARAM_ORGANIZATION, organization.getKey(), + PARAM_QPROFILE, qualityProfile.getKee(), + PARAM_COMPARE_TO_PROFILE, compareToQualityProfile.getKee(), PARAM_REPOSITORIES, "pmd,checkstyle", PARAM_SEVERITIES, "MINOR,CRITICAL", PARAM_STATUSES, "DEPRECATED,READY", @@ -149,6 +153,7 @@ public class RuleQueryFactoryTest { assertThat(result.getLanguages()).containsOnly(qualityProfile.getLanguage()); assertThat(result.getQueryText()).isEqualTo("S001"); assertThat(result.getQProfile().getKee()).isEqualTo(qualityProfile.getKee()); + assertThat(result.getCompareToQProfile().getKee()).isEqualTo(compareToQualityProfile.getKee()); assertThat(result.getOrganization().getUuid()).isEqualTo(organization.getUuid()); assertThat(result.getRepositories()).containsOnly("pmd", "checkstyle"); assertThat(result.getRuleKey()).isNull(); @@ -205,6 +210,17 @@ public class RuleQueryFactoryTest { assertThat(result.getOrganization().getUuid()).isEqualTo(organization.getUuid()); } + @Test + public void filter_on_compare_to() { + QProfileDto compareToProfile = dbTester.qualityProfiles().insert(organization); + + RuleQuery result = execute( + PARAM_ORGANIZATION, organization.getKey(), + PARAM_COMPARE_TO_PROFILE, compareToProfile.getKee()); + + assertThat(result.getCompareToQProfile().getKee()).isEqualTo(compareToProfile.getKee()); + } + @Test public void fail_if_organization_and_quality_profile_are_contradictory() throws Exception { OrganizationDto organization1 = dbTester.organizations().insert(); @@ -215,13 +231,60 @@ public class RuleQueryFactoryTest { String qualityProfileKey = qualityProfile.getKee(); String organization2Key = organization2.getKey(); - thrown.expect(IllegalArgumentException.class); - thrown.expectMessage("The specified quality profile '" + qualityProfileKey + "' is not part of the specified organization '" + organization2Key + "'"); + expectedException.expect(IllegalArgumentException.class); + expectedException.expectMessage("The specified quality profile '" + qualityProfileKey + "' is not part of the specified organization '" + organization2Key + "'"); execute(PARAM_QPROFILE, qualityProfileKey, PARAM_ORGANIZATION, organization2Key); } + @Test + public void fail_if_organization_and_compare_to_quality_profile_are_contradictory() throws Exception { + OrganizationDto organization = dbTester.organizations().insert(); + QProfileDto qualityProfile = dbTester.qualityProfiles().insert(organization); + + OrganizationDto otherOrganization = dbTester.organizations().insert(); + QProfileDto compareToQualityProfile = dbTester.qualityProfiles().insert(otherOrganization); + + expectedException.expect(IllegalArgumentException.class); + expectedException.expectMessage("The specified quality profile '" + compareToQualityProfile.getKee() + "' is not part of the specified organization '" + organization.getKey() + "'"); + + execute(PARAM_QPROFILE, qualityProfile.getKee(), + PARAM_COMPARE_TO_PROFILE, compareToQualityProfile.getKee(), + PARAM_ORGANIZATION, organization.getKey()); + } + + @Test + public void fail_when_organization_does_not_exist() { + QProfileDto qualityProfile = dbTester.qualityProfiles().insert(organization); + String qualityProfileKey = qualityProfile.getKee(); + + expectedException.expect(NotFoundException.class); + expectedException.expectMessage("No organization with key 'unknown'"); + + execute(PARAM_QPROFILE, qualityProfileKey, + PARAM_ORGANIZATION, "unknown"); + } + + @Test + public void fail_when_profile_does_not_exist() { + expectedException.expect(NotFoundException.class); + expectedException.expectMessage("The specified qualityProfile 'unknown' does not exist"); + + execute(PARAM_QPROFILE, "unknown"); + } + + @Test + public void fail_when_compare_to_profile_does_not_exist() { + QProfileDto qualityProfile = dbTester.qualityProfiles().insert(organization); + + expectedException.expect(NotFoundException.class); + expectedException.expectMessage("The specified qualityProfile 'unknown' does not exist"); + + execute(PARAM_QPROFILE, qualityProfile.getKee(), + PARAM_COMPARE_TO_PROFILE, "unknown"); + } + private RuleQuery execute(String... paramsKeyAndValue) { WsActionTester ws = new WsActionTester(fakeAction); TestRequest request = ws.newRequest(); diff --git a/server/sonar-server/src/test/java/org/sonar/server/rule/ws/SearchActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/rule/ws/SearchActionTest.java index 1d5507795ea..4f9108f2256 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/rule/ws/SearchActionTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/rule/ws/SearchActionTest.java @@ -82,36 +82,42 @@ import static org.mockito.Mockito.mock; import static org.sonar.api.rule.Severity.BLOCKER; import static org.sonar.db.rule.RuleTesting.setSystemTags; import static org.sonar.db.rule.RuleTesting.setTags; +import static org.sonarqube.ws.client.rule.RulesWsParameters.PARAM_ACTIVATION; +import static org.sonarqube.ws.client.rule.RulesWsParameters.PARAM_COMPARE_TO_PROFILE; +import static org.sonarqube.ws.client.rule.RulesWsParameters.PARAM_ORGANIZATION; +import static org.sonarqube.ws.client.rule.RulesWsParameters.PARAM_QPROFILE; import static org.sonarqube.ws.client.rule.RulesWsParameters.PARAM_RULE_KEY; public class SearchActionTest { + private static final String JAVA = "java"; + @org.junit.Rule public UserSessionRule userSession = UserSessionRule.standalone(); @org.junit.Rule - public ExpectedException thrown = ExpectedException.none(); + public ExpectedException expectedException = ExpectedException.none(); private System2 system2 = new AlwaysIncreasingSystem2(); @org.junit.Rule - public DbTester dbTester = DbTester.create(system2); + public DbTester db = DbTester.create(system2); @org.junit.Rule public EsTester es = new EsTester(new RuleIndexDefinition(new MapSettings())); - private DefaultOrganizationProvider defaultOrganizationProvider = TestDefaultOrganizationProvider.from(dbTester); + private DefaultOrganizationProvider defaultOrganizationProvider = TestDefaultOrganizationProvider.from(db); private RuleIndex ruleIndex = new RuleIndex(es.client()); - private RuleIndexer ruleIndexer = new RuleIndexer(es.client(), dbTester.getDbClient()); - private ActiveRuleIndexer activeRuleIndexer = new ActiveRuleIndexer(dbTester.getDbClient(), es.client(), new ActiveRuleIteratorFactory(dbTester.getDbClient())); - private Languages languages = LanguageTesting.newLanguages("java", "js"); - private ActiveRuleCompleter activeRuleCompleter = new ActiveRuleCompleter(dbTester.getDbClient(), languages); - private RuleWsSupport wsSupport = new RuleWsSupport(dbTester.getDbClient(), userSession, defaultOrganizationProvider); - private RuleQueryFactory ruleQueryFactory = new RuleQueryFactory(dbTester.getDbClient(), wsSupport); + private RuleIndexer ruleIndexer = new RuleIndexer(es.client(), db.getDbClient()); + private ActiveRuleIndexer activeRuleIndexer = new ActiveRuleIndexer(db.getDbClient(), es.client(), new ActiveRuleIteratorFactory(db.getDbClient())); + private Languages languages = LanguageTesting.newLanguages(JAVA, "js"); + private ActiveRuleCompleter activeRuleCompleter = new ActiveRuleCompleter(db.getDbClient(), languages); + private RuleWsSupport wsSupport = new RuleWsSupport(db.getDbClient(), userSession, defaultOrganizationProvider); + private RuleQueryFactory ruleQueryFactory = new RuleQueryFactory(db.getDbClient(), wsSupport); private MacroInterpreter macroInterpreter = mock(MacroInterpreter.class); private RuleMapper ruleMapper = new RuleMapper(languages, macroInterpreter); - private SearchAction underTest = new SearchAction(ruleIndex, activeRuleCompleter, ruleQueryFactory, dbTester.getDbClient(), ruleMapper); + private SearchAction underTest = new SearchAction(ruleIndex, activeRuleCompleter, ruleQueryFactory, db.getDbClient(), ruleMapper); - private RuleActivatorContextFactory contextFactory = new RuleActivatorContextFactory(dbTester.getDbClient()); + private RuleActivatorContextFactory contextFactory = new RuleActivatorContextFactory(db.getDbClient()); private TypeValidations typeValidations = new TypeValidations(asList(new StringTypeValidation(), new IntegerTypeValidation())); - private RuleActivator ruleActivator = new RuleActivator(system2, dbTester.getDbClient(), ruleIndex, contextFactory, typeValidations, activeRuleIndexer, + private RuleActivator ruleActivator = new RuleActivator(system2, db.getDbClient(), ruleIndex, contextFactory, typeValidations, activeRuleIndexer, userSession); private WsActionTester ws = new WsActionTester(underTest); @@ -122,10 +128,19 @@ public class SearchActionTest { @Test public void test_definition() { - assertThat(ws.getDef().isPost()).isFalse(); - assertThat(ws.getDef().since()).isEqualTo("4.4"); - assertThat(ws.getDef().isInternal()).isFalse(); - assertThat(ws.getDef().params()).hasSize(22); + WebService.Action def = ws.getDef(); + + assertThat(def.isPost()).isFalse(); + assertThat(def.since()).isEqualTo("4.4"); + assertThat(def.isInternal()).isFalse(); + assertThat(def.responseExampleAsString()).isNotEmpty(); + assertThat(def.params()).hasSize(23); + + WebService.Param compareToProfile = def.param("compareToProfile"); + assertThat(compareToProfile.since()).isEqualTo("6.5"); + assertThat(compareToProfile.isRequired()).isFalse(); + assertThat(compareToProfile.isInternal()).isTrue(); + assertThat(compareToProfile.description()).isEqualTo("Quality profile key to filter rules that are activated. Meant to compare easily to profile set in 'qprofile'"); } @Test @@ -208,7 +223,7 @@ public class SearchActionTest { @Test public void should_filter_on_organization_specific_tags() throws IOException { - OrganizationDto organization = dbTester.organizations().insert(); + OrganizationDto organization = db.organizations().insert(); RuleDefinitionDto rule1 = createJavaRule(); RuleMetadataDto metadata1 = insertMetadata(organization, rule1, setTags("tag1", "tag2")); RuleDefinitionDto rule2 = createJavaRule(); @@ -244,8 +259,8 @@ public class SearchActionTest { @Test public void should_list_tags_in_tags_facet() throws IOException { - OrganizationDto organization = dbTester.organizations().insert(); - RuleDefinitionDto rule = dbTester.rules().insert(setSystemTags("tag1", "tag3", "tag5", "tag7", "tag9", "x")); + OrganizationDto organization = db.organizations().insert(); + RuleDefinitionDto rule = db.rules().insert(setSystemTags("tag1", "tag3", "tag5", "tag7", "tag9", "x")); RuleMetadataDto metadata = insertMetadata(organization, rule, setTags("tag2", "tag4", "tag6", "tag8", "tagA")); indexRules(); @@ -260,7 +275,7 @@ public class SearchActionTest { @Test public void should_include_selected_matching_tag_in_facet() throws IOException { - RuleDefinitionDto rule = dbTester.rules().insert(setSystemTags("tag1", "tag2", "tag3", "tag4", "tag5", "tag6", "tag7", "tag8", "tag9", "tagA", "x")); + RuleDefinitionDto rule = db.rules().insert(setSystemTags("tag1", "tag2", "tag3", "tag4", "tag5", "tag6", "tag7", "tag8", "tag9", "tagA", "x")); indexRules(); SearchResponse result = ws.newRequest() @@ -272,7 +287,7 @@ public class SearchActionTest { @Test public void should_included_selected_non_matching_tag_in_facet() throws IOException { - RuleDefinitionDto rule = dbTester.rules().insert(setSystemTags("tag1", "tag2", "tag3", "tag4", "tag5", "tag6", "tag7", "tag8", "tag9", "tagA")); + RuleDefinitionDto rule = db.rules().insert(setSystemTags("tag1", "tag2", "tag3", "tag4", "tag5", "tag6", "tag7", "tag8", "tag9", "tagA")); indexRules(); SearchResponse result = ws.newRequest() @@ -284,7 +299,7 @@ public class SearchActionTest { @Test public void should_return_organization_specific_tags() throws IOException { - OrganizationDto organization = dbTester.organizations().insert(); + OrganizationDto organization = db.organizations().insert(); RuleDefinitionDto rule = createJavaRule(); RuleMetadataDto metadata = insertMetadata(organization, rule, setTags("tag1", "tag2")); indexRules(); @@ -344,9 +359,9 @@ public class SearchActionTest { @Test public void return_lang_key_field_when_language_name_is_not_available() throws Exception { - OrganizationDto organization = dbTester.organizations().insert(); + OrganizationDto organization = db.organizations().insert(); String unknownLanguage = "unknown_" + randomAlphanumeric(5); - RuleDefinitionDto rule = dbTester.rules().insert(r -> r.setLanguage(unknownLanguage)); + RuleDefinitionDto rule = db.rules().insert(r -> r.setLanguage(unknownLanguage)); indexRules(); @@ -364,13 +379,13 @@ public class SearchActionTest { @Test public void search_debt_rules_with_default_and_overridden_debt_values() throws Exception { - RuleDefinitionDto rule = dbTester.rules().insert(r -> + RuleDefinitionDto rule = db.rules().insert(r -> r.setLanguage("java") .setDefRemediationFunction(DebtRemediationFunction.Type.LINEAR_OFFSET.name()) .setDefRemediationGapMultiplier("1h") .setDefRemediationBaseEffort("15min")); - RuleMetadataDto metadata = insertMetadata(dbTester.getDefaultOrganization(), rule, + RuleMetadataDto metadata = insertMetadata(db.getDefaultOrganization(), rule, r -> r.setRemediationFunction(DebtRemediationFunction.Type.LINEAR_OFFSET.name()) .setRemediationGapMultiplier("2h") .setRemediationBaseEffort("25min")); @@ -399,13 +414,13 @@ public class SearchActionTest { @Test public void search_debt_rules_with_default_linear_offset_and_overridden_constant_debt() throws Exception { - RuleDefinitionDto rule = dbTester.rules().insert(r -> + RuleDefinitionDto rule = db.rules().insert(r -> r.setLanguage("java") .setDefRemediationFunction(DebtRemediationFunction.Type.LINEAR_OFFSET.name()) .setDefRemediationGapMultiplier("1h") .setDefRemediationBaseEffort("15min")); - RuleMetadataDto metadata = insertMetadata(dbTester.getDefaultOrganization(), rule, + RuleMetadataDto metadata = insertMetadata(db.getDefaultOrganization(), rule, r -> r.setRemediationFunction(DebtRemediationFunction.Type.CONSTANT_ISSUE.name()) .setRemediationGapMultiplier(null) .setRemediationBaseEffort("5min")); @@ -435,13 +450,13 @@ public class SearchActionTest { @Test public void search_debt_rules_with_default_linear_offset_and_overridden_linear_debt() throws Exception { - RuleDefinitionDto rule = dbTester.rules().insert(r -> + RuleDefinitionDto rule = db.rules().insert(r -> r.setLanguage("java") .setDefRemediationFunction(DebtRemediationFunction.Type.LINEAR_OFFSET.name()) .setDefRemediationGapMultiplier("1h") .setDefRemediationBaseEffort("15min")); - RuleMetadataDto metadata = insertMetadata(dbTester.getDefaultOrganization(), rule, + RuleMetadataDto metadata = insertMetadata(db.getDefaultOrganization(), rule, r -> r.setRemediationFunction(DebtRemediationFunction.Type.LINEAR.name()) .setRemediationGapMultiplier("1h") .setRemediationBaseEffort(null)); @@ -470,10 +485,10 @@ public class SearchActionTest { @Test public void search_template_rules() throws Exception { - RuleDefinitionDto templateRule = dbTester.rules().insert(r -> + RuleDefinitionDto templateRule = db.rules().insert(r -> r.setLanguage("java") .setIsTemplate(true)); - RuleDefinitionDto rule = dbTester.rules().insert(r -> + RuleDefinitionDto rule = db.rules().insert(r -> r.setLanguage("java") .setTemplateId(templateRule.getId())); @@ -494,10 +509,10 @@ public class SearchActionTest { @Test public void search_custom_rules_from_template_key() throws Exception { - RuleDefinitionDto templateRule = dbTester.rules().insert(r -> + RuleDefinitionDto templateRule = db.rules().insert(r -> r.setLanguage("java") .setIsTemplate(true)); - RuleDefinitionDto rule = dbTester.rules().insert(r -> + RuleDefinitionDto rule = db.rules().insert(r -> r.setLanguage("java") .setTemplateId(templateRule.getId())); @@ -518,11 +533,11 @@ public class SearchActionTest { @Test public void search_all_active_rules() throws Exception { - OrganizationDto organization = dbTester.organizations().insert(); - QProfileDto profile = dbTester.qualityProfiles().insert(organization, p -> p.setLanguage("java")); + OrganizationDto organization = db.organizations().insert(); + QProfileDto profile = db.qualityProfiles().insert(organization, p -> p.setLanguage("java")); RuleDefinitionDto rule = createJavaRule(); RuleActivation activation = RuleActivation.create(rule.getKey(), BLOCKER, null); - ruleActivator.activate(dbTester.getSession(), activation, profile); + ruleActivator.activate(db.getSession(), activation, profile); indexRules(); @@ -543,38 +558,38 @@ public class SearchActionTest { @Test public void search_profile_active_rules() throws Exception { - OrganizationDto organization = dbTester.organizations().insert(); - QProfileDto profile = dbTester.qualityProfiles().insert(organization, p -> p.setLanguage("java")); - QProfileDto waterproofProfile = dbTester.qualityProfiles().insert(organization, p -> p.setLanguage("java")); + OrganizationDto organization = db.organizations().insert(); + QProfileDto profile = db.qualityProfiles().insert(organization, p -> p.setLanguage("java")); + QProfileDto waterproofProfile = db.qualityProfiles().insert(organization, p -> p.setLanguage("java")); RuleDefinitionDto rule = createJavaRule(); - RuleParamDto ruleParam1 = dbTester.rules().insertRuleParam(rule, p -> + RuleParamDto ruleParam1 = db.rules().insertRuleParam(rule, p -> p.setDefaultValue("some value") .setType("STRING") .setDescription("My small description") .setName("my_var")); - RuleParamDto ruleParam2 = dbTester.rules().insertRuleParam(rule, p -> + RuleParamDto ruleParam2 = db.rules().insertRuleParam(rule, p -> p.setDefaultValue("1") .setType("INTEGER") .setDescription("My small description") .setName("the_var")); // SONAR-7083 - RuleParamDto ruleParam3 = dbTester.rules().insertRuleParam(rule, p -> + RuleParamDto ruleParam3 = db.rules().insertRuleParam(rule, p -> p.setDefaultValue(null) .setType("STRING") .setDescription("Empty Param") .setName("empty_var")); RuleActivation activation = RuleActivation.create(rule.getKey()); - List activeRuleChanges1 = ruleActivator.activate(dbTester.getSession(), activation, profile); - ruleActivator.activate(dbTester.getSession(), activation, waterproofProfile); + List activeRuleChanges1 = ruleActivator.activate(db.getSession(), activation, profile); + ruleActivator.activate(db.getSession(), activation, waterproofProfile); assertThat(activeRuleChanges1).hasSize(1); - dbTester.commit(); + db.commit(); indexRules(); indexActiveRules(); @@ -602,8 +617,8 @@ public class SearchActionTest { ); String unknownProfile = "unknown_profile" + randomAlphanumeric(5); - thrown.expect(NotFoundException.class); - thrown.expectMessage("The specified qualityProfile '" + unknownProfile + "' does not exist"); + expectedException.expect(NotFoundException.class); + expectedException.expectMessage("The specified qualityProfile '" + unknownProfile + "' does not exist"); ws.newRequest() .setParam("activation", "true") @@ -612,26 +627,26 @@ public class SearchActionTest { } @Test - public void test_SONAR7083() { - OrganizationDto organization = dbTester.organizations().insert(); - QProfileDto profile = dbTester.qualityProfiles().insert(organization, p -> p.setLanguage("java")); + public void search_for_active_rules_when_parameter_value_is_null() { + OrganizationDto organization = db.organizations().insert(); + QProfileDto profile = db.qualityProfiles().insert(organization, p -> p.setLanguage("java")); RuleDefinitionDto rule = createJavaRule(); - RuleParamDto ruleParam = dbTester.rules().insertRuleParam(rule, p -> + RuleParamDto ruleParam = db.rules().insertRuleParam(rule, p -> p.setDefaultValue("some value") .setType("STRING") .setDescription("My small description") .setName("my_var")); RuleActivation activation = RuleActivation.create(rule.getKey()); - List activeRuleChanges = ruleActivator.activate(dbTester.getSession(), activation, profile); + List activeRuleChanges = ruleActivator.activate(db.getSession(), activation, profile); // Insert directly in database a rule parameter with a null value ActiveRuleParamDto activeRuleParam = ActiveRuleParamDto.createFor(ruleParam).setValue(null); - dbTester.getDbClient().activeRuleDao().insertParam(dbTester.getSession(), activeRuleChanges.get(0).getActiveRule(), activeRuleParam); + db.getDbClient().activeRuleDao().insertParam(db.getSession(), activeRuleChanges.get(0).getActiveRule(), activeRuleParam); - dbTester.commit(); + db.commit(); indexRules(); indexActiveRules(); @@ -660,9 +675,9 @@ public class SearchActionTest { @Test public void statuses_facet_should_be_sticky() throws Exception { - RuleDefinitionDto rule1 = dbTester.rules().insert(r -> r.setLanguage("java")); - RuleDefinitionDto rule2 = dbTester.rules().insert(r -> r.setLanguage("java").setStatus(RuleStatus.BETA)); - RuleDefinitionDto rule3 = dbTester.rules().insert(r -> r.setLanguage("java").setStatus(RuleStatus.DEPRECATED)); + RuleDefinitionDto rule1 = db.rules().insert(r -> r.setLanguage("java")); + RuleDefinitionDto rule2 = db.rules().insert(r -> r.setLanguage("java").setStatus(RuleStatus.BETA)); + RuleDefinitionDto rule3 = db.rules().insert(r -> r.setLanguage("java").setStatus(RuleStatus.DEPRECATED)); indexRules(); @@ -679,6 +694,39 @@ public class SearchActionTest { ); } + @Test + public void compare_to_another_profile() throws Exception { + OrganizationDto organization = db.organizations().insert(); + QProfileDto profile = db.qualityProfiles().insert(organization, p -> p.setLanguage(JAVA)); + QProfileDto anotherProfile = db.qualityProfiles().insert(organization, p -> p.setLanguage(JAVA)); + RuleDefinitionDto commonRule = db.rules().insertRule(r -> r.setLanguage(JAVA)).getDefinition(); + RuleDefinitionDto profileRule1 = db.rules().insertRule(r -> r.setLanguage(JAVA)).getDefinition(); + RuleDefinitionDto profileRule2 = db.rules().insertRule(r -> r.setLanguage(JAVA)).getDefinition(); + RuleDefinitionDto profileRule3 = db.rules().insertRule(r -> r.setLanguage(JAVA)).getDefinition(); + RuleDefinitionDto anotherProfileRule1 = db.rules().insertRule(r -> r.setLanguage(JAVA)).getDefinition(); + RuleDefinitionDto anotherProfileRule2 = db.rules().insertRule(r -> r.setLanguage(JAVA)).getDefinition(); + db.qualityProfiles().activateRule(profile, commonRule); + db.qualityProfiles().activateRule(profile, profileRule1); + db.qualityProfiles().activateRule(profile, profileRule2); + db.qualityProfiles().activateRule(profile, profileRule3); + db.qualityProfiles().activateRule(anotherProfile, commonRule); + db.qualityProfiles().activateRule(anotherProfile, anotherProfileRule1); + db.qualityProfiles().activateRule(anotherProfile, anotherProfileRule2); + indexRules(); + indexActiveRules(); + + SearchResponse result = ws.newRequest() + .setParam(PARAM_ORGANIZATION, organization.getKey()) + .setParam(PARAM_QPROFILE, profile.getKee()) + .setParam(PARAM_ACTIVATION, "false") + .setParam(PARAM_COMPARE_TO_PROFILE, anotherProfile.getKee()) + .executeProtobuf(SearchResponse.class); + + assertThat(result.getRulesList()) + .extracting(Rule::getKey) + .containsExactlyInAnyOrder(anotherProfileRule1.getKey().toString(), anotherProfileRule2.getKey().toString()); + } + @SafeVarargs private final void checkField(RuleDefinitionDto rule, String fieldName, Extractor responseExtractor, T... expected) throws IOException { SearchResponse result = ws.newRequest() @@ -690,7 +738,7 @@ public class SearchActionTest { @SafeVarargs private final RuleMetadataDto insertMetadata(OrganizationDto organization, RuleDefinitionDto rule, Consumer... populaters) { - RuleMetadataDto metadata = dbTester.rules().insertOrUpdateMetadata(rule, organization, populaters); + RuleMetadataDto metadata = db.rules().insertOrUpdateMetadata(rule, organization, populaters); ruleIndexer.indexRuleExtension(organization, rule.getKey()); return metadata; } @@ -723,6 +771,6 @@ public class SearchActionTest { } private RuleDefinitionDto createJavaRule() { - return dbTester.rules().insert(r -> r.setLanguage("java")); + return db.rules().insert(r -> r.setLanguage("java")); } } diff --git a/sonar-ws/src/main/java/org/sonarqube/ws/client/rule/RulesWsParameters.java b/sonar-ws/src/main/java/org/sonarqube/ws/client/rule/RulesWsParameters.java index 0024d620430..0cbe4dc7e70 100644 --- a/sonar-ws/src/main/java/org/sonarqube/ws/client/rule/RulesWsParameters.java +++ b/sonar-ws/src/main/java/org/sonarqube/ws/client/rule/RulesWsParameters.java @@ -38,6 +38,7 @@ public class RulesWsParameters { public static final String PARAM_IS_TEMPLATE = "is_template"; public static final String PARAM_TEMPLATE_KEY = "template_key"; public static final String PARAM_ORGANIZATION = "organization"; + public static final String PARAM_COMPARE_TO_PROFILE = "compare_to_profile"; public static final String FIELD_REPO = "repo"; public static final String FIELD_NAME = "name";