Skip to content

⬅️ Back to Table of Contents

📄 consistent-generic-constructors

📊 Analysis Summary

Metric Count
🔧 Functions 4
📦 Imports 6
📊 Variables & Constants 1
📑 Type Aliases 2

📚 Table of Contents

🛠️ File Location:

📂 packages/eslint-plugin/src/rules/consistent-generic-constructors.ts

📤 Default Export

export default createRule<Options, MessageIds>({ ... })
Property Value
name 'consistent-generic-constructors'
meta.type 'suggestion'
meta.docs.description 'Enforce specifying generic type arguments on type annotation or constructor name of a constructor call'
meta.docs.recommended 'stylistic'
meta.fixable 'code'
meta.messages.preferConstructor 'The generic type arguments should be specified as part of the constructor type arguments.'
meta.messages.preferTypeAnnotation 'The generic type arguments should be specified as part of the type annotation.'
meta.schema [ { type: 'string', description: 'Which constructor call syntax to prefer.', enum: ['type-annotation', 'constructor']...
defaultOptions ['constructor']

Entry point: create — documented under Functions.


📦 Imports

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

Variables & Constants

Name Type Kind Value Exported
builtInArrays Set<string> const new Set([ 'Float32Array', 'Float64Array', 'Int16Array', 'Int32Array', 'Int8Ar...

Functions

create(context: any, [mode]: any): { 'VariableDeclarator,PropertyDefinition,AccessorProperty,:…

Parameters:

  • context any
  • [mode] any

Returns: { 'VariableDeclarator,PropertyDefinition,AccessorProperty,:matches(FunctionDeclaration,FunctionExpression) > AssignmentPattern'(node: TSESTree.AccessorProperty | TSESTree.AssignmentPattern | TSESTree.PropertyDefinition | TSESTree.VariableDeclarator): void; }

Calls:

  • builtInArrays.has
  • isReferenceToGlobalFunction (from ../util)
  • getLHSRHS
  • isBuiltInArray
  • context.sourceCode.getText
  • context.report
  • nullThrows (from ../util)
  • context.sourceCode.getTokenAfter
  • NullThrowsReasons.MissingToken
  • fixer.remove
  • fixer.insertTextAfter
  • getIDToAttachAnnotation
  • context.sourceCode.getCommentsInside
  • context.sourceCode .getCommentsInside(lhs.typeArguments) .forEach
  • extraComments.delete

Internal Comments:

// If the property's computed, we have to attach the
// annotation after the square bracket, not the enclosed expression

Code
create(context, [mode]) {
    return {
      'VariableDeclarator,PropertyDefinition,AccessorProperty,:matches(FunctionDeclaration,FunctionExpression) > AssignmentPattern'(
        node:
          | TSESTree.AccessorProperty
          | TSESTree.AssignmentPattern
          | TSESTree.PropertyDefinition
          | TSESTree.VariableDeclarator,
      ): void {
        function getLHSRHS(): [
          (
            | TSESTree.AccessorProperty
            | TSESTree.BindingName
            | TSESTree.PropertyDefinition
          ),
          TSESTree.Expression | null,
        ] {
          switch (node.type) {
            case AST_NODE_TYPES.VariableDeclarator:
              return [node.id, node.init];
            case AST_NODE_TYPES.PropertyDefinition:
            case AST_NODE_TYPES.AccessorProperty:
              return [node, node.value];
            case AST_NODE_TYPES.AssignmentPattern:
              return [node.left, node.right];
            default:
              throw new Error(
                `Unhandled node type: ${(node as { type: string }).type}`,
              );
          }
        }

        function isBuiltInArray(typeName: TSESTree.Identifier) {
          return (
            builtInArrays.has(typeName.name) &&
            isReferenceToGlobalFunction(
              typeName.name,
              typeName,
              context.sourceCode,
            )
          );
        }

        const [lhsName, rhs] = getLHSRHS();
        const lhs = lhsName.typeAnnotation?.typeAnnotation;

        if (
          rhs?.type !== AST_NODE_TYPES.NewExpression ||
          rhs.callee.type !== AST_NODE_TYPES.Identifier
        ) {
          return;
        }
        if (
          lhs &&
          (lhs.type !== AST_NODE_TYPES.TSTypeReference ||
            lhs.typeName.type !== AST_NODE_TYPES.Identifier ||
            lhs.typeName.name !== rhs.callee.name ||
            isBuiltInArray(lhs.typeName))
        ) {
          return;
        }
        if (mode === 'type-annotation') {
          if (!lhs && rhs.typeArguments) {
            const { callee, typeArguments } = rhs;
            const typeAnnotation =
              context.sourceCode.getText(callee) +
              context.sourceCode.getText(typeArguments);
            context.report({
              node,
              messageId: 'preferTypeAnnotation',
              fix(fixer) {
                function getIDToAttachAnnotation():
                  TSESTree.Node | TSESTree.Token {
                  if (
                    node.type !== AST_NODE_TYPES.PropertyDefinition &&
                    node.type !== AST_NODE_TYPES.AccessorProperty
                  ) {
                    return lhsName;
                  }
                  if (!node.computed) {
                    return node.key;
                  }
                  // If the property's computed, we have to attach the
                  // annotation after the square bracket, not the enclosed expression
                  return nullThrows(
                    context.sourceCode.getTokenAfter(node.key),
                    NullThrowsReasons.MissingToken(']', 'key'),
                  );
                }
                return [
                  fixer.remove(typeArguments),
                  fixer.insertTextAfter(
                    getIDToAttachAnnotation(),
                    `: ${typeAnnotation}`,
                  ),
                ];
              },
            });
          }
          return;
        }

        const isolatedDeclarations =
          context.languageOptions.parserOptions.isolatedDeclarations;
        if (!isolatedDeclarations && lhs?.typeArguments && !rhs.typeArguments) {
          const hasParens =
            context.sourceCode.getTokenAfter(rhs.callee)?.value === '(';
          const extraComments = new Set(
            context.sourceCode.getCommentsInside(lhs.parent),
          );
          context.sourceCode
            .getCommentsInside(lhs.typeArguments)
            .forEach(c => extraComments.delete(c));
          context.report({
            node,
            messageId: 'preferConstructor',
            *fix(fixer) {
              yield fixer.remove(lhs.parent);
              for (const comment of extraComments) {
                yield fixer.insertTextAfter(
                  rhs.callee,
                  context.sourceCode.getText(comment),
                );
              }
              yield fixer.insertTextAfter(
                rhs.callee,
                context.sourceCode.getText(lhs.typeArguments),
              );
              if (!hasParens) {
                yield fixer.insertTextAfter(rhs.callee, '()');
              }
            },
          });
        }
      },
    };
  }

Internal helpers

Declared inside another function in this file.

getLHSRHS(): [ ( | TSESTree.AccessorProperty | TSESTree.BindingName | TS…

Returns: [ ( | TSESTree.AccessorProperty | TSESTree.BindingName | TSESTree.PropertyDefinition ), TSESTree.Expression | null, ]

Code
function getLHSRHS(): [
          (
            | TSESTree.AccessorProperty
            | TSESTree.BindingName
            | TSESTree.PropertyDefinition
          ),
          TSESTree.Expression | null,
        ] {
          switch (node.type) {
            case AST_NODE_TYPES.VariableDeclarator:
              return [node.id, node.init];
            case AST_NODE_TYPES.PropertyDefinition:
            case AST_NODE_TYPES.AccessorProperty:
              return [node, node.value];
            case AST_NODE_TYPES.AssignmentPattern:
              return [node.left, node.right];
            default:
              throw new Error(
                `Unhandled node type: ${(node as { type: string }).type}`,
              );
          }
        }

isBuiltInArray(typeName: TSESTree.Identifier): boolean

Parameters:

  • typeName TSESTree.Identifier

Returns: boolean

Calls:

  • builtInArrays.has
  • isReferenceToGlobalFunction (from ../util)
Code
function isBuiltInArray(typeName: TSESTree.Identifier) {
          return (
            builtInArrays.has(typeName.name) &&
            isReferenceToGlobalFunction(
              typeName.name,
              typeName,
              context.sourceCode,
            )
          );
        }

getIDToAttachAnnotation(): TSESTree.Node | TSESTree.Token

Returns: TSESTree.Node | TSESTree.Token

Calls:

  • nullThrows (from ../util)
  • context.sourceCode.getTokenAfter
  • NullThrowsReasons.MissingToken

Internal Comments:

// If the property's computed, we have to attach the
// annotation after the square bracket, not the enclosed expression

Code
function getIDToAttachAnnotation():
                  TSESTree.Node | TSESTree.Token {
                  if (
                    node.type !== AST_NODE_TYPES.PropertyDefinition &&
                    node.type !== AST_NODE_TYPES.AccessorProperty
                  ) {
                    return lhsName;
                  }
                  if (!node.computed) {
                    return node.key;
                  }
                  // If the property's computed, we have to attach the
                  // annotation after the square bracket, not the enclosed expression
                  return nullThrows(
                    context.sourceCode.getTokenAfter(node.key),
                    NullThrowsReasons.MissingToken(']', 'key'),
                  );
                }

Type Aliases

MessageIds

type MessageIds = 'preferConstructor' | 'preferTypeAnnotation';

Options

type Options = ['constructor' | 'type-annotation'];

Generated by Syntax Scribe