eslint-plugin-deprecation
Version:
ESLint rule that reports usage of deprecated code
279 lines • 11.3 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
/**
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
const utils_1 = require("@typescript-eslint/utils");
const ts_api_utils_1 = require("ts-api-utils");
const ts = require("typescript");
const stringifyJSDocTagInfoText_1 = require("../utils/stringifyJSDocTagInfoText");
const createRule = utils_1.ESLintUtils.RuleCreator(() => 'https://github.com/gund/eslint-plugin-deprecation');
exports.default = createRule({
name: 'deprecation',
meta: {
type: 'problem',
docs: {
description: 'Do not use deprecated APIs.',
requiresTypeChecking: true,
},
messages: {
deprecated: `'{{name}}' is deprecated. {{reason}}`,
},
schema: [],
},
defaultOptions: [],
create(context) {
const services = utils_1.ESLintUtils.getParserServices(context);
if (!services.program) {
throw new Error('TypeScript is required for this rule: https://github.com/gund/eslint-plugin-deprecation#prerequisites');
}
const identifierRule = createRuleForIdentifier(context);
return {
Identifier: identifierRule,
JSXIdentifier: identifierRule,
};
},
});
function createRuleForIdentifier(context) {
return function identifierRule(id) {
// Don't consider deprecations in certain cases:
var _a;
// - On JSX closing elements (only flag the opening element)
const isClosingElement = id.type === 'JSXIdentifier' && ((_a = id.parent) === null || _a === void 0 ? void 0 : _a.type) === 'JSXClosingElement';
if (isClosingElement) {
return;
}
// - At the spot where something is declared
const isIdDeclaration = (id.type === 'Identifier' || id.type === 'JSXIdentifier') &&
isDeclaration(id, context);
if (isIdDeclaration) {
return;
}
// - Inside an import
const isInsideImport = context
.getAncestors()
.some((anc) => anc.type.includes('Import'));
if (isInsideImport) {
return;
}
const services = utils_1.ESLintUtils.getParserServices(context);
const deprecation = getDeprecation(id, services, context);
if (deprecation) {
context.report({
node: id,
messageId: 'deprecated',
data: {
name: id.name,
reason: deprecation.reason,
},
});
}
};
}
function getParent(context) {
const ancestors = context.getAncestors();
return ancestors.length > 0 ? ancestors[ancestors.length - 1] : undefined;
}
// Unfortunately need to keep some state because identifiers like foo in
// `const { foo } = bar` will be processed twice.
let lastProcessedDuplicateName;
function isDeclaration(id, context) {
var _a, _b;
const parent = getParent(context);
switch (parent === null || parent === void 0 ? void 0 : parent.type) {
case 'TSEnumDeclaration':
case 'TSInterfaceDeclaration':
case 'TSTypeAliasDeclaration':
case 'TSModuleDeclaration': // module or namespace
case 'TSPropertySignature': // property in an interface
case 'TSParameterProperty': // `constructor(public foo) {}`
// Type parameter: constraint and default are not identifiers
case 'TSTypeParameter': // T in `type Foo<T> = {}`
// Function/method: only the name and param names are identifiers
// (default values are assignment patterns or other types)
case 'FunctionDeclaration': // `function foo(param) {}`
case 'FunctionExpression': // `<something> = function foo(param) {}`
case 'TSDeclareFunction': // `function foo(param);` (no body)
case 'MethodDefinition': // methods, getters, setters, constructor
case 'TSEmptyBodyFunctionExpression': // method without implementation
case 'TSMethodSignature': // method in an interface
case 'ArrowFunctionExpression':
return true;
case 'ClassExpression': // `<something> = class Foo {}`
case 'ClassDeclaration': // `class Foo {}`
case 'VariableDeclarator': // `const foo` or `const foo = bar`
case 'TSEnumMember': // enum member declaration
// Prevent considering initializer, extends, or implements to be declaration
return parent.id === id;
//@ts-ignore
case 'ClassProperty':
//@ts-ignore
case 'PropertyDefinition':
// Prevent considering value to be a declaration
return parent.key === id;
case 'ArrayPattern': // `const [foo, bar] = baz`
// Array destructuring is truly a declaration on the left side
// (even if there's reassignment)
return id.type === 'Identifier' && parent.elements.includes(id);
case 'Property':
// no for ObjectExpression: `const foo = { bar: baz }`
if (((_a = parent.parent) === null || _a === void 0 ? void 0 : _a.type) === 'ObjectPattern') {
if (isShortHandProperty(parent)) {
// foo in `const { foo } = bar` will be processed twice, as key and
// value. Consider the second to be a declaration and the first not.
if (lastProcessedDuplicateName === id.name) {
lastProcessedDuplicateName = undefined;
return true;
}
else {
lastProcessedDuplicateName = id.name;
}
}
else {
// yes for bar, no for foo in `const { foo: bar } = baz`
return parent.value === id;
}
}
return false;
case 'AssignmentPattern':
if (((_b = parent.parent) === null || _b === void 0 ? void 0 : _b.type) === 'Property' &&
isShortHandProperty(parent.parent)) {
// Variant of the Property/ObjectPattern case above: foo in
// `const { foo = 3 } = bar` will also show up twice, but the second
// time will be as an AssignmentPattern left.
if (lastProcessedDuplicateName === id.name) {
lastProcessedDuplicateName = undefined;
return true;
}
}
// Yes: bar in `function foo(bar = 3) {}` and `const [bar = 3] = []`
// No: bar in `const { bar = 3 }`
return parent.left === id && !isShortHandProperty(parent.parent);
default:
return false;
}
}
function getDeprecation(id, services, context) {
const tc = services.program.getTypeChecker();
const callExpression = getCallExpression(context, id);
if (callExpression) {
const tsCallExpression = services.esTreeNodeToTSNodeMap.get(callExpression);
const signature = tc.getResolvedSignature(tsCallExpression);
if (signature) {
const deprecation = getJsDocDeprecation(signature.getJsDocTags());
if (deprecation) {
return deprecation;
}
}
}
const symbol = getSymbol(id, services, tc);
if (!symbol) {
return undefined;
}
if (callExpression && isFunction(symbol)) {
return undefined;
}
return getJsDocDeprecation(symbol.getJsDocTags());
}
function getSymbol(id, services, tc) {
let symbol;
const tsId = services.esTreeNodeToTSNodeMap.get(id);
const parent = tsId.parent;
if (parent.kind === ts.SyntaxKind.BindingElement) {
symbol = tc.getTypeAtLocation(parent.parent).getProperty(tsId.text);
}
else if ((isPropertyAssignment(parent) && parent.name === tsId) ||
(isShorthandPropertyAssignment(parent) &&
parent.name === tsId &&
isReassignmentTarget(tsId))) {
try {
symbol = tc.getPropertySymbolOfDestructuringAssignment(tsId);
}
catch (e) {
// we are in object literal, not destructuring
// no obvious easy way to check that in advance
symbol = tc.getSymbolAtLocation(tsId);
}
}
else {
symbol = tc.getSymbolAtLocation(tsId);
}
if (symbol && (symbol.flags & ts.SymbolFlags.Alias) !== 0) {
symbol = tc.getAliasedSymbol(symbol);
}
return symbol;
}
function getCallExpression(context, id) {
const ancestors = context.getAncestors();
let callee = id;
let parent = ancestors.length > 0 ? ancestors[ancestors.length - 1] : undefined;
if (parent && parent.type === 'MemberExpression' && parent.property === id) {
callee = parent;
parent = ancestors.length > 1 ? ancestors[ancestors.length - 2] : undefined;
}
if (isCallExpression(parent, callee)) {
return parent;
}
}
function isCallExpression(node, callee) {
if (node) {
if (node.type === 'NewExpression' || node.type === 'CallExpression') {
return node.callee === callee;
}
else if (node.type === 'TaggedTemplateExpression') {
return node.tag === callee;
}
else if (node.type === 'JSXOpeningElement') {
return node.name === callee;
}
}
return false;
}
function getJsDocDeprecation(tags) {
for (const tag of tags) {
if (tag.name === 'deprecated') {
return { reason: (0, stringifyJSDocTagInfoText_1.stringifyJSDocTagInfoText)(tag) };
}
}
return undefined;
}
function isFunction(symbol) {
const { declarations } = symbol;
if (declarations === undefined || declarations.length === 0) {
return false;
}
switch (declarations[0].kind) {
case ts.SyntaxKind.MethodDeclaration:
case ts.SyntaxKind.FunctionDeclaration:
case ts.SyntaxKind.FunctionExpression:
case ts.SyntaxKind.MethodSignature:
return true;
default:
return false;
}
}
function isReassignmentTarget(node) {
return ((0, ts_api_utils_1.getAccessKind)(node) & ts_api_utils_1.AccessKind.Write) !== 0;
}
function isPropertyAssignment(node) {
return node.kind === ts.SyntaxKind.PropertyAssignment;
}
function isShorthandPropertyAssignment(node) {
return node.kind === ts.SyntaxKind.ShorthandPropertyAssignment;
}
function isShortHandProperty(parent) {
return !!parent && parent.type === 'Property' && parent.shorthand;
}
//# sourceMappingURL=deprecation.js.map