Skip to content

⬅️ Back to Table of Contents

📄 no-deprecated

📊 Analysis Summary

Metric Count
🔧 Functions 13
📦 Imports 9
📑 Type Aliases 4

📚 Table of Contents

🛠️ File Location:

📂 packages/eslint-plugin/src/rules/no-deprecated.ts

📤 Default Export

export default createRule<Options, MessageIds>({ ... })
Property Value
name 'no-deprecated'
meta.type 'problem'
meta.docs.description 'Disallow using code marked as @deprecated'
meta.docs.recommended 'strict'
meta.docs.requiresTypeChecking true
meta.messages.deprecated \` is deprecated.`}
meta.messages.deprecatedWithReason \`}}` is deprecated. {{reason}
meta.schema [ { type: 'object', additionalProperties: false, properties: { allow: { ...typeOrValueSpecifiersSchema, description: ...
defaultOptions [ { allow: [], }, ]

Entry point: create — documented under Functions.


📦 Imports

Name Source
TSESTree @typescript-eslint/utils
AST_NODE_TYPES @typescript-eslint/utils
TypeOrValueSpecifier ../util
createRule ../util
getParserServices ../util
nullThrows ../util
typeOrValueSpecifiersSchema ../util
typeMatchesSomeSpecifier ../util
valueMatchesSomeSpecifier ../util

Functions

create(context: any, [options]: any): { Identifier(node: any): void; JSXIdentifier(node: any): vo…

Parameters:

  • context any
  • [options] any

Returns: { Identifier(node: any): void; JSXIdentifier(node: any): void; MemberExpression: (node: TSESTree.MemberExpression) => void; PrivateIdentifier: (node: IdentifierLike) => void; Super: (node: IdentifierLike) => void; }

Calls:

  • getParserServices (from ../util)
  • services.program.getTypeChecker
  • tsutils.isSymbolFlagSet
  • getJsDocDeprecation
  • checker.getAliasedSymbol
  • symbol.getDeclarations
  • checker.getImmediateAliasedSymbol
  • parent.elements.includes
  • symbol?.getJsDocTags
  • jsDocTags?.find
  • ts.displayPartsToString
  • isNodeCalleeOfParent
  • services.esTreeNodeToTSNodeMap.get
  • nullThrows (from ../util)
  • checker.getResolvedSignature
  • services.getSymbolAtLocation
  • searchForDeprecationInAliasesChain
  • services.getContextualType
  • contextualType.getProperty
  • getCallLikeNode
  • getCallLikeDeprecation
  • getJSXAttributeDeprecation
  • services .getTypeAtLocation(node.parent.parent) .getProperty
  • checker.getShorthandAssignmentValueSymbol
  • isDeclaration
  • isInsideImport
  • getDeprecationReason
  • services.getTypeAtLocation
  • typeMatchesSomeSpecifier (from ../util)
  • valueMatchesSomeSpecifier (from ../util)
  • getReportedNodeName
  • context.report
  • propertyType.isLiteral
  • propertyType.isStringLiteral
  • String
  • objectType.getProperty
  • checkIdentifier

Internal Comments:

// Deprecated jsdoc tags can be added on some symbol alias, e.g.
// (x8)
// export { /** @deprecated */ foo }
// When we import foo, its symbol is an alias of the exported foo (the one
// with the deprecated tag), which is itself an alias of the original foo.
// Therefore, we carefully go through the chain of aliases and check each
// immediate alias for deprecated tags
// foo in "const { foo } = bar" will be processed twice, as parent.key
// and parent.value. The second is treated as a declaration.
// const { foo: bar } = baz; -- bar IS a declaration.
// const baz = { foo: bar }; -- bar IS NOT a declaration.
// const { foo: bar } = baz; -- foo IS NOT a declaration.
// const baz = { foo: bar }; -- foo IS a declaration.
// foo in "const { foo = "" } = bar" will be processed twice, as parent.parent.key
// and parent.left. The second is treated as a declaration.
// treat `export import Bar = Foo;` (and `import Foo = require('...')`) as declarations
// workaround for https://github.com/microsoft/TypeScript/issues/60024
// If the node is a direct function call, we look for its signature. (x2)
// Properties with function-like types have "deprecated" jsdoc
// on their symbols, not on their signatures:
// interface Props {
//   /** @deprecated */
//   property: () => 'foo'
//   ^symbol^  ^signature^
// }
// Here we're working with a function declaration or method.
// Both can have 1 or more overloads, each overload creates one
// ts.Declaration which is placed in symbol.declarations.
// Imagine the following code:
// function foo(): void
// /** @deprecated Some Reason */
// function foo(arg: string): void
// function foo(arg?: string): void {}
// foo()    // <- foo is our symbol
// If we call getJsDocDeprecation(checker.getAliasedSymbol(symbol)),
// we get 'Some Reason', but after all, we are calling foo with
// a signature that is not deprecated!
// It works this way because symbol.getJsDocTags returns tags from
// all symbol declarations combined into one array. And AFAIK there is
// no publicly exported TS function that can tell us if a particular
// declaration is deprecated or not.
// So, in case of function and method declarations, we don't check original
// aliased symbol, but rely on the getJsDocDeprecation(signature) call below.
// Computed identifier expressions are handled by checkMemberExpression
// only deal with the alias (exported) side, not the local binding
// whether it's a plain identifier or the exported alias (x3)

Code
create(context, [options]) {
    const { jsDocParsingMode } = context.languageOptions.parserOptions;
    const allow = options.allow;
    if (jsDocParsingMode === 'none' || jsDocParsingMode === 'type-info') {
      throw new Error(
        `Cannot be used with jsDocParsingMode: '${jsDocParsingMode}'.`,
      );
    }

    const services = getParserServices(context);
    const checker = services.program.getTypeChecker();

    // Deprecated jsdoc tags can be added on some symbol alias, e.g.
    //
    // export { /** @deprecated */ foo }
    //
    // When we import foo, its symbol is an alias of the exported foo (the one
    // with the deprecated tag), which is itself an alias of the original foo.
    // Therefore, we carefully go through the chain of aliases and check each
    // immediate alias for deprecated tags
    function searchForDeprecationInAliasesChain(
      symbol: ts.Symbol | undefined,
      checkDeprecationsOfAliasedSymbol: boolean,
    ): string | undefined {
      if (!symbol || !tsutils.isSymbolFlagSet(symbol, ts.SymbolFlags.Alias)) {
        return checkDeprecationsOfAliasedSymbol
          ? getJsDocDeprecation(symbol)
          : undefined;
      }
      const targetSymbol = checker.getAliasedSymbol(symbol);
      while (tsutils.isSymbolFlagSet(symbol, ts.SymbolFlags.Alias)) {
        const reason = getJsDocDeprecation(symbol);
        if (reason != null) {
          return reason;
        }
        const immediateAliasedSymbol: ts.Symbol | undefined =
          symbol.getDeclarations() && checker.getImmediateAliasedSymbol(symbol);
        if (!immediateAliasedSymbol) {
          break;
        }
        symbol = immediateAliasedSymbol;
        if (checkDeprecationsOfAliasedSymbol && symbol === targetSymbol) {
          return getJsDocDeprecation(symbol);
        }
      }
      return undefined;
    }

    function isDeclaration(node: IdentifierLike): boolean {
      const { parent } = node;

      switch (parent.type) {
        case AST_NODE_TYPES.ArrayPattern:
          return parent.elements.includes(node as TSESTree.Identifier);

        case AST_NODE_TYPES.ClassExpression:
        case AST_NODE_TYPES.ClassDeclaration:
        case AST_NODE_TYPES.VariableDeclarator:
        case AST_NODE_TYPES.TSEnumMember:
          return parent.id === node;

        case AST_NODE_TYPES.MethodDefinition:
        case AST_NODE_TYPES.PropertyDefinition:
        case AST_NODE_TYPES.AccessorProperty:
          return parent.key === node;

        case AST_NODE_TYPES.Property:
          // foo in "const { foo } = bar" will be processed twice, as parent.key
          // and parent.value. The second is treated as a declaration.

          if (parent.value === node) {
            // const { foo: bar } = baz; -- bar IS a declaration.
            // const baz = { foo: bar }; -- bar IS NOT a declaration.
            return parent.parent.type === AST_NODE_TYPES.ObjectPattern;
          }
          // const { foo: bar } = baz; -- foo IS NOT a declaration.
          // const baz = { foo: bar }; -- foo IS a declaration.
          return parent.parent.type === AST_NODE_TYPES.ObjectExpression;

        case AST_NODE_TYPES.AssignmentPattern:
          // foo in "const { foo = "" } = bar" will be processed twice, as parent.parent.key
          // and parent.left. The second is treated as a declaration.
          return parent.left === node;

        case AST_NODE_TYPES.ArrowFunctionExpression:
        case AST_NODE_TYPES.FunctionDeclaration:
        case AST_NODE_TYPES.FunctionExpression:
        case AST_NODE_TYPES.TSDeclareFunction:
        case AST_NODE_TYPES.TSEmptyBodyFunctionExpression:
        case AST_NODE_TYPES.TSEnumDeclaration:
        case AST_NODE_TYPES.TSInterfaceDeclaration:
        case AST_NODE_TYPES.TSMethodSignature:
        case AST_NODE_TYPES.TSModuleDeclaration:
        case AST_NODE_TYPES.TSParameterProperty:
        case AST_NODE_TYPES.TSPropertySignature:
        case AST_NODE_TYPES.TSTypeAliasDeclaration:
        case AST_NODE_TYPES.TSTypeParameter:
          return true;

        // treat `export import Bar = Foo;` (and `import Foo = require('...')`) as declarations
        case AST_NODE_TYPES.TSImportEqualsDeclaration:
          return parent.id === node;

        default:
          return false;
      }
    }

    function isInsideImport(node: TSESTree.Node): boolean {
      let current = node;

      while (true) {
        switch (current.type) {
          case AST_NODE_TYPES.ImportDeclaration:
            return true;

          case AST_NODE_TYPES.ArrowFunctionExpression:
          case AST_NODE_TYPES.ExportAllDeclaration:
          case AST_NODE_TYPES.ExportNamedDeclaration:
          case AST_NODE_TYPES.BlockStatement:
          case AST_NODE_TYPES.ClassDeclaration:
          case AST_NODE_TYPES.TSInterfaceDeclaration:
          case AST_NODE_TYPES.FunctionDeclaration:
          case AST_NODE_TYPES.FunctionExpression:
          case AST_NODE_TYPES.Program:
          case AST_NODE_TYPES.TSUnionType:
          case AST_NODE_TYPES.VariableDeclarator:
            return false;

          default:
            current = current.parent;
        }
      }
    }

    function getJsDocDeprecation(
      symbol: ts.Signature | ts.Symbol | undefined,
    ): string | undefined {
      let jsDocTags: ts.JSDocTagInfo[] | undefined;
      try {
        jsDocTags = symbol?.getJsDocTags(checker);
      } catch {
        // workaround for https://github.com/microsoft/TypeScript/issues/60024
        return;
      }
      const tag = jsDocTags?.find(tag => tag.name === 'deprecated');

      if (!tag) {
        return undefined;
      }

      const displayParts = tag.text;

      return displayParts ? ts.displayPartsToString(displayParts) : '';
    }

    type CallLikeNode =
      | TSESTree.CallExpression
      | TSESTree.JSXOpeningElement
      | TSESTree.NewExpression
      | TSESTree.TaggedTemplateExpression;

    function isNodeCalleeOfParent(node: TSESTree.Node): node is CallLikeNode {
      switch (node.parent?.type) {
        case AST_NODE_TYPES.NewExpression:
        case AST_NODE_TYPES.CallExpression:
          return node.parent.callee === node;

        case AST_NODE_TYPES.TaggedTemplateExpression:
          return node.parent.tag === node;

        case AST_NODE_TYPES.JSXOpeningElement:
          return node.parent.name === node;

        default:
          return false;
      }
    }

    function getCallLikeNode(node: TSESTree.Node): CallLikeNode | undefined {
      let callee = node;

      while (
        callee.parent?.type === AST_NODE_TYPES.MemberExpression &&
        callee.parent.property === callee
      ) {
        callee = callee.parent;
      }

      return isNodeCalleeOfParent(callee) ? callee : undefined;
    }

    function getCallLikeDeprecation(node: CallLikeNode): string | undefined {
      const tsNode = services.esTreeNodeToTSNodeMap.get(node.parent);

      // If the node is a direct function call, we look for its signature.
      const signature = nullThrows(
        checker.getResolvedSignature(tsNode as ts.CallLikeExpression),
        'Expected call like node to have signature',
      );

      const symbol = services.getSymbolAtLocation(node);
      const aliasedSymbol =
        symbol != null && tsutils.isSymbolFlagSet(symbol, ts.SymbolFlags.Alias)
          ? checker.getAliasedSymbol(symbol)
          : symbol;
      const symbolDeclarationKind = aliasedSymbol?.declarations?.[0].kind;
      // Properties with function-like types have "deprecated" jsdoc
      // on their symbols, not on their signatures:
      //
      // interface Props {
      //   /** @deprecated */
      //   property: () => 'foo'
      //   ^symbol^  ^signature^
      // }
      if (
        symbolDeclarationKind !== ts.SyntaxKind.MethodDeclaration &&
        symbolDeclarationKind !== ts.SyntaxKind.FunctionDeclaration &&
        symbolDeclarationKind !== ts.SyntaxKind.MethodSignature
      ) {
        return (
          searchForDeprecationInAliasesChain(symbol, true) ??
          getJsDocDeprecation(signature) ??
          getJsDocDeprecation(aliasedSymbol)
        );
      }
      return (
        searchForDeprecationInAliasesChain(
          symbol,
          // Here we're working with a function declaration or method.
          // Both can have 1 or more overloads, each overload creates one
          // ts.Declaration which is placed in symbol.declarations.
          //
          // Imagine the following code:
          //
          // function foo(): void
          // /** @deprecated Some Reason */
          // function foo(arg: string): void
          // function foo(arg?: string): void {}
          //
          // foo()    // <- foo is our symbol
          //
          // If we call getJsDocDeprecation(checker.getAliasedSymbol(symbol)),
          // we get 'Some Reason', but after all, we are calling foo with
          // a signature that is not deprecated!
          // It works this way because symbol.getJsDocTags returns tags from
          // all symbol declarations combined into one array. And AFAIK there is
          // no publicly exported TS function that can tell us if a particular
          // declaration is deprecated or not.
          //
          // So, in case of function and method declarations, we don't check original
          // aliased symbol, but rely on the getJsDocDeprecation(signature) call below.
          false,
        ) ?? getJsDocDeprecation(signature)
      );
    }

    function getJSXAttributeDeprecation(
      openingElement: TSESTree.JSXOpeningElement,
      propertyName: string,
    ): string | undefined {
      const contextualType = nullThrows(
        services.getContextualType(
          openingElement.name as unknown as TSESTree.Expression,
        ),
        'Expected JSX opening element name to have contextualType',
      );

      const symbol = contextualType.getProperty(propertyName);

      return getJsDocDeprecation(symbol);
    }

    function getDeprecationReason(node: IdentifierLike): string | undefined {
      const callLikeNode = getCallLikeNode(node);
      if (callLikeNode) {
        return getCallLikeDeprecation(callLikeNode);
      }

      if (
        node.parent.type === AST_NODE_TYPES.JSXAttribute &&
        node.type !== AST_NODE_TYPES.Super
      ) {
        return getJSXAttributeDeprecation(node.parent.parent, node.name);
      }

      if (
        node.parent.type === AST_NODE_TYPES.Property &&
        node.type !== AST_NODE_TYPES.Super
      ) {
        const property = services
          .getTypeAtLocation(node.parent.parent)
          .getProperty(node.name);
        const propertySymbol = services.getSymbolAtLocation(node);
        const valueSymbol = checker.getShorthandAssignmentValueSymbol(
          propertySymbol?.valueDeclaration,
        );
        return (
          searchForDeprecationInAliasesChain(propertySymbol, true) ??
          getJsDocDeprecation(property) ??
          getJsDocDeprecation(propertySymbol) ??
          searchForDeprecationInAliasesChain(valueSymbol, true)
        );
      }

      return searchForDeprecationInAliasesChain(
        services.getSymbolAtLocation(node),
        true,
      );
    }

    function checkIdentifier(node: IdentifierLike): void {
      if (isDeclaration(node) || isInsideImport(node)) {
        return;
      }

      const reason = getDeprecationReason(node);

      if (reason == null) {
        return;
      }

      const type = services.getTypeAtLocation(node);
      if (
        typeMatchesSomeSpecifier(type, allow, services.program) ||
        valueMatchesSomeSpecifier(node, allow, services.program, type)
      ) {
        return;
      }

      const name = getReportedNodeName(node);

      context.report({
        ...(reason
          ? {
              messageId: 'deprecatedWithReason',
              data: { name, reason },
            }
          : {
              messageId: 'deprecated',
              data: { name },
            }),
        node,
      });
    }

    function checkMemberExpression(node: TSESTree.MemberExpression): void {
      if (!node.computed) {
        return;
      }

      const propertyType = services.getTypeAtLocation(node.property);

      if (propertyType.isLiteral()) {
        const objectType = services.getTypeAtLocation(node.object);

        const propertyName = propertyType.isStringLiteral()
          ? propertyType.value
          : // eslint-disable-next-line @typescript-eslint/no-base-to-string
            String(propertyType.value);

        const property = objectType.getProperty(propertyName);

        const reason = getJsDocDeprecation(property);
        if (reason == null) {
          return;
        }

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

        context.report({
          ...(reason
            ? {
                messageId: 'deprecatedWithReason',
                data: { name: propertyName, reason },
              }
            : {
                messageId: 'deprecated',
                data: { name: propertyName },
              }),
          node: node.property,
        });
      }
    }

    return {
      Identifier(node): void {
        const { parent } = node;

        if (
          parent.type === AST_NODE_TYPES.ExportNamedDeclaration ||
          parent.type === AST_NODE_TYPES.ExportAllDeclaration
        ) {
          return;
        }

        // Computed identifier expressions are handled by checkMemberExpression
        if (
          parent.type === AST_NODE_TYPES.MemberExpression &&
          parent.computed &&
          parent.property === node
        ) {
          return;
        }

        if (parent.type === AST_NODE_TYPES.ExportSpecifier) {
          // only deal with the alias (exported) side, not the local binding
          if (parent.exported !== node) {
            return;
          }

          const symbol = services.getSymbolAtLocation(node);
          const aliasDeprecation = getJsDocDeprecation(symbol);

          if (aliasDeprecation != null) {
            return;
          }
        }

        // whether it's a plain identifier or the exported alias
        checkIdentifier(node);
      },
      JSXIdentifier(node): void {
        if (node.parent.type !== AST_NODE_TYPES.JSXClosingElement) {
          checkIdentifier(node);
        }
      },
      MemberExpression: checkMemberExpression,
      PrivateIdentifier: checkIdentifier,
      Super: checkIdentifier,
    };
  }

getReportedNodeName(node: IdentifierLike): string

Parameters:

  • node IdentifierLike

Returns: string

Code
function getReportedNodeName(node: IdentifierLike): string {
  if (node.type === AST_NODE_TYPES.Super) {
    return 'super';
  }

  if (node.type === AST_NODE_TYPES.PrivateIdentifier) {
    return `#${node.name}`;
  }

  return node.name;
}

Internal helpers

Declared inside another function in this file.

searchForDeprecationInAliasesChain(symbol: ts.Symbol | undefined, checkDeprecationsOfAliasedSymbol: boolean): string | undefined

Parameters:

  • symbol ts.Symbol | undefined
  • checkDeprecationsOfAliasedSymbol boolean

Returns: string | undefined

Calls:

  • tsutils.isSymbolFlagSet
  • getJsDocDeprecation
  • checker.getAliasedSymbol
  • symbol.getDeclarations
  • checker.getImmediateAliasedSymbol
Code
function searchForDeprecationInAliasesChain(
      symbol: ts.Symbol | undefined,
      checkDeprecationsOfAliasedSymbol: boolean,
    ): string | undefined {
      if (!symbol || !tsutils.isSymbolFlagSet(symbol, ts.SymbolFlags.Alias)) {
        return checkDeprecationsOfAliasedSymbol
          ? getJsDocDeprecation(symbol)
          : undefined;
      }
      const targetSymbol = checker.getAliasedSymbol(symbol);
      while (tsutils.isSymbolFlagSet(symbol, ts.SymbolFlags.Alias)) {
        const reason = getJsDocDeprecation(symbol);
        if (reason != null) {
          return reason;
        }
        const immediateAliasedSymbol: ts.Symbol | undefined =
          symbol.getDeclarations() && checker.getImmediateAliasedSymbol(symbol);
        if (!immediateAliasedSymbol) {
          break;
        }
        symbol = immediateAliasedSymbol;
        if (checkDeprecationsOfAliasedSymbol && symbol === targetSymbol) {
          return getJsDocDeprecation(symbol);
        }
      }
      return undefined;
    }

isDeclaration(node: IdentifierLike): boolean

Parameters:

  • node IdentifierLike

Returns: boolean

Calls:

  • parent.elements.includes

Internal Comments:

// foo in "const { foo } = bar" will be processed twice, as parent.key
// and parent.value. The second is treated as a declaration.
// const { foo: bar } = baz; -- bar IS a declaration.
// const baz = { foo: bar }; -- bar IS NOT a declaration.
// const { foo: bar } = baz; -- foo IS NOT a declaration.
// const baz = { foo: bar }; -- foo IS a declaration.
// foo in "const { foo = "" } = bar" will be processed twice, as parent.parent.key
// and parent.left. The second is treated as a declaration.
// treat `export import Bar = Foo;` (and `import Foo = require('...')`) as declarations

Code
function isDeclaration(node: IdentifierLike): boolean {
      const { parent } = node;

      switch (parent.type) {
        case AST_NODE_TYPES.ArrayPattern:
          return parent.elements.includes(node as TSESTree.Identifier);

        case AST_NODE_TYPES.ClassExpression:
        case AST_NODE_TYPES.ClassDeclaration:
        case AST_NODE_TYPES.VariableDeclarator:
        case AST_NODE_TYPES.TSEnumMember:
          return parent.id === node;

        case AST_NODE_TYPES.MethodDefinition:
        case AST_NODE_TYPES.PropertyDefinition:
        case AST_NODE_TYPES.AccessorProperty:
          return parent.key === node;

        case AST_NODE_TYPES.Property:
          // foo in "const { foo } = bar" will be processed twice, as parent.key
          // and parent.value. The second is treated as a declaration.

          if (parent.value === node) {
            // const { foo: bar } = baz; -- bar IS a declaration.
            // const baz = { foo: bar }; -- bar IS NOT a declaration.
            return parent.parent.type === AST_NODE_TYPES.ObjectPattern;
          }
          // const { foo: bar } = baz; -- foo IS NOT a declaration.
          // const baz = { foo: bar }; -- foo IS a declaration.
          return parent.parent.type === AST_NODE_TYPES.ObjectExpression;

        case AST_NODE_TYPES.AssignmentPattern:
          // foo in "const { foo = "" } = bar" will be processed twice, as parent.parent.key
          // and parent.left. The second is treated as a declaration.
          return parent.left === node;

        case AST_NODE_TYPES.ArrowFunctionExpression:
        case AST_NODE_TYPES.FunctionDeclaration:
        case AST_NODE_TYPES.FunctionExpression:
        case AST_NODE_TYPES.TSDeclareFunction:
        case AST_NODE_TYPES.TSEmptyBodyFunctionExpression:
        case AST_NODE_TYPES.TSEnumDeclaration:
        case AST_NODE_TYPES.TSInterfaceDeclaration:
        case AST_NODE_TYPES.TSMethodSignature:
        case AST_NODE_TYPES.TSModuleDeclaration:
        case AST_NODE_TYPES.TSParameterProperty:
        case AST_NODE_TYPES.TSPropertySignature:
        case AST_NODE_TYPES.TSTypeAliasDeclaration:
        case AST_NODE_TYPES.TSTypeParameter:
          return true;

        // treat `export import Bar = Foo;` (and `import Foo = require('...')`) as declarations
        case AST_NODE_TYPES.TSImportEqualsDeclaration:
          return parent.id === node;

        default:
          return false;
      }
    }

isInsideImport(node: TSESTree.Node): boolean

Parameters:

  • node TSESTree.Node

Returns: boolean

Code
function isInsideImport(node: TSESTree.Node): boolean {
      let current = node;

      while (true) {
        switch (current.type) {
          case AST_NODE_TYPES.ImportDeclaration:
            return true;

          case AST_NODE_TYPES.ArrowFunctionExpression:
          case AST_NODE_TYPES.ExportAllDeclaration:
          case AST_NODE_TYPES.ExportNamedDeclaration:
          case AST_NODE_TYPES.BlockStatement:
          case AST_NODE_TYPES.ClassDeclaration:
          case AST_NODE_TYPES.TSInterfaceDeclaration:
          case AST_NODE_TYPES.FunctionDeclaration:
          case AST_NODE_TYPES.FunctionExpression:
          case AST_NODE_TYPES.Program:
          case AST_NODE_TYPES.TSUnionType:
          case AST_NODE_TYPES.VariableDeclarator:
            return false;

          default:
            current = current.parent;
        }
      }
    }

getJsDocDeprecation(symbol: ts.Signature | ts.Symbol | undefined): string | undefined

Parameters:

  • symbol ts.Signature | ts.Symbol | undefined

Returns: string | undefined

Calls:

  • symbol?.getJsDocTags
  • jsDocTags?.find
  • ts.displayPartsToString

Internal Comments:

// workaround for https://github.com/microsoft/TypeScript/issues/60024

Code
function getJsDocDeprecation(
      symbol: ts.Signature | ts.Symbol | undefined,
    ): string | undefined {
      let jsDocTags: ts.JSDocTagInfo[] | undefined;
      try {
        jsDocTags = symbol?.getJsDocTags(checker);
      } catch {
        // workaround for https://github.com/microsoft/TypeScript/issues/60024
        return;
      }
      const tag = jsDocTags?.find(tag => tag.name === 'deprecated');

      if (!tag) {
        return undefined;
      }

      const displayParts = tag.text;

      return displayParts ? ts.displayPartsToString(displayParts) : '';
    }

isNodeCalleeOfParent(node: TSESTree.Node): node is CallLikeNode

Parameters:

  • node TSESTree.Node

Returns: node is CallLikeNode

Code
function isNodeCalleeOfParent(node: TSESTree.Node): node is CallLikeNode {
      switch (node.parent?.type) {
        case AST_NODE_TYPES.NewExpression:
        case AST_NODE_TYPES.CallExpression:
          return node.parent.callee === node;

        case AST_NODE_TYPES.TaggedTemplateExpression:
          return node.parent.tag === node;

        case AST_NODE_TYPES.JSXOpeningElement:
          return node.parent.name === node;

        default:
          return false;
      }
    }

getCallLikeNode(node: TSESTree.Node): CallLikeNode | undefined

Parameters:

  • node TSESTree.Node

Returns: CallLikeNode | undefined

Calls:

  • isNodeCalleeOfParent
Code
function getCallLikeNode(node: TSESTree.Node): CallLikeNode | undefined {
      let callee = node;

      while (
        callee.parent?.type === AST_NODE_TYPES.MemberExpression &&
        callee.parent.property === callee
      ) {
        callee = callee.parent;
      }

      return isNodeCalleeOfParent(callee) ? callee : undefined;
    }

getCallLikeDeprecation(node: CallLikeNode): string | undefined

Parameters:

  • node CallLikeNode

Returns: string | undefined

Calls:

  • services.esTreeNodeToTSNodeMap.get
  • nullThrows (from ../util)
  • checker.getResolvedSignature
  • services.getSymbolAtLocation
  • tsutils.isSymbolFlagSet
  • checker.getAliasedSymbol
  • searchForDeprecationInAliasesChain
  • getJsDocDeprecation

Internal Comments:

// If the node is a direct function call, we look for its signature. (x2)
// Properties with function-like types have "deprecated" jsdoc
// on their symbols, not on their signatures:
// (x6)
// interface Props {
//   /** @deprecated */
//   property: () => 'foo'
//   ^symbol^  ^signature^
// }
// Here we're working with a function declaration or method.
// Both can have 1 or more overloads, each overload creates one
// ts.Declaration which is placed in symbol.declarations.
// Imagine the following code:
// function foo(): void
// /** @deprecated Some Reason */
// function foo(arg: string): void
// function foo(arg?: string): void {}
// foo()    // <- foo is our symbol
// If we call getJsDocDeprecation(checker.getAliasedSymbol(symbol)),
// we get 'Some Reason', but after all, we are calling foo with
// a signature that is not deprecated!
// It works this way because symbol.getJsDocTags returns tags from
// all symbol declarations combined into one array. And AFAIK there is
// no publicly exported TS function that can tell us if a particular
// declaration is deprecated or not.
// So, in case of function and method declarations, we don't check original
// aliased symbol, but rely on the getJsDocDeprecation(signature) call below.

Code
function getCallLikeDeprecation(node: CallLikeNode): string | undefined {
      const tsNode = services.esTreeNodeToTSNodeMap.get(node.parent);

      // If the node is a direct function call, we look for its signature.
      const signature = nullThrows(
        checker.getResolvedSignature(tsNode as ts.CallLikeExpression),
        'Expected call like node to have signature',
      );

      const symbol = services.getSymbolAtLocation(node);
      const aliasedSymbol =
        symbol != null && tsutils.isSymbolFlagSet(symbol, ts.SymbolFlags.Alias)
          ? checker.getAliasedSymbol(symbol)
          : symbol;
      const symbolDeclarationKind = aliasedSymbol?.declarations?.[0].kind;
      // Properties with function-like types have "deprecated" jsdoc
      // on their symbols, not on their signatures:
      //
      // interface Props {
      //   /** @deprecated */
      //   property: () => 'foo'
      //   ^symbol^  ^signature^
      // }
      if (
        symbolDeclarationKind !== ts.SyntaxKind.MethodDeclaration &&
        symbolDeclarationKind !== ts.SyntaxKind.FunctionDeclaration &&
        symbolDeclarationKind !== ts.SyntaxKind.MethodSignature
      ) {
        return (
          searchForDeprecationInAliasesChain(symbol, true) ??
          getJsDocDeprecation(signature) ??
          getJsDocDeprecation(aliasedSymbol)
        );
      }
      return (
        searchForDeprecationInAliasesChain(
          symbol,
          // Here we're working with a function declaration or method.
          // Both can have 1 or more overloads, each overload creates one
          // ts.Declaration which is placed in symbol.declarations.
          //
          // Imagine the following code:
          //
          // function foo(): void
          // /** @deprecated Some Reason */
          // function foo(arg: string): void
          // function foo(arg?: string): void {}
          //
          // foo()    // <- foo is our symbol
          //
          // If we call getJsDocDeprecation(checker.getAliasedSymbol(symbol)),
          // we get 'Some Reason', but after all, we are calling foo with
          // a signature that is not deprecated!
          // It works this way because symbol.getJsDocTags returns tags from
          // all symbol declarations combined into one array. And AFAIK there is
          // no publicly exported TS function that can tell us if a particular
          // declaration is deprecated or not.
          //
          // So, in case of function and method declarations, we don't check original
          // aliased symbol, but rely on the getJsDocDeprecation(signature) call below.
          false,
        ) ?? getJsDocDeprecation(signature)
      );
    }

getJSXAttributeDeprecation(openingElement: TSESTree.JSXOpeningElement, propertyName: string): string | undefined

Parameters:

  • openingElement TSESTree.JSXOpeningElement
  • propertyName string

Returns: string | undefined

Calls:

  • nullThrows (from ../util)
  • services.getContextualType
  • contextualType.getProperty
  • getJsDocDeprecation
Code
function getJSXAttributeDeprecation(
      openingElement: TSESTree.JSXOpeningElement,
      propertyName: string,
    ): string | undefined {
      const contextualType = nullThrows(
        services.getContextualType(
          openingElement.name as unknown as TSESTree.Expression,
        ),
        'Expected JSX opening element name to have contextualType',
      );

      const symbol = contextualType.getProperty(propertyName);

      return getJsDocDeprecation(symbol);
    }

getDeprecationReason(node: IdentifierLike): string | undefined

Parameters:

  • node IdentifierLike

Returns: string | undefined

Calls:

  • getCallLikeNode
  • getCallLikeDeprecation
  • getJSXAttributeDeprecation
  • services .getTypeAtLocation(node.parent.parent) .getProperty
  • services.getSymbolAtLocation
  • checker.getShorthandAssignmentValueSymbol
  • searchForDeprecationInAliasesChain
  • getJsDocDeprecation
Code
function getDeprecationReason(node: IdentifierLike): string | undefined {
      const callLikeNode = getCallLikeNode(node);
      if (callLikeNode) {
        return getCallLikeDeprecation(callLikeNode);
      }

      if (
        node.parent.type === AST_NODE_TYPES.JSXAttribute &&
        node.type !== AST_NODE_TYPES.Super
      ) {
        return getJSXAttributeDeprecation(node.parent.parent, node.name);
      }

      if (
        node.parent.type === AST_NODE_TYPES.Property &&
        node.type !== AST_NODE_TYPES.Super
      ) {
        const property = services
          .getTypeAtLocation(node.parent.parent)
          .getProperty(node.name);
        const propertySymbol = services.getSymbolAtLocation(node);
        const valueSymbol = checker.getShorthandAssignmentValueSymbol(
          propertySymbol?.valueDeclaration,
        );
        return (
          searchForDeprecationInAliasesChain(propertySymbol, true) ??
          getJsDocDeprecation(property) ??
          getJsDocDeprecation(propertySymbol) ??
          searchForDeprecationInAliasesChain(valueSymbol, true)
        );
      }

      return searchForDeprecationInAliasesChain(
        services.getSymbolAtLocation(node),
        true,
      );
    }

checkIdentifier(node: IdentifierLike): void

Parameters:

  • node IdentifierLike

Returns: void

Calls:

  • isDeclaration
  • isInsideImport
  • getDeprecationReason
  • services.getTypeAtLocation
  • typeMatchesSomeSpecifier (from ../util)
  • valueMatchesSomeSpecifier (from ../util)
  • getReportedNodeName
  • context.report
Code
function checkIdentifier(node: IdentifierLike): void {
      if (isDeclaration(node) || isInsideImport(node)) {
        return;
      }

      const reason = getDeprecationReason(node);

      if (reason == null) {
        return;
      }

      const type = services.getTypeAtLocation(node);
      if (
        typeMatchesSomeSpecifier(type, allow, services.program) ||
        valueMatchesSomeSpecifier(node, allow, services.program, type)
      ) {
        return;
      }

      const name = getReportedNodeName(node);

      context.report({
        ...(reason
          ? {
              messageId: 'deprecatedWithReason',
              data: { name, reason },
            }
          : {
              messageId: 'deprecated',
              data: { name },
            }),
        node,
      });
    }

checkMemberExpression(node: TSESTree.MemberExpression): void

Parameters:

  • node TSESTree.MemberExpression

Returns: void

Calls:

  • services.getTypeAtLocation
  • propertyType.isLiteral
  • propertyType.isStringLiteral
  • String
  • objectType.getProperty
  • getJsDocDeprecation
  • typeMatchesSomeSpecifier (from ../util)
  • context.report
Code
function checkMemberExpression(node: TSESTree.MemberExpression): void {
      if (!node.computed) {
        return;
      }

      const propertyType = services.getTypeAtLocation(node.property);

      if (propertyType.isLiteral()) {
        const objectType = services.getTypeAtLocation(node.object);

        const propertyName = propertyType.isStringLiteral()
          ? propertyType.value
          : // eslint-disable-next-line @typescript-eslint/no-base-to-string
            String(propertyType.value);

        const property = objectType.getProperty(propertyName);

        const reason = getJsDocDeprecation(property);
        if (reason == null) {
          return;
        }

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

        context.report({
          ...(reason
            ? {
                messageId: 'deprecatedWithReason',
                data: { name: propertyName, reason },
              }
            : {
                messageId: 'deprecated',
                data: { name: propertyName },
              }),
          node: node.property,
        });
      }
    }

Type Aliases

IdentifierLike

type IdentifierLike = | TSESTree.Identifier
  | TSESTree.JSXIdentifier
  | TSESTree.PrivateIdentifier
  | TSESTree.Super;

MessageIds

type MessageIds = 'deprecated' | 'deprecatedWithReason';

Options

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

CallLikeNode

type CallLikeNode = | TSESTree.CallExpression
      | TSESTree.JSXOpeningElement
      | TSESTree.NewExpression
      | TSESTree.TaggedTemplateExpression;

Generated by Syntax Scribe