Skip to main content
SFDC Developers
DevOps

Salesforce Code Quality: Automated PR Comments

Vinay Vernekar · · 6 min read

As Salesforce organizations scale, managing technical debt and security risks across Apex, Lightning Web Components (LWCs), and extensive metadata becomes increasingly challenging through manual peer reviews alone. Static code analysis (SCA) is a critical component of modern DevOps workflows, enabling automated quality gates. By integrating tools like Salesforce Code Analyzer, PMD, and ESLint into CI/CD pipelines (e.g., Azure DevOps), teams can adopt a "shift-left" strategy. Generating automated pull request (PR) comments for these analyses provides immediate, actionable feedback, ensuring code adherence to standards before merging.

Understanding the Tooling Landscape

Effective static analysis requires engines that can parse Salesforce-specific languages. PMD has historically been the standard for Apex analysis, identifying patterns like SOQL queries within loops and high cyclomatic complexity. However, the modern Salesforce development stack includes LWCs and Aura components, necessitating broader analysis capabilities.

Salesforce Code Analyzer is the official, Salesforce-supported tool that unifies multiple analysis engines. It integrates PMD for Apex with ESLint for LWCs and Aura, offering a single interface for your project. This simplifies version management and ensures rule sets align with Salesforce best practices.

Note: This guide assumes familiarity with Azure DevOps. For an introduction, refer to Azure DevOps Pipelines documentation.

The true value of SCA lies in delivering feedback efficiently. Injecting findings as line-level comments directly into PRs eliminates the need for developers to sift through build logs. Coupled with branch policies, this ensures only compliant code is merged, promoting faster, safer, and more maintainable development.

Repository Structure

An Azure DevOps pipeline for Salesforce typically relies on a azure-pipelines.yml file at the project root. A common structure includes:

my-salesforce-project/
├── force-app/
│   └── main/
│       └── default/
│           ├── classes/
│           ├── triggers/
│           └── lwc/
├── azure-pipelines.yml
└── pmd-rules/ (optional)
    └── apex-ruleset.xml

Azure DevOps Configuration

To implement automated PR comments, configure your Azure DevOps pipeline as follows:

Enable System Access Token

In Azure DevOps, navigate to Pipeline Settings and check Allow scripts to access the OAuth token. Save the settings.

Set Branch Policies

Navigate to Repos → Branches. Select your main branch, then Branch policies. Add a Build validation requirement and select your static analysis pipeline.

Agent Requirements

Choose an appropriate agent pool. For Microsoft-hosted builds, ubuntu-latest is recommended.

Ruleset Definition

Define custom ruleset XML files (e.g., apex-ruleset.xml) to specify the PMD rules to be applied for your project.

Violation Thresholds

Establish objective "Pass/Fail" criteria. For instance, fail the build on any critical security issue but allow a limited number of minor warnings.

Artifact Storage Strategy

Configure the pipeline to publish scan reports (XML, JSON, or HTML) as artifacts for auditing and review.

Organizational Prerequisites

Before technical pipeline implementation, ensure the following governance foundations are in place:

  • Documented Coding Standards: Align scanner configurations with documented internal policies for security and maintainability.
  • Defined Violation Thresholds: Set clear "pass/fail" criteria (e.g., critical security violations fail the build).
  • Repository Access Control: The pipeline's service identity requires "Contribute to pull requests" permissions in Azure Repos to create automated threads.
  • Ruleset Definition: Maintain version-controlled rulesets (e.g., apex-ruleset.xml) for consistency between local development and the CI/CD pipeline.

High-Level Pipeline Flow

The typical execution order for the pipeline steps is as follows:

  1. PR Context Validation: Verifies the pipeline is running within a Pull Request context.
  2. Scanner Execution: Invokes the Salesforce Code Analyzer engine.
  3. Results Processing: Parses the JSON output to count violations.
  4. API Configuration: Dynamically builds the connection to the Azure DevOps API.
  5. Path Normalization: Aligns file paths between the agent and the repository.
  6. Comment Generation: Creates actionable feedback strings for developers.
  7. Guidance Logic: Provides rule-specific "Quick Fix" suggestions.
  8. API Request Construction: Packages comments into the PR diff view.
  9. Error Handling: Manages API rate limits and connection retries.

Code Breakdown and Explanation

The following script snippets illustrate key aspects of the azure-pipelines.yml configuration.

Step 1: PR Context Validation

This step prevents unnecessary analysis by checking for the SYSTEM_PULLREQUEST_PULLREQUESTID environment variable.

if (!$env:SYSTEM_PULLREQUEST_PULLREQUESTID) {
  Write-Host "Not in PR context"
  exit 0
}

Step 2: PMD Scanner Execution

This command executes the Salesforce CLI scanner, targeting the force-app directory and outputting results to a JSON file.

cmd /c "sfdx scanner:run --target force-app --engine pmd --format json --outfile $outputFile 2>&1" | Out-Null

Step 3: Results Processing and Validation

Parses the JSON output to count violations. The -Raw parameter ensures the entire file is read as a single string for conversion to a PowerShell object.

$jsonContent = Get-Content $outputFile -Raw
$results = $jsonContent | ConvertFrom-Json

$totalViolations = 0
foreach ($file in $results) {
  if ($file.violations) {
    $totalViolations += $file.violations.Count
  }
}

Step 4: Azure DevOps API Configuration

Constructs the API endpoint URL using predefined Azure DevOps variables.

$org = $env:SYSTEM_TEAMFOUNDATIONCOLLECTIONURI.TrimEnd('/')
$project = $env:SYSTEM_TEAMPROJECT
$repoId = $env:BUILD_REPOSITORY_ID
$prId = $env:SYSTEM_PULLREQUEST_PULLREQUESTID

$baseUrl = "$org/$project/_apis/git/repositories/$repoId/pullRequests/$prId"

Step 5: File Path Normalization

Normalizes file paths to ensure comments align correctly within the Azure DevOps PR UI.

if ($filePath -match '.*\(force-app\.*)') {
  $filePath = $Matches[1] -replace '\', '/'
} elseif ($filePath -match '.*(force-app\.*)') {
  $filePath = $Matches[1] -replace '\', '/'
} else {
  $workingDir = Get-Location
  $filePath = $filePath -replace [regex]::Escape($workingDir), '' -replace '^\', '' -replace '\', '/'
}

Step 6: Comprehensive Comment Generation

Generates detailed, actionable feedback, including severity and issue descriptions.

$comment = "$severityIcon $cleanRuleName`n`n"
$comment += "File: $filePath`n"
$comment += "Line: $($violation.line)"

if ($violation.column) {
  $comment += ", Column: $($violation.column)"
}
$comment += "`n`n"

$comment += "Issue: $($violation.message.Trim())`n`n"

if ($violation.category) {
  $comment += "Category: $($violation.category)`n"
}

$priorityText = switch ($violation.severity) {
  1 { "Critical - Fix Immediately" }
  2 { "Important - Should Fix" }
  3 { "Suggestion - Consider Fixing" }
  default { "Info" }
}
$comment += "Priority: $priorityText`n"

Step 7: Rule-Specific Fix Suggestions

Provides targeted advice for common violation types.

switch -Wildcard ($cleanRuleName) {
  "*SOQL*" {
    $comment += "- Move SOQL queries outside of loops`n"
    $comment += "- Use bulk operations and collections`n"
    $comment += "- Consider using Map<Id, SObject> for efficient lookups"
  }
  "*Security*" {
    $comment += "- Validate user permissions before SOQL/DML operations`n"
    $comment += "- Use 'WITH SECURITY_ENFORCED' in SOQL queries`n"
    $comment += "- Escape user input to prevent injection attacks"
  }
  # Additional patterns...
}

Step 8: API Request Construction

Constructs the Azure DevOps API request body with thread context for precise commenting.

$commentBody = @{
  comments = @(
    @{ 
      parentCommentId = 0
      content = $comment
      commentType = 1
    }
  )
  threadContext = @{
    filePath = $filePath
    rightFileStart = @{
      line = $startLine
      offset = 1
    }
    rightFileEnd = @{
      line = $endLine
      offset = 1
    }
  }
}

Key Takeaways

  • Automate Salesforce code quality checks using Salesforce Code Analyzer, PMD, and ESLint within a CI/CD pipeline.
  • Inject analysis findings as comments directly into pull requests for immediate developer feedback.
  • Implement a "shift-left" strategy to catch issues early in the development lifecycle.
  • Define clear coding standards, violation thresholds, and ensure proper repository access control.
  • Normalize file paths and construct API requests correctly to ensure comments appear on the right code lines.

Share this article

Get weekly Salesforce dev tutorials in your inbox

Comments

Loading comments...

Leave a Comment

Trending Now