benjamn / ast-types

Esprima-compatible implementation of the Mozilla JS Parser API

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

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)

demo

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