我有一个作为字符串的 Javascript 函数声明(来自
Function.toString
),我想用一个函数(也在 Javascript 中)包装所有变量声明,例如
const value = 42
到 const value = wrapper(42)
.
首先我想到使用 RegEx 来获取原始值和位置,然后用包装值替换它们,但是 RegEx 变得太复杂了,因为需要考虑多行字符串和对象之类的东西。使用 RegEx 也会影响其他人为项目做出贡献的难易程度。
之后我研究了为此使用的模块,我发现了 Acorn(由 Babel、Svelte 使用。将 Javascript 解析为 ESTree,即 Javascript 抽象语法树的规范):https://github.com/acornjs/acorn ,但在进行修改后,我找不到将 ESTree 解析回 Javascript 函数声明的方法。
有没有办法将 ESTree 解析回函数,或者其他更好的解决方案?
你真的不需要一个函数来将树字符串化回代码。相反,记下应该发生更改的 offsets,然后不要将更改应用到树中,而是应用到原始字符串。
这是一个橡子 API 的演示:
function test () { // The function we want to tamper with
const value = 42, prefix = "prefix";
let x = 3;
for (let i = 0; i < 10; i++) {
x = (x * 997 + value) % 1000;
}
return prefix + " " + x;
}
function addInitWrappers(str) { // Returns an updated string
let ast = acorn.parse(str, {ecmaVersion: 2020});
function* iter(node) {
if (Object(node) !== node) return; // Primitive
if (node.type == "VariableDeclaration" && node.kind == "const") {
for (let {init} of node.declarations) {
yield init; // yield the offset where this initialisation occurs
}
}
for (let value of Object.values(node)) {
yield* iter(value);
}
}
// Inject the wrapper -- starting at the back
for (let {start, end} of [...iter(ast)].reverse()) {
str = str.slice(0, start) + "wrapper(" + str.slice(start, end) + ")" + str.slice(end);
}
return str;
}
function wrapper(value) { // A wrapper function to demo with
return value + 1;
}
console.log("before wrapping test() returns:", test());
let str = test.toString();
str = addInitWrappers(str);
eval(str); // Override the test function with its new definition
console.log("after wrapping test() returns:", test());
<script src="https://cdnjs.cloudflare.com/ajax/libs/acorn/8.7.1/acorn.min.js"></script>
const {code} = putout('const a = 42', {
plugins: [
['wrap', {
report: () => `Add wrap()`,
replace: () => ({
'const __a = __b': 'const __a = wrap(__b)',
})
}]
]
});
console.log(code);
<script src="https://cdn.jsdelivr.net/npm/@putout/[email protected]/bundle/putout-iife.js"></script>
recast
打印结果,它会是这样的:
import {parse, print} from "@putout/recast";
console.log(print(parse(source)).code);
它支持
babel
、acorn
,甚至typescript
解析器。
但最简单的方法是使用 🐊Putout 代码转换器。 这是它的样子:
export const report = () => `Add wrap()`;
export const replace = () => ({
'const __a = __b': 'const __a = wrap(__b)',
});
🐊Putout 本身有解析器和打印机,所以你可以只使用函数调用来做这件事:
import putout from 'putout';
const {code} = putout('const a = 42', {
plugins: [
['wrap', {
report: () => `Add wrap()`,
replace: () => ({
'const __a = __b': 'const __a = wrap(__b)',
})
}]
]
});
console.log(code);
// outputs
const a = wrap(42);
在 🐊Putout Editor.
中查看