Skip to content

⬅️ Back to Table of Contents

📄 no-misused-spread.ts

📊 Analysis Summary

Metric Count
🔧 Functions 33
📦 Imports 14
📊 Variables & Constants 1
📑 Type Aliases 2

📚 Table of Contents

🛠️ File Location:

📂 packages/eslint-plugin/src/rules/no-misused-spread.ts

📦 Imports

Name Source
TSESLint @typescript-eslint/utils
TSESTree @typescript-eslint/utils
AST_NODE_TYPES @typescript-eslint/utils
TypeOrValueSpecifier ../util
createRule ../util
getConstrainedTypeAtLocation ../util
getParserServices ../util
getWrappingFixer ../util
isBuiltinSymbolLike ../util
isPromiseLike ../util
isTypeFlagSet ../util
readonlynessOptionsSchema ../util
typeMatchesSomeSpecifier ../util
isHigherPrecedenceThanAwait ../util

Variables & Constants

Name Type Kind Value Exported
kind any const t.getSymbol()?.valueDeclaration?.kind

Functions

checkArrayOrCallSpread(node: TSESTree.SpreadElement): void

Code
function checkArrayOrCallSpread(node: TSESTree.SpreadElement): void {
      const type = getConstrainedTypeAtLocation(services, node.argument);

      if (
        !typeMatchesSomeSpecifier(type, options.allow, services.program) &&
        isString(type)
      ) {
        context.report({
          node,
          messageId: 'noStringSpread',
        });
      }
    }
  • Parameters:
  • node: TSESTree.SpreadElement
  • Return Type: void
  • Calls:
  • getConstrainedTypeAtLocation (from ../util)
  • typeMatchesSomeSpecifier (from ../util)
  • isString
  • context.report

getMapSpreadSuggestions(node: TSESTree.JSXSpreadAttribute | TSESTree.SpreadElement, type: ts.Type): TSESLint.ReportSuggestionArray<MessageIds> | null

Code
function getMapSpreadSuggestions(
      node: TSESTree.JSXSpreadAttribute | TSESTree.SpreadElement,
      type: ts.Type,
    ): TSESLint.ReportSuggestionArray<MessageIds> | null {
      const types = tsutils.unionConstituents(type);
      if (types.some(t => !isMap(services.program, t))) {
        return null;
      }

      if (
        node.parent.type === AST_NODE_TYPES.ObjectExpression &&
        node.parent.properties.length === 1
      ) {
        return [
          {
            messageId: 'replaceMapSpreadInObject',
            fix: getWrappingFixer({
              node: node.parent,
              innerNode: node.argument,
              sourceCode: context.sourceCode,
              wrap: code => `Object.fromEntries(${code})`,
            }),
          },
        ];
      }

      return [
        {
          messageId: 'replaceMapSpreadInObject',
          fix: getWrappingFixer({
            node: node.argument,
            sourceCode: context.sourceCode,
            wrap: code => `Object.fromEntries(${code})`,
          }),
        },
      ];
    }
  • Parameters:
  • node: TSESTree.JSXSpreadAttribute | TSESTree.SpreadElement
  • type: ts.Type
  • Return Type: TSESLint.ReportSuggestionArray<MessageIds> | null
  • Calls:
  • tsutils.unionConstituents
  • types.some
  • isMap
  • getWrappingFixer (from ../util)

wrap(code: string): string

Code
code => `Object.fromEntries(${code})`
  • Parameters:
  • code: string
  • Return Type: string

wrap(code: string): string

Code
code => `Object.fromEntries(${code})`
  • Parameters:
  • code: string
  • Return Type: string

wrap(code: string): string

Code
code => `Object.fromEntries(${code})`
  • Parameters:
  • code: string
  • Return Type: string

wrap(code: string): string

Code
code => `Object.fromEntries(${code})`
  • Parameters:
  • code: string
  • Return Type: string

wrap(code: string): string

Code
code => `Object.fromEntries(${code})`
  • Parameters:
  • code: string
  • Return Type: string

wrap(code: string): string

Code
code => `Object.fromEntries(${code})`
  • Parameters:
  • code: string
  • Return Type: string

wrap(code: string): string

Code
code => `Object.fromEntries(${code})`
  • Parameters:
  • code: string
  • Return Type: string

wrap(code: string): string

Code
code => `Object.fromEntries(${code})`
  • Parameters:
  • code: string
  • Return Type: string

getPromiseSpreadSuggestions(node: TSESTree.Expression): TSESLint.ReportSuggestionArray<MessageIds>

Code
function getPromiseSpreadSuggestions(
      node: TSESTree.Expression,
    ): TSESLint.ReportSuggestionArray<MessageIds> {
      const isHighPrecendence = isHigherPrecedenceThanAwait(
        services.esTreeNodeToTSNodeMap.get(node),
      );

      return [
        {
          messageId: 'addAwait',
          fix: fixer =>
            isHighPrecendence
              ? fixer.insertTextBefore(node, 'await ')
              : [
                  fixer.insertTextBefore(node, 'await ('),
                  fixer.insertTextAfter(node, ')'),
                ],
        },
      ];
    }
  • Parameters:
  • node: TSESTree.Expression
  • Return Type: TSESLint.ReportSuggestionArray<MessageIds>
  • Calls:
  • isHigherPrecedenceThanAwait (from ../util)
  • services.esTreeNodeToTSNodeMap.get
  • fixer.insertTextBefore
  • fixer.insertTextAfter

fix(fixer: any): any

Code
fixer =>
            isHighPrecendence
              ? fixer.insertTextBefore(node, 'await ')
              : [
                  fixer.insertTextBefore(node, 'await ('),
                  fixer.insertTextAfter(node, ')'),
                ]
  • Parameters:
  • fixer: any
  • Return Type: any

fix(fixer: any): any

Code
fixer =>
            isHighPrecendence
              ? fixer.insertTextBefore(node, 'await ')
              : [
                  fixer.insertTextBefore(node, 'await ('),
                  fixer.insertTextAfter(node, ')'),
                ]
  • Parameters:
  • fixer: any
  • Return Type: any

checkObjectSpread(node: TSESTree.JSXSpreadAttribute | TSESTree.SpreadElement): void

Code
function checkObjectSpread(
      node: TSESTree.JSXSpreadAttribute | TSESTree.SpreadElement,
    ): void {
      const type = getConstrainedTypeAtLocation(services, node.argument);

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

      if (isPromise(services.program, type)) {
        context.report({
          node,
          messageId: 'noPromiseSpreadInObject',
          suggest: getPromiseSpreadSuggestions(node.argument),
        });

        return;
      }

      if (isFunctionWithoutProps(type)) {
        context.report({
          node,
          messageId: 'noFunctionSpreadInObject',
        });

        return;
      }

      if (isMap(services.program, type)) {
        context.report({
          node,
          messageId: 'noMapSpreadInObject',
          suggest: getMapSpreadSuggestions(node, type),
        });

        return;
      }

      if (isArray(checker, type)) {
        context.report({
          node,
          messageId: 'noArraySpreadInObject',
        });

        return;
      }

      if (
        isIterable(type, checker) &&
        // Don't report when the type is string, since TS will flag it already
        !isString(type)
      ) {
        context.report({
          node,
          messageId: 'noIterableSpreadInObject',
        });

        return;
      }

      if (isClassInstance(checker, type)) {
        context.report({
          node,
          messageId: 'noClassInstanceSpreadInObject',
        });

        return;
      }

      if (isClassDeclaration(type)) {
        context.report({
          node,
          messageId: 'noClassDeclarationSpreadInObject',
        });
      }
    }
  • Parameters:
  • node: TSESTree.JSXSpreadAttribute | TSESTree.SpreadElement
  • Return Type: void
  • Calls:
  • getConstrainedTypeAtLocation (from ../util)
  • typeMatchesSomeSpecifier (from ../util)
  • isPromise
  • context.report
  • getPromiseSpreadSuggestions
  • isFunctionWithoutProps
  • isMap
  • getMapSpreadSuggestions
  • isArray
  • isIterable
  • isString
  • isClassInstance
  • isClassDeclaration
  • Internal Comments:
    // Don't report when the type is string, since TS will flag it already
    

wrap(code: string): string

Code
code => `Object.fromEntries(${code})`
  • Parameters:
  • code: string
  • Return Type: string

wrap(code: string): string

Code
code => `Object.fromEntries(${code})`
  • Parameters:
  • code: string
  • Return Type: string

wrap(code: string): string

Code
code => `Object.fromEntries(${code})`
  • Parameters:
  • code: string
  • Return Type: string

wrap(code: string): string

Code
code => `Object.fromEntries(${code})`
  • Parameters:
  • code: string
  • Return Type: string

wrap(code: string): string

Code
code => `Object.fromEntries(${code})`
  • Parameters:
  • code: string
  • Return Type: string

wrap(code: string): string

Code
code => `Object.fromEntries(${code})`
  • Parameters:
  • code: string
  • Return Type: string

wrap(code: string): string

Code
code => `Object.fromEntries(${code})`
  • Parameters:
  • code: string
  • Return Type: string

wrap(code: string): string

Code
code => `Object.fromEntries(${code})`
  • Parameters:
  • code: string
  • Return Type: string

fix(fixer: any): any

Code
fixer =>
            isHighPrecendence
              ? fixer.insertTextBefore(node, 'await ')
              : [
                  fixer.insertTextBefore(node, 'await ('),
                  fixer.insertTextAfter(node, ')'),
                ]
  • Parameters:
  • fixer: any
  • Return Type: any

fix(fixer: any): any

Code
fixer =>
            isHighPrecendence
              ? fixer.insertTextBefore(node, 'await ')
              : [
                  fixer.insertTextBefore(node, 'await ('),
                  fixer.insertTextAfter(node, ')'),
                ]
  • Parameters:
  • fixer: any
  • Return Type: any

isIterable(type: ts.Type, checker: ts.TypeChecker): boolean

Code
function isIterable(type: ts.Type, checker: ts.TypeChecker): boolean {
  return tsutils
    .typeConstituents(type)
    .some(
      t => !!tsutils.getWellKnownSymbolPropertyOfType(t, 'iterator', checker),
    );
}
  • Parameters:
  • type: ts.Type
  • checker: ts.TypeChecker
  • Return Type: boolean
  • Calls:
  • tsutils .typeConstituents(type) .some
  • tsutils.getWellKnownSymbolPropertyOfType

isArray(checker: ts.TypeChecker, type: ts.Type): boolean

Code
function isArray(checker: ts.TypeChecker, type: ts.Type): boolean {
  return isTypeRecurser(
    type,
    t => checker.isArrayType(t) || checker.isTupleType(t),
  );
}
  • Parameters:
  • checker: ts.TypeChecker
  • type: ts.Type
  • Return Type: boolean
  • Calls:
  • isTypeRecurser
  • checker.isArrayType
  • checker.isTupleType

isString(type: ts.Type): boolean

Code
function isString(type: ts.Type): boolean {
  return isTypeRecurser(type, t => isTypeFlagSet(t, ts.TypeFlags.StringLike));
}
  • Parameters:
  • type: ts.Type
  • Return Type: boolean
  • Calls:
  • isTypeRecurser
  • isTypeFlagSet (from ../util)

isFunctionWithoutProps(type: ts.Type): boolean

Code
function isFunctionWithoutProps(type: ts.Type): boolean {
  return isTypeRecurser(
    type,
    t => t.getCallSignatures().length > 0 && t.getProperties().length === 0,
  );
}
  • Parameters:
  • type: ts.Type
  • Return Type: boolean
  • Calls:
  • isTypeRecurser
  • t.getCallSignatures
  • t.getProperties

isPromise(program: ts.Program, type: ts.Type): boolean

Code
function isPromise(program: ts.Program, type: ts.Type): boolean {
  return isTypeRecurser(type, t => isPromiseLike(program, t));
}
  • Parameters:
  • program: ts.Program
  • type: ts.Type
  • Return Type: boolean
  • Calls:
  • isTypeRecurser
  • isPromiseLike (from ../util)

isClassInstance(checker: ts.TypeChecker, type: ts.Type): boolean

Code
function isClassInstance(checker: ts.TypeChecker, type: ts.Type): boolean {
  return isTypeRecurser(type, t => {
    // If the type itself has a construct signature, it's a class(-like)
    if (t.getConstructSignatures().length) {
      return false;
    }

    const symbol = t.getSymbol();

    // If the type's symbol has a construct signature, the type is an instance
    return !!symbol
      ?.getDeclarations()
      ?.some(
        declaration =>
          checker
            .getTypeOfSymbolAtLocation(symbol, declaration)
            .getConstructSignatures().length,
      );
  });
}
  • Parameters:
  • checker: ts.TypeChecker
  • type: ts.Type
  • Return Type: boolean
  • Calls:
  • isTypeRecurser
  • t.getConstructSignatures
  • t.getSymbol
  • symbol ?.getDeclarations() ?.some
  • checker .getTypeOfSymbolAtLocation(symbol, declaration) .getConstructSignatures
  • Internal Comments:
    // If the type itself has a construct signature, it's a class(-like)
    // If the type's symbol has a construct signature, the type is an instance
    

isClassDeclaration(type: ts.Type): boolean

Code
function isClassDeclaration(type: ts.Type): boolean {
  return isTypeRecurser(type, t => {
    if (
      tsutils.isObjectType(t) &&
      tsutils.isObjectFlagSet(t, ts.ObjectFlags.InstantiationExpressionType)
    ) {
      return true;
    }

    const kind = t.getSymbol()?.valueDeclaration?.kind;

    return (
      kind === ts.SyntaxKind.ClassDeclaration ||
      kind === ts.SyntaxKind.ClassExpression
    );
  });
}
  • Parameters:
  • type: ts.Type
  • Return Type: boolean
  • Calls:
  • isTypeRecurser
  • tsutils.isObjectType
  • tsutils.isObjectFlagSet
  • t.getSymbol

isMap(program: ts.Program, type: ts.Type): boolean

Code
function isMap(program: ts.Program, type: ts.Type): boolean {
  return isTypeRecurser(type, t =>
    isBuiltinSymbolLike(program, t, ['Map', 'ReadonlyMap', 'WeakMap']),
  );
}
  • Parameters:
  • program: ts.Program
  • type: ts.Type
  • Return Type: boolean
  • Calls:
  • isTypeRecurser
  • isBuiltinSymbolLike (from ../util)

isTypeRecurser(type: ts.Type, predicate: (t: ts.Type) => boolean): boolean

Code
function isTypeRecurser(
  type: ts.Type,
  predicate: (t: ts.Type) => boolean,
): boolean {
  if (type.isUnionOrIntersection()) {
    return type.types.some(t => isTypeRecurser(t, predicate));
  }

  return predicate(type);
}
  • Parameters:
  • type: ts.Type
  • predicate: (t: ts.Type) => boolean
  • Return Type: boolean
  • Calls:
  • type.isUnionOrIntersection
  • type.types.some
  • isTypeRecurser
  • predicate

Type Aliases

Options

type Options = [
  {
    allow?: TypeOrValueSpecifier[];
  },
];

MessageIds

type MessageIds = | 'addAwait'
  | 'noArraySpreadInObject'
  | 'noClassDeclarationSpreadInObject'
  | 'noClassInstanceSpreadInObject'
  | 'noFunctionSpreadInObject'
  | 'noIterableSpreadInObject'
  | 'noMapSpreadInObject'
  | 'noPromiseSpreadInObject'
  | 'noStringSpread'
  | 'replaceMapSpreadInObject';