const { createMacro, MacroError } = require("babel-plugin-macros") const { addNamed } = require("@babel/helper-module-imports") const path = require("path") // `createMacro` is simply a function that ensures your macro is only // called in the context of a babel transpilation and will throw an // error with a helpful message if someone does not have babel-plugin-macros // configured correctly module.exports = createMacro(useMethod) function useMethod({ references, state, babel }) { // state is the second argument you're passed to a visitor in a // normal babel plugin. `babel` is the `babel-plugin-macros` module. // do whatever you like to the AST paths you find in `references` // read more below... const { default: defaultImport = [] } = references defaultImport.forEach(referencePath => { if (referencePath.parentPath.type != "CallExpression") { throw new MacroError( "useQuery should be called with a function", ) } const ourArguments = referencePath.parentPath.get("arguments") if (ourArguments.length < 1) { throw new MacroError( "There are not enough arguments", ) } else if (ourArguments.length > 3) { throw new MacroError( "There are too many arguments", ) } if (ourArguments[0].has("callee")) { throw new MacroError( "function used in useMethod can not be a function call", ) } else if (ourArguments.length > 1 && !ourArguments[1].isFunction()) { throw new MacroError( "useMethods 2nd argument must be a callable function", ) } else if (ourArguments.length > 2 && !ourArguments[2].isFunction()) { throw new MacroError( "useMethods 3rd argument must be a callable function", ) } let declPath = referencePath.parentPath.parentPath; let call = ourArguments[0].node let nop = babel.types.arrowFunctionExpression([babel.types.identifier("e")], babel.types.blockStatement([])) let onDone = ourArguments[1] ? ourArguments[1].node : nop; let onError = ourArguments[2] ? ourArguments[2].node : null; let parsWithSet = referencePath.parentPath.parentPath.get("id").get("elements") if (!parsWithSet[0].isIdentifier()) throw new MacroError( "first result of useMethod must be a function name", ) let runMethod = parsWithSet[0].node if (!parsWithSet[1].isObjectPattern()) throw new MacroError( "second result of useMethod must be the returned data object", ) let pars = parsWithSet[1].node let setStateIdentifier = declPath.scope.generateUidIdentifier("setStateInternalMagic") let variableArgsId = declPath.scope.generateUidIdentifier("variableArgsInternalMagic") let useStateHookImport = addNamed(declPath, "useState", "react"); let useCallbackHookImport = addNamed(declPath, "useCallback", "react"); let servicePath = "@services/api"; let apiErrorImport = addNamed(declPath, "ApiError", servicePath); let nejServicesPath = "@utils/NejManager/NejProvider"; let emptyErrorIdentifier = declPath.scope.generateUidIdentifier("emptyErrorInternalMagic") let onErrorIdentifier = declPath.scope.generateUidIdentifier("onErrorInternalMagic") let onSuccessIdentifier = declPath.scope.generateUidIdentifier("onSuccessInternalMagic") let onStartLoadingIdentifier = declPath.scope.generateUidIdentifier("onErrorInternalMagic") let onStopLoadingIdentifier = declPath.scope.generateUidIdentifier("onErrorInternalMagic") let useOnErrorImport = addNamed(declPath, "useOnError", nejServicesPath); let useOnSuccessImport = addNamed(declPath, "useOnSuccess", nejServicesPath); let useStartLoadingImport = addNamed(declPath, "useStartLoading", nejServicesPath); let useStopLoadingImport = addNamed(declPath, "useStopLoading", nejServicesPath); let useStateSrc = babel.template("[DATA, CALL_SET] = USE_STATE({data: null, error: null, loading: false})")({ DATA: pars, CALL_SET: setStateIdentifier, USE_STATE: useStateHookImport }) declPath.replaceWith(useStateSrc) let src = babel.template(` const ON_ERROR_IDENTIFIER = USE_ERROR_IMPORT(); const ON_SUCCESS_IDENTIFIER = USE_SUCCESS_IMPORT(); const ON_START_LOADING_IDENTIFIER = USE_START_LOADING_IMPORT(); const ON_STOP_LOADING_IDENTIFIER = USE_STOP_LOADING_IMPORT(); function EMPTY_ERROR(x){ return null } const RUN_NAME = USE_CALLBACK(async (...ARGS) => { let notifKey = ""; try{ notifKey = ON_START_LOADING_IDENTIFIER(); CALL_SET({ data: null, error: null, loading: true }); let ret = await IMPORT_NAME(...ARGS); CALL_SET({ data: ret, error: null, loading: false }); ON_STOP_LOADING_IDENTIFIER(notifKey); ON_SUCCESS_IDENTIFIER(); DONE(ret, ...ARGS); return ret; } catch(e){ if(e instanceof ERROR_CLASS){ CALL_SET({data: null, error: e.body, loading: false}) ON_STOP_LOADING_IDENTIFIER(notifKey); ON_ERROR_IDENTIFIER(e.body); ERROR(e.body) return } CALL_SET({data: null, error: e, loading: false}) ON_STOP_LOADING_IDENTIFIER(notifKey); ON_ERROR_IDENTIFIER(e); ERROR(e) return null; } }, []) `)({ IMPORT_NAME: call, CALL_SET: setStateIdentifier, RUN_NAME: runMethod, ARGS: variableArgsId, USE_CALLBACK: useCallbackHookImport, ERROR_CLASS: apiErrorImport, DONE: onDone, //if its null create an empty function to call ERROR: onError != null ? onError : emptyErrorIdentifier, EMPTY_ERROR: emptyErrorIdentifier, USE_ERROR_IMPORT: useOnErrorImport, USE_SUCCESS_IMPORT: useOnSuccessImport, USE_START_LOADING_IMPORT: useStartLoadingImport, USE_STOP_LOADING_IMPORT: useStopLoadingImport, ON_SUCCESS_IDENTIFIER: onSuccessIdentifier, ON_START_LOADING_IDENTIFIER: onStartLoadingIdentifier, ON_STOP_LOADING_IDENTIFIER: onStopLoadingIdentifier, ON_ERROR_IDENTIFIER: onErrorIdentifier, }) declPath.parentPath.insertAfter(src) }) }