Skip to content

⬅️ Back to Table of Contents

πŸ“„ explicit-module-boundary-types

πŸ“Š Analysis Summary

Metric Count
πŸ”§ Functions 14
πŸ“¦ Imports 15
πŸ“‘ Type Aliases 2

πŸ“š Table of Contents

πŸ› οΈ File Location:

πŸ“‚ packages/eslint-plugin/src/rules/explicit-module-boundary-types.ts

πŸ“€ Default Export

export default createRule<Options, MessageIds>({ ... })
Property Value
name 'explicit-module-boundary-types'
meta.type 'problem'
meta.docs.description "Require explicit return and argument types on exported functions' and classes' public class methods"
meta.messages.anyTypedArg "Argument '{{name}}' should be typed with a non-any type."
meta.messages.anyTypedArgUnnamed '{{type}} argument should be typed with a non-any type.'
meta.messages.missingArgType "Argument '{{name}}' should be typed."
meta.messages.missingArgTypeUnnamed '{{type}} argument should be typed.'
meta.messages.missingReturnType 'Missing return type on function.'
meta.schema [ { type: 'object', additionalProperties: false, properties: { allowArgumentsExplicitlyTypedAsAny: { type: 'boolean',...
defaultOptions [ { allowArgumentsExplicitlyTypedAsAny: false, allowDirectConstAssertionInArrowFunctions: true, allowedNames: [], all...

Entry point: create β€” documented under Functions.


πŸ“¦ Imports

Name Source
TSESTree @typescript-eslint/utils
DefinitionType @typescript-eslint/scope-manager
AST_NODE_TYPES @typescript-eslint/utils
FunctionExpression ../util/explicitReturnTypeUtils
FunctionInfo ../util/explicitReturnTypeUtils
FunctionNode ../util/explicitReturnTypeUtils
createRule ../util
hasOverloadSignatures ../util
isFunction ../util
isStaticMemberAccessOfValue ../util
ancestorHasReturnType ../util/explicitReturnTypeUtils
checkFunctionExpressionReturnType ../util/explicitReturnTypeUtils
checkFunctionReturnType ../util/explicitReturnTypeUtils
doesImmediatelyReturnFunctionExpression ../util/explicitReturnTypeUtils
isTypedFunctionExpression ../util/explicitReturnTypeUtils

Functions

create(context: any, [options]: any): { 'ArrowFunctionExpression, FunctionDeclaration, FunctionEx…

Parameters:

  • context any
  • [options] any

Returns: { 'ArrowFunctionExpression, FunctionDeclaration, FunctionExpression': (node: FunctionNode) => void; 'ArrowFunctionExpression:exit': () => void; 'ExportDefaultDeclaration:exit'(node: any): void; 'ExportNamedDeclaration:not([source]):exit'(node: TSESTree.ExportNamedDeclarationWithoutSource): void; 'FunctionDeclaration:exit': () => void; 'FunctionExpression:exit': () => void; 'Program:exit'(): void; ReturnStatement(node: any): void; 'TSExportAssignment:exit'(node: any): void; }

Calls:

  • functionReturnsMap.get
  • functionStack.push
  • functionReturnsMap.set
  • functionStack.pop
  • checkNode
  • followReference
  • isExportedHigherOrderFunction
  • functionReturnsMap.get(current)?.push
  • context.report
  • report
  • checkParameter
  • options.allowedNames.includes
  • isStaticMemberAccessOfValue (from ../util)
  • isFunction (from ../util)
  • getReturnsInFunction
  • doesImmediatelyReturnFunctionExpression (from ../util/explicitReturnTypeUtils)
  • checkedFunctions.has
  • context.sourceCode.getScope
  • scope.set.get
  • [ DefinitionType.CatchClause, DefinitionType.ImplicitGlobalVariable, DefinitionType.ImportBinding, DefinitionType.Parameter, ].includes
  • alreadyVisited.has
  • alreadyVisited.add
  • checkFunctionExpression
  • checkFunction
  • checkEmptyBodyFunctionExpression
  • checkParameters
  • checkedFunctions.add
  • isAllowedName
  • isTypedFunctionExpression (from ../util/explicitReturnTypeUtils)
  • ancestorHasReturnType (from ../util/explicitReturnTypeUtils)
  • hasOverloadSignatures (from ../util)
  • checkFunctionExpressionReturnType (from ../util/explicitReturnTypeUtils)
  • checkFunctionReturnType (from ../util/explicitReturnTypeUtils)

Internal Comments:

// tracks all of the functions we've already checked (x2)
// all nodes visited, avoids infinite recursion for cyclic references (x2)
// (such as class member referring to itself) (x2)
/*
    # How the rule works:

    As the rule traverses the AST, it immediately checks every single function that it finds is exported.
    "exported" means that it is either directly exported, or that its name is exported.

    It also collects a list of every single function it finds on the way, but does not check them.
    After it's finished traversing the AST, it then iterates through the list of found functions, and checks to see if
    any of them are part of a higher-order function
    */
/**
     * Checks if a function name is allowed and should not be checked.
     */
// the parent of a return will always be a block statement, so we can skip over it (x3)
/* istanbul ignore if */
// check all of the definitions
// cases we don't care about in this rule
// follow references to find writes to the variable
// we don't want to check the initialization ref, as this is handled by the declaration check (x2)

Code
create(context, [options]) {
    // tracks all of the functions we've already checked
    const checkedFunctions = new Set<FunctionNode>();

    const functionStack: FunctionNode[] = [];
    const functionReturnsMap = new Map<
      FunctionNode,
      TSESTree.ReturnStatement[]
    >();

    // all nodes visited, avoids infinite recursion for cyclic references
    // (such as class member referring to itself)
    const alreadyVisited = new Set<TSESTree.Node>();

    function getReturnsInFunction(
      node: FunctionNode,
    ): TSESTree.ReturnStatement[] {
      return functionReturnsMap.get(node) ?? [];
    }

    function enterFunction(node: FunctionNode): void {
      functionStack.push(node);
      functionReturnsMap.set(node, []);
    }

    function exitFunction(): void {
      functionStack.pop();
    }

    /*
    # How the rule works:

    As the rule traverses the AST, it immediately checks every single function that it finds is exported.
    "exported" means that it is either directly exported, or that its name is exported.

    It also collects a list of every single function it finds on the way, but does not check them.
    After it's finished traversing the AST, it then iterates through the list of found functions, and checks to see if
    any of them are part of a higher-order function
    */

    return {
      'ArrowFunctionExpression, FunctionDeclaration, FunctionExpression':
        enterFunction,
      'ArrowFunctionExpression:exit': exitFunction,
      'ExportDefaultDeclaration:exit'(node): void {
        checkNode(node.declaration);
      },
      'ExportNamedDeclaration:not([source]):exit'(
        node: TSESTree.ExportNamedDeclarationWithoutSource,
      ): void {
        if (node.declaration) {
          checkNode(node.declaration);
        } else {
          for (const specifier of node.specifiers) {
            followReference(specifier.local);
          }
        }
      },
      'FunctionDeclaration:exit': exitFunction,
      'FunctionExpression:exit': exitFunction,
      'Program:exit'(): void {
        for (const [node, returns] of functionReturnsMap) {
          if (isExportedHigherOrderFunction({ node, returns })) {
            checkNode(node);
          }
        }
      },
      ReturnStatement(node): void {
        const current = functionStack[functionStack.length - 1];
        functionReturnsMap.get(current)?.push(node);
      },
      'TSExportAssignment:exit'(node): void {
        checkNode(node.expression);
      },
    };

    function checkParameters(
      node: FunctionNode | TSESTree.TSEmptyBodyFunctionExpression,
    ): void {
      function checkParameter(param: TSESTree.Parameter): void {
        function report(
          namedMessageId: MessageIds,
          unnamedMessageId: MessageIds,
        ): void {
          if (param.type === AST_NODE_TYPES.Identifier) {
            context.report({
              node: param,
              messageId: namedMessageId,
              data: { name: param.name },
            });
          } else if (param.type === AST_NODE_TYPES.ArrayPattern) {
            context.report({
              node: param,
              messageId: unnamedMessageId,
              data: { type: 'Array pattern' },
            });
          } else if (param.type === AST_NODE_TYPES.ObjectPattern) {
            context.report({
              node: param,
              messageId: unnamedMessageId,
              data: { type: 'Object pattern' },
            });
          } else if (param.type === AST_NODE_TYPES.RestElement) {
            if (param.argument.type === AST_NODE_TYPES.Identifier) {
              context.report({
                node: param,
                messageId: namedMessageId,
                data: { name: param.argument.name },
              });
            } else {
              context.report({
                node: param,
                messageId: unnamedMessageId,
                data: { type: 'Rest' },
              });
            }
          }
        }

        switch (param.type) {
          case AST_NODE_TYPES.ArrayPattern:
          case AST_NODE_TYPES.Identifier:
          case AST_NODE_TYPES.ObjectPattern:
          case AST_NODE_TYPES.RestElement:
            if (!param.typeAnnotation) {
              report('missingArgType', 'missingArgTypeUnnamed');
            } else if (
              options.allowArgumentsExplicitlyTypedAsAny !== true &&
              param.typeAnnotation.typeAnnotation.type ===
                AST_NODE_TYPES.TSAnyKeyword
            ) {
              report('anyTypedArg', 'anyTypedArgUnnamed');
            }
            return;

          case AST_NODE_TYPES.TSParameterProperty:
            return checkParameter(param.parameter);

          case AST_NODE_TYPES.AssignmentPattern: // ignored as it has a type via its assignment
            return;
        }
      }

      for (const arg of node.params) {
        checkParameter(arg);
      }
    }

    /**
     * Checks if a function name is allowed and should not be checked.
     */
    function isAllowedName(node: TSESTree.Node | undefined): boolean {
      if (!node || !options.allowedNames || options.allowedNames.length === 0) {
        return false;
      }

      if (
        node.type === AST_NODE_TYPES.VariableDeclarator ||
        node.type === AST_NODE_TYPES.FunctionDeclaration
      ) {
        return (
          node.id?.type === AST_NODE_TYPES.Identifier &&
          options.allowedNames.includes(node.id.name)
        );
      }

      if (
        node.type === AST_NODE_TYPES.MethodDefinition ||
        node.type === AST_NODE_TYPES.TSAbstractMethodDefinition ||
        (node.type === AST_NODE_TYPES.Property && node.method) ||
        node.type === AST_NODE_TYPES.PropertyDefinition ||
        node.type === AST_NODE_TYPES.AccessorProperty
      ) {
        return isStaticMemberAccessOfValue(
          node,
          context,
          ...options.allowedNames,
        );
      }

      return false;
    }

    function isExportedHigherOrderFunction({
      node,
    }: FunctionInfo<FunctionNode>): boolean {
      let current: TSESTree.Node | undefined = node.parent;
      while (current) {
        if (current.type === AST_NODE_TYPES.ReturnStatement) {
          // the parent of a return will always be a block statement, so we can skip over it
          current = current.parent.parent;
          continue;
        }

        if (!isFunction(current)) {
          return false;
        }
        const returns = getReturnsInFunction(current);
        if (
          !doesImmediatelyReturnFunctionExpression({ node: current, returns })
        ) {
          return false;
        }

        if (checkedFunctions.has(current)) {
          return true;
        }

        current = current.parent;
      }

      return false;
    }

    function followReference(node: TSESTree.Identifier): void {
      const scope = context.sourceCode.getScope(node);
      const variable = scope.set.get(node.name);
      /* istanbul ignore if */ if (!variable) {
        return;
      }

      // check all of the definitions
      for (const definition of variable.defs) {
        // cases we don't care about in this rule
        if (
          [
            DefinitionType.CatchClause,
            DefinitionType.ImplicitGlobalVariable,
            DefinitionType.ImportBinding,
            DefinitionType.Parameter,
          ].includes(definition.type)
        ) {
          continue;
        }

        checkNode(definition.node);
      }

      // follow references to find writes to the variable
      for (const reference of variable.references) {
        if (
          // we don't want to check the initialization ref, as this is handled by the declaration check
          !reference.init &&
          reference.writeExpr
        ) {
          checkNode(reference.writeExpr);
        }
      }
    }

    function checkNode(node: TSESTree.Node | null): void {
      if (node == null || alreadyVisited.has(node)) {
        return;
      }
      alreadyVisited.add(node);

      switch (node.type) {
        case AST_NODE_TYPES.ArrowFunctionExpression:
        case AST_NODE_TYPES.FunctionExpression: {
          const returns = getReturnsInFunction(node);
          return checkFunctionExpression({ node, returns });
        }

        case AST_NODE_TYPES.ArrayExpression:
          for (const element of node.elements) {
            checkNode(element);
          }
          return;

        case AST_NODE_TYPES.PropertyDefinition:
        case AST_NODE_TYPES.AccessorProperty:
        case AST_NODE_TYPES.MethodDefinition:
        case AST_NODE_TYPES.TSAbstractMethodDefinition:
          if (
            node.accessibility === 'private' ||
            node.key.type === AST_NODE_TYPES.PrivateIdentifier
          ) {
            return;
          }
          return checkNode(node.value);

        case AST_NODE_TYPES.ClassDeclaration:
        case AST_NODE_TYPES.ClassExpression:
          for (const element of node.body.body) {
            checkNode(element);
          }
          return;

        case AST_NODE_TYPES.FunctionDeclaration: {
          const returns = getReturnsInFunction(node);
          return checkFunction({ node, returns });
        }

        case AST_NODE_TYPES.Identifier:
          return followReference(node);

        case AST_NODE_TYPES.ObjectExpression:
          for (const property of node.properties) {
            checkNode(property);
          }
          return;

        case AST_NODE_TYPES.Property:
          return checkNode(node.value);

        case AST_NODE_TYPES.TSEmptyBodyFunctionExpression:
          return checkEmptyBodyFunctionExpression(node);

        case AST_NODE_TYPES.VariableDeclaration:
          for (const declaration of node.declarations) {
            checkNode(declaration);
          }
          return;

        case AST_NODE_TYPES.VariableDeclarator:
          return checkNode(node.init);
      }
    }

    function checkEmptyBodyFunctionExpression(
      node: TSESTree.TSEmptyBodyFunctionExpression,
    ): void {
      const isConstructor =
        node.parent.type === AST_NODE_TYPES.MethodDefinition &&
        node.parent.kind === 'constructor';
      const isSetAccessor =
        (node.parent.type === AST_NODE_TYPES.TSAbstractMethodDefinition ||
          node.parent.type === AST_NODE_TYPES.MethodDefinition) &&
        node.parent.kind === 'set';
      if (!isConstructor && !isSetAccessor && !node.returnType) {
        context.report({
          node,
          messageId: 'missingReturnType',
        });
      }

      checkParameters(node);
    }

    function checkFunctionExpression({
      node,
      returns,
    }: FunctionInfo<FunctionExpression>): void {
      if (checkedFunctions.has(node)) {
        return;
      }
      checkedFunctions.add(node);

      if (
        isAllowedName(node.parent) ||
        isTypedFunctionExpression(node, options) ||
        ancestorHasReturnType(node)
      ) {
        return;
      }

      if (
        options.allowOverloadFunctions &&
        node.parent.type === AST_NODE_TYPES.MethodDefinition &&
        hasOverloadSignatures(node.parent, context)
      ) {
        return;
      }

      checkFunctionExpressionReturnType(
        { node, returns },
        options,
        context.sourceCode,
        loc => {
          context.report({
            loc,
            node,
            messageId: 'missingReturnType',
          });
        },
      );

      checkParameters(node);
    }

    function checkFunction({
      node,
      returns,
    }: FunctionInfo<TSESTree.FunctionDeclaration>): void {
      if (checkedFunctions.has(node)) {
        return;
      }
      checkedFunctions.add(node);

      if (isAllowedName(node) || ancestorHasReturnType(node)) {
        return;
      }

      if (
        options.allowOverloadFunctions &&
        hasOverloadSignatures(node, context)
      ) {
        return;
      }

      checkFunctionReturnType(
        { node, returns },
        options,
        context.sourceCode,
        loc => {
          context.report({
            loc,
            node,
            messageId: 'missingReturnType',
          });
        },
      );

      checkParameters(node);
    }
  }

Internal helpers

Declared inside another function in this file.

getReturnsInFunction(node: FunctionNode): TSESTree.ReturnStatement[]

Parameters:

  • node FunctionNode

Returns: TSESTree.ReturnStatement[]

Calls:

  • functionReturnsMap.get
Code
function getReturnsInFunction(
      node: FunctionNode,
    ): TSESTree.ReturnStatement[] {
      return functionReturnsMap.get(node) ?? [];
    }

enterFunction(node: FunctionNode): void

Parameters:

  • node FunctionNode

Returns: void

Calls:

  • functionStack.push
  • functionReturnsMap.set
Code
function enterFunction(node: FunctionNode): void {
      functionStack.push(node);
      functionReturnsMap.set(node, []);
    }

exitFunction(): void

Returns: void

Calls:

  • functionStack.pop
Code
function exitFunction(): void {
      functionStack.pop();
    }

checkParameters(node: FunctionNode | TSESTree.TSEmptyBodyFunc…): void

Parameters:

  • node FunctionNode | TSESTree.TSEmptyBodyFunctionExpression

Returns: void

Calls:

  • context.report
  • report
  • checkParameter
Code
function checkParameters(
      node: FunctionNode | TSESTree.TSEmptyBodyFunctionExpression,
    ): void {
      function checkParameter(param: TSESTree.Parameter): void {
        function report(
          namedMessageId: MessageIds,
          unnamedMessageId: MessageIds,
        ): void {
          if (param.type === AST_NODE_TYPES.Identifier) {
            context.report({
              node: param,
              messageId: namedMessageId,
              data: { name: param.name },
            });
          } else if (param.type === AST_NODE_TYPES.ArrayPattern) {
            context.report({
              node: param,
              messageId: unnamedMessageId,
              data: { type: 'Array pattern' },
            });
          } else if (param.type === AST_NODE_TYPES.ObjectPattern) {
            context.report({
              node: param,
              messageId: unnamedMessageId,
              data: { type: 'Object pattern' },
            });
          } else if (param.type === AST_NODE_TYPES.RestElement) {
            if (param.argument.type === AST_NODE_TYPES.Identifier) {
              context.report({
                node: param,
                messageId: namedMessageId,
                data: { name: param.argument.name },
              });
            } else {
              context.report({
                node: param,
                messageId: unnamedMessageId,
                data: { type: 'Rest' },
              });
            }
          }
        }

        switch (param.type) {
          case AST_NODE_TYPES.ArrayPattern:
          case AST_NODE_TYPES.Identifier:
          case AST_NODE_TYPES.ObjectPattern:
          case AST_NODE_TYPES.RestElement:
            if (!param.typeAnnotation) {
              report('missingArgType', 'missingArgTypeUnnamed');
            } else if (
              options.allowArgumentsExplicitlyTypedAsAny !== true &&
              param.typeAnnotation.typeAnnotation.type ===
                AST_NODE_TYPES.TSAnyKeyword
            ) {
              report('anyTypedArg', 'anyTypedArgUnnamed');
            }
            return;

          case AST_NODE_TYPES.TSParameterProperty:
            return checkParameter(param.parameter);

          case AST_NODE_TYPES.AssignmentPattern: // ignored as it has a type via its assignment
            return;
        }
      }

      for (const arg of node.params) {
        checkParameter(arg);
      }
    }

checkParameter(param: TSESTree.Parameter): void

Parameters:

  • param TSESTree.Parameter

Returns: void

Calls:

  • context.report
  • report
  • checkParameter
Code
function checkParameter(param: TSESTree.Parameter): void {
        function report(
          namedMessageId: MessageIds,
          unnamedMessageId: MessageIds,
        ): void {
          if (param.type === AST_NODE_TYPES.Identifier) {
            context.report({
              node: param,
              messageId: namedMessageId,
              data: { name: param.name },
            });
          } else if (param.type === AST_NODE_TYPES.ArrayPattern) {
            context.report({
              node: param,
              messageId: unnamedMessageId,
              data: { type: 'Array pattern' },
            });
          } else if (param.type === AST_NODE_TYPES.ObjectPattern) {
            context.report({
              node: param,
              messageId: unnamedMessageId,
              data: { type: 'Object pattern' },
            });
          } else if (param.type === AST_NODE_TYPES.RestElement) {
            if (param.argument.type === AST_NODE_TYPES.Identifier) {
              context.report({
                node: param,
                messageId: namedMessageId,
                data: { name: param.argument.name },
              });
            } else {
              context.report({
                node: param,
                messageId: unnamedMessageId,
                data: { type: 'Rest' },
              });
            }
          }
        }

        switch (param.type) {
          case AST_NODE_TYPES.ArrayPattern:
          case AST_NODE_TYPES.Identifier:
          case AST_NODE_TYPES.ObjectPattern:
          case AST_NODE_TYPES.RestElement:
            if (!param.typeAnnotation) {
              report('missingArgType', 'missingArgTypeUnnamed');
            } else if (
              options.allowArgumentsExplicitlyTypedAsAny !== true &&
              param.typeAnnotation.typeAnnotation.type ===
                AST_NODE_TYPES.TSAnyKeyword
            ) {
              report('anyTypedArg', 'anyTypedArgUnnamed');
            }
            return;

          case AST_NODE_TYPES.TSParameterProperty:
            return checkParameter(param.parameter);

          case AST_NODE_TYPES.AssignmentPattern: // ignored as it has a type via its assignment
            return;
        }
      }

report(namedMessageId: MessageIds, unnamedMessageId: MessageIds): void

Parameters:

  • namedMessageId MessageIds
  • unnamedMessageId MessageIds

Returns: void

Calls:

  • context.report
Code
function report(
          namedMessageId: MessageIds,
          unnamedMessageId: MessageIds,
        ): void {
          if (param.type === AST_NODE_TYPES.Identifier) {
            context.report({
              node: param,
              messageId: namedMessageId,
              data: { name: param.name },
            });
          } else if (param.type === AST_NODE_TYPES.ArrayPattern) {
            context.report({
              node: param,
              messageId: unnamedMessageId,
              data: { type: 'Array pattern' },
            });
          } else if (param.type === AST_NODE_TYPES.ObjectPattern) {
            context.report({
              node: param,
              messageId: unnamedMessageId,
              data: { type: 'Object pattern' },
            });
          } else if (param.type === AST_NODE_TYPES.RestElement) {
            if (param.argument.type === AST_NODE_TYPES.Identifier) {
              context.report({
                node: param,
                messageId: namedMessageId,
                data: { name: param.argument.name },
              });
            } else {
              context.report({
                node: param,
                messageId: unnamedMessageId,
                data: { type: 'Rest' },
              });
            }
          }
        }

isAllowedName(node: TSESTree.Node | undefined): boolean

Checks if a function name is allowed and should not be checked.

Raw JSDoc
/**
     * Checks if a function name is allowed and should not be checked.
     */

Calls:

  • options.allowedNames.includes
  • isStaticMemberAccessOfValue (from ../util)
Code
function isAllowedName(node: TSESTree.Node | undefined): boolean {
      if (!node || !options.allowedNames || options.allowedNames.length === 0) {
        return false;
      }

      if (
        node.type === AST_NODE_TYPES.VariableDeclarator ||
        node.type === AST_NODE_TYPES.FunctionDeclaration
      ) {
        return (
          node.id?.type === AST_NODE_TYPES.Identifier &&
          options.allowedNames.includes(node.id.name)
        );
      }

      if (
        node.type === AST_NODE_TYPES.MethodDefinition ||
        node.type === AST_NODE_TYPES.TSAbstractMethodDefinition ||
        (node.type === AST_NODE_TYPES.Property && node.method) ||
        node.type === AST_NODE_TYPES.PropertyDefinition ||
        node.type === AST_NODE_TYPES.AccessorProperty
      ) {
        return isStaticMemberAccessOfValue(
          node,
          context,
          ...options.allowedNames,
        );
      }

      return false;
    }

isExportedHigherOrderFunction({ node, }: FunctionInfo<FunctionNode>): boolean

Parameters:

  • { node, } FunctionInfo<FunctionNode>

Returns: boolean

Calls:

  • isFunction (from ../util)
  • getReturnsInFunction
  • doesImmediatelyReturnFunctionExpression (from ../util/explicitReturnTypeUtils)
  • checkedFunctions.has

Internal Comments:

// the parent of a return will always be a block statement, so we can skip over it (x3)

Code
function isExportedHigherOrderFunction({
      node,
    }: FunctionInfo<FunctionNode>): boolean {
      let current: TSESTree.Node | undefined = node.parent;
      while (current) {
        if (current.type === AST_NODE_TYPES.ReturnStatement) {
          // the parent of a return will always be a block statement, so we can skip over it
          current = current.parent.parent;
          continue;
        }

        if (!isFunction(current)) {
          return false;
        }
        const returns = getReturnsInFunction(current);
        if (
          !doesImmediatelyReturnFunctionExpression({ node: current, returns })
        ) {
          return false;
        }

        if (checkedFunctions.has(current)) {
          return true;
        }

        current = current.parent;
      }

      return false;
    }

followReference(node: TSESTree.Identifier): void

Parameters:

  • node TSESTree.Identifier

Returns: void

Calls:

  • context.sourceCode.getScope
  • scope.set.get
  • [ DefinitionType.CatchClause, DefinitionType.ImplicitGlobalVariable, DefinitionType.ImportBinding, DefinitionType.Parameter, ].includes
  • checkNode

Internal Comments:

/* istanbul ignore if */
// check all of the definitions
// cases we don't care about in this rule
// follow references to find writes to the variable
// we don't want to check the initialization ref, as this is handled by the declaration check (x2)

Code
function followReference(node: TSESTree.Identifier): void {
      const scope = context.sourceCode.getScope(node);
      const variable = scope.set.get(node.name);
      /* istanbul ignore if */ if (!variable) {
        return;
      }

      // check all of the definitions
      for (const definition of variable.defs) {
        // cases we don't care about in this rule
        if (
          [
            DefinitionType.CatchClause,
            DefinitionType.ImplicitGlobalVariable,
            DefinitionType.ImportBinding,
            DefinitionType.Parameter,
          ].includes(definition.type)
        ) {
          continue;
        }

        checkNode(definition.node);
      }

      // follow references to find writes to the variable
      for (const reference of variable.references) {
        if (
          // we don't want to check the initialization ref, as this is handled by the declaration check
          !reference.init &&
          reference.writeExpr
        ) {
          checkNode(reference.writeExpr);
        }
      }
    }

checkNode(node: TSESTree.Node | null): void

Parameters:

  • node TSESTree.Node | null

Returns: void

Calls:

  • alreadyVisited.has
  • alreadyVisited.add
  • getReturnsInFunction
  • checkFunctionExpression
  • checkNode
  • checkFunction
  • followReference
  • checkEmptyBodyFunctionExpression
Code
function checkNode(node: TSESTree.Node | null): void {
      if (node == null || alreadyVisited.has(node)) {
        return;
      }
      alreadyVisited.add(node);

      switch (node.type) {
        case AST_NODE_TYPES.ArrowFunctionExpression:
        case AST_NODE_TYPES.FunctionExpression: {
          const returns = getReturnsInFunction(node);
          return checkFunctionExpression({ node, returns });
        }

        case AST_NODE_TYPES.ArrayExpression:
          for (const element of node.elements) {
            checkNode(element);
          }
          return;

        case AST_NODE_TYPES.PropertyDefinition:
        case AST_NODE_TYPES.AccessorProperty:
        case AST_NODE_TYPES.MethodDefinition:
        case AST_NODE_TYPES.TSAbstractMethodDefinition:
          if (
            node.accessibility === 'private' ||
            node.key.type === AST_NODE_TYPES.PrivateIdentifier
          ) {
            return;
          }
          return checkNode(node.value);

        case AST_NODE_TYPES.ClassDeclaration:
        case AST_NODE_TYPES.ClassExpression:
          for (const element of node.body.body) {
            checkNode(element);
          }
          return;

        case AST_NODE_TYPES.FunctionDeclaration: {
          const returns = getReturnsInFunction(node);
          return checkFunction({ node, returns });
        }

        case AST_NODE_TYPES.Identifier:
          return followReference(node);

        case AST_NODE_TYPES.ObjectExpression:
          for (const property of node.properties) {
            checkNode(property);
          }
          return;

        case AST_NODE_TYPES.Property:
          return checkNode(node.value);

        case AST_NODE_TYPES.TSEmptyBodyFunctionExpression:
          return checkEmptyBodyFunctionExpression(node);

        case AST_NODE_TYPES.VariableDeclaration:
          for (const declaration of node.declarations) {
            checkNode(declaration);
          }
          return;

        case AST_NODE_TYPES.VariableDeclarator:
          return checkNode(node.init);
      }
    }

checkEmptyBodyFunctionExpression(node: TSESTree.TSEmptyBodyFunctionExpression): void

Parameters:

  • node TSESTree.TSEmptyBodyFunctionExpression

Returns: void

Calls:

  • context.report
  • checkParameters
Code
function checkEmptyBodyFunctionExpression(
      node: TSESTree.TSEmptyBodyFunctionExpression,
    ): void {
      const isConstructor =
        node.parent.type === AST_NODE_TYPES.MethodDefinition &&
        node.parent.kind === 'constructor';
      const isSetAccessor =
        (node.parent.type === AST_NODE_TYPES.TSAbstractMethodDefinition ||
          node.parent.type === AST_NODE_TYPES.MethodDefinition) &&
        node.parent.kind === 'set';
      if (!isConstructor && !isSetAccessor && !node.returnType) {
        context.report({
          node,
          messageId: 'missingReturnType',
        });
      }

      checkParameters(node);
    }

checkFunctionExpression({ node, returns, }: FunctionInfo<FunctionExpression>): void

Parameters:

  • { node, returns, } FunctionInfo<FunctionExpression>

Returns: void

Calls:

  • checkedFunctions.has
  • checkedFunctions.add
  • isAllowedName
  • isTypedFunctionExpression (from ../util/explicitReturnTypeUtils)
  • ancestorHasReturnType (from ../util/explicitReturnTypeUtils)
  • hasOverloadSignatures (from ../util)
  • checkFunctionExpressionReturnType (from ../util/explicitReturnTypeUtils)
  • context.report
  • checkParameters
Code
function checkFunctionExpression({
      node,
      returns,
    }: FunctionInfo<FunctionExpression>): void {
      if (checkedFunctions.has(node)) {
        return;
      }
      checkedFunctions.add(node);

      if (
        isAllowedName(node.parent) ||
        isTypedFunctionExpression(node, options) ||
        ancestorHasReturnType(node)
      ) {
        return;
      }

      if (
        options.allowOverloadFunctions &&
        node.parent.type === AST_NODE_TYPES.MethodDefinition &&
        hasOverloadSignatures(node.parent, context)
      ) {
        return;
      }

      checkFunctionExpressionReturnType(
        { node, returns },
        options,
        context.sourceCode,
        loc => {
          context.report({
            loc,
            node,
            messageId: 'missingReturnType',
          });
        },
      );

      checkParameters(node);
    }

checkFunction({ node, returns, }: FunctionInfo<TSESTree.FunctionDeclarati…): void

Parameters:

  • { node, returns, } FunctionInfo<TSESTree.FunctionDeclaration>

Returns: void

Calls:

  • checkedFunctions.has
  • checkedFunctions.add
  • isAllowedName
  • ancestorHasReturnType (from ../util/explicitReturnTypeUtils)
  • hasOverloadSignatures (from ../util)
  • checkFunctionReturnType (from ../util/explicitReturnTypeUtils)
  • context.report
  • checkParameters
Code
function checkFunction({
      node,
      returns,
    }: FunctionInfo<TSESTree.FunctionDeclaration>): void {
      if (checkedFunctions.has(node)) {
        return;
      }
      checkedFunctions.add(node);

      if (isAllowedName(node) || ancestorHasReturnType(node)) {
        return;
      }

      if (
        options.allowOverloadFunctions &&
        hasOverloadSignatures(node, context)
      ) {
        return;
      }

      checkFunctionReturnType(
        { node, returns },
        options,
        context.sourceCode,
        loc => {
          context.report({
            loc,
            node,
            messageId: 'missingReturnType',
          });
        },
      );

      checkParameters(node);
    }

Type Aliases

Options

type Options = [
  {
    allowArgumentsExplicitlyTypedAsAny?: boolean;
    allowDirectConstAssertionInArrowFunctions?: boolean;
    allowedNames?: string[];
    allowHigherOrderFunctions?: boolean;
    allowTypedFunctionExpressions?: boolean;
    allowOverloadFunctions?: boolean;
  },
];

MessageIds

type MessageIds = | 'anyTypedArg'
  | 'anyTypedArgUnnamed'
  | 'missingArgType'
  | 'missingArgTypeUnnamed'
  | 'missingReturnType';

Generated by Syntax Scribe