path.scope.isGlobal should be true for identifiers in arrow functions
GianlucaGuarini opened this issue · comments
The path.scope.isGlobal
property should be true
for identifiers located in arrow functions since the scope doesn't change:
const recast = require('recast')
const types = recast.types
const assert = require('assert')
const code = '(bar) => foo'
const ast = recast.parse(code)
types.visit(ast, {
visitIdentifier(path) {
assert(path.scope.isGlobal, true)
this.traverse(path)
}
})
console.log(ast)
Notice also that in case of functions with arguments the isGlobal
property should be only true if the identifier wasn't previously defined as function argument
// the foo argument should return isGlobal=true
// the bar identifier should return isGlobal=true
// the foo in the function body was defined as argument so it should return isGlobal=false
(foo) => bar + foo
@GianlucaGuarini I don't think this is correct. An arrow function does establish a new lexical scope; it just doesn't shadow the value bound to this
or arguments
on the enclosing scope, which I think has confused many people into thinking it doesn't introduce a new lexical scope. The behavior of path.scope.isGlobal
is correct.
The following code demonstrates that an arrow function establishes a new lexical scope, unlike a loop:
var foo = 'outer';
(() => {
var foo = 'inner'; // doesn't overwrite foo on global scope
})();
console.log(foo); // 'outer'
var bar = 'outer';
do {
var bar = 'inner'; // overwrites bar on global scope
} while (false);
console.log(bar); // 'inner'
@jedwards1211 that's fair enough thanks for the detailed explanation. Is there a way at this point to detect whether an identifier might come from the global scope?
Yes, Scope.lookup(name)
returns the scope that actually declares name
, so the following should work:
function isGlobal(path, name) {
const scope = path.scope.lookup(name)
if (!scope) throw new Error(`identifier not found: ${name}`)
return scope.isGlobal
}
This won't work for built-in globals like require
, setTimeout
, etc. though, since the AST only has information about explicitly declared identifiers. So you may want to check against a list of built-in global identifiers as well.
path.scope
is just the nearest enclosing scope. So at the path for bar
in (foo) => foo + bar
, path.scope
is the scope of the ArrowFunctionExpression
, but path.scope.lookup('bar')
would get you the outer enclosing scope in which bar
is declared.
Ok that should be enough. Thank you so much for your help and for your time