Skip to content

Scopes and variable shadowing #37

@hbenl

Description

@hbenl

The env proposal would add information about scopes and bindings in the original source to a sourcemap. That proposal satisfies all the scenarios listed here but there is one scenario it doesn't support: variable shadowing, i.e. a variable declared in an outer scope that is shadowed by another variable with the same name in an inner scope. This scenario is quite common because javascript minifiers reuse variable names aggressively.

To recap: The env proposal would add an env field encoding the scopes and bindings:

  • for each scope, its start and end locations, type (function or block scope), name (in case of a function scope), bindings and child scopes are encoded
  • for each binding, its type, original variable name and an expression that can be evaluated by the debugger to get the current value are encoded; in most cases, this expression is simply the name of the generated variable that corresponds to the original variable but in some cases (e.g. when variables are inlined or with named imports where the transpiled code may only contain a variable for the imported module but not for the named import), there is no generated variable corresponding to the original variable

Imagine that this original code

function outer() {
  function inner(value) {
    const value_plus_one = value + 1;
    console.log(value_plus_one);
  }
  const num = 1;
  const num_plus_one = num + 1;
  inner(num_plus_one);
}
outer();

was transpiled to

function outer() {
  function inner(a) {
    const b = a + 1;
    console.log(b);
  }
  const a = 1;
  const b = num + 1;
  inner(b);
}
outer();

The scope information encoded according to the env proposal would contain the following binding information (I'm omitting the bindings for the functions here):

  • for the inner scope: value => a, value_plus_one => b
  • for the outer scope: num => a, num_plus_one => b

When the debugger is paused at console.log(b);, the scopes would be:

  • inner function scope: a => 2, b => 3
  • outer function scope: a => 1, b => 2

Now the debugger tries to reconstruct the original scopes: for num_plus_one in the outer function scope it would evaluate b and get 3 but the correct value is 2.
To get the correct value, the debugger would need to understand that it should take the value of b in the outer function scope.
In this example it seems rather obvious from which scope the value of b should be taken but in general, with deeper nesting of scopes and the transpiler adding and/or removing scopes there's no way for the debugger to know which generated scope it should use.
This could be fixed by adding information about the generated scopes to the sourcemap (in addition to the information about original scopes): to find the value for a variable in a given original scope the debugger would translate the start and end locations of that scope to generated locations and then find the innermost generated scope that contains those locations. It would then evaluate the expression from the binding information in that scope. Note, however, that debuggers usually don't support evaluating in an outer scope of the one that execution is currently paused in, so the debugger would have to "emulate" this evaluation. This would probably only be feasible for very simple expressions, e.g. x, obj.prop and arr[idx], but this would cover a lot of ground already and go beyond the current capabilities of javascript debuggers I've worked with (in the best case they support what can be done by only supporting variable names as binding expressions).
Note that the information about generated scopes would only need to contain the start and end locations of the scopes, but not their type, name and bindings (although I would optionally keep the type information because I imagine it could be useful).

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions