diff --git a/packages/eslint-plugin/TSLINT_RULE_ALTERNATIVES.md b/packages/eslint-plugin/TSLINT_RULE_ALTERNATIVES.md index 8276fabd922..d6dd55f8b9e 100644 --- a/packages/eslint-plugin/TSLINT_RULE_ALTERNATIVES.md +++ b/packages/eslint-plugin/TSLINT_RULE_ALTERNATIVES.md @@ -24,7 +24,7 @@ It lists all TSLint rules along side rules from the ESLint ecosystem that are th | [`member-access`] | ✅ | [`@typescript-eslint/explicit-member-accessibility`] | | [`member-ordering`] | ✅ | [`@typescript-eslint/member-ordering`] | | [`no-any`] | ✅ | [`@typescript-eslint/no-explicit-any`] | -| [`no-empty-interface`] | ✅ | [`@typescript-eslint/no-empty-interface`] | +| [`no-empty-interface`] | ✅ | [`@typescript-eslint/no-empty-object-type`] | | [`no-import-side-effect`] | 🔌 | [`import/no-unassigned-import`] | | [`no-inferrable-types`] | ✅ | [`@typescript-eslint/no-inferrable-types`] | | [`no-internal-module`] | ✅ | [`@typescript-eslint/prefer-namespace-keyword`] | @@ -604,7 +604,7 @@ Relevant plugins: [`chai-expect-keywords`](https://github.com/gavinaiken/eslint- [`@typescript-eslint/member-ordering`]: https://typescript-eslint.io/rules/member-ordering [`@typescript-eslint/method-signature-style`]: https://typescript-eslint.io/rules/method-signature-style [`@typescript-eslint/no-explicit-any`]: https://typescript-eslint.io/rules/no-explicit-any -[`@typescript-eslint/no-empty-interface`]: https://typescript-eslint.io/rules/no-empty-interface +[`@typescript-eslint/no-empty-object-type`]: https://typescript-eslint.io/rules/no-empty-object-type [`@typescript-eslint/no-implied-eval`]: https://typescript-eslint.io/rules/no-implied-eval [`@typescript-eslint/no-inferrable-types`]: https://typescript-eslint.io/rules/no-inferrable-types [`@typescript-eslint/prefer-namespace-keyword`]: https://typescript-eslint.io/rules/prefer-namespace-keyword diff --git a/packages/eslint-plugin/docs/rules/ban-types.mdx b/packages/eslint-plugin/docs/rules/ban-types.mdx index da30f495911..b52ae22df7d 100644 --- a/packages/eslint-plugin/docs/rules/ban-types.mdx +++ b/packages/eslint-plugin/docs/rules/ban-types.mdx @@ -36,9 +36,6 @@ const func: Function = () => 1; // use safer object types const lowerObj: Object = {}; const capitalObj: Object = { a: 'string' }; - -const curly1: {} = 1; -const curly2: {} = { a: 'string' }; ``` @@ -58,9 +55,6 @@ const func: () => number = () => 1; // use safer object types const lowerObj: object = {}; const capitalObj: { a: string } = { a: 'string' }; - -const curly1: number = 1; -const curly2: Record<'a', string> = { a: 'string' }; ``` @@ -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, you should use the lower-case types for consistency. +- 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. -- Avoid the `Object` and `{}` types, as they mean "any non-nullish value". - - This is a point of confusion for many developers, who think it means "any object type". - - See [this comment for more information](https://github.com/typescript-eslint/typescript-eslint/issues/2063#issuecomment-675156492).
Default Options @@ -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) diff --git a/packages/eslint-plugin/docs/rules/no-empty-interface.mdx b/packages/eslint-plugin/docs/rules/no-empty-interface.mdx index ad240237ddb..733d8b39ebe 100644 --- a/packages/eslint-plugin/docs/rules/no-empty-interface.mdx +++ b/packages/eslint-plugin/docs/rules/no-empty-interface.mdx @@ -9,6 +9,12 @@ import TabItem from '@theme/TabItem'; > > See **https://typescript-eslint.io/rules/no-empty-interface** for documentation. +:::danger Deprecated + +This rule has been deprecated in favour of the more comprehensive [`@typescript-eslint/no-empty-object-type`](./no-empty-object-type.mdx) rule. + +::: + An empty interface in TypeScript does very little: any non-nullable value is assignable to `{}`. Using an empty interface is often a sign of programmer error, such as misunderstanding the concept of `{}` or forgetting to fill in fields. @@ -61,3 +67,7 @@ interface Baz extends Foo, Bar {} ## When Not To Use It If you don't care about having empty/meaningless interfaces, then you will not need this rule. + +## Related To + +- [`no-empty-object-type`](./no-empty-object-type.mdx) diff --git a/packages/eslint-plugin/docs/rules/no-empty-object-type.mdx b/packages/eslint-plugin/docs/rules/no-empty-object-type.mdx new file mode 100644 index 00000000000..8bf941a6661 --- /dev/null +++ b/packages/eslint-plugin/docs/rules/no-empty-object-type.mdx @@ -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.'; +``` + +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). +::: + +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 & {}`), as this can be useful in type system operations. +- Interfaces that extend from multiple other interfaces. + +## Examples + + + + +```ts +let anyObject: {}; +let anyValue: {}; + +interface AnyObjectA {} +interface AnyValueA {} + +type AnyObjectB = {}; +type AnyValueB = {}; +``` + + + + +```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 }; +``` + + + + +## 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: + +- `'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$' }`: + + + + +```ts option='{ "allowWithName": "Props$" }' showPlaygroundButton +interface InterfaceValue {} + +type TypeValue = {}; +``` + + + + +```ts option='{ "allowWithName": "Props$" }' showPlaygroundButton +interface InterfaceProps {} + +type TypeProps = {}; +``` + + + + +## 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) diff --git a/packages/eslint-plugin/src/configs/all.ts b/packages/eslint-plugin/src/configs/all.ts index 71db2c523b2..0ac9c3653d9 100644 --- a/packages/eslint-plugin/src/configs/all.ts +++ b/packages/eslint-plugin/src/configs/all.ts @@ -54,7 +54,7 @@ export = { '@typescript-eslint/no-dynamic-delete': 'error', 'no-empty-function': 'off', '@typescript-eslint/no-empty-function': 'error', - '@typescript-eslint/no-empty-interface': 'error', + '@typescript-eslint/no-empty-object-type': 'error', '@typescript-eslint/no-explicit-any': 'error', '@typescript-eslint/no-extra-non-null-assertion': 'error', '@typescript-eslint/no-extraneous-class': 'error', diff --git a/packages/eslint-plugin/src/configs/recommended-type-checked.ts b/packages/eslint-plugin/src/configs/recommended-type-checked.ts index 20993a06640..858eb655509 100644 --- a/packages/eslint-plugin/src/configs/recommended-type-checked.ts +++ b/packages/eslint-plugin/src/configs/recommended-type-checked.ts @@ -18,6 +18,7 @@ export = { '@typescript-eslint/no-base-to-string': 'error', '@typescript-eslint/no-duplicate-enum-values': 'error', '@typescript-eslint/no-duplicate-type-constituents': 'error', + '@typescript-eslint/no-empty-object-type': 'error', '@typescript-eslint/no-explicit-any': 'error', '@typescript-eslint/no-extra-non-null-assertion': 'error', '@typescript-eslint/no-floating-promises': 'error', diff --git a/packages/eslint-plugin/src/configs/recommended.ts b/packages/eslint-plugin/src/configs/recommended.ts index 58f31702ada..c93e38eabb2 100644 --- a/packages/eslint-plugin/src/configs/recommended.ts +++ b/packages/eslint-plugin/src/configs/recommended.ts @@ -15,6 +15,7 @@ export = { 'no-array-constructor': 'off', '@typescript-eslint/no-array-constructor': 'error', '@typescript-eslint/no-duplicate-enum-values': 'error', + '@typescript-eslint/no-empty-object-type': 'error', '@typescript-eslint/no-explicit-any': 'error', '@typescript-eslint/no-extra-non-null-assertion': 'error', '@typescript-eslint/no-misused-new': 'error', diff --git a/packages/eslint-plugin/src/configs/strict-type-checked.ts b/packages/eslint-plugin/src/configs/strict-type-checked.ts index 5b00d236984..91fd8b1589d 100644 --- a/packages/eslint-plugin/src/configs/strict-type-checked.ts +++ b/packages/eslint-plugin/src/configs/strict-type-checked.ts @@ -24,6 +24,7 @@ export = { '@typescript-eslint/no-duplicate-enum-values': 'error', '@typescript-eslint/no-duplicate-type-constituents': 'error', '@typescript-eslint/no-dynamic-delete': 'error', + '@typescript-eslint/no-empty-object-type': 'error', '@typescript-eslint/no-explicit-any': 'error', '@typescript-eslint/no-extra-non-null-assertion': 'error', '@typescript-eslint/no-extraneous-class': 'error', diff --git a/packages/eslint-plugin/src/configs/strict.ts b/packages/eslint-plugin/src/configs/strict.ts index 598b3246e27..ae000f72d3f 100644 --- a/packages/eslint-plugin/src/configs/strict.ts +++ b/packages/eslint-plugin/src/configs/strict.ts @@ -19,6 +19,7 @@ export = { '@typescript-eslint/no-array-constructor': 'error', '@typescript-eslint/no-duplicate-enum-values': 'error', '@typescript-eslint/no-dynamic-delete': 'error', + '@typescript-eslint/no-empty-object-type': 'error', '@typescript-eslint/no-explicit-any': 'error', '@typescript-eslint/no-extra-non-null-assertion': 'error', '@typescript-eslint/no-extraneous-class': 'error', diff --git a/packages/eslint-plugin/src/configs/stylistic-type-checked.ts b/packages/eslint-plugin/src/configs/stylistic-type-checked.ts index 0bb075e5c8f..3766c7f5695 100644 --- a/packages/eslint-plugin/src/configs/stylistic-type-checked.ts +++ b/packages/eslint-plugin/src/configs/stylistic-type-checked.ts @@ -23,7 +23,6 @@ export = { '@typescript-eslint/no-confusing-non-null-assertion': 'error', 'no-empty-function': 'off', '@typescript-eslint/no-empty-function': 'error', - '@typescript-eslint/no-empty-interface': 'error', '@typescript-eslint/no-inferrable-types': 'error', '@typescript-eslint/non-nullable-type-assertion-style': 'error', '@typescript-eslint/prefer-for-of': 'error', diff --git a/packages/eslint-plugin/src/configs/stylistic.ts b/packages/eslint-plugin/src/configs/stylistic.ts index 74f2586dd78..d9ac6faf9d9 100644 --- a/packages/eslint-plugin/src/configs/stylistic.ts +++ b/packages/eslint-plugin/src/configs/stylistic.ts @@ -21,7 +21,6 @@ export = { '@typescript-eslint/no-confusing-non-null-assertion': 'error', 'no-empty-function': 'off', '@typescript-eslint/no-empty-function': 'error', - '@typescript-eslint/no-empty-interface': 'error', '@typescript-eslint/no-inferrable-types': 'error', '@typescript-eslint/prefer-for-of': 'error', '@typescript-eslint/prefer-function-type': 'error', diff --git a/packages/eslint-plugin/src/rules/ban-types.ts b/packages/eslint-plugin/src/rules/ban-types.ts index da2d79716a3..06c9daa62d4 100644 --- a/packages/eslint-plugin/src/rules/ban-types.ts +++ b/packages/eslint-plugin/src/rules/ban-types.ts @@ -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'), }, - - // object typing - Object: { - message: [ - 'The `Object` type actually means "any non-nullish value", so it is marginally better than `unknown`.', - '- If you want a type meaning "any object", you probably want `object` instead.', - '- If you want a type meaning "any value", you probably want `unknown` instead.', - '- If you really want a type meaning "any non-nullish value", you probably want `NonNullable` instead.', - ].join('\n'), - suggest: ['object', 'unknown', 'NonNullable'], - }, - '{}': { - message: [ - '`{}` actually means "any non-nullish value".', - '- If you want a type meaning "any object", you probably want `object` instead.', - '- If you want a type meaning "any value", you probably want `unknown` instead.', - '- If you want a type meaning "empty object", you probably want `Record` instead.', - '- If you really want a type meaning "any non-nullish value", you probably want `NonNullable` instead.', - ].join('\n'), - suggest: [ - 'object', - 'unknown', - 'Record', - 'NonNullable', - ], - }, }; export const TYPE_KEYWORDS = { diff --git a/packages/eslint-plugin/src/rules/index.ts b/packages/eslint-plugin/src/rules/index.ts index ae1da2b6d43..c580027d059 100644 --- a/packages/eslint-plugin/src/rules/index.ts +++ b/packages/eslint-plugin/src/rules/index.ts @@ -36,6 +36,7 @@ import noDuplicateTypeConstituents from './no-duplicate-type-constituents'; import noDynamicDelete from './no-dynamic-delete'; import noEmptyFunction from './no-empty-function'; import noEmptyInterface from './no-empty-interface'; +import noEmptyObjectType from './no-empty-object-type'; import noExplicitAny from './no-explicit-any'; import noExtraNonNullAssertion from './no-extra-non-null-assertion'; import noExtraneousClass from './no-extraneous-class'; @@ -160,6 +161,7 @@ export default { 'no-dynamic-delete': noDynamicDelete, 'no-empty-function': noEmptyFunction, 'no-empty-interface': noEmptyInterface, + 'no-empty-object-type': noEmptyObjectType, 'no-explicit-any': noExplicitAny, 'no-extra-non-null-assertion': noExtraNonNullAssertion, 'no-extraneous-class': noExtraneousClass, diff --git a/packages/eslint-plugin/src/rules/no-empty-interface.ts b/packages/eslint-plugin/src/rules/no-empty-interface.ts index 674b86d44bf..03d00c1a953 100644 --- a/packages/eslint-plugin/src/rules/no-empty-interface.ts +++ b/packages/eslint-plugin/src/rules/no-empty-interface.ts @@ -17,8 +17,9 @@ export default createRule({ type: 'suggestion', docs: { description: 'Disallow the declaration of empty interfaces', - recommended: 'stylistic', }, + deprecated: true, + replacedBy: ['@typescript-eslint/no-empty-object-type'], fixable: 'code', hasSuggestions: true, messages: { diff --git a/packages/eslint-plugin/src/rules/no-empty-object-type.ts b/packages/eslint-plugin/src/rules/no-empty-object-type.ts new file mode 100644 index 00000000000..ad20103061c --- /dev/null +++ b/packages/eslint-plugin/src/rules/no-empty-object-type.ts @@ -0,0 +1,187 @@ +import type { TSESLint } from '@typescript-eslint/utils'; +import { AST_NODE_TYPES } from '@typescript-eslint/utils'; + +import { createRule } from '../util'; + +export type AllowInterfaces = 'always' | 'never' | 'with-single-extends'; + +export type AllowObjectTypes = 'always' | 'never'; + +export type Options = [ + { + allowInterfaces?: AllowInterfaces; + allowObjectTypes?: AllowObjectTypes; + allowWithName?: string; + }, +]; + +export type MessageIds = + | 'noEmptyInterface' + | 'noEmptyObject' + | 'noEmptyInterfaceWithSuper' + | 'replaceEmptyInterface' + | 'replaceEmptyInterfaceWithSuper' + | 'replaceEmptyObjectType'; + +const noEmptyMessage = (emptyType: string): string => + [ + `${emptyType} allows any non-nullish value, including literals like \`0\` and \`""\`.`, + "- If that's what you want, disable this lint rule with an inline comment or configure the '{{ option }}' rule option.", + '- If you want a type meaning "any object", you probably want `object` instead.', + '- If you want a type meaning "any value", you probably want `unknown` instead.', + ].join('\n'); + +export default createRule({ + name: 'no-empty-object-type', + meta: { + type: 'suggestion', + docs: { + description: 'Disallow accidentally using the "empty object" type', + recommended: 'recommended', + }, + hasSuggestions: true, + messages: { + noEmptyInterface: noEmptyMessage('An empty interface declaration'), + noEmptyObject: noEmptyMessage('The `{}` ("empty object") type'), + noEmptyInterfaceWithSuper: + 'An interface declaring no members is equivalent to its supertype.', + replaceEmptyInterface: 'Replace empty interface with `{{replacement}}`.', + replaceEmptyInterfaceWithSuper: + 'Replace empty interface with a type alias.', + replaceEmptyObjectType: 'Replace `{}` with `{{replacement}}`.', + }, + schema: [ + { + type: 'object', + additionalProperties: false, + properties: { + allowInterfaces: { + enum: ['always', 'never', 'with-single-extends'], + type: 'string', + }, + allowObjectTypes: { + enum: ['always', 'in-type-alias-with-name', 'never'], + type: 'string', + }, + allowWithName: { + type: 'string', + }, + }, + }, + ], + }, + defaultOptions: [ + { + allowInterfaces: 'never', + allowObjectTypes: 'never', + }, + ], + create(context, [{ allowInterfaces, allowWithName, allowObjectTypes }]) { + const allowWithNameTester = allowWithName + ? new RegExp(allowWithName, 'u') + : undefined; + + return { + ...(allowInterfaces !== 'always' && { + TSInterfaceDeclaration(node): void { + if (allowWithNameTester?.test(node.id.name)) { + return; + } + + const extend = node.extends; + if ( + node.body.body.length !== 0 || + (extend.length === 1 && + allowInterfaces === 'with-single-extends') || + extend.length > 1 + ) { + return; + } + + const scope = context.sourceCode.getScope(node); + + const mergedWithClassDeclaration = scope.set + .get(node.id.name) + ?.defs.some( + def => def.node.type === AST_NODE_TYPES.ClassDeclaration, + ); + + if (extend.length === 0) { + context.report({ + data: { option: 'allowInterfaces' }, + node: node.id, + messageId: 'noEmptyInterface', + ...(!mergedWithClassDeclaration && { + suggest: ['object', 'unknown'].map(replacement => ({ + data: { replacement }, + fix(fixer): TSESLint.RuleFix { + const id = context.sourceCode.getText(node.id); + const typeParam = node.typeParameters + ? context.sourceCode.getText(node.typeParameters) + : ''; + + return fixer.replaceText( + node, + `type ${id}${typeParam} = ${replacement}`, + ); + }, + messageId: 'replaceEmptyInterface', + })), + }), + }); + return; + } + + context.report({ + node: node.id, + messageId: 'noEmptyInterfaceWithSuper', + ...(!mergedWithClassDeclaration && { + suggest: [ + { + fix(fixer): TSESLint.RuleFix { + const extended = context.sourceCode.getText(extend[0]); + const id = context.sourceCode.getText(node.id); + const typeParam = node.typeParameters + ? context.sourceCode.getText(node.typeParameters) + : ''; + + return fixer.replaceText( + node, + `type ${id}${typeParam} = ${extended}`, + ); + }, + messageId: 'replaceEmptyInterfaceWithSuper', + }, + ], + }), + }); + }, + }), + ...(allowObjectTypes !== 'always' && { + TSTypeLiteral(node): void { + if ( + node.members.length || + node.parent.type === AST_NODE_TYPES.TSIntersectionType || + (allowWithNameTester && + node.parent.type === AST_NODE_TYPES.TSTypeAliasDeclaration && + allowWithNameTester.test(node.parent.id.name)) + ) { + return; + } + + context.report({ + data: { option: 'allowObjectTypes' }, + messageId: 'noEmptyObject', + node, + suggest: ['object', 'unknown'].map(replacement => ({ + data: { replacement }, + messageId: 'replaceEmptyObjectType', + fix: (fixer): TSESLint.RuleFix => + fixer.replaceText(node, replacement), + })), + }); + }, + }), + }; + }, +}); diff --git a/packages/eslint-plugin/tests/docs-eslint-output-snapshots/ban-types.shot b/packages/eslint-plugin/tests/docs-eslint-output-snapshots/ban-types.shot index f96ac650ffa..6644fd985d9 100644 --- a/packages/eslint-plugin/tests/docs-eslint-output-snapshots/ban-types.shot +++ b/packages/eslint-plugin/tests/docs-eslint-output-snapshots/ban-types.shot @@ -24,28 +24,9 @@ const func: Function = () => 1; // use safer object types const lowerObj: Object = {}; - ~~~~~~ Don't use \`Object\` as a type. The \`Object\` type actually means "any non-nullish value", so it is marginally better than \`unknown\`. - - If you want a type meaning "any object", you probably want \`object\` instead. - - If you want a type meaning "any value", you probably want \`unknown\` instead. - - If you really want a type meaning "any non-nullish value", you probably want \`NonNullable\` instead. + ~~~~~~ Don't use \`Object\` as a type. Use object instead const capitalObj: Object = { a: 'string' }; - ~~~~~~ Don't use \`Object\` as a type. The \`Object\` type actually means "any non-nullish value", so it is marginally better than \`unknown\`. - - If you want a type meaning "any object", you probably want \`object\` instead. - - If you want a type meaning "any value", you probably want \`unknown\` instead. - - If you really want a type meaning "any non-nullish value", you probably want \`NonNullable\` instead. - -const curly1: {} = 1; - ~~ Don't use \`{}\` as a type. \`{}\` actually means "any non-nullish value". - - If you want a type meaning "any object", you probably want \`object\` instead. - - If you want a type meaning "any value", you probably want \`unknown\` instead. - - If you want a type meaning "empty object", you probably want \`Record\` instead. - - If you really want a type meaning "any non-nullish value", you probably want \`NonNullable\` instead. -const curly2: {} = { a: 'string' }; - ~~ Don't use \`{}\` as a type. \`{}\` actually means "any non-nullish value". - - If you want a type meaning "any object", you probably want \`object\` instead. - - If you want a type meaning "any value", you probably want \`unknown\` instead. - - If you want a type meaning "empty object", you probably want \`Record\` instead. - - If you really want a type meaning "any non-nullish value", you probably want \`NonNullable\` instead. + ~~~~~~ Don't use \`Object\` as a type. Use object instead " `; @@ -65,8 +46,5 @@ const func: () => number = () => 1; // use safer object types const lowerObj: object = {}; const capitalObj: { a: string } = { a: 'string' }; - -const curly1: number = 1; -const curly2: Record<'a', string> = { a: 'string' }; " `; diff --git a/packages/eslint-plugin/tests/docs-eslint-output-snapshots/no-empty-object-type.shot b/packages/eslint-plugin/tests/docs-eslint-output-snapshots/no-empty-object-type.shot new file mode 100644 index 00000000000..15ee654ff40 --- /dev/null +++ b/packages/eslint-plugin/tests/docs-eslint-output-snapshots/no-empty-object-type.shot @@ -0,0 +1,100 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Validating rule docs no-empty-object-type.mdx code examples ESLint output 1`] = ` +"Incorrect + +let anyObject: {}; + ~~ The \`{}\` ("empty object") type allows any non-nullish value, including literals like \`0\` and \`""\`. + - If that's what you want, disable this lint rule with an inline comment or configure the 'allowObjectTypes' rule option. + - If you want a type meaning "any object", you probably want \`object\` instead. + - If you want a type meaning "any value", you probably want \`unknown\` instead. +let anyValue: {}; + ~~ The \`{}\` ("empty object") type allows any non-nullish value, including literals like \`0\` and \`""\`. + - If that's what you want, disable this lint rule with an inline comment or configure the 'allowObjectTypes' rule option. + - If you want a type meaning "any object", you probably want \`object\` instead. + - If you want a type meaning "any value", you probably want \`unknown\` instead. + +interface AnyObjectA {} + ~~~~~~~~~~ An empty interface declaration allows any non-nullish value, including literals like \`0\` and \`""\`. + - If that's what you want, disable this lint rule with an inline comment or configure the 'allowInterfaces' rule option. + - If you want a type meaning "any object", you probably want \`object\` instead. + - If you want a type meaning "any value", you probably want \`unknown\` instead. +interface AnyValueA {} + ~~~~~~~~~ An empty interface declaration allows any non-nullish value, including literals like \`0\` and \`""\`. + - If that's what you want, disable this lint rule with an inline comment or configure the 'allowInterfaces' rule option. + - If you want a type meaning "any object", you probably want \`object\` instead. + - If you want a type meaning "any value", you probably want \`unknown\` instead. + +type AnyObjectB = {}; + ~~ The \`{}\` ("empty object") type allows any non-nullish value, including literals like \`0\` and \`""\`. + - If that's what you want, disable this lint rule with an inline comment or configure the 'allowObjectTypes' rule option. + - If you want a type meaning "any object", you probably want \`object\` instead. + - If you want a type meaning "any value", you probably want \`unknown\` instead. +type AnyValueB = {}; + ~~ The \`{}\` ("empty object") type allows any non-nullish value, including literals like \`0\` and \`""\`. + - If that's what you want, disable this lint rule with an inline comment or configure the 'allowObjectTypes' rule option. + - If you want a type meaning "any object", you probably want \`object\` instead. + - If you want a type meaning "any value", you probably want \`unknown\` instead. +" +`; + +exports[`Validating rule docs no-empty-object-type.mdx code examples ESLint output 2`] = ` +"Correct + +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 }; +" +`; + +exports[`Validating rule docs no-empty-object-type.mdx code examples ESLint output 3`] = ` +"Options: { "allowInterfaces": "with-single-extends" } + +interface Base { + value: boolean; +} + +interface Derived extends Base {} +" +`; + +exports[`Validating rule docs no-empty-object-type.mdx code examples ESLint output 4`] = ` +"Incorrect +Options: { "allowWithName": "Props$" } + +interface InterfaceValue {} + ~~~~~~~~~~~~~~ An empty interface declaration allows any non-nullish value, including literals like \`0\` and \`""\`. + - If that's what you want, disable this lint rule with an inline comment or configure the 'allowInterfaces' rule option. + - If you want a type meaning "any object", you probably want \`object\` instead. + - If you want a type meaning "any value", you probably want \`unknown\` instead. + +type TypeValue = {}; + ~~ The \`{}\` ("empty object") type allows any non-nullish value, including literals like \`0\` and \`""\`. + - If that's what you want, disable this lint rule with an inline comment or configure the 'allowObjectTypes' rule option. + - If you want a type meaning "any object", you probably want \`object\` instead. + - If you want a type meaning "any value", you probably want \`unknown\` instead. +" +`; + +exports[`Validating rule docs no-empty-object-type.mdx code examples ESLint output 5`] = ` +"Correct +Options: { "allowWithName": "Props$" } + +interface InterfaceProps {} + +type TypeProps = {}; +" +`; diff --git a/packages/eslint-plugin/tests/rules/ban-types.test.ts b/packages/eslint-plugin/tests/rules/ban-types.test.ts index 5f8d72ab0f7..046ac290a63 100644 --- a/packages/eslint-plugin/tests/rules/ban-types.test.ts +++ b/packages/eslint-plugin/tests/rules/ban-types.test.ts @@ -137,44 +137,6 @@ ruleTester.run('ban-types', rule, { ], options, }, - { - code: 'let a: Object;', - output: null, - errors: [ - { - messageId: 'bannedTypeMessage', - data: { - name: 'Object', - customMessage: [ - ' The `Object` type actually means "any non-nullish value", so it is marginally better than `unknown`.', - '- If you want a type meaning "any object", you probably want `object` instead.', - '- If you want a type meaning "any value", you probably want `unknown` instead.', - '- If you really want a type meaning "any non-nullish value", you probably want `NonNullable` instead.', - ].join('\n'), - }, - line: 1, - column: 8, - suggestions: [ - { - messageId: 'bannedTypeReplacement', - data: { name: 'Object', replacement: 'object' }, - output: 'let a: object;', - }, - { - messageId: 'bannedTypeReplacement', - data: { name: 'Object', replacement: 'unknown' }, - output: 'let a: unknown;', - }, - { - messageId: 'bannedTypeReplacement', - data: { name: 'Object', replacement: 'NonNullable' }, - output: 'let a: NonNullable;', - }, - ], - }, - ], - options: [{}], - }, { code: 'let aa: Foo;', output: null, diff --git a/packages/eslint-plugin/tests/rules/no-empty-object-type.test.ts b/packages/eslint-plugin/tests/rules/no-empty-object-type.test.ts new file mode 100644 index 00000000000..5ccc5f5c181 --- /dev/null +++ b/packages/eslint-plugin/tests/rules/no-empty-object-type.test.ts @@ -0,0 +1,591 @@ +import { RuleTester } from '@typescript-eslint/rule-tester'; + +import rule from '../../src/rules/no-empty-object-type'; + +const ruleTester = new RuleTester({ + parser: '@typescript-eslint/parser', +}); + +ruleTester.run('no-empty-object-type', rule, { + valid: [ + ` +interface Base { + name: string; +} + `, + ` +interface Base { + name: string; +} + +interface Derived { + age: number; +} + +// valid because extending multiple interfaces can be used instead of a union type +interface Both extends Base, Derived {} + `, + { + code: 'interface Base {}', + options: [{ allowInterfaces: 'always' }], + }, + { + code: ` +interface Base { + name: string; +} + +interface Derived extends Base {} + `, + options: [{ allowInterfaces: 'with-single-extends' }], + }, + { + code: ` +interface Base { + props: string; +} + +interface Derived extends Base {} + +class Derived {} + `, + options: [{ allowInterfaces: 'with-single-extends' }], + }, + 'let value: object;', + 'let value: Object;', + 'let value: { inner: true };', + 'type MyNonNullable = T & {};', + { + code: 'type Base = {};', + options: [{ allowObjectTypes: 'always' }], + }, + { + code: 'type Base = {};', + options: [{ allowWithName: 'Base' }], + }, + { + code: 'type BaseProps = {};', + options: [{ allowWithName: 'Props$' }], + }, + { + code: 'interface Base {}', + options: [{ allowWithName: 'Base' }], + }, + { + code: 'interface BaseProps {}', + options: [{ allowWithName: 'Props$' }], + }, + ], + invalid: [ + { + code: 'interface Base {}', + errors: [ + { + column: 11, + data: { option: 'allowInterfaces' }, + line: 1, + messageId: 'noEmptyInterface', + suggestions: [ + { + data: { replacement: 'object' }, + messageId: 'replaceEmptyInterface', + output: `type Base = object`, + }, + { + messageId: 'replaceEmptyInterface', + output: `type Base = unknown`, + }, + ], + }, + ], + }, + { + code: 'interface Base {}', + errors: [ + { + column: 11, + data: { option: 'allowInterfaces' }, + line: 1, + messageId: 'noEmptyInterface', + suggestions: [ + { + data: { replacement: 'object' }, + messageId: 'replaceEmptyInterface', + output: `type Base = object`, + }, + { + messageId: 'replaceEmptyInterface', + output: `type Base = unknown`, + }, + ], + }, + ], + options: [{ allowInterfaces: 'never' }], + }, + { + code: ` +interface Base { + props: string; +} + +interface Derived extends Base {} + +class Other {} + `, + errors: [ + { + column: 11, + line: 6, + messageId: 'noEmptyInterfaceWithSuper', + suggestions: [ + { + messageId: 'replaceEmptyInterfaceWithSuper', + output: ` +interface Base { + props: string; +} + +type Derived = Base + +class Other {} + `, + }, + ], + }, + ], + }, + { + code: ` +interface Base { + props: string; +} + +interface Derived extends Base {} + +class Derived {} + `, + errors: [ + { + column: 11, + line: 6, + messageId: 'noEmptyInterfaceWithSuper', + }, + ], + }, + { + code: ` +interface Base { + props: string; +} + +interface Derived extends Base {} + +const derived = class Derived {}; + `, + errors: [ + { + messageId: 'noEmptyInterfaceWithSuper', + line: 6, + column: 11, + suggestions: [ + { + messageId: 'replaceEmptyInterfaceWithSuper', + output: ` +interface Base { + props: string; +} + +type Derived = Base + +const derived = class Derived {}; + `, + }, + ], + }, + ], + }, + { + code: ` +interface Base { + name: string; +} + +interface Derived extends Base {} + `, + errors: [ + { + messageId: 'noEmptyInterfaceWithSuper', + line: 6, + column: 11, + suggestions: [ + { + messageId: 'replaceEmptyInterfaceWithSuper', + output: ` +interface Base { + name: string; +} + +type Derived = Base + `, + }, + ], + }, + ], + }, + { + code: 'interface Base extends Array {}', + errors: [ + { + messageId: 'noEmptyInterfaceWithSuper', + line: 1, + column: 11, + suggestions: [ + { + messageId: 'replaceEmptyInterfaceWithSuper', + output: `type Base = Array`, + }, + ], + }, + ], + }, + { + code: 'interface Base extends Array {}', + errors: [ + { + messageId: 'noEmptyInterfaceWithSuper', + line: 1, + column: 11, + endColumn: 15, + suggestions: [ + { + messageId: 'replaceEmptyInterfaceWithSuper', + output: `type Base = Array`, + }, + ], + }, + { + data: { option: 'allowObjectTypes' }, + messageId: 'noEmptyObject', + line: 1, + column: 39, + endColumn: 41, + suggestions: [ + { + data: { replacement: 'object' }, + messageId: 'replaceEmptyObjectType', + output: `interface Base extends Array {}`, + }, + { + data: { replacement: 'unknown' }, + messageId: 'replaceEmptyObjectType', + output: `interface Base extends Array {}`, + }, + ], + }, + ], + }, + { + code: ` +interface Derived { + property: string; +} +interface Base extends Array {} + `, + errors: [ + { + messageId: 'noEmptyInterfaceWithSuper', + line: 5, + column: 11, + suggestions: [ + { + messageId: 'replaceEmptyInterfaceWithSuper', + output: ` +interface Derived { + property: string; +} +type Base = Array + `, + }, + ], + }, + ], + }, + { + code: ` +type R = Record; +interface Base extends R {} + `, + errors: [ + { + messageId: 'noEmptyInterfaceWithSuper', + line: 3, + column: 11, + suggestions: [ + { + messageId: 'replaceEmptyInterfaceWithSuper', + output: ` +type R = Record; +type Base = R + `, + }, + ], + }, + ], + }, + { + code: 'interface Base extends Derived {}', + errors: [ + { + messageId: 'noEmptyInterfaceWithSuper', + line: 1, + column: 11, + suggestions: [ + { + messageId: 'replaceEmptyInterfaceWithSuper', + output: `type Base = Derived`, + }, + ], + }, + ], + }, + { + filename: 'test.d.ts', + code: ` +declare namespace BaseAndDerived { + type Base = typeof base; + export interface Derived extends Base {} +} + `, + errors: [ + { + messageId: 'noEmptyInterfaceWithSuper', + line: 4, + column: 20, + endLine: 4, + endColumn: 27, + suggestions: [ + { + messageId: 'replaceEmptyInterfaceWithSuper', + output: ` +declare namespace BaseAndDerived { + type Base = typeof base; + export type Derived = Base +} + `, + }, + ], + }, + ], + }, + { + code: 'type Base = {};', + errors: [ + { + column: 13, + line: 1, + endColumn: 15, + endLine: 1, + data: { option: 'allowObjectTypes' }, + messageId: 'noEmptyObject', + suggestions: [ + { + data: { replacement: 'object' }, + messageId: 'replaceEmptyObjectType', + output: 'type Base = object;', + }, + { + data: { replacement: 'unknown' }, + messageId: 'replaceEmptyObjectType', + output: 'type Base = unknown;', + }, + ], + }, + ], + }, + { + code: 'type Base = {};', + errors: [ + { + column: 13, + line: 1, + endColumn: 15, + endLine: 1, + data: { option: 'allowObjectTypes' }, + messageId: 'noEmptyObject', + suggestions: [ + { + data: { replacement: 'object' }, + messageId: 'replaceEmptyObjectType', + output: 'type Base = object;', + }, + { + data: { replacement: 'unknown' }, + messageId: 'replaceEmptyObjectType', + output: 'type Base = unknown;', + }, + ], + }, + ], + options: [{ allowObjectTypes: 'never' }], + }, + { + code: 'let value: {};', + errors: [ + { + column: 12, + line: 1, + endColumn: 14, + endLine: 1, + data: { option: 'allowObjectTypes' }, + messageId: 'noEmptyObject', + suggestions: [ + { + data: { replacement: 'object' }, + messageId: 'replaceEmptyObjectType', + output: 'let value: object;', + }, + { + data: { replacement: 'unknown' }, + messageId: 'replaceEmptyObjectType', + output: 'let value: unknown;', + }, + ], + }, + ], + }, + { + code: 'let value: {};', + errors: [ + { + column: 12, + line: 1, + endColumn: 14, + endLine: 1, + data: { option: 'allowObjectTypes' }, + messageId: 'noEmptyObject', + suggestions: [ + { + data: { replacement: 'object' }, + messageId: 'replaceEmptyObjectType', + output: 'let value: object;', + }, + { + data: { replacement: 'unknown' }, + messageId: 'replaceEmptyObjectType', + output: 'let value: unknown;', + }, + ], + }, + ], + options: [{ allowObjectTypes: 'never' }], + }, + { + code: ` +let value: { + /* ... */ +}; + `, + errors: [ + { + line: 2, + endLine: 4, + column: 12, + endColumn: 2, + data: { option: 'allowObjectTypes' }, + messageId: 'noEmptyObject', + suggestions: [ + { + data: { replacement: 'object' }, + messageId: 'replaceEmptyObjectType', + output: ` +let value: object; + `, + }, + { + data: { replacement: 'unknown' }, + messageId: 'replaceEmptyObjectType', + output: ` +let value: unknown; + `, + }, + ], + }, + ], + }, + { + code: 'type MyUnion = T | {};', + errors: [ + { + column: 23, + line: 1, + endColumn: 25, + endLine: 1, + data: { option: 'allowObjectTypes' }, + messageId: 'noEmptyObject', + suggestions: [ + { + data: { replacement: 'object' }, + messageId: 'replaceEmptyObjectType', + output: 'type MyUnion = T | object;', + }, + { + data: { replacement: 'unknown' }, + messageId: 'replaceEmptyObjectType', + output: 'type MyUnion = T | unknown;', + }, + ], + }, + ], + }, + { + code: 'type Base = {} | null;', + errors: [ + { + column: 13, + line: 1, + endColumn: 15, + endLine: 1, + messageId: 'noEmptyObject', + }, + ], + options: [{ allowWithName: 'Base' }], + }, + { + code: 'type Base = {};', + errors: [ + { + column: 13, + line: 1, + endColumn: 15, + endLine: 1, + messageId: 'noEmptyObject', + }, + ], + options: [{ allowWithName: 'Mismatch' }], + }, + { + code: 'interface Base {}', + errors: [ + { + column: 11, + line: 1, + endColumn: 15, + endLine: 1, + messageId: 'noEmptyInterface', + suggestions: [ + { + data: { replacement: 'object' }, + messageId: 'replaceEmptyInterface', + output: `type Base = object`, + }, + { + messageId: 'replaceEmptyInterface', + output: `type Base = unknown`, + }, + ], + }, + ], + options: [{ allowWithName: '.*Props$' }], + }, + ], +}); diff --git a/packages/eslint-plugin/tests/schema-snapshots/no-empty-object-type.shot b/packages/eslint-plugin/tests/schema-snapshots/no-empty-object-type.shot new file mode 100644 index 00000000000..632b3ce83ca --- /dev/null +++ b/packages/eslint-plugin/tests/schema-snapshots/no-empty-object-type.shot @@ -0,0 +1,38 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Rule schemas should be convertible to TS types for documentation purposes no-empty-object-type 1`] = ` +" +# SCHEMA: + +[ + { + "additionalProperties": false, + "properties": { + "allowInterfaces": { + "enum": ["always", "never", "with-single-extends"], + "type": "string" + }, + "allowObjectTypes": { + "enum": ["always", "in-type-alias-with-name", "never"], + "type": "string" + }, + "allowWithName": { + "type": "string" + } + }, + "type": "object" + } +] + + +# TYPES: + +type Options = [ + { + allowInterfaces?: 'always' | 'never' | 'with-single-extends'; + allowObjectTypes?: 'always' | 'in-type-alias-with-name' | 'never'; + allowWithName?: string; + }, +]; +" +`; diff --git a/packages/typescript-eslint/src/configs/all.ts b/packages/typescript-eslint/src/configs/all.ts index e555dd197e5..01d4a56f9ee 100644 --- a/packages/typescript-eslint/src/configs/all.ts +++ b/packages/typescript-eslint/src/configs/all.ts @@ -63,7 +63,7 @@ export default ( '@typescript-eslint/no-dynamic-delete': 'error', 'no-empty-function': 'off', '@typescript-eslint/no-empty-function': 'error', - '@typescript-eslint/no-empty-interface': 'error', + '@typescript-eslint/no-empty-object-type': 'error', '@typescript-eslint/no-explicit-any': 'error', '@typescript-eslint/no-extra-non-null-assertion': 'error', '@typescript-eslint/no-extraneous-class': 'error', diff --git a/packages/typescript-eslint/src/configs/recommended-type-checked.ts b/packages/typescript-eslint/src/configs/recommended-type-checked.ts index 5dc2fe5a124..2d954c70581 100644 --- a/packages/typescript-eslint/src/configs/recommended-type-checked.ts +++ b/packages/typescript-eslint/src/configs/recommended-type-checked.ts @@ -27,6 +27,7 @@ export default ( '@typescript-eslint/no-base-to-string': 'error', '@typescript-eslint/no-duplicate-enum-values': 'error', '@typescript-eslint/no-duplicate-type-constituents': 'error', + '@typescript-eslint/no-empty-object-type': 'error', '@typescript-eslint/no-explicit-any': 'error', '@typescript-eslint/no-extra-non-null-assertion': 'error', '@typescript-eslint/no-floating-promises': 'error', diff --git a/packages/typescript-eslint/src/configs/recommended.ts b/packages/typescript-eslint/src/configs/recommended.ts index d3aefe29618..7df78599ea9 100644 --- a/packages/typescript-eslint/src/configs/recommended.ts +++ b/packages/typescript-eslint/src/configs/recommended.ts @@ -24,6 +24,7 @@ export default ( 'no-array-constructor': 'off', '@typescript-eslint/no-array-constructor': 'error', '@typescript-eslint/no-duplicate-enum-values': 'error', + '@typescript-eslint/no-empty-object-type': 'error', '@typescript-eslint/no-explicit-any': 'error', '@typescript-eslint/no-extra-non-null-assertion': 'error', '@typescript-eslint/no-misused-new': 'error', diff --git a/packages/typescript-eslint/src/configs/strict-type-checked.ts b/packages/typescript-eslint/src/configs/strict-type-checked.ts index 0f786b3f401..1542bf52850 100644 --- a/packages/typescript-eslint/src/configs/strict-type-checked.ts +++ b/packages/typescript-eslint/src/configs/strict-type-checked.ts @@ -33,6 +33,7 @@ export default ( '@typescript-eslint/no-duplicate-enum-values': 'error', '@typescript-eslint/no-duplicate-type-constituents': 'error', '@typescript-eslint/no-dynamic-delete': 'error', + '@typescript-eslint/no-empty-object-type': 'error', '@typescript-eslint/no-explicit-any': 'error', '@typescript-eslint/no-extra-non-null-assertion': 'error', '@typescript-eslint/no-extraneous-class': 'error', diff --git a/packages/typescript-eslint/src/configs/strict.ts b/packages/typescript-eslint/src/configs/strict.ts index 0406dc76e0f..680813d7d64 100644 --- a/packages/typescript-eslint/src/configs/strict.ts +++ b/packages/typescript-eslint/src/configs/strict.ts @@ -28,6 +28,7 @@ export default ( '@typescript-eslint/no-array-constructor': 'error', '@typescript-eslint/no-duplicate-enum-values': 'error', '@typescript-eslint/no-dynamic-delete': 'error', + '@typescript-eslint/no-empty-object-type': 'error', '@typescript-eslint/no-explicit-any': 'error', '@typescript-eslint/no-extra-non-null-assertion': 'error', '@typescript-eslint/no-extraneous-class': 'error', diff --git a/packages/typescript-eslint/src/configs/stylistic-type-checked.ts b/packages/typescript-eslint/src/configs/stylistic-type-checked.ts index 95b8c516fae..63ef5a71d0f 100644 --- a/packages/typescript-eslint/src/configs/stylistic-type-checked.ts +++ b/packages/typescript-eslint/src/configs/stylistic-type-checked.ts @@ -32,7 +32,6 @@ export default ( '@typescript-eslint/no-confusing-non-null-assertion': 'error', 'no-empty-function': 'off', '@typescript-eslint/no-empty-function': 'error', - '@typescript-eslint/no-empty-interface': 'error', '@typescript-eslint/no-inferrable-types': 'error', '@typescript-eslint/non-nullable-type-assertion-style': 'error', '@typescript-eslint/prefer-for-of': 'error', diff --git a/packages/typescript-eslint/src/configs/stylistic.ts b/packages/typescript-eslint/src/configs/stylistic.ts index 6a9b4cd306d..6e12fe9de23 100644 --- a/packages/typescript-eslint/src/configs/stylistic.ts +++ b/packages/typescript-eslint/src/configs/stylistic.ts @@ -30,7 +30,6 @@ export default ( '@typescript-eslint/no-confusing-non-null-assertion': 'error', 'no-empty-function': 'off', '@typescript-eslint/no-empty-function': 'error', - '@typescript-eslint/no-empty-interface': 'error', '@typescript-eslint/no-inferrable-types': 'error', '@typescript-eslint/prefer-for-of': 'error', '@typescript-eslint/prefer-function-type': 'error', diff --git a/packages/typescript-estree/src/parser.ts b/packages/typescript-estree/src/parser.ts index af649278e68..3eaaf773537 100644 --- a/packages/typescript-estree/src/parser.ts +++ b/packages/typescript-estree/src/parser.ts @@ -87,11 +87,11 @@ function getProgramAndAST( ); } -/* eslint-disable @typescript-eslint/ban-types */ +/* eslint-disable @typescript-eslint/no-empty-object-type */ type AST = TSESTree.Program & (T['comment'] extends true ? { comments: TSESTree.Comment[] } : {}) & (T['tokens'] extends true ? { tokens: TSESTree.Token[] } : {}); -/* eslint-enable @typescript-eslint/ban-types */ +/* eslint-enable @typescript-eslint/no-empty-object-type */ interface ParseAndGenerateServicesResult { ast: AST; diff --git a/packages/typescript-estree/src/ts-estree/ts-nodes.ts b/packages/typescript-estree/src/ts-estree/ts-nodes.ts index 2a07ee0e7ea..9501c031f6f 100644 --- a/packages/typescript-estree/src/ts-estree/ts-nodes.ts +++ b/packages/typescript-estree/src/ts-estree/ts-nodes.ts @@ -2,7 +2,7 @@ import type * as ts from 'typescript'; // Workaround to support new TS version features for consumers on old TS versions // Eg: https://github.com/typescript-eslint/typescript-eslint/issues/2388, https://github.com/typescript-eslint/typescript-eslint/issues/2784 -/* eslint-disable @typescript-eslint/no-empty-interface */ +/* eslint-disable @typescript-eslint/no-empty-object-type */ declare module 'typescript' { // added in TS 4.5, deprecated in TS 5.3 export interface AssertClause extends ts.ImportAttributes {} @@ -15,7 +15,7 @@ declare module 'typescript' { export interface ImportAttribute extends ts.Node {} export interface ImportAttributes extends ts.Node {} } -/* eslint-enable @typescript-eslint/no-empty-interface */ +/* eslint-enable @typescript-eslint/no-empty-object-type */ export type TSToken = ts.Token; diff --git a/packages/utils/src/json-schema.ts b/packages/utils/src/json-schema.ts index 73ab2f3ddc8..4f822a32267 100644 --- a/packages/utils/src/json-schema.ts +++ b/packages/utils/src/json-schema.ts @@ -41,7 +41,7 @@ export interface JSONSchema4Object { // Workaround for infinite type recursion // https://github.com/Microsoft/TypeScript/issues/3496#issuecomment-128553540 -// eslint-disable-next-line @typescript-eslint/no-empty-interface +// eslint-disable-next-line @typescript-eslint/no-empty-object-type export interface JSONSchema4Array extends Array {} /** diff --git a/packages/utils/src/ts-eslint/Rule.ts b/packages/utils/src/ts-eslint/Rule.ts index 2ef5a17ad2f..c4d260797ca 100644 --- a/packages/utils/src/ts-eslint/Rule.ts +++ b/packages/utils/src/ts-eslint/Rule.ts @@ -571,7 +571,7 @@ type RuleListenerExitSelectors = { }; type RuleListenerCatchAllBaseCase = Record; // Interface to merge into for anyone that wants to add more selectors -// eslint-disable-next-line @typescript-eslint/no-empty-interface +// eslint-disable-next-line @typescript-eslint/no-empty-object-type export interface RuleListenerExtension { // The code path functions below were introduced in ESLint v8.7.0 but are // intentionally commented out because they cause unresolvable compiler