import { ASTPath, CallExpression, JSCodeshift, MemberExpression, StringLiteral } from 'jscodeshift'; import { ComponentType, Details, UIConfig } from '../types/runner'; import { Operation, RegisterAPI } from '../types/transformer'; import { AUTO_MODIFIED_COMMENT } from '../constants'; import { addComment, flexCustomizationAST, getRegisterPropsAST } from './ast'; import { merge } from './helper'; /** * Creates and returns the `registerComponent` and `registerWrapper` MemberExpression AST node * along with the arguments to be passed to the final CallExpression and the details to be updated * in UI Config snippet * @param operation add/replace/wrapper * @param componentKey Unique key for the custom component * @param customComponentHint Dynamic Component * @param args Arguments passed to the Dynamic Component APIs * @param flexVariable Flex instance variable * @param j JSCodeshift core */ export const toRegisterComponent = ( operation: string, componentKey: string, customComponentHint: string, args: any[], flexVariable: string, j: JSCodeshift, ): { memberExpression?: MemberExpression; args?: any[]; details?: Details; } => { const registerProperties = [ { name: 'customComponent', value: componentKey, }, { name: 'componentHint', value: customComponentHint, }, ]; if (operation !== Operation.Wrap) { registerProperties.push({ name: 'operationHint', value: operation, }); } const registerPropsExpr = getRegisterPropsAST(registerProperties, j); const finalArgs = [registerPropsExpr, ...args]; const registerMemberExpression = flexCustomizationAST( j, operation === Operation.Wrap ? RegisterAPI.Wrapper : RegisterAPI.Content, flexVariable, ); return { memberExpression: registerMemberExpression, args: finalArgs, details: { type: ComponentType.Custom, custom_component: componentKey, }, }; }; /** * Creates and returns the `registerRemove` MemberExpression AST node * along with the arguments to be passed to the final CallExpression and the details to be updated * in UI Config snippet * @param componentKey Unique key for the custom component * @param customComponentHint Dynamic Component * @param args Arguments passed to the Dynamic Component APIs * @param flexVariable Flex instance variable * @param j JSCodeshift core */ export const toRegisterRemove = ( componentKey: string, customComponentHint: string, args: any[], flexVariable: string, j: JSCodeshift, ): { memberExpression?: MemberExpression; args?: any[]; details?: Details; } => { const [key, options] = args as [StringLiteral, ...any]; if (!key || !key?.value) { return {}; } const registerPropsExpr = getRegisterPropsAST( [ { name: 'ref', value: componentKey, }, { name: 'keyHint', value: key.value, }, { name: 'componentHint', value: customComponentHint, }, ], j, ); const finalArgs = [registerPropsExpr, ...(options ? [options] : [])]; const registerMemberExpression = flexCustomizationAST(j, RegisterAPI.Remove, flexVariable); return { memberExpression: registerMemberExpression, args: finalArgs, details: { key: key.value, ref: componentKey, type: ComponentType.Custom, }, }; }; /** * Creates and updates UIConfig snippet * @param uiConfig Initial UIConfig snippet * @param componentHint Dynamic Component * @param operation Dynamic Component API * @param details Properties of the custom component to be updated in the UI Config snippet */ export const updateUIConfig = ( uiConfig: UIConfig, componentHint: string, operation: Operation, details?: Details, ): void => { uiConfig.components = merge(uiConfig.components, { [componentHint]: { operations: [ { operation, details: { ...(details || {}), }, }, ], }, }); }; /** * Replaces the CallExpression node paths with the new Register MemeberExpression and updated arguments * @param operation Dynamic Component API * @param uiConfig UI Config Snippet * @param componentKey Unique key for the custom component * @param componentHint Dynamic Component name * @param orginalArgs Original arguments passed to Dynamic component API * @param flexVariable Flex instance variable * @param expr CallExpression whose node paths will be replaced * @param j JSCodeshift * @param shouldAddComment Flag to add auto-generated comment */ export const modifyAST = ( operation: Operation, uiConfig: UIConfig, componentKey: string, componentHint: string, orginalArgs: any[], flexVariable: string, expr: ASTPath, j: JSCodeshift, shouldAddComment = true, ): void => { let memberExpression: MemberExpression | undefined; let finalArgs: any[] | undefined; let details: Details | undefined; switch (operation) { case Operation.Add: case Operation.Replace: case Operation.Wrap: ({ memberExpression, args: finalArgs, details, } = toRegisterComponent(operation, componentKey, componentHint, orginalArgs, flexVariable, j)); break; case Operation.Remove: ({ memberExpression, args: finalArgs, details, } = toRegisterRemove(componentKey, componentHint, orginalArgs, flexVariable, j)); break; default: throw new Error('Unable to identify the Dynamic Component operation'); } if (memberExpression && finalArgs) { expr.replace(j.callExpression(memberExpression, finalArgs)); if (shouldAddComment) { addComment(j, expr, AUTO_MODIFIED_COMMENT); } updateUIConfig(uiConfig, componentHint, operation, details); } };