const { createMacro, MacroError } = require("babel-plugin-macros") const { addNamed } = require("@babel/helper-module-imports") const path = require("path") const Babel = require("@babel/core") // `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(useQuery); //jsdoc /** * @param {Object} param0 * @param {import("@babel/types").ArrayExpression} param0.references * @param {Object} param0.state * @param {Babel} param0.babel */ function useQuery({ 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 > 1) { throw new MacroError( "There are too many arguments", ) } //jsdoc /** * @type {import("@babel/types").CallExpression} * */ let call = ourArguments[0].node let declPath = referencePath.parentPath.parentPath; let setStateIdentifier = declPath.scope.generateUidIdentifier("setStateInternalMagic") let refreshFuncName = declPath.scope.generateUidIdentifier("refreshInternalMagic") let queryObjName = declPath.scope.generateUidIdentifier("queryInternalMagic"); let assignParsPath = referencePath.parentPath.parentPath.get("id"); let pars = assignParsPath.node if (!assignParsPath.isObjectPattern()) { if (!assignParsPath.isArrayPattern() || !assignParsPath.get("elements")[0].isObjectPattern()) throw new MacroError( "Return is either just the object or array of object and refresh method", ) pars = assignParsPath.get("elements")[0].node if (assignParsPath.get("elements").length > 1) { if (!assignParsPath.get("elements")[1].isIdentifier()) throw new MacroError( "There is second argument to array return, but its surprisingly not a function identifier", ) refreshFuncName = assignParsPath.get("elements")[1].node } } let useStateHookImport = addNamed(declPath, "useState", "react"); let useCallbackHookImport = addNamed(declPath, "useCallback", "react"); let useEffectHookImport = addNamed(declPath, "useEffect", "react"); let useRQuery = addNamed(declPath, "useQuery", "@tanstack/react-query"); //create a string literal for identification //reduce the callee's MemberExpression object and property to astring with member.member.member.etc... var reduceCallee = (callee) => { if (callee.type == "Identifier") return callee.name return reduceCallee(callee.object) + "." + reduceCallee(callee.property) } let fullCallPathString = reduceCallee(call.callee) let servicePath = "@services/portal-api"; let apiErrorImport = addNamed(declPath, "ApiError", servicePath); let useStateSrc = babel.template("[DATA, CALL_SET] = USE_STATE({data: null, error: null, loading: true})")({ DATA: pars, CALL_SET: setStateIdentifier, USE_STATE: useStateHookImport }) declPath.replaceWith(useStateSrc) let src = babel.template(` const Q_UNAME = USE_RQUERY({queryKey: ["Q_KEY", ...(new Array(DEPS))], queryFn: async () => IMPORT_NAME}) USE_EFFECT(() => { if(Q_UNAME.error instanceof ERROR_CLASS){ CALL_SET({...Q_UNAME, error: Q_UNAME.error.body, loading: Q_UNAME.isLoading}) return } CALL_SET({...Q_UNAME, loading: Q_UNAME.isLoading}) }, [Q_UNAME.data, Q_UNAME.error, Q_UNAME.isLoading]) const REFRESH = USE_CALLBACK(async () =>{ await Q_UNAME.refetch() }, [Q_UNAME.data, Q_UNAME.error, Q_UNAME.isLoading]) `)({ IMPORT_NAME: call, DEPS: call.arguments, CALL_SET: setStateIdentifier, USE_EFFECT: useEffectHookImport, USE_CALLBACK: useCallbackHookImport, ERROR_CLASS: apiErrorImport, REFRESH: refreshFuncName, Q_UNAME: queryObjName, Q_KEY: fullCallPathString, USE_RQUERY: useRQuery }) declPath.parentPath.insertAfter(src) }) }