Skip to content

⬅️ Back to Table of Contents

πŸ“„ no-unsafe-argument

πŸ“Š Analysis Summary

Metric Count
πŸ”§ Functions 5
πŸ“¦ Imports 8
πŸ“‘ Type Aliases 1

πŸ“š Table of Contents

πŸ› οΈ File Location:

πŸ“‚ packages/eslint-plugin/src/rules/no-unsafe-argument.ts

πŸ“€ Default Export

export default createRule<[], MessageIds>({ ... })
Property Value
name 'no-unsafe-argument'
meta.type 'problem'
meta.docs.description 'Disallow calling a function with a value with type any'
meta.docs.recommended 'recommended'
meta.docs.requiresTypeChecking true
meta.messages.unsafeArgument 'Unsafe argument of type {{sender}} assigned to a parameter of type {{receiver}}.'
meta.messages.unsafeArraySpread 'Unsafe spread of an {{sender}} array type.'
meta.messages.unsafeSpread 'Unsafe spread of an {{sender}} type.'
meta.messages.unsafeTupleSpread 'Unsafe spread of a tuple type. The argument is {{sender}} and is assigned to a parameter of type {{receiver}}.'
meta.schema []
defaultOptions []

Entry point: create β€” documented under Functions.


πŸ“¦ Imports

Name Source
TSESTree @typescript-eslint/utils
AST_NODE_TYPES @typescript-eslint/utils
createRule ../util
FunctionSignature ../util
getParserServices ../util
isTypeAnyArrayType ../util
isTypeAnyType ../util
isUnsafeAssignment ../util

Functions

create(context: any): { 'CallExpression, NewExpression'(node: TSESTree.CallExpres…

Parameters:

  • context any

Returns: { 'CallExpression, NewExpression'(node: TSESTree.CallExpression | TSESTree.NewExpression): void; TaggedTemplateExpression(node: TSESTree.TaggedTemplateExpression): void; }

Calls:

  • getParserServices (from ../util)
  • services.program.getTypeChecker
  • tsutils.isIntrinsicErrorType
  • checker.typeToString
  • checker.isArrayType
  • checker.getTypeArguments
  • describeType
  • isTypeAnyType (from ../util)
  • services.getTypeAtLocation
  • services.esTreeNodeToTSNodeMap.get
  • FunctionSignature.create
  • signature.getNextParameterType
  • context.report
  • isTypeAnyArrayType (from ../util)
  • describeTypeForSpread
  • checker.isTupleType
  • isUnsafeAssignment (from ../util)
  • describeTypeForTuple
  • signature.consumeRemainingArguments
  • checkUnsafeArguments

Internal Comments:

// ignore any-typed calls as these are caught by no-unsafe-call
// Consumes the first parameter (TemplateStringsArray) of the function called with TaggedTemplateExpression. (x4)
// spreads consume
// foo(...any) (x4)
// foo(...any[]) (x4)
// TODO - we could break down the spread and compare the array type against each argument (x4)
// foo(...[tuple1, tuple2]) (x2)
// we can't pass the individual tuple members in here as this will most likely be a spread variable
// not a spread array
// the last element was a rest - so all remaining defined arguments can be considered "consumed" (x4)
// all remaining arguments should be compared against the rest type (if one exists) (x4)

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

    function describeType(type: ts.Type): string {
      if (tsutils.isIntrinsicErrorType(type)) {
        return 'error typed';
      }

      return `\`${checker.typeToString(type)}\``;
    }

    function describeTypeForSpread(type: ts.Type): string {
      if (
        checker.isArrayType(type) &&
        tsutils.isIntrinsicErrorType(checker.getTypeArguments(type)[0])
      ) {
        return 'error';
      }

      return describeType(type);
    }

    function describeTypeForTuple(type: ts.Type): string {
      if (tsutils.isIntrinsicErrorType(type)) {
        return 'error typed';
      }

      return `of type \`${checker.typeToString(type)}\``;
    }

    function checkUnsafeArguments(
      args: TSESTree.CallExpressionArgument[] | TSESTree.Expression[],
      callee: TSESTree.Expression,
      node:
        | TSESTree.CallExpression
        | TSESTree.NewExpression
        | TSESTree.TaggedTemplateExpression,
    ): void {
      if (args.length === 0) {
        return;
      }

      // ignore any-typed calls as these are caught by no-unsafe-call
      if (isTypeAnyType(services.getTypeAtLocation(callee))) {
        return;
      }

      const tsNode = services.esTreeNodeToTSNodeMap.get(node);
      const signature = FunctionSignature.create(checker, tsNode);

      if (node.type === AST_NODE_TYPES.TaggedTemplateExpression) {
        // Consumes the first parameter (TemplateStringsArray) of the function called with TaggedTemplateExpression.
        signature.getNextParameterType();
      }

      for (const argument of args) {
        switch (argument.type) {
          // spreads consume
          case AST_NODE_TYPES.SpreadElement: {
            const spreadArgType = services.getTypeAtLocation(argument.argument);

            if (isTypeAnyType(spreadArgType)) {
              // foo(...any)
              context.report({
                node: argument,
                messageId: 'unsafeSpread',
                data: { sender: describeType(spreadArgType) },
              });
            } else if (isTypeAnyArrayType(spreadArgType, checker)) {
              // foo(...any[])

              // TODO - we could break down the spread and compare the array type against each argument
              context.report({
                node: argument,
                messageId: 'unsafeArraySpread',
                data: { sender: describeTypeForSpread(spreadArgType) },
              });
            } else if (checker.isTupleType(spreadArgType)) {
              // foo(...[tuple1, tuple2])
              const spreadTypeArguments =
                checker.getTypeArguments(spreadArgType);
              for (const tupleType of spreadTypeArguments) {
                const parameterType = signature.getNextParameterType();
                if (parameterType == null) {
                  continue;
                }
                const result = isUnsafeAssignment(
                  tupleType,
                  parameterType,
                  checker,
                  // we can't pass the individual tuple members in here as this will most likely be a spread variable
                  // not a spread array
                  null,
                );
                if (result) {
                  context.report({
                    node: argument,
                    messageId: 'unsafeTupleSpread',
                    data: {
                      receiver: describeType(parameterType),
                      sender: describeTypeForTuple(tupleType),
                    },
                  });
                }
              }
              if (
                spreadArgType.target.combinedFlags & ts.ElementFlags.Variable
              ) {
                // the last element was a rest - so all remaining defined arguments can be considered "consumed"
                // all remaining arguments should be compared against the rest type (if one exists)
                signature.consumeRemainingArguments();
              }
            } else {
              // something that's iterable
              // handling this will be pretty complex - so we ignore it for now
              // TODO - handle generic iterable case
            }
            break;
          }

          default: {
            const parameterType = signature.getNextParameterType();
            if (parameterType == null) {
              continue;
            }

            const argumentType = services.getTypeAtLocation(argument);
            const result = isUnsafeAssignment(
              argumentType,
              parameterType,
              checker,
              argument,
            );
            if (result) {
              context.report({
                node: argument,
                messageId: 'unsafeArgument',
                data: {
                  receiver: describeType(parameterType),
                  sender: describeType(argumentType),
                },
              });
            }
          }
        }
      }
    }

    return {
      'CallExpression, NewExpression'(
        node: TSESTree.CallExpression | TSESTree.NewExpression,
      ): void {
        checkUnsafeArguments(node.arguments, node.callee, node);
      },
      TaggedTemplateExpression(node: TSESTree.TaggedTemplateExpression): void {
        checkUnsafeArguments(node.quasi.expressions, node.tag, node);
      },
    };
  }

Internal helpers

Declared inside another function in this file.

describeType(type: ts.Type): string

Parameters:

  • type ts.Type

Returns: string

Calls:

  • tsutils.isIntrinsicErrorType
  • checker.typeToString
Code
function describeType(type: ts.Type): string {
      if (tsutils.isIntrinsicErrorType(type)) {
        return 'error typed';
      }

      return `\`${checker.typeToString(type)}\``;
    }

describeTypeForSpread(type: ts.Type): string

Parameters:

  • type ts.Type

Returns: string

Calls:

  • checker.isArrayType
  • tsutils.isIntrinsicErrorType
  • checker.getTypeArguments
  • describeType
Code
function describeTypeForSpread(type: ts.Type): string {
      if (
        checker.isArrayType(type) &&
        tsutils.isIntrinsicErrorType(checker.getTypeArguments(type)[0])
      ) {
        return 'error';
      }

      return describeType(type);
    }

describeTypeForTuple(type: ts.Type): string

Parameters:

  • type ts.Type

Returns: string

Calls:

  • tsutils.isIntrinsicErrorType
  • checker.typeToString
Code
function describeTypeForTuple(type: ts.Type): string {
      if (tsutils.isIntrinsicErrorType(type)) {
        return 'error typed';
      }

      return `of type \`${checker.typeToString(type)}\``;
    }

checkUnsafeArguments(args: TSESTree.CallExpressionArgument[] | TSE…, callee: TSESTree.Expression, node: | TSESTree.CallExpression | TSESTree.Ne…): void

Parameters:

  • args TSESTree.CallExpressionArgument[] | TSESTree.Expression[]
  • callee TSESTree.Expression
  • node | TSESTree.CallExpression | TSESTree.NewExpression | TSESTree.TaggedTemplateExpression

Returns: void

Calls:

  • isTypeAnyType (from ../util)
  • services.getTypeAtLocation
  • services.esTreeNodeToTSNodeMap.get
  • FunctionSignature.create
  • signature.getNextParameterType
  • context.report
  • describeType
  • isTypeAnyArrayType (from ../util)
  • describeTypeForSpread
  • checker.isTupleType
  • checker.getTypeArguments
  • isUnsafeAssignment (from ../util)
  • describeTypeForTuple
  • signature.consumeRemainingArguments

Internal Comments:

// ignore any-typed calls as these are caught by no-unsafe-call
// Consumes the first parameter (TemplateStringsArray) of the function called with TaggedTemplateExpression. (x4)
// spreads consume
// foo(...any) (x4)
// foo(...any[]) (x4)
// TODO - we could break down the spread and compare the array type against each argument (x4)
// foo(...[tuple1, tuple2]) (x2)
// we can't pass the individual tuple members in here as this will most likely be a spread variable
// not a spread array
// the last element was a rest - so all remaining defined arguments can be considered "consumed" (x4)
// all remaining arguments should be compared against the rest type (if one exists) (x4)

Code
function checkUnsafeArguments(
      args: TSESTree.CallExpressionArgument[] | TSESTree.Expression[],
      callee: TSESTree.Expression,
      node:
        | TSESTree.CallExpression
        | TSESTree.NewExpression
        | TSESTree.TaggedTemplateExpression,
    ): void {
      if (args.length === 0) {
        return;
      }

      // ignore any-typed calls as these are caught by no-unsafe-call
      if (isTypeAnyType(services.getTypeAtLocation(callee))) {
        return;
      }

      const tsNode = services.esTreeNodeToTSNodeMap.get(node);
      const signature = FunctionSignature.create(checker, tsNode);

      if (node.type === AST_NODE_TYPES.TaggedTemplateExpression) {
        // Consumes the first parameter (TemplateStringsArray) of the function called with TaggedTemplateExpression.
        signature.getNextParameterType();
      }

      for (const argument of args) {
        switch (argument.type) {
          // spreads consume
          case AST_NODE_TYPES.SpreadElement: {
            const spreadArgType = services.getTypeAtLocation(argument.argument);

            if (isTypeAnyType(spreadArgType)) {
              // foo(...any)
              context.report({
                node: argument,
                messageId: 'unsafeSpread',
                data: { sender: describeType(spreadArgType) },
              });
            } else if (isTypeAnyArrayType(spreadArgType, checker)) {
              // foo(...any[])

              // TODO - we could break down the spread and compare the array type against each argument
              context.report({
                node: argument,
                messageId: 'unsafeArraySpread',
                data: { sender: describeTypeForSpread(spreadArgType) },
              });
            } else if (checker.isTupleType(spreadArgType)) {
              // foo(...[tuple1, tuple2])
              const spreadTypeArguments =
                checker.getTypeArguments(spreadArgType);
              for (const tupleType of spreadTypeArguments) {
                const parameterType = signature.getNextParameterType();
                if (parameterType == null) {
                  continue;
                }
                const result = isUnsafeAssignment(
                  tupleType,
                  parameterType,
                  checker,
                  // we can't pass the individual tuple members in here as this will most likely be a spread variable
                  // not a spread array
                  null,
                );
                if (result) {
                  context.report({
                    node: argument,
                    messageId: 'unsafeTupleSpread',
                    data: {
                      receiver: describeType(parameterType),
                      sender: describeTypeForTuple(tupleType),
                    },
                  });
                }
              }
              if (
                spreadArgType.target.combinedFlags & ts.ElementFlags.Variable
              ) {
                // the last element was a rest - so all remaining defined arguments can be considered "consumed"
                // all remaining arguments should be compared against the rest type (if one exists)
                signature.consumeRemainingArguments();
              }
            } else {
              // something that's iterable
              // handling this will be pretty complex - so we ignore it for now
              // TODO - handle generic iterable case
            }
            break;
          }

          default: {
            const parameterType = signature.getNextParameterType();
            if (parameterType == null) {
              continue;
            }

            const argumentType = services.getTypeAtLocation(argument);
            const result = isUnsafeAssignment(
              argumentType,
              parameterType,
              checker,
              argument,
            );
            if (result) {
              context.report({
                node: argument,
                messageId: 'unsafeArgument',
                data: {
                  receiver: describeType(parameterType),
                  sender: describeType(argumentType),
                },
              });
            }
          }
        }
      }
    }

Type Aliases

MessageIds

type MessageIds = 'unsafeArgument' | 'unsafeArraySpread' | 'unsafeSpread' | 'unsafeTupleSpread';

Generated by Syntax Scribe