Esprima get function and variable declarations

1.3k Views Asked by At

I want to get all function and variable declarations made in a Javascript code. I use esprima and I wonder if there is script that I can use for my goal?

For example we have this code:

var myVar1;
var myVar2;
function myTestFunction(funcVar1, funcVar2) {
  var myVar3; 
}

What I except:

Array with variables

["myVar1", "myVar2"]

And an array with functions:

[{"name": "myTestFuncttion", "params":["funcVar1", "funcVar2"], "variables": ["myVar3"]}]

Any ideas how to achieve this?

1

There are 1 best solutions below

1
mathheadinclouds On

Complete solution with fiddle:

Here is some JavaScript code for a test-run:

var hello = 41;
function aaaaa(p1, p2, p3){
    var a1 = 7, a2 = 8;
    var a3 = 9;
    function bbbbb(q1, q2){
        var b1 = 10, b2 = 11;
        return 12;
    }
    var a4 = 99;
    function ccccc(r1, r2, r3){
        var c1 = 13;
        var c2 = 14;
        var c3 = 15, c4 = 16;
        return 17;
    }
    var a5 = 88, a6 = 77;
    function ddddd(s1){
        return s1 === 18
            ? function (x){ return x+1; }
            : function (y){ return 22; }
    }
    return p1 + a3 <= 42 ? 55 : ccc(p1, 0, 0);
}
var world = 42;
function xxxxx(x){
    var z=0;
    return 0;
}

I'm assuming that this is the desired output:

{
    "vars": ["hello", "world" ],
    "funcs": [
        {
            "name": "aaaaa",
            "params": ["p1", "p2", "p3"],
            "variables": ["a1","a2","a3","a4","a5","a6"]
        },
        {
            "name": "bbbbb",
            "params": ["q1","q2"],
            "variables": ["b1","b2"]
        },
        {
            "name": "ccccc",
            "params": ["r1","r2","r3"],
            "variables": ["c1","c2","c3","c4"]
        },
        {
            "name": "ddddd",
            "params": ["s1"],
            "variables": []
        },
        {
            "name": "xxxxx",
            "params": ["x"],
            "variables": ["z"]
        }
    ]
}

The lists are flat and the anonymous functions within ddddd are being ignored (they are FunctionExpressions not FunctionDeclarations). Guessing that this is how you want it.

Here is the code - probably / hopefully easy to understand without further explanation:

function findDeclarations(code){
    var ast = esprima.parse(code);
    var funcDecls = [];
    var globalVarDecls = [];
    var funcStack = [];
    function visitEachAstNode(root, enter, leave){
        function visit(node){
            function isSubNode(key){
                var child = node[key];
                if (child===null) return false;
                var ty = typeof child;
                if (ty!=='object') return false;
                if (child.constructor===Array) return ( key!=='range' );
                if (key==='loc') return false;
                if ('type' in child){
                    if (child.type in esprima.Syntax) return true;
                    debugger; throw new Error('unexpected');
                } else { return false; }
            }
            enter(node);
            var keys = Object.keys(node);
            var subNodeKeys = keys.filter(isSubNode);
            for (var i=0; i<subNodeKeys.length; i++){
                var key = subNodeKeys[i];
                visit(node[key]);
            }
            leave(node);
        }
        visit(root);
    }
    function myEnter(node){
        if (node.type==='FunctionDeclaration') {
            var current = {
                name      : node.id.name,
                params    : node.params.map(function(p){return p.name;}),
                variables : []
            }
            funcDecls.push(current);
            funcStack.push(current);
        }
        if (node.type==='VariableDeclaration'){
            var foundVarNames = node.declarations.map(function(d){ return d.id.name; });
            if (funcStack.length===0){
                globalVarDecls = globalVarDecls.concat(foundVarNames);
            } else {
                var onTopOfStack = funcStack[funcStack.length-1];
                onTopOfStack.variables = onTopOfStack.variables.concat(foundVarNames);
            }
        }
    }
    function myLeave(node){
        if (node.type==='FunctionDeclaration') {
            funcStack.pop();
        }
    }
    visitEachAstNode(ast, myEnter, myLeave);
    return {
        vars  : globalVarDecls,
        funcs : funcDecls
    };
}

for testing, you can type

JSON.stringify(
    findDeclarations(
        'var hello=41;\n' +
        aaaaa.toString() +
        'var world=42;\n' +
        xxxxx.toString()
    ),
    null, 4
)

You can also use the estraverse package, it's available on github. Then, essentially, the function visitEachAstNode should be replaced by estraverse.traverse, and otherwise you can leave the code unchanged.

fiddle