Skip to content

๐Ÿ”Ž SFDX Code Review โ€‹

This repository implements a unified GitHub composite action for the static code review of Salesforce projects. It bundles the checks that are otherwise scattered across separate CI steps into a single, configurable quality gate:

  • ๐Ÿ›ก๏ธ Salesforce Code Analyzer โ€” static analysis for Apex, LWC, Aura and more (PMD, ESLint, RetireJS, Flow, โ€ฆ)
  • ๐ŸŽจ Prettier โ€” code formatting check
  • ๐Ÿ“ ESLint โ€” JavaScript/LWC code quality, with a native report for SonarQube/SonarCloud

The Salesforce Code Analyzer is enabled by default; Prettier and ESLint are opt-in.

Why all three? The Salesforce Code Analyzer does not check formatting (Prettier is not a static-analysis engine), and while it bundles an ESLint engine, it emits its own unified format rather than the native eslint-report.json that Sonar's sonar.eslint.reportPaths consumes. Running ESLint and Prettier here keeps everything in one gate and produces Sonar-ready reports.

Usage โ€‹

Full code review (analyzer + formatting + linting) โ€‹

yaml
jobs:
  code-review:
    name: Code Review
    runs-on: ubuntu-latest
    permissions:
      contents: read # least-privilege GITHUB_TOKEN - this job only reads the repository
    steps:
      - name: Checkout
        uses: actions/checkout@v7.0.1
        with:
          fetch-depth: 0 # full history, required for the Sonar analysis
          persist-credentials: false # the GITHUB_TOKEN is not needed after the checkout - don't leave it in .git/config

      - name: Select Node Version
        uses: svierk/get-node-version@v1.5.1

      - name: Install Dependencies
        run: npm ci --ignore-scripts

      - name: Install SF CLI
        uses: svierk/sfdx-cli-setup@v1.1.3

      - name: Code Review
        uses: svierk/sfdx-code-review@v1.0.0
        with:
          code-analyzer: true
          prettier: true
          eslint: true
          workspace: force-app
          severity-threshold: 3

      - name: SonarCloud Scan
        uses: SonarSource/sonarqube-scan-action@v8.2.1
        env:
          SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} # SONAR_HOST_URL is additionally required for a self-hosted SonarQube Server

Prettier and ESLint run locally via Node, so install the project dependencies first (e.g. npm ci) and make sure the project provides the matching config (.prettierrc, eslint.config.js/.eslintrc).

Applying your ESLint config to the Code Analyzer engine. By default the Code Analyzer's bundled ESLint engine uses its own base rules and does not apply your workspace eslint.config.js โ€” it logs a hint when it finds one (ESLint configuration file 'eslint.config.js' was found but not applied). This is expected: the separate ESLint check above (eslint: true) is what applies your project config and produces the Sonar report. If you also want your config applied inside the Code Analyzer's ESLint engine, this is an engine setting (no CLI flag exists), so pass a code-analyzer.yml via the config-file input:

yaml
engines:
  eslint:
    # either point at the config explicitly โ€ฆ
    eslint_config_file: eslint.config.js
    # โ€ฆ or let the engine discover it automatically
    auto_discover_eslint_config: true

Uploading Code Analyzer results to GitHub code scanning โ€‹

When producing a SARIF file, let the analyzer report findings without failing the job immediately, then upload the SARIF. Uploading to code scanning requires the job to additionally grant security-events: write:

yaml
    permissions:
      contents: read
      security-events: write # required to upload the SARIF file to GitHub code scanning

    steps:
      - name: Code Review
        continue-on-error: true
        uses: svierk/sfdx-code-review@v1.0.0
        with:
          workspace: force-app
          output-file: results.sarif
          severity-threshold: 3

      - name: Upload SARIF
        uses: github/codeql-action/upload-sarif@v4.37.7
        with:
          sarif_file: results.sarif

Inputs โ€‹

