Scope
contains information about the scope, like all the declared variables (bindings),
references of a specific variable, labels etc.
By default, scope tracking is disabled. After enabling it using options,
it would be available in NodePath.scope
.
const { traverse } = require('estree-toolkit');
traverse(ast, {
$: { scope: true },
Program(path) {
path.scope // This is our `Scope`
}
});
path
NodePath
The path the current scope is associated with.
parent
Scope | null
The parent of the current scope, if there is any. If there is no parent scope
then it would be null
.
children
Scope[]
An array containing all the direct descendent scopes of the current scope.
bindings
Record<string, Binding>
All the bindings defined in the current scope. Contains information about the bindings.
globalBindings
Record<string, GlobalBinding>
All unresolved references in the scope would be treated as a global variable, and they would be mapped to this object.
labels
Record<string, Label>
All labels that are defined in the current scope.
getProgramScope()
Scope
> The program scopeReturns the Scope
associated with the Program
node of the AST.
const ast = parseModule(`x`);
let programScope;
traverse(ast, {
$: { scope: true },
Program(path) {
programScope = path.scope;
},
Identifier(path) {
path.scope.getProgramScope() === programScope // => true
}
});
crawl()
Crawl the tree and create all the scope information. Whenever you make a change to the AST
(like defining a new variable, introducing a new global binding etc.), the Scope
would not reflect the
changes. You would have to run the crawl()
function manually in order to update the scope information.
It's recommended that you run this function on Scope
associated with the Program
node.
traverse(ast, {
$: { scope: true },
Program(path) {
path.scope.crawl();
}
})
Note that this function recreates all the information, so there is some overhead. Don't overdo it, or it will impact on your app's performance.
hasOwnBinding(name)
name
: <string
> The name of the bindingboolean
> If the binding existsChecks if the binding exists in the current scope. Unlike hasBinding(name)
it doesn't check the parent scopes.
const ast = parseModule(`
let x;
{
let y;
target;
}
`);
traverse(ast, {
$: { scope: true },
Identifier(path) {
if (path.node.name === 'target') {
path.scope.hasOwnBinding('x') // => false
path.scope.hasOwnBinding('y') // => true
}
}
});
getOwnBinding(name)
name
: <string
> The name of the bindingBinding
> The binding with the provided nameChecks the current scope for the binding. If found, returns the binding information.
Returns undefined
if no binding can be found.
const ast = parseModule(`
let x;
{
let y;
target;
}
`);
traverse(ast, {
$: { scope: true },
Identifier(path) {
if (path.node.name === 'target') {
path.scope.getOwnBinding('x') // => undefined
path.scope.getOwnBinding('y') // => Binding { ... }
}
}
});
hasBinding(name)
name
: <string
> The name of the bindingboolean
> If the binding existsChecks if the binding exists in the current scope or parent scopes.
const ast = parseModule(`
let x;
{
let y;
target;
}
`);
traverse(ast, {
$: { scope: true },
Identifier(path) {
if (path.node.name === 'target') {
path.scope.hasBinding('x') // => true
path.scope.hasBinding('y') // => true
}
}
});
getBinding(name)
name
: <string
> The name of the bindingBindings
> The binding with the provided nameChecks the current scope and parent scopes for the binding. If found, returns the
binding information. Returns null
if no binding can be found.
const ast = parseModule(`
let x;
{
let y;
target;
}
`);
traverse(ast, {
$: { scope: true },
Identifier(path) {
if (path.node.name === 'target') {
path.scope.getBinding('x') // => Binding { ... }
path.scope.getBinding('y') // => Binding { ... }
}
}
});
hasGlobalBinding(name)
name
: <string
> The name of the global bindingboolean
> If the global binding existsChecks if the global binding exists in the scope.
const ast = parseModule(`
const module = require('mod');
window.location.reload();
`);
traverse(ast, {
$: { scope: true },
Program(path) {
path.scope.hasGlobalBinding('module') // => false
path.scope.hasGlobalBinding('require') // => true
path.scope.hasGlobalBinding('window') // => true
}
});
getGlobalBinding(name)
name
: <string
> The name of the global bindingGlobalBinding
> The global binding with the provided nameReturns the global binding with the provided name. Returns undefined
if no
binding can be found.
hasLabel(name)
name
: <string
> The name of the labelboolean
> If the label existsChecks if the label exists in the scope.
const ast = parseModule(`
loop: for (let i = 0; i < 1000; i++) {
for (let j = 0; j < 1000; j++) {
if (i === 500 && j === 200) {
target;
break loop;
}
}
}
`);
traverse(ast, {
$: { scope: true },
Identifier(path) {
if (path.node.name === 'target') {
path.scope.hasLabel('loop') // => true
}
}
});
getLabel(name)
name
: <string
> The name of the labelLabel
> The label with the provided nameReturns the label with the provided name. Returns undefined
if no label can be
found.
getAllBindings(...kind)
kind
: <string[]
> The kind of binding, that it will collectRecord<string, Binding>
> A record of bindingsReturns all the bindings available in a scope including its parents' bindings. Maintains variable shadowing.
const ast = parseModule(`
const a = 0;
let b = 0;
var c = 0;
function Fn(param_1, {param_2}) {
const e = 0;
let f = 0;
var g = 0;
target;
}
class Cls {}
`);
traverse(ast, {
$: { scope: true },
Identifier(path) {
if (path.node.name === 'target') {
// Get all kind of binding
path.scope.getAllBindings()
// =>
// {
// a: Binding {...},
// b: Binding {...},
// c: Binding {...},
// Fn: Binding {...},
// param_1: Binding {...},
// param_2: Binding {...},
// e: Binding {...},
// f: Binding {...},
// g: Binding {...},
// Cls: Binding {...},
// }
// Get only binding with type `const` and `hoisted`
path.scope.getAllBindings('const', 'hoisted')
// =>
// {
// a: Binding {...},
// e: Binding {...},
// Fn: Binding {...},
// Cls: Binding {...},
// }
}
}
});
generateUid(name?)
name
: <string
> The preferable name of the UIDstring
> A unique idThis function generates an unique ID that you can use as a variable's name. Makes sure that the unique ID does not clashes with other pre-existing IDs (binding names).
const ast = parseModule(`
const myID = 5
`)
traverse(ast, {
$: { scope: true },
Program(path) {
path.scope.generateUid() // => _tmp, The `name` defaults to `_tmp`
path.scope.generateUid('myID') // => myID1, because `myID` already exists
path.scope.generateUid('thatID') // => thatID, because `thatID` does not exist
}
})
generateUidIdentifier(name?)
name
: <string
> Name of the IDIdentifier
> An identifier with its name
set to the IDWraps the value of generateUid(name?)
in an identifier node and returns the node
const ast = parseModule(`
const a = 0
`)
traverse(ast, {
$: { scope: true },
Program(path) {
path.scope.generateUidIdentifier('some_id')
// => { type: 'Identifier', name: 'some_id' }
path.scope.generateUidIdentifier('some_id')
// => { type: 'Identifier', name: 'some_id1' }
// because the ID `some_id` has been used in the
// first generation
}
})
generateDeclaredUidIdentifier(name?)
name
: <string
> Name of the IDIdentifier
> Identifier with the unique IDGenerates an identifier with unique ID and declares the ID as a new variable in the containing scope.
const ast = parseModule(`
const a = 0
`)
traverse(ast, {
$: { scope: true },
Program(path) {
path.scope.generateDeclaredUidIdentifier('thing')
// => { type: 'Identifier', name: 'thing' }
path.scope.generateDeclaredUidIdentifier('thing')
// => { type: 'Identifier', name: 'thing1' }
}
})
// Now if you generate code from the AST
// you would get this
// var thing, thing1;
// const a = 0;
// as you can see the new IDs are declared as variable
renameBinding(oldName, newName)
oldName
: <string
> The name of the binding which should be replacednewName
: <string
> New name of the bindingRenames all occurrence of the given binding. Covers all the possible cases.
const ast = parseModule(`
let { a } = global;
f(a);
({a} = newValue);
const x = ({ d, y: [f] } = a) => {
target;
}
`)
traverse(ast, {
$: { scope: true },
Identifier(path) {
if (path.node.name === 'target') {
// Rename `a` to `newA`
path.scope.renameBinding('a', 'newA')
// Rename `d` to `nD`
path.scope.renameBinding('d', 'nD')
}
}
})
// Now if you generate code from the AST
// you would get this
// let { a: newA } = global;
//
// f(newA);
//
// ({ a: newA } = newValue);
//
// const x = ({ d: nD, y: [f] } = newA) => {
// target;
// }