Uh oh!
There was an error while loading.Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork2.8k
feat(eslint-plugin): split no-empty-object-type out from ban-types and no-empty-interfaces#8977
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
Uh oh!
There was an error while loading.Please reload this page.
Changes fromall commits
bcfbb03
173251c
b83cb50
39870f1
f90ab3f
0a5c2bd
c01f10e
f021d26
ca7fb0f
fad7a5e
e8a2d7f
3971d71
d33a246
28ed70d
9b6dd1a
e0bbd1d
b7a70b8
713404a
218d8e5
9987c2b
bd764ef
7dc88d7
File filter
Filter by extension
Conversations
Uh oh!
There was an error while loading.Please reload this page.
Jump to
Uh oh!
There was an error while loading.Please reload this page.
Diff view
Diff view
There are no files selected for viewing
JoshuaKGoldberg marked this conversation as resolved. Show resolvedHide resolvedUh oh!There was an error while loading.Please reload this page. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -36,9 +36,6 @@ const func: Function = () => 1; | ||
// use safer object types | ||
const lowerObj: Object = {}; | ||
const capitalObj: Object = { a: 'string' }; | ||
``` | ||
</TabItem> | ||
@@ -58,9 +55,6 @@ const func: () => number = () => 1; | ||
// use safer object types | ||
const lowerObj: object = {}; | ||
const capitalObj: { a: string } = { a: 'string' }; | ||
``` | ||
</TabItem> | ||
@@ -70,13 +64,10 @@ const curly2: Record<'a', string> = { a: 'string' }; | ||
The default options provide a set of "best practices", intended to provide safety and standardization in your codebase: | ||
- Don't use the upper-case primitive types or `Object`, you should use the lower-case types for consistency. | ||
- Avoid the `Function` type, as it provides little safety for the following reasons: | ||
- It provides no type safety when calling the value, which means it's easy to provide the wrong arguments. | ||
- It accepts class declarations, which will fail when called, as they are called without the `new` keyword. | ||
JoshuaKGoldberg marked this conversation as resolved. Show resolvedHide resolvedUh oh!There was an error while loading.Please reload this page. | ||
<details> | ||
<summary>Default Options</summary> | ||
@@ -136,3 +127,7 @@ Example configuration: | ||
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. | ||
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. | ||
## Related To | ||
- [`no-empty-object-type`](./no-empty-object-type.mdx) |
JoshuaKGoldberg marked this conversation as resolved. Show resolvedHide resolvedUh oh!There was an error while loading.Please reload this page.
JoshuaKGoldberg marked this conversation as resolved. Show resolvedHide resolvedUh oh!There was an error while loading.Please reload this page. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,145 @@ | ||
--- | ||
description: 'Disallow accidentally using the "empty object" type.' | ||
--- | ||
import Tabs from '@theme/Tabs'; | ||
import TabItem from '@theme/TabItem'; | ||
> 🛑 This file is source code, not the primary documentation location! 🛑 | ||
> | ||
> See **https://typescript-eslint.io/rules/no-empty-object-type** for documentation. | ||
The `{}`, or "empty object" type in TypeScript is a common source of confusion for developers unfamiliar with TypeScript's structural typing. | ||
`{}` represents any _non-nullish value_, including literals like `0` and `""`: | ||
```ts | ||
let anyNonNullishValue: {} = 'Intentionally allowed by TypeScript.'; | ||
JoshuaKGoldberg marked this conversation as resolved. Show resolvedHide resolvedUh oh!There was an error while loading.Please reload this page. | ||
``` | ||
Often, developers writing `{}` actually mean either: | ||
- `object`: representing any _object_ value | ||
- `unknown`: representing any value at all, including `null` and `undefined` | ||
In other words, the "empty object" type `{}` really means _"any value that is defined"_. | ||
That includes arrays, class instances, functions, and primitives such as `string` and `symbol`. | ||
To avoid confusion around the `{}` type allowing any _non-nullish value_, this rule bans usage of the `{}` type. | ||
That includes interfaces and object type aliases with no fields. | ||
:::tip | ||
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). | ||
::: | ||
JoshuaKGoldberg marked this conversation as resolved. Show resolvedHide resolvedUh oh!There was an error while loading.Please reload this page. | ||
Note that this rule does not report on: | ||
- `{}` 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. | ||
- Interfaces that extend from multiple other interfaces. | ||
## Examples | ||
<Tabs> | ||
<TabItem value="❌ Incorrect"> | ||
```ts | ||
let anyObject: {}; | ||
let anyValue: {}; | ||
interface AnyObjectA {} | ||
interface AnyValueA {} | ||
type AnyObjectB = {}; | ||
type AnyValueB = {}; | ||
``` | ||
</TabItem> | ||
<TabItem value="✅ Correct"> | ||
```ts | ||
let anyObject: object; | ||
let anyValue: unknown; | ||
type AnyObjectA = object; | ||
type AnyValueA = unknown; | ||
type AnyObjectB = object; | ||
type AnyValueB = unknown; | ||
let objectWith: { property: boolean }; | ||
interface InterfaceWith { | ||
property: boolean; | ||
} | ||
type TypeWith = { property: boolean }; | ||
``` | ||
</TabItem> | ||
</Tabs> | ||
## Options | ||
By default, this rule flags both interfaces and object types. | ||
### `allowInterfaces` | ||
Whether to allow empty interfaces, as one of: | ||
- `'always'`: to always allow interfaces with no fields | ||
- `'never'` _(default)_: to never allow interfaces with no fields | ||
- `'with-single-extends'`: to allow empty interfaces that `extend` from a single base interface | ||
Examples of **correct** code for this rule with `{ allowInterfaces: 'with-single-extends' }`: | ||
```ts option='{ "allowInterfaces": "with-single-extends" }' showPlaygroundButton | ||
interface Base { | ||
value: boolean; | ||
} | ||
interface Derived extends Base {} | ||
``` | ||
### `allowObjectTypes` | ||
Whether to allow empty object type literals, as one of: | ||
kirkwaiblinger marked this conversation as resolved. Show resolvedHide resolvedUh oh!There was an error while loading.Please reload this page. | ||
- `'always'`: to always allow object type literals with no fields | ||
- `'never'` _(default)_: to never allow object type literals with no fields | ||
### `allowWithName` | ||
A stringified regular expression to allow interfaces and object type aliases with the configured name. | ||
This can be useful if your existing code style includes a pattern of declaring empty types with `{}` instead of `object`. | ||
Examples of code for this rule with `{ allowWithName: 'Props$' }`: | ||
<Tabs> | ||
<TabItem value="❌ Incorrect"> | ||
```ts option='{ "allowWithName": "Props$" }' showPlaygroundButton | ||
interface InterfaceValue {} | ||
type TypeValue = {}; | ||
``` | ||
</TabItem> | ||
<TabItem value="✅ Correct"> | ||
```ts option='{ "allowWithName": "Props$" }' showPlaygroundButton | ||
interface InterfaceProps {} | ||
type TypeProps = {}; | ||
``` | ||
</TabItem> | ||
</Tabs> | ||
## When Not To Use It | ||
If your code commonly needs to represent the _"any non-nullish value"_ type, this rule may not be for you. | ||
Projects that extensively use type operations such as conditional types and mapped types oftentimes benefit from disabling this rule. | ||
## Further Reading | ||
- [Enhancement: [ban-types] Split the {} ban into a separate, better-phrased rule](https://github.com/typescript-eslint/typescript-eslint/issues/8700) | ||
- [The Empty Object Type in TypeScript](https://www.totaltypescript.com/the-empty-object-type-in-typescript) | ||
JoshuaKGoldberg marked this conversation as resolved. Show resolvedHide resolvedUh oh!There was an error while loading.Please reload this page. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -73,7 +73,10 @@ const defaultTypes: Types = { | ||
message: 'Use bigint instead', | ||
fixWith: 'bigint', | ||
}, | ||
Object: { | ||
message: 'Use object instead', | ||
fixWith: 'object', | ||
}, | ||
Function: { | ||
message: [ | ||
'The `Function` type accepts any function-like value.', | ||
@@ -82,32 +85,6 @@ const defaultTypes: Types = { | ||
'If you are expecting the function to accept certain arguments, you should explicitly define the function shape.', | ||
].join('\n'), | ||
}, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others.Learn more. We didn't explicitly discuss | ||
}; | ||
export const TYPE_KEYWORDS = { | ||
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -17,8 +17,9 @@ export default createRule<Options, MessageIds>({ | ||
type: 'suggestion', | ||
docs: { | ||
description: 'Disallow the declaration of empty interfaces', | ||
}, | ||
deprecated: true, | ||
JoshuaKGoldberg marked this conversation as resolved. Show resolvedHide resolvedUh oh!There was an error while loading.Please reload this page. | ||
replacedBy: ['@typescript-eslint/no-empty-object-type'], | ||
fixable: 'code', | ||
hasSuggestions: true, | ||
messages: { | ||
Uh oh!
There was an error while loading.Please reload this page.
Uh oh!
There was an error while loading.Please reload this page.