NameRequiredDefaultDescription
code-analyzernotrueRun the Salesforce Code Analyzer.
prettiernofalseRun a Prettier formatting check.
eslintnofalseRun ESLint and produce a report (e.g. for Sonar).
workspacenoComma-separated files/folders for the Code Analyzer. Defaults to the current directory.
rule-selectornoRecommendedComma-separated Code Analyzer rule selectors, e.g. Security,Performance.
config-filenoPath to a Code Analyzer configuration file (code-analyzer.yml).
output-filenoComma-separated Code Analyzer output files; format derived from the extension (.sarif, .html, .json, โ€ฆ).
severity-thresholdnoFail if the Code Analyzer finds a violation with this severity or higher (1โ€“5 / Criticalโ€“Info).
install-pluginnotrueInstall the Code Analyzer plugin before running. Set to false if installed via sfdx-cli-setup.
plugin-versionnoSpecific version of the code-analyzer plugin to install, e.g. 5.0.0.
prettier-commandnonpx prettier --check .Command used to check formatting. Override to use your own, e.g. npm run prettier:verify.
eslint-commandnonpx eslint . --format json --output-file eslint-report.jsonCommand used to run ESLint and write its report. Override to use your own, e.g. npm run lint:sonar.
eslint-report-pathno./eslint-report.jsonPath to the ESLint JSON report, used to report error/warning counts.
step-summarynotrueWrite a result section to the GitHub Actions job summary. Set to false to avoid collisions with a custom workflow summary.

Outputs โ€‹

NameDescription
code-analyzer-outcomeOutcome of the Code Analyzer run (Passed/Failed).
prettier-outcomeOutcome of the Prettier check (Passed/Failed).
eslint-outcomeOutcome of the ESLint run (Passed/Failed).
eslint-errorsNumber of ESLint errors.
eslint-warningsNumber of ESLint warnings.

Outputs for a check that was not run are empty. The step fails if any enabled check fails, while still writing the summary and outputs.

Sonar integration โ€‹

ESLint writes its native JSON report so Sonar can pick it up directly:

properties
sonar.eslint.reportPaths=./eslint-report.json

The Salesforce Code Analyzer can additionally emit a SARIF/JSON file via output-file for GitHub code scanning or other tools.

The scan step itself only needs SONAR_TOKEN as an environment variable (plus SONAR_HOST_URL for a self-hosted SonarQube Server). A GITHUB_TOKEN is not required: pull request decoration is performed by the SonarQube Cloud GitHub App, not by the scanner, so passing the token would hand a credential to a third-party action for no gain.

Security & versioning โ€‹

Every uses: reference in the snippets above is pinned to an exact release version, e.g. svierk/sfdx-code-review@v1.0.0. Do the same in your own pipelines:

  • Never reference a mutable ref such as @main, @master or @v1. It runs whatever code sits on that branch/tag at run time โ€” with access to your repository and org credentials โ€” so a compromised or rewritten ref would run unnoticed.
  • Good โ€” pin to an exact release tag (@v1.0.0). Readable, concrete, and bumped through reviewed pull requests.
  • Strictest โ€” pin to a full-length commit SHA (@a1b2c3dโ€ฆ) with the version as a trailing comment. A SHA can never be re-pointed by the publisher; the cost is readability.
  • Enable Dependabot for github-actions in the repository that consumes this action, so those pins are bumped for you instead of silently ageing. Note that Dependabot only scans .github/workflows โ€” copy-paste snippets in documentation have to be updated by hand.

This action itself needs no secrets and no GITHUB_TOKEN permissions โ€” it only runs the Salesforce Code Analyzer, Prettier and ESLint against the checked-out sources. Grant the calling job the least privilege it needs (contents: read is enough, plus security-events: write when uploading SARIF) and use persist-credentials: false on the checkout so the token is not written to .git/config, where the CLI, a plugin or an ESLint config could pick it up.

If you pass credentials to other steps of the same workflow, reference them as environment variables (env: SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}, then "$SONAR_TOKEN" in the script), never by interpolating ${{ secrets.โ€ฆ }} into a run: script itself โ€” that would allow command injection and can leak values into the log. The steps of this action follow that rule for all of their inputs: every input is passed through env: and referenced as a shell variable, so a value such as eslint-command or workspace can never be expanded into the script body.

References โ€‹

Releases โ€‹

Latest release notes can be found on the release page.

License โ€‹

The scripts and documentation in this project are released under the MIT License.


โžก๏ธ Full source & releases: svierk/sfdx-code-review

Released under the MIT License.