UNPKG

unplugin-ast

Version:

Manipulate the AST to transform your code.

115 lines (110 loc) 3.35 kB
import { babelParse, getLang, walkASTAsync } from "ast-kit"; import { createUnplugin } from "unplugin"; import { createFilter } from "unplugin-utils"; import { toArray } from "@antfu/utils"; import generate from "@babel/generator"; import { MagicStringAST, generateTransform } from "magic-string-ast"; //#region src/core/options.ts function resolveOption(options) { return { include: options.include || [/\.[jt]sx?$/], exclude: options.exclude || undefined, enforce: options.enforce || undefined, parserOptions: options.parserOptions || {}, transformer: options.transformer ? toArray(options.transformer) : [] }; } //#endregion //#region src/core/utils.ts function useNodeRef() { const nodeRefs = new Map(); function getNodeRef(node) { if (nodeRefs.has(node)) return nodeRefs.get(node); const ref = { value: node, set(node$1) { this.value = node$1; } }; nodeRefs.set(node, ref); return ref; } return { nodeRefs, getNodeRef }; } //#endregion //#region src/core/transform.ts async function getTransformersByFile(transformer, id) { const transformers = (await Promise.all(transformer.map(async (t) => { if (t.transformInclude && !await t.transformInclude(id)) return undefined; return { transformer: t, nodes: [] }; }))).filter((t) => !!t); return transformers; } async function transform(code, id, options) { const { getNodeRef } = useNodeRef(); const transformers = await getTransformersByFile(options.transformer, id); if (transformers.length === 0) return; const program = babelParse(code, getLang(id), options.parserOptions); await walkASTAsync(program, { async enter(node, parent, key, index) { for (const { transformer, nodes } of transformers) { if (transformer.onNode) { const bool = await transformer.onNode?.(node, parent, index); if (!bool) continue; } nodes.push(getNodeRef(node)); } } }); const s = new MagicStringAST(code); for (const { transformer, nodes } of transformers) for (const node of nodes) { const value = node.value; if (!value) continue; const result = await transformer.transform(value, code, { id }); if (result) { let newAST; if (typeof result === "string") { s.overwriteNode(value, result); newAST = babelParse(`{${result}}`, getLang(id), options.parserOptions).body[0].body[0]; if (newAST.type === "ExpressionStatement") newAST = newAST.expression; newAST.start = value.start; newAST.end = value.end; } else { const generated = (generate.default || generate)(result); let code$1 = generated.code; if (result.type.endsWith("Expression")) code$1 = `(${code$1})`; s.overwriteNode(value, code$1); newAST = result; } node.set(newAST); } else if (result === false) { node.set(undefined); s.removeNode(value); } } for (const { transformer } of transformers) await transformer.finalize?.(s); return generateTransform(s, id); } //#endregion //#region src/index.ts const AST = createUnplugin((options = {}) => { const opt = resolveOption(options); const filter = createFilter(opt.include, opt.exclude); const name = "unplugin-ast"; return { name, enforce: options.enforce, transformInclude(id) { return filter(id); }, transform(code, id) { return transform(code, id, opt); } }; }); //#endregion export { AST, resolveOption, transform };