Skip to content

Code Quality Gates: Static Analysis and Testing

Scope note: This section covers the pipeline integration of static analysis, test execution, and formatting tools as enforceable quality gates. It is not a comprehensive Apex coding standards guide, an LWC best practices reference, or a prescription for which specific PMD rules an organisation should adopt. The appropriate rule selection, coverage thresholds, and suppressibility policies for a given project are context-dependent decisions that belong to the technical lead team - not defaults to be applied without review. Where this section references metadata-related rules (permission governance, sharing model enforcement, Flow quality), those rules are discussed specifically because they are enforced through the same Code Analyzer / PMD pipeline that governs Apex - they are a governance concern that belongs in the pipeline architecture, even though they target declarative metadata rather than code. A deeper treatment of metadata-specific governance is in Section 6. The reference PMD ruleset in Appendix F.2 is a calibrated starting point, not a standard to adopt wholesale.

Choosing a Static Analysis Tool

Static code analysis for Salesforce has matured significantly - there are now multiple viable tools, each with different strengths, coverage profiles, and integration models. The right choice depends on the project's existing tooling landscape, the client's licensing posture, the team's familiarity, and the specific coverage requirements of the engagement. What matters architecturally is not which tool is chosen, but that the tool is integrated into the pipeline as an enforced quality gate, producing structured output that feeds into the PR feedback pattern described in Section 4.

The Salesforce static analysis tooling landscape currently includes:

Salesforce Code Analyzer is Salesforce's free, open-source CLI plugin that orchestrates multiple underlying engines (PMD, ESLint, RetireJS, Flow Analyzer, CPD, Regex, and the Graph Engine) under a single interface. Two more engines, ApexGuru and UIBundle, run only when they are selected by name (Appendix E). It produces unified output in SARIF, JSON, CSV, and HTML formats, is run by the forcedotcom/run-code-analyzer GitHub Action, and is configured via a versioned code-analyzer.yml file. It is the lowest-friction starting point for most projects. Detailed engine reference, configuration patterns, and --rule-selector syntax are provided in Appendix E.

AutoRABIT CodeScan is a commercial static analysis platform built specifically for Salesforce environments. It automatically reviews Apex, Visualforce, Lightning Web Components, and metadata to detect coding standard violations, security vulnerabilities, and performance issues. CodeScan reaches a GitHub repository in three ways, and which one an organisation uses depends on its infrastructure: its VS Code extension flags violations in the editor (Section 4); a step in the PR workflow runs the scan in GitHub Actions and uploads the results to GitHub code scanning; or CodeScan Cloud's own webhook, created when the project is added from GitHub, scans each push and pull request with no workflow file in the repository. The webhook has a trade-off: the scan does not run in a GitHub Actions workflow, so there is no workflow output in which to build a table of violations for the pull request (Section 4) and no workflow log to debug. CodeScan can limit its checks to changed files/lines, allowing its introduction to existing projects. CodeScan holds a FedRAMP Moderate Authorization to Operate (ATO), authorised November 24, 2025 per the FedRAMP Marketplace, along with SOC 2 Type II, ISO 27001, GDPR, and CCPA compliance. AutoRABIT Guard, the companion posture and configuration management product, received the same FedRAMP Moderate ATO in the same package. CodeScan is one of the few Salesforce-specific DevSecOps tools with a FedRAMP ATO, making it the strongest option for U.S. federal agency engagements or contractors operating under FedRAMP requirements.

Gearset Code Reviews is an enterprise-grade code review platform built for Salesforce. It scans 300+ metadata types including Apex, Flows, Lightning Web Components, Aura, Visualforce, Agentforce, Profiles, and Permission Sets. A notable capability is its handling of technical debt: it can distinguish legacy violations from newly introduced ones, enabling teams to enforce quality gates on new code without requiring remediation of all historical violations before gating begins - a practical advantage on brownfield engagements. (Of note, CodeScan can do this as well.) Gearset holds ISO 27001 certification and hosts on AWS infrastructure. It does not currently hold FedRAMP authorisation; federal agency engagements with strict FedRAMP tooling requirements should consider this when evaluating Gearset.

Hubbl Diagnostics provides Salesforce org health analysis with a focus on metadata complexity, technical debt, and org-wide pattern detection - complementing code-level static analysis with broader org governance signals. Hubbl does not support custom rule authoring - its analysis is based on a fixed rule set, which simplifies adoption but limits extensibility for organisations with specialised governance requirements. Hubbl does not publicly disclose FedRAMP or SOC 2 certifications; organisations with formal compliance requirements for their tooling should verify Hubbl's current security posture directly with the vendor before adoption.

SonarQube / SonarCloud is a widely adopted general-purpose static analysis platform with Apex support. Teams already running SonarQube for non-Salesforce codebases can extend its coverage to Apex rather than introducing a separate tool, making it a natural choice for organisations with an existing SonarQube investment. Sonar maintains SOC 2 Type II attestation (achieved February 2025) and ISO 27001:2022 certification at the company level across all SonarQube products. It does not hold FedRAMP authorisation; federal agency engagements with FedRAMP tooling requirements should factor this in.

Checkmarx is an enterprise (Static Application Security Testing) platform with Apex support, understanding platform-specific constructs like SOQL, DML, and governor limits. It is most relevant for organisations that need cross-platform security analysis covering both Salesforce and external integrations in a single scanning platform. Checkmarx One for Government achieved FedRAMP High Ready status in September 2025 - the most stringent FedRAMP baseline - with a 3PAO-reviewed Security Assessment Report complete. Full ATO has not yet been granted as no sponsoring agency has been named, but the Ready designation and completed SAR place Checkmarx in an advanced position for federal authorisation. This makes Checkmarx the strongest option for organisations targeting FedRAMP High workloads once full ATO is achieved.

Standalone PMD and ESLint remain valid options for teams that want direct control over the underlying engines without the Code Analyzer wrapper. PMD can be invoked directly against Apex and metadata XML, and ESLint against LWC and Aura, with results parsed and surfaced as PR annotations via custom workflow steps. This approach requires more pipeline assembly effort but eliminates the Code Analyzer dependency and provides maximum control over engine versions and configuration.

Regardless of which tool is selected, the following requirements apply uniformly:

  • The tool must be invokable from a GitHub workflow job/step as part of the PR quality gate chain, either using an action or with the CLI.
  • Output must be parseable into the two-table PR comment pattern (blocking vs. non-blocking violations) from Section 4.
  • Configuration files must be version-controlled, centralised in the configuration repository (Section 8), and protected from unauthorised modification.
  • The tool must support a severity threshold that produces a non-zero exit code on violations above the defined level, enabling workflow failure on blocking findings.
  • Execution model: Static analysis tools integrate with GitHub Actions in two ways - some tools are invoked directly in the runner via CLI commands in a run: step (PMD standalone, ESLint standalone, RetireJS standalone, and Code Analyzer via sf code-analyzer run), while others provide first-party GitHub Actions that handle installation, execution, output parsing, and artifact upload as a single configured step (Code Analyzer via forcedotcom/run-code-analyzer and CodeScan via AutoRABIT's published action codescan-io/codescan-scanner-action). Both integration models are valid; the GitHub Action approach reduces pipeline assembly effort and is generally preferred where available, while the direct CLI approach provides maximum control over execution parameters and output handling.
  • Custom rule extensibility: Several tools support custom rule authoring beyond their out-of-the-box rulesets - PMD supports custom rules written in XPath or Java; AutoRABIT CodeScan similarly supports custom XPath rules within its framework; SonarQube supports custom rules via its Java plugin API. Hubbl Diagnostics does not support custom rule authoring. When custom rule capability is a requirement, it should be an explicit evaluation criterion in tool selection.

Running Code Analyzer on every push rather than standalone tools ensures that PMD, ESLint, RetireJS, Flow Analyzer, and CPD all execute under the same configuration that governs PR gates and IDE analysis. While the Java and Python prerequisites may add setup time and compute compared to a Node.js-only ESLint invocation (as an example), the unified coverage and configuration consistency justify this cost. On a push, Code Analyzer scans the files that the push changed, not the whole repository (Configuring GitHub Actions to Enforce Quality Gates). For organisations where setup overhead is a concern at scale, a custom runner image with all dependencies pre-installed eliminates this tradeoff - see Appendix A.1.1.

Third-Party and AI-Powered Code Analysis Tools

The deterministic static analysis layer described in this section - Code Analyzer, standalone PMD and ESLint, or a commercial alternative - is the appropriate foundation for quality gates that block merge. A second category of tooling exists alongside it: AI-powered and third-party analysis platforms that augment deterministic findings with contextual signals, natural language explanations, org-wide pattern detection, and conversational PR review. These tools are complementary by design, not replacements for the gate layer.

The current landscape of AI-powered tools relevant to Salesforce development includes:

CodeRabbit is an AI code review tool that integrates directly with GitHub PRs, providing natural language review comments alongside automated analysis. It summarises changes, identifies potential issues, and engages in conversational review threads - augmenting human reviewer capacity rather than replacing it. CodeRabbit is currently pursuing FedRAMP authorisation.

Salesforce ApexGuru is Salesforce's own AI-powered Apex analysis feature, and it comes in two forms. Within Scale Center, it analyses JVM traces from the production org using an LLM to find issues with Apex code when it is being executed at runtime - surfacing performance issues and anti-patterns based on actual org telemetry. As an engine of Code Analyzer, it analyses the Apex classes and triggers of a project: the engine uploads them to the ApexGuru service through an org that Salesforce CLI is logged in to, and runs only when it is selected (Appendix E). Its runtime findings need ApexGuru activated for the org; in any other org the engine runs a static analysis only. In both forms the analysis runs on Salesforce's servers and its rules cannot be read, so it is complementary to static analysis in the pipeline rather than a replacement.

GitHub Copilot code review extends GitHub Copilot's capabilities into the PR review flow, providing AI-generated review suggestions inline on changed files. For teams already using GitHub Copilot for development, this adds a review-time signal without introducing a separate tool.

Checkmarx One Assist augments Checkmarx's SAST capabilities with AI-assisted vulnerability discovery that ranks Apex security issues by exploitability - prioritising findings by actual risk rather than presenting all violations with equal weight.

Before adopting any tool in this category, the following criteria should drive the evaluation:

Advisory-only positioning. AI-based code review assistants are non-deterministic: they may produce different results each time they scan the same source code, making them unsuitable as the sole mechanism for quality gates or compliance reporting where consistent detection is required. They must be positioned as an advisory layer on top of a deterministic static analysis-based quality gate, not as a replacement for it.

PR integration model. The tool must integrate into the GitHub PR workflow without introducing a blocking status check that depends on non-deterministic output. A tool that posts review comments or summaries without controlling merge eligibility is architecturally sound; one that requires its own required status check introduces fragility.

Configuration consistency. Where the tool is configurable - custom rule sets, severity thresholds, suppression policies - its configuration should be version-controlled in the centralised configuration repository alongside the deterministic analysis configuration. A tool whose configuration lives outside the repository introduces the same governance gap that centralised configuration management is designed to close.

Maintenance surface. AI tooling in the Salesforce ecosystem is evolving rapidly. Any tool adopted should be evaluated not just on current capability but on the team's ability to maintain the integration, respond to pricing or API changes, and remove the tool cleanly if it is superseded. Vendor lock-in risk is higher in this category than in established open-source tooling.

Human in the loop. Some AI tools suggest code changes; these should be evaluated manually, as the tools do have the capacity for hallucinations.

Teams evaluating specific tools in this space - AI-assisted PR review, org health analysis platforms, AI-augmented SAST - should apply these criteria and treat adoption as a Phase 3 activity (Section 10), after the deterministic gate chain is stable and producing reliable signals.

Severity Levels

Every rule carries a severity from 1 to 5. The severity states how a violation of the rule is treated at the quality gate, whichever tool reports it.

Severity Label At the quality gate
1 Critical Blocking; fixed before the merge, and rarely accepted
2 High Blocking; fixed, or accepted by the owning team
3 Moderate Not blocking; a fix is strongly encouraged
4 Low Not blocking; advisory
5 Info Not blocking; for information only

Severities 1 and 2 block the pull request. Severities 3 to 5 are listed in the non-blocking violations table and do not prevent merging.

A severity 1 violation is also cleaned up on sight. A pull request that edits a file fixes the severity 1 violations in that file, including the ones that were there before the pull request.

How a blocking violation is accepted, and the remediation timelines that an organisation may set for severities 3 and 4, are in the Priority Reference, with how a rule's PMD priority becomes its severity. The policy behind these levels is in Ruleset Files as a Governance Artifact, and the scan that applies them is A.2.9, Static Code Analysis.

Ruleset Files as a Governance Artifact

Regardless of which static analysis tool is chosen, the configuration files that govern its behaviour are governance artifacts that deserve the same change management rigour as production code. This applies to PMD ruleset XML files, eslint.config.js, jest.config.js, code-analyzer.yml, or any equivalent - the specific format varies by tool, the governance requirements do not.

Configuration files should be:

  • Version-controlled alongside source code, not stored in external systems or maintained manually on runners.
  • Centralised in the configuration repository (Section 8) as the canonical source and placed in each consuming repository where its tool looks for it (Repository Structure). ESLint locates eslint.config.js from the root by default; jest.config.js should similarly reside at the root; placement elsewhere requires explicit path configuration on all invocations.
  • Protected so that only the designated governance team can modify them - changes require PR, review, and sign-off.
  • Documented with a changelog that records what changed, why, and when - enabling pass rate changes to be traced to a configuration modification rather than treated as unexplained variation.

Changes to rule configurations should be treated as potentially breaking changes for consuming repositories. A new rule addition that produces zero findings in the configuration repository's own test suite may produce hundreds of findings in a large production codebase. The versioning and promotion pattern from Section 8 applies equally to configuration file changes.

There is no expectation that an organisation will simply adopt every standard here without a proper evaluation, though this is a solid setup that errs on the side of security. Rule selection reflects the organisation's risk posture, coding standards, and compliance requirements.

For teams who want a ready-to-use starting point with an intentionally strict starting posture, Appendix F provides {N} reference ruleset files:

  • pmd-stock-rules.xml: stock PMD rules across Apex, Visualforce, HTML, and XML
  • pmd-stock-rules-javascript.xml: stock PMD rules for JavaScript
  • pmd-custom-rules-apex.xml: custom XPath rules for Apex, covering access mode and sharing, error prevention, documentation, naming, and performance
  • pmd-custom-rules-markup.xml: custom XPath rules for Visualforce and Lightning markup, covering accessibility and markup security
  • pmd-custom-rules-metadata.xml: custom XPath rules covering permission governance, sharing model enforcement, and metadata quality
  • pmd-custom-rules-flow.xml: custom XPath rules for Flows, Process Builder processes, and Workflow Rules
  • pmd-custom-rules-javascript.xml: custom XPath rules for JavaScript.

They should be adjusted to the project's specific context, not applied wholesale without review.

No Scanner Suppressions

Suppression mechanisms (tools to tell the static analysis engines to skip that line/section) such as @SuppressWarnings, //NOPMD, /* eslint-disable */, and equivalent mechanisms deserve explicit policy attention regardless of tool. Every suppression should be accompanied by a comment explaining the justification, and the CI pipeline should track suppression usage over time. A codebase with an increasing suppression count is one where the quality gate is being worked around rather than satisfied. Every unapproved and unreviewed suppression increases risk; if a suppression is approved for a specific line of code, a malicious developer could substitute dangerous code in a later update, and it would not be detected automatically. This is yet another reason why code changes should be reviewed by a human.

{N} rules make a no-unapproved-suppression policy enforceable rather than aspirational. A suppression is itself a blocking violation (severity 2), enforced by NoUnapprovedSuppression, a rule that matches @SuppressWarnings, // NOPMD, and Code Analyzer's code-analyzer-suppress marker in Apex, by NoUnapprovedEslintSuppression, which matches every eslint-disable comment in JavaScript, by @lwc/lwc-platform/no-inline-disable, and by @salesforce-ux/slds/reduce-annotations, which matches the suppression comments of the SLDS linter (@sldsValidatorIgnore, @sldsValidatorIgnoreNextLine, and @sldsValidatorAllow) in stylesheets, so it cannot pass the quality gate unnoticed. Like any blocking violation, it is cleared as Severity Levels sets out.

Apex Test Execution

Apex test execution is a mandatory CI quality gate, not a deployment-time afterthought but a PR-time requirement that every change must satisfy before it enters the review queue. Running tests at deployment time only means that a developer discovers test failures at the moment of highest pressure and lowest flexibility. Running them at PR time means failures are caught while the context is fresh, the scope is small, and remediation is straightforward.

Test Levels

Test level selection decides how long a developer waits for an answer, and it is two decisions, not one. A pull request runs the tests that bear on its change: the tests it contains, and the tests that exercise the code it changes. Every local test still runs, on a schedule and at the deployment to production. Running every local test on every pull request is not the standard: in most orgs the suite takes long enough that the answer arrives after the developer has moved on, which undermines the shift-left objective.

Run Test level
A pull request RunRelevantTests (Beta, API 66.0 and later), or RunSpecifiedTests with a computed list where that cannot be used
The scheduled test run, every night RunLocalTests
The deployment to production RunLocalTests
Never RunAllTestsInOrg

On a pull request the tests run inside the validation of the delta package (sf project deploy start --dry-run), so that the code under test is the pull request's own and not the copy already in the org. The validation sets the test level, and Appendix A.2.11 is the sample. It chooses the level from a configuration variable, set for the whole organization or for one repository, so an organisation starts on the computed list and moves to RunRelevantTests when its Apex is at API 66.0 and its tests are linked to the code they test.

RunRelevantTests (Beta, API 66.0 and later) is the first choice for the pull request gate. Salesforce selects the tests from an analysis of the deployment payload and its dependencies. The tests included in a RunRelevantTests execution are: tests present in the deployment payload itself, tests that Salesforce's dependency analysis infers are related to the changed components, tests explicitly linked to specific classes via the @IsTest(testFor='ApexClass:ClassName,ApexTrigger:TriggerName') annotation, and tests marked as @IsTest(critical=true), which are always included regardless of dependency inference.

The analysis has limits. Tested in a scratch org at API 67.0, while the feature was Beta, it selected the test that refers to a changed class in code, and it selected nothing in four cases: for a test that reaches a class only by name (Type.forName); for a test that reaches a trigger only by inserting a record; for a change with no Apex in it, whether a custom label, a custom field, a validation rule, or a record-triggered Flow; and for a payload identical to what the org already holds. A published test of the Beta found the same for dynamic instantiation, and found that it also missed a test that reached the changed code through another class. So link the tests of every trigger to it with testFor='ApexTrigger:TriggerName', link by testFor any test that reaches its class by name, and mark as critical=true the tests that guard declarative automation, which then run on every pull request. These are standards: Apex: Test Selection.

Three constraints govern adoption:

  • It is a Beta feature, so an organisation that does not use Beta features cannot adopt it yet.
  • It is a test level of a deployment (sf project deploy start and sf project deploy validate), and sf apex run test does not offer it.
  • API version 66.0 or later is required both for the deployment itself and in the *-meta.xml file of each Apex class for the test annotations to take effect; if the deployment runs at API 65.0 or below, the command errors rather than falling back silently to another test level.

RunSpecifiedTests executes a named list of test classes. It is the test level for the pull request gate where RunRelevantTests cannot be used: in an organisation that does not use Beta features, or for a deployment below API 66.0. The list is computed for each pull request and never kept by hand, from four sources: every class and trigger in the delta package, tests or not, because the deployment runs the tests among them and ignores the other names; the classes named for them with the Test or Tests suffix; the tests that the author lists in the pull request description; and a short list of tests that always run. None of these depends on an earlier test run: the list is computed from the pull request and the repository alone, so it is the same every time. When a pull request changes Apex and none of those names is a test, no test runs, and the gate fails because the changed class or trigger has no coverage. It does not fall back to running every local test. The sfdx-git-delta plugin (GitHub) produces the delta package from which the list is computed.

Both of these test levels change the coverage requirement: the tests that ran must cover each class and trigger in the deployment by at least 75%, computed for each one separately and not as an overall percentage. A changed class whose tests were not selected therefore fails the gate, and the remedy is a test, or a testFor link to the test it already has. One case slipped through in the same test: under RunRelevantTests, a changed trigger for which no test was selected, deployed with no other Apex, was accepted with no coverage at all, because no test ran. Once any test ran, its coverage was required. Keeping at least one test marked critical=true closes that gap, because that test always runs.

RunLocalTests executes the local tests in the org and the tests of unlocked packages that have no namespace. It does not run the tests of managed packages or of namespaced unlocked packages. It is the comprehensive level, and the default for a production deployment that includes Apex. It runs on a schedule and at the deployment to production. It suits a pull request only in an org whose whole suite runs in a few minutes.

RunAllTestsInOrg executes every test including managed package tests. The standard is not to run it: not on a pull request, not on a schedule, and not at a deployment. A failing managed package test does not affect the org's own code or the change being deployed, and it is outside the team's control, because managed package tests can fail due to issues in the package itself rather than anything introduced by the deployment. Running them adds time and failures, and no signal. The full run is RunLocalTests.

Test Coverage

Coverage enforcement requires two distinct signals. The platform's 75% aggregate coverage floor is a hard gate. The PR comment output should surface not just the aggregate but all individual Apex classes falling below the organisational threshold - listed by name with their current coverage percentage. On a pull request the validation reports the coverage of each class in its own result, and the workflow puts it in the tables - see Appendix A.2.12. In the scheduled full run, sf apex run test --result-format json --code-coverage produces a test-result-codecoverage.json file in the output directory containing per-class coverage data - see Appendix A.2.16. That file can be uploaded directly to Codecov (codecov.io) without format conversion. The coverage files that a validation can write cannot: they name each class no-map/<name>, which Codecov cannot match to a file in the repository.

Test Quality

Test quality is a companion concern to coverage percentage, and it is a standard rather than a number: the standard and the rule that enforces it are in Apex: Testing. The CI pipeline should flag violations as a governance signal distinct from coverage percentage.

LWC/Aura JavaScript Linting

JavaScript linting for Aura and Lightning Web Components requires an ESLint configuration tailored to the Salesforce component model. The standard Salesforce project setup uses a combination of Salesforce-maintained ESLint plugins that enforce LWC-specific constraints, Lightning API usage patterns, and Aura interoperability rules, alongside base ESLint, Prettier integration, and Jest-aware rules. The packages, the settings that these standards set, and a sample eslint.config.js are in Version Control: ESLint.

The eslint.config.js file at the repository root governs rule configuration and is a centralised governance artifact (see Ruleset Files as a Governance Artifact), so changes should require PR review and be versioned in the configuration repository (Section 8). See Appendix E for ESLint engine configuration within Salesforce Code Analyzer.

LWC Jest Test Execution

Jest tests for LWC components run entirely in the Node.js environment - no org connection required, no Salesforce CLI authentication, no sandbox availability dependency. This makes Jest execution one of the fastest gates in the pipeline and one of the lowest-friction to implement.

The @salesforce/sfdx-lwc-jest (GitHub) package provides the Jest preset configured for Salesforce LWC conventions, including module name mappings for @salesforce/ scoped imports that would otherwise fail to resolve in a Node.js test environment. The jest.config.js file should be versioned in the repository root and treated as a governance artifact subject to the same protection controls as other configuration files.

Jest test execution should be scoped to PRs that actually modify LWC source files using GitHub Actions paths: filtering. Coverage enforcement uses Jest's built-in --coverage flag combined with coverageThreshold configuration in jest.config.js. The threshold should be set explicitly from the first day of the project - retrofitting a coverage requirement onto an existing component library with low coverage is a significantly harder conversation than establishing the expectation before the first component is written. Most organisations, though, come to Jest with components that have few tests or none. The reference therefore sets the threshold for each component that has a test, and for no other: a tested component below the threshold fails the gate, and a component with no test fails nothing (Jest).

Failing tests should be surfaced where the developer already is: the reference workflow reads the JSON output of Jest and writes a table of the failing tests, with the message of each, to the pull request and to the job summary, so no JUnit reporter is needed. Components with no corresponding test file should be flagged as a governance signal - the workflow enumerates the changed LWC components, verifies the existence of a test file in a __tests__ directory, and names each component that has none. It reports them and does not fail the gate for them.

Jest is not suitable for testing Aura components, and the formerly-recommended Lightning Testing Service is deprecated and no longer supported.

Extensible Quality Gate Plugins

The quality gate layer is intentionally composable. Additional tools can be inserted as discrete workflow steps without restructuring the pipeline, provided each step has a defined failure threshold, a defined suppressibility policy, and a defined owner.

Prettier, Husky, and lint-staged implement a layered formatting enforcement strategy that starts before code leaves the developer's machine. Husky (GitHub) installs Git hooks directly into the repository. lint-staged (GitHub) scopes pre-commit hook execution to staged files only. Prettier (prettier.io) is invoked by lint-staged on staged Apex, LWC, and XML metadata files, auto-correcting formatting before the commit is created. lint-staged runs ESLint as well, on the staged JavaScript of Aura components and Lightning Web Components, as a new Salesforce DX project is set up to do. ESLint does not correct the file: a rule that the repository's eslint.config.js makes an error stops the commit (Version Control: ESLint). The third command runs the Jest tests that relate to a changed Lightning Web Component, and a failing test stops the commit. The hook, its settings, and a sample of each are in Version Control: Husky and lint-staged.

In CI, prettier --check is used rather than prettier --write - the workflow checks that every file conforms to the formatting standard and fails if any does not, without auto-correcting.

The repository's Prettier configuration file should be maintained as a versioned project artifact. Prettier accepts several file names and formats, and a JavaScript one (prettier.config.mjs, for example) lets a repository import a centrally written configuration and extend it (Version Control: Prettier has a sample of each form). For organisations managing multiple Salesforce repositories, the configuration should be published as a shared npm package (e.g. @<my-org>/prettier-config) and referenced from each repository, either by name in package.json or by importing it into the repository's own configuration file and overriding what differs. This eliminates configuration duplication, enables centralised versioning of formatting standards, and ensures that all repositories in the organization format code identically. The shared package is an npm dependency like any other and should be tracked by Dependabot and Renovate alongside external packages (Section 7) - closing the loop between standard updates and consuming repository adoption.

Configuring GitHub Actions to Enforce Quality Gates

Severity thresholds: sf code-analyzer run accepts a --severity-threshold flag that causes the command to exit with a non-zero code when any finding meets or exceeds the specified severity level. A threshold of 2 fails the workflow on severity 1 and severity 2 findings while allowing severity 3 to 5 findings to be surfaced without blocking merge, as Severity Levels sets out. This implements the two-table PR comment pattern from Section 4. In the sample workflows the threshold is set once, at the top of each caller, and passed to the workflow that runs the scan (Appendix A.1).

SARIF upload: Static analysis results should be uploaded to GitHub Code Scanning via the github/codeql-action/upload-sarif action (GitHub). This populates the repository's Security tab with a persistent, queryable record of all findings across all PRs.

Results artifact: The pull request scan also stores its complete results as a CSV file, a JSON file, and a SARIF file in one workflow artifact, kept for a few days, because they serve the review of one pull request. The comment on the pull request is a summary; the artifact has every violation, and it serves a repository where GitHub code scanning is not available. The scan of the whole repository stores the same three files, and keeps them for longer (Appendix A.2.9).

Coverage upload: Coverage should be uploaded to Codecov (codecov.io) via the codecov/codecov-action action (GitHub), from the Apex tests and from the Jest tests alike. What Codecov takes is the coverage file that the test run writes, such as the test-result-codecoverage.json of sf apex run test, and it reads that JSON as it is (Test Coverage). The settings of Codecov are in Version Control, Codecov.

Differential scanning: The sfdx-git-delta plugin provides a reliable mechanism for generating a clean delta package from a PR diff, scoping analysis to only the changed metadata. The grep -q '<types>' check against the generated package.xml is the recommended approach for determining whether a delta contains deployable metadata before executing org-connected steps.

Scanning on a push: A push has no pull request to take a diff from, so the push baseline builds its delta package from the commit before the push to the new commit, with the same sfdx-git-delta, and scans that delta package alone. The delta then holds every commit of the push. A push and a pull request are scanned the same way, and what to leave out is set in code-analyzer.yml, for every scan alike. A push never scans the whole repository: in a large repository that is slow, and the violations of old code would fill the summary of every push (Appendix A.2.9).

A scan of the whole repository: The gates look only at what a change touches, so the code that was in the repository before the gates needs a scan of its own. That scan is a workflow that a person starts, and no push, pull request, or schedule runs it (Appendix A.1.7). It writes every violation to a CSV file, a JSON file, and a SARIF file, in one artifact to download, and it never fails: it is a report for measuring and cleaning up old code, and not a gate.

JSON output: Results are parsed from a file and displayed on the pull request, never left in a log for the developer to read. Salesforce CLI commands write their results as JSON when given the --json flag, and several write a results file as well: sf code-analyzer run (with --output-file results.json or results.sarif), sf apex run test (with --result-format json), and sf project deploy start. CodeScan and other analysis tools produce JSON or SARIF in the same way, and SARIF is itself JSON. The jq command-line JSON processor is pre-installed on GitHub-hosted runners and parses all of these into the tables described in Section 4. xq handles XML output equivalently and is a separate tool that must be explicitly installed via pip install yq in any workflow step that processes XML.

Required status checks: Each quality gate - Code Analyzer, Apex tests, Jest tests, Prettier verification, metadata validation, etc. - runs as its own job, so developers and reviewers get a precise failure signal on the PR. The GitHub Ruleset, however, requires a single check: a final Quality Gates job that depends on every gate and fails if any gate failed. Requiring each gate by name breaks when a gate is legitimately skipped, as GitHub's own troubleshooting guidance explains; the reasoning and the source are in Appendix C.

Further reading: