Skip to content

⬅️ Back to Table of Contents

πŸ“„ consistent-type-definitions

πŸ“Š Analysis Summary

Metric Count
πŸ”§ Functions 2
πŸ“¦ Imports 6

πŸ“š Table of Contents

πŸ› οΈ File Location:

πŸ“‚ packages/eslint-plugin/src/rules/consistent-type-definitions.ts

πŸ“€ Default Export

export default createRule({ ... })
Property Value
name 'consistent-type-definitions'
meta.type 'suggestion'
meta.docs.description 'Enforce type definitions to consistently use either interface or type'
meta.docs.recommended 'stylistic'
meta.fixable 'code'
meta.messages.interfaceOverType 'Use an interface instead of a type.'
meta.messages.typeOverInterface 'Use a type instead of an interface.'
meta.schema [ { type: 'string', description: 'Which type definition syntax to prefer.', enum: ['interface', 'type'], }, ]
defaultOptions ['interface']

Entry point: create β€” documented under Functions.


πŸ“¦ Imports

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

Functions

create(context: any, [option]: any): { TSInterfaceDeclaration(node: any): void; "TSTypeAliasDecl…

Parameters:

  • context any
  • [option] any

Returns: { TSInterfaceDeclaration(node: any): void; "TSTypeAliasDeclaration[typeAnnotation.type='TSTypeLiteral']"(node: TSESTree.TSTypeAliasDeclaration): void; }

Calls:

  • context.sourceCode .getAncestors(node) .some
  • context.report
  • nullThrows (from ../util)
  • context.sourceCode.getTokenBefore
  • NullThrowsReasons.MissingToken
  • fixer.replaceText
  • fixer.replaceTextRange
  • fixer.removeRange
  • isCurrentlyTraversedNodeWithinModuleDeclaration
  • fixes.push
  • node.extends.forEach
  • context.sourceCode.getText
  • fixer.insertTextAfter

Internal Comments:

/**
     * Iterates from the highest parent to the currently traversed node
     * to determine whether any node in tree is globally declared module declaration
     */
// replace 'type' with 'interface'. (x3)
// delete from the = to the { of the type, and put a space to be pretty. (x3)
// remove from the closing } through the end of the statement. (x3)
/**
             * remove automatically fix when the interface is within a declare global
             * @see {@link https://github.com/typescript-eslint/typescript-eslint/issues/2707}
             */ (x2)

Code
create(context, [option]) {
    /**
     * Iterates from the highest parent to the currently traversed node
     * to determine whether any node in tree is globally declared module declaration
     */
    function isCurrentlyTraversedNodeWithinModuleDeclaration(
      node: TSESTree.Node,
    ): boolean {
      return context.sourceCode
        .getAncestors(node)
        .some(
          node =>
            node.type === AST_NODE_TYPES.TSModuleDeclaration &&
            node.declare &&
            node.kind === 'global',
        );
    }

    return {
      ...(option === 'interface' && {
        "TSTypeAliasDeclaration[typeAnnotation.type='TSTypeLiteral']"(
          node: TSESTree.TSTypeAliasDeclaration,
        ): void {
          context.report({
            node: node.id,
            messageId: 'interfaceOverType',
            fix(fixer) {
              const typeToken = nullThrows(
                context.sourceCode.getTokenBefore(
                  node.id,
                  token => token.value === 'type',
                ),
                NullThrowsReasons.MissingToken('type keyword', 'type alias'),
              );

              const equalsToken = nullThrows(
                context.sourceCode.getTokenBefore(
                  node.typeAnnotation,
                  token => token.value === '=',
                ),
                NullThrowsReasons.MissingToken('=', 'type alias'),
              );

              const beforeEqualsToken = nullThrows(
                context.sourceCode.getTokenBefore(equalsToken, {
                  includeComments: true,
                }),
                NullThrowsReasons.MissingToken('before =', 'type alias'),
              );

              return [
                // replace 'type' with 'interface'.
                fixer.replaceText(typeToken, 'interface'),

                // delete from the = to the { of the type, and put a space to be pretty.
                fixer.replaceTextRange(
                  [beforeEqualsToken.range[1], node.typeAnnotation.range[0]],
                  ' ',
                ),

                // remove from the closing } through the end of the statement.
                fixer.removeRange([
                  node.typeAnnotation.range[1],
                  node.range[1],
                ]),
              ];
            },
          });
        },
      }),
      ...(option === 'type' && {
        TSInterfaceDeclaration(node): void {
          const fix = isCurrentlyTraversedNodeWithinModuleDeclaration(node)
            ? null
            : (fixer: TSESLint.RuleFixer): TSESLint.RuleFix[] => {
                const typeNode = node.typeParameters ?? node.id;
                const fixes: TSESLint.RuleFix[] = [];

                const firstToken = context.sourceCode.getTokenBefore(node.id);
                if (firstToken) {
                  fixes.push(fixer.replaceText(firstToken, 'type'));
                  fixes.push(
                    fixer.replaceTextRange(
                      [typeNode.range[1], node.body.range[0]],
                      ' = ',
                    ),
                  );
                }

                node.extends.forEach(heritage => {
                  const typeIdentifier = context.sourceCode.getText(heritage);
                  fixes.push(
                    fixer.insertTextAfter(node.body, ` & ${typeIdentifier}`),
                  );
                });

                if (
                  node.parent.type === AST_NODE_TYPES.ExportDefaultDeclaration
                ) {
                  fixes.push(
                    fixer.removeRange([node.parent.range[0], node.range[0]]),
                    fixer.insertTextAfter(
                      node.body,
                      `\nexport default ${node.id.name}`,
                    ),
                  );
                }

                return fixes;
              };
          context.report({
            node: node.id,
            messageId: 'typeOverInterface',
            /**
             * remove automatically fix when the interface is within a declare global
             * @see {@link https://github.com/typescript-eslint/typescript-eslint/issues/2707}
             */
            fix,
          });
        },
      }),
    };
  }

Internal helpers

Declared inside another function in this file.

isCurrentlyTraversedNodeWithinModuleDeclaration(node: TSESTree.Node): boolean

Iterates from the highest parent to the currently traversed node to determine whether any node in tree is globally declared module declaration

Raw JSDoc
/**
     * Iterates from the highest parent to the currently traversed node
     * to determine whether any node in tree is globally declared module declaration
     */

Calls:

  • context.sourceCode .getAncestors(node) .some
Code
function isCurrentlyTraversedNodeWithinModuleDeclaration(
      node: TSESTree.Node,
    ): boolean {
      return context.sourceCode
        .getAncestors(node)
        .some(
          node =>
            node.type === AST_NODE_TYPES.TSModuleDeclaration &&
            node.declare &&
            node.kind === 'global',
        );
    }

Generated by Syntax Scribe