Automating Salesforce Code Analysis with PMD Using CI/CD Pipelines

As Salesforce developers, maintaining high-quality code is crucial for ensuring robust and efficient applications. One way to achieve this is by integrating static code analysis tools into your development process. In this blog post, we’ll explore how to set up a CI/CD pipeline to automate Salesforce code analysis using PMD, a popular static code analysis tool.

What is PMD?

PMD is an open-source static code analyzer that finds common programming flaws such as unused variables, empty catch blocks, unnecessary object creation, and more. By incorporating PMD into your Salesforce development workflow, you can catch potential issues early and improve the overall quality of your code.

Why Use a CI/CD Pipeline?

A Continuous Integration and Continuous Deployment (CI/CD) pipeline automates the process of integrating code changes, running tests, and deploying applications. By integrating PMD into a CI/CD pipeline, you ensure that code analysis is performed consistently and automatically with every code change. This helps in maintaining code quality and preventing issues from reaching production.

Setting Up the CI/CD Pipeline

We’ll use Bitbucket Pipelines for this example, but the concepts can be applied to other CI/CD tools like Jenkins, GitHub Actions, or GitLab CI.

Step 1: Define the Pipeline Configuration

Create a bitbucket-pipelines.yml file in the root of your Salesforce project repository. This file defines the pipeline configuration. Below is the configuration we will use:

image:
  name: salesforce/salesforcedx:latest-full

pipelines:
  pull-requests:
    '**':
      - step:
          name: Salesforce Code Analyzer with PMD
          caches:
            - node
          script:
            - sfdx scanner:run --format html --target force-app --engine "pmd" --pmdconfig=config/pmd-rules.xml --outfile pmd-report.html
          artifacts:
            - pmd-report.html

Breakdown of the Configuration

  • Image: Specifies the Docker image to use. In this case, we use salesforce/salesforcedx:latest-full, which includes the Salesforce DX CLI and other necessary tools.
  • Pipelines: Defines the pipeline stages. Here, we configure the pipeline to run for all pull requests ('**').
  • Step: Describes the individual step in the pipeline:
  • name: The name of the step, “Salesforce Code Analyzer with PMD”.
  • caches: Specifies caching for dependencies to speed up the pipeline. We cache the node environment.
  • script: Lists the commands to execute:
    • sfdx scanner:run --format html --target force-app --engine "pmd" --pmdconfig=config/pmd-rules.xml --outfile pmd-report.html: This command runs the PMD analysis using Salesforce’s scanner plugin. It targets the force-app directory, uses PMD as the engine, specifies a custom PMD rules configuration file (config/pmd-rules.xml), and outputs the results to pmd-report.html.
  • artifacts: Specifies the files to be preserved after the pipeline runs. In this case, we keep pmd-report.html so that we can review the analysis results.

Step 2: Configure PMD Rules

Create a PMD rules configuration file (pmd-rules.xml) in your project. This file defines the rules that PMD will use to analyze your code. Here’s a simple example:

<?xml version="1.0" encoding="UTF-8"?>
<ruleset name="Apex Rules"
         xmlns="http://pmd.sourceforge.net/ruleset/2.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0
                             http://pmd.sourceforge.net/ruleset_2_0_0.xsd">
    <description>Custom PMD rules for Apex</description>
	
	<!-- Best Practices -->
    <rule ref="category/apex/bestpractices.xml/ApexUnitTestClassShouldHaveRunAs" />
	<rule ref="category/apex/bestpractices.xml/ApexUnitTestClassShouldHaveAsserts" />
	<rule ref="category/apex/bestpractices.xml/ApexUnitTestShouldNotUseSeeAllDataTrue" />
	<rule ref="category/apex/bestpractices.xml/AvoidGlobalModifier" />
	<rule ref="category/apex/bestpractices.xml/AvoidLogicInTrigger" />
	<rule ref="category/apex/bestpractices.xml/UnusedLocalVariable" />
	
	<!-- Code Styles -->
	<rule ref="category/apex/codestyle.xml/ClassNamingConventions">
		<properties>
			<property name="testClassPattern" value="[A-Z][a-zA-Z0-9_]*Test" />
			<property name="abstractClassPattern" value="[A-Z][a-zA-Z0-9_]*" />
			<property name="classPattern" value="[A-Z][a-zA-Z0-9_]*" />
			<property name="interfacePattern" value="[A-Z][a-zA-Z0-9_]*" />
			<property name="enumPattern" value="[A-Z][a-zA-Z0-9_]*" />
		</properties>
	</rule>
	<rule ref="category/apex/codestyle.xml/FieldNamingConventions">
		<properties>
			<property name="enumConstantPattern" value="[A-Z][A-Z0-9_]*" />
			<property name="constantPattern" value="[A-Z][A-Z0-9_]*" />
			<property name="finalPattern" value="[a-z][a-zA-Z0-9]*" />
			<property name="staticPattern" value="[a-z][a-zA-Z0-9]*" />
			<property name="instancePattern" value="[a-z][a-zA-Z0-9]*" />
		</properties>
	</rule>
	<rule ref="category/apex/codestyle.xml/ForLoopsMustUseBraces" />
	<rule ref="category/apex/codestyle.xml/IfElseStmtsMustUseBraces" />
	<rule ref="category/apex/codestyle.xml/IfStmtsMustUseBraces" />
	<rule ref="category/apex/codestyle.xml/LocalVariableNamingConventions" />
	<rule ref="category/apex/codestyle.xml/MethodNamingConventions">
		<properties>
			<property name="testPattern" value="validate[A-Z][a-zA-Z0-9]*" />
			<property name="staticPattern" value="[a-z][a-zA-Z0-9]*" />
			<property name="instancePattern" value="[a-z][a-zA-Z0-9]*" />
		</properties>
	</rule>
	<rule ref="category/apex/codestyle.xml/PropertyNamingConventions" />
	<rule ref="category/apex/codestyle.xml/WhileLoopsMustUseBraces" />
	
	<!-- Design -->
	<rule ref="category/apex/design.xml/AvoidDeeplyNestedIfStmts">
		<properties>
			<property name="problemDepth" value="3" />
		</properties>
	</rule>
	<rule ref="category/apex/design.xml/CognitiveComplexity">
		<properties>
			<property name="classReportLevel" value="50" />
			<property name="methodReportLevel" value="15" />
		</properties>
	</rule>
	<rule ref="category/apex/design.xml/CyclomaticComplexity">
		<properties>
			<property name="classReportLevel" value="40" />
			<property name="methodReportLevel" value="10" />
		</properties>
	</rule>
	<rule ref="category/apex/design.xml/ExcessiveClassLength">
		<properties>
			<property name="minimum" value="1000.0" />
		</properties>
	</rule>
	<rule ref="category/apex/design.xml/ExcessiveParameterList">
		<properties>
			<property name="minimum" value="4.0" />
		</properties>
	</rule>
	<rule ref="category/apex/design.xml/ExcessivePublicCount">
		<properties>
			<property name="minimum" value="20.0" />
		</properties>
	</rule>
	<rule ref="category/apex/design.xml/StdCyclomaticComplexity">
		<properties>
			<property name="reportLevel" value="10" />
			<property name="showClassesComplexity" value="true" />
			<property name="showMethodsComplexity" value="true" />
		</properties>
	</rule>
	<rule ref="category/apex/design.xml/TooManyFields">
		<properties>
			<property name="maxfields" value="15" />
		</properties>
	</rule>
	
	<!-- Documentation -->
	<rule ref="category/apex/documentation.xml/ApexDoc">
		<properties>
			<property name="reportPrivate" value="false" />
			<property name="reportProtected" value="false" />
			<property name="reportMissingDescription" value="true" />
			<property name="reportProperty" value="true" />
		</properties>
	</rule>
	
	<!-- Error Prone -->
	<rule ref="category/apex/errorprone.xml/AvoidDirectAccessTriggerMap" />
	<rule ref="category/apex/errorprone.xml/AvoidHardcodingId" />
	<rule ref="category/apex/errorprone.xml/AvoidNonExistentAnnotations" />
	<rule ref="category/apex/errorprone.xml/EmptyCatchBlock" />
	<rule ref="category/apex/errorprone.xml/EmptyIfStmt" />
	<rule ref="category/apex/errorprone.xml/EmptyStatementBlock" />
	<rule ref="category/apex/errorprone.xml/EmptyTryOrFinallyBlock" />
	<rule ref="category/apex/errorprone.xml/EmptyWhileStmt" />
	<rule ref="category/apex/errorprone.xml/InaccessibleAuraEnabledGetter" />
	<rule ref="category/apex/errorprone.xml/MethodWithSameNameAsEnclosingClass" />
	<rule ref="category/apex/errorprone.xml/TestMethodsMustBeInTestClasses" />
	
	<!-- Performance -->
	<rule ref="category/apex/performance.xml/AvoidDebugStatements" />
	<rule ref="category/apex/performance.xml/AvoidDmlStatementsInLoops" />
	<rule ref="category/apex/performance.xml/AvoidSoqlInLoops" />
	<rule ref="category/apex/performance.xml/AvoidSoslInLoops" />
	<rule ref="category/apex/performance.xml/EagerlyLoadedDescribeSObjectResult" />
	<rule ref="category/apex/performance.xml/OperationWithLimitsInLoop" />
	
	<!-- Security -->
	<rule ref="category/apex/security.xml/ApexBadCrypto" />
	<rule ref="category/apex/security.xml/ApexCRUDViolation" />
	<rule ref="category/apex/security.xml/ApexDangerousMethods" />
	<rule ref="category/apex/security.xml/ApexInsecureEndpoint" />
	<rule ref="category/apex/security.xml/ApexSharingViolations" />
	<rule ref="category/apex/security.xml/ApexSOQLInjection" />
	<rule ref="category/apex/security.xml/ApexSuggestUsingNamedCred" />
	<rule ref="category/apex/security.xml/ApexXSSFromEscapeFalse" />
	<rule ref="category/apex/security.xml/ApexXSSFromURLParam" />
	
	<!-- VF Security -->
	<rule ref="category/vf/security.xml/VfUnescapeEl" />
</ruleset>

you can add/remove rules as per your requirement.

Step 3: Commit and Push Your Changes

Once you’ve configured your pipeline and PMD rules, commit the bitbucket-pipelines.yml and pmd-rules.xml files to your repository:

git add bitbucket-pipelines.yml config/pmd-rules.xml
git commit -m "Add CI/CD pipeline configuration for PMD code analysis"
git push origin your-branch

Step 4: Review the Analysis Results

When you create a pull request, the pipeline will automatically run the PMD analysis. Once the pipeline completes, you can download and review the pmd-report.html file to see the analysis results.

Conclusion

Integrating PMD into your CI/CD pipeline is a powerful way to ensure consistent code quality in your Salesforce projects. By automating static code analysis, you can catch potential issues early and maintain a high standard of code across your development team. Give this setup a try and experience the benefits of automated code quality checks in your Salesforce development workflow.

Demo

Published by Sandeep Kumar

He is a Salesforce Certified Application Architect having 11+ years of experience in Salesforce.

Leave a Reply