UNPKG

@typescript-eslint/eslint-plugin

Version:
144 lines (143 loc) 6.92 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const utils_1 = require("@typescript-eslint/utils"); const util_1 = require("../util"); const noEmptyMessage = (emptyType) => [ `${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'); exports.default = (0, util_1.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'), noEmptyInterfaceWithSuper: 'An interface declaring no members is equivalent to its supertype.', noEmptyObject: noEmptyMessage('The `{}` ("empty object") type'), 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: { type: 'string', description: 'Whether to allow empty interfaces.', enum: ['always', 'never', 'with-single-extends'], }, allowObjectTypes: { type: 'string', description: 'Whether to allow empty object type literals.', enum: ['always', 'never'], }, allowWithName: { type: 'string', description: 'A stringified regular expression to allow interfaces and object type aliases with the configured name.', }, }, }, ], }, defaultOptions: [ { allowInterfaces: 'never', allowObjectTypes: 'never', }, ], create(context, [{ allowInterfaces, allowObjectTypes, allowWithName }]) { const allowWithNameTester = allowWithName ? new RegExp(allowWithName, 'u') : undefined; return { ...(allowInterfaces !== 'always' && { TSInterfaceDeclaration(node) { 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 === utils_1.AST_NODE_TYPES.ClassDeclaration); if (extend.length === 0) { context.report({ node: node.id, messageId: 'noEmptyInterface', data: { option: 'allowInterfaces' }, ...(!mergedWithClassDeclaration && { suggest: ['object', 'unknown'].map(replacement => ({ messageId: 'replaceEmptyInterface', data: { replacement }, fix(fixer) { const id = context.sourceCode.getText(node.id); const typeParam = node.typeParameters ? context.sourceCode.getText(node.typeParameters) : ''; return fixer.replaceText(node, `type ${id}${typeParam} = ${replacement}`); }, })), }), }); return; } context.report({ node: node.id, messageId: 'noEmptyInterfaceWithSuper', ...(!mergedWithClassDeclaration && { suggest: [ { messageId: 'replaceEmptyInterfaceWithSuper', fix(fixer) { 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}`); }, }, ], }), }); }, }), ...(allowObjectTypes !== 'always' && { TSTypeLiteral(node) { if (node.members.length || node.parent.type === utils_1.AST_NODE_TYPES.TSIntersectionType || (allowWithNameTester && node.parent.type === utils_1.AST_NODE_TYPES.TSTypeAliasDeclaration && allowWithNameTester.test(node.parent.id.name))) { return; } context.report({ node, messageId: 'noEmptyObject', data: { option: 'allowObjectTypes' }, suggest: ['object', 'unknown'].map(replacement => ({ messageId: 'replaceEmptyObjectType', data: { replacement }, fix: (fixer) => fixer.replaceText(node, replacement), })), }); }, }), }; }, });