Skip to content

Commit dc1c6d3

Browse files
docs: [no-unnecessary-type-parameters] add FAQ section (#9975)
* [no-unnecessary-type-parameters] add FAQ section * wording * wording * update admonition link * test case * stuf * prettierer * test case formatting * log * mention 9735 * explain the equal false positives a little better * add implicit return type FAQ
1 parent e8555a0 commit dc1c6d3

File tree

2 files changed

+149
-2
lines changed

2 files changed

+149
-2
lines changed

packages/eslint-plugin/docs/rules/no-unnecessary-type-parameters.mdx

Lines changed: 122 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ At best unnecessary type parameters make code harder to read.
1919
At worst they can be used to disguise unsafe type assertions.
2020

2121
:::warning
22-
This rule was recently added, and has a surprising amount of hidden complexity compared to most of our rules. If you encounter unexpected behavior with it, please check closely the [Limitations](#limitations) section below and our [issue tracker](https://github.com/typescript-eslint/typescript-eslint/issues?q=is%3Aissue+no-unnecessary-type-parameters).
22+
This rule was recently added, and has a surprising amount of hidden complexity compared to most of our rules. If you encounter unexpected behavior with it, please check closely the [Limitations](#limitations) and [FAQ](#faq) sections below and our [issue tracker](https://github.com/typescript-eslint/typescript-eslint/issues?q=is%3Aissue+no-unnecessary-type-parameters).
2323
If you don't see your case covered, please [reach out to us](https://typescript-eslint.io/contributing/issues)!
2424
:::
2525

@@ -87,6 +87,127 @@ This is because the type parameter `T` relates multiple methods in the `T[]` tog
8787
Therefore, this rule won't report on type parameters used as a type argument.
8888
That includes type arguments given to global types such as `Array` (including the `T[]` shorthand and in tuples), `Map`, and `Set`.
8989

90+
## FAQ
91+
92+
### The return type is only used as an input, so why isn't the rule reporting?
93+
94+
One common reason that this might be the case is when the return type is not specified explicitly.
95+
The rule uses uses type information to count implicit usages of the type parameter in the function signature, including in the inferred return type.
96+
For example, the following function...
97+
98+
```ts
99+
function identity<T>(arg: T) {
100+
return arg;
101+
}
102+
```
103+
104+
...implicitly has a return type of `T`. Therefore, the type parameter `T` is used twice, and the rule will not report this function.
105+
106+
For other reasons the rule might not be reporting, be sure to check the [Limitations section](#limitations) and other FAQs.
107+
108+
### I'm using the type parameter inside the function, so why is the rule reporting?
109+
110+
You might be surprised to that the rule reports on a function like this:
111+
112+
```ts
113+
function log<T extends string>(string1: T): void {
114+
const string2: T = string1;
115+
console.log(string2);
116+
}
117+
```
118+
119+
After all, the type parameter `T` relates the input `string1` and the local variable `string2`, right?
120+
However, this usage is unnecessary, since we can achieve the same results by replacing all usages of the type parameter with its constraint.
121+
That is to say, the function can always be rewritten as:
122+
123+
```ts
124+
function log(string1: string): void {
125+
const string2: string = string1;
126+
console.log(string2);
127+
}
128+
```
129+
130+
Therefore, this rule only counts usages of a type parameter in the _signature_ of a function, method, or class, but not in the implementation. See also [#9735](https://github.com/typescript-eslint/typescript-eslint/issues/9735)
131+
132+
### Why am I getting TypeScript errors saying "Object literal may only specify known properties" after removing an unnecessary type parameter?
133+
134+
Suppose you have a situation like the following, which will trigger the rule to report.
135+
136+
```ts
137+
interface SomeProperties {
138+
foo: string;
139+
}
140+
141+
// T is only used once, so the rule will report.
142+
function serialize<T extends SomeProperties>(x: T): string {
143+
return JSON.stringify(x);
144+
}
145+
146+
serialize({ foo: 'bar', anotherProperty: 'baz' });
147+
```
148+
149+
If we remove the unnecessary type parameter, we'll get an error:
150+
151+
```ts
152+
function serialize(x: SomeProperties): string {
153+
return JSON.stringify(x);
154+
}
155+
156+
// TS Error: Object literal may only specify known properties, and 'anotherProperty' does not exist in type 'SomeProperties'.
157+
serialize({ foo: 'bar', anotherProperty: 'baz' });
158+
```
159+
160+
This is because TypeScript figures it's _usually_ an error to explicitly provide excess properties in a location that expects a specific type.
161+
See [the TypeScript handbook's section on excess property checks](https://www.typescriptlang.org/docs/handbook/2/objects.html#excess-property-checks) for further discussion.
162+
163+
To resolve this, you have two approaches to choose from.
164+
165+
1. If it doesn't make sense to accept excess properties in your function, you'll want to fix the errors at the call sites. Usually, you can simply remove any excess properties where the function is called.
166+
2. Otherwise, if you do want your function to accept excess properties, you can modify the parameter type in order to allow excess properties explicitly by using an [index signature](https://www.typescriptlang.org/docs/handbook/2/objects.html#index-signatures):
167+
168+
```ts
169+
interface SomeProperties {
170+
foo: string;
171+
172+
// This allows any other properties.
173+
// You may wish to make these types more specific according to your use case.
174+
[key: PropertKey]: unknown;
175+
}
176+
177+
function serialize(x: SomeProperties): string {
178+
return JSON.stringify(x);
179+
}
180+
181+
// No error!
182+
serialize({ foo: 'bar', anotherProperty: 'baz' });
183+
```
184+
185+
Which solution is appropriate is a case-by-case decision, depending on the intended use case of your function.
186+
187+
### I have a complex scenario that is reported by the rule, but I can't see how to remove the type parameter. What should I do?
188+
189+
Sometimes, you may be able to rewrite the code by reaching for some niche TypeScript features, such as [the `NoInfer<T>` utility type](https://www.typescriptlang.org/docs/handbook/utility-types.html#noinfertype) (see [#9751](https://github.com/typescript-eslint/typescript-eslint/issues/9751)).
190+
191+
But, quite possibly, you've hit an edge case where the type is being used in a subtle way that the rule doesn't account for.
192+
For example, the following arcane code is a way of testing whether two types are equal, and will be reported by the rule (see [#9709](https://github.com/typescript-eslint/typescript-eslint/issues/9709)):
193+
194+
{/* prettier-ignore */}
195+
```ts
196+
type Compute<A> = A extends Function ? A : { [K in keyof A]: Compute<A[K]> };
197+
type Equal<X, Y> =
198+
(<T1>() => T1 extends Compute<X> ? 1 : 2) extends
199+
(<T2>() => T2 extends Compute<Y> ? 1 : 2)
200+
? true
201+
: false;
202+
```
203+
204+
In this case, the function types created within the `Equal` type are never expected to be assigned to; they're just created for the purpose of type system manipulations.
205+
This usage is not what the rule is intended to analyze.
206+
207+
Use eslint-disable comments as appropriate to suppress the rule in these kinds of cases.
208+
209+
{/* TODO - include an FAQ entry regarding instantiation expressions once the conversation in https://github.com/typescript-eslint/typescript-eslint/pull/9536#discussion_r1705850744 is done */}
210+
90211
## When Not To Use It
91212

92213
This rule will report on functions that use type parameters solely to test types, for example:

packages/eslint-plugin/tests/rules/no-unnecessary-type-parameters.test.ts

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { RuleTester } from '@typescript-eslint/rule-tester';
1+
import { noFormat, RuleTester } from '@typescript-eslint/rule-tester';
22

33
import rule from '../../src/rules/no-unnecessary-type-parameters';
44
import { getFixturesRootDir } from '../RuleTester';
@@ -970,5 +970,31 @@ const f = <T,>(
970970
},
971971
],
972972
},
973+
{
974+
// This isn't actually an important test case.
975+
// However, we use it as an example in the docs of code that is flagged,
976+
// but shouldn't necessarily be. So, if you make a change to the rule logic
977+
// that resolves this sort-of-false-positive, please update the docs
978+
// accordingly.
979+
// Original discussion in https://github.com/typescript-eslint/typescript-eslint/issues/9709
980+
code: noFormat`
981+
type Compute<A> = A extends Function ? A : { [K in keyof A]: Compute<A[K]> };
982+
type Equal<X, Y> =
983+
(<T1>() => T1 extends Compute<X> ? 1 : 2) extends
984+
(<T2>() => T2 extends Compute<Y> ? 1 : 2)
985+
? true
986+
: false;
987+
`,
988+
errors: [
989+
{
990+
messageId: 'sole',
991+
data: { descriptor: 'function', name: 'T1', uses: 'used only once' },
992+
},
993+
{
994+
messageId: 'sole',
995+
data: { descriptor: 'function', name: 'T2', uses: 'used only once' },
996+
},
997+
],
998+
},
973999
],
9741000
});

0 commit comments

Comments
 (0)