将所有变量声明包装在功能中

发布于 2025-02-03 01:39:49 字数 572 浏览 4 评论 0原文

我有一个JavaScript函数声明为字符串(从function.tostring),我想用函数包装所有变量声明(也在JavaScript中),例如 const value = 42 to const value = wrapper(42)

首先,我想到使用正直值来获取原始值和位置,然后用包装值替换它们,但是由于需要考虑多行字符串和对象之类的事物,因此正则是非常复杂的。使用Regex也会影响其他人为该项目做出贡献的便利性。

之后,我研究了为此使用一个模块,我找到了橡子(由Babel,Svelte。将JavaScript解析为estree,javascript抽象语法树的规格): https://github.com/acornjs/acorn ,但是我找不到将Estree解析回Javascript函数声明的方法,在进行修改后声明。

是否有一种方法可以将estree解析回功能或其他更好的解决方案?

I have a Javascript function declartions as a string (gotten from Function.toString), and I want to wrap all variable declarations with a function (also in Javascript), E.g.
const value = 42 to const value = wrapper(42).

First I thought of using RegEx to get the original values and location and then replace them with the wrapped value, but the RegEx got too complex very fast because of needing to think about things like multiline strings and objects. Using RegEx would also impact the ease of other people contributing to the project.

After that I looked into using a module for this, I found Acorn (used by Babel, Svelte. Parses the Javascript into an ESTree, the spec for Javascript Abstract Syntax Trees): https://github.com/acornjs/acorn, but I couldn't find a way of parsing the ESTree back to a Javascript function declaration after making the modifications.

Is there a way of parsing the ESTree back to a function, or another better solution?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

樱娆 2025-02-10 01:39:49

您实际上不需要将树将树重新串起来的函数。相反,请注意应该发生更改的位置 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>

You don't really need a function to stringify the tree back into code. Instead, take note of the offsets where the change should occur, and then don't apply the change in the tree, but to the original string.

Here is a demo with the acorn 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>

幽蝶幻影 2025-02-10 01:39:49
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>

是:

import {parse, print} from "@putout/recast";
console.log(print(parse(source)).code);

您可以使用 recast 要打印结果,它将 支持babelacorn,甚至typescript解析器。

但是最简单的方法是使用

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>

You can use recast to print results, it would be something like:

import {parse, print} from "@putout/recast";
console.log(print(parse(source)).code);

It supports babel, acorn, and even typescript parsers.

But the simplest possible way would be to use ????Putout code transformer.
Here is how it can look like:

export const report = () => `Add wrap()`;

export const replace = () => ({
    'const __a = __b': 'const __a = wrap(__b)',
});

????Putout has both parser and printer inside of itself, so you can just use function call to do the thing:

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);

Check out in ????Putout Editor.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文