将所有变量声明包装在一个函数中

问题描述 投票:0回答:2

我有一个作为字符串的 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 解析回函数,或者其他更好的解决方案?

javascript regex wrapper abstract-syntax-tree variable-declaration
2个回答
2
投票

你真的不需要一个函数来将树字符串化回代码。相反,记下应该发生更改的 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>


0
投票

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.

中查看
© www.soinside.com 2019 - 2024. All rights reserved.