Skip to content

chore: add biome linter/formatter (take 2) #213

@backnotprop

Description

@backnotprop

Background

PR #211 attempted to add biome linting/formatting. The formatting and config were fine, but bunx biome lint --write --unsafe modified 59 files and introduced silent regressions in React hook dependency arrays that multiple review passes failed to fully catch. PR was closed.

What went wrong

  1. --unsafe modified dep arrays — biome's useExhaustiveDependencies rule added/removed deps from useEffect/useMemo/useCallback arrays across the entire codebase
  2. TDZ crashes — Some added deps referenced const variables defined later in the component. React evaluates dep arrays when the hook call executes, so forward references to const hit the Temporal Dead Zone and crash: "Cannot access 'X' before initialization"
  3. Functional regressions — Some added deps were plain functions (not useCallback), which get recreated every render. Adding them as deps caused effects to re-run every render, destroying stateful objects like the web-highlighter instance (broke text selection entirely)
  4. Suppression masking — We added biome-ignore comments with noInvalidUseBeforeDeclaration to silence the TDZ warnings, not realizing the suppression hides a runtime crash, not just a lint pedantry

Recommended approach for next attempt

Do

  • Format first, lint secondbiome format --write . in one commit, then assess lint findings separately
  • Never use --write --unsafe for lint — review every lint suggestion manually, especially useExhaustiveDependencies
  • Disable useExhaustiveDependencies entirely — this rule is the source of all the regressions. The codebase has intentional dep array choices (extra deps as triggers, omitted deps to prevent loops). Biome's rule doesn't understand these patterns. If we want exhaustive deps checking, use React's own eslint plugin which handles these edge cases better
  • Audit dep arrays with a script — after any automated changes, diff every }, [ line against main and verify each change
  • Test the production build — TDZ errors only manifest in bundled builds (or on first render in dev), not in lint passes

Don't

  • Don't run --unsafe on the full codebase
  • Don't suppress noInvalidUseBeforeDeclaration — if biome flags it, the dep needs to be removed, not suppressed
  • Don't add plain functions to dep arrays without wrapping them in useCallback first

Config that worked

// biome.jsonc
{
  "$schema": "https://biomejs.dev/schemas/2.0/schema.json",
  "vcs": { "enabled": true, "clientKind": "git", "useIgnoreFile": true },
  "formatter": { "indentStyle": "space", "indentWidth": 2, "lineWidth": 100 },
  "linter": {
    "rules": {
      "recommended": true,
      "correctness": {
        "useExhaustiveDependencies": "off" // too dangerous for automated fixes
      }
    }
  },
  "javascript": { "formatter": { "quoteStyle": "single", "trailingCommas": "all" } },
  "files": { "ignore": ["apps/marketing/**"] }
}

CI workflow that worked

name: CI
on:
  pull_request:
    branches: [main]
jobs:
  check:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: biomejs/setup-biome@v2
      - run: biome ci .

Salvageable work from PR #211

These commits from the closed PR were clean and should be cherry-picked into separate PRs:

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions