UNPKG

eslint-codemod-utils

Version:

A collection of AST helper functions for more complex ESLint rule fixes.

123 lines (122 loc) 4.26 kB
import { identifier, importDeclaration, importDefaultSpecifier, importSpecifier, literal, } from '../nodes'; import { isNodeOfType } from './is-node-of-type'; export function hasJSXAttribute(node, attributeName) { if (!node.openingElement) return false; if (!node.openingElement.attributes.length) return false; return node.openingElement.attributes.some((attr) => isNodeOfType(attr, 'JSXAttribute') && attr.name.name === attributeName); } export function hasJSXChild(node, childIdentifier) { const jsxIdentifierMatch = isNodeOfType(node.openingElement.name, 'JSXIdentifier') && node.openingElement.name.name && node.openingElement.name.name === childIdentifier; return (jsxIdentifierMatch || Boolean(node.children && node.children .filter((child) => isNodeOfType(child, 'JSXElement')) .find((child) => hasJSXChild(child, childIdentifier)))); } /** * Whether a declaration does or does not include a specified source. * * @param declaration * @param source * @returns */ export function hasImportDeclaration(declaration, source) { return declaration.source.value === source; } /** * * @param declaration * @param specifierId */ export function hasImportSpecifier(declaration, importName) { if (importName === 'default') { return declaration.specifiers.some((spec) => isNodeOfType(spec, 'ImportDefaultSpecifier')); } return declaration.specifiers .filter((spec) => isNodeOfType(spec, 'ImportSpecifier')) .some((node) => node.imported.name === importName); } /** * 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>} */ export 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 = identifier(importName); return importDeclaration({ ...declaration, specifiers: declaration.specifiers.concat(importName === 'default' ? importDefaultSpecifier({ // @ts-expect-error no undefined on identifier local: identifier(specifierAlias), }) : importSpecifier({ imported: identifier(importName), local: specifierAlias ? identifier(specifierAlias) : id, })), }); } /** * @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' * ``` */ export function insertImportDeclaration(source, specifiers) { return importDeclaration({ source: literal(source), specifiers: specifiers.map((spec) => { return spec === 'default' ? importDefaultSpecifier({ local: identifier('__default'), }) : importSpecifier({ imported: typeof spec === 'string' ? identifier(spec) : identifier(spec.imported), local: typeof spec === 'string' ? identifier(spec) : identifier(spec.local), }); }), }); } /** * Removes an import specifier to an existing import declaration. * * @param declaration * @param importName * @returns {StringableASTNode<ESTree.ImportDeclaration>} */ export function removeImportSpecifier(declaration, importName) { return importDeclaration({ ...declaration, specifiers: declaration.specifiers.filter((spec) => importName === 'default' ? spec.type !== 'ImportDefaultSpecifier' : !(isNodeOfType(spec, 'ImportSpecifier') && spec.imported.name === importName)), }); }