I've tried looking at global, but it only contains variables, not functions. How can I list all the functions created in my script?
-
what would be the use case for this?mihai– mihai2012-05-05 20:34:52 +00:00Commented May 5, 2012 at 20:34
-
1A custom AOP script that I'm creating for a node app. I want to use it for things like profiling, throttling, and custom security policies. I checked out Dojo but had some early probs just loading it into my node app so I thought I'd write a custom script. It shouldn't be that hard.Trindaz– Trindaz2012-05-05 21:12:49 +00:00Commented May 5, 2012 at 21:12
-
So you want to do this from within the script itself? I'm not sure it's so easy because you can have anonymous functions, functions inside closures, functions dynamically created etc.mihai– mihai2012-05-05 21:18:13 +00:00Commented May 5, 2012 at 21:18
-
True - but in my case applying aspects to only 'root level' named functions will surfice.Trindaz– Trindaz2012-05-05 21:27:08 +00:00Commented May 5, 2012 at 21:27
6 Answers
Run node debug from command line with the file you want to look at. Then you can use list(some big number here)
node debug mini_file_server.js
< debugger listening on port 5858
connecting... ok
debug> scripts
26: mini_file_server.js
debug> list(1000)
1 var http = require('http'),
2 util = require('util'),
3 fs = require('fs');
4
5 server = http.createServer(function(req, res){
6 var stream = fs.createReadStream('one.html'),
7 stream2 = fs.createReadStream('two.html');
8 console.log(stream);
9 console.log(stream2);
10 stream.on('end', function(){
11 stream2.pipe(res, { end:false});
12 });
13
14 stream2.on('end', function(){
15 res.end("Thats all!");
16 });
17
18 res.writeHead(200, {'Content-Type' : 'text/plain'});
19 stream.pipe(res, { end:false});
20 stream2.pipe(res, { end:true});
21
22 }).listen(8001);
23 });
debug>
Comments
If the function has a name, it'll show up in global just fine:
mb-work-laptop:~ markbessey$ node
> for (var k in global) { console.log(k); }
global
process
GLOBAL
root
Buffer
setTimeout
setInterval
clearTimeout
clearInterval
console
module
require
k
> function z(a) { return a*10; }
> for (var k in global) { console.log(k); }
global
process
GLOBAL
root
Buffer
setTimeout
setInterval
clearTimeout
clearInterval
console
module
require
k
z
>
> global.z
[Function: z]
3 Comments
This is impossible in node without more advanced reflecting tools like the debugger.
The only way to do this would be to use __parent__ which was removed due to security issues and other things. Like Mark Bessey said, when you run the script those variables become module closure variables. You can not access them elsewhere without explicitly exporting them.
This is not a bug, it's by design. It's just how node works. However, if you just ask your users to write function expression assignments, all would work a-ok:
module.exports = {
a:function(){
//same logic you had in the function declaration
}
}
Then, you can easily reflect on and enumerate module.exports and get all the function names.
Comments
cli: http://nodejs.org/docs/v0.3.7/api/debugger.html
gui: https://github.com/dannycoates/node-inspector
There's also https://github.com/c4milo/node-webkit-agent in the works which will be a more powerful version of node-inspector.
Comments
If you want to do some AOP, the route is AST.
You could build your own AOP framework with something like: http://esprima.org.
Or you could try node-burrito, excellent for not so complex aspects:
var burrito = require('burrito');
var src = burrito('someCall()', function (node) {
if (node.name === 'call') node.wrap('qqq(%s)');
});
will generate
qqq(somecall())
Comments
Wanted the exact thing you're looking for. Couldn't find any other solutions (most assumed use of "window" or "this" or "global", none of which will work for us in Node).
We can do it in about 25 lines with the help of a few libraries: fs, esprima, and escodegen.
const fs = require('fs');
const esprima = require("esprima");
const escodegen = require("escodegen");
The logic is this:
- Let's take the file this function is in, and first read it as text, just like any other file we would read as plain text
- We'll use esprima to parse that text into a valid tree, part of which will contain our functions
- We'll filter out that tree to only contain functions (except for this function we're using to do this! We're not after it)
- For that, we'll need to grab the easy function declarations
- But ideally we can also grab any functions that were declared as variables with arrow expressions, which will take a little more work but is very doable.
- Next we want to reconstruct these objects from the tree back into actual usable functions in the code, so for all of our functions:
- Use escodegen to reconstruct that object into a string that looks just like written code for that function
- Convert that string into a usable function within the script itself again
The end result will spit back out an object with the key:value pairs where the key is the string of the function's name, and the value is the function itself.
function getAllFunctionsInThisFileExceptThisFunction() {
const thisFunctionName = arguments.callee.name;
const rawTextFromThisFile = fs.readFileSync(__filename, "utf8");
const parsed = esprima.parseScript(rawTextFromThisFile);
const allDeclaredVariables = parsed.body.filter(e=>e.type === "VariableDeclaration");
const allDeclaredFunctions = parsed.body.filter(e=>e.type === "FunctionDeclaration");
let allFunctions = []
for (declaredVariable of allDeclaredVariables){
const declarations = declaredVariable.declarations[0];
if (declarations.init.type === "ArrowFunctionExpression"){
const anonymousFunction = declarations.init;
let reconstructedFunction = anonymousFunction;
reconstructedFunction.id = declarations.id;
allFunctions.push(reconstructedFunction);
}
}
allFunctions.push(...allDeclaredFunctions)
const allFunctionsExceptThisOne = allFunctions.filter(e => e.id.name !== thisFunctionName);
let functionsDict = {};
for (parsedFunction of allFunctionsExceptThisOne) {
const functionString = escodegen.generate(parsedFunction);
const newFunction = eval(`(${functionString})`)
functionsDict[parsedFunction.id.name] = newFunction;
}
return functionsDict;
}
From there you can play away with it like any other object/dictionary.
const allFunctionsDict = getAllFunctionsInThisFileExceptThisFunction();
console.log( allFunctionsDict["sum"](10,30) ) //prints 40
console.log( allFunctionsDict["difference"](350,250) ) //prints 100
console.log( allFunctionsDict["product"](6,4) ) // prints 24
console.log( Object.keys(allFunctionsDict) ) //prints [ 'product', 'sum', 'difference' ]
function sum(a, b) {
return a + b;
}
function difference(a, b) {
return a - b;
}
const product = (a,b) => {
return a * b;
}