Skip to content

Appendix E: Salesforce Code Analyzer - Engine Reference

This appendix provides detailed reference material for the engines bundled with Salesforce Code Analyzer. The architectural positioning of Code Analyzer as one option among several is covered in Section 5; this appendix is a practical reference for teams that have selected Code Analyzer as their static analysis tool.

Official documentation: https://developer.salesforce.com/docs/platform/salesforce-code-analyzer/overview GitHub Action: https://github.com/marketplace/actions/run-salesforce-code-analyzer

Standalone Engine Usage

Several of the engines that Code Analyzer orchestrates also exist as independent, standalone tools with their own CLI, documentation, and community ecosystems. Teams that prefer direct engine control, need to pin to a specific engine version independently of the Code Analyzer release cycle, or need to integrate with tooling that does not support the Code Analyzer CLI can invoke these engines directly. When using standalone engines, output parsing, severity thresholding, SARIF conversion, and PR annotation must be handled by custom workflow steps. The jq command-line JSON processor (pre-installed on GitHub-hosted runners) and xq (installed via pip install yq) are particularly useful for parsing raw engine output.

The standalone-capable engines are:

  • PMD - pmd check | https://docs.pmd-code.org/latest/ | https://github.com/pmd/pmd
  • ESLint - eslint | https://eslint.org/docs/latest/ | https://github.com/eslint/eslint
  • RetireJS - retire (npm package) | https://github.com/RetireJS/retire.js
  • CPD - ships with the PMD distribution, invoked via pmd cpd

The Flow Analyzer and Regex engines are specific to Code Analyzer and do not have standalone equivalents.

Installation and Prerequisites

sf plugins install code-analyzer

Runner prerequisites by engine:

  • Node.js v20 or later - required universally
  • Java v11 or later - required by PMD, CPD, and Salesforce Graph Engine
  • Python v3.10 or later - required by the Flow Scanner engine

Engines with unsatisfied prerequisites should be explicitly disabled in code-analyzer.yml:

engines:
  flow:
    disable_engine: true

The code-analyzer.yml Configuration File

The central configuration artifact for Code Analyzer. Should reside in the root directory of the consuming repository - the default location that Code Analyzer expects without additional configuration. Where project structure requires a non-root location, all invocations must explicitly reference the file path via --config-file.

The command to create and update the config file is:

echo 'y' | sf code-analyzer config \
  --include-unmodified-rules \
  --rule-selector all \
  --config-file code-analyzer.yml \
  --output-file code-analyzer.yml

Examples and configuration options are in the documentation.

PMD - Static Analysis

Evaluates source against rules spanning security, performance, best practices, error-prone patterns, design quality, and code style. Covers Apex, LWC/JavaScript, Visualforce, and XML metadata.

# Via Code Analyzer
sf code-analyzer run --rule-selector pmd
sf code-analyzer run --rule-selector "pmd:(Security,Performance):(1,2)"

# Standalone, after download and installation
pmd check --rulesets pmd-ruleset.xml --dir force-app --format sarif

Custom rules: PMD supports custom rule authoring in XPath and Java. XPath rules are the more accessible option for Salesforce teams - they operate directly on the AST of Apex, Visualforce, and XML metadata files without requiring Java compilation. The full custom ruleset is provided in Appendix F.

Configuration artifact: A versioned pmd-ruleset.xml referenced in code-analyzer.yml. Specific rule recommendations are outside the scope of this whitepaper - the reference ruleset in Appendix F is a starting point.

ESLint - LWC Code Quality

Surfaces LWC-specific rule violations covering JavaScript quality, component API patterns, accessibility requirements, and Salesforce platform conventions.

# Via Code Analyzer
sf code-analyzer run --rule-selector "eslint:(Recommended,LWC)"

# Standalone, after download and installation
eslint "force-app/**/*.js" --format json

Configuration artifact: eslint.config.js using ESLint's flat configuration format - the default format as of ESLint v9. Projects still using .eslintrc must migrate before upgrading to ESLint v10, where the old format is not recognized. ESLint configurations and plugins are beyond the scope of this document, include Salesforce-provided LWC-specific rulesets, Prettier plugins, and Jest plugins.

ESLint documentation: https://eslint.org/docs/latest/ | https://github.com/eslint/eslint

RetireJS - JavaScript Dependency Vulnerability Scanning

Scans LWC and Aura component JavaScript for dependencies with known CVEs.

# Via Code Analyzer
sf code-analyzer run --rule-selector retire-js

# Standalone, after download and installation
npx retire --path force-app --outputformat json

Flow Analyzer - Declarative Governance

Scans Salesforce Flows for anti-patterns, performance issues, and security violations. Requires Python v3.10 or later.

sf code-analyzer run --rule-selector flow

Flow governance is the most commonly neglected dimension of Salesforce static analysis. A Flow with an unbounded loop can exhaust governor limit headroom; an unhandled fault path silently swallows errors; a CRUD/FLS violation bypasses security controls that Apex code is routinely scanned for. Teams not yet ready to address Flow findings should explicitly disable the engine in code-analyzer.yml with a documented re-enablement date.

CPD - Copy-Paste Detection

Identifies duplicated code blocks across Apex classes. New in Code Analyzer.

# Via Code Analyzer
sf code-analyzer run --rule-selector cpd

# Standalone (CPD ships with the PMD distribution)
pmd cpd --minimum-tokens 100 --dir force-app/main/default/classes --format csv

CPD findings are most useful as a scheduled scan metric and refactoring governance signal rather than a hard PR gate. The --minimum-tokens threshold of 100 avoids flagging unavoidable boilerplate while surfacing meaningful duplication.

Regex Engine - Custom Pattern Detection

Allows teams to define custom regular expression rules scanning any file type. Requires no Java or Python prerequisites.

sf code-analyzer run --rule-selector regex

Custom Regex rules are defined in code-analyzer.yml. Use cases include enforcing naming conventions, detecting deprecated API patterns, flagging hardcoded environment-specific strings, or identifying TODO/FIXME comments that should be resolved before merge.

sfge Engine - Salesforce Graph Engine (Developer Preview)

Performs complex analysis on Apex code and identifies security vulnerabilities and code issues using data flow analysis.

sf code-analyzer run --rule-selector sfge

At this time, there is no way to create custom sfge rules.

Running Code Analyzer in GitHub Workflows

Using the GitHub Action

- name: Run Salesforce Code Analyzer
  id: run-code-analyzer
  uses: forcedotcom/run-code-analyzer@v2
  with:
    run-arguments: >
      --workspace .
      --rule-selector "all:(Recommended,Security,Custom)"
      --severity-threshold 2
      --output-file sca.sarif

- name: Upload SARIF to GitHub Code Scanning
  uses: github/codeql-action/upload-sarif@v3
  with:
    sarif_file: sca.sarif

# run-code-analyzer@v2 does not auto-fail the workflow.
# Check the exit-code output and fail explicitly.
- name: Fail if blocking violations found
  if: steps.run-code-analyzer.outputs.exit-code > 0
  run: exit 1

Using the Code Analyzer CLI Directly

- name: Install Salesforce CLI
  run: |
    npm install --global @salesforce/cli
    nodeInstallPath=$(npm config get prefix)
    echo "$nodeInstallPath/bin" >> $GITHUB_PATH

- name: Install Salesforce CLI Code Analyzer plugin
  run: sf plugins install code-analyzer

- name: Run Code Analyzer (JSON output)
  run: |
    sf code-analyzer run \
      --config-file code-analyzer.yml \
      --workspace . \
      --rule-selector "all:(BestPractices,ErrorProne,Security,Performance,Recommended,Custom)" \
      --output-file ./code-analyzer/code-analyzer-results.json

- name: Parse and post PR comment
  run: |
    BLOCKING=$(jq '[.[] | select(.severity <= 2)] | length' code-analyzer/code-analyzer-results.json)
    NON_BLOCKING=$(jq '[.[] | select(.severity > 2)] | length' code-analyzer/code-analyzer-results.json)
    # Construct and post structured PR comment and workflow output
    # from $BLOCKING and $NON_BLOCKING counts and their entries

Delta-Scoped Analysis with sfdx-git-delta

The recommended approach for differential scanning is sfdx-git-delta (GitHub), which generates a clean deployment-ready delta of changed metadata and is more robust than passing a raw list of changed file paths. The full reusable workflow that wraps this step and uploads the result as a GitHub Actions artifact is in Appendix A.2.5.

- name: Install CLI and plugins
  run: |
    npm install --global @salesforce/cli
    mkdir -p ~/.config/sf
    echo '["sfdx-git-delta", "code-analyzer"]' > ~/.config/sf/unsignedPluginAllowList.json
    sf plugins install sfdx-git-delta
    sf plugins install code-analyzer

- name: Generate delta package
  run: |
    sf sgd source delta \
      --from ${{ github.event.pull_request.base.sha }} \
      --to ${{ github.event.pull_request.head.sha }} \
      --output delta \
      --generate-delta

- name: Check for deployable metadata
  id: check-delta
  run: |
    if grep -q '<types>' delta/package/package.xml 2>/dev/null; then
      echo "has-metadata=true" >> $GITHUB_OUTPUT
    else
      echo "---- No changes to analyze ----"
      echo "has-metadata=false" >> $GITHUB_OUTPUT
    fi

- name: Run Code Analyzer on delta only
  if: steps.check-delta.outputs.has-metadata == 'true'
  run: |
    sf code-analyzer run \
      --workspace delta \
      --rule-selector "all:(Recommended,Security)" \
      --severity-threshold 2 \
      --output-file results.json

sfdx-git-delta also generates package.xml and destructiveChanges.xml manifests: the destructive changes manifest feeding into the destructive change detection gate (Section 6), and the package.xml feeding into sf project deploy. The full reusable workflow that consumes the delta artifact and runs deployment validation is in Appendix A.2.11.

Delta-Scoped Apex Test Execution with sfdx-git-delta

- name: Install xq for XML parsing
  run: pip install yq --break-system-packages

- name: Collect changed Apex classes from delta
  id: changed-apex
  run: |
    if grep -q '<types>' delta/package/package.xml 2>/dev/null; then
      CLASSES=$(xq -r '
        .Package.types
        | if type == "array" then . else [.] end
        | map(select(.name == "ApexClass"))
        | .[].members
        | if type == "array" then . else [.] end
        | map(select(test("\\*") | not))
        | .[]
      ' delta/package/package.xml 2>/dev/null | sort -u | tr '\n' ' ' | sed 's/ $//')
      if [ -z "$CLASSES" ]; then
        echo "test-level=RunLocalTests" >> $GITHUB_OUTPUT
        echo "classes=" >> $GITHUB_OUTPUT
      else
        echo "test-level=RunSpecifiedTests" >> $GITHUB_OUTPUT
        echo "classes=$CLASSES" >> $GITHUB_OUTPUT
      fi
    else
      echo "test-level=skip" >> $GITHUB_OUTPUT
      echo "classes=" >> $GITHUB_OUTPUT
    fi

- name: Run tests for changed classes
  if: steps.changed-apex.outputs.test-level == 'RunSpecifiedTests'
  run: |
    sf apex run test \
      --test-level RunSpecifiedTests \
      --class-names ${{ steps.changed-apex.outputs.classes }} \
      --result-format json \
      --code-coverage \
      --output-dir test-results \
      --wait 20

This pattern produces a RunSpecifiedTests execution scoped to the classes changed in the PR, and is most reliable when the project maintains a consistent test class naming convention. The test-results/test-result-codecoverage.json output can be uploaded directly to CodeCov without format conversion - see Appendix A.2.8. The --code-coverage flag is required for the coverage file to be generated; omitting it produces test results without per-class coverage data.

The output from sf apex run test --result-format json is well-suited to jq post-processing for the structured PR comment pattern described in Section 4 - extracting failing test names, per-class coverage percentages, and classes below the organizational coverage threshold.