Reputation: 100010
This code works:
it.cb(h => {
console.log(h);
h.ctn();
});
it.cb(new Function(
'h', [
'console.log(h)',
'h.ctn()'
]
.join(';')
));
these two test cases are basically identical. But constructing a string with array like that is cumbersome, and you can't get static analysis. So what I was thinking of doing was something like this:
it.cb(isolated(h => {
console.log(h);
h.ctn();
}));
where isolated is a helper function that looks something like:
const isolated = function(fn){
const str = fn.toString();
const paramNames = getParamNames(str);
return new Function(...paramNames.concat(str));
};
the biggest problem is that Function.prototype.toString()
gives you the whole function. Does anyone know of a good way to just get the function body from the string representation of the function?
Update: PRoberts was asking what the purpose of this is, the purpose is simply:
const foo = 3;
it.cb(isolated(h => {
console.log(foo); // this will throw "ReferenceError: foo is not defined"
h.ctn();
}));
Upvotes: 2
Views: 748
Reputation: 51876
I wrote a version of isolated()
that handles any non-bind
ed user-defined function expression and throws custom errors for scoped accesses:
function isolated (fn) {
return new Function(`
with (new Proxy({}, {
has () { return true; },
get (target, property) {
if (typeof property !== 'string') return target[property];
throw new ReferenceError(property + ' accessed from isolated scope');
},
set (target, property) {
throw new ReferenceError(property + ' accessed from isolated scope');
}
})) return ${Function.prototype.toString.call(fn)}
`).call(new Proxy(function () {}, new Proxy({}, {
get() { throw new ReferenceError('this accessed from isolated scope'); }
})));
}
// test functions
[
() => arguments, // fail
() => this, // pass, no way to intercept this
() => this.foo, // fail
() => this.foo = 'bar', // fail
() => this(), // fail
() => new this, // fail
h => h, // pass
h => i, // fail
(a, b) => b > a ? b : a, // pass
].forEach(fn => {
const isolate = isolated(fn);
console.log(isolate.toString());
try {
isolate();
console.log('passed');
} catch (error) {
console.log(`${error.name}: ${error.message}`);
}
})
This implementation is somewhat simpler, and therefore much less error-prone than attempting to parse the parameters and body of a user-defined function.
The with
statement is a relatively simplistic means of catching any scoped references within the forcibly isolated function and throwing a ReferenceError
. It does so by inserting a Proxy
intermediate into the scope with a get trap that intercepts the scoped variable name that was accessed.
The Proxy
that is passed as the context of the function was the only part that was a bit tricky to implement, and also incomplete. It was necessary because the Proxy
provided as the scope to the with
statement does not intercept accesses to the this
keyword, so the context must also be wrapped explicitly in order to intercept and throw on any indirect usage of this
inside an isolated arrow function.
Upvotes: 2
Reputation: 100010
Alright this works, that wasn't too hard. We just assume the first and last parens are the outline of function body.
const isolated = function(fn){
const str = fn.toString();
const first = str.indexOf('{') + 1;
const last = str.lastIndexOf('}');
const body = str.substr(first, last-first);
const paramNames = ['h'];
return new Function(...paramNames.concat(body));
};
above we assume the only argument is called "h", but you will need to find function arguments parser. I have used require('function-arguments')
in the past.
Upvotes: 0
Reputation: 20885
I would simply use indexOf('{')
and lastIndexOf('}')
.
const yourFunction = h => {
console.log(h);
h.ctn();
};
const fnText = yourFunction.toString();
const body = fnText.substring(fnText.indexOf('{') + 1, fnText.lastIndexOf('}'));
console.log(body);
Knowing that this will not cover arrow functions without a body:
const fn = k => k + 1
Upvotes: 1