Skip to content

feat(eslint): enable @typescript-eslint/no-unnecessary-type-parameters#112404

Merged
JoshuaKGoldberg merged 5 commits intomasterfrom
tseslint-no-unnecessary-type-parameters
Apr 9, 2026
Merged

feat(eslint): enable @typescript-eslint/no-unnecessary-type-parameters#112404
JoshuaKGoldberg merged 5 commits intomasterfrom
tseslint-no-unnecessary-type-parameters

Conversation

@JoshuaKGoldberg
Copy link
Copy Markdown
Member

@JoshuaKGoldberg JoshuaKGoldberg commented Apr 7, 2026

Enables @typescript-eslint/no-unnecessary-type-parameters internally. This enforces the "Golden Rule of Generics":

Rule: If a type parameter only appears in one location, strongly reconsider if you actually need it.

Summarizing the rule's docs (would recommend reading, especially the FAQs) and that excellent blog post (definitely would recommend the read!): if a type parameter (commonly: <T>) is only used in one place, it's not actually useful. Single-use type parameters almost always either do nothing or mask an unsafe type assertion.

I added explainer comments inline in the PR.

Fixes ENG-7007

@github-actions github-actions bot added the Scope: Frontend Automatically applied to PRs that change frontend components label Apr 7, 2026
@linear-code
Copy link
Copy Markdown

linear-code bot commented Apr 7, 2026

}

export function getEscapedKey<Value extends SelectKey | undefined>(value: Value): string {
export function getEscapedKey(value: SelectKey): string {
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

[Explanation] This is an example of a type parameter that did nothing. Value didn't change the function's return type at all. So value's type effectively already was SelectKey.

export function useOrganizationRepositories<T extends Repository = Repository>(
{query = {}} = {} as Props
) {
export function useOrganizationRepositories({query = {}} = {} as Props) {
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

[Explanation] This, too, is an example of a type parameter doing nothing. T shows up later in the function, but those uses don't impact the return type. https://typescript-eslint.io/rules/no-unnecessary-type-parameters/#im-using-the-type-parameter-inside-the-function-so-why-is-the-rule-reporting


type KnownDataDetails = Omit<KeyValueListDataItem, 'key'> | undefined;

// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-parameters
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

[Explanation] This is used in a few places and seems to be a kind of intentional "just give me the value as a specific type". I tried for a couple minutes to fix it locally but it was nontrivial.

threshold: 0.6,
location: 0,
distance: 100,
maxPatternLength: 32,
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

[Explanation] This is, I believe, legitimately dead code now detected by TypeScript!

Before, createFuzzySearch in fuzzySearch.tsx had a type parameter for its options. That meant any arbitrary property like maxPatternLength would be allowed and counted as part of the type argument.

Now that options is only ever Fuse.IFuseOptions<T>, unknown property names are not allowed. maxPatternLength was removed in Fuse v5. https://github.com/krisk/Fuse/blob/main/CHANGELOG.md#version-500-beta


/**
* Identity function that casts the raw completion data to a typed shape.
* Identity function that asserts the raw completion data to a typed shape.
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

[Explanation] This is super nitpicky, please forgive me: but assertions and casts are not the same. This function asserts (type system only, unsafe). It doesn't cast (runtime change, safe in this context).

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

good call out

Comment thread static/app/components/searchSyntax/parser.tsx Outdated
return function WithDomainRedirectWrapper(props: P) {
export function withDomainRedirect(WrappedComponent: RouteComponent) {
// eslint-disable-next-line @typescript-eslint/no-restricted-types
return function WithDomainRedirectWrapper(props: object) {
Copy link
Copy Markdown
Member Author

@JoshuaKGoldberg JoshuaKGoldberg Apr 7, 2026

Choose a reason for hiding this comment

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

[Explanation] Our ESLint config includes an option for @typescript-eslint/no-restricted-types copied from the old defaults for @typescript-eslint/ban-types:

sentry/eslint.config.ts

Lines 657 to 661 in 2795da8

object: {
message:
'The `object` type is hard to use. Use `Record<PropertyKey, unknown>` instead. See: https://github.com/typescript-eslint/typescript-eslint/pull/848',
fixWith: 'Record<PropertyKey, unknown>',
},

See https://typescript-eslint.io/blog/revamping-the-ban-types-rule: that old value is no longer the recommendation. Mucking with that is out of scope for this PR, I think.

): ChildType | null;
findChild(predicate: (child: BaseNode) => boolean): BaseNode | null;
findChild<ChildType extends BaseNode = BaseNode>(
predicate: (child: BaseNode) => child is ChildType
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

[Explanation] These methods are used in roughly two types situations, corresponding to the two function overloads in order:

  1. Oftentimes they're given a true type predicate. This gives us a nicely narrowed return type, so we put it first as the preferred form.
  2. As a fallback, if the function just returns boolean (no type system indication as to what type the value is), we just return the same broad type as the value.

Aside: I haven't had a good excuse to write function overloads with generics in a while. It was nice to actually have a use for that knowledge, for once. 🥲

P.S. https://effectivetypescript.com/2024/02/27/type-guards is a great article on type predicates.

@JoshuaKGoldberg JoshuaKGoldberg force-pushed the tseslint-no-unnecessary-type-parameters branch from 0fb330c to d590db9 Compare April 7, 2026 21:02
@JoshuaKGoldberg JoshuaKGoldberg marked this pull request as ready for review April 7, 2026 21:10
@JoshuaKGoldberg JoshuaKGoldberg requested review from a team as code owners April 7, 2026 21:10
@JoshuaKGoldberg JoshuaKGoldberg removed request for a team April 7, 2026 21:10
@JoshuaKGoldberg JoshuaKGoldberg removed request for a team April 7, 2026 21:10
Comment thread static/gsApp/views/subscriptionPage/pendingChanges.tsx
Comment thread static/app/utils/analytics/makeAnalyticsFunction.tsx Outdated
Comment on lines +177 to 178
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-parameters
export function getApiQueryData<TResponseData>(
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

love this! It surfaces type parameters that are type assertions in disguise ❤️

Comment thread static/gsApp/views/subscriptionPage/pendingChanges.tsx
Comment thread static/gsApp/utils/trackGetsentryAnalytics.tsx Outdated
… static/gsApp/utils/trackSpendVisibilityAnalytics.tsx
Copy link
Copy Markdown
Contributor

@cursor cursor bot left a comment

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 8422c6d. Configure here.

Comment thread static/app/utils/useLocalStorageState.ts Outdated
@JoshuaKGoldberg JoshuaKGoldberg merged commit abe7c05 into master Apr 9, 2026
62 checks passed
@JoshuaKGoldberg JoshuaKGoldberg deleted the tseslint-no-unnecessary-type-parameters branch April 9, 2026 11:58
george-sentry pushed a commit that referenced this pull request Apr 9, 2026
#112404)

Enables
[`@typescript-eslint/no-unnecessary-type-parameters`](https://typescript-eslint.io/rules/no-unnecessary-type-parameters)
internally. This enforces the ["Golden Rule of
Generics"](https://effectivetypescript.com/2020/08/12/generics-golden-rule):

> **Rule: If a type parameter only appears in one location, strongly
reconsider if you actually need it.**

Summarizing the rule's docs (would recommend reading, especially the
FAQs) and that excellent blog post (definitely would recommend the
read!): if a type parameter (commonly: `<T>`) is only used in one place,
it's not actually useful. Single-use type parameters almost always
either do nothing or mask an unsafe type assertion.

I added explainer comments inline in the PR.

Fixes ENG-7007
@JoshuaKGoldberg
Copy link
Copy Markdown
Member Author

Fixes ENG-7256.

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

Labels

Scope: Frontend Automatically applied to PRs that change frontend components

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants