JS: rename variables for refactor (using an AST, not text)

China☆狼群 提交于 2020-01-14 22:32:30

问题


I often need to rename variables when refactoring code, which I currently do in a somewhat hacky way using regexs - I end up having to come with silly text workaround workarounds for the lack of actual structure, eg, rename 'req' to 'request' and avoid side effects with similar names like 'require'.

Thinking about this stuff: it's kind of like modifying the DOM with regexs: it just doesn't work.

I've learnt about ASTs and code structure modification tools like Esprima. Is there a tool to rename variables, Esprima based or otherwise?


回答1:


1. grasp.js

It looks like http://graspjs.com/ does exactly this.

grasp selector --replace replacement file.js

For example, to rename 'el' to 'element':

grasp '#el' --replace 'element' index.js

Official grasp.replace docs.

2. vscode

Visual Studio Code also includes a real replacement tool. Just right click a token and choose rename symbol.




回答2:


I know you've been asking for 'a tool'; but I think it's better to use just esprima itself and the various general purpose tools on top of esprima, and roll your own renamer. Because it's really really easy, and then you have more control. Here is a complete example in just 12 lines of code. It uses escodegen and estraverse, both on github, and, as far as I can see kind of 'the standard' to use in conjunction with esprima. While esprima essentially gives the parse function string -> abstract syntax tree, escodegen essentially gives the reverse of that, i.e. abstract syntax tree -> string. And estraverse 'walks the tree' with the traverse method, thus helping to analyze or modify it.

Here the code:

function rename(code, renamingObj){
    var ast = esprima.parse(code);
    function callback(node){
        if (node.type==='Identifier') {
            if (node.name in renamingObj){
                node.name = renamingObj[node.name];
            }
        }
    }
    estraverse.traverse(ast, { enter: callback });
    return escodegen.generate(ast);
}

Testcase:

function blah(x,y){
    var difference = x + y;
    var product    = x - y;
    var sum        = x * y;
    return 42;
}
var renamingObj = {
    sum        : 'product',
    difference : 'sum',
    product    : 'difference'
};

run it:

rename(blah.toString(), renamingObj)

output:

function blah(x, y) {
    var sum = x + y;
    var difference = x - y;
    var product = x * y;
    return 42;
}

I would say, if you have something special to do, it's easier to modify above code than sifting through some tool documentation.




回答3:


Our JavaScript Formatter/Obfuscator will do this reliably. It parses JavaScript, builds an AST, and either prints a pretty version, or an ugly (obfuscated) version. [The reason these are together is because pretty and ugly are opposite sides of the same coin!).

One of the things it does is scramble (whole) identifier names. By using the AST, it only renames "whole identifiers"; it won't confuse "req" with "require", or modify string or comment content by accident.

You can tell it to build a map of all identifier names, and then rename them all to themselves; change just one, and you get your rename effect. The identifier map looks like this:

    x ->  y
    p -> q
    ...

directing that x gets renamed to y, p to q, etc. You want:

    x -> x
    p -> q
    ...

to change just p to q. Its pretty easy to produce the identity map as your starting place.

I wouldn't say this is convenient, but it does work. It obviously doesn't know anything about scopes. (someday!).

See my bio for details. Many SO moderators hate tool questions, and they especially seem to dislike it when I provide a tool answer that includes a link to the tool. So you'll have to track down the link yourself, sorry. (Complain on Meta if you think this is dumb).



来源:https://stackoverflow.com/questions/19710494/js-rename-variables-for-refactor-using-an-ast-not-text