babel-plugin-codegen
Version:
Generate code at build-time
201 lines (168 loc) • 5.34 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.getReplacers = getReplacers;
var _helpers = require("./helpers");
function getReplacers(babel) {
function asProgram(path, fileOpts) {
var _result$code;
const result = babel.transformFromAstSync(path.node, path.getSource(), {
filename: fileOpts.filename,
plugins: fileOpts.plugins,
presets: fileOpts.presets
}); // istanbul ignore next because this should never happen, but TypeScript needs me to handle it
const code = (_result$code = result == null ? void 0 : result.code) != null ? _result$code : '';
const replacement = (0, _helpers.getReplacement)({
code,
fileOpts
}, babel);
path.node.body = Array.isArray(replacement) ? replacement : [replacement];
}
function asImportDeclaration(path, fileOpts) {
var _path$node$source$lea, _path$node$source$lea2;
const filename = (0, _helpers.getFilename)(fileOpts);
const codegenComment = (_path$node$source$lea = path.node.source.leadingComments) == null ? void 0 : (_path$node$source$lea2 = _path$node$source$lea.find(_helpers.isCodegenComment)) == null ? void 0 : _path$node$source$lea2.value.trim(); // istanbul ignore next because we don't even call `asImportDeclaration` if
// there's not a codegen comment, but TypeScript gets mad otherwise
if (!codegenComment) return;
const {
code,
resolvedPath
} = (0, _helpers.resolveModuleContents)({
filename,
module: path.node.source.value
});
let args;
if (codegenComment !== 'codegen') {
args = (0, _helpers.requireFromString)(`module.exports = [${codegenComment.replace(/codegen\((.*)\)/, '$1').trim()}]`, filename);
}
(0, _helpers.replace)({
path,
code,
fileOpts: { ...fileOpts,
filename: resolvedPath
},
args
}, babel);
} // eslint-disable-next-line complexity
function asIdentifier(path, fileOpts) {
const targetPath = path.parentPath;
switch (targetPath.type) {
case 'TaggedTemplateExpression':
{
return asTag(targetPath, fileOpts);
}
case 'CallExpression':
{
const isCallee = targetPath.get('callee') === path;
if (isCallee) {
return asFunction(targetPath, fileOpts);
} else {
return false;
}
}
case 'JSXOpeningElement':
{
const jsxElement = targetPath.parentPath;
return asJSX(jsxElement, fileOpts);
}
case 'JSXClosingElement':
{
// ignore the closing element
// but don't mark as unhandled (return false)
// we already handled the opening element
return true;
}
case 'MemberExpression':
{
const callPath = targetPath.parentPath; // istanbul ignore if (don't know when this could happen)
if (!callPath) return false;
const isRequireCall = (0, _helpers.isPropertyCall)(callPath, 'require');
if (isRequireCall) {
return asImportCall(callPath, fileOpts);
} else {
return false;
}
}
default:
{
return false;
}
}
}
function asImportCall(path, fileOpts) {
const [source, ...args] = path.get('arguments');
const {
code,
resolvedPath
} = (0, _helpers.resolveModuleContents)({
filename: (0, _helpers.getFilename)(fileOpts),
module: source.node.value
});
const argValues = args.map(a => {
const result = a.evaluate();
if (!result.confident) {
throw new Error('codegen cannot determine the value of an argument in codegen.require');
}
return result.value;
});
(0, _helpers.replace)({
path,
code,
fileOpts: { ...fileOpts,
filename: resolvedPath
},
args: argValues
}, babel);
}
function asTag(path, fileOpts) {
const code = path.get('quasi').evaluate().value;
if (!code) {
throw path.buildCodeFrameError('Unable to determine the value of your codegen string', Error);
}
(0, _helpers.replace)({
path,
code,
fileOpts
}, babel);
}
function asFunction(path, fileOpts) {
const argumentsPaths = path.get('arguments');
const code = argumentsPaths[0].evaluate().value;
(0, _helpers.replace)({
path: argumentsPaths[0].parentPath,
code,
fileOpts
}, babel);
}
function asJSX(path, fileOpts) {
const children = path.get('children');
const container = children[0];
const expression = container.node.expression; // @ts-expect-error value isn't on all nodes, but we will assume nobody
// uses this for anything but the right kind of nodes...
let code = expression.value;
if (expression.type === 'TemplateLiteral') {
code = container.get('expression').evaluate().value;
}
(0, _helpers.replace)({
path: container.parentPath,
code,
fileOpts
}, babel);
}
return {
asTag,
asJSX,
asFunction,
asProgram,
asImportCall,
asImportDeclaration,
asIdentifier
};
}
/*
eslint
@typescript-eslint/no-explicit-any: "off",
complexity: ["error", 8],
max-lines-per-function: ["error", 250],
*/