import type { API, ASTPath, Collection, FileInfo, ImportDeclaration, JSCodeshift, JSXAttribute, JSXMemberExpression, JSXOpeningElement, } from 'jscodeshift'; const TARGET_COMPONENTS = new Set([ 'TextLink', 'Menu', 'GNBList.Item', ]); const DESIGN_SYSTEM_PACKAGE = '@wishket/design-system'; const NEXT_LINK_PACKAGE = 'next/link'; const NEXT_LINK_LOCAL_NAME = 'Link'; export default function transformer(file: FileInfo, api: API): string { const j: JSCodeshift = api.jscodeshift; const root = j(file.source); const dsImport: Collection = root.find( j.ImportDeclaration, { source: { value: DESIGN_SYSTEM_PACKAGE }, }, ); if (dsImport.size() === 0) return file.source; let didChange = false; root .find(j.JSXOpeningElement) .forEach((path: ASTPath) => { const name = getJSXName(path.node); if (!name || !TARGET_COMPONENTS.has(name)) return; const attrs = path.node.attributes ?? []; const hasAs = attrs.some( (attr): attr is JSXAttribute => attr.type === 'JSXAttribute' && attr.name.type === 'JSXIdentifier' && attr.name.name === 'as', ); if (hasAs) return; const hasHref = attrs.some( (attr): attr is JSXAttribute => attr.type === 'JSXAttribute' && attr.name.type === 'JSXIdentifier' && attr.name.name === 'href', ); if (!hasHref) return; const asAttr = j.jsxAttribute( j.jsxIdentifier('as'), j.jsxExpressionContainer(j.identifier(NEXT_LINK_LOCAL_NAME)), ); path.node.attributes = [asAttr, ...attrs]; didChange = true; }); if (!didChange) return file.source; const hasNextLinkImport = root .find(j.ImportDeclaration, { source: { value: NEXT_LINK_PACKAGE }, }) .size() > 0; if (!hasNextLinkImport) { const newImport = j.importDeclaration( [j.importDefaultSpecifier(j.identifier(NEXT_LINK_LOCAL_NAME))], j.literal(NEXT_LINK_PACKAGE), ); root.get().node.program.body.unshift(newImport); } return root.toSource({ quote: 'single' }); } function getJSXName(node: JSXOpeningElement): string | null { const { name } = node; if (name.type === 'JSXIdentifier') return name.name; if (name.type === 'JSXMemberExpression') { return memberExpressionToString(name); } return null; } function memberExpressionToString( expr: JSXMemberExpression, ): string | null { const right = expr.property.name; if (expr.object.type === 'JSXIdentifier') { return `${expr.object.name}.${right}`; } if (expr.object.type === 'JSXMemberExpression') { const left = memberExpressionToString(expr.object); return left ? `${left}.${right}` : null; } return null; }