@exadel/eslint-plugin-esl
Version:
Helper ESLint rules to find and migrate ESL (@exadel/esl) library deprecations
114 lines (113 loc) • 4.06 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.buildRule = buildRule;
const ast_utils_1 = require("./ast.utils");
const meta = {
type: 'suggestion',
docs: {
description: 'replace deprecated aliases',
recommended: true,
},
fixable: 'code'
};
/** Builds deprecation rule from {@link ESLintDeprecationCfg} object */
function buildRule(configs) {
configs = Array.isArray(configs) ? configs : [configs];
const create = (context) => ({
ImportSpecifier(node) {
const importedValue = node.imported;
if (importedValue.type !== 'Identifier')
return null;
configs.forEach((config) => {
if (importedValue.name === config.deprecation) {
context.report({
node,
message: `[ESL Lint]: Deprecated alias ${config.deprecation} for ${config.alias}`,
fix: buildFixer(node, context, config.alias)
});
}
});
return null;
}
});
return { meta, create };
}
/**
* Creates fixe-list for the node of incorrect import
* @param context - AST tree object
* @param node - import node to process
* @param alias - current name
*/
function buildFixer(node, context, alias) {
return (fixer) => {
const ranges = getIdentifierRanges(node, context);
return ranges.map((range) => fixer.replaceTextRange(range, alias));
};
}
/**
* Find usage's ranges of the deprecated alias
* @param context - AST tree object
* @param importNode - import node to process
*/
// eslint-disable-next-line sonarjs/cognitive-complexity
function getIdentifierRanges(importNode, context) {
var _a;
const root = (0, ast_utils_1.findRoot)(importNode);
if (importNode.imported.type !== 'Identifier' || !root)
return [];
const { name } = importNode.imported;
const identifiers = (0, ast_utils_1.findAllBy)(context, root, { type: 'Identifier', name });
const overrides = [];
const occurrences = new Set();
for (const idNode of identifiers) {
const { parent } = idNode;
if ((parent === null || parent === void 0 ? void 0 : parent.type) === 'MemberExpression') {
if (parent.object.name === name)
occurrences.add(parent.object);
}
else if ((parent === null || parent === void 0 ? void 0 : parent.type) === 'VariableDeclarator') {
overrides.push(parent);
}
else {
occurrences.add(idNode);
}
}
for (const declaration of overrides) {
const scope = getScopeNode(declaration);
if (!scope)
continue;
const nestedNodes = (0, ast_utils_1.findAllBy)(context, scope, { type: 'Identifier', name });
for (const node of nestedNodes) {
if (((_a = node.parent) === null || _a === void 0 ? void 0 : _a.type) !== 'ImportSpecifier') {
occurrences.delete(node);
}
}
const initExpNodes = (0, ast_utils_1.findAllBy)(context, declaration.init, { type: 'Identifier', name });
for (const node of initExpNodes) {
occurrences.add(node);
}
}
return getRanges(occurrences);
}
function getScopeNode(declaration) {
var _a;
let node = declaration.parent;
if (!node)
return null;
const isBlockScoped = node.kind && (node.kind === 'const' || node.kind === 'let');
while (node.parent) {
node = node.parent;
if (node.type === 'BlockStatement' && (isBlockScoped || ((_a = node.parent) === null || _a === void 0 ? void 0 : _a.type) === 'FunctionExpression'))
return node;
}
return node;
}
function getRanges(nodes) {
const uniqNodes = [];
for (const node of nodes) {
if (!uniqNodes.some((item) => String(item.range) === String(node.range))) {
uniqNodes.push(node);
}
}
return uniqNodes.map((node) => node.range);
}