Skip to content

⬅️ Back to Table of Contents

📄 only-throw-error

📊 Analysis Summary

Metric Count
🔧 Functions 3
📦 Imports 15
📑 Type Aliases 2

📚 Table of Contents

🛠️ File Location:

📂 packages/eslint-plugin/src/rules/only-throw-error.ts

📤 Default Export

export default createRule<Options, MessageIds>({ ... })
Property Value
name 'only-throw-error'
meta.type 'problem'
meta.docs.description 'Disallow throwing non-Error values as exceptions'
meta.docs.extendsBaseRule 'no-throw-literal'
meta.docs.recommended 'recommended'
meta.docs.requiresTypeChecking true
meta.messages.object 'Expected an error object to be thrown.'
meta.messages.undef 'Do not throw undefined.'
meta.schema [ { type: 'object', additionalProperties: false, properties: { allow: { ...typeOrValueSpecifiersSchema, description: ...
defaultOptions [ { allow: [], allowRethrowing: true, allowThrowingAny: true, allowThrowingUnknown: true, }, ]

Entry point: create — documented under Functions.


📦 Imports

Name Source
TSESTree @typescript-eslint/utils
AST_NODE_TYPES @typescript-eslint/utils
isThenableType ts-api-utils
TypeOrValueSpecifier ../util
createRule ../util
findVariable ../util
getParserServices ../util
isErrorLike ../util
isTypeAnyType ../util
isTypeUnknownType ../util
typeMatchesSomeSpecifier ../util
typeOrValueSpecifiersSchema ../util
nullThrows ../util
parseCatchCall ../util/promiseUtils
parseThenCall ../util/promiseUtils

Functions

create(context: any, [options]: any): { ThrowStatement(node: any): void; }

Parameters:

  • context any
  • [options] any

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

Calls:

  • getParserServices (from ../util)
  • context.sourceCode.getScope
  • nullThrows (from ../util)
  • findVariable (from ../util)
  • smVariable.defs.filter
  • parseCatchCall (from ../util/promiseUtils)
  • parseThenCall (from ../util/promiseUtils)
  • services.esTreeNodeToTSNodeMap.get
  • isThenableType (from ts-api-utils)
  • services.program.getTypeChecker
  • isRethrownError
  • services.getTypeAtLocation
  • typeMatchesSomeSpecifier (from ../util)
  • tsutils.isTypeFlagSet
  • context.report
  • isTypeAnyType (from ../util)
  • isTypeUnknownType (from ../util)
  • isErrorLike (from ../util)
  • checkThrowArgument

Internal Comments:

// try { /* ... */ } catch (x) { throw x; }
// promise.catch(x => { throw x; })
// promise.then(onFulfilled, x => { throw x; })
// make sure we're actually dealing with a promise

Code
create(context, [options]) {
    const services = getParserServices(context);
    const allow = options.allow;

    function isRethrownError(node: TSESTree.Node): boolean {
      if (node.type !== AST_NODE_TYPES.Identifier) {
        return false;
      }

      const scope = context.sourceCode.getScope(node);

      const smVariable = nullThrows(
        findVariable(scope, node),
        `Variable ${node.name} should exist in scope manager`,
      );

      const variableDefinitions = smVariable.defs.filter(
        def => def.isVariableDefinition,
      );
      if (variableDefinitions.length !== 1) {
        return false;
      }
      const def = smVariable.defs[0];

      // try { /* ... */ } catch (x) { throw x; }
      if (def.node.type === AST_NODE_TYPES.CatchClause) {
        return true;
      }

      // promise.catch(x => { throw x; })
      // promise.then(onFulfilled, x => { throw x; })
      if (
        def.node.type === AST_NODE_TYPES.ArrowFunctionExpression &&
        def.node.params.length >= 1 &&
        def.node.params[0] === def.name &&
        def.node.parent.type === AST_NODE_TYPES.CallExpression
      ) {
        const callExpression = def.node.parent;

        const parsedPromiseHandlingCall =
          parseCatchCall(callExpression, context) ??
          parseThenCall(callExpression, context);
        if (parsedPromiseHandlingCall != null) {
          const { object, onRejected } = parsedPromiseHandlingCall;
          if (onRejected === def.node) {
            const tsObjectNode = services.esTreeNodeToTSNodeMap.get(
              object,
            ) as ts.Expression;

            // make sure we're actually dealing with a promise
            if (
              isThenableType(services.program.getTypeChecker(), tsObjectNode)
            ) {
              return true;
            }
          }
        }
      }

      return false;
    }

    function checkThrowArgument(node: TSESTree.Node): void {
      if (options.allowRethrowing && isRethrownError(node)) {
        return;
      }

      const type = services.getTypeAtLocation(node);

      if (typeMatchesSomeSpecifier(type, allow, services.program)) {
        return;
      }

      if (tsutils.isTypeFlagSet(type, ts.TypeFlags.Undefined)) {
        context.report({ node, messageId: 'undef' });
        return;
      }

      if (options.allowThrowingAny && isTypeAnyType(type)) {
        return;
      }

      if (options.allowThrowingUnknown && isTypeUnknownType(type)) {
        return;
      }

      if (isErrorLike(services.program, type)) {
        return;
      }

      context.report({ node, messageId: 'object' });
    }

    return {
      ThrowStatement(node): void {
        checkThrowArgument(node.argument);
      },
    };
  }

Internal helpers

Declared inside another function in this file.

isRethrownError(node: TSESTree.Node): boolean

Parameters:

  • node TSESTree.Node

Returns: boolean

Calls:

  • context.sourceCode.getScope
  • nullThrows (from ../util)
  • findVariable (from ../util)
  • smVariable.defs.filter
  • parseCatchCall (from ../util/promiseUtils)
  • parseThenCall (from ../util/promiseUtils)
  • services.esTreeNodeToTSNodeMap.get
  • isThenableType (from ts-api-utils)
  • services.program.getTypeChecker

Internal Comments:

// try { /* ... */ } catch (x) { throw x; }
// promise.catch(x => { throw x; })
// promise.then(onFulfilled, x => { throw x; })
// make sure we're actually dealing with a promise

Code
function isRethrownError(node: TSESTree.Node): boolean {
      if (node.type !== AST_NODE_TYPES.Identifier) {
        return false;
      }

      const scope = context.sourceCode.getScope(node);

      const smVariable = nullThrows(
        findVariable(scope, node),
        `Variable ${node.name} should exist in scope manager`,
      );

      const variableDefinitions = smVariable.defs.filter(
        def => def.isVariableDefinition,
      );
      if (variableDefinitions.length !== 1) {
        return false;
      }
      const def = smVariable.defs[0];

      // try { /* ... */ } catch (x) { throw x; }
      if (def.node.type === AST_NODE_TYPES.CatchClause) {
        return true;
      }

      // promise.catch(x => { throw x; })
      // promise.then(onFulfilled, x => { throw x; })
      if (
        def.node.type === AST_NODE_TYPES.ArrowFunctionExpression &&
        def.node.params.length >= 1 &&
        def.node.params[0] === def.name &&
        def.node.parent.type === AST_NODE_TYPES.CallExpression
      ) {
        const callExpression = def.node.parent;

        const parsedPromiseHandlingCall =
          parseCatchCall(callExpression, context) ??
          parseThenCall(callExpression, context);
        if (parsedPromiseHandlingCall != null) {
          const { object, onRejected } = parsedPromiseHandlingCall;
          if (onRejected === def.node) {
            const tsObjectNode = services.esTreeNodeToTSNodeMap.get(
              object,
            ) as ts.Expression;

            // make sure we're actually dealing with a promise
            if (
              isThenableType(services.program.getTypeChecker(), tsObjectNode)
            ) {
              return true;
            }
          }
        }
      }

      return false;
    }

checkThrowArgument(node: TSESTree.Node): void

Parameters:

  • node TSESTree.Node

Returns: void

Calls:

  • isRethrownError
  • services.getTypeAtLocation
  • typeMatchesSomeSpecifier (from ../util)
  • tsutils.isTypeFlagSet
  • context.report
  • isTypeAnyType (from ../util)
  • isTypeUnknownType (from ../util)
  • isErrorLike (from ../util)
Code
function checkThrowArgument(node: TSESTree.Node): void {
      if (options.allowRethrowing && isRethrownError(node)) {
        return;
      }

      const type = services.getTypeAtLocation(node);

      if (typeMatchesSomeSpecifier(type, allow, services.program)) {
        return;
      }

      if (tsutils.isTypeFlagSet(type, ts.TypeFlags.Undefined)) {
        context.report({ node, messageId: 'undef' });
        return;
      }

      if (options.allowThrowingAny && isTypeAnyType(type)) {
        return;
      }

      if (options.allowThrowingUnknown && isTypeUnknownType(type)) {
        return;
      }

      if (isErrorLike(services.program, type)) {
        return;
      }

      context.report({ node, messageId: 'object' });
    }

Type Aliases

MessageIds

type MessageIds = 'object' | 'undef';

Options

type Options = [
  {
    allow?: TypeOrValueSpecifier[];
    allowRethrowing?: boolean;
    allowThrowingAny?: boolean;
    allowThrowingUnknown?: boolean;
  },
];

Generated by Syntax Scribe