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:
function/*post-keyword*/fn/*post-name*/()/*post-parens*/{
/*inside*/
}
document.write(fn.toString());
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.
You may nest a function within another function. The nested (inner) function is private to its containing (outer) function. It also forms a closure.
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.
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.
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;
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With