Skip to content

Commit

Permalink
docs: add dedicated TypeOrValueSpecifier docs page (typescript-eslint…
Browse files Browse the repository at this point in the history
…#9875)

* docs: add dedicated TypeOrValueSpecifier docs page

* spelling

* Unnecessary new test

* path/package copypasta

* push updated test snapshots
  • Loading branch information
JoshuaKGoldberg authored Aug 30, 2024
1 parent c27b9e9 commit e5d1ac4
Show file tree
Hide file tree
Showing 9 changed files with 219 additions and 29 deletions.
6 changes: 5 additions & 1 deletion .cspell.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,10 @@
"words": [
"Airbnb",
"Airbnb's",
"ambiently",
"allowdefaultproject",
"allowdefaultprojectforfiles",
"allowforknownsafecalls",
"allowforknownsafepromises",
"ambiently",
"Arka",
"Armano",
Expand Down Expand Up @@ -102,6 +103,7 @@
"estree",
"extrafileextensions",
"falsiness",
"filespecifier",
"fisker",
"García",
"globby",
Expand All @@ -115,6 +117,7 @@
"joshuakgoldberg",
"Katt",
"kirkwaiblinger",
"libspecifier",
"linebreaks",
"Lundberg",
"lzstring",
Expand All @@ -140,6 +143,7 @@
"OOM",
"OOMs",
"oxlint",
"packagespecifier",
"parameterised",
"performant",
"pluggable",
Expand Down
135 changes: 135 additions & 0 deletions docs/packages/type-utils/TypeOrValueSpecifier.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
---
id: type-or-value-specifier
title: TypeOrValueSpecifier
---

Some lint rules include options to describe specific _types_ and/or _values_.
These options use a standardized format exported from the `type-utils` package, **`TypeOrValueSpecifier`**.

`TypeOrValueSpecifier` allows three object forms of specifiers:

- [`FileSpecifier`](#filespecifier): for types or values declared in local files
- [`LibSpecifier`](#libspecifier): for types or values declared in TypeScript's built-in lib definitions
- [`PackageSpecifier`](#packagespecifier): for types or values imported from packages

For example, the following configuration of [`@typescript-eslint/no-floating-promises` > `allowForKnownSafeCalls`](/rules/no-floating-promises#allowforknownsafecalls) marks `node:test`'s `it` as safe using a package specifier:

```json
{
"@typescript-eslint/no-floating-promises": [
"error",
{
"allowForKnownSafeCalls": [
{ "from": "package", "name": "it", "package": "node:test" }
]
}
]
}
```

Each object format requires at least:

- `from`: which specifier to use, as `'file' | 'lib' | 'package'`
- `name`: a `string` or `string[]` for type or value name(s) to match on

## FileSpecifier

```ts
interface FileSpecifier {
from: 'file';
name: string[] | string;
path?: string;
}
```

Describes specific types or values declared in local files.

`path` may be used to specify a file the types or values must be declared in.
If omitted, all files will be matched.

### FileSpecifier Examples

Matching all types and values named `Props`:

```json
{ "from": "file", "name": "Props" }
```

Matching all types and values named `Props` in `file.tsx`:

```json
{ "from": "file", "name": "Props", "path": "file.tsx" }
```

## LibSpecifier

```ts
interface LibSpecifier {
from: 'lib';
name: string[] | string;
}
```

Describes specific types or values declared in TypeScript's built-in `lib.*.d.ts` ("lib") types.

Lib types include `lib.dom.d.ts` globals such as `Window` and `lib.es*.ts` globals such as `Array`.

### LibSpecifier Examples

Matching all array-typed values:

```json
{ "from": "lib", "name": "Array" }
```

Matching all `Promise` and `PromiseLike`-typed values:

```json
{ "from": "lib", "name": ["Promise", "PromiseLike"] }
```

## PackageSpecifier

```ts
interface PackageSpecifier {
from: 'package';
name: string[] | string;
package: string;
}
```

Describes specific types or values imported from packages.

`package` must be used to specify the package name.

### PackageSpecifier Examples

Matching the `SafePromise` type from `@reduxjs/toolkit`:

```json
{ "from": "package", "name": "SafePromise", "package": "@reduxjs/toolkit" }
```

Matching the `describe`, `it`, and `test` values from `vitest`:

```json
{ "from": "package", "name": ["describe", "it", "test"], "package": "vitest" }
```

## Universal String Specifiers

`TypeOrValueSpecifier` also allows providing a plain string specifier to match all names regardless of declaration source.
For example, providing `"RegExp"` matches _all_ types and values named `RegExp`.

:::danger
We strongly recommend not using universal string specifiers.
Matching _all_ names without specifying a source file, library, or package can accidentally match other types or values with a coincidentally similar name.

Universal string specifiers will be removed in a future major version of typescript-eslint.
:::

## Rule Options Using This Format

- [`@typescript-eslint/no-floating-promises` > `allowForKnownSafeCalls`](/rules/no-floating-promises#allowforknownsafecalls)
- [`@typescript-eslint/no-floating-promises` > `allowForKnownSafePromises`](/rules/no-floating-promises#allowforknownsafepromises)
- [`@typescript-eslint/prefer-readonly-parameter-types` > `allow`](/rules/prefer-readonly-parameter-types/#allow)
13 changes: 4 additions & 9 deletions packages/eslint-plugin/docs/rules/no-floating-promises.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -166,14 +166,9 @@ await (async function () {

### `allowForKnownSafePromises`

This option allows marking specific types as "safe" to be floating. For example, you may need to do this in the case of libraries whose APIs return Promises whose rejections are safely handled by the library.
Specific types to be marked as "safe" to be floating. For example, you may need to do this in the case of libraries whose APIs return Promises whose rejections are safely handled by the library.

This option takes an array of type specifiers to consider safe.
Each item in the array must have one of the following forms:

- A type defined in a file (`{ from: "file", name: "Foo", path: "src/foo-file.ts" }` with `path` being an optional path relative to the project root directory)
- A type from the default library (`{ from: "lib", name: "PromiseLike" }`)
- A type from a package (`{ from: "package", name: "Foo", package: "foo-lib" }`, this also works for types defined in a typings package).
This option takes the shared [`TypeOrValueSpecifier` format](/packages/type-utils/type-or-value-specifier).

Examples of code for this rule with:

Expand Down Expand Up @@ -223,10 +218,10 @@ returnsSafePromise();

### `allowForKnownSafeCalls`

This option allows marking specific functions as "safe" to be called to create floating Promises.
Specific functions to be marked as "safe" to be called to create floating Promises.
For example, you may need to do this in the case of libraries whose APIs may be called without handling the resultant Promises.

This option takes the same array format as [`allowForKnownSafePromises`](#allowForKnownSafePromises).
This option takes the shared [`TypeOrValueSpecifier` format](/packages/type-utils/type-or-value-specifier).

Examples of code for this rule with:

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -143,20 +143,13 @@ interface Foo {
An array of type specifiers to ignore.
Some complex types cannot easily be made readonly, for example the `HTMLElement` type or the `JQueryStatic` type from `@types/jquery`. This option allows you to globally disable reporting of such types.

Each item in the array must have one of the following forms:

- A type defined in a file (`{ from: "file", name: "Foo", path: "src/foo-file.ts" }` with `path` being an optional path relative to the project root directory)
- A type from the default library (`{ from: "lib", name: "Foo" }`)
- A type from a package (`{ from: "package", name: "Foo", package: "foo-lib" }`, this also works for types defined in a typings package).

Additionally, a type may be defined just as a simple string, which then matches the type independently of its origin.
This option takes the shared [`TypeOrValueSpecifier` format](/packages/type-utils/type-or-value-specifier).

Examples of code for this rule with:

```json
{
"allow": [
"$",
{ "from": "file", "name": "Foo" },
{ "from": "lib", "name": "HTMLElement" },
{ "from": "package", "name": "Bar", "package": "bar-lib" }
Expand All @@ -167,7 +160,7 @@ Examples of code for this rule with:
<Tabs>
<TabItem value="❌ Incorrect">

```ts option='{"allow":["$",{"from":"file","name":"Foo"},{"from":"lib","name":"HTMLElement"},{"from":"package","name":"Bar","package":"bar-lib"}]}'
```ts option='{"allow":[{"from":"file","name":"Foo"},{"from":"lib","name":"HTMLElement"},{"from":"package","name":"Bar","package":"bar-lib"}]}'
interface ThisIsMutable {
prop: string;
}
Expand All @@ -191,7 +184,7 @@ function fn2(arg: Wrapper) {}
function fn3(arg: WrapperWithOther) {}
```

```ts option='{"allow":["$",{"from":"file","name":"Foo"},{"from":"lib","name":"HTMLElement"},{"from":"package","name":"Bar","package":"bar-lib"}]}'
```ts option='{"allow":[{"from":"file","name":"Foo"},{"from":"lib","name":"HTMLElement"},{"from":"package","name":"Bar","package":"bar-lib"}]}'
import { Foo } from 'some-lib';
import { Bar } from 'incorrect-lib';

Expand All @@ -212,7 +205,7 @@ function fn3(arg: Bar) {}
</TabItem>
<TabItem value="✅ Correct">

```ts option='{"allow":["$",{"from":"file","name":"Foo"},{"from":"lib","name":"HTMLElement"},{"from":"package","name":"Bar","package":"bar-lib"}]}'
```ts option='{"allow":[{"from":"file","name":"Foo"},{"from":"lib","name":"HTMLElement"},{"from":"package","name":"Bar","package":"bar-lib"}]}'
interface Foo {
prop: string;
}
Expand All @@ -229,7 +222,7 @@ function fn1(arg: Foo) {}
function fn2(arg: Wrapper) {}
```

```ts option='{"allow":["$",{"from":"file","name":"Foo"},{"from":"lib","name":"HTMLElement"},{"from":"package","name":"Bar","package":"bar-lib"}]}'
```ts option='{"allow":[{"from":"file","name":"Foo"},{"from":"lib","name":"HTMLElement"},{"from":"package","name":"Bar","package":"bar-lib"}]}'
import { Bar } from 'bar-lib';

interface Foo {
Expand All @@ -246,7 +239,7 @@ function fn2(arg: HTMLElement) {}
function fn3(arg: Bar) {}
```

```ts option='{"allow":["$",{"from":"file","name":"Foo"},{"from":"lib","name":"HTMLElement"},{"from":"package","name":"Bar","package":"bar-lib"}]}'
```ts option='{"allow":[{"from":"file","name":"Foo"},{"from":"lib","name":"HTMLElement"},{"from":"package","name":"Bar","package":"bar-lib"}]}'
import { Foo } from './foo';

// Works because Foo is still a local type - it has to be in the same package
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

14 changes: 14 additions & 0 deletions packages/eslint-plugin/tests/rules/no-floating-promises.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -819,6 +819,20 @@ promise().then(() => {});
},
],
},
{
code: `
import { it } from 'node:test';
it('...', () => {});
`,
options: [
{
allowForKnownSafeCalls: [
{ from: 'package', name: 'it', package: 'node:test' },
],
},
],
},
{
code: `
interface SafePromise<T> extends Promise<T> {
Expand Down
36 changes: 36 additions & 0 deletions packages/type-utils/src/TypeOrValueSpecifier.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,23 +7,59 @@ import { typeDeclaredInFile } from './typeOrValueSpecifiers/typeDeclaredInFile';
import { typeDeclaredInLib } from './typeOrValueSpecifiers/typeDeclaredInLib';
import { typeDeclaredInPackageDeclarationFile } from './typeOrValueSpecifiers/typeDeclaredInPackageDeclarationFile';

/**
* Describes specific types or values declared in local files.
* See [TypeOrValueSpecifier > FileSpecifier](/packages/type-utils/type-or-value-specifier#filespecifier).
*/
export interface FileSpecifier {
from: 'file';

/**
* Type or value name(s) to match on.
*/
name: string[] | string;

/**
* A specific file the types or values must be declared in.
*/
path?: string;
}

/**
* Describes specific types or values declared in TypeScript's built-in lib definitions.
* See [TypeOrValueSpecifier > LibSpecifier](/packages/type-utils/type-or-value-specifier#libspecifier).
*/
export interface LibSpecifier {
from: 'lib';

/**
* Type or value name(s) to match on.
*/
name: string[] | string;
}

/**
* Describes specific types or values imported from packages.
* See [TypeOrValueSpecifier > PackageSpecifier](/packages/type-utils/type-or-value-specifier#packagespecifier).
*/
export interface PackageSpecifier {
from: 'package';

/**
* Type or value name(s) to match on.
*/
name: string[] | string;

/**
* Package name the type or value must be declared in.
*/
package: string;
}

/**
* A centralized format for rule options to describe specific _types_ and/or _values_.
* See [TypeOrValueSpecifier](/packages/type-utils/type-or-value-specifier).
*/
export type TypeOrValueSpecifier =
| FileSpecifier
| LibSpecifier
Expand Down
Loading

0 comments on commit e5d1ac4

Please sign in to comment.