📄 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¶
| 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:
contextany[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.getTypeCheckertsutils.isSymbolFlagSetgetJsDocDeprecationchecker.getAliasedSymbolsymbol.getDeclarationschecker.getImmediateAliasedSymbolparent.elements.includessymbol?.getJsDocTagsjsDocTags?.findts.displayPartsToStringisNodeCalleeOfParentservices.esTreeNodeToTSNodeMap.getnullThrows (from ../util)checker.getResolvedSignatureservices.getSymbolAtLocationsearchForDeprecationInAliasesChainservices.getContextualTypecontextualType.getPropertygetCallLikeNodegetCallLikeDeprecationgetJSXAttributeDeprecationservices .getTypeAtLocation(node.parent.parent) .getPropertychecker.getShorthandAssignmentValueSymbolisDeclarationisInsideImportgetDeprecationReasonservices.getTypeAtLocationtypeMatchesSomeSpecifier (from ../util)valueMatchesSomeSpecifier (from ../util)getReportedNodeNamecontext.reportpropertyType.isLiteralpropertyType.isStringLiteralStringobjectType.getPropertycheckIdentifier
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:
nodeIdentifierLike
Returns: string
Code
Internal helpers¶
Declared inside another function in this file.
searchForDeprecationInAliasesChain(symbol: ts.Symbol | undefined, checkDeprecationsOfAliasedSymbol: boolean): string | undefined¶
Parameters:
symbolts.Symbol | undefinedcheckDeprecationsOfAliasedSymbolboolean
Returns: string | undefined
Calls:
tsutils.isSymbolFlagSetgetJsDocDeprecationchecker.getAliasedSymbolsymbol.getDeclarationschecker.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:
nodeIdentifierLike
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:
nodeTSESTree.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:
symbolts.Signature | ts.Symbol | undefined
Returns: string | undefined
Calls:
symbol?.getJsDocTagsjsDocTags?.findts.displayPartsToString
Internal Comments:
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:
nodeTSESTree.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:
nodeTSESTree.Node
Returns: CallLikeNode | undefined
Calls:
isNodeCalleeOfParent
Code
getCallLikeDeprecation(node: CallLikeNode): string | undefined¶
Parameters:
nodeCallLikeNode
Returns: string | undefined
Calls:
services.esTreeNodeToTSNodeMap.getnullThrows (from ../util)checker.getResolvedSignatureservices.getSymbolAtLocationtsutils.isSymbolFlagSetchecker.getAliasedSymbolsearchForDeprecationInAliasesChaingetJsDocDeprecation
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:
openingElementTSESTree.JSXOpeningElementpropertyNamestring
Returns: string | undefined
Calls:
nullThrows (from ../util)services.getContextualTypecontextualType.getPropertygetJsDocDeprecation
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:
nodeIdentifierLike
Returns: string | undefined
Calls:
getCallLikeNodegetCallLikeDeprecationgetJSXAttributeDeprecationservices .getTypeAtLocation(node.parent.parent) .getPropertyservices.getSymbolAtLocationchecker.getShorthandAssignmentValueSymbolsearchForDeprecationInAliasesChaingetJsDocDeprecation
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:
nodeIdentifierLike
Returns: void
Calls:
isDeclarationisInsideImportgetDeprecationReasonservices.getTypeAtLocationtypeMatchesSomeSpecifier (from ../util)valueMatchesSomeSpecifier (from ../util)getReportedNodeNamecontext.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:
nodeTSESTree.MemberExpression
Returns: void
Calls:
services.getTypeAtLocationpropertyType.isLiteralpropertyType.isStringLiteralStringobjectType.getPropertygetJsDocDeprecationtypeMatchesSomeSpecifier (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¶
Options¶
CallLikeNode¶
type CallLikeNode = | TSESTree.CallExpression
| TSESTree.JSXOpeningElement
| TSESTree.NewExpression
| TSESTree.TaggedTemplateExpression;
Generated by Syntax Scribe