You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: docs/typescript.md
+74-67Lines changed: 74 additions & 67 deletions
Original file line number
Diff line number
Diff line change
@@ -8,20 +8,20 @@ These guidelines specifically apply to TypeScript.
8
8
9
9
TypeScript is very good at inferring types. It is capable of providing strict type safety while ensuring that explicit type annotations are the exception rather than the rule.
10
10
11
-
Some fundamental type information must always be supplied by the user, such as function and class signatures, interfaces for interacting with external entities, and types that express the domain model of the codebase.
11
+
Some fundamental type information must always be supplied by the user, such as function and class signatures, interfaces for interacting with external entities or data types, and types that express the domain model of the codebase.
12
12
13
13
However, for the remaining majority of types and values, **type inference should generally be preferred over type annotations and assertions**.
14
14
15
15
There are several reasons for this:
16
16
17
-
####Advantages
17
+
##### Type inference should be preferred over explicit annotations and assertions
18
18
19
19
- Explicit type annotations (`:`) and type assertions (`as`, `!`) prevent inference-based narrowing of the user-supplied types.
20
-
- The compiler errs on the side of trusting user input, which prevents it from providing additional, and sometimes crucial, type information that it could otherwise infer.
21
-
<!-- TODO: Expand into entry. Add example. -->
22
-
- In TypeScript v4.9+ the `satisfies` operator can be used to assign type constraints that are narrowable through type inference.
20
+
- The compiler errs on the side of trusting user input, which prevents it from providing additionaltype information that it could infer.
21
+
- In TypeScript v4.9+, the `satisfies` operator can be used to assign type constraints that are narrowable through type inference.
22
+
<!-- TODO: Add examples -->
23
23
- Type inferences are responsive to changes in code without requiring user input, while annotations and assertions rely on hard-coding, making them brittle against code drift.
24
-
- The `as const` operator can be used to further narrow an inferred abstract type into a specific literal type.
24
+
- The `as const` operator can be used to narrow an inferred abstract type into a specific literal type.
There is a clear exception to the above: if an explicit type annotation or assertion can narrow an inferred type further, thereby improving its accuracy, it should be applied.
97
+
An explicit type annotation or assertion should not be avoided if they can further narrow an inferred type.
98
98
99
-
##### Avoid widening a type with a type annotation
99
+
##### Avoid unintentionally widening a type with a type annotation
100
100
101
101
> **Warning**<br />
102
-
> Double-check that a declared type is narrower than the inferred type.<br />
103
-
> Enforcing an even wider type defeats the purpose of adding an explicit type annotation, as it _loses_ type information instead of adding it.
102
+
> Enforcing an even wider type defeats the purpose of adding an explicit type annotation, as it _loses_ type information instead of adding it.<br />
103
+
> Double-check that the declared type is narrower than the inferred type.
##### When instantiating an empty container type, provide a type annotation
122
122
123
-
This is one case where type inference is unable to reach a useful conclusion without user-provided information. Since the compiler cannot arbitrarily restrict the range of types that could be inserted into the container, it has to assume the widest type, which is often `any`. It's up to the user to narrow that into the intended type with an explicit type annotation.
123
+
This is one case where type inference is unable to reach a useful conclusion without user-provided information. Since the compiler cannot arbitrarily restrict the range of types that could be inserted into the container, it has to assume the widest type, which is often `any`. It's up to the user to narrow that into the intended type by adding an explicit annotation.
124
124
125
125
🚫
126
126
@@ -162,25 +162,27 @@ function f(x: SomeInterface | SomeOtherInterface) {
Theyshouldonlybeintroducedintothecodeiftheaccuratetypeis unreachable through other means.
166
+
165
167
##### Documentsafeornecessaryuseoftypeassertions
166
168
167
-
Whenatypeassertion is absolutely necessary due to constraints or is even safe due to runtime checks, we should document the reason for doing so.
169
+
Whenatypeassertion is absolutely necessary due to constraints or is even safe due to runtime checks, we should document the reasoning behind its usage in the form of a comment.
168
170
169
171
<!--TODO: Addexample-->
170
172
171
173
#### Avoid`as`
172
174
173
175
Typeassertionsmakethecodebrittleagainstchanges.
174
176
175
-
WhileTypeScriptwillthrowtypeerrors against some unsafe, structurally unsound, or redundant type assertions, it will generally accept the user-supplied type without type-checking.
177
+
WhileTypeScriptandESLintwillflagsomeunsafe, structurallyunsound, orredundanttypeassertions, they will generally accept user-supplied types without further type-checking.
176
178
177
-
Thiscancausesilentfailureswhereerrorsaresuppressed, eventhoughthetype's relationship to the rest of the code, or the type itself, may have been altered so that the type assertion is no longer valid.
179
+
Thiscancausesilentfailuresorfalsenegativeswhereerrorsaresuppressed. Thisisespeciallydamagingasthecodebaseaccumulateschangesovertime. Typeassertionsmaycontinuetosilenceerrors, eventhoughthetypeitself, or the type's relationship to the rest of the codemay have been altered so that the asserted type is no longer valid.
import { getKnownPropertyNames } from '@metamask/utils';
183
+
Typeassertionscanalsocausefalsepositives, becausetheyassertionsareindependentexpressions, untiedtothetypeerrors they were intended to fix. Thus, even if code drift fixes or removes a particular type error, the type assertions that were put in place to fix that error will provide no indication that they are no longer necessary and now should be removed.
183
184
185
+
```ts
184
186
enum Direction {
185
187
Up = 'up',
186
188
Down = 'down',
@@ -189,20 +191,9 @@ enum Direction {
189
191
}
190
192
const directions = Object.values(Direction);
191
193
192
-
// Element implicitly has an 'any' type because index expression is not of type 'number'.(7015)
193
-
for (const key of Object.keys(directions)) {
194
-
const direction = directions[key];
195
-
}
196
-
// Fix 1: use `as` assertion
197
-
for (const key of Object.keys(directions)) {
198
-
const direction = directions[key as keyof typeof directions];
199
-
}
200
-
// Fix 2: use `getKnownPropertyNames`
201
-
for (const key of getKnownPropertyNames(directions)) {
202
-
const direction = directions[key];
203
-
}
204
-
// Redundant `as` assertion does not trigger any warning
205
-
for (const key of getKnownPropertyNames(directions)) {
194
+
// Error: Element implicitly has an 'any' type because index expression is not of type 'number'.(7015)
195
+
// Only one of the two `as` assertions necessary to fix error, but neither are flagged as redundant.
196
+
for (const key of Object.keys(directions) as keyof directions[]) {
206
197
const direction = directions[key as keyof typeof directions];
207
198
}
208
199
```
@@ -213,17 +204,31 @@ for (const key of getKnownPropertyNames(directions)) {
-`any`subsumesallothertypesitcomesintocontactwith. Anytypethat is in a union, intersection, is a property of, or has any other relationship with an `any` type or value is erased and becomes an `any` type itself.
-Everytypeis assignable to `unknown`, but `unknown` is only assignable to `unknown`.
276
+
- When typing the _assignee_, `any` and `unknown` are completely interchangeable since every type is assignable to both.
277
+
- `any` usage is often motivated by a need to find a placeholder type that could be anything. `unknown` is the most likely type-safe substitute for `any` in these cases.
-Everytypeis assignable to `unknown`, but `unknown` is only assignable to `unknown`.
308
-
- When typing the _assignee_, `any` and `unknown` are completely interchangeable since every type is assignable to both.
309
-
- `any` usage is often motivated by a need to find a placeholder type that could be anything. `unknown` is the most likely type-safe substitute for `any` in these cases.
0 commit comments