Skip to content

perf(no-unnecessary-type-parameters): stop counting settled candidates#967

Merged
graphite-app[bot] merged 1 commit into
mainfrom
perf/no-unnecessary-type-parameters-code-only
May 18, 2026
Merged

perf(no-unnecessary-type-parameters): stop counting settled candidates#967
graphite-app[bot] merged 1 commit into
mainfrom
perf/no-unnecessary-type-parameters-code-only

Conversation

@camc314

@camc314 camc314 commented May 18, 2026

Copy link
Copy Markdown
Contributor

Improves no-unnecessary-type-parameters performance by avoiding the expensive recursive type-usage walk for type parameters that are already obviously safe to skip.

The rule already had an AST-based fast path:

isTypeParameterRepeatedInAST(...)

but it ran after countTypeParameterUsage, which is the expensive checker-heavy part. This meant we were paying the full recursive type analysis cost even for type parameters that could be ruled out cheaply.

This PR moves that cheap filtering earlier, then narrows the expensive pass to only the remaining candidates.

Why This Is Faster

The slow path is countTypeParameterUsage, which recursively walks TypeScript types and calls into the checker. On large projects like VS Code, most generic declarations are not reportable because their type parameters are used more than once in the signature.

Before this change, the rule did this for every generic declaration:

  1. recursively count type parameter usage with checker types
  2. collect AST references
  3. skip if the AST already proves the type parameter is repeated

That order wastes work. If the AST can prove the type parameter appears multiple times before the function body, then the rule will not report it, so there is no reason to do the recursive checker walk first.

After this change, the rule does this instead:

  1. collect AST references
  2. keep only type parameters that are still possible reports
  3. recursively count usage only for those candidates
  4. stop counting once every candidate has exceeded the reportable threshold

This keeps the same behavior, but avoids a lot of type-checker and map work on declarations that are never going to produce diagnostics.

Benchmark

Measured with a shallow clone of microsoft/vscode, using 6,187 src/**/*.ts(x) files through local tsgolint headless.

Environment:

  • rule: no-unnecessary-type-parameters

Hyperfine, 3 runs:

Binary Mean User System
baseline 11.333s ± 0.061s 43.635s 0.992s
patched 4.002s ± 0.078s 14.775s 0.570s

That is a 2.83x speedup for this isolated rule workload.

fixes #961

@camc314 camc314 marked this pull request as ready for review May 18, 2026 06:58
Copilot AI review requested due to automatic review settings May 18, 2026 06:58

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Optimizes the no-unnecessary-type-parameters rule by running the cheap AST-based filter before the expensive checker-based type-usage walk, and stopping that walk early once every remaining candidate has been proven safe.

Changes:

  • Move AST reference collection / isTypeParameterRepeatedInAST filter ahead of countTypeParameterUsage, then pass only surviving candidates as targetSymbols.
  • Add a remainingTargets counter threaded through collectTypeParameterUsageCounts so type/signature traversal short-circuits once each target has exceeded the reportable threshold (>2 usages).
  • Refactor reporting loop to operate on the precomputed candidateTypeParameter list (node, name, symbol, references) instead of recomputing per type parameter.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@camc314 camc314 self-assigned this May 18, 2026

camc314 commented May 18, 2026

Copy link
Copy Markdown
Contributor Author

Merge activity

#967)

Improves `no-unnecessary-type-parameters` performance by avoiding the expensive recursive type-usage walk for type parameters that are already obviously safe to skip.

The rule already had an AST-based fast path:

 ```go
isTypeParameterRepeatedInAST(...)
```

but it ran after countTypeParameterUsage, which is the expensive checker-heavy part. This meant we were paying the full recursive type analysis cost even for type parameters that could be ruled out cheaply.

This PR moves that cheap filtering earlier, then narrows the expensive pass to only the remaining candidates.

## Why This Is Faster

The slow path is countTypeParameterUsage, which recursively walks TypeScript types and calls into the checker. On large projects like VS Code, most generic declarations are not reportable because their type parameters are used more than once in the signature.

Before this change, the rule did this for every generic declaration:

 1. recursively count type parameter usage with checker types
 2. collect AST references
 3. skip if the AST already proves the type parameter is repeated

That order wastes work. If the AST can prove the type parameter appears multiple times before the function body, then the rule will not report it, so there is no reason to do the recursive checker walk first.

After this change, the rule does this instead:

 1. collect AST references
 2. keep only type parameters that are still possible reports
 3. recursively count usage only for those candidates
 4. stop counting once every candidate has exceeded the reportable threshold

This keeps the same behavior, but avoids a lot of type-checker and map work on declarations that are never going to produce diagnostics.

## Benchmark

Measured with a shallow clone of microsoft/vscode, using 6,187 src/**/*.ts(x) files through local tsgolint headless.

Environment:

- rule: no-unnecessary-type-parameters

Hyperfine, 3 runs:

| Binary | Mean | User | System |
| --- | ---: | ---: | ---: |
| baseline | 11.333s ± 0.061s | 43.635s | 0.992s |
| patched | 4.002s ± 0.078s | 14.775s | 0.570s |

That is a 2.83x speedup for this isolated rule workload.

fixes #961
@graphite-app graphite-app Bot force-pushed the perf/no-unnecessary-type-parameters-code-only branch from 8a576a5 to bb8886b Compare May 18, 2026 07:12
@graphite-app graphite-app Bot merged commit bb8886b into main May 18, 2026
8 of 9 checks passed
@graphite-app graphite-app Bot removed the 0-merge label May 18, 2026
@graphite-app graphite-app Bot deleted the perf/no-unnecessary-type-parameters-code-only branch May 18, 2026 07:18
@connorshea

Copy link
Copy Markdown
Member

Thank you!!

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.

typescript/no-unnecessary-type-parameters rule is quite slow

3 participants