ecma-variable-scope v2.1.0
ecma-variable-scope

AST utility to collect scope info for variables
Scope detection is hard, especially when with exists. This utility extracts all relevant info for making decisions. This project was built as part of esformatter-phonetic, a esformatter plugin that makes obfuscated variable names more comprehensible.
Features:
- Detect
withusage - Does not mark
labels - Support for
letandconst - Support for destructured variables (e.g.
var {hello, world} = obj;) - Support for arrow expressions (e.g.
(hello) => hello.world)
Getting Started
Install the module with: npm install ecma-variable-scope
// Gather an AST to analyze
var esprima = require('esprima');
var ecmaVariableScope = require('ecma-variable-scope');
var ast = esprima.parse([
'function logger(str) {',
'console.log(str);',
'}'
].join('\n'));
// Determine the scope of a variable
ecmaVariableScope(ast);
ast.body[0].id;
/*
// `logger` variable
scope:
{ type: 'lexical',
node: Program,
parent: undefined,
identifiers: { logger: [Circular] },
children: [ [Object] ] },
scopeInfo:
{ insideWith: false,
topLevel: true,
type: 'lexical',
usedInAWith: false }
*/
ast.body[0].body.body[0].expression.callee.object;
/*
// `console` variable
scope: undefined,
scopeInfo:
{ insideWith: false,
topLevel: true,
type: 'undeclared',
usedInAWith: false }
*/Documentation
ecma-variable-scope exports ecmaVariableScope as its module.exports.
ecmaVariableScope(ast)
Walk an abstract syntax tree and add scope and scopeInfo properties to appropriate nodes.
- ast
Object- Abstract syntax tree to walk over/mutate- We have developed against
esprimawhich matches the Spidermonkey API
- We have developed against
Returns:
We return the same ast variable but with the addition of scope and scopeInfo nodes on Identifiers that are variables.
// `scope` and `scopeInfo` will be defined for all `hello`, `world`, and `moon` references
function hello(world) {
var moon;
hello(world, moon);
}
// `log` is an `Identifier` but will not have `scope` and `scopeInfo`
console.log('hello');scope
Object containing information about the outermost scope a variable can be accessed from:
- type
String- Variant of scope that a variable is in- This can be
lexicalorblock. When we traversescope.parent, we can run intowithbut this is not directly found fromnode.scope. - For example in
function a() { var b; }, we havetype: 'lexical'forb's scope. - We make these values available via
exports.SCOPE_TYPES.LEXICAL('lexical'),exports.SCOPE_TYPES.WITH('with'), andexports.SCOPE_TYPES.BLOCK('block').
- This can be
- node
Object<Node>- AST node that corresponds to the top of scope- For example in
function a() { var b; }, we haveb.scope.node === a
- For example in
- parent
Object<Scope>|undefined- Next scope containing the currentscope. This can be any other type (e.g.lexical,block,with). - identifiers
Object- Map from identifier name toIdentifierreference of variables declared within this scope- For example in
function a() { var b; }, we havea.scope.identifiers === {b: b's Identifier} - This will not contain identifiers within child scopes
- For example in
- children
Scope[]- Array of child scopes contained by this scope- For example in
function a() { },Program's scopewill containa.scope
- For example in
It is possible for an Identifier to have scopeInfo but not scope. For example, console is defined as a global outside of a script context. We cannot determine if it is defined or not and make the decision to leave it as undefined.
scopeInfo
Object containing information about the variable itself:
- insideWith
Boolean- Indicator of whether a variable has awithbetween its declaration and its containing scopetrueif there was awith,falseif there was not one- For example in
function a() { with (window) { document(); } }, we havea.insideWith === falseanddocument.insideWith === true. - We provide
exports.SCOPE_INFO_INSIDE_WITH.YES(true)andexports.SCOPE_INFO_INSIDE_WITH.NO(false).
- toplevel
Boolean- Indicator of whether a variable was declared in theProgramscope or nottrueif it was,falseif it was not- For example in
function a() { var b; }, we havea.scopeInfo.topLevel === trueandb.scopeInfo.topLevel === false. - We provide
exports.SCOPE_INFO_TOP_LEVEL.YES(true) andexports.SCOPE_INFO_TOP_LEVEL.NO(false).
- type
String- Reference to the type of scope given to a variable- This can vary from the containing scope (e.g. a
letis scoped to afunction;blocktolexical) - This can be
lexical(e.g.function),block(e.g.forloop), orundeclared(not declared in any containing scope) - For example in
function a() { let b; }, we havea.scopeInfo.type === 'lexical'andb.scopeInfo.type === 'block'. - We provide
exports.SCOPE_INFO_TYPES.LEXICAL('lexical'),exports.SCOPE_INFO_TYPES.BLOCK('block'), andexports.SCOPE_INFO_TYPES.UNDECLARED('undeclared').
- This can vary from the containing scope (e.g. a
- usedInAWith
Boolean- Indicator of whether a variable has ever been referenced inside awithin its entire scopetrueif it has been,falseif it has not- For example in
var hello; var obj = {}; with (obj) { hello; } }, we havehello1.usedInAWith === trueandobj1.usedInAWith === false. - We provide
exports.SCOPE_INFO_USED_IN_A_WITH.YES(true)andexports.SCOPE_INFO_USED_IN_A_WITH.NO(false).
If you would like to determine if a variable can be renamed without causing other problems, use
usedInAWith(false),topLevel(false), andtype(lexical/block).
Unstable
There are a few extra properties that are thrown in for preparation of scope and scopeInfo. They could be replaced with a better algorithm but are there if you need them. If you are using them, please let us know via an issue.
_nearestScope- Present on every node and points to the closest scope of any type up its parents. This is useful for jumping throughblockscopes until reaching alexicalone._scopeType- Stored on initial declarations of identifiers. This is the same asscopeInfo.typebut needs to be preserved beforescopeInfois generated.
Contributing
In lieu of a formal styleguide, take care to maintain the existing coding style. Add unit tests for any new or changed functionality. Lint via grunt and test via npm test.
Donating
Support this project and others by twolfson via gratipay.
Unlicense
As of Nov 04 2014, Todd Wolfson has released this repository and its contents to the public domain.
It has been released under the UNLICENSE.
