Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

docs: blog post on Avoidinganys with Linting and TypeScript#10362

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to ourterms of service andprivacy statement. We’ll occasionally send you account related emails.

Already on GitHub?Sign in to your account

Merged
Show file tree
Hide file tree
Changes fromall commits
Commits
Show all changes
27 commits
Select commitHold shift + click to select a range
85d4fa4
[WIP] docs: blog post on catching any
JoshuaKGoldbergNov 20, 2024
5475369
Seems legit
JoshuaKGoldbergJan 2, 2025
6b8c357
Merge branch 'main' into blog-post-catching-any
JoshuaKGoldbergJan 2, 2025
162b3cc
Update packages/website/blog/2025-01-13-catching-any.mdx
JoshuaKGoldbergJan 3, 2025
a33dc85
Apply suggestions from code review
JoshuaKGoldbergJan 7, 2025
3da18e7
Apply suggestions from code review
JoshuaKGoldbergJan 7, 2025
8450d97
A bit of intro rephrasing
JoshuaKGoldbergJan 7, 2025
5862c33
no-unsafe-call block
JoshuaKGoldbergJan 7, 2025
0b1541c
Merge branch 'main' into blog-post-catching-any
JoshuaKGoldbergJan 12, 2025
e62fb94
Rename to 'Avoiding', and better truncate
JoshuaKGoldbergJan 13, 2025
b1ee81d
Pluralize slug
JoshuaKGoldbergJan 13, 2025
8546630
Merge branch 'main' into blog-post-catching-any
JoshuaKGoldbergJan 13, 2025
8e0cd0b
add anys to cspell.json words
JoshuaKGoldbergJan 13, 2025
55726f5
404 fixes: catching-any -> avoiding-any
JoshuaKGoldbergJan 13, 2025
0447cb0
title fixes: catching-any -> avoiding-any
JoshuaKGoldbergJan 13, 2025
4acb807
Final slug fixes: avoiding-any -> avoiding-anys
JoshuaKGoldbergJan 13, 2025
ddb88ab
Apply suggestions from code review
JoshuaKGoldbergJan 16, 2025
2c61661
More improvements
JoshuaKGoldbergJan 16, 2025
a985afd
Agreed, 'absolutely' is a bit much
JoshuaKGoldbergJan 16, 2025
ea11fa7
around any safety
JoshuaKGoldbergJan 16, 2025
0e2faf3
It can either be a deep dive or an overview, not both
JoshuaKGoldbergJan 16, 2025
e6f5af9
It's a blog post; add back no-throw
JoshuaKGoldbergJan 16, 2025
1bb13d6
Bit of an opening reword, again
JoshuaKGoldbergJan 16, 2025
4c21a9b
sort footnotes
JoshuaKGoldbergJan 16, 2025
8f77f28
more process
kirkwaiblingerJan 19, 2025
97433d6
Update packages/website/blog/2025-01-19-avoiding-anys.mdx
JoshuaKGoldbergJan 20, 2025
6ed0cb0
Schedule post for the 21st
JoshuaKGoldbergJan 20, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions.cspell.json
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -55,6 +55,7 @@
"allowforknownsafepromises",
"ambiently",
"Amiel",
"anys",
"Arka",
"Armano",
"astexplorer",
Expand Down
1 change: 1 addition & 0 deletionspackages/eslint-plugin/docs/rules/no-explicit-any.mdx
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -162,6 +162,7 @@ You might consider using [ESLint disable comments](https://eslint.org/docs/lates

## Related To

- [Avoiding `any`s with Linting and TypeScript](/blog/avoiding-anys)
- [`no-unsafe-argument`](./no-unsafe-argument.mdx)
- [`no-unsafe-assignment`](./no-unsafe-assignment.mdx)
- [`no-unsafe-call`](./no-unsafe-call.mdx)
Expand Down
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -90,6 +90,7 @@ You might consider using [ESLint disable comments](https://eslint.org/docs/lates

## Related To

- [Avoiding `any`s with Linting and TypeScript](/blog/avoiding-anys)
- [`no-explicit-any`](./no-explicit-any.mdx)
- [`no-unsafe-assignment`](./no-unsafe-assignment.mdx)
- [`no-unsafe-call`](./no-unsafe-call.mdx)
Expand Down
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -93,6 +93,7 @@ You might consider using [ESLint disable comments](https://eslint.org/docs/lates

## Related To

- [Avoiding `any`s with Linting and TypeScript](/blog/avoiding-anys)
- [`no-explicit-any`](./no-explicit-any.mdx)
- [`no-unsafe-argument`](./no-unsafe-argument.mdx)
- [`no-unsafe-call`](./no-unsafe-call.mdx)
Expand Down
1 change: 1 addition & 0 deletionspackages/eslint-plugin/docs/rules/no-unsafe-call.mdx
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -112,6 +112,7 @@ You might consider using [ESLint disable comments](https://eslint.org/docs/lates

## Related To

- [Avoiding `any`s with Linting and TypeScript](/blog/avoiding-anys)
- [`no-explicit-any`](./no-explicit-any.mdx)
- [`no-unsafe-argument`](./no-unsafe-argument.mdx)
- [`no-unsafe-assignment`](./no-unsafe-assignment.mdx)
Expand Down
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -58,6 +58,7 @@ You might consider using [ESLint disable comments](https://eslint.org/docs/lates

## Related To

- [Avoiding `any`s with Linting and TypeScript](/blog/avoiding-anys)
- [`no-empty-object-type`](./no-empty-object-type.mdx)
- [`no-restricted-types`](./no-restricted-types.mdx)
- [`no-unsafe-call`](./no-unsafe-call.mdx)
Expand Down
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -73,6 +73,7 @@ You might consider using [ESLint disable comments](https://eslint.org/docs/lates

## Related To

- [Avoiding `any`s with Linting and TypeScript](/blog/avoiding-anys)
- [`no-explicit-any`](./no-explicit-any.mdx)
- [`no-unsafe-argument`](./no-unsafe-argument.mdx)
- [`no-unsafe-assignment`](./no-unsafe-assignment.mdx)
Expand Down
1 change: 1 addition & 0 deletionspackages/eslint-plugin/docs/rules/no-unsafe-return.mdx
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -118,6 +118,7 @@ You might consider using [ESLint disable comments](https://eslint.org/docs/lates

## Related To

- [Avoiding `any`s with Linting and TypeScript](/blog/avoiding-anys)
- [`no-explicit-any`](./no-explicit-any.mdx)
- [`no-unsafe-argument`](./no-unsafe-argument.mdx)
- [`no-unsafe-assignment`](./no-unsafe-assignment.mdx)
Expand Down
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -91,3 +91,7 @@ For further reading on this, you may also want to look into
If your codebase is not yet able to enable `useUnknownInCatchVariables`, it likely would be similarly difficult to enable this rule.

If you have modified the global type declarations in order to make `then()` and `catch()` callbacks use the `unknown` type without an explicit type annotation, you do not need this rule.

## Related To

- [Avoiding `any`s with Linting and TypeScript](/blog/avoiding-anys)
288 changes: 288 additions & 0 deletionspackages/website/blog/2025-01-21-avoiding-anys.mdx
View file
Open in desktop
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,288 @@
---
authors: joshuakgoldberg
description: How typescript-eslint expands on TypeScript's type safety to catch explicit and implicit `any`s.
slug: avoiding-anys
tags: [any, no-explicit-any, no-unsafe, noImplicitAny, typed linting]
title: 'Avoiding `any`s with Linting and TypeScript'
---

import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';

TypeScript's `any` type is, by design, the single most unsafe part of its type system.
The `any` type indicates a type that can be anything and can be used in place of any other type.
Using `any` is unsafe because the type disables many of TypeScript's type-checking features and hampers TypeScript's ability to provide developer assistance.

typescript-eslint includes several lint rules that help prevent unsafe practices around the `any` type.
These rules flag uses of `any` or code patterns that sneakily introduce it.

In this blog post, we'll show you those lint rules and several other handy ways to prevent `any`s from sneaking into your code.

{/* truncate */}

## `noImplicitAny` Isn't Enough

TypeScript includes a [`noImplicitAny`](https://www.typescriptlang.org/tsconfig/#noImplicitAny) flag to report when a better type than `any` can't be inferred for a value.
`noImplicitAny` is part of its family of [`strict` compiler options](https://www.typescriptlang.org/tsconfig/#strict) and is generally recommended for all projects.

However, even with `noImplicitAny` enabled, the `any` type can easily be introduced into codebases.
`noImplicitAny` doesn't prevent developers from explicitly writing the `any` type in type type annotations.
Some built-in APIs such as `JSON.stringify` and types such as `Function` can introduce the `any` type without triggering `noImplicitAny`.

Enabling `noImplicitAny` is a great first step towards better project type safety, but it's not enough to prevent `any`s altogether.

## Banning Unsafe Types

The first line of defense against `any` for many repositories is adding lint rules that report on explicitly written unsafe types.
Developers can always [disable ESLint rules with inline comments](https://eslint.org/docs/latest/use/configure/rules#using-configuration-comments-1), so this isn't guaranteed to prevent explicit `any`s from popping up.
But these lint rules put an immediate restriction on unsafe types -- and help guide towards better alternatives.

### Banning Explicit `any`s

[`@typescript-eslint/no-explicit-any`](/rules/no-explicit-any) reports on any instance of the `any` type written in your source code.
Doing so helps prevent developers from using `any` instead of a more safe type.

Take the following unsafe declaration of `friend: any` in a `greet` function.
Because its `friend` parameter is typed as `any` instead of `string`, TypeScript won't report a type error on a call that provides another type.
`@typescript-eslint/no-explicit-any` would report on that `any`:

```ts
function greet(friend: any) {
// ~~~
// eslint(@typescript-eslint/no-explicit-any):
// Unexpected any. Specify a different type.
console.log(`Hello, ${friend.toUpperCase()}!`);
}

greet('Lazlo'); // Ok

greet({ name: 'Nadya' }); // Should be a type error, but isn't
```

Instead of `any`, it would have been more type safe to give `friend` the more precise type `string`.

#### Prefer `unknown`

If you have data that is of an unknown type, instead of describing it with the unsafe `any` type, prefer the safer `unknown` instead.
`unknown` is almost always preferable because it doesn't allow using the value in any arbitrary, potentially unsafe way.

[`@typescript-eslint/no-explicit-any`](/rules/no-explicit-any)'s rule reports include a suggestion fixer that switches the explicit `any` to `unknown`.

### Banning `Function`

[`@typescript-eslint/no-unsafe-function-type`](/rules/no-unsafe-function-type) reports on any instance of the built-in `Function` type written in source code.
`Function` is a loose, unsafe type: it allows being called with any number of arguments and returns type `any`.

Take the following version of `greet` that takes in a function for its parameter.
`Function` doesn't describe any parameter or return types, so TypeScript can't know what types it's meant to take in or return.
`@typescript-eslint/no-unsafe-function-type` would report on that `Function`:

```ts
function greet(getFriend: Function) {
// ~~~~~~~~
// eslint(@typescript-eslint/no-unsafe-function-type):
// The `Function` type accepts any function-like value.
// Prefer explicitly defining any function parameters and return type.
console.log(`Hello, ${getFriend().toUpperCase()}!`);
}

greet(() => 'Lazlo'); // Ok

greet(() => ({ name: 'Nadya' })); // Should be a type error, but isn't
```

Instead of `Function`, it would have been more type safe to give `getFriend` the more precise type `() => string`.

### Enforcing `unknown` for Caught Exceptions

There is no way to indicate what types functions may throw in TypeScript, due to the highly dynamic nature of JavaScript[^no-throws].
Therefore, TypeScript treats all caught values as `any` by default.
TypeScript's [`useUnknownInCatchVariables`](https://www.typescriptlang.org/tsconfig/#useUnknownInCatchVariables) changes `catch` block variables to have the more appropriate `unknown` type, but no compiler option equivalent exists for `Promise` rejections' `.catch()` method.

[`@typescript-eslint/use-unknown-in-catch-callback-variable`](/rules/use-unknown-in-catch-callback-variable) enforces always using the `unknown` type for the parameter of a Promise rejection callback.

For example, given the following code, TypeScript would not report a type error, but the lint rule would report:

```ts
function rejectWith(value: string) {
return Promise.reject(value);
}

rejectWith('Nandor').catch(error => {
// ~~~~~
// eslint(@typescript-eslint/use-unknown-in-catch-callback-variable):
// Prefer the safe `: unknown` for a `catch` callback variable.

console.log(error.message); // Should be a type error, but isn't
});
```

Had the `error` been given a `: unknown` type, TypeScript would be able to report on `error.message` as being unsafe.

## Banning Usage of Unsafe Types

Once an `any` type exists in code, it is "infectious": it can turn the types of values based on it into more `any`s.

typescript-eslint includes a collection of rules that flag code patterns which make use of `any`'s unsafe, viral nature.
Each of the following rules reports on a specific use of `any`.

- [`@typescript-eslint/no-unsafe-argument`](/rules/no-unsafe-argument) reports on passing an `any` typed value to a function call
- [`@typescript-eslint/no-unsafe-assignment`](/rules/no-unsafe-assignment) reports on assigning an `any` typed value to a property or variable
- [`@typescript-eslint/no-unsafe-call`](/rules/no-unsafe-call) reports on calling an `any` typed value like a function
- [`@typescript-eslint/no-unsafe-member-access`](/rules/no-unsafe-member-access) reports on accessing members of any value typed as `any`
- [`@typescript-eslint/no-unsafe-return`](/rules/no-unsafe-return) reports on returning a value typed as `any` from a function

For example, this `parseData` function violates several of those lint rules by creating a `shape` value of type `any` and not validating its type before using it:

```ts
export interface Shape {
label: string;
value: number;
}

export function parseShapeFromData(raw: string): Shape {
const shape = JSON.parse(raw);
// ~~~~~~~~~~~~~~~~~~~~~~~
// eslint(@typescript-eslint/no-unsafe-assignment):
// Unsafe assignment of an `any` value.

console.log('Making a shape with value:', shape.value);
// ~~~~~
// eslint(@typescript-eslint/no-unsafe-member-access):
// Unsafe member access .value of an `any` value.

return shape;
// eslint(@typescript-eslint/no-unsafe-return):
// Unsafe return of an `any` value.
}
```

Had the `parseShapeFromData` function included checks on the type of the `shape` value or used a schema validation library such as [Zod](https://zod.dev), it would be informed at runtime if its `raw` string didn't create the expected `Shape`.

Put together, the `@typescript-eslint/no-unusafe-*` rules around `any` safety provide a comprehensive suite of checks that can flag most accidental uses of `any` across a codebase.
We highly recommend using them alongside TypeScript's `noImplicitAny` compiler option.

## Additional Helpers

### Disabling TypeScript Suppressions

The `any` type is not the only way to bypass TypeScript's type system.
Developers are also able to use inline TypeScript directives: `// @ts-expect-error` and `// @ts-ignore`.
Those inline comment directives cause TypeScript to ignore type errors on a line.
Disabling TypeScript on a per-line basis can sometimes be necessary in rare cases, but is always unsafe and should not be part of your standard toolkit.

:::tip
`// @ts-expect-error` and `// @ts-ignore` are almost the same, except `// @ts-expect-error` will produce a new error if there isn't any existing error for it to suppress.
It's generally preferable to use `// @ts-expect-error` over `// @ts-ignore`.
:::

[`@typescript-eslint/ban-comment`](/rules/ban-ts-comment) can be used to report on cases where developers use TypeScript comment directives.
By default, the rule:

- Prohibits all `@ts-ignore` and `@ts-nocheck` comment directives
- Prohibits `@ts-expect-error` directives unless they have an explanatory comment description
- Requires explanatory comment descriptions to contain at least 3 characters

For example, suppose `"@example/package"` exports a `processString` function that should take in a `string` but whose types incorrectly indicate take in a `number`.
A `// @ts-expect-error` comment could be used to tell TypeScript to ignore the line:

<Tabs>
<TabItem value="❌ Incorrect">

```ts
import { processString } from '@example/package';

// @ts-ignore
processString('New York City');
```

</TabItem>
<TabItem value="✅ Correct">

```ts
import { processString } from '@example/package';

// @ts-expect-error -- Pending updating the processString types. See GH-1234.
processString('New York City');
```

</TabItem>
</Tabs>

Once the types for `@example/package` are fixed to no longer produce a type error, the comment directive will itself produce a type error asking to delete itself.

### ESLint Comments Plugin

ESLint includes its own inline comment directives separate from TypeScript's.
`// eslint-disable`, `// eslint-disable-next-line`, and other inline ESLint config comments can suppress lint rules — including those mentioned in this post.
Suppressing ESLint rules can also be necessary in cases where the rule and/or TypeScript's type system have misunderstand your code.

If you must use ESLint comment directives, we recommend utilizing [`@eslint-community/eslint-plugin-eslint-comments`](https://eslint-community.github.io/eslint-plugin-eslint-comments).
The plugin's [recommended preset](https://eslint-community.github.io/eslint-plugin-eslint-comments/#%F0%9F%93%96-usage) enables rules that enforce best practices around ESLint comment directives, including:

- [`@eslint-community/eslint-comments/disable-enable-pair`](https://eslint-community.github.io/eslint-plugin-eslint-comments/rules/disable-enable-pair.html): to prevent accidentally disabling rules for the rest of a file, rather than on specific lines
- [`@eslint-community/eslint-comments/no-unlimited-disable`](https://eslint-community.github.io/eslint-plugin-eslint-comments/rules/no-unlimited-disable.html): to prevent accidentally disabling all ESLint rules, rather than specific ones
- [`@eslint-community/eslint-comments/require-description`](https://eslint-community.github.io/eslint-plugin-eslint-comments/rules/require-description.html): to require comments explaining why directives are necessary

For example, suppose the type of an imported variable is temporarily `any` pending some soon-to-be-completed refactoring.
It might be reasonable to include an ESLint disable comment to suppress a lint rule reporting on the `any`.
That comment should ideally include an explanation and link to the pending task, so developers know it's not normal to disable lint rules without good reason:

<Tabs>
<TabItem value="❌ Incorrect">

```ts
import { processString } from '@example/package';

// eslint-disable-next-line @typescript-eslint/no-unsafe-call
processString('New York City');
```

</TabItem>
<TabItem value="✅ Correct">

```ts
import { processString } from '@example/package';

// Pending GH-1234: will soon not be any.
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
processString('New York City');
```

</TabItem>
</Tabs>

Once the types for `@example/package` are fixed to no longer produce an `any`, running ESLint with [`reportUnusedDisableDirectives`](https://eslint.org/docs/latest/use/configure/rules#report-unused-eslint-disable-comments) option will produce a lint report asking to remove the comment directive.

### `ts-reset`

Some sources of `any` come from built-in global types provided by TypeScript.
[`JSON.parse()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/parse), [`.json()`](https://developer.mozilla.org/en-US/docs/Web/API/Response/json), and [`Storage`](https://developer.mozilla.org/en-US/docs/Web/API/Web_Storage_API/Using_the_Web_Storage_API#basic_concepts) properties are all typed as `any` by default in TypeScript.
TypeScript keeps `any` in the types for legacy support reasons[^compiler-option-lib-any].

The [`ts-reset`](https://www.totaltypescript.com/ts-reset) library switches those global type definitions to safer equivalents.
It switches the `any`s in those built-in types to `unknown`.

With `ts-reset`, the following `data` variable would switch from being type `any` to `unknown`:

```ts
const data = JSON.parse(`"clearly-a-string"`);
// ^? any (without ts-reset)
// ^? unknown (with ts-reset)

console.log(data.some.property.that.does.not.exist);
```

Note that `ts-reset` applies globally, so it should only be used in application code.

## Next Steps

We highly recommend using at least the [`tseslint.configs.recommendedTypeChecked`](/users/configs#recommended-type-checked) preset in your ESLint configuration.
It enables the `no-explicit-any` and `no-unsafe-*` lint rules mentioned in this blog post, as well as a large set of other rules that help enforce type safety and TypeScript best practices.

If you're interested in achieving stronger type safety with more strict linting, consider upgrading to the [`tseslint.configs.strictTypeChecked`](/users/configs#strict-type-checked) preset.
It includes all the recommended rules, as well as `use-unknown-incatch-callback-variable` and other rules that enforce more strict type safety and TypeScript best practices.

[^compiler-option-lib-any]: [microsoft/TypeScript#60899 Compiler option to switch lib .d.ts anys to unknown](https://github.com/microsoft/TypeScript/issues/60899)

[^no-throws]: [Why TypeScript Doesn't Include a `throws` Keyword](https://www.learningtypescript.com/articles/why-typescript-doesnt-include-a-throws-keyword)
Loading

[8]ページ先頭

©2009-2025 Movatter.jp