Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added support for block scoping #455

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -437,6 +437,18 @@ contains `path.node`. See [scope.ts](lib/scope.ts) for its public
interface, which currently includes `.isGlobal`, `.getGlobalScope()`,
`.depth`, `.declares(name)`, `.lookup(name)`, and `.getBindings()`.

A note on block scoping: As of ES2015 (ES6), variable declartions using `const` or `let` are block scoped. It also is the first ECMAScript spec to mention hoisting. However hoisting does not form part of the syntax, it is a runtime behaviour. Therefore `var` declarations are also scoped by block. For example:

```javascript
function x() {
var one = 1; // in x's scope
if (true) {
const two = 2; // in the if statement's scope
var three = 3; // also in the if statement scope, but it's binding is hoisted and preinitialized at runtime
}
}
```

Custom AST Node Types
---

Expand Down
9 changes: 8 additions & 1 deletion lib/scope.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,14 @@ export default function scopePlugin(fork: Fork) {

// In case you didn't know, the caught parameter shadows any variable
// of the same name in an outer scope.
namedTypes.CatchClause
namedTypes.CatchClause,

// The following are statements that create block scopes
namedTypes.IfStatement,
namedTypes.ForStatement,
namedTypes.ForInStatement,
namedTypes.ForOfStatement,
namedTypes.TryStatement,
];

var ScopeType = Type.or.apply(Type, scopeTypes);
Expand Down
103 changes: 103 additions & 0 deletions test/ecmascript.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import {
visit,
defineMethod,
astNodesAreEquivalent,
Visitor,
} from "../main";
import {
babylonParse,
Expand Down Expand Up @@ -1313,6 +1314,108 @@ describe("scope methods", function () {
assert.strictEqual(romVarDecl.declarations[0].id.name, "$1$0");
assert.strictEqual(romVarDecl.declarations[1].id.name, "$1$1");
});

const cases: Array<[string, Array<string>, Visitor]> = [
[
"if statement",
[
" if (true) {",
" const two = 2;",
" var three = 3;",
" }",
],
{
visitIfStatement: function (path) {
const bindingNames = Object.keys(path.scope.getBindings()).sort()
assert.deepEqual(bindingNames, ['three', 'two'])
this.traverse(path)
}
},
],
[
'for statement',
[
" for (const two = 0; two < 2; two++) {",
" const three = 3;",
" var four = 4;",
" }",
],
{
visitForStatement: function (path) {
const bindingNames = Object.keys(path.scope.getBindings()).sort()
assert.deepEqual(bindingNames, ['four', 'three', 'two'])
this.traverse(path)
}
}
],
[
'for in statement',
[
" for (const two in [1,2,3]) {",
" const three = 3;",
" var four = 4;",
" }",
],
{
visitForInStatement: function(path) {
const bindingNames = Object.keys(path.scope.getBindings()).sort()
assert.deepEqual(bindingNames, ['four', 'three', 'two'])
this.traverse(path)
}
},
],
[
'for of statement',
[
" for (const two of [1,2,3]) {",
" const three = 3;",
" var four = 4;",
" }",
],
{
visitForOfStatement: function (path) {
const bindingNames = Object.keys(path.scope.getBindings()).sort()
assert.deepEqual(bindingNames, ['four', 'three', 'two'])
this.traverse(path)
}
},
],
[
'try statement',
[
" try {",
" const two = 2;",
" var three = 3;",
" } catch(e) {}",
],
{
visitTryStatement: function (path) {
const bindingNames = Object.keys(path.scope.getBindings()).sort()
assert.deepEqual(bindingNames, ['three', 'two'])
this.traverse(path)
}
},
],
];
cases.forEach(([desc, nestedScope, visitFns]) => {
it("getBindings - block scope - " + desc, () => {
var scope = [
"function x() {",
" const one = 1;",
...nestedScope,
"}"
];
var ast = parse(scope.join("\n"));
visit(ast, {
visitFunctionDeclaration: function(path) {
const bindingNames = Object.keys(path.scope.getBindings()).sort()
assert.deepEqual(bindingNames, ['one'])
this.traverse(path)
},
...visitFns,
})
})
})
});

describe("catch block scope", function() {
Expand Down