135 lines
4.6 KiB
JavaScript
135 lines
4.6 KiB
JavaScript
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/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)
|
|
})
|
|
}
|