Skip to content

Commit fd097ef

Browse files
JoshuaKGoldbergkirkwaiblingerJosh-Cena
authored
feat(eslint-plugin): split no-empty-object-type out from ban-types and no-empty-interfaces (typescript-eslint#8977)
* feat(eslint-plugin): split no-empty-object-type rule out from ban-types rule * Mention no-props * Update packages/eslint-plugin/docs/rules/no-empty-object-type.mdx Co-authored-by: Kirk Waiblinger <53019676+kirkwaiblinger@users.noreply.github.com> * Update packages/eslint-plugin/docs/rules/no-empty-object-type.mdx Co-authored-by: Kirk Waiblinger <53019676+kirkwaiblinger@users.noreply.github.com> * Allow in intersections, and touch up docs per suggestions * Update packages/eslint-plugin/docs/rules/no-empty-object-type.mdx Co-authored-by: Joshua Chen <sidachen2003@gmail.com> * Trimming * Update snapshots * Finish removing Record * Correct phrasing on Object * Merge with no-empty-interface * nit the tip * Explicit report message for interfaces * Add in-type-alias-with-name * Switched to more general allowWithName * snapshot -u * snapshot -u * Fixed up unit tests * Update packages/eslint-plugin/docs/rules/no-empty-object-type.mdx Co-authored-by: Kirk Waiblinger <kirk.waiblinger@gmail.com> * docs touchups from Kirk * replacedBy too --------- Co-authored-by: Kirk Waiblinger <53019676+kirkwaiblinger@users.noreply.github.com> Co-authored-by: Joshua Chen <sidachen2003@gmail.com> Co-authored-by: Kirk Waiblinger <kirk.waiblinger@gmail.com>
1 parent 04f1938 commit fd097ef

31 files changed

+1104
-114
lines changed

packages/eslint-plugin/TSLINT_RULE_ALTERNATIVES.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ It lists all TSLint rules along side rules from the ESLint ecosystem that are th
2424
| [`member-access`] || [`@typescript-eslint/explicit-member-accessibility`] |
2525
| [`member-ordering`] || [`@typescript-eslint/member-ordering`] |
2626
| [`no-any`] || [`@typescript-eslint/no-explicit-any`] |
27-
| [`no-empty-interface`] || [`@typescript-eslint/no-empty-interface`] |
27+
| [`no-empty-interface`] || [`@typescript-eslint/no-empty-object-type`] |
2828
| [`no-import-side-effect`] | 🔌 | [`import/no-unassigned-import`] |
2929
| [`no-inferrable-types`] || [`@typescript-eslint/no-inferrable-types`] |
3030
| [`no-internal-module`] || [`@typescript-eslint/prefer-namespace-keyword`] |
@@ -604,7 +604,7 @@ Relevant plugins: [`chai-expect-keywords`](https://github.com/gavinaiken/eslint-
604604
[`@typescript-eslint/member-ordering`]: https://typescript-eslint.io/rules/member-ordering
605605
[`@typescript-eslint/method-signature-style`]: https://typescript-eslint.io/rules/method-signature-style
606606
[`@typescript-eslint/no-explicit-any`]: https://typescript-eslint.io/rules/no-explicit-any
607-
[`@typescript-eslint/no-empty-interface`]: https://typescript-eslint.io/rules/no-empty-interface
607+
[`@typescript-eslint/no-empty-object-type`]: https://typescript-eslint.io/rules/no-empty-object-type
608608
[`@typescript-eslint/no-implied-eval`]: https://typescript-eslint.io/rules/no-implied-eval
609609
[`@typescript-eslint/no-inferrable-types`]: https://typescript-eslint.io/rules/no-inferrable-types
610610
[`@typescript-eslint/prefer-namespace-keyword`]: https://typescript-eslint.io/rules/prefer-namespace-keyword

packages/eslint-plugin/docs/rules/ban-types.mdx

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,6 @@ const func: Function = () => 1;
3636
// use safer object types
3737
const lowerObj: Object = {};
3838
const capitalObj: Object = { a: 'string' };
39-
40-
const curly1: {} = 1;
41-
const curly2: {} = { a: 'string' };
4239
```
4340

4441
</TabItem>
@@ -58,9 +55,6 @@ const func: () => number = () => 1;
5855
// use safer object types
5956
const lowerObj: object = {};
6057
const capitalObj: { a: string } = { a: 'string' };
61-
62-
const curly1: number = 1;
63-
const curly2: Record<'a', string> = { a: 'string' };
6458
```
6559

6660
</TabItem>
@@ -70,13 +64,10 @@ const curly2: Record<'a', string> = { a: 'string' };
7064

7165
The default options provide a set of "best practices", intended to provide safety and standardization in your codebase:
7266

73-
- Don't use the upper-case primitive types, you should use the lower-case types for consistency.
67+
- Don't use the upper-case primitive types or `Object`, you should use the lower-case types for consistency.
7468
- Avoid the `Function` type, as it provides little safety for the following reasons:
7569
- It provides no type safety when calling the value, which means it's easy to provide the wrong arguments.
7670
- It accepts class declarations, which will fail when called, as they are called without the `new` keyword.
77-
- Avoid the `Object` and `{}` types, as they mean "any non-nullish value".
78-
- This is a point of confusion for many developers, who think it means "any object type".
79-
- See [this comment for more information](https://github.com/typescript-eslint/typescript-eslint/issues/2063#issuecomment-675156492).
8071

8172
<details>
8273
<summary>Default Options</summary>
@@ -136,3 +127,7 @@ Example configuration:
136127

137128
If your project is a rare one that intentionally deals with the class equivalents of primitives, it might not be worthwhile to enable the default `ban-types` options.
138129
You might consider using [ESLint disable comments](https://eslint.org/docs/latest/use/configure/rules#using-configuration-comments-1) for those specific situations instead of completely disabling this rule.
130+
131+
## Related To
132+
133+
- [`no-empty-object-type`](./no-empty-object-type.mdx)

packages/eslint-plugin/docs/rules/no-empty-interface.mdx

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,12 @@ import TabItem from '@theme/TabItem';
99
>
1010
> See **https://typescript-eslint.io/rules/no-empty-interface** for documentation.
1111
12+
:::danger Deprecated
13+
14+
This rule has been deprecated in favour of the more comprehensive [`@typescript-eslint/no-empty-object-type`](./no-empty-object-type.mdx) rule.
15+
16+
:::
17+
1218
An empty interface in TypeScript does very little: any non-nullable value is assignable to `{}`.
1319
Using an empty interface is often a sign of programmer error, such as misunderstanding the concept of `{}` or forgetting to fill in fields.
1420

@@ -61,3 +67,7 @@ interface Baz extends Foo, Bar {}
6167
## When Not To Use It
6268

6369
If you don't care about having empty/meaningless interfaces, then you will not need this rule.
70+
71+
## Related To
72+
73+
- [`no-empty-object-type`](./no-empty-object-type.mdx)
Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
---
2+
description: 'Disallow accidentally using the "empty object" type.'
3+
---
4+
5+
import Tabs from '@theme/Tabs';
6+
import TabItem from '@theme/TabItem';
7+
8+
> 🛑 This file is source code, not the primary documentation location! 🛑
9+
>
10+
> See **https://typescript-eslint.io/rules/no-empty-object-type** for documentation.
11+
12+
The `{}`, or "empty object" type in TypeScript is a common source of confusion for developers unfamiliar with TypeScript's structural typing.
13+
`{}` represents any _non-nullish value_, including literals like `0` and `""`:
14+
15+
```ts
16+
let anyNonNullishValue: {} = 'Intentionally allowed by TypeScript.';
17+
```
18+
19+
Often, developers writing `{}` actually mean either:
20+
21+
- `object`: representing any _object_ value
22+
- `unknown`: representing any value at all, including `null` and `undefined`
23+
24+
In other words, the "empty object" type `{}` really means _"any value that is defined"_.
25+
That includes arrays, class instances, functions, and primitives such as `string` and `symbol`.
26+
27+
To avoid confusion around the `{}` type allowing any _non-nullish value_, this rule bans usage of the `{}` type.
28+
That includes interfaces and object type aliases with no fields.
29+
30+
:::tip
31+
If you do have a use case for an API allowing `{}`, you can always configure the [rule's options](#options), use an [ESLint disable comment](https://eslint.org/docs/latest/use/configure/rules#using-configuration-comments-1), or [disable the rule in your ESLint config](https://eslint.org/docs/latest/use/configure/rules#using-configuration-files-1).
32+
:::
33+
34+
Note that this rule does not report on:
35+
36+
- `{}` as a type constituent in an intersection type (e.g. types like TypeScript's built-in `type NonNullable<T> = T & {}`), as this can be useful in type system operations.
37+
- Interfaces that extend from multiple other interfaces.
38+
39+
## Examples
40+
41+
<Tabs>
42+
<TabItem value="❌ Incorrect">
43+
44+
```ts
45+
let anyObject: {};
46+
let anyValue: {};
47+
48+
interface AnyObjectA {}
49+
interface AnyValueA {}
50+
51+
type AnyObjectB = {};
52+
type AnyValueB = {};
53+
```
54+
55+
</TabItem>
56+
<TabItem value="✅ Correct">
57+
58+
```ts
59+
let anyObject: object;
60+
let anyValue: unknown;
61+
62+
type AnyObjectA = object;
63+
type AnyValueA = unknown;
64+
65+
type AnyObjectB = object;
66+
type AnyValueB = unknown;
67+
68+
let objectWith: { property: boolean };
69+
70+
interface InterfaceWith {
71+
property: boolean;
72+
}
73+
74+
type TypeWith = { property: boolean };
75+
```
76+
77+
</TabItem>
78+
</Tabs>
79+
80+
## Options
81+
82+
By default, this rule flags both interfaces and object types.
83+
84+
### `allowInterfaces`
85+
86+
Whether to allow empty interfaces, as one of:
87+
88+
- `'always'`: to always allow interfaces with no fields
89+
- `'never'` _(default)_: to never allow interfaces with no fields
90+
- `'with-single-extends'`: to allow empty interfaces that `extend` from a single base interface
91+
92+
Examples of **correct** code for this rule with `{ allowInterfaces: 'with-single-extends' }`:
93+
94+
```ts option='{ "allowInterfaces": "with-single-extends" }' showPlaygroundButton
95+
interface Base {
96+
value: boolean;
97+
}
98+
99+
interface Derived extends Base {}
100+
```
101+
102+
### `allowObjectTypes`
103+
104+
Whether to allow empty object type literals, as one of:
105+
106+
- `'always'`: to always allow object type literals with no fields
107+
- `'never'` _(default)_: to never allow object type literals with no fields
108+
109+
### `allowWithName`
110+
111+
A stringified regular expression to allow interfaces and object type aliases with the configured name.
112+
This can be useful if your existing code style includes a pattern of declaring empty types with `{}` instead of `object`.
113+
114+
Examples of code for this rule with `{ allowWithName: 'Props$' }`:
115+
116+
<Tabs>
117+
<TabItem value="❌ Incorrect">
118+
119+
```ts option='{ "allowWithName": "Props$" }' showPlaygroundButton
120+
interface InterfaceValue {}
121+
122+
type TypeValue = {};
123+
```
124+
125+
</TabItem>
126+
<TabItem value="✅ Correct">
127+
128+
```ts option='{ "allowWithName": "Props$" }' showPlaygroundButton
129+
interface InterfaceProps {}
130+
131+
type TypeProps = {};
132+
```
133+
134+
</TabItem>
135+
</Tabs>
136+
137+
## When Not To Use It
138+
139+
If your code commonly needs to represent the _"any non-nullish value"_ type, this rule may not be for you.
140+
Projects that extensively use type operations such as conditional types and mapped types oftentimes benefit from disabling this rule.
141+
142+
## Further Reading
143+
144+
- [Enhancement: [ban-types] Split the {} ban into a separate, better-phrased rule](https://github.com/typescript-eslint/typescript-eslint/issues/8700)
145+
- [The Empty Object Type in TypeScript](https://www.totaltypescript.com/the-empty-object-type-in-typescript)

packages/eslint-plugin/src/configs/all.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ export = {
5454
'@typescript-eslint/no-dynamic-delete': 'error',
5555
'no-empty-function': 'off',
5656
'@typescript-eslint/no-empty-function': 'error',
57-
'@typescript-eslint/no-empty-interface': 'error',
57+
'@typescript-eslint/no-empty-object-type': 'error',
5858
'@typescript-eslint/no-explicit-any': 'error',
5959
'@typescript-eslint/no-extra-non-null-assertion': 'error',
6060
'@typescript-eslint/no-extraneous-class': 'error',

packages/eslint-plugin/src/configs/recommended-type-checked.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ export = {
1818
'@typescript-eslint/no-base-to-string': 'error',
1919
'@typescript-eslint/no-duplicate-enum-values': 'error',
2020
'@typescript-eslint/no-duplicate-type-constituents': 'error',
21+
'@typescript-eslint/no-empty-object-type': 'error',
2122
'@typescript-eslint/no-explicit-any': 'error',
2223
'@typescript-eslint/no-extra-non-null-assertion': 'error',
2324
'@typescript-eslint/no-floating-promises': 'error',

packages/eslint-plugin/src/configs/recommended.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ export = {
1515
'no-array-constructor': 'off',
1616
'@typescript-eslint/no-array-constructor': 'error',
1717
'@typescript-eslint/no-duplicate-enum-values': 'error',
18+
'@typescript-eslint/no-empty-object-type': 'error',
1819
'@typescript-eslint/no-explicit-any': 'error',
1920
'@typescript-eslint/no-extra-non-null-assertion': 'error',
2021
'@typescript-eslint/no-misused-new': 'error',

packages/eslint-plugin/src/configs/strict-type-checked.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ export = {
2424
'@typescript-eslint/no-duplicate-enum-values': 'error',
2525
'@typescript-eslint/no-duplicate-type-constituents': 'error',
2626
'@typescript-eslint/no-dynamic-delete': 'error',
27+
'@typescript-eslint/no-empty-object-type': 'error',
2728
'@typescript-eslint/no-explicit-any': 'error',
2829
'@typescript-eslint/no-extra-non-null-assertion': 'error',
2930
'@typescript-eslint/no-extraneous-class': 'error',

packages/eslint-plugin/src/configs/strict.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ export = {
1919
'@typescript-eslint/no-array-constructor': 'error',
2020
'@typescript-eslint/no-duplicate-enum-values': 'error',
2121
'@typescript-eslint/no-dynamic-delete': 'error',
22+
'@typescript-eslint/no-empty-object-type': 'error',
2223
'@typescript-eslint/no-explicit-any': 'error',
2324
'@typescript-eslint/no-extra-non-null-assertion': 'error',
2425
'@typescript-eslint/no-extraneous-class': 'error',

packages/eslint-plugin/src/configs/stylistic-type-checked.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@ export = {
2323
'@typescript-eslint/no-confusing-non-null-assertion': 'error',
2424
'no-empty-function': 'off',
2525
'@typescript-eslint/no-empty-function': 'error',
26-
'@typescript-eslint/no-empty-interface': 'error',
2726
'@typescript-eslint/no-inferrable-types': 'error',
2827
'@typescript-eslint/non-nullable-type-assertion-style': 'error',
2928
'@typescript-eslint/prefer-for-of': 'error',

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy