UNPKG

unplugin-quansync

Version:

Write async functions, get both async and sync functions

121 lines (117 loc) 4.08 kB
import { createUnplugin } from "unplugin"; import { createFilter } from "unplugin-utils"; import { babelParse, getLang, isCallOf, isFunctionType, isTypeOf, walkAST, walkImportDeclaration } from "ast-kit"; import { MagicString, generateTransform } from "magic-string-ast"; //#region src/core/index.ts const THIS_REGEX = /\bthis\b/; const ARROW_FN_START = `\nreturn function* () {`; const ARROW_FN_END = `}.call(this)\n`; function transformQuansync(code, id) { const lang = getLang(id); const program = babelParse(code, lang, { createParenthesizedExpressions: true }); const imports = Object.create(null); for (const node of program.body) if (node.type === "ImportDeclaration") walkImportDeclaration(imports, node); const macroName = Object.values(imports).find((i) => i.source === "quansync/macro" && i.imported === "quansync")?.local; if (!macroName) return; const s = new MagicString(code); const functionScopes = []; const nodeStack = []; function findUpExpressionStatement() { for (let i = nodeStack.length - 1; i >= 0; i--) { const node = nodeStack[i]; if (isFunctionType(node) || node.type === "BlockStatement") return; if (node.type === "ExpressionStatement") return node; } } function prependSemi(stmt) { if (stmt.semi) return; s.prependLeft(stmt.start, `;`); stmt.semi = true; } walkAST(program, { enter(node, parent) { nodeStack.push(node); if (node.type === "AwaitExpression" && functionScopes.at(-1)) { const needParen = isTypeOf(parent, [ "UnaryExpression", "BinaryExpression", "LogicalExpression", "TSAsExpression", "TSSatisfiesExpression" ]); s.overwrite(node.start, node.argument.start, `${needParen ? "(" : ""}yield `); if (needParen) { s.appendLeft(node.end, ")"); const stmt = findUpExpressionStatement(); if (stmt && stmt.start === node.start) prependSemi(stmt); } return; } if (!isFunctionType(node)) return; const inMacroFunction = isCallOf(parent, macroName); functionScopes.push(inMacroFunction); if (!inMacroFunction || !node.async) return; const name = "id" in node && node.id ? node.id.name : ""; const isArrowFunction = node.type === "ArrowFunctionExpression"; const body = s.slice(node.body.start, node.body.end); const hasParentThis = isArrowFunction && THIS_REGEX.test(body); if (hasParentThis) { rewriteFunctionSignature(node, "(", ") => "); rewriteFunctionBody(node, ARROW_FN_START, ARROW_FN_END); } else { rewriteFunctionSignature(node, `function* ${name}(`, ") "); rewriteFunctionBody(node); } }, leave(node) { nodeStack.pop(); if (isFunctionType(node)) functionScopes.pop(); } }); return generateTransform(s, id); function rewriteFunctionSignature(node, start, end) { const firstParam = node.params[0]; if (firstParam) { s.overwrite(node.start, firstParam.start, start); s.overwrite(node.params.at(-1).end, node.body.start, end); } else s.overwrite(node.start, node.body.start, start + end); } function rewriteFunctionBody(node, prefix = "", suffix = "") { if (node.body.type === "BlockStatement") { s.appendLeft(node.body.start + 1, prefix); s.appendLeft(node.body.end - 1, suffix); } else { s.appendLeft(node.body.start, `{\n${prefix}return `); s.appendLeft(node.body.end, `${suffix}\n}`); } } } //#endregion //#region src/core/options.ts function resolveOptions(options) { return { include: options.include || [/\.[cm]?[jt]sx?$/], exclude: options.exclude || [/node_modules/], enforce: "enforce" in options ? options.enforce : "pre" }; } //#endregion //#region src/index.ts const Quansync = createUnplugin((rawOptions = {}) => { const options = resolveOptions(rawOptions); const filter = createFilter(options.include, options.exclude); const name = "unplugin-quansync"; return { name, enforce: options.enforce, transformInclude(id) { return filter(id); }, transform(code, id) { if (!code.includes("quansync")) return; return transformQuansync(code, id); } }; }); //#endregion export { Quansync };