Skip to content

⬅️ Back to Table of Contents

📄 no-unnecessary-type-arguments

📊 Analysis Summary

Metric Count
🔧 Functions 12
📦 Imports 6
📑 Type Aliases 2

📚 Table of Contents

🛠️ File Location:

📂 packages/eslint-plugin/src/rules/no-unnecessary-type-arguments.ts

📤 Default Export

export default createRule<[], MessageIds>({ ... })
Property Value
name 'no-unnecessary-type-arguments'
meta.type 'suggestion'
meta.docs.description 'Disallow type arguments that are equal to the default'
meta.docs.recommended 'strict'
meta.docs.requiresTypeChecking true
meta.fixable 'code'
meta.messages.unnecessaryTypeParameter 'This is the default value for this type parameter, so it can be omitted.'
meta.schema []
defaultOptions []

Entry point: create — documented under Functions.


📦 Imports

Name Source
TSESTree @typescript-eslint/utils
AST_NODE_TYPES @typescript-eslint/utils
createRule ../util
findFirstResult ../util
getParserServices ../util
isTypeReferenceType ../util

Functions

create(context: any): { TSTypeParameterInstantiation(node: any): void; }

Parameters:

  • context any

Returns: { TSTypeParameterInstantiation(node: any): void; }

Calls:

  • getParserServices (from ../util)
  • services.program.getTypeChecker
  • isTypeReferenceType (from ../util)
  • checker.getTypeArguments
  • typeParameters.at
  • checker.getTypeAtLocation
  • services.getTypeAtLocation
  • getTypeForComparison
  • defaultTypeResolved.typeArguments.some
  • context.report
  • fixer.removeRange
  • services.esTreeNodeToTSNodeMap.get
  • getTypeParametersFromNode
  • checkTSArgsAndParameters

Internal Comments:

// Just check the last one. Must specify previous type parameters if the last one is specified. (x2)
// TODO: would like checker.areTypesEquivalent. https://github.com/Microsoft/TypeScript/issues/13502 (x2)
// this check should handle some of the most simple cases of like strings, numbers, etc
// For more complex types (like aliases to generic object types) - TS won't always create a (x2)
// global shared type object for the type - so we need to resort to manually comparing the (x2)
// reference type and the passed type arguments. (x2)
// Also - in case there are aliases - we need to resolve them before we do checks (x2)
// ensure the resolved type AND all the parameters are the same (x5)
// TypeScript does not apply default type parameters in instantiation
// expressions, so explicit type args here are always meaningful.

Code
create(context) {
    const services = getParserServices(context);
    const checker = services.program.getTypeChecker();

    function getTypeForComparison(type: ts.Type): {
      type: ts.Type;
      typeArguments: readonly ts.Type[];
    } {
      if (isTypeReferenceType(type)) {
        return {
          type: type.target,
          typeArguments: checker.getTypeArguments(type),
        };
      }
      return {
        type,
        typeArguments: [],
      };
    }

    function checkTSArgsAndParameters(
      esParameters: TSESTree.TSTypeParameterInstantiation,
      typeParameters: readonly ts.TypeParameterDeclaration[],
    ): void {
      // Just check the last one. Must specify previous type parameters if the last one is specified.
      const i = esParameters.params.length - 1;
      const arg = esParameters.params[i];
      const param = typeParameters.at(i);
      if (!param?.default) {
        return;
      }

      // TODO: would like checker.areTypesEquivalent. https://github.com/Microsoft/TypeScript/issues/13502
      const defaultType = checker.getTypeAtLocation(param.default);
      const argType = services.getTypeAtLocation(arg);
      // this check should handle some of the most simple cases of like strings, numbers, etc
      if (defaultType !== argType) {
        // For more complex types (like aliases to generic object types) - TS won't always create a
        // global shared type object for the type - so we need to resort to manually comparing the
        // reference type and the passed type arguments.
        // Also - in case there are aliases - we need to resolve them before we do checks
        const defaultTypeResolved = getTypeForComparison(defaultType);
        const argTypeResolved = getTypeForComparison(argType);
        if (
          // ensure the resolved type AND all the parameters are the same
          defaultTypeResolved.type !== argTypeResolved.type ||
          defaultTypeResolved.typeArguments.length !==
            argTypeResolved.typeArguments.length ||
          defaultTypeResolved.typeArguments.some(
            (t, i) => t !== argTypeResolved.typeArguments[i],
          )
        ) {
          return;
        }
      }

      context.report({
        node: arg,
        messageId: 'unnecessaryTypeParameter',
        fix: fixer =>
          fixer.removeRange(
            i === 0
              ? esParameters.range
              : [esParameters.params[i - 1].range[1], arg.range[1]],
          ),
      });
    }

    return {
      TSTypeParameterInstantiation(node): void {
        // TypeScript does not apply default type parameters in instantiation
        // expressions, so explicit type args here are always meaningful.
        if (node.parent.type === AST_NODE_TYPES.TSInstantiationExpression) {
          return;
        }

        const expression = services.esTreeNodeToTSNodeMap.get(node);
        const typeParameters = getTypeParametersFromNode(
          node,
          expression,
          checker,
        );

        if (typeParameters) {
          checkTSArgsAndParameters(node, typeParameters);
        }
      },
    };
  }

getTypeParametersFromNode(node: TSESTree.TSTypeParameterInstantiation, tsNode: ParameterCapableTSNode, checker: ts.TypeChecker): readonly ts.TypeParameterDeclaration[] | undefined

Parameters:

  • node TSESTree.TSTypeParameterInstantiation
  • tsNode ParameterCapableTSNode
  • checker ts.TypeChecker

Returns: readonly ts.TypeParameterDeclaration[] | undefined

Calls:

  • ts.isExpressionWithTypeArguments
  • getTypeParametersFromType
  • ts.isTypeReferenceNode
  • ts.isCallExpression
  • ts.isNewExpression
  • ts.isTaggedTemplateExpression
  • ts.isJsxOpeningElement
  • ts.isJsxSelfClosingElement
  • getTypeParametersFromCall
Code
function getTypeParametersFromNode(
  node: TSESTree.TSTypeParameterInstantiation,
  tsNode: ParameterCapableTSNode,
  checker: ts.TypeChecker,
): readonly ts.TypeParameterDeclaration[] | undefined {
  if (ts.isExpressionWithTypeArguments(tsNode)) {
    return getTypeParametersFromType(node, tsNode.expression, checker);
  }

  if (ts.isTypeReferenceNode(tsNode)) {
    return getTypeParametersFromType(node, tsNode.typeName, checker);
  }

  if (
    ts.isCallExpression(tsNode) ||
    ts.isNewExpression(tsNode) ||
    ts.isTaggedTemplateExpression(tsNode) ||
    ts.isJsxOpeningElement(tsNode) ||
    ts.isJsxSelfClosingElement(tsNode)
  ) {
    return getTypeParametersFromCall(node, tsNode, checker);
  }

  return undefined;
}

getTypeParametersFromType(…): readonly ts.TypeParameterDeclaration[] | undefined

Parameters:

  • node TSESTree.TSTypeParameterInstantiation
  • type ts.ClassDeclaration | ts.EntityName | ts.Expression
  • checker ts.TypeChecker

Returns: readonly ts.TypeParameterDeclaration[] | undefined

Calls:

  • checker.getSymbolAtLocation
  • getAliasedSymbol
  • sym.getDeclarations
  • sortDeclarationsByTypeValueContext
  • findFirstResult (from ../util)
  • ts.isTypeAliasDeclaration
  • ts.isInterfaceDeclaration
  • ts.isClassLike
  • ts.isVariableDeclaration
  • getConstructSignatureDeclaration
Code
function getTypeParametersFromType(
  node: TSESTree.TSTypeParameterInstantiation,
  type: ts.ClassDeclaration | ts.EntityName | ts.Expression,
  checker: ts.TypeChecker,
): readonly ts.TypeParameterDeclaration[] | undefined {
  const symAtLocation = checker.getSymbolAtLocation(type);
  if (!symAtLocation) {
    return undefined;
  }

  const sym = getAliasedSymbol(symAtLocation, checker);
  const declarations = sym.getDeclarations();

  if (!declarations) {
    return undefined;
  }

  const sortedDeclarations = sortDeclarationsByTypeValueContext(
    node,
    declarations,
  );
  return findFirstResult(sortedDeclarations, decl => {
    if (
      ts.isTypeAliasDeclaration(decl) ||
      ts.isInterfaceDeclaration(decl) ||
      ts.isClassLike(decl)
    ) {
      return decl.typeParameters;
    }
    if (ts.isVariableDeclaration(decl)) {
      return getConstructSignatureDeclaration(symAtLocation, checker)
        ?.typeParameters;
    }
    return undefined;
  });
}

getTypeParametersFromCall(…): readonly ts.TypeParameterDeclaration[] | undefined

Parameters:

  • node TSESTree.TSTypeParameterInstantiation
  • tsNode | ts.CallExpression | ts.JsxOpeningElement | ts.JsxSelfClosingElement | ts.NewExpression | ts.TaggedTemplateExpression
  • checker ts.TypeChecker

Returns: readonly ts.TypeParameterDeclaration[] | undefined

Calls:

  • checker.getResolvedSignature
  • sig?.getDeclaration
  • ts.isNewExpression
  • getTypeParametersFromType
Code
function getTypeParametersFromCall(
  node: TSESTree.TSTypeParameterInstantiation,
  tsNode:
    | ts.CallExpression
    | ts.JsxOpeningElement
    | ts.JsxSelfClosingElement
    | ts.NewExpression
    | ts.TaggedTemplateExpression,
  checker: ts.TypeChecker,
): readonly ts.TypeParameterDeclaration[] | undefined {
  const sig = checker.getResolvedSignature(tsNode);
  const sigDecl = sig?.getDeclaration();
  if (!sigDecl) {
    return ts.isNewExpression(tsNode)
      ? getTypeParametersFromType(node, tsNode.expression, checker)
      : undefined;
  }

  return sigDecl.typeParameters;
}

getAliasedSymbol(symbol: ts.Symbol, checker: ts.TypeChecker): ts.Symbol

Parameters:

  • symbol ts.Symbol
  • checker ts.TypeChecker

Returns: ts.Symbol

Calls:

  • tsutils.isSymbolFlagSet
  • checker.getAliasedSymbol
Code
function getAliasedSymbol(
  symbol: ts.Symbol,
  checker: ts.TypeChecker,
): ts.Symbol {
  return tsutils.isSymbolFlagSet(symbol, ts.SymbolFlags.Alias)
    ? checker.getAliasedSymbol(symbol)
    : symbol;
}

isInTypeContext(node: TSESTree.TSTypeParameterInstantiation): boolean

Parameters:

  • node TSESTree.TSTypeParameterInstantiation

Returns: boolean

Code
function isInTypeContext(node: TSESTree.TSTypeParameterInstantiation) {
  return (
    node.parent.type === AST_NODE_TYPES.TSInterfaceHeritage ||
    node.parent.type === AST_NODE_TYPES.TSTypeReference ||
    node.parent.type === AST_NODE_TYPES.TSClassImplements
  );
}

isTypeContextDeclaration(decl: ts.Declaration): any

Parameters:

  • decl ts.Declaration

Returns: any

Calls:

  • ts.isTypeAliasDeclaration
  • ts.isInterfaceDeclaration
Code
function isTypeContextDeclaration(decl: ts.Declaration) {
  return ts.isTypeAliasDeclaration(decl) || ts.isInterfaceDeclaration(decl);
}

typeFirstCompare(declA: ts.Declaration, declB: ts.Declaration): number

Parameters:

  • declA ts.Declaration
  • declB ts.Declaration

Returns: number

Calls:

  • isTypeContextDeclaration
  • Number
Code
function typeFirstCompare(declA: ts.Declaration, declB: ts.Declaration) {
  const aIsType = isTypeContextDeclaration(declA);
  const bIsType = isTypeContextDeclaration(declB);

  return Number(bIsType) - Number(aIsType);
}

sortDeclarationsByTypeValueContext(node: TSESTree.TSTypeParameterInstantiation, declarations: ts.Declaration[]): ts.Declaration[]

Parameters:

  • node TSESTree.TSTypeParameterInstantiation
  • declarations ts.Declaration[]

Returns: ts.Declaration[]

Calls:

  • [...declarations].sort
  • isInTypeContext
  • sorted.reverse
Code
function sortDeclarationsByTypeValueContext(
  node: TSESTree.TSTypeParameterInstantiation,
  declarations: ts.Declaration[],
) {
  const sorted = [...declarations].sort(typeFirstCompare);
  if (isInTypeContext(node)) {
    return sorted;
  }
  return sorted.reverse();
}

getConstructSignatureDeclaration(symbol: ts.Symbol, checker: ts.TypeChecker): ts.SignatureDeclaration | undefined

Parameters:

  • symbol ts.Symbol
  • checker ts.TypeChecker

Returns: ts.SignatureDeclaration | undefined

Calls:

  • checker.getTypeOfSymbol
  • type.getConstructSignatures
  • sig.at(0)?.getDeclaration
Code
function getConstructSignatureDeclaration(
  symbol: ts.Symbol,
  checker: ts.TypeChecker,
): ts.SignatureDeclaration | undefined {
  const type = checker.getTypeOfSymbol(symbol);
  const sig = type.getConstructSignatures();
  return sig.at(0)?.getDeclaration();
}

Internal helpers

Declared inside another function in this file.

getTypeForComparison(type: ts.Type): { type: ts.Type; typeArguments: readonly ts.Type[]; }

Parameters:

  • type ts.Type

Returns: { type: ts.Type; typeArguments: readonly ts.Type[]; }

Calls:

  • isTypeReferenceType (from ../util)
  • checker.getTypeArguments
Code
function getTypeForComparison(type: ts.Type): {
      type: ts.Type;
      typeArguments: readonly ts.Type[];
    } {
      if (isTypeReferenceType(type)) {
        return {
          type: type.target,
          typeArguments: checker.getTypeArguments(type),
        };
      }
      return {
        type,
        typeArguments: [],
      };
    }

checkTSArgsAndParameters(esParameters: TSESTree.TSTypeParameterInstantiation, typeParameters: readonly ts.TypeParameterDeclaration[]): void

Parameters:

  • esParameters TSESTree.TSTypeParameterInstantiation
  • typeParameters readonly ts.TypeParameterDeclaration[]

Returns: void

Calls:

  • typeParameters.at
  • checker.getTypeAtLocation
  • services.getTypeAtLocation
  • getTypeForComparison
  • defaultTypeResolved.typeArguments.some
  • context.report
  • fixer.removeRange

Internal Comments:

// Just check the last one. Must specify previous type parameters if the last one is specified. (x2)
// TODO: would like checker.areTypesEquivalent. https://github.com/Microsoft/TypeScript/issues/13502 (x2)
// this check should handle some of the most simple cases of like strings, numbers, etc
// For more complex types (like aliases to generic object types) - TS won't always create a (x2)
// global shared type object for the type - so we need to resort to manually comparing the (x2)
// reference type and the passed type arguments. (x2)
// Also - in case there are aliases - we need to resolve them before we do checks (x2)
// ensure the resolved type AND all the parameters are the same (x5)

Code
function checkTSArgsAndParameters(
      esParameters: TSESTree.TSTypeParameterInstantiation,
      typeParameters: readonly ts.TypeParameterDeclaration[],
    ): void {
      // Just check the last one. Must specify previous type parameters if the last one is specified.
      const i = esParameters.params.length - 1;
      const arg = esParameters.params[i];
      const param = typeParameters.at(i);
      if (!param?.default) {
        return;
      }

      // TODO: would like checker.areTypesEquivalent. https://github.com/Microsoft/TypeScript/issues/13502
      const defaultType = checker.getTypeAtLocation(param.default);
      const argType = services.getTypeAtLocation(arg);
      // this check should handle some of the most simple cases of like strings, numbers, etc
      if (defaultType !== argType) {
        // For more complex types (like aliases to generic object types) - TS won't always create a
        // global shared type object for the type - so we need to resort to manually comparing the
        // reference type and the passed type arguments.
        // Also - in case there are aliases - we need to resolve them before we do checks
        const defaultTypeResolved = getTypeForComparison(defaultType);
        const argTypeResolved = getTypeForComparison(argType);
        if (
          // ensure the resolved type AND all the parameters are the same
          defaultTypeResolved.type !== argTypeResolved.type ||
          defaultTypeResolved.typeArguments.length !==
            argTypeResolved.typeArguments.length ||
          defaultTypeResolved.typeArguments.some(
            (t, i) => t !== argTypeResolved.typeArguments[i],
          )
        ) {
          return;
        }
      }

      context.report({
        node: arg,
        messageId: 'unnecessaryTypeParameter',
        fix: fixer =>
          fixer.removeRange(
            i === 0
              ? esParameters.range
              : [esParameters.params[i - 1].range[1], arg.range[1]],
          ),
      });
    }

Type Aliases

ParameterCapableTSNode

type ParameterCapableTSNode = | ts.CallExpression
  | ts.ExpressionWithTypeArguments
  | ts.ImportTypeNode
  | ts.JsxOpeningElement
  | ts.JsxSelfClosingElement
  | ts.NewExpression
  | ts.TaggedTemplateExpression
  | ts.TypeQueryNode
  | ts.TypeReferenceNode;

MessageIds

type MessageIds = 'unnecessaryTypeParameter';

Generated by Syntax Scribe