Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Extracting nested function names from a JavaScript function

Given a function, I'm trying to find out the names of the nested functions in it (only one level deep).

A simple regex against toString() worked until I started using functions with comments in them. It turns out that some browsers store parts of the raw source while others reconstruct the source from what's compiled; The output of toString() may contain the original code comments in some browsers. As an aside, here are my findings:

Test subject

function/*post-keyword*/fn/*post-name*/()/*post-parens*/{
    /*inside*/
}

document.write(fn.toString());

Results

Browser      post-keyword  post-name  post-parens  inside
-----------  ------------  ---------  -----------  --------
 Firefox      No            No         No           No
 Safari       No            No         No           No
 Chrome       No            No         Yes          Yes
 IE           Yes           Yes        Yes          Yes
 Opera        Yes           Yes        Yes          Yes

I'm looking for a cross-browser way of extracting the nested function names from a given function. The solution should be able to extract "fn1" and "fn2" out of the following function:

function someFn() {
    /**
     * Some comment
     */
     function fn1() {
         alert("/*This is not a comment, it's a string literal*/");
     }

     function // keyword
     fn2 // name
     (x, y) // arguments
     {
         /*
         body
         */
     }

     var f = function () { // anonymous, ignore
     };
}

The solution doesn't have to be pure regex.

Update: You can assume that we're always dealing with valid, properly nested code with all string literals, comments and blocks terminated properly. This is because I'm parsing a function that has already been compiled as a valid function.

Update2: If you're wondering about the motivation behind this: I'm working on a new JavaScript unit testing framework that's called jsUnity. There are several different formats in which you can write tests & test suites. One of them is a function:

function myTests() {
    function setUp() {
    }

    function tearDown() {
    }

    function testSomething() {
    }

    function testSomethingElse() {
    }
}

Since the functions are hidden inside a closure, there's no way for me invoke them from outside the function. I therefore convert the outer function to a string, extract the function names, append a "now run the given inner function" statement at the bottom and recompile it as a function with new Function(). If the test function have comments in them, it gets tricky to extract the function names and to avoid false positives. Hence I'm soliciting the help of the SO community...

Update3: I've come up with a new solution that doesn't require a lot of semantic fiddling with code. I use the original source itself to probe for first-level functions.

like image 282
Ates Goral Avatar asked Feb 05 '09 19:02

Ates Goral


People also ask

Can you nest a function inside a function JavaScript?

You may nest a function within another function. The nested (inner) function is private to its containing (outer) function. It also forms a closure.

What is $() in JavaScript?

The $() function The dollar function, $(), can be used as shorthand for the getElementById function. To refer to an element in the Document Object Model (DOM) of an HTML page, the usual function identifying an element is: document. getElementById("id_of_element"). style.

Should I use nested functions JavaScript?

If you use the classical paradigm, you need to define classes but you don't need nested functions. Still, in scripting and classical, you MAY use nested functions if you want to. Only if you switch to the functional paradigm, which is the actual "native" paradigm of javascript, you NEED nested functions in some cases.


1 Answers

Cosmetic changes and bugfix

The regular expression must read \bfunction\b to avoid false positives!

Functions defined in blocks (e.g. in the bodies of loops) will be ignored if nested does not evaluate to true.

function tokenize(code) {
    var code = code.split(/\\./).join(''),
        regex = /\bfunction\b|\(|\)|\{|\}|\/\*|\*\/|\/\/|"|'|\n|\s+/mg,
        tokens = [],
        pos = 0;

    for(var matches; matches = regex.exec(code); pos = regex.lastIndex) {
        var match = matches[0],
            matchStart = regex.lastIndex - match.length;

        if(pos < matchStart)
            tokens.push(code.substring(pos, matchStart));

        tokens.push(match);
    }

    if(pos < code.length)
        tokens.push(code.substring(pos));

    return tokens;
}

var separators = {
    '/*' : '*/',
    '//' : '\n',
    '"' : '"',
    '\'' : '\''
};

function extractInnerFunctionNames(func, nested) {
    var names = [],
        tokens = tokenize(func.toString()),
        level = 0;

    for(var i = 0; i < tokens.length; ++i) {
        var token = tokens[i];

        switch(token) {
            case '{':
            ++level;
            break;

            case '}':
            --level;
            break;

            case '/*':
            case '//':
            case '"':
            case '\'':
            var sep = separators[token];
            while(++i < tokens.length && tokens[i] !== sep);
            break;

            case 'function':
            if(level === 1 || (nested && level)) {
                while(++i < tokens.length) {
                    token = tokens[i];

                    if(token === '(')
                        break;

                    if(/^\s+$/.test(token))
                        continue;

                    if(token === '/*' || token === '//') {
                        var sep = separators[token];
                        while(++i < tokens.length && tokens[i] !== sep);
                        continue;
                    }

                    names.push(token);
                    break;
                }
            }
            break;
        }
    }

    return names;
}
like image 53
GSree Avatar answered Sep 30 '22 09:09

GSree