eslint-codemod-utils
Version:
A collection of AST helper functions for more complex ESLint rule fixes.
139 lines (138 loc) • 5.61 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.removeImportSpecifier = exports.insertImportDeclaration = exports.insertImportSpecifier = exports.hasImportSpecifier = exports.hasImportDeclaration = exports.hasJSXChild = exports.hasJSXAttribute = void 0;
const nodes_1 = require("../nodes");
const is_node_of_type_1 = require("./is-node-of-type");
const types_1 = require("@typescript-eslint/types");
function hasJSXAttribute(node, attributeName) {
if (!node.openingElement)
return false;
if (!node.openingElement.attributes.length)
return false;
return node.openingElement.attributes.some((attr) => (0, is_node_of_type_1.isNodeOfType)(attr, types_1.AST_NODE_TYPES.JSXAttribute) &&
attr.name.name === attributeName);
}
exports.hasJSXAttribute = hasJSXAttribute;
function hasJSXChild(node, childIdentifier) {
const jsxIdentifierMatch = (0, is_node_of_type_1.isNodeOfType)(node.openingElement.name, types_1.AST_NODE_TYPES.JSXIdentifier) &&
node.openingElement.name.name &&
node.openingElement.name.name === childIdentifier;
return (jsxIdentifierMatch ||
Boolean(node.children &&
node.children
.filter((child) => (0, is_node_of_type_1.isNodeOfType)(child, types_1.AST_NODE_TYPES.JSXElement))
.find((child) => hasJSXChild(child, childIdentifier))));
}
exports.hasJSXChild = hasJSXChild;
/**
* Whether a declaration does or does not include a specified source.
*
* @param declaration
* @param source
* @returns
*/
function hasImportDeclaration(declaration, source) {
return declaration.source.value === source;
}
exports.hasImportDeclaration = hasImportDeclaration;
/**
*
* @param declaration
* @param specifierId
*/
function hasImportSpecifier(declaration, importName) {
if (importName === 'default') {
return declaration.specifiers.some((spec) => (0, is_node_of_type_1.isNodeOfType)(spec, types_1.AST_NODE_TYPES.ImportDefaultSpecifier));
}
return declaration.specifiers
.filter((spec) => (0, is_node_of_type_1.isNodeOfType)(spec, types_1.AST_NODE_TYPES.ImportSpecifier))
.some((node) => node.imported.name === importName);
}
exports.hasImportSpecifier = hasImportSpecifier;
/**
* Appends or adds an import specifier to an existing import declaration.
*
* Does not validate whether the insertion is already present.
*
* @param declaration
* @param importName
* @param specifierAlias
* @returns {StringableASTNode<ImportDeclaration>}
*/
function insertImportSpecifier(declaration, importName, specifierAlias) {
if (importName === 'default' && !specifierAlias) {
throw new Error('A specifier name must be provided when inserting the default import.');
}
const id = (0, nodes_1.identifier)(importName);
const newSpecifier = importName === 'default'
? // Narrowed above — `specifierAlias` is guaranteed non-undefined here.
(0, nodes_1.importDefaultSpecifier)({
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
local: (0, nodes_1.identifier)(specifierAlias),
})
: (0, nodes_1.importSpecifier)({
imported: (0, nodes_1.identifier)(importName),
local: specifierAlias ? (0, nodes_1.identifier)(specifierAlias) : id,
});
// `.concat` on a heterogenous specifier array widens to the union — build
// the array explicitly to keep types consistent with `WithoutType<…>`.
return (0, nodes_1.importDeclaration)({
...declaration,
specifiers: [...declaration.specifiers, newSpecifier],
});
}
exports.insertImportSpecifier = insertImportSpecifier;
/**
* @example
* ```tsx
* insertImportDeclaration('source', ['specifier', 'second'])
*
* // produces
* import { specifier, second } from 'source'
* ```
*
* @example
* ```tsx
* * insertImportDeclaration('source', ['specifier', { imported: 'second', local: 'other' }])
*
* // produces
* import { specifier, second as other } from 'source'
* ```
*/
function insertImportDeclaration(source, specifiers) {
return (0, nodes_1.importDeclaration)({
source: (0, nodes_1.literal)(source),
specifiers: specifiers.map((spec) => {
return spec === 'default'
? (0, nodes_1.importDefaultSpecifier)({
local: (0, nodes_1.identifier)('__default'),
})
: (0, nodes_1.importSpecifier)({
imported: typeof spec === 'string'
? (0, nodes_1.identifier)(spec)
: (0, nodes_1.identifier)(spec.imported),
local: typeof spec === 'string'
? (0, nodes_1.identifier)(spec)
: (0, nodes_1.identifier)(spec.local),
});
}),
});
}
exports.insertImportDeclaration = insertImportDeclaration;
/**
* Removes an import specifier to an existing import declaration.
*
* @param declaration
* @param importName
* @returns {StringableASTNode<ESTree.ImportDeclaration>}
*/
function removeImportSpecifier(declaration, importName) {
return (0, nodes_1.importDeclaration)({
...declaration,
specifiers: declaration.specifiers.filter((spec) => importName === 'default'
? spec.type !== 'ImportDefaultSpecifier'
: !((0, is_node_of_type_1.isNodeOfType)(spec, types_1.AST_NODE_TYPES.ImportSpecifier) &&
spec.imported.name === importName)),
});
}
exports.removeImportSpecifier = removeImportSpecifier;