Skip to content

perf: speed up lint and check-types (cache, incremental, staged-only hook) #2090

Closed

Description

@edelauna

Problem

pnpm lint and pnpm check-types are slow. The src package causes most of the wait in both.
The pre-commit hook runs pnpm lint on the whole repo. The pre-push hook runs pnpm check-types.

Measurements

All runs are warm, with the turbo cache bypassed (--force). Machine: WSL2, 20 cores.

Lint

Package Time
src 26.1 s
webview-ui 6.3 s
vscode-e2e 3.1 s
cli 2.1 s
types 1.8 s
other 6 packages 0.8 s to 1.4 s each
pnpm lint --force (all, turbo) 28.1 s

Turbo runs the packages in parallel. The total is about 2 s more than src alone.

TIMING=15 on src:

Rule Time Share of rule time
@typescript-eslint/no-floating-promises 7.2 s 73%
@typescript-eslint/no-unused-vars 1.3 s 13%
each other rule 0.2 s or less under 2%

no-floating-promises is the only type-aware rule. It needs a TypeScript program (parserOptions.project: true in src/eslint.config.mjs).

Check types

Package Time
src 19.0 s
webview-ui 15.1 s
cli 5.4 s
other 8 packages 0.7 s to 4.8 s each
pnpm check-types --force (all, turbo) 25.9 s

tsc --noEmit --extendedDiagnostics on src:

  • 3,428 files. 1.7 GB of memory.
  • Check time is 16.4 s of 20.3 s total. Parse time is 1.7 s. Bind time is 0.9 s.
  • skipLibCheck is already on. File loading is not the cost.

Experiments

Run on src with CLI flags and a scratch cache. No repo files changed.

TypeScript incremental mode

Command: tsc --noEmit --incremental --tsBuildInfoFile <path>.
Today packages/config-typescript/base.json sets "incremental": false, and src/tsconfig.json does not extend it.

Run Time
Cold (no build info) 18.9 s to 19.6 s
Warm, no change 3.2 s to 3.4 s
Comment edit in a leaf test file 3.3 s
Comment edit in utils/path.ts (41 direct importers) 19.7 s to 20.6 s

Without the declaration option, tsc cannot tell if a change affects other files. Any edit invalidates every file that depends on the edited file.
So incremental mode helps for edits to leaf files. It does not help for edits to shared files.
tsc --noEmit --incremental --declaration exits with TS errors in src. Do not use it until we fix them.

ESLint cache

Command: eslint . --ext=ts --max-warnings=0 --cache --cache-strategy content --cache-location <path>.

Run Time
Cold (no cache) 26.4 s
Warm, no change 1.3 s
After a comment edit in one file 1.3 s

The edited file was outside the typed-lint globs. An edit to a file inside those globs may cost more. We did not test that.

Proposed changes

  1. Add --cache --cache-strategy content to the lint scripts in src and webview-ui. Put the cache under node_modules/.cache/ so git ignores it.
  2. Add "incremental": true and a tsBuildInfoFile under node_modules/.cache/ to src/tsconfig.json and webview-ui/tsconfig.json.
  3. Change the pre-commit hook to lint only staged files through lint-staged. Keep the full lint in CI.
  4. Check that webview-ui check-types needs to emit. Its script is tsc without --noEmit.

Not yet measured

  • Cold-start time on CI or on other machines.
  • webview-ui with the proposed changes.
  • ESLint after an edit to a file inside the typed-lint globs.
  • parserOptions.projectService against project: true. The typescript-eslint performance repo reports that projectService no longer beats project.
  • A claim that --prune-suppressions slows ESLint. We found no evidence for it.

Sources

🤖 Generated with Claude Code

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