Skip to content

perf(eslint-plugin): run syntactic checks before type queries - #47

Merged
CatOfJupit3r merged 2 commits into
mainfrom
perf/cheap-checks-before-type-queries
Oct 16, 2026
Merged

CatOfJupit3r merged 2 commits into
mainfrom
perf/cheap-checks-before-type-queries

Conversation

@CatOfJupit3r

Copy link
Copy Markdown
Owner

What changed

Two type-aware rules asked the TypeScript checker for types before running their cheap syntactic checks. Findings are unchanged.

  • prefer-native-schema-adapters calls getResolvedSignature() only when the member call's static method name is a Zod or Valibot schema factory (enum, nativeEnum, picklist). Before this, it resolved a signature for every member call that had an argument.
  • no-enumwaii-case-misuse runs every syntactic exclusion before getTypeAtLocation(), which skips imports, declarations, member objects and properties, parameters and type positions. For non-.cases member accesses, it skips the type query when the access is already an allowed case usage, unless the key is computed. It also caches its cases-brand decision per TypeScript Type in a WeakMap. TypeScript types never change, and each program has its own type objects, so the cache cannot return a stale decision.

The changeset is a patch bump of eslint-plugin-enumwaii (1.2.1 → 1.2.2).

Validation

All numbers come from the koneko monorepo (apps/web, apps/server), linted with only the enumwaii/* rules enabled. A config wrapper outside koneko swapped in a local build of this plugin. "Before" is origin/main (217e60d) and "after" is this branch. Nothing in koneko was changed.

hyperfine (one invocation per workspace, --warmup 1 --runs 8)

The machine was quiet: no other hyperfine, Vitest or ESLint process ran, and CPU load was sampled every 30 s at 5-53% (mostly 12-23%).

Workspace Before [s] After [s] Relative
server 20.963 ± 0.174 20.453 ± 0.079 1.02 ± 0.01
web 24.017 ± 0.855 23.315 ± 0.307 1.03 ± 0.04

The server gain (0.51 s) is outside both standard deviations. The web gain (0.70 s) is within the before run's standard deviation, so it is not claimed as a measured win.

Per-rule TIMING=all (ms, all enumwaii/* rules in one run)

Rule web before web after server before server after
no-enumwaii-case-misuse 5531 5663 7288 6669
prefer-native-schema-adapters 1249 9 1853 9
no-manual-enum 53 370 27 1854
no-raw-enum-comparison 115 229 14 59
others (5 rules) 121 127 70 77
sum 7069 6397 9251 8669

ESLint TIMING charges TypeScript's lazy checking to whichever rule first asks the checker about a node. With prefer-native-schema-adapters no longer resolving signatures, part of that shared checking moves to no-manual-enum. Use the sum to compare totals; the per-rule rows show where the time moved.

Checker calls (counted by wrapping parser services)

Rule / call web before web after server before server after
no-enumwaii-case-misuse getTypeAtLocation 116,364 55,492 103,540 46,356
prefer-native-schema-adapters getResolvedSignature 2,583 0 6,896 0

Other rules' checker calls are unchanged. A CPU profile of no-enumwaii-case-misuse alone on web shows that most of its remaining time is TypeScript's checkExpression behind getTypeAtLocation on the identifiers it must classify. The rule cannot skip those queries without changing what it reports. The per-type cache cuts the rule's own getProperties scanning from 266 ms to 98 ms.

Findings are identical

  • Plugin test suite: 116/116 passed. No test was changed, so every assertion is as strict as before.
  • koneko ESLint findings from the enumwaii rules are identical before and after: 0 = 0 on web and 0 = 0 on server, compared by file, line, column, rule and message.
  • The pnpm check chain passed (exit 0). It ran with format:check:core in place of format:check, because the root Prettier check also scans an untracked local .claude/worktrees checkout that is not part of this branch.

Checklist

  • The change is focused and linked to an issue when applicable.
  • Runtime and type tests cover behavior or inference changes. No behavior changes; the existing rule tests pass unchanged.
  • Public API changes include useful JSDoc and authored documentation. No public API change.
  • Lint rule changes include valid and invalid parser-service cases. The existing cases cover both rules and pass unchanged.
  • A Changeset is included for each user-visible published-package change, or this pull request explains why none is needed.
  • pnpm check passes.

…names

prefer-native-schema-adapters now checks that the member call's static
method name is a Zod or Valibot schema factory before asking the checker
for the resolved signature.
… queries

no-enumwaii-case-misuse resolves an identifier's type only after every
syntactic exclusion, resolves a member object's type only for accesses it
could report, and caches its cases-brand decision per TypeScript type.
@CatOfJupit3r

Copy link
Copy Markdown
Owner Author

image

@CatOfJupit3r
CatOfJupit3r merged commit 9a80af8 into main Oct 16, 2026
2 checks passed
@CatOfJupit3r
CatOfJupit3r deleted the perf/cheap-checks-before-type-queries branch October 16, 2026 18:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant