I'm looking for a way to get a function declaration body by name from a string of js code. I'm in Nodejs environment.
Let's say I have some spaghetti.js file. I can read it into a string
const allJs = fs.readFileSync('spaghetti.js');
Now I need a function that receives that string and function name and returns a string with everything between { }.
Something like this
allJs = 'let a=1; const b=[2, 3]; function cook(items){return items}; cook(b)';
parseFunction(allJs, 'cook');//'return items'
The complexity of input js is not limited.
I tried to find an npm module for that, but no luck.
You should have a look at an AST parser for Javascript:
http://esprima.org/
https://github.com/ternjs/acorn
That should be more safe than using RegExp or something.
A String can be evaluated locally with the native eval() method. But remember, eval is a form of evil!
If the parseFunction() above is relying on something like this then the global Function constructor is being used and the 'new' function is bound to the return value of that operation (and thus that return value itself needs to be called).
A simple way to achieve this might be to do something like this...
var funcString = 'var a = 1, b = 3;';
funcString += 'function summit(){return a + b;}';
funcString += 'return summit();';
function makeNewFunc(str) {
return new Function(str);
}
var newFunc = makeNewFunc( funcString );
console.log('newFunc:',newFunc);
//-> newFunc: function anonymous()
console.log('newFunc():',newFunc());
//-> newFunc(): 4
This demonstrates how functions can be created and invoked from String snippets. (EDIT: Turning something like that into a Node module is a simple matter);
Hope that helped. :)
Related
I have a method in string like:
var str = "function evalTest(param){if(param)return '<div>hello</div>'}else return '<div>world</div>'"
I am replacing param like:
var res = str.replace("param", "param=false");
Now if I do eval like:
var final = eval(res);
I am expecting final should contain result "world" because passed param = false.
How to achieve this result "world"?
First a caveat: There's almost certainly a better solution to whatever problem you're trying to solve by having that function in a string.
And another one: Never eval code supplied by an end user except for that same end user. For instance, never let user A supply the code, then eval it in user B's browser. (Without user B knowing that you're doing that and expressly consenting to it.)
Really, almost any time you're reaching for eval (or its cousin new Function), it's worth stepping back to see if there's another approach you can use.
But answering the question asked:
eval is just creating the function. You don't have anything in that string that calls it.
Because eval works magically in the current scope, evaling your string creates the function in the current scope. You could then call your function to get the result you're looking for:
var str = "function evalTest(param){if(param){return '<div>hello</div>'}else {return '<div>world</div>'}}";
var res = str.replace("param", "param=false");
eval(res);
var final = evalTest();
console.log(final);
Note that I fixed a couple of syntax errors in the function's text (curly brace issues, mostly).
If you don't want the function defined in the current scope, you can modify the string to make it a function expression and call it immediately:
var str = "function evalTest(param){if(param){return '<div>hello</div>'}else {return '<div>world</div>'}}";
var res = str.replace("param", "param=false");
var final = eval("(" + res + ")()");
console.log(final);
I have an angular service fetching some JSON data, and in this data I have a function as a string (building it server side and passing it to angular), something like:
{
"fn": "function( data ) { data.rendered = data.value; }"
}
Is it possible to parse this and use it as an expression with angular? I.e.
settings.myCustomFunction = serviceData.fn;
I've attempted to do this with different combinations of eval, $eval, $parse, but I haven't had any luck with any of them. They all either throw errors about "Unexpected *" or don't throw errors, but also don't seem to do anything.
You could wrap the string in parentheses to avoid the syntax error.
eval("(" + fnString + ")")
This makes it a syntactically valid JavaScript statement which is composed of a single function expression that you can pass around freely.
An anonymous function by itself, without any parentheses or LHS operators (like the commonly used assignment operator =) acting upon it, is not a syntactically valid JavaScript statement. That's why you'll see IIFEs (immediately invoked function expressions) wrapped up in parentheses in JS:
(function() {
// ...
})()
If you really want to get eval to work, then you can do this:
var settings = {};
eval("settings.myCustomFunction = " + serviceData.fn);
Using eval in this way of course opens your app up to possible malicious script injections.
You could do user new Function():
settings.myCustomFunction = new Function('argument',functionbody);
To execute:
settings.myCustomFunction();//
You might want to check out eval.call. This allows you to not care what the arguments are and use any arguments from strings. Of course to call it you must use the same arguments, BUT, you could string-drive that too, by concatting the args together when you eval.call it.
{
"fn": "function( data ) { return data + 1; }"
}
Example:
var serviceData = {
fn: '(function (data) {return data + 1;})'
};
var data = 44;
var result = window.eval.call(window, serviceData.fn)(data);
console.log(result); // 45
I am working with a Javascript code that uses eval function.
eval(myString)
The value of myString = myFunc(arg), I want to call myFunc directly without using eval.
I dont have any control over the function to call as I am getting that function as a String (here myString).
The arguments to that function is also part of the same string.
So, is there any way through which I can call the intended function without using eval?
I'm a bit skeptical of allowing users to provide function names at all, but... Assume you have the function name in a variable and the value of arg in a variable. Boom:
var myString = window[fn](arg);
arg is already presumably in an argument, so that's simple enough. The next part is exatracting the function name. Just a bit of regex:
var fn = /^([a-z0-9_]+)\(arg\)$/i.exec(str)[1];
if (fn && typeof window[fn] === 'function') {
window[fn](arg);
}
This does of course assume that the function is always in the global scope, but if it's not, you should be able to adjust accordingly for that. Also, my regex is just the first thing I came up with. It probably doesn't cover all possible function names.
If you wanted to limit the string to just a certain set of functions (and you almost certainly should), that becomes quite easy as well once you have the function name:
var allowedFunctions = {fn1: fn1, fn2: fn2, someOtherFunction: function() {} },
fn = /^([a-z0-9_]+)\(arg\)$/i.exec(str)[1];
if (fn && allowedFunctions[fn]) {
allowedFunctions[fn](arg);
} else {
// Hah, nice try.
}
(If arg isn't actually a variable name but is some kind of literal or perhaps an arbitrary expression, this gets a little more complicated and a lot less safe.)
JavaScript does not provide any way of calling a function represented as a string, other than using eval. There's nothing wrong about using it, though. Given that you have no other option.
Possibly you may try using Function:
var sure = function(s) {
return confirm(s);
};
var str = 'sure("Are you sure?")';
var rtn = new Function('return ' + str)();
alert(rtn);
I am working in JavaScript. I want to parse and evaluate conditional expression.
ex:
var param1=1;
var param2=2;
var param3=3;
var expression="(param1==param2)||(param3<param1)";
I want to write a function which will accept 'expression' as a input and parse the expression as well as evaluate expression and return evaluated result.
Please let me know for any suggestions.
Thanks in advance.
Here is the evil one: eval();
var param1=1;
var param2=2;
var param3=3;
var expression=eval("(param1==param2)||(param3<param1)");
Then your function comes,
function myEvaluator(s) {
return eval(s);
}
You must have variables in expression public.
This is a really old question and I am surprised that no libraries came out since to help with this.
Out of necessity, I built a library called angel-eval that does exactly that. It parses a string expression and evaluates with a context object. It uses a parser generator called Nearley under the hood and does not use eval or new Function(…).
To use, install: npm i angel-eval
const { evaluate } = require("angel-eval");
evaluate("(param1===param2)||(param3<param1)", { param1: 1, param2: 2, param3: 3 });
angel-eval does not support == or != so you need to use === and !==.
Here is a sandbox:
There are a few expression parsers out there, but I would like to suggest mine. It's called swan-js and it doesn't use eval or new Function.
Installation:
npm install #onlabsorg/swan-js
Usage (it works both in NodeJS and in the browser):
const swan = require('#onlabsorg/swan-js');
const evaluate = swan.parse("param1 == param2 | param3 < param1");
const context = swan.createContext({param1:1, param2:2, param3:3});
const value = await evaluate(context); // false
I'm trying to alert any JavaScript object as a string, in a function. This means if the parameter given to the function is window.document, the actual object, it should alert "window.document" (without quotes) as a literal string.
The following calls...
example(window);
example(window.document);
example(document.getElementById('something'));
...calling this function...
function example(o) {/* A little help here please? */}
...should output the following strings...
window
window.document
document.getElementById('something')
I've attempted to do this with combinations of toString() and eval() among some more miscellaneous shots in the dark without success.
No need insane backwards compatibility, newer ECMAScript / JavaScript features/functions are fine. Feel free to inquire for clarifications though the goal should be pretty straight forward.
This is not possible to do in a self contained script.
If using a preprocessor would be an option, then you could write one which converts example(whatever) into example('whatever'). Other than that I'm afraid you're out of luck.
The first problem is that objects don't have names.
The second problem is that from your examples, you're not really wanting to print the (nonexistent) name of an object, you want to print the expression that evaluated into a reference to an object. That's what you're trying to do in this example:
example(document.getElementById('something'));
For that to print document.getElementById('something'), JavaScript would have had to keep the actual text of that expression somewhere that it would make available to you. But it doesn't do that. It merely evaluates the parsed and compiled expression without reference to the original text of the expression.
If you were willing to quote the argument to example(), then of course it would be trivial:
example( "document.getElementById('something')" );
Obviously in this case you could either print the string directly, or eval() it to get the result of the expression.
OTOH, if you want to try a real hack, here's a trick you could use in some very limited circumstances:
function example( value ) {
var code = arguments.callee.caller.toString();
var match = code.match( /example\s*\(\s*(.*)\s*\)/ );
console.log( match && match[1] );
}
function test() {
var a = (1);
example( document.getElementById('body') );
var b = (2);
}
test();
This will print what you wanted:
document.getElementById('body')
(The assignments to a and b in the test() function are just there to verify that the regular expression isn't picking up too much code.)
But this will fail if there's more than one call to example() in the calling function, or if that call is split across more than one line. Also, arguments.callee.caller has been deprecated for some time but is still supported by most browsers as long as you're not in strict mode. I suppose this hack could be useful for some kind of debugging purposes though.
Don't know why you need this, but you can try walking the object tree recursively and compare its nodes with your argument:
function objectName(x) {
function search(x, context, path) {
if(x === context)
return path;
if(typeof context != "object" || seen.indexOf(context) >= 0)
return;
seen.push(context);
for(var p in context) {
var q = search(x, context[p], path + "." + p);
if(q)
return q;
}
}
var seen = [];
return search(x, window, "window");
}
Example:
console.log(objectName(document.body))
prints for me
window.document.activeElement