unplugin-ast
Version:
Manipulate the AST to transform your code.
115 lines (110 loc) • 3.35 kB
JavaScript
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 };