Skip to content

Add PMD static analysis and enforce Prettier / ESLint / PMD in CI (every change must pass) #61

Description

@Mateusz7410

Motivation

A consumer who vendors the async-lib source into their own repo ran PMD over it
and got 7 findings (6 critical, 1 major) — NcssCount, UnusedLocalVariable ×5,
AvoidHardcodingId. We can't reproduce that locally because async-lib runs no
PMD at all
, and the formatting/lint tools we do have are not enforced where it
counts.

Current state (verified):

  • Prettier — exists (prettier, prettier:verify scripts) but enforced only
    in the local pre-commit hook (lint-staged + husky), which any dev can skip
    with git commit --no-verify.
  • ESLint — exists but scoped to **/{aura,lwc}/** only; also local pre-commit
    only. (ESLint does not analyze Apex — it never will catch the PMD findings.)
  • PMD — none. No ruleset, no scanner, nothing.
  • CI (.github/workflows/ci.yml → shared cicd-template/salesforce-ci) runs
    only deploy + Apex tests + Codecov. No prettier, no eslint, no PMD.

Net: the only real gate is Apex tests. Formatting and static analysis are advisory
and bypassable. We ship code that fails a standard PMD ruleset, and our consumers
see it before we do.

Goal

Every change to the lib must pass Prettier, ESLint, and PMD in CI — not just
in a local hook that can be skipped.

Proposal

1. Add PMD with a tuned Apex ruleset

  • Store pmd/ruleset.xml. Maintainer supplies the base ruleset (the common
    one already used across other btcdev projects) — do not seed from scratch. Then
    tune it here:
    • Test classes: relax or exclude the noisy rules for *Test.cls
      (NcssCount, UnusedLocalVariable, AvoidHardcodingId are expected in a
      4800-line test class with fake Ids and scaffold vars). Prefer a path/pattern
      exclusion for test sources over weakening the rule globally.
    • Intentional idiom: QueueableJob.getFullClassName deliberately assigns an
      unused local to force a TypeException (Apex forbids a bare cast statement).
      Suppress narrowly via @SuppressWarnings('PMD.UnusedLocalVariable') on that
      method (or a ruleset exclusion), with a comment explaining why.
  • Add npm scripts: pmd (run) and pmd:verify (fail on findings).

2. Keep Prettier + ESLint, make them non-bypassable

  • Prettier stays as-is; ESLint config already no-ops cleanly when there is no
    LWC/Aura (see verify-static.sh). No behavior change — just add them to CI.

3. Enforce all three in CI (the actual ask)

No such CI check exists today (verified: the reusable salesforce-ci runs only
deploy + Apex tests + Codecov). So this is net-new. A static-analysis gate that
fails the PR:

  • Run prettier:verify + eslint + pmd:verify on every PR/push.
  • Two placement options (decision needed):
    • A. Local job in this repo's ci.yml alongside the reusable salesforce-ci
      call — a static-analysis job that fails independently. Self-contained.
    • B. Extend the shared cicd-template to include a lint/PMD stage — benefits
      every btcdev lib at once, but is a cross-repo change affecting other packages.
  • Recommendation: A first (unblocks async-lib now), then upstream to the
    template (B) once the ruleset is proven.

4. Wire into the agent static gate + implementer agent steps

  • Add pmd:verify to scripts/agent/verify-static.sh so verify.sh and agents
    catch findings before CI, keeping the local/CI checks in sync.
  • Update the implementer agent definition so its workflow explicitly runs the
    static gate (prettier + eslint + PMD) as a step and treats any finding as a
    blocker before it commits — the agent must know PMD is now part of "done".

5. Keep pre-commit as fast local feedback

lint-staged stays (prettier + eslint) for quick loops. CI is the authority.

Baseline decision

The existing 7 findings must reach zero before the gate can be required:

  • QueueableJob:229 → suppress (intentional idiom).
  • AsyncTest findings → covered by test-source exclusion, OR fix the real ones
    (drop genuinely unused locals, replace hardcoded Ids with generated ones,
    consider splitting AsyncTest for NcssCount).
  • Decide: fix, suppress, or baseline (accept current, block only new).
    Prefer fix/suppress over a baseline file so the tree is genuinely clean.

Tooling decision

  • PMD runner: sf code-analyzer (Salesforce Code Analyzer plugin, current) vs
    sf scanner (legacy) vs standalone pmd binary vs a pmd-github-action.
    Pick one; sf code-analyzer aligns with the existing sf-CLI toolchain.
  • Pin the PMD/analyzer version so findings are reproducible across dev and CI.

Scope

  • Tooling + config + CI only. No product code change beyond the one suppression
    and any real finding fixes.

Out of scope

  • New lint rules beyond a sane PMD default + our tuning.
  • Reformatting unrelated code (Prettier already governs formatting).

Open questions

  • Exclude test sources from PMD wholesale, or tune per-rule for tests?
  • Fix vs suppress vs baseline for the current AsyncTest findings?
  • Which PMD runner + pinned version?

Resolved from discussion: base ruleset supplied by maintainer (common btcdev
ruleset); CI placement = local job (A) first, upstream to shared template later;
no CI static check exists today.

Acceptance criteria

  • pmd/ruleset.xml committed; pmd + pmd:verify npm scripts.
  • Tree passes pmd:verify at zero findings (via fixes + the documented suppression).
  • CI fails a PR on any Prettier, ESLint, or PMD violation (option A wired at least).
  • scripts/agent/verify-static.sh runs pmd:verify.
  • Implementer agent definition updated: static gate (prettier + eslint + PMD) is an
    explicit step and a commit blocker.
  • Docs/CONTRIBUTING note: changes must pass prettier + eslint + PMD; how to run
    them locally.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions