import { Transform , Core , JSCodeshift , ImportSpecifier } from "jscodeshift" ; import { camel , pascal } from "change-case" ; /**
 * The default name of the icon component provided by Font Awesome React. It's
 * possible it's aliased or wrapped in another component which can be configured
 * via the `componentName` option.
 */ const DEFAULT_COMPONENT = "FontAwesomeIcon" ; /**
 * The default name of the icon prop that the name (or value) of the icon is
 * passed to. By default, Font Awesome React exposes this as the `icon` prop
 * but it's possible it's wrapped or passed to another component. This can be
 * configured via the `propName` option.
 */ const DEFAULT_PROP = "icon" ; /**
 * The default package types to use. Font Awesome provides both free and pro
 * icons. This can be configured via the `type` option.
 */ const DEFAULT_PACKAGE_KEY = "free" ; const BRAND_ICONS = "@fortawesome/free-brands-svg-icons" ; const PACKAGES = { pro : { fab : BRAND_ICONS , fal : "@fortawesome/pro-light-svg-icons" , far : "@fortawesome/pro-regular-svg-icons" , fas : "@fortawesome/pro-solid-svg-icons" } , free : { fab : BRAND_ICONS , far : "@fortawesome/free-regular-svg-icons" , fas : "@fortawesome/free-solid-svg-icons" } } ; type FontStyle = keyof typeof PACKAGES [ "pro" ] ; /**
 * Options accepted by this transform to customize the behavior.
 */ export interface TransformOptions { default componentName ? : string ; default propName ? : string ; default type ? : "free" | "pro" ; } example param icon const transformName = ( icon ) => camel ( `fa-${ icon }` ) ; param componentName param root param j const findComponents = ( componentName , root , j ) => { const IS_DOT_NOTATION = componentName . indexOf ( "." ) !== - 1 ; if ( IS_DOT_NOTATION ) { const [ objectName , propertyName ] = componentName . split ( "." ) ; return root . find ( j . JSXElement , { openingElement : { name : { object : { name : objectName } , property : { name : propertyName } } } } ) ; } else { return root . findJSXElements ( componentName ) ; } } ; param firstImportSpecifier param secondImportSpecifier const sortImportSpecifiers = ( { imported : { name : firstName } } , { imported : { name : secondName } } ) => { if ( firstName < secondName ) return - 1 ; if ( firstName > secondName ) return 1 ; return 0 ; } ; /**
 * The custom transform adhering to the jscodeshift API.
 */ const transform = ( file , api , { componentName = DEFAULT_COMPONENT , propName = DEFAULT_PROP , type = DEFAULT_PACKAGE_KEY } ) => { const j = api . jscodeshift ; const root = j ( file . source ) ; const FIRST_IMPORT = root . find ( j . ImportDeclaration ) . at ( 0 ) ; let hasModifications = false ; returns param iconName param fontStyle const updateImports = ( iconName , fontStyle = "fas" ) : string => { const pkg = PACKAGES [ type ] [ fontStyle ] ; const iconDefinition = transformName ( iconName ) ; let localIconDefinition = iconDefinition ; const duplicateIcons = root . find ( j . ImportDeclaration ) . filter ( imported => imported . node . source . value !== pkg ) . find ( j . ImportSpecifier , { imported : { type : "Identifier" , name : iconDefinition } } ) ; if ( duplicateIcons . size ( ) > 0 ) { localIconDefinition = `${ iconDefinition }${ pascal ( fontStyle ) }` ; } const existingImport = root . find ( j . ImportDeclaration ) . filter ( imported => imported . node . source . value === pkg ) . at ( 0 ) ; if ( existingImport . size ( ) > 0 ) { const existingSpecifiers = existingImport . find ( j . ImportSpecifier ) . nodes ( ) ; const specifiers = [ j . importSpecifier ( j . identifier ( iconDefinition ) , j . identifier ( localIconDefinition ) ) , ... existingSpecifiers ] . sort ( sortImportSpecifiers ) . filter ( ( specifier , index , self ) => self . findIndex ( otherSpecifier => otherSpecifier . imported . name === specifier . imported . name ) === index ) ; const updatedImport = j . importDeclaration ( specifiers , j . stringLiteral ( pkg ) , "value" ) ; existingImport . replaceWith ( updatedImport ) ; } else { FIRST_IMPORT . insertAfter ( j . importDeclaration ( [ j . importSpecifier ( j . identifier ( iconDefinition ) , j . identifier ( localIconDefinition ) ) ] , j . stringLiteral ( pkg ) , "value" ) ) ; } return localIconDefinition ; } ; const iconComponents = findComponents ( componentName , root , j ) ; iconComponents . find ( j . JSXAttribute , { name : { type : "JSXIdentifier" , name : propName } , value : { type : "JSXExpressionContainer" , expression : { type : "ArrayExpression" } } } ) . find ( j . ArrayExpression ) . filter ( nodePath => { const [ iconType , iconName ] = nodePath . node . elements ; if ( iconType . type !== "StringLiteral" || iconName . type !== "StringLiteral" ) { console . error ( `expected: icon={[StringLiteral, StringLiteral]}\nreceived: icon={[${ iconType . type }, ${ iconName . type }]}\nManually update: ${ file . path }` ) ; return false ; } else { return true ; } } ) . replaceWith ( nodePath => { const [ iconType , iconName ] = nodePath . node . elements ; if ( iconType . type !== "StringLiteral" || iconName . type !== "StringLiteral" ) { return ; } const iconDefinition = updateImports ( iconName . value , iconType . value as FontStyle ) ; const newNode = j . identifier ( iconDefinition ) ; hasModifications = true ; return newNode ; } ) ; iconComponents . find ( j . JSXAttribute , { name : { type : "JSXIdentifier" , name : propName } , value : { type : "StringLiteral" } } ) . find ( j . StringLiteral ) . replaceWith ( nodePath => { const { node } = nodePath ; const iconDefinition = updateImports ( node . value ) ; const newNode = j . jsxExpressionContainer ( j . identifier ( iconDefinition ) ) ; hasModifications = true ; return newNode ; } ) ; return hasModifications ? root . toSource ( ) : null ; } ; export default transform ;