nej-react-utils/macros/useMethod.macro.cjs
2024-12-27 23:50:13 +01:00

167 lines
6.0 KiB
JavaScript

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);
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)
})
}