Skip to content

⬅️ Back to Table of Contents

📄 unified-signatures

📊 Analysis Summary

Metric Count
🔧 Functions 37
📦 Imports 7
📐 Interfaces 2
📑 Type Aliases 9

📚 Table of Contents

🛠️ File Location:

📂 packages/eslint-plugin/src/rules/unified-signatures.ts

📤 Default Export

export default createRule<Options, MessageIds>({ ... })
Property Value
name 'unified-signatures'
meta.type 'suggestion'
meta.docs.description 'Disallow two overloads that could be unified into one with a union or an optional/rest parameter'
meta.docs.recommended 'strict'
meta.messages.allParametersAreSame '{{failureStringStart}} with identical parameters.'
meta.messages.omittingRestParameter '{{failureStringStart}} with a rest parameter.'
meta.messages.omittingSingleParameter '{{failureStringStart}} with an optional parameter.'
meta.messages.singleParameterDifference '{{failureStringStart}} taking {{types}}.'
meta.schema [ { type: 'object', additionalProperties: false, properties: { ignoreDifferentlyNamedParameters: { type: 'boolean', d...
defaultOptions [ { ignoreDifferentlyNamedParameters: false, ignoreOverloadsWithDifferentJSDoc: false, }, ]

Entry point: create — documented under Functions.


📦 Imports

Name Source
TSESTree @typescript-eslint/utils
AST_NODE_TYPES @typescript-eslint/utils
AST_TOKEN_TYPES @typescript-eslint/utils
Equal ../util
arraysAreEqual ../util
createRule ../util
nullThrows ../util

Functions

create(context: any, [{ ignoreDifferentlyNamedParame…: any): { ClassDeclaration(node: any): void; Program: (parent: Scop…

Parameters:

  • context any
  • [{ ignoreDifferentlyNamedParameters, ignoreOverloadsWithDifferentJSDoc }] any

Returns: { ClassDeclaration(node: any): void; Program: (parent: ScopeNode, typeParameters?: TSESTree.TSTypeParameterDeclaration) => void; TSInterfaceDeclaration(node: any): void; TSModuleBlock: (parent: ScopeNode, typeParameters?: TSESTree.TSTypeParameterDeclaration) => void; TSTypeLiteral: (parent: ScopeNode, typeParameters?: TSESTree.TSTypeParameterDeclaration) => void; MethodDefinition(node: any): void; TSAbstractMethodDefinition(node: any): void; TSCallSignatureDeclaration: (signature: OverloadNode, key?: string, containingNode?: ContainingNode) => void; TSConstructSignatureDeclaration: (signature: OverloadNode, key?: string, containingNode?: ContainingNode) => void; TSDeclareFunction(node: any): void; TSMethodSignature(node: any): void; 'ClassDeclaration:exit': () => void; 'Program:exit': () => void; 'TSInterfaceDeclaration:exit': () => void; 'TSModuleBlock:exit': () => void; 'TSTypeLiteral:exit': () => void; }

Calls:

  • getParameterTypeAnnotation
  • context.report
  • failureStringStart
  • getUnifiedTypeText
  • getIsTypeParameter
  • forEachPair
  • compareSignatures
  • result.push
  • signaturesCanBeUnified
  • signaturesHaveSameAmountOfParameters
  • signaturesDifferByOptionalOrRestParameter
  • Math.min
  • getStaticParameterName
  • getBlockCommentForNode
  • getCommentTargetNode
  • typesAreEqual
  • arraysAreEqual (from ../util)
  • signatureUsesTypeParameter
  • isThisVoidParam
  • getIndexOfFirstDifference
  • types1.slice
  • types2.slice
  • parametersHaveEqualSigils
  • isTSParameterProperty
  • getUnionMemberText
  • nullThrows (from ../util)
  • getUnionMembers
  • uniqueMembers.some
  • uniqueMembers.push
  • uniqueMembers .map(member => getUnionMemberText(member)) .join
  • type.types.flatMap
  • context.sourceCode.getText
  • isThisParam
  • sig1.at
  • sig2.at
  • parameterMayBeMissing
  • set.add
  • set.has
  • sig.params.some
  • typeContainsTypeParameter
  • isIdentifier
  • isTypeParameter
  • equal
  • action
  • scopes.push
  • checkOverloads
  • scope.overloads.values
  • addFailures
  • scopes.pop
  • context.sourceCode .getCommentsBefore(node) .reverse() .find
  • getOverloadKey
  • currentScope.overloads.get
  • overloads.push
  • currentScope.overloads.set
  • createScope
  • isGetterOrSetter
  • addOverload
  • getExportingNode

Internal Comments:

//---------------------------------------------------------------------- (x4)
// Helpers
// For only 2 overloads we don't need to specify which is the other one. (x2)
// Must return the same type. (x2)
// Must take the same type parameters. (x2)
// If one uses a type parameter (from outside) and the other doesn't, they shouldn't be joined. (x2)
/**
     * Detect no difference, i.e. `a(x: number, y: string)` and `a(x: number, y: string)`,
     * or one param difference, i.e. `a(x: number, y: number, z: number)` and `a(x: number, y: string, z: number)`.
     */
// exempt signatures with `this: void` from the rule (x2)
// If remaining arrays are equal, the signatures differ by just one parameter type
// Can unify `a?: string` and `b?: number`. Can't unify `...args: string[]` and `...args: number[]`.
// See https://github.com/Microsoft/TypeScript/issues/5077
// When a signature's parameter has no type annotation
/**
     * Detect `a(): void` and `a(x: number): void`.
     * Returns the parameter declaration (`x: number` in this example) that should be optional/rest, and overload it's a part of.
     */
// If one signature has explicit this type and another doesn't, they can't
// be unified.
// If one is has 2+ parameters more than the other, they must all be optional/rest.
// Differ by optional parameters: f() and f(x), f() and f(x, ?y, ...z)
// Not allowed: f() and f(x, y)
/** Given type parameters, returns a function to test whether a type is one of those parameters. */
/** True if any of the outer type parameters are used in a signature. */
/** True for optional/rest parameters. */
/** False if one is optional and the other isn't, or one is a rest parameter and the other isn't. */
/* Returns the first index where `a` and `b` differ. */
/** Calls `action` for every pair of values in `values`. */
/**
     * @returns the first valid JSDoc comment annotating `node`
     */
// Public
// collect overloads (x2)
// validate scopes (x2)

Code
create(
    context,
    [{ ignoreDifferentlyNamedParameters, ignoreOverloadsWithDifferentJSDoc }],
  ) {
    //----------------------------------------------------------------------
    // Helpers
    //----------------------------------------------------------------------

    function failureStringStart(otherLine?: number): string {
      // For only 2 overloads we don't need to specify which is the other one.
      const overloads =
        otherLine == null
          ? 'These overloads'
          : `This overload and the one on line ${otherLine}`;
      return `${overloads} can be combined into one signature`;
    }

    function addFailures(failures: Failure[]): void {
      for (const failure of failures) {
        const { only2, unify } = failure;
        switch (unify.kind) {
          case 'single-parameter-difference': {
            const { p0, p1 } = unify;
            const lineOfOtherOverload = only2 ? undefined : p0.loc.start.line;

            const typeAnnotation0 = getParameterTypeAnnotation(p0);
            const typeAnnotation1 = getParameterTypeAnnotation(p1);

            context.report({
              loc: p1.loc,
              node: p1,
              messageId: 'singleParameterDifference',
              data: {
                failureStringStart: failureStringStart(lineOfOtherOverload),
                types: getUnifiedTypeText(typeAnnotation0, typeAnnotation1),
              },
            });
            break;
          }
          case 'extra-parameter': {
            const { extraParameter, otherSignature } = unify;
            const lineOfOtherOverload = only2
              ? undefined
              : otherSignature.loc.start.line;

            context.report({
              loc: extraParameter.loc,
              node: extraParameter,
              messageId:
                extraParameter.type === AST_NODE_TYPES.RestElement
                  ? 'omittingRestParameter'
                  : 'omittingSingleParameter',
              data: {
                failureStringStart: failureStringStart(lineOfOtherOverload),
              },
            });
            break;
          }
          case 'all-parameters-are-same': {
            const { signature0, signature1 } = unify;
            const lineOfOtherOverload = only2
              ? undefined
              : signature0.loc.start.line;

            context.report({
              node: signature1,
              messageId: 'allParametersAreSame',
              data: {
                failureStringStart: failureStringStart(lineOfOtherOverload),
              },
            });
            break;
          }
          default:
            unify satisfies never;
        }
      }
    }

    function checkOverloads(
      signatures: readonly OverloadNode[][],
      typeParameters?: TSESTree.TSTypeParameterDeclaration,
    ): Failure[] {
      const result: Failure[] = [];
      const isTypeParameter = getIsTypeParameter(typeParameters);
      for (const overloads of signatures) {
        forEachPair(overloads, (a, b) => {
          const signature0 = (a as Partial<MethodDefinition>).value ?? a;
          const signature1 = (b as Partial<MethodDefinition>).value ?? b;

          const unify = compareSignatures(
            signature0 as SignatureDefinition,
            signature1 as SignatureDefinition,
            isTypeParameter,
          );
          if (unify != null) {
            result.push({ only2: overloads.length === 2, unify });
          }
        });
      }
      return result;
    }

    function compareSignatures(
      a: SignatureDefinition,
      b: SignatureDefinition,
      isTypeParameter: IsTypeParameter,
    ): Unify | undefined {
      if (!signaturesCanBeUnified(a, b, isTypeParameter)) {
        return undefined;
      }

      return a.params.length === b.params.length
        ? signaturesHaveSameAmountOfParameters(a, b)
        : signaturesDifferByOptionalOrRestParameter(a, b);
    }

    function signaturesCanBeUnified(
      a: SignatureDefinition,
      b: SignatureDefinition,
      isTypeParameter: IsTypeParameter,
    ): boolean {
      // Must return the same type.

      const aTypeParams =
        a.typeParameters != null ? a.typeParameters.params : undefined;
      const bTypeParams =
        b.typeParameters != null ? b.typeParameters.params : undefined;

      if (ignoreDifferentlyNamedParameters) {
        const commonParamsLength = Math.min(a.params.length, b.params.length);
        for (let i = 0; i < commonParamsLength; i += 1) {
          if (
            a.params[i].type === b.params[i].type &&
            getStaticParameterName(a.params[i]) !==
              getStaticParameterName(b.params[i])
          ) {
            return false;
          }
        }
      }

      if (ignoreOverloadsWithDifferentJSDoc) {
        const aComment = getBlockCommentForNode(getCommentTargetNode(a));
        const bComment = getBlockCommentForNode(getCommentTargetNode(b));
        if (aComment?.value !== bComment?.value) {
          return false;
        }
      }

      return (
        typesAreEqual(
          a.returnType?.typeAnnotation,
          b.returnType?.typeAnnotation,
        ) &&
        // Must take the same type parameters.
        // If one uses a type parameter (from outside) and the other doesn't, they shouldn't be joined.
        arraysAreEqual(aTypeParams, bTypeParams, typeParametersAreEqual) &&
        signatureUsesTypeParameter(a, isTypeParameter) ===
          signatureUsesTypeParameter(b, isTypeParameter)
      );
    }

    /**
     * Detect no difference, i.e. `a(x: number, y: string)` and `a(x: number, y: string)`,
     * or one param difference, i.e. `a(x: number, y: number, z: number)` and `a(x: number, y: string, z: number)`.
     */
    function signaturesHaveSameAmountOfParameters(
      signature0: SignatureDefinition,
      signature1: SignatureDefinition,
    ): Unify | undefined {
      const types1 = signature0.params;
      const types2 = signature1.params;
      const firstParam1 = types1[0];
      const firstParam2 = types2[0];

      // exempt signatures with `this: void` from the rule
      if (isThisVoidParam(firstParam1) || isThisVoidParam(firstParam2)) {
        return undefined;
      }

      const index = getIndexOfFirstDifference(
        types1,
        types2,
        parametersAreEqual,
      );
      if (index == null) {
        return {
          kind: 'all-parameters-are-same',
          signature0,
          signature1,
        };
      }

      // If remaining arrays are equal, the signatures differ by just one parameter type
      if (
        !arraysAreEqual(
          types1.slice(index + 1),
          types2.slice(index + 1),
          parametersAreEqual,
        )
      ) {
        return undefined;
      }

      const a = types1[index];
      const b = types2[index];
      // Can unify `a?: string` and `b?: number`. Can't unify `...args: string[]` and `...args: number[]`.
      // See https://github.com/Microsoft/TypeScript/issues/5077
      return parametersHaveEqualSigils(a, b) &&
        a.type !== AST_NODE_TYPES.RestElement
        ? { kind: 'single-parameter-difference', p0: a, p1: b }
        : undefined;
    }

    function getParameterTypeAnnotation(
      parameter: TSESTree.Parameter,
    ): TSESTree.TypeNode | undefined {
      return isTSParameterProperty(parameter)
        ? parameter.parameter.typeAnnotation?.typeAnnotation
        : parameter.typeAnnotation?.typeAnnotation;
    }

    function getUnifiedTypeText(
      type0: TSESTree.TypeNode | undefined,
      type1: TSESTree.TypeNode | undefined,
    ): string {
      // When a signature's parameter has no type annotation
      if (type0 == null || type1 == null) {
        return getUnionMemberText(
          nullThrows(
            type0 ?? type1,
            'Expected a type annotation for one of the parameters, but both were undefined',
          ),
        );
      }

      const members = [...getUnionMembers(type0), ...getUnionMembers(type1)];
      const uniqueMembers: TSESTree.TypeNode[] = [];

      for (const member of members) {
        if (!uniqueMembers.some(other => typesAreEqual(other, member))) {
          uniqueMembers.push(member);
        }
      }

      return uniqueMembers
        .map(member => getUnionMemberText(member))
        .join(' | ');
    }

    function getUnionMembers(type: TSESTree.TypeNode): TSESTree.TypeNode[] {
      return type.type === AST_NODE_TYPES.TSUnionType
        ? type.types.flatMap(getUnionMembers)
        : [type];
    }

    function getUnionMemberText(type: TSESTree.TypeNode): string {
      const text = context.sourceCode.getText(type);
      const needsParentheses =
        type.type === AST_NODE_TYPES.TSConditionalType ||
        type.type === AST_NODE_TYPES.TSConstructorType ||
        type.type === AST_NODE_TYPES.TSFunctionType;

      return needsParentheses ? `(${text})` : text;
    }

    function isThisParam(param: TSESTree.Parameter | undefined): boolean {
      return param?.type === AST_NODE_TYPES.Identifier && param.name === 'this';
    }

    function isThisVoidParam(param: TSESTree.Parameter | undefined) {
      return (
        isThisParam(param) &&
        (param as TSESTree.Identifier).typeAnnotation?.typeAnnotation.type ===
          AST_NODE_TYPES.TSVoidKeyword
      );
    }

    /**
     * Detect `a(): void` and `a(x: number): void`.
     * Returns the parameter declaration (`x: number` in this example) that should be optional/rest, and overload it's a part of.
     */
    function signaturesDifferByOptionalOrRestParameter(
      a: SignatureDefinition,
      b: SignatureDefinition,
    ): Unify | undefined {
      const sig1 = a.params;
      const sig2 = b.params;

      const minLength = Math.min(sig1.length, sig2.length);
      const longer = sig1.length < sig2.length ? sig2 : sig1;
      const shorter = sig1.length < sig2.length ? sig1 : sig2;
      const shorterSig = sig1.length < sig2.length ? a : b;

      const firstParam1 = sig1.at(0);
      const firstParam2 = sig2.at(0);
      // If one signature has explicit this type and another doesn't, they can't
      // be unified.
      if (isThisParam(firstParam1) !== isThisParam(firstParam2)) {
        return undefined;
      }

      // exempt signatures with `this: void` from the rule
      if (isThisVoidParam(firstParam1) || isThisVoidParam(firstParam2)) {
        return undefined;
      }

      // If one is has 2+ parameters more than the other, they must all be optional/rest.
      // Differ by optional parameters: f() and f(x), f() and f(x, ?y, ...z)
      // Not allowed: f() and f(x, y)
      for (let i = minLength + 1; i < longer.length; i++) {
        if (!parameterMayBeMissing(longer[i])) {
          return undefined;
        }
      }

      for (let i = 0; i < minLength; i++) {
        if (
          !typesAreEqual(
            getParameterTypeAnnotation(sig1[i]),
            getParameterTypeAnnotation(sig2[i]),
          )
        ) {
          return undefined;
        }
      }

      if (
        minLength > 0 &&
        shorter[minLength - 1].type === AST_NODE_TYPES.RestElement
      ) {
        return undefined;
      }

      return {
        extraParameter: longer[longer.length - 1],
        kind: 'extra-parameter',
        otherSignature: shorterSig,
      };
    }

    /** Given type parameters, returns a function to test whether a type is one of those parameters. */
    function getIsTypeParameter(
      typeParameters?: TSESTree.TSTypeParameterDeclaration,
    ): IsTypeParameter {
      if (typeParameters == null) {
        return () => false;
      }

      const set = new Set<string>();
      for (const t of typeParameters.params) {
        set.add(t.name.name);
      }
      return typeName => set.has(typeName);
    }

    /** True if any of the outer type parameters are used in a signature. */
    function signatureUsesTypeParameter(
      sig: SignatureDefinition,
      isTypeParameter: IsTypeParameter,
    ): boolean {
      return sig.params.some((p: TSESTree.Parameter) =>
        typeContainsTypeParameter(
          isTSParameterProperty(p)
            ? p.parameter.typeAnnotation
            : p.typeAnnotation,
        ),
      );

      function typeContainsTypeParameter(
        type?: TSESTree.TSTypeAnnotation | TSESTree.TypeNode,
      ): boolean {
        if (!type) {
          return false;
        }

        if (type.type === AST_NODE_TYPES.TSTypeReference) {
          const typeName = type.typeName;
          if (isIdentifier(typeName) && isTypeParameter(typeName.name)) {
            return true;
          }
        }

        return typeContainsTypeParameter(
          (type as Partial<TSESTree.TSTypeAnnotation>).typeAnnotation ??
            (type as TSESTree.TSArrayType).elementType,
        );
      }
    }

    function isTSParameterProperty(
      node: TSESTree.Node,
    ): node is TSESTree.TSParameterProperty {
      return node.type === AST_NODE_TYPES.TSParameterProperty;
    }

    function parametersAreEqual(
      a: TSESTree.Parameter,
      b: TSESTree.Parameter,
    ): boolean {
      return (
        parametersHaveEqualSigils(a, b) &&
        typesAreEqual(
          getParameterTypeAnnotation(a),
          getParameterTypeAnnotation(b),
        )
      );
    }

    /** True for optional/rest parameters. */
    function parameterMayBeMissing(p: TSESTree.Parameter): boolean | undefined {
      const optional = isTSParameterProperty(p)
        ? p.parameter.optional
        : p.optional;

      return p.type === AST_NODE_TYPES.RestElement || optional;
    }

    /** False if one is optional and the other isn't, or one is a rest parameter and the other isn't. */
    function parametersHaveEqualSigils(
      a: TSESTree.Parameter,
      b: TSESTree.Parameter,
    ): boolean {
      const optionalA = isTSParameterProperty(a)
        ? a.parameter.optional
        : a.optional;
      const optionalB = isTSParameterProperty(b)
        ? b.parameter.optional
        : b.optional;

      return (
        (a.type === AST_NODE_TYPES.RestElement) ===
          (b.type === AST_NODE_TYPES.RestElement) && optionalA === optionalB
      );
    }

    function typeParametersAreEqual(
      a: TSESTree.TSTypeParameter,
      b: TSESTree.TSTypeParameter,
    ): boolean {
      return (
        a.name.name === b.name.name && typesAreEqual(a.constraint, b.constraint)
      );
    }

    function typesAreEqual(
      a: TSESTree.TypeNode | undefined,
      b: TSESTree.TypeNode | undefined,
    ): boolean {
      return (
        a === b ||
        (a != null &&
          b != null &&
          context.sourceCode.getText(a) === context.sourceCode.getText(b))
      );
    }

    /* Returns the first index where `a` and `b` differ. */
    function getIndexOfFirstDifference<T>(
      a: readonly T[],
      b: readonly T[],
      equal: Equal<T>,
    ): number | undefined {
      for (let i = 0; i < a.length && i < b.length; i++) {
        if (!equal(a[i], b[i])) {
          return i;
        }
      }
      return undefined;
    }

    /** Calls `action` for every pair of values in `values`. */
    function forEachPair<T>(
      values: readonly T[],
      action: (a: T, b: T) => void,
    ): void {
      for (let i = 0; i < values.length; i++) {
        for (let j = i + 1; j < values.length; j++) {
          action(values[i], values[j]);
        }
      }
    }

    interface Scope {
      overloads: Map<string, OverloadNode[]>;
      parent?: ScopeNode;
      typeParameters?: TSESTree.TSTypeParameterDeclaration;
    }

    const scopes: Scope[] = [];
    let currentScope: Scope | undefined = {
      overloads: new Map<string, OverloadNode[]>(),
    };

    function createScope(
      parent: ScopeNode,
      typeParameters?: TSESTree.TSTypeParameterDeclaration,
    ): void {
      if (currentScope) {
        scopes.push(currentScope);
      }
      currentScope = {
        overloads: new Map<string, OverloadNode[]>(),
        parent,
        typeParameters,
      };
    }

    function checkScope(): void {
      const scope = nullThrows(
        currentScope,
        'checkScope() called without a current scope',
      );
      const failures = checkOverloads(
        [...scope.overloads.values()],
        scope.typeParameters,
      );
      addFailures(failures);
      currentScope = scopes.pop();
    }

    /**
     * @returns the first valid JSDoc comment annotating `node`
     */
    function getBlockCommentForNode(
      node: TSESTree.Node,
    ): TSESTree.Comment | undefined {
      return context.sourceCode
        .getCommentsBefore(node)
        .reverse()
        .find(comment => comment.type === AST_TOKEN_TYPES.Block);
    }

    function addOverload(
      signature: OverloadNode,
      key?: string,
      containingNode?: ContainingNode,
    ): void {
      key ??= getOverloadKey(signature);
      if ((containingNode ?? signature).parent === currentScope?.parent) {
        const overloads = currentScope.overloads.get(key);
        if (overloads != null) {
          overloads.push(signature);
        } else {
          currentScope.overloads.set(key, [signature]);
        }
      }
    }

    //----------------------------------------------------------------------
    // Public
    //----------------------------------------------------------------------

    return {
      ClassDeclaration(node): void {
        createScope(node.body, node.typeParameters);
      },
      Program: createScope,
      TSInterfaceDeclaration(node): void {
        createScope(node.body, node.typeParameters);
      },
      TSModuleBlock: createScope,
      TSTypeLiteral: createScope,

      // collect overloads
      MethodDefinition(node): void {
        if (!node.value.body && !isGetterOrSetter(node)) {
          addOverload(node);
        }
      },
      TSAbstractMethodDefinition(node): void {
        if (!node.value.body && !isGetterOrSetter(node)) {
          addOverload(node);
        }
      },
      TSCallSignatureDeclaration: addOverload,
      TSConstructSignatureDeclaration: addOverload,
      TSDeclareFunction(node): void {
        const exportingNode = getExportingNode(node);
        addOverload(node, node.id?.name ?? exportingNode?.type, exportingNode);
      },
      TSMethodSignature(node): void {
        if (!isGetterOrSetter(node)) {
          addOverload(node);
        }
      },

      // validate scopes
      'ClassDeclaration:exit': checkScope,
      'Program:exit': checkScope,
      'TSInterfaceDeclaration:exit': checkScope,
      'TSModuleBlock:exit': checkScope,
      'TSTypeLiteral:exit': checkScope,
    };
  }

getCommentTargetNode(node: SignatureDefinition): any

Parameters:

  • node SignatureDefinition

Returns: any

Calls:

  • getExportingNode
Code
function getCommentTargetNode(node: SignatureDefinition) {
  if (node.type === AST_NODE_TYPES.TSEmptyBodyFunctionExpression) {
    return node.parent;
  }

  return getExportingNode(node) ?? node;
}

getExportingNode(node: SignatureDefinition): | TSESTree.ExportDefaultDeclaration | TSESTree.ExportNamedD…

Parameters:

  • node SignatureDefinition

Returns: | TSESTree.ExportDefaultDeclaration | TSESTree.ExportNamedDeclaration | undefined

Code
function getExportingNode(
  node: SignatureDefinition,
):
  | TSESTree.ExportDefaultDeclaration
  | TSESTree.ExportNamedDeclaration
  | undefined {
  return node.parent.type === AST_NODE_TYPES.ExportNamedDeclaration ||
    node.parent.type === AST_NODE_TYPES.ExportDefaultDeclaration
    ? node.parent
    : undefined;
}

getOverloadKey(node: OverloadNode): string

Parameters:

  • node OverloadNode

Returns: string

Calls:

  • getOverloadInfo
Code
function getOverloadKey(node: OverloadNode): string {
  const info = getOverloadInfo(node);

  return (
    ((node as MethodDefinition).computed ? '0' : '1') +
    ((node as MethodDefinition).static ? '0' : '1') +
    info
  );
}

getOverloadInfo(node: OverloadNode): string

Parameters:

  • node OverloadNode

Returns: string

Calls:

  • isPrivateIdentifier
  • isIdentifier
Code
function getOverloadInfo(node: OverloadNode): string {
  switch (node.type) {
    case AST_NODE_TYPES.TSConstructSignatureDeclaration:
      return 'constructor';
    case AST_NODE_TYPES.TSCallSignatureDeclaration:
      return '()';
    default: {
      const { key } = node as MethodDefinition;

      if (isPrivateIdentifier(key)) {
        return `private_identifier_${key.name}`;
      }

      if (isIdentifier(key)) {
        return `identifier_${key.name}`;
      }

      return (key as TSESTree.Literal).raw;
    }
  }
}

getStaticParameterName(param: TSESTree.Node): string | undefined

Parameters:

  • param TSESTree.Node

Returns: string | undefined

Calls:

  • getStaticParameterName
Code
function getStaticParameterName(param: TSESTree.Node): string | undefined {
  switch (param.type) {
    case AST_NODE_TYPES.Identifier:
      return param.name;
    case AST_NODE_TYPES.RestElement:
      return getStaticParameterName(param.argument);
    default:
      return undefined;
  }
}

isIdentifier(node: TSESTree.Node): node is TSESTree.Identifier

Parameters:

  • node TSESTree.Node

Returns: node is TSESTree.Identifier

Code
function isIdentifier(node: TSESTree.Node): node is TSESTree.Identifier {
  return node.type === AST_NODE_TYPES.Identifier;
}

isPrivateIdentifier(node: TSESTree.Node): node is TSESTree.PrivateIdentifier

Parameters:

  • node TSESTree.Node

Returns: node is TSESTree.PrivateIdentifier

Code
function isPrivateIdentifier(
  node: TSESTree.Node,
): node is TSESTree.PrivateIdentifier {
  return node.type === AST_NODE_TYPES.PrivateIdentifier;
}

isGetterOrSetter(node: | TSESTree.MethodDefinition | TSESTree.…): boolean

Parameters:

  • node | TSESTree.MethodDefinition | TSESTree.TSAbstractMethodDefinition | TSESTree.TSMethodSignature

Returns: boolean

Code
function isGetterOrSetter(
  node:
    | TSESTree.MethodDefinition
    | TSESTree.TSAbstractMethodDefinition
    | TSESTree.TSMethodSignature,
): boolean {
  return node.kind === 'get' || node.kind === 'set';
}

Internal helpers

Declared inside another function in this file.

failureStringStart(otherLine: number): string

Parameters:

  • otherLine number

Returns: string

Internal Comments:

// For only 2 overloads we don't need to specify which is the other one. (x2)

Code
function failureStringStart(otherLine?: number): string {
      // For only 2 overloads we don't need to specify which is the other one.
      const overloads =
        otherLine == null
          ? 'These overloads'
          : `This overload and the one on line ${otherLine}`;
      return `${overloads} can be combined into one signature`;
    }

addFailures(failures: Failure[]): void

Parameters:

  • failures Failure[]

Returns: void

Calls:

  • getParameterTypeAnnotation
  • context.report
  • failureStringStart
  • getUnifiedTypeText
Code
function addFailures(failures: Failure[]): void {
      for (const failure of failures) {
        const { only2, unify } = failure;
        switch (unify.kind) {
          case 'single-parameter-difference': {
            const { p0, p1 } = unify;
            const lineOfOtherOverload = only2 ? undefined : p0.loc.start.line;

            const typeAnnotation0 = getParameterTypeAnnotation(p0);
            const typeAnnotation1 = getParameterTypeAnnotation(p1);

            context.report({
              loc: p1.loc,
              node: p1,
              messageId: 'singleParameterDifference',
              data: {
                failureStringStart: failureStringStart(lineOfOtherOverload),
                types: getUnifiedTypeText(typeAnnotation0, typeAnnotation1),
              },
            });
            break;
          }
          case 'extra-parameter': {
            const { extraParameter, otherSignature } = unify;
            const lineOfOtherOverload = only2
              ? undefined
              : otherSignature.loc.start.line;

            context.report({
              loc: extraParameter.loc,
              node: extraParameter,
              messageId:
                extraParameter.type === AST_NODE_TYPES.RestElement
                  ? 'omittingRestParameter'
                  : 'omittingSingleParameter',
              data: {
                failureStringStart: failureStringStart(lineOfOtherOverload),
              },
            });
            break;
          }
          case 'all-parameters-are-same': {
            const { signature0, signature1 } = unify;
            const lineOfOtherOverload = only2
              ? undefined
              : signature0.loc.start.line;

            context.report({
              node: signature1,
              messageId: 'allParametersAreSame',
              data: {
                failureStringStart: failureStringStart(lineOfOtherOverload),
              },
            });
            break;
          }
          default:
            unify satisfies never;
        }
      }
    }

checkOverloads(signatures: readonly OverloadNode[][], typeParameters: TSESTree.TSTypeParameterDeclaration): Failure[]

Parameters:

  • signatures readonly OverloadNode[][]
  • typeParameters TSESTree.TSTypeParameterDeclaration

Returns: Failure[]

Calls:

  • getIsTypeParameter
  • forEachPair
  • compareSignatures
  • result.push
Code
function checkOverloads(
      signatures: readonly OverloadNode[][],
      typeParameters?: TSESTree.TSTypeParameterDeclaration,
    ): Failure[] {
      const result: Failure[] = [];
      const isTypeParameter = getIsTypeParameter(typeParameters);
      for (const overloads of signatures) {
        forEachPair(overloads, (a, b) => {
          const signature0 = (a as Partial<MethodDefinition>).value ?? a;
          const signature1 = (b as Partial<MethodDefinition>).value ?? b;

          const unify = compareSignatures(
            signature0 as SignatureDefinition,
            signature1 as SignatureDefinition,
            isTypeParameter,
          );
          if (unify != null) {
            result.push({ only2: overloads.length === 2, unify });
          }
        });
      }
      return result;
    }

compareSignatures(a: SignatureDefinition, b: SignatureDefinition, isTypeParameter: IsTypeParameter): Unify | undefined

Parameters:

  • a SignatureDefinition
  • b SignatureDefinition
  • isTypeParameter IsTypeParameter

Returns: Unify | undefined

Calls:

  • signaturesCanBeUnified
  • signaturesHaveSameAmountOfParameters
  • signaturesDifferByOptionalOrRestParameter
Code
function compareSignatures(
      a: SignatureDefinition,
      b: SignatureDefinition,
      isTypeParameter: IsTypeParameter,
    ): Unify | undefined {
      if (!signaturesCanBeUnified(a, b, isTypeParameter)) {
        return undefined;
      }

      return a.params.length === b.params.length
        ? signaturesHaveSameAmountOfParameters(a, b)
        : signaturesDifferByOptionalOrRestParameter(a, b);
    }

signaturesCanBeUnified(a: SignatureDefinition, b: SignatureDefinition, isTypeParameter: IsTypeParameter): boolean

Parameters:

  • a SignatureDefinition
  • b SignatureDefinition
  • isTypeParameter IsTypeParameter

Returns: boolean

Calls:

  • Math.min
  • getStaticParameterName
  • getBlockCommentForNode
  • getCommentTargetNode
  • typesAreEqual
  • arraysAreEqual (from ../util)
  • signatureUsesTypeParameter

Internal Comments:

// Must return the same type. (x2)
// Must take the same type parameters. (x2)
// If one uses a type parameter (from outside) and the other doesn't, they shouldn't be joined. (x2)

Code
function signaturesCanBeUnified(
      a: SignatureDefinition,
      b: SignatureDefinition,
      isTypeParameter: IsTypeParameter,
    ): boolean {
      // Must return the same type.

      const aTypeParams =
        a.typeParameters != null ? a.typeParameters.params : undefined;
      const bTypeParams =
        b.typeParameters != null ? b.typeParameters.params : undefined;

      if (ignoreDifferentlyNamedParameters) {
        const commonParamsLength = Math.min(a.params.length, b.params.length);
        for (let i = 0; i < commonParamsLength; i += 1) {
          if (
            a.params[i].type === b.params[i].type &&
            getStaticParameterName(a.params[i]) !==
              getStaticParameterName(b.params[i])
          ) {
            return false;
          }
        }
      }

      if (ignoreOverloadsWithDifferentJSDoc) {
        const aComment = getBlockCommentForNode(getCommentTargetNode(a));
        const bComment = getBlockCommentForNode(getCommentTargetNode(b));
        if (aComment?.value !== bComment?.value) {
          return false;
        }
      }

      return (
        typesAreEqual(
          a.returnType?.typeAnnotation,
          b.returnType?.typeAnnotation,
        ) &&
        // Must take the same type parameters.
        // If one uses a type parameter (from outside) and the other doesn't, they shouldn't be joined.
        arraysAreEqual(aTypeParams, bTypeParams, typeParametersAreEqual) &&
        signatureUsesTypeParameter(a, isTypeParameter) ===
          signatureUsesTypeParameter(b, isTypeParameter)
      );
    }

signaturesHaveSameAmountOfParameters(signature0: SignatureDefinition, signature1: SignatureDefinition): Unify | undefined

Detect no difference, i.e. a(x: number, y: string) and a(x: number, y: string), or one param difference, i.e. a(x: number, y: number, z: number) and a(x: number, y: string, z: number).

Raw JSDoc
/**
     * Detect no difference, i.e. `a(x: number, y: string)` and `a(x: number, y: string)`,
     * or one param difference, i.e. `a(x: number, y: number, z: number)` and `a(x: number, y: string, z: number)`.
     */

Calls:

  • isThisVoidParam
  • getIndexOfFirstDifference
  • arraysAreEqual (from ../util)
  • types1.slice
  • types2.slice
  • parametersHaveEqualSigils

Internal Comments:

// exempt signatures with `this: void` from the rule
// If remaining arrays are equal, the signatures differ by just one parameter type
// Can unify `a?: string` and `b?: number`. Can't unify `...args: string[]` and `...args: number[]`.
// See https://github.com/Microsoft/TypeScript/issues/5077

Code
function signaturesHaveSameAmountOfParameters(
      signature0: SignatureDefinition,
      signature1: SignatureDefinition,
    ): Unify | undefined {
      const types1 = signature0.params;
      const types2 = signature1.params;
      const firstParam1 = types1[0];
      const firstParam2 = types2[0];

      // exempt signatures with `this: void` from the rule
      if (isThisVoidParam(firstParam1) || isThisVoidParam(firstParam2)) {
        return undefined;
      }

      const index = getIndexOfFirstDifference(
        types1,
        types2,
        parametersAreEqual,
      );
      if (index == null) {
        return {
          kind: 'all-parameters-are-same',
          signature0,
          signature1,
        };
      }

      // If remaining arrays are equal, the signatures differ by just one parameter type
      if (
        !arraysAreEqual(
          types1.slice(index + 1),
          types2.slice(index + 1),
          parametersAreEqual,
        )
      ) {
        return undefined;
      }

      const a = types1[index];
      const b = types2[index];
      // Can unify `a?: string` and `b?: number`. Can't unify `...args: string[]` and `...args: number[]`.
      // See https://github.com/Microsoft/TypeScript/issues/5077
      return parametersHaveEqualSigils(a, b) &&
        a.type !== AST_NODE_TYPES.RestElement
        ? { kind: 'single-parameter-difference', p0: a, p1: b }
        : undefined;
    }

getParameterTypeAnnotation(parameter: TSESTree.Parameter): TSESTree.TypeNode | undefined

Parameters:

  • parameter TSESTree.Parameter

Returns: TSESTree.TypeNode | undefined

Calls:

  • isTSParameterProperty
Code
function getParameterTypeAnnotation(
      parameter: TSESTree.Parameter,
    ): TSESTree.TypeNode | undefined {
      return isTSParameterProperty(parameter)
        ? parameter.parameter.typeAnnotation?.typeAnnotation
        : parameter.typeAnnotation?.typeAnnotation;
    }

getUnifiedTypeText(type0: TSESTree.TypeNode | undefined, type1: TSESTree.TypeNode | undefined): string

Parameters:

  • type0 TSESTree.TypeNode | undefined
  • type1 TSESTree.TypeNode | undefined

Returns: string

Calls:

  • getUnionMemberText
  • nullThrows (from ../util)
  • getUnionMembers
  • uniqueMembers.some
  • typesAreEqual
  • uniqueMembers.push
  • uniqueMembers .map(member => getUnionMemberText(member)) .join

Internal Comments:

// When a signature's parameter has no type annotation

Code
function getUnifiedTypeText(
      type0: TSESTree.TypeNode | undefined,
      type1: TSESTree.TypeNode | undefined,
    ): string {
      // When a signature's parameter has no type annotation
      if (type0 == null || type1 == null) {
        return getUnionMemberText(
          nullThrows(
            type0 ?? type1,
            'Expected a type annotation for one of the parameters, but both were undefined',
          ),
        );
      }

      const members = [...getUnionMembers(type0), ...getUnionMembers(type1)];
      const uniqueMembers: TSESTree.TypeNode[] = [];

      for (const member of members) {
        if (!uniqueMembers.some(other => typesAreEqual(other, member))) {
          uniqueMembers.push(member);
        }
      }

      return uniqueMembers
        .map(member => getUnionMemberText(member))
        .join(' | ');
    }

getUnionMembers(type: TSESTree.TypeNode): TSESTree.TypeNode[]

Parameters:

  • type TSESTree.TypeNode

Returns: TSESTree.TypeNode[]

Calls:

  • type.types.flatMap
Code
function getUnionMembers(type: TSESTree.TypeNode): TSESTree.TypeNode[] {
      return type.type === AST_NODE_TYPES.TSUnionType
        ? type.types.flatMap(getUnionMembers)
        : [type];
    }

getUnionMemberText(type: TSESTree.TypeNode): string

Parameters:

  • type TSESTree.TypeNode

Returns: string

Calls:

  • context.sourceCode.getText
Code
function getUnionMemberText(type: TSESTree.TypeNode): string {
      const text = context.sourceCode.getText(type);
      const needsParentheses =
        type.type === AST_NODE_TYPES.TSConditionalType ||
        type.type === AST_NODE_TYPES.TSConstructorType ||
        type.type === AST_NODE_TYPES.TSFunctionType;

      return needsParentheses ? `(${text})` : text;
    }

isThisParam(param: TSESTree.Parameter | undefined): boolean

Parameters:

  • param TSESTree.Parameter | undefined

Returns: boolean

Code
function isThisParam(param: TSESTree.Parameter | undefined): boolean {
      return param?.type === AST_NODE_TYPES.Identifier && param.name === 'this';
    }

isThisVoidParam(param: TSESTree.Parameter | undefined): boolean

Parameters:

  • param TSESTree.Parameter | undefined

Returns: boolean

Calls:

  • isThisParam
Code
function isThisVoidParam(param: TSESTree.Parameter | undefined) {
      return (
        isThisParam(param) &&
        (param as TSESTree.Identifier).typeAnnotation?.typeAnnotation.type ===
          AST_NODE_TYPES.TSVoidKeyword
      );
    }

signaturesDifferByOptionalOrRestParameter(a: SignatureDefinition, b: SignatureDefinition): Unify | undefined

Detect a(): void and a(x: number): void. Returns the parameter declaration (x: number in this example) that should be optional/rest, and overload it's a part of.

Raw JSDoc
/**
     * Detect `a(): void` and `a(x: number): void`.
     * Returns the parameter declaration (`x: number` in this example) that should be optional/rest, and overload it's a part of.
     */

Calls:

  • Math.min
  • sig1.at
  • sig2.at
  • isThisParam
  • isThisVoidParam
  • parameterMayBeMissing
  • typesAreEqual
  • getParameterTypeAnnotation

Internal Comments:

// If one signature has explicit this type and another doesn't, they can't
// be unified.
// exempt signatures with `this: void` from the rule
// If one is has 2+ parameters more than the other, they must all be optional/rest.
// Differ by optional parameters: f() and f(x), f() and f(x, ?y, ...z)
// Not allowed: f() and f(x, y)

Code
function signaturesDifferByOptionalOrRestParameter(
      a: SignatureDefinition,
      b: SignatureDefinition,
    ): Unify | undefined {
      const sig1 = a.params;
      const sig2 = b.params;

      const minLength = Math.min(sig1.length, sig2.length);
      const longer = sig1.length < sig2.length ? sig2 : sig1;
      const shorter = sig1.length < sig2.length ? sig1 : sig2;
      const shorterSig = sig1.length < sig2.length ? a : b;

      const firstParam1 = sig1.at(0);
      const firstParam2 = sig2.at(0);
      // If one signature has explicit this type and another doesn't, they can't
      // be unified.
      if (isThisParam(firstParam1) !== isThisParam(firstParam2)) {
        return undefined;
      }

      // exempt signatures with `this: void` from the rule
      if (isThisVoidParam(firstParam1) || isThisVoidParam(firstParam2)) {
        return undefined;
      }

      // If one is has 2+ parameters more than the other, they must all be optional/rest.
      // Differ by optional parameters: f() and f(x), f() and f(x, ?y, ...z)
      // Not allowed: f() and f(x, y)
      for (let i = minLength + 1; i < longer.length; i++) {
        if (!parameterMayBeMissing(longer[i])) {
          return undefined;
        }
      }

      for (let i = 0; i < minLength; i++) {
        if (
          !typesAreEqual(
            getParameterTypeAnnotation(sig1[i]),
            getParameterTypeAnnotation(sig2[i]),
          )
        ) {
          return undefined;
        }
      }

      if (
        minLength > 0 &&
        shorter[minLength - 1].type === AST_NODE_TYPES.RestElement
      ) {
        return undefined;
      }

      return {
        extraParameter: longer[longer.length - 1],
        kind: 'extra-parameter',
        otherSignature: shorterSig,
      };
    }

getIsTypeParameter(typeParameters: TSESTree.TSTypeParameterDeclaration): IsTypeParameter

Given type parameters, returns a function to test whether a type is one of those parameters.

Raw JSDoc
/** Given type parameters, returns a function to test whether a type is one of those parameters. */

Calls:

  • set.add
  • set.has
Code
function getIsTypeParameter(
      typeParameters?: TSESTree.TSTypeParameterDeclaration,
    ): IsTypeParameter {
      if (typeParameters == null) {
        return () => false;
      }

      const set = new Set<string>();
      for (const t of typeParameters.params) {
        set.add(t.name.name);
      }
      return typeName => set.has(typeName);
    }

signatureUsesTypeParameter(sig: SignatureDefinition, isTypeParameter: IsTypeParameter): boolean

True if any of the outer type parameters are used in a signature.

Raw JSDoc
/** True if any of the outer type parameters are used in a signature. */

Calls:

  • sig.params.some
  • typeContainsTypeParameter
  • isTSParameterProperty
  • isIdentifier
  • isTypeParameter
Code
function signatureUsesTypeParameter(
      sig: SignatureDefinition,
      isTypeParameter: IsTypeParameter,
    ): boolean {
      return sig.params.some((p: TSESTree.Parameter) =>
        typeContainsTypeParameter(
          isTSParameterProperty(p)
            ? p.parameter.typeAnnotation
            : p.typeAnnotation,
        ),
      );

      function typeContainsTypeParameter(
        type?: TSESTree.TSTypeAnnotation | TSESTree.TypeNode,
      ): boolean {
        if (!type) {
          return false;
        }

        if (type.type === AST_NODE_TYPES.TSTypeReference) {
          const typeName = type.typeName;
          if (isIdentifier(typeName) && isTypeParameter(typeName.name)) {
            return true;
          }
        }

        return typeContainsTypeParameter(
          (type as Partial<TSESTree.TSTypeAnnotation>).typeAnnotation ??
            (type as TSESTree.TSArrayType).elementType,
        );
      }
    }

typeContainsTypeParameter(type: TSESTree.TSTypeAnnotation | TSESTree.Ty…): boolean

Parameters:

  • type TSESTree.TSTypeAnnotation | TSESTree.TypeNode

Returns: boolean

Calls:

  • isIdentifier
  • isTypeParameter
  • typeContainsTypeParameter
Code
function typeContainsTypeParameter(
        type?: TSESTree.TSTypeAnnotation | TSESTree.TypeNode,
      ): boolean {
        if (!type) {
          return false;
        }

        if (type.type === AST_NODE_TYPES.TSTypeReference) {
          const typeName = type.typeName;
          if (isIdentifier(typeName) && isTypeParameter(typeName.name)) {
            return true;
          }
        }

        return typeContainsTypeParameter(
          (type as Partial<TSESTree.TSTypeAnnotation>).typeAnnotation ??
            (type as TSESTree.TSArrayType).elementType,
        );
      }

isTSParameterProperty(node: TSESTree.Node): node is TSESTree.TSParameterProperty

Parameters:

  • node TSESTree.Node

Returns: node is TSESTree.TSParameterProperty

Code
function isTSParameterProperty(
      node: TSESTree.Node,
    ): node is TSESTree.TSParameterProperty {
      return node.type === AST_NODE_TYPES.TSParameterProperty;
    }

parametersAreEqual(a: TSESTree.Parameter, b: TSESTree.Parameter): boolean

Parameters:

  • a TSESTree.Parameter
  • b TSESTree.Parameter

Returns: boolean

Calls:

  • parametersHaveEqualSigils
  • typesAreEqual
  • getParameterTypeAnnotation
Code
function parametersAreEqual(
      a: TSESTree.Parameter,
      b: TSESTree.Parameter,
    ): boolean {
      return (
        parametersHaveEqualSigils(a, b) &&
        typesAreEqual(
          getParameterTypeAnnotation(a),
          getParameterTypeAnnotation(b),
        )
      );
    }

parameterMayBeMissing(p: TSESTree.Parameter): boolean | undefined

True for optional/rest parameters.

Raw JSDoc
/** True for optional/rest parameters. */

Calls:

  • isTSParameterProperty
Code
function parameterMayBeMissing(p: TSESTree.Parameter): boolean | undefined {
      const optional = isTSParameterProperty(p)
        ? p.parameter.optional
        : p.optional;

      return p.type === AST_NODE_TYPES.RestElement || optional;
    }

parametersHaveEqualSigils(a: TSESTree.Parameter, b: TSESTree.Parameter): boolean

False if one is optional and the other isn't, or one is a rest parameter and the other isn't.

Raw JSDoc
/** False if one is optional and the other isn't, or one is a rest parameter and the other isn't. */

Calls:

  • isTSParameterProperty
Code
function parametersHaveEqualSigils(
      a: TSESTree.Parameter,
      b: TSESTree.Parameter,
    ): boolean {
      const optionalA = isTSParameterProperty(a)
        ? a.parameter.optional
        : a.optional;
      const optionalB = isTSParameterProperty(b)
        ? b.parameter.optional
        : b.optional;

      return (
        (a.type === AST_NODE_TYPES.RestElement) ===
          (b.type === AST_NODE_TYPES.RestElement) && optionalA === optionalB
      );
    }

typeParametersAreEqual(a: TSESTree.TSTypeParameter, b: TSESTree.TSTypeParameter): boolean

Parameters:

  • a TSESTree.TSTypeParameter
  • b TSESTree.TSTypeParameter

Returns: boolean

Calls:

  • typesAreEqual
Code
function typeParametersAreEqual(
      a: TSESTree.TSTypeParameter,
      b: TSESTree.TSTypeParameter,
    ): boolean {
      return (
        a.name.name === b.name.name && typesAreEqual(a.constraint, b.constraint)
      );
    }

typesAreEqual(a: TSESTree.TypeNode | undefined, b: TSESTree.TypeNode | undefined): boolean

Parameters:

  • a TSESTree.TypeNode | undefined
  • b TSESTree.TypeNode | undefined

Returns: boolean

Calls:

  • context.sourceCode.getText
Code
function typesAreEqual(
      a: TSESTree.TypeNode | undefined,
      b: TSESTree.TypeNode | undefined,
    ): boolean {
      return (
        a === b ||
        (a != null &&
          b != null &&
          context.sourceCode.getText(a) === context.sourceCode.getText(b))
      );
    }

getIndexOfFirstDifference(a: readonly T[], b: readonly T[], equal: Equal<T>): number | undefined

Parameters:

  • a readonly T[]
  • b readonly T[]
  • equal Equal<T>

Returns: number | undefined

Calls:

  • equal
Code
function getIndexOfFirstDifference<T>(
      a: readonly T[],
      b: readonly T[],
      equal: Equal<T>,
    ): number | undefined {
      for (let i = 0; i < a.length && i < b.length; i++) {
        if (!equal(a[i], b[i])) {
          return i;
        }
      }
      return undefined;
    }

forEachPair(values: readonly T[], action: (a: T, b: T) => void): void

Calls action for every pair of values in values.

Raw JSDoc
/** Calls `action` for every pair of values in `values`. */

Calls:

  • action
Code
function forEachPair<T>(
      values: readonly T[],
      action: (a: T, b: T) => void,
    ): void {
      for (let i = 0; i < values.length; i++) {
        for (let j = i + 1; j < values.length; j++) {
          action(values[i], values[j]);
        }
      }
    }

createScope(parent: ScopeNode, typeParameters: TSESTree.TSTypeParameterDeclaration): void

Parameters:

  • parent ScopeNode
  • typeParameters TSESTree.TSTypeParameterDeclaration

Returns: void

Calls:

  • scopes.push
Code
function createScope(
      parent: ScopeNode,
      typeParameters?: TSESTree.TSTypeParameterDeclaration,
    ): void {
      if (currentScope) {
        scopes.push(currentScope);
      }
      currentScope = {
        overloads: new Map<string, OverloadNode[]>(),
        parent,
        typeParameters,
      };
    }

checkScope(): void

Returns: void

Calls:

  • nullThrows (from ../util)
  • checkOverloads
  • scope.overloads.values
  • addFailures
  • scopes.pop
Code
function checkScope(): void {
      const scope = nullThrows(
        currentScope,
        'checkScope() called without a current scope',
      );
      const failures = checkOverloads(
        [...scope.overloads.values()],
        scope.typeParameters,
      );
      addFailures(failures);
      currentScope = scopes.pop();
    }

getBlockCommentForNode(node: TSESTree.Node): TSESTree.Comment | undefined

Returns: undefined the first valid JSDoc comment annotating node

Raw JSDoc
/**
     * @returns the first valid JSDoc comment annotating `node`
     */

Calls:

  • context.sourceCode .getCommentsBefore(node) .reverse() .find
Code
function getBlockCommentForNode(
      node: TSESTree.Node,
    ): TSESTree.Comment | undefined {
      return context.sourceCode
        .getCommentsBefore(node)
        .reverse()
        .find(comment => comment.type === AST_TOKEN_TYPES.Block);
    }

addOverload(signature: OverloadNode, key: string, containingNode: ContainingNode): void

Parameters:

  • signature OverloadNode
  • key string
  • containingNode ContainingNode

Returns: void

Calls:

  • getOverloadKey
  • currentScope.overloads.get
  • overloads.push
  • currentScope.overloads.set
Code
function addOverload(
      signature: OverloadNode,
      key?: string,
      containingNode?: ContainingNode,
    ): void {
      key ??= getOverloadKey(signature);
      if ((containingNode ?? signature).parent === currentScope?.parent) {
        const overloads = currentScope.overloads.get(key);
        if (overloads != null) {
          overloads.push(signature);
        } else {
          currentScope.overloads.set(key, [signature]);
        }
      }
    }

Interfaces

Failure

Interface Code
interface Failure {
  only2: boolean;
  unify: Unify;
}

Properties

Name Type Optional Description
only2 boolean not shown
unify Unify not shown

Scope

Interface Code
interface Scope {
      overloads: Map<string, OverloadNode[]>;
      parent?: ScopeNode;
      typeParameters?: TSESTree.TSTypeParameterDeclaration;
    }

Properties

Name Type Optional Description
overloads Map<string, OverloadNode[]> not shown
parent ScopeNode not shown
typeParameters TSESTree.TSTypeParameterDeclaration not shown

Type Aliases

Unify

type Unify = | {
      extraParameter: TSESTree.Parameter;
      kind: 'extra-parameter';
      otherSignature: SignatureDefinition;
    }
  | {
      kind: 'all-parameters-are-same';
      signature0: SignatureDefinition;
      signature1: SignatureDefinition;
    }
  | {
      kind: 'single-parameter-difference';
      p0: TSESTree.Parameter;
      p1: TSESTree.Parameter;
    };

IsTypeParameter

/ * Returns true if typeName is the name of an outer type parameter. * In: interface I<T> { m<U>(x: U): T }, only T is an outer type parameter. */

type IsTypeParameter = (typeName: string) => boolean;

ScopeNode

type ScopeNode = | TSESTree.ClassBody
  | TSESTree.Program
  | TSESTree.TSInterfaceBody
  | TSESTree.TSModuleBlock
  | TSESTree.TSTypeLiteral;

OverloadNode

type OverloadNode = MethodDefinition | SignatureDefinition;

ContainingNode

type ContainingNode = TSESTree.ExportDefaultDeclaration | TSESTree.ExportNamedDeclaration;

SignatureDefinition

type SignatureDefinition = | TSESTree.FunctionExpression
  | TSESTree.TSCallSignatureDeclaration
  | TSESTree.TSConstructSignatureDeclaration
  | TSESTree.TSDeclareFunction
  | TSESTree.TSEmptyBodyFunctionExpression
  | TSESTree.TSMethodSignature;

MethodDefinition

type MethodDefinition = TSESTree.MethodDefinition | TSESTree.TSAbstractMethodDefinition;

MessageIds

type MessageIds = | 'allParametersAreSame'
  | 'omittingRestParameter'
  | 'omittingSingleParameter'
  | 'singleParameterDifference';

Options

type Options = [
  {
    ignoreDifferentlyNamedParameters?: boolean;
    ignoreOverloadsWithDifferentJSDoc?: boolean;
  },
];

Generated by Syntax Scribe