Skip to content

⬅️ Back to Table of Contents

📄 no-invalid-void-type

📊 Analysis Summary

Metric Count
🔧 Functions 6
📦 Imports 4
📐 Interfaces 1
📑 Type Aliases 1

📚 Table of Contents

🛠️ File Location:

📂 packages/eslint-plugin/src/rules/no-invalid-void-type.ts

📤 Default Export

export default createRule<[Options], MessageIds>({ ... })
Property Value
name 'no-invalid-void-type'
meta.type 'problem'
meta.docs.description 'Disallow void type outside of generic or return types'
meta.docs.recommended 'strict'
meta.messages.invalidVoidForGeneric '{{ generic }} may not have void as a type argument.'
meta.messages.invalidVoidNotReturn 'void is only valid as a return type.'
meta.messages.invalidVoidNotReturnOrGeneric 'void is only valid as a return type or generic type argument.'
meta.messages.invalidVoidNotReturnOrThisParam 'void is only valid as return type or type of this parameter.'
meta.messages.invalidVoidNotReturnOrThisParamOrGeneric 'void is only valid as a return type or generic type argument or the type of a this parameter.'
meta.messages.invalidVoidUnionConstituent 'void is not valid as a constituent in a union type'
meta.schema [ { type: 'object', additionalProperties: false, properties: { allowAsThisParameter: { type: 'boolean', description: ...
defaultOptions [ { allowAsThisParameter: false, allowInGenericTypeArguments: true }, ]

Entry point: create — documented under Functions.


📦 Imports

Name Source
TSESTree @typescript-eslint/utils
AST_NODE_TYPES @typescript-eslint/utils
createRule ../util
hasOverloadSignatures ../util

Functions

create(context: any, [{ allowAsThisParameter, allowI…: any): { TSVoidKeyword(node: TSESTree.TSVoidKeyword): void; }

Parameters:

  • context any
  • [{ allowAsThisParameter, allowInGenericTypeArguments }] any

Returns: { TSVoidKeyword(node: TSESTree.TSVoidKeyword): void; }

Calls:

  • validParents.push
  • Array.isArray
  • context.sourceCode .getText(node.parent.parent.typeName) .replaceAll
  • allowInGenericTypeArguments .map(s => s.replaceAll(' ', '')) .includes
  • context.report
  • getNotReturnOrGenericMessageId
  • node.types.every
  • validUnionMembers.includes
  • member.typeArguments.params .map(param => param.type) .includes
  • checkGenericTypeArgument
  • checkDefaultVoid
  • isValidUnionType
  • getParentFunctionDeclarationNode
  • hasOverloadSignatures (from ../util)
  • validParents.includes
  • invalidGrandParents.includes

Internal Comments:

/**
     * @brief check if the given void keyword is used as a valid generic type
     *
     * reports if the type parametrized by void is not in the allowlist, or
     * allowInGenericTypeArguments is false.
     * no-op if the given void keyword is not used as generic type
     */
// only matches T<..., void, ...>
// extra check for precaution
/* istanbul ignore next */
// check allowlist
/**
     * @brief checks if the generic type parameter defaults to void
     */
/**
     * @brief checks that a union containing void is valid
     * @return true if every member of the union is specified as a valid type in
     * validUnionMembers, or is a valid generic type parametrized by void
     */
// allows any T<..., void, ...> here, checked by checkGenericTypeArgument
// checks T<..., void, ...> against specification of allowInGenericArguments option
// allow <T = void> if allowInGenericTypeArguments is specified, and report if the generic type parameter extends void
// union w/ void must contain types from validUnionMembers, or a valid generic void type
// using `void` as part of the return type of function overloading implementation
// this parameter is ok to be void.
// default cases
// https://github.com/typescript-eslint/typescript-eslint/issues/6225
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion

Code
create(context, [{ allowAsThisParameter, allowInGenericTypeArguments }]) {
    const validParents: AST_NODE_TYPES[] = [
      AST_NODE_TYPES.TSTypeAnnotation, //
    ];
    const invalidGrandParents: AST_NODE_TYPES[] = [
      AST_NODE_TYPES.TSPropertySignature,
      AST_NODE_TYPES.CallExpression,
      AST_NODE_TYPES.PropertyDefinition,
      AST_NODE_TYPES.AccessorProperty,
      AST_NODE_TYPES.Identifier,
    ];
    const validUnionMembers: AST_NODE_TYPES[] = [
      AST_NODE_TYPES.TSVoidKeyword,
      AST_NODE_TYPES.TSNeverKeyword,
    ];

    if (allowInGenericTypeArguments === true) {
      validParents.push(AST_NODE_TYPES.TSTypeParameterInstantiation);
    }

    /**
     * @brief check if the given void keyword is used as a valid generic type
     *
     * reports if the type parametrized by void is not in the allowlist, or
     * allowInGenericTypeArguments is false.
     * no-op if the given void keyword is not used as generic type
     */
    function checkGenericTypeArgument(node: TSESTree.TSVoidKeyword): void {
      // only matches T<..., void, ...>
      // extra check for precaution
      /* istanbul ignore next */
      if (
        node.parent.type !== AST_NODE_TYPES.TSTypeParameterInstantiation ||
        node.parent.parent.type !== AST_NODE_TYPES.TSTypeReference
      ) {
        return;
      }

      // check allowlist
      if (Array.isArray(allowInGenericTypeArguments)) {
        const fullyQualifiedName = context.sourceCode
          .getText(node.parent.parent.typeName)
          .replaceAll(' ', '');

        if (
          !allowInGenericTypeArguments
            .map(s => s.replaceAll(' ', ''))
            .includes(fullyQualifiedName)
        ) {
          context.report({
            node,
            messageId: 'invalidVoidForGeneric',
            data: { generic: fullyQualifiedName },
          });
        }
        return;
      }

      if (!allowInGenericTypeArguments) {
        context.report({
          node,
          messageId: allowAsThisParameter
            ? 'invalidVoidNotReturnOrThisParam'
            : 'invalidVoidNotReturn',
        });
      }
    }

    /**
     * @brief checks if the generic type parameter defaults to void
     */
    function checkDefaultVoid(
      node: TSESTree.TSVoidKeyword,
      parentNode: TSESTree.TSTypeParameter,
    ): void {
      if (parentNode.default !== node) {
        context.report({
          node,
          messageId: getNotReturnOrGenericMessageId(node),
        });
      }
    }

    /**
     * @brief checks that a union containing void is valid
     * @return true if every member of the union is specified as a valid type in
     * validUnionMembers, or is a valid generic type parametrized by void
     */
    function isValidUnionType(node: TSESTree.TSUnionType): boolean {
      return node.types.every(
        member =>
          validUnionMembers.includes(member.type) ||
          // allows any T<..., void, ...> here, checked by checkGenericTypeArgument
          (member.type === AST_NODE_TYPES.TSTypeReference &&
            member.typeArguments?.type ===
              AST_NODE_TYPES.TSTypeParameterInstantiation &&
            member.typeArguments.params
              .map(param => param.type)
              .includes(AST_NODE_TYPES.TSVoidKeyword)),
      );
    }

    return {
      TSVoidKeyword(node: TSESTree.TSVoidKeyword): void {
        // checks T<..., void, ...> against specification of allowInGenericArguments option
        if (
          node.parent.type === AST_NODE_TYPES.TSTypeParameterInstantiation &&
          node.parent.parent.type === AST_NODE_TYPES.TSTypeReference
        ) {
          checkGenericTypeArgument(node);
          return;
        }

        // allow <T = void> if allowInGenericTypeArguments is specified, and report if the generic type parameter extends void
        if (
          allowInGenericTypeArguments &&
          node.parent.type === AST_NODE_TYPES.TSTypeParameter &&
          node.parent.default?.type === AST_NODE_TYPES.TSVoidKeyword
        ) {
          checkDefaultVoid(node, node.parent);
          return;
        }

        // union w/ void must contain types from validUnionMembers, or a valid generic void type
        if (
          node.parent.type === AST_NODE_TYPES.TSUnionType &&
          isValidUnionType(node.parent)
        ) {
          return;
        }

        // using `void` as part of the return type of function overloading implementation
        if (node.parent.type === AST_NODE_TYPES.TSUnionType) {
          const declaringFunction = getParentFunctionDeclarationNode(
            node.parent,
          );

          if (
            declaringFunction &&
            hasOverloadSignatures(declaringFunction, context)
          ) {
            return;
          }
        }

        // this parameter is ok to be void.
        if (
          allowAsThisParameter &&
          node.parent.type === AST_NODE_TYPES.TSTypeAnnotation &&
          node.parent.parent.type === AST_NODE_TYPES.Identifier &&
          node.parent.parent.name === 'this'
        ) {
          return;
        }

        // default cases
        if (
          validParents.includes(node.parent.type) &&
          // https://github.com/typescript-eslint/typescript-eslint/issues/6225
          // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
          !invalidGrandParents.includes(node.parent.parent!.type)
        ) {
          return;
        }

        context.report({
          node,
          messageId:
            allowInGenericTypeArguments && allowAsThisParameter
              ? 'invalidVoidNotReturnOrThisParamOrGeneric'
              : allowInGenericTypeArguments
                ? getNotReturnOrGenericMessageId(node)
                : allowAsThisParameter
                  ? 'invalidVoidNotReturnOrThisParam'
                  : 'invalidVoidNotReturn',
        });
      },
    };
  }

getNotReturnOrGenericMessageId(node: TSESTree.TSVoidKeyword): MessageIds

Parameters:

  • node TSESTree.TSVoidKeyword

Returns: MessageIds

Code
function getNotReturnOrGenericMessageId(
  node: TSESTree.TSVoidKeyword,
): MessageIds {
  return node.parent.type === AST_NODE_TYPES.TSUnionType
    ? 'invalidVoidUnionConstituent'
    : 'invalidVoidNotReturnOrGeneric';
}

getParentFunctionDeclarationNode(node: TSESTree.Node): TSESTree.FunctionDeclaration | TSESTree.MethodDefinition | …

Parameters:

  • node TSESTree.Node

Returns: TSESTree.FunctionDeclaration | TSESTree.MethodDefinition | null

Code
function getParentFunctionDeclarationNode(
  node: TSESTree.Node,
): TSESTree.FunctionDeclaration | TSESTree.MethodDefinition | null {
  let current = node.parent;
  while (current) {
    if (current.type === AST_NODE_TYPES.FunctionDeclaration) {
      return current;
    }

    if (
      current.type === AST_NODE_TYPES.MethodDefinition &&
      current.value.body != null
    ) {
      return current;
    }

    current = current.parent;
  }

  return null;
}

Internal helpers

Declared inside another function in this file.

checkGenericTypeArgument(node: TSESTree.TSVoidKeyword): void

Tags: @brief check if the given void keyword is used as a valid generic type

reports if the type parametrized by void is not in the allowlist, or allowInGenericTypeArguments is false. no-op if the given void keyword is not used as generic type

Raw JSDoc
/**
     * @brief check if the given void keyword is used as a valid generic type
     *
     * reports if the type parametrized by void is not in the allowlist, or
     * allowInGenericTypeArguments is false.
     * no-op if the given void keyword is not used as generic type
     */

Calls:

  • Array.isArray
  • context.sourceCode .getText(node.parent.parent.typeName) .replaceAll
  • allowInGenericTypeArguments .map(s => s.replaceAll(' ', '')) .includes
  • context.report

Internal Comments:

// only matches T<..., void, ...>
// extra check for precaution
/* istanbul ignore next */
// check allowlist

Code
function checkGenericTypeArgument(node: TSESTree.TSVoidKeyword): void {
      // only matches T<..., void, ...>
      // extra check for precaution
      /* istanbul ignore next */
      if (
        node.parent.type !== AST_NODE_TYPES.TSTypeParameterInstantiation ||
        node.parent.parent.type !== AST_NODE_TYPES.TSTypeReference
      ) {
        return;
      }

      // check allowlist
      if (Array.isArray(allowInGenericTypeArguments)) {
        const fullyQualifiedName = context.sourceCode
          .getText(node.parent.parent.typeName)
          .replaceAll(' ', '');

        if (
          !allowInGenericTypeArguments
            .map(s => s.replaceAll(' ', ''))
            .includes(fullyQualifiedName)
        ) {
          context.report({
            node,
            messageId: 'invalidVoidForGeneric',
            data: { generic: fullyQualifiedName },
          });
        }
        return;
      }

      if (!allowInGenericTypeArguments) {
        context.report({
          node,
          messageId: allowAsThisParameter
            ? 'invalidVoidNotReturnOrThisParam'
            : 'invalidVoidNotReturn',
        });
      }
    }

checkDefaultVoid(node: TSESTree.TSVoidKeyword, parentNode: TSESTree.TSTypeParameter): void

Tags: @brief checks if the generic type parameter defaults to void

Raw JSDoc
/**
     * @brief checks if the generic type parameter defaults to void
     */

Calls:

  • context.report
  • getNotReturnOrGenericMessageId
Code
function checkDefaultVoid(
      node: TSESTree.TSVoidKeyword,
      parentNode: TSESTree.TSTypeParameter,
    ): void {
      if (parentNode.default !== node) {
        context.report({
          node,
          messageId: getNotReturnOrGenericMessageId(node),
        });
      }
    }

isValidUnionType(node: TSESTree.TSUnionType): boolean

Returns: undefined true if every member of the union is specified as a valid type in validUnionMembers, or is a valid generic type parametrized by void

Tags: @brief checks that a union containing void is valid

Raw JSDoc
/**
     * @brief checks that a union containing void is valid
     * @return true if every member of the union is specified as a valid type in
     * validUnionMembers, or is a valid generic type parametrized by void
     */

Calls:

  • node.types.every
  • validUnionMembers.includes
  • member.typeArguments.params .map(param => param.type) .includes

Internal Comments:

// allows any T<..., void, ...> here, checked by checkGenericTypeArgument

Code
function isValidUnionType(node: TSESTree.TSUnionType): boolean {
      return node.types.every(
        member =>
          validUnionMembers.includes(member.type) ||
          // allows any T<..., void, ...> here, checked by checkGenericTypeArgument
          (member.type === AST_NODE_TYPES.TSTypeReference &&
            member.typeArguments?.type ===
              AST_NODE_TYPES.TSTypeParameterInstantiation &&
            member.typeArguments.params
              .map(param => param.type)
              .includes(AST_NODE_TYPES.TSVoidKeyword)),
      );
    }

Interfaces

Options

Interface Code
export interface Options {
  allowAsThisParameter?: boolean;
  allowInGenericTypeArguments?: boolean | [string, ...string[]];
}

Properties

Name Type Optional Description
allowAsThisParameter boolean not shown
allowInGenericTypeArguments boolean \| [string, ...string[]] not shown

Type Aliases

MessageIds

type MessageIds = | 'invalidVoidForGeneric'
  | 'invalidVoidNotReturn'
  | 'invalidVoidNotReturnOrGeneric'
  | 'invalidVoidNotReturnOrThisParam'
  | 'invalidVoidNotReturnOrThisParamOrGeneric'
  | 'invalidVoidUnionConstituent';

Generated by Syntax Scribe