Open nicolo-ribaudo opened 1 week ago
Intuitively I would have said we should emit a generated range for _loop
with no definition
. That is it does not point to any authored scope back. This is not implemented yet but I can give it a go and see if such an approach is sufficient.
I agree with @szuend, that's a generated range without an original scope. This should already be implemented in my beta scopes
branch.
I didn't implement anything yet but just thinking out loud. I modified the example a bit so we have the following input:
function run(x) {
x();
}
function foo() {
for (let x of [1, 2, 3]) {
console.trace(x);
run(() => x);
}
}
foo();
And Babel generates:
function run(x) {
x();
}
function foo() {
var _loop = function _loop() {
var x = _arr[_i];
console.trace(x);
run(function () {
return x;
});
};
for (var _i = 0, _arr = [1, 2, 3]; _i < _arr.length; _i++) {
_loop();
}
}
foo();
Now I would expect the following generated ranges (among others): One for foo
, that points back the the original foo
function scope. A generated range for
var x = _arr[_i];
console.trace(x);
run(function () {
return x;
});
that points back to the original block scope for the for
loop. And a generated range for the _loop();
call site (or the whole generated for
loop body) with no original scope definition.
The stack trace for pausing on the console.trace
line would be:
_loop (script.js 7:5)
foo (script.js 13:5)
<anonymous> (script.js 17:1)
For _loop (script.js 7:5)
we are inside a generated range with a definition. So we look at that original block scope and follow the scope chain outwards to the original function scope (foo
). Using the name of that original function scope and standard "mappings" we would translate _loop (script.js 7:5)
to foo (original.js 7:5)
because the console.trace call is actually on the same position in both original and authored.
foo (script.js 13:5)
is inside a generated range with no definition so we drop the stack frame.
<anonymous> (script.js 17:1)
would probably be in a generated range that maps to the script/global scope so we keep and map it.
The resulting stack trace would be:
foo (original.js 7:5)
<anonymous> (origianl.js 12:1)
Note that this approach uses the current proposal only. It would require generators to hide functions from stack traces by "masking" their call-sites with generated ranges that have no definition.
Second note: Emitting a generated range for the whole var _loop = function _loop() ...
with no definition doesn't help us since you actually want to hide calls to this function not the actual function body as that corresponds to actual authored code.
Hope all of this makes sense. Please keep more examples coming, this is a great way to think through if the proposal actually works for the various transformations babel and other generators are doing.
Alternative implementation:
If we add a GeneratedRange.isFunctionScope
flag (or rename the existing isScope
flag, then we can also implement this differently without generators having to "mask" call-sites to hidden functions.
Then processing a stack trace would look something like this:
range.isFunctionScope && !range.hasDefinition
is true for the found generated range.For the above example this would mean: Generators would emit a range for var _loop = function _loop() ...
with no definition and isFunctionScope: true
. When we process the second frame foo (script.js 13:5)
we look at the previous frame _loop (script.js 7:5)
and find the closest isFunctionScope: true
generated range from 7:5 going outwards. We find one and it doesn't have a definition
so we can omit the second frame from the stack trace.
Why a isHidden
flag won't work
Just annotating generated ranges with isHidden
, is not enough. There could be multiple generated ranges in the chain that correspond to JS functions, but we have no way to tell. For example:
function foo() {
// ...
function bar() {
fnThatThrows();
}
}
bar
should be visible but foo
should be hidden. With just an isHidden
flag this won't since we would find foo
as a hidden range when doing the check for the fnThatThrows
call-site in bar
.
While we focused a lot on function inlining, sometimes Babel does the opposite. For example, given this input:
Babel generates this code:
With the scopes proposal, is it possible to somehow mark
_loop
so that it does not appear in the stack when placing a breakpoint atconsole.trace(x)
?