Skip to content

5. Static Code Analysis, Test Execution and Code Quality - Apex and LWC

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 organization 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 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, and Regex) under a single interface. 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.

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

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 integrates with CI/CD pipelines and offers IDE plugins for real-time developer feedback. CodeScan can limit its checks to changed files/lines, allowing its introduction to existing projects. CodeScan holds a FedRAMP Moderate Authorization to Operate (ATO), authorized 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.

  • https://www.autorabit.com/products/codescan-static-code-analysis/

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 authorization; federal agency engagements with strict FedRAMP tooling requirements should consider this when evaluating Gearset.

  • https://gearset.com/solutions/code-reviews/

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 organizations with specialized governance requirements. Hubbl does not publicly disclose FedRAMP or SOC 2 certifications; organizations with formal compliance requirements for their tooling should verify Hubbl's current security posture directly with the vendor before adoption.

  • https://www.hubbl.com/

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 organizations 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 authorization; federal agency engagements with FedRAMP tooling requirements should factor this in.

  • https://www.sonarsource.com/products/sonarqube/

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 organizations 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 authorization. This makes Checkmarx the strongest option for organizations targeting FedRAMP High workloads once full ATO is achieved.

  • https://checkmarx.com/

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.

  • PMD: https://docs.pmd-code.org/latest/ | https://github.com/pmd/pmd
  • ESLint: https://eslint.org/docs/latest/ | https://github.com/eslint/eslint

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, centralized in the configuration repository (Section 8), and protected from unauthorized 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. For organizations 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.

AI-Powered Code Analysis

AI-powered code analysis tools represent an emerging and rapidly evolving complement to deterministic static analysis. Where rule-based tools flag known patterns consistently, AI-powered tools can surface contextual issues, suggest refactoring approaches, and provide natural language explanations of violations that accelerate developer understanding and remediation.

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 summarizes changes, identifies potential issues, and engages in conversational review threads - augmenting human reviewer capacity rather than replacing it. CodeRabbit is currently pursuing FedRAMP authorization.

  • https://www.coderabbit.ai/

Salesforce ApexGuru is Salesforce's own AI-powered Apex analysis feature, available within Scale Center. Unlike Code Analyzer which statically analyses code, ApexGuru analyzes 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. It is complementary to static analysis rather than a replacement.

  • https://help.salesforce.com/s/articleView?id=xcloud.apexguru_overview.htm

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.

  • https://docs.github.com/en/copilot/how-tos/use-copilot-agents/request-a-code-review/use-code-review

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

  • https://checkmarx.com/product/checkmarx-one-assist/

An important architectural consideration for AI-powered tools: 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. AI tools should be positioned as an advisory layer on top of a deterministic static analysis-based quality gate - not as a replacement for it.

Ruleset Files as a Governance Artifact

Regardless of which static analysis tool is chosen, the configuration files that govern its behavior are governance artifacts that deserve the same change management rigor as production code. This applies to PMD ruleset XML files, eslint.config.js, jest.config.js, code-analyzer.yml, SonarQube quality profiles, CodeScan rule configurations, 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.
  • Centralized in the configuration repository (Section 8) as the canonical source and placed in the root directory of each consuming repository. 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.

This whitepaper intentionally does not prescribe specific rules for any tool. Rule selection reflects the organization's risk posture, coding standards, and compliance requirements. For teams who want a ready-to-use starting point, Appendix F provides a reference pmd-ruleset.xml incorporating standard PMD category references across Apex, Visualforce, XML, and JavaScript, as well as a set of custom XPath rules covering permission governance, sharing model enforcement, naming conventions, and metadata quality. It should be adopted as a starting point and adjusted to the project's specific context, not applied wholesale without review.

@SuppressWarnings, //NOPMD, /* eslint-disable */, and equivalent suppression 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.

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 level selection is a consequential decision with meaningful tradeoffs:

RunLocalTests executes all tests in the org excluding managed package tests. It is the safest default - comprehensive, universally applicable, and the minimum threshold for production deployments. For small to medium orgs with fast test suites it is the right choice for PR-time execution. For large orgs where RunLocalTests takes twenty or thirty minutes, the feedback latency undermines the shift-left objective.

RunSpecifiedTests executes a defined list of test classes. It is fast and precise when the test list is well-maintained and accurately reflects the scope of the change - but brittle when it is not. For PR-scoped RunSpecifiedTests execution, the sfdx-git-delta plugin (GitHub) provides a reliable mechanism for generating the list of changed Apex classes from the PR diff - see Appendix E for the full implementation pattern.

RunRelevantTests (Beta, Spring '26, API 66.0) is the most promising approach for large orgs. Salesforce dynamically selects which tests to run based on platform-inferred dependency relationships between the deployed components and the test suite. 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. RunRelevantTests applies equally to sf apex run test and sf project deploy; teams adopting it for PR-time test execution should apply the same test level to metadata validation runs for consistency. Two important constraints govern RunRelevantTests adoption: 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.

RunAllTestsInOrg executes every test including managed package tests. It provides the maximum coverage signal but is also the most time-consuming and the most prone to failures outside the team's control - managed package tests can fail due to issues in the package itself rather than anything introduced by the deployment. Opinions differ on whether managed package tests should be included in validation runs at all; some organizations treat any test failure as a blocker regardless of origin, while others explicitly exclude managed package tests from required gate status on the grounds that their failure does not indicate a problem with the org's own code. This tradeoff should be a deliberate organizational policy decision rather than an implicit consequence of test level selection. When RunAllTestsInOrg is used, it is sometimes appropriate for scheduled full-org validation runs rather than PR-time execution where feedback speed is a priority - though some teams do apply it as a pre-production gate where maximum confidence justifies the cost.

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 organizational threshold - listed by name with their current coverage percentage. 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. This file can be uploaded directly to CodeCov (codecov.io) without format conversion - see Appendix A.2.8 for the implementation pattern.

Test quality is a companion concern to coverage percentage. A test suite that achieves coverage through assertion-free test methods, or through Assert.isTrue(true) patterns that pass unconditionally, satisfies the coverage gate while providing no actual regression protection. The CI pipeline should flag these patterns 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.

These packages are installed as devDependencies in the project's package.json. The eslint.config.js file at the repository root governs rule configuration and is a centralized governance artifact (see Section 5), 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.

Package Purpose
eslint Base ESLint linting engine
@lwc/eslint-plugin-lwc LWC-specific rules enforcing component model constraints
@salesforce/eslint-config-lwc Recommended ESLint config preset for LWC projects
@salesforce/eslint-plugin-aura Aura interoperability and component rules
@salesforce/eslint-plugin-lightning Lightning base component usage rules
eslint-config-prettier Disables ESLint formatting rules that conflict with Prettier
eslint-plugin-import Import/export statement linting
eslint-plugin-jest Jest test file linting rules
globals Global variable definitions for browser, Node.js, and Jest environments
@sa11y/jest Accessibility assertions for Jest test suites
@salesforce/sfdx-lwc-jest Jest test runner and LWC component mocking for unit tests

The ESLint package list above is not required when using Code Analyzer; it is included here because Code Analyzer's behavior can be overridden by a local eslint.config.js.

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.

Test results should be output in JUnit XML format via the jest-junit (GitHub) reporter. Components with no corresponding test file should be flagged as a governance signal - a pre-check step that enumerates LWC components and verifies the existence of a corresponding __tests__ directory makes this gap explicit.

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

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.

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

Advisory-only positioning. As noted in the AI-Powered Code Analysis section above, these tools produce non-deterministic output and must be positioned as an advisory layer alongside deterministic gate output, 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 centralized configuration repository alongside the deterministic analysis configuration. A tool whose configuration lives outside the repository introduces the same governance gap that centralized 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.

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.

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 Prettier configuration file .prettierrc or prettier.config.js should be maintained as a versioned project artifact. For organizations managing multiple Salesforce repositories, the configuration should be published as a shared npm package (e.g. @<my-org>/prettier-config) and referenced in each repository's package.json. This eliminates configuration duplication, enables centralized 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 Critical (1) and High (2) findings while allowing Moderate, Low, and Info findings to be surfaced without blocking merge. This implements the two-table PR comment pattern from Section 4.

SARIF upload: 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.

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.

jq for output processing: The jq command-line JSON processor is pre-installed on GitHub-hosted runners and is invaluable for parsing tool output into structured PR comment formats. Both sf code-analyzer run (with --output-file results.json) and sf apex run test (with --result-format json) produce JSON output well-suited to jq post-processing for the two-table pattern described in Section 4. xq handles XML output equivalently and is a separate tool that must be explicitly installed in any workflow step that processes XML.

Required status checks: Each quality gate - Code Analyzer, Apex tests, Jest tests, Prettier verification, metadata validation, etc. - should be registered as a distinct required status check in the GitHub Ruleset configuration. Individual required checks give developers and reviewers a precise failure signal and allow gates irrelevant to a given PR to be skipped without blocking on a check that never ran.

Further reading: