Sunday 11 October 2020

Salesforce CLI Scanner Custom XPath Rules - Part 2


Introduction

In Part 1 of this post I showed how to create an XPath expression that looks for SOQL queries outside of test method or accessor classes via the PMD designer. This in itself was good fun, and there's a definite rush when you finally come up with the appropriate runes, but is of limited value. In this post I'll embed the expression in a custom rule and add it to the Salesforce CLI Scanner.

Rule

A rule needs to be inside a ruleset XML file. The PMD site has excellent documentation on this, so I won't reproduce it here for the sake of padding my post out.

A couple of key aspects of my rule is that it is an XPath expression to evaluate against Apex coded, so I need to add that detail to the definition so that the appropriate class can be instantiated to handle it:

<rule name="SoqlOnlyInTestAndAccessorClasses" 
      language="apex" 
      message="SOQL queries can only appear in test methods and accessor classes"
      class="net.sourceforge.pmd.lang.apex.rule.ApexXPathRule">

and the XPath expression itself appears in the rule properties, along with the XPath version:

<properties>
  <property name="version" value="2.0"/>
  <property name="xpath">
    <value>
      <![CDATA[
//UserClass[not(ends-with(@Image, 'Accessor'))]/Method/ModifierNode[@Test=false()]/..//
(SoqlExpression | MethodCallExpression[lower-case(@FullMethodName)='database.query'])
]]>
    </value>
  </property>
</properties>

Packaging the Rule

The ruleset xml needs to be packaged as a JAR file (Java ARchive) using the tool of the same name. If I'd written a custom Java rule, I'd need to compile the class and include that in the JAR, but as I'm relying on a pre-existing class (net.sourceforge.pmd.lang.apex.rule.ApexXPathRule) I can just package the ruleset xml file.

Per the official Authoring Custom Rules documentation, my ruleset file lives at:

    ./category/apex/bobbuzzard.xml

so to create my JAR I execute the following command, which gives me plenty of information about what is being added:

$ jar -cvf bobbuzzard.jar ./category

added manifest
adding: category/(in = 0) (out= 0)(stored 0%)
adding: category/apex/(in = 0) (out= 0)(stored 0%)
adding: category/apex/bobbuzzard.xml(in = 1105) (out= 545)(deflated 50%)

Installing the Rule

Once I've packaged the rule, I can install it using the scanner:rule:add Salesforce CLI command, specifying the language as Apex and the JAR I just created:

$ sfdx scanner:rule:add -l apex -p bobbuzzard.jar 
Successfully added rules for apex.
1 Path(s) added: /Users/kbowden/PMDRULE/bobbuzzard.jar

Per the official docs, I list the commands to ensure it went in correctly (when I was figuring all this out but didn't have my ruleset.xml file set up right, the command would succeed but the rule wouldn't be added, so verifying via the list command is definitely a step worth taking). So that I don't have to wade through a tone of output, I pipe it through grep to exclude any lines that don't contain the characters Buzzard :

$ sfdx scanner:rule:list | grep Buzzard
SoqlOnlyInTestAndAccessorClasses          apex         Bob Buzzard     pmd

so all is well - my rule is all present and correct.

Running the Rule

All that is left is to run my rule against some sample Apex classes. The classes, and the ruleset and JAR re available at the Github repository.

I have:

  • ScannerExample.cls - this has a SOQL expression and a Database.query method call, so I expect it to be flagged twice.
  • ScannerExampleAccessor.cls - as above, but as it's a class name ending in Accessor I don't expect it to be flagged.
  • ScannerExampleTest.cls - a test class with one SOQL expression in a test method and one in a regular method. I expect it to be flagged once regarding the regular method
When running the scanner, I set the category to "Bob Buzzard" as I only want the code evaluated against my rule:
$ sfdx scanner:run --target './**/*.cls' -c "Bob Buzzard"
LOCATION                                                  DESCRIPTION                                          CATEGORY     U R L
────────────────────────────────────────────────────────  ───────────────────────────────────────────────────  ───────────  ─────
force-app/main/default/classes/ScannerExample.cls:5         SOQL queries can only appear in test methods and   Bob Buzzard
                                                            accessor classes
force-app/main/default/classes/ScannerExample.cls:10        SOQL queries can only appear in test methods and   Bob Buzzard
                                                            accessor classes
force-app/main/default/classes/ScannerExampleTest.cls:19    SOQL queries can only appear in test methods and   Bob Buzzard
                                                            accessor classes
and there we have it - the lines that break my rule have been identified.

Resources

No comments:

Post a Comment