Create new rule in sonarqube

The documentation includes general guidance on adding coding rules, including which languages support adding custom rules, and general guidance on how to go about it.
If the docs don’t answer your question, please tell us:

  • What language are you writing rules for? Java
  • What have you tried, and what’s your challenge / stumbling block

Please share the relevant code snippet, along with any error messages you’re encountering:

I wrote this rule:

But it doesn’t catch this instance, e.g:

image

I have tried making sense of your documentation, but I don’t even know where to look for.

Hi,

What version of SonarQube are you using?

Also, I don’t think the ArgumentTypes input is looking for an argument list. Instead, I think it expects a simple list of possible argument types. In fact, it’s possible this isn’t working for you because it assumes single-arg methods. But I would first try simplifying that ArgumentTypes value.

 
HTH,
Ann

  • Enterprise Edition
  • Version 9.9 (build 65466)

I am sorry, I don’t really comprehend any of the above.

I want a custom rule that bans any JSONAssert.assertEquals(..., false). According to the source JSONassert/src/main/java/org/skyscreamer/jsonassert/JSONAssert.java at 7414e901af11c559bc553e5bb8e12b99a57d1c1c · skyscreamer/JSONassert · GitHub, signature is String expectedStr, JSONObject actual, boolean strict. (edit: okay, now I noticed a small error in my definition, but I fixed it and it’s not working).

java.lang.String, org.json.JSONObject, false is close to what argumentTypes is giving as an example (java.lang.String, int[], int; I assume that means funcA(String foo, int[] bar, int baz))

… ofc false is not a type, but I’d rather not ban true

Hi,

What I’m saying is that this rule template (technically, we don’t consider this a “custom rule”) may not work, or may not work as you expect with methods that accept multiple arguments.

Can you try clearing out the ArgumentTypes field and just setting it to java.lang.String?

 
Ann

Did that. Nothing is happening after force-triggering a current-file analysis with the expected issue

Hi,

It’s quite possible this rule template simply doesn’t work with multi-argument methods. But I’m going to flag it for the language experts.

 
Ann

Hey there,

As you mentioned, the rule template cannot match the desired method invocation because false is not a type. Thus, it will never match the method signature you are interested in.

This rule template is not meant to check the values provided to the method’s parameter, but it only checks if a specific method signature is being invoked at any point.
To do what you want you should try with a custom rule.

Let me know if I can help further

An example would be nice for Sonar-noobs :smile: :sweat_smile:

… Or at least point me “where in documentation” creating a custom rule is “abundantly explained”

As you mentioned, the rule template cannot match the desired method invocation because false is not a type. Thus, it will never match the method signature you are interested in.

I have created that template, but it’s “ghosting me”. It does not complain that it is wrong (or right!), and I don’t know how could I have triggered it. I could try to ban all , boolean invocations of it - but it’s still ignoring me :sweat_smile:

Here you can find the tutorial on how to bootstrap your first custom plugin, with your own rules.

You could implement the rule you described with the AbstractMethodDetection class and the MethodMatchers API to detect the method you want. I suggest you to go through the tutorial to get an idea of how all this works, but your new rule should look something like this:

@Rule(key = "DisallowJSONAssert_assertEquals")
public class DisallowJSONAssert extends AbstractMethodDetection {

  private static final MethodMatchers JSON_ASSERT_MATCHER = MethodMatchers.create()
    .ofTypes("org.skyscreamer.jsonassert.JSONAssert")
    .names("assertEquals")
    .addParametersMatcher("java.lang.String", "java.lang.String", "boolean")
    .build();

  @Override
  protected MethodMatchers getMethodInvocationMatchers() {
    return JSON_ASSERT_MATCHER;
  }

  @Override
  protected void onMethodInvocationFound(MethodInvocationTree mit) {
    //when this method is invoked it means that the rule matched on a method invocation
    //with the signature of JSONAssert.assertEquals(String, String, boolean)
    ExpressionTree booleanArg = mit.arguments().get(2);
    boolean value = Boolean.parseBoolean(((LiteralTree) booleanArg).value());
    //after getting the value of the boolean argument, we can report an issue if it is false
    if(!value){
      reportIssue(ExpressionUtils.methodName(mit), "Remove this forbidden call");
    }
  }

}

I hope this helps

1 Like

Yes :star_struck: . It does :heart_eyes:

1 Like

Whoops, actually you cannot directly use the AbstractMethodDetection as it is not part of the public API, but you can easily look at its implementation to replicate its behavior in a subclass of IssuableSubscriptionVisitor (which is the class you should extend, part of the public API)

1 Like

I cannot get any of


    // .addParametersMatcher("java.lang.String", "java.lang.String", "boolean")
    .addParametersMatcher(params -> params.size() == 3 && params.get(2).is("boolean"))
    // .addParametersMatcher(ANY, ANY, "boolean")

to match my

import org.junit.jupiter.api.Test;
import org.skyscreamer.jsonassert.JSONAssert;

class MyClassTest {
    @Test
    void testJSONAssert() {
        String expected = "{id:1,name:\"John\"}";
        String actual = "{id:1,name:\"John\",extra:\"extra\"}";

        JSONAssert.assertEquals(expected, actual, true);
        JSONAssert.assertEquals(expected, actual, false); // Noncompliant
        JSONAssert.assertEquals(expected, actual, JSONCompareMode.LENIENT);
        JSONAssert.assertEquals(expected, actual, JSONCompareMode.STRICT);
        JSONAssert.assertEquals(expected, actual, JSONCompareMode.NON_EXTENSIBLE);
        JSONAssert.assertEquals(expected, actual, JSONCompareMode.STRICT_ORDER);
    }
}

test.

tl;dr if (this.matchers().matches(mit)) { is never true - and I cannot “further debug” .addParametersMatcher on how to narrow it down.

I cannot even use .withAnyParameters() to “get around it”.

Is also sonar-java/docs/java-custom-rules-example/pom_SQ_9_9_LTS.xml at c4e02a8420be224074c5af0c7d69dc44712a2771 · SonarSource/sonar-java · GitHub

<!-- Make sure to be compatible with java 8 -->

still mandatory? To what extend? 9.9 LTS, or “something else”?

pom’s have differences among them :confused:

Could you share the entire rule implementation you wrote, and the unit test that you are trying to run?

Which implementation?

DisallowJSONAssert is copy-paste from yours, and AbstractMethodDetection does not matter (since the test runs “locally”, and AbstractMethodDetection is available)

If you could share the unit test you’re trying to run I can try reproduce the problem with the code sample you shared.

package sonarqube.language.java.checks;

import org.junit.jupiter.api.Test;
import org.sonar.java.checks.verifier.CheckVerifier;

class BanJSONAssertAssertEqualsStrictFalseTest {

    @Test
    void test() {
        CheckVerifier.newVerifier()
          .onFile("src/test/files/BanJSONAssert_assertEquals_strictFalse.java")
          .withCheck(new BanJSONAssertAssertEqualsFalse())
          .verifyIssues();
    }
}

and

import org.junit.jupiter.api.Test;
import org.skyscreamer.jsonassert.JSONAssert;

class MyClassTest {
    @Test
    void testJSONAssert() {
        String expected = "{\"data\": {\"name\":\"John\"}}";
        String actual = "{\"data\": {\"id\":1, \"name\":\"John\", \"extra\":\"extra\"}, \"errors\": [{\"message\": \"The variables input contains a field name 'id' that is not defined for input object type 'CreateFolderInput'\"}]}";

        JSONAssert.assertEquals(expected, actual, true);
        JSONAssert.assertEquals(expected, actual, false); // Noncompliant
        JSONAssert.assertEquals(expected, actual, JSONCompareMode.LENIENT);
        JSONAssert.assertEquals(expected, actual, JSONCompareMode.STRICT);
        JSONAssert.assertEquals(expected, actual, JSONCompareMode.NON_EXTENSIBLE);
        JSONAssert.assertEquals(expected, actual, JSONCompareMode.STRICT_ORDER);
    }
}

It might be related to the classpath missing the jsonassert library, did you go through this step?

Adding the dependency in this portion of the pom will allow the unit test to have the correct semantic information when running

Oh, oops? :sweat: I did miss that (and those UTs “didn’t mean anything to me”)

… idk - shouldn’t “compilation” lead to a “library missing” error, which should fail the test with such a warning? :confused:

Or is there a “maven target” that would try to compile those files in the context that they are executed (and fail when inadequate)?

I have added

                <artifactItem>
                  <groupId>org.skyscreamer</groupId>
                  <artifactId>jsonassert</artifactId>
                  <version>1.5.1</version>
                  <type>jar</type>
                </artifactItem>

on both poms, and ran mvn clean install -f pom_SQ_9_9_LTS.xml; no luck (neither IDEA test runner works)

So I think if you can confirm that after adding that entry in the pom.xml file you can see the jsonassert-1.5.1.jar file inside target/test-jars folder, now the last step remaining is to specify this classpath in your unit test, like so:

@Test
    void test() {
        CheckVerifier.newVerifier()
          .onFile("src/test/files/BanJSONAssert_assertEquals_strictFalse.java")
          .withCheck(new BanJSONAssertAssertEqualsFalse())
          .withClassPath(FilesUtils.getClassPath("target/test-jars"))
          .verifyIssues();
    }

I realize that this extra step is not mentioned in the docs, and I will create a ticket to improve this.

Let me know if this fixes the issue, on my side it is working like this

2 Likes