Skip to content

⬅️ Back to Table of Contents

πŸ“„ no-empty-object-type

πŸ“Š Analysis Summary

Metric Count
πŸ”§ Functions 2
πŸ“¦ Imports 3
πŸ“‘ Type Aliases 4

πŸ“š Table of Contents

πŸ› οΈ File Location:

πŸ“‚ packages/eslint-plugin/src/rules/no-empty-object-type.ts

πŸ“€ Default Export

export default createRule<Options, MessageIds>({ ... })
Property Value
name 'no-empty-object-type'
meta.type 'suggestion'
meta.docs.description 'Disallow accidentally using the "empty object" type'
meta.docs.recommended 'recommended'
meta.hasSuggestions true
meta.messages.noEmptyInterface noEmptyMessage('An empty interface declaration')
meta.messages.noEmptyInterfaceWithSuper 'An interface declaring no members is equivalent to its supertype.'
meta.messages.noEmptyObject noEmptyMessage('The {} ("empty object") type')
meta.messages.replaceEmptyInterface 'Replace empty interface with {{replacement}}.'
meta.messages.replaceEmptyInterfaceWithSuper 'Replace empty interface with a type alias.'
meta.messages.replaceEmptyObjectType 'Replace {} with {{replacement}}.'
meta.schema [ { type: 'object', additionalProperties: false, properties: { allowInterfaces: { type: 'string', description: 'Wheth...
defaultOptions [ { allowInterfaces: 'never', allowObjectTypes: 'never', }, ]

Entry point: create β€” documented under Functions.


πŸ“¦ Imports

Name Source
TSESLint @typescript-eslint/utils
AST_NODE_TYPES @typescript-eslint/utils
createRule ../util

Functions

create(context: any, [{ allowInterfaces, allowObject…: any): { TSTypeLiteral(node: any): void; TSInterfaceDeclaration(no…

Parameters:

  • context any
  • [{ allowInterfaces, allowObjectTypes, allowWithName }] any

Returns: { TSTypeLiteral(node: any): void; TSInterfaceDeclaration(node: any): void; }

Calls:

  • allowWithNameTester?.test
  • context.sourceCode.getScope
  • scope.set .get(node.id.name) ?.defs.some
  • context.report
  • ['object', 'unknown'].map
  • context.sourceCode.getText
  • fixer.replaceText
  • allowWithNameTester.test
Code
create(context, [{ allowInterfaces, allowObjectTypes, allowWithName }]) {
    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 mergedWithOtherDeclaration = scope.set
            .get(node.id.name)
            ?.defs.some(
              def =>
                def.node !== node &&
                (def.node.type === AST_NODE_TYPES.ClassDeclaration ||
                  def.node.type === AST_NODE_TYPES.TSInterfaceDeclaration),
            );

          const isDefaultExport =
            node.parent.type === AST_NODE_TYPES.ExportDefaultDeclaration;

          const shouldSuggest = !mergedWithOtherDeclaration && !isDefaultExport;

          if (extend.length === 0) {
            context.report({
              node: node.id,
              messageId: 'noEmptyInterface',
              data: { option: 'allowInterfaces' },
              ...(shouldSuggest && {
                suggest: ['object', 'unknown'].map(replacement => ({
                  messageId: 'replaceEmptyInterface',
                  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}`,
                    );
                  },
                })),
              }),
            });
            return;
          }

          context.report({
            node: node.id,
            messageId: 'noEmptyInterfaceWithSuper',
            ...(shouldSuggest && {
              suggest: [
                {
                  messageId: 'replaceEmptyInterfaceWithSuper',
                  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}`,
                    );
                  },
                },
              ],
            }),
          });
        },
      }),
      ...(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({
            node,
            messageId: 'noEmptyObject',
            data: { option: 'allowObjectTypes' },
            suggest: ['object', 'unknown'].map(replacement => ({
              messageId: 'replaceEmptyObjectType',
              data: { replacement },
              fix: (fixer): TSESLint.RuleFix =>
                fixer.replaceText(node, replacement),
            })),
          });
        },
      }),
    };
  }

noEmptyMessage(emptyType: string): string

Parameters:

  • emptyType string

Returns: string

Calls:

  • [${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 wantobjectinstead.', '- If you want a type meaning "any value", you probably wantunknowninstead.', ].join
Code
(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')

Type Aliases

AllowInterfaces

type AllowInterfaces = 'always' | 'never' | 'with-single-extends';

AllowObjectTypes

type AllowObjectTypes = 'always' | 'never';

Options

type Options = [
  {
    allowInterfaces?: AllowInterfaces;
    allowObjectTypes?: AllowObjectTypes;
    allowWithName?: string;
  },
];

MessageIds

type MessageIds = | 'noEmptyInterface'
  | 'noEmptyInterfaceWithSuper'
  | 'noEmptyObject'
  | 'replaceEmptyInterface'
  | 'replaceEmptyInterfaceWithSuper'
  | 'replaceEmptyObjectType';

Generated by Syntax Scribe