@typescript-eslint/eslint-plugin
Version:
TypeScript plugin for ESLint
341 lines (340 loc) • 15.7 kB
JavaScript
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
const utils_1 = require("@typescript-eslint/utils");
const tsutils = __importStar(require("ts-api-utils"));
const ts = __importStar(require("typescript"));
const util_1 = require("../util");
exports.default = (0, util_1.createRule)({
name: 'no-deprecated',
meta: {
type: 'problem',
docs: {
description: 'Disallow using code marked as `@deprecated`',
recommended: 'strict',
requiresTypeChecking: true,
},
messages: {
deprecated: `\`{{name}}\` is deprecated.`,
deprecatedWithReason: `\`{{name}}\` is deprecated. {{reason}}`,
},
schema: [
{
type: 'object',
additionalProperties: false,
properties: {
allow: {
...util_1.typeOrValueSpecifiersSchema,
description: 'Type specifiers that can be allowed.',
},
},
},
],
},
defaultOptions: [
{
allow: [],
},
],
create(context, [options]) {
const { jsDocParsingMode } = context.parserOptions;
const allow = options.allow;
if (jsDocParsingMode === 'none' || jsDocParsingMode === 'type-info') {
throw new Error(`Cannot be used with jsDocParsingMode: '${jsDocParsingMode}'.`);
}
const services = (0, util_1.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, checkDeprecationsOfAliasedSymbol) {
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 = symbol.getDeclarations() && checker.getImmediateAliasedSymbol(symbol);
if (!immediateAliasedSymbol) {
break;
}
symbol = immediateAliasedSymbol;
if (checkDeprecationsOfAliasedSymbol && symbol === targetSymbol) {
return getJsDocDeprecation(symbol);
}
}
return undefined;
}
function isDeclaration(node) {
const { parent } = node;
switch (parent.type) {
case utils_1.AST_NODE_TYPES.ArrayPattern:
return parent.elements.includes(node);
case utils_1.AST_NODE_TYPES.ClassExpression:
case utils_1.AST_NODE_TYPES.ClassDeclaration:
case utils_1.AST_NODE_TYPES.VariableDeclarator:
case utils_1.AST_NODE_TYPES.TSEnumMember:
return parent.id === node;
case utils_1.AST_NODE_TYPES.MethodDefinition:
case utils_1.AST_NODE_TYPES.PropertyDefinition:
case utils_1.AST_NODE_TYPES.AccessorProperty:
return parent.key === node;
case utils_1.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.shorthand && parent.value === node) {
return parent.parent.type === utils_1.AST_NODE_TYPES.ObjectPattern;
}
if (parent.value === node) {
return false;
}
return parent.parent.type === utils_1.AST_NODE_TYPES.ObjectExpression;
case utils_1.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 utils_1.AST_NODE_TYPES.ArrowFunctionExpression:
case utils_1.AST_NODE_TYPES.FunctionDeclaration:
case utils_1.AST_NODE_TYPES.FunctionExpression:
case utils_1.AST_NODE_TYPES.TSDeclareFunction:
case utils_1.AST_NODE_TYPES.TSEmptyBodyFunctionExpression:
case utils_1.AST_NODE_TYPES.TSEnumDeclaration:
case utils_1.AST_NODE_TYPES.TSInterfaceDeclaration:
case utils_1.AST_NODE_TYPES.TSMethodSignature:
case utils_1.AST_NODE_TYPES.TSModuleDeclaration:
case utils_1.AST_NODE_TYPES.TSParameterProperty:
case utils_1.AST_NODE_TYPES.TSPropertySignature:
case utils_1.AST_NODE_TYPES.TSTypeAliasDeclaration:
case utils_1.AST_NODE_TYPES.TSTypeParameter:
return true;
default:
return false;
}
}
function isInsideExportOrImport(node) {
let current = node;
while (true) {
switch (current.type) {
case utils_1.AST_NODE_TYPES.ExportAllDeclaration:
case utils_1.AST_NODE_TYPES.ExportNamedDeclaration:
case utils_1.AST_NODE_TYPES.ImportDeclaration:
return true;
case utils_1.AST_NODE_TYPES.ArrowFunctionExpression:
case utils_1.AST_NODE_TYPES.BlockStatement:
case utils_1.AST_NODE_TYPES.ClassDeclaration:
case utils_1.AST_NODE_TYPES.TSInterfaceDeclaration:
case utils_1.AST_NODE_TYPES.FunctionDeclaration:
case utils_1.AST_NODE_TYPES.FunctionExpression:
case utils_1.AST_NODE_TYPES.Program:
case utils_1.AST_NODE_TYPES.TSUnionType:
case utils_1.AST_NODE_TYPES.VariableDeclarator:
return false;
default:
current = current.parent;
}
}
}
function getJsDocDeprecation(symbol) {
let jsDocTags;
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) : '';
}
function isNodeCalleeOfParent(node) {
switch (node.parent?.type) {
case utils_1.AST_NODE_TYPES.NewExpression:
case utils_1.AST_NODE_TYPES.CallExpression:
return node.parent.callee === node;
case utils_1.AST_NODE_TYPES.TaggedTemplateExpression:
return node.parent.tag === node;
case utils_1.AST_NODE_TYPES.JSXOpeningElement:
return node.parent.name === node;
default:
return false;
}
}
function getCallLikeNode(node) {
let callee = node;
while (callee.parent?.type === utils_1.AST_NODE_TYPES.MemberExpression &&
callee.parent.property === callee) {
callee = callee.parent;
}
return isNodeCalleeOfParent(callee) ? callee : undefined;
}
function getCallLikeDeprecation(node) {
const tsNode = services.esTreeNodeToTSNodeMap.get(node.parent);
// If the node is a direct function call, we look for its signature.
const signature = (0, util_1.nullThrows)(checker.getResolvedSignature(tsNode), '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, propertyName) {
const tsNode = services.esTreeNodeToTSNodeMap.get(openingElement.name);
const contextualType = (0, util_1.nullThrows)(checker.getContextualType(tsNode), 'Expected JSX opening element name to have contextualType');
const symbol = contextualType.getProperty(propertyName);
return getJsDocDeprecation(symbol);
}
function getDeprecationReason(node) {
const callLikeNode = getCallLikeNode(node);
if (callLikeNode) {
return getCallLikeDeprecation(callLikeNode);
}
if (node.parent.type === utils_1.AST_NODE_TYPES.JSXAttribute &&
node.type !== utils_1.AST_NODE_TYPES.Super) {
return getJSXAttributeDeprecation(node.parent.parent, node.name);
}
if (node.parent.type === utils_1.AST_NODE_TYPES.Property &&
node.type !== utils_1.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 (getJsDocDeprecation(property) ??
getJsDocDeprecation(propertySymbol) ??
getJsDocDeprecation(valueSymbol));
}
return searchForDeprecationInAliasesChain(services.getSymbolAtLocation(node), true);
}
function checkIdentifier(node) {
if (isDeclaration(node) || isInsideExportOrImport(node)) {
return;
}
const reason = getDeprecationReason(node);
if (reason == null) {
return;
}
const type = services.getTypeAtLocation(node);
if ((0, util_1.typeMatchesSomeSpecifier)(type, allow, services.program)) {
return;
}
const name = getReportedNodeName(node);
context.report({
...(reason
? {
messageId: 'deprecatedWithReason',
data: { name, reason },
}
: {
messageId: 'deprecated',
data: { name },
}),
node,
});
}
return {
Identifier: checkIdentifier,
JSXIdentifier(node) {
if (node.parent.type !== utils_1.AST_NODE_TYPES.JSXClosingElement) {
checkIdentifier(node);
}
},
PrivateIdentifier: checkIdentifier,
Super: checkIdentifier,
};
},
});
function getReportedNodeName(node) {
if (node.type === utils_1.AST_NODE_TYPES.Super) {
return 'super';
}
if (node.type === utils_1.AST_NODE_TYPES.PrivateIdentifier) {
return `#${node.name}`;
}
return node.name;
